//--------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner [....] // @backupOwner [....] //--------------------------------------------------------------------- namespace System.Data.Mapping.ViewGeneration.Structures { using System.Collections.Generic; using System.Data.Common.Utils; using System.Data.Mapping.ViewGeneration.Utils; using System.Data.Metadata.Edm; using System.Diagnostics; using System.Linq; using System.Text; using CellConstantSet = Common.Utils.Set; // This class keeps track of the domain values of the different members // in a schema. E.g., for a discriminator, it keeps track of "P", // "C"; for type of Person, it keeps track of Person, Customer, etc // It exposes two concepts -- the domain of a member variable and the // different possible values for that member, e.g., the possible values // could be 3, 4, 5 but the domain could be 3, 4 (domain is always a // subset of possibleVales internal class MemberDomainMap : InternalBase { #region Fields // Keep track of the actual domain for each member on which we have conditions // Note: some subtleties: For QueryDomainMap it holds just C-side condition members. For UpdateDominMap // it now holds S-side condition members as well as members with no s-side condition but C-side condition // such that C-side condition restricts the domain of the member(column). private Dictionary m_conditionDomainMap; // Keep track of the actual domain for each member on which we have no conditions // CellConstantSet in m_nonConditionDomainMap is really CellConstantSetInfo private Dictionary m_nonConditionDomainMap; // members on C-side that are projected, don't have conditions, but the respective S-side members do // we need to threat those just as regular members except in validation, where S-side conditions are // projected to C-side. For that, KB needs to add the respective constraints involving this members // For example: CPerson1.Phone IN {?, NOT(?, NULL)) on C-side. We need to know that // type(CPerson1)=Customer <-> !(CPerson1.Phone IN {?}) for validation of domain constraints private Set m_projectedConditionMembers = new Set(); private EdmItemCollection m_edmItemCollection; #endregion #region Constructor private MemberDomainMap(Dictionary domainMap, Dictionary nonConditionDomainMap, EdmItemCollection edmItemCollection) { m_conditionDomainMap = domainMap; m_nonConditionDomainMap = nonConditionDomainMap; m_edmItemCollection = edmItemCollection; } // effects: Creates a map with all the condition member constants // from extentCells. viewtarget determines whether the view is an // update or query view internal MemberDomainMap(ViewTarget viewTarget, bool isValidationEnabled, IEnumerable extentCells, EdmItemCollection edmItemCollection, ConfigViewGenerator config, Dictionary> inheritanceGraph) { m_conditionDomainMap = new Dictionary(MemberPath.EqualityComparer); m_edmItemCollection = edmItemCollection; Dictionary domainMap = null; if (viewTarget == ViewTarget.UpdateView) { domainMap = Domain.ComputeConstantDomainSetsForSlotsInUpdateViews(extentCells, m_edmItemCollection); } else { domainMap = Domain.ComputeConstantDomainSetsForSlotsInQueryViews(extentCells, m_edmItemCollection, isValidationEnabled); } foreach (Cell cell in extentCells) { CellQuery cellQuery = cell.GetLeftQuery(viewTarget); // Get the atoms from cellQuery and only keep the ones that // are condition members foreach (MemberRestriction condition in cellQuery.GetConjunctsFromWhereClause()) { // Note: TypeConditions are created using OneOfTypeConst and // scalars are created using OneOfScalarConst MemberPath memberPath = condition.RestrictedMemberSlot.MemberPath; Debug.Assert(condition is ScalarRestriction || condition is TypeRestriction, "Unexpected restriction"); // Take the narrowed domain from domainMap, if any CellConstantSet domainValues; if (!domainMap.TryGetValue(memberPath, out domainValues)) { domainValues = Domain.DeriveDomainFromMemberPath(memberPath, edmItemCollection, isValidationEnabled); } //Don't count conditions that are satisfied through IsNull=false if (!domainValues.Contains(Constant.Null)) { //multiple values of condition represent disjunction in conditions (not currently supported) // if there is any condition constant that is NotNull if (condition.Domain.Values.All(conditionConstant => (conditionConstant.Equals(Constant.NotNull)))) { continue; } //else there is atleast one condition value that is allowed, continue view generation } //------------------------------------------ //| Nullable | IsNull | Test case | //| T | T | T | //| T | F | T | //| F | T | F | //| F | F | T | //------------------------------------------ //IsNull condition on a member that is non nullable is an invalid condition if (domainValues.Count <= 0 || (!domainValues.Contains(Constant.Null) && condition.Domain.Values.Contains(Constant.Null))) { string message = System.Data.Entity.Strings.ViewGen_InvalidCondition(memberPath.PathToString(false)); ErrorLog.Record record = new ErrorLog.Record(true, ViewGenErrorCode.InvalidCondition, message, cell, String.Empty); ExceptionHelpers.ThrowMappingException(record, config); } if (memberPath.IsAlwaysDefined(inheritanceGraph) == false) { domainValues.Add(Constant.Undefined); } AddToDomainMap(memberPath, domainValues); } } // Fill up the domains for the remaining slots as well m_nonConditionDomainMap = new Dictionary(MemberPath.EqualityComparer); foreach (Cell cell in extentCells) { CellQuery cellQuery = cell.GetLeftQuery(viewTarget); // Get the atoms from cellQuery and only keep the ones that // are condition members foreach (MemberProjectedSlot slot in cellQuery.GetAllQuerySlots()) { MemberPath member = slot.MemberPath; if (m_conditionDomainMap.ContainsKey(member) == false && m_nonConditionDomainMap.ContainsKey(member) == false) { CellConstantSet memberSet = Domain.DeriveDomainFromMemberPath(member, m_edmItemCollection, true /* Regardless of validation, leave the domain unbounded because this is not a condition member */); if (member.IsAlwaysDefined(inheritanceGraph) == false) { // nonConditionMember may belong to subclass memberSet.Add(Constant.Undefined); } memberSet = Domain.ExpandNegationsInDomain(memberSet, memberSet); m_nonConditionDomainMap.Add(member, new CellConstantSetInfo(memberSet, slot)); } } } } #endregion #region Properties internal bool IsProjectedConditionMember(MemberPath memberPath) { return m_projectedConditionMembers.Contains(memberPath); } #endregion #region Methods // effects: Returns an "open-world" domain, i.e., // one in which not-null constants are used to represent some other value from the domain internal MemberDomainMap GetOpenDomain() { var domainMap = m_conditionDomainMap.ToDictionary(p => p.Key, p => new Set(p.Value, Constant.EqualityComparer)); ExpandDomainsIfNeeded(domainMap); return new MemberDomainMap(domainMap, m_nonConditionDomainMap, m_edmItemCollection); } // effects: Creates a deep copy of MemberDomainMap // nonConditionDomainMap is read-only so it is reused without cloning internal MemberDomainMap MakeCopy() { var domainMap = m_conditionDomainMap.ToDictionary(p => p.Key, p => new Set(p.Value, Constant.EqualityComparer)); return new MemberDomainMap(domainMap, m_nonConditionDomainMap, m_edmItemCollection); } // effects: Adds negated constants to the possible set of values if none exists in that set. // Needed so that we can handle cases when discriminator in the store as P, C but could have other values // as well. internal void ExpandDomainsToIncludeAllPossibleValues() { ExpandDomainsIfNeeded(m_conditionDomainMap); } private void ExpandDomainsIfNeeded(Dictionary domainMapForMembers) { // For the S-side, we always says that NOT(...) is // present. For example, if we are told "C", "P", we assume // that NOT(C, P) is possibly present in that column foreach (MemberPath path in domainMapForMembers.Keys) { CellConstantSet possibleValues = domainMapForMembers[path]; if (path.IsScalarType() && possibleValues.Any(c => c is NegatedConstant) == false) { if (MetadataHelper.HasDiscreteDomain(path.EdmType)) { // for a discrete domain, add all values that are not currently represented // in the domain Set completeDomain = Domain.DeriveDomainFromMemberPath(path, m_edmItemCollection, true /* leaveDomainUnbounded */); possibleValues.Unite(completeDomain); } else { // for a non-discrete domain, add NOT("C", "P") NegatedConstant negatedConstant = new NegatedConstant(possibleValues); possibleValues.Add(negatedConstant); } } } } // effects: Shrinks the domain of members whose types can be enumerated - currently it applies // only to boolean type as for enums we don't restrict enum values to specified members only. // For example NOT(False, True, Null) for a boolean domain should be removed internal void ReduceEnumerableDomainToEnumeratedValues(ViewTarget target, ConfigViewGenerator config) { // Go through the two maps ReduceEnumerableDomainToEnumeratedValues(target, m_conditionDomainMap, config, m_edmItemCollection); ReduceEnumerableDomainToEnumeratedValues(target, m_nonConditionDomainMap, config, m_edmItemCollection); } // effects: Fixes the domains of variables in this as specified in FixEnumerableDomains private static void ReduceEnumerableDomainToEnumeratedValues(ViewTarget target, Dictionary domainMap, ConfigViewGenerator config, EdmItemCollection edmItemCollection) { foreach (MemberPath member in domainMap.Keys) { if (MetadataHelper.HasDiscreteDomain(member.EdmType) == false) { continue; } CellConstantSet domain = Domain.DeriveDomainFromMemberPath(member, edmItemCollection, true /* leaveDomainUnbounded */); CellConstantSet extra = domainMap[member].Difference(domain); extra.Remove(Constant.Undefined); if (extra.Count > 0) { // domainMap has extra members -- we should get rid of them if (config.IsNormalTracing) { Helpers.FormatTraceLine("Changed domain of {0} from {1} - subtract {2}", member, domainMap[member], extra); } domainMap[member].Subtract(extra); } } } // requires: this domainMap has been created for the C-side // effects: Fixes the mergedDomain map in this by merging entries // available in updateDomainMap internal static void PropagateUpdateDomainToQueryDomain(IEnumerable cells, MemberDomainMap queryDomainMap, MemberDomainMap updateDomainMap) { foreach (Cell cell in cells) { CellQuery cQuery = cell.CQuery; CellQuery sQuery = cell.SQuery; for (int i = 0; i < cQuery.NumProjectedSlots; i++) { MemberProjectedSlot cSlot = cQuery.ProjectedSlotAt(i) as MemberProjectedSlot; MemberProjectedSlot sSlot = sQuery.ProjectedSlotAt(i) as MemberProjectedSlot; if (cSlot == null || sSlot == null) { continue; } // Get the domain for sSlot and merge with cSlot's MemberPath cPath = cSlot.MemberPath; MemberPath sPath = sSlot.MemberPath; CellConstantSet cDomain = queryDomainMap.GetDomainInternal(cPath); CellConstantSet sDomain = updateDomainMap.GetDomainInternal(sPath); // skip NULL because if c-side member is nullable, it's already there, and otherwise can't be taken // skip negated because negated values are translated in a special way cDomain.Unite(sDomain.Where(constant => !constant.IsNull() && !(constant is NegatedConstant))); if (updateDomainMap.IsConditionMember(sPath) && !queryDomainMap.IsConditionMember(cPath)) { // record this member so KB knows we have to generate constraints for it queryDomainMap.m_projectedConditionMembers.Add(cPath); } } } ExpandNegationsInDomainMap(queryDomainMap.m_conditionDomainMap); ExpandNegationsInDomainMap(queryDomainMap.m_nonConditionDomainMap); } private static void ExpandNegationsInDomainMap(Dictionary> domainMap) { foreach (var path in domainMap.Keys.ToArray()) { domainMap[path] = Domain.ExpandNegationsInDomain(domainMap[path]); } } internal bool IsConditionMember(MemberPath path) { return m_conditionDomainMap.ContainsKey(path); } internal IEnumerable ConditionMembers(EntitySetBase extent) { foreach (MemberPath path in m_conditionDomainMap.Keys) { if (path.Extent.Equals(extent)) { yield return path; } } } internal IEnumerable NonConditionMembers(EntitySetBase extent) { foreach (MemberPath path in m_nonConditionDomainMap.Keys) { if (path.Extent.Equals(extent)) { yield return path; } } } /// /// Adds AllOtherConstants element to the domain set given by MemberPath /// internal void AddSentinel(MemberPath path) { CellConstantSet set = GetDomainInternal(path); set.Add(Constant.AllOtherConstants); } /// /// Removes AllOtherConstant element from the domain set given by MemberPath /// internal void RemoveSentinel(MemberPath path) { CellConstantSet set = GetDomainInternal(path); set.Remove(Constant.AllOtherConstants); } // requires member exist in this // effects: Returns the possible values/domain for that member internal IEnumerable GetDomain(MemberPath path) { return GetDomainInternal(path); } private CellConstantSet GetDomainInternal(MemberPath path) { CellConstantSet result; bool found = m_conditionDomainMap.TryGetValue(path, out result); if (!found) { result = m_nonConditionDomainMap[path]; // It better be in this one! } return result; } // keeps the same set identity for the updated cell constant domain internal void UpdateConditionMemberDomain(MemberPath path, IEnumerable domainValues) { // update domainMap Set oldDomain = m_conditionDomainMap[path]; oldDomain.Clear(); oldDomain.Unite(domainValues); } // effects: For member, adds domainValues as the set of values that // member can take. Merges them with any existing values if present private void AddToDomainMap(MemberPath member, IEnumerable domainValues) { CellConstantSet possibleValues; if (false == m_conditionDomainMap.TryGetValue(member, out possibleValues)) { possibleValues = new CellConstantSet(Constant.EqualityComparer); } possibleValues.Unite(domainValues); // Add the normalized domain to the map so that later uses of the // domain are consistent m_conditionDomainMap[member] = Domain.ExpandNegationsInDomain(possibleValues, possibleValues); } internal override void ToCompactString(StringBuilder builder) { foreach (MemberPath memberPath in m_conditionDomainMap.Keys) { builder.Append('('); memberPath.ToCompactString(builder); IEnumerable domain = GetDomain(memberPath); builder.Append(": "); StringUtil.ToCommaSeparatedStringSorted(builder, domain); builder.Append(") "); } } #endregion // struct to keep track of the constant set for a particular slot private class CellConstantSetInfo : CellConstantSet { internal CellConstantSetInfo(Set iconstants, MemberProjectedSlot islot) : base(iconstants) { slot = islot; } internal MemberProjectedSlot slot; public override string ToString() { return base.ToString(); } } } }