Xamarin Public Jenkins (auto-signing) 536cd135cc Imported Upstream version 5.4.0.167
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
2017-08-21 15:34:15 +00:00

655 lines
27 KiB
C#

//---------------------------------------------------------------------
// <copyright file="ViewValidator.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @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;
/// <summary>
/// Verifies that only legal expressions exist in a user-defined query mapping view.
/// </summary>
internal static class ViewValidator
{
/// <summary>
/// Determines whether the given view is valid.
/// </summary>
/// <param name="view">Query view to validate.</param>
/// <param name="storeItemCollection">Store item collection.</param>
/// <param name="setMapping">Mapping in which view is declared.</param>
/// <returns>Errors in view definition.</returns>
internal static IEnumerable<EdmSchemaError> 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<EdmSchemaError> _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<EdmSchemaError>();
}
internal IEnumerable<EdmSchemaError> 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));
}
}
}
/// <summary>
/// Retrieves all complex types that can be constructed as part of the view.
/// </summary>
private IEnumerable<ComplexType> GetComplexTypes()
{
// Retrieve all top-level properties of entity types constructed in the view.
IEnumerable<EdmProperty> properties = GetEntityTypes().SelectMany(entityType => entityType.Properties).Distinct();
return GetComplexTypes(properties);
}
/// <summary>
/// Recursively identify complex types.
/// </summary>
private IEnumerable<ComplexType> GetComplexTypes(IEnumerable<EdmProperty> properties)
{
//
foreach (ComplexType complexType in properties.Select(p => p.TypeUsage.EdmType).OfType<ComplexType>())
{
yield return complexType;
foreach (ComplexType nestedComplexType in GetComplexTypes(complexType.Properties))
{
yield return nestedComplexType;
}
}
}
/// <summary>
/// Gets all entity types in scope for this view.
/// </summary>
private IEnumerable<EntityType> GetEntityTypes()
{
if (_includeSubtypes)
{
// Return all entity types in the hierarchy for OfType or 'complete' views.
return MetadataHelper.GetTypeAndSubtypesOf(_elementType, this.EdmItemCollection, true).OfType<EntityType>();
}
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<EntityType>();
}
}
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));
}
}
}
/// <summary>
/// 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.
/// </summary>
private class AssociationSetViewValidator : DbExpressionVisitor<DbExpressionEntitySetInfo>
{
private readonly Stack<KeyValuePair<string, DbExpressionEntitySetInfo>> variableScopes = new Stack<KeyValuePair<string, DbExpressionEntitySetInfo>>();
private StorageSetMapping _setMapping;
private List<EdmSchemaError> _errors = new List<EdmSchemaError>();
internal AssociationSetViewValidator(StorageSetMapping setMapping)
: base()
{
Debug.Assert(setMapping != null);
_setMapping = setMapping;
}
internal List<EdmSchemaError> 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<string, DbExpressionEntitySetInfo>(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<DbExpression> 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<DbExpression> 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<String, DbExpressionEntitySetInfo> m_entitySetInfos;
internal DbExpressionStructuralTypeEntitySetInfo()
{
m_entitySetInfos = new Dictionary<string, DbExpressionEntitySetInfo>();
}
internal void Add(string key, DbExpressionEntitySetInfo value)
{
m_entitySetInfos.Add(key, value);
}
internal IEnumerable<KeyValuePair<string, DbExpressionEntitySetInfo>> SetInfos
{
get
{
return m_entitySetInfos;
}
}
internal DbExpressionEntitySetInfo GetEntitySetInfoForMember(string memberName)
{
return m_entitySetInfos[memberName];
}
}
private class DbExpressionMemberCollectionEntitySetInfo : DbExpressionEntitySetInfo
{
private IEnumerable<DbExpressionEntitySetInfo> m_entitySets;
internal DbExpressionMemberCollectionEntitySetInfo(IEnumerable<DbExpressionEntitySetInfo> entitySetInfos)
{
m_entitySets = entitySetInfos;
}
internal IEnumerable<DbExpressionEntitySetInfo> entitySetInfos
{
get
{
return m_entitySets;
}
}
}
#endregion
}
}