37005f747e96181a27c558a719221147f2c3abbd
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Mapping / ViewGeneration / CqlGenerator.cs
1 //---------------------------------------------------------------------
2 // <copyright file="CQLGenerator.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner Microsoft
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
9
10 using System.Data.Common;
11 using System.Data.Common.CommandTrees;
12 using System.Data.Common.Utils;
13 using System.Data.Mapping.ViewGeneration.Structures;
14 using System.Collections.Generic;
15 using System.Data.Mapping.ViewGeneration.CqlGeneration;
16 using System.Text;
17 using System.Diagnostics;
18 using System.Data.Metadata.Edm;
19
20 namespace System.Data.Mapping.ViewGeneration
21 {
22     /// <summary>
23     /// This class is responsible for generation of CQL after the cell merging process has been done.
24     /// </summary>
25     internal sealed class CqlGenerator : InternalBase
26     {
27         #region Constructor
28         /// <summary>
29         /// Given the generated <paramref name="view"/>, the <paramref name="caseStatements"/> for the multiconstant fields,
30         /// the <paramref name="projectedSlotMap"/> that maps different paths of the entityset (for which the view is being generated) to slot indexes in the view,
31         /// creates an object that is capable of generating the Cql for <paramref name="view"/>.
32         /// </summary>
33         internal CqlGenerator(CellTreeNode view,
34                               Dictionary<MemberPath,
35                               CaseStatement> caseStatements,
36                               CqlIdentifiers identifiers,
37                               MemberProjectionIndex projectedSlotMap,
38                               int numCellsInView,
39                               BoolExpression topLevelWhereClause,
40                               StorageMappingItemCollection mappingItemCollection)
41         {
42             m_view = view;
43             m_caseStatements = caseStatements;
44             m_projectedSlotMap = projectedSlotMap;
45             m_numBools = numCellsInView; // We have that many booleans
46             m_topLevelWhereClause = topLevelWhereClause;
47             m_identifiers = identifiers;
48             m_mappingItemCollection = mappingItemCollection;
49         }
50         #endregion
51
52         #region Fields
53         /// <summary>
54         /// The generated view from the cells.
55         /// </summary>
56         private readonly CellTreeNode m_view;
57         /// <summary>
58         /// Case statements for the multiconstant fields.
59         /// </summary>
60         private readonly Dictionary<MemberPath, CaseStatement> m_caseStatements;
61         /// <summary>
62         /// Mapping from member paths to slot indexes.
63         /// </summary>
64         private MemberProjectionIndex m_projectedSlotMap;
65         /// <summary>
66         /// Number of booleans in the view, one per cell (from0, from1, etc...)
67         /// </summary>
68         private readonly int m_numBools;
69         /// <summary>
70         /// A counter used to generate aliases for blocks.
71         /// </summary>
72         private int m_currentBlockNum = 0;
73         private readonly BoolExpression m_topLevelWhereClause;
74         /// <summary>
75         /// Identifiers used in the Cql queries.
76         /// </summary>
77         private readonly CqlIdentifiers m_identifiers;
78         private readonly StorageMappingItemCollection m_mappingItemCollection;
79         #endregion
80
81         #region Properties
82         private int TotalSlots
83         {
84             get { return m_projectedSlotMap.Count + m_numBools; }
85         }
86         #endregion
87
88         #region CqlBlock generation methods for all node types
89         /// <summary>
90         /// Returns eSQL query that represents a query/update mapping view for the view information that was supplied in the constructor.
91         /// </summary>
92         internal string GenerateEsql()
93         {
94             // Generate a CqlBlock tree and then convert that to eSQL.
95             CqlBlock blockTree = GenerateCqlBlockTree();
96
97             // Create the string builder with 1K so that we don't have to
98             // keep growing it
99             StringBuilder builder = new StringBuilder(1024);
100             blockTree.AsEsql(builder, true, 1);
101             return  builder.ToString();
102         }
103
104         /// <summary>
105         /// Returns Cqtl query that represents a query/update mapping view for the view information that was supplied in the constructor.
106         /// </summary>
107         internal DbQueryCommandTree GenerateCqt()
108         {
109             // Generate a CqlBlock tree and then convert that to CQT.
110             CqlBlock blockTree = GenerateCqlBlockTree();
111
112             DbExpression query = blockTree.AsCqt(true);
113             Debug.Assert(query != null, "Null CQT generated for query/update view.");
114
115             return DbQueryCommandTree.FromValidExpression(m_mappingItemCollection.Workspace, TargetPerspective.TargetPerspectiveDataSpace, query);
116         }
117
118         /// <summary>
119         /// Generates a <see cref="CqlBlock"/> tree that is capable of generating the actual Cql strings.
120         /// </summary>
121         private CqlBlock GenerateCqlBlockTree()
122         {
123             // Essentially, we create a block for each CellTreeNode in the
124             // tree and then we layer case statements on top of that view --
125             // one case statement for each multiconstant entry
126
127             // Dertmine the slots that are projected by the whole tree. Tell
128             // the children that they need to produce those slots somehow --
129             // if they don't have it, they can produce null
130             bool[] requiredSlots = GetRequiredSlots();
131             Debug.Assert(requiredSlots.Length == TotalSlots, "Wrong number of requiredSlots");
132
133             List<WithRelationship> withRelationships = new List<WithRelationship>();
134             CqlBlock viewBlock = m_view.ToCqlBlock(requiredSlots, m_identifiers, ref m_currentBlockNum, ref withRelationships);
135
136             // Handle case statements for multiconstant entries
137             // Right now, we have a simplication step that removes one of the
138             // entries and adds ELSE instead
139             foreach (CaseStatement statement in m_caseStatements.Values)
140             {
141                 statement.Simplify();
142             }
143
144             // Generate the case statements and get the top level block which
145             // must correspond to the entity set
146             CqlBlock finalViewBlock = ConstructCaseBlocks(viewBlock, withRelationships);
147             return finalViewBlock;
148         }
149
150         private bool[] GetRequiredSlots()
151         {
152             bool[] requiredSlots = new bool[TotalSlots];
153             // union all slots that are required in case statements
154             foreach (CaseStatement caseStatement in m_caseStatements.Values)
155             {
156                 GetRequiredSlotsForCaseMember(caseStatement.MemberPath, requiredSlots);
157             }
158
159             // For now, make sure that all booleans are required
160             // Reason: OUTER JOINs may introduce an extra CASE statement (in OpCellTreeNode.cs/GetJoinSlotInfo)
161             // if a member is projected in both inputs to the join.
162             // This case statement may use boolean variables that may not be marked as "required"
163             // The problem is that this decision is made _after_ CqlBlocks for children get produced (in OpCellTreeNode.cs/JoinToCqlBlock)
164             for (int i = TotalSlots - m_numBools; i < TotalSlots; i++)
165             {
166                 requiredSlots[i] = true;
167             }
168             // Because of the above we don't need to harvest used booleans from the top-level WHERE clause
169             // m_topLevelWhereClause.GetRequiredSlots(m_projectedSlotMap, requiredSlots);
170
171             // Do we require the case statement member slot be produced by the inner queries?
172             foreach (CaseStatement caseStatement in m_caseStatements.Values)
173             {
174                 bool notNeeded = !caseStatement.MemberPath.IsPartOfKey && // keys are required in inner queries for joins conditions
175                                  !caseStatement.DependsOnMemberValue; // if case statement returns its slot value as one of the options, then we need to produce it
176                 if (notNeeded)
177                 {
178                     requiredSlots[m_projectedSlotMap.IndexOf(caseStatement.MemberPath)] = false;
179                 }
180             }
181             return requiredSlots;
182         }
183         #endregion
184
185         #region Multiconstant CaseStatement methods
186         /// <summary>
187         /// Given the <paramref name="viewBlock"/> tree, generates the case statement blocks on top of it (using <see cref="m_caseStatements"/>) and returns the resulting tree.
188         /// One block per case statement is generated. Generated blocks are nested, with the <paramref name="viewBlock"/> is the innermost input.
189         /// </summary>
190         private CqlBlock ConstructCaseBlocks(CqlBlock viewBlock, IEnumerable<WithRelationship> withRelationships)
191         {
192             // Get the 0th slot only, i.e., the extent
193             bool[] topSlots = new bool[TotalSlots];
194             topSlots[0] = true;
195
196             // all booleans in the top-level WHERE clause are required and get bubbled up
197             // this makes some _fromX booleans be marked as 'required by parent'
198             m_topLevelWhereClause.GetRequiredSlots(m_projectedSlotMap, topSlots);
199             CqlBlock result = ConstructCaseBlocks(viewBlock, 0, topSlots, withRelationships);
200             return result;
201         }
202
203         /// <summary>
204         /// Given the <paramref name="viewBlock"/> tree generated by the cell merging process and the <paramref name="parentRequiredSlots"/>,
205         /// generates the block tree for the case statement at or past the startSlotNum, i.e., only for case statements that are beyond startSlotNum.
206         /// </summary>
207         private CqlBlock ConstructCaseBlocks(CqlBlock viewBlock, int startSlotNum, bool[] parentRequiredSlots, IEnumerable<WithRelationship> withRelationships)
208         {
209             int numMembers = m_projectedSlotMap.Count;
210             // Find the next slot for which we have a case statement, i.e.,
211             // which was in the multiconstants
212             int foundSlot = FindNextCaseStatementSlot(startSlotNum, parentRequiredSlots, numMembers);
213
214             if (foundSlot == -1)
215             {
216                 // We have bottomed out - no more slots to generate cases for
217                 // Just get the base view block
218                 return viewBlock;
219             }
220
221             // Compute the requiredSlots for this member, i.e., what slots are needed to produce this member.
222             MemberPath thisMember = m_projectedSlotMap[foundSlot];
223             bool[] thisRequiredSlots = new bool[TotalSlots];
224             GetRequiredSlotsForCaseMember(thisMember, thisRequiredSlots);
225             Debug.Assert(thisRequiredSlots.Length == parentRequiredSlots.Length &&
226                          thisRequiredSlots.Length == TotalSlots,
227                          "Number of slots in array should not vary across blocks");
228
229             // Merge parent's requirements with this requirements
230             for (int i = 0; i < TotalSlots; i++)
231             {
232                 // We do ask the children to generate the slot that we are
233                 // producing if it is available
234                 if (parentRequiredSlots[i])
235                 {
236                     thisRequiredSlots[i] = true;
237                 }
238             }
239
240             // If current case statement depends on its slot value, then make sure the value is produced by the child block.
241             CaseStatement thisCaseStatement = m_caseStatements[thisMember];
242             thisRequiredSlots[foundSlot] = thisCaseStatement.DependsOnMemberValue;
243
244             // Recursively, determine the block tree for slots beyond foundSlot.
245             CqlBlock childBlock = ConstructCaseBlocks(viewBlock, foundSlot + 1, thisRequiredSlots, null);
246
247             // For each slot, create a SlotInfo object
248             SlotInfo[] slotInfos = CreateSlotInfosForCaseStatement(parentRequiredSlots, foundSlot, childBlock, thisCaseStatement, withRelationships);
249             m_currentBlockNum++;
250
251             // We have a where clause only at the top level
252             BoolExpression whereClause = startSlotNum == 0 ? m_topLevelWhereClause : BoolExpression.True;
253             if (startSlotNum == 0)
254             {
255                 // only slot #0 is required by parent; reset all 'required by parent' booleans introduced above
256                 for (int i = 1; i < slotInfos.Length; i++)
257                 {
258                     slotInfos[i].ResetIsRequiredByParent();
259                 }
260             }
261
262             CaseCqlBlock result = new CaseCqlBlock(slotInfos, foundSlot, childBlock, whereClause, m_identifiers, m_currentBlockNum);
263             return result;
264         }
265
266         /// <summary>
267         /// Given the slot (<paramref name="foundSlot"/>) and its corresponding case statement (<paramref name="thisCaseStatement"/>),
268         /// generates the slotinfos for the cql block producing the case statement.
269         /// </summary>
270         private SlotInfo[] CreateSlotInfosForCaseStatement(bool[] parentRequiredSlots, 
271                                                            int foundSlot,
272                                                            CqlBlock childBlock, 
273                                                            CaseStatement thisCaseStatement,
274                                                            IEnumerable<WithRelationship> withRelationships)
275         {
276             int numSlotsAddedByChildBlock = childBlock.Slots.Count - TotalSlots;
277             SlotInfo[] slotInfos = new SlotInfo[TotalSlots + numSlotsAddedByChildBlock];
278             for (int slotNum = 0; slotNum < TotalSlots; slotNum++)
279             {
280                 bool isProjected = childBlock.IsProjected(slotNum);
281                 bool isRequiredByParent = parentRequiredSlots[slotNum];
282                 ProjectedSlot slot = childBlock.SlotValue(slotNum);
283                 MemberPath outputMember = GetOutputMemberPath(slotNum);
284                 if (slotNum == foundSlot)
285                 {
286                     // We need a case statement instead for this slot that we
287                     // are handling right now
288                     Debug.Assert(isRequiredByParent, "Case result not needed by parent");
289
290                     // Get a case statement with all slots replaced by aliases slots
291                     CaseStatement newCaseStatement = thisCaseStatement.DeepQualify(childBlock);
292                     slot = new CaseStatementProjectedSlot(newCaseStatement, withRelationships);
293                     isProjected = true; // We are projecting this slot now
294                 }
295                 else if (isProjected && isRequiredByParent)
296                 {
297                     // We only alias something that is needed and is being projected by the child.
298                     // It is a qualified slot into the child block.
299                     slot = childBlock.QualifySlotWithBlockAlias(slotNum);
300                 }
301                 // For slots, if it is not required by the parent, we want to
302                 // set the isRequiredByParent for this slot to be
303                 // false. Furthermore, we do not want to introduce any "NULL
304                 // AS something" at this stage for slots not being
305                 // projected. So if the child does not project that slot, we
306                 // declare it as not being required by the parent (if such a
307                 // NULL was needed, it would have been pushed all the way
308                 // down to a non-case block.
309                 // Essentially, from a Case statement's parent perspective,
310                 // it is saying "If you can produce a slot either by yourself
311                 // or your children, please do. Otherwise, do not concoct anything"
312                 SlotInfo slotInfo = new SlotInfo(isRequiredByParent && isProjected, isProjected, slot, outputMember);
313                 slotInfos[slotNum] = slotInfo;
314             }
315             for (int i = TotalSlots; i < TotalSlots + numSlotsAddedByChildBlock; i++)
316             {
317                 QualifiedSlot childAddedSlot = childBlock.QualifySlotWithBlockAlias(i);
318                 slotInfos[i] = new SlotInfo(true, true, childAddedSlot, childBlock.MemberPath(i));
319             }
320             return slotInfos;
321         }
322
323         /// <summary>
324         /// Returns the next slot starting at <paramref name="startSlotNum"/> that is present in the <see cref="m_caseStatements"/>.
325         /// </summary>
326         private int FindNextCaseStatementSlot(int startSlotNum, bool[] parentRequiredSlots, int numMembers)
327         {
328             int foundSlot = -1;
329             // Simply go through the slots and check the m_caseStatements map
330             for (int slotNum = startSlotNum; slotNum < numMembers; slotNum++)
331             {
332                 MemberPath member = m_projectedSlotMap[slotNum];
333                 if (parentRequiredSlots[slotNum] && m_caseStatements.ContainsKey(member))
334                 {
335                     foundSlot = slotNum;
336                     break;
337                 }
338             }
339             return foundSlot;
340         }
341
342         /// <summary>
343         /// Returns an array of size <see cref="TotalSlots"/> which indicates the slots that are needed to constuct value at <paramref name="caseMemberPath"/>,
344         /// e.g., CPerson may need pid and name (say slots 2 and 5 - then bools[2] and bools[5] will be true.
345         /// </summary>
346         /// <param name="caseMemberPath">must be part of <see cref="m_caseStatements"/></param>
347         private void GetRequiredSlotsForCaseMember(MemberPath caseMemberPath, bool[] requiredSlots)
348         {
349             Debug.Assert(true == m_caseStatements.ContainsKey(caseMemberPath), "Constructing case for regular field?");
350             Debug.Assert(requiredSlots.Length == TotalSlots, "Invalid array size for populating required slots");
351
352             CaseStatement statement = m_caseStatements[caseMemberPath];
353
354             // Find the required slots from the when then clause conditions
355             // and values
356             bool requireThisSlot = false;
357             foreach (CaseStatement.WhenThen clause in statement.Clauses)
358             {
359                 clause.Condition.GetRequiredSlots(m_projectedSlotMap, requiredSlots);
360                 ProjectedSlot slot = clause.Value;
361                 if (!(slot is ConstantProjectedSlot))
362                 {
363                     // If this slot is a scalar and a non-constant, 
364                     // we need the lower down blocks to generate it for us
365                     requireThisSlot = true;
366                 }
367             }
368
369             EdmType edmType = caseMemberPath.EdmType;
370             if (Helper.IsEntityType(edmType) || Helper.IsComplexType(edmType))
371             {
372                 foreach (EdmType instantiatedType in statement.InstantiatedTypes)
373                 {
374                     foreach (EdmMember childMember in Helper.GetAllStructuralMembers(instantiatedType) )
375                     {
376                         int slotNum = GetSlotIndex(caseMemberPath, childMember);
377                         requiredSlots[slotNum] = true;
378                     }
379                 }
380             }
381             else if (caseMemberPath.IsScalarType())
382             {
383                 // A scalar does not need anything per se to be constructed
384                 // unless it is referring to a field in the tree below, i.e., the THEN
385                 // slot is not a constant slot
386                 if (requireThisSlot)
387                 {
388                     int caseMemberSlotNum = m_projectedSlotMap.IndexOf(caseMemberPath);
389                     requiredSlots[caseMemberSlotNum] = true;
390                 }
391             }
392             else if (Helper.IsAssociationType(edmType))
393             {
394                 // For an association, get the indices of the ends, e.g.,
395                 // CProduct and CCategory in CProductCategory1
396                 // Need just it's ends
397                 AssociationSet associationSet = (AssociationSet)caseMemberPath.Extent;
398                 AssociationType associationType = associationSet.ElementType;
399                 foreach (AssociationEndMember endMember in associationType.AssociationEndMembers)
400                 {
401                     int slotNum = GetSlotIndex(caseMemberPath, endMember);
402                     requiredSlots[slotNum] = true;
403                 }
404             }
405             else
406             {
407                 // For a reference, all we need are the keys
408                 RefType refType = edmType as RefType;
409                 Debug.Assert(refType != null, "What other non scalars do we have? Relation end must be a reference type");
410
411                 EntityTypeBase refElementType = refType.ElementType;
412                 // Go through all the members of elementType and get the key properties
413
414                 EntitySet entitySet = MetadataHelper.GetEntitySetAtEnd((AssociationSet)caseMemberPath.Extent,
415                                                                        (AssociationEndMember)caseMemberPath.LeafEdmMember);
416                 foreach (EdmMember entityMember in refElementType.KeyMembers)
417                 {
418                     int slotNum = GetSlotIndex(caseMemberPath, entityMember);
419                     requiredSlots[slotNum] = true;
420                 }
421             }
422         }
423         #endregion
424
425         #region Helper methods
426         /// <summary>
427         /// Given the <paramref name="slotNum"/>, returns the output member path that this slot contributes/corresponds to in the extent view.
428         /// If the slot corresponds to one of the boolean variables, returns null.
429         /// </summary>
430         private MemberPath GetOutputMemberPath(int slotNum)
431         {
432             return m_projectedSlotMap.GetMemberPath(slotNum, TotalSlots - m_projectedSlotMap.Count);
433         }
434
435         /// <summary>
436         /// Returns the slot index for the following member path: <paramref name="member"/>.<paramref name="child"/>, e.g., CPerson1.pid
437         /// </summary>
438         private int GetSlotIndex(MemberPath member, EdmMember child)
439         {
440             MemberPath fullMember = new MemberPath(member, child);
441             int index = m_projectedSlotMap.IndexOf(fullMember);
442             Debug.Assert(index != -1, "Couldn't locate " + fullMember.ToString() + " in m_projectedSlotMap");
443             return index;
444         }
445         #endregion
446
447         #region String methods
448         internal override void ToCompactString(StringBuilder builder)
449         {
450             builder.Append("View: ");
451             m_view.ToCompactString(builder);
452             builder.Append("ProjectedSlotMap: ");
453             m_projectedSlotMap.ToCompactString(builder);
454             builder.Append("Case statements: ");
455             foreach (MemberPath member in m_caseStatements.Keys)
456             {
457                 CaseStatement statement = m_caseStatements[member];
458                 statement.ToCompactString(builder);
459                 builder.AppendLine();
460             }
461         }
462         #endregion
463     }
464 }