From d5cf5bddbca93cfbfd235ee0d9e80dbc06ba159c Mon Sep 17 00:00:00 2001 From: Mark Carrington <31017244+MarkMpn@users.noreply.github.com> Date: Mon, 8 Jul 2024 20:19:36 +0100 Subject: [PATCH] Use in-memory implementations for DISTINCT, OFFSET/FETCH, ORDER BY and TOP when working with virtual entity providers Fixes #503 --- .../MarkMpn.Sql4Cds.Engine.Tests.csproj | 16 ++++++---------- .../ExecutionPlan/DistinctNode.cs | 4 +++- .../ExecutionPlan/FetchXmlScan.cs | 2 ++ .../ExecutionPlan/OffsetFetchNode.cs | 4 ++++ MarkMpn.Sql4Cds.Engine/ExecutionPlan/SortNode.cs | 8 ++++++++ MarkMpn.Sql4Cds.Engine/ExecutionPlan/TopNode.cs | 5 +++++ 6 files changed, 28 insertions(+), 11 deletions(-) diff --git a/MarkMpn.Sql4Cds.Engine.Tests/MarkMpn.Sql4Cds.Engine.Tests.csproj b/MarkMpn.Sql4Cds.Engine.Tests/MarkMpn.Sql4Cds.Engine.Tests.csproj index d502b328..f19791ce 100644 --- a/MarkMpn.Sql4Cds.Engine.Tests/MarkMpn.Sql4Cds.Engine.Tests.csproj +++ b/MarkMpn.Sql4Cds.Engine.Tests/MarkMpn.Sql4Cds.Engine.Tests.csproj @@ -110,16 +110,6 @@ - - - {c77b731d-e55c-4197-b96c-2b23eb9f56ef} - MarkMpn.Sql4Cds.Engine - - - {a7af5d13-a44e-426d-b3fc-ae390832c7df} - MarkMpn.Sql4Cds - - 2.1.28 @@ -140,6 +130,12 @@ 6.0.7 + + + {c77b731d-e55c-4197-b96c-2b23eb9f56ef} + MarkMpn.Sql4Cds.Engine + + \ No newline at end of file diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/DistinctNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/DistinctNode.cs index 058aeca5..98081d6c 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/DistinctNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/DistinctNode.cs @@ -119,7 +119,9 @@ public override IDataExecutionPlanNodeInternal FoldQuery(NodeCompilationContext } } - if (!virtualAttr) + // Virtual entity providers are unreliable - still fold the DISTINCT to the fetch but keep + // this node to ensure the DISTINCT is applied if the provider doesn't support it. + if (!virtualAttr && !fetch.IsUnreliableVirtualEntityProvider) return fetch; schema = Source.GetSchema(context); diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/FetchXmlScan.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/FetchXmlScan.cs index ab176e92..42c9800c 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/FetchXmlScan.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/FetchXmlScan.cs @@ -196,6 +196,8 @@ public FetchXmlScan() [Browsable(false)] public string Warning => _missingPagingCookie && RowsOut == 50_000 ? "Using legacy paging - results may be incomplete" : null; + internal bool IsUnreliableVirtualEntityProvider => _isVirtualEntity; + public bool RequiresCustomPaging(IDictionary dataSources) { // Never need to do paging if we're enforcing a TOP constraint diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/OffsetFetchNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/OffsetFetchNode.cs index ed880e4d..afc53e5e 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/OffsetFetchNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/OffsetFetchNode.cs @@ -73,6 +73,10 @@ public override IDataExecutionPlanNodeInternal FoldQuery(NodeCompilationContext if (Source is FetchXmlScan fetchXml) { + // Virtual entity providers aren't reliable - use naive implementation + if (fetchXml.IsUnreliableVirtualEntityProvider) + return this; + var expressionExecutionContext = new ExpressionExecutionContext(expressionCompilationContext); var offset = SqlTypeConverter.ChangeType(offsetLiteral.Compile(expressionCompilationContext)(expressionExecutionContext)); var count = SqlTypeConverter.ChangeType(fetchLiteral.Compile(expressionCompilationContext)(expressionExecutionContext)); diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/SortNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/SortNode.cs index 938f53f5..eb7d6ce4 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/SortNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/SortNode.cs @@ -519,6 +519,14 @@ private IDataExecutionPlanNodeInternal FoldSorts(NodeCompilationContext context) fetchXml.FetchXml.UseRawOrderBy = true; } + // Virtual entity providers are unreliable - fold the sorts to the FetchXML but keep this + // node to resort if required. + if (fetchXml.IsUnreliableVirtualEntityProvider) + { + PresortedCount = 0; + return this; + } + return Source; } diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/TopNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/TopNode.cs index 0f5d0044..05b96cb9 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/TopNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/TopNode.cs @@ -169,6 +169,11 @@ public override IDataExecutionPlanNodeInternal FoldQuery(NodeCompilationContext fetchXml.FetchXml.top = literal.Value; fetchXml.AllPages = false; + // Virtual entity providers aren't reliable - fold the TOP into the FetchXML but keep + // this node in case the provider doesn't support TOP + if (fetchXml.IsUnreliableVirtualEntityProvider) + return this; + if (Source == fetchXml) return fetchXml;