1 /*
2 Copyright 2002-2007 MySQL AB, 2008 Sun Microsystems
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of version 2 of the GNU General Public License as
6 published by the Free Software Foundation.
7
8 There are special exceptions to the terms and conditions of the GPL
9 as it is applied to this software. View the full text of the
10 exception in file EXCEPTIONS-CONNECTOR-J in the directory of this
11 software distribution.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
22
23
24 */
25 package com.mysql.jdbc;
26
27 import java.io.ByteArrayInputStream;
28 import java.io.ByteArrayOutputStream;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.io.ObjectOutputStream;
32 import java.io.Reader;
33 import java.io.StringReader;
34 import java.io.UnsupportedEncodingException;
35 import java.lang.reflect.Constructor;
36 import java.math.BigDecimal;
37 import java.math.BigInteger;
38 import java.net.URL;
39 import java.nio.ByteBuffer;
40 import java.nio.CharBuffer;
41 import java.nio.charset.Charset;
42 import java.nio.charset.CharsetEncoder;
43 import java.sql.Array;
44 import java.sql.Clob;
45 import java.sql.DatabaseMetaData;
46 import java.sql.Date;
47 import java.sql.ParameterMetaData;
48 import java.sql.Ref;
49 import java.sql.ResultSet;
50 import java.sql.SQLException;
51 import java.sql.Time;
52 import java.sql.Timestamp;
53 import java.sql.Types;
54 import java.text.ParsePosition;
55 import java.text.SimpleDateFormat;
56 import java.util.ArrayList;
57 import java.util.Calendar;
58 import java.util.Iterator;
59 import java.util.LinkedList;
60 import java.util.List;
61 import java.util.Locale;
62 import java.util.TimeZone;
63
64 import com.mysql.jdbc.exceptions.DeadlockTimeoutRollbackMarker;
65 import com.mysql.jdbc.exceptions.MySQLStatementCancelledException;
66 import com.mysql.jdbc.exceptions.MySQLTimeoutException;
67 import com.mysql.jdbc.exceptions.MySQLTransactionRollbackException;
68 import com.mysql.jdbc.profiler.ProfilerEvent;
69
70 /**
71 * A SQL Statement is pre-compiled and stored in a PreparedStatement object.
72 * This object can then be used to efficiently execute this statement multiple
73 * times.
74 *
75 * <p>
76 * <B>Note:</B> The setXXX methods for setting IN parameter values must specify
77 * types that are compatible with the defined SQL type of the input parameter.
78 * For instance, if the IN parameter has SQL type Integer, then setInt should be
79 * used.
80 * </p>
81 *
82 * <p>
83 * If arbitrary parameter type conversions are required, then the setObject
84 * method should be used with a target SQL type.
85 * </p>
86 *
87 * @author Mark Matthews
88 * @version $Id: PreparedStatement.java,v 1.1.2.1 2005/05/13 18:58:38 mmatthews
89 * Exp $
90 *
91 * @see java.sql.ResultSet
92 * @see java.sql.PreparedStatement
93 */
94 public class PreparedStatement extends com.mysql.jdbc.StatementImpl implements
95 java.sql.PreparedStatement {
96 private static final Constructor JDBC_4_PSTMT_2_ARG_CTOR;
97 private static final Constructor JDBC_4_PSTMT_3_ARG_CTOR;
98 private static final Constructor JDBC_4_PSTMT_4_ARG_CTOR;
99
100 static {
101 if (Util.isJdbc4()) {
102 try {
103 JDBC_4_PSTMT_2_ARG_CTOR = Class.forName(
104 "com.mysql.jdbc.JDBC4PreparedStatement")
105 .getConstructor(
106 new Class[] { ConnectionImpl.class, String.class });
107 JDBC_4_PSTMT_3_ARG_CTOR = Class.forName(
108 "com.mysql.jdbc.JDBC4PreparedStatement")
109 .getConstructor(
110 new Class[] { ConnectionImpl.class, String.class,
111 String.class });
112 JDBC_4_PSTMT_4_ARG_CTOR = Class.forName(
113 "com.mysql.jdbc.JDBC4PreparedStatement")
114 .getConstructor(
115 new Class[] { ConnectionImpl.class, String.class,
116 String.class, ParseInfo.class });
117 } catch (SecurityException e) {
118 throw new RuntimeException(e);
119 } catch (NoSuchMethodException e) {
120 throw new RuntimeException(e);
121 } catch (ClassNotFoundException e) {
122 throw new RuntimeException(e);
123 }
124 } else {
125 JDBC_4_PSTMT_2_ARG_CTOR = null;
126 JDBC_4_PSTMT_3_ARG_CTOR = null;
127 JDBC_4_PSTMT_4_ARG_CTOR = null;
128 }
129 }
130
131 class BatchParams {
132 boolean[] isNull = null;
133
134 boolean[] isStream = null;
135
136 InputStream[] parameterStreams = null;
137
138 byte[][] parameterStrings = null;
139
140 int[] streamLengths = null;
141
142 BatchParams(byte[][] strings, InputStream[] streams,
143 boolean[] isStreamFlags, int[] lengths, boolean[] isNullFlags) {
144 //
145 // Make copies
146 //
147 this.parameterStrings = new byte[strings.length][];
148 this.parameterStreams = new InputStream[streams.length];
149 this.isStream = new boolean[isStreamFlags.length];
150 this.streamLengths = new int[lengths.length];
151 this.isNull = new boolean[isNullFlags.length];
152 System.arraycopy(strings, 0, this.parameterStrings, 0,
153 strings.length);
154 System.arraycopy(streams, 0, this.parameterStreams, 0,
155 streams.length);
156 System.arraycopy(isStreamFlags, 0, this.isStream, 0,
157 isStreamFlags.length);
158 System.arraycopy(lengths, 0, this.streamLengths, 0, lengths.length);
159 System
160 .arraycopy(isNullFlags, 0, this.isNull, 0,
161 isNullFlags.length);
162 }
163 }
164
165 class EndPoint {
166 int begin;
167
168 int end;
169
170 EndPoint(int b, int e) {
171 this.begin = b;
172 this.end = e;
173 }
174 }
175
176 class ParseInfo {
177 char firstStmtChar = 0;
178
179 boolean foundLimitClause = false;
180
181 boolean foundLoadData = false;
182
183 long lastUsed = 0;
184
185 int statementLength = 0;
186
187 int statementStartPos = 0;
188
189 boolean canRewriteAsMultiValueInsert = false;
190
191 byte[][] staticSql = null;
192
193 boolean isOnDuplicateKeyUpdate = false;
194
195 int locationOfOnDuplicateKeyUpdate = -1;
196
197 String valuesClause;
198
199 boolean parametersInDuplicateKeyClause = false;
200
201 /**
202 * Represents the "parsed" state of a client-side
203 * prepared statement, with the statement broken up into
204 * it's static and dynamic (where parameters are bound)
205 * parts.
206 */
207 ParseInfo(String sql, ConnectionImpl conn,
208 java.sql.DatabaseMetaData dbmd, String encoding,
209 SingleByteCharsetConverter converter) throws SQLException {
210 this(sql, conn, dbmd, encoding, converter, true);
211 }
212
213 public ParseInfo(String sql, ConnectionImpl conn,
214 java.sql.DatabaseMetaData dbmd, String encoding,
215 SingleByteCharsetConverter converter, boolean buildRewriteInfo) throws SQLException {
216 try {
217 if (sql == null) {
218 throw SQLError.createSQLException(Messages
219 .getString("PreparedStatement.61"), //$NON-NLS-1$
220 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
221 }
222
223 this.locationOfOnDuplicateKeyUpdate = getOnDuplicateKeyLocation(sql);
224 this.isOnDuplicateKeyUpdate = this.locationOfOnDuplicateKeyUpdate != -1;
225
226 this.lastUsed = System.currentTimeMillis();
227
228 String quotedIdentifierString = dbmd.getIdentifierQuoteString();
229
230 char quotedIdentifierChar = 0;
231
232 if ((quotedIdentifierString != null)
233 && !quotedIdentifierString.equals(" ") //$NON-NLS-1$
234 && (quotedIdentifierString.length() > 0)) {
235 quotedIdentifierChar = quotedIdentifierString.charAt(0);
236 }
237
238 this.statementLength = sql.length();
239
240 ArrayList endpointList = new ArrayList();
241 boolean inQuotes = false;
242 char quoteChar = 0;
243 boolean inQuotedId = false;
244 int lastParmEnd = 0;
245 int i;
246
247 int stopLookingForLimitClause = this.statementLength - 5;
248
249 this.foundLimitClause = false;
250
251 boolean noBackslashEscapes = connection.isNoBackslashEscapesSet();
252
253 // we're not trying to be real pedantic here, but we'd like to
254 // skip comments at the beginning of statements, as frameworks
255 // such as Hibernate use them to aid in debugging
256
257 statementStartPos = findStartOfStatement(sql);
258
259 for (i = statementStartPos; i < this.statementLength; ++i) {
260 char c = sql.charAt(i);
261
262 if ((this.firstStmtChar == 0) && Character.isLetter(c)) {
263 // Determine what kind of statement we're doing (_S_elect,
264 // _I_nsert, etc.)
265 this.firstStmtChar = Character.toUpperCase(c);
266 }
267
268 if (!noBackslashEscapes &&
269 c == '\\' && i < (this.statementLength - 1)) {
270 i++;
271 continue; // next character is escaped
272 }
273
274 // are we in a quoted identifier?
275 // (only valid when the id is not inside a 'string')
276 if (!inQuotes && (quotedIdentifierChar != 0)
277 && (c == quotedIdentifierChar)) {
278 inQuotedId = !inQuotedId;
279 } else if (!inQuotedId) {
280 // only respect quotes when not in a quoted identifier
281
282 if (inQuotes) {
283 if (((c == '\'') || (c == '"')) && c == quoteChar) {
284 if (i < (this.statementLength - 1) && sql.charAt(i + 1) == quoteChar) {
285 i++;
286 continue; // inline quote escape
287 }
288
289 inQuotes = !inQuotes;
290 quoteChar = 0;
291 } else if (((c == '\'') || (c == '"')) && c == quoteChar) {
292 inQuotes = !inQuotes;
293 quoteChar = 0;
294 }
295 } else {
296 if (c == '#'
297 || (c == '-' && (i + 1) < this.statementLength && sql
298 .charAt(i + 1) == '-')) {
299 // run out to end of statement, or newline,
300 // whichever comes first
301 int endOfStmt = this.statementLength - 1;
302
303 for (; i < endOfStmt; i++) {
304 c = sql.charAt(i);
305
306 if (c == '\r' || c == '\n') {
307 break;
308 }
309 }
310
311 continue;
312 } else if (c == '/' && (i + 1) < this.statementLength) {
313 // Comment?
314 char cNext = sql.charAt(i + 1);
315
316 if (cNext == '*') {
317 i+= 2;
318
319 for (int j = i; j < this.statementLength; j++) {
320 i++;
321 cNext = sql.charAt(j);
322
323 if (cNext == '*' && (j + 1) < this.statementLength) {
324 if (sql.charAt(j + 1) == '/') {
325 i++;
326
327 if (i < this.statementLength) {
328 c = sql.charAt(i);
329 }
330
331 break; // comment done
332 }
333 }
334 }
335 }
336 } else if ((c == '\'') || (c == '"')) {
337 inQuotes = true;
338 quoteChar = c;
339 }
340 }
341 }
342
343 if ((c == '?') && !inQuotes && !inQuotedId) {
344 endpointList.add(new int[] { lastParmEnd, i });
345 lastParmEnd = i + 1;
346
347 if (isOnDuplicateKeyUpdate && i > locationOfOnDuplicateKeyUpdate) {
348 parametersInDuplicateKeyClause = true;
349 }
350 }
351
352 if (!inQuotes && (i < stopLookingForLimitClause)) {
353 if ((c == 'L') || (c == 'l')) {
354 char posI1 = sql.charAt(i + 1);
355
356 if ((posI1 == 'I') || (posI1 == 'i')) {
357 char posM = sql.charAt(i + 2);
358
359 if ((posM == 'M') || (posM == 'm')) {
360 char posI2 = sql.charAt(i + 3);
361
362 if ((posI2 == 'I') || (posI2 == 'i')) {
363 char posT = sql.charAt(i + 4);
364
365 if ((posT == 'T') || (posT == 't')) {
366 foundLimitClause = true;
367 }
368 }
369 }
370 }
371 }
372 }
373 }
374
375 if (this.firstStmtChar == 'L') {
376 if (StringUtils.startsWithIgnoreCaseAndWs(sql, "LOAD DATA")) { //$NON-NLS-1$
377 this.foundLoadData = true;
378 } else {
379 this.foundLoadData = false;
380 }
381 } else {
382 this.foundLoadData = false;
383 }
384
385 endpointList.add(new int[] { lastParmEnd, this.statementLength });
386 this.staticSql = new byte[endpointList.size()][];
387 char[] asCharArray = sql.toCharArray();
388
389 for (i = 0; i < this.staticSql.length; i++) {
390 int[] ep = (int[]) endpointList.get(i);
391 int end = ep[1];
392 int begin = ep[0];
393 int len = end - begin;
394
395 if (this.foundLoadData) {
396 String temp = new String(asCharArray, begin, len);
397 this.staticSql[i] = temp.getBytes();
398 } else if (encoding == null) {
399 byte[] buf = new byte[len];
400
401 for (int j = 0; j < len; j++) {
402 buf[j] = (byte) sql.charAt(begin + j);
403 }
404
405 this.staticSql[i] = buf;
406 } else {
407 if (converter != null) {
408 this.staticSql[i] = StringUtils.getBytes(sql,
409 converter, encoding, connection
410 .getServerCharacterEncoding(), begin,
411 len, connection.parserKnowsUnicode(), getExceptionInterceptor());
412 } else {
413 String temp = new String(asCharArray, begin, len);
414
415 this.staticSql[i] = StringUtils.getBytes(temp,
416 encoding, connection
417 .getServerCharacterEncoding(),
418 connection.parserKnowsUnicode(), conn, getExceptionInterceptor());
419 }
420 }
421 }
422 } catch (StringIndexOutOfBoundsException oobEx) {
423 SQLException sqlEx = new SQLException("Parse error for " + sql);
424 sqlEx.initCause(oobEx);
425
426 throw sqlEx;
427 }
428
429
430 if (buildRewriteInfo) {
431 this.canRewriteAsMultiValueInsert = PreparedStatement
432 .canRewrite(sql, this.isOnDuplicateKeyUpdate,
433 this.locationOfOnDuplicateKeyUpdate,
434 this.statementStartPos) && !this.parametersInDuplicateKeyClause;
435
436 if (this.canRewriteAsMultiValueInsert
437 && conn.getRewriteBatchedStatements()) {
438 buildRewriteBatchedParams(sql, conn, dbmd, encoding,
439 converter);
440 }
441 }
442 }
443
444 private ParseInfo batchHead;
445
446 private ParseInfo batchValues;
447
448 private ParseInfo batchODKUClause;
449
450 private void buildRewriteBatchedParams(String sql, ConnectionImpl conn,
451 DatabaseMetaData metadata, String encoding,
452 SingleByteCharsetConverter converter) throws SQLException {
453 this.valuesClause = extractValuesClause(sql);
454 String odkuClause = isOnDuplicateKeyUpdate ? sql
455 .substring(locationOfOnDuplicateKeyUpdate) : null;
456
457 String headSql = null;
458
459 if (isOnDuplicateKeyUpdate) {
460 headSql = sql.substring(0, locationOfOnDuplicateKeyUpdate);
461 } else {
462 headSql = sql;
463 }
464
465 this.batchHead = new ParseInfo(headSql, conn, metadata, encoding,
466 converter, false);
467 this.batchValues = new ParseInfo("," + this.valuesClause, conn,
468 metadata, encoding, converter, false);
469 this.batchODKUClause = null;
470
471 if (odkuClause != null && odkuClause.length() > 0) {
472 this.batchODKUClause = new ParseInfo("," + this.valuesClause
473 + " " + odkuClause, conn, metadata, encoding,
474 converter, false);
475 }
476 }
477
478 private String extractValuesClause(String sql) throws SQLException {
479 String quoteCharStr = connection.getMetaData()
480 .getIdentifierQuoteString();
481
482 int indexOfValues = -1;
483 int valuesSearchStart = statementStartPos;
484
485 while (indexOfValues == -1) {
486 if (quoteCharStr.length() > 0) {
487 indexOfValues = StringUtils.indexOfIgnoreCaseRespectQuotes(
488 valuesSearchStart,
489 originalSql, "VALUES", quoteCharStr.charAt(0), false);
490 } else {
491 indexOfValues = StringUtils.indexOfIgnoreCase(valuesSearchStart,
492 originalSql,
493 "VALUES");
494 }
495
496 if (indexOfValues > 0) {
497 /* check if the char immediately preceding VALUES may be part of the table name */
498 char c = originalSql.charAt(indexOfValues - 1);
499 if(!(Character.isWhitespace(c) || c == ')' || c == '`')){
500 valuesSearchStart = indexOfValues + 6;
501 indexOfValues = -1;
502 } else {
503 /* check if the char immediately following VALUES may be whitespace or open parenthesis */
504 c = originalSql.charAt(indexOfValues + 6);
505 if(!(Character.isWhitespace(c) || c == '(')){
506 valuesSearchStart = indexOfValues + 6;
507 indexOfValues = -1;
508 }
509 }
510 } else {
511 break;
512 }
513 }
514
515 if (indexOfValues == -1) {
516 return null;
517 }
518
519 int indexOfFirstParen = sql.indexOf('(', indexOfValues + 6);
520
521 if (indexOfFirstParen == -1) {
522 return null;
523 }
524
525 int endOfValuesClause = sql.lastIndexOf(')');
526
527 if (endOfValuesClause == -1) {
528 return null;
529 }
530
531 if (isOnDuplicateKeyUpdate) {
532 endOfValuesClause = this.locationOfOnDuplicateKeyUpdate - 1;
533 }
534
535 return sql.substring(indexOfFirstParen, endOfValuesClause + 1);
536 }
537
538 /**
539 * Returns a ParseInfo for a multi-value INSERT for a batch of size numBatch (without parsing!).
540 */
541 synchronized ParseInfo getParseInfoForBatch(int numBatch) {
542 AppendingBatchVisitor apv = new AppendingBatchVisitor();
543 buildInfoForBatch(numBatch, apv);
544
545 ParseInfo batchParseInfo = new ParseInfo(apv.getStaticSqlStrings(),
546 this.firstStmtChar, this.foundLimitClause,
547 this.foundLoadData, this.isOnDuplicateKeyUpdate,
548 this.locationOfOnDuplicateKeyUpdate, this.statementLength,
549 this.statementStartPos);
550
551 return batchParseInfo;
552 }
553
554 /**
555 * Returns a preparable SQL string for the number of batched parameters, used by server-side prepared statements
556 * when re-writing batch INSERTs.
557 */
558
559 String getSqlForBatch(int numBatch) throws UnsupportedEncodingException {
560 ParseInfo batchInfo = getParseInfoForBatch(numBatch);
561
562 return getSqlForBatch(batchInfo);
563 }
564
565 /**
566 * Used for filling in the SQL for getPreparedSql() - for debugging
567 */
568 String getSqlForBatch(ParseInfo batchInfo) throws UnsupportedEncodingException {
569 int size = 0;
570 final byte[][] sqlStrings = batchInfo.staticSql;
571 final int sqlStringsLength = sqlStrings.length;
572
573 for (int i = 0; i < sqlStringsLength; i++) {
574 size += sqlStrings[i].length;
575 size++; // for the '?'
576 }
577
578 StringBuffer buf = new StringBuffer(size);
579
580 for (int i = 0; i < sqlStringsLength - 1; i++) {
581 buf.append(new String(sqlStrings[i], charEncoding));
582 buf.append("?");
583 }
584
585 buf.append(new String(sqlStrings[sqlStringsLength - 1]));
586
587 return buf.toString();
588 }
589
590 /**
591 * Builds a ParseInfo for the given batch size, without parsing. We use
592 * a visitor pattern here, because the if {}s make computing a size for the
593 * resultant byte[][] make this too complex, and we don't necessarily want to
594 * use a List for this, because the size can be dynamic, and thus we'll not be
595 * able to guess a good initial size for an array-based list, and it's not
596 * efficient to convert a LinkedList to an array.
597 */
598 private void buildInfoForBatch(int numBatch, BatchVisitor visitor) {
599 final byte[][] headStaticSql = this.batchHead.staticSql;
600 final int headStaticSqlLength = headStaticSql.length;
601
602 if (headStaticSqlLength > 1) {
603 for (int i = 0; i < headStaticSqlLength - 1; i++) {
604 visitor.append(headStaticSql[i]).increment();
605 }
606 }
607
608 // merge end of head, with beginning of a value clause
609 byte[] endOfHead = headStaticSql[headStaticSqlLength - 1];
610 final byte[][] valuesStaticSql = this.batchValues.staticSql;
611 byte[] beginOfValues = valuesStaticSql[0];
612
613 visitor.merge(endOfHead, beginOfValues).increment();
614
615 int numValueRepeats = numBatch - 1; // first one is in the "head"
616
617 if (this.batchODKUClause != null) {
618 numValueRepeats--; // Last one is in the ODKU clause
619 }
620
621 final int valuesStaticSqlLength = valuesStaticSql.length;
622 byte[] endOfValues = valuesStaticSql[valuesStaticSqlLength - 1];
623
624 for (int i = 0; i < numValueRepeats; i++) {
625 for (int j = 1; j < valuesStaticSqlLength - 1; j++) {
626 visitor.append(valuesStaticSql[j]).increment();
627 }
628 visitor.merge(endOfValues, beginOfValues).increment();
629 }
630
631 if (this.batchODKUClause != null) {
632 final byte[][] batchOdkuStaticSql = this.batchODKUClause.staticSql;
633 byte[] beginOfOdku = batchOdkuStaticSql[0];
634 visitor.decrement().merge(endOfValues, beginOfOdku).increment();
635
636 final int batchOdkuStaticSqlLength = batchOdkuStaticSql.length;
637
638 if (numBatch > 1) {
639 for (int i = 1; i < batchOdkuStaticSqlLength; i++) {
640 visitor.append(batchOdkuStaticSql[i])
641 .increment();
642 }
643 } else {
644 visitor.decrement().append(batchOdkuStaticSql[(batchOdkuStaticSqlLength - 1)]);
645 }
646 } else {
647 // Everything after the values clause, but not ODKU, which today is nothing
648 // but a syntax error, but we should still not mangle the SQL!
649 visitor.decrement().append(this.staticSql[this.staticSql.length - 1]);
650 }
651 }
652
653 private ParseInfo(byte[][] staticSql, char firstStmtChar,
654 boolean foundLimitClause, boolean foundLoadData,
655 boolean isOnDuplicateKeyUpdate,
656 int locationOfOnDuplicateKeyUpdate, int statementLength,
657 int statementStartPos) {
658 this.firstStmtChar = firstStmtChar;
659 this.foundLimitClause = foundLimitClause;
660 this.foundLoadData = foundLoadData;
661 this.isOnDuplicateKeyUpdate = isOnDuplicateKeyUpdate;
662 this.locationOfOnDuplicateKeyUpdate = locationOfOnDuplicateKeyUpdate;
663 this.statementLength = statementLength;
664 this.statementStartPos = statementStartPos;
665 this.staticSql = staticSql;
666 }
667 }
668
669 interface BatchVisitor {
670 abstract BatchVisitor increment();
671
672 abstract BatchVisitor decrement();
673
674 abstract BatchVisitor append(byte[] values);
675
676 abstract BatchVisitor merge(byte[] begin, byte[] end);
677 }
678
679 class AppendingBatchVisitor implements BatchVisitor {
680 LinkedList statementComponents = new LinkedList();
681
682 public BatchVisitor append(byte[] values) {
683 statementComponents.addLast(values);
684
685 return this;
686 }
687
688 public BatchVisitor increment() {
689 // no-op
690 return this;
691 }
692
693 public BatchVisitor decrement() {
694 statementComponents.removeLast();
695
696 return this;
697 }
698
699 public BatchVisitor merge(byte[] front, byte[] back) {
700 int mergedLength = front.length + back.length;
701 byte[] merged = new byte[mergedLength];
702 System.arraycopy(front, 0, merged, 0, front.length);
703 System.arraycopy(back, 0, merged, front.length, back.length);
704 statementComponents.addLast(merged);
705 return this;
706 }
707
708 public byte[][] getStaticSqlStrings() {
709 byte[][] asBytes = new byte[this.statementComponents.size()][];
710 this.statementComponents.toArray(asBytes);
711
712 return asBytes;
713 }
714
715 public String toString() {
716 StringBuffer buf = new StringBuffer();
717 Iterator iter = this.statementComponents.iterator();
718 while (iter.hasNext()) {
719 buf.append(new String((byte[]) iter.next()));
720 }
721
722 return buf.toString();
723 }
724
725 }
726
727 private final static byte[] HEX_DIGITS = new byte[] { (byte) '0',
728 (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5',
729 (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'A',
730 (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F' };
731
732 /**
733 * Reads length bytes from reader into buf. Blocks until enough input is
734 * available
735 *
736 * @param reader
737 * DOCUMENT ME!
738 * @param buf
739 * DOCUMENT ME!
740 * @param length
741 * DOCUMENT ME!
742 *
743 * @return DOCUMENT ME!
744 *
745 * @throws IOException
746 * DOCUMENT ME!
747 */
748 protected static int readFully(Reader reader, char[] buf, int length)
749 throws IOException {
750 int numCharsRead = 0;
751
752 while (numCharsRead < length) {
753 int count = reader.read(buf, numCharsRead, length - numCharsRead);
754
755 if (count < 0) {
756 break;
757 }
758
759 numCharsRead += count;
760 }
761
762 return numCharsRead;
763 }
764
765 /**
766 * Does the batch (if any) contain "plain" statements added by
767 * Statement.addBatch(String)?
768 *
769 * If so, we can't re-write it to use multi-value or multi-queries.
770 */
771 protected boolean batchHasPlainStatements = false;
772
773 private java.sql.DatabaseMetaData dbmd = null;
774
775 /**
776 * What is the first character of the prepared statement (used to check for
777 * SELECT vs. INSERT/UPDATE/DELETE)
778 */
779 protected char firstCharOfStmt = 0;
780
781 /** Does the SQL for this statement contain a 'limit' clause? */
782 protected boolean hasLimitClause = false;
783
784 /** Is this query a LOAD DATA query? */
785 protected boolean isLoadDataQuery = false;
786
787 private boolean[] isNull = null;
788
789 private boolean[] isStream = null;
790
791 protected int numberOfExecutions = 0;
792
793 /** The SQL that was passed in to 'prepare' */
794 protected String originalSql = null;
795
796 /** The number of parameters in this PreparedStatement */
797 protected int parameterCount;
798
799 protected MysqlParameterMetadata parameterMetaData;
800
801 private InputStream[] parameterStreams = null;
802
803 private byte[][] parameterValues = null;
804
805 /**
806 * Only used by statement interceptors at the moment to
807 * provide introspection of bound values
808 */
809 protected int[] parameterTypes = null;
810
811 protected ParseInfo parseInfo;
812
813 private java.sql.ResultSetMetaData pstmtResultMetaData;
814
815 private byte[][] staticSqlStrings = null;
816
817 private byte[] streamConvertBuf = new byte[4096];
818
819 private int[] streamLengths = null;
820
821 private SimpleDateFormat tsdf = null;
822
823 /**
824 * Are we using a version of MySQL where we can use 'true' boolean values?
825 */
826 protected boolean useTrueBoolean = false;
827
828 protected boolean usingAnsiMode;
829
830 protected String batchedValuesClause;
831
832 private boolean doPingInstead;
833 private SimpleDateFormat ddf;
834 private SimpleDateFormat tdf;
835
836 private boolean compensateForOnDuplicateKeyUpdate = false;
837
838 /** Charset encoder used to escape if needed, such as Yen sign in SJIS */
839 private CharsetEncoder charsetEncoder;
840
841 /** Command index of currently executing batch command. */
842 private int batchCommandIndex = -1;
843
844 /**
845 * Creates a prepared statement instance -- We need to provide factory-style
846 * methods so we can support both JDBC3 (and older) and JDBC4 runtimes,
847 * otherwise the class verifier complains when it tries to load JDBC4-only
848 * interface classes that are present in JDBC4 method signatures.
849 */
850
851 protected static PreparedStatement getInstance(ConnectionImpl conn,
852 String catalog) throws SQLException {
853 if (!Util.isJdbc4()) {
854 return new PreparedStatement(conn, catalog);
855 }
856
857 return (PreparedStatement) Util.handleNewInstance(
858 JDBC_4_PSTMT_2_ARG_CTOR, new Object[] { conn, catalog }, conn.getExceptionInterceptor());
859 }
860
861 /**
862 * Creates a prepared statement instance -- We need to provide factory-style
863 * methods so we can support both JDBC3 (and older) and JDBC4 runtimes,
864 * otherwise the class verifier complains when it tries to load JDBC4-only
865 * interface classes that are present in JDBC4 method signatures.
866 */
867
868 protected static PreparedStatement getInstance(ConnectionImpl conn, String sql,
869 String catalog) throws SQLException {
870 if (!Util.isJdbc4()) {
871 return new PreparedStatement(conn, sql, catalog);
872 }
873
874 return (PreparedStatement) Util.handleNewInstance(
875 JDBC_4_PSTMT_3_ARG_CTOR, new Object[] { conn, sql, catalog }, conn.getExceptionInterceptor());
876 }
877
878 /**
879 * Creates a prepared statement instance -- We need to provide factory-style
880 * methods so we can support both JDBC3 (and older) and JDBC4 runtimes,
881 * otherwise the class verifier complains when it tries to load JDBC4-only
882 * interface classes that are present in JDBC4 method signatures.
883 */
884
885 protected static PreparedStatement getInstance(ConnectionImpl conn, String sql,
886 String catalog, ParseInfo cachedParseInfo) throws SQLException {
887 if (!Util.isJdbc4()) {
888 return new PreparedStatement(conn, sql, catalog, cachedParseInfo);
889 }
890
891 return (PreparedStatement) Util.handleNewInstance(
892 JDBC_4_PSTMT_4_ARG_CTOR, new Object[] { conn, sql, catalog,
893 cachedParseInfo }, conn.getExceptionInterceptor());
894 }
895
896 /**
897 * Constructor used by server-side prepared statements
898 *
899 * @param conn
900 * the connection that created us
901 * @param catalog
902 * the catalog in use when we were created
903 *
904 * @throws SQLException
905 * if an error occurs
906 */
907 public PreparedStatement(ConnectionImpl conn, String catalog)
908 throws SQLException {
909 super(conn, catalog);
910
911 this.compensateForOnDuplicateKeyUpdate = this.connection.getCompensateOnDuplicateKeyUpdateCounts();
912 }
913
914 /**
915 * Constructor for the PreparedStatement class.
916 *
917 * @param conn
918 * the connection creating this statement
919 * @param sql
920 * the SQL for this statement
921 * @param catalog
922 * the catalog/database this statement should be issued against
923 *
924 * @throws SQLException
925 * if a database error occurs.
926 */
927 public PreparedStatement(ConnectionImpl conn, String sql, String catalog)
928 throws SQLException {
929 super(conn, catalog);
930
931 if (sql == null) {
932 throw SQLError.createSQLException(Messages.getString("PreparedStatement.0"), //$NON-NLS-1$
933 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
934 }
935
936 this.originalSql = sql;
937
938 if (this.originalSql.startsWith(PING_MARKER)) {
939 this.doPingInstead = true;
940 } else {
941 this.doPingInstead = false;
942 }
943
944 this.dbmd = this.connection.getMetaData();
945
946 this.useTrueBoolean = this.connection.versionMeetsMinimum(3, 21, 23);
947
948 this.parseInfo = new ParseInfo(sql, this.connection, this.dbmd,
949 this.charEncoding, this.charConverter);
950
951 initializeFromParseInfo();
952
953 this.compensateForOnDuplicateKeyUpdate = this.connection.getCompensateOnDuplicateKeyUpdateCounts();
954
955 if (conn.getRequiresEscapingEncoder())
956 charsetEncoder = Charset.forName(conn.getEncoding()).newEncoder();
957 }
958
959 /**
960 * Creates a new PreparedStatement object.
961 *
962 * @param conn
963 * the connection creating this statement
964 * @param sql
965 * the SQL for this statement
966 * @param catalog
967 * the catalog/database this statement should be issued against
968 * @param cachedParseInfo
969 * already created parseInfo.
970 *
971 * @throws SQLException
972 * DOCUMENT ME!
973 */
974 public PreparedStatement(ConnectionImpl conn, String sql, String catalog,
975 ParseInfo cachedParseInfo) throws SQLException {
976 super(conn, catalog);
977
978 if (sql == null) {
979 throw SQLError.createSQLException(Messages.getString("PreparedStatement.1"), //$NON-NLS-1$
980 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
981 }
982
983 this.originalSql = sql;
984
985 this.dbmd = this.connection.getMetaData();
986
987 this.useTrueBoolean = this.connection.versionMeetsMinimum(3, 21, 23);
988
989 this.parseInfo = cachedParseInfo;
990
991 this.usingAnsiMode = !this.connection.useAnsiQuotedIdentifiers();
992
993 initializeFromParseInfo();
994
995 this.compensateForOnDuplicateKeyUpdate = this.connection.getCompensateOnDuplicateKeyUpdateCounts();
996
997 if (conn.getRequiresEscapingEncoder())
998 charsetEncoder = Charset.forName(conn.getEncoding()).newEncoder();
999 }
1000
1001 /**
1002 * JDBC 2.0 Add a set of parameters to the batch.
1003 *
1004 * @exception SQLException
1005 * if a database-access error occurs.
1006 *
1007 * @see StatementImpl#addBatch
1008 */
1009 public void addBatch() throws SQLException {
1010 if (this.batchedArgs == null) {
1011 this.batchedArgs = new ArrayList();
1012 }
1013
1014 for (int i = 0; i < this.parameterValues.length; i++) {
1015 checkAllParametersSet(this.parameterValues[i],
1016 this.parameterStreams[i], i);
1017 }
1018
1019 this.batchedArgs.add(new BatchParams(this.parameterValues,
1020 this.parameterStreams, this.isStream, this.streamLengths,
1021 this.isNull));
1022 }
1023
1024 public synchronized void addBatch(String sql) throws SQLException {
1025 this.batchHasPlainStatements = true;
1026
1027 super.addBatch(sql);
1028 }
1029
1030 protected String asSql() throws SQLException {
1031 return asSql(false);
1032 }
1033
1034 protected String asSql(boolean quoteStreamsAndUnknowns) throws SQLException {
1035 if (this.isClosed) {
1036 return "statement has been closed, no further internal information available";
1037 }
1038
1039 StringBuffer buf = new StringBuffer();
1040
1041 try {
1042 int realParameterCount = this.parameterCount + getParameterIndexOffset();
1043 Object batchArg = null;
1044 if (batchCommandIndex != -1)
1045 batchArg = batchedArgs.get(batchCommandIndex);
1046
1047 for (int i = 0; i < realParameterCount; ++i) {
1048 if (this.charEncoding != null) {
1049 buf.append(new String(this.staticSqlStrings[i],
1050 this.charEncoding));
1051 } else {
1052 buf.append(new String(this.staticSqlStrings[i]));
1053 }
1054
1055 byte val[] = null;
1056 if (batchArg != null && batchArg instanceof String) {
1057 buf.append((String)batchArg);
1058 continue;
1059 }
1060 if (batchCommandIndex == -1)
1061 val = parameterValues[i];
1062 else
1063 val = ((BatchParams)batchArg).parameterStrings[i];
1064
1065 boolean isStreamParam = false;
1066 if (batchCommandIndex == -1)
1067 isStreamParam = isStream[i];
1068 else
1069 isStreamParam = ((BatchParams)batchArg).isStream[i];
1070
1071 if ((val == null) && !isStreamParam) {
1072 if (quoteStreamsAndUnknowns) {
1073 buf.append("'");
1074 }
1075
1076 buf.append("** NOT SPECIFIED **"); //$NON-NLS-1$
1077
1078 if (quoteStreamsAndUnknowns) {
1079 buf.append("'");
1080 }
1081 } else if (isStreamParam) {
1082 if (quoteStreamsAndUnknowns) {
1083 buf.append("'");
1084 }
1085
1086 buf.append("** STREAM DATA **"); //$NON-NLS-1$
1087
1088 if (quoteStreamsAndUnknowns) {
1089 buf.append("'");
1090 }
1091 } else {
1092 if (this.charConverter != null) {
1093 buf.append(this.charConverter.toString(val));
1094 } else {
1095 if (this.charEncoding != null) {
1096 buf.append(new String(val, this.charEncoding));
1097 } else {
1098 buf.append(StringUtils.toAsciiString(val));
1099 }
1100 }
1101 }
1102 }
1103
1104 if (this.charEncoding != null) {
1105 buf.append(new String(
1106 this.staticSqlStrings[this.parameterCount + getParameterIndexOffset()],
1107 this.charEncoding));
1108 } else {
1109 buf
1110 .append(StringUtils
1111 .toAsciiString(this.staticSqlStrings[this.parameterCount + getParameterIndexOffset()]));
1112 }
1113 } catch (UnsupportedEncodingException uue) {
1114 throw new RuntimeException(Messages
1115 .getString("PreparedStatement.32") //$NON-NLS-1$
1116 + this.charEncoding
1117 + Messages.getString("PreparedStatement.33")); //$NON-NLS-1$
1118 }
1119
1120 return buf.toString();
1121 }
1122
1123 public synchronized void clearBatch() throws SQLException {
1124 this.batchHasPlainStatements = false;
1125
1126 super.clearBatch();
1127 }
1128
1129 /**
1130 * In general, parameter values remain in force for repeated used of a
1131 * Statement. Setting a parameter value automatically clears its previous
1132 * value. However, in some cases, it is useful to immediately release the
1133 * resources used by the current parameter values; this can be done by
1134 * calling clearParameters
1135 *
1136 * @exception SQLException
1137 * if a database access error occurs
1138 */
1139 public synchronized void clearParameters() throws SQLException {
1140 checkClosed();
1141
1142 for (int i = 0; i < this.parameterValues.length; i++) {
1143 this.parameterValues[i] = null;
1144 this.parameterStreams[i] = null;
1145 this.isStream[i] = false;
1146 this.isNull[i] = false;
1147 this.parameterTypes[i] = Types.NULL;
1148 }
1149 }
1150
1151 /**
1152 * Closes this prepared statement and releases all resources.
1153 *
1154 * @throws SQLException
1155 * if database error occurs.
1156 */
1157 public synchronized void close() throws SQLException {
1158 realClose(true, true);
1159 }
1160
1161 private final void escapeblockFast(byte[] buf, Buffer packet, int size)
1162 throws SQLException {
1163 int lastwritten = 0;
1164
1165 for (int i = 0; i < size; i++) {
1166 byte b = buf[i];
1167
1168 if (b == '\0') {
1169 // write stuff not yet written
1170 if (i > lastwritten) {
1171 packet.writeBytesNoNull(buf, lastwritten, i - lastwritten);
1172 }
1173
1174 // write escape
1175 packet.writeByte((byte) '\\');
1176 packet.writeByte((byte) '0');
1177 lastwritten = i + 1;
1178 } else {
1179 if ((b == '\\') || (b == '\'')
1180 || (!this.usingAnsiMode && b == '"')) {
1181 // write stuff not yet written
1182 if (i > lastwritten) {
1183 packet.writeBytesNoNull(buf, lastwritten, i
1184 - lastwritten);
1185 }
1186
1187 // write escape
1188 packet.writeByte((byte) '\\');
1189 lastwritten = i; // not i+1 as b wasn't written.
1190 }
1191 }
1192 }
1193
1194 // write out remaining stuff from buffer
1195 if (lastwritten < size) {
1196 packet.writeBytesNoNull(buf, lastwritten, size - lastwritten);
1197 }
1198 }
1199
1200 private final void escapeblockFast(byte[] buf,
1201 ByteArrayOutputStream bytesOut, int size) {
1202 int lastwritten = 0;
1203
1204 for (int i = 0; i < size; i++) {
1205 byte b = buf[i];
1206
1207 if (b == '\0') {
1208 // write stuff not yet written
1209 if (i > lastwritten) {
1210 bytesOut.write(buf, lastwritten, i - lastwritten);
1211 }
1212
1213 // write escape
1214 bytesOut.write('\\');
1215 bytesOut.write('0');
1216 lastwritten = i + 1;
1217 } else {
1218 if ((b == '\\') || (b == '\'')
1219 || (!this.usingAnsiMode && b == '"')) {
1220 // write stuff not yet written
1221 if (i > lastwritten) {
1222 bytesOut.write(buf, lastwritten, i - lastwritten);
1223 }
1224
1225 // write escape
1226 bytesOut.write('\\');
1227 lastwritten = i; // not i+1 as b wasn't written.
1228 }
1229 }
1230 }
1231
1232 // write out remaining stuff from buffer
1233 if (lastwritten < size) {
1234 bytesOut.write(buf, lastwritten, size - lastwritten);
1235 }
1236 }
1237
1238 /**
1239 * Check to see if the statement is safe for read-only slaves after failover.
1240 *
1241 * @return true if safe for read-only.
1242 * @throws SQLException
1243 */
1244 protected boolean checkReadOnlySafeStatement() throws SQLException {
1245 return ((!this.connection.isReadOnly()) || (this.firstCharOfStmt == 'S'));
1246 }
1247
1248 /**
1249 * Some prepared statements return multiple results; the execute method
1250 * handles these complex statements as well as the simpler form of
1251 * statements handled by executeQuery and executeUpdate
1252 *
1253 * @return true if the next result is a ResultSet; false if it is an update
1254 * count or there are no more results
1255 *
1256 * @exception SQLException
1257 * if a database access error occurs
1258 */
1259 public boolean execute() throws SQLException {
1260 checkClosed();
1261
1262 ConnectionImpl locallyScopedConn = this.connection;
1263
1264 if(!checkReadOnlySafeStatement()) {
1265 throw SQLError.createSQLException(Messages.getString("PreparedStatement.20") //$NON-NLS-1$
1266 + Messages.getString("PreparedStatement.21"), //$NON-NLS-1$
1267 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
1268 }
1269
1270 ResultSetInternalMethods rs = null;
1271
1272 CachedResultSetMetaData cachedMetadata = null;
1273
1274 synchronized (locallyScopedConn.getMutex()) {
1275 lastQueryIsOnDupKeyUpdate = false;
1276 if (retrieveGeneratedKeys)
1277 lastQueryIsOnDupKeyUpdate = containsOnDuplicateKeyUpdateInSQL();
1278 boolean doStreaming = createStreamingResultSet();
1279
1280 clearWarnings();
1281
1282 // Adjust net_write_timeout to a higher value if we're
1283 // streaming result sets. More often than not, someone runs into
1284 // an issue where they blow net_write_timeout when using this
1285 // feature, and if they're willing to hold a result set open
1286 // for 30 seconds or more, one more round-trip isn't going to hurt
1287 //
1288 // This is reset by RowDataDynamic.close().
1289
1290 if (doStreaming
1291 && this.connection.getNetTimeoutForStreamingResults() > 0) {
1292 executeSimpleNonQuery(locallyScopedConn,
1293 "SET net_write_timeout="
1294 + this.connection
1295 .getNetTimeoutForStreamingResults());
1296 }
1297
1298 this.batchedGeneratedKeys = null;
1299
1300 Buffer sendPacket = fillSendPacket();
1301
1302 String oldCatalog = null;
1303
1304 if (!locallyScopedConn.getCatalog().equals(this.currentCatalog)) {
1305 oldCatalog = locallyScopedConn.getCatalog();
1306 locallyScopedConn.setCatalog(this.currentCatalog);
1307 }
1308
1309 //
1310 // Check if we have cached metadata for this query...
1311 //
1312 if (locallyScopedConn.getCacheResultSetMetadata()) {
1313 cachedMetadata = locallyScopedConn.getCachedMetaData(this.originalSql);
1314 }
1315
1316 Field[] metadataFromCache = null;
1317
1318 if (cachedMetadata != null) {
1319 metadataFromCache = cachedMetadata.fields;
1320 }
1321
1322 boolean oldInfoMsgState = false;
1323
1324 if (this.retrieveGeneratedKeys) {
1325 oldInfoMsgState = locallyScopedConn.isReadInfoMsgEnabled();
1326 locallyScopedConn.setReadInfoMsgEnabled(true);
1327 }
1328
1329 // If there isn't a limit clause in the SQL
1330 // then limit the number of rows to return in
1331 // an efficient manner. Only do this if
1332 // setMaxRows() hasn't been used on any Statements
1333 // generated from the current Connection (saves
1334 // a query, and network traffic).
1335 //
1336 // Only apply max_rows to selects
1337 //
1338 if (locallyScopedConn.useMaxRows()) {
1339 int rowLimit = -1;
1340
1341 if (this.firstCharOfStmt == 'S') {
1342 if (this.hasLimitClause) {
1343 rowLimit = this.maxRows;
1344 } else {
1345 if (this.maxRows <= 0) {
1346 executeSimpleNonQuery(locallyScopedConn,
1347 "SET OPTION SQL_SELECT_LIMIT=DEFAULT");
1348 } else {
1349 executeSimpleNonQuery(locallyScopedConn,
1350 "SET OPTION SQL_SELECT_LIMIT="
1351 + this.maxRows);
1352 }
1353 }
1354 } else {
1355 executeSimpleNonQuery(locallyScopedConn,
1356 "SET OPTION SQL_SELECT_LIMIT=DEFAULT");
1357 }
1358
1359 // Finally, execute the query
1360 rs = executeInternal(rowLimit, sendPacket,
1361 doStreaming,
1362 (this.firstCharOfStmt == 'S'), metadataFromCache, false);
1363 } else {
1364 rs = executeInternal(-1, sendPacket,
1365 doStreaming,
1366 (this.firstCharOfStmt == 'S'), metadataFromCache, false);
1367 }
1368
1369 if (cachedMetadata != null) {
1370 locallyScopedConn.initializeResultsMetadataFromCache(this.originalSql,
1371 cachedMetadata, this.results);
1372 } else {
1373 if (rs.reallyResult() && locallyScopedConn.getCacheResultSetMetadata()) {
1374 locallyScopedConn.initializeResultsMetadataFromCache(this.originalSql,
1375 null /* will be created */, rs);
1376 }
1377 }
1378
1379 if (this.retrieveGeneratedKeys) {
1380 locallyScopedConn.setReadInfoMsgEnabled(oldInfoMsgState);
1381 rs.setFirstCharOfQuery(this.firstCharOfStmt);
1382 }
1383
1384 if (oldCatalog != null) {
1385 locallyScopedConn.setCatalog(oldCatalog);
1386 }
1387
1388 if (rs != null) {
1389 this.lastInsertId = rs.getUpdateID();
1390
1391 this.results = rs;
1392 }
1393 }
1394
1395 return ((rs != null) && rs.reallyResult());
1396 }
1397
1398 /**
1399 * JDBC 2.0 Submit a batch of commands to the database for execution. This
1400 * method is optional.
1401 *
1402 * @return an array of update counts containing one element for each command
1403 * in the batch. The array is ordered according to the order in
1404 * which commands were inserted into the batch
1405 *
1406 * @exception SQLException
1407 * if a database-access error occurs, or the driver does not
1408 * support batch statements
1409 * @throws java.sql.BatchUpdateException
1410 * DOCUMENT ME!
1411 */
1412 public int[] executeBatch() throws SQLException {
1413 checkClosed();
1414
1415 if (this.connection.isReadOnly()) {
1416 throw new SQLException(Messages.getString("PreparedStatement.25") //$NON-NLS-1$
1417 + Messages.getString("PreparedStatement.26"), //$NON-NLS-1$
1418 SQLError.SQL_STATE_ILLEGAL_ARGUMENT);
1419 }
1420
1421 synchronized (this.connection.getMutex()) {
1422 if (this.batchedArgs == null || this.batchedArgs.size() == 0) {
1423 return new int[0];
1424 }
1425
1426 // we timeout the entire batch, not individual statements
1427 int batchTimeout = this.timeoutInMillis;
1428 this.timeoutInMillis = 0;
1429
1430 resetCancelledState();
1431
1432 try {
1433 clearWarnings();
1434
1435 if (!this.batchHasPlainStatements
1436 && this.connection.getRewriteBatchedStatements()) {
1437
1438
1439 if (canRewriteAsMultiValueInsertAtSqlLevel()) {
1440 return executeBatchedInserts(batchTimeout);
1441 }
1442
1443 if (this.connection.versionMeetsMinimum(4, 1, 0)
1444 && !this.batchHasPlainStatements
1445 && this.batchedArgs != null
1446 && this.batchedArgs.size() > 3 /* cost of option setting rt-wise */) {
1447 return executePreparedBatchAsMultiStatement(batchTimeout);
1448 }
1449 }
1450
1451 return executeBatchSerially(batchTimeout);
1452 } finally {
1453 clearBatch();
1454 }
1455 }
1456 }
1457
1458 public boolean canRewriteAsMultiValueInsertAtSqlLevel() throws SQLException {
1459 return this.parseInfo.canRewriteAsMultiValueInsert;
1460 }
1461
1462 protected int getLocationOfOnDuplicateKeyUpdate() {
1463 return this.parseInfo.locationOfOnDuplicateKeyUpdate;
1464 }
1465
1466 /**
1467 * Rewrites the already prepared statement into a multi-statement
1468 * query of 'statementsPerBatch' values and executes the entire batch
1469 * using this new statement.
1470 *
1471 * @return update counts in the same fashion as executeBatch()
1472 *
1473 * @throws SQLException
1474 */
1475
1476 protected int[] executePreparedBatchAsMultiStatement(int batchTimeout) throws SQLException {
1477 synchronized (this.connection.getMutex()) {
1478 // This is kind of an abuse, but it gets the job done
1479 if (this.batchedValuesClause == null) {
1480 this.batchedValuesClause = this.originalSql + ";";
1481 }
1482
1483 ConnectionImpl locallyScopedConn = this.connection;
1484
1485 boolean multiQueriesEnabled = locallyScopedConn.getAllowMultiQueries();
1486 CancelTask timeoutTask = null;
1487
1488 try {
1489 clearWarnings();
1490
1491 int numBatchedArgs = this.batchedArgs.size();
1492
1493 if (this.retrieveGeneratedKeys) {
1494 this.batchedGeneratedKeys = new ArrayList(numBatchedArgs);
1495 }
1496
1497 int numValuesPerBatch = computeBatchSize(numBatchedArgs);
1498
1499 if (numBatchedArgs < numValuesPerBatch) {
1500 numValuesPerBatch = numBatchedArgs;
1501 }
1502
1503 java.sql.PreparedStatement batchedStatement = null;
1504
1505 int batchedParamIndex = 1;
1506 int numberToExecuteAsMultiValue = 0;
1507 int batchCounter = 0;
1508 int updateCountCounter = 0;
1509 int[] updateCounts = new int[numBatchedArgs];
1510 SQLException sqlEx = null;
1511
1512 try {
1513 if (!multiQueriesEnabled) {
1514 locallyScopedConn.getIO().enableMultiQueries();
1515 }
1516
1517 if (this.retrieveGeneratedKeys) {
1518 batchedStatement = locallyScopedConn.prepareStatement(
1519 generateMultiStatementForBatch(numValuesPerBatch),
1520 RETURN_GENERATED_KEYS);
1521 } else {
1522 batchedStatement = locallyScopedConn
1523 .prepareStatement(generateMultiStatementForBatch(numValuesPerBatch));
1524 }
1525
1526 if (locallyScopedConn.getEnableQueryTimeouts() &&
1527 batchTimeout != 0
1528 && locallyScopedConn.versionMeetsMinimum(5, 0, 0)) {
1529 timeoutTask = new CancelTask((StatementImpl)batchedStatement);
1530 locallyScopedConn.getCancelTimer().schedule(timeoutTask,
1531 batchTimeout);
1532 }
1533
1534 if (numBatchedArgs < numValuesPerBatch) {
1535 numberToExecuteAsMultiValue = numBatchedArgs;
1536 } else {
1537 numberToExecuteAsMultiValue = numBatchedArgs / numValuesPerBatch;
1538 }
1539
1540 int numberArgsToExecute = numberToExecuteAsMultiValue * numValuesPerBatch;
1541
1542 for (int i = 0; i < numberArgsToExecute; i++) {
1543 if (i != 0 && i % numValuesPerBatch == 0) {
1544 try {
1545 batchedStatement.execute();
1546 } catch (SQLException ex) {
1547 sqlEx = handleExceptionForBatch(batchCounter, numValuesPerBatch,
1548 updateCounts, ex);
1549 }
1550
1551 updateCountCounter = processMultiCountsAndKeys(
1552 (StatementImpl)batchedStatement, updateCountCounter,
1553 updateCounts);
1554
1555 batchedStatement.clearParameters();
1556 batchedParamIndex = 1;
1557 }
1558
1559 batchedParamIndex = setOneBatchedParameterSet(batchedStatement,
1560 batchedParamIndex, this.batchedArgs
1561 .get(batchCounter++));
1562 }
1563
1564 try {
1565 batchedStatement.execute();
1566 } catch (SQLException ex) {
1567 sqlEx = handleExceptionForBatch(batchCounter - 1, numValuesPerBatch,
1568 updateCounts, ex);
1569 }
1570
1571 updateCountCounter = processMultiCountsAndKeys(
1572 (StatementImpl)batchedStatement, updateCountCounter,
1573 updateCounts);
1574
1575 batchedStatement.clearParameters();
1576
1577 numValuesPerBatch = numBatchedArgs - batchCounter;
1578 } finally {
1579 if (batchedStatement != null) {
1580 batchedStatement.close();
1581 }
1582 }
1583
1584 try {
1585 if (numValuesPerBatch > 0) {
1586
1587 if (this.retrieveGeneratedKeys) {
1588 batchedStatement = locallyScopedConn.prepareStatement(
1589 generateMultiStatementForBatch(numValuesPerBatch),
1590 RETURN_GENERATED_KEYS);
1591 } else {
1592 batchedStatement = locallyScopedConn.prepareStatement(
1593 generateMultiStatementForBatch(numValuesPerBatch));
1594 }
1595
1596 if (timeoutTask != null) {
1597 timeoutTask.toCancel = (StatementImpl)batchedStatement;
1598 }
1599
1600 batchedParamIndex = 1;
1601
1602 while (batchCounter < numBatchedArgs) {
1603 batchedParamIndex = setOneBatchedParameterSet(batchedStatement,
1604 batchedParamIndex, this.batchedArgs
1605 .get(batchCounter++));
1606 }
1607
1608 try {
1609 batchedStatement.execute();
1610 } catch (SQLException ex) {
1611 sqlEx = handleExceptionForBatch(batchCounter - 1, numValuesPerBatch,
1612 updateCounts, ex);
1613 }
1614
1615 updateCountCounter = processMultiCountsAndKeys(
1616 (StatementImpl)batchedStatement, updateCountCounter,
1617 updateCounts);
1618
1619 batchedStatement.clearParameters();
1620 }
1621
1622 if (timeoutTask != null) {
1623 if (timeoutTask.caughtWhileCancelling != null) {
1624 throw timeoutTask.caughtWhileCancelling;
1625 }
1626
1627 timeoutTask.cancel();
1628 timeoutTask = null;
1629 }
1630
1631 if (sqlEx != null) {
1632 throw new java.sql.BatchUpdateException(sqlEx
1633 .getMessage(), sqlEx.getSQLState(), sqlEx
1634 .getErrorCode(), updateCounts);
1635 }
1636
1637 return updateCounts;
1638 } finally {
1639 if (batchedStatement != null) {
1640 batchedStatement.close();
1641 }
1642 }
1643 } finally {
1644 if (timeoutTask != null) {
1645 timeoutTask.cancel();
1646 }
1647
1648 resetCancelledState();
1649
1650 if (!multiQueriesEnabled) {
1651 locallyScopedConn.getIO().disableMultiQueries();
1652 }
1653
1654 clearBatch();
1655 }
1656 }
1657 }
1658
1659 private String generateMultiStatementForBatch(int numBatches) {
1660 StringBuffer newStatementSql = new StringBuffer((this.originalSql
1661 .length() + 1) * numBatches);
1662
1663 newStatementSql.append(this.originalSql);
1664
1665 for (int i = 0; i < numBatches - 1; i++) {
1666 newStatementSql.append(';');
1667 newStatementSql.append(this.originalSql);
1668 }
1669
1670 return newStatementSql.toString();
1671 }
1672
1673 /**
1674 * Rewrites the already prepared statement into a multi-value insert
1675 * statement of 'statementsPerBatch' values and executes the entire batch
1676 * using this new statement.
1677 *
1678 * @return update counts in the same fashion as executeBatch()
1679 *
1680 * @throws SQLException
1681 */
1682 protected int[] executeBatchedInserts(int batchTimeout) throws SQLException {
1683 String valuesClause = getValuesClause();
1684
1685 ConnectionImpl locallyScopedConn = this.connection;
1686
1687 if (valuesClause == null) {
1688 return executeBatchSerially(batchTimeout);
1689 }
1690
1691 int numBatchedArgs = this.batchedArgs.size();
1692
1693 if (this.retrieveGeneratedKeys) {
1694 this.batchedGeneratedKeys = new ArrayList(numBatchedArgs);
1695 }
1696
1697 int numValuesPerBatch = computeBatchSize(numBatchedArgs);
1698
1699 if (numBatchedArgs < numValuesPerBatch) {
1700 numValuesPerBatch = numBatchedArgs;
1701 }
1702
1703 java.sql.PreparedStatement batchedStatement = null;
1704
1705 int batchedParamIndex = 1;
1706 int updateCountRunningTotal = 0;
1707 int numberToExecuteAsMultiValue = 0;
1708 int batchCounter = 0;
1709 CancelTask timeoutTask = null;
1710 SQLException sqlEx = null;
1711
1712 int[] updateCounts = new int[numBatchedArgs];
1713
1714 for (int i = 0; i < this.batchedArgs.size(); i++) {
1715 updateCounts[i] = 1;
1716 }
1717
1718 try {
1719 try {
1720 batchedStatement = /* FIXME -if we ever care about folks proxying our ConnectionImpl */
1721 prepareBatchedInsertSQL((ConnectionImpl) locallyScopedConn, numValuesPerBatch);
1722
1723 if (locallyScopedConn.getEnableQueryTimeouts()
1724 && batchTimeout != 0
1725 && locallyScopedConn.versionMeetsMinimum(5, 0, 0)) {
1726 timeoutTask = new CancelTask(
1727 (StatementImpl) batchedStatement);
1728 locallyScopedConn.getCancelTimer().schedule(timeoutTask,
1729 batchTimeout);
1730 }
1731
1732 if (numBatchedArgs < numValuesPerBatch) {
1733 numberToExecuteAsMultiValue = numBatchedArgs;
1734 } else {
1735 numberToExecuteAsMultiValue = numBatchedArgs
1736 / numValuesPerBatch;
1737 }
1738
1739 int numberArgsToExecute = numberToExecuteAsMultiValue
1740 * numValuesPerBatch;
1741
1742 for (int i = 0; i < numberArgsToExecute; i++) {
1743 if (i != 0 && i % numValuesPerBatch == 0) {
1744 try {
1745 updateCountRunningTotal += batchedStatement
1746 .executeUpdate();
1747 } catch (SQLException ex) {
1748 sqlEx = handleExceptionForBatch(batchCounter - 1,
1749 numValuesPerBatch, updateCounts, ex);
1750 }
1751
1752 getBatchedGeneratedKeys(batchedStatement);
1753 batchedStatement.clearParameters();
1754 batchedParamIndex = 1;
1755
1756 }
1757
1758 batchedParamIndex = setOneBatchedParameterSet(
1759 batchedStatement, batchedParamIndex,
1760 this.batchedArgs.get(batchCounter++));
1761 }
1762
1763 try {
1764 updateCountRunningTotal += batchedStatement.executeUpdate();
1765 } catch (SQLException ex) {
1766 sqlEx = handleExceptionForBatch(batchCounter - 1,
1767 numValuesPerBatch, updateCounts, ex);
1768 }
1769
1770 getBatchedGeneratedKeys(batchedStatement);
1771
1772 numValuesPerBatch = numBatchedArgs - batchCounter;
1773 } finally {
1774 if (batchedStatement != null) {
1775 batchedStatement.close();
1776 }
1777 }
1778
1779 try {
1780 if (numValuesPerBatch > 0) {
1781 batchedStatement =
1782 prepareBatchedInsertSQL((ConnectionImpl) locallyScopedConn,
1783 numValuesPerBatch);
1784
1785 if (timeoutTask != null) {
1786 timeoutTask.toCancel = (StatementImpl) batchedStatement;
1787 }
1788
1789 batchedParamIndex = 1;
1790
1791 while (batchCounter < numBatchedArgs) {
1792 batchedParamIndex = setOneBatchedParameterSet(
1793 batchedStatement, batchedParamIndex,
1794 this.batchedArgs.get(batchCounter++));
1795 }
1796
1797 try {
1798 updateCountRunningTotal += batchedStatement.executeUpdate();
1799 } catch (SQLException ex) {
1800 sqlEx = handleExceptionForBatch(batchCounter - 1,
1801 numValuesPerBatch, updateCounts, ex);
1802 }
1803
1804 getBatchedGeneratedKeys(batchedStatement);
1805 }
1806
1807 if (sqlEx != null) {
1808 throw new java.sql.BatchUpdateException(sqlEx
1809 .getMessage(), sqlEx.getSQLState(), sqlEx
1810 .getErrorCode(), updateCounts);
1811 }
1812
1813 return updateCounts;
1814 } finally {
1815 if (batchedStatement != null) {
1816 batchedStatement.close();
1817 }
1818 }
1819 } finally {
1820 if (timeoutTask != null) {
1821 timeoutTask.cancel();
1822 }
1823
1824 resetCancelledState();
1825 }
1826 }
1827
1828 protected String getValuesClause() throws SQLException {
1829 return this.parseInfo.valuesClause;
1830 }
1831
1832 /**
1833 * Computes the optimum number of batched parameter lists to send
1834 * without overflowing max_allowed_packet.
1835 *
1836 * @param numBatchedArgs
1837 * @return
1838 * @throws SQLException
1839 */
1840 protected int computeBatchSize(int numBatchedArgs) throws SQLException {
1841 long[] combinedValues = computeMaxParameterSetSizeAndBatchSize(numBatchedArgs);
1842
1843 long maxSizeOfParameterSet = combinedValues[0];
1844 long sizeOfEntireBatch = combinedValues[1];
1845
1846 int maxAllowedPacket = this.connection.getMaxAllowedPacket();
1847
1848 if (sizeOfEntireBatch < maxAllowedPacket - this.originalSql.length()) {
1849 return numBatchedArgs;
1850 }
1851
1852 return (int)Math.max(1, (maxAllowedPacket - this.originalSql.length()) / maxSizeOfParameterSet);
1853 }
1854
1855 /**
1856 * Computes the maximum parameter set size, and entire batch size given
1857 * the number of arguments in the batch.
1858 * @throws SQLException
1859 */
1860 protected long[] computeMaxParameterSetSizeAndBatchSize(int numBatchedArgs) throws SQLException {
1861 long sizeOfEntireBatch = 0;
1862 long maxSizeOfParameterSet = 0;
1863
1864 for (int i = 0; i < numBatchedArgs; i++) {
1865 BatchParams paramArg = (BatchParams) this.batchedArgs
1866 .get(i);
1867
1868 boolean[] isNullBatch = paramArg.isNull;
1869 boolean[] isStreamBatch = paramArg.isStream;
1870
1871 long sizeOfParameterSet = 0;
1872
1873 for (int j = 0; j < isNullBatch.length; j++) {
1874 if (!isNullBatch[j]) {
1875
1876 if (isStreamBatch[j]) {
1877 int streamLength = paramArg.streamLengths[j];
1878
1879 if (streamLength != -1) {
1880 sizeOfParameterSet += streamLength * 2; // for safety in escaping
1881 } else {
1882 int paramLength = paramArg.parameterStrings[j].length;
1883 sizeOfParameterSet += paramLength;
1884 }
1885 } else {
1886 sizeOfParameterSet += paramArg.parameterStrings[j].length;
1887 }
1888 } else {
1889 sizeOfParameterSet += 4; // for NULL literal in SQL
1890 }
1891 }
1892
1893 //
1894 // Account for static part of values clause
1895 // This is a little naiive, because the ?s will be replaced
1896 // but it gives us some padding, and is less housekeeping
1897 // to ignore them. We're looking for a "fuzzy" value here
1898 // anyway
1899 //
1900
1901 if (getValuesClause() != null) {
1902 sizeOfParameterSet += getValuesClause().length() + 1;
1903 } else {
1904 sizeOfParameterSet += this.originalSql.length() + 1;
1905 }
1906
1907 sizeOfEntireBatch += sizeOfParameterSet;
1908
1909 if (sizeOfParameterSet > maxSizeOfParameterSet) {
1910 maxSizeOfParameterSet = sizeOfParameterSet;
1911 }
1912 }
1913
1914 return new long[] {maxSizeOfParameterSet, sizeOfEntireBatch};
1915 }
1916
1917
1918 /**
1919 * Executes the current batch of statements by executing them one-by-one.
1920 *
1921 * @return a list of update counts
1922 * @throws SQLException
1923 * if an error occurs
1924 */
1925 protected int[] executeBatchSerially(int batchTimeout) throws SQLException {
1926
1927 ConnectionImpl locallyScopedConn = this.connection;
1928
1929 if (locallyScopedConn == null) {
1930 checkClosed();
1931 }
1932
1933 int[] updateCounts = null;
1934
1935 if (this.batchedArgs != null) {
1936 int nbrCommands = this.batchedArgs.size();
1937 updateCounts = new int[nbrCommands];
1938
1939 for (int i = 0; i < nbrCommands; i++) {
1940 updateCounts[i] = -3;
1941 }
1942
1943 SQLException sqlEx = null;
1944
1945 CancelTask timeoutTask = null;
1946
1947 try {
1948 if (locallyScopedConn.getEnableQueryTimeouts() &&
1949 batchTimeout != 0
1950 && locallyScopedConn.versionMeetsMinimum(5, 0, 0)) {
1951 timeoutTask = new CancelTask(this);
1952 locallyScopedConn.getCancelTimer().schedule(timeoutTask,
1953 batchTimeout);
1954 }
1955
1956 if (this.retrieveGeneratedKeys) {
1957 this.batchedGeneratedKeys = new ArrayList(nbrCommands);
1958 }
1959
1960 for (batchCommandIndex = 0; batchCommandIndex < nbrCommands; batchCommandIndex++) {
1961 Object arg = this.batchedArgs.get(batchCommandIndex);
1962
1963 if (arg instanceof String) {
1964 updateCounts[batchCommandIndex] = executeUpdate((String) arg);
1965 } else {
1966 BatchParams paramArg = (BatchParams) arg;
1967
1968 try {
1969 updateCounts[batchCommandIndex] = executeUpdate(
1970 paramArg.parameterStrings,
1971 paramArg.parameterStreams, paramArg.isStream,
1972 paramArg.streamLengths, paramArg.isNull, true);
1973
1974 if (this.retrieveGeneratedKeys) {
1975 java.sql.ResultSet rs = null;
1976
1977 try {
1978 if (containsOnDuplicateKeyUpdateInSQL())
1979 rs = getGeneratedKeysInternal(1);
1980 else
1981 rs = getGeneratedKeysInternal();
1982
1983 while (rs.next()) {
1984 this.batchedGeneratedKeys
1985 .add(new ByteArrayRow(new byte[][] { rs.getBytes(1) }, getExceptionInterceptor()));
1986 }
1987 } finally {
1988 if (rs != null) {
1989 rs.close();
1990 }
1991 }
1992 }
1993 } catch (SQLException ex) {
1994 updateCounts[batchCommandIndex] = EXECUTE_FAILED;
1995
1996 if (this.continueBatchOnError &&
1997 !(ex instanceof MySQLTimeoutException) &&
1998 !(ex instanceof MySQLStatementCancelledException) &&
1999 !hasDeadlockOrTimeoutRolledBackTx(ex)) {
2000 sqlEx = ex;
2001 } else {
2002 int[] newUpdateCounts = new int[batchCommandIndex];
2003 System.arraycopy(updateCounts, 0,
2004 newUpdateCounts, 0, batchCommandIndex);
2005
2006 throw new java.sql.BatchUpdateException(ex
2007 .getMessage(), ex.getSQLState(), ex
2008 .getErrorCode(), newUpdateCounts);
2009 }
2010 }
2011 }
2012 }
2013
2014 if (sqlEx != null) {
2015 throw new java.sql.BatchUpdateException(sqlEx.getMessage(),
2016 sqlEx.getSQLState(), sqlEx.getErrorCode(), updateCounts);
2017 }
2018 } catch (NullPointerException npe) {
2019 try {
2020 checkClosed();
2021 } catch (SQLException connectionClosedEx) {
2022 updateCounts[batchCommandIndex] = EXECUTE_FAILED;
2023
2024 int[] newUpdateCounts = new int[batchCommandIndex];
2025
2026 System.arraycopy(updateCounts, 0,
2027 newUpdateCounts, 0, batchCommandIndex);
2028
2029 throw new java.sql.BatchUpdateException(connectionClosedEx
2030 .getMessage(), connectionClosedEx.getSQLState(), connectionClosedEx
2031 .getErrorCode(), newUpdateCounts);
2032 }
2033
2034 throw npe; // we don't know why this happened, punt
2035 } finally {
2036 batchCommandIndex = -1;
2037
2038 if (timeoutTask != null) {
2039 timeoutTask.cancel();
2040 }
2041
2042 resetCancelledState();
2043 }
2044 }
2045
2046 return (updateCounts != null) ? updateCounts : new int[0];
2047
2048 }
2049
2050 /**
2051 * Actually execute the prepared statement. This is here so server-side
2052 * PreparedStatements can re-use most of the code from this class.
2053 *
2054 * @param maxRowsToRetrieve
2055 * the max number of rows to return
2056 * @param sendPacket
2057 * the packet to send
2058 * @param createStreamingResultSet
2059 * should a 'streaming' result set be created?
2060 * @param queryIsSelectOnly
2061 * is this query doing a SELECT?
2062 * @param unpackFields
2063 * DOCUMENT ME!
2064 *
2065 * @return the results as a ResultSet
2066 *
2067 * @throws SQLException
2068 * if an error occurs.
2069 */
2070 protected ResultSetInternalMethods executeInternal(int maxRowsToRetrieve,
2071 Buffer sendPacket, boolean createStreamingResultSet,
2072 boolean queryIsSelectOnly, Field[] metadataFromCache,
2073 boolean isBatch)
2074 throws SQLException {
2075 try {
2076
2077 resetCancelledState();
2078
2079 ConnectionImpl locallyScopedConnection = this.connection;
2080
2081 this.numberOfExecutions++;
2082
2083 if (this.doPingInstead) {
2084 doPingInstead();
2085
2086 return this.results;
2087 }
2088
2089 ResultSetInternalMethods rs;
2090
2091 CancelTask timeoutTask = null;
2092
2093 try {
2094 if (locallyScopedConnection.getEnableQueryTimeouts() &&
2095 this.timeoutInMillis != 0
2096 && locallyScopedConnection.versionMeetsMinimum(5, 0, 0)) {
2097 timeoutTask = new CancelTask(this);
2098 locallyScopedConnection.getCancelTimer().schedule(timeoutTask,
2099 this.timeoutInMillis);
2100 }
2101
2102 rs = locallyScopedConnection.execSQL(this, null, maxRowsToRetrieve, sendPacket,
2103 this.resultSetType, this.resultSetConcurrency,
2104 createStreamingResultSet, this.currentCatalog,
2105 metadataFromCache, isBatch);
2106
2107 if (timeoutTask != null) {
2108 timeoutTask.cancel();
2109
2110 if (timeoutTask.caughtWhileCancelling != null) {
2111 throw timeoutTask.caughtWhileCancelling;
2112 }
2113
2114 timeoutTask = null;
2115 }
2116
2117 synchronized (this.cancelTimeoutMutex) {
2118 if (this.wasCancelled) {
2119 SQLException cause = null;
2120
2121 if (this.wasCancelledByTimeout) {
2122 cause = new MySQLTimeoutException();
2123 } else {
2124 cause = new MySQLStatementCancelledException();
2125 }
2126
2127 resetCancelledState();
2128
2129 throw cause;
2130 }
2131 }
2132 } finally {
2133 if (timeoutTask != null) {
2134 timeoutTask.cancel();
2135 }
2136 }
2137
2138 return rs;
2139 } catch (NullPointerException npe) {
2140 checkClosed(); // we can't synchronize ourselves against async connection-close
2141 // due to deadlock issues, so this is the next best thing for
2142 // this particular corner case.
2143
2144 throw npe;
2145 }
2146 }
2147
2148 /**
2149 * A Prepared SQL query is executed and its ResultSet is returned
2150 *
2151 * @return a ResultSet that contains the data produced by the query - never
2152 * null
2153 *
2154 * @exception SQLException
2155 * if a database access error occurs
2156 */
2157 public java.sql.ResultSet executeQuery() throws SQLException {
2158 checkClosed();
2159
2160 ConnectionImpl locallyScopedConn = this.connection;
2161
2162 checkForDml(this.originalSql, this.firstCharOfStmt);
2163
2164 CachedResultSetMetaData cachedMetadata = null;
2165
2166 // We need to execute this all together
2167 // So synchronize on the Connection's mutex (because
2168 // even queries going through there synchronize
2169 // on the same mutex.
2170 synchronized (locallyScopedConn.getMutex()) {
2171 clearWarnings();
2172
2173 boolean doStreaming = createStreamingResultSet();
2174
2175 this.batchedGeneratedKeys = null;
2176
2177 // Adjust net_write_timeout to a higher value if we're
2178 // streaming result sets. More often than not, someone runs into
2179 // an issue where they blow net_write_timeout when using this
2180 // feature, and if they're willing to hold a result set open
2181 // for 30 seconds or more, one more round-trip isn't going to hurt
2182 //
2183 // This is reset by RowDataDynamic.close().
2184
2185 if (doStreaming
2186 && this.connection.getNetTimeoutForStreamingResults() > 0) {
2187
2188 java.sql.Statement stmt = null;
2189
2190 try {
2191 stmt = this.connection.createStatement();
2192
2193 ((com.mysql.jdbc.StatementImpl)stmt).executeSimpleNonQuery(this.connection, "SET net_write_timeout="
2194 + this.connection.getNetTimeoutForStreamingResults());
2195 } finally {
2196 if (stmt != null) {
2197 stmt.close();
2198 }
2199 }
2200 }
2201
2202 Buffer sendPacket = fillSendPacket();
2203
2204 if (this.results != null) {
2205 if (!this.connection.getHoldResultsOpenOverStatementClose()) {
2206 if (!this.holdResultsOpenOverClose) {
2207 this.results.realClose(false);
2208 }
2209 }
2210 }
2211
2212 String oldCatalog = null;
2213
2214 if (!locallyScopedConn.getCatalog().equals(this.currentCatalog)) {
2215 oldCatalog = locallyScopedConn.getCatalog();
2216 locallyScopedConn.setCatalog(this.currentCatalog);
2217 }
2218
2219 //
2220 // Check if we have cached metadata for this query...
2221 //
2222 if (locallyScopedConn.getCacheResultSetMetadata()) {
2223 cachedMetadata = locallyScopedConn.getCachedMetaData(this.originalSql);
2224 }
2225
2226 Field[] metadataFromCache = null;
2227
2228 if (cachedMetadata != null) {
2229 metadataFromCache = cachedMetadata.fields;
2230 }
2231
2232 if (locallyScopedConn.useMaxRows()) {
2233 // If there isn't a limit clause in the SQL
2234 // then limit the number of rows to return in
2235 // an efficient manner. Only do this if
2236 // setMaxRows() hasn't been used on any Statements
2237 // generated from the current Connection (saves
2238 // a query, and network traffic).
2239 if (this.hasLimitClause) {
2240 this.results = executeInternal(this.maxRows, sendPacket,
2241 createStreamingResultSet(), true,
2242 metadataFromCache, false);
2243 } else {
2244 if (this.maxRows <= 0) {
2245 executeSimpleNonQuery(locallyScopedConn,
2246 "SET OPTION SQL_SELECT_LIMIT=DEFAULT");
2247 } else {
2248 executeSimpleNonQuery(locallyScopedConn,
2249 "SET OPTION SQL_SELECT_LIMIT=" + this.maxRows);
2250 }
2251
2252 this.results = executeInternal(-1, sendPacket,
2253 doStreaming, true,
2254 metadataFromCache, false);
2255
2256 if (oldCatalog != null) {
2257 this.connection.setCatalog(oldCatalog);
2258 }
2259 }
2260 } else {
2261 this.results = executeInternal(-1, sendPacket,
2262 doStreaming, true,
2263 metadataFromCache, false);
2264 }
2265
2266 if (oldCatalog != null) {
2267 locallyScopedConn.setCatalog(oldCatalog);
2268 }
2269
2270 if (cachedMetadata != null) {
2271 locallyScopedConn.initializeResultsMetadataFromCache(this.originalSql,
2272 cachedMetadata, this.results);
2273 } else {
2274 if (locallyScopedConn.getCacheResultSetMetadata()) {
2275 locallyScopedConn.initializeResultsMetadataFromCache(this.originalSql,
2276 null /* will be created */, this.results);
2277 }
2278 }
2279 }
2280
2281 this.lastInsertId = this.results.getUpdateID();
2282
2283 return this.results;
2284 }
2285
2286 /**
2287 * Execute a SQL INSERT, UPDATE or DELETE statement. In addition, SQL
2288 * statements that return nothing such as SQL DDL statements can be
2289 * executed.
2290 *
2291 * @return either the row count for INSERT, UPDATE or DELETE; or 0 for SQL
2292 * statements that return nothing.
2293 *
2294 * @exception SQLException
2295 * if a database access error occurs
2296 */
2297 public int executeUpdate() throws SQLException {
2298 return executeUpdate(true, false);
2299 }
2300
2301 /*
2302 * We need this variant, because ServerPreparedStatement calls this for
2303 * batched updates, which will end up clobbering the warnings and generated
2304 * keys we need to gather for the batch.
2305 */
2306 protected int executeUpdate(
2307 boolean clearBatchedGeneratedKeysAndWarnings, boolean isBatch) throws SQLException {
2308 if (clearBatchedGeneratedKeysAndWarnings) {
2309 clearWarnings();
2310 this.batchedGeneratedKeys = null;
2311 }
2312
2313 return executeUpdate(this.parameterValues, this.parameterStreams,
2314 this.isStream, this.streamLengths, this.isNull, isBatch);
2315 }
2316
2317 /**
2318 * Added to allow batch-updates
2319 *
2320 * @param batchedParameterStrings
2321 * string values used in single statement
2322 * @param batchedParameterStreams
2323 * stream values used in single statement
2324 * @param batchedIsStream
2325 * flags for streams used in single statement
2326 * @param batchedStreamLengths
2327 * lengths of streams to be read.
2328 * @param batchedIsNull
2329 * flags for parameters that are null
2330 *
2331 * @return the update count
2332 *
2333 * @throws SQLException
2334 * if a database error occurs
2335 */
2336 protected int executeUpdate(byte[][] batchedParameterStrings,
2337 InputStream[] batchedParameterStreams, boolean[] batchedIsStream,
2338 int[] batchedStreamLengths, boolean[] batchedIsNull, boolean isReallyBatch)
2339 throws SQLException {
2340
2341 checkClosed();
2342
2343 ConnectionImpl locallyScopedConn = this.connection;
2344
2345 if (locallyScopedConn.isReadOnly()) {
2346 throw SQLError.createSQLException(Messages.getString("PreparedStatement.34") //$NON-NLS-1$
2347 + Messages.getString("PreparedStatement.35"), //$NON-NLS-1$
2348 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
2349 }
2350
2351 if ((this.firstCharOfStmt == 'S')
2352 && isSelectQuery()) { //$NON-NLS-1$
2353 throw SQLError.createSQLException(Messages.getString("PreparedStatement.37"), //$NON-NLS-1$
2354 "01S03", getExceptionInterceptor()); //$NON-NLS-1$
2355 }
2356
2357 if (this.results != null) {
2358 if (!locallyScopedConn.getHoldResultsOpenOverStatementClose()) {
2359 this.results.realClose(false);
2360 }
2361 }
2362
2363 ResultSetInternalMethods rs = null;
2364
2365 // The checking and changing of catalogs
2366 // must happen in sequence, so synchronize
2367 // on the same mutex that _conn is using
2368 synchronized (locallyScopedConn.getMutex()) {
2369 Buffer sendPacket = fillSendPacket(batchedParameterStrings,
2370 batchedParameterStreams, batchedIsStream,
2371 batchedStreamLengths);
2372
2373 String oldCatalog = null;
2374
2375 if (!locallyScopedConn.getCatalog().equals(this.currentCatalog)) {
2376 oldCatalog = locallyScopedConn.getCatalog();
2377 locallyScopedConn.setCatalog(this.currentCatalog);
2378 }
2379
2380 //
2381 // Only apply max_rows to selects
2382 //
2383 if (locallyScopedConn.useMaxRows()) {
2384 executeSimpleNonQuery(locallyScopedConn,
2385 "SET OPTION SQL_SELECT_LIMIT=DEFAULT");
2386 }
2387
2388 boolean oldInfoMsgState = false;
2389
2390 if (this.retrieveGeneratedKeys) {
2391 oldInfoMsgState = locallyScopedConn.isReadInfoMsgEnabled();
2392 locallyScopedConn.setReadInfoMsgEnabled(true);
2393 }
2394
2395 rs = executeInternal(-1, sendPacket, false, false, null,
2396 isReallyBatch);
2397
2398 if (this.retrieveGeneratedKeys) {
2399 locallyScopedConn.setReadInfoMsgEnabled(oldInfoMsgState);
2400 rs.setFirstCharOfQuery(this.firstCharOfStmt);
2401 }
2402
2403 if (oldCatalog != null) {
2404 locallyScopedConn.setCatalog(oldCatalog);
2405 }
2406 }
2407
2408 this.results = rs;
2409
2410 this.updateCount = rs.getUpdateCount();
2411
2412 if (containsOnDuplicateKeyUpdateInSQL() &&
2413 this.compensateForOnDuplicateKeyUpdate) {
2414 if (this.updateCount == 2 || this.updateCount == 0) {
2415 this.updateCount = 1;
2416 }
2417 }
2418
2419 int truncatedUpdateCount = 0;
2420
2421 if (this.updateCount > Integer.MAX_VALUE) {
2422 truncatedUpdateCount = Integer.MAX_VALUE;
2423 } else {
2424 truncatedUpdateCount = (int) this.updateCount;
2425 }
2426
2427 this.lastInsertId = rs.getUpdateID();
2428
2429 return truncatedUpdateCount;
2430 }
2431
2432 protected boolean containsOnDuplicateKeyUpdateInSQL() {
2433 return this.parseInfo.isOnDuplicateKeyUpdate;
2434 }
2435
2436
2437
2438 /**
2439 * Creates the packet that contains the query to be sent to the server.
2440 *
2441 * @return A Buffer filled with the query representing the
2442 * PreparedStatement.
2443 *
2444 * @throws SQLException
2445 * if an error occurs.
2446 */
2447 protected Buffer fillSendPacket() throws SQLException {
2448 return fillSendPacket(this.parameterValues, this.parameterStreams,
2449 this.isStream, this.streamLengths);
2450 }
2451
2452 /**
2453 * Creates the packet that contains the query to be sent to the server.
2454 *
2455 * @param batchedParameterStrings
2456 * the parameters as strings
2457 * @param batchedParameterStreams
2458 * the parameters as streams
2459 * @param batchedIsStream
2460 * is the given parameter a stream?
2461 * @param batchedStreamLengths
2462 * the lengths of the streams (if appropriate)
2463 *
2464 * @return a Buffer filled with the query that represents this statement
2465 *
2466 * @throws SQLException
2467 * if an error occurs.
2468 */
2469 protected Buffer fillSendPacket(byte[][] batchedParameterStrings,
2470 InputStream[] batchedParameterStreams, boolean[] batchedIsStream,
2471 int[] batchedStreamLengths) throws SQLException {
2472 Buffer sendPacket = this.connection.getIO().getSharedSendPacket();
2473
2474 sendPacket.clear();
2475
2476 sendPacket.writeByte((byte) MysqlDefs.QUERY);
2477
2478 boolean useStreamLengths = this.connection
2479 .getUseStreamLengthsInPrepStmts();
2480
2481 //
2482 // Try and get this allocation as close as possible
2483 // for BLOBs
2484 //
2485 int ensurePacketSize = 0;
2486
2487 String statementComment = this.connection.getStatementComment();
2488
2489 byte[] commentAsBytes = null;
2490
2491 if (statementComment != null) {
2492 if (this.charConverter != null) {
2493 commentAsBytes = this.charConverter.toBytes(statementComment);
2494 } else {
2495 commentAsBytes = StringUtils.getBytes(statementComment, this.charConverter,
2496 this.charEncoding, this.connection
2497 .getServerCharacterEncoding(), this.connection
2498 .parserKnowsUnicode(), getExceptionInterceptor());
2499 }
2500
2501 ensurePacketSize += commentAsBytes.length;
2502 ensurePacketSize += 6; // for /*[space] [space]*/
2503 }
2504
2505 for (int i = 0; i < batchedParameterStrings.length; i++) {
2506 if (batchedIsStream[i] && useStreamLengths) {
2507 ensurePacketSize += batchedStreamLengths[i];
2508 }
2509 }
2510
2511 if (ensurePacketSize != 0) {
2512 sendPacket.ensureCapacity(ensurePacketSize);
2513 }
2514
2515 if (commentAsBytes != null) {
2516 sendPacket.writeBytesNoNull(Constants.SLASH_STAR_SPACE_AS_BYTES);
2517 sendPacket.writeBytesNoNull(commentAsBytes);
2518 sendPacket.writeBytesNoNull(Constants.SPACE_STAR_SLASH_SPACE_AS_BYTES);
2519 }
2520
2521 for (int i = 0; i < batchedParameterStrings.length; i++) {
2522 checkAllParametersSet(batchedParameterStrings[i],
2523 batchedParameterStreams[i], i);
2524
2525 sendPacket.writeBytesNoNull(this.staticSqlStrings[i]);
2526
2527 if (batchedIsStream[i]) {
2528 streamToBytes(sendPacket, batchedParameterStreams[i], true,
2529 batchedStreamLengths[i], useStreamLengths);
2530 } else {
2531 sendPacket.writeBytesNoNull(batchedParameterStrings[i]);
2532 }
2533 }
2534
2535 sendPacket
2536 .writeBytesNoNull(this.staticSqlStrings[batchedParameterStrings.length]);
2537
2538 return sendPacket;
2539 }
2540
2541 private void checkAllParametersSet(byte[] parameterString,
2542 InputStream parameterStream, int columnIndex) throws SQLException {
2543 if ((parameterString == null)
2544 && parameterStream == null) {
2545 System.out.println(toString());
2546 throw SQLError.createSQLException(Messages
2547 .getString("PreparedStatement.40") //$NON-NLS-1$
2548 + (columnIndex + 1), SQLError.SQL_STATE_WRONG_NO_OF_PARAMETERS, getExceptionInterceptor());
2549 }
2550 }
2551
2552 /**
2553 * Returns a prepared statement for the number of batched parameters, used when re-writing batch INSERTs.
2554 */
2555 protected PreparedStatement prepareBatchedInsertSQL(ConnectionImpl localConn, int numBatches) throws SQLException {
2556 PreparedStatement pstmt = new PreparedStatement(localConn, "Rewritten batch of: " + this.originalSql, this.currentCatalog, this.parseInfo.getParseInfoForBatch(numBatches));
2557 pstmt.setRetrieveGeneratedKeys(this.retrieveGeneratedKeys);
2558 pstmt.rewrittenBatchSize = numBatches;
2559
2560 return pstmt;
2561 }
2562
2563 protected int rewrittenBatchSize = 0;
2564
2565 public int getRewrittenBatchSize() {
2566 return this.rewrittenBatchSize;
2567 }
2568
2569 public String getNonRewrittenSql() {
2570 int indexOfBatch = this.originalSql.indexOf(" of: ");
2571
2572 if (indexOfBatch != -1) {
2573 return this.originalSql.substring(indexOfBatch + 5);
2574 }
2575
2576 return this.originalSql;
2577 }
2578
2579
2580 /**
2581 * DOCUMENT ME!
2582 *
2583 * @param parameterIndex
2584 * DOCUMENT ME!
2585 *
2586 * @return DOCUMENT ME!
2587 *
2588 * @throws SQLException
2589 * DOCUMENT ME!
2590 */
2591 public byte[] getBytesRepresentation(int parameterIndex)
2592 throws SQLException {
2593 if (this.isStream[parameterIndex]) {
2594 return streamToBytes(this.parameterStreams[parameterIndex], false,
2595 this.streamLengths[parameterIndex], this.connection
2596 .getUseStreamLengthsInPrepStmts());
2597 }
2598
2599 byte[] parameterVal = this.parameterValues[parameterIndex];
2600
2601 if (parameterVal == null) {
2602 return null;
2603 }
2604
2605 if ((parameterVal[0] == '\'')
2606 && (parameterVal[parameterVal.length - 1] == '\'')) {
2607 byte[] valNoQuotes = new byte[parameterVal.length - 2];
2608 System.arraycopy(parameterVal, 1, valNoQuotes, 0,
2609 parameterVal.length - 2);
2610
2611 return valNoQuotes;
2612 }
2613
2614 return parameterVal;
2615 }
2616
2617 /**
2618 * Get bytes representation for a parameter in a statement batch.
2619 * @param parameterIndex
2620 * @param commandIndex
2621 * @return
2622 * @throws SQLException
2623 */
2624 protected byte[] getBytesRepresentationForBatch(int parameterIndex, int commandIndex)
2625 throws SQLException {
2626 Object batchedArg = batchedArgs.get(commandIndex);
2627 if (batchedArg instanceof String) {
2628 try {
2629 return ((String)batchedArg).getBytes(charEncoding);
2630
2631 } catch (UnsupportedEncodingException uue) {
2632 throw new RuntimeException(Messages
2633 .getString("PreparedStatement.32") //$NON-NLS-1$
2634 + this.charEncoding
2635 + Messages.getString("PreparedStatement.33")); //$NON-NLS-1$
2636 }
2637 }
2638
2639 BatchParams params = (BatchParams)batchedArg;
2640 if (params.isStream[parameterIndex])
2641 return streamToBytes(params.parameterStreams[parameterIndex], false,
2642 params.streamLengths[parameterIndex],
2643 connection.getUseStreamLengthsInPrepStmts());
2644 byte parameterVal[] = params.parameterStrings[parameterIndex];
2645 if (parameterVal == null)
2646 return null;
2647
2648 if ((parameterVal[0] == '\'')
2649 && (parameterVal[parameterVal.length - 1] == '\'')) {
2650 byte[] valNoQuotes = new byte[parameterVal.length - 2];
2651 System.arraycopy(parameterVal, 1, valNoQuotes, 0,
2652 parameterVal.length - 2);
2653
2654 return valNoQuotes;
2655 }
2656
2657 return parameterVal;
2658 }
2659
2660 // --------------------------JDBC 2.0-----------------------------
2661
2662 private final String getDateTimePattern(String dt, boolean toTime)
2663 throws Exception {
2664 //
2665 // Special case
2666 //
2667 int dtLength = (dt != null) ? dt.length() : 0;
2668
2669 if ((dtLength >= 8) && (dtLength <= 10)) {
2670 int dashCount = 0;
2671 boolean isDateOnly = true;
2672
2673 for (int i = 0; i < dtLength; i++) {
2674 char c = dt.charAt(i);
2675
2676 if (!Character.isDigit(c) && (c != '-')) {
2677 isDateOnly = false;
2678
2679 break;
2680 }
2681
2682 if (c == '-') {
2683 dashCount++;
2684 }
2685 }
2686
2687 if (isDateOnly && (dashCount == 2)) {
2688 return "yyyy-MM-dd"; //$NON-NLS-1$
2689 }
2690 }
2691
2692 //
2693 // Special case - time-only
2694 //
2695 boolean colonsOnly = true;
2696
2697 for (int i = 0; i < dtLength; i++) {
2698 char c = dt.charAt(i);
2699
2700 if (!Character.isDigit(c) && (c != ':')) {
2701 colonsOnly = false;
2702
2703 break;
2704 }
2705 }
2706
2707 if (colonsOnly) {
2708 return "HH:mm:ss"; //$NON-NLS-1$
2709 }
2710
2711 int n;
2712 int z;
2713 int count;
2714 int maxvecs;
2715 char c;
2716 char separator;
2717 StringReader reader = new StringReader(dt + " "); //$NON-NLS-1$
2718 ArrayList vec = new ArrayList();
2719 ArrayList vecRemovelist = new ArrayList();
2720 Object[] nv = new Object[3];
2721 Object[] v;
2722 nv[0] = Constants.characterValueOf('y');
2723 nv[1] = new StringBuffer();
2724 nv[2] = Constants.integerValueOf(0);
2725 vec.add(nv);
2726
2727 if (toTime) {
2728 nv = new Object[3];
2729 nv[0] = Constants.characterValueOf('h');
2730 nv[1] = new StringBuffer();
2731 nv[2] = Constants.integerValueOf(0);
2732 vec.add(nv);
2733 }
2734
2735 while ((z = reader.read()) != -1) {
2736 separator = (char) z;
2737 maxvecs = vec.size();
2738
2739 for (count = 0; count < maxvecs; count++) {
2740 v = (Object[]) vec.get(count);
2741 n = ((Integer) v[2]).intValue();
2742 c = getSuccessor(((Character) v[0]).charValue(), n);
2743
2744 if (!Character.isLetterOrDigit(separator)) {
2745 if ((c == ((Character) v[0]).charValue()) && (c != 'S')) {
2746 vecRemovelist.add(v);
2747 } else {
2748 ((StringBuffer) v[1]).append(separator);
2749
2750 if ((c == 'X') || (c == 'Y')) {
2751 v[2] = Constants.integerValueOf(4);
2752 }
2753 }
2754 } else {
2755 if (c == 'X') {
2756 c = 'y';
2757 nv = new Object[3];
2758 nv[1] = (new StringBuffer(((StringBuffer) v[1])
2759 .toString())).append('M');
2760 nv[0] = Constants.characterValueOf('M');
2761 nv[2] = Constants.integerValueOf(1);
2762 vec.add(nv);
2763 } else if (c == 'Y') {
2764 c = 'M';
2765 nv = new Object[3];
2766 nv[1] = (new StringBuffer(((StringBuffer) v[1])
2767 .toString())).append('d');
2768 nv[0] = Constants.characterValueOf('d');
2769 nv[2] = Constants.integerValueOf(1);
2770 vec.add(nv);
2771 }
2772
2773 ((StringBuffer) v[1]).append(c);
2774
2775 if (c == ((Character) v[0]).charValue()) {
2776 v[2] = Constants.integerValueOf(n + 1);
2777 } else {
2778 v[0] = Constants.characterValueOf(c);
2779 v[2] = Constants.integerValueOf(1);
2780 }
2781 }
2782 }
2783
2784 int size = vecRemovelist.size();
2785
2786 for (int i = 0; i < size; i++) {
2787 v = (Object[]) vecRemovelist.get(i);
2788 vec.remove(v);
2789 }
2790
2791 vecRemovelist.clear();
2792 }
2793
2794 int size = vec.size();
2795
2796 for (int i = 0; i < size; i++) {
2797 v = (Object[]) vec.get(i);
2798 c = ((Character) v[0]).charValue();
2799 n = ((Integer) v[2]).intValue();
2800
2801 boolean bk = getSuccessor(c, n) != c;
2802 boolean atEnd = (((c == 's') || (c == 'm') || ((c == 'h') && toTime)) && bk);
2803 boolean finishesAtDate = (bk && (c == 'd') && !toTime);
2804 boolean containsEnd = (((StringBuffer) v[1]).toString()
2805 .indexOf('W') != -1);
2806
2807 if ((!atEnd && !finishesAtDate) || (containsEnd)) {
2808 vecRemovelist.add(v);
2809 }
2810 }
2811
2812 size = vecRemovelist.size();
2813
2814 for (int i = 0; i < size; i++) {
2815 vec.remove(vecRemovelist.get(i));
2816 }
2817
2818 vecRemovelist.clear();
2819 v = (Object[]) vec.get(0); // might throw exception
2820
2821 StringBuffer format = (StringBuffer) v[1];
2822 format.setLength(format.length() - 1);
2823
2824 return format.toString();
2825 }
2826
2827 /**
2828 * The number, types and properties of a ResultSet's columns are provided by
2829 * the getMetaData method.
2830 *
2831 * @return the description of a ResultSet's columns
2832 *
2833 * @exception SQLException
2834 * if a database-access error occurs.
2835 */
2836 public java.sql.ResultSetMetaData getMetaData()
2837 throws SQLException {
2838
2839 //
2840 // We could just tack on a LIMIT 0 here no matter what the
2841 // statement, and check if a result set was returned or not,
2842 // but I'm not comfortable with that, myself, so we take
2843 // the "safer" road, and only allow metadata for _actual_
2844 // SELECTS (but not SHOWs).
2845 //
2846 // CALL's are trapped further up and you end up with a
2847 // CallableStatement anyway.
2848 //
2849
2850 if (!isSelectQuery()) {
2851 return null;
2852 }
2853
2854 PreparedStatement mdStmt = null;
2855 java.sql.ResultSet mdRs = null;
2856
2857 if (this.pstmtResultMetaData == null) {
2858 try {
2859 mdStmt = new PreparedStatement(this.connection,
2860 this.originalSql, this.currentCatalog, this.parseInfo);
2861
2862 mdStmt.setMaxRows(0);
2863
2864 int paramCount = this.parameterValues.length;
2865
2866 for (int i = 1; i <= paramCount; i++) {
2867 mdStmt.setString(i, ""); //$NON-NLS-1$
2868 }
2869
2870 boolean hadResults = mdStmt.execute();
2871
2872 if (hadResults) {
2873 mdRs = mdStmt.getResultSet();
2874
2875 this.pstmtResultMetaData = mdRs.getMetaData();
2876 } else {
2877 this.pstmtResultMetaData = new ResultSetMetaData(
2878 new Field[0],
2879 this.connection.getUseOldAliasMetadataBehavior(), getExceptionInterceptor());
2880 }
2881 } finally {
2882 SQLException sqlExRethrow = null;
2883
2884 if (mdRs != null) {
2885 try {
2886 mdRs.close();
2887 } catch (SQLException sqlEx) {
2888 sqlExRethrow = sqlEx;
2889 }
2890
2891 mdRs = null;
2892 }
2893
2894 if (mdStmt != null) {
2895 try {
2896 mdStmt.close();
2897 } catch (SQLException sqlEx) {
2898 sqlExRethrow = sqlEx;
2899 }
2900
2901 mdStmt = null;
2902 }
2903
2904 if (sqlExRethrow != null) {
2905 throw sqlExRethrow;
2906 }
2907 }
2908 }
2909
2910 return this.pstmtResultMetaData;
2911 }
2912
2913 protected boolean isSelectQuery() {
2914 return StringUtils.startsWithIgnoreCaseAndWs(
2915 StringUtils.stripComments(this.originalSql,
2916 "'\"", "'\"", true, false, true, true),
2917 "SELECT");
2918 }
2919
2920 /**
2921 * @see PreparedStatement#getParameterMetaData()
2922 */
2923 public ParameterMetaData getParameterMetaData()
2924 throws SQLException {
2925 if (this.parameterMetaData == null) {
2926 if (this.connection.getGenerateSimpleParameterMetadata()) {
2927 this.parameterMetaData = new MysqlParameterMetadata(this.parameterCount);
2928 } else {
2929 this.parameterMetaData = new MysqlParameterMetadata(
2930 null, this.parameterCount, getExceptionInterceptor());
2931 }
2932 }
2933
2934 return this.parameterMetaData;
2935 }
2936
2937 ParseInfo getParseInfo() {
2938 return this.parseInfo;
2939 }
2940
2941 private final char getSuccessor(char c, int n) {
2942 return ((c == 'y') && (n == 2)) ? 'X'
2943 : (((c == 'y') && (n < 4)) ? 'y'
2944 : ((c == 'y') ? 'M'
2945 : (((c == 'M') && (n == 2)) ? 'Y'
2946 : (((c == 'M') && (n < 3)) ? 'M'
2947 : ((c == 'M') ? 'd'
2948 : (((c == 'd') && (n < 2)) ? 'd'
2949 : ((c == 'd') ? 'H'
2950 : (((c == 'H') && (n < 2)) ? 'H'
2951 : ((c == 'H') ? 'm'
2952 : (((c == 'm') && (n < 2)) ? 'm'
2953 : ((c == 'm') ? 's'
2954 : (((c == 's') && (n < 2)) ? 's'
2955 : 'W'))))))))))));
2956 }
2957
2958 /**
2959 * Used to escape binary data with hex for mb charsets
2960 *
2961 * @param buf
2962 * @param packet
2963 * @param size
2964 * @throws SQLException
2965 */
2966 private final void hexEscapeBlock(byte[] buf, Buffer packet, int size)
2967 throws SQLException {
2968 for (int i = 0; i < size; i++) {
2969 byte b = buf[i];
2970 int lowBits = (b & 0xff) / 16;
2971 int highBits = (b & 0xff) % 16;
2972
2973 packet.writeByte(HEX_DIGITS[lowBits]);
2974 packet.writeByte(HEX_DIGITS[highBits]);
2975 }
2976 }
2977
2978 private void initializeFromParseInfo() throws SQLException {
2979 this.staticSqlStrings = this.parseInfo.staticSql;
2980 this.hasLimitClause = this.parseInfo.foundLimitClause;
2981 this.isLoadDataQuery = this.parseInfo.foundLoadData;
2982 this.firstCharOfStmt = this.parseInfo.firstStmtChar;
2983
2984 this.parameterCount = this.staticSqlStrings.length - 1;
2985
2986 this.parameterValues = new byte[this.parameterCount][];
2987 this.parameterStreams = new InputStream[this.parameterCount];
2988 this.isStream = new boolean[this.parameterCount];
2989 this.streamLengths = new int[this.parameterCount];
2990 this.isNull = new boolean[this.parameterCount];
2991 this.parameterTypes = new int[this.parameterCount];
2992
2993 clearParameters();
2994
2995 for (int j = 0; j < this.parameterCount; j++) {
2996 this.isStream[j] = false;
2997 }
2998 }
2999
3000 boolean isNull(int paramIndex) {
3001 return this.isNull[paramIndex];
3002 }
3003
3004 private final int readblock(InputStream i, byte[] b) throws SQLException {
3005 try {
3006 return i.read(b);
3007 } catch (Throwable ex) {
3008 SQLException sqlEx = SQLError.createSQLException(Messages.getString("PreparedStatement.56") //$NON-NLS-1$
3009 + ex.getClass().getName(), SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
3010 sqlEx.initCause(ex);
3011
3012 throw sqlEx;
3013 }
3014 }
3015
3016 private final int readblock(InputStream i, byte[] b, int length)
3017 throws SQLException {
3018 try {
3019 int lengthToRead = length;
3020
3021 if (lengthToRead > b.length) {
3022 lengthToRead = b.length;
3023 }
3024
3025 return i.read(b, 0, lengthToRead);
3026 } catch (Throwable ex) {
3027 SQLException sqlEx = SQLError.createSQLException(Messages.getString("PreparedStatement.56") //$NON-NLS-1$
3028 + ex.getClass().getName(), SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
3029 sqlEx.initCause(ex);
3030
3031 throw sqlEx;
3032 }
3033 }
3034
3035 /**
3036 * Closes this statement, releasing all resources
3037 *
3038 * @param calledExplicitly
3039 * was this called by close()?
3040 *
3041 * @throws SQLException
3042 * if an error occurs
3043 */
3044 protected void realClose(boolean calledExplicitly,
3045 boolean closeOpenResults) throws SQLException {
3046 if (this.useUsageAdvisor) {
3047 if (this.numberOfExecutions <= 1) {
3048 String message = Messages.getString("PreparedStatement.43"); //$NON-NLS-1$
3049
3050 this.eventSink.consumeEvent(new ProfilerEvent(
3051 ProfilerEvent.TYPE_WARN, "", this.currentCatalog, //$NON-NLS-1$
3052 this.connectionId, this.getId(), -1, System
3053 .currentTimeMillis(), 0, Constants.MILLIS_I18N,
3054 null,
3055 this.pointOfOrigin, message));
3056 }
3057 }
3058
3059 super.realClose(calledExplicitly, closeOpenResults);
3060
3061 this.dbmd = null;
3062 this.originalSql = null;
3063 this.staticSqlStrings = null;
3064 this.parameterValues = null;
3065 this.parameterStreams = null;
3066 this.isStream = null;
3067 this.streamLengths = null;
3068 this.isNull = null;
3069 this.streamConvertBuf = null;
3070 this.parameterTypes = null;
3071 }
3072
3073 /**
3074 * JDBC 2.0 Set an Array parameter.
3075 *
3076 * @param i
3077 * the first parameter is 1, the second is 2, ...
3078 * @param x
3079 * an object representing an SQL array
3080 *
3081 * @throws SQLException
3082 * because this method is not implemented.
3083 * @throws NotImplemented
3084 * DOCUMENT ME!
3085 */
3086 public void setArray(int i, Array x) throws SQLException {
3087 throw SQLError.notImplemented();
3088 }
3089
3090 /**
3091 * When a very large ASCII value is input to a LONGVARCHAR parameter, it may
3092 * be more practical to send it via a java.io.InputStream. JDBC will read
3093 * the data from the stream as needed, until it reaches end-of-file. The
3094 * JDBC driver will do any necessary conversion from ASCII to the database
3095 * char format.
3096 *
3097 * <P>
3098 * <B>Note:</B> This stream object can either be a standard Java stream
3099 * object or your own subclass that implements the standard interface.
3100 * </p>
3101 *
3102 * @param parameterIndex
3103 * the first parameter is 1...
3104 * @param x
3105 * the parameter value
3106 * @param length
3107 * the number of bytes in the stream
3108 *
3109 * @exception SQLException
3110 * if a database access error occurs
3111 */
3112 public void setAsciiStream(int parameterIndex, InputStream x,
3113 int length) throws SQLException {
3114 if (x == null) {
3115 setNull(parameterIndex, java.sql.Types.VARCHAR);
3116 } else {
3117 setBinaryStream(parameterIndex, x, length);
3118 }
3119 }
3120
3121 /**
3122 * Set a parameter to a java.math.BigDecimal value. The driver converts this
3123 * to a SQL NUMERIC value when it sends it to the database.
3124 *
3125 * @param parameterIndex
3126 * the first parameter is 1...
3127 * @param x
3128 * the parameter value
3129 *
3130 * @exception SQLException
3131 * if a database access error occurs
3132 */
3133 public void setBigDecimal(int parameterIndex, BigDecimal x)
3134 throws SQLException {
3135 if (x == null) {
3136 setNull(parameterIndex, java.sql.Types.DECIMAL);
3137 } else {
3138 setInternal(parameterIndex, StringUtils
3139 .fixDecimalExponent(StringUtils.consistentToString(x)));
3140
3141 this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.DECIMAL;
3142 }
3143 }
3144
3145 /**
3146 * When a very large binary value is input to a LONGVARBINARY parameter, it
3147 * may be more practical to send it via a java.io.InputStream. JDBC will
3148 * read the data from the stream as needed, until it reaches end-of-file.
3149 *
3150 * <P>
3151 * <B>Note:</B> This stream object can either be a standard Java stream
3152 * object or your own subclass that implements the standard interface.
3153 * </p>
3154 *
3155 * @param parameterIndex
3156 * the first parameter is 1...
3157 * @param x
3158 * the parameter value
3159 * @param length
3160 * the number of bytes to read from the stream (ignored)
3161 *
3162 * @throws SQLException
3163 * if a database access error occurs
3164 */
3165 public void setBinaryStream(int parameterIndex, InputStream x, int length)
3166 throws SQLException {
3167 if (x == null) {
3168 setNull(parameterIndex, java.sql.Types.BINARY);
3169 } else {
3170 int parameterIndexOffset = getParameterIndexOffset();
3171
3172 if ((parameterIndex < 1)
3173 || (parameterIndex > this.staticSqlStrings.length)) {
3174 throw SQLError.createSQLException(
3175 Messages.getString("PreparedStatement.2") //$NON-NLS-1$
3176 + parameterIndex
3177 + Messages.getString("PreparedStatement.3") + this.staticSqlStrings.length + Messages.getString("PreparedStatement.4"), //$NON-NLS-1$ //$NON-NLS-2$
3178 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
3179 } else if (parameterIndexOffset == -1 && parameterIndex == 1) {
3180 throw SQLError.createSQLException("Can't set IN parameter for return value of stored function call.",
3181 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
3182 }
3183
3184
3185 this.parameterStreams[parameterIndex - 1 + parameterIndexOffset] = x;
3186 this.isStream[parameterIndex - 1 + parameterIndexOffset] = true;
3187 this.streamLengths[parameterIndex - 1 + parameterIndexOffset] = length;
3188 this.isNull[parameterIndex - 1 + parameterIndexOffset] = false;
3189 this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.BLOB;
3190 }
3191 }
3192
3193 public void setBlob(int parameterIndex, InputStream inputStream, long length)
3194 throws SQLException {
3195 setBinaryStream(parameterIndex, inputStream, (int)length);
3196 }
3197
3198 /**
3199 * JDBC 2.0 Set a BLOB parameter.
3200 *
3201 * @param i
3202 * the first parameter is 1, the second is 2, ...
3203 * @param x
3204 * an object representing a BLOB
3205 *
3206 * @throws SQLException
3207 * if a database error occurs
3208 */
3209 public void setBlob(int i, java.sql.Blob x) throws SQLException {
3210 if (x == null) {
3211 setNull(i, Types.BLOB);
3212 } else {
3213 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
3214
3215 bytesOut.write('\'');
3216 escapeblockFast(x.getBytes(1, (int) x.length()), bytesOut, (int) x
3217 .length());
3218 bytesOut.write('\'');
3219
3220 setInternal(i, bytesOut.toByteArray());
3221
3222 this.parameterTypes[i - 1 + getParameterIndexOffset()] = Types.BLOB;
3223 }
3224 }
3225
3226 /**
3227 * Set a parameter to a Java boolean value. The driver converts this to a
3228 * SQL BIT value when it sends it to the database.
3229 *
3230 * @param parameterIndex
3231 * the first parameter is 1...
3232 * @param x
3233 * the parameter value
3234 *
3235 * @throws SQLException
3236 * if a database access error occurs
3237 */
3238 public void setBoolean(int parameterIndex, boolean x) throws SQLException {
3239 if (this.useTrueBoolean) {
3240 setInternal(parameterIndex, x ? "1" : "0"); //$NON-NLS-1$ //$NON-NLS-2$
3241 } else {
3242 setInternal(parameterIndex, x ? "'t'" : "'f'"); //$NON-NLS-1$ //$NON-NLS-2$
3243
3244 this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.BOOLEAN;
3245 }
3246 }
3247
3248 /**
3249 * Set a parameter to a Java byte value. The driver converts this to a SQL
3250 * TINYINT value when it sends it to the database.
3251 *
3252 * @param parameterIndex
3253 * the first parameter is 1...
3254 * @param x
3255 * the parameter value
3256 *
3257 * @exception SQLException
3258 * if a database access error occurs
3259 */
3260 public void setByte(int parameterIndex, byte x) throws SQLException {
3261 setInternal(parameterIndex, String.valueOf(x));
3262
3263 this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.TINYINT;
3264 }
3265
3266 /**
3267 * Set a parameter to a Java array of bytes. The driver converts this to a
3268 * SQL VARBINARY or LONGVARBINARY (depending on the argument's size relative
3269 * to the driver's limits on VARBINARYs) when it sends it to the database.
3270 *
3271 * @param parameterIndex
3272 * the first parameter is 1...
3273 * @param x
3274 * the parameter value
3275 *
3276 * @exception SQLException
3277 * if a database access error occurs
3278 */
3279 public void setBytes(int parameterIndex, byte[] x) throws SQLException {
3280 setBytes(parameterIndex, x, true, true);
3281
3282 if (x != null) {
3283 this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.BINARY;
3284 }
3285 }
3286
3287 protected void setBytes(int parameterIndex, byte[] x,
3288 boolean checkForIntroducer, boolean escapeForMBChars)
3289 throws SQLException {
3290 if (x == null) {
3291 setNull(parameterIndex, java.sql.Types.BINARY);
3292 } else {
3293 String connectionEncoding = this.connection.getEncoding();
3294
3295 if (this.connection.isNoBackslashEscapesSet()
3296 || (escapeForMBChars
3297 && this.connection.getUseUnicode()
3298 && connectionEncoding != null
3299 && CharsetMapping.isMultibyteCharset(connectionEncoding))) {
3300
3301 // Send as hex
3302
3303 ByteArrayOutputStream bOut = new ByteArrayOutputStream(
3304 (x.length * 2) + 3);
3305 bOut.write('x');
3306 bOut.write('\'');
3307
3308 for (int i = 0; i < x.length; i++) {
3309 int lowBits = (x[i] & 0xff) / 16;
3310 int highBits = (x[i] & 0xff) % 16;
3311
3312 bOut.write(HEX_DIGITS[lowBits]);
3313 bOut.write(HEX_DIGITS[highBits]);
3314 }
3315
3316 bOut.write('\'');
3317
3318 setInternal(parameterIndex, bOut.toByteArray());
3319
3320 return;
3321 }
3322
3323 // escape them
3324 int numBytes = x.length;
3325
3326 int pad = 2;
3327
3328 boolean needsIntroducer = checkForIntroducer
3329 && this.connection.versionMeetsMinimum(4, 1, 0);
3330
3331 if (needsIntroducer) {
3332 pad += 7;
3333 }
3334
3335 ByteArrayOutputStream bOut = new ByteArrayOutputStream(numBytes
3336 + pad);
3337
3338 if (needsIntroducer) {
3339 bOut.write('_');
3340 bOut.write('b');
3341 bOut.write('i');
3342 bOut.write('n');
3343 bOut.write('a');
3344 bOut.write('r');
3345 bOut.write('y');
3346 }
3347 bOut.write('\'');
3348
3349 for (int i = 0; i < numBytes; ++i) {
3350 byte b = x[i];
3351
3352 switch (b) {
3353 case 0: /* Must be escaped for 'mysql' */
3354 bOut.write('\\');
3355 bOut.write('0');
3356
3357 break;
3358
3359 case '\n': /* Must be escaped for logs */
3360 bOut.write('\\');
3361 bOut.write('n');
3362
3363 break;
3364
3365 case '\r':
3366 bOut.write('\\');
3367 bOut.write('r');
3368
3369 break;
3370
3371 case '\\':
3372 bOut.write('\\');
3373 bOut.write('\\');
3374
3375 break;
3376
3377 case '\'':
3378 bOut.write('\\');
3379 bOut.write('\'');
3380
3381 break;
3382
3383 case '"': /* Better safe than sorry */
3384 bOut.write('\\');
3385 bOut.write('"');
3386
3387 break;
3388
3389 case '\032': /* This gives problems on Win32 */
3390 bOut.write('\\');
3391 bOut.write('Z');
3392
3393 break;
3394
3395 default:
3396 bOut.write(b);
3397 }
3398 }
3399
3400 bOut.write('\'');
3401
3402 setInternal(parameterIndex, bOut.toByteArray());
3403 }
3404 }
3405
3406 /**
3407 * Used by updatable result sets for refreshRow() because the parameter has
3408 * already been escaped for updater or inserter prepared statements.
3409 *
3410 * @param parameterIndex
3411 * the parameter to set.
3412 * @param parameterAsBytes
3413 * the parameter as a string.
3414 *
3415 * @throws SQLException
3416 * if an error occurs
3417 */
3418 protected void setBytesNoEscape(int parameterIndex, byte[] parameterAsBytes)
3419 throws SQLException {
3420 byte[] parameterWithQuotes = new byte[parameterAsBytes.length + 2];
3421 parameterWithQuotes[0] = '\'';
3422 System.arraycopy(parameterAsBytes, 0, parameterWithQuotes, 1,
3423 parameterAsBytes.length);
3424 parameterWithQuotes[parameterAsBytes.length + 1] = '\'';
3425
3426 setInternal(parameterIndex, parameterWithQuotes);
3427 }
3428
3429 protected void setBytesNoEscapeNoQuotes(int parameterIndex,
3430 byte[] parameterAsBytes) throws SQLException {
3431 setInternal(parameterIndex, parameterAsBytes);
3432 }
3433
3434 /**
3435 * JDBC 2.0 When a very large UNICODE value is input to a LONGVARCHAR
3436 * parameter, it may be more practical to send it via a java.io.Reader. JDBC
3437 * will read the data from the stream as needed, until it reaches
3438 * end-of-file. The JDBC driver will do any necessary conversion from
3439 * UNICODE to the database char format.
3440 *
3441 * <P>
3442 * <B>Note:</B> This stream object can either be a standard Java stream
3443 * object or your own subclass that implements the standard interface.
3444 * </p>
3445 *
3446 * @param parameterIndex
3447 * the first parameter is 1, the second is 2, ...
3448 * @param reader
3449 * the java reader which contains the UNICODE data
3450 * @param length
3451 * the number of characters in the stream
3452 *
3453 * @exception SQLException
3454 * if a database-access error occurs.
3455 */
3456 public void setCharacterStream(int parameterIndex, java.io.Reader reader,
3457 int length) throws SQLException {
3458 try {
3459 if (reader == null) {
3460 setNull(parameterIndex, Types.LONGVARCHAR);
3461 } else {
3462 char[] c = null;
3463 int len = 0;
3464
3465 boolean useLength = this.connection
3466 .getUseStreamLengthsInPrepStmts();
3467
3468 String forcedEncoding = this.connection.getClobCharacterEncoding();
3469
3470 if (useLength && (length != -1)) {
3471 c = new char[length];
3472
3473 int numCharsRead = readFully(reader, c, length); // blocks
3474 // until
3475 // all
3476 // read
3477
3478 if (forcedEncoding == null) {
3479 setString(parameterIndex, new String(c, 0, numCharsRead));
3480 } else {
3481 try {
3482 setBytes(parameterIndex, new String(c,
3483 0,
3484 numCharsRead).getBytes(forcedEncoding));
3485 } catch (UnsupportedEncodingException uee) {
3486 throw SQLError.createSQLException("Unsupported character encoding " +
3487 forcedEncoding, SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
3488 }
3489 }
3490 } else {
3491 c = new char[4096];
3492
3493 StringBuffer buf = new StringBuffer();
3494
3495 while ((len = reader.read(c)) != -1) {
3496 buf.append(c, 0, len);
3497 }
3498
3499 if (forcedEncoding == null) {
3500 setString(parameterIndex, buf.toString());
3501 } else {
3502 try {
3503 setBytes(parameterIndex,
3504 buf.toString().getBytes(forcedEncoding));
3505 } catch (UnsupportedEncodingException uee) {
3506 throw SQLError.createSQLException("Unsupported character encoding " +
3507 forcedEncoding, SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
3508 }
3509 }
3510 }
3511
3512 this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.CLOB;
3513 }
3514 } catch (java.io.IOException ioEx) {
3515 throw SQLError.createSQLException(ioEx.toString(),
3516 SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
3517 }
3518 }
3519
3520 /**
3521 * JDBC 2.0 Set a CLOB parameter.
3522 *
3523 * @param i
3524 * the first parameter is 1, the second is 2, ...
3525 * @param x
3526 * an object representing a CLOB
3527 *
3528 * @throws SQLException
3529 * if a database error occurs
3530 */
3531 public void setClob(int i, Clob x) throws SQLException {
3532 if (x == null) {
3533 setNull(i, Types.CLOB);
3534 } else {
3535
3536 String forcedEncoding = this.connection.getClobCharacterEncoding();
3537
3538 if (forcedEncoding == null) {
3539 setString(i, x.getSubString(1L, (int) x.length()));
3540 } else {
3541 try {
3542 setBytes(i, x.getSubString(1L,
3543 (int)x.length()).getBytes(forcedEncoding));
3544 } catch (UnsupportedEncodingException uee) {
3545 throw SQLError.createSQLException("Unsupported character encoding " +
3546 forcedEncoding, SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
3547 }
3548 }
3549
3550 this.parameterTypes[i - 1 + getParameterIndexOffset()] = Types.CLOB;
3551 }
3552 }
3553
3554 /**
3555 * Set a parameter to a java.sql.Date value. The driver converts this to a
3556 * SQL DATE value when it sends it to the database.
3557 *
3558 * @param parameterIndex
3559 * the first parameter is 1...
3560 * @param x
3561 * the parameter value
3562 *
3563 * @exception java.sql.SQLException
3564 * if a database access error occurs
3565 */
3566 public void setDate(int parameterIndex, java.sql.Date x)
3567 throws java.sql.SQLException {
3568 setDate(parameterIndex, x, null);
3569 }
3570
3571 /**
3572 * Set a parameter to a java.sql.Date value. The driver converts this to a
3573 * SQL DATE value when it sends it to the database.
3574 *
3575 * @param parameterIndex
3576 * the first parameter is 1, the second is 2, ...
3577 * @param x
3578 * the parameter value
3579 * @param cal
3580 * the calendar to interpret the date with
3581 *
3582 * @exception SQLException
3583 * if a database-access error occurs.
3584 */
3585 public void setDate(int parameterIndex, java.sql.Date x, Calendar cal)
3586 throws SQLException {
3587 if (x == null) {
3588 setNull(parameterIndex, java.sql.Types.DATE);
3589 } else {
3590 checkClosed();
3591
3592 if (!this.useLegacyDatetimeCode) {
3593 newSetDateInternal(parameterIndex, x, cal);
3594 } else {
3595 // FIXME: Have instance version of this, problem as it's
3596 // not thread-safe :(
3597 SimpleDateFormat dateFormatter = new SimpleDateFormat(
3598 "''yyyy-MM-dd''", Locale.US); //$NON-NLS-1$
3599 setInternal(parameterIndex, dateFormatter.format(x));
3600
3601 this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.DATE;
3602 }
3603 }
3604 }
3605
3606 /**
3607 * Set a parameter to a Java double value. The driver converts this to a SQL
3608 * DOUBLE value when it sends it to the database
3609 *
3610 * @param parameterIndex
3611 * the first parameter is 1...
3612 * @param x
3613 * the parameter value
3614 *
3615 * @exception SQLException
3616 * if a database access error occurs
3617 */
3618 public void setDouble(int parameterIndex, double x) throws SQLException {
3619
3620 if (!this.connection.getAllowNanAndInf()
3621 && (x == Double.POSITIVE_INFINITY
3622 || x == Double.NEGATIVE_INFINITY || Double.isNaN(x))) {
3623 throw SQLError.createSQLException("'" + x
3624 + "' is not a valid numeric or approximate numeric value",
3625 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
3626
3627 }
3628
3629 setInternal(parameterIndex, StringUtils.fixDecimalExponent(String
3630 .valueOf(x)));
3631
3632 this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.DOUBLE;
3633 }
3634
3635 /**
3636 * Set a parameter to a Java float value. The driver converts this to a SQL
3637 * FLOAT value when it sends it to the database.
3638 *
3639 * @param parameterIndex
3640 * the first parameter is 1...
3641 * @param x
3642 * the parameter value
3643 *
3644 * @exception SQLException
3645 * if a database access error occurs
3646 */
3647 public void setFloat(int parameterIndex, float x) throws SQLException {
3648 setInternal(parameterIndex, StringUtils.fixDecimalExponent(String
3649 .valueOf(x)));
3650
3651 this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.FLOAT;
3652 }
3653
3654 /**
3655 * Set a parameter to a Java int value. The driver converts this to a SQL
3656 * INTEGER value when it sends it to the database.
3657 *
3658 * @param parameterIndex
3659 * the first parameter is 1...
3660 * @param x
3661 * the parameter value
3662 *
3663 * @exception SQLException
3664 * if a database access error occurs
3665 */
3666 public void setInt(int parameterIndex, int x) throws SQLException {
3667 setInternal(parameterIndex, String.valueOf(x));
3668
3669 this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.INTEGER;
3670 }
3671
3672 protected final void setInternal(int paramIndex, byte[] val)
3673 throws SQLException {
3674 if (this.isClosed) {
3675 throw SQLError.createSQLException(Messages.getString("PreparedStatement.48"), //$NON-NLS-1$
3676 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
3677 }
3678
3679 int parameterIndexOffset = getParameterIndexOffset();
3680
3681 checkBounds(paramIndex, parameterIndexOffset);
3682
3683 this.isStream[paramIndex - 1 + parameterIndexOffset] = false;
3684 this.isNull[paramIndex - 1 + parameterIndexOffset] = false;
3685 this.parameterStreams[paramIndex - 1 + parameterIndexOffset] = null;
3686 this.parameterValues[paramIndex - 1 + parameterIndexOffset] = val;
3687 }
3688
3689 private void checkBounds(int paramIndex, int parameterIndexOffset)
3690 throws SQLException {
3691 if ((paramIndex < 1)) {
3692 throw SQLError.createSQLException(
3693 Messages.getString("PreparedStatement.49") //$NON-NLS-1$
3694 + paramIndex
3695 + Messages.getString("PreparedStatement.50"), SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor()); //$NON-NLS-1$
3696 } else if (paramIndex > this.parameterCount) {
3697 throw SQLError.createSQLException(
3698 Messages.getString("PreparedStatement.51") //$NON-NLS-1$
3699 + paramIndex
3700 + Messages.getString("PreparedStatement.52") + (this.parameterValues.length) + Messages.getString("PreparedStatement.53"), //$NON-NLS-1$ //$NON-NLS-2$
3701 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
3702 } else if (parameterIndexOffset == -1 && paramIndex == 1) {
3703 throw SQLError.createSQLException("Can't set IN parameter for return value of stored function call.",
3704 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
3705 }
3706 }
3707
3708 protected final void setInternal(int paramIndex, String val)
3709 throws SQLException {
3710 checkClosed();
3711
3712 byte[] parameterAsBytes = null;
3713
3714 if (this.charConverter != null) {
3715 parameterAsBytes = this.charConverter.toBytes(val);
3716 } else {
3717 parameterAsBytes = StringUtils.getBytes(val, this.charConverter,
3718 this.charEncoding, this.connection
3719 .getServerCharacterEncoding(), this.connection
3720 .parserKnowsUnicode(), getExceptionInterceptor());
3721 }
3722
3723 setInternal(paramIndex, parameterAsBytes);
3724 }
3725
3726 /**
3727 * Set a parameter to a Java long value. The driver converts this to a SQL
3728 * BIGINT value when it sends it to the database.
3729 *
3730 * @param parameterIndex
3731 * the first parameter is 1...
3732 * @param x
3733 * the parameter value
3734 *
3735 * @exception SQLException
3736 * if a database access error occurs
3737 */
3738 public void setLong(int parameterIndex, long x) throws SQLException {
3739 setInternal(parameterIndex, String.valueOf(x));
3740
3741 this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.BIGINT;
3742 }
3743
3744 /**
3745 * Set a parameter to SQL NULL
3746 *
3747 * <p>
3748 * <B>Note:</B> You must specify the parameters SQL type (although MySQL
3749 * ignores it)
3750 * </p>
3751 *
3752 * @param parameterIndex
3753 * the first parameter is 1, etc...
3754 * @param sqlType
3755 * the SQL type code defined in java.sql.Types
3756 *
3757 * @exception SQLException
3758 * if a database access error occurs
3759 */
3760 public void setNull(int parameterIndex, int sqlType) throws SQLException {
3761 setInternal(parameterIndex, "null"); //$NON-NLS-1$
3762 this.isNull[parameterIndex - 1 + getParameterIndexOffset()] = true;
3763
3764 this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.NULL;
3765 }
3766
3767 /**
3768 * Set a parameter to SQL NULL.
3769 *
3770 * <P>
3771 * <B>Note:</B> You must specify the parameter's SQL type.
3772 * </p>
3773 *
3774 * @param parameterIndex
3775 * the first parameter is 1, the second is 2, ...
3776 * @param sqlType
3777 * SQL type code defined by java.sql.Types
3778 * @param arg
3779 * argument parameters for null
3780 *
3781 * @exception SQLException
3782 * if a database-access error occurs.
3783 */
3784 public void setNull(int parameterIndex, int sqlType, String arg)
3785 throws SQLException {
3786 setNull(parameterIndex, sqlType);
3787
3788 this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.NULL;
3789 }
3790
3791 private void setNumericObject(int parameterIndex, Object parameterObj, int targetSqlType, int scale) throws SQLException {
3792 Number parameterAsNum;
3793
3794 if (parameterObj instanceof Boolean) {
3795 parameterAsNum = ((Boolean) parameterObj)
3796 .booleanValue() ? Constants.integerValueOf(1) : Constants.integerValueOf(
3797 0);
3798 } else if (parameterObj instanceof String) {
3799 switch (targetSqlType) {
3800 case Types.BIT:
3801 if ("1".equals((String) parameterObj)
3802 || "0".equals((String) parameterObj)) {
3803 parameterAsNum = Integer.valueOf((String) parameterObj);
3804 } else {
3805 boolean parameterAsBoolean = "true"
3806 .equalsIgnoreCase((String) parameterObj);
3807
3808 parameterAsNum = parameterAsBoolean ? Constants.integerValueOf(1)
3809 : Constants.integerValueOf(0);
3810 }
3811
3812 break;
3813
3814 case Types.TINYINT:
3815 case Types.SMALLINT:
3816 case Types.INTEGER:
3817 parameterAsNum = Integer
3818 .valueOf((String) parameterObj);
3819
3820 break;
3821
3822 case Types.BIGINT:
3823 parameterAsNum = Long
3824 .valueOf((String) parameterObj);
3825
3826 break;
3827
3828 case Types.REAL:
3829 parameterAsNum = Float
3830 .valueOf((String) parameterObj);
3831
3832 break;
3833
3834 case Types.FLOAT:
3835 case Types.DOUBLE:
3836 parameterAsNum = Double
3837 .valueOf((String) parameterObj);
3838
3839 break;
3840
3841 case Types.DECIMAL:
3842 case Types.NUMERIC:
3843 default:
3844 parameterAsNum = new java.math.BigDecimal(
3845 (String) parameterObj);
3846 }
3847 } else {
3848 parameterAsNum = (Number) parameterObj;
3849 }
3850
3851 switch (targetSqlType) {
3852 case Types.BIT:
3853 case Types.TINYINT:
3854 case Types.SMALLINT:
3855 case Types.INTEGER:
3856 setInt(parameterIndex, parameterAsNum.intValue());
3857
3858 break;
3859
3860 case Types.BIGINT:
3861 setLong(parameterIndex, parameterAsNum.longValue());
3862
3863 break;
3864
3865 case Types.REAL:
3866 setFloat(parameterIndex, parameterAsNum.floatValue());
3867
3868 break;
3869
3870 case Types.FLOAT:
3871 case Types.DOUBLE:
3872 setDouble(parameterIndex, parameterAsNum.doubleValue());
3873
3874 break;
3875
3876 case Types.DECIMAL:
3877 case Types.NUMERIC:
3878
3879 if (parameterAsNum instanceof java.math.BigDecimal) {
3880 BigDecimal scaledBigDecimal = null;
3881
3882 try {
3883 scaledBigDecimal = ((java.math.BigDecimal) parameterAsNum)
3884 .setScale(scale);
3885 } catch (ArithmeticException ex) {
3886 try {
3887 scaledBigDecimal = ((java.math.BigDecimal) parameterAsNum)
3888 .setScale(scale,
3889 BigDecimal.ROUND_HALF_UP);
3890 } catch (ArithmeticException arEx) {
3891 throw SQLError.createSQLException(
3892 "Can't set scale of '"
3893 + scale
3894 + "' for DECIMAL argument '"
3895 + parameterAsNum + "'",
3896 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
3897 }
3898 }
3899
3900 setBigDecimal(parameterIndex, scaledBigDecimal);
3901 } else if (parameterAsNum instanceof java.math.BigInteger) {
3902 setBigDecimal(
3903 parameterIndex,
3904 new java.math.BigDecimal(
3905 (java.math.BigInteger) parameterAsNum,
3906 scale));
3907 } else {
3908 setBigDecimal(parameterIndex,
3909 new java.math.BigDecimal(parameterAsNum
3910 .doubleValue()));
3911 }
3912
3913 break;
3914 }
3915 }
3916
3917 /**
3918 * DOCUMENT ME!
3919 *
3920 * @param parameterIndex
3921 * DOCUMENT ME!
3922 * @param parameterObj
3923 * DOCUMENT ME!
3924 *
3925 * @throws SQLException
3926 * DOCUMENT ME!
3927 */
3928 public void setObject(int parameterIndex, Object parameterObj)
3929 throws SQLException {
3930 if (parameterObj == null) {
3931 setNull(parameterIndex, java.sql.Types.OTHER);
3932 } else {
3933 if (parameterObj instanceof Byte) {
3934 setInt(parameterIndex, ((Byte) parameterObj).intValue());
3935 } else if (parameterObj instanceof String) {
3936 setString(parameterIndex, (String) parameterObj);
3937 } else if (parameterObj instanceof BigDecimal) {
3938 setBigDecimal(parameterIndex, (BigDecimal) parameterObj);
3939 } else if (parameterObj instanceof Short) {
3940 setShort(parameterIndex, ((Short) parameterObj).shortValue());
3941 } else if (parameterObj instanceof Integer) {
3942 setInt(parameterIndex, ((Integer) parameterObj).intValue());
3943 } else if (parameterObj instanceof Long) {
3944 setLong(parameterIndex, ((Long) parameterObj).longValue());
3945 } else if (parameterObj instanceof Float) {
3946 setFloat(parameterIndex, ((Float) parameterObj).floatValue());
3947 } else if (parameterObj instanceof Double) {
3948 setDouble(parameterIndex, ((Double) parameterObj).doubleValue());
3949 } else if (parameterObj instanceof byte[]) {
3950 setBytes(parameterIndex, (byte[]) parameterObj);
3951 } else if (parameterObj instanceof java.sql.Date) {
3952 setDate(parameterIndex, (java.sql.Date) parameterObj);
3953 } else if (parameterObj instanceof Time) {
3954 setTime(parameterIndex, (Time) parameterObj);
3955 } else if (parameterObj instanceof Timestamp) {
3956 setTimestamp(parameterIndex, (Timestamp) parameterObj);
3957 } else if (parameterObj instanceof Boolean) {
3958 setBoolean(parameterIndex, ((Boolean) parameterObj)
3959 .booleanValue());
3960 } else if (parameterObj instanceof InputStream) {
3961 setBinaryStream(parameterIndex, (InputStream) parameterObj, -1);
3962 } else if (parameterObj instanceof java.sql.Blob) {
3963 setBlob(parameterIndex, (java.sql.Blob) parameterObj);
3964 } else if (parameterObj instanceof java.sql.Clob) {
3965 setClob(parameterIndex, (java.sql.Clob) parameterObj);
3966 } else if (this.connection.getTreatUtilDateAsTimestamp() &&
3967 parameterObj instanceof java.util.Date) {
3968 setTimestamp(parameterIndex, new Timestamp(
3969 ((java.util.Date) parameterObj).getTime()));
3970 } else if (parameterObj instanceof BigInteger) {
3971 setString(parameterIndex, parameterObj.toString());
3972 } else {
3973 setSerializableObject(parameterIndex, parameterObj);
3974 }
3975 }
3976 }
3977
3978
3979 /**
3980 * DOCUMENT ME!
3981 *
3982 * @param parameterIndex
3983 * DOCUMENT ME!
3984 * @param parameterObj
3985 * DOCUMENT ME!
3986 * @param targetSqlType
3987 * DOCUMENT ME!
3988 *
3989 * @throws SQLException
3990 * DOCUMENT ME!
3991 */
3992 public void setObject(int parameterIndex, Object parameterObj,
3993 int targetSqlType) throws SQLException {
3994 if (!(parameterObj instanceof BigDecimal)) {
3995 setObject(parameterIndex, parameterObj, targetSqlType, 0);
3996 } else {
3997 setObject(parameterIndex, parameterObj, targetSqlType,
3998 ((BigDecimal)parameterObj).scale());
3999 }
4000 }
4001
4002 /**
4003 * Set the value of a parameter using an object; use the java.lang
4004 * equivalent objects for integral values.
4005 *
4006 * <P>
4007 * The given Java object will be converted to the targetSqlType before being
4008 * sent to the database.
4009 * </p>
4010 *
4011 * <P>
4012 * note that this method may be used to pass database-specific abstract data
4013 * types. This is done by using a Driver-specific Java type and using a
4014 * targetSqlType of java.sql.Types.OTHER
4015 * </p>
4016 *
4017 * @param parameterIndex
4018 * the first parameter is 1...
4019 * @param parameterObj
4020 * the object containing the input parameter value
4021 * @param targetSqlType
4022 * The SQL type to be send to the database
4023 * @param scale
4024 * For java.sql.Types.DECIMAL or java.sql.Types.NUMERIC types
4025 * this is the number of digits after the decimal. For all other
4026 * types this value will be ignored.
4027 *
4028 * @throws SQLException
4029 * if a database access error occurs
4030 */
4031 public void setObject(int parameterIndex, Object parameterObj,
4032 int targetSqlType, int scale) throws SQLException {
4033 if (parameterObj == null) {
4034 setNull(parameterIndex, java.sql.Types.OTHER);
4035 } else {
4036 try {
4037 switch (targetSqlType) {
4038 case Types.BOOLEAN:
4039 /*
4040 From Table-B5 in the JDBC-3.0 Spec
4041
4042 T S I B R F D D N B B C V L
4043 I M N I E L O E U I O H A O
4044 N A T G A O U C M T O A R N
4045 Y L E I L A B I E L R C G
4046 I L G N T L M R E H V
4047 N I E T E A I A A A
4048 T N R L C N R R
4049 T C
4050 H
4051 A
4052 R
4053 -----------------------------------
4054 Boolean x x x x x x x x x x x x x x
4055 */
4056
4057 if (parameterObj instanceof Boolean) {
4058 setBoolean(parameterIndex, ((Boolean)parameterObj).booleanValue());
4059
4060 break;
4061 } else if (parameterObj instanceof String) {
4062 setBoolean(parameterIndex, "true".equalsIgnoreCase((String)parameterObj) ||
4063 !"0".equalsIgnoreCase((String)parameterObj));
4064
4065 break;
4066 } else if (parameterObj instanceof Number) {
4067 int intValue = ((Number)parameterObj).intValue();
4068
4069 setBoolean(parameterIndex, intValue != 0);
4070
4071 break;
4072 } else {
4073 throw SQLError.createSQLException("No conversion from " + parameterObj.getClass().getName() +
4074 " to Types.BOOLEAN possible.", SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
4075 }
4076
4077
4078 case Types.BIT:
4079 case Types.TINYINT:
4080 case Types.SMALLINT:
4081 case Types.INTEGER:
4082 case Types.BIGINT:
4083 case Types.REAL:
4084 case Types.FLOAT:
4085 case Types.DOUBLE:
4086 case Types.DECIMAL:
4087 case Types.NUMERIC:
4088
4089 setNumericObject(parameterIndex, parameterObj, targetSqlType, scale);
4090
4091 break;
4092
4093 case Types.CHAR:
4094 case Types.VARCHAR:
4095 case Types.LONGVARCHAR:
4096 if (parameterObj instanceof BigDecimal) {
4097 setString(
4098 parameterIndex,
4099 (StringUtils
4100 .fixDecimalExponent(StringUtils
4101 .consistentToString((BigDecimal) parameterObj))));
4102 } else {
4103 setString(parameterIndex, parameterObj.toString());
4104 }
4105
4106 break;
4107
4108 case Types.CLOB:
4109
4110 if (parameterObj instanceof java.sql.Clob) {
4111 setClob(parameterIndex, (java.sql.Clob) parameterObj);
4112 } else {
4113 setString(parameterIndex, parameterObj.toString());
4114 }
4115
4116 break;
4117
4118 case Types.BINARY:
4119 case Types.VARBINARY:
4120 case Types.LONGVARBINARY:
4121 case Types.BLOB:
4122
4123 if (parameterObj instanceof byte[]) {
4124 setBytes(parameterIndex, (byte[]) parameterObj);
4125 } else if (parameterObj instanceof java.sql.Blob) {
4126 setBlob(parameterIndex, (java.sql.Blob) parameterObj);
4127 } else {
4128 setBytes(parameterIndex, StringUtils.getBytes(
4129 parameterObj.toString(), this.charConverter,
4130 this.charEncoding, this.connection
4131 .getServerCharacterEncoding(),
4132 this.connection.parserKnowsUnicode(), getExceptionInterceptor()));
4133 }
4134
4135 break;
4136
4137 case Types.DATE:
4138 case Types.TIMESTAMP:
4139
4140 java.util.Date parameterAsDate;
4141
4142 if (parameterObj instanceof String) {
4143 ParsePosition pp = new ParsePosition(0);
4144 java.text.DateFormat sdf = new java.text.SimpleDateFormat(
4145 getDateTimePattern((String) parameterObj, false), Locale.US);
4146 parameterAsDate = sdf.parse((String) parameterObj, pp);
4147 } else {
4148 parameterAsDate = (java.util.Date) parameterObj;
4149 }
4150
4151 switch (targetSqlType) {
4152 case Types.DATE:
4153
4154 if (parameterAsDate instanceof java.sql.Date) {
4155 setDate(parameterIndex,
4156 (java.sql.Date) parameterAsDate);
4157 } else {
4158 setDate(parameterIndex, new java.sql.Date(
4159 parameterAsDate.getTime()));
4160 }
4161
4162 break;
4163
4164 case Types.TIMESTAMP:
4165
4166 if (parameterAsDate instanceof java.sql.Timestamp) {
4167 setTimestamp(parameterIndex,
4168 (java.sql.Timestamp) parameterAsDate);
4169 } else {
4170 setTimestamp(parameterIndex,
4171 new java.sql.Timestamp(parameterAsDate
4172 .getTime()));
4173 }
4174
4175 break;
4176 }
4177
4178 break;
4179
4180 case Types.TIME:
4181
4182 if (parameterObj instanceof String) {
4183 java.text.DateFormat sdf = new java.text.SimpleDateFormat(
4184 getDateTimePattern((String) parameterObj, true), Locale.US);
4185 setTime(parameterIndex, new java.sql.Time(sdf.parse(
4186 (String) parameterObj).getTime()));
4187 } else if (parameterObj instanceof Timestamp) {
4188 Timestamp xT = (Timestamp) parameterObj;
4189 setTime(parameterIndex, new java.sql.Time(xT.getTime()));
4190 } else {
4191 setTime(parameterIndex, (java.sql.Time) parameterObj);
4192 }
4193
4194 break;
4195
4196 case Types.OTHER:
4197 setSerializableObject(parameterIndex, parameterObj);
4198
4199 break;
4200
4201 default:
4202 throw SQLError.createSQLException(Messages
4203 .getString("PreparedStatement.16"), //$NON-NLS-1$
4204 SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
4205 }
4206 } catch (Exception ex) {
4207 if (ex instanceof SQLException) {
4208 throw (SQLException) ex;
4209 }
4210
4211 SQLException sqlEx = SQLError.createSQLException(
4212 Messages.getString("PreparedStatement.17") //$NON-NLS-1$
4213 + parameterObj.getClass().toString()
4214 + Messages.getString("PreparedStatement.18") //$NON-NLS-1$
4215 + ex.getClass().getName()
4216 + Messages.getString("PreparedStatement.19") + ex.getMessage(), //$NON-NLS-1$
4217 SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
4218
4219 sqlEx.initCause(ex);
4220
4221 throw sqlEx;
4222 }
4223 }
4224 }
4225
4226 protected int setOneBatchedParameterSet(
4227 java.sql.PreparedStatement batchedStatement, int batchedParamIndex,
4228 Object paramSet) throws SQLException {
4229 BatchParams paramArg = (BatchParams)paramSet;
4230
4231 boolean[] isNullBatch = paramArg.isNull;
4232 boolean[] isStreamBatch = paramArg.isStream;
4233
4234 for (int j = 0; j < isNullBatch.length; j++) {
4235 if (isNullBatch[j]) {
4236 batchedStatement.setNull(batchedParamIndex++, Types.NULL);
4237 } else {
4238 if (isStreamBatch[j]) {
4239 batchedStatement.setBinaryStream(batchedParamIndex++,
4240 paramArg.parameterStreams[j],
4241 paramArg.streamLengths[j]);
4242 } else {
4243 ((com.mysql.jdbc.PreparedStatement) batchedStatement)
4244 .setBytesNoEscapeNoQuotes(batchedParamIndex++,
4245 paramArg.parameterStrings[j]);
4246 }
4247 }
4248 }
4249
4250 return batchedParamIndex;
4251 }
4252
4253 /**
4254 * JDBC 2.0 Set a REF(<structured-type>) parameter.
4255 *
4256 * @param i
4257 * the first parameter is 1, the second is 2, ...
4258 * @param x
4259 * an object representing data of an SQL REF Type
4260 *
4261 * @throws SQLException
4262 * if a database error occurs
4263 * @throws NotImplemented
4264 * DOCUMENT ME!
4265 */
4266 public void setRef(int i, Ref x) throws SQLException {
4267 throw SQLError.notImplemented();
4268 }
4269
4270 /**
4271 * Sets the concurrency for result sets generated by this statement
4272 *
4273 * @param concurrencyFlag
4274 * DOCUMENT ME!
4275 */
4276 void setResultSetConcurrency(int concurrencyFlag) {
4277 this.resultSetConcurrency = concurrencyFlag;
4278 }
4279
4280 /**
4281 * Sets the result set type for result sets generated by this statement
4282 *
4283 * @param typeFlag
4284 * DOCUMENT ME!
4285 */
4286 void setResultSetType(int typeFlag) {
4287 this.resultSetType = typeFlag;
4288 }
4289
4290 /**
4291 * DOCUMENT ME!
4292 *
4293 * @param retrieveGeneratedKeys
4294 */
4295 protected void setRetrieveGeneratedKeys(boolean retrieveGeneratedKeys) {
4296 this.retrieveGeneratedKeys = retrieveGeneratedKeys;
4297 }
4298
4299
4300
4301 /**
4302 * Sets the value for the placeholder as a serialized Java object (used by
4303 * various forms of setObject()
4304 *
4305 * @param parameterIndex
4306 * DOCUMENT ME!
4307 * @param parameterObj
4308 * DOCUMENT ME!
4309 *
4310 * @throws SQLException
4311 * DOCUMENT ME!
4312 */
4313 private final void setSerializableObject(int parameterIndex,
4314 Object parameterObj) throws SQLException {
4315 try {
4316 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
4317 ObjectOutputStream objectOut = new ObjectOutputStream(bytesOut);
4318 objectOut.writeObject(parameterObj);
4319 objectOut.flush();
4320 objectOut.close();
4321 bytesOut.flush();
4322 bytesOut.close();
4323
4324 byte[] buf = bytesOut.toByteArray();
4325 ByteArrayInputStream bytesIn = new ByteArrayInputStream(buf);
4326 setBinaryStream(parameterIndex, bytesIn, buf.length);
4327 this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.BINARY;
4328 } catch (Exception ex) {
4329 SQLException sqlEx = SQLError.createSQLException(Messages.getString("PreparedStatement.54") //$NON-NLS-1$
4330 + ex.getClass().getName(),
4331 SQLError.SQL_STATE_ILLEGAL_ARGUMENT, getExceptionInterceptor());
4332 sqlEx.initCause(ex);
4333
4334 throw sqlEx;
4335 }
4336 }
4337
4338 /**
4339 * Set a parameter to a Java short value. The driver converts this to a SQL
4340 * SMALLINT value when it sends it to the database.
4341 *
4342 * @param parameterIndex
4343 * the first parameter is 1...
4344 * @param x
4345 * the parameter value
4346 *
4347 * @exception SQLException
4348 * if a database access error occurs
4349 */
4350 public void setShort(int parameterIndex, short x) throws SQLException {
4351 setInternal(parameterIndex, String.valueOf(x));
4352
4353 this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.SMALLINT;
4354 }
4355
4356 /**
4357 * Set a parameter to a Java String value. The driver converts this to a SQL
4358 * VARCHAR or LONGVARCHAR value (depending on the arguments size relative to
4359 * the driver's limits on VARCHARs) when it sends it to the database.
4360 *
4361 * @param parameterIndex
4362 * the first parameter is 1...
4363 * @param x
4364 * the parameter value
4365 *
4366 * @exception SQLException
4367 * if a database access error occurs
4368 */
4369 public void setString(int parameterIndex, String x) throws SQLException {
4370 // if the passed string is null, then set this column to null
4371 if (x == null) {
4372 setNull(parameterIndex, Types.CHAR);
4373 } else {
4374 checkClosed();
4375
4376 int stringLength = x.length();
4377
4378 if (this.connection.isNoBackslashEscapesSet()) {
4379 // Scan for any nasty chars
4380
4381 boolean needsHexEscape = isEscapeNeededForString(x,
4382 stringLength);
4383
4384 if (!needsHexEscape) {
4385 byte[] parameterAsBytes = null;
4386
4387 StringBuffer quotedString = new StringBuffer(x.length() + 2);
4388 quotedString.append('\'');
4389 quotedString.append(x);
4390 quotedString.append('\'');
4391
4392 if (!this.isLoadDataQuery) {
4393 parameterAsBytes = StringUtils.getBytes(quotedString.toString(),
4394 this.charConverter, this.charEncoding,
4395 this.connection.getServerCharacterEncoding(),
4396 this.connection.parserKnowsUnicode(), getExceptionInterceptor());
4397 } else {
4398 // Send with platform character encoding
4399 parameterAsBytes = quotedString.toString().getBytes();
4400 }
4401
4402 setInternal(parameterIndex, parameterAsBytes);
4403 } else {
4404 byte[] parameterAsBytes = null;
4405
4406 if (!this.isLoadDataQuery) {
4407 parameterAsBytes = StringUtils.getBytes(x,
4408 this.charConverter, this.charEncoding,
4409 this.connection.getServerCharacterEncoding(),
4410 this.connection.parserKnowsUnicode(), getExceptionInterceptor());
4411 } else {
4412 // Send with platform character encoding
4413 parameterAsBytes = x.getBytes();
4414 }
4415
4416 setBytes(parameterIndex, parameterAsBytes);
4417 }
4418
4419 return;
4420 }
4421
4422 String parameterAsString = x;
4423 boolean needsQuoted = true;
4424
4425 if (this.isLoadDataQuery || isEscapeNeededForString(x, stringLength)) {
4426 needsQuoted = false; // saves an allocation later
4427
4428 StringBuffer buf = new StringBuffer((int) (x.length() * 1.1));
4429
4430 buf.append('\'');
4431
4432 //
4433 // Note: buf.append(char) is _faster_ than
4434 // appending in blocks, because the block
4435 // append requires a System.arraycopy()....
4436 // go figure...
4437 //
4438
4439 for (int i = 0; i < stringLength; ++i) {
4440 char c = x.charAt(i);
4441
4442 switch (c) {
4443 case 0: /* Must be escaped for 'mysql' */
4444 buf.append('\\');
4445 buf.append('0');
4446
4447 break;
4448
4449 case '\n': /* Must be escaped for logs */
4450 buf.append('\\');
4451 buf.append('n');
4452
4453 break;
4454
4455 case '\r':
4456 buf.append('\\');
4457 buf.append('r');
4458
4459 break;
4460
4461 case '\\':
4462 buf.append('\\');
4463 buf.append('\\');
4464
4465 break;
4466
4467 case '\'':
4468 buf.append('\\');
4469 buf.append('\'');
4470
4471 break;
4472
4473 case '"': /* Better safe than sorry */
4474 if (this.usingAnsiMode) {
4475 buf.append('\\');
4476 }
4477
4478 buf.append('"');
4479
4480 break;
4481
4482 case '\032': /* This gives problems on Win32 */
4483 buf.append('\\');
4484 buf.append('Z');
4485
4486 break;
4487
4488 case '\u00a5':
4489 case '\u20a9':
4490 // escape characters interpreted as backslash by mysql
4491 if(charsetEncoder != null) {
4492 CharBuffer cbuf = CharBuffer.allocate(1);
4493 ByteBuffer bbuf = ByteBuffer.allocate(1);
4494 cbuf.put(c);
4495 cbuf.position(0);
4496 charsetEncoder.encode(cbuf, bbuf, true);
4497 if(bbuf.get(0) == '\\') {
4498 buf.append('\\');
4499 }
4500 }
4501 // fall through
4502
4503 default:
4504 buf.append(c);
4505 }
4506 }
4507
4508 buf.append('\'');
4509
4510 parameterAsString = buf.toString();
4511 }
4512
4513 byte[] parameterAsBytes = null;
4514
4515 if (!this.isLoadDataQuery) {
4516 if (needsQuoted) {
4517 parameterAsBytes = StringUtils.getBytesWrapped(parameterAsString,
4518 '\'', '\'', this.charConverter, this.charEncoding, this.connection
4519 .getServerCharacterEncoding(), this.connection
4520 .parserKnowsUnicode(), getExceptionInterceptor());
4521 } else {
4522 parameterAsBytes = StringUtils.getBytes(parameterAsString,
4523 this.charConverter, this.charEncoding, this.connection
4524 .getServerCharacterEncoding(), this.connection
4525 .parserKnowsUnicode(), getExceptionInterceptor());
4526 }
4527 } else {
4528 // Send with platform character encoding
4529 parameterAsBytes = parameterAsString.getBytes();
4530 }
4531
4532 setInternal(parameterIndex, parameterAsBytes);
4533
4534 this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.VARCHAR;
4535 }
4536 }
4537
4538 private boolean isEscapeNeededForString(String x, int stringLength) {
4539 boolean needsHexEscape = false;
4540
4541 for (int i = 0; i < stringLength; ++i) {
4542 char c = x.charAt(i);
4543
4544 switch (c) {
4545 case 0: /* Must be escaped for 'mysql' */
4546
4547 needsHexEscape = true;
4548 break;
4549
4550 case '\n': /* Must be escaped for logs */
4551 needsHexEscape = true;
4552
4553 break;
4554
4555 case '\r':
4556 needsHexEscape = true;
4557 break;
4558
4559 case '\\':
4560 needsHexEscape = true;
4561
4562 break;
4563
4564 case '\'':
4565 needsHexEscape = true;
4566
4567 break;
4568
4569 case '"': /* Better safe than sorry */
4570 needsHexEscape = true;
4571
4572 break;
4573
4574 case '\032': /* This gives problems on Win32 */
4575 needsHexEscape = true;
4576 break;
4577 }
4578
4579 if (needsHexEscape) {
4580 break; // no need to scan more
4581 }
4582 }
4583 return needsHexEscape;
4584 }
4585
4586 /**
4587 * Set a parameter to a java.sql.Time value. The driver converts this to a
4588 * SQL TIME value when it sends it to the database.
4589 *
4590 * @param parameterIndex
4591 * the first parameter is 1, the second is 2, ...
4592 * @param x
4593 * the parameter value
4594 * @param cal
4595 * the cal specifying the timezone
4596 *
4597 * @throws SQLException
4598 * if a database-access error occurs.
4599 */
4600 public void setTime(int parameterIndex, java.sql.Time x, Calendar cal)
4601 throws SQLException {
4602 setTimeInternal(parameterIndex, x, cal, cal.getTimeZone(), true);
4603 }
4604
4605 /**
4606 * Set a parameter to a java.sql.Time value. The driver converts this to a
4607 * SQL TIME value when it sends it to the database.
4608 *
4609 * @param parameterIndex
4610 * the first parameter is 1...));
4611 * @param x
4612 * the parameter value
4613 *
4614 * @throws java.sql.SQLException
4615 * if a database access error occurs
4616 */
4617 public void setTime(int parameterIndex, Time x)
4618 throws java.sql.SQLException {
4619 setTimeInternal(parameterIndex, x, null, Util.getDefaultTimeZone(), false);
4620 }
4621
4622 /**
4623 * Set a parameter to a java.sql.Time value. The driver converts this to a
4624 * SQL TIME value when it sends it to the database, using the given
4625 * timezone.
4626 *
4627 * @param parameterIndex
4628 * the first parameter is 1...));
4629 * @param x
4630 * the parameter value
4631 * @param tz
4632 * the timezone to use
4633 *
4634 * @throws java.sql.SQLException
4635 * if a database access error occurs
4636 */
4637 private void setTimeInternal(int parameterIndex, Time x, Calendar targetCalendar,
4638 TimeZone tz,
4639 boolean rollForward) throws java.sql.SQLException {
4640 if (x == null) {
4641 setNull(parameterIndex, java.sql.Types.TIME);
4642 } else {
4643 checkClosed();
4644
4645 if (!this.useLegacyDatetimeCode) {
4646 newSetTimeInternal(parameterIndex, x, targetCalendar);
4647 } else {
4648 Calendar sessionCalendar = getCalendarInstanceForSessionOrNew();
4649
4650 synchronized (sessionCalendar) {
4651 x = TimeUtil.changeTimezone(this.connection,
4652 sessionCalendar,
4653 targetCalendar,
4654 x, tz, this.connection
4655 .getServerTimezoneTZ(), rollForward);
4656 }
4657
4658 setInternal(parameterIndex, "'" + x.toString() + "'"); //$NON-NLS-1$ //$NON-NLS-2$
4659 }
4660
4661 this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.TIME;
4662 }
4663 }
4664
4665 /**
4666 * Set a parameter to a java.sql.Timestamp value. The driver converts this
4667 * to a SQL TIMESTAMP value when it sends it to the database.
4668 *
4669 * @param parameterIndex
4670 * the first parameter is 1, the second is 2, ...
4671 * @param x
4672 * the parameter value
4673 * @param cal
4674 * the calendar specifying the timezone to use
4675 *
4676 * @throws SQLException
4677 * if a database-access error occurs.
4678 */
4679 public void setTimestamp(int parameterIndex, java.sql.Timestamp x,
4680 Calendar cal) throws SQLException {
4681 setTimestampInternal(parameterIndex, x, cal, cal.getTimeZone(), true);
4682 }
4683
4684 /**
4685 * Set a parameter to a java.sql.Timestamp value. The driver converts this
4686 * to a SQL TIMESTAMP value when it sends it to the database.
4687 *
4688 * @param parameterIndex
4689 * the first parameter is 1...
4690 * @param x
4691 * the parameter value
4692 *
4693 * @throws java.sql.SQLException
4694 * if a database access error occurs
4695 */
4696 public void setTimestamp(int parameterIndex, Timestamp x)
4697 throws java.sql.SQLException {
4698 setTimestampInternal(parameterIndex, x, null, Util.getDefaultTimeZone(), false);
4699 }
4700
4701 /**
4702 * Set a parameter to a java.sql.Timestamp value. The driver converts this
4703 * to a SQL TIMESTAMP value when it sends it to the database.
4704 *
4705 * @param parameterIndex
4706 * the first parameter is 1, the second is 2, ...
4707 * @param x
4708 * the parameter value
4709 * @param tz
4710 * the timezone to use
4711 *
4712 * @throws SQLException
4713 * if a database-access error occurs.
4714 */
4715 private void setTimestampInternal(int parameterIndex,
4716 Timestamp x, Calendar targetCalendar,
4717 TimeZone tz, boolean rollForward) throws SQLException {
4718 if (x == null) {
4719 setNull(parameterIndex, java.sql.Types.TIMESTAMP);
4720 } else {
4721 checkClosed();
4722
4723 if (!this.useLegacyDatetimeCode) {
4724 newSetTimestampInternal(parameterIndex, x, targetCalendar);
4725 } else {
4726 String timestampString = null;
4727
4728 Calendar sessionCalendar = this.connection.getUseJDBCCompliantTimezoneShift() ?
4729 this.connection.getUtcCalendar() :
4730 getCalendarInstanceForSessionOrNew();
4731
4732 synchronized (sessionCalendar) {
4733 x = TimeUtil.changeTimezone(this.connection,
4734 sessionCalendar,
4735 targetCalendar,
4736 x, tz, this.connection
4737 .getServerTimezoneTZ(), rollForward);
4738 }
4739
4740 if (this.connection.getUseSSPSCompatibleTimezoneShift()) {
4741 doSSPSCompatibleTimezoneShift(parameterIndex, x, sessionCalendar);
4742 } else {
4743 synchronized (this) {
4744 if (this.tsdf == null) {
4745 this.tsdf = new SimpleDateFormat("''yyyy-MM-dd HH:mm:ss", Locale.US); //$NON-NLS-1$
4746 }
4747
4748 timestampString = this.tsdf.format(x);
4749 StringBuffer buf = new StringBuffer();
4750 buf.append(timestampString);
4751 buf.append('.');
4752 buf.append(formatNanos(x.getNanos()));
4753 buf.append('\'');
4754
4755 setInternal(parameterIndex, buf.toString()); // SimpleDateFormat is not
4756 // thread-safe
4757 }
4758 }
4759 }
4760
4761 this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.TIMESTAMP;
4762 }
4763 }
4764
4765 private synchronized void newSetTimestampInternal(int parameterIndex,
4766 Timestamp x, Calendar targetCalendar) throws SQLException {
4767 if (this.tsdf == null) {
4768 this.tsdf = new SimpleDateFormat("''yyyy-MM-dd HH:mm:ss", Locale.US); //$NON-NLS-1$
4769 }
4770
4771 String timestampString = null;
4772
4773 if (targetCalendar != null) {
4774 targetCalendar.setTime(x);
4775 this.tsdf.setTimeZone(targetCalendar.getTimeZone());
4776
4777 timestampString = this.tsdf.format(x);
4778 } else {
4779 this.tsdf.setTimeZone(this.connection.getServerTimezoneTZ());
4780 timestampString = this.tsdf.format(x);
4781 }
4782
4783 StringBuffer buf = new StringBuffer();
4784 buf.append(timestampString);
4785 buf.append('.');
4786 buf.append(formatNanos(x.getNanos()));
4787 buf.append('\'');
4788
4789 setInternal(parameterIndex, buf.toString());
4790 }
4791
4792 private String formatNanos(int nanos) {
4793 if (true /* for now */ || nanos == 0) {
4794 return "0";
4795 }
4796
4797 boolean usingMicros = true;
4798
4799 if (usingMicros) {
4800 nanos /= 1000;
4801 }
4802
4803 final int digitCount = usingMicros ? 6 : 9;
4804
4805 String nanosString = Integer.toString(nanos);
4806 final String zeroPadding = usingMicros ? "000000" : "000000000";
4807
4808 nanosString = zeroPadding.substring(0, (digitCount-nanosString.length())) +
4809 nanosString;
4810
4811 int pos = digitCount; // the end, we're padded to the end by the code above
4812
4813 while (nanosString.charAt(pos) == '0') {
4814 pos--;
4815 }
4816
4817 nanosString = nanosString.substring(0, pos + 1);
4818
4819 return nanosString;
4820 }
4821
4822 private synchronized void newSetTimeInternal(int parameterIndex,
4823 Time x, Calendar targetCalendar) throws SQLException {
4824 if (this.tdf == null) {
4825 this.tdf = new SimpleDateFormat("''HH:mm:ss''", Locale.US); //$NON-NLS-1$
4826
4827 }
4828
4829 String timeString = null;
4830
4831 if (targetCalendar != null) {
4832 targetCalendar.setTime(x);
4833 this.tdf.setTimeZone(targetCalendar.getTimeZone());
4834
4835 timeString = this.tdf.format(x);
4836 } else {
4837 this.tdf.setTimeZone(this.connection.getServerTimezoneTZ());
4838 timeString = this.tdf.format(x);
4839 }
4840
4841 setInternal(parameterIndex, timeString);
4842 }
4843
4844 private synchronized void newSetDateInternal(int parameterIndex,
4845 Date x, Calendar targetCalendar) throws SQLException {
4846 if (this.ddf == null) {
4847 this.ddf = new SimpleDateFormat("''yyyy-MM-dd''", Locale.US); //$NON-NLS-1$
4848
4849 }
4850
4851 String timeString = null;
4852
4853 if (targetCalendar != null) {
4854 targetCalendar.setTime(x);
4855 this.ddf.setTimeZone(targetCalendar.getTimeZone());
4856
4857 timeString = this.ddf.format(x);
4858 } else {
4859 this.ddf.setTimeZone(this.connection.getServerTimezoneTZ());
4860 timeString = this.ddf.format(x);
4861 }
4862
4863 setInternal(parameterIndex, timeString);
4864 }
4865
4866 private void doSSPSCompatibleTimezoneShift(int parameterIndex, Timestamp x, Calendar sessionCalendar) throws SQLException {
4867 Calendar sessionCalendar2 = (this.connection
4868 .getUseJDBCCompliantTimezoneShift()) ? this.connection
4869 .getUtcCalendar()
4870 : getCalendarInstanceForSessionOrNew();
4871
4872 synchronized (sessionCalendar2) {
4873 java.util.Date oldTime = sessionCalendar2.getTime();
4874
4875 try {
4876 sessionCalendar2.setTime(x);
4877
4878 int year = sessionCalendar2.get(Calendar.YEAR);
4879 int month = sessionCalendar2.get(Calendar.MONTH) + 1;
4880 int date = sessionCalendar2.get(Calendar.DAY_OF_MONTH);
4881
4882 int hour = sessionCalendar2.get(Calendar.HOUR_OF_DAY);
4883 int minute = sessionCalendar2.get(Calendar.MINUTE);
4884 int seconds = sessionCalendar2.get(Calendar.SECOND);
4885
4886 StringBuffer tsBuf = new StringBuffer();
4887
4888 tsBuf.append('\'');
4889 tsBuf.append(year);
4890
4891 tsBuf.append("-");
4892
4893 if (month < 10) {
4894 tsBuf.append('0');
4895 }
4896
4897 tsBuf.append(month);
4898
4899 tsBuf.append('-');
4900
4901 if (date < 10) {
4902 tsBuf.append('0');
4903 }
4904
4905 tsBuf.append(date);
4906
4907 tsBuf.append(' ');
4908
4909 if (hour < 10) {
4910 tsBuf.append('0');
4911 }
4912
4913 tsBuf.append(hour);
4914
4915 tsBuf.append(':');
4916
4917 if (minute < 10) {
4918 tsBuf.append('0');
4919 }
4920
4921 tsBuf.append(minute);
4922
4923 tsBuf.append(':');
4924
4925 if (seconds < 10) {
4926 tsBuf.append('0');
4927 }
4928
4929 tsBuf.append(seconds);
4930
4931 tsBuf.append('.');
4932 tsBuf.append(formatNanos(x.getNanos()));
4933 tsBuf.append('\'');
4934
4935 setInternal(parameterIndex, tsBuf.toString());
4936
4937 } finally {
4938 sessionCalendar.setTime(oldTime);
4939 }
4940 }
4941 }
4942
4943 /**
4944 * When a very large Unicode value is input to a LONGVARCHAR parameter, it
4945 * may be more practical to send it via a java.io.InputStream. JDBC will
4946 * read the data from the stream as needed, until it reaches end-of-file.
4947 * The JDBC driver will do any necessary conversion from UNICODE to the
4948 * database char format.
4949 *
4950 * <P>
4951 * <B>Note:</B> This stream object can either be a standard Java stream
4952 * object or your own subclass that implements the standard interface.
4953 * </p>
4954 *
4955 * @param parameterIndex
4956 * the first parameter is 1...
4957 * @param x
4958 * the parameter value
4959 * @param length
4960 * the number of bytes to read from the stream
4961 *
4962 * @throws SQLException
4963 * if a database access error occurs
4964 *
4965 * @deprecated
4966 */
4967 public void setUnicodeStream(int parameterIndex, InputStream x, int length)
4968 throws SQLException {
4969 if (x == null) {
4970 setNull(parameterIndex, java.sql.Types.VARCHAR);
4971 } else {
4972 setBinaryStream(parameterIndex, x, length);
4973
4974 this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.CLOB;
4975 }
4976 }
4977
4978 /**
4979 * @see PreparedStatement#setURL(int, URL)
4980 */
4981 public void setURL(int parameterIndex, URL arg) throws SQLException {
4982 if (arg != null) {
4983 setString(parameterIndex, arg.toString());
4984
4985 this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.DATALINK;
4986 } else {
4987 setNull(parameterIndex, Types.CHAR);
4988 }
4989 }
4990
4991 private final void streamToBytes(Buffer packet, InputStream in,
4992 boolean escape, int streamLength, boolean useLength)
4993 throws SQLException {
4994 try {
4995 String connectionEncoding = this.connection.getEncoding();
4996
4997 boolean hexEscape = false;
4998
4999 if (this.connection.isNoBackslashEscapesSet()
5000 || (this.connection.getUseUnicode()
5001 && connectionEncoding != null
5002 && CharsetMapping.isMultibyteCharset(connectionEncoding)
5003 && !this.connection.parserKnowsUnicode())) {
5004 hexEscape = true;
5005 }
5006
5007 if (streamLength == -1) {
5008 useLength = false;
5009 }
5010
5011 int bc = -1;
5012
5013 if (useLength) {
5014 bc = readblock(in, streamConvertBuf, streamLength);
5015 } else {
5016 bc = readblock(in, streamConvertBuf);
5017 }
5018
5019 int lengthLeftToRead = streamLength - bc;
5020
5021 if (hexEscape) {
5022 packet.writeStringNoNull("x");
5023 } else if (this.connection.getIO().versionMeetsMinimum(4, 1, 0)) {
5024 packet.writeStringNoNull("_binary");
5025 }
5026
5027 if (escape) {
5028 packet.writeByte((byte) '\'');
5029 }
5030
5031 while (bc > 0) {
5032 if (hexEscape) {
5033 hexEscapeBlock(streamConvertBuf, packet, bc);
5034 } else if (escape) {
5035 escapeblockFast(streamConvertBuf, packet, bc);
5036 } else {
5037 packet.writeBytesNoNull(streamConvertBuf, 0, bc);
5038 }
5039
5040 if (useLength) {
5041 bc = readblock(in, streamConvertBuf, lengthLeftToRead);
5042
5043 if (bc > 0) {
5044 lengthLeftToRead -= bc;
5045 }
5046 } else {
5047 bc = readblock(in, streamConvertBuf);
5048 }
5049 }
5050
5051 if (escape) {
5052 packet.writeByte((byte) '\'');
5053 }
5054 } finally {
5055 if (this.connection.getAutoClosePStmtStreams()) {
5056 try {
5057 in.close();
5058 } catch (IOException ioEx) {
5059 ;
5060 }
5061
5062 in = null;
5063 }
5064 }
5065 }
5066
5067 private final byte[] streamToBytes(InputStream in, boolean escape,
5068 int streamLength, boolean useLength) throws SQLException {
5069 try {
5070 if (streamLength == -1) {
5071 useLength = false;
5072 }
5073
5074 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
5075
5076 int bc = -1;
5077
5078 if (useLength) {
5079 bc = readblock(in, this.streamConvertBuf, streamLength);
5080 } else {
5081 bc = readblock(in, this.streamConvertBuf);
5082 }
5083
5084 int lengthLeftToRead = streamLength - bc;
5085
5086 if (escape) {
5087 if (this.connection.versionMeetsMinimum(4, 1, 0)) {
5088 bytesOut.write('_');
5089 bytesOut.write('b');
5090 bytesOut.write('i');
5091 bytesOut.write('n');
5092 bytesOut.write('a');
5093 bytesOut.write('r');
5094 bytesOut.write('y');
5095 }
5096
5097 bytesOut.write('\'');
5098 }
5099
5100 while (bc > 0) {
5101 if (escape) {
5102 escapeblockFast(this.streamConvertBuf, bytesOut, bc);
5103 } else {
5104 bytesOut.write(this.streamConvertBuf, 0, bc);
5105 }
5106
5107 if (useLength) {
5108 bc = readblock(in, this.streamConvertBuf, lengthLeftToRead);
5109
5110 if (bc > 0) {
5111 lengthLeftToRead -= bc;
5112 }
5113 } else {
5114 bc = readblock(in, this.streamConvertBuf);
5115 }
5116 }
5117
5118 if (escape) {
5119 bytesOut.write('\'');
5120 }
5121
5122 return bytesOut.toByteArray();
5123 } finally {
5124 if (this.connection.getAutoClosePStmtStreams()) {
5125 try {
5126 in.close();
5127 } catch (IOException ioEx) {
5128 ;
5129 }
5130
5131 in = null;
5132 }
5133 }
5134 }
5135
5136 /**
5137 * Returns this PreparedStatement represented as a string.
5138 *
5139 * @return this PreparedStatement represented as a string.
5140 */
5141 public String toString() {
5142 StringBuffer buf = new StringBuffer();
5143 buf.append(super.toString());
5144 buf.append(": "); //$NON-NLS-1$
5145
5146 try {
5147 buf.append(asSql());
5148 } catch (SQLException sqlEx) {
5149 buf.append("EXCEPTION: " + sqlEx.toString());
5150 }
5151
5152 return buf.toString();
5153 }
5154
5155
5156
5157 public synchronized boolean isClosed() throws SQLException {
5158 return this.isClosed;
5159 }
5160
5161 /**
5162 * For calling stored functions, this will be -1 as we don't really count
5163 * the first '?' parameter marker, it's only syntax, but JDBC counts it
5164 * as #1, otherwise it will return 0
5165 *
5166 */
5167
5168 protected int getParameterIndexOffset() {
5169 return 0;
5170 }
5171
5172 public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException {
5173 setAsciiStream(parameterIndex, x, -1);
5174 }
5175
5176 public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException {
5177 setAsciiStream(parameterIndex, x, (int)length);
5178 this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = Types.CLOB;
5179 }
5180
5181 public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException {
5182 setBinaryStream(parameterIndex, x, -1);
5183 }
5184
5185 public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException {
5186 setBinaryStream(parameterIndex, x, (int)length);
5187 }
5188
5189 public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException {
5190 setBinaryStream(parameterIndex, inputStream);
5191 }
5192
5193 public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException {
5194 setCharacterStream(parameterIndex, reader, -1);
5195 }
5196
5197 public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException {
5198 setCharacterStream(parameterIndex, reader, (int)length);
5199
5200 }
5201
5202 public void setClob(int parameterIndex, Reader reader) throws SQLException {
5203 setCharacterStream(parameterIndex, reader);
5204
5205 }
5206
5207 public void setClob(int parameterIndex, Reader reader, long length)
5208 throws SQLException {
5209 setCharacterStream(parameterIndex, reader, length);
5210 }
5211
5212 public void setNCharacterStream(int parameterIndex, Reader value) throws SQLException {
5213 setNCharacterStream(parameterIndex, value, -1);
5214 }
5215
5216 /**
5217 * Set a parameter to a Java String value. The driver converts this to a SQL
5218 * VARCHAR or LONGVARCHAR value with introducer _utf8 (depending on the
5219 * arguments size relative to the driver's limits on VARCHARs) when it sends
5220 * it to the database. If charset is set as utf8, this method just call setString.
5221 *
5222 * @param parameterIndex
5223 * the first parameter is 1...
5224 * @param x
5225 * the parameter value
5226 *
5227 * @exception SQLException
5228 * if a database access error occurs
5229 */
5230 public void setNString(int parameterIndex, String x) throws SQLException {
5231 if (this.charEncoding.equalsIgnoreCase("UTF-8")
5232 || this.charEncoding.equalsIgnoreCase("utf8")) {
5233 setString(parameterIndex, x);
5234 return;
5235 }
5236
5237 // if the passed string is null, then set this column to null
5238 if (x == null) {
5239 setNull(parameterIndex, java.sql.Types.CHAR);
5240 } else {
5241 int stringLength = x.length();
5242 // Ignore sql_mode=NO_BACKSLASH_ESCAPES in current implementation.
5243
5244 // Add introducer _utf8 for NATIONAL CHARACTER
5245 StringBuffer buf = new StringBuffer((int) (x.length() * 1.1 + 4));
5246 buf.append("_utf8");
5247 buf.append('\'');
5248
5249 //
5250 // Note: buf.append(char) is _faster_ than
5251 // appending in blocks, because the block
5252 // append requires a System.arraycopy()....
5253 // go figure...
5254 //
5255
5256 for (int i = 0; i < stringLength; ++i) {
5257 char c = x.charAt(i);
5258
5259 switch (c) {
5260 case 0: /* Must be escaped for 'mysql' */
5261 buf.append('\\');
5262 buf.append('0');
5263
5264 break;
5265
5266 case '\n': /* Must be escaped for logs */
5267 buf.append('\\');
5268 buf.append('n');
5269
5270 break;
5271
5272 case '\r':
5273 buf.append('\\');
5274 buf.append('r');
5275
5276 break;
5277
5278 case '\\':
5279 buf.append('\\');
5280 buf.append('\\');
5281
5282 break;
5283
5284 case '\'':
5285 buf.append('\\');
5286 buf.append('\'');
5287
5288 break;
5289
5290 case '"': /* Better safe than sorry */
5291 if (this.usingAnsiMode) {
5292 buf.append('\\');
5293 }
5294
5295 buf.append('"');
5296
5297 break;
5298
5299 case '\032': /* This gives problems on Win32 */
5300 buf.append('\\');
5301 buf.append('Z');
5302
5303 break;
5304
5305 default:
5306 buf.append(c);
5307 }
5308 }
5309
5310 buf.append('\'');
5311
5312 String parameterAsString = buf.toString();
5313
5314 byte[] parameterAsBytes = null;
5315
5316 if (!this.isLoadDataQuery) {
5317 parameterAsBytes = StringUtils.getBytes(parameterAsString,
5318 this.connection.getCharsetConverter("UTF-8"), "UTF-8",
5319 this.connection.getServerCharacterEncoding(),
5320 this.connection.parserKnowsUnicode(), getExceptionInterceptor());
5321 } else {
5322 // Send with platform character encoding
5323 parameterAsBytes = parameterAsString.getBytes();
5324 }
5325
5326 setInternal(parameterIndex, parameterAsBytes);
5327
5328 this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = -9; /* Types.NVARCHAR */
5329 }
5330 }
5331
5332 /**
5333 * JDBC 2.0 When a very large UNICODE value is input to a LONGVARCHAR
5334 * parameter, it may be more practical to send it via a java.io.Reader. JDBC
5335 * will read the data from the stream as needed, until it reaches
5336 * end-of-file. The JDBC driver will do any necessary conversion from
5337 * UNICODE to the database char format.
5338 *
5339 * <P>
5340 * <B>Note:</B> This stream object can either be a standard Java stream
5341 * object or your own subclass that implements the standard interface.
5342 * </p>
5343 *
5344 * @param parameterIndex
5345 * the first parameter is 1, the second is 2, ...
5346 * @param reader
5347 * the java reader which contains the UNICODE data
5348 * @param length
5349 * the number of characters in the stream
5350 *
5351 * @exception SQLException
5352 * if a database-access error occurs.
5353 */
5354 public void setNCharacterStream(int parameterIndex, Reader reader,
5355 long length) throws SQLException {
5356 try {
5357 if (reader == null) {
5358 setNull(parameterIndex, java.sql.Types.LONGVARCHAR);
5359
5360 } else {
5361 char[] c = null;
5362 int len = 0;
5363
5364 boolean useLength = this.connection
5365 .getUseStreamLengthsInPrepStmts();
5366
5367 // Ignore "clobCharacterEncoding" because utf8 should be used this time.
5368
5369 if (useLength && (length != -1)) {
5370 c = new char[(int) length]; // can't take more than Integer.MAX_VALUE
5371
5372 int numCharsRead = readFully(reader, c, (int) length); // blocks
5373 // until
5374 // all
5375 // read
5376 setNString(parameterIndex, new String(c, 0, numCharsRead));
5377
5378 } else {
5379 c = new char[4096];
5380
5381 StringBuffer buf = new StringBuffer();
5382
5383 while ((len = reader.read(c)) != -1) {
5384 buf.append(c, 0, len);
5385 }
5386
5387 setNString(parameterIndex, buf.toString());
5388 }
5389
5390 this.parameterTypes[parameterIndex - 1 + getParameterIndexOffset()] = 2011; /* Types.NCLOB */
5391 }
5392 } catch (java.io.IOException ioEx) {
5393 throw SQLError.createSQLException(ioEx.toString(),
5394 SQLError.SQL_STATE_GENERAL_ERROR, getExceptionInterceptor());
5395 }
5396 }
5397
5398 public void setNClob(int parameterIndex, Reader reader) throws SQLException {
5399 setNCharacterStream(parameterIndex, reader);
5400 }
5401
5402 /**
5403 * JDBC 4.0 Set a NCLOB parameter.
5404 *
5405 * @param parameterIndex
5406 * the first parameter is 1, the second is 2, ...
5407 * @param reader
5408 * the java reader which contains the UNICODE data
5409 * @param length
5410 * the number of characters in the stream
5411 *
5412 * @throws SQLException
5413 * if a database error occurs
5414 */
5415 public void setNClob(int parameterIndex, Reader reader, long length)
5416 throws SQLException {
5417 if (reader == null) {
5418 setNull(parameterIndex, java.sql.Types.LONGVARCHAR);
5419 } else {
5420 setNCharacterStream(parameterIndex, reader, length);
5421 }
5422 }
5423
5424 public ParameterBindings getParameterBindings() throws SQLException {
5425 return new EmulatedPreparedStatementBindings();
5426 }
5427
5428 class EmulatedPreparedStatementBindings implements ParameterBindings {
5429
5430 private ResultSetImpl bindingsAsRs;
5431 private boolean[] parameterIsNull;
5432
5433 public EmulatedPreparedStatementBindings() throws SQLException {
5434 List rows = new ArrayList();
5435 parameterIsNull = new boolean[parameterCount];
5436 System
5437 .arraycopy(isNull, 0, this.parameterIsNull, 0,
5438 parameterCount);
5439 byte[][] rowData = new byte[parameterCount][];
5440 Field[] typeMetadata = new Field[parameterCount];
5441
5442 for (int i = 0; i < parameterCount; i++) {
5443 if (batchCommandIndex == -1)
5444 rowData[i] = getBytesRepresentation(i);
5445 else
5446 rowData[i] = getBytesRepresentationForBatch(i, batchCommandIndex);
5447
5448 int charsetIndex = 0;
5449
5450 if (parameterTypes[i] == Types.BINARY
5451 || parameterTypes[i] == Types.BLOB) {
5452 charsetIndex = 63;
5453 } else {
5454 String mysqlEncodingName = CharsetMapping
5455 .getMysqlEncodingForJavaEncoding(connection
5456 .getEncoding(), connection);
5457 charsetIndex = CharsetMapping
5458 .getCharsetIndexForMysqlEncodingName(mysqlEncodingName);
5459 }
5460
5461 Field parameterMetadata = new Field(null, "parameter_"
5462 + (i + 1), charsetIndex, parameterTypes[i],
5463 rowData[i].length);
5464 parameterMetadata.setConnection(connection);
5465 typeMetadata[i] = parameterMetadata;
5466 }
5467
5468 rows.add(new ByteArrayRow(rowData, getExceptionInterceptor()));
5469
5470 this.bindingsAsRs = new ResultSetImpl(connection.getCatalog(),
5471 typeMetadata, new RowDataStatic(rows), connection, null);
5472 this.bindingsAsRs.next();
5473 }
5474
5475 public Array getArray(int parameterIndex) throws SQLException {
5476 return this.bindingsAsRs.getArray(parameterIndex);
5477 }
5478
5479 public InputStream getAsciiStream(int parameterIndex)
5480 throws SQLException {
5481 return this.bindingsAsRs.getAsciiStream(parameterIndex);
5482 }
5483
5484 public BigDecimal getBigDecimal(int parameterIndex) throws SQLException {
5485 return this.bindingsAsRs.getBigDecimal(parameterIndex);
5486 }
5487
5488 public InputStream getBinaryStream(int parameterIndex)
5489 throws SQLException {
5490 return this.bindingsAsRs.getBinaryStream(parameterIndex);
5491 }
5492
5493 public java.sql.Blob getBlob(int parameterIndex) throws SQLException {
5494 return this.bindingsAsRs.getBlob(parameterIndex);
5495 }
5496
5497 public boolean getBoolean(int parameterIndex) throws SQLException {
5498 return this.bindingsAsRs.getBoolean(parameterIndex);
5499 }
5500
5501 public byte getByte(int parameterIndex) throws SQLException {
5502 return this.bindingsAsRs.getByte(parameterIndex);
5503 }
5504
5505 public byte[] getBytes(int parameterIndex) throws SQLException {
5506 return this.bindingsAsRs.getBytes(parameterIndex);
5507 }
5508
5509 public Reader getCharacterStream(int parameterIndex)
5510 throws SQLException {
5511 return this.bindingsAsRs.getCharacterStream(parameterIndex);
5512 }
5513
5514 public java.sql.Clob getClob(int parameterIndex) throws SQLException {
5515 return this.bindingsAsRs.getClob(parameterIndex);
5516 }
5517
5518 public Date getDate(int parameterIndex) throws SQLException {
5519 return this.bindingsAsRs.getDate(parameterIndex);
5520 }
5521
5522 public double getDouble(int parameterIndex) throws SQLException {
5523 return this.bindingsAsRs.getDouble(parameterIndex);
5524 }
5525
5526 public float getFloat(int parameterIndex) throws SQLException {
5527 return this.bindingsAsRs.getFloat(parameterIndex);
5528 }
5529
5530 public int getInt(int parameterIndex) throws SQLException {
5531 return this.bindingsAsRs.getInt(parameterIndex);
5532 }
5533
5534 public long getLong(int parameterIndex) throws SQLException {
5535 return this.bindingsAsRs.getLong(parameterIndex);
5536 }
5537
5538 public Reader getNCharacterStream(int parameterIndex)
5539 throws SQLException {
5540 return this.bindingsAsRs.getCharacterStream(parameterIndex);
5541 }
5542
5543 public Reader getNClob(int parameterIndex) throws SQLException {
5544 return this.bindingsAsRs.getCharacterStream(parameterIndex);
5545 }
5546
5547 public Object getObject(int parameterIndex) throws SQLException {
5548 checkBounds(parameterIndex, 0);
5549
5550 if (parameterIsNull[parameterIndex - 1]) {
5551 return null;
5552 }
5553
5554 // we can't rely on the default mapping for JDBC's
5555 // ResultSet.getObject() for numerics, they're not one-to-one with
5556 // PreparedStatement.setObject
5557
5558 switch (parameterTypes[parameterIndex - 1]) {
5559 case Types.TINYINT:
5560 return new Byte(getByte(parameterIndex));
5561 case Types.SMALLINT:
5562 return new Short(getShort(parameterIndex));
5563 case Types.INTEGER:
5564 return new Integer(getInt(parameterIndex));
5565 case Types.BIGINT:
5566 return new Long(getLong(parameterIndex));
5567 case Types.FLOAT:
5568 return new Float(getFloat(parameterIndex));
5569 case Types.DOUBLE:
5570 return new Double(getDouble(parameterIndex));
5571 default:
5572 return this.bindingsAsRs.getObject(parameterIndex);
5573 }
5574 }
5575
5576 public Ref getRef(int parameterIndex) throws SQLException {
5577 return this.bindingsAsRs.getRef(parameterIndex);
5578 }
5579
5580 public short getShort(int parameterIndex) throws SQLException {
5581 return this.bindingsAsRs.getShort(parameterIndex);
5582 }
5583
5584 public String getString(int parameterIndex) throws SQLException {
5585 return this.bindingsAsRs.getString(parameterIndex);
5586 }
5587
5588 public Time getTime(int parameterIndex) throws SQLException {
5589 return this.bindingsAsRs.getTime(parameterIndex);
5590 }
5591
5592 public Timestamp getTimestamp(int parameterIndex) throws SQLException {
5593 return this.bindingsAsRs.getTimestamp(parameterIndex);
5594 }
5595
5596 public URL getURL(int parameterIndex) throws SQLException {
5597 return this.bindingsAsRs.getURL(parameterIndex);
5598 }
5599
5600 public boolean isNull(int parameterIndex) throws SQLException {
5601 checkBounds(parameterIndex, 0);
5602
5603 return this.parameterIsNull[parameterIndex -1];
5604 }
5605 }
5606
5607 public String getPreparedSql() {
5608 if (this.rewrittenBatchSize == 0) {
5609 return this.originalSql;
5610 }
5611
5612 try {
5613 return this.parseInfo.getSqlForBatch(this.parseInfo);
5614 } catch (UnsupportedEncodingException e) {
5615 throw new RuntimeException(e);
5616 }
5617 }
5618
5619 public int getUpdateCount() throws SQLException {
5620 int count = super.getUpdateCount();
5621
5622 if (containsOnDuplicateKeyUpdateInSQL() &&
5623 this.compensateForOnDuplicateKeyUpdate) {
5624 if (count == 2 || count == 0) {
5625 count = 1;
5626 }
5627 }
5628
5629 return count;
5630 }
5631
5632 protected static boolean canRewrite(String sql, boolean isOnDuplicateKeyUpdate, int locationOfOnDuplicateKeyUpdate, int statementStartPos) {
5633 // Needs to be INSERT, can't have INSERT ... SELECT or
5634 // INSERT ... ON DUPLICATE KEY UPDATE with an id=LAST_INSERT_ID(...)
5635
5636 boolean rewritableOdku = true;
5637
5638 if (isOnDuplicateKeyUpdate) {
5639 int updateClausePos = StringUtils.indexOfIgnoreCase(
5640 locationOfOnDuplicateKeyUpdate, sql, " UPDATE ");
5641
5642 if (updateClausePos != -1) {
5643 rewritableOdku = StringUtils
5644 .indexOfIgnoreCaseRespectMarker(updateClausePos,
5645 sql, "LAST_INSERT_ID", "\"'`", "\"'`",
5646 false) == -1;
5647 }
5648 }
5649
5650 return StringUtils
5651 .startsWithIgnoreCaseAndWs(sql, "INSERT",
5652 statementStartPos)
5653 && StringUtils.indexOfIgnoreCaseRespectMarker(
5654 statementStartPos, sql, "SELECT", "\"'`",
5655 "\"'`", false) == -1 && rewritableOdku;
5656 }
5657 }