All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.org.apache.calcite.avatica.AvaticaConnection Maven / Gradle / Ivy

/*
 * 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 com.hazelcast.org.apache.calcite.avatica;

import com.hazelcast.org.apache.calcite.avatica.ColumnMetaData.AvaticaType;
import com.hazelcast.org.apache.calcite.avatica.ColumnMetaData.Rep;
import com.hazelcast.org.apache.calcite.avatica.Meta.ExecuteBatchResult;
import com.hazelcast.org.apache.calcite.avatica.Meta.MetaResultSet;
import com.hazelcast.org.apache.calcite.avatica.remote.KerberosConnection;
import com.hazelcast.org.apache.calcite.avatica.remote.Service;
import com.hazelcast.org.apache.calcite.avatica.remote.Service.ErrorResponse;
import com.hazelcast.org.apache.calcite.avatica.remote.Service.OpenConnectionRequest;
import com.hazelcast.org.apache.calcite.avatica.remote.TypedValue;
import com.hazelcast.org.apache.calcite.avatica.util.ArrayFactoryImpl;

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.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.TimeZone;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Implementation of JDBC connection
 * for the Avatica framework.
 *
 * 

Abstract to allow newer versions of JDBC to add methods. */ public abstract class AvaticaConnection implements Connection { /** The name of the sole column returned by DML statements, containing * the number of rows modified. */ public static final String ROWCOUNT_COLUMN_NAME = "ROWCOUNT"; //TODO shouldn't we move this to BuiltInConnectionProperty ? public static final String NUM_EXECUTE_RETRIES_KEY = "avatica.statement.retries"; public static final String NUM_EXECUTE_RETRIES_DEFAULT = "5"; /** The name of the sole column returned by an EXPLAIN statement. * *

Actually Avatica does not care what this column is called, but here is * a useful place to define a suggested value. */ public static final String PLAN_COLUMN_NAME = "PLAN"; public static final Helper HELPER = Helper.INSTANCE; protected int statementCount; private boolean closed; private int holdability; private int networkTimeout; private KerberosConnection kerberosConnection; private Service service; public final String id; public final Meta.ConnectionHandle handle; protected final UnregisteredDriver driver; protected final AvaticaFactory factory; final String url; protected final Properties info; protected final Meta meta; protected final AvaticaSpecificDatabaseMetaData metaData; public final Map properties = new HashMap<>(); public final Map statementMap = new ConcurrentHashMap<>(); final Map flagMap = new ConcurrentHashMap<>(); protected final long maxRetriesPerExecute; protected final boolean transparentReconnectEnabled; /** * Creates an AvaticaConnection. * *

Not public; method is called only from the driver or a derived * class.

* * @param driver Driver * @param factory Factory for JDBC objects * @param url Server URL * @param info Other connection properties */ protected AvaticaConnection(UnregisteredDriver driver, AvaticaFactory factory, String url, Properties info) { this.id = UUID.randomUUID().toString(); this.handle = new Meta.ConnectionHandle(this.id); this.driver = driver; this.factory = factory; this.url = url; this.info = info; this.meta = driver.createMeta(this); this.metaData = factory.newDatabaseMetaData(this); try { this.holdability = metaData.getResultSetHoldability(); } catch (SQLException e) { // We know the impl doesn't throw this. throw new RuntimeException(e); } this.maxRetriesPerExecute = getNumStatementRetries(info); this.transparentReconnectEnabled = config().transparentReconnectionEnabled(); } /** Computes the number of retries * {@link AvaticaStatement#executeInternal(Meta.Signature, boolean)} * should retry before failing. */ long getNumStatementRetries(Properties props) { return Long.parseLong(Objects.requireNonNull(props) .getProperty(NUM_EXECUTE_RETRIES_KEY, NUM_EXECUTE_RETRIES_DEFAULT)); } /** Returns a view onto this connection's configuration properties. Code * in Avatica and derived projects should use this view rather than calling * {@link java.util.Properties#getProperty(String)}. Derived projects will * almost certainly subclass {@link ConnectionConfig} with their own * properties. */ public ConnectionConfig config() { return new ConnectionConfigImpl(info); } /** * Opens the connection on the server. */ public void openConnection() { // Open the connection on the server this.meta.openConnection(handle, OpenConnectionRequest.serializeProperties(info)); } protected void checkOpen() throws SQLException { if (isClosed()) { throw HELPER.closed(); } } // Connection methods public AvaticaStatement createStatement() throws SQLException { checkOpen(); //noinspection MagicConstant return createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, holdability); } public PreparedStatement prepareStatement(String sql) throws SQLException { checkOpen(); //noinspection MagicConstant return prepareStatement( sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, holdability); } public CallableStatement prepareCall(String sql) throws SQLException { throw HELPER.unsupported(); } public String nativeSQL(String sql) throws SQLException { throw HELPER.unsupported(); } public void setAutoCommit(boolean autoCommit) throws SQLException { checkOpen(); meta.connectionSync(handle, new ConnectionPropertiesImpl().setAutoCommit(autoCommit)); } public boolean getAutoCommit() throws SQLException { checkOpen(); return unbox(sync().isAutoCommit(), true); } public void commit() throws SQLException { checkOpen(); meta.commit(handle); } public void rollback() throws SQLException { checkOpen(); meta.rollback(handle); } public void close() throws SQLException { if (!closed) { closed = true; // Per specification, if onConnectionClose throws, this method will throw // a SQLException, but statement will still be closed. try { meta.closeConnection(handle); driver.handler.onConnectionClose(this); if (null != kerberosConnection) { kerberosConnection.stopRenewalThread(); } } catch (RuntimeException e) { throw HELPER.createException("While closing connection", e); } } } public boolean isClosed() throws SQLException { return closed; } public DatabaseMetaData getMetaData() throws SQLException { checkOpen(); return metaData; } public void setReadOnly(boolean readOnly) throws SQLException { checkOpen(); meta.connectionSync(handle, new ConnectionPropertiesImpl().setReadOnly(readOnly)); } public boolean isReadOnly() throws SQLException { checkOpen(); return unbox(sync().isReadOnly(), true); } public void setCatalog(String catalog) throws SQLException { checkOpen(); meta.connectionSync(handle, new ConnectionPropertiesImpl().setCatalog(catalog)); } public String getCatalog() throws SQLException { checkOpen(); return sync().getCatalog(); } public void setTransactionIsolation(int level) throws SQLException { checkOpen(); meta.connectionSync(handle, new ConnectionPropertiesImpl().setTransactionIsolation(level)); } public int getTransactionIsolation() throws SQLException { checkOpen(); //noinspection MagicConstant return unbox(sync().getTransactionIsolation(), TRANSACTION_NONE); } public SQLWarning getWarnings() throws SQLException { checkOpen(); return null; } public void clearWarnings() throws SQLException { checkOpen(); // no-op since connection pooling often calls this. } public Statement createStatement( int resultSetType, int resultSetConcurrency) throws SQLException { checkOpen(); //noinspection MagicConstant return createStatement(resultSetType, resultSetConcurrency, holdability); } public PreparedStatement prepareStatement( String sql, int resultSetType, int resultSetConcurrency) throws SQLException { checkOpen(); //noinspection MagicConstant return prepareStatement( sql, resultSetType, resultSetConcurrency, holdability); } public CallableStatement prepareCall( String sql, int resultSetType, int resultSetConcurrency) throws SQLException { throw HELPER.unsupported(); } public Map> getTypeMap() throws SQLException { throw HELPER.unsupported(); } public void setTypeMap(Map> map) throws SQLException { throw HELPER.unsupported(); } public void setHoldability(int holdability) throws SQLException { checkOpen(); if (!(holdability == ResultSet.CLOSE_CURSORS_AT_COMMIT || holdability == ResultSet.HOLD_CURSORS_OVER_COMMIT)) { throw new SQLException("invalid value"); } this.holdability = holdability; } public int getHoldability() throws SQLException { checkOpen(); return holdability; } public Savepoint setSavepoint() throws SQLException { throw HELPER.unsupported(); } public Savepoint setSavepoint(String name) throws SQLException { throw HELPER.unsupported(); } public void rollback(Savepoint savepoint) throws SQLException { throw HELPER.unsupported(); } public void releaseSavepoint(Savepoint savepoint) throws SQLException { throw HELPER.unsupported(); } public AvaticaStatement createStatement( int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { checkOpen(); return factory.newStatement(this, null, resultSetType, resultSetConcurrency, resultSetHoldability); } public PreparedStatement prepareStatement( String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { checkOpen(); try { final Meta.StatementHandle h = meta.prepare(handle, sql, -1); return factory.newPreparedStatement(this, h, h.signature, resultSetType, resultSetConcurrency, resultSetHoldability); } catch (RuntimeException e) { throw HELPER.createException("while preparing SQL: " + sql, e); } } public CallableStatement prepareCall( String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { throw HELPER.unsupported(); } public PreparedStatement prepareStatement( String sql, int autoGeneratedKeys) throws SQLException { throw HELPER.unsupported(); } public PreparedStatement prepareStatement( String sql, int[] columnIndexes) throws SQLException { throw HELPER.unsupported(); } public PreparedStatement prepareStatement( String sql, String[] columnNames) throws SQLException { throw HELPER.unsupported(); } public Clob createClob() throws SQLException { throw HELPER.unsupported(); } public Blob createBlob() throws SQLException { throw HELPER.unsupported(); } public NClob createNClob() throws SQLException { throw HELPER.unsupported(); } public SQLXML createSQLXML() throws SQLException { throw HELPER.unsupported(); } public boolean isValid(int timeout) throws SQLException { if (timeout < 0) { throw HELPER.createException("timeout is less than 0"); } // TODO check if connection is actually alive using timeout return !isClosed(); } public void setClientInfo(String name, String value) throws SQLClientInfoException { throw HELPER.clientInfo(); } public void setClientInfo(Properties properties) throws SQLClientInfoException { throw HELPER.clientInfo(); } public String getClientInfo(String name) throws SQLException { return getClientInfo().getProperty(name); } public Properties getClientInfo() throws SQLException { checkOpen(); return new Properties(); } public Array createArrayOf(String typeName, Object[] elements) throws SQLException { checkOpen(); @SuppressWarnings("unchecked") List elementList = (List) AvaticaUtils.primitiveList(elements); SqlType type; try { type = SqlType.valueOf(typeName); } catch (IllegalArgumentException e) { throw new SQLException("Could not find JDBC type for '" + typeName + "'"); } AvaticaType avaticaType = null; switch (type) { case ARRAY: // TODO: Nested ARRAYs throw HELPER.createException("Cannot create an ARRAY of ARRAY's"); case STRUCT: // TODO: ARRAYs of STRUCTs throw HELPER.createException("Cannot create an ARRAY of STRUCT's"); default: // This is an ARRAY, we need to use Objects, not primitives (nullable). avaticaType = ColumnMetaData.scalar(type.id, typeName, Rep.nonPrimitiveRepOf(type)); } ArrayFactoryImpl arrayFactory = new ArrayFactoryImpl(getTimeZone()); return arrayFactory.createArray(avaticaType, elementList); } public Struct createStruct(String typeName, Object[] attributes) throws SQLException { throw HELPER.unsupported(); } public void setSchema(String schema) throws SQLException { checkOpen(); meta.connectionSync(handle, new ConnectionPropertiesImpl().setSchema(schema)); } public String getSchema() throws SQLException { checkOpen(); return sync().getSchema(); } public void abort(Executor executor) throws SQLException { throw HELPER.unsupported(); } public void setNetworkTimeout( Executor executor, int milliseconds) throws SQLException { checkOpen(); this.networkTimeout = milliseconds; } public int getNetworkTimeout() throws SQLException { checkOpen(); return networkTimeout; } public T unwrap(Class iface) throws SQLException { if (iface.isInstance(this)) { return iface.cast(this); } throw HELPER.createException( "does not implement '" + iface + "'"); } public boolean isWrapperFor(Class iface) throws SQLException { return iface.isInstance(this); } /** Returns the time zone of this connection. Determines the offset applied * when converting datetime values from the database into * {@link java.sql.Timestamp} values. */ public TimeZone getTimeZone() { final String timeZoneName = config().timeZone(); return timeZoneName == null ? TimeZone.getDefault() : TimeZone.getTimeZone(timeZoneName); } /** * Executes a prepared query, closing any previously open result set. * * @param statement Statement * @param signature Prepared query * @param firstFrame First frame of rows, or null if we need to execute * @param state The state used to create the given result * @param isUpdate Was the caller context via {@link PreparedStatement#executeUpdate()}. * @return Result set * @throws java.sql.SQLException if a database error occurs */ protected ResultSet executeQueryInternal(AvaticaStatement statement, Meta.Signature signature, Meta.Frame firstFrame, QueryState state, boolean isUpdate) throws SQLException { // Close the previous open result set, if there is one. Meta.Frame frame = firstFrame; Meta.Signature signature2 = signature; synchronized (statement) { if (statement.openResultSet != null) { final AvaticaResultSet rs = statement.openResultSet; statement.openResultSet = null; try { rs.close(); } catch (Exception e) { throw HELPER.createException( "Error while closing previous result set", e); } } try { if (statement.isWrapperFor(AvaticaPreparedStatement.class)) { final AvaticaPreparedStatement pstmt = (AvaticaPreparedStatement) statement; Meta.StatementHandle handle = pstmt.handle; if (isUpdate) { // Make a copy of the StatementHandle, nulling out the Signature. // CALCITE-1086 we don't need to send the Signature to the server // when we're only performing an update. Saves on serialization. handle = new Meta.StatementHandle(handle.connectionId, handle.id, null); } final Meta.ExecuteResult executeResult = meta.execute(handle, pstmt.getParameterValues(), statement.getFetchSize()); final MetaResultSet metaResultSet = executeResult.resultSets.get(0); frame = metaResultSet.firstFrame; statement.updateCount = metaResultSet.updateCount; signature2 = executeResult.resultSets.get(0).signature; } } catch (Exception e) { throw HELPER.createException(e.getMessage(), e); } final TimeZone timeZone = getTimeZone(); if (frame == null && signature2 == null && statement.updateCount != -1) { statement.openResultSet = null; } else { // Duplicative SQL, for support non-prepared statements statement.openResultSet = factory.newResultSet(statement, state, signature2, timeZone, frame); } } // Release the monitor before executing, to give another thread the // opportunity to call cancel. try { if (statement.openResultSet != null) { statement.openResultSet.execute(); isUpdateCapable(statement); } } catch (Exception e) { throw HELPER.createException( "exception while executing query: " + e.getMessage(), e); } return statement.openResultSet; } /** Executes a batch update using an {@link AvaticaPreparedStatement}. * * @param pstmt The prepared statement. * @return An array of update counts containing one element for each command in the batch. */ protected long[] executeBatchUpdateInternal(AvaticaPreparedStatement pstmt) throws SQLException { try { // Get the handle from the statement Meta.StatementHandle handle = pstmt.handle; // Execute it against meta return meta.executeBatch(handle, pstmt.getParameterValueBatch()).updateCounts; } catch (Exception e) { throw HELPER.createException(e.getMessage(), e); } } /** Returns whether a a statement is capable of updates and if so, * and the statement's {@code updateCount} is still -1, proceeds to * get updateCount value from statement's resultSet. * *

Handles "ROWCOUNT" object as Number or List * * @param statement Statement * @throws SQLException on error */ private void isUpdateCapable(final AvaticaStatement statement) throws SQLException { Meta.Signature signature = statement.getSignature(); if (signature == null || signature.statementType == null) { return; } if (signature.statementType.canUpdate() && statement.updateCount == -1) { statement.openResultSet.next(); Object obj = statement.openResultSet.getObject(ROWCOUNT_COLUMN_NAME); if (obj instanceof Number) { statement.updateCount = ((Number) obj).intValue(); } else if (obj instanceof List) { @SuppressWarnings("unchecked") final List numbers = (List) obj; statement.updateCount = numbers.get(0).intValue(); } else { throw HELPER.createException("Not a valid return result."); } statement.openResultSet = null; } } protected Meta.ExecuteResult prepareAndExecuteInternal( final AvaticaStatement statement, final String sql, long maxRowCount) throws SQLException, NoSuchStatementException { final Meta.PrepareCallback callback = new Meta.PrepareCallback() { public Object getMonitor() { return statement; } public void clear() throws SQLException { if (statement.openResultSet != null) { final AvaticaResultSet rs = statement.openResultSet; statement.openResultSet = null; try { rs.close(); } catch (Exception e) { throw HELPER.createException( "Error while closing previous result set", e); } } } public void assign(Meta.Signature signature, Meta.Frame firstFrame, long updateCount) throws SQLException { statement.setSignature(signature); if (updateCount != -1) { statement.updateCount = updateCount; } else { final TimeZone timeZone = getTimeZone(); statement.openResultSet = factory.newResultSet(statement, new QueryState(sql), signature, timeZone, firstFrame); } } public void execute() throws SQLException { if (statement.openResultSet != null) { statement.openResultSet.execute(); isUpdateCapable(statement); } } }; // The old semantics were that maxRowCount was also treated as the maximum number of // elements in the first Frame of results. A value of -1 would also preserve this, but an // explicit (positive) number is easier to follow, IMO. return meta.prepareAndExecute(statement.handle, sql, maxRowCount, AvaticaUtils.toSaturatedInt(maxRowCount), callback); } protected ExecuteBatchResult prepareAndUpdateBatch(final AvaticaStatement statement, final List queries) throws NoSuchStatementException, SQLException { return meta.prepareAndExecuteBatch(statement.handle, queries); } protected ResultSet createResultSet(Meta.MetaResultSet metaResultSet, QueryState state) throws SQLException { final Meta.StatementHandle h = new Meta.StatementHandle( metaResultSet.connectionId, metaResultSet.statementId, null); final AvaticaStatement statement = lookupStatement(h); // These are all the metadata operations, no updates ResultSet resultSet = executeQueryInternal(statement, metaResultSet.signature.sanitize(), metaResultSet.firstFrame, state, false); if (metaResultSet.ownStatement) { resultSet.getStatement().closeOnCompletion(); } return resultSet; } /** Creates a statement wrapper around an existing handle. */ protected AvaticaStatement lookupStatement(Meta.StatementHandle h) throws SQLException { final AvaticaStatement statement = statementMap.get(h.id); if (statement != null) { return statement; } //noinspection MagicConstant return factory.newStatement(this, Objects.requireNonNull(h), ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY, holdability); } // do not make public protected static Trojan createTrojan() { return new Trojan(); } /** Converts a {@link Boolean} to a {@code boolean}, with a default value. */ private boolean unbox(Boolean b, boolean defaultValue) { return b == null ? defaultValue : b; } /** Converts an {@link Integer} to an {@code int}, with a default value. */ private int unbox(Integer i, int defaultValue) { return i == null ? defaultValue : i; } private Meta.ConnectionProperties sync() { return meta.connectionSync(handle, new ConnectionPropertiesImpl()); } /** Returns or creates a slot whose state can be changed to cancel a * statement. Statements will receive the same slot if and only if their id * is the same. */ public AtomicBoolean getCancelFlag(Meta.StatementHandle h) throws NoSuchStatementException { AvaticaUtils.upgrade("after dropping JDK 1.7, use Map.computeIfAbsent"); synchronized (flagMap) { AtomicBoolean b = flagMap.get(h.id); if (b == null) { b = new AtomicBoolean(); flagMap.put(h.id, b); } return b; } } /** A way to call package-protected methods. But only a sub-class of * connection can create one. */ public static class Trojan { // must be private private Trojan() { } /** A means for anyone who has a trojan to call the protected method * {@link com.hazelcast.org.apache.calcite.avatica.AvaticaResultSet#execute()}. * @throws SQLException if execute fails for some reason. */ public ResultSet execute(AvaticaResultSet resultSet) throws SQLException { return resultSet.execute(); } /** A means for anyone who has a trojan to call the protected method * {@link com.hazelcast.org.apache.calcite.avatica.AvaticaStatement#getParameterValues()}. */ public List getParameterValues(AvaticaStatement statement) { return statement.getParameterValues(); } /** A means for anyone who has a trojan to get the protected field * {@link com.hazelcast.org.apache.calcite.avatica.AvaticaConnection#meta}. */ public Meta getMeta(AvaticaConnection connection) { return connection.meta; } } /** * A Callable-like interface but without a "throws Exception". * * @param The return type from {@code call}. */ public interface CallableWithoutException { T call(); } /** * Invokes the given "callable", retrying the call when the server responds with an error * denoting that the connection is missing on the server. * * @param callable The function to invoke. * @return The value from the result of the callable. */ public T invokeWithRetries(CallableWithoutException callable) { RuntimeException lastException = null; for (int i = 0; i < maxRetriesPerExecute; i++) { try { return callable.call(); } catch (AvaticaClientRuntimeException e) { lastException = e; if (ErrorResponse.MISSING_CONNECTION_ERROR_CODE == e.getErrorCode() && transparentReconnectEnabled) { this.openConnection(); continue; } throw e; } } if (null != lastException) { throw lastException; } else { // Shouldn't ever happen. throw new IllegalStateException(); } } public void setKerberosConnection(KerberosConnection kerberosConnection) { this.kerberosConnection = Objects.requireNonNull(kerberosConnection); } public KerberosConnection getKerberosConnection() { return this.kerberosConnection; } public Service getService() { assert null != service; return service; } public void setService(Service service) { this.service = Objects.requireNonNull(service); } } // End AvaticaConnection.java