//--------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner [....] // @backupOwner [....] //--------------------------------------------------------------------- namespace System.Data.Common.EntitySql { using System.Collections.Generic; using System.Data.Common.CommandTrees; using System.Data.Common.CommandTrees.ExpressionBuilder; using System.Data.Entity; using System.Diagnostics; /// /// Represents a scope of key-value pairs. /// internal sealed class Scope : IEnumerable> { private readonly Dictionary _scopeEntries; /// /// Initialize using a given key comparer. /// /// internal Scope(IEqualityComparer keyComparer) { _scopeEntries = new Dictionary(keyComparer); } /// /// Add new key to the scope. If key already exists - throw. /// internal Scope Add(string key, ScopeEntry value) { _scopeEntries.Add(key, value); return this; } /// /// Remove an entry from the scope. /// internal void Remove(string key) { Debug.Assert(Contains(key)); _scopeEntries.Remove(key); } internal void Replace(string key, ScopeEntry value) { Debug.Assert(Contains(key)); _scopeEntries[key] = value; } /// /// Returns true if the key belongs to the scope. /// internal bool Contains(string key) { return _scopeEntries.ContainsKey(key); } /// /// Search item by key. Returns true in case of success and false otherwise. /// internal bool TryLookup(string key, out ScopeEntry value) { return (_scopeEntries.TryGetValue(key, out value)); } #region GetEnumerator public Dictionary.Enumerator GetEnumerator() { return _scopeEntries.GetEnumerator(); } System.Collections.Generic.IEnumerator> System.Collections.Generic.IEnumerable>.GetEnumerator() { return _scopeEntries.GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return _scopeEntries.GetEnumerator(); } #endregion } internal enum ScopeEntryKind { SourceVar, GroupKeyDefinition, ProjectionItemDefinition, FreeVar, /// /// Represents a group input scope entry that should no longer be referenced. /// InvalidGroupInputRef } /// /// Represents an entry in the scope. /// internal abstract class ScopeEntry { private readonly ScopeEntryKind _scopeEntryKind; internal ScopeEntry(ScopeEntryKind scopeEntryKind) { _scopeEntryKind = scopeEntryKind; } internal ScopeEntryKind EntryKind { get { return _scopeEntryKind; } } /// /// Returns CQT expression corresponding to the scope entry. /// internal abstract DbExpression GetExpression(string refName, ErrorContext errCtx); } internal interface IGroupExpressionExtendedInfo { /// /// Returns based expression during the construction process, otherwise null. /// DbExpression GroupVarBasedExpression { get; } /// /// Returns based expression during the construction process, otherwise null. /// DbExpression GroupAggBasedExpression { get; } } internal interface IGetAlternativeName { /// /// If current scope entry reperesents an alternative group key name (see SemanticAnalyzer.ProcessGroupByClause(...) for more info) /// then this property returns the alternative name, otherwise null. /// string[] AlternativeName { get; } } /// /// Represents simple source var scope entry. /// internal sealed class SourceScopeEntry : ScopeEntry, IGroupExpressionExtendedInfo, IGetAlternativeName { private readonly string[] _alternativeName; private List _propRefs; private DbExpression _varBasedExpression; private DbExpression _groupVarBasedExpression; private DbExpression _groupAggBasedExpression; private bool _joinClauseLeftExpr = false; internal SourceScopeEntry(DbVariableReferenceExpression varRef) : this(varRef, null) { } internal SourceScopeEntry(DbVariableReferenceExpression varRef, string[] alternativeName) : base(ScopeEntryKind.SourceVar) { _varBasedExpression = varRef; _alternativeName = alternativeName; } internal override DbExpression GetExpression(string refName, ErrorContext errCtx) { return _varBasedExpression; } DbExpression IGroupExpressionExtendedInfo.GroupVarBasedExpression { get { return _groupVarBasedExpression; } } DbExpression IGroupExpressionExtendedInfo.GroupAggBasedExpression { get { return _groupAggBasedExpression; } } internal bool IsJoinClauseLeftExpr { get { return _joinClauseLeftExpr; } set { _joinClauseLeftExpr = value; } } string[] IGetAlternativeName.AlternativeName { get { return _alternativeName; } } /// /// Prepend to the property chain. /// internal SourceScopeEntry AddParentVar(DbVariableReferenceExpression parentVarRef) { // // No parent var adjustment is allowed while adjusted to group var (see AdjustToGroupVar(...) for more info). // Debug.Assert(_groupVarBasedExpression == null, "_groupVarBasedExpression == null"); Debug.Assert(_groupAggBasedExpression == null, "_groupAggBasedExpression == null"); if (_propRefs == null) { Debug.Assert(_varBasedExpression is DbVariableReferenceExpression, "_varBasedExpression is DbVariableReferenceExpression"); _propRefs = new List(2); _propRefs.Add(((DbVariableReferenceExpression)_varBasedExpression).VariableName); } _varBasedExpression = parentVarRef; for (int i = _propRefs.Count - 1; i >= 0; --i) { _varBasedExpression = _varBasedExpression.Property(_propRefs[i]); } _propRefs.Add(parentVarRef.VariableName); return this; } /// /// Replace existing var at the head of the property chain with the new . /// internal void ReplaceParentVar(DbVariableReferenceExpression parentVarRef) { // // No parent var adjustment is allowed while adjusted to group var (see AdjustToGroupVar(...) for more info). // Debug.Assert(_groupVarBasedExpression == null, "_groupVarBasedExpression == null"); Debug.Assert(_groupAggBasedExpression == null, "_groupAggBasedExpression == null"); if (_propRefs == null) { Debug.Assert(_varBasedExpression is DbVariableReferenceExpression, "_varBasedExpression is DbVariableReferenceExpression"); _varBasedExpression = parentVarRef; } else { Debug.Assert(_propRefs.Count > 0, "_propRefs.Count > 0"); _propRefs.RemoveAt(_propRefs.Count - 1); AddParentVar(parentVarRef); } } /// /// Rebuild the current scope entry expression as the property chain off the expression. /// Also build /// - off the expression; /// - off the expression. /// This adjustment is reversable by (...). /// internal void AdjustToGroupVar(DbVariableReferenceExpression parentVarRef, DbVariableReferenceExpression parentGroupVarRef, DbVariableReferenceExpression groupAggRef) { // Adjustment is not reentrant. Debug.Assert(_groupVarBasedExpression == null, "_groupVarBasedExpression == null"); Debug.Assert(_groupAggBasedExpression == null, "_groupAggBasedExpression == null"); // // Let's assume this entry represents variable "x" in the following query: // select x, y, z from {1, 2} as x join {2, 3} as y on x = y join {3, 4} as z on y = z // In this case _propRefs contains x._##join0._##join1 and the corresponding input expression looks like this: // |_Input : '_##join1' // | |_InnerJoin // | |_Left : '_##join0' // | | |_InnerJoin // | | |_Left : 'x' // | | |_Right : 'y' // | |_Right : 'z' // When we start processing a group by, like in this query: // select k1, k2, k3 from {1, 2} as x join {2, 3} as y on x = y join {3, 4} as z on y = z group by x as k1, y as k2, z as k3 // we are switching to the following input expression: // |_Input : '_##geb2', '_##group3' // | |_InnerJoin // | |_Left : '_##join0' // | | |_InnerJoin // | | |_Left : 'x' // | | |_Right : 'y' // | |_Right : 'z' // where _##join1 is replaced by _##geb2 for the regular expression and by _##group3 for the group var based expression. // So the switch, or the adjustment, is done by // a. replacing _##join1 with _##geb2 in _propRefs and rebuilding the regular expression accordingly to get // the following property chain: _##geb2._##join1.x // b. building a group var based expression using _##group3 instead of _##geb2 to get // the following property chain: _##group3._##join1.x // // // Rebuild ScopeEntry.Expression using the new parent var. // ReplaceParentVar(parentVarRef); // // Build the GroupVarBasedExpression and GroupAggBasedExpression, // take into account that parentVarRef has already been added to the _propRefs in the AdjustToParentVar(...) call, so ignore it. // _groupVarBasedExpression = parentGroupVarRef; _groupAggBasedExpression = groupAggRef; if (_propRefs != null) { for (int i = _propRefs.Count - 2/*ignore the parentVarRef*/; i >= 0; --i) { _groupVarBasedExpression = _groupVarBasedExpression.Property(_propRefs[i]); _groupAggBasedExpression = _groupAggBasedExpression.Property(_propRefs[i]); } } } /// /// Rolls back the (...) adjustment, clears the . /// internal void RollbackAdjustmentToGroupVar(DbVariableReferenceExpression pregroupParentVarRef) { Debug.Assert(_groupVarBasedExpression != null, "_groupVarBasedExpression != null"); _groupVarBasedExpression = null; _groupAggBasedExpression = null; ReplaceParentVar(pregroupParentVarRef); } } /// /// Represents a group input scope entry that should no longer be referenced. /// internal sealed class InvalidGroupInputRefScopeEntry : ScopeEntry { internal InvalidGroupInputRefScopeEntry() : base(ScopeEntryKind.InvalidGroupInputRef) { } internal override DbExpression GetExpression(string refName, ErrorContext errCtx) { throw EntityUtil.EntitySqlError(errCtx, Strings.InvalidGroupIdentifierReference(refName)); } } /// /// Represents group key during GROUP BY clause processing phase, used during group aggregate search mode. /// This entry will be replaced by the when GROUP BY processing is complete. /// internal sealed class GroupKeyDefinitionScopeEntry : ScopeEntry, IGroupExpressionExtendedInfo, IGetAlternativeName { private readonly DbExpression _varBasedExpression; private readonly DbExpression _groupVarBasedExpression; private readonly DbExpression _groupAggBasedExpression; private readonly string[] _alternativeName; internal GroupKeyDefinitionScopeEntry( DbExpression varBasedExpression, DbExpression groupVarBasedExpression, DbExpression groupAggBasedExpression, string[] alternativeName) : base(ScopeEntryKind.GroupKeyDefinition) { _varBasedExpression = varBasedExpression; _groupVarBasedExpression = groupVarBasedExpression; _groupAggBasedExpression = groupAggBasedExpression; _alternativeName = alternativeName; } internal override DbExpression GetExpression(string refName, ErrorContext errCtx) { return _varBasedExpression; } DbExpression IGroupExpressionExtendedInfo.GroupVarBasedExpression { get { return _groupVarBasedExpression; } } DbExpression IGroupExpressionExtendedInfo.GroupAggBasedExpression { get { return _groupAggBasedExpression; } } string[] IGetAlternativeName.AlternativeName { get { return _alternativeName; } } } /// /// Represents a projection item definition scope entry. /// internal sealed class ProjectionItemDefinitionScopeEntry : ScopeEntry { private readonly DbExpression _expression; internal ProjectionItemDefinitionScopeEntry(DbExpression expression) : base(ScopeEntryKind.ProjectionItemDefinition) { _expression = expression; } internal override DbExpression GetExpression(string refName, ErrorContext errCtx) { return _expression; } } /// /// Represents a free variable scope entry. /// Example: parameters of an inline function definition are free variables in the scope of the function definition. /// internal sealed class FreeVariableScopeEntry : ScopeEntry { private readonly DbVariableReferenceExpression _varRef; internal FreeVariableScopeEntry(DbVariableReferenceExpression varRef) : base(ScopeEntryKind.FreeVar) { _varRef = varRef; } internal override DbExpression GetExpression(string refName, ErrorContext errCtx) { return _varRef; } } /// /// Represents a generic list of scopes. /// internal sealed class ScopeManager { private readonly IEqualityComparer _keyComparer; private readonly List _scopes = new List(); /// /// Initialize scope manager using given key-string comparer. /// internal ScopeManager(IEqualityComparer keyComparer) { _keyComparer = keyComparer; } /// /// Enter a new scope. /// internal void EnterScope() { _scopes.Add(new Scope(_keyComparer)); } /// /// Leave the current scope. /// internal void LeaveScope() { Debug.Assert(CurrentScopeIndex >= 0); _scopes.RemoveAt(CurrentScopeIndex); } /// /// Return current scope index. /// Outer scopes have smaller index values than inner scopes. /// internal int CurrentScopeIndex { get { return _scopes.Count - 1; } } /// /// Return current scope. /// internal Scope CurrentScope { get { return _scopes[CurrentScopeIndex]; } } /// /// Get a scope by the index. /// internal Scope GetScopeByIndex(int scopeIndex) { Debug.Assert(scopeIndex >= 0, "scopeIndex >= 0"); Debug.Assert(scopeIndex <= CurrentScopeIndex, "scopeIndex <= CurrentScopeIndex"); if (0 > scopeIndex || scopeIndex > CurrentScopeIndex) { throw EntityUtil.EntitySqlError(Strings.InvalidScopeIndex); } return _scopes[scopeIndex]; } /// /// Rollback all scopes to the scope at the index. /// internal void RollbackToScope(int scopeIndex) { // // assert preconditions // Debug.Assert(scopeIndex >= 0, "[PRE] savePoint.ScopeIndex >= 0"); Debug.Assert(scopeIndex <= CurrentScopeIndex, "[PRE] savePoint.ScopeIndex <= CurrentScopeIndex"); Debug.Assert(CurrentScopeIndex >= 0, "[PRE] CurrentScopeIndex >= 0"); if (scopeIndex > CurrentScopeIndex || scopeIndex < 0 || CurrentScopeIndex < 0) { throw EntityUtil.EntitySqlError(Strings.InvalidSavePoint); } int delta = CurrentScopeIndex - scopeIndex; if (delta > 0) { _scopes.RemoveRange(scopeIndex + 1, CurrentScopeIndex - scopeIndex); } // // make sure invariants are preserved // Debug.Assert(scopeIndex == CurrentScopeIndex, "[POST] savePoint.ScopeIndex == CurrentScopeIndex"); Debug.Assert(CurrentScopeIndex >= 0, "[POST] CurrentScopeIndex >= 0"); } /// /// True if key exists in current scope. /// internal bool IsInCurrentScope(string key) { return CurrentScope.Contains(key); } } }