diff --git a/NOTICE.txt b/NOTICE.txt index adfaf00..8eb427a 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1,4 +1,12 @@ -This work is based upon +The work in jdbc-connection-pool is base upon + +https://github.com/brettwooldridge/HikariCP + +as of 28 Dec 2021 + +License: Apache 2.0 + +The work in jdbc-query is based upon https://github.com/susom/database diff --git a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/SaturatedPoolTest830.java b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/SaturatedPoolTest.java similarity index 98% rename from jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/SaturatedPoolTest830.java rename to jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/SaturatedPoolTest.java index 8534919..686e1d6 100644 --- a/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/SaturatedPoolTest830.java +++ b/jdbc-connection-pool/src/test/java/org/xbib/io/pool/jdbc/SaturatedPoolTest.java @@ -20,9 +20,9 @@ import org.xbib.jdbc.connection.pool.PoolConfig; import org.xbib.jdbc.connection.pool.PoolDataSource; @ExtendWith(PoolTestExtension.class) -public class SaturatedPoolTest830 { +public class SaturatedPoolTest { - private static final Logger logger = Logger.getLogger(SaturatedPoolTest830.class.getName()); + private static final Logger logger = Logger.getLogger(SaturatedPoolTest.class.getName()); private static final int MAX_POOL_SIZE = 10; diff --git a/jdbc-mariadb/src/main/java/org/xbib/jdbc/mariadb/MariaDB.java b/jdbc-mariadb/src/main/java/org/xbib/jdbc/mariadb/MariaDB.java index 39fbed0..5b6e279 100644 --- a/jdbc-mariadb/src/main/java/org/xbib/jdbc/mariadb/MariaDB.java +++ b/jdbc-mariadb/src/main/java/org/xbib/jdbc/mariadb/MariaDB.java @@ -4,6 +4,7 @@ import org.xbib.jdbc.query.Flavor; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.Locale; public class MariaDB implements Flavor { @@ -26,8 +27,16 @@ public class MariaDB implements Flavor { } @Override - public boolean isNormalizedUpperCase() { - return false; + public String normalizeTableName(String tableName) { + if (tableName == null) { + return tableName; + } + if (tableName.length() > 2) { + if (tableName.startsWith("\"") && tableName.endsWith("\"")) { + return tableName.substring(1, tableName.length() - 1); + } + } + return tableName.toLowerCase(Locale.ROOT); } @Override @@ -52,7 +61,7 @@ public class MariaDB implements Flavor { @Override public void setFloat(PreparedStatement preparedStatement, int i, Float floatValue) throws SQLException { - preparedStatement.setFloat(i, floatValue); + preparedStatement.setDouble(i, floatValue); } @Override @@ -98,6 +107,11 @@ public class MariaDB implements Flavor { return "datetime(3)"; } + @Override + public String columnTypeLocalDateTime() { + return "DATETIME"; + } + @Override public String typeLocalDate() { return "date"; diff --git a/jdbc-mariadb/src/test/java/org/xbib/jdbc/mariadb/test/MariaDBTest.java b/jdbc-mariadb/src/test/java/org/xbib/jdbc/mariadb/test/MariaDBTest.java index 122d29f..9681b9b 100644 --- a/jdbc-mariadb/src/test/java/org/xbib/jdbc/mariadb/test/MariaDBTest.java +++ b/jdbc-mariadb/src/test/java/org/xbib/jdbc/mariadb/test/MariaDBTest.java @@ -1,6 +1,7 @@ package org.xbib.jdbc.mariadb.test; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -9,13 +10,24 @@ import org.xbib.jdbc.query.Config; import org.xbib.jdbc.query.ConfigSupplier; import org.xbib.jdbc.query.DatabaseProvider; import org.xbib.jdbc.query.OptionsOverride; +import org.xbib.jdbc.query.Schema; +import org.xbib.jdbc.query.Sql; +import org.xbib.jdbc.query.SqlArgs; import org.xbib.jdbc.test.CommonTest; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + public class MariaDBTest extends CommonTest { static MariaDBContainer mariaDBContainer; static { + // mariadb 10.3.6 mariaDBContainer = new MariaDBContainer<>("mariadb") .withDatabaseName("testDB") .withUsername("testUser") @@ -88,21 +100,156 @@ public class MariaDBTest extends CommonTest { super.intervals(); } - @Disabled("MariaDB temporarily disabled") - @Override - public void metadataColumnNames() { - super.intervals(); - } - @Disabled("MariaDB temporarily disabled") @Override public void metadataColumnTypes() { super.metadataColumnTypes(); } - @Disabled("MariaDB temporarily disabled") + /** + * MariaDB seems to have different behavior in that is does not convert + * column names. + * I haven't figured out how to smooth over this difference, since all databases + * seem to respect the provided case when it is inside quotes, but don't provide + * a way to tell whether a particular parameter was quoted. + */ @Override + @Test + public void metadataColumnNames() { + db.dropTableQuietly("dbtest"); + new Schema().addTable("dbtest").addColumn("pk").primaryKey().schema().execute(db); + db.toSelect("select Pk, Pk as Foo, Pk as \"Foo\" from dbtest") + .query(rs -> { + Assertions.assertArrayEquals(new String[]{"Pk", "Foo", "Foo"}, rs.getColumnLabels()); + return null; + }); + } + + /** + * This one is adjusted in that the float values are passed as double, because + * the database stores them both as double and there doesn't appear to be a way + * to tell that one was actually declared as a float. + * + * Also, CLOBs are stored as strings ("mediumtext" is just an alias). + */ + @Test public void saveResultAsTable() { - super.saveResultAsTable(); + new Schema().addTable("dbtest") + .addColumn("nbr_integer").asInteger().primaryKey().table() + .addColumn("nbr_long").asLong().table() + .addColumn("nbr_float").asFloat().table() + .addColumn("nbr_double").asDouble().table() + .addColumn("nbr_big_decimal").asBigDecimal(19, 9).table() + .addColumn("str_varchar").asString(80).table() + .addColumn("str_fixed").asStringFixed(1).table() + .addColumn("str_lob").asClob().table() + .addColumn("bin_blob").asBlob().table() + .addColumn("boolean_flag").asBoolean().table() + .addColumn("date_millis").asLocalDateTime().table() + .addColumn("local_date").asLocalDate().schema().execute(db); + db.toInsert("insert into dbtest (nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal, str_varchar," + + " str_fixed, str_lob, bin_blob, boolean_flag, date_millis, local_date) values (?,?,?,?,?,?,?,?,?,?,?,?)") + .argInteger(Integer.MAX_VALUE) + .argLong(Long.MAX_VALUE) + .argDouble((double) Float.MAX_VALUE) + .argDouble(Double.MAX_VALUE) + .argBigDecimal(new BigDecimal("123.456")) + .argString("hello") + .argString("Z") + .argClobString("hello again") + .argBlobBytes(new byte[]{'1', '2'}) + .argBoolean(true) + .argLocalDateTime(now) + .argLocalDate(localDateNow) + .insert(1); + + db.toInsert("insert into dbtest (nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal, str_varchar," + + " str_fixed, str_lob, bin_blob, boolean_flag, date_millis, local_date) values (?,?,?,?,?,?,?,?,?,?,?,?)") + .argInteger(Integer.MIN_VALUE) + .argLong(Long.MIN_VALUE) + .argDouble((double) Float.MIN_VALUE) + .argDouble(Double.MIN_VALUE) + .argBigDecimal(new BigDecimal("-123.456")) + .argString("goodbye") + .argString("A") + .argClobString("bye again") + .argBlobBytes(new byte[]{'3', '4'}) + .argBoolean(false) + .argLocalDateTime(now) + .argLocalDate(localDateNow) + .insert(1); + + String expectedSchema = new Schema().addTable("dbtest2") + .addColumn("nbr_integer").asInteger().table() + .addColumn("nbr_long").asLong().table() + .addColumn("nbr_float").asFloat().table() + .addColumn("nbr_double").asDouble().table() + .addColumn("nbr_big_decimal").asBigDecimal(19, 9).table() + .addColumn("str_varchar").asString(80).table() + .addColumn("str_fixed").asStringFixed(1).table() + .addColumn("str_lob").asClob().table() + .addColumn("bin_blob").asBlob().table() + .addColumn("boolean_flag").asBoolean().table() + .addColumn("date_millis").asLocalDateTime().table() + .addColumn("local_date").asLocalDate() + .schema() + .print(db.flavor()); + + List args = db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal," + + " str_varchar, str_fixed, str_lob, bin_blob, boolean_flag, date_millis, local_date from dbtest") + .query(rs -> { + List result = new ArrayList<>(); + while (rs.next()) { + if (result.size() == 0) { + db.dropTableQuietly("dbtest2"); + Schema schema = new Schema().addTableFromRow("dbtest2", rs).schema(); + assertEquals(expectedSchema, schema.print(db.flavor())); + schema.execute(db); + } + result.add(SqlArgs.readRow(rs)); + } + return result; + }); + + db.toInsert(Sql.insert("dbtest2", args)).insertBatch(); + + assertEquals(2, db.toSelect("select count(*) from dbtest2").queryIntegerOrZero()); + assertEquals(db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal," + + " str_varchar, str_fixed, str_lob, bin_blob, boolean_flag, date_millis, local_date from dbtest order by 1") + .queryMany(SqlArgs::readRow), + db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal," + + " str_varchar, str_fixed, str_lob, bin_blob, boolean_flag, date_millis, local_date from dbtest2 order by 1") + .queryMany(SqlArgs::readRow)); + assertEquals(Arrays.asList( + new SqlArgs() + .argInteger("nbr_integer", Integer.MIN_VALUE) + .argLong("nbr_long", Long.MIN_VALUE) + .argDouble("nbr_float", (double) Float.MIN_VALUE) + .argDouble("nbr_double", Double.MIN_VALUE) + .argBigDecimal("nbr_big_decimal", new BigDecimal("-123.456")) + .argString("str_varchar", "goodbye") + .argString("str_fixed", "A") + .argString("str_lob", "bye again") + .argBlobBytes("bin_blob", new byte[]{'3', '4'}) + .argString("boolean_flag", "N")//.argBoolean("boolean_flag", false) + .argLocalDateTime("date_millis", now) + .argLocalDate("local_date", localDateNow), + new SqlArgs() + .argInteger("nbr_integer", Integer.MAX_VALUE) + .argLong("nbr_long", Long.MAX_VALUE) + .argDouble("nbr_float", (double) Float.MAX_VALUE) + .argDouble("nbr_double", Double.MAX_VALUE) + .argBigDecimal("nbr_big_decimal", new BigDecimal("123.456")) + .argString("str_varchar", "hello") + .argString("str_fixed", "Z") + .argString("str_lob", "hello again") + .argBlobBytes("bin_blob", new byte[]{'1', '2'}) + .argString("boolean_flag", "Y")//.argBoolean("boolean_flag", true) + .argLocalDateTime("date_millis", now) + .argLocalDate("local_date", localDateNow)), + db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal," + + " str_varchar, str_fixed, str_lob, bin_blob, boolean_flag, date_millis, local_date from dbtest2 order by 1") + .queryMany(SqlArgs::readRow)); } + } diff --git a/jdbc-oracle/src/main/java/org/xbib/jdbc/oracle/Oracle.java b/jdbc-oracle/src/main/java/org/xbib/jdbc/oracle/Oracle.java index e431f6e..c714ba5 100644 --- a/jdbc-oracle/src/main/java/org/xbib/jdbc/oracle/Oracle.java +++ b/jdbc-oracle/src/main/java/org/xbib/jdbc/oracle/Oracle.java @@ -28,8 +28,16 @@ public class Oracle implements Flavor { } @Override - public boolean isNormalizedUpperCase() { - return true; + public String normalizeTableName(String tableName) { + if (tableName == null) { + return tableName; + } + if (tableName.length() > 2) { + if (tableName.startsWith("\"") && tableName.endsWith("\"")) { + return tableName.substring(1, tableName.length() - 1); + } + } + return tableName.toUpperCase(); } @Override @@ -87,6 +95,11 @@ public class Oracle implements Flavor { return "timestamp(3)"; } + @Override + public String columnTypeLocalDateTime() { + return "TIMESTAMP"; + } + @Override public String typeLocalDate() { return "date"; diff --git a/jdbc-postgresql/src/main/java/org/xbib/jdbc/postgresql/Postgresql.java b/jdbc-postgresql/src/main/java/org/xbib/jdbc/postgresql/Postgresql.java index f82ec5c..33ab4b7 100644 --- a/jdbc-postgresql/src/main/java/org/xbib/jdbc/postgresql/Postgresql.java +++ b/jdbc-postgresql/src/main/java/org/xbib/jdbc/postgresql/Postgresql.java @@ -4,6 +4,7 @@ import org.xbib.jdbc.query.Flavor; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.Locale; public class Postgresql implements Flavor { @@ -26,8 +27,16 @@ public class Postgresql implements Flavor { } @Override - public boolean isNormalizedUpperCase() { - return false; + public String normalizeTableName(String tableName) { + if (tableName == null) { + return tableName; + } + if (tableName.length() > 2) { + if (tableName.startsWith("\"") && tableName.endsWith("\"")) { + return tableName.substring(1, tableName.length() - 1); + } + } + return tableName.toLowerCase(Locale.ROOT); } @Override @@ -95,6 +104,11 @@ public class Postgresql implements Flavor { return "timestamp(3)"; } + @Override + public String columnTypeLocalDateTime() { + return "timestamp"; + } + @Override public String typeLocalDate() { return "date"; diff --git a/jdbc-postgresql/src/test/java/org/xbib/jdbc/postgresql/test/PostgresqlTest.java b/jdbc-postgresql/src/test/java/org/xbib/jdbc/postgresql/test/PostgresqlTest.java index c62b72c..df0e2f1 100644 --- a/jdbc-postgresql/src/test/java/org/xbib/jdbc/postgresql/test/PostgresqlTest.java +++ b/jdbc-postgresql/src/test/java/org/xbib/jdbc/postgresql/test/PostgresqlTest.java @@ -20,6 +20,7 @@ public class PostgresqlTest extends CommonTest { static PostgreSQLContainer postgreSQLContainer; static { + // postgresql 9.6.12 postgreSQLContainer = new PostgreSQLContainer<>("postgres") .withDatabaseName("testDB") .withUsername("testUser") diff --git a/jdbc-postgresql/src/test/resources/logging.properties b/jdbc-postgresql/src/test/resources/logging.properties index 5885e4d..f0ffda7 100644 --- a/jdbc-postgresql/src/test/resources/logging.properties +++ b/jdbc-postgresql/src/test/resources/logging.properties @@ -8,3 +8,4 @@ java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter java.util.logging.FileHandler.pattern=build/database.log jdk.event.security.level=INFO javax.management.level=INFO +org.postgresql.level=INFO diff --git a/jdbc-query/build.gradle b/jdbc-query/build.gradle index 19de6b8..1f334c1 100644 --- a/jdbc-query/build.gradle +++ b/jdbc-query/build.gradle @@ -1,7 +1,13 @@ dependencies { - api project(":jdbc-connection-pool") + api project(':jdbc-connection-pool') + testImplementation project(':jdbc-test') testImplementation libs.derby testImplementation libs.hsqldb + testImplementation libs.h2 testImplementation libs.testcontainers testImplementation libs.testcontainers.junit.jupiter } + +test { + systemProperty 'user.timezone', 'GMT' +} diff --git a/jdbc-query/src/main/java/module-info.java b/jdbc-query/src/main/java/module-info.java index 19df7a3..935bbe0 100644 --- a/jdbc-query/src/main/java/module-info.java +++ b/jdbc-query/src/main/java/module-info.java @@ -1,12 +1,12 @@ import org.xbib.jdbc.query.Flavor; import org.xbib.jdbc.query.flavor.Derby; +import org.xbib.jdbc.query.flavor.H2; import org.xbib.jdbc.query.flavor.Hsql; -import org.xbib.jdbc.query.flavor.SqlServer; module org.xbib.jdbc.query { uses Flavor; requires org.xbib.jdbc.connection.pool; requires java.sql; exports org.xbib.jdbc.query; - provides Flavor with Derby, Hsql, SqlServer; + provides Flavor with Derby, Hsql, H2; } diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/Database.java b/jdbc-query/src/main/java/org/xbib/jdbc/query/Database.java index 6138778..c3de0f0 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/Database.java +++ b/jdbc-query/src/main/java/org/xbib/jdbc/query/Database.java @@ -11,6 +11,11 @@ import java.util.function.Supplier; * Primary class for accessing a relational (SQL) database. */ public interface Database extends Supplier { + + int jdbcMajorVersion(); + + int jdbcMinorVersion(); + /** * Create a SQL "insert" statement for further manipulation and execution. * Note this call does not actually execute the SQL. @@ -181,18 +186,6 @@ public interface Database extends Supplier { */ Map getColumnSizesOfTable(String tableName); - /** - * Return the DB table name in the normalized form in which it is stored. - * Databases like Oracle, Derby, HSQL store their tables in upper case. - * Databases like postgres and sqlserver use lower case unless configured otherwise. - * If the caller passes in a quoted string, we will leave the name as is, removing - * the quotes. - * - * @param tableName this should be a name, not a pattern - * @return table name in appropriate format for DB lookup - original case, uppercase, or lowercase - */ - String normalizeTableName(String tableName); - /** * Check the JVM time (and timezone) against the database and log a warning * or throw an error if they are too far apart. It is a good idea to do this diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/DatabaseImpl.java b/jdbc-query/src/main/java/org/xbib/jdbc/query/DatabaseImpl.java index 8c635e1..0173ec8 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/DatabaseImpl.java +++ b/jdbc-query/src/main/java/org/xbib/jdbc/query/DatabaseImpl.java @@ -131,16 +131,15 @@ public class DatabaseImpl implements Database { throw new DatabaseException("Unable to rollback transaction", e); } } - + @Override public Connection underlyingConnection() { if (!options.allowConnectionAccess()) { throw new DatabaseException("Calls to underlyingConnection() are not allowed"); } - return connection; } - + @Override public Options options() { return options; @@ -151,7 +150,24 @@ public class DatabaseImpl implements Database { return options.flavor(); } - + @Override + public int jdbcMajorVersion() { + try { + return connection.getMetaData().getJDBCMajorVersion(); + } catch (SQLException e) { + throw new DatabaseException(e); + } + } + + @Override + public int jdbcMinorVersion() { + try { + return connection.getMetaData().getJDBCMinorVersion(); + } catch (SQLException e) { + throw new DatabaseException(e); + } + } + @Override public When when() { return new When(options.flavor()); @@ -181,7 +197,7 @@ public class DatabaseImpl implements Database { if (tableName != null && connection != null) { try { DatabaseMetaData metadata = connection.getMetaData(); - String normalizedTable = normalizeTableName(tableName); + String normalizedTable = flavor().normalizeTableName(tableName); ResultSet resultSet = metadata.getTables(connection.getCatalog(), schemaName, normalizedTable, new String[]{"TABLE", "VIEW"}); while (resultSet.next()) { if (normalizedTable.equals(resultSet.getString("TABLE_NAME"))) { @@ -203,7 +219,7 @@ public class DatabaseImpl implements Database { if (tableName != null && connection != null) { try { DatabaseMetaData metaData = connection.getMetaData(); - String normalizedTable = normalizeTableName(tableName); + String normalizedTable = flavor().normalizeTableName(tableName); ResultSet resultSet = metaData.getColumns(null, null, normalizedTable, "%"); ResultSetMetaData rsmd = resultSet.getMetaData(); int cols = rsmd.getColumnCount(); @@ -229,24 +245,6 @@ public class DatabaseImpl implements Database { return map; } - @Override - public String normalizeTableName(String tableName) { - if (tableName == null) { - return tableName; - } - // If user gave us a quoted string, leave it alone for look up - if (tableName.length() > 2) { - if (tableName.startsWith("\"") && tableName.endsWith("\"")) { - // Remove quotes and return as is. - return tableName.substring(1, tableName.length() - 1); - } - } - if (flavor().isNormalizedUpperCase()) { - return tableName.toUpperCase(); - } - return tableName.toLowerCase(); - } - @Override public void assertTimeSynchronized(long millisToWarn, long millisToError) { toSelect("select ?" + flavor().fromAny()) diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/DatabaseProvider.java b/jdbc-query/src/main/java/org/xbib/jdbc/query/DatabaseProvider.java index 02636a5..d790c4a 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/DatabaseProvider.java +++ b/jdbc-query/src/main/java/org/xbib/jdbc/query/DatabaseProvider.java @@ -6,12 +6,8 @@ import org.xbib.jdbc.query.util.Metric; import javax.sql.DataSource; import java.io.Closeable; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStreamReader; import java.lang.reflect.InvocationTargetException; -import java.nio.charset.Charset; -import java.nio.charset.CharsetDecoder; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; @@ -30,7 +26,7 @@ import java.util.logging.Logger; */ public final class DatabaseProvider implements Supplier, Closeable { - private static final Logger log = Logger.getLogger(DatabaseProvider.class.getName()); + private static final Logger logger = Logger.getLogger(DatabaseProvider.class.getName()); private static final AtomicInteger poolNameCounter = new AtomicInteger(1); @@ -63,7 +59,8 @@ public final class DatabaseProvider implements Supplier, Closeable { * *

A database pool will be created using jdbc-connection-pool.

*/ - public static DatabaseProviderBuilder builder(Config config) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { + public static DatabaseProviderBuilder builder(Config config) + throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { return builder(createDataSource(config), getFlavor(config)); } @@ -85,67 +82,12 @@ public final class DatabaseProvider implements Supplier, Closeable { * the JDBC standard DriverManager method. The url parameter will be inspected * to determine the Flavor for this database. */ - public static DatabaseProviderBuilder builder(String url) { - return builder(url, Flavor.fromJdbcUrl(url), null, null, null); + public static DatabaseProviderBuilder builder(ClassLoader classLoader, String url) { + return builder(classLoader, url, Flavor.fromJdbcUrl(url), null, null, null); } - /** - * Builder method to create and initialize an instance of this class using - * the JDBC standard DriverManager method. - * - * @param flavor use this flavor rather than guessing based on the url - */ - - public static DatabaseProviderBuilder builder(String url, Flavor flavor) { - return builder(url, flavor, null, null, null); - } - - /** - * Builder method to create and initialize an instance of this class using - * the JDBC standard DriverManager method. The url parameter will be inspected - * to determine the Flavor for this database. - */ - - public static DatabaseProviderBuilder builder(String url, Properties info) { - return builder(url, Flavor.fromJdbcUrl(url), info, null, null); - } - - /** - * Builder method to create and initialize an instance of this class using - * the JDBC standard DriverManager method. - * - * @param flavor use this flavor rather than guessing based on the url - */ - - public static DatabaseProviderBuilder builder(String url, Flavor flavor, Properties info) { - return builder(url, flavor, info, null, null); - } - - /** - * Builder method to create and initialize an instance of this class using - * the JDBC standard DriverManager method. The url parameter will be inspected - * to determine the Flavor for this database. - */ - - public static DatabaseProviderBuilder builder(String url, String user, String password) { - return builder(url, Flavor.fromJdbcUrl(url), null, user, password); - } - - /** - * Builder method to create and initialize an instance of this class using - * the JDBC standard DriverManager method. - * - * @param flavor use this flavor rather than guessing based on the url - */ - - public static DatabaseProviderBuilder builder(String url, - Flavor flavor, - String user, - String password) { - return builder(url, flavor, null, user, password); - } - - private static DatabaseProviderBuilder builder(String url, + private static DatabaseProviderBuilder builder(ClassLoader classLoader, + String url, Flavor flavor, Properties info, String user, @@ -156,7 +98,7 @@ public final class DatabaseProvider implements Supplier, Closeable { } catch (SQLException e) { if (flavor.driverClass() != null) { try { - Class.forName(flavor.driverClass()); + Class.forName(flavor.driverClass(), true, classLoader); } catch (ClassNotFoundException e1) { throw new DatabaseException("couldn't locate JDBC driver", e1); } @@ -176,417 +118,6 @@ public final class DatabaseProvider implements Supplier, Closeable { }, options); } - /** - * Configure the database from up to five properties read from a file: - *
-     *   database.url=...      Database connect string (required)
-     *   database.user=...     Authenticate as this user (optional if provided in url)
-     *   database.password=... User password (optional if user and password provided in
-     *                         url; prompted on standard input if user is provided and
-     *                         password is not)
-     *   database.flavor=...   What kind of database it is (optional, will guess based
-     *                         on the url if this is not provided)
-     *   database.driver=...   The Java class of the JDBC driver to load (optional, will
-     *                         guess based on the flavor if this is not provided)
-     * 
- *

This will use the JVM default character encoding to read the property file.

- * - * @param propertyFileName path to the properties file we will attempt to read - * @throws DatabaseException if the property file could not be read for any reason - */ - public static DatabaseProviderBuilder fromPropertyFile(String propertyFileName) { - return fromPropertyFile(propertyFileName, Charset.defaultCharset().newDecoder()); - } - - /** - * Configure the database from up to five properties read from a file: - *
-     *   database.url=...      Database connect string (required)
-     *   database.user=...     Authenticate as this user (optional if provided in url)
-     *   database.password=... User password (optional if user and password provided in
-     *                         url; prompted on standard input if user is provided and
-     *                         password is not)
-     *   database.flavor=...   What kind of database it is (optional, will guess based
-     *                         on the url if this is not provided)
-     *   database.driver=...   The Java class of the JDBC driver to load (optional, will
-     *                         guess based on the flavor if this is not provided)
-     * 
- * - * @param propertyFileName path to the properties file we will attempt to read - * @param decoder character encoding to use when reading the property file - * @throws DatabaseException if the property file could not be read for any reason - */ - public static DatabaseProviderBuilder fromPropertyFile(String propertyFileName, CharsetDecoder decoder) { - Properties properties = new Properties(); - if (propertyFileName != null && propertyFileName.length() > 0) { - try ( - FileInputStream fis = new FileInputStream(propertyFileName); - InputStreamReader reader = new InputStreamReader(fis, decoder) - ) { - properties.load(reader); - } catch (Exception e) { - throw new DatabaseException("Unable to read properties file: " + propertyFileName, e); - } - } - return fromProperties(properties, "", true); - } - - /** - * Configure the database from up to five properties read from a file: - *
-     *   database.url=...      Database connect string (required)
-     *   database.user=...     Authenticate as this user (optional if provided in url)
-     *   database.password=... User password (optional if user and password provided in
-     *                         url; prompted on standard input if user is provided and
-     *                         password is not)
-     *   database.flavor=...   What kind of database it is (optional, will guess based
-     *                         on the url if this is not provided)
-     *   database.driver=...   The Java class of the JDBC driver to load (optional, will
-     *                         guess based on the flavor if this is not provided)
-     * 
- *

This will use the JVM default character encoding to read the property file.

- * - * @param filename path to the properties file we will attempt to read - * @param propertyPrefix if this is null or empty the properties above will be read; - * if a value is provided it will be prefixed to each property - * (exactly, so if you want to use "my.database.url" you must - * pass "my." as the prefix) - * @throws DatabaseException if the property file could not be read for any reason - */ - public static DatabaseProviderBuilder fromPropertyFile(String filename, String propertyPrefix) { - return fromPropertyFile(filename, propertyPrefix, Charset.defaultCharset().newDecoder()); - } - - /** - * Configure the database from up to five properties read from a file: - *
-     *   database.url=...      Database connect string (required)
-     *   database.user=...     Authenticate as this user (optional if provided in url)
-     *   database.password=... User password (optional if user and password provided in
-     *                         url; prompted on standard input if user is provided and
-     *                         password is not)
-     *   database.flavor=...   What kind of database it is (optional, will guess based
-     *                         on the url if this is not provided)
-     *   database.driver=...   The Java class of the JDBC driver to load (optional, will
-     *                         guess based on the flavor if this is not provided)
-     * 
- * - * @param filename path to the properties file we will attempt to read - * @param propertyPrefix if this is null or empty the properties above will be read; - * if a value is provided it will be prefixed to each property - * (exactly, so if you want to use "my.database.url" you must - * pass "my." as the prefix) - * @param decoder character encoding to use when reading the property file - * @throws DatabaseException if the property file could not be read for any reason - */ - public static DatabaseProviderBuilder fromPropertyFile(String filename, String propertyPrefix, CharsetDecoder decoder) { - Properties properties = new Properties(); - if (filename != null && filename.length() > 0) { - try ( - FileInputStream fis = new FileInputStream(filename); - InputStreamReader reader = new InputStreamReader(fis, decoder) - ) { - properties.load(reader); - } catch (Exception e) { - throw new DatabaseException("Unable to read properties file: " + filename, e); - } - } - return fromProperties(properties, propertyPrefix, true); - } - - /** - * Configure the database from up to five properties read from the provided properties: - *
-     *   database.url=...      Database connect string (required)
-     *   database.user=...     Authenticate as this user (optional if provided in url)
-     *   database.password=... User password (optional if user and password provided in
-     *                         url; prompted on standard input if user is provided and
-     *                         password is not)
-     *   database.flavor=...   What kind of database it is (optional, will guess based
-     *                         on the url if this is not provided)
-     *   database.driver=...   The Java class of the JDBC driver to load (optional, will
-     *                         guess based on the flavor if this is not provided)
-     * 
- * - * @param properties properties will be read from here - * @throws DatabaseException if the property file could not be read for any reason - */ - public static DatabaseProviderBuilder fromProperties(Properties properties) { - return fromProperties(properties, "", false); - } - - /** - * Configure the database from up to five properties read from the provided properties: - *
-     *   database.url=...      Database connect string (required)
-     *   database.user=...     Authenticate as this user (optional if provided in url)
-     *   database.password=... User password (optional if user and password provided in
-     *                         url; prompted on standard input if user is provided and
-     *                         password is not)
-     *   database.flavor=...   What kind of database it is (optional, will guess based
-     *                         on the url if this is not provided)
-     *   database.driver=...   The Java class of the JDBC driver to load (optional, will
-     *                         guess based on the flavor if this is not provided)
-     * 
- * - * @param properties properties will be read from here - * @param propertyPrefix if this is null or empty the properties above will be read; - * if a value is provided it will be prefixed to each property - * (exactly, so if you want to use "my.database.url" you must - * pass "my." as the prefix) - * @throws DatabaseException if the property file could not be read for any reason - */ - public static DatabaseProviderBuilder fromProperties(Properties properties, String propertyPrefix) { - return fromProperties(properties, propertyPrefix, false); - } - - /** - * Configure the database from up to five properties read from the specified - * properties file, or from the system properties (system properties will take - * precedence over the file): - *
-     *   database.url=...      Database connect string (required)
-     *   database.user=...     Authenticate as this user (optional if provided in url)
-     *   database.password=... User password (optional if user and password provided in
-     *                         url; prompted on standard input if user is provided and
-     *                         password is not)
-     *   database.flavor=...   What kind of database it is (optional, will guess based
-     *                         on the url if this is not provided)
-     *   database.driver=...   The Java class of the JDBC driver to load (optional, will
-     *                         guess based on the flavor if this is not provided)
-     * 
- *

This will use the JVM default character encoding to read the property file.

- * - * @param filename path to the properties file we will attempt to read; if the file - * cannot be read for any reason (e.g. does not exist) a debug level - * log entry will be entered, but it will attempt to proceed using - * solely the system properties - */ - public static DatabaseProviderBuilder fromPropertyFileOrSystemProperties(String filename) { - return fromPropertyFileOrSystemProperties(filename, Charset.defaultCharset().newDecoder()); - } - - /** - * Configure the database from up to five properties read from the specified - * properties file, or from the system properties (system properties will take - * precedence over the file): - *
-     *   database.url=...      Database connect string (required)
-     *   database.user=...     Authenticate as this user (optional if provided in url)
-     *   database.password=... User password (optional if user and password provided in
-     *                         url; prompted on standard input if user is provided and
-     *                         password is not)
-     *   database.flavor=...   What kind of database it is (optional, will guess based
-     *                         on the url if this is not provided)
-     *   database.driver=...   The Java class of the JDBC driver to load (optional, will
-     *                         guess based on the flavor if this is not provided)
-     * 
- * - * @param filename path to the properties file we will attempt to read; if the file - * cannot be read for any reason (e.g. does not exist) a debug level - * log entry will be entered, but it will attempt to proceed using - * solely the system properties - * @param decoder character encoding to use when reading the property file - */ - public static DatabaseProviderBuilder fromPropertyFileOrSystemProperties(String filename, CharsetDecoder decoder) { - Properties properties = new Properties(); - if (filename != null && filename.length() > 0) { - try ( - FileInputStream fis = new FileInputStream(filename); - InputStreamReader reader = new InputStreamReader(fis, decoder) - ) { - properties.load(reader); - } catch (Exception e) { - log.fine("Trying system properties - unable to read properties file: " + filename); - } - } - return fromProperties(properties, "", true); - } - - /** - * Configure the database from up to five properties read from the specified - * properties file, or from the system properties (system properties will take - * precedence over the file): - *
-     *   database.url=...      Database connect string (required)
-     *   database.user=...     Authenticate as this user (optional if provided in url)
-     *   database.password=... User password (optional if user and password provided in
-     *                         url; prompted on standard input if user is provided and
-     *                         password is not)
-     *   database.flavor=...   What kind of database it is (optional, will guess based
-     *                         on the url if this is not provided)
-     *   database.driver=...   The Java class of the JDBC driver to load (optional, will
-     *                         guess based on the flavor if this is not provided)
-     * 
- *

This will use the JVM default character encoding to read the property file.

- * - * @param filename path to the properties file we will attempt to read; if the file - * cannot be read for any reason (e.g. does not exist) a debug level - * log entry will be entered, but it will attempt to proceed using - * solely the system properties - * @param propertyPrefix if this is null or empty the properties above will be read; - * if a value is provided it will be prefixed to each property - * (exactly, so if you want to use "my.database.url" you must - * pass "my." as the prefix) - */ - public static DatabaseProviderBuilder fromPropertyFileOrSystemProperties(String filename, String propertyPrefix) { - return fromPropertyFileOrSystemProperties(filename, propertyPrefix, Charset.defaultCharset().newDecoder()); - } - - /** - * Configure the database from up to five properties read from the specified - * properties file, or from the system properties (system properties will take - * precedence over the file): - *
-     *   database.url=...      Database connect string (required)
-     *   database.user=...     Authenticate as this user (optional if provided in url)
-     *   database.password=... User password (optional if user and password provided in
-     *                         url; prompted on standard input if user is provided and
-     *                         password is not)
-     *   database.flavor=...   What kind of database it is (optional, will guess based
-     *                         on the url if this is not provided)
-     *   database.driver=...   The Java class of the JDBC driver to load (optional, will
-     *                         guess based on the flavor if this is not provided)
-     * 
- * - * @param filename path to the properties file we will attempt to read; if the file - * cannot be read for any reason (e.g. does not exist) a debug level - * log entry will be entered, but it will attempt to proceed using - * solely the system properties - * @param propertyPrefix if this is null or empty the properties above will be read; - * if a value is provided it will be prefixed to each property - * (exactly, so if you want to use "my.database.url" you must - * pass "my." as the prefix) - * @param decoder character encoding to use when reading the property file - */ - public static DatabaseProviderBuilder fromPropertyFileOrSystemProperties(String filename, String propertyPrefix, - CharsetDecoder decoder) { - Properties properties = new Properties(); - if (filename != null && filename.length() > 0) { - try ( - FileInputStream fis = new FileInputStream(filename); - InputStreamReader reader = new InputStreamReader(fis, decoder) - ) { - properties.load(reader); - } catch (Exception e) { - log.fine("Trying system properties - unable to read properties file: " + filename); - } - } - return fromProperties(properties, propertyPrefix, true); - } - - /** - * Configure the database from up to five system properties: - *
-     *   -Ddatabase.url=...      Database connect string (required)
-     *   -Ddatabase.user=...     Authenticate as this user (optional if provided in url)
-     *   -Ddatabase.password=... User password (optional if user and password provided in
-     *                           url; prompted on standard input if user is provided and
-     *                           password is not)
-     *   -Ddatabase.flavor=...   What kind of database it is (optional, will guess based
-     *                           on the url if this is not provided)
-     *   -Ddatabase.driver=...   The Java class of the JDBC driver to load (optional, will
-     *                           guess based on the flavor if this is not provided)
-     * 
- */ - - public static DatabaseProviderBuilder fromSystemProperties() { - return fromProperties(null, "", true); - } - - /** - * Configure the database from up to five system properties: - *
-     *   -D{prefix}database.url=...      Database connect string (required)
-     *   -D{prefix}database.user=...     Authenticate as this user (optional if provided in url)
-     *   -D{prefix}database.password=... User password (optional if user and password provided in
-     *                                   url; prompted on standard input if user is provided and
-     *                                   password is not)
-     *   -D{prefix}database.flavor=...   What kind of database it is (optional, will guess based
-     *                                   on the url if this is not provided)
-     *   -D{prefix}database.driver=...   The Java class of the JDBC driver to load (optional, will
-     *                                   guess based on the flavor if this is not provided)
-     * 
- * - * @param propertyPrefix a prefix to attach to each system property - be sure to include the - * dot if desired (e.g. "mydb." for properties like -Dmydb.database.url) - */ - - public static DatabaseProviderBuilder fromSystemProperties(String propertyPrefix) { - return fromProperties(null, propertyPrefix, true); - } - - private static DatabaseProviderBuilder fromProperties(Properties properties, String propertyPrefix, boolean useSystemProperties) { - if (propertyPrefix == null) { - propertyPrefix = ""; - } - String driver; - String flavorStr; - String url; - String user; - String password; - if (useSystemProperties) { - if (properties == null) { - properties = new Properties(); - } - driver = System.getProperty(propertyPrefix + "database.driver", - properties.getProperty(propertyPrefix + "database.driver")); - flavorStr = System.getProperty(propertyPrefix + "database.flavor", - properties.getProperty(propertyPrefix + "database.flavor")); - url = System.getProperty(propertyPrefix + "database.url", - properties.getProperty(propertyPrefix + "database.url")); - user = System.getProperty(propertyPrefix + "database.user", - properties.getProperty(propertyPrefix + "database.user")); - password = System.getProperty(propertyPrefix + "database.password", - properties.getProperty(propertyPrefix + "database.password")); - } else { - if (properties == null) { - throw new DatabaseException("No properties were provided"); - } - driver = properties.getProperty(propertyPrefix + "database.driver"); - flavorStr = properties.getProperty(propertyPrefix + "database.flavor"); - url = properties.getProperty(propertyPrefix + "database.url"); - user = properties.getProperty(propertyPrefix + "database.user"); - password = properties.getProperty(propertyPrefix + "database.password"); - } - if (url == null) { - throw new DatabaseException("You must use -D" + propertyPrefix + "database.url=..."); - } - if (user != null && password == null) { - System.out.println("Enter database password for user " + user + ":"); - byte[] input = new byte[256]; - try { - int bytesRead = System.in.read(input); - password = new String(input, 0, bytesRead - 1, Charset.defaultCharset()); - } catch (IOException e) { - throw new DatabaseException("Error reading password from standard input", e); - } - } - Flavor flavor; - if (flavorStr != null) { - flavor = Flavor.valueOf(flavorStr); - } else { - flavor = Flavor.fromJdbcUrl(url); - } - if (driver != null) { - try { - Class.forName(driver).getDeclaredConstructor().newInstance(); - } catch (Exception e) { - driver = flavor.driverClass(); - try { - Class.forName(driver).getDeclaredConstructor().newInstance(); - } catch (Exception ee) { - throw new DatabaseException("Unable to load JDBC driver: " + driver, ee); - } - } - } - if (user == null) { - return builder(url, flavor); - } else { - return builder(url, flavor, user, password); - } - } - private static DataSource createDataSource(Config config) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { @@ -749,7 +280,7 @@ public final class DatabaseProvider implements Supplier, Closeable { if (builder.isClosed()) { throw new DatabaseException("Called get() on a DatabaseProvider after close()"); } - Metric metric = new Metric(log.isLoggable(Level.FINE)); + Metric metric = new Metric(logger.isLoggable(Level.FINE)); try { connection = builder.connectionProvider.get(); metric.checkpoint("getConn"); @@ -770,10 +301,10 @@ public final class DatabaseProvider implements Supplier, Closeable { throw e; } finally { metric.done(); - if (log.isLoggable(Level.FINE)) { + if (logger.isLoggable(Level.FINE)) { StringBuilder buf = new StringBuilder("Get ").append(builder.options.flavor()).append(" database: "); metric.printMessage(buf); - log.fine(buf.toString()); + logger.fine(buf.toString()); } } return database; @@ -811,7 +342,7 @@ public final class DatabaseProvider implements Supplier, Closeable { connection.rollback(); } } catch (Exception e) { - log.log(Level.SEVERE, "Unable to rollback the transaction", e); + logger.log(Level.SEVERE, "Unable to rollback the transaction", e); } } } @@ -825,7 +356,7 @@ public final class DatabaseProvider implements Supplier, Closeable { } close(); } catch (Exception e) { - log.log(Level.SEVERE, "Unable to rollback the transaction", e); + logger.log(Level.SEVERE, "Unable to rollback the transaction", e); } } @@ -835,7 +366,7 @@ public final class DatabaseProvider implements Supplier, Closeable { try { connection.close(); } catch (Exception e) { - log.log(Level.SEVERE, "Unable to close the database connection", e); + logger.log(Level.SEVERE, "Unable to close the database connection", e); } } connection = null; @@ -843,129 +374,6 @@ public final class DatabaseProvider implements Supplier, Closeable { builder.close(); } - /** - * This builder is immutable, so setting various options does not affect - * the previous instance. This is intended to make it safe to pass builders - * around without risk someone will reconfigure it. - */ - public interface DatabaseProviderBuilder { - - DatabaseProviderBuilder withOptions(OptionsOverride options); - - /** - * Enable logging of parameter values along with the SQL. - */ - - DatabaseProviderBuilder withSqlParameterLogging(); - - /** - * Include SQL in exception messages. This will also include parameters in the - * exception messages if SQL parameter logging is enabled. This is handy for - * development, but be careful as this is an information disclosure risk, - * dependent on how the exception are caught and handled. - */ - - DatabaseProviderBuilder withSqlInExceptionMessages(); - - /** - * Wherever argDateNowPerDb() is specified, use argDateNowPerApp() instead. This is - * useful for testing purposes as you can use OptionsOverride to provide your - * own system clock that will be used for time travel. - */ - - DatabaseProviderBuilder withDatePerAppOnly(); - - /** - * Allow provided Database instances to explicitly control transactions using the - * commitNow() and rollbackNow() methods. Otherwise calling those methods would - * throw an exception. - */ - - DatabaseProviderBuilder withTransactionControl(); - - /** - * This can be useful when testing code, as it can pretend to use transactions, - * while giving you control over whether it actually commits or rolls back. - */ - - DatabaseProviderBuilder withTransactionControlSilentlyIgnored(); - - /** - * Allow direct access to the underlying database connection. Normally this is - * not allowed, and is a bad idea, but it can be helpful when migrating from - * legacy code that works with raw JDBC. - */ - - DatabaseProviderBuilder withConnectionAccess(); - - /** - * WARNING: You should try to avoid using this method. If you use it more - * that once or twice in your entire codebase you are probably doing - * something wrong. - * - *

If you use this method you are responsible for managing - * the transaction and commit/rollback/close.

- */ - - DatabaseProvider build(); - - /** - * This is a convenience method to eliminate the need for explicitly - * managing the resources (and error handling) for this class. After - * the run block is complete the transaction will commit unless the - * {@link DbCode#run(Supplier) run(Supplier)} method threw a {@link Throwable}. - * - *

Here is a typical usage: - *

-         *   dbp.transact(dbs -> {
-         *     List r = dbs.get().toSelect("select a from b where c=?").argInteger(1).queryStrings();
-         *     ... do something with the results ...
-         *   });
-         * 
- *

- * - * @param code the code you want to run as a transaction with a Database - * @see #transact(DbCodeTx) - */ - void transact(DbCode code); - - /** - * This method is the same as {@link #transact(DbCode)} but allows a return value. - * - *

Here is a typical usage: - *

-         *   List r = dbp.transact(dbs -> {
-         *     return dbs.get().toSelect("select a from b where c=?").argInteger(1).queryStrings();
-         *   });
-         * 
- *

- */ - T transactReturning(DbCodeTyped code); - - /** - * This is a convenience method to eliminate the need for explicitly - * managing the resources (and error handling) for this class. After - * the run block is complete commit() will be called unless either the - * {@link DbCodeTx#run(Supplier, Transaction)} method threw a {@link Throwable} - * while {@link Transaction#isRollbackOnError()} returns true, or - * {@link Transaction#isRollbackOnly()} returns a true value. - * - *

Here is a typical usage: - *

-         *   dbp.transact((dbs, tx) -> {
-         *     tx.setRollbackOnError(false);
-         *     dbs.get().toInsert("...").argInteger(1).insert(1);
-         *     ...some stuff that might fail...
-         *   });
-         * 
- *

- * - * @param code the code you want to run as a transaction with a Database - */ - void transact(DbCodeTx code); - - } - private static class DatabaseProviderBuilderImpl implements DatabaseProviderBuilder, Closeable { private DataSource dataSource; diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/DatabaseProviderBuilder.java b/jdbc-query/src/main/java/org/xbib/jdbc/query/DatabaseProviderBuilder.java new file mode 100644 index 0000000..9d501bb --- /dev/null +++ b/jdbc-query/src/main/java/org/xbib/jdbc/query/DatabaseProviderBuilder.java @@ -0,0 +1,126 @@ +package org.xbib.jdbc.query; + +import java.util.function.Supplier; + +/** + * This builder is immutable, so setting various options does not affect + * the previous instance. This is intended to make it safe to pass builders + * around without risk someone will reconfigure it. + */ +public interface DatabaseProviderBuilder { + + DatabaseProviderBuilder withOptions(OptionsOverride options); + + /** + * Enable logging of parameter values along with the SQL. + */ + + DatabaseProviderBuilder withSqlParameterLogging(); + + /** + * Include SQL in exception messages. This will also include parameters in the + * exception messages if SQL parameter logging is enabled. This is handy for + * development, but be careful as this is an information disclosure risk, + * dependent on how the exception are caught and handled. + */ + + DatabaseProviderBuilder withSqlInExceptionMessages(); + + /** + * Wherever argDateNowPerDb() is specified, use argDateNowPerApp() instead. This is + * useful for testing purposes as you can use OptionsOverride to provide your + * own system clock that will be used for time travel. + */ + + DatabaseProviderBuilder withDatePerAppOnly(); + + /** + * Allow provided Database instances to explicitly control transactions using the + * commitNow() and rollbackNow() methods. Otherwise calling those methods would + * throw an exception. + */ + + DatabaseProviderBuilder withTransactionControl(); + + /** + * This can be useful when testing code, as it can pretend to use transactions, + * while giving you control over whether it actually commits or rolls back. + */ + + DatabaseProviderBuilder withTransactionControlSilentlyIgnored(); + + /** + * Allow direct access to the underlying database connection. Normally this is + * not allowed, and is a bad idea, but it can be helpful when migrating from + * legacy code that works with raw JDBC. + */ + + DatabaseProviderBuilder withConnectionAccess(); + + /** + * WARNING: You should try to avoid using this method. If you use it more + * that once or twice in your entire codebase you are probably doing + * something wrong. + * + *

If you use this method you are responsible for managing + * the transaction and commit/rollback/close.

+ */ + + DatabaseProvider build(); + + /** + * This is a convenience method to eliminate the need for explicitly + * managing the resources (and error handling) for this class. After + * the run block is complete the transaction will commit unless the + * {@link DbCode#run(Supplier) run(Supplier)} method threw a {@link Throwable}. + * + *

Here is a typical usage: + *

+     *   dbp.transact(dbs -> {
+     *     List r = dbs.get().toSelect("select a from b where c=?").argInteger(1).queryStrings();
+     *     ... do something with the results ...
+     *   });
+     * 
+ *

+ * + * @param code the code you want to run as a transaction with a Database + * @see #transact(DbCodeTx) + */ + void transact(DbCode code); + + /** + * This method is the same as {@link #transact(DbCode)} but allows a return value. + * + *

Here is a typical usage: + *

+     *   List r = dbp.transact(dbs -> {
+     *     return dbs.get().toSelect("select a from b where c=?").argInteger(1).queryStrings();
+     *   });
+     * 
+ *

+ */ + T transactReturning(DbCodeTyped code); + + /** + * This is a convenience method to eliminate the need for explicitly + * managing the resources (and error handling) for this class. After + * the run block is complete commit() will be called unless either the + * {@link DbCodeTx#run(Supplier, Transaction)} method threw a {@link Throwable} + * while {@link Transaction#isRollbackOnError()} returns true, or + * {@link Transaction#isRollbackOnly()} returns a true value. + * + *

Here is a typical usage: + *

+     *   dbp.transact((dbs, tx) -> {
+     *     tx.setRollbackOnError(false);
+     *     dbs.get().toInsert("...").argInteger(1).insert(1);
+     *     ...some stuff that might fail...
+     *   });
+     * 
+ *

+ * + * @param code the code you want to run as a transaction with a Database + */ + void transact(DbCodeTx code); + +} diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/Flavor.java b/jdbc-query/src/main/java/org/xbib/jdbc/query/Flavor.java index 175b95e..cf7b3fc 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/Flavor.java +++ b/jdbc-query/src/main/java/org/xbib/jdbc/query/Flavor.java @@ -12,11 +12,7 @@ public interface Flavor { String driverClass(); - /** - * Returns true if DB normalizes to upper case names for ids like tables and columns - * See (); + sequences = new ArrayList<>(); + indexForeignKeys = true; + userTableName = "user_principal"; } public Sequence addSequence(String name) { @@ -70,10 +74,10 @@ public class Schema { return table; } - public Table addTableFromRow(String tableName, Row r) { + public Table addTableFromRow(String tableName, Row row) { Table table = addTable(tableName); try { - ResultSetMetaData metadata = r.getMetadata(); + ResultSetMetaData metadata = row.getMetadata(); int columnCount = metadata.getColumnCount(); String[] names = new String[columnCount]; for (int i = 0; i < columnCount; i++) { @@ -464,51 +468,70 @@ public class Schema { } public class Table { + private final String name; + private String comment; - private final List columns = new ArrayList<>(); + private PrimaryKey primaryKey; - private final List foreignKeys = new ArrayList<>(); - private final List indexes = new ArrayList<>(); - private final List checks = new ArrayList<>(); - private final List uniques = new ArrayList<>(); - private final Map customClauses = new HashMap<>(); + + private final List columns; + + private final List foreignKeys; + + private final List indexes; + + private final List checks; + + private final List uniques; + + private final Map customClauses; + private boolean createTracking; + private String createTrackingFkName; + private String createTrackingFkTable; + private boolean updateTracking; + private String updateTrackingFkName; + private String updateTrackingFkTable; + private boolean updateSequence; + private boolean historyTable; public Table(String name) { this.name = toName(name); if (this.name.length() > 27) { - throw new RuntimeException("Table name should be 27 characters or less"); + throw new IllegalArgumentException("Table name should be 27 characters or less"); } + columns = new ArrayList<>(); + foreignKeys = new ArrayList<>(); + indexes = new ArrayList<>(); + checks = new ArrayList<>(); + uniques = new ArrayList<>(); + customClauses = new HashMap<>(); } public void validate() { if (columns.size() < 1) { - throw new RuntimeException("Table " + name + " needs at least one column"); + throw new IllegalArgumentException("Table " + name + " needs at least one column"); } for (Column c : columns) { c.validate(); } - if (primaryKey != null) { primaryKey.validate(); } - for (ForeignKey fk : foreignKeys) { fk.validate(); } - for (Check c : checks) { c.validate(); } - for (Index i : indexes) { i.validate(); } @@ -629,12 +652,12 @@ public class Schema { public PrimaryKey addPrimaryKey(String name, String... columnNames) { if (primaryKey != null) { - throw new RuntimeException("Only one primary key is allowed. For composite keys use" + throw new IllegalArgumentException("Only one primary key is allowed. For composite keys use" + " addPrimaryKey(name, c1, c2, ...)."); } for (Column c : columns) { if (c.name.equalsIgnoreCase(name)) { - throw new RuntimeException("For table: " + this.name + " primary key name should not be a column name: " + name); + throw new IllegalArgumentException("For table: " + this.name + " primary key name should not be a column name: " + name); } } primaryKey = new PrimaryKey(name, columnNames); @@ -671,18 +694,20 @@ public class Schema { } public class PrimaryKey { + private final String name; - private final List columnNames = new ArrayList<>(); + + private final List columnNames; public PrimaryKey(String name, String[] columnNames) { this.name = toName(name); + this.columnNames = new ArrayList<>(); for (String s : columnNames) { this.columnNames.add(toName(s)); } } public void validate() { - } public Table table() { @@ -692,18 +717,20 @@ public class Schema { } public class Unique { + private final String name; - private final List columnNames = new ArrayList<>(); + + private final List columnNames; public Unique(String name, String[] columnNames) { this.name = toName(name); + this.columnNames = new ArrayList<>(); for (String s : columnNames) { this.columnNames.add(toName(s)); } } public void validate() { - } public Table table() { @@ -713,13 +740,18 @@ public class Schema { } public class ForeignKey { + private final String name; - private final List columnNames = new ArrayList<>(); - public String foreignTable; - private boolean onDeleteCascade = false; + + private final List columnNames; + + private String foreignTable; + + private boolean onDeleteCascade; public ForeignKey(String name, String[] columnNames) { this.name = toName(name); + this.columnNames = new ArrayList<>(); for (String s : columnNames) { this.columnNames.add(toName(s)); } @@ -737,7 +769,7 @@ public class Schema { private void validate() { if (foreignTable == null) { - throw new RuntimeException("Foreign key " + name + " must reference a table"); + throw new IllegalArgumentException("Foreign key " + name + " must reference a table"); } } @@ -748,7 +780,9 @@ public class Schema { } public class Check { + private final String name; + private final String expression; public Check(String name, String expression) { @@ -758,7 +792,7 @@ public class Schema { private void validate() { if (expression == null) { - throw new RuntimeException("Expression needed for check constraint " + name + " on table " + Table.this.name); + throw new IllegalArgumentException("Expression needed for check constraint " + name + " on table " + Table.this.name); } } @@ -769,12 +803,16 @@ public class Schema { } public class Index { + private final String name; - private final List columnNames = new ArrayList<>(); + + private final List columnNames; + private boolean unique; public Index(String name, String[] columnNames) { this.name = toName(name); + this.columnNames = new ArrayList<>(); for (String s : columnNames) { this.columnNames.add(toName(s)); } @@ -787,7 +825,7 @@ public class Schema { private void validate() { if (columnNames.size() < 1) { - throw new RuntimeException("Index " + name + " needs at least one column"); + throw new IllegalArgumentException("Index " + name + " needs at least one column"); } } @@ -798,11 +836,17 @@ public class Schema { } public class Column { + private final String name; + private ColumnType type; + private int scale; + private int precision; + private boolean notNull; + private String comment; public Column(String name) { @@ -886,7 +930,7 @@ public class Schema { private void validate() { if (type == null) { - throw new RuntimeException("Call as*() on column " + name + " table " + Table.this.name); + throw new IllegalArgumentException("Call as*() on column " + name + " table " + Table.this.name); } } diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/When.java b/jdbc-query/src/main/java/org/xbib/jdbc/query/When.java index e81ea02..e394295 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/When.java +++ b/jdbc-query/src/main/java/org/xbib/jdbc/query/When.java @@ -1,8 +1,5 @@ package org.xbib.jdbc.query; -import org.xbib.jdbc.query.flavor.Derby; -import org.xbib.jdbc.query.flavor.SqlServer; - import java.util.Objects; /** @@ -17,21 +14,42 @@ public class When { public When(Flavor actualFlavor) { this.actualFlavor = actualFlavor; } - - public When oracle(String sql) { - if ("oracle".equals(actualFlavor.getName())) { + + public When derby(String sql) { + if ("derby".equals(actualFlavor.getName())) { chosen = sql; } return this; } - - public When derby(String sql) { - if (actualFlavor instanceof Derby) { + + public When h2(String sql) { + if ("h2".equals(actualFlavor.getName())) { chosen = sql; } return this; } - + + public When hsqldb(String sql) { + if ("hsqldb".equals(actualFlavor.getName())) { + chosen = sql; + } + return this; + } + + public When mariadb(String sql) { + if ("mariadb".equals(actualFlavor.getName())) { + chosen = sql; + } + return this; + } + + public When oracle(String sql) { + if ("oracle".equals(actualFlavor.getName())) { + chosen = sql; + } + return this; + } + public When postgres(String sql) { if ("postgresql".equals(actualFlavor.getName())) { chosen = sql; @@ -40,12 +58,11 @@ public class When { } public When sqlserver(String sql) { - if (actualFlavor instanceof SqlServer) { + if ("sqlserver".equals(actualFlavor.getName())) { chosen = sql; } return this; } - public String other(String sql) { if (chosen == null) { diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/Derby.java b/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/Derby.java index 6dffb16..2eb7d4a 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/Derby.java +++ b/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/Derby.java @@ -23,8 +23,16 @@ public class Derby implements Flavor { } @Override - public boolean isNormalizedUpperCase() { - return true; + public String normalizeTableName(String tableName) { + if (tableName == null) { + return tableName; + } + if (tableName.length() > 2) { + if (tableName.startsWith("\"") && tableName.endsWith("\"")) { + return tableName.substring(1, tableName.length() - 1); + } + } + return tableName.toUpperCase(); } @Override @@ -92,6 +100,11 @@ public class Derby implements Flavor { return "timestamp"; } + @Override + public String columnTypeLocalDateTime() { + return "timestamp"; + } + @Override public String typeLocalDate() { return "date"; diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/H2.java b/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/H2.java index 8725875..a615e4d 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/H2.java +++ b/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/H2.java @@ -23,8 +23,16 @@ public class H2 implements Flavor { } @Override - public boolean isNormalizedUpperCase() { - return true; + public String normalizeTableName(String tableName) { + if (tableName == null) { + return tableName; + } + if (tableName.length() > 2) { + if (tableName.startsWith("\"") && tableName.endsWith("\"")) { + return tableName.substring(1, tableName.length() - 1); + } + } + return tableName.toUpperCase(); } @Override @@ -92,6 +100,11 @@ public class H2 implements Flavor { return "timestamp(3)"; } + @Override + public String columnTypeLocalDateTime() { + return "timestamp"; + } + @Override public String typeLocalDate() { return "date"; diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/Hsql.java b/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/Hsql.java index 2ec425b..6c1b452 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/Hsql.java +++ b/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/Hsql.java @@ -23,8 +23,16 @@ public class Hsql implements Flavor { } @Override - public boolean isNormalizedUpperCase() { - return true; + public String normalizeTableName(String tableName) { + if (tableName == null) { + return tableName; + } + if (tableName.length() > 2) { + if (tableName.startsWith("\"") && tableName.endsWith("\"")) { + return tableName.substring(1, tableName.length() - 1); + } + } + return tableName.toUpperCase(); } @Override @@ -92,6 +100,11 @@ public class Hsql implements Flavor { return "timestamp with time zone"; } + @Override + public String columnTypeLocalDateTime() { + return "TIMESTAMP WITH TIME ZONE"; + } + @Override public String typeLocalDate() { return "date"; diff --git a/jdbc-query/src/main/resources/META-INF/services/org.xbib.jdbc.query.Flavor b/jdbc-query/src/main/resources/META-INF/services/org.xbib.jdbc.query.Flavor index 0e4363b..017c60c 100644 --- a/jdbc-query/src/main/resources/META-INF/services/org.xbib.jdbc.query.Flavor +++ b/jdbc-query/src/main/resources/META-INF/services/org.xbib.jdbc.query.Flavor @@ -1,4 +1,3 @@ org.xbib.jdbc.query.flavor.Derby org.xbib.jdbc.query.flavor.Hsql -org.xbib.jdbc.query.flavor.H2 -org.xbib.jdbc.query.flavor.SqlServer \ No newline at end of file +org.xbib.jdbc.query.flavor.H2 \ No newline at end of file diff --git a/jdbc-query/src/test/java/org/xbib/jdbc/query/test/CommonTest.java b/jdbc-query/src/test/java/org/xbib/jdbc/query/test/CommonTest.java deleted file mode 100644 index f47ad3d..0000000 --- a/jdbc-query/src/test/java/org/xbib/jdbc/query/test/CommonTest.java +++ /dev/null @@ -1,1721 +0,0 @@ -package org.xbib.jdbc.query.test; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.xbib.jdbc.query.ConstraintViolationException; -import org.xbib.jdbc.query.Database; -import org.xbib.jdbc.query.DatabaseException; -import org.xbib.jdbc.query.DatabaseProvider; -import org.xbib.jdbc.query.OptionsDefault; -import org.xbib.jdbc.query.OptionsOverride; -import org.xbib.jdbc.query.Row; -import org.xbib.jdbc.query.RowHandler; -import org.xbib.jdbc.query.RowsHandler; -import org.xbib.jdbc.query.Schema; -import org.xbib.jdbc.query.Sql; -import org.xbib.jdbc.query.SqlArgs; -import org.xbib.jdbc.query.StatementAdapter; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.Reader; -import java.io.StringReader; -import java.math.BigDecimal; -import java.sql.ResultSetMetaData; -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.Month; -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.time.format.DateTimeFormatter; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.TimeZone; -import java.util.logging.Level; -import java.util.logging.Logger; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -/** - * Exercise Database functionality with a real database. - */ -public abstract class CommonTest { - - private static final Logger logger = Logger.getLogger(CommonTest.class.getName()); - - final static String TEST_TABLE_NAME = "dbtest"; - - protected static DatabaseProvider dbp; - - protected Database db; - - protected LocalDateTime now; - - protected LocalDate localDateNow; - - protected abstract DatabaseProvider createDatabaseProvider(OptionsOverride options) throws Exception; - - @BeforeEach - public void setupJdbc() throws Exception { - now = LocalDateTime.now().truncatedTo(ChronoUnit.MILLIS); - localDateNow = LocalDate.now(); - dbp = createDatabaseProvider(new OptionsOverride() { - }); - db = dbp.get(); - db.dropTableQuietly(TEST_TABLE_NAME); - } - - @AfterEach - public void closeJdbc() { - if (dbp != null) { - dbp.commitAndClose(); - } - } - - @Test - public void tableExists() { - // Verify dbtest table does not exist - String lowercaseTable = TEST_TABLE_NAME.toLowerCase(); - testTableLookup(lowercaseTable); - db.dropTableQuietly(lowercaseTable); - // Let's try creating a table with an upper case name and verify it works - String uppercaseTable = TEST_TABLE_NAME.toUpperCase(); - testTableLookup(uppercaseTable); - db.dropTableQuietly(uppercaseTable); - // Verify that null or empty name is handled gracefully - assertFalse(db.tableExists(null)); - assertFalse(db.tableExists("")); - } - - private void testTableLookup(String tableName) { - // Verify test table does not exist - assertFalse(db.tableExists(tableName)); - // Create and verify it exists. - new Schema().addTable(tableName).addColumn("pk").primaryKey().schema().execute(db); - assertTrue(db.tableExists(tableName)); - } - - @Test - public void normalizeTableName() { - // Verify that null and empty cases are handled gracefully - assertNull(db.normalizeTableName(null)); - assertEquals("", db.normalizeTableName("")); - - // Verify a quoted table name is returned in exactly the same case, with quotes removed. - String camelCaseTableName = "\"DbTest\""; - assertEquals(camelCaseTableName.substring(1, camelCaseTableName.length() - 1), - db.normalizeTableName(camelCaseTableName)); - - // Verify that the database flavor gets the expected normalized case - boolean isUpperCase = db.flavor().isNormalizedUpperCase(); - if (isUpperCase) { - assertEquals(TEST_TABLE_NAME.toUpperCase(), db.normalizeTableName(TEST_TABLE_NAME)); - } else { - assertEquals(TEST_TABLE_NAME.toLowerCase(), db.normalizeTableName(TEST_TABLE_NAME)); - } - } - - @Test - public void selectNewTable() { - new Schema() - .addTable("dbtest") - .addColumn("nbr_integer").asInteger().primaryKey().table() - .addColumn("nbr_long").asLong().table() - .addColumn("nbr_float").asFloat().table() - .addColumn("nbr_double").asDouble().table() - .addColumn("nbr_big_decimal").asBigDecimal(19, 9).table() - .addColumn("str_varchar").asString(80).table() - .addColumn("str_fixed").asStringFixed(1).table() - .addColumn("str_lob").asClob().table() - .addColumn("bin_blob").asBlob().table() - .addColumn("date_millis").asLocalDateTime().table() - .addColumn("local_date").asLocalDate().table().schema().execute(db); - - BigDecimal bigDecimal = new BigDecimal("5.3"); - db.toInsert("insert into dbtest values (?,?,?,?,?,?,?,?,?,?,?)").argInteger(1).argLong(2L).argFloat(3.2f).argDouble(4.2) - .argBigDecimal(bigDecimal).argString("Hello").argString("T").argClobString("World") - .argBlobBytes("More".getBytes()).argLocalDateTime(now).argLocalDate(localDateNow).insert(1); - - db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal, str_varchar, str_fixed, str_lob, " - + "bin_blob, date_millis, local_date from dbtest") - .query((RowsHandler) rs -> { - assertTrue(rs.next()); - assertEquals(Integer.valueOf(1), rs.getIntegerOrNull(1)); - assertEquals(Integer.valueOf(1), rs.getIntegerOrNull("nbr_integer")); - assertEquals(1, rs.getIntegerOrZero(1)); - assertEquals(1, rs.getIntegerOrZero("nbr_integer")); - assertEquals(Long.valueOf(2), rs.getLongOrNull(2)); - assertEquals(Long.valueOf(2), rs.getLongOrNull("nbr_long")); - assertEquals(2, rs.getLongOrZero(2)); - assertEquals(2, rs.getLongOrZero("nbr_long")); - assertEquals(Float.valueOf(3.2f), rs.getFloatOrNull(3)); - assertEquals(Float.valueOf(3.2f), rs.getFloatOrNull("nbr_float")); - assertEquals(3.2, rs.getFloatOrZero(3), 0.01); - assertEquals(3.2, rs.getFloatOrZero("nbr_float"), 0.01); - assertEquals(Double.valueOf(4.2), rs.getDoubleOrNull(4)); - assertEquals(Double.valueOf(4.2), rs.getDoubleOrNull("nbr_double")); - assertEquals(4.2, rs.getDoubleOrZero(4), 0.01); - assertEquals(4.2, rs.getDoubleOrZero("nbr_double"), 0.01); - assertEquals(new BigDecimal("5.3"), rs.getBigDecimalOrNull(5)); - assertEquals(new BigDecimal("5.3"), rs.getBigDecimalOrNull("nbr_big_decimal")); - assertEquals(new BigDecimal("5.3"), rs.getBigDecimalOrZero(5)); - assertEquals(new BigDecimal("5.3"), rs.getBigDecimalOrZero("nbr_big_decimal")); - assertEquals("Hello", rs.getStringOrNull(6)); - assertEquals("Hello", rs.getStringOrNull("str_varchar")); - assertEquals("Hello", rs.getStringOrEmpty(6)); - assertEquals("Hello", rs.getStringOrEmpty("str_varchar")); - assertEquals("T", rs.getStringOrNull(7)); - assertEquals("T", rs.getStringOrNull("str_fixed")); - assertEquals("T", rs.getStringOrEmpty(7)); - assertEquals("T", rs.getStringOrEmpty("str_fixed")); - assertEquals("World", rs.getClobStringOrNull(8)); - assertEquals("World", rs.getClobStringOrNull("str_lob")); - assertEquals("World", rs.getClobStringOrEmpty(8)); - assertEquals("World", rs.getClobStringOrEmpty("str_lob")); - assertArrayEquals("More".getBytes(), rs.getBlobBytesOrNull(9)); - assertArrayEquals("More".getBytes(), rs.getBlobBytesOrNull("bin_blob")); - assertArrayEquals("More".getBytes(), rs.getBlobBytesOrZeroLen(9)); - assertArrayEquals("More".getBytes(), rs.getBlobBytesOrZeroLen("bin_blob")); - assertEquals(now, rs.getLocalDateTimeOrNull(10)); - assertEquals(now, rs.getLocalDateTimeOrNull("date_millis")); - assertEquals(localDateNow, rs.getLocalDateOrNull(11)); - assertEquals(localDateNow, rs.getLocalDateOrNull("local_date")); - return null; - }); - // Repeat the above query, using the various methods that automatically infer the column - db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal, str_varchar, str_fixed, str_lob, " - + "bin_blob, date_millis, local_date from dbtest") - .query((RowsHandler) rs -> { - assertTrue(rs.next()); - assertEquals(Integer.valueOf(1), rs.getIntegerOrNull()); - assertEquals(Long.valueOf(2), rs.getLongOrNull()); - assertEquals(Float.valueOf(3.2f), rs.getFloatOrNull()); - assertEquals(Double.valueOf(4.2), rs.getDoubleOrNull()); - assertEquals(new BigDecimal("5.3"), rs.getBigDecimalOrNull()); - assertEquals("Hello", rs.getStringOrNull()); - assertEquals("T", rs.getStringOrNull()); - assertEquals("World", rs.getClobStringOrNull()); - assertArrayEquals("More".getBytes(), rs.getBlobBytesOrNull()); - assertEquals(now, rs.getLocalDateTimeOrNull()); - assertEquals(localDateNow, rs.getLocalDateOrNull()); - return null; - }); - db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal, str_varchar, str_fixed, str_lob, " - + "bin_blob, date_millis, local_date from dbtest") - .query((RowsHandler) rs -> { - assertTrue(rs.next()); - assertEquals(1, rs.getIntegerOrZero()); - assertEquals(2, rs.getLongOrZero()); - assertEquals(3.2, rs.getFloatOrZero(), 0.01); - assertEquals(4.2, rs.getDoubleOrZero(), 0.01); - assertEquals(new BigDecimal("5.3"), rs.getBigDecimalOrZero()); - assertEquals("Hello", rs.getStringOrEmpty()); - assertEquals("T", rs.getStringOrEmpty()); - assertEquals("World", rs.getClobStringOrEmpty()); - assertArrayEquals("More".getBytes(), rs.getBlobBytesOrZeroLen()); - return null; - }); - db.toSelect("select str_lob, bin_blob from dbtest").query((RowsHandler) rs -> { - assertTrue(rs.next()); - assertEquals("World", readerToString(rs.getClobReaderOrNull(1))); - assertArrayEquals("More".getBytes(), inputStreamToString(rs.getBlobInputStreamOrNull(2))); - return null; - }); - db.toSelect("select str_lob, bin_blob from dbtest").query((RowsHandler) rs -> { - assertTrue(rs.next()); - assertEquals("World", readerToString(rs.getClobReaderOrEmpty(1))); - assertArrayEquals("More".getBytes(), inputStreamToString(rs.getBlobInputStreamOrEmpty(2))); - return null; - }); - db.toSelect("select str_lob, bin_blob from dbtest").query((RowsHandler) rs -> { - assertTrue(rs.next()); - assertEquals("World", readerToString(rs.getClobReaderOrNull())); - assertArrayEquals("More".getBytes(), inputStreamToString(rs.getBlobInputStreamOrNull())); - return null; - }); - db.toSelect("select str_lob, bin_blob from dbtest").query((RowsHandler) rs -> { - assertTrue(rs.next()); - assertEquals("World", readerToString(rs.getClobReaderOrEmpty())); - assertArrayEquals("More".getBytes(), inputStreamToString(rs.getBlobInputStreamOrEmpty())); - return null; - }); - db.toSelect("select str_lob, bin_blob from dbtest").query((RowsHandler) rs -> { - assertTrue(rs.next()); - assertEquals("World", readerToString(rs.getClobReaderOrNull("str_lob"))); - assertArrayEquals("More".getBytes(), inputStreamToString(rs.getBlobInputStreamOrNull("bin_blob"))); - return null; - }); - db.toSelect("select str_lob, bin_blob from dbtest").query((RowsHandler) rs -> { - assertTrue(rs.next()); - assertEquals("World", readerToString(rs.getClobReaderOrEmpty("str_lob"))); - assertArrayEquals("More".getBytes(), inputStreamToString(rs.getBlobInputStreamOrEmpty("bin_blob"))); - return null; - }); - assertEquals(Long.valueOf(1), db.toSelect("select count(*) from dbtest where nbr_integer=:i and nbr_long=:l and " - + "abs(nbr_float-:f)<0.01 and abs(nbr_double-:d)<0.01 and nbr_big_decimal=:bd and str_varchar=:s " - + "and str_fixed=:sf and date_millis=:date and local_date=:local_date") - .argInteger("i", 1) - .argLong("l", 2L) - .argFloat("f", 3.2f) - .argDouble("d", 4.2) - .argBigDecimal("bd", bigDecimal) - .argString("s", "Hello") - .argString("sf", "T") - .argLocalDateTime("date", now) - .argLocalDate("local_date", localDateNow) - .queryLongOrNull()); - List result = db.toSelect("select count(*) from dbtest where nbr_integer=:i and nbr_long=:l and " - + "abs(nbr_float-:f)<0.01 and abs(nbr_double-:d)<0.01 and nbr_big_decimal=:bd and str_varchar=:s " - + "and str_fixed=:sf and date_millis=:date and local_date=:local_date").argInteger("i", 1).argLong("l", 2L).argFloat("f", 3.2f) - .argDouble("d", 4.2).argBigDecimal("bd", bigDecimal).argString("s", "Hello").argString("sf", "T") - .argLocalDateTime("date", now).argLocalDate("local_date", localDateNow).queryLongs(); - assertEquals(1, result.size()); - assertEquals(Long.valueOf(1), result.get(0)); - } - - @Test - public void updatePositionalArgs() { - new Schema() - .addTable("dbtest") - .addColumn("pk").primaryKey().table() - .addColumn("nbr_integer").asInteger().table() - .addColumn("nbr_long").asLong().table() - .addColumn("nbr_float").asFloat().table() - .addColumn("nbr_double").asDouble().table() - .addColumn("nbr_big_decimal").asBigDecimal(19, 9).table() - .addColumn("str_varchar").asString(80).table() - .addColumn("str_fixed").asStringFixed(1).table() - .addColumn("str_lob").asClob().table() - .addColumn("bin_blob").asBlob().table() - .addColumn("date_millis").asLocalDateTime().table() - .addColumn("local_date").asLocalDate().table().schema().execute(db); - - BigDecimal bigDecimal = new BigDecimal("5.3"); - assertEquals(1, db.toInsert("insert into dbtest values (?,?,?,?,?,?,?,?,?,?,?,?)") - .argLong(1L) - .argInteger(1) - .argLong(2L) - .argFloat(3.2f) - .argDouble(4.2) - .argBigDecimal(bigDecimal) - .argString("Hello") - .argString("T") - .argClobString("World") - .argBlobBytes("More".getBytes()) - .argLocalDateTime(now) - .argLocalDate(localDateNow).insert()); - db.toUpdate("update dbtest set nbr_integer=?, nbr_long=?, nbr_float=?, nbr_double=?, nbr_big_decimal=?, " - + "str_varchar=?, str_fixed=?, str_lob=?, bin_blob=?, date_millis=?, local_date=?").argInteger(null).argLong(null) - .argFloat(null).argDouble(null).argBigDecimal(null).argString(null).argString(null).argClobString(null) - .argBlobBytes(null).argLocalDateTime(null).argLocalDate(null).update(1); - db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal, str_varchar, str_fixed, str_lob, " - + "bin_blob, date_millis, local_date from dbtest").query((RowsHandler) rs -> { - assertTrue(rs.next()); - assertNull(rs.getIntegerOrNull(1)); - assertNull(rs.getIntegerOrNull("nbr_integer")); - assertNull(rs.getLongOrNull(2)); - assertNull(rs.getLongOrNull("nbr_long")); - assertNull(rs.getFloatOrNull(3)); - assertNull(rs.getFloatOrNull("nbr_float")); - assertNull(rs.getDoubleOrNull(4)); - assertNull(rs.getDoubleOrNull("nbr_double")); - assertNull(rs.getBigDecimalOrNull(5)); - assertNull(rs.getBigDecimalOrNull("nbr_big_decimal")); - assertNull(rs.getStringOrNull(6)); - assertNull(rs.getStringOrNull("str_varchar")); - assertNull(rs.getStringOrNull(7)); - assertNull(rs.getStringOrNull("str_fixed")); - assertNull(rs.getClobStringOrNull(8)); - assertNull(rs.getClobStringOrNull("str_lob")); - assertNull(rs.getBlobBytesOrNull(9)); - assertNull(rs.getBlobBytesOrNull("bin_blob")); - assertNull(rs.getLocalDateTimeOrNull(10)); - assertNull(rs.getLocalDateTimeOrNull("date_millis")); - assertNull(rs.getLocalDateOrNull(11)); - assertNull(rs.getLocalDateOrNull("local_date")); - return null; - }); - assertEquals(1, db.toUpdate("update dbtest set nbr_integer=?, nbr_long=?, nbr_float=?, nbr_double=?, " - + "nbr_big_decimal=?, str_varchar=?, str_fixed=?, str_lob=?, bin_blob=?, date_millis=?, local_date=?").argInteger(1) - .argLong(2L).argFloat(3.2f).argDouble(4.2).argBigDecimal(bigDecimal).argString("Hello").argString("T") - .argClobString("World").argBlobBytes("More".getBytes()).argLocalDateTime(now).argLocalDate(localDateNow).update()); - db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal, str_varchar, str_fixed, str_lob, " - + "bin_blob, date_millis, local_date from dbtest").query((RowsHandler) rs -> { - assertTrue(rs.next()); - assertEquals(Integer.valueOf(1), rs.getIntegerOrNull(1)); - assertEquals(Integer.valueOf(1), rs.getIntegerOrNull("nbr_integer")); - assertEquals(Long.valueOf(2), rs.getLongOrNull(2)); - assertEquals(Long.valueOf(2), rs.getLongOrNull("nbr_long")); - assertEquals(Float.valueOf(3.2f), rs.getFloatOrNull(3)); - assertEquals(Float.valueOf(3.2f), rs.getFloatOrNull("nbr_float")); - assertEquals(Double.valueOf(4.2), rs.getDoubleOrNull(4)); - assertEquals(Double.valueOf(4.2), rs.getDoubleOrNull("nbr_double")); - assertEquals(new BigDecimal("5.3"), rs.getBigDecimalOrNull(5)); - assertEquals(new BigDecimal("5.3"), rs.getBigDecimalOrNull("nbr_big_decimal")); - assertEquals("Hello", rs.getStringOrNull(6)); - assertEquals("Hello", rs.getStringOrNull("str_varchar")); - assertEquals("T", rs.getStringOrNull(7)); - assertEquals("T", rs.getStringOrNull("str_fixed")); - assertEquals("World", rs.getClobStringOrNull(8)); - assertEquals("World", rs.getClobStringOrNull("str_lob")); - assertArrayEquals("More".getBytes(), rs.getBlobBytesOrNull(9)); - assertArrayEquals("More".getBytes(), rs.getBlobBytesOrNull("bin_blob")); - assertEquals(now, rs.getLocalDateTimeOrNull(10)); - assertEquals(now, rs.getLocalDateTimeOrNull("date_millis")); - assertEquals(localDateNow, rs.getLocalDateOrNull(11)); - assertEquals(localDateNow, rs.getLocalDateOrNull("local_date")); - return null; - }); - db.toUpdate("update dbtest set str_lob=?, bin_blob=?").argClobReader(null).argBlobStream(null).update(1); - db.toSelect("select str_lob, bin_blob from dbtest").query((RowsHandler) rs -> { - assertTrue(rs.next()); - assertNull(rs.getClobStringOrNull(1)); - assertNull(rs.getClobStringOrNull("str_lob")); - assertNull(rs.getBlobBytesOrNull(2)); - assertNull(rs.getBlobBytesOrNull("bin_blob")); - return null; - }); - db.toUpdate("update dbtest set str_lob=?, bin_blob=?").argClobReader(new StringReader("World")) - .argBlobStream(new ByteArrayInputStream("More".getBytes())).update(1); - db.toSelect("select str_lob, bin_blob from dbtest").query((RowsHandler) rs -> { - assertTrue(rs.next()); - assertEquals("World", rs.getClobStringOrNull(1)); - assertEquals("World", rs.getClobStringOrNull("str_lob")); - assertArrayEquals("More".getBytes(), rs.getBlobBytesOrNull(2)); - assertArrayEquals("More".getBytes(), rs.getBlobBytesOrNull("bin_blob")); - return null; - }); - } - - @Test - public void updateNamedArgs() { - new Schema() - .addTable("dbtest") - .addColumn("pk").primaryKey().table() - .addColumn("nbr_integer").asInteger().table() - .addColumn("nbr_long").asLong().table() - .addColumn("nbr_float").asFloat().table() - .addColumn("nbr_double").asDouble().table() - .addColumn("nbr_big_decimal").asBigDecimal(19, 9).table() - .addColumn("str_varchar").asString(80).table() - .addColumn("str_fixed").asStringFixed(1).table() - .addColumn("str_lob").asClob().table() - .addColumn("bin_blob").asBlob().table() - .addColumn("date_millis").asLocalDateTime().table() - .addColumn("local_date").asLocalDate().table().schema().execute(db); - BigDecimal bigDecimal = new BigDecimal("5.3"); - db.toInsert("insert into dbtest values (:pk,:a,:b,:c,:d,:e,:f,:sf,:g,:h,:i,:j)").argLong(":pk", 1L).argInteger(":a", 1) - .argLong(":b", 2L).argFloat(":c", 3.2f).argDouble(":d", 4.2).argBigDecimal(":e", bigDecimal) - .argString(":f", "Hello").argString(":sf", "T") - .argClobString(":g", "World").argBlobBytes(":h", "More".getBytes()) - .argLocalDateTime(":i", now).argLocalDate(":j", localDateNow).insert(1); - db.toUpdate("update dbtest set nbr_integer=:a, nbr_long=:b, nbr_float=:c, nbr_double=:d, nbr_big_decimal=:e, " - + "str_varchar=:f, str_fixed=:sf, str_lob=:g, bin_blob=:h, date_millis=:i, local_date=:j").argInteger(":a", null) - .argLong(":b", null).argFloat(":c", null).argDouble(":d", null).argBigDecimal(":e", null) - .argString(":f", null).argString(":sf", null) - .argClobString(":g", null).argBlobBytes(":h", null) - .argLocalDateTime(":i", null).argLocalDate(":j", null).update(1); - db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal, str_varchar, str_fixed, str_lob, " - + "bin_blob, date_millis, local_date from dbtest").query((RowsHandler) rs -> { - assertTrue(rs.next()); - assertNull(rs.getIntegerOrNull(1)); - assertNull(rs.getIntegerOrNull("nbr_integer")); - assertNull(rs.getLongOrNull(2)); - assertNull(rs.getLongOrNull("nbr_long")); - assertNull(rs.getFloatOrNull(3)); - assertNull(rs.getFloatOrNull("nbr_float")); - assertNull(rs.getDoubleOrNull(4)); - assertNull(rs.getDoubleOrNull("nbr_double")); - assertNull(rs.getBigDecimalOrNull(5)); - assertNull(rs.getBigDecimalOrNull("nbr_big_decimal")); - assertNull(rs.getStringOrNull(6)); - assertNull(rs.getStringOrNull("str_varchar")); - assertNull(rs.getStringOrNull(7)); - assertNull(rs.getStringOrNull("str_fixed")); - assertNull(rs.getClobStringOrNull(8)); - assertNull(rs.getClobStringOrNull("str_lob")); - assertNull(rs.getBlobBytesOrNull(9)); - assertNull(rs.getBlobBytesOrNull("bin_blob")); - assertNull(rs.getLocalDateTimeOrNull(10)); - assertNull(rs.getLocalDateTimeOrNull("date_millis")); - assertNull(rs.getLocalDateOrNull(11)); - assertNull(rs.getLocalDateOrNull("local_date")); - return null; - }); - db.toUpdate("update dbtest set nbr_integer=:a, nbr_long=:b, nbr_float=:c, nbr_double=:d, nbr_big_decimal=:e, " - + "str_varchar=:f, str_fixed=:sf, str_lob=:g, bin_blob=:h, date_millis=:i, local_date=:j").argInteger(":a", 1) - .argLong(":b", 2L).argFloat(":c", 3.2f).argDouble(":d", 4.2).argBigDecimal(":e", bigDecimal) - .argString(":f", "Hello").argString(":sf", "T") - .argClobString(":g", "World").argBlobBytes(":h", "More".getBytes()) - .argLocalDateTime(":i", now).argLocalDate(":j", localDateNow).update(1); - db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal, str_varchar, str_fixed, str_lob, " - + "bin_blob, date_millis, local_date from dbtest") - .query((RowsHandler) rs -> { - assertTrue(rs.next()); - assertEquals(Integer.valueOf(1), rs.getIntegerOrNull(1)); - assertEquals(Integer.valueOf(1), rs.getIntegerOrNull("nbr_integer")); - assertEquals(Long.valueOf(2), rs.getLongOrNull(2)); - assertEquals(Long.valueOf(2), rs.getLongOrNull("nbr_long")); - assertEquals(Float.valueOf(3.2f), rs.getFloatOrNull(3)); - assertEquals(Float.valueOf(3.2f), rs.getFloatOrNull("nbr_float")); - assertEquals(Double.valueOf(4.2), rs.getDoubleOrNull(4)); - assertEquals(Double.valueOf(4.2), rs.getDoubleOrNull("nbr_double")); - assertEquals(new BigDecimal("5.3"), rs.getBigDecimalOrNull(5)); - assertEquals(new BigDecimal("5.3"), rs.getBigDecimalOrNull("nbr_big_decimal")); - assertEquals("Hello", rs.getStringOrNull(6)); - assertEquals("Hello", rs.getStringOrNull("str_varchar")); - assertEquals("T", rs.getStringOrNull(7)); - assertEquals("T", rs.getStringOrNull("str_fixed")); - assertEquals("World", rs.getClobStringOrNull(8)); - assertEquals("World", rs.getClobStringOrNull("str_lob")); - assertArrayEquals("More".getBytes(), rs.getBlobBytesOrNull(9)); - assertArrayEquals("More".getBytes(), rs.getBlobBytesOrNull("bin_blob")); - assertEquals(now, rs.getLocalDateTimeOrNull(10)); - assertEquals(now, rs.getLocalDateTimeOrNull("date_millis")); - assertEquals(localDateNow, rs.getLocalDateOrNull(11)); - assertEquals(localDateNow, rs.getLocalDateOrNull("local_date")); - return null; - }); - - db.toUpdate("update dbtest set str_lob=:a, bin_blob=:b").argClobReader(":a", null).argBlobStream(":b", null).update(1); - db.toSelect("select str_lob, bin_blob from dbtest").query((RowsHandler) rs -> { - assertTrue(rs.next()); - assertNull(rs.getClobStringOrNull(1)); - assertNull(rs.getClobStringOrNull("str_lob")); - assertNull(rs.getBlobBytesOrNull(2)); - assertNull(rs.getBlobBytesOrNull("bin_blob")); - return null; - }); - db.toUpdate("update dbtest set str_lob=:a, bin_blob=:b").argClobReader(":a", new StringReader("World")) - .argBlobStream(":b", new ByteArrayInputStream("More".getBytes())).update(1); - db.toSelect("select str_lob, bin_blob from dbtest").query((RowsHandler) rs -> { - assertTrue(rs.next()); - assertEquals("World", rs.getClobStringOrNull(1)); - assertEquals("World", rs.getClobStringOrNull("str_lob")); - assertArrayEquals("More".getBytes(), rs.getBlobBytesOrNull(2)); - assertArrayEquals("More".getBytes(), rs.getBlobBytesOrNull("bin_blob")); - return null; - }); - } - - @Test - public void nullValues() { - new Schema() - .addTable("dbtest") - .addColumn("pk").primaryKey().table() - .addColumn("nbr_integer").asInteger().table() - .addColumn("nbr_long").asLong().table() - .addColumn("nbr_float").asFloat().table() - .addColumn("nbr_double").asDouble().table() - .addColumn("nbr_big_decimal").asBigDecimal(19, 9).table() - .addColumn("str_varchar").asString(80).table() - .addColumn("str_fixed").asStringFixed(1).table() - .addColumn("str_lob").asClob().table() - .addColumn("bin_blob").asBlob().table() - .addColumn("date_millis").asLocalDateTime().table() - .addColumn("local_date").asLocalDate().table().schema().execute(db); - db.toInsert("insert into dbtest values (?,?,?,?,?,?,?,?,?,?,?,?)").argLong(1L).argInteger(null).argLong(null) - .argFloat(null).argDouble(null).argBigDecimal(null).argString(null).argString(null).argClobString(null) - .argBlobBytes(null).argLocalDateTime(null).argLocalDate(null).insert(1); - db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal, str_varchar, str_fixed, str_lob, " - + "bin_blob, date_millis, local_date from dbtest") - .query((RowsHandler) rs -> { - assertTrue(rs.next()); - assertNull(rs.getIntegerOrNull(1)); - assertNull(rs.getIntegerOrNull("nbr_integer")); - assertNull(rs.getLongOrNull(2)); - assertNull(rs.getLongOrNull("nbr_long")); - assertNull(rs.getFloatOrNull(3)); - assertNull(rs.getFloatOrNull("nbr_float")); - assertNull(rs.getDoubleOrNull(4)); - assertNull(rs.getDoubleOrNull("nbr_double")); - assertNull(rs.getBigDecimalOrNull(5)); - assertNull(rs.getBigDecimalOrNull("nbr_big_decimal")); - assertNull(rs.getStringOrNull(6)); - assertNull(rs.getStringOrNull("str_varchar")); - assertNull(rs.getStringOrNull(7)); - assertNull(rs.getStringOrNull("str_fixed")); - assertNull(rs.getClobStringOrNull(8)); - assertNull(rs.getClobStringOrNull("str_lob")); - assertNull(rs.getBlobBytesOrNull(9)); - assertNull(rs.getBlobBytesOrNull("bin_blob")); - assertNull(rs.getLocalDateTimeOrNull(10)); - assertNull(rs.getLocalDateTimeOrNull("date_millis")); - assertNull(rs.getLocalDateOrNull(11)); - assertNull(rs.getLocalDateOrNull("local_date")); - return null; - }); - db.toSelect("select str_lob, bin_blob from dbtest").query((RowsHandler) rs -> { - assertTrue(rs.next()); - assertNull(rs.getClobReaderOrNull(1)); - assertNull(rs.getBlobInputStreamOrNull(2)); - return null; - }); - db.toSelect("select str_lob, bin_blob from dbtest").query((RowsHandler) rs -> { - assertTrue(rs.next()); - assertNull(rs.getClobReaderOrNull("str_lob")); - assertNull(rs.getBlobInputStreamOrNull("bin_blob")); - return null; - }); - } - - @Test - public void fromAny() { - assertEquals(db.toSelect("select 1" + db.flavor().fromAny()).queryIntegerOrZero(), 1); - } - - @Test - public void metadataColumnNames() { - new Schema().addTable("dbtest").addColumn("pk").primaryKey().schema().execute(db); - db.toSelect("select Pk, Pk as Foo, Pk as \"Foo\" from dbtest").query(rs -> { - assertArrayEquals(new String[]{"PK", "FOO", "Foo"}, rs.getColumnLabels()); - return null; - }); - } - - @Test - public void metadataColumnTypes() { - String timestampColumnName = "data_millis"; - String dateColumnName = "local_date"; - new Schema() - .addTable("dbtest") - .addColumn(timestampColumnName).asLocalDateTime().table() - .addColumn(dateColumnName).asLocalDate().table().schema().execute(db); - db.toSelect("select * from dbtest").query((RowsHandler) rs -> { - ResultSetMetaData metadata = rs.getMetadata(); - for (int i = 1; i <= metadata.getColumnCount(); i++) { - String columnName = metadata.getColumnName(i); - String columnType = metadata.getColumnTypeName(i); - if (columnName.equalsIgnoreCase(timestampColumnName)) { - if ("sqlserver".equals(db.flavor().getName())) { - assertEquals("DATETIME2", columnType.toUpperCase()); - } else if ("hsqldb".equals(db.flavor().getName())) { - assertEquals("TIMESTAMP WITH TIME ZONE", columnType.toUpperCase()); - } else { - assertEquals("TIMESTAMP", columnType.toUpperCase()); - } - } else if (columnName.equalsIgnoreCase(dateColumnName)) { - assertEquals("DATE", columnType.toUpperCase()); - } else { - fail("Unexpected column " + columnName + " of type " + columnType); - } - } - return null; - }); - } - - @Test - public void intervals() { - new Schema().addTable("dbtest").addColumn("d").asLocalDateTime().schema().execute(db); - db.toInsert("insert into dbtest (d) values (?)").argLocalDateTime(now).insert(1); - assertEquals(1, db.toSelect("select count(1) from dbtest where d - interval '1' hour * ? < ?") - .argInteger(2) - .argLocalDateTime(now) - .queryIntegerOrZero()); - } - - @Test - public void saveResultAsTable() { - new Schema().addTable("dbtest") - .addColumn("nbr_integer").asInteger().primaryKey().table() - .addColumn("nbr_long").asLong().table() - .addColumn("nbr_float").asFloat().table() - .addColumn("nbr_double").asDouble().table() - .addColumn("nbr_big_decimal").asBigDecimal(19, 9).table() - .addColumn("str_varchar").asString(80).table() - .addColumn("str_fixed").asStringFixed(1).table() - .addColumn("str_lob").asClob().table() - .addColumn("bin_blob").asBlob().table() - .addColumn("boolean_flag").asBoolean().table() - .addColumn("date_millis").asLocalDateTime().table() - .addColumn("local_date").asLocalDate().schema().execute(db); - db.toInsert("insert into dbtest (nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal, str_varchar," - + " str_fixed, str_lob, bin_blob, boolean_flag, date_millis, local_date) values (?,?,?,?,?,?,?,?,?,?,?,?)") - .argInteger(Integer.MAX_VALUE).argLong(Long.MAX_VALUE).argFloat(Float.MAX_VALUE) - .argDouble(Double.MAX_VALUE).argBigDecimal(new BigDecimal("123.456")) - .argString("hello").argString("Z").argClobString("hello again") - .argBlobBytes(new byte[]{'1', '2'}).argBoolean(true) - .argLocalDateTime(now) - .argLocalDate(localDateNow).insert(1); - db.toInsert("insert into dbtest (nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal, str_varchar," - + " str_fixed, str_lob, bin_blob, boolean_flag, date_millis, local_date) values (?,?,?,?,?,?,?,?,?,?,?,?)") - .argInteger(Integer.MIN_VALUE).argLong(Long.MIN_VALUE).argFloat(0.000001f) - .argDouble(Double.MIN_VALUE).argBigDecimal(new BigDecimal("-123.456")) - .argString("goodbye").argString("A").argClobString("bye again") - .argBlobBytes(new byte[]{'3', '4'}).argBoolean(false) - .argLocalDateTime(now) - .argLocalDate(localDateNow).insert(1); - String expectedSchema = new Schema().addTable("dbtest2") - .addColumn("nbr_integer").asInteger().table() - .addColumn("nbr_long").asLong().table() - .addColumn("nbr_float").asFloat().table() - .addColumn("nbr_double").asDouble().table() - .addColumn("nbr_big_decimal").asBigDecimal(19, 9).table() - .addColumn("str_varchar").asString(80).table() - .addColumn("str_fixed").asStringFixed(1).table() - .addColumn("str_lob").asClob().table() - .addColumn("bin_blob").asBlob().table() - .addColumn("boolean_flag").asBoolean().table() - .addColumn("date_millis").asLocalDateTime().table() - .addColumn("local_date").asLocalDate().schema().print(db.flavor()); - List args = db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal," - + " str_varchar, str_fixed, str_lob, bin_blob, boolean_flag, date_millis, local_date from dbtest") - .query(rs -> { - List result = new ArrayList<>(); - while (rs.next()) { - if (result.size() == 0) { - db.dropTableQuietly("dbtest2"); - Schema schema = new Schema().addTableFromRow("dbtest2", rs).schema(); - assertEquals(expectedSchema, schema.print(db.flavor())); - schema.execute(db); - } - result.add(SqlArgs.readRow(rs)); - } - return result; - }); - - db.toInsert(Sql.insert("dbtest2", args)).insertBatch(); - - assertEquals(2, db.toSelect("select count(*) from dbtest2").queryIntegerOrZero()); - - assertEquals( - db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal," - + " str_varchar, str_fixed, str_lob, bin_blob, boolean_flag, date_millis, local_date from dbtest order by 1") - .queryMany(SqlArgs::readRow), - db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal," - + " str_varchar, str_fixed, str_lob, bin_blob, boolean_flag, date_millis, local_date from dbtest2 order by 1") - .queryMany(SqlArgs::readRow)); - - assertEquals( - Arrays.asList( - new SqlArgs().argInteger("nbr_integer", Integer.MIN_VALUE) - .argLong("nbr_long", Long.MIN_VALUE) - .argFloat("nbr_float", 0.000001f) - .argDouble("nbr_double", Double.MIN_VALUE) - .argBigDecimal("nbr_big_decimal", new BigDecimal("-123.456")) - .argString("str_varchar", "goodbye") - .argString("str_fixed", "A") - .argClobString("str_lob", "bye again") - .argBlobBytes("bin_blob", new byte[]{'3', '4'}) - .argString("boolean_flag", "N")//.argBoolean("boolean_flag", false) - .argLocalDateTime("date_millis", now) - .argLocalDate("local_date", localDateNow), - new SqlArgs().argInteger("nbr_integer", Integer.MAX_VALUE) - .argLong("nbr_long", Long.MAX_VALUE) - .argFloat("nbr_float", Float.MAX_VALUE) - .argDouble("nbr_double", Double.MAX_VALUE) - .argBigDecimal("nbr_big_decimal", new BigDecimal("123.456")) - .argString("str_varchar", "hello") - .argString("str_fixed", "Z") - .argClobString("str_lob", "hello again") - .argBlobBytes("bin_blob", new byte[]{'1', '2'}) - .argString("boolean_flag", "Y")//.argBoolean("boolean_flag", true) - .argLocalDateTime("date_millis", now) - .argLocalDate("local_date", localDateNow)), - db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal," - + " str_varchar, str_fixed, str_lob, bin_blob, boolean_flag, date_millis, local_date from dbtest2 order by 1") - .queryMany(SqlArgs::readRow)); - } - - @Test - public void readSqlArgs() { - new Schema().addTable("dbtest").addColumn("pk").primaryKey().schema().execute(db); - db.toInsert("insert into dbtest (pk) values (?)").argInteger(1).insert(1); - SqlArgs args = db.toSelect("select Pk, Pk as Foo, Pk as \"Foo\", pk as \"g arB#G!\"," - + " pk as \"TitleCase\" from dbtest") - .queryOneOrThrow(SqlArgs::readRow); - - assertEquals(Arrays.asList("pk", "foo", "foo_2", "g_ar_b_g", "title_case"), args.names()); - } - - @Test - public void clockSync() { - db.assertTimeSynchronized(); - } - - @Test - public void booleanColumn() { - new Schema().addTable("dbtest") - .addColumn("t").asBoolean().table() - .addColumn("f").asBoolean().table() - .addColumn("n").asBoolean().schema().execute(db); - db.toInsert("insert into dbtest (t,f,n) values (?,:f,?)") - .argBoolean(true).argBoolean("f", false).argBoolean(null).insert(1); - db.toSelect("select t,f,n from dbtest") - .query(rs -> { - assertTrue(rs.next()); - assertSame(rs.getBooleanOrNull(), Boolean.TRUE); - assertSame(rs.getBooleanOrNull(), Boolean.FALSE); - assertNull(rs.getBooleanOrNull()); - return null; - }); - // Verify use of getBooleanOrNull(int) followed by default getBooleanOrNull() tracks - // the current column index correctly (picks up where the explicit one left off) - db.toSelect("select t,f,n from dbtest") - .query(rs -> { - assertTrue(rs.next()); - assertSame(rs.getBooleanOrNull(2), Boolean.FALSE); - assertNull(rs.getBooleanOrNull()); - return null; - }); - // Verify use of getBooleanOrNull(String) followed by default getBooleanOrNull() tracks - // the current column index correctly (picks up where the explicit one left off) - db.toSelect("select t,f,n from dbtest") - .query(rs -> { - assertTrue(rs.next()); - assertSame(rs.getBooleanOrNull("f"), Boolean.FALSE); - assertNull(rs.getBooleanOrNull()); - return null; - }); - db.toSelect("select t,f,n from dbtest") - .query(rs -> { - assertTrue(rs.next()); - assertTrue(rs.getBooleanOrFalse()); - assertFalse(rs.getBooleanOrFalse()); - assertFalse(rs.getBooleanOrFalse()); - return null; - }); - // Verify use of getBooleanOrFalse(int) followed by default getBooleanOrFalse() tracks - // the current column index correctly (picks up where the explicit one left off) - db.toSelect("select t,f,n from dbtest") - .query(rs -> { - assertTrue(rs.next()); - assertFalse(rs.getBooleanOrFalse(2)); - assertFalse(rs.getBooleanOrFalse()); - return null; - }); - // Verify use of getBooleanOrFalse(String) followed by default getBooleanOrFalse() tracks - // the current column index correctly (picks up where the explicit one left off) - db.toSelect("select t,f,n from dbtest") - .query(rs -> { - assertTrue(rs.next()); - assertFalse(rs.getBooleanOrFalse("f")); - assertFalse(rs.getBooleanOrFalse()); - return null; - }); - db.toSelect("select t,f,n from dbtest") - .query(rs -> { - assertTrue(rs.next()); - assertTrue(rs.getBooleanOrTrue()); - assertFalse(rs.getBooleanOrTrue()); - assertTrue(rs.getBooleanOrTrue()); - return null; - }); - // Verify use of getBooleanOrTrue(int) followed by default getBooleanOrTrue() tracks - // the current column index correctly (picks up where the explicit one left off) - db.toSelect("select t,f,n from dbtest") - .query(rs -> { - assertTrue(rs.next()); - assertFalse(rs.getBooleanOrTrue(2)); - assertTrue(rs.getBooleanOrTrue()); - return null; - }); - // Verify use of getBooleanOrTrue(String) followed by default getBooleanOrTrue() tracks - // the current column index correctly (picks up where the explicit one left off) - db.toSelect("select t,f,n from dbtest") - .query(rs -> { - assertTrue(rs.next()); - assertFalse(rs.getBooleanOrTrue("f")); - assertTrue(rs.getBooleanOrTrue()); - return null; - }); - db.toDelete("delete from dbtest where t=? and f=?") - .argBoolean(true).argBoolean(false).update(1); - - db.toInsert("insert into dbtest (t,f,n) values (:t,:f,:n)") - .argBoolean("t", true).argBoolean("f", false).argBoolean("n", null).insert(1); - db.toSelect("select t,f,n from dbtest") - .query(rs -> { - assertTrue(rs.next()); - assertSame(rs.getBooleanOrNull(1), Boolean.TRUE); - assertSame(rs.getBooleanOrNull(2), Boolean.FALSE); - assertNull(rs.getBooleanOrNull(3)); - assertEquals(rs.getBooleanOrFalse(1), Boolean.TRUE); - assertEquals(rs.getBooleanOrFalse(2), Boolean.FALSE); - assertEquals(rs.getBooleanOrFalse(3), Boolean.FALSE); - assertEquals(rs.getBooleanOrTrue(1), Boolean.TRUE); - assertEquals(rs.getBooleanOrTrue(2), Boolean.FALSE); - assertEquals(rs.getBooleanOrTrue(3), Boolean.TRUE); - assertSame(rs.getBooleanOrNull("t"), Boolean.TRUE); - assertSame(rs.getBooleanOrNull("f"), Boolean.FALSE); - assertNull(rs.getBooleanOrNull("n")); - assertEquals(rs.getBooleanOrFalse("t"), Boolean.TRUE); - assertEquals(rs.getBooleanOrFalse("f"), Boolean.FALSE); - assertEquals(rs.getBooleanOrFalse("n"), Boolean.FALSE); - assertEquals(rs.getBooleanOrTrue("t"), Boolean.TRUE); - assertEquals(rs.getBooleanOrTrue("f"), Boolean.FALSE); - assertEquals(rs.getBooleanOrTrue("n"), Boolean.TRUE); - return null; - }); - assertSame(db.toSelect("select t from dbtest").queryBooleanOrNull(), Boolean.TRUE); - assertTrue(db.toSelect("select t from dbtest").queryBooleanOrFalse()); - assertTrue(db.toSelect("select t from dbtest").queryBooleanOrTrue()); - assertSame(db.toSelect("select f from dbtest").queryBooleanOrNull(), Boolean.FALSE); - assertFalse(db.toSelect("select f from dbtest").queryBooleanOrFalse()); - assertFalse(db.toSelect("select f from dbtest").queryBooleanOrTrue()); - assertNull(db.toSelect("select n from dbtest").queryBooleanOrNull()); - assertFalse(db.toSelect("select n from dbtest").queryBooleanOrFalse()); - assertTrue(db.toSelect("select n from dbtest").queryBooleanOrTrue()); - } - - @Test - public void batchInsert() { - new Schema().addTable("dbtest") - .addColumn("pk").primaryKey().schema().execute(db); - - db.toInsert("insert into dbtest (pk) values (?)") - .argInteger(1).batch() - .argInteger(2).batch() - .argInteger(3).batch().insertBatch(); - - assertEquals(3, db.toSelect("select count(*) from dbtest").queryIntegerOrZero()); - } - - @Test - public void batchInsertPkLong() { - new Schema().addTable("dbtest") - .addColumn("pk").primaryKey().table() - .addColumn("s").asString(10).schema().execute(db); - - db.toInsert("insert into dbtest (pk,s) values (?,?)") - .argPkLong(1L).argString("hi").batch() - .argPkLong(2L).argString("hello").batch() - .argPkLong(3L).argString("howdy").batch().insertBatch(); - - assertEquals(3, db.toSelect("select count(*) from dbtest").queryIntegerOrZero()); - - try { - db.toInsert("insert into dbtest (pk,s) values (?,?)") - .argPkLong(1L).argString("hi").batch() - // argPkLong in different position ==> error - .argString("hello").argPkLong(2L).batch().insertBatch(); - fail("Expecting an exception to be thrown"); - } catch (DatabaseException e) { - assertEquals("The argPkLong() calls must be in the same position across batch records", e.getMessage()); - } - - try { - db.toInsert("insert into dbtest (pk,s) values (?,?)") - // multiple pk calls ==> error - .argPkLong(1L).argPkLong(1L).batch() - .argPkLong(2L).argString("hello").batch().insertBatch(); - fail("Expecting an exception to be thrown"); - } catch (DatabaseException e) { - assertEquals("Only call one argPk*() method", e.getMessage()); - } - } - - @Test - public void batchInsertPkLongNamed() { - new Schema().addTable("dbtest") - .addColumn("pk").primaryKey().table() - .addColumn("s").asString(10).schema().execute(db); - - db.toInsert("insert into dbtest (pk,s) values (:pk,?)") - .argPkLong("pk", 1L).argString("hi").batch() - .argPkLong("pk", 2L).argString("hello").batch() - .argPkLong("pk", 3L).argString("howdy").batch().insertBatch(); - - assertEquals(3, db.toSelect("select count(*) from dbtest").queryIntegerOrZero()); - - db.toInsert("insert into dbtest (pk,s) values (:pk,?)") - .batch().argPkLong("pk", 4L).argString("hi").batch() - .argString("hello").argPkLong("pk", 5L).insertBatch(); - - assertEquals(5, db.toSelect("select count(*) from dbtest").queryIntegerOrZero()); - - try { - db.toInsert("insert into dbtest (pk,s) values (:pk,?)") - // multiple pk calls ==> error - .argPkLong("pk", 1L).argPkLong(1L).batch().insertBatch(); - fail("Expecting an exception to be thrown"); - } catch (DatabaseException e) { - assertEquals("Only call one argPk*() method", e.getMessage()); - } - - try { - db.toInsert("insert into dbtest (pk,s) values (?,?)") - .argPkLong("pk", 1L).argString("howdy").batch() - // different name for pk on second batch ==> error - .argPkLong("na", 2L).argString("hello").batch().insertBatch(); - fail("Expecting an exception to be thrown"); - } catch (DatabaseException e) { - assertEquals("The primary key argument name must match across batch rows", e.getMessage()); - } - } - - @Test - public void batchInsertPkSeq() { - db.dropSequenceQuietly("seq"); - new Schema().addTable("dbtest") - .addColumn("pk").primaryKey().table() - .addColumn("s").asString(10).schema() - .addSequence("seq").schema().execute(db); - - db.toInsert("insert into dbtest (pk,s) values (?,?)") - .argPkSeq("seq").argString("hi").batch() - .argPkSeq("seq").argString("hello").batch() - .argPkSeq("seq").argString("howdy").batch().insertBatch(); - - assertEquals(3, db.toSelect("select count(*) from dbtest").queryIntegerOrZero()); - - try { - db.toInsert("insert into dbtest (pk,s) values (?,?)") - .argPkSeq("seq").argString("hi").batch() - // argPkLong in different position ==> error - .argString("hello").argPkSeq("seq").batch().insertBatch(); - fail("Expecting an exception to be thrown"); - } catch (DatabaseException e) { - assertEquals("The argPkSeq() calls must be in the same position across batch records", e.getMessage()); - } - - try { - db.toInsert("insert into dbtest (pk,s) values (?,?)") - // multiple pk calls ==> error - .argPkSeq("seq").argPkSeq("seq").batch().insertBatch(); - fail("Expecting an exception to be thrown"); - } catch (DatabaseException e) { - assertEquals("Only call one argPk*() method", e.getMessage()); - } - } - - @Test - public void batchInsertPkSeqNamed() { - db.dropSequenceQuietly("seq"); - new Schema().addTable("dbtest") - .addColumn("pk").primaryKey().table() - .addColumn("s").asString(10).schema() - .addSequence("seq").schema().execute(db); - - db.toInsert("insert into dbtest (pk,s) values (:pk,?)") - .argPkSeq("pk", "seq").argString("hi").batch() - .argPkSeq("pk", "seq").argString("hello").batch() - .argPkSeq("pk", "seq").argString("howdy").batch().insertBatch(); - - assertEquals(3, db.toSelect("select count(*) from dbtest").queryIntegerOrZero()); - - db.toInsert("insert into dbtest (pk,s) values (:pk,?)") - .batch().argPkSeq("pk", "seq").argString("hi").batch() - .argString("hello").argPkSeq("pk", "seq").insertBatch(); - - assertEquals(5, db.toSelect("select count(*) from dbtest").queryIntegerOrZero()); - - try { - db.toInsert("insert into dbtest (pk,s) values (:pk,?)") - // multiple pk calls ==> error - .argPkSeq("pk", "seq").argPkSeq("pk", "seq").batch().insertBatch(); - fail("Expecting an exception to be thrown"); - } catch (DatabaseException e) { - assertEquals("Only call one argPk*() method", e.getMessage()); - } - - try { - db.toInsert("insert into dbtest (pk,s) values (?,?)") - .argPkSeq("pk", "seq").argString("howdy").batch() - // different name for pk on second batch ==> error - .argPkSeq("na", "seq").argString("hello").batch().insertBatch(); - fail("Expecting an exception to be thrown"); - } catch (DatabaseException e) { - assertEquals("The primary key argument name must match across batch rows", e.getMessage()); - } - } - - @Test - public void bigClob() { - new Schema().addTable("dbtest").addColumn("str_lob").asClob().schema().execute(db); - final String longString = "0123456789".repeat(40000); - db.toInsert("insert into dbtest values (?)").argClobString(longString).insert(1); - db.toSelect("select str_lob from dbtest").query((RowsHandler) rs -> { - assertTrue(rs.next()); - assertEquals(longString, rs.getClobStringOrNull(1)); - assertEquals(longString, rs.getClobStringOrNull("str_lob")); - assertEquals(longString, readerToString(rs.getClobReaderOrNull(1))); - return null; - }); - // Intentional slight variation here to test get() - db.get().toSelect("select str_lob from dbtest").query((RowsHandler) rs -> { - assertTrue(rs.next()); - assertEquals(longString, readerToString(rs.getClobReaderOrNull("str_lob"))); - return null; - }); - db.toDelete("delete from dbtest").update(1); - db.toInsert("insert into dbtest values (?)").argClobReader(new StringReader(longString)).insert(1); - db.toSelect("select str_lob from dbtest").query((RowsHandler) rs -> { - assertTrue(rs.next()); - assertEquals(longString, rs.getClobStringOrNull(1)); - assertEquals(longString, rs.getClobStringOrNull("str_lob")); - assertEquals(longString, readerToString(rs.getClobReaderOrNull(1))); - return null; - }); - db.toSelect("select str_lob from dbtest").query((RowsHandler) rs -> { - assertTrue(rs.next()); - assertEquals(longString, readerToString(rs.getClobReaderOrNull("str_lob"))); - return null; - }); - } - - @Test - public void bigBlob() { - new Schema().addTable("dbtest").addColumn("bin_blob").asBlob().schema().execute(db); - final byte[] bigBytes = "0123456789".repeat(40000).getBytes(); - db.toInsert("insert into dbtest values (?)").argBlobBytes(bigBytes).insert(1); - db.toSelect("select bin_blob from dbtest").query((RowsHandler) rs -> { - assertTrue(rs.next()); - assertArrayEquals(bigBytes, rs.getBlobBytesOrNull(1)); - assertArrayEquals(bigBytes, rs.getBlobBytesOrNull("bin_blob")); - assertArrayEquals(bigBytes, inputStreamToString(rs.getBlobInputStreamOrNull(1))); - return null; - }); - db.toSelect("select bin_blob from dbtest").query((RowsHandler) rs -> { - assertTrue(rs.next()); - assertArrayEquals(bigBytes, inputStreamToString(rs.getBlobInputStreamOrNull("bin_blob"))); - return null; - }); - db.toDelete("delete from dbtest").update(1); - db.toInsert("insert into dbtest values (?)").argBlobStream(new ByteArrayInputStream(bigBytes)).insert(1); - db.toSelect("select bin_blob from dbtest").query((RowsHandler) rs -> { - assertTrue(rs.next()); - assertArrayEquals(bigBytes, rs.getBlobBytesOrNull(1)); - assertArrayEquals(bigBytes, rs.getBlobBytesOrNull("bin_blob")); - assertArrayEquals(bigBytes, inputStreamToString(rs.getBlobInputStreamOrNull(1))); - return null; - }); - db.toSelect("select bin_blob from dbtest").query((RowsHandler) rs -> { - assertTrue(rs.next()); - assertArrayEquals(bigBytes, inputStreamToString(rs.getBlobInputStreamOrNull("bin_blob"))); - return null; - }); - } - - @Test - public void argLocalDateTimeZones() { - LocalDate januaryOne2000 = LocalDate.of(2000, Month.JANUARY, 1); - // Verify we always get the same LocalDate regardless of time zone and DB across all drivers - new Schema().addTable("dbtest").addColumn("i").asLocalDate().schema().execute(db); - db.toInsert("insert into dbtest (i) values (?)").argLocalDate(januaryOne2000).insert(1); - // Query without specifying a zone - assertEquals(januaryOne2000, - db.toSelect("select i from dbtest where i=?").argLocalDate(januaryOne2000).queryLocalDateOrNull()); - TimeZone defaultTZ = TimeZone.getDefault(); - try { - String[] availableTZs = TimeZone.getAvailableIDs(); - for (String tz : availableTZs) { - TimeZone.setDefault(TimeZone.getTimeZone(tz)); - LocalDate result = - db.toSelect("select i from dbtest where i=?").argLocalDate(januaryOne2000).queryLocalDateOrNull(); - assertEquals(januaryOne2000, result); - } - } finally { - TimeZone.setDefault(defaultTZ); - } - } - - @Test - public void argLocalDateLeapYear() { - new Schema().addTable("dbtest").addColumn("testdate").asLocalDate().schema().execute(db); - - // Start by adding Febriary 28 and March 1 of 1900. This was not a leap year. - LocalDate feb1900 = LocalDate.of(1900, Month.FEBRUARY, 28); - db.toInsert("insert into dbtest (testdate) values (?)").argLocalDate(feb1900).insert(1); - assertEquals(feb1900, - db.toSelect("select testdate from dbtest where testdate=?").argLocalDate(feb1900).queryLocalDateOrNull()); - - LocalDate mar1900 = LocalDate.of(1900, Month.MARCH, 1); - db.toInsert("insert into dbtest (testdate) values (?)").argLocalDate(mar1900).insert(1); - assertEquals(mar1900, - db.toSelect("select testdate from dbtest where testdate=?").argLocalDate(mar1900).queryLocalDateOrNull()); - - // Now try Feb 28, 29, and March 1 of 2000. This was a leap year - LocalDate feb2000 = LocalDate.of(2000, Month.FEBRUARY, 28); - db.toInsert("insert into dbtest (testdate) values (?)").argLocalDate(feb2000).insert(1); - assertEquals(feb2000, - db.toSelect("select testdate from dbtest where testdate=?").argLocalDate(feb2000).queryLocalDateOrNull()); - - LocalDate febLeap2000 = LocalDate.of(2000, Month.FEBRUARY, 29); - db.toInsert("insert into dbtest (testdate) values (?)").argLocalDate(febLeap2000).insert(1); - assertEquals(febLeap2000, - db.toSelect("select testdate from dbtest where testdate=?").argLocalDate(febLeap2000).queryLocalDateOrNull()); - - LocalDate mar2000 = LocalDate.of(2000, Month.MARCH, 1); - db.toInsert("insert into dbtest (testdate) values (?)").argLocalDate(mar2000).insert(1); - assertEquals(mar2000, - db.toSelect("select testdate from dbtest where testdate=?").argLocalDate(mar2000).queryLocalDateOrNull()); - } - - @Test - public void argIntegerMinMax() { - new Schema().addTable("dbtest").addColumn("i").asInteger().schema().execute(db); - - db.toInsert("insert into dbtest (i) values (?)").argInteger(Integer.MIN_VALUE).insert(1); - assertEquals(Integer.valueOf(Integer.MIN_VALUE), - db.toSelect("select i from dbtest where i=?").argInteger(Integer.MIN_VALUE).queryIntegerOrNull()); - - db.toInsert("insert into dbtest (i) values (?)").argInteger(Integer.MAX_VALUE).insert(1); - assertEquals(Integer.valueOf(Integer.MAX_VALUE), - db.toSelect("select i from dbtest where i=?").argInteger(Integer.MAX_VALUE).queryIntegerOrNull()); - } - - @Test - public void argLongMinMax() { - new Schema().addTable("dbtest").addColumn("i").asLong().schema().execute(db); - - db.toInsert("insert into dbtest (i) values (?)").argLong(Long.MIN_VALUE).insert(1); - assertEquals(Long.valueOf(Long.MIN_VALUE), - db.toSelect("select i from dbtest where i=?").argLong(Long.MIN_VALUE).queryLongOrNull()); - - db.toInsert("insert into dbtest (i) values (?)").argLong(Long.MAX_VALUE).insert(1); - assertEquals(Long.valueOf(Long.MAX_VALUE), - db.toSelect("select i from dbtest where i=?").argLong(Long.MAX_VALUE).queryLongOrNull()); - } - - @Test - public void argFloatMinMax() { - new Schema().addTable("dbtest").addColumn("i").asFloat().schema().execute(db); - - db.toInsert("insert into dbtest (i) values (?)").argFloat(Float.MIN_VALUE).insert(1); - assertEquals(Float.valueOf(Float.MIN_VALUE), - db.toSelect("select i from dbtest where i=?").argFloat(Float.MIN_VALUE).queryFloatOrNull()); - - db.toInsert("insert into dbtest (i) values (?)").argFloat(Float.MAX_VALUE).insert(1); - assertEquals(Float.valueOf(Float.MAX_VALUE), - db.toSelect("select i from dbtest where i=?").argFloat(Float.MAX_VALUE).queryFloatOrNull()); - } - - @Test - public void argFloatNaN() { - new Schema().addTable("dbtest").addColumn("i").asFloat().schema().execute(db); - - db.toInsert("insert into dbtest (i) values (?)").argFloat(Float.NaN).insert(1); - assertEquals(Float.valueOf(Float.NaN), - db.toSelect("select i from dbtest where i=?").argFloat(Float.NaN).queryFloatOrNull()); - } - - @Test - public void argFloatInfinity() { - new Schema().addTable("dbtest").addColumn("i").asFloat().schema().execute(db); - - db.toInsert("insert into dbtest (i) values (?)").argFloat(Float.NEGATIVE_INFINITY).insert(1); - assertEquals(Float.valueOf(Float.NEGATIVE_INFINITY), - db.toSelect("select i from dbtest where i=?").argFloat(Float.NEGATIVE_INFINITY).queryFloatOrNull()); - - db.toInsert("insert into dbtest (i) values (?)").argFloat(Float.POSITIVE_INFINITY).insert(1); - assertEquals(Float.valueOf(Float.POSITIVE_INFINITY), - db.toSelect("select i from dbtest where i=?").argFloat(Float.POSITIVE_INFINITY).queryFloatOrNull()); - } - - @Test - public void argFloatZero() { - new Schema().addTable("dbtest").addColumn("i").asFloat().schema().execute(db); - - db.toInsert("insert into dbtest (i) values (?)").argFloat(0f).insert(1); - assertEquals(Float.valueOf(0f), - db.toSelect("select i from dbtest where i=?").argFloat(0f).queryFloatOrNull()); - } - - @Test - public void argFloatNegativeZero() { - new Schema().addTable("dbtest").addColumn("i").asFloat().schema().execute(db); - - db.toInsert("insert into dbtest (i) values (?)").argFloat(-0f).insert(1); - assertEquals(Float.valueOf(-0f), - db.toSelect("select i from dbtest where i=?").argFloat(-0f).queryFloatOrNull()); - } - - @Test - public void argDoubleMinMax() { - new Schema().addTable("dbtest").addColumn("i").asDouble().schema().execute(db); - - db.toInsert("insert into dbtest (i) values (?)").argDouble(Double.MIN_VALUE).insert(1); - assertEquals(Double.valueOf(Double.MIN_VALUE), - db.toSelect("select i from dbtest where i=?").argDouble(Double.MIN_VALUE).queryDoubleOrNull()); - - db.toInsert("insert into dbtest (i) values (?)").argDouble(Double.MAX_VALUE).insert(1); - assertEquals(Double.valueOf(Double.MAX_VALUE), - db.toSelect("select i from dbtest where i=?").argDouble(Double.MAX_VALUE).queryDoubleOrNull()); - } - - @Test - public void argDoubleNaN() { - new Schema().addTable("dbtest").addColumn("i").asDouble().schema().execute(db); - - db.toInsert("insert into dbtest (i) values (?)").argDouble(Double.NaN).insert(1); - assertEquals(Double.valueOf(Double.NaN), - db.toSelect("select i from dbtest where i=?").argDouble(Double.NaN).queryDoubleOrNull()); - } - - @Test - public void argDoubleInfinity() { - new Schema().addTable("dbtest").addColumn("i").asDouble().schema().execute(db); - - db.toInsert("insert into dbtest (i) values (?)").argDouble(Double.NEGATIVE_INFINITY).insert(1); - assertEquals(Double.valueOf(Double.NEGATIVE_INFINITY), - db.toSelect("select i from dbtest where i=?").argDouble(Double.NEGATIVE_INFINITY).queryDoubleOrNull()); - - db.toInsert("insert into dbtest (i) values (?)").argDouble(Double.POSITIVE_INFINITY).insert(1); - assertEquals(Double.valueOf(Double.POSITIVE_INFINITY), - db.toSelect("select i from dbtest where i=?").argDouble(Double.POSITIVE_INFINITY).queryDoubleOrNull()); - } - - @Test - public void argDoubleZero() { - new Schema().addTable("dbtest").addColumn("i").asDouble().schema().execute(db); - - db.toInsert("insert into dbtest (i) values (?)").argDouble(0d).insert(1); - assertEquals(Double.valueOf(0d), - db.toSelect("select i from dbtest where i=?").argDouble(0d).queryDoubleOrNull()); - } - - @Test - public void argDoubleNegativeZero() { - new Schema().addTable("dbtest").addColumn("i").asDouble().schema().execute(db); - - db.toInsert("insert into dbtest (i) values (?)").argDouble(-0d).insert(1); - assertEquals(Double.valueOf(-0d), - db.toSelect("select i from dbtest where i=?").argDouble(-0d).queryDoubleOrNull()); - } - - @Test - public void argBigDecimal38Precision0() { - new Schema().addTable("dbtest").addColumn("i").asBigDecimal(38, 0).schema().execute(db); - - BigDecimal value = new BigDecimal("99999999999999999999999999999999999999"); // 38 digits - db.toInsert("insert into dbtest (i) values (?)").argBigDecimal(value).insert(1); - assertEquals(value, - db.toSelect("select i from dbtest where i=?").argBigDecimal(value).queryBigDecimalOrNull()); - } - - @Test - public void argBigDecimal38Precision1() { - new Schema().addTable("dbtest").addColumn("i").asBigDecimal(38, 1).schema().execute(db); - - BigDecimal value = new BigDecimal("9999999999999999999999999999999999999.9"); // 38 digits - db.toInsert("insert into dbtest (i) values (?)").argBigDecimal(value).insert(1); - assertEquals(value, - db.toSelect("select i from dbtest where i=?").argBigDecimal(value).queryBigDecimalOrNull()); - } - - @Test - public void argBigDecimal38Precision37() { - new Schema().addTable("dbtest").addColumn("i").asBigDecimal(38, 37).schema().execute(db); - - BigDecimal value = new BigDecimal("9.9999999999999999999999999999999999999"); // 38 digits - db.toInsert("insert into dbtest (i) values (?)").argBigDecimal(value).insert(1); - assertEquals(value, - db.toSelect("select i from dbtest where i=?").argBigDecimal(value).queryBigDecimalOrNull()); - } - - @Test - public void argBigDecimal38Precision38() { - new Schema().addTable("dbtest").addColumn("i").asBigDecimal(38, 38).schema().execute(db); - BigDecimal value = new BigDecimal("0.99999999999999999999999999999999999999"); // 38 digits - db.toInsert("insert into dbtest (i) values (?)").argBigDecimal(value).insert(1); - logger.info(db.toSelect("select i from dbtest").queryBigDecimalOrNull().toString()); - assertEquals(value, - db.toSelect("select i from dbtest where i=?").argBigDecimal(value).queryBigDecimalOrNull()); - } - - @Test - public void dropTableQuietly() { - db.dropTableQuietly("dbtest"); - new Schema().addTable("dbtest").addColumn("pk").primaryKey().schema().execute(db); - db.dropTableQuietly("dbtest"); - // Verify the quietly part really kicks in, since the table might have existed above - db.dropTableQuietly("dbtest"); - new Schema().addTable("dbtest").addColumn("pk").primaryKey().schema().execute(db); - } - - @Test - public void dropSequenceQuietly() { - db.dropSequenceQuietly("dbtest_seq"); - // Verify the quietly part really kicks in, since the sequence might have existed above - db.dropSequenceQuietly("dbtest_seq"); - } - - @Test - public void insertReturningPkSeq() { - db.dropSequenceQuietly("dbtest_seq"); - - db.ddl("create table dbtest (pk numeric)").execute(); - db.ddl("create sequence dbtest_seq start with 1").execute(); - - assertEquals(Long.valueOf(1L), db.toInsert("insert into dbtest (pk) values (:seq)") - .argPkSeq(":seq", "dbtest_seq").insertReturningPkSeq("pk")); - assertEquals(Long.valueOf(2L), db.toInsert("insert into dbtest (pk) values (:seq)") - .argPkSeq(":seq", "dbtest_seq").insertReturningPkSeq("pk")); - } - - @Test - public void insertReturningAppDate() { - db.dropSequenceQuietly("dbtest_seq"); - new Schema() - .addTable("dbtest") - .addColumn("pk").primaryKey().table() - .addColumn("d") - .asLocalDateTime() - .table() - .schema() - .addSequence("dbtest_seq").schema() - .execute(db); - db.toInsert("insert into dbtest (pk, d) values (:seq, :d)") - .argPkSeq(":seq", "dbtest_seq") - .argLocalDateTime(":d", now) - .insertReturning("dbtest", "pk", rs -> { - assertTrue(rs.next()); - assertEquals(Long.valueOf(1L), rs.getLongOrNull(1)); - assertThat(rs.getLocalDateTimeOrNull(2, ZoneId.systemDefault()), equalTo(now)); - assertFalse(rs.next()); - return null; - }, "d"); - assertEquals(Long.valueOf(1L), db.toSelect("select count(*) from dbtest where d=?").argLocalDateTime(now).queryLongOrNull()); - } - - @Test - public void quickQueries() { - new Schema() - .addTable("dbtest") - .addColumn("pk").primaryKey().table() - .addColumn("d").asLocalDateTime().table() - .addColumn("d2").asLocalDateTime().table() - .addColumn("d3").asLocalDate().table() - .addColumn("d4").asLocalDate().table() - .addColumn("s").asString(5).table() - .addColumn("s2").asString(5).table() - .addColumn("i").asInteger().table().schema() - .execute(db); - db.toInsert("insert into dbtest (pk, d, d3, s) values (?,?,?,?)") - .argLong(1L) - .argLocalDateTime(now) - .argLocalDate(localDateNow) - .argString("foo") - .insert(1); - assertEquals(Long.valueOf(1L), db.toSelect("select pk from dbtest").queryLongOrNull()); - assertNull(db.toSelect("select pk from dbtest where 1=0").queryLongOrNull()); - assertNull(db.toSelect("select i from dbtest").queryLongOrNull()); - assertEquals(1L, db.toSelect("select pk from dbtest").queryLongOrZero()); - assertEquals(0L, db.toSelect("select pk from dbtest where 1=0").queryLongOrZero()); - assertEquals(0L, db.toSelect("select i from dbtest").queryLongOrZero()); - assertEquals(1L, (long) db.toSelect("select pk from dbtest").queryLongs().get(0)); - assertTrue(db.toSelect("select pk from dbtest where 1=0").queryLongs().isEmpty()); - assertTrue(db.toSelect("select i from dbtest").queryLongs().isEmpty()); - assertEquals(Integer.valueOf(1), db.toSelect("select pk from dbtest").queryIntegerOrNull()); - assertNull(db.toSelect("select pk from dbtest where 1=0").queryIntegerOrNull()); - assertNull(db.toSelect("select i from dbtest").queryIntegerOrNull()); - assertEquals(1, db.toSelect("select pk from dbtest").queryIntegerOrZero()); - assertEquals(0, db.toSelect("select pk from dbtest where 1=0").queryIntegerOrZero()); - assertEquals(0, db.toSelect("select i from dbtest").queryIntegerOrZero()); - assertEquals(1L, (int) db.toSelect("select pk from dbtest").queryIntegers().get(0)); - assertTrue(db.toSelect("select pk from dbtest where 1=0").queryIntegers().isEmpty()); - assertTrue(db.toSelect("select i from dbtest").queryIntegers().isEmpty()); - assertEquals("foo", db.toSelect("select s from dbtest").queryStringOrNull()); - assertNull(db.toSelect("select s from dbtest where 1=0").queryStringOrNull()); - assertNull(db.toSelect("select s2 from dbtest").queryStringOrNull()); - assertEquals("foo", db.toSelect("select s from dbtest").queryStringOrEmpty()); - assertEquals("", db.toSelect("select s from dbtest where 1=0").queryStringOrEmpty()); - assertEquals("", db.toSelect("select s2 from dbtest").queryStringOrEmpty()); - assertEquals("foo", db.toSelect("select s from dbtest").queryStrings().get(0)); - assertTrue(db.toSelect("select s from dbtest where 1=0").queryStrings().isEmpty()); - assertTrue(db.toSelect("select s2 from dbtest").queryStrings().isEmpty()); - assertEquals(now, db.toSelect("select d from dbtest").queryLocalDateTimeOrNull()); - assertNull(db.toSelect("select d from dbtest where 1=0").queryLocalDateTimeOrNull()); - assertNull(db.toSelect("select d2 from dbtest").queryLocalDateTimeOrNull()); - assertEquals(db.toSelect("select d from dbtest").queryLocalDateTimes().get(0), now); - assertTrue(db.toSelect("select d from dbtest where 1=0").queryLocalDateTimes().isEmpty()); - assertTrue(db.toSelect("select d2 from dbtest").queryLocalDateTimes().isEmpty()); - - assertEquals(localDateNow, db.toSelect("select d3 from dbtest").queryLocalDateOrNull()); - assertNull(db.toSelect("select d3 from dbtest where 1=0").queryLocalDateOrNull()); - assertEquals(db.toSelect("select d3 from dbtest").queryLocalDates().get(0), localDateNow); - assertEquals(Long.valueOf(1L), - db.toSelect("select count(*) from dbtest where d3=?").argLocalDate(localDateNow).queryLongOrNull()); - - assertNull(db.toSelect("select d4 from dbtest").queryLocalDateOrNull()); - assertNull(db.toSelect("select d4 from dbtest where 1=0").queryLocalDateOrNull()); - assertTrue(db.toSelect("select d4 from dbtest").queryLocalDates().isEmpty()); - } - - @Test - public void rowHandlerQueries() { - new Schema() - .addTable("dbtest") - .addColumn("pk").primaryKey().schema() - .execute(db); - - db.toInsert("insert into dbtest (pk) values (?)").argLong(1L).insert(1); - db.toInsert("insert into dbtest (pk) values (?)").argLong(2L).insert(1); - - RowHandler rowHandler = Row::getLongOrNull; - - List many = db.toSelect("select pk from dbtest").queryMany(rowHandler); - assertEquals(2, many.size()); - - assertEquals(Long.valueOf(1), db.toSelect("select pk from dbtest where pk=1").queryOneOrNull(rowHandler)); - assertNull(db.toSelect("select pk from dbtest where pk=9").queryOneOrNull(rowHandler)); - try { - db.toSelect("select pk from dbtest").queryOneOrNull(rowHandler); - fail("Should have thrown an exception"); - } catch (ConstraintViolationException e) { - assertEquals("Expected exactly one row to be returned but found multiple", e.getCause().getMessage()); - } - try { - db.toSelect("select pk from dbtest where pk=9").queryOneOrThrow(rowHandler); - fail("Should have thrown an exception"); - } catch (ConstraintViolationException e) { - assertEquals("Expected exactly one row to be returned but found none", e.getMessage()); - } - - assertEquals(Long.valueOf(1), db.toSelect("select pk from dbtest where pk=1").queryFirstOrNull(rowHandler)); - assertEquals(Long.valueOf(1), db.toSelect("select pk from dbtest order by 1").queryFirstOrNull(rowHandler)); - assertNull(db.toSelect("select pk from dbtest where pk=9").queryFirstOrNull(rowHandler)); - try { - db.toSelect("select pk from dbtest where pk=9").queryFirstOrThrow(rowHandler); - fail("Should have thrown an exception"); - } catch (ConstraintViolationException e) { - assertEquals("Expected one or more rows to be returned but found none", e.getMessage()); - } - } - - @Test - public void nextSequenceValue() { - db.dropSequenceQuietly("dbtest_seq"); - new Schema() - .addSequence("dbtest_seq").schema() - .execute(db); - - assertEquals(Long.valueOf(1L), db.nextSequenceValue("dbtest_seq")); - } - - @Test - public void insertReturningDbDate() { - db.dropSequenceQuietly("dbtest_seq"); - new Schema() - .addTable("dbtest") - .addColumn("pk").primaryKey().table() - .addColumn("d").asLocalDateTime().table().schema() - .addSequence("dbtest_seq").schema() - .execute(db); - LocalDateTime dbNow = db.toInsert("insert into dbtest (pk, d) values (:seq, :d)") - .argPkSeq(":seq", "dbtest_seq") - .argLocalDateTimeNowPerDb(":d") - .insertReturning("dbtest", "pk", rs -> { - assertTrue(rs.next()); - assertEquals(Long.valueOf(1L), rs.getLongOrNull(1)); - LocalDateTime dbDate = rs.getLocalDateTimeOrNull(2); - assertFalse(rs.next()); - return dbDate; - }, "d"); - assertEquals(Long.valueOf(1L), - db.toSelect("select count(*) from dbtest where d = ?").argLocalDateTime(dbNow).queryLongOrNull()); - } - - @Test - public void daylightSavings() { - LocalDate lastStdDateSpring = LocalDate.of(2019, Month.MARCH, 9); - LocalDate firstDSTDateSpring = LocalDate.of(2019, Month.MARCH, 10); - // Verify that the original LocalDate matches the driver SQL LocalDate generated. - StatementAdapter adaptor = new StatementAdapter(new OptionsDefault(db.flavor())); - assertEquals(lastStdDateSpring.toString(), adaptor.nullLocalDate(lastStdDateSpring).toString()); - assertEquals(firstDSTDateSpring.toString(), adaptor.nullLocalDate(firstDSTDateSpring).toString()); - } - - @Test - public void insertLocalDate() { - // Date without time - new Schema() - .addTable("dbtest") - .addColumn("d").asLocalDate().table().schema() - .execute(db); - LocalDate dateOfBirth = LocalDate.of(1951, Month.AUGUST, 9); - db.toInsert("insert into dbtest (d) values (?)") - .argLocalDate(dateOfBirth) - .insert(1); - LocalDate testDate = db.toSelect("select d from dbtest").queryLocalDateOrNull(); - assertEquals(dateOfBirth, testDate); - } - - @Test - public void localDateRoundTrip() { - new Schema() - .addTable("dbtest") - .addColumn("d1").asLocalDate().table() - .addColumn("d2").asLocalDate().table().schema() - .execute(db); - // Store current time as per the database - db.toInsert("insert into dbtest (d1) values (?)") - .argLocalDate(localDateNow) - .insert(1); - // Now pull it out, put it back in, and verify it matches in the database - LocalDate queryRsDate = db.toSelect("select d1 from dbtest").queryLocalDateOrNull(); - db.toUpdate("update dbtest set d2=?") - .argLocalDate(queryRsDate) - .update(1); - assertEquals(Long.valueOf(1L), db.toSelect("select count(*) from dbtest where d1=d2").queryLongOrNull()); - } - - /** - * Make sure database times are inserted with at least millisecond precision. - * This test is non-deterministic since it is checking the timestamp provided - * by the database, so we use a retry to give it up to ten attempts. - */ - @Test - public void dateMillis() { - for (int attempts = 1; attempts <= 10; attempts++) { - new Schema() - .addTable("dbtest") - .addColumn("d").asLocalDateTime().table().schema() - .execute(db); - db.toInsert("insert into dbtest (d) values (?)") - .argLocalDateTimeNowPerDb() - .insert(1); - LocalDateTime dbNow = db.toSelect("select d from dbtest").queryLocalDateTimeOrNull(); - if (dbNow != null && dbNow.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli() % 10 != 0) { - return; - } - logger.info("Zero in least significant digit (attempt " + attempts + ")"); - db.dropTableQuietly(TEST_TABLE_NAME); - } - fail("Timestamp had zero in the least significant digit"); - } - - @Test - public void dateRoundTrip() { - new Schema() - .addTable("dbtest") - .addColumn("d1").asLocalDateTime().table() - .addColumn("d2").asLocalDateTime().table() - .schema() - .execute(db); - db.toInsert("insert into dbtest (d1) values (?)") - .argLocalDateTimeNowPerDb() - .insert(1); - LocalDateTime dbNow = db.toSelect("select d1 from dbtest").queryLocalDateTimeOrNull(); - db.toUpdate("update dbtest set d2 = ?") - .argLocalDateTime(dbNow) - .update(1); - assertEquals(Long.valueOf(1L), db.toSelect("select count(*) from dbtest where d1=d2").queryLongOrNull()); - } - - @Test - public void dateRoundTripTimezones() { - new Schema() - .addTable("dbtest") - .addColumn("d") - .asLocalDateTime() - .table() - .schema() - .execute(db); - Instant instant = Instant.ofEpochMilli(166656789L); - ZoneId dateMinusZone = ZoneId.ofOffset("GMT", ZoneOffset.ofHours(-4)); - LocalDateTime dateMinus = LocalDateTime.ofInstant(instant, dateMinusZone); - ZoneId datePlusZone = ZoneId.ofOffset("GMT", ZoneOffset.ofHours(+4)); - LocalDateTime datePlus = LocalDateTime.ofInstant(instant,datePlusZone); - logger.log(Level.INFO, "dateMinus = " + dateMinus); - db.toInsert("insert into dbtest (d) values (?)") - .argLocalDateTime(dateMinus) - .insert(1); - LocalDateTime localDateTimeMinus = db.toSelect("select d from dbtest") - .queryLocalDateTimeOrNull(); - assertEquals(dateMinus, localDateTimeMinus); - assertEquals("1970-01-02 18:17:36.789", - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS").format(localDateTimeMinus)); - db.toDelete("delete from dbtest where d = ?").argLocalDateTime(dateMinus).update(1); - db.toInsert("insert into dbtest (d) values (?)") - .argLocalDateTime(datePlus) - .insert(1); - LocalDateTime localDateTimePlus = db.toSelect("select d from dbtest") - .queryLocalDateTimeOrNull(datePlusZone); - assertEquals(datePlus, localDateTimePlus); - assertEquals("1970-01-03 02:17:36.789", - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS").format(localDateTimePlus)); - db.toDelete("delete from dbtest where d = ?").argLocalDateTime(datePlus).update(1); - } - - /** - * Verify the appropriate database flavor can correctly convert a {@code LocalDateTime} - * into a SQL function representing a conversion from string to timestamp. This - * function is used to write debug SQL to the log in a way that could be manually - * executed if desired. - */ - @Test - public void stringLocalDateTimeFunctions() { - Instant instant = Instant.ofEpochMilli(166656789L); - ZoneId dateMinusZone = ZoneId.ofOffset("GMT", ZoneOffset.ofHours(-4)); - LocalDateTime dateMinus = LocalDateTime.ofInstant(instant, dateMinusZone); - ZoneId datePlusZone = ZoneId.ofOffset("GMT", ZoneOffset.ofHours(+4)); - LocalDateTime datePlus = LocalDateTime.ofInstant(instant, datePlusZone); - logger.info("LocalDateTime: dateMinus=" + dateMinus + " datePlus=" + datePlus); - new Schema().addTable("dbtest").addColumn("d") - .asLocalDateTime() - .schema() - .execute(db); - db.toInsert("insert into dbtest (d) values (?)") - .argLocalDateTime(dateMinus) - .insert(1); - LocalDateTime localDateTime = db.toSelect("select d from dbtest") - .queryLocalDateTimeOrNull(); - assertEquals("1970-01-02 18:17:36.789", - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS").format(localDateTime.atZone(dateMinusZone))); - db.toDelete("delete from dbtest where d = ?") - .argLocalDateTime(dateMinus) - .update(1); - db.toInsert("insert into dbtest (d) values (?)") - .argLocalDateTime(datePlus) - .insert(1); - localDateTime = db.toSelect("select d from dbtest") - .queryLocalDateTimeOrNull(datePlusZone); - assertEquals("1970-01-03 02:17:36.789", - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS").format(localDateTime.atZone(datePlusZone))); - db.toDelete("delete from dbtest where d = ?") - .argLocalDateTime(datePlus) - .update(1); - } - - @Test - public void mixPositionalAndNamedParameters() { - new Schema() - .addTable("dbtest") - .addColumn("pk").primaryKey().table() - .addColumn("d").asLocalDateTime().table() - .addColumn("a").asInteger().table().schema() - .execute(db); - - db.toSelect("select pk as \"time:: now??\" from dbtest where a=? and d=:now") - .argInteger(1).argLocalDateTimeNowPerDb("now").query(rs -> { - assertFalse(rs.next()); - return null; - }); - } - - public String readerToString(Reader reader) throws IOException { - char[] buffer = new char[1024]; - StringBuilder out = new StringBuilder(); - int byteCount; - while ((byteCount = reader.read(buffer, 0, buffer.length)) >= 0) { - out.append(buffer, 0, byteCount); - } - return out.toString(); - } - - public byte[] inputStreamToString(InputStream inputStream) throws IOException { - byte[] buffer = new byte[1024]; - ByteArrayOutputStream out = new ByteArrayOutputStream(); - int byteCount; - while ((byteCount = inputStream.read(buffer, 0, buffer.length)) >= 0) { - out.write(buffer, 0, byteCount); - } - return out.toByteArray(); - } -} diff --git a/jdbc-query/src/test/java/org/xbib/jdbc/query/test/DerbyTest.java b/jdbc-query/src/test/java/org/xbib/jdbc/query/test/DerbyTest.java index 1ea67e6..5759110 100644 --- a/jdbc-query/src/test/java/org/xbib/jdbc/query/test/DerbyTest.java +++ b/jdbc-query/src/test/java/org/xbib/jdbc/query/test/DerbyTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.Test; import org.xbib.jdbc.query.DatabaseProvider; import org.xbib.jdbc.query.OptionsOverride; import org.xbib.jdbc.query.Schema; +import org.xbib.jdbc.test.CommonTest; import java.math.BigDecimal; import java.util.logging.Logger; @@ -15,7 +16,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; /** * Exercise Database functionality with a real database (Derby). */ -@Disabled +@Disabled("Derby 10.16.1.1 does not conform to JDBC 4.2 with getObject(... LocalDatetime.class) https://chariotsolutions.com/blog/post/using-java-time-resultset-preparedstatement/") public class DerbyTest extends CommonTest { private static final Logger logger = Logger.getLogger(DerbyTest.class.getName()); @@ -26,12 +27,10 @@ public class DerbyTest extends CommonTest { @Override protected DatabaseProvider createDatabaseProvider(OptionsOverride options) { - return DatabaseProvider.builder("jdbc:derby:build/testdb;create=true") + return DatabaseProvider.builder(getClass().getClassLoader(), "jdbc:derby:build/testdb;create=true") .withSqlParameterLogging().withSqlInExceptionMessages().withOptions(options).build(); } - // TODO fix this test - @Disabled("Not sure why this fails on the build servers right now...") @Test public void clockSync() { super.clockSync(); @@ -79,7 +78,6 @@ public class DerbyTest extends CommonTest { super.intervals(); } - @Disabled("Derby limits out at precision 31") @Test public void argBigDecimal38Precision0() { diff --git a/jdbc-query/src/test/java/org/xbib/jdbc/query/test/HsqldbTest.java b/jdbc-query/src/test/java/org/xbib/jdbc/query/test/HsqldbTest.java index 1381be4..73b565e 100644 --- a/jdbc-query/src/test/java/org/xbib/jdbc/query/test/HsqldbTest.java +++ b/jdbc-query/src/test/java/org/xbib/jdbc/query/test/HsqldbTest.java @@ -10,6 +10,7 @@ import org.xbib.jdbc.query.OptionsOverride; import org.xbib.jdbc.query.Schema; import org.xbib.jdbc.query.Sql; import org.xbib.jdbc.query.SqlArgs; +import org.xbib.jdbc.test.CommonTest; import java.math.BigDecimal; import java.util.ArrayList; @@ -38,25 +39,6 @@ public class HsqldbTest extends CommonTest { .build(); } - @Test - public void noDatabaseAccess() throws Exception { - DatabaseProvider provider = createDatabaseProvider(new OptionsOverride()); - provider.transact(dbp -> { - // Do nothing, just making sure no exception is thrown - }); - provider.transact((dbp, tx) -> { - // Do nothing, just making sure no exception is thrown - }); - provider.transact((dbp, tx) -> { - tx.setRollbackOnError(true); - // Do nothing, just making sure no exception is thrown - }); - provider.transact((dbp, tx) -> { - tx.setRollbackOnly(true); - // Do nothing, just making sure no exception is thrown - }); - } - @Disabled("LocalDate implementations should be TimeZone agnostic, but HSQLDB implementation has a bug.") @Test public void argLocalDateTimeZones() { @@ -85,7 +67,6 @@ public class HsqldbTest extends CommonTest { .addColumn("boolean_flag").asBoolean().table() .addColumn("date_millis").asLocalDateTime().table() .addColumn("local_date").asLocalDate().schema().execute(db); - db.toInsert("insert into dbtest (nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal, str_varchar," + " str_fixed, str_lob, bin_blob, boolean_flag, date_millis, local_date) values (?,?,?,?,?,?,?,?,?,?,?,?)") .argInteger(Integer.MAX_VALUE) diff --git a/jdbc-query/src/test/java/org/xbib/jdbc/query/test/example/DerbyExample.java b/jdbc-query/src/test/java/org/xbib/jdbc/query/test/example/DerbyExample.java index fb09d69..d3bcdd5 100644 --- a/jdbc-query/src/test/java/org/xbib/jdbc/query/test/example/DerbyExample.java +++ b/jdbc-query/src/test/java/org/xbib/jdbc/query/test/example/DerbyExample.java @@ -2,6 +2,7 @@ package org.xbib.jdbc.query.test.example; import org.xbib.jdbc.query.Database; import org.xbib.jdbc.query.DatabaseProvider; +import org.xbib.jdbc.query.DatabaseProviderBuilder; /** * Demo of using some com.github.susom.database classes with Derby. @@ -12,7 +13,7 @@ public abstract class DerbyExample { // For subclasses to override } - void example(DatabaseProvider.DatabaseProviderBuilder dbb, final String[] args) { + void example(DatabaseProviderBuilder dbb, final String[] args) { dbb.transact(db -> { example(db.get(), args); }); @@ -26,7 +27,7 @@ public abstract class DerbyExample { try { System.setProperty("derby.stream.error.file", "java.lang.System.err"); String url = "jdbc:derby:target/testdb;create=true"; - example(DatabaseProvider.builder(url), args); + example(DatabaseProvider.builder(getClass().getClassLoader(), url), args); } catch (Exception e) { e.printStackTrace(); System.exit(1); diff --git a/jdbc-query/src/test/java/org/xbib/jdbc/query/test/example/HelloAny.java b/jdbc-query/src/test/java/org/xbib/jdbc/query/test/example/HelloAny.java deleted file mode 100644 index b9144df..0000000 --- a/jdbc-query/src/test/java/org/xbib/jdbc/query/test/example/HelloAny.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.xbib.jdbc.query.test.example; - -import org.xbib.jdbc.query.Database; -import org.xbib.jdbc.query.DatabaseProvider; - -/** - * Example with database info provided from command line. To use this, set properties like this: - *
- *
- *   -Ddatabase.url=...      Database connect string (required)
- *   -Ddatabase.user=...     Authenticate as this user (optional if provided in url)
- *   -Ddatabase.password=... User password (optional if user and password provided in
- *                           url; prompted on standard input if user is provided and
- *                           password is not)
- *   -Ddatabase.flavor=...   What kind of database it is (optional, will guess based
- *                           on the url if this is not provided)
- *   -Ddatabase.driver=...   The Java class of the JDBC driver to load (optional, will
- *                           guess based on the flavor if this is not provided)
- * 
- */ -public class HelloAny { - public static void main(final String[] args) { - try { - new HelloAny().run(); - } catch (Exception e) { - e.printStackTrace(); - System.exit(1); - } - } - - public void run() { - DatabaseProvider.fromSystemProperties().transact(dbp -> { - Database db = dbp.get(); - db.dropTableQuietly("t"); - db.ddl("create table t (a numeric)").execute(); - db.toInsert("insert into t (a) values (?)") - .argInteger(32) - .insert(1); - db.toUpdate("update t set a=:val") - .argInteger("val", 23) - .update(1); - - Long rows = db.toSelect("select count(1) from t ").queryLongOrNull(); - System.out.println("Rows: " + rows); - }); - } -} diff --git a/jdbc-query/src/test/java/org/xbib/jdbc/query/test/example/HelloDerby.java b/jdbc-query/src/test/java/org/xbib/jdbc/query/test/example/HelloDerby.java index d7bbf90..e547668 100644 --- a/jdbc-query/src/test/java/org/xbib/jdbc/query/test/example/HelloDerby.java +++ b/jdbc-query/src/test/java/org/xbib/jdbc/query/test/example/HelloDerby.java @@ -5,9 +5,6 @@ import org.xbib.jdbc.query.DatabaseProvider; import java.io.File; -/** - * Demo of using some com.github.susom.database classes with Derby. - */ public class HelloDerby { public static void main(final String[] args) { try { @@ -19,14 +16,12 @@ public class HelloDerby { } public void run() { - // Put all Derby related files inside ./build to keep our working copy clean - File directory = new File("target").getAbsoluteFile(); + File directory = new File("build").getAbsoluteFile(); if (directory.exists() || directory.mkdirs()) { System.setProperty("derby.stream.error.file", new File(directory, "derby.log").getAbsolutePath()); } - String url = "jdbc:derby:target/testdb;create=true"; - DatabaseProvider.builder(url).transact(dbp -> { + DatabaseProvider.builder(getClass().getClassLoader(), url).transact(dbp -> { Database db = dbp.get(); db.ddl("drop table t").executeQuietly(); db.ddl("create table t (a numeric)").execute(); diff --git a/jdbc-sqlserver/build.gradle b/jdbc-sqlserver/build.gradle new file mode 100644 index 0000000..4574a9f --- /dev/null +++ b/jdbc-sqlserver/build.gradle @@ -0,0 +1,10 @@ +dependencies { + api project(':jdbc-query') + testImplementation project(':jdbc-test') + testImplementation libs.testcontainers + testImplementation libs.testcontainers.junit.jupiter +} + +test { + systemProperty 'user.timezone', 'GMT' +} diff --git a/jdbc-sqlserver/src/main/java/module-info.java b/jdbc-sqlserver/src/main/java/module-info.java new file mode 100644 index 0000000..e3555d2 --- /dev/null +++ b/jdbc-sqlserver/src/main/java/module-info.java @@ -0,0 +1,10 @@ +import org.xbib.jdbc.query.Flavor; +import org.xbib.jdbc.sqlserver.SqlServer; + +module org.xbib.jdbc.sqlserver { + requires org.xbib.jdbc.query; + requires java.sql; + uses Flavor; + exports org.xbib.jdbc.sqlserver; + provides Flavor with SqlServer; +} diff --git a/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/SqlServer.java b/jdbc-sqlserver/src/main/java/org/xbib/jdbc/sqlserver/SqlServer.java similarity index 88% rename from jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/SqlServer.java rename to jdbc-sqlserver/src/main/java/org/xbib/jdbc/sqlserver/SqlServer.java index ff98110..3d555a8 100644 --- a/jdbc-query/src/main/java/org/xbib/jdbc/query/flavor/SqlServer.java +++ b/jdbc-sqlserver/src/main/java/org/xbib/jdbc/sqlserver/SqlServer.java @@ -1,4 +1,4 @@ -package org.xbib.jdbc.query.flavor; +package org.xbib.jdbc.sqlserver; import org.xbib.jdbc.query.Flavor; @@ -7,6 +7,9 @@ import java.sql.SQLException; public class SqlServer implements Flavor { + public SqlServer() { + } + @Override public String getName() { return "sqlServer"; @@ -23,8 +26,16 @@ public class SqlServer implements Flavor { } @Override - public boolean isNormalizedUpperCase() { - return false; + public String normalizeTableName(String tableName) { + if (tableName == null) { + return tableName; + } + if (tableName.length() > 2) { + if (tableName.startsWith("\"") && tableName.endsWith("\"")) { + return tableName.substring(1, tableName.length() - 1); + } + } + return tableName; } @Override @@ -72,6 +83,11 @@ public class SqlServer implements Flavor { return "datetime2(3)"; } + @Override + public String columnTypeLocalDateTime() { + return "datetime2"; + } + @Override public String typeLocalDate() { return "date"; diff --git a/jdbc-sqlserver/src/main/resources/META-INF/services/org.xbib.jdbc.query.Flavor b/jdbc-sqlserver/src/main/resources/META-INF/services/org.xbib.jdbc.query.Flavor new file mode 100644 index 0000000..a1e1dec --- /dev/null +++ b/jdbc-sqlserver/src/main/resources/META-INF/services/org.xbib.jdbc.query.Flavor @@ -0,0 +1 @@ +org.xbib.jdbc.sqlserver.SqlServer \ No newline at end of file diff --git a/jdbc-query/src/test/java/org/xbib/jdbc/query/test/SqlServerTest.java b/jdbc-sqlserver/src/test/java/org/xbib/jdbc/sqlserver/test/SqlServerTest.java similarity index 97% rename from jdbc-query/src/test/java/org/xbib/jdbc/query/test/SqlServerTest.java rename to jdbc-sqlserver/src/test/java/org/xbib/jdbc/sqlserver/test/SqlServerTest.java index a21c163..a8e28d9 100644 --- a/jdbc-query/src/test/java/org/xbib/jdbc/query/test/SqlServerTest.java +++ b/jdbc-sqlserver/src/test/java/org/xbib/jdbc/sqlserver/test/SqlServerTest.java @@ -1,4 +1,4 @@ -package org.xbib.jdbc.query.test; +package org.xbib.jdbc.sqlserver.test; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -7,6 +7,7 @@ import org.xbib.jdbc.query.ConfigSupplier; import org.xbib.jdbc.query.DatabaseProvider; import org.xbib.jdbc.query.OptionsOverride; import org.xbib.jdbc.query.Schema; +import org.xbib.jdbc.test.CommonTest; import static org.junit.jupiter.api.Assertions.assertArrayEquals; @@ -77,9 +78,7 @@ public class SqlServerTest extends CommonTest { @Test public void metadataColumnNames() { db.dropTableQuietly("dbtest"); - new Schema().addTable("dbtest").addColumn("pk").primaryKey().schema().execute(db); - db.toSelect("select Pk, Pk as Foo, Pk as \"Foo\" from dbtest").query(rs -> { assertArrayEquals(new String[]{"Pk", "Foo", "Foo"}, rs.getColumnLabels()); return null; diff --git a/jdbc-sqlserver/src/test/resources/logging.properties b/jdbc-sqlserver/src/test/resources/logging.properties new file mode 100644 index 0000000..5885e4d --- /dev/null +++ b/jdbc-sqlserver/src/test/resources/logging.properties @@ -0,0 +1,10 @@ +handlers=java.util.logging.FileHandler, java.util.logging.ConsoleHandler +.level=ALL +java.util.logging.SimpleFormatter.format=%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$-7s [%3$s] %5$s %6$s%n +java.util.logging.ConsoleHandler.level=ALL +java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter +java.util.logging.FileHandler.level=ALL +java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter +java.util.logging.FileHandler.pattern=build/database.log +jdk.event.security.level=INFO +javax.management.level=INFO diff --git a/jdbc-test/src/main/java/org/xbib/jdbc/test/CommonTest.java b/jdbc-test/src/main/java/org/xbib/jdbc/test/CommonTest.java index 4748633..4db005d 100644 --- a/jdbc-test/src/main/java/org/xbib/jdbc/test/CommonTest.java +++ b/jdbc-test/src/main/java/org/xbib/jdbc/test/CommonTest.java @@ -36,6 +36,7 @@ import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Locale; import java.util.TimeZone; import java.util.logging.Level; import java.util.logging.Logger; @@ -86,6 +87,13 @@ public abstract class CommonTest { } } + @Test + public void testJdbcVersion() { + // we want JDBC >= 4.2 + assertEquals(4, db.jdbcMajorVersion()); + assertTrue(db.jdbcMinorVersion() >= 2); + } + @Test public void tableExists() { // Verify dbtest table does not exist @@ -111,22 +119,13 @@ public abstract class CommonTest { @Test public void normalizeTableName() { - // Verify that null and empty cases are handled gracefully - assertNull(db.normalizeTableName(null)); - assertEquals("", db.normalizeTableName("")); - - // Verify a quoted table name is returned in exactly the same case, with quotes removed. + assertNull(db.flavor().normalizeTableName(null)); + assertEquals("", db.flavor().normalizeTableName("")); String camelCaseTableName = "\"DbTest\""; assertEquals(camelCaseTableName.substring(1, camelCaseTableName.length() - 1), - db.normalizeTableName(camelCaseTableName)); - - // Verify that the database flavor gets the expected normalized case - boolean isUpperCase = db.flavor().isNormalizedUpperCase(); - if (isUpperCase) { - assertEquals(TEST_TABLE_NAME.toUpperCase(), db.normalizeTableName(TEST_TABLE_NAME)); - } else { - assertEquals(TEST_TABLE_NAME.toLowerCase(), db.normalizeTableName(TEST_TABLE_NAME)); - } + db.flavor().normalizeTableName(camelCaseTableName)); + assertEquals(TEST_TABLE_NAME.toUpperCase(Locale.ENGLISH), + db.flavor().normalizeTableName(TEST_TABLE_NAME).toUpperCase(Locale.ENGLISH)); } @Test @@ -144,12 +143,10 @@ public abstract class CommonTest { .addColumn("bin_blob").asBlob().table() .addColumn("date_millis").asLocalDateTime().table() .addColumn("local_date").asLocalDate().table().schema().execute(db); - BigDecimal bigDecimal = new BigDecimal("5.3"); db.toInsert("insert into dbtest values (?,?,?,?,?,?,?,?,?,?,?)").argInteger(1).argLong(2L).argFloat(3.2f).argDouble(4.2) .argBigDecimal(bigDecimal).argString("Hello").argString("T").argClobString("World") .argBlobBytes("More".getBytes()).argLocalDateTime(now).argLocalDate(localDateNow).insert(1); - db.toSelect("select nbr_integer, nbr_long, nbr_float, nbr_double, nbr_big_decimal, str_varchar, str_fixed, str_lob, " + "bin_blob, date_millis, local_date from dbtest") .query((RowsHandler) rs -> { @@ -594,20 +591,15 @@ public abstract class CommonTest { new Schema() .addTable("dbtest") .addColumn(timestampColumnName).asLocalDateTime().table() - .addColumn(dateColumnName).asLocalDate().table().schema().execute(db); + .addColumn(dateColumnName).asLocalDate().table() + .schema().execute(db); db.toSelect("select * from dbtest").query((RowsHandler) rs -> { ResultSetMetaData metadata = rs.getMetadata(); for (int i = 1; i <= metadata.getColumnCount(); i++) { String columnName = metadata.getColumnName(i); String columnType = metadata.getColumnTypeName(i); if (columnName.equalsIgnoreCase(timestampColumnName)) { - if ("sqlserver".equals(db.flavor().getName())) { - assertEquals("DATETIME2", columnType.toUpperCase()); - } else if ("hsqldb".equals(db.flavor().getName())) { - assertEquals("TIMESTAMP WITH TIME ZONE", columnType.toUpperCase()); - } else { - assertEquals("TIMESTAMP", columnType.toUpperCase()); - } + assertEquals(db.flavor().columnTypeLocalDateTime(), columnType); } else if (columnName.equalsIgnoreCase(dateColumnName)) { assertEquals("DATE", columnType.toUpperCase()); } else { diff --git a/settings.gradle b/settings.gradle index 112d795..613d067 100644 --- a/settings.gradle +++ b/settings.gradle @@ -31,3 +31,4 @@ include 'jdbc-test' include 'jdbc-mariadb' include 'jdbc-oracle' include 'jdbc-postgresql' +include 'jdbc-sqlserver'