1 //---------------------------------------------------------------------
2 // <copyright file="CqlBlock.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
8 //---------------------------------------------------------------------
11 using System.Data.Common.CommandTrees;
12 using System.Data.Common.CommandTrees.ExpressionBuilder;
13 using System.Data.Common.Utils;
14 using System.Collections.Generic;
15 using System.Data.Mapping.ViewGeneration.Structures;
16 using System.Collections.ObjectModel;
18 using System.Diagnostics;
20 namespace System.Data.Mapping.ViewGeneration.CqlGeneration
23 /// A class that holds an expression of the form "(SELECT .. FROM .. WHERE) AS alias".
24 /// Essentially, it allows generating Cql query in a localized manner, i.e., all global decisions about nulls, constants,
25 /// case statements, etc have already been made.
27 internal abstract class CqlBlock : InternalBase
30 /// Initializes a <see cref="CqlBlock"/> with the SELECT (<paramref name="slotInfos"/>), FROM (<paramref name="children"/>),
31 /// WHERE (<paramref name="whereClause"/>), AS (<paramref name="blockAliasNum"/>).
33 protected CqlBlock(SlotInfo[] slotInfos, List<CqlBlock> children, BoolExpression whereClause, CqlIdentifiers identifiers, int blockAliasNum)
35 m_slots = new ReadOnlyCollection<SlotInfo>(slotInfos);
36 m_children = new ReadOnlyCollection<CqlBlock>(children);
37 m_whereClause = whereClause;
38 m_blockAlias = identifiers.GetBlockAlias(blockAliasNum);
43 /// Essentially, SELECT. May be replaced with another collection after block construction.
45 private ReadOnlyCollection<SlotInfo> m_slots;
49 private readonly ReadOnlyCollection<CqlBlock> m_children;
53 private readonly BoolExpression m_whereClause;
55 /// Alias of the whole block for cql generation.
57 private readonly string m_blockAlias;
59 /// See <see cref="JoinTreeContext"/> for more info.
61 private JoinTreeContext m_joinTreeContext;
66 /// Returns all the slots for this block (SELECT).
68 internal ReadOnlyCollection<SlotInfo> Slots
70 get { return m_slots; }
71 set { m_slots = value; }
75 /// Returns all the child (input) blocks of this block (FROM).
77 protected ReadOnlyCollection<CqlBlock> Children
79 get { return m_children; }
83 /// Returns the where clause of this block (WHERE).
85 protected BoolExpression WhereClause
87 get { return m_whereClause; }
91 /// Returns an alias for this block that can be used for "AS".
93 internal string CqlAlias
95 get { return m_blockAlias; }
99 #region Abstract Methods
101 /// Returns a string corresponding to the eSQL representation of this block (and its children below).
103 internal abstract StringBuilder AsEsql(StringBuilder builder, bool isTopLevel, int indentLevel);
106 /// Returns a string corresponding to the CQT representation of this block (and its children below).
108 internal abstract DbExpression AsCqt(bool isTopLevel);
113 /// For the given <paramref name="slotNum"/> creates a <see cref="QualifiedSlot"/> qualified with <see cref="CqlAlias"/> of the current block:
114 /// "<see cref="CqlAlias"/>.slot_alias"
116 internal QualifiedSlot QualifySlotWithBlockAlias(int slotNum)
118 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));
119 var slotInfo = m_slots[slotNum];
120 return new QualifiedSlot(this, slotInfo.SlotValue);
123 internal ProjectedSlot SlotValue(int slotNum)
125 Debug.Assert(slotNum < m_slots.Count, "Slotnum too high");
126 return m_slots[slotNum].SlotValue;
129 internal MemberPath MemberPath(int slotNum)
131 Debug.Assert(slotNum < m_slots.Count, "Slotnum too high");
132 return m_slots[slotNum].OutputMember;
136 /// Returns true iff <paramref name="slotNum"/> is being projected by this block.
138 internal bool IsProjected(int slotNum)
140 Debug.Assert(slotNum < m_slots.Count, "Slotnum too high");
141 return m_slots[slotNum].IsProjected;
145 /// Generates "A, B, C, ..." for all the slots in the block.
147 protected void GenerateProjectionEsql(StringBuilder builder, string blockAlias, bool addNewLineAfterEachSlot, int indentLevel, bool isTopLevel)
150 foreach (SlotInfo slotInfo in Slots)
152 if (false == slotInfo.IsRequiredByParent)
154 // Ignore slots that are not needed
157 if (isFirst == false)
159 builder.Append(", ");
162 if (addNewLineAfterEachSlot)
164 StringUtil.IndentNewLine(builder, indentLevel + 1);
167 slotInfo.AsEsql(builder, blockAlias, indentLevel);
169 // Print the field alias for complex expressions that don't produce default alias.
170 // Don't print alias for qualified fields as they reproduce their alias.
171 // Don't print alias if it's a top level query using SELECT VALUE.
172 if (!isTopLevel && (!(slotInfo.SlotValue is QualifiedSlot) || slotInfo.IsEnforcedNotNull))
174 builder.Append(" AS ")
175 .Append(slotInfo.CqlFieldAlias);
179 if (addNewLineAfterEachSlot)
181 StringUtil.IndentNewLine(builder, indentLevel);
186 /// Generates "NewRow(A, B, C, ...)" for all the slots in the block.
187 /// If <paramref name="isTopLevel"/>=true then generates "A" for the only slot that is marked as <see cref="SlotInfo.IsRequiredByParent"/>.
189 protected DbExpression GenerateProjectionCqt(DbExpression row, bool isTopLevel)
193 Debug.Assert(this.Slots.Where(slot => slot.IsRequiredByParent).Count() == 1, "Top level projection must project only one slot.");
194 return this.Slots.Where(slot => slot.IsRequiredByParent).Single().AsCqt(row);
198 return DbExpressionBuilder.NewRow(
199 this.Slots.Where(slot => slot.IsRequiredByParent).Select(slot => new KeyValuePair<string, DbExpression>(slot.CqlFieldAlias, slot.AsCqt(row))));
204 /// Initializes context positioning in the join tree that owns the <see cref="CqlBlock"/>.
205 /// For more info see <see cref="JoinTreeContext"/>.
207 internal void SetJoinTreeContext(IList<string> parentQualifiers, string leafQualifier)
209 Debug.Assert(m_joinTreeContext == null, "Join tree context is already set.");
210 m_joinTreeContext = new JoinTreeContext(parentQualifiers, leafQualifier);
214 /// Searches the input <paramref name="row"/> for the property that represents the current <see cref="CqlBlock"/>.
215 /// In all cases except JOIN, the <paramref name="row"/> is returned as is.
216 /// In case of JOIN, <paramref name="row"/>.JoinVarX.JoinVarY...blockVar is returned.
217 /// See <see cref="SetJoinTreeContext"/> for more info.
219 internal DbExpression GetInput(DbExpression row)
221 return m_joinTreeContext != null ? m_joinTreeContext.FindInput(row) : row;
224 internal override void ToCompactString(StringBuilder builder)
226 for (int i = 0; i < m_slots.Count; i++)
228 StringUtil.FormatStringBuilder(builder, "{0}: ", i);
229 m_slots[i].ToCompactString(builder);
232 m_whereClause.ToCompactString(builder);
236 #region JoinTreeContext
238 /// The class represents a position of a <see cref="CqlBlock"/> in a join tree.
239 /// It is expected that the join tree is left-recursive (not balanced) and looks like this:
253 /// CqlBlock1 CqlBlock2 CqlBlock3 CqlBlock4
255 /// Example of <see cref="JoinTreeContext"/>s for the <see cref="CqlBlock"/>s:
256 /// block# m_parentQualifiers m_indexInParentQualifiers m_leafQualifier FindInput(row) = ...
257 /// 1 (L2, L3) 0 L1 row.(L3.L2).L1
258 /// 2 (L2, L3) 0 R1 row.(L3.L2).R1
259 /// 3 (L2, L3) 1 R2 row.(L3).R2
260 /// 4 (L2, L3) 2 R3 row.().R3
263 private sealed class JoinTreeContext
265 internal JoinTreeContext(IList<string> parentQualifiers, string leafQualifier)
267 Debug.Assert(parentQualifiers != null, "parentQualifiers != null");
268 Debug.Assert(leafQualifier != null, "leafQualifier != null");
270 m_parentQualifiers = parentQualifiers;
271 m_indexInParentQualifiers = parentQualifiers.Count;
272 m_leafQualifier = leafQualifier;
275 private readonly IList<string> m_parentQualifiers;
276 private readonly int m_indexInParentQualifiers;
277 private readonly string m_leafQualifier;
279 internal DbExpression FindInput(DbExpression row)
281 DbExpression cqt = row;
282 for (int i = m_parentQualifiers.Count - 1; i >= m_indexInParentQualifiers; --i)
284 cqt = cqt.Property(m_parentQualifiers[i]);
286 return cqt.Property(m_leafQualifier);