|
//---------------------------------------------------------------------
// <copyright file="SemanticResolver.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
namespace System.Data.Common.EntitySql
{
using System;
using System.Collections.Generic;
using System.Data.Common.CommandTrees;
using System.Data.Common.CommandTrees.ExpressionBuilder;
using System.Data.Entity;
using System.Data.Metadata.Edm;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
/// <summary>
/// Represents eSQL expression class.
/// </summary>
internal enum ExpressionResolutionClass
{
/// <summary>
/// A value expression such as a literal, variable or a value-returning expression.
/// </summary>
Value,
/// <summary>
/// An expression returning an entity container.
/// </summary>
EntityContainer,
/// <summary>
/// An expression returning a metadata member such as a type, function group or namespace.
/// </summary>
MetadataMember
}
/// <summary>
/// Abstract class representing the result of an eSQL expression classification.
/// </summary>
internal abstract class ExpressionResolution
{
protected ExpressionResolution(ExpressionResolutionClass @class)
{
ExpressionClass = @class;
}
internal readonly ExpressionResolutionClass ExpressionClass;
internal abstract string ExpressionClassName { get; }
}
/// <summary>
/// Represents an eSQL expression classified as <see cref="ExpressionResolutionClass.Value"/>.
/// </summary>
internal sealed class ValueExpression : ExpressionResolution
{
internal ValueExpression(DbExpression value)
: base(ExpressionResolutionClass.Value)
{
Value = value;
}
internal override string ExpressionClassName { get { return ValueClassName; } }
internal static string ValueClassName { get { return Strings.LocalizedValueExpression; } }
/// <summary>
/// Null if <see cref="ValueExpression"/> represents the untyped null.
/// </summary>
internal readonly DbExpression Value;
}
/// <summary>
/// Represents an eSQL expression classified as <see cref="ExpressionResolutionClass.EntityContainer"/>.
/// </summary>
internal sealed class EntityContainerExpression : ExpressionResolution
{
internal EntityContainerExpression(EntityContainer entityContainer)
: base(ExpressionResolutionClass.EntityContainer)
{
EntityContainer = entityContainer;
}
internal override string ExpressionClassName { get { return EntityContainerClassName; } }
internal static string EntityContainerClassName { get { return Strings.LocalizedEntityContainerExpression; } }
internal readonly EntityContainer EntityContainer;
}
/// <summary>
/// Implements the semantic resolver in the context of a metadata workspace and typespace.
/// </summary>
/// <remarks>not thread safe</remarks>
internal sealed class SemanticResolver
{
#region Fields
private readonly ParserOptions _parserOptions;
private readonly Dictionary<string, DbParameterReferenceExpression> _parameters;
private readonly Dictionary<string, DbVariableReferenceExpression> _variables;
private readonly TypeResolver _typeResolver;
private readonly ScopeManager _scopeManager;
private readonly List<ScopeRegion> _scopeRegions = new List<ScopeRegion>();
private bool _ignoreEntityContainerNameResolution = false;
private GroupAggregateInfo _currentGroupAggregateInfo = null;
private uint _namegenCounter = 0;
#endregion
#region Constructors
/// <summary>
/// Creates new instance of <see cref="SemanticResolver"/>.
/// </summary>
internal static SemanticResolver Create(Perspective perspective,
ParserOptions parserOptions,
IEnumerable<DbParameterReferenceExpression> parameters,
IEnumerable<DbVariableReferenceExpression> variables)
{
EntityUtil.CheckArgumentNull(perspective, "perspective");
EntityUtil.CheckArgumentNull(parserOptions, "parserOptions");
return new SemanticResolver(
parserOptions,
ProcessParameters(parameters, parserOptions),
ProcessVariables(variables, parserOptions),
new TypeResolver(perspective, parserOptions));
}
/// <summary>
/// Creates a copy of <see cref="SemanticResolver"/> with clean scopes and shared inline function definitions inside of the type resolver.
/// </summary>
internal SemanticResolver CloneForInlineFunctionConversion()
{
return new SemanticResolver(
_parserOptions,
_parameters,
_variables,
_typeResolver);
}
private SemanticResolver(ParserOptions parserOptions,
Dictionary<string, DbParameterReferenceExpression> parameters,
Dictionary<string, DbVariableReferenceExpression> variables,
TypeResolver typeResolver)
{
_parserOptions = parserOptions;
_parameters = parameters;
_variables = variables;
_typeResolver = typeResolver;
//
// Creates Scope manager
//
_scopeManager = new ScopeManager(this.NameComparer);
//
// Push a root scope region
//
EnterScopeRegion();
//
// Add command free variables to the root scope
//
foreach (DbVariableReferenceExpression variable in _variables.Values)
{
this.CurrentScope.Add(variable.VariableName, new FreeVariableScopeEntry(variable));
}
}
/// <summary>
/// Validates that the specified parameters have valid, non-duplicated names
/// </summary>
/// <param name="paramDefs">The set of query parameters</param>
/// <returns>A valid dictionary that maps parameter names to <see cref="DbParameterReferenceExpression"/>s using the current NameComparer</returns>
private static Dictionary<string, DbParameterReferenceExpression> ProcessParameters(IEnumerable<DbParameterReferenceExpression> paramDefs, ParserOptions parserOptions)
{
Dictionary<string, DbParameterReferenceExpression> retParams = new Dictionary<string, DbParameterReferenceExpression>(parserOptions.NameComparer);
if (paramDefs != null)
{
foreach (DbParameterReferenceExpression paramDef in paramDefs)
{
if (retParams.ContainsKey(paramDef.ParameterName))
{
throw EntityUtil.EntitySqlError(Strings.MultipleDefinitionsOfParameter(paramDef.ParameterName));
}
Debug.Assert(paramDef.ResultType.IsReadOnly, "paramDef.ResultType.IsReadOnly must be set");
retParams.Add(paramDef.ParameterName, paramDef);
}
}
return retParams;
}
/// <summary>
/// Validates that the specified variables have valid, non-duplicated names
/// </summary>
/// <param name="varDefs">The set of free variables</param>
/// <returns>A valid dictionary that maps variable names to <see cref="DbVariableReferenceExpression"/>s using the current NameComparer</returns>
private static Dictionary<string, DbVariableReferenceExpression> ProcessVariables(IEnumerable<DbVariableReferenceExpression> varDefs, ParserOptions parserOptions)
{
Dictionary<string, DbVariableReferenceExpression> retVars = new Dictionary<string, DbVariableReferenceExpression>(parserOptions.NameComparer);
if (varDefs != null)
{
foreach (DbVariableReferenceExpression varDef in varDefs)
{
if (retVars.ContainsKey(varDef.VariableName))
{
throw EntityUtil.EntitySqlError(Strings.MultipleDefinitionsOfVariable(varDef.VariableName));
}
Debug.Assert(varDef.ResultType.IsReadOnly, "varDef.ResultType.IsReadOnly must be set");
retVars.Add(varDef.VariableName, varDef);
}
}
return retVars;
}
#endregion
#region Properties
/// <summary>
/// Returns ordinary command parameters. Empty dictionary in case of no parameters.
/// </summary>
internal Dictionary<string, DbParameterReferenceExpression> Parameters
{
get { return _parameters; }
}
/// <summary>
/// Returns command free variables. Empty dictionary in case of no variables.
/// </summary>
internal Dictionary<string, DbVariableReferenceExpression> Variables
{
get { return _variables; }
}
/// <summary>
/// TypeSpace/Metadata/Perspective dependent type resolver.
/// </summary>
internal TypeResolver TypeResolver
{
get { return _typeResolver; }
}
/// <summary>
/// Returns current Parser Options.
/// </summary>
internal ParserOptions ParserOptions
{
get { return _parserOptions; }
}
/// <summary>
/// Returns the current string comparer.
/// </summary>
internal StringComparer NameComparer
{
get { return _parserOptions.NameComparer; }
}
/// <summary>
/// Returns the list of scope regions: outer followed by inner.
/// </summary>
internal IEnumerable<ScopeRegion> ScopeRegions
{
get { return _scopeRegions; }
}
/// <summary>
/// Returns the current scope region.
/// </summary>
internal ScopeRegion CurrentScopeRegion
{
get { return _scopeRegions[_scopeRegions.Count - 1]; }
}
/// <summary>
/// Returns the current scope.
/// </summary>
internal Scope CurrentScope
{
get { return _scopeManager.CurrentScope; }
}
/// <summary>
/// Returns index of the current scope.
/// </summary>
internal int CurrentScopeIndex
{
get { return _scopeManager.CurrentScopeIndex; }
}
/// <summary>
/// Returns the current group aggregate info when processing group aggregate argument.
/// </summary>
internal GroupAggregateInfo CurrentGroupAggregateInfo
{
get { return _currentGroupAggregateInfo; }
}
#endregion
#region GetExpressionFromScopeEntry
/// <summary>
/// Returns the appropriate expression from a given scope entry.
/// May return null for scope entries like <see cref="InvalidGroupInputRefScopeEntry"/>.
/// </summary>
private DbExpression GetExpressionFromScopeEntry(ScopeEntry scopeEntry, int scopeIndex, string varName, ErrorContext errCtx)
{
//
// If
// 1) we are in the context of a group aggregate or group key,
// 2) and the scopeEntry can have multiple interpretations depending on the aggregation context,
// 3) and the defining scope region of the scopeEntry is outer or equal to the defining scope region of the group aggregate,
// 4) and the defining scope region of the scopeEntry is not performing conversion of a group key definition,
// Then the expression that corresponds to the scopeEntry is either the GroupVarBasedExpression or the GroupAggBasedExpression.
// Otherwise the default expression that corresponds to the scopeEntry is provided by scopeEntry.GetExpression(...) call.
//
// Explanation for #2 from the list above:
// A scope entry may have multiple aggregation-context interpretations:
// - An expression in the context of a group key definition, obtained by scopeEntry.GetExpression(...);
// Example: select k1 from {0} as a group by a%2 as k1
// ^^^
// - An expression in the context of a function aggregate, provided by iGroupExpressionExtendedInfo.GroupVarBasedExpression;
// Example: select max( a ) from {0} as a group by a%2 as k1
// ^^^
// - An expression in the context of a group partition, provided by iGroupExpressionExtendedInfo.GroupAggBasedExpression;
// Example: select GroupPartition( a ) from {0} as a group by a%2 as k1
// ^^^
// Note that expressions obtained from aggregation-context-dependent scope entries outside of the three contexts mentioned above
// will default to the value returned by the scopeEntry.GetExpression(...) call. This value is the same as in the group key definition context.
// These expressions have correct result types which enables partial expression validation.
// However the contents of the expressions are invalid outside of the group key definitions, hence they can not appear in the final expression tree.
// SemanticAnalyzer.ProcessGroupByClause(...) method guarantees that such expressions are only temporarily used during GROUP BY clause processing and
// dropped afterwards.
// Example: select a, k1 from {0} as a group by a%2 as k1
// ^^^^^ - these expressions are processed twice: once during GROUP BY and then SELECT clause processing,
// the expressions obtained during GROUP BY clause processing are dropped and only
// the ones obtained during SELECT clause processing are accepted.
//
// Explanation for #3 from the list above:
// - An outer scope entry referenced inside of an aggregate may lift the aggregate to the outer scope region for evaluation,
// hence such a scope entry must be interpreted in the aggregation context. See explanation for #4 below for more info.
// Example:
//
// select
// (select max(x) from {1} as y)
// from {0} as x
//
// - If a scope entry is defined inside of a group aggregate, then the scope entry is not affected by the aggregate,
// hence such a scope entry is not interpreted in the aggregation context.
// Example:
//
// select max(
// anyelement( select b from {1} as b )
// )
// from {0} as a group by a %2 as a1
//
// In this query the aggregate argument contains a nested query expression.
// The nested query references b. Because b is defined inside of the aggregate it is not interpreted in the aggregation context and
// the expression for b should not be GroupVar/GroupAgg based, even though the reference to b appears inside of an aggregate.
//
// Explanation for #4 from the list above:
// An aggregate evaluating on a particular scope region defines the interpretation of scope entries defined on that scope region.
// In the case when an inner aggregate references a scope entry belonging to the evaluating region of an outer aggregate, the interpretation
// of the scope entry is controlled by the outer aggregate, otherwise it is controlled by the inner aggregate.
// Example:
//
// select a1
// from {0} as a group by
// anyelement(select value max(a + b) from {1} as b)
// as a1
//
// In this query the aggregate inside of a1 group key definition, the max(a + b), references scope entry a.
// Because a is referenced inside of the group key definition (which serves as an outer aggregate) and the key definition belongs to
// the same scope region as a, a is interpreted in the context of the group key definition, not the function aggregate and
// the expression for a is obtained by scopeEntry.GetExpression(...) call, not iGroupExpressionExtendedInfo.GroupVarBasedExpression.
//
DbExpression expr = scopeEntry.GetExpression(varName, errCtx);
Debug.Assert(expr != null, "scopeEntry.GetExpression(...) returned null");
if (_currentGroupAggregateInfo != null)
{
//
// Make sure defining scope regions agree as described above.
// Outer scope region has smaller index value than the inner.
//
ScopeRegion definingScopeRegionOfScopeEntry = GetDefiningScopeRegion(scopeIndex);
if (definingScopeRegionOfScopeEntry.ScopeRegionIndex <= _currentGroupAggregateInfo.DefiningScopeRegion.ScopeRegionIndex)
{
//
// Let the group aggregate know the scope of the scope entry it references.
// This affects the scope region that will evaluate the group aggregate.
//
_currentGroupAggregateInfo.UpdateScopeIndex(scopeIndex, this);
IGroupExpressionExtendedInfo iGroupExpressionExtendedInfo = scopeEntry as IGroupExpressionExtendedInfo;
if (iGroupExpressionExtendedInfo != null)
{
//
// Find the aggregate that controls interpretation of the current scope entry.
// This would be a containing aggregate with the defining scope region matching definingScopeRegionOfScopeEntry.
// If there is no such aggregate, then the current containing aggregate controls interpretation.
//
GroupAggregateInfo expressionInterpretationContext;
for (expressionInterpretationContext = _currentGroupAggregateInfo;
expressionInterpretationContext != null &&
expressionInterpretationContext.DefiningScopeRegion.ScopeRegionIndex >= definingScopeRegionOfScopeEntry.ScopeRegionIndex;
expressionInterpretationContext = expressionInterpretationContext.ContainingAggregate)
{
if (expressionInterpretationContext.DefiningScopeRegion.ScopeRegionIndex == definingScopeRegionOfScopeEntry.ScopeRegionIndex)
{
break;
}
}
if (expressionInterpretationContext == null ||
expressionInterpretationContext.DefiningScopeRegion.ScopeRegionIndex < definingScopeRegionOfScopeEntry.ScopeRegionIndex)
{
expressionInterpretationContext = _currentGroupAggregateInfo;
}
switch (expressionInterpretationContext.AggregateKind)
{
case GroupAggregateKind.Function:
if (iGroupExpressionExtendedInfo.GroupVarBasedExpression != null)
{
expr = iGroupExpressionExtendedInfo.GroupVarBasedExpression;
}
break;
case GroupAggregateKind.Partition:
if (iGroupExpressionExtendedInfo.GroupAggBasedExpression != null)
{
expr = iGroupExpressionExtendedInfo.GroupAggBasedExpression;
}
break;
case GroupAggregateKind.GroupKey:
//
// User the current expression obtained from scopeEntry.GetExpression(...)
//
break;
default:
Debug.Fail("Unexpected group aggregate kind.");
break;
}
}
}
}
return expr;
}
#endregion
#region Name resolution
#region Resolve simple / metadata member name
internal IDisposable EnterIgnoreEntityContainerNameResolution()
{
Debug.Assert(!_ignoreEntityContainerNameResolution, "EnterIgnoreEntityContainerNameResolution() is not reentrant.");
_ignoreEntityContainerNameResolution = true;
return new Disposer(delegate
{
Debug.Assert(this._ignoreEntityContainerNameResolution, "_ignoreEntityContainerNameResolution must be true.");
this._ignoreEntityContainerNameResolution = false;
});
}
internal ExpressionResolution ResolveSimpleName(string name, bool leftHandSideOfMemberAccess, ErrorContext errCtx)
{
Debug.Assert(!String.IsNullOrEmpty(name), "name must not be null or empty");
//
// Try resolving as a scope entry.
//
ScopeEntry scopeEntry;
int scopeIndex;
if (TryScopeLookup(name, out scopeEntry, out scopeIndex))
{
//
// Check for invalid join left expression correlation.
//
if (scopeEntry.EntryKind == ScopeEntryKind.SourceVar && ((SourceScopeEntry)scopeEntry).IsJoinClauseLeftExpr)
{
throw EntityUtil.EntitySqlError(errCtx, Strings.InvalidJoinLeftCorrelation);
}
//
// Set correlation flag.
//
SetScopeRegionCorrelationFlag(scopeIndex);
return new ValueExpression(GetExpressionFromScopeEntry(scopeEntry, scopeIndex, name, errCtx));
}
//
// Try resolving as a member of the default entity container.
//
EntityContainer defaultEntityContainer = this.TypeResolver.Perspective.GetDefaultContainer();
ExpressionResolution defaultEntityContainerResolution;
if (defaultEntityContainer != null && TryResolveEntityContainerMemberAccess(defaultEntityContainer, name, errCtx, out defaultEntityContainerResolution))
{
return defaultEntityContainerResolution;
}
if (!_ignoreEntityContainerNameResolution)
{
//
// Try resolving as an entity container.
//
EntityContainer entityContainer;
if (this.TypeResolver.Perspective.TryGetEntityContainer(name, _parserOptions.NameComparisonCaseInsensitive /*ignoreCase*/, out entityContainer))
{
return new EntityContainerExpression(entityContainer);
}
}
//
// Otherwise, resolve as an unqualified name.
//
return this.TypeResolver.ResolveUnqualifiedName(name, leftHandSideOfMemberAccess /* partOfQualifiedName */, errCtx);
}
internal MetadataMember ResolveSimpleFunctionName(string name, ErrorContext errCtx)
{
//
// "Foo()" represents a simple function name. Resolve it as an unqualified name by calling the type resolver directly.
// Note that calling type resolver directly will avoid resolution of the identifier as a local variable or entity container
// (these resolutions are performed only by ResolveSimpleName(...)).
//
var resolution = this.TypeResolver.ResolveUnqualifiedName(name, false /* partOfQualifiedName */, errCtx);
if (resolution.MetadataMemberClass == MetadataMemberClass.Namespace)
{
//
// Try resolving as a function import inside the default entity container.
//
EntityContainer defaultEntityContainer = this.TypeResolver.Perspective.GetDefaultContainer();
ExpressionResolution defaultEntityContainerResolution;
if (defaultEntityContainer != null &&
TryResolveEntityContainerMemberAccess(defaultEntityContainer, name, errCtx, out defaultEntityContainerResolution) &&
defaultEntityContainerResolution.ExpressionClass == ExpressionResolutionClass.MetadataMember)
{
resolution = (MetadataMember)defaultEntityContainerResolution;
}
}
return resolution;
}
/// <summary>
/// Performs scope lookup returning the scope entry and its index.
/// </summary>
private bool TryScopeLookup(string key, out ScopeEntry scopeEntry, out int scopeIndex)
{
scopeEntry = null;
scopeIndex = -1;
for (int i = CurrentScopeIndex; i >= 0; i--)
{
if (_scopeManager.GetScopeByIndex(i).TryLookup(key, out scopeEntry))
{
scopeIndex = i;
return true;
}
}
return false;
}
internal MetadataMember ResolveMetadataMemberName(string[] name, ErrorContext errCtx)
{
return this.TypeResolver.ResolveMetadataMemberName(name, errCtx);
}
#endregion
#region Resolve member name in member access
#region Resolve property access
/// <summary>
/// Resolve property <paramref name="name"/> off the <paramref name="valueExpr"/>.
/// </summary>
internal ValueExpression ResolvePropertyAccess(DbExpression valueExpr, string name, ErrorContext errCtx)
{
DbExpression propertyExpr;
if (TryResolveAsPropertyAccess(valueExpr, name, errCtx, out propertyExpr))
{
return new ValueExpression(propertyExpr);
}
if (TryResolveAsRefPropertyAccess(valueExpr, name, errCtx, out propertyExpr))
{
return new ValueExpression(propertyExpr);
}
if (TypeSemantics.IsCollectionType(valueExpr.ResultType))
{
throw EntityUtil.EntitySqlError(errCtx, Strings.NotAMemberOfCollection(name, valueExpr.ResultType.EdmType.FullName));
}
else
{
throw EntityUtil.EntitySqlError(errCtx, Strings.NotAMemberOfType(name, valueExpr.ResultType.EdmType.FullName));
}
}
/// <summary>
/// Try resolving <paramref name="name"/> as a property of the value returned by the <paramref name="valueExpr"/>.
/// </summary>
private bool TryResolveAsPropertyAccess(DbExpression valueExpr, string name, ErrorContext errCtx, out DbExpression propertyExpr)
{
Debug.Assert(valueExpr != null, "valueExpr != null");
propertyExpr = null;
if (Helper.IsStructuralType(valueExpr.ResultType.EdmType))
{
EdmMember member;
if (TypeResolver.Perspective.TryGetMember((StructuralType)valueExpr.ResultType.EdmType, name, _parserOptions.NameComparisonCaseInsensitive /*ignoreCase*/, out member))
{
Debug.Assert(member != null, "member != null");
Debug.Assert(this.NameComparer.Equals(name, member.Name), "this.NameComparer.Equals(name, member.Name)");
propertyExpr = DbExpressionBuilder.CreatePropertyExpressionFromMember(valueExpr, member);
return true;
}
}
return false;
}
/// <summary>
/// If <paramref name="valueExpr"/> returns a reference, then deref and try resolving <paramref name="name"/> as a property of the dereferenced value.
/// </summary>
private bool TryResolveAsRefPropertyAccess(DbExpression valueExpr, string name, ErrorContext errCtx, out DbExpression propertyExpr)
{
Debug.Assert(valueExpr != null, "valueExpr != null");
propertyExpr = null;
if (TypeSemantics.IsReferenceType(valueExpr.ResultType))
{
DbExpression derefExpr = valueExpr.Deref();
TypeUsage derefExprType = derefExpr.ResultType;
if (TryResolveAsPropertyAccess(derefExpr, name, errCtx, out propertyExpr))
{
return true;
}
else
{
throw EntityUtil.EntitySqlError(errCtx, Strings.InvalidDeRefProperty(name, derefExprType.EdmType.FullName, valueExpr.ResultType.EdmType.FullName));
}
}
return false;
}
#endregion
#region Resolve entity container member access
/// <summary>
/// Resolve entity set or function import <paramref name="name"/> in the <paramref name="entityContainer"/>
/// </summary>
internal ExpressionResolution ResolveEntityContainerMemberAccess(EntityContainer entityContainer, string name, ErrorContext errCtx)
{
ExpressionResolution resolution;
if (TryResolveEntityContainerMemberAccess(entityContainer, name, errCtx, out resolution))
{
return resolution;
}
else
{
throw EntityUtil.EntitySqlError(errCtx, Strings.MemberDoesNotBelongToEntityContainer(name, entityContainer.Name));
}
}
private bool TryResolveEntityContainerMemberAccess(EntityContainer entityContainer, string name, ErrorContext errCtx, out ExpressionResolution resolution)
{
EntitySetBase entitySetBase;
EdmFunction functionImport;
if (this.TypeResolver.Perspective.TryGetExtent(entityContainer, name, _parserOptions.NameComparisonCaseInsensitive /*ignoreCase*/, out entitySetBase))
{
resolution = new ValueExpression(entitySetBase.Scan());
return true;
}
else if (this.TypeResolver.Perspective.TryGetFunctionImport(entityContainer, name, _parserOptions.NameComparisonCaseInsensitive /*ignoreCase*/, out functionImport))
{
resolution = new MetadataFunctionGroup(functionImport.FullName, new EdmFunction[] { functionImport });
return true;
}
else
{
resolution = null;
return false;
}
}
#endregion
#region Resolve metadata member access
/// <summary>
/// Resolve namespace, type or function <paramref name="name"/> in the <paramref name="metadataMember"/>
/// </summary>
internal MetadataMember ResolveMetadataMemberAccess(MetadataMember metadataMember, string name, ErrorContext errCtx)
{
return this.TypeResolver.ResolveMetadataMemberAccess(metadataMember, name, errCtx);
}
#endregion
#endregion
#region Resolve internal aggregate name / alternative group key name
/// <summary>
/// Try resolving an internal aggregate name.
/// </summary>
internal bool TryResolveInternalAggregateName(string name, ErrorContext errCtx, out DbExpression dbExpression)
{
ScopeEntry scopeEntry;
int scopeIndex;
if (TryScopeLookup(name, out scopeEntry, out scopeIndex))
{
//
// Set the correlation flag.
//
SetScopeRegionCorrelationFlag(scopeIndex);
dbExpression = scopeEntry.GetExpression(name, errCtx);
return true;
}
else
{
dbExpression = null;
return false;
}
}
/// <summary>
/// Try resolving multipart identifier as an alternative name of a group key (see SemanticAnalyzer.ProcessGroupByClause(...) for more info).
/// </summary>
internal bool TryResolveDotExprAsGroupKeyAlternativeName(AST.DotExpr dotExpr, out ValueExpression groupKeyResolution)
{
groupKeyResolution = null;
string[] names;
ScopeEntry scopeEntry;
int scopeIndex;
if (IsInAnyGroupScope() &&
dotExpr.IsMultipartIdentifier(out names) &&
TryScopeLookup(TypeResolver.GetFullName(names), out scopeEntry, out scopeIndex))
{
IGetAlternativeName iGetAlternativeName = scopeEntry as IGetAlternativeName;
//
// Accept only if names[] match alternative name part by part.
//
if (iGetAlternativeName != null && iGetAlternativeName.AlternativeName != null &&
names.SequenceEqual(iGetAlternativeName.AlternativeName, this.NameComparer))
{
//
// Set correlation flag
//
SetScopeRegionCorrelationFlag(scopeIndex);
groupKeyResolution = new ValueExpression(GetExpressionFromScopeEntry(scopeEntry, scopeIndex, TypeResolver.GetFullName(names), dotExpr.ErrCtx));
return true;
}
}
return false;
}
#endregion
#endregion
#region Name generation utils (GenerateInternalName, CreateNewAlias, InferAliasName)
/// <summary>
/// Generates unique internal name.
/// </summary>
internal string GenerateInternalName(string hint)
{
// string concat is much faster than String.Format
return "_##" + hint + unchecked(_namegenCounter++).ToString(CultureInfo.InvariantCulture);
}
/// <summary>
/// Creates a new alias name based on the <paramref name="expr"/> information.
/// </summary>
private string CreateNewAlias(DbExpression expr)
{
DbScanExpression extent = expr as DbScanExpression;
if (null != extent)
{
return extent.Target.Name;
}
DbPropertyExpression property = expr as DbPropertyExpression;
if (null != property)
{
return property.Property.Name;
}
DbVariableReferenceExpression varRef = expr as DbVariableReferenceExpression;
if (null != varRef)
{
return varRef.VariableName;
}
return GenerateInternalName(String.Empty);
}
/// <summary>
/// Returns alias name from <paramref name="aliasedExpr"/> ast node if it contains an alias,
/// otherwise creates a new alias name based on the <paramref name="aliasedExpr"/>.Expr or <paramref name="convertedExpression"/> information.
/// </summary>
internal string InferAliasName(AST.AliasedExpr aliasedExpr, DbExpression convertedExpression)
{
if (aliasedExpr.Alias != null)
{
return aliasedExpr.Alias.Name;
}
AST.Identifier id = aliasedExpr.Expr as AST.Identifier;
if (null != id)
{
return id.Name;
}
AST.DotExpr dotExpr = aliasedExpr.Expr as AST.DotExpr;
string[] names;
if (null != dotExpr && dotExpr.IsMultipartIdentifier(out names))
{
return names[names.Length - 1];
}
return CreateNewAlias(convertedExpression);
}
#endregion
#region Scope/ScopeRegion utils
/// <summary>
/// Enters a new scope region.
/// </summary>
internal IDisposable EnterScopeRegion()
{
//
// Push new scope (the first scope in the new scope region)
//
_scopeManager.EnterScope();
//
// Create new scope region and push it
//
ScopeRegion scopeRegion = new ScopeRegion(_scopeManager, CurrentScopeIndex, _scopeRegions.Count);
_scopeRegions.Add(scopeRegion);
//
// Return scope region disposer that rolls back the scope.
//
return new Disposer(delegate
{
Debug.Assert(this.CurrentScopeRegion == scopeRegion, "Scope region stack is corrupted.");
//
// Root scope region is permanent.
//
Debug.Assert(this._scopeRegions.Count > 1, "_scopeRegionFlags.Count > 1");
//
// Reset aggregate info of AST nodes of aggregates resolved to the CurrentScopeRegion.
//
this.CurrentScopeRegion.GroupAggregateInfos.ForEach(groupAggregateInfo => groupAggregateInfo.DetachFromAstNode());
//
// Rollback scopes of the region.
//
this.CurrentScopeRegion.RollbackAllScopes();
//
// Remove the scope region.
//
this._scopeRegions.Remove(CurrentScopeRegion);
});
}
/// <summary>
/// Rollback all scopes above the <paramref name="scopeIndex"/>.
/// </summary>
internal void RollbackToScope(int scopeIndex)
{
_scopeManager.RollbackToScope(scopeIndex);
}
/// <summary>
/// Enter a new scope.
/// </summary>
internal void EnterScope()
{
_scopeManager.EnterScope();
}
/// <summary>
/// Leave the current scope.
/// </summary>
internal void LeaveScope()
{
_scopeManager.LeaveScope();
}
/// <summary>
/// Returns true if any of the ScopeRegions from the closest to the outermost has IsAggregating = true
/// </summary>
internal bool IsInAnyGroupScope()
{
for (int i = 0; i < _scopeRegions.Count; i++)
{
if (_scopeRegions[i].IsAggregating)
{
return true;
}
}
return false;
}
internal ScopeRegion GetDefiningScopeRegion(int scopeIndex)
{
//
// Starting from the innermost, find the outermost scope region that contains the scope.
//
for (int i = _scopeRegions.Count - 1; i >= 0; --i)
{
if (_scopeRegions[i].ContainsScope(scopeIndex))
{
return _scopeRegions[i];
}
}
Debug.Fail("Failed to find the defining scope region for the given scope.");
return null;
}
/// <summary>
/// Sets the scope region correlation flag based on the scope index of the referenced scope entry.
/// </summary>
private void SetScopeRegionCorrelationFlag(int scopeIndex)
{
GetDefiningScopeRegion(scopeIndex).WasResolutionCorrelated = true;
}
#endregion
#region Group aggregate utils
/// <summary>
/// Enters processing of a function group aggregate.
/// </summary>
internal IDisposable EnterFunctionAggregate(AST.MethodExpr methodExpr, ErrorContext errCtx, out FunctionAggregateInfo aggregateInfo)
{
aggregateInfo = new FunctionAggregateInfo(methodExpr, errCtx, _currentGroupAggregateInfo, CurrentScopeRegion);
return EnterGroupAggregate(aggregateInfo);
}
/// <summary>
/// Enters processing of a group partition aggregate.
/// </summary>
internal IDisposable EnterGroupPartition(AST.GroupPartitionExpr groupPartitionExpr, ErrorContext errCtx, out GroupPartitionInfo aggregateInfo)
{
aggregateInfo = new GroupPartitionInfo(groupPartitionExpr, errCtx, _currentGroupAggregateInfo, CurrentScopeRegion);
return EnterGroupAggregate(aggregateInfo);
}
/// <summary>
/// Enters processing of a group partition aggregate.
/// </summary>
internal IDisposable EnterGroupKeyDefinition(GroupAggregateKind aggregateKind, ErrorContext errCtx, out GroupKeyAggregateInfo aggregateInfo)
{
aggregateInfo = new GroupKeyAggregateInfo(aggregateKind, errCtx, _currentGroupAggregateInfo, CurrentScopeRegion);
return EnterGroupAggregate(aggregateInfo);
}
private IDisposable EnterGroupAggregate(GroupAggregateInfo aggregateInfo)
{
_currentGroupAggregateInfo = aggregateInfo;
return new Disposer(delegate
{
//
// First, pop the element from the stack to keep the stack valid...
//
Debug.Assert(this._currentGroupAggregateInfo == aggregateInfo, "Aggregare info stack is corrupted.");
this._currentGroupAggregateInfo = aggregateInfo.ContainingAggregate;
//
// ...then validate and seal the aggregate info.
// Note that this operation may throw an EntitySqlException.
//
aggregateInfo.ValidateAndComputeEvaluatingScopeRegion(this);
});
}
#endregion
#region Function overload resolution (untyped null aware)
internal static EdmFunction ResolveFunctionOverloads(IList<EdmFunction> functionsMetadata,
IList<TypeUsage> argTypes,
bool isGroupAggregateFunction,
out bool isAmbiguous)
{
return FunctionOverloadResolver.ResolveFunctionOverloads(
functionsMetadata,
argTypes,
UntypedNullAwareFlattenArgumentType,
UntypedNullAwareFlattenParameterType,
UntypedNullAwareIsPromotableTo,
UntypedNullAwareIsStructurallyEqual,
isGroupAggregateFunction,
out isAmbiguous);
}
internal static TFunctionMetadata ResolveFunctionOverloads<TFunctionMetadata, TFunctionParameterMetadata>(
IList<TFunctionMetadata> functionsMetadata,
IList<TypeUsage> argTypes,
Func<TFunctionMetadata, IList<TFunctionParameterMetadata>> getSignatureParams,
Func<TFunctionParameterMetadata, TypeUsage> getParameterTypeUsage,
Func<TFunctionParameterMetadata, ParameterMode> getParameterMode,
bool isGroupAggregateFunction,
out bool isAmbiguous) where TFunctionMetadata : class
{
return FunctionOverloadResolver.ResolveFunctionOverloads(
functionsMetadata,
argTypes,
getSignatureParams,
getParameterTypeUsage,
getParameterMode,
UntypedNullAwareFlattenArgumentType,
UntypedNullAwareFlattenParameterType,
UntypedNullAwareIsPromotableTo,
UntypedNullAwareIsStructurallyEqual,
isGroupAggregateFunction,
out isAmbiguous);
}
private static IEnumerable<TypeUsage> UntypedNullAwareFlattenArgumentType(TypeUsage argType)
{
return argType != null ? TypeSemantics.FlattenType(argType) : new TypeUsage[] { null };
}
private static IEnumerable<TypeUsage> UntypedNullAwareFlattenParameterType(TypeUsage paramType, TypeUsage argType)
{
return argType != null ? TypeSemantics.FlattenType(paramType) : new TypeUsage[] { paramType };
}
private static bool UntypedNullAwareIsPromotableTo(TypeUsage fromType, TypeUsage toType)
{
if (fromType == null)
{
//
// We can implicitly promote null to any type except collection.
//
return !Helper.IsCollectionType(toType.EdmType);
}
else
{
return TypeSemantics.IsPromotableTo(fromType, toType);
}
}
private static bool UntypedNullAwareIsStructurallyEqual(TypeUsage fromType, TypeUsage toType)
{
if (fromType == null)
{
return UntypedNullAwareIsPromotableTo(fromType, toType);
}
else
{
return TypeSemantics.IsStructurallyEqual(fromType, toType);
}
}
#endregion
}
/// <summary>
/// Represents an utility for creating anonymous IDisposable implementations.
/// </summary>
internal class Disposer : IDisposable
{
private readonly Action _action;
internal Disposer(Action action)
{
Debug.Assert(action != null, "action != null");
_action = action;
}
public void Dispose()
{
_action();
GC.SuppressFinalize(this);
}
}
internal enum GroupAggregateKind
{
None,
/// <summary>
/// Inside of an aggregate function (Max, Min, etc).
/// All range variables originating on the defining scope of this aggregate should yield <see cref="IGroupExpressionExtendedInfo.GroupVarBasedExpression"/>.
/// </summary>
Function,
/// <summary>
/// Inside of GROUPPARTITION expression.
/// All range variables originating on the defining scope of this aggregate should yield <see cref="IGroupExpressionExtendedInfo.GroupAggBasedExpression"/>.
/// </summary>
Partition,
/// <summary>
/// Inside of a group key definition
/// All range variables originating on the defining scope of this aggregate should yield <see cref="ScopeEntry.GetExpression"/>.
/// </summary>
GroupKey
}
/// <summary>
/// Represents group aggregate information during aggregate construction/resolution.
/// </summary>
internal abstract class GroupAggregateInfo
{
protected GroupAggregateInfo(
GroupAggregateKind aggregateKind,
AST.GroupAggregateExpr astNode,
ErrorContext errCtx,
GroupAggregateInfo containingAggregate,
ScopeRegion definingScopeRegion)
{
Debug.Assert(aggregateKind != GroupAggregateKind.None, "aggregateKind != GroupAggregateKind.None");
Debug.Assert(errCtx != null, "errCtx != null");
Debug.Assert(definingScopeRegion != null, "definingScopeRegion != null");
AggregateKind = aggregateKind;
AstNode = astNode;
ErrCtx = errCtx;
DefiningScopeRegion = definingScopeRegion;
SetContainingAggregate(containingAggregate);
}
protected void AttachToAstNode(string aggregateName, TypeUsage resultType)
{
Debug.Assert(AstNode != null, "AstNode must be set.");
Debug.Assert(aggregateName != null && resultType != null, "aggregateName and aggregateDefinition must not be null.");
Debug.Assert(AggregateName == null && AggregateStubExpression == null, "Cannot reattach.");
AggregateName = aggregateName;
AggregateStubExpression = resultType.Null();
// Attach group aggregate info to the ast node.
AstNode.AggregateInfo = this;
}
internal void DetachFromAstNode()
{
Debug.Assert(AstNode != null, "AstNode must be set.");
AstNode.AggregateInfo = null;
}
/// <summary>
/// Updates referenced scope index of the aggregate.
/// Function call is not allowed after <see cref="ValidateAndComputeEvaluatingScopeRegion"/> has been called.
/// </summary>
internal void UpdateScopeIndex(int referencedScopeIndex, SemanticResolver sr)
{
Debug.Assert(_evaluatingScopeRegion == null, "Can not update referenced scope index after _evaluatingScopeRegion have been computed.");
ScopeRegion referencedScopeRegion = sr.GetDefiningScopeRegion(referencedScopeIndex);
if (_innermostReferencedScopeRegion == null ||
_innermostReferencedScopeRegion.ScopeRegionIndex < referencedScopeRegion.ScopeRegionIndex)
{
_innermostReferencedScopeRegion = referencedScopeRegion;
}
}
/// <summary>
/// Gets/sets the innermost referenced scope region of the current aggregate.
/// This property is used to save/restore the scope region value during a potentially throw-away attempt to
/// convert an <see cref="AST.MethodExpr"/> as a collection function in the <see cref="SemanticAnalyzer.ConvertAggregateFunctionInGroupScope"/> method.
/// Setting the value is not allowed after <see cref="ValidateAndComputeEvaluatingScopeRegion"/> has been called.
/// </summary>
internal ScopeRegion InnermostReferencedScopeRegion
{
get { return _innermostReferencedScopeRegion; }
set
{
Debug.Assert(_evaluatingScopeRegion == null, "Can't change _innermostReferencedScopeRegion after _evaluatingScopeRegion has been initialized.");
_innermostReferencedScopeRegion = value;
}
}
private ScopeRegion _innermostReferencedScopeRegion;
/// <summary>
/// Validates the aggregate info and computes <see cref="EvaluatingScopeRegion"/> property.
/// Seals the aggregate info object (no more AddContainedAggregate(...), RemoveContainedAggregate(...) and UpdateScopeIndex(...) calls allowed).
/// </summary>
internal void ValidateAndComputeEvaluatingScopeRegion(SemanticResolver sr)
{
Debug.Assert(_evaluatingScopeRegion == null, "_evaluatingScopeRegion has already been initialized");
//
// If _innermostReferencedScopeRegion is null, it means the aggregate is not correlated (a constant value),
// so resolve it to the DefiningScopeRegion.
//
_evaluatingScopeRegion = _innermostReferencedScopeRegion ?? DefiningScopeRegion;
if (!_evaluatingScopeRegion.IsAggregating)
{
//
// In some cases the found scope region does not aggregate (has no grouping). So adding the aggregate to that scope won't work.
// In this situation we need to backtrack from the found region to the first inner region that performs aggregation.
// Example:
// select yy.cx, yy.cy, yy.cz
// from {1, 2} as x cross apply (select zz.cx, zz.cy, zz.cz
// from {3, 4} as y cross apply (select Count(x) as cx, Count(y) as cy, Count(z) as cz
// from {5, 6} as z) as zz
// ) as yy
// Note that Count aggregates cx and cy refer to scope regions that do aggregate. All three aggregates needs to be added to the only
// aggregating region - the innermost.
//
int scopeRegionIndex = _evaluatingScopeRegion.ScopeRegionIndex;
_evaluatingScopeRegion = null;
foreach (ScopeRegion innerSR in sr.ScopeRegions.Skip(scopeRegionIndex))
{
if (innerSR.IsAggregating)
{
_evaluatingScopeRegion = innerSR;
break;
}
}
if (_evaluatingScopeRegion == null)
{
throw EntityUtil.EntitySqlError(Strings.GroupVarNotFoundInScope);
}
}
//
// Validate all the contained aggregates for violation of the containment rule:
// None of the nested (contained) aggregates must be evaluating on a scope region that is
// a. equal or inner to the evaluating scope of the current aggregate and
// b. equal or outer to the defining scope of the current aggregate.
//
// Example of a disallowed query:
//
// select
// (select max(x + max(y))
// from {1} as y)
// from {0} as x
//
// Example of an allowed query where the ESR of the nested aggregate is outer to the ESR of the outer aggregate:
//
// select
// (select max(y + max(x))
// from {1} as y)
// from {0} as x
//
// Example of an allowed query where the ESR of the nested aggregate is inner to the DSR of the outer aggregate:
//
// select max(x + anyelement(select value max(y) from {1} as y))
// from {0} as x
//
Debug.Assert(_evaluatingScopeRegion.IsAggregating, "_evaluatingScopeRegion.IsAggregating must be true");
Debug.Assert(_evaluatingScopeRegion.ScopeRegionIndex <= DefiningScopeRegion.ScopeRegionIndex, "_evaluatingScopeRegion must outer to the DefiningScopeRegion");
ValidateContainedAggregates(_evaluatingScopeRegion.ScopeRegionIndex, DefiningScopeRegion.ScopeRegionIndex);
}
/// <summary>
/// Recursively validates that <see cref="GroupAggregateInfo.EvaluatingScopeRegion"/> of all contained aggregates
/// is outside of the range of scope regions defined by <paramref name="outerBoundaryScopeRegionIndex"/> and <paramref name="innerBoundaryScopeRegionIndex"/>.
/// Throws in the case of violation.
/// </summary>
private void ValidateContainedAggregates(int outerBoundaryScopeRegionIndex, int innerBoundaryScopeRegionIndex)
{
if (_containedAggregates != null)
{
foreach (GroupAggregateInfo containedAggregate in _containedAggregates)
{
if (containedAggregate.EvaluatingScopeRegion.ScopeRegionIndex >= outerBoundaryScopeRegionIndex &&
containedAggregate.EvaluatingScopeRegion.ScopeRegionIndex <= innerBoundaryScopeRegionIndex)
{
int line, column;
string currentAggregateInfo = EntitySqlException.FormatErrorContext(
ErrCtx.CommandText,
ErrCtx.InputPosition,
ErrCtx.ErrorContextInfo,
ErrCtx.UseContextInfoAsResourceIdentifier,
out line, out column);
string nestedAggregateInfo = EntitySqlException.FormatErrorContext(
containedAggregate.ErrCtx.CommandText,
containedAggregate.ErrCtx.InputPosition,
containedAggregate.ErrCtx.ErrorContextInfo,
containedAggregate.ErrCtx.UseContextInfoAsResourceIdentifier,
out line, out column);
throw EntityUtil.EntitySqlError(Strings.NestedAggregateCannotBeUsedInAggregate(nestedAggregateInfo, currentAggregateInfo));
}
//
// We need to check the full subtree in order to catch this case:
// select max(x +
// anyelement(select max(y +
// anyelement(select value max(x)
// from {2} as z))
// from {1} as y))
// from {0} as x
//
containedAggregate.ValidateContainedAggregates(outerBoundaryScopeRegionIndex, innerBoundaryScopeRegionIndex);
}
}
}
internal void SetContainingAggregate(GroupAggregateInfo containingAggregate)
{
if (_containingAggregate != null)
{
//
// Aggregates in this query
//
// select value max(anyelement(select value max(b + max(a + anyelement(select value c1
// from {2} as c group by c as c1)))
// from {1} as b group by b as b1))
//
// from {0} as a group by a as a1
//
// are processed in the following steps:
// 1. the outermost aggregate (max1) begins processing as a collection function;
// 2. the middle aggregate (max2) begins processing as a collection function;
// 3. the innermost aggregate (max3) is processed as a collection function;
// 4. max3 is reprocessed as an aggregate; it does not see any containing aggregates at this point, so it's not wired up;
// max3 is validated and sealed;
// evaluating scope region for max3 is the outermost scope region, to which it gets assigned;
// max3 aggregate info object is attached to the corresponding AST node;
// 5. max2 completes processing as a collection function and begins processing as an aggregate;
// 6. max3 is reprocessed as an aggregate in the SemanticAnalyzer.TryConvertAsResolvedGroupAggregate(...) method, and
// wired up to max2 as contained/containing;
// 7. max2 completes processing as an aggregate;
// max2 is validated and sealed;
// note that max2 does not see any containing aggregates at this point, so it's wired up only to max3;
// evaluating scope region for max2 is the middle scope region to which it gets assigned;
// 6. middle scope region completes processing, yields a DbExpression and cleans up all aggregate info objects assigned to it (max2);
// max2 is detached from the corresponding AST node;
// at this point max3 is still assigned to the outermost scope region and still wired to the dropped max2 as containing/contained;
// 7. max1 completes processing as a collection function and begins processing as an aggregate;
// 8. max2 is revisited and begins processing as a collection function (note that because the old aggregate info object for max2 was dropped
// and detached from the AST node in step 6, SemanticAnalyzer.TryConvertAsResolvedGroupAggregate(...) does not recognize max2 as an aggregate);
// 9. max3 is recognized as an aggregate in the SemanticAnalyzer.TryConvertAsResolvedGroupAggregate(...) method;
// max3 is rewired from the dropped max2 (step 6) to max1 as contained/containing, now max1 and max3 are wired as containing/contained;
// 10. max2 completes processing as a collection function and begins processing as an aggregate;
// max2 sees max1 as a containing aggregate and wires to it;
// 11. max3 is reprocessed as resolved aggregate inside of TryConvertAsResolvedGroupAggregate(...) method;
// max3 is rewired from max1 to max2 as containing/contained aggregate;
// 12. at this point max1 is wired to max2 and max2 is wired to max3, the tree is correct;
//
// ... both max1 and max3 are assigned to the same scope for evaluation, this is detected and an error is reported;
//
//
// Remove this aggregate from the old containing aggregate before rewiring to the new parent.
//
_containingAggregate.RemoveContainedAggregate(this);
}
//
// Accept the new parent and wire to it as a contained aggregate.
//
_containingAggregate = containingAggregate;
if (_containingAggregate != null)
{
_containingAggregate.AddContainedAggregate(this);
}
}
/// <summary>
/// Function call is not allowed after <see cref="ValidateAndComputeEvaluatingScopeRegion"/> has been called.
/// Adding new contained aggregate may invalidate the current aggregate.
/// </summary>
private void AddContainedAggregate(GroupAggregateInfo containedAggregate)
{
Debug.Assert(_evaluatingScopeRegion == null, "Can not add contained aggregate after _evaluatingScopeRegion have been computed.");
if (_containedAggregates == null)
{
_containedAggregates = new List<GroupAggregateInfo>();
}
Debug.Assert(_containedAggregates.Contains(containedAggregate) == false, "containedAggregate is already registered");
_containedAggregates.Add(containedAggregate);
}
private List<GroupAggregateInfo> _containedAggregates;
/// <summary>
/// Function call is _allowed_ after <see cref="ValidateAndComputeEvaluatingScopeRegion"/> has been called.
/// Removing contained aggregates cannot invalidate the current aggregate.
///
/// Consider the following query:
///
/// select value max(a + anyelement(select value max(b + max(a + anyelement(select value c1
/// from {2} as c group by c as c1)))
/// from {1} as b group by b as b1))
/// from {0} as a group by a as a1
///
/// Outer aggregate - max1, middle aggregate - max2, inner aggregate - max3.
/// In this query after max1 have been processed as a collection function, max2 and max3 are wired as containing/contained.
/// There is a point later when max1 is processed as an aggregate, max2 is processed as a collection function and max3 is processed as
/// an aggregate. Note that at this point the "aggregate" version of max2 is dropped and detached from the AST node when the middle scope region
/// completes processing; also note that because evaluating scope region of max3 is the outer scope region, max3 aggregate info is still attached to
/// the AST node and it is still wired to the dropped aggregate info object of max2. At this point max3 does not see new max2 as a containing aggregate,
/// and it rewires to max1, during this rewiring it needs to to remove itself from the old max2 and add itself to max1.
/// The old max2 at this point is sealed, so the removal is performed on the sealed object.
/// </summary>
private void RemoveContainedAggregate(GroupAggregateInfo containedAggregate)
{
Debug.Assert(_containedAggregates != null && _containedAggregates.Contains(containedAggregate), "_containedAggregates.Contains(containedAggregate)");
_containedAggregates.Remove(containedAggregate);
}
internal readonly GroupAggregateKind AggregateKind;
/// <summary>
/// Null when <see cref="GroupAggregateInfo"/> is created for a group key processing.
/// </summary>
internal readonly AST.GroupAggregateExpr AstNode;
internal readonly ErrorContext ErrCtx;
/// <summary>
/// Scope region that contains the aggregate expression.
/// </summary>
internal readonly ScopeRegion DefiningScopeRegion;
/// <summary>
/// Scope region that evaluates the aggregate expression.
/// </summary>
internal ScopeRegion EvaluatingScopeRegion
{
get
{
//
// _evaluatingScopeRegion is initialized in the ValidateAndComputeEvaluatingScopeRegion(...) method.
//
Debug.Assert(_evaluatingScopeRegion != null, "_evaluatingScopeRegion is not initialized");
return _evaluatingScopeRegion;
}
}
private ScopeRegion _evaluatingScopeRegion;
/// <summary>
/// Parent aggregate expression that contains the current aggregate expression.
/// May be null.
/// </summary>
internal GroupAggregateInfo ContainingAggregate
{
get { return _containingAggregate; }
}
private GroupAggregateInfo _containingAggregate;
internal string AggregateName;
internal DbNullExpression AggregateStubExpression;
}
internal sealed class FunctionAggregateInfo : GroupAggregateInfo
{
internal FunctionAggregateInfo(AST.MethodExpr methodExpr, ErrorContext errCtx, GroupAggregateInfo containingAggregate, ScopeRegion definingScopeRegion)
: base(GroupAggregateKind.Function, methodExpr, errCtx, containingAggregate, definingScopeRegion)
{
Debug.Assert(methodExpr != null, "methodExpr != null");
}
internal void AttachToAstNode(string aggregateName, DbAggregate aggregateDefinition)
{
Debug.Assert(aggregateDefinition != null, "aggregateDefinition != null");
base.AttachToAstNode(aggregateName, aggregateDefinition.ResultType);
AggregateDefinition = aggregateDefinition;
}
internal DbAggregate AggregateDefinition;
}
internal sealed class GroupPartitionInfo : GroupAggregateInfo
{
internal GroupPartitionInfo(AST.GroupPartitionExpr groupPartitionExpr, ErrorContext errCtx, GroupAggregateInfo containingAggregate, ScopeRegion definingScopeRegion)
: base(GroupAggregateKind.Partition, groupPartitionExpr, errCtx, containingAggregate, definingScopeRegion)
{
Debug.Assert(groupPartitionExpr != null, "groupPartitionExpr != null");
}
internal void AttachToAstNode(string aggregateName, DbExpression aggregateDefinition)
{
Debug.Assert(aggregateDefinition != null, "aggregateDefinition != null");
base.AttachToAstNode(aggregateName, aggregateDefinition.ResultType);
AggregateDefinition = aggregateDefinition;
}
internal DbExpression AggregateDefinition;
}
internal sealed class GroupKeyAggregateInfo : GroupAggregateInfo
{
internal GroupKeyAggregateInfo(GroupAggregateKind aggregateKind, ErrorContext errCtx, GroupAggregateInfo containingAggregate, ScopeRegion definingScopeRegion)
: base(aggregateKind, null /* there is no AST.GroupAggregateExpression corresponding to the group key */, errCtx, containingAggregate, definingScopeRegion)
{ }
}
internal abstract class InlineFunctionInfo
{
internal InlineFunctionInfo(AST.FunctionDefinition functionDef, List<DbVariableReferenceExpression> parameters)
{
FunctionDefAst = functionDef;
Parameters = parameters;
}
internal readonly AST.FunctionDefinition FunctionDefAst;
internal readonly List<DbVariableReferenceExpression> Parameters;
internal abstract DbLambda GetLambda(SemanticResolver sr);
}
internal sealed class ScopeRegion
{
private readonly ScopeManager _scopeManager;
internal ScopeRegion(ScopeManager scopeManager, int firstScopeIndex, int scopeRegionIndex)
{
_scopeManager = scopeManager;
_firstScopeIndex = firstScopeIndex;
_scopeRegionIndex = scopeRegionIndex;
}
/// <summary>
/// First scope of the region.
/// </summary>
internal int FirstScopeIndex
{
get { return _firstScopeIndex; }
}
private readonly int _firstScopeIndex;
/// <summary>
/// Index of the scope region.
/// Outer scope regions have smaller index value than inner scope regions.
/// </summary>
internal int ScopeRegionIndex
{
get { return _scopeRegionIndex; }
}
private readonly int _scopeRegionIndex;
/// <summary>
/// True if given scope is in the current scope region.
/// </summary>
internal bool ContainsScope(int scopeIndex)
{
return (scopeIndex >= _firstScopeIndex);
}
/// <summary>
/// Marks current scope region as performing group/folding operation.
/// </summary>
internal void EnterGroupOperation(DbExpressionBinding groupAggregateBinding)
{
Debug.Assert(!IsAggregating, "Scope region group operation is not reentrant.");
_groupAggregateBinding = groupAggregateBinding;
}
/// <summary>
/// Clears the <see cref="IsAggregating"/> flag on the group scope.
/// </summary>
internal void RollbackGroupOperation()
{
Debug.Assert(IsAggregating, "Scope region must inside group operation in order to leave it.");
_groupAggregateBinding = null;
}
/// <summary>
/// True when the scope region performs group/folding operation.
/// </summary>
internal bool IsAggregating
{
get { return _groupAggregateBinding != null; }
}
internal DbExpressionBinding GroupAggregateBinding
{
get
{
Debug.Assert(IsAggregating, "IsAggregating must be true.");
return _groupAggregateBinding;
}
}
private DbExpressionBinding _groupAggregateBinding;
/// <summary>
/// Returns list of group aggregates evaluated on the scope region.
/// </summary>
internal List<GroupAggregateInfo> GroupAggregateInfos
{
get { return _groupAggregateInfos; }
}
private List<GroupAggregateInfo> _groupAggregateInfos = new List<GroupAggregateInfo>();
/// <summary>
/// Adds group aggregate name to the scope region.
/// </summary>
internal void RegisterGroupAggregateName(string groupAggregateName)
{
Debug.Assert(!_groupAggregateNames.Contains(groupAggregateName), "!_groupAggregateNames.ContainsKey(groupAggregateName)");
_groupAggregateNames.Add(groupAggregateName);
}
internal bool ContainsGroupAggregate(string groupAggregateName)
{
return _groupAggregateNames.Contains(groupAggregateName);
}
private HashSet<string> _groupAggregateNames = new HashSet<string>();
/// <summary>
/// True if a recent expression resolution was correlated.
/// </summary>
internal bool WasResolutionCorrelated
{
get { return _wasResolutionCorrelated; }
set { _wasResolutionCorrelated = value; }
}
private bool _wasResolutionCorrelated = false;
/// <summary>
/// Applies <paramref name="action"/> to all scope entries in the current scope region.
/// </summary>
internal void ApplyToScopeEntries(Action<ScopeEntry> action)
{
Debug.Assert(FirstScopeIndex <= _scopeManager.CurrentScopeIndex, "FirstScopeIndex <= CurrentScopeIndex");
for (int i = FirstScopeIndex; i <= _scopeManager.CurrentScopeIndex; ++i)
{
foreach (KeyValuePair<string, ScopeEntry> scopeEntry in _scopeManager.GetScopeByIndex(i))
{
action(scopeEntry.Value);
}
}
}
/// <summary>
/// Applies <paramref name="action"/> to all scope entries in the current scope region.
/// </summary>
internal void ApplyToScopeEntries(Func<ScopeEntry, ScopeEntry> action)
{
Debug.Assert(FirstScopeIndex <= _scopeManager.CurrentScopeIndex, "FirstScopeIndex <= CurrentScopeIndex");
for (int i = FirstScopeIndex; i <= _scopeManager.CurrentScopeIndex; ++i)
{
Scope scope = _scopeManager.GetScopeByIndex(i);
List<KeyValuePair<string, ScopeEntry>> updatedEntries = null;
foreach (KeyValuePair<string, ScopeEntry> scopeEntry in scope)
{
ScopeEntry newScopeEntry = action(scopeEntry.Value);
Debug.Assert(newScopeEntry != null, "newScopeEntry != null");
if (scopeEntry.Value != newScopeEntry)
{
if (updatedEntries == null)
{
updatedEntries = new List<KeyValuePair<string, ScopeEntry>>();
}
updatedEntries.Add(new KeyValuePair<string, ScopeEntry>(scopeEntry.Key, newScopeEntry));
}
}
if (updatedEntries != null)
{
updatedEntries.ForEach((updatedScopeEntry) => scope.Replace(updatedScopeEntry.Key, updatedScopeEntry.Value));
}
}
}
internal void RollbackAllScopes()
{
_scopeManager.RollbackToScope(FirstScopeIndex - 1);
}
}
/// <summary>
/// Represents a pair of types to avoid uncessary enumerations to split kvp elements
/// </summary>
internal sealed class Pair<L, R>
{
internal Pair(L left, R right)
{
Left = left;
Right = right;
}
internal L Left;
internal R Right;
internal KeyValuePair<L, R> GetKVP()
{
return new KeyValuePair<L, R>(Left, Right);
}
}
}
|