All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mariadb.jdbc.ClientSidePreparedStatement Maven / Gradle / Ivy

There is a newer version: 3.4.1
Show newest version
/*
 *
 * 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; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy