diff --git a/MarkMpn.Sql4Cds.Engine.Tests/AdoProviderTests.cs b/MarkMpn.Sql4Cds.Engine.Tests/AdoProviderTests.cs index 3c3840f0..be276058 100644 --- a/MarkMpn.Sql4Cds.Engine.Tests/AdoProviderTests.cs +++ b/MarkMpn.Sql4Cds.Engine.Tests/AdoProviderTests.cs @@ -1080,6 +1080,8 @@ public void MergeSemiJoin() using (var con = new Sql4CdsConnection(_dataSources)) using (var cmd = con.CreateCommand()) { + cmd.CommandTimeout = 0; + cmd.CommandText = "insert into account (name) values ('data8')"; cmd.ExecuteNonQuery(); cmd.ExecuteNonQuery(); diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/AliasNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/AliasNode.cs index 1c4540cf..7359904b 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/AliasNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/AliasNode.cs @@ -155,7 +155,7 @@ public override INodeSchema GetSchema(NodeCompilationContext context) { // Map the base names to the alias names var sourceSchema = Source.GetSchema(context); - var schema = new Dictionary(StringComparer.OrdinalIgnoreCase); + var schema = new ColumnList(); var aliases = new Dictionary>(); var primaryKey = (string)null; var mappings = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -218,7 +218,7 @@ public override INodeSchema GetSchema(NodeCompilationContext context) sortOrder: sortOrder); } - private void AddSchemaColumn(string outputColumn, string sourceColumn, Dictionary schema, Dictionary> aliases, ref string primaryKey, Dictionary mappings, INodeSchema sourceSchema) + private void AddSchemaColumn(string outputColumn, string sourceColumn, ColumnList schema, Dictionary> aliases, ref string primaryKey, Dictionary mappings, INodeSchema sourceSchema) { if (!sourceSchema.ContainsColumn(sourceColumn, out var normalized)) return; diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/BaseAggregateNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/BaseAggregateNode.cs index ad73a0fa..e1073edf 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/BaseAggregateNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/BaseAggregateNode.cs @@ -178,7 +178,7 @@ public override INodeSchema GetSchema(NodeCompilationContext context) { var sourceSchema = Source.GetSchema(context); var expressionContext = new ExpressionCompilationContext(context, sourceSchema, null); - var schema = new Dictionary(StringComparer.OrdinalIgnoreCase); + var schema = new ColumnList(); var aliases = new Dictionary>(StringComparer.OrdinalIgnoreCase); var primaryKey = (string)null; var notNullColumns = new List(); diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/BaseDmlNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/BaseDmlNode.cs index 416f6780..291bddc3 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/BaseDmlNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/BaseDmlNode.cs @@ -211,7 +211,7 @@ protected List GetDmlSourceEntities(NodeExecutionContext context, out IN // Store the values under the column index as well as name for compatibility with INSERT ... SELECT ... var dataTable = new DataTable(); var schemaTable = dataReader.GetSchemaTable(); - var columnTypes = new Dictionary(StringComparer.OrdinalIgnoreCase); + var columnTypes = new ColumnList(); var targetDataSource = context.DataSources[DataSource]; for (var i = 0; i < schemaTable.Rows.Count; i++) diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/BaseJoinNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/BaseJoinNode.cs index 1b46b3fa..d408cc98 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/BaseJoinNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/BaseJoinNode.cs @@ -17,6 +17,7 @@ abstract class BaseJoinNode : BaseDataNode private INodeSchema _lastLeftSchema; private INodeSchema _lastRightSchema; private INodeSchema _lastSchema; + private bool _lastSchemaIncludedSemiJoin; /// /// The first data source to merge @@ -115,10 +116,10 @@ protected virtual INodeSchema GetSchema(NodeCompilationContext context, bool inc var outerSchema = LeftSource.GetSchema(context); var innerSchema = GetRightSchema(context); - if (outerSchema == _lastLeftSchema && innerSchema == _lastRightSchema) + if (outerSchema == _lastLeftSchema && innerSchema == _lastRightSchema && includeSemiJoin == _lastSchemaIncludedSemiJoin) return _lastSchema; - var schema = new Dictionary(StringComparer.OrdinalIgnoreCase); + var schema = new ColumnList(); var aliases = new Dictionary>(StringComparer.OrdinalIgnoreCase); var primaryKey = GetPrimaryKey(outerSchema, innerSchema); var notNullColumns = new List(); @@ -162,6 +163,7 @@ protected virtual INodeSchema GetSchema(NodeCompilationContext context, bool inc aliases: aliases, notNullColumns: notNullColumns, sortOrder: GetSortOrder(outerSchema, innerSchema)); + _lastSchemaIncludedSemiJoin = includeSemiJoin; return _lastSchema; } diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/ComputeScalarNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/ComputeScalarNode.cs index f6bd3489..7d971116 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/ComputeScalarNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/ComputeScalarNode.cs @@ -54,7 +54,7 @@ public override INodeSchema GetSchema(NodeCompilationContext context) // Copy the source schema and add in the additional computed columns var sourceSchema = Source.GetSchema(context); var expressionCompilationContext = new ExpressionCompilationContext(context, sourceSchema, null); - var schema = new Dictionary(sourceSchema.Schema.Count, StringComparer.OrdinalIgnoreCase); + var schema = new ColumnList(); foreach (var col in sourceSchema.Schema) schema[col.Key] = col.Value; diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/ConcatenateNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/ConcatenateNode.cs index 478d93e7..f7bf40bb 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/ConcatenateNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/ConcatenateNode.cs @@ -48,7 +48,7 @@ protected override IEnumerable ExecuteInternal(NodeExecutionContext cont public override INodeSchema GetSchema(NodeCompilationContext context) { - var schema = new Dictionary(StringComparer.OrdinalIgnoreCase); + var schema = new ColumnList(); var sourceSchema = Sources[0].GetSchema(context); diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/ConstantScanNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/ConstantScanNode.cs index 36b8e685..1fb6502a 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/ConstantScanNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/ConstantScanNode.cs @@ -31,7 +31,7 @@ class ConstantScanNode : BaseDataNode /// The types of values to be returned /// [Browsable(false)] - public Dictionary Schema { get; private set; } = new Dictionary(); + public IDictionary Schema { get; private set; } = new ColumnList(); protected override IEnumerable ExecuteInternal(NodeExecutionContext context) { @@ -56,9 +56,14 @@ public override IEnumerable GetSources() public override INodeSchema GetSchema(NodeCompilationContext context) { + var schema = new ColumnList(); + + foreach (var col in Schema) + schema[PrefixWithAlias(col.Key)] = col.Value; + return new NodeSchema( primaryKey: null, - schema: Schema.ToDictionary(kvp => PrefixWithAlias(kvp.Key), kvp => kvp.Value, StringComparer.OrdinalIgnoreCase), + schema: schema, aliases: Schema.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList) new List { PrefixWithAlias(kvp.Key) }, StringComparer.OrdinalIgnoreCase), notNullColumns: null, sortOrder: null); diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/ExecuteMessageNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/ExecuteMessageNode.cs index 33a2469d..79d3f684 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/ExecuteMessageNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/ExecuteMessageNode.cs @@ -92,7 +92,7 @@ class ExecuteMessageNode : BaseDataNode, IDmlQueryExecutionPlanNode /// The types of values to be returned /// [Browsable(false)] - public Dictionary Schema { get; private set; } = new Dictionary(); + public IDictionary Schema { get; private set; } = new ColumnList(); /// /// Indicates if custom plugins should be skipped @@ -148,9 +148,14 @@ private bool GetBypassPluginExecution(IList queryHints, IQueryExe public override INodeSchema GetSchema(NodeCompilationContext context) { + var schema = new ColumnList(); + + foreach (var col in Schema) + schema[PrefixWithAlias(col.Key)] = col.Value; + return new NodeSchema( primaryKey: null, - schema: Schema.ToDictionary(kvp => PrefixWithAlias(kvp.Key), kvp => kvp.Value, StringComparer.OrdinalIgnoreCase), + schema: schema, aliases: Schema.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList)new List { PrefixWithAlias(kvp.Key) }, StringComparer.OrdinalIgnoreCase), notNullColumns: null, sortOrder: null); diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/FetchXmlScan.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/FetchXmlScan.cs index 2f86555e..6c85af17 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/FetchXmlScan.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/FetchXmlScan.cs @@ -655,7 +655,7 @@ public override INodeSchema GetSchema(NodeCompilationContext context) var entity = FetchXml.Items.OfType().Single(); var meta = dataSource.Metadata[entity.name]; - var schema = new Dictionary(StringComparer.OrdinalIgnoreCase); + var schema = new ColumnList(); var aliases = new Dictionary>(StringComparer.OrdinalIgnoreCase); var primaryKey = FetchXml.aggregate ? null : $"{Alias}.{meta.PrimaryIdAttribute}"; var notNullColumns = new HashSet(); @@ -759,7 +759,7 @@ internal static bool IsValidAlias(string alias) return Regex.IsMatch(alias, "^[A-Za-z_][A-Za-z0-9_]*$"); } - private void AddSchemaAttributes(DataSource dataSource, Dictionary schema, Dictionary> aliases, ref string primaryKey, HashSet notNullColumns, List sortOrder, string entityName, string alias, object[] items, bool innerJoin, bool requireTablePrefix) + private void AddSchemaAttributes(DataSource dataSource, ColumnList schema, Dictionary> aliases, ref string primaryKey, HashSet notNullColumns, List sortOrder, string entityName, string alias, object[] items, bool innerJoin, bool requireTablePrefix) { if (items == null && !ReturnFullSchema) return; @@ -768,7 +768,7 @@ private void AddSchemaAttributes(DataSource dataSource, Dictionary a.LogicalName)) { if (attrMetadata.IsValidForRead == false) continue; @@ -918,7 +918,7 @@ private void AddSchemaAttributes(DataSource dataSource, Dictionary schema, Dictionary> aliases, HashSet notNullColumns, string alias, filter filter) + private void AddNotNullFilters(ColumnList schema, Dictionary> aliases, HashSet notNullColumns, string alias, filter filter) { if (filter.Items == null) return; @@ -941,7 +941,7 @@ private void AddNotNullFilters(Dictionary schema, Dic AddNotNullFilters(schema, aliases, notNullColumns, alias, subFilter); } - private void AddSchemaAttribute(DataSource dataSource, Dictionary schema, Dictionary> aliases, HashSet notNullColumns, string fullName, string simpleName, DataTypeReference type, AttributeMetadata attrMetadata, bool innerJoin) + private void AddSchemaAttribute(DataSource dataSource, ColumnList schema, Dictionary> aliases, HashSet notNullColumns, string fullName, string simpleName, DataTypeReference type, AttributeMetadata attrMetadata, bool innerJoin) { var notNull = innerJoin && (attrMetadata.RequiredLevel?.Value == AttributeRequiredLevel.SystemRequired || attrMetadata.LogicalName == "createdon" || attrMetadata.LogicalName == "createdby" || attrMetadata.AttributeOf == "createdby"); @@ -969,7 +969,7 @@ private void AddSchemaAttribute(DataSource dataSource, Dictionary schema, Dictionary> aliases, HashSet notNullColumns, string fullName, string simpleName, DataTypeReference type, bool notNull) + private void AddSchemaAttribute(ColumnList schema, Dictionary> aliases, HashSet notNullColumns, string fullName, string simpleName, DataTypeReference type, bool notNull) { schema[fullName] = type; diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/GlobalOptionSetQueryNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/GlobalOptionSetQueryNode.cs index c31f8032..fbb79d97 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/GlobalOptionSetQueryNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/GlobalOptionSetQueryNode.cs @@ -117,7 +117,7 @@ public override IDataExecutionPlanNodeInternal FoldQuery(NodeCompilationContext public override INodeSchema GetSchema(NodeCompilationContext context) { - var schema = new Dictionary(StringComparer.OrdinalIgnoreCase); + var schema = new ColumnList(); var aliases = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (var prop in _optionsetProps.Values) diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/MetadataQueryNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/MetadataQueryNode.cs index 2e45f681..581287c2 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/MetadataQueryNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/MetadataQueryNode.cs @@ -441,7 +441,7 @@ public override IDataExecutionPlanNodeInternal FoldQuery(NodeCompilationContext public override INodeSchema GetSchema(NodeCompilationContext context) { - var schema = new Dictionary(StringComparer.OrdinalIgnoreCase); + var schema = new ColumnList(); var aliases = new Dictionary>(); var primaryKey = (string)null; var notNullColumns = new HashSet(StringComparer.OrdinalIgnoreCase); @@ -456,7 +456,7 @@ public override INodeSchema GetSchema(NodeCompilationContext context) if (Query.Properties != null) entityProps = entityProps.Where(p => Query.Properties.AllProperties || Query.Properties.PropertyNames.Contains(p.PropertyName, StringComparer.OrdinalIgnoreCase)); - foreach (var prop in entityProps) + foreach (var prop in entityProps.OrderBy(p => p.SqlName)) { var fullName = $"{EntityAlias}.{prop.SqlName}"; schema[fullName] = prop.SqlType; @@ -483,7 +483,7 @@ public override INodeSchema GetSchema(NodeCompilationContext context) if (Query.AttributeQuery?.Properties != null) attributeProps = attributeProps.Where(p => Query.AttributeQuery.Properties.AllProperties || Query.AttributeQuery.Properties.PropertyNames.Contains(p.PropertyName, StringComparer.OrdinalIgnoreCase)); - foreach (var prop in attributeProps) + foreach (var prop in attributeProps.OrderBy(p => p.SqlName)) { var fullName = $"{AttributeAlias}.{prop.SqlName}"; schema[fullName] = prop.SqlType; @@ -511,7 +511,7 @@ public override INodeSchema GetSchema(NodeCompilationContext context) if (Query.RelationshipQuery?.Properties != null) relationshipProps = relationshipProps.Where(p => Query.RelationshipQuery.Properties.AllProperties || Query.RelationshipQuery.Properties.PropertyNames.Contains(p.PropertyName, StringComparer.OrdinalIgnoreCase)); - foreach (var prop in relationshipProps) + foreach (var prop in relationshipProps.OrderBy(p => p.SqlName)) { var fullName = $"{OneToManyRelationshipAlias}.{prop.SqlName}"; schema[fullName] = prop.SqlType; @@ -539,7 +539,7 @@ public override INodeSchema GetSchema(NodeCompilationContext context) if (Query.RelationshipQuery?.Properties != null) relationshipProps = relationshipProps.Where(p => Query.RelationshipQuery.Properties.AllProperties || Query.RelationshipQuery.Properties.PropertyNames.Contains(p.PropertyName, StringComparer.OrdinalIgnoreCase)); - foreach (var prop in relationshipProps) + foreach (var prop in relationshipProps.OrderBy(p => p.SqlName)) { var fullName = $"{ManyToOneRelationshipAlias}.{prop.SqlName}"; schema[fullName] = prop.SqlType; @@ -567,7 +567,7 @@ public override INodeSchema GetSchema(NodeCompilationContext context) if (Query.RelationshipQuery?.Properties != null) relationshipProps = relationshipProps.Where(p => Query.RelationshipQuery.Properties.AllProperties || Query.RelationshipQuery.Properties.PropertyNames.Contains(p.PropertyName, StringComparer.OrdinalIgnoreCase)); - foreach (var prop in relationshipProps) + foreach (var prop in relationshipProps.OrderBy(p => p.SqlName)) { var fullName = $"{ManyToManyRelationshipAlias}.{prop.SqlName}"; schema[fullName] = prop.SqlType; diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/NodeSchema.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/NodeSchema.cs index 1b196d43..a2cca010 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/NodeSchema.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/NodeSchema.cs @@ -1,5 +1,7 @@ using System; +using System.Collections; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -10,7 +12,7 @@ namespace MarkMpn.Sql4Cds.Engine.ExecutionPlan /// /// Describes the schema of data produced by a node in an execution plan /// - public class NodeSchema : INodeSchema + class NodeSchema : INodeSchema { /// /// Creates a new @@ -18,7 +20,7 @@ public class NodeSchema : INodeSchema public NodeSchema(IReadOnlyDictionary schema, IReadOnlyDictionary> aliases, string primaryKey, IReadOnlyList notNullColumns, IReadOnlyList sortOrder) { PrimaryKey = primaryKey; - Schema = schema ?? new Dictionary(); + Schema = schema ?? new ColumnList(); Aliases = aliases ?? new Dictionary>(); SortOrder = sortOrder ?? Array.Empty(); NotNullColumns = notNullColumns ?? Array.Empty(); @@ -32,19 +34,12 @@ public NodeSchema(INodeSchema copy) { PrimaryKey = copy.PrimaryKey; - if (copy.Schema is Dictionary schema) - { - Schema = new Dictionary(schema, StringComparer.OrdinalIgnoreCase); - } - else - { - schema = new Dictionary(copy.Schema.Count, StringComparer.OrdinalIgnoreCase); + var schema = new ColumnList(); - foreach (var kvp in copy.Schema) - schema[kvp.Key] = kvp.Value; + foreach (var kvp in copy.Schema) + schema[kvp.Key] = kvp.Value; - Schema = schema; - } + Schema = schema; if (copy.Aliases is Dictionary> aliases) { @@ -168,4 +163,105 @@ public interface INodeSchema /// true if the data is sorted by the required columns, irrespective of the column ordering, or false otherwise bool IsSortedBy(ISet requiredSorts); } + + class ColumnList : IDictionary, IReadOnlyDictionary + { + private readonly OrderedDictionary _inner; + + public ColumnList() + { + _inner = new OrderedDictionary(StringComparer.OrdinalIgnoreCase); + } + + public DataTypeReference this[string key] + { + get => (DataTypeReference)_inner[key]; + set => _inner[key] = value; + } + + public ICollection Keys => _inner.Keys.Cast().ToList(); + + public ICollection Values => _inner.Values.Cast().ToList(); + + public int Count => _inner.Count; + + public bool IsReadOnly => false; + + IEnumerable IReadOnlyDictionary.Keys => _inner.Keys.Cast(); + + IEnumerable IReadOnlyDictionary.Values => _inner.Values.Cast(); + + public void Add(string key, DataTypeReference value) + { + _inner.Add(key, value); + } + + public void Add(KeyValuePair item) + { + _inner.Add(item.Key, item.Value); + } + + public void Clear() + { + _inner.Clear(); + } + + public bool Contains(KeyValuePair item) + { + return TryGetValue(item.Key, out var value) && value == item.Value; + } + + public bool ContainsKey(string key) + { + return _inner.Contains(key); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + _inner.CopyTo(array, arrayIndex); + } + + public IEnumerator> GetEnumerator() + { + var enumerator = _inner.GetEnumerator(); + + while (enumerator.MoveNext()) + yield return new KeyValuePair((string)enumerator.Key, (DataTypeReference)enumerator.Value); + } + + public bool Remove(string key) + { + if (!_inner.Contains(key)) + return false; + + _inner.Remove(key); + return true; + } + + public bool Remove(KeyValuePair item) + { + if (!Contains(item)) + return false; + + _inner.Remove(item.Key); + return true; + } + + public bool TryGetValue(string key, out DataTypeReference value) + { + if (!_inner.Contains(key)) + { + value = null; + return false; + } + + value = (DataTypeReference)_inner[key]; + return true; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + } } diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/RetrieveTotalRecordCountNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/RetrieveTotalRecordCountNode.cs index acc1b475..05f1c7f2 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/RetrieveTotalRecordCountNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/RetrieveTotalRecordCountNode.cs @@ -51,7 +51,7 @@ public override INodeSchema GetSchema(NodeCompilationContext context) { return new NodeSchema( primaryKey: null, - schema: new Dictionary(StringComparer.OrdinalIgnoreCase) + schema: new ColumnList { [$"{EntityName}_count"] = DataTypeHelpers.BigInt }, diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/SelectNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/SelectNode.cs index c402b485..3e778f48 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/SelectNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/SelectNode.cs @@ -284,7 +284,7 @@ internal static void ExpandWildcardColumns(IDataExecutionPlanNodeInternal source continue; } - foreach (var src in sourceSchema.Schema.Keys.Where(k => col.SourceColumn == null || k.StartsWith(col.SourceColumn + ".", StringComparison.OrdinalIgnoreCase)).OrderBy(k => k, StringComparer.OrdinalIgnoreCase)) + foreach (var src in sourceSchema.Schema.Keys.Where(k => col.SourceColumn == null || k.StartsWith(col.SourceColumn + ".", StringComparison.OrdinalIgnoreCase))) { // Columns might be available in the logical source schema but not in // the real one, e.g. due to aggregation diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/SystemFunctionNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/SystemFunctionNode.cs index 3c47caa7..25b3bac9 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/SystemFunctionNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/SystemFunctionNode.cs @@ -50,7 +50,7 @@ public override INodeSchema GetSchema(NodeCompilationContext context) { case SystemFunction.fn_helpcollations: return new NodeSchema( - schema: new Dictionary(StringComparer.OrdinalIgnoreCase) + schema: new ColumnList { ["name"] = DataTypeHelpers.NVarChar(128, dataSource.DefaultCollation, CollationLabel.CoercibleDefault), ["description"] = DataTypeHelpers.NVarChar(1000, dataSource.DefaultCollation, CollationLabel.CoercibleDefault),