/* Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved. The MySQL Connector/J is licensed under the terms of the GPLv2 <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most MySQL Connectors. There are special exceptions to the terms and conditions of the GPLv2 as it is applied to this software, see the FLOSS License Exception <http://www.mysql.com/about/legal/licensing/foss-exception.html>. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package com.mysql.jdbc; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.math.BigDecimal; import java.net.URL; import java.sql.Array; import java.sql.Blob; import java.sql.Clob; import java.sql.Date; import java.sql.ParameterMetaData; import java.sql.Ref; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; import java.util.ArrayList; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.TimeZone; import com.mysql.jdbc.exceptions.MySQLStatementCancelledException; import com.mysql.jdbc.exceptions.MySQLTimeoutException; import com.mysql.jdbc.log.LogUtils; import com.mysql.jdbc.profiler.ProfilerEvent; /** * JDBC Interface for MySQL-4.1 and newer server-side PreparedStatements. * * @author Mark Matthews * @version $Id: ServerPreparedStatement.java,v 1.1.2.2 2005/05/17 14:58:56 * mmatthews Exp $ */ public class ServerPreparedStatement extends PreparedStatement { private static final Constructor<?> JDBC_4_SPS_CTOR; static { if (Util.isJdbc4()) { try { JDBC_4_SPS_CTOR = Class.forName("com.mysql.jdbc.JDBC4ServerPreparedStatement") .getConstructor( new Class[] { MySQLConnection.class, String.class, String.class, Integer.TYPE, Integer.TYPE}); } catch (SecurityException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } else { JDBC_4_SPS_CTOR = null; } } protected static final int BLOB_STREAM_READ_BUF_SIZE = 8192; public static class BatchedBindValues { public BindValue[] batchedParameterValues; BatchedBindValues(BindValue[] paramVals) { int numParams = paramVals.length; this.batchedParameterValues = new BindValue[numParams]; for (int i = 0; i < numParams; i++) { this.batchedParameterValues[i] = new BindValue(paramVals[i]); } } } public static class BindValue { public long boundBeforeExecutionNum = 0; public long bindLength; /* Default length of data */ public int bufferType; /* buffer type */ public double doubleBinding; public float floatBinding; public boolean isLongData; /* long data indicator */ public boolean isNull; /* NULL indicator */ public boolean isSet = false; /* has this parameter been set? */ public long longBinding; /* all integral values are stored here */ public Object value; /* The value to store */ BindValue() { } BindValue(BindValue copyMe) { this.value = copyMe.value; this.isSet = copyMe.isSet; this.isLongData = copyMe.isLongData; this.isNull = copyMe.isNull; this.bufferType = copyMe.bufferType; this.bindLength = copyMe.bindLength; this.longBinding = copyMe.longBinding; this.floatBinding = copyMe.floatBinding; this.doubleBinding = copyMe.doubleBinding; } void reset() { this.isSet = false; this.value = null; this.isLongData = false; this.longBinding = 0L; this.floatBinding = 0; this.doubleBinding = 0D; } public String toString() { return toString(false); } public String toString(boolean quoteIfNeeded) { if (this.isLongData) { return "' STREAM DATA '"; } switch (this.bufferType) { case MysqlDefs.FIELD_TYPE_TINY: case MysqlDefs.FIELD_TYPE_SHORT: case MysqlDefs.FIELD_TYPE_LONG: case MysqlDefs.FIELD_TYPE_LONGLONG: return String.valueOf(longBinding); case MysqlDefs.FIELD_TYPE_FLOAT: return String.valueOf(floatBinding); case MysqlDefs.FIELD_TYPE_DOUBLE: return String.valueOf(doubleBinding); case MysqlDefs.FIELD_TYPE_TIME: case MysqlDefs.FIELD_TYPE_DATE: case MysqlDefs.FIELD_TYPE_DATETIME: case MysqlDefs.FIELD_TYPE_TIMESTAMP: case MysqlDefs.FIELD_TYPE_VAR_STRING: case MysqlDefs.FIELD_TYPE_STRING: case MysqlDefs.FIELD_TYPE_VARCHAR: if (quoteIfNeeded) { return "'" + String.valueOf(value) + "'"; } return String.valueOf(value); default: if (value instanceof byte[]) { return "byte data"; } if (quoteIfNeeded) { return "'" + String.valueOf(value) + "'"; } return String.valueOf(value); } } long getBoundLength() { if (isNull) { return 0; } if (isLongData) { return bindLength; } switch (bufferType) { case MysqlDefs.FIELD_TYPE_TINY: return 1; case MysqlDefs.FIELD_TYPE_SHORT: return 2; case MysqlDefs.FIELD_TYPE_LONG: return 4; case MysqlDefs.FIELD_TYPE_LONGLONG: return 8; case MysqlDefs.FIELD_TYPE_FLOAT: return 4; case MysqlDefs.FIELD_TYPE_DOUBLE: return 8; case MysqlDefs.FIELD_TYPE_TIME: return 9; case MysqlDefs.FIELD_TYPE_DATE: return 7; case MysqlDefs.FIELD_TYPE_DATETIME: case MysqlDefs.FIELD_TYPE_TIMESTAMP: return 11; case MysqlDefs.FIELD_TYPE_VAR_STRING: case MysqlDefs.FIELD_TYPE_STRING: case MysqlDefs.FIELD_TYPE_VARCHAR: case MysqlDefs.FIELD_TYPE_DECIMAL: case MysqlDefs.FIELD_TYPE_NEW_DECIMAL: if (value instanceof byte[]) { return ((byte[]) value).length; } return ((String) value).length(); default: return 0; } } } /* 1 (length) + 2 (year) + 1 (month) + 1 (day) */ //private static final byte MAX_DATE_REP_LENGTH = (byte) 5; /* * 1 (length) + 2 (year) + 1 (month) + 1 (day) + 1 (hour) + 1 (minute) + 1 * (second) + 4 (microseconds) */ //private static final byte MAX_DATETIME_REP_LENGTH = 12; /* * 1 (length) + 1 (is negative) + 4 (day count) + 1 (hour) + 1 (minute) + 1 * (seconds) + 4 (microseconds) */ //private static final byte MAX_TIME_REP_LENGTH = 13; private boolean hasOnDuplicateKeyUpdate = false; private void storeTime(Buffer intoBuf, Time tm) throws SQLException { intoBuf.ensureCapacity(9); intoBuf.writeByte((byte) 8); // length intoBuf.writeByte((byte) 0); // neg flag intoBuf.writeLong(0); // tm->day, not used Calendar sessionCalendar = getCalendarInstanceForSessionOrNew(); synchronized (sessionCalendar) { java.util.Date oldTime = sessionCalendar.getTime(); try { sessionCalendar.setTime(tm); intoBuf.writeByte((byte) sessionCalendar.get(Calendar.HOUR_OF_DAY)); intoBuf.writeByte((byte) sessionCalendar.get(Calendar.MINUTE)); intoBuf.writeByte((byte) sessionCalendar.get(Calendar.SECOND)); // intoBuf.writeLongInt(0); // tm-second_part } finally { sessionCalendar.setTime(oldTime); } } } /** * Flag indicating whether or not the long parameters have been 'switched' * back to normal parameters. We can not execute() if clearParameters() * hasn't been called in this case. */ private boolean detectedLongParameterSwitch = false; /** * The number of fields in the result set (if any) for this * PreparedStatement. */ private int fieldCount; /** Has this prepared statement been marked invalid? */ private boolean invalid = false; /** If this statement has been marked invalid, what was the reason? */ private SQLException invalidationException; private Buffer outByteBuffer; /** Bind values for individual fields */ private BindValue[] parameterBindings; /** Field-level metadata for parameters */ private Field[] parameterFields; /** Field-level metadata for result sets. */ private Field[] resultFields; /** Do we need to send/resend types to the server? */ private boolean sendTypesToServer = false; /** The ID that the server uses to identify this PreparedStatement */ private long serverStatementId; /** The type used for string bindings, changes from version-to-version */ private int stringTypeCode = MysqlDefs.FIELD_TYPE_STRING; private boolean serverNeedsResetBeforeEachExecution; /** * Creates a prepared statement instance -- We need to provide factory-style * methods so we can support both JDBC3 (and older) and JDBC4 runtimes, * otherwise the class verifier complains when it tries to load JDBC4-only * interface classes that are present in JDBC4 method signatures. */ protected static ServerPreparedStatement getInstance(MySQLConnection conn, String sql, String catalog, int resultSetType, int resultSetConcurrency) throws SQLException { if (!Util.isJdbc4()) { return new ServerPreparedStatement(conn, sql, catalog, resultSetType, resultSetConcurrency); } try { return (ServerPreparedStatement) JDBC_4_SPS_CTOR.newInstance(new Object[] { conn, sql, catalog, Integer.valueOf(resultSetType), Integer.valueOf(resultSetConcurrency) }); } catch (IllegalArgumentException e) { throw new SQLException(e.toString(), SQLError.SQL_STATE_GENERAL_ERROR); } catch (InstantiationException e) { throw new SQLException(e.toString(), SQLError.SQL_STATE_GENERAL_ERROR); } catch (IllegalAccessException e) { throw new SQLException(e.toString(), SQLError.SQL_STATE_GENERAL_ERROR); } catch (InvocationTargetException e) { Throwable target = e.getTargetException(); if (target instanceof SQLException) { throw (SQLException)target; } throw new SQLException(target.toString(), SQLError.SQL_STATE_GENERAL_ERROR); } } /** * Creates a new ServerPreparedStatement object. * * @param conn * the connection creating us. * @param sql * the SQL containing the statement to prepare. * @param catalog * the catalog in use when we were created. * * @throws SQLException * If an error occurs */ protected ServerPreparedStatement(MySQLConnection conn, String sql, String catalog, int resultSetType, int resultSetConcurrency) throws SQLException { super(conn, catalog); checkNullOrEmptyQuery(sql); this.hasOnDuplicateKeyUpdate = containsOnDuplicateKeyInString(sql); int startOfStatement = findStartOfStatement(sql); this.firstCharOfStmt = StringUtils.firstAlphaCharUc(sql, startOfStatement); if (this.connection.versionMeetsMinimum(5, 0, 0)) { this.serverNeedsResetBeforeEachExecution = !this.connection.versionMeetsMinimum(5, 0, 3); } else { this.serverNeedsResetBeforeEachExecution = !this.connection.versionMeetsMinimum(4, 1, 10); } this.useAutoSlowLog = this.connection.getAutoSlowLog(); this.useTrueBoolean = this.connection.versionMeetsMinimum(3, 21, 23); int lim_id = StringUtils.indexOfIgnoreCase(sql, "LIMIT"); if (lim_id != -1) { boolean hasPreviosIdChar = false; boolean hasFollowingIdChar = false; if (lim_id > 0 && ( sql.charAt(lim_id - 1) == '`' || StringUtils.isValidIdChar(sql.charAt(lim_id - 1)) )) { hasPreviosIdChar = true; } if (lim_id + 5 < sql.length() && ( sql.charAt(lim_id + 5) == '`' || StringUtils.isValidIdChar(sql.charAt(lim_id + 5)) )) { hasFollowingIdChar = true; } if (!hasPreviosIdChar && !hasFollowingIdChar) { this.hasLimitClause = true; } } else { this.hasLimitClause = false; } String statementComment = this.connection.getStatementComment(); this.originalSql = (statementComment == null) ? sql : "/* " + statementComment + " */ " + sql; if (this.connection.versionMeetsMinimum(4, 1, 2)) { this.stringTypeCode = MysqlDefs.FIELD_TYPE_VAR_STRING; } else { this.stringTypeCode = MysqlDefs.FIELD_TYPE_STRING; } try { serverPrepare(sql); } catch (SQLException sqlEx) { realClose(false, true); // don't wrap SQLExceptions throw sqlEx; } catch (Exception ex) { realClose(false, true); SQLException sqlEx = SQLError.createSQLException(ex.toString(), SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); sqlEx.initCause(ex); throw sqlEx; } setResultSetType(resultSetType); setResultSetConcurrency(resultSetConcurrency); this.parameterTypes = new int[this.parameterCount]; } /** * JDBC 2.0 Add a set of parameters to the batch. * * @exception SQLException * if a database-access error occurs. * * @see StatementImpl#addBatch */ public void addBatch() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.batchedArgs == null) { this.batchedArgs = new ArrayList<Object>(); } this.batchedArgs.add(new BatchedBindValues(this.parameterBindings)); } } protected String asSql(boolean quoteStreamsAndUnknowns) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { PreparedStatement pStmtForSub = null; try { pStmtForSub = PreparedStatement.getInstance(this.connection, this.originalSql, this.currentCatalog); int numParameters = pStmtForSub.parameterCount; int ourNumParameters = this.parameterCount; for (int i = 0; (i < numParameters) && (i < ourNumParameters); i++) { if (this.parameterBindings[i] != null) { if (this.parameterBindings[i].isNull) { pStmtForSub.setNull(i + 1, Types.NULL); } else { BindValue bindValue = this.parameterBindings[i]; // // Handle primitives first // switch (bindValue.bufferType) { case MysqlDefs.FIELD_TYPE_TINY: pStmtForSub.setByte(i + 1, (byte)bindValue.longBinding); break; case MysqlDefs.FIELD_TYPE_SHORT: pStmtForSub.setShort(i + 1, (short)bindValue.longBinding); break; case MysqlDefs.FIELD_TYPE_LONG: pStmtForSub.setInt(i + 1, (int)bindValue.longBinding); break; case MysqlDefs.FIELD_TYPE_LONGLONG: pStmtForSub.setLong(i + 1, bindValue.longBinding); break; case MysqlDefs.FIELD_TYPE_FLOAT: pStmtForSub.setFloat(i + 1, bindValue.floatBinding); break; case MysqlDefs.FIELD_TYPE_DOUBLE: pStmtForSub.setDouble(i + 1, bindValue.doubleBinding); break; default: pStmtForSub.setObject(i + 1, this.parameterBindings[i].value); break; } } } } return pStmtForSub.asSql(quoteStreamsAndUnknowns); } finally { if (pStmtForSub != null) { try { pStmtForSub.close(); } catch (SQLException sqlEx) { ; // ignore } } } } } /* * (non-Javadoc) * * @see com.mysql.jdbc.Statement#checkClosed() */ protected MySQLConnection checkClosed() throws SQLException { if (this.invalid) { throw this.invalidationException; } return super.checkClosed(); } /** * @see java.sql.PreparedStatement#clearParameters() */ public void clearParameters() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { clearParametersInternal(true); } } private void clearParametersInternal(boolean clearServerParameters) throws SQLException { boolean hadLongData = false; if (this.parameterBindings != null) { for (int i = 0; i < this.parameterCount; i++) { if ((this.parameterBindings[i] != null) && this.parameterBindings[i].isLongData) { hadLongData = true; } this.parameterBindings[i].reset(); } } if (clearServerParameters && hadLongData) { serverResetStatement(); this.detectedLongParameterSwitch = false; } } protected boolean isCached = false; private boolean useAutoSlowLog; private Calendar serverTzCalendar; private Calendar defaultTzCalendar; protected void setClosed(boolean flag) { this.isClosed = flag; } /** * @see java.sql.Statement#close() */ public void close() throws SQLException { try { synchronized (checkClosed().getConnectionMutex()) { if (this.isCached && !this.isClosed) { clearParameters(); this.isClosed = true; this.connection.recachePreparedStatement(this); return; } realClose(true, true); } } catch (SQLException sqlEx) { if (SQLError.SQL_STATE_CONNECTION_NOT_OPEN.equals(sqlEx.getSQLState())) { return; } throw sqlEx; } } private void dumpCloseForTestcase() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { StringBuffer buf = new StringBuffer(); this.connection.generateConnectionCommentBlock(buf); buf.append("DEALLOCATE PREPARE debug_stmt_"); buf.append(this.statementId); buf.append(";\n"); this.connection.dumpTestcaseQuery(buf.toString()); } } private void dumpExecuteForTestcase() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { StringBuffer buf = new StringBuffer(); for (int i = 0; i < this.parameterCount; i++) { this.connection.generateConnectionCommentBlock(buf); buf.append("SET @debug_stmt_param"); buf.append(this.statementId); buf.append("_"); buf.append(i); buf.append("="); if (this.parameterBindings[i].isNull) { buf.append("NULL"); } else { buf.append(this.parameterBindings[i].toString(true)); } buf.append(";\n"); } this.connection.generateConnectionCommentBlock(buf); buf.append("EXECUTE debug_stmt_"); buf.append(this.statementId); if (this.parameterCount > 0) { buf.append(" USING "); for (int i = 0; i < this.parameterCount; i++) { if (i > 0) { buf.append(", "); } buf.append("@debug_stmt_param"); buf.append(this.statementId); buf.append("_"); buf.append(i); } } buf.append(";\n"); this.connection.dumpTestcaseQuery(buf.toString()); } } private void dumpPrepareForTestcase() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { StringBuffer buf = new StringBuffer(this.originalSql.length() + 64); this.connection.generateConnectionCommentBlock(buf); buf.append("PREPARE debug_stmt_"); buf.append(this.statementId); buf.append(" FROM \""); buf.append(this.originalSql); buf.append("\";\n"); this.connection.dumpTestcaseQuery(buf.toString()); } } protected int[] executeBatchSerially(int batchTimeout) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { MySQLConnection locallyScopedConn = this.connection; if (locallyScopedConn.isReadOnly()) { throw SQLError.createSQLException(Messages .getString("ServerPreparedStatement.2") //$NON-NLS-1$ + Messages.getString("ServerPreparedStatement.3"), //$NON-NLS-1$ SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } clearWarnings(); // Store this for later, we're going to 'swap' them out // as we execute each batched statement... BindValue[] oldBindValues = this.parameterBindings; try { int[] updateCounts = null; if (this.batchedArgs != null) { int nbrCommands = this.batchedArgs.size(); updateCounts = new int[nbrCommands]; if (this.retrieveGeneratedKeys) { this.batchedGeneratedKeys = new ArrayList<ResultSetRow>(nbrCommands); } for (int i = 0; i < nbrCommands; i++) { updateCounts[i] = -3; } SQLException sqlEx = null; int commandIndex = 0; BindValue[] previousBindValuesForBatch = null; CancelTask timeoutTask = null; try { if (locallyScopedConn.getEnableQueryTimeouts() && batchTimeout != 0 && locallyScopedConn.versionMeetsMinimum(5, 0, 0)) { timeoutTask = new CancelTask(this); locallyScopedConn.getCancelTimer().schedule(timeoutTask, batchTimeout); } for (commandIndex = 0; commandIndex < nbrCommands; commandIndex++) { Object arg = this.batchedArgs.get(commandIndex); if (arg instanceof String) { updateCounts[commandIndex] = executeUpdate((String) arg); } else { this.parameterBindings = ((BatchedBindValues) arg).batchedParameterValues; try { // We need to check types each time, as // the user might have bound different // types in each addBatch() if (previousBindValuesForBatch != null) { for (int j = 0; j < this.parameterBindings.length; j++) { if (this.parameterBindings[j].bufferType != previousBindValuesForBatch[j].bufferType) { this.sendTypesToServer = true; break; } } } try { updateCounts[commandIndex] = executeUpdate(false, true); } finally { previousBindValuesForBatch = this.parameterBindings; } if (this.retrieveGeneratedKeys) { java.sql.ResultSet rs = null; try { // we don't want to use our version, // because we've altered the behavior of // ours to support batch updates // (catch-22) // Ideally, what we need here is // super.super.getGeneratedKeys() // but that construct doesn't exist in // Java, so that's why there's // this kludge. rs = getGeneratedKeysInternal(); while (rs.next()) { this.batchedGeneratedKeys .add(new ByteArrayRow(new byte[][] { rs .getBytes(1) }, getExceptionInterceptor())); } } finally { if (rs != null) { rs.close(); } } } } catch (SQLException ex) { updateCounts[commandIndex] = EXECUTE_FAILED; if (this.continueBatchOnError && !(ex instanceof MySQLTimeoutException) && !(ex instanceof MySQLStatementCancelledException) && !hasDeadlockOrTimeoutRolledBackTx(ex)) { sqlEx = ex; } else { int[] newUpdateCounts = new int[commandIndex]; System.arraycopy(updateCounts, 0, newUpdateCounts, 0, commandIndex); throw new java.sql.BatchUpdateException(ex .getMessage(), ex.getSQLState(), ex .getErrorCode(), newUpdateCounts); } } } } } finally { if (timeoutTask != null) { timeoutTask.cancel(); locallyScopedConn.getCancelTimer().purge(); } resetCancelledState(); } if (sqlEx != null) { throw new java.sql.BatchUpdateException(sqlEx .getMessage(), sqlEx.getSQLState(), sqlEx .getErrorCode(), updateCounts); } } return (updateCounts != null) ? updateCounts : new int[0]; } finally { this.parameterBindings = oldBindValues; this.sendTypesToServer = true; clearBatch(); } } } /** * @see com.mysql.jdbc.PreparedStatement#executeInternal(int, * com.mysql.jdbc.Buffer, boolean, boolean) */ protected com.mysql.jdbc.ResultSetInternalMethods executeInternal(int maxRowsToRetrieve, Buffer sendPacket, boolean createStreamingResultSet, boolean queryIsSelectOnly, Field[] metadataFromCache, boolean isBatch) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { this.numberOfExecutions++; // We defer to server-side execution try { return serverExecute(maxRowsToRetrieve, createStreamingResultSet, metadataFromCache); } catch (SQLException sqlEx) { // don't wrap SQLExceptions if (this.connection.getEnablePacketDebug()) { this.connection.getIO().dumpPacketRingBuffer(); } if (this.connection.getDumpQueriesOnException()) { String extractedSql = toString(); StringBuffer messageBuf = new StringBuffer(extractedSql .length() + 32); messageBuf .append("\n\nQuery being executed when exception was thrown:\n"); messageBuf.append(extractedSql); messageBuf.append("\n\n"); sqlEx = ConnectionImpl.appendMessageToException(sqlEx, messageBuf .toString(), getExceptionInterceptor()); } throw sqlEx; } catch (Exception ex) { if (this.connection.getEnablePacketDebug()) { this.connection.getIO().dumpPacketRingBuffer(); } SQLException sqlEx = SQLError.createSQLException(ex.toString(), SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); if (this.connection.getDumpQueriesOnException()) { String extractedSql = toString(); StringBuffer messageBuf = new StringBuffer(extractedSql .length() + 32); messageBuf .append("\n\nQuery being executed when exception was thrown:\n"); messageBuf.append(extractedSql); messageBuf.append("\n\n"); sqlEx = ConnectionImpl.appendMessageToException(sqlEx, messageBuf .toString(), getExceptionInterceptor()); } sqlEx.initCause(ex); throw sqlEx; } } } /** * @see com.mysql.jdbc.PreparedStatement#fillSendPacket() */ protected Buffer fillSendPacket() throws SQLException { return null; // we don't use this type of packet } /** * @see com.mysql.jdbc.PreparedStatement#fillSendPacket(byte, * java.io.InputStream, boolean, int) */ protected Buffer fillSendPacket(byte[][] batchedParameterStrings, InputStream[] batchedParameterStreams, boolean[] batchedIsStream, int[] batchedStreamLengths) throws SQLException { return null; // we don't use this type of packet } /** * Returns the structure representing the value that (can be)/(is) * bound at the given parameter index. * * @param parameterIndex 1-based * @param forLongData is this for a stream? * @return * @throws SQLException */ protected BindValue getBinding(int parameterIndex, boolean forLongData) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.parameterBindings.length == 0) { throw SQLError.createSQLException(Messages .getString("ServerPreparedStatement.8"), //$NON-NLS-1$ SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } parameterIndex--; if ((parameterIndex < 0) || (parameterIndex >= this.parameterBindings.length)) { throw SQLError.createSQLException(Messages .getString("ServerPreparedStatement.9") //$NON-NLS-1$ + (parameterIndex + 1) + Messages.getString("ServerPreparedStatement.10") //$NON-NLS-1$ + this.parameterBindings.length, SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } if (this.parameterBindings[parameterIndex] == null) { this.parameterBindings[parameterIndex] = new BindValue(); } else { if (this.parameterBindings[parameterIndex].isLongData && !forLongData) { this.detectedLongParameterSwitch = true; } } this.parameterBindings[parameterIndex].isSet = true; this.parameterBindings[parameterIndex].boundBeforeExecutionNum = this.numberOfExecutions; return this.parameterBindings[parameterIndex]; } } /** * Return current bind values for use by Statement Interceptors. * @return the bind values as set by setXXX and stored by addBatch * @see #executeBatch() * @see #addBatch() */ public BindValue[] getParameterBindValues() { return parameterBindings; } /** * @see com.mysql.jdbc.PreparedStatement#getBytes(int) */ byte[] getBytes(int parameterIndex) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { BindValue bindValue = getBinding(parameterIndex, false); if (bindValue.isNull) { return null; } else if (bindValue.isLongData) { throw SQLError.notImplemented(); } else { if (this.outByteBuffer == null) { this.outByteBuffer = new Buffer(this.connection .getNetBufferLength()); } this.outByteBuffer.clear(); int originalPosition = this.outByteBuffer.getPosition(); storeBinding(this.outByteBuffer, bindValue, this.connection.getIO()); int newPosition = this.outByteBuffer.getPosition(); int length = newPosition - originalPosition; byte[] valueAsBytes = new byte[length]; System.arraycopy(this.outByteBuffer.getByteBuffer(), originalPosition, valueAsBytes, 0, length); return valueAsBytes; } } } /** * @see java.sql.PreparedStatement#getMetaData() */ public java.sql.ResultSetMetaData getMetaData() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.resultFields == null) { return null; } return new ResultSetMetaData(this.resultFields, this.connection.getUseOldAliasMetadataBehavior(), getExceptionInterceptor()); } } /** * @see java.sql.PreparedStatement#getParameterMetaData() */ public ParameterMetaData getParameterMetaData() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.parameterMetaData == null) { this.parameterMetaData = new MysqlParameterMetadata( this.parameterFields, this.parameterCount, getExceptionInterceptor()); } return this.parameterMetaData; } } /** * @see com.mysql.jdbc.PreparedStatement#isNull(int) */ boolean isNull(int paramIndex) { throw new IllegalArgumentException(Messages .getString("ServerPreparedStatement.7")); //$NON-NLS-1$ } /** * Closes this connection and frees all resources. * * @param calledExplicitly * was this called from close()? * * @throws SQLException * if an error occurs */ protected void realClose(boolean calledExplicitly, boolean closeOpenResults) throws SQLException { MySQLConnection locallyScopedConn; try { locallyScopedConn = checkClosed(); } catch (SQLException sqlEx) { return; // already closed } synchronized (locallyScopedConn.getConnectionMutex()) { if (this.connection != null) { if (this.connection.getAutoGenerateTestcaseScript()) { dumpCloseForTestcase(); } // // Don't communicate with the server if we're being // called from the finalizer... // // This will leak server resources, but if we don't do this, // we'll deadlock (potentially, because there's no guarantee // when, what order, and what concurrency finalizers will be // called with). Well-behaved programs won't rely on finalizers // to clean up their statements. // SQLException exceptionDuringClose = null; if (calledExplicitly && !this.connection.isClosed()) { synchronized (this.connection.getConnectionMutex()) { try { MysqlIO mysql = this.connection.getIO(); Buffer packet = mysql.getSharedSendPacket(); packet.writeByte((byte) MysqlDefs.COM_CLOSE_STATEMENT); packet.writeLong(this.serverStatementId); mysql.sendCommand(MysqlDefs.COM_CLOSE_STATEMENT, null, packet, true, null, 0); } catch (SQLException sqlEx) { exceptionDuringClose = sqlEx; } } } super.realClose(calledExplicitly, closeOpenResults); clearParametersInternal(false); this.parameterBindings = null; this.parameterFields = null; this.resultFields = null; if (exceptionDuringClose != null) { throw exceptionDuringClose; } } } } /** * Used by Connection when auto-reconnecting to retrieve 'lost' prepared * statements. * * @throws SQLException * if an error occurs. */ protected void rePrepare() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { this.invalidationException = null; try { serverPrepare(this.originalSql); } catch (SQLException sqlEx) { // don't wrap SQLExceptions this.invalidationException = sqlEx; } catch (Exception ex) { this.invalidationException = SQLError.createSQLException(ex.toString(), SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); this.invalidationException.initCause(ex); } if (this.invalidationException != null) { this.invalid = true; this.parameterBindings = null; this.parameterFields = null; this.resultFields = null; if (this.results != null) { try { this.results.close(); } catch (Exception ex) { ; } } if (this.generatedKeysResults != null) { try { this.generatedKeysResults.close(); } catch (Exception ex) { ; } } try { closeAllOpenResults(); } catch (Exception e) { ; } if (this.connection != null) { if (this.maxRowsChanged) { this.connection.unsetMaxRows(this); } if (!this.connection.getDontTrackOpenResources()) { this.connection.unregisterStatement(this); } } } } } /** * Tells the server to execute this prepared statement with the current * parameter bindings. * * <pre> * * * - Server gets the command 'COM_EXECUTE' to execute the * previously prepared query. If there is any param markers; * then client will send the data in the following format: * * [COM_EXECUTE:1] * [STMT_ID:4] * [NULL_BITS:(param_count+7)/8)] * [TYPES_SUPPLIED_BY_CLIENT(0/1):1] * [[length]data] * [[length]data] .. [[length]data]. * * (Note: Except for string/binary types; all other types will not be * supplied with length field) * * * </pre> * * @param maxRowsToRetrieve * DOCUMENT ME! * @param createStreamingResultSet * DOCUMENT ME! * * @return DOCUMENT ME! * * @throws SQLException */ private com.mysql.jdbc.ResultSetInternalMethods serverExecute(int maxRowsToRetrieve, boolean createStreamingResultSet, Field[] metadataFromCache) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { MysqlIO mysql = this.connection.getIO(); if (mysql.shouldIntercept()) { ResultSetInternalMethods interceptedResults = mysql.invokeStatementInterceptorsPre(this.originalSql, this, true); if (interceptedResults != null) { return interceptedResults; } } if (this.detectedLongParameterSwitch) { // Check when values were bound boolean firstFound = false; long boundTimeToCheck = 0; for (int i = 0; i < this.parameterCount - 1; i++) { if (this.parameterBindings[i].isLongData) { if (firstFound && boundTimeToCheck != this.parameterBindings[i].boundBeforeExecutionNum) { throw SQLError.createSQLException(Messages .getString("ServerPreparedStatement.11") //$NON-NLS-1$ + Messages.getString("ServerPreparedStatement.12"), //$NON-NLS-1$ SQLError.SQL_STATE_DRIVER_NOT_CAPABLE, getExceptionInterceptor()); } firstFound = true; boundTimeToCheck = this.parameterBindings[i].boundBeforeExecutionNum; } } // Okay, we've got all "newly"-bound streams, so reset // server-side state to clear out previous bindings serverResetStatement(); } // Check bindings for (int i = 0; i < this.parameterCount; i++) { if (!this.parameterBindings[i].isSet) { throw SQLError.createSQLException(Messages .getString("ServerPreparedStatement.13") + (i + 1) //$NON-NLS-1$ + Messages.getString("ServerPreparedStatement.14"), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$ } } // // Send all long data // for (int i = 0; i < this.parameterCount; i++) { if (this.parameterBindings[i].isLongData) { serverLongData(i, this.parameterBindings[i]); } } if (this.connection.getAutoGenerateTestcaseScript()) { dumpExecuteForTestcase(); } // // store the parameter values // Buffer packet = mysql.getSharedSendPacket(); packet.clear(); packet.writeByte((byte) MysqlDefs.COM_EXECUTE); packet.writeLong(this.serverStatementId); // boolean usingCursor = false; if (this.connection.versionMeetsMinimum(4, 1, 2)) { // we only create cursor-backed result sets if // a) The query is a SELECT // b) The server supports it // c) We know it is forward-only (note this doesn't // preclude updatable result sets) // d) The user has set a fetch size if (this.resultFields != null && this.connection.isCursorFetchEnabled() && getResultSetType() == ResultSet.TYPE_FORWARD_ONLY && getResultSetConcurrency() == ResultSet.CONCUR_READ_ONLY && getFetchSize() > 0) { packet.writeByte(MysqlDefs.OPEN_CURSOR_FLAG); // usingCursor = true; } else { packet.writeByte((byte) 0); // placeholder for flags } packet.writeLong(1); // placeholder for parameter // iterations } /* Reserve place for null-marker bytes */ int nullCount = (this.parameterCount + 7) / 8; // if (mysql.versionMeetsMinimum(4, 1, 2)) { // nullCount = (this.parameterCount + 9) / 8; // } int nullBitsPosition = packet.getPosition(); for (int i = 0; i < nullCount; i++) { packet.writeByte((byte) 0); } byte[] nullBitsBuffer = new byte[nullCount]; /* In case if buffers (type) altered, indicate to server */ packet.writeByte(this.sendTypesToServer ? (byte) 1 : (byte) 0); if (this.sendTypesToServer) { /* * Store types of parameters in first in first package that is * sent to the server. */ for (int i = 0; i < this.parameterCount; i++) { packet.writeInt(this.parameterBindings[i].bufferType); } } // // store the parameter values // for (int i = 0; i < this.parameterCount; i++) { if (!this.parameterBindings[i].isLongData) { if (!this.parameterBindings[i].isNull) { storeBinding(packet, this.parameterBindings[i], mysql); } else { nullBitsBuffer[i / 8] |= (1 << (i & 7)); } } } // // Go back and write the NULL flags // to the beginning of the packet // int endPosition = packet.getPosition(); packet.setPosition(nullBitsPosition); packet.writeBytesNoNull(nullBitsBuffer); packet.setPosition(endPosition); long begin = 0; boolean logSlowQueries = this.connection.getLogSlowQueries(); boolean gatherPerformanceMetrics = this.connection .getGatherPerformanceMetrics(); if (this.profileSQL || logSlowQueries || gatherPerformanceMetrics) { begin = mysql.getCurrentTimeNanosOrMillis(); } resetCancelledState(); CancelTask timeoutTask = null; try { if (this.connection.getEnableQueryTimeouts() && this.timeoutInMillis != 0 && this.connection.versionMeetsMinimum(5, 0, 0)) { timeoutTask = new CancelTask(this); this.connection.getCancelTimer().schedule(timeoutTask, this.timeoutInMillis); } statementBegins(); Buffer resultPacket = mysql.sendCommand(MysqlDefs.COM_EXECUTE, null, packet, false, null, 0); long queryEndTime = 0L; if (logSlowQueries || gatherPerformanceMetrics || this.profileSQL) { queryEndTime = mysql.getCurrentTimeNanosOrMillis(); } if (timeoutTask != null) { timeoutTask.cancel(); this.connection.getCancelTimer().purge(); if (timeoutTask.caughtWhileCancelling != null) { throw timeoutTask.caughtWhileCancelling; } timeoutTask = null; } synchronized (this.cancelTimeoutMutex) { if (this.wasCancelled) { SQLException cause = null; if (this.wasCancelledByTimeout) { cause = new MySQLTimeoutException(); } else { cause = new MySQLStatementCancelledException(); } resetCancelledState(); throw cause; } } boolean queryWasSlow = false; if (logSlowQueries || gatherPerformanceMetrics) { long elapsedTime = queryEndTime - begin; if (logSlowQueries) { if (this.useAutoSlowLog) { queryWasSlow = elapsedTime > this.connection.getSlowQueryThresholdMillis(); } else { queryWasSlow = this.connection.isAbonormallyLongQuery(elapsedTime); this.connection.reportQueryTime(elapsedTime); } } if (queryWasSlow) { StringBuffer mesgBuf = new StringBuffer( 48 + this.originalSql.length()); mesgBuf.append(Messages .getString("ServerPreparedStatement.15")); //$NON-NLS-1$ mesgBuf.append(mysql.getSlowQueryThreshold()); mesgBuf.append(Messages .getString("ServerPreparedStatement.15a")); //$NON-NLS-1$ mesgBuf.append(elapsedTime); mesgBuf.append(Messages .getString("ServerPreparedStatement.16")); //$NON-NLS-1$ mesgBuf.append("as prepared: "); mesgBuf.append(this.originalSql); mesgBuf.append("\n\n with parameters bound:\n\n"); mesgBuf.append(asSql(true)); this.eventSink .consumeEvent(new ProfilerEvent( ProfilerEvent.TYPE_SLOW_QUERY, "", this.currentCatalog, this.connection.getId(), //$NON-NLS-1$ getId(), 0, System.currentTimeMillis(), elapsedTime, mysql .getQueryTimingUnits(), null, LogUtils.findCallingClassAndMethod(new Throwable()), mesgBuf.toString())); } if (gatherPerformanceMetrics) { this.connection.registerQueryExecutionTime(elapsedTime); } } this.connection.incrementNumberOfPreparedExecutes(); if (this.profileSQL) { this.eventSink = ProfilerEventHandlerFactory .getInstance(this.connection); this.eventSink.consumeEvent(new ProfilerEvent( ProfilerEvent.TYPE_EXECUTE, "", this.currentCatalog, //$NON-NLS-1$ this.connectionId, this.statementId, -1, System .currentTimeMillis(), mysql .getCurrentTimeNanosOrMillis() - begin, mysql.getQueryTimingUnits(), null, LogUtils.findCallingClassAndMethod(new Throwable()), truncateQueryToLog(asSql(true)))); } com.mysql.jdbc.ResultSetInternalMethods rs = mysql.readAllResults(this, maxRowsToRetrieve, this.resultSetType, this.resultSetConcurrency, createStreamingResultSet, this.currentCatalog, resultPacket, true, this.fieldCount, metadataFromCache); if (mysql.shouldIntercept()) { ResultSetInternalMethods interceptedResults = mysql.invokeStatementInterceptorsPost(this.originalSql, this, rs, true, null); if (interceptedResults != null) { rs = interceptedResults; } } if (this.profileSQL) { long fetchEndTime = mysql.getCurrentTimeNanosOrMillis(); this.eventSink.consumeEvent(new ProfilerEvent( ProfilerEvent.TYPE_FETCH, "", this.currentCatalog, this.connection.getId(), //$NON-NLS-1$ getId(), 0 /* FIXME rs.resultId */, System.currentTimeMillis(), (fetchEndTime - queryEndTime), mysql .getQueryTimingUnits(), null, LogUtils.findCallingClassAndMethod(new Throwable()), null)); } if (queryWasSlow && this.connection.getExplainSlowQueries()) { String queryAsString = asSql(true); mysql.explainSlowQuery(StringUtils.getBytes(queryAsString), queryAsString); } if (!createStreamingResultSet && this.serverNeedsResetBeforeEachExecution) { serverResetStatement(); // clear any long data... } this.sendTypesToServer = false; this.results = rs; if (mysql.hadWarnings()) { mysql.scanForAndThrowDataTruncation(); } return rs; } catch (SQLException sqlEx) { if (mysql.shouldIntercept()) { mysql.invokeStatementInterceptorsPost(this.originalSql, this, null, true, sqlEx); } throw sqlEx; } finally { this.statementExecuting.set(false); if (timeoutTask != null) { timeoutTask.cancel(); this.connection.getCancelTimer().purge(); } } } } /** * Sends stream-type data parameters to the server. * * <pre> * * Long data handling: * * - Server gets the long data in pieces with command type 'COM_LONG_DATA'. * - The packet recieved will have the format as: * [COM_LONG_DATA: 1][STMT_ID:4][parameter_number:2][type:2][data] * - Checks if the type is specified by client, and if yes reads the type, * and stores the data in that format. * - It's up to the client to check for read data ended. The server doesn't * care; and also server doesn't notify to the client that it got the * data or not; if there is any error; then during execute; the error * will be returned * * </pre> * * @param parameterIndex * DOCUMENT ME! * @param longData * DOCUMENT ME! * * @throws SQLException * if an error occurs. */ private void serverLongData(int parameterIndex, BindValue longData) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { MysqlIO mysql = this.connection.getIO(); Buffer packet = mysql.getSharedSendPacket(); Object value = longData.value; if (value instanceof byte[]) { packet.clear(); packet.writeByte((byte) MysqlDefs.COM_LONG_DATA); packet.writeLong(this.serverStatementId); packet.writeInt((parameterIndex)); packet.writeBytesNoNull((byte[]) longData.value); mysql.sendCommand(MysqlDefs.COM_LONG_DATA, null, packet, true, null, 0); } else if (value instanceof InputStream) { storeStream(mysql, parameterIndex, packet, (InputStream) value); } else if (value instanceof java.sql.Blob) { storeStream(mysql, parameterIndex, packet, ((java.sql.Blob) value).getBinaryStream()); } else if (value instanceof Reader) { storeReader(mysql, parameterIndex, packet, (Reader) value); } else { throw SQLError.createSQLException(Messages .getString("ServerPreparedStatement.18") //$NON-NLS-1$ + value.getClass().getName() + "'", //$NON-NLS-1$ SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } } } private void serverPrepare(String sql) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { MysqlIO mysql = this.connection.getIO(); if (this.connection.getAutoGenerateTestcaseScript()) { dumpPrepareForTestcase(); } try { long begin = 0; if (StringUtils.startsWithIgnoreCaseAndWs(sql, "LOAD DATA")) { //$NON-NLS-1$ this.isLoadDataQuery = true; } else { this.isLoadDataQuery = false; } if (this.connection.getProfileSql()) { begin = System.currentTimeMillis(); } String characterEncoding = null; String connectionEncoding = this.connection.getEncoding(); if (!this.isLoadDataQuery && this.connection.getUseUnicode() && (connectionEncoding != null)) { characterEncoding = connectionEncoding; } Buffer prepareResultPacket = mysql.sendCommand( MysqlDefs.COM_PREPARE, sql, null, false, characterEncoding, 0); if (this.connection.versionMeetsMinimum(4, 1, 1)) { // 4.1.1 and newer use the first byte // as an 'ok' or 'error' flag, so move // the buffer pointer past it to // start reading the statement id. prepareResultPacket.setPosition(1); } else { // 4.1.0 doesn't use the first byte as an // 'ok' or 'error' flag prepareResultPacket.setPosition(0); } this.serverStatementId = prepareResultPacket.readLong(); this.fieldCount = prepareResultPacket.readInt(); this.parameterCount = prepareResultPacket.readInt(); this.parameterBindings = new BindValue[this.parameterCount]; for (int i = 0; i < this.parameterCount; i++) { this.parameterBindings[i] = new BindValue(); } this.connection.incrementNumberOfPrepares(); if (this.profileSQL) { this.eventSink.consumeEvent(new ProfilerEvent( ProfilerEvent.TYPE_PREPARE, "", this.currentCatalog, //$NON-NLS-1$ this.connectionId, this.statementId, -1, System.currentTimeMillis(), mysql.getCurrentTimeNanosOrMillis() - begin, mysql.getQueryTimingUnits(), null, LogUtils.findCallingClassAndMethod(new Throwable()), truncateQueryToLog(sql))); } if (this.parameterCount > 0) { if (this.connection.versionMeetsMinimum(4, 1, 2) && !mysql.isVersion(5, 0, 0)) { this.parameterFields = new Field[this.parameterCount]; Buffer metaDataPacket = mysql.readPacket(); int i = 0; while (!metaDataPacket.isLastDataPacket() && (i < this.parameterCount)) { this.parameterFields[i++] = mysql.unpackField( metaDataPacket, false); metaDataPacket = mysql.readPacket(); } } } if (this.fieldCount > 0) { this.resultFields = new Field[this.fieldCount]; Buffer fieldPacket = mysql.readPacket(); int i = 0; // Read in the result set column information while (!fieldPacket.isLastDataPacket() && (i < this.fieldCount)) { this.resultFields[i++] = mysql.unpackField(fieldPacket, false); fieldPacket = mysql.readPacket(); } } } catch (SQLException sqlEx) { if (this.connection.getDumpQueriesOnException()) { StringBuffer messageBuf = new StringBuffer(this.originalSql .length() + 32); messageBuf .append("\n\nQuery being prepared when exception was thrown:\n\n"); messageBuf.append(this.originalSql); sqlEx = ConnectionImpl.appendMessageToException(sqlEx, messageBuf.toString(), getExceptionInterceptor()); } throw sqlEx; } finally { // Leave the I/O channel in a known state...there might be // packets out there // that we're not interested in this.connection.getIO().clearInputStream(); } } } private String truncateQueryToLog(String sql) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { String query = null; if (sql.length() > this.connection.getMaxQuerySizeToLog()) { StringBuffer queryBuf = new StringBuffer( this.connection.getMaxQuerySizeToLog() + 12); queryBuf.append(sql.substring(0, this.connection.getMaxQuerySizeToLog())); queryBuf.append(Messages.getString("MysqlIO.25")); query = queryBuf.toString(); } else { query = sql; } return query; } } private void serverResetStatement() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { MysqlIO mysql = this.connection.getIO(); Buffer packet = mysql.getSharedSendPacket(); packet.clear(); packet.writeByte((byte) MysqlDefs.COM_RESET_STMT); packet.writeLong(this.serverStatementId); try { mysql.sendCommand(MysqlDefs.COM_RESET_STMT, null, packet, !this.connection.versionMeetsMinimum(4, 1, 2), null, 0); } catch (SQLException sqlEx) { throw sqlEx; } catch (Exception ex) { SQLException sqlEx = SQLError.createSQLException(ex.toString(), SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); sqlEx.initCause(ex); throw sqlEx; } finally { mysql.clearInputStream(); } } } /** * @see java.sql.PreparedStatement#setArray(int, java.sql.Array) */ public void setArray(int i, Array x) throws SQLException { throw SQLError.notImplemented(); } /** * @see java.sql.PreparedStatement#setAsciiStream(int, java.io.InputStream, * int) */ public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (x == null) { setNull(parameterIndex, java.sql.Types.BINARY); } else { BindValue binding = getBinding(parameterIndex, true); setType(binding, MysqlDefs.FIELD_TYPE_BLOB); binding.value = x; binding.isNull = false; binding.isLongData = true; if (this.connection.getUseStreamLengthsInPrepStmts()) { binding.bindLength = length; } else { binding.bindLength = -1; } } } } /** * @see java.sql.PreparedStatement#setBigDecimal(int, java.math.BigDecimal) */ public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (x == null) { setNull(parameterIndex, java.sql.Types.DECIMAL); } else { BindValue binding = getBinding(parameterIndex, false); if (this.connection.versionMeetsMinimum(5, 0, 3)) { setType(binding, MysqlDefs.FIELD_TYPE_NEW_DECIMAL); } else { setType(binding, this.stringTypeCode); } binding.value = StringUtils .fixDecimalExponent(StringUtils.consistentToString(x)); binding.isNull = false; binding.isLongData = false; } } } /** * @see java.sql.PreparedStatement#setBinaryStream(int, java.io.InputStream, * int) */ public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (x == null) { setNull(parameterIndex, java.sql.Types.BINARY); } else { BindValue binding = getBinding(parameterIndex, true); setType(binding, MysqlDefs.FIELD_TYPE_BLOB); binding.value = x; binding.isNull = false; binding.isLongData = true; if (this.connection.getUseStreamLengthsInPrepStmts()) { binding.bindLength = length; } else { binding.bindLength = -1; } } } } /** * @see java.sql.PreparedStatement#setBlob(int, java.sql.Blob) */ public void setBlob(int parameterIndex, Blob x) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (x == null) { setNull(parameterIndex, java.sql.Types.BINARY); } else { BindValue binding = getBinding(parameterIndex, true); setType(binding, MysqlDefs.FIELD_TYPE_BLOB); binding.value = x; binding.isNull = false; binding.isLongData = true; if (this.connection.getUseStreamLengthsInPrepStmts()) { binding.bindLength = x.length(); } else { binding.bindLength = -1; } } } } /** * @see java.sql.PreparedStatement#setBoolean(int, boolean) */ public void setBoolean(int parameterIndex, boolean x) throws SQLException { setByte(parameterIndex, (x ? (byte) 1 : (byte) 0)); } /** * @see java.sql.PreparedStatement#setByte(int, byte) */ public void setByte(int parameterIndex, byte x) throws SQLException { checkClosed(); BindValue binding = getBinding(parameterIndex, false); setType(binding, MysqlDefs.FIELD_TYPE_TINY); binding.value = null; binding.longBinding = x; binding.isNull = false; binding.isLongData = false; } /** * @see java.sql.PreparedStatement#setBytes(int, byte) */ public void setBytes(int parameterIndex, byte[] x) throws SQLException { checkClosed(); if (x == null) { setNull(parameterIndex, java.sql.Types.BINARY); } else { BindValue binding = getBinding(parameterIndex, false); setType(binding, MysqlDefs.FIELD_TYPE_VAR_STRING); binding.value = x; binding.isNull = false; binding.isLongData = false; } } /** * @see java.sql.PreparedStatement#setCharacterStream(int, java.io.Reader, * int) */ public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (reader == null) { setNull(parameterIndex, java.sql.Types.BINARY); } else { BindValue binding = getBinding(parameterIndex, true); setType(binding, MysqlDefs.FIELD_TYPE_BLOB); binding.value = reader; binding.isNull = false; binding.isLongData = true; if (this.connection.getUseStreamLengthsInPrepStmts()) { binding.bindLength = length; } else { binding.bindLength = -1; } } } } /** * @see java.sql.PreparedStatement#setClob(int, java.sql.Clob) */ public void setClob(int parameterIndex, Clob x) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (x == null) { setNull(parameterIndex, java.sql.Types.BINARY); } else { BindValue binding = getBinding(parameterIndex, true); setType(binding, MysqlDefs.FIELD_TYPE_BLOB); binding.value = x.getCharacterStream(); binding.isNull = false; binding.isLongData = true; if (this.connection.getUseStreamLengthsInPrepStmts()) { binding.bindLength = x.length(); } else { binding.bindLength = -1; } } } } /** * Set a parameter to a java.sql.Date value. The driver converts this to a * SQL DATE value when it sends it to the database. * * @param parameterIndex * the first parameter is 1, the second is 2, ... * @param x * the parameter value * * @exception SQLException * if a database-access error occurs. */ public void setDate(int parameterIndex, Date x) throws SQLException { setDate(parameterIndex, x, null); } /** * Set a parameter to a java.sql.Date value. The driver converts this to a * SQL DATE value when it sends it to the database. * * @param parameterIndex * the first parameter is 1, the second is 2, ... * @param x * the parameter value * @param cal * the calendar to interpret the date with * * @exception SQLException * if a database-access error occurs. */ public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException { if (x == null) { setNull(parameterIndex, java.sql.Types.DATE); } else { BindValue binding = getBinding(parameterIndex, false); setType(binding, MysqlDefs.FIELD_TYPE_DATE); binding.value = x; binding.isNull = false; binding.isLongData = false; } } /** * @see java.sql.PreparedStatement#setDouble(int, double) */ public void setDouble(int parameterIndex, double x) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (!this.connection.getAllowNanAndInf() && (x == Double.POSITIVE_INFINITY || x == Double.NEGATIVE_INFINITY || Double.isNaN(x))) { throw SQLError.createSQLException("'" + x + "' is not a valid numeric or approximate numeric value", SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } BindValue binding = getBinding(parameterIndex, false); setType(binding, MysqlDefs.FIELD_TYPE_DOUBLE); binding.value = null; binding.doubleBinding = x; binding.isNull = false; binding.isLongData = false; } } /** * @see java.sql.PreparedStatement#setFloat(int, float) */ public void setFloat(int parameterIndex, float x) throws SQLException { checkClosed(); BindValue binding = getBinding(parameterIndex, false); setType(binding, MysqlDefs.FIELD_TYPE_FLOAT); binding.value = null; binding.floatBinding = x; binding.isNull = false; binding.isLongData = false; } /** * @see java.sql.PreparedStatement#setInt(int, int) */ public void setInt(int parameterIndex, int x) throws SQLException { checkClosed(); BindValue binding = getBinding(parameterIndex, false); setType(binding, MysqlDefs.FIELD_TYPE_LONG); binding.value = null; binding.longBinding = x; binding.isNull = false; binding.isLongData = false; } /** * @see java.sql.PreparedStatement#setLong(int, long) */ public void setLong(int parameterIndex, long x) throws SQLException { checkClosed(); BindValue binding = getBinding(parameterIndex, false); setType(binding, MysqlDefs.FIELD_TYPE_LONGLONG); binding.value = null; binding.longBinding = x; binding.isNull = false; binding.isLongData = false; } /** * @see java.sql.PreparedStatement#setNull(int, int) */ public void setNull(int parameterIndex, int sqlType) throws SQLException { checkClosed(); BindValue binding = getBinding(parameterIndex, false); // // Don't re-set types, but use something if this // parameter was never specified // if (binding.bufferType == 0) { setType(binding, MysqlDefs.FIELD_TYPE_NULL); } binding.value = null; binding.isNull = true; binding.isLongData = false; } /** * @see java.sql.PreparedStatement#setNull(int, int, java.lang.String) */ public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException { checkClosed(); BindValue binding = getBinding(parameterIndex, false); // // Don't re-set types, but use something if this // parameter was never specified // if (binding.bufferType == 0) { setType(binding, MysqlDefs.FIELD_TYPE_NULL); } binding.value = null; binding.isNull = true; binding.isLongData = false; } /** * @see java.sql.PreparedStatement#setRef(int, java.sql.Ref) */ public void setRef(int i, Ref x) throws SQLException { throw SQLError.notImplemented(); } /** * @see java.sql.PreparedStatement#setShort(int, short) */ public void setShort(int parameterIndex, short x) throws SQLException { checkClosed(); BindValue binding = getBinding(parameterIndex, false); setType(binding, MysqlDefs.FIELD_TYPE_SHORT); binding.value = null; binding.longBinding = x; binding.isNull = false; binding.isLongData = false; } /** * @see java.sql.PreparedStatement#setString(int, java.lang.String) */ public void setString(int parameterIndex, String x) throws SQLException { checkClosed(); if (x == null) { setNull(parameterIndex, java.sql.Types.CHAR); } else { BindValue binding = getBinding(parameterIndex, false); setType(binding, this.stringTypeCode); binding.value = x; binding.isNull = false; binding.isLongData = false; } } /** * Set a parameter to a java.sql.Time value. * * @param parameterIndex * the first parameter is 1...)); * @param x * the parameter value * * @throws SQLException * if a database access error occurs */ public void setTime(int parameterIndex, java.sql.Time x) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { setTimeInternal(parameterIndex, x, null, this.connection.getDefaultTimeZone(), false); } } /** * Set a parameter to a java.sql.Time value. The driver converts this to a * SQL TIME value when it sends it to the database, using the given * timezone. * * @param parameterIndex * the first parameter is 1...)); * @param x * the parameter value * @param cal * the timezone to use * * @throws SQLException * if a database access error occurs */ public void setTime(int parameterIndex, java.sql.Time x, Calendar cal) throws SQLException { setTimeInternal(parameterIndex, x, cal, cal.getTimeZone(), true); } /** * Set a parameter to a java.sql.Time value. The driver converts this to a * SQL TIME value when it sends it to the database, using the given * timezone. * * @param parameterIndex * the first parameter is 1...)); * @param x * the parameter value * @param tz * the timezone to use * * @throws SQLException * if a database access error occurs */ protected void setTimeInternal(int parameterIndex, java.sql.Time x, Calendar targetCalendar, TimeZone tz, boolean rollForward) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (x == null) { setNull(parameterIndex, java.sql.Types.TIME); } else { BindValue binding = getBinding(parameterIndex, false); setType(binding, MysqlDefs.FIELD_TYPE_TIME); if (!this.useLegacyDatetimeCode) { binding.value = x; } else { Calendar sessionCalendar = getCalendarInstanceForSessionOrNew(); binding.value = TimeUtil.changeTimezone(this.connection, sessionCalendar, targetCalendar, x, tz, this.connection.getServerTimezoneTZ(), rollForward); } binding.isNull = false; binding.isLongData = false; } } } /** * Set a parameter to a java.sql.Timestamp value. The driver converts this * to a SQL TIMESTAMP value when it sends it to the database. * * @param parameterIndex * the first parameter is 1, the second is 2, ... * @param x * the parameter value * * @throws SQLException * if a database-access error occurs. */ public void setTimestamp(int parameterIndex, java.sql.Timestamp x) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { setTimestampInternal(parameterIndex, x, null, this.connection.getDefaultTimeZone(), false); } } /** * Set a parameter to a java.sql.Timestamp value. The driver converts this * to a SQL TIMESTAMP value when it sends it to the database. * * @param parameterIndex * the first parameter is 1, the second is 2, ... * @param x * the parameter value * @param cal * the timezone to use * * @throws SQLException * if a database-access error occurs. */ public void setTimestamp(int parameterIndex, java.sql.Timestamp x, Calendar cal) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { setTimestampInternal(parameterIndex, x, cal, cal.getTimeZone(), true); } } protected void setTimestampInternal(int parameterIndex, java.sql.Timestamp x, Calendar targetCalendar, TimeZone tz, boolean rollForward) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (x == null) { setNull(parameterIndex, java.sql.Types.TIMESTAMP); } else { BindValue binding = getBinding(parameterIndex, false); setType(binding, MysqlDefs.FIELD_TYPE_DATETIME); if (!this.useLegacyDatetimeCode) { binding.value = x; } else { Calendar sessionCalendar = this.connection.getUseJDBCCompliantTimezoneShift() ? this.connection.getUtcCalendar() : getCalendarInstanceForSessionOrNew(); binding.value = TimeUtil.changeTimezone(this.connection, sessionCalendar, targetCalendar, x, tz, this.connection.getServerTimezoneTZ(), rollForward); binding.isNull = false; binding.isLongData = false; } } } } protected void setType(BindValue oldValue, int bufferType) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (oldValue.bufferType != bufferType) { this.sendTypesToServer = true; } oldValue.bufferType = bufferType; } } /** * DOCUMENT ME! * * @param parameterIndex * DOCUMENT ME! * @param x * DOCUMENT ME! * @param length * DOCUMENT ME! * * @throws SQLException * DOCUMENT ME! * @throws NotImplemented * DOCUMENT ME! * * @see java.sql.PreparedStatement#setUnicodeStream(int, * java.io.InputStream, int) * @deprecated */ public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException { checkClosed(); throw SQLError.notImplemented(); } /** * @see java.sql.PreparedStatement#setURL(int, java.net.URL) */ public void setURL(int parameterIndex, URL x) throws SQLException { checkClosed(); setString(parameterIndex, x.toString()); } /** * Method storeBinding. * * @param packet * @param bindValue * @param mysql * DOCUMENT ME! * * @throws SQLException * DOCUMENT ME! */ private void storeBinding(Buffer packet, BindValue bindValue, MysqlIO mysql) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { try { Object value = bindValue.value; // // Handle primitives first // switch (bindValue.bufferType) { case MysqlDefs.FIELD_TYPE_TINY: packet.writeByte((byte)bindValue.longBinding); return; case MysqlDefs.FIELD_TYPE_SHORT: packet.ensureCapacity(2); packet.writeInt((int)bindValue.longBinding); return; case MysqlDefs.FIELD_TYPE_LONG: packet.ensureCapacity(4); packet.writeLong((int)bindValue.longBinding); return; case MysqlDefs.FIELD_TYPE_LONGLONG: packet.ensureCapacity(8); packet.writeLongLong(bindValue.longBinding); return; case MysqlDefs.FIELD_TYPE_FLOAT: packet.ensureCapacity(4); packet.writeFloat(bindValue.floatBinding); return; case MysqlDefs.FIELD_TYPE_DOUBLE: packet.ensureCapacity(8); packet.writeDouble(bindValue.doubleBinding); return; case MysqlDefs.FIELD_TYPE_TIME: storeTime(packet, (Time) value); return; case MysqlDefs.FIELD_TYPE_DATE: case MysqlDefs.FIELD_TYPE_DATETIME: case MysqlDefs.FIELD_TYPE_TIMESTAMP: storeDateTime(packet, (java.util.Date) value, mysql, bindValue.bufferType); return; case MysqlDefs.FIELD_TYPE_VAR_STRING: case MysqlDefs.FIELD_TYPE_STRING: case MysqlDefs.FIELD_TYPE_VARCHAR: case MysqlDefs.FIELD_TYPE_DECIMAL: case MysqlDefs.FIELD_TYPE_NEW_DECIMAL: if (value instanceof byte[]) { packet.writeLenBytes((byte[]) value); } else if (!this.isLoadDataQuery) { packet.writeLenString((String) value, this.charEncoding, this.connection.getServerCharacterEncoding(), this.charConverter, this.connection .parserKnowsUnicode(), this.connection); } else { packet.writeLenBytes(StringUtils.getBytes((String) value)); } return; } } catch (UnsupportedEncodingException uEE) { throw SQLError.createSQLException(Messages .getString("ServerPreparedStatement.22") //$NON-NLS-1$ + this.connection.getEncoding() + "'", //$NON-NLS-1$ SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); } } } private void storeDateTime412AndOlder(Buffer intoBuf, java.util.Date dt, int bufferType) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { Calendar sessionCalendar = null; if (!this.useLegacyDatetimeCode) { if (bufferType == MysqlDefs.FIELD_TYPE_DATE) { sessionCalendar = getDefaultTzCalendar(); } else { sessionCalendar = getServerTzCalendar(); } } else { sessionCalendar = (dt instanceof Timestamp && this.connection.getUseJDBCCompliantTimezoneShift()) ? this.connection.getUtcCalendar() : getCalendarInstanceForSessionOrNew(); } java.util.Date oldTime = sessionCalendar.getTime(); try { intoBuf.ensureCapacity(8); intoBuf.writeByte((byte) 7); // length sessionCalendar.setTime(dt); int year = sessionCalendar.get(Calendar.YEAR); int month = sessionCalendar.get(Calendar.MONTH) + 1; int date = sessionCalendar.get(Calendar.DATE); intoBuf.writeInt(year); intoBuf.writeByte((byte) month); intoBuf.writeByte((byte) date); if (dt instanceof java.sql.Date) { intoBuf.writeByte((byte) 0); intoBuf.writeByte((byte) 0); intoBuf.writeByte((byte) 0); } else { intoBuf.writeByte((byte) sessionCalendar .get(Calendar.HOUR_OF_DAY)); intoBuf.writeByte((byte) sessionCalendar .get(Calendar.MINUTE)); intoBuf.writeByte((byte) sessionCalendar .get(Calendar.SECOND)); } } finally { sessionCalendar.setTime(oldTime); } } } /** * * @param intoBuf * @param dt * @param mysql * @param bufferType * @throws SQLException */ private void storeDateTime(Buffer intoBuf, java.util.Date dt, MysqlIO mysql, int bufferType) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.connection.versionMeetsMinimum(4, 1, 3)) { storeDateTime413AndNewer(intoBuf, dt, bufferType); } else { storeDateTime412AndOlder(intoBuf, dt, bufferType); } } } private void storeDateTime413AndNewer(Buffer intoBuf, java.util.Date dt, int bufferType) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { Calendar sessionCalendar = null; if (!this.useLegacyDatetimeCode) { if (bufferType == MysqlDefs.FIELD_TYPE_DATE) { sessionCalendar = getDefaultTzCalendar(); } else { sessionCalendar = getServerTzCalendar(); } } else { sessionCalendar = (dt instanceof Timestamp && this.connection.getUseJDBCCompliantTimezoneShift()) ? this.connection.getUtcCalendar() : getCalendarInstanceForSessionOrNew(); } java.util.Date oldTime = sessionCalendar.getTime(); try { sessionCalendar.setTime(dt); if (dt instanceof java.sql.Date) { sessionCalendar.set(Calendar.HOUR_OF_DAY, 0); sessionCalendar.set(Calendar.MINUTE, 0); sessionCalendar.set(Calendar.SECOND, 0); } byte length = (byte) 7; if (dt instanceof java.sql.Timestamp) { length = (byte) 11; } intoBuf.ensureCapacity(length); intoBuf.writeByte(length); // length int year = sessionCalendar.get(Calendar.YEAR); int month = sessionCalendar.get(Calendar.MONTH) + 1; int date = sessionCalendar.get(Calendar.DAY_OF_MONTH); intoBuf.writeInt(year); intoBuf.writeByte((byte) month); intoBuf.writeByte((byte) date); if (dt instanceof java.sql.Date) { intoBuf.writeByte((byte) 0); intoBuf.writeByte((byte) 0); intoBuf.writeByte((byte) 0); } else { intoBuf.writeByte((byte) sessionCalendar .get(Calendar.HOUR_OF_DAY)); intoBuf.writeByte((byte) sessionCalendar .get(Calendar.MINUTE)); intoBuf.writeByte((byte) sessionCalendar .get(Calendar.SECOND)); } if (length == 11) { // MySQL expects microseconds, not nanos intoBuf.writeLong(((java.sql.Timestamp) dt).getNanos() / 1000); } } finally { sessionCalendar.setTime(oldTime); } } } private Calendar getServerTzCalendar() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (serverTzCalendar == null) { serverTzCalendar = new GregorianCalendar(this.connection.getServerTimezoneTZ()); } return this.serverTzCalendar; } } private Calendar getDefaultTzCalendar() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (defaultTzCalendar == null) { defaultTzCalendar = new GregorianCalendar(TimeZone.getDefault()); } return this.defaultTzCalendar; } } // // TO DO: Investigate using NIO to do this faster // private void storeReader(MysqlIO mysql, int parameterIndex, Buffer packet, Reader inStream) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { String forcedEncoding = this.connection.getClobCharacterEncoding(); String clobEncoding = (forcedEncoding == null ? this.connection.getEncoding() : forcedEncoding); int maxBytesChar = 2; if (clobEncoding != null) { if (!clobEncoding.equals("UTF-16")) { maxBytesChar = this.connection.getMaxBytesPerChar(clobEncoding); if (maxBytesChar == 1) { maxBytesChar = 2; // for safety } } else { maxBytesChar = 4; } } char[] buf = new char[BLOB_STREAM_READ_BUF_SIZE / maxBytesChar]; int numRead = 0; int bytesInPacket = 0; int totalBytesRead = 0; int bytesReadAtLastSend = 0; int packetIsFullAt = this.connection.getBlobSendChunkSize(); try { packet.clear(); packet.writeByte((byte) MysqlDefs.COM_LONG_DATA); packet.writeLong(this.serverStatementId); packet.writeInt((parameterIndex)); boolean readAny = false; while ((numRead = inStream.read(buf)) != -1) { readAny = true; byte[] valueAsBytes = StringUtils.getBytes(buf, null, clobEncoding, this.connection .getServerCharacterEncoding(), 0, numRead, this.connection.parserKnowsUnicode(), getExceptionInterceptor()); packet.writeBytesNoNull(valueAsBytes, 0, valueAsBytes.length); bytesInPacket += valueAsBytes.length; totalBytesRead += valueAsBytes.length; if (bytesInPacket >= packetIsFullAt) { bytesReadAtLastSend = totalBytesRead; mysql.sendCommand(MysqlDefs.COM_LONG_DATA, null, packet, true, null, 0); bytesInPacket = 0; packet.clear(); packet.writeByte((byte) MysqlDefs.COM_LONG_DATA); packet.writeLong(this.serverStatementId); packet.writeInt((parameterIndex)); } } if (totalBytesRead != bytesReadAtLastSend) { mysql.sendCommand(MysqlDefs.COM_LONG_DATA, null, packet, true, null, 0); } if (!readAny) { mysql.sendCommand(MysqlDefs.COM_LONG_DATA, null, packet, true, null, 0); } } catch (IOException ioEx) { SQLException sqlEx = SQLError.createSQLException(Messages .getString("ServerPreparedStatement.24") //$NON-NLS-1$ + ioEx.toString(), SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); sqlEx.initCause(ioEx); throw sqlEx; } finally { if (this.connection.getAutoClosePStmtStreams()) { if (inStream != null) { try { inStream.close(); } catch (IOException ioEx) { ; // ignore } } } } } } private void storeStream(MysqlIO mysql, int parameterIndex, Buffer packet, InputStream inStream) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { byte[] buf = new byte[BLOB_STREAM_READ_BUF_SIZE]; int numRead = 0; try { int bytesInPacket = 0; int totalBytesRead = 0; int bytesReadAtLastSend = 0; int packetIsFullAt = this.connection.getBlobSendChunkSize(); packet.clear(); packet.writeByte((byte) MysqlDefs.COM_LONG_DATA); packet.writeLong(this.serverStatementId); packet.writeInt((parameterIndex)); boolean readAny = false; while ((numRead = inStream.read(buf)) != -1) { readAny = true; packet.writeBytesNoNull(buf, 0, numRead); bytesInPacket += numRead; totalBytesRead += numRead; if (bytesInPacket >= packetIsFullAt) { bytesReadAtLastSend = totalBytesRead; mysql.sendCommand(MysqlDefs.COM_LONG_DATA, null, packet, true, null, 0); bytesInPacket = 0; packet.clear(); packet.writeByte((byte) MysqlDefs.COM_LONG_DATA); packet.writeLong(this.serverStatementId); packet.writeInt((parameterIndex)); } } if (totalBytesRead != bytesReadAtLastSend) { mysql.sendCommand(MysqlDefs.COM_LONG_DATA, null, packet, true, null, 0); } if (!readAny) { mysql.sendCommand(MysqlDefs.COM_LONG_DATA, null, packet, true, null, 0); } } catch (IOException ioEx) { SQLException sqlEx = SQLError.createSQLException(Messages .getString("ServerPreparedStatement.25") //$NON-NLS-1$ + ioEx.toString(), SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); sqlEx.initCause(ioEx); throw sqlEx; } finally { if (this.connection.getAutoClosePStmtStreams()) { if (inStream != null) { try { inStream.close(); } catch (IOException ioEx) { ; // ignore } } } } } } /** * @see java.lang.Object#toString() */ public String toString() { StringBuffer toStringBuf = new StringBuffer(); toStringBuf.append("com.mysql.jdbc.ServerPreparedStatement["); //$NON-NLS-1$ toStringBuf.append(this.serverStatementId); toStringBuf.append("] - "); //$NON-NLS-1$ try { toStringBuf.append(asSql()); } catch (SQLException sqlEx) { toStringBuf.append(Messages.getString("ServerPreparedStatement.6")); //$NON-NLS-1$ toStringBuf.append(sqlEx); } return toStringBuf.toString(); } protected long getServerStatementId() { return serverStatementId; } private boolean hasCheckedRewrite = false; private boolean canRewrite = false; public boolean canRewriteAsMultiValueInsertAtSqlLevel() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (!hasCheckedRewrite) { this.hasCheckedRewrite = true; this.canRewrite = canRewrite(this.originalSql, isOnDuplicateKeyUpdate(), getLocationOfOnDuplicateKeyUpdate(), 0); // We need to client-side parse this to get the VALUES clause, etc. this.parseInfo = new ParseInfo(this.originalSql, this.connection, this.connection.getMetaData(), this.charEncoding, this.charConverter); } return this.canRewrite; } } public boolean canRewriteAsMultivalueInsertStatement() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (!canRewriteAsMultiValueInsertAtSqlLevel()) { return false; } BindValue[] currentBindValues = null; BindValue[] previousBindValues = null; int nbrCommands = this.batchedArgs.size(); // Can't have type changes between sets of bindings for this to work... for (int commandIndex = 0; commandIndex < nbrCommands; commandIndex++) { Object arg = this.batchedArgs.get(commandIndex); if (!(arg instanceof String)) { currentBindValues = ((BatchedBindValues) arg).batchedParameterValues; // We need to check types each time, as // the user might have bound different // types in each addBatch() if (previousBindValues != null) { for (int j = 0; j < this.parameterBindings.length; j++) { if (currentBindValues[j].bufferType != previousBindValues[j].bufferType) { return false; } } } } } return true; } } private int locationOfOnDuplicateKeyUpdate = -2; protected int getLocationOfOnDuplicateKeyUpdate() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.locationOfOnDuplicateKeyUpdate == -2) { this.locationOfOnDuplicateKeyUpdate = getOnDuplicateKeyLocation(this.originalSql); } return this.locationOfOnDuplicateKeyUpdate; } } protected boolean isOnDuplicateKeyUpdate() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { return getLocationOfOnDuplicateKeyUpdate() != -1; } } /** * Computes the maximum parameter set size, and entire batch size given * the number of arguments in the batch. * @throws SQLException */ protected long[] computeMaxParameterSetSizeAndBatchSize(int numBatchedArgs) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { long sizeOfEntireBatch = 1 + /* com_execute */ + 4 /* stmt id */ + 1 /* flags */ + 4 /* batch count padding */; long maxSizeOfParameterSet = 0; for (int i = 0; i < numBatchedArgs; i++) { BindValue[] paramArg = ((BatchedBindValues) this.batchedArgs.get(i)).batchedParameterValues; long sizeOfParameterSet = 0; sizeOfParameterSet += (this.parameterCount + 7) / 8; // for isNull sizeOfParameterSet += this.parameterCount * 2; // have to send types for (int j = 0; j < this.parameterBindings.length; j++) { if (!paramArg[j].isNull) { long size = paramArg[j].getBoundLength(); if (paramArg[j].isLongData) { if (size != -1) { sizeOfParameterSet += size; } } else { sizeOfParameterSet += size; } } } sizeOfEntireBatch += sizeOfParameterSet; if (sizeOfParameterSet > maxSizeOfParameterSet) { maxSizeOfParameterSet = sizeOfParameterSet; } } return new long[] {maxSizeOfParameterSet, sizeOfEntireBatch}; } } protected int setOneBatchedParameterSet( java.sql.PreparedStatement batchedStatement, int batchedParamIndex, Object paramSet) throws SQLException { BindValue[] paramArg = ((BatchedBindValues) paramSet).batchedParameterValues; for (int j = 0; j < paramArg.length; j++) { if (paramArg[j].isNull) { batchedStatement.setNull(batchedParamIndex++, Types.NULL); } else { if (paramArg[j].isLongData) { Object value = paramArg[j].value; if (value instanceof InputStream) { batchedStatement.setBinaryStream(batchedParamIndex++, (InputStream) value, (int) paramArg[j].bindLength); } else { batchedStatement.setCharacterStream( batchedParamIndex++, (Reader) value, (int) paramArg[j].bindLength); } } else { switch (paramArg[j].bufferType) { case MysqlDefs.FIELD_TYPE_TINY: batchedStatement.setByte(batchedParamIndex++, (byte)paramArg[j].longBinding); break; case MysqlDefs.FIELD_TYPE_SHORT: batchedStatement.setShort(batchedParamIndex++, (short)paramArg[j].longBinding); break; case MysqlDefs.FIELD_TYPE_LONG: batchedStatement.setInt(batchedParamIndex++, (int)paramArg[j].longBinding); break; case MysqlDefs.FIELD_TYPE_LONGLONG: batchedStatement.setLong(batchedParamIndex++, paramArg[j].longBinding); break; case MysqlDefs.FIELD_TYPE_FLOAT: batchedStatement.setFloat(batchedParamIndex++, paramArg[j].floatBinding); break; case MysqlDefs.FIELD_TYPE_DOUBLE: batchedStatement.setDouble(batchedParamIndex++, paramArg[j].doubleBinding); break; case MysqlDefs.FIELD_TYPE_TIME: batchedStatement.setTime(batchedParamIndex++, (Time) paramArg[j].value); break; case MysqlDefs.FIELD_TYPE_DATE: batchedStatement.setDate(batchedParamIndex++, (Date) paramArg[j].value); break; case MysqlDefs.FIELD_TYPE_DATETIME: case MysqlDefs.FIELD_TYPE_TIMESTAMP: batchedStatement.setTimestamp(batchedParamIndex++, (Timestamp) paramArg[j].value); break; case MysqlDefs.FIELD_TYPE_VAR_STRING: case MysqlDefs.FIELD_TYPE_STRING: case MysqlDefs.FIELD_TYPE_VARCHAR: case MysqlDefs.FIELD_TYPE_DECIMAL: case MysqlDefs.FIELD_TYPE_NEW_DECIMAL: Object value = paramArg[j].value; if (value instanceof byte[]) { batchedStatement.setBytes(batchedParamIndex, (byte[]) value); } else { batchedStatement.setString(batchedParamIndex, (String) value); } // If we ended up here as a multi-statement, we're not working with a server prepared statement if (batchedStatement instanceof ServerPreparedStatement) { BindValue asBound = ((ServerPreparedStatement) batchedStatement) .getBinding( batchedParamIndex, false); asBound.bufferType = paramArg[j].bufferType; } batchedParamIndex++; break; default: throw new IllegalArgumentException( "Unknown type when re-binding parameter into batched statement for parameter index " + batchedParamIndex); } } } } return batchedParamIndex; } protected boolean containsOnDuplicateKeyUpdateInSQL() { return this.hasOnDuplicateKeyUpdate; } protected PreparedStatement prepareBatchedInsertSQL(MySQLConnection localConn, int numBatches) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { try { PreparedStatement pstmt = new ServerPreparedStatement(localConn, this.parseInfo.getSqlForBatch(numBatches), this.currentCatalog, this.resultSetConcurrency, this.resultSetType); pstmt.setRetrieveGeneratedKeys(this.retrieveGeneratedKeys); return pstmt; } catch (UnsupportedEncodingException e) { SQLException sqlEx = SQLError.createSQLException("Unable to prepare batch statement", SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); sqlEx.initCause(e); throw sqlEx; } } } }