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

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

The newest version!
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (c) 2012-2014 Monty Program Ab
// Copyright (c) 2015-2024 MariaDB Corporation Ab
package org.mariadb.jdbc;

import static org.mariadb.jdbc.util.constants.Capabilities.*;

import java.sql.*;
import java.util.*;
import org.mariadb.jdbc.client.ColumnDecoder;
import org.mariadb.jdbc.client.result.CompleteResult;
import org.mariadb.jdbc.client.result.Result;
import org.mariadb.jdbc.client.util.ClosableLock;
import org.mariadb.jdbc.export.ExceptionFactory;
import org.mariadb.jdbc.message.ClientMessage;
import org.mariadb.jdbc.message.client.*;
import org.mariadb.jdbc.message.server.OkPacket;
import org.mariadb.jdbc.util.ClientParser;
import org.mariadb.jdbc.util.ParameterList;
import org.mariadb.jdbc.util.constants.ServerStatus;
import org.mariadb.jdbc.util.timeout.QueryTimeoutHandler;

/**
 * Client side prepare statement. Question mark will be replaced by escaped parameters, client side
 */
public class ClientPreparedStatement extends BasePreparedStatement {
  private final ClientParser parser;

  /**
   * Client prepare statement constructor
   *
   * @param sql command
   * @param con connection
   * @param lock thread safe lock
   * @param canUseServerTimeout can server use timeout
   * @param canUseServerMaxRows can server use max rows
   * @param autoGeneratedKeys must command return automatically generated keys
   * @param resultSetType resultset type
   * @param resultSetConcurrency resultset concurrency
   * @param defaultFetchSize default fetch size
   */
  public ClientPreparedStatement(
      String sql,
      Connection con,
      ClosableLock lock,
      boolean canUseServerTimeout,
      boolean canUseServerMaxRows,
      int autoGeneratedKeys,
      int resultSetType,
      int resultSetConcurrency,
      int defaultFetchSize) {
    super(
        sql,
        con,
        lock,
        canUseServerTimeout,
        canUseServerMaxRows,
        false,
        autoGeneratedKeys,
        resultSetType,
        resultSetConcurrency,
        defaultFetchSize);
    boolean noBackslashEscapes =
        (con.getContext().getServerStatus() & ServerStatus.NO_BACKSLASH_ESCAPES) > 0;
    parser = ClientParser.parameterParts(sql, noBackslashEscapes);
    parameters = new ParameterList(parser.getParamCount());
  }

  /**
   * use additional part for timeout if possible
   *
   * @return pre command for handling timeout
   */
  protected String preSqlCmd() {
    if (queryTimeout != 0 && canUseServerTimeout) {
      if (canUseServerMaxRows && maxRows > 0) {
        return "SET STATEMENT max_statement_time="
            + queryTimeout
            + ", SQL_SELECT_LIMIT="
            + maxRows
            + " FOR ";
      }
      return "SET STATEMENT max_statement_time=" + queryTimeout + " FOR ";
    }
    if (canUseServerMaxRows && maxRows > 0) {
      return "SET STATEMENT SQL_SELECT_LIMIT=" + maxRows + " FOR ";
    }
    return null;
  }

  @SuppressWarnings("try")
  private void executeInternal() throws SQLException {
    checkNotClosed();
    validParameters();
    try (ClosableLock ignore = lock.closeableLock();
        QueryTimeoutHandler ignore2 = this.con.handleTimeout(queryTimeout)) {
      QueryWithParametersPacket query =
          new QueryWithParametersPacket(preSqlCmd(), parser, parameters, localInfileInputStream);
      results =
          con.getClient()
              .execute(
                  query,
                  this,
                  fetchSize,
                  maxRows,
                  resultSetConcurrency,
                  resultSetType,
                  closeOnCompletion,
                  false);
    } catch (SQLException e) {
      results = null;
      currResult = null;
      throw e;
    } finally {
      localInfileInputStream = null;
    }
  }

  protected boolean executeInternalPreparedBatch() throws SQLException {
    checkNotClosed();
    parseCommandIfNeeded(sql);
    Configuration conf = con.getContext().getConf();
    boolean isNormalInsert = clientParser.isInsert() && !clientParser.isInsertDuplicate();

    if (con.getContext().hasServerCapability(STMT_BULK_OPERATIONS)
        && ((isNormalInsert && (conf.useBulkStmts() || conf.useBulkStmtsForInserts()))
            || (!clientParser.isInsert() && conf.useBulkStmts()))
        && batchParameters.size() > 1
        && !clientParser.isMultiQuery()
        && (con.getContext().hasClientCapability(BULK_UNIT_RESULTS)
            || autoGeneratedKeys != Statement.RETURN_GENERATED_KEYS)) {
      executeBatchBulk(escapeTimeout(sql));
      return true;
    } else {
      boolean possibleLoadLocal = con.getContext().hasClientCapability(LOCAL_FILES);
      if (possibleLoadLocal) {
        String sqlUpper = sql.toUpperCase(Locale.ROOT);
        possibleLoadLocal =
            sqlUpper.contains(" LOCAL ")
                && sqlUpper.contains("LOAD")
                && sqlUpper.contains(" INFILE");
      }
      if (possibleLoadLocal) {
        executeBatchStd();
      } else {
        executeBatchPipeline();
      }
    }
    return false;
  }

  /**
   * Send n * COM_QUERY + n * read answer
   *
   * @throws SQLException if IOException / Command error
   */
  private void executeBatchPipeline() throws SQLException {
    ClientMessage[] packets = new ClientMessage[batchParameters.size()];
    for (int i = 0; i < batchParameters.size(); i++) {
      packets[i] = new QueryWithParametersPacket(preSqlCmd(), parser, batchParameters.get(i), null);
    }
    try {
      results =
          con.getClient()
              .executePipeline(
                  packets,
                  this,
                  0,
                  maxRows,
                  ResultSet.CONCUR_READ_ONLY,
                  ResultSet.TYPE_FORWARD_ONLY,
                  closeOnCompletion,
                  false);
    } catch (SQLException bue) {
      results = null;
      throw bue;
    }
  }

  /**
   * Send n * (COM_QUERY + read answer)
   *
   * @throws SQLException if IOException / Command error
   */
  private void executeBatchStd() throws SQLException {
    int i = 0;
    try {
      results = new ArrayList<>();
      for (; i < batchParameters.size(); i++) {
        results.addAll(
            con.getClient()
                .execute(
                    new QueryWithParametersPacket(
                        preSqlCmd(), parser, batchParameters.get(i), localInfileInputStream),
                    this,
                    0,
                    maxRows,
                    ResultSet.CONCUR_READ_ONLY,
                    ResultSet.TYPE_FORWARD_ONLY,
                    closeOnCompletion,
                    false));
      }
    } catch (SQLException bue) {
      BatchUpdateException exception =
          exceptionFactory().createBatchUpdate(results, batchParameters.size(), bue);
      results = null;
      localInfileInputStream = null;
      throw exception;
    }
  }

  /**
   * 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 getMoreResults 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 * @throws SQLTimeoutException when the driver has determined that the timeout value that was * specified by the {@code setQueryTimeout} method has been exceeded and has at least * attempted to cancel the currently running {@code Statement} * @see Statement#execute * @see Statement#getResultSet * @see Statement#getUpdateCount * @see Statement#getMoreResults */ @Override public boolean execute() throws SQLException { executeInternal(); currResult = results.remove(0); return currResult instanceof Result; } /** * 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 * @throws SQLTimeoutException when the driver has determined that the timeout value that was * specified by the {@code setQueryTimeout} method has been exceeded and has at least * attempted to cancel the currently running {@code Statement} */ @Override public ResultSet executeQuery() throws SQLException { executeInternal(); currResult = results.remove(0); if (currResult instanceof Result) { return (Result) currResult; } if (Boolean.parseBoolean( con.getContext().getConf().nonMappedOptions().getProperty("permitNoResults", "false"))) { // for compatibility with pre 3.4.0 version return new CompleteResult( new ColumnDecoder[0], new byte[0][], con.getContext(), resultSetType); } throw new SQLException( "PrepareStatement.executeQuery() command does NOT return a result-set as expected. Either" + " use PrepareStatement.execute(), PrepareStatement.executeUpdate(), or correct" + " command"); } /** * 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 * @throws SQLTimeoutException when the driver has determined that the timeout value that was * specified by the {@code setQueryTimeout} method has been exceeded and has at least * attempted to cancel the currently running {@code Statement} */ @Override public int executeUpdate() throws SQLException { return (int) executeLargeUpdate(); } /** * 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. * *

This method should be used when the returned row count may exceed {@link Integer#MAX_VALUE}. * *

The default implementation will throw {@code UnsupportedOperationException} * * @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 * @throws SQLTimeoutException when the driver has determined that the timeout value that was * specified by the {@code setQueryTimeout} method has been exceeded and has at least * attempted to cancel the currently running {@code Statement} * @since 1.8 */ @Override public long executeLargeUpdate() throws SQLException { executeInternal(); currResult = results.remove(0); if (currResult instanceof Result) { throw exceptionFactory() .create("the given SQL statement produces an unexpected ResultSet object", "HY000"); } return ((OkPacket) currResult).getAffectedRows(); } private ExceptionFactory exceptionFactory() { return con.getExceptionFactory().of(this); } /** * Adds a set of parameters to this PreparedStatement object's batch of commands. * * @throws SQLException if a database access error occurs or this method is called on a closed * PreparedStatement * @see Statement#addBatch * @since 1.2 */ @Override public void addBatch() throws SQLException { validParameters(); if (batchParameters == null) batchParameters = new ArrayList<>(); batchParameters.add(parameters); parameters = parameters.clone(); } /** * Validate parameter number according to expected parameter. * * @throws SQLException if doesn't correspond */ protected void validParameters() throws SQLException { for (int i = 0; i < parser.getParamCount(); i++) { if (!parameters.containsKey(i)) { throw exceptionFactory() .create("Parameter at position " + (i + 1) + " is not set", "07004"); } } } @Override public void setQueryTimeout(int seconds) throws SQLException { super.setQueryTimeout(seconds); if (canUseServerTimeout && prepareResult != null) { prepareResult.close(con.getClient()); prepareResult = null; } } @Override public void setMaxRows(int max) throws SQLException { super.setMaxRows(max); if (canUseServerMaxRows && prepareResult != null) { prepareResult.close(con.getClient()); prepareResult = null; } } @Override public void setLargeMaxRows(long max) throws SQLException { super.setLargeMaxRows(max); if (canUseServerMaxRows && prepareResult != null) { prepareResult.close(con.getClient()); prepareResult = null; } } /** * 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 */ @Override public ResultSetMetaData getMetaData() throws SQLException { // send COM_STMT_PREPARE if (prepareResult == null) con.getClient().execute(new PreparePacket(escapeTimeout(sql)), this, true); return new org.mariadb.jdbc.client.result.ResultSetMetaData( exceptionFactory(), prepareResult.getColumns(), con.getContext().getConf(), false); } /** * 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 */ @Override public java.sql.ParameterMetaData getParameterMetaData() throws SQLException { // send COM_STMT_PREPARE if (prepareResult == null) { try { con.getClient().execute(new PreparePacket(escapeTimeout(sql)), this, true); } catch (SQLException e) { return new SimpleParameterMetaData(exceptionFactory(), parser.getParamCount()); } } return new ParameterMetaData(exceptionFactory(), prepareResult.getParameters()); } @Override @SuppressWarnings("try") public void close() throws SQLException { if (prepareResult != null) { try (ClosableLock ignore = lock.closeableLock()) { prepareResult.close(this.con.getClient()); } } con.fireStatementClosed(this); super.close(); } @Override public String toString() { return "ClientPreparedStatement{" + super.toString() + '}'; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy