org.mariadb.jdbc.ClientSidePreparedStatement Maven / Gradle / Ivy
Show all versions of mariadb-java-client Show documentation
/*
*
* MariaDB Client for Java
*
* Copyright (c) 2012-2014 Monty Program Ab.
* Copyright (c) 2015-2019 MariaDB Ab.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with this library; if not, write to Monty Program Ab [email protected].
*
* This particular MariaDB Client for Java file is work
* derived from a Drizzle-JDBC. Drizzle-JDBC file which is covered by subject to
* the following copyright and notice provisions:
*
* Copyright (c) 2009-2011, Marcus Eriksson
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list
* of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice, this
* list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
*
* Neither the name of the driver nor the names of its contributors may not be
* used to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
* OF SUCH DAMAGE.
*
*/
package org.mariadb.jdbc;
import org.mariadb.jdbc.internal.com.read.dao.*;
import org.mariadb.jdbc.internal.com.read.resultset.*;
import org.mariadb.jdbc.internal.com.send.parameters.*;
import org.mariadb.jdbc.internal.logging.*;
import org.mariadb.jdbc.internal.util.dao.*;
import org.mariadb.jdbc.internal.util.exceptions.*;
import java.sql.*;
import java.util.*;
public class ClientSidePreparedStatement extends BasePrepareStatement {
private static final Logger logger = LoggerFactory.getLogger(ClientSidePreparedStatement.class);
private final List parameterList = new ArrayList<>();
private ClientPrepareResult prepareResult;
private String sqlQuery;
private ParameterHolder[] parameters;
private ResultSetMetaData resultSetMetaData = null;
private ParameterMetaData parameterMetaData = null;
/**
* Constructor.
*
* @param connection connection
* @param sql sql query
* @param resultSetScrollType one of the following ResultSet
constants:
* ResultSet.TYPE_FORWARD_ONLY
, ResultSet.TYPE_SCROLL_INSENSITIVE
, or
* ResultSet.TYPE_SCROLL_SENSITIVE
* @param resultSetConcurrency a concurrency type; one of ResultSet.CONCUR_READ_ONLY
* or ResultSet.CONCUR_UPDATABLE
* @param autoGeneratedKeys a flag indicating whether auto-generated keys should be returned; one
* of Statement.RETURN_GENERATED_KEYS
or Statement.NO_GENERATED_KEYS
* @throws SQLException exception
*/
public ClientSidePreparedStatement(
MariaDbConnection connection,
String sql,
int resultSetScrollType,
int resultSetConcurrency,
int autoGeneratedKeys)
throws SQLException {
super(connection, resultSetScrollType, resultSetConcurrency, autoGeneratedKeys);
sqlQuery = sql;
if (options.rewriteBatchedStatements) {
prepareResult = ClientPrepareResult.rewritableParts(sqlQuery, protocol.noBackslashEscapes());
} else {
prepareResult = ClientPrepareResult.parameterParts(sqlQuery, protocol.noBackslashEscapes());
}
parameters = new ParameterHolder[prepareResult.getParamCount()];
}
/**
* Clone statement.
*
* @param connection connection
* @return Clone statement.
* @throws CloneNotSupportedException if any error occur.
*/
public ClientSidePreparedStatement clone(MariaDbConnection connection)
throws CloneNotSupportedException {
ClientSidePreparedStatement clone = (ClientSidePreparedStatement) super.clone(connection);
clone.sqlQuery = sqlQuery;
clone.prepareResult = prepareResult;
clone.parameters = new ParameterHolder[prepareResult.getParamCount()];
clone.resultSetMetaData = resultSetMetaData;
clone.parameterMetaData = parameterMetaData;
return clone;
}
/**
* Executes the SQL statement in this PreparedStatement
object, which may be any kind
* of SQL statement. Some prepared statements return multiple results; the execute
* method handles these complex statements as well as the simpler form of statements handled by
* the methods executeQuery
and executeUpdate
.
* The execute
method returns a boolean
to indicate the form of the
* first result. You must call either the method getResultSet
or getUpdateCount
*
to retrieve the result; you must call getInternalMoreResults
to move to
* any subsequent result(s).
*
* @return true
if the first result is a ResultSet
object; false
*
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
* PreparedStatement
or an argument is supplied to this method
* @see Statement#execute
* @see Statement#getResultSet
* @see Statement#getUpdateCount
* @see Statement#getMoreResults
*/
public boolean execute() throws SQLException {
return executeInternal(getFetchSize());
}
/**
* Executes the SQL query in this PreparedStatement
object and returns the
* ResultSet
object generated by the query.
*
* @return a ResultSet
object that contains the data produced by the query; never
* null
* @throws SQLException if a database access error occurs; this method is called on a closed
* PreparedStatement
or the SQL statement does not return a ResultSet
*
object
*/
public ResultSet executeQuery() throws SQLException {
if (execute()) {
return results.getResultSet();
}
return SelectResultSet.createEmptyResultSet();
}
/**
* Executes the SQL statement in this PreparedStatement
object, which must be an SQL
* Data Manipulation Language (DML) statement, such as INSERT
, UPDATE
or
* DELETE
; 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
* PreparedStatement
or the SQL statement returns a ResultSet
object
*/
public int executeUpdate() throws SQLException {
if (execute()) {
return 0;
}
return getUpdateCount();
}
protected boolean executeInternal(int fetchSize) throws SQLException {
// valid parameters
for (int i = 0; i < prepareResult.getParamCount(); i++) {
if (parameters[i] == null) {
logger.error("Parameter at position {} is not set", (i + 1));
ExceptionMapper.throwException(
new SQLException("Parameter at position " + (i + 1) + " is not set", "07004"),
connection,
this);
}
}
lock.lock();
try {
executeQueryPrologue(false);
results =
new Results(
this,
fetchSize,
false,
1,
false,
resultSetScrollType,
resultSetConcurrency,
autoGeneratedKeys,
protocol.getAutoIncrementIncrement(),
sqlQuery,
parameters);
if (queryTimeout != 0 && canUseServerTimeout) {
// timer will not be used for timeout to avoid having threads
protocol.executeQuery(
protocol.isMasterConnection(), results, prepareResult, parameters, queryTimeout);
} else {
protocol.executeQuery(protocol.isMasterConnection(), results, prepareResult, parameters);
}
results.commandEnd();
return results.getResultSet() != null;
} catch (SQLException exception) {
if (results != null) {
results.commandEnd();
}
throw executeExceptionEpilogue(exception);
} finally {
executeEpilogue();
lock.unlock();
}
}
/**
* Adds a set of parameters to this PreparedStatement
object's batch of send.
*
*
* @throws SQLException if a database access error occurs or this method is called on a closed
* PreparedStatement
* @see Statement#addBatch
* @since 1.2
*/
public void addBatch() throws SQLException {
ParameterHolder[] holder = new ParameterHolder[prepareResult.getParamCount()];
for (int i = 0; i < holder.length; i++) {
holder[i] = parameters[i];
if (holder[i] == null) {
logger.error(
"You need to set exactly "
+ prepareResult.getParamCount()
+ " parameters on the prepared statement");
throw ExceptionMapper.getSqlException(
"You need to set exactly "
+ prepareResult.getParamCount()
+ " parameters on the prepared statement");
}
}
parameterList.add(holder);
}
/**
* Add batch.
*
* @param sql typically this is a SQL INSERT
or UPDATE
statement
* @throws SQLException every time since that method is forbidden on prepareStatement
*/
@Override
public void addBatch(final String sql) throws SQLException {
throw new SQLException("Cannot do addBatch(String) on preparedStatement");
}
/** Clear batch. */
@Override
public void clearBatch() {
parameterList.clear();
hasLongData = false;
this.parameters = new ParameterHolder[prepareResult.getParamCount()];
}
/** {inheritdoc}. */
public int[] executeBatch() throws SQLException {
checkClose();
int size = parameterList.size();
if (size == 0) {
return new int[0];
}
lock.lock();
try {
executeInternalBatch(size);
results.commandEnd();
return results.getCmdInformation().getUpdateCounts();
} catch (SQLException sqle) {
throw executeBatchExceptionEpilogue(sqle, size);
} finally {
executeBatchEpilogue();
lock.unlock();
}
}
/**
* Non JDBC : Permit to retrieve server update counts when using option rewriteBatchedStatements.
*
* @return an array of update counts containing one element for each command in the batch. The
* elements of the array are ordered according to the order in which commands were added to
* the batch.
*/
public int[] getServerUpdateCounts() {
if (results != null && results.getCmdInformation() != null) {
return results.getCmdInformation().getServerUpdateCounts();
}
return new int[0];
}
/**
* Execute batch, like executeBatch(), with returning results with long[]. For when row count may
* exceed Integer.MAX_VALUE.
*
* @return an array of update counts (one element for each command in the batch)
* @throws SQLException if a database error occur.
*/
public long[] executeLargeBatch() throws SQLException {
checkClose();
int size = parameterList.size();
if (size == 0) {
return new long[0];
}
lock.lock();
try {
executeInternalBatch(size);
results.commandEnd();
return results.getCmdInformation().getLargeUpdateCounts();
} catch (SQLException sqle) {
throw executeBatchExceptionEpilogue(sqle, size);
} finally {
executeBatchEpilogue();
lock.unlock();
}
}
/**
* Choose better way to execute queries according to query and options.
*
* @param size parameters number
* @throws SQLException if any error occur
*/
private void executeInternalBatch(int size) throws SQLException {
executeQueryPrologue(true);
results =
new Results(
this,
0,
true,
size,
false,
resultSetScrollType,
resultSetConcurrency,
autoGeneratedKeys,
protocol.getAutoIncrementIncrement(),
null,
null);
if (protocol.executeBatchClient(
protocol.isMasterConnection(), results, prepareResult, parameterList, hasLongData)) {
return;
}
// send query one by one, reading results for each query before sending another one
SQLException exception = null;
if (queryTimeout > 0) {
for (int batchQueriesCount = 0; batchQueriesCount < size; batchQueriesCount++) {
protocol.stopIfInterrupted();
try {
protocol.executeQuery(
protocol.isMasterConnection(),
results,
prepareResult,
parameterList.get(batchQueriesCount));
} catch (SQLException e) {
if (options.continueBatchOnError) {
exception = e;
} else {
throw e;
}
}
}
} else {
for (int batchQueriesCount = 0; batchQueriesCount < size; batchQueriesCount++) {
try {
protocol.executeQuery(
protocol.isMasterConnection(),
results,
prepareResult,
parameterList.get(batchQueriesCount));
} catch (SQLException e) {
if (options.continueBatchOnError) {
exception = e;
} else {
throw e;
}
}
}
}
if (exception != null) {
throw exception;
}
}
/**
* Retrieves a ResultSetMetaData
object that contains information about the columns
* of the ResultSet
object that will be returned when this PreparedStatement
*
object is executed.
* Because a PreparedStatement
object is precompiled, it is possible to know about
* the ResultSet
object that it will return without having to execute it.
* Consequently, it is possible to invoke the method getMetaData
on a
* PreparedStatement
object rather than waiting to execute it and then invoking the
* ResultSet.getMetaData
method on the ResultSet
object that is returned.
*
* @return the description of a ResultSet
object's columns or null
if
* the driver cannot return a ResultSetMetaData
object
* @throws SQLException if a database access error occurs or this method is called on a closed
* PreparedStatement
*/
public ResultSetMetaData getMetaData() throws SQLException {
checkClose();
ResultSet rs = getResultSet();
if (rs != null) {
return rs.getMetaData();
}
if (resultSetMetaData == null) {
loadParametersData();
}
return resultSetMetaData;
}
/**
* Set parameter.
*
* @param parameterIndex index
* @param holder parameter holder
* @throws SQLException if index position doesn't correspond to query parameters
*/
public void setParameter(final int parameterIndex, final ParameterHolder holder)
throws SQLException {
if (parameterIndex >= 1 && parameterIndex < prepareResult.getParamCount() + 1) {
parameters[parameterIndex - 1] = holder;
} else {
String error =
"Could not set parameter at position "
+ parameterIndex
+ " (values was "
+ holder.toString()
+ ")\n"
+ "Query - conn:"
+ protocol.getServerThreadId()
+ "("
+ (protocol.isMasterConnection() ? "M" : "S")
+ ") ";
if (options.maxQuerySizeToLog > 0) {
error += " - \"";
if (sqlQuery.length() < options.maxQuerySizeToLog) {
error += sqlQuery;
} else {
error += sqlQuery.substring(0, options.maxQuerySizeToLog) + "...";
}
error += "\"";
} else {
error += " - \"" + sqlQuery + "\"";
}
logger.error(error);
throw ExceptionMapper.getSqlException(error);
}
}
/**
* Retrieves the number, types and properties of this PreparedStatement
object's
* parameters.
*
* @return a ParameterMetaData
object that contains information about the number,
* types and properties for each parameter marker of this PreparedStatement
* object
* @throws SQLException if a database access error occurs or this method is called on a closed
* PreparedStatement
* @see ParameterMetaData
* @since 1.4
*/
public ParameterMetaData getParameterMetaData() throws SQLException {
checkClose();
if (parameterMetaData == null) {
loadParametersData();
}
return parameterMetaData;
}
private void loadParametersData() throws SQLSyntaxErrorException {
try (ServerSidePreparedStatement ssps =
new ServerSidePreparedStatement(
connection,
sqlQuery,
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY,
Statement.NO_GENERATED_KEYS)) {
resultSetMetaData = ssps.getMetaData();
parameterMetaData = ssps.getParameterMetaData();
} catch (SQLSyntaxErrorException sqlSyntaxErrorException) {
// if error is due to wrong SQL syntax, better to throw exception immediately
throw sqlSyntaxErrorException;
} catch (SQLException sqle) {
parameterMetaData = new MariaDbParameterMetaData(null);
}
}
/**
* Clears the current parameter values immediately.
*
* In general, parameter values remain in force for repeated use of a statement. Setting a
* parameter value automatically clears its previous value. However, in some cases it is useful to
* immediately release the resources used by the current parameter values; this can be done by
* calling the method clearParameters
.
*/
public void clearParameters() {
parameters = new ParameterHolder[prepareResult.getParamCount()];
}
// Close prepared statement, maybe fire closed-statement events
@Override
public void close() throws SQLException {
super.close();
if (connection == null
|| connection.pooledConnection == null
|| connection.pooledConnection.noStmtEventListeners()) {
return;
}
connection.pooledConnection.fireStatementClosed(this);
connection = null;
}
protected int getParameterCount() {
return prepareResult.getParamCount();
}
/** {inherit}. */
@Override
public String toString() {
StringBuilder sb = new StringBuilder("sql : '" + sqlQuery + "'");
sb.append(", parameters : [");
for (int i = 0; i < parameters.length; i++) {
ParameterHolder holder = parameters[i];
if (holder == null) {
sb.append("null");
} else {
sb.append(holder.toString());
}
if (i != parameters.length - 1) {
sb.append(",");
}
}
sb.append("]");
return sb.toString();
}
protected ClientPrepareResult getPrepareResult() {
return prepareResult;
}
}