Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ A query engine is software that interprets a query (Structured Query Language) a
- [X] Aggregations - `min`/`max`/`stddev`/etc...
- [X] Math opperations
- [X] `CAST` functions
- [X] `EXPLAIN` plans
- [ ] `EXCLUDE`/`EXCEPT` - Not yet implemented
- [ ] `EXPLAIN` - Not yet implemented
- [ ] `INSERT`, `UPDATE`, `DROP` operations

## Motivation
Expand Down
2 changes: 2 additions & 0 deletions src/Engine/Prequel/Execution/ExecutionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Prequel.Metrics;
using Prequel.Data;
using Prequel.Logical;
using Prequel.Logical.Plans;

namespace Prequel.Execution;

Expand Down Expand Up @@ -76,6 +77,7 @@ internal ILogicalPlan BuildLogicalPlan(string sql)
var plan = ast.First() switch
{
Statement.Select select => LogicalExtensions.CreateQuery(select.Query, new PlannerContext(_tables)),
Statement.Explain explain => new Explain(LogicalExtensions.CreateQuery(explain.Statement.AsSelect(), new PlannerContext(_tables))),
_ => throw new NotImplementedException()
};

Expand Down
25 changes: 25 additions & 0 deletions src/Engine/Prequel/Execution/ExplainExecution.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Runtime.CompilerServices;
using Prequel.Data;
using Prequel.Logical;
using Prequel.Logical.Plans;

namespace Prequel.Execution;

internal class ExplainExecution(Explain explain) : IExecutionPlan
{
public Schema Schema => explain.Schema;

public async IAsyncEnumerable<RecordBatch> ExecuteAsync(QueryContext queryContext,

Check warning on line 12 in src/Engine/Prequel/Execution/ExplainExecution.cs

View workflow job for this annotation

GitHub Actions / continuous-integration / build-and-sign

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
[EnumeratorCancellation] CancellationToken cancellation = default)
{
var steps = explain.ToStringIndented(new Indentation()).Split(Environment.NewLine);
var batch = new RecordBatch(Schema);

foreach (var step in steps)
{
batch.AddResult(0, step);
}

yield return batch;
}
}
13 changes: 13 additions & 0 deletions src/Engine/Prequel/Logical/Plans/Explain.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Prequel.Data;

namespace Prequel.Logical.Plans;

internal class Explain(ILogicalPlan plan) : ILogicalPlan
{
public Schema Schema { get; } = new([new QualifiedField("plan", ColumnDataType.Utf8) ]);

public string ToStringIndented(Indentation? indentation = null)
{
return plan.ToStringIndented(indentation ?? new Indentation());
}
}
2 changes: 1 addition & 1 deletion src/Engine/Prequel/Logical/Plans/Filter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ public override string ToString()
public string ToStringIndented(Indentation? indentation = null)
{
var indent = indentation ?? new Indentation();
return $"Filter: {Predicate}{indent.Next(Plan)}";
return $"{this}{indent.Next(Plan)}";
}
}
2 changes: 1 addition & 1 deletion src/Engine/Prequel/Logical/Plans/Limit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ public override string ToString()
public string ToStringIndented(Indentation? indentation = null)
{
var indent = indentation ?? new Indentation();
return $"Limit: Skip {Skip}, Limit {Fetch}{indent.Next(Plan)}";
return $"{this}{indent.Next(Plan)}";
}
}
5 changes: 3 additions & 2 deletions src/Engine/Prequel/Logical/Plans/Sort.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,11 @@ private static ILogicalExpression RewriteForProjection(
});
}


public string ToStringIndented(Indentation? indentation = null)
{
var indent = indentation ?? new Indentation();
return $"Sort: {indent.Next(Plan)}";
var orders = string.Join(",", OrderByExpressions.Select(o => o.ToString()?
.Replace("Order By",string.Empty, StringComparison.InvariantCultureIgnoreCase)));
return $"Sort: {orders}{indent.Next(Plan)}";
}
}
10 changes: 4 additions & 6 deletions src/Engine/Prequel/Logical/Plans/TableScan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,10 @@ public string ToStringIndented(Indentation? indentation = null)

