// requires: IsForeignKeySuperSetOfPrimaryKeyInChildTable() is false // and primaryKeys of ChildTable are not mapped in cell. cell // corresponds to an association set. parentSet is the set // corresponding to the end that we are looking at // effects: Checks if the constraint is correctly maintained in // C-space via an association set (being a subset of the // corresponding entitySet) private static bool CheckConstraintWhenOnlyParentMapped( AssociationSet assocSet, AssociationEndMember endMember, QueryRewriter childRewriter, QueryRewriter parentRewriter) { var childContext = childRewriter.ViewgenContext; var parentContext = parentRewriter.ViewgenContext; var pNode = parentRewriter.BasicView; Debug.Assert(pNode != null); var endRoleBoolean = new RoleBoolean(assocSet.AssociationSetEnds[endMember.Name]); // use query in pNode as a factory to create a bool expression for the endRoleBoolean var endCondition = pNode.RightFragmentQuery.Condition.Create(endRoleBoolean); var cNodeQuery = FragmentQuery.Create(pNode.RightFragmentQuery.Attributes, endCondition); var qp = FragmentQueryProcessor.Merge(childContext.RightFragmentQP, parentContext.RightFragmentQP); var cImpliesP = qp.IsContainedIn(cNodeQuery, pNode.RightFragmentQuery); return cImpliesP; }
// effects: Ensures that there is a relationship mapped into the C-space for some cell in m_cellGroup. Else // adds an error to errorLog private void GuaranteeMappedRelationshipForForeignKey( QueryRewriter childRewriter, QueryRewriter parentRewriter, IEnumerable<Cell> cells, ErrorLog errorLog, ConfigViewGenerator config) { var childContext = childRewriter.ViewgenContext; var parentContext = parentRewriter.ViewgenContext; // Find a cell where this foreign key is mapped as a relationship var prefix = new MemberPath(ChildTable); var primaryKey = ExtentKey.GetPrimaryKeyForEntityType(prefix, ChildTable.ElementType); var primaryKeyFields = primaryKey.KeyFields; var foundCell = false; var foundValidParentColumnsForForeignKey = false; //we need to find only one, dont error on any one check being false List<ErrorLog.Record> errorListForInvalidParentColumnsForForeignKey = null; foreach (var cell in cells) { if (cell.SQuery.Extent.Equals(ChildTable) == false) { continue; } // The childtable is mapped to a relationship in the C-space in cell // Check that all the columns of the foreign key and the primary key in the child table are mapped to some // property in the C-space var parentEnd = GetRelationEndForColumns(cell, ChildColumns); if (parentEnd != null && CheckParentColumnsForForeignKey(cell, cells, parentEnd, ref errorListForInvalidParentColumnsForForeignKey) == false) { // Not an error unless we find no valid case continue; } else { foundValidParentColumnsForForeignKey = true; } var childEnd = GetRelationEndForColumns(cell, primaryKeyFields); Debug.Assert( childEnd == null || parentEnd != childEnd, "Ends are same => PKey and child columns are same - code should gone to other method"); // Note: If both of them are not-null, they are mapped to the // same association set -- since we checked that particular cell if (childEnd != null && parentEnd != null && FindEntitySetForColumnsMappedToEntityKeys(cells, primaryKeyFields) != null) { foundCell = true; CheckConstraintWhenParentChildMapped(cell, errorLog, parentEnd, config); break; // Done processing for the foreign key - either it was mapped correctly or it was not } else if (parentEnd != null) { // At this point, we know cell corresponds to an association set var assocSet = (AssociationSet)cell.CQuery.Extent; var parentSet = MetadataHelper.GetEntitySetAtEnd(assocSet, parentEnd); foundCell = CheckConstraintWhenOnlyParentMapped(assocSet, parentEnd, childRewriter, parentRewriter); if (foundCell) { break; } } } //CheckParentColumnsForForeignKey has returned no matches, Error. if (!foundValidParentColumnsForForeignKey) { Debug.Assert( errorListForInvalidParentColumnsForForeignKey != null && errorListForInvalidParentColumnsForForeignKey.Count > 0); foreach (var errorRecord in errorListForInvalidParentColumnsForForeignKey) { errorLog.AddEntry(errorRecord); } return; } if (foundCell == false) { // No cell found -- Declare error var message = Strings.ViewGen_Foreign_Key_Missing_Relationship_Mapping(ToUserString()); IEnumerable<LeftCellWrapper> parentWrappers = GetWrappersFromContext(parentContext, ParentTable); IEnumerable<LeftCellWrapper> childWrappers = GetWrappersFromContext(childContext, ChildTable); var bothExtentWrappers = new Set<LeftCellWrapper>(parentWrappers); bothExtentWrappers.AddRange(childWrappers); var record = new ErrorLog.Record( ViewGenErrorCode.ForeignKeyMissingRelationshipMapping, message, bothExtentWrappers, String.Empty); errorLog.AddEntry(record); } }
private bool CheckIfConstraintMappedToForeignKeyAssociation( QueryRewriter childRewriter, QueryRewriter parentRewriter, Set<Cell> cells) { var childContext = childRewriter.ViewgenContext; var parentContext = parentRewriter.ViewgenContext; //First collect the sets of properties that the principal and dependant ends of this FK //are mapped to in the Edm side. var childPropertiesSet = new List<Set<EdmProperty>>(); var parentPropertiesSet = new List<Set<EdmProperty>>(); foreach (var cell in cells) { if (cell.CQuery.Extent.BuiltInTypeKind != BuiltInTypeKind.AssociationSet) { var childProperties = cell.GetCSlotsForTableColumns(ChildColumns); if ((childProperties != null) && (childProperties.Count != 0)) { childPropertiesSet.Add(childProperties); } var parentProperties = cell.GetCSlotsForTableColumns(ParentColumns); if ((parentProperties != null) && (parentProperties.Count != 0)) { parentPropertiesSet.Add(parentProperties); } } } //Now Check if the properties on the Edm side are connected via an FK relationship. if ((childPropertiesSet.Count != 0) && (parentPropertiesSet.Count != 0)) { var foreignKeyAssociations = childContext.EntityContainerMapping.EdmEntityContainer.BaseEntitySets.OfType<AssociationSet>().Where( it => it.ElementType.IsForeignKey).Select(it => it.ElementType); foreach (var association in foreignKeyAssociations) { var refConstraint = association.ReferentialConstraints.FirstOrDefault(); //We need to check to see if the dependent properties that were mapped from S side are present as //dependant properties of this ref constraint on the Edm side. We need to do the same for principal side but //we can not enforce equality since the order of the properties participating in the constraint on the S side and //C side could be different. This is OK as long as they are mapped appropriately. We also can not use Existance as a sufficient //condition since it will allow invalid mapping where FK columns could have been flipped when mapping to the Edm side. So //we make sure that the index of the properties in the principal and dependant are same on the Edm side even if they are in //different order for ref constraints for Edm and store side. var childRefPropertiesCollection = childPropertiesSet.Where(it => it.SetEquals(new Set<EdmProperty>(refConstraint.ToProperties))); var parentRefPropertiesCollection = parentPropertiesSet.Where(it => it.SetEquals(new Set<EdmProperty>(refConstraint.FromProperties))); if ((childRefPropertiesCollection.Count() != 0 && parentRefPropertiesCollection.Count() != 0)) { foreach (var parentRefProperties in parentRefPropertiesCollection) { var parentIndexes = GetPropertyIndexes(parentRefProperties, refConstraint.FromProperties); foreach (var childRefProperties in childRefPropertiesCollection) { var childIndexes = GetPropertyIndexes(childRefProperties, refConstraint.ToProperties); if (childIndexes.SequenceEqual(parentIndexes)) { return true; } } } } } } return false; }
// requires: constraint.ChildColumns form a key in // constraint.ChildTable (actually they should subsume the primary key) private void GuaranteeForeignKeyConstraintInCSpace( QueryRewriter childRewriter, QueryRewriter parentRewriter, ErrorLog errorLog) { var childContext = childRewriter.ViewgenContext; var parentContext = parentRewriter.ViewgenContext; var cNode = childRewriter.BasicView; var pNode = parentRewriter.BasicView; var qp = FragmentQueryProcessor.Merge(childContext.RightFragmentQP, parentContext.RightFragmentQP); var cImpliesP = qp.IsContainedIn(cNode.RightFragmentQuery, pNode.RightFragmentQuery); if (false == cImpliesP) { // Foreign key constraint not being ensured in C-space var childExtents = LeftCellWrapper.GetExtentListAsUserString(cNode.GetLeaves()); var parentExtents = LeftCellWrapper.GetExtentListAsUserString(pNode.GetLeaves()); var message = Strings.ViewGen_Foreign_Key_Not_Guaranteed_InCSpace( ToUserString()); // Add all wrappers into allWrappers var allWrappers = new Set<LeftCellWrapper>(pNode.GetLeaves()); allWrappers.AddRange(cNode.GetLeaves()); var record = new ErrorLog.Record(ViewGenErrorCode.ForeignKeyNotGuaranteedInCSpace, message, allWrappers, String.Empty); errorLog.AddEntry(record); } }
// effects: Checks that this foreign key constraints for all the // tables are being ensured on the C-side as well. If not, adds // errors to the errorLog internal void CheckConstraint( Set<Cell> cells, QueryRewriter childRewriter, QueryRewriter parentRewriter, ErrorLog errorLog, ConfigViewGenerator config) { if (IsConstraintRelevantForCells(cells) == false) { // if the constraint does not deal with any cell in this group, ignore it return; } if (config.IsNormalTracing) { Trace.WriteLine(String.Empty); Trace.WriteLine(String.Empty); Trace.Write("Checking: "); Trace.WriteLine(this); } if (childRewriter == null && parentRewriter == null) { // Neither table is mapped - so we are fine return; } // If the child table has not been mapped, we used to say that we // are fine. However, if we have SPerson(pid) and SAddress(aid, // pid), where pid is an FK into SPerson, we are in trouble if // SAddress is not mapped - SPerson could get deleted. So we // check for it as well // if the parent table is not mapped, we also have a problem if (childRewriter == null) { var message = Strings.ViewGen_Foreign_Key_Missing_Table_Mapping( ToUserString(), ChildTable.Name); // Get the cells from the parent table var record = new ErrorLog.Record( ViewGenErrorCode.ForeignKeyMissingTableMapping, message, parentRewriter.UsedCells, String.Empty); errorLog.AddEntry(record); return; } if (parentRewriter == null) { var message = Strings.ViewGen_Foreign_Key_Missing_Table_Mapping( ToUserString(), ParentTable.Name); // Get the cells from the child table var record = new ErrorLog.Record( ViewGenErrorCode.ForeignKeyMissingTableMapping, message, childRewriter.UsedCells, String.Empty); errorLog.AddEntry(record); return; } // Note: we do not check if the parent columns correspond to the // table's keys - metadata checks for that //First check if the FK is covered by Foreign Key Association //If we find this, we don't need to check for independent associations. If user maps the Fk to both FK and independent associations, //the regular round tripping validation will catch the error. if (CheckIfConstraintMappedToForeignKeyAssociation(childRewriter, parentRewriter, cells)) { return; } // Check if the foreign key in the child table corresponds to the primary key, i.e., if // the foreign key (e.g., pid, pid2) is a superset of the actual key members (e.g., pid), it means // that the foreign key is also the primary key for this table -- so we can propagate the queries upto C-Space // rather than doing the cell check var initialErrorLogSize = errorLog.Count; if (IsForeignKeySuperSetOfPrimaryKeyInChildTable()) { GuaranteeForeignKeyConstraintInCSpace(childRewriter, parentRewriter, errorLog); } else { GuaranteeMappedRelationshipForForeignKey(childRewriter, parentRewriter, cells, errorLog, config); } if (initialErrorLogSize == errorLog.Count) { // Check if the order of columns in foreign key correponds to the // mappings in m_cellGroup, e.g., if <pid1, pid2> in SAddress is // a foreign key into <pid1, pid2> of the SPerson table, make // sure that this order is preserved through the mappings in m_cellGroup CheckForeignKeyColumnOrder(cells, errorLog); } }
// requires: IsForeignKeySuperSetOfPrimaryKeyInChildTable() is false // and primaryKeys of ChildTable are not mapped in cell. cell // corresponds to an association set. parentSet is the set // corresponding to the end that we are looking at // effects: Checks if the constraint is correctly maintained in // C-space via an association set (being a subset of the // corresponding entitySet) private bool CheckConstraintWhenOnlyParentMapped(Cell cell, EntitySet parentSet, AssociationSet assocSet, AssociationEndMember endMember, QueryRewriter childRewriter, QueryRewriter parentRewriter, ConfigViewGenerator config) { ViewgenContext childContext = childRewriter.ViewgenContext; ViewgenContext parentContext = parentRewriter.ViewgenContext; CellTreeNode pNode = parentRewriter.BasicView; Debug.Assert(pNode != null); RoleBoolean endRoleBoolean = new RoleBoolean(assocSet.AssociationSetEnds[endMember.Name]); // use query in pNode as a factory to create a bool expression for the endRoleBoolean BoolExpression endCondition = pNode.RightFragmentQuery.Condition.Create(endRoleBoolean); FragmentQuery cNodeQuery = FragmentQuery.Create(pNode.RightFragmentQuery.Attributes, endCondition); FragmentQueryProcessor qp = FragmentQueryProcessor.Merge(childContext.RightFragmentQP, parentContext.RightFragmentQP); bool cImpliesP = qp.IsContainedIn(cNodeQuery, pNode.RightFragmentQuery); return cImpliesP; }
// requires: constraint.ChildColumns form a key in // constraint.ChildTable (actually they should subsume the primary key) private void GuaranteeForeignKeyConstraintInCSpace(QueryRewriter childRewriter, QueryRewriter parentRewriter, ErrorLog errorLog, ConfigViewGenerator config) { ViewgenContext childContext = childRewriter.ViewgenContext; ViewgenContext parentContext = parentRewriter.ViewgenContext; CellTreeNode cNode = childRewriter.BasicView; CellTreeNode pNode = parentRewriter.BasicView; FragmentQueryProcessor qp = FragmentQueryProcessor.Merge(childContext.RightFragmentQP, parentContext.RightFragmentQP); bool cImpliesP = qp.IsContainedIn(cNode.RightFragmentQuery, pNode.RightFragmentQuery); if (false == cImpliesP) { // Foreign key constraint not being ensured in C-space string childExtents = LeftCellWrapper.GetExtentListAsUserString(cNode.GetLeaves()); string parentExtents = LeftCellWrapper.GetExtentListAsUserString(pNode.GetLeaves()); string message = System.Data.Entity.Strings.ViewGen_Foreign_Key_Not_Guaranteed_InCSpace( ToUserString()); // Add all wrappers into allWrappers Set<LeftCellWrapper> allWrappers = new Set<LeftCellWrapper>(pNode.GetLeaves()); allWrappers.AddRange(cNode.GetLeaves()); ErrorLog.Record record = new ErrorLog.Record(true, ViewGenErrorCode.ForeignKeyNotGuaranteedInCSpace, message, allWrappers, String.Empty); errorLog.AddEntry(record); } }
private QueryRewriter GenerateViewsForExtentAndType( EdmType generatedType, ViewgenContext context, CqlIdentifiers identifiers, ViewSet views, ViewGenMode mode) { Debug.Assert(mode != ViewGenMode.GenerateAllViews, "By definition this method can not handle generating views for all extents"); var queryRewriter = new QueryRewriter(generatedType, context, mode); queryRewriter.GenerateViewComponents(); // Get the basic view var basicView = queryRewriter.BasicView; if (m_config.IsNormalTracing) { Helpers.StringTrace("Basic View: "); Helpers.StringTraceLine(basicView.ToString()); } var simplifiedView = GenerateSimplifiedView(basicView, queryRewriter.UsedCells); if (m_config.IsNormalTracing) { Helpers.StringTraceLine(String.Empty); Helpers.StringTrace("Simplified View: "); Helpers.StringTraceLine(simplifiedView.ToString()); } var cqlGen = new CqlGenerator( simplifiedView, queryRewriter.CaseStatements, identifiers, context.MemberMaps.ProjectedSlotMap, queryRewriter.UsedCells.Count, queryRewriter.TopLevelWhereClause, m_entityContainerMapping.StorageMappingItemCollection); string eSQLView; DbQueryCommandTree commandTree; if (m_config.GenerateEsql) { eSQLView = cqlGen.GenerateEsql(); commandTree = null; } else { eSQLView = null; commandTree = cqlGen.GenerateCqt(); } var generatedView = GeneratedView.CreateGeneratedView( context.Extent, generatedType, commandTree, eSQLView, m_entityContainerMapping.StorageMappingItemCollection, m_config); views.Add(context.Extent, generatedView); return queryRewriter; }