blob: 23b98af66422805111e3ca66a2a6998e7a6fe5f5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.asterix.jdbc.core;
import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
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.SQLPermission;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
public class ADBConnection extends ADBWrapperSupport implements Connection {
protected final ADBProtocolBase protocol;
protected final String url;
protected final String databaseVersion;
protected final ADBDriverProperty.CatalogDataverseMode catalogDataverseMode;
protected final boolean catalogIncludesSchemaless;
protected final boolean sqlCompatMode;
private final AtomicBoolean closed;
private final ConcurrentLinkedQueue<ADBStatement> statements;
private volatile SQLWarning warning;
private volatile ADBMetaStatement metaStatement;
private volatile String catalog;
private volatile String schema;
// Lifecycle
public ADBConnection(ADBProtocolBase protocol, String url, String databaseVersion, String dataverseCanonicalName,
Map<ADBDriverProperty, Object> properties, SQLWarning connectWarning) throws SQLException {
this.url = Objects.requireNonNull(url);
this.protocol = Objects.requireNonNull(protocol);
this.databaseVersion = databaseVersion;
this.statements = new ConcurrentLinkedQueue<>();
this.warning = connectWarning;
this.closed = new AtomicBoolean(false);
this.sqlCompatMode = (Boolean) ADBDriverProperty.Common.SQL_COMPAT_MODE.fetchPropertyValue(properties);
this.catalogDataverseMode = getCatalogDataverseMode(properties, protocol.getErrorReporter());
this.catalogIncludesSchemaless =
(Boolean) ADBDriverProperty.Common.CATALOG_INCLUDES_SCHEMALESS.fetchPropertyValue(properties);
initCatalogSchema(protocol, dataverseCanonicalName);
}
protected void initCatalogSchema(ADBProtocolBase protocol, String dataverseCanonicalName) throws SQLException {
switch (catalogDataverseMode) {
case CATALOG:
catalog = dataverseCanonicalName == null || dataverseCanonicalName.isEmpty()
? protocol.getDefaultDataverse() : dataverseCanonicalName;
// schema = null
break;
case CATALOG_SCHEMA:
if (dataverseCanonicalName == null || dataverseCanonicalName.isEmpty()) {
catalog = protocol.getDefaultDataverse();
// schema = null
} else {
String[] parts = dataverseCanonicalName.split("/");
switch (parts.length) {
case 1:
catalog = parts[0];
break;
case 2:
catalog = parts[0];
schema = parts[1];
break;
default:
throw protocol.getErrorReporter().errorInConnection(dataverseCanonicalName); //TODO:FIXME
}
}
break;
default:
throw new IllegalStateException();
}
}
@Override
public void close() throws SQLException {
closeImpl(null);
}
@Override
public void abort(Executor executor) throws SQLException {
if (executor == null) {
throw getErrorReporter().errorParameterValueNotSupported("executor");
}
SecurityManager sec = System.getSecurityManager();
if (sec != null) {
sec.checkPermission(new SQLPermission("callAbort"));
}
closeImpl(executor);
}
protected void closeImpl(Executor executor) throws SQLException {
boolean wasClosed = closed.getAndSet(true);
if (wasClosed) {
return;
}
if (executor == null) {
closeStatementsAndProtocol();
} else {
executor.execute(() -> {
try {
closeStatementsAndProtocol();
} catch (SQLException e) {
if (getLogger().isLoggable(Level.FINE)) {
getLogger().log(Level.FINE, e.getMessage(), e);
}
}
});
}
}
protected void closeStatementsAndProtocol() throws SQLException {
SQLException err = null;
try {
closeRegisteredStatements();
} catch (SQLException e) {
err = e;
}
try {
protocol.close();
} catch (SQLException e) {
if (err != null) {
e.addSuppressed(err);
}
err = e;
}
if (err != null) {
throw err;
}
}
@Override
public boolean isClosed() {
return closed.get();
}
private void checkClosed() throws SQLException {
if (isClosed()) {
throw getErrorReporter().errorObjectClosed(Connection.class);
}
}
// Connectivity
@Override
public boolean isValid(int timeoutSeconds) throws SQLException {
if (isClosed()) {
return false;
}
if (timeoutSeconds < 0) {
throw getErrorReporter().errorParameterValueNotSupported("timeoutSeconds");
}
return protocol.ping(timeoutSeconds);
}
@Override
public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
throw getErrorReporter().errorMethodNotSupported(Connection.class, "setNetworkTimeout");
}
@Override
public int getNetworkTimeout() throws SQLException {
throw getErrorReporter().errorMethodNotSupported(Connection.class, "getNetworkTimeout");
}
// Metadata
@Override
public DatabaseMetaData getMetaData() throws SQLException {
checkClosed();
ADBMetaStatement metaStatement = getOrCreateMetaStatement();
return createDatabaseMetaData(metaStatement);
}
private ADBMetaStatement getOrCreateMetaStatement() {
ADBMetaStatement stmt = metaStatement;
if (stmt == null) {
synchronized (this) {
stmt = metaStatement;
if (stmt == null) {
stmt = createMetaStatement();
registerStatement(stmt);
metaStatement = stmt;
}
}
}
return stmt;
}
protected ADBMetaStatement createMetaStatement() {
return new ADBMetaStatement(this);
}
protected ADBDatabaseMetaData createDatabaseMetaData(ADBMetaStatement metaStatement) {
return new ADBDatabaseMetaData(metaStatement, databaseVersion);
}
// Statement construction
@Override
public Statement createStatement() throws SQLException {
checkClosed();
return createStatementImpl();
}
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
return createStatement(resultSetType, resultSetConcurrency, getHoldability());
}
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
checkClosed();
checkResultSetConfig(resultSetType, resultSetConcurrency, resultSetHoldability);
return createStatementImpl();
}
private void checkResultSetConfig(int resultSetType, int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
boolean ok = resultSetType == ResultSet.TYPE_FORWARD_ONLY && resultSetConcurrency == ResultSet.CONCUR_READ_ONLY;
if (!ok) {
throw getErrorReporter().errorParameterValueNotSupported("resultSetType/resultSetConcurrency");
}
if (resultSetHoldability != ADBResultSet.RESULT_SET_HOLDABILITY) {
if (getLogger().isLoggable(Level.FINE)) {
getLogger().log(Level.FINE,
getErrorReporter().warningParameterValueNotSupported("ResultSetHoldability"));
}
}
}
protected ADBStatement createStatementImpl() {
ADBStatement stmt = new ADBStatement(this);
registerStatement(stmt);
return stmt;
}
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
checkClosed();
return prepareStatementImpl(sql);
}
@Override
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
throws SQLException {
return prepareStatement(sql, resultSetType, resultSetConcurrency, getHoldability());
}
@Override
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency,
int resultSetHoldability) throws SQLException {
checkClosed();
checkResultSetConfig(resultSetType, resultSetConcurrency, resultSetHoldability);
return prepareStatementImpl(sql);
}
@Override
public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
throw getErrorReporter().errorMethodNotSupported(Connection.class, "prepareStatement");
}
@Override
public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
throw getErrorReporter().errorMethodNotSupported(Connection.class, "prepareStatement");
}
@Override
public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
throw getErrorReporter().errorMethodNotSupported(Connection.class, "prepareStatement");
}
private ADBPreparedStatement prepareStatementImpl(String sql) throws SQLException {
ADBPreparedStatement stmt = new ADBPreparedStatement(this, sql);
registerStatement(stmt);
return stmt;
}
@Override
public CallableStatement prepareCall(String sql) throws SQLException {
throw getErrorReporter().errorMethodNotSupported(Connection.class, "prepareCall");
}
@Override
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
throw getErrorReporter().errorMethodNotSupported(Connection.class, "prepareCall");
}
@Override
public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency,
int resultSetHoldability) throws SQLException {
throw getErrorReporter().errorMethodNotSupported(Connection.class, "prepareCall");
}
@Override
public String nativeSQL(String sql) throws SQLException {
checkClosed();
return sql;
}
@Override
public String getCatalog() throws SQLException {
checkClosed();
return catalog;
}
@Override
public void setCatalog(String catalog) throws SQLException {
checkClosed();
this.catalog = catalog;
}
@Override
public String getSchema() throws SQLException {
checkClosed();
return schema;
}
@Override
public void setSchema(String schema) throws SQLException {
checkClosed();
if (catalogDataverseMode == ADBDriverProperty.CatalogDataverseMode.CATALOG
&& (schema != null && !schema.isEmpty())) {
throw getErrorReporter().errorInConnection(schema); //TODO:FIXME:REVIEW make no-op?
}
this.schema = schema;
}
protected String getDataverseCanonicalName() {
switch (catalogDataverseMode) {
case CATALOG:
return catalog;
case CATALOG_SCHEMA:
String c = catalog;
String s = schema;
return s == null ? c : c + "/" + s;
default:
throw new IllegalStateException();
}
}
protected static ADBDriverProperty.CatalogDataverseMode getCatalogDataverseMode(
Map<ADBDriverProperty, Object> properties, ADBErrorReporter errorReporter) throws SQLException {
int mode = ((Number) ADBDriverProperty.Common.CATALOG_DATAVERSE_MODE.fetchPropertyValue(properties)).intValue();
try {
return ADBDriverProperty.CatalogDataverseMode.valueOf(mode);
} catch (IllegalArgumentException e) {
throw errorReporter.errorInConnection(String.valueOf(mode)); //TODO:FIXME
}
}
// Statement lifecycle
private void registerStatement(ADBStatement stmt) {
statements.add(Objects.requireNonNull(stmt));
}
void deregisterStatement(ADBStatement stmt) {
statements.remove(Objects.requireNonNull(stmt));
}
private void closeRegisteredStatements() throws SQLException {
SQLException err = null;
ADBStatement statement;
while ((statement = statements.poll()) != null) {
try {
statement.closeImpl(true, false);
} catch (SQLException e) {
if (err != null) {
e.addSuppressed(err);
}
err = e;
}
}
if (err != null) {
throw err;
}
}
// Transaction control
@Override
public int getTransactionIsolation() throws SQLException {
checkClosed();
return Connection.TRANSACTION_READ_COMMITTED;
}
@Override
public void setTransactionIsolation(int level) throws SQLException {
checkClosed();
switch (level) {
case Connection.TRANSACTION_READ_COMMITTED:
break;
case Connection.TRANSACTION_READ_UNCOMMITTED:
case Connection.TRANSACTION_REPEATABLE_READ:
case Connection.TRANSACTION_SERIALIZABLE:
if (getLogger().isLoggable(Level.FINE)) {
getLogger().log(Level.FINE,
getErrorReporter().warningParameterValueNotSupported("TransactionIsolationLevel"));
}
break;
default:
throw getErrorReporter().errorParameterValueNotSupported("TransactionIsolationLevel");
}
}
@Override
public int getHoldability() throws SQLException {
checkClosed();
return ResultSet.HOLD_CURSORS_OVER_COMMIT;
}
@Override
public void setHoldability(int holdability) throws SQLException {
checkClosed();
switch (holdability) {
case ResultSet.HOLD_CURSORS_OVER_COMMIT:
break;
case ResultSet.CLOSE_CURSORS_AT_COMMIT:
if (getLogger().isLoggable(Level.FINE)) {
getLogger().log(Level.FINE, getErrorReporter().warningParameterValueNotSupported("Holdability"));
}
break;
default:
throw getErrorReporter().errorParameterValueNotSupported("Holdability");
}
}
@Override
public boolean getAutoCommit() throws SQLException {
checkClosed();
return true;
}
@Override
public void setAutoCommit(boolean autoCommit) throws SQLException {
checkClosed();
}
@Override
public void commit() throws SQLException {
checkClosed();
throw getErrorReporter().errorIncompatibleMode("AutoCommit");
}
@Override
public void rollback() throws SQLException {
checkClosed();
throw getErrorReporter().errorIncompatibleMode("AutoCommit");
}
@Override
public Savepoint setSavepoint() throws SQLException {
throw getErrorReporter().errorMethodNotSupported(Connection.class, "setSavepoint");
}
@Override
public Savepoint setSavepoint(String name) throws SQLException {
throw getErrorReporter().errorMethodNotSupported(Connection.class, "setSavepoint");
}
@Override
public void releaseSavepoint(Savepoint savepoint) throws SQLException {
throw getErrorReporter().errorMethodNotSupported(Connection.class, "releaseSavepoint");
}
@Override
public void rollback(Savepoint savepoint) throws SQLException {
throw getErrorReporter().errorMethodNotSupported(Connection.class, "rollback");
}
// Value construction
@Override
public Clob createClob() throws SQLException {
throw getErrorReporter().errorMethodNotSupported(Connection.class, "createClob");
}
@Override
public Blob createBlob() throws SQLException {
throw getErrorReporter().errorMethodNotSupported(Connection.class, "createBlob");
}
@Override
public NClob createNClob() throws SQLException {
throw getErrorReporter().errorMethodNotSupported(Connection.class, "createNClob");
}
@Override
public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
throw getErrorReporter().errorMethodNotSupported(Connection.class, "createArrayOf");
}
@Override
public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
throw getErrorReporter().errorMethodNotSupported(Connection.class, "createStruct");
}
@Override
public SQLXML createSQLXML() throws SQLException {
throw getErrorReporter().errorMethodNotSupported(Connection.class, "createSQLXML");
}
@Override
public Map<String, Class<?>> getTypeMap() throws SQLException {
checkClosed();
return Collections.emptyMap();
}
@Override
public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
throw getErrorReporter().errorMethodNotSupported(Connection.class, "setTypeMap");
}
// Unsupported hints (ignored)
@Override
public boolean isReadOnly() throws SQLException {
checkClosed();
return false;
}
@Override
public void setReadOnly(boolean readOnly) throws SQLException {
checkClosed();
}
// Errors and warnings
@Override
public SQLWarning getWarnings() throws SQLException {
checkClosed();
return warning;
}
@Override
public void clearWarnings() throws SQLException {
checkClosed();
warning = null;
}
@Override
protected ADBErrorReporter getErrorReporter() {
return protocol.getErrorReporter();
}
protected Logger getLogger() {
return protocol.getLogger();
}
// Miscellaneous unsupported features (error is raised)
@Override
public String getClientInfo(String name) throws SQLException {
checkClosed();
return null;
}
@Override
public Properties getClientInfo() throws SQLException {
checkClosed();
return new Properties();
}
@Override
public void setClientInfo(Properties properties) throws SQLClientInfoException {
throw getErrorReporter().errorClientInfoMethodNotSupported(Connection.class, "setClientInfo");
}
@Override
public void setClientInfo(String name, String value) throws SQLClientInfoException {
throw getErrorReporter().errorClientInfoMethodNotSupported(Connection.class, "setClientInfo");
}
}