| /* |
| * 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"); |
| } |
| } |