private static bool ApplyRulesToNode( RuleProcessingContext context, ReadOnlyCollection<ReadOnlyCollection<Rule>> rules, Node currentNode, out Node newNode) { newNode = currentNode; // Apply any pre-rule delegates context.PreProcess(currentNode); foreach (var r in rules[(int)currentNode.Op.OpType]) { if (!r.Match(currentNode)) { continue; } // Did the rule modify the subtree? if (r.Apply(context, currentNode, out newNode)) { // The node has changed; don't try to apply any more rules context.PostProcess(newNode, r); return true; } else { Debug.Assert(newNode == currentNode, "Liar! This rule should have returned 'true'"); } } context.PostProcess(currentNode, null); return false; }
// <summary> // Convert a // SingleRowOp(X) => X // if X produces at most one row // </summary> // <param name="context"> Rule Processing context </param> // <param name="singleRowNode"> Current subtree </param> // <param name="newNode"> transformed subtree </param> // <returns> Transformation status </returns> private static bool ProcessSingleRowOpOverAnything(RuleProcessingContext context, Node singleRowNode, out Node newNode) { newNode = singleRowNode; var trc = (TransformationRulesContext)context; var childNodeInfo = context.Command.GetExtendedNodeInfo(singleRowNode.Child0); // If the input to this Op can produce at most one row, then we don't need the // singleRowOp - simply return the input if (childNodeInfo.MaxRows <= RowCount.One) { newNode = singleRowNode.Child0; return true; } // // if the current node is a FilterOp, then try and determine if the FilterOp // produces one row at most // if (singleRowNode.Child0.Op.OpType == OpType.Filter) { var predicate = new Predicate(context.Command, singleRowNode.Child0.Child1); if (predicate.SatisfiesKey(childNodeInfo.Keys.KeyVars, childNodeInfo.Definitions)) { childNodeInfo.MaxRows = RowCount.One; newNode = singleRowNode.Child0; return true; } } // we couldn't do anything return false; }
/// <summary> /// Process a SetOp when one of the inputs is an emptyset. /// /// An emptyset is represented by a Filter(X, ConstantPredicate) /// where the ConstantPredicate has a value of "false" /// /// The general rules are /// UnionAll(X, EmptySet) => X /// UnionAll(EmptySet, X) => X /// Intersect(EmptySet, X) => EmptySet /// Intersect(X, EmptySet) => EmptySet /// Except(EmptySet, X) => EmptySet /// Except(X, EmptySet) => X /// /// These rules then translate into /// UnionAll: return the non-empty input /// Intersect: return the empty input /// Except: return the "left" input /// </summary> /// <param name="context"> Rule processing context </param> /// <param name="setOpNode"> the current setop tree </param> /// <param name="filterNodeIndex"> Index of the filter node in the setop </param> /// <param name="newNode"> transformed subtree </param> /// <returns> transformation status </returns> private static bool ProcessSetOpOverEmptySet(RuleProcessingContext context, Node setOpNode, out Node newNode) { var leftChildIsEmptySet = context.Command.GetExtendedNodeInfo(setOpNode.Child0).MaxRows == RowCount.Zero; var rightChildIsEmptySet = context.Command.GetExtendedNodeInfo(setOpNode.Child1).MaxRows == RowCount.Zero; if (!leftChildIsEmptySet && !rightChildIsEmptySet) { newNode = setOpNode; return false; } int indexToReturn; var setOp = (SetOp)setOpNode.Op; if (!rightChildIsEmptySet && setOp.OpType == OpType.UnionAll || !leftChildIsEmptySet && setOp.OpType == OpType.Intersect) { indexToReturn = 1; } else { indexToReturn = 0; } newNode = setOpNode.Children[indexToReturn]; var trc = (TransformationRulesContext)context; foreach (var kv in setOp.VarMap[indexToReturn]) { trc.AddVarMapping(kv.Key, kv.Value); } return true; }
/// <summary> /// We perform the following simple transformation for CaseOps. If every single /// then/else expression in the CaseOp is equivalent, then we can simply replace /// the Op with the first then/expression. Specifically, /// case when w1 then t1 when w2 then t2 ... when wn then tn else e end /// => t1 /// assuming that t1 is equivalent to t2 is equivalent to ... to e /// </summary> /// <param name="context"> Rule Processing context </param> /// <param name="caseOpNode"> The current subtree for the CaseOp </param> /// <param name="newNode"> the (possibly) modified subtree </param> /// <returns> true, if we performed any transformations </returns> private static bool ProcessSimplifyCase(RuleProcessingContext context, Node caseOpNode, out Node newNode) { var caseOp = (CaseOp)caseOpNode.Op; newNode = caseOpNode; // // Can I collapse the entire case-expression into a single expression - yes, // if all the then/else clauses are the same expression // if (ProcessSimplifyCase_Collapse(caseOpNode, out newNode)) { return true; } // // Can I remove any unnecessary when-then pairs ? // if (ProcessSimplifyCase_EliminateWhenClauses(context, caseOp, caseOpNode, out newNode)) { return true; } // Nothing else I can think of return false; }
/// <summary> /// If the DistinctOp includes all all the keys of the input, than it is unnecessary. /// Distinct (X, distinct_keys) -> Project( X, distinct_keys) where distinct_keys includes all keys of X. /// </summary> /// <param name="context"> Rule processing context </param> /// <param name="n"> current subtree </param> /// <param name="newNode"> transformed subtree </param> /// <returns> transformation status </returns> private static bool ProcessDistinctOpOfKeys(RuleProcessingContext context, Node n, out Node newNode) { var command = context.Command; var nodeInfo = command.GetExtendedNodeInfo(n.Child0); var op = (DistinctOp)n.Op; //If we know the keys of the input and the list of distinct keys includes them all, omit the distinct if (!nodeInfo.Keys.NoKeys && op.Keys.Subsumes(nodeInfo.Keys.KeyVars)) { var newOp = command.CreateProjectOp(op.Keys); //Create empty vardef list var varDefListOp = command.CreateVarDefListOp(); var varDefListNode = command.CreateNode(varDefListOp); newNode = command.CreateNode(newOp, n.Child0, varDefListNode); return true; } //Otherwise return the node as is newNode = n; return false; }
private static bool ApplyRulesToNode( RuleProcessingContext context, ReadOnlyCollection <ReadOnlyCollection <Rule> > rules, Node currentNode, out Node newNode) { newNode = currentNode; // Apply any pre-rule delegates context.PreProcess(currentNode); foreach (var r in rules[(int)currentNode.Op.OpType]) { if (!r.Match(currentNode)) { continue; } // Did the rule modify the subtree? if (r.Apply(context, currentNode, out newNode)) { // The node has changed; don't try to apply any more rules context.PostProcess(newNode, r); return(true); } else { Debug.Assert(newNode == currentNode, "Liar! This rule should have returned 'true'"); } } context.PostProcess(currentNode, null); return(false); }
private Node ApplyRulesToSubtree( RuleProcessingContext context, ReadOnlyCollection <ReadOnlyCollection <Rule> > rules, Node subTreeRoot, Node parent, int childIndexInParent) { int num = 0; Dictionary <SubTreeId, SubTreeId> dictionary = new Dictionary <SubTreeId, SubTreeId>(); SubTreeId key; while (true) { ++num; context.PreProcessSubTree(subTreeRoot); key = new SubTreeId(context, subTreeRoot, parent, childIndexInParent); if (!this.m_processedNodeMap.ContainsKey(key)) { if (!dictionary.ContainsKey(key)) { dictionary[key] = key; for (int childIndexInParent1 = 0; childIndexInParent1 < subTreeRoot.Children.Count; ++childIndexInParent1) { Node child = subTreeRoot.Children[childIndexInParent1]; if (RuleProcessor.ShouldApplyRules(child, subTreeRoot)) { subTreeRoot.Children[childIndexInParent1] = this.ApplyRulesToSubtree(context, rules, child, subTreeRoot, childIndexInParent1); } } Node newNode; if (RuleProcessor.ApplyRulesToNode(context, rules, subTreeRoot, out newNode)) { context.PostProcessSubTree(subTreeRoot); subTreeRoot = newNode; } else { goto label_10; } } else { break; } } else { goto label_12; } } this.m_processedNodeMap[key] = key; goto label_12; label_10: this.m_processedNodeMap[key] = key; label_12: context.PostProcessSubTree(subTreeRoot); return(subTreeRoot); }
/// <summary> /// Converts a Project(Project(X, c1,...), d1,...) => /// Project(X, d1', d2'...) /// where d1', d2' etc. are the "mapped" versions of d1, d2 etc. /// </summary> /// <param name="context"> Rule processing context </param> /// <param name="projectNode"> Current ProjectOp node </param> /// <param name="newNode"> modified subtree </param> /// <returns> Transformation status </returns> private static bool ProcessProjectOverProject(RuleProcessingContext context, Node projectNode, out Node newNode) { newNode = projectNode; var projectOp = (ProjectOp)projectNode.Op; var varDefListNode = projectNode.Child1; var subProjectNode = projectNode.Child0; var subProjectOp = (ProjectOp)subProjectNode.Op; var trc = (TransformationRulesContext)context; // If any of the defining expressions is not a scalar op tree, then simply // quit var varRefMap = new Dictionary<Var, int>(); foreach (var varDefNode in varDefListNode.Children) { if (!trc.IsScalarOpTree(varDefNode.Child0, varRefMap)) { return false; } } var varMap = trc.GetVarMap(subProjectNode.Child1, varRefMap); if (varMap == null) { return false; } // create a new varDefList node... var newVarDefListNode = trc.Command.CreateNode(trc.Command.CreateVarDefListOp()); // Remap any local definitions, I have foreach (var varDefNode in varDefListNode.Children) { // update the defining expression varDefNode.Child0 = trc.ReMap(varDefNode.Child0, varMap); trc.Command.RecomputeNodeInfo(varDefNode); newVarDefListNode.Children.Add(varDefNode); } // Now, pull up any definitions of the subProject that I publish myself var projectNodeInfo = trc.Command.GetExtendedNodeInfo(projectNode); foreach (var chi in subProjectNode.Child1.Children) { var varDefOp = (VarDefOp)chi.Op; if (projectNodeInfo.Definitions.IsSet(varDefOp.Var)) { newVarDefListNode.Children.Add(chi); } } // // now that we have remapped all our computed vars, simply bypass the subproject // node // projectNode.Child0 = subProjectNode.Child0; projectNode.Child1 = newVarDefListNode; return true; }
/// <summary> /// Convert Filter(Filter(X, p1), p2) => Filter(X, (p1 and p2)) /// </summary> /// <param name="context"> rule processing context </param> /// <param name="filterNode"> FilterOp node </param> /// <param name="newNode"> modified subtree </param> /// <returns> transformed subtree </returns> private static bool ProcessFilterOverFilter(RuleProcessingContext context, Node filterNode, out Node newNode) { var newAndNode = context.Command.CreateNode( context.Command.CreateConditionalOp(OpType.And), filterNode.Child0.Child1, filterNode.Child1); newNode = context.Command.CreateNode(context.Command.CreateFilterOp(), filterNode.Child0.Child0, newAndNode); return true; }
// <summary> // Convert // SingleRowOp(Project) => Project(SingleRowOp) // </summary> // <param name="context"> Rule Processing context </param> // <param name="singleRowNode"> current subtree </param> // <param name="newNode"> transformeed subtree </param> // <returns> transformation status </returns> private static bool ProcessSingleRowOpOverProject(RuleProcessingContext context, Node singleRowNode, out Node newNode) { newNode = singleRowNode; var projectNode = singleRowNode.Child0; var projectNodeInput = projectNode.Child0; // Simply push the SingleRowOp below the ProjectOp singleRowNode.Child0 = projectNodeInput; context.Command.RecomputeNodeInfo(singleRowNode); projectNode.Child0 = singleRowNode; newNode = projectNode; return true; // subtree modified internally }
// <summary> // If the ConstrainedSortOp's input is guaranteed to produce no rows, remove the ConstrainedSortOp completly: // CSort(EmptySet) => EmptySet // </summary> // <param name="context"> Rule processing context </param> // <param name="n"> current subtree </param> // <param name="newNode"> transformed subtree </param> // <returns> transformation status </returns> private static bool ProcessConstrainedSortOpOverEmptySet(RuleProcessingContext context, Node n, out Node newNode) { var nodeInfo = (context).Command.GetExtendedNodeInfo(n.Child0); //If the input has no rows, remove the ConstraintSortOp node completly if (nodeInfo.MaxRows == RowCount.Zero) { newNode = n.Child0; return true; } newNode = n; return false; }
/// <summary> /// If the SortOp's input is guaranteed to produce at most 1 row, remove the node with the SortOp: /// Sort(X) => X, if X is guaranteed to produce no more than 1 row /// </summary> /// <param name="context">Rule processing context</param> /// <param name="n">current subtree</param> /// <param name="newNode">transformed subtree</param> /// <returns>transformation status</returns> private static bool ProcessSortOpOverAtMostOneRow(RuleProcessingContext context, Node n, out Node newNode) { var nodeInfo = (context).Command.GetExtendedNodeInfo(n.Child0); //If the input has at most one row, omit the SortOp if (nodeInfo.MaxRows == RowCount.Zero || nodeInfo.MaxRows == RowCount.One) { newNode = n.Child0; return true; } //Otherwise return the node as is newNode = n; return false; }
private static bool ApplyRulesToNode( RuleProcessingContext context, ReadOnlyCollection <ReadOnlyCollection <Rule> > rules, Node currentNode, out Node newNode) { newNode = currentNode; context.PreProcess(currentNode); foreach (Rule rule in rules[(int)currentNode.Op.OpType]) { if (rule.Match(currentNode) && rule.Apply(context, currentNode, out newNode)) { context.PostProcess(newNode, rule); return(true); } } context.PostProcess(currentNode, (Rule)null); return(false); }
private static bool ProcessJoinOverProject(RuleProcessingContext context, Node joinNode, out Node newNode) { newNode = joinNode; var trc = (TransformationRulesContext)context; var command = trc.Command; var joinConditionNode = joinNode.HasChild2 ? joinNode.Child2 : null; var varRefMap = new Dictionary<Var, int>(); if (joinConditionNode != null && !trc.IsScalarOpTree(joinConditionNode, varRefMap)) { return false; } Node newJoinNode; Node newProjectNode; // Now locate the ProjectOps var newVarSet = command.CreateVarVec(); var varDefNodes = new List<Node>(); // // Try and handle "project" on both sides only if we're not dealing with // an LOJ. // if ((joinNode.Op.OpType != OpType.LeftOuterJoin) && (joinNode.Child0.Op.OpType == OpType.Project) && (joinNode.Child1.Op.OpType == OpType.Project)) { var projectOp1 = (ProjectOp)joinNode.Child0.Op; var projectOp2 = (ProjectOp)joinNode.Child1.Op; var varMap1 = trc.GetVarMap(joinNode.Child0.Child1, varRefMap); var varMap2 = trc.GetVarMap(joinNode.Child1.Child1, varRefMap); if (varMap1 == null || varMap2 == null) { return false; } if (joinConditionNode != null) { joinConditionNode = trc.ReMap(joinConditionNode, varMap1); joinConditionNode = trc.ReMap(joinConditionNode, varMap2); newJoinNode = context.Command.CreateNode(joinNode.Op, joinNode.Child0.Child0, joinNode.Child1.Child0, joinConditionNode); } else { newJoinNode = context.Command.CreateNode(joinNode.Op, joinNode.Child0.Child0, joinNode.Child1.Child0); } newVarSet.InitFrom(projectOp1.Outputs); foreach (var v in projectOp2.Outputs) { newVarSet.Set(v); } var newProjectOp = command.CreateProjectOp(newVarSet); varDefNodes.AddRange(joinNode.Child0.Child1.Children); varDefNodes.AddRange(joinNode.Child1.Child1.Children); var varDefListNode = command.CreateNode( command.CreateVarDefListOp(), varDefNodes); newProjectNode = command.CreateNode( newProjectOp, newJoinNode, varDefListNode); newNode = newProjectNode; return true; } var projectNodeIdx = -1; var otherNodeIdx = -1; if (joinNode.Child0.Op.OpType == OpType.Project) { projectNodeIdx = 0; otherNodeIdx = 1; } else { PlanCompiler.Assert(joinNode.Op.OpType != OpType.LeftOuterJoin, "unexpected non-LeftOuterJoin"); projectNodeIdx = 1; otherNodeIdx = 0; } var projectNode = joinNode.Children[projectNodeIdx]; var projectOp = projectNode.Op as ProjectOp; var varMap = trc.GetVarMap(projectNode.Child1, varRefMap); if (varMap == null) { return false; } var otherChildInfo = command.GetExtendedNodeInfo(joinNode.Children[otherNodeIdx]); var vec = command.CreateVarVec(projectOp.Outputs); vec.Or(otherChildInfo.Definitions); projectOp.Outputs.InitFrom(vec); if (joinConditionNode != null) { joinConditionNode = trc.ReMap(joinConditionNode, varMap); joinNode.Child2 = joinConditionNode; } joinNode.Children[projectNodeIdx] = projectNode.Child0; // bypass the projectOp context.Command.RecomputeNodeInfo(joinNode); newNode = context.Command.CreateNode(projectOp, joinNode, projectNode.Child1); return true; }
/// <summary> /// Convert a CrossJoin(SingleRowTable, X) or CrossJoin(X, SingleRowTable) or LeftOuterJoin(X, SingleRowTable) /// into just "X" /// </summary> /// <param name="context">rule processing context</param> /// <param name="joinNode">the join node</param> /// <param name="newNode">transformed subtree</param> /// <returns>transformation status</returns> private static bool ProcessJoinOverSingleRowTable(RuleProcessingContext context, Node joinNode, out Node newNode) { newNode = joinNode; if (joinNode.Child0.Op.OpType == OpType.SingleRowTable) { newNode = joinNode.Child1; } else { newNode = joinNode.Child0; } return true; }
/// <summary> /// If the GroupByOp defines some computedVars as part of its keys, but those computedVars are simply /// redefinitions of other Vars, then eliminate the computedVars. /// GroupBy(X, VarDefList(VarDef(cv1, VarRef(v1)), ...), VarDefList(...)) /// can be transformed into /// GroupBy(X, VarDefList(...)) /// where cv1 has now been replaced by v1 /// </summary> /// <param name="context"> Rule processing context </param> /// <param name="n"> current subtree </param> /// <param name="newNode"> transformed subtree </param> /// <returns> transformation status </returns> private static bool ProcessGroupByWithSimpleVarRedefinitions(RuleProcessingContext context, Node n, out Node newNode) { newNode = n; var groupByOp = (GroupByOp)n.Op; // no local keys? nothing to do if (n.Child1.Children.Count == 0) { return false; } var trc = (TransformationRulesContext)context; var command = trc.Command; var nodeInfo = command.GetExtendedNodeInfo(n); // // Check to see if any of the computed Vars defined by this GroupByOp // are simple redefinitions of other VarRefOps. Consider only those // VarRefOps that are not "external" references // var canEliminateSomeVars = false; foreach (var varDefNode in n.Child1.Children) { var definingExprNode = varDefNode.Child0; if (definingExprNode.Op.OpType == OpType.VarRef) { var varRefOp = (VarRefOp)definingExprNode.Op; if (!nodeInfo.ExternalReferences.IsSet(varRefOp.Var)) { // this is a Var that we should remove canEliminateSomeVars = true; } } } // Did we have any redefinitions if (!canEliminateSomeVars) { return false; } // // OK. We've now identified a set of vars that are simple redefinitions. // Try and replace the computed Vars with the Vars that they're redefining // // Lets now build up a new VarDefListNode var newVarDefNodes = new List<Node>(); foreach (var varDefNode in n.Child1.Children) { var varDefOp = (VarDefOp)varDefNode.Op; var varRefOp = varDefNode.Child0.Op as VarRefOp; if (varRefOp != null && !nodeInfo.ExternalReferences.IsSet(varRefOp.Var)) { groupByOp.Outputs.Clear(varDefOp.Var); groupByOp.Outputs.Set(varRefOp.Var); groupByOp.Keys.Clear(varDefOp.Var); groupByOp.Keys.Set(varRefOp.Var); trc.AddVarMapping(varDefOp.Var, varRefOp.Var); } else { newVarDefNodes.Add(varDefNode); } } // Create a new vardeflist node, and set that as Child1 for the group by op var newVarDefListNode = command.CreateNode(command.CreateVarDefListOp(), newVarDefNodes); n.Child1 = newVarDefListNode; return true; // subtree modified }
private static bool ProcessNotOverConstantPredicate(RuleProcessingContext context, Node node, out Node newNode) { return ProcessLogOpOverConstant(context, node, node.Child0, null, out newNode); }
private static bool ProcessLikeOverConstant(RuleProcessingContext context, Node n, out Node newNode) { newNode = n; var patternOp = (InternalConstantOp)n.Child1.Op; var strOp = (InternalConstantOp)n.Child0.Op; var str = (string)strOp.Value; var pattern = (string)patternOp.Value; var match = MatchesPattern((string)strOp.Value, (string)patternOp.Value); if (match == null) { return false; } var constOp = context.Command.CreateConstantPredicateOp((bool)match); newNode = context.Command.CreateNode(constOp); return true; }
private static bool ProcessComparisonsOverConstant(RuleProcessingContext context, Node node, out Node newNode) { newNode = node; PlanCompiler.Assert(node.Op.OpType == OpType.EQ || node.Op.OpType == OpType.NE, "unexpected comparison op type?"); bool? comparisonStatus = node.Child0.Op.IsEquivalent(node.Child1.Op); // Don't mess with nulls or with non-internal constants if (comparisonStatus == null) { return false; } var result = (node.Op.OpType == OpType.EQ) ? (bool)comparisonStatus : !((bool)comparisonStatus); var newOp = context.Command.CreateConstantPredicateOp(result); newNode = context.Command.CreateNode(newOp); return true; }
/// <summary> /// Apply rules to the current subtree in a bottom-up fashion. /// </summary> /// <param name="context"> Current rule processing context </param> /// <param name="rules"> The look-up table with the rules to be applied </param> /// <param name="subTreeRoot"> Current subtree </param> /// <param name="parent"> Parent node </param> /// <param name="childIndexInParent"> Index of this child within the parent </param> /// <returns> the result of the transformation </returns> private Node ApplyRulesToSubtree( RuleProcessingContext context, ReadOnlyCollection <ReadOnlyCollection <Rule> > rules, Node subTreeRoot, Node parent, int childIndexInParent) { var loopCount = 0; var localProcessedMap = new Dictionary <SubTreeId, SubTreeId>(); SubTreeId subTreeId; while (true) { // Am I looping forever Debug.Assert(loopCount < 12, "endless loops?"); loopCount++; // // We may need to update state regardless of whether this subTree has // changed after it has been processed last. For example, it may be // affected by transformation in its siblings due to external references. // context.PreProcessSubTree(subTreeRoot); subTreeId = new SubTreeId(context, subTreeRoot, parent, childIndexInParent); // Have I seen this subtree already? Just return, if so if (m_processedNodeMap.ContainsKey(subTreeId)) { break; } // Avoid endless loops here - avoid cycles of 2 or more if (localProcessedMap.ContainsKey(subTreeId)) { // mark this subtree as processed m_processedNodeMap[subTreeId] = subTreeId; break; } // Keep track of this one localProcessedMap[subTreeId] = subTreeId; // Walk my children for (var i = 0; i < subTreeRoot.Children.Count; i++) { var childNode = subTreeRoot.Children[i]; if (ShouldApplyRules(childNode, subTreeRoot)) { subTreeRoot.Children[i] = ApplyRulesToSubtree(context, rules, childNode, subTreeRoot, i); } } // Apply rules to myself. If no transformations were performed, // then mark this subtree as processed, and break out Node newSubTreeRoot; if (!ApplyRulesToNode(context, rules, subTreeRoot, out newSubTreeRoot)) { Debug.Assert(subTreeRoot == newSubTreeRoot); // mark this subtree as processed m_processedNodeMap[subTreeId] = subTreeId; break; } context.PostProcessSubTree(subTreeRoot); subTreeRoot = newSubTreeRoot; } context.PostProcessSubTree(subTreeRoot); return(subTreeRoot); }
/// <summary> /// eliminates nested null casts into a single cast of the outermost cast type. /// basically the transformation applied is: cast(null[x] as T) => null[t] /// </summary> /// <param name="context"> </param> /// <param name="castNullOp"> </param> /// <param name="newNode"> modified subtree </param> /// <returns> </returns> private static bool ProcessNullCast(RuleProcessingContext context, Node castNullOp, out Node newNode) { newNode = context.Command.CreateNode(context.Command.CreateNullOp(castNullOp.Op.Type)); return true; }
/// <summary> /// Apply a set of rules to the subtree /// </summary> /// <param name="context"> Rule processing context </param> /// <param name="subTreeRoot"> current subtree </param> /// <returns> transformed subtree </returns> internal Node ApplyRulesToSubtree( RuleProcessingContext context, ReadOnlyCollection <ReadOnlyCollection <Rule> > rules, Node subTreeRoot) { return(ApplyRulesToSubtree(context, rules, subTreeRoot, null, 0)); }
/// <summary> /// We need to invoke the specified callback on the subtree in question - but only /// if the match succeeds /// </summary> /// <param name="ruleProcessingContext"> Current rule processing context </param> /// <param name="node"> The node (subtree) to process </param> /// <param name="newNode"> the (possibly) modified subtree </param> /// <returns> true, if the subtree was modified </returns> internal bool Apply(RuleProcessingContext ruleProcessingContext, Node node, out Node newNode) { // invoke the real callback return(m_nodeDelegate(ruleProcessingContext, node, out newNode)); }
/// <summary> /// If the GroupByOp has no aggregates: /// (1) and if it includes all all the keys of the input, than it is unnecessary /// GroupBy (X, keys) -> Project(X, keys) where keys includes all keys of X. /// (2) else it can be turned into a Distinct: /// GroupBy (X, keys) -> Distinct(X, keys) /// </summary> /// <param name="context"> Rule processing context </param> /// <param name="n"> current subtree </param> /// <param name="newNode"> transformed subtree </param> /// <returns> transformation status </returns> private static bool ProcessGroupByOpWithNoAggregates(RuleProcessingContext context, Node n, out Node newNode) { var command = context.Command; var op = (GroupByOp)n.Op; var nodeInfo = command.GetExtendedNodeInfo(n.Child0); var newOp = command.CreateProjectOp(op.Keys); var varDefListOp = command.CreateVarDefListOp(); var varDefListNode = command.CreateNode(varDefListOp); newNode = command.CreateNode(newOp, n.Child0, n.Child1); //If we know the keys of the input and the list of keys includes them all, // this is the result, otherwise add distinct if (nodeInfo.Keys.NoKeys || !op.Keys.Subsumes(nodeInfo.Keys.KeyVars)) { newNode = command.CreateNode(command.CreateDistinctOp(command.CreateVarVec(op.Keys)), newNode); } return true; }
private static bool ProcessSimplifyCase_EliminateWhenClauses( RuleProcessingContext context, CaseOp caseOp, Node caseOpNode, out Node newNode) { List<Node> newNodeArgs = null; newNode = caseOpNode; for (var i = 0; i < caseOpNode.Children.Count;) { // Special handling for the else clause if (i == caseOpNode.Children.Count - 1) { // If the else clause is a SoftCast then we do not attempt to simplify // the case operation, since this may change the result type. // This really belongs in more general SoftCastOp logic in the CTreeGenerator // that converts SoftCasts that could affect the result type of the query into // a real cast or a trivial case statement, to preserve the result type. // This is tracked by SQL PT Work Item #300003327. if (OpType.SoftCast == caseOpNode.Children[i].Op.OpType) { return false; } if (newNodeArgs != null) { newNodeArgs.Add(caseOpNode.Children[i]); } break; } // If the current then clause is a SoftCast then we do not attempt to simplify // the case operation, since this may change the result type. // Again, this really belongs in the CTreeGenerator as per SQL PT Work Item #300003327. if (OpType.SoftCast == caseOpNode.Children[i + 1].Op.OpType) { return false; } // Check to see if the when clause is a ConstantPredicate if (caseOpNode.Children[i].Op.OpType != OpType.ConstantPredicate) { if (newNodeArgs != null) { newNodeArgs.Add(caseOpNode.Children[i]); newNodeArgs.Add(caseOpNode.Children[i + 1]); } i += 2; continue; } // Found a when-clause which is a constant predicate var constPred = (ConstantPredicateOp)caseOpNode.Children[i].Op; // Create the newArgs list, if we haven't done so already if (newNodeArgs == null) { newNodeArgs = new List<Node>(); for (var j = 0; j < i; j++) { newNodeArgs.Add(caseOpNode.Children[j]); } } // If the when-clause is the "true" predicate, then we simply ignore all // the succeeding arguments. We make the "then" clause of this when-clause // as the "else-clause" of the resulting caseOp if (constPred.IsTrue) { newNodeArgs.Add(caseOpNode.Children[i + 1]); break; } else { // Otherwise, we simply skip the when-then pair PlanCompiler.Assert(constPred.IsFalse, "constant predicate must be either true or false"); i += 2; continue; } } // Did we see any changes? Simply return if (newNodeArgs == null) { return false; } // Otherwise, we did do some processing PlanCompiler.Assert(newNodeArgs.Count > 0, "new args list must not be empty"); // Is there only one expression in the args list - simply return that expression if (newNodeArgs.Count == 1) { newNode = newNodeArgs[0]; } else { newNode = context.Command.CreateNode(caseOp, newNodeArgs); } return true; }
private static bool ProcessIsNullOverAnything(RuleProcessingContext context, Node isNullNode, out Node newNode) { Debug.Assert(isNullNode.Op.OpType == OpType.IsNull); var command = context.Command; switch (isNullNode.Child0.Op.OpType) { case OpType.Cast: newNode = command.CreateNode( command.CreateConditionalOp(OpType.IsNull), isNullNode.Child0.Child0); break; case OpType.Function: var function = ((FunctionOp)isNullNode.Child0.Op).Function; newNode = PreservesNulls(function) ? command.CreateNode( command.CreateConditionalOp(OpType.IsNull), isNullNode.Child0.Child0) : isNullNode; break; default: newNode = isNullNode; break; } switch (isNullNode.Child0.Op.OpType) { case OpType.Constant: case OpType.InternalConstant: case OpType.NullSentinel: return ProcessIsNullOverConstant(context, newNode, out newNode); case OpType.Null: return ProcessIsNullOverNull(context, newNode, out newNode); case OpType.VarRef: return ProcessIsNullOverVarRef(context, newNode, out newNode); default: return !ReferenceEquals(isNullNode, newNode); } }
/// <summary> /// If the else clause of the CaseOp is another CaseOp, when two can be collapsed into one. /// In particular, /// /// CASE /// WHEN W1 THEN T1 /// WHEN W2 THEN T2 ... /// ELSE (CASE /// WHEN WN1 THEN TN1, … /// ELSE E) /// /// Is transformed into /// /// CASE /// WHEN W1 THEN T1 /// WHEN W2 THEN T2 ... /// WHEN WN1 THEN TN1 ... /// ELSE E /// </summary> /// <param name="caseOp"> the current caseOp </param> /// <param name="caseOpNode"> current subtree </param> /// <param name="newNode"> new subtree </param> /// <returns> true, if we performed a transformation </returns> private static bool ProcessFlattenCase(RuleProcessingContext context, Node caseOpNode, out Node newNode) { newNode = caseOpNode; var elseChild = caseOpNode.Children[caseOpNode.Children.Count - 1]; if (elseChild.Op.OpType != OpType.Case) { return false; } // // Flatten the case statements. // The else child is removed from the outer CaseOp op // and the else child's children are reparented to the outer CaseOp // Node info recomputation does not need to happen, the outer CaseOp // node still has the same descendants. // caseOpNode.Children.RemoveAt(caseOpNode.Children.Count - 1); caseOpNode.Children.AddRange(elseChild.Children); return true; }
/// <summary> /// Apply rules to the current subtree in a bottom-up fashion. /// </summary> /// <param name="context">Current rule processing context</param> /// <param name="rules">The look-up table with the rules to be applied</param> /// <param name="subTreeRoot">Current subtree</param> /// <param name="parent">Parent node</param> /// <param name="childIndexInParent">Index of this child within the parent</param> /// <returns>the result of the transformation</returns> private Node ApplyRulesToSubtree( RuleProcessingContext context, ReadOnlyCollection<ReadOnlyCollection<Rule>> rules, Node subTreeRoot, Node parent, int childIndexInParent) { var loopCount = 0; var localProcessedMap = new Dictionary<SubTreeId, SubTreeId>(); SubTreeId subTreeId; while (true) { // Am I looping forever Debug.Assert(loopCount < 12, "endless loops?"); loopCount++; // // We may need to update state regardless of whether this subTree has // changed after it has been processed last. For example, it may be // affected by transformation in its siblings due to external references. // context.PreProcessSubTree(subTreeRoot); subTreeId = new SubTreeId(context, subTreeRoot, parent, childIndexInParent); // Have I seen this subtree already? Just return, if so if (m_processedNodeMap.ContainsKey(subTreeId)) { break; } // Avoid endless loops here - avoid cycles of 2 or more if (localProcessedMap.ContainsKey(subTreeId)) { // mark this subtree as processed m_processedNodeMap[subTreeId] = subTreeId; break; } // Keep track of this one localProcessedMap[subTreeId] = subTreeId; // Walk my children for (var i = 0; i < subTreeRoot.Children.Count; i++) { subTreeRoot.Children[i] = ApplyRulesToSubtree(context, rules, subTreeRoot.Children[i], subTreeRoot, i); } // Apply rules to myself. If no transformations were performed, // then mark this subtree as processed, and break out Node newSubTreeRoot; if (!ApplyRulesToNode(context, rules, subTreeRoot, out newSubTreeRoot)) { Debug.Assert(subTreeRoot == newSubTreeRoot); // mark this subtree as processed m_processedNodeMap[subTreeId] = subTreeId; break; } context.PostProcessSubTree(subTreeRoot); subTreeRoot = newSubTreeRoot; } context.PostProcessSubTree(subTreeRoot); return subTreeRoot; }
private static bool ProcessFilterWithConstantPredicate(RuleProcessingContext context, Node n, out Node newNode) { newNode = n; var predOp = (ConstantPredicateOp)n.Child1.Op; // If we're dealing with a "true" predicate, then simply return the RelOp // input to the filter if (predOp.IsTrue) { newNode = n.Child0; return true; } PlanCompiler.Assert(predOp.IsFalse, "unexpected non-false predicate?"); // We're dealing with a "false" predicate, then we can get rid of the // input, and replace it with a dummy project // // If the input is already a singlerowtableOp, then there's nothing // further to do // if (n.Child0.Op.OpType == OpType.SingleRowTable || (n.Child0.Op.OpType == OpType.Project && n.Child0.Child0.Op.OpType == OpType.SingleRowTable)) { return false; } var trc = (TransformationRulesContext)context; var childNodeInfo = trc.Command.GetExtendedNodeInfo(n.Child0); var varDefNodeList = new List<Node>(); var newVars = trc.Command.CreateVarVec(); foreach (var v in childNodeInfo.Definitions) { var nullConst = trc.Command.CreateNullOp(v.Type); var constNode = trc.Command.CreateNode(nullConst); Var computedVar; var varDefNode = trc.Command.CreateVarDefNode(constNode, out computedVar); trc.AddVarMapping(v, computedVar); newVars.Set(computedVar); varDefNodeList.Add(varDefNode); } // If no vars have been selected out, add a dummy var if (newVars.IsEmpty) { var nullConst = trc.Command.CreateNullOp(trc.Command.BooleanType); var constNode = trc.Command.CreateNode(nullConst); Var computedVar; var varDefNode = trc.Command.CreateVarDefNode(constNode, out computedVar); newVars.Set(computedVar); varDefNodeList.Add(varDefNode); } var singleRowTableNode = trc.Command.CreateNode(trc.Command.CreateSingleRowTableOp()); n.Child0 = singleRowTableNode; var varDefListNode = trc.Command.CreateNode(trc.Command.CreateVarDefListOp(), varDefNodeList); var projectOp = trc.Command.CreateProjectOp(newVars); var projectNode = trc.Command.CreateNode(projectOp, n, varDefListNode); projectNode.Child0 = n; newNode = projectNode; return true; }
// Simplifies the following two cases: // (CASE WHEN condition THEN NULL ELSE constant END) IS NULL => condition // (CASE WHEN condition THEN constant ELSE NULL END) IS NULL => NOT condition private static bool ProcessIsNullOverCase(RuleProcessingContext context, Node isNullOpNode, out Node newNode) { var caseOpNode = isNullOpNode.Child0; if (caseOpNode.Children.Count != 3) { newNode = isNullOpNode; return false; } var whenNode = caseOpNode.Child0; var thenNode = caseOpNode.Child1; var elseNode = caseOpNode.Child2; switch (thenNode.Op.OpType) { case OpType.Null: switch (elseNode.Op.OpType) { case OpType.Constant: case OpType.InternalConstant: case OpType.NullSentinel: newNode = whenNode; return true; } break; case OpType.Constant: case OpType.InternalConstant: case OpType.NullSentinel: if (elseNode.Op.OpType == OpType.Null) { newNode = context.Command.CreateNode( context.Command.CreateConditionalOp(OpType.Not), whenNode); return true; } break; } newNode = isNullOpNode; return false; }
private static bool ProcessLogOpOverConstant( RuleProcessingContext context, Node node, Node constantPredicateNode, Node otherNode, out Node newNode) { PlanCompiler.Assert(constantPredicateNode != null, "null constantPredicateOp?"); var pred = (ConstantPredicateOp)constantPredicateNode.Op; switch (node.Op.OpType) { case OpType.And: newNode = pred.IsTrue ? otherNode : constantPredicateNode; break; case OpType.Or: newNode = pred.IsTrue ? constantPredicateNode : otherNode; break; case OpType.Not: PlanCompiler.Assert(otherNode == null, "Not Op with more than 1 child. Gasp!"); newNode = context.Command.CreateNode(context.Command.CreateConstantPredicateOp(!pred.Value)); break; default: PlanCompiler.Assert(false, "Unexpected OpType - " + node.Op.OpType); newNode = null; break; } return true; }
/// <summary> /// CrossJoin(Filter(A,p), B) => Filter(CrossJoin(A, B), p) /// CrossJoin(A, Filter(B,p)) => Filter(CrossJoin(A, B), p) /// /// InnerJoin(Filter(A,p), B, c) => Filter(InnerJoin(A, B, c), p) /// InnerJoin(A, Filter(B,p), c) => Filter(InnerJoin(A, B, c), p) /// /// LeftOuterJoin(Filter(A,p), B, c) => Filter(LeftOuterJoin(A, B, c), p) /// /// Note that the predicate on the right table in a left-outer-join cannot be pulled /// up above the join. /// /// </summary> /// <param name="context">Rule processing context</param> /// <param name="joinNode">Current JoinOp tree to process</param> /// <param name="newNode">transformed subtree</param> /// <returns>transformation status</returns> private static bool ProcessJoinOverFilter(RuleProcessingContext context, Node joinNode, out Node newNode) { newNode = joinNode; var trc = (TransformationRulesContext)context; var command = trc.Command; Node predicateNode = null; var newLeftInput = joinNode.Child0; // get the predicate from the first filter if (joinNode.Child0.Op.OpType == OpType.Filter) { predicateNode = joinNode.Child0.Child1; newLeftInput = joinNode.Child0.Child0; // bypass the filter } // get the predicate from the second filter var newRightInput = joinNode.Child1; if (joinNode.Child1.Op.OpType == OpType.Filter && joinNode.Op.OpType != OpType.LeftOuterJoin) { if (predicateNode == null) { predicateNode = joinNode.Child1.Child1; } else { predicateNode = command.CreateNode( command.CreateConditionalOp(OpType.And), predicateNode, joinNode.Child1.Child1); } newRightInput = joinNode.Child1.Child0; // bypass the filter } // No optimizations to perform if we can't locate the appropriate predicate if (predicateNode == null) { return false; } // // Create a new join node with the new inputs // Node newJoinNode; if (joinNode.Op.OpType == OpType.CrossJoin) { newJoinNode = command.CreateNode(joinNode.Op, newLeftInput, newRightInput); } else { newJoinNode = command.CreateNode(joinNode.Op, newLeftInput, newRightInput, joinNode.Child2); } // // create a new filterOp with the combined predicates, and with the // newjoinNode as the input // var newFilterOp = command.CreateFilterOp(); newNode = command.CreateNode(newFilterOp, newJoinNode, predicateNode); // // Mark this subtree so that we don't try to push filters down again // trc.SuppressFilterPushdown(newNode); return true; }
/// <summary> /// Convert an IsNull(null) to just the 'true' predicate /// </summary> /// <param name="context"> </param> /// <param name="isNullNode"> </param> /// <param name="newNode"> new subtree </param> /// <returns> </returns> private static bool ProcessIsNullOverNull(RuleProcessingContext context, Node isNullNode, out Node newNode) { newNode = context.Command.CreateNode(context.Command.CreateTrueOp()); return true; }
/// <summary> /// Converts a GroupBy(Project(X, c1,..ck), agg1, agg2, .. aggm) => /// GroupBy(X, agg1', agg2', .. aggm') /// where agg1', agg2', .. aggm' are the "mapped" versions /// of agg1, agg2, .. aggm, such that the references to c1, ... ck are /// replaced by their definitions. /// We only do this if each c1, ..ck is refereneced (in aggregates) at most once or it is a constant. /// </summary> /// <param name="context"> Rule processing context </param> /// <param name="projectNode"> Current ProjectOp node </param> /// <param name="newNode"> modified subtree </param> /// <returns> Transformation status </returns> private static bool ProcessGroupByOverProject(RuleProcessingContext context, Node n, out Node newNode) { newNode = n; var op = (GroupByOp)n.Op; var command = (context).Command; var projectNode = n.Child0; var projectNodeVarDefList = projectNode.Child1; var keys = n.Child1; var aggregates = n.Child2; // If there are any keys, we should not remove the inner project if (keys.Children.Count > 0) { return false; } //Get a list of all defining vars var projectDefinitions = command.GetExtendedNodeInfo(projectNode).LocalDefinitions; //If any of the defined vars is output, than we need the extra project anyway. if (op.Outputs.Overlaps(projectDefinitions)) { return false; } var createdNewProjectDefinitions = false; //If there are any constants remove them from the list that needs to be tested, //These can safely be replaced for (var i = 0; i < projectNodeVarDefList.Children.Count; i++) { var varDefNode = projectNodeVarDefList.Children[i]; if (varDefNode.Child0.Op.OpType == OpType.Constant || varDefNode.Child0.Op.OpType == OpType.InternalConstant || varDefNode.Child0.Op.OpType == OpType.NullSentinel) { //We shouldn't modify the original project definitions, thus we copy it // the first time we encounter a constant if (!createdNewProjectDefinitions) { projectDefinitions = command.CreateVarVec(projectDefinitions); createdNewProjectDefinitions = true; } projectDefinitions.Clear(((VarDefOp)varDefNode.Op).Var); } } if (VarRefUsageFinder.AnyVarUsedMoreThanOnce(projectDefinitions, aggregates, command)) { return false; } //If we got here it means that all vars were either constants, or used at most once // Create a dictionary to be used for remapping the keys and the aggregates var varToDefiningNode = new Dictionary<Var, Node>(projectNodeVarDefList.Children.Count); for (var j = 0; j < projectNodeVarDefList.Children.Count; j++) { var varDefNode = projectNodeVarDefList.Children[j]; var var = ((VarDefOp)varDefNode.Op).Var; varToDefiningNode.Add(var, varDefNode.Child0); } newNode.Child2 = VarRefReplacer.Replace(varToDefiningNode, aggregates, command); newNode.Child0 = projectNode.Child0; return true; }
/// <summary> /// Convert a /// IsNull(VarRef(v)) /// to just the /// False predicate /// /// if v is guaranteed to be non nullable. /// </summary> /// <param name="context"> </param> /// <param name="isNullNode"> </param> /// <param name="newNode"> new subtree </param> /// <returns> </returns> private static bool ProcessIsNullOverVarRef(RuleProcessingContext context, Node isNullNode, out Node newNode) { var command = context.Command; var trc = (TransformationRulesContext)context; var v = ((VarRefOp)isNullNode.Child0.Op).Var; if (trc.IsNonNullable(v)) { newNode = command.CreateNode(context.Command.CreateFalseOp()); return true; } else { newNode = isNullNode; return false; } }
/// <summary> /// Apply a set of rules to the subtree /// </summary> /// <param name="context">Rule processing context</param> /// <param name="subTreeRoot">current subtree</param> /// <returns>transformed subtree</returns> internal Node ApplyRulesToSubtree( RuleProcessingContext context, ReadOnlyCollection<ReadOnlyCollection<Rule>> rules, Node subTreeRoot) { return ApplyRulesToSubtree(context, rules, subTreeRoot, null, 0); }