/// <summary> /// This is called after a relational node's input has been visited, and the /// input's sql statement cannot be reused. <see cref="Visit(DbProjectExpression)"/> /// /// When the input's sql statement cannot be reused, we create a new sql /// statement, with the old one as the from clause of the new statement. /// /// The old statement must be completed i.e. if it has an empty select list, /// the list of columns must be projected out. /// /// If the old statement being completed has a join symbol as its from extent, /// the new statement must have a clone of the join symbol as its extent. /// We cannot reuse the old symbol, but the new select statement must behave /// as though it is working over the "join" record. /// </summary> /// <param name="oldStatement"></param> /// <param name="inputVarName"></param> /// <param name="inputVarType"></param> /// <param name="finalizeOldStatement"></param> /// <param name="fromSymbol"></param> /// <returns>A new select statement, with the old one as the from clause.</returns> private SqlSelectStatement CreateNewSelectStatement(SqlSelectStatement oldStatement, string inputVarName, TypeUsage inputVarType, bool finalizeOldStatement, out Symbol fromSymbol) { fromSymbol = null; // Finalize the old statement if (finalizeOldStatement && oldStatement.Select.IsEmpty) { List <Symbol> columns = AddDefaultColumns(oldStatement); // Thid could not have been called from a join node. Debug.Assert(oldStatement.FromExtents.Count == 1); // if the oldStatement has a join as its input, ... // clone the join symbol, so that we "reuse" the // join symbol. Normally, we create a new symbol - see the next block // of code. JoinSymbol oldJoinSymbol = oldStatement.FromExtents[0] as JoinSymbol; if (oldJoinSymbol != null) { // Note: oldStatement.FromExtents will not do, since it might // just be an alias of joinSymbol, and we want an actual JoinSymbol. JoinSymbol newJoinSymbol = new JoinSymbol(inputVarName, inputVarType, oldJoinSymbol.ExtentList); // This indicates that the oldStatement is a blocking scope // i.e. it hides/renames extent columns newJoinSymbol.IsNestedJoin = true; newJoinSymbol.ColumnList = columns; newJoinSymbol.FlattenedExtentList = oldJoinSymbol.FlattenedExtentList; fromSymbol = newJoinSymbol; } } if (fromSymbol == null) { if (oldStatement.OutputColumnsRenamed) { fromSymbol = new Symbol(inputVarName, inputVarType, oldStatement.OutputColumns); } else { // This is just a simple extent/SqlSelectStatement, // and we can get the column list from the type. fromSymbol = new Symbol(inputVarName, inputVarType); } } // Observe that the following looks like the body of Visit(ExtentExpression). SqlSelectStatement selectStatement = new SqlSelectStatement(); selectStatement.From.Append("( "); selectStatement.From.Append(oldStatement); selectStatement.From.AppendLine(); selectStatement.From.Append(") "); return(selectStatement); }
/// <summary> /// This is called by the relational nodes. It does the following /// <list> /// <item>If the input is not a SqlSelectStatement, it assumes that the input /// is a collection expression, and creates a new SqlSelectStatement </item> /// </list> /// </summary> /// <param name="inputExpression"></param> /// <param name="inputVarName"></param> /// <param name="inputVarType"></param> /// <param name="fromSymbol"></param> /// <returns>A <see cref="SqlSelectStatement"/> and the main fromSymbol /// for this select statement.</returns> private SqlSelectStatement VisitInputExpression(DbExpression inputExpression, string inputVarName, TypeUsage inputVarType, out Symbol fromSymbol) { SqlSelectStatement result; ISqlFragment sqlFragment = inputExpression.Accept(this); result = sqlFragment as SqlSelectStatement; if (result == null) { result = new SqlSelectStatement(); WrapNonQueryExtent(result, sqlFragment, inputExpression.ExpressionKind); } if (result.FromExtents.Count == 0) { // input was an extent fromSymbol = new Symbol(inputVarName, inputVarType); } else if (result.FromExtents.Count == 1) { // input was Filter/GroupBy/Project/OrderBy // we are likely to reuse this statement. fromSymbol = result.FromExtents[0]; } else { // input was a join. // we are reusing the select statement produced by a Join node // we need to remove the original extents, and replace them with a // new extent with just the Join symbol. JoinSymbol joinSymbol = new JoinSymbol(inputVarName, inputVarType, result.FromExtents); joinSymbol.FlattenedExtentList = result.AllJoinExtents; fromSymbol = joinSymbol; result.FromExtents.Clear(); result.FromExtents.Add(fromSymbol); } return(result); }
/// <summary> /// This is called from <see cref="VisitJoinExpression"/>. /// /// This is responsible for maintaining the symbol table after visiting /// a child of a join expression. /// /// The child's sql statement may need to be completed. /// /// The child's result could be one of /// <list type="number"> /// <item>The same as the parent's - this is treated specially.</item> /// <item>A sql select statement, which may need to be completed</item> /// <item>An extent - just copy it to the from clause</item> /// <item>Anything else (from a collection-valued expression) - /// unnest and copy it.</item> /// </list> /// /// If the input was a Join, we need to create a new join symbol, /// otherwise, we create a normal symbol. /// /// We then call AddFromSymbol to add the AS clause, and update the symbol table. /// /// /// /// If the child's result was the same as the parent's, we have to clean up /// the list of symbols in the FromExtents list, since this contains symbols from /// the children of both the parent and the child. /// The happens when the child visited is a Join, and is the leftmost child of /// the parent. /// </summary> /// <param name="fromExtentFragment"></param> /// <param name="result"></param> /// <param name="input"></param> /// <param name="fromSymbolStart"></param> private void ProcessJoinInputResult(ISqlFragment fromExtentFragment, SqlSelectStatement result, DbExpressionBinding input, int fromSymbolStart) { Symbol fromSymbol = null; if (result != fromExtentFragment) { // The child has its own select statement, and is not reusing // our select statement. // This should look a lot like VisitInputExpression(). SqlSelectStatement sqlSelectStatement = fromExtentFragment as SqlSelectStatement; if (sqlSelectStatement != null) { if (sqlSelectStatement.Select.IsEmpty) { List <Symbol> columns = AddDefaultColumns(sqlSelectStatement); if (IsJoinExpression(input.Expression) || IsApplyExpression(input.Expression)) { List <Symbol> extents = sqlSelectStatement.FromExtents; JoinSymbol newJoinSymbol = new JoinSymbol(input.VariableName, input.VariableType, extents); newJoinSymbol.IsNestedJoin = true; newJoinSymbol.ColumnList = columns; fromSymbol = newJoinSymbol; } else { // this is a copy of the code in CreateNewSelectStatement. // if the oldStatement has a join as its input, ... // clone the join symbol, so that we "reuse" the // join symbol. Normally, we create a new symbol - see the next block // of code. JoinSymbol oldJoinSymbol = sqlSelectStatement.FromExtents[0] as JoinSymbol; if (oldJoinSymbol != null) { // Note: sqlSelectStatement.FromExtents will not do, since it might // just be an alias of joinSymbol, and we want an actual JoinSymbol. JoinSymbol newJoinSymbol = new JoinSymbol(input.VariableName, input.VariableType, oldJoinSymbol.ExtentList); // This indicates that the sqlSelectStatement is a blocking scope // i.e. it hides/renames extent columns newJoinSymbol.IsNestedJoin = true; newJoinSymbol.ColumnList = columns; newJoinSymbol.FlattenedExtentList = oldJoinSymbol.FlattenedExtentList; fromSymbol = newJoinSymbol; } else if (sqlSelectStatement.FromExtents[0].OutputColumnsRenamed) { fromSymbol = new Symbol(input.VariableName, input.VariableType, sqlSelectStatement.FromExtents[0].Columns); } } } else if (sqlSelectStatement.OutputColumnsRenamed) { fromSymbol = new Symbol(input.VariableName, input.VariableType, sqlSelectStatement.OutputColumns); } result.From.Append(" ("); result.From.Append(sqlSelectStatement); result.From.Append(" )"); } else if (input.Expression is DbScanExpression) { result.From.Append(fromExtentFragment); } else // bracket it { WrapNonQueryExtent(result, fromExtentFragment, input.Expression.ExpressionKind); } if (fromSymbol == null) // i.e. not a join symbol { fromSymbol = new Symbol(input.VariableName, input.VariableType); } AddFromSymbol(result, input.VariableName, fromSymbol); result.AllJoinExtents.Add(fromSymbol); } else // result == fromExtentFragment. The child extents have been merged into the parent's. { // we are adding extents to the current sql statement via flattening. // We are replacing the child's extents with a single Join symbol. // The child's extents are all those following the index fromSymbolStart. // List <Symbol> extents = new List <Symbol>(); // We cannot call extents.AddRange, since the is no simple way to // get the range of symbols fromSymbolStart..result.FromExtents.Count // from result.FromExtents. // We copy these symbols to create the JoinSymbol later. for (int i = fromSymbolStart; i < result.FromExtents.Count; ++i) { extents.Add(result.FromExtents[i]); } result.FromExtents.RemoveRange(fromSymbolStart, result.FromExtents.Count - fromSymbolStart); fromSymbol = new JoinSymbol(input.VariableName, input.VariableType, extents); result.FromExtents.Add(fromSymbol); // this Join Symbol does not have its own select statement, so we // do not set IsNestedJoin // We do not call AddFromSymbol(), since we do not want to add // "AS alias" to the FROM clause- it has been done when the extent was added earlier. symbolTable.Add(input.VariableName, fromSymbol); } }