Skip to content

Commit

Permalink
Fold conditional query for IF/WHILE statements
Browse files Browse the repository at this point in the history
Remove unnecessary table spool for EXISTS
  • Loading branch information
MarkMpn committed Apr 6, 2022
1 parent d3dcb8e commit 5c3134c
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 16 deletions.
42 changes: 42 additions & 0 deletions MarkMpn.Sql4Cds.Engine.Tests/ExecutionPlanTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4212,5 +4212,47 @@ public void InsertParameters()
var compute = AssertNode<ComputeScalarNode>(insert.Source);
var constant = AssertNode<ConstantScanNode>(compute.Source);
}

[TestMethod]
public void NotExistsParameters()
{
var metadata = new AttributeMetadataCache(_service);
var planBuilder = new ExecutionPlanBuilder(metadata, new StubTableSizeCache(), this);

var query = @"DECLARE @firstname AS VARCHAR (100) = 'Mark', @lastname AS VARCHAR (100) = 'Carrington';
IF NOT EXISTS (SELECT * FROM contact WHERE firstname = @firstname AND lastname = @lastname)
BEGIN
INSERT INTO contact (firstname, lastname)
VALUES (@firstname, @lastname);
END";

var plans = planBuilder.Build(query, null, out _);

Assert.AreEqual(4, plans.Length);

AssertNode<DeclareVariablesNode>(plans[0]);
AssertNode<AssignVariablesNode>(plans[1]);
AssertNode<AssignVariablesNode>(plans[2]);
var cond = AssertNode<ConditionalNode>(plans[3]);
var compute = AssertNode<ComputeScalarNode>(cond.Source);
var loop = AssertNode<NestedLoopNode>(compute.Source);
var constant = AssertNode<ConstantScanNode>(loop.LeftSource);
var fetch = AssertNode<FetchXmlScan>(loop.RightSource);
var insert = AssertNode<InsertNode>(cond.TrueStatements[0]);
var insertCompute = AssertNode<ComputeScalarNode>(insert.Source);
var insertConstant = AssertNode<ConstantScanNode>(insertCompute.Source);

AssertFetchXml(fetch, @"
<fetch xmlns:generator='MarkMpn.SQL4CDS' top='1'>
<entity name='contact'>
<attribute name='contactid' />
<filter>
<condition attribute='firstname' operator='eq' value='@firstname' generator:IsVariable='true' />
<condition attribute='lastname' operator='eq' value='@lastname' generator:IsVariable='true' />
</filter>
</entity>
</fetch>");
}
}
}
30 changes: 14 additions & 16 deletions MarkMpn.Sql4Cds.Engine/ExecutionPlan/ConditionalNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,24 +69,24 @@ public string Execute(IDictionary<string, DataSource> dataSources, IQueryExecuti

public IRootExecutionPlanNodeInternal[] FoldQuery(IDictionary<string, DataSource> dataSources, IQueryExecutionOptions options, IDictionary<string, DataTypeReference> parameterTypes, IList<OptimizerHint> hints)
{
if (hints != null && hints.OfType<DoNotCompileConditionsHint>().Any())
{
TrueStatements = TrueStatements
.SelectMany(s => s.FoldQuery(dataSources, options, parameterTypes, hints))
.ToArray();
TrueLabel = Guid.NewGuid().ToString();
FalseLabel = Guid.NewGuid().ToString();

FalseStatements = FalseStatements
?.SelectMany(s => s.FoldQuery(dataSources, options, parameterTypes, hints))
?.ToArray();
Source = Source?.FoldQuery(dataSources, options, parameterTypes, hints);

TrueStatements = TrueStatements
.SelectMany(s => s.FoldQuery(dataSources, options, parameterTypes, hints))
.ToArray();

FalseStatements = FalseStatements
?.SelectMany(s => s.FoldQuery(dataSources, options, parameterTypes, hints))
?.ToArray();

if (hints != null && hints.OfType<DoNotCompileConditionsHint>().Any())
return new[] { this };
}

var statements = new List<IRootExecutionPlanNodeInternal>();

TrueLabel = Guid.NewGuid().ToString();
FalseLabel = Guid.NewGuid().ToString();

statements.AddRange(
new GoToNode
{
Expand All @@ -104,8 +104,7 @@ public IRootExecutionPlanNodeInternal[] FoldQuery(IDictionary<string, DataSource

statements.Add(new GotoLabelNode { Label = TrueLabel });

foreach (var stmt in TrueStatements)
statements.AddRange(stmt.FoldQuery(dataSources, options, parameterTypes, hints));
statements.AddRange(TrueStatements);

if (FalseStatements == null)
{
Expand Down Expand Up @@ -134,8 +133,7 @@ public IRootExecutionPlanNodeInternal[] FoldQuery(IDictionary<string, DataSource

statements.Add(new GotoLabelNode { Label = FalseLabel });

foreach (var stmt in FalseStatements)
statements.AddRange(stmt.FoldQuery(dataSources, options, parameterTypes, hints));
statements.AddRange(FalseStatements);

statements.Add(new GotoLabelNode { Label = endLabel });
}
Expand Down
11 changes: 11 additions & 0 deletions MarkMpn.Sql4Cds.Engine/ExecutionPlan/NestedLoopNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,17 @@ public override IDataExecutionPlanNodeInternal FoldQuery(IDictionary<string, Dat
var innerParameterTypes = GetInnerParameterTypes(leftSchema, parameterTypes);
RightSource = RightSource.FoldQuery(dataSources, options, innerParameterTypes, hints);
RightSource.Parent = this;

if (RightSource is TableSpoolNode spool)
{
LeftSource.EstimateRowsOut(dataSources, options, parameterTypes);
if (LeftSource.EstimatedRowsOut <= 1)
{
RightSource = spool.Source;
RightSource.Parent = this;
}
}

return this;
}

Expand Down

0 comments on commit 5c3134c

Please sign in to comment.