//--------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner [....] // @backupOwner [....] //--------------------------------------------------------------------- using System.Linq; using System.Data.Common.CommandTrees; using System.Data.Common.CommandTrees.ExpressionBuilder; using System.Data.Common.Utils; using System.Collections.Generic; using System.Data.Mapping.ViewGeneration.Structures; using System.Collections.ObjectModel; using System.Text; using System.Diagnostics; namespace System.Data.Mapping.ViewGeneration.CqlGeneration { /// /// A class that holds an expression of the form "(SELECT .. FROM .. WHERE) AS alias". /// Essentially, it allows generating Cql query in a localized manner, i.e., all global decisions about nulls, constants, /// case statements, etc have already been made. /// internal abstract class CqlBlock : InternalBase { /// /// Initializes a with the SELECT (), FROM (), /// WHERE (), AS (). /// protected CqlBlock(SlotInfo[] slotInfos, List children, BoolExpression whereClause, CqlIdentifiers identifiers, int blockAliasNum) { m_slots = new ReadOnlyCollection(slotInfos); m_children = new ReadOnlyCollection(children); m_whereClause = whereClause; m_blockAlias = identifiers.GetBlockAlias(blockAliasNum); } #region Fields /// /// Essentially, SELECT. May be replaced with another collection after block construction. /// private ReadOnlyCollection m_slots; /// /// FROM inputs. /// private readonly ReadOnlyCollection m_children; /// /// WHERER. /// private readonly BoolExpression m_whereClause; /// /// Alias of the whole block for cql generation. /// private readonly string m_blockAlias; /// /// See for more info. /// private JoinTreeContext m_joinTreeContext; #endregion #region Properties /// /// Returns all the slots for this block (SELECT). /// internal ReadOnlyCollection Slots { get { return m_slots; } set { m_slots = value; } } /// /// Returns all the child (input) blocks of this block (FROM). /// protected ReadOnlyCollection Children { get { return m_children; } } /// /// Returns the where clause of this block (WHERE). /// protected BoolExpression WhereClause { get { return m_whereClause; } } /// /// Returns an alias for this block that can be used for "AS". /// internal string CqlAlias { get { return m_blockAlias; } } #endregion #region Abstract Methods /// /// Returns a string corresponding to the eSQL representation of this block (and its children below). /// internal abstract StringBuilder AsEsql(StringBuilder builder, bool isTopLevel, int indentLevel); /// /// Returns a string corresponding to the CQT representation of this block (and its children below). /// internal abstract DbExpression AsCqt(bool isTopLevel); #endregion #region Methods /// /// For the given creates a qualified with of the current block: /// ".slot_alias" /// internal QualifiedSlot QualifySlotWithBlockAlias(int slotNum) { Debug.Assert(this.IsProjected(slotNum), StringUtil.FormatInvariant("Slot {0} that is to be qualified with the block alias is not projected in this block", slotNum)); var slotInfo = m_slots[slotNum]; return new QualifiedSlot(this, slotInfo.SlotValue); } internal ProjectedSlot SlotValue(int slotNum) { Debug.Assert(slotNum < m_slots.Count, "Slotnum too high"); return m_slots[slotNum].SlotValue; } internal MemberPath MemberPath(int slotNum) { Debug.Assert(slotNum < m_slots.Count, "Slotnum too high"); return m_slots[slotNum].OutputMember; } /// /// Returns true iff is being projected by this block. /// internal bool IsProjected(int slotNum) { Debug.Assert(slotNum < m_slots.Count, "Slotnum too high"); return m_slots[slotNum].IsProjected; } /// /// Generates "A, B, C, ..." for all the slots in the block. /// protected void GenerateProjectionEsql(StringBuilder builder, string blockAlias, bool addNewLineAfterEachSlot, int indentLevel, bool isTopLevel) { bool isFirst = true; foreach (SlotInfo slotInfo in Slots) { if (false == slotInfo.IsRequiredByParent) { // Ignore slots that are not needed continue; } if (isFirst == false) { builder.Append(", "); } if (addNewLineAfterEachSlot) { StringUtil.IndentNewLine(builder, indentLevel + 1); } slotInfo.AsEsql(builder, blockAlias, indentLevel); // Print the field alias for complex expressions that don't produce default alias. // Don't print alias for qualified fields as they reproduce their alias. // Don't print alias if it's a top level query using SELECT VALUE. if (!isTopLevel && (!(slotInfo.SlotValue is QualifiedSlot) || slotInfo.IsEnforcedNotNull)) { builder.Append(" AS ") .Append(slotInfo.CqlFieldAlias); } isFirst = false; } if (addNewLineAfterEachSlot) { StringUtil.IndentNewLine(builder, indentLevel); } } /// /// Generates "NewRow(A, B, C, ...)" for all the slots in the block. /// If =true then generates "A" for the only slot that is marked as . /// protected DbExpression GenerateProjectionCqt(DbExpression row, bool isTopLevel) { if (isTopLevel) { Debug.Assert(this.Slots.Where(slot => slot.IsRequiredByParent).Count() == 1, "Top level projection must project only one slot."); return this.Slots.Where(slot => slot.IsRequiredByParent).Single().AsCqt(row); } else { return DbExpressionBuilder.NewRow( this.Slots.Where(slot => slot.IsRequiredByParent).Select(slot => new KeyValuePair(slot.CqlFieldAlias, slot.AsCqt(row)))); } } /// /// Initializes context positioning in the join tree that owns the . /// For more info see . /// internal void SetJoinTreeContext(IList parentQualifiers, string leafQualifier) { Debug.Assert(m_joinTreeContext == null, "Join tree context is already set."); m_joinTreeContext = new JoinTreeContext(parentQualifiers, leafQualifier); } /// /// Searches the input for the property that represents the current . /// In all cases except JOIN, the is returned as is. /// In case of JOIN, .JoinVarX.JoinVarY...blockVar is returned. /// See for more info. /// internal DbExpression GetInput(DbExpression row) { return m_joinTreeContext != null ? m_joinTreeContext.FindInput(row) : row; } internal override void ToCompactString(StringBuilder builder) { for (int i = 0; i < m_slots.Count; i++) { StringUtil.FormatStringBuilder(builder, "{0}: ", i); m_slots[i].ToCompactString(builder); builder.Append(' '); } m_whereClause.ToCompactString(builder); } #endregion #region JoinTreeContext /// /// The class represents a position of a in a join tree. /// It is expected that the join tree is left-recursive (not balanced) and looks like this: /// /// ___J___ /// / \ /// L3/ \R3 /// / \ /// __J__ \ /// / \ \ /// L2/ \R2 \ /// / \ \ /// _J_ \ \ /// / \ \ \ /// L1/ \R1 \ \ /// / \ \ \ /// CqlBlock1 CqlBlock2 CqlBlock3 CqlBlock4 /// /// Example of s for the s: /// block# m_parentQualifiers m_indexInParentQualifiers m_leafQualifier FindInput(row) = ... /// 1 (L2, L3) 0 L1 row.(L3.L2).L1 /// 2 (L2, L3) 0 R1 row.(L3.L2).R1 /// 3 (L2, L3) 1 R2 row.(L3).R2 /// 4 (L2, L3) 2 R3 row.().R3 /// /// private sealed class JoinTreeContext { internal JoinTreeContext(IList parentQualifiers, string leafQualifier) { Debug.Assert(parentQualifiers != null, "parentQualifiers != null"); Debug.Assert(leafQualifier != null, "leafQualifier != null"); m_parentQualifiers = parentQualifiers; m_indexInParentQualifiers = parentQualifiers.Count; m_leafQualifier = leafQualifier; } private readonly IList m_parentQualifiers; private readonly int m_indexInParentQualifiers; private readonly string m_leafQualifier; internal DbExpression FindInput(DbExpression row) { DbExpression cqt = row; for (int i = m_parentQualifiers.Count - 1; i >= m_indexInParentQualifiers; --i) { cqt = cqt.Property(m_parentQualifiers[i]); } return cqt.Property(m_leafQualifier); } } #endregion } }