//--------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner [....] // @backupOwner [....] //--------------------------------------------------------------------- using System.Data.Common.Utils; using System.Data.Mapping.ViewGeneration.Structures; using System.Collections.Generic; using System.Data.Mapping.ViewGeneration.Utils; using System.Diagnostics; using System.Data.Metadata.Edm; using System.Linq; namespace System.Data.Mapping.ViewGeneration { /// /// A class that handles creation of cells from the meta data information. /// internal class CellCreator : InternalBase { #region Constructors // effects: Creates a cell creator object for an entity container's // mappings (specified in "maps") internal CellCreator(StorageEntityContainerMapping containerMapping) { m_containerMapping = containerMapping; m_identifiers = new CqlIdentifiers(); } #endregion #region Fields // The mappings from the metadata for different containers private StorageEntityContainerMapping m_containerMapping; private int m_currentCellNumber; private CqlIdentifiers m_identifiers; // Keep track of all the identifiers to prevent clashes with _from0, // _from1, T, T1, etc // Keep track of names of // * Entity Containers // * Extent names // * Entity Types // * Complex Types // * Properties // * Roles #endregion #region Properties // effects: Returns the set of identifiers used in this internal CqlIdentifiers Identifiers { get { return m_identifiers; } } #endregion #region External methods // effects: Generates the cells for all the entity containers // specified in this. The generated cells are geared for query view generation internal List GenerateCells(ConfigViewGenerator config) { List cells = new List(); // Get the cells from the entity container metadata ExtractCells(cells); ExpandCells(cells); // Get the identifiers from the cells m_identifiers.AddIdentifier(m_containerMapping.EdmEntityContainer.Name); m_identifiers.AddIdentifier(m_containerMapping.StorageEntityContainer.Name); foreach (Cell cell in cells) { cell.GetIdentifiers(m_identifiers); } return cells; } #endregion #region Private Methods /// /// Boolean members have a closed domain and are enumerated when domains are established i.e. (T, F) instead of (notNull). /// Query Rewriting is exercised over every domain of the condition member. If the member contains not_null condition /// for example, it cannot generate a view for partitions (member=T), (Member=F). For this reason we need to expand the cells /// in a predefined situation (below) to include sub-fragments mapping individual elements of the closed domain. /// Enums (a planned feature) need to be handled in a similar fashion. /// /// Find booleans that are projected with a not_null condition /// Expand ALL cells where they are projected. Why? See Unit Test case NullabilityConditionOnBoolean5.es /// Validation will fail because it will not be able to validate rewritings for partitions on the 'other' cells. /// private void ExpandCells(List cells) { var sSideMembersToBeExpanded = new Set(); foreach (Cell cell in cells) { //Find Projected members that are Boolean AND are mentioned in the Where clause with not_null condition foreach (var memberToExpand in cell.SQuery.GetProjectedMembers() .Where(member => IsBooleanMember(member)) .Where(boolMember => cell.SQuery.GetConjunctsFromWhereClause() .Where(restriction => restriction.Domain.Values.Contains(Constant.NotNull)) .Select(restriction => restriction.RestrictedMemberSlot.MemberPath).Contains(boolMember))) { sSideMembersToBeExpanded.Add(memberToExpand); } } //Foreach s-side members, find all c-side members it is mapped to // We need these because we need to expand all cells where the boolean candidate is projected or mapped member is projected, e.g: // (1) C[id, cdisc] WHERE d=true <=> T1[id, sdisc] WHERE sdisc=NOTNULL // (2) C[id, cdisc] WHERE d=false <=> T2[id, sdisc] // Here we need to know that because of T1.sdisc, we need to expand T2.sdisc. // This is done by tracking cdisc, and then seeing in cell 2 that it is mapped to T2.sdisc var cSideMembersForSSideExpansionCandidates = new Dictionary>(); foreach (Cell cell in cells) { foreach (var sSideMemberToExpand in sSideMembersToBeExpanded) { var cSideMembers = cell.SQuery.GetProjectedPositions(sSideMemberToExpand).Select(pos => ((MemberProjectedSlot)cell.CQuery.ProjectedSlotAt(pos)).MemberPath); Set cSidePaths = null; if (!cSideMembersForSSideExpansionCandidates.TryGetValue(sSideMemberToExpand, out cSidePaths)) { cSidePaths = new Set(); cSideMembersForSSideExpansionCandidates[sSideMemberToExpand] = cSidePaths; } cSidePaths.AddRange(cSideMembers); } } // Expand cells that project members collected earlier with T/F conditiions foreach (Cell cell in cells.ToArray()) { //Each member gets its own expansion. Including multiple condition candidates in one SQuery // "... <=> T[..] WHERE a=notnull AND b=notnull" means a and b get their own independent expansions // Note: this is not a cross-product foreach (var memberToExpand in sSideMembersToBeExpanded) { var mappedCSideMembers = cSideMembersForSSideExpansionCandidates[memberToExpand]; //Check if member is projected in this cell. if (cell.SQuery.GetProjectedMembers().Contains(memberToExpand)) { // Creationg additional cel can fail when the condition to be appended contradicts existing condition in the CellQuery // We don't add contradictions because they seem to cause unrelated problems in subsequent validation routines Cell resultCell = null; if (TryCreateAdditionalCellWithCondition(cell, memberToExpand, true /*condition value*/, ViewTarget.UpdateView /*s-side member*/, out resultCell)) { cells.Add(resultCell); } if (TryCreateAdditionalCellWithCondition(cell, memberToExpand, false /*condition value*/, ViewTarget.UpdateView /*s-side member*/, out resultCell)) { cells.Add(resultCell); } } else { //If the s-side member is not projected, see if the mapped C-side member(s) is projected foreach (var cMemberToExpand in cell.CQuery.GetProjectedMembers().Intersect(mappedCSideMembers)) { Cell resultCell = null; if (TryCreateAdditionalCellWithCondition(cell, cMemberToExpand, true /*condition value*/, ViewTarget.QueryView /*c-side member*/, out resultCell)) { cells.Add(resultCell); } if (TryCreateAdditionalCellWithCondition(cell, cMemberToExpand, false /*condition value*/, ViewTarget.QueryView /*c-side member*/, out resultCell)) { cells.Add(resultCell); } } } } } } /// /// Given a cell, a member and a boolean condition on that member, creates additional cell /// which with the specified restriction on the member in addition to original condition. /// e.i conjunction of original condition AND member in newCondition /// /// Creation fails when the original condition contradicts new boolean condition /// /// ViewTarget tells whether MemberPath is in Cquery or SQuery /// private bool TryCreateAdditionalCellWithCondition(Cell originalCell, MemberPath memberToExpand, bool conditionValue, ViewTarget viewTarget, out Cell result) { Debug.Assert(originalCell != null); Debug.Assert(memberToExpand != null); result = null; //Create required structures MemberPath leftExtent = originalCell.GetLeftQuery(viewTarget).SourceExtentMemberPath; MemberPath rightExtent = originalCell.GetRightQuery(viewTarget).SourceExtentMemberPath; //Now for the given left-side projected member, find corresponding right-side member that it is mapped to int indexOfBooLMemberInProjection = originalCell.GetLeftQuery(viewTarget).GetProjectedMembers().TakeWhile(path => !path.Equals(memberToExpand)).Count(); MemberProjectedSlot rightConditionMemberSlot = ((MemberProjectedSlot)originalCell.GetRightQuery(viewTarget).ProjectedSlotAt(indexOfBooLMemberInProjection)); MemberPath rightSidePath = rightConditionMemberSlot.MemberPath; List leftSlots = new List(); List rightSlots = new List(); //Check for impossible conditions (otehrwise we get inaccurate pre-validation errors) ScalarConstant negatedCondition = new ScalarConstant(!conditionValue); if (originalCell.GetLeftQuery(viewTarget).Conditions .Where(restriction => restriction.RestrictedMemberSlot.MemberPath.Equals(memberToExpand)) .Where(restriction => restriction.Domain.Values.Contains(negatedCondition)).Any() || originalCell.GetRightQuery(viewTarget).Conditions .Where(restriction => restriction.RestrictedMemberSlot.MemberPath.Equals(rightSidePath)) .Where(restriction => restriction.Domain.Values.Contains(negatedCondition)).Any()) { return false; } //End check //Create Projected Slots // Map all slots in original cell (not just keys) because some may be required (non nullable and no default) // and others may have not_null condition so MUST be projected. Rely on the user doing the right thing, otherwise // they will get the error message anyway for (int i = 0; i < originalCell.GetLeftQuery(viewTarget).NumProjectedSlots; i++) { leftSlots.Add(originalCell.GetLeftQuery(viewTarget).ProjectedSlotAt(i)); } for (int i = 0; i < originalCell.GetRightQuery(viewTarget).NumProjectedSlots; i++) { rightSlots.Add(originalCell.GetRightQuery(viewTarget).ProjectedSlotAt(i)); } //Create condition boolena expressions BoolExpression leftQueryWhereClause = BoolExpression.CreateLiteral(new ScalarRestriction(memberToExpand, new ScalarConstant(conditionValue)), null); leftQueryWhereClause = BoolExpression.CreateAnd(originalCell.GetLeftQuery(viewTarget).WhereClause, leftQueryWhereClause); BoolExpression rightQueryWhereClause = BoolExpression.CreateLiteral(new ScalarRestriction(rightSidePath, new ScalarConstant(conditionValue)), null); rightQueryWhereClause = BoolExpression.CreateAnd(originalCell.GetRightQuery(viewTarget).WhereClause, rightQueryWhereClause); //Create additional Cells CellQuery rightQuery = new CellQuery(rightSlots, rightQueryWhereClause, rightExtent, originalCell.GetRightQuery(viewTarget).SelectDistinctFlag); CellQuery leftQuery = new CellQuery(leftSlots, leftQueryWhereClause, leftExtent, originalCell.GetLeftQuery(viewTarget).SelectDistinctFlag); Cell newCell; if (viewTarget == ViewTarget.UpdateView) { newCell = Cell.CreateCS(rightQuery, leftQuery, originalCell.CellLabel, m_currentCellNumber); } else { newCell = Cell.CreateCS(leftQuery, rightQuery, originalCell.CellLabel, m_currentCellNumber); } m_currentCellNumber++; result = newCell; return true; } // effects: Given the metadata information for a container in // containerMap, generate the cells for it and modify cells to // contain the newly-generated cells private void ExtractCells(List cells) { // extract entity mappings, i.e., for CPerson1, COrder1, etc foreach (StorageSetMapping extentMap in m_containerMapping.AllSetMaps) { // Get each type map in an entity set mapping, i.e., for // CPerson, CCustomer, etc in CPerson1 foreach (StorageTypeMapping typeMap in extentMap.TypeMappings) { StorageEntityTypeMapping entityTypeMap = typeMap as StorageEntityTypeMapping; Debug.Assert(entityTypeMap != null || typeMap is StorageAssociationTypeMapping, "Invalid typemap"); // A set for all the types in this type mapping Set allTypes = new Set(); if (entityTypeMap != null) { // Gather a set of all explicit types for an entity // type mapping in allTypes. Note that we do not have // subtyping in association sets allTypes.AddRange(entityTypeMap.Types); foreach (EdmType type in entityTypeMap.IsOfTypes) { IEnumerable typeAndSubTypes = MetadataHelper.GetTypeAndSubtypesOf(type, m_containerMapping.StorageMappingItemCollection.EdmItemCollection, false /*includeAbstractTypes*/); allTypes.AddRange(typeAndSubTypes); } } EntitySetBase extent = extentMap.Set; Debug.Assert(extent != null, "Extent map for a null extent or type of extentMap.Exent " + "is not Extent"); // For each table mapping for the type mapping, we create cells foreach (StorageMappingFragment fragmentMap in typeMap.MappingFragments) { ExtractCellsFromTableFragment(extent, fragmentMap, allTypes, cells); } } } } // effects: Given an extent's ("extent") table fragment that is // contained inside typeMap, determine the cells that need to be // created and add them to cells // allTypes corresponds to all the different types that the type map // represents -- this parameter has something useful only if extent // is an entity set private void ExtractCellsFromTableFragment(EntitySetBase extent, StorageMappingFragment fragmentMap, Set allTypes, List cells) { // create C-query components MemberPath cRootExtent = new MemberPath(extent); BoolExpression cQueryWhereClause = BoolExpression.True; List cSlots = new List(); if (allTypes.Count > 0) { // Create a type condition for the extent, i.e., "extent in allTypes" cQueryWhereClause = BoolExpression.CreateLiteral(new TypeRestriction(cRootExtent, allTypes), null); } // create S-query components MemberPath sRootExtent = new MemberPath(fragmentMap.TableSet); BoolExpression sQueryWhereClause = BoolExpression.True; List sSlots = new List(); // Association or entity set // Add the properties and the key properties to a list and // then process them in ExtractProperties ExtractProperties(fragmentMap.AllProperties, cRootExtent, cSlots, ref cQueryWhereClause, sRootExtent, sSlots, ref sQueryWhereClause); // limitation of MSL API: cannot assign constant values to table columns CellQuery cQuery = new CellQuery(cSlots, cQueryWhereClause, cRootExtent, CellQuery.SelectDistinct.No /*no distinct flag*/); CellQuery sQuery = new CellQuery(sSlots, sQueryWhereClause, sRootExtent, fragmentMap.IsSQueryDistinct ? CellQuery.SelectDistinct.Yes : CellQuery.SelectDistinct.No); StorageMappingFragment fragmentInfo = fragmentMap as StorageMappingFragment; Debug.Assert((fragmentInfo != null), "CSMappingFragment should support Line Info"); CellLabel label = new CellLabel(fragmentInfo); Cell cell = Cell.CreateCS(cQuery, sQuery, label, m_currentCellNumber); m_currentCellNumber++; cells.Add(cell); } // requires: "properties" corresponds to all the properties that are // inside cNode.Value, e.g., cNode corresponds to an extent Person, // properties contains all the properties inside Person (recursively) // effects: Given C-side and S-side Cell Query for a cell, generates // the projected slots on both sides corresponding to // properties. Also updates the C-side whereclause corresponding to // discriminator properties on the C-side, e.g, isHighPriority private void ExtractProperties(IEnumerable properties, MemberPath cNode, List cSlots, ref BoolExpression cQueryWhereClause, MemberPath sRootExtent, List sSlots, ref BoolExpression sQueryWhereClause) { // For each property mapping, we add an entry to the C and S cell queries foreach (StoragePropertyMapping propMap in properties) { StorageScalarPropertyMapping scalarPropMap = propMap as StorageScalarPropertyMapping; StorageComplexPropertyMapping complexPropMap = propMap as StorageComplexPropertyMapping; StorageEndPropertyMapping associationEndPropertypMap = propMap as StorageEndPropertyMapping; StorageConditionPropertyMapping conditionMap = propMap as StorageConditionPropertyMapping; Debug.Assert(scalarPropMap != null || complexPropMap != null || associationEndPropertypMap != null || conditionMap != null, "Unimplemented property mapping"); if (scalarPropMap != null) { Debug.Assert(scalarPropMap.ColumnProperty != null, "ColumnMember for a Scalar Property can not be null"); // Add an attribute node to node MemberPath cAttributeNode = new MemberPath(cNode, scalarPropMap.EdmProperty); // Add a column (attribute) node the sQuery // unlike the C side, there is no nesting. Hence we // did not need an internal node MemberPath sAttributeNode = new MemberPath(sRootExtent, scalarPropMap.ColumnProperty); cSlots.Add(new MemberProjectedSlot(cAttributeNode)); sSlots.Add(new MemberProjectedSlot(sAttributeNode)); } // Note: S-side constants are not allowed since they can cause // problems -- for example, if such a cell says 5 for the // third field, we cannot guarantee the fact that an // application may not set that field to 7 in the C-space // Check if the property mapping is for a complex types if (complexPropMap != null) { foreach (StorageComplexTypeMapping complexTypeMap in complexPropMap.TypeMappings) { // Create a node for the complex type property and call recursively MemberPath complexMemberNode = new MemberPath(cNode, complexPropMap.EdmProperty); //Get the list of types that this type map represents Set allTypes = new Set(); // Gather a set of all explicit types for an entity // type mapping in allTypes. IEnumerable exactTypes = Helpers.AsSuperTypeList(complexTypeMap.Types); allTypes.AddRange(exactTypes); foreach (EdmType type in complexTypeMap.IsOfTypes) { allTypes.AddRange(MetadataHelper.GetTypeAndSubtypesOf(type, m_containerMapping.StorageMappingItemCollection.EdmItemCollection, false /*includeAbstractTypes*/)); } BoolExpression complexInTypes = BoolExpression.CreateLiteral(new TypeRestriction(complexMemberNode, allTypes), null); cQueryWhereClause = BoolExpression.CreateAnd(cQueryWhereClause, complexInTypes); // Now extract the properties of the complex type // (which could have other complex types) ExtractProperties(complexTypeMap.AllProperties, complexMemberNode, cSlots, ref cQueryWhereClause, sRootExtent, sSlots, ref sQueryWhereClause); } } // Check if the property mapping is for an associaion if (associationEndPropertypMap != null) { // create join tree node representing this relation end MemberPath associationEndNode = new MemberPath(cNode, associationEndPropertypMap.EndMember); // call recursively ExtractProperties(associationEndPropertypMap.Properties, associationEndNode, cSlots, ref cQueryWhereClause, sRootExtent, sSlots, ref sQueryWhereClause); } //Check if the this is a condition and add it to the Where clause if (conditionMap != null) { if (conditionMap.ColumnProperty != null) { //Produce a Condition Expression for the Condition Map. BoolExpression conditionExpression = GetConditionExpression(sRootExtent, conditionMap); //Add the condition expression to the exisiting S side Where clause using an "And" sQueryWhereClause = BoolExpression.CreateAnd(sQueryWhereClause, conditionExpression); } else { Debug.Assert(conditionMap.EdmProperty != null); //Produce a Condition Expression for the Condition Map. BoolExpression conditionExpression = GetConditionExpression(cNode, conditionMap); //Add the condition expression to the exisiting C side Where clause using an "And" cQueryWhereClause = BoolExpression.CreateAnd(cQueryWhereClause, conditionExpression); } } } } /// /// Takes in a JoinTreeNode and a Contition Property Map and creates an BoolExpression /// for the Condition Map. /// /// /// /// private static BoolExpression GetConditionExpression(MemberPath member, StorageConditionPropertyMapping conditionMap) { //Get the member for which the condition is being specified EdmMember conditionMember = (conditionMap.ColumnProperty != null) ? conditionMap.ColumnProperty : conditionMap.EdmProperty; MemberPath conditionMemberNode = new MemberPath(member, conditionMember); //Check if this is a IsNull condition MemberRestriction conditionExpression = null; if (conditionMap.IsNull.HasValue) { // for conditions on scalars, create NodeValue nodes, otherwise NodeType Constant conditionConstant = (true == conditionMap.IsNull.Value) ? Constant.Null : Constant.NotNull; if (true == MetadataHelper.IsNonRefSimpleMember(conditionMember)) { conditionExpression = new ScalarRestriction(conditionMemberNode, conditionConstant); } else { conditionExpression = new TypeRestriction(conditionMemberNode, conditionConstant); } } else { conditionExpression = new ScalarRestriction(conditionMemberNode, new ScalarConstant(conditionMap.Value)); } Debug.Assert(conditionExpression != null); return BoolExpression.CreateLiteral(conditionExpression, null); } private static bool IsBooleanMember(MemberPath path) { PrimitiveType primitive = path.EdmType as PrimitiveType; return (primitive != null && primitive.PrimitiveTypeKind == PrimitiveTypeKind.Boolean); } #endregion #region String methods internal override void ToCompactString(System.Text.StringBuilder builder) { builder.Append("CellCreator"); // No state to really show i.e., m_maps } #endregion } }