| /* |
| |
| Derby - Class org.apache.derbyTesting.functionTests.tests.lang.OffsetFetchNextTest |
| |
| 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 junit.framework.Test; |
| 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.TestConfiguration; |
| |
| /** |
| * Test {@code <result offset clause>} and {@code <fetch first clause>}. |
| */ |
| public class OffsetFetchNextTest extends BaseJDBCTestCase { |
| |
| private final static String LANG_FORMAT_EXCEPTION = "22018"; |
| private final static String LANG_INTEGER_LITERAL_EXPECTED = "42X20"; |
| private final static String LANG_INVALID_ROW_COUNT_FIRST = "2201W"; |
| private final static String LANG_INVALID_ROW_COUNT_OFFSET = "2201X"; |
| private final static String LANG_MISSING_PARMS = "07000"; |
| private final static String LANG_SYNTAX_ERROR = "42X01"; |
| private final static String LANG_ROW_COUNT_OFFSET_FIRST_IS_NULL = "2201Z"; |
| |
| private final static String PERCENT_TOKEN = "%"; |
| |
| // flavors of SQL Standard syntax |
| private final static String FIRST_ROWS_ONLY = "fetch first % rows only"; |
| private final static String FIRST_ROW_ONLY = "fetch first % row only"; |
| private final static String NEXT_ROWS_ONLY = "fetch next % rows only"; |
| |
| // variants |
| private final static int SQL_STANDARD_VARIANT = 0; |
| private final static int JDBC_VARIANT = SQL_STANDARD_VARIANT + 1; |
| private final static int VARIANT_COUNT = JDBC_VARIANT + 1; |
| |
| public OffsetFetchNextTest(String name) { |
| super(name); |
| } |
| |
| public static Test suite() { |
| BaseTestSuite suite = new BaseTestSuite("OffsetFetchNextTest"); |
| |
| suite.addTest( |
| baseSuite("OffsetFetchNextTest:embedded")); |
| suite.addTest( |
| TestConfiguration.clientServerDecorator( |
| baseSuite("OffsetFetchNextTest:client"))); |
| |
| return suite; |
| } |
| |
| public static Test baseSuite(String suiteName) { |
| return new CleanDatabaseTestSetup( |
| new BaseTestSuite(OffsetFetchNextTest.class, |
| suiteName)) { |
| @Override |
| protected void decorateSQL(Statement s) |
| throws SQLException { |
| createSchemaObjects(s); |
| } |
| }; |
| } |
| |
| |
| /** |
| * Creates tables used by the tests (never modified, we use rollback after |
| * changes). |
| */ |
| private static void createSchemaObjects(Statement st) throws SQLException |
| { |
| // T1 (no indexes) |
| st.executeUpdate("create table t1 (a int, b bigint)"); |
| st.executeUpdate("insert into t1 (a, b) " + |
| "values (1,1), (1,2), (1,3), (1,4), (1,5)"); |
| |
| // T2 (primary key) |
| st.executeUpdate("create table t2 (a int primary key, b bigint)"); |
| st.executeUpdate("insert into t2 (a, b) " + |
| "values (1,1), (2,1), (3,1), (4,1), (5,1)"); |
| |
| // T3 (primary key + secondary key) |
| st.executeUpdate("create table t3 (a int primary key, " + |
| " b bigint unique)"); |
| st.executeUpdate("insert into t3 (a, b) " + |
| "values (1,1), (2,2), (3,3), (4,4), (5,5)"); |
| } |
| |
| /** |
| * Negative tests. Test various invalid OFFSET and FETCH NEXT clauses. |
| * |
| * @throws java.sql.SQLException |
| */ |
| public void testErrors() throws SQLException |
| { |
| Statement st = createStatement(); |
| |
| String stub = "select * from t1 %"; |
| |
| // Wrong range in row count argument |
| vetStatement( st, LANG_INVALID_ROW_COUNT_OFFSET, stub, FIRST_ROWS_ONLY, "-1", null, null ); |
| |
| vetStatement( st, LANG_SYNTAX_ERROR, stub, FIRST_ROWS_ONLY, "-?", null, null ); |
| |
| assertStatementError(LANG_INVALID_ROW_COUNT_FIRST, st, |
| "select * from t1 fetch first 0 rows only"); |
| |
| vetStatement( st, LANG_INVALID_ROW_COUNT_FIRST, stub, FIRST_ROWS_ONLY, null, "-1", null ); |
| |
| // Wrong type in row count argument |
| vetStatement( st, LANG_INTEGER_LITERAL_EXPECTED, stub, FIRST_ROWS_ONLY, null, "3.14", null ); |
| |
| // Wrong order of clauses |
| assertStatementError(LANG_SYNTAX_ERROR, st, |
| "select * from t1 " + |
| "fetch first 0 rows only offset 0 rows"); |
| assertStatementError(LANG_SYNTAX_ERROR, st, |
| "select * from t1 { offset 0 limit 0 }"); |
| } |
| |
| |
| /** |
| * Positive tests. Check that the new keyword OFFSET introduced is not |
| * reserved so we don't risk breaking existing applications. |
| * |
| * @throws java.sql.SQLException |
| */ |
| public void testNewKeywordNonReserved() throws SQLException |
| { |
| setAutoCommit(false); |
| prepareStatement("select a,b as offset from t1 offset 0 rows"); |
| prepareStatement("select a,b as limit from t1 offset 0 rows"); |
| |
| // Column and table correlation name usage |
| prepareStatement("select a,b from t1 as offset"); |
| prepareStatement("select a,b from t1 as limit"); |
| |
| prepareStatement("select a,b offset from t1 offset"); |
| prepareStatement("select a,b limit from t1 limit"); |
| |
| prepareStatement("select a,b offset from t1 offset +2 rows"); |
| |
| prepareStatement("select a offset,b from t1 offset ? rows"); |
| |
| prepareStatement("select offset.a, offset.b offset from t1 as offset offset ? rows"); |
| prepareStatement("select limit.a, limit.b offset from t1 as limit offset ? rows"); |
| |
| // DERBY-4562 |
| Statement s = createStatement(); |
| s.executeUpdate("create table t4562(i int, offset int)"); |
| ResultSet rs = s.executeQuery( |
| "select * from t4562 where i > 0 and offset + i < 0 offset 2 rows"); |
| rs.next(); |
| |
| rs = s.executeQuery( |
| "select * from t4562 where i > 0 and offset - i < 0 offset 2 rows"); |
| rs.next(); |
| |
| rs = s.executeQuery( |
| "select * from t4562 where i > 0 and offset * i < 0 offset 2 rows"); |
| rs.next(); |
| |
| rs.close(); |
| |
| rollback(); |
| } |
| |
| |
| /** |
| * Positive tests. |
| * |
| * @throws java.sql.SQLException |
| */ |
| public void testOffsetFetchFirstReadOnlyForwardOnlyRS() throws SQLException |
| { |
| Statement stm = createStatement(); |
| |
| /* |
| * offset 0 rows (a no-op) |
| */ |
| |
| vetStatement |
| ( |
| stm, null, "select a, b from t1%", FIRST_ROWS_ONLY, "0", null, |
| new String [][] { {"1","1"}, {"1","2"},{"1","3"}, {"1","4"},{"1","5"} } |
| ); |
| vetStatement |
| ( |
| stm, null, "select a,b from t2%", FIRST_ROWS_ONLY, "0", null, |
| new String [][] { {"1","1"}, {"2","1"},{"3","1"}, {"4","1"},{"5","1"} } |
| ); |
| vetStatement |
| ( |
| stm, null, "select a,b from t3%", FIRST_ROWS_ONLY, "0", null, |
| new String [][] { {"1","1"}, {"2","2"},{"3","3"}, {"4","4"},{"5","5"} } |
| ); |
| |
| /* |
| * offset 1 rows |
| */ |
| |
| vetStatement |
| ( |
| stm, null, "select a,b from t1%", FIRST_ROWS_ONLY, "1", null, |
| new String [][] { {"1","2"},{"1","3"}, {"1","4"},{"1","5"} } |
| ); |
| vetStatement |
| ( |
| stm, null, "select a,b from t2%", FIRST_ROWS_ONLY, "1", null, |
| new String [][] { {"2","1"},{"3","1"}, {"4","1"},{"5","1"} } |
| ); |
| vetStatement |
| ( |
| stm, null, "select a,b from t3%", FIRST_ROWS_ONLY, "1", null, |
| new String [][] { {"2","2"},{"3","3"}, {"4","4"},{"5","5"} } |
| ); |
| |
| /* |
| * offset 4 rows |
| */ |
| |
| vetStatement |
| ( |
| stm, null, "select a,b from t1%", FIRST_ROWS_ONLY, "4", null, |
| new String [][] { {"1","5"} } |
| ); |
| vetStatement |
| ( |
| stm, null, "select a,b from t2%", FIRST_ROWS_ONLY, "4", null, |
| new String [][] { {"5","1"} } |
| ); |
| vetStatement |
| ( |
| stm, null, "select a,b from t3%", FIRST_ROWS_ONLY, "4", null, |
| new String [][] { {"5","5"} } |
| ); |
| |
| /* |
| * offset 1 rows fetch 1 row. Use "next"/"rows" syntax |
| */ |
| vetStatement |
| ( |
| stm, null, "select a,b from t1%", FIRST_ROWS_ONLY, "1", "1", |
| new String [][] { {"1","2"} } |
| ); |
| vetStatement |
| ( |
| stm, null, "select a,b from t2%", FIRST_ROWS_ONLY, "1", "1", |
| new String [][] { {"2","1"} } |
| ); |
| vetStatement |
| ( |
| stm, null, "select a,b from t3%", FIRST_ROWS_ONLY, "1", "1", |
| new String [][] { {"2","2"} } |
| ); |
| |
| /* |
| * offset 1 rows fetch so many rows we drain rs row. Use "first"/"row" |
| * syntax |
| */ |
| vetStatement |
| ( |
| stm, null, "select a,b from t1%", FIRST_ROW_ONLY, "1", "10", |
| new String [][] { {"1","2"},{"1","3"}, {"1","4"},{"1","5"} } |
| ); |
| vetStatement |
| ( |
| stm, null, "select a,b from t2%", FIRST_ROW_ONLY, "1", "10", |
| new String [][] { {"2","1"},{"3","1"}, {"4","1"},{"5","1"} } |
| ); |
| vetStatement |
| ( |
| stm, null, "select a,b from t3%", FIRST_ROW_ONLY, "1", "10", |
| new String [][] { {"2","2"},{"3","3"}, {"4","4"},{"5","5"} } |
| ); |
| |
| /* |
| * offset so many rows that we see empty rs |
| */ |
| vetStatement |
| ( |
| stm, null, "select a,b from t1%", FIRST_ROW_ONLY, "10", null, |
| new String [][] { } |
| ); |
| vetStatement |
| ( |
| stm, null, "select a,b from t2%", FIRST_ROW_ONLY, "10", null, |
| new String [][] { } |
| ); |
| vetStatement |
| ( |
| stm, null, "select a,b from t3%", FIRST_ROW_ONLY, "10", null, |
| new String [][] { } |
| ); |
| |
| /* |
| * fetch first/next row (no row count given) |
| */ |
| queryAndCheck( |
| stm, |
| "select a,b from t1 fetch first row only", |
| new String [][] {{"1","1"}}); |
| queryAndCheck( |
| stm, |
| "select a,b from t2 fetch next row only", |
| new String [][] {{"1","1"}}); |
| queryAndCheck( |
| stm, |
| "select a,b from t3 fetch next row only", |
| new String [][] {{"1","1"}}); |
| |
| /* |
| * Combine with order by asc |
| */ |
| queryAndCheck( |
| stm, |
| "select a,b from t1 order by b asc fetch first row only", |
| new String [][] {{"1","1"}}); |
| queryAndCheck( |
| stm, |
| "select a,b from t2 order by a asc fetch next row only", |
| new String [][] {{"1","1"}}); |
| queryAndCheck( |
| stm, |
| "select a,b from t3 order by a asc fetch next row only", |
| new String [][] {{"1","1"}}); |
| |
| /* |
| * Combine with order by desc. |
| */ |
| queryAndCheck( |
| stm, |
| // Note: use column b here since for t1 all column a values are the |
| // same and order can change after sorting, want unique row first |
| // in rs so we can test it. |
| "select a,b from t1 order by b desc fetch first row only", |
| new String [][] {{"1","5"}}); |
| queryAndCheck( |
| stm, |
| "select a,b from t2 order by a desc fetch next row only", |
| new String [][] {{"5","1"}}); |
| queryAndCheck( |
| stm, |
| "select a,b from t3 order by a desc fetch next row only", |
| new String [][] {{"5","5"}}); |
| |
| /* |
| * Combine with group by, order by. |
| */ |
| queryAndCheck( |
| stm, |
| "select max(a) from t1 group by b fetch first row only", |
| new String [][] {{"1"}}); |
| vetStatement |
| ( |
| stm, null, "select max(a) from t2 group by b %", FIRST_ROW_ONLY, "0", null, |
| new String [][] { {"5"} } |
| ); |
| vetStatement |
| ( |
| stm, null, "select max(a) from t3 group by b order by max(a) %", NEXT_ROWS_ONLY, null, "2", |
| new String [][] { {"1"},{"2"} } |
| ); |
| |
| /* |
| * Combine with union |
| */ |
| |
| vetStatement |
| ( |
| stm, null, "select * from t1 union all select * from t1 %", FIRST_ROW_ONLY, null, "2", |
| new String [][] { {"1","1"}, {"1","2"} } |
| ); |
| |
| /* |
| * Combine with join |
| */ |
| vetStatement |
| ( |
| stm, null, "select t2.b, t3.b from t2,t3 where t2.a=t3.a %", FIRST_ROW_ONLY, null, "2", |
| new String [][] { {"1","1"}, {"1","2"} } |
| ); |
| |
| stm.close(); |
| } |
| |
| |
| /** |
| * Positive tests. |
| * |
| * @throws java.sql.SQLException |
| */ |
| public void testOffsetFetchFirstUpdatableForwardOnlyRS() throws SQLException |
| { |
| Statement stm = createStatement(ResultSet.TYPE_FORWARD_ONLY, |
| ResultSet.CONCUR_UPDATABLE); |
| ResultSet rs; |
| String[] variants; |
| |
| setAutoCommit(false); |
| |
| /* |
| * offset 0 rows (a no-op), update a row and verify result |
| */ |
| variants = makeVariants( "select * from t1 %", FIRST_ROWS_ONLY, "0", null ); |
| for (String variant : variants) |
| { |
| rs = stm.executeQuery( variant ); |
| rs.next(); |
| rs.next(); // at row 2 |
| rs.updateInt(1, -rs.getInt(1)); |
| rs.updateRow(); |
| rs.close(); |
| |
| queryAndCheck( |
| stm, |
| "select a,b from t1", |
| new String [][] { |
| {"1","1"}, {"-1","2"},{"1","3"}, {"1","4"},{"1","5"}}); |
| |
| rollback(); |
| } |
| |
| /* |
| * offset 1 rows, update a row and verify result |
| */ |
| variants = makeVariants( "select * from t1 %", FIRST_ROWS_ONLY, "1", null ); |
| for ( String variant : variants ) |
| { |
| rs = stm.executeQuery( variant ); |
| rs.next(); // at row 1, but row 2 of underlying rs |
| |
| rs.updateInt(1, -rs.getInt(1)); |
| rs.updateRow(); |
| rs.close(); |
| |
| queryAndCheck( |
| stm, |
| "select a,b from t1", |
| new String [][] { |
| {"1","1"}, {"-1","2"},{"1","3"}, {"1","4"},{"1","5"}}); |
| |
| rollback(); |
| } |
| |
| stm.close(); |
| } |
| |
| |
| /** |
| * Positive tests with scrollable read-only. |
| * |
| * @throws java.sql.SQLException |
| */ |
| public void testOffsetFetchFirstReadOnlyScrollableRS() throws SQLException |
| { |
| Statement stm = createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, |
| ResultSet.CONCUR_READ_ONLY); |
| ResultSet rs; |
| String[] variants; |
| |
| /* |
| * offset 0 rows (a no-op), update a row and verify result |
| */ |
| variants = makeVariants( "select * from t1 %", FIRST_ROWS_ONLY, "0", null ); |
| for ( String variant : variants ) |
| { |
| rs = stm.executeQuery( variant ); |
| rs.next(); |
| rs.next(); // at row 2 |
| assertTrue(rs.getInt(2) == 2); |
| rs.close(); |
| } |
| |
| /* |
| * offset 1 rows, fetch 3 row, check that we have the right ones |
| */ |
| variants = makeVariants( "select * from t1 %", FIRST_ROWS_ONLY, "1", "3" ); |
| for ( String variant : variants ) |
| { |
| rs = stm.executeQuery( variant ); |
| rs.next(); |
| rs.next(); // at row 2, but row 3 of underlying rs |
| |
| assertTrue(rs.getInt(2) == 3); |
| |
| // Go backbards and update |
| rs.previous(); |
| assertTrue(rs.getInt(2) == 2); |
| |
| // Try some navigation and border conditions |
| rs.previous(); |
| assertTrue(rs.isBeforeFirst()); |
| rs.next(); |
| rs.next(); |
| rs.next(); |
| rs.next(); |
| assertTrue(rs.isAfterLast()); |
| } |
| |
| stm.close(); |
| } |
| |
| |
| /** |
| * Positive tests with SUR (Scrollable updatable result set). |
| * |
| * @throws java.sql.SQLException |
| */ |
| public void testOffsetFetchFirstUpdatableScrollableRS() throws SQLException |
| { |
| Statement stm = createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, |
| ResultSet.CONCUR_UPDATABLE); |
| ResultSet rs; |
| String[] variants; |
| |
| setAutoCommit(false); |
| |
| /* |
| * offset 0 rows (a no-op), update a row and verify result |
| * also try the "for update" syntax so we see that it still works |
| */ |
| variants = makeVariants( "select * from t1 % for update", FIRST_ROWS_ONLY, "0", null ); |
| for (String variant : variants) |
| { |
| rs = stm.executeQuery( variant ); |
| rs.next(); |
| rs.next(); // at row 2 |
| rs.updateInt(1, -rs.getInt(1)); |
| rs.updateRow(); |
| rs.close(); |
| |
| queryAndCheck( |
| stm, |
| "select a,b from t1", |
| new String [][] { |
| {"1","1"}, {"-1","2"},{"1","3"}, {"1","4"},{"1","5"}}); |
| |
| rollback(); |
| } |
| |
| /* |
| * offset 1 rows, fetch 3 row, update some rows and verify result |
| */ |
| variants = makeVariants( "select * from t1 %", NEXT_ROWS_ONLY, "1", "3" ); |
| for ( String variant : variants ) |
| { |
| rs = stm.executeQuery( variant ); |
| rs.next(); |
| rs.next(); // at row 2, but row 3 of underlying rs |
| |
| rs.updateInt(1, -rs.getInt(1)); |
| rs.updateRow(); |
| |
| // Go backbards and update |
| rs.previous(); |
| rs.updateInt(1, -rs.getInt(1)); |
| rs.updateRow(); |
| |
| // Try some navigation and border conditions |
| rs.previous(); |
| assertTrue(rs.isBeforeFirst()); |
| rs.next(); |
| rs.next(); |
| rs.next(); |
| rs.next(); |
| assertTrue(rs.isAfterLast()); |
| |
| // Insert a row |
| rs.moveToInsertRow(); |
| rs.updateInt(1,42); |
| rs.updateInt(2,42); |
| rs.insertRow(); |
| |
| // Delete a row |
| rs.previous(); |
| rs.deleteRow(); |
| |
| // .. and see that a hole is left in its place |
| rs.previous(); |
| rs.next(); |
| assertTrue(rs.rowDeleted()); |
| |
| rs.close(); |
| |
| queryAndCheck( |
| stm, |
| "select a,b from t1", |
| new String [][] { |
| {"1","1"}, {"-1","2"},{"-1","3"},{"1","5"},{"42","42"}}); |
| rollback(); |
| } |
| |
| // Test with projection |
| variants = makeVariants( "select * from t1 where a + 1 < b%", NEXT_ROWS_ONLY, "1", null ); |
| for (String variant : variants) |
| { |
| rs = stm.executeQuery( variant ); |
| // should yield 2 rows |
| rs.absolute(2); |
| assertTrue(rs.getInt(2) == 5); |
| rs.updateInt(2, -5); |
| rs.updateRow(); |
| rs.close(); |
| |
| queryAndCheck( |
| stm, |
| "select a,b from t1", |
| new String [][] { |
| {"1","1"}, {"1","2"},{"1","3"},{"1","4"},{"1","-5"}}); |
| rollback(); |
| } |
| |
| stm.close(); |
| } |
| |
| |
| public void testValues() throws SQLException |
| { |
| Statement stm = createStatement(); |
| |
| vetStatement |
| ( |
| stm, null, "values 4%", FIRST_ROW_ONLY, null, "2", |
| new String [][] { {"4"} } |
| ); |
| |
| vetStatement |
| ( |
| stm, null, "values 4%", FIRST_ROW_ONLY, "1", null, |
| new String [][] { } |
| ); |
| |
| stm.close(); |
| } |
| |
| /** |
| * Positive tests, result set metadata |
| * |
| * @throws java.sql.SQLException |
| */ |
| public void testMetadata() throws SQLException |
| { |
| Statement stm = createStatement(); |
| ResultSet rs; |
| String[] variants; |
| |
| variants = makeVariants( "select * from t1%", NEXT_ROWS_ONLY, "1", null ); |
| for (String variant : variants) |
| { |
| rs = stm.executeQuery( variant ); |
| ResultSetMetaData rsmd= rs.getMetaData(); |
| int cnt = rsmd.getColumnCount(); |
| |
| String[] cols = new String[]{"A","B"}; |
| int[] types = {Types.INTEGER, Types.BIGINT}; |
| |
| for (int i=1; i <= cnt; i++) { |
| String name = rsmd.getColumnName(i); |
| int type = rsmd.getColumnType(i); |
| |
| assertTrue(name.equals(cols[i-1])); |
| assertTrue(type == types[i-1]); |
| } |
| |
| rs.close(); |
| } |
| |
| stm.close(); |
| } |
| |
| |
| /** |
| * Test that we see correct traces of the filtering in the statistics |
| * |
| * @throws java.sql.SQLException |
| */ |
| public void testRunTimeStatistics() throws SQLException |
| { |
| Statement stm = createStatement(); |
| ResultSet rs; |
| String[] variants; |
| |
| variants = makeVariants( "select a,b from t1%", NEXT_ROWS_ONLY, "2", null ); |
| for (String variant : variants) |
| { |
| stm.executeUpdate( "call syscs_util.syscs_set_runtimestatistics(1)" ); |
| |
| queryAndCheck( |
| stm, |
| variant, |
| new String [][] { |
| {"1","3"}, {"1","4"},{"1","5"}}); |
| |
| stm.executeUpdate( "call syscs_util.syscs_set_runtimestatistics(0)" ); |
| |
| rs = stm.executeQuery( "values syscs_util.syscs_get_runtimestatistics()" ); |
| rs.next(); |
| String plan = rs.getString(1); |
| |
| // Verify that the plan shows the filtering (2 rows of 3 seen): |
| assertTrue(plan.indexOf("Row Count (1):\n" + |
| "Number of opens = 1\n" + |
| "Rows seen = 3\n" + |
| "Rows filtered = 2") != -1); |
| |
| rs.close(); |
| } |
| |
| stm.close(); |
| } |
| |
| |
| /** |
| * Test against a bigger table |
| * |
| * @throws java.sql.SQLException |
| */ |
| public void testBigTable() throws SQLException |
| { |
| Statement stm = createStatement(); |
| |
| setAutoCommit(false); |
| |
| stm.executeUpdate("declare global temporary table session.t (i int) " + |
| "on commit preserve rows not logged"); |
| |
| PreparedStatement ps = |
| prepareStatement("insert into session.t values ?"); |
| |
| for (int i=1; i <= 100000; i++) { |
| ps.setInt(1, i); |
| ps.executeUpdate(); |
| |
| if (i % 10000 == 0) { |
| commit(); |
| } |
| } |
| |
| queryAndCheck( |
| stm, |
| "select count(*) from session.t", |
| new String [][] { |
| {"100000"}}); |
| |
| vetStatement |
| ( |
| stm, null, "select i from session.t%", FIRST_ROWS_ONLY, "99999", null, |
| new String [][] { {"100000"} } |
| ); |
| |
| stm.executeUpdate("drop table session.t"); |
| stm.close(); |
| } |
| |
| /** |
| * Test that the values of offset and fetch first are not forgotten if |
| * a {@code PreparedStatement} is executed multiple times (DERBY-4212). |
| * |
| * @throws java.sql.SQLException |
| */ |
| public void testRepeatedExecution() throws SQLException |
| { |
| PreparedStatement ps; |
| String[] variants; |
| |
| variants = makeVariants( "select * from t1 order by b%", NEXT_ROWS_ONLY, "2", "2" ); |
| for (String variant : variants) |
| { |
| ps = prepareStatement( variant ); |
| String[][] expected = {{"1", "3"}, {"1", "4"}}; |
| for (int i = 0; i < 10; i++) { |
| JDBC.assertFullResultSet(ps.executeQuery(), expected); |
| } |
| } |
| } |
| |
| /** |
| * Test dynamic arguments |
| * |
| * @throws java.sql.SQLException |
| */ |
| public void testDynamicArgs() throws SQLException |
| { |
| PreparedStatement ps; |
| String[] variants; |
| String[][] expected = null; |
| |
| // Check look-ahead also for ? in grammar since offset is not reserved |
| variants = makeVariants( "select * from t1%", NEXT_ROWS_ONLY, "?", null ); |
| for (String variant : variants) |
| { |
| ps = prepareStatement( variant ); |
| } |
| |
| |
| variants = makeVariants( "select * from t1 order by b%", NEXT_ROWS_ONLY, "?", "?" ); |
| for ( int j = 0; j < variants.length; j++ ) |
| { |
| // SQL Standard and JDBC limit/offset parameter orders are different |
| int offsetParam = ( j == SQL_STANDARD_VARIANT ) ? 1 : 2; |
| int fetchParam = ( j == SQL_STANDARD_VARIANT ) ? 2 : 1; |
| |
| expected = new String[][] {{"1", "3"}, {"1", "4"}}; |
| ps = prepareStatement( variants[ j ] ); |
| |
| // Check range errors |
| |
| ps.setInt( offsetParam, 0 ); |
| assertPreparedStatementError(LANG_MISSING_PARMS, ps); |
| |
| ps.setInt( offsetParam, -1 ); |
| ps.setInt( fetchParam, 2 ); |
| assertPreparedStatementError(LANG_INVALID_ROW_COUNT_OFFSET, ps); |
| |
| ps.setInt( offsetParam, 0 ); |
| ps.setInt( fetchParam, ( j == SQL_STANDARD_VARIANT ) ? 0 : -1 ); |
| assertPreparedStatementError(LANG_INVALID_ROW_COUNT_FIRST, ps); |
| |
| // Check non-integer values |
| try { |
| ps.setString( offsetParam, "aaa"); |
| } catch (SQLException e) { |
| assertSQLState(LANG_FORMAT_EXCEPTION, e); |
| } |
| |
| try { |
| ps.setString( fetchParam, "aaa"); |
| } catch (SQLException e) { |
| assertSQLState(LANG_FORMAT_EXCEPTION, e); |
| } |
| |
| |
| // A normal case |
| for (int i = 0; i < 2; i++) { |
| ps.setInt( offsetParam,2 ); |
| ps.setInt( fetchParam,2 ); |
| JDBC.assertFullResultSet(ps.executeQuery(), expected); |
| } |
| |
| // Now, note that since we now have different values for offset and |
| // fetch first, we also exercise reusing the result set for this |
| // prepared statement (i.e. the values are computed at execution time, |
| // not at result set generation time). Try long value for change. |
| ps.setLong( offsetParam, 1L ); |
| ps.setInt( fetchParam, 3 ); |
| expected = new String[][]{{"1", "2"}, {"1", "3"}, {"1", "4"}}; |
| JDBC.assertFullResultSet(ps.executeQuery(), expected); |
| |
| |
| // Try a large number |
| ps.setLong( offsetParam, Integer.MAX_VALUE * 2L ); |
| ps.setInt( fetchParam, 5 ); |
| JDBC.assertEmpty(ps.executeQuery()); |
| } |
| |
| // Mix of prepared and not |
| variants = makeVariants( "select * from t1 order by b%", NEXT_ROWS_ONLY, "?", "3" ); |
| for (String variant : variants) |
| { |
| ps = prepareStatement( variant ); |
| ps.setLong(1, 1L); |
| JDBC.assertFullResultSet(ps.executeQuery(), expected); |
| } |
| |
| variants = makeVariants( "select * from t1 order by b%", NEXT_ROWS_ONLY, "4", "?" ); |
| for (String variant : variants) |
| { |
| ps = prepareStatement( variant ); |
| ps.setLong(1, 1L); |
| JDBC.assertFullResultSet(ps.executeQuery(), new String[][]{{"1", "5"}}); |
| } |
| |
| // Mix of other dyn args and ours: |
| variants = makeVariants( "select * from t1 where a = ? order by b%", NEXT_ROWS_ONLY, "?", "3" ); |
| for (String variant : variants) |
| { |
| ps = prepareStatement( variant ); |
| ps.setInt(1, 1); |
| ps.setLong(2, 1L); |
| JDBC.assertFullResultSet(ps.executeQuery(), expected); |
| } |
| |
| variants = makeVariants( "select * from t1 where a = ? order by b%", NEXT_ROWS_ONLY, "1", "?" ); |
| for (String variant : variants) |
| { |
| ps = prepareStatement( variant ); |
| ps.setInt(1, 1); |
| ps.setLong(2, 2L); |
| expected = new String[][]{{"1", "2"}, {"1", "3"}}; |
| JDBC.assertFullResultSet(ps.executeQuery(), expected); |
| } |
| |
| // NULLs not allowed (Note: parameter metadata says "isNullable" for |
| // all ? args in Derby...) |
| variants = makeVariants( "select * from t1 order by b%", NEXT_ROWS_ONLY, "?", "?" ); |
| for ( int i = 0; i < variants.length; i++ ) |
| { |
| ps = prepareStatement( variants[ i ] ); |
| int offsetParam = ( i == SQL_STANDARD_VARIANT ) ? 1 : 2; |
| int fetchParam = ( i == SQL_STANDARD_VARIANT ) ? 2 : 1; |
| |
| ps.setNull( offsetParam, Types.BIGINT ); |
| ps.setInt( fetchParam, 2 ); |
| assertPreparedStatementError(LANG_ROW_COUNT_OFFSET_FIRST_IS_NULL, ps); |
| |
| ps.setInt( offsetParam,1 ); |
| ps.setNull( fetchParam, Types.BIGINT ); |
| assertPreparedStatementError(LANG_ROW_COUNT_OFFSET_FIRST_IS_NULL, ps); |
| |
| ps.close(); |
| } |
| } |
| |
| /** |
| * Test dynamic arguments |
| * |
| * @throws java.sql.SQLException |
| */ |
| public void testDynamicArgsMetaData() throws SQLException |
| { |
| |
| //since there is no getParameterMetaData() call available in JSR169 |
| //implementations, do not run this test if we are running JSR169 |
| if (JDBC.vmSupportsJSR169()) return; |
| |
| PreparedStatement ps; |
| String[] variants; |
| |
| variants = makeVariants( "select * from t1 where a = ? order by b%", NEXT_ROWS_ONLY, "?", "?" ); |
| for (String variant : variants) |
| { |
| ps = prepareStatement( variant ); |
| |
| ParameterMetaData pmd = ps.getParameterMetaData(); |
| int[] expectedTypes = { Types.INTEGER, Types.BIGINT, Types.BIGINT }; |
| |
| for (int i = 0; i < 3; i++) { |
| assertEquals("Unexpected parameter type", |
| expectedTypes[i], pmd.getParameterType(i+1)); |
| assertEquals("Derby ? args are nullable", |
| // Why is that? Cf. logic in ParameterNode.setType |
| ParameterMetaData.parameterNullable, |
| pmd.isNullable(i+1)); |
| } |
| ps.close(); |
| } |
| } |
| |
| /** |
| * Test some additional corner cases in JDBC limit/offset syntax. |
| * |
| * @throws java.sql.SQLException |
| */ |
| public void testJDBCLimitOffset() throws SQLException |
| { |
| // LIMIT 0 is allowed. It means: everything from the OFFSET forward |
| PreparedStatement ps = prepareStatement( "select a from t2 order by a { limit ? }" ); |
| ps.setInt( 1, 0 ); |
| JDBC.assertFullResultSet |
| ( |
| ps.executeQuery(), |
| new String[][] { { "1" }, { "2" }, { "3" }, { "4" }, { "5" } } |
| ); |
| ps.close(); |
| |
| ps = prepareStatement( "select a from t2 order by a { limit ? offset 3 }" ); |
| ps.setInt( 1, 0 ); |
| JDBC.assertFullResultSet |
| ( |
| ps.executeQuery(), |
| new String[][] { { "4" }, { "5" } } |
| ); |
| ps.close(); |
| |
| // mix JDBC and SQL Standard syntax |
| ps = prepareStatement |
| ( |
| "select t.a from\n" + |
| "( select * from t2 order by a { limit 3 offset 1 } ) t,\n" + |
| "( select * from t3 order by a offset 2 rows fetch next 10 rows only ) s\n" + |
| "where t.a = s.a order by t.a" |
| ); |
| JDBC.assertFullResultSet |
| ( |
| ps.executeQuery(), |
| new String[][] { { "3" }, { "4" } } |
| ); |
| ps.close(); |
| } |
| |
| /** |
| * Run a statement with both SQL Standard and JDBC limit/offset syntax. Verify |
| * that we get the expected error or results. The statement has a % literal at the |
| * point where the offset/fetchFirst and limit/offset clauses are to be inserted. |
| */ |
| private void vetStatement |
| ( Statement stmt, String sqlState, String stub, String fetchFormat, String offset, String fetchFirst, String[][] expectedResults ) |
| throws SQLException |
| { |
| String[] variants = makeVariants( stub, fetchFormat, offset, fetchFirst ); |
| |
| for (String text : variants) |
| { |
| if ( sqlState != null ) |
| { |
| assertStatementError( sqlState, stmt, text ); |
| } |
| else |
| { |
| queryAndCheck( stmt, text, expectedResults ); |
| } |
| } |
| } |
| |
| /** |
| * Make the SQL Standard and JDBC limit/offset variants of a stub statement, |
| * plugging in the given offset and fetch count. |
| */ |
| private String[] makeVariants |
| ( String stub, String fetchFormat, String offset, String fetchFirst ) |
| { |
| String[] result = new String[ VARIANT_COUNT ]; |
| |
| result[ SQL_STANDARD_VARIANT ] = makeSQLStandardText( stub, fetchFormat, offset, fetchFirst ); |
| result[ JDBC_VARIANT ] = makeJDBCText( stub, offset, fetchFirst ); |
| |
| return result; |
| } |
| |
| /** |
| * Substitute the SQL Standard syntax into a stub statement, given an offset and fetch count. |
| */ |
| private String makeSQLStandardText |
| ( String stub, String fetchFormat, String offset, String fetchFirst ) |
| { |
| String sqlStandardText = ""; |
| |
| if ( offset != null ) |
| { |
| sqlStandardText = " offset " + offset + " rows "; |
| } |
| if ( fetchFirst != null ) |
| { |
| sqlStandardText = sqlStandardText + substitute( fetchFormat, PERCENT_TOKEN, fetchFirst ); |
| } |
| |
| sqlStandardText = substitute( stub, PERCENT_TOKEN, sqlStandardText ); |
| |
| println( sqlStandardText ); |
| |
| return sqlStandardText; |
| } |
| /** |
| * Substitute JDBC limit/offset syntax into a stub statement, given an offset and fetch count. |
| */ |
| private String makeJDBCText |
| ( String stub, String offset, String fetchFirst ) |
| { |
| String jdbcText = ""; |
| |
| if ( offset != null ) |
| { |
| jdbcText = " offset " + offset; |
| } |
| if ( fetchFirst != null ) |
| { |
| jdbcText = " limit " + fetchFirst + " " + jdbcText; |
| } |
| else |
| { |
| jdbcText = "limit 0 " + jdbcText; |
| } |
| |
| jdbcText = substitute( stub, PERCENT_TOKEN, " { " + jdbcText + " } " ); |
| |
| println( jdbcText ); |
| |
| return jdbcText; |
| } |
| |
| private String substitute( String stub, String token, String replacement ) |
| { |
| int substitutionIndex = stub.indexOf( token ); |
| if ( substitutionIndex < 0 ) { fail( "Bad stub: " + stub + ". Can't find token: " + token ); } |
| |
| String prefix = stub.substring( 0, substitutionIndex ); |
| String suffix = ( substitutionIndex == stub.length() - 1 ) ? |
| "" : stub.substring( substitutionIndex + 1, stub.length() ); |
| |
| return prefix + replacement + suffix; |
| } |
| |
| private void queryAndCheck( |
| Statement stm, |
| String queryText, |
| String [][] expectedRows) throws SQLException { |
| |
| ResultSet rs = stm.executeQuery(queryText); |
| JDBC.assertFullResultSet(rs, expectedRows); |
| } |
| |
| } |