Skip to content

Commit

Permalink
Improved folding of sorts to aggregate FetchXML - restrictions on sor…
Browse files Browse the repository at this point in the history
…ting on groupby columns and lookup columns
  • Loading branch information
MarkMpn committed Aug 2, 2024
1 parent 7ee3709 commit fca61e1
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 8 deletions.
28 changes: 28 additions & 0 deletions MarkMpn.Sql4Cds.Engine.Tests/ExecutionPlanTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8260,5 +8260,33 @@ metadata.entity AS p
var metadata_a = AssertNode<MetadataQueryNode>(join.RightSource);
Assert.AreEqual("a", metadata_a.AttributeAlias);
}

[TestMethod]
public void AggregateWithDynamicFilterValues()
{
var query = @"
SELECT COUNT(*) AS AccountCount
FROM account
WHERE createdon >= CAST(GETDATE() AS DATE)
AND createdon < DATEADD(day, 1, CAST(GETDATE() AS DATE))";

var planBuilder = new ExecutionPlanBuilder(_localDataSources.Values, this);

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

Assert.AreEqual(1, plans.Length);

var select = AssertNode<SelectNode>(plans[0]);
var loop = AssertNode<NestedLoopNode>(select.Source);
var compute = AssertNode<ComputeScalarNode>(loop.LeftSource);
var constant = AssertNode<ConstantScanNode>(compute.Source);
var tryCatch1 = AssertNode<TryCatchNode>(loop.RightSource);
var tryCatch2 = AssertNode<TryCatchNode>(tryCatch1.TrySource);
var fetch1 = AssertNode<FetchXmlScan>(tryCatch2.TrySource);
var partition = AssertNode<PartitionedAggregateNode>(tryCatch2.CatchSource);
var fetch2 = AssertNode<FetchXmlScan>(partition.Source);
var streamAggregate = AssertNode<StreamAggregateNode>(tryCatch1.CatchSource);
var fetch3 = AssertNode<FetchXmlScan>(streamAggregate.Source);
}
}
}
58 changes: 50 additions & 8 deletions MarkMpn.Sql4Cds.Engine/ExecutionPlan/SortNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -356,10 +356,28 @@ private IDataExecutionPlanNodeInternal FoldSorts(NodeCompilationContext context)

if (entityName == fetchXml.Alias)
{
if (fetchSort.attribute != null)
var attributeName = fetchSort.attribute;
if (fetchSort.alias != null)
{
var fetchAttribute = fetchXml.Entity.Items.OfType<FetchAttributeType>().Where(a => a.alias == fetchSort.alias).FirstOrDefault();

if (fetchAttribute != null)
{
attributeName = fetchAttribute.name;

if (fetchAttribute.groupbySpecified && fetchAttribute.groupby == FetchBoolType.@true &&
fetchSort.descending)
{
// Sorts on groupby columns always seem to be in ascending order, descending flag is ignored
return this;
}
}
}

if (attributeName != null)
{
var meta = dataSource.Metadata[fetchXml.Entity.name];
var attribute = meta.Attributes.SingleOrDefault(a => a.LogicalName == fetchSort.attribute && a.AttributeOf == null);
var attribute = meta.Attributes.SingleOrDefault(a => a.LogicalName == attributeName && a.AttributeOf == null);

// Sorting on multi-select picklist fields isn't supported in FetchXML
if (attribute is MultiSelectPicklistAttributeMetadata)
Expand Down Expand Up @@ -394,12 +412,15 @@ private IDataExecutionPlanNodeInternal FoldSorts(NodeCompilationContext context)
}

// Sorts on the virtual ___name attribute should be applied to the underlying field
if (attribute == null && fetchSort.attribute.EndsWith("name") == true)
if (attribute == null && attributeName.EndsWith("name") == true)
{
attribute = meta.Attributes.SingleOrDefault(a => a.LogicalName == fetchSort.attribute.Substring(0, fetchSort.attribute.Length - 4) && a.AttributeOf == null);
attribute = meta.Attributes.SingleOrDefault(a => a.LogicalName == attributeName.Substring(0, fetchSort.attribute.Length - 4) && a.AttributeOf == null);

if (attribute != null)
{
if (fetchSort.attribute == null)
return this;

fetchSort.attribute = attribute.LogicalName;

if (attribute is EnumAttributeMetadata || attribute is BooleanAttributeMetadata)
Expand All @@ -425,10 +446,28 @@ private IDataExecutionPlanNodeInternal FoldSorts(NodeCompilationContext context)
if (linkEntity == null)
return this;

if (fetchSort.attribute != null)
var attributeName = fetchSort.attribute;
if (fetchSort.alias != null)
{
var fetchAttribute = linkEntity.Items.OfType<FetchAttributeType>().Where(a => a.alias == fetchSort.alias).FirstOrDefault();

if (fetchAttribute != null)
{
attributeName = fetchAttribute.name;

if (fetchAttribute.groupbySpecified && fetchAttribute.groupby == FetchBoolType.@true &&
fetchSort.descending)
{
// Sorts on groupby columns always seem to be in ascending order, descending flag is ignored
return this;
}
}
}

if (attributeName != null)
{
var meta = dataSource.Metadata[linkEntity.name];
var attribute = meta.Attributes.SingleOrDefault(a => a.LogicalName == fetchSort.attribute && a.AttributeOf == null);
var attribute = meta.Attributes.SingleOrDefault(a => a.LogicalName == attributeName && a.AttributeOf == null);

// Sorting on a lookup Guid or picklist column actually sorts by the associated name field, which isn't what we want
// Picklist sorting can be controlled by the useraworderby flag though.
Expand All @@ -448,12 +487,15 @@ private IDataExecutionPlanNodeInternal FoldSorts(NodeCompilationContext context)
return this;

// Sorts on the virtual ___name attribute should be applied to the underlying field
if (attribute == null && fetchSort.attribute.EndsWith("name") == true)
if (attribute == null && attributeName.EndsWith("name") == true)
{
attribute = meta.Attributes.SingleOrDefault(a => a.LogicalName == fetchSort.attribute.Substring(0, fetchSort.attribute.Length - 4) && a.AttributeOf == null);
attribute = meta.Attributes.SingleOrDefault(a => a.LogicalName == attributeName.Substring(0, fetchSort.attribute.Length - 4) && a.AttributeOf == null);

if (attribute != null)
{
if (fetchSort.attribute == null)
return this;

fetchSort.attribute = attribute.LogicalName;

if (attribute is EnumAttributeMetadata || attribute is BooleanAttributeMetadata)
Expand Down

0 comments on commit fca61e1

Please sign in to comment.