/* Copyright (C) 2002 MySQL AB 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; either version 2 of the License, or (at your option) any later version. 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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.math.BigDecimal; import java.net.URL; import java.sql.Array; import java.sql.Clob; 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.TimeZone; /** * 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.27.2.32 2004/01/12 19:48:26 mmatthew Exp $ * * @see java.sql.ResultSet * @see java.sql.PreparedStatement */ public class PreparedStatement extends com.mysql.jdbc.Statement implements java.sql.PreparedStatement { private java.sql.DatabaseMetaData dbmd = null; private ParseInfo parseInfo; private java.sql.ResultSetMetaData pstmtResultMetaData; private SimpleDateFormat tsdf = null; private String originalSql = null; private boolean[] isNull = null; private boolean[] isStream = null; private InputStream[] parameterStreams = null; private byte[][] parameterValues = null; private byte[][] staticSqlStrings = null; private byte[] streamConvertBuf = new byte[4096]; private int[] streamLengths = null; private boolean hasLimitClause = false; private boolean isLoadDataQuery = false; private boolean retrieveGeneratedKeys = false; private boolean useTrueBoolean = false; private char firstCharOfStmt = 0; /** * 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(Connection conn, String sql, String catalog) throws SQLException { super(conn, catalog); if (sql == null) { throw new SQLException("SQL String can not be NULL", SQLError.SQL_STATE_ILLEGAL_ARGUMENT); } originalSql = sql; this.dbmd = this.connection.getMetaData(); useTrueBoolean = connection.getIO().versionMeetsMinimum(3, 21, 23); this.parseInfo = new ParseInfo(sql, this.connection, this.dbmd, this.charEncoding, this.charConverter); initializeFromParseInfo(); } /** * 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(Connection conn, String sql, String catalog, ParseInfo cachedParseInfo) throws SQLException { super(conn, catalog); if (sql == null) { throw new SQLException("SQL String can not be NULL", SQLError.SQL_STATE_ILLEGAL_ARGUMENT); } originalSql = sql; this.dbmd = this.connection.getMetaData(); useTrueBoolean = connection.getIO().versionMeetsMinimum(3, 21, 23); this.parseInfo = cachedParseInfo; initializeFromParseInfo(); } /** * 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 new 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 synchronized 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, fixDecimalExponent(x.toString())); } } /** * 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 * @throws java.sql.SQLException DOCUMENT ME! */ public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException { if (x == null) { setNull(parameterIndex, java.sql.Types.BINARY); } else { if ((parameterIndex < 1) || (parameterIndex > staticSqlStrings.length)) { throw new java.sql.SQLException( "Parameter index out of range (" + parameterIndex + " > " + staticSqlStrings.length + ")", SQLError.SQL_STATE_ILLEGAL_ARGUMENT); } parameterStreams[parameterIndex - 1] = x; isStream[parameterIndex - 1] = true; streamLengths[parameterIndex - 1] = length; isNull[parameterIndex - 1] = false; } } /** * 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 { setBinaryStream(i, x.getBinaryStream(), (int) x.length()); } /** * 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 (useTrueBoolean) { setInternal(parameterIndex, x ? "'1'" : "'0'"); } else { setInternal(parameterIndex, x ? "'t'" : "'f'"); } } /** * 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)); } /** * 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 { if (x == null) { setNull(parameterIndex, java.sql.Types.BINARY); } else { // escape them int numBytes = x.length; ByteArrayOutputStream bOut = new ByteArrayOutputStream(numBytes); 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()); } } /** * 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 * * @throws SQLException if a database-access error occurs. */ public void setCharacterStream(int parameterIndex, java.io.Reader reader, int length) throws SQLException { try { if (reader == null) { setNull(parameterIndex, Types.LONGVARCHAR); } else { char[] c = null; int len = 0; boolean useLength = this.connection.useStreamLengthsInPrepStmts(); if (useLength && (length != -1)) { c = new char[length]; int numCharsRead = readFully(reader, c, length); // blocks until all read setString(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); } setString(parameterIndex, buf.toString()); } } } catch (java.io.IOException ioEx) { throw new SQLException(ioEx.toString(), SQLError.SQL_STATE_GENERAL_ERROR); } } /** * 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 { setString(i, x.getSubString(1L, (int) x.length())); } /** * 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 SQLException if a database access error occurs */ public void setDate(int parameterIndex, java.sql.Date x) throws SQLException { if (x == null) { setNull(parameterIndex, java.sql.Types.DATE); } else { // FIXME: Have instance version of this, problem as it's // not thread-safe :( SimpleDateFormat dateFormatter = new SimpleDateFormat( "''yyyy-MM-dd''"); setInternal(parameterIndex, dateFormatter.format(x)); } } /** * 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 * * @throws SQLException if a database-access error occurs. */ public void setDate(int parameterIndex, java.sql.Date x, Calendar cal) throws SQLException { setDate(parameterIndex, x); } /** * 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 * * @throws SQLException if a database access error occurs */ public void setDouble(int parameterIndex, double x) throws SQLException { setInternal(parameterIndex, fixDecimalExponent(String.valueOf(x))); } /** * 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 * * @throws SQLException if a database access error occurs */ public void setFloat(int parameterIndex, float x) throws SQLException { setInternal(parameterIndex, fixDecimalExponent(String.valueOf(x))); } /** * 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 * * @throws SQLException if a database access error occurs */ public void setInt(int parameterIndex, int x) throws SQLException { setInternal(parameterIndex, String.valueOf(x)); } /** * 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 * * @throws SQLException if a database access error occurs */ public void setLong(int parameterIndex, long x) throws SQLException { setInternal(parameterIndex, String.valueOf(x)); } /** * The number, types and properties of a ResultSet's columns are provided * by the getMetaData method. * * @return the description of a ResultSet's columns * * @throws SQLException if a database-access error occurs. */ public synchronized java.sql.ResultSetMetaData getMetaData() throws SQLException { 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(0); int paramCount = this.parameterValues.length; for (int i = 1; i <= paramCount; i++) { mdStmt.setString(i, ""); } boolean hadResults = mdStmt.execute(); if (hadResults) { mdRs = mdStmt.getResultSet(); this.pstmtResultMetaData = mdRs.getMetaData(); } else { this.pstmtResultMetaData = new ResultSetMetaData(new Field[0]); } } 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; } /** * 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 * * @throws SQLException if a database access error occurs */ public void setNull(int parameterIndex, int sqlType) throws SQLException { setInternal(parameterIndex, "null"); isNull[parameterIndex - 1] = true; } //--------------------------JDBC 2.0----------------------------- /** * 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 * * @throws SQLException if a database-access error occurs. */ public void setNull(int parameterIndex, int sqlType, String arg) throws SQLException { setNull(parameterIndex, sqlType); } /** * 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 * @throws java.sql.SQLException DOCUMENT ME! */ public void setObject(int parameterIndex, Object parameterObj, int targetSqlType, int scale) throws SQLException { if (parameterObj == null) { setNull(parameterIndex, java.sql.Types.OTHER); } else { try { switch (targetSqlType) { 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: Number parameterAsNum; if (parameterObj instanceof Boolean) { parameterAsNum = ((Boolean) parameterObj).booleanValue() ? new Integer(1) : new Integer(0); } else if (parameterObj instanceof String) { switch (targetSqlType) { case Types.BIT: parameterAsNum = (Boolean.getBoolean((String) parameterObj) ? new Integer("1") : new Integer("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:default: if (parameterAsNum instanceof java.math.BigDecimal) { setBigDecimal(parameterIndex, (java.math.BigDecimal) parameterAsNum); } 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; } break; case Types.CHAR: case Types.VARCHAR: case Types.LONGVARCHAR: 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)); } 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)); 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)); 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 new java.sql.SQLException("Unknown Types value", SQLError.SQL_STATE_GENERAL_ERROR); } } catch (Exception ex) { if (ex instanceof java.sql.SQLException) { throw (java.sql.SQLException) ex; } else { throw new java.sql.SQLException("Cannot convert " + parameterObj.getClass().toString() + " to SQL type requested due to " + ex.getClass().getName() + " - " + ex.getMessage(), SQLError.SQL_STATE_GENERAL_ERROR); } } } } /** * Set the value of a parameter using an object; use the java.lang * equivalent objects for integral values. * * @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 * * @throws SQLException if an error occurs */ public void setObject(int parameterIndex, Object parameterObj, int targetSqlType) throws SQLException { setObject(parameterIndex, parameterObj, targetSqlType, 0); } /** * Sets the given parameter to the given object. * * @param parameterIndex the parameter to set. * @param parameterObj the object to use as a value for the parameter. * * @throws SQLException if an error occurs. */ public void setObject(int parameterIndex, Object parameterObj) throws SQLException { 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 { setSerializableObject(parameterIndex, parameterObj); } } } /** * @see PreparedStatement#getParameterMetaData() */ public ParameterMetaData getParameterMetaData() throws SQLException { throw new NotImplemented(); } /** * 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 new NotImplemented(); } /** * 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 * * @throws SQLException if a database access error occurs */ public void setShort(int parameterIndex, short x) throws SQLException { setInternal(parameterIndex, String.valueOf(x)); } /** * 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 * * @throws SQLException if a database error occurs. */ public void setString(int parameterIndex, String x) throws SQLException { // if the passed string is null, then set this column to null if (x == null) { try { setInternal(parameterIndex, StringUtils.getBytes("null", this.charConverter, this.charEncoding)); } catch (UnsupportedEncodingException uue) { throw new SQLException("Unsupported character encoding '" + this.charEncoding + "'", SQLError.SQL_STATE_ILLEGAL_ARGUMENT); } } else { StringBuffer buf = new StringBuffer((int) (x.length() * 1.1)); buf.append('\''); int stringLength = x.length(); 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 */ 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(); try { byte[] parameterAsBytes = null; if (!this.isLoadDataQuery) { parameterAsBytes = StringUtils.getBytes(parameterAsString, this.charConverter, this.charEncoding); } else { // Send with platform character encoding parameterAsBytes = parameterAsString.getBytes(); } setInternal(parameterIndex, parameterAsBytes); } catch (UnsupportedEncodingException uue) { throw new SQLException("Unsupported character encoding '" + this.charEncoding + "'", SQLError.SQL_STATE_ILLEGAL_ARGUMENT); } } } /** * 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 SQLException if a database access error occurs */ public void setTime(int parameterIndex, Time x) throws SQLException { setTimeInternal(parameterIndex, x, TimeZone.getDefault()); } /** * 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.getTimeZone()); } /** * 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 SQLException if a database access error occurs */ public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException { setTimestampInternal(parameterIndex, x, TimeZone.getDefault()); } /** * 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.getTimeZone()); } /** * @see PreparedStatement#setURL(int, URL) */ public void setURL(int parameterIndex, URL arg) throws SQLException { if (arg != null) { setString(parameterIndex, arg.toString()); } else { setNull(parameterIndex, Types.CHAR); } } /** * 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); } } /** * JDBC 2.0 Add a set of parameters to the batch. * * @throws SQLException if a database-access error occurs. * * @see Statement#addBatch */ public void addBatch() throws SQLException { if (batchedArgs == null) { batchedArgs = new ArrayList(); } batchedArgs.add(new BatchParams(parameterValues, parameterStreams, isStream, streamLengths, isNull)); } /** * 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 * * @throws SQLException if a database access error occurs */ public void clearParameters() throws SQLException { for (int i = 0; i < parameterValues.length; i++) { parameterValues[i] = null; parameterStreams[i] = null; isStream[i] = false; isNull[i] = false; } } /** * Closes this prepared statement and releases all resources. * * @throws SQLException if database error occurs. */ public void close() throws SQLException { super.close(); this.parseInfo = null; 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; } /** * 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 * * @throws SQLException if a database error occurs. */ public boolean execute() throws SQLException { if (connection.isReadOnly() && (firstCharOfStmt != 'S')) { throw new SQLException("Connection is read-only. " + "Queries leading to data modification are not allowed", SQLError.SQL_STATE_ILLEGAL_ARGUMENT); } checkClosed(); ResultSet rs = null; synchronized (connection.getMutex()) { Buffer sendPacket = fillSendPacket(); String oldCatalog = null; if (!this.connection.getCatalog().equals(currentCatalog)) { oldCatalog = this.connection.getCatalog(); this.connection.setCatalog(currentCatalog); } boolean oldInfoMsgState = false; if (this.retrieveGeneratedKeys) { oldInfoMsgState = this.connection.isReadInfoMsgEnabled(); this.connection.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 (this.connection.useMaxRows()) { if (firstCharOfStmt == 'S') { if (hasLimitClause) { rs = this.connection.execSQL((String) null, maxRows, sendPacket, resultSetConcurrency, createStreamingResultSet(), true, this.currentCatalog); } else { if (maxRows <= 0) { this.connection.execSQL("SET OPTION SQL_SELECT_LIMIT=DEFAULT", -1, this.currentCatalog); } else { this.connection.execSQL( "SET OPTION SQL_SELECT_LIMIT=" + maxRows, -1, this.currentCatalog); } } } else { this.connection.execSQL("SET OPTION SQL_SELECT_LIMIT=DEFAULT", -1, this.currentCatalog); } // Finally, execute the query rs = this.connection.execSQL(null, -1, sendPacket, resultSetConcurrency, createStreamingResultSet(), (firstCharOfStmt == 'S'), this.currentCatalog); } else { rs = this.connection.execSQL(null, -1, sendPacket, resultSetConcurrency, createStreamingResultSet(), (firstCharOfStmt == 'S'), this.currentCatalog); } if (this.retrieveGeneratedKeys) { this.connection.setReadInfoMsgEnabled(oldInfoMsgState); } if (oldCatalog != null) { this.connection.setCatalog(oldCatalog); } } lastInsertId = rs.getUpdateID(); if (rs != null) { this.results = rs; } rs.setFirstCharOfQuery(this.firstCharOfStmt); rs.setConnection(connection); rs.setResultSetType(resultSetType); rs.setResultSetConcurrency(resultSetConcurrency); rs.setStatement(this); 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 * * @throws 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 { if (connection.isReadOnly()) { throw new SQLException("Connection is read-only. " + "Queries leading to data modification are not allowed", SQLError.SQL_STATE_ILLEGAL_ARGUMENT); } try { int[] updateCounts = null; if (batchedArgs != null) { int nbrCommands = batchedArgs.size(); updateCounts = new int[nbrCommands]; for (int i = 0; i < nbrCommands; i++) { updateCounts[i] = -3; } SQLException sqlEx = null; int commandIndex = 0; for (commandIndex = 0; commandIndex < nbrCommands; commandIndex++) { Object arg = batchedArgs.get(commandIndex); if (arg instanceof String) { updateCounts[commandIndex] = executeUpdate((String) arg); } else { BatchParams paramArg = (BatchParams) arg; try { updateCounts[commandIndex] = executeUpdate(paramArg.parameterStrings, paramArg.parameterStreams, paramArg.isStream, paramArg.streamLengths, paramArg.isNull); } catch (SQLException ex) { updateCounts[commandIndex] = EXECUTE_FAILED; if (this.connection.continueBatchOnError()) { sqlEx = ex; } else { int[] newUpdateCounts = new int[commandIndex]; System.arraycopy(updateCounts, 0, newUpdateCounts, 0, commandIndex); throw new java.sql.BatchUpdateException(ex .getMessage(), ex.getSQLState(), ex.getErrorCode(), newUpdateCounts); } } } } if (sqlEx != null) { throw new java.sql.BatchUpdateException(sqlEx.getMessage(), sqlEx.getSQLState(), sqlEx.getErrorCode(), updateCounts); } } return (updateCounts != null) ? updateCounts : new int[0]; } finally { clearBatch(); } } /** * A Prepared SQL query is executed and its ResultSet is returned * * @return a ResultSet that contains the data produced by the query - never * null * * @throws SQLException if a database error occurs. */ public synchronized java.sql.ResultSet executeQuery() throws SQLException { checkClosed(); if ((this.firstCharOfStmt == 'I') || (this.firstCharOfStmt == 'U') || (this.firstCharOfStmt == 'D') || (this.firstCharOfStmt == 'A') || (this.firstCharOfStmt == 'C')) { if (StringUtils.startsWithIgnoreCaseAndWs(this.originalSql, "INSERT") || StringUtils.startsWithIgnoreCaseAndWs(this.originalSql, "UPDATE") || StringUtils.startsWithIgnoreCaseAndWs(this.originalSql, "DELETE") || StringUtils.startsWithIgnoreCaseAndWs(this.originalSql, "DROP") || StringUtils.startsWithIgnoreCaseAndWs(this.originalSql, "CREATE") || StringUtils.startsWithIgnoreCaseAndWs(this.originalSql, "ALTER")) { throw new SQLException("Can not issue data manipulation statements with executeQuery()", SQLError.SQL_STATE_ILLEGAL_ARGUMENT); } } // We need to execute this all together // So synchronize on the Connection's mutex (because // even queries going through there synchronize // on the same mutex. synchronized (connection.getMutex()) { Buffer sendPacket = fillSendPacket(); if (this.results != null) { this.results.close(); } String oldCatalog = null; if (!this.connection.getCatalog().equals(currentCatalog)) { oldCatalog = this.connection.getCatalog(); this.connection.setCatalog(currentCatalog); } if (this.connection.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 (hasLimitClause) { results = this.connection.execSQL((String) null, maxRows, sendPacket, resultSetConcurrency, createStreamingResultSet(), true, this.currentCatalog); } else { if (maxRows <= 0) { this.connection.execSQL("SET OPTION SQL_SELECT_LIMIT=DEFAULT", -1, this.currentCatalog); } else { this.connection.execSQL("SET OPTION SQL_SELECT_LIMIT=" + maxRows, -1, this.currentCatalog); } this.results = this.connection.execSQL(null, -1, sendPacket, resultSetConcurrency, createStreamingResultSet(), true, this.currentCatalog); if (oldCatalog != null) { this.connection.setCatalog(oldCatalog); } } } else { this.results = this.connection.execSQL(null, -1, sendPacket, resultSetConcurrency, createStreamingResultSet(), true, this.currentCatalog); } if (oldCatalog != null) { this.connection.setCatalog(oldCatalog); } } lastInsertId = this.results.getUpdateID(); nextResults = this.results; this.results.setConnection(connection); this.results.setResultSetType(resultSetType); this.results.setResultSetConcurrency(resultSetConcurrency); this.results.setStatement(this); return (java.sql.ResultSet) 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. * * @throws SQLException if a database access error occurs */ public synchronized int executeUpdate() throws SQLException { return executeUpdate(parameterValues, parameterStreams, isStream, streamLengths, isNull); } /** * Returns this PreparedStatement represented as a string. * * @return this PreparedStatement represented as a string. * * @throws RuntimeException if an error occurs */ public String toString() { StringBuffer buf = new StringBuffer(); buf.append(super.toString()); buf.append(": "); try { for (int i = 0; i < parameterValues.length; ++i) { if (this.charEncoding != null) { buf.append(new String(staticSqlStrings[i], this.charEncoding)); } else { buf.append(new String(staticSqlStrings[i])); } if ((parameterValues[i] == null) && !isStream[i]) { buf.append("** NOT SPECIFIED **"); } else if (isStream[i]) { buf.append("** STREAM DATA **"); } else { if (this.charConverter != null) { buf.append(this.charConverter.toString( parameterValues[i])); } else { if (this.charEncoding != null) { buf.append(new String(parameterValues[i], this.charEncoding)); } else { buf.append(StringUtils.toAsciiString( parameterValues[i])); } } } } if (this.charEncoding != null) { buf.append(new String( staticSqlStrings[parameterValues.length], this.charEncoding)); } else { buf.append(staticSqlStrings[parameterValues.length]); } } catch (UnsupportedEncodingException uue) { throw new RuntimeException("Unsupported character encoding '" + this.charEncoding + "'"); } return buf.toString(); } /** * 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); } /** * Sets wheather or not this statement should retreive generated keys. * * @param retrieveGeneratedKeys */ protected void setRetrieveGeneratedKeys(boolean retrieveGeneratedKeys) { this.retrieveGeneratedKeys = retrieveGeneratedKeys; } /** * 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 * @throws java.sql.SQLException DOCUMENT ME! */ protected synchronized int executeUpdate(byte[][] batchedParameterStrings, InputStream[] batchedParameterStreams, boolean[] batchedIsStream, int[] batchedStreamLengths, boolean[] batchedIsNull) throws SQLException { if (connection.isReadOnly()) { throw new SQLException("Connection is read-only. " + "Queries leading to data modification are not allowed", SQLError.SQL_STATE_ILLEGAL_ARGUMENT); } checkClosed(); if ((this.firstCharOfStmt == 'S') && StringUtils.startsWithIgnoreCaseAndWs(this.originalSql, "SELECT")) { throw new java.sql.SQLException("Can not issue executeUpdate() for SELECTs", SQLError.SQL_STATE_ILLEGAL_ARGUMENT); } ResultSet rs = null; // The checking and changing of catalogs // must happen in sequence, so synchronize // on the same mutex that _conn is using synchronized (connection.getMutex()) { Buffer sendPacket = fillSendPacket(batchedParameterStrings, batchedParameterStreams, batchedIsStream, batchedStreamLengths); String oldCatalog = null; if (!this.connection.getCatalog().equals(currentCatalog)) { oldCatalog = this.connection.getCatalog(); this.connection.setCatalog(currentCatalog); } // // Only apply max_rows to selects // if (this.connection.useMaxRows()) { this.connection.execSQL("SET OPTION SQL_SELECT_LIMIT=DEFAULT", -1, this.currentCatalog); } boolean oldInfoMsgState = false; if (this.retrieveGeneratedKeys) { oldInfoMsgState = this.connection.isReadInfoMsgEnabled(); this.connection.setReadInfoMsgEnabled(true); } rs = this.connection.execSQL(null, -1, sendPacket, resultSetConcurrency, false, false, this.currentCatalog); if (this.retrieveGeneratedKeys) { this.connection.setReadInfoMsgEnabled(oldInfoMsgState); } if (oldCatalog != null) { this.connection.setCatalog(oldCatalog); } } this.results = rs; rs.setFirstCharOfQuery(this.firstCharOfStmt); updateCount = rs.getUpdateCount(); int truncatedUpdateCount = 0; if (updateCount > Integer.MAX_VALUE) { truncatedUpdateCount = Integer.MAX_VALUE; } else { truncatedUpdateCount = (int) updateCount; } lastInsertId = rs.getUpdateID(); this.results = rs; return truncatedUpdateCount; } byte[] getBytes(int parameterIndex) throws SQLException { if (isStream[parameterIndex]) { return streamToBytes(parameterStreams[parameterIndex], false, streamLengths[parameterIndex], this.connection.useStreamLengthsInPrepStmts()); } else { byte[] parameterVal = 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; } else { return parameterVal; } } } boolean isNull(int paramIndex) { return isNull[paramIndex]; } ParseInfo getParseInfo() { return this.parseInfo; } /** * Sets the concurrency for result sets generated by this statement * * @param concurrencyFlag the result set concurrency flag from the * ResultSet interface. */ void setResultSetConcurrency(int concurrencyFlag) { resultSetConcurrency = concurrencyFlag; } /** * Sets the result set type for result sets generated by this statement * * @param typeFlag the result set type from the ResultSet interface */ void setResultSetType(int typeFlag) { resultSetType = typeFlag; } 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"; } } // // 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"; } int n; int z; int count; int maxvecs; char c; char separator; StringReader reader = new StringReader(dt + " "); ArrayList vec = new ArrayList(); ArrayList vecRemovelist = new ArrayList(); Object[] nv = new Object[3]; Object[] v; nv[0] = new Character('y'); nv[1] = new StringBuffer(); nv[2] = new Integer(0); vec.add(nv); if (toTime) { nv = new Object[3]; nv[0] = new Character('h'); nv[1] = new StringBuffer(); nv[2] = new Integer(0); vec.add(nv); } while ((z = reader.read()) != -1) { separator = (char) z; maxvecs = vec.size(); for (count = 0; count < maxvecs; count++) { v = (Object[]) 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] = new Integer(4); } } } else { if (c == 'X') { c = 'y'; nv = new Object[3]; nv[1] = (new StringBuffer(((StringBuffer) v[1]) .toString())).append('M'); nv[0] = new Character('M'); nv[2] = new Integer(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] = new Character('d'); nv[2] = new Integer(1); vec.add(nv); } ((StringBuffer) v[1]).append(c); if (c == ((Character) v[0]).charValue()) { v[2] = new Integer(n + 1); } else { v[0] = new Character(c); v[2] = new Integer(1); } } } int size = vecRemovelist.size(); for (int i = 0; i < size; i++) { v = (Object[]) vecRemovelist.get(i); vec.remove(v); } vecRemovelist.clear(); } int size = vec.size(); for (int i = 0; i < size; i++) { v = (Object[]) 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 = (Object[]) vec.get(0); //might throw exception StringBuffer format = (StringBuffer) v[1]; format.setLength(format.length() - 1); return format.toString(); } private final void setInternal(int paramIndex, byte[] val) throws SQLException { if (this.isClosed) { throw new SQLException("PreparedStatement has been closed. No further operations allowed.", SQLError.SQL_STATE_ILLEGAL_ARGUMENT); } if ((paramIndex < 1)) { throw new SQLException("Parameter index out of range (" + paramIndex + " < 1 ).", SQLError.SQL_STATE_ILLEGAL_ARGUMENT); } else if (paramIndex >= staticSqlStrings.length) { throw new SQLException("Parameter index out of range (" + paramIndex + " > " + (staticSqlStrings.length - 1) + ").", SQLError.SQL_STATE_ILLEGAL_ARGUMENT); } isStream[paramIndex - 1] = false; isNull[paramIndex - 1] = false; parameterStreams[paramIndex - 1] = null; parameterValues[paramIndex - 1] = val; } private final void setInternal(int paramIndex, String val) throws SQLException { byte[] parameterAsBytes = null; if (this.charConverter != null) { parameterAsBytes = this.charConverter.toBytes(val); } else { try { parameterAsBytes = StringUtils.getBytes(val, this.charConverter, this.charEncoding); } catch (UnsupportedEncodingException uEE) { throw new SQLException("Unsupported encoding '" + this.charEncoding + "'", SQLError.SQL_STATE_ILLEGAL_ARGUMENT); } } setInternal(paramIndex, parameterAsBytes); } /** * 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 if a database error occurs. * @throws java.sql.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); } catch (Exception ex) { throw new java.sql.SQLException("Invalid argument value: " + ex.getClass().getName(), SQLError.SQL_STATE_ILLEGAL_ARGUMENT); } } 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')))))))))))); } /** * Set a parameter to a java.sql.Time value. The driver converts this to a * SQL TIME value when it sends it to the database, using the given * timezone. * * @param parameterIndex the first parameter is 1...)); * @param x the parameter value * @param tz the timezone to use * * @throws SQLException if a database access error occurs */ private void setTimeInternal(int parameterIndex, Time x, TimeZone tz) throws SQLException { if (x == null) { setNull(parameterIndex, java.sql.Types.TIME); } else { x = TimeUtil.changeTimezone(this.connection, x, tz, this.connection.getServerTimezone()); setInternal(parameterIndex, "'" + x.toString() + "'"); } } /** * 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 synchronized void setTimestampInternal(int parameterIndex, Timestamp x, TimeZone tz) throws SQLException { if (x == null) { setNull(parameterIndex, java.sql.Types.TIMESTAMP); } else { String timestampString = null; x = TimeUtil.changeTimezone(this.connection, x, tz, this.connection.getServerTimezone()); if (this.tsdf == null) { this.tsdf = new SimpleDateFormat("''yyyy-MM-dd HH:mm:ss''"); } timestampString = this.tsdf.format(x); setInternal(parameterIndex, timestampString); // SimpleDateFormat is not thread-safe } } 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 == '\'') || (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); } } 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 == '\'') || (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); } } /** * Creates the packet that contains the query to be sent to the server. * * @return the packet to send to the server to issue the query. * * @throws SQLException if a database error occurs. */ private Buffer fillSendPacket() throws SQLException { 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 DOCUMENT ME! * @param batchedParameterStreams DOCUMENT ME! * @param batchedIsStream DOCUMENT ME! * @param batchedStreamLengths DOCUMENT ME! * * @return DOCUMENT ME! * * @throws SQLException if a database error occurs. * @throws java.sql.SQLException DOCUMENT ME! */ private Buffer fillSendPacket(byte[][] batchedParameterStrings, InputStream[] batchedParameterStreams, boolean[] batchedIsStream, int[] batchedStreamLengths) throws SQLException { Buffer sendPacket = this.connection.getIO().getSharedSendPacket(); sendPacket.clear(); sendPacket.writeByte((byte) MysqlDefs.QUERY); boolean useStreamLengths = this.connection.useStreamLengthsInPrepStmts(); // // Try and get this allocation as close as possible // for BLOBs // int ensurePacketSize = 0; for (int i = 0; i < batchedParameterStrings.length; i++) { if (batchedIsStream[i] && useStreamLengths) { ensurePacketSize += batchedStreamLengths[i]; } } if (ensurePacketSize != 0) { sendPacket.ensureCapacity(ensurePacketSize); } for (int i = 0; i < batchedParameterStrings.length; i++) { if ((batchedParameterStrings[i] == null) && (batchedParameterStreams[i] == null)) { throw new java.sql.SQLException( "No value specified for parameter " + (i + 1), SQLError.SQL_STATE_WRONG_NO_OF_PARAMETERS); } sendPacket.writeBytesNoNull(staticSqlStrings[i]); if (batchedIsStream[i]) { streamToBytes(sendPacket, batchedParameterStreams[i], true, batchedStreamLengths[i], useStreamLengths); } else { sendPacket.writeBytesNoNull(batchedParameterStrings[i]); } } sendPacket.writeBytesNoNull(staticSqlStrings[batchedParameterStrings.length]); return sendPacket; } // // Adds '+' to decimal numbers that are positive (MySQL doesn't // understand them otherwise // private static final String fixDecimalExponent(String dString) { int ePos = dString.indexOf("E"); if (ePos == -1) { ePos = dString.indexOf("e"); } if (ePos != -1) { if (dString.length() > (ePos + 1)) { char maybeMinusChar = dString.charAt(ePos + 1); if (maybeMinusChar != '-') { StringBuffer buf = new StringBuffer(dString.length() + 1); buf.append(dString.substring(0, ePos + 1)); buf.append('+'); buf.append(dString.substring(ePos + 1, dString.length())); dString = buf.toString(); } } } return dString; } private void initializeFromParseInfo() throws SQLException { this.staticSqlStrings = this.parseInfo.staticSql; this.hasLimitClause = this.parseInfo.foundLimitClause; this.isLoadDataQuery = this.parseInfo.foundLoadData; this.firstCharOfStmt = this.parseInfo.firstStmtChar; int numberOfParameters = staticSqlStrings.length - 1; parameterValues = new byte[numberOfParameters][]; parameterStreams = new InputStream[numberOfParameters]; isStream = new boolean[numberOfParameters]; streamLengths = new int[numberOfParameters]; isNull = new boolean[numberOfParameters]; clearParameters(); for (int j = 0; j < numberOfParameters; j++) { isStream[j] = false; } } 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 E) { throw new java.sql.SQLException("Error reading from InputStream " + E.getClass().getName(), SQLError.SQL_STATE_GENERAL_ERROR); } } private final int readblock(InputStream i, byte[] b) throws SQLException { try { return i.read(b); } catch (Throwable E) { throw new java.sql.SQLException("Error reading from InputStream " + E.getClass().getName(), SQLError.SQL_STATE_GENERAL_ERROR); } } /** * For the setXXXStream() methods. Basically converts an InputStream into a * String. Not very efficient, but it works. * * @param in DOCUMENT ME! * @param streamLength DOCUMENT ME! * @param useLength DOCUMENT ME! * * @return DOCUMENT ME! * * @throws SQLException DOCUMENT ME! */ private final byte[] streamToBytes(InputStream in, int streamLength, boolean useLength) throws SQLException { return streamToBytes(in, true, streamLength, useLength); } private final byte[] streamToBytes(InputStream in, boolean escape, int streamLength, boolean useLength) throws SQLException { try { if (streamLength == -1) { useLength = false; } ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); int bc = -1; if (useLength) { bc = readblock(in, streamConvertBuf, streamLength); } else { bc = readblock(in, streamConvertBuf); } int lengthLeftToRead = streamLength - bc; if (escape) { bytesOut.write('\''); } while (bc > 0) { if (escape) { escapeblockFast(streamConvertBuf, bytesOut, bc); } else { bytesOut.write(streamConvertBuf, 0, bc); } if (useLength) { bc = readblock(in, streamConvertBuf, lengthLeftToRead); if (bc > 0) { lengthLeftToRead -= bc; } } else { bc = readblock(in, streamConvertBuf); } } if (escape) { bytesOut.write('\''); } return bytesOut.toByteArray(); } finally { try { in.close(); } catch (IOException ioEx) { ; } in = null; } } private final void streamToBytes(Buffer packet, InputStream in, boolean escape, int streamLength, boolean useLength) throws SQLException { try { 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 (escape) { packet.writeByte((byte) '\''); } while (bc > 0) { 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 { try { in.close(); } catch (IOException ioEx) { ; } in = null; } } /** * 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! */ private 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; } class BatchParams { boolean[] isNull = null; boolean[] isStream = null; InputStream[] parameterStreams = null; byte[][] parameterStrings = null; int[] streamLengths = null; BatchParams(byte[][] strings, InputStream[] streams, boolean[] isStreamFlags, int[] lengths, boolean[] isNullFlags) { // // Make copies // parameterStrings = new byte[strings.length][]; parameterStreams = new InputStream[streams.length]; isStream = new boolean[isStreamFlags.length]; streamLengths = new int[lengths.length]; isNull = new boolean[isNullFlags.length]; System.arraycopy(strings, 0, parameterStrings, 0, strings.length); System.arraycopy(streams, 0, parameterStreams, 0, streams.length); System.arraycopy(isStreamFlags, 0, isStream, 0, isStreamFlags.length); System.arraycopy(lengths, 0, streamLengths, 0, lengths.length); System.arraycopy(isNullFlags, 0, isNull, 0, isNullFlags.length); } } class ParseInfo { byte[][] staticSql = null; boolean foundLimitClause = false; boolean foundLoadData = false; char firstStmtChar = 0; int statementLength = 0; long lastUsed = 0; /** * */ public ParseInfo(String sql, Connection conn, java.sql.DatabaseMetaData dbmd, String encoding, SingleByteCharsetConverter converter) throws SQLException { if (sql == null) { throw new SQLException("SQL String can not be NULL", SQLError.SQL_STATE_ILLEGAL_ARGUMENT); } lastUsed = System.currentTimeMillis(); String quotedIdentifierString = dbmd.getIdentifierQuoteString(); char quotedIdentifierChar = 0; if ((quotedIdentifierString != null) && !quotedIdentifierString.equals(" ") && (quotedIdentifierString.length() > 0)) { quotedIdentifierChar = quotedIdentifierString.charAt(0); } statementLength = sql.length(); ArrayList endpointList = new ArrayList(); boolean inQuotes = false; boolean inQuotedId = false; int lastParmEnd = 0; int i; int pre1 = 0; int pre2 = 0; int lastAlphaCharPos = 0; int stopLookingForLimitClause = statementLength - 5; foundLimitClause = false; for (i = 0; i < statementLength; ++i) { char c = sql.charAt(i); if ((firstStmtChar == 0) && !Character.isWhitespace(c)) { // Determine what kind of statement we're doing (_S_elect, _I_nsert, etc.) firstStmtChar = Character.toUpperCase(c); } if (Character.isLetter(c)) { lastAlphaCharPos = i; } // are we in a quoted identifier? // (only valid when the id is not inside a 'string') if (!inQuotes && (quotedIdentifierChar != 0) && (c == quotedIdentifierChar)) { inQuotedId = !inQuotedId; } // only respect quotes when not in a quoted identifier if (!inQuotedId) { if ((c == '\'') && (pre1 == '\\') && (pre2 == '\\')) { inQuotes = !inQuotes; } else if ((c == '\'') && (pre1 != '\\')) { inQuotes = !inQuotes; } } if ((c == '?') && !inQuotes) { endpointList.add(new int[] { lastParmEnd, i }); lastParmEnd = i + 1; } if (!inQuotes && (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')) { foundLimitClause = true; } } } } } } pre2 = pre1; pre1 = c; } if (firstStmtChar == 'L') { if (StringUtils.startsWithIgnoreCaseAndWs(sql, "LOAD DATA")) { foundLoadData = true; } else { foundLoadData = false; } } else { foundLoadData = false; } endpointList.add(new int[] { lastParmEnd, statementLength }); staticSql = new byte[endpointList.size()][]; for (i = 0; i < staticSql.length; i++) { int[] ep = (int[]) endpointList.get(i); int end = ep[1]; int begin = ep[0]; int len = end - begin; if (foundLoadData) { String temp = new String(sql.toCharArray(), begin, len); staticSql[i] = temp.getBytes(); } else if (encoding == null) { byte[] buf = new byte[len]; for (int j = 0; j < len; j++) { buf[j] = (byte) sql.charAt(begin + j); } staticSql[i] = buf; } else { try { if (converter != null) { staticSql[i] = StringUtils.getBytes(sql, converter, encoding, begin, len); } else { String temp = new String(sql.toCharArray(), begin, len); staticSql[i] = StringUtils.getBytes(temp, null, encoding); } } catch (java.io.UnsupportedEncodingException ue) { throw new SQLException(ue.toString()); } } } int numberOfParameters = staticSql.length - 1; } } }