change flavor from enum to interface, fix int/long bug in JDBC pool, add queue implementation
This commit is contained in:
parent
7198e766c3
commit
1047be8456
42 changed files with 1667 additions and 1274 deletions
|
@ -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'
|
||||
}
|
||||
|
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -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
|
||||
return tableExists(tableName, connection.getSchema());
|
||||
} catch (SQLException e) {
|
||||
throw new DatabaseException("Unable to getSchema()", e);
|
||||
}
|
||||
|
||||
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, 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);
|
||||
}
|
||||
|