org.h2.jdbc.JdbcConnection Maven / Gradle / Ivy
/*
* Copyright 2004-2019 H2 Group. Multiple-Licensed under the MPL 2.0, and the
* EPL 1.0 (https://h2database.com/html/license.html). Initial Developer: H2
* Group
*/
package org.h2.jdbc;
import java.io.InputStream;
import java.io.Reader;
import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.ClientInfoStatus;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.Executor;
import java.util.regex.Pattern;
import org.h2.api.ErrorCode;
import org.h2.command.CommandInterface;
import org.h2.engine.CastDataProvider;
import org.h2.engine.ConnectionInfo;
import org.h2.engine.Constants;
import org.h2.engine.IsolationLevel;
import org.h2.engine.Mode;
import org.h2.engine.Mode.ModeEnum;
import org.h2.engine.SessionInterface;
import org.h2.engine.SessionRemote;
import org.h2.engine.SysProperties;
import org.h2.message.DbException;
import org.h2.message.TraceObject;
import org.h2.result.ResultInterface;
import org.h2.util.CloseWatcher;
import org.h2.util.CurrentTimestamp;
import org.h2.util.JdbcUtils;
import org.h2.value.CompareMode;
import org.h2.value.DataType;
import org.h2.value.Value;
import org.h2.value.ValueBytes;
import org.h2.value.ValueInt;
import org.h2.value.ValueNull;
import org.h2.value.ValueResultSet;
import org.h2.value.ValueString;
import org.h2.value.ValueTimestampTimeZone;
/**
*
* Represents a connection (session) to a database.
*
*
* Thread safety: the connection is thread-safe, because access is synchronized.
* However, for compatibility with other databases, a connection should only be
* used in one thread at any time.
*
*/
public class JdbcConnection extends TraceObject implements Connection, JdbcConnectionBackwardsCompat,
CastDataProvider {
/**
* Database settings.
*/
public static final class Settings {
/**
* The database mode.
*/
public final Mode mode;
/**
* Whether unquoted identifiers are converted to upper case.
*/
public final boolean databaseToUpper;
/**
* Whether unquoted identifiers are converted to lower case.
*/
public final boolean databaseToLower;
/**
* Whether all identifiers are case insensitive.
*/
public final boolean caseInsensitiveIdentifiers;
/**
* Creates new instance of database settings.
*
* @param mode
* the database mode
* @param databaseToUpper
* whether unquoted identifiers are converted to upper case
* @param databaseToLower
* whether unquoted identifiers are converted to lower case
* @param caseInsensitiveIdentifiers
* whether all identifiers are case insensitive
*/
Settings(Mode mode, boolean databaseToUpper, boolean databaseToLower, boolean caseInsensitiveIdentifiers) {
this.mode = mode;
this.databaseToUpper = databaseToUpper;
this.databaseToLower = databaseToLower;
this.caseInsensitiveIdentifiers = caseInsensitiveIdentifiers;
}
}
private static final String NUM_SERVERS = "numServers";
private static final String PREFIX_SERVER = "server";
private static boolean keepOpenStackTrace;
private final String url;
private final String user;
// ResultSet.HOLD_CURSORS_OVER_COMMIT
private int holdability = 1;
private SessionInterface session;
private CommandInterface commit, rollback;
private CommandInterface getReadOnly, getGeneratedKeys;
private CommandInterface setQueryTimeout, getQueryTimeout;
private int savepointId;
private String catalog;
private Statement executingStatement;
private final CloseWatcher watcher;
private int queryTimeoutCache = -1;
private Map clientInfo;
private volatile Settings settings;
private final boolean scopeGeneratedKeys;
/**
* INTERNAL
*/
public JdbcConnection(String url, Properties info) throws SQLException {
this(new ConnectionInfo(url, info), true);
}
/**
* INTERNAL
*/
/*
* the session closable object does not leak as Eclipse warns - due to the
* CloseWatcher.
*/
@SuppressWarnings("resource")
public JdbcConnection(ConnectionInfo ci, boolean useBaseDir)
throws SQLException {
try {
if (useBaseDir) {
String baseDir = SysProperties.getBaseDir();
if (baseDir != null) {
ci.setBaseDir(baseDir);
}
}
// this will return an embedded or server connection
session = new SessionRemote(ci).connectEmbeddedOrServer(false);
trace = session.getTrace();
int id = getNextId(TraceObject.CONNECTION);
setTrace(trace, TraceObject.CONNECTION, id);
this.user = ci.getUserName();
if (isInfoEnabled()) {
trace.infoCode("Connection " + getTraceObjectName()
+ " = DriverManager.getConnection("
+ quote(ci.getOriginalURL()) + ", " + quote(user)
+ ", \"\");");
}
this.url = ci.getURL();
scopeGeneratedKeys = ci.getProperty("SCOPE_GENERATED_KEYS", false);
closeOld();
watcher = CloseWatcher.register(this, session, keepOpenStackTrace);
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* INTERNAL
*/
public JdbcConnection(JdbcConnection clone) {
this.session = clone.session;
trace = session.getTrace();
int id = getNextId(TraceObject.CONNECTION);
setTrace(trace, TraceObject.CONNECTION, id);
this.user = clone.user;
this.url = clone.url;
this.catalog = clone.catalog;
this.commit = clone.commit;
this.getGeneratedKeys = clone.getGeneratedKeys;
this.getQueryTimeout = clone.getQueryTimeout;
this.getReadOnly = clone.getReadOnly;
this.rollback = clone.rollback;
this.scopeGeneratedKeys = clone.scopeGeneratedKeys;
this.watcher = null;
if (clone.clientInfo != null) {
this.clientInfo = new HashMap<>(clone.clientInfo);
}
}
/**
* INTERNAL
*/
public JdbcConnection(SessionInterface session, String user, String url) {
this.session = session;
trace = session.getTrace();
int id = getNextId(TraceObject.CONNECTION);
setTrace(trace, TraceObject.CONNECTION, id);
this.user = user;
this.url = url;
this.scopeGeneratedKeys = false;
this.watcher = null;
}
private void closeOld() {
while (true) {
CloseWatcher w = CloseWatcher.pollUnclosed();
if (w == null) {
break;
}
try {
w.getCloseable().close();
} catch (Exception e) {
trace.error(e, "closing session");
}
// there was an unclosed object -
// keep the stack trace from now on
keepOpenStackTrace = true;
String s = w.getOpenStackTrace();
Exception ex = DbException
.get(ErrorCode.TRACE_CONNECTION_NOT_CLOSED);
trace.error(ex, s);
}
}
/**
* Creates a new statement.
*
* @return the new statement
* @throws SQLException if the connection is closed
*/
@Override
public Statement createStatement() throws SQLException {
try {
int id = getNextId(TraceObject.STATEMENT);
if (isDebugEnabled()) {
debugCodeAssign("Statement", TraceObject.STATEMENT, id,
"createStatement()");
}
checkClosed();
return new JdbcStatement(this, id, ResultSet.TYPE_FORWARD_ONLY,
Constants.DEFAULT_RESULT_SET_CONCURRENCY, false);
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Creates a statement with the specified result set type and concurrency.
*
* @param resultSetType the result set type (ResultSet.TYPE_*)
* @param resultSetConcurrency the concurrency (ResultSet.CONCUR_*)
* @return the statement
* @throws SQLException if the connection is closed or the result set type
* or concurrency are not supported
*/
@Override
public Statement createStatement(int resultSetType,
int resultSetConcurrency) throws SQLException {
try {
int id = getNextId(TraceObject.STATEMENT);
if (isDebugEnabled()) {
debugCodeAssign("Statement", TraceObject.STATEMENT, id,
"createStatement(" + resultSetType + ", "
+ resultSetConcurrency + ")");
}
checkTypeConcurrency(resultSetType, resultSetConcurrency);
checkClosed();
return new JdbcStatement(this, id, resultSetType,
resultSetConcurrency, false);
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Creates a statement with the specified result set type, concurrency, and
* holdability.
*
* @param resultSetType the result set type (ResultSet.TYPE_*)
* @param resultSetConcurrency the concurrency (ResultSet.CONCUR_*)
* @param resultSetHoldability the holdability (ResultSet.HOLD* / CLOSE*)
* @return the statement
* @throws SQLException if the connection is closed or the result set type,
* concurrency, or holdability are not supported
*/
@Override
public Statement createStatement(int resultSetType,
int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
try {
int id = getNextId(TraceObject.STATEMENT);
if (isDebugEnabled()) {
debugCodeAssign("Statement", TraceObject.STATEMENT, id,
"createStatement(" + resultSetType + ", "
+ resultSetConcurrency + ", "
+ resultSetHoldability + ")");
}
checkTypeConcurrency(resultSetType, resultSetConcurrency);
checkHoldability(resultSetHoldability);
checkClosed();
return new JdbcStatement(this, id, resultSetType,
resultSetConcurrency, false);
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Creates a new prepared statement.
*
* @param sql the SQL statement
* @return the prepared statement
* @throws SQLException if the connection is closed
*/
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
try {
int id = getNextId(TraceObject.PREPARED_STATEMENT);
if (isDebugEnabled()) {
debugCodeAssign("PreparedStatement",
TraceObject.PREPARED_STATEMENT, id,
"prepareStatement(" + quote(sql) + ")");
}
checkClosed();
sql = translateSQL(sql);
return new JdbcPreparedStatement(this, sql, id,
ResultSet.TYPE_FORWARD_ONLY,
Constants.DEFAULT_RESULT_SET_CONCURRENCY, false, null);
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Prepare a statement that will automatically close when the result set is
* closed. This method is used to retrieve database meta data.
*
* @param sql the SQL statement
* @return the prepared statement
*/
PreparedStatement prepareAutoCloseStatement(String sql)
throws SQLException {
try {
int id = getNextId(TraceObject.PREPARED_STATEMENT);
if (isDebugEnabled()) {
debugCodeAssign("PreparedStatement",
TraceObject.PREPARED_STATEMENT, id,
"prepareStatement(" + quote(sql) + ")");
}
checkClosed();
sql = translateSQL(sql);
return new JdbcPreparedStatement(this, sql, id,
ResultSet.TYPE_FORWARD_ONLY,
Constants.DEFAULT_RESULT_SET_CONCURRENCY, true, null);
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Gets the database meta data for this database.
*
* @return the database meta data
* @throws SQLException if the connection is closed
*/
@Override
public DatabaseMetaData getMetaData() throws SQLException {
try {
int id = getNextId(TraceObject.DATABASE_META_DATA);
if (isDebugEnabled()) {
debugCodeAssign("DatabaseMetaData",
TraceObject.DATABASE_META_DATA, id, "getMetaData()");
}
checkClosed();
return new JdbcDatabaseMetaData(this, trace, id);
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* INTERNAL
*/
public SessionInterface getSession() {
return session;
}
/**
* Closes this connection. All open statements, prepared statements and
* result sets that where created by this connection become invalid after
* calling this method. If there is an uncommitted transaction, it will be
* rolled back.
*/
@Override
public synchronized void close() throws SQLException {
try {
debugCodeCall("close");
if (session == null) {
return;
}
CloseWatcher.unregister(watcher);
session.cancel();
synchronized (session) {
if (executingStatement != null) {
try {
executingStatement.cancel();
} catch (NullPointerException e) {
// ignore
}
}
try {
if (!session.isClosed()) {
try {
if (session.hasPendingTransaction()) {
try {
rollbackInternal();
} catch (DbException e) {
// ignore if the connection is broken
// right now
if (e.getErrorCode() != ErrorCode.CONNECTION_BROKEN_1) {
throw e;
}
}
}
closePreparedCommands();
} finally {
session.close();
}
}
} finally {
session = null;
}
}
} catch (Throwable e) {
throw logAndConvert(e);
}
}
private void closePreparedCommands() {
commit = closeAndSetNull(commit);
rollback = closeAndSetNull(rollback);
getReadOnly = closeAndSetNull(getReadOnly);
getGeneratedKeys = closeAndSetNull(getGeneratedKeys);
getQueryTimeout = closeAndSetNull(getQueryTimeout);
setQueryTimeout = closeAndSetNull(setQueryTimeout);
}
private static CommandInterface closeAndSetNull(CommandInterface command) {
if (command != null) {
command.close();
}
return null;
}
/**
* Switches auto commit on or off. Enabling it commits an uncommitted
* transaction, if there is one.
*
* @param autoCommit true for auto commit on, false for off
* @throws SQLException if the connection is closed
*/
@Override
public synchronized void setAutoCommit(boolean autoCommit)
throws SQLException {
try {
if (isDebugEnabled()) {
debugCode("setAutoCommit(" + autoCommit + ");");
}
checkClosed();
synchronized (session) {
if (autoCommit && !session.getAutoCommit()) {
commit();
}
session.setAutoCommit(autoCommit);
}
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Gets the current setting for auto commit.
*
* @return true for on, false for off
* @throws SQLException if the connection is closed
*/
@Override
public synchronized boolean getAutoCommit() throws SQLException {
try {
checkClosed();
debugCodeCall("getAutoCommit");
return session.getAutoCommit();
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Commits the current transaction. This call has only an effect if auto
* commit is switched off.
*
* @throws SQLException if the connection is closed
*/
@Override
public synchronized void commit() throws SQLException {
try {
debugCodeCall("commit");
checkClosedForWrite();
if (SysProperties.FORCE_AUTOCOMMIT_OFF_ON_COMMIT
&& getAutoCommit()) {
throw DbException.get(ErrorCode.METHOD_DISABLED_ON_AUTOCOMMIT_TRUE, "commit()");
}
commit = prepareCommand("COMMIT", commit);
commit.executeUpdate(null);
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Rolls back the current transaction. This call has only an effect if auto
* commit is switched off.
*
* @throws SQLException if the connection is closed
*/
@Override
public synchronized void rollback() throws SQLException {
try {
debugCodeCall("rollback");
checkClosedForWrite();
if (SysProperties.FORCE_AUTOCOMMIT_OFF_ON_COMMIT
&& getAutoCommit()) {
throw DbException.get(ErrorCode.METHOD_DISABLED_ON_AUTOCOMMIT_TRUE, "rollback()");
}
rollbackInternal();
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Returns true if this connection has been closed.
*
* @return true if close was called
*/
@Override
public boolean isClosed() throws SQLException {
try {
debugCodeCall("isClosed");
return session == null || session.isClosed();
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Translates a SQL statement into the database grammar.
*
* @param sql the SQL statement with or without JDBC escape sequences
* @return the translated statement
* @throws SQLException if the connection is closed
*/
@Override
public String nativeSQL(String sql) throws SQLException {
try {
debugCodeCall("nativeSQL", sql);
checkClosed();
return translateSQL(sql);
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* According to the JDBC specs, this setting is only a hint to the database
* to enable optimizations - it does not cause writes to be prohibited.
*
* @param readOnly ignored
* @throws SQLException if the connection is closed
*/
@Override
public void setReadOnly(boolean readOnly) throws SQLException {
try {
if (isDebugEnabled()) {
debugCode("setReadOnly(" + readOnly + ");");
}
checkClosed();
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Returns true if the database is read-only.
*
* @return if the database is read-only
* @throws SQLException if the connection is closed
*/
@Override
public boolean isReadOnly() throws SQLException {
try {
debugCodeCall("isReadOnly");
checkClosed();
getReadOnly = prepareCommand("CALL READONLY()", getReadOnly);
ResultInterface result = getReadOnly.executeQuery(0, false);
result.next();
return result.currentRow()[0].getBoolean();
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Set the default catalog name. This call is ignored.
*
* @param catalog ignored
* @throws SQLException if the connection is closed
*/
@Override
public void setCatalog(String catalog) throws SQLException {
try {
debugCodeCall("setCatalog", catalog);
checkClosed();
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Gets the current catalog name.
*
* @return the catalog name
* @throws SQLException if the connection is closed
*/
@Override
public String getCatalog() throws SQLException {
try {
debugCodeCall("getCatalog");
checkClosed();
if (catalog == null) {
CommandInterface cat = prepareCommand("CALL DATABASE()",
Integer.MAX_VALUE);
ResultInterface result = cat.executeQuery(0, false);
result.next();
catalog = result.currentRow()[0].getString();
cat.close();
}
return catalog;
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Gets the first warning reported by calls on this object.
*
* @return null
*/
@Override
public SQLWarning getWarnings() throws SQLException {
try {
debugCodeCall("getWarnings");
checkClosed();
return null;
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Clears all warnings.
*/
@Override
public void clearWarnings() throws SQLException {
try {
debugCodeCall("clearWarnings");
checkClosed();
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Creates a prepared statement with the specified result set type and
* concurrency.
*
* @param sql the SQL statement
* @param resultSetType the result set type (ResultSet.TYPE_*)
* @param resultSetConcurrency the concurrency (ResultSet.CONCUR_*)
* @return the prepared statement
* @throws SQLException if the connection is closed or the result set type
* or concurrency are not supported
*/
@Override
public PreparedStatement prepareStatement(String sql, int resultSetType,
int resultSetConcurrency) throws SQLException {
try {
int id = getNextId(TraceObject.PREPARED_STATEMENT);
if (isDebugEnabled()) {
debugCodeAssign("PreparedStatement",
TraceObject.PREPARED_STATEMENT, id,
"prepareStatement(" + quote(sql) + ", " + resultSetType
+ ", " + resultSetConcurrency + ")");
}
checkTypeConcurrency(resultSetType, resultSetConcurrency);
checkClosed();
sql = translateSQL(sql);
return new JdbcPreparedStatement(this, sql, id, resultSetType,
resultSetConcurrency, false, null);
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Changes the current transaction isolation level. Calling this method will
* commit an open transaction, even if the new level is the same as the old
* one.
*
* @param level the new transaction isolation level:
* Connection.TRANSACTION_READ_UNCOMMITTED,
* Connection.TRANSACTION_READ_COMMITTED,
* Connection.TRANSACTION_REPEATABLE_READ,
* 6 (SNAPSHOT), or
* Connection.TRANSACTION_SERIALIZABLE
* @throws SQLException if the connection is closed or the isolation level
* is not valid
*/
@Override
public void setTransactionIsolation(int level) throws SQLException {
try {
debugCodeCall("setTransactionIsolation", level);
checkClosed();
if (!getAutoCommit()) {
commit();
}
session.setIsolationLevel(IsolationLevel.fromJdbc(level));
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* INTERNAL
*/
public void setQueryTimeout(int seconds) throws SQLException {
try {
debugCodeCall("setQueryTimeout", seconds);
checkClosed();
setQueryTimeout = prepareCommand("SET QUERY_TIMEOUT ?",
setQueryTimeout);
setQueryTimeout.getParameters().get(0)
.setValue(ValueInt.get(seconds * 1000), false);
setQueryTimeout.executeUpdate(null);
queryTimeoutCache = seconds;
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* INTERNAL
*/
int getQueryTimeout() throws SQLException {
try {
if (queryTimeoutCache == -1) {
checkClosed();
getQueryTimeout = prepareCommand(
"SELECT VALUE FROM INFORMATION_SCHEMA.SETTINGS "
+ "WHERE NAME=?",
getQueryTimeout);
getQueryTimeout.getParameters().get(0)
.setValue(ValueString.get("QUERY_TIMEOUT"), false);
ResultInterface result = getQueryTimeout.executeQuery(0, false);
result.next();
int queryTimeout = result.currentRow()[0].getInt();
result.close();
if (queryTimeout != 0) {
// round to the next second, otherwise 999 millis would
// return 0 seconds
queryTimeout = (queryTimeout + 999) / 1000;
}
queryTimeoutCache = queryTimeout;
}
return queryTimeoutCache;
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Returns the current transaction isolation level.
*
* @return the isolation level
* @throws SQLException if the connection is closed
*/
@Override
public int getTransactionIsolation() throws SQLException {
try {
debugCodeCall("getTransactionIsolation");
checkClosed();
return session.getIsolationLevel().getJdbc();
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Changes the current result set holdability.
*
* @param holdability ResultSet.HOLD_CURSORS_OVER_COMMIT or
* ResultSet.CLOSE_CURSORS_AT_COMMIT;
* @throws SQLException if the connection is closed or the holdability is
* not supported
*/
@Override
public void setHoldability(int holdability) throws SQLException {
try {
debugCodeCall("setHoldability", holdability);
checkClosed();
checkHoldability(holdability);
this.holdability = holdability;
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Returns the current result set holdability.
*
* @return the holdability
* @throws SQLException if the connection is closed
*/
@Override
public int getHoldability() throws SQLException {
try {
debugCodeCall("getHoldability");
checkClosed();
return holdability;
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Gets the type map.
*
* @return null
* @throws SQLException if the connection is closed
*/
@Override
public Map> getTypeMap() throws SQLException {
try {
debugCodeCall("getTypeMap");
checkClosed();
return null;
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* [Partially supported] Sets the type map. This is only supported if the
* map is empty or null.
*/
@Override
public void setTypeMap(Map> map) throws SQLException {
try {
if (isDebugEnabled()) {
debugCode("setTypeMap(" + quoteMap(map) + ");");
}
checkMap(map);
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Creates a new callable statement.
*
* @param sql the SQL statement
* @return the callable statement
* @throws SQLException if the connection is closed or the statement is not
* valid
*/
@Override
public CallableStatement prepareCall(String sql) throws SQLException {
try {
int id = getNextId(TraceObject.CALLABLE_STATEMENT);
if (isDebugEnabled()) {
debugCodeAssign("CallableStatement",
TraceObject.CALLABLE_STATEMENT, id,
"prepareCall(" + quote(sql) + ")");
}
checkClosed();
sql = translateSQL(sql);
return new JdbcCallableStatement(this, sql, id,
ResultSet.TYPE_FORWARD_ONLY,
Constants.DEFAULT_RESULT_SET_CONCURRENCY);
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Creates a callable statement with the specified result set type and
* concurrency.
*
* @param sql the SQL statement
* @param resultSetType the result set type (ResultSet.TYPE_*)
* @param resultSetConcurrency the concurrency (ResultSet.CONCUR_*)
* @return the callable statement
* @throws SQLException if the connection is closed or the result set type
* or concurrency are not supported
*/
@Override
public CallableStatement prepareCall(String sql, int resultSetType,
int resultSetConcurrency) throws SQLException {
try {
int id = getNextId(TraceObject.CALLABLE_STATEMENT);
if (isDebugEnabled()) {
debugCodeAssign("CallableStatement",
TraceObject.CALLABLE_STATEMENT, id,
"prepareCall(" + quote(sql) + ", " + resultSetType
+ ", " + resultSetConcurrency + ")");
}
checkTypeConcurrency(resultSetType, resultSetConcurrency);
checkClosed();
sql = translateSQL(sql);
return new JdbcCallableStatement(this, sql, id, resultSetType,
resultSetConcurrency);
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Creates a callable statement with the specified result set type,
* concurrency, and holdability.
*
* @param sql the SQL statement
* @param resultSetType the result set type (ResultSet.TYPE_*)
* @param resultSetConcurrency the concurrency (ResultSet.CONCUR_*)
* @param resultSetHoldability the holdability (ResultSet.HOLD* / CLOSE*)
* @return the callable statement
* @throws SQLException if the connection is closed or the result set type,
* concurrency, or holdability are not supported
*/
@Override
public CallableStatement prepareCall(String sql, int resultSetType,
int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
try {
int id = getNextId(TraceObject.CALLABLE_STATEMENT);
if (isDebugEnabled()) {
debugCodeAssign("CallableStatement",
TraceObject.CALLABLE_STATEMENT, id,
"prepareCall(" + quote(sql) + ", " + resultSetType
+ ", " + resultSetConcurrency + ", "
+ resultSetHoldability + ")");
}
checkTypeConcurrency(resultSetType, resultSetConcurrency);
checkHoldability(resultSetHoldability);
checkClosed();
sql = translateSQL(sql);
return new JdbcCallableStatement(this, sql, id, resultSetType,
resultSetConcurrency);
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Creates a new unnamed savepoint.
*
* @return the new savepoint
*/
@Override
public Savepoint setSavepoint() throws SQLException {
try {
int id = getNextId(TraceObject.SAVEPOINT);
if (isDebugEnabled()) {
debugCodeAssign("Savepoint", TraceObject.SAVEPOINT, id,
"setSavepoint()");
}
checkClosed();
CommandInterface set = prepareCommand(
"SAVEPOINT " + JdbcSavepoint.getName(null, savepointId),
Integer.MAX_VALUE);
set.executeUpdate(null);
JdbcSavepoint savepoint = new JdbcSavepoint(this, savepointId, null,
trace, id);
savepointId++;
return savepoint;
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Creates a new named savepoint.
*
* @param name the savepoint name
* @return the new savepoint
*/
@Override
public Savepoint setSavepoint(String name) throws SQLException {
try {
int id = getNextId(TraceObject.SAVEPOINT);
if (isDebugEnabled()) {
debugCodeAssign("Savepoint", TraceObject.SAVEPOINT, id,
"setSavepoint(" + quote(name) + ")");
}
checkClosed();
CommandInterface set = prepareCommand(
"SAVEPOINT " + JdbcSavepoint.getName(name, 0),
Integer.MAX_VALUE);
set.executeUpdate(null);
return new JdbcSavepoint(this, 0, name, trace, id);
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Rolls back to a savepoint.
*
* @param savepoint the savepoint
*/
@Override
public void rollback(Savepoint savepoint) throws SQLException {
try {
JdbcSavepoint sp = convertSavepoint(savepoint);
if (isDebugEnabled()) {
debugCode("rollback(" + sp.getTraceObjectName() + ");");
}
checkClosedForWrite();
sp.rollback();
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Releases a savepoint.
*
* @param savepoint the savepoint to release
*/
@Override
public void releaseSavepoint(Savepoint savepoint) throws SQLException {
try {
debugCode("releaseSavepoint(savepoint);");
checkClosed();
convertSavepoint(savepoint).release();
} catch (Exception e) {
throw logAndConvert(e);
}
}
private static JdbcSavepoint convertSavepoint(Savepoint savepoint) {
if (!(savepoint instanceof JdbcSavepoint)) {
throw DbException.get(ErrorCode.SAVEPOINT_IS_INVALID_1,
String.valueOf(savepoint));
}
return (JdbcSavepoint) savepoint;
}
/**
* Creates a prepared statement with the specified result set type,
* concurrency, and holdability.
*
* @param sql the SQL statement
* @param resultSetType the result set type (ResultSet.TYPE_*)
* @param resultSetConcurrency the concurrency (ResultSet.CONCUR_*)
* @param resultSetHoldability the holdability (ResultSet.HOLD* / CLOSE*)
* @return the prepared statement
* @throws SQLException if the connection is closed or the result set type,
* concurrency, or holdability are not supported
*/
@Override
public PreparedStatement prepareStatement(String sql, int resultSetType,
int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
try {
int id = getNextId(TraceObject.PREPARED_STATEMENT);
if (isDebugEnabled()) {
debugCodeAssign("PreparedStatement",
TraceObject.PREPARED_STATEMENT, id,
"prepareStatement(" + quote(sql) + ", " + resultSetType
+ ", " + resultSetConcurrency + ", "
+ resultSetHoldability + ")");
}
checkTypeConcurrency(resultSetType, resultSetConcurrency);
checkHoldability(resultSetHoldability);
checkClosed();
sql = translateSQL(sql);
return new JdbcPreparedStatement(this, sql, id, resultSetType,
resultSetConcurrency, false, null);
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Creates a new prepared statement.
*
* @param sql the SQL statement
* @param autoGeneratedKeys
* {@link Statement#RETURN_GENERATED_KEYS} if generated keys should
* be available for retrieval, {@link Statement#NO_GENERATED_KEYS} if
* generated keys should not be available
* @return the prepared statement
* @throws SQLException if the connection is closed
*/
@Override
public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
throws SQLException {
try {
int id = getNextId(TraceObject.PREPARED_STATEMENT);
if (isDebugEnabled()) {
debugCodeAssign("PreparedStatement",
TraceObject.PREPARED_STATEMENT, id,
"prepareStatement(" + quote(sql) + ", "
+ autoGeneratedKeys + ");");
}
checkClosed();
sql = translateSQL(sql);
return new JdbcPreparedStatement(this, sql, id,
ResultSet.TYPE_FORWARD_ONLY,
Constants.DEFAULT_RESULT_SET_CONCURRENCY, false,
autoGeneratedKeys == Statement.RETURN_GENERATED_KEYS);
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Creates a new prepared statement.
*
* @param sql the SQL statement
* @param columnIndexes
* an array of column indexes indicating the columns with generated
* keys that should be returned from the inserted row
* @return the prepared statement
* @throws SQLException if the connection is closed
*/
@Override
public PreparedStatement prepareStatement(String sql, int[] columnIndexes)
throws SQLException {
try {
int id = getNextId(TraceObject.PREPARED_STATEMENT);
if (isDebugEnabled()) {
debugCodeAssign("PreparedStatement",
TraceObject.PREPARED_STATEMENT, id,
"prepareStatement(" + quote(sql) + ", "
+ quoteIntArray(columnIndexes) + ");");
}
checkClosed();
sql = translateSQL(sql);
return new JdbcPreparedStatement(this, sql, id,
ResultSet.TYPE_FORWARD_ONLY,
Constants.DEFAULT_RESULT_SET_CONCURRENCY, false, columnIndexes);
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Creates a new prepared statement.
*
* @param sql the SQL statement
* @param columnNames
* an array of column names indicating the columns with generated
* keys that should be returned from the inserted row
* @return the prepared statement
* @throws SQLException if the connection is closed
*/
@Override
public PreparedStatement prepareStatement(String sql, String[] columnNames)
throws SQLException {
try {
int id = getNextId(TraceObject.PREPARED_STATEMENT);
if (isDebugEnabled()) {
debugCodeAssign("PreparedStatement",
TraceObject.PREPARED_STATEMENT, id,
"prepareStatement(" + quote(sql) + ", "
+ quoteArray(columnNames) + ");");
}
checkClosed();
sql = translateSQL(sql);
return new JdbcPreparedStatement(this, sql, id,
ResultSet.TYPE_FORWARD_ONLY,
Constants.DEFAULT_RESULT_SET_CONCURRENCY, false, columnNames);
} catch (Exception e) {
throw logAndConvert(e);
}
}
// =============================================================
/**
* Prepare an command. This will parse the SQL statement.
*
* @param sql the SQL statement
* @param fetchSize the fetch size (used in remote connections)
* @return the command
*/
CommandInterface prepareCommand(String sql, int fetchSize) {
return session.prepareCommand(sql, fetchSize);
}
private CommandInterface prepareCommand(String sql, CommandInterface old) {
return old == null ? session.prepareCommand(sql, Integer.MAX_VALUE)
: old;
}
private static int translateGetEnd(String sql, int i, char c) {
int len = sql.length();
switch (c) {
case '$': {
if (i < len - 1 && sql.charAt(i + 1) == '$'
&& (i == 0 || sql.charAt(i - 1) <= ' ')) {
int j = sql.indexOf("$$", i + 2);
if (j < 0) {
throw DbException.getSyntaxError(sql, i);
}
return j + 1;
}
return i;
}
case '\'': {
int j = sql.indexOf('\'', i + 1);
if (j < 0) {
throw DbException.getSyntaxError(sql, i);
}
return j;
}
case '"': {
int j = sql.indexOf('"', i + 1);
if (j < 0) {
throw DbException.getSyntaxError(sql, i);
}
return j;
}
case '/': {
checkRunOver(i + 1, len, sql);
if (sql.charAt(i + 1) == '*') {
// block comment
int j = sql.indexOf("*/", i + 2);
if (j < 0) {
throw DbException.getSyntaxError(sql, i);
}
i = j + 1;
} else if (sql.charAt(i + 1) == '/') {
// single line comment
i += 2;
while (i < len && (c = sql.charAt(i)) != '\r' && c != '\n') {
i++;
}
}
return i;
}
case '-': {
checkRunOver(i + 1, len, sql);
if (sql.charAt(i + 1) == '-') {
// single line comment
i += 2;
while (i < len && (c = sql.charAt(i)) != '\r' && c != '\n') {
i++;
}
}
return i;
}
default:
throw DbException.throwInternalError("c=" + c);
}
}
/**
* Convert JDBC escape sequences in the SQL statement. This method throws an
* exception if the SQL statement is null.
*
* @param sql the SQL statement with or without JDBC escape sequences
* @return the SQL statement without JDBC escape sequences
*/
private static String translateSQL(String sql) {
return translateSQL(sql, true);
}
/**
* Convert JDBC escape sequences in the SQL statement if required. This
* method throws an exception if the SQL statement is null.
*
* @param sql the SQL statement with or without JDBC escape sequences
* @param escapeProcessing whether escape sequences should be replaced
* @return the SQL statement without JDBC escape sequences
*/
static String translateSQL(String sql, boolean escapeProcessing) {
if (sql == null) {
throw DbException.getInvalidValueException("SQL", null);
}
if (!escapeProcessing) {
return sql;
}
if (sql.indexOf('{') < 0) {
return sql;
}
int len = sql.length();
char[] chars = null;
int level = 0;
for (int i = 0; i < len; i++) {
char c = sql.charAt(i);
switch (c) {
case '\'':
case '"':
case '/':
case '-':
i = translateGetEnd(sql, i, c);
break;
case '{':
level++;
if (chars == null) {
chars = sql.toCharArray();
}
chars[i] = ' ';
while (Character.isSpaceChar(chars[i])) {
i++;
checkRunOver(i, len, sql);
}
int start = i;
if (chars[i] >= '0' && chars[i] <= '9') {
chars[i - 1] = '{';
while (true) {
checkRunOver(i, len, sql);
c = chars[i];
if (c == '}') {
break;
}
switch (c) {
case '\'':
case '"':
case '/':
case '-':
i = translateGetEnd(sql, i, c);
break;
default:
}
i++;
}
level--;
break;
} else if (chars[i] == '?') {
i++;
checkRunOver(i, len, sql);
while (Character.isSpaceChar(chars[i])) {
i++;
checkRunOver(i, len, sql);
}
if (sql.charAt(i) != '=') {
throw DbException.getSyntaxError(sql, i, "=");
}
i++;
checkRunOver(i, len, sql);
while (Character.isSpaceChar(chars[i])) {
i++;
checkRunOver(i, len, sql);
}
}
while (!Character.isSpaceChar(chars[i])) {
i++;
checkRunOver(i, len, sql);
}
int remove = 0;
if (found(sql, start, "fn")) {
remove = 2;
} else if (found(sql, start, "escape")) {
break;
} else if (found(sql, start, "call")) {
break;
} else if (found(sql, start, "oj")) {
remove = 2;
} else if (found(sql, start, "ts")) {
break;
} else if (found(sql, start, "t")) {
break;
} else if (found(sql, start, "d")) {
break;
} else if (found(sql, start, "params")) {
remove = "params".length();
}
for (i = start; remove > 0; i++, remove--) {
chars[i] = ' ';
}
break;
case '}':
if (--level < 0) {
throw DbException.getSyntaxError(sql, i);
}
chars[i] = ' ';
break;
case '$':
i = translateGetEnd(sql, i, c);
break;
default:
}
}
if (level != 0) {
throw DbException.getSyntaxError(sql, sql.length() - 1);
}
if (chars != null) {
sql = new String(chars);
}
return sql;
}
private static void checkRunOver(int i, int len, String sql) {
if (i >= len) {
throw DbException.getSyntaxError(sql, i);
}
}
private static boolean found(String sql, int start, String other) {
return sql.regionMatches(true, start, other, 0, other.length());
}
private static void checkTypeConcurrency(int resultSetType,
int resultSetConcurrency) {
switch (resultSetType) {
case ResultSet.TYPE_FORWARD_ONLY:
case ResultSet.TYPE_SCROLL_INSENSITIVE:
case ResultSet.TYPE_SCROLL_SENSITIVE:
break;
default:
throw DbException.getInvalidValueException("resultSetType",
resultSetType);
}
switch (resultSetConcurrency) {
case ResultSet.CONCUR_READ_ONLY:
case ResultSet.CONCUR_UPDATABLE:
break;
default:
throw DbException.getInvalidValueException("resultSetConcurrency",
resultSetConcurrency);
}
}
private static void checkHoldability(int resultSetHoldability) {
// TODO compatibility / correctness: DBPool uses
// ResultSet.HOLD_CURSORS_OVER_COMMIT
if (resultSetHoldability != ResultSet.HOLD_CURSORS_OVER_COMMIT
&& resultSetHoldability != ResultSet.CLOSE_CURSORS_AT_COMMIT) {
throw DbException.getInvalidValueException("resultSetHoldability",
resultSetHoldability);
}
}
/**
* INTERNAL. Check if this connection is closed. The next operation is a
* read request.
*
* @throws DbException if the connection or session is closed
*/
protected void checkClosed() {
checkClosed(false);
}
/**
* Check if this connection is closed. The next operation may be a write
* request.
*
* @throws DbException if the connection or session is closed
*/
private void checkClosedForWrite() {
checkClosed(true);
}
/**
* INTERNAL. Check if this connection is closed.
*
* @param write if the next operation is possibly writing
* @throws DbException if the connection or session is closed
*/
protected void checkClosed(boolean write) {
if (session == null) {
throw DbException.get(ErrorCode.OBJECT_CLOSED);
}
if (session.isClosed()) {
throw DbException.get(ErrorCode.DATABASE_CALLED_AT_SHUTDOWN);
}
}
String getURL() {
checkClosed();
return url;
}
String getUser() {
checkClosed();
return user;
}
private void rollbackInternal() {
rollback = prepareCommand("ROLLBACK", rollback);
rollback.executeUpdate(null);
}
/**
* INTERNAL
*/
public int getPowerOffCount() {
return (session == null || session.isClosed()) ? 0
: session.getPowerOffCount();
}
/**
* INTERNAL
*/
public void setPowerOffCount(int count) {
if (session != null) {
session.setPowerOffCount(count);
}
}
/**
* INTERNAL
*/
public void setExecutingStatement(Statement stat) {
executingStatement = stat;
}
/**
* INTERNAL
*/
boolean scopeGeneratedKeys() {
return scopeGeneratedKeys;
}
/**
* INTERNAL
*/
JdbcResultSet getGeneratedKeys(JdbcStatement stat, int id) {
getGeneratedKeys = prepareCommand(
"SELECT SCOPE_IDENTITY() "
+ "WHERE SCOPE_IDENTITY() IS NOT NULL",
getGeneratedKeys);
ResultInterface result = getGeneratedKeys.executeQuery(0, false);
return new JdbcResultSet(this, stat, getGeneratedKeys, result,
id, false, true, false);
}
/**
* Create a new empty Clob object.
*
* @return the object
*/
@Override
public Clob createClob() throws SQLException {
try {
int id = getNextId(TraceObject.CLOB);
debugCodeAssign("Clob", TraceObject.CLOB, id, "createClob()");
checkClosedForWrite();
return new JdbcClob(this, ValueString.EMPTY, JdbcLob.State.NEW, id);
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Create a new empty Blob object.
*
* @return the object
*/
@Override
public Blob createBlob() throws SQLException {
try {
int id = getNextId(TraceObject.BLOB);
debugCodeAssign("Blob", TraceObject.BLOB, id, "createClob()");
checkClosedForWrite();
return new JdbcBlob(this, ValueBytes.EMPTY, JdbcLob.State.NEW, id);
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Create a new empty NClob object.
*
* @return the object
*/
@Override
public NClob createNClob() throws SQLException {
try {
int id = getNextId(TraceObject.CLOB);
debugCodeAssign("NClob", TraceObject.CLOB, id, "createNClob()");
checkClosedForWrite();
return new JdbcClob(this, ValueString.EMPTY, JdbcLob.State.NEW, id);
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Create a new SQLXML object with no data.
*
* @return the object
*/
@Override
public SQLXML createSQLXML() throws SQLException {
try {
int id = getNextId(TraceObject.SQLXML);
debugCodeAssign("SQLXML", TraceObject.SQLXML, id, "createSQLXML()");
checkClosedForWrite();
return new JdbcSQLXML(this, ValueString.EMPTY, JdbcLob.State.NEW, id);
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* Create a new Array object.
*
* @param typeName the type name
* @param elements the values
* @return the array
*/
@Override
public Array createArrayOf(String typeName, Object[] elements)
throws SQLException {
try {
int id = getNextId(TraceObject.ARRAY);
debugCodeAssign("Array", TraceObject.ARRAY, id, "createArrayOf()");
checkClosed();
Value value = DataType.convertToValue(session, elements,
Value.ARRAY);
return new JdbcArray(this, value, id);
} catch (Exception e) {
throw logAndConvert(e);
}
}
/**
* [Not supported] Create a new empty Struct object.
*/
@Override
public Struct createStruct(String typeName, Object[] attributes)
throws SQLException {
throw unsupported("Struct");
}
/**
* Returns true if this connection is still valid.
*
* @param timeout the number of seconds to wait for the database to respond
* (ignored)
* @return true if the connection is valid.
*/
@Override
public synchronized boolean isValid(int timeout) {
try {
debugCodeCall("isValid", timeout);
if (session == null || session.isClosed()) {
return false;
}
// force a network round trip (if networked)
getTransactionIsolation();
return true;
} catch (Exception e) {
// this method doesn't throw an exception, but it logs it
logAndConvert(e);
return false;
}
}
/**
* Set a client property. This method always throws a SQLClientInfoException
* in standard mode. In compatibility mode the following properties are
* supported:
*
* - DB2: The properties: ApplicationName, ClientAccountingInformation,
* ClientUser and ClientCorrelationToken are supported.
* - MySQL: All property names are supported.
* - Oracle: All properties in the form <namespace>.<key name>
* are supported.
* - PostgreSQL: The ApplicationName property is supported.
*
* For unsupported properties a SQLClientInfoException is thrown.
*
* @param name the name of the property
* @param value the value
*/
@Override
public void setClientInfo(String name, String value)
throws SQLClientInfoException {
try {
if (isDebugEnabled()) {
debugCode("setClientInfo(" + quote(name) + ", " + quote(value)
+ ");");
}
checkClosed();
// no change to property: Ignore call. This early exit fixes a
// problem with websphere liberty resetting the client info of a
// pooled connection to its initial values.
if (Objects.equals(value, getClientInfo(name))) {
return;
}
if (isInternalProperty(name)) {
throw new SQLClientInfoException(
"Property name '" + name + " is used internally by H2.",
Collections. emptyMap());
}
Pattern clientInfoNameRegEx = getMode().supportedClientInfoPropertiesRegEx;
if (clientInfoNameRegEx != null
&& clientInfoNameRegEx.matcher(name).matches()) {
if (clientInfo == null) {
clientInfo = new HashMap<>();
}
clientInfo.put(name, value);
} else {
throw new SQLClientInfoException(
"Client info name '" + name + "' not supported.",
Collections. emptyMap());
}
} catch (Exception e) {
throw convertToClientInfoException(logAndConvert(e));
}
}
private static boolean isInternalProperty(String name) {
return NUM_SERVERS.equals(name) || name.startsWith(PREFIX_SERVER);
}
private static SQLClientInfoException convertToClientInfoException(
SQLException x) {
if (x instanceof SQLClientInfoException) {
return (SQLClientInfoException) x;
}
return new SQLClientInfoException(x.getMessage(), x.getSQLState(),
x.getErrorCode(), null, null);
}
/**
* Set the client properties. This replaces all existing properties. This
* method always throws a SQLClientInfoException in standard mode. In
* compatibility mode some properties may be supported (see
* setProperty(String, String) for details).
*
* @param properties the properties (ignored)
*/
@Override
public void setClientInfo(Properties properties)
throws SQLClientInfoException {
try {
if (isDebugEnabled()) {
debugCode("setClientInfo(properties);");
}
checkClosed();
if (clientInfo == null) {
clientInfo = new HashMap<>();
} else {
clientInfo.clear();
}
for (Map.Entry