1 //---------------------------------------------------------------------
2 // <copyright file="CQLGenerator.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
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;
17 using System.Diagnostics;
18 using System.Data.Metadata.Edm;
20 namespace System.Data.Mapping.ViewGeneration
23 /// This class is responsible for generation of CQL after the cell merging process has been done.
25 internal sealed class CqlGenerator : InternalBase
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"/>.
33 internal CqlGenerator(CellTreeNode view,
34 Dictionary<MemberPath,
35 CaseStatement> caseStatements,
36 CqlIdentifiers identifiers,
37 MemberProjectionIndex projectedSlotMap,
39 BoolExpression topLevelWhereClause,
40 StorageMappingItemCollection mappingItemCollection)
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;
54 /// The generated view from the cells.
56 private readonly CellTreeNode m_view;
58 /// Case statements for the multiconstant fields.
60 private readonly Dictionary<MemberPath, CaseStatement> m_caseStatements;
62 /// Mapping from member paths to slot indexes.
64 private MemberProjectionIndex m_projectedSlotMap;
66 /// Number of booleans in the view, one per cell (from0, from1, etc...)
68 private readonly int m_numBools;
70 /// A counter used to generate aliases for blocks.
72 private int m_currentBlockNum = 0;
73 private readonly BoolExpression m_topLevelWhereClause;
75 /// Identifiers used in the Cql queries.
77 private readonly CqlIdentifiers m_identifiers;
78 private readonly StorageMappingItemCollection m_mappingItemCollection;
82 private int TotalSlots
84 get { return m_projectedSlotMap.Count + m_numBools; }
88 #region CqlBlock generation methods for all node types
90 /// Returns eSQL query that represents a query/update mapping view for the view information that was supplied in the constructor.
92 internal string GenerateEsql()
94 // Generate a CqlBlock tree and then convert that to eSQL.
95 CqlBlock blockTree = GenerateCqlBlockTree();
97 // Create the string builder with 1K so that we don't have to
99 StringBuilder builder = new StringBuilder(1024);
100 blockTree.AsEsql(builder, true, 1);
101 return builder.ToString();
105 /// Returns Cqtl query that represents a query/update mapping view for the view information that was supplied in the constructor.
107 internal DbQueryCommandTree GenerateCqt()
109 // Generate a CqlBlock tree and then convert that to CQT.
110 CqlBlock blockTree = GenerateCqlBlockTree();
112 DbExpression query = blockTree.AsCqt(true);
113 Debug.Assert(query != null, "Null CQT generated for query/update view.");
115 return DbQueryCommandTree.FromValidExpression(m_mappingItemCollection.Workspace, TargetPerspective.TargetPerspectiveDataSpace, query);
119 /// Generates a <see cref="CqlBlock"/> tree that is capable of generating the actual Cql strings.
121 private CqlBlock GenerateCqlBlockTree()
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
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");
133 List<WithRelationship> withRelationships = new List<WithRelationship>();
134 CqlBlock viewBlock = m_view.ToCqlBlock(requiredSlots, m_identifiers, ref m_currentBlockNum, ref withRelationships);
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)
141 statement.Simplify();
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;
150 private bool[] GetRequiredSlots()
152 bool[] requiredSlots = new bool[TotalSlots];
153 // union all slots that are required in case statements
154 foreach (CaseStatement caseStatement in m_caseStatements.Values)
156 GetRequiredSlotsForCaseMember(caseStatement.MemberPath, requiredSlots);
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++)
166 requiredSlots[i] = true;
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);
171 // Do we require the case statement member slot be produced by the inner queries?
172 foreach (CaseStatement caseStatement in m_caseStatements.Values)
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
178 requiredSlots[m_projectedSlotMap.IndexOf(caseStatement.MemberPath)] = false;
181 return requiredSlots;
185 #region Multiconstant CaseStatement methods
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.
190 private CqlBlock ConstructCaseBlocks(CqlBlock viewBlock, IEnumerable<WithRelationship> withRelationships)
192 // Get the 0th slot only, i.e., the extent
193 bool[] topSlots = new bool[TotalSlots];
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);
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.
207 private CqlBlock ConstructCaseBlocks(CqlBlock viewBlock, int startSlotNum, bool[] parentRequiredSlots, IEnumerable<WithRelationship> withRelationships)
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);
216 // We have bottomed out - no more slots to generate cases for
217 // Just get the base view block
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");
229 // Merge parent's requirements with this requirements
230 for (int i = 0; i < TotalSlots; i++)
232 // We do ask the children to generate the slot that we are
233 // producing if it is available
234 if (parentRequiredSlots[i])
236 thisRequiredSlots[i] = true;
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;
244 // Recursively, determine the block tree for slots beyond foundSlot.
245 CqlBlock childBlock = ConstructCaseBlocks(viewBlock, foundSlot + 1, thisRequiredSlots, null);
247 // For each slot, create a SlotInfo object
248 SlotInfo[] slotInfos = CreateSlotInfosForCaseStatement(parentRequiredSlots, foundSlot, childBlock, thisCaseStatement, withRelationships);
251 // We have a where clause only at the top level
252 BoolExpression whereClause = startSlotNum == 0 ? m_topLevelWhereClause : BoolExpression.True;
253 if (startSlotNum == 0)
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++)
258 slotInfos[i].ResetIsRequiredByParent();
262 CaseCqlBlock result = new CaseCqlBlock(slotInfos, foundSlot, childBlock, whereClause, m_identifiers, m_currentBlockNum);
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.
270 private SlotInfo[] CreateSlotInfosForCaseStatement(bool[] parentRequiredSlots,
273 CaseStatement thisCaseStatement,
274 IEnumerable<WithRelationship> withRelationships)
276 int numSlotsAddedByChildBlock = childBlock.Slots.Count - TotalSlots;
277 SlotInfo[] slotInfos = new SlotInfo[TotalSlots + numSlotsAddedByChildBlock];
278 for (int slotNum = 0; slotNum < TotalSlots; slotNum++)
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)
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");
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
295 else if (isProjected && isRequiredByParent)
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);
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;
315 for (int i = TotalSlots; i < TotalSlots + numSlotsAddedByChildBlock; i++)
317 QualifiedSlot childAddedSlot = childBlock.QualifySlotWithBlockAlias(i);
318 slotInfos[i] = new SlotInfo(true, true, childAddedSlot, childBlock.MemberPath(i));
324 /// Returns the next slot starting at <paramref name="startSlotNum"/> that is present in the <see cref="m_caseStatements"/>.
326 private int FindNextCaseStatementSlot(int startSlotNum, bool[] parentRequiredSlots, int numMembers)
329 // Simply go through the slots and check the m_caseStatements map
330 for (int slotNum = startSlotNum; slotNum < numMembers; slotNum++)
332 MemberPath member = m_projectedSlotMap[slotNum];
333 if (parentRequiredSlots[slotNum] && m_caseStatements.ContainsKey(member))
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.
346 /// <param name="caseMemberPath">must be part of <see cref="m_caseStatements"/></param>
347 private void GetRequiredSlotsForCaseMember(MemberPath caseMemberPath, bool[] requiredSlots)
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");
352 CaseStatement statement = m_caseStatements[caseMemberPath];
354 // Find the required slots from the when then clause conditions
356 bool requireThisSlot = false;
357 foreach (CaseStatement.WhenThen clause in statement.Clauses)
359 clause.Condition.GetRequiredSlots(m_projectedSlotMap, requiredSlots);
360 ProjectedSlot slot = clause.Value;
361 if (!(slot is ConstantProjectedSlot))
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;
369 EdmType edmType = caseMemberPath.EdmType;
370 if (Helper.IsEntityType(edmType) || Helper.IsComplexType(edmType))
372 foreach (EdmType instantiatedType in statement.InstantiatedTypes)
374 foreach (EdmMember childMember in Helper.GetAllStructuralMembers(instantiatedType) )
376 int slotNum = GetSlotIndex(caseMemberPath, childMember);
377 requiredSlots[slotNum] = true;
381 else if (caseMemberPath.IsScalarType())
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
388 int caseMemberSlotNum = m_projectedSlotMap.IndexOf(caseMemberPath);
389 requiredSlots[caseMemberSlotNum] = true;
392 else if (Helper.IsAssociationType(edmType))
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)
401 int slotNum = GetSlotIndex(caseMemberPath, endMember);
402 requiredSlots[slotNum] = true;
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");
411 EntityTypeBase refElementType = refType.ElementType;
412 // Go through all the members of elementType and get the key properties
414 EntitySet entitySet = MetadataHelper.GetEntitySetAtEnd((AssociationSet)caseMemberPath.Extent,
415 (AssociationEndMember)caseMemberPath.LeafEdmMember);
416 foreach (EdmMember entityMember in refElementType.KeyMembers)
418 int slotNum = GetSlotIndex(caseMemberPath, entityMember);
419 requiredSlots[slotNum] = true;
425 #region Helper methods
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.
430 private MemberPath GetOutputMemberPath(int slotNum)
432 return m_projectedSlotMap.GetMemberPath(slotNum, TotalSlots - m_projectedSlotMap.Count);
436 /// Returns the slot index for the following member path: <paramref name="member"/>.<paramref name="child"/>, e.g., CPerson1.pid
438 private int GetSlotIndex(MemberPath member, EdmMember child)
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");
447 #region String methods
448 internal override void ToCompactString(StringBuilder builder)
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)
457 CaseStatement statement = m_caseStatements[member];
458 statement.ToCompactString(builder);
459 builder.AppendLine();