From 24f612570807e18f8077250e556eb5f597d9a317 Mon Sep 17 00:00:00 2001
From: Mark Carrington <31017244+MarkMpn@users.noreply.github.com>
Date: Tue, 26 Mar 2024 08:45:39 +0000
Subject: [PATCH] Support
---
.../FetchXml2SqlTests.cs | 22 +++---
.../Metadata/New_CustomEntity.cs | 13 ++++
.../ExecutionPlanTests.cs | 71 +++++++++++++++++++
.../ExecutionPlan/SortNode.cs | 42 +++++++++--
MarkMpn.Sql4Cds.Engine/FetchXml2Sql.cs | 11 ++-
5 files changed, 136 insertions(+), 23 deletions(-)
diff --git a/MarkMpn.Sql4Cds.Engine.FetchXml.Tests/FetchXml2SqlTests.cs b/MarkMpn.Sql4Cds.Engine.FetchXml.Tests/FetchXml2SqlTests.cs
index 8b3f2aca..46213370 100644
--- a/MarkMpn.Sql4Cds.Engine.FetchXml.Tests/FetchXml2SqlTests.cs
+++ b/MarkMpn.Sql4Cds.Engine.FetchXml.Tests/FetchXml2SqlTests.cs
@@ -115,39 +115,37 @@ public void Order()
}
[TestMethod]
- public void OrderLookup()
+ public void OrderPicklist()
{
var metadata = new AttributeMetadataCache(_service);
var fetch = @"
-
-
-
-
+
+
+
";
var converted = FetchXml2Sql.Convert(_service, metadata, fetch, new FetchXml2SqlOptions(), out _);
- Assert.AreEqual("SELECT firstname, lastname FROM contact ORDER BY parentcustomeridname ASC", NormalizeWhitespace(converted));
+ Assert.AreEqual("SELECT new_name FROM new_customentity ORDER BY new_optionsetvaluename ASC", NormalizeWhitespace(converted));
}
[TestMethod]
- public void OrderLookupRaw()
+ public void OrderPicklistRaw()
{
var metadata = new AttributeMetadataCache(_service);
var fetch = @"
-
-
-
-
+
+
+
";
var converted = FetchXml2Sql.Convert(_service, metadata, fetch, new FetchXml2SqlOptions(), out _);
- Assert.AreEqual("SELECT firstname, lastname FROM contact ORDER BY parentcustomerid ASC", NormalizeWhitespace(converted));
+ Assert.AreEqual("SELECT new_name FROM new_customentity ORDER BY new_optionsetvalue ASC", NormalizeWhitespace(converted));
}
[TestMethod]
diff --git a/MarkMpn.Sql4Cds.Engine.FetchXml.Tests/Metadata/New_CustomEntity.cs b/MarkMpn.Sql4Cds.Engine.FetchXml.Tests/Metadata/New_CustomEntity.cs
index e2f09e2d..7c6c1a42 100644
--- a/MarkMpn.Sql4Cds.Engine.FetchXml.Tests/Metadata/New_CustomEntity.cs
+++ b/MarkMpn.Sql4Cds.Engine.FetchXml.Tests/Metadata/New_CustomEntity.cs
@@ -26,5 +26,18 @@ class New_CustomEntity
[RelationshipSchemaName("new_customentity_children")]
public IEnumerable Children { get; }
+
+ [AttributeLogicalName("new_optionsetvalue")]
+ public New_OptionSet? New_OptionSetValue { get; set; }
+
+ [AttributeLogicalName("new_optionsetvaluename")]
+ public string New_OptionSetValueName { get; set; }
+ }
+
+ enum New_OptionSet
+ {
+ Value1 = 100001,
+ Value2,
+ Value3
}
}
diff --git a/MarkMpn.Sql4Cds.Engine.Tests/ExecutionPlanTests.cs b/MarkMpn.Sql4Cds.Engine.Tests/ExecutionPlanTests.cs
index cf7a8bbd..fa620b60 100644
--- a/MarkMpn.Sql4Cds.Engine.Tests/ExecutionPlanTests.cs
+++ b/MarkMpn.Sql4Cds.Engine.Tests/ExecutionPlanTests.cs
@@ -7187,5 +7187,76 @@ public void AliasSameAsVirtualAttribute()
");
}
+
+ [TestMethod]
+ public void OrderByOptionSetName()
+ {
+ var planBuilder = new ExecutionPlanBuilder(_localDataSources.Values, this);
+
+ var query = @"SELECT new_customentityid FROM new_customentity ORDER BY new_optionsetvaluename";
+
+ 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 OrderByOptionSetValue()
+ {
+ var planBuilder = new ExecutionPlanBuilder(_localDataSources.Values, this);
+
+ var query = @"SELECT new_customentityid FROM new_customentity ORDER BY new_optionsetvalue";
+
+ 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 OrderByOptionSetValueAndName()
+ {
+ var planBuilder = new ExecutionPlanBuilder(_localDataSources.Values, this);
+
+ var query = @"SELECT new_customentityid FROM new_customentity ORDER BY new_optionsetvalue, new_optionsetvaluename";
+
+ var plans = planBuilder.Build(query, null, out _);
+
+ Assert.AreEqual(1, plans.Length);
+
+ var select = AssertNode(plans[0]);
+ var sort = AssertNode(select.Source);
+ Assert.AreEqual(1, sort.PresortedCount);
+ Assert.AreEqual(2, sort.Sorts.Count);
+ Assert.AreEqual("new_customentity.new_optionsetvaluename", sort.Sorts[1].Expression.ToSql());
+ var fetch = AssertNode(sort.Source);
+ AssertFetchXml(fetch, @"
+
+
+
+
+
+
+ ");
+ }
}
}
diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/SortNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/SortNode.cs
index 16b28e02..d006568a 100644
--- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/SortNode.cs
+++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/SortNode.cs
@@ -283,6 +283,7 @@ private IDataExecutionPlanNodeInternal FoldSorts(NodeCompilationContext context)
var fetchSchema = fetchXml.GetSchema(context);
var validOrder = true;
var currentEntity = 0;
+ bool? useRawOrderBy = null;
foreach (var sortOrder in Sorts)
{
@@ -375,9 +376,18 @@ private IDataExecutionPlanNodeInternal FoldSorts(NodeCompilationContext context)
}
else
{
- // Sorting on a lookup Guid column actually sorts by the associated name field, which isn't what we want
- if (attribute is LookupAttributeMetadata || attribute is EnumAttributeMetadata || attribute is BooleanAttributeMetadata)
+ // Sorting on a lookup Guid and 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.
+ if (attribute is LookupAttributeMetadata)
return this;
+
+ if (attribute is EnumAttributeMetadata || attribute is BooleanAttributeMetadata)
+ {
+ if (useRawOrderBy == false)
+ return this;
+
+ useRawOrderBy = true;
+ }
// Sorts on the virtual ___name attribute should be applied to the underlying field
if (attribute == null && fetchSort.attribute.EndsWith("name") == true)
@@ -385,7 +395,13 @@ private IDataExecutionPlanNodeInternal FoldSorts(NodeCompilationContext context)
attribute = meta.Attributes.SingleOrDefault(a => a.LogicalName == fetchSort.attribute.Substring(0, fetchSort.attribute.Length - 4) && a.AttributeOf == null);
if (attribute != null)
+ {
+ if (attribute is LookupAttributeMetadata || useRawOrderBy == true)
+ return this;
+
fetchSort.attribute = attribute.LogicalName;
+ useRawOrderBy = false;
+ }
}
}
@@ -406,9 +422,18 @@ private IDataExecutionPlanNodeInternal FoldSorts(NodeCompilationContext context)
var meta = dataSource.Metadata[linkEntity.name];
var attribute = meta.Attributes.SingleOrDefault(a => a.LogicalName == fetchSort.attribute && a.AttributeOf == null);
- // Sorting on a lookup Guid column actually sorts by the associated name field, which isn't what we want
- if (attribute is LookupAttributeMetadata || attribute is EnumAttributeMetadata || attribute is BooleanAttributeMetadata)
+ // 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.
+ if (attribute is LookupAttributeMetadata)
return this;
+
+ if (attribute is EnumAttributeMetadata || attribute is BooleanAttributeMetadata)
+ {
+ if (useRawOrderBy == false)
+ return this;
+
+ useRawOrderBy = true;
+ }
// Sorting on multi-select picklist fields isn't supported in FetchXML
if (attribute is MultiSelectPicklistAttributeMetadata)
@@ -420,7 +445,13 @@ private IDataExecutionPlanNodeInternal FoldSorts(NodeCompilationContext context)
attribute = meta.Attributes.SingleOrDefault(a => a.LogicalName == fetchSort.attribute.Substring(0, fetchSort.attribute.Length - 4) && a.AttributeOf == null);
if (attribute != null)
+ {
+ if (attribute is LookupAttributeMetadata || useRawOrderBy == true)
+ return this;
+
fetchSort.attribute = attribute.LogicalName;
+ useRawOrderBy = false;
+ }
}
if (attribute == null)
@@ -471,6 +502,9 @@ private IDataExecutionPlanNodeInternal FoldSorts(NodeCompilationContext context)
}
PresortedCount++;
+
+ if (useRawOrderBy == true)
+ fetchXml.FetchXml.UseRawOrderBy = true;
}
return Source;
diff --git a/MarkMpn.Sql4Cds.Engine/FetchXml2Sql.cs b/MarkMpn.Sql4Cds.Engine/FetchXml2Sql.cs
index 82c14fe6..4f713e57 100644
--- a/MarkMpn.Sql4Cds.Engine/FetchXml2Sql.cs
+++ b/MarkMpn.Sql4Cds.Engine/FetchXml2Sql.cs
@@ -2592,14 +2592,11 @@ private static void AddOrderBy(string name, object[] items, QuerySpecification q
if (!aliasToLogicalName.TryGetValue(entityAlias, out var entityLogicalName))
entityLogicalName = entityAlias;
- if (!useRawOrderBy)
- {
- var entityMetadata = metadata[entityLogicalName];
- var attr = entityMetadata.Attributes.SingleOrDefault(a => a.LogicalName == attributeName);
+ var entityMetadata = metadata[entityLogicalName];
+ var attr = entityMetadata.Attributes.SingleOrDefault(a => a.LogicalName == attributeName);
- if (attr is LookupAttributeMetadata || attr is EnumAttributeMetadata)
- attributeName += "name";
- }
+ if (attr is LookupAttributeMetadata || ((attr is EnumAttributeMetadata || attr is BooleanAttributeMetadata) && !useRawOrderBy))
+ attributeName += "name";
query.OrderByClause.OrderByElements.Add(new ExpressionWithSortOrder
{