public override string ToString()
{
string? fields = null;
if (Projection != null)
{
fields = " projection=" + string.Join(",", Projection.Select(i => Table.Schema!.Fields[i].Name));
}
var fields = string.Join(", ", Projection != null
? Projection.Select(i => Table.Schema!.Fields[i].Name)
: Schema.Fields.Select(f => f.QualifiedName));

return $"Table Scan: {Name}{fields}";
return $"Table Scan: {Name} projection=({fields})";
}
}
2 changes: 1 addition & 1 deletion src/Engine/Prequel/Logical/Plans/Union.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public string ToStringIndented(Indentation? indentation = null)
{
var indent = indentation ?? new Indentation();

var children = string.Join("", Inputs.Select((input, index) => index == 0 ? indent.Next(input) : indent.Repeat(input)));
var children = string.Join(string.Empty, Inputs.Select((input, index) => index == 0 ? indent.Next(input) : indent.Repeat(input)));

return $"{this} {children}";
}
Expand Down
2 changes: 2 additions & 0 deletions src/Engine/Prequel/Physical/PhysicalPlanner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public IExecutionPlan CreateInitialPlan(ILogicalPlan plan)
// Distinct should have been replaced by an
// aggregate plan by this point.
Distinct => throw new InvalidOperationException("Distinct plans must be replaced with aggregations"),
Explain explain => new ExplainExecution(explain),

_ => throw new NotImplementedException("The physical plan type has not been implemented")
};
}
Expand Down
42 changes: 41 additions & 1 deletion src/Tests/Prequel.Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,14 @@ SELECT 1
"""
});

execution.AddQuery(new Query
{
Name = "explain_scalar_value",
Text = """
EXPLAIN SELECT 1
"""
});

// Slightly more complex, scalar values can be calculated using basic
// SQL-style arithmetic. The query returns a table with three columns
// with the calculated values.
Expand Down Expand Up @@ -212,7 +220,7 @@ SELECT 1
{
Name = "all_colors",
Text = """
SELECT
EXPLAIN SELECT
*
FROM colors
"""
Expand Down Expand Up @@ -327,6 +335,38 @@ LEFT JOIN departments d
"""
});

execution.AddQuery(new Query
{
Name = "explain_simple_query",
Text = """
EXPLAIN SELECT
max(c1) as max_c1, avg(c3) avg_c3
FROM colors
WHERE c1 in (1,2,3)
ORDER BY max_c1, avg_c3 desc
LIMIT 10
OFFSET 3
"""
});

execution.AddQuery(new Query
{
Name = "explain_complex_query",
Text = """
EXPLAIN SELECT
m.employee_id as ManagerId,
e.employee_id as EmpId,
m.first_name ManagerFN,
m.last_name ManagerLN,
e.first_name EmployeeFN,
e.last_name EmployeeLN
FROM employees m
JOIN employees e
ON m.employee_id = e.manager_id
WHERE e.manager_id in (100, 101)
ORDER BY e.manager_id
"""
});
#endregion

var result = await execution.ExecuteAllAsync();
Expand Down
4 changes: 2 additions & 2 deletions src/Tests/Prequel.Tests/Data/ModelTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,8 @@ public void TableScan_Overrides_ToString()
new EmptyDataTable("", schema, []),
[]);

Assert.Equal("Table Scan: table projection=", scan.ToStringIndented());
Assert.Equal("Table Scan: table projection=", scan.ToString());
Assert.Equal("Table Scan: table projection=()", scan.ToStringIndented());
Assert.Equal("Table Scan: table projection=()", scan.ToString());
}

[Fact]
Expand Down
27 changes: 27 additions & 0 deletions src/Tests/Prequel.Tests/Physical/ExecutionPlanTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Prequel.Physical.Expressions;
using Prequel.Execution;
using Prequel.Data;
using SqlParser;
using Schema = Prequel.Data.Schema;
using Exec = Prequel.Execution.ExecutionContext;

Expand Down Expand Up @@ -329,4 +330,30 @@ public void Planner_Rejects_Distinct_Plan()
{
Assert.Throws<InvalidOperationException>(() => new PhysicalPlanner().CreateInitialPlan(new Distinct(new TestPlan())));
}

[Fact]
public async Task Context_Explains_Query_Plan()
{
const string sql = "EXPLAIN SELECT a, b FROM db order by a desc offset 5 limit 10";
var explainPlan = _context.BuildLogicalPlan(sql);
var explain = (ExplainExecution)Exec.BuildPhysicalPlan(explainPlan);

var batch = await explain.ExecuteAsync(new QueryContext()).FirstAsync();

var expectedSchema = new Schema([new QualifiedField("plan", ColumnDataType.Utf8)]);
Assert.Equal(expectedSchema, batch.Schema);

Assert.Equal(4, batch.Results[0].Values.Count);
Assert.Equal("Limit: Skip 5, Limit 10", batch.Results[0].Values[0]);
Assert.Equal(" Sort: db.a Desc", batch.Results[0].Values[1]);
Assert.Equal(" Projection: db.a, db.b ", batch.Results[0].Values[2]);
Assert.Equal(" Table Scan: db projection=(db.a, db.b, db.c)", batch.Results[0].Values[3]);
}

[Fact]
public void Nested_Explain_Should_Fail()
{
const string sql = "EXPLAIN EXPLAIN SELECT 1";
Assert.Throws<ParserException>(() => _context.BuildLogicalPlan(sql));
}
}
Loading