Skip to content

Commit

Permalink
Fixed error when folding single-record joins to nested loop
Browse files Browse the repository at this point in the history
  • Loading branch information
MarkMpn committed Nov 15, 2024
1 parent d8105b1 commit 5762636
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 6 deletions.
35 changes: 31 additions & 4 deletions MarkMpn.Sql4Cds.Engine.Tests/ExecutionPlanTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4761,18 +4761,19 @@ public void MetadataLeftJoinData()
var nestedLoop = AssertNode<NestedLoopNode>(select.Source);
Assert.AreEqual(QualifiedJoinType.LeftOuter, nestedLoop.JoinType);
Assert.AreEqual(1, nestedLoop.OuterReferences.Count);
Assert.AreEqual("@Cond1", nestedLoop.OuterReferences["entity.metadataid"]);
var outerReference = nestedLoop.OuterReferences["entity.metadataid"];
Assert.IsTrue(outerReference.StartsWith("@Cond"));
var meta = AssertNode<MetadataQueryNode>(nestedLoop.LeftSource);
var fetch = AssertNode<FetchXmlScan>(nestedLoop.RightSource);
AssertFetchXml(fetch, @"
AssertFetchXml(fetch, $@"
<fetch xmlns:generator='MarkMpn.SQL4CDS'>
<entity name='account'>
<attribute name='name' />
<link-entity name='contact' alias='contact' from='contactid' to='primarycontactid' link-type='outer'>
<attribute name='firstname' />
</link-entity>
<filter>
<condition attribute='accountid' operator='eq' value='@Cond1' generator:IsVariable='true' />
<condition attribute='accountid' operator='eq' value='{outerReference}' generator:IsVariable='true' />
</filter>
</entity>
</fetch>");
Expand Down Expand Up @@ -7442,7 +7443,7 @@ public void MistypedJoinCriteriaGeneratesWarning()
var select = AssertNode<SelectNode>(plans[0]);
var loop = AssertNode<NestedLoopNode>(select.Source);
Assert.IsNull(loop.JoinCondition);
Assert.AreEqual("No Join Predicate", loop.Warning);
Assert.AreEqual("No Join Predicate or Outer References", loop.Warning);
var accountFetch = AssertNode<FetchXmlScan>(loop.LeftSource);

AssertFetchXml(accountFetch, @"
Expand Down Expand Up @@ -8672,5 +8673,31 @@ public void InVariables()
</entity>
</fetch>");
}

[TestMethod]
public void FoldSingleRowJoinToNestedLoop()
{
// https://github.com/MarkMpn/Sql4Cds/issues/582
var planBuilder = new ExecutionPlanBuilder(new SessionContext(_localDataSources, this), this);

var query = @"
SELECT TOP 10 a.name,
e.logicalname
FROM account AS a
INNER JOIN
metadata.entity e
ON a.accountid = e.metadataid
WHERE a.accountid = '9B8AAC69-EECA-497A-99AB-C65B9E702D89'";

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

Assert.AreEqual(1, plans.Length);

var select = AssertNode<SelectNode>(plans[0]);
var top = AssertNode<TopNode>(select.Source);
var loop = AssertNode<NestedLoopNode>(top.Source);
var fetch = AssertNode<FetchXmlScan>(loop.LeftSource);
var meta = AssertNode<MetadataQueryNode>(loop.RightSource);
}
}
}
6 changes: 5 additions & 1 deletion MarkMpn.Sql4Cds.Engine/ExecutionPlan/FoldableJoinNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -821,7 +821,11 @@ private bool FoldSingleRowJoinToNestedLoop(NodeCompilationContext context, IList
SecondExpression = new VariableReference { Name = outerReference }
}
};
var foldedRightSource = filteredRightSource.FoldQuery(context, hints);
var contextWithOuterReference = context.CreateChildContext(new Dictionary<string, DataTypeReference>
{
[outerReference] = leftSchema.Schema[leftAttr].Type
});
var foldedRightSource = filteredRightSource.FoldQuery(contextWithOuterReference, hints);

// If we can't fold the filter down to the data source, there's no benefit from doing this so stick with the
// original join type
Expand Down
2 changes: 1 addition & 1 deletion MarkMpn.Sql4Cds.Engine/ExecutionPlan/NestedLoopNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class NestedLoopNode : BaseJoinNode, IExecutionPlanNodeWarning
public Dictionary<string,string> OuterReferences { get; set; }

[Browsable(false)]
public string Warning => JoinCondition == null ? "No Join Predicate" : null;
public string Warning => JoinCondition == null && (OuterReferences == null || OuterReferences.Count == 0) ? "No Join Predicate or Outer References" : null;

protected override IEnumerable<Entity> ExecuteInternal(NodeExecutionContext context)
{
Expand Down

0 comments on commit 5762636

Please sign in to comment.