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
|
https://github.com/susom/database
|
||||||
|
|
||||||
|
|
|
@ -20,9 +20,9 @@ import org.xbib.jdbc.connection.pool.PoolConfig;
|
||||||
import org.xbib.jdbc.connection.pool.PoolDataSource;
|
import org.xbib.jdbc.connection.pool.PoolDataSource;
|
||||||
|
|
||||||
@ExtendWith(PoolTestExtension.class)
|
@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;
|
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.PreparedStatement;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
public class MariaDB implements Flavor {
|
public class MariaDB implements Flavor {
|
||||||
|
|
||||||
|
@ -26,8 +27,16 @@ public class MariaDB implements Flavor {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isNormalizedUpperCase() {
|
public String normalizeTableName(String tableName) {
|
||||||
return false;
|
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
|
@Override
|
||||||
|
@ -52,7 +61,7 @@ public class MariaDB implements Flavor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setFloat(PreparedStatement preparedStatement, int i, Float floatValue) throws SQLException {
|
public void setFloat(PreparedStatement preparedStatement, int i, Float floatValue) throws SQLException {
|
||||||
preparedStatement.setFloat(i, floatValue);
|
preparedStatement.setDouble(i, floatValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -98,6 +107,11 @@ public class MariaDB implements Flavor {
|
||||||
return "datetime(3)";
|
return "datetime(3)";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String columnTypeLocalDateTime() {
|
||||||
|
return "DATETIME";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String typeLocalDate() {
|
public String typeLocalDate() {
|
||||||
return "date";
|
return "date";
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package org.xbib.jdbc.mariadb.test;
|
package org.xbib.jdbc.mariadb.test;
|
||||||
|
|
||||||
import org.junit.jupiter.api.AfterAll;
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
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.ConfigSupplier;
|
||||||
import org.xbib.jdbc.query.DatabaseProvider;
|
import org.xbib.jdbc.query.DatabaseProvider;
|
||||||
import org.xbib.jdbc.query.OptionsOverride;
|
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 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 {
|
public class MariaDBTest extends CommonTest {
|
||||||
|
|
||||||
static MariaDBContainer<?> mariaDBContainer;
|
static MariaDBContainer<?> mariaDBContainer;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
// mariadb 10.3.6
|
||||||
mariaDBContainer = new MariaDBContainer<>("mariadb")
|
mariaDBContainer = new MariaDBContainer<>("mariadb")
|
||||||
.withDatabaseName("testDB")
|
.withDatabaseName("testDB")
|
||||||
.withUsername("testUser")
|
.withUsername("testUser")
|
||||||
|
@ -88,21 +100,156 @@ public class MariaDBTest extends CommonTest {
|
||||||
super.intervals();
|
super.intervals();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Disabled("MariaDB temporarily disabled")
|
|
||||||
@Override
|
|
||||||
public void metadataColumnNames() {
|
|
||||||
super.intervals();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Disabled("MariaDB temporarily disabled")
|
@Disabled("MariaDB temporarily disabled")
|
||||||
@Override
|
@Override
|
||||||
public void metadataColumnTypes() {
|
public void metadataColumnTypes() {
|
||||||
super.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
|
@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() {
|
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<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
|
@Override
|
||||||
public boolean isNormalizedUpperCase() {
|
public String normalizeTableName(String tableName) {
|
||||||
return true;
|
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
|
@Override
|
||||||
|
@ -87,6 +95,11 @@ public class Oracle implements Flavor {
|
||||||
return "timestamp(3)";
|
return "timestamp(3)";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String columnTypeLocalDateTime() {
|
||||||
|
return "TIMESTAMP";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String typeLocalDate() {
|
public String typeLocalDate() {
|
||||||
return "date";
|
return "date";
|
||||||
|
|
|
@ -4,6 +4,7 @@ import org.xbib.jdbc.query.Flavor;
|
||||||
|
|
||||||
import java.sql.PreparedStatement;
|
import java.sql.PreparedStatement;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
public class Postgresql implements Flavor {
|
public class Postgresql implements Flavor {
|
||||||
|
|
||||||
|
@ -26,8 +27,16 @@ public class Postgresql implements Flavor {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isNormalizedUpperCase() {
|
public String normalizeTableName(String tableName) {
|
||||||
return false;
|
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
|
@Override
|
||||||
|
@ -95,6 +104,11 @@ public class Postgresql implements Flavor {
|
||||||
return "timestamp(3)";
|
return "timestamp(3)";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String columnTypeLocalDateTime() {
|
||||||
|
return "timestamp";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String typeLocalDate() {
|
public String typeLocalDate() {
|
||||||
return "date";
|
return "date";
|
||||||
|
|
|
@ -20,6 +20,7 @@ public class PostgresqlTest extends CommonTest {
|
||||||
static PostgreSQLContainer<?> postgreSQLContainer;
|
static PostgreSQLContainer<?> postgreSQLContainer;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
// postgresql 9.6.12
|
||||||
postgreSQLContainer = new PostgreSQLContainer<>("postgres")
|
postgreSQLContainer = new PostgreSQLContainer<>("postgres")
|
||||||
.withDatabaseName("testDB")
|
.withDatabaseName("testDB")
|
||||||
.withUsername("testUser")
|
.withUsername("testUser")
|
||||||
|
|
|
@ -8,3 +8,4 @@ java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
|
||||||
java.util.logging.FileHandler.pattern=build/database.log
|
java.util.logging.FileHandler.pattern=build/database.log
|
||||||
jdk.event.security.level=INFO
|
jdk.event.security.level=INFO
|
||||||
javax.management.level=INFO
|
javax.management.level=INFO
|
||||||
|
org.postgresql.level=INFO
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
dependencies {
|
dependencies {
|
||||||
api project(":jdbc-connection-pool")
|
api project(':jdbc-connection-pool')
|
||||||
|
testImplementation project(':jdbc-test')
|
||||||
testImplementation libs.derby
|
testImplementation libs.derby
|
||||||
testImplementation libs.hsqldb
|
testImplementation libs.hsqldb
|
||||||
|
testImplementation libs.h2
|
||||||
testImplementation libs.testcontainers
|
testImplementation libs.testcontainers
|
||||||
testImplementation libs.testcontainers.junit.jupiter
|
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;
|
||||||
import org.xbib.jdbc.query.flavor.Derby;
|
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.Hsql;
|
||||||
import org.xbib.jdbc.query.flavor.SqlServer;
|
|
||||||
|
|
||||||
module org.xbib.jdbc.query {
|
module org.xbib.jdbc.query {
|
||||||
uses Flavor;
|
uses Flavor;
|
||||||
requires org.xbib.jdbc.connection.pool;
|
requires org.xbib.jdbc.connection.pool;
|
||||||
requires java.sql;
|
requires java.sql;
|
||||||
exports org.xbib.jdbc.query;
|
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.
|
* Primary class for accessing a relational (SQL) database.
|
||||||
*/
|
*/
|
||||||
public interface Database extends Supplier<Database> {
|
public interface Database extends Supplier<Database> {
|
||||||
|
|
||||||
|
int jdbcMajorVersion();
|
||||||
|
|
||||||
|
int jdbcMinorVersion();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a SQL "insert" statement for further manipulation and execution.
|
* Create a SQL "insert" statement for further manipulation and execution.
|
||||||
* Note this call does not actually execute the SQL.
|
* 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);
|
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
|
* 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
|
* 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()) {
|
if (!options.allowConnectionAccess()) {
|
||||||
throw new DatabaseException("Calls to underlyingConnection() are not allowed");
|
throw new DatabaseException("Calls to underlyingConnection() are not allowed");
|
||||||
}
|
}
|
||||||
|
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,6 +150,23 @@ public class DatabaseImpl implements Database {
|
||||||
return options.flavor();
|
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
|
@Override
|
||||||
public When when() {
|
public When when() {
|
||||||
|
@ -181,7 +197,7 @@ public class DatabaseImpl implements Database {
|
||||||
if (tableName != null && connection != null) {
|
if (tableName != null && connection != null) {
|
||||||
try {
|
try {
|
||||||
DatabaseMetaData metadata = connection.getMetaData();
|
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"});
|
ResultSet resultSet = metadata.getTables(connection.getCatalog(), schemaName, normalizedTable, new String[]{"TABLE", "VIEW"});
|
||||||
while (resultSet.next()) {
|
while (resultSet.next()) {
|
||||||
if (normalizedTable.equals(resultSet.getString("TABLE_NAME"))) {
|
if (normalizedTable.equals(resultSet.getString("TABLE_NAME"))) {
|
||||||
|
@ -203,7 +219,7 @@ public class DatabaseImpl implements Database {
|
||||||
if (tableName != null && connection != null) {
|
if (tableName != null && connection != null) {
|
||||||
try {
|
try {
|
||||||
DatabaseMetaData metaData = connection.getMetaData();
|
DatabaseMetaData metaData = connection.getMetaData();
|
||||||
String normalizedTable = normalizeTableName(tableName);
|
String normalizedTable = flavor().normalizeTableName(tableName);
|
||||||
ResultSet resultSet = metaData.getColumns(null, null, normalizedTable, "%");
|
ResultSet resultSet = metaData.getColumns(null, null, normalizedTable, "%");
|
||||||
ResultSetMetaData rsmd = resultSet.getMetaData();
|
ResultSetMetaData rsmd = resultSet.getMetaData();
|
||||||
int cols = rsmd.getColumnCount();
|
int cols = rsmd.getColumnCount();
|
||||||
|
@ -229,24 +245,6 @@ public class DatabaseImpl implements Database {
|
||||||
return map;
|
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
|
@Override
|
||||||
public void assertTimeSynchronized(long millisToWarn, long millisToError) {
|
public void assertTimeSynchronized(long millisToWarn, long millisToError) {
|
||||||
toSelect("select ?" + flavor().fromAny())
|
toSelect("select ?" + flavor().fromAny())
|
||||||
|
|
|
@ -6,12 +6,8 @@ import org.xbib.jdbc.query.util.Metric;
|
||||||
|
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.nio.charset.CharsetDecoder;
|
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.DriverManager;
|
import java.sql.DriverManager;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
@ -30,7 +26,7 @@ import java.util.logging.Logger;
|
||||||
*/
|
*/
|
||||||
public final class DatabaseProvider implements Supplier<Database>, Closeable {
|
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);
|
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>
|
* <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));
|
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
|
* the JDBC standard DriverManager method. The url parameter will be inspected
|
||||||
* to determine the Flavor for this database.
|
* to determine the Flavor for this database.
|
||||||
*/
|
*/
|
||||||
public static DatabaseProviderBuilder builder(String url) {
|
public static DatabaseProviderBuilder builder(ClassLoader classLoader, String url) {
|
||||||
return builder(url, Flavor.fromJdbcUrl(url), null, null, null);
|
return builder(classLoader, url, Flavor.fromJdbcUrl(url), null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static DatabaseProviderBuilder builder(ClassLoader classLoader,
|
||||||
* Builder method to create and initialize an instance of this class using
|
String url,
|
||||||
* 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,
|
|
||||||
Flavor flavor,
|
Flavor flavor,
|
||||||
Properties info,
|
Properties info,
|
||||||
String user,
|
String user,
|
||||||
|
@ -156,7 +98,7 @@ public final class DatabaseProvider implements Supplier<Database>, Closeable {
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
if (flavor.driverClass() != null) {
|
if (flavor.driverClass() != null) {
|
||||||
try {
|
try {
|
||||||
Class.forName(flavor.driverClass());
|
Class.forName(flavor.driverClass(), true, classLoader);
|
||||||
} catch (ClassNotFoundException e1) {
|
} catch (ClassNotFoundException e1) {
|
||||||
throw new DatabaseException("couldn't locate JDBC driver", e1);
|
throw new DatabaseException("couldn't locate JDBC driver", e1);
|
||||||
}
|
}
|
||||||
|
@ -176,417 +118,6 @@ public final class DatabaseProvider implements Supplier<Database>, Closeable {
|
||||||
}, options);
|
}, 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)
|
private static DataSource createDataSource(Config config)
|
||||||
throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException,
|
throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException,
|
||||||
IllegalAccessException {
|
IllegalAccessException {
|
||||||
|
@ -749,7 +280,7 @@ public final class DatabaseProvider implements Supplier<Database>, Closeable {
|
||||||
if (builder.isClosed()) {
|
if (builder.isClosed()) {
|
||||||
throw new DatabaseException("Called get() on a DatabaseProvider after close()");
|
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 {
|
try {
|
||||||
connection = builder.connectionProvider.get();
|
connection = builder.connectionProvider.get();
|
||||||
metric.checkpoint("getConn");
|
metric.checkpoint("getConn");
|
||||||
|
@ -770,10 +301,10 @@ public final class DatabaseProvider implements Supplier<Database>, Closeable {
|
||||||
throw e;
|
throw e;
|
||||||
} finally {
|
} finally {
|
||||||
metric.done();
|
metric.done();
|
||||||
if (log.isLoggable(Level.FINE)) {
|
if (logger.isLoggable(Level.FINE)) {
|
||||||
StringBuilder buf = new StringBuilder("Get ").append(builder.options.flavor()).append(" database: ");
|
StringBuilder buf = new StringBuilder("Get ").append(builder.options.flavor()).append(" database: ");
|
||||||
metric.printMessage(buf);
|
metric.printMessage(buf);
|
||||||
log.fine(buf.toString());
|
logger.fine(buf.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return database;
|
return database;
|
||||||
|
@ -811,7 +342,7 @@ public final class DatabaseProvider implements Supplier<Database>, Closeable {
|
||||||
connection.rollback();
|
connection.rollback();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} 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();
|
close();
|
||||||
} catch (Exception e) {
|
} 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 {
|
try {
|
||||||
connection.close();
|
connection.close();
|
||||||
} catch (Exception e) {
|
} 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;
|
connection = null;
|
||||||
|
@ -843,129 +374,6 @@ public final class DatabaseProvider implements Supplier<Database>, Closeable {
|
||||||
builder.close();
|
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 static class DatabaseProviderBuilderImpl implements DatabaseProviderBuilder, Closeable {
|
||||||
|
|
||||||
private DataSource dataSource;
|
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();
|
String driverClass();
|
||||||
|
|
||||||
/**
|
String normalizeTableName(String tableName);
|
||||||
* 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 typeInteger();
|
String typeInteger();
|
||||||
|
|
||||||
|
@ -44,6 +40,8 @@ public interface Flavor {
|
||||||
|
|
||||||
String typeLocalDateTime();
|
String typeLocalDateTime();
|
||||||
|
|
||||||
|
String columnTypeLocalDateTime();
|
||||||
|
|
||||||
String typeLocalDate();
|
String typeLocalDate();
|
||||||
|
|
||||||
boolean useStringForClob();
|
boolean useStringForClob();
|
||||||
|
|
|
@ -21,15 +21,19 @@ import java.util.function.Supplier;
|
||||||
*/
|
*/
|
||||||
public class Schema {
|
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 boolean indexForeignKeys = true;
|
||||||
|
|
||||||
private String userTableName = "user_principal";
|
private String userTableName;
|
||||||
|
|
||||||
public Schema() {
|
public Schema() {
|
||||||
|
tables = new ArrayList<>();
|
||||||
|
sequences = new ArrayList<>();
|
||||||
|
indexForeignKeys = true;
|
||||||
|
userTableName = "user_principal";
|
||||||
}
|
}
|
||||||
|
|
||||||
public Sequence addSequence(String name) {
|
public Sequence addSequence(String name) {
|
||||||
|
@ -70,10 +74,10 @@ public class Schema {
|
||||||
return table;
|
return table;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Table addTableFromRow(String tableName, Row r) {
|
public Table addTableFromRow(String tableName, Row row) {
|
||||||
Table table = addTable(tableName);
|
Table table = addTable(tableName);
|
||||||
try {
|
try {
|
||||||
ResultSetMetaData metadata = r.getMetadata();
|
ResultSetMetaData metadata = row.getMetadata();
|
||||||
int columnCount = metadata.getColumnCount();
|
int columnCount = metadata.getColumnCount();
|
||||||
String[] names = new String[columnCount];
|
String[] names = new String[columnCount];
|
||||||
for (int i = 0; i < columnCount; i++) {
|
for (int i = 0; i < columnCount; i++) {
|
||||||
|
@ -464,51 +468,70 @@ public class Schema {
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Table {
|
public class Table {
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
private String comment;
|
private String comment;
|
||||||
private final List<Column> columns = new ArrayList<>();
|
|
||||||
private PrimaryKey primaryKey;
|
private PrimaryKey primaryKey;
|
||||||
private final List<ForeignKey> foreignKeys = new ArrayList<>();
|
|
||||||
private final List<Index> indexes = new ArrayList<>();
|
private final List<Column> columns;
|
||||||
private final List<Check> checks = new ArrayList<>();
|
|
||||||
private final List<Unique> uniques = new ArrayList<>();
|
private final List<ForeignKey> foreignKeys;
|
||||||
private final Map<Flavor, String> customClauses = new HashMap<>();
|
|
||||||
|
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 boolean createTracking;
|
||||||
|
|
||||||
private String createTrackingFkName;
|
private String createTrackingFkName;
|
||||||
|
|
||||||
private String createTrackingFkTable;
|
private String createTrackingFkTable;
|
||||||
|
|
||||||
private boolean updateTracking;
|
private boolean updateTracking;
|
||||||
|
|
||||||
private String updateTrackingFkName;
|
private String updateTrackingFkName;
|
||||||
|
|
||||||
private String updateTrackingFkTable;
|
private String updateTrackingFkTable;
|
||||||
|
|
||||||
private boolean updateSequence;
|
private boolean updateSequence;
|
||||||
|
|
||||||
private boolean historyTable;
|
private boolean historyTable;
|
||||||
|
|
||||||
public Table(String name) {
|
public Table(String name) {
|
||||||
this.name = toName(name);
|
this.name = toName(name);
|
||||||
if (this.name.length() > 27) {
|
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() {
|
public void validate() {
|
||||||
if (columns.size() < 1) {
|
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) {
|
for (Column c : columns) {
|
||||||
c.validate();
|
c.validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (primaryKey != null) {
|
if (primaryKey != null) {
|
||||||
primaryKey.validate();
|
primaryKey.validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (ForeignKey fk : foreignKeys) {
|
for (ForeignKey fk : foreignKeys) {
|
||||||
fk.validate();
|
fk.validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Check c : checks) {
|
for (Check c : checks) {
|
||||||
c.validate();
|
c.validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Index i : indexes) {
|
for (Index i : indexes) {
|
||||||
i.validate();
|
i.validate();
|
||||||
}
|
}
|
||||||
|
@ -629,12 +652,12 @@ public class Schema {
|
||||||
|
|
||||||
public PrimaryKey addPrimaryKey(String name, String... columnNames) {
|
public PrimaryKey addPrimaryKey(String name, String... columnNames) {
|
||||||
if (primaryKey != null) {
|
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, ...).");
|
+ " addPrimaryKey(name, c1, c2, ...).");
|
||||||
}
|
}
|
||||||
for (Column c : columns) {
|
for (Column c : columns) {
|
||||||
if (c.name.equalsIgnoreCase(name)) {
|
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);
|
primaryKey = new PrimaryKey(name, columnNames);
|
||||||
|
@ -671,18 +694,20 @@ public class Schema {
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PrimaryKey {
|
public class PrimaryKey {
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
private final List<String> columnNames = new ArrayList<>();
|
|
||||||
|
private final List<String> columnNames;
|
||||||
|
|
||||||
public PrimaryKey(String name, String[] columnNames) {
|
public PrimaryKey(String name, String[] columnNames) {
|
||||||
this.name = toName(name);
|
this.name = toName(name);
|
||||||
|
this.columnNames = new ArrayList<>();
|
||||||
for (String s : columnNames) {
|
for (String s : columnNames) {
|
||||||
this.columnNames.add(toName(s));
|
this.columnNames.add(toName(s));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void validate() {
|
public void validate() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Table table() {
|
public Table table() {
|
||||||
|
@ -692,18 +717,20 @@ public class Schema {
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Unique {
|
public class Unique {
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
private final List<String> columnNames = new ArrayList<>();
|
|
||||||
|
private final List<String> columnNames;
|
||||||
|
|
||||||
public Unique(String name, String[] columnNames) {
|
public Unique(String name, String[] columnNames) {
|
||||||
this.name = toName(name);
|
this.name = toName(name);
|
||||||
|
this.columnNames = new ArrayList<>();
|
||||||
for (String s : columnNames) {
|
for (String s : columnNames) {
|
||||||
this.columnNames.add(toName(s));
|
this.columnNames.add(toName(s));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void validate() {
|
public void validate() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Table table() {
|
public Table table() {
|
||||||
|
@ -713,13 +740,18 @@ public class Schema {
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ForeignKey {
|
public class ForeignKey {
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
private final List<String> columnNames = new ArrayList<>();
|
|
||||||
public String foreignTable;
|
private final List<String> columnNames;
|
||||||
private boolean onDeleteCascade = false;
|
|
||||||
|
private String foreignTable;
|
||||||
|
|
||||||
|
private boolean onDeleteCascade;
|
||||||
|
|
||||||
public ForeignKey(String name, String[] columnNames) {
|
public ForeignKey(String name, String[] columnNames) {
|
||||||
this.name = toName(name);
|
this.name = toName(name);
|
||||||
|
this.columnNames = new ArrayList<>();
|
||||||
for (String s : columnNames) {
|
for (String s : columnNames) {
|
||||||
this.columnNames.add(toName(s));
|
this.columnNames.add(toName(s));
|
||||||
}
|
}
|
||||||
|
@ -737,7 +769,7 @@ public class Schema {
|
||||||
|
|
||||||
private void validate() {
|
private void validate() {
|
||||||
if (foreignTable == null) {
|
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 {
|
public class Check {
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
private final String expression;
|
private final String expression;
|
||||||
|
|
||||||
public Check(String name, String expression) {
|
public Check(String name, String expression) {
|
||||||
|
@ -758,7 +792,7 @@ public class Schema {
|
||||||
|
|
||||||
private void validate() {
|
private void validate() {
|
||||||
if (expression == null) {
|
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 {
|
public class Index {
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
private final List<String> columnNames = new ArrayList<>();
|
|
||||||
|
private final List<String> columnNames;
|
||||||
|
|
||||||
private boolean unique;
|
private boolean unique;
|
||||||
|
|
||||||
public Index(String name, String[] columnNames) {
|
public Index(String name, String[] columnNames) {
|
||||||
this.name = toName(name);
|
this.name = toName(name);
|
||||||
|
this.columnNames = new ArrayList<>();
|
||||||
for (String s : columnNames) {
|
for (String s : columnNames) {
|
||||||
this.columnNames.add(toName(s));
|
this.columnNames.add(toName(s));
|
||||||
}
|
}
|
||||||
|
@ -787,7 +825,7 @@ public class Schema {
|
||||||
|
|
||||||
private void validate() {
|
private void validate() {
|
||||||
if (columnNames.size() < 1) {
|
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 {
|
public class Column {
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
private ColumnType type;
|
private ColumnType type;
|
||||||
|
|
||||||
private int scale;
|
private int scale;
|
||||||
|
|
||||||
private int precision;
|
private int precision;
|
||||||
|
|
||||||
private boolean notNull;
|
private boolean notNull;
|
||||||
|
|
||||||
private String comment;
|
private String comment;
|
||||||
|
|
||||||
public Column(String name) {
|
public Column(String name) {
|
||||||
|
@ -886,7 +930,7 @@ public class Schema {
|
||||||
|
|
||||||
private void validate() {
|
private void validate() {
|
||||||
if (type == null) {
|
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;
|
package org.xbib.jdbc.query;
|
||||||
|
|
||||||
import org.xbib.jdbc.query.flavor.Derby;
|
|
||||||
import org.xbib.jdbc.query.flavor.SqlServer;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,15 +15,36 @@ public class When {
|
||||||
this.actualFlavor = actualFlavor;
|
this.actualFlavor = actualFlavor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public When oracle(String sql) {
|
public When derby(String sql) {
|
||||||
if ("oracle".equals(actualFlavor.getName())) {
|
if ("derby".equals(actualFlavor.getName())) {
|
||||||
chosen = sql;
|
chosen = sql;
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public When derby(String sql) {
|
public When h2(String sql) {
|
||||||
if (actualFlavor instanceof Derby) {
|
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;
|
chosen = sql;
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
|
@ -40,13 +58,12 @@ public class When {
|
||||||
}
|
}
|
||||||
|
|
||||||
public When sqlserver(String sql) {
|
public When sqlserver(String sql) {
|
||||||
if (actualFlavor instanceof SqlServer) {
|
if ("sqlserver".equals(actualFlavor.getName())) {
|
||||||
chosen = sql;
|
chosen = sql;
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public String other(String sql) {
|
public String other(String sql) {
|
||||||
if (chosen == null) {
|
if (chosen == null) {
|
||||||
chosen = sql;
|
chosen = sql;
|
||||||
|
|
|
@ -23,8 +23,16 @@ public class Derby implements Flavor {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isNormalizedUpperCase() {
|
public String normalizeTableName(String tableName) {
|
||||||
return true;
|
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
|
@Override
|
||||||
|
@ -92,6 +100,11 @@ public class Derby implements Flavor {
|
||||||
return "timestamp";
|
return "timestamp";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String columnTypeLocalDateTime() {
|
||||||
|
return "timestamp";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String typeLocalDate() {
|
public String typeLocalDate() {
|
||||||
return "date";
|
return "date";
|
||||||
|
|
|
@ -23,8 +23,16 @@ public class H2 implements Flavor {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isNormalizedUpperCase() {
|
public String normalizeTableName(String tableName) {
|
||||||
return true;
|
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
|
@Override
|
||||||
|
@ -92,6 +100,11 @@ public class H2 implements Flavor {
|
||||||
return "timestamp(3)";
|
return "timestamp(3)";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String columnTypeLocalDateTime() {
|
||||||
|
return "timestamp";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String typeLocalDate() {
|
public String typeLocalDate() {
|
||||||
return "date";
|
return "date";
|
||||||
|
|
|
@ -23,8 +23,16 @@ public class Hsql implements Flavor {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isNormalizedUpperCase() {
|
public String normalizeTableName(String tableName) {
|
||||||
return true;
|
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
|
@Override
|
||||||
|
@ -92,6 +100,11 @@ public class Hsql implements Flavor {
|
||||||
return "timestamp with time zone";
|
return "timestamp with time zone";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String columnTypeLocalDateTime() {
|
||||||
|
return "TIMESTAMP WITH TIME ZONE";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String typeLocalDate() {
|
public String typeLocalDate() {
|
||||||
return "date";
|
return "date";
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
org.xbib.jdbc.query.flavor.Derby
|
org.xbib.jdbc.query.flavor.Derby
|
||||||
org.xbib.jdbc.query.flavor.Hsql
|
org.xbib.jdbc.query.flavor.Hsql
|
||||||
org.xbib.jdbc.query.flavor.H2
|
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.DatabaseProvider;
|
||||||
import org.xbib.jdbc.query.OptionsOverride;
|
import org.xbib.jdbc.query.OptionsOverride;
|
||||||
import org.xbib.jdbc.query.Schema;
|
import org.xbib.jdbc.query.Schema;
|
||||||
|
import org.xbib.jdbc.test.CommonTest;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.logging.Logger;
|
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).
|
* 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 {
|
public class DerbyTest extends CommonTest {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(DerbyTest.class.getName());
|
private static final Logger logger = Logger.getLogger(DerbyTest.class.getName());
|
||||||
|
@ -26,12 +27,10 @@ public class DerbyTest extends CommonTest {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected DatabaseProvider createDatabaseProvider(OptionsOverride options) {
|
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();
|
.withSqlParameterLogging().withSqlInExceptionMessages().withOptions(options).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO fix this test
|
|
||||||
@Disabled("Not sure why this fails on the build servers right now...")
|
|
||||||
@Test
|
@Test
|
||||||
public void clockSync() {
|
public void clockSync() {
|
||||||
super.clockSync();
|
super.clockSync();
|
||||||
|
@ -79,7 +78,6 @@ public class DerbyTest extends CommonTest {
|
||||||
super.intervals();
|
super.intervals();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Disabled("Derby limits out at precision 31")
|
@Disabled("Derby limits out at precision 31")
|
||||||
@Test
|
@Test
|
||||||
public void argBigDecimal38Precision0() {
|
public void argBigDecimal38Precision0() {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import org.xbib.jdbc.query.OptionsOverride;
|
||||||
import org.xbib.jdbc.query.Schema;
|
import org.xbib.jdbc.query.Schema;
|
||||||
import org.xbib.jdbc.query.Sql;
|
import org.xbib.jdbc.query.Sql;
|
||||||
import org.xbib.jdbc.query.SqlArgs;
|
import org.xbib.jdbc.query.SqlArgs;
|
||||||
|
import org.xbib.jdbc.test.CommonTest;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -38,25 +39,6 @@ public class HsqldbTest extends CommonTest {
|
||||||
.build();
|
.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.")
|
@Disabled("LocalDate implementations should be TimeZone agnostic, but HSQLDB implementation has a bug.")
|
||||||
@Test
|
@Test
|
||||||
public void argLocalDateTimeZones() {
|
public void argLocalDateTimeZones() {
|
||||||
|
@ -85,7 +67,6 @@ public class HsqldbTest extends CommonTest {
|
||||||
.addColumn("boolean_flag").asBoolean().table()
|
.addColumn("boolean_flag").asBoolean().table()
|
||||||
.addColumn("date_millis").asLocalDateTime().table()
|
.addColumn("date_millis").asLocalDateTime().table()
|
||||||
.addColumn("local_date").asLocalDate().schema().execute(db);
|
.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,"
|
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 (?,?,?,?,?,?,?,?,?,?,?,?)")
|
+ " str_fixed, str_lob, bin_blob, boolean_flag, date_millis, local_date) values (?,?,?,?,?,?,?,?,?,?,?,?)")
|
||||||
.argInteger(Integer.MAX_VALUE)
|
.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.Database;
|
||||||
import org.xbib.jdbc.query.DatabaseProvider;
|
import org.xbib.jdbc.query.DatabaseProvider;
|
||||||
|
import org.xbib.jdbc.query.DatabaseProviderBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Demo of using some com.github.susom.database classes with Derby.
|
* Demo of using some com.github.susom.database classes with Derby.
|
||||||
|
@ -12,7 +13,7 @@ public abstract class DerbyExample {
|
||||||
// For subclasses to override
|
// For subclasses to override
|
||||||
}
|
}
|
||||||
|
|
||||||
void example(DatabaseProvider.DatabaseProviderBuilder dbb, final String[] args) {
|
void example(DatabaseProviderBuilder dbb, final String[] args) {
|
||||||
dbb.transact(db -> {
|
dbb.transact(db -> {
|
||||||
example(db.get(), args);
|
example(db.get(), args);
|
||||||
});
|
});
|
||||||
|
@ -26,7 +27,7 @@ public abstract class DerbyExample {
|
||||||
try {
|
try {
|
||||||
System.setProperty("derby.stream.error.file", "java.lang.System.err");
|
System.setProperty("derby.stream.error.file", "java.lang.System.err");
|
||||||
String url = "jdbc:derby:target/testdb;create=true";
|
String url = "jdbc:derby:target/testdb;create=true";
|
||||||
example(DatabaseProvider.builder(url), args);
|
example(DatabaseProvider.builder(getClass().getClassLoader(), url), args);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
System.exit(1);
|
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;
|
import java.io.File;
|
||||||
|
|
||||||
/**
|
|
||||||
* Demo of using some com.github.susom.database classes with Derby.
|
|
||||||
*/
|
|
||||||
public class HelloDerby {
|
public class HelloDerby {
|
||||||
public static void main(final String[] args) {
|
public static void main(final String[] args) {
|
||||||
try {
|
try {
|
||||||
|
@ -19,14 +16,12 @@ public class HelloDerby {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void run() {
|
public void run() {
|
||||||
// Put all Derby related files inside ./build to keep our working copy clean
|
File directory = new File("build").getAbsoluteFile();
|
||||||
File directory = new File("target").getAbsoluteFile();
|
|
||||||
if (directory.exists() || directory.mkdirs()) {
|
if (directory.exists() || directory.mkdirs()) {
|
||||||
System.setProperty("derby.stream.error.file", new File(directory, "derby.log").getAbsolutePath());
|
System.setProperty("derby.stream.error.file", new File(directory, "derby.log").getAbsolutePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
String url = "jdbc:derby:target/testdb;create=true";
|
String url = "jdbc:derby:target/testdb;create=true";
|
||||||
DatabaseProvider.builder(url).transact(dbp -> {
|
DatabaseProvider.builder(getClass().getClassLoader(), url).transact(dbp -> {
|
||||||
Database db = dbp.get();
|
Database db = dbp.get();
|
||||||
db.ddl("drop table t").executeQuietly();
|
db.ddl("drop table t").executeQuietly();
|
||||||
db.ddl("create table t (a numeric)").execute();
|
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;
|
import org.xbib.jdbc.query.Flavor;
|
||||||
|
|
||||||
|
@ -7,6 +7,9 @@ import java.sql.SQLException;
|
||||||
|
|
||||||
public class SqlServer implements Flavor {
|
public class SqlServer implements Flavor {
|
||||||
|
|
||||||
|
public SqlServer() {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "sqlServer";
|
return "sqlServer";
|
||||||
|
@ -23,8 +26,16 @@ public class SqlServer implements Flavor {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isNormalizedUpperCase() {
|
public String normalizeTableName(String tableName) {
|
||||||
return false;
|
if (tableName == null) {
|
||||||
|
return tableName;
|
||||||
|
}
|
||||||
|
if (tableName.length() > 2) {
|
||||||
|
if (tableName.startsWith("\"") && tableName.endsWith("\"")) {
|
||||||
|
return tableName.substring(1, tableName.length() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tableName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -72,6 +83,11 @@ public class SqlServer implements Flavor {
|
||||||
return "datetime2(3)";
|
return "datetime2(3)";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String columnTypeLocalDateTime() {
|
||||||
|
return "datetime2";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String typeLocalDate() {
|
public String typeLocalDate() {
|
||||||
return "date";
|
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.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
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.DatabaseProvider;
|
||||||
import org.xbib.jdbc.query.OptionsOverride;
|
import org.xbib.jdbc.query.OptionsOverride;
|
||||||
import org.xbib.jdbc.query.Schema;
|
import org.xbib.jdbc.query.Schema;
|
||||||
|
import org.xbib.jdbc.test.CommonTest;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
|
|
||||||
|
@ -77,9 +78,7 @@ public class SqlServerTest extends CommonTest {
|
||||||
@Test
|
@Test
|
||||||
public void metadataColumnNames() {
|
public void metadataColumnNames() {
|
||||||
db.dropTableQuietly("dbtest");
|
db.dropTableQuietly("dbtest");
|
||||||
|
|
||||||
new Schema().addTable("dbtest").addColumn("pk").primaryKey().schema().execute(db);
|
new Schema().addTable("dbtest").addColumn("pk").primaryKey().schema().execute(db);
|
||||||
|
|
||||||
db.toSelect("select Pk, Pk as Foo, Pk as \"Foo\" from dbtest").query(rs -> {
|
db.toSelect("select Pk, Pk as Foo, Pk as \"Foo\" from dbtest").query(rs -> {
|
||||||
assertArrayEquals(new String[]{"Pk", "Foo", "Foo"}, rs.getColumnLabels());
|
assertArrayEquals(new String[]{"Pk", "Foo", "Foo"}, rs.getColumnLabels());
|
||||||
return null;
|
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.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
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
|
@Test
|
||||||
public void tableExists() {
|
public void tableExists() {
|
||||||
// Verify dbtest table does not exist
|
// Verify dbtest table does not exist
|
||||||
|
@ -111,22 +119,13 @@ public abstract class CommonTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void normalizeTableName() {
|
public void normalizeTableName() {
|
||||||
// Verify that null and empty cases are handled gracefully
|
assertNull(db.flavor().normalizeTableName(null));
|
||||||
assertNull(db.normalizeTableName(null));
|
assertEquals("", db.flavor().normalizeTableName(""));
|
||||||
assertEquals("", db.normalizeTableName(""));
|
|
||||||
|
|
||||||
// Verify a quoted table name is returned in exactly the same case, with quotes removed.
|
|
||||||
String camelCaseTableName = "\"DbTest\"";
|
String camelCaseTableName = "\"DbTest\"";
|
||||||
assertEquals(camelCaseTableName.substring(1, camelCaseTableName.length() - 1),
|
assertEquals(camelCaseTableName.substring(1, camelCaseTableName.length() - 1),
|
||||||
db.normalizeTableName(camelCaseTableName));
|
db.flavor().normalizeTableName(camelCaseTableName));
|
||||||
|
assertEquals(TEST_TABLE_NAME.toUpperCase(Locale.ENGLISH),
|
||||||
// Verify that the database flavor gets the expected normalized case
|
db.flavor().normalizeTableName(TEST_TABLE_NAME).toUpperCase(Locale.ENGLISH));
|
||||||
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
|
@Test
|
||||||
|
@ -144,12 +143,10 @@ public abstract class CommonTest {
|
||||||
.addColumn("bin_blob").asBlob().table()
|
.addColumn("bin_blob").asBlob().table()
|
||||||
.addColumn("date_millis").asLocalDateTime().table()
|
.addColumn("date_millis").asLocalDateTime().table()
|
||||||
.addColumn("local_date").asLocalDate().table().schema().execute(db);
|
.addColumn("local_date").asLocalDate().table().schema().execute(db);
|
||||||
|
|
||||||
BigDecimal bigDecimal = new BigDecimal("5.3");
|
BigDecimal bigDecimal = new BigDecimal("5.3");
|
||||||
db.toInsert("insert into dbtest values (?,?,?,?,?,?,?,?,?,?,?)").argInteger(1).argLong(2L).argFloat(3.2f).argDouble(4.2)
|
db.toInsert("insert into dbtest values (?,?,?,?,?,?,?,?,?,?,?)").argInteger(1).argLong(2L).argFloat(3.2f).argDouble(4.2)
|
||||||
.argBigDecimal(bigDecimal).argString("Hello").argString("T").argClobString("World")
|
.argBigDecimal(bigDecimal).argString("Hello").argString("T").argClobString("World")
|
||||||
.argBlobBytes("More".getBytes()).argLocalDateTime(now).argLocalDate(localDateNow).insert(1);
|
.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, "
|
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")
|
+ "bin_blob, date_millis, local_date from dbtest")
|
||||||
.query((RowsHandler<Void>) rs -> {
|
.query((RowsHandler<Void>) rs -> {
|
||||||
|
@ -594,20 +591,15 @@ public abstract class CommonTest {
|
||||||
new Schema()
|
new Schema()
|
||||||
.addTable("dbtest")
|
.addTable("dbtest")
|
||||||
.addColumn(timestampColumnName).asLocalDateTime().table()
|
.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 -> {
|
db.toSelect("select * from dbtest").query((RowsHandler<Void>) rs -> {
|
||||||
ResultSetMetaData metadata = rs.getMetadata();
|
ResultSetMetaData metadata = rs.getMetadata();
|
||||||
for (int i = 1; i <= metadata.getColumnCount(); i++) {
|
for (int i = 1; i <= metadata.getColumnCount(); i++) {
|
||||||
String columnName = metadata.getColumnName(i);
|
String columnName = metadata.getColumnName(i);
|
||||||
String columnType = metadata.getColumnTypeName(i);
|
String columnType = metadata.getColumnTypeName(i);
|
||||||
if (columnName.equalsIgnoreCase(timestampColumnName)) {
|
if (columnName.equalsIgnoreCase(timestampColumnName)) {
|
||||||
if ("sqlserver".equals(db.flavor().getName())) {
|
assertEquals(db.flavor().columnTypeLocalDateTime(), columnType);
|
||||||
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)) {
|
} else if (columnName.equalsIgnoreCase(dateColumnName)) {
|
||||||
assertEquals("DATE", columnType.toUpperCase());
|
assertEquals("DATE", columnType.toUpperCase());
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -31,3 +31,4 @@ include 'jdbc-test'
|
||||||
include 'jdbc-mariadb'
|
include 'jdbc-mariadb'
|
||||||
include 'jdbc-oracle'
|
include 'jdbc-oracle'
|
||||||
include 'jdbc-postgresql'
|
include 'jdbc-postgresql'
|
||||||
|
include 'jdbc-sqlserver'
|
||||||
|
|
Loading…
Reference in a new issue