From da02945e7322db51062e7a137b450a3ef82979cb Mon Sep 17 00:00:00 2001 From: Mark Carrington <31017244+MarkMpn@users.noreply.github.com> Date: Thu, 25 Jul 2024 08:40:13 +0100 Subject: [PATCH] Do not fold DISTINCT to queries including audit.objectid Fixes #519 --- .../ExecutionPlanTests.cs | 27 +++++++++++++++++++ .../ExecutionPlan/DistinctNode.cs | 5 ++++ .../ExecutionPlan/HashJoinNode.cs | 1 - 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/MarkMpn.Sql4Cds.Engine.Tests/ExecutionPlanTests.cs b/MarkMpn.Sql4Cds.Engine.Tests/ExecutionPlanTests.cs index ec132dd2..003c2cf1 100644 --- a/MarkMpn.Sql4Cds.Engine.Tests/ExecutionPlanTests.cs +++ b/MarkMpn.Sql4Cds.Engine.Tests/ExecutionPlanTests.cs @@ -6040,6 +6040,33 @@ public void SelectAuditObjectId() "); } + [DataTestMethod] + [DataRow("objectid")] + [DataRow("objectidname")] + [DataRow("objectidtype")] + public void SelectAuditObjectIdDistinct(string column) + { + // https://github.com/MarkMpn/Sql4Cds/issues/296 + // https://github.com/MarkMpn/Sql4Cds/issues/519 + // We need to add the objecttypecode attribute to the FetchXML for the first issue, but combining this + // with DISTINCT doesn't work because of the second issue. + var planBuilder = new ExecutionPlanBuilder(_dataSources.Values, new OptionsWrapper(this) { PrimaryDataSource = "prod" }); + var query = $"SELECT DISTINCT {column} AS o FROM audit"; + var plans = planBuilder.Build(query, null, out _); + + Assert.AreEqual(1, plans.Length); + var select = AssertNode(plans[0]); + var distinct = AssertNode(select.Source); + var fetch = AssertNode(distinct.Source); + AssertFetchXml(fetch, @" + + + + + + "); + } + [TestMethod] public void FilterAuditOnLeftJoinColumn() { diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/DistinctNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/DistinctNode.cs index e55a08f7..017079b0 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/DistinctNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/DistinctNode.cs @@ -90,6 +90,11 @@ public override IDataExecutionPlanNodeInternal FoldQuery(NodeCompilationContext if (Source is FetchXmlScan fetch) { + // Can't apply DISTINCT to audit.objectid + // https://github.com/MarkMpn/Sql4Cds/issues/519 + if (fetch.Entity.name == "audit" && Columns.Any(col => col.StartsWith(fetch.Alias.EscapeIdentifier() + ".objectid"))) + return this; + fetch.FetchXml.distinct = true; fetch.FetchXml.distinctSpecified = true; var metadata = context.DataSources[fetch.DataSource].Metadata; diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/HashJoinNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/HashJoinNode.cs index 9de1e78d..cf6c6d95 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/HashJoinNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/HashJoinNode.cs @@ -200,7 +200,6 @@ public override IDataExecutionPlanNodeInternal FoldQuery(NodeCompilationContext // Make sure the join keys are not null - the SqlType classes override == to prevent NULL = NULL // but .Equals used by the hash table allows them to match - // TODO: We do this in the base class as well, can we delete this version? if (ComparisonType == BooleanComparisonType.Equals) { if (JoinType == QualifiedJoinType.Inner || JoinType == QualifiedJoinType.RightOuter)