1 //---------------------------------------------------------------------
2 // <copyright file="Validator.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
10 namespace System.Data.Common.CommandTrees.Internal
13 using System.Collections.Generic;
14 using System.Data.Entity;
15 using System.Data.Metadata.Edm;
16 using System.Diagnostics;
19 internal sealed class DbExpressionValidator : DbExpressionRebinder
21 private readonly DataSpace requiredSpace;
22 private readonly DataSpace[] allowedMetadataSpaces;
23 private readonly DataSpace[] allowedFunctionSpaces;
24 private readonly Dictionary<string, DbParameterReferenceExpression> paramMappings = new Dictionary<string, DbParameterReferenceExpression>();
25 private readonly Stack<Dictionary<string, TypeUsage>> variableScopes = new Stack<Dictionary<string, TypeUsage>>();
27 private string expressionArgumentName;
29 internal DbExpressionValidator(MetadataWorkspace metadata, DataSpace expectedDataSpace)
32 this.requiredSpace = expectedDataSpace;
33 this.allowedFunctionSpaces = new[] { DataSpace.CSpace, DataSpace.SSpace };
34 if (expectedDataSpace == DataSpace.SSpace)
36 this.allowedMetadataSpaces = new[] { DataSpace.SSpace, DataSpace.CSpace };
40 this.allowedMetadataSpaces = new[] { DataSpace.CSpace };
44 internal Dictionary<string, DbParameterReferenceExpression> Parameters { get { return this.paramMappings; } }
46 internal void ValidateExpression(DbExpression expression, string argumentName)
48 Debug.Assert(expression != null, "Ensure expression is non-null before calling ValidateExpression");
49 this.expressionArgumentName = argumentName;
50 this.VisitExpression(expression);
51 this.expressionArgumentName = null;
52 Debug.Assert(this.variableScopes.Count == 0, "Variable scope stack left in inconsistent state");
55 protected override EntitySetBase VisitEntitySet(EntitySetBase entitySet)
57 return ValidateMetadata(entitySet, base.VisitEntitySet, es => es.EntityContainer.DataSpace, this.allowedMetadataSpaces);
60 protected override EdmFunction VisitFunction(EdmFunction function)
62 // Functions from the current space and S-Space are allowed
63 return ValidateMetadata(function, base.VisitFunction, func => func.DataSpace, this.allowedFunctionSpaces);
66 protected override EdmType VisitType(EdmType type)
68 return ValidateMetadata(type, base.VisitType, et => et.DataSpace, this.allowedMetadataSpaces);
71 protected override TypeUsage VisitTypeUsage(TypeUsage type)
73 return ValidateMetadata(type, base.VisitTypeUsage, tu => tu.EdmType.DataSpace, this.allowedMetadataSpaces);
76 protected override void OnEnterScope(IEnumerable<DbVariableReferenceExpression> scopeVariables)
78 var newScope = scopeVariables.ToDictionary(var => var.VariableName, var => var.ResultType, StringComparer.Ordinal);
79 this.variableScopes.Push(newScope);
82 protected override void OnExitScope()
84 this.variableScopes.Pop();
87 public override DbExpression Visit(DbVariableReferenceExpression expression)
89 DbExpression result = base.Visit(expression);
90 if(result.ExpressionKind == DbExpressionKind.VariableReference)
92 DbVariableReferenceExpression varRef = (DbVariableReferenceExpression)result;
93 TypeUsage foundType = null;
94 foreach(Dictionary<string, TypeUsage> scope in this.variableScopes)
96 if(scope.TryGetValue(varRef.VariableName, out foundType))
102 if(foundType == null)
104 ThrowInvalid(System.Data.Entity.Strings.Cqt_Validator_VarRefInvalid(varRef.VariableName));
107 // SQLBUDT#545720: Equivalence is not a sufficient check (consider row types) - equality is required.
108 if (!TypeSemantics.IsEqual(varRef.ResultType, foundType))
110 ThrowInvalid(System.Data.Entity.Strings.Cqt_Validator_VarRefTypeMismatch(varRef.VariableName));
117 public override DbExpression Visit(DbParameterReferenceExpression expression)
119 DbExpression result = base.Visit(expression);
120 if (result.ExpressionKind == DbExpressionKind.ParameterReference)
122 DbParameterReferenceExpression paramRef = result as DbParameterReferenceExpression;
124 DbParameterReferenceExpression foundParam;
125 if (this.paramMappings.TryGetValue(paramRef.ParameterName, out foundParam))
127 // SQLBUDT#545720: Equivalence is not a sufficient check (consider row types for TVPs) - equality is required.
128 if (!TypeSemantics.IsEqual(paramRef.ResultType, foundParam.ResultType))
130 ThrowInvalid(Strings.Cqt_Validator_InvalidIncompatibleParameterReferences(paramRef.ParameterName));
135 this.paramMappings.Add(paramRef.ParameterName, paramRef);
141 private TMetadata ValidateMetadata<TMetadata>(TMetadata metadata, Func<TMetadata, TMetadata> map, Func<TMetadata, DataSpace> getDataSpace, DataSpace[] allowedSpaces)
143 TMetadata result = map(metadata);
144 if (!object.ReferenceEquals(metadata, result))
146 ThrowInvalidMetadata(metadata);
149 DataSpace resultSpace = getDataSpace(result);
150 if (!allowedSpaces.Any(ds => ds == resultSpace))
152 ThrowInvalidSpace(metadata);
157 private void ThrowInvalidMetadata<TMetadata>(TMetadata invalid)
159 ThrowInvalid(Strings.Cqt_Validator_InvalidOtherWorkspaceMetadata(typeof(TMetadata).Name));
162 private void ThrowInvalidSpace<TMetadata>(TMetadata invalid)
164 ThrowInvalid(Strings.Cqt_Validator_InvalidIncorrectDataSpaceMetadata(typeof(TMetadata).Name, Enum.GetName(typeof(DataSpace), this.requiredSpace)));
167 private void ThrowInvalid(string message)
169 throw EntityUtil.Argument(message, this.expressionArgumentName);