/* 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.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectOutputStream; import java.io.Reader; import java.io.StringReader; import java.io.UnsupportedEncodingException; import java.lang.reflect.Constructor; import java.math.BigDecimal; import java.math.BigInteger; import java.net.URL; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetEncoder; import java.sql.Array; import java.sql.Clob; import java.sql.DatabaseMetaData; import java.sql.Date; import java.sql.ParameterMetaData; import java.sql.Ref; import java.sql.SQLException; import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; import java.text.ParsePosition; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.TimeZone; import com.mysql.jdbc.exceptions.MySQLStatementCancelledException; import com.mysql.jdbc.exceptions.MySQLTimeoutException; import com.mysql.jdbc.profiler.ProfilerEvent; /** * A SQL Statement is pre-compiled and stored in a PreparedStatement object. * This object can then be used to efficiently execute this statement multiple * times. * * <p> * <B>Note:</B> The setXXX methods for setting IN parameter values must specify * types that are compatible with the defined SQL type of the input parameter. * For instance, if the IN parameter has SQL type Integer, then setInt should be * used. * </p> * * <p> * If arbitrary parameter type conversions are required, then the setObject * method should be used with a target SQL type. * </p> * * @author Mark Matthews * @version $Id: PreparedStatement.java,v 1.1.2.1 2005/05/13 18:58:38 mmatthews * Exp $ * * @see java.sql.ResultSet * @see java.sql.PreparedStatement */ public class PreparedStatement extends com.mysql.jdbc.StatementImpl implements java.sql.PreparedStatement { private static final Constructor<?> JDBC_4_PSTMT_2_ARG_CTOR; private static final Constructor<?> JDBC_4_PSTMT_3_ARG_CTOR; private static final Constructor<?> JDBC_4_PSTMT_4_ARG_CTOR; static { if (Util.isJdbc4()) { try { JDBC_4_PSTMT_2_ARG_CTOR = Class.forName( "com.mysql.jdbc.JDBC4PreparedStatement") .getConstructor( new Class[] { MySQLConnection.class, String.class }); JDBC_4_PSTMT_3_ARG_CTOR = Class.forName( "com.mysql.jdbc.JDBC4PreparedStatement") .getConstructor( new Class[] { MySQLConnection.class, String.class, String.class }); JDBC_4_PSTMT_4_ARG_CTOR = Class.forName( "com.mysql.jdbc.JDBC4PreparedStatement") .getConstructor( new Class[] { MySQLConnection.class, String.class, String.class, ParseInfo.class }); } catch (SecurityException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } else { JDBC_4_PSTMT_2_ARG_CTOR = null; JDBC_4_PSTMT_3_ARG_CTOR = null; JDBC_4_PSTMT_4_ARG_CTOR = null; } } public class BatchParams { public boolean[] isNull = null; public boolean[] isStream = null; public InputStream[] parameterStreams = null; public byte[][] parameterStrings = null; public int[] streamLengths = null; BatchParams(byte[][] strings, InputStream[] streams, boolean[] isStreamFlags, int[] lengths, boolean[] isNullFlags) { // // Make copies // this.parameterStrings = new byte[strings.length][]; this.parameterStreams = new InputStream[streams.length]; this.isStream = new boolean[isStreamFlags.length]; this.streamLengths = new int[lengths.length]; this.isNull = new boolean[isNullFlags.length]; System.arraycopy(strings, 0, this.parameterStrings, 0, strings.length); System.arraycopy(streams, 0, this.parameterStreams, 0, streams.length); System.arraycopy(isStreamFlags, 0, this.isStream, 0, isStreamFlags.length); System.arraycopy(lengths, 0, this.streamLengths, 0, lengths.length); System .arraycopy(isNullFlags, 0, this.isNull, 0, isNullFlags.length); } } class EndPoint { int begin; int end; EndPoint(int b, int e) { this.begin = b; this.end = e; } } class ParseInfo { char firstStmtChar = 0; boolean foundLimitClause = false; boolean foundLoadData = false; long lastUsed = 0; int statementLength = 0; int statementStartPos = 0; boolean canRewriteAsMultiValueInsert = false; byte[][] staticSql = null; boolean isOnDuplicateKeyUpdate = false; int locationOfOnDuplicateKeyUpdate = -1; String valuesClause; boolean parametersInDuplicateKeyClause = false; /** * Represents the "parsed" state of a client-side * prepared statement, with the statement broken up into * it's static and dynamic (where parameters are bound) * parts. */ ParseInfo(String sql, MySQLConnection conn, java.sql.DatabaseMetaData dbmd, String encoding, SingleByteCharsetConverter converter) throws SQLException { this(sql, conn, dbmd, encoding, converter, true); } public ParseInfo(String sql, MySQLConnection conn, java.sql.DatabaseMetaData dbmd, String encoding, SingleByteCharsetConverter converter, boolean buildRewriteInfo) throws SQLException { try { if (sql == null) { throw SQLError.createSQLException(Messages .getString("PreparedStatement.61"), //$NON-NLS-1$ SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } this.locationOfOnDuplicateKeyUpdate = getOnDuplicateKeyLocation(sql); this.isOnDuplicateKeyUpdate = this.locationOfOnDuplicateKeyUpdate != -1; this.lastUsed = System.currentTimeMillis(); String quotedIdentifierString = dbmd.getIdentifierQuoteString(); char quotedIdentifierChar = 0; if ((quotedIdentifierString != null) && !quotedIdentifierString.equals(" ") //$NON-NLS-1$ && (quotedIdentifierString.length() > 0)) { quotedIdentifierChar = quotedIdentifierString.charAt(0); } this.statementLength = sql.length(); ArrayList<int[]> endpointList = new ArrayList<int[]>(); boolean inQuotes = false; char quoteChar = 0; boolean inQuotedId = false; int lastParmEnd = 0; int i; int stopLookingForLimitClause = this.statementLength - 5; this.foundLimitClause = false; boolean noBackslashEscapes = connection.isNoBackslashEscapesSet(); // we're not trying to be real pedantic here, but we'd like to // skip comments at the beginning of statements, as frameworks // such as Hibernate use them to aid in debugging statementStartPos = findStartOfStatement(sql); for (i = statementStartPos; i < this.statementLength; ++i) { char c = sql.charAt(i); if ((this.firstStmtChar == 0) && Character.isLetter(c)) { // Determine what kind of statement we're doing (_S_elect, // _I_nsert, etc.) this.firstStmtChar = Character.toUpperCase(c); } if (!noBackslashEscapes && c == '\\' && i < (this.statementLength - 1)) { i++; continue; // next character is escaped } // are we in a quoted identifier? // (only valid when the id is not inside a 'string') if (!inQuotes && (quotedIdentifierChar != 0) && (c == quotedIdentifierChar)) { inQuotedId = !inQuotedId; } else if (!inQuotedId) { // only respect quotes when not in a quoted identifier if (inQuotes) { if (((c == '\'') || (c == '"')) && c == quoteChar) { if (i < (this.statementLength - 1) && sql.charAt(i + 1) == quoteChar) { i++; continue; // inline quote escape } inQuotes = !inQuotes; quoteChar = 0; } else if (((c == '\'') || (c == '"')) && c == quoteChar) { inQuotes = !inQuotes; quoteChar = 0; } } else { if (c == '#' || (c == '-' && (i + 1) < this.statementLength && sql .charAt(i + 1) == '-')) { // run out to end of statement, or newline, // whichever comes first int endOfStmt = this.statementLength - 1; for (; i < endOfStmt; i++) { c = sql.charAt(i); if (c == '\r' || c == '\n') { break; } } continue; } else if (c == '/' && (i + 1) < this.statementLength) { // Comment? char cNext = sql.charAt(i + 1); if (cNext == '*') { i+= 2; for (int j = i; j < this.statementLength; j++) { i++; cNext = sql.charAt(j); if (cNext == '*' && (j + 1) < this.statementLength) { if (sql.charAt(j + 1) == '/') { i++; if (i < this.statementLength) { c = sql.charAt(i); } break; // comment done } } } } } else if ((c == '\'') || (c == '"')) { inQuotes = true; quoteChar = c; } } } if ((c == '?') && !inQuotes && !inQuotedId) { endpointList.add(new int[] { lastParmEnd, i }); lastParmEnd = i + 1; if (isOnDuplicateKeyUpdate && i > locationOfOnDuplicateKeyUpdate) { parametersInDuplicateKeyClause = true; } } if (!inQuotes && !inQuotedId && (i < stopLookingForLimitClause)) { if ((c == 'L') || (c == 'l')) { char posI1 = sql.charAt(i + 1); if ((posI1 == 'I') || (posI1 == 'i')) { char posM = sql.charAt(i + 2); if ((posM == 'M') || (posM == 'm')) { char posI2 = sql.charAt(i + 3); if ((posI2 == 'I') || (posI2 == 'i')) { char posT = sql.charAt(i + 4); if ((posT == 'T') || (posT == 't')) { boolean hasPreviosIdChar = false; boolean hasFollowingIdChar = false; if (i>statementStartPos && StringUtils.isValidIdChar(sql.charAt(i - 1))) { hasPreviosIdChar = true; } if (i + 5 < this.statementLength && StringUtils.isValidIdChar(sql.charAt(i + 5))) { hasFollowingIdChar = true; } if (!hasPreviosIdChar && !hasFollowingIdChar) { foundLimitClause = true; } } } } } } } } if (this.firstStmtChar == 'L') { if (StringUtils.startsWithIgnoreCaseAndWs(sql, "LOAD DATA")) { //$NON-NLS-1$ this.foundLoadData = true; } else { this.foundLoadData = false; } } else { this.foundLoadData = false; } endpointList.add(new int[] { lastParmEnd, this.statementLength }); this.staticSql = new byte[endpointList.size()][]; char[] asCharArray = sql.toCharArray(); for (i = 0; i < this.staticSql.length; i++) { int[] ep = endpointList.get(i); int end = ep[1]; int begin = ep[0]; int len = end - begin; if (this.foundLoadData) { String temp = new String(asCharArray, begin, len); this.staticSql[i] = StringUtils.getBytes(temp); } else if (encoding == null) { byte[] buf = new byte[len]; for (int j = 0; j < len; j++) { buf[j] = (byte) sql.charAt(begin + j); } this.staticSql[i] = buf; } else { if (converter != null) { this.staticSql[i] = StringUtils.getBytes(sql, converter, encoding, connection .getServerCharacterEncoding(), begin, len, connection.parserKnowsUnicode(), getExceptionInterceptor()); } else { String temp = new String(asCharArray, begin, len); this.staticSql[i] = StringUtils.getBytes(temp, encoding, connection .getServerCharacterEncoding(), connection.parserKnowsUnicode(), conn, getExceptionInterceptor()); } } } } catch (StringIndexOutOfBoundsException oobEx) { SQLException sqlEx = new SQLException("Parse error for " + sql); sqlEx.initCause(oobEx); throw sqlEx; } if (buildRewriteInfo) { this.canRewriteAsMultiValueInsert = PreparedStatement .canRewrite(sql, this.isOnDuplicateKeyUpdate, this.locationOfOnDuplicateKeyUpdate, this.statementStartPos) && !this.parametersInDuplicateKeyClause; if (this.canRewriteAsMultiValueInsert && conn.getRewriteBatchedStatements()) { buildRewriteBatchedParams(sql, conn, dbmd, encoding, converter); } } } private ParseInfo batchHead; private ParseInfo batchValues; private ParseInfo batchODKUClause; private void buildRewriteBatchedParams(String sql, MySQLConnection conn, DatabaseMetaData metadata, String encoding, SingleByteCharsetConverter converter) throws SQLException { this.valuesClause = extractValuesClause(sql); String odkuClause = isOnDuplicateKeyUpdate ? sql .substring(locationOfOnDuplicateKeyUpdate) : null; String headSql = null; if (isOnDuplicateKeyUpdate) { headSql = sql.substring(0, locationOfOnDuplicateKeyUpdate); } else { headSql = sql; } this.batchHead = new ParseInfo(headSql, conn, metadata, encoding, converter, false); this.batchValues = new ParseInfo("," + this.valuesClause, conn, metadata, encoding, converter, false); this.batchODKUClause = null; if (odkuClause != null && odkuClause.length() > 0) { this.batchODKUClause = new ParseInfo("," + this.valuesClause + " " + odkuClause, conn, metadata, encoding, converter, false); } } private String extractValuesClause(String sql) throws SQLException { String quoteCharStr = connection.getMetaData() .getIdentifierQuoteString(); int indexOfValues = -1; int valuesSearchStart = statementStartPos; while (indexOfValues == -1) { if (quoteCharStr.length() > 0) { indexOfValues = StringUtils.indexOfIgnoreCaseRespectQuotes( valuesSearchStart, originalSql, "VALUES", quoteCharStr.charAt(0), false); } else { indexOfValues = StringUtils.indexOfIgnoreCase(valuesSearchStart, originalSql, "VALUES"); } if (indexOfValues > 0) { /* check if the char immediately preceding VALUES may be part of the table name */ char c = originalSql.charAt(indexOfValues - 1); if(!(Character.isWhitespace(c) || c == ')' || c == '`')){ valuesSearchStart = indexOfValues + 6; indexOfValues = -1; } else { /* check if the char immediately following VALUES may be whitespace or open parenthesis */ c = originalSql.charAt(indexOfValues + 6); if(!(Character.isWhitespace(c) || c == '(')){ valuesSearchStart = indexOfValues + 6; indexOfValues = -1; } } } else { break; } } if (indexOfValues == -1) { return null; } int indexOfFirstParen = sql.indexOf('(', indexOfValues + 6); if (indexOfFirstParen == -1) { return null; } int endOfValuesClause = sql.lastIndexOf(')'); if (endOfValuesClause == -1) { return null; } if (isOnDuplicateKeyUpdate) { endOfValuesClause = this.locationOfOnDuplicateKeyUpdate - 1; } return sql.substring(indexOfFirstParen, endOfValuesClause + 1); } /** * Returns a ParseInfo for a multi-value INSERT for a batch of size numBatch (without parsing!). */ synchronized ParseInfo getParseInfoForBatch(int numBatch) { AppendingBatchVisitor apv = new AppendingBatchVisitor(); buildInfoForBatch(numBatch, apv); ParseInfo batchParseInfo = new ParseInfo(apv.getStaticSqlStrings(), this.firstStmtChar, this.foundLimitClause, this.foundLoadData, this.isOnDuplicateKeyUpdate, this.locationOfOnDuplicateKeyUpdate, this.statementLength, this.statementStartPos); return batchParseInfo; } /** * Returns a preparable SQL string for the number of batched parameters, used by server-side prepared statements * when re-writing batch INSERTs. */ String getSqlForBatch(int numBatch) throws UnsupportedEncodingException { ParseInfo batchInfo = getParseInfoForBatch(numBatch); return getSqlForBatch(batchInfo); } /** * Used for filling in the SQL for getPreparedSql() - for debugging */ String getSqlForBatch(ParseInfo batchInfo) throws UnsupportedEncodingException { int size = 0; final byte[][] sqlStrings = batchInfo.staticSql; final int sqlStringsLength = sqlStrings.length; for (int i = 0; i < sqlStringsLength; i++) { size += sqlStrings[i].length; size++; // for the '?' } StringBuffer buf = new StringBuffer(size); for (int i = 0; i < sqlStringsLength - 1; i++) { buf.append(StringUtils.toString(sqlStrings[i], charEncoding)); buf.append("?"); } buf.append(StringUtils.toString(sqlStrings[sqlStringsLength - 1])); return buf.toString(); } /** * Builds a ParseInfo for the given batch size, without parsing. We use * a visitor pattern here, because the if {}s make computing a size for the * resultant byte[][] make this too complex, and we don't necessarily want to * use a List for this, because the size can be dynamic, and thus we'll not be * able to guess a good initial size for an array-based list, and it's not * efficient to convert a LinkedList to an array. */ private void buildInfoForBatch(int numBatch, BatchVisitor visitor) { final byte[][] headStaticSql = this.batchHead.staticSql; final int headStaticSqlLength = headStaticSql.length; if (headStaticSqlLength > 1) { for (int i = 0; i < headStaticSqlLength - 1; i++) { visitor.append(headStaticSql[i]).increment(); } } // merge end of head, with beginning of a value clause byte[] endOfHead = headStaticSql[headStaticSqlLength - 1]; final byte[][] valuesStaticSql = this.batchValues.staticSql; byte[] beginOfValues = valuesStaticSql[0]; visitor.merge(endOfHead, beginOfValues).increment(); int numValueRepeats = numBatch - 1; // first one is in the "head" if (this.batchODKUClause != null) { numValueRepeats--; // Last one is in the ODKU clause } final int valuesStaticSqlLength = valuesStaticSql.length; byte[] endOfValues = valuesStaticSql[valuesStaticSqlLength - 1]; for (int i = 0; i < numValueRepeats; i++) { for (int j = 1; j < valuesStaticSqlLength - 1; j++) { visitor.append(valuesStaticSql[j]).increment(); } visitor.merge(endOfValues, beginOfValues).increment(); } if (this.batchODKUClause != null) { final byte[][] batchOdkuStaticSql = this.batchODKUClause.staticSql; byte[] beginOfOdku = batchOdkuStaticSql[0]; visitor.decrement().merge(endOfValues, beginOfOdku).increment(); final int batchOdkuStaticSqlLength = batchOdkuStaticSql.length; if (numBatch > 1) { for (int i = 1; i < batchOdkuStaticSqlLength; i++) { visitor.append(batchOdkuStaticSql[i]) .increment(); } } else { visitor.decrement().append(batchOdkuStaticSql[(batchOdkuStaticSqlLength - 1)]); } } else { // Everything after the values clause, but not ODKU, which today is nothing // but a syntax error, but we should still not mangle the SQL! visitor.decrement().append(this.staticSql[this.staticSql.length - 1]); } } private ParseInfo(byte[][] staticSql, char firstStmtChar, boolean foundLimitClause, boolean foundLoadData, boolean isOnDuplicateKeyUpdate, int locationOfOnDuplicateKeyUpdate, int statementLength, int statementStartPos) { this.firstStmtChar = firstStmtChar; this.foundLimitClause = foundLimitClause; this.foundLoadData = foundLoadData; this.isOnDuplicateKeyUpdate = isOnDuplicateKeyUpdate; this.locationOfOnDuplicateKeyUpdate = locationOfOnDuplicateKeyUpdate; this.statementLength = statementLength; this.statementStartPos = statementStartPos; this.staticSql = staticSql; } } interface BatchVisitor { abstract BatchVisitor increment(); abstract BatchVisitor decrement(); abstract BatchVisitor append(byte[] values); abstract BatchVisitor merge(byte[] begin, byte[] end); } class AppendingBatchVisitor implements BatchVisitor { LinkedList<byte[]> statementComponents = new LinkedList<byte[]>(); public BatchVisitor append(byte[] values) { statementComponents.addLast(values); return this; } public BatchVisitor increment() { // no-op return this; } public BatchVisitor decrement() { statementComponents.removeLast(); return this; } public BatchVisitor merge(byte[] front, byte[] back) { int mergedLength = front.length + back.length; byte[] merged = new byte[mergedLength]; System.arraycopy(front, 0, merged, 0, front.length); System.arraycopy(back, 0, merged, front.length, back.length); statementComponents.addLast(merged); return this; } public byte[][] getStaticSqlStrings() { byte[][] asBytes = new byte[this.statementComponents.size()][]; this.statementComponents.toArray(asBytes); return asBytes; } public String toString() { StringBuffer buf = new StringBuffer(); Iterator<byte[]> iter = this.statementComponents.iterator(); while (iter.hasNext()) { buf.append(StringUtils.toString(iter.next())); } return buf.toString(); } } private final static byte[] HEX_DIGITS = new byte[] { (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F' }; /** * Reads length bytes from reader into buf. Blocks until enough input is * available * * @param reader * DOCUMENT ME! * @param buf * DOCUMENT ME! * @param length * DOCUMENT ME! * * @return DOCUMENT ME! * * @throws IOException * DOCUMENT ME! */ protected static int readFully(Reader reader, char[] buf, int length) throws IOException { int numCharsRead = 0; while (numCharsRead < length) { int count = reader.read(buf, numCharsRead, length - numCharsRead); if (count < 0) { break; } numCharsRead += count; } return numCharsRead; } /** * Does the batch (if any) contain "plain" statements added by * Statement.addBatch(String)? * * If so, we can't re-write it to use multi-value or multi-queries. */ protected boolean batchHasPlainStatements = false; private java.sql.DatabaseMetaData dbmd = null; /** * What is the first character of the prepared statement (used to check for * SELECT vs. INSERT/UPDATE/DELETE) */ protected char firstCharOfStmt = 0; /** Does the SQL for this statement contain a 'limit' clause? */ protected boolean hasLimitClause = false; /** Is this query a LOAD DATA query? */ protected boolean isLoadDataQuery = false; protected boolean[] isNull = null; private boolean[] isStream = null; protected int numberOfExecutions = 0; /** The SQL that was passed in to 'prepare' */ protected String originalSql = null; /** The number of parameters in this PreparedStatement */ protected int parameterCount; protected MysqlParameterMetadata parameterMetaData; private InputStream[] parameterStreams = null; private byte[][] parameterValues = null; /** * Only used by statement interceptors at the moment to * provide introspection of bound values */ protected int[] parameterTypes = null; protected ParseInfo parseInfo; private java.sql.ResultSetMetaData pstmtResultMetaData; private byte[][] staticSqlStrings = null; private byte[] streamConvertBuf = null; private int[] streamLengths = null; private SimpleDateFormat tsdf = null; /** * Are we using a version of MySQL where we can use 'true' boolean values? */ protected boolean useTrueBoolean = false; protected boolean usingAnsiMode; protected String batchedValuesClause; private boolean doPingInstead; private SimpleDateFormat ddf; private SimpleDateFormat tdf; private boolean compensateForOnDuplicateKeyUpdate = false; /** Charset encoder used to escape if needed, such as Yen sign in SJIS */ private CharsetEncoder charsetEncoder; /** Command index of currently executing batch command. */ protected int batchCommandIndex = -1; protected boolean serverSupportsFracSecs; /** * 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 PreparedStatement getInstance(MySQLConnection conn, String catalog) throws SQLException { if (!Util.isJdbc4()) { return new PreparedStatement(conn, catalog); } return (PreparedStatement) Util.handleNewInstance( JDBC_4_PSTMT_2_ARG_CTOR, new Object[] { conn, catalog }, conn.getExceptionInterceptor()); } /** * 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 PreparedStatement getInstance(MySQLConnection conn, String sql, String catalog) throws SQLException { if (!Util.isJdbc4()) { return new PreparedStatement(conn, sql, catalog); } return (PreparedStatement) Util.handleNewInstance( JDBC_4_PSTMT_3_ARG_CTOR, new Object[] { conn, sql, catalog }, conn.getExceptionInterceptor()); } /** * 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 PreparedStatement getInstance(MySQLConnection conn, String sql, String catalog, ParseInfo cachedParseInfo) throws SQLException { if (!Util.isJdbc4()) { return new PreparedStatement(conn, sql, catalog, cachedParseInfo); } return (PreparedStatement) Util.handleNewInstance( JDBC_4_PSTMT_4_ARG_CTOR, new Object[] { conn, sql, catalog, cachedParseInfo }, conn.getExceptionInterceptor()); } /** * Constructor used by server-side prepared statements * * @param conn * the connection that created us * @param catalog * the catalog in use when we were created * * @throws SQLException * if an error occurs */ public PreparedStatement(MySQLConnection conn, String catalog) throws SQLException { super(conn, catalog); detectFractionalSecondsSupport(); this.compensateForOnDuplicateKeyUpdate = this.connection.getCompensateOnDuplicateKeyUpdateCounts(); } protected void detectFractionalSecondsSupport() throws SQLException { this.serverSupportsFracSecs = this.connection != null && this.connection.versionMeetsMinimum(5, 6, 4); } /** * Constructor for the PreparedStatement class. * * @param conn * the connection creating this statement * @param sql * the SQL for this statement * @param catalog * the catalog/database this statement should be issued against * * @throws SQLException * if a database error occurs. */ public PreparedStatement(MySQLConnection conn, String sql, String catalog) throws SQLException { super(conn, catalog); if (sql == null) { throw SQLError.createSQLException(Messages.getString("PreparedStatement.0"), //$NON-NLS-1$ SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } detectFractionalSecondsSupport(); this.originalSql = sql; if (this.originalSql.startsWith(PING_MARKER)) { this.doPingInstead = true; } else { this.doPingInstead = false; } this.dbmd = this.connection.getMetaData(); this.useTrueBoolean = this.connection.versionMeetsMinimum(3, 21, 23); this.parseInfo = new ParseInfo(sql, this.connection, this.dbmd, this.charEncoding, this.charConverter); initializeFromParseInfo(); this.compensateForOnDuplicateKeyUpdate = this.connection.getCompensateOnDuplicateKeyUpdateCounts(); if (conn.getRequiresEscapingEncoder()) charsetEncoder = Charset.forName(conn.getEncoding()).newEncoder(); } /** * Creates a new PreparedStatement object. * * @param conn * the connection creating this statement * @param sql * the SQL for this statement * @param catalog * the catalog/database this statement should be issued against * @param cachedParseInfo * already created parseInfo. * * @throws SQLException * DOCUMENT ME! */ public PreparedStatement(MySQLConnection conn, String sql, String catalog, ParseInfo cachedParseInfo) throws SQLException { super(conn, catalog); if (sql == null) { throw SQLError.createSQLException(Messages.getString("PreparedStatement.1"), //$NON-NLS-1$ SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } detectFractionalSecondsSupport(); this.originalSql = sql; this.dbmd = this.connection.getMetaData(); this.useTrueBoolean = this.connection.versionMeetsMinimum(3, 21, 23); this.parseInfo = cachedParseInfo; this.usingAnsiMode = !this.connection.useAnsiQuotedIdentifiers(); initializeFromParseInfo(); this.compensateForOnDuplicateKeyUpdate = this.connection.getCompensateOnDuplicateKeyUpdateCounts(); if (conn.getRequiresEscapingEncoder()) charsetEncoder = Charset.forName(conn.getEncoding()).newEncoder(); } /** * 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>(); } for (int i = 0; i < this.parameterValues.length; i++) { checkAllParametersSet(this.parameterValues[i], this.parameterStreams[i], i); } this.batchedArgs.add(new BatchParams(this.parameterValues, this.parameterStreams, this.isStream, this.streamLengths, this.isNull)); } } public void addBatch(String sql) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { this.batchHasPlainStatements = true; super.addBatch(sql); } } protected String asSql() throws SQLException { return asSql(false); } protected String asSql(boolean quoteStreamsAndUnknowns) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { StringBuffer buf = new StringBuffer(); try { int realParameterCount = this.parameterCount + getParameterIndexOffset(); Object batchArg = null; if (batchCommandIndex != -1) batchArg = batchedArgs.get(batchCommandIndex); for (int i = 0; i < realParameterCount; ++i) { if (this.charEncoding != null) { buf.append(StringUtils.toString(this.staticSqlStrings[i], this.charEncoding)); } else { buf.append(StringUtils.toString(this.staticSqlStrings[i])); } byte val[] = null; if (batchArg != null && batchArg instanceof String) { buf.append((String)batchArg); continue; } if (batchCommandIndex == -1) val = parameterValues[i]; else val = ((BatchParams)batchArg).parameterStrings[i]; boolean isStreamParam = false; if (batchCommandIndex == -1) isStreamParam = isStream[i]; else isStreamParam = ((BatchParams)batchArg).isStream[i]; if ((val == null) && !isStreamParam) { if (quoteStreamsAndUnknowns) { buf.append("'"); } buf.append("** NOT SPECIFIED **"); //$NON-NLS-1$ if (quoteStreamsAndUnknowns) { buf.append("'"); } } else if (isStreamParam) { if (quoteStreamsAndUnknowns) { buf.append("'"); } buf.append("** STREAM DATA **"); //$NON-NLS-1$ if (quoteStreamsAndUnknowns) { buf.append("'"); } } else { if (this.charConverter != null) { buf.append(this.charConverter.toString(val)); } else { if (this.charEncoding != null) { buf.append(new String(val, this.charEncoding)); } else { buf.append(StringUtils.toAsciiString(val)); } } } } if (this.charEncoding != null) { buf.append(StringUtils.toString( this.staticSqlStrings[this.parameterCount + getParameterIndexOffset()], this.charEncoding)); } else { buf .append(StringUtils .toAsciiString(this.staticSqlStrings[this.parameterCount + getParameterIndexOffset()])); } } catch (UnsupportedEncodingException uue) { throw new RuntimeException(Messages .getString("PreparedStatement.32") //$NON-NLS-1$ + this.charEncoding + Messages.getString("PreparedStatement.33")); //$NON-NLS-1$ } return buf.toString(); } } public void clearBatch() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { this.batchHasPlainStatements = false; super.clearBatch(); } } /** * In general, parameter values remain in force for repeated used of a * Statement. Setting a parameter value automatically clears its previous * value. However, in some cases, it is useful to immediately release the * resources used by the current parameter values; this can be done by * calling clearParameters * * @exception SQLException * if a database access error occurs */ public void clearParameters() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { for (int i = 0; i < this.parameterValues.length; i++) { this.parameterValues[i] = null; this.parameterStreams[i] = null; this.isStream[i] = false; this.isNull[i] = false; this.parameterTypes[i] = Types.NULL; } } } private final void escapeblockFast(byte[] buf, Buffer packet, int size) throws SQLException { int lastwritten = 0; for (int i = 0; i < size; i++) { byte b = buf[i]; if (b == '\0') { // write stuff not yet written if (i > lastwritten) { packet.writeBytesNoNull(buf, lastwritten, i - lastwritten); } // write escape packet.writeByte((byte) '\\'); packet.writeByte((byte) '0'); lastwritten = i + 1; } else { if ((b == '\\') || (b == '\'') || (!this.usingAnsiMode && b == '"')) { // write stuff not yet written if (i > lastwritten) { packet.writeBytesNoNull(buf, lastwritten, i - lastwritten); } // write escape packet.writeByte((byte) '\\'); lastwritten = i; // not i+1 as b wasn't written. } } } // write out remaining stuff from buffer if (lastwritten < size) { packet.writeBytesNoNull(buf, lastwritten, size - lastwritten); } } private final void escapeblockFast(byte[] buf, ByteArrayOutputStream bytesOut, int size) { int lastwritten = 0; for (int i = 0; i < size; i++) { byte b = buf[i]; if (b == '\0') { // write stuff not yet written if (i > lastwritten) { bytesOut.write(buf, lastwritten, i - lastwritten); } // write escape bytesOut.write('\\'); bytesOut.write('0'); lastwritten = i + 1; } else { if ((b == '\\') || (b == '\'') || (!this.usingAnsiMode && b == '"')) { // write stuff not yet written if (i > lastwritten) { bytesOut.write(buf, lastwritten, i - lastwritten); } // write escape bytesOut.write('\\'); lastwritten = i; // not i+1 as b wasn't written. } } } // write out remaining stuff from buffer if (lastwritten < size) { bytesOut.write(buf, lastwritten, size - lastwritten); } } /** * Check to see if the statement is safe for read-only slaves after failover. * * @return true if safe for read-only. * @throws SQLException */ protected boolean checkReadOnlySafeStatement() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { return ((!this.connection.isReadOnly()) || (this.firstCharOfStmt == 'S')); } } /** * Some prepared statements return multiple results; the execute method * handles these complex statements as well as the simpler form of * statements handled by executeQuery and executeUpdate * * @return true if the next result is a ResultSet; false if it is an update * count or there are no more results * * @exception SQLException * if a database access error occurs */ public boolean execute() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { MySQLConnection locallyScopedConn = this.connection; if(!checkReadOnlySafeStatement()) { throw SQLError.createSQLException(Messages.getString("PreparedStatement.20") //$NON-NLS-1$ + Messages.getString("PreparedStatement.21"), //$NON-NLS-1$ SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } ResultSetInternalMethods rs = null; CachedResultSetMetaData cachedMetadata = null; lastQueryIsOnDupKeyUpdate = false; if (retrieveGeneratedKeys) { lastQueryIsOnDupKeyUpdate = containsOnDuplicateKeyUpdateInSQL(); } boolean doStreaming = createStreamingResultSet(); clearWarnings(); // Adjust net_write_timeout to a higher value if we're // streaming result sets. More often than not, someone runs into // an issue where they blow net_write_timeout when using this // feature, and if they're willing to hold a result set open // for 30 seconds or more, one more round-trip isn't going to hurt // // This is reset by RowDataDynamic.close(). if (doStreaming && this.connection.getNetTimeoutForStreamingResults() > 0) { executeSimpleNonQuery(locallyScopedConn, "SET net_write_timeout=" + this.connection .getNetTimeoutForStreamingResults()); } this.batchedGeneratedKeys = null; Buffer sendPacket = fillSendPacket(); String oldCatalog = null; if (!locallyScopedConn.getCatalog().equals(this.currentCatalog)) { oldCatalog = locallyScopedConn.getCatalog(); locallyScopedConn.setCatalog(this.currentCatalog); } // // Check if we have cached metadata for this query... // if (locallyScopedConn.getCacheResultSetMetadata()) { cachedMetadata = locallyScopedConn.getCachedMetaData(this.originalSql); } Field[] metadataFromCache = null; if (cachedMetadata != null) { metadataFromCache = cachedMetadata.fields; } boolean oldInfoMsgState = false; if (this.retrieveGeneratedKeys) { oldInfoMsgState = locallyScopedConn.isReadInfoMsgEnabled(); locallyScopedConn.setReadInfoMsgEnabled(true); } // If there isn't a limit clause in the SQL // then limit the number of rows to return in // an efficient manner. Only do this if // setMaxRows() hasn't been used on any Statements // generated from the current Connection (saves // a query, and network traffic). // // Only apply max_rows to selects // if (locallyScopedConn.useMaxRows()) { int rowLimit = -1; if (this.firstCharOfStmt == 'S') { if (this.hasLimitClause) { rowLimit = this.maxRows; } else { if (this.maxRows <= 0) { executeSimpleNonQuery(locallyScopedConn, "SET SQL_SELECT_LIMIT=DEFAULT"); } else { executeSimpleNonQuery(locallyScopedConn, "SET SQL_SELECT_LIMIT=" + this.maxRows); } } } else { executeSimpleNonQuery(locallyScopedConn, "SET SQL_SELECT_LIMIT=DEFAULT"); } // Finally, execute the query rs = executeInternal(rowLimit, sendPacket, doStreaming, (this.firstCharOfStmt == 'S'), metadataFromCache, false); } else { rs = executeInternal(-1, sendPacket, doStreaming, (this.firstCharOfStmt == 'S'), metadataFromCache, false); } if (cachedMetadata != null) { locallyScopedConn.initializeResultsMetadataFromCache(this.originalSql, cachedMetadata, this.results); } else { if (rs.reallyResult() && locallyScopedConn.getCacheResultSetMetadata()) { locallyScopedConn.initializeResultsMetadataFromCache(this.originalSql, null /* will be created */, rs); } } if (this.retrieveGeneratedKeys) { locallyScopedConn.setReadInfoMsgEnabled(oldInfoMsgState); rs.setFirstCharOfQuery(this.firstCharOfStmt); } if (oldCatalog != null) { locallyScopedConn.setCatalog(oldCatalog); } if (rs != null) { this.lastInsertId = rs.getUpdateID(); this.results = rs; } return ((rs != null) && rs.reallyResult()); } } /** * JDBC 2.0 Submit a batch of commands to the database for execution. This * method is optional. * * @return an array of update counts containing one element for each command * in the batch. The array is ordered according to the order in * which commands were inserted into the batch * * @exception SQLException * if a database-access error occurs, or the driver does not * support batch statements * @throws java.sql.BatchUpdateException * DOCUMENT ME! */ public int[] executeBatch() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.connection.isReadOnly()) { throw new SQLException(Messages.getString("PreparedStatement.25") //$NON-NLS-1$ + Messages.getString("PreparedStatement.26"), //$NON-NLS-1$ SQLError.SQL_STATE_ILLEGAL_ARGUMENT); } if (this.batchedArgs == null || this.batchedArgs.size() == 0) { return new int[0]; } // we timeout the entire batch, not individual statements int batchTimeout = this.timeoutInMillis; this.timeoutInMillis = 0; resetCancelledState(); try { statementBegins(); clearWarnings(); if (!this.batchHasPlainStatements && this.connection.getRewriteBatchedStatements()) { if (canRewriteAsMultiValueInsertAtSqlLevel()) { return executeBatchedInserts(batchTimeout); } if (this.connection.versionMeetsMinimum(4, 1, 0) && !this.batchHasPlainStatements && this.batchedArgs != null && this.batchedArgs.size() > 3 /* cost of option setting rt-wise */) { return executePreparedBatchAsMultiStatement(batchTimeout); } } return executeBatchSerially(batchTimeout); } finally { this.statementExecuting.set(false); clearBatch(); } } } public boolean canRewriteAsMultiValueInsertAtSqlLevel() throws SQLException { return this.parseInfo.canRewriteAsMultiValueInsert; } protected int getLocationOfOnDuplicateKeyUpdate() throws SQLException { return this.parseInfo.locationOfOnDuplicateKeyUpdate; } /** * Rewrites the already prepared statement into a multi-statement * query of 'statementsPerBatch' values and executes the entire batch * using this new statement. * * @return update counts in the same fashion as executeBatch() * * @throws SQLException */ protected int[] executePreparedBatchAsMultiStatement(int batchTimeout) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { // This is kind of an abuse, but it gets the job done if (this.batchedValuesClause == null) { this.batchedValuesClause = this.originalSql + ";"; } MySQLConnection locallyScopedConn = this.connection; boolean multiQueriesEnabled = locallyScopedConn.getAllowMultiQueries(); CancelTask timeoutTask = null; try { clearWarnings(); int numBatchedArgs = this.batchedArgs.size(); if (this.retrieveGeneratedKeys) { this.batchedGeneratedKeys = new ArrayList<ResultSetRow>(numBatchedArgs); } int numValuesPerBatch = computeBatchSize(numBatchedArgs); if (numBatchedArgs < numValuesPerBatch) { numValuesPerBatch = numBatchedArgs; } java.sql.PreparedStatement batchedStatement = null; int batchedParamIndex = 1; int numberToExecuteAsMultiValue = 0; int batchCounter = 0; int updateCountCounter = 0; int[] updateCounts = new int[numBatchedArgs]; SQLException sqlEx = null; try { if (!multiQueriesEnabled) { locallyScopedConn.getIO().enableMultiQueries(); } if (this.retrieveGeneratedKeys) { batchedStatement = locallyScopedConn.prepareStatement( generateMultiStatementForBatch(numValuesPerBatch), RETURN_GENERATED_KEYS); } else { batchedStatement = locallyScopedConn .prepareStatement(generateMultiStatementForBatch(numValuesPerBatch)); } if (locallyScopedConn.getEnableQueryTimeouts() && batchTimeout != 0 && locallyScopedConn.versionMeetsMinimum(5, 0, 0)) { timeoutTask = new CancelTask((StatementImpl)batchedStatement); locallyScopedConn.getCancelTimer().schedule(timeoutTask, batchTimeout); } if (numBatchedArgs < numValuesPerBatch) { numberToExecuteAsMultiValue = numBatchedArgs; } else { numberToExecuteAsMultiValue = numBatchedArgs / numValuesPerBatch; } int numberArgsToExecute = numberToExecuteAsMultiValue * numValuesPerBatch; for (int i = 0; i < numberArgsToExecute; i++) { if (i != 0 && i % numValuesPerBatch == 0) { try { batchedStatement.execute(); } catch (SQLException ex) { sqlEx = handleExceptionForBatch(batchCounter, numValuesPerBatch, updateCounts, ex); } updateCountCounter = processMultiCountsAndKeys( (StatementImpl)batchedStatement, updateCountCounter, updateCounts); batchedStatement.clearParameters(); batchedParamIndex = 1; } batchedParamIndex = setOneBatchedParameterSet(batchedStatement, batchedParamIndex, this.batchedArgs .get(batchCounter++)); } try { batchedStatement.execute(); } catch (SQLException ex) { sqlEx = handleExceptionForBatch(batchCounter - 1, numValuesPerBatch, updateCounts, ex); } updateCountCounter = processMultiCountsAndKeys( (StatementImpl)batchedStatement, updateCountCounter, updateCounts); batchedStatement.clearParameters(); numValuesPerBatch = numBatchedArgs - batchCounter; } finally { if (batchedStatement != null) { batchedStatement.close(); } } try { if (numValuesPerBatch > 0) { if (this.retrieveGeneratedKeys) { batchedStatement = locallyScopedConn.prepareStatement( generateMultiStatementForBatch(numValuesPerBatch), RETURN_GENERATED_KEYS); } else { batchedStatement = locallyScopedConn.prepareStatement( generateMultiStatementForBatch(numValuesPerBatch)); } if (timeoutTask != null) { timeoutTask.toCancel = (StatementImpl)batchedStatement; } batchedParamIndex = 1; while (batchCounter < numBatchedArgs) { batchedParamIndex = setOneBatchedParameterSet(batchedStatement, batchedParamIndex, this.batchedArgs .get(batchCounter++)); } try { batchedStatement.execute(); } catch (SQLException ex) { sqlEx = handleExceptionForBatch(batchCounter - 1, numValuesPerBatch, updateCounts, ex); } updateCountCounter = processMultiCountsAndKeys( (StatementImpl)batchedStatement, updateCountCounter, updateCounts); batchedStatement.clearParameters(); } if (timeoutTask != null) { if (timeoutTask.caughtWhileCancelling != null) { throw timeoutTask.caughtWhileCancelling; } timeoutTask.cancel(); locallyScopedConn.getCancelTimer().purge(); timeoutTask = null; } if (sqlEx != null) { SQLException batchUpdateException = new java.sql.BatchUpdateException(sqlEx .getMessage(), sqlEx.getSQLState(), sqlEx .getErrorCode(), updateCounts); batchUpdateException.initCause(sqlEx); throw batchUpdateException; } return updateCounts; } finally { if (batchedStatement != null) { batchedStatement.close(); } } } finally { if (timeoutTask != null) { timeoutTask.cancel(); locallyScopedConn.getCancelTimer().purge(); } resetCancelledState(); if (!multiQueriesEnabled) { locallyScopedConn.getIO().disableMultiQueries(); } clearBatch(); } } } private String generateMultiStatementForBatch(int numBatches) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { StringBuffer newStatementSql = new StringBuffer((this.originalSql .length() + 1) * numBatches); newStatementSql.append(this.originalSql); for (int i = 0; i < numBatches - 1; i++) { newStatementSql.append(';'); newStatementSql.append(this.originalSql); } return newStatementSql.toString(); } } /** * Rewrites the already prepared statement into a multi-value insert * statement of 'statementsPerBatch' values and executes the entire batch * using this new statement. * * @return update counts in the same fashion as executeBatch() * * @throws SQLException */ protected int[] executeBatchedInserts(int batchTimeout) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { String valuesClause = getValuesClause(); MySQLConnection locallyScopedConn = this.connection; if (valuesClause == null) { return executeBatchSerially(batchTimeout); } int numBatchedArgs = this.batchedArgs.size(); if (this.retrieveGeneratedKeys) { this.batchedGeneratedKeys = new ArrayList<ResultSetRow>(numBatchedArgs); } int numValuesPerBatch = computeBatchSize(numBatchedArgs); if (numBatchedArgs < numValuesPerBatch) { numValuesPerBatch = numBatchedArgs; } java.sql.PreparedStatement batchedStatement = null; int batchedParamIndex = 1; int updateCountRunningTotal = 0; int numberToExecuteAsMultiValue = 0; int batchCounter = 0; CancelTask timeoutTask = null; SQLException sqlEx = null; int[] updateCounts = new int[numBatchedArgs]; for (int i = 0; i < this.batchedArgs.size(); i++) { updateCounts[i] = 1; } try { try { batchedStatement = /* FIXME -if we ever care about folks proxying our MySQLConnection */ prepareBatchedInsertSQL(locallyScopedConn, numValuesPerBatch); if (locallyScopedConn.getEnableQueryTimeouts() && batchTimeout != 0 && locallyScopedConn.versionMeetsMinimum(5, 0, 0)) { timeoutTask = new CancelTask( (StatementImpl) batchedStatement); locallyScopedConn.getCancelTimer().schedule(timeoutTask, batchTimeout); } if (numBatchedArgs < numValuesPerBatch) { numberToExecuteAsMultiValue = numBatchedArgs; } else { numberToExecuteAsMultiValue = numBatchedArgs / numValuesPerBatch; } int numberArgsToExecute = numberToExecuteAsMultiValue * numValuesPerBatch; for (int i = 0; i < numberArgsToExecute; i++) { if (i != 0 && i % numValuesPerBatch == 0) { try { updateCountRunningTotal += batchedStatement .executeUpdate(); } catch (SQLException ex) { sqlEx = handleExceptionForBatch(batchCounter - 1, numValuesPerBatch, updateCounts, ex); } getBatchedGeneratedKeys(batchedStatement); batchedStatement.clearParameters(); batchedParamIndex = 1; } batchedParamIndex = setOneBatchedParameterSet( batchedStatement, batchedParamIndex, this.batchedArgs.get(batchCounter++)); } try { //updateCountRunningTotal += batchedStatement.executeUpdate(); } catch (SQLException ex) { sqlEx = handleExceptionForBatch(batchCounter - 1, numValuesPerBatch, updateCounts, ex); } getBatchedGeneratedKeys(batchedStatement); numValuesPerBatch = numBatchedArgs - batchCounter; } finally { if (batchedStatement != null) { batchedStatement.close(); } } try { if (numValuesPerBatch > 0) { batchedStatement = prepareBatchedInsertSQL(locallyScopedConn, numValuesPerBatch); if (timeoutTask != null) { timeoutTask.toCancel = (StatementImpl) batchedStatement; } batchedParamIndex = 1; while (batchCounter < numBatchedArgs) { batchedParamIndex = setOneBatchedParameterSet( batchedStatement, batchedParamIndex, this.batchedArgs.get(batchCounter++)); } try { updateCountRunningTotal += batchedStatement.executeUpdate(); } catch (SQLException ex) { sqlEx = handleExceptionForBatch(batchCounter - 1, numValuesPerBatch, updateCounts, ex); } getBatchedGeneratedKeys(batchedStatement); } if (sqlEx != null) { SQLException batchUpdateException = new java.sql.BatchUpdateException(sqlEx .getMessage(), sqlEx.getSQLState(), sqlEx .getErrorCode(), updateCounts); batchUpdateException.initCause(sqlEx); throw batchUpdateException; } return updateCounts; } finally { if (batchedStatement != null) { batchedStatement.close(); } } } finally { if (timeoutTask != null) { timeoutTask.cancel(); locallyScopedConn.getCancelTimer().purge(); } resetCancelledState(); } } } protected String getValuesClause() throws SQLException { return this.parseInfo.valuesClause; } /** * Computes the optimum number of batched parameter lists to send * without overflowing max_allowed_packet. * * @param numBatchedArgs * @return * @throws SQLException */ protected int computeBatchSize(int numBatchedArgs) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { long[] combinedValues = computeMaxParameterSetSizeAndBatchSize(numBatchedArgs); long maxSizeOfParameterSet = combinedValues[0]; long sizeOfEntireBatch = combinedValues[1]; int maxAllowedPacket = this.connection.getMaxAllowedPacket(); if (sizeOfEntireBatch < maxAllowedPacket - this.originalSql.length()) { return numBatchedArgs; } return (int)Math.max(1, (maxAllowedPacket - this.originalSql.length()) / maxSizeOfParameterSet); } } /** * 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 = 0; long maxSizeOfParameterSet = 0; for (int i = 0; i < numBatchedArgs; i++) { BatchParams paramArg = (BatchParams) this.batchedArgs .get(i); boolean[] isNullBatch = paramArg.isNull; boolean[] isStreamBatch = paramArg.isStream; long sizeOfParameterSet = 0; for (int j = 0; j < isNullBatch.length; j++) { if (!isNullBatch[j]) { if (isStreamBatch[j]) { int streamLength = paramArg.streamLengths[j]; if (streamLength != -1) { sizeOfParameterSet += streamLength * 2; // for safety in escaping } else { int paramLength = paramArg.parameterStrings[j].length; sizeOfParameterSet += paramLength; } } else { sizeOfParameterSet += paramArg.parameterStrings[j].length; } } else { sizeOfParameterSet += 4; // for NULL literal in SQL } } // // Account for static part of values clause // This is a little naiive, because the ?s will be replaced // but it gives us some padding, and is less housekeeping // to ignore them. We're looking for a "fuzzy" value here // anyway // if (getValuesClause() != null) { sizeOfParameterSet += getValuesClause().length() + 1; } else { sizeOfParameterSet += this.originalSql.length() + 1; } sizeOfEntireBatch += sizeOfParameterSet; if (sizeOfParameterSet > maxSizeOfParameterSet) { maxSizeOfParameterSet = sizeOfParameterSet; } } return new long[] {maxSizeOfParameterSet, sizeOfEntireBatch}; } } /** * Executes the current batch of statements by executing them one-by-one. * * @return a list of update counts * @throws SQLException * if an error occurs */ protected int[] executeBatchSerially(int batchTimeout) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { MySQLConnection locallyScopedConn = this.connection; if (locallyScopedConn == null) { checkClosed(); } int[] updateCounts = null; if (this.batchedArgs != null) { int nbrCommands = this.batchedArgs.size(); updateCounts = new int[nbrCommands]; for (int i = 0; i < nbrCommands; i++) { updateCounts[i] = -3; } SQLException sqlEx = null; CancelTask timeoutTask = null; try { if (locallyScopedConn.getEnableQueryTimeouts() && batchTimeout != 0 && locallyScopedConn.versionMeetsMinimum(5, 0, 0)) { timeoutTask = new CancelTask(this); locallyScopedConn.getCancelTimer().schedule(timeoutTask, batchTimeout); } if (this.retrieveGeneratedKeys) { this.batchedGeneratedKeys = new ArrayList<ResultSetRow>(nbrCommands); } for (batchCommandIndex = 0; batchCommandIndex < nbrCommands; batchCommandIndex++) { Object arg = this.batchedArgs.get(batchCommandIndex); if (arg instanceof String) { updateCounts[batchCommandIndex] = executeUpdate((String) arg); } else { BatchParams paramArg = (BatchParams) arg; try { updateCounts[batchCommandIndex] = executeUpdate( paramArg.parameterStrings, paramArg.parameterStreams, paramArg.isStream, paramArg.streamLengths, paramArg.isNull, true); if (this.retrieveGeneratedKeys) { java.sql.ResultSet rs = null; try { if (containsOnDuplicateKeyUpdateInSQL()) rs = getGeneratedKeysInternal(1); else 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[batchCommandIndex] = EXECUTE_FAILED; if (this.continueBatchOnError && !(ex instanceof MySQLTimeoutException) && !(ex instanceof MySQLStatementCancelledException) && !hasDeadlockOrTimeoutRolledBackTx(ex)) { sqlEx = ex; } else { int[] newUpdateCounts = new int[batchCommandIndex]; System.arraycopy(updateCounts, 0, newUpdateCounts, 0, batchCommandIndex); SQLException batchUpdateException = new java.sql.BatchUpdateException(ex .getMessage(), ex.getSQLState(), ex .getErrorCode(), newUpdateCounts); batchUpdateException.initCause(ex); throw batchUpdateException; } } } } if (sqlEx != null) { SQLException batchUpdateException = new java.sql.BatchUpdateException(sqlEx.getMessage(), sqlEx.getSQLState(), sqlEx.getErrorCode(), updateCounts); batchUpdateException.initCause(sqlEx); throw batchUpdateException; } } catch (NullPointerException npe) { try { checkClosed(); } catch (SQLException connectionClosedEx) { updateCounts[batchCommandIndex] = EXECUTE_FAILED; int[] newUpdateCounts = new int[batchCommandIndex]; System.arraycopy(updateCounts, 0, newUpdateCounts, 0, batchCommandIndex); throw new java.sql.BatchUpdateException(connectionClosedEx .getMessage(), connectionClosedEx.getSQLState(), connectionClosedEx .getErrorCode(), newUpdateCounts); } throw npe; // we don't know why this happened, punt } finally { batchCommandIndex = -1; if (timeoutTask != null) { timeoutTask.cancel(); locallyScopedConn.getCancelTimer().purge(); } resetCancelledState(); } } return (updateCounts != null) ? updateCounts : new int[0]; } } public String getDateTime(String pattern){ SimpleDateFormat sdf = new SimpleDateFormat(pattern); return sdf.format(new java.util.Date()); } /** * Actually execute the prepared statement. This is here so server-side * PreparedStatements can re-use most of the code from this class. * * @param maxRowsToRetrieve * the max number of rows to return * @param sendPacket * the packet to send * @param createStreamingResultSet * should a 'streaming' result set be created? * @param queryIsSelectOnly * is this query doing a SELECT? * @param unpackFields * DOCUMENT ME! * * @return the results as a ResultSet * * @throws SQLException * if an error occurs. */ protected ResultSetInternalMethods executeInternal(int maxRowsToRetrieve, Buffer sendPacket, boolean createStreamingResultSet, boolean queryIsSelectOnly, Field[] metadataFromCache, boolean isBatch) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { try { resetCancelledState(); MySQLConnection locallyScopedConnection = this.connection; this.numberOfExecutions++; if (this.doPingInstead) { doPingInstead(); return this.results; } ResultSetInternalMethods rs; CancelTask timeoutTask = null; try { if (locallyScopedConnection.getEnableQueryTimeouts() && this.timeoutInMillis != 0 && locallyScopedConnection.versionMeetsMinimum(5, 0, 0)) { timeoutTask = new CancelTask(this); locallyScopedConnection.getCancelTimer().schedule(timeoutTask, this.timeoutInMillis); } if (!isBatch) { statementBegins(); } rs = locallyScopedConnection.execSQL(this, null, maxRowsToRetrieve, sendPacket, this.resultSetType, this.resultSetConcurrency, createStreamingResultSet, this.currentCatalog, metadataFromCache, isBatch); if (timeoutTask != null) { timeoutTask.cancel(); locallyScopedConnection.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; } } } finally { if (!isBatch) { this.statementExecuting.set(false); } if (timeoutTask != null) { timeoutTask.cancel(); locallyScopedConnection.getCancelTimer().purge(); } } return rs; } catch (NullPointerException npe) { checkClosed(); // we can't synchronize ourselves against async connection-close // due to deadlock issues, so this is the next best thing for // this particular corner case. throw npe; } } } /** * A Prepared SQL query is executed and its ResultSet is returned * * @return a ResultSet that contains the data produced by the query - never * null * * @exception SQLException * if a database access error occurs */ public java.sql.ResultSet executeQuery() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { MySQLConnection locallyScopedConn = this.connection; checkForDml(this.originalSql, this.firstCharOfStmt); CachedResultSetMetaData cachedMetadata = null; clearWarnings(); boolean doStreaming = createStreamingResultSet(); this.batchedGeneratedKeys = null; // Adjust net_write_timeout to a higher value if we're // streaming result sets. More often than not, someone runs into // an issue where they blow net_write_timeout when using this // feature, and if they're willing to hold a result set open // for 30 seconds or more, one more round-trip isn't going to hurt // // This is reset by RowDataDynamic.close(). if (doStreaming && this.connection.getNetTimeoutForStreamingResults() > 0) { java.sql.Statement stmt = null; try { stmt = this.connection.createStatement(); ((com.mysql.jdbc.StatementImpl)stmt).executeSimpleNonQuery(this.connection, "SET net_write_timeout=" + this.connection.getNetTimeoutForStreamingResults()); } finally { if (stmt != null) { stmt.close(); } } } Buffer sendPacket = fillSendPacket(); if (!this.connection.getHoldResultsOpenOverStatementClose()) { if (!this.holdResultsOpenOverClose) { if (this.results != null) { this.results.realClose(false); } if (this.generatedKeysResults != null) { this.generatedKeysResults.realClose(false); } closeAllOpenResults(); } } String oldCatalog = null; if (!locallyScopedConn.getCatalog().equals(this.currentCatalog)) { oldCatalog = locallyScopedConn.getCatalog(); locallyScopedConn.setCatalog(this.currentCatalog); } // // Check if we have cached metadata for this query... // if (locallyScopedConn.getCacheResultSetMetadata()) { cachedMetadata = locallyScopedConn.getCachedMetaData(this.originalSql); } Field[] metadataFromCache = null; if (cachedMetadata != null) { metadataFromCache = cachedMetadata.fields; } if (locallyScopedConn.useMaxRows()) { // If there isn't a limit clause in the SQL // then limit the number of rows to return in // an efficient manner. Only do this if // setMaxRows() hasn't been used on any Statements // generated from the current Connection (saves // a query, and network traffic). if (this.hasLimitClause) { this.results = executeInternal(this.maxRows, sendPacket, createStreamingResultSet(), true, metadataFromCache, false); } else { if (this.maxRows <= 0) { executeSimpleNonQuery(locallyScopedConn, "SET SQL_SELECT_LIMIT=DEFAULT"); } else { executeSimpleNonQuery(locallyScopedConn, "SET SQL_SELECT_LIMIT=" + this.maxRows); } this.results = executeInternal(-1, sendPacket, doStreaming, true, metadataFromCache, false); if (oldCatalog != null) { this.connection.setCatalog(oldCatalog); } } } else { this.results = executeInternal(-1, sendPacket, doStreaming, true, metadataFromCache, false); } if (oldCatalog != null) { locallyScopedConn.setCatalog(oldCatalog); } if (cachedMetadata != null) { locallyScopedConn.initializeResultsMetadataFromCache(this.originalSql, cachedMetadata, this.results); } else { if (locallyScopedConn.getCacheResultSetMetadata()) { locallyScopedConn.initializeResultsMetadataFromCache(this.originalSql, null /* will be created */, this.results); } } this.lastInsertId = this.results.getUpdateID(); return this.results; } } /** * Execute a SQL INSERT, UPDATE or DELETE statement. In addition, SQL * statements that return nothing such as SQL DDL statements can be * executed. * * @return either the row count for INSERT, UPDATE or DELETE; or 0 for SQL * statements that return nothing. * * @exception SQLException * if a database access error occurs */ public int executeUpdate() throws SQLException { return executeUpdate(true, false); } /* * We need this variant, because ServerPreparedStatement calls this for * batched updates, which will end up clobbering the warnings and generated * keys we need to gather for the batch. */ protected int executeUpdate( boolean clearBatchedGeneratedKeysAndWarnings, boolean isBatch) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (clearBatchedGeneratedKeysAndWarnings) { clearWarnings(); this.batchedGeneratedKeys = null; } return executeUpdate(this.parameterValues, this.parameterStreams, this.isStream, this.streamLengths, this.isNull, isBatch); } } /** * Added to allow batch-updates * * @param batchedParameterStrings * string values used in single statement * @param batchedParameterStreams * stream values used in single statement * @param batchedIsStream * flags for streams used in single statement * @param batchedStreamLengths * lengths of streams to be read. * @param batchedIsNull * flags for parameters that are null * * @return the update count * * @throws SQLException * if a database error occurs */ protected int executeUpdate(byte[][] batchedParameterStrings, InputStream[] batchedParameterStreams, boolean[] batchedIsStream, int[] batchedStreamLengths, boolean[] batchedIsNull, boolean isReallyBatch) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { MySQLConnection locallyScopedConn = this.connection; if (locallyScopedConn.isReadOnly()) { throw SQLError.createSQLException(Messages.getString("PreparedStatement.34") //$NON-NLS-1$ + Messages.getString("PreparedStatement.35"), //$NON-NLS-1$ SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } if ((this.firstCharOfStmt == 'S') && isSelectQuery()) { //$NON-NLS-1$ throw SQLError.createSQLException(Messages.getString("PreparedStatement.37"), //$NON-NLS-1$ "01S03", getExceptionInterceptor()); //$NON-NLS-1$ } if (!locallyScopedConn.getHoldResultsOpenOverStatementClose()) { if (this.results != null) { this.results.realClose(false); } if (this.generatedKeysResults != null) { this.generatedKeysResults.realClose(false); } closeAllOpenResults(); } ResultSetInternalMethods rs = null; Buffer sendPacket = fillSendPacket(batchedParameterStrings, batchedParameterStreams, batchedIsStream, batchedStreamLengths); String oldCatalog = null; if (!locallyScopedConn.getCatalog().equals(this.currentCatalog)) { oldCatalog = locallyScopedConn.getCatalog(); locallyScopedConn.setCatalog(this.currentCatalog); } // // Only apply max_rows to selects // if (locallyScopedConn.useMaxRows()) { executeSimpleNonQuery(locallyScopedConn, "SET SQL_SELECT_LIMIT=DEFAULT"); } boolean oldInfoMsgState = false; if (this.retrieveGeneratedKeys) { oldInfoMsgState = locallyScopedConn.isReadInfoMsgEnabled(); locallyScopedConn.setReadInfoMsgEnabled(true); } rs = executeInternal(-1, sendPacket, false, false, null, isReallyBatch); if (this.retrieveGeneratedKeys) { locallyScopedConn.setReadInfoMsgEnabled(oldInfoMsgState); rs.setFirstCharOfQuery(this.firstCharOfStmt); } if (oldCatalog != null) { locallyScopedConn.setCatalog(oldCatalog); } this.results = rs; this.updateCount = rs.getUpdateCount(); if (containsOnDuplicateKeyUpdateInSQL() && this.compensateForOnDuplicateKeyUpdate) { if (this.updateCount == 2 || this.updateCount == 0) { this.updateCount = 1; } } int truncatedUpdateCount = 0; if (this.updateCount > Integer.MAX_VALUE) { truncatedUpdateCount = Integer.MAX_VALUE; } else { truncatedUpdateCount = (int) this.updateCount; } this.lastInsertId = rs.getUpdateID(); return truncatedUpdateCount; } } protected boolean containsOnDuplicateKeyUpdateInSQL() { return this.parseInfo.isOnDuplicateKeyUpdate; } /** * Creates the packet that contains the query to be sent to the server. * * @return A Buffer filled with the query representing the * PreparedStatement. * * @throws SQLException * if an error occurs. */ protected Buffer fillSendPacket() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { return fillSendPacket(this.parameterValues, this.parameterStreams, this.isStream, this.streamLengths); } } /** * Creates the packet that contains the query to be sent to the server. * * @param batchedParameterStrings * the parameters as strings * @param batchedParameterStreams * the parameters as streams * @param batchedIsStream * is the given parameter a stream? * @param batchedStreamLengths * the lengths of the streams (if appropriate) * * @return a Buffer filled with the query that represents this statement * * @throws SQLException * if an error occurs. */ protected Buffer fillSendPacket(byte[][] batchedParameterStrings, InputStream[] batchedParameterStreams, boolean[] batchedIsStream, int[] batchedStreamLengths) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { Buffer sendPacket = this.connection.getIO().getSharedSendPacket(); sendPacket.clear(); sendPacket.writeByte((byte) MysqlDefs.QUERY); boolean useStreamLengths = this.connection .getUseStreamLengthsInPrepStmts(); // // Try and get this allocation as close as possible // for BLOBs // int ensurePacketSize = 0; String statementComment = this.connection.getStatementComment(); byte[] commentAsBytes = null; if (statementComment != null) { if (this.charConverter != null) { commentAsBytes = this.charConverter.toBytes(statementComment); } else { commentAsBytes = StringUtils.getBytes(statementComment, this.charConverter, this.charEncoding, this.connection .getServerCharacterEncoding(), this.connection .parserKnowsUnicode(), getExceptionInterceptor()); } ensurePacketSize += commentAsBytes.length; ensurePacketSize += 6; // for /*[space] [space]*/ } for (int i = 0; i < batchedParameterStrings.length; i++) { if (batchedIsStream[i] && useStreamLengths) { ensurePacketSize += batchedStreamLengths[i]; } } if (ensurePacketSize != 0) { sendPacket.ensureCapacity(ensurePacketSize); } if (commentAsBytes != null) { sendPacket.writeBytesNoNull(Constants.SLASH_STAR_SPACE_AS_BYTES); sendPacket.writeBytesNoNull(commentAsBytes); sendPacket.writeBytesNoNull(Constants.SPACE_STAR_SLASH_SPACE_AS_BYTES); } for (int i = 0; i < batchedParameterStrings.length; i++) { checkAllParametersSet(batchedParameterStrings[i], batchedParameterStreams[i], i); sendPacket.writeBytesNoNull(this.staticSqlStrings[i]); if (batchedIsStream[i]) { streamToBytes(sendPacket, batchedParameterStreams[i], true, batchedStreamLengths[i], useStreamLengths); } else { sendPacket.writeBytesNoNull(batchedParameterStrings[i]); } } sendPacket .writeBytesNoNull(this.staticSqlStrings[batchedParameterStrings.length]); return sendPacket; } } private void checkAllParametersSet(byte[] parameterString, InputStream parameterStream, int columnIndex) throws SQLException { if ((parameterString == null) && parameterStream == null) { throw SQLError.createSQLException(Messages .getString("PreparedStatement.40") //$NON-NLS-1$ + (columnIndex + 1), SQLError.SQL_STATE_WRONG_NO_OF_PARAMETERS, getExceptionInterceptor()); } } /** * Returns a prepared statement for the number of batched parameters, used when re-writing batch INSERTs. */ protected PreparedStatement prepareBatchedInsertSQL(MySQLConnection localConn, int numBatches) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { PreparedStatement pstmt = new PreparedStatement(localConn, "Rewritten batch of: " + this.originalSql, this.currentCatalog, this.parseInfo.getParseInfoForBatch(numBatches)); pstmt.setRetrieveGeneratedKeys(this.retrieveGeneratedKeys); pstmt.rewrittenBatchSize = numBatches; return pstmt; } } protected void setRetrieveGeneratedKeys(boolean flag) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { this.retrieveGeneratedKeys = flag; } } protected int rewrittenBatchSize = 0; public int getRewrittenBatchSize() { return this.rewrittenBatchSize; } public String getNonRewrittenSql() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { int indexOfBatch = this.originalSql.indexOf(" of: "); if (indexOfBatch != -1) { return this.originalSql.substring(indexOfBatch + 5); } return this.originalSql; } } /** * DOCUMENT ME! * * @param parameterIndex * DOCUMENT ME! * * @return DOCUMENT ME! * * @throws SQLException * DOCUMENT ME! */ public byte[] getBytesRepresentation(int parameterIndex) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.isStream[parameterIndex]) { return streamToBytes(this.parameterStreams[parameterIndex], false, this.streamLengths[parameterIndex], this.connection .getUseStreamLengthsInPrepStmts()); } byte[] parameterVal = this.parameterValues[parameterIndex]; if (parameterVal == null) { return null; } if ((parameterVal[0] == '\'') && (parameterVal[parameterVal.length - 1] == '\'')) { byte[] valNoQuotes = new byte[parameterVal.length - 2]; System.arraycopy(parameterVal, 1, valNoQuotes, 0, parameterVal.length - 2); return valNoQuotes; } return parameterVal; } } /** * Get bytes representation for a parameter in a statement batch. * @param parameterIndex * @param commandIndex * @return * @throws SQLException */ protected byte[] getBytesRepresentationForBatch(int parameterIndex, int commandIndex) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { Object batchedArg = batchedArgs.get(commandIndex); if (batchedArg instanceof String) { try { return (StringUtils.getBytes((String)batchedArg, charEncoding)); } catch (UnsupportedEncodingException uue) { throw new RuntimeException(Messages .getString("PreparedStatement.32") //$NON-NLS-1$ + this.charEncoding + Messages.getString("PreparedStatement.33")); //$NON-NLS-1$ } } BatchParams params = (BatchParams)batchedArg; if (params.isStream[parameterIndex]) return streamToBytes(params.parameterStreams[parameterIndex], false, params.streamLengths[parameterIndex], connection.getUseStreamLengthsInPrepStmts()); byte parameterVal[] = params.parameterStrings[parameterIndex]; if (parameterVal == null) return null; if ((parameterVal[0] == '\'') && (parameterVal[parameterVal.length - 1] == '\'')) { byte[] valNoQuotes = new byte[parameterVal.length - 2]; System.arraycopy(parameterVal, 1, valNoQuotes, 0, parameterVal.length - 2); return valNoQuotes; } return parameterVal; } } // --------------------------JDBC 2.0----------------------------- private final String getDateTimePattern(String dt, boolean toTime) throws Exception { // // Special case // int dtLength = (dt != null) ? dt.length() : 0; if ((dtLength >= 8) && (dtLength <= 10)) { int dashCount = 0; boolean isDateOnly = true; for (int i = 0; i < dtLength; i++) { char c = dt.charAt(i); if (!Character.isDigit(c) && (c != '-')) { isDateOnly = false; break; } if (c == '-') { dashCount++; } } if (isDateOnly && (dashCount == 2)) { return "yyyy-MM-dd"; //$NON-NLS-1$ } } // // Special case - time-only // boolean colonsOnly = true; for (int i = 0; i < dtLength; i++) { char c = dt.charAt(i); if (!Character.isDigit(c) && (c != ':')) { colonsOnly = false; break; } } if (colonsOnly) { return "HH:mm:ss"; //$NON-NLS-1$ } int n; int z; int count; int maxvecs; char c; char separator; StringReader reader = new StringReader(dt + " "); //$NON-NLS-1$ ArrayList<Object[]> vec = new ArrayList<Object[]>(); ArrayList<Object[]> vecRemovelist = new ArrayList<Object[]>(); Object[] nv = new Object[3]; Object[] v; nv[0] = Character.valueOf('y'); nv[1] = new StringBuffer(); nv[2] = Integer.valueOf(0); vec.add(nv); if (toTime) { nv = new Object[3]; nv[0] = Character.valueOf('h'); nv[1] = new StringBuffer(); nv[2] = Integer.valueOf(0); vec.add(nv); } while ((z = reader.read()) != -1) { separator = (char) z; maxvecs = vec.size(); for (count = 0; count < maxvecs; count++) { v = vec.get(count); n = ((Integer) v[2]).intValue(); c = getSuccessor(((Character) v[0]).charValue(), n); if (!Character.isLetterOrDigit(separator)) { if ((c == ((Character) v[0]).charValue()) && (c != 'S')) { vecRemovelist.add(v); } else { ((StringBuffer) v[1]).append(separator); if ((c == 'X') || (c == 'Y')) { v[2] = Integer.valueOf(4); } } } else { if (c == 'X') { c = 'y'; nv = new Object[3]; nv[1] = (new StringBuffer(((StringBuffer) v[1]) .toString())).append('M'); nv[0] = Character.valueOf('M'); nv[2] = Integer.valueOf(1); vec.add(nv); } else if (c == 'Y') { c = 'M'; nv = new Object[3]; nv[1] = (new StringBuffer(((StringBuffer) v[1]) .toString())).append('d'); nv[0] = Character.valueOf('d'); nv[2] = Integer.valueOf(1); vec.add(nv); } ((StringBuffer) v[1]).append(c); if (c == ((Character) v[0]).charValue()) { v[2] = Integer.valueOf(n + 1); } else { v[0] = Character.valueOf(c); v[2] = Integer.valueOf(1); } } } int size = vecRemovelist.size(); for (int i = 0; i < size; i++) { v = vecRemovelist.get(i); vec.remove(v); } vecRemovelist.clear(); } int size = vec.size(); for (int i = 0; i < size; i++) { v = vec.get(i); c = ((Character) v[0]).charValue(); n = ((Integer) v[2]).intValue(); boolean bk = getSuccessor(c, n) != c; boolean atEnd = (((c == 's') || (c == 'm') || ((c == 'h') && toTime)) && bk); boolean finishesAtDate = (bk && (c == 'd') && !toTime); boolean containsEnd = (((StringBuffer) v[1]).toString() .indexOf('W') != -1); if ((!atEnd && !finishesAtDate) || (containsEnd)) { vecRemovelist.add(v); } } size = vecRemovelist.size(); for (int i = 0; i < size; i++) { vec.remove(vecRemovelist.get(i)); } vecRemovelist.clear(); v = vec.get(0); // might throw exception StringBuffer format = (StringBuffer) v[1]; format.setLength(format.length() - 1); return format.toString(); } /** * The number, types and properties of a ResultSet's columns are provided by * the getMetaData method. * * @return the description of a ResultSet's columns * * @exception SQLException * if a database-access error occurs. */ public java.sql.ResultSetMetaData getMetaData() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { // // We could just tack on a LIMIT 0 here no matter what the // statement, and check if a result set was returned or not, // but I'm not comfortable with that, myself, so we take // the "safer" road, and only allow metadata for _actual_ // SELECTS (but not SHOWs). // // CALL's are trapped further up and you end up with a // CallableStatement anyway. // if (!isSelectQuery()) { return null; } PreparedStatement mdStmt = null; java.sql.ResultSet mdRs = null; if (this.pstmtResultMetaData == null) { try { mdStmt = new PreparedStatement(this.connection, this.originalSql, this.currentCatalog, this.parseInfo); mdStmt.setMaxRows(1); int paramCount = this.parameterValues.length; for (int i = 1; i <= paramCount; i++) { mdStmt.setString(i, ""); //$NON-NLS-1$ } boolean hadResults = mdStmt.execute(); if (hadResults) { mdRs = mdStmt.getResultSet(); this.pstmtResultMetaData = mdRs.getMetaData(); } else { this.pstmtResultMetaData = new ResultSetMetaData( new Field[0], this.connection.getUseOldAliasMetadataBehavior(), getExceptionInterceptor()); } } finally { SQLException sqlExRethrow = null; if (mdRs != null) { try { mdRs.close(); } catch (SQLException sqlEx) { sqlExRethrow = sqlEx; } mdRs = null; } if (mdStmt != null) { try { mdStmt.close(); } catch (SQLException sqlEx) { sqlExRethrow = sqlEx; } mdStmt = null; } if (sqlExRethrow != null) { throw sqlExRethrow; } } } return this.pstmtResultMetaData; } } protected boolean isSelectQuery() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { return StringUtils.startsWithIgnoreCaseAndWs( StringUtils.stripComments(this.originalSql, "'\"", "'\"", true, false, true, true), "SELECT"); } } /** * @see PreparedStatement#getParameterMetaData() */ public ParameterMetaData getParameterMetaData() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.parameterMetaData == null) { if (this.connection.getGenerateSimpleParameterMetadata()) { this.parameterMetaData = new MysqlParameterMetadata(this.parameterCount); } else { this.parameterMetaData = new MysqlParameterMetadata( null, this.parameterCount, getExceptionInterceptor()); } } return this.parameterMetaData; } } ParseInfo getParseInfo() { return this.parseInfo; } private final char getSuccessor(char c, int n) { return ((c == 'y') && (n == 2)) ? 'X' : (((c == 'y') && (n < 4)) ? 'y' : ((c == 'y') ? 'M' : (((c == 'M') && (n == 2)) ? 'Y' : (((c == 'M') && (n < 3)) ? 'M' : ((c == 'M') ? 'd' : (((c == 'd') && (n < 2)) ? 'd' : ((c == 'd') ? 'H' : (((c == 'H') && (n < 2)) ? 'H' : ((c == 'H') ? 'm' : (((c == 'm') && (n < 2)) ? 'm' : ((c == 'm') ? 's' : (((c == 's') && (n < 2)) ? 's' : 'W')))))))))))); } /** * Used to escape binary data with hex for mb charsets * * @param buf * @param packet * @param size * @throws SQLException */ private final void hexEscapeBlock(byte[] buf, Buffer packet, int size) throws SQLException { for (int i = 0; i < size; i++) { byte b = buf[i]; int lowBits = (b & 0xff) / 16; int highBits = (b & 0xff) % 16; packet.writeByte(HEX_DIGITS[lowBits]); packet.writeByte(HEX_DIGITS[highBits]); } } private void initializeFromParseInfo() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { this.staticSqlStrings = this.parseInfo.staticSql; this.hasLimitClause = this.parseInfo.foundLimitClause; this.isLoadDataQuery = this.parseInfo.foundLoadData; this.firstCharOfStmt = this.parseInfo.firstStmtChar; this.parameterCount = this.staticSqlStrings.length - 1; this.parameterValues = new byte[this.parameterCount][]; this.parameterStreams = new InputStream[this.parameterCount]; this.isStream = new boolean[this.parameterCount]; this.streamLengths = new int[this.parameterCount]; this.isNull = new boolean[this.parameterCount]; this.parameterTypes = new int[this.parameterCount]; clearParameters(); for (int j = 0; j < this.parameterCount; j++) { this.isStream[j] = false; } } } boolean isNull(int paramIndex) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { return this.isNull[paramIndex]; } } private final int readblock(InputStream i, byte[] b) throws SQLException { try { return i.read(b); } catch (Throwable ex) { SQLException sqlEx = SQLError.createSQLException(Messages.getString("PreparedStatement.56") //$NON-NLS-1$ + ex.getClass().getName(), SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); sqlEx.initCause(ex); throw sqlEx; } } private final int readblock(InputStream i, byte[] b, int length) throws SQLException { try { int lengthToRead = length; if (lengthToRead > b.length) { lengthToRead = b.length; } return i.read(b, 0, lengthToRead); } catch (Throwable ex) { SQLException sqlEx = SQLError.createSQLException(Messages.getString("PreparedStatement.56") //$NON-NLS-1$ + ex.getClass().getName(), SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); sqlEx.initCause(ex); throw sqlEx; } } /** * Closes this statement, releasing all resources * * @param calledExplicitly * was this called by 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.useUsageAdvisor) { if (this.numberOfExecutions <= 1) { String message = Messages.getString("PreparedStatement.43"); //$NON-NLS-1$ this.eventSink.consumeEvent(new ProfilerEvent( ProfilerEvent.TYPE_WARN, "", this.currentCatalog, //$NON-NLS-1$ this.connectionId, this.getId(), -1, System .currentTimeMillis(), 0, Constants.MILLIS_I18N, null, this.pointOfOrigin, message)); } } super.realClose(calledExplicitly, closeOpenResults); this.dbmd = null; this.originalSql = null; this.staticSqlStrings = null; this.parameterValues = null; this.parameterStreams = null; this.isStream = null; this.streamLengths = null; this.isNull = null; this.streamConvertBuf = null; this.parameterTypes = null; } } /** * JDBC 2.0 Set an Array parameter. * * @param i * the first parameter is 1, the second is 2, ... * @param x * an object representing an SQL array * * @throws SQLException * because this method is not implemented. * @throws NotImplemented * DOCUMENT ME! */ public void setArray(int i, Array x) throws SQLException { throw SQLError.notImplemented(); } /** * When a very large ASCII value is input to a LONGVARCHAR parameter, it may * be more practical to send it via a java.io.InputStream. JDBC will read * the data from the stream as needed, until it reaches end-of-file. The * JDBC driver will do any necessary conversion from ASCII to the database * char format. * * <P> * <B>Note:</B> This stream object can either be a standard Java stream * object or your own subclass that implements the standard interface. * </p> * * @param parameterIndex * the first parameter is 1... * @param x * the parameter value * @param length * the number of bytes in the stream * * @exception SQLException * if a database access error occurs */ public void setAsciiStream(int parameterIndex, InputStream x, int length) throws SQLException { if (x == null) { setNull(parameterIndex, java.sql.Types.VARCHAR); } else { setBinaryStream(parameterIndex, x, length); } } /** * Set a parameter to a java.math.BigDecimal value. The driver converts this * to a SQL NUMERIC value when it sends it to the database. * * @param parameterIndex * the first parameter is 1... * @param x * the parameter value * * @exception SQLException * if a database access error occurs */ public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException { if (x == null) { setNull(parameterIndex, java.sql.Types.DECIMAL); } else { setInternal(parameterIndex, StringUtils .fixDecimalExponent(StringUtils.consistentToString(x))); this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.DECIMAL; } } /** * When a very large binary value is input to a LONGVARBINARY parameter, it * may be more practical to send it via a java.io.InputStream. JDBC will * read the data from the stream as needed, until it reaches end-of-file. * * <P> * <B>Note:</B> This stream object can either be a standard Java stream * object or your own subclass that implements the standard interface. * </p> * * @param parameterIndex * the first parameter is 1... * @param x * the parameter value * @param length * the number of bytes to read from the stream (ignored) * * @throws SQLException * if a database access error occurs */ public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (x == null) { setNull(parameterIndex, java.sql.Types.BINARY); } else { int parameterIndexOffset = getParameterIndexOffset(); if ((parameterIndex < 1) || (parameterIndex > this.staticSqlStrings.length)) { throw SQLError.createSQLException( Messages.getString("PreparedStatement.2") //$NON-NLS-1$ + parameterIndex + Messages.getString("PreparedStatement.3") + this.staticSqlStrings.length + Messages.getString("PreparedStatement.4"), //$NON-NLS-1$ //$NON-NLS-2$ SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } else if (parameterIndexOffset == -1 && parameterIndex == 1) { throw SQLError.createSQLException("Can't set IN parameter for return value of stored function call.", SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } this.parameterStreams[parameterIndex - 1 + parameterIndexOffset] = x; this.isStream[parameterIndex - 1 + parameterIndexOffset] = true; this.streamLengths[parameterIndex - 1 + parameterIndexOffset] = length; this.isNull[parameterIndex - 1 + parameterIndexOffset] = false; this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.BLOB; } } } public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException { setBinaryStream(parameterIndex, inputStream, (int)length); } /** * JDBC 2.0 Set a BLOB parameter. * * @param i * the first parameter is 1, the second is 2, ... * @param x * an object representing a BLOB * * @throws SQLException * if a database error occurs */ public void setBlob(int i, java.sql.Blob x) throws SQLException { if (x == null) { setNull(i, Types.BLOB); } else { ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); bytesOut.write('\''); escapeblockFast(x.getBytes(1, (int) x.length()), bytesOut, (int) x .length()); bytesOut.write('\''); setInternal(i, bytesOut.toByteArray()); this.parameterTypes[i - 1 + getParameterIndexOffset()] = Types.BLOB; } } /** * Set a parameter to a Java boolean value. The driver converts this to a * SQL BIT value when it sends it to the database. * * @param parameterIndex * the first parameter is 1... * @param x * the parameter value * * @throws SQLException * if a database access error occurs */ public void setBoolean(int parameterIndex, boolean x) throws SQLException { if (this.useTrueBoolean) { setInternal(parameterIndex, x ? "1" : "0"); //$NON-NLS-1$ //$NON-NLS-2$ } else { setInternal(parameterIndex, x ? "'t'" : "'f'"); //$NON-NLS-1$ //$NON-NLS-2$ this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.BOOLEAN; } } /** * Set a parameter to a Java byte value. The driver converts this to a SQL * TINYINT value when it sends it to the database. * * @param parameterIndex * the first parameter is 1... * @param x * the parameter value * * @exception SQLException * if a database access error occurs */ public void setByte(int parameterIndex, byte x) throws SQLException { setInternal(parameterIndex, String.valueOf(x)); this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.TINYINT; } /** * Set a parameter to a Java array of bytes. The driver converts this to a * SQL VARBINARY or LONGVARBINARY (depending on the argument's size relative * to the driver's limits on VARBINARYs) when it sends it to the database. * * @param parameterIndex * the first parameter is 1... * @param x * the parameter value * * @exception SQLException * if a database access error occurs */ public void setBytes(int parameterIndex, byte[] x) throws SQLException { setBytes(parameterIndex, x, true, true); if (x != null) { this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.BINARY; } } protected void setBytes(int parameterIndex, byte[] x, boolean checkForIntroducer, boolean escapeForMBChars) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (x == null) { setNull(parameterIndex, java.sql.Types.BINARY); } else { String connectionEncoding = this.connection.getEncoding(); try { if (this.connection.isNoBackslashEscapesSet() || (escapeForMBChars && this.connection.getUseUnicode() && connectionEncoding != null && CharsetMapping.isMultibyteCharset(connectionEncoding))) { // Send as hex ByteArrayOutputStream bOut = new ByteArrayOutputStream( (x.length * 2) + 3); bOut.write('x'); bOut.write('\''); for (int i = 0; i < x.length; i++) { int lowBits = (x[i] & 0xff) / 16; int highBits = (x[i] & 0xff) % 16; bOut.write(HEX_DIGITS[lowBits]); bOut.write(HEX_DIGITS[highBits]); } bOut.write('\''); setInternal(parameterIndex, bOut.toByteArray()); return; } } catch (SQLException ex) { throw ex; } catch (RuntimeException ex) { SQLException sqlEx = SQLError.createSQLException(ex.toString(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null); sqlEx.initCause(ex); throw sqlEx; } // escape them int numBytes = x.length; int pad = 2; boolean needsIntroducer = checkForIntroducer && this.connection.versionMeetsMinimum(4, 1, 0); if (needsIntroducer) { pad += 7; } ByteArrayOutputStream bOut = new ByteArrayOutputStream(numBytes + pad); if (needsIntroducer) { bOut.write('_'); bOut.write('b'); bOut.write('i'); bOut.write('n'); bOut.write('a'); bOut.write('r'); bOut.write('y'); } bOut.write('\''); for (int i = 0; i < numBytes; ++i) { byte b = x[i]; switch (b) { case 0: /* Must be escaped for 'mysql' */ bOut.write('\\'); bOut.write('0'); break; case '\n': /* Must be escaped for logs */ bOut.write('\\'); bOut.write('n'); break; case '\r': bOut.write('\\'); bOut.write('r'); break; case '\\': bOut.write('\\'); bOut.write('\\'); break; case '\'': bOut.write('\\'); bOut.write('\''); break; case '"': /* Better safe than sorry */ bOut.write('\\'); bOut.write('"'); break; case '\032': /* This gives problems on Win32 */ bOut.write('\\'); bOut.write('Z'); break; default: bOut.write(b); } } bOut.write('\''); setInternal(parameterIndex, bOut.toByteArray()); } } } /** * Used by updatable result sets for refreshRow() because the parameter has * already been escaped for updater or inserter prepared statements. * * @param parameterIndex * the parameter to set. * @param parameterAsBytes * the parameter as a string. * * @throws SQLException * if an error occurs */ protected void setBytesNoEscape(int parameterIndex, byte[] parameterAsBytes) throws SQLException { byte[] parameterWithQuotes = new byte[parameterAsBytes.length + 2]; parameterWithQuotes[0] = '\''; System.arraycopy(parameterAsBytes, 0, parameterWithQuotes, 1, parameterAsBytes.length); parameterWithQuotes[parameterAsBytes.length + 1] = '\''; setInternal(parameterIndex, parameterWithQuotes); } protected void setBytesNoEscapeNoQuotes(int parameterIndex, byte[] parameterAsBytes) throws SQLException { setInternal(parameterIndex, parameterAsBytes); } /** * JDBC 2.0 When a very large UNICODE value is input to a LONGVARCHAR * parameter, it may be more practical to send it via a java.io.Reader. JDBC * will read the data from the stream as needed, until it reaches * end-of-file. The JDBC driver will do any necessary conversion from * UNICODE to the database char format. * * <P> * <B>Note:</B> This stream object can either be a standard Java stream * object or your own subclass that implements the standard interface. * </p> * * @param parameterIndex * the first parameter is 1, the second is 2, ... * @param reader * the java reader which contains the UNICODE data * @param length * the number of characters in the stream * * @exception SQLException * if a database-access error occurs. */ public void setCharacterStream(int parameterIndex, java.io.Reader reader, int length) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { try { if (reader == null) { setNull(parameterIndex, Types.LONGVARCHAR); } else { char[] c = null; int len = 0; boolean useLength = this.connection .getUseStreamLengthsInPrepStmts(); String forcedEncoding = this.connection.getClobCharacterEncoding(); if (useLength && (length != -1)) { c = new char[length]; int numCharsRead = readFully(reader, c, length); // blocks // until // all // read if (forcedEncoding == null) { setString(parameterIndex, new String(c, 0, numCharsRead)); } else { try { setBytes(parameterIndex, StringUtils.getBytes(new String(c, 0, numCharsRead), forcedEncoding)); } catch (UnsupportedEncodingException uee) { throw SQLError.createSQLException("Unsupported character encoding " + forcedEncoding, SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } } } else { c = new char[4096]; StringBuffer buf = new StringBuffer(); while ((len = reader.read(c)) != -1) { buf.append(c, 0, len); } if (forcedEncoding == null) { setString(parameterIndex, buf.toString()); } else { try { setBytes(parameterIndex, StringUtils.getBytes(buf.toString(), forcedEncoding)); } catch (UnsupportedEncodingException uee) { throw SQLError.createSQLException("Unsupported character encoding " + forcedEncoding, SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } } } this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.CLOB; } } catch (java.io.IOException ioEx) { throw SQLError.createSQLException(ioEx.toString(), SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); } } } /** * JDBC 2.0 Set a CLOB parameter. * * @param i * the first parameter is 1, the second is 2, ... * @param x * an object representing a CLOB * * @throws SQLException * if a database error occurs */ public void setClob(int i, Clob x) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (x == null) { setNull(i, Types.CLOB); } else { String forcedEncoding = this.connection.getClobCharacterEncoding(); if (forcedEncoding == null) { setString(i, x.getSubString(1L, (int) x.length())); } else { try { setBytes(i, StringUtils.getBytes(x.getSubString(1L, (int)x.length()), forcedEncoding)); } catch (UnsupportedEncodingException uee) { throw SQLError.createSQLException("Unsupported character encoding " + forcedEncoding, SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } } this.parameterTypes[i - 1 + getParameterIndexOffset()] = Types.CLOB; } } } /** * 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... * @param x * the parameter value * * @exception java.sql.SQLException * if a database access error occurs */ public void setDate(int parameterIndex, java.sql.Date x) throws java.sql.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, java.sql.Date x, Calendar cal) throws SQLException { if (x == null) { setNull(parameterIndex, java.sql.Types.DATE); } else { checkClosed(); if (!this.useLegacyDatetimeCode) { newSetDateInternal(parameterIndex, x, cal); } else { // FIXME: Have instance version of this, problem as it's // not thread-safe :( SimpleDateFormat dateFormatter = new SimpleDateFormat( "''yyyy-MM-dd''", Locale.US); //$NON-NLS-1$ setInternal(parameterIndex, dateFormatter.format(x)); this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.DATE; } } } /** * Set a parameter to a Java double value. The driver converts this to a SQL * DOUBLE value when it sends it to the database * * @param parameterIndex * the first parameter is 1... * @param x * the parameter value * * @exception SQLException * if a database access error occurs */ 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()); } setInternal(parameterIndex, StringUtils.fixDecimalExponent(String .valueOf(x))); this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.DOUBLE; } } /** * Set a parameter to a Java float value. The driver converts this to a SQL * FLOAT value when it sends it to the database. * * @param parameterIndex * the first parameter is 1... * @param x * the parameter value * * @exception SQLException * if a database access error occurs */ public void setFloat(int parameterIndex, float x) throws SQLException { setInternal(parameterIndex, StringUtils.fixDecimalExponent(String .valueOf(x))); this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.FLOAT; } /** * Set a parameter to a Java int value. The driver converts this to a SQL * INTEGER value when it sends it to the database. * * @param parameterIndex * the first parameter is 1... * @param x * the parameter value * * @exception SQLException * if a database access error occurs */ public void setInt(int parameterIndex, int x) throws SQLException { setInternal(parameterIndex, String.valueOf(x)); this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.INTEGER; } protected final void setInternal(int paramIndex, byte[] val) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { int parameterIndexOffset = getParameterIndexOffset(); checkBounds(paramIndex, parameterIndexOffset); this.isStream[paramIndex - 1 + parameterIndexOffset] = false; this.isNull[paramIndex - 1 + parameterIndexOffset] = false; this.parameterStreams[paramIndex - 1 + parameterIndexOffset] = null; this.parameterValues[paramIndex - 1 + parameterIndexOffset] = val; } } protected void checkBounds(int paramIndex, int parameterIndexOffset) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if ((paramIndex < 1)) { throw SQLError.createSQLException( Messages.getString("PreparedStatement.49") //$NON-NLS-1$ + paramIndex + Messages.getString("PreparedStatement.50"), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$ } else if (paramIndex > this.parameterCount) { throw SQLError.createSQLException( Messages.getString("PreparedStatement.51") //$NON-NLS-1$ + paramIndex + Messages.getString("PreparedStatement.52") + (this.parameterValues.length) + Messages.getString("PreparedStatement.53"), //$NON-NLS-1$ //$NON-NLS-2$ SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } else if (parameterIndexOffset == -1 && paramIndex == 1) { throw SQLError.createSQLException("Can't set IN parameter for return value of stored function call.", SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } } } protected final void setInternal(int paramIndex, String val) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { byte[] parameterAsBytes = null; if (this.charConverter != null) { parameterAsBytes = this.charConverter.toBytes(val); } else { parameterAsBytes = StringUtils.getBytes(val, this.charConverter, this.charEncoding, this.connection .getServerCharacterEncoding(), this.connection .parserKnowsUnicode(), getExceptionInterceptor()); } setInternal(paramIndex, parameterAsBytes); } } /** * Set a parameter to a Java long value. The driver converts this to a SQL * BIGINT value when it sends it to the database. * * @param parameterIndex * the first parameter is 1... * @param x * the parameter value * * @exception SQLException * if a database access error occurs */ public void setLong(int parameterIndex, long x) throws SQLException { setInternal(parameterIndex, String.valueOf(x)); this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.BIGINT; } /** * Set a parameter to SQL NULL * * <p> * <B>Note:</B> You must specify the parameters SQL type (although MySQL * ignores it) * </p> * * @param parameterIndex * the first parameter is 1, etc... * @param sqlType * the SQL type code defined in java.sql.Types * * @exception SQLException * if a database access error occurs */ public void setNull(int parameterIndex, int sqlType) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { setInternal(parameterIndex, "null"); //$NON-NLS-1$ this.isNull[parameterIndex - 1 + getParameterIndexOffset()] = true; this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.NULL; } } /** * Set a parameter to SQL NULL. * * <P> * <B>Note:</B> You must specify the parameter's SQL type. * </p> * * @param parameterIndex * the first parameter is 1, the second is 2, ... * @param sqlType * SQL type code defined by java.sql.Types * @param arg * argument parameters for null * * @exception SQLException * if a database-access error occurs. */ public void setNull(int parameterIndex, int sqlType, String arg) throws SQLException { setNull(parameterIndex, sqlType); this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.NULL; } private void setNumericObject(int parameterIndex, Object parameterObj, int targetSqlType, int scale) throws SQLException { Number parameterAsNum; if (parameterObj instanceof Boolean) { parameterAsNum = ((Boolean) parameterObj) .booleanValue() ? Integer.valueOf(1) : Integer.valueOf( 0); } else if (parameterObj instanceof String) { switch (targetSqlType) { case Types.BIT: if ("1".equals(parameterObj) || "0".equals(parameterObj)) { parameterAsNum = Integer.valueOf((String) parameterObj); } else { boolean parameterAsBoolean = "true" .equalsIgnoreCase((String) parameterObj); parameterAsNum = parameterAsBoolean ? Integer.valueOf(1) : Integer.valueOf(0); } break; case Types.TINYINT: case Types.SMALLINT: case Types.INTEGER: parameterAsNum = Integer .valueOf((String) parameterObj); break; case Types.BIGINT: parameterAsNum = Long .valueOf((String) parameterObj); break; case Types.REAL: parameterAsNum = Float .valueOf((String) parameterObj); break; case Types.FLOAT: case Types.DOUBLE: parameterAsNum = Double .valueOf((String) parameterObj); break; case Types.DECIMAL: case Types.NUMERIC: default: parameterAsNum = new java.math.BigDecimal( (String) parameterObj); } } else { parameterAsNum = (Number) parameterObj; } switch (targetSqlType) { case Types.BIT: case Types.TINYINT: case Types.SMALLINT: case Types.INTEGER: setInt(parameterIndex, parameterAsNum.intValue()); break; case Types.BIGINT: setLong(parameterIndex, parameterAsNum.longValue()); break; case Types.REAL: setFloat(parameterIndex, parameterAsNum.floatValue()); break; case Types.FLOAT: case Types.DOUBLE: setDouble(parameterIndex, parameterAsNum.doubleValue()); break; case Types.DECIMAL: case Types.NUMERIC: if (parameterAsNum instanceof java.math.BigDecimal) { BigDecimal scaledBigDecimal = null; try { scaledBigDecimal = ((java.math.BigDecimal) parameterAsNum) .setScale(scale); } catch (ArithmeticException ex) { try { scaledBigDecimal = ((java.math.BigDecimal) parameterAsNum) .setScale(scale, BigDecimal.ROUND_HALF_UP); } catch (ArithmeticException arEx) { throw SQLError.createSQLException( "Can't set scale of '" + scale + "' for DECIMAL argument '" + parameterAsNum + "'", SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } } setBigDecimal(parameterIndex, scaledBigDecimal); } else if (parameterAsNum instanceof java.math.BigInteger) { setBigDecimal( parameterIndex, new java.math.BigDecimal( (java.math.BigInteger) parameterAsNum, scale)); } else { setBigDecimal(parameterIndex, new java.math.BigDecimal(parameterAsNum .doubleValue())); } break; } } public void setObject(int parameterIndex, Object parameterObj) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (parameterObj == null) { setNull(parameterIndex, java.sql.Types.OTHER); } else { if (parameterObj instanceof Byte) { setInt(parameterIndex, ((Byte) parameterObj).intValue()); } else if (parameterObj instanceof String) { setString(parameterIndex, (String) parameterObj); } else if (parameterObj instanceof BigDecimal) { setBigDecimal(parameterIndex, (BigDecimal) parameterObj); } else if (parameterObj instanceof Short) { setShort(parameterIndex, ((Short) parameterObj).shortValue()); } else if (parameterObj instanceof Integer) { setInt(parameterIndex, ((Integer) parameterObj).intValue()); } else if (parameterObj instanceof Long) { setLong(parameterIndex, ((Long) parameterObj).longValue()); } else if (parameterObj instanceof Float) { setFloat(parameterIndex, ((Float) parameterObj).floatValue()); } else if (parameterObj instanceof Double) { setDouble(parameterIndex, ((Double) parameterObj).doubleValue()); } else if (parameterObj instanceof byte[]) { setBytes(parameterIndex, (byte[]) parameterObj); } else if (parameterObj instanceof java.sql.Date) { setDate(parameterIndex, (java.sql.Date) parameterObj); } else if (parameterObj instanceof Time) { setTime(parameterIndex, (Time) parameterObj); } else if (parameterObj instanceof Timestamp) { setTimestamp(parameterIndex, (Timestamp) parameterObj); } else if (parameterObj instanceof Boolean) { setBoolean(parameterIndex, ((Boolean) parameterObj) .booleanValue()); } else if (parameterObj instanceof InputStream) { setBinaryStream(parameterIndex, (InputStream) parameterObj, -1); } else if (parameterObj instanceof java.sql.Blob) { setBlob(parameterIndex, (java.sql.Blob) parameterObj); } else if (parameterObj instanceof java.sql.Clob) { setClob(parameterIndex, (java.sql.Clob) parameterObj); } else if (this.connection.getTreatUtilDateAsTimestamp() && parameterObj instanceof java.util.Date) { setTimestamp(parameterIndex, new Timestamp( ((java.util.Date) parameterObj).getTime())); } else if (parameterObj instanceof BigInteger) { setString(parameterIndex, parameterObj.toString()); } else { setSerializableObject(parameterIndex, parameterObj); } } } } /** * DOCUMENT ME! * * @param parameterIndex * DOCUMENT ME! * @param parameterObj * DOCUMENT ME! * @param targetSqlType * DOCUMENT ME! * * @throws SQLException * DOCUMENT ME! */ public void setObject(int parameterIndex, Object parameterObj, int targetSqlType) throws SQLException { if (!(parameterObj instanceof BigDecimal)) { setObject(parameterIndex, parameterObj, targetSqlType, 0); } else { setObject(parameterIndex, parameterObj, targetSqlType, ((BigDecimal)parameterObj).scale()); } } /** * Set the value of a parameter using an object; use the java.lang * equivalent objects for integral values. * * <P> * The given Java object will be converted to the targetSqlType before being * sent to the database. * </p> * * <P> * note that this method may be used to pass database-specific abstract data * types. This is done by using a Driver-specific Java type and using a * targetSqlType of java.sql.Types.OTHER * </p> * * @param parameterIndex * the first parameter is 1... * @param parameterObj * the object containing the input parameter value * @param targetSqlType * The SQL type to be send to the database * @param scale * For java.sql.Types.DECIMAL or java.sql.Types.NUMERIC types * this is the number of digits after the decimal. For all other * types this value will be ignored. * * @throws SQLException * if a database access error occurs */ public void setObject(int parameterIndex, Object parameterObj, int targetSqlType, int scale) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (parameterObj == null) { setNull(parameterIndex, java.sql.Types.OTHER); } else { try { switch (targetSqlType) { case Types.BOOLEAN: /* From Table-B5 in the JDBC-3.0 Spec T S I B R F D D N B B C V L I M N I E L O E U I O H A O N A T G A O U C M T O A R N Y L E I L A B I E L R C G I L G N T L M R E H V N I E T E A I A A A T N R L C N R R T C H A R ----------------------------------- Boolean x x x x x x x x x x x x x x */ if (parameterObj instanceof Boolean) { setBoolean(parameterIndex, ((Boolean)parameterObj).booleanValue()); break; } else if (parameterObj instanceof String) { setBoolean(parameterIndex, "true".equalsIgnoreCase((String)parameterObj) || !"0".equalsIgnoreCase((String)parameterObj)); break; } else if (parameterObj instanceof Number) { int intValue = ((Number)parameterObj).intValue(); setBoolean(parameterIndex, intValue != 0); break; } else { throw SQLError.createSQLException("No conversion from " + parameterObj.getClass().getName() + " to Types.BOOLEAN possible.", SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); } case Types.BIT: case Types.TINYINT: case Types.SMALLINT: case Types.INTEGER: case Types.BIGINT: case Types.REAL: case Types.FLOAT: case Types.DOUBLE: case Types.DECIMAL: case Types.NUMERIC: setNumericObject(parameterIndex, parameterObj, targetSqlType, scale); break; case Types.CHAR: case Types.VARCHAR: case Types.LONGVARCHAR: if (parameterObj instanceof BigDecimal) { setString( parameterIndex, (StringUtils .fixDecimalExponent(StringUtils .consistentToString((BigDecimal) parameterObj)))); } else { setString(parameterIndex, parameterObj.toString()); } break; case Types.CLOB: if (parameterObj instanceof java.sql.Clob) { setClob(parameterIndex, (java.sql.Clob) parameterObj); } else { setString(parameterIndex, parameterObj.toString()); } break; case Types.BINARY: case Types.VARBINARY: case Types.LONGVARBINARY: case Types.BLOB: if (parameterObj instanceof byte[]) { setBytes(parameterIndex, (byte[]) parameterObj); } else if (parameterObj instanceof java.sql.Blob) { setBlob(parameterIndex, (java.sql.Blob) parameterObj); } else { setBytes(parameterIndex, StringUtils.getBytes( parameterObj.toString(), this.charConverter, this.charEncoding, this.connection .getServerCharacterEncoding(), this.connection.parserKnowsUnicode(), getExceptionInterceptor())); } break; case Types.DATE: case Types.TIMESTAMP: java.util.Date parameterAsDate; if (parameterObj instanceof String) { ParsePosition pp = new ParsePosition(0); java.text.DateFormat sdf = new java.text.SimpleDateFormat( getDateTimePattern((String) parameterObj, false), Locale.US); parameterAsDate = sdf.parse((String) parameterObj, pp); } else { parameterAsDate = (java.util.Date) parameterObj; } switch (targetSqlType) { case Types.DATE: if (parameterAsDate instanceof java.sql.Date) { setDate(parameterIndex, (java.sql.Date) parameterAsDate); } else { setDate(parameterIndex, new java.sql.Date( parameterAsDate.getTime())); } break; case Types.TIMESTAMP: if (parameterAsDate instanceof java.sql.Timestamp) { setTimestamp(parameterIndex, (java.sql.Timestamp) parameterAsDate); } else { setTimestamp(parameterIndex, new java.sql.Timestamp(parameterAsDate .getTime())); } break; } break; case Types.TIME: if (parameterObj instanceof String) { java.text.DateFormat sdf = new java.text.SimpleDateFormat( getDateTimePattern((String) parameterObj, true), Locale.US); setTime(parameterIndex, new java.sql.Time(sdf.parse( (String) parameterObj).getTime())); } else if (parameterObj instanceof Timestamp) { Timestamp xT = (Timestamp) parameterObj; setTime(parameterIndex, new java.sql.Time(xT.getTime())); } else { setTime(parameterIndex, (java.sql.Time) parameterObj); } break; case Types.OTHER: setSerializableObject(parameterIndex, parameterObj); break; default: throw SQLError.createSQLException(Messages .getString("PreparedStatement.16"), //$NON-NLS-1$ SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); } } catch (Exception ex) { if (ex instanceof SQLException) { throw (SQLException) ex; } SQLException sqlEx = SQLError.createSQLException( Messages.getString("PreparedStatement.17") //$NON-NLS-1$ + parameterObj.getClass().toString() + Messages.getString("PreparedStatement.18") //$NON-NLS-1$ + ex.getClass().getName() + Messages.getString("PreparedStatement.19") + ex.getMessage(), //$NON-NLS-1$ SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); sqlEx.initCause(ex); throw sqlEx; } } } } protected int setOneBatchedParameterSet( java.sql.PreparedStatement batchedStatement, int batchedParamIndex, Object paramSet) throws SQLException { BatchParams paramArg = (BatchParams)paramSet; boolean[] isNullBatch = paramArg.isNull; boolean[] isStreamBatch = paramArg.isStream; for (int j = 0; j < isNullBatch.length; j++) { if (isNullBatch[j]) { batchedStatement.setNull(batchedParamIndex++, Types.NULL); } else { if (isStreamBatch[j]) { batchedStatement.setBinaryStream(batchedParamIndex++, paramArg.parameterStreams[j], paramArg.streamLengths[j]); } else { ((com.mysql.jdbc.PreparedStatement) batchedStatement) .setBytesNoEscapeNoQuotes(batchedParamIndex++, paramArg.parameterStrings[j]); } } } return batchedParamIndex; } /** * JDBC 2.0 Set a REF(<structured-type>) parameter. * * @param i * the first parameter is 1, the second is 2, ... * @param x * an object representing data of an SQL REF Type * * @throws SQLException * if a database error occurs * @throws NotImplemented * DOCUMENT ME! */ public void setRef(int i, Ref x) throws SQLException { throw SQLError.notImplemented(); } /** * Sets the value for the placeholder as a serialized Java object (used by * various forms of setObject() * * @param parameterIndex * DOCUMENT ME! * @param parameterObj * DOCUMENT ME! * * @throws SQLException * DOCUMENT ME! */ private final void setSerializableObject(int parameterIndex, Object parameterObj) throws SQLException { try { ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); ObjectOutputStream objectOut = new ObjectOutputStream(bytesOut); objectOut.writeObject(parameterObj); objectOut.flush(); objectOut.close(); bytesOut.flush(); bytesOut.close(); byte[] buf = bytesOut.toByteArray(); ByteArrayInputStream bytesIn = new ByteArrayInputStream(buf); setBinaryStream(parameterIndex, bytesIn, buf.length); this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.BINARY; } catch (Exception ex) { SQLException sqlEx = SQLError.createSQLException(Messages.getString("PreparedStatement.54") //$NON-NLS-1$ + ex.getClass().getName(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); sqlEx.initCause(ex); throw sqlEx; } } /** * Set a parameter to a Java short value. The driver converts this to a SQL * SMALLINT value when it sends it to the database. * * @param parameterIndex * the first parameter is 1... * @param x * the parameter value * * @exception SQLException * if a database access error occurs */ public void setShort(int parameterIndex, short x) throws SQLException { setInternal(parameterIndex, String.valueOf(x)); this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.SMALLINT; } /** * Set a parameter to a Java String value. The driver converts this to a SQL * VARCHAR or LONGVARCHAR value (depending on the arguments size relative to * the driver's limits on VARCHARs) when it sends it to the database. * * @param parameterIndex * the first parameter is 1... * @param x * the parameter value * * @exception SQLException * if a database access error occurs */ public void setString(int parameterIndex, String x) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { // if the passed string is null, then set this column to null if (x == null) { setNull(parameterIndex, Types.CHAR); } else { checkClosed(); int stringLength = x.length(); if (this.connection.isNoBackslashEscapesSet()) { // Scan for any nasty chars boolean needsHexEscape = isEscapeNeededForString(x, stringLength); if (!needsHexEscape) { byte[] parameterAsBytes = null; StringBuffer quotedString = new StringBuffer(x.length() + 2); quotedString.append('\''); quotedString.append(x); quotedString.append('\''); if (!this.isLoadDataQuery) { parameterAsBytes = StringUtils.getBytes(quotedString.toString(), this.charConverter, this.charEncoding, this.connection.getServerCharacterEncoding(), this.connection.parserKnowsUnicode(), getExceptionInterceptor()); } else { // Send with platform character encoding parameterAsBytes = StringUtils.getBytes(quotedString.toString()); } setInternal(parameterIndex, parameterAsBytes); } else { byte[] parameterAsBytes = null; if (!this.isLoadDataQuery) { parameterAsBytes = StringUtils.getBytes(x, this.charConverter, this.charEncoding, this.connection.getServerCharacterEncoding(), this.connection.parserKnowsUnicode(), getExceptionInterceptor()); } else { // Send with platform character encoding parameterAsBytes = StringUtils.getBytes(x); } setBytes(parameterIndex, parameterAsBytes); } return; } String parameterAsString = x; boolean needsQuoted = true; if (this.isLoadDataQuery || isEscapeNeededForString(x, stringLength)) { needsQuoted = false; // saves an allocation later StringBuffer buf = new StringBuffer((int) (x.length() * 1.1)); buf.append('\''); // // Note: buf.append(char) is _faster_ than // appending in blocks, because the block // append requires a System.arraycopy().... // go figure... // for (int i = 0; i < stringLength; ++i) { char c = x.charAt(i); switch (c) { case 0: /* Must be escaped for 'mysql' */ buf.append('\\'); buf.append('0'); break; case '\n': /* Must be escaped for logs */ buf.append('\\'); buf.append('n'); break; case '\r': buf.append('\\'); buf.append('r'); break; case '\\': buf.append('\\'); buf.append('\\'); break; case '\'': buf.append('\\'); buf.append('\''); break; case '"': /* Better safe than sorry */ if (this.usingAnsiMode) { buf.append('\\'); } buf.append('"'); break; case '\032': /* This gives problems on Win32 */ buf.append('\\'); buf.append('Z'); break; case '\u00a5': case '\u20a9': // escape characters interpreted as backslash by mysql if(charsetEncoder != null) { CharBuffer cbuf = CharBuffer.allocate(1); ByteBuffer bbuf = ByteBuffer.allocate(1); cbuf.put(c); cbuf.position(0); charsetEncoder.encode(cbuf, bbuf, true); if(bbuf.get(0) == '\\') { buf.append('\\'); } } // fall through default: buf.append(c); } } buf.append('\''); parameterAsString = buf.toString(); } byte[] parameterAsBytes = null; if (!this.isLoadDataQuery) { if (needsQuoted) { parameterAsBytes = StringUtils.getBytesWrapped(parameterAsString, '\'', '\'', this.charConverter, this.charEncoding, this.connection .getServerCharacterEncoding(), this.connection .parserKnowsUnicode(), getExceptionInterceptor()); } else { parameterAsBytes = StringUtils.getBytes(parameterAsString, this.charConverter, this.charEncoding, this.connection .getServerCharacterEncoding(), this.connection .parserKnowsUnicode(), getExceptionInterceptor()); } } else { // Send with platform character encoding parameterAsBytes = StringUtils.getBytes(parameterAsString); } setInternal(parameterIndex, parameterAsBytes); this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.VARCHAR; } } } private boolean isEscapeNeededForString(String x, int stringLength) { boolean needsHexEscape = false; for (int i = 0; i < stringLength; ++i) { char c = x.charAt(i); switch (c) { case 0: /* Must be escaped for 'mysql' */ needsHexEscape = true; break; case '\n': /* Must be escaped for logs */ needsHexEscape = true; break; case '\r': needsHexEscape = true; break; case '\\': needsHexEscape = true; break; case '\'': needsHexEscape = true; break; case '"': /* Better safe than sorry */ needsHexEscape = true; break; case '\032': /* This gives problems on Win32 */ needsHexEscape = true; break; } if (needsHexEscape) { break; // no need to scan more } } return needsHexEscape; } /** * 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. * * @param parameterIndex * the first parameter is 1, the second is 2, ... * @param x * the parameter value * @param cal * the cal specifying the timezone * * @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. * * @param parameterIndex * the first parameter is 1...)); * @param x * the parameter value * * @throws java.sql.SQLException * if a database access error occurs */ public void setTime(int parameterIndex, Time x) throws java.sql.SQLException { setTimeInternal(parameterIndex, x, null, Util.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 tz * the timezone to use * * @throws java.sql.SQLException * if a database access error occurs */ private void setTimeInternal(int parameterIndex, Time x, Calendar targetCalendar, TimeZone tz, boolean rollForward) throws java.sql.SQLException { synchronized (checkClosed().getConnectionMutex()) { if (x == null) { setNull(parameterIndex, java.sql.Types.TIME); } else { checkClosed(); if (!this.useLegacyDatetimeCode) { newSetTimeInternal(parameterIndex, x, targetCalendar); } else { Calendar sessionCalendar = getCalendarInstanceForSessionOrNew(); synchronized (sessionCalendar) { x = TimeUtil.changeTimezone(this.connection, sessionCalendar, targetCalendar, x, tz, this.connection .getServerTimezoneTZ(), rollForward); } setInternal(parameterIndex, "'" + x.toString() + "'"); //$NON-NLS-1$ //$NON-NLS-2$ } this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.TIME; } } } /** * 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 calendar specifying 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 { setTimestampInternal(parameterIndex, x, cal, cal.getTimeZone(), true); } /** * 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... * @param x * the parameter value * * @throws java.sql.SQLException * if a database access error occurs */ public void setTimestamp(int parameterIndex, Timestamp x) throws java.sql.SQLException { setTimestampInternal(parameterIndex, x, null, Util.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 tz * the timezone to use * * @throws SQLException * if a database-access error occurs. */ private void setTimestampInternal(int parameterIndex, Timestamp x, Calendar targetCalendar, TimeZone tz, boolean rollForward) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (x == null) { setNull(parameterIndex, java.sql.Types.TIMESTAMP); } else { checkClosed(); if (!this.useLegacyDatetimeCode) { newSetTimestampInternal(parameterIndex, x, targetCalendar); } else { Calendar sessionCalendar = this.connection.getUseJDBCCompliantTimezoneShift() ? this.connection.getUtcCalendar() : getCalendarInstanceForSessionOrNew(); synchronized (sessionCalendar) { x = TimeUtil.changeTimezone(this.connection, sessionCalendar, targetCalendar, x, tz, this.connection .getServerTimezoneTZ(), rollForward); } if (this.connection.getUseSSPSCompatibleTimezoneShift()) { doSSPSCompatibleTimezoneShift(parameterIndex, x, sessionCalendar); } else { synchronized (this) { if (this.tsdf == null) { this.tsdf = new SimpleDateFormat("''yyyy-MM-dd HH:mm:ss", Locale.US); //$NON-NLS-1$ } StringBuffer buf = new StringBuffer(); buf.append(this.tsdf.format(x)); if (this.serverSupportsFracSecs) { int nanos = x.getNanos(); if (nanos != 0) { buf.append('.'); buf.append(TimeUtil.formatNanos(nanos, this.serverSupportsFracSecs)); } } buf.append('\''); setInternal(parameterIndex, buf.toString()); // SimpleDateFormat is not // thread-safe } } } this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.TIMESTAMP; } } } private void newSetTimestampInternal(int parameterIndex, Timestamp x, Calendar targetCalendar) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.tsdf == null) { this.tsdf = new SimpleDateFormat("''yyyy-MM-dd HH:mm:ss", Locale.US); //$NON-NLS-1$ } String timestampString = null; if (targetCalendar != null) { targetCalendar.setTime(x); this.tsdf.setTimeZone(targetCalendar.getTimeZone()); timestampString = this.tsdf.format(x); } else { this.tsdf.setTimeZone(this.connection.getServerTimezoneTZ()); timestampString = this.tsdf.format(x); } StringBuffer buf = new StringBuffer(); buf.append(timestampString); buf.append('.'); buf.append(TimeUtil.formatNanos(x.getNanos(), this.serverSupportsFracSecs)); buf.append('\''); setInternal(parameterIndex, buf.toString()); } } private void newSetTimeInternal(int parameterIndex, Time x, Calendar targetCalendar) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.tdf == null) { this.tdf = new SimpleDateFormat("''HH:mm:ss''", Locale.US); //$NON-NLS-1$ } String timeString = null; if (targetCalendar != null) { targetCalendar.setTime(x); this.tdf.setTimeZone(targetCalendar.getTimeZone()); timeString = this.tdf.format(x); } else { this.tdf.setTimeZone(this.connection.getServerTimezoneTZ()); timeString = this.tdf.format(x); } setInternal(parameterIndex, timeString); } } private void newSetDateInternal(int parameterIndex, Date x, Calendar targetCalendar) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.ddf == null) { this.ddf = new SimpleDateFormat("''yyyy-MM-dd''", Locale.US); //$NON-NLS-1$ } String timeString = null; if (targetCalendar != null) { targetCalendar.setTime(x); this.ddf.setTimeZone(targetCalendar.getTimeZone()); timeString = this.ddf.format(x); } else { this.ddf.setTimeZone(this.connection.getServerTimezoneTZ()); timeString = this.ddf.format(x); } setInternal(parameterIndex, timeString); } } private void doSSPSCompatibleTimezoneShift(int parameterIndex, Timestamp x, Calendar sessionCalendar) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { Calendar sessionCalendar2 = (this.connection .getUseJDBCCompliantTimezoneShift()) ? this.connection .getUtcCalendar() : getCalendarInstanceForSessionOrNew(); synchronized (sessionCalendar2) { java.util.Date oldTime = sessionCalendar2.getTime(); try { sessionCalendar2.setTime(x); int year = sessionCalendar2.get(Calendar.YEAR); int month = sessionCalendar2.get(Calendar.MONTH) + 1; int date = sessionCalendar2.get(Calendar.DAY_OF_MONTH); int hour = sessionCalendar2.get(Calendar.HOUR_OF_DAY); int minute = sessionCalendar2.get(Calendar.MINUTE); int seconds = sessionCalendar2.get(Calendar.SECOND); StringBuffer tsBuf = new StringBuffer(); tsBuf.append('\''); tsBuf.append(year); tsBuf.append("-"); if (month < 10) { tsBuf.append('0'); } tsBuf.append(month); tsBuf.append('-'); if (date < 10) { tsBuf.append('0'); } tsBuf.append(date); tsBuf.append(' '); if (hour < 10) { tsBuf.append('0'); } tsBuf.append(hour); tsBuf.append(':'); if (minute < 10) { tsBuf.append('0'); } tsBuf.append(minute); tsBuf.append(':'); if (seconds < 10) { tsBuf.append('0'); } tsBuf.append(seconds); tsBuf.append('.'); tsBuf.append(TimeUtil.formatNanos(x.getNanos(), this.serverSupportsFracSecs)); tsBuf.append('\''); setInternal(parameterIndex, tsBuf.toString()); } finally { sessionCalendar.setTime(oldTime); } } } } /** * When a very large Unicode value is input to a LONGVARCHAR parameter, it * may be more practical to send it via a java.io.InputStream. JDBC will * read the data from the stream as needed, until it reaches end-of-file. * The JDBC driver will do any necessary conversion from UNICODE to the * database char format. * * <P> * <B>Note:</B> This stream object can either be a standard Java stream * object or your own subclass that implements the standard interface. * </p> * * @param parameterIndex * the first parameter is 1... * @param x * the parameter value * @param length * the number of bytes to read from the stream * * @throws SQLException * if a database access error occurs * * @deprecated */ public void setUnicodeStream(int parameterIndex, InputStream x, int length) throws SQLException { if (x == null) { setNull(parameterIndex, java.sql.Types.VARCHAR); } else { setBinaryStream(parameterIndex, x, length); this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.CLOB; } } /** * @see PreparedStatement#setURL(int, URL) */ public void setURL(int parameterIndex, URL arg) throws SQLException { if (arg != null) { setString(parameterIndex, arg.toString()); this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.DATALINK; } else { setNull(parameterIndex, Types.CHAR); } } private final void streamToBytes(Buffer packet, InputStream in, boolean escape, int streamLength, boolean useLength) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { try { if (streamConvertBuf == null) { streamConvertBuf = new byte[4096]; } String connectionEncoding = this.connection.getEncoding(); boolean hexEscape = false; try { if (this.connection.isNoBackslashEscapesSet() || (this.connection.getUseUnicode() && connectionEncoding != null && CharsetMapping.isMultibyteCharset(connectionEncoding) && !this.connection.parserKnowsUnicode())) { hexEscape = true; } } catch (RuntimeException ex) { SQLException sqlEx = SQLError.createSQLException(ex.toString(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null); sqlEx.initCause(ex); throw sqlEx; } if (streamLength == -1) { useLength = false; } int bc = -1; if (useLength) { bc = readblock(in, streamConvertBuf, streamLength); } else { bc = readblock(in, streamConvertBuf); } int lengthLeftToRead = streamLength - bc; if (hexEscape) { packet.writeStringNoNull("x"); } else if (this.connection.getIO().versionMeetsMinimum(4, 1, 0)) { packet.writeStringNoNull("_binary"); } if (escape) { packet.writeByte((byte) '\''); } while (bc > 0) { if (hexEscape) { hexEscapeBlock(streamConvertBuf, packet, bc); } else if (escape) { escapeblockFast(streamConvertBuf, packet, bc); } else { packet.writeBytesNoNull(streamConvertBuf, 0, bc); } if (useLength) { bc = readblock(in, streamConvertBuf, lengthLeftToRead); if (bc > 0) { lengthLeftToRead -= bc; } } else { bc = readblock(in, streamConvertBuf); } } if (escape) { packet.writeByte((byte) '\''); } } finally { if (this.connection.getAutoClosePStmtStreams()) { try { in.close(); } catch (IOException ioEx) { ; } in = null; } } } } private final byte[] streamToBytes(InputStream in, boolean escape, int streamLength, boolean useLength) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { try { if (streamConvertBuf == null) { streamConvertBuf = new byte[4096]; } if (streamLength == -1) { useLength = false; } ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); int bc = -1; if (useLength) { bc = readblock(in, this.streamConvertBuf, streamLength); } else { bc = readblock(in, this.streamConvertBuf); } int lengthLeftToRead = streamLength - bc; if (escape) { if (this.connection.versionMeetsMinimum(4, 1, 0)) { bytesOut.write('_'); bytesOut.write('b'); bytesOut.write('i'); bytesOut.write('n'); bytesOut.write('a'); bytesOut.write('r'); bytesOut.write('y'); } bytesOut.write('\''); } while (bc > 0) { if (escape) { escapeblockFast(this.streamConvertBuf, bytesOut, bc); } else { bytesOut.write(this.streamConvertBuf, 0, bc); } if (useLength) { bc = readblock(in, this.streamConvertBuf, lengthLeftToRead); if (bc > 0) { lengthLeftToRead -= bc; } } else { bc = readblock(in, this.streamConvertBuf); } } if (escape) { bytesOut.write('\''); } return bytesOut.toByteArray(); } finally { if (this.connection.getAutoClosePStmtStreams()) { try { in.close(); } catch (IOException ioEx) { ; } in = null; } } } } /** * Returns this PreparedStatement represented as a string. * * @return this PreparedStatement represented as a string. */ public String toString() { StringBuffer buf = new StringBuffer(); buf.append(super.toString()); buf.append(": "); //$NON-NLS-1$ try { buf.append(asSql()); } catch (SQLException sqlEx) { buf.append("EXCEPTION: " + sqlEx.toString()); } return buf.toString(); } /** * For calling stored functions, this will be -1 as we don't really count * the first '?' parameter marker, it's only syntax, but JDBC counts it * as #1, otherwise it will return 0 * */ protected int getParameterIndexOffset() { return 0; } public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException { setAsciiStream(parameterIndex, x, -1); } public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException { setAsciiStream(parameterIndex, x, (int)length); this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.CLOB; } public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException { setBinaryStream(parameterIndex, x, -1); } public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException { setBinaryStream(parameterIndex, x, (int)length); } public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException { setBinaryStream(parameterIndex, inputStream); } public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException { setCharacterStream(parameterIndex, reader, -1); } public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException { setCharacterStream(parameterIndex, reader, (int)length); } public void setClob(int parameterIndex, Reader reader) throws SQLException { setCharacterStream(parameterIndex, reader); } public void setClob(int parameterIndex, Reader reader, long length) throws SQLException { setCharacterStream(parameterIndex, reader, length); } public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException { setNCharacterStream(parameterIndex, value, -1); } /** * Set a parameter to a Java String value. The driver converts this to a SQL * VARCHAR or LONGVARCHAR value with introducer _utf8 (depending on the * arguments size relative to the driver's limits on VARCHARs) when it sends * it to the database. If charset is set as utf8, this method just call setString. * * @param parameterIndex * the first parameter is 1... * @param x * the parameter value * * @exception SQLException * if a database access error occurs */ public void setNString(int parameterIndex, String x) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { if (this.charEncoding.equalsIgnoreCase("UTF-8") || this.charEncoding.equalsIgnoreCase("utf8")) { setString(parameterIndex, x); return; } // if the passed string is null, then set this column to null if (x == null) { setNull(parameterIndex, java.sql.Types.CHAR); } else { int stringLength = x.length(); // Ignore sql_mode=NO_BACKSLASH_ESCAPES in current implementation. // Add introducer _utf8 for NATIONAL CHARACTER StringBuffer buf = new StringBuffer((int) (x.length() * 1.1 + 4)); buf.append("_utf8"); buf.append('\''); // // Note: buf.append(char) is _faster_ than // appending in blocks, because the block // append requires a System.arraycopy().... // go figure... // for (int i = 0; i < stringLength; ++i) { char c = x.charAt(i); switch (c) { case 0: /* Must be escaped for 'mysql' */ buf.append('\\'); buf.append('0'); break; case '\n': /* Must be escaped for logs */ buf.append('\\'); buf.append('n'); break; case '\r': buf.append('\\'); buf.append('r'); break; case '\\': buf.append('\\'); buf.append('\\'); break; case '\'': buf.append('\\'); buf.append('\''); break; case '"': /* Better safe than sorry */ if (this.usingAnsiMode) { buf.append('\\'); } buf.append('"'); break; case '\032': /* This gives problems on Win32 */ buf.append('\\'); buf.append('Z'); break; default: buf.append(c); } } buf.append('\''); String parameterAsString = buf.toString(); byte[] parameterAsBytes = null; if (!this.isLoadDataQuery) { parameterAsBytes = StringUtils.getBytes(parameterAsString, this.connection.getCharsetConverter("UTF-8"), "UTF-8", this.connection.getServerCharacterEncoding(), this.connection.parserKnowsUnicode(), getExceptionInterceptor()); } else { // Send with platform character encoding parameterAsBytes = StringUtils.getBytes(parameterAsString); } setInternal(parameterIndex, parameterAsBytes); this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = -9; /* Types.NVARCHAR */ } } } /** * JDBC 2.0 When a very large UNICODE value is input to a LONGVARCHAR * parameter, it may be more practical to send it via a java.io.Reader. JDBC * will read the data from the stream as needed, until it reaches * end-of-file. The JDBC driver will do any necessary conversion from * UNICODE to the database char format. * * <P> * <B>Note:</B> This stream object can either be a standard Java stream * object or your own subclass that implements the standard interface. * </p> * * @param parameterIndex * the first parameter is 1, the second is 2, ... * @param reader * the java reader which contains the UNICODE data * @param length * the number of characters in the stream * * @exception SQLException * if a database-access error occurs. */ public void setNCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException { synchronized (checkClosed().getConnectionMutex()) { try { if (reader == null) { setNull(parameterIndex, java.sql.Types.LONGVARCHAR); } else { char[] c = null; int len = 0; boolean useLength = this.connection .getUseStreamLengthsInPrepStmts(); // Ignore "clobCharacterEncoding" because utf8 should be used this time. if (useLength && (length != -1)) { c = new char[(int) length]; // can't take more than Integer.MAX_VALUE int numCharsRead = readFully(reader, c, (int) length); // blocks // until // all // read setNString(parameterIndex, new String(c, 0, numCharsRead)); } else { c = new char[4096]; StringBuffer buf = new StringBuffer(); while ((len = reader.read(c)) != -1) { buf.append(c, 0, len); } setNString(parameterIndex, buf.toString()); } this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = 2011; /* Types.NCLOB */ } } catch (java.io.IOException ioEx) { throw SQLError.createSQLException(ioEx.toString(), SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor()); } } } public void setNClob(int parameterIndex, Reader reader) throws SQLException { setNCharacterStream(parameterIndex, reader); } /** * JDBC 4.0 Set a NCLOB parameter. * * @param parameterIndex * the first parameter is 1, the second is 2, ... * @param reader * the java reader which contains the UNICODE data * @param length * the number of characters in the stream * * @throws SQLException * if a database error occurs */ public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException { if (reader == null) { setNull(parameterIndex, java.sql.Types.LONGVARCHAR); } else { setNCharacterStream(parameterIndex, reader, length); } } public ParameterBindings getParameterBindings() throws SQLException { synchronized (checkClosed().getConnectionMutex()) { return new EmulatedPreparedStatementBindings(); } } class EmulatedPreparedStatementBindings implements ParameterBindings { private ResultSetImpl bindingsAsRs; private boolean[] parameterIsNull; EmulatedPreparedStatementBindings() throws SQLException { List<ResultSetRow> rows = new ArrayList<ResultSetRow>(); parameterIsNull = new boolean[parameterCount]; System .arraycopy(isNull, 0, this.parameterIsNull, 0, parameterCount); byte[][] rowData = new byte[parameterCount][]; Field[] typeMetadata = new Field[parameterCount]; for (int i = 0; i < parameterCount; i++) { if (batchCommandIndex == -1) rowData[i] = getBytesRepresentation(i); else rowData[i] = getBytesRepresentationForBatch(i, batchCommandIndex); int charsetIndex = 0; if (parameterTypes[i] == Types.BINARY || parameterTypes[i] == Types.BLOB) { charsetIndex = 63; } else { try { String mysqlEncodingName = CharsetMapping .getMysqlEncodingForJavaEncoding(connection .getEncoding(), connection); charsetIndex = CharsetMapping .getCharsetIndexForMysqlEncodingName(mysqlEncodingName); } catch (SQLException ex) { throw ex; } catch (RuntimeException ex) { SQLException sqlEx = SQLError.createSQLException(ex.toString(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, null); sqlEx.initCause(ex); throw sqlEx; } } Field parameterMetadata = new Field(null, "parameter_" + (i + 1), charsetIndex, parameterTypes[i], rowData[i].length); parameterMetadata.setConnection(connection); typeMetadata[i] = parameterMetadata; } rows.add(new ByteArrayRow(rowData, getExceptionInterceptor())); this.bindingsAsRs = new ResultSetImpl(connection.getCatalog(), typeMetadata, new RowDataStatic(rows), connection, null); this.bindingsAsRs.next(); } public Array getArray(int parameterIndex) throws SQLException { return this.bindingsAsRs.getArray(parameterIndex); } public InputStream getAsciiStream(int parameterIndex) throws SQLException { return this.bindingsAsRs.getAsciiStream(parameterIndex); } public BigDecimal getBigDecimal(int parameterIndex) throws SQLException { return this.bindingsAsRs.getBigDecimal(parameterIndex); } public InputStream getBinaryStream(int parameterIndex) throws SQLException { return this.bindingsAsRs.getBinaryStream(parameterIndex); } public java.sql.Blob getBlob(int parameterIndex) throws SQLException { return this.bindingsAsRs.getBlob(parameterIndex); } public boolean getBoolean(int parameterIndex) throws SQLException { return this.bindingsAsRs.getBoolean(parameterIndex); } public byte getByte(int parameterIndex) throws SQLException { return this.bindingsAsRs.getByte(parameterIndex); } public byte[] getBytes(int parameterIndex) throws SQLException { return this.bindingsAsRs.getBytes(parameterIndex); } public Reader getCharacterStream(int parameterIndex) throws SQLException { return this.bindingsAsRs.getCharacterStream(parameterIndex); } public java.sql.Clob getClob(int parameterIndex) throws SQLException { return this.bindingsAsRs.getClob(parameterIndex); } public Date getDate(int parameterIndex) throws SQLException { return this.bindingsAsRs.getDate(parameterIndex); } public double getDouble(int parameterIndex) throws SQLException { return this.bindingsAsRs.getDouble(parameterIndex); } public float getFloat(int parameterIndex) throws SQLException { return this.bindingsAsRs.getFloat(parameterIndex); } public int getInt(int parameterIndex) throws SQLException { return this.bindingsAsRs.getInt(parameterIndex); } public long getLong(int parameterIndex) throws SQLException { return this.bindingsAsRs.getLong(parameterIndex); } public Reader getNCharacterStream(int parameterIndex) throws SQLException { return this.bindingsAsRs.getCharacterStream(parameterIndex); } public Reader getNClob(int parameterIndex) throws SQLException { return this.bindingsAsRs.getCharacterStream(parameterIndex); } public Object getObject(int parameterIndex) throws SQLException { checkBounds(parameterIndex, 0); if (parameterIsNull[parameterIndex - 1]) { return null; } // we can't rely on the default mapping for JDBC's // ResultSet.getObject() for numerics, they're not one-to-one with // PreparedStatement.setObject switch (parameterTypes[parameterIndex - 1]) { case Types.TINYINT: return Byte.valueOf(getByte(parameterIndex)); case Types.SMALLINT: return Short.valueOf(getShort(parameterIndex)); case Types.INTEGER: return Integer.valueOf(getInt(parameterIndex)); case Types.BIGINT: return Long.valueOf(getLong(parameterIndex)); case Types.FLOAT: return Float.valueOf(getFloat(parameterIndex)); case Types.DOUBLE: return Double.valueOf(getDouble(parameterIndex)); default: return this.bindingsAsRs.getObject(parameterIndex); } } public Ref getRef(int parameterIndex) throws SQLException { return this.bindingsAsRs.getRef(parameterIndex); } public short getShort(int parameterIndex) throws SQLException { return this.bindingsAsRs.getShort(parameterIndex); } public String getString(int parameterIndex) throws SQLException { return this.bindingsAsRs.getString(parameterIndex); } public Time getTime(int parameterIndex) throws SQLException { return this.bindingsAsRs.getTime(parameterIndex); } public Timestamp getTimestamp(int parameterIndex) throws SQLException { return this.bindingsAsRs.getTimestamp(parameterIndex); } public URL getURL(int parameterIndex) throws SQLException { return this.bindingsAsRs.getURL(parameterIndex); } public boolean isNull(int parameterIndex) throws SQLException { checkBounds(parameterIndex, 0); return this.parameterIsNull[parameterIndex -1]; } } public String getPreparedSql() { try { synchronized (checkClosed().getConnectionMutex()) { if (this.rewrittenBatchSize == 0) { return this.originalSql; } try { return this.parseInfo.getSqlForBatch(this.parseInfo); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } } catch (SQLException e) { throw new RuntimeException(e); // FIXME: evolve public interface } } public int getUpdateCount() throws SQLException { int count = super.getUpdateCount(); if (containsOnDuplicateKeyUpdateInSQL() && this.compensateForOnDuplicateKeyUpdate) { if (count == 2 || count == 0) { count = 1; } } return count; } protected static boolean canRewrite(String sql, boolean isOnDuplicateKeyUpdate, int locationOfOnDuplicateKeyUpdate, int statementStartPos) { // Needs to be INSERT, can't have INSERT ... SELECT or // INSERT ... ON DUPLICATE KEY UPDATE with an id=LAST_INSERT_ID(...) boolean rewritableOdku = true; if (isOnDuplicateKeyUpdate) { int updateClausePos = StringUtils.indexOfIgnoreCase( locationOfOnDuplicateKeyUpdate, sql, " UPDATE "); if (updateClausePos != -1) { rewritableOdku = StringUtils .indexOfIgnoreCaseRespectMarker(updateClausePos, sql, "LAST_INSERT_ID", "\"'`", "\"'`", false) == -1; } } return StringUtils .startsWithIgnoreCaseAndWs(sql, "INSERT", statementStartPos) && StringUtils.indexOfIgnoreCaseRespectMarker( statementStartPos, sql, "SELECT", "\"'`", "\"'`", false) == -1 && rewritableOdku; } }