001: // jTDS JDBC Driver for Microsoft SQL Server and Sybase
002: // Copyright (C) 2004 The jTDS Project
003: //
004: // This library is free software; you can redistribute it and/or
005: // modify it under the terms of the GNU Lesser General Public
006: // License as published by the Free Software Foundation; either
007: // version 2.1 of the License, or (at your option) any later version.
008: //
009: // This library is distributed in the hope that it will be useful,
010: // but WITHOUT ANY WARRANTY; without even the implied warranty of
011: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: // Lesser General Public License for more details.
013: //
014: // You should have received a copy of the GNU Lesser General Public
015: // License along with this library; if not, write to the Free Software
016: // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: //
018: package net.sourceforge.jtds.jdbc;
019:
020: import java.io.InputStream;
021: import java.io.Reader;
022: import java.math.BigDecimal;
023: import java.net.URL;
024: import java.sql.Array;
025: import java.sql.Blob;
026: import java.sql.Clob;
027: import java.sql.Date;
028: import java.sql.ParameterMetaData;
029: import java.sql.PreparedStatement;
030: import java.sql.Ref;
031: import java.sql.ResultSet;
032: import java.sql.ResultSetMetaData;
033: import java.sql.SQLException;
034: import java.sql.Time;
035: import java.sql.Timestamp;
036: import java.util.Calendar;
037: import java.util.ArrayList;
038: import java.util.Collection;
039: import java.io.InputStreamReader;
040: import java.io.UnsupportedEncodingException;
041: import java.lang.reflect.Constructor;
042: import java.text.NumberFormat;
043:
044: /**
045: * jTDS implementation of the java.sql.PreparedStatement interface.
046: * <p>
047: * Implementation notes:
048: * <ol>
049: * <li>Generally a simple subclass of Statement mainly adding support for the
050: * setting of parameters.
051: * <li>The stream logic is taken over from the work Brian did to add Blob support
052: * to the original jTDS.
053: * <li>Use of Statement specific method calls eg executeQuery(sql) is blocked by
054: * this version of the driver. This is unlike the original jTDS but inline
055: * with all the other JDBC drivers that I have been able to test.
056: * </ol>
057: *
058: * @author Mike Hutchinson
059: * @author Brian Heineman
060: * @version $Id: JtdsPreparedStatement.java,v 1.63 2007/07/12 21:03:23 bheineman Exp $
061: */
062: public class JtdsPreparedStatement extends JtdsStatement implements
063: PreparedStatement {
064: /** The SQL statement being prepared. */
065: protected final String sql;
066: /** The first SQL keyword in the SQL string.*/
067: protected String sqlWord;
068: /** The procedure name for CallableStatements. */
069: protected String procName;
070: /** The parameter list for the call. */
071: protected ParamInfo[] parameters;
072: /** True to return generated keys. */
073: private boolean returnKeys;
074: /** The cached parameter meta data. */
075: protected ParamInfo[] paramMetaData;
076: /** Used to format numeric values when scale is specified. */
077: private final static NumberFormat f = NumberFormat.getInstance();
078: /** Collection of handles used by this statement */
079: Collection handles;
080:
081: /**
082: * Construct a new preparedStatement object.
083: *
084: * @param connection The parent connection.
085: * @param sql The SQL statement to prepare.
086: * @param resultSetType The result set type eg SCROLLABLE etc.
087: * @param concurrency The result set concurrency eg READONLY.
088: * @param returnKeys True if generated keys should be returned.
089: * @throws SQLException
090: */
091: JtdsPreparedStatement(ConnectionJDBC2 connection, String sql,
092: int resultSetType, int concurrency, boolean returnKeys)
093: throws SQLException {
094: super (connection, resultSetType, concurrency);
095:
096: // Parse the SQL looking for escapes and parameters
097: if (this instanceof JtdsCallableStatement) {
098: sql = normalizeCall(sql);
099: }
100:
101: ArrayList params = new ArrayList();
102: String[] parsedSql = SQLParser.parse(sql, params, connection,
103: false);
104:
105: if (parsedSql[0].length() == 0) {
106: throw new SQLException(Messages.get("error.prepare.nosql"),
107: "07000");
108: }
109:
110: if (parsedSql[1].length() > 1) {
111: if (this instanceof JtdsCallableStatement) {
112: // Procedure call
113: this .procName = parsedSql[1];
114: }
115: }
116: sqlWord = parsedSql[2];
117:
118: if (returnKeys && "insert".equals(sqlWord)) {
119: if (connection.getServerType() == Driver.SQLSERVER
120: && connection.getDatabaseMajorVersion() >= 8) {
121: this .sql = parsedSql[0]
122: + " SELECT SCOPE_IDENTITY() AS ID";
123: } else {
124: this .sql = parsedSql[0] + " SELECT @@IDENTITY AS ID";
125: }
126: this .returnKeys = true;
127: } else {
128: this .sql = parsedSql[0];
129: this .returnKeys = false;
130: }
131:
132: parameters = (ParamInfo[]) params.toArray(new ParamInfo[params
133: .size()]);
134: }
135:
136: /**
137: * This method converts native call syntax into (hopefully) valid JDBC
138: * escape syntax.
139: * <p/>
140: * <b>Note:</b> This method is required for backwards compatibility with
141: * previous versions of jTDS. Strictly speaking only the JDBC syntax needs
142: * to be recognised, constructions such as "?=#testproc ?,?" are neither
143: * valid native syntax nor valid escapes. All the substrings and trims
144: * below are not as bad as they look. The objects created all refer back to
145: * the original sql string it is just the start and length positions which
146: * change.
147: *
148: * @param sql the SQL statement to process
149: * @return the SQL, possibly in original form
150: */
151: protected static String normalizeCall(String sql) {
152: String original = sql;
153: sql = sql.trim();
154:
155: if (sql.length() > 0 && sql.charAt(0) == '{') {
156: return original; // Assume already escaped
157: }
158:
159: if (sql.length() > 4
160: && sql.substring(0, 5).equalsIgnoreCase("exec ")) {
161: sql = sql.substring(4).trim();
162: } else if (sql.length() > 7
163: && sql.substring(0, 8).equalsIgnoreCase("execute ")) {
164: sql = sql.substring(7).trim();
165: }
166:
167: if (sql.length() > 1 && sql.charAt(0) == '?') {
168: sql = sql.substring(1).trim();
169:
170: if (sql.length() < 1 || sql.charAt(0) != '=') {
171: return original; // Give up, error will be reported elsewhere
172: }
173:
174: sql = sql.substring(1).trim();
175:
176: // OK now reconstruct as JDBC escaped call
177: return "{?=call " + sql + '}';
178: }
179:
180: return "{call " + sql + '}';
181: }
182:
183: /**
184: * Check that this statement is still open.
185: *
186: * @throws SQLException if statement closed.
187: */
188: protected void checkOpen() throws SQLException {
189: if (closed) {
190: throw new SQLException(Messages.get("error.generic.closed",
191: "PreparedStatement"), "HY010");
192: }
193: }
194:
195: /**
196: * Report that user tried to call a method not supported on this type of statement.
197: *
198: * @param method The method name to report in the error message.
199: * @throws SQLException
200: */
201: protected void notSupported(String method) throws SQLException {
202: throw new SQLException(Messages.get("error.generic.notsup",
203: method), "HYC00");
204: }
205:
206: /**
207: * Execute the SQL batch on a MS server.
208: * <p/>
209: * When running with <code>prepareSQL=1</code> or <code>3</code>, the driver will first prepare temporary stored
210: * procedures or statements for each parameter combination found in the batch. The handles to these pre-preared
211: * statements will then be used to execute the actual batch statements.
212: *
213: * @param size the total size of the batch
214: * @param executeSize the maximum number of statements to send in one request
215: * @param counts the returned update counts
216: * @return chained exceptions linked to a <code>SQLException</code>
217: * @throws SQLException if a serious error occurs during execution
218: */
219: protected SQLException executeMSBatch(int size, int executeSize,
220: ArrayList counts) throws SQLException {
221: if (parameters.length == 0) {
222: // There are no parameters, each SQL call is the same so execute as a simple batch
223: return super .executeMSBatch(size, executeSize, counts);
224: }
225: SQLException sqlEx = null;
226: String procHandle[] = null;
227:
228: // Prepare any statements before executing the batch
229: if (connection.getPrepareSql() == TdsCore.TEMPORARY_STORED_PROCEDURES
230: || connection.getPrepareSql() == TdsCore.PREPARE) {
231: procHandle = new String[size];
232: for (int i = 0; i < size; i++) {
233: // Prepare the statement
234: procHandle[i] = connection.prepareSQL(this , sql,
235: (ParamInfo[]) batchValues.get(i), false, false);
236: }
237: }
238:
239: for (int i = 0; i < size;) {
240: Object value = batchValues.get(i);
241: if (procHandle != null) {
242: procName = procHandle[i];
243: }
244: ++i;
245: // Execute batch now if max size reached or end of batch
246: boolean executeNow = (i % executeSize == 0) || i == size;
247:
248: tds.startBatch();
249: tds.executeSQL(sql, procName, (ParamInfo[]) value, false,
250: 0, -1, -1, executeNow);
251:
252: // If the batch has been sent, process the results
253: if (executeNow) {
254: sqlEx = tds.getBatchCounts(counts, sqlEx);
255:
256: // If a serious error then we stop execution now as count
257: // is too small.
258: if (sqlEx != null && counts.size() != i) {
259: break;
260: }
261: }
262: }
263: return sqlEx;
264: }
265:
266: /**
267: * Execute the SQL batch on a Sybase server.
268: * <p/>
269: * Sybase needs to have the SQL concatenated into one TDS language packet followed by up to 1000 parameters. This
270: * method will be overriden for <code>CallableStatements</code>.
271: *
272: * @param size the total size of the batch
273: * @param executeSize the maximum number of statements to send in one request
274: * @param counts the returned update counts
275: * @return chained exceptions linked to a <code>SQLException</code>
276: * @throws SQLException if a serious error occurs during execution
277: */
278: protected SQLException executeSybaseBatch(int size,
279: int executeSize, ArrayList counts) throws SQLException {
280: if (parameters.length == 0) {
281: // There are no parameters each SQL call is the same so
282: // execute as a simple batch
283: return super .executeSybaseBatch(size, executeSize, counts);
284: }
285: // Revise the executeSize down if too many parameters will be required.
286: // Be conservative the actual maximums are 256 for older servers and 2048.
287: int maxParams = (connection.getDatabaseMajorVersion() < 12 || (connection
288: .getDatabaseMajorVersion() == 12 && connection
289: .getDatabaseMinorVersion() < 50)) ? 200 : 1000;
290: StringBuffer sqlBuf = new StringBuffer(size * 32);
291: SQLException sqlEx = null;
292: if (parameters.length * executeSize > maxParams) {
293: executeSize = maxParams / parameters.length;
294: if (executeSize == 0) {
295: executeSize = 1;
296: }
297: }
298: ArrayList paramList = new ArrayList();
299: for (int i = 0; i < size;) {
300: Object value = batchValues.get(i);
301: ++i;
302: // Execute batch now if max size reached or end of batch
303: boolean executeNow = (i % executeSize == 0) || i == size;
304:
305: int offset = sqlBuf.length();
306: sqlBuf.append(sql).append(' ');
307: for (int n = 0; n < parameters.length; n++) {
308: ParamInfo p = ((ParamInfo[]) value)[n];
309: // Allow for the position of the '?' marker in the buffer
310: p.markerPos += offset;
311: paramList.add(p);
312: }
313: if (executeNow) {
314: ParamInfo args[];
315: args = (ParamInfo[]) paramList
316: .toArray(new ParamInfo[paramList.size()]);
317: tds.executeSQL(sqlBuf.toString(), null, args, false, 0,
318: -1, -1, true);
319: sqlBuf.setLength(0);
320: paramList.clear();
321: // If the batch has been sent, process the results
322: sqlEx = tds.getBatchCounts(counts, sqlEx);
323:
324: // If a serious error or a server error then we stop
325: // execution now as count is too small.
326: if (sqlEx != null && counts.size() != i) {
327: break;
328: }
329: }
330: }
331: return sqlEx;
332: }
333:
334: /**
335: * Check the supplied index and return the selected parameter.
336: *
337: * @param parameterIndex the parameter index 1 to n.
338: * @return the parameter as a <code>ParamInfo</code> object.
339: * @throws SQLException if the statement is closed;
340: * if <code>parameterIndex</code> is less than 0;
341: * if <code>parameterIndex</code> is greater than the
342: * number of parameters;
343: * if <code>checkIfSet</code> was <code>true</code>
344: * and the parameter was not set
345: */
346: protected ParamInfo getParameter(int parameterIndex)
347: throws SQLException {
348: checkOpen();
349:
350: if (parameterIndex < 1 || parameterIndex > parameters.length) {
351: throw new SQLException(Messages.get(
352: "error.prepare.paramindex", Integer
353: .toString(parameterIndex)), "07009");
354: }
355:
356: return parameters[parameterIndex - 1];
357: }
358:
359: /**
360: * Generic setObject method.
361: *
362: * @param parameterIndex Parameter index 1 to n.
363: * @param x The value to set.
364: * @param targetSqlType The java.sql.Types constant describing the data.
365: * @param scale The decimal scale -1 if not set.
366: */
367: public void setObjectBase(int parameterIndex, Object x,
368: int targetSqlType, int scale) throws SQLException {
369: checkOpen();
370:
371: int length = 0;
372:
373: if (targetSqlType == java.sql.Types.CLOB) {
374: targetSqlType = java.sql.Types.LONGVARCHAR;
375: } else if (targetSqlType == java.sql.Types.BLOB) {
376: targetSqlType = java.sql.Types.LONGVARBINARY;
377: }
378:
379: if (x != null) {
380: x = Support.convert(this , x, targetSqlType, connection
381: .getCharset());
382:
383: if (scale >= 0) {
384: if (x instanceof BigDecimal) {
385: x = ((BigDecimal) x).setScale(scale,
386: BigDecimal.ROUND_HALF_UP);
387: } else if (x instanceof Number) {
388: synchronized (f) {
389: f.setGroupingUsed(false);
390: f.setMaximumFractionDigits(scale);
391: x = Support.convert(this , f.format(x),
392: targetSqlType, connection.getCharset());
393: }
394: }
395: }
396:
397: if (x instanceof Blob) {
398: Blob blob = (Blob) x;
399: length = (int) blob.length();
400: x = blob.getBinaryStream();
401: } else if (x instanceof Clob) {
402: Clob clob = (Clob) x;
403: length = (int) clob.length();
404: x = clob.getCharacterStream();
405: }
406: }
407:
408: setParameter(parameterIndex, x, targetSqlType, scale, length);
409: }
410:
411: /**
412: * Update the ParamInfo object for the specified parameter.
413: *
414: * @param parameterIndex Parameter index 1 to n.
415: * @param x The value to set.
416: * @param targetSqlType The java.sql.Types constant describing the data.
417: * @param scale The decimal scale -1 if not set.
418: * @param length The length of the data item.
419: */
420: protected void setParameter(int parameterIndex, Object x,
421: int targetSqlType, int scale, int length)
422: throws SQLException {
423: ParamInfo pi = getParameter(parameterIndex);
424:
425: if ("ERROR".equals(Support.getJdbcTypeName(targetSqlType))) {
426: throw new SQLException(Messages.get(
427: "error.generic.badtype", Integer
428: .toString(targetSqlType)), "HY092");
429: }
430:
431: // Update parameter descriptor
432: if (targetSqlType == java.sql.Types.DECIMAL
433: || targetSqlType == java.sql.Types.NUMERIC) {
434:
435: pi.precision = connection.getMaxPrecision();
436: if (x instanceof BigDecimal) {
437: x = Support.normalizeBigDecimal((BigDecimal) x,
438: pi.precision);
439: pi.scale = ((BigDecimal) x).scale();
440: } else {
441: pi.scale = (scale < 0) ? TdsData.DEFAULT_SCALE : scale;
442: }
443: } else {
444: pi.scale = (scale < 0) ? 0 : scale;
445: }
446:
447: if (x instanceof String) {
448: pi.length = ((String) x).length();
449: } else if (x instanceof byte[]) {
450: pi.length = ((byte[]) x).length;
451: } else {
452: pi.length = length;
453: }
454:
455: if (x instanceof Date) {
456: x = new DateTime((Date) x);
457: } else if (x instanceof Time) {
458: x = new DateTime((Time) x);
459: } else if (x instanceof Timestamp) {
460: x = new DateTime((Timestamp) x);
461: }
462:
463: pi.value = x;
464: pi.jdbcType = targetSqlType;
465: pi.isSet = true;
466: pi.isUnicode = connection.getUseUnicode();
467: }
468:
469: /**
470: * Update the cached column meta data information.
471: *
472: * @param value The Column meta data array.
473: */
474: void setColMetaData(ColInfo[] value) {
475: this .colMetaData = value;
476: }
477:
478: /**
479: * Update the cached parameter meta data information.
480: *
481: * @param value The Column meta data array.
482: */
483: void setParamMetaData(ParamInfo[] value) {
484: for (int i = 0; i < value.length && i < parameters.length; i++) {
485: if (!parameters[i].isSet) {
486: // Only update parameter descriptors if the user
487: // has not yet set them.
488: parameters[i].jdbcType = value[i].jdbcType;
489: parameters[i].isOutput = value[i].isOutput;
490: parameters[i].precision = value[i].precision;
491: parameters[i].scale = value[i].scale;
492: parameters[i].sqlType = value[i].sqlType;
493: }
494: }
495: }
496:
497: // -------------------- java.sql.PreparedStatement methods follow -----------------
498:
499: public void close() throws SQLException {
500: try {
501: super .close();
502: } finally {
503: // Null these fields to reduce memory usage while
504: // wating for Statement.finalize() to execute.
505: this .handles = null;
506: this .parameters = null;
507: }
508: }
509:
510: public int executeUpdate() throws SQLException {
511: checkOpen();
512: initialize();
513:
514: if (procName == null
515: && !(this instanceof JtdsCallableStatement)) {
516: // Sync on the connection to make sure rollback() isn't called
517: // between the moment when the statement is prepared and the moment
518: // when it's executed.
519: synchronized (connection) {
520: String spName = connection.prepareSQL(this , sql,
521: parameters, returnKeys, false);
522: executeSQL(sql, spName, parameters, returnKeys, true,
523: false);
524: }
525: } else {
526: executeSQL(sql, procName, parameters, returnKeys, true,
527: false);
528: }
529:
530: int res = getUpdateCount();
531: return res == -1 ? 0 : res;
532: }
533:
534: public void addBatch() throws SQLException {
535: checkOpen();
536:
537: if (batchValues == null) {
538: batchValues = new ArrayList();
539: }
540:
541: if (parameters.length == 0) {
542: // This is likely to be an error. Batch execution
543: // of a prepared statement with no parameters means
544: // exactly the same SQL will be executed each time!
545: batchValues.add(sql);
546: } else {
547: batchValues.add(parameters);
548:
549: ParamInfo tmp[] = new ParamInfo[parameters.length];
550:
551: for (int i = 0; i < parameters.length; ++i) {
552: tmp[i] = (ParamInfo) parameters[i].clone();
553: }
554:
555: parameters = tmp;
556: }
557: }
558:
559: public void clearParameters() throws SQLException {
560: checkOpen();
561:
562: for (int i = 0; i < parameters.length; i++) {
563: parameters[i].clearInValue();
564: }
565: }
566:
567: public boolean execute() throws SQLException {
568: checkOpen();
569: initialize();
570: boolean useCursor = useCursor(returnKeys, sqlWord);
571:
572: if (procName == null
573: && !(this instanceof JtdsCallableStatement)) {
574: // Sync on the connection to make sure rollback() isn't called
575: // between the moment when the statement is prepared and the moment
576: // when it's executed.
577: synchronized (connection) {
578: String spName = connection.prepareSQL(this , sql,
579: parameters, returnKeys, useCursor);
580: return executeSQL(sql, spName, parameters, returnKeys,
581: false, useCursor);
582: }
583: } else {
584: return executeSQL(sql, procName, parameters, returnKeys,
585: false, useCursor);
586: }
587: }
588:
589: public void setByte(int parameterIndex, byte x) throws SQLException {
590: setParameter(parameterIndex, new Integer((int) (x & 0xFF)),
591: java.sql.Types.TINYINT, 0, 0);
592: }
593:
594: public void setDouble(int parameterIndex, double x)
595: throws SQLException {
596: setParameter(parameterIndex, new Double(x),
597: java.sql.Types.DOUBLE, 0, 0);
598: }
599:
600: public void setFloat(int parameterIndex, float x)
601: throws SQLException {
602: setParameter(parameterIndex, new Float(x), java.sql.Types.REAL,
603: 0, 0);
604: }
605:
606: public void setInt(int parameterIndex, int x) throws SQLException {
607: setParameter(parameterIndex, new Integer(x),
608: java.sql.Types.INTEGER, 0, 0);
609: }
610:
611: public void setNull(int parameterIndex, int sqlType)
612: throws SQLException {
613: if (sqlType == java.sql.Types.CLOB) {
614: sqlType = java.sql.Types.LONGVARCHAR;
615: } else if (sqlType == java.sql.Types.BLOB) {
616: sqlType = java.sql.Types.LONGVARBINARY;
617: }
618:
619: setParameter(parameterIndex, null, sqlType, -1, 0);
620: }
621:
622: public void setLong(int parameterIndex, long x) throws SQLException {
623: setParameter(parameterIndex, new Long(x),
624: java.sql.Types.BIGINT, 0, 0);
625: }
626:
627: public void setShort(int parameterIndex, short x)
628: throws SQLException {
629: setParameter(parameterIndex, new Integer(x),
630: java.sql.Types.SMALLINT, 0, 0);
631: }
632:
633: public void setBoolean(int parameterIndex, boolean x)
634: throws SQLException {
635: setParameter(parameterIndex, x ? Boolean.TRUE : Boolean.FALSE,
636: BOOLEAN, 0, 0);
637: }
638:
639: public void setBytes(int parameterIndex, byte[] x)
640: throws SQLException {
641: setParameter(parameterIndex, x, java.sql.Types.BINARY, 0, 0);
642: }
643:
644: public void setAsciiStream(int parameterIndex,
645: InputStream inputStream, int length) throws SQLException {
646: if (inputStream == null || length < 0) {
647: setParameter(parameterIndex, null,
648: java.sql.Types.LONGVARCHAR, 0, 0);
649: } else {
650: try {
651: setCharacterStream(parameterIndex,
652: new InputStreamReader(inputStream, "US-ASCII"),
653: length);
654: } catch (UnsupportedEncodingException e) {
655: // Should never happen!
656: }
657: }
658: }
659:
660: public void setBinaryStream(int parameterIndex, InputStream x,
661: int length) throws SQLException {
662: checkOpen();
663:
664: if (x == null || length < 0) {
665: setBytes(parameterIndex, null);
666: } else {
667: setParameter(parameterIndex, x,
668: java.sql.Types.LONGVARBINARY, 0, length);
669: }
670: }
671:
672: public void setUnicodeStream(int parameterIndex,
673: InputStream inputStream, int length) throws SQLException {
674: checkOpen();
675: if (inputStream == null || length < 0) {
676: setString(parameterIndex, null);
677: } else {
678: try {
679: length /= 2;
680: char[] tmp = new char[length];
681: int pos = 0;
682: int b1 = inputStream.read();
683: int b2 = inputStream.read();
684:
685: while (b1 >= 0 && b2 >= 0 && pos < length) {
686: tmp[pos++] = (char) (((b1 << 8) & 0xFF00) | (b2 & 0xFF));
687: b1 = inputStream.read();
688: b2 = inputStream.read();
689: }
690: setString(parameterIndex, new String(tmp, 0, pos));
691: } catch (java.io.IOException e) {
692: throw new SQLException(Messages.get(
693: "error.generic.ioerror", e.getMessage()),
694: "HY000");
695: }
696: }
697: }
698:
699: public void setCharacterStream(int parameterIndex, Reader reader,
700: int length) throws SQLException {
701: if (reader == null || length < 0) {
702: setParameter(parameterIndex, null,
703: java.sql.Types.LONGVARCHAR, 0, 0);
704: } else {
705: setParameter(parameterIndex, reader,
706: java.sql.Types.LONGVARCHAR, 0, length);
707: }
708: }
709:
710: public void setObject(int parameterIndex, Object x)
711: throws SQLException {
712: setObjectBase(parameterIndex, x, Support.getJdbcType(x), -1);
713: }
714:
715: public void setObject(int parameterIndex, Object x,
716: int targetSqlType) throws SQLException {
717: setObjectBase(parameterIndex, x, targetSqlType, -1);
718: }
719:
720: public void setObject(int parameterIndex, Object x,
721: int targetSqlType, int scale) throws SQLException {
722: checkOpen();
723: if (scale < 0 || scale > connection.getMaxPrecision()) {
724: throw new SQLException(Messages
725: .get("error.generic.badscale"), "HY092");
726: }
727: setObjectBase(parameterIndex, x, targetSqlType, scale);
728: }
729:
730: public void setNull(int parameterIndex, int sqlType, String typeName)
731: throws SQLException {
732: notImplemented("PreparedStatement.setNull(int, int, String)");
733: }
734:
735: public void setString(int parameterIndex, String x)
736: throws SQLException {
737: setParameter(parameterIndex, x, java.sql.Types.VARCHAR, 0, 0);
738: }
739:
740: public void setBigDecimal(int parameterIndex, BigDecimal x)
741: throws SQLException {
742: setParameter(parameterIndex, x, java.sql.Types.DECIMAL, -1, 0);
743: }
744:
745: public void setURL(int parameterIndex, URL url) throws SQLException {
746: setString(parameterIndex, (url == null) ? null : url.toString());
747: }
748:
749: public void setArray(int arg0, Array arg1) throws SQLException {
750: notImplemented("PreparedStatement.setArray");
751: }
752:
753: public void setBlob(int parameterIndex, Blob x) throws SQLException {
754: if (x == null) {
755: setBytes(parameterIndex, null);
756: } else {
757: long length = x.length();
758:
759: if (length > Integer.MAX_VALUE) {
760: throw new SQLException(Messages
761: .get("error.resultset.longblob"), "24000");
762: }
763:
764: setBinaryStream(parameterIndex, x.getBinaryStream(),
765: (int) x.length());
766: }
767: }
768:
769: public void setClob(int parameterIndex, Clob x) throws SQLException {
770: if (x == null) {
771: this .setString(parameterIndex, null);
772: } else {
773: long length = x.length();
774:
775: if (length > Integer.MAX_VALUE) {
776: throw new SQLException(Messages
777: .get("error.resultset.longclob"), "24000");
778: }
779:
780: setCharacterStream(parameterIndex, x.getCharacterStream(),
781: (int) x.length());
782: }
783: }
784:
785: public void setDate(int parameterIndex, Date x) throws SQLException {
786: setParameter(parameterIndex, x, java.sql.Types.DATE, 0, 0);
787: }
788:
789: public ParameterMetaData getParameterMetaData() throws SQLException {
790: checkOpen();
791:
792: //
793: // NB. This is usable only with the JDBC3 version of the interface.
794: //
795: if (connection.getServerType() == Driver.SYBASE) {
796: // Sybase does return the parameter types for prepared sql.
797: connection.prepareSQL(this , sql, new ParamInfo[0], false,
798: false);
799: }
800:
801: try {
802: Class pmdClass = Class
803: .forName("net.sourceforge.jtds.jdbc.ParameterMetaDataImpl");
804: Class[] parameterTypes = new Class[] { ParamInfo[].class,
805: ConnectionJDBC2.class };
806: Object[] arguments = new Object[] { parameters, connection };
807: Constructor pmdConstructor = pmdClass
808: .getConstructor(parameterTypes);
809:
810: return (ParameterMetaData) pmdConstructor
811: .newInstance(arguments);
812: } catch (Exception e) {
813: notImplemented("PreparedStatement.getParameterMetaData");
814: }
815:
816: return null;
817: }
818:
819: public void setRef(int parameterIndex, Ref x) throws SQLException {
820: notImplemented("PreparedStatement.setRef");
821: }
822:
823: public ResultSet executeQuery() throws SQLException {
824: checkOpen();
825: initialize();
826: boolean useCursor = useCursor(false, null);
827:
828: if (procName == null
829: && !(this instanceof JtdsCallableStatement)) {
830: // Sync on the connection to make sure rollback() isn't called
831: // between the moment when the statement is prepared and the moment
832: // when it's executed.
833: synchronized (connection) {
834: String spName = connection.prepareSQL(this , sql,
835: parameters, false, useCursor);
836: return executeSQLQuery(sql, spName, parameters,
837: useCursor);
838: }
839: } else {
840: return executeSQLQuery(sql, procName, parameters, useCursor);
841: }
842: }
843:
844: public ResultSetMetaData getMetaData() throws SQLException {
845: checkOpen();
846:
847: if (colMetaData == null) {
848: if (currentResult != null) {
849: colMetaData = currentResult.columns;
850: } else if (connection.getServerType() == Driver.SYBASE) {
851: // Sybase can provide meta data as a by product of preparing the call.
852: connection.prepareSQL(this , sql, new ParamInfo[0],
853: false, false);
854:
855: if (colMetaData == null) {
856: return null; // Sorry still no go
857: }
858: } else {
859: // For Microsoft set all parameters to null and execute the query.
860: // SET FMTONLY ON asks the server just to return meta data.
861: // This only works for select statements
862: if (!"select".equals(sqlWord)) {
863: return null;
864: }
865:
866: // Copy parameters to avoid corrupting any values already set
867: // by the user as we need to set a flag and null out the data.
868: ParamInfo[] params = new ParamInfo[parameters.length];
869: for (int i = 0; i < params.length; i++) {
870: params[i] = new ParamInfo(parameters[i].markerPos,
871: false);
872: params[i].isSet = true;
873: }
874:
875: // Substitute nulls into SQL String
876: StringBuffer testSql = new StringBuffer(
877: sql.length() + 128);
878: testSql.append("SET FMTONLY ON ");
879: testSql.append(Support.substituteParameters(sql,
880: params, connection));
881: testSql.append(" SET FMTONLY OFF");
882:
883: try {
884: tds.submitSQL(testSql.toString());
885: colMetaData = tds.getColumns();
886: } catch (SQLException e) {
887: // Ensure FMTONLY is switched off!
888: tds.submitSQL("SET FMTONLY OFF");
889: return null;
890: }
891: }
892: }
893:
894: return new JtdsResultSetMetaData(colMetaData, JtdsResultSet
895: .getColumnCount(colMetaData), connection.getUseLOBs());
896: }
897:
898: public void setTime(int parameterIndex, Time x) throws SQLException {
899: setParameter(parameterIndex, x, java.sql.Types.TIME, 0, 0);
900: }
901:
902: public void setTimestamp(int parameterIndex, Timestamp x)
903: throws SQLException {
904: setParameter(parameterIndex, x, java.sql.Types.TIMESTAMP, 0, 0);
905: }
906:
907: public void setDate(int parameterIndex, Date x, Calendar cal)
908: throws SQLException {
909:
910: if (x != null && cal != null) {
911: x = new java.sql.Date(Support.timeFromZone(x, cal));
912: }
913:
914: setDate(parameterIndex, x);
915: }
916:
917: public void setTime(int parameterIndex, Time x, Calendar cal)
918: throws SQLException {
919:
920: if (x != null && cal != null) {
921: x = new Time(Support.timeFromZone(x, cal));
922: }
923:
924: setTime(parameterIndex, x);
925: }
926:
927: public void setTimestamp(int parameterIndex, Timestamp x,
928: Calendar cal) throws SQLException {
929:
930: if (x != null && cal != null) {
931: x = new java.sql.Timestamp(Support.timeFromZone(x, cal));
932: }
933:
934: setTimestamp(parameterIndex, x);
935: }
936:
937: public int executeUpdate(String sql) throws SQLException {
938: notSupported("executeUpdate(String)");
939: return 0;
940: }
941:
942: public void addBatch(String sql) throws SQLException {
943: notSupported("executeBatch(String)");
944: }
945:
946: public boolean execute(String sql) throws SQLException {
947: notSupported("execute(String)");
948: return false;
949: }
950:
951: public int executeUpdate(String sql, int getKeys)
952: throws SQLException {
953: notSupported("executeUpdate(String, int)");
954: return 0;
955: }
956:
957: public boolean execute(String arg0, int arg1) throws SQLException {
958: notSupported("execute(String, int)");
959: return false;
960: }
961:
962: public int executeUpdate(String arg0, int[] arg1)
963: throws SQLException {
964: notSupported("executeUpdate(String, int[])");
965: return 0;
966: }
967:
968: public boolean execute(String arg0, int[] arg1) throws SQLException {
969: notSupported("execute(String, int[])");
970: return false;
971: }
972:
973: public int executeUpdate(String arg0, String[] arg1)
974: throws SQLException {
975: notSupported("executeUpdate(String, String[])");
976: return 0;
977: }
978:
979: public boolean execute(String arg0, String[] arg1)
980: throws SQLException {
981: notSupported("execute(String, String[])");
982: return false;
983: }
984:
985: public ResultSet executeQuery(String sql) throws SQLException {
986: notSupported("executeQuery(String)");
987: return null;
988: }
989: }
|