| /* |
| * 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.calcite.avatica; |
| |
| import org.apache.calcite.avatica.ColumnMetaData.AvaticaType; |
| import org.apache.calcite.avatica.ColumnMetaData.Rep; |
| import org.apache.calcite.avatica.Meta.ExecuteBatchResult; |
| import org.apache.calcite.avatica.Meta.MetaResultSet; |
| import org.apache.calcite.avatica.remote.KerberosConnection; |
| import org.apache.calcite.avatica.remote.Service; |
| import org.apache.calcite.avatica.remote.Service.ErrorResponse; |
| import org.apache.calcite.avatica.remote.Service.OpenConnectionRequest; |
| import org.apache.calcite.avatica.remote.TypedValue; |
| import 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. |
| * |
| * <p>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"; |
| |
| 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. |
| * |
| * <p>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<InternalProperty, Object> properties = new HashMap<>(); |
| public final Map<Integer, AvaticaStatement> statementMap = new ConcurrentHashMap<>(); |
| final Map<Integer, AtomicBoolean> flagMap = new ConcurrentHashMap<>(); |
| protected final long maxRetriesPerExecute; |
| |
| /** |
| * Creates an AvaticaConnection. |
| * |
| * <p>Not public; method is called only from the driver or a derived |
| * class.</p> |
| * |
| * @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); |
| } |
| |
| /** 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<String, Class<?>> getTypeMap() throws SQLException { |
| throw HELPER.unsupported(); |
| } |
| |
| public void setTypeMap(Map<String, Class<?>> 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<Object> elementList = (List<Object>) 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> T unwrap(Class<T> 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. |
| * |
| * <p>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<Number> numbers = (List<Number>) 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<String> 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 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 org.apache.calcite.avatica.AvaticaStatement#getParameterValues()}. |
| */ |
| public List<TypedValue> getParameterValues(AvaticaStatement statement) { |
| return statement.getParameterValues(); |
| } |
| |
| /** A means for anyone who has a trojan to get the protected field |
| * {@link org.apache.calcite.avatica.AvaticaConnection#meta}. */ |
| public Meta getMeta(AvaticaConnection connection) { |
| return connection.meta; |
| } |
| } |
| |
| /** |
| * A Callable-like interface but without a "throws Exception". |
| * |
| * @param <T> The return type from {@code call}. |
| */ |
| public interface CallableWithoutException<T> { |
| 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> T invokeWithRetries(CallableWithoutException<T> 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()) { |
| 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 |