431 lines
20 KiB
C#
431 lines
20 KiB
C#
//---------------------------------------------------------------------
|
|
// <copyright file="MemberDomainMap.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//
|
|
// @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<Constant>;
|
|
|
|
// 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<MemberPath, CellConstantSet> 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<MemberPath, CellConstantSet> 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<MemberPath> m_projectedConditionMembers = new Set<MemberPath>();
|
|
|
|
private EdmItemCollection m_edmItemCollection;
|
|
|
|
#endregion
|
|
|
|
|
|
#region Constructor
|
|
private MemberDomainMap(Dictionary<MemberPath, CellConstantSet> domainMap,
|
|
Dictionary<MemberPath, CellConstantSet> 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<Cell> extentCells, EdmItemCollection edmItemCollection, ConfigViewGenerator config, Dictionary<EntityType, Set<EntityType>> inheritanceGraph)
|
|
{
|
|
m_conditionDomainMap = new Dictionary<MemberPath, CellConstantSet>(MemberPath.EqualityComparer);
|
|
m_edmItemCollection = edmItemCollection;
|
|
|
|
Dictionary<MemberPath, CellConstantSet> 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, CellConstantSet>(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<Constant>(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<Constant>(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<MemberPath, CellConstantSet> 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<Constant> 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<MemberPath, CellConstantSet> 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<Cell> 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<MemberPath, Set<Constant>> 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<MemberPath> ConditionMembers(EntitySetBase extent)
|
|
{
|
|
foreach (MemberPath path in m_conditionDomainMap.Keys)
|
|
{
|
|
if (path.Extent.Equals(extent))
|
|
{
|
|
yield return path;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
internal IEnumerable<MemberPath> NonConditionMembers(EntitySetBase extent)
|
|
{
|
|
foreach (MemberPath path in m_nonConditionDomainMap.Keys)
|
|
{
|
|
if (path.Extent.Equals(extent))
|
|
{
|
|
yield return path;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Adds AllOtherConstants element to the domain set given by MemberPath
|
|
/// </summary>
|
|
internal void AddSentinel(MemberPath path)
|
|
{
|
|
CellConstantSet set = GetDomainInternal(path);
|
|
set.Add(Constant.AllOtherConstants);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes AllOtherConstant element from the domain set given by MemberPath
|
|
/// </summary>
|
|
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<Constant> 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<Constant> domainValues)
|
|
{
|
|
// update domainMap
|
|
Set<Constant> 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<Constant> 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<Constant> 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<Constant> iconstants, MemberProjectedSlot islot)
|
|
: base(iconstants)
|
|
{
|
|
slot = islot;
|
|
}
|
|
|
|
internal MemberProjectedSlot slot;
|
|
|
|
public override string ToString()
|
|
{
|
|
return base.ToString();
|
|
}
|
|
}
|
|
}
|
|
}
|