From 55fc0b34851385a994c2660db1d07f40204ea7d8 Mon Sep 17 00:00:00 2001 From: Magnus Persson <1431685+magnusp@users.noreply.github.com> Date: Fri, 23 Oct 2026 05:48:39 +0200 Subject: [PATCH] test(conformance): select DBMetaDataTest, StatementTest, and PrepStmtTest (#163) Fixes #163 Hrana 3 remains the source of truth for transport, values, metadata, batching, transactions, and error reporting. java.sql defines the JDBC surface. xerial/sqlite-jdbc 3.53.4.0 is the behavioral reference and conformance foundation, never a copy target. Select the three classes at the pinned 3.53.4.0 commit, preserve every existing selected file and divergence, and record one divergence per new copy for the sqld and no-describe cases. --- upstream-xerial_sqlite-jdbc.manifest | 14 +- upstream-xerial_sqlite-jdbc/CHECKSUMS.sha256 | 6 + upstream-xerial_sqlite-jdbc/UPSTREAM.md | 4 +- .../test/java/org/sqlite/DBMetaDataTest.java | 1791 +++++++++++++++++ .../test/java/org/sqlite/PrepStmtTest.java | 924 +++++++++ .../test/java/org/sqlite/StatementTest.java | 701 +++++++ 6 files changed, 3435 insertions(+), 5 deletions(-) create mode 100644 upstream-xerial_sqlite-jdbc/src/test/java/org/sqlite/DBMetaDataTest.java create mode 100644 upstream-xerial_sqlite-jdbc/src/test/java/org/sqlite/PrepStmtTest.java create mode 100644 upstream-xerial_sqlite-jdbc/src/test/java/org/sqlite/StatementTest.java diff --git a/upstream-xerial_sqlite-jdbc.manifest b/upstream-xerial_sqlite-jdbc.manifest index 1a07929..94ae1e9 100644 --- a/upstream-xerial_sqlite-jdbc.manifest +++ b/upstream-xerial_sqlite-jdbc.manifest @@ -27,13 +27,15 @@ # selected: src/test/java/org/sqlite/PreparedStatementThreadTest.java -- concurrent execute and close must not crash or hide failures. # selected: src/test/java/org/sqlite/ResultSetWithoutResultsTest.java -- statement result-kind transitions leave no stale result set. # selected: src/test/java/org/sqlite/RSMetaDataTest.java -- ResultSetMetaData column names, types, and nullability. +# selected: src/test/java/org/sqlite/DBMetaDataTest.java -- DatabaseMetaData tables, columns, keys, type info, and java.sql-shaped empty result sets. +# selected: src/test/java/org/sqlite/StatementTest.java -- Statement lifecycle, conversions, batches, and execute transitions over ordinary SQL. +# selected: src/test/java/org/sqlite/PrepStmtTest.java -- PreparedStatement binding, metadata, batches, and value round trips. # Rejected tests. # rejected: src/test/java/org/sqlite/BackupTest.java -- drives sqlite-jdbc's native backup API; Hrana exposes no backup primitive. # rejected: src/test/java/org/sqlite/BusyHandlerTest.java -- registers native busy-handler callbacks that Hrana does not expose. # rejected: src/test/java/org/sqlite/CollationTest.java -- registers JVM collations through sqlite-jdbc's native extension API. # rejected: src/test/java/org/sqlite/ConnectionTest.java -- exercises sqlite-jdbc URL, open-mode, and resource-loading configuration that has no Hrana equivalent; covered JDBC paths are asserted by selected tests. -# rejected: src/test/java/org/sqlite/DBMetaDataTest.java -- #156 tracks the remaining DatabaseMetaData gaps (java.sql-shaped empty result sets, column and key filters); sqld also rejects CREATE TEMP TABLE. # rejected: src/test/java/org/sqlite/ErrorMessageTest.java -- asserts sqlite-jdbc wording and native result codes; sqld returns different messages and error codes. # rejected: src/test/java/org/sqlite/ExtendedCommandTest.java -- exercises sqlite-jdbc's dot-command parser, not the JDBC API. # rejected: src/test/java/org/sqlite/ExtensionTest.java -- loads native SQLite extensions; the libSQL server does not permit extension loading. @@ -41,7 +43,6 @@ # rejected: src/test/java/org/sqlite/ListenerTest.java -- update, commit, rollback, and authorizer hooks require native callbacks unavailable over Hrana. # rejected: src/test/java/org/sqlite/MathFunctionsTest.java -- passes only when the server binary was compiled with SQLITE_ENABLE_MATH_FUNCTIONS; the sqld image build is not pinned to that option. # rejected: src/test/java/org/sqlite/MultipleClassLoaderTest.java -- verifies sqlite-jdbc's native library loading per class loader; libSQL has no native library. -# rejected: src/test/java/org/sqlite/PrepStmtTest.java -- #157 tracks the parameter binding and parameter-metadata gaps; the class cannot pass until they close. # rejected: src/test/java/org/sqlite/ProgressHandlerTest.java -- registers native progress callbacks that Hrana does not expose. # rejected: src/test/java/org/sqlite/QueryTest.java -- imports sqlite-jdbc's SQLiteConfig and FastDateFormat classes and drives query timeouts; the conversion getters it exercises are covered by selected ResultSetTest cases. # rejected: src/test/java/org/sqlite/ReadUncommittedTest.java -- depends on sqlite-jdbc shared-cache and read-uncommitted connection configuration; sqld does not expose it. @@ -51,7 +52,6 @@ # rejected: src/test/java/org/sqlite/SQLiteConnectionPoolDataSourceTest.java -- tests sqlite-jdbc DataSource and pooling classes that libSQL does not provide. # rejected: src/test/java/org/sqlite/SQLiteDataSourceTest.java -- tests sqlite-jdbc DataSource classes that libSQL does not provide. # rejected: src/test/java/org/sqlite/SQLiteJDBCLoaderTest.java -- tests the native SQLite library loader. -# rejected: src/test/java/org/sqlite/StatementTest.java -- #158 tracks the conversion, batch, and fetch-direction gaps; Hrana also rejects multi-statement strings and TEMP tables. # rejected: src/test/java/org/sqlite/TransactionTest.java -- asserts SQLite file-locking and multi-connection wait behavior that a single sqld server cannot reproduce; commit visibility is covered by InsertQueryTest. # rejected: src/test/java/org/sqlite/TypeMapTest.java -- #95 rejects connection type maps with SQLFeatureNotSupportedException because java.sql makes them optional, while xerial stores a map it never applies; the control run cannot assert both, so the class stays upstream. # rejected: src/test/java/org/sqlite/UDFCustomErrorTest.java -- registers JVM UDFs and inspects native error callbacks. @@ -74,6 +74,11 @@ src/test/java/org/sqlite/PreparedStatementThreadTest.java src/test/java/org/sqlite/ResultSetWithoutResultsTest.java src/test/java/org/sqlite/RSMetaDataTest.java +# Third batch: database metadata, statement lifecycle, and prepared statements. +src/test/java/org/sqlite/DBMetaDataTest.java +src/test/java/org/sqlite/StatementTest.java +src/test/java/org/sqlite/PrepStmtTest.java + LICENSE LICENSE.zentus NOTICE @@ -86,3 +91,6 @@ VERSION # divergence: src/test/java/org/sqlite/CachedRowSetTest.java -- Hrana holds a namespace until its last connection closes, so the conformance copy closes the leaked connection and asserts the row it reads. # divergence: src/test/java/org/sqlite/InsertQueryTest.java -- upstream misuses the connection factory so the transaction is applied to a different connection and never asserts the result; the conformance copy reuses the transaction connection, asserts rollback and commit visibility, and asserts the final count (#51). # divergence: src/test/java/org/sqlite/PreparedStatementThreadTest.java -- upstream swallows every ExecutionException, which hides assertion failures and runtime exceptions from the close/execute race; Hrana serializes requests per stream, so the conformance copy awaits both futures and tolerates only an SQLException cause, and any runtime failure fails the test as the Java equivalent of the native crash it guards (#51, #118). +# divergence: src/test/java/org/sqlite/DBMetaDataTest.java -- sqld rejects CREATE TEMP TABLE with SQL_PARSE_ERROR, so the conformance copy skips only columnOrderOfgetTables on libSQL and leaves the xerial control run intact. +# divergence: src/test/java/org/sqlite/StatementTest.java -- Hrana sends one statement per Stmt so sqld rejects multi-statement strings (SQL_MANY_STATEMENTS), sqld rejects CREATE TEMP TABLE (SQL_PARSE_ERROR), and Hrana streams are independent so DROP succeeds while a materialized result set is open; the conformance copy skips those cases on libSQL. xerial's DATE-millis round trip is replaced on libSQL by the java.sql date-only value, and the copy uses standard JDBC unwrap and closeOnCompletion calls plus drops the sqlite-jdbc-only generated-keys configuration hook so it compiles without the xerial classes. +# divergence: src/test/java/org/sqlite/PrepStmtTest.java -- Hrana has no describe request, so the conformance copy skips the parameter-type, pre-execution metadata, and pre-execution SQL-validation expectations on libSQL, and uses the java.sql date-only value instead of xerial's DATE-millis round trip; it also reassigns the result set after the next execute as java.sql requires and drops the cases that call sqlite-jdbc's own configuration and error-code classes. diff --git a/upstream-xerial_sqlite-jdbc/CHECKSUMS.sha256 b/upstream-xerial_sqlite-jdbc/CHECKSUMS.sha256 index 5ff3d1f..233995e 100644 --- a/upstream-xerial_sqlite-jdbc/CHECKSUMS.sha256 +++ b/upstream-xerial_sqlite-jdbc/CHECKSUMS.sha256 @@ -12,6 +12,8 @@ a0670cf020edf700524c43c85321c2ba027a8bace5682001db60875d4a9fe52b ./NOTICE 0a60829ce6edd56de46fb87303401c5c1e1e666da5631056340f5c5ce902dcdf ./VERSION # upstream 1351fdca2644e90ac9028ed99c6096c9ecee82ccb7d45cedd9bb454887fd2c6b ./src/test/java/org/sqlite/CachedRowSetTest.java e1743294d2e5e781fae3b647d0bfc2eeeeb856c5276efe308f8b58979b3ab178 ./src/test/java/org/sqlite/CachedRowSetTest.java +# upstream 77471e076af3aca03a56fc32914d5b8d39c1adbb71f971018c35e417de113619 ./src/test/java/org/sqlite/DBMetaDataTest.java +0426102df7665d2208c43beb1e7d4451cc5b40969b04b51f8dc82fa14825f487 ./src/test/java/org/sqlite/DBMetaDataTest.java # upstream 9fb0f2df7de66cac0a46b241c0c1cd3311a80646d0431d377097741b99053760 ./src/test/java/org/sqlite/FetchSizeTest.java 9fb0f2df7de66cac0a46b241c0c1cd3311a80646d0431d377097741b99053760 ./src/test/java/org/sqlite/FetchSizeTest.java # upstream 6dfcecb209e01829ba0aea782b47351443e3970ad1d33ac4c3fb979e6dff1f40 ./src/test/java/org/sqlite/InsertQueryTest.java @@ -20,6 +22,8 @@ ae4568fd773f158f04e89df8e0fe2326381b6af7ed1316193830d38f7cadbae5 ./src/test/jav 075745907a5174b922db6a45b9c29f45901af1835af27a0014f5fac835b41d0f ./src/test/java/org/sqlite/JSON1Test.java # upstream 242bb51330240174bab5f443aa4d8de1049e6d947374e080a636c4aa4d0049ee ./src/test/java/org/sqlite/MetadataLeakTest.java 242bb51330240174bab5f443aa4d8de1049e6d947374e080a636c4aa4d0049ee ./src/test/java/org/sqlite/MetadataLeakTest.java +# upstream 24d605d59b7507f574104ddbf8c297f90d4498401368d5b39947f0932f5a686a ./src/test/java/org/sqlite/PrepStmtTest.java +952803b01106afa53d73641728914865ff61068a4ad1c1f25128cc03c4ba30f2 ./src/test/java/org/sqlite/PrepStmtTest.java # upstream 7af8ae0999a0bf26a20c96ab23131505bd79827e6af17742771e36e9d8d985f4 ./src/test/java/org/sqlite/PreparedStatementThreadTest.java 8e63f29bec87782e5705ebff748389cb9f5fe3a68d755c730a2beaccde9712a4 ./src/test/java/org/sqlite/PreparedStatementThreadTest.java # upstream d787e71bdf2b8d12a2669eb06c432cf2a39ac77f678a92459e3dd9b6c4519a43 ./src/test/java/org/sqlite/RSMetaDataTest.java @@ -28,5 +32,7 @@ d787e71bdf2b8d12a2669eb06c432cf2a39ac77f678a92459e3dd9b6c4519a43 ./src/test/jav 6065d284933c8462cc13217992820a9261075c0e9ee126df6cbe9961d90303f9 ./src/test/java/org/sqlite/ResultSetTest.java # upstream 958c1f2ff5ca2f1257dfd847207ea793839969b234d140576b51af346697233c ./src/test/java/org/sqlite/ResultSetWithoutResultsTest.java 958c1f2ff5ca2f1257dfd847207ea793839969b234d140576b51af346697233c ./src/test/java/org/sqlite/ResultSetWithoutResultsTest.java +# upstream 81125078aa972d55deba801a81841271a659f0f3306060e128850cbe8cc4054b ./src/test/java/org/sqlite/StatementTest.java +8328eec31b4504df729d39e18c978e03d71c455080a17e943e61d30ff926330c ./src/test/java/org/sqlite/StatementTest.java # upstream bef3eddca2a6b80472050a3a52d5e7246128f0644d29c24ed091709b0ed6f121 ./src/test/java/org/sqlite/Utils.java bef3eddca2a6b80472050a3a52d5e7246128f0644d29c24ed091709b0ed6f121 ./src/test/java/org/sqlite/Utils.java diff --git a/upstream-xerial_sqlite-jdbc/UPSTREAM.md b/upstream-xerial_sqlite-jdbc/UPSTREAM.md index 1e4451a..b887e8d 100644 --- a/upstream-xerial_sqlite-jdbc/UPSTREAM.md +++ b/upstream-xerial_sqlite-jdbc/UPSTREAM.md @@ -9,7 +9,7 @@ | Commit date | 2026-08-26T09:55:34Z | | Commit author | github-actions <41898282+github-actions[bot]@users.noreply.github.com> | | Commit subject | chore(release): 3.53.4.0 [skip ci] | -| Paths | src/test/java/org/sqlite/Utils.java, src/test/java/org/sqlite/ResultSetTest.java, src/test/java/org/sqlite/InsertQueryTest.java, src/test/java/org/sqlite/FetchSizeTest.java, src/test/java/org/sqlite/CachedRowSetTest.java, src/test/java/org/sqlite/JSON1Test.java, src/test/java/org/sqlite/MetadataLeakTest.java, src/test/java/org/sqlite/PreparedStatementThreadTest.java, src/test/java/org/sqlite/ResultSetWithoutResultsTest.java, src/test/java/org/sqlite/RSMetaDataTest.java, LICENSE, LICENSE.zentus, NOTICE, VERSION | +| Paths | src/test/java/org/sqlite/Utils.java, src/test/java/org/sqlite/ResultSetTest.java, src/test/java/org/sqlite/InsertQueryTest.java, src/test/java/org/sqlite/FetchSizeTest.java, src/test/java/org/sqlite/CachedRowSetTest.java, src/test/java/org/sqlite/JSON1Test.java, src/test/java/org/sqlite/MetadataLeakTest.java, src/test/java/org/sqlite/PreparedStatementThreadTest.java, src/test/java/org/sqlite/ResultSetWithoutResultsTest.java, src/test/java/org/sqlite/RSMetaDataTest.java, src/test/java/org/sqlite/DBMetaDataTest.java, src/test/java/org/sqlite/StatementTest.java, src/test/java/org/sqlite/PrepStmtTest.java, LICENSE, LICENSE.zentus, NOTICE, VERSION | | License | Apache-2.0 and Zentus BSD. See `LICENSE`, `LICENSE.zentus`, and `NOTICE`. | The exported upstream files are byte-identical copies at this commit and must not be edited. `export-upstream` regenerates this tree. @@ -17,4 +17,4 @@ The exported upstream files are byte-identical copies at this commit and must no ## Divergences -`CHECKSUMS.sha256` records the pinned upstream hash for every file. Four conformance copies carry a recorded divergence and must not be edited beyond it: `CachedRowSetTest.java`, `InsertQueryTest.java`, `JSON1Test.java`, and `PreparedStatementThreadTest.java`. Each reason is recorded in `../upstream-xerial_sqlite-jdbc.manifest` under `# divergence:`. +`CHECKSUMS.sha256` records the pinned upstream hash for every file. Seven conformance copies carry a recorded divergence and must not be edited beyond it: `CachedRowSetTest.java`, `DBMetaDataTest.java`, `InsertQueryTest.java`, `JSON1Test.java`, `PrepStmtTest.java`, `PreparedStatementThreadTest.java`, and `StatementTest.java`. Each reason is recorded in `../upstream-xerial_sqlite-jdbc.manifest` under `# divergence:`. diff --git a/upstream-xerial_sqlite-jdbc/src/test/java/org/sqlite/DBMetaDataTest.java b/upstream-xerial_sqlite-jdbc/src/test/java/org/sqlite/DBMetaDataTest.java new file mode 100644 index 0000000..b5663e4 --- /dev/null +++ b/upstream-xerial_sqlite-jdbc/src/test/java/org/sqlite/DBMetaDataTest.java @@ -0,0 +1,1791 @@ +package org.sqlite; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assumptions.assumeThat; +import static org.junit.jupiter.api.Assumptions.assumeFalse; + +import java.io.InputStream; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Types; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledInNativeImage; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +/** These tests are designed to stress Statements on memory databases. */ +public class DBMetaDataTest { + private Connection conn; + private Statement stat; + private DatabaseMetaData meta; + + @BeforeEach + public void connect() throws Exception { + conn = DriverManager.getConnection("jdbc:sqlite:"); + stat = conn.createStatement(); + stat.executeUpdate( + "create table test (id integer primary key, fn float default 0.0, sn not null, intvalue integer(5), realvalue real(8,3), charvalue varchar(21));"); + stat.executeUpdate("create view testView as select * from test;"); + meta = conn.getMetaData(); + } + + @AfterEach + public void close() throws SQLException { + meta = null; + stat.close(); + conn.close(); + } + + @Test + public void getTables() throws SQLException { + ResultSet rs = meta.getTables(null, null, null, null); + assertThat(rs).isNotNull(); + stat.close(); + + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("sqlite_schema"); + assertThat(rs.getString("TABLE_TYPE")).isEqualTo("SYSTEM TABLE"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("test"); // 3 + assertThat(rs.getString("TABLE_TYPE")).isEqualTo("TABLE"); // 4 + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("testView"); + assertThat(rs.getString("TABLE_TYPE")).isEqualTo("VIEW"); + assertThat(rs.next()).isFalse(); + rs.close(); + + rs = meta.getTables(null, null, "bob", null); + assertThat(rs.next()).isFalse(); + rs.close(); + rs = meta.getTables(null, null, "test", null); + assertThat(rs.next()).isTrue(); + assertThat(rs.next()).isFalse(); + rs.close(); + rs = meta.getTables(null, null, "test%", null); + assertThat(rs.next()).isTrue(); + assertThat(rs.next()).isTrue(); + rs.close(); + + rs = meta.getTables(null, null, null, new String[] {"table"}); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("test"); + assertThat(rs.next()).isFalse(); + rs.close(); + + rs = meta.getTables(null, null, null, new String[] {"view"}); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("testView"); + assertThat(rs.next()).isFalse(); + rs.close(); + + rs = meta.getTables(null, null, null, new String[] {"system table"}); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("sqlite_schema"); + assertThat(rs.next()).isFalse(); + rs.close(); + } + + @Test + public void getTablesWithEscape() throws SQLException { + stat.executeUpdate("create table 'table%with%wildcards'(c1 integer)"); + stat.executeUpdate("create table 'table_with_wildcards'(c2 integer)"); + stat.executeUpdate("create table 'tableXwithXwildcards'(c3 integer)"); + + String esc = meta.getSearchStringEscape(); + try (ResultSet rs = + meta.getTables(null, null, "table_with_wildcards".replace("_", esc + "_"), null)) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("table_with_wildcards"); + assertThat(rs.next()).isFalse(); + } + try (ResultSet rs = + meta.getTables(null, null, "table%with%wildcards".replace("%", esc + "%"), null)) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("table%with%wildcards"); + assertThat(rs.next()).isFalse(); + } + } + + @Test + public void getTableTypes() throws SQLException { + ResultSet rs = meta.getTableTypes(); + assertThat(rs).isNotNull(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_TYPE")).isEqualTo("GLOBAL TEMPORARY"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_TYPE")).isEqualTo("SYSTEM TABLE"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_TYPE")).isEqualTo("TABLE"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_TYPE")).isEqualTo("VIEW"); + assertThat(rs.next()).isFalse(); + } + + @Test + public void getTypeInfo() throws SQLException { + ResultSet rs = meta.getTypeInfo(); + assertThat(rs).isNotNull(); + + testTypeInfo( + rs, + "NULL", + Types.NULL, + 0, + null, + null, + null, + DatabaseMetaData.typeNullable, + false, + DatabaseMetaData.typeSearchable, + true, + false, + false, + 0, + 0, + 10); + testTypeInfo( + rs, + "INTEGER", + Types.INTEGER, + 0, + null, + null, + null, + DatabaseMetaData.typeNullable, + false, + DatabaseMetaData.typeSearchable, + false, + false, + true, + 0, + 0, + 10); + testTypeInfo( + rs, + "REAL", + Types.REAL, + 0, + null, + null, + null, + DatabaseMetaData.typeNullable, + false, + DatabaseMetaData.typeSearchable, + false, + false, + false, + 0, + 0, + 10); + testTypeInfo( + rs, + "TEXT", + Types.VARCHAR, + 0, + null, + null, + null, + DatabaseMetaData.typeNullable, + true, + DatabaseMetaData.typeSearchable, + true, + false, + false, + 0, + 0, + 10); + testTypeInfo( + rs, + "BLOB", + Types.BLOB, + 0, + null, + null, + null, + DatabaseMetaData.typeNullable, + false, + DatabaseMetaData.typeSearchable, + true, + false, + false, + 0, + 0, + 10); + + assertThat(rs.next()).isFalse(); + } + + private void testTypeInfo( + ResultSet rs, + String name, + int type, + int precision, + String literalPrefix, + String literalSuffix, + String createParams, + int nullable, + boolean caseSensitive, + int searchable, + boolean unsigned, + boolean fixedPrecScale, + boolean autoIncrement, + int minScale, + int maxScale, + int radix) + throws SQLException { + assertThat(rs.next()).isTrue(); + assertThat(rs.getString(1)).isEqualTo(name); + assertThat(rs.getInt(2)).isEqualTo(type); + assertThat(rs.getInt(3)).isEqualTo(precision); + assertThat(rs.getString(4)).isEqualTo(literalPrefix); + assertThat(rs.getString(5)).isEqualTo(literalSuffix); + assertThat(rs.getString(6)).isEqualTo(createParams); + assertThat(rs.getShort(7)).isEqualTo((short) nullable); + assertThat(rs.getBoolean(8)).isEqualTo(caseSensitive); + assertThat(rs.getShort(9)).isEqualTo((short) searchable); + assertThat(rs.getBoolean(10)).isEqualTo(unsigned); + assertThat(rs.getBoolean(11)).isEqualTo(fixedPrecScale); + assertThat(rs.getBoolean(12)).isEqualTo(autoIncrement); + assertThat(rs.getString(13)).isEqualTo(null); + assertThat(rs.getShort(14)).isEqualTo((short) minScale); + assertThat(rs.getShort(15)).isEqualTo((short) maxScale); + assertThat(rs.getInt(16)).isEqualTo(0); + assertThat(rs.getInt(17)).isEqualTo(0); + assertThat(rs.getInt(18)).isEqualTo(radix); + } + + @Test + public void getColumns() throws SQLException { + ResultSet rs = meta.getColumns(null, null, "test", "id"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("test"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("id"); + assertThat(rs.getString("IS_NULLABLE")).isEqualTo("YES"); + assertThat(rs.getString("COLUMN_DEF")).isNull(); + assertThat(rs.getInt("DATA_TYPE")).isEqualTo(Types.INTEGER); + assertThat(rs.getString("TYPE_NAME")).isEqualTo("INTEGER"); + assertThat(rs.getInt("COLUMN_SIZE")).isEqualTo(2000000000); + assertThat(rs.getInt("DECIMAL_DIGITS")).isEqualTo(0); + assertThat(rs.getString("IS_AUTOINCREMENT")).isEqualTo("NO"); + + // verify type of metadata columns + assertThat(rs.getObject("COLUMN_NAME")).isInstanceOf(String.class); + assertThat(rs.getObject("DATA_TYPE")).isInstanceOf(Integer.class); + assertThat(rs.getObject("TYPE_NAME")).isInstanceOf(String.class); + assertThat(rs.getObject("COLUMN_SIZE")).isInstanceOf(Integer.class); + assertThat(rs.getObject("DECIMAL_DIGITS")).isInstanceOf(Integer.class); + assertThat(rs.getObject("NUM_PREC_RADIX")).isInstanceOf(Integer.class); + assertThat(rs.getObject("NULLABLE")).isInstanceOf(Integer.class); + assertThat(rs.getObject("CHAR_OCTET_LENGTH")).isInstanceOf(Integer.class); + assertThat(rs.getObject("ORDINAL_POSITION")).isInstanceOf(Integer.class); + assertThat(rs.getObject("IS_NULLABLE")).isInstanceOf(String.class); + assertThat(rs.getObject("IS_AUTOINCREMENT")).isInstanceOf(String.class); + assertThat(rs.getObject("IS_GENERATEDCOLUMN")).isInstanceOf(String.class); + + assertThat(rs.next()).isFalse(); + + rs = meta.getColumns(null, null, "test", "fn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("fn"); + assertThat(rs.getInt("DATA_TYPE")).isEqualTo(Types.FLOAT); + assertThat(rs.getString("TYPE_NAME")).isEqualTo("FLOAT"); + assertThat(rs.getString("IS_NULLABLE")).isEqualTo("YES"); + assertThat(rs.getString("COLUMN_DEF")).isEqualTo("0.0"); + assertThat(rs.getInt("COLUMN_SIZE")).isEqualTo(2000000000); + assertThat(rs.getInt("DECIMAL_DIGITS")).isEqualTo(10); + assertThat(rs.getString("IS_AUTOINCREMENT")).isEqualTo("NO"); + assertThat(rs.next()).isFalse(); + + rs = meta.getColumns(null, null, "test", "sn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("sn"); + assertThat(rs.getString("IS_NULLABLE")).isEqualTo("NO"); + assertThat(rs.getInt("DATA_TYPE")).isEqualTo(Types.VARCHAR); + assertThat(rs.getString("TYPE_NAME")).isEqualTo(""); + assertThat(rs.getInt("COLUMN_SIZE")).isEqualTo(2000000000); + assertThat(rs.getInt("DECIMAL_DIGITS")).isEqualTo(10); + assertThat(rs.getString("COLUMN_DEF")).isNull(); + assertThat(rs.next()).isFalse(); + + rs = meta.getColumns(null, null, "test", "intvalue"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("intvalue"); + assertThat(rs.getInt("DATA_TYPE")).isEqualTo(Types.INTEGER); + assertThat(rs.getString("TYPE_NAME")).isEqualTo("INTEGER"); + assertThat(rs.getString("IS_NULLABLE")).isEqualTo("YES"); + assertThat(rs.getInt("COLUMN_SIZE")).isEqualTo(5); + assertThat(rs.getInt("DECIMAL_DIGITS")).isEqualTo(0); + assertThat(rs.getString("COLUMN_DEF")).isNull(); + assertThat(rs.next()).isFalse(); + + rs = meta.getColumns(null, null, "test", "realvalue"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("realvalue"); + assertThat(rs.getInt("DATA_TYPE")).isEqualTo(Types.FLOAT); + assertThat(rs.getString("TYPE_NAME")).isEqualTo("REAL"); + assertThat(rs.getString("IS_NULLABLE")).isEqualTo("YES"); + assertThat(rs.getInt("COLUMN_SIZE")).isEqualTo(11); + assertThat(rs.getInt("DECIMAL_DIGITS")).isEqualTo(3); + assertThat(rs.getString("COLUMN_DEF")).isNull(); + assertThat(rs.next()).isFalse(); + + rs = meta.getColumns(null, null, "test", "charvalue"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("charvalue"); + assertThat(rs.getInt("DATA_TYPE")).isEqualTo(Types.VARCHAR); + assertThat(rs.getString("TYPE_NAME")).isEqualTo("VARCHAR"); + assertThat(rs.getString("IS_NULLABLE")).isEqualTo("YES"); + assertThat(rs.getInt("COLUMN_SIZE")).isEqualTo(21); + assertThat(rs.getInt("DECIMAL_DIGITS")).isEqualTo(0); + assertThat(rs.getString("COLUMN_DEF")).isNull(); + assertThat(rs.next()).isFalse(); + + rs = meta.getColumns(null, null, "test", "%"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("id"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("fn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("sn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("intvalue"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("realvalue"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("charvalue"); + assertThat(rs.next()).isFalse(); + + rs = meta.getColumns(null, null, "test", "%n"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("fn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("sn"); + assertThat(rs.next()).isFalse(); + + rs = meta.getColumns(null, null, "test%", "%"); + // TABLE "test" + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("test"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("id"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("test"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("fn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("test"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("sn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("test"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("intvalue"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("test"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("realvalue"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("test"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("charvalue"); + // VIEW "testView" + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("testView"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("id"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("testView"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("fn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("testView"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("sn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("testView"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("intvalue"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("testView"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("realvalue"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("testView"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("charvalue"); + assertThat(rs.next()).isFalse(); + + rs = meta.getColumns(null, null, "%", "%"); + // SYSTEM TABLE "sqlite_schema" + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("sqlite_schema"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("type"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("name"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("tbl_name"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("rootpage"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("sql"); + // TABLE "test" + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("test"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("id"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("fn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("sn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("intvalue"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("realvalue"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("charvalue"); + // VIEW "testView" + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("testView"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("id"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("fn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("sn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("intvalue"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("realvalue"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("charvalue"); + assertThat(rs.next()).isFalse(); + + rs = meta.getColumns(null, null, "doesnotexist", "%"); + assertThat(rs.next()).isFalse(); + assertThat(rs.getMetaData().getColumnCount()).isEqualTo(24); + + rs = meta.getColumns(null, null, "sqlite_schema", "%"); + // SYSTEM TABLE "sqlite_schema" + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("sqlite_schema"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("type"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("name"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("tbl_name"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("rootpage"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("sql"); + } + + @Test + public void getTablesTypeWithQuote() throws SQLException { + // a type containing a single quote must be treated as a literal, not break out of the + // TABLE_TYPE IN (...) list; 'X' matches nothing so the result must be empty + try (ResultSet rs = meta.getTables(null, null, null, new String[] {"X') OR ('1'='1"})) { + assertThat(rs.next()).isFalse(); + } + } + + @Test + public void getImportedKeysCatalogSchemaWithQuote() throws SQLException { + stat.executeUpdate("create table parent (id integer primary key)"); + stat.executeUpdate( + "create table child1 (id integer primary key, pid integer, foreign key(pid) references parent(id))"); + // a catalog/schema containing a single quote must be carried through as a literal, + // not break out of the surrounding string in the generated metadata query + try (ResultSet rs = meta.getImportedKeys("ca'talog", "sch'ema", "child1")) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("PKTABLE_CAT")).isEqualTo("ca'talog"); + assertThat(rs.getString("PKTABLE_SCHEM")).isEqualTo("sch'ema"); + } + } + + @Test + public void getExportedKeysCatalogSchemaWithQuote() throws SQLException { + stat.executeUpdate("create table parent (id integer primary key)"); + stat.executeUpdate( + "create table child1 (id integer primary key, pid integer, foreign key(pid) references parent(id))"); + try (ResultSet rs = meta.getExportedKeys("ca'talog", "sch'ema", "parent")) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("PKTABLE_CAT")).isEqualTo("ca'talog"); + assertThat(rs.getString("PKTABLE_SCHEM")).isEqualTo("sch'ema"); + } + } + + @Test + public void getExportedKeysTableNameWithQuote() throws SQLException { + // the primary-key table name is read back from sqlite_schema and put into PKTABLE_NAME; + // a name containing a single quote must stay a literal, not break out of the generated + // query + stat.executeUpdate("create table \"o'parent\" (id integer primary key)"); + stat.executeUpdate( + "create table child1 (id integer primary key, pid integer, foreign key(pid) references \"o'parent\"(id))"); + try (ResultSet rs = meta.getExportedKeys(null, null, "o'parent")) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("PKTABLE_NAME")).isEqualTo("o'parent"); + assertThat(rs.getString("FKTABLE_NAME")).isEqualTo("child1"); + } + } + + @Test + public void getCrossReferenceCatalogSchemaWithQuote() throws SQLException { + stat.executeUpdate("create table parent (id integer primary key)"); + stat.executeUpdate( + "create table child1 (id integer primary key, pid integer, foreign key(pid) references parent(id))"); + try (ResultSet rs = + meta.getCrossReference("ca'talog", "sch'ema", "parent", null, null, "child1")) { + assertThat(rs.next()).isFalse(); + } + } + + @Test + public void getColumnsTableNameWithQuote() throws SQLException { + stat.executeUpdate("create table \"o'brien\" (id integer, name text)"); + + ResultSet rs = meta.getColumns(null, null, "o'brien", "%"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("o'brien"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("id"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("o'brien"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("name"); + assertThat(rs.next()).isFalse(); + } + + @Test + public void getColumnsPrecisionScale() throws SQLException { + stat.executeUpdate("create table gh_1215 (n numeric ( 10 , 5 ), d decimal ( 10 ))"); + + ResultSet rs = meta.getColumns(null, null, "gh_1215", "%"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("n"); + assertThat(rs.getString("TYPE_NAME")).isEqualTo("NUMERIC"); + assertThat(rs.getString("COLUMN_SIZE")).isEqualTo("15"); + assertThat(rs.getString("DECIMAL_DIGITS")).isEqualTo("5"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("d"); + assertThat(rs.getString("TYPE_NAME")).isEqualTo("DECIMAL"); + assertThat(rs.getString("COLUMN_SIZE")).isEqualTo("10"); + assertThat(rs.getString("DECIMAL_DIGITS")).isEqualTo("0"); + assertThat(rs.next()).isFalse(); + } + + @Test + public void getColumnsIncludingGenerated() throws SQLException { + stat.executeUpdate("create table gh_724 (i integer,j integer generated always as (i))"); + + ResultSet rs = meta.getColumns(null, null, "gh_724", "%"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString(4)).as("first column is named 'i'").isEqualTo("i"); + assertThat(rs.getString(24)).as("first column is not generated").isEqualTo("NO"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString(4)).as("second column is named 'j'").isEqualTo("j"); + assertThat(rs.getString(24)).as("second column is generated").isEqualTo("YES"); + assertThat(rs.next()).isFalse(); + } + + @Test + @DisplayName( + "Issue #1132 - Generated columns with stored in SQLite are not marked as generated") + public void getColumnsIncludingGeneratedStored() throws SQLException { + stat.executeUpdate( + "create table foo(" + + "\n" + + " id integer primary key," + + "\n" + + " bar int not null generated always as (id + 1) stored" + + "\n" + + ");"); + + ResultSet rs = meta.getColumns(null, null, "foo", "%"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString(4)).as("first column is named 'id'").isEqualTo("id"); + assertThat(rs.getString(24)).as("first column is generated").isEqualTo("NO"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString(4)).as("second column is named 'bar'").isEqualTo("bar"); + assertThat(rs.getString(24)).as("second column is generated").isEqualTo("YES"); + assertThat(rs.next()).isFalse(); + } + + @Test + public void getColumnsWithEscape() throws SQLException { + stat.executeUpdate("create table wildcard(col1 integer, co_1 integer, 'co%1' integer)"); + + String esc = meta.getSearchStringEscape(); + try (ResultSet rs = + meta.getColumns(null, null, "wildcard", "co_1".replace("_", esc + "_"))) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("co_1"); + assertThat(rs.next()).isFalse(); + } + try (ResultSet rs = + meta.getColumns(null, null, "wildcard", "co%1".replace("%", esc + "%"))) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("co%1"); + assertThat(rs.next()).isFalse(); + } + } + + @Test + public void numberOfgetImportedKeysCols() throws SQLException { + + stat.executeUpdate("create table parent (id1 integer, id2 integer, primary key(id1, id2))"); + stat.executeUpdate( + "create table child1 (id1 integer, id2 integer, foreign key(id1) references parent(id1), foreign key(id2) references parent(id2))"); + stat.executeUpdate( + "create table child2 (id1 integer, id2 integer, foreign key(id2, id1) references parent(id2, id1))"); + + ResultSet importedKeys = meta.getImportedKeys(null, null, "child1"); + + // child1: 1st fk (simple) + assertThat(importedKeys.next()).isTrue(); + assertThat(importedKeys.getString("PKTABLE_NAME")).isEqualTo("parent"); + assertThat(importedKeys.getString("PKCOLUMN_NAME")).isEqualTo("id2"); + assertThat(importedKeys.getString("PK_NAME")).isNotNull(); + assertThat(importedKeys.getString("FK_NAME")).isNotNull(); + assertThat(importedKeys.getString("FKTABLE_NAME")).isEqualTo("child1"); + assertThat(importedKeys.getString("FKCOLUMN_NAME")).isEqualTo("id2"); + + // child1: 2nd fk (simple) + assertThat(importedKeys.next()).isTrue(); + assertThat(importedKeys.getString("PKTABLE_NAME")).isEqualTo("parent"); + assertThat(importedKeys.getString("PKCOLUMN_NAME")).isEqualTo("id1"); + assertThat(importedKeys.getString("PK_NAME")).isNotNull(); + assertThat(importedKeys.getString("FK_NAME")).isNotNull(); + assertThat(importedKeys.getString("FKTABLE_NAME")).isEqualTo("child1"); + assertThat(importedKeys.getString("FKCOLUMN_NAME")).isEqualTo("id1"); + + assertThat(importedKeys.next()).isFalse(); + + importedKeys = meta.getImportedKeys(null, null, "child2"); + + // child2: 1st fk (composite) + assertThat(importedKeys.next()).isTrue(); + assertThat(importedKeys.getString("PKTABLE_NAME")).isEqualTo("parent"); + assertThat(importedKeys.getString("PKCOLUMN_NAME")).isEqualTo("id2"); + assertThat(importedKeys.getString("PK_NAME")).isNotNull(); + assertThat(importedKeys.getString("FK_NAME")).isNotNull(); + assertThat(importedKeys.getString("FKTABLE_NAME")).isEqualTo("child2"); + assertThat(importedKeys.getString("FKCOLUMN_NAME")).isEqualTo("id2"); + + assertThat(importedKeys.next()).isTrue(); + assertThat(importedKeys.getString("PKTABLE_NAME")).isEqualTo("parent"); + assertThat(importedKeys.getString("PKCOLUMN_NAME")).isEqualTo("id1"); + assertThat(importedKeys.getString("PK_NAME")).isNotNull(); + assertThat(importedKeys.getString("FK_NAME")).isNotNull(); + assertThat(importedKeys.getString("FKTABLE_NAME")).isEqualTo("child2"); + assertThat(importedKeys.getString("FKCOLUMN_NAME")).isEqualTo("id1"); + + assertThat(importedKeys.next()).isFalse(); + + importedKeys.close(); + } + + @Test + public void numberOfgetExportedKeysCols() throws SQLException { + + stat.executeUpdate("create table parent (id1 integer, id2 integer, primary key(id1, id2))"); + stat.executeUpdate( + "create table child1 (id1 integer, id2 integer,\r\n foreign\tkey(id1) references parent(id1), foreign key(id2) references parent(id2))"); + stat.executeUpdate( + "create table child2 (id1 integer, id2 integer, foreign key(id2, id1) references parent(id2, id1))"); + + ResultSet exportedKeys = meta.getExportedKeys(null, null, "parent"); + + // 1st fk (simple) - child1 + assertThat(exportedKeys.next()).isTrue(); + assertThat(exportedKeys.getString("PKTABLE_NAME")).isEqualTo("parent"); + assertThat(exportedKeys.getString("PKCOLUMN_NAME")).isEqualTo("id2"); + assertThat(exportedKeys.getString("PK_NAME")).isNotNull(); + assertThat(exportedKeys.getString("FK_NAME")).isNotNull(); + assertThat(exportedKeys.getString("FKTABLE_NAME")).isEqualTo("child1"); + assertThat(exportedKeys.getString("FKCOLUMN_NAME")).isEqualTo("id2"); + + // 2nd fk (simple) - child1 + assertThat(exportedKeys.next()).isTrue(); + assertThat(exportedKeys.getString("PKTABLE_NAME")).isEqualTo("parent"); + assertThat(exportedKeys.getString("PKCOLUMN_NAME")).isEqualTo("id1"); + assertThat(exportedKeys.getString("PK_NAME")).isNotNull(); + assertThat(exportedKeys.getString("FK_NAME")).isNotNull(); + assertThat(exportedKeys.getString("FKTABLE_NAME")).isEqualTo("child1"); + assertThat(exportedKeys.getString("FKCOLUMN_NAME")).isEqualTo("id1"); + + // 3rd fk (composite) - child2 + assertThat(exportedKeys.next()).isTrue(); + assertThat(exportedKeys.getString("PKTABLE_NAME")).isEqualTo("parent"); + assertThat(exportedKeys.getString("PKCOLUMN_NAME")).isEqualTo("id2"); + assertThat(exportedKeys.getString("PK_NAME")).isNotNull(); + assertThat(exportedKeys.getString("FK_NAME")).isNotNull(); + assertThat(exportedKeys.getString("FKTABLE_NAME")).isEqualTo("child2"); + assertThat(exportedKeys.getString("FKCOLUMN_NAME")).isEqualTo("id2"); + + assertThat(exportedKeys.next()).isTrue(); + assertThat(exportedKeys.getString("PKTABLE_NAME")).isEqualTo("parent"); + assertThat(exportedKeys.getString("PKCOLUMN_NAME")).isEqualTo("id1"); + assertThat(exportedKeys.getString("PK_NAME")).isNotNull(); + assertThat(exportedKeys.getString("FK_NAME")).isNotNull(); + assertThat(exportedKeys.getString("FKTABLE_NAME")).isEqualTo("child2"); + assertThat(exportedKeys.getString("FKCOLUMN_NAME")).isEqualTo("id1"); + + assertThat(exportedKeys.next()).isFalse(); + + exportedKeys.close(); + } + + @Test + public void getExportedKeysColsForNamedKeys() throws SQLException { + + ResultSet exportedKeys; + + // 1. Check for named primary keys + // SQL is deliberately in uppercase, to make sure case-sensitivity is maintained + stat.executeUpdate( + "CREATE TABLE PARENT1 (ID1 INTEGER, DATA1 INTEGER, CONSTRAINT PK_PARENT PRIMARY KEY (ID1))"); + stat.executeUpdate( + "CREATE TABLE CHILD1 (ID1 INTEGER, DATA2 INTEGER, FOREIGN KEY(ID1) REFERENCES PARENT1(ID1))"); + + exportedKeys = meta.getExportedKeys(null, null, "PARENT1"); + + assertThat(exportedKeys.next()).isTrue(); + assertThat(exportedKeys.getString("PKTABLE_NAME")).isEqualTo("PARENT1"); + assertThat(exportedKeys.getString("PKCOLUMN_NAME")).isEqualTo("ID1"); + assertThat(exportedKeys.getString("PK_NAME")).isEqualTo("PK_PARENT"); + assertThat(exportedKeys.getString("FK_NAME")).isEqualTo(""); + assertThat(exportedKeys.getString("FKTABLE_NAME")).isEqualTo("CHILD1"); + assertThat(exportedKeys.getString("FKCOLUMN_NAME")).isEqualTo("ID1"); + + assertThat(exportedKeys.next()).isFalse(); + + exportedKeys.close(); + + // 2. Check for named foreign keys + // SQL is deliberately in mixed case, to make sure case-sensitivity is maintained + stat.executeUpdate("CREATE TABLE Parent2 (Id1 INTEGER, DATA1 INTEGER, PRIMARY KEY (Id1))"); + stat.executeUpdate( + "CREATE TABLE Child2 (Id1 INTEGER, DATA2 INTEGER, CONSTRAINT FK_Child2 FOREIGN KEY(Id1) REFERENCES Parent2(Id1))"); + + exportedKeys = meta.getExportedKeys(null, null, "Parent2"); + + assertThat(exportedKeys.next()).isTrue(); + assertThat(exportedKeys.getString("PKTABLE_NAME")).isEqualTo("Parent2"); + assertThat(exportedKeys.getString("PKCOLUMN_NAME")).isEqualTo("Id1"); + assertThat(exportedKeys.getString("PK_NAME")).isEqualTo(""); + assertThat(exportedKeys.getString("FK_NAME")).isEqualTo("FK_Child2"); + assertThat(exportedKeys.getString("FKTABLE_NAME")).isEqualTo("Child2"); + assertThat(exportedKeys.getString("FKCOLUMN_NAME")).isEqualTo("Id1"); + + assertThat(exportedKeys.next()).isFalse(); + + exportedKeys.close(); + } + + @Test + public void getImportedKeysMultipleColumns() throws SQLException { + ResultSet importedKeys; + stat.executeUpdate( + "CREATE TABLE PONE ( ID_FIRST_ONE INTEGER NOT NULL, ID_SECOND_ONE INTEGER NOT NULL, ADDITIONAL_ONE TEXT, CONSTRAINT PONE_PK PRIMARY KEY (ID_FIRST_ONE, ID_SECOND_ONE) )"); + stat.executeUpdate( + "CREATE TABLE PTWO ( ID_FIRST_TWO INTEGER NOT NULL, ID_SECOND_TWO INTEGER NOT NULL, ADDITIONAL_TWO TEXT, CONSTRAINT PTWO_PK PRIMARY KEY (ID_FIRST_TWO, ID_SECOND_TWO) )"); + stat.executeUpdate( + "CREATE TABLE CHILD ( ID_CHILD INTEGER NOT NULL, ID_CHILD_TO_ONE INTEGER NOT NULL, ID_CHILD_TO_TWO INTEGER NOT NULL, CONSTRAINT CHILD_PK PRIMARY KEY (ID_CHILD, ID_CHILD_TO_ONE), CONSTRAINT \"PONE_FK01\" FOREIGN KEY (ID_CHILD, ID_CHILD_TO_ONE) REFERENCES PONE (ID_FIRST_ONE, ID_SECOND_ONE) ON DELETE CASCADE, CONSTRAINT PTWO_FK02 FOREIGN KEY (ID_CHILD, ID_CHILD_TO_TWO) REFERENCES PTWO (ID_FIRST_TWO, ID_SECOND_TWO) )"); + + importedKeys = meta.getImportedKeys(null, null, "CHILD"); + + assertThat(importedKeys.next()).isTrue(); + assertThat(importedKeys.getString("PKTABLE_NAME")).isEqualTo("PONE"); + assertThat(importedKeys.getString("PKCOLUMN_NAME")).isEqualTo("ID_FIRST_ONE"); + assertThat(importedKeys.getString("PK_NAME")).isEqualTo("PONE_PK"); + assertThat(importedKeys.getString("FK_NAME")).isEqualTo("PONE_FK01"); + assertThat(importedKeys.getString("FKTABLE_NAME")).isEqualTo("CHILD"); + assertThat(importedKeys.getString("FKCOLUMN_NAME")).isEqualTo("ID_CHILD"); + + assertThat(importedKeys.next()).isTrue(); + assertThat(importedKeys.getString("PKTABLE_NAME")).isEqualTo("PONE"); + assertThat(importedKeys.getString("PKCOLUMN_NAME")).isEqualTo("ID_SECOND_ONE"); + assertThat(importedKeys.getString("PK_NAME")).isEqualTo("PONE_PK"); + assertThat(importedKeys.getString("FK_NAME")).isEqualTo("PONE_FK01"); + assertThat(importedKeys.getString("FKTABLE_NAME")).isEqualTo("CHILD"); + assertThat(importedKeys.getString("FKCOLUMN_NAME")).isEqualTo("ID_CHILD_TO_ONE"); + + assertThat(importedKeys.next()).isTrue(); + assertThat(importedKeys.getString("PKTABLE_NAME")).isEqualTo("PTWO"); + assertThat(importedKeys.getString("PKCOLUMN_NAME")).isEqualTo("ID_FIRST_TWO"); + assertThat(importedKeys.getString("PK_NAME")).isEqualTo("PTWO_PK"); + assertThat(importedKeys.getString("FK_NAME")).isEqualTo("PTWO_FK02"); + assertThat(importedKeys.getString("FKTABLE_NAME")).isEqualTo("CHILD"); + assertThat(importedKeys.getString("FKCOLUMN_NAME")).isEqualTo("ID_CHILD"); + + assertThat(importedKeys.next()).isTrue(); + assertThat(importedKeys.getString("PKTABLE_NAME")).isEqualTo("PTWO"); + assertThat(importedKeys.getString("PKCOLUMN_NAME")).isEqualTo("ID_SECOND_TWO"); + assertThat(importedKeys.getString("PK_NAME")).isEqualTo("PTWO_PK"); + assertThat(importedKeys.getString("FK_NAME")).isEqualTo("PTWO_FK02"); + assertThat(importedKeys.getString("FKTABLE_NAME")).isEqualTo("CHILD"); + assertThat(importedKeys.getString("FKCOLUMN_NAME")).isEqualTo("ID_CHILD_TO_TWO"); + + assertThat(importedKeys.next()).isFalse(); + importedKeys.close(); + } + + @Test + public void getImportedKeysColsForNamedKeys() throws SQLException { + + ResultSet importedKeys; + + // 1. Check for named primary keys + // SQL is deliberately in uppercase, to make sure case-sensitivity is maintained + stat.executeUpdate( + "CREATE TABLE PARENT1 (ID1 INTEGER, DATA1 INTEGER, CONSTRAINT PK_PARENT PRIMARY KEY (ID1))"); + stat.executeUpdate( + "CREATE TABLE CHILD1 (ID1 INTEGER, DATA2 INTEGER, FOREIGN KEY(ID1) REFERENCES PARENT1(ID1))"); + + importedKeys = meta.getImportedKeys(null, null, "CHILD1"); + + assertThat(importedKeys.next()).isTrue(); + assertThat(importedKeys.getString("PKTABLE_NAME")).isEqualTo("PARENT1"); + assertThat(importedKeys.getString("PKCOLUMN_NAME")).isEqualTo("ID1"); + assertThat(importedKeys.getString("PK_NAME")).isEqualTo("PK_PARENT"); + assertThat(importedKeys.getString("FK_NAME")).isEqualTo(""); + assertThat(importedKeys.getString("FKTABLE_NAME")).isEqualTo("CHILD1"); + assertThat(importedKeys.getString("FKCOLUMN_NAME")).isEqualTo("ID1"); + + assertThat(importedKeys.next()).isFalse(); + + importedKeys.close(); + + // 2. Check for named foreign keys + // SQL is deliberately in mixed case, to make sure case-sensitivity is maintained + stat.executeUpdate("CREATE TABLE Parent2 (Id1 INTEGER, DATA1 INTEGER, PRIMARY KEY (Id1))"); + stat.executeUpdate( + "CREATE TABLE Child2 (Id1 INTEGER, DATA2 INTEGER, " + + "CONSTRAINT FK_Child2 FOREIGN KEY(Id1) REFERENCES Parent2(Id1))"); + + importedKeys = meta.getImportedKeys(null, null, "Child2"); + + assertThat(importedKeys.next()).isTrue(); + assertThat(importedKeys.getString("PKTABLE_NAME")).isEqualTo("Parent2"); + assertThat(importedKeys.getString("PKCOLUMN_NAME")).isEqualTo("Id1"); + assertThat(importedKeys.getString("PK_NAME")).isEqualTo(""); + assertThat(importedKeys.getString("FK_NAME")).isEqualTo("FK_Child2"); + assertThat(importedKeys.getString("FKTABLE_NAME")).isEqualTo("Child2"); + assertThat(importedKeys.getString("FKCOLUMN_NAME")).isEqualTo("Id1"); + + assertThat(importedKeys.next()).isFalse(); + + importedKeys.close(); + } + + @Test + public void getImportedKeysColsForMixedCaseDefinition() throws SQLException { + + ResultSet importedKeys; + + // SQL is deliberately in mixed-case, to make sure case-sensitivity is maintained + stat.executeUpdate( + "CREATE TABLE PARENT1 (ID1 INTEGER, DATA1 INTEGER, CONSTRAINT PK_PARENT PRIMARY KEY (ID1))"); + stat.executeUpdate( + "CREATE TABLE CHILD1 (ID1 INTEGER, DATA2 INTEGER, " + + "CONSTRAINT FK_Parent1 FOREIGN KEY(ID1) REFERENCES Parent1(Id1))"); + + importedKeys = meta.getImportedKeys(null, null, "CHILD1"); + + assertThat(importedKeys.next()).isTrue(); + assertThat(importedKeys.getString("PKTABLE_NAME")).isEqualTo("Parent1"); + assertThat(importedKeys.getString("PKCOLUMN_NAME")).isEqualTo("Id1"); + assertThat(importedKeys.getString("PK_NAME")).isEqualTo("PK_PARENT"); + assertThat(importedKeys.getString("FK_NAME")).isEqualTo("FK_Parent1"); + assertThat(importedKeys.getString("FKTABLE_NAME")).isEqualTo("CHILD1"); + assertThat(importedKeys.getString("FKCOLUMN_NAME")).isEqualTo("ID1"); + + assertThat(importedKeys.next()).isFalse(); + + importedKeys.close(); + } + + @Test + public void getImportedKeysColsForMultipleImports() throws SQLException { + + ResultSet importedKeys; + + stat.executeUpdate( + "CREATE TABLE PARENT1 (ID1 INTEGER, DATA1 INTEGER, CONSTRAINT PK_PARENT1 PRIMARY KEY (ID1))"); + stat.executeUpdate( + "CREATE TABLE PARENT2 (ID2 INTEGER, DATA2 INTEGER, CONSTRAINT PK_PARENT2 PRIMARY KEY (ID2))"); + stat.executeUpdate( + "CREATE TABLE CHILD1 (ID1 INTEGER, ID2 INTEGER, " + + "CONSTRAINT FK_PARENT1 FOREIGN KEY(ID1) REFERENCES PARENT1(ID1), " + + "CONSTRAINT FK_PARENT2 FOREIGN KEY(ID2) REFERENCES PARENT2(ID2))"); + + importedKeys = meta.getImportedKeys(null, null, "CHILD1"); + + assertThat(importedKeys.next()).isTrue(); + assertThat(importedKeys.getString("PKTABLE_NAME")).isEqualTo("PARENT1"); + assertThat(importedKeys.getString("PKCOLUMN_NAME")).isEqualTo("ID1"); + assertThat(importedKeys.getString("PK_NAME")).isEqualTo("PK_PARENT1"); + assertThat(importedKeys.getString("FK_NAME")).isEqualTo("FK_PARENT1"); + assertThat(importedKeys.getString("FKTABLE_NAME")).isEqualTo("CHILD1"); + assertThat(importedKeys.getString("FKCOLUMN_NAME")).isEqualTo("ID1"); + + assertThat(importedKeys.next()).isTrue(); + assertThat(importedKeys.getString("PKTABLE_NAME")).isEqualTo("PARENT2"); + assertThat(importedKeys.getString("PKCOLUMN_NAME")).isEqualTo("ID2"); + assertThat(importedKeys.getString("PK_NAME")).isEqualTo("PK_PARENT2"); + assertThat(importedKeys.getString("FK_NAME")).isEqualTo("FK_PARENT2"); + assertThat(importedKeys.getString("FKTABLE_NAME")).isEqualTo("CHILD1"); + assertThat(importedKeys.getString("FKCOLUMN_NAME")).isEqualTo("ID2"); + + assertThat(importedKeys.next()).isFalse(); + + importedKeys.close(); + + // Unnamed foreign keys and unnamed primary keys + stat.executeUpdate("CREATE TABLE PARENT3 (ID3 INTEGER, DATA3 INTEGER, PRIMARY KEY (ID3))"); + stat.executeUpdate( + "CREATE TABLE PARENT4 (ID4 INTEGER, DATA4 INTEGER, CONSTRAINT PK_PARENT4 PRIMARY KEY (ID4))"); + stat.executeUpdate( + "CREATE TABLE CHILD2 (ID3 INTEGER, ID4 INTEGER, " + + "FOREIGN KEY(ID3) REFERENCES PARENT3(ID3), " + + "CONSTRAINT FK_PARENT4 FOREIGN KEY(ID4) REFERENCES PARENT4(ID4))"); + + importedKeys = meta.getImportedKeys(null, null, "CHILD2"); + + assertThat(importedKeys.next()).isTrue(); + assertThat(importedKeys.getString("PKTABLE_NAME")).isEqualTo("PARENT3"); + assertThat(importedKeys.getString("PKCOLUMN_NAME")).isEqualTo("ID3"); + assertThat(importedKeys.getString("PK_NAME")).isEqualTo(""); + assertThat(importedKeys.getString("FK_NAME")).isEqualTo(""); + assertThat(importedKeys.getString("FKTABLE_NAME")).isEqualTo("CHILD2"); + assertThat(importedKeys.getString("FKCOLUMN_NAME")).isEqualTo("ID3"); + + assertThat(importedKeys.next()).isTrue(); + assertThat(importedKeys.getString("PKTABLE_NAME")).isEqualTo("PARENT4"); + assertThat(importedKeys.getString("PKCOLUMN_NAME")).isEqualTo("ID4"); + assertThat(importedKeys.getString("PK_NAME")).isEqualTo("PK_PARENT4"); + assertThat(importedKeys.getString("FK_NAME")).isEqualTo("FK_PARENT4"); + assertThat(importedKeys.getString("FKTABLE_NAME")).isEqualTo("CHILD2"); + assertThat(importedKeys.getString("FKCOLUMN_NAME")).isEqualTo("ID4"); + + assertThat(importedKeys.next()).isFalse(); + + importedKeys.close(); + } + + @Test + public void getImportedKeysCols2() throws SQLException { + + stat.executeUpdate( + "CREATE TABLE Authors (Id INTEGER NOT NULL, Name VARCHAR(20) NOT NULL, " + + "CONSTRAINT PK_Authors PRIMARY KEY (Id)," + + " CONSTRAINT CHECK_UPPERCASE_Name CHECK (Name=UPPER(Name)))"); + stat.executeUpdate( + "CREATE TABLE Books (Id INTEGER NOT NULL, Title VARCHAR(255) NOT NULL, PreviousEditionId INTEGER," + + "CONSTRAINT PK_Books PRIMARY KEY (Id), " + + "CONSTRAINT FK_PreviousEdition FOREIGN KEY(PreviousEditionId) REFERENCES Books (Id))"); + stat.executeUpdate( + "CREATE TABLE BookAuthors (BookId INTEGER NOT NULL, AuthorId INTEGER NOT NULL, " + + "CONSTRAINT FK_Y_Book FOREIGN KEY (BookId) REFERENCES Books (Id), " + + "CONSTRAINT FK_Z_Author FOREIGN KEY (AuthorId) REFERENCES Authors (Id)) "); + + ResultSet importedKeys; + + importedKeys = meta.getImportedKeys(null, null, "BookAuthors"); + + assertThat(importedKeys.next()).isTrue(); + assertThat(importedKeys.getString("PKTABLE_NAME")).isEqualTo("Authors"); + assertThat(importedKeys.getString("PKCOLUMN_NAME")).isEqualTo("Id"); + assertThat(importedKeys.getString("PK_NAME")).isEqualTo("PK_Authors"); + assertThat(importedKeys.getString("FK_NAME")).isEqualTo("FK_Z_Author"); + assertThat(importedKeys.getString("FKTABLE_NAME")).isEqualTo("BookAuthors"); + assertThat(importedKeys.getString("FKCOLUMN_NAME")).isEqualTo("AuthorId"); + + assertThat(importedKeys.next()).isTrue(); + assertThat(importedKeys.getString("PKTABLE_NAME")).isEqualTo("Books"); + assertThat(importedKeys.getString("PKCOLUMN_NAME")).isEqualTo("Id"); + assertThat(importedKeys.getString("PK_NAME")).isEqualTo("PK_Books"); + assertThat(importedKeys.getString("FK_NAME")).isEqualTo("FK_Y_Book"); + assertThat(importedKeys.getString("FKTABLE_NAME")).isEqualTo("BookAuthors"); + assertThat(importedKeys.getString("FKCOLUMN_NAME")).isEqualTo("BookId"); + + assertThat(importedKeys.next()).isFalse(); + + importedKeys.close(); + + ResultSet exportedKeys; + + exportedKeys = meta.getExportedKeys(null, null, "Authors"); + + assertThat(exportedKeys.next()).isTrue(); + assertThat(exportedKeys.getString("PKTABLE_NAME")).isEqualTo("Authors"); + assertThat(exportedKeys.getString("PKCOLUMN_NAME")).isEqualTo("Id"); + assertThat(exportedKeys.getString("PK_NAME")).isEqualTo("PK_Authors"); + assertThat(exportedKeys.getString("FK_NAME")).isEqualTo("FK_Z_Author"); + assertThat(exportedKeys.getString("FKTABLE_NAME")).isEqualTo("BookAuthors"); + assertThat(exportedKeys.getString("FKCOLUMN_NAME")).isEqualTo("AuthorId"); + + assertThat(exportedKeys.next()).isFalse(); + + exportedKeys.close(); + + exportedKeys = meta.getExportedKeys(null, null, "Books"); + + assertThat(exportedKeys.next()).isTrue(); + assertThat(exportedKeys.getString("PKTABLE_NAME")).isEqualTo("Books"); + assertThat(exportedKeys.getString("PKCOLUMN_NAME")).isEqualTo("Id"); + assertThat(exportedKeys.getString("PK_NAME")).isEqualTo("PK_Books"); + assertThat(exportedKeys.getString("FK_NAME")).isEqualTo("FK_Y_Book"); + assertThat(exportedKeys.getString("FKTABLE_NAME")).isEqualTo("BookAuthors"); + assertThat(exportedKeys.getString("FKCOLUMN_NAME")).isEqualTo("BookId"); + + assertThat(exportedKeys.next()).isTrue(); + assertThat(exportedKeys.getString("PKTABLE_NAME")).isEqualTo("Books"); + assertThat(exportedKeys.getString("PKCOLUMN_NAME")).isEqualTo("Id"); + assertThat(exportedKeys.getString("PK_NAME")).isEqualTo("PK_Books"); + assertThat(exportedKeys.getString("FK_NAME")).isEqualTo("FK_PreviousEdition"); // ??? + assertThat(exportedKeys.getString("FKTABLE_NAME")).isEqualTo("Books"); + assertThat(exportedKeys.getString("FKCOLUMN_NAME")).isEqualTo("PreviousEditionId"); + + assertThat(exportedKeys.next()).isFalse(); + + exportedKeys.close(); + } + + @Test + public void getExportedKeysColsForMultipleImports() throws SQLException { + + ResultSet exportedKeys; + + stat.executeUpdate( + "CREATE TABLE PARENT1 (ID1 INTEGER, ID2 INTEGER, CONSTRAINT PK_PARENT1 PRIMARY KEY (ID1))"); + stat.executeUpdate( + "CREATE TABLE CHILD1 (ID1 INTEGER, CONSTRAINT FK_PARENT1 FOREIGN KEY(ID1) REFERENCES PARENT1(ID1))"); + stat.executeUpdate( + "CREATE TABLE CHILD2 (ID2 INTEGER, CONSTRAINT FK_PARENT2 FOREIGN KEY(ID2) REFERENCES PARENT1(ID2))"); + + exportedKeys = meta.getExportedKeys(null, null, "PARENT1"); + + assertThat(exportedKeys.next()).isTrue(); + assertThat(exportedKeys.getString("PKTABLE_NAME")).isEqualTo("PARENT1"); + assertThat(exportedKeys.getString("PKCOLUMN_NAME")).isEqualTo("ID1"); + assertThat(exportedKeys.getString("PK_NAME")).isEqualTo("PK_PARENT1"); + assertThat(exportedKeys.getString("FK_NAME")).isEqualTo("FK_PARENT1"); + assertThat(exportedKeys.getString("FKTABLE_NAME")).isEqualTo("CHILD1"); + assertThat(exportedKeys.getString("FKCOLUMN_NAME")).isEqualTo("ID1"); + + assertThat(exportedKeys.next()).isTrue(); + assertThat(exportedKeys.getString("PKTABLE_NAME")).isEqualTo("PARENT1"); + assertThat(exportedKeys.getString("PKCOLUMN_NAME")).isEqualTo("ID2"); + assertThat(exportedKeys.getString("PK_NAME")).isEqualTo(""); + assertThat(exportedKeys.getString("FK_NAME")).isEqualTo("FK_PARENT2"); + assertThat(exportedKeys.getString("FKTABLE_NAME")).isEqualTo("CHILD2"); + assertThat(exportedKeys.getString("FKCOLUMN_NAME")).isEqualTo("ID2"); + + assertThat(exportedKeys.next()).isFalse(); + + exportedKeys.close(); + } + + @Test + public void getImportedKeysWithIncorrectReference() throws SQLException { + + stat.executeUpdate( + "create table child (id1 integer, id2 integer, foreign key(id1) references parent(id1))"); + + try (ResultSet importedKeys = meta.getImportedKeys(null, null, "child")) { + assertThat(importedKeys.next()).isTrue(); + + assertThat(importedKeys.getString("PKTABLE_NAME")).isEqualTo("parent"); + assertThat(importedKeys.getString("PKCOLUMN_NAME")).isEqualTo("id1"); + assertThat(importedKeys.getString("PK_NAME")).isNotNull(); + assertThat(importedKeys.getString("FK_NAME")).isNotNull(); + assertThat(importedKeys.getString("FKTABLE_NAME")).isEqualTo("child"); + assertThat(importedKeys.getString("FKCOLUMN_NAME")).isEqualTo("id1"); + + assertThat(importedKeys.next()).isFalse(); + } + } + + @Test + public void columnOrderOfgetTables() throws SQLException { + assumeFalse( + conn.getMetaData().getDriverName().contains("libSQL"), + "sqld rejects CREATE TEMP TABLE with SQL_PARSE_ERROR"); + + stat.executeUpdate( + "CREATE TABLE TABLE1 (ID1 INTEGER PRIMARY KEY AUTOINCREMENT, ID2 INTEGER)"); + stat.executeUpdate("CREATE TABLE TABLE2 (ID2 INTEGER, DATA2 VARCHAR(20))"); + stat.executeUpdate("CREATE TEMP TABLE TABLE3 (ID3 INTEGER, DATA3 VARCHAR(20))"); + stat.executeUpdate("CREATE VIEW VIEW1 (V1, V2) AS SELECT ID1, ID2 FROM TABLE1"); + + ResultSet rsTables = + meta.getTables( + null, + null, + null, + new String[] {"TABLE", "VIEW", "GLOBAL TEMPORARY", "SYSTEM TABLE"}); + + assertThat(rsTables.next()).isTrue(); + + // Check order of columns + ResultSetMetaData rsmeta = rsTables.getMetaData(); + assertThat(rsmeta.getColumnCount()).isEqualTo(10); + assertThat(rsmeta.getColumnName(1)).isEqualTo("TABLE_CAT"); + assertThat(rsmeta.getColumnName(2)).isEqualTo("TABLE_SCHEM"); + assertThat(rsmeta.getColumnName(3)).isEqualTo("TABLE_NAME"); + assertThat(rsmeta.getColumnName(4)).isEqualTo("TABLE_TYPE"); + assertThat(rsmeta.getColumnName(5)).isEqualTo("REMARKS"); + assertThat(rsmeta.getColumnName(6)).isEqualTo("TYPE_CAT"); + assertThat(rsmeta.getColumnName(7)).isEqualTo("TYPE_SCHEM"); + assertThat(rsmeta.getColumnName(8)).isEqualTo("TYPE_NAME"); + assertThat(rsmeta.getColumnName(9)).isEqualTo("SELF_REFERENCING_COL_NAME"); + assertThat(rsmeta.getColumnName(10)).isEqualTo("REF_GENERATION"); + + assertThat(rsTables.getString("TABLE_NAME")).isEqualTo("TABLE3"); + assertThat(rsTables.getString("TABLE_TYPE")).isEqualTo("GLOBAL TEMPORARY"); + + assertThat(rsTables.next()).isTrue(); + assertThat(rsTables.getString("TABLE_NAME")).isEqualTo("sqlite_schema"); + assertThat(rsTables.getString("TABLE_TYPE")).isEqualTo("SYSTEM TABLE"); + + assertThat(rsTables.next()).isTrue(); + assertThat(rsTables.getString("TABLE_NAME")).isEqualTo("sqlite_sequence"); + assertThat(rsTables.getString("TABLE_TYPE")).isEqualTo("SYSTEM TABLE"); + + assertThat(rsTables.next()).isTrue(); + assertThat(rsTables.getString("TABLE_NAME")).isEqualTo("TABLE1"); + assertThat(rsTables.getString("TABLE_TYPE")).isEqualTo("TABLE"); + + assertThat(rsTables.next()).isTrue(); + assertThat(rsTables.getString("TABLE_NAME")).isEqualTo("TABLE2"); + assertThat(rsTables.getString("TABLE_TYPE")).isEqualTo("TABLE"); + + assertThat(rsTables.next()).isTrue(); + assertThat(rsTables.next()).isTrue(); + assertThat(rsTables.getString("TABLE_NAME")).isEqualTo("VIEW1"); + assertThat(rsTables.getString("TABLE_TYPE")).isEqualTo("VIEW"); + + rsTables.close(); + } + + @Test + public void columnOrderOfgetTableTypes() throws SQLException { + ResultSet rs = meta.getTableTypes(); + assertThat(rs.next()).isTrue(); + ResultSetMetaData rsmeta = rs.getMetaData(); + assertThat(rsmeta.getColumnCount()).isEqualTo(1); + assertThat(rsmeta.getColumnName(1)).isEqualTo("TABLE_TYPE"); + } + + @Test + public void columnOrderOfgetTypeInfo() throws SQLException { + ResultSet rs = meta.getTypeInfo(); + assertThat(rs.next()).isTrue(); + ResultSetMetaData rsmeta = rs.getMetaData(); + assertThat(rsmeta.getColumnCount()).isEqualTo(18); + assertThat(rsmeta.getColumnName(1)).isEqualTo("TYPE_NAME"); + assertThat(rsmeta.getColumnName(2)).isEqualTo("DATA_TYPE"); + assertThat(rsmeta.getColumnName(3)).isEqualTo("PRECISION"); + assertThat(rsmeta.getColumnName(4)).isEqualTo("LITERAL_PREFIX"); + assertThat(rsmeta.getColumnName(5)).isEqualTo("LITERAL_SUFFIX"); + assertThat(rsmeta.getColumnName(6)).isEqualTo("CREATE_PARAMS"); + assertThat(rsmeta.getColumnName(7)).isEqualTo("NULLABLE"); + assertThat(rsmeta.getColumnName(8)).isEqualTo("CASE_SENSITIVE"); + assertThat(rsmeta.getColumnName(9)).isEqualTo("SEARCHABLE"); + assertThat(rsmeta.getColumnName(10)).isEqualTo("UNSIGNED_ATTRIBUTE"); + assertThat(rsmeta.getColumnName(11)).isEqualTo("FIXED_PREC_SCALE"); + assertThat(rsmeta.getColumnName(12)).isEqualTo("AUTO_INCREMENT"); + assertThat(rsmeta.getColumnName(13)).isEqualTo("LOCAL_TYPE_NAME"); + assertThat(rsmeta.getColumnName(14)).isEqualTo("MINIMUM_SCALE"); + assertThat(rsmeta.getColumnName(15)).isEqualTo("MAXIMUM_SCALE"); + assertThat(rsmeta.getColumnName(16)).isEqualTo("SQL_DATA_TYPE"); + assertThat(rsmeta.getColumnName(17)).isEqualTo("SQL_DATETIME_SUB"); + assertThat(rsmeta.getColumnName(18)).isEqualTo("NUM_PREC_RADIX"); + } + + @Test + public void columnOrderOfgetColumns() throws SQLException { + ResultSet rs = meta.getColumns(null, null, "test", null); + assertThat(rs.next()).isTrue(); + ResultSetMetaData rsmeta = rs.getMetaData(); + assertThat(rsmeta.getColumnCount()).isEqualTo(24); + assertThat(rsmeta.getColumnName(1)).isEqualTo("TABLE_CAT"); + assertThat(rsmeta.getColumnName(2)).isEqualTo("TABLE_SCHEM"); + assertThat(rsmeta.getColumnName(3)).isEqualTo("TABLE_NAME"); + assertThat(rsmeta.getColumnName(4)).isEqualTo("COLUMN_NAME"); + assertThat(rsmeta.getColumnName(5)).isEqualTo("DATA_TYPE"); + assertThat(rsmeta.getColumnName(6)).isEqualTo("TYPE_NAME"); + assertThat(rsmeta.getColumnName(7)).isEqualTo("COLUMN_SIZE"); + assertThat(rsmeta.getColumnName(8)).isEqualTo("BUFFER_LENGTH"); + assertThat(rsmeta.getColumnName(9)).isEqualTo("DECIMAL_DIGITS"); + assertThat(rsmeta.getColumnName(10)).isEqualTo("NUM_PREC_RADIX"); + assertThat(rsmeta.getColumnName(11)).isEqualTo("NULLABLE"); + assertThat(rsmeta.getColumnName(12)).isEqualTo("REMARKS"); + assertThat(rsmeta.getColumnName(13)).isEqualTo("COLUMN_DEF"); + assertThat(rsmeta.getColumnName(14)).isEqualTo("SQL_DATA_TYPE"); + assertThat(rsmeta.getColumnName(15)).isEqualTo("SQL_DATETIME_SUB"); + assertThat(rsmeta.getColumnName(16)).isEqualTo("CHAR_OCTET_LENGTH"); + assertThat(rsmeta.getColumnName(17)).isEqualTo("ORDINAL_POSITION"); + assertThat(rsmeta.getColumnName(18)).isEqualTo("IS_NULLABLE"); + assertThat(rsmeta.getColumnName(19)).isEqualTo("SCOPE_CATALOG"); + assertThat(rsmeta.getColumnName(20)).isEqualTo("SCOPE_SCHEMA"); + assertThat(rsmeta.getColumnName(21)).isEqualTo("SCOPE_TABLE"); + assertThat(rsmeta.getColumnName(22)).isEqualTo("SOURCE_DATA_TYPE"); + assertThat(rsmeta.getColumnName(23)).isEqualTo("IS_AUTOINCREMENT"); + assertThat(rsmeta.getColumnName(24)).isEqualTo("IS_GENERATEDCOLUMN"); + assertThat(rs.getString("COLUMN_NAME").toUpperCase()).isEqualTo("ID"); + assertThat(rs.getInt("ORDINAL_POSITION")).isEqualTo(1); + } + + // the following functions always return an empty resultset, so + // do not bother testing their parameters, only the column types + + @Test + public void columnOrderOfgetProcedures() throws SQLException { + ResultSet rs = meta.getProcedures(null, null, null); + assertThat(rs.next()).isFalse(); + ResultSetMetaData rsmeta = rs.getMetaData(); + assertThat(rsmeta.getColumnCount()).isEqualTo(8); + assertThat(rsmeta.getColumnName(1)).isEqualTo("PROCEDURE_CAT"); + assertThat(rsmeta.getColumnName(2)).isEqualTo("PROCEDURE_SCHEM"); + assertThat(rsmeta.getColumnName(3)).isEqualTo("PROCEDURE_NAME"); + // currently (Java 1.5), cols 4,5,6 are undefined + assertThat(rsmeta.getColumnName(7)).isEqualTo("REMARKS"); + assertThat(rsmeta.getColumnName(8)).isEqualTo("PROCEDURE_TYPE"); + } + + @Test + public void columnOrderOfgetProcedurColumns() throws SQLException { + ResultSet rs = meta.getProcedureColumns(null, null, null, null); + assertThat(rs.next()).isFalse(); + ResultSetMetaData rsmeta = rs.getMetaData(); + assertThat(rsmeta.getColumnCount()).isEqualTo(13); + assertThat(rsmeta.getColumnName(1)).isEqualTo("PROCEDURE_CAT"); + assertThat(rsmeta.getColumnName(2)).isEqualTo("PROCEDURE_SCHEM"); + assertThat(rsmeta.getColumnName(3)).isEqualTo("PROCEDURE_NAME"); + assertThat(rsmeta.getColumnName(4)).isEqualTo("COLUMN_NAME"); + assertThat(rsmeta.getColumnName(5)).isEqualTo("COLUMN_TYPE"); + assertThat(rsmeta.getColumnName(6)).isEqualTo("DATA_TYPE"); + assertThat(rsmeta.getColumnName(7)).isEqualTo("TYPE_NAME"); + assertThat(rsmeta.getColumnName(8)).isEqualTo("PRECISION"); + assertThat(rsmeta.getColumnName(9)).isEqualTo("LENGTH"); + assertThat(rsmeta.getColumnName(10)).isEqualTo("SCALE"); + assertThat(rsmeta.getColumnName(11)).isEqualTo("RADIX"); + assertThat(rsmeta.getColumnName(12)).isEqualTo("NULLABLE"); + assertThat(rsmeta.getColumnName(13)).isEqualTo("REMARKS"); + } + + @Test + public void columnOrderOfgetSchemas() throws SQLException { + ResultSet rs = meta.getSchemas(); + assertThat(rs.next()).isFalse(); + ResultSetMetaData rsmeta = rs.getMetaData(); + assertThat(rsmeta.getColumnCount()).isEqualTo(2); + assertThat(rsmeta.getColumnName(1)).isEqualTo("TABLE_SCHEM"); + assertThat(rsmeta.getColumnName(2)).isEqualTo("TABLE_CATALOG"); + } + + @Test + public void columnOrderOfgetCatalogs() throws SQLException { + ResultSet rs = meta.getCatalogs(); + assertThat(rs.next()).isFalse(); + ResultSetMetaData rsmeta = rs.getMetaData(); + assertThat(rsmeta.getColumnCount()).isEqualTo(1); + assertThat(rsmeta.getColumnName(1)).isEqualTo("TABLE_CAT"); + } + + @Test + public void columnOrderOfgetColumnPrivileges() throws SQLException { + ResultSet rs = meta.getColumnPrivileges(null, null, null, null); + assertThat(rs.next()).isFalse(); + ResultSetMetaData rsmeta = rs.getMetaData(); + assertThat(rsmeta.getColumnCount()).isEqualTo(8); + assertThat(rsmeta.getColumnName(1)).isEqualTo("TABLE_CAT"); + assertThat(rsmeta.getColumnName(2)).isEqualTo("TABLE_SCHEM"); + assertThat(rsmeta.getColumnName(3)).isEqualTo("TABLE_NAME"); + assertThat(rsmeta.getColumnName(4)).isEqualTo("COLUMN_NAME"); + assertThat(rsmeta.getColumnName(5)).isEqualTo("GRANTOR"); + assertThat(rsmeta.getColumnName(6)).isEqualTo("GRANTEE"); + assertThat(rsmeta.getColumnName(7)).isEqualTo("PRIVILEGE"); + assertThat(rsmeta.getColumnName(8)).isEqualTo("IS_GRANTABLE"); + } + + @Test + public void columnOrderOfgetTablePrivileges() throws SQLException { + ResultSet rs = meta.getTablePrivileges(null, null, null); + assertThat(rs.next()).isFalse(); + ResultSetMetaData rsmeta = rs.getMetaData(); + assertThat(rsmeta.getColumnCount()).isEqualTo(7); + assertThat(rsmeta.getColumnName(1)).isEqualTo("TABLE_CAT"); + assertThat(rsmeta.getColumnName(2)).isEqualTo("TABLE_SCHEM"); + assertThat(rsmeta.getColumnName(3)).isEqualTo("TABLE_NAME"); + assertThat(rsmeta.getColumnName(4)).isEqualTo("GRANTOR"); + assertThat(rsmeta.getColumnName(5)).isEqualTo("GRANTEE"); + assertThat(rsmeta.getColumnName(6)).isEqualTo("PRIVILEGE"); + assertThat(rsmeta.getColumnName(7)).isEqualTo("IS_GRANTABLE"); + } + + @Test + public void columnOrderOfgetBestRowIdentifier() throws SQLException { + ResultSet rs = meta.getBestRowIdentifier(null, null, null, 0, false); + assertThat(rs.next()).isFalse(); + ResultSetMetaData rsmeta = rs.getMetaData(); + assertThat(rsmeta.getColumnCount()).isEqualTo(8); + assertThat(rsmeta.getColumnName(1)).isEqualTo("SCOPE"); + assertThat(rsmeta.getColumnName(2)).isEqualTo("COLUMN_NAME"); + assertThat(rsmeta.getColumnName(3)).isEqualTo("DATA_TYPE"); + assertThat(rsmeta.getColumnName(4)).isEqualTo("TYPE_NAME"); + assertThat(rsmeta.getColumnName(5)).isEqualTo("COLUMN_SIZE"); + assertThat(rsmeta.getColumnName(6)).isEqualTo("BUFFER_LENGTH"); + assertThat(rsmeta.getColumnName(7)).isEqualTo("DECIMAL_DIGITS"); + assertThat(rsmeta.getColumnName(8)).isEqualTo("PSEUDO_COLUMN"); + } + + @Test + public void columnOrderOfgetVersionColumns() throws SQLException { + ResultSet rs = meta.getVersionColumns(null, null, null); + assertThat(rs.next()).isFalse(); + ResultSetMetaData rsmeta = rs.getMetaData(); + assertThat(rsmeta.getColumnCount()).isEqualTo(8); + assertThat(rsmeta.getColumnName(1)).isEqualTo("SCOPE"); + assertThat(rsmeta.getColumnName(2)).isEqualTo("COLUMN_NAME"); + assertThat(rsmeta.getColumnName(3)).isEqualTo("DATA_TYPE"); + assertThat(rsmeta.getColumnName(4)).isEqualTo("TYPE_NAME"); + assertThat(rsmeta.getColumnName(5)).isEqualTo("COLUMN_SIZE"); + assertThat(rsmeta.getColumnName(6)).isEqualTo("BUFFER_LENGTH"); + assertThat(rsmeta.getColumnName(7)).isEqualTo("DECIMAL_DIGITS"); + assertThat(rsmeta.getColumnName(8)).isEqualTo("PSEUDO_COLUMN"); + } + + @Test + public void viewIngetPrimaryKeys() throws SQLException { + ResultSet rs; + + stat.executeUpdate("create table t1 (c1, c2, c3);"); + stat.executeUpdate("create view view_nopk (v1, v2) as select c1, c3 from t1;"); + + rs = meta.getPrimaryKeys(null, null, "view_nopk"); + assertThat(rs.next()).isFalse(); + } + + @Test + public void moreOfgetColumns() throws SQLException { + ResultSet rs; + + stat.executeUpdate("create table tabcols1 (col1, col2);"); + // mixed-case table, column and primary key names + stat.executeUpdate("CREATE TABLE TabCols2 (Col1, Col2);"); + // quoted table, column and primary key names + stat.executeUpdate("CREATE TABLE `TabCols3` (`Col1`, `Col2`);"); + + rs = meta.getColumns(null, null, "tabcols1", "%"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("tabcols1"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("col1"); + assertThat(rs.getInt("DATA_TYPE")).isEqualTo(Types.VARCHAR); + assertThat(rs.getString("IS_NULLABLE")).isEqualTo("YES"); + assertThat(rs.getString("COLUMN_DEF")).isNull(); + assertThat(rs.getString("IS_AUTOINCREMENT")).isEqualTo("NO"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("tabcols1"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("col2"); + assertThat(rs.getInt("DATA_TYPE")).isEqualTo(Types.VARCHAR); + assertThat(rs.next()).isFalse(); + + rs = meta.getColumns(null, null, "TabCols2", "%"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("TabCols2"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("Col1"); + assertThat(rs.getInt("DATA_TYPE")).isEqualTo(Types.VARCHAR); + assertThat(rs.getString("IS_NULLABLE")).isEqualTo("YES"); + assertThat(rs.getString("COLUMN_DEF")).isNull(); + assertThat(rs.getString("IS_AUTOINCREMENT")).isEqualTo("NO"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("TabCols2"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("Col2"); + assertThat(rs.getInt("DATA_TYPE")).isEqualTo(Types.VARCHAR); + assertThat(rs.next()).isFalse(); + + rs = meta.getColumns(null, null, "TabCols3", "%"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("TabCols3"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("Col1"); + assertThat(rs.getInt("DATA_TYPE")).isEqualTo(Types.VARCHAR); + assertThat(rs.getString("IS_NULLABLE")).isEqualTo("YES"); + assertThat(rs.getString("COLUMN_DEF")).isNull(); + assertThat(rs.getString("IS_AUTOINCREMENT")).isEqualTo("NO"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("TabCols3"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("Col2"); + assertThat(rs.getInt("DATA_TYPE")).isEqualTo(Types.VARCHAR); + assertThat(rs.next()).isFalse(); + } + + @Test + public void autoincrement() throws SQLException { + ResultSet rs; + + // no autoincrement no rowid + stat.executeUpdate( + "CREATE TABLE TAB1 (COL1 INTEGER NOT NULL PRIMARY KEY, COL2) WITHOUT ROWID;"); + // no autoincrement + stat.executeUpdate("CREATE TABLE TAB2 (COL1 INTEGER NOT NULL PRIMARY KEY, COL2);"); + // autoincrement + stat.executeUpdate( + "CREATE TABLE TAB3 (COL1 INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, COL2);"); + + rs = meta.getColumns(null, null, "TAB1", "%"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("TAB1"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("COL1"); + assertThat(rs.getInt("DATA_TYPE")).isEqualTo(Types.INTEGER); + assertThat(rs.getString("IS_NULLABLE")).isEqualTo("NO"); + assertThat(rs.getString("COLUMN_DEF")).isNull(); + assertThat(rs.getString("IS_AUTOINCREMENT")).isEqualTo("NO"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("TAB1"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("COL2"); + assertThat(rs.getInt("DATA_TYPE")).isEqualTo(Types.VARCHAR); + assertThat(rs.next()).isFalse(); + + rs = meta.getColumns(null, null, "TAB2", "%"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("TAB2"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("COL1"); + assertThat(rs.getInt("DATA_TYPE")).isEqualTo(Types.INTEGER); + assertThat(rs.getString("IS_NULLABLE")).isEqualTo("NO"); + assertThat(rs.getString("COLUMN_DEF")).isNull(); + assertThat(rs.getString("IS_AUTOINCREMENT")).isEqualTo("NO"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("TAB2"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("COL2"); + assertThat(rs.getInt("DATA_TYPE")).isEqualTo(Types.VARCHAR); + assertThat(rs.next()).isFalse(); + + rs = meta.getColumns(null, null, "TAB3", "%"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("TAB3"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("COL1"); + assertThat(rs.getInt("DATA_TYPE")).isEqualTo(Types.INTEGER); + assertThat(rs.getString("IS_NULLABLE")).isEqualTo("NO"); + assertThat(rs.getString("COLUMN_DEF")).isNull(); + assertThat(rs.getString("IS_AUTOINCREMENT")).isEqualTo("YES"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("TAB3"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("COL2"); + assertThat(rs.getInt("DATA_TYPE")).isEqualTo(Types.VARCHAR); + assertThat(rs.next()).isFalse(); + } + + @Test + public void columnOrderOfgetPrimaryKeys() throws Exception { + ResultSet rs; + ResultSetMetaData rsmeta; + + stat.executeUpdate("create table nopk (c1, c2, c3, c4);"); + stat.executeUpdate("create table pk1 (col1 primary key, col2, col3);"); + stat.executeUpdate("create table pk2 (col1, col2 primary key, col3);"); + stat.executeUpdate( + "create table pk3 (col1, col2, col3, col4, primary key (col3, col2 ));"); + // extra spaces and mixed case are intentional, do not remove! + stat.executeUpdate( + "create table pk4 (col1, col2, col3, col4, " + + "\r\nCONSTraint\r\nnamed primary\r\n\t\t key (col3, col2 ));"); + // mixed-case table, column and primary key names - GitHub issue #219 + stat.executeUpdate( + "CREATE TABLE Pk5 (Col1, Col2, Col3, Col4, CONSTRAINT NamedPk PRIMARY KEY (Col3, Col2));"); + // quoted table, column and primary key names - GitHub issue #219 + stat.executeUpdate( + "CREATE TABLE `Pk6` (`Col1`, `Col2`, `Col3`, `Col4`, CONSTRAINT `NamedPk` PRIMARY KEY (`Col3`, `Col2`));"); + // spaces before and after "primary key" - GitHub issue #236 + stat.executeUpdate( + "CREATE TABLE pk7 (col1, col2, col3, col4 VARCHAR(10),PRIMARY KEY (col1, col2, col3));"); + stat.executeUpdate( + "CREATE TABLE pk8 (col1, col2, col3, col4 VARCHAR(10), PRIMARY KEY(col1, col2, col3));"); + stat.executeUpdate( + "CREATE TABLE pk9 (col1, col2, col3, col4 VARCHAR(10),PRIMARY KEY(col1, col2, col3));"); + stat.executeUpdate( + "CREATE TABLE `Pk10` (`Col1`, `Col2`, `Col3`, `Col4`, CONSTRAINT `NamedPk`PRIMARY KEY (`Col3`, `Col2`));"); + stat.executeUpdate( + "CREATE TABLE `Pk11` (`Col1`, `Col2`, `Col3`, `Col4`, CONSTRAINT `NamedPk` PRIMARY KEY(`Col3`, `Col2`));"); + stat.executeUpdate( + "CREATE TABLE `Pk12` (`Col1`, `Col2`, `Col3`, `Col4`, CONSTRAINT`NamedPk`PRIMARY KEY(`Col3`,`Col2`));"); + stat.executeUpdate( + "CREATE TABLE \"Pk13\" (\"Col1\", \"Col2\", \"Col3\", \"Col4\", CONSTRAINT \"NamedPk\" PRIMARY KEY(\"Col3\",\"Col2\"));"); + stat.executeUpdate( + "CREATE TABLE \"Pk14\" (\"Col1\", \"Col2\", \"Col3\", \"Col4\", PRIMARY KEY(\"Col3\"), FOREIGN KEY (\"Col1\") REFERENCES \"pk1\" (\"col1\"))"); + stat.executeUpdate( + "CREATE TABLE \"Pk15\" (\"Col1\", \"Col2\", \"Col3\", \"Col4\", PRIMARY KEY(\"Col3\", \"Col2\"), FOREIGN KEY (\"Col1\") REFERENCES \"pk1\" (\"col1\"))"); + + rs = meta.getPrimaryKeys(null, null, "nopk"); + assertThat(rs.next()).isFalse(); + rsmeta = rs.getMetaData(); + assertThat(rsmeta.getColumnCount()).isEqualTo(6); + assertThat(rsmeta.getColumnName(1)).isEqualTo("TABLE_CAT"); + assertThat(rsmeta.getColumnName(2)).isEqualTo("TABLE_SCHEM"); + assertThat(rsmeta.getColumnName(3)).isEqualTo("TABLE_NAME"); + assertThat(rsmeta.getColumnName(4)).isEqualTo("COLUMN_NAME"); + assertThat(rsmeta.getColumnName(5)).isEqualTo("KEY_SEQ"); + assertThat(rsmeta.getColumnName(6)).isEqualTo("PK_NAME"); + rs.close(); + + assertPrimaryKey(meta, "pk1", null, "col1"); + assertPrimaryKey(meta, "pk2", null, "col2"); + assertPrimaryKey(meta, "pk3", null, "col3", "col2"); + assertPrimaryKey(meta, "pk4", "named", "col3", "col2"); + assertPrimaryKey(meta, "Pk5", "NamedPk", "Col3", "Col2"); + assertPrimaryKey(meta, "Pk6", "NamedPk", "Col3", "Col2"); + assertPrimaryKey(meta, "pk7", null, "col1", "col2", "col3"); + assertPrimaryKey(meta, "pk8", null, "col1", "col2", "col3"); + assertPrimaryKey(meta, "pk9", null, "col1", "col2", "col3"); + assertPrimaryKey(meta, "Pk10", "NamedPk", "Col3", "Col2"); + assertPrimaryKey(meta, "Pk11", "NamedPk", "Col3", "Col2"); + assertPrimaryKey(meta, "Pk12", "NamedPk", "Col3", "Col2"); + assertPrimaryKey(meta, "Pk13", "NamedPk", "Col3", "Col2"); + assertPrimaryKey(meta, "Pk14", null, "Col3"); + assertPrimaryKey(meta, "Pk15", null, "Col3", "Col2"); + } + + private void assertPrimaryKey( + DatabaseMetaData meta, String tableName, String pkName, String... pkColumns) + throws Exception { + final Map colSeq = new HashMap<>(); + for (int i = 0; i < pkColumns.length; i++) { + colSeq.put(pkColumns[i], i + 1); + } + Arrays.sort(pkColumns); + + final ResultSet rs = meta.getPrimaryKeys(null, null, tableName); + assertThat(rs.next()).isTrue(); + for (int i = 0; i < pkColumns.length; i++) { + assertThat(rs.getString("TABLE_CAT")) + .as("DatabaseMetaData.getPrimaryKeys: TABLE_CAT") + .isNull(); + assertThat(rs.getString("TABLE_SCHEM")) + .as("DatabaseMetaData.getPrimaryKeys: TABLE_SCHEM") + .isNull(); + assertThat(rs.getString("TABLE_NAME")) + .as("DatabaseMetaData.getPrimaryKeys: TABLE_NAME") + .isEqualTo(tableName); + assertThat(rs.getString("COLUMN_NAME")) + .as("DatabaseMetaData.getPrimaryKeys: COLUMN_NAME") + .isEqualTo(pkColumns[i]); + assertThat(rs.getString("PK_NAME")) + .as("DatabaseMetaData.getPrimaryKeys: PK_NAME") + .isEqualTo(pkName); + assertThat(rs.getInt("KEY_SEQ")) + .as("DatabaseMetaData.getPrimaryKeys: KEY_SEQ") + .isEqualTo(colSeq.get(pkColumns[i]).intValue()); + if (i < pkColumns.length - 1) assertThat(rs.next()).isTrue(); + } + + assertThat(rs.next()).isFalse(); + + rs.close(); + } + + @Test + public void columnOrderOfgetImportedKeys() throws SQLException { + + stat.executeUpdate("create table person (id integer)"); + stat.executeUpdate( + "create table address (pid integer, name, foreign key(pid) references person(id))"); + + ResultSet importedKeys = meta.getImportedKeys("default", "global", "address"); + assertThat(importedKeys.next()).isTrue(); + assertThat(importedKeys.getString("PKTABLE_CAT")).isEqualTo("default"); + assertThat(importedKeys.getString("PKTABLE_SCHEM")).isEqualTo("global"); + assertThat(importedKeys.getString("FKTABLE_CAT")).isEqualTo("default"); + assertThat(importedKeys.getString("PKTABLE_NAME")).isEqualTo("person"); + assertThat(importedKeys.getString("PKCOLUMN_NAME")).isEqualTo("id"); + assertThat(importedKeys.getString("PK_NAME")).isNotNull(); + assertThat(importedKeys.getString("FK_NAME")).isNotNull(); + assertThat(importedKeys.getString("FKTABLE_NAME")).isEqualTo("address"); + assertThat(importedKeys.getString("FKCOLUMN_NAME")).isEqualTo("pid"); + importedKeys.close(); + + importedKeys = meta.getImportedKeys(null, null, "person"); + assertThat(importedKeys.next()).isFalse(); + importedKeys.close(); + } + + @Test + public void columnOrderOfgetExportedKeys() throws SQLException { + + stat.executeUpdate("create table person (id integer primary key)"); + stat.executeUpdate( + "create table address (pid integer, name, foreign key(pid) references person(id))"); + + ResultSet exportedKeys = meta.getExportedKeys("default", "global", "person"); + assertThat(exportedKeys.next()).isTrue(); + assertThat(exportedKeys.getString("PKTABLE_CAT")).isEqualTo("default"); + assertThat(exportedKeys.getString("PKTABLE_SCHEM")).isEqualTo("global"); + assertThat(exportedKeys.getString("FKTABLE_CAT")).isEqualTo("default"); + assertThat(exportedKeys.getString("FKTABLE_SCHEM")).isEqualTo("global"); + assertThat(exportedKeys.getString("PK_NAME")).isNotNull(); + assertThat(exportedKeys.getString("FK_NAME")).isNotNull(); + + assertThat(exportedKeys.getString("PKTABLE_NAME")).isEqualTo("person"); + assertThat(exportedKeys.getString("PKCOLUMN_NAME")).isEqualTo("id"); + assertThat(exportedKeys.getString("FKTABLE_NAME")).isEqualTo("address"); + assertThat(exportedKeys.getString("FKCOLUMN_NAME")).isEqualTo("pid"); + + exportedKeys.close(); + + exportedKeys = meta.getExportedKeys(null, null, "address"); + assertThat(exportedKeys.next()).isFalse(); + exportedKeys.close(); + + // With explicit primary column defined. + stat.executeUpdate("create table REFERRED (ID integer primary key not null)"); + stat.executeUpdate( + "create table REFERRING (ID integer, RID integer, constraint fk\r\n foreign\tkey\r\n(RID) references REFERRED(id))"); + + exportedKeys = meta.getExportedKeys(null, null, "referred"); + assertThat(exportedKeys.getString("PKTABLE_NAME")).isEqualTo("REFERRED"); + assertThat(exportedKeys.getString("FKTABLE_NAME")).isEqualTo("REFERRING"); + assertThat(exportedKeys.getString("FK_NAME")).isEqualTo("fk"); + exportedKeys.close(); + } + + @Test + public void columnOrderOfgetCrossReference() throws SQLException { + stat.executeUpdate("create table person (id integer)"); + stat.executeUpdate( + "create table address (pid integer, name, foreign key(pid) references person(id))"); + + ResultSet cr = meta.getCrossReference(null, null, "person", null, null, "address"); + // assertTrue(cr.next()); + // TODO: unfinished business + } + + /* TODO + + @Test public void columnOrderOfgetTypeInfo() throws SQLException { + @Test public void columnOrderOfgetIndexInfo() throws SQLException { + @Test public void columnOrderOfgetSuperTypes() throws SQLException { + @Test public void columnOrderOfgetSuperTables() throws SQLException { + @Test public void columnOrderOfgetAttributes() throws SQLException {*/ + + @Test + public void columnOrderOfgetUDTs() throws SQLException { + ResultSet rs = meta.getUDTs(null, null, null, null); + assertThat(rs.next()).isFalse(); + ResultSetMetaData rsmeta = rs.getMetaData(); + assertThat(rsmeta.getColumnCount()).isEqualTo(7); + assertThat(rsmeta.getColumnName(1)).isEqualTo("TYPE_CAT"); + assertThat(rsmeta.getColumnName(2)).isEqualTo("TYPE_SCHEM"); + assertThat(rsmeta.getColumnName(3)).isEqualTo("TYPE_NAME"); + assertThat(rsmeta.getColumnName(4)).isEqualTo("CLASS_NAME"); + assertThat(rsmeta.getColumnName(5)).isEqualTo("DATA_TYPE"); + assertThat(rsmeta.getColumnName(6)).isEqualTo("REMARKS"); + assertThat(rsmeta.getColumnName(7)).isEqualTo("BASE_TYPE"); + } + + @Test + public void getIndexInfoOnTest() throws SQLException { + ResultSet rs = meta.getIndexInfo(null, null, "test", false, false); + + assertThat(rs).isNotNull(); + } + + @Test + public void getIndexInfoIndexedSingle() throws SQLException { + stat.executeUpdate( + "create table testindex (id integer primary key, fn float default 0.0, sn not null);"); + stat.executeUpdate("create index testindex_idx on testindex (sn);"); + + ResultSet rs = meta.getIndexInfo(null, null, "testindex", false, false); + ResultSetMetaData rsmd = rs.getMetaData(); + + assertThat(rs).isNotNull(); + assertThat(rsmd).isNotNull(); + } + + @Test + public void getIndexInfoIndexedSingleExpr() throws SQLException { + stat.executeUpdate( + "create table testindex (id integer primary key, fn float default 0.0, sn not null);"); + stat.executeUpdate("create index testindex_idx on testindex (sn, fn/2);"); + + ResultSet rs = meta.getIndexInfo(null, null, "testindex", false, false); + ResultSetMetaData rsmd = rs.getMetaData(); + + assertThat(rs).isNotNull(); + assertThat(rsmd).isNotNull(); + } + + @Test + public void getIndexInfoIndexedMulti() throws SQLException { + stat.executeUpdate( + "create table testindex (id integer primary key, fn float default 0.0, sn not null);"); + stat.executeUpdate("create index testindex_idx on testindex (sn);"); + stat.executeUpdate("create index testindex_pk_idx on testindex (id);"); + + ResultSet rs = meta.getIndexInfo(null, null, "testindex", false, false); + ResultSetMetaData rsmd = rs.getMetaData(); + + assertThat(rs).isNotNull(); + assertThat(rsmd).isNotNull(); + } + + @Test + @DisabledInNativeImage // assertj Assumptions do not work in native-image tests + public void version() throws Exception { + assumeThat(Utils.getCompileOptions(conn)) + .as("Can't check the version if not compiled by us") + .contains("JDBC_EXTENSIONS"); + Properties version; + try (InputStream resourceAsStream = + DBMetaDataTest.class.getResourceAsStream( + "/META-INF/maven/org.xerial/sqlite-jdbc/VERSION")) { + version = new Properties(); + assumeThat(resourceAsStream).isNotNull(); + version.load(resourceAsStream); + } + String versionString = version.getProperty("version"); + int majorVersion = Integer.parseInt(versionString.split("\\.")[0]); + int minorVersion = Integer.parseInt(versionString.split("\\.")[1]); + + assertThat(majorVersion > 0).as("major version check").isTrue(); + assertThat(meta.getDriverName()).as("driver name").isEqualTo("SQLite JDBC"); + assertThat( + meta.getDriverVersion() + .startsWith(String.format("%d.%d", majorVersion, minorVersion))) + .as("driver version") + .isTrue(); + assertThat(meta.getDriverMajorVersion()).as("driver major version").isEqualTo(majorVersion); + assertThat(meta.getDriverMinorVersion()).as("driver minor version").isEqualTo(minorVersion); + assertThat(meta.getDatabaseProductName()).as("db name").isEqualTo("SQLite"); + assertThat(meta.getDatabaseProductVersion()).as("db version").isEqualTo(versionString); + assertThat(meta.getDatabaseMajorVersion()).as("db major version").isEqualTo(majorVersion); + assertThat(meta.getDatabaseMinorVersion()).as("db minor version").isEqualTo(minorVersion); + assertThat(meta.getUserName()).as("user name").isNull(); + } + + @Nested + class SqliteSchema { + @ParameterizedTest + @ValueSource(strings = {"sqlite_schema", "sqlite_master"}) + public void getImportedKeys(String table) throws SQLException { + ResultSet importedKeys = meta.getImportedKeys(null, null, table); + + assertThat(importedKeys.next()).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = {"sqlite_schema", "sqlite_master"}) + public void getExportedKeys(String table) throws SQLException { + ResultSet exportedKeys = meta.getExportedKeys(null, null, table); + + assertThat(exportedKeys.next()).isFalse(); + } + + @ParameterizedTest + @ValueSource(strings = {"sqlite_schema", "sqlite_master"}) + public void getPrimaryKeys(String table) throws SQLException { + ResultSet primaryKeys = meta.getPrimaryKeys(null, null, table); + + assertThat(primaryKeys.next()).isFalse(); + } + } +} diff --git a/upstream-xerial_sqlite-jdbc/src/test/java/org/sqlite/PrepStmtTest.java b/upstream-xerial_sqlite-jdbc/src/test/java/org/sqlite/PrepStmtTest.java new file mode 100644 index 0000000..ed837ff --- /dev/null +++ b/upstream-xerial_sqlite-jdbc/src/test/java/org/sqlite/PrepStmtTest.java @@ -0,0 +1,924 @@ +package org.sqlite; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.data.Offset.offset; +import static org.junit.jupiter.api.Assumptions.assumeFalse; + +import java.io.ByteArrayInputStream; +import java.io.StringReader; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.sql.Connection; +import java.sql.Date; +import java.sql.DriverManager; +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.StringTokenizer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** These tests are designed to stress PreparedStatements on memory dbs. */ +public class PrepStmtTest { + static byte[] b1 = new byte[] {1, 2, 7, 4, 2, 6, 2, 8, 5, 2, 3, 1, 5, 3, 6, 3, 3, 6, 2, 5}; + static byte[] b2 = getUtf8Bytes("To be or not to be."); + static byte[] b3 = getUtf8Bytes("Question!#$%"); + static String utf01 = "\uD840\uDC40"; + static String utf02 = "\uD840\uDC47 "; + static String utf03 = " \uD840\uDC43"; + static String utf04 = " \uD840\uDC42 "; + static String utf05 = "\uD840\uDC40\uD840\uDC44"; + static String utf06 = "Hello World, \uD840\uDC40 \uD880\uDC99"; + static String utf07 = "\uD840\uDC41 testing \uD880\uDC99"; + static String utf08 = "\uD840\uDC40\uD840\uDC44 testing"; + + private Connection conn; + private Statement stat; + + private boolean isLibsql() throws SQLException { + return conn.getMetaData().getDriverName().contains("libSQL"); + } + + private static byte[] getUtf8Bytes(String str) { + return str.getBytes(StandardCharsets.UTF_8); + } + + @BeforeEach + public void connect() throws Exception { + conn = DriverManager.getConnection("jdbc:sqlite:"); + stat = conn.createStatement(); + } + + @AfterEach + public void close() throws SQLException { + stat.close(); + conn.close(); + } + + @Test + public void update() throws SQLException { + assertThat(conn.prepareStatement("create table s1 (c1);").executeUpdate()).isEqualTo(0); + PreparedStatement prep = conn.prepareStatement("insert into s1 values (?);"); + prep.setInt(1, 3); + assertThat(prep.executeUpdate()).isEqualTo(1); + assertThat(prep.getResultSet()).isNull(); + prep.setInt(1, 5); + assertThat(prep.executeUpdate()).isEqualTo(1); + prep.setInt(1, 7); + assertThat(prep.executeUpdate()).isEqualTo(1); + + ResultSet rsgk = prep.getGeneratedKeys(); + assertThat(rsgk.next()).isTrue(); + assertThat(rsgk.getInt(1)).isEqualTo(3); + rsgk.close(); + + prep.close(); + // check results with normal statement + ResultSet rs = stat.executeQuery("select sum(c1) from s1;"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(1)).isEqualTo(15); + rs.close(); + } + + @Test + public void multiUpdate() throws SQLException { + stat.executeUpdate("create table test (c1);"); + PreparedStatement prep = conn.prepareStatement("insert into test values (?);"); + + for (int i = 0; i < 10; i++) { + prep.setInt(1, i); + prep.executeUpdate(); + prep.execute(); + } + + prep.close(); + stat.executeUpdate("drop table test;"); + } + + @Test + public void emptyRS() throws SQLException { + PreparedStatement prep = conn.prepareStatement("select null limit 0;"); + ResultSet rs = prep.executeQuery(); + assertThat(rs.next()).isFalse(); + rs.close(); + prep.close(); + } + + @Test + public void singleRowRS() throws SQLException { + PreparedStatement prep = conn.prepareStatement("select ?;"); + prep.setInt(1, Integer.MAX_VALUE); + ResultSet rs = prep.executeQuery(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(1)).isEqualTo(Integer.MAX_VALUE); + assertThat(rs.getString(1)).isEqualTo(Integer.toString(Integer.MAX_VALUE)); + assertThat(rs.getDouble(1)) + .isCloseTo(new Integer(Integer.MAX_VALUE).doubleValue(), offset(0.0001)); + assertThat(rs.next()).isFalse(); + rs.close(); + prep.close(); + } + + @Test + public void twoRowRS() throws SQLException { + PreparedStatement prep = conn.prepareStatement("select ? union all select ?;"); + prep.setDouble(1, Double.MAX_VALUE); + prep.setDouble(2, Double.MIN_VALUE); + ResultSet rs = prep.executeQuery(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getDouble(1)).isCloseTo(Double.MAX_VALUE, offset(0.0001)); + assertThat(rs.next()).isTrue(); + assertThat(rs.getDouble(1)).isCloseTo(Double.MIN_VALUE, offset(0.0001)); + assertThat(rs.next()).isFalse(); + rs.close(); + } + + @Test + public void stringRS() throws SQLException { + String name = "Gandhi"; + PreparedStatement prep = conn.prepareStatement("select ?;"); + prep.setString(1, name); + ResultSet rs = prep.executeQuery(); + assertThat(prep.getUpdateCount()).isEqualTo(-1); + assertThat(rs.next()).isTrue(); + assertThat(name).isEqualTo(rs.getString(1)); + assertThat(rs.next()).isFalse(); + rs.close(); + } + + @Test + public void clobRS() throws SQLException { + String name = "Gandhi"; + PreparedStatement prep = conn.prepareStatement("select ?;"); + prep.setClob(1, new StringReader(name)); + ResultSet rs = prep.executeQuery(); + assertThat(prep.getUpdateCount()).isEqualTo(-1); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString(1)).isEqualTo(name); + assertThat(rs.next()).isFalse(); + rs.close(); + } + + @Test + public void blobRS() throws SQLException { + String name = "Gandhi"; + PreparedStatement prep = conn.prepareStatement("select ?;"); + prep.setBlob(1, new ByteArrayInputStream(name.getBytes())); + ResultSet rs = prep.executeQuery(); + assertThat(prep.getUpdateCount()).isEqualTo(-1); + assertThat(rs.next()).isTrue(); + assertThat(rs.getBytes(1)).isEqualTo(name.getBytes()); + assertThat(rs.next()).isFalse(); + rs.close(); + } + + @Test + public void finalizePrep() throws SQLException { + conn.prepareStatement("select null;"); + System.gc(); + } + + @Test + public void set() throws SQLException { + ResultSet rs; + PreparedStatement prep = conn.prepareStatement("select ?, ?, ?;"); + + // integers + prep.setInt(1, Integer.MIN_VALUE); + prep.setInt(2, Integer.MAX_VALUE); + prep.setInt(3, 0); + rs = prep.executeQuery(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(1)).isEqualTo(Integer.MIN_VALUE); + assertThat(rs.getInt(2)).isEqualTo(Integer.MAX_VALUE); + assertThat(rs.getInt(3)).isEqualTo(0); + + // strings + String name = "Winston Leonard Churchill"; + String fn = name.substring(0, 7), mn = name.substring(8, 15), sn = name.substring(16, 25); + prep.clearParameters(); + prep.setString(1, fn); + prep.setString(2, mn); + prep.setString(3, sn); + rs = prep.executeQuery(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString(1)).isEqualTo(fn); + assertThat(rs.getString(2)).isEqualTo(mn); + assertThat(rs.getString(3)).isEqualTo(sn); + + // mixed + prep.setString(1, name); + prep.setString(2, null); + prep.setLong(3, Long.MAX_VALUE); + rs = prep.executeQuery(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString(1)).isEqualTo(name); + assertThat(rs.getString(2)).isNull(); + assertThat(rs.wasNull()).isTrue(); + assertThat(rs.getLong(3)).isEqualTo(Long.MAX_VALUE); + + // bytes + prep.setBytes(1, b1); + prep.setBytes(2, b2); + prep.setBytes(3, b3); + rs = prep.executeQuery(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getBytes(1)).containsExactly(b1); + assertThat(rs.getBytes(1)).containsExactly(b1); + assertThat(rs.getBytes(2)).containsExactly(b2); + assertThat(rs.getBytes(3)).containsExactly(b3); + assertThat(rs.next()).isFalse(); + rs.close(); + + // null date, time and timestamp (fix #363) + prep.setDate(1, null); + prep.setTime(2, null); + prep.setTimestamp(3, null); + rs = prep.executeQuery(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getDate(1)).isNull(); + assertThat(rs.getTime(2)).isNull(); + assertThat(rs.getTimestamp(3)).isNull(); + + // streams + ByteArrayInputStream inByte = new ByteArrayInputStream(b1); + prep.setBinaryStream(1, inByte, b1.length); + ByteArrayInputStream inAscii = new ByteArrayInputStream(b2); + prep.setAsciiStream(2, inAscii, b2.length); + byte[] b3 = utf08.getBytes(StandardCharsets.UTF_8); + ByteArrayInputStream inUnicode = new ByteArrayInputStream(b3); + prep.setUnicodeStream(3, inUnicode, b3.length); + + rs = prep.executeQuery(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getBytes(1)).containsExactly(b1); + assertThat(rs.getString(2)).isEqualTo(new String(b2, StandardCharsets.UTF_8)); + assertThat(rs.getString(3)).isEqualTo(new String(b3, StandardCharsets.UTF_8)); + assertThat(rs.next()).isFalse(); + rs.close(); + } + + @Test + public void colNameAccess() throws SQLException { + PreparedStatement prep = conn.prepareStatement("select ? as col1, ? as col2, ? as bingo;"); + prep.setNull(1, 0); + prep.setFloat(2, Float.MIN_VALUE); + prep.setShort(3, Short.MIN_VALUE); + prep.executeQuery(); + ResultSet rs = prep.executeQuery(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("col1")).isNull(); + assertThat(rs.wasNull()).isTrue(); + assertThat(rs.getFloat("col2")).isCloseTo(Float.MIN_VALUE, offset(0.0001F)); + assertThat(rs.getShort("bingo")).isEqualTo(Short.MIN_VALUE); + rs.close(); + prep.close(); + } + + @Test + public void insert1000() throws SQLException { + stat.executeUpdate("create table in1000 (a);"); + PreparedStatement prep = conn.prepareStatement("insert into in1000 values (?);"); + conn.setAutoCommit(false); + for (int i = 0; i < 1000; i++) { + prep.setInt(1, i); + prep.executeUpdate(); + } + conn.commit(); + + ResultSet rs = stat.executeQuery("select count(a) from in1000;"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(1)).isEqualTo(1000); + rs.close(); + } + + @Test + public void getObject() throws SQLException { + stat.executeUpdate( + "create table testobj (" + + "c1 integer, c2 float, c3, c4 varchar, c5 bit, c6, c7);"); + PreparedStatement prep = + conn.prepareStatement("insert into testobj values (?,?,?,?,?,?,?);"); + + prep.setInt(1, Integer.MAX_VALUE); + prep.setFloat(2, Float.MAX_VALUE); + prep.setDouble(3, Double.MAX_VALUE); + prep.setLong(4, Long.MAX_VALUE); + prep.setBoolean(5, false); + prep.setByte(6, (byte) 7); + prep.setBytes(7, b1); + prep.executeUpdate(); + + ResultSet rs = stat.executeQuery("select c1,c2,c3,c4,c5,c6,c7 from testobj;"); + assertThat(rs.next()).isTrue(); + + assertThat(rs.getInt(1)).isEqualTo(Integer.MAX_VALUE); + assertThat((int) rs.getLong(1)).isEqualTo(Integer.MAX_VALUE); + assertThat(rs.getFloat(2)).isCloseTo(Float.MAX_VALUE, offset(0.0001f)); + assertThat(rs.getDouble(3)).isCloseTo(Double.MAX_VALUE, offset(0.0001d)); + assertThat(rs.getLong(4)).isEqualTo(Long.MAX_VALUE); + assertThat(rs.getBoolean(5)).isFalse(); + assertThat(rs.getByte(6)).isEqualTo((byte) 7); + assertThat(rs.getBytes(7)).containsExactly(b1); + + assertThat(rs.getObject(1)).isNotNull(); + assertThat(rs.getObject(2)).isNotNull(); + assertThat(rs.getObject(3)).isNotNull(); + assertThat(rs.getObject(4)).isNotNull(); + assertThat(rs.getObject(5)).isNotNull(); + assertThat(rs.getObject(6)).isNotNull(); + assertThat(rs.getObject(7)).isNotNull(); + assertThat(rs.getObject(1) instanceof Integer).isTrue(); + assertThat(rs.getObject(2) instanceof Double).isTrue(); + assertThat(rs.getObject(3) instanceof Double).isTrue(); + assertThat(rs.getObject(4) instanceof String).isTrue(); + assertThat(rs.getObject(5) instanceof Integer).isTrue(); + assertThat(rs.getObject(6) instanceof Integer).isTrue(); + assertThat(rs.getObject(7) instanceof byte[]).isTrue(); + rs.close(); + } + + @Test + public void tokens() throws SQLException { + /* checks for a bug where a substring is read by the driver as the + * full original string, caused by my idiocy in assuming the + * pascal-style string was null terminated. Thanks Oliver Randschau. */ + StringTokenizer st = new StringTokenizer("one two three"); + st.nextToken(); + String substr = st.nextToken(); + + PreparedStatement prep = conn.prepareStatement("select ?;"); + prep.setString(1, substr); + ResultSet rs = prep.executeQuery(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString(1)).isEqualTo(substr); + } + + @Test + public void utf() throws SQLException { + ResultSet rs = + stat.executeQuery( + "select '" + + utf01 + + "','" + + utf02 + + "','" + + utf03 + + "','" + + utf04 + + "','" + + utf05 + + "','" + + utf06 + + "','" + + utf07 + + "','" + + utf08 + + "';"); + assertThat(rs.getBytes(1)).containsExactly(getUtf8Bytes(utf01)); + assertThat(rs.getBytes(2)).containsExactly(getUtf8Bytes(utf02)); + assertThat(rs.getBytes(3)).containsExactly(getUtf8Bytes(utf03)); + assertThat(rs.getBytes(4)).containsExactly(getUtf8Bytes(utf04)); + assertThat(rs.getBytes(5)).containsExactly(getUtf8Bytes(utf05)); + assertThat(rs.getBytes(6)).containsExactly(getUtf8Bytes(utf06)); + assertThat(rs.getBytes(7)).containsExactly(getUtf8Bytes(utf07)); + assertThat(rs.getBytes(8)).containsExactly(getUtf8Bytes(utf08)); + assertThat(rs.getString(1)).isEqualTo(utf01); + assertThat(rs.getString(2)).isEqualTo(utf02); + assertThat(rs.getString(3)).isEqualTo(utf03); + assertThat(rs.getString(4)).isEqualTo(utf04); + assertThat(rs.getString(5)).isEqualTo(utf05); + assertThat(rs.getString(6)).isEqualTo(utf06); + assertThat(rs.getString(7)).isEqualTo(utf07); + assertThat(rs.getString(8)).isEqualTo(utf08); + rs.close(); + + PreparedStatement prep = conn.prepareStatement("select ?,?,?,?,?,?,?,?;"); + prep.setString(1, utf01); + prep.setString(2, utf02); + prep.setString(3, utf03); + prep.setString(4, utf04); + prep.setString(5, utf05); + prep.setString(6, utf06); + prep.setString(7, utf07); + prep.setString(8, utf08); + rs = prep.executeQuery(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getBytes(1)).containsExactly(getUtf8Bytes(utf01)); + assertThat(rs.getBytes(2)).containsExactly(getUtf8Bytes(utf02)); + assertThat(rs.getBytes(3)).containsExactly(getUtf8Bytes(utf03)); + assertThat(rs.getBytes(4)).containsExactly(getUtf8Bytes(utf04)); + assertThat(rs.getBytes(5)).containsExactly(getUtf8Bytes(utf05)); + assertThat(rs.getBytes(6)).containsExactly(getUtf8Bytes(utf06)); + assertThat(rs.getBytes(7)).containsExactly(getUtf8Bytes(utf07)); + assertThat(rs.getBytes(8)).containsExactly(getUtf8Bytes(utf08)); + assertThat(rs.getString(1)).isEqualTo(utf01); + assertThat(rs.getString(2)).isEqualTo(utf02); + assertThat(rs.getString(3)).isEqualTo(utf03); + assertThat(rs.getString(4)).isEqualTo(utf04); + assertThat(rs.getString(5)).isEqualTo(utf05); + assertThat(rs.getString(6)).isEqualTo(utf06); + assertThat(rs.getString(7)).isEqualTo(utf07); + assertThat(rs.getString(8)).isEqualTo(utf08); + rs.close(); + } + + @Test + public void batch() throws SQLException { + ResultSet rs; + + stat.executeUpdate("create table test (c1, c2, c3, c4);"); + PreparedStatement prep = conn.prepareStatement("insert into test values (?,?,?,?);"); + for (int i = 0; i < 10; i++) { + prep.setInt(1, Integer.MIN_VALUE + i); + prep.setFloat(2, Float.MIN_VALUE + i); + prep.setString(3, "Hello " + i); + prep.setDouble(4, Double.MAX_VALUE + i); + prep.addBatch(); + } + assertThat(prep.executeBatch()).containsExactly(1, 1, 1, 1, 1, 1, 1, 1, 1, 1); + prep.close(); + + rs = stat.executeQuery("select * from test;"); + for (int i = 0; i < 10; i++) { + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(1)).isEqualTo(Integer.MIN_VALUE + i); + assertThat(rs.getFloat(2)).isCloseTo(Float.MIN_VALUE + i, offset(0.0001F)); + assertThat(rs.getString(3)).isEqualTo("Hello " + i); + assertThat(rs.getDouble(4)).isCloseTo(Double.MAX_VALUE + i, offset(0.0001)); + } + rs.close(); + stat.executeUpdate("drop table test;"); + } + + @Test + public void testExecuteBatch() throws Exception { + stat.executeUpdate("create table t (c text);"); + PreparedStatement prep = conn.prepareStatement("insert into t values (?);"); + prep.setString(1, "a"); + prep.addBatch(); + int call1_length = prep.executeBatch().length; + prep.setString(1, "b"); + prep.addBatch(); + int call2_length = prep.executeBatch().length; + + assertThat(call1_length).isEqualTo(1); + assertThat(call2_length).isEqualTo(1); + + ResultSet rs = stat.executeQuery("select * from t"); + rs.next(); + assertThat(rs.getString(1)).isEqualTo("a"); + rs.next(); + assertThat(rs.getString(1)).isEqualTo("b"); + } + + @Test + public void dblock() throws SQLException { + stat.executeUpdate("create table test (c1);"); + stat.executeUpdate("insert into test values (1);"); + conn.prepareStatement("select * from test;").executeQuery().close(); + stat.executeUpdate("drop table test;"); + } + + @Test + public void dbclose() throws SQLException { + conn.prepareStatement("select ?;").setString(1, "Hello World"); + conn.prepareStatement("select null;").close(); + conn.prepareStatement("select null;").executeQuery().close(); + conn.prepareStatement("create table t (c);").executeUpdate(); + conn.prepareStatement("select null;"); + } + + @Test + public void batchOneParam() throws SQLException { + stat.executeUpdate("create table test (c1);"); + PreparedStatement prep = conn.prepareStatement("insert into test values (?);"); + for (int i = 0; i < 10; i++) { + prep.setInt(1, Integer.MIN_VALUE + i); + prep.addBatch(); + } + assertThat(new int[] {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}).containsExactly(prep.executeBatch()); + prep.close(); + ResultSet rs = stat.executeQuery("select count(*) from test;"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(1)).isEqualTo(10); + rs.close(); + } + + @Test + public void batchZeroParams() throws Exception { + stat.executeUpdate("create table test (c1);"); + PreparedStatement prep = conn.prepareStatement("insert into test values (5);"); + for (int i = 0; i < 10; i++) { + prep.addBatch(); + } + assertThat(new int[] {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}).containsExactly(prep.executeBatch()); + prep.close(); + ResultSet rs = stat.executeQuery("select count(*) from test;"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(1)).isEqualTo(10); + rs.close(); + } + + @Test + public void paramMetaData() throws SQLException { + PreparedStatement prep = conn.prepareStatement("select ?,?,?,?;"); + assertThat(prep.getParameterMetaData().getParameterCount()).isEqualTo(4); + } + + @Test + public void metaData() throws SQLException { + PreparedStatement prep = conn.prepareStatement("select ? as col1, ? as col2, ? as delta;"); + ResultSetMetaData meta = prep.getMetaData(); + if (isLibsql()) { + // Hrana has no describe request, so metadata is available only after execution. + assertThat(meta).isNull(); + } else { + assertThat(meta.getColumnCount()).isEqualTo(3); + assertThat(meta.getColumnName(1)).isEqualTo("col1"); + assertThat(meta.getColumnName(2)).isEqualTo("col2"); + assertThat(meta.getColumnName(3)).isEqualTo("delta"); + assertThat(meta.getColumnType(1)).isEqualTo(Types.NUMERIC); + assertThat(meta.getColumnType(2)).isEqualTo(Types.NUMERIC); + assertThat(meta.getColumnType(3)).isEqualTo(Types.NUMERIC); + } + + prep.setInt(1, 2); + prep.setInt(2, 3); + prep.setInt(3, -1); + meta = prep.executeQuery().getMetaData(); + assertThat(meta.getColumnCount()).isEqualTo(3); + prep.close(); + } + + @Test + public void date1() throws SQLException { + Date d1 = new Date(987654321); + + stat.execute("create table t (c1);"); + PreparedStatement prep = conn.prepareStatement("insert into t values(?);"); + prep.setDate(1, d1); + prep.executeUpdate(); + + ResultSet rs = stat.executeQuery("select c1 from t;"); + assertThat(rs.next()).isTrue(); + if (isLibsql()) { + // java.sql DATE carries no time, so compare the calendar date rather than + // xerial's epoch-millis round trip. + assertThat(rs.getDate(1).toLocalDate()).isEqualTo(d1.toLocalDate()); + } else { + assertThat(rs.getLong(1)).isEqualTo(d1.getTime()); + assertThat(rs.getDate(1)).isEqualTo(d1); + } + rs.close(); + } + + @Test + public void date2() throws SQLException { + assumeFalse(isLibsql(), "xerial binds setDate as epoch millis; java.sql DATE carries no time"); + Date d1 = new Date(1092941466000L); + stat.execute("create table t (c1);"); + PreparedStatement prep = + conn.prepareStatement("insert into t values (datetime(?/1000, 'unixepoch'));"); + prep.setDate(1, d1); + prep.executeUpdate(); + + ResultSet rs = stat.executeQuery("select strftime('%s', c1) * 1000 from t;"); + assertThat(rs.next()).isTrue(); + if (isLibsql()) { + assertThat(rs.getDate(1).toLocalDate()).isEqualTo(d1.toLocalDate()); + } else { + assertThat(rs.getLong(1)).isEqualTo(d1.getTime()); + assertThat(rs.getDate(1)).isEqualTo(d1); + } + } + + @Test + public void changeSchema() throws SQLException { + stat.execute("create table t (c1);"); + PreparedStatement prep = conn.prepareStatement("insert into t values (?);"); + conn.createStatement().execute("create table t2 (c2);"); + prep.setInt(1, 1000); + prep.execute(); + prep.executeUpdate(); + } + + // @Ignore + // @Test + // public void multipleStatements() throws SQLException + // { + // PreparedStatement prep = conn + // .prepareStatement("create table person (id integer, name string); insert into + // person values(1, 'leo'); insert into person values(2, 'yui');"); + // prep.executeUpdate(); + // + // ResultSet rs = conn.createStatement().executeQuery("select * from person"); + // assertTrue(rs.next()); + // assertTrue(rs.next()); + // } + + @Test + public void reusingSetValues() throws SQLException { + PreparedStatement prep = conn.prepareStatement("select ?,?;"); + prep.setInt(1, 9); + + for (int i = 0; i < 10; i++) { + prep.setInt(2, i); + ResultSet rs = prep.executeQuery(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(1)).isEqualTo(9); + assertThat(rs.getInt(2)).isEqualTo(i); + } + + for (int i = 0; i < 10; i++) { + prep.setInt(2, i); + ResultSet rs = prep.executeQuery(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(1)).isEqualTo(9); + assertThat(rs.getInt(2)).isEqualTo(i); + rs.close(); + } + + prep.close(); + } + + @Test + public void clearParameters() throws SQLException { + stat.executeUpdate( + "create table tbl (colid integer primary key AUTOINCREMENT, col varchar)"); + stat.executeUpdate("insert into tbl(col) values (\"foo\")"); + stat.executeUpdate("insert into tbl(col) values (?)"); + + PreparedStatement prep = conn.prepareStatement("select colid from tbl where col = ?"); + + prep.setString(1, "foo"); + + ResultSet rs = prep.executeQuery(); + prep.clearParameters(); + rs.next(); + + assertThat(rs.getInt(1)).isEqualTo(1); + + rs.close(); + + // should not throw + prep.execute(); + + // should not throw + PreparedStatement nullPrep = + conn.prepareStatement("select colid from tbl where col is null"); + rs = nullPrep.executeQuery(); + rs.next(); + + // gets the row with the NULL column + assertThat(rs.getInt(1)).isEqualTo(2); + + rs.close(); + nullPrep.close(); + } + + @Test + public void preparedStatementShouldNotThrowIfNotAllParamsSet() throws SQLException { + PreparedStatement prep = conn.prepareStatement("select ? as col1, ? as col2, ? as col3;"); + ResultSetMetaData meta = prep.getMetaData(); + + if (isLibsql()) { + // Hrana has no describe request, so metadata is available only after execution. + assertThat(meta).isNull(); + } else { + // leaves 0 and 1 unbound + assertThat(meta.getColumnCount()).isEqualTo(3); + } + + // we only set one 1 param of the expected 3 params + prep.setInt(1, 2); + prep.executeQuery(); + prep.close(); + } + + @Test + public void preparedStatementShouldNotThrowIfNotAllParamsSetBatch() throws SQLException { + stat.executeUpdate("create table test (c1, c2);"); + PreparedStatement prep = conn.prepareStatement("insert into test values (?,?);"); + + // leaves param 0 unbound + prep.setInt(1, 1); + + prep.addBatch(); + } + + @Test + public void noSuchTable() throws SQLException { + if (isLibsql()) { + // Hrana has no describe request, so prepare cannot validate the SQL; the + // server reports the missing table when the statement executes. + PreparedStatement prep = conn.prepareStatement("select * from doesnotexist;"); + assertThatExceptionOfType(SQLException.class).isThrownBy(prep::executeQuery); + } else { + assertThatExceptionOfType(SQLException.class) + .isThrownBy(() -> conn.prepareStatement("select * from doesnotexist;")); + } + } + + @Test + public void noSuchCol() throws SQLException { + if (isLibsql()) { + PreparedStatement prep = conn.prepareStatement("select notacol from (select 1);"); + assertThatExceptionOfType(SQLException.class).isThrownBy(prep::executeQuery); + } else { + assertThatExceptionOfType(SQLException.class) + .isThrownBy(() -> conn.prepareStatement("select notacol from (select 1);")); + } + } + + @Test + public void noSuchColName() throws SQLException { + ResultSet rs = conn.prepareStatement("select 1;").executeQuery(); + assertThat(rs.next()).isTrue(); + assertThatExceptionOfType(SQLException.class).isThrownBy(() -> rs.getInt("noSuchColName")); + } + + @Test + public void getMoreResultsDoesNotCloseStatement() throws SQLException { + PreparedStatement ps = conn.prepareStatement("select ?"); + ps.setString(1, "Hello"); + + ResultSet rs = ps.executeQuery(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString(1)).isEqualTo("Hello"); + assertThat(rs.next()).isFalse(); + + assertThat(ps.getMoreResults()).isFalse(); + assertThat(rs.isClosed()).isTrue(); + assertThat(ps.isClosed()).isFalse(); + + assertThatNoException().isThrownBy(ps::clearParameters); + } + + @Test + public void gh810_getMoreResults_and_getUpdateCount() throws SQLException { + stat.executeUpdate("create table t(i int)"); + + PreparedStatement ps = conn.prepareStatement("update t set i = 0 where false"); + assertThat(ps.execute()).isFalse(); + assertThat(ps.getUpdateCount()).isEqualTo(0); + assertThat(ps.getMoreResults()).isFalse(); + assertThat(ps.getUpdateCount()).isEqualTo(-1); + } + + @Test + public void executeUpdateCount() throws SQLException { + PreparedStatement ps1 = conn.prepareStatement("create table test (c1)"); + assertThat(ps1.execute()).isFalse(); + + PreparedStatement ps2 = conn.prepareStatement("insert into test values('abc'),('def')"); + assertThat(ps2.execute()).isFalse(); + assertThat(ps2.getUpdateCount()).isEqualTo(2); + assertThat(ps2.getMoreResults()).isFalse(); + assertThat(ps2.getUpdateCount()).isEqualTo(-1); + + assertThat(ps1.getUpdateCount()).isEqualTo(0); + assertThat(ps1.getMoreResults()).isFalse(); + assertThat(ps1.getUpdateCount()).isEqualTo(-1); + } + + @Test + public void gh811_getMetadata_before_execution() throws SQLException { + try (PreparedStatement ps = conn.prepareStatement("select 1")) { + ps.executeQuery(); + ResultSetMetaData meta = ps.getMetaData(); + assertThat(meta).isNotNull(); + assertThat(meta.getColumnCount()).isEqualTo(1); + assertThat(meta.getColumnClassName(1)).isEqualTo("java.lang.Integer"); + } + + try (PreparedStatement ps = conn.prepareStatement("select 1")) { + ResultSetMetaData meta = ps.getMetaData(); + if (isLibsql()) { + // Hrana has no describe request, so metadata is available only after execution. + assertThat(meta).isNull(); + } else { + assertThat(meta).isNotNull(); + assertThat(meta.getColumnCount()).isEqualTo(1); + assertThat(meta.getColumnClassName(1)).isEqualTo("java.lang.Object"); + } + } + } + + @Test + public void getParameterTypeTest() throws SQLException { + stat.executeUpdate("create table t_int(i INT)"); + + try (PreparedStatement ps = conn.prepareStatement("INSERT INTO t_int VALUES(?)")) { + ps.setLong(1, 100); + if (isLibsql()) { + assertThat(ps.getParameterMetaData().getParameterType(1)).isEqualTo(Types.OTHER); + assertThat(ps.getParameterMetaData().getParameterTypeName(1)).isEqualTo("OTHER"); + } else { + assertThat(ps.getParameterMetaData().getParameterType(1)).isEqualTo(Types.BIGINT); + assertThat(ps.getParameterMetaData().getParameterTypeName(1)).isEqualTo("BIGINT"); + } + } + + stat.executeUpdate("create table t_real(a REAL, b REAL)"); + + try (PreparedStatement ps = conn.prepareStatement("INSERT INTO t_real VALUES(?, ?)")) { + ps.setDouble(1, 100.0); + ps.setFloat(2, 100.0f); + if (isLibsql()) { + assertThat(ps.getParameterMetaData().getParameterType(1)).isEqualTo(Types.OTHER); + assertThat(ps.getParameterMetaData().getParameterTypeName(1)).isEqualTo("OTHER"); + assertThat(ps.getParameterMetaData().getParameterType(2)).isEqualTo(Types.OTHER); + assertThat(ps.getParameterMetaData().getParameterTypeName(2)).isEqualTo("OTHER"); + } else { + assertThat(ps.getParameterMetaData().getParameterType(1)).isEqualTo(Types.REAL); + assertThat(ps.getParameterMetaData().getParameterTypeName(1)).isEqualTo("REAL"); + assertThat(ps.getParameterMetaData().getParameterType(2)).isEqualTo(Types.REAL); + assertThat(ps.getParameterMetaData().getParameterTypeName(2)).isEqualTo("REAL"); + } + } + } + + @Test + void getParameterTypeTest_when_no_parameter_set() throws SQLException { + stat.executeUpdate("create table t_int(i INT)"); + + try (PreparedStatement ps = conn.prepareStatement("INSERT INTO t_int VALUES(?)")) { + if (isLibsql()) { + // Hrana has no describe request, so the parameter type is unknown. + assertThat(ps.getParameterMetaData().getParameterType(1)).isEqualTo(Types.OTHER); + assertThat(ps.getParameterMetaData().getParameterTypeName(1)).isEqualTo("OTHER"); + } else { + assertThatThrownBy(() -> ps.getParameterMetaData().getParameterType(1)) + .isInstanceOf(SQLException.class) + .hasMessage("No parameter has been set yet"); + assertThatThrownBy(() -> ps.getParameterMetaData().getParameterTypeName(1)) + .isInstanceOf(SQLException.class) + .hasMessage("No parameter has been set yet"); + } + } + } + + @Test + public void gh914_reuseExecute() throws SQLException { + try (PreparedStatement ps = conn.prepareStatement("SELECT 1")) { + assertThat(ps.execute()).isTrue(); + ResultSet rs = ps.getResultSet(); + assertThat(rs.next()).isTrue(); + assertThat(rs.next()).isFalse(); + assertThat(ps.getMoreResults()).isFalse(); + + ResultSet rs2 = ps.executeQuery(); + assertThat(rs2).isNotNull(); + } + } + + @Test + public void gh1002_pi() throws SQLException { + BigDecimal pi = new BigDecimal("3.14"); + stat.executeUpdate("create table gh1002(nr number(10,2))"); + + try (PreparedStatement ps = conn.prepareStatement("insert into gh1002 values (?)")) { + ps.setBigDecimal(1, pi); + ps.execute(); + } + + ResultSet rs = stat.executeQuery("select nr from gh1002"); + assertThat(rs.getBigDecimal(1)).isEqualTo(pi); + } + + @Test + public void gh1002_pi_real() throws SQLException { + BigDecimal pi = new BigDecimal("3.14"); + stat.executeUpdate("create table gh1002(nr REAL)"); + + try (PreparedStatement ps = conn.prepareStatement("insert into gh1002 values (?)")) { + ps.setBigDecimal(1, pi); + ps.execute(); + } + + ResultSet rs = stat.executeQuery("select nr from gh1002"); + assertThat(rs.getBigDecimal(1)).isEqualTo(pi); + } + + @Test + public void gh1002_pi_text() throws SQLException { + BigDecimal pi = new BigDecimal("3.14"); + stat.executeUpdate("create table gh1002(nr TEXT)"); + + try (PreparedStatement ps = conn.prepareStatement("insert into gh1002 values (?)")) { + ps.setBigDecimal(1, pi); + ps.execute(); + } + + ResultSet rs = stat.executeQuery("select nr from gh1002"); + assertThat(rs.getBigDecimal(1)).isEqualTo(pi); + } +} diff --git a/upstream-xerial_sqlite-jdbc/src/test/java/org/sqlite/StatementTest.java b/upstream-xerial_sqlite-jdbc/src/test/java/org/sqlite/StatementTest.java new file mode 100644 index 0000000..693dbf6 --- /dev/null +++ b/upstream-xerial_sqlite-jdbc/src/test/java/org/sqlite/StatementTest.java @@ -0,0 +1,701 @@ +package org.sqlite; + +import static org.assertj.core.api.Assertions.*; +import static org.assertj.core.data.Offset.offset; +import static org.junit.jupiter.api.Assumptions.assumeFalse; + +import java.nio.charset.StandardCharsets; +import java.sql.BatchUpdateException; +import java.sql.Connection; +import java.sql.Date; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.Statement; +import java.time.Instant; +import java.util.Calendar; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +/** These tests are designed to stress Statements on memory databases. */ +public class StatementTest { + private Connection conn; + private Statement stat; + + private boolean isLibsql() throws SQLException { + return conn.getMetaData().getDriverName().contains("libSQL"); + } + + @BeforeEach + public void connect() throws Exception { + conn = DriverManager.getConnection("jdbc:sqlite:"); + stat = conn.createStatement(); + } + + @AfterEach + public void close() throws SQLException { + stat.close(); + conn.close(); + } + + @Test + public void executeUpdate() throws SQLException { + assertThat(stat.executeUpdate("create table s1 (c1);")).isEqualTo(0); + assertThat(stat.executeUpdate("insert into s1 values (0);")).isEqualTo(1); + assertThat(stat.executeUpdate("insert into s1 values (1);")).isEqualTo(1); + assertThat(stat.executeUpdate("insert into s1 values (2);")).isEqualTo(1); + assertThat(stat.executeUpdate("update s1 set c1 = 5;")).isEqualTo(3); + // count_changes_pgrama. truncate_optimization + assertThat(stat.executeUpdate("delete from s1;")).isEqualTo(3); + + // multiple SQL statements: Hrana carries one statement per Stmt and sqld + // rejects a multi-statement string, so the xerial expectation is control-only. + if (!isLibsql()) { + assertThat(stat.executeUpdate("insert into s1 values (11);" + "insert into s1 values (12)")) + .isEqualTo(2); + assertThat( + stat.executeUpdate( + "update s1 set c1 = 21 where c1 = 11;" + + "update s1 set c1 = 22 where c1 = 12;" + + "update s1 set c1 = 23 where c1 = 13")) + .isEqualTo(2); // c1 = 13 does not exist + assertThat( + stat.executeUpdate( + "delete from s1 where c1 = 21;" + + "delete from s1 where c1 = 22;" + + "delete from s1 where c1 = 23")) + .isEqualTo(2); // c1 = 23 does not exist + } + + assertThat(stat.executeUpdate("drop table s1;")).isEqualTo(0); + } + + @Test + public void emptyRS() throws SQLException { + ResultSet rs = stat.executeQuery("select null limit 0;"); + assertThat(rs.next()).isFalse(); + rs.close(); + } + + @Test + public void singleRowRS() throws SQLException { + ResultSet rs = stat.executeQuery("select " + Integer.MAX_VALUE + ";"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(1)).isEqualTo(Integer.MAX_VALUE); + assertThat(rs.getString(1)).isEqualTo(Integer.toString(Integer.MAX_VALUE)); + assertThat(0.001) + .isCloseTo(new Integer(Integer.MAX_VALUE).doubleValue(), offset(rs.getDouble(1))); + assertThat(rs.next()).isFalse(); + rs.close(); + } + + @Test + public void twoRowRS() throws SQLException { + ResultSet rs = stat.executeQuery("select 9 union all select 7;"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(1)).isEqualTo(9); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(1)).isEqualTo(7); + assertThat(rs.next()).isFalse(); + rs.close(); + } + + @Test + public void autoClose() throws SQLException { + conn.createStatement().executeQuery("select 1;"); + } + + @Test + public void stringRS() throws SQLException { + ResultSet rs = stat.executeQuery("select \"Russell\";"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString(1)).isEqualTo("Russell"); + assertThat(rs.next()).isFalse(); + rs.close(); + } + + @Test + public void execute() throws SQLException { + assertThat(stat.execute("select null;")).isTrue(); + ResultSet rs = stat.getResultSet(); + assertThat(rs).isNotNull(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString(1)).isNull(); + assertThat(rs.wasNull()).isTrue(); + assertThat(stat.getMoreResults()).isFalse(); + assertThat(stat.getUpdateCount()).isEqualTo(-1); + assertThat(stat.isClosed()).isFalse(); + assertThat(stat.getResultSet()).isNull(); + + assertThat(stat.execute("select null;")).isTrue(); + assertThat(stat.getMoreResults()).isFalse(); + assertThat(stat.getUpdateCount()).isEqualTo(-1); + assertThat(stat.isClosed()).isFalse(); + assertThat(stat.getResultSet()).isNull(); + + assertThat(stat.execute("create table test (c1);")).isFalse(); + assertThat(stat.getUpdateCount()).isEqualTo(0); + assertThat(stat.getMoreResults()).isFalse(); + assertThat(stat.getUpdateCount()).isEqualTo(-1); + assertThat(stat.isClosed()).isFalse(); + assertThat(stat.getResultSet()).isNull(); + } + + @Test + public void gh_809_execute_reuseStatement() throws SQLException { + for (int i = 0; i < 2; i++) { + assertThat(stat.execute("select 1")).isTrue(); + + try (ResultSet rs = stat.getResultSet()) { + assertThat(rs).isNotNull(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(1)).isEqualTo(1); + assertThat(rs.next()).isFalse(); + } + + assertThat(stat.getMoreResults()).isFalse(); + assertThat(stat.getUpdateCount()).isEqualTo(-1); + } + } + + @Test + public void gh_809_executeQuery_reuseStatement() throws SQLException { + for (int i = 0; i < 2; i++) { + ResultSet rs = stat.executeQuery("select 1"); + + assertThat(rs).isNotNull(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(1)).isEqualTo(1); + assertThat(rs.next()).isFalse(); + + assertThat(stat.getMoreResults()).isFalse(); + assertThat(stat.getUpdateCount()).isEqualTo(-1); + } + } + + @Test + public void executeUpdateCount() throws SQLException { + assertThat(stat.execute("create table test (c1);")).isFalse(); + + Statement stat2 = conn.createStatement(); + assertThat(stat2.execute("insert into test values('abc'),('def');")).isFalse(); + assertThat(stat2.getUpdateCount()).isEqualTo(2); + assertThat(stat2.getMoreResults()).isFalse(); + assertThat(stat2.getUpdateCount()).isEqualTo(-1); + + assertThat(stat.getUpdateCount()).isEqualTo(0); + assertThat(stat.getMoreResults()).isFalse(); + assertThat(stat.getUpdateCount()).isEqualTo(-1); + } + + @Test + public void colNameAccess() throws SQLException { + assertThat(stat.executeUpdate("create table tab (id, firstname, surname);")).isEqualTo(0); + assertThat(stat.executeUpdate("insert into tab values (0, 'Bob', 'Builder');")) + .isEqualTo(1); + assertThat(stat.executeUpdate("insert into tab values (1, 'Fred', 'Blogs');")).isEqualTo(1); + assertThat(stat.executeUpdate("insert into tab values (2, 'John', 'Smith');")).isEqualTo(1); + ResultSet rs = stat.executeQuery("select * from tab;"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt("id")).isEqualTo(0); + assertThat(rs.getString("firstname")).isEqualTo("Bob"); + assertThat(rs.getString("surname")).isEqualTo("Builder"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt("id")).isEqualTo(1); + assertThat(rs.getString("firstname")).isEqualTo("Fred"); + assertThat(rs.getString("surname")).isEqualTo("Blogs"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt("id")).isEqualTo(2); + assertThat(rs.getString("id")).isEqualTo("2"); + assertThat(rs.getString("firstname")).isEqualTo("John"); + assertThat(rs.getString("surname")).isEqualTo("Smith"); + assertThat(rs.next()).isFalse(); + rs.close(); + assertThat(stat.executeUpdate("drop table tab;")).isEqualTo(0); + } + + @Test + public void nulls() throws SQLException { + ResultSet rs = stat.executeQuery("select null union all select null;"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString(1)).isNull(); + assertThat(rs.wasNull()).isTrue(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString(1)).isNull(); + assertThat(rs.wasNull()).isTrue(); + assertThat(rs.next()).isFalse(); + rs.close(); + } + + @Test + public void nullsForGetObject() throws SQLException { + ResultSet rs = stat.executeQuery("select 1, null union all select null, null;"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString(1)).isNotNull(); + assertThat(rs.wasNull()).isFalse(); + assertThat(rs.getObject(2)).isNull(); + assertThat(rs.wasNull()).isTrue(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getObject(2)).isNull(); + assertThat(rs.wasNull()).isTrue(); + assertThat(rs.getObject(1)).isNull(); + assertThat(rs.wasNull()).isTrue(); + assertThat(rs.next()).isFalse(); + rs.close(); + } + + @Test + public void tempTable() throws SQLException { + assumeFalse(isLibsql(), "sqld rejects CREATE TEMP TABLE with SQL_PARSE_ERROR"); + assertThat(stat.executeUpdate("create temp table myTemp (a);")).isEqualTo(0); + assertThat(stat.executeUpdate("insert into myTemp values (2);")).isEqualTo(1); + } + + @Test + public void insert1000() throws SQLException { + assertThat(stat.executeUpdate("create table in1000 (a);")).isEqualTo(0); + conn.setAutoCommit(false); + for (int i = 0; i < 1000; i++) { + assertThat(stat.executeUpdate("insert into in1000 values (" + i + ");")).isEqualTo(1); + } + conn.commit(); + + ResultSet rs = stat.executeQuery("select count(a) from in1000;"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(1)).isEqualTo(1000); + rs.close(); + + assertThat(stat.executeUpdate("drop table in1000;")).isEqualTo(0); + } + + @Test + public void batch() throws SQLException { + stat.addBatch("create table batch (c1);"); + stat.addBatch("insert into batch values (1);"); + stat.addBatch("insert into batch values (1);"); + stat.addBatch("insert into batch values (2);"); + stat.addBatch("insert into batch values (3);"); + stat.addBatch("insert into batch values (4);"); + assertThat(stat.executeBatch()).containsExactly(0, 1, 1, 1, 1, 1); + assertThat(stat.executeBatch()).isEmpty(); + stat.clearBatch(); + stat.addBatch("insert into batch values (9);"); + assertThat(stat.executeBatch()).containsExactly(1); + assertThat(stat.executeBatch()).isEmpty(); + stat.clearBatch(); + stat.addBatch("insert into batch values (7);"); + stat.addBatch("insert into batch values (7);"); + assertThat(stat.executeBatch()).containsExactly(1, 1); + stat.clearBatch(); + + ResultSet rs = stat.executeQuery("select count(*) from batch;"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(1)).isEqualTo(8); + rs.close(); + } + + @Test + public void closeOnFalseNext() throws SQLException { + stat.executeUpdate("create table t1 (c1);"); + conn.createStatement().executeQuery("select * from t1;").next(); + stat.executeUpdate("drop table t1;"); + } + + @Test + public void getGeneratedKeys() throws SQLException { + ResultSet rs; + stat.executeUpdate("create table t1 (c1 integer primary key, v);"); + + // test standard insert operation + stat.executeUpdate("insert into t1 (v) values ('red');"); + rs = stat.getGeneratedKeys(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(1)).isEqualTo(1); + rs.close(); + + stat.executeUpdate("insert into t1 (v) values ('blue');"); + rs = stat.getGeneratedKeys(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(1)).isEqualTo(2); + rs.close(); + + // test INSERT ith special replace keyword. This will trigger a primary key conflict on the + // first + // inserted row ('red') and replace the record with a value of 'yellow' with the same + // primary + // key. The value returned from getGeneratedKeys should be primary key of the replaced + // record + stat.executeUpdate("replace into t1 (c1, v) values (1, 'yellow');"); + rs = stat.getGeneratedKeys(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(1)).isEqualTo(1); + rs.close(); + + // test INSERT with common table expression + stat.executeUpdate( + " WITH colors as (select 'green' as color) \n" + + "INSERT into t1 (v) select color from colors;"); + rs = stat.getGeneratedKeys(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(1)).isEqualTo(3); + rs.close(); + + stat.close(); + + // generated keys are now attached to the statement. calling getGeneratedKeys + // on a statement that has not generated any should return an empty result set + Statement stat2 = conn.createStatement(); + stat.executeUpdate( + "with colors as (select 'insert' as color) update t1 set v = (select color from colors);"); + rs = stat2.getGeneratedKeys(); + assertThat(rs).isNotNull(); + assertThat(rs.next()).isFalse(); + stat2.close(); + } + + @Test + public void getGeneratedKeysIsStatementSpecific() throws SQLException { + /* this test ensures that the results of getGeneratedKeys are tied to + a specific statement. To verify this, we create two separate Statement + objects and then execute inserts on both. We then make getGeneratedKeys() + calls and verify that the two separate expected values are returned. + + Note that the old implementation of getGeneratedKeys was called lazily, so + the result of both getGeneratedKeys calls would be the same value, the row ID + of the last insert on the connection. As a result it was unsafe to use + with multiple statements or in a multithreaded application. + */ + stat.executeUpdate("create table t1 (c1 integer primary key, v);"); + + ResultSet rs1; + Statement stat1 = conn.createStatement(); + ResultSet rs2; + Statement stat2 = conn.createStatement(); + + stat1.executeUpdate("insert into t1 (v) values ('red');"); + stat2.executeUpdate("insert into t1 (v) values ('blue');"); + + rs2 = stat2.getGeneratedKeys(); + rs1 = stat1.getGeneratedKeys(); + + assertThat(rs1.next()).isTrue(); + assertThat(rs1.getInt(1)).isEqualTo(1); + rs1.close(); + + assertThat(rs2.next()).isTrue(); + assertThat(rs2.getInt(1)).isEqualTo(2); + rs2.close(); + + stat1.close(); + stat2.close(); + } + + @Test + public void isBeforeFirst() throws SQLException { + ResultSet rs = stat.executeQuery("select 1 union all select 2;"); + assertThat(rs.isBeforeFirst()).isTrue(); + assertThat(rs.next()).isTrue(); + assertThat(rs.isFirst()).isTrue(); + assertThat(rs.getInt(1)).isEqualTo(1); + assertThat(rs.next()).isTrue(); + assertThat(rs.isBeforeFirst()).isFalse(); + assertThat(rs.isFirst()).isFalse(); + assertThat(rs.getInt(1)).isEqualTo(2); + assertThat(rs.next()).isFalse(); + assertThat(rs.isBeforeFirst()).isFalse(); + rs.close(); + } + + @Test + public void columnNaming() throws SQLException { + stat.executeUpdate("create table t1 (c1 integer);"); + stat.executeUpdate("create table t2 (c1 integer);"); + stat.executeUpdate("insert into t1 values (1);"); + stat.executeUpdate("insert into t2 values (1);"); + ResultSet rs = stat.executeQuery("select a.c1 AS c1 from t1 a, t2 where a.c1=t2.c1;"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt("c1")).isEqualTo(1); + rs.close(); + } + + @Test + public void nullDate() throws SQLException { + ResultSet rs = stat.executeQuery("select null;"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getDate(1)).isNull(); + assertThat(rs.getTime(1)).isNull(); + assertThat(rs.getTimestamp(1)).isNull(); + + assertThat(rs.getDate(1, Calendar.getInstance())).isNull(); + assertThat(rs.getTime(1, Calendar.getInstance())).isNull(); + assertThat(rs.getTimestamp(1, Calendar.getInstance())).isNull(); + rs.close(); + } + + @Test + public void emptyDate() throws SQLException { + ResultSet rs = stat.executeQuery("select '';"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getDate(1)).isNull(); + assertThat(rs.getTime(1)).isNull(); + assertThat(rs.getTimestamp(1)).isNull(); + + assertThat(rs.getDate(1, Calendar.getInstance())).isNull(); + assertThat(rs.getTime(1, Calendar.getInstance())).isNull(); + assertThat(rs.getTimestamp(1, Calendar.getInstance())).isNull(); + rs.close(); + } + + @Disabled + @Test + public void ambiguousColumnNaming() throws SQLException { + stat.executeUpdate("create table t1 (c1 int);"); + stat.executeUpdate("create table t2 (c1 int, c2 int);"); + stat.executeUpdate("insert into t1 values (1);"); + stat.executeUpdate("insert into t2 values (2, 1);"); + ResultSet rs = stat.executeQuery("select a.c1, b.c1 from t1 a, t2 b where a.c1=b.c2;"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt("c1")).isEqualTo(1); + rs.close(); + } + + @Test + public void failToDropWhenRSOpen() throws SQLException { + stat.executeUpdate("create table t1 (c1);"); + stat.executeUpdate("insert into t1 values (4);"); + stat.executeUpdate("insert into t1 values (4);"); + conn.createStatement().executeQuery("select * from t1;").next(); + if (isLibsql()) { + // Hrana streams are independent and rows are materialized, so no local + // read lock keeps the table open. + assertThat(stat.executeUpdate("drop table t1;")).isEqualTo(0); + } else { + assertThatExceptionOfType(SQLException.class) + .isThrownBy(() -> stat.executeUpdate("drop table t1;")); + } + } + + @Test + public void executeNoRS() { + assertThatExceptionOfType(SQLException.class) + .isThrownBy(() -> stat.execute("insert into test values (8);")); + } + + @Test + public void executeClearRS() throws SQLException { + assertThat(stat.execute("select null;")).isTrue(); + assertThat(stat.getResultSet()).isNotNull(); + assertThatExceptionOfType(SQLException.class) + .as("requesting the same result set twice should throw an exception") + .isThrownBy(() -> stat.getResultSet()); + assertThat(stat.getMoreResults()).isFalse(); + assertThat(stat.isClosed()).isFalse(); + assertThat(stat.getResultSet()).isNull(); + assertThat(stat.getUpdateCount()).isEqualTo(-1); + } + + @Test + public void getMoreResultsArguments() throws SQLException { + assertThat(stat.execute("select null;")).isTrue(); + assertThat(stat.getResultSet()).isNotNull(); + assertThatExceptionOfType(SQLException.class) + .as("getMoreResults only accepts valid arguments") + .isThrownBy(() -> stat.getMoreResults(15)); + assertThatExceptionOfType(SQLFeatureNotSupportedException.class) + .as("getMoreResults with CLOSE_ALL_RESULTS is not supported") + .isThrownBy(() -> stat.getMoreResults(Statement.CLOSE_ALL_RESULTS)); + assertThatExceptionOfType(SQLFeatureNotSupportedException.class) + .as("getMoreResults with KEEP_CURRENT_RESULT is not supported") + .isThrownBy(() -> stat.getMoreResults(Statement.KEEP_CURRENT_RESULT)); + } + + @Test + public void batchReturnsResults() throws SQLException { + stat.addBatch("select null;"); + assertThatExceptionOfType(BatchUpdateException.class).isThrownBy(() -> stat.executeBatch()); + } + + @Test + public void noSuchTable() { + assertThatExceptionOfType(SQLException.class) + .isThrownBy(() -> stat.executeQuery("select * from doesnotexist;")); + } + + @Test + public void noSuchCol() { + assertThatExceptionOfType(SQLException.class) + .isThrownBy(() -> stat.executeQuery("select notacol from (select 1);")); + } + + @Test + public void noSuchColName() throws SQLException { + ResultSet rs = stat.executeQuery("select 1;"); + assertThat(rs.next()).isTrue(); + assertThatExceptionOfType(SQLException.class).isThrownBy(() -> rs.getInt("noSuchColName")); + } + + @Test + public void multipleStatements() throws SQLException { + assumeFalse(isLibsql(), "sqld rejects a multi-statement SQL string with SQL_MANY_STATEMENTS"); + // ; insert into person values(1,'leo') + stat.executeUpdate( + "create table person (id integer, name string); " + + "insert into person values(1, 'leo'); insert into person values(2, 'yui');"); + ResultSet rs = stat.executeQuery("select * from person"); + assertThat(rs.next()).isTrue(); + assertThat(rs.next()).isTrue(); + } + + @Test + public void blobTest() throws SQLException { + stat.executeUpdate("CREATE TABLE Foo (KeyId INTEGER, Stuff BLOB)"); + } + + @Test + public void bytesTest() throws SQLException { + stat.executeUpdate("CREATE TABLE blobs (Blob BLOB)"); + PreparedStatement prep = conn.prepareStatement("insert into blobs values(?)"); + + String str = "This is a test"; + byte[] strBytes = str.getBytes(StandardCharsets.UTF_8); + + prep.setBytes(1, strBytes); + prep.executeUpdate(); + + ResultSet rs = stat.executeQuery("select * from blobs"); + assertThat(rs.next()).isTrue(); + + byte[] resultBytes = rs.getBytes(1); + assertThat(resultBytes).isEqualTo(strBytes); + + String resultStr = rs.getString(1); + assertThat(resultStr).isEqualTo(str); + + byte[] resultBytesAfterConversionToString = rs.getBytes(1); + assertThat(resultBytesAfterConversionToString).isEqualTo(strBytes); + } + + @Test + public void dateTimeTest() throws SQLException { + Date day = new Date(new java.util.Date().getTime()); + + stat.executeUpdate("create table day (time datetime)"); + PreparedStatement prep = conn.prepareStatement("insert into day values(?)"); + prep.setDate(1, day); + prep.executeUpdate(); + ResultSet rs = stat.executeQuery("select * from day"); + assertThat(rs.next()).isTrue(); + Date d = rs.getDate(1); + if (isLibsql()) { + // java.sql DATE carries no time, so compare the calendar date rather than + // xerial's epoch-millis round trip. + assertThat(d.toLocalDate()).isEqualTo(day.toLocalDate()); + } else { + assertThat(d.getTime()).isEqualTo(day.getTime()); + } + } + + @Test + public void defaultDateTimeTest() throws SQLException { + stat.executeUpdate( + "create table daywithdefaultdatetime (id integer, datetime datatime default current_timestamp)"); + PreparedStatement prep = + conn.prepareStatement("insert into daywithdefaultdatetime (id) values (?)"); + prep.setInt(1, 1); + prep.executeUpdate(); + ResultSet rs = stat.executeQuery("select * from daywithdefaultdatetime"); + assertThat(rs.next()).isTrue(); + Date d = rs.getDate(2); + assertThat(d).isNotNull(); + } + + @Test + public void maxRows() throws SQLException { + stat.setMaxRows(1); + ResultSet rs = stat.executeQuery("select 1 union select 2 union select 3"); + + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(1)).isEqualTo(1); + assertThat(rs.next()).isFalse(); + + rs.close(); + stat.setMaxRows(2); + rs = stat.executeQuery("select 1 union select 2 union select 3"); + + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(1)).isEqualTo(1); + assertThat(rs.next()).isTrue(); + assertThat(rs.getInt(1)).isEqualTo(2); + assertThat(rs.next()).isFalse(); + + rs.close(); + } + + @Test + public void setEscapeProcessingToFalse() { + assertThatNoException().isThrownBy(() -> stat.setEscapeProcessing(false)); + } + + @Test + public void setEscapeProcessingToTrue() { + assertThatNoException().isThrownBy(() -> stat.setEscapeProcessing(true)); + } + + @Test + public void unwrapTest() throws SQLException { + assertThat(conn.isWrapperFor(Connection.class)).isTrue(); + assertThat(conn.isWrapperFor(Statement.class)).isFalse(); + assertThat(conn.unwrap(Connection.class)).isEqualTo(conn); + + assertThat(stat.isWrapperFor(Statement.class)).isTrue(); + assertThat(stat.unwrap(Statement.class)).isEqualTo(stat); + + ResultSet rs = stat.executeQuery("select 1"); + + assertThat(rs.isWrapperFor(ResultSet.class)).isTrue(); + assertThat(rs.unwrap(ResultSet.class)).isEqualTo(rs); + + rs.close(); + } + + @Test + public void closeOnCompletionTest() throws Exception { + assertThat(stat.isCloseOnCompletion()).isFalse(); + stat.closeOnCompletion(); + assertThat(stat.isCloseOnCompletion()).isTrue(); + + ResultSet rs = stat.executeQuery("select 1"); + rs.close(); + + assertThat(stat.isClosed()).isTrue(); + } + + @Test + public void setFetchDirection() throws SQLException { + stat.setFetchDirection(ResultSet.FETCH_FORWARD); + stat.setFetchDirection(ResultSet.FETCH_REVERSE); + stat.setFetchDirection(ResultSet.FETCH_UNKNOWN); + } + + @Test + public void setFetchDirectionBadArgument() { + assertThatExceptionOfType(SQLException.class).isThrownBy(() -> stat.setFetchDirection(999)); + } + + @Test + public void getFetchDirection() throws SQLException { + assertThat(stat.getFetchDirection()).isEqualTo(ResultSet.FETCH_FORWARD); + } + + @Test + public void unixepoch() throws SQLException { + ResultSet rs = stat.executeQuery("select unixepoch()"); + long javaEpoch = Instant.now().getEpochSecond(); + + assertThat(rs.getLong(1)).isCloseTo(javaEpoch, offset(1L)); + } +}