change flavor from enum to interface, fix int/long bug in JDBC pool, add queue implementation

main
Jörg Prante 2 years ago
parent 7198e766c3
commit 1047be8456

@ -1,5 +1,5 @@
group = org.xbib
name = database
version = 0.0.5
version = 1.0.0
org.gradle.warning.mode = ALL

@ -20,4 +20,5 @@ test {
"${result.skippedTestCount} skipped"
}
}
systemProperty 'java.util.logging.config.file', 'src/test/resources/logging.properties'
}

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

@ -407,15 +407,17 @@ public class Pool implements BagStateListener {
private void initializeDataSource()
throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
String jdbcUrl = config.getProperties().getProperty("url");
String dsClassName = config.getDataSourceClassName();
String url = config.getUrl();
DataSource ds = config.getDataSource();
if (ds == null) {
String dsClassName = config.getDataSourceClassName();
if (dsClassName != null) {
Class<?> clazz = Class.forName(dsClassName, true, ClassLoader.getSystemClassLoader());
ds = (DataSource) clazz.getDeclaredConstructor().newInstance();
} else if (jdbcUrl != null) {
ds = new DriverDataSource(jdbcUrl, config.getDriverClassName(), config.getProperties(), config.getUsername(), config.getPassword());
} else if (url != null) {
ds = new DriverDataSource(url, config.getDriverClassName(), config.getProperties(), config.getUsername(), config.getPassword());
} else {
throw new IllegalStateException("no dataSource configured?");
}
}
setTargetFromProperties(ds, config.getProperties());

@ -56,7 +56,7 @@ public class PoolConfig {
private String driverClassName;
private String jdbcUrl;
private String url;
private String poolName;
@ -106,11 +106,18 @@ public class PoolConfig {
this.idleTimeout = IDLE_TIMEOUT;
this.initializationFailTimeout = -1;
this.isAutoCommit = true;
this.jdbcUrl = properties.getProperty("url");
this.aliveBypassWindowMs = TimeUnit.MILLISECONDS.toMillis(500);
this.housekeepingPeriodMs = TimeUnit.SECONDS.toMillis(30);
}
public void setUrl(String url) {
this.url = url;
}
public String getUrl() {
return url;
}
public String getCatalog() {
return catalog;
}
@ -588,7 +595,7 @@ public class PoolConfig {
transactionIsolationName = getNullIfEmpty(transactionIsolationName);
dataSourceClassName = getNullIfEmpty(dataSourceClassName);
driverClassName = getNullIfEmpty(driverClassName);
jdbcUrl = getNullIfEmpty(jdbcUrl);
url = getNullIfEmpty(url);
if (dataSource != null) {
if (dataSourceClassName != null) {
logger.log(Level.WARNING, "using dataSource and ignoring dataSourceClassName: " + poolName);
@ -597,17 +604,17 @@ public class PoolConfig {
if (driverClassName != null) {
logger.log(Level.SEVERE, "cannot use driverClassName and dataSourceClassName together: " + poolName);
throw new IllegalStateException("cannot use driverClassName and dataSourceClassName together.");
} else if (jdbcUrl != null) {
logger.log(Level.WARNING, "using dataSourceClassName and ignoring jdbcUrl: " + poolName);
} else if (url != null) {
logger.log(Level.WARNING, "using dataSourceClassName and ignoring url: " + poolName);
}
} else if (jdbcUrl != null) {
} else if (url != null) {
// ok
} else if (driverClassName != null) {
logger.log(Level.SEVERE, "jdbcUrl is required with driverClassName: " + poolName);
throw new IllegalArgumentException("jdbcUrl is required with driverClassName.");
logger.log(Level.SEVERE, "url is required with driverClassName: " + poolName);
throw new IllegalArgumentException("url is required with driverClassName");
} else {
logger.log(Level.SEVERE, "dataSource or dataSourceClassName or jdbcUrl is required: " + poolName);
throw new IllegalArgumentException("dataSource or dataSourceClassName or jdbcUrl is required.");
logger.log(Level.SEVERE, "dataSource or dataSourceClassName or url is required: " + poolName);
throw new IllegalArgumentException("dataSource or dataSourceClassName or url is required");
}
validateNumerics();
}

@ -481,7 +481,7 @@ public class ProxyResultSet implements ResultSet {
@Override
public long getLong(int columnIndex) throws SQLException {
return delegate.getInt(columnIndex);
return delegate.getLong(columnIndex);
}
@Override

@ -136,12 +136,21 @@ public class DriverDataSource implements DataSource {
@Override
public void setLoginTimeout(int seconds) {
DriverManager.setLoginTimeout(seconds);
try {
DriverManager.setLoginTimeout(seconds);
} catch (Exception e) {
logger.log(Level.WARNING, "setLoginTimeout failed");
}
}
@Override
public int getLoginTimeout() {
return DriverManager.getLoginTimeout();
try {
return DriverManager.getLoginTimeout();
} catch (Exception e) {
logger.log(Level.WARNING, "getLoginTimeout failed");
return 0;
}
}
@Override

@ -63,7 +63,6 @@ public class BagTest {
bag.remove(inuse);
bag.remove(inuse);
assertTrue(bag.getLastMessage().contains("not borrowed or reserved"));
bag.close();
try {
PoolEntry bagEntry = pool.newPoolEntry();
bag.add(bagEntry);

@ -29,12 +29,11 @@ public class ConnectionStateTest {
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
try (Connection connection = ds.getConnection()) {
Connection unwrap = connection.unwrap(Connection.class);
unwrap.setAutoCommit(false);
connection.close();
assertFalse(unwrap.getAutoCommit());
}
Connection connection = ds.getConnection();
Connection unwrap = connection.unwrap(Connection.class);
unwrap.setAutoCommit(false);
connection.close();
assertFalse(unwrap.getAutoCommit());
}
}
@ -48,12 +47,11 @@ public class ConnectionStateTest {
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
try (Connection connection = ds.getConnection()) {
Connection unwrap = connection.unwrap(Connection.class);
unwrap.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
connection.close();
assertEquals(Connection.TRANSACTION_READ_UNCOMMITTED, unwrap.getTransactionIsolation());
}
Connection connection = ds.getConnection();
Connection unwrap = connection.unwrap(Connection.class);
unwrap.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
connection.close();
assertEquals(Connection.TRANSACTION_READ_UNCOMMITTED, unwrap.getTransactionIsolation());
}
}
@ -76,12 +74,11 @@ public class ConnectionStateTest {
config.setConnectionTestQuery("VALUES 1");
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config)) {
try (Connection connection = ds.getConnection()) {
Connection unwrap = connection.unwrap(Connection.class);
connection.setReadOnly(true);
connection.close();
assertFalse(unwrap.isReadOnly());
}
Connection connection = ds.getConnection();
Connection unwrap = connection.unwrap(Connection.class);
connection.setReadOnly(true);
connection.close();
assertFalse(unwrap.isReadOnly());
}
}

@ -453,6 +453,7 @@ public class ConnectionTest {
config.setDataSource(stubDataSource);
try (PoolDataSource ds = new PoolDataSource(config);
Connection ignored = ds.getConnection()) {
assertNotNull(ignored);
fail("Initialization should have failed");
} catch (SQLTransientConnectionException e) {
// passed
@ -472,6 +473,7 @@ public class ConnectionTest {
config.setDataSource(stubDataSource);
try (PoolDataSource ds = new PoolDataSource(config)) {
try (Connection ignored = ds.getConnection()) {
assertNotNull(ignored);
stubDataSource.setErrorOnConnection(true);
fail("SQLException should occur!");
} catch (Exception e) {
@ -520,6 +522,7 @@ public class ConnectionTest {
config.setDataSourceClassName("org.xbib.io.pool.jdbc.mock.StubDataSource");
try (PoolDataSource ds = new PoolDataSource(config);
Connection ignored = ds.getConnection()) {
assertNotNull(ignored);
// passed
} catch (SQLTransientConnectionException sqle) {
fail("Failed to obtain connection");

@ -1,5 +1,6 @@
package org.xbib.io.pool.jdbc;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.AfterEach;
@ -38,6 +39,7 @@ public class JdbcDriverTest {
assertNotNull(unwrap);
try (Connection connection = ds.getConnection()) {
// test that getConnection() succeeds
assertFalse(connection.isClosed());
}
}

@ -1,6 +1,7 @@
dependencies {
api project(":jdbc-connection-pool")
testImplementation libs.derby
testImplementation libs.hsqldb
testImplementation libs.testcontainers
testImplementation libs.testcontainers.junit.jupiter
testImplementation libs.testcontainers.oracle.xe

@ -1,4 +1,13 @@
import org.xbib.jdbc.query.Flavor;
import org.xbib.jdbc.query.flavor.Derby;
import org.xbib.jdbc.query.flavor.Hsql;
import org.xbib.jdbc.query.flavor.Oracle;
import org.xbib.jdbc.query.flavor.Postgresql;
import org.xbib.jdbc.query.flavor.SqlServer;
module org.xbib.jdbc.query {
uses Flavor;
requires transitive org.xbib.jdbc.connection.pool;
exports org.xbib.jdbc.query;
provides Flavor with Derby, Hsql, Oracle, Postgresql, SqlServer;
}

@ -1,6 +1,6 @@
package org.xbib.jdbc.query;
import java.io.File;
import java.io.InputStream;
import java.nio.charset.CharsetDecoder;
import java.util.Properties;
import java.util.function.Function;
@ -47,7 +47,7 @@ public interface ConfigFrom extends Supplier<Config> {
* defaultPropertyFiles("properties", "conf/app.properties", "local.properties", "sample.properties")
* </pre>
*/
ConfigFrom defaultPropertyFiles();
ConfigFrom defaultProperties();
/**
* Adds a set of properties files to read from, which can be overridden by a specified system property.
@ -56,7 +56,7 @@ public interface ConfigFrom extends Supplier<Config> {
* defaultPropertyFiles(systemPropertyKey, Charset.defaultCharset().newDecoder(), filenames)
* </pre>
*/
ConfigFrom defaultPropertyFiles(String systemPropertyKey, String... filenames);
ConfigFrom defaultProperties(String systemPropertyKey, String... filenames);
/**
* Adds a set of properties files to read from, which can be overridden by a specified system property.
@ -67,15 +67,15 @@ public interface ConfigFrom extends Supplier<Config> {
* .split(File.pathSeparator));
* </pre>
*/
ConfigFrom defaultPropertyFiles(String systemPropertyKey, CharsetDecoder decoder, String... filenames);
ConfigFrom defaultProperties(String systemPropertyKey, CharsetDecoder decoder, String... filenames);
ConfigFrom propertyFile(String... filenames);
ConfigFrom properties(String... filenames);
ConfigFrom propertyFile(CharsetDecoder decoder, String... filenames);
ConfigFrom properties(CharsetDecoder decoder, String... filenames);
ConfigFrom propertyFile(File... files);
ConfigFrom properties(InputStream... inputStreams);
ConfigFrom propertyFile(CharsetDecoder decoder, File... files);
ConfigFrom properties(CharsetDecoder decoder, InputStream... inputStreams);
ConfigFrom rename(String key, String newKey);

@ -1,16 +1,22 @@
package org.xbib.jdbc.query;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -20,6 +26,10 @@ import java.util.regex.Pattern;
*/
public class ConfigFromImpl implements ConfigFrom {
private static final Logger logger = Logger.getLogger(ConfigFromImpl.class.getName());
private static final String SEPARATOR = FileSystems.getDefault().getSeparator();
private final List<Config> searchPath = new ArrayList<>();
public ConfigFromImpl() {
@ -72,64 +82,59 @@ public class ConfigFromImpl implements ConfigFrom {
}
@Override
public ConfigFrom defaultPropertyFiles() {
return defaultPropertyFiles("properties", "conf/app.properties", "local.properties", "sample.properties");
public ConfigFrom defaultProperties() {
return defaultProperties("properties", "conf/app.properties", "local.properties", "sample.properties");
}
@Override
public ConfigFrom defaultPropertyFiles(String systemPropertyKey, String... filenames) {
return defaultPropertyFiles(systemPropertyKey, Charset.defaultCharset().newDecoder(), filenames);
public ConfigFrom defaultProperties(String systemPropertyKey, String... filenames) {
return defaultProperties(systemPropertyKey, Charset.defaultCharset().newDecoder(), filenames);
}
@Override
public ConfigFrom defaultPropertyFiles(String systemPropertyKey, CharsetDecoder decoder, String... filenames) {
String properties = System.getProperty(systemPropertyKey, String.join(File.pathSeparator, filenames));
return propertyFile(Charset.defaultCharset().newDecoder(), properties.split(File.pathSeparator));
public ConfigFrom defaultProperties(String systemPropertyKey, CharsetDecoder decoder, String... filenames) {
String properties = System.getProperty(systemPropertyKey, String.join(SEPARATOR, filenames));
return properties(Charset.defaultCharset().newDecoder(), properties.split(SEPARATOR));
}
@Override
public ConfigFrom propertyFile(String... filenames) {
return propertyFile(Charset.defaultCharset().newDecoder(), filenames);
public ConfigFrom properties(String... filenames) {
return properties(Charset.defaultCharset().newDecoder(), filenames);
}
@Override
public ConfigFrom propertyFile(CharsetDecoder decoder, String... filenames) {
public ConfigFrom properties(CharsetDecoder decoder, String... filenames) {
for (String filename : filenames) {
if (filename != null) {
propertyFile(decoder, new File(filename));
Path path = Paths.get(filename);
if (Files.exists(path)) {
try (InputStream inputStream = Files.newInputStream(path)) {
properties(decoder, inputStream);
} catch (IOException e) {
custom(k -> null, "Ignored: propertyFile(" + filename + ") " + e.getClass().getSimpleName());
}
} else {
properties(decoder, getClass().getResourceAsStream(filename));
}
}
}
return this;
}
@Override
public ConfigFrom propertyFile(File... files) {
return propertyFile(Charset.defaultCharset().newDecoder(), files);
public ConfigFrom properties(InputStream... inputStreams) {
return properties(Charset.defaultCharset().newDecoder(), inputStreams);
}
@Override
public ConfigFrom propertyFile(CharsetDecoder decoder, File... files) {
for (File file : files) {
if (file != null) {
try {
Properties properties = new Properties();
try (
FileInputStream fis = new FileInputStream(file);
InputStreamReader reader = new InputStreamReader(fis, decoder)
) {
properties.load(reader);
}
searchPath.add(new ConfigImpl(properties::getProperty, "propertyFile(" + file.getAbsolutePath() + ")"));
} catch (Exception e) {
// Put a "fake" provider in so we can see it failed
String fileName = file.getName();
try {
fileName = file.getAbsolutePath();
} catch (Exception ignored) {
// Fall back to relative name
}
custom(k -> null, "Ignored: propertyFile(" + fileName + ") " + e.getClass().getSimpleName());
}
public ConfigFrom properties(CharsetDecoder decoder, InputStream... inputStreams) {
for (InputStream inputStream : inputStreams) {
Properties properties = new Properties();
try (InputStreamReader reader = new InputStreamReader(inputStream, decoder)) {
properties.load(reader);
searchPath.add(new ConfigImpl(properties::getProperty, "propertyFile(" + inputStream + ")"));
} catch (Exception e) {
custom(k -> null, "Ignored: propertyFile(" + inputStream + ") " + e.getClass().getSimpleName());
}
}
return this;

@ -169,7 +169,6 @@ public class ConfigImpl implements Config {
return null;
}
}
@Override
public BigDecimal getBigDecimal(String key, BigDecimal defaultValue) {
@ -177,7 +176,6 @@ public class ConfigImpl implements Config {
return value == null ? defaultValue : value;
}
@Override
public BigDecimal getBigDecimalOrThrow(String key) {
return nonnull(key, getBigDecimal(key));

@ -1,8 +1,10 @@
package org.xbib.jdbc.query;
import java.sql.Connection;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;
/**
@ -216,4 +218,27 @@ public interface Database extends Supplier<Database> {
* with millisToWarn=10000 and millisToError=30000.
*/
void assertTimeSynchronized();
/**
* Write data into a queue table, select a channel, and consume the returned primary keys.
*
* @param table the queue table
* @param channel the queue table channel
* @param data the data for the queue table
* @param consumer the consumer for the primary keys
* @throws SQLException if writing fails
*/
void writeQueue(String table, String channel, String data, Consumer<Long> consumer) throws SQLException;
/**
* Read from a queue table and lock rows in a transaction until processing by a consumer
* has succeeded or failed.
* Commit only if processing was successful, otherwise roll back the transaction.
* The connection is switched into manual commit and afterwards, auto-commit is enabled again.
* @param table the queue table
* @param channel the queue table channel. A queue table can have many channels
* @param consumer a consumer for the queue table data column or null
* @throws SQLException if rollback fails
*/
public void readQueue(String table, String channel, Consumer<String> consumer) throws SQLException;
}

@ -1,24 +1,27 @@
package org.xbib.jdbc.query;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.logging.Logger;
/**
* Primary class for accessing a relational (SQL) database.
* Primary class for accessing a relational database.
*/
public class DatabaseImpl implements Database {
private static final Logger log = Logger.getLogger(Database.class.getName());
private static final Logger logger = Logger.getLogger(Database.class.getName());
private final Connection connection;
@ -88,7 +91,7 @@ public class DatabaseImpl implements Database {
}
@Override
public Long nextSequenceValue(/*@Untainted*/ String sequenceName) {
public Long nextSequenceValue(String sequenceName) {
return toSelect(flavor().sequenceSelectNextVal(sequenceName)).queryLongOrNull();
}
@ -97,9 +100,10 @@ public class DatabaseImpl implements Database {
return options.currentDate();
}
@Override
public void commitNow() {
if (options.ignoreTransactionControl()) {
log.fine("Ignoring call to commitNow()");
logger.fine("Ignoring call to commitNow()");
return;
}
if (!options.allowTransactionControl()) {
@ -112,9 +116,10 @@ public class DatabaseImpl implements Database {
}
}
@Override
public void rollbackNow() {
if (options.ignoreTransactionControl()) {
log.fine("Ignoring call to rollbackNow()");
logger.fine("Ignoring call to rollbackNow()");
return;
}
if (!options.allowTransactionControl()) {
@ -153,53 +158,22 @@ public class DatabaseImpl implements Database {
}
@Override
public void dropSequenceQuietly(/*@Untainted*/ String sequenceName) {
public void dropSequenceQuietly(String sequenceName) {
ddl(flavor().sequenceDrop(sequenceName)).executeQuietly();
}
@Override
public void dropTableQuietly(/*@Untainted*/ String tableName) {
if (flavor() == Flavor.postgresql || flavor() == Flavor.hsqldb) {
ddl("drop table if exists " + tableName).executeQuietly();
} else {
ddl("drop table " + tableName).executeQuietly();
}
public void dropTableQuietly(String tableName) {
ddl(flavor().tableDrop(tableName)).executeQuietly();
}
@Override
public boolean tableExists(String tableName) throws DatabaseException {
String schemaName = null;
Method getSchema = null;
try {
// Use reflections to see if connection.getSchema API exists. It should exist for any JDBC7 or later implementation
// We still support Oracle 11 with odbc6, however, so we can't assume it's there.
getSchema = connection.getClass().getDeclaredMethod("getSchema");
} catch (NoSuchMethodException noMethodExc) {
// Expected if method does not exist - just let it go
}
try {
if (getSchema != null) {
schemaName = ((String) getSchema.invoke(connection, new Object[0]));
} else if (flavor() == Flavor.oracle) {
// Oracle defaults to user name schema - use that.
log.warning("Connection getSchema API was not found. Defaulting to Oracle user name schema." +
"If this is not appropriate, please use tableExists(tableName, schemaName) API or upgrade to ojdbc7 or later");
schemaName = connection.getMetaData().getUserName();
}
if (schemaName == null) {
// connection.getSchema API was supported starting at JDK1.7. Method should not be null.
throw new NullPointerException("Unable to retrieve schema name.");
}
} catch (Exception exc) {
throw new DatabaseException("Unable to determine the schema. " +
"Please use tableExists(tableName, schemaName API) or upgrade to a JDBC7 driver or later.", exc);
return tableExists(tableName, connection.getSchema());
} catch (SQLException e) {
throw new DatabaseException("Unable to getSchema()", e);
}
return tableExists(tableName, schemaName);
}
@Override
@ -230,24 +204,24 @@ public class DatabaseImpl implements Database {
try {
DatabaseMetaData metaData = connection.getMetaData();
String normalizedTable = normalizeTableName(tableName);
ResultSet rs = metaData.getColumns(null, null, normalizedTable, "%");
ResultSetMetaData rsmd = rs.getMetaData();
ResultSet resultSet = metaData.getColumns(null, null, normalizedTable, "%");
ResultSetMetaData rsmd = resultSet.getMetaData();
int cols = rsmd.getColumnCount();
while (rs.next()) {
while (resultSet.next()) {
String name = "";
int size = 0;
for (int i = 1; i <= cols; i++) {
String label = rsmd.getColumnName(i);
if ("COLUMN_NAME".equalsIgnoreCase(label)) {
name = rs.getString(i);
name = resultSet.getString(i);
}
if ("COLUMN_SIZE".equalsIgnoreCase(label)) {
size = rs.getBigDecimal(i).intValue();
size = resultSet.getBigDecimal(i).intValue();
}
}
map.put(name, size);
}
rs.close();
resultSet.close();
} catch (SQLException exc) {
throw new DatabaseException("Unable to look up table " + tableName + " : " + exc.getMessage(), exc);
}
@ -260,7 +234,6 @@ public class DatabaseImpl implements Database {
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("\"")) {
@ -268,11 +241,9 @@ public class DatabaseImpl implements Database {
return tableName.substring(1, tableName.length() - 1);
}
}
if (flavor().isNormalizedUpperCase()) {
return tableName.toUpperCase();
}
return tableName.toLowerCase();
}
@ -288,18 +259,18 @@ public class DatabaseImpl implements Database {
Duration duration = Duration.between(appDate, dbDate).abs();
if (duration.getSeconds() > 3600) {
throw new DatabaseException("App and db time are over an hour apart (check your timezones) app: "
+ DateTimeFormatter.ISO_INSTANT.format(appDate) + " db: "
+ DateTimeFormatter.ISO_INSTANT.format(dbDate));
+ appDate + " db: "
+ dbDate);
}
if (duration.getSeconds() * 1000 > millisToError) {
throw new DatabaseException("App and db time over " + millisToError + " millis apart (check your clocks) app: "
+ DateTimeFormatter.ISO_INSTANT.format(appDate) + " db: "
+ DateTimeFormatter.ISO_INSTANT.format(dbDate));
+ appDate + " db: "
+ dbDate);
}
if (duration.getSeconds() * 1000 > millisToWarn) {
log.warning("App and db time are over " + millisToWarn + " millis apart (check your clocks) app: "
+ DateTimeFormatter.ISO_INSTANT.format(appDate) + " db: "
+ DateTimeFormatter.ISO_INSTANT.format(dbDate));
logger.warning("App and db time are over " + millisToWarn + " millis apart (check your clocks) app: "
+ appDate + " db: "
+ dbDate);
}
return null;
});
@ -309,4 +280,83 @@ public class DatabaseImpl implements Database {
public void assertTimeSynchronized() {
assertTimeSynchronized(10000, 30000);
}
@Override
public void writeQueue(String table, String channel, String data, Consumer<Long> consumer) throws SQLException {
writeNextIntoQueue(connection, table, channel, data, consumer);
}
@Override
public void readQueue(String table, String channel, Consumer<String> consumer) throws SQLException {
try {
connection.setAutoCommit(false);
while (true) {
try (ResultSet resultSet = readNextFromQueue(connection, table, channel)) {
if (resultSet.next()) {
Long key = resultSet.getLong("key");
try {
if (consumer != null) {
consumer.accept(resultSet.getString("data"));
}
succeedInQueue(connection, table, key);
} catch (Exception e) {
failInQueue(connection, table, key);
throw e;
}
}
} finally {
connection.commit();
}
}
} catch (Exception e) {
connection.rollback();
throw e;
} finally {
connection.setAutoCommit(true);
}
}
private void writeNextIntoQueue(Connection connection,
String table,
String channel,
String data,
Consumer<Long> consumer) throws SQLException {
try (PreparedStatement preparedStatement = connection.prepareStatement(flavor().writeNextIntoQueue(table),
Statement.RETURN_GENERATED_KEYS)) {
preparedStatement.setString(1, channel);
preparedStatement.setString(2, data);
preparedStatement.setTimestamp(3, Timestamp.valueOf(LocalDateTime.now()));
int rows = preparedStatement.executeUpdate();
if (rows > 0) {
try (ResultSet resultSet = preparedStatement.getGeneratedKeys()) {
if (resultSet.next()) {
if (consumer != null) {
consumer.accept(resultSet.getLong(1));
}
}
}
}
}
}
private ResultSet readNextFromQueue(Connection connection, String table, String channel) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement(flavor().readNextFromQueue(table));
preparedStatement.setString(1, channel);
preparedStatement.setTimestamp(2, Timestamp.valueOf(LocalDateTime.now()));
return preparedStatement.executeQuery();
}
private void succeedInQueue(Connection connection, String table, Long key) throws SQLException {
try (PreparedStatement preparedStatement = connection.prepareStatement(flavor().succeedInQueue(table))) {
preparedStatement.setLong(1, key);
preparedStatement.executeUpdate();
}
}
private void failInQueue(Connection connection, String table, Long key) throws SQLException {
try (PreparedStatement preparedStatement = connection.prepareStatement(flavor().succeedInQueue(table))) {
preparedStatement.setLong(1, key);
preparedStatement.executeUpdate();
}
}
}

@ -67,8 +67,7 @@ public final class DatabaseProvider implements Supplier<Database> {
* database.pool.size=... How many connections in the connection pool (default 10).
* database.driver.class The driver to initialize with Class.forName(). This will
* be guessed from the database.url if not provided.
* database.flavor One of the enumerated values in {@link Flavor}. If this
* is not provided the flavor will be guessed based on the
* database.flavor A {@link Flavor}. If this is not provided the flavor will be guessed based on the
* value for database.url, if possible.
* </pre>
*
@ -167,10 +166,12 @@ public final class DatabaseProvider implements Supplier<Database> {
try {
DriverManager.getDriver(url);
} catch (SQLException e) {
try {
Class.forName(Flavor.driverForJdbcUrl(url));
} catch (ClassNotFoundException e1) {
throw new DatabaseException("couldn't locate JDBC driver - try setting -Djdbc.drivers=some.Driver", e1);
if (flavor.driverClass() != null) {
try {
Class.forName(flavor.driverClass());
} catch (ClassNotFoundException e1) {
throw new DatabaseException("couldn't locate JDBC driver", e1);
}
}
}
return new BuilderImpl(null, () -> {
@ -531,7 +532,6 @@ public final class DatabaseProvider implements Supplier<Database> {
if (propertyPrefix == null) {
propertyPrefix = "";
}
String driver;
String flavorStr;
String url;
@ -561,11 +561,9 @@ public final class DatabaseProvider implements Supplier<Database> {
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];
@ -576,28 +574,22 @@ public final class DatabaseProvider implements Supplier<Database> {
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) {
if (flavor == Flavor.oracle) {
driver = "oracle.jdbc.OracleDriver";
} else if (flavor == Flavor.postgresql) {
driver = "org.postgresql.Driver";
} else if (flavor == Flavor.derby) {
driver = "org.apache.derby.jdbc.EmbeddedDriver";
}
}
if (driver != null) {
try {
Class.forName(driver).getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new DatabaseException("Unable to load JDBC driver: " + driver, 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) {
@ -616,7 +608,12 @@ public final class DatabaseProvider implements Supplier<Database> {
}
PoolConfig poolConfig = new PoolConfig();
poolConfig.setPoolName(config.getString("database.pool.name", "database-pool-" + poolNameCounter.getAndAdd(1)));
poolConfig.setDriverClassName(config.getString("database.driver.class", Flavor.driverForJdbcUrl(url)));
poolConfig.setUrl(config.getString("database.url"));
// provide driver class only if in config
String driverClassName = config.getString("database.driver.class");
if (driverClassName != null) {
poolConfig.setDriverClassName(config.getString("database.driver.class"));
}
poolConfig.setUsername(config.getString("database.user"));
poolConfig.setPassword(config.getString("database.password"));
poolConfig.setMaximumPoolSize(config.getInteger("database.pool.size", 8));
@ -774,7 +771,7 @@ public final class DatabaseProvider implements Supplier<Database> {
try {
// JDBC specifies that autoCommit is the default for all new connections.
// Don't try to be clever about clearing it conditionally.
if (!options.flavor().autoCommitOnly()) {
if (!options.flavor().isAutoCommitOnly()) {
connection.setAutoCommit(false);
metric.checkpoint("setAutoCommit");
}
@ -868,7 +865,7 @@ public final class DatabaseProvider implements Supplier<Database> {
}
if (connection != null) {
try {
if (!options.flavor().autoCommitOnly()) {
if (!options.flavor().isAutoCommitOnly()) {
connection.commit();
}
} catch (Exception e) {
@ -884,7 +881,7 @@ public final class DatabaseProvider implements Supplier<Database> {
}
if (connection != null) {
try {
if (!options.flavor().autoCommitOnly()) {
if (!options.flavor().isAutoCommitOnly()) {
connection.commit();
}
} catch (Exception e) {
@ -902,7 +899,7 @@ public final class DatabaseProvider implements Supplier<Database> {
if (connection != null) {
try {
if (!options.flavor().autoCommitOnly()) {
if (!options.flavor().isAutoCommitOnly()) {
connection.rollback();
}
} catch (Exception e) {
@ -919,7 +916,7 @@ public final class DatabaseProvider implements Supplier<Database> {
if (connection != null) {
try {
if (!options.flavor().autoCommitOnly()) {
if (!options.flavor().isAutoCommitOnly()) {
connection.rollback();
}
} catch (Exception e) {

File diff suppressed because it is too large Load Diff

@ -114,9 +114,6 @@ public interface Options {
*
* <p>It is strongly recommended to always run your database in GMT timezone, and
* leave this set to the default.</p>
*
* <p>Behavior in releases 1.3 and prior was to use the JVM default TimeZone, and
* this was not configurable.</p>
*/
Calendar calendarForTimestamps();

@ -1,5 +1,7 @@
package org.xbib.jdbc.query;
import org.xbib.jdbc.query.flavor.Postgresql;
import java.time.LocalDateTime;
import java.util.Calendar;
import java.util.Date;
@ -23,7 +25,7 @@ public class OptionsOverride implements Options {
* Defer to OptionsDefault for anything that is not specified, and use postgresql flavor.
*/
public OptionsOverride() {
parent = new OptionsDefault(Flavor.postgresql);
parent = new OptionsDefault(new Postgresql());
}
/**

@ -5,6 +5,8 @@ import org.xbib.jdbc.query.Schema.Table.Column;
import org.xbib.jdbc.query.Schema.Table.ForeignKey;
import org.xbib.jdbc.query.Schema.Table.Index;
import org.xbib.jdbc.query.Schema.Table.Unique;
import org.xbib.jdbc.query.flavor.Oracle;
import org.xbib.jdbc.query.flavor.Postgresql;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
@ -274,15 +276,13 @@ public class Schema {
sql.append(check.expression);
sql.append(")");
}
sql.append("\n)");
if (table.customClauses.containsKey(flavor)) {
sql.append(" ").append(table.customClauses.get(flavor));
}
executeOrPrint(sql, db, script);
sql = new Sql();
if (flavor == Flavor.oracle || flavor == Flavor.postgresql) {
if (flavor instanceof Oracle || flavor instanceof Postgresql) {
if (table.comment != null) {
sql.append("comment on table ");
sql.append(table.name);
@ -292,7 +292,6 @@ public class Schema {
executeOrPrint(sql, db, script);
sql = new Sql();
}
for (Column c : table.columns) {
if (c.comment != null) {
sql.append("comment on column ");
@ -308,7 +307,6 @@ public class Schema {
}
}
}
for (Table table : tables) {
for (ForeignKey fk : table.foreignKeys) {
Sql sql = new Sql();
@ -329,7 +327,6 @@ public class Schema {
executeOrPrint(sql, db, script);
}
}
for (Table table : tables) {
for (Index index : table.indexes) {
Sql sql = new Sql();

@ -22,18 +22,17 @@ public class Sql implements SqlInsert.Apply, SqlUpdate.Apply, SqlSelect.Apply {
public Sql() {
}
public Sql(/*@Untainted*/ String sql) {
public Sql(String sql) {
this.sql.append(sql);
}
public static Sql insert(/*@Untainted*/ String table, SqlArgs args) {
public static Sql insert(String table, SqlArgs args) {
return insert(table, Collections.singletonList(args));
}
public static Sql insert(/*@Untainted*/ String table, List<SqlArgs> args) {
public static Sql insert(String table, List<SqlArgs> args) {
Sql sql = null;
List<String> expectedColumns = null;
for (SqlArgs arg : args) {
if (arg.positionalCount() > 0) {
throw new DatabaseException("The SqlArgs must all be named to do this");
@ -98,7 +97,7 @@ public class Sql implements SqlInsert.Apply, SqlUpdate.Apply, SqlSelect.Apply {
return this;
}
public Sql append(/*@Untainted*/ String sql) {
public Sql append(String sql) {
this.sql.append(sql);
return this;
}
@ -133,12 +132,12 @@ public class Sql implements SqlInsert.Apply, SqlUpdate.Apply, SqlSelect.Apply {
return this;
}
public Sql replace(int start, int end, /*@Untainted*/ String str) {
public Sql replace(int start, int end, String str) {
this.sql.replace(start, end, str);
return this;
}
public Sql insert(int offset, /*@Untainted*/ String str) {
public Sql insert(int offset, String str) {
this.sql.insert(offset, str);
return this;
}
@ -189,7 +188,7 @@ public class Sql implements SqlInsert.Apply, SqlUpdate.Apply, SqlSelect.Apply {
* <p>
* Each list started must have be ended. "Lists" are only to support using listSeparator(sep)
*/
public Sql listStart(/*@Untainted*/ String sql) {
public Sql listStart(String sql) {
listFirstItem.push(true);
return append(sql);
}
@ -198,7 +197,7 @@ public class Sql implements SqlInsert.Apply, SqlUpdate.Apply, SqlSelect.Apply {
* Appends the passed bit of sql only if a previous item has already been appended,
* and notes that the list is not empty.
*/
public Sql listSeparator(/*@Untainted*/ String sql) {
public Sql listSeparator(String sql) {
if (listFirstItem.peek()) {
listFirstItem.pop();
listFirstItem.push(false);
@ -209,12 +208,11 @@ public class Sql implements SqlInsert.Apply, SqlUpdate.Apply, SqlSelect.Apply {
}
public Sql listEnd(/*@Untainted*/ String sql) {
public Sql listEnd(String sql) {
listFirstItem.pop();
return append(sql);
}
/*@Untainted*/
public String sql() {
return sql.toString();
}
@ -222,7 +220,6 @@ public class Sql implements SqlInsert.Apply, SqlUpdate.Apply, SqlSelect.Apply {
/**
* Same as sql(), provided for drop-in compatibility with StringBuilder.
*/
/*@Untainted*/
public String toString() {
return sql();
}
@ -231,81 +228,68 @@ public class Sql implements SqlInsert.Apply, SqlUpdate.Apply, SqlSelect.Apply {
sqlArgs.argBoolean(arg);
return this;
}
public Sql argBoolean( String argName, Boolean arg) {
sqlArgs.argBoolean(argName, arg);
return this;
}
public Sql argInteger(Integer arg) {
sqlArgs.argInteger(arg);
return this;
}
public Sql argInteger( String argName, Integer arg) {
sqlArgs.argInteger(argName, arg);
return this;
}
public Sql argLong(Long arg) {
sqlArgs.argLong(arg);
return this;
}
public Sql argLong(String argName, Long arg) {
sqlArgs.argLong(argName, arg);
return this;
}
public Sql argFloat(Float arg) {
sqlArgs.argFloat(arg);
return this;
}
public Sql argFloat( String argName, Float arg) {
sqlArgs.argFloat(argName, arg);
return this;
}
public Sql argDouble(Double arg) {
sqlArgs.argDouble(arg);
return this;
}
public Sql argDouble( String argName, Double arg) {
sqlArgs.argDouble(argName, arg);
return this;
}
public Sql argBigDecimal(BigDecimal arg) {
sqlArgs.argBigDecimal(arg);
return this;
}
public Sql argBigDecimal( String argName, BigDecimal arg) {
sqlArgs.argBigDecimal(argName, arg);
return this;
}
public Sql argString(String arg) {
sqlArgs.argString(arg);
return this;
}
public Sql argString( String argName, String arg) {
public Sql argString(String argName, String arg) {
sqlArgs.argString(argName, arg);
return this;
}
@ -314,33 +298,28 @@ public class Sql implements SqlInsert.Apply, SqlUpdate.Apply, SqlSelect.Apply {
sqlArgs.argDate(arg);
return this;
}
public Sql argDate( String argName, LocalDateTime arg) {
public Sql argDate(String argName, LocalDateTime arg) {
sqlArgs.argDate(argName, arg);
return this;
}
public Sql argDateNowPerApp() {
sqlArgs.argDateNowPerApp();
return this;
}
public Sql argDateNowPerApp( String argName) {
public Sql argDateNowPerApp(String argName) {
sqlArgs.argDateNowPerApp(argName);
return this;
}
public Sql argDateNowPerDb() {
sqlArgs.argDateNowPerDb();
return this;
}
public Sql argDateNowPerDb( String argName) {
public Sql argDateNowPerDb(String argName) {
sqlArgs.argDateNowPerDb(argName);
return this;
}
@ -352,35 +331,30 @@ public class Sql implements SqlInsert.Apply, SqlUpdate.Apply, SqlSelect.Apply {
}
public Sql argBlobBytes( String argName, byte[] arg) {
public Sql argBlobBytes(String argName, byte[] arg) {
sqlArgs.argBlobBytes(argName, arg);
return this;
}
public Sql argBlobInputStream(InputStream arg) {
sqlArgs.argBlobInputStream(arg);
return this;
}
public Sql argBlobInputStream( String argName, InputStream arg) {
sqlArgs.argBlobInputStream(argName, arg);
return this;
}
public Sql argClobString(String arg) {
sqlArgs.argClobString(arg);
return this;
}
public Sql argClobString( String argName, String arg) {
sqlArgs.argClobString(argName, arg);
return this;
}
public Sql argClobReader(Reader arg) {
sqlArgs.argClobReader(arg);
@ -419,7 +393,6 @@ public class Sql implements SqlInsert.Apply, SqlUpdate.Apply, SqlSelect.Apply {
if (batched != null) {
throw new DatabaseException("Batch not supported for update");
}
sqlArgs.apply(update);
}
@ -427,16 +400,4 @@ public class Sql implements SqlInsert.Apply, SqlUpdate.Apply, SqlSelect.Apply {
Integer, Long, Float, Double, BigDecimal, String, ClobString, ClobStream,
BlobBytes, BlobStream, Date, DateNowPerApp, DateNowPerDb, Boolean
}
private static class Invocation {
ColumnType columnType;
String argName;
Object arg;
Invocation(ColumnType columnType, String argName, Object arg) {
this.columnType = columnType;
this.argName = argName;
this.arg = arg;
}
}
}

@ -36,7 +36,6 @@ public class SqlArgs implements SqlInsert.Apply, SqlUpdate.Apply, SqlSelect.Appl
* @return a SqlArgs with one invocation for each column in the Row, with name
* and type inferred from the metadata
*/
public static SqlArgs readRow(Row r) {
return new Builder(r).read(r);
}
@ -118,44 +117,37 @@ public class SqlArgs implements SqlInsert.Apply, SqlUpdate.Apply, SqlSelect.Appl
invocations.add(new Invocation(ColumnType.Double, null, arg));
return this;
}
public SqlArgs argDouble( String argName, Double arg) {
invocations.add(new Invocation(ColumnType.Double, argName, arg));
return this;
}
public SqlArgs argBigDecimal(BigDecimal arg) {
invocations.add(new Invocation(ColumnType.BigDecimal, null, arg));
return this;
}
public SqlArgs argBigDecimal( String argName, BigDecimal arg) {
invocations.add(new Invocation(ColumnType.BigDecimal, argName, arg));
return this;
}
public SqlArgs argString(String arg) {
invocations.add(new Invocation(ColumnType.String, null, arg));
return this;
}
public SqlArgs argString( String argName, String arg) {
invocations.add(new Invocation(ColumnType.String, argName, arg));
return this;
}
public SqlArgs argDate(LocalDateTime arg) {
// date argument with a time on it
invocations.add(new Invocation(ColumnType.Date, null, arg));
return this;
}
public SqlArgs argDate( String argName, LocalDateTime arg) {
// date argument with a time on it
@ -693,11 +685,9 @@ public class SqlArgs implements SqlInsert.Apply, SqlUpdate.Apply, SqlSelect.Appl
throw new DatabaseException("Unable to retrieve metadata from ResultSet", e);
}
}
public SqlArgs read(Row r) {
SqlArgs args = new SqlArgs();
for (int i = 0; i < names.length; i++) {
switch (types[i]) {
case Types.SMALLINT:
@ -705,22 +695,24 @@ public class SqlArgs implements SqlInsert.Apply, SqlUpdate.Apply, SqlSelect.Appl
args.argInteger(names[i], r.getIntegerOrNull());
break;
case Types.BIGINT:
args.argLong(names[i], r.getLongOrNull());
if (precision[i] <= 64 && scale[i] == 0) {
args.argLong(names[i], r.getLongOrNull());
} else {
args.argBigDecimal(names[i], r.getBigDecimalOrNull());
}
break;
case Types.REAL:
case 100: // Oracle proprietary it seems
case 100: // Oracle binary float
args.argFloat(names[i], r.getFloatOrNull());
break;
case Types.DOUBLE:
case 101: // Oracle proprietary it seems
case 101: // Oracle binary double
args.argDouble(names[i], r.getDoubleOrNull());
break;
case Types.NUMERIC:
if (precision[i] == 10 && scale[i] == 0) {
// Oracle reports integer as numeric
if (precision[i] <= 10 && scale[i] == 0) {
args.argInteger(names[i], r.getIntegerOrNull());
} else if (precision[i] == 19 && scale[i] == 0) {
// Oracle reports long as numeric
} else if (precision[i] <= 19 && scale[i] == 0) {
args.argLong(names[i], r.getLongOrNull());
} else {
args.argBigDecimal(names[i], r.getBigDecimalOrNull());

@ -288,7 +288,6 @@ public class SqlInsertImpl implements SqlInsert {
if (!hasPk()) {
throw new DatabaseException("Call argPkSeq() before insertReturningPkSeq()");
}
if (options.flavor().supportsInsertReturning()) {
return updateInternal(1, primaryKeyColumnName);
} else {

@ -1,5 +1,10 @@
package org.xbib.jdbc.query;
import org.xbib.jdbc.query.flavor.Derby;
import org.xbib.jdbc.query.flavor.Oracle;
import org.xbib.jdbc.query.flavor.Postgresql;
import org.xbib.jdbc.query.flavor.SqlServer;
import java.util.Objects;
/**
@ -16,28 +21,28 @@ public class When {
}
public When oracle(String sql) {
if (actualFlavor == Flavor.oracle) {
if (actualFlavor instanceof Oracle) {
chosen = sql;
}
return this;
}
public When derby(String sql) {
if (actualFlavor == Flavor.derby) {
if (actualFlavor instanceof Derby) {
chosen = sql;
}
return this;
}
public When postgres(String sql) {
if (actualFlavor == Flavor.postgresql) {
if (actualFlavor instanceof Postgresql) {
chosen = sql;
}
return this;
}
public When sqlserver(String sql) {
if (actualFlavor == Flavor.sqlserver) {
if (actualFlavor instanceof SqlServer) {
chosen = sql;
}
return this;

@ -0,0 +1,196 @@
package org.xbib.jdbc.query.flavor;
import org.xbib.jdbc.query.Flavor;
import java.sql.Date;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Calendar;
public class BigQuery implements Flavor {
@Override
public String getName() {
return "bigQuery";
}
@Override
public boolean supports(String url) {
return url.startsWith("jdbc:bigquery:");
}
@Override
public String driverClass() {
return "com.simba.googlebigquery.jdbc42.Driver";
}
@Override
public boolean isNormalizedUpperCase() {
return false;
}
@Override
public String typeInteger() {
return "int64";
}
@Override
public String typeBoolean() {
// BigQuery has a native boolean type, but we're not trying to use it
return "string";
}
@Override
public String typeLong() {
return "int64";
}
@Override
public String typeFloat() {
return "float64";
}
@Override
public String typeDouble() {
return "float64";
}
@Override
public String typeBigDecimal(int size, int precision) {
return "numeric";
}
@Override
public String typeStringVar(int length) {
return "string";
}
@Override
public String typeStringFixed(int length) {
return "string";
}
@Override
public String typeClob() {
return "string";
}
@Override
public String typeBlob() {
return "bytes";
}
@Override
public String typeDate() {
return "datetime";
}
@Override
public String typeLocalDate() {
return "date";
}
@Override
public boolean useStringForClob() {
return true;
}
@Override
public boolean useBytesForBlob() {
return true;
}
@Override
public String sequenceNextVal(String sequenceName) {
throw new UnsupportedOperationException();
}
@Override
public String sequenceSelectNextVal(String sequenceName) {
throw new UnsupportedOperationException();
}
@Override
public String sequenceDrop(String dbtestSeq) {
throw new UnsupportedOperationException();
}
@Override
public String tableDrop(String table) {
return "drop table " + table;
}
@Override
public boolean supportsInsertReturning() {
return false;
}
@Override
public String dbTimeMillis() {
return "current_timestamp()";
}
@Override
public String sequenceCacheClause(int nbrValuesToCache) {
throw new UnsupportedOperationException();
}
@Override
public String sequenceOrderClause(boolean order) {
throw new UnsupportedOperationException();
}
@Override
public String sequenceCycleClause(boolean cycle) {
throw new UnsupportedOperationException();
}
@Override
public String fromAny() {
return "";
}
@Override
public String dateAsSqlFunction(Timestamp date, Calendar calendar) {
// Construct a datetime literal
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS000");
dateFormat.setCalendar(calendar);
return String.format("datetime '%s'", dateFormat.format(date));
}
@Override
public String localDateAsSqlFunction(Date date) {
// Construct a datetime literal
return String.format("datetime '%s'", date.toString());
}
@Override
public String sequenceOptions() {
throw new UnsupportedOperationException();
}
@Override
public boolean isAutoCommitOnly() {
return true;
}
@Override
public String writeNextIntoQueue(String table) {
throw new UnsupportedOperationException();
}
@Override
public String readNextFromQueue(String table) {
throw new UnsupportedOperationException();
}
@Override
public String succeedInQueue(String table) {
throw new UnsupportedOperationException();
}
@Override
public String failInQueue(String table) {
throw new UnsupportedOperationException();
}
}

@ -0,0 +1,194 @@
package org.xbib.jdbc.query.flavor;
import org.xbib.jdbc.query.Flavor;
import java.sql.Date;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Calendar;
public class Derby implements Flavor {
@Override
public String getName() {
return "derby";
}
@Override
public boolean supports(String url) {
return url.startsWith("jdbc:derby:");
}
@Override
public String driverClass() {
return "org.apache.derby.jdbc.EmbeddedDriver";
}
@Override
public boolean isNormalizedUpperCase() {
return true;
}
@Override
public String typeInteger() {
return "integer";
}
@Override
public String typeBoolean() {
return "char(1)";
}
@Override
public String typeLong() {
return "bigint";
}
@Override
public String typeFloat() {
return "real";
}
@Override
public String typeDouble() {
return "double";
}
@Override
public String typeBigDecimal(int size, int precision) {
return "numeric(" + size + "," + precision + ")";
}
@Override
public String typeStringVar(int length) {
return "varchar(" + length + ")";
}
@Override
public String typeStringFixed(int length) {
return "char(" + length + ")";
}
@Override
public String typeClob() {
return "clob";
}
@Override
public String typeBlob() {
return "blob";
}
@Override
public String typeDate() {
return "timestamp";
}
@Override
public String typeLocalDate() {
return "date";
}
@Override
public boolean useStringForClob() {
return false;
}
@Override
public boolean useBytesForBlob() {
return false;
}
@Override
public String sequenceNextVal(String sequenceName) {
return "next value for " + sequenceName;
}
@Override
public String sequenceSelectNextVal(String sequenceName) {
return "values next value for " + sequenceName;
}
@Override
public String sequenceDrop(String sequenceName) {
return "drop sequence " + sequenceName + " restrict";
}
@Override
public String tableDrop(String table) {
return "drop table " + table;
}
@Override
public boolean supportsInsertReturning() {
return false;
}
@Override
public String sequenceCacheClause(int nbrValuesToCache) {
return "";
}
@Override
public String sequenceOrderClause(boolean order) {
return "";
}
@Override
public String sequenceCycleClause(boolean cycle) {
return cycle ? " cycle" : " no cycle";
}
@Override
public String dbTimeMillis() {
return "current_timestamp";
}
@Override
public String fromAny() {
return " from sysibm.sysdummy1";
}
@Override
public String dateAsSqlFunction(Timestamp date, Calendar calendar) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
dateFormat.setCalendar(calendar);
return "timestamp('" + dateFormat.format(date) + "')";
}
@Override
public String localDateAsSqlFunction(Date date) {
return "'" + date.toString() + "'";
}
@Override
public String sequenceOptions() {
return " as bigint";
}
@Override
public boolean isAutoCommitOnly() {
return false;
}
@Override
public String writeNextIntoQueue(String table) {
throw new UnsupportedOperationException();
}
@Override
public String readNextFromQueue(String table) {
throw new UnsupportedOperationException();
}
@Override
public String succeedInQueue(String table) {
throw new UnsupportedOperationException();
}
@Override
public String failInQueue(String table) {
throw new UnsupportedOperationException();
}
}

@ -0,0 +1,192 @@
package org.xbib.jdbc.query.flavor;
import org.xbib.jdbc.query.Flavor;
import java.sql.Date;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Calendar;
public class Hsql implements Flavor {
@Override
public String getName() {
return "hsqldb";
}
@Override
public boolean supports(String url) {
return url.startsWith("jdbc:hsqldb:");
}
@Override
public String driverClass() {
return "org.hsqldb.jdbc.JDBCDriver";
}
@Override
public boolean isNormalizedUpperCase() {
return true;
}
@Override
public String typeInteger() {
return "integer";
}
@Override
public String typeBoolean() {
return "char(1)";
}
@Override
public String typeLong() {
return "bigint";
}
@Override
public String typeFloat() {
return "double";
}
@Override
public String typeDouble() {
return "double";
}
@Override
public String typeBigDecimal(int size, int precision) {
return "numeric(" + size + "," + precision + ")";
}
@Override
public String typeStringVar(int length) {
return "varchar(" + length + ")";
}
@Override
public String typeStringFixed(int length) {
return "char(" + length + ")";
}
@Override
public String typeClob() {
return "clob(2G)";
}
@Override
public String typeBlob() {
return "blob(2G)";
}
@Override
public String typeDate() {
return "timestamp(3)";
}
@Override
public String typeLocalDate() {
return "date";
}
@Override
public boolean useStringForClob() {
return true;
}
@Override
public boolean useBytesForBlob() {
return true;
}
@Override
public String sequenceNextVal(String sequenceName) {
return "next value for " + sequenceName + "";
}
@Override
public String sequenceSelectNextVal(String sequenceName) {
return "select " + sequenceNextVal(sequenceName) + fromAny();
}
@Override
public String sequenceDrop(String dbtestSeq) {
return "drop sequence if exists " + dbtestSeq;
}
@Override
public String tableDrop(String table) {
return "drop table if exists " + table;
}
@Override
public String sequenceOrderClause(boolean order) {
return "";
}
@Override
public String sequenceCycleClause(boolean cycle) {
return cycle ? " cycle" : " no cycle";
}
@Override
public String fromAny() {
return " from (values(0))";
}
@Override
public boolean supportsInsertReturning() {
return false;
}
@Override
public String dbTimeMillis() {
return "localtimestamp";
}
@Override
public String sequenceCacheClause(int nbrValuesToCache) {
return "";
}
@Override
public String dateAsSqlFunction(Timestamp date, Calendar calendar) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS000XXX");
dateFormat.setCalendar(calendar);
return "cast(timestamp '" + dateFormat.format(date) + "' as timestamp without time zone)";
}
@Override
public String localDateAsSqlFunction(Date date) {
return "'" + date.toString() + "'";
}
@Override
public String sequenceOptions() {
return " as bigint";
}
@Override
public boolean isAutoCommitOnly() {
return false;
}
@Override
public String writeNextIntoQueue(String table) {
throw new UnsupportedOperationException();
}
@Override
public String readNextFromQueue(String table) {
throw new UnsupportedOperationException();
}
@Override
public String succeedInQueue(String table) {
throw new UnsupportedOperationException();
}
@Override
public String failInQueue(String table) {
throw new UnsupportedOperationException();
}
}

@ -0,0 +1,200 @@
package org.xbib.jdbc.query.flavor;
import org.xbib.jdbc.query.Flavor;
import java.sql.Date;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Calendar;
public class Oracle implements Flavor {
@Override
public String getName() {
return "oracle";
}
@Override
public boolean supports(String url) {
return url.startsWith("jdbc:oracle:");
}
@Override
public String driverClass() {
return "oracle.jdbc.OracleDriver";
}
@Override
public boolean isNormalizedUpperCase() {
return true;
}
@Override
public String typeFloat() {
return "binary_float";
}
@Override
public String typeDouble() {
return "binary_double";
}
@Override
public String typeBigDecimal(int size, int precision) {
return "numeric(" + size + "," + precision + ")";
}
@Override
public String typeInteger() {
return "numeric(10)";
}
@Override
public String typeBoolean() {
return "char(1 char)";
}
@Override
public String typeLong() {
return "numeric(19)";
}
@Override
public String typeDate() {
return "timestamp(3)";
}
@Override
public String typeLocalDate() {
return "date";
}
@Override
public boolean useStringForClob() {
return false;
}
@Override
public boolean useBytesForBlob() {
return false;
}
@Override
public String sequenceNextVal(String sequenceName) {
return sequenceName + ".nextval";
}
@Override
public String sequenceSelectNextVal(String sequenceName) {
return "select " + sequenceName + ".nextval from dual";
}
@Override
public String sequenceDrop(String sequenceName) {
return "drop sequence " + sequenceName;
}
@Override
public String tableDrop(String table) {
return "drop table " + table;
}
@Override
public String typeStringVar(int length) {
return "varchar2(" + length + " char)";
}
@Override
public String typeStringFixed(int length) {
return "char(" + length + " char)";
}
@Override
public String typeClob() {
return "clob";
}
@Override
public String typeBlob() {
return "blob";
}
@Override
public String sequenceOrderClause(boolean order) {
return order ? " order" : " noorder";
}
@Override
public String sequenceCycleClause(boolean cycle) {
return cycle ? " cycle" : " nocycle";
}
@Override
public boolean supportsInsertReturning() {
return true;
}
@Override
public String dbTimeMillis() {
return "systimestamp(3)";
}
@Override
public String sequenceCacheClause(int nbrValuesToCache) {
if (nbrValuesToCache < 2) {
return " nocache";
}
return " cache " + nbrValuesToCache;
}
@Override
public String fromAny() {
return " from dual";
}
@Override
public String dateAsSqlFunction(Timestamp date, Calendar calendar) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS000");
dateFormat.setCalendar(calendar);
return "timestamp '" + dateFormat.format(date) + "'";
}
@Override
public String localDateAsSqlFunction(Date date) {
return "to_date('" + date.toString() + "', 'yyyy-mm-dd')";
}
@Override
public String sequenceOptions() {
return "";
}
@Override
public boolean isAutoCommitOnly() {
return false;
}
@Override
public String writeNextIntoQueue(String table) {
return "insert into " + table
+ " (channel, data, state, delay, modified) "
+ "values (?, ?, null, 0, ?)";
}
@Override
public String readNextFromQueue(String table) {
return "select id, data from " + table +
" where channel = ? and state is NULL and modified <= (CAST (? as TIMESTAMP) - (INTERVAL '1 minute' * delay))" +
" order by id asc limit 1 for update skip lock";
}
@Override
public String succeedInQueue(String table) {
return "update" + table + " set state = 'OK', modified = " + dbTimeMillis() + " where id = ?";
}
@Override
public String failInQueue(String table) {
return "update" + table + " set state = 'FAIL', modified = " + dbTimeMillis() + " where id = ?";
}
}

@ -0,0 +1,197 @@
package org.xbib.jdbc.query.flavor;
import org.xbib.jdbc.query.Flavor;
import java.sql.Date;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Calendar;
public class Postgresql implements Flavor {
@Override
public String getName() {
return "postgresql";
}
@Override
public boolean supports(String url) {
return url.startsWith("jdbc:postgresql:");
}
@Override
public String driverClass() {
return "org.postgresql.Driver";
}
@Override
public boolean isNormalizedUpperCase() {
return false;
}
@Override
public String typeInteger() {
return "integer";
}
@Override
public String typeBoolean() {
return "char(1)";
}
@Override
public String typeLong() {
return "bigint";
}
@Override
public String typeFloat() {
return "real";
}
@Override
public String typeDouble() {
return "double precision";
}
@Override
public String typeBigDecimal(int size, int precision) {
return "numeric(" + size + "," + precision + ")";
}
@Override
public String typeStringVar(int length) {
return "varchar(" + length + ")";
}
@Override
public String typeStringFixed(int length) {
return "char(" + length + ")";
}
@Override
public String typeClob() {
return "text";
}
@Override
public String typeBlob() {
return "bytea";
}
@Override
public String typeDate() {
return "timestamp(3)";
}
@Override
public String typeLocalDate() {
return "date";
}
@Override
public boolean useStringForClob() {
return true;
}
@Override
public boolean useBytesForBlob() {
return true;
}
@Override
public String sequenceNextVal(String sequenceName) {
return "nextval('" + sequenceName + "')";
}
@Override
public String sequenceSelectNextVal(String sequenceName) {
return "select nextval('" + sequenceName + "')";
}
@Override
public String sequenceDrop(String dbtestSeq) {
return "drop sequence " + dbtestSeq;
}
@Override
public String tableDrop(String table) {
return "drop table if exists " + table;
}
@Override
public String sequenceOrderClause(boolean order) {
return "";
}
@Override
public String sequenceCycleClause(boolean cycle) {
return cycle ? " cycle" : " no cycle";
}
@Override
public String fromAny() {
return "";
}
@Override
public boolean supportsInsertReturning() {
return true;
}
@Override
public String dbTimeMillis() {
return "date_trunc('milliseconds',localtimestamp)";
}
@Override
public String sequenceCacheClause(int nbrValuesToCache) {
return " cache " + nbrValuesToCache;
}
@Override
public String dateAsSqlFunction(Timestamp date, Calendar calendar) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS000");
dateFormat.setCalendar(calendar);
return "'" + dateFormat.format(date) + " GMT'::timestamp";
}
@Override
public String localDateAsSqlFunction(Date date) {
return "'" + date.toString() + "'";
}
@Override
public String sequenceOptions() {
return "";
}
@Override
public boolean isAutoCommitOnly() {
return false;
}
@Override
public String writeNextIntoQueue(String table) {
return "insert into " + table
+ " (channel, data, state, delay, modified) "
+ "values (?, ?, null, 0, ?)";
}
@Override
public String readNextFromQueue(String table) {
return "select id, data from " + table +
" where channel = ? and state is NULL and pushed_at <= (CAST (? as TIMESTAMP) - (INTERVAL '1 minute' * delay))" +
" order by id asc limit 1 for update skip lock";
}
@Override
public String succeedInQueue(String table) {
return "update" + table + " set state = 'OK', modified = " + dbTimeMillis() + " where id = ?";
}
@Override
public String failInQueue(String table) {
return "update" + table + " set state = 'FAIL', modified = " + dbTimeMillis() + " where id = ?";
}
}

@ -0,0 +1,197 @@
package org.xbib.jdbc.query.flavor;
import org.xbib.jdbc.query.Flavor;
import java.sql.Date;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Calendar;
public class SqlServer implements Flavor {
@Override
public String getName() {
return "sqlServer";
}
@Override
public boolean supports(String url) {
return url.startsWith("jdbc:sqlserver:");
}
@Override
public String driverClass() {
return "com.microsoft.sqlserver.jdbc.SQLServerDriver";
}
@Override
public boolean isNormalizedUpperCase() {
return false;
}
@Override
public String typeFloat() {
return "float(24)";
}
@Override
public String typeDouble() {
return "float(53)";
}
@Override
public String typeBigDecimal(int size, int precision) {
return "numeric(" + size + "," + precision + ")";
}
@Override
public String typeInteger() {
return "numeric(10)";
}
@Override
public String typeBoolean() {
return "char(1)";
}
@Override
public String typeLong() {
return "numeric(19)";
}
@Override
public String typeDate() {
return "datetime2(3)";
}
@Override
public String typeLocalDate() {
return "date";
}
@Override
public boolean useStringForClob() {
return false;
}
@Override
public boolean useBytesForBlob() {
return false;
}
@Override
public String sequenceNextVal(String sequenceName) {
return "next value for " + sequenceName;
}
@Override
public String sequenceSelectNextVal(String sequenceName) {
return "select next value for " + sequenceName;
}
@Override
public String sequenceDrop(String dbtestSeq) {
return "drop sequence " + dbtestSeq;
}
@Override
public String tableDrop(String table) {
return "drop table " + table;
}
@Override
public String typeStringVar(int length) {
return "varchar(" + length + ")";
}
@Override
public String typeStringFixed(int length) {
return "char(" + length + ")";
}
@Override
public String typeClob() {
return "varchar(max)";
}
@Override
public String typeBlob() {
return "varbinary(max)";
}
@Override
public String sequenceOrderClause(boolean order) {
// Not supported
return "";
}
@Override
public String sequenceCycleClause(boolean cycle) {
return cycle ? " cycle" : " no cycle";
}
@Override
public boolean supportsInsertReturning() {
return true;
}
@Override
public String dbTimeMillis() {
return "current_timestamp";
}
@Override
public String sequenceCacheClause(int nbrValuesToCache) {
if (nbrValuesToCache < 2) {
return " no cache";
}
return " cache " + nbrValuesToCache;
}
@Override
public String fromAny() {
return "";
}
@Override
public String dateAsSqlFunction(Timestamp date, Calendar calendar) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS000");
dateFormat.setCalendar(calendar);
return "cast('" + dateFormat.format(date) + "' as datetime2(3))";
}
@Override
public String localDateAsSqlFunction(Date date) {
return "'" + date.toString() + "'";
}
@Override
public String sequenceOptions() {
return "";
}
@Override
public boolean isAutoCommitOnly() {
return false;
}
@Override
public String writeNextIntoQueue(String table) {
throw new UnsupportedOperationException();
}
@Override
public String readNextFromQueue(String table) {
throw new UnsupportedOperationException();
}
@Override
public String succeedInQueue(String table) {
throw new UnsupportedOperationException();
}
@Override
public String failInQueue(String table) {
throw new UnsupportedOperationException();
}
}

@ -0,0 +1,6 @@
org.xbib.jdbc.query.flavor.BigQuery
org.xbib.jdbc.query.flavor.Derby
org.xbib.jdbc.query.flavor.Hsql
org.xbib.jdbc.query.flavor.Oracle
org.xbib.jdbc.query.flavor.Postgresql
org.xbib.jdbc.query.flavor.SqlServer

@ -26,14 +26,12 @@ import java.io.StringReader;
import java.math.BigDecimal;
import java.sql.ResultSetMetaData;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
@ -54,7 +52,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
/**
* Exercise Database functionality with a real databases.
* Exercise Database functionality with a real database.
*/
public abstract class CommonTest {
@ -62,10 +60,6 @@ public abstract class CommonTest {
final static String TEST_TABLE_NAME = "dbtest";
/**
* Enable retrying failed tests if they have the @Retry annotation.
*/
protected DatabaseProvider dbp;
protected Database db;

@ -4,7 +4,6 @@ import org.junit.jupiter.api.Test;
import org.xbib.jdbc.query.Config;
import org.xbib.jdbc.query.ConfigFrom;
import java.io.File;
import java.io.FileWriter;
import java.math.BigDecimal;
import java.util.Properties;
@ -54,7 +53,7 @@ public class ConfigTest {
properties.store(new FileWriter(filename2), null);
// Throw a null in here just to make sure it doesn't blow up
Config config = ConfigFrom.firstOf().propertyFile(filename1, null, filename2).get();
Config config = ConfigFrom.firstOf().properties(filename1, null, filename2).get();
assertEquals(Integer.valueOf(1), config.getInteger("foo"));
assertEquals(Integer.valueOf(-2), config.getInteger("foo2"));
@ -62,12 +61,12 @@ public class ConfigTest {
assertEquals(5, config.getInteger("unknown", 5));
// Now flip the order and verify precedence works
config = ConfigFrom.firstOf().propertyFile(filename2, null, filename1).get();
config = ConfigFrom.firstOf().properties(filename2, null, filename1).get();
assertEquals(Integer.valueOf(2), config.getInteger("foo"));
assertEquals(Integer.valueOf(-2), config.getInteger("foo2"));
// Same as above tests, but using File version rather than filename String
config = ConfigFrom.firstOf().propertyFile(new File(filename1), new File("does not exist"), new File(filename2)).get();
config = ConfigFrom.firstOf().properties(filename1, "does not exist", filename2).get();
assertEquals(Integer.valueOf(1), config.getInteger("foo"));
assertEquals(Integer.valueOf(-2), config.getInteger("foo2"));
@ -75,7 +74,7 @@ public class ConfigTest {
assertEquals(5, config.getInteger("unknown", 5));
// Now flip the order and verify precedence works
config = ConfigFrom.firstOf().propertyFile(new File(filename2), null, new File(filename1)).get();
config = ConfigFrom.firstOf().properties(filename2, null, filename1).get();
assertEquals(Integer.valueOf(2), config.getInteger("foo"));
assertEquals(Integer.valueOf(-2), config.getInteger("foo2"));
}

@ -12,7 +12,6 @@ import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* Exercise Database functionality with a real database (Derby).
*/
@ -79,14 +78,38 @@ public class DerbyTest extends CommonTest {
super.intervals();
}
@Disabled("Derby limits out at precision 31")
@Test
public void argBigDecimal38Precision0() {
super.argBigDecimal38Precision0();
}
@Disabled("Derby limits out at precision 31")
@Test
public void argBigDecimal38Precision1() {
super.argBigDecimal38Precision1();
}
@Disabled("Derby limits out at precision 31")
@Test
public void argBigDecimal38Precision37() {
super.argBigDecimal38Precision37();
}
@Disabled("Derby limits out at precision 31")
@Test
public void argBigDecimal38Precision38() {
super.argBigDecimal38Precision38();
}
@Test
public void argBigDecimal31Precision0() {
db.dropTableQuietly("dbtest");
new Schema().addTable("dbtest").addColumn("i").asBigDecimal(31, 0).schema().execute(db);
BigDecimal value = new BigDecimal("9999999999999999999999999999999"); // 31 digits
db.toInsert("insert into dbtest (i) values (?)").argBigDecimal(value).insert(1);
assertEquals(value,
db.toSelect("select i from dbtest where i=?").argBigDecimal(value).queryBigDecimalOrNull());
assertEquals(value, db.toSelect("select i from dbtest where i=?").argBigDecimal(value).queryBigDecimalOrNull());
}
@Test
@ -95,8 +118,7 @@ public class DerbyTest extends CommonTest {
new Schema().addTable("dbtest").addColumn("i").asBigDecimal(31, 1).schema().execute(db);
BigDecimal value = new BigDecimal("999999999999999999999999999999.9"); // 31 digits
db.toInsert("insert into dbtest (i) values (?)").argBigDecimal(value).insert(1);
assertEquals(value,
db.toSelect("select i from dbtest where i=?").argBigDecimal(value).queryBigDecimalOrNull());
assertEquals(value, db.toSelect("select i from dbtest where i=?").argBigDecimal(value).queryBigDecimalOrNull());
}
@Test
@ -105,8 +127,7 @@ public class DerbyTest extends CommonTest {
new Schema().addTable("dbtest").addColumn("i").asBigDecimal(31, 30).schema().execute(db);
BigDecimal value = new BigDecimal("9.999999999999999999999999999999"); // 31 digits
db.toInsert("insert into dbtest (i) values (?)").argBigDecimal(value).insert(1);
assertEquals(value,
db.toSelect("select i from dbtest where i=?").argBigDecimal(value).queryBigDecimalOrNull());
assertEquals(value, db.toSelect("select i from dbtest where i=?").argBigDecimal(value).queryBigDecimalOrNull());
}
@Test
@ -116,31 +137,6 @@ public class DerbyTest extends CommonTest {
BigDecimal value = new BigDecimal("0.9999999999999999999999999999999"); // 31 digits
db.toInsert("insert into dbtest (i) values (?)").argBigDecimal(value).insert(1);
logger.log(Level.INFO, db.toSelect("select i from dbtest").queryBigDecimalOrNull().toString());
assertEquals(value,
db.toSelect("select i from dbtest where i=?").argBigDecimal(value).queryBigDecimalOrNull());
}
@Disabled("Derby limits out at precision 31")
@Test
public void argBigDecimal38Precision0() {
super.argBigDecimal38Precision0();
}
@Disabled("Derby limits out at precision 31")
@Test
public void argBigDecimal38Precision1() {
super.argBigDecimal38Precision1();
}
@Disabled("Derby limits out at precision 31")
@Test
public void argBigDecimal38Precision37() {
super.argBigDecimal38Precision37();
}
@Disabled("Derby limits out at precision 31")
@Test
public void argBigDecimal38Precision38() {
super.argBigDecimal38Precision38();
assertEquals(value, db.toSelect("select i from dbtest where i=?").argBigDecimal(value).queryBigDecimalOrNull());
}
}

@ -1,6 +1,5 @@
package org.xbib.jdbc.query.test;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@ -22,24 +21,21 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* Exercise database functionality with a real HyperSQL database.
*/
@Disabled
public class HsqldbTest extends CommonTest {
private static final Logger logger = Logger.getLogger(HsqldbTest.class.getName());
@Override
protected DatabaseProvider createDatabaseProvider(OptionsOverride options) throws Exception {
String propertiesFile = System.getProperty("local.properties", "local.properties");
String propertiesFile = "hsqldb.properties";
Config config = ConfigFrom.firstOf()
.systemProperties()
.propertyFile(propertiesFile)
.excludePrefix("database.")
.removePrefix("hsqldb.").get();
logger.log(Level.INFO, "config = " + config);
.properties(getClass().getResourceAsStream(propertiesFile))
.get();
return DatabaseProvider.builder(config)
.withSqlParameterLogging()
.withSqlInExceptionMessages()
.withOptions(options).build();
.withOptions(options)
.build();
}
@Test
@ -61,9 +57,16 @@ public class HsqldbTest extends CommonTest {
});
}
@Disabled("HSQLDB uses always static GMT timezone")
@Test
public void clockSync() {
db.assertTimeSynchronized();
}
@Disabled("LocalDate implementations should be TimeZone agnostic, but HSQLDB implementation has a bug.")
@Test
public void argLocalDateTimeZones() {
// <2000-01-01> but was: <1999-12-31>
// See bug: https://bugs.documentfoundation.org/show_bug.cgi?id=63566
super.argLocalDateTimeZones();
}
@ -91,19 +94,35 @@ public class HsqldbTest extends CommonTest {
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)
.argDateNowPerApp().argLocalDate(localDateNow).insert(1);
.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)
.argDateNowPerApp()
.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(0.000001d)
.argDouble(Double.MIN_VALUE).argBigDecimal(new BigDecimal("-123.456"))
.argString("goodbye").argString("A").argClobString("bye again")
.argBlobBytes(new byte[]{'3', '4'}).argBoolean(false)
.argDateNowPerApp().argLocalDate(localDateNow).insert(1);
.argInteger(Integer.MIN_VALUE)
.argLong(Long.MIN_VALUE)
.argDouble(0.000001d)
.argDouble(Double.MIN_VALUE)
.argBigDecimal(new BigDecimal("-123.456"))
.argString("goodbye")
.argString("A")
.argClobString("bye again")
.argBlobBytes(new byte[]{'3', '4'})
.argBoolean(false)
.argDateNowPerApp()
.argLocalDate(localDateNow)
.insert(1);
String expectedSchema = new Schema().addTable("dbtest2")
.addColumn("nbr_integer").asInteger().table()

@ -20,9 +20,10 @@ public class SqlServerTest extends CommonTest {
String propertiesFile = System.getProperty("local.properties", "local.properties");
Config config = ConfigFrom.firstOf()
.systemProperties()
.propertyFile(propertiesFile)
.properties(propertiesFile)
.excludePrefix("database.")
.removePrefix("sqlserver.").get();
.removePrefix("sqlserver.")
.get();
return DatabaseProvider.builder(config)
.withSqlParameterLogging()
.withSqlInExceptionMessages()

@ -0,0 +1,9 @@
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/marc.log
jdk.event.security.level=INFO

@ -0,0 +1,3 @@
database.url=jdbc:hsqldb:file:build/hsqldb;shutdown=true
database.user=SA
database.password=

@ -1,7 +1,7 @@
dependencyResolutionManagement {
versionCatalogs {
libs {
version('gradle', '7.5')
version('gradle', '7.5.1')
version('junit', '5.8.2')
version('testcontainers', '1.17.3')
library('junit-jupiter-api', 'org.junit.jupiter', 'junit-jupiter-api').versionRef('junit')
@ -10,6 +10,7 @@ dependencyResolutionManagement {
library('hamcrest', 'org.hamcrest', 'hamcrest-library').version('2.2')
library('junit4', 'junit', 'junit').version('4.13.2')
library('derby', 'org.apache.derby', 'derby').version('10.15.2.0')
library('hsqldb', 'org.hsqldb', 'hsqldb').version('2.7.1')
library('h2', 'com.h2database', 'h2').version('1.4.200')
library('testcontainers', 'org.testcontainers', 'testcontainers').versionRef('testcontainers')
library('testcontainers-junit-jupiter', 'org.testcontainers', 'junit-jupiter').versionRef('testcontainers')

Loading…
Cancel
Save