protected virtual Expression AnalyzeConstant(Expression expression, BuilderContext builderContext) { // we try to find a non-constant operand, and if we do, we won't change this expression foreach (var operand in expression.GetOperands()) { if (!(operand is ConstantExpression)) return expression; } // now, we just simply return a constant with new value try { var optimizedExpression = Expression.Constant(expression.Evaluate()); // sometimes, optimizing an expression changes its type, and we just can't allow this. if (optimizedExpression.Type == expression.Type) return optimizedExpression; } // if we fail to evaluate the expression, then just return it catch (ArgumentException) { } return expression; }
protected virtual SqlStatement BuildSelect(Expression select, QueryContext queryContext) { var sqlProvider = queryContext.DataContext.Vendor.SqlProvider; var selectClauses = new List<SqlStatement>(); foreach (var selectExpression in select.GetOperands()) { var expressionString = BuildExpression(selectExpression, queryContext); if (selectExpression is SelectExpression) selectClauses.Add(sqlProvider.GetParenthesis(expressionString)); else selectClauses.Add(expressionString); } SelectExpression selectExp = select as SelectExpression; if (selectExp != null) { if (selectExp.Group.Count == 1 && selectExp.Group[0].GroupedExpression == selectExp.Group[0].KeyExpression) { // this is a select DISTINCT expression // TODO: better handle selected columns on DISTINCT: I suspect this will not work in some cases if (selectClauses.Count == 0) { selectClauses.Add(sqlProvider.GetColumns()); } return sqlProvider.GetSelectDistinctClause(selectClauses.ToArray()); } } return sqlProvider.GetSelectClause(selectClauses.ToArray()); }
/// <summary> /// The simple part: converts an expression to SQL /// This is not used for FROM clause /// </summary> /// <param name="expression"></param> /// <param name="queryContext"></param> /// <returns></returns> protected virtual SqlStatement BuildExpression(Expression expression, QueryContext queryContext) { var sqlProvider = queryContext.DataContext.Vendor.SqlProvider; var currentPrecedence = ExpressionQualifier.GetPrecedence(expression); // first convert operands var operands = expression.GetOperands(); var literalOperands = new List<SqlStatement>(); foreach (var operand in operands) { var operandPrecedence = ExpressionQualifier.GetPrecedence(operand); var literalOperand = BuildExpression(operand, queryContext); if (operandPrecedence > currentPrecedence) literalOperand = sqlProvider.GetParenthesis(literalOperand); literalOperands.Add(literalOperand); } // then converts expression if (expression is SpecialExpression) return sqlProvider.GetLiteral(((SpecialExpression)expression).SpecialNodeType, literalOperands); if (expression is EntitySetExpression) expression = ((EntitySetExpression)expression).TableExpression; if (expression is TableExpression) { var tableExpression = (TableExpression)expression; if (tableExpression.Alias != null) // if we have an alias, use it { return sqlProvider.GetColumn(sqlProvider.GetTableAlias(tableExpression.Alias), sqlProvider.GetColumns()); } return sqlProvider.GetColumns(); } if (expression is ColumnExpression) { var columnExpression = (ColumnExpression)expression; if (columnExpression.Table.Alias != null) { return sqlProvider.GetColumn(sqlProvider.GetTableAlias(columnExpression.Table.Alias), columnExpression.Name); } return sqlProvider.GetColumn(columnExpression.Name); } if (expression is InputParameterExpression) { var inputParameterExpression = (InputParameterExpression)expression; if (expression.Type.IsArray) { int i = 0; List<SqlStatement> inputParameters = new List<SqlStatement>(); foreach (object p in (Array)inputParameterExpression.GetValue()) { inputParameters.Add(new SqlStatement(new SqlParameterPart(sqlProvider.GetParameterName(inputParameterExpression.Alias + i.ToString()), inputParameterExpression.Alias + i.ToString()))); ++i; } return new SqlStatement(sqlProvider.GetLiteral(inputParameters.ToArray())); } return new SqlStatement(new SqlParameterPart(sqlProvider.GetParameterName(inputParameterExpression.Alias), inputParameterExpression.Alias)); } if (expression is SelectExpression) return Build((SelectExpression)expression, queryContext); if (expression is ConstantExpression) return sqlProvider.GetLiteral(((ConstantExpression)expression).Value); if (expression is GroupExpression) return BuildExpression(((GroupExpression)expression).GroupedExpression, queryContext); StartIndexOffsetExpression indexExpression = expression as StartIndexOffsetExpression; if (indexExpression!=null) { if (indexExpression.StartsAtOne) { literalOperands.Add(BuildExpression(Expression.Constant(1), queryContext)); return sqlProvider.GetLiteral(ExpressionType.Add, literalOperands); } else return literalOperands.First(); } if (expression.NodeType == ExpressionType.Convert || expression.NodeType == ExpressionType.ConvertChecked) { var unaryExpression = (UnaryExpression)expression; var firstOperand = literalOperands.First(); if (IsConversionRequired(unaryExpression)) return sqlProvider.GetLiteralConvert(firstOperand, unaryExpression.Type); return firstOperand; } return sqlProvider.GetLiteral(expression.NodeType, literalOperands); }
/// <summary> /// Cuts tiers in CLR / SQL. /// The search for cut is top-down /// </summary> /// <param name="expression"></param> /// <param name="dataRecordParameter"></param> /// <param name="mappingContextParameter"></param> /// <param name="builderContext"></param> /// <returns></returns> protected virtual Expression CutOutOperands(Expression expression, ParameterExpression dataRecordParameter, ParameterExpression mappingContextParameter, BuilderContext builderContext) { // two options: we cut and return if (GetCutOutOperand(expression, builderContext)) { // "cutting out" means we replace the current expression by a SQL result reader // before cutting out, we check that we're not cutting a table // in this case, we convert it into its declared columns if (expression is TableExpression) { return GetOutputTableReader((TableExpression)expression, dataRecordParameter, mappingContextParameter, builderContext); } // for EntitySets, we have a special EntitySet builder if (expression is EntitySetExpression) { return GetEntitySetBuilder((EntitySetExpression) expression, dataRecordParameter, mappingContextParameter, builderContext); // TODO record EntitySet information, so we can initalize it with owner } // then, the result is registered return GetOutputValueReader(expression, dataRecordParameter, mappingContextParameter, builderContext); } // or we dig down var operands = new List<Expression>(); foreach (var operand in expression.GetOperands()) { operands.Add(operand == null ? null : CutOutOperands(operand, dataRecordParameter, mappingContextParameter, builderContext)); } return expression.ChangeOperands(operands); }
protected virtual Expression AnalyzeNull(Expression expression, BuilderContext builderContext) { // this first test only to speed up things a little if (expression.NodeType == ExpressionType.Equal || expression.NodeType == ExpressionType.NotEqual) { var operands = expression.GetOperands().ToList(); var nullComparison = GetNullComparison(expression.NodeType, operands[0], operands[1]); if (nullComparison == null) nullComparison = GetNullComparison(expression.NodeType, operands[1], operands[0]); if (nullComparison != null) return nullComparison; return expression; } return expression; }
protected virtual Expression AnalyzeConstant(Expression expression, BuilderContext builderContext) { // we try to find a non-constant operand, and if we do, we won't change this expression foreach (var operand in expression.GetOperands()) { if (!(operand is ConstantExpression)) return expression; } if (expression.NodeType == ExpressionType.Parameter) return expression; if (expression.NodeType == (ExpressionType)SpecialExpressionType.Like) return expression; // SETuse // If the value of the first SpecialExpressionType change this 999 should change too if ((short)expression.NodeType > 999) return expression; // now, we just simply return a constant with new value try { var optimizedExpression = Expression.Constant(expression.Evaluate()); // sometimes, optimizing an expression changes its type, and we just can't allow this. if (optimizedExpression.Type == expression.Type) return optimizedExpression; } // if we fail to evaluate the expression, then just return it catch (ArgumentException) { return expression; } return expression; }
protected virtual string BuildSelect(Expression select, QueryContext queryContext) { var sqlProvider = queryContext.DataContext.Vendor.SqlProvider; var selectClauses = new List<string>(); foreach (var selectExpression in select.GetOperands()) { selectClauses.Add(BuildExpression(selectExpression, queryContext)); } return sqlProvider.GetSelectClause(selectClauses.ToArray()); }
/// <summary> /// The simple part: converts an expression to SQL /// This is not used for FROM clause /// </summary> /// <param name="expression"></param> /// <param name="queryContext"></param> /// <returns></returns> protected virtual string BuildExpression(Expression expression, QueryContext queryContext) { var sqlProvider = queryContext.DataContext.Vendor.SqlProvider; var currentPrecedence = ExpressionQualifier.GetPrecedence(expression); // first convert operands var operands = expression.GetOperands(); var literalOperands = new List<string>(); foreach (var operand in operands) { var operandPrecedence = ExpressionQualifier.GetPrecedence(operand); string literalOperand = BuildExpression(operand, queryContext); if (operandPrecedence > currentPrecedence) literalOperand = sqlProvider.GetParenthesis(literalOperand); literalOperands.Add(literalOperand); } // then converts expression if (expression is SpecialExpression) return sqlProvider.GetLiteral(((SpecialExpression)expression).SpecialNodeType, literalOperands); if (expression is TableExpression) { var tableExpression = (TableExpression)expression; if (tableExpression.Alias != null) // if we have an alias, use it { return sqlProvider.GetColumn(sqlProvider.GetTableAlias(tableExpression.Alias), sqlProvider.GetColumns()); } return sqlProvider.GetColumns(); } if (expression is ColumnExpression) { var columnExpression = (ColumnExpression)expression; if (columnExpression.Table.Alias != null) { return sqlProvider.GetColumn(sqlProvider.GetTableAlias(columnExpression.Table.Alias), columnExpression.Name); } return sqlProvider.GetColumn(columnExpression.Name); } if (expression is InputParameterExpression) return sqlProvider.GetParameterName(((InputParameterExpression)expression).Alias); if (expression is SelectExpression) return Build((SelectExpression)expression, queryContext); if (expression is ConstantExpression) return sqlProvider.GetLiteral(((ConstantExpression)expression).Value); if (expression is GroupExpression) return BuildExpression(((GroupExpression)expression).GroupedExpression, queryContext); if (expression.NodeType == ExpressionType.Convert || expression.NodeType == ExpressionType.ConvertChecked) return sqlProvider.GetLiteralConvert(literalOperands.First(), (expression as UnaryExpression).Type); return sqlProvider.GetLiteral(expression.NodeType, literalOperands); }
protected virtual Expression AnalyzeOperands(Expression expression, TranslationContext context) { var operands = expression.GetOperands().ToList(); var newOperands = new List<Expression>(); for (int operandIndex = 0; operandIndex < operands.Count; operandIndex++) { var op = operands[operandIndex]; var newOp = Analyze(op, context); newOperands.Add(newOp); } return expression.ChangeOperands(newOperands, operands); }
// TODO: (RI:) should be removed most likely protected Expression AnalyzeTimeSpanMemberAccess(Expression objectExpression, MemberInfo memberInfo) { //A timespan expression can be only generated in a c# query as a DateTime difference, as a function call return or as a paramter //this case is for the DateTime difference operation if (!(objectExpression is BinaryExpression)) throw new NotSupportedException(); var operands = objectExpression.GetOperands(); bool absoluteSpam = memberInfo.Name.StartsWith("Total"); string operationKey = absoluteSpam ? memberInfo.Name.Substring(5) : memberInfo.Name; Expression currentExpression; switch (operationKey) { case "Milliseconds": currentExpression = Expression.Convert(CreateSqlFunction(SqlFunctionType.DateDiffInMilliseconds, operands.First(), operands.ElementAt(1)), typeof(double)); break; case "Seconds": currentExpression = Expression.Divide( Expression.Convert(CreateSqlFunction(SqlFunctionType.DateDiffInMilliseconds, operands.First(), operands.ElementAt(1)), typeof(double)), Expression.Constant(1000.0)); break; case "Minutes": currentExpression = Expression.Divide( Expression.Convert(CreateSqlFunction(SqlFunctionType.DateDiffInMilliseconds, operands.First(), operands.ElementAt(1)), typeof(double)), Expression.Constant(60000.0)); break; case "Hours": currentExpression = Expression.Divide( Expression.Convert(CreateSqlFunction(SqlFunctionType.DateDiffInMilliseconds, operands.First(), operands.ElementAt(1)), typeof(double)), Expression.Constant(3600000.0)); break; case "Days": currentExpression = Expression.Divide( Expression.Convert(CreateSqlFunction(SqlFunctionType.DateDiffInMilliseconds, operands.First(), operands.ElementAt(1)), typeof(double)), Expression.Constant(86400000.0)); break; default: throw new NotSupportedException(string.Format("The operation {0} over the TimeSpan isn't currently supported", memberInfo.Name)); } if (!absoluteSpam) { switch (memberInfo.Name) { case "Milliseconds": currentExpression = Expression.Convert(Expression.Modulo(Expression.Convert(currentExpression, typeof(long)), Expression.Constant(1000L)), typeof(int)); break; case "Seconds": currentExpression = Expression.Convert(Expression.Modulo(Expression.Convert(currentExpression, typeof(long)), Expression.Constant(60L)), typeof(int)); break; case "Minutes": currentExpression = Expression.Convert(Expression.Modulo(Expression.Convert(currentExpression, typeof(long)), Expression.Constant(60L)), typeof(int)); break; case "Hours": currentExpression = Expression.Convert(Expression.Modulo(Expression.Convert( currentExpression, typeof(long)), Expression.Constant(24L)), typeof(int)); break; case "Days": currentExpression = Expression.Convert(currentExpression, typeof(int)); break; } } return currentExpression; }
protected virtual Expression CutOutSqlTier(Expression expression, ParameterExpression dataRecordParameter, ParameterExpression sessionParameter, Type expectedType, TranslationContext context) { expectedType = expectedType ?? expression.Type; // two options: we cut and return if (IsSqlTier(expression, context)) { // "cutting out" means we replace the current expression by a SQL result reader // before cutting out, we check that we're not cutting a table in this case, we convert it into its declared columns if (expression is TableExpression) // RI: entity reader comes here return GetOutputTableReader((TableExpression)expression, dataRecordParameter, sessionParameter, context); // RI: single-value result goes here return GetOutputValueReader(expression, expectedType, dataRecordParameter, sessionParameter, context); } // RI: Anon types, custom types go here var newOperands = new List<Expression>(); var operands = expression.GetOperands(); var argTypes = expression.GetArgumentTypes(); for(int i = 0; i < operands.Count; i++ ) { var op = operands[i]; var newOp = op == null ? null : CutOutSqlTier(op, dataRecordParameter, sessionParameter, argTypes[i], context); newOperands.Add(newOp); } Expression newExpr; if (expression is MetaTableExpression) //Joins go here newExpr = ((MetaTableExpression)expression).ConvertToNew(newOperands); else newExpr = expression.ChangeOperands(newOperands, operands); return newExpr; }
protected virtual SqlStatement BuildExpression(Expression expression) { if (expression is ConstantExpression) return _sqlProvider.GetLiteral(((ConstantExpression)expression).Value); var currentPrecedence = ExpressionUtil.GetPrecedence(expression); // first convert operands var operands = expression.GetOperands(); var literalOperands = new List<SqlStatement>(); foreach (var operand in operands) { var operandPrecedence = ExpressionUtil.GetPrecedence(operand); var literalOperand = BuildExpression(operand); if (operandPrecedence > currentPrecedence) literalOperand = _sqlProvider.GetParenthesis(literalOperand); literalOperands.Add(literalOperand); } if (expression is AliasedExpression) { var aliasExpr = (AliasedExpression) expression; return BuildExpression(aliasExpr.Expression); //Alias will be added later } // then converts expression if (expression is SqlFunctionExpression) { var specExpr = (SqlFunctionExpression)expression; //RI: Special case for multiple "*" operands if (specExpr.FunctionType == SqlFunctionType.Count && literalOperands.Count > 0) { literalOperands.Clear(); literalOperands.Add("*"); } return _sqlProvider.GetSqlFunction(specExpr.FunctionType, specExpr.ForceIgnoreCase, literalOperands); } if (expression is TableExpression) { var tableExpression = (TableExpression)expression; if (tableExpression.Alias != null) // if we have an alias, use it { return _sqlProvider.GetColumn(_sqlProvider.GetTableAlias(tableExpression.Alias), _sqlProvider.GetColumns()); } return _sqlProvider.GetColumns(); } //RI: We might have NewExpression here! Query: (from b in books select new {b.Title}).Count(); // in this case the NewExpression is 'hidden' inside subquery and it is not visible to CutOutOperands // We just return list of arguments (columns) of New expression if (expression is NewExpression) return new SqlStatement(literalOperands); //RI: adding this if (expression is MetaTableExpression) { var metaTable = (MetaTableExpression)expression; return _sqlProvider.GetColumns(); } if (expression is ColumnExpression) { var columnExpression = (ColumnExpression)expression; if(columnExpression.Table.Alias != null) { return _sqlProvider.GetColumn(_sqlProvider.GetTableAlias(columnExpression.Table.Alias), columnExpression.Name); } //RI: changed this to keep output type var sqlPart = new SqlLiteralPart(_sqlProvider.GetColumn(columnExpression.Name), expression.Type); return new SqlStatement(sqlPart); //return _sqlProvider.GetColumn(columnExpression.Name); } if (expression is ExternalValueExpression) { var extValue = (ExternalValueExpression)expression; switch(extValue.SqlUse) { case ExternalValueSqlUse.Literal: var sql = _sqlProvider.GetLiteral(extValue.LiteralValue); return sql; case ExternalValueSqlUse.Parameter: // In SQL templates the first 2 args are reserved for { and } symbols return _sqlProvider.GetParameter(extValue); default: // we should never get here Util.Throw("LINQ engine error: encountered ExternalValueExpression with invalid Usage type: {0}, Expression: {1}", extValue.SqlUse, extValue.SourceExpression); return null; //never happens } } if (expression is SelectExpression) return BuildSelectSql((SelectExpression)expression); if (expression is GroupExpression) return BuildExpression(((GroupExpression)expression).GroupedExpression); StartIndexOffsetExpression indexExpression = expression as StartIndexOffsetExpression; if (indexExpression!=null) { if (indexExpression.StartsAtOne) { literalOperands.Add(BuildExpression(Expression.Constant(1))); return _sqlProvider.GetLiteral(ExpressionType.Add, literalOperands); } else return literalOperands.First(); } if (expression.NodeType == ExpressionType.Convert || expression.NodeType == ExpressionType.ConvertChecked) { var unaryExpression = (UnaryExpression)expression; var firstOperand = literalOperands.First(); if (IsConversionRequired(unaryExpression)) return _sqlProvider.GetLiteralConvert(firstOperand, unaryExpression.Type); return firstOperand; } if (expression is BinaryExpression || expression is UnaryExpression) return _sqlProvider.GetLiteral(expression.NodeType, literalOperands); if (expression is TableFilterExpression) return _sqlProvider.GetTableFilter((TableFilterExpression)expression); return _sqlProvider.GetLiteral(expression.NodeType, literalOperands); }
protected virtual SqlStatement BuildSelectClause(Expression select) { var selectClauses = new List<SqlStatement>(); var ops = select.GetOperands().ToList(); foreach(var outputExpr in ops) { var exprString = BuildOutputExpression(outputExpr); selectClauses.Add(exprString); } SelectExpression selectExp = select as SelectExpression; if(selectExp != null) { if(selectExp.Group.Count == 1 && selectExp.Group[0].IsDistinct) { //RI: changed code var g = selectExp.Group[0]; var outCols = new List<SqlStatement>(); foreach(var col in g.Columns) { var sqlCol = col.Table.Alias == null ? _sqlProvider.GetColumn(col.Name) : _sqlProvider.GetColumn(_sqlProvider.GetTableAlias(col.Table.Alias), col.Name); if(!string.IsNullOrEmpty(col.Alias)) sqlCol += " AS " + col.Alias; outCols.Add(sqlCol); } return _sqlProvider.GetSelectDistinctClause(outCols.ToArray()); /* // this is a select DISTINCT expression // TODO: better handle selected columns on DISTINCT: I suspect this will not work in some cases if (selectClauses.Count == 0) { selectClauses.Add(_sqlProvider.GetColumns()); } return _sqlProvider.GetSelectDistinctClause(selectClauses.ToArray()); */ } } return _sqlProvider.GetSelectClause(selectClauses.ToArray()); }