Example #1
0
 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();
 }
Example #18
0
        // 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);
 }
Example #26
0
 // 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();
 }