org.mariadb.jdbc.MariaDbPreparedStatementClient Maven / Gradle / Ivy
Show all versions of mariadb-java-client-jre7 Show documentation
/*
*
* 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.ClientPrepareResult;
import org.mariadb.jdbc.internal.util.exceptions.ExceptionMapper;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class MariaDbPreparedStatementClient extends BasePrepareStatement {
private static Logger logger = LoggerFactory.getLogger(MariaDbPreparedStatementClient.class);
protected ClientPrepareResult prepareResult;
protected List parameterList = new ArrayList<>();
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
* @throws SQLException exception
*/
public MariaDbPreparedStatementClient(MariaDbConnection connection, String sql, int resultSetScrollType) throws SQLException {
super(connection, resultSetScrollType);
this.sqlQuery = sql;
if (options.cachePrepStmts) {
String key = new StringBuilder(this.protocol.getDatabase()).append("-").append(sqlQuery).toString();
prepareResult = connection.getClientPrepareStatementCache().get(key);
}
if (prepareResult == null) {
if (options.rewriteBatchedStatements) {
prepareResult = ClientPrepareResult.rewritableParts(sqlQuery, connection.noBackslashEscapes);
} else {
prepareResult = ClientPrepareResult.parameterParts(sqlQuery, connection.noBackslashEscapes);
}
if (options.cachePrepStmts && sql.length() < 1024) {
String key = new StringBuilder(this.protocol.getDatabase()).append("-").append(sqlQuery).toString();
connection.getClientPrepareStatementCache().put(key, prepareResult);
}
}
parameters = new ParameterHolder[prepareResult.getParamCount()];
}
/**
* Clone statement.
*
* @param connection connection
* @return Clone statement.
* @throws CloneNotSupportedException if any error occur.
*/
public MariaDbPreparedStatementClient clone(MariaDbConnection connection) throws CloneNotSupportedException {
MariaDbPreparedStatementClient clone = (MariaDbPreparedStatementClient) 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 " + (i + 1) + " is not set");
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,
protocol.getAutoIncrementIncrement());
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) {
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();
this.parameters = new ParameterHolder[prepareResult.getParamCount()];
}
/**
* {inheritdoc}.
*/
public int[] executeBatch() throws SQLException {
checkClose();
int size = parameterList.size();
if (size == 0) return new int[0];
boolean rewritten = options.rewriteBatchedStatements && prepareResult.isQueryMultiValuesRewritable();
lock.lock();
try {
executeInternalBatch(size);
if (!rewritten) {
return results.getCmdInformation().getUpdateCounts();
} else {
return results.getCmdInformation().getRewriteUpdateCounts();
}
} catch (SQLException sqle) {
throw executeBatchExceptionEpilogue(sqle, results.getCmdInformation(), size, rewritten);
} finally {
executeBatchEpilogue();
lock.unlock();
}
}
/**
* 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];
boolean rewritten = options.rewriteBatchedStatements && prepareResult.isQueryMultiValuesRewritable();
lock.lock();
try {
executeInternalBatch(size);
if (!rewritten) {
return results.getCmdInformation().getLargeUpdateCounts();
} else {
return results.getCmdInformation().getRewriteLargeUpdateCounts();
}
} catch (SQLException sqle) {
throw executeBatchExceptionEpilogue(sqle, results.getCmdInformation(), size, rewritten);
} 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
*/
protected void executeInternalBatch(int size) throws SQLException {
executeQueryPrologue(true);
results.reset(0, true, size, false, resultSetScrollType);
if (options.rewriteBatchedStatements) {
if (prepareResult.isQueryMultiValuesRewritable()) {
//values rewritten in one query :
// INSERT INTO X(a,b) VALUES (1,2), (3,4), ...
protocol.executeBatchRewrite(protocol.isMasterConnection(), results, prepareResult,
parameterList, true);
return;
} else if (prepareResult.isQueryMultipleRewritable()) {
//multi rewritten in one query :
// INSERT INTO X(a,b) VALUES (1,2);INSERT INTO X(a,b) VALUES (3,4); ...
protocol.executeBatchRewrite(protocol.isMasterConnection(), results, prepareResult,
parameterList, false);
return;
}
}
if (options.useBatchMultiSend) {
//send by bulk : send data by bulk before reading corresponding results
protocol.executeBatchMulti(protocol.isMasterConnection(), results, prepareResult, parameterList);
} else {
//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;
}
results.commandEnd();
}
/**
* 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.
*
* NOTE: Using this method may be expensive for some drivers due to the lack of underlying DBMS support.
*
* @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
* @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method
* @since 1.2
*/
public ResultSetMetaData getMetaData() throws SQLException {
checkClose();
ResultSet rs = getResultSet();
if (rs != null) {
return rs.getMetaData();
}
if (resultSetMetaData == null) {
setParametersData();
}
return resultSetMetaData;
}
protected void setParameter(final int parameterIndex, final ParameterHolder holder) throws SQLException {
if (parameterIndex >= 1 && parameterIndex < prepareResult.getParamCount() + 1) {
parameters[parameterIndex - 1] = holder;
} else {
logger.error("Could not set parameter at position " + parameterIndex
+ " (values vas " + holder.toString() + ")");
throw ExceptionMapper.getSqlException("Could not set parameter at position " + parameterIndex
+ " (values vas " + holder.toString() + ")");
}
}
/**
* 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) {
setParametersData();
}
return parameterMetaData;
}
private void setParametersData() throws SQLException {
MariaDbPreparedStatementServer ssps = new MariaDbPreparedStatementServer(connection, this.sqlQuery,
ResultSet.TYPE_SCROLL_INSENSITIVE, true);
resultSetMetaData = ssps.getMetaData();
parameterMetaData = ssps.getParameterMetaData();
ssps.close();
}
/**
* 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.statementEventListeners.isEmpty()) {
return;
}
}
protected int getParameterCount() {
return prepareResult.getParamCount();
}
/**
* {inherit}.
*/
@Override
public String toString() {
StringBuffer sb = new StringBuffer("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;
}
}