| // SPDX-License-Identifier: LGPL-2.1-or-later |
| // Copyright (c) 2012-2014 Monty Program Ab |
| // Copyright (c) 2015-2021 MariaDB Corporation Ab |
| |
| package org.mariadb.jdbc; |
| |
| import static org.mariadb.jdbc.util.constants.Capabilities.*; |
| |
| import java.sql.*; |
| import java.util.*; |
| import java.util.concurrent.locks.ReentrantLock; |
| import java.util.regex.Pattern; |
| import org.mariadb.jdbc.client.ColumnDecoder; |
| import org.mariadb.jdbc.client.Completion; |
| import org.mariadb.jdbc.client.result.CompleteResult; |
| import org.mariadb.jdbc.client.result.Result; |
| import org.mariadb.jdbc.client.util.Parameters; |
| import org.mariadb.jdbc.export.ExceptionFactory; |
| import org.mariadb.jdbc.message.ClientMessage; |
| import org.mariadb.jdbc.message.client.BulkExecutePacket; |
| import org.mariadb.jdbc.message.client.ExecutePacket; |
| import org.mariadb.jdbc.message.client.PrepareExecutePacket; |
| import org.mariadb.jdbc.message.client.PreparePacket; |
| import org.mariadb.jdbc.message.server.OkPacket; |
| import org.mariadb.jdbc.message.server.PrepareResultPacket; |
| import org.mariadb.jdbc.util.ParameterList; |
| |
| /** |
| * Server prepare statement. command will generate COM_STMT_PREPARE + COM_STMT_EXECUTE (+ |
| * COM_STMT_CLOSE) |
| */ |
| public class ServerPreparedStatement extends BasePreparedStatement { |
| private static final Pattern PREPARABLE_STATEMENT_PATTERN = |
| Pattern.compile("^(SELECT|UPDATE|INSERT|DELETE|REPLACE|DO|CALL)", Pattern.CASE_INSENSITIVE); |
| private final boolean canCachePrepStmts; |
| /** |
| * Server prepare statement constructor |
| * |
| * @param sql command |
| * @param con connection |
| * @param lock thread safe lock |
| * @param canUseServerTimeout can server use timeout |
| * @param canUseServerMaxRows can server use max rows |
| * @param canCachePrepStmts can server cache prepared statement |
| * @param autoGeneratedKeys must command return automatically generated keys |
| * @param resultSetType resultset type |
| * @param resultSetConcurrency resultset concurrency |
| * @param defaultFetchSize default fetch size |
| * @throws SQLException if prepare fails |
| */ |
| public ServerPreparedStatement( |
| String sql, |
| Connection con, |
| ReentrantLock lock, |
| boolean canUseServerTimeout, |
| boolean canUseServerMaxRows, |
| boolean canCachePrepStmts, |
| int autoGeneratedKeys, |
| int resultSetType, |
| int resultSetConcurrency, |
| int defaultFetchSize) |
| throws SQLException { |
| super( |
| sql, |
| con, |
| lock, |
| canUseServerTimeout, |
| canUseServerMaxRows, |
| autoGeneratedKeys, |
| resultSetType, |
| resultSetConcurrency, |
| defaultFetchSize); |
| this.canCachePrepStmts = canCachePrepStmts; |
| prepareResult = canCachePrepStmts ? con.getContext().getPrepareCache().get(sql, this) : null; |
| if (prepareResult == null && !PREPARABLE_STATEMENT_PATTERN.matcher(sql).find()) { |
| con.getClient().execute(new PreparePacket(sql), this, true); |
| } |
| parameters = new ParameterList(); |
| } |
| |
| /** |
| * Execute command with parameters |
| * |
| * @throws SQLException if any error occurs |
| */ |
| protected void executeInternal() throws SQLException { |
| checkNotClosed(); |
| validParameters(); |
| lock.lock(); |
| String cmd = escapeTimeout(sql); |
| if (prepareResult == null) |
| if (canCachePrepStmts) prepareResult = con.getContext().getPrepareCache().get(cmd, this); |
| try { |
| if (prepareResult == null && con.getContext().permitPipeline()) { |
| executePipeline(cmd); |
| } else { |
| executeStandard(cmd); |
| } |
| } finally { |
| localInfileInputStream = null; |
| lock.unlock(); |
| } |
| } |
| |
| /** |
| * Send COM_STMT_PREPARE + COM_STMT_EXECUTE, then read for the 2 answers |
| * |
| * @param cmd command |
| * @throws SQLException if IOException / Command error |
| */ |
| private void executePipeline(String cmd) throws SQLException { |
| // server is 10.2+, permitting to execute last prepare with (-1) statement id. |
| // Server send prepare, followed by execute, in one exchange. |
| try { |
| List<Completion> res = |
| con.getClient() |
| .execute( |
| new PrepareExecutePacket(cmd, parameters, this, localInfileInputStream), |
| this, |
| fetchSize, |
| maxRows, |
| resultSetConcurrency, |
| resultSetType, |
| closeOnCompletion, |
| false); |
| results = res.subList(1, res.size()); |
| } catch (SQLException ex) { |
| results = null; |
| throw ex; |
| } |
| } |
| |
| private void executeStandard(String cmd) throws SQLException { |
| // send COM_STMT_PREPARE |
| if (prepareResult == null) { |
| if (canCachePrepStmts) prepareResult = con.getContext().getPrepareCache().get(cmd, this); |
| if (prepareResult == null) { |
| con.getClient().execute(new PreparePacket(cmd), this, true); |
| } |
| } |
| validParameters(); |
| // send COM_STMT_EXECUTE |
| ExecutePacket execute = |
| new ExecutePacket(prepareResult, parameters, cmd, this, localInfileInputStream); |
| results = |
| con.getClient() |
| .execute( |
| execute, |
| this, |
| fetchSize, |
| maxRows, |
| resultSetConcurrency, |
| resultSetType, |
| closeOnCompletion, |
| false); |
| } |
| |
| private void executeInternalPreparedBatch() throws SQLException { |
| checkNotClosed(); |
| String cmd = escapeTimeout(sql); |
| if (batchParameters.size() > 1 && con.getContext().hasServerCapability(STMT_BULK_OPERATIONS)) { |
| |
| // ensure pipelining is possible (no LOAD DATA/XML INFILE commands) |
| boolean possibleLoadLocal = con.getContext().hasClientCapability(LOCAL_FILES); |
| if (possibleLoadLocal) { |
| String sqlUpper = sql.toUpperCase(Locale.ROOT); |
| possibleLoadLocal = |
| sqlUpper.contains(" LOCAL ") |
| && sqlUpper.contains("LOAD") |
| && sqlUpper.contains(" INFILE"); |
| } |
| |
| if (!possibleLoadLocal) { |
| if (con.getContext().getConf().useBulkStmts() |
| && autoGeneratedKeys != Statement.RETURN_GENERATED_KEYS) { |
| executeBatchBulk(cmd); |
| } else { |
| executeBatchPipeline(cmd); |
| } |
| return; |
| } |
| } |
| executeBatchStandard(cmd); |
| } |
| |
| /** |
| * Send COM_STMT_PREPARE + X * COM_STMT_BULK_EXECUTE, then read for the all answers |
| * |
| * @param cmd command |
| * @throws SQLException if IOException / Command error |
| */ |
| private void executeBatchBulk(String cmd) throws SQLException { |
| List<Completion> res; |
| if (prepareResult == null && canCachePrepStmts) |
| prepareResult = con.getContext().getPrepareCache().get(cmd, this); |
| try { |
| if (prepareResult == null) { |
| ClientMessage[] packets; |
| packets = |
| new ClientMessage[] { |
| new PreparePacket(cmd), new BulkExecutePacket(null, batchParameters, cmd, this) |
| }; |
| res = |
| con.getClient() |
| .executePipeline( |
| packets, |
| this, |
| 0, |
| maxRows, |
| ResultSet.CONCUR_READ_ONLY, |
| ResultSet.TYPE_FORWARD_ONLY, |
| closeOnCompletion, |
| false); |
| |
| // in case of failover, prepare is done in failover, skipping prepare result |
| if (res.get(0) instanceof PrepareResultPacket) { |
| results = res.subList(1, res.size()); |
| } else { |
| results = res; |
| } |
| } else { |
| results = |
| con.getClient() |
| .execute( |
| new BulkExecutePacket(prepareResult, batchParameters, cmd, this), |
| this, |
| 0, |
| maxRows, |
| ResultSet.CONCUR_READ_ONLY, |
| ResultSet.TYPE_FORWARD_ONLY, |
| closeOnCompletion, |
| false); |
| } |
| |
| } catch (SQLException bue) { |
| results = null; |
| throw exceptionFactory() |
| .createBatchUpdate(Collections.emptyList(), batchParameters.size(), bue); |
| } |
| } |
| |
| /** |
| * Send COM_STMT_PREPARE + X * COM_STMT_EXECUTE, then read for the all answers |
| * |
| * @param cmd command |
| * @throws SQLException if Command error |
| */ |
| private void executeBatchPipeline(String cmd) throws SQLException { |
| if (prepareResult == null && canCachePrepStmts) |
| prepareResult = con.getContext().getPrepareCache().get(cmd, this); |
| // server is 10.2+, permitting to execute last prepare with (-1) statement id. |
| // Server send prepare, followed by execute, in one exchange. |
| int maxCmd = 250; |
| List<Completion> res = new ArrayList<>(); |
| try { |
| int index = 0; |
| if (prepareResult == null) { |
| res.addAll(executeBunchPrepare(cmd, index, maxCmd)); |
| index += maxCmd; |
| } |
| while (index < batchParameters.size()) { |
| res.addAll(executeBunch(cmd, index, maxCmd)); |
| index += maxCmd; |
| } |
| results = res; |
| |
| } catch (SQLException bue) { |
| results = null; |
| throw exceptionFactory().createBatchUpdate(res, batchParameters.size(), bue); |
| } |
| } |
| |
| private List<Completion> executeBunch(String cmd, int index, int maxCmd) throws SQLException { |
| int maxCmdToSend = Math.min(batchParameters.size() - index, maxCmd); |
| ClientMessage[] packets = new ClientMessage[maxCmdToSend]; |
| for (int i = index; i < index + maxCmdToSend; i++) { |
| packets[i - index] = |
| new ExecutePacket( |
| prepareResult, batchParameters.get(i), cmd, this, localInfileInputStream); |
| } |
| return con.getClient() |
| .executePipeline( |
| packets, |
| this, |
| 0, |
| maxRows, |
| ResultSet.CONCUR_READ_ONLY, |
| ResultSet.TYPE_FORWARD_ONLY, |
| closeOnCompletion, |
| false); |
| } |
| |
| private List<Completion> executeBunchPrepare(String cmd, int index, int maxCmd) |
| throws SQLException { |
| int maxCmdToSend = Math.min(batchParameters.size() - index, maxCmd); |
| ClientMessage[] packets = new ClientMessage[maxCmdToSend + 1]; |
| packets[0] = new PreparePacket(cmd); |
| for (int i = index; i < index + maxCmdToSend; i++) { |
| packets[i + 1 - index] = |
| new ExecutePacket(null, batchParameters.get(i), cmd, this, localInfileInputStream); |
| } |
| List<Completion> res = |
| con.getClient() |
| .executePipeline( |
| packets, |
| this, |
| 0, |
| maxRows, |
| ResultSet.CONCUR_READ_ONLY, |
| ResultSet.TYPE_FORWARD_ONLY, |
| closeOnCompletion, |
| false); |
| // in case of failover, prepare is done in failover, skipping prepare result |
| if (res.get(0) instanceof PrepareResultPacket) { |
| return res.subList(1, res.size()); |
| } else { |
| return res; |
| } |
| } |
| |
| /** |
| * Send COM_STMT_PREPARE + read answer, then Send a COM_STMT_EXECUTE + read answer * n time |
| * |
| * @param cmd command |
| * @throws SQLException if IOException / Command error |
| */ |
| private void executeBatchStandard(String cmd) throws SQLException { |
| // send COM_STMT_PREPARE |
| List<Completion> tmpResults = new ArrayList<>(); |
| SQLException error = null; |
| for (Parameters batchParameter : batchParameters) { |
| // prepare is in loop, because if connection fail, prepare is reset, and need to be re |
| // prepared |
| if (prepareResult == null) { |
| if (canCachePrepStmts) prepareResult = con.getContext().getPrepareCache().get(cmd, this); |
| if (prepareResult == null) { |
| con.getClient().execute(new PreparePacket(cmd), this, false); |
| } |
| } |
| try { |
| ExecutePacket execute = |
| new ExecutePacket(prepareResult, batchParameter, cmd, this, localInfileInputStream); |
| tmpResults.addAll(con.getClient().execute(execute, this, false)); |
| } catch (SQLException e) { |
| if (error == null) error = e; |
| } |
| } |
| |
| if (error != null) { |
| throw exceptionFactory().createBatchUpdate(tmpResults, batchParameters.size(), error); |
| } |
| this.results = tmpResults; |
| } |
| |
| /** |
| * Executes the SQL statement in this <code>PreparedStatement</code> object, which may be any kind |
| * of SQL statement. Some prepared statements return multiple results; the <code>execute</code> |
| * method handles these complex statements as well as the simpler form of statements handled by |
| * the methods <code>executeQuery</code> and <code>executeUpdate</code>. |
| * |
| * <p>The <code>execute</code> method returns a <code>boolean</code> to indicate the form of the |
| * first result. You must call either the method <code>getResultSet</code> or <code>getUpdateCount |
| * </code> to retrieve the result; you must call <code>getMoreResults</code> to move to any |
| * subsequent result(s). |
| * |
| * @return <code>true</code> if the first result is a <code>ResultSet</code> object; <code>false |
| * </code> if the first result is an update count or there is no result |
| * @throws SQLException if a database access error occurs; this method is called on a closed |
| * <code>PreparedStatement</code> or an argument is supplied to this method |
| * @throws SQLTimeoutException when the driver has determined that the timeout value that was |
| * specified by the {@code setQueryTimeout} method has been exceeded and has at least |
| * attempted to cancel the currently running {@code Statement} |
| * @see Statement#execute |
| * @see Statement#getResultSet |
| * @see Statement#getUpdateCount |
| * @see Statement#getMoreResults |
| */ |
| @Override |
| public boolean execute() throws SQLException { |
| executeInternal(); |
| handleParameterOutput(); |
| if (results.size() > 0) { |
| currResult = results.remove(0); |
| return currResult instanceof Result; |
| } |
| return false; |
| } |
| |
| @Override |
| public void setMaxRows(int max) throws SQLException { |
| super.setMaxRows(max); |
| if (canUseServerMaxRows && prepareResult != null) { |
| prepareResult.decrementUse(con.getClient(), this); |
| prepareResult = null; |
| } |
| } |
| |
| @Override |
| public void setLargeMaxRows(long max) throws SQLException { |
| super.setLargeMaxRows(max); |
| if (canUseServerMaxRows && prepareResult != null) { |
| prepareResult.decrementUse(con.getClient(), this); |
| prepareResult = null; |
| } |
| } |
| |
| @Override |
| public void setQueryTimeout(int seconds) throws SQLException { |
| super.setQueryTimeout(seconds); |
| if (canUseServerTimeout && prepareResult != null) { |
| prepareResult.decrementUse(con.getClient(), this); |
| prepareResult = null; |
| } |
| } |
| |
| /** |
| * Executes the SQL query in this <code>PreparedStatement</code> object and returns the <code> |
| * ResultSet</code> object generated by the query. |
| * |
| * @return a <code>ResultSet</code> object that contains the data produced by the query; never |
| * <code>null</code> |
| * @throws SQLException if a database access error occurs; this method is called on a closed |
| * <code>PreparedStatement</code> or the SQL statement does not return a <code>ResultSet |
| * </code> object |
| * @throws SQLTimeoutException when the driver has determined that the timeout value that was |
| * specified by the {@code setQueryTimeout} method has been exceeded and has at least |
| * attempted to cancel the currently running {@code Statement} |
| */ |
| @Override |
| public ResultSet executeQuery() throws SQLException { |
| executeInternal(); |
| handleParameterOutput(); |
| if (results.size() > 0) { |
| currResult = results.remove(0); |
| if (currResult instanceof Result) return (Result) currResult; |
| } |
| return new CompleteResult(new ColumnDecoder[0], new byte[0][], con.getContext()); |
| } |
| |
| /** |
| * Executes the SQL statement in this <code>PreparedStatement</code> object, which must be an SQL |
| * Data Manipulation Language (DML) statement, such as <code>INSERT</code>, <code>UPDATE</code> or |
| * <code>DELETE</code>; or an SQL statement that returns nothing, such as a DDL statement. |
| * |
| * @return either (1) the row count for SQL Data Manipulation Language (DML) statements or (2) 0 |
| * for SQL statements that return nothing |
| * @throws SQLException if a database access error occurs; this method is called on a closed |
| * <code>PreparedStatement</code> or the SQL statement returns a <code>ResultSet</code> object |
| * @throws SQLTimeoutException when the driver has determined that the timeout value that was |
| * specified by the {@code setQueryTimeout} method has been exceeded and has at least |
| * attempted to cancel the currently running {@code Statement} |
| */ |
| @Override |
| public int executeUpdate() throws SQLException { |
| return (int) executeLargeUpdate(); |
| } |
| |
| /** |
| * Executes the SQL statement in this <code>PreparedStatement</code> object, which must be an SQL |
| * Data Manipulation Language (DML) statement, such as <code>INSERT</code>, <code>UPDATE</code> or |
| * <code>DELETE</code>; or an SQL statement that returns nothing, such as a DDL statement. |
| * |
| * <p>This method should be used when the returned row count may exceed {@link Integer#MAX_VALUE}. |
| * |
| * <p>The default implementation will throw {@code UnsupportedOperationException} |
| * |
| * @return either (1) the row count for SQL Data Manipulation Language (DML) statements or (2) 0 |
| * for SQL statements that return nothing |
| * @throws SQLException if a database access error occurs; this method is called on a closed |
| * <code>PreparedStatement</code> or the SQL statement returns a <code>ResultSet</code> object |
| * @throws SQLTimeoutException when the driver has determined that the timeout value that was |
| * specified by the {@code setQueryTimeout} method has been exceeded and has at least |
| * attempted to cancel the currently running {@code Statement} |
| * @since 1.8 |
| */ |
| @Override |
| public long executeLargeUpdate() throws SQLException { |
| executeInternal(); |
| handleParameterOutput(); |
| currResult = results.remove(0); |
| if (currResult instanceof Result) { |
| throw exceptionFactory() |
| .create("the given SQL statement produces an unexpected ResultSet object", "HY000"); |
| } |
| return ((OkPacket) currResult).getAffectedRows(); |
| } |
| |
| /** |
| * Handle output parameter result-set (only for CallableStatement) |
| * |
| * @throws SQLException if any error occurs |
| */ |
| protected void handleParameterOutput() throws SQLException {} |
| |
| /** |
| * Adds a set of parameters to this <code>PreparedStatement</code> object's batch of commands. |
| * |
| * @throws SQLException if a database access error occurs or this method is called on a closed |
| * <code>PreparedStatement</code> |
| * @see Statement#addBatch |
| * @since 1.2 |
| */ |
| @Override |
| public void addBatch() throws SQLException { |
| validParameters(); |
| if (batchParameters == null) batchParameters = new ArrayList<>(); |
| batchParameters.add(parameters); |
| parameters = parameters.clone(); |
| } |
| |
| /** |
| * Validated that all parameters have been set. |
| * |
| * @throws SQLException if number of parameters doesn't correspond to expected number |
| */ |
| protected void validParameters() throws SQLException { |
| if (prepareResult != null) { |
| for (int i = 0; i < prepareResult.getParameters().length; i++) { |
| if (!parameters.containsKey(i)) { |
| throw exceptionFactory() |
| .create("Parameter at position " + (i + 1) + " is not set", "07004"); |
| } |
| } |
| } else { |
| |
| if (batchParameters != null |
| && !batchParameters.isEmpty() |
| && parameters.size() < batchParameters.get(0).size()) { |
| // ensure batch parameters set same number |
| throw exceptionFactory() |
| .create( |
| "batch set of parameters differ from previous set. All parameters must be set", |
| "07004"); |
| } |
| |
| // ensure all parameters are set |
| for (int i = 0; i < parameters.size(); i++) { |
| if (!parameters.containsKey(i)) { |
| throw exceptionFactory() |
| .create("Parameter at position " + (i + 1) + " is not set", "07004"); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Retrieves a <code>ResultSetMetaData</code> object that contains information about the columns |
| * of the <code>ResultSet</code> object that will be returned when this <code>PreparedStatement |
| * </code> object is executed. |
| * |
| * <p>Because a <code>PreparedStatement</code> object is precompiled, it is possible to know about |
| * the <code>ResultSet</code> object that it will return without having to execute it. |
| * Consequently, it is possible to invoke the method <code>getMetaData</code> on a <code> |
| * PreparedStatement</code> object rather than waiting to execute it and then invoking the <code> |
| * ResultSet.getMetaData</code> method on the <code>ResultSet</code> object that is returned. |
| * |
| * <p><B>NOTE:</B> Using this method may be expensive for some drivers due to the lack of |
| * underlying DBMS support. |
| * |
| * @return the description of a <code>ResultSet</code> object's columns or <code>null</code> if |
| * the driver cannot return a <code>ResultSetMetaData</code> object |
| * @throws SQLException if a database access error occurs or this method is called on a closed |
| * <code>PreparedStatement</code> |
| * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method |
| * @since 1.2 |
| */ |
| @Override |
| public ResultSetMetaData getMetaData() throws SQLException { |
| |
| // send COM_STMT_PREPARE |
| if (prepareResult == null) { |
| con.getClient().execute(new PreparePacket(escapeTimeout(sql)), this, true); |
| } |
| |
| return new org.mariadb.jdbc.client.result.ResultSetMetaData( |
| exceptionFactory(), prepareResult.getColumns(), con.getContext().getConf(), false); |
| } |
| |
| /** |
| * Retrieves the number, types and properties of this <code>PreparedStatement</code> object's |
| * parameters. |
| * |
| * @return a <code>ParameterMetaData</code> object that contains information about the number, |
| * types and properties for each parameter marker of this <code>PreparedStatement</code> |
| * object |
| * @throws SQLException if a database access error occurs or this method is called on a closed |
| * <code>PreparedStatement</code> |
| * @see ParameterMetaData |
| * @since 1.4 |
| */ |
| @Override |
| public java.sql.ParameterMetaData getParameterMetaData() throws SQLException { |
| // send COM_STMT_PREPARE |
| if (prepareResult == null) { |
| con.getClient().execute(new PreparePacket(escapeTimeout(sql)), this, true); |
| } |
| |
| return new ParameterMetaData(exceptionFactory(), prepareResult.getParameters()); |
| } |
| |
| @Override |
| public int[] executeBatch() throws SQLException { |
| checkNotClosed(); |
| if (batchParameters == null || batchParameters.isEmpty()) return new int[0]; |
| lock.lock(); |
| try { |
| executeInternalPreparedBatch(); |
| |
| int[] updates = new int[batchParameters.size()]; |
| if (results.size() != updates.length) { |
| for (int i = 0; i < updates.length; i++) { |
| updates[i] = Statement.SUCCESS_NO_INFO; |
| } |
| } else { |
| for (int i = 0; i < updates.length; i++) { |
| if (results.get(i) instanceof OkPacket) { |
| updates[i] = (int) ((OkPacket) results.get(i)).getAffectedRows(); |
| } else { |
| updates[i] = org.mariadb.jdbc.Statement.SUCCESS_NO_INFO; |
| } |
| } |
| } |
| currResult = results.remove(0); |
| return updates; |
| |
| } finally { |
| localInfileInputStream = null; |
| batchParameters.clear(); |
| lock.unlock(); |
| } |
| } |
| |
| @Override |
| public long[] executeLargeBatch() throws SQLException { |
| checkNotClosed(); |
| if (batchParameters == null || batchParameters.isEmpty()) return new long[0]; |
| lock.lock(); |
| try { |
| executeInternalPreparedBatch(); |
| |
| long[] updates = new long[batchParameters.size()]; |
| if (results.size() != updates.length) { |
| for (int i = 0; i < updates.length; i++) { |
| updates[i] = Statement.SUCCESS_NO_INFO; |
| } |
| } else { |
| for (int i = 0; i < updates.length; i++) { |
| if (results.get(i) instanceof OkPacket) { |
| updates[i] = ((OkPacket) results.get(i)).getAffectedRows(); |
| } else { |
| updates[i] = org.mariadb.jdbc.Statement.SUCCESS_NO_INFO; |
| } |
| } |
| } |
| |
| currResult = results.remove(0); |
| return updates; |
| |
| } finally { |
| batchParameters.clear(); |
| lock.unlock(); |
| } |
| } |
| |
| private ExceptionFactory exceptionFactory() { |
| return con.getExceptionFactory().of(this); |
| } |
| |
| @Override |
| public void close() throws SQLException { |
| if (prepareResult != null) { |
| prepareResult.decrementUse(con.getClient(), this); |
| prepareResult = null; |
| } |
| con.fireStatementClosed(this); |
| super.close(); |
| } |
| |
| /** |
| * reset prepare statement in case of a failover. (Command need then to be re-prepared on server) |
| */ |
| public void reset() { |
| lock.lock(); |
| try { |
| prepareResult = null; |
| } finally { |
| lock.unlock(); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return "ServerPreparedStatement{" + super.toString() + '}'; |
| } |
| } |