
org.mariadb.jdbc.MariaDbServerPreparedStatement Maven / Gradle / Ivy
package org.mariadb.jdbc;
/*
MariaDB Client for Java
Copyright (c) 2012-2014 Monty Program 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, Trond Norbye, Stephane Giron
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.
*/
import org.mariadb.jdbc.internal.logging.Logger;
import org.mariadb.jdbc.internal.logging.LoggerFactory;
import org.mariadb.jdbc.internal.packet.dao.parameters.ParameterHolder;
import org.mariadb.jdbc.internal.queryresults.*;
import org.mariadb.jdbc.internal.queryresults.resultset.MariaSelectResultSet;
import org.mariadb.jdbc.internal.stream.PrepareException;
import org.mariadb.jdbc.internal.stream.PrepareSqlException;
import org.mariadb.jdbc.internal.util.ExceptionMapper;
import org.mariadb.jdbc.internal.util.dao.QueryException;
import org.mariadb.jdbc.internal.util.dao.ServerPrepareResult;
import org.mariadb.jdbc.internal.util.Utils;
import java.sql.*;
import java.util.*;
public class MariaDbServerPreparedStatement extends AbstractMariaDbPrepareStatement implements Cloneable {
private static Logger logger = LoggerFactory.getLogger(MariaDbServerPreparedStatement.class);
String sql;
ServerPrepareResult serverPrepareResult = null;
boolean returnTableAlias = false;
int parameterCount = -1;
MariaDbResultSetMetaData metadata;
MariaDbParameterMetaData parameterMetaData;
Map currentParameterHolder;
List queryParameters = new ArrayList<>();
boolean mustExecuteOnMaster;
/**
* Constructor for creating Server prepared statement.
* @param connection current connection
* @param sql Sql String to prepare
* @param resultSetScrollType one of the following ResultSet
constants: ResultSet.TYPE_FORWARD_ONLY
,
* ResultSet.TYPE_SCROLL_INSENSITIVE
, or ResultSet.TYPE_SCROLL_SENSITIVE
* @param forcePrepare force immediate prepare
* @throws SQLException exception
*/
public MariaDbServerPreparedStatement(MariaDbConnection connection, String sql, int resultSetScrollType, boolean forcePrepare)
throws SQLException {
super(connection, resultSetScrollType);
this.sql = sql;
useFractionalSeconds = options.useFractionalSeconds;
returnTableAlias = options.useOldAliasMetadataBehavior;
currentParameterHolder = Collections.synchronizedMap(new TreeMap());
mustExecuteOnMaster = protocol.isMasterConnection();
if (forcePrepare) prepare(this.sql);
}
/**
* Constructor for creating Server prepared statement.
* @param connection current connection
* @param sql Sql String to prepare
* @param resultSetScrollType one of the following ResultSet
constants: ResultSet.TYPE_FORWARD_ONLY
,
* ResultSet.TYPE_SCROLL_INSENSITIVE
, or ResultSet.TYPE_SCROLL_SENSITIVE
* @param serverPrepareResult prepare result from cache
* @throws SQLException exception
*/
public MariaDbServerPreparedStatement(MariaDbConnection connection, String sql, int resultSetScrollType, ServerPrepareResult serverPrepareResult)
throws SQLException {
super(connection, resultSetScrollType);
this.sql = sql;
useFractionalSeconds = options.useFractionalSeconds;
returnTableAlias = options.useOldAliasMetadataBehavior;
currentParameterHolder = new TreeMap<>();
mustExecuteOnMaster = protocol.isMasterConnection();
this.serverPrepareResult = serverPrepareResult;
setMetaFromResult();
}
/**
* Clone statement.
*
* @return Clone statement.
* @throws CloneNotSupportedException if any error occur.
*/
public MariaDbServerPreparedStatement clone() throws CloneNotSupportedException {
MariaDbServerPreparedStatement clone = (MariaDbServerPreparedStatement) super.clone();
clone.metadata = metadata;
clone.parameterMetaData = parameterMetaData;
clone.queryParameters = new ArrayList<>();
clone.mustExecuteOnMaster = mustExecuteOnMaster;
//force prepare
try {
clone.prepare(sql);
} catch (SQLException e) {
throw new CloneNotSupportedException("PrepareStatement not ");
}
return clone;
}
private void prepare(String sql) throws SQLException {
try {
serverPrepareResult = protocol.prepare(sql, mustExecuteOnMaster);
setMetaFromResult();
} catch (PrepareException exception) {
throw new PrepareSqlException(exception);
} catch (QueryException e) {
try {
this.close();
} catch (Exception ee) {
//eat exception.
}
logger.error("error preparing query", e);
ExceptionMapper.throwException(e, connection, this);
}
}
private void setMetaFromResult() {
parameterCount = serverPrepareResult.getParameters().length;
metadata = new MariaDbResultSetMetaData(serverPrepareResult.getColumns(), protocol.getDataTypeMappingFlags(), returnTableAlias);
parameterMetaData = new MariaDbParameterMetaData(serverPrepareResult.getParameters());
sql = null;
}
@Override
protected boolean isNoBackslashEscapes() {
return connection.noBackslashEscapes;
}
@Override
protected boolean useFractionalSeconds() {
return useFractionalSeconds;
}
@Override
protected Calendar cal() {
return protocol.getCalendar();
}
protected void setParameter(final int parameterIndex, final ParameterHolder holder) throws SQLException {
currentParameterHolder.put(parameterIndex - 1, holder);
}
@Override
public void addBatch() throws SQLException {
validParameters();
queryParameters.add(currentParameterHolder.values().toArray(new ParameterHolder[0]));
}
/**
* 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");
}
public void clearBatch() {
queryParameters.clear();
hasLongData = false;
}
@Override
public ParameterMetaData getParameterMetaData() throws SQLException {
if (serverPrepareResult == null) prepare(sql);
return parameterMetaData;
}
@Override
public ResultSetMetaData getMetaData() throws SQLException {
if (serverPrepareResult == null) prepare(sql);
return metadata;
}
/**
* Submits a batch of send to the database for execution and if all send execute successfully, returns an
* array of update counts. The int
elements of the array that is returned are ordered to correspond to
* the send in the batch, which are ordered according to the order in which they were added to the batch. The
* elements in the array returned by the method executeBatch
may be one of the following:
* - A number greater than or equal to zero -- indicates that the command was processed successfully and is an update
* count giving the number of rows in the database that were affected by the command's execution
*
- A value of
SUCCESS_NO_INFO
-- indicates that the command was processed successfully but that the number of rows
* affected is unknown.
* If one of the send in a batch update fails to execute properly, this method throws a
* BatchUpdateException
, and a JDBC driver may or may not continue to process the remaining send in
* the batch. However, the driver's behavior must be consistent with a particular DBMS, either always continuing to
* process send or never continuing to process send. If the driver continues processing after a failure,
* the array returned by the method BatchUpdateException.getUpdateCounts
will contain as many elements
* as there are send in the batch, and at least one of the elements will be the following:
* - A value of
EXECUTE_FAILED
-- indicates that the command failed to execute successfully and
* occurs only if a driver continues to process send after a command fails
* The possible implementations and return values have been modified in the Java 2 SDK, Standard Edition, version
* 1.3 to accommodate the option of continuing to proccess send in a batch update after a
* BatchUpdateException
object has been thrown.
*
* @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 send were added to the batch.
* @throws SQLException if a database access error occurs, this method is called on a closed
* Statement
or the driver does not support batch statements. Throws
* {@link BatchUpdateException} (a subclass of SQLException
) if
* one of the send sent to the database fails to execute properly or attempts to
* return a result set.
* @see #addBatch
* @see DatabaseMetaData#supportsBatchUpdates
* @since 1.3
*/
@Override
public int[] executeBatch() throws SQLException {
checkClose();
batchResultSet = null;
int queryParameterSize = queryParameters.size();
if (queryParameterSize == 0) return new int[0];
lock.lock();
executing = true;
QueryException exception = null;
MultiFixedIntExecutionResult internalExecutionResult = null;
try {
executeQueryProlog(serverPrepareResult);
try {
internalExecutionResult = new MultiFixedIntExecutionResult(this, queryParameterSize, 0, false);
executeBatchInternal(internalExecutionResult, queryParameterSize);
} catch (QueryException queryException) {
internalExecutionResult.fixStatsError(queryParameterSize);
exception = queryException;
} finally {
executionResult = internalExecutionResult;
executeQueryEpilog(exception);
executing = false;
}
clearBatch();
return internalExecutionResult.getAffectedRows();
} catch (PrepareSqlException p) {
throw p;
} catch (SQLException sqle) {
clearBatch();
throw new BatchUpdateException(sqle.getMessage(), sqle.getSQLState(), sqle.getErrorCode(), internalExecutionResult.getAffectedRows(),
sqle);
} finally {
lock.unlock();
}
}
/**
* Send batch datas according to options.
*
* @param internalExecutionResult results.
* @param queryParameterSize batch size
* @throws QueryException if any error occur.
* @throws SQLException if prepare fail
*/
private void executeBatchInternal(MultiFixedIntExecutionResult internalExecutionResult, int queryParameterSize)
throws QueryException, SQLException {
//if multi send capacity
if (options.useBatchMultiSend) {
//send all sub-command in one packet (or more if > max_allowed_packet)
serverPrepareResult = protocol.prepareAndExecutes(mustExecuteOnMaster, serverPrepareResult, internalExecutionResult, sql,
queryParameters, resultSetScrollType);
if (metadata == null) setMetaFromResult(); //first prepare
return;
}
//send query one by one, reading results for each query before sending another one
QueryException exception = null;
for (int counter = 0; counter < queryParameterSize; counter++) {
ParameterHolder[] parameterHolder = queryParameters.get(counter);
try {
serverPrepareResult.resetParameterTypeHeader();
protocol.executePreparedQuery(mustExecuteOnMaster, serverPrepareResult, internalExecutionResult,
parameterHolder, resultSetScrollType);
} catch (QueryException queryException) {
if (options.continueBatchOnError || queryException.isPrepareError()) {
if (exception == null) exception = queryException;
} else {
throw queryException;
}
}
}
if (exception != null) throw exception;
}
// must have "lock" locked before invoking
private void executeQueryProlog(ServerPrepareResult serverPrepareResult) throws SQLException {
if (closed) {
throw new SQLException("execute() is called on closed statement");
}
protocol.prologProxy(serverPrepareResult, executionResult, maxRows, protocol.getProxy() != null, connection, this);
if (queryTimeout != 0) {
setTimerTask();
}
}
@Override
public ResultSet executeQuery() throws SQLException {
if (execute()) {
return executionResult.getResultSet();
}
return MariaSelectResultSet.EMPTY;
}
@Override
public int executeUpdate() throws SQLException {
execute();
return getUpdateCount();
}
@Override
public void clearParameters() throws SQLException {
currentParameterHolder.clear();
}
@Override
public boolean execute() throws SQLException {
return executeInternal(getFetchSize(), false);
}
protected void validParameters() throws SQLException {
if (serverPrepareResult != null) {
for (int i = 0; i < parameterCount; i++) {
if (currentParameterHolder.get(i) == null) {
logger.error("Parameter at position " + (i + 1) + " is not set");
ExceptionMapper.throwException(new QueryException("Parameter at position " + (i + 1) + " is not set", -1, "07004"),
connection, this);
}
}
} else {
if (parameterCount == -1) parameterCount = currentParameterHolder.size();
for (int i = 0; i < parameterCount; i++) {
if (!currentParameterHolder.containsKey(i)) {
parameterCount = -1;
logger.error("Parameter at position " + (i + 1) + " is not set");
ExceptionMapper.throwException(new QueryException("Parameter at position " + (i + 1) + " is not set", -1, "07004"),
connection, this);
}
}
}
}
protected boolean executeInternal(int fetchSize, boolean canHaveCallableResultset) throws SQLException {
validParameters();
lock.lock();
try {
executing = true;
QueryException exception = null;
executeQueryProlog(serverPrepareResult);
try {
batchResultSet = null;
SingleExecutionResult internalExecutionResult = new SingleExecutionResult(this, fetchSize, true, canHaveCallableResultset,
true);
ParameterHolder[] parameterHolders = currentParameterHolder.values().toArray(new ParameterHolder[0]);
if (serverPrepareResult != null) {
serverPrepareResult.resetParameterTypeHeader();
protocol.executePreparedQuery(mustExecuteOnMaster, serverPrepareResult, internalExecutionResult,
parameterHolders, resultSetScrollType);
} else {
serverPrepareResult = protocol.prepareAndExecute(mustExecuteOnMaster, null, internalExecutionResult, sql,
parameterHolders, resultSetScrollType);
setMetaFromResult();
}
executionResult = internalExecutionResult;
return executionResult.getResultSet() != null;
} catch (QueryException e) {
exception = e;
return false;
} finally {
executeQueryEpilog(exception);
executing = false;
}
} finally {
lock.unlock();
}
}
/**
* Releases this Statement
object's database and JDBC resources immediately instead of waiting for this
* to happen when it is automatically closed. It is generally good practice to release resources as soon as you are
* finished with them to avoid tying up database resources.
* Calling the method close
on a Statement
object that is already closed has no effect.
* Note:When a Statement
object is closed, its current ResultSet
object, if one
* exists, is also closed.
*
* @throws SQLException if a database access error occurs
*/
@Override
public void close() throws SQLException {
lock.lock();
try {
closed = true;
// No possible future use for the cached results, so these can be cleared
// This makes the cache eligible for garbage collection earlier if the statement is not
// immediately garbage collected
if (serverPrepareResult != null && protocol != null) {
try {
serverPrepareResult.getUnProxiedProtocol().releasePrepareStatement(serverPrepareResult);
} catch (QueryException e) {
//if (log.isDebugEnabled()) log.debug("Error releasing preparedStatement", e);
}
}
serverPrepareResult = null;
protocol = null;
if (connection == null || connection.pooledConnection == null
|| connection.pooledConnection.statementEventListeners.isEmpty()) {
return;
}
connection.pooledConnection.fireStatementClosed(this);
connection = null;
} finally {
lock.unlock();
}
}
protected int getParameterCount() {
return parameterCount;
}
/**
* Return sql String value.
* @return String representation
*/
public String toString() {
StringBuffer sb;
if (serverPrepareResult != null) {
sb = new StringBuffer("sql : '" + serverPrepareResult.getSql() + "'");
if (parameterCount > 0) {
sb.append(", parameters : [");
for (int i = 0; i < parameterCount; i++) {
ParameterHolder holder = currentParameterHolder.get(i);
if (holder == null) {
sb.append("null");
} else {
sb.append(holder.toString());
}
if (i != parameterCount - 1) {
sb.append(",");
}
}
sb.append("]");
}
} else {
sb = new StringBuffer("sql : '" + sql + "'");
sb.append(", parameters : [");
for (int i = 0; i < currentParameterHolder.size(); i++) {
ParameterHolder holder = currentParameterHolder.get(i);
if (holder == null) {
sb.append("null");
} else {
sb.append(holder.toString());
}
if (i != currentParameterHolder.size() - 1) {
sb.append(",");
}
}
sb.append("]");
}
return sb.toString();
}
protected ExecutionResult getExecutionResult() {
return executionResult;
}
}