//--------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner [....] // @backupOwner [....] //--------------------------------------------------------------------- using System.Data.Common; using System.Data.Common.CommandTrees; using System.Data.Common.Utils; using System.Data.Mapping.ViewGeneration.Structures; using System.Collections.Generic; using System.Data.Mapping.ViewGeneration.CqlGeneration; using System.Text; using System.Diagnostics; using System.Data.Metadata.Edm; namespace System.Data.Mapping.ViewGeneration { /// /// This class is responsible for generation of CQL after the cell merging process has been done. /// internal sealed class CqlGenerator : InternalBase { #region Constructor /// /// Given the generated , the for the multiconstant fields, /// the that maps different paths of the entityset (for which the view is being generated) to slot indexes in the view, /// creates an object that is capable of generating the Cql for . /// internal CqlGenerator(CellTreeNode view, Dictionary caseStatements, CqlIdentifiers identifiers, MemberProjectionIndex projectedSlotMap, int numCellsInView, BoolExpression topLevelWhereClause, StorageMappingItemCollection mappingItemCollection) { m_view = view; m_caseStatements = caseStatements; m_projectedSlotMap = projectedSlotMap; m_numBools = numCellsInView; // We have that many booleans m_topLevelWhereClause = topLevelWhereClause; m_identifiers = identifiers; m_mappingItemCollection = mappingItemCollection; } #endregion #region Fields /// /// The generated view from the cells. /// private readonly CellTreeNode m_view; /// /// Case statements for the multiconstant fields. /// private readonly Dictionary m_caseStatements; /// /// Mapping from member paths to slot indexes. /// private MemberProjectionIndex m_projectedSlotMap; /// /// Number of booleans in the view, one per cell (from0, from1, etc...) /// private readonly int m_numBools; /// /// A counter used to generate aliases for blocks. /// private int m_currentBlockNum = 0; private readonly BoolExpression m_topLevelWhereClause; /// /// Identifiers used in the Cql queries. /// private readonly CqlIdentifiers m_identifiers; private readonly StorageMappingItemCollection m_mappingItemCollection; #endregion #region Properties private int TotalSlots { get { return m_projectedSlotMap.Count + m_numBools; } } #endregion #region CqlBlock generation methods for all node types /// /// Returns eSQL query that represents a query/update mapping view for the view information that was supplied in the constructor. /// internal string GenerateEsql() { // Generate a CqlBlock tree and then convert that to eSQL. CqlBlock blockTree = GenerateCqlBlockTree(); // Create the string builder with 1K so that we don't have to // keep growing it StringBuilder builder = new StringBuilder(1024); blockTree.AsEsql(builder, true, 1); return builder.ToString(); } /// /// Returns Cqtl query that represents a query/update mapping view for the view information that was supplied in the constructor. /// internal DbQueryCommandTree GenerateCqt() { // Generate a CqlBlock tree and then convert that to CQT. CqlBlock blockTree = GenerateCqlBlockTree(); DbExpression query = blockTree.AsCqt(true); Debug.Assert(query != null, "Null CQT generated for query/update view."); return DbQueryCommandTree.FromValidExpression(m_mappingItemCollection.Workspace, TargetPerspective.TargetPerspectiveDataSpace, query); } /// /// Generates a tree that is capable of generating the actual Cql strings. /// private CqlBlock GenerateCqlBlockTree() { // Essentially, we create a block for each CellTreeNode in the // tree and then we layer case statements on top of that view -- // one case statement for each multiconstant entry // Dertmine the slots that are projected by the whole tree. Tell // the children that they need to produce those slots somehow -- // if they don't have it, they can produce null bool[] requiredSlots = GetRequiredSlots(); Debug.Assert(requiredSlots.Length == TotalSlots, "Wrong number of requiredSlots"); List withRelationships = new List(); CqlBlock viewBlock = m_view.ToCqlBlock(requiredSlots, m_identifiers, ref m_currentBlockNum, ref withRelationships); // Handle case statements for multiconstant entries // Right now, we have a simplication step that removes one of the // entries and adds ELSE instead foreach (CaseStatement statement in m_caseStatements.Values) { statement.Simplify(); } // Generate the case statements and get the top level block which // must correspond to the entity set CqlBlock finalViewBlock = ConstructCaseBlocks(viewBlock, withRelationships); return finalViewBlock; } private bool[] GetRequiredSlots() { bool[] requiredSlots = new bool[TotalSlots]; // union all slots that are required in case statements foreach (CaseStatement caseStatement in m_caseStatements.Values) { GetRequiredSlotsForCaseMember(caseStatement.MemberPath, requiredSlots); } // For now, make sure that all booleans are required // Reason: OUTER JOINs may introduce an extra CASE statement (in OpCellTreeNode.cs/GetJoinSlotInfo) // if a member is projected in both inputs to the join. // This case statement may use boolean variables that may not be marked as "required" // The problem is that this decision is made _after_ CqlBlocks for children get produced (in OpCellTreeNode.cs/JoinToCqlBlock) for (int i = TotalSlots - m_numBools; i < TotalSlots; i++) { requiredSlots[i] = true; } // Because of the above we don't need to harvest used booleans from the top-level WHERE clause // m_topLevelWhereClause.GetRequiredSlots(m_projectedSlotMap, requiredSlots); // Do we require the case statement member slot be produced by the inner queries? foreach (CaseStatement caseStatement in m_caseStatements.Values) { bool notNeeded = !caseStatement.MemberPath.IsPartOfKey && // keys are required in inner queries for joins conditions !caseStatement.DependsOnMemberValue; // if case statement returns its slot value as one of the options, then we need to produce it if (notNeeded) { requiredSlots[m_projectedSlotMap.IndexOf(caseStatement.MemberPath)] = false; } } return requiredSlots; } #endregion #region Multiconstant CaseStatement methods /// /// Given the tree, generates the case statement blocks on top of it (using ) and returns the resulting tree. /// One block per case statement is generated. Generated blocks are nested, with the is the innermost input. /// private CqlBlock ConstructCaseBlocks(CqlBlock viewBlock, IEnumerable withRelationships) { // Get the 0th slot only, i.e., the extent bool[] topSlots = new bool[TotalSlots]; topSlots[0] = true; // all booleans in the top-level WHERE clause are required and get bubbled up // this makes some _fromX booleans be marked as 'required by parent' m_topLevelWhereClause.GetRequiredSlots(m_projectedSlotMap, topSlots); CqlBlock result = ConstructCaseBlocks(viewBlock, 0, topSlots, withRelationships); return result; } /// /// Given the tree generated by the cell merging process and the , /// generates the block tree for the case statement at or past the startSlotNum, i.e., only for case statements that are beyond startSlotNum. /// private CqlBlock ConstructCaseBlocks(CqlBlock viewBlock, int startSlotNum, bool[] parentRequiredSlots, IEnumerable withRelationships) { int numMembers = m_projectedSlotMap.Count; // Find the next slot for which we have a case statement, i.e., // which was in the multiconstants int foundSlot = FindNextCaseStatementSlot(startSlotNum, parentRequiredSlots, numMembers); if (foundSlot == -1) { // We have bottomed out - no more slots to generate cases for // Just get the base view block return viewBlock; } // Compute the requiredSlots for this member, i.e., what slots are needed to produce this member. MemberPath thisMember = m_projectedSlotMap[foundSlot]; bool[] thisRequiredSlots = new bool[TotalSlots]; GetRequiredSlotsForCaseMember(thisMember, thisRequiredSlots); Debug.Assert(thisRequiredSlots.Length == parentRequiredSlots.Length && thisRequiredSlots.Length == TotalSlots, "Number of slots in array should not vary across blocks"); // Merge parent's requirements with this requirements for (int i = 0; i < TotalSlots; i++) { // We do ask the children to generate the slot that we are // producing if it is available if (parentRequiredSlots[i]) { thisRequiredSlots[i] = true; } } // If current case statement depends on its slot value, then make sure the value is produced by the child block. CaseStatement thisCaseStatement = m_caseStatements[thisMember]; thisRequiredSlots[foundSlot] = thisCaseStatement.DependsOnMemberValue; // Recursively, determine the block tree for slots beyond foundSlot. CqlBlock childBlock = ConstructCaseBlocks(viewBlock, foundSlot + 1, thisRequiredSlots, null); // For each slot, create a SlotInfo object SlotInfo[] slotInfos = CreateSlotInfosForCaseStatement(parentRequiredSlots, foundSlot, childBlock, thisCaseStatement, withRelationships); m_currentBlockNum++; // We have a where clause only at the top level BoolExpression whereClause = startSlotNum == 0 ? m_topLevelWhereClause : BoolExpression.True; if (startSlotNum == 0) { // only slot #0 is required by parent; reset all 'required by parent' booleans introduced above for (int i = 1; i < slotInfos.Length; i++) { slotInfos[i].ResetIsRequiredByParent(); } } CaseCqlBlock result = new CaseCqlBlock(slotInfos, foundSlot, childBlock, whereClause, m_identifiers, m_currentBlockNum); return result; } /// /// Given the slot () and its corresponding case statement (), /// generates the slotinfos for the cql block producing the case statement. /// private SlotInfo[] CreateSlotInfosForCaseStatement(bool[] parentRequiredSlots, int foundSlot, CqlBlock childBlock, CaseStatement thisCaseStatement, IEnumerable withRelationships) { int numSlotsAddedByChildBlock = childBlock.Slots.Count - TotalSlots; SlotInfo[] slotInfos = new SlotInfo[TotalSlots + numSlotsAddedByChildBlock]; for (int slotNum = 0; slotNum < TotalSlots; slotNum++) { bool isProjected = childBlock.IsProjected(slotNum); bool isRequiredByParent = parentRequiredSlots[slotNum]; ProjectedSlot slot = childBlock.SlotValue(slotNum); MemberPath outputMember = GetOutputMemberPath(slotNum); if (slotNum == foundSlot) { // We need a case statement instead for this slot that we // are handling right now Debug.Assert(isRequiredByParent, "Case result not needed by parent"); // Get a case statement with all slots replaced by aliases slots CaseStatement newCaseStatement = thisCaseStatement.DeepQualify(childBlock); slot = new CaseStatementProjectedSlot(newCaseStatement, withRelationships); isProjected = true; // We are projecting this slot now } else if (isProjected && isRequiredByParent) { // We only alias something that is needed and is being projected by the child. // It is a qualified slot into the child block. slot = childBlock.QualifySlotWithBlockAlias(slotNum); } // For slots, if it is not required by the parent, we want to // set the isRequiredByParent for this slot to be // false. Furthermore, we do not want to introduce any "NULL // AS something" at this stage for slots not being // projected. So if the child does not project that slot, we // declare it as not being required by the parent (if such a // NULL was needed, it would have been pushed all the way // down to a non-case block. // Essentially, from a Case statement's parent perspective, // it is saying "If you can produce a slot either by yourself // or your children, please do. Otherwise, do not concoct anything" SlotInfo slotInfo = new SlotInfo(isRequiredByParent && isProjected, isProjected, slot, outputMember); slotInfos[slotNum] = slotInfo; } for (int i = TotalSlots; i < TotalSlots + numSlotsAddedByChildBlock; i++) { QualifiedSlot childAddedSlot = childBlock.QualifySlotWithBlockAlias(i); slotInfos[i] = new SlotInfo(true, true, childAddedSlot, childBlock.MemberPath(i)); } return slotInfos; } /// /// Returns the next slot starting at that is present in the . /// private int FindNextCaseStatementSlot(int startSlotNum, bool[] parentRequiredSlots, int numMembers) { int foundSlot = -1; // Simply go through the slots and check the m_caseStatements map for (int slotNum = startSlotNum; slotNum < numMembers; slotNum++) { MemberPath member = m_projectedSlotMap[slotNum]; if (parentRequiredSlots[slotNum] && m_caseStatements.ContainsKey(member)) { foundSlot = slotNum; break; } } return foundSlot; } /// /// Returns an array of size which indicates the slots that are needed to constuct value at , /// e.g., CPerson may need pid and name (say slots 2 and 5 - then bools[2] and bools[5] will be true. /// /// must be part of private void GetRequiredSlotsForCaseMember(MemberPath caseMemberPath, bool[] requiredSlots) { Debug.Assert(true == m_caseStatements.ContainsKey(caseMemberPath), "Constructing case for regular field?"); Debug.Assert(requiredSlots.Length == TotalSlots, "Invalid array size for populating required slots"); CaseStatement statement = m_caseStatements[caseMemberPath]; // Find the required slots from the when then clause conditions // and values bool requireThisSlot = false; foreach (CaseStatement.WhenThen clause in statement.Clauses) { clause.Condition.GetRequiredSlots(m_projectedSlotMap, requiredSlots); ProjectedSlot slot = clause.Value; if (!(slot is ConstantProjectedSlot)) { // If this slot is a scalar and a non-constant, // we need the lower down blocks to generate it for us requireThisSlot = true; } } EdmType edmType = caseMemberPath.EdmType; if (Helper.IsEntityType(edmType) || Helper.IsComplexType(edmType)) { foreach (EdmType instantiatedType in statement.InstantiatedTypes) { foreach (EdmMember childMember in Helper.GetAllStructuralMembers(instantiatedType) ) { int slotNum = GetSlotIndex(caseMemberPath, childMember); requiredSlots[slotNum] = true; } } } else if (caseMemberPath.IsScalarType()) { // A scalar does not need anything per se to be constructed // unless it is referring to a field in the tree below, i.e., the THEN // slot is not a constant slot if (requireThisSlot) { int caseMemberSlotNum = m_projectedSlotMap.IndexOf(caseMemberPath); requiredSlots[caseMemberSlotNum] = true; } } else if (Helper.IsAssociationType(edmType)) { // For an association, get the indices of the ends, e.g., // CProduct and CCategory in CProductCategory1 // Need just it's ends AssociationSet associationSet = (AssociationSet)caseMemberPath.Extent; AssociationType associationType = associationSet.ElementType; foreach (AssociationEndMember endMember in associationType.AssociationEndMembers) { int slotNum = GetSlotIndex(caseMemberPath, endMember); requiredSlots[slotNum] = true; } } else { // For a reference, all we need are the keys RefType refType = edmType as RefType; Debug.Assert(refType != null, "What other non scalars do we have? Relation end must be a reference type"); EntityTypeBase refElementType = refType.ElementType; // Go through all the members of elementType and get the key properties EntitySet entitySet = MetadataHelper.GetEntitySetAtEnd((AssociationSet)caseMemberPath.Extent, (AssociationEndMember)caseMemberPath.LeafEdmMember); foreach (EdmMember entityMember in refElementType.KeyMembers) { int slotNum = GetSlotIndex(caseMemberPath, entityMember); requiredSlots[slotNum] = true; } } } #endregion #region Helper methods /// /// Given the , returns the output member path that this slot contributes/corresponds to in the extent view. /// If the slot corresponds to one of the boolean variables, returns null. /// private MemberPath GetOutputMemberPath(int slotNum) { return m_projectedSlotMap.GetMemberPath(slotNum, TotalSlots - m_projectedSlotMap.Count); } /// /// Returns the slot index for the following member path: ., e.g., CPerson1.pid /// private int GetSlotIndex(MemberPath member, EdmMember child) { MemberPath fullMember = new MemberPath(member, child); int index = m_projectedSlotMap.IndexOf(fullMember); Debug.Assert(index != -1, "Couldn't locate " + fullMember.ToString() + " in m_projectedSlotMap"); return index; } #endregion #region String methods internal override void ToCompactString(StringBuilder builder) { builder.Append("View: "); m_view.ToCompactString(builder); builder.Append("ProjectedSlotMap: "); m_projectedSlotMap.ToCompactString(builder); builder.Append("Case statements: "); foreach (MemberPath member in m_caseStatements.Keys) { CaseStatement statement = m_caseStatements[member]; statement.ToCompactString(builder); builder.AppendLine(); } } #endregion } }