//--------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner [....] // @backupOwner [....] //--------------------------------------------------------------------- using System.Data.Common.CommandTrees; using System.Data.Common.CommandTrees.ExpressionBuilder; using System.Data.Common.Utils; using System.Text; using System.Collections.Generic; using System.Data.Mapping.ViewGeneration.Structures; using System.Diagnostics; namespace System.Data.Mapping.ViewGeneration.CqlGeneration { /// /// Represents to the various Join nodes in the view: IJ, LOJ, FOJ. /// internal sealed class JoinCqlBlock : CqlBlock { #region Constructor /// /// Creates a join block (type given by ) with SELECT (), FROM (), /// ON ( - one for each child except 0th), WHERE (true), AS (). /// internal JoinCqlBlock(CellTreeOpType opType, SlotInfo[] slotInfos, List children, List onClauses, CqlIdentifiers identifiers, int blockAliasNum) : base(slotInfos, children, BoolExpression.True, identifiers, blockAliasNum) { m_opType = opType; m_onClauses = onClauses; } #endregion #region Fields private readonly CellTreeOpType m_opType; private readonly List m_onClauses; #endregion #region Methods internal override StringBuilder AsEsql(StringBuilder builder, bool isTopLevel, int indentLevel) { // The SELECT part. StringUtil.IndentNewLine(builder, indentLevel); builder.Append("SELECT "); GenerateProjectionEsql( builder, null, /* There is no single input, so the blockAlias is null. ProjectedSlot objects will have to carry their own input block info: * see QualifiedSlot and QualifiedCellIdBoolean for more info. */ false, indentLevel, isTopLevel); StringUtil.IndentNewLine(builder, indentLevel); // The FROM part by joining all the children using ON Clauses. builder.Append("FROM "); int i = 0; foreach (CqlBlock child in Children) { if (i > 0) { StringUtil.IndentNewLine(builder, indentLevel + 1); builder.Append(OpCellTreeNode.OpToEsql(m_opType)); } builder.Append(" ("); child.AsEsql(builder, false, indentLevel + 1); builder.Append(") AS ") .Append(child.CqlAlias); // The ON part. if (i > 0) { StringUtil.IndentNewLine(builder, indentLevel + 1); builder.Append("ON "); m_onClauses[i - 1].AsEsql(builder); } i++; } return builder; } internal override DbExpression AsCqt(bool isTopLevel) { // The FROM part: // - build a tree of binary joins out of the inputs (this.Children). // - update each child block with its relative position in the join tree, // so that QualifiedSlot and QualifiedCellIdBoolean objects could find their // designated block areas inside the cumulative join row passed into their AsCqt(row) method. CqlBlock leftmostBlock = this.Children[0]; DbExpression left = leftmostBlock.AsCqt(false); List joinTreeCtxParentQualifiers = new List(); for (int i = 1; i < this.Children.Count; ++i) { // Join the current left expression (a tree) to the current right block. CqlBlock rightBlock = this.Children[i]; DbExpression right = rightBlock.AsCqt(false); Func joinConditionFunc = m_onClauses[i - 1].AsCqt; DbJoinExpression join; switch (m_opType) { case CellTreeOpType.FOJ: join = left.FullOuterJoin(right, joinConditionFunc); break; case CellTreeOpType.IJ: join = left.InnerJoin(right, joinConditionFunc); break; case CellTreeOpType.LOJ: join = left.LeftOuterJoin(right, joinConditionFunc); break; default: Debug.Fail("Unknown operator"); return null; } if (i == 1) { // Assign the joinTreeContext to the leftmost block. leftmostBlock.SetJoinTreeContext(joinTreeCtxParentQualifiers, join.Left.VariableName); } else { // Update the joinTreeCtxParentQualifiers. // Note that all blocks that already participate in the left expression tree share the same copy of the joinTreeContext. joinTreeCtxParentQualifiers.Add(join.Left.VariableName); } // Assign the joinTreeContext to the right block. rightBlock.SetJoinTreeContext(joinTreeCtxParentQualifiers, join.Right.VariableName); left = join; } // The SELECT part. return left.Select(row => GenerateProjectionCqt(row, false)); } #endregion /// /// Represents a complete ON clause "slot1 == slot2 AND "slot3 == slot4" ... for two s. /// internal sealed class OnClause : InternalBase { #region Constructor internal OnClause() { m_singleClauses = new List(); } #endregion #region Fields private readonly List m_singleClauses; #endregion #region Methods /// /// Adds an element for a join of the form = . /// internal void Add(QualifiedSlot leftSlot, MemberPath leftSlotOutputMember, QualifiedSlot rightSlot, MemberPath rightSlotOutputMember) { SingleClause singleClause = new SingleClause(leftSlot, leftSlotOutputMember, rightSlot, rightSlotOutputMember); m_singleClauses.Add(singleClause); } /// /// Generates eSQL string of the form "LeftSlot1 = RightSlot1 AND LeftSlot2 = RightSlot2 AND ... /// internal StringBuilder AsEsql(StringBuilder builder) { bool isFirst = true; foreach (SingleClause singleClause in m_singleClauses) { if (false == isFirst) { builder.Append(" AND "); } singleClause.AsEsql(builder); isFirst = false; } return builder; } /// /// Generates CQT of the form "LeftSlot1 = RightSlot1 AND LeftSlot2 = RightSlot2 AND ... /// internal DbExpression AsCqt(DbExpression leftRow, DbExpression rightRow) { DbExpression cqt = m_singleClauses[0].AsCqt(leftRow, rightRow); for (int i = 1; i < m_singleClauses.Count; ++i) { cqt = cqt.And(m_singleClauses[i].AsCqt(leftRow, rightRow)); } return cqt; } internal override void ToCompactString(StringBuilder builder) { builder.Append("ON "); StringUtil.ToSeparatedString(builder, m_singleClauses, " AND "); } #endregion #region SingleClause /// /// Represents an expression between slots of the form: LeftSlot = RightSlot /// private sealed class SingleClause : InternalBase { internal SingleClause(QualifiedSlot leftSlot, MemberPath leftSlotOutputMember, QualifiedSlot rightSlot, MemberPath rightSlotOutputMember) { m_leftSlot = leftSlot; m_leftSlotOutputMember = leftSlotOutputMember; m_rightSlot = rightSlot; m_rightSlotOutputMember = rightSlotOutputMember; } #region Fields private readonly QualifiedSlot m_leftSlot; private readonly MemberPath m_leftSlotOutputMember; private readonly QualifiedSlot m_rightSlot; private readonly MemberPath m_rightSlotOutputMember; #endregion #region Methods /// /// Generates eSQL string of the form "leftSlot = rightSlot". /// internal StringBuilder AsEsql(StringBuilder builder) { builder.Append(m_leftSlot.GetQualifiedCqlName(m_leftSlotOutputMember)) .Append(" = ") .Append(m_rightSlot.GetQualifiedCqlName(m_rightSlotOutputMember)); return builder; } /// /// Generates CQT of the form "leftSlot = rightSlot". /// internal DbExpression AsCqt(DbExpression leftRow, DbExpression rightRow) { return m_leftSlot.AsCqt(leftRow, m_leftSlotOutputMember).Equal(m_rightSlot.AsCqt(rightRow, m_rightSlotOutputMember)); } internal override void ToCompactString(StringBuilder builder) { m_leftSlot.ToCompactString(builder); builder.Append(" = "); m_rightSlot.ToCompactString(builder); } #endregion } #endregion } } }