/// <summary> /// Define GroupBy optimization (try re-use index) /// </summary> private void DefineGroupBy() { if (_query.GroupBy == null) { return; } if (_query.OrderBy != null) { throw new NotSupportedException("GROUP BY expression do not support ORDER BY"); } if (_query.Includes.Count > 0) { throw new NotSupportedException("GROUP BY expression do not support INCLUDE"); } var groupBy = new GroupBy(_query.GroupBy, _queryPlan.Select.Expression, _query.Having); var orderBy = (OrderBy)null; // if groupBy use same expression in index, set group by order to MaxValue to not run if (groupBy.Expression.Source == _queryPlan.IndexExpression) { // great - group by expression are same used in index - no changes here } else { // create orderBy expression orderBy = new OrderBy(groupBy.Expression, Query.Ascending); } _queryPlan.GroupBy = groupBy; _queryPlan.OrderBy = orderBy; }
/// <summary> /// YieldDocuments will run over all key-ordered source and returns groups of source /// </summary> private IEnumerable <BsonDocument> YieldDocuments(BsonValue key, IEnumerator <BsonDocument> enumerator, GroupBy groupBy, Done done) { yield return(enumerator.Current); while (done.Running = enumerator.MoveNext()) { var current = groupBy.Expression.ExecuteScalar(enumerator.Current); if (key == current) { // yield return document in same key (group) yield return(enumerator.Current); } else { groupBy.Select.Parameters["key"] = current; // stop current sequence yield break; } } }
/// <summary> /// GROUP BY: Apply groupBy expression and aggregate results in DocumentGroup /// </summary> private IEnumerable <DocumentGroup> GroupBy(IEnumerable <BsonDocument> source, GroupBy groupBy) { using (var enumerator = source.GetEnumerator()) { var done = new Done { Running = enumerator.MoveNext() }; while (done.Running) { var key = groupBy.Expression.ExecuteScalar(enumerator.Current); groupBy.Select.Parameters["key"] = key; var group = YieldDocuments(key, enumerator, groupBy, done); yield return(new DocumentGroup(key, enumerator.Current, group, _lookup)); } } }
/// <summary> /// Run Select expression over a group source - each group will return a single value /// If contains Having expression, test if result = true before run Select /// </summary> private IEnumerable <BsonDocument> SelectGroupBy(IEnumerable <DocumentGroup> groups, GroupBy groupBy) { var defaultName = groupBy.Select.DefaultFieldName(); foreach (var group in groups) { // transfom group result if contains select expression BsonValue value; try { if (groupBy.Having != null) { var filter = groupBy.Having.ExecuteScalar(group, group.Root, group.Root); if (!filter.IsBoolean || !filter.AsBoolean) { continue; } } value = groupBy.Select.ExecuteScalar(group, group.Root, group.Root); } finally { group.Dispose(); } if (value.IsDocument) { yield return(value.AsDocument); } else { yield return(new BsonDocument { [defaultName] = value }); } } }