/// <summary> /// Utility method that determines whether a given CaseOp subtree can be optimized. /// Called by both PreProcessor and NominalTypeEliminator. /// /// If the case statement is of the shape: /// case when X then NULL else Y, or /// case when X then Y else NULL, /// where Y is of row type, and the types of the input CaseOp, the NULL and Y are the same, /// return true /// </summary> /// <param name="op"></param> /// <param name="n"></param> /// <returns></returns> internal static bool IsRowTypeCaseOpWithNullability(CaseOp op, Node n, out bool thenClauseIsNull) { thenClauseIsNull = false; //any default value will do if (!TypeSemantics.IsRowType(op.Type)) { return false; } if (n.Children.Count != 3) { return false; } //All three types must be equal if (!n.Child1.Op.Type.EdmEquals(op.Type) || !n.Child2.Op.Type.EdmEquals(op.Type)) { return false; } //At least one of Child1 and Child2 needs to be a null if (n.Child1.Op.OpType == OpType.Null) { thenClauseIsNull = true; return true; } if (n.Child2.Op.OpType == OpType.Null) { // thenClauseIsNull stays false return true; } return false; }
/// <summary> /// Utility method that determines whether a given CaseOp subtree can be optimized. /// Called by both PreProcessor and NominalTypeEliminator. /// /// If the case statement is of the shape: /// case when X then NULL else Y, or /// case when X then Y else NULL, /// where Y is of row type, and the types of the input CaseOp, the NULL and Y are the same, /// return true /// </summary> /// <param name="op"></param> /// <param name="n"></param> /// <returns></returns> internal static bool IsRowTypeCaseOpWithNullability(CaseOp op, Node n, out bool thenClauseIsNull) { thenClauseIsNull = false; //any default value will do if (!TypeSemantics.IsRowType(op.Type)) { return(false); } if (n.Children.Count != 3) { return(false); } //All three types must be equal if (!n.Child1.Op.Type.EdmEquals(op.Type) || !n.Child2.Op.Type.EdmEquals(op.Type)) { return(false); } //At least one of Child1 and Child2 needs to be a null if (n.Child1.Op.OpType == OpType.Null) { thenClauseIsNull = true; return(true); } if (n.Child2.Op.OpType == OpType.Null) { // thenClauseIsNull stays false return(true); } return(false); }
private static bool ProcessSimplifyCase( RuleProcessingContext context, System.Data.Entity.Core.Query.InternalTrees.Node caseOpNode, out System.Data.Entity.Core.Query.InternalTrees.Node newNode) { CaseOp op = (CaseOp)caseOpNode.Op; newNode = caseOpNode; return(ScalarOpRules.ProcessSimplifyCase_Collapse(caseOpNode, out newNode) || ScalarOpRules.ProcessSimplifyCase_EliminateWhenClauses(context, op, caseOpNode, out newNode)); }
public override void Visit(CaseOp op, System.Data.Entity.Core.Query.InternalTrees.Node n) { PropertyRefList propertyRefList = this.GetPropertyRefList(n); for (int index = 1; index < n.Children.Count - 1; index += 2) { PropertyRefList propertyRefs = propertyRefList.Clone(); this.AddPropertyRefs(n.Children[index], propertyRefs); } this.AddPropertyRefs(n.Children[n.Children.Count - 1], propertyRefList.Clone()); this.VisitChildren(n); }
internal static bool IsRowTypeCaseOpWithNullability( CaseOp op, System.Data.Entity.Core.Query.InternalTrees.Node n, out bool thenClauseIsNull) { thenClauseIsNull = false; if (!TypeSemantics.IsRowType(op.Type) || n.Children.Count != 3 || (!n.Child1.Op.Type.EdmEquals((MetadataItem)op.Type) || !n.Child2.Op.Type.EdmEquals((MetadataItem)op.Type))) { return(false); } if (n.Child1.Op.OpType == OpType.Null) { thenClauseIsNull = true; return(true); } return(n.Child2.Op.OpType == OpType.Null); }
/// <summary> /// CaseOp handling /// /// Pushes its desired properties to each of the WHEN/ELSE clauses /// </summary> /// <param name="op"></param> /// <param name="n"></param> public override void Visit(CaseOp op, Node n) { // First find the properties that my parent expects from me PropertyRefList pdProps = GetPropertyRefList(n); // push down the same properties to my then/else clauses. // the "when" clauses are irrelevant for (int i = 1; i < n.Children.Count - 1; i += 2) { PropertyRefList cdProps = pdProps.Clone(); AddPropertyRefs(n.Children[i], cdProps); } AddPropertyRefs(n.Children[n.Children.Count - 1], pdProps.Clone()); // Now visit the children VisitChildren(n); }
public override void Visit(CaseOp op, Node n) { VisitScalarOpDefault(op, n); Assert((n.Children.Count >= 3 && n.Children.Count % 2 == 1), "CaseOp: Expected odd number of arguments, and at least 3; found {0}", n.Children.Count); // Validate that each when statement is of type Boolean for (int i = 0; i < n.Children.Count - 1; i += 2) { Assert(TypeSemantics.IsBooleanType(n.Children[i].Op.Type), "Encountered a when node with a non-boolean return type"); } // Ensure that the then clauses, the else clause and the result type are all the same for (int i = 1; i < n.Children.Count - 1; i += 2) { AssertEqualTypes(n.Op.Type, n.Children[i].Op.Type); } AssertEqualTypes(n.Op.Type, n.Children[n.Children.Count - 1].Op.Type); }
public override void Visit(CaseOp op, Node n) { using (new AutoXml(this, op)) { int i = 0; while (i < n.Children.Count) { if ((i + 1) < n.Children.Count) { using (new AutoXml(this, "when")) { VisitNode(n.Children[i++]); } using (new AutoXml(this, "then")) { VisitNode(n.Children[i++]); } } else { using (new AutoXml(this, "else")) { VisitNode(n.Children[i++]); } } } } }
// <summary> // We don't yet support nest pullups over Case // </summary> public override Node Visit(CaseOp op, Node n) { // Make sure we don't have a child that is a nest op. foreach (var chi in n.Children) { if (chi.Op.OpType == OpType.Collect) { throw new NotSupportedException(Strings.ADP_NestingNotSupported(op.OpType.ToString(), chi.Op.OpType.ToString())); } else if (chi.Op.OpType == OpType.VarRef) { var refVar = ((VarRefOp)chi.Op).Var; if (m_definingNodeMap.ContainsKey(refVar)) { throw new NotSupportedException(Strings.ADP_NestingNotSupported(op.OpType.ToString(), chi.Op.OpType.ToString())); } } } return VisitDefault(n); }
/// <summary> /// CaseOp /// Special handling /// If the case statement is of one of the following two shapes: /// (1) case when X then NULL else Y, or /// (2) case when X then Y else NULL, /// where Y is of row type and the types of the input CaseOp, the NULL and Y are the same, /// it gets rewritten into: Y', where Y's null sentinel N' is: /// (1) case when X then NULL else N, or /// where N is Y's null sentinel. /// </summary> /// <param name="op"> the CaseOp </param> /// <param name="n"> corresponding node </param> /// <returns> new subtree </returns> public override Node Visit(CaseOp op, Node n) { // Before visiting the children, check whether the case statment can be optimized bool thenClauseIsNull; var canSimplifyPrecheck = PlanCompilerUtil.IsRowTypeCaseOpWithNullability(op, n, out thenClauseIsNull); VisitChildren(n); if (canSimplifyPrecheck) { Node rewrittenNode; if (TryRewriteCaseOp(n, thenClauseIsNull, out rewrittenNode)) { return rewrittenNode; } } // // If the CaseOp returns a simple type, then we don't need to do // anything special. // // Bug 480780: We must perform further processing, if the result // type is not a scalar // // If the CaseOp returns a collection, then we need to create a // new CaseOp of the new and improved collection type. Similarly // for enums we need to convert the result of the operation from // the enum type to the underlying type of the enum type, and // for spatial types we must convert it to the underlying spatial union type. if (TypeUtils.IsCollectionType(op.Type) || md.TypeSemantics.IsEnumerationType(op.Type) || md.TypeSemantics.IsStrongSpatialType(op.Type)) { var newType = GetNewType(op.Type); n.Op = m_command.CreateCaseOp(newType); return n; } else if (TypeUtils.IsStructuredType(op.Type)) { // We've got a structured type, so the CaseOp is flattened out into // a NewRecordOp via the FlattenCaseOp method. var desiredProperties = m_nodePropertyMap[n]; var newNode = FlattenCaseOp(n, m_typeInfo.GetTypeInfo(op.Type), desiredProperties); return newNode; } else { return n; } }
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 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); }
/// <summary> /// Flattens a CaseOp - Specifically, if the CaseOp returns a structuredtype, /// then the CaseOp is broken up so that we build up a "flat" record constructor /// for that structured type, with each argument to the record constructor being /// a (scalar) CaseOp. For example: /// /// Case when b1 then e1 else e2 end /// /// gets translated into: /// /// RecordOp(case when b1 then e1.a else e2.a end, /// case when b1 then e1.b else e2.b end, /// ...) /// /// The property extraction is optimized by producing only those properties /// that have actually been requested. /// </summary> /// <param name="op">the CaseOp</param> /// <param name="n">Node corresponding to the CaseOp</param> /// <param name="typeInfo">Information about the type</param> /// <param name="desiredProperties">Set of properties desired</param> /// <returns></returns> private Node FlattenCaseOp(CaseOp op, Node n, TypeInfo typeInfo, PropertyRefList desiredProperties) { // Build up a type constructor - with only as many fields filled in // as are desired. List<md.EdmProperty> fieldTypes = new List<md.EdmProperty>(); List<Node> fieldValues = new List<Node>(); foreach (PropertyRef pref in typeInfo.PropertyRefList) { // Is this property desired later? if (!desiredProperties.Contains(pref)) { continue; } md.EdmProperty property = typeInfo.GetNewProperty(pref); // Build up an accessor for this property across each when/then clause List<Node> caseChildren = new List<Node>(); for (int i = 0; i < n.Children.Count - 1; ) { Node whenNode = Copy(n.Children[i]); caseChildren.Add(whenNode); i++; Node propNode = BuildAccessorWithNulls(n.Children[i], property); caseChildren.Add(propNode); i++; } Node elseNode = BuildAccessorWithNulls(n.Children[n.Children.Count - 1], property); caseChildren.Add(elseNode); Node caseNode = m_command.CreateNode(m_command.CreateCaseOp(md.Helper.GetModelTypeUsage(property)), caseChildren); fieldTypes.Add(property); fieldValues.Add(caseNode); } NewRecordOp newRec = m_command.CreateNewRecordOp(typeInfo.FlattenedTypeUsage, fieldTypes); return m_command.CreateNode(newRec, fieldValues); }
/// <summary> /// Default processing. /// In addition, if the case statement is of the shape /// case when X then NULL else Y, or /// case when X then Y else NULL, /// where Y is of row type and the types of the input CaseOp, the NULL and Y are the same, /// marks that type as needing a null sentinel. /// This allows in NominalTypeElimination the case op to be pushed inside Y's null sentinel. /// </summary> /// <param name="op"></param> /// <param name="n"></param> /// <returns></returns> public override Node Visit(CaseOp op, Node n) { VisitScalarOpDefault(op, n); //special handling to enable optimization bool thenClauseIsNull; if (PlanCompilerUtil.IsRowTypeCaseOpWithNullability(op, n, out thenClauseIsNull)) { //Add a null sentinel for the row type m_typesNeedingNullSentinel.Add(op.Type.EdmType.Identity); } return n; }
public override void Visit(CaseOp op, Node n) { VisitScalarOpDefault(op, n); Assert( (n.Children.Count >= 3 && n.Children.Count % 2 == 1), "CaseOp: Expected odd number of arguments, and at least 3; found {0}", n.Children.Count); // Validate that each when statement is of type Boolean for (var i = 0; i < n.Children.Count - 1; i += 2) { Assert(TypeSemantics.IsBooleanType(n.Children[i].Op.Type), "Encountered a when node with a non-boolean return type"); } // Ensure that the then clauses, the else clause and the result type are all the same for (var i = 1; i < n.Children.Count - 1; i += 2) { AssertEqualTypes(n.Op.Type, n.Children[i].Op.Type); } AssertEqualTypes(n.Op.Type, n.Children[n.Children.Count - 1].Op.Type); }
/// <summary> /// Copies a CaseOp /// </summary> /// <param name="op">The Op to Copy</param> /// <param name="n">The Node that references the Op</param> /// <returns>A copy of the original Node that references a copy of the original Op</returns> public override Node Visit(CaseOp op, Node n) { return(CopyDefault(m_destCmd.CreateCaseOp(op.Type), n)); }
/// <summary> /// Visitor pattern method for CaseOp /// </summary> /// <param name="op"> The CaseOp being visited </param> /// <param name="n"> The Node that references the Op </param> public virtual void Visit(CaseOp op, Node n) { VisitScalarOpDefault(op, n); }
private static bool ProcessSimplifyCase_EliminateWhenClauses( RuleProcessingContext context, CaseOp caseOp, System.Data.Entity.Core.Query.InternalTrees.Node caseOpNode, out System.Data.Entity.Core.Query.InternalTrees.Node newNode) { List <System.Data.Entity.Core.Query.InternalTrees.Node> args = (List <System.Data.Entity.Core.Query.InternalTrees.Node>)null; newNode = caseOpNode; int index1 = 0; while (index1 < caseOpNode.Children.Count) { if (index1 == caseOpNode.Children.Count - 1) { if (OpType.SoftCast == caseOpNode.Children[index1].Op.OpType) { return(false); } if (args != null) { args.Add(caseOpNode.Children[index1]); break; } break; } if (OpType.SoftCast == caseOpNode.Children[index1 + 1].Op.OpType) { return(false); } if (caseOpNode.Children[index1].Op.OpType != OpType.ConstantPredicate) { if (args != null) { args.Add(caseOpNode.Children[index1]); args.Add(caseOpNode.Children[index1 + 1]); } index1 += 2; } else { ConstantPredicateOp op = (ConstantPredicateOp)caseOpNode.Children[index1].Op; if (args == null) { args = new List <System.Data.Entity.Core.Query.InternalTrees.Node>(); for (int index2 = 0; index2 < index1; ++index2) { args.Add(caseOpNode.Children[index2]); } } if (op.IsTrue) { args.Add(caseOpNode.Children[index1 + 1]); break; } System.Data.Entity.Core.Query.PlanCompiler.PlanCompiler.Assert(op.IsFalse, "constant predicate must be either true or false"); index1 += 2; } } if (args == null) { return(false); } System.Data.Entity.Core.Query.PlanCompiler.PlanCompiler.Assert(args.Count > 0, "new args list must not be empty"); newNode = args.Count != 1 ? context.Command.CreateNode((Op)caseOp, args) : args[0]; return(true); }
/// <summary> /// CaseOp handling /// /// Pushes its desired properties to each of the WHEN/ELSE clauses /// </summary> /// <param name="op"> </param> /// <param name="n"> </param> public override void Visit(CaseOp op, Node n) { // First find the properties that my parent expects from me var pdProps = GetPropertyRefList(n); // push down the same properties to my then/else clauses. // the "when" clauses are irrelevant for (var i = 1; i < n.Children.Count - 1; i += 2) { var cdProps = pdProps.Clone(); AddPropertyRefs(n.Children[i], cdProps); } AddPropertyRefs(n.Children[n.Children.Count - 1], pdProps.Clone()); // Now visit the children VisitChildren(n); }
// <summary> // Copies a CaseOp // </summary> // <param name="op"> The Op to Copy </param> // <param name="n"> The Node that references the Op </param> // <returns> A copy of the original Node that references a copy of the original Op </returns> public override Node Visit(CaseOp op, Node n) { return CopyDefault(m_destCmd.CreateCaseOp(op.Type), n); }
/// <summary> /// We don't yet support nest pullups over Case /// </summary> /// <param name="op"></param> /// <param name="n"></param> /// <returns></returns> public override Node Visit(CaseOp op, Node n) { // Make sure we don't have a child that is a nest op. foreach (Node chi in n.Children) { if (chi.Op.OpType == OpType.Collect) { throw EntityUtil.NestingNotSupported(op, chi.Op); } else if (chi.Op.OpType == OpType.VarRef) { Var refVar = ((VarRefOp)chi.Op).Var; if (m_definingNodeMap.ContainsKey(refVar)) { throw EntityUtil.NestingNotSupported(op, chi.Op); } } } return VisitDefault(n); }