|
//---------------------------------------------------------------------
// <copyright file="ITreeGenerator.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
//using System.Diagnostics; // Please use PlanCompiler.Assert instead of Debug.Assert in this class...
// It is fine to use Debug.Assert in cases where you assert an obvious thing that is supposed
// to prevent from simple mistakes during development (e.g. method argument validation
// in cases where it was you who created the variables or the variables had already been validated or
// in "else" clauses where due to code changes (e.g. adding a new value to an enum type) the default
// "else" block is chosen why the new condition should be treated separately). This kind of asserts are
// (can be) helpful when developing new code to avoid simple mistakes but have no or little value in
// the shipped product.
// PlanCompiler.Assert *MUST* be used to verify conditions in the trees. These would be assumptions
// about how the tree was built etc. - in these cases we probably want to throw an exception (this is
// what PlanCompiler.Assert does when the condition is not met) if either the assumption is not correct
// or the tree was built/rewritten not the way we thought it was.
// Use your judgment - if you rather remove an assert than ship it use Debug.Assert otherwise use
// PlanCompiler.Assert.
namespace System.Data.Query.PlanCompiler
{
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.Common.CommandTrees;
using System.Data.Common.CommandTrees.ExpressionBuilder;
using System.Data.Common.Utils;
using System.Data.Entity;
using System.Data.Entity.Util;
using System.Data.Metadata.Edm;
using System.Data.Query.InternalTrees;
using System.Linq;
internal class ITreeGenerator : DbExpressionVisitor<Node>
{
#region Nested Types
/// <summary>
/// Abstract base class for both DbExpressionBinding and LambdaFunction scopes
/// </summary>
private abstract class CqtVariableScope
{
internal abstract bool Contains(string varName);
internal abstract Node this[string varName] { get; }
/// <summary>
/// Returns true if it is a lambda variable representing a predicate expression.
/// </summary>
internal abstract bool IsPredicate(string varName);
}
/// <summary>
/// Represents a variable scope introduced by a CQT DbExpressionBinding, and therefore contains a single variable.
/// </summary>
private class ExpressionBindingScope : CqtVariableScope
{
private Command _tree;
private string _varName;
private Var _var;
internal ExpressionBindingScope(Command iqtTree, string name, Var iqtVar)
{
_tree = iqtTree;
_varName = name;
_var = iqtVar;
}
internal override bool Contains(string name) { return (_varName == name); }
internal override Node this[string name]
{
get
{
PlanCompiler.Assert(name == _varName,"huh?");
return _tree.CreateNode(_tree.CreateVarRefOp(_var));
}
}
internal override bool IsPredicate(string varName)
{
return false;
}
internal Var ScopeVar { get { return _var; } }
}
/// <summary>
/// Represents a variable scope introduced by a LambdaFunction.
/// </summary>
private sealed class LambdaScope : CqtVariableScope
{
private readonly ITreeGenerator _treeGen;
private readonly Command _command;
/// <summary>
/// varName : [node, IsPredicate]
/// </summary>
private readonly Dictionary<string, Tuple<Node, bool>> _arguments;
private readonly Dictionary<Node, bool> _referencedArgs;
internal LambdaScope(ITreeGenerator treeGen, Command command, Dictionary<string, Tuple<Node, bool>> args)
{
_treeGen = treeGen;
_command = command;
_arguments = args;
_referencedArgs = new Dictionary<Node, bool>(_arguments.Count);
}
internal override bool Contains(string name) { return (_arguments.ContainsKey(name)); }
internal override Node this[string name]
{
get
{
PlanCompiler.Assert(_arguments.ContainsKey(name), "LambdaScope indexer called for invalid Var");
Node argNode = _arguments[name].Item1;
if (_referencedArgs.ContainsKey(argNode))
{
// The specified argument has already been substituted into the
// IQT and so this substitution requires a copy of the argument.
VarMap mappedVars = null;
// This is a 'deep copy' operation that clones the entire subtree rooted at the node.
Node argCopy = OpCopier.Copy(_command, argNode, out mappedVars);
// If any Nodes in the copy of the argument produce Vars then the
// Node --> Var map must be updated to include them.
if (mappedVars.Count > 0)
{
List<Node> sources = new List<Node>(1);
sources.Add(argNode);
List<Node> copies = new List<Node>(1);
copies.Add(argCopy);
MapCopiedNodeVars(sources, copies, mappedVars);
}
argNode = argCopy;
}
else
{
// This is the first reference of the lambda argument, so the Node itself
// can be returned rather than a copy, but the dictionary that tracks
// whether or not an argument has been referenced needs to be updated.
_referencedArgs[argNode] = true;
}
return argNode;
}
}
internal override bool IsPredicate(string name)
{
PlanCompiler.Assert(_arguments.ContainsKey(name), "LambdaScope indexer called for invalid Var");
return _arguments[name].Item2;
}
private void MapCopiedNodeVars(IList<Node> sources, IList<Node> copies, Dictionary<Var, Var> varMappings)
{
PlanCompiler.Assert(sources.Count == copies.Count, "Source/Copy Node count mismatch");
//
// For each Source/Copy Node in the two lists:
// - Recursively update the Node --> Var map for any child nodes
// - If the Source Node is mapped to a Var, then retrieve the new Var
// produced by the Op copier that corresponds to that Source Var, and
// add an entry to the Node --> Var map that maps the Copy Node to the
// new Var.
//
for (int idx = 0; idx < sources.Count; idx++)
{
Node sourceNode = sources[idx];
Node copyNode = copies[idx];
if (sourceNode.Children.Count > 0)
{
MapCopiedNodeVars(sourceNode.Children, copyNode.Children, varMappings);
}
Var sourceVar = null;
if (_treeGen.VarMap.TryGetValue(sourceNode, out sourceVar))
{
PlanCompiler.Assert(varMappings.ContainsKey(sourceVar), "No mapping found for Var in Var to Var map from OpCopier");
this._treeGen.VarMap[copyNode] = varMappings[sourceVar];
}
}
}
}
#endregion
private static Dictionary<DbExpressionKind, OpType> s_opMap = InitializeExpressionKindToOpTypeMap();
private readonly Command _iqtCommand;
private readonly Stack<CqtVariableScope> _varScopes = new Stack<CqtVariableScope>();
private readonly Dictionary<Node, Var> _varMap = new Dictionary<Node, Var>();
private readonly Stack<EdmFunction> _functionExpansions = new Stack<EdmFunction>();
/// <summary>
/// Maintained for lambda and model-defined function applications (DbLambdaExpression and DbFunctionExpression).
/// </summary>
private readonly Dictionary<DbExpression, bool> _functionsIsPredicateFlag = new Dictionary<DbExpression, bool>();
// Used to track which IsOf type filter expressions have already been processed
private readonly HashSet<DbFilterExpression> _processedIsOfFilters = new HashSet<DbFilterExpression>();
private readonly HashSet<DbTreatExpression> _fakeTreats = new HashSet<DbTreatExpression>();
// leverage discriminator metadata in the top-level project when translating query mapping views...
private readonly System.Data.Mapping.ViewGeneration.DiscriminatorMap _discriminatorMap;
private readonly DbProjectExpression _discriminatedViewTopProject;
/// <summary>
/// Initialize the DbExpressionKind --> OpType mappings for DbComparisonExpression and DbArithmeticExpression
/// </summary>
private static Dictionary<DbExpressionKind, OpType> InitializeExpressionKindToOpTypeMap()
{
Dictionary<DbExpressionKind, OpType> opMap = new Dictionary<DbExpressionKind, OpType>(12);
//
// Arithmetic operators
//
opMap[DbExpressionKind.Plus] = OpType.Plus;
opMap[DbExpressionKind.Minus] = OpType.Minus;
opMap[DbExpressionKind.Multiply] = OpType.Multiply;
opMap[DbExpressionKind.Divide] = OpType.Divide;
opMap[DbExpressionKind.Modulo] = OpType.Modulo;
opMap[DbExpressionKind.UnaryMinus] = OpType.UnaryMinus;
//
// Comparison operators
//
opMap[DbExpressionKind.Equals] = OpType.EQ;
opMap[DbExpressionKind.NotEquals] = OpType.NE;
opMap[DbExpressionKind.LessThan] = OpType.LT;
opMap[DbExpressionKind.GreaterThan] = OpType.GT;
opMap[DbExpressionKind.LessThanOrEquals] = OpType.LE;
opMap[DbExpressionKind.GreaterThanOrEquals] = OpType.GE;
return opMap;
}
internal Dictionary<Node, Var> VarMap { get { return _varMap; } }
public static Command Generate(DbQueryCommandTree ctree)
{
return Generate(ctree, null);
}
/// <summary>
/// Generate an IQT given a query command tree and discriminator metadata (available for certain query mapping views)
/// </summary>
internal static Command Generate(DbQueryCommandTree ctree, System.Data.Mapping.ViewGeneration.DiscriminatorMap discriminatorMap)
{
ITreeGenerator treeGenerator = new ITreeGenerator(ctree, discriminatorMap);
return treeGenerator._iqtCommand;
}
private ITreeGenerator(DbQueryCommandTree ctree, System.Data.Mapping.ViewGeneration.DiscriminatorMap discriminatorMap)
{
//
// Create a new IQT Command instance that uses the same metadata workspace as the incoming command tree
//
_iqtCommand = new Command(ctree.MetadataWorkspace);
//
// When translating a query mapping view matching the TPH discrimination pattern, remember the top level discriminator map
// (leveraged to produced a DiscriminatedNewInstanceOp for the top-level projection in the view)
//
if (null != discriminatorMap)
{
_discriminatorMap = discriminatorMap;
// see System.Data.Mapping.ViewGeneration.DiscriminatorMap
PlanCompiler.Assert(ctree.Query.ExpressionKind == DbExpressionKind.Project,
"top level QMV expression must be project to match discriminator pattern");
_discriminatedViewTopProject = (DbProjectExpression)ctree.Query;
}
//
// For each Parameter declared by the command tree, add a ParameterVar to the set of parameter vars maintained by the conversion visitor.
// Each ParameterVar has the same name and type as the corresponding parameter on the command tree.
//
foreach (KeyValuePair<string, TypeUsage> paramInfo in ctree.Parameters)
{
if (!ValidateParameterType(paramInfo.Value))
{
throw EntityUtil.NotSupported(System.Data.Entity.Strings.ParameterTypeNotSupported(paramInfo.Key, paramInfo.Value.ToString()));
}
_iqtCommand.CreateParameterVar(paramInfo.Key, paramInfo.Value);
}
// Convert into an ITree
_iqtCommand.Root = VisitExpr(ctree.Query);
//
// If the root of the tree is not a relop, build up a fake project over a
// a singlerowtableOp.
// "s" => Project(SingleRowTableOp, "s")
//
if (!_iqtCommand.Root.Op.IsRelOp)
{
Node scalarExpr = ConvertToScalarOpTree(_iqtCommand.Root, ctree.Query);
Node singletonTableNode = _iqtCommand.CreateNode(_iqtCommand.CreateSingleRowTableOp());
Var newVar;
Node varDefListNode = _iqtCommand.CreateVarDefListNode(scalarExpr, out newVar);
ProjectOp projectOp = _iqtCommand.CreateProjectOp(newVar);
Node newRoot = _iqtCommand.CreateNode(projectOp, singletonTableNode, varDefListNode);
if (TypeSemantics.IsCollectionType(_iqtCommand.Root.Op.Type))
{
UnnestOp unnestOp = _iqtCommand.CreateUnnestOp(newVar);
newRoot = _iqtCommand.CreateNode(unnestOp, varDefListNode.Child0);
newVar = unnestOp.Table.Columns[0];
}
_iqtCommand.Root = newRoot;
_varMap[_iqtCommand.Root] = newVar;
}
//
// Ensure that the topmost portion of the query is capped by a
// PhysicalProject expression
//
_iqtCommand.Root = CapWithPhysicalProject(_iqtCommand.Root);
}
private static bool ValidateParameterType(TypeUsage paramType)
{
return (paramType != null && paramType.EdmType != null &&
(TypeSemantics.IsPrimitiveType(paramType) || paramType.EdmType is EnumType));
}
#region DbExpressionVisitor Helpers
private static RowType ExtractElementRowType(TypeUsage typeUsage)
{
return TypeHelpers.GetEdmType<RowType>(TypeHelpers.GetEdmType<CollectionType>(typeUsage).TypeUsage);
}
#if DEBUG
private static bool IsCollectionOfRecord(TypeUsage typeUsage)
{
CollectionType collectionType;
return (TypeHelpers.TryGetEdmType<CollectionType>(typeUsage, out collectionType) &&
collectionType != null &&
TypeSemantics.IsRowType(collectionType.TypeUsage));
}
#endif
/// <summary>
/// Is the current expression a predicate?
/// </summary>
/// <param name="expr">expr to check</param>
/// <returns>true, if the expression is a predicate</returns>
private bool IsPredicate(DbExpression expr)
{
if (TypeSemantics.IsPrimitiveType(expr.ResultType, PrimitiveTypeKind.Boolean))
{
switch (expr.ExpressionKind)
{
case DbExpressionKind.Equals:
case DbExpressionKind.NotEquals:
case DbExpressionKind.LessThan:
case DbExpressionKind.LessThanOrEquals:
case DbExpressionKind.GreaterThan:
case DbExpressionKind.GreaterThanOrEquals:
case DbExpressionKind.And:
case DbExpressionKind.Or:
case DbExpressionKind.Not:
case DbExpressionKind.Like:
case DbExpressionKind.IsEmpty:
case DbExpressionKind.IsNull:
case DbExpressionKind.IsOf:
case DbExpressionKind.IsOfOnly:
case DbExpressionKind.Any:
case DbExpressionKind.All:
return true;
case DbExpressionKind.VariableReference:
var varRef = (DbVariableReferenceExpression)expr;
return ResolveScope(varRef).IsPredicate(varRef.VariableName);
case DbExpressionKind.Lambda:
{
//
bool isPredicateFunction;
if (_functionsIsPredicateFlag.TryGetValue(expr, out isPredicateFunction))
{
return isPredicateFunction;
}
else
{
// It is important that IsPredicate is called after the expression has been visited, otherwise
// _functionsIsPredicateFlag map will not contain an entry for the lambda
PlanCompiler.Assert(false, "IsPredicate must be called on a visited lambda expression");
return false;
}
}
case DbExpressionKind.Function:
{
//
EdmFunction edmFunction = ((DbFunctionExpression)expr).Function;
if (edmFunction.HasUserDefinedBody)
{
bool isPredicateFunction;
if (_functionsIsPredicateFlag.TryGetValue(expr, out isPredicateFunction))
{
return isPredicateFunction;
}
else
{
// It is important that IsPredicate is called after the expression has been visited, otherwise
// _functionsIsPredicateFlag map will not contain an entry for the function with a definition
PlanCompiler.Assert(false, "IsPredicate must be called on a visited function expression");
return false;
}
}
else
{
return false;
}
}
default:
return false;
}
}
else
{
return false;
}
}
/// <summary>
/// Callback to process an expression
/// </summary>
/// <param name="e">The expression to convert</param>
/// <returns></returns>
private delegate Node VisitExprDelegate(DbExpression e);
private Node VisitExpr(DbExpression e)
{
if (e == null)
{
return null;
}
else
{
return e.Accept<Node>(this);
}
}
/// <summary>
/// Convert this expression into a "scalar value" ITree expression. There are two main
/// </summary>
/// <param name="expr"></param>
/// <returns></returns>
private Node VisitExprAsScalar(DbExpression expr)
{
if (expr == null)
{
return null;
}
Node node = VisitExpr(expr); // the real work
node = ConvertToScalarOpTree(node, expr);
return node;
}
/// <summary>
/// Convert an Itree node into a scalar op tree
/// </summary>
/// <param name="node">the subtree</param>
/// <param name="expr">the original CQT expression</param>
/// <returns>the converted subtree</returns>
private Node ConvertToScalarOpTree(Node node, DbExpression expr)
{
//
// If the current expression is a collection, and we've simply produced a RelOp
// then we need to add a CollectOp above a PhysicalProjectOp above the RelOp
//
if (node.Op.IsRelOp)
{
node = ConvertRelOpToScalarOpTree(node, expr.ResultType);
}
//
// If the current expression is a boolean, and it is really a predicate, then
// scalarize the predicate (ie) convert it into a "case when <predicate> then 'true' else 'false' end" expression
// SQLBUDT #431406: handle 3-valued logic for all predicates except IsNull
// Convert boolean predicate p into
// case when p then true when not(p) then false else null end
//
else if (IsPredicate(expr))
{
node = ConvertPredicateToScalarOpTree(node, expr);
}
return node;
}
/// <summary>
/// Convert a rel op Itree node into a scalar op tree
/// </summary>
/// <param name="node"></param>
/// <param name="resultType"></param>
/// <returns></returns>
private Node ConvertRelOpToScalarOpTree(Node node, TypeUsage resultType)
{
PlanCompiler.Assert(TypeSemantics.IsCollectionType(resultType), "RelOp with non-Collection result type");
CollectOp collectOp = _iqtCommand.CreateCollectOp(resultType);
//
// I'm not thrilled about having to build a PhysicalProjectOp here - this
// is definitely something I will need to revisit soon
//
Node projectNode = CapWithPhysicalProject(node);
node = _iqtCommand.CreateNode(collectOp, projectNode);
return node;
}
/// <summary>
/// Scalarize the predicate (x = y) by converting it into a "case when x = y then 'true' else 'false' end" expression.
/// </summary>
private Node ConvertPredicateToScalarOpTree(Node node, DbExpression expr)
{
CaseOp caseOp = _iqtCommand.CreateCaseOp(_iqtCommand.BooleanType);
//For 2-valued logic there are 3 arguments, for 3-valued there are 5
List<Node> arguments = new List<Node>((expr.ExpressionKind == DbExpressionKind.IsNull) ? 3 : 5);
//Add the original as the first when
arguments.Add(node);
//Add the first then, the true node
arguments.Add(_iqtCommand.CreateNode(_iqtCommand.CreateInternalConstantOp(_iqtCommand.BooleanType, true)));
//If the expression has 3-valued logic, add a second when
if (expr.ExpressionKind != DbExpressionKind.IsNull)
{
Node predCopy = VisitExpr(expr);
arguments.Add(_iqtCommand.CreateNode(_iqtCommand.CreateConditionalOp(OpType.Not), predCopy));
}
//Add the false node: for 3 valued logic this is the second then, for 2 valued the else
arguments.Add(_iqtCommand.CreateNode(_iqtCommand.CreateInternalConstantOp(_iqtCommand.BooleanType, false)));
//The null node, it is the else-clause for 3-valued logic
if (expr.ExpressionKind != DbExpressionKind.IsNull)
{
arguments.Add(_iqtCommand.CreateNode(_iqtCommand.CreateNullOp(_iqtCommand.BooleanType)));
}
node = _iqtCommand.CreateNode(caseOp, arguments);
return node;
}
/// <summary>
/// Convert an expression into an iqt predicate
/// </summary>
/// <param name="expr">the expression to process</param>
/// <returns></returns>
private Node VisitExprAsPredicate(DbExpression expr)
{
if (expr == null)
{
return null;
}
Node node = VisitExpr(expr);
//
// If the current expression is not a predicate, then we need to make it one, by
// comparing it with the constant 'true'
//
if (!IsPredicate(expr))
{
ComparisonOp comparisonOp = _iqtCommand.CreateComparisonOp(OpType.EQ);
Node trueNode = _iqtCommand.CreateNode(_iqtCommand.CreateInternalConstantOp(_iqtCommand.BooleanType, true));
node = _iqtCommand.CreateNode(comparisonOp, node, trueNode);
}
else
{
PlanCompiler.Assert(!node.Op.IsRelOp, "unexpected relOp as predicate?");
}
return node;
}
/// <summary>
/// Process a list of expressions, and apply the delegate to each of the expressions
/// </summary>
/// <param name="exprs">list of cqt expressions to process</param>
/// <param name="exprDelegate">the callback to apply</param>
/// <returns>a list of IQT expressions</returns>
private static IList<Node> VisitExpr(IList<DbExpression> exprs, VisitExprDelegate exprDelegate)
{
List<Node> nodeList = new List<Node>();
for(int idx = 0; idx < exprs.Count; idx++)
{
nodeList.Add(exprDelegate(exprs[idx]));
}
return nodeList;
}
/// <summary>
/// Process a set of cqt expressions - and convert them into scalar iqt expressions
/// </summary>
/// <param name="exprs">list of cqt expressions</param>
/// <returns>list of iqt expressions</returns>
private IList<Node> VisitExprAsScalar(IList<DbExpression> exprs)
{
return VisitExpr(exprs, VisitExprAsScalar);
}
private Node VisitUnary(DbUnaryExpression e, Op op, VisitExprDelegate exprDelegate)
{
return _iqtCommand.CreateNode(op, exprDelegate(e.Argument));
}
private Node VisitBinary(DbBinaryExpression e, Op op, VisitExprDelegate exprDelegate)
{
return _iqtCommand.CreateNode(op, exprDelegate(e.Left), exprDelegate(e.Right));
}
/// <summary>
/// Ensures that an input op is a RelOp. If the specified Node's Op is not a RelOp then it is wrapped in an Unnest to create a synthetic RelOp. This is only possible if the input Op produces a collection.
/// </summary>
/// <param name="inputNode">The input Node/Op pair</param>
/// <returns>A Node with an Op that is guaranteed to be a RelOp (this may be the original Node or a new Node created to perform the Unnest)</returns>
private Node EnsureRelOp(Node inputNode)
{
//
// Input node = N1
//
Op inputOp = inputNode.Op;
//
// If the Op is already a RelOp then simply return its Node
//
if (inputOp.IsRelOp)
{
return inputNode;
}
//
// Assert that the input is a ScalarOp (CQT expressions should only ever produce RelOps or ScalarOps)
//
ScalarOp scalar = inputOp as ScalarOp;
PlanCompiler.Assert(scalar != null, "An expression in a CQT produced a non-ScalarOp and non-RelOp output Op");
//
// Assert that the ScalarOp has a collection result type. EnsureRelOp is called to ensure that arguments to
// RelOps are either also RelOps or are ScalarOps that produce a collection, which can be wrapped in an
// unnest to produce a RelOp.
//
PlanCompiler.Assert(TypeSemantics.IsCollectionType(scalar.Type), "An expression used as a RelOp argument was neither a RelOp or a collection");
//
// If the ScalarOp represents the nesting of an existing RelOp, simply return that RelOp instead.
// CollectOp(PhysicalProjectOp(x)) => x
//
CollectOp collect = inputOp as CollectOp;
if (collect != null)
{
PlanCompiler.Assert(inputNode.HasChild0, "CollectOp without argument");
if (inputNode.Child0.Op as PhysicalProjectOp != null)
{
PlanCompiler.Assert(inputNode.Child0.HasChild0, "PhysicalProjectOp without argument");
PlanCompiler.Assert(inputNode.Child0.Child0.Op.IsRelOp, "PhysicalProjectOp applied to non-RelOp input");
//
// The structure of the Input is Collect(PhysicalProject(x)), so return x
//
return inputNode.Child0.Child0;
}
}
//
// Create a new VarDefOp that defines the computed var that represents the ScalarOp collection.
// This var is the input to the UnnestOp.
// varDefNode = N2
//
Var inputCollectionVar;
Node varDefNode = _iqtCommand.CreateVarDefNode(inputNode, out inputCollectionVar);
//
// Create an UnnestOp that references the computed var created above. The VarDefOp that defines the var
// using the original input Node/Op pair becomes a child of the UnnestOp.
//
UnnestOp unnest = _iqtCommand.CreateUnnestOp(inputCollectionVar);
PlanCompiler.Assert(unnest.Table.Columns.Count == 1, "Unnest of collection ScalarOp produced unexpected number of columns (1 expected)");
//
// Create the unnest node, N3
// The UnnestOp produces a new Var, the single ColumnVar produced by the table that results from the Unnest.
//
Node unnestNode = _iqtCommand.CreateNode(unnest, varDefNode);
_varMap[unnestNode] = unnest.Table.Columns[0];
//
// Create a Project node above the Unnest, so we can simplify the work to eliminate
// the Unnest later. That means we need to create a VarRef to the column var in the
// table, a VarDef to define it, and a VarDefList to hold it, then a Project node, N4,
// which we return.
//
Var projectVar;
Node varRefNode = _iqtCommand.CreateNode(_iqtCommand.CreateVarRefOp(unnest.Table.Columns[0]));
Node varDefListNode = _iqtCommand.CreateVarDefListNode(varRefNode, out projectVar);
ProjectOp projectOp = _iqtCommand.CreateProjectOp(projectVar);
Node projectNode = _iqtCommand.CreateNode(projectOp, unnestNode, varDefListNode);
_varMap[projectNode] = projectVar;
return projectNode;
}
/// <summary>
/// Cap a RelOp with a ProjectOp. The output var of the Project is the
/// output var from the input
/// </summary>
/// <param name="input">the input relop tree</param>
/// <returns>the relop tree with a projectNode at the root</returns>
private Node CapWithProject(Node input)
{
PlanCompiler.Assert(input.Op.IsRelOp, "unexpected non-RelOp?");
if (input.Op.OpType == OpType.Project)
{
return input;
}
// Get the Var from the input; and build up a Project above it
Var inputVar = _varMap[input];
ProjectOp projectOp = _iqtCommand.CreateProjectOp(inputVar);
Node projectNode = _iqtCommand.CreateNode(projectOp, input,
_iqtCommand.CreateNode(_iqtCommand.CreateVarDefListOp()));
_varMap[projectNode] = inputVar;
return projectNode;
}
/// <summary>
/// Cap a relop tree with a PhysicalProjectOp. The Vars of the PhysicalProjectOp
/// are the vars from the RelOp tree
/// </summary>
/// <param name="input">the input relop tree</param>
/// <returns>relop tree capped by a PhysicalProjectOp</returns>
private Node CapWithPhysicalProject(Node input)
{
PlanCompiler.Assert(input.Op.IsRelOp, "unexpected non-RelOp?");
// Get the Var from the input; and build up a Project above it
Var inputVar = _varMap[input];
PhysicalProjectOp projectOp = _iqtCommand.CreatePhysicalProjectOp(inputVar);
Node projectNode = _iqtCommand.CreateNode(projectOp, input);
return projectNode;
}
/// <summary>
/// Creates a new variable scope that is based on a CQT DbExpressionBinding and pushes it onto the variable scope stack. The scope defines a single variable based on the DbExpressionBinding's VarName and DbExpression.
/// </summary>
/// <param name="binding">The DbExpressionBinding that defines the scope</param>
/// <returns>The Node produced by converting the binding's DbExpression</returns>
private Node EnterExpressionBinding(DbExpressionBinding binding)
{
return VisitBoundExpressionPushBindingScope(binding.Expression, binding.VariableName);
}
/// <summary>
/// Creates a new variable scope that is based on a CQT DbGroupExpressionBinding and pushes it onto the variable scope stack. The scope defines a single variable based on the DbExpressionBinding's VarName and DbExpression.
/// This method does not bring the GroupVarName into scope. Note that ExitExpressionBinding and NOT ExitGroupExpressionBinding should be used to remove this scope from the stack.
/// </summary>
/// <param name="binding">The DbGroupExpressionBinding that defines the scope</param>
/// <returns>The Node produced by converting the binding's DbExpression</returns>
private Node EnterGroupExpressionBinding(DbGroupExpressionBinding binding)
{
return VisitBoundExpressionPushBindingScope(binding.Expression, binding.VariableName);
}
/// <summary>
/// Common implementation method called by both EnterExpressionBinding and EnterGroupExpressionBinding
/// </summary>
/// <param name="boundExpression">The DbExpression that defines the binding</param>
/// <param name="bindingName">The name of the binding variable</param>
/// <returns></returns>
private Node VisitBoundExpressionPushBindingScope(DbExpression boundExpression, string bindingName)
{
Var boundVar;
Node inputNode = VisitBoundExpression(boundExpression, out boundVar);
PushBindingScope(boundVar, bindingName);
return inputNode;
}
/// <summary>
/// Common implementation method called by both VisitBoundExpressionPushBindingScope and VisitJoin
/// </summary>
/// <param name="boundExpression">The DbExpression that defines the binding</param>
/// <param name="boundVar">Var representing the RelOp produced for the <paramref name="boundExpression"/></param>
/// <returns></returns>
private Node VisitBoundExpression(DbExpression boundExpression, out Var boundVar)
{
//
// Visit the expression binding's DbExpression to convert it to a Node/Op pair
//
Node inputNode = VisitExpr(boundExpression);
PlanCompiler.Assert(inputNode != null, "DbExpressionBinding.Expression produced null conversion");
//
// Call EnsureRelOp on the converted Node and set inputNode equal to the result
//
inputNode = EnsureRelOp(inputNode);
//
// Retrieve the Var produced by the RelOp from the Node --> Var map
//
boundVar = _varMap[inputNode];
PlanCompiler.Assert(boundVar != null, "No Var found for Input Op");
return inputNode;
}
/// <summary>
/// Common implementation method called by both VisitBoundExpressionPushBindingScope and VisitJoin
/// </summary>
/// <param name="boundVar">The Var produced by the RelOp from DbExpression that defines the binding</param>
/// <param name="bindingName">The name of the binding variable</param>
/// <returns></returns>
private void PushBindingScope(Var boundVar, string bindingName)
{
//
// Create a new ExpressionBindingScope using the VarName from the DbExpressionBinding and
// the Var associated with the Input RelOp, and push the new scope onto the variable scope stack.
//
_varScopes.Push(new ExpressionBindingScope(_iqtCommand, bindingName, boundVar));
}
/// <summary>
/// Removes a variable scope created based on a DbExpressionBinding from the top of the variable scope stack, verifying that it is in fact an ExpressionBindingScope.
/// </summary>
/// <returns>The removed ExpressionBindingScope</returns>
private ExpressionBindingScope ExitExpressionBinding()
{
//
// Pop the scope from the variable scope stack, assert that it is a DbExpressionBinding scope, and return it.
//
ExpressionBindingScope retScope = _varScopes.Pop() as ExpressionBindingScope;
PlanCompiler.Assert(retScope != null, "ExitExpressionBinding called without ExpressionBindingScope on top of scope stack");
return retScope;
}
/// <summary>
/// Removes a variable scope created based on a DbGroupExpressionBinding from the top of the variable scope stack, verifying that it is in fact an ExpressionBindingScope.
/// Should only be called after visiting the Aggregates of a DbGroupByExpression in Visit(DbGroupByExpression).
/// The sequence (in Visit(GroupExpression e) is:
/// 1. EnterGroupExpressionBinding
/// 2. Visit e.Keys
/// 3. ExitExpressionBinding
/// 4. (Push new scope with GroupVarName instead of VarName)
/// 5. Visit e.Aggregates
/// 6. ExitGroupExpressionBinding
/// </summary>
private void ExitGroupExpressionBinding()
{
ExpressionBindingScope retScope = _varScopes.Pop() as ExpressionBindingScope;
PlanCompiler.Assert(retScope != null, "ExitGroupExpressionBinding called without ExpressionBindingScope on top of scope stack");
}
/// <summary>
/// Creates a new variable scope that is based on a CQT DbLambda and pushes it onto the variable scope stack.
/// </summary>
/// <param name="function">The DbLambda that defines the scope</param>
/// <param name="argumentValues">A list of Nodes and IsPredicate bits produced by converting the CQT Expressions that provide the arguments to the Lambda function</param>
/// <param name="expandingEdmFunction">an edm function for which the current lambda represents the generated body, otherwise null</param>
private void EnterLambdaFunction(DbLambda lambda, List<Tuple<Node, bool>> argumentValues, EdmFunction expandingEdmFunction)
{
IList<DbVariableReferenceExpression> lambdaParams = lambda.Variables;
var args = new Dictionary<string, Tuple<Node, bool>>();
int idx = 0;
foreach (var argumentValue in argumentValues)
{
args.Add(lambdaParams[idx].VariableName, argumentValue);
idx++;
}
//
// If lambda represents an edm function body then check for a possible recursion in the function definition.
//
if (expandingEdmFunction != null)
{
//
// Check if we are already inside the function body.
//
if (_functionExpansions.Contains(expandingEdmFunction))
{
throw EntityUtil.CommandCompilation(Strings.Cqt_UDF_FunctionDefinitionWithCircularReference(expandingEdmFunction.FullName), null);
}
//
// Push the function before processing its body
//
_functionExpansions.Push(expandingEdmFunction);
}
_varScopes.Push(new LambdaScope(this, _iqtCommand, args));
}
/// <summary>
/// Removes a variable scope created based on a Lambda function from the top of the variable scope stack, verifying that it is in fact a LambdaScope.
/// </summary>
/// <param name="expandingEdmFunction">an edm function for which the current lambda represents the generated body, otherwise null</param>
private LambdaScope ExitLambdaFunction(EdmFunction expandingEdmFunction)
{
//
// Pop the scope from the variable scope stack, assert that it is a Lambda scope, and return it.
//
LambdaScope retScope = _varScopes.Pop() as LambdaScope;
PlanCompiler.Assert(retScope != null, "ExitLambdaFunction called without LambdaScope on top of scope stack");
//
// If lambda represents an edm function body then pop the function from the expansion stack and make sure it is the expected one.
//
if (expandingEdmFunction != null)
{
EdmFunction edmFunction = _functionExpansions.Pop();
PlanCompiler.Assert(edmFunction == expandingEdmFunction, "Function expansion stack corruption: unexpected function at the top of the stack");
}
return retScope;
}
/// <summary>
/// Constructs a NewRecordOp on top of a multi-Var-producing Op, resulting in a RelOp that produces a single Var.
/// </summary>
/// <param name="inputNode">The Node that references the multi-Var-producing Op. This Node will become the first child node of the new ProjectOp's Node</param>
/// <param name="recType">Type metadata that describes the output record type</param>
/// <param name="colVars">A list of Vars that provide the output columns of the projection</param>
/// <returns>A new ProjectOp that projects a new record of the specified type from the specified Vars over the original input Op/Node</returns>
private Node ProjectNewRecord(Node inputNode, RowType recType, IEnumerable<Var> colVars)
{
//
// Create a list of VarRefOp Nodes that provide the column values for the new record
//
List<Node> recordColumns = new List<Node>();
foreach (Var colVar in colVars)
{
recordColumns.Add(_iqtCommand.CreateNode(_iqtCommand.CreateVarRefOp(colVar)));
}
//
// Create the NewRecordOp Node using the record column nodes as its child nodes
//
Node newRecordNode = _iqtCommand.CreateNode(_iqtCommand.CreateNewRecordOp(recType), recordColumns);
//
// Create a new ComputedVar and a VarDefOp that uses the NewRecordOp Node to define it
//
Var newRecordVar;
Node varDefNode = _iqtCommand.CreateVarDefListNode(newRecordNode, out newRecordVar);
//
// Create a ProjectOp with the single Computed Var defined by the new record construction
//
ProjectOp projection = _iqtCommand.CreateProjectOp(newRecordVar);
Node projectionNode = _iqtCommand.CreateNode(projection, inputNode, varDefNode);
_varMap[projectionNode] = newRecordVar;
return projectionNode;
}
#endregion
#region DbExpressionVisitor<Node> Members
public override Node Visit(DbExpression e)
{
throw EntityUtil.NotSupported(System.Data.Entity.Strings.Cqt_General_UnsupportedExpression(e.GetType().FullName));
}
public override Node Visit(DbConstantExpression e)
{
// Don't use CreateInternalConstantOp - respect user-intent
//
// Note that it is only safe to call GetValue and access the
// constant value directly because any immutable values (byte[])
// will be cloned as the result expression is built in CTreeGenerator,
// during the call to DbExpressionBuilder.Constant in VisitConstantOp.
ConstantBaseOp op = _iqtCommand.CreateConstantOp(e.ResultType, e.GetValue());
return _iqtCommand.CreateNode(op);
}
public override Node Visit(DbNullExpression e)
{
NullOp op = _iqtCommand.CreateNullOp(e.ResultType);
return _iqtCommand.CreateNode(op);
}
public override Node Visit(DbVariableReferenceExpression e)
{
Node varNode = ResolveScope(e)[e.VariableName];
return varNode;
}
private CqtVariableScope ResolveScope(DbVariableReferenceExpression e)
{
//
// Search the stack of variables scopes, top-down,
// until the first one is found that defines a variable with the specified name.
//
foreach (CqtVariableScope scope in _varScopes)
{
if (scope.Contains(e.VariableName))
{
return scope;
}
}
//
// If the variable name was not resolved then either:
// 1. The original CQT was invalid (should not be allowed into the ITreeGenerator).
// 2. The variable scope stack itself is invalid.
//
PlanCompiler.Assert(false, "CQT VarRef could not be resolved in the variable scope stack");
return null;
}
public override Node Visit(DbParameterReferenceExpression e)
{
Op op = _iqtCommand.CreateVarRefOp(_iqtCommand.GetParameter(e.ParameterName));
return _iqtCommand.CreateNode(op);
}
public override Node Visit(DbFunctionExpression e)
{
Node retNode = null;
if (e.Function.IsModelDefinedFunction)
{
// This is a user-defined CSpace function with a body definition.
// Try expanding it:
// - replace the function call with the call to the body lambda,
// - visit the lambda call expression.
// Get/generate the body lambda. Wrap body generation exceptions.
DbLambda lambda;
try
{
lambda = _iqtCommand.MetadataWorkspace.GetGeneratedFunctionDefinition(e.Function);
}
catch (Exception exception)
{
if (EntityUtil.IsCatchableExceptionType(exception))
{
throw EntityUtil.CommandCompilation(Strings.Cqt_UDF_FunctionDefinitionGenerationFailed(e.Function.FullName), exception);
}
throw;
}
// Visit the lambda call expression.
// Argument types should be validated by now, hence the visitor should not throw under normal conditions.
retNode = VisitLambdaExpression(lambda, e.Arguments, e, e.Function);
}
else // a provider-manifest-defined or store function call - no expansion needed
{
List<Node> argNodes = new List<Node>(e.Arguments.Count);
for (int idx = 0; idx < e.Arguments.Count; idx++)
{
// Ensure that any argument with a result type that does not exactly match the type of
// the corresponding function parameter is enclosed in a SoftCastOp.
argNodes.Add(BuildSoftCast(VisitExprAsScalar(e.Arguments[idx]), e.Function.Parameters[idx].TypeUsage));
}
retNode = _iqtCommand.CreateNode(_iqtCommand.CreateFunctionOp(e.Function), argNodes);
}
return retNode;
}
public override Node Visit(DbLambdaExpression e)
{
return VisitLambdaExpression(e.Lambda, e.Arguments, e, null);
}
private Node VisitLambdaExpression(DbLambda lambda, IList<DbExpression> arguments, DbExpression applicationExpr, EdmFunction expandingEdmFunction)
{
Node retNode = null;
var argNodes = new List<Tuple<Node, bool>>(arguments.Count);
foreach (DbExpression argExpr in arguments)
{
// #484709: Lambda function parameters should not have enclosing SoftCastOps.
argNodes.Add(Tuple.Create(VisitExpr(argExpr), IsPredicate(argExpr)));
}
EnterLambdaFunction(lambda, argNodes, expandingEdmFunction);
retNode = VisitExpr(lambda.Body);
// Check the body to see if the current lambda yields a predicate.
_functionsIsPredicateFlag[applicationExpr] = IsPredicate(lambda.Body);
ExitLambdaFunction(expandingEdmFunction);
return retNode;
}
#if METHOD_EXPRESSION
public override Node Visit(MethodExpression e)
{
throw EntityUtil.NotSupported();
}
#endif
#region SoftCast Helpers
/// <summary>
/// This method builds a "soft"Cast operator over the input node (if necessary) to (soft)
/// cast it to the desired type (targetType)
///
/// If the input is a scalarOp, then we simply add on the SoftCastOp
/// directly (if it is needed, of course). If the input is a RelOp, we create a
/// new ProjectOp above the input, add a SoftCast above the Var of the
/// input, and then return the new ProjectOp
///
/// The "need to cast" is determined by the Command.EqualTypes function. All type
/// equivalence in the plan compiler is determined by this function
/// </summary>
/// <param name="node">the expression to soft-cast</param>
/// <param name="targetType">the desired type to cast to</param>
/// <returns></returns>
private Node BuildSoftCast(Node node, TypeUsage targetType)
{
//
// If the input is a RelOp (say X), and the Var of the input is "x",
// we convert this into
// Project(X, softCast(x, t))
// where t is the element type of the desired target type
//
if (node.Op.IsRelOp)
{
CollectionType targetCollectionType = TypeHelpers.GetEdmType<CollectionType>(targetType);
targetType = targetCollectionType.TypeUsage;
Var nodeVar = _varMap[node];
// Do we need a cast at all?
if (Command.EqualTypes(targetType, nodeVar.Type))
{
return node;
}
// Build up the projectOp
Var projectVar;
Node varRefNode = _iqtCommand.CreateNode(_iqtCommand.CreateVarRefOp(nodeVar));
Node castNode = _iqtCommand.CreateNode(_iqtCommand.CreateSoftCastOp(targetType), varRefNode);
Node varDefListNode = _iqtCommand.CreateVarDefListNode(castNode, out projectVar);
ProjectOp projectOp = _iqtCommand.CreateProjectOp(projectVar);
Node projectNode = _iqtCommand.CreateNode(projectOp, node, varDefListNode);
_varMap[projectNode] = projectVar;
return projectNode;
}
else
{
PlanCompiler.Assert(node.Op.IsScalarOp, "I want a scalar op");
if (Command.EqualTypes(node.Op.Type, targetType))
{
return node;
}
else
{
SoftCastOp castOp = _iqtCommand.CreateSoftCastOp(targetType);
return _iqtCommand.CreateNode(castOp, node);
}
}
}
/// <summary>
/// A variant of the function above. Works with an EdmType instead
/// of a TypeUsage, but leverages all the work above
/// </summary>
/// <param name="node">the node to "cast"</param>
/// <param name="targetType">the desired type</param>
/// <returns>the transformed expression</returns>
private Node BuildSoftCast(Node node, EdmType targetType)
{
return BuildSoftCast(node, TypeUsage.Create(targetType));
}
private Node BuildEntityRef(Node arg, TypeUsage entityType)
{
TypeUsage refType = TypeHelpers.CreateReferenceTypeUsage((EntityType)entityType.EdmType);
return _iqtCommand.CreateNode(_iqtCommand.CreateGetEntityRefOp(refType), arg);
}
#endregion
/// <summary>
/// We simplify the property instance where the user is accessing a key member of
/// a reference navigation. The instance becomes simply the reference key in such
/// cases.
///
/// For instance, product.Category.CategoryID becomes Ref(product.Category).CategoryID,
/// which gives us a chance of optimizing the query (using foreign keys rather than joins)
/// </summary>
/// <param name="propertyExpression">The original property expression that specifies the member and instance</param>
/// <param name="rewritten">'Simplified' instance. If the member is a key and the instance is a navigation
/// the rewritten expression's instance is a reference navigation rather than the full entity.</param>
/// <returns><c>true</c> if the property expression was rewritten, in which case <paramref name="rewritten"/> will be non-null,
/// otherwise <c>false</c>, in which case <paramref name="rewritten"/> will be null.</returns>
private bool TryRewriteKeyPropertyAccess(DbPropertyExpression propertyExpression, out DbExpression rewritten)
{
// if we're accessing a key member of a navigation, collapse the structured instance
// to the key reference.
if (propertyExpression.Instance.ExpressionKind == DbExpressionKind.Property &&
Helper.IsEntityType(propertyExpression.Instance.ResultType.EdmType))
{
EntityType instanceType = (EntityType)propertyExpression.Instance.ResultType.EdmType;
DbPropertyExpression instanceExpression = (DbPropertyExpression)propertyExpression.Instance;
if (Helper.IsNavigationProperty(instanceExpression.Property) &&
instanceType.KeyMembers.Contains(propertyExpression.Property))
{
// modify the property expression so that it merely retrieves the reference
// not the entire entity
NavigationProperty navigationProperty = (NavigationProperty)instanceExpression.Property;
DbExpression navigationSource = instanceExpression.Instance.GetEntityRef();
DbExpression navigationExpression = navigationSource.Navigate(navigationProperty.FromEndMember, navigationProperty.ToEndMember);
rewritten = navigationExpression.GetRefKey();
rewritten = rewritten.Property(propertyExpression.Property.Name);
return true;
}
}
rewritten = null;
return false;
}
public override Node Visit(DbPropertyExpression e)
{
// Only Properties, Relationship End and NavigationProperty members are supported.
if (BuiltInTypeKind.EdmProperty != e.Property.BuiltInTypeKind &&
BuiltInTypeKind.AssociationEndMember != e.Property.BuiltInTypeKind &&
BuiltInTypeKind.NavigationProperty != e.Property.BuiltInTypeKind)
{
throw EntityUtil.NotSupported();
}
PlanCompiler.Assert(e.Instance != null, "Static properties are not supported");
Node retNode = null;
DbExpression rewritten;
if (TryRewriteKeyPropertyAccess(e, out rewritten))
{
retNode = this.VisitExpr(rewritten);
}
else
{
Node instance = VisitExpr(e.Instance);
//
// Retrieving a property from a new instance constructor can be
// simplified to just the node that provides the corresponding property.
// For example, Property(Row(A = x, B = y), 'A') => x
// All structured types (including association types) are considered.
//
if (e.Instance.ExpressionKind == DbExpressionKind.NewInstance &&
Helper.IsStructuralType(e.Instance.ResultType.EdmType))
{
// Retrieve the 'structural' members of the instance's type.
// For Association types this should be only Association End members,
// while for Complex, Entity or Row types is should be only Properties.
System.Collections.IList propertyOrEndMembers = Helper.GetAllStructuralMembers(e.Instance.ResultType.EdmType);
// Find the position of the member with the same name as the retrieved
// member in the list of structural members.
int memberIdx = -1;
for (int idx = 0; idx < propertyOrEndMembers.Count; idx++)
{
if (string.Equals(e.Property.Name, ((EdmMember)propertyOrEndMembers[idx]).Name, StringComparison.Ordinal))
{
memberIdx = idx;
break;
}
}
PlanCompiler.Assert(memberIdx > -1, "The specified property was not found");
// If the member was found, return the corresponding argument value
// to the new instance op.
retNode = instance.Children[memberIdx];
// Make sure the argument value has been "cast" to the return type
// of the property, if necessary.
retNode = BuildSoftCast(retNode, e.ResultType);
}
else
{
Op op = _iqtCommand.CreatePropertyOp(e.Property);
// Make sure that the input has been "cast" to the right type
instance = BuildSoftCast(instance, e.Property.DeclaringType);
retNode = _iqtCommand.CreateNode(op, instance);
}
}
return retNode;
}
public override Node Visit(DbComparisonExpression e)
{
Op op = _iqtCommand.CreateComparisonOp(s_opMap[e.ExpressionKind]);
Node leftArg = VisitExprAsScalar(e.Left);
Node rightArg = VisitExprAsScalar(e.Right);
TypeUsage commonType = TypeHelpers.GetCommonTypeUsage(e.Left.ResultType, e.Right.ResultType);
// Make sure that the inputs have been cast to the right types
if (!Command.EqualTypes(e.Left.ResultType, e.Right.ResultType))
{
leftArg = BuildSoftCast(leftArg, commonType);
rightArg = BuildSoftCast(rightArg, commonType);
}
if (TypeSemantics.IsEntityType(commonType) &&
(e.ExpressionKind == DbExpressionKind.Equals || e.ExpressionKind == DbExpressionKind.NotEquals))
{
// Entity (in)equality is implemented as ref (in)equality
leftArg = BuildEntityRef(leftArg, commonType);
rightArg = BuildEntityRef(rightArg, commonType);
}
return _iqtCommand.CreateNode(op, leftArg, rightArg);
}
public override Node Visit(DbLikeExpression e)
{
return _iqtCommand.CreateNode(
_iqtCommand.CreateLikeOp(),
VisitExpr(e.Argument),
VisitExpr(e.Pattern),
VisitExpr(e.Escape)
);
}
private Node CreateLimitNode(Node inputNode, Node limitNode, bool withTies)
{
//
// Limit(Skip(x)) - which becomes ConstrainedSortOp - and Limit(Sort(x)) are special cases
//
Node retNode = null;
if (OpType.ConstrainedSort == inputNode.Op.OpType &&
OpType.Null == inputNode.Child2.Op.OpType)
{
//
// The input was a DbSkipExpression which is now represented
// as a ConstrainedSortOp with a NullOp Limit. The Limit from
// this DbLimitExpression can be merged into the input ConstrainedSortOp
// rather than creating a new ConstrainedSortOp.
//
inputNode.Child2 = limitNode;
// If this DbLimitExpression specifies WithTies, the input ConstrainedSortOp must be
// updated to reflect this (DbSkipExpression always produces a ConstrainedSortOp with
// WithTies equal to false).
if (withTies)
{
((ConstrainedSortOp)inputNode.Op).WithTies = true;
}
retNode = inputNode;
}
else if (OpType.Sort == inputNode.Op.OpType)
{
//
// This DbLimitExpression is applying a limit to a DbSortExpression.
// The two expressions can be merged into a single ConstrainedSortOp
// rather than creating a new ConstrainedSortOp over the input SortOp.
//
// The new ConstrainedSortOp has the same SortKeys as the input SortOp.
// The returned Node will have the following children:
// - The input to the Sort
// - A NullOp to indicate no Skip operation is specified
// - The limit Node from the DbLimitExpression
//
retNode =
_iqtCommand.CreateNode(
_iqtCommand.CreateConstrainedSortOp(((SortOp)inputNode.Op).Keys, withTies),
inputNode.Child0,
_iqtCommand.CreateNode(_iqtCommand.CreateNullOp(_iqtCommand.IntegerType)),
limitNode
);
}
else
{
//
// The input to the Limit is neither ConstrainedSortOp or SortOp.
// A new ConstrainedSortOp must be created with an empty list of keys
// and the following children:
// - The input to the DbLimitExpression
// - a NullOp to indicate that no Skip operation is specified
// - The limit Node from the DbLimitExpression
//
retNode =
_iqtCommand.CreateNode(
_iqtCommand.CreateConstrainedSortOp(new List<SortKey>(), withTies),
inputNode,
_iqtCommand.CreateNode(_iqtCommand.CreateNullOp(_iqtCommand.IntegerType)),
limitNode
);
}
return retNode;
}
public override Node Visit(DbLimitExpression expression)
{
//
// Visit the Argument and retrieve its Var
//
Node inputNode = EnsureRelOp(VisitExpr(expression.Argument));
Var inputVar = _varMap[inputNode];
//
// Visit the Limit ensuring that it is a scalar
//
Node limitNode = VisitExprAsScalar(expression.Limit);
Node retNode;
if (OpType.Project == inputNode.Op.OpType
&& (!AppSettings.SimplifyLimitOperations
|| (OpType.Sort == inputNode.Child0.Op.OpType
|| OpType.ConstrainedSort == inputNode.Child0.Op.OpType)))
{
//
// If the input to the DbLimitExpression is a projection, then apply the Limit operation to the
// input to the ProjectOp instead. This allows Limit(Project(Skip(x))) and Limit(Project(Sort(x)))
// to be treated in the same way as Limit(Skip(x)) and Limit(Sort(x)).
// Note that even if the input to the projection is not a ConstrainedSortOp or SortOp, the
// Limit operation is still pushed under the Project when the SimplifyLimitOperations AppSetting
// is set to false. SimplifyLimitOperations is false by default.
//
inputNode.Child0 = CreateLimitNode(inputNode.Child0, limitNode, expression.WithTies);
retNode = inputNode;
}
else
{
//
// Otherwise, apply the Limit operation directly to the input.
//
retNode = CreateLimitNode(inputNode, limitNode, expression.WithTies);
}
//
// The output Var of the resulting Node is the same as the output Var of its input Node.
// If the input node is being returned (either because the Limit was pushed under a Project
// or because the input was a ConstrainedSortOp that was simply updated with the Limit value)
// then the Node -> Var map does not need to be updated.
//
if(!object.ReferenceEquals(retNode, inputNode))
{
_varMap[retNode] = inputVar;
}
return retNode;
}
public override Node Visit(DbIsNullExpression e)
{
// SQLBUDT #484294: We need to recognize and simplify IsNull - IsNull and IsNull - Not - IsNull
// This is the latest point where such patterns can be easily recognized.
// After this the input predicate would get translated into a case statement.
bool isAlwaysFalse = false; //true if IsNull - IsNull and IsNull - Not - IsNull is recognized
if (e.Argument.ExpressionKind == DbExpressionKind.IsNull)
{
isAlwaysFalse = true;
}
else if (e.Argument.ExpressionKind == DbExpressionKind.Not)
{
DbNotExpression notExpression = (DbNotExpression)e.Argument;
if (notExpression.Argument.ExpressionKind == DbExpressionKind.IsNull)
{
isAlwaysFalse = true;
}
}
Op op = _iqtCommand.CreateConditionalOp(OpType.IsNull);
//If we have recognized that the result is always false, return IsNull(true), to still have predicate as output.
//This gets further simplified by transformation rules.
if (isAlwaysFalse)
{
return _iqtCommand.CreateNode(op, _iqtCommand.CreateNode(_iqtCommand.CreateInternalConstantOp(_iqtCommand.BooleanType, true)));
}
Node argNode = VisitExprAsScalar(e.Argument);
if (TypeSemantics.IsEntityType(e.Argument.ResultType))
{
argNode = BuildEntityRef(argNode, e.Argument.ResultType);
}
return _iqtCommand.CreateNode(op, argNode);
}
public override Node Visit(DbArithmeticExpression e)
{
Op op = _iqtCommand.CreateArithmeticOp(s_opMap[e.ExpressionKind], e.ResultType);
// Make sure that the inputs have been "cast" to the result type
// Assumption: The input type must be the same as the result type. Is this always true?
List<Node> children = new List<Node>();
foreach (DbExpression arg in e.Arguments)
{
Node child = VisitExprAsScalar(arg);
children.Add(BuildSoftCast(child, e.ResultType));
}
return _iqtCommand.CreateNode(op, children);
}
public override Node Visit(DbAndExpression e)
{
Op op = _iqtCommand.CreateConditionalOp(OpType.And);
return VisitBinary(e, op, VisitExprAsPredicate);
}
public override Node Visit(DbOrExpression e)
{
Op op = _iqtCommand.CreateConditionalOp(OpType.Or);
return VisitBinary(e, op, VisitExprAsPredicate);
}
public override Node Visit(DbNotExpression e)
{
Op op = _iqtCommand.CreateConditionalOp(OpType.Not);
return VisitUnary(e, op, VisitExprAsPredicate);
}
public override Node Visit(DbDistinctExpression e)
{
Node inputSetNode = EnsureRelOp(VisitExpr(e.Argument));
Var inputVar = _varMap[inputSetNode];
Op distinctOp = _iqtCommand.CreateDistinctOp(inputVar);
Node distinctNode = _iqtCommand.CreateNode(distinctOp, inputSetNode);
_varMap[distinctNode] = inputVar;
return distinctNode;
}
public override Node Visit(DbElementExpression e)
{
Op elementOp = _iqtCommand.CreateElementOp(e.ResultType);
Node inputSetNode = EnsureRelOp(VisitExpr(e.Argument));
// Add a soft cast if needed
inputSetNode = BuildSoftCast(inputSetNode, TypeHelpers.CreateCollectionTypeUsage(e.ResultType));
Var inputVar = _varMap[inputSetNode];
//
// Add a singleRowOp enforcer, as we are not guaranteed that the input
// collection produces at most one row
//
inputSetNode = _iqtCommand.CreateNode(_iqtCommand.CreateSingleRowOp(), inputSetNode);
_varMap[inputSetNode] = inputVar;
// add a fake projectNode
inputSetNode = CapWithProject(inputSetNode);
return _iqtCommand.CreateNode(elementOp, inputSetNode);
}
public override Node Visit(DbIsEmptyExpression e)
{
//
// IsEmpty(input set) --> Not(Exists(input set))
//
Op existsOp = _iqtCommand.CreateExistsOp();
Node inputSetNode = EnsureRelOp(VisitExpr(e.Argument));
return _iqtCommand.CreateNode(
_iqtCommand.CreateConditionalOp(OpType.Not),
_iqtCommand.CreateNode(existsOp, inputSetNode)
);
}
/// <summary>
/// Encapsulates the logic required to convert a SetOp (Except, Intersect, UnionAll) expression
/// into an IQT Node/Op pair.
/// </summary>
/// <param name="expression">The DbExceptExpression, DbIntersectExpression or DbUnionAllExpression to convert, as an instance of DbBinaryExpression</param>
/// <returns>A new IQT Node that references the ExceptOp, IntersectOp or UnionAllOp created based on the expression</returns>
private Node VisitSetOpExpression(DbBinaryExpression expression)
{
PlanCompiler.Assert(DbExpressionKind.Except == expression.ExpressionKind ||
DbExpressionKind.Intersect == expression.ExpressionKind ||
DbExpressionKind.UnionAll == expression.ExpressionKind,
"Non-SetOp DbExpression used as argument to VisitSetOpExpression");
PlanCompiler.Assert(TypeSemantics.IsCollectionType(expression.ResultType), "SetOp DbExpression does not have collection result type?");
// Visit the left and right collection arguments
Node leftNode = EnsureRelOp(VisitExpr(expression.Left));
Node rightNode = EnsureRelOp(VisitExpr(expression.Right));
//
// Now the hard part. "Normalize" the left and right sides to
// match the result type.
//
leftNode = BuildSoftCast(leftNode, expression.ResultType);
rightNode = BuildSoftCast(rightNode, expression.ResultType);
// The SetOp produces a single Var of the same type as the element type of the expression's collection result type
Var outputVar = _iqtCommand.CreateSetOpVar(TypeHelpers.GetEdmType<CollectionType>(expression.ResultType).TypeUsage);
// Create VarMaps for the left and right arguments that map the output Var to the Var produced by the corresponding argument
VarMap leftMap = new VarMap();
leftMap.Add(outputVar, _varMap[leftNode]);
VarMap rightMap = new VarMap();
rightMap.Add(outputVar, _varMap[rightNode]);
// Create a SetOp that corresponds to the operation specified by the expression's DbExpressionKind
Op setOp = null;
switch(expression.ExpressionKind)
{
case DbExpressionKind.Except:
setOp = _iqtCommand.CreateExceptOp(leftMap, rightMap);
break;
case DbExpressionKind.Intersect:
setOp = _iqtCommand.CreateIntersectOp(leftMap, rightMap);
break;
case DbExpressionKind.UnionAll:
setOp = _iqtCommand.CreateUnionAllOp(leftMap, rightMap);
break;
}
// Create a new Node that references the SetOp
Node setOpNode = _iqtCommand.CreateNode(setOp, leftNode, rightNode);
// Update the Node => Var map with an entry that maps the new Node to the output Var
_varMap[setOpNode] = outputVar;
// Return the newly created SetOp Node
return setOpNode;
}
public override Node Visit(DbUnionAllExpression e)
{
return VisitSetOpExpression(e);
}
public override Node Visit(DbIntersectExpression e)
{
return VisitSetOpExpression(e);
}
public override Node Visit(DbExceptExpression e)
{
return VisitSetOpExpression(e);
}
public override Node Visit(DbTreatExpression e)
{
Op op;
if (_fakeTreats.Contains(e))
{
op = _iqtCommand.CreateFakeTreatOp(e.ResultType);
}
else
{
op = _iqtCommand.CreateTreatOp(e.ResultType);
}
return VisitUnary(e, op, VisitExprAsScalar);
}
public override Node Visit(DbIsOfExpression e)
{
Op op = null;
if (DbExpressionKind.IsOfOnly == e.ExpressionKind)
{
op = _iqtCommand.CreateIsOfOnlyOp(e.OfType);
}
else
{
op = _iqtCommand.CreateIsOfOp(e.OfType);
}
return VisitUnary(e, op, VisitExprAsScalar);
}
public override Node Visit(DbCastExpression e)
{
Op op = _iqtCommand.CreateCastOp(e.ResultType);
return VisitUnary(e, op, VisitExprAsScalar);
}
public override Node Visit(DbCaseExpression e)
{
List<Node> childNodes = new List<Node>();
for (int idx = 0; idx < e.When.Count; idx++)
{
childNodes.Add(VisitExprAsPredicate(e.When[idx]));
// Make sure that each then-clause is the same type as the result
childNodes.Add(BuildSoftCast(VisitExprAsScalar(e.Then[idx]), e.ResultType));
}
// Make sure that the else-clause is the same type as the result
childNodes.Add(BuildSoftCast(VisitExprAsScalar(e.Else), e.ResultType));
return _iqtCommand.CreateNode(_iqtCommand.CreateCaseOp(e.ResultType), childNodes);
}
/// <summary>
/// Represents one or more type filters that should be AND'd together to produce an aggregate IsOf filter expression
/// </summary>
private class IsOfFilter
{
/// <summary>
/// The type that elements of the filtered input set must be to satisfy this IsOf filter
/// </summary>
private readonly TypeUsage requiredType;
/// <summary>
/// Indicates whether elements of the filtered input set may be of a subtype (IsOf) of the required type
/// and still satisfy the IsOfFilter, or must be exactly of the required type (IsOfOnly) to do so.
/// </summary>
private readonly bool isExact;
/// <summary>
/// The next IsOfFilter in the AND chain.
/// </summary>
private IsOfFilter next;
internal IsOfFilter(DbIsOfExpression template)
{
this.requiredType = template.OfType;
this.isExact = (template.ExpressionKind == DbExpressionKind.IsOfOnly);
}
internal IsOfFilter(DbOfTypeExpression template)
{
this.requiredType = template.OfType;
this.isExact = (template.ExpressionKind == DbExpressionKind.OfTypeOnly);
}
private IsOfFilter(TypeUsage required, bool exact)
{
this.requiredType = required;
this.isExact = exact;
}
private IsOfFilter Merge(TypeUsage otherRequiredType, bool otherIsExact)
{
// Can the two type filters be merged? In general, a more specific
// type filter can replace a less specific type filter.
IsOfFilter result;
bool typesEqual = this.requiredType.EdmEquals(otherRequiredType);
// The simplest case - the filters are equivalent
if (typesEqual && this.isExact == otherIsExact)
{
result = this;
}
// Next simplest - two IsOfOnly filters can never be merged if the types are different
// (and if the types were equal the above condition would have been satisfied).
// SC_
else if (this.isExact && otherIsExact)
{
result = new IsOfFilter(otherRequiredType, otherIsExact);
result.next = this;
}
// Two IsOf filters can potentially be adjusted - the more specific type filter should be kept, if present
else if (!this.isExact && !otherIsExact)
{
// At this point the types cannot be equal. If one filter specifies a type that is a subtype of the other,
// then the subtype filter is the one that should remain
if (otherRequiredType.IsSubtypeOf(this.requiredType))
{
result = new IsOfFilter(otherRequiredType, false);
result.next = this.next;
}
else if (this.requiredType.IsSubtypeOf(otherRequiredType))
{
result = this;
}
else
{
// The types are not related and the filters cannot be merged
// Note that this case may not be possible since IsOf and OfType
// both require an argument with a compatible type to the IsOf type.
result = new IsOfFilter(otherRequiredType, otherIsExact);
result.next = this;
}
}
// One filter is an IsOf filter while the other is an IsOfOnly filter
else
{
// For IsOf(T) AND IsOfOnly(T), the IsOf filter can be dropped
if (typesEqual)
{
result = new IsOfFilter(otherRequiredType, true);
result.next = this.next;
}
else
{
// Decide which is the 'IsOfOnly' type and which is the 'IsOf' type
TypeUsage isOfOnlyType = (this.isExact ? this.requiredType : otherRequiredType);
TypeUsage isOfType = (this.isExact ? otherRequiredType : this.requiredType);
// IsOf(Super) && IsOfOnly(Sub) => IsOfOnly(Sub)
// In all other cases, both filters remain - even though the IsOfOnly(Super) and IsOf(Sub) is obviously a contradiction.
// SC_
if (isOfOnlyType.IsSubtypeOf(isOfType))
{
if (object.ReferenceEquals(isOfOnlyType, this.requiredType) && this.isExact)
{
result = this;
}
else
{
result = new IsOfFilter(isOfOnlyType, true);
result.next = this.next;
}
}
else
{
result = new IsOfFilter(otherRequiredType, otherIsExact);
result.next = this;
}
}
}
return result;
}
internal IsOfFilter Merge(DbIsOfExpression other)
{
return Merge(other.OfType, (other.ExpressionKind == DbExpressionKind.IsOfOnly));
}
internal IsOfFilter Merge(DbOfTypeExpression other)
{
return Merge(other.OfType, (other.ExpressionKind == DbExpressionKind.OfTypeOnly));
}
internal IEnumerable<KeyValuePair<TypeUsage, bool>> ToEnumerable()
{
IsOfFilter currentFilter = this;
while (currentFilter != null)
{
yield return new KeyValuePair<TypeUsage, bool>(currentFilter.requiredType, currentFilter.isExact);
currentFilter = currentFilter.next;
}
}
}
private DbFilterExpression CreateIsOfFilterExpression(DbExpression input, IsOfFilter typeFilter)
{
// Create a filter expression based on the IsOf/IsOfOnly operations specified by typeFilter
DbExpressionBinding resultBinding = input.Bind();
List<DbExpression> predicates = new List<DbExpression>(
typeFilter.ToEnumerable().Select(tf => tf.Value ? resultBinding.Variable.IsOfOnly(tf.Key) : resultBinding.Variable.IsOf(tf.Key)).ToList()
);
DbExpression predicate = Helpers.BuildBalancedTreeInPlace(predicates, (left, right) => left.And(right));
DbFilterExpression result = resultBinding.Filter(predicate);
// Track the fact that this IsOfFilter was created by the ITreeGenerator itself and should
// simply be converted to an ITree Node when it is encountered again by the visitor pass.
_processedIsOfFilters.Add(result);
return result;
}
private bool IsIsOfFilter(DbFilterExpression filter)
{
if(filter.Predicate.ExpressionKind != DbExpressionKind.IsOf &&
filter.Predicate.ExpressionKind != DbExpressionKind.IsOfOnly)
{
return false;
}
DbExpression isOfArgument = ((DbIsOfExpression)filter.Predicate).Argument;
return (isOfArgument.ExpressionKind == DbExpressionKind.VariableReference &&
((DbVariableReferenceExpression)isOfArgument).VariableName == filter.Input.VariableName);
}
private DbExpression ApplyIsOfFilter(DbExpression current, IsOfFilter typeFilter)
{
// An IsOf filter can be safely pushed down through the following expressions:
//
// Distinct
// Filter - may be merged if the Filter is also an OfType filter
// OfType - converted to Project(Filter(input, IsOf(T)), TreatAs(T)) and the Filter may be merged
// Project - only for identity project
// SC_
DbExpression result;
switch(current.ExpressionKind)
{
case DbExpressionKind.Distinct:
{
result = ApplyIsOfFilter(((DbDistinctExpression)current).Argument, typeFilter).Distinct();
}
break;
case DbExpressionKind.Filter:
{
DbFilterExpression filter = (DbFilterExpression)current;
if (IsIsOfFilter(filter))
{
// If this is an IsOf filter, examine the interaction with the current filter we are trying to apply
DbIsOfExpression isOfExp = (DbIsOfExpression)filter.Predicate;
typeFilter = typeFilter.Merge(isOfExp);
result = ApplyIsOfFilter(filter.Input.Expression, typeFilter);
}
else
{
// Otherwise, push the current IsOf filter under this filter
DbExpression rewritten = ApplyIsOfFilter(filter.Input.Expression, typeFilter);
result = rewritten.BindAs(filter.Input.VariableName).Filter(filter.Predicate);
}
}
break;
case DbExpressionKind.OfType:
case DbExpressionKind.OfTypeOnly:
{
// Examine the interaction of this nested OfType filter with the OfType filter we are trying to apply
// and construct an aggregated type filter (where possible)
DbOfTypeExpression ofTypeExp = (DbOfTypeExpression)current;
typeFilter = typeFilter.Merge(ofTypeExp);
DbExpression rewrittenIsOf = ApplyIsOfFilter(ofTypeExp.Argument, typeFilter);
DbExpressionBinding treatBinding = rewrittenIsOf.Bind();
DbTreatExpression treatProjection = treatBinding.Variable.TreatAs(ofTypeExp.OfType);
_fakeTreats.Add(treatProjection);
result = treatBinding.Project(treatProjection);
}
break;
case DbExpressionKind.Project:
{
DbProjectExpression project = (DbProjectExpression)current;
if(project.Projection.ExpressionKind == DbExpressionKind.VariableReference &&
((DbVariableReferenceExpression)project.Projection).VariableName == project.Input.VariableName)
{
// If this is an identity-project, remove it by visiting the input expression
result = ApplyIsOfFilter(project.Input.Expression, typeFilter);
}
else
{
// Otherwise, the projection is opaque to the IsOf rewrite
result = CreateIsOfFilterExpression(current, typeFilter);
}
}
break;
case DbExpressionKind.Sort:
{
// The IsOf filter is applied to the Sort input, then the sort keys are reapplied to create a new Sort expression.
DbSortExpression sort = (DbSortExpression)current;
DbExpression sortInput = ApplyIsOfFilter(sort.Input.Expression, typeFilter);
result = sortInput.BindAs(sort.Input.VariableName).Sort(sort.SortOrder);
}
break;
default:
{
// This is not a recognized case, so simply apply the type filter to the expression.
result = CreateIsOfFilterExpression(current, typeFilter);
}
break;
}
return result;
}
/// <summary>
/// Build the equivalent of an OfTypeExpression over the input (ie) produce the set of values from the
/// input that are of the desired type (exactly of the desired type, if the "includeSubtypes" parameter is false).
///
/// Further more, "update" the result element type to be the desired type.
///
/// We accomplish this by first building a FilterOp with an IsOf (or an IsOfOnly) predicate for the desired
/// type. We then build out a ProjectOp over the FilterOp, where we introduce a "Fake" TreatOp over the input
/// element to cast it to the right type. The "Fake" TreatOp is only there for "compile-time" typing reasons,
/// and will be ignored in the rest of the plan compiler
/// </summary>
// <param name="inputNode">the input collection</param>
// <param name="inputVar">the single Var produced by the input collection</param>
// <param name="desiredType">the desired element type </param>
// <param name="includeSubtypes">do we include subtypes of the desired element type</param>
// <param name="resultNode">the result subtree</param>
// <param name="resultVar">the single Var produced by the result subtree</param>
public override Node Visit(DbOfTypeExpression e)
{
//
// The argument to OfType must be a collection
//
PlanCompiler.Assert(TypeSemantics.IsCollectionType(e.Argument.ResultType), "Non-Collection Type Argument in DbOfTypeExpression");
DbExpression rewrittenIsOfFilter = ApplyIsOfFilter(e.Argument, new IsOfFilter(e));
//
// Visit the collection argument and ensure that it is a RelOp suitable for subsequent use in the Filter/Project used to convert OfType.
//
Node inputNode = EnsureRelOp(VisitExpr(rewrittenIsOfFilter));
//
// Retrieve the Var produced by the RelOp input.
//
Var inputVar = _varMap[inputNode];
//
// Build the Treat part of the OfType expression tree - note that this is a 'fake'
// Treat because the underlying IsOf filter makes it unnecessary (as far as the
// plan compiler is concerned).
//
Var resultVar;
Node resultNode = _iqtCommand.BuildFakeTreatProject(inputNode, inputVar, e.OfType, out resultVar);
//
// Add the node-var mapping, and return
//
_varMap[resultNode] = resultVar;
return resultNode;
}
public override Node Visit(DbNewInstanceExpression e)
{
Op newInstOp = null;
List<Node> relPropertyExprs = null;
if (TypeSemantics.IsCollectionType(e.ResultType))
{
newInstOp = _iqtCommand.CreateNewMultisetOp(e.ResultType);
}
else if (TypeSemantics.IsRowType(e.ResultType))
{
newInstOp = _iqtCommand.CreateNewRecordOp(e.ResultType);
}
else if (TypeSemantics.IsEntityType(e.ResultType))
{
List<RelProperty> relPropertyList = new List<RelProperty>();
relPropertyExprs = new List<Node>();
if (e.HasRelatedEntityReferences)
{
foreach (DbRelatedEntityRef targetRef in e.RelatedEntityReferences)
{
RelProperty relProperty = new RelProperty((RelationshipType)targetRef.TargetEnd.DeclaringType, targetRef.SourceEnd, targetRef.TargetEnd);
relPropertyList.Add(relProperty);
Node relPropertyNode = VisitExprAsScalar(targetRef.TargetEntityReference);
relPropertyExprs.Add(relPropertyNode);
}
}
newInstOp = _iqtCommand.CreateNewEntityOp(e.ResultType, relPropertyList);
}
else
{
newInstOp = _iqtCommand.CreateNewInstanceOp(e.ResultType);
}
//
// Build up the list of arguments. Make sure that they match
// the expected types (and add "soft" casts, if needed)
//
List<Node> newArgs = new List<Node>();
if (TypeSemantics.IsStructuralType(e.ResultType))
{
StructuralType resultType = TypeHelpers.GetEdmType<StructuralType>(e.ResultType);
int i = 0;
foreach (EdmMember m in TypeHelpers.GetAllStructuralMembers(resultType))
{
Node newArg = BuildSoftCast(VisitExprAsScalar(e.Arguments[i]), Helper.GetModelTypeUsage(m));
newArgs.Add(newArg);
i++;
}
}
else
{
CollectionType resultType = TypeHelpers.GetEdmType<CollectionType>(e.ResultType);
TypeUsage elementTypeUsage = resultType.TypeUsage;
foreach (DbExpression arg in e.Arguments)
{
Node newArg = BuildSoftCast(VisitExprAsScalar(arg), elementTypeUsage);
newArgs.Add(newArg);
}
}
if (relPropertyExprs != null)
{
newArgs.AddRange(relPropertyExprs);
}
Node node = _iqtCommand.CreateNode(newInstOp, newArgs);
return node;
}
public override Node Visit(DbRefExpression e)
{
// SQLBUDT #502617: Creating a collection of refs throws an Assert
// A SoftCastOp may be required if the argument to the RefExpression is only promotable
// to the row type produced from the key properties of the referenced Entity type. Since
// this row type is not actually represented anywhere in the tree it must be built here in
// order to determine whether or not the SoftCastOp should be applied.
//
Op op = _iqtCommand.CreateRefOp(e.EntitySet, e.ResultType);
Node newArg = BuildSoftCast(VisitExprAsScalar(e.Argument), TypeHelpers.CreateKeyRowType(e.EntitySet.ElementType));
return _iqtCommand.CreateNode(op, newArg);
}
public override Node Visit(DbRelationshipNavigationExpression e)
{
RelProperty relProperty = new RelProperty(e.Relationship, e.NavigateFrom, e.NavigateTo);
Op op = _iqtCommand.CreateNavigateOp(e.ResultType, relProperty);
Node arg = VisitExprAsScalar(e.NavigationSource);
return _iqtCommand.CreateNode(op, arg);
}
public override Node Visit(DbDerefExpression e)
{
Op op = _iqtCommand.CreateDerefOp(e.ResultType);
return VisitUnary(e, op, VisitExprAsScalar);
}
public override Node Visit(DbRefKeyExpression e)
{
Op op = _iqtCommand.CreateGetRefKeyOp(e.ResultType);
return VisitUnary(e, op, VisitExprAsScalar);
}
public override Node Visit(DbEntityRefExpression e)
{
Op op = _iqtCommand.CreateGetEntityRefOp(e.ResultType);
return VisitUnary(e, op, VisitExprAsScalar);
}
public override Node Visit(DbScanExpression e)
{
// Create a new table definition
TableMD tableMetadata = Command.CreateTableDefinition(e.Target);
// Create a scan table operator
ScanTableOp op = _iqtCommand.CreateScanTableOp(tableMetadata);
// Map the ScanTableOp to the ColumnVar of the Table's single column of the Extent's element type
Node node = _iqtCommand.CreateNode(op);
Var singleColumn = op.Table.Columns[0];
_varMap[node] = singleColumn;
return node;
}
public override Node Visit(DbFilterExpression e)
{
if (!IsIsOfFilter(e) || _processedIsOfFilters.Contains(e))
{
//
// Visit the Predicate with the Input binding's variable in scope
//
Node inputSetNode = EnterExpressionBinding(e.Input);
Node predicateNode = VisitExprAsPredicate(e.Predicate);
ExitExpressionBinding();
Op filtOp = _iqtCommand.CreateFilterOp();
// Update the Node --> Var mapping. Filter maps to the same Var as its input.
Node filtNode = _iqtCommand.CreateNode(filtOp, inputSetNode, predicateNode);
_varMap[filtNode] = _varMap[inputSetNode];
return filtNode;
}
else
{
DbIsOfExpression isOfPredicate = (DbIsOfExpression)e.Predicate;
DbExpression processed = ApplyIsOfFilter(e.Input.Expression, new IsOfFilter(isOfPredicate));
return this.VisitExpr(processed);
}
}
public override Node Visit(DbProjectExpression e)
{
// check if this is the discriminated projection for a query mapping view
if (e == this._discriminatedViewTopProject)
{
return GenerateDiscriminatedProject(e);
}
else
{
return GenerateStandardProject(e);
}
}
private Node GenerateDiscriminatedProject(DbProjectExpression e)
{
PlanCompiler.Assert(null != _discriminatedViewTopProject, "if a project matches the pattern, there must be a corresponding discriminator map");
// convert the input to the top level projection
Node source = EnterExpressionBinding(e.Input);
List<RelProperty> relPropertyList = new List<RelProperty>();
List<Node> relPropertyExprs = new List<Node>();
foreach (KeyValuePair<RelProperty, DbExpression> kv in _discriminatorMap.RelPropertyMap)
{
relPropertyList.Add(kv.Key);
relPropertyExprs.Add(VisitExprAsScalar(kv.Value));
}
// construct a DiscriminatedNewInstanceOp
DiscriminatedNewEntityOp newInstOp = _iqtCommand.CreateDiscriminatedNewEntityOp(e.Projection.ResultType,
new ExplicitDiscriminatorMap(_discriminatorMap), _discriminatorMap.EntitySet, relPropertyList);
// args include all projected properties and discriminator and the relProperties
List<Node> newArgs = new List<Node>(_discriminatorMap.PropertyMap.Count + 1);
newArgs.Add(CreateNewInstanceArgument(_discriminatorMap.Discriminator.Property, _discriminatorMap.Discriminator));
foreach (var propertyMap in _discriminatorMap.PropertyMap)
{
DbExpression value = propertyMap.Value;
EdmProperty property = propertyMap.Key;
Node newArg = CreateNewInstanceArgument(property, value);
newArgs.Add(newArg);
}
newArgs.AddRange(relPropertyExprs);
Node newInstNode = _iqtCommand.CreateNode(newInstOp, newArgs);
ExitExpressionBinding();
Var sourceVar;
Node varDefListNode = _iqtCommand.CreateVarDefListNode(newInstNode, out sourceVar);
ProjectOp projOp = _iqtCommand.CreateProjectOp(sourceVar);
Node projNode = _iqtCommand.CreateNode(projOp, source, varDefListNode);
_varMap[projNode] = sourceVar;
return projNode;
}
private Node CreateNewInstanceArgument(EdmMember property, DbExpression value)
{
Node newArg = BuildSoftCast(VisitExprAsScalar(value), Helper.GetModelTypeUsage(property));
return newArg;
}
private Node GenerateStandardProject(DbProjectExpression e)
{
Node projectedSetNode = EnterExpressionBinding(e.Input);
Node projectionNode = VisitExprAsScalar(e.Projection);
ExitExpressionBinding();
Var projectionVar;
Node varDefListNode = _iqtCommand.CreateVarDefListNode(projectionNode, out projectionVar);
ProjectOp projOp = _iqtCommand.CreateProjectOp(projectionVar);
Node projNode = _iqtCommand.CreateNode(projOp, projectedSetNode, varDefListNode);
_varMap[projNode] = projectionVar;
return projNode;
}
public override Node Visit(DbCrossJoinExpression e)
{
return VisitJoin(e, e.Inputs, null);
}
public override Node Visit(DbJoinExpression e)
{
List<DbExpressionBinding> inputs = new List<DbExpressionBinding>();
inputs.Add(e.Left);
inputs.Add(e.Right);
return VisitJoin(e, inputs, e.JoinCondition);
}
private Node VisitJoin(DbExpression e, IList<DbExpressionBinding> inputs, DbExpression joinCond)
{
//
// Assert that the JoinType is covered. If JoinTypes are added to CQT then the
// switch statement that constructs the JoinOp must be updated, along with this assert.
//
PlanCompiler.Assert(DbExpressionKind.CrossJoin == e.ExpressionKind ||
DbExpressionKind.InnerJoin == e.ExpressionKind ||
DbExpressionKind.LeftOuterJoin == e.ExpressionKind ||
DbExpressionKind.FullOuterJoin == e.ExpressionKind,
"Unrecognized JoinType specified in DbJoinExpression");
#if DEBUG
//
// Assert that the DbJoinExpression is producing a collection result with a record element type.
// !!! IsCollectionOfRecord() is defined only in DEBUG !!!
PlanCompiler.Assert(IsCollectionOfRecord(e.ResultType), "Invalid Type returned by DbJoinExpression");
#endif
//
// Visit Join inputs, track their nodes and vars.
//
List<Node> inputNodes = new List<Node>();
List<Var> inputVars = new List<Var>();
for(int idx = 0; idx < inputs.Count; idx++)
{
Var boundVar;
Node inputNode = VisitBoundExpression(inputs[idx].Expression, out boundVar);
inputNodes.Add(inputNode);
inputVars.Add(boundVar);
}
//
// Bring the variables for the Join inputs into scope.
//
for (int scopeCount = 0; scopeCount < inputNodes.Count; scopeCount++)
{
PushBindingScope(inputVars[scopeCount], inputs[scopeCount].VariableName);
}
//
// Visit join condition, if present.
//
Node joinCondNode = VisitExprAsPredicate(joinCond);
//
// Remove the input variables from scope after visiting the Join condition.
//
for (int scopeCount = 0; scopeCount < inputNodes.Count; scopeCount++)
{
ExitExpressionBinding();
}
//
// Create an appropriate JoinOp based on the JoinType specified in the DbJoinExpression.
//
JoinBaseOp joinOp = null;
switch (e.ExpressionKind)
{
case DbExpressionKind.CrossJoin:
{
joinOp = _iqtCommand.CreateCrossJoinOp();
}
break;
case DbExpressionKind.InnerJoin:
{
joinOp = _iqtCommand.CreateInnerJoinOp();
}
break;
case DbExpressionKind.LeftOuterJoin:
{
joinOp = _iqtCommand.CreateLeftOuterJoinOp();
}
break;
case DbExpressionKind.FullOuterJoin:
{
joinOp = _iqtCommand.CreateFullOuterJoinOp();
}
break;
}
//
// Assert that a JoinOp was produced. This check is again in case a new JoinType is introduced to CQT and this method is not updated.
//
PlanCompiler.Assert(joinOp != null, "Unrecognized JoinOp specified in DbJoinExpression, no JoinOp was produced");
//
// If the Join condition was present then add its converted form to the list of child nodes for the new Join node.
//
if (e.ExpressionKind != DbExpressionKind.CrossJoin)
{
PlanCompiler.Assert(joinCondNode != null, "Non CrossJoinOps must specify a join condition");
inputNodes.Add(joinCondNode);
}
//
// Create and return a new projection that unifies the multiple vars produced by the Join columns into a single record constructor.
//
return ProjectNewRecord(
_iqtCommand.CreateNode(joinOp, inputNodes),
ExtractElementRowType(e.ResultType),
inputVars
);
}
public override Node Visit(DbApplyExpression e)
{
#if DEBUG
//
// Assert that the DbJoinExpression is producing a collection result with a record element type.
// !!! IsCollectionOfRecord() is defined only in DEBUG !!!
PlanCompiler.Assert(IsCollectionOfRecord(e.ResultType), "Invalid Type returned by DbApplyExpression");
#endif
//
// Bring the Input set's variable into scope
//
Node inputNode = EnterExpressionBinding(e.Input);
//
// Visit the Apply expression with the Input's variable in scope.
// This is done via EnterExpressionBinding, which is allowable only because
// it will only bring the Apply variable into scope *after* visiting the Apply expression
// (which means that the Apply expression cannot validly reference its own binding variable)
//
Node applyNode = EnterExpressionBinding(e.Apply);
//
// Remove the Apply and Input variables from scope
//
ExitExpressionBinding(); // for the Apply
ExitExpressionBinding(); // for the Input
//
// The ApplyType should only be either CrossApply or OuterApply.
//
PlanCompiler.Assert(DbExpressionKind.CrossApply == e.ExpressionKind || DbExpressionKind.OuterApply == e.ExpressionKind, "Unrecognized DbExpressionKind specified in DbApplyExpression");
//
// Create a new Node with the correct ApplyOp as its Op and the input and apply nodes as its child nodes.
//
ApplyBaseOp applyOp = null;
if (DbExpressionKind.CrossApply == e.ExpressionKind)
{
applyOp = _iqtCommand.CreateCrossApplyOp();
}
else
{
applyOp = _iqtCommand.CreateOuterApplyOp();
}
Node retNode = _iqtCommand.CreateNode(applyOp, inputNode, applyNode);
//
// Create and return a new projection that unifies the vars produced by the input and apply columns into a single record constructor.
//
return ProjectNewRecord(
retNode,
ExtractElementRowType(e.ResultType),
new Var[] { _varMap[inputNode], _varMap[applyNode] }
);
}
public override Node Visit(DbGroupByExpression e)
{
#if DEBUG
// !!! IsCollectionOfRecord() is defined only in DEBUG !!!
PlanCompiler.Assert(IsCollectionOfRecord(e.ResultType), "DbGroupByExpression has invalid result Type (not record collection)");
#endif
//
// Process the input and the keys
//
VarVec keyVarSet = _iqtCommand.CreateVarVec();
VarVec outputVarSet = _iqtCommand.CreateVarVec();
Node inputNode;
List<Node> keyVarDefNodes;
ExpressionBindingScope scope;
ExtractKeys(e, keyVarSet, outputVarSet, out inputNode, out keyVarDefNodes, out scope);
// Get the index of the group aggregate if any
int groupAggregateIndex = -1;
for (int i = 0; i < e.Aggregates.Count; i++)
{
if (e.Aggregates[i].GetType() == typeof(DbGroupAggregate))
{
groupAggregateIndex = i;
break;
}
}
//
//If there is a group aggregate, create a copy of the input
//
Node copyOfInput = null;
List<Node> copyOfKeyVarDefNodes = null;
VarVec copyOutputVarSet = _iqtCommand.CreateVarVec();
VarVec copyKeyVarSet = _iqtCommand.CreateVarVec();
if (groupAggregateIndex >= 0)
{
ExpressionBindingScope copyOfScope; //not needed
ExtractKeys(e, copyKeyVarSet, copyOutputVarSet, out copyOfInput, out copyOfKeyVarDefNodes, out copyOfScope);
}
//
// Bring the Input variable from the DbGroupByExpression into scope
//
scope = new ExpressionBindingScope(_iqtCommand, e.Input.GroupVariableName, scope.ScopeVar);
_varScopes.Push(scope);
//
// Process the Aggregates: For each DbAggregate, produce the corresponding IQT conversion depending on whether the DbAggregate is a DbFunctionAggregate or DbGroupAggregate.
// The converted Node is then used as the child node of a VarDefOp Node that is added to a list of Aggregate VarDefs or Group Aggregate VarDefs correspondingly.
// The Var defined by the converted DbAggregate is added only to the overall list of Vars produced by the GroupBy (not the list of Keys).
//
List<Node> aggVarDefNodes = new List<Node>();
Node groupAggDefNode = null;
for(int idx = 0; idx < e.Aggregates.Count; idx++)
{
DbAggregate agg = e.Aggregates[idx];
Var aggVar;
//
// Produce the converted form of the Arguments to the aggregate
//
IList<Node> argNodes = VisitExprAsScalar(agg.Arguments);
//
// Handle if it is DbFunctionAggregate
//
if (idx != groupAggregateIndex)
{
DbFunctionAggregate funcAgg = agg as DbFunctionAggregate;
PlanCompiler.Assert(funcAgg != null, "Unrecognized DbAggregate used in DbGroupByExpression");
aggVarDefNodes.Add(ProcessFunctionAggregate(funcAgg, argNodes, out aggVar));
}
//
// Handle if it is DbGroupAggregate
//
else
{
groupAggDefNode = ProcessGroupAggregate(keyVarDefNodes, copyOfInput, copyOfKeyVarDefNodes, copyKeyVarSet, e.Input.Expression.ResultType, out aggVar);
}
outputVarSet.Set(aggVar);
}
//
// The Aggregates have now been processed, so remove the group variable from scope.
//
ExitGroupExpressionBinding();
//
// Construct the GroupBy. This consists of a GroupByOp (or GroupByIntoOp) with 3 (or 4) children:
// 1. The Node produced from the Input set
// 2. A VarDefListOp Node that uses the Key VarDefs to define the Key Vars (created above)
// 3. A VarDefListOp Node that uses the Aggregate VarDefs to define the Aggregate Vars (created above)
// 4. For a GroupByIntoOp a verDefLIstOp Node with a single var def node that defines the group aggregate
//
List<Node> groupByChildren = new List<Node>();
groupByChildren.Add(inputNode); // The Node produced from the Input set
groupByChildren.Add( // The Key VarDefs
_iqtCommand.CreateNode(
_iqtCommand.CreateVarDefListOp(),
keyVarDefNodes
));
groupByChildren.Add( // The Aggregate VarDefs
_iqtCommand.CreateNode(
_iqtCommand.CreateVarDefListOp(),
aggVarDefNodes
));
GroupByBaseOp op;
if (groupAggregateIndex >= 0)
{
groupByChildren.Add( // The GroupAggregate VarDef
_iqtCommand.CreateNode(
_iqtCommand.CreateVarDefListOp(),
groupAggDefNode
));
op = _iqtCommand.CreateGroupByIntoOp(keyVarSet, this._iqtCommand.CreateVarVec(_varMap[inputNode]), outputVarSet);
}
else
{
op = _iqtCommand.CreateGroupByOp(keyVarSet, outputVarSet);
}
Node groupByNode = _iqtCommand.CreateNode(
op, groupByChildren);
//
// Create and return a projection that unifies the multiple output vars of the GroupBy into a single record constructor.
//
return ProjectNewRecord(
groupByNode,
ExtractElementRowType(e.ResultType),
outputVarSet //todo: it is not correct to pass a varvec where an ordered list is expected
);
}
private void ExtractKeys(DbGroupByExpression e, VarVec keyVarSet, VarVec outputVarSet, out Node inputNode, out List<Node> keyVarDefNodes, out ExpressionBindingScope scope)
{
inputNode = EnterGroupExpressionBinding(e.Input);
//
// Process the Keys: For each Key, produce the corresponding IQT conversion.
// The converted Node is then used as the child node of a VarDefOp Node that is
// added to a list of Key VarDefs. The Var defined by the converted Key expression
// is added to both the overall list of Vars produced by the GroupBy and the list of Key vars produced by the GroupBy.
//
keyVarDefNodes = new List<Node>();
for (int idx = 0; idx < e.Keys.Count; idx++)
{
DbExpression keyExpr = e.Keys[idx];
Node keyNode = VisitExprAsScalar(keyExpr);
ScalarOp keyOp = keyNode.Op as ScalarOp;
//
// In a valid CQT, each group key expressions will result in a ScalarOp since they
// must be of an equality comparable type.
//
PlanCompiler.Assert(keyOp != null, "GroupBy Key is not a ScalarOp");
//
// Create a ComputedVar with the same type as the Key and add it to both the set of output Vars produced by the GroupBy and the set of Key vars.
//
Var keyVar;
//
// Create a VarDefOp that uses the converted form of the Key to define the ComputedVar and add it to the list of Key VarDefs.
//
keyVarDefNodes.Add(_iqtCommand.CreateVarDefNode(keyNode, out keyVar));
outputVarSet.Set(keyVar);
keyVarSet.Set(keyVar);
}
//
// Before the Aggregates are processed, the Input variable must be taken out of scope and the 'group' variable introduced into scope in its place
// This is done as follows:
// 1. Pop the current ExpressionBindingScope from the stack
// 2. Create a new ExpressionBindingScope using the same Var but the name of the 'group' variable from the DbGroupByExpression's DbGroupExpressionBinding
// 3. Push this new scope onto the variable scope stack.
//
scope = ExitExpressionBinding();
}
private Node ProcessFunctionAggregate(DbFunctionAggregate funcAgg, IList<Node> argNodes, out Var aggVar)
{
Node aggNode = _iqtCommand.CreateNode(
_iqtCommand.CreateAggregateOp(funcAgg.Function, funcAgg.Distinct),
argNodes
);
//
// Create a VarDefOp that uses the converted form of the DbAggregate to define the ComputedVar
//
return _iqtCommand.CreateVarDefNode(aggNode, out aggVar);
}
/// <summary>
/// Translation for GroupAggregate
///
/// Create the translation as :
///
/// Collect
/// |
/// PhysicalProject
/// |
/// GroupNodeDefinition
///
/// Here, GroupNodeDefinition is:
/// 1. If there are no keys: copyOfInput;
/// 2. If there are keys:
///
/// Filter (keyDef1 = copyOfKeyDef1 or keyDef1 is null and copyOfKeyDef1 is null) and ... and (keyDefn = copyOfKeyDefn or keyDefn is null and copyOfKeyDefn is null)
/// |
/// Project (copyOfInput, copyOfKeyDef1, copyOfKeyDef1, ... copyOfKeyDefn)
/// |
/// copyOfInput
///
/// </summary>
/// <param name="keyVarDefNodes"></param>
/// <param name="copyOfInput"></param>
/// <param name="copyOfkeyVarDefNodes"></param>
/// <param name="copyKeyVarSet"></param>
/// <param name="inputResultType"></param>
/// <param name="groupAggVar"></param>
/// <returns></returns>
private Node ProcessGroupAggregate(List<Node> keyVarDefNodes, Node copyOfInput, List<Node> copyOfkeyVarDefNodes, VarVec copyKeyVarSet, TypeUsage inputResultType, out Var groupAggVar)
{
Var inputVar = this._varMap[copyOfInput];
Node groupDefNode = copyOfInput;
if (keyVarDefNodes.Count > 0)
{
VarVec projectOutpus = _iqtCommand.CreateVarVec();
projectOutpus.Set(inputVar);
projectOutpus.Or(copyKeyVarSet);
Node projectNodeWithKeys = _iqtCommand.CreateNode(
_iqtCommand.CreateProjectOp(projectOutpus),
groupDefNode, //the input
_iqtCommand.CreateNode( //the key var defs
_iqtCommand.CreateVarDefListOp(),
copyOfkeyVarDefNodes
));
List<Node> flattentedKeys = new List<Node>();
List<Node> copyFlattenedKeys = new List<Node>();
for (int i = 0; i < keyVarDefNodes.Count; i++)
{
Node keyVarDef = keyVarDefNodes[i];
Node copyOfKeyVarDef = copyOfkeyVarDefNodes[i];
Var keyVar = ((VarDefOp)keyVarDef.Op).Var;
Var copyOfKeyVar = ((VarDefOp)copyOfKeyVarDef.Op).Var;
//
// The keys of type row need to be flattened, because grouping by a row means grouping by its individual
// members and thus we have to check the individual members whether they are null.
// IsNull(x) where x is a row type does not mean whether the individual properties of x are null,
// but rather whether the entire row is null.
//
FlattenProperties(_iqtCommand.CreateNode(_iqtCommand.CreateVarRefOp(keyVar)), flattentedKeys);
FlattenProperties(_iqtCommand.CreateNode(_iqtCommand.CreateVarRefOp(copyOfKeyVar)), copyFlattenedKeys);
}
PlanCompiler.Assert(flattentedKeys.Count == copyFlattenedKeys.Count, "The flattened keys lists should have the same nubmer of elements");
Node filterPredicateNode = null;
for(int j = 0; j< flattentedKeys.Count; j++)
{
Node keyNode = flattentedKeys[j];
Node copyKeyNode = copyFlattenedKeys[j];
//
// Create the predicate for a single key
// keyVar = copyOfKeyVar or keyVar is null and copyOfKeyVar is null
//
Node predicate = _iqtCommand.CreateNode(
_iqtCommand.CreateConditionalOp(OpType.Or),
_iqtCommand.CreateNode(
_iqtCommand.CreateComparisonOp(OpType.EQ), keyNode, copyKeyNode),
_iqtCommand.CreateNode(
_iqtCommand.CreateConditionalOp(OpType.And),
_iqtCommand.CreateNode(
_iqtCommand.CreateConditionalOp(OpType.IsNull),
OpCopier.Copy(_iqtCommand, keyNode)),
_iqtCommand.CreateNode(
_iqtCommand.CreateConditionalOp(OpType.IsNull),
OpCopier.Copy(_iqtCommand, copyKeyNode))));
if (filterPredicateNode == null)
{
filterPredicateNode = predicate;
}
else
{
filterPredicateNode = _iqtCommand.CreateNode(
_iqtCommand.CreateConditionalOp(OpType.And),
filterPredicateNode, predicate);
}
}
Node filterNode = _iqtCommand.CreateNode(
_iqtCommand.CreateFilterOp(), projectNodeWithKeys, filterPredicateNode);
groupDefNode = filterNode;
}
//Cap with Collect over PhysicalProject
_varMap[groupDefNode] = inputVar;
groupDefNode = ConvertRelOpToScalarOpTree(groupDefNode, inputResultType);
Node result = _iqtCommand.CreateVarDefNode(groupDefNode, out groupAggVar);
return result;
}
/// <summary>
/// If the return type of the input node is a RowType it flattens its individual non-row properties.
/// The produced nodes are added to the given flattenedProperties list
/// </summary>
/// <param name="input"></param>
/// <param name="flattenedProperties"></param>
private void FlattenProperties(Node input, IList<Node> flattenedProperties)
{
if (input.Op.Type.EdmType.BuiltInTypeKind == BuiltInTypeKind.RowType)
{
IList<EdmProperty> properties = TypeHelpers.GetProperties(input.Op.Type);
PlanCompiler.Assert(properties.Count != 0, "No nested properties for RowType");
for (int i = 0; i < properties.Count; i++)
{
Node newInput = (i == 0) ? input : OpCopier.Copy(_iqtCommand, input);
FlattenProperties(_iqtCommand.CreateNode(_iqtCommand.CreatePropertyOp(properties[i]), newInput), flattenedProperties);
}
}
else
{
flattenedProperties.Add(input);
}
}
/// <summary>
/// Common processing for the identical input and sort order arguments to the unrelated
/// DbSkipExpression and DbSortExpression types.
/// </summary>
/// <param name="input">The input DbExpressionBinding from the DbSkipExpression or DbSortExpression</param>
/// <param name="sortOrder">The list of SortClauses from the DbSkipExpression or DbSortExpression</param>
/// <param name="sortKeys">A list to contain the converted SortKeys produced from the SortClauses</param>
/// <param name="inputVar">The Var produced by the input to the DbSkipExpression or DbSortExpression</param>
/// <returns>
/// The converted form of the input to the DbSkipExpression or DbSortExpression, capped by a
/// ProjectOp that defines and Vars referenced by the SortKeys.
/// </returns>
private Node VisitSortArguments(DbExpressionBinding input, IList<DbSortClause> sortOrder, List<SortKey> sortKeys, out Var inputVar)
{
//
// Skip/DbSortExpression conversion first produces a ProjectOp over the original input.
// This is done to ensure that the new (Constrained)SortOp itself does not
// contain any local variable definitions (in the form of a VarDefList child node)
// which makes it simpler to pull SortOps over ProjectOps later in the PlanCompiler
// (specifically the PreProcessor).
// The new ProjectOp projects the output Var of the input along with any Vars referenced
// by the SortKeys, and its VarDefList child defines those Vars.
//
// Bring the variable defined by the DbSortExpression's input set into scope
// and retrieve it from the Node => Var map for later use.
//
Node inputNode = EnterExpressionBinding(input);
inputVar = _varMap[inputNode];
//
// Convert the SortClauses, building a new VarDefOp Node for each one.
//
VarVec projectedVars = _iqtCommand.CreateVarVec();
projectedVars.Set(inputVar);
List<Node> sortVarDefs = new List<Node>();
PlanCompiler.Assert(sortKeys.Count == 0, "Non-empty SortKey list before adding converted SortClauses");
for (int idx = 0; idx < sortOrder.Count; idx++)
{
DbSortClause clause = sortOrder[idx];
//
// Convert the DbSortClause DbExpression to a Node/Op pair
//
Node exprNode = VisitExprAsScalar(clause.Expression);
//
// In a valid CQT, DbSortClause expressions must have a result of an OrderComparable Type,
// and such expressions will always convert to ScalarOps.
//
ScalarOp specOp = exprNode.Op as ScalarOp;
PlanCompiler.Assert(specOp != null, "DbSortClause Expression converted to non-ScalarOp");
//
// Create a new ComputedVar with the same Type as the result Type of the DbSortClause DbExpression
//
Var specVar;
//
// Create a new VarDefOp Node that defines the ComputedVar and add it both to the
// list of VarDefs and the VarVec of produced Vars that will be used to create a
// SortKey-defining ProjectOp over the Sort input.
//
sortVarDefs.Add(_iqtCommand.CreateVarDefNode(exprNode, out specVar));
projectedVars.Set(specVar);
//
// Create a new IQT SortKey that references the ComputedVar and has the same
// Ascending and Collation as the original DbSortClause, then add it to the list of SortKeys.
//
SortKey sortKey = null;
if (string.IsNullOrEmpty(clause.Collation))
{
sortKey = Command.CreateSortKey(specVar, clause.Ascending);
}
else
{
sortKey = Command.CreateSortKey(specVar, clause.Ascending, clause.Collation);
}
sortKeys.Add(sortKey);
}
//
// Now that the SortClauses have been converted, remove the Input set's variable from scope.
//
ExitExpressionBinding();
//
// Cap the Input with a ProjectOp that pushes the sort key VarDefs down to that projection.
//
inputNode =
_iqtCommand.CreateNode(
_iqtCommand.CreateProjectOp(projectedVars),
inputNode,
_iqtCommand.CreateNode(
_iqtCommand.CreateVarDefListOp(),
sortVarDefs
)
);
return inputNode;
}
public override Node Visit(DbSkipExpression expression)
{
//
// Invoke common processing of Skip/DbSortExpression arguments.
//
Var inputVar;
List<SortKey> sortKeys = new List<SortKey>();
Node inputNode = VisitSortArguments(expression.Input, expression.SortOrder, sortKeys, out inputVar);
//
// Visit the Skip Count
//
Node countNode = VisitExprAsScalar(expression.Count);
//
// Create a new Node that has a new ConstrainedSortOp based on the SortKeys as its Op
// and the following children:
// - The Input node from VisitSortArguments
// - The converted form of the skip count
// - A NullOp of type Int64 to indicate that no limit operation is applied
//
Node skipNode =
_iqtCommand.CreateNode(
_iqtCommand.CreateConstrainedSortOp(sortKeys),
inputNode,
countNode,
_iqtCommand.CreateNode(_iqtCommand.CreateNullOp(_iqtCommand.IntegerType))
);
// Update the Node --> Var mapping for the new ConstrainedSort Node.
// ConstrainedSortOp maps to the same Op that its RelOp input maps to.
_varMap[skipNode] = inputVar;
return skipNode;
}
public override Node Visit(DbSortExpression e)
{
//
// Invoke common processing of Skip/DbSortExpression arguments.
//
Var inputVar;
List<SortKey> sortKeys = new List<SortKey>();
Node inputNode = VisitSortArguments(e.Input, e.SortOrder, sortKeys, out inputVar);
//
// Create a new SortOp that uses the constructed SortKeys.
//
SortOp newSortOp = _iqtCommand.CreateSortOp(sortKeys);
//
// Create a new SortOp Node that has the new SortOp as its Op the Key-defining ProjectOp Node as its only child.
//
Node newSortNode = _iqtCommand.CreateNode(newSortOp, inputNode);
// Update the Node --> Var mapping for the new Sort Node.
// SortOp maps to the same Op that its RelOp input maps to.
_varMap[newSortNode] = inputVar;
return newSortNode;
}
public override Node Visit(DbQuantifierExpression e)
{
Node retNode = null;
//
// Any converts to Exists(Filter(Input, Predicate))
// All converts to Not(Exists(Filter(Input, Or(Not(Predicate), IsNull(Predicate)))))
//
PlanCompiler.Assert(DbExpressionKind.Any == e.ExpressionKind || DbExpressionKind.All == e.ExpressionKind, "Invalid DbExpressionKind in DbQuantifierExpression");
//
// Bring the input's variable into scope
//
Node inputNode = EnterExpressionBinding(e.Input);
//
// Convert the predicate
//
Node predicateNode = VisitExprAsPredicate(e.Predicate);
//
// If the quantifier is All then the predicate must become 'Not(Predicate) Or IsNull(Predicate)',
// since the converted form of the predicate should exclude a member of the input set if and only if
// the predicate evaluates to False - filtering only with the negated predicate would also exclude members
// for which that negated predicate evaluates to null, possibly resulting in an erroneous empty result set
// and causing the quantifier to produce a false positive result.
//
if (DbExpressionKind.All == e.ExpressionKind)
{
// Create the 'Not(Predicate)' branch of the Or.
predicateNode = _iqtCommand.CreateNode(
_iqtCommand.CreateConditionalOp(OpType.Not),
predicateNode
);
// Visit the original predicate for use in the 'IsNull(Predicate)' branch of the Or.
// Note that this is treated as a scalar value rather than a Boolean predicate.
Node predicateCopy = VisitExprAsScalar(e.Predicate);
// Create the 'IsNull(Predicate)' branch of the Or.
predicateCopy = _iqtCommand.CreateNode(
_iqtCommand.CreateConditionalOp(OpType.IsNull),
predicateCopy
);
// Finally, combine the branches with a Boolean 'Or' Op to create the updated predicate node.
predicateNode = _iqtCommand.CreateNode(
_iqtCommand.CreateConditionalOp(OpType.Or),
predicateNode,
predicateCopy
);
}
//
// Remove the input's variable from scope
//
ExitExpressionBinding();
//
// Create a FilterOp around the original input set and map the FilterOp to the Var produced by the original input set.
//
Var inputVar = _varMap[inputNode];
inputNode = _iqtCommand.CreateNode(_iqtCommand.CreateFilterOp(), inputNode, predicateNode);
_varMap[inputNode] = inputVar;
//
// Create an ExistsOp around the filtered set to perform the quantifier operation.
//
retNode = _iqtCommand.CreateNode(_iqtCommand.CreateExistsOp(), inputNode);
//
// For All, the exists operation as currently built must now be negated.
//
if (DbExpressionKind.All == e.ExpressionKind)
{
retNode = _iqtCommand.CreateNode(_iqtCommand.CreateConditionalOp(OpType.Not), retNode);
}
return retNode;
}
#endregion
}
}
|