cleaning the code, move sqlserver, table name normalization in flavor, get rid of properties/file handling in DatabaseProvider
This commit is contained in:
parent
ca784dc6cf
commit
7378b91c28
35 changed files with 626 additions and 2554 deletions
10
NOTICE.txt
10
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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
@ -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";
|
||||
|
|
|
@ -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
|
||||
public void saveResultAsTable() {
|
||||
super.saveResultAsTable();
|
||||
@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() {
|
||||
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<SqlArgs> 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<SqlArgs> 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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -11,6 +11,11 @@ import java.util.function.Supplier;
|
|||
* Primary class for accessing a relational (SQL) database.
|
||||
*/
|
||||
public interface Database extends Supplier<Database> {
|
||||
|
||||
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<Database> {
|
|||
*/
|
||||
Map<String, Integer> 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
|
||||
|
|
|
@ -137,7 +137,6 @@ public class DatabaseImpl implements Database {
|
|||
if (!options.allowConnectionAccess()) {
|
||||
throw new DatabaseException("Calls to underlyingConnection() are not allowed");
|
||||
}
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
|
@ -151,6 +150,23 @@ 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() {
|
||||
|
@ -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())
|
||||
|
|
|
@ -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<Database>, 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<Database>, Closeable {
|
|||
*
|
||||
* <p>A database pool will be created using jdbc-connection-pool.</p>
|
||||
*/
|
||||
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<Database>, 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<Database>, 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<Database>, Closeable {
|
|||
}, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the database from up to five properties read from a file:
|
||||
* <pre>
|
||||
* 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)
|
||||
* </pre>
|
||||
* <p>This will use the JVM default character encoding to read the property file.</p>
|
||||
*
|
||||
* @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:
|
||||
* <pre>
|
||||
* 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)
|
||||
* </pre>
|
||||
*
|
||||
* @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:
|
||||
* <pre>
|
||||
* 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)
|
||||
* </pre>
|
||||
* <p>This will use the JVM default character encoding to read the property file.</p>
|
||||
*
|
||||
* @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:
|
||||
* <pre>
|
||||
* 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)
|
||||
* </pre>
|
||||
*
|
||||
* @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:
|
||||
* <pre>
|
||||
* 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)
|
||||
* </pre>
|
||||
*
|
||||
* @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:
|
||||
* <pre>
|
||||
* 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)
|
||||
* </pre>
|
||||
*
|
||||
* @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):
|
||||
* <pre>
|
||||
* 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)
|
||||
* </pre>
|
||||
* <p>This will use the JVM default character encoding to read the property file.</p>
|
||||
*
|
||||
* @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):
|
||||
* <pre>
|
||||
* 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)
|
||||
* </pre>
|
||||
*
|
||||
* @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):
|
||||
* <pre>
|
||||
* 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)
|
||||
* </pre>
|
||||
* <p>This will use the JVM default character encoding to read the property file.</p>
|
||||
*
|
||||
* @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):
|
||||
* <pre>
|
||||
* 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)
|
||||
* </pre>
|
||||
*
|
||||
* @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:
|
||||
* <pre>
|
||||
* -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)
|
||||
* </pre>
|
||||
*/
|
||||
|
||||
public static DatabaseProviderBuilder fromSystemProperties() {
|
||||
return fromProperties(null, "", true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the database from up to five system properties:
|
||||
* <pre>
|
||||
* -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)
|
||||
* </pre>
|
||||
*
|
||||
* @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<Database>, 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<Database>, 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<Database>, 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<Database>, 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<Database>, 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<Database>, 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.
|
||||
*
|
||||
* <p>If you use this method you are responsible for managing
|
||||
* the transaction and commit/rollback/close.</p>
|
||||
*/
|
||||
|
||||
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}.
|
||||
*
|
||||
* <p>Here is a typical usage:
|
||||
* <pre>
|
||||
* dbp.transact(dbs -> {
|
||||
* List<String> r = dbs.get().toSelect("select a from b where c=?").argInteger(1).queryStrings();
|
||||
* ... do something with the results ...
|
||||
* });
|
||||
* </pre>
|
||||
* </p>
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* <p>Here is a typical usage:
|
||||
* <pre>
|
||||
* List<String> r = dbp.transact(dbs -> {
|
||||
* return dbs.get().toSelect("select a from b where c=?").argInteger(1).queryStrings();
|
||||
* });
|
||||
* </pre>
|
||||
* </p>
|
||||
*/
|
||||
<T> T transactReturning(DbCodeTyped<T> 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.
|
||||
*
|
||||
* <p>Here is a typical usage:
|
||||
* <pre>
|
||||
* dbp.transact((dbs, tx) -> {
|
||||
* tx.setRollbackOnError(false);
|
||||
* dbs.get().toInsert("...").argInteger(1).insert(1);
|
||||
* ...some stuff that might fail...
|
||||
* });
|
||||
* </pre>
|
||||
* </p>
|
||||
*
|
||||
* @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;
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
* <p>If you use this method you are responsible for managing
|
||||
* the transaction and commit/rollback/close.</p>
|
||||
*/
|
||||
|
||||
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}.
|
||||
*
|
||||
* <p>Here is a typical usage:
|
||||
* <pre>
|
||||
* dbp.transact(dbs -> {
|
||||
* List<String> r = dbs.get().toSelect("select a from b where c=?").argInteger(1).queryStrings();
|
||||
* ... do something with the results ...
|
||||
* });
|
||||
* </pre>
|
||||
* </p>
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* <p>Here is a typical usage:
|
||||
* <pre>
|
||||
* List<String> r = dbp.transact(dbs -> {
|
||||
* return dbs.get().toSelect("select a from b where c=?").argInteger(1).queryStrings();
|
||||
* });
|
||||
* </pre>
|
||||
* </p>
|
||||
*/
|
||||
<T> T transactReturning(DbCodeTyped<T> 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.
|
||||
*
|
||||
* <p>Here is a typical usage:
|
||||
* <pre>
|
||||
* dbp.transact((dbs, tx) -> {
|
||||
* tx.setRollbackOnError(false);
|
||||
* dbs.get().toInsert("...").argInteger(1).insert(1);
|
||||
* ...some stuff that might fail...
|
||||
* });
|
||||
* </pre>
|
||||
* </p>
|
||||
*
|
||||
* @param code the code you want to run as a transaction with a Database
|
||||
*/
|
||||
void transact(DbCodeTx code);
|
||||
|
||||
}
|
|
@ -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 <a href="https://github.com/ontop/ontop/wiki/Case-sensitivity-for-SQL-identifiers>SQL case sensitivity</a>
|
||||
*/
|
||||
boolean isNormalizedUpperCase();
|
||||
String normalizeTableName(String tableName);
|
||||
|
||||
String typeInteger();
|
||||
|
||||
|
@ -44,6 +40,8 @@ public interface Flavor {
|
|||
|
||||
String typeLocalDateTime();
|
||||
|
||||
String columnTypeLocalDateTime();
|
||||
|
||||
String typeLocalDate();
|
||||
|
||||
boolean useStringForClob();
|
||||
|
|
|
@ -21,15 +21,19 @@ import java.util.function.Supplier;
|
|||
*/
|
||||
public class Schema {
|
||||
|
||||
private final List<Table> tables = new ArrayList<>();
|
||||
private final List<Table> tables;
|
||||
|
||||
private final List<Sequence> sequences = new ArrayList<>();
|
||||
private final List<Sequence> sequences;
|
||||
|
||||
private boolean indexForeignKeys = true;
|
||||
|
||||
private String userTableName = "user_principal";
|
||||
private String userTableName;
|
||||
|
||||
public Schema() {
|
||||
tables = new ArrayList<>();
|
||||
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<Column> columns = new ArrayList<>();
|
||||
|
||||
private PrimaryKey primaryKey;
|
||||
private final List<ForeignKey> foreignKeys = new ArrayList<>();
|
||||
private final List<Index> indexes = new ArrayList<>();
|
||||
private final List<Check> checks = new ArrayList<>();
|
||||
private final List<Unique> uniques = new ArrayList<>();
|
||||
private final Map<Flavor, String> customClauses = new HashMap<>();
|
||||
|
||||
private final List<Column> columns;
|
||||
|
||||
private final List<ForeignKey> foreignKeys;
|
||||
|
||||
private final List<Index> indexes;
|
||||
|
||||
private final List<Check> checks;
|
||||
|
||||
private final List<Unique> uniques;
|
||||
|
||||
private final Map<Flavor, String> 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<String> columnNames = new ArrayList<>();
|
||||
|
||||
private final List<String> 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<String> columnNames = new ArrayList<>();
|
||||
|
||||
private final List<String> 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<String> columnNames = new ArrayList<>();
|
||||
public String foreignTable;
|
||||
private boolean onDeleteCascade = false;
|
||||
|
||||
private final List<String> 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<String> columnNames = new ArrayList<>();
|
||||
|
||||
private final List<String> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -18,15 +15,36 @@ public class When {
|
|||
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;
|
||||
|
@ -40,13 +58,12 @@ 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) {
|
||||
chosen = sql;
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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
|
File diff suppressed because it is too large
Load diff
|
@ -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() {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
* <br/>
|
||||
* <pre>
|
||||
* -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)
|
||||
* </pre>
|
||||
*/
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
10
jdbc-sqlserver/build.gradle
Normal file
10
jdbc-sqlserver/build.gradle
Normal file
|
@ -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'
|
||||
}
|
10
jdbc-sqlserver/src/main/java/module-info.java
Normal file
10
jdbc-sqlserver/src/main/java/module-info.java
Normal file
|
@ -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;
|
||||
}
|
|
@ -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";
|
|
@ -0,0 +1 @@
|
|||
org.xbib.jdbc.sqlserver.SqlServer
|
|
@ -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;
|
10
jdbc-sqlserver/src/test/resources/logging.properties
Normal file
10
jdbc-sqlserver/src/test/resources/logging.properties
Normal file
|
@ -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
|
|
@ -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<Void>) 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<Void>) 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 {
|
||||
|
|
|
@ -31,3 +31,4 @@ include 'jdbc-test'
|
|||
include 'jdbc-mariadb'
|
||||
include 'jdbc-oracle'
|
||||
include 'jdbc-postgresql'
|
||||
include 'jdbc-sqlserver'
|
||||
|
|
Loading…
Reference in a new issue