internal void Merge(ErrorLog log) { foreach (Record record in log.m_log) { m_log.Add(record); } }
// effects: constructor that allows single mapping error internal InternalMappingException(string message, ErrorLog.Record record) : base(message) { //Contract.Requires(record != null); m_errorLog = new ErrorLog(); m_errorLog.AddEntry(record); }
internal static void ThrowMappingException(ErrorLog errorLog, ConfigViewGenerator config) { InternalMappingException exception = new InternalMappingException(errorLog.ToUserString(), errorLog); if (config.IsNormalTracing) { exception.ErrorLog.PrintTrace(); } throw exception; }
// effects: Creates a view generator object that can be used to generate views // based on usedCells (projectedSlotMap are useful for deciphering the fields) internal BasicViewGenerator(MemberProjectionIndex projectedSlotMap, List<LeftCellWrapper> usedCells, FragmentQuery activeDomain, ViewgenContext context, MemberDomainMap domainMap, ErrorLog errorLog, ConfigViewGenerator config) { Debug.Assert(usedCells.Count > 0, "No used cells"); m_projectedSlotMap = projectedSlotMap; m_usedCells = usedCells; m_viewgenContext = context; m_activeDomain = activeDomain; m_errorLog = errorLog; m_config = config; m_domainMap = domainMap; }
private bool CheckIfConstraintMappedToForeignKeyAssociation(QueryRewriter childRewriter, QueryRewriter parentRewriter, Set<Cell> cells, ErrorLog errorLog) { ViewgenContext childContext = childRewriter.ViewgenContext; ViewgenContext 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 (Cell 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 (AssociationType association in foreignKeyAssociations) { ReferentialConstraint 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, 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); } }
// effects: constructor that allows a log internal InternalMappingException(string message, ErrorLog errorLog) : base(message) { //Contract.Requires(errorLog != null); m_errorLog = errorLog; }
// effects: constructor that allows a log internal InternalMappingException(string message, ErrorLog errorLog) : base(message) { EntityUtil.CheckArgumentNull(errorLog, "errorLog"); m_errorLog = errorLog; }
// requires: IsForeignKeySuperSetOfPrimaryKeyInChildTable() is false // effects: Given that both the ChildColumns in this and the // primaryKey of ChildTable are mapped. Return true iff no error occurred private bool CheckConstraintWhenParentChildMapped( Cell cell, ErrorLog errorLog, AssociationEndMember parentEnd, ConfigViewGenerator config) { var ok = true; // The foreign key constraint has been mapped to a // relationship. Check if the multiplicities are consistent // If all columns in the child table (corresponding to // the constraint) are nullable, the parent end can be // 0..1 or 1..1. Else if must be 1..1 if (parentEnd.RelationshipMultiplicity == RelationshipMultiplicity.Many) { // Parent should at most one since we are talking // about foreign keys here var message = Strings.ViewGen_Foreign_Key_UpperBound_MustBeOne( ToUserString(), cell.CQuery.Extent.Name, parentEnd.Name); var record = new ErrorLog.Record(ViewGenErrorCode.ForeignKeyUpperBoundMustBeOne, message, cell, String.Empty); errorLog.AddEntry(record); ok = false; } if (MemberPath.AreAllMembersNullable(ChildColumns) == false && parentEnd.RelationshipMultiplicity != RelationshipMultiplicity.One) { // Some column in the constraint in the child table // is non-nullable and lower bound is not 1 var message = Strings.ViewGen_Foreign_Key_LowerBound_MustBeOne( ToUserString(), cell.CQuery.Extent.Name, parentEnd.Name); var record = new ErrorLog.Record(ViewGenErrorCode.ForeignKeyLowerBoundMustBeOne, message, cell, String.Empty); errorLog.AddEntry(record); ok = false; } if (config.IsNormalTracing && ok) { Trace.WriteLine("Foreign key mapped to relationship " + cell.CQuery.Extent.Name); } return ok; }
// 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: Add the set of errors in errorLog to this internal void AddErrors(ErrorLog errorLog) { m_errorLog.Merge(errorLog); }
private void CheckForeignKeyConstraints(ErrorLog errorLog) { foreach (var constraint in m_foreignKeyConstraints) { QueryRewriter childRewriter = null; QueryRewriter parentRewriter = null; m_queryRewriterCache.TryGetValue(constraint.ChildTable, out childRewriter); m_queryRewriterCache.TryGetValue(constraint.ParentTable, out parentRewriter); constraint.CheckConstraint(m_cellGroup, childRewriter, parentRewriter, errorLog, m_config); } }
// requires: schema refers to C-side or S-side schema for the cells // inside this. if schema.IsQueryView is true, the left side of cells refers // to the C side (and vice-versa for the right side) // effects: Generates the relevant views for the schema side and // returns them. If allowMissingAttributes is true and attributes // are missing on the schema side, substitutes them with NULL // Modifies views to contain the generated views for different // extents specified by cells and the the schemaContext private ErrorLog GenerateDirectionalViews(ViewTarget viewTarget, CqlIdentifiers identifiers, ViewSet views) { var isQueryView = viewTarget == ViewTarget.QueryView; // Partition cells by extent. var extentCellMap = GroupCellsByExtent(m_cellGroup, viewTarget); // Keep track of the mapping exceptions that we have generated var errorLog = new ErrorLog(); // Generate views for each extent foreach (var extent in extentCellMap.Keys) { if (m_config.IsViewTracing) { Helpers.StringTraceLine(String.Empty); Helpers.StringTraceLine(String.Empty); Helpers.FormatTraceLine( "================= Generating {0} View for: {1} ===========================", isQueryView ? "Query" : "Update", extent.Name); Helpers.StringTraceLine(String.Empty); Helpers.StringTraceLine(String.Empty); } try { // (1) view generation (checks that extents are fully mapped) var queryRewriter = GenerateDirectionalViewsForExtent(viewTarget, extent, identifiers, views); // (2) validation for update views if (viewTarget == ViewTarget.UpdateView && m_config.IsValidationEnabled) { if (m_config.IsViewTracing) { Helpers.StringTraceLine(String.Empty); Helpers.StringTraceLine(String.Empty); Helpers.FormatTraceLine( "----------------- Validation for generated update view for: {0} -----------------", extent.Name); Helpers.StringTraceLine(String.Empty); Helpers.StringTraceLine(String.Empty); } var validator = new RewritingValidator(queryRewriter.ViewgenContext, queryRewriter.BasicView); validator.Validate(); } } catch (InternalMappingException exception) { // All exceptions have mapping errors in them Debug.Assert( exception.ErrorLog.Count > 0, "Incorrectly created mapping exception"); errorLog.Merge(exception.ErrorLog); } } return errorLog; }
private ErrorLog GenerateQueryViewForExtentAndType( CqlIdentifiers identifiers, ViewSet views, EntitySetBase entity, EntityTypeBase type, ViewGenMode mode) { Debug.Assert(mode != ViewGenMode.GenerateAllViews); // Keep track of the mapping exceptions that we have generated var errorLog = new ErrorLog(); if (m_config.IsViewTracing) { Helpers.StringTraceLine(String.Empty); Helpers.StringTraceLine(String.Empty); Helpers.FormatTraceLine( "================= Generating {0} Query View for: {1} ===========================", (mode == ViewGenMode.OfTypeViews) ? "OfType" : "OfTypeOnly", entity.Name); Helpers.StringTraceLine(String.Empty); Helpers.StringTraceLine(String.Empty); } try { // (1) view generation (checks that extents are fully mapped) var context = CreateViewgenContext(entity, ViewTarget.QueryView, identifiers); var queryRewriter = GenerateViewsForExtentAndType(type, context, identifiers, views, mode); } catch (InternalMappingException exception) { // All exceptions have mapping errors in them Debug.Assert(exception.ErrorLog.Count > 0, "Incorrectly created mapping exception"); errorLog.Merge(exception.ErrorLog); } return errorLog; }
private void AddUnrecoverableAttributesError( IEnumerable<MemberPath> attributes, BoolExpression domainAddedWhereClause, ErrorLog errorLog) { var builder = new StringBuilder(); var extentName = StringUtil.FormatInvariant("{0}", _extentPath); var tableString = Strings.ViewGen_Extent; var attributesString = StringUtil.ToCommaSeparatedString(GetTypeBasedMemberPathList(attributes)); builder.AppendLine(Strings.ViewGen_Cannot_Recover_Attributes(attributesString, tableString, extentName)); RewritingValidator.EntityConfigurationToUserString(domainAddedWhereClause, builder); var record = new ErrorLog.Record( ViewGenErrorCode.AttributesUnrecoverable, builder.ToString(), _context.AllWrappersForExtent, String.Empty); errorLog.AddEntry(record); }
// make sure that we can find a rewriting for each possible entity shape appearing in an extent // Possible optimization for OfType view generation: // Cache "used views" for each (currentPath, domainValue) combination private void EnsureConfigurationIsFullyMapped( MemberPath currentPath, BoolExpression currentWhereClause, HashSet<FragmentQuery> outputUsedViews, ErrorLog errorLog) { foreach (var domainValue in GetDomain(currentPath)) { if (domainValue == Constant.Undefined) { continue; // no point in trying to recover a situation that can never happen } TraceVerbose("REWRITING FOR {0}={1}", currentPath, domainValue); // construct WHERE clause for this value var domainAddedWhereClause = CreateMemberCondition(currentPath, domainValue); // AND the current where clause to it var domainWhereClause = BoolExpression.CreateAnd(currentWhereClause, domainAddedWhereClause); // first check whether we can recover instances of this type - don't care about the attributes - to produce a helpful error message Tile<FragmentQuery> rewriting; if (false == FindRewritingAndUsedViews(_keyAttributes, domainWhereClause, outputUsedViews, out rewriting)) { if (!ErrorPatternMatcher.FindMappingErrors(_context, _domainMap, _errorLog)) { var builder = new StringBuilder(); var extentName = StringUtil.FormatInvariant("{0}", _extentPath); var whereClause = rewriting.Query.Condition; whereClause.ExpensiveSimplify(); if (whereClause.RepresentsAllTypeConditions) { var tableString = Strings.ViewGen_Extent; builder.AppendLine(Strings.ViewGen_Cannot_Recover_Types(tableString, extentName)); } else { var entitiesString = Strings.ViewGen_Entities; builder.AppendLine(Strings.ViewGen_Cannot_Disambiguate_MultiConstant(entitiesString, extentName)); } RewritingValidator.EntityConfigurationToUserString(whereClause, builder); var record = new ErrorLog.Record( ViewGenErrorCode.AmbiguousMultiConstants, builder.ToString(), _context.AllWrappersForExtent, String.Empty); errorLog.AddEntry(record); } } else { var typeConstant = domainValue as TypeConstant; if (typeConstant != null) { // we are enumerating types var edmType = typeConstant.EdmType; // If can recover the type, make sure can get all the necessary attributes (key is included for EntityTypes) var nonConditionalAttributes = GetNonConditionalScalarMembers(edmType, currentPath, _domainMap).Union( GetNonConditionalComplexMembers(edmType, currentPath, _domainMap)).ToList(); IEnumerable<MemberPath> notCoverdAttributes; if (nonConditionalAttributes.Count > 0 && !FindRewritingAndUsedViews( nonConditionalAttributes, domainWhereClause, outputUsedViews, out rewriting, out notCoverdAttributes)) { //Error: No mapping specified for some attributes // remove keys nonConditionalAttributes = new List<MemberPath>(nonConditionalAttributes.Where(a => !a.IsPartOfKey)); Debug.Assert(nonConditionalAttributes.Count > 0, "Must have caught key-only case earlier"); AddUnrecoverableAttributesError(notCoverdAttributes, domainAddedWhereClause, errorLog); } else { // recurse into complex members foreach (var complexMember in GetConditionalComplexMembers(edmType, currentPath, _domainMap)) { EnsureConfigurationIsFullyMapped(complexMember, domainWhereClause, outputUsedViews, errorLog); } // recurse into scalar members foreach (var scalarMember in GetConditionalScalarMembers(edmType, currentPath, _domainMap)) { EnsureConfigurationIsFullyMapped(scalarMember, domainWhereClause, outputUsedViews, errorLog); } } } } } }
internal ViewGenResults() { m_views = new KeyToListMap<EntitySetBase, GeneratedView>(EqualityComparer<EntitySetBase>.Default); m_errorLog = new ErrorLog(); }
// requires: node corresponds to an IJ, LOJ, FOJ node // effects: Given a union node and the slots required by the parent, // generates a CqlBlock for the subtree rooted at node private CqlBlock JoinToCqlBlock(bool[] requiredSlots, CqlIdentifiers identifiers, ref int blockAliasNum, ref List <WithRelationship> withRelationships) { int totalSlots = requiredSlots.Length; Debug.Assert(OpType == CellTreeOpType.IJ || OpType == CellTreeOpType.LOJ || OpType == CellTreeOpType.FOJ, "Only these join operations handled"); List <CqlBlock> children = new List <CqlBlock>(); List <Tuple <QualifiedSlot, MemberPath> > additionalChildSlots = new List <Tuple <QualifiedSlot, MemberPath> >(); // First get the children nodes (FROM part) foreach (CellTreeNode child in Children) { // Determine the slots that are projected by this child. // These are the required slots as well - unlike Union, we do not need the child to project any extra nulls. bool[] childProjectedSlots = child.GetProjectedSlots(); AndWith(childProjectedSlots, requiredSlots); CqlBlock childBlock = child.ToCqlBlock(childProjectedSlots, identifiers, ref blockAliasNum, ref withRelationships); children.Add(childBlock); for (int qualifiedSlotNumber = childProjectedSlots.Length; qualifiedSlotNumber < childBlock.Slots.Count; qualifiedSlotNumber++) { additionalChildSlots.Add(Tuple.Create(childBlock.QualifySlotWithBlockAlias(qualifiedSlotNumber), childBlock.MemberPath(qualifiedSlotNumber))); } Debug.Assert(totalSlots == child.NumBoolSlots + child.NumProjectedSlots, "Number of required slots is different from what each node in the tree has?"); } // Now get the slots that are projected out by this node (SELECT part) SlotInfo[] slotInfos = new SlotInfo[totalSlots + additionalChildSlots.Count]; for (int slotNum = 0; slotNum < totalSlots; slotNum++) { // Note: this call could create a CaseStatementSlot (i.e., slotInfo.SlotValue is CaseStatementSlot) // which uses "from" booleans that need to be projected by children SlotInfo slotInfo = GetJoinSlotInfo(OpType, requiredSlots[slotNum], children, slotNum, identifiers); slotInfos[slotNum] = slotInfo; } for (int i = 0, slotNum = totalSlots; slotNum < totalSlots + additionalChildSlots.Count; slotNum++, i++) { slotInfos[slotNum] = new SlotInfo(true, true, additionalChildSlots[i].Item1, additionalChildSlots[i].Item2); } // Generate the ON conditions: For each child, generate an ON // clause with the 0th child on the key fields List <JoinCqlBlock.OnClause> onClauses = new List <JoinCqlBlock.OnClause>(); for (int i = 1; i < children.Count; i++) { CqlBlock child = children[i]; JoinCqlBlock.OnClause onClause = new JoinCqlBlock.OnClause(); foreach (int keySlotNum in this.KeySlots) { if (ViewgenContext.Config.IsValidationEnabled) { Debug.Assert(children[0].IsProjected(keySlotNum), "Key is not in 0th child"); Debug.Assert(child.IsProjected(keySlotNum), "Key is not in child"); } else { if (!child.IsProjected(keySlotNum) || !children[0].IsProjected(keySlotNum)) { ErrorLog errorLog = new ErrorLog(); errorLog.AddEntry(new ErrorLog.Record(true, ViewGenErrorCode.NoJoinKeyOrFKProvidedInMapping, Strings.Viewgen_NoJoinKeyOrFK, ViewgenContext.AllWrappersForExtent, String.Empty)); ExceptionHelpers.ThrowMappingException(errorLog, ViewgenContext.Config); } } var firstSlot = children[0].QualifySlotWithBlockAlias(keySlotNum); var secondSlot = child.QualifySlotWithBlockAlias(keySlotNum); var outputMember = slotInfos[keySlotNum].OutputMember; onClause.Add(firstSlot, outputMember, secondSlot, outputMember); } onClauses.Add(onClause); } CqlBlock result = new JoinCqlBlock(OpType, slotInfos, children, onClauses, identifiers, ++blockAliasNum); return(result); }
// 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); } }
// effects: Given a list of cells in the schema, generates the query and // update mapping views for OFTYPE(Extent, Type) combinations in this schema // container. Returns a list of generated query and update views. // If it is false and some columns in a table are unmapped, an // exception is raised private static ViewGenResults GenerateViewsFromCells(List<Cell> cells, ConfigViewGenerator config, CqlIdentifiers identifiers, StorageEntityContainerMapping containerMapping) { EntityUtil.CheckArgumentNull(cells, "cells"); EntityUtil.CheckArgumentNull(config, "config"); Debug.Assert(cells.Count > 0, "There must be at least one cell in the container mapping"); // Go through each table and determine their foreign key constraints EntityContainer container = containerMapping.StorageEntityContainer; Debug.Assert(container != null); ViewGenResults viewGenResults = new ViewGenResults(); ErrorLog tmpLog = EnsureAllCSpaceContainerSetsAreMapped(cells, config, containerMapping); if (tmpLog.Count > 0) { viewGenResults.AddErrors(tmpLog); Helpers.StringTraceLine(viewGenResults.ErrorsToString()); return viewGenResults; } List<ForeignConstraint> foreignKeyConstraints = ForeignConstraint.GetForeignConstraints(container); CellPartitioner partitioner = new CellPartitioner(cells, foreignKeyConstraints); List<CellGroup> cellGroups = partitioner.GroupRelatedCells(); foreach (CellGroup cellGroup in cellGroups) { ViewGenerator viewGenerator = null; ErrorLog groupErrorLog = new ErrorLog(); try { viewGenerator = new ViewGenerator(cellGroup, config, foreignKeyConstraints, containerMapping); } catch (InternalMappingException exception) { // All exceptions have mapping errors in them Debug.Assert(exception.ErrorLog.Count > 0, "Incorrectly created mapping exception"); groupErrorLog = exception.ErrorLog; } if (groupErrorLog.Count == 0) { Debug.Assert(viewGenerator != null); groupErrorLog = viewGenerator.GenerateAllBidirectionalViews(viewGenResults.Views, identifiers); } if (groupErrorLog.Count != 0) { viewGenResults.AddErrors(groupErrorLog); } } // We used to print the errors here. Now we trace them as they are being thrown //if (viewGenResults.HasErrors && config.IsViewTracing) { // Helpers.StringTraceLine(viewGenResults.ErrorsToString()); //} return viewGenResults; }
// 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); } }
// effects: Given a container, ensures that all entity/association // sets in container on the C-side have been mapped private static ErrorLog EnsureAllCSpaceContainerSetsAreMapped(IEnumerable<Cell> cells, ConfigViewGenerator config, StorageEntityContainerMapping containerMapping) { Set<EntitySetBase> mappedExtents = new Set<EntitySetBase>(); string mslFileLocation = null; EntityContainer container = null; // Determine the container and name of the file while determining // the set of mapped extents in the cells foreach (Cell cell in cells) { mappedExtents.Add(cell.CQuery.Extent); mslFileLocation = cell.CellLabel.SourceLocation; // All cells are from the same container container = cell.CQuery.Extent.EntityContainer; } Debug.Assert(container != null); List<EntitySetBase> missingExtents = new List<EntitySetBase>(); // Go through all the extents in the container and determine // extents that are missing foreach (EntitySetBase extent in container.BaseEntitySets) { if (mappedExtents.Contains(extent) == false && !(containerMapping.HasQueryViewForSetMap(extent.Name))) { AssociationSet associationSet = extent as AssociationSet; if (associationSet==null || !associationSet.ElementType.IsForeignKey) { missingExtents.Add(extent); } } } ErrorLog errorLog = new ErrorLog(); // If any extent is not mapped, add an error if (missingExtents.Count > 0) { StringBuilder extentBuilder = new StringBuilder(); bool isFirst = true; foreach (EntitySetBase extent in missingExtents) { if (isFirst == false) { extentBuilder.Append(", "); } isFirst = false; extentBuilder.Append(extent.Name); } string message = System.Data.Entity.Strings.ViewGen_Missing_Set_Mapping(extentBuilder); // Find the cell with smallest line number - so that we can // point to the beginning of the file int lowestLineNum = -1; Cell smallestCell = null; foreach (Cell cell in cells) { if (lowestLineNum == -1 || cell.CellLabel.StartLineNumber < lowestLineNum) { smallestCell = cell; lowestLineNum = cell.CellLabel.StartLineNumber; } } Debug.Assert(smallestCell != null && lowestLineNum >= 0); EdmSchemaError edmSchemaError = new EdmSchemaError(message, (int)ViewGenErrorCode.MissingExtentMapping, EdmSchemaErrorSeverity.Error, containerMapping.SourceLocation, containerMapping.StartLineNumber, containerMapping.StartLinePosition, null); ErrorLog.Record record = new ErrorLog.Record(edmSchemaError); errorLog.AddEntry(record); } return errorLog; }
// requires: all columns in constraint.ParentColumns and // constraint.ChildColumns must have been mapped in some cell in m_cellGroup // effects: Given the foreign key constraint, checks if the // constraint.ChildColumns are mapped to the constraint.ParentColumns // in m_cellGroup in the right oder. If not, adds an error to m_errorLog and returns // false. Else returns true private bool CheckForeignKeyColumnOrder(Set<Cell> cells, ErrorLog errorLog) { // Go through every cell and find the cells that are relevant to // parent and those that are relevant to child // Then for each cell pair (parent, child) make sure that the // projected foreign keys columns in C-space are aligned var parentCells = new List<Cell>(); var childCells = new List<Cell>(); foreach (var cell in cells) { if (cell.SQuery.Extent.Equals(ChildTable)) { childCells.Add(cell); } if (cell.SQuery.Extent.Equals(ParentTable)) { parentCells.Add(cell); } } // Make sure that all child cells and parent cells align on // the columns, i.e., for each DISTINCT pair C and P, get the columns // on the S-side. Then get the corresponding fields on the // C-side. The fields on the C-side should match var foundParentCell = false; var foundChildCell = false; foreach (var childCell in childCells) { var allChildSlotNums = GetSlotNumsForColumns(childCell, ChildColumns); if (allChildSlotNums.Count == 0) { // slots in present in S-side, ignore continue; } List<MemberPath> childPaths = null; List<MemberPath> parentPaths = null; Cell errorParentCell = null; foreach (var childSlotNums in allChildSlotNums) { foundChildCell = true; // Get the fields on the C-side childPaths = new List<MemberPath>(childSlotNums.Count); foreach (var childSlotNum in childSlotNums) { // Initial slots only have JoinTreeSlots var childSlot = (MemberProjectedSlot)childCell.CQuery.ProjectedSlotAt(childSlotNum); Debug.Assert(childSlot != null); childPaths.Add(childSlot.MemberPath); } foreach (var parentCell in parentCells) { var allParentSlotNums = GetSlotNumsForColumns(parentCell, ParentColumns); if (allParentSlotNums.Count == 0) { // * Parent and child cell are the same - we do not // need to check since we want to check the foreign // key constraint mapping across cells // * Some slots not in present in S-side, ignore continue; } foreach (var parentSlotNums in allParentSlotNums) { foundParentCell = true; parentPaths = new List<MemberPath>(parentSlotNums.Count); foreach (var parentSlotNum in parentSlotNums) { var parentSlot = (MemberProjectedSlot)parentCell.CQuery.ProjectedSlotAt(parentSlotNum); Debug.Assert(parentSlot != null); parentPaths.Add(parentSlot.MemberPath); } // Make sure that the last member of each of these is the same // or the paths are essentially equivalent via referential constraints // We need to check that the last member is essentially the same because it could // be a regular scenario where aid is mapped to PersonAddress and Address - there // is no ref constraint. So when projected into C-Space, we will get Address.aid // and PersonAddress.Address.aid if (childPaths.Count == parentPaths.Count) { var notAllPathsMatched = false; for (var i = 0; i < childPaths.Count && !notAllPathsMatched; i++) { var parentPath = parentPaths[i]; var childPath = childPaths[i]; if (!parentPath.LeafEdmMember.Equals(childPath.LeafEdmMember)) //Child path did not match { if (parentPath.IsEquivalentViaRefConstraint(childPath)) { //Specifying the referential constraint once in the C space should be enough. //This is the only way possible today. //We might be able to derive more knowledge by using boolean logic return true; } else { notAllPathsMatched = true; } } } if (!notAllPathsMatched) { return true; //all childPaths matched parentPaths } else { //If not this one, some other Parent Cell may match. errorParentCell = parentCell; } } } } //foreach parentCell } //If execution is at this point, no parent cell's end has matched (otherwise it would have returned true) Debug.Assert(childPaths != null, "child paths should be set"); Debug.Assert(parentPaths != null, "parent paths should be set"); Debug.Assert(errorParentCell != null, "errorParentCell should be set"); var message = Strings.ViewGen_Foreign_Key_ColumnOrder_Incorrect( ToUserString(), MemberPath.PropertiesToUserString(ChildColumns, false), ChildTable.Name, MemberPath.PropertiesToUserString(childPaths, false), childCell.CQuery.Extent.Name, MemberPath.PropertiesToUserString(ParentColumns, false), ParentTable.Name, MemberPath.PropertiesToUserString(parentPaths, false), errorParentCell.CQuery.Extent.Name); var record = new ErrorLog.Record( ViewGenErrorCode.ForeignKeyColumnOrderIncorrect, message, new[] { errorParentCell, childCell }, String.Empty); errorLog.AddEntry(record); return false; } Debug.Assert(foundParentCell, "Some cell that mapped the parent's key must be present!"); Debug.Assert( foundChildCell == true, "Some cell that mapped the child's foreign key must be present according to the requires clause!"); return true; }
/// <summary> /// Entry point for Type specific generation of Query Views /// </summary> internal static ViewGenResults GenerateTypeSpecificQueryView(StorageEntityContainerMapping containerMapping, ConfigViewGenerator config, EntitySetBase entity, EntityTypeBase type, bool includeSubtypes, out bool success) { EntityUtil.CheckArgumentNull(containerMapping, "containerMapping"); EntityUtil.CheckArgumentNull(config, "config"); EntityUtil.CheckArgumentNull(entity, "entity"); EntityUtil.CheckArgumentNull(type, "type"); Debug.Assert(!type.Abstract, "Can not generate OfType/OfTypeOnly query view for and abstract type"); if (config.IsNormalTracing) { Helpers.StringTraceLine(""); Helpers.StringTraceLine("<<<<<<<< Generating Query View for Entity [" + entity.Name + "] OfType" + (includeSubtypes ? "" : "Only") + "(" + type.Name + ") >>>>>>>"); } if (containerMapping.GetEntitySetMapping(entity.Name).QueryView != null) { //Type-specific QV does not exist in the cache, but // there is a EntitySet QV. So we can't generate the view (no mapping exists for this EntitySet) // and we rely on Query to call us again to get the EntitySet View. success = false; return null; } //Compute Cell Groups or get it from Memoizer InputForComputingCellGroups args = new InputForComputingCellGroups(containerMapping, config); OutputFromComputeCellGroups result = containerMapping.GetCellgroups(args); success = result.Success; if (!success) { return null; } List<ForeignConstraint> foreignKeyConstraints = result.ForeignKeyConstraints; // Get a Clone of cell groups from cache since cells are modified during viewgen, and we dont want the cached copy to change List<CellGroup> cellGroups = cellGroups = result.CellGroups.Select(setOfcells => new CellGroup(setOfcells.Select(cell => new Cell(cell)))).ToList(); List<Cell> cells = result.Cells; CqlIdentifiers identifiers = result.Identifiers; ViewGenResults viewGenResults = new ViewGenResults(); ErrorLog tmpLog = EnsureAllCSpaceContainerSetsAreMapped(cells, config, containerMapping); if (tmpLog.Count > 0) { viewGenResults.AddErrors(tmpLog); Helpers.StringTraceLine(viewGenResults.ErrorsToString()); success = true; //atleast we tried successfully return viewGenResults; } foreach (CellGroup cellGroup in cellGroups) { if (!DoesCellGroupContainEntitySet(cellGroup, entity)) { continue; } ViewGenerator viewGenerator = null; ErrorLog groupErrorLog = new ErrorLog(); try { viewGenerator = new ViewGenerator(cellGroup, config, foreignKeyConstraints, containerMapping); } catch (InternalMappingException exception) { // All exceptions have mapping errors in them Debug.Assert(exception.ErrorLog.Count > 0, "Incorrectly created mapping exception"); groupErrorLog = exception.ErrorLog; } if (groupErrorLog.Count > 0) { break; } Debug.Assert(viewGenerator != null); //make sure there is no exception thrown that does not add error to log ViewGenMode mode = includeSubtypes ? ViewGenMode.OfTypeViews : ViewGenMode.OfTypeOnlyViews; groupErrorLog = viewGenerator.GenerateQueryViewForSingleExtent(viewGenResults.Views, identifiers, entity, type, mode); if (groupErrorLog.Count != 0) { viewGenResults.AddErrors(groupErrorLog); } } success = true; return viewGenResults; }
// effects: constructor that allows single mapping error internal InternalMappingException(string message, ErrorLog.Record record) : base(message) { EntityUtil.CheckArgumentNull(record, "record"); m_errorLog = new ErrorLog(); m_errorLog.AddEntry(record); }
// requires: cells are not normalized, i.e., no slot is null in the cell queries // effects: Constructs a validator object that is capable of // validating all the schema cells together internal CellGroupValidator(IEnumerable<Cell> cells, ConfigViewGenerator config) { m_cells = cells; m_config = config; m_errorLog = new ErrorLog(); }