2016-08-03 10:59:49 +00:00
|
|
|
|
//---------------------------------------------------------------------
|
|
|
|
|
// <copyright file="AggregatePushdown.cs" company="Microsoft">
|
|
|
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
|
|
|
// </copyright>
|
|
|
|
|
//
|
2017-08-21 15:34:15 +00:00
|
|
|
|
// @owner Microsoft
|
|
|
|
|
// @backupOwner Microsoft
|
2016-08-03 10:59:49 +00:00
|
|
|
|
//---------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Data.Common;
|
|
|
|
|
//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.
|
|
|
|
|
|
|
|
|
|
using System.Globalization;
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
|
|
|
|
using System.Data.Metadata.Edm;
|
|
|
|
|
using System.Data.Query.InternalTrees;
|
|
|
|
|
|
|
|
|
|
namespace System.Data.Query.PlanCompiler
|
|
|
|
|
{
|
|
|
|
|
internal delegate bool TryGetValue(Node key, out Node value);
|
|
|
|
|
|
|
|
|
|
#region Helper Classes
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Helper class to track the aggregate nodes that are candidates to be
|
|
|
|
|
/// pushed into the definingGroupByNode.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal class GroupAggregateVarInfo
|
|
|
|
|
{
|
|
|
|
|
#region Private Fields
|
|
|
|
|
private readonly Node _definingGroupByNode;
|
|
|
|
|
private HashSet<KeyValuePair<Node, Node>> _candidateAggregateNodes;
|
|
|
|
|
private readonly Var _groupAggregateVar;
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Constructor
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Public constructor
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="defingingGroupNode">The GroupIntoOp node</param>
|
|
|
|
|
/// <param name="groupAggregateVar"></param>
|
|
|
|
|
internal GroupAggregateVarInfo(Node defingingGroupNode, Var groupAggregateVar)
|
|
|
|
|
{
|
|
|
|
|
_definingGroupByNode = defingingGroupNode;
|
|
|
|
|
_groupAggregateVar = groupAggregateVar;
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region 'Public' Properties
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Each key value pair represents a candidate aggregate.
|
|
|
|
|
/// The key is the function aggregate subtree and the value is a 'template' of translation of the
|
|
|
|
|
/// function aggregate's argument over the var representing the group aggregate.
|
|
|
|
|
/// A valid candidate has an argument that does not have any external references
|
|
|
|
|
/// except for the group aggregate corresponding to the DefiningGroupNode.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal HashSet<KeyValuePair<Node, Node>> CandidateAggregateNodes
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
if (_candidateAggregateNodes == null)
|
|
|
|
|
{
|
|
|
|
|
_candidateAggregateNodes = new HashSet<KeyValuePair<Node, Node>>();
|
|
|
|
|
}
|
|
|
|
|
return _candidateAggregateNodes;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Are there are agregates that are candidates to be pushed into the DefiningGroupNode
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal bool HasCandidateAggregateNodes
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
return (_candidateAggregateNodes != null && _candidateAggregateNodes.Count != 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The GroupIntoOp node that this GroupAggregateVarInfo represents
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal Node DefiningGroupNode
|
|
|
|
|
{
|
|
|
|
|
get { return _definingGroupByNode; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal Var GroupAggregateVar
|
|
|
|
|
{
|
|
|
|
|
get { return _groupAggregateVar; }
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Helper class to track usage of GroupAggregateVarInfo
|
|
|
|
|
/// It represents the usage of a single GroupAggregateVar.
|
|
|
|
|
/// The usage is defined by the computation, it should be a subree whose only
|
|
|
|
|
/// external reference is the group var represented by the GroupAggregateVarInfo.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal class GroupAggregateVarRefInfo
|
|
|
|
|
{
|
|
|
|
|
#region Private fields
|
|
|
|
|
private readonly Node _computation;
|
|
|
|
|
private readonly GroupAggregateVarInfo _groupAggregateVarInfo;
|
|
|
|
|
private readonly bool _isUnnested;
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Constructor
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Public constructor
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="groupAggregateVarInfo"></param>
|
|
|
|
|
/// <param name="computation"></param>
|
|
|
|
|
internal GroupAggregateVarRefInfo(GroupAggregateVarInfo groupAggregateVarInfo, Node computation, bool isUnnested)
|
|
|
|
|
{
|
|
|
|
|
this._groupAggregateVarInfo = groupAggregateVarInfo;
|
|
|
|
|
this._computation = computation;
|
|
|
|
|
this._isUnnested = isUnnested;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region 'Public' Properties
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Subtree whose only external reference is
|
|
|
|
|
/// the group var represented by the GroupAggregateVarInfo
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal Node Computation
|
|
|
|
|
{
|
|
|
|
|
get { return _computation; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The GroupAggregateVarInfo (possibly) referenced by the computation
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal GroupAggregateVarInfo GroupAggregateVarInfo
|
|
|
|
|
{
|
|
|
|
|
get { return _groupAggregateVarInfo; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Is the computation over unnested group aggregate var
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal bool IsUnnested
|
|
|
|
|
{
|
|
|
|
|
get { return _isUnnested; }
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Manages refereces to groupAggregate variables.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal class GroupAggregateVarInfoManager
|
|
|
|
|
{
|
|
|
|
|
#region Private state
|
|
|
|
|
private readonly Dictionary<Var, GroupAggregateVarRefInfo> _groupAggregateVarRelatedVarToInfo = new Dictionary<Var, GroupAggregateVarRefInfo>();
|
|
|
|
|
private Dictionary<Var, Dictionary<EdmMember, GroupAggregateVarRefInfo>> _groupAggregateVarRelatedVarPropertyToInfo;
|
|
|
|
|
private HashSet<GroupAggregateVarInfo> _groupAggregateVarInfos = new HashSet<GroupAggregateVarInfo>();
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Public Surface
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Get all the groupAggregateVarInfos
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal IEnumerable<GroupAggregateVarInfo> GroupAggregateVarInfos
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
return _groupAggregateVarInfos;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Add an entry that var is a computation represented by the computationTemplate
|
|
|
|
|
/// over the var represented by the given groupAggregateVarInfo
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="var"></param>
|
|
|
|
|
/// <param name="groupAggregateVarInfo"></param>
|
|
|
|
|
/// <param name="computationTemplate"></param>
|
|
|
|
|
/// <param name="isUnnested"></param>
|
|
|
|
|
internal void Add(Var var, GroupAggregateVarInfo groupAggregateVarInfo, Node computationTemplate, bool isUnnested)
|
|
|
|
|
{
|
|
|
|
|
this._groupAggregateVarRelatedVarToInfo.Add(var, new GroupAggregateVarRefInfo(groupAggregateVarInfo, computationTemplate, isUnnested));
|
|
|
|
|
_groupAggregateVarInfos.Add(groupAggregateVarInfo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Add an entry that the given property of the given var is a computation represented
|
|
|
|
|
/// by the computationTemplate over the var represented by the given groupAggregateVarInfo
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="var"></param>
|
|
|
|
|
/// <param name="groupAggregateVarInfo"></param>
|
|
|
|
|
/// <param name="computationTemplate"></param>
|
|
|
|
|
/// <param name="isUnnested"></param>
|
|
|
|
|
/// <param name="property"></param>
|
|
|
|
|
internal void Add(Var var, GroupAggregateVarInfo groupAggregateVarInfo, Node computationTemplate, bool isUnnested, EdmMember property)
|
|
|
|
|
{
|
|
|
|
|
if (property == null)
|
|
|
|
|
{
|
|
|
|
|
Add(var, groupAggregateVarInfo, computationTemplate, isUnnested);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (this._groupAggregateVarRelatedVarPropertyToInfo == null)
|
|
|
|
|
{
|
|
|
|
|
this._groupAggregateVarRelatedVarPropertyToInfo = new Dictionary<Var, Dictionary<System.Data.Metadata.Edm.EdmMember, GroupAggregateVarRefInfo>>();
|
|
|
|
|
}
|
|
|
|
|
Dictionary<EdmMember, GroupAggregateVarRefInfo> varPropertyDictionary;
|
|
|
|
|
if (!_groupAggregateVarRelatedVarPropertyToInfo.TryGetValue(var, out varPropertyDictionary))
|
|
|
|
|
{
|
|
|
|
|
varPropertyDictionary = new Dictionary<System.Data.Metadata.Edm.EdmMember, GroupAggregateVarRefInfo>();
|
|
|
|
|
_groupAggregateVarRelatedVarPropertyToInfo.Add(var, varPropertyDictionary);
|
|
|
|
|
}
|
|
|
|
|
varPropertyDictionary.Add(property, new GroupAggregateVarRefInfo(groupAggregateVarInfo, computationTemplate, isUnnested));
|
|
|
|
|
|
|
|
|
|
// Note: The following line is not necessary with the current usage pattern, this method is
|
|
|
|
|
// never called with a new groupAggregateVarInfo thus it is a no-op.
|
|
|
|
|
_groupAggregateVarInfos.Add(groupAggregateVarInfo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the groupAggregateVarRefInfo representing the definition of the given var over
|
|
|
|
|
/// a group aggregate var if any.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="var"></param>
|
|
|
|
|
/// <param name="groupAggregateVarRefInfo"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
internal bool TryGetReferencedGroupAggregateVarInfo(Var var, out GroupAggregateVarRefInfo groupAggregateVarRefInfo)
|
|
|
|
|
{
|
|
|
|
|
return this._groupAggregateVarRelatedVarToInfo.TryGetValue(var, out groupAggregateVarRefInfo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the groupAggregateVarRefInfo representing the definition of the given property of the given
|
|
|
|
|
/// var over a group aggregate var if any.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="var"></param>
|
|
|
|
|
/// <param name="property"></param>
|
|
|
|
|
/// <param name="groupAggregateVarRefInfo"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
internal bool TryGetReferencedGroupAggregateVarInfo(Var var, EdmMember property, out GroupAggregateVarRefInfo groupAggregateVarRefInfo)
|
|
|
|
|
{
|
|
|
|
|
if (property == null)
|
|
|
|
|
{
|
|
|
|
|
return TryGetReferencedGroupAggregateVarInfo(var, out groupAggregateVarRefInfo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Dictionary<EdmMember, GroupAggregateVarRefInfo> varPropertyDictionary;
|
|
|
|
|
if (_groupAggregateVarRelatedVarPropertyToInfo == null || !_groupAggregateVarRelatedVarPropertyToInfo.TryGetValue(var, out varPropertyDictionary))
|
|
|
|
|
{
|
|
|
|
|
groupAggregateVarRefInfo = null;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return varPropertyDictionary.TryGetValue(property, out groupAggregateVarRefInfo);
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Utility class that tries to produce an equivalent tree to the input tree over
|
|
|
|
|
/// a single group aggregate variable and no other external references
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal class GroupAggregateVarComputationTranslator : BasicOpVisitorOfNode
|
|
|
|
|
{
|
|
|
|
|
#region Private State
|
|
|
|
|
private GroupAggregateVarInfo _targetGroupAggregateVarInfo;
|
|
|
|
|
private bool _isUnnested;
|
|
|
|
|
private readonly Command _command;
|
|
|
|
|
private readonly GroupAggregateVarInfoManager _groupAggregateVarInfoManager;
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Constructor
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Private constructor
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="command"></param>
|
|
|
|
|
/// <param name="groupAggregateVarInfoManager"></param>
|
|
|
|
|
private GroupAggregateVarComputationTranslator(
|
|
|
|
|
Command command,
|
|
|
|
|
GroupAggregateVarInfoManager groupAggregateVarInfoManager)
|
|
|
|
|
{
|
|
|
|
|
this._command = command;
|
|
|
|
|
this._groupAggregateVarInfoManager = groupAggregateVarInfoManager;
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region 'Public' Surface
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Try to produce an equivalent tree to the input subtree, over a single group aggregate variable.
|
|
|
|
|
/// Such translation can only be produced if all external references of the input subtree are to a
|
|
|
|
|
/// single group aggregate var, or to vars that are can be translated over that single group
|
|
|
|
|
/// aggregate var
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="subtree">The input subtree</param>
|
|
|
|
|
/// <param name="isVarDefinition"></param>
|
|
|
|
|
/// <param name="command"></param>
|
|
|
|
|
/// <param name="groupAggregateVarInfoManager"></param>
|
|
|
|
|
/// <param name="groupAggregateVarInfo">The groupAggregateVarInfo over which the input subtree can be translated </param>
|
|
|
|
|
/// <param name="templateNode">A tree that is equvalent to the input tree, but over the group aggregate variable
|
|
|
|
|
/// represented by the groupAggregetVarInfo</param>
|
|
|
|
|
/// <param name="isUnnested"></param>
|
|
|
|
|
/// <returns>True, if the translation can be done, false otherwise</returns>
|
|
|
|
|
public static bool TryTranslateOverGroupAggregateVar(
|
|
|
|
|
Node subtree,
|
|
|
|
|
bool isVarDefinition,
|
|
|
|
|
Command command,
|
|
|
|
|
GroupAggregateVarInfoManager groupAggregateVarInfoManager,
|
|
|
|
|
out GroupAggregateVarInfo groupAggregateVarInfo,
|
|
|
|
|
out Node templateNode,
|
|
|
|
|
out bool isUnnested)
|
|
|
|
|
{
|
|
|
|
|
GroupAggregateVarComputationTranslator handler = new GroupAggregateVarComputationTranslator(command, groupAggregateVarInfoManager);
|
|
|
|
|
|
|
|
|
|
Node inputNode = subtree;
|
|
|
|
|
SoftCastOp softCastOp = null;
|
|
|
|
|
bool isCollect;
|
|
|
|
|
if (inputNode.Op.OpType == OpType.SoftCast)
|
|
|
|
|
{
|
|
|
|
|
softCastOp = (SoftCastOp)inputNode.Op;
|
|
|
|
|
inputNode = inputNode.Child0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (inputNode.Op.OpType == OpType.Collect)
|
|
|
|
|
{
|
|
|
|
|
templateNode = handler.VisitCollect(inputNode);
|
|
|
|
|
isCollect = true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
templateNode = handler.VisitNode(inputNode);
|
|
|
|
|
isCollect = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
groupAggregateVarInfo = handler._targetGroupAggregateVarInfo;
|
|
|
|
|
isUnnested = handler._isUnnested;
|
|
|
|
|
|
|
|
|
|
if (handler._targetGroupAggregateVarInfo == null || templateNode == null)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (softCastOp != null)
|
|
|
|
|
{
|
|
|
|
|
SoftCastOp newSoftCastOp;
|
|
|
|
|
//
|
|
|
|
|
// The type needs to be fixed only if the unnesting happened during this translation.
|
|
|
|
|
// That can be recognized by these two cases:
|
|
|
|
|
// 1) if the input node was a collect, or
|
|
|
|
|
// 2) if the input did not represent a var definition, but a function aggregate argument and
|
|
|
|
|
// the template is VarRef of a group aggregate var.
|
|
|
|
|
//
|
|
|
|
|
if (isCollect || !isVarDefinition && AggregatePushdownUtil.IsVarRefOverGivenVar(templateNode, handler._targetGroupAggregateVarInfo.GroupAggregateVar))
|
|
|
|
|
{
|
|
|
|
|
newSoftCastOp = command.CreateSoftCastOp(TypeHelpers.GetEdmType<CollectionType>(softCastOp.Type).TypeUsage);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
newSoftCastOp = softCastOp;
|
|
|
|
|
}
|
|
|
|
|
templateNode = command.CreateNode(newSoftCastOp, templateNode);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Visitor Methods
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// See <cref="TryTranslateOverGroupAggregateVar"/>
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="op"></param>
|
|
|
|
|
/// <param name="n"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public override Node Visit(VarRefOp op, Node n)
|
|
|
|
|
{
|
|
|
|
|
return TranslateOverGroupAggregateVar(op.Var, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// If the child is VarRef check if the subtree PropertyOp(VarRef) is reference to a
|
|
|
|
|
/// group aggregate var.
|
|
|
|
|
/// Otherwise do default processing
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="op"></param>
|
|
|
|
|
/// <param name="n"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public override Node Visit(PropertyOp op, Node n)
|
|
|
|
|
{
|
|
|
|
|
if (n.Child0.Op.OpType != OpType.VarRef)
|
|
|
|
|
{
|
|
|
|
|
return base.Visit(op, n);
|
|
|
|
|
}
|
|
|
|
|
VarRefOp varRefOp = (VarRefOp)n.Child0.Op;
|
|
|
|
|
return TranslateOverGroupAggregateVar(varRefOp.Var, op.PropertyInfo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// If the Subtree rooted at the collect is of the following structure:
|
|
|
|
|
///
|
|
|
|
|
/// PhysicalProject(outputVar)
|
|
|
|
|
/// |
|
|
|
|
|
/// Project(s)
|
|
|
|
|
/// |
|
|
|
|
|
/// Unnest
|
|
|
|
|
///
|
|
|
|
|
/// where the unnest is over the group aggregate var and the output var
|
|
|
|
|
/// is either a reference to the group aggregate var or to a constant, it returns the
|
|
|
|
|
/// translation of the ouput var.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="n"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
private Node VisitCollect(Node n)
|
|
|
|
|
{
|
|
|
|
|
//Make sure the only children are projects over unnest
|
|
|
|
|
Node currentNode = n.Child0;
|
|
|
|
|
Dictionary<Var, Node> constantDefinitions = new Dictionary<Var, Node>();
|
|
|
|
|
while (currentNode.Child0.Op.OpType == OpType.Project)
|
|
|
|
|
{
|
|
|
|
|
currentNode = currentNode.Child0;
|
|
|
|
|
//Visit the VarDefListOp child
|
|
|
|
|
if (VisitDefault(currentNode.Child1) == null)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
foreach (Node definitionNode in currentNode.Child1.Children)
|
|
|
|
|
{
|
|
|
|
|
if (IsConstant(definitionNode.Child0))
|
|
|
|
|
{
|
|
|
|
|
constantDefinitions.Add(((VarDefOp)definitionNode.Op).Var, definitionNode.Child0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (currentNode.Child0.Op.OpType != OpType.Unnest)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle the unnest
|
|
|
|
|
UnnestOp unnestOp = (UnnestOp)currentNode.Child0.Op;
|
|
|
|
|
GroupAggregateVarRefInfo groupAggregateVarRefInfo;
|
|
|
|
|
if (_groupAggregateVarInfoManager.TryGetReferencedGroupAggregateVarInfo(unnestOp.Var, out groupAggregateVarRefInfo))
|
|
|
|
|
{
|
|
|
|
|
if (_targetGroupAggregateVarInfo == null)
|
|
|
|
|
{
|
|
|
|
|
_targetGroupAggregateVarInfo = groupAggregateVarRefInfo.GroupAggregateVarInfo;
|
|
|
|
|
}
|
|
|
|
|
else if (_targetGroupAggregateVarInfo != groupAggregateVarRefInfo.GroupAggregateVarInfo)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
if (!_isUnnested)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PhysicalProjectOp physicalProjectOp = (PhysicalProjectOp)n.Child0.Op;
|
|
|
|
|
PlanCompiler.Assert(physicalProjectOp.Outputs.Count == 1, "PhysicalProject should only have one output at this stage");
|
|
|
|
|
Var outputVar = physicalProjectOp.Outputs[0];
|
|
|
|
|
|
|
|
|
|
Node computationTemplate = TranslateOverGroupAggregateVar(outputVar, null);
|
|
|
|
|
if (computationTemplate != null)
|
|
|
|
|
{
|
|
|
|
|
_isUnnested = true;
|
|
|
|
|
return computationTemplate;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Node constantDefinitionNode;
|
|
|
|
|
if (constantDefinitions.TryGetValue(outputVar, out constantDefinitionNode))
|
|
|
|
|
{
|
|
|
|
|
_isUnnested = true;
|
|
|
|
|
return constantDefinitionNode;
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Determines whether the given Node is a constant subtree
|
|
|
|
|
/// It only recognizes any of the constant base ops
|
|
|
|
|
/// and possibly casts over these nodes.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="node"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
private static bool IsConstant(Node node)
|
|
|
|
|
{
|
|
|
|
|
Node currentNode = node;
|
|
|
|
|
while (currentNode.Op.OpType == OpType.Cast)
|
|
|
|
|
{
|
|
|
|
|
currentNode = currentNode.Child0;
|
|
|
|
|
}
|
|
|
|
|
return PlanCompilerUtil.IsConstantBaseOp(currentNode.Op.OpType);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// (1) If the given var or the given property of the given var are defined over a group aggregate var,
|
|
|
|
|
/// (2) and if that group aggregate var matches the var represented by represented by _targetGroupAggregateVarInfo
|
|
|
|
|
/// if any
|
|
|
|
|
///
|
|
|
|
|
/// it returns the corresponding translation over the group aggregate var. Also, if _targetGroupAggregateVarInfo
|
|
|
|
|
/// is not set, it sets it to the group aggregate var representing the referenced var.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="var"></param>
|
|
|
|
|
/// <param name="property"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
private Node TranslateOverGroupAggregateVar(Var var, EdmMember property)
|
|
|
|
|
{
|
|
|
|
|
GroupAggregateVarRefInfo groupAggregateVarRefInfo;
|
|
|
|
|
EdmMember localProperty;
|
|
|
|
|
if (_groupAggregateVarInfoManager.TryGetReferencedGroupAggregateVarInfo(var, out groupAggregateVarRefInfo))
|
|
|
|
|
{
|
|
|
|
|
localProperty = property;
|
|
|
|
|
}
|
|
|
|
|
else if (_groupAggregateVarInfoManager.TryGetReferencedGroupAggregateVarInfo(var, property, out groupAggregateVarRefInfo))
|
|
|
|
|
{
|
|
|
|
|
localProperty = null;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (_targetGroupAggregateVarInfo == null)
|
|
|
|
|
{
|
|
|
|
|
_targetGroupAggregateVarInfo = groupAggregateVarRefInfo.GroupAggregateVarInfo;
|
|
|
|
|
_isUnnested = groupAggregateVarRefInfo.IsUnnested;
|
|
|
|
|
}
|
|
|
|
|
else if (_targetGroupAggregateVarInfo != groupAggregateVarRefInfo.GroupAggregateVarInfo || _isUnnested != groupAggregateVarRefInfo.IsUnnested)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Node computationTemplate = groupAggregateVarRefInfo.Computation;
|
|
|
|
|
if (localProperty != null)
|
|
|
|
|
{
|
|
|
|
|
computationTemplate = this._command.CreateNode(this._command.CreatePropertyOp(localProperty), computationTemplate);
|
|
|
|
|
}
|
|
|
|
|
return computationTemplate;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Default processing for nodes.
|
|
|
|
|
/// Visits the children and if any child has changed it creates a new node
|
|
|
|
|
/// for the parent.
|
|
|
|
|
/// If the reference of the child node did not change, the child node did not change either,
|
|
|
|
|
/// this is because a node can only be reused "as is" when building a template.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="n"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
protected override Node VisitDefault(Node n)
|
|
|
|
|
{
|
|
|
|
|
List<Node> newChildren = new List<Node>(n.Children.Count);
|
|
|
|
|
bool anyChildChanged = false;
|
|
|
|
|
for (int i = 0; i < n.Children.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
Node processedChild = VisitNode(n.Children[i]);
|
|
|
|
|
if (processedChild == null)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
if (!anyChildChanged && !Object.ReferenceEquals(n.Children[i], processedChild))
|
|
|
|
|
{
|
|
|
|
|
anyChildChanged = true;
|
|
|
|
|
}
|
|
|
|
|
newChildren.Add(processedChild);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!anyChildChanged)
|
|
|
|
|
{
|
|
|
|
|
return n;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
return _command.CreateNode(n.Op, newChildren);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#region Unsupported node types
|
|
|
|
|
|
|
|
|
|
protected override Node VisitRelOpDefault(RelOp op, Node n)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override Node Visit(AggregateOp op, Node n)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override Node Visit(CollectOp op, Node n)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override Node Visit(ElementOp op, Node n)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// A visitor that collects all group aggregates and the corresponding function aggregates
|
|
|
|
|
/// that are defined over them, referred to as 'candidate aggregates'. The candidate aggregates are aggregates
|
|
|
|
|
/// that have an argument that has the corresponding group aggregate as the only external reference
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal class GroupAggregateRefComputingVisitor : BasicOpVisitor
|
|
|
|
|
{
|
|
|
|
|
#region private state
|
|
|
|
|
private readonly Command _command;
|
|
|
|
|
private readonly GroupAggregateVarInfoManager _groupAggregateVarInfoManager = new GroupAggregateVarInfoManager();
|
|
|
|
|
private readonly Dictionary<Node, Node> _childToParent = new Dictionary<Node, Node>();
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region 'Public'
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Produces a list of all GroupAggregateVarInfos, each of which represents a single group aggregate
|
|
|
|
|
/// and it candidate function aggregates. It also produces a delegate that given a child node returns the parent node
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="itree"></param>
|
|
|
|
|
/// <param name="tryGetParent"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
internal static IEnumerable<GroupAggregateVarInfo> Process(Command itree, out TryGetValue tryGetParent)
|
|
|
|
|
{
|
|
|
|
|
GroupAggregateRefComputingVisitor groupRefComputingVisitor = new GroupAggregateRefComputingVisitor(itree);
|
|
|
|
|
groupRefComputingVisitor.VisitNode(itree.Root);
|
|
|
|
|
tryGetParent = groupRefComputingVisitor._childToParent.TryGetValue;
|
|
|
|
|
|
|
|
|
|
return groupRefComputingVisitor._groupAggregateVarInfoManager.GroupAggregateVarInfos;
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Private Constructor
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Private constructor
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="itree"></param>
|
|
|
|
|
private GroupAggregateRefComputingVisitor(Command itree)
|
|
|
|
|
{
|
|
|
|
|
this._command = itree;
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Visitor Methods
|
|
|
|
|
|
|
|
|
|
#region AncillaryOps
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Determines whether the var or a property of the var (if the var is defined as a NewRecord)
|
|
|
|
|
/// is defined exclusively over a single group aggregate. If so, it registers it as such with the
|
|
|
|
|
/// group aggregate var info manager.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="op"></param>
|
|
|
|
|
/// <param name="n"></param>
|
|
|
|
|
public override void Visit(VarDefOp op, Node n)
|
|
|
|
|
{
|
|
|
|
|
VisitDefault(n);
|
|
|
|
|
|
|
|
|
|
Node definingNode = n.Child0;
|
|
|
|
|
Op definingNodeOp = definingNode.Op;
|
|
|
|
|
|
|
|
|
|
GroupAggregateVarInfo referencedVarInfo;
|
|
|
|
|
Node templateNode;
|
|
|
|
|
bool isUnnested;
|
|
|
|
|
if (GroupAggregateVarComputationTranslator.TryTranslateOverGroupAggregateVar(definingNode, true, this._command, this._groupAggregateVarInfoManager, out referencedVarInfo, out templateNode, out isUnnested))
|
|
|
|
|
{
|
|
|
|
|
_groupAggregateVarInfoManager.Add(op.Var, referencedVarInfo, templateNode, isUnnested);
|
|
|
|
|
}
|
|
|
|
|
else if (definingNodeOp.OpType == OpType.NewRecord)
|
|
|
|
|
{
|
|
|
|
|
NewRecordOp newRecordOp = (NewRecordOp)definingNodeOp;
|
|
|
|
|
for (int i = 0; i < definingNode.Children.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
Node argumentNode = definingNode.Children[i];
|
|
|
|
|
if (GroupAggregateVarComputationTranslator.TryTranslateOverGroupAggregateVar(argumentNode, true, this._command, this._groupAggregateVarInfoManager, out referencedVarInfo, out templateNode, out isUnnested))
|
|
|
|
|
{
|
|
|
|
|
_groupAggregateVarInfoManager.Add(op.Var, referencedVarInfo, templateNode, isUnnested, newRecordOp.Properties[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region RelOp Visitors
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Registers the group aggregate var with the group aggregate var info manager
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="op"></param>
|
|
|
|
|
/// <param name="n"></param>
|
|
|
|
|
public override void Visit(GroupByIntoOp op, Node n)
|
|
|
|
|
{
|
|
|
|
|
VisitGroupByOp(op, n);
|
|
|
|
|
foreach (Node child in n.Child3.Children)
|
|
|
|
|
{
|
|
|
|
|
Var groupAggregateVar = ((VarDefOp)child.Op).Var;
|
|
|
|
|
GroupAggregateVarRefInfo groupAggregateVarRefInfo;
|
|
|
|
|
// If the group by is over a group, it may be already tracked as referencing a group var
|
|
|
|
|
// An optimization would be to separately track this groupAggregateVar too, for the cases when the aggregate can
|
|
|
|
|
// not be pushed to the group by node over which this one is defined but can be propagated to this group by node.
|
|
|
|
|
if (!_groupAggregateVarInfoManager.TryGetReferencedGroupAggregateVarInfo(groupAggregateVar, out groupAggregateVarRefInfo))
|
|
|
|
|
{
|
|
|
|
|
_groupAggregateVarInfoManager.Add(groupAggregateVar, new GroupAggregateVarInfo(n, groupAggregateVar), this._command.CreateNode(this._command.CreateVarRefOp(groupAggregateVar)), false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// If the unnestOp's var is defined as a reference of a group aggregate var,
|
|
|
|
|
/// then the columns it produces should be registered too, but as 'unnested' references
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="op">the unnestOp</param>
|
|
|
|
|
/// <param name="n">current subtree</param>
|
|
|
|
|
/// <returns>modified subtree</returns>
|
|
|
|
|
public override void Visit(UnnestOp op, Node n)
|
|
|
|
|
{
|
|
|
|
|
VisitDefault(n);
|
|
|
|
|
GroupAggregateVarRefInfo groupAggregateVarRefInfo;
|
|
|
|
|
if (_groupAggregateVarInfoManager.TryGetReferencedGroupAggregateVarInfo(op.Var, out groupAggregateVarRefInfo))
|
|
|
|
|
{
|
|
|
|
|
PlanCompiler.Assert(op.Table.Columns.Count == 1, "Expected one column before NTE");
|
|
|
|
|
_groupAggregateVarInfoManager.Add(op.Table.Columns[0], groupAggregateVarRefInfo.GroupAggregateVarInfo, groupAggregateVarRefInfo.Computation, true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region ScalarOps Visitors
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// If the op is a collection aggregate function it checks whether its arguement can be translated over
|
|
|
|
|
/// a single group aggregate var. If so, it is tracked as a candidate to be pushed into that
|
|
|
|
|
/// group by into node.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="op"></param>
|
|
|
|
|
/// <param name="n"></param>
|
|
|
|
|
public override void Visit(FunctionOp op, Node n)
|
|
|
|
|
{
|
|
|
|
|
VisitDefault(n);
|
|
|
|
|
if (!PlanCompilerUtil.IsCollectionAggregateFunction(op, n))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
PlanCompiler.Assert(n.Children.Count == 1, "Aggregate Function must have one argument");
|
|
|
|
|
|
|
|
|
|
Node argumentNode = n.Child0;
|
|
|
|
|
|
|
|
|
|
GroupAggregateVarInfo referencedGroupAggregateVarInfo;
|
|
|
|
|
Node templateNode;
|
|
|
|
|
bool isUnnested;
|
|
|
|
|
if (GroupAggregateVarComputationTranslator.TryTranslateOverGroupAggregateVar(n.Child0, false, _command, _groupAggregateVarInfoManager, out referencedGroupAggregateVarInfo, out templateNode, out isUnnested)
|
|
|
|
|
&& (isUnnested || AggregatePushdownUtil.IsVarRefOverGivenVar(templateNode, referencedGroupAggregateVarInfo.GroupAggregateVar)))
|
|
|
|
|
{
|
|
|
|
|
referencedGroupAggregateVarInfo.CandidateAggregateNodes.Add(new KeyValuePair<Node, Node>(n, templateNode));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Default visitor for nodes.
|
|
|
|
|
/// It tracks the child-parent relationship.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="n"></param>
|
|
|
|
|
protected override void VisitDefault(Node n)
|
|
|
|
|
{
|
|
|
|
|
VisitChildren(n);
|
|
|
|
|
foreach (Node child in n.Children)
|
|
|
|
|
{
|
|
|
|
|
//No need to track terminal nodes, plus some of these may be reused.
|
|
|
|
|
if (child.Op.Arity != 0)
|
|
|
|
|
{
|
|
|
|
|
_childToParent.Add(child, n);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Utility class to gather helper methods used by more than one class in the Aggregate Pushdown feature.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal static class AggregatePushdownUtil
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Determines whether the given node is a VarRef over the given var
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="node"></param>
|
|
|
|
|
/// <param name="var"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
internal static bool IsVarRefOverGivenVar(Node node, Var var)
|
|
|
|
|
{
|
|
|
|
|
if (node.Op.OpType != OpType.VarRef)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return ((VarRefOp)node.Op).Var == var;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The Aggregate Pushdown feature tries to identify function aggregates defined over a
|
|
|
|
|
/// group aggregate and push their definitions in the group by into node corresponding to
|
|
|
|
|
/// the group aggregate.
|
|
|
|
|
/// </summary>
|
|
|
|
|
internal class AggregatePushdown
|
|
|
|
|
{
|
|
|
|
|
#region Private fields
|
|
|
|
|
private readonly Command m_command;
|
|
|
|
|
private TryGetValue m_tryGetParent;
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Private Constructor
|
|
|
|
|
private AggregatePushdown(Command command)
|
|
|
|
|
{
|
|
|
|
|
this.m_command = command;
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region 'Public' Surface
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Apply Aggregate Pushdown over the tree in the given plan complier state.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="planCompilerState"></param>
|
|
|
|
|
internal static void Process(PlanCompiler planCompilerState)
|
|
|
|
|
{
|
|
|
|
|
AggregatePushdown aggregatePushdown = new AggregatePushdown(planCompilerState.Command);
|
|
|
|
|
aggregatePushdown.Process();
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Private Methods
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// The main driver
|
|
|
|
|
/// </summary>
|
|
|
|
|
private void Process()
|
|
|
|
|
{
|
|
|
|
|
IEnumerable<GroupAggregateVarInfo> groupAggregateVarInfos = GroupAggregateRefComputingVisitor.Process(m_command, out m_tryGetParent);
|
|
|
|
|
foreach (GroupAggregateVarInfo groupAggregateVarInfo in groupAggregateVarInfos)
|
|
|
|
|
{
|
|
|
|
|
if (groupAggregateVarInfo.HasCandidateAggregateNodes)
|
|
|
|
|
{
|
|
|
|
|
foreach (KeyValuePair<Node, Node> candidate in groupAggregateVarInfo.CandidateAggregateNodes)
|
|
|
|
|
{
|
|
|
|
|
TryProcessCandidate(candidate, groupAggregateVarInfo);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Try to push the given function aggregate candidate to the corresponding group into node.
|
|
|
|
|
/// The candidate can be pushed if all ancestors of the group into node up to the least common
|
|
|
|
|
/// ancestor between the group into node and the function aggregate have one of the following node op types:
|
|
|
|
|
/// Project
|
|
|
|
|
/// Filter
|
|
|
|
|
/// ConstraintSortOp
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="command"></param>
|
|
|
|
|
/// <param name="candidate"></param>
|
|
|
|
|
/// <param name="groupAggregateVarInfo"></param>
|
|
|
|
|
/// <param name="m_childToParent"></param>
|
|
|
|
|
private void TryProcessCandidate(
|
|
|
|
|
KeyValuePair<Node, Node> candidate,
|
|
|
|
|
GroupAggregateVarInfo groupAggregateVarInfo)
|
|
|
|
|
{
|
|
|
|
|
IList<Node> functionAncestors;
|
|
|
|
|
IList<Node> groupByAncestors;
|
|
|
|
|
Node definingGroupNode = groupAggregateVarInfo.DefiningGroupNode;
|
|
|
|
|
FindPathsToLeastCommonAncestor(candidate.Key, definingGroupNode, out functionAncestors, out groupByAncestors);
|
|
|
|
|
|
|
|
|
|
//Check whether all ancestors of the GroupByInto node are of type that we support propagating through
|
|
|
|
|
if (!AreAllNodesSupportedForPropagation(groupByAncestors))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Add the function to the group by node
|
|
|
|
|
GroupByIntoOp definingGroupOp = (GroupByIntoOp)definingGroupNode.Op;
|
|
|
|
|
PlanCompiler.Assert(definingGroupOp.Inputs.Count == 1, "There should be one input var to GroupByInto at this stage");
|
|
|
|
|
Var inputVar = definingGroupOp.Inputs.First;
|
|
|
|
|
FunctionOp functionOp = (FunctionOp)candidate.Key.Op;
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Remap the template from referencing the groupAggregate var to reference the input to
|
|
|
|
|
// the group by into
|
|
|
|
|
//
|
|
|
|
|
Node argumentNode = OpCopier.Copy(m_command, candidate.Value);
|
|
|
|
|
Dictionary<Var, Var> dictionary = new Dictionary<Var, Var>(1);
|
|
|
|
|
dictionary.Add(groupAggregateVarInfo.GroupAggregateVar, inputVar);
|
|
|
|
|
VarRemapper remapper = new VarRemapper(m_command, dictionary);
|
|
|
|
|
remapper.RemapSubtree(argumentNode);
|
|
|
|
|
|
|
|
|
|
Node newFunctionDefiningNode = m_command.CreateNode(
|
|
|
|
|
m_command.CreateAggregateOp(functionOp.Function, false),
|
|
|
|
|
argumentNode);
|
|
|
|
|
|
|
|
|
|
Var newFunctionVar;
|
|
|
|
|
Node varDefNode = m_command.CreateVarDefNode(newFunctionDefiningNode, out newFunctionVar);
|
|
|
|
|
|
|
|
|
|
// Add the new aggregate to the list of aggregates
|
|
|
|
|
definingGroupNode.Child2.Children.Add(varDefNode);
|
|
|
|
|
GroupByIntoOp groupByOp = (GroupByIntoOp)definingGroupNode.Op;
|
|
|
|
|
groupByOp.Outputs.Set(newFunctionVar);
|
|
|
|
|
|
|
|
|
|
//Propagate the new var throught the ancestors of the GroupByInto
|
|
|
|
|
for (int i = 0; i < groupByAncestors.Count; i++)
|
|
|
|
|
{
|
|
|
|
|
Node groupByAncestor = groupByAncestors[i];
|
|
|
|
|
if (groupByAncestor.Op.OpType == OpType.Project)
|
|
|
|
|
{
|
|
|
|
|
ProjectOp ancestorProjectOp = (ProjectOp)groupByAncestor.Op;
|
|
|
|
|
ancestorProjectOp.Outputs.Set(newFunctionVar);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Update the functionNode
|
|
|
|
|
candidate.Key.Op = m_command.CreateVarRefOp(newFunctionVar);
|
|
|
|
|
candidate.Key.Children.Clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Check whether all nodes in the given list of nodes are of types
|
|
|
|
|
/// that we know how to propagate an aggregate through
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="nodes"></param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
private static bool AreAllNodesSupportedForPropagation(IList<Node> nodes)
|
|
|
|
|
{
|
|
|
|
|
foreach (Node node in nodes)
|
|
|
|
|
{
|
|
|
|
|
if (node.Op.OpType != OpType.Project
|
|
|
|
|
&& node.Op.OpType != OpType.Filter
|
|
|
|
|
&& node.Op.OpType != OpType.ConstrainedSort
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Finds the paths from each of node1 and node2 to their least common ancestor
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="node1"></param>
|
|
|
|
|
/// <param name="node2"></param>
|
|
|
|
|
/// <param name="ancestors1"></param>
|
|
|
|
|
/// <param name="ancestors2"></param>
|
|
|
|
|
private void FindPathsToLeastCommonAncestor(Node node1, Node node2, out IList<Node> ancestors1, out IList<Node> ancestors2)
|
|
|
|
|
{
|
|
|
|
|
ancestors1 = FindAncestors(node1);
|
|
|
|
|
ancestors2 = FindAncestors(node2);
|
|
|
|
|
|
|
|
|
|
int currentIndex1 = ancestors1.Count - 1;
|
|
|
|
|
int currentIndex2 = ancestors2.Count - 1;
|
|
|
|
|
while (ancestors1[currentIndex1] == ancestors2[currentIndex2])
|
|
|
|
|
{
|
|
|
|
|
currentIndex1--;
|
|
|
|
|
currentIndex2--;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = ancestors1.Count - 1; i > currentIndex1; i--)
|
|
|
|
|
{
|
|
|
|
|
ancestors1.RemoveAt(i);
|
|
|
|
|
}
|
|
|
|
|
for (int i = ancestors2.Count - 1; i > currentIndex2; i--)
|
|
|
|
|
{
|
|
|
|
|
ancestors2.RemoveAt(i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Finds all ancestors of the given node.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="node"></param>
|
|
|
|
|
/// <returns>An ordered list of the all the ancestors of the given node starting from the immediate parent
|
|
|
|
|
/// to the root of the tree</returns>
|
|
|
|
|
private IList<Node> FindAncestors(Node node)
|
|
|
|
|
{
|
|
|
|
|
List<Node> ancestors = new List<Node>();
|
|
|
|
|
Node currentNode = node;
|
|
|
|
|
Node ancestor;
|
|
|
|
|
while (m_tryGetParent(currentNode, out ancestor))
|
|
|
|
|
{
|
|
|
|
|
ancestors.Add(ancestor);
|
|
|
|
|
currentNode = ancestor;
|
|
|
|
|
}
|
|
|
|
|
return ancestors;
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
}
|
|
|
|
|
}
|