Index: java/engine/org/apache/derby/impl/sql/compile/SelectNode.java =================================================================== --- java/engine/org/apache/derby/impl/sql/compile/SelectNode.java (revision 611937) +++ java/engine/org/apache/derby/impl/sql/compile/SelectNode.java (working copy) @@ -816,8 +816,11 @@ * because the subquery transformations assume that any subquery operator * negation has already occurred. */ - normExpressions(); - + whereClause = normExpressions(whereClause); + // DERBY-3257. We need to normalize the having clause as well, because + // preProcess expects CNF. + havingClause = normExpressions(havingClause); + /** * This method determines if (1) the query is a LOJ, and (2) if the LOJ is a candidate for * reordering (i.e., linearization). The condition for LOJ linearization is: @@ -880,6 +883,18 @@ } if (havingClause != null) { + // DERBY-3257 + // Mark subqueries that are part of the having clause as + // such so we can avoid flattenning later. Having subqueries + // cannot be flattened because we cannot currently handle + // column references at the same source level. + // DERBY-3257 required we normalize the having clause which + // triggered flattening because SubqueryNode.underTopAndNode + // became true after normalization. We needed another way to + // turn flattening off. Perhaps the long term solution is + // to avoid this restriction all together but that was beyond + // the scope of this bugfix. + havingSubquerys.markHavingSubqueries(); havingClause = havingClause.preprocess( numTables, fromList, havingSubquerys, wherePredicates); } @@ -1070,7 +1085,7 @@ * * @exception StandardException Thrown on error */ - private void normExpressions() + private ValueNode normExpressions(ValueNode boolClause) throws StandardException { /* For each expression tree: @@ -1079,41 +1094,43 @@ * top level expression. (putAndsOnTop()) * o Finish the job (changeToCNF()) */ - if (whereClause != null) + if (boolClause != null) { - whereClause = whereClause.eliminateNots(false); + boolClause = boolClause.eliminateNots(false); if (SanityManager.DEBUG) { - if (!(whereClause.verifyEliminateNots()) ) + if (!(boolClause.verifyEliminateNots()) ) { - whereClause.treePrint(); + boolClause.treePrint(); SanityManager.THROWASSERT( - "whereClause in invalid form: " + whereClause); + "boolClause in invalid form: " + boolClause); } } - whereClause = whereClause.putAndsOnTop(); + boolClause = boolClause.putAndsOnTop(); if (SanityManager.DEBUG) { - if (! ((whereClause instanceof AndNode) && - (whereClause.verifyPutAndsOnTop())) ) + if (! ((boolClause instanceof AndNode) && + (boolClause.verifyPutAndsOnTop())) ) { - whereClause.treePrint(); + boolClause.treePrint(); SanityManager.THROWASSERT( - "whereClause in invalid form: " + whereClause); + "boolClause in invalid form: " + boolClause); } } - whereClause = whereClause.changeToCNF(true); + boolClause = boolClause.changeToCNF(true); if (SanityManager.DEBUG) { - if (! ((whereClause instanceof AndNode) && - (whereClause.verifyChangeToCNF())) ) + if (! ((boolClause instanceof AndNode) && + (boolClause.verifyChangeToCNF())) ) { - whereClause.treePrint(); + boolClause.treePrint(); SanityManager.THROWASSERT( - "whereClause in invalid form: " + whereClause); + "boolClause in invalid form: " + boolClause); } } } + + return boolClause; } /** Index: java/engine/org/apache/derby/impl/sql/compile/SubqueryNode.java =================================================================== --- java/engine/org/apache/derby/impl/sql/compile/SubqueryNode.java (revision 611937) +++ java/engine/org/apache/derby/impl/sql/compile/SubqueryNode.java (working copy) @@ -120,6 +120,12 @@ ValueNode leftOperand; boolean pushedNewPredicate; + /** + * is this subquery part of a having clause. We need to know this so + * we can avoid flattening. + */ + boolean havingSubquery = false; + /* Expression subqueries on the right side of a BinaryComparisonOperatorNode * will get passed a pointer to that node prior to preprocess(). This * allows us to replace the entire comparison, if we want to, when @@ -609,7 +615,7 @@ * of a BinaryComparisonOperatorNode. */ flattenable = (resultSet instanceof RowResultSetNode) && - underTopAndNode && + underTopAndNode && !havingSubquery && parentComparisonOperator instanceof BinaryComparisonOperatorNode; if (flattenable) { @@ -673,7 +679,7 @@ boolean flattenableNotExists = (isNOT_EXISTS() || canAllBeFlattened()); flattenable = (resultSet instanceof SelectNode) && - underTopAndNode && + underTopAndNode && !havingSubquery && (isIN() || isANY() || isEXISTS() || flattenableNotExists || parentComparisonOperator != null); @@ -2285,4 +2291,21 @@ { return false; } + + /** + * Is this subquery part of a having clause? + * + * @return true if it is part of a having clause, otherwise false + */ + public boolean isHavingSubquery() { + return havingSubquery; + } + + /** + * Mark this subquery as being part of a having clause. + * @param havingSubquery + */ + public void setHavingSubquery(boolean havingSubquery) { + this.havingSubquery = havingSubquery; + } } Index: java/engine/org/apache/derby/impl/sql/compile/SubqueryList.java =================================================================== --- java/engine/org/apache/derby/impl/sql/compile/SubqueryList.java (revision 611937) +++ java/engine/org/apache/derby/impl/sql/compile/SubqueryList.java (working copy) @@ -237,5 +237,17 @@ ((SubqueryNode) elementAt(index)).getResultSet().decrementLevel(decrement); } } + + public void markHavingSubqueries() { + int size = size(); + + for (int index = 0; index < size; index++) + { + SubqueryNode subqueryNode; + + subqueryNode = (SubqueryNode) elementAt(index); + subqueryNode.setHavingSubquery(true); + } + } } Index: java/testing/org/apache/derbyTesting/functionTests/tests/lang/GroupByTest.java =================================================================== --- java/testing/org/apache/derbyTesting/functionTests/tests/lang/GroupByTest.java (revision 611937) +++ java/testing/org/apache/derbyTesting/functionTests/tests/lang/GroupByTest.java (working copy) @@ -168,5 +168,26 @@ s.executeUpdate("drop table yy"); } + + + /** + * DERBY-3257 check for correct number of rows returned with + * or in having clause. + * + * @throws SQLException + */ + public void testDerby3257() throws SQLException + { + Statement s = createStatement(); + s.executeUpdate("CREATE TABLE TAB ( ID VARCHAR(20), INFO VARCHAR(20))"); + s.executeUpdate("insert into TAB values ('1', 'A')"); + s.executeUpdate("insert into TAB values ('2', 'A')"); + s.executeUpdate("insert into TAB values ('3', 'B')"); + s.executeUpdate("insert into TAB values ('4', 'B')"); + ResultSet rs = s.executeQuery("SELECT t0.INFO, COUNT(t0.ID) FROM TAB t0 GROUP BY t0.INFO HAVING (t0.INFO = 'A' OR t0.INFO = 'B') AND t0.INFO IS NOT NULL"); + String [][] expectedRows = {{"A","2"},{"B","2"}}; + JDBC.assertFullResultSet(rs, expectedRows); + s.executeUpdate("DROP TABLE TAB"); + } }