//--------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner Microsoft // @backupOwner Microsoft //--------------------------------------------------------------------- namespace System.Data.Mapping { using System.Collections.Generic; using System.Data.Common; using System.Data.Common.CommandTrees; using System.Data.Common.Utils; using System.Data.Metadata.Edm; using System.Diagnostics; using System.Linq; /// /// Verifies that only legal expressions exist in a user-defined query mapping view. /// internal static class ViewValidator { /// /// Determines whether the given view is valid. /// /// Query view to validate. /// Store item collection. /// Mapping in which view is declared. /// Errors in view definition. internal static IEnumerable ValidateQueryView(DbQueryCommandTree view, StorageSetMapping setMapping, EntityTypeBase elementType, bool includeSubtypes) { ViewExpressionValidator validator = new ViewExpressionValidator(setMapping, elementType, includeSubtypes); validator.VisitExpression(view.Query); if (validator.Errors.Count() == 0) { //For AssociationSet views, we have to check for a specific pattern of errors where //the Ref expression passed into the construcor might use an EntitySet that is different from //the EntitySet defined in the CSDL. if (setMapping.Set.BuiltInTypeKind == BuiltInTypeKind.AssociationSet) { AssociationSetViewValidator refValidator = new AssociationSetViewValidator(setMapping); refValidator.VisitExpression(view.Query); return refValidator.Errors; } } return validator.Errors; } private sealed class ViewExpressionValidator : BasicExpressionVisitor { private readonly StorageSetMapping _setMapping; private readonly List _errors; private readonly EntityTypeBase _elementType; private readonly bool _includeSubtypes; private EdmItemCollection EdmItemCollection { get { return _setMapping.EntityContainerMapping.StorageMappingItemCollection.EdmItemCollection; } } private StoreItemCollection StoreItemCollection { get { return _setMapping.EntityContainerMapping.StorageMappingItemCollection.StoreItemCollection; } } internal ViewExpressionValidator(StorageSetMapping setMapping, EntityTypeBase elementType, bool includeSubtypes) { Debug.Assert(null != setMapping); Debug.Assert(null != elementType); _setMapping = setMapping; _elementType = elementType; _includeSubtypes = includeSubtypes; _errors = new List(); } internal IEnumerable Errors { get { return _errors; } } public override void VisitExpression(DbExpression expression) { if (null != expression) { ValidateExpressionKind(expression.ExpressionKind); } base.VisitExpression(expression); } private void ValidateExpressionKind(DbExpressionKind expressionKind) { switch (expressionKind) { // Supported expression kinds case DbExpressionKind.Constant: case DbExpressionKind.Property: case DbExpressionKind.Null: case DbExpressionKind.VariableReference: case DbExpressionKind.Cast: case DbExpressionKind.Case: case DbExpressionKind.Not: case DbExpressionKind.Or: case DbExpressionKind.And: case DbExpressionKind.IsNull: case DbExpressionKind.Equals: case DbExpressionKind.NotEquals: case DbExpressionKind.LessThan: case DbExpressionKind.LessThanOrEquals: case DbExpressionKind.GreaterThan: case DbExpressionKind.GreaterThanOrEquals: case DbExpressionKind.Project: case DbExpressionKind.NewInstance: case DbExpressionKind.Filter: case DbExpressionKind.Ref: case DbExpressionKind.UnionAll: case DbExpressionKind.Scan: case DbExpressionKind.FullOuterJoin: case DbExpressionKind.LeftOuterJoin: case DbExpressionKind.InnerJoin: case DbExpressionKind.EntityRef: case DbExpressionKind.Function: break; default: string elementString = (_includeSubtypes) ? "IsTypeOf(" + _elementType.ToString() + ")" : _elementType.ToString(); _errors.Add(new EdmSchemaError(System.Data.Entity.Strings.Mapping_UnsupportedExpressionKind_QueryView( _setMapping.Set.Name, elementString, expressionKind), (int)StorageMappingErrorCode.MappingUnsupportedExpressionKindQueryView, EdmSchemaErrorSeverity.Error, _setMapping.EntityContainerMapping.SourceLocation, _setMapping.StartLineNumber, _setMapping.StartLinePosition)); break; } } public override void Visit(DbPropertyExpression expression) { base.Visit(expression); if (expression.Property.BuiltInTypeKind != BuiltInTypeKind.EdmProperty) { _errors.Add(new EdmSchemaError(System.Data.Entity.Strings.Mapping_UnsupportedPropertyKind_QueryView( _setMapping.Set.Name, expression.Property.Name, expression.Property.BuiltInTypeKind), (int)StorageMappingErrorCode.MappingUnsupportedPropertyKindQueryView, EdmSchemaErrorSeverity.Error, _setMapping.EntityContainerMapping.SourceLocation, _setMapping.StartLineNumber, _setMapping.StartLinePosition)); } } public override void Visit(DbNewInstanceExpression expression) { base.Visit(expression); EdmType type = expression.ResultType.EdmType; if (type.BuiltInTypeKind != BuiltInTypeKind.RowType) { // restrict initialization of non-row types to the target of the view or complex types // in the target if (!(type == _elementType || (_includeSubtypes && _elementType.IsAssignableFrom(type))) && !(type.BuiltInTypeKind == BuiltInTypeKind.ComplexType && GetComplexTypes().Contains((ComplexType)type))) { _errors.Add(new EdmSchemaError(System.Data.Entity.Strings.Mapping_UnsupportedInitialization_QueryView( _setMapping.Set.Name, type.FullName), (int)StorageMappingErrorCode.MappingUnsupportedInitializationQueryView, EdmSchemaErrorSeverity.Error, _setMapping.EntityContainerMapping.SourceLocation, _setMapping.StartLineNumber, _setMapping.StartLinePosition)); } } } /// /// Retrieves all complex types that can be constructed as part of the view. /// private IEnumerable GetComplexTypes() { // Retrieve all top-level properties of entity types constructed in the view. IEnumerable properties = GetEntityTypes().SelectMany(entityType => entityType.Properties).Distinct(); return GetComplexTypes(properties); } /// /// Recursively identify complex types. /// private IEnumerable GetComplexTypes(IEnumerable properties) { // foreach (ComplexType complexType in properties.Select(p => p.TypeUsage.EdmType).OfType()) { yield return complexType; foreach (ComplexType nestedComplexType in GetComplexTypes(complexType.Properties)) { yield return nestedComplexType; } } } /// /// Gets all entity types in scope for this view. /// private IEnumerable GetEntityTypes() { if (_includeSubtypes) { // Return all entity types in the hierarchy for OfType or 'complete' views. return MetadataHelper.GetTypeAndSubtypesOf(_elementType, this.EdmItemCollection, true).OfType(); } else if (_elementType.BuiltInTypeKind == BuiltInTypeKind.EntityType) { // Yield single entity type for OfType(only ) views. return Enumerable.Repeat((EntityType)_elementType, 1); } else { // For association set views, there are no entity types involved. return Enumerable.Empty(); } } public override void Visit(DbFunctionExpression expression) { base.Visit(expression); // Verify function is defined in S-space or it is a built-in canonical function. if (!IsStoreSpaceOrCanonicalFunction(this.StoreItemCollection, expression.Function)) { _errors.Add(new EdmSchemaError(System.Data.Entity.Strings.Mapping_UnsupportedFunctionCall_QueryView( _setMapping.Set.Name, expression.Function.Identity), (int)StorageMappingErrorCode.UnsupportedFunctionCallInQueryView, EdmSchemaErrorSeverity.Error, _setMapping.EntityContainerMapping.SourceLocation, _setMapping.StartLineNumber, _setMapping.StartLinePosition)); } } internal static bool IsStoreSpaceOrCanonicalFunction(StoreItemCollection sSpace, EdmFunction function) { if (TypeHelpers.IsCanonicalFunction(function)) { return true; } else { // Even if function is declared in s-space, view expression will contain the version of the function // in c-space terms, thus checking function.DataSpace will always give c-space. // In order to determine if the function originates in s-space we need to get check if it belongs // to the list of c-space conversions. var cTypeFunctions = sSpace.GetCTypeFunctions(function.FullName, false); return cTypeFunctions.Contains(function); } } public override void Visit(DbScanExpression expression) { base.Visit(expression); Debug.Assert(null != expression.Target); // Verify scan target is in S-space. EntitySetBase target = expression.Target; EntityContainer targetContainer = target.EntityContainer; Debug.Assert(null != target.EntityContainer); if ((targetContainer.DataSpace != DataSpace.SSpace)) { _errors.Add(new EdmSchemaError(System.Data.Entity.Strings.Mapping_UnsupportedScanTarget_QueryView( _setMapping.Set.Name, target.Name), (int)StorageMappingErrorCode.MappingUnsupportedScanTargetQueryView, EdmSchemaErrorSeverity.Error, _setMapping.EntityContainerMapping.SourceLocation, _setMapping.StartLineNumber, _setMapping.StartLinePosition)); } } } /// /// The visitor validates that the QueryView for an AssociationSet uses the same EntitySets when /// creating the ends that were used in CSDL. Since the Query View is already validated, we can expect to /// see only a very restricted set of expressions in the tree. /// private class AssociationSetViewValidator : DbExpressionVisitor { private readonly Stack> variableScopes = new Stack>(); private StorageSetMapping _setMapping; private List _errors = new List(); internal AssociationSetViewValidator(StorageSetMapping setMapping) : base() { Debug.Assert(setMapping != null); _setMapping = setMapping; } internal List Errors { get { return _errors; } } internal DbExpressionEntitySetInfo VisitExpression(DbExpression expression) { return expression.Accept(this); } private DbExpressionEntitySetInfo VisitExpressionBinding(DbExpressionBinding binding) { DbExpressionBinding result = binding; if (binding != null) { return this.VisitExpression(binding.Expression); } return null; } private void VisitExpressionBindingEnterScope(DbExpressionBinding binding) { DbExpressionEntitySetInfo info = this.VisitExpressionBinding(binding); this.variableScopes.Push(new KeyValuePair(binding.VariableName, info)); } private void VisitExpressionBindingExitScope() { this.variableScopes.Pop(); } //Verifies that the Sets we got from visiting the tree( under AssociationType constructor) match the ones //defined in CSDL private void ValidateEntitySetsMappedForAssociationSetMapping(DbExpressionStructuralTypeEntitySetInfo setInfos) { AssociationSet associationSet = _setMapping.Set as AssociationSet; int i = 0; //While we should be able to find the EntitySets in all cases, since this is a user specified //query view, it is better to be defensive since we might have missed some path up the tree //while computing the sets if (setInfos.SetInfos.All(it => ((it.Value != null) && (it.Value is DbExpressionSimpleTypeEntitySetInfo))) && setInfos.SetInfos.Count() == 2) { foreach (DbExpressionSimpleTypeEntitySetInfo setInfo in setInfos.SetInfos.Select(it => it.Value)) { AssociationSetEnd setEnd = associationSet.AssociationSetEnds[i]; EntitySet declaredSet = setEnd.EntitySet; if (!declaredSet.Equals(setInfo.EntitySet)) { _errors.Add(new EdmSchemaError(System.Data.Entity.Strings.Mapping_EntitySetMismatchOnAssociationSetEnd_QueryView( setInfo.EntitySet.Name, declaredSet.Name, setEnd.Name, _setMapping.Set.Name), (int)StorageMappingErrorCode.MappingUnsupportedInitializationQueryView, EdmSchemaErrorSeverity.Error, _setMapping.EntityContainerMapping.SourceLocation, _setMapping.StartLineNumber, _setMapping.StartLinePosition)); } i++; } } } #region DbExpressionVisitor Members public override DbExpressionEntitySetInfo Visit(DbExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbVariableReferenceExpression expression) { return this.variableScopes.Where(it => (it.Key == expression.VariableName)).Select(it => it.Value).FirstOrDefault(); } public override DbExpressionEntitySetInfo Visit(DbPropertyExpression expression) { DbExpressionStructuralTypeEntitySetInfo setInfos = VisitExpression(expression.Instance) as DbExpressionStructuralTypeEntitySetInfo; if (setInfos != null) { return setInfos.GetEntitySetInfoForMember(expression.Property.Name); } return null; } public override DbExpressionEntitySetInfo Visit(DbProjectExpression expression) { this.VisitExpressionBindingEnterScope(expression.Input); DbExpressionEntitySetInfo setInfo = VisitExpression(expression.Projection); this.VisitExpressionBindingExitScope(); return setInfo; } public override DbExpressionEntitySetInfo Visit(DbNewInstanceExpression expression) { DbExpressionMemberCollectionEntitySetInfo argumentSetInfos = VisitExpressionList(expression.Arguments); StructuralType structuralType = (expression.ResultType.EdmType as StructuralType); if (argumentSetInfos != null && structuralType != null) { DbExpressionStructuralTypeEntitySetInfo structuralTypeSetInfos = new DbExpressionStructuralTypeEntitySetInfo(); int i = 0; foreach (DbExpressionEntitySetInfo info in argumentSetInfos.entitySetInfos) { structuralTypeSetInfos.Add(structuralType.Members[i].Name, info); i++; } //Since we already validated the query view, the only association type that //can be constructed is the type for the set we are validating the mapping for. if (expression.ResultType.EdmType.BuiltInTypeKind == BuiltInTypeKind.AssociationType) { ValidateEntitySetsMappedForAssociationSetMapping(structuralTypeSetInfos); } return structuralTypeSetInfos; } return null; } private DbExpressionMemberCollectionEntitySetInfo VisitExpressionList(IList list) { return new DbExpressionMemberCollectionEntitySetInfo(list.Select(it => (VisitExpression(it)))); } public override DbExpressionEntitySetInfo Visit(DbRefExpression expression) { return new DbExpressionSimpleTypeEntitySetInfo(expression.EntitySet); } public override DbExpressionEntitySetInfo Visit(DbComparisonExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbLikeExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbLimitExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbIsNullExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbArithmeticExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbAndExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbOrExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbNotExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbDistinctExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbElementExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbIsEmptyExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbUnionAllExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbIntersectExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbExceptExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbTreatExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbIsOfExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbCastExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbCaseExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbOfTypeExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbRelationshipNavigationExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbDerefExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbRefKeyExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbEntityRefExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbScanExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbFilterExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbConstantExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbNullExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbCrossJoinExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbJoinExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbParameterReferenceExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbFunctionExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbLambdaExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbApplyExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbGroupByExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbSkipExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbSortExpression expression) { return null; } public override DbExpressionEntitySetInfo Visit(DbQuantifierExpression expression) { return null; } #endregion } internal abstract class DbExpressionEntitySetInfo { } #region DbExpressionEntitySetInfo implementations private class DbExpressionSimpleTypeEntitySetInfo : DbExpressionEntitySetInfo { private EntitySet m_entitySet; internal EntitySet EntitySet { get { return m_entitySet; } } internal DbExpressionSimpleTypeEntitySetInfo(EntitySet entitySet) { m_entitySet = entitySet; } } private class DbExpressionStructuralTypeEntitySetInfo : DbExpressionEntitySetInfo { private Dictionary m_entitySetInfos; internal DbExpressionStructuralTypeEntitySetInfo() { m_entitySetInfos = new Dictionary(); } internal void Add(string key, DbExpressionEntitySetInfo value) { m_entitySetInfos.Add(key, value); } internal IEnumerable> SetInfos { get { return m_entitySetInfos; } } internal DbExpressionEntitySetInfo GetEntitySetInfoForMember(string memberName) { return m_entitySetInfos[memberName]; } } private class DbExpressionMemberCollectionEntitySetInfo : DbExpressionEntitySetInfo { private IEnumerable m_entitySets; internal DbExpressionMemberCollectionEntitySetInfo(IEnumerable entitySetInfos) { m_entitySets = entitySetInfos; } internal IEnumerable entitySetInfos { get { return m_entitySets; } } } #endregion } }