db-derby-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From kahat...@apache.org
Subject svn commit: r1593898 - in /db/derby/code/trunk/java: build/org/apache/derbyBuild/ engine/org/apache/derby/impl/sql/compile/ testing/org/apache/derbyTesting/functionTests/tests/lang/
Date Mon, 12 May 2014 07:30:46 GMT
Author: kahatlen
Date: Mon May 12 07:30:46 2014
New Revision: 1593898

URL: http://svn.apache.org/r1593898
Log:
DERBY-6566: Simplify handling of untyped nulls in CASE and NULLIF expressions

Make the parser represent untyped nulls the same way in CASE
expressions as in NULLIF expressions, so that ConditionalNode can
handle the two kinds of expressions uniformly.

Make the parser create a single ConditionalNode for each CASE
expression, instead of creating a tree of ConditionalNodes for a
single CASE expression.

Modified:
    db/derby/code/trunk/java/build/org/apache/derbyBuild/odbcgen_fragments.properties
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ConditionalNode.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ValueNodeList.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/sqlgrammar.jj
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/CaseExpressionTest.java

Modified: db/derby/code/trunk/java/build/org/apache/derbyBuild/odbcgen_fragments.properties
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/build/org/apache/derbyBuild/odbcgen_fragments.properties?rev=1593898&r1=1593897&r2=1593898&view=diff
==============================================================================
--- db/derby/code/trunk/java/build/org/apache/derbyBuild/odbcgen_fragments.properties (original)
+++ db/derby/code/trunk/java/build/org/apache/derbyBuild/odbcgen_fragments.properties Mon
May 12 07:30:46 2014
@@ -118,7 +118,7 @@ CASE WHEN (JDBC_SUBQUERY.DATA_TYPE IN (j
 \\\n	java.sql.Types::DATE, java.sql.Types::TIME, \
 \\\n	java.sql.Types::TIMESTAMP)) \
 \\\n		THEN JDBC_SUBQUERY.UNSIGNED_ATTRIBUTE \
-\\\n		ELSE CAST (NULL AS SMALLINT) END
+\\\n		ELSE NULL END
 
 # ----------
 #
@@ -141,7 +141,7 @@ CASE WHEN (JDBC_SUBQUERY.DATA_TYPE IN (j
 \\\n	java.sql.Types::DATE, java.sql.Types::TIME, \
 \\\n	java.sql.Types::TIMESTAMP)) \
 \\\n		THEN JDBC_SUBQUERY.AUTO_UNIQUE_VAL \
-\\\n		ELSE CAST (NULL AS SMALLINT) END
+\\\n		ELSE NULL END
 
 # ----------
 #

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ConditionalNode.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ConditionalNode.java?rev=1593898&r1=1593897&r2=1593898&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ConditionalNode.java
(original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ConditionalNode.java
Mon May 12 07:30:46 2014
@@ -21,6 +21,7 @@
 
 package	org.apache.derby.impl.sql.compile;
 
+import java.sql.Types;
 import java.util.List;
 import org.apache.derby.iapi.error.StandardException;
 import org.apache.derby.iapi.reference.ClassName;
@@ -45,28 +46,29 @@ import org.apache.derby.iapi.util.JBitSe
 
 class ConditionalNode extends ValueNode
 {
-	ValueNode		testCondition;
-	ValueNodeList	thenElseList;
-	//true means we are here for NULLIF(V1,V2), false means we are here for following
-	//CASE WHEN BooleanExpression THEN thenExpression ELSE elseExpression END
-	boolean	thisIsNullIfNode;
+    /** The list of test conditions in the WHEN clauses. */
+    private ValueNodeList testConditions;
+
+    /**
+     * The list of corresponding THEN expressions to the test conditions in
+     * {@link #testConditions}. The last element represents the ELSE clause.
+     */
+    private ValueNodeList thenElseList;
 
 	/**
      * Constructor for a ConditionalNode
 	 *
-	 * @param testCondition		The boolean test condition
+     * @param testConditions    The boolean test conditions
 	 * @param thenElseList		ValueNodeList with then and else expressions
      * @param cm                The context manager
 	 */
-    ConditionalNode(ValueNode testCondition,
+    ConditionalNode(ValueNodeList testConditions,
                     ValueNodeList thenElseList,
-                    boolean thisIsNullIfNode,
                     ContextManager cm)
 	{
         super(cm);
-        this.testCondition = testCondition;
+        this.testConditions = testConditions;
         this.thenElseList = thenElseList;
-        this.thisIsNullIfNode = thisIsNullIfNode;
 	}
 
 	/**
@@ -82,10 +84,10 @@ class ConditionalNode extends ValueNode
 		{
 			super.printSubNodes(depth);
 
-			if (testCondition != null)
+            if (testConditions != null)
 			{
-				printLabel(depth, "testCondition: ");
-				testCondition.treePrint(depth + 1);
+                printLabel(depth, "testConditions: ");
+                testConditions.treePrint(depth + 1);
 			}
 
 			if (thenElseList != null)
@@ -97,167 +99,43 @@ class ConditionalNode extends ValueNode
 	}
 
 	/**
-	 * Checks if the provided node is a CastNode.
-	 *
-	 * @param node	The node to check.
-	 * @return 		True if this node is a CastNode, false otherwise.
-	 */
-	private boolean isCastNode(ValueNode node) {
-        return node instanceof CastNode;
-	}
-
-	/**
-	 * Checks if the provided CastNode is cast to a SQL CHAR type.
-	 *
-	 * @param node	The CastNode to check.
-	 * @return		True if this CastNode's target type is CHAR,
-	 *              false otherwise.
-	 * @throws StandardException 
-	 */
-	private boolean isCastToChar(ValueNode node) throws StandardException {
-		if (node.getTypeServices().getTypeName().equals(TypeId.CHAR_NAME))
-			return true;
-		else
-			return false;
-	}
-
-	/**
-	 * Checks to see if the provided node represents
-	 * a parsing of an SQL NULL.
-	 *
-	 * @param node  The node to check.
-	 * @return      True if this node represents a SQL NULL, false otherwise.
-	 */
-	private boolean isNullNode(ValueNode node) {
-		if (isCastNode(node) &&
-			(((CastNode)node).castOperand instanceof UntypedNullConstantNode))
-			return true;
-		else
-			return false;
-	}
-
- 	/**
-	 * Checks to see if the provided node represents
-	 * a ConditionalNode.
-	 *
-	 * @param node    The node to check.
-	 * @return        True if this node is a CondtionalNode, false otherwise.
-	 */
-	private boolean isConditionalNode(ValueNode node) {
-        return node instanceof ConditionalNode;
-	}
-
-	/**
-	 * Checks to see if oldType should be casted to the newType.
-	 * Returns TRUE if the two DataTypeDescriptors have different
-	 * TypeID's or if the oldType is NULL.  Returns FALSE if the newType is
-	 * NULL or if the two Types are identical.
-	 *
-	 * @param newType    The type to cast oldType to if they're different.
-	 * @param oldType    The type that should be casted to the newType if
-	 *                   they're different.
-	 * @return           False if the newType is null or they have the same
-	 *                   TypeId, true otherwise.
-	 */
-	private boolean shouldCast(DataTypeDescriptor newType,
-		DataTypeDescriptor oldType) throws StandardException
-	{
-		if ((newType != null) &&
-			((oldType == null) ||
-			 (!oldType.getTypeId().equals(newType.getTypeId()))))
-			return true;
-		else
-			return false;
-	}
-
-	/**
-	 * This method is a 'prebind.'  We need to determine what the types of
-	 * the nodes are going to be before we can set all the SQLParsed NULL's
-	 * to the appropriate type.  After we bind, however, we want to ignore
-	 * the SQLParsed NULL's which will be bound to CHAR.  Also, we might
-	 * have to delve into the CASE Expression tree.
+     * Find a type to which we can cast the untyped NULLs generated by
+     * the parser (for clauses such as ELSE NULL). This does not have to
+     * be the type that the CASE expression ends up returning. It is
+     * enough that it is a type that can be converted into the type of the
+     * CASE expression in order to keep the type checking in the compiler
+     * happy.
 	 *
-	 * @param thenElseList    The thenElseList (recursive method)
 	 * @param fromList        The fromList (required for Column References).
 	 *
 	 * @exception             StandardException Thrown on error.
 	 */
-	private DataTypeDescriptor findType(ValueNodeList thenElseList,
+    private DataTypeDescriptor findType(
         FromList fromList, SubqueryList subqueryList, List<AggregateNode> aggregates)
 		throws StandardException
 	{
 		/* We need to "prebind" because we want the Types.  Provide
 		 * dummy SubqueryList and AggreateList (we don't care)
 		 */
+        thenElseList.bindExpression(fromList, subqueryList, aggregates);
 
-        ValueNode thenNode = thenElseList.elementAt(0).bindExpression(
-                fromList, subqueryList, aggregates);
-        thenElseList.setElementAt( thenNode, 0 );
-
-        ValueNode elseNode = thenElseList.elementAt(1).bindExpression(
-                fromList, subqueryList, aggregates);
-        thenElseList.setElementAt( elseNode, 1 );
-
-		DataTypeDescriptor thenType = thenNode.getTypeServices();
-		DataTypeDescriptor elseType = elseNode.getTypeServices();
-		DataTypeDescriptor theType = null;
-
-		/* If it's not a Cast Node or a Conditional Node, then we'll
-		 * use this type.
-		 */
-		if ((thenType != null) && !isCastNode(thenNode)
-			&& !isConditionalNode(thenNode))
-		{
-			return thenType;
-		}
-
-		/* If it's not cast to CHAR it isn't a SQL parsed NULL, so
-		 * we can use it.
-		 */
-		if (isCastNode(thenNode) && !isCastToChar(thenNode))
-			return thenNode.getTypeServices();
-
-		/* If we get here, we can't use the THEN node type, so we'll
-		 * use the ELSE node type
-		 */
-		if ((elseType != null) && !isCastNode(elseNode)
-			&& !isConditionalNode(elseNode))
-		{
-			return elseType;
-		}
-
-		if (isCastNode(elseNode) && !isCastToChar(elseNode))
-			return elseNode.getTypeServices();
-
-		/* If we get here, it means that we've got a conditional and a
-		 * SQL parsed NULL or two conditionals.
-		 */
-		if (isConditionalNode(thenNode))
-		{
-			theType =
-				findType(((ConditionalNode)thenNode).thenElseList, fromList,
-                    subqueryList, aggregates);
-		}
-
-		if (theType != null) return theType;
+        // Find the first typed expression.
+        DataTypeDescriptor dtd = thenElseList.getTypeServices();
 
-		// Two conditionals and the first one was all SQL parsed NULLS.
-		if (isConditionalNode(elseNode))
-		{
-			theType =
-				findType(((ConditionalNode)elseNode).thenElseList, fromList,
-                    subqueryList, aggregates);
-		}
+        if (dtd == null) {
+            // If none of the expressions have a type, we should probably have
+            // raised an error (DERBY-2002). However, Derby has always used the
+            // type CHAR(1) in this case, so return that for now.
+            dtd = DataTypeDescriptor.getBuiltInDataTypeDescriptor(
+                                                            Types.CHAR, 1);
+        }
 
-		if (theType != null) return theType;
-		return null;
+        return dtd;
 	}
+
 	/**
-	 * This recursive method will hunt through the ValueNodeList thenElseList
-	 * looking for SQL NULL's.  If it finds any, it casts them to the provided
-	 * castType.
+     * This method makes sure any SQL NULLs will be cast to the correct type.
 	 *
-	 * @param thenElseList    The thenElseList to update.
 	 * @param castType        The type to cast SQL parsed NULL's too.
 	 * @param fromList        FromList to pass on to bindExpression if recast is performed
 	 * @param subqueryList    SubqueryList to pass on to bindExpression if recast is performed
@@ -265,7 +143,7 @@ class ConditionalNode extends ValueNode
 	 *
 	 * @exception             StandardException Thrown on error.
 	 */
-	private void recastNullNodes(ValueNodeList thenElseList,
+    private void recastNullNodes(
 	                           DataTypeDescriptor castType, FromList fromList,
                                SubqueryList subqueryList, List<AggregateNode> aggregates)
 	 throws StandardException {
@@ -275,67 +153,15 @@ class ConditionalNode extends ValueNode
 		
 		// need to have nullNodes nullable
 		castType = castType.getNullabilityType(true);
-        ValueNode thenNode = thenElseList.elementAt(0);
-        ValueNode elseNode = thenElseList.elementAt(1);
-
-		// first check if the "then" node is NULL
-		if (isNullNode(thenNode) &&
-		    shouldCast(castType, thenNode.getTypeServices()))
-		{
-			// recast and rebind. findTypes would have bound as SQL CHAR.
-			// need to rebind here. (DERBY-3032)
-			thenElseList.setElementAt(recastNullNode(thenNode, castType), 0);
-            thenElseList.elementAt(0).bindExpression(
-                    fromList, subqueryList, aggregates);
-			
-		// otherwise recurse on thenNode, but only if it's a conditional
-		} else if (isConditionalNode(thenNode)) {
-			recastNullNodes(((ConditionalNode)thenNode).thenElseList,
-                            castType,fromList, subqueryList, aggregates);
-		}
 
-		// lastly, check if the "else" node is NULL
-		if (isNullNode(elseNode) &&
-		    shouldCast(castType, elseNode.getTypeServices()))
-		{
-			// recast and rebind. findTypes would have bound as SQL CHAR.
-			// need to rebind here. (DERBY-3032)
-			thenElseList.setElementAt(recastNullNode(elseNode, castType), 1);
-            thenElseList.elementAt(1).bindExpression(
-                    fromList, subqueryList, aggregates);
-		// otherwise recurse on elseNode, but only if it's a conditional
-		} else if (isConditionalNode(elseNode)) {
-			recastNullNodes(((ConditionalNode)elseNode).thenElseList,
-                            castType,fromList,subqueryList,aggregates);
-		}
-	}
-
-	/**
-	 * recastNullNode casts the nodeToCast node to the typeToUse.
-	 *
-	 * recastNullNode is called by recastNullNodes.  It is called when the
-	 * nodeToCast is an UntypedNullConstantNode that's been cast by the
-	 * SQLParser to a CHAR.  The node needs to be recasted to the same type
-	 * of the other nodes in order to prevent the type compatibility error
-	 * 42X89 from occuring.  SQL Standard requires that:
-	 *
-	 *  VALUES CASE WHEN 1=2 THEN 3 ELSE NULL END
-	 *
-	 * returns NULL and not an error message.
-	 *
-	 * @param nodeToCast    The node that represents a SQL NULL value.
-	 * @param typeToUse     The type which the nodeToCast should be
-	 *                      recasted too.
-	 *
-	 * @exception StandardException Thrown on error.
-	 */
-    private CastNode recastNullNode(ValueNode nodeToCast,
-		DataTypeDescriptor typeToUse) throws StandardException
-	{
-        return new CastNode(
-					((CastNode)nodeToCast).castOperand,
-					typeToUse,
-					getContextManager());
+        for (int i = 0; i < thenElseList.size(); i++) {
+            ValueNode vn = thenElseList.elementAt(i);
+            if (vn instanceof UntypedNullConstantNode) {
+                CastNode cast = new CastNode(vn, castType, getContextManager());
+                cast.bindExpression(fromList, subqueryList, aggregates);
+                thenElseList.setElementAt(cast, i);
+            }
+        }
 	}
 
 	/**
@@ -359,66 +185,29 @@ class ConditionalNode extends ValueNode
         
         int previousReliability = orReliability( CompilerContext.CONDITIONAL_RESTRICTION
);
         
-		testCondition = testCondition.bindExpression(fromList,
+        testConditions.bindExpression(fromList,
 			subqueryList,
             aggregates);
 
-		if (thisIsNullIfNode) {
-			//for NULLIF(V1,V2), parser binds thenElseList.elementAt(0) to untyped NULL
-			//At bind phase, we should bind it to the type of V1 since now we know the
-			//type of V1  
-			BinaryComparisonOperatorNode bcon = (BinaryComparisonOperatorNode)testCondition;
-			
-			/* 
-			 * NULLIF(V1,V2) is equivalent to: 
-			 * 
-			 *    CASE WHEN V1=V2 THEN NULL ELSE V1 END
-			 * 
-			 * The untyped NULL should have a data type descriptor
-			 * that allows its value to be nullable.
-			 */
-            CastNode cast = new CastNode(
-                        thenElseList.elementAt(0),
-						bcon.getLeftOperand().getTypeServices().getNullabilityType(true),
-						getContextManager());
-
-			thenElseList.setElementAt(cast,0);
-			thenElseList.bindExpression(fromList,
-				subqueryList,
-                aggregates);
-
-		} else {
-			/* Following call to "findType()"  and "recastNullNodes" will indirectly bind the
-			 * expressions in the thenElseList, so no need to call
-			 * "thenElseList.bindExpression(...)" after we do this.
-			 * DERBY-2986.
-			 */
-			recastNullNodes(thenElseList,
-                findType(thenElseList, fromList, subqueryList, aggregates),fromList,
-					subqueryList,
-                    aggregates);
-			
- 		}
-		
-		
-		// Can't get the then and else expressions until after they've been bound
-		// expressions have been bound by findType and rebound by recastNullNodes if needed.
-        ValueNode thenExpression = thenElseList.elementAt(0);
-        ValueNode elseExpression = thenElseList.elementAt(1);
+        // Following call to "findType()"  and "recastNullNodes" will
+        // indirectly bind the expressions in the thenElseList, so no need
+        // to call "thenElseList.bindExpression(...)" after we do this.
+        // DERBY-2986.
+        recastNullNodes(findType(fromList, subqueryList, aggregates),
+                        fromList, subqueryList, aggregates);
+
+        // Set the result type of this conditional to be the dominant type
+        // of the result expressions.
+        setType(thenElseList.getDominantTypeServices());
 
 		/* testCondition must be a boolean expression.
 		 * If it is a ? parameter on the left, then set type to boolean,
 		 * otherwise verify that the result type is boolean.
 		 */
-		if (testCondition.requiresTypeFromContext())
-		{
-			testCondition.setType(
-							new DataTypeDescriptor(
-										TypeId.BOOLEAN_ID,
-										true));
-		}
-		else
-		{
+        testConditions.setParameterDescriptor(
+                new DataTypeDescriptor(TypeId.BOOLEAN_ID, true));
+
+        for (ValueNode testCondition : testConditions) {
 			if ( ! testCondition.getTypeServices().getTypeId().equals(
 														TypeId.BOOLEAN_ID))
 			{
@@ -433,26 +222,9 @@ class ConditionalNode extends ValueNode
 		{
 			throw StandardException.newException(SQLState.LANG_ALL_RESULT_EXPRESSIONS_PARAMS, "conditional");
 		}
-		else if (thenElseList.containsParameterNode())
-		{
-			/* Set the parameter's type to be the same as the other element in
-			 * the list
-			 */
-
-			DataTypeDescriptor dts;
-			ValueNode typeExpression;
-
-			if (thenExpression.requiresTypeFromContext())
-			{
-				dts = elseExpression.getTypeServices();
-			}
-			else
-			{
-				dts = thenExpression.getTypeServices();
-			}
 
-			thenElseList.setParameterDescriptor(dts);
-		}
+        // Set the type of the parameters.
+        thenElseList.setParameterDescriptor(getTypeServices());
 
 		/* The then and else expressions must be type compatible */
 		ClassInspector cu = getClassFactory().getClassInspector();
@@ -463,27 +235,22 @@ class ConditionalNode extends ValueNode
 		** since we are going to generate a cast node, but that might
 		** be confusing to users...
 		*/
-
-		// RESOLVE DJDOI - this looks wrong, why should the then expression
-		// be comparable to the then expression ??
-		if (! thenExpression.getTypeServices().
-			 comparable(elseExpression.getTypeServices(), false, getClassFactory()) &&
-			! cu.assignableTo(thenExpression.getTypeId().getCorrespondingJavaTypeName(),
-							  elseExpression.getTypeId().getCorrespondingJavaTypeName()) &&
-			! cu.assignableTo(elseExpression.getTypeId().getCorrespondingJavaTypeName(),
-							  thenExpression.getTypeId().getCorrespondingJavaTypeName()))
-		{
-			throw StandardException.newException(SQLState.LANG_NOT_TYPE_COMPATIBLE, 
-						thenExpression.getTypeId().getSQLTypeName(),
-						elseExpression.getTypeId().getSQLTypeName()
-						);
-		}
-
-		/*
-		** Set the result type of this conditional to be the dominant type
-		** of the result expressions.
-		*/
-		setType(thenElseList.getDominantTypeServices());
+        for (ValueNode expr : thenElseList) {
+            DataTypeDescriptor dtd = expr.getTypeServices();
+            String javaTypeName =
+                    dtd.getTypeId().getCorrespondingJavaTypeName();
+            String resultJavaTypeName =
+                    getTypeId().getCorrespondingJavaTypeName();
+
+            if (!dtd.comparable(getTypeServices(), false, getClassFactory())
+                    && !cu.assignableTo(javaTypeName, resultJavaTypeName)
+                    && !cu.assignableTo(resultJavaTypeName, javaTypeName)) {
+                throw StandardException.newException(
+                        SQLState.LANG_NOT_TYPE_COMPATIBLE,
+                        dtd.getTypeId().getSQLTypeName(),
+                        getTypeId().getSQLTypeName());
+            }
+        }
 
         // The result is nullable if and only if at least one of the result
         // expressions is nullable (DERBY-6567).
@@ -494,37 +261,17 @@ class ConditionalNode extends ValueNode
 		** stick it over the original expression
 		*/
 		TypeId condTypeId = getTypeId();
-        TypeId thenTypeId = thenElseList.elementAt(0).getTypeId();
-        TypeId elseTypeId = thenElseList.elementAt(1).getTypeId();
-
-		/* Need to generate conversion if thenExpr or elseExpr is not of 
-		 * dominant type.  (At least 1 of them must be of the dominant type.)
-		 */
-		if (thenTypeId.typePrecedence() != condTypeId.typePrecedence())
-		{
-            ValueNode cast = new CastNode(
-                                thenElseList.elementAt(0),
-                                getTypeServices(),	// cast to dominant type
-								getContextManager());
-			cast = cast.bindExpression(fromList, 
-											subqueryList,
-                                            aggregates);
-			
-			thenElseList.setElementAt(cast, 0);
-		}
-
-		else if (elseTypeId.typePrecedence() != condTypeId.typePrecedence())
-		{
-            ValueNode cast = new CastNode(
-                                thenElseList.elementAt(1),
-                                getTypeServices(),	// cast to dominant type
-								getContextManager());
-			cast = cast.bindExpression(fromList, 
-											subqueryList,
-                                            aggregates);
-			
-			thenElseList.setElementAt(cast, 1);
-		}
+        for (int i = 0; i < thenElseList.size(); i++) {
+            ValueNode expr = thenElseList.elementAt(i);
+            if (expr.getTypeId().typePrecedence()
+                    != condTypeId.typePrecedence()) {
+                // Cast to dominant type.
+                ValueNode cast = new CastNode(
+                        expr, getTypeServices(), getContextManager());
+                cast = cast.bindExpression(fromList, subqueryList, aggregates);
+                thenElseList.setElementAt(cast, i);
+            }
+        }
 
         cc.setReliability( previousReliability );
         
@@ -553,9 +300,9 @@ class ConditionalNode extends ValueNode
 								PredicateList outerPredicateList) 
 					throws StandardException
 	{
-		testCondition = testCondition.preprocess(numTables,
-												 outerFromList, outerSubqueryList,
-												 outerPredicateList);
+        testConditions.preprocess(numTables,
+                                  outerFromList, outerSubqueryList,
+                                  outerPredicateList);
  		thenElseList.preprocess(numTables,
 								outerFromList, outerSubqueryList,
 								outerPredicateList);
@@ -602,7 +349,7 @@ class ConditionalNode extends ValueNode
 
 		boolean pushable;
 
-		pushable = testCondition.categorize(referencedTabs, simplePredsOnly);
+        pushable = testConditions.categorize(referencedTabs, simplePredsOnly);
 		pushable = (thenElseList.categorize(referencedTabs, simplePredsOnly) && pushable);
 		return pushable;
 	}
@@ -619,7 +366,7 @@ class ConditionalNode extends ValueNode
     ValueNode remapColumnReferencesToExpressions()
 		throws StandardException
 	{
-		testCondition = testCondition.remapColumnReferencesToExpressions();
+        testConditions = testConditions.remapColumnReferencesToExpressions();
 		thenElseList = thenElseList.remapColumnReferencesToExpressions();
 		return this;
 	}
@@ -632,7 +379,7 @@ class ConditionalNode extends ValueNode
     @Override
     boolean isConstantExpression()
 	{
-		return (testCondition.isConstantExpression() &&
+        return (testConditions.isConstantExpression() &&
 			    thenElseList.isConstantExpression());
 	}
 
@@ -640,7 +387,7 @@ class ConditionalNode extends ValueNode
     @Override
     boolean constantExpression(PredicateList whereClause)
 	{
-		return (testCondition.constantExpression(whereClause) &&
+        return (testConditions.constantExpression(whereClause) &&
 			    thenElseList.constantExpression(whereClause));
 	}
 
@@ -667,16 +414,12 @@ class ConditionalNode extends ValueNode
         // NOT CASE WHEN a THEN b ELSE c END is equivalent to
         // CASE WHEN a THEN NOT b ELSE NOT c END, so just push the
         // NOT node down to the THEN and ELSE expressions.
-        for (int i = 0; i < thenElseList.size(); i++) {
-            thenElseList.setElementAt(
-                    thenElseList.elementAt(i).eliminateNots(underNotNode),
-                    i);
-        }
+        thenElseList.eliminateNots(underNotNode);
 
-        // Eliminate NOTs in the WHEN expression too. The NOT node above us
-        // should not be pushed into the WHEN expression, though, as that
+        // Eliminate NOTs in the WHEN expressions too. The NOT node above us
+        // should not be pushed into the WHEN expressions, though, as that
         // would alter the meaning of the CASE expression.
-        testCondition = testCondition.eliminateNots(false);
+        testConditions.eliminateNots(false);
 
 		return this;
 	}
@@ -693,16 +436,34 @@ class ConditionalNode extends ValueNode
     void generateExpression(ExpressionClassBuilder acb, MethodBuilder mb)
 									throws StandardException
 	{
-		testCondition.generateExpression(acb, mb);
-		mb.cast(ClassName.BooleanDataValue);
-		mb.push(true);
-		mb.callMethod(VMOpcode.INVOKEINTERFACE, (String) null, "equals", "boolean", 1);
-
-		mb.conditionalIf();
-         thenElseList.elementAt(0).generateExpression(acb, mb);
-		mb.startElseCode();
-         thenElseList.elementAt(1).generateExpression(acb, mb);
-		mb.completeConditional();
+        if (SanityManager.DEBUG) {
+            // There should be at least one test condition.
+            SanityManager.ASSERT(testConditions.size() > 0);
+            // Because of the ELSE clause, there should always be one
+            // more element in thenElseList than in testConditions.
+            SanityManager.ASSERT(
+                    thenElseList.size() == testConditions.size() + 1);
+        }
+
+        // Generate code for all WHEN ... THEN clauses.
+        for (int i = 0; i < testConditions.size(); i++) {
+            testConditions.elementAt(i).generateExpression(acb, mb);
+            mb.cast(ClassName.BooleanDataValue);
+            mb.push(true);
+            mb.callMethod(VMOpcode.INVOKEINTERFACE, (String) null,
+                          "equals", "boolean", 1);
+            mb.conditionalIf();
+            thenElseList.elementAt(i).generateExpression(acb, mb);
+            mb.startElseCode();
+        }
+
+        // Generate code for the ELSE clause.
+        thenElseList.elementAt(thenElseList.size() - 1)
+                    .generateExpression(acb, mb);
+
+        for (int i = 0; i < testConditions.size(); i++) {
+            mb.completeConditional();
+        }
 	}
 
 	/**
@@ -718,9 +479,9 @@ class ConditionalNode extends ValueNode
 	{
 		super.acceptChildren(v);
 
-		if (testCondition != null)
+        if (testConditions != null)
 		{
-			testCondition = (ValueNode)testCondition.accept(v);
+            testConditions = (ValueNodeList) testConditions.accept(v);
 		}
 
 		if (thenElseList != null)
@@ -736,7 +497,7 @@ class ConditionalNode extends ValueNode
 	{
         if (isSameNodeKind(o)) {
 			ConditionalNode other = (ConditionalNode)o;
-            return testCondition.isEquivalent(other.testCondition) &&
+            return testConditions.isEquivalent(other.testConditions) &&
                     thenElseList.isEquivalent(other.thenElseList);
 		}
 

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ValueNodeList.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ValueNodeList.java?rev=1593898&r1=1593897&r2=1593898&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ValueNodeList.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/ValueNodeList.java Mon
May 12 07:30:46 2014
@@ -494,6 +494,18 @@ class ValueNodeList extends QueryTreeNod
 		}
 	}
 
+    /**
+     * Eliminate NotNodes in all the nodes in this list.
+     *
+     * @param underNotNode whether or not we are under a NotNode
+     * @see ValueNode#eliminateNots(boolean)
+     */
+    void eliminateNots(boolean underNotNode) throws StandardException {
+        for (int i = 0; i < size(); i++) {
+            setElementAt(elementAt(i).eliminateNots(underNotNode), i);
+        }
+    }
+
 	/**
 	 * Set the descriptor for every ParameterNode in the list.
 	 *

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/sqlgrammar.jj
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/sqlgrammar.jj?rev=1593898&r1=1593897&r2=1593898&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/sqlgrammar.jj (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/sqlgrammar.jj Mon May
12 07:30:46 2014
@@ -12571,82 +12571,69 @@ valueSpecification() throws StandardExce
 		// "NULLIF(L, R)" is the same as "L=R ? untyped NULL : L"
 		// An impl assumption here is that Derby can promote CHAR to any comparable datatypes such
as numeric
 		ContextManager cm = getContextManager();
+        ValueNodeList whenList = new ValueNodeList(cm);
         ValueNodeList thenElseList = new ValueNodeList(cm);
 
+        // Generate L = R for the WHEN clause.
+        whenList.addElement(new BinaryRelationalOperatorNode(
+                                    BinaryRelationalOperatorNode.K_EQUALS,
+                                    leftExpression,
+                                    rightExpression,
+                                    false,
+                                    cm));
+
 		//Use untyped null for then clause at this point. At the bind time, we will cast it to
the datatype of L 
        thenElseList.addElement(new UntypedNullConstantNode(cm));
 		thenElseList.addElement(leftExpression);
 
-        return new ConditionalNode(
-                   new BinaryRelationalOperatorNode(
-                       BinaryRelationalOperatorNode.K_EQUALS,
-                       leftExpression,
-                       rightExpression,
-                       false,
-                       cm),
-                   thenElseList,
-                   true, // this node is for nullif
-                   cm);
+        return new ConditionalNode(whenList, thenElseList, cm);
 	}
 |
 	// CASE WHEN P1 THEN [T1 | NULL] (WHEN Pi THEN [Ti | NULL])* [ELSE E | NULL] END
-	<CASE> value = whenThenExpression()
+    <CASE> value = searchedCaseExpression()
 	{
 		return value;
 	}
 }
 
-/*
- * <A NAME="caseExpression">caseExpression</A>
- */
-ValueNode
-caseExpression() throws StandardException :
+ConditionalNode searchedCaseExpression() throws StandardException :
 {
-	ValueNode	   expr;
+    ContextManager cm = getContextManager();
+    ValueNodeList whenList = new ValueNodeList(cm);
+    ValueNodeList thenElseList = new ValueNodeList(cm);
+    ValueNode elseExpr = null;
 }
 {
-	<END>
-	{
-        ValueNode value = new CastNode(
-            new UntypedNullConstantNode(getContextManager()),
-            DataTypeDescriptor.getBuiltInDataTypeDescriptor(Types.CHAR, 1),
-            getContextManager());
-		((CastNode) value).setForExternallyGeneratedCASTnode();
-		return value;
-	}
-|
-	<ELSE> expr = thenElseExpression() <END>
-	{
-		return expr;
-	}
-|
-	expr = whenThenExpression()
-	{
-		return expr;
-	}
+    ( whenThenExpression(whenList, thenElseList) ) +
+    ( <ELSE> elseExpr = thenElseExpression() ) ?
+    <END>
+    {
+        if (elseExpr == null) {
+            // ELSE NULL is implicit if there is no ELSE clause.
+            elseExpr = new UntypedNullConstantNode(cm);
+        }
+        thenElseList.addElement(elseExpr);
+
+        return new ConditionalNode(whenList, thenElseList, cm);
+    }
 }
 
 /*
  * <A NAME="whenThenExpression">whenThenExpression</A>
  */
-ValueNode
-whenThenExpression() throws StandardException :
+void
+whenThenExpression(ValueNodeList whenList, ValueNodeList thenElseList)
+throws StandardException :
 {
 	ValueNode	   expr;
 	ValueNode	   thenExpr;
-	ValueNode	   elseExpr;
 }
 {
 	<WHEN> expr = valueExpression()
 	<THEN> thenExpr = thenElseExpression()
-	elseExpr = caseExpression()
 	{
-		ContextManager cm = getContextManager();
-        ValueNodeList thenElseList = new ValueNodeList(cm);
-		thenElseList.addElement(thenExpr); // then
-		thenElseList.addElement(elseExpr); // else
-
-        return new ConditionalNode(expr, thenElseList, false, cm);
+        whenList.addElement(expr);
+        thenElseList.addElement(thenExpr);
 	}
 }
 
@@ -12662,12 +12649,7 @@ thenElseExpression() throws StandardExce
 	LOOKAHEAD ( {getToken(1).kind == NULL} )
 	<NULL>
 	{
-        ValueNode value = new CastNode(
-            new UntypedNullConstantNode(getContextManager()),
-            DataTypeDescriptor.getBuiltInDataTypeDescriptor(Types.CHAR, 1),
-            getContextManager());
-		((CastNode) value).setForExternallyGeneratedCASTnode();
-		return value;
+        return new UntypedNullConstantNode(getContextManager());
 	}
 |
     expr = valueExpression()

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/CaseExpressionTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/CaseExpressionTest.java?rev=1593898&r1=1593897&r2=1593898&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/CaseExpressionTest.java
(original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/CaseExpressionTest.java
Mon May 12 07:30:46 2014
@@ -508,4 +508,43 @@ public class CaseExpressionTest extends 
         JDBC.assertSingleValueResultSet(ps.executeQuery(), "0");
     }
 
+    /**
+     * Test how untyped NULLs are handled.
+     */
+    public void testUntypedNulls() throws SQLException {
+        Statement s = createStatement();
+
+        // When all branches specify NULL, then Derby currently returns NULL
+        // with type CHAR(1). It should have raised an error according to the
+        // SQL standard. See DERBY-2002.
+        String[] allNull = {
+            "values case when true then null end",
+            "values case when true then null else null end",
+            "values case when true then null when false then null else null end"
+        };
+        for (String sql : allNull) {
+            JDBC.assertSingleValueResultSet(s.executeQuery(sql), null);
+        }
+
+        // Check that expressions with untyped NULLs compile as long as
+        // there is at least one typed expression.
+        JDBC.assertFullResultSet(s.executeQuery(
+                "select case when a then 1 when b then null end, "
+                    + "case when a then null when b then 1 end, "
+                    + "case when a then null when b then null else 1 end "
+                    + "from (values (false, false), (false, true), "
+                    + " (true, false), (true, true)) v(a, b) order by a, b"),
+            new Object[][] {
+                { null, null, 1    },
+                { null, 1,    null },
+                { 1,    null, null },
+                { 1,    null, null },
+            },
+            false);
+
+        // When there is a typed NULL, its type has to be compatible with
+        // the types of the other expressions.
+        assertCompileError("42X89",
+            "values case when 1<>1 then 'abc' else cast(null as smallint) end");
+    }
 }



Mime
View raw message