protected virtual Expression AnalyzeProjectionQuery(SqlFunctionType specialExpressionType, IList<Expression> parameters, TranslationContext context, bool canHaveFilter = true) { if (context.IsExternalInExpressionChain) { var operand0 = Analyze(parameters[0], context); Expression functionOperand = null; Expression projectionOperand; if ( context.CurrentSelect.NextSelectExpression != null || context.CurrentSelect.Operands.Count() > 0 || context.CurrentSelect.Group.Count > 0 ) { // No TableInfo in projection operand0 = new SubSelectExpression(context.CurrentSelect, operand0.Type, "source", null); context.NewParentSelect(); // In the new scope we should not have MaximumDatabaseLoad //context.QueryContext.MaximumDatabaseLoad = false; context.CurrentSelect.Tables.Add(operand0 as TableExpression); } // basically, we have three options for projection methods: // - projection on grouped table (1 operand, a GroupExpression) // - projection on grouped column (2 operands, GroupExpression and ColumnExpression) // - projection on table/column, with optional restriction var groupOperand0 = operand0 as GroupExpression; if (groupOperand0 != null) { if (parameters.Count > 1) projectionOperand = Analyze(parameters[1], groupOperand0.GroupedExpression, context); else projectionOperand = Analyze(groupOperand0.GroupedExpression, context); } else { projectionOperand = operand0; if (parameters.Count > 1) functionOperand = Analyze(parameters[1], operand0, context); int filterIndex = canHaveFilter ? 1 : -1; //special case for Average - its second parameter is NOT filter CheckWhere(projectionOperand, parameters, filterIndex, context); } if (projectionOperand is TableExpression) projectionOperand = RegisterTable((TableExpression)projectionOperand, context); if (groupOperand0 != null) { var childColumns = GetChildColumns(projectionOperand, context); projectionOperand = new GroupExpression(projectionOperand, groupOperand0.KeyExpression, childColumns); } var opList = new List<Expression>(); opList.Add(projectionOperand); if (functionOperand != null) opList.Add(functionOperand); return CreateSqlFunction(specialExpressionType, opList.ToArray()); } else { var subQueryContext = context.NewSelect(); var tableExpression = Analyze(parameters[0], subQueryContext); //RI: new stuff - handling grouping with aggregates //if (IsAggregate(specialExpressionType)) { var grpExpr = tableExpression as GroupExpression; var srcTable = grpExpr == null ? tableExpression : grpExpr.GroupedExpression ; SqlFunctionExpression specialExpr; if (parameters.Count > 1) { var predicate = Analyze(parameters[1], srcTable, subQueryContext); specialExpr = CreateSqlFunction(specialExpressionType, tableExpression, predicate); } else { specialExpr = CreateSqlFunction(specialExpressionType, tableExpression); } // If subQuery context has no tables added, it is not a subquery, it's just an aggregate function over 'main' table var currSelect = subQueryContext.CurrentSelect; if (currSelect.Tables.Count == 0) return specialExpr; //this is a real subquery, so mutate and return the current select from this context currSelect = currSelect.ChangeOperands(new Expression[] { specialExpr }, currSelect.Operands); return currSelect; //} //RI: end my special code } }
/// <summary> /// "Distinct" means select X group by X /// </summary> /// <param name="parameters"></param> /// <param name="context"></param> /// <returns></returns> protected virtual Expression AnalyzeDistinct(IList<Expression> parameters, TranslationContext context) { var expression = Analyze(parameters[0], context); // we select and group by the same criterion // RI: adding explicit list of columns, to catch situation of duplicate column names in joins var childColumns = GetChildColumns(expression, context); // some providers (ex SQL CE) do not allow Text columns in distinct output CheckDistinctClauseColumns(childColumns, context); var group = new GroupExpression(expression, expression, childColumns); if (context.CurrentSelect.NextSelectExpression != null) { var tableInfo = context.DbModel.GetTable(expression.Type); expression = new SubSelectExpression(context.CurrentSelect, expression.Type, "source", tableInfo); context.NewParentSelect(); // In the new scope we should not have MaximumDatabaseLoad //context.QueryContext.MaximumDatabaseLoad = false; context.CurrentSelect.Tables.Add(expression as TableExpression); } context.CurrentSelect.Group.Add(group); //RI: added this special check // var table // var cols = RegisterAllColumns() // "Distinct" method is equivalent to a GroupBy // but for some obscure reasons, Linq expects a IQueryable instead of an IGrouping // so we return the column, not the group return expression; }
protected virtual Expression AnalyzeGroupBy(MethodInfo method, IList<Expression> parameters, TranslationContext context) { // there are 8 overloads of Queryable.GroupBy method. 4 of them have last parameter IEqualityComparer - reject these overloads // The remaining 4 have Expression<> as last parameter - we use it to detect these. var methodParams = method.GetParameters(); var lastParamType = methodParams[methodParams.Length - 1].ParameterType; if (methodParams.Length > 2 && !lastParamType.IsSubclassOf(typeof(LambdaExpression))) throw Util.Throw("The overload of GroupBy method with IEqualityComparer parameter is not supported."); /* The remaining overloads are the following: (#1) public static IQueryable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector); -- 2 args, 2 type args (#2) public static IQueryable<IGrouping<TKey, TElement>> GroupBy<TSource, TKey, TElement>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector, Expression<Func<TSource, TElement>> elementSelector); -- 3 args, 3 type args (#3) public static IQueryable<TResult> GroupBy<TSource, TKey, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector, Expression<Func<TKey, IEnumerable<TSource>, TResult>> resultSelector); -- 3 args, 3 type args (#4) public static IQueryable<TResult> GroupBy<TSource, TKey, TElement, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector, Expression<Func<TSource, TElement>> elementSelector, Expression<Func<TKey, IEnumerable<TElement>, TResult>> resultSelector); -- 4 args, 4 type args We can distinguish them by number of args; for #2 and #3 - both have 3 args and 3 type args. We distinguish them by analyzing type argument */ // Detect which overload we have int ovldNum = 0; switch (methodParams.Length) { case 2: ovldNum = 1; break; case 4: ovldNum = 4; break; case 3: //analyze type argument of the last parameter (generic expression) var funcType = lastParamType.GenericTypeArguments[0]; //this is a Func<..>, check it count of arguments var funcGenArgCount = funcType.GenericTypeArguments.Length; switch(funcGenArgCount) { case 2: ovldNum = 2; break; case 3: ovldNum = 3; break; } break; } if (ovldNum == 0) throw Util.Throw("Unknown/unsupported overload of GroupBy method - failed to detect overload type."); //analyze parameters var table = Analyze(parameters[0], context); var keySelector = Analyze(parameters[1], table, context); Expression result = null; bool clrGrouping = false; switch(ovldNum) { case 1: //2 params result = table; // we return the whole table clrGrouping = true; break; case 2: //3 params result = Analyze(parameters[2], new Expression[] {table }, context); clrGrouping = true; break; case 3: // 3 params result = Analyze(parameters[2], new Expression[] { keySelector, table }, context); break; case 4: // 4 params Util.Throw("This overload of GroupBy method is not supported. Rewrite the LINQ query."); break; } var childColumns = GetChildColumns(keySelector, context); var group = new GroupExpression(result, keySelector, childColumns, clrGrouping); context.CurrentSelect.Group.Add(group); return group; }
/// <summary> /// Any() returns true if the given condition satisfies at least one of provided elements /// </summary> /// <param name="parameters"></param> /// <param name="context"></param> /// <returns></returns> protected virtual Expression AnalyzeAny(IList<Expression> parameters, TranslationContext context) { if (context.IsExternalInExpressionChain) { var tableExpression = Analyze(parameters[0], context); Expression projectionOperand; if (context.CurrentSelect.NextSelectExpression != null) { TableExpression currentTableExpression = tableExpression as TableExpression; tableExpression = new SubSelectExpression(context.CurrentSelect, currentTableExpression.Type, "source", currentTableExpression.TableInfo); context.NewParentSelect(); // In the new scope we should not have MaximumDatabaseLoad //context.QueryContext.MaximumDatabaseLoad = false; context.CurrentSelect.Tables.Add(tableExpression as TableExpression); } // basically, we have three options for projection methods: // - projection on grouped table (1 operand, a GroupExpression) // - projection on grouped column (2 operands, GroupExpression and ColumnExpression) // - projection on table/column, with optional restriction var groupOperand0 = tableExpression as GroupExpression; if (groupOperand0 != null) { if (parameters.Count > 1) { projectionOperand = Analyze(parameters[1], groupOperand0.GroupedExpression, context); } else projectionOperand = Analyze(groupOperand0.GroupedExpression, context); } else { projectionOperand = tableExpression; CheckWhere(projectionOperand, parameters, 1, context); } if (projectionOperand is TableExpression) projectionOperand = RegisterTable((TableExpression)projectionOperand, context); if (groupOperand0 != null) { var childColumns = GetChildColumns(groupOperand0.KeyExpression, context); projectionOperand = new GroupExpression(projectionOperand, groupOperand0.KeyExpression, childColumns); } return ExpressionUtil.MakeGreaterThanZero(CreateSqlFunction(SqlFunctionType.Count, projectionOperand)); } else { var anyBuilderContext = context.NewSelect(); var tableExpression = Analyze(parameters[0], anyBuilderContext); if (!(tableExpression is TableExpression)) tableExpression = Analyze(tableExpression, anyBuilderContext); // from here we build a custom clause: // <anyClause> ==> "(select count(*) from <table> where <anyClause>)>0" // TODO (later...): see if some vendors support native Any operator and avoid this substitution if (parameters.Count > 1) { var anyClause = Analyze(parameters[1], tableExpression, anyBuilderContext); RegisterWhere(anyClause, anyBuilderContext); } var countExpr = CreateSqlFunction(SqlFunctionType.Count, tableExpression); var currSelect = anyBuilderContext.CurrentSelect; anyBuilderContext.CurrentSelect = currSelect.ChangeOperands(new Expression[] {countExpr}, currSelect.Operands); // TODO: see if we need to register the tablePiece here (we probably don't) // we now switch back to current context, and compare the result with 0 // note - we might have count returning int or long, so we must adjust 0 constant (int or long) var anyExpression = ExpressionUtil.MakeGreaterThanZero(anyBuilderContext.CurrentSelect); return anyExpression; } }