// jTDS JDBC Driver for Microsoft SQL Server and Sybase // Copyright (C) 2004 The jTDS Project // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // package net.sourceforge.jtds.jdbc; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.lang.reflect.Constructor; import java.math.BigDecimal; import java.net.URL; import java.sql.Array; import java.sql.Blob; import java.sql.Clob; import java.sql.Date; import java.sql.NClob; import java.sql.ParameterMetaData; import java.sql.PreparedStatement; import java.sql.Ref; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.RowId; import java.sql.SQLException; import java.sql.SQLXML; import java.sql.Time; import java.sql.Timestamp; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; /** * jTDS implementation of the java.sql.PreparedStatement interface. * <p> * Implementation notes: * <ol> * <li>Generally a simple subclass of Statement mainly adding support for the * setting of parameters. * <li>The stream logic is taken over from the work Brian did to add Blob support * to the original jTDS. * <li>Use of Statement specific method calls eg executeQuery(sql) is blocked by * this version of the driver. This is unlike the original jTDS but inline * with all the other JDBC drivers that I have been able to test. * </ol> * * @author * Brian Heineman, Mike Hutchinson, Holger Rehn */ public class JtdsPreparedStatement extends JtdsStatement implements PreparedStatement { /** The SQL statement being prepared. */ protected final String sql; /** The original SQL statement provided at construction time. */ private final String originalSql; /** The first SQL keyword in the SQL string.*/ protected String sqlWord; /** The procedure name for CallableStatements. */ protected String procName; /** The parameter list for the call. */ protected ParamInfo[] parameters; /** True to return generated keys. */ private boolean returnKeys; /** The cached parameter meta data. */ protected ParamInfo[] paramMetaData; /** Used to format numeric values when scale is specified. */ private final static NumberFormat f = NumberFormat.getInstance(); /** Collection of handles used by this statement */ Collection handles; /** * Construct a new preparedStatement object. * * @param connection The parent connection. * @param sql The SQL statement to prepare. * @param resultSetType The result set type eg SCROLLABLE etc. * @param concurrency The result set concurrency eg READONLY. * @param returnKeys True if generated keys should be returned. * @throws SQLException */ JtdsPreparedStatement(JtdsConnection connection, String sql, int resultSetType, int concurrency, boolean returnKeys) throws SQLException { super(connection, resultSetType, concurrency); // returned by toString() originalSql = sql; // Parse the SQL looking for escapes and parameters if (this instanceof JtdsCallableStatement) { sql = normalizeCall(sql); } ArrayList params = new ArrayList(); String[] parsedSql = SQLParser.parse(sql, params, connection, false); if (parsedSql[0].length() == 0) { throw new SQLException(Messages.get("error.prepare.nosql"), "07000"); } if (parsedSql[1].length() > 1) { if (this instanceof JtdsCallableStatement) { // Procedure call procName = parsedSql[1]; } } sqlWord = parsedSql[2]; if (returnKeys /*&& "insert".equals(sqlWord) REVIEW: see JtdsStatement.executeImpl */) { if (connection.getServerType() == Driver.SQLSERVER && connection.getDatabaseMajorVersion() >= 8) { this.sql = parsedSql[0] + " SELECT SCOPE_IDENTITY() AS " + GENKEYCOL; } else { this.sql = parsedSql[0] + " SELECT @@IDENTITY AS " + GENKEYCOL; } this.returnKeys = true; } else { this.sql = parsedSql[0]; this.returnKeys = false; } parameters = (ParamInfo[]) params.toArray(new ParamInfo[params.size()]); } /** * Returns the SQL command provided at construction time. */ @Override public String toString() { return originalSql; } /** * <p> This method converts native call syntax into (hopefully) valid JDBC * escape syntax. </p> * * <p><b>Note:</b> This method is required for backwards compatibility with * previous versions of jTDS. Strictly speaking only the JDBC syntax needs to * be recognized, constructions such as "?=#testproc ?,?" are neither valid * native syntax nor valid escapes. All the substrings and trims below are * not as bad as they look. The objects created all refer back to the * original SQL string it is just the start and length positions which * change. </p> * * @param sql * the SQL statement to process * * @return * the SQL, possibly in original form * * @throws SQLException * if the SQL statement is detected to be a normal SQL INSERT, UPDATE or * DELETE statement instead of a procedure call */ protected static String normalizeCall( final String sql ) throws SQLException { try { return normalize( sql, 0 ); } catch( SQLException sqle ) { // if normalize was giving up due to an unrecognized syntax error it // would have thrown an SQLException without state if( sqle.getSQLState() != null ) throw sqle; return sql; } } /** * <p> This method converts native call syntax into (hopefully) valid JDBC * escape syntax. </p> * * <p><b>Note:</b> This method is required for backwards compatibility with * previous versions of jTDS. Strictly speaking only the JDBC syntax needs to * be recognized, constructions such as "?=#testproc ?,?" are neither valid * native syntax nor valid escapes. All the substrings and trims below are * not as bad as they look. The objects created all refer back to the * original SQL string it is just the start and length positions which * change. </p> * * @param sql * the SQL statement to process * * @return * the SQL, possibly in original form * * @throws SQLException * if the SQL statement is detected to be a normal SQL INSERT, UPDATE or * DELETE statement instead of a procedure call */ private static String normalize( final String sql, int level ) throws SQLException { if( level > 1 ) throw new SQLException(); int len = sql.length(); int qmark = -1; int equal = -1; int call = -1; // try to isolate the call for( int i = 0; i < len && call < 0; i ++ ) { // skip whitespace while( Character.isWhitespace( sql.charAt( i ) ) ) { i ++; } switch( sql.charAt( i ) ) { case '{': // escape syntax with leading comment/white space or syntax error return sql; case '?': // should be leading '?' if( qmark == -1 ) { qmark = i; } else { // syntax error, give up throw new SQLException(); } break; case '=': // should be leading '=' if( equal == -1 && qmark >= 0 ) { equal = i; } else { // syntax error, give up throw new SQLException(); } break; case '-': // skip single comment if( i + 1 < len && sql.charAt( i + 1 ) == '-' ) { i += 2; while( i < len && sql.charAt( i ) != '\n' && sql.charAt( i ) != '\r' ) { i ++; } } break; case '/': // skip multi line comment if( i + 1 < len && sql.charAt( i + 1 ) == '*' ) { i += 1; int block = 1; do { if( i >= len -1 ) throw new SQLException( Messages.get( "error.parsesql.missing", "*/" ), "22025" ); i ++; if( sql.charAt( i ) == '/' && sql.charAt( i + 1 ) == '*' ) { i ++; block ++; } else if( sql.charAt( i ) == '*' && sql.charAt( i + 1 ) == '/' ) { i ++; block --; } } while( block > 0 ); } break; default: // if( len - i > 4 && ( sql.substring( i, i + 5 ).equalsIgnoreCase( "exec " ) ) || sql.substring( i, i + 5 ).equalsIgnoreCase( "call " ) ) { return normalize( sql.substring( 0, i ) + sql.substring( i + 4, sql.length() ), level ++ ); } else if( len - i > 7 && sql.substring( i, i + 8 ).equalsIgnoreCase( "execute " ) ) { return normalize( sql.substring( 0, i ) + sql.substring( i + 7, sql.length() ), level ++ ); } // keep backward compatibility, things like "testproc()" are accepted call = i; break; } } if( equal == -1 && qmark != -1 ) { // syntax error, give up throw new SQLException(); } // check for a more or less common mistake to execute a normal statement via CallableStatement (bug #637) if( call + 7 < len ) { String sub = sql.substring( call, call + 7 ); if( sub != null && ( sub.equalsIgnoreCase( "insert " ) || sub.equalsIgnoreCase( "update " ) || sub.equalsIgnoreCase( "delete " ) ) ) throw new SQLException( Messages.get( "error.parsesql.noprocedurecall" ), "07000" ); } // fast scan whether the statement ends with a single line comment and append an additional line break in that case return "{" + sql.substring( 0, call ) + "call " + sql.substring( call ) + ( openComment( sql, call ) ? "\n" : "" ) + "}"; } /** * */ private static boolean openComment( String sql, int offset ) throws SQLException { int len = sql.length(); for( int i = offset; i < len; i ++ ) { switch( sql.charAt( i ) ) { case '-': // single comment if( i + 1 < len && sql.charAt( i + 1 ) == '-' ) { i += 2; while( i < len && sql.charAt( i ) != '\n' && sql.charAt( i ) != '\r' ) { i ++; } if( i == len ) { // reached end of statement, comment still open return true; } } break; case '/': // multi line comment if( i + 1 < len && sql.charAt( i + 1 ) == '*' ) { i += 1; int block = 1; do { if( i >= len -1 ) throw new SQLException( Messages.get( "error.parsesql.missing", "*/" ), "22025" ); i ++; if( sql.charAt( i ) == '/' && sql.charAt( i + 1 ) == '*' ) { i ++; block ++; } else if( sql.charAt( i ) == '*' && sql.charAt( i + 1 ) == '/' ) { i ++; block --; } } while( block > 0 ); } break; } } return false; } /** * Check that this statement is still open. * * @throws SQLException if statement closed. */ @Override protected void checkOpen() throws SQLException { if (isClosed()) { throw new SQLException( Messages.get("error.generic.closed", "PreparedStatement"), "HY010"); } } /** * Report that user tried to call a method not supported on this type of statement. * * @param method The method name to report in the error message. * @throws SQLException */ protected void notSupported(String method) throws SQLException { throw new SQLException( Messages.get("error.generic.notsup", method), "HYC00"); } /** * Execute the SQL batch on a MS server. * <p/> * When running with <code>prepareSQL=1</code> or <code>3</code>, the driver will first prepare temporary stored * procedures or statements for each parameter combination found in the batch. The handles to these pre-preared * statements will then be used to execute the actual batch statements. * * @param size the total size of the batch * @param executeSize the maximum number of statements to send in one request * @param counts the returned update counts * @return chained exceptions linked to a <code>SQLException</code> * @throws SQLException if a serious error occurs during execution */ @Override protected SQLException executeMSBatch(int size, int executeSize, ArrayList counts) throws SQLException { if (parameters.length == 0) { // There are no parameters, each SQL call is the same so execute as a simple batch return super.executeMSBatch(size, executeSize, counts); } SQLException sqlEx = null; String procHandle[] = null; // Prepare any statements before executing the batch if (connection.getPrepareSql() == TdsCore.TEMPORARY_STORED_PROCEDURES || connection.getPrepareSql() == TdsCore.PREPARE) { procHandle = new String[size]; for (int i = 0; i < size; i++) { // Prepare the statement procHandle[i] = connection.prepareSQL(this, sql, (ParamInfo[]) batchValues.get(i), false, false); } } for (int i = 0; i < size;) { Object value = batchValues.get(i); String proc = (procHandle == null) ? procName : procHandle[i]; ++i; // Execute batch now if max size reached or end of batch boolean executeNow = (i % executeSize == 0) || i == size; tds.startBatch(); tds.executeSQL(sql, proc, (ParamInfo[]) value, false, 0, -1, -1, executeNow); // If the batch has been sent, process the results if (executeNow) { sqlEx = tds.getBatchCounts(counts, sqlEx); // If a serious error then we stop execution now as count // is too small. if (sqlEx != null && counts.size() != i) { break; } } } return sqlEx; } /** * Execute the SQL batch on a Sybase server. * <p/> * Sybase needs to have the SQL concatenated into one TDS language packet followed by up to 1000 parameters. This * method will be overridden for <code>CallableStatements</code>. * * @param size the total size of the batch * @param executeSize the maximum number of statements to send in one request * @param counts the returned update counts * @return chained exceptions linked to a <code>SQLException</code> * @throws SQLException if a serious error occurs during execution */ @Override protected SQLException executeSybaseBatch(int size, int executeSize, ArrayList counts) throws SQLException { if (parameters.length == 0) { // There are no parameters each SQL call is the same so // execute as a simple batch return super.executeSybaseBatch(size, executeSize, counts); } // Revise the executeSize down if too many parameters will be required. // Be conservative the actual maximums are 256 for older servers and 2048. int maxParams = (connection.getDatabaseMajorVersion() < 12 || (connection.getDatabaseMajorVersion() == 12 && connection.getDatabaseMinorVersion() < 50)) ? 200 : 1000; StringBuilder sqlBuf = new StringBuilder(size * 32); SQLException sqlEx = null; if (parameters.length * executeSize > maxParams) { executeSize = maxParams / parameters.length; if (executeSize == 0) { executeSize = 1; } } ArrayList paramList = new ArrayList(); for (int i = 0; i < size;) { Object value = batchValues.get(i); ++i; // Execute batch now if max size reached or end of batch boolean executeNow = (i % executeSize == 0) || i == size; int offset = sqlBuf.length(); sqlBuf.append(sql).append(' '); for (int n = 0; n < parameters.length; n++) { ParamInfo p = ((ParamInfo[]) value)[n]; // Allow for the position of the '?' marker in the buffer p.markerPos += offset; paramList.add(p); } if (executeNow) { ParamInfo args[]; args = (ParamInfo[]) paramList.toArray(new ParamInfo[paramList.size()]); tds.executeSQL(sqlBuf.toString(), null, args, false, 0, -1, -1, true); sqlBuf.setLength(0); paramList.clear(); // If the batch has been sent, process the results sqlEx = tds.getBatchCounts(counts, sqlEx); // If a serious error or a server error then we stop // execution now as count is too small. if (sqlEx != null && counts.size() != i) { break; } } } return sqlEx; } /** * Check the supplied index and return the selected parameter. * * @param parameterIndex the parameter index 1 to n. * @return the parameter as a <code>ParamInfo</code> object. * @throws SQLException if the statement is closed; * if <code>parameterIndex</code> is less than 0; * if <code>parameterIndex</code> is greater than the * number of parameters; * if <code>checkIfSet</code> was <code>true</code> * and the parameter was not set */ protected ParamInfo getParameter(int parameterIndex) throws SQLException { checkOpen(); if (parameterIndex < 1 || parameterIndex > parameters.length) { throw new SQLException(Messages.get("error.prepare.paramindex", Integer.toString(parameterIndex)), "07009"); } return parameters[parameterIndex - 1]; } /** * Generic setObject method. * * @param parameterIndex Parameter index 1 to n. * @param x The value to set. * @param targetSqlType The java.sql.Types constant describing the data. * @param scale The decimal scale -1 if not set. */ public void setObjectBase(int parameterIndex, Object x, int targetSqlType, int scale) throws SQLException { checkOpen(); int length = 0; if (targetSqlType == java.sql.Types.CLOB) { targetSqlType = java.sql.Types.LONGVARCHAR; } else if (targetSqlType == java.sql.Types.BLOB) { targetSqlType = java.sql.Types.LONGVARBINARY; } if (x != null) { x = Support.convert(this, x, targetSqlType, connection.getCharset()); if (scale >= 0) { if (x instanceof BigDecimal) { x = ((BigDecimal) x).setScale(scale, BigDecimal.ROUND_HALF_UP); } else if (x instanceof Number) { synchronized (f) { f.setGroupingUsed(false); f.setMaximumFractionDigits(scale); x = Support.convert(this, f.format(x), targetSqlType, connection.getCharset()); } } } if (x instanceof Blob) { Blob blob = (Blob) x; length = (int) blob.length(); x = blob.getBinaryStream(); } else if (x instanceof Clob) { Clob clob = (Clob) x; length = (int) clob.length(); x = clob.getCharacterStream(); } } setParameter(parameterIndex, x, targetSqlType, scale, length); } /** * Update the ParamInfo object for the specified parameter. * * @param parameterIndex Parameter index 1 to n. * @param x The value to set. * @param targetSqlType The java.sql.Types constant describing the data. * @param scale The decimal scale -1 if not set. * @param length The length of the data item. */ protected void setParameter(int parameterIndex, Object x, int targetSqlType, int scale, int length) throws SQLException { ParamInfo pi = getParameter(parameterIndex); if ("ERROR".equals(Support.getJdbcTypeName(targetSqlType))) { throw new SQLException(Messages.get("error.generic.badtype", Integer.toString(targetSqlType)), "HY092"); } // Update parameter descriptor if (targetSqlType == java.sql.Types.DECIMAL || targetSqlType == java.sql.Types.NUMERIC) { pi.precision = connection.getMaxPrecision(); if (x instanceof BigDecimal) { x = Support.normalizeBigDecimal((BigDecimal) x, pi.precision); pi.scale = ((BigDecimal) x).scale(); } else { pi.scale = (scale < 0) ? TdsData.DEFAULT_SCALE : scale; } } else { pi.scale = (scale < 0) ? 0 : scale; } if (x instanceof String) { pi.length = ((String) x).length(); } else if (x instanceof byte[]) { pi.length = ((byte[]) x).length; } else { pi.length = length; } if (x instanceof Date) { x = new DateTime((Date) x); } else if (x instanceof Time) { x = new DateTime((Time) x); } else if (x instanceof Timestamp) { x = new DateTime((Timestamp) x); } pi.value = x; pi.jdbcType = targetSqlType; pi.isSet = true; pi.isUnicode = connection.getUseUnicode(); } /** * Update the cached column meta data information. * * @param value The Column meta data array. */ void setColMetaData(ColInfo[] value) { colMetaData = value; } /** * Update the cached parameter meta data information. * * @param value The Column meta data array. */ void setParamMetaData(ParamInfo[] value) { for (int i = 0; i < value.length && i < parameters.length; i++) { if (!parameters[i].isSet) { // Only update parameter descriptors if the user // has not yet set them. parameters[i].jdbcType = value[i].jdbcType; parameters[i].isOutput = value[i].isOutput; parameters[i].precision = value[i].precision; parameters[i].scale = value[i].scale; parameters[i].sqlType = value[i].sqlType; } } } // -------------------- java.sql.PreparedStatement methods follow ----------------- @Override public void close() throws SQLException { try { super.close(); } finally { // Null these fields to reduce memory usage while // waiting for Statement.finalize() to execute. handles = null; parameters = null; } } @Override public int executeUpdate() throws SQLException { checkOpen(); reset(); if (procName == null && !(this instanceof JtdsCallableStatement)) { // Sync on the connection to make sure rollback() isn't called // between the moment when the statement is prepared and the moment // when it's executed. synchronized (connection) { String spName = connection.prepareSQL(this, sql, parameters, returnKeys, false); executeSQL(sql, spName, parameters, true, false); } } else { executeSQL(sql, procName, parameters, true, false); } int res = getUpdateCount(); return res == -1 ? 0 : res; } @Override public void addBatch() throws SQLException { checkOpen(); if (batchValues == null) { batchValues = new ArrayList(); } if (parameters.length == 0) { // This is likely to be an error. Batch execution // of a prepared statement with no parameters means // exactly the same SQL will be executed each time! batchValues.add(sql); } else { batchValues.add(parameters); ParamInfo tmp[] = new ParamInfo[parameters.length]; for (int i = 0; i < parameters.length; ++i) { tmp[i] = (ParamInfo) parameters[i].clone(); } parameters = tmp; } } @Override public void clearParameters() throws SQLException { checkOpen(); for (int i = 0; i < parameters.length; i++) { parameters[i].clearInValue(); } } @Override public boolean execute() throws SQLException { checkOpen(); reset(); boolean useCursor = useCursor(returnKeys, sqlWord); if (procName == null && !(this instanceof JtdsCallableStatement)) { // Sync on the connection to make sure rollback() isn't called // between the moment when the statement is prepared and the moment // when it's executed. synchronized (connection) { String spName = connection.prepareSQL(this, sql, parameters, returnKeys, useCursor); return executeSQL(sql, spName, parameters, false, useCursor); } } else { return executeSQL(sql, procName, parameters, false, useCursor); } } @Override public void setByte(int parameterIndex, byte x) throws SQLException { setParameter(parameterIndex, new Integer((x & 0xFF)), java.sql.Types.TINYINT, 0, 0); } @Override public void setDouble(int parameterIndex, double x) throws SQLException { setParameter(parameterIndex, new Double(x), java.sql.Types.DOUBLE, 0, 0); } @Override public void setFloat(int parameterIndex, float x) throws SQLException { setParameter(parameterIndex, new Float(x), java.sql.Types.REAL, 0, 0); } @Override public void setInt(int parameterIndex, int x) throws SQLException { setParameter(parameterIndex, new Integer(x), java.sql.Types.INTEGER, 0, 0); } @Override public void setNull(int parameterIndex, int sqlType) throws SQLException { if (sqlType == java.sql.Types.CLOB) { sqlType = java.sql.Types.LONGVARCHAR; } else if (sqlType == java.sql.Types.BLOB) { sqlType = java.sql.Types.LONGVARBINARY; } setParameter(parameterIndex, null, sqlType, -1, 0); } @Override public void setLong(int parameterIndex, long x) throws SQLException { setParameter(parameterIndex, new Long(x), java.sql.Types.BIGINT, 0, 0); } @Override public void setShort(int parameterIndex, short x) throws SQLException { setParameter(parameterIndex, new Integer(x), java.sql.Types.SMALLINT, 0, 0); } @Override public void setBoolean(int parameterIndex, boolean x) throws SQLException { setParameter(parameterIndex, x ? Boolean.TRUE : Boolean.FALSE, BOOLEAN, 0, 0); } @Override public void setBytes(int parameterIndex, byte[] x) throws SQLException { setParameter(parameterIndex, x, java.sql.Types.BINARY, 0, 0); } @Override public void setAsciiStream(int parameterIndex, InputStream inputStream, int length) throws SQLException { if (inputStream == null || length < 0) { setParameter(parameterIndex, null, java.sql.Types.LONGVARCHAR, 0, 0); } else { try { setCharacterStream(parameterIndex, new InputStreamReader(inputStream, "US-ASCII"), length); } catch (UnsupportedEncodingException e) { // Should never happen! } } } @Override public void setBinaryStream(int parameterIndex, InputStream x, int length) throws SQLException { checkOpen(); if (x == null || length < 0) { setBytes(parameterIndex, null); } else { setParameter(parameterIndex, x, java.sql.Types.LONGVARBINARY, 0, length); } } @Override public void setUnicodeStream(int parameterIndex, InputStream inputStream, int length) throws SQLException { checkOpen(); if (inputStream == null || length < 0) { setString(parameterIndex, null); } else { try { length /= 2; char[] tmp = new char[length]; int pos = 0; int b1 = inputStream.read(); int b2 = inputStream.read(); while (b1 >= 0 && b2 >= 0 && pos < length) { tmp[pos++] = (char) (((b1 << 8) &0xFF00) | (b2 & 0xFF)); b1 = inputStream.read(); b2 = inputStream.read(); } setString(parameterIndex, new String(tmp, 0, pos)); } catch (java.io.IOException e) { throw new SQLException(Messages.get("error.generic.ioerror", e.getMessage()), "HY000"); } } } @Override public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException { if (reader == null || length < 0) { setParameter(parameterIndex, null, java.sql.Types.LONGVARCHAR, 0, 0); } else { setParameter(parameterIndex, reader, java.sql.Types.LONGVARCHAR, 0, length); } } @Override public void setObject(int parameterIndex, Object x) throws SQLException { setObjectBase(parameterIndex, x, Support.getJdbcType(x), -1); } @Override public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException { setObjectBase(parameterIndex, x, targetSqlType, -1); } @Override public void setObject(int parameterIndex, Object x, int targetSqlType, int scale) throws SQLException { checkOpen(); if (scale < 0 || scale > connection.getMaxPrecision()) { throw new SQLException(Messages.get("error.generic.badscale"), "HY092"); } setObjectBase(parameterIndex, x, targetSqlType, scale); } @Override public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException { notImplemented("PreparedStatement.setNull(int, int, String)"); } @Override public void setString(int parameterIndex, String x) throws SQLException { setParameter(parameterIndex, x, java.sql.Types.VARCHAR, 0, 0); } @Override public void setBigDecimal(int parameterIndex, BigDecimal x) throws SQLException { setParameter(parameterIndex, x, java.sql.Types.DECIMAL, -1, 0); } @Override public void setURL(int parameterIndex, URL url) throws SQLException { setString(parameterIndex, (url == null)? null: url.toString()); } @Override public void setArray(int arg0, Array arg1) throws SQLException { notImplemented("PreparedStatement.setArray"); } @Override public void setBlob(int parameterIndex, Blob x) throws SQLException { if (x == null) { setBytes(parameterIndex, null); } else { long length = x.length(); if (length > Integer.MAX_VALUE) { throw new SQLException(Messages.get("error.resultset.longblob"), "24000"); } setBinaryStream(parameterIndex, x.getBinaryStream(), (int) x.length()); } } @Override public void setClob(int parameterIndex, Clob x) throws SQLException { if (x == null) { setString(parameterIndex, null); } else { long length = x.length(); if (length > Integer.MAX_VALUE) { throw new SQLException(Messages.get("error.resultset.longclob"), "24000"); } setCharacterStream(parameterIndex, x.getCharacterStream(), (int) x.length()); } } @Override public void setDate(int parameterIndex, Date x) throws SQLException { setParameter(parameterIndex, x, java.sql.Types.DATE, 0, 0); } @Override public ParameterMetaData getParameterMetaData() throws SQLException { checkOpen(); // // NB. This is usable only with the JDBC3 version of the interface. // if (connection.getServerType() == Driver.SYBASE) { // Sybase does return the parameter types for prepared sql. connection.prepareSQL(this, sql, new ParamInfo[0], false, false); } try { Class pmdClass = Class.forName("net.sourceforge.jtds.jdbc.ParameterMetaDataImpl"); Class[] parameterTypes = new Class[] {ParamInfo[].class, JtdsConnection.class}; Object[] arguments = new Object[] {parameters, connection}; Constructor pmdConstructor = pmdClass.getConstructor(parameterTypes); return (ParameterMetaData) pmdConstructor.newInstance(arguments); } catch (Exception e) { notImplemented("PreparedStatement.getParameterMetaData"); } return null; } @Override public void setRef(int parameterIndex, Ref x) throws SQLException { notImplemented("PreparedStatement.setRef"); } @Override public ResultSet executeQuery() throws SQLException { checkOpen(); reset(); boolean useCursor = useCursor(false, null); if (procName == null && !(this instanceof JtdsCallableStatement)) { // Sync on the connection to make sure rollback() isn't called // between the moment when the statement is prepared and the moment // when it's executed. synchronized (connection) { String spName = connection.prepareSQL(this, sql, parameters, false, useCursor); return executeSQLQuery(sql, spName, parameters, useCursor); } } else { return executeSQLQuery(sql, procName, parameters, useCursor); } } @Override public ResultSetMetaData getMetaData() throws SQLException { checkOpen(); if( colMetaData == null ) { if( currentResult != null ) { colMetaData = currentResult.columns; } else if( connection.getServerType() == Driver.SYBASE ) { // Sybase can provide meta data as a by product of preparing the call connection.prepareSQL( this, sql, new ParamInfo[0], false, false ); } else { // For Microsoft set all parameters to null and execute the query. // SET FMTONLY ON asks the server just to return meta data. // This only works for select statements if( "select".equals( sqlWord ) || "with".equals( sqlWord ) ) { // copy parameters to avoid corrupting any values already set // by the user as we need to set a flag and null out the data. ParamInfo[] params = new ParamInfo[parameters.length]; for( int i = 0; i < params.length; i ++ ) { params[i] = new ParamInfo( parameters[i].markerPos, false ); params[i].isSet = true; } // substitute nulls into SQL String StringBuilder testSql = new StringBuilder( sql.length() + 128 ); testSql.append( "SET FMTONLY ON; " ); testSql.append( Support.substituteParameters( sql, params, connection ) ); testSql.append( "\r\n; SET FMTONLY OFF" ); try { tds.submitSQL( testSql.toString() ); colMetaData = tds.getColumns(); } catch( SQLException e ) { // ensure FMTONLY is switched off! tds.submitSQL( "SET FMTONLY OFF" ); } } } } return colMetaData == null ? null : new JtdsResultSetMetaData( colMetaData, JtdsResultSet.getColumnCount( colMetaData ), connection.getUseLOBs() ); } @Override public void setTime(int parameterIndex, Time x) throws SQLException { setParameter( parameterIndex, x, java.sql.Types.TIME, 0, 0 ); } @Override public void setTimestamp(int parameterIndex, Timestamp x) throws SQLException { setParameter(parameterIndex, x, java.sql.Types.TIMESTAMP, 0, 0); } @Override public void setDate(int parameterIndex, Date x, Calendar cal) throws SQLException { if (x != null && cal != null) { x = new java.sql.Date(Support.timeFromZone(x, cal)); } setDate(parameterIndex, x); } @Override public void setTime(int parameterIndex, Time x, Calendar cal) throws SQLException { if (x != null && cal != null) { x = new Time(Support.timeFromZone(x, cal)); } setTime(parameterIndex, x); } @Override public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) throws SQLException { if (x != null && cal != null) { x = new java.sql.Timestamp(Support.timeFromZone(x, cal)); } setTimestamp(parameterIndex, x); } @Override public int executeUpdate(String sql) throws SQLException { notSupported("executeUpdate(String)"); return 0; } @Override public void addBatch(String sql) throws SQLException { notSupported("executeBatch(String)"); } @Override public boolean execute(String sql) throws SQLException { notSupported("execute(String)"); return false; } @Override public int executeUpdate(String sql, int getKeys) throws SQLException { notSupported("executeUpdate(String, int)"); return 0; } @Override public boolean execute(String arg0, int arg1) throws SQLException { notSupported("execute(String, int)"); return false; } @Override public int executeUpdate(String arg0, int[] arg1) throws SQLException { notSupported("executeUpdate(String, int[])"); return 0; } @Override public boolean execute(String arg0, int[] arg1) throws SQLException { notSupported("execute(String, int[])"); return false; } @Override public int executeUpdate(String arg0, String[] arg1) throws SQLException { notSupported("executeUpdate(String, String[])"); return 0; } @Override public boolean execute(String arg0, String[] arg1) throws SQLException { notSupported("execute(String, String[])"); return false; } @Override public ResultSet executeQuery(String sql) throws SQLException { notSupported("executeQuery(String)"); return null; } /////// JDBC4 demarcation, do NOT put any JDBC3 code below this line /////// /* (non-Javadoc) * @see java.sql.PreparedStatement#setAsciiStream(int, java.io.InputStream) */ @Override public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException { // TODO Auto-generated method stub throw new AbstractMethodError(); } /* (non-Javadoc) * @see java.sql.PreparedStatement#setAsciiStream(int, java.io.InputStream, long) */ @Override public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException { // TODO Auto-generated method stub throw new AbstractMethodError(); } /* (non-Javadoc) * @see java.sql.PreparedStatement#setBinaryStream(int, java.io.InputStream) */ @Override public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException { // TODO Auto-generated method stub throw new AbstractMethodError(); } /* (non-Javadoc) * @see java.sql.PreparedStatement#setBinaryStream(int, java.io.InputStream, long) */ @Override public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException { // TODO Auto-generated method stub throw new AbstractMethodError(); } /* (non-Javadoc) * @see java.sql.PreparedStatement#setBlob(int, java.io.InputStream) */ @Override public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException { // TODO Auto-generated method stub throw new AbstractMethodError(); } /* (non-Javadoc) * @see java.sql.PreparedStatement#setBlob(int, java.io.InputStream, long) */ @Override public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException { // TODO Auto-generated method stub throw new AbstractMethodError(); } /* (non-Javadoc) * @see java.sql.PreparedStatement#setCharacterStream(int, java.io.Reader) */ @Override public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException { // TODO Auto-generated method stub throw new AbstractMethodError(); } /* (non-Javadoc) * @see java.sql.PreparedStatement#setCharacterStream(int, java.io.Reader, long) */ @Override public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException { // TODO Auto-generated method stub throw new AbstractMethodError(); } /* (non-Javadoc) * @see java.sql.PreparedStatement#setClob(int, java.io.Reader) */ @Override public void setClob(int parameterIndex, Reader reader) throws SQLException { // TODO Auto-generated method stub throw new AbstractMethodError(); } /* (non-Javadoc) * @see java.sql.PreparedStatement#setClob(int, java.io.Reader, long) */ @Override public void setClob(int parameterIndex, Reader reader, long length) throws SQLException { // TODO Auto-generated method stub throw new AbstractMethodError(); } /* (non-Javadoc) * @see java.sql.PreparedStatement#setNCharacterStream(int, java.io.Reader) */ @Override public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException { // TODO Auto-generated method stub throw new AbstractMethodError(); } /* (non-Javadoc) * @see java.sql.PreparedStatement#setNCharacterStream(int, java.io.Reader, long) */ @Override public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException { // TODO Auto-generated method stub throw new AbstractMethodError(); } /* (non-Javadoc) * @see java.sql.PreparedStatement#setNClob(int, java.sql.NClob) */ @Override public void setNClob(int parameterIndex, NClob value) throws SQLException { // TODO Auto-generated method stub throw new AbstractMethodError(); } /* (non-Javadoc) * @see java.sql.PreparedStatement#setNClob(int, java.io.Reader) */ @Override public void setNClob(int parameterIndex, Reader reader) throws SQLException { // TODO Auto-generated method stub throw new AbstractMethodError(); } /* (non-Javadoc) * @see java.sql.PreparedStatement#setNClob(int, java.io.Reader, long) */ @Override public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException { // TODO Auto-generated method stub throw new AbstractMethodError(); } /* (non-Javadoc) * @see java.sql.PreparedStatement#setNString(int, java.lang.String) */ @Override public void setNString(int parameterIndex, String value) throws SQLException { // TODO Auto-generated method stub throw new AbstractMethodError(); } /* (non-Javadoc) * @see java.sql.PreparedStatement#setRowId(int, java.sql.RowId) */ @Override public void setRowId(int parameterIndex, RowId x) throws SQLException { // TODO Auto-generated method stub throw new AbstractMethodError(); } /* (non-Javadoc) * @see java.sql.PreparedStatement#setSQLXML(int, java.sql.SQLXML) */ @Override public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException { // TODO Auto-generated method stub throw new AbstractMethodError(); } }