org.mariadb.jdbc.MariaDbPreparedStatementServer Maven / Gradle / Ivy
/*
*
* MariaDB Client for Java
*
* Copyright (c) 2012-2014 Monty Program Ab.
* Copyright (c) 2015-2017 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.Results;
import org.mariadb.jdbc.internal.com.read.resultset.SelectResultSet;
import org.mariadb.jdbc.internal.com.send.parameters.ParameterHolder;
import org.mariadb.jdbc.internal.logging.Logger;
import org.mariadb.jdbc.internal.logging.LoggerFactory;
import org.mariadb.jdbc.internal.util.dao.ServerPrepareResult;
import org.mariadb.jdbc.internal.util.exceptions.ExceptionMapper;
import java.sql.*;
import java.util.*;
public class MariaDbPreparedStatementServer extends BasePrepareStatement implements Cloneable {
private static final Logger logger = LoggerFactory.getLogger(MariaDbPreparedStatementServer.class);
protected int parameterCount = -1;
private String sql;
private ServerPrepareResult serverPrepareResult = null;
private boolean returnTableAlias = false;
private MariaDbResultSetMetaData metadata;
private MariaDbParameterMetaData parameterMetaData;
private Map currentParameterHolder;
private List queryParameters = new ArrayList();
private 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 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 MariaDbPreparedStatementServer(MariaDbConnection connection, String sql, int resultSetScrollType,
int resultSetConcurrency, int autoGeneratedKeys)
throws SQLException {
super(connection, resultSetScrollType, resultSetConcurrency, autoGeneratedKeys);
this.sql = sql;
returnTableAlias = options.useOldAliasMetadataBehavior;
currentParameterHolder = Collections.synchronizedMap(new TreeMap());
mustExecuteOnMaster = protocol.isMasterConnection();
prepare(this.sql);
}
/**
* Clone statement.
*
* @param connection connection
* @return Clone statement.
* @throws CloneNotSupportedException if any error occur.
*/
public MariaDbPreparedStatementServer clone(MariaDbConnection connection) throws CloneNotSupportedException {
MariaDbPreparedStatementServer clone = (MariaDbPreparedStatementServer) super.clone(connection);
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 (SQLException e) {
try {
this.close();
} catch (Exception ee) {
//eat exception.
}
logger.error("error preparing query", e);
throw ExceptionMapper.getException(e, connection, this, false);
}
}
private void setMetaFromResult() {
parameterCount = serverPrepareResult.getParameters().length;
metadata = new MariaDbResultSetMetaData(serverPrepareResult.getColumns(), protocol.getUrlParser().getOptions(), returnTableAlias);
parameterMetaData = new MariaDbParameterMetaData(serverPrepareResult.getParameters());
}
public 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 {
return parameterMetaData;
}
@Override
public ResultSetMetaData getMetaData() throws SQLException {
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();
int queryParameterSize = queryParameters.size();
if (queryParameterSize == 0) return new int[0];
executeBatchInternal(queryParameterSize);
return results.getCmdInformation().getUpdateCounts();
}
/**
* 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 queryParameterSize = queryParameters.size();
if (queryParameterSize == 0) return new long[0];
executeBatchInternal(queryParameterSize);
return results.getCmdInformation().getLargeUpdateCounts();
}
private void executeBatchInternal(int queryParameterSize) throws SQLException {
lock.lock();
executing = true;
try {
executeQueryPrologue(serverPrepareResult);
results = new Results(this,
0,
true,
queryParameterSize,
true,
resultSetScrollType,
resultSetConcurrency,
autoGeneratedKeys,
protocol.getAutoIncrementIncrement());
//if multi send capacity
if ((options.useBatchMultiSend || options.useBulkStmts)
&& (protocol.executeBatchServer(mustExecuteOnMaster, serverPrepareResult, results, sql, queryParameters, hasLongData))) {
if (metadata == null) setMetaFromResult(); //first prepare
results.commandEnd();
return;
}
//send query one by one, reading results for each query before sending another one
SQLException exception = null;
if (queryTimeout > 0) {
for (int counter = 0; counter < queryParameterSize; counter++) {
ParameterHolder[] parameterHolder = queryParameters.get(counter);
try {
protocol.stopIfInterrupted();
serverPrepareResult.resetParameterTypeHeader();
protocol.executePreparedQuery(mustExecuteOnMaster, serverPrepareResult, results, parameterHolder);
} catch (SQLException queryException) {
if (options.continueBatchOnError) {
if (exception == null) exception = queryException;
} else {
throw queryException;
}
}
}
} else {
for (int counter = 0; counter < queryParameterSize; counter++) {
ParameterHolder[] parameterHolder = queryParameters.get(counter);
try {
serverPrepareResult.resetParameterTypeHeader();
protocol.executePreparedQuery(mustExecuteOnMaster, serverPrepareResult, results, parameterHolder);
} catch (SQLException queryException) {
if (options.continueBatchOnError) {
if (exception == null) exception = queryException;
} else {
throw queryException;
}
}
}
}
if (exception != null) throw exception;
results.commandEnd();
} catch (SQLException initialSqlEx) {
results.commandEnd();
throw executeBatchExceptionEpilogue(initialSqlEx, results.getCmdInformation(), queryParameterSize);
} finally {
executeBatchEpilogue();
lock.unlock();
}
}
// must have "lock" locked before invoking
private void executeQueryPrologue(ServerPrepareResult serverPrepareResult) throws SQLException {
executing = true;
if (closed) {
throw new SQLException("execute() is called on closed statement");
}
protocol.prologProxy(serverPrepareResult, maxRows, protocol.getProxy() != null, connection, this);
if (queryTimeout != 0) setTimerTask();
}
@Override
public ResultSet executeQuery() throws SQLException {
if (execute()) {
return results.getResultSet();
}
return SelectResultSet.createEmptyResultSet();
}
@Override
public int executeUpdate() throws SQLException {
if (execute()) {
return 0;
}
return getUpdateCount();
}
@Override
public void clearParameters() throws SQLException {
currentParameterHolder.clear();
}
@Override
public boolean execute() throws SQLException {
return executeInternal(getFetchSize());
}
protected void validParameters() throws SQLException {
for (int i = 0; i < parameterCount; i++) {
if (currentParameterHolder.get(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);
}
}
}
protected boolean executeInternal(int fetchSize) throws SQLException {
validParameters();
lock.lock();
try {
executeQueryPrologue(serverPrepareResult);
ParameterHolder[] parameterHolders = currentParameterHolder.values().toArray(new ParameterHolder[0]);
results = new Results(this,
fetchSize,
false,
1,
true,
resultSetScrollType,
resultSetConcurrency,
autoGeneratedKeys,
protocol.getAutoIncrementIncrement());
serverPrepareResult.resetParameterTypeHeader();
protocol.executePreparedQuery(mustExecuteOnMaster, serverPrepareResult, results, parameterHolders);
results.commandEnd();
return results.getResultSet() != null;
} catch (SQLException exception) {
throw executeExceptionEpilogue(exception);
} finally {
executeEpilogue();
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;
if (results != null) {
if (results.getFetchSize() != 0) {
skipMoreResults();
}
results.close();
}
// 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 (protocol != null) {
try {
serverPrepareResult.getUnProxiedProtocol().releasePrepareStatement(serverPrepareResult);
} catch (SQLException e) {
//if (log.isDebugEnabled()) log.debug("Error releasing preparedStatement", e);
}
}
protocol = null;
if (connection == null || connection.pooledConnection == null
|| connection.pooledConnection.noStmtEventListeners()) {
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() {
StringBuilder sb = new StringBuilder("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("]");
}
return sb.toString();
}
/**
* Permit to retrieve current connection thread id, or -1 if unknown.
*
* @return current connection thread id.
*/
public long getServerThreadId() {
return serverPrepareResult.getUnProxiedProtocol().getServerThreadId();
}
}