blob: ba4d8f8a4d93392dba3256619fafb764ca69ea5b [file] [log] [blame]
/**
* Derby - Class org.apache.derbyTesting.functionTests.tests.lang.CaseExpressionTest
*
* 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.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import junit.framework.Test;
import org.apache.derbyTesting.functionTests.util.streams.LoopingAlphabetReader;
import org.apache.derbyTesting.functionTests.util.streams.LoopingAlphabetStream;
import org.apache.derbyTesting.junit.BaseJDBCTestCase;
import org.apache.derbyTesting.junit.BaseTestSuite;
import org.apache.derbyTesting.junit.CleanDatabaseTestSetup;
import org.apache.derbyTesting.junit.JDBC;
import org.apache.derbyTesting.junit.SQLUtilities;
import org.apache.derbyTesting.junit.TestConfiguration;
public class CaseExpressionTest extends BaseJDBCTestCase {
// Results if the Case Expression evaluates to a column reference :
//
// 1. SELECT CASE WHEN 1 = 1 THEN <column reference> ELSE NULL END
// 2. SELECT CASE WHEN 1 = 1 THEN
// (CASE WHEN 1 = 1 THEN <column reference> ELSE NULL END)
// ELSE NULL END
//
private static String[][] columnReferenceResults = {
/*SMALLINT*/ {null,"0","1","2"},
/*INTEGER*/ {null,"0","1","21"},
/*BIGINT*/ {null,"0","1","22"},
/*DECIMAL(10,5)*/ {null,"0.00000","1.00000","23.00000"},
/*REAL*/ {null,"0.0","1.0","24.0"},
/*DOUBLE*/ {null,"0.0","1.0","25.0"},
/*CHAR(60)*/ {
null,
"0 ",
"aa ",
"2.0 "},
/*VARCHAR(60)*/ {null,"0","aa","15:30:20"},
/*LONG VARCHAR*/ {null,"0","aa","2000-01-01 15:30:20"},
/*CHAR(60) FOR BIT DATA*/ {
null,
"10aa20202020202020202020202020202020202020202020202020202020" +
"202020202020202020202020202020202020202020202020202020202020",
null,
"10aaaa202020202020202020202020202020202020202020202020202020" +
"202020202020202020202020202020202020202020202020202020202020"},
/*VARCHAR(60) FOR BIT DATA*/ {null,"10aa",null,"10aaba"},
/*LONG VARCHAR FOR BIT DATA*/ {null,"10aa",null,"10aaca"},
/*CLOB(1k)*/ {null,"13","14",null},
/*DATE*/ {null,"2000-01-01","2000-01-01",null},
/*TIME*/ {null,"15:30:20","15:30:20","15:30:20"},
/*TIMESTAMP*/ {
null,
"2000-01-01 15:30:20.0",
"2000-01-01 15:30:20.0",
"2000-01-01 15:30:20.0"},
/*BLOB(1k)*/ {null,null,null,null},
};
// Results if the Case Expression evaluates to a NULL value :
//
// 3. SELECT CASE WHEN 1 = 1 THEN NULL ELSE <column reference> END
// 4. SELECT CASE WHEN 1 = 1 THEN
// (CASE WHEN 1 = 1 THEN NULL ELSE <column reference> END)
// ELSE NULL END
// 5. SELECT CASE WHEN 1 = 1 THEN NULL ELSE
// (CASE WHEN 1 = 1 THEN <column reference> ELSE NULL END) END
// 6. SELECT CASE WHEN 1 = 1 THEN NULL ELSE
// (CASE WHEN 1 = 1 THEN NULL ELSE <column reference> END) END
//
private static String[][] nullValueResults = {
/*SMALLINT*/ {null,null,null,null},
/*INTEGER*/ {null,null,null,null},
/*BIGINT*/ {null,null,null,null},
/*DECIMAL(10,5)*/ {null,null,null,null},
/*REAL*/ {null,null,null,null},
/*DOUBLE*/ {null,null,null,null},
/*CHAR(60)*/ {null,null,null,null},
/*VARCHAR(60)*/ {null,null,null,null},
/*LONG VARCHAR*/ {null,null,null,null},
/*CHAR(60) FOR BIT DATA*/ {null,null,null,null},
/*VARCHAR(60) FOR BIT DATA*/ {null,null,null,null},
/*LONG VARCHAR FOR BIT DATA*/ {null,null,null,null},
/*CLOB(1k)*/ {null,null,null,null},
/*DATE*/ {null,null,null,null},
/*TIME*/ {null,null,null,null},
/*TIMESTAMP*/ {null,null,null,null},
/*BLOB(1k)*/ {null,null,null,null},
};
public CaseExpressionTest(String name) {
super(name);
}
/**
* Test various statements that
*
*/
public void testWhenNonBoolean() {
// DERBY-2809: BOOLEAN datatype was forced upon
// unary expressions that were not BOOLEAN, such
// as SQRT(?)
String[] unaryOperators = {
"SQRT(?)", "SQRT(9)",
"UPPER(?)", "UPPER('haight')",
"LOWER(?)", "LOWER('HAIGHT')",
};
for (int i = 0; i < unaryOperators.length; i++)
{
assertCompileError("42X88",
"VALUES CASE WHEN " + unaryOperators[i] +
" THEN 3 ELSE 4 END");
}
}
public void testAllDatatypesCombinationsForCaseExpressions()
throws SQLException
{
Statement s = createStatement();
/* 1. Column Reference in the THEN node, and NULL in
* the ELSE node.
*/
testCaseExpressionQuery(s, columnReferenceResults,
"SELECT CASE WHEN 1 = 1 THEN ",
" ELSE NULL END from AllDataTypesTable");
/* 2. Test Column Reference nested in the THEN's node THEN node,
* NULL's elsewhere.
*/
testCaseExpressionQuery(s, columnReferenceResults,
"SELECT CASE WHEN 1 = 1 THEN (CASE WHEN 1 = 1 THEN ",
" ELSE NULL END) ELSE NULL END from AllDataTypesTable");
/* 3. NULL in the THEN node, and a Column Reference in
* the ELSE node.
*/
testCaseExpressionQuery(s, nullValueResults,
"SELECT CASE WHEN 1 = 1 THEN NULL ELSE ",
" END from AllDataTypesTable");
/* 4. Test Column Reference nested in the THEN's node ELSE node,
* NULL's elsewhere.
*/
testCaseExpressionQuery(s, nullValueResults,
"SELECT CASE WHEN 1 = 1 THEN (CASE WHEN 1 = 1 THEN NULL ELSE ",
" END) ELSE NULL END from AllDataTypesTable");
/* 5. Test Column Reference nested in the ELSE's node THEN node,
* NULL's elsewhere.
*/
testCaseExpressionQuery(s, nullValueResults,
"SELECT CASE WHEN 1 = 1 THEN NULL ELSE (CASE WHEN 1 = 1 THEN ",
" ELSE NULL END) END from AllDataTypesTable");
/* 6. Test Column Reference nested in the ELSE's node ELSE node,
* NULL's elsewhere.
*/
testCaseExpressionQuery(s, nullValueResults,
"SELECT CASE WHEN 1 = 1 THEN NULL " +
"ELSE (CASE WHEN 1 = 1 THEN NULL ELSE ",
" END) END from AllDataTypesTable");
s.close();
}
/**
* Test a query that has many WHEN conditions in it. This is mostly
* checking for the performance regression filed as DERBY-2986. That
* regression may not be noticeable in the scope of the full regression
* suite, but if this test is run standalone then this fixture could
* still be useful.
*/
public void testMultipleWhens() throws SQLException
{
Statement s = createStatement();
JDBC.assertFullResultSet(
s.executeQuery(
"values CASE WHEN 10 = 1 THEN 'a' " +
"WHEN 10 = 2 THEN 'b' " +
"WHEN 10 = 3 THEN 'c' " +
"WHEN 10 = 4 THEN 'd' " +
"WHEN 10 = 5 THEN 'e' " +
"WHEN 10 = 6 THEN 'f' " +
"WHEN 10 = 7 THEN 'g' " +
"WHEN 10 = 8 THEN 'h' " +
"WHEN 10 = 11 THEN 'i' " +
"WHEN 10 = 12 THEN 'j' " +
"WHEN 10 = 15 THEN 'k' " +
"WHEN 10 = 16 THEN 'l' " +
"WHEN 10 = 23 THEN 'm' " +
"WHEN 10 = 24 THEN 'n' " +
"WHEN 10 = 27 THEN 'o' " +
"WHEN 10 = 31 THEN 'p' " +
"WHEN 10 = 41 THEN 'q' " +
"WHEN 10 = 42 THEN 'r' " +
"WHEN 10 = 50 THEN 's' " +
"ELSE '*' END"),
new String[][] {{"*"}});
s.close();
}
/**
* Before DERBY-6423, boolean expressions (such as A OR B, or A AND B)
* were not accepted in THEN and ELSE clauses.
*/
public void testBooleanExpressions() throws SQLException {
Statement s = createStatement();
// Test both with and without parentheses around the expressions.
// Those with parentheses used to work, and those without used to
// cause syntax errors. Now both should work.
JDBC.assertFullResultSet(
s.executeQuery(
"select case when a or b then b or c else a or c end,\n" +
" case when a and b then b and c else a and c end,\n" +
" case when (a or b) then (b or c) else (a or c) end,\n" +
" case when (a and b) then (b and c) else (a and c) end\n" +
"from (values (true, true, true), (true, true, false),\n" +
" (true, false, true), (true, false, false),\n" +
" (false, true, true), (false, true, false),\n" +
" (false, false, true), (false, false, false)\n" +
" ) v(a, b, c)\n" +
"order by a desc, b desc, c desc"),
new String[][] {
{ "true", "true", "true", "true" },
{ "true", "false", "true", "false" },
{ "true", "true", "true", "true" },
{ "false", "false", "false", "false" },
{ "true", "false", "true", "false" },
{ "true", "false", "true", "false" },
{ "true", "false", "true", "false" },
{ "false", "false", "false", "false" },
});
}
/**
* Runs the test fixtures in embedded.
*
* @return test suite
*/
public static Test suite()
{
BaseTestSuite suite = (BaseTestSuite)
TestConfiguration.embeddedSuite(CaseExpressionTest.class);
return new CleanDatabaseTestSetup(suite) {
/**
* Creates the table used in the test cases.
*/
protected void decorateSQL(Statement s) throws SQLException {
SQLUtilities.createAndPopulateAllDataTypesTable(s);
}
};
}
/**
* Execute the received caseExpression on the received Statement
* and check the results against the receieved expected array.
*/
private void testCaseExpressionQuery(Statement st,
String [][] expRS, String caseExprBegin, String caseExprEnd)
throws SQLException
{
ResultSet rs;
int colType;
int row;
for (colType = 0;
colType < SQLUtilities.SQLTypes.length;
colType++)
{
rs = st.executeQuery(
caseExprBegin +
SQLUtilities.allDataTypesColumnNames[colType] +
caseExprEnd);
row = 0;
while (rs.next()) {
String val = rs.getString(1);
assertEquals(expRS[colType][row], val);
row++;
}
rs.close();
}
}
/**
* Test fix for DERBY-3032. Fix ClassCastException if SQL NULL is returned from conditional.
*
* @throws SQLException
*/
public void testDerby3032() throws SQLException
{
Statement s = createStatement();
s.executeUpdate("create table t (d date, vc varchar(30))");
s.executeUpdate("insert into t values(CURRENT_DATE, 'hello')");
ResultSet rs = s.executeQuery("SELECT d from t where d = (SELECT CASE WHEN 1 = 1 THEN CURRENT_DATE ELSE NULL END from t)");
JDBC.assertDrainResults(rs,1);
// Make sure null gets cast properly to date type to avoid cast exception. DERBY-3032
rs = s.executeQuery("SELECT d from t where d = (SELECT CASE WHEN 1 = 1 THEN NULL ELSE CURRENT_DATE END from t)");
JDBC.assertEmpty(rs);
rs = s.executeQuery("SELECT d from t where d = (SELECT CASE WHEN 1 = 0 THEN CURRENT_DATE ELSE NULL END from t)");
JDBC.assertEmpty(rs);
// Make sure metadata has correct type for various null handling
rs = s.executeQuery("SELECT CASE WHEN 1 = 1 THEN NULL ELSE CURRENT_DATE END from t");
ResultSetMetaData rsmd = rs.getMetaData();
assertEquals(java.sql.Types.DATE, rsmd.getColumnType(1));
// should be nullable since it returns NULL #:)
assertEquals(ResultSetMetaData.columnNullable, rsmd.isNullable(1));
JDBC.assertSingleValueResultSet(rs, null);
rs = s.executeQuery("SELECT CASE WHEN 1 = 0 THEN CURRENT_DATE ELSE NULL END from t");
rsmd = rs.getMetaData();
assertEquals(java.sql.Types.DATE, rsmd.getColumnType(1));
// should be nullable since it returns NULL #:)
assertEquals(ResultSetMetaData.columnNullable, rsmd.isNullable(1));
JDBC.assertSingleValueResultSet(rs, null);
// and with an implicit NULL return.
rs = s.executeQuery("SELECT CASE WHEN 1 = 0 THEN CURRENT_DATE END from t");
rsmd = rs.getMetaData();
assertEquals(java.sql.Types.DATE, rsmd.getColumnType(1));
// should be nullable since it returns NULL #:)
assertEquals(ResultSetMetaData.columnNullable, rsmd.isNullable(1));
JDBC.assertSingleValueResultSet(rs, null);
// and no possible NULL return.
rs = s.executeQuery("SELECT CASE WHEN 1 = 0 THEN 6 ELSE 4 END from t");
rsmd = rs.getMetaData();
assertEquals(java.sql.Types.INTEGER, rsmd.getColumnType(1));
// should be nullable since it returns NULL #:)
assertEquals(ResultSetMetaData.columnNoNulls, rsmd.isNullable(1));
JDBC.assertSingleValueResultSet(rs, "4");
rs = s.executeQuery("SELECT CASE WHEN 1 = 1 THEN 6 ELSE 4 END from t");
rsmd = rs.getMetaData();
assertEquals(java.sql.Types.INTEGER, rsmd.getColumnType(1));
// should be nullable since it returns NULL #:)
assertEquals(ResultSetMetaData.columnNoNulls, rsmd.isNullable(1));
JDBC.assertSingleValueResultSet(rs, "6");
}
/**
* Verify that NOT elimination produces the correct results.
* DERBY-6563.
*/
public void testNotElimination() throws SQLException {
setAutoCommit(false);
Statement s = createStatement();
s.execute("create table d6563(b1 boolean, b2 boolean, b3 boolean)");
// Fill the table with all possible combinations of true/false/null.
Boolean[] universe = { true, false, null };
PreparedStatement insert = prepareStatement(
"insert into d6563 values (?, ?, ?)");
for (Boolean v1 : universe) {
insert.setObject(1, v1);
for (Boolean v2 : universe) {
insert.setObject(2, v2);
for (Boolean v3 : universe) {
insert.setObject(3, v3);
insert.executeUpdate();
}
}
}
// Truth table for
// B1, B2, B3, WHEN B1 THEN B2 ELSE B3, NOT (WHEN B1 THEN B2 ELSE B3).
Object[][] expectedRows = {
{ false, false, false, false, true },
{ false, false, true, true, false },
{ false, false, null, null, null },
{ false, true, false, false, true },
{ false, true, true, true, false },
{ false, true, null, null, null },
{ false, null, false, false, true },
{ false, null, true, true, false },
{ false, null, null, null, null },
{ true, false, false, false, true },
{ true, false, true, false, true },
{ true, false, null, false, true },
{ true, true, false, true, false },
{ true, true, true, true, false },
{ true, true, null, true, false },
{ true, null, false, null, null },
{ true, null, true, null, null },
{ true, null, null, null, null },
{ null, false, false, false, true },
{ null, false, true, true, false },
{ null, false, null, null, null },
{ null, true, false, false, true },
{ null, true, true, true, false },
{ null, true, null, null, null },
{ null, null, false, false, true },
{ null, null, true, true, false },
{ null, null, null, null, null },
};
// Verify the truth table. Since NOT elimination is not performed on
// expressions in the SELECT list, this passed even before the fix.
JDBC.assertFullResultSet(
s.executeQuery(
"select b1, b2, b3, case when b1 then b2 else b3 end, "
+ "not case when b1 then b2 else b3 end "
+ "from d6563 order by b1, b2, b3"),
expectedRows, false);
// Now take only those rows where the NOT CASE expression evaluated
// to TRUE, and strip off the expression columns at the end.
ArrayList<Object[]> rows = new ArrayList<Object[]>();
for (Object[] row : expectedRows) {
if (row[4] == Boolean.TRUE) {
rows.add(Arrays.copyOf(row, 3));
}
}
// Assert that those are the only rows returned if the NOT CASE
// expression is used as a predicate. This query used to return a
// different set of rows before the fix.
expectedRows = rows.toArray(new Object[rows.size()][]);
JDBC.assertFullResultSet(
s.executeQuery("select * from d6563 where "
+ "not case when b1 then b2 else b3 end "
+ "order by b1, b2, b3"),
expectedRows, false);
}
/**
* Test that parameters can be used in CASE expressions.
*/
public void testParameters() throws SQLException {
// If all of the result expressions are untyped parameters, the
// type cannot be determined, and an error should be raised.
assertCompileError("42X87", "values case when true then ? else ? end");
// If at least one result expression is typed, the parameter should
// get its type from it.
PreparedStatement ps = prepareStatement(
"values case when true then ? else 1 end");
// DERBY-6567: The result should be nullable, since the parameter
// could be set to null. It used to be reported as not nullable.
assertEquals(ResultSetMetaData.columnNullable,
ps.getMetaData().isNullable(1));
ps.setNull(1, Types.INTEGER);
JDBC.assertSingleValueResultSet(ps.executeQuery(), null);
ps.setInt(1, 1);
JDBC.assertSingleValueResultSet(ps.executeQuery(), "1");
ps = prepareStatement(
"values case when true then ? else cast(? as integer) end");
ParameterMetaData params = ps.getParameterMetaData();
assertEquals(Types.INTEGER, params.getParameterType(1));
assertEquals(Types.INTEGER, params.getParameterType(2));
ps.setInt(1, 1);
ps.setInt(2, 2);
JDBC.assertSingleValueResultSet(ps.executeQuery(), "1");
// Parameters in the WHEN clause can be untyped. They will
// implicitly get the BOOLEAN type.
ps = prepareStatement("values case when ? then 1 else 0 end");
assertEquals(Types.BOOLEAN,
ps.getParameterMetaData().getParameterType(1));
ps.setBoolean(1, true);
JDBC.assertSingleValueResultSet(ps.executeQuery(), "1");
ps.setBoolean(1, false);
JDBC.assertSingleValueResultSet(ps.executeQuery(), "0");
ps.setNull(1, Types.BOOLEAN);
JDBC.assertSingleValueResultSet(ps.executeQuery(), "0");
}
/**
* Test how untyped NULLs are handled.
*/
public void testUntypedNulls() throws SQLException {
Statement s = createStatement();
// Before DERBY-2002, Derby accepted a CASE expression to have an
// untyped NULL in all the result branches. Verify that an error
// is raised.
String[] allUntyped = {
// The SQL standard says at least one result should not be an
// untyped NULL, so expect these to fail.
"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",
// We're not able to tell the type if we have a mix of untyped
// NULLs and untyped parameters.
"values case when true then ? end", // implicit ELSE NULL
"values case when true then null else ? end",
"values case when true then ? when false then ? else null end",
// These ones failed even before DERBY-2002.
"values case when true then ? else ? end",
"values case when true then ? when false then ? else ? end",
};
for (String sql : allUntyped) {
assertCompileError("42X87", sql);
}
// 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");
}
/** Regression test case for DERBY-6577. */
public void testQuantifiedComparison() throws SQLException {
// This query used to return wrong results.
JDBC.assertUnorderedResultSet(createStatement().executeQuery(
"select c, case when c = all (values 'Y') then true end "
+ "from (values 'Y', 'N') v(c)"),
new String[][] { { "N", null }, { "Y", "true" }});
}
/**
* Tests for the simple case syntax added in DERBY-1576.
*/
public void testSimpleCaseSyntax() throws SQLException {
Statement s = createStatement();
// Simplest of the simple cases. SQL:1999 syntax, which allows a
// single operand per WHEN clause, and the operand is a value
// expression.
JDBC.assertUnorderedResultSet(s.executeQuery(
"select i, case i when 0 then 'zero' "
+ "when 1 then 'one' when 1+1 then 'two' "
+ "else 'many' end from "
+ "(values 0, 1, 2, 3, cast(null as int)) v(i)"),
new String[][] {
{"0", "zero"},
{"1", "one"},
{"2", "two"},
{"3", "many"},
{null, "many"}
});
// SQL:2003 added feature F262 Extended CASE Expression, which
// allows more complex WHEN operands. Essentially, it allows any
// last part of a predicate (everything after the left operand).
JDBC.assertFullResultSet(s.executeQuery(
"select i, case i when < 0 then 'negative' "
+ "when < 10 then 'small' "
+ "when between 10 and 20 then 'medium' "
+ "when in (19, 23, 29, 37, 41) then 'prime' "
+ "when = some (values 7, 42) then 'lucky number' "
+ "when >= 40 then 'big' end "
+ "from (values -1, 0, 1, 2, 3, 8, 9, 10, 17, 19, "
+ "29, 37, 38, 39, 40, 41, 42, 50) v(i) order by i"),
new String[][] {
{ "-1", "negative" },
{ "0", "small" },
{ "1", "small" },
{ "2", "small" },
{ "3", "small" },
{ "8", "small" },
{ "9", "small" },
{ "10", "medium" },
{ "17", "medium" },
{ "19", "medium" },
{ "29", "prime" },
{ "37", "prime" },
{ "38", null },
{ "39", null },
{ "40", "big" },
{ "41", "prime" },
{ "42", "lucky number" },
{ "50", "big" },
});
JDBC.assertUnorderedResultSet(s.executeQuery(
"select c, case c "
+ "when like 'abc%' then 0 "
+ "when like 'x%%' escape 'x' then 1 "
+ "when = all (select ibmreqd from sysibm.sysdummy1) then 2 "
+ "when 'xyz' || 'zyx' then 3 "
+ "when is null then 4 "
+ "when is not null then 5 end "
+ "from (values 'abcdef', 'xyzzyx', '%s', 'hello', "
+ "cast(null as char(1)), 'Y', 'N') v(c)"),
new String[][] {
{ "abcdef", "0" },
{ "xyzzyx", "3" },
{ "%s", "1" },
{ "hello", "5" },
{ null, "4" },
{ "Y", "2" },
{ "N", "5" },
});
// SQL:2011 added feature F263 Comma-separated predicates in simple
// CASE expression, which allows multiple operands per WHEN clause.
JDBC.assertFullResultSet(s.executeQuery(
"select i, case i "
+ "when between 2 and 3, 5, =7 then 'prime' "
+ "when <1, >7 then 'out of range' "
+ "when is not null then 'small' end "
+ "from (values 0, 1, 2, 3, 4, 5, 6, 7, 8, cast(null as int)) "
+ "as v(i) order by i"),
new String[][] {
{ "0", "out of range" },
{ "1", "small" },
{ "2", "prime" },
{ "3", "prime" },
{ "4", "small" },
{ "5", "prime" },
{ "6", "small" },
{ "7", "prime" },
{ "8", "out of range" },
{ null, null },
});
JDBC.assertUnorderedResultSet(s.executeQuery(
"select c, case c "
+ "when in ('ab', 'cd'), like '_' then 'matched' "
+ "else 'not matched' end "
+ "from (values cast('a' as varchar(1)), 'b', 'c', 'ab', "
+ "'cd', 'ac', 'abc') v(c)"),
new String[][] {
{ "a", "matched" },
{ "b", "matched" },
{ "c", "matched" },
{ "ab", "matched" },
{ "cd", "matched" },
{ "ac", "not matched" },
{ "abc", "not matched" },
});
// Untyped null is not allowed as CASE operand. Use typed null instead.
assertCompileError("42X01", "values case null when 1 then 'one' end");
JDBC.assertSingleValueResultSet(s.executeQuery(
"values case cast(null as int) when 1 then 'one' end"),
null);
// Untyped null is not allowed as WHEN operand. Use IS NULL instead.
assertCompileError("42X01", "values case 1 when null then 'null' end");
JDBC.assertUnorderedResultSet(s.executeQuery(
"select i, case i when is null then 1 when is not null "
+ "then 2 else 3 end from (values 1, cast(null as int)) v(i)"),
new String[][] { { "1", "2" }, { null, "1" } });
// Non-deterministic functions are not allowed in the case operand.
assertCompileError("42Y98",
"values case sysfun.random() when 1 then true else false end");
assertCompileError("42Y98",
"values case (values sysfun.random()) "
+ "when 1 then true else false end");
// Deterministic functions, on the other hand, are allowed.
JDBC.assertFullResultSet(s.executeQuery(
"select case sysfun.sin(angle) when < 0 then 'negative' "
+ "when > 0 then 'positive' end "
+ "from (values -pi()/2, 0, pi()/2) v(angle) "
+ "order by angle"),
new String[][] { {"negative"}, {null}, {"positive"} });
// Non-deterministic functions can be used outside of the case operand.
JDBC.assertDrainResults(
s.executeQuery(
"values case 1 when sysfun.random() then sysfun.random() end"),
1);
// Scalar subqueries are allowed in the case operand.
JDBC.assertSingleValueResultSet(
s.executeQuery("values case (values 1) when 1 then true end"),
"true");
// Non-scalar subqueries are not allowed.
assertCompileError(
"42X39", "values case (values (1, 2)) when 1 then true end");
assertStatementError(
"21000", s, "values case (values 1, 2) when 1 then true end");
// The type of the CASE operand must be compatible with the types
// of all the WHEN operands.
assertCompileError("42818", "values case 1 when true then 'yes' end");
assertCompileError("42818",
"values case 1 when 1 then 'yes' when 2 then 'no' "
+ "when 'three' then 'maybe' end");
JDBC.assertSingleValueResultSet(s.executeQuery(
"values case cast(1 as bigint)"
+ " when cast(1 as smallint) then 'yes' end"),
"yes");
// A sequence cannot be accessed anywhere in a CASE expression.
s.execute("create sequence d1576_s start with 1");
assertCompileError(
"42XAH",
"values case next value for d1576_s when 1 then 1 else 0 end");
assertCompileError(
"42XAH",
"values case 1 when next value for d1576_s then 1 else 0 end");
assertCompileError(
"42XAH",
"values case 1 when 1 then next value for d1576_s else 0 end");
// Instead, access the sequence value in a nested query.
JDBC.assertSingleValueResultSet(
s.executeQuery(
"select case x when 1 then 1 else 0 end from "
+ "(values next value for d1576_s) v(x)"),
"1");
s.execute("drop sequence d1576_s restrict");
// Window functions are allowed.
JDBC.assertFullResultSet(
s.executeQuery(
"select case row_number() over () when 1 then 'one' "
+ "when 2 then 'two' end from (values 1, 1, 1) v(x)"),
new String[][] { {"one"}, {"two"}, {null} });
// Test that you can have a typed parameter in the case operand.
PreparedStatement ps = prepareStatement(
"values case cast(? as integer) "
+ "when 1 then 'one' when 2 then 'two' end");
ps.setInt(1, 1);
JDBC.assertSingleValueResultSet(ps.executeQuery(), "one");
ps.setInt(1, 2);
JDBC.assertSingleValueResultSet(ps.executeQuery(), "two");
ps.setInt(1, 3);
JDBC.assertSingleValueResultSet(ps.executeQuery(), null);
// This one fails to compile because an integer cannot be checked
// with LIKE.
assertCompileError("42884",
"values case cast(? as integer) "
+ "when 1 then 1 when like 'abc' then 2 end");
// Untyped parameter in the case operand. Should be able to infer
// the type from the WHEN clauses.
ps = prepareStatement("values case ? when 1 then 2 when 3 then 4 end");
ParameterMetaData pmd = ps.getParameterMetaData();
assertEquals(Types.INTEGER, pmd.getParameterType(1));
assertEquals(ParameterMetaData.parameterNullable, pmd.isNullable(1));
ps.setInt(1, 1);
JDBC.assertSingleValueResultSet(ps.executeQuery(), "2");
ps.setInt(1, 2);
JDBC.assertSingleValueResultSet(ps.executeQuery(), null);
ps.setInt(1, 3);
JDBC.assertSingleValueResultSet(ps.executeQuery(), "4");
ps = prepareStatement(
"values case ? when cast(1.1 as double) then true "
+ "when cast(1.2 as double) then false end");
pmd = ps.getParameterMetaData();
assertEquals(Types.DOUBLE, pmd.getParameterType(1));
assertEquals(ParameterMetaData.parameterNullable, pmd.isNullable(1));
ps.setDouble(1, 1.1);
JDBC.assertSingleValueResultSet(ps.executeQuery(), "true");
ps.setDouble(1, 1.2);
JDBC.assertSingleValueResultSet(ps.executeQuery(), "false");
ps.setDouble(1, 1.3);
JDBC.assertSingleValueResultSet(ps.executeQuery(), null);
// Mixed types are accepted, as long as they are compatible.
ps = prepareStatement(
"values case ? when 1 then 'one' when 2.1 then 'two' end");
pmd = ps.getParameterMetaData();
assertEquals(Types.DECIMAL, pmd.getParameterType(1));
assertEquals(ParameterMetaData.parameterNullable, pmd.isNullable(1));
ps.setInt(1, 1);
JDBC.assertSingleValueResultSet(ps.executeQuery(), "one");
ps.setInt(1, 2);
JDBC.assertSingleValueResultSet(ps.executeQuery(), null);
ps.setDouble(1, 1.1);
JDBC.assertSingleValueResultSet(ps.executeQuery(), null);
ps.setDouble(1, 2.1);
JDBC.assertSingleValueResultSet(ps.executeQuery(), "two");
ps = prepareStatement(
"values case ? when 1 then 'one' when 2.1 then 'two'"
+ " when cast(3 as bigint) then 'three' end");
assertEquals(Types.DECIMAL, pmd.getParameterType(1));
assertEquals(ParameterMetaData.parameterNullable, pmd.isNullable(1));
ps.setInt(1, 1);
JDBC.assertSingleValueResultSet(ps.executeQuery(), "one");
ps.setInt(1, 2);
JDBC.assertSingleValueResultSet(ps.executeQuery(), null);
ps.setInt(1, 3);
JDBC.assertSingleValueResultSet(ps.executeQuery(), "three");
ps.setDouble(1, 1.1);
JDBC.assertSingleValueResultSet(ps.executeQuery(), null);
ps.setDouble(1, 2.1);
JDBC.assertSingleValueResultSet(ps.executeQuery(), "two");
ps.setDouble(1, 3.1);
JDBC.assertSingleValueResultSet(ps.executeQuery(), null);
ps = prepareStatement(
"values case ? when 'abcdef' then 1 "
+ "when cast('abcd' as varchar(4)) then 2 end");
pmd = ps.getParameterMetaData();
assertEquals(Types.VARCHAR, pmd.getParameterType(1));
assertEquals(6, pmd.getPrecision(1));
assertEquals(ParameterMetaData.parameterNullable, pmd.isNullable(1));
ps.setString(1, "abcdef");
JDBC.assertSingleValueResultSet(ps.executeQuery(), "1");
ps.setString(1, "abcd");
JDBC.assertSingleValueResultSet(ps.executeQuery(), "2");
ps.setString(1, "ab");
JDBC.assertSingleValueResultSet(ps.executeQuery(), null);
ps.setString(1, "abcdefghi");
JDBC.assertSingleValueResultSet(ps.executeQuery(), null);
// The types in the WHEN clauses are incompatible, so the type of
// the case operand cannot be inferred.
assertCompileError("42818",
"values case ? when 1 then true when like 'abc' then false end");
assertCompileError("42818",
"values case ? when like 'abc' then true when 1 then false end");
// BLOB and CLOB are not comparable with anything.
assertCompileError("42818",
"values case ? when cast(x'abcd' as blob) then true end");
assertCompileError("42818",
"values case ? when cast('abcd' as clob) then true end");
// Cannot infer type if both sides of the comparison are untyped.
assertCompileError("42X35", "values case ? when ? then true end");
assertCompileError("42X35", "values case ? when ? then true "
+ "when 1 then false end");
// Should be able to infer type when the untyped parameter is prefixed
// with plus or minus.
ps = prepareStatement(
"values (case +? when 1 then 1 when 2.1 then 2 end, "
+ "case -? when 1 then 1 when 2.1 then 2 end)");
pmd = ps.getParameterMetaData();
assertEquals(Types.DECIMAL, pmd.getParameterType(1));
assertEquals(ParameterMetaData.parameterNullable, pmd.isNullable(1));
assertEquals(Types.DECIMAL, pmd.getParameterType(2));
assertEquals(ParameterMetaData.parameterNullable, pmd.isNullable(2));
ps.setInt(1, 1);
ps.setInt(2, -1);
JDBC.assertFullResultSet(ps.executeQuery(),
new String[][] {{ "1", "1" }});
ps.setInt(1, 2);
ps.setInt(2, -2);
JDBC.assertFullResultSet(ps.executeQuery(),
new String[][] {{ null, null }});
ps.setDouble(1, 1.1);
ps.setDouble(2, -1.1);
JDBC.assertFullResultSet(ps.executeQuery(),
new String[][] {{ null, null }});
ps.setDouble(1, 2.1);
ps.setDouble(2, -2.1);
JDBC.assertFullResultSet(ps.executeQuery(),
new String[][] {{ "2", "2" }});
// If the untyped parameter is part of an arithmetic expression, its
// type is inferred from that expression and not from the WHEN clause.
ps = prepareStatement(
"values case 2*? when 2 then true when 3.0 then false end");
pmd = ps.getParameterMetaData();
assertEquals(Types.INTEGER, pmd.getParameterType(1));
assertEquals(ParameterMetaData.parameterNullable, pmd.isNullable(1));
ps.setInt(1, 1);
JDBC.assertSingleValueResultSet(ps.executeQuery(), "true");
ps.setDouble(1, 1.5);
JDBC.assertSingleValueResultSet(ps.executeQuery(), "true");
ps.setInt(1, 2);
JDBC.assertSingleValueResultSet(ps.executeQuery(), null);
ps = prepareStatement(
"values case 2.0*? when 2 then true when 3.0 then false end");
pmd = ps.getParameterMetaData();
assertEquals(Types.DECIMAL, pmd.getParameterType(1));
assertEquals(ParameterMetaData.parameterNullable, pmd.isNullable(1));
ps.setInt(1, 1);
JDBC.assertSingleValueResultSet(ps.executeQuery(), "true");
ps.setDouble(1, 1.5);
JDBC.assertSingleValueResultSet(ps.executeQuery(), "false");
ps.setInt(1, 2);
JDBC.assertSingleValueResultSet(ps.executeQuery(), null);
// The EXISTS predicate can only be used in the WHEN operand if
// the CASE operand is a BOOLEAN.
JDBC.assertSingleValueResultSet(
s.executeQuery("values case true when exists" +
"(select * from sysibm.sysdummy1) then 1 end"),
"1");
assertCompileError("42818",
"values case 1 when exists" +
"(select * from sysibm.sysdummy1) then 1 end");
// Scalar subqueries are allowed in the operands.
JDBC.assertSingleValueResultSet(
s.executeQuery(
"values case (select ibmreqd from sysibm.sysdummy1) "
+ "when 'N' then 'no' when 'Y' then 'yes' end"),
"yes");
JDBC.assertSingleValueResultSet(
s.executeQuery("values case 'Y' when "
+ "(select ibmreqd from sysibm.sysdummy1) "
+ "then 'yes' end"),
"yes");
// Subquery returns two columns - fail.
assertCompileError(
"42X39",
"values case (select ibmreqd, 1 from sysibm.sysdummy1)"
+ " when 'Y' then true end");
assertCompileError(
"42X39",
"values case 'Y' when "
+ "(select ibmreqd, 1 from sysibm.sysdummy1) then true end");
// Subquery returns multiple rows - fail.
assertStatementError("21000", s,
"values case (select 1 from sys.systables) when 1 then true end");
assertStatementError("21000", s,
"values case 1 when (select 1 from sys.systables) then true end");
// Subquery returns zero rows, which is converted to NULL for scalar
// subqueries.
JDBC.assertSingleValueResultSet(s.executeQuery(
"values case (select ibmreqd from sysibm.sysdummy1 where false)"
+ " when is null then 'yes' end"),
"yes");
JDBC.assertSingleValueResultSet(s.executeQuery(
"values case true when true then "
+ "(select ibmreqd from sysibm.sysdummy1 where false) end"),
null);
// Simple case expressions should work in join conditions.
JDBC.assertSingleValueResultSet(
s.executeQuery("select x from (values 1, 2, 3) v1(x) "
+ "join (values 13, 14) v2(y) "
+ "on case y-x when 10 then true end"),
"3");
}
/**
* Verify that the case operand expression is evaluated only once per
* evaluation of the CASE expression.
*/
public void testSingleEvaluationOfCaseOperand() throws SQLException {
setAutoCommit(false);
Statement s = createStatement();
s.execute("create function count_me(x int) returns int "
+ "language java parameter style java external name '"
+ getClass().getName() + ".countMe' no sql deterministic");
callCount.set(0);
JDBC.assertUnorderedResultSet(
s.executeQuery(
"select case count_me(x) when 1 then 'one' when 2 then 'two' "
+ "when 3 then 'three' end from (values 1, 2, 3) v(x)"),
new String[][] { {"one"}, {"two"}, {"three"} });
// The CASE expression is evaluated once per row. There are three
// rows. Expect that the COUNT_ME function was only invoked once
// per row.
assertEquals(3, callCount.get());
}
/** Count how many times countMe() has been called. */
private static final AtomicInteger callCount = new AtomicInteger();
/**
* Stored function that keeps track of how many times it has been called.
* @param i an integer
* @return the integer {@code i}
*/
public static int countMe(int i) {
callCount.incrementAndGet();
return i;
}
/**
* Test that large objects can be used as case operands.
*/
public void testLobAsCaseOperand() throws SQLException {
Statement s = createStatement();
// BLOB and CLOB are allowed in the case operand.
JDBC.assertSingleValueResultSet(s.executeQuery(
"values case cast(null as blob) when is null then 'yes' end"),
"yes");
JDBC.assertSingleValueResultSet(s.executeQuery(
"values case cast(null as clob) when is null then 'yes' end"),
"yes");
// Comparisons between BLOB and BLOB, or between CLOB and CLOB, are
// not allowed, so expect a compile-time error for these queries.
assertCompileError("42818",
"values case cast(null as blob) "
+ "when cast(null as blob) then true end");
assertCompileError("42818",
"values case cast(null as clob) "
+ "when cast(null as clob) then true end");
// Now create a table with some actual LOBs in them.
s.execute("create table lobs_for_simple_case("
+ "id int generated always as identity, b blob, c clob)");
PreparedStatement insert = prepareStatement(
"insert into lobs_for_simple_case(b, c) values (?, ?)");
// A small one.
insert.setBytes(1, new byte[] {1, 2, 3});
insert.setString(2, "small");
insert.executeUpdate();
// And a big one (larger than 32K means it will be streamed
// from store, instead of being returned as a materialized value).
insert.setBinaryStream(1, new LoopingAlphabetStream(40000));
insert.setCharacterStream(2, new LoopingAlphabetReader(40000));
insert.executeUpdate();
// And a NULL.
insert.setNull(1, Types.BLOB);
insert.setNull(2, Types.CLOB);
insert.executeUpdate();
// IS [NOT] NULL can be used on both BLOB and CLOB. LIKE can be
// used on CLOB. Those are the only predicates supported on BLOB
// and CLOB in simple case expressions currently. Test that they
// all work.
JDBC.assertUnorderedResultSet(
s.executeQuery(
"select id, case b when is null then 'yes'"
+ " when is not null then 'no' end, "
+ "case c when is null then 'yes' when like 'abc' then 'abc'"
+ " when like 'abc%' then 'abc...' when is not null then 'no'"
+ " end "
+ "from lobs_for_simple_case"),
new String[][] {
{ "1", "no", "no" },
{ "2", "no", "abc..." },
{ "3", "yes", "yes" },
});
s.execute("drop table lobs_for_simple_case");
}
}