Update Reference Sources to .NET Framework 4.6.1
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Mapping / ViewGeneration / CqlGeneration / CqlBlock.cs
1 //---------------------------------------------------------------------
2 // <copyright file="CqlBlock.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner [....]
7 // @backupOwner [....]
8 //---------------------------------------------------------------------
9
10 using System.Linq;
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;
17 using System.Text;
18 using System.Diagnostics;
19
20 namespace System.Data.Mapping.ViewGeneration.CqlGeneration
21 {
22     /// <summary>
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.
26     /// </summary>
27     internal abstract class CqlBlock : InternalBase
28     {
29         /// <summary>
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"/>).
32         /// </summary>
33         protected CqlBlock(SlotInfo[] slotInfos, List<CqlBlock> children, BoolExpression whereClause, CqlIdentifiers identifiers, int blockAliasNum)
34         {
35             m_slots = new ReadOnlyCollection<SlotInfo>(slotInfos);
36             m_children = new ReadOnlyCollection<CqlBlock>(children);
37             m_whereClause = whereClause;
38             m_blockAlias = identifiers.GetBlockAlias(blockAliasNum);
39         }
40
41         #region Fields
42         /// <summary>
43         /// Essentially, SELECT. May be replaced with another collection after block construction.
44         /// </summary>
45         private ReadOnlyCollection<SlotInfo> m_slots;
46         /// <summary>
47         /// FROM inputs.
48         /// </summary>
49         private readonly ReadOnlyCollection<CqlBlock> m_children;
50         /// <summary>
51         /// WHERER.
52         /// </summary>
53         private readonly BoolExpression m_whereClause;
54         /// <summary>
55         /// Alias of the whole block for cql generation.
56         /// </summary>
57         private readonly string m_blockAlias;
58         /// <summary>
59         /// See <see cref="JoinTreeContext"/> for more info.
60         /// </summary>
61         private JoinTreeContext m_joinTreeContext;
62         #endregion
63
64         #region Properties
65         /// <summary>
66         /// Returns all the slots for this block (SELECT).
67         /// </summary>
68         internal ReadOnlyCollection<SlotInfo> Slots
69         {
70             get { return m_slots; }
71             set { m_slots = value; }
72         }
73
74         /// <summary>
75         /// Returns all the child (input) blocks of this block (FROM).
76         /// </summary>
77         protected ReadOnlyCollection<CqlBlock> Children
78         {
79             get { return m_children; }
80         }
81
82         /// <summary>
83         /// Returns the where clause of this block (WHERE).
84         /// </summary>
85         protected BoolExpression WhereClause
86         {
87             get { return m_whereClause; }
88         }
89
90         /// <summary>
91         /// Returns an alias for this block that can be used for "AS".
92         /// </summary>
93         internal string CqlAlias
94         {
95             get { return m_blockAlias; }
96         }
97         #endregion
98
99         #region Abstract Methods
100         /// <summary>
101         /// Returns a string corresponding to the eSQL representation of this block (and its children below).
102         /// </summary>
103         internal abstract StringBuilder AsEsql(StringBuilder builder, bool isTopLevel, int indentLevel);
104
105         /// <summary>
106         /// Returns a string corresponding to the CQT representation of this block (and its children below).
107         /// </summary>
108         internal abstract DbExpression AsCqt(bool isTopLevel);
109         #endregion
110
111         #region Methods
112         /// <summary>
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"
115         /// </summary>
116         internal QualifiedSlot QualifySlotWithBlockAlias(int slotNum)
117         {
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);
121         }
122
123         internal ProjectedSlot SlotValue(int slotNum)
124         {
125             Debug.Assert(slotNum < m_slots.Count, "Slotnum too high");
126             return m_slots[slotNum].SlotValue;
127         }
128
129         internal MemberPath MemberPath(int slotNum)
130         {
131             Debug.Assert(slotNum < m_slots.Count, "Slotnum too high");
132             return m_slots[slotNum].OutputMember;
133         }
134
135         /// <summary>
136         /// Returns true iff <paramref name="slotNum"/> is being projected by this block.
137         /// </summary>
138         internal bool IsProjected(int slotNum)
139         {
140             Debug.Assert(slotNum < m_slots.Count, "Slotnum too high");
141             return m_slots[slotNum].IsProjected;
142         }
143
144         /// <summary>
145         /// Generates "A, B, C, ..." for all the slots in the block.
146         /// </summary>
147         protected void GenerateProjectionEsql(StringBuilder builder, string blockAlias, bool addNewLineAfterEachSlot, int indentLevel, bool isTopLevel)
148         {
149             bool isFirst = true;
150             foreach (SlotInfo slotInfo in Slots)
151             {
152                 if (false == slotInfo.IsRequiredByParent)
153                 {
154                     // Ignore slots that are not needed
155                     continue;
156                 }
157                 if (isFirst == false)
158                 {
159                     builder.Append(", ");
160                 }
161
162                 if (addNewLineAfterEachSlot)
163                 {
164                     StringUtil.IndentNewLine(builder, indentLevel + 1);
165                 }
166
167                 slotInfo.AsEsql(builder, blockAlias, indentLevel);
168
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))
173                 {
174                     builder.Append(" AS ")
175                            .Append(slotInfo.CqlFieldAlias);
176                 }
177                 isFirst = false;
178             }
179             if (addNewLineAfterEachSlot)
180             {
181                 StringUtil.IndentNewLine(builder, indentLevel);
182             }
183         }
184
185         /// <summary>
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"/>.
188         /// </summary>
189         protected DbExpression GenerateProjectionCqt(DbExpression row, bool isTopLevel)
190         {
191             if (isTopLevel)
192             {
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);
195             }
196             else
197             {
198                 return DbExpressionBuilder.NewRow(
199                     this.Slots.Where(slot => slot.IsRequiredByParent).Select(slot => new KeyValuePair<string, DbExpression>(slot.CqlFieldAlias, slot.AsCqt(row))));
200             }
201         }
202
203         /// <summary>
204         /// Initializes context positioning in the join tree that owns the <see cref="CqlBlock"/>.
205         /// For more info see <see cref="JoinTreeContext"/>.
206         /// </summary>
207         internal void SetJoinTreeContext(IList<string> parentQualifiers, string leafQualifier)
208         {
209             Debug.Assert(m_joinTreeContext == null, "Join tree context is already set.");
210             m_joinTreeContext = new JoinTreeContext(parentQualifiers, leafQualifier);
211         }
212
213         /// <summary>
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.
218         /// </summary>
219         internal DbExpression GetInput(DbExpression row)
220         {
221             return m_joinTreeContext != null ? m_joinTreeContext.FindInput(row) : row;
222         }
223
224         internal override void ToCompactString(StringBuilder builder)
225         {
226             for (int i = 0; i < m_slots.Count; i++)
227             {
228                 StringUtil.FormatStringBuilder(builder, "{0}: ", i);
229                 m_slots[i].ToCompactString(builder);
230                 builder.Append(' ');
231             }
232             m_whereClause.ToCompactString(builder);
233         }
234         #endregion
235
236         #region JoinTreeContext
237         /// <summary>
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:
240         /// 
241         ///                     ___J___
242         ///                    /       \
243         ///                 L3/         \R3
244         ///                  /           \
245         ///               __J__           \
246         ///              /     \           \
247         ///           L2/       \R2         \
248         ///            /         \           \
249         ///          _J_          \           \
250         ///         /   \          \           \
251         ///      L1/     \R1        \           \
252         ///       /       \          \           \
253         /// CqlBlock1   CqlBlock2   CqlBlock3   CqlBlock4
254         /// 
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
261         /// 
262         /// </summary>
263         private sealed class JoinTreeContext
264         {
265             internal JoinTreeContext(IList<string> parentQualifiers, string leafQualifier)
266             {
267                 Debug.Assert(parentQualifiers != null, "parentQualifiers != null");
268                 Debug.Assert(leafQualifier != null, "leafQualifier != null");
269
270                 m_parentQualifiers = parentQualifiers;
271                 m_indexInParentQualifiers = parentQualifiers.Count;
272                 m_leafQualifier = leafQualifier;
273             }
274
275             private readonly IList<string> m_parentQualifiers;
276             private readonly int m_indexInParentQualifiers;
277             private readonly string m_leafQualifier;
278
279             internal DbExpression FindInput(DbExpression row)
280             {
281                 DbExpression cqt = row;
282                 for (int i = m_parentQualifiers.Count - 1; i >= m_indexInParentQualifiers; --i)
283                 {
284                     cqt = cqt.Property(m_parentQualifiers[i]);
285                 }
286                 return cqt.Property(m_leafQualifier);
287             }
288         }
289         #endregion
290     }
291 }