private IDataExecutionPlanNode FoldToStreamAggregate(IDictionary <string, DataSource> dataSources, IQueryExecutionOptions options, IDictionary <string, Type> parameterTypes, IList <OptimizerHint> hints) { // Use stream aggregate where possible - if there are no grouping fields or the groups can be folded into sorts var streamAggregate = new StreamAggregateNode { Source = Source }; streamAggregate.GroupBy.AddRange(GroupBy); foreach (var aggregate in Aggregates) { streamAggregate.Aggregates[aggregate.Key] = aggregate.Value; } if (!IsScalarAggregate) { // Use hash grouping if explicitly requested with optimizer hint if (hints != null && hints.Any(h => h.HintKind == OptimizerHintKind.HashGroup)) { return(this); } var sorts = new SortNode { Source = Source }; foreach (var group in GroupBy) { sorts.Sorts.Add(new ExpressionWithSortOrder { Expression = group, SortOrder = SortOrder.Ascending }); } streamAggregate.Source = sorts.FoldQuery(dataSources, options, parameterTypes, hints); // Don't bother using a sort + stream aggregate if none of the sorts can be folded if (streamAggregate.Source == sorts && sorts.PresortedCount == 0) { return(this); } } return(streamAggregate); }
public override IDataExecutionPlanNode FoldQuery(IDictionary <string, DataSource> dataSources, IQueryExecutionOptions options, IDictionary <string, Type> parameterTypes, IList <OptimizerHint> hints) { Source = Source.FoldQuery(dataSources, options, parameterTypes, hints); Source.Parent = this; // Remove any duplicated column names for (var i = Columns.Count - 1; i >= 0; i--) { if (Columns.IndexOf(Columns[i]) < i) { Columns.RemoveAt(i); } } // If one of the fields to include in the DISTINCT calculation is the primary key, there is no possibility of duplicate // rows so we can discard the distinct node var schema = Source.GetSchema(dataSources, parameterTypes); if (!String.IsNullOrEmpty(schema.PrimaryKey) && Columns.Contains(schema.PrimaryKey, StringComparer.OrdinalIgnoreCase)) { return(Source); } if (Source is FetchXmlScan fetch) { fetch.FetchXml.distinct = true; fetch.FetchXml.distinctSpecified = true; // Ensure there is a sort order applied to avoid paging issues if (fetch.Entity.Items == null || !fetch.Entity.Items.OfType <FetchOrderType>().Any()) { // Sort by each attribute. Make sure we only add one sort per attribute, taking virtual attributes // into account (i.e. don't attempt to sort on statecode and statecodename) var sortedAttributes = new HashSet <string>(StringComparer.OrdinalIgnoreCase); foreach (var column in Columns) { if (!schema.ContainsColumn(column, out var normalized)) { continue; } if (!sortedAttributes.Add(normalized)) { continue; } var parts = normalized.Split('.'); if (parts.Length != 2) { continue; } if (parts[0].Equals(fetch.Alias, StringComparison.OrdinalIgnoreCase)) { var attr = dataSources[fetch.DataSource].Metadata[fetch.Entity.name].Attributes.SingleOrDefault(a => a.LogicalName.Equals(parts[1], StringComparison.OrdinalIgnoreCase)); if (attr == null) { continue; } if (attr.AttributeOf != null && !sortedAttributes.Add(parts[0] + "." + attr.AttributeOf)) { continue; } fetch.Entity.AddItem(new FetchOrderType { attribute = parts[1] }); } else { var linkEntity = fetch.Entity.FindLinkEntity(parts[0]); var attr = dataSources[fetch.DataSource].Metadata[linkEntity.name].Attributes.SingleOrDefault(a => a.LogicalName.Equals(parts[1], StringComparison.OrdinalIgnoreCase)); if (attr == null) { continue; } if (attr.AttributeOf != null && !sortedAttributes.Add(parts[0] + "." + attr.AttributeOf)) { continue; } linkEntity.AddItem(new FetchOrderType { attribute = parts[1] }); } } } return(fetch); } // If the data is already sorted by all the distinct columns we can use a stream aggregate instead. // We don't mind what order the columns are sorted in though, so long as the distinct columns form a // prefix of the sort order. var requiredSorts = new HashSet <string>(StringComparer.OrdinalIgnoreCase); foreach (var col in Columns) { if (!schema.ContainsColumn(col, out var column)) { return(this); } requiredSorts.Add(column); } if (!schema.IsSortedBy(requiredSorts)) { return(this); } var aggregate = new StreamAggregateNode { Source = Source }; Source.Parent = aggregate; for (var i = 0; i < requiredSorts.Count; i++) { aggregate.GroupBy.Add(schema.SortOrder[i].ToColumnReference()); } return(aggregate); }