blob: d33f1252378506b66e820b5a7e141bc8ba91ffd0 [file] [log] [blame]
/*
* Derby - Class org.apache.derbyTesting.functionTests.tests.lang.SubqueryFlatteningTest
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*/
package org.apache.derbyTesting.functionTests.tests.lang;
import java.sql.SQLException;
import java.sql.Statement;
import junit.framework.Test;
import org.apache.derbyTesting.junit.BaseJDBCTestCase;
import org.apache.derbyTesting.junit.JDBC;
import org.apache.derbyTesting.junit.RuntimeStatisticsParser;
import org.apache.derbyTesting.junit.SQLUtilities;
import org.apache.derbyTesting.junit.TestConfiguration;
/**
* This test verifies that flattening of subqueries works correctly.
*
* The test cases in <tt>subqueryFlattening.sql</tt> could be moved to this
* class when they are converted to JUnit.
*/
public class SubqueryFlatteningTest extends BaseJDBCTestCase {
public SubqueryFlatteningTest(String name) {
super(name);
}
public static Test suite() {
// We're testing the SQL layer, so we run the test in embedded
// mode only.
return TestConfiguration.embeddedSuite(SubqueryFlatteningTest.class);
}
/**
* Set up the test environment. Turn off auto-commit so that all the test
* data can easily be removed by the rollback performed in
* {@code BaseJDBCTestCase.tearDown()}.
*/
protected void setUp() throws SQLException {
setAutoCommit(false);
}
/**
* Enable collection of runtime statistics in the current connection.
* @param s the statement to use for enabling runtime statistics
*/
private void enableRuntimeStatistics(Statement s) throws SQLException {
s.execute("call syscs_util.syscs_set_runtimestatistics(1)");
}
/**
* Check that a query returns the expected rows and whether or not it was
* flattened to an exists join (or not exists join). An error is raised if
* wrong results are returned or if the query plan is not the expected one.
*
* @param s the statement on which the query is executed
* @param sql the query text
* @param rows the expected result
* @param flattenable whether or not we expect the query to be flattened
* to a (not) exists join
* @throws SQLException if a database error occurs
* @throws junit.framework.AssertionFailedError if the wrong results are
* returned from the query, or if the query plan is not as expected
*/
private void checkExistsJoin(Statement s, String sql, String[][] rows,
boolean flattenable)
throws SQLException
{
JDBC.assertFullResultSet(s.executeQuery(sql), rows);
RuntimeStatisticsParser parser =
SQLUtilities.getRuntimeStatisticsParser(s);
assertEquals("unexpected plan", flattenable, parser.usedExistsJoin());
}
/**
* DERBY-4001: Test that certain NOT EXISTS/NOT IN/ALL subqueries are
* flattened, and that their predicates are not pulled out. Their
* predicates are known to be always false, so when they are (correctly)
* applied on the subquery, they will cause all the rows from the outer
* query to be returned. If the predicates are (incorrectly) pulled out to
* the outer query, the query won't return any rows at all. DERBY-4001.
*/
public void testNotExistsFlattenablePredicatesNotPulled()
throws SQLException
{
Statement s = createStatement();
// X must be NOT NULL, otherwise X NOT IN and X < ALL won't be
// rewritten to NOT EXISTS
s.execute("create table t (x int not null)");
s.execute("insert into t values 1,2,3");
enableRuntimeStatistics(s);
String[][] allRows = {{"1"}, {"2"}, {"3"}};
checkExistsJoin(
s,
"select * from t where not exists (select x from t where 1<>1)",
allRows, true);
checkExistsJoin(
s,
"select * from t where x not in (select x from t where 1<>1)",
allRows, true);
checkExistsJoin(
s,
"select * from t where x < all (select x from t where 1<>1)",
allRows, true);
}
/**
* DERBY-4001: Test that some ALL subqueries that used to be flattened to
* a not exists join and return incorrect results, are not flattened.
* These queries should not be flattened because the generated NOT EXISTS
* JOIN condition or some of the subquery's predicates could be pushed
* down into the left side of the join, which is not allowed in a not
* exists join because the predicates have a completely different effect
* if they're used on one side of the join than if they're used on the
* other side of the join.
*/
public void testAllNotFlattenableToNotExists() throws SQLException {
Statement s = createStatement();
// X must be NOT NULL, otherwise rewriting ALL to NOT EXISTS won't even
// be attempted
s.execute("create table t (x int not null)");
s.execute("insert into t values 1,2,3");
enableRuntimeStatistics(s);
String[][] allRows = {{"1"}, {"2"}, {"3"}};
// Join condition is X >= 100, which should make the right side of
// the not exists join empty and return all rows from the left side.
// If (incorrectly) pushed down on the left side, no rows will be
// returned.
checkExistsJoin(
s, "select * from t where x < all (select 100 from t)",
allRows, false);
// Join condition is 1 >= 100, which should make the right side of
// the not exists join empty and return all rows from the left side.
// If (incorrectly) pushed down on the left side, no rows will be
// returned.
checkExistsJoin(
s, "select * from t where 1 < all (select 2 from t)",
allRows, false);
// Join condition is X <> 1, which will remove the only interesting
// row from the left side if (incorrectly) pushed down there.
checkExistsJoin(
s, "select * from t where x = all (select 1 from t)",
new String[][]{{"1"}}, false);
// Join condition is T1.X >= T2.X which cannot be pushed down on the
// left side. The predicate in the subquery (T1.X > 100) can be pushed
// down on the left side and filter out rows that should not be
// filtered out, so check that this query is not flattened.
checkExistsJoin(
s, "select * from t t1 where x < all " +
"(select x from t t2 where t1.x > 100)",
allRows, false);
// Same as above, but with an extra, unproblematic predicate added
// to the subquery.
checkExistsJoin(
s, "select * from t t1 where x < all " +
"(select x from t t2 where t1.x > 100 and t2.x > 100)",
allRows, false);
// Same as above, but since the problematic predicate is ORed with
// an unproblematic one, it is not possible to push it down on the
// left side (only ANDed predicates can be split and pushed down
// separately), so in this case we expect the query to be flattened.
// (This query worked correctly also before DERBY-4001 was fixed.)
checkExistsJoin(
s, "select * from t t1 where x < all " +
"(select x from t t2 where t1.x > 100 or t2.x > 100)",
allRows, true);
}
/**
* DERBY-4001: Test that some NOT IN subqueries that used to be flattened
* to a not exists join and return incorrect results, are not flattened.
* These queries should not be flattened because the generated NOT EXISTS
* JOIN condition or some of the subquery's predicates could be pushed
* down into the left side of the join, which is not allowed in a not
* exists join because the predicates have a completely different effect
* if they're used on one side of the join than if they're used on the
* other side of the join.
*/
public void testNotInNotFlattenableToNotExists() throws SQLException {
Statement s = createStatement();
// X must be NOT NULL, otherwise rewriting NOT IN to NOT EXISTS won't
// even be attempted
s.execute("create table t (x int not null)");
s.execute("insert into t values 1,2,3");
enableRuntimeStatistics(s);
String[][] allRows = {{"1"}, {"2"}, {"3"}};
// Join condition is X = 100, which should make the right side of
// the not exists join empty and return all rows from the left side.
// If (incorrectly) pushed down on the left side, no rows will be
// returned.
checkExistsJoin(
s, "select * from t where x not in (select 100 from t)",
allRows, false);
// Join condition is 1 = 100, which should make the right side of
// the not exists join empty and return all rows from the left side.
// If (incorrectly) pushed down on the left side, no rows will be
// returned.
checkExistsJoin(
s, "select * from t where 1 not in (select 100 from t)",
allRows, false);
// Join condition is X = 2, which will remove the interesting rows
// from the left side if (incorrectly) pushed down there.
checkExistsJoin(
s, "select * from t where x not in (select 2 from t)",
new String[][]{{"1"}, {"3"}}, false);
// Join condition is T1.X = T2.X which cannot be pushed down on the
// left side. The predicate in the subquery (T1.X > 100) can be pushed
// down on the left side and filter out rows that should not be
// filtered out, so check that this query is not flattened.
checkExistsJoin(
s, "select * from t t1 where x not in " +
"(select x from t t2 where t1.x > 100)",
allRows, false);
// Same as above, but with an extra, unproblematic predicate added
// to the subquery.
checkExistsJoin(
s, "select * from t t1 where x not in " +
"(select x from t t2 where t1.x > 100 and t2.x > 100)",
allRows, false);
// Same as above, but since the problematic predicate is ORed with
// an unproblematic one, it is not possible to push it down on the
// left side (only ANDed predicates can be split and pushed down
// separately), so in this case we expect the query to be flattened.
// (This query worked correctly also before DERBY-4001 was fixed.)
checkExistsJoin(
s, "select * from t t1 where x not in " +
"(select x from t t2 where t1.x > 100 or t2.x > 100)",
allRows, true);
}
}