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

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

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

*
  1. 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 *
  2. 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: *
  3. 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(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy