diff --git a/MarkMpn.Sql4Cds.Engine.Tests/ExecutionPlanTests.cs b/MarkMpn.Sql4Cds.Engine.Tests/ExecutionPlanTests.cs index ecd31ba7..7d27464d 100644 --- a/MarkMpn.Sql4Cds.Engine.Tests/ExecutionPlanTests.cs +++ b/MarkMpn.Sql4Cds.Engine.Tests/ExecutionPlanTests.cs @@ -6062,6 +6062,62 @@ public void FilterAuditOnLeftJoinColumn() "); } + [TestMethod] + public void FilterAuditOnUserId() + { + var planBuilder = new ExecutionPlanBuilder(_dataSources.Values, new OptionsWrapper(this) { PrimaryDataSource = "prod" }); + var query = "SELECT * FROM audit WHERE userid = 'B52C0694-E16F-40CD-9F27-800023C47A98'"; + var plans = planBuilder.Build(query, null, out _); + + Assert.AreEqual(1, plans.Length); + var select = AssertNode(plans[0]); + var fetch = AssertNode(select.Source); + AssertFetchXml(fetch, @" + + + + + + + + "); + } + + [TestMethod] + public void FilterAuditOnUserIdAggregate() + { + // Can do aggregates on audit table when any filtering is done only on the table itself, + // not on any joins + var planBuilder = new ExecutionPlanBuilder(_dataSources.Values, new OptionsWrapper(this) { PrimaryDataSource = "prod" }); + var query = "SELECT COUNT(*) FROM audit WHERE userid = 'B52C0694-E16F-40CD-9F27-800023C47A98'"; + var plans = planBuilder.Build(query, null, out _); + + Assert.AreEqual(1, plans.Length); + var select = AssertNode(plans[0]); + var tryCatch = AssertNode(select.Source); + var aggregateFetch = AssertNode(tryCatch.TrySource); + AssertFetchXml(aggregateFetch, @" + + + + + + + + "); + var streamAggregate = AssertNode(tryCatch.CatchSource); + var fetch = AssertNode(streamAggregate.Source); + AssertFetchXml(fetch, @" + + + + + + + + "); + } + [TestMethod] public void FilterAuditOnInnerJoinColumn() { @@ -6087,6 +6143,33 @@ public void FilterAuditOnInnerJoinColumn() "); } + [TestMethod] + public void FilterAuditOnInnerJoinColumnAggregate() + { + // Can't do aggregates on audit table when filtering is done on a join + // https://github.com/MarkMpn/Sql4Cds/issues/488 + var planBuilder = new ExecutionPlanBuilder(_dataSources.Values, new OptionsWrapper(this) { PrimaryDataSource = "prod" }); + var query = "SELECT COUNT(*) FROM audit INNER JOIN systemuser ON audit.userid = systemuser.systemuserid WHERE systemuser.domainname <> 'SYSTEM'"; + var plans = planBuilder.Build(query, null, out _); + + Assert.AreEqual(1, plans.Length); + var select = AssertNode(plans[0]); + var aggregate = AssertNode(select.Source); + var fetch = AssertNode(aggregate.Source); + AssertFetchXml(fetch, @" + + + + + + + + + + + "); + } + [TestMethod] public void SortAuditOnJoinColumn() { diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/HashMatchAggregateNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/HashMatchAggregateNode.cs index 7c85b61b..98d8c4c4 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/HashMatchAggregateNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/HashMatchAggregateNode.cs @@ -340,6 +340,11 @@ Source is FetchXmlScan fetch && (fetchXml.Entity.name == "audit" || metadata[fetchXml.Entity.name].DataProviderId == DataProviders.ElasticDataProvider)) canUseFetchXmlAggregate = false; + // Aggregates on audit entity seem to transform inner joins to left outer joins, so can't + // use FetchXML aggregates here + if (fetchXml.Entity.name == "audit" && fetchXml.Entity.GetLinkEntities(true).Any()) + canUseFetchXmlAggregate = false; + var serializer = new XmlSerializer(typeof(FetchXml.FetchType)); if (canUseFetchXmlAggregate)