in Microsoft.Azure.Cosmos/src/Linq/QueryUnderConstruction.cs [498:595]
public bool ShouldBeOnNewQuery(string methodName, int argumentCount)
{
// In the LINQ provider perspective, a SQL query (without subquery) the order of the execution of the operations is:
// Join -> Where -> Order By -> Aggregates/Distinct/Select -> Top/Offset Limit
// | |
// |-> Group By->|
//
// The order for the corresponding LINQ operations is:
// SelectMany -> Where -> OrderBy -> Aggregates/Distinct/Select -> Skip/Take
// | |
// |-> Group By->|
//
// In general, if an operation Op1 is being visited and the current query already has Op0 which
// appear not before Op1 in the execution order, then this Op1 needs to be in a new query. This ensures
// the semantics because if both of them are in the same query then the order would be Op0 -> Op1
// which is not true to the order they appear. In this case, Op1 will be consider to be in a parent-query
// in the flattening step.
//
// In some cases, two operations has commutativity property, e.g. Select and Skip/Take/OrderBy/Where.
// So visiting Select after Skip/Take has the same affect as visiting Skip/Take and then Select.
//
// Some operations are represented together with another operations in QueryUnderConstruction for simplicity purpose.
// Therefore, the carrying operation needs to be considered instead. E.g. Aggregation functions are represented as a
// SELECT VALUE <aggregate method> by themselves, Distinct is represented as SELECT VALUE DISTINCT.
//
// The rules in this function are simplified based on the above observations.
bool shouldPackage = false;
switch (methodName)
{
case LinqMethods.Select:
// New query is needed when adding a Select to an existing Select
shouldPackage = this.selectClause != null;
break;
case LinqMethods.Min:
case LinqMethods.Max:
case LinqMethods.Sum:
case LinqMethods.Average:
shouldPackage = (this.selectClause != null) ||
(this.offsetSpec != null) ||
(this.topSpec != null);
break;
case LinqMethods.Count:
// When Count has 2 arguments, it calls into AddWhereClause so it should be considered as a Where in that case.
// Otherwise, treat it as other aggregate functions (using Sum here for simplicity).
shouldPackage = (argumentCount == 2 && this.ShouldBeOnNewQuery(LinqMethods.Where, 2)) ||
this.ShouldBeOnNewQuery(LinqMethods.Sum, 1);
break;
case LinqMethods.Where:
// Where expression parameter needs to be substituted if necessary so
// It is not needed in Select distinct because the Select distinct would have the necessary parameter name adjustment.
case LinqMethods.Any:
case nameof(CosmosLinqExtensions.OrderByRank):
case LinqMethods.OrderBy:
case LinqMethods.OrderByDescending:
case LinqMethods.ThenBy:
case LinqMethods.ThenByDescending:
case LinqMethods.Distinct:
// New query is needed when there is already a Take or a non-distinct Select
// Or when an Order By Rank is added to a query with an Order By clause (and vice versa)
shouldPackage = (this.topSpec != null) ||
(this.offsetSpec != null) ||
(this.selectClause != null && !this.selectClause.HasDistinct) ||
(this.groupByClause != null) ||
(this.orderByClause != null && (methodName == nameof(CosmosLinqExtensions.OrderByRank))) ||
(this.orderByClause != null && (this.orderByClause.Rank == true) && (methodName == LinqMethods.OrderBy));
break;
case LinqMethods.GroupBy:
// New query is needed when there is already a Take or a Select or a Group by clause or an Order By clause
shouldPackage = (this.topSpec != null) ||
(this.offsetSpec != null) ||
(this.selectClause != null) ||
(this.groupByClause != null) ||
(this.orderByClause != null);
break;
case LinqMethods.Skip:
shouldPackage = (this.topSpec != null) ||
(this.limitSpec != null);
break;
case LinqMethods.SelectMany:
shouldPackage = (this.topSpec != null) ||
(this.offsetSpec != null) ||
(this.selectClause != null);
break;
default:
break;
}
return shouldPackage;
}