File: System\Data\Mapping\FunctionImportMapping.cs
Project: ndp\fx\src\DataEntity\System.Data.Entity.csproj (System.Data.Entity)
//---------------------------------------------------------------------
// <copyright file="FunctionImportMapping.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner       Microsoft
// @backupOwner willa
//---------------------------------------------------------------------
 
namespace System.Data.Mapping
{
    using System.Collections;
    using System.Collections.Generic;
    using System.Data.Common.Utils;
    using System.Data.Common.Utils.Boolean;
    using System.Data.Entity;
    using System.Data.Metadata.Edm;
    using System.Diagnostics;
    using System.Globalization;
    using System.Linq;
    using System.Xml;
    using System.Xml.XPath;
    using OM = System.Collections.ObjectModel;
 
    /// <summary>
    /// Represents a mapping from a model function import to a store composable or non-composable function.
    /// </summary>
    internal abstract class FunctionImportMapping
    {
        internal FunctionImportMapping(EdmFunction functionImport, EdmFunction targetFunction)
        {
            this.FunctionImport = EntityUtil.CheckArgumentNull(functionImport, "functionImport");
            this.TargetFunction = EntityUtil.CheckArgumentNull(targetFunction, "targetFunction");
        }
 
        /// <summary>
        /// Gets model function (or source of the mapping)
        /// </summary>
        internal readonly EdmFunction FunctionImport;
 
        /// <summary>
        /// Gets store function (or target of the mapping)
        /// </summary>
        internal readonly EdmFunction TargetFunction;
    }
 
    internal sealed class FunctionImportStructuralTypeMappingKB
    {
        internal FunctionImportStructuralTypeMappingKB(
            IEnumerable<FunctionImportStructuralTypeMapping> structuralTypeMappings,
            ItemCollection itemCollection)
        {
            EntityUtil.CheckArgumentNull(structuralTypeMappings, "structuralTypeMappings");
            m_itemCollection = EntityUtil.CheckArgumentNull(itemCollection, "itemCollection");
 
            // If no specific type mapping.
            if (structuralTypeMappings.Count() == 0)
            {
                // Initialize with defaults.
                this.ReturnTypeColumnsRenameMapping = new Dictionary<string, FunctionImportReturnTypeStructuralTypeColumnRenameMapping>();
                this.NormalizedEntityTypeMappings = new OM.ReadOnlyCollection<FunctionImportNormalizedEntityTypeMapping>(new List<FunctionImportNormalizedEntityTypeMapping>());
                this.DiscriminatorColumns = new OM.ReadOnlyCollection<string>(new List<string>());
                this.MappedEntityTypes = new OM.ReadOnlyCollection<EntityType>(new List<EntityType>());
                return;
            }
 
            IEnumerable<FunctionImportEntityTypeMapping> entityTypeMappings = structuralTypeMappings.OfType<FunctionImportEntityTypeMapping>();
 
            // FunctionImportEntityTypeMapping
            if (null != entityTypeMappings && null != entityTypeMappings.FirstOrDefault<FunctionImportEntityTypeMapping>())
            {
                var isOfTypeEntityTypeColumnsRenameMapping = new Dictionary<EntityType, OM.Collection<FunctionImportReturnTypePropertyMapping>>();
                var entityTypeColumnsRenameMapping = new Dictionary<EntityType, OM.Collection<FunctionImportReturnTypePropertyMapping>>();
                var normalizedEntityTypeMappings = new List<FunctionImportNormalizedEntityTypeMapping>();
 
                // Collect all mapped entity types.
                this.MappedEntityTypes = entityTypeMappings
                    .SelectMany(mapping => mapping.GetMappedEntityTypes(m_itemCollection))
                    .Distinct()
                    .ToList()
                    .AsReadOnly();
 
                // Collect all discriminator columns.
                this.DiscriminatorColumns = entityTypeMappings
                    .SelectMany(mapping => mapping.GetDiscriminatorColumns())
                    .Distinct()
                    .ToList()
                    .AsReadOnly();
 
                m_entityTypeLineInfos = new KeyToListMap<EntityType, LineInfo>(EqualityComparer<EntityType>.Default);
                m_isTypeOfLineInfos = new KeyToListMap<EntityType, LineInfo>(EqualityComparer<EntityType>.Default);
 
                foreach (var entityTypeMapping in entityTypeMappings)
                {
                    // Remember LineInfos for error reporting.
                    foreach (var entityType in entityTypeMapping.EntityTypes)
                    {
                        m_entityTypeLineInfos.Add(entityType, entityTypeMapping.LineInfo);
                    }
                    foreach (var isTypeOf in entityTypeMapping.IsOfTypeEntityTypes)
                    {
                        m_isTypeOfLineInfos.Add(isTypeOf, entityTypeMapping.LineInfo);
                    }
 
                    // Create map from column name to condition.
                    var columnMap = entityTypeMapping.Conditions.ToDictionary(
                        condition => condition.ColumnName,
                        condition => condition);
 
                    // Align conditions with discriminator columns.
                    var columnMappings = new List<FunctionImportEntityTypeMappingCondition>(this.DiscriminatorColumns.Count);
                    for (int i = 0; i < this.DiscriminatorColumns.Count; i++)
                    {
                        string discriminatorColumn = this.DiscriminatorColumns[i];
                        FunctionImportEntityTypeMappingCondition mappingCondition;
                        if (columnMap.TryGetValue(discriminatorColumn, out mappingCondition))
                        {
                            columnMappings.Add(mappingCondition);
                        }
                        else
                        {
                            // Null indicates the value for this discriminator doesn't matter.
                            columnMappings.Add(null);
                        }
                    }
 
                    // Create bit map for implied entity types.
                    bool[] impliedEntityTypesBitMap = new bool[this.MappedEntityTypes.Count];
                    var impliedEntityTypesSet = new Set<EntityType>(entityTypeMapping.GetMappedEntityTypes(m_itemCollection));
                    for (int i = 0; i < this.MappedEntityTypes.Count; i++)
                    {
                        impliedEntityTypesBitMap[i] = impliedEntityTypesSet.Contains(this.MappedEntityTypes[i]);
                    }
 
                    // Construct normalized mapping.
                    normalizedEntityTypeMappings.Add(new FunctionImportNormalizedEntityTypeMapping(this, columnMappings, new BitArray(impliedEntityTypesBitMap)));
 
                    // Construct the rename mappings by adding isTypeOf types and specific entity types to the corresponding lists.
                    foreach (var isOfType in entityTypeMapping.IsOfTypeEntityTypes)
                    {
                        if (!isOfTypeEntityTypeColumnsRenameMapping.Keys.Contains(isOfType))
                        {
                            isOfTypeEntityTypeColumnsRenameMapping.Add(isOfType, new OM.Collection<FunctionImportReturnTypePropertyMapping>());
                        }
                        foreach (var rename in entityTypeMapping.ColumnsRenameList)
                        {
                            isOfTypeEntityTypeColumnsRenameMapping[isOfType].Add(rename);
                        }
                    }
                    foreach (var entityType in entityTypeMapping.EntityTypes)
                    {
                        if (!entityTypeColumnsRenameMapping.Keys.Contains(entityType))
                        {
                            entityTypeColumnsRenameMapping.Add(entityType, new OM.Collection<FunctionImportReturnTypePropertyMapping>());
                        }
                        foreach (var rename in entityTypeMapping.ColumnsRenameList)
                        {
                            entityTypeColumnsRenameMapping[entityType].Add(rename);
                        }
                    }
                }
 
                this.ReturnTypeColumnsRenameMapping = new FunctionImportReturnTypeEntityTypeColumnsRenameBuilder(isOfTypeEntityTypeColumnsRenameMapping,
                                                                                                                 entityTypeColumnsRenameMapping)
                                                      .ColumnRenameMapping;
 
                this.NormalizedEntityTypeMappings = new OM.ReadOnlyCollection<FunctionImportNormalizedEntityTypeMapping>(
                    normalizedEntityTypeMappings);
            }
            else
            {
                // FunctionImportComplexTypeMapping
                Debug.Assert(structuralTypeMappings.First() is FunctionImportComplexTypeMapping, "only two types can have renames, complexType and entityType");
                IEnumerable<FunctionImportComplexTypeMapping> complexTypeMappings = structuralTypeMappings.Cast<FunctionImportComplexTypeMapping>();
 
                Debug.Assert(complexTypeMappings.Count() == 1, "how come there are more than 1, complex type cannot derive from other complex type");
 
                this.ReturnTypeColumnsRenameMapping = new Dictionary<string, FunctionImportReturnTypeStructuralTypeColumnRenameMapping>();
                foreach (var rename in complexTypeMappings.First().ColumnsRenameList)
                {
                    FunctionImportReturnTypeStructuralTypeColumnRenameMapping columnRenameMapping = new FunctionImportReturnTypeStructuralTypeColumnRenameMapping(rename.CMember);
                    columnRenameMapping.AddRename(new FunctionImportReturnTypeStructuralTypeColumn(
                            rename.SColumn,
                            complexTypeMappings.First().ReturnType,
                            false,
                            rename.LineInfo));
                    this.ReturnTypeColumnsRenameMapping.Add(rename.CMember, columnRenameMapping);
                }
 
                // Initialize the entity mapping data as empty.
                this.NormalizedEntityTypeMappings = new OM.ReadOnlyCollection<FunctionImportNormalizedEntityTypeMapping>(new List<FunctionImportNormalizedEntityTypeMapping>());
                this.DiscriminatorColumns = new OM.ReadOnlyCollection<string>(new List<string>() { });
                this.MappedEntityTypes = new OM.ReadOnlyCollection<EntityType>(new List<EntityType>() { });
            }
        }
 
        private readonly ItemCollection m_itemCollection;
        private readonly KeyToListMap<EntityType, LineInfo> m_entityTypeLineInfos;
        private readonly KeyToListMap<EntityType, LineInfo> m_isTypeOfLineInfos;
 
        /// <summary>
        /// Gets all types in scope for this mapping.
        /// </summary>
        internal readonly OM.ReadOnlyCollection<EntityType> MappedEntityTypes;
 
        /// <summary>
        /// Gets a list of all discriminator columns used in this mapping.
        /// </summary>
        internal readonly OM.ReadOnlyCollection<string> DiscriminatorColumns;
 
        /// <summary>
        /// Gets normalized representation of all EntityTypeMapping fragments for this
        /// function import mapping.
        /// </summary>
        internal readonly OM.ReadOnlyCollection<FunctionImportNormalizedEntityTypeMapping> NormalizedEntityTypeMappings;
 
        /// <summary>
        /// Get the columns rename mapping for return type, the first string is the member name
        /// the second one is column names for different types that mentioned in the mapping.
        /// </summary>
        internal readonly Dictionary<string, FunctionImportReturnTypeStructuralTypeColumnRenameMapping> ReturnTypeColumnsRenameMapping;
 
        internal bool ValidateTypeConditions(bool validateAmbiguity, IList<EdmSchemaError> errors, string sourceLocation)
        {
            // Verify that all types can be produced
            KeyToListMap<EntityType, LineInfo> unreachableEntityTypes;
            KeyToListMap<EntityType, LineInfo> unreachableIsTypeOfs;
            GetUnreachableTypes(validateAmbiguity, out unreachableEntityTypes, out unreachableIsTypeOfs);
 
            bool valid = true;
            foreach (var unreachableEntityType in unreachableEntityTypes.KeyValuePairs)
            {
                var lineInfo = unreachableEntityType.Value.First();
                string lines = StringUtil.ToCommaSeparatedString(unreachableEntityType.Value.Select(li => li.LineNumber));
                EdmSchemaError error = new EdmSchemaError(
                    Strings.Mapping_FunctionImport_UnreachableType(unreachableEntityType.Key.FullName, lines),
                    (int)StorageMappingErrorCode.MappingFunctionImportAmbiguousTypeConditions,
                    EdmSchemaErrorSeverity.Error,
                    sourceLocation,
                    lineInfo.LineNumber, 
                    lineInfo.LinePosition);
                errors.Add(error);
                valid = false;
            }
            foreach (var unreachableIsTypeOf in unreachableIsTypeOfs.KeyValuePairs)
            {
                var lineInfo = unreachableIsTypeOf.Value.First();
                string lines = StringUtil.ToCommaSeparatedString(unreachableIsTypeOf.Value.Select(li => li.LineNumber));
                string isTypeOfDescription = StorageMslConstructs.IsTypeOf + unreachableIsTypeOf.Key.FullName + StorageMslConstructs.IsTypeOfTerminal;
                EdmSchemaError error = new EdmSchemaError(
                    Strings.Mapping_FunctionImport_UnreachableIsTypeOf(isTypeOfDescription, lines),
                    (int)StorageMappingErrorCode.MappingFunctionImportAmbiguousTypeConditions,
                    EdmSchemaErrorSeverity.Error,
                    sourceLocation,
                    lineInfo.LineNumber,
                    lineInfo.LinePosition);
                errors.Add(error);
                valid = false;
            }
 
            return valid;
        }
 
        /// <summary>
        /// Determines which explicitly mapped types in the function import mapping cannot be generated.
        /// For IsTypeOf declarations, reports if no type in hierarchy can be produced.
        /// 
        /// Works by:
        /// 
        /// - Converting type mapping conditions into vertices
        /// - Checking that some assignment satisfies 
        /// </summary>
        private void GetUnreachableTypes(
            bool validateAmbiguity,
            out KeyToListMap<EntityType, LineInfo> unreachableEntityTypes,
            out KeyToListMap<EntityType, LineInfo> unreachableIsTypeOfs)
        {
            // Contains, for each DiscriminatorColumn, a domain variable where the domain values are
            // integers representing the ordinal within discriminatorDomains.
            DomainVariable<string, ValueCondition>[] variables = ConstructDomainVariables();
 
            // Convert type mapping conditions to decision diagram vertices.
            var converter = new DomainConstraintConversionContext<string, ValueCondition>();
            Vertex[] mappingConditions = ConvertMappingConditionsToVertices(converter, variables);
 
            // Find reachable types.
            Set<EntityType> reachableTypes = validateAmbiguity ?
                FindUnambiguouslyReachableTypes(converter, mappingConditions) :
                FindReachableTypes(converter, mappingConditions);
 
            CollectUnreachableTypes(reachableTypes, out unreachableEntityTypes, out unreachableIsTypeOfs);
        }
 
        private DomainVariable<string, ValueCondition>[] ConstructDomainVariables()
        {
            // Determine domain for each discriminator column, including "other" and "null" placeholders.
            var discriminatorDomains = new Set<ValueCondition>[this.DiscriminatorColumns.Count];
            for (int i = 0; i < discriminatorDomains.Length; i++)
            {
                discriminatorDomains[i] = new Set<ValueCondition>();
                discriminatorDomains[i].Add(ValueCondition.IsOther);
                discriminatorDomains[i].Add(ValueCondition.IsNull);
            }
 
            // Collect all domain values.
            foreach (var typeMapping in this.NormalizedEntityTypeMappings)
            {
                for (int i = 0; i < this.DiscriminatorColumns.Count; i++)
                {
                    var discriminatorValue = typeMapping.ColumnConditions[i];
                    if (null != discriminatorValue &&
                        !discriminatorValue.ConditionValue.IsNotNullCondition) // NotNull is a special range (everything but IsNull)
                    {
                        discriminatorDomains[i].Add(discriminatorValue.ConditionValue);
                    }
                }
            }
 
            var discriminatorVariables = new DomainVariable<string, ValueCondition>[discriminatorDomains.Length];
            for (int i = 0; i < discriminatorVariables.Length; i++)
            {
                // domain variable is identified by the column name and takes all collected domain values
                discriminatorVariables[i] = new DomainVariable<string, ValueCondition>(
                    this.DiscriminatorColumns[i], discriminatorDomains[i].MakeReadOnly());
            }
 
            return discriminatorVariables;
        }
 
        private Vertex[] ConvertMappingConditionsToVertices(
            ConversionContext<DomainConstraint<string, ValueCondition>> converter,
            DomainVariable<string, ValueCondition>[] variables)
        {
            Vertex[] conditions = new Vertex[this.NormalizedEntityTypeMappings.Count];
            for (int i = 0; i < conditions.Length; i++)
            {
                var typeMapping = this.NormalizedEntityTypeMappings[i];
 
                // create conjunction representing the condition
                Vertex condition = Vertex.One;
                for (int j = 0; j < this.DiscriminatorColumns.Count; j++)
                {
                    var columnCondition = typeMapping.ColumnConditions[j];
                    if (null != columnCondition)
                    {
                        var conditionValue = columnCondition.ConditionValue;
                        if (conditionValue.IsNotNullCondition)
                        {
                            // the 'not null' condition is not actually part of the domain (since it
                            // covers other elements), so create a Not(value in {null}) condition
                            var isNull = new TermExpr<DomainConstraint<string, ValueCondition>>(
                                new DomainConstraint<string, ValueCondition>(variables[j], ValueCondition.IsNull));
                            Vertex isNullVertex = converter.TranslateTermToVertex(isNull);
                            condition = converter.Solver.And(condition, converter.Solver.Not(isNullVertex));
                        }
                        else
                        {
                            var hasValue = new TermExpr<DomainConstraint<string, ValueCondition>>(
                                new DomainConstraint<string, ValueCondition>(variables[j], conditionValue));
                            condition = converter.Solver.And(condition, converter.TranslateTermToVertex(hasValue));
                        }
                    }
                }
                conditions[i] = condition;
            }
            return conditions;
        }
 
        /// <summary>
        /// Determines which types are produced by this mapping.
        /// </summary>
        private Set<EntityType> FindReachableTypes(DomainConstraintConversionContext<string, ValueCondition> converter, Vertex[] mappingConditions)
        {
            // For each entity type, create a candidate function that evaluates to true given
            // discriminator assignments iff. all of that type's conditions evaluate to true
            // and its negative conditions evaluate to false.
            Vertex[] candidateFunctions = new Vertex[this.MappedEntityTypes.Count];
            for (int i = 0; i < candidateFunctions.Length; i++)
            {
                // Seed the candidate function conjunction with 'true'.
                Vertex candidateFunction = Vertex.One;
                for (int j = 0; j < this.NormalizedEntityTypeMappings.Count; j++)
                {
                    var entityTypeMapping = this.NormalizedEntityTypeMappings[j];
 
                    // Determine if this mapping is a positive or negative case for the current type.
                    if (entityTypeMapping.ImpliedEntityTypes[i])
                    {
                        candidateFunction = converter.Solver.And(candidateFunction, mappingConditions[j]);
                    }
                    else
                    {
                        candidateFunction = converter.Solver.And(candidateFunction, converter.Solver.Not(mappingConditions[j]));
                    }
                }
                candidateFunctions[i] = candidateFunction;
            }
 
            // Make sure that for each type there is an assignment that resolves to only that type.
            var reachableTypes = new Set<EntityType>();
            for (int i = 0; i < candidateFunctions.Length; i++)
            {
                // Create a function that evaluates to true iff. the current candidate function is true
                // and every other candidate function is false.
                Vertex isExactlyThisTypeCondition = converter.Solver.And(
                    candidateFunctions.Select((typeCondition, ordinal) => ordinal == i ?
                        typeCondition :
                        converter.Solver.Not(typeCondition)));
 
                // If the above conjunction is satisfiable, it means some row configuration exists producing the type.
                if (!isExactlyThisTypeCondition.IsZero())
                {
                    reachableTypes.Add(this.MappedEntityTypes[i]);
                }
            }
 
            return reachableTypes;
        }
 
        /// <summary>
        /// Determines which types are produced by this mapping.
        /// </summary>
        private Set<EntityType> FindUnambiguouslyReachableTypes(DomainConstraintConversionContext<string, ValueCondition> converter, Vertex[] mappingConditions)
        {
            // For each entity type, create a candidate function that evaluates to true given
            // discriminator assignments iff. all of that type's conditions evaluate to true.
            Vertex[] candidateFunctions = new Vertex[this.MappedEntityTypes.Count];
            for (int i = 0; i < candidateFunctions.Length; i++)
            {
                // Seed the candidate function conjunction with 'true'.
                Vertex candidateFunction = Vertex.One;
                for (int j = 0; j < this.NormalizedEntityTypeMappings.Count; j++)
                {
                    var entityTypeMapping = this.NormalizedEntityTypeMappings[j];
 
                    // Determine if this mapping is a positive or negative case for the current type.
                    if (entityTypeMapping.ImpliedEntityTypes[i])
                    {
                        candidateFunction = converter.Solver.And(candidateFunction, mappingConditions[j]);
                    }
                }
                candidateFunctions[i] = candidateFunction;
            }
 
            // Make sure that for each type with satisfiable candidateFunction all assignments for the type resolve to only that type.
            var unambigouslyReachableMap = new BitArray(candidateFunctions.Length, true);
            for (int i = 0; i < candidateFunctions.Length; ++i)
            {
                if (candidateFunctions[i].IsZero())
                {
                    // The i-th type is unreachable regardless of other types.
                    unambigouslyReachableMap[i] = false;
                }
                else
                {
                    for (int j = i + 1; j < candidateFunctions.Length; ++j)
                    {
                        if (!converter.Solver.And(candidateFunctions[i], candidateFunctions[j]).IsZero())
                        {
                            // The i-th and j-th types have common assignments, hence they aren't unambiguously reachable.
                            unambigouslyReachableMap[i] = false;
                            unambigouslyReachableMap[j] = false;
                        }
                    }
                }
            }
            var reachableTypes = new Set<EntityType>();
            for (int i = 0; i < candidateFunctions.Length; ++i)
            {
                if (unambigouslyReachableMap[i])
                {
                    reachableTypes.Add(this.MappedEntityTypes[i]);
                }
            }
 
            return reachableTypes;
        }
 
        private void CollectUnreachableTypes(Set<EntityType> reachableTypes, out KeyToListMap<EntityType, LineInfo> entityTypes, out KeyToListMap<EntityType, LineInfo> isTypeOfEntityTypes)
        {
            // Collect line infos for types in violation
            entityTypes = new KeyToListMap<EntityType, LineInfo>(EqualityComparer<EntityType>.Default);
            isTypeOfEntityTypes = new KeyToListMap<EntityType, LineInfo>(EqualityComparer<EntityType>.Default);
 
            if (reachableTypes.Count == this.MappedEntityTypes.Count)
            {
                // All types are reachable; nothing to check
                return;
            }
 
            // Find IsTypeOf mappings where no type in hierarchy can generate a row
            foreach (var isTypeOf in m_isTypeOfLineInfos.Keys)
            {
                if (!MetadataHelper.GetTypeAndSubtypesOf(isTypeOf, m_itemCollection, false)
                    .Cast<EntityType>()
                    .Intersect(reachableTypes)
                    .Any())
                {
                    // no type in the hierarchy is reachable...
                    isTypeOfEntityTypes.AddRange(isTypeOf, m_isTypeOfLineInfos.EnumerateValues(isTypeOf));
                }
            }
 
            // Find explicit types not generating a value
            foreach (var entityType in m_entityTypeLineInfos.Keys)
            {
                if (!reachableTypes.Contains(entityType))
                {
                    entityTypes.AddRange(entityType, m_entityTypeLineInfos.EnumerateValues(entityType));
                }
            }
        }
    }
 
    internal sealed class FunctionImportNormalizedEntityTypeMapping
    {
        internal FunctionImportNormalizedEntityTypeMapping(FunctionImportStructuralTypeMappingKB parent, 
            List<FunctionImportEntityTypeMappingCondition> columnConditions, BitArray impliedEntityTypes)
        {
            // validate arguments
            EntityUtil.CheckArgumentNull(parent, "parent");
            EntityUtil.CheckArgumentNull(columnConditions, "discriminatorValues");
            EntityUtil.CheckArgumentNull(impliedEntityTypes, "impliedEntityTypes");
 
            Debug.Assert(columnConditions.Count == parent.DiscriminatorColumns.Count,
                "discriminator values must be ordinally aligned with discriminator columns");
            Debug.Assert(impliedEntityTypes.Count == parent.MappedEntityTypes.Count,
                "implied entity types must be ordinally aligned with mapped entity types");
 
            this.ColumnConditions = new OM.ReadOnlyCollection<FunctionImportEntityTypeMappingCondition>(columnConditions.ToList());
            this.ImpliedEntityTypes = impliedEntityTypes;
            this.ComplementImpliedEntityTypes = (new BitArray(this.ImpliedEntityTypes)).Not();
        }
 
        /// <summary>
        /// Gets discriminator values aligned with DiscriminatorColumns of the parent FunctionImportMapping.
        /// A null ValueCondition indicates 'anything goes'.
        /// </summary>
        internal readonly OM.ReadOnlyCollection<FunctionImportEntityTypeMappingCondition> ColumnConditions;
 
        /// <summary>
        /// Gets bit array with 'true' indicating the corresponding MappedEntityType of the parent
        /// FunctionImportMapping is implied by this fragment.
        /// </summary>
        internal readonly BitArray ImpliedEntityTypes;
 
        /// <summary>
        /// Gets the complement of the ImpliedEntityTypes BitArray.
        /// </summary>
        internal readonly BitArray ComplementImpliedEntityTypes;
 
        public override string ToString()
        {
            return String.Format(CultureInfo.InvariantCulture, "Values={0}, Types={1}",
                StringUtil.ToCommaSeparatedString(this.ColumnConditions), StringUtil.ToCommaSeparatedString(this.ImpliedEntityTypes));
        }
    }
 
    internal abstract class FunctionImportEntityTypeMappingCondition
    {
        protected FunctionImportEntityTypeMappingCondition(string columnName, LineInfo lineInfo)
        {
            this.ColumnName = EntityUtil.CheckArgumentNull(columnName, "columnName");
            this.LineInfo = lineInfo;
        }
 
        internal readonly string ColumnName;
        internal readonly LineInfo LineInfo;
 
        internal abstract ValueCondition ConditionValue { get; }
 
        internal abstract bool ColumnValueMatchesCondition(object columnValue);
 
        public override string ToString()
        {
            return this.ConditionValue.ToString();
        }
    }
 
    internal sealed class FunctionImportEntityTypeMappingConditionValue : FunctionImportEntityTypeMappingCondition
    {
        internal FunctionImportEntityTypeMappingConditionValue(string columnName, XPathNavigator columnValue, LineInfo lineInfo)
            : base(columnName, lineInfo)
        {
            this._xPathValue = EntityUtil.CheckArgumentNull(columnValue, "columnValue");
            this._convertedValues = new Memoizer<Type, object>(this.GetConditionValue, null);
        }
 
        private readonly XPathNavigator _xPathValue;
        private readonly Memoizer<Type, object> _convertedValues;
 
        internal override ValueCondition ConditionValue
        {
            get { return new ValueCondition(_xPathValue.Value); }
        }
 
        internal override bool ColumnValueMatchesCondition(object columnValue)
        {
            if (null == columnValue || Convert.IsDBNull(columnValue))
            {
                // only FunctionImportEntityTypeMappingConditionIsNull can match a null
                // column value
                return false;
            }
 
            Type columnValueType = columnValue.GetType();
 
            // check if we've interpreted this column type yet
            object conditionValue = _convertedValues.Evaluate(columnValueType);
            return ByValueEqualityComparer.Default.Equals(columnValue, conditionValue);
        }
 
        private object GetConditionValue(Type columnValueType)
        {
            return GetConditionValue(
                columnValueType,
                handleTypeNotComparable: () =>
                {
                    throw EntityUtil.CommandExecution(Strings.Mapping_FunctionImport_UnsupportedType(this.ColumnName, columnValueType.FullName));
                },
                handleInvalidConditionValue: () =>
                {
                    throw EntityUtil.CommandExecution(Strings.Mapping_FunctionImport_ConditionValueTypeMismatch(StorageMslConstructs.FunctionImportMappingElement, this.ColumnName, columnValueType.FullName));
                });
        }
 
        internal object GetConditionValue(Type columnValueType, Action handleTypeNotComparable, Action handleInvalidConditionValue)
        {
            // Check that the type is supported and comparable.
            PrimitiveType primitiveType;
            if (!ClrProviderManifest.Instance.TryGetPrimitiveType(columnValueType, out primitiveType) ||
                !StorageMappingItemLoader.IsTypeSupportedForCondition(primitiveType.PrimitiveTypeKind))
            {
                handleTypeNotComparable();
                return null;
            }
 
            try
            {
                return _xPathValue.ValueAs(columnValueType);
            }
            catch (FormatException)
            {
                handleInvalidConditionValue();
                return null;
            }
        }
    }
 
    internal sealed class FunctionImportEntityTypeMappingConditionIsNull : FunctionImportEntityTypeMappingCondition
    {
        internal FunctionImportEntityTypeMappingConditionIsNull(string columnName, bool isNull, LineInfo lineInfo)
            : base(columnName, lineInfo)
        {
            this.IsNull = isNull;
        }
 
        internal readonly bool IsNull;
 
        internal override ValueCondition ConditionValue
        {
            get { return IsNull ? ValueCondition.IsNull : ValueCondition.IsNotNull; }
        }
 
        internal override bool ColumnValueMatchesCondition(object columnValue)
        {
            bool valueIsNull = null == columnValue || Convert.IsDBNull(columnValue);
            return valueIsNull == this.IsNull;
        }
    }
 
    /// <summary>
    /// Represents a simple value condition of the form (value IS NULL), (value IS NOT NULL)
    /// or (value EQ X). Supports IEquatable(Of ValueCondition) so that equivalent conditions
    /// can be identified.
    /// </summary>
    internal class ValueCondition : IEquatable<ValueCondition>
    {
        internal readonly string Description;
        internal readonly bool IsSentinel;
 
        internal const string IsNullDescription = "NULL";
        internal const string IsNotNullDescription = "NOT NULL";
        internal const string IsOtherDescription = "OTHER";
 
        internal readonly static ValueCondition IsNull = new ValueCondition(IsNullDescription, true);
        internal readonly static ValueCondition IsNotNull = new ValueCondition(IsNotNullDescription, true);
        internal readonly static ValueCondition IsOther = new ValueCondition(IsOtherDescription, true);
 
        private ValueCondition(string description, bool isSentinel)
        {
            Description = description;
            IsSentinel = isSentinel;
        }
 
        internal ValueCondition(string description)
            : this(description, false)
        {
        }
 
        internal bool IsNotNullCondition { get { return object.ReferenceEquals(this, IsNotNull); } }
 
        public bool Equals(ValueCondition other)
        {
            return other.IsSentinel == this.IsSentinel &&
                other.Description == this.Description;
        }
 
        public override int GetHashCode()
        {
            return Description.GetHashCode();
        }
 
        public override string ToString()
        {
            return this.Description;
        }
    }
 
    internal sealed class LineInfo: IXmlLineInfo
    {
        private readonly bool m_hasLineInfo;
        private readonly int m_lineNumber;
        private readonly int m_linePosition;
 
        internal LineInfo(XPathNavigator nav)
            : this((IXmlLineInfo)nav)
        { }
 
        internal LineInfo(IXmlLineInfo lineInfo)
        {
            m_hasLineInfo = lineInfo.HasLineInfo();
            m_lineNumber = lineInfo.LineNumber;
            m_linePosition = lineInfo.LinePosition;
        }
 
        internal static readonly LineInfo Empty = new LineInfo();
        private LineInfo()
        {
            m_hasLineInfo = false;
            m_lineNumber = default(int);
            m_linePosition = default(int);
        }
 
        public int LineNumber
        { get { return m_lineNumber; } }
 
        public int LinePosition
        { get { return m_linePosition; } }
 
        public bool HasLineInfo()
        {
            return m_hasLineInfo;
        }
    }
}