1 | /* |
2 | Copyright (C) 2002-2004 MySQL AB |
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 com.mysql.jdbc.Statement.CancelTask; |
28 | import com.mysql.jdbc.exceptions.MySQLTimeoutException; |
29 | import com.mysql.jdbc.profiler.ProfileEventSink; |
30 | import com.mysql.jdbc.profiler.ProfilerEvent; |
31 | |
32 | import java.io.ByteArrayInputStream; |
33 | import java.io.IOException; |
34 | import java.io.InputStream; |
35 | import java.io.Reader; |
36 | import java.io.UnsupportedEncodingException; |
37 | |
38 | import java.math.BigDecimal; |
39 | |
40 | import java.net.URL; |
41 | |
42 | import java.sql.Array; |
43 | import java.sql.Blob; |
44 | import java.sql.Clob; |
45 | import java.sql.Date; |
46 | import java.sql.ParameterMetaData; |
47 | import java.sql.Ref; |
48 | import java.sql.SQLException; |
49 | import java.sql.Time; |
50 | import java.sql.Timestamp; |
51 | import java.sql.Types; |
52 | |
53 | import java.util.ArrayList; |
54 | import java.util.BitSet; |
55 | import java.util.Calendar; |
56 | import java.util.Locale; |
57 | import java.util.TimeZone; |
58 | |
59 | /** |
60 | * JDBC Interface for MySQL-4.1 and newer server-side PreparedStatements. |
61 | * |
62 | * @author Mark Matthews |
63 | * @version $Id: ServerPreparedStatement.java,v 1.1.2.2 2005/05/17 14:58:56 |
64 | * mmatthews Exp $ |
65 | */ |
66 | public class ServerPreparedStatement extends PreparedStatement { |
67 | protected static final int BLOB_STREAM_READ_BUF_SIZE = 8192; |
68 | |
69 | static class BatchedBindValues { |
70 | BindValue[] batchedParameterValues; |
71 | |
72 | BatchedBindValues(BindValue[] paramVals) { |
73 | int numParams = paramVals.length; |
74 | |
75 | this.batchedParameterValues = new BindValue[numParams]; |
76 | |
77 | for (int i = 0; i < numParams; i++) { |
78 | this.batchedParameterValues[i] = new BindValue(paramVals[i]); |
79 | } |
80 | } |
81 | } |
82 | |
83 | static class BindValue { |
84 | |
85 | long boundBeforeExecutionNum = 0; |
86 | |
87 | long bindLength; /* Default length of data */ |
88 | |
89 | int bufferType; /* buffer type */ |
90 | |
91 | byte byteBinding; |
92 | |
93 | double doubleBinding; |
94 | |
95 | float floatBinding; |
96 | |
97 | int intBinding; |
98 | |
99 | boolean isLongData; /* long data indicator */ |
100 | |
101 | boolean isNull; /* NULL indicator */ |
102 | |
103 | boolean isSet = false; /* has this parameter been set? */ |
104 | |
105 | long longBinding; |
106 | |
107 | short shortBinding; |
108 | |
109 | Object value; /* The value to store */ |
110 | |
111 | BindValue() { |
112 | } |
113 | |
114 | BindValue(BindValue copyMe) { |
115 | this.value = copyMe.value; |
116 | this.isSet = copyMe.isSet; |
117 | this.isLongData = copyMe.isLongData; |
118 | this.isNull = copyMe.isNull; |
119 | this.bufferType = copyMe.bufferType; |
120 | this.bindLength = copyMe.bindLength; |
121 | this.byteBinding = copyMe.byteBinding; |
122 | this.shortBinding = copyMe.shortBinding; |
123 | this.intBinding = copyMe.intBinding; |
124 | this.longBinding = copyMe.longBinding; |
125 | this.floatBinding = copyMe.floatBinding; |
126 | this.doubleBinding = copyMe.doubleBinding; |
127 | } |
128 | |
129 | void reset() { |
130 | this.isSet = false; |
131 | this.value = null; |
132 | this.isLongData = false; |
133 | |
134 | this.byteBinding = 0; |
135 | this.shortBinding = 0; |
136 | this.intBinding = 0; |
137 | this.longBinding = 0L; |
138 | this.floatBinding = 0; |
139 | this.doubleBinding = 0D; |
140 | } |
141 | |
142 | public String toString() { |
143 | return toString(false); |
144 | } |
145 | |
146 | public String toString(boolean quoteIfNeeded) { |
147 | if (this.isLongData) { |
148 | return "' STREAM DATA '"; |
149 | } |
150 | |
151 | switch (this.bufferType) { |
152 | case MysqlDefs.FIELD_TYPE_TINY: |
153 | return String.valueOf(byteBinding); |
154 | case MysqlDefs.FIELD_TYPE_SHORT: |
155 | return String.valueOf(shortBinding); |
156 | case MysqlDefs.FIELD_TYPE_LONG: |
157 | return String.valueOf(intBinding); |
158 | case MysqlDefs.FIELD_TYPE_LONGLONG: |
159 | return String.valueOf(longBinding); |
160 | case MysqlDefs.FIELD_TYPE_FLOAT: |
161 | return String.valueOf(floatBinding); |
162 | case MysqlDefs.FIELD_TYPE_DOUBLE: |
163 | return String.valueOf(doubleBinding); |
164 | case MysqlDefs.FIELD_TYPE_TIME: |
165 | case MysqlDefs.FIELD_TYPE_DATE: |
166 | case MysqlDefs.FIELD_TYPE_DATETIME: |
167 | case MysqlDefs.FIELD_TYPE_TIMESTAMP: |
168 | case MysqlDefs.FIELD_TYPE_VAR_STRING: |
169 | case MysqlDefs.FIELD_TYPE_STRING: |
170 | case MysqlDefs.FIELD_TYPE_VARCHAR: |
171 | if (quoteIfNeeded) { |
172 | return "'" + String.valueOf(value) + "'"; |
173 | } else { |
174 | return String.valueOf(value); |
175 | } |
176 | default: |
177 | if (value instanceof byte[]) { |
178 | return "byte data"; |
179 | |
180 | } else { |
181 | if (quoteIfNeeded) { |
182 | return "'" + String.valueOf(value) + "'"; |
183 | } else { |
184 | return String.valueOf(value); |
185 | } |
186 | } |
187 | } |
188 | } |
189 | } |
190 | |
191 | /* 1 (length) + 2 (year) + 1 (month) + 1 (day) */ |
192 | private static final byte MAX_DATE_REP_LENGTH = (byte) 5; |
193 | |
194 | /* |
195 | * 1 (length) + 2 (year) + 1 (month) + 1 (day) + 1 (hour) + 1 (minute) + 1 |
196 | * (second) + 4 (microseconds) |
197 | */ |
198 | private static final byte MAX_DATETIME_REP_LENGTH = 12; |
199 | |
200 | /* |
201 | * 1 (length) + 1 (is negative) + 4 (day count) + 1 (hour) + 1 (minute) + 1 |
202 | * (seconds) + 4 (microseconds) |
203 | */ |
204 | private static final byte MAX_TIME_REP_LENGTH = 13; |
205 | |
206 | private void storeTime(Buffer intoBuf, Time tm) throws SQLException { |
207 | |
208 | intoBuf.ensureCapacity(9); |
209 | intoBuf.writeByte((byte) 8); // length |
210 | intoBuf.writeByte((byte) 0); // neg flag |
211 | intoBuf.writeLong(0); // tm->day, not used |
212 | |
213 | Calendar sessionCalendar = getCalendarInstanceForSessionOrNew(); |
214 | |
215 | synchronized (sessionCalendar) { |
216 | java.util.Date oldTime = sessionCalendar.getTime(); |
217 | try { |
218 | sessionCalendar.setTime(tm); |
219 | intoBuf.writeByte((byte) sessionCalendar.get(Calendar.HOUR_OF_DAY)); |
220 | intoBuf.writeByte((byte) sessionCalendar.get(Calendar.MINUTE)); |
221 | intoBuf.writeByte((byte) sessionCalendar.get(Calendar.SECOND)); |
222 | |
223 | // intoBuf.writeLongInt(0); // tm-second_part |
224 | } finally { |
225 | sessionCalendar.setTime(oldTime); |
226 | } |
227 | } |
228 | } |
229 | |
230 | /** |
231 | * Flag indicating whether or not the long parameters have been 'switched' |
232 | * back to normal parameters. We can not execute() if clearParameters() |
233 | * hasn't been called in this case. |
234 | */ |
235 | private boolean detectedLongParameterSwitch = false; |
236 | |
237 | /** |
238 | * The number of fields in the result set (if any) for this |
239 | * PreparedStatement. |
240 | */ |
241 | private int fieldCount; |
242 | |
243 | /** Has this prepared statement been marked invalid? */ |
244 | private boolean invalid = false; |
245 | |
246 | /** If this statement has been marked invalid, what was the reason? */ |
247 | private SQLException invalidationException; |
248 | |
249 | /** Does this query modify data? */ |
250 | private boolean isSelectQuery; |
251 | |
252 | private Buffer outByteBuffer; |
253 | |
254 | /** Bind values for individual fields */ |
255 | private BindValue[] parameterBindings; |
256 | |
257 | /** Field-level metadata for parameters */ |
258 | private Field[] parameterFields; |
259 | |
260 | /** Field-level metadata for result sets. */ |
261 | private Field[] resultFields; |
262 | |
263 | /** Do we need to send/resend types to the server? */ |
264 | private boolean sendTypesToServer = false; |
265 | |
266 | /** The ID that the server uses to identify this PreparedStatement */ |
267 | private long serverStatementId; |
268 | |
269 | /** The type used for string bindings, changes from version-to-version */ |
270 | private int stringTypeCode = MysqlDefs.FIELD_TYPE_STRING; |
271 | |
272 | private boolean serverNeedsResetBeforeEachExecution; |
273 | |
274 | /** |
275 | * Creates a new ServerPreparedStatement object. |
276 | * |
277 | * @param conn |
278 | * the connection creating us. |
279 | * @param sql |
280 | * the SQL containing the statement to prepare. |
281 | * @param catalog |
282 | * the catalog in use when we were created. |
283 | * |
284 | * @throws SQLException |
285 | * If an error occurs |
286 | */ |
287 | public ServerPreparedStatement(Connection conn, String sql, String catalog) |
288 | throws SQLException { |
289 | super(conn, catalog); |
290 | |
291 | checkNullOrEmptyQuery(sql); |
292 | |
293 | this.isSelectQuery = StringUtils.startsWithIgnoreCaseAndWs(sql, |
294 | "SELECT"); //$NON-NLS-1$ |
295 | |
296 | if (this.connection.versionMeetsMinimum(5, 0, 0)) { |
297 | this.serverNeedsResetBeforeEachExecution = |
298 | !this.connection.versionMeetsMinimum(5, 0, 3); |
299 | } else { |
300 | this.serverNeedsResetBeforeEachExecution = |
301 | !this.connection.versionMeetsMinimum(4, 1, 10); |
302 | } |
303 | |
304 | this.useTrueBoolean = this.connection.versionMeetsMinimum(3, 21, 23); |
305 | this.hasLimitClause = (StringUtils.indexOfIgnoreCase(sql, "LIMIT") != -1); //$NON-NLS-1$ |
306 | this.firstCharOfStmt = StringUtils.firstNonWsCharUc(sql); |
307 | this.originalSql = sql; |
308 | |
309 | if (this.connection.versionMeetsMinimum(4, 1, 2)) { |
310 | this.stringTypeCode = MysqlDefs.FIELD_TYPE_VAR_STRING; |
311 | } else { |
312 | this.stringTypeCode = MysqlDefs.FIELD_TYPE_STRING; |
313 | } |
314 | |
315 | try { |
316 | serverPrepare(sql); |
317 | } catch (SQLException sqlEx) { |
318 | realClose(false, true); |
319 | // don't wrap SQLExceptions |
320 | throw sqlEx; |
321 | } catch (Exception ex) { |
322 | realClose(false, true); |
323 | |
324 | throw SQLError.createSQLException(ex.toString(), |
325 | SQLError.SQL_STATE_GENERAL_ERROR); |
326 | } |
327 | } |
328 | |
329 | /** |
330 | * JDBC 2.0 Add a set of parameters to the batch. |
331 | * |
332 | * @exception SQLException |
333 | * if a database-access error occurs. |
334 | * |
335 | * @see Statement#addBatch |
336 | */ |
337 | public synchronized void addBatch() throws SQLException { |
338 | checkClosed(); |
339 | |
340 | if (this.batchedArgs == null) { |
341 | this.batchedArgs = new ArrayList(); |
342 | } |
343 | |
344 | this.batchedArgs.add(new BatchedBindValues(this.parameterBindings)); |
345 | } |
346 | |
347 | protected String asSql(boolean quoteStreamsAndUnknowns) throws SQLException { |
348 | |
349 | PreparedStatement pStmtForSub = null; |
350 | |
351 | try { |
352 | pStmtForSub = new PreparedStatement(this.connection, |
353 | this.originalSql, this.currentCatalog); |
354 | |
355 | int numParameters = pStmtForSub.parameterCount; |
356 | int ourNumParameters = this.parameterCount; |
357 | |
358 | for (int i = 0; (i < numParameters) && (i < ourNumParameters); i++) { |
359 | if (this.parameterBindings[i] != null) { |
360 | if (this.parameterBindings[i].isNull) { |
361 | pStmtForSub.setNull(i + 1, Types.NULL); |
362 | } else { |
363 | BindValue bindValue = this.parameterBindings[i]; |
364 | |
365 | // |
366 | // Handle primitives first |
367 | // |
368 | switch (bindValue.bufferType) { |
369 | |
370 | case MysqlDefs.FIELD_TYPE_TINY: |
371 | pStmtForSub.setByte(i + 1, bindValue.byteBinding); |
372 | break; |
373 | case MysqlDefs.FIELD_TYPE_SHORT: |
374 | pStmtForSub.setShort(i + 1, bindValue.shortBinding); |
375 | break; |
376 | case MysqlDefs.FIELD_TYPE_LONG: |
377 | pStmtForSub.setInt(i + 1, bindValue.intBinding); |
378 | break; |
379 | case MysqlDefs.FIELD_TYPE_LONGLONG: |
380 | pStmtForSub.setLong(i + 1, bindValue.longBinding); |
381 | break; |
382 | case MysqlDefs.FIELD_TYPE_FLOAT: |
383 | pStmtForSub.setFloat(i + 1, bindValue.floatBinding); |
384 | break; |
385 | case MysqlDefs.FIELD_TYPE_DOUBLE: |
386 | pStmtForSub.setDouble(i + 1, |
387 | bindValue.doubleBinding); |
388 | break; |
389 | default: |
390 | pStmtForSub.setObject(i + 1, |
391 | this.parameterBindings[i].value); |
392 | break; |
393 | } |
394 | } |
395 | } |
396 | } |
397 | |
398 | return pStmtForSub.asSql(quoteStreamsAndUnknowns); |
399 | } finally { |
400 | if (pStmtForSub != null) { |
401 | try { |
402 | pStmtForSub.close(); |
403 | } catch (SQLException sqlEx) { |
404 | ; // ignore |
405 | } |
406 | } |
407 | } |
408 | } |
409 | |
410 | /* |
411 | * (non-Javadoc) |
412 | * |
413 | * @see com.mysql.jdbc.Statement#checkClosed() |
414 | */ |
415 | protected void checkClosed() throws SQLException { |
416 | if (this.invalid) { |
417 | throw this.invalidationException; |
418 | } |
419 | |
420 | super.checkClosed(); |
421 | } |
422 | |
423 | /** |
424 | * @see java.sql.PreparedStatement#clearParameters() |
425 | */ |
426 | public void clearParameters() throws SQLException { |
427 | checkClosed(); |
428 | clearParametersInternal(true); |
429 | } |
430 | |
431 | private void clearParametersInternal(boolean clearServerParameters) |
432 | throws SQLException { |
433 | boolean hadLongData = false; |
434 | |
435 | if (this.parameterBindings != null) { |
436 | for (int i = 0; i < this.parameterCount; i++) { |
437 | if ((this.parameterBindings[i] != null) |
438 | && this.parameterBindings[i].isLongData) { |
439 | hadLongData = true; |
440 | } |
441 | |
442 | this.parameterBindings[i].reset(); |
443 | } |
444 | } |
445 | |
446 | if (clearServerParameters && hadLongData) { |
447 | serverResetStatement(); |
448 | |
449 | this.detectedLongParameterSwitch = false; |
450 | } |
451 | } |
452 | |
453 | protected boolean isCached = false; |
454 | |
455 | protected void setClosed(boolean flag) { |
456 | this.isClosed = flag; |
457 | } |
458 | /** |
459 | * @see java.sql.Statement#close() |
460 | */ |
461 | public void close() throws SQLException { |
462 | if (this.isCached) { |
463 | this.isClosed = true; |
464 | this.connection.recachePreparedStatement(this); |
465 | return; |
466 | } |
467 | |
468 | realClose(true, true); |
469 | } |
470 | |
471 | private void dumpCloseForTestcase() { |
472 | StringBuffer buf = new StringBuffer(); |
473 | this.connection.generateConnectionCommentBlock(buf); |
474 | buf.append("DEALLOCATE PREPARE debug_stmt_"); |
475 | buf.append(this.statementId); |
476 | buf.append(";\n"); |
477 | |
478 | this.connection.dumpTestcaseQuery(buf.toString()); |
479 | } |
480 | |
481 | private void dumpExecuteForTestcase() throws SQLException { |
482 | StringBuffer buf = new StringBuffer(); |
483 | |
484 | for (int i = 0; i < this.parameterCount; i++) { |
485 | this.connection.generateConnectionCommentBlock(buf); |
486 | |
487 | buf.append("SET @debug_stmt_param"); |
488 | buf.append(this.statementId); |
489 | buf.append("_"); |
490 | buf.append(i); |
491 | buf.append("="); |
492 | |
493 | if (this.parameterBindings[i].isNull) { |
494 | buf.append("NULL"); |
495 | } else { |
496 | buf.append(this.parameterBindings[i].toString(true)); |
497 | } |
498 | |
499 | buf.append(";\n"); |
500 | } |
501 | |
502 | this.connection.generateConnectionCommentBlock(buf); |
503 | |
504 | buf.append("EXECUTE debug_stmt_"); |
505 | buf.append(this.statementId); |
506 | |
507 | if (this.parameterCount > 0) { |
508 | buf.append(" USING "); |
509 | for (int i = 0; i < this.parameterCount; i++) { |
510 | if (i > 0) { |
511 | buf.append(", "); |
512 | } |
513 | |
514 | buf.append("@debug_stmt_param"); |
515 | buf.append(this.statementId); |
516 | buf.append("_"); |
517 | buf.append(i); |
518 | |
519 | } |
520 | } |
521 | |
522 | buf.append(";\n"); |
523 | |
524 | this.connection.dumpTestcaseQuery(buf.toString()); |
525 | } |
526 | |
527 | private void dumpPrepareForTestcase() throws SQLException { |
528 | |
529 | StringBuffer buf = new StringBuffer(this.originalSql.length() + 64); |
530 | |
531 | this.connection.generateConnectionCommentBlock(buf); |
532 | |
533 | buf.append("PREPARE debug_stmt_"); |
534 | buf.append(this.statementId); |
535 | buf.append(" FROM \""); |
536 | buf.append(this.originalSql); |
537 | buf.append("\";\n"); |
538 | |
539 | this.connection.dumpTestcaseQuery(buf.toString()); |
540 | } |
541 | |
542 | /** |
543 | * @see java.sql.Statement#executeBatch() |
544 | */ |
545 | public synchronized int[] executeBatch() throws SQLException { |
546 | if (this.connection.isReadOnly()) { |
547 | throw SQLError.createSQLException(Messages |
548 | .getString("ServerPreparedStatement.2") //$NON-NLS-1$ |
549 | + Messages.getString("ServerPreparedStatement.3"), //$NON-NLS-1$ |
550 | SQLError.SQL_STATE_ILLEGAL_ARGUMENT); |
551 | } |
552 | |
553 | checkClosed(); |
554 | |
555 | synchronized (this.connection.getMutex()) { |
556 | clearWarnings(); |
557 | |
558 | // Store this for later, we're going to 'swap' them out |
559 | // as we execute each batched statement... |
560 | BindValue[] oldBindValues = this.parameterBindings; |
561 | |
562 | try { |
563 | int[] updateCounts = null; |
564 | |
565 | if (this.batchedArgs != null) { |
566 | int nbrCommands = this.batchedArgs.size(); |
567 | updateCounts = new int[nbrCommands]; |
568 | |
569 | if (this.retrieveGeneratedKeys) { |
570 | this.batchedGeneratedKeys = new ArrayList(nbrCommands); |
571 | } |
572 | |
573 | for (int i = 0; i < nbrCommands; i++) { |
574 | updateCounts[i] = -3; |
575 | } |
576 | |
577 | SQLException sqlEx = null; |
578 | |
579 | int commandIndex = 0; |
580 | |
581 | BindValue[] previousBindValuesForBatch = null; |
582 | |
583 | for (commandIndex = 0; commandIndex < nbrCommands; commandIndex++) { |
584 | Object arg = this.batchedArgs.get(commandIndex); |
585 | |
586 | if (arg instanceof String) { |
587 | updateCounts[commandIndex] = executeUpdate((String) arg); |
588 | } else { |
589 | this.parameterBindings = ((BatchedBindValues) arg).batchedParameterValues; |
590 | |
591 | try { |
592 | // We need to check types each time, as |
593 | // the user might have bound different |
594 | // types in each addBatch() |
595 | |
596 | if (previousBindValuesForBatch != null) { |
597 | for (int j = 0; j < this.parameterBindings.length; j++) { |
598 | if (this.parameterBindings[j].bufferType != previousBindValuesForBatch[j].bufferType) { |
599 | this.sendTypesToServer = true; |
600 | |
601 | break; |
602 | } |
603 | } |
604 | } |
605 | |
606 | try { |
607 | updateCounts[commandIndex] = executeUpdate(false, true); |
608 | } finally { |
609 | previousBindValuesForBatch = this.parameterBindings; |
610 | } |
611 | |
612 | if (this.retrieveGeneratedKeys) { |
613 | java.sql.ResultSet rs = null; |
614 | |
615 | try { |
616 | // we don't want to use our version, |
617 | // because we've altered the behavior of |
618 | // ours to support batch updates |
619 | // (catch-22) |
620 | // Ideally, what we need here is |
621 | // super.super.getGeneratedKeys() |
622 | // but that construct doesn't exist in |
623 | // Java, so that's why there's |
624 | // this kludge. |
625 | rs = getGeneratedKeysInternal(); |
626 | |
627 | while (rs.next()) { |
628 | this.batchedGeneratedKeys |
629 | .add(new byte[][] { rs |
630 | .getBytes(1) }); |
631 | } |
632 | } finally { |
633 | if (rs != null) { |
634 | rs.close(); |
635 | } |
636 | } |
637 | } |
638 | } catch (SQLException ex) { |
639 | updateCounts[commandIndex] = EXECUTE_FAILED; |
640 | |
641 | if (this.connection.getContinueBatchOnError()) { |
642 | sqlEx = ex; |
643 | } else { |
644 | int[] newUpdateCounts = new int[commandIndex]; |
645 | System.arraycopy(updateCounts, 0, |
646 | newUpdateCounts, 0, commandIndex); |
647 | |
648 | throw new java.sql.BatchUpdateException(ex |
649 | .getMessage(), ex.getSQLState(), ex |
650 | .getErrorCode(), newUpdateCounts); |
651 | } |
652 | } |
653 | } |
654 | } |
655 | |
656 | if (sqlEx != null) { |
657 | throw new java.sql.BatchUpdateException(sqlEx |
658 | .getMessage(), sqlEx.getSQLState(), sqlEx |
659 | .getErrorCode(), updateCounts); |
660 | } |
661 | } |
662 | |
663 | return (updateCounts != null) ? updateCounts : new int[0]; |
664 | } finally { |
665 | this.parameterBindings = oldBindValues; |
666 | this.sendTypesToServer = true; |
667 | |
668 | clearBatch(); |
669 | } |
670 | } |
671 | } |
672 | |
673 | /** |
674 | * @see com.mysql.jdbc.PreparedStatement#executeInternal(int, |
675 | * com.mysql.jdbc.Buffer, boolean, boolean) |
676 | */ |
677 | protected com.mysql.jdbc.ResultSet executeInternal(int maxRowsToRetrieve, |
678 | Buffer sendPacket, boolean createStreamingResultSet, |
679 | boolean queryIsSelectOnly, boolean unpackFields, boolean isBatch) |
680 | throws SQLException { |
681 | this.numberOfExecutions++; |
682 | |
683 | // We defer to server-side execution |
684 | try { |
685 | return serverExecute(maxRowsToRetrieve, createStreamingResultSet); |
686 | } catch (SQLException sqlEx) { |
687 | // don't wrap SQLExceptions |
688 | if (this.connection.getEnablePacketDebug()) { |
689 | this.connection.getIO().dumpPacketRingBuffer(); |
690 | } |
691 | |
692 | if (this.connection.getDumpQueriesOnException()) { |
693 | String extractedSql = toString(); |
694 | StringBuffer messageBuf = new StringBuffer(extractedSql |
695 | .length() + 32); |
696 | messageBuf |
697 | .append("\n\nQuery being executed when exception was thrown:\n\n"); |
698 | messageBuf.append(extractedSql); |
699 | |
700 | sqlEx = Connection.appendMessageToException(sqlEx, messageBuf |
701 | .toString()); |
702 | } |
703 | |
704 | throw sqlEx; |
705 | } catch (Exception ex) { |
706 | if (this.connection.getEnablePacketDebug()) { |
707 | this.connection.getIO().dumpPacketRingBuffer(); |
708 | } |
709 | |
710 | SQLException sqlEx = SQLError.createSQLException(ex.toString(), |
711 | SQLError.SQL_STATE_GENERAL_ERROR); |
712 | |
713 | if (this.connection.getDumpQueriesOnException()) { |
714 | String extractedSql = toString(); |
715 | StringBuffer messageBuf = new StringBuffer(extractedSql |
716 | .length() + 32); |
717 | messageBuf |
718 | .append("\n\nQuery being executed when exception was thrown:\n\n"); |
719 | messageBuf.append(extractedSql); |
720 | |
721 | sqlEx = Connection.appendMessageToException(sqlEx, messageBuf |
722 | .toString()); |
723 | } |
724 | |
725 | throw sqlEx; |
726 | } |
727 | } |
728 | |
729 | /** |
730 | * @see com.mysql.jdbc.PreparedStatement#fillSendPacket() |
731 | */ |
732 | protected Buffer fillSendPacket() throws SQLException { |
733 | return null; // we don't use this type of packet |
734 | } |
735 | |
736 | /** |
737 | * @see com.mysql.jdbc.PreparedStatement#fillSendPacket(byte, |
738 | * java.io.InputStream, boolean, int) |
739 | */ |
740 | protected Buffer fillSendPacket(byte[][] batchedParameterStrings, |
741 | InputStream[] batchedParameterStreams, boolean[] batchedIsStream, |
742 | int[] batchedStreamLengths) throws SQLException { |
743 | return null; // we don't use this type of packet |
744 | } |
745 | |
746 | private BindValue getBinding(int parameterIndex, boolean forLongData) |
747 | throws SQLException { |
748 | checkClosed(); |
749 | |
750 | if (this.parameterBindings.length == 0) { |
751 | throw SQLError.createSQLException(Messages |
752 | .getString("ServerPreparedStatement.8"), //$NON-NLS-1$ |
753 | SQLError.SQL_STATE_ILLEGAL_ARGUMENT); |
754 | } |
755 | |
756 | parameterIndex--; |
757 | |
758 | if ((parameterIndex < 0) |
759 | || (parameterIndex >= this.parameterBindings.length)) { |
760 | throw SQLError.createSQLException(Messages |
761 | .getString("ServerPreparedStatement.9") //$NON-NLS-1$ |
762 | + (parameterIndex + 1) |
763 | + Messages.getString("ServerPreparedStatement.10") //$NON-NLS-1$ |
764 | + this.parameterBindings.length, |
765 | SQLError.SQL_STATE_ILLEGAL_ARGUMENT); |
766 | } |
767 | |
768 | if (this.parameterBindings[parameterIndex] == null) { |
769 | this.parameterBindings[parameterIndex] = new BindValue(); |
770 | } else { |
771 | if (this.parameterBindings[parameterIndex].isLongData |
772 | && !forLongData) { |
773 | this.detectedLongParameterSwitch = true; |
774 | } |
775 | } |
776 | |
777 | this.parameterBindings[parameterIndex].isSet = true; |
778 | this.parameterBindings[parameterIndex].boundBeforeExecutionNum = this.numberOfExecutions; |
779 | |
780 | return this.parameterBindings[parameterIndex]; |
781 | } |
782 | |
783 | /** |
784 | * @see com.mysql.jdbc.PreparedStatement#getBytes(int) |
785 | */ |
786 | byte[] getBytes(int parameterIndex) throws SQLException { |
787 | BindValue bindValue = getBinding(parameterIndex, false); |
788 | |
789 | if (bindValue.isNull) { |
790 | return null; |
791 | } else if (bindValue.isLongData) { |
792 | throw new NotImplemented(); |
793 | } else { |
794 | if (this.outByteBuffer == null) { |
795 | this.outByteBuffer = new Buffer(this.connection |
796 | .getNetBufferLength()); |
797 | } |
798 | |
799 | this.outByteBuffer.clear(); |
800 | |
801 | int originalPosition = this.outByteBuffer.getPosition(); |
802 | |
803 | storeBinding(this.outByteBuffer, bindValue, this.connection.getIO()); |
804 | |
805 | int newPosition = this.outByteBuffer.getPosition(); |
806 | |
807 | int length = newPosition - originalPosition; |
808 | |
809 | byte[] valueAsBytes = new byte[length]; |
810 | |
811 | System.arraycopy(this.outByteBuffer.getByteBuffer(), |
812 | originalPosition, valueAsBytes, 0, length); |
813 | |
814 | return valueAsBytes; |
815 | } |
816 | } |
817 | |
818 | /** |
819 | * @see java.sql.PreparedStatement#getMetaData() |
820 | */ |
821 | public java.sql.ResultSetMetaData getMetaData() throws SQLException { |
822 | checkClosed(); |
823 | |
824 | if (this.resultFields == null) { |
825 | return null; |
826 | } |
827 | |
828 | return new ResultSetMetaData(this.resultFields); |
829 | } |
830 | |
831 | /** |
832 | * @see java.sql.PreparedStatement#getParameterMetaData() |
833 | */ |
834 | public ParameterMetaData getParameterMetaData() throws SQLException { |
835 | checkClosed(); |
836 | |
837 | if (this.parameterMetaData == null) { |
838 | this.parameterMetaData = new MysqlParameterMetadata( |
839 | this.parameterFields, this.parameterCount); |
840 | } |
841 | |
842 | return this.parameterMetaData; |
843 | } |
844 | |
845 | /** |
846 | * @see com.mysql.jdbc.PreparedStatement#isNull(int) |
847 | */ |
848 | boolean isNull(int paramIndex) { |
849 | throw new IllegalArgumentException(Messages |
850 | .getString("ServerPreparedStatement.7")); //$NON-NLS-1$ |
851 | } |
852 | |
853 | /** |
854 | * Closes this connection and frees all resources. |
855 | * |
856 | * @param calledExplicitly |
857 | * was this called from close()? |
858 | * |
859 | * @throws SQLException |
860 | * if an error occurs |
861 | */ |
862 | protected void realClose(boolean calledExplicitly, |
863 | boolean closeOpenResults) throws SQLException { |
864 | if (this.isClosed) { |
865 | return; |
866 | } |
867 | |
868 | if (this.connection != null) { |
869 | if (this.connection.getAutoGenerateTestcaseScript()) { |
870 | dumpCloseForTestcase(); |
871 | } |
872 | |
873 | synchronized (this.connection.getMutex()) { |
874 | |
875 | // |
876 | // Don't communicate with the server if we're being |
877 | // called from the finalizer... |
878 | // |
879 | // This will leak server resources, but if we don't do this, |
880 | // we'll deadlock (potentially, because there's no guarantee |
881 | // when, what order, and what concurrency finalizers will be |
882 | // called with). Well-behaved programs won't rely on finalizers |
883 | // to clean up their statements. |
884 | // |
885 | |
886 | SQLException exceptionDuringClose = null; |
887 | |
888 | |
889 | if (calledExplicitly) { |
890 | try { |
891 | |
892 | MysqlIO mysql = this.connection.getIO(); |
893 | |
894 | Buffer packet = mysql.getSharedSendPacket(); |
895 | |
896 | packet.writeByte((byte) MysqlDefs.COM_CLOSE_STATEMENT); |
897 | packet.writeLong(this.serverStatementId); |
898 | |
899 | mysql.sendCommand(MysqlDefs.COM_CLOSE_STATEMENT, null, |
900 | packet, true, null); |
901 | } catch (SQLException sqlEx) { |
902 | exceptionDuringClose = sqlEx; |
903 | } |
904 | |
905 | } |
906 | |
907 | super.realClose(calledExplicitly, closeOpenResults); |
908 | |
909 | clearParametersInternal(false); |
910 | this.parameterBindings = null; |
911 | |
912 | this.parameterFields = null; |
913 | this.resultFields = null; |
914 | |
915 | if (exceptionDuringClose != null) { |
916 | throw exceptionDuringClose; |
917 | } |
918 | } |
919 | } |
920 | } |
921 | |
922 | /** |
923 | * Used by Connection when auto-reconnecting to retrieve 'lost' prepared |
924 | * statements. |
925 | * |
926 | * @throws SQLException |
927 | * if an error occurs. |
928 | */ |
929 | protected void rePrepare() throws SQLException { |
930 | this.invalidationException = null; |
931 | |
932 | try { |
933 | serverPrepare(this.originalSql); |
934 | } catch (SQLException sqlEx) { |
935 | // don't wrap SQLExceptions |
936 | this.invalidationException = sqlEx; |
937 | } catch (Exception ex) { |
938 | this.invalidationException = SQLError.createSQLException(ex.toString(), |
939 | SQLError.SQL_STATE_GENERAL_ERROR); |
940 | } |
941 | |
942 | if (this.invalidationException != null) { |
943 | this.invalid = true; |
944 | |
945 | this.parameterBindings = null; |
946 | |
947 | this.parameterFields = null; |
948 | this.resultFields = null; |
949 | |
950 | if (this.results != null) { |
951 | try { |
952 | this.results.close(); |
953 | } catch (Exception ex) { |
954 | ; |
955 | } |
956 | } |
957 | |
958 | if (this.connection != null) { |
959 | if (this.maxRowsChanged) { |
960 | this.connection.unsetMaxRows(this); |
961 | } |
962 | |
963 | if (!this.connection.getDontTrackOpenResources()) { |
964 | this.connection.unregisterStatement(this); |
965 | } |
966 | } |
967 | } |
968 | } |
969 | |
970 | /** |
971 | * Tells the server to execute this prepared statement with the current |
972 | * parameter bindings. |
973 | * |
974 | * <pre> |
975 | * |
976 | * |
977 | * - Server gets the command 'COM_EXECUTE' to execute the |
978 | * previously prepared query. If there is any param markers; |
979 | * then client will send the data in the following format: |
980 | * |
981 | * [COM_EXECUTE:1] |
982 | * [STMT_ID:4] |
983 | * [NULL_BITS:(param_count+7)/8)] |
984 | * [TYPES_SUPPLIED_BY_CLIENT(0/1):1] |
985 | * [[length]data] |
986 | * [[length]data] .. [[length]data]. |
987 | * |
988 | * (Note: Except for string/binary types; all other types will not be |
989 | * supplied with length field) |
990 | * |
991 | * |
992 | * </pre> |
993 | * |
994 | * @param maxRowsToRetrieve |
995 | * DOCUMENT ME! |
996 | * @param createStreamingResultSet |
997 | * DOCUMENT ME! |
998 | * |
999 | * @return DOCUMENT ME! |
1000 | * |
1001 | * @throws SQLException |
1002 | */ |
1003 | private com.mysql.jdbc.ResultSet serverExecute(int maxRowsToRetrieve, |
1004 | boolean createStreamingResultSet) throws SQLException { |
1005 | synchronized (this.connection.getMutex()) { |
1006 | if (this.detectedLongParameterSwitch) { |
1007 | // Check when values were bound |
1008 | boolean firstFound = false; |
1009 | long boundTimeToCheck = 0; |
1010 | |
1011 | for (int i = 0; i < this.parameterCount - 1; i++) { |
1012 | if (this.parameterBindings[i].isLongData) { |
1013 | if (firstFound && boundTimeToCheck != |
1014 | this.parameterBindings[i].boundBeforeExecutionNum) { |
1015 | throw SQLError.createSQLException(Messages |
1016 | .getString("ServerPreparedStatement.11") //$NON-NLS-1$ |
1017 | + Messages.getString("ServerPreparedStatement.12"), //$NON-NLS-1$ |
1018 | SQLError.SQL_STATE_DRIVER_NOT_CAPABLE); |
1019 | } else { |
1020 | firstFound = true; |
1021 | boundTimeToCheck = this.parameterBindings[i].boundBeforeExecutionNum; |
1022 | } |
1023 | } |
1024 | } |
1025 | |
1026 | // Okay, we've got all "newly"-bound streams, so reset |
1027 | // server-side state to clear out previous bindings |
1028 | |
1029 | serverResetStatement(); |
1030 | } |
1031 | |
1032 | |
1033 | // Check bindings |
1034 | for (int i = 0; i < this.parameterCount; i++) { |
1035 | if (!this.parameterBindings[i].isSet) { |
1036 | throw SQLError.createSQLException(Messages |
1037 | .getString("ServerPreparedStatement.13") + (i + 1) //$NON-NLS-1$ |
1038 | + Messages.getString("ServerPreparedStatement.14"), |
1039 | SQLError.SQL_STATE_ILLEGAL_ARGUMENT); //$NON-NLS-1$ |
1040 | } |
1041 | } |
1042 | |
1043 | // |
1044 | // Send all long data |
1045 | // |
1046 | for (int i = 0; i < this.parameterCount; i++) { |
1047 | if (this.parameterBindings[i].isLongData) { |
1048 | serverLongData(i, this.parameterBindings[i]); |
1049 | } |
1050 | } |
1051 | |
1052 | if (this.connection.getAutoGenerateTestcaseScript()) { |
1053 | dumpExecuteForTestcase(); |
1054 | } |
1055 | |
1056 | // |
1057 | // store the parameter values |
1058 | // |
1059 | MysqlIO mysql = this.connection.getIO(); |
1060 | |
1061 | Buffer packet = mysql.getSharedSendPacket(); |
1062 | |
1063 | packet.clear(); |
1064 | packet.writeByte((byte) MysqlDefs.COM_EXECUTE); |
1065 | packet.writeLong(this.serverStatementId); |
1066 | |
1067 | boolean usingCursor = false; |
1068 | |
1069 | if (this.connection.versionMeetsMinimum(4, 1, 2)) { |
1070 | // we only create cursor-backed result sets if |
1071 | // a) The query is a SELECT |
1072 | // b) The server supports it |
1073 | // c) We know it is forward-only (note this doesn't |
1074 | // preclude updatable result sets) |
1075 | // d) The user has set a fetch size |
1076 | if (this.resultFields != null && |
1077 | this.connection.isCursorFetchEnabled() |
1078 | && getResultSetType() == ResultSet.TYPE_FORWARD_ONLY |
1079 | && getResultSetConcurrency() == ResultSet.CONCUR_READ_ONLY |
1080 | && getFetchSize() > 0) { |
1081 | packet.writeByte(MysqlDefs.OPEN_CURSOR_FLAG); |
1082 | usingCursor = true; |
1083 | } else { |
1084 | packet.writeByte((byte) 0); // placeholder for flags |
1085 | } |
1086 | |
1087 | packet.writeLong(1); // placeholder for parameter |
1088 | // iterations |
1089 | } |
1090 | |
1091 | /* Reserve place for null-marker bytes */ |
1092 | int nullCount = (this.parameterCount + 7) / 8; |
1093 | |
1094 | // if (mysql.versionMeetsMinimum(4, 1, 2)) { |
1095 | // nullCount = (this.parameterCount + 9) / 8; |
1096 | // } |
1097 | int nullBitsPosition = packet.getPosition(); |
1098 | |
1099 | for (int i = 0; i < nullCount; i++) { |
1100 | packet.writeByte((byte) 0); |
1101 | } |
1102 | |
1103 | byte[] nullBitsBuffer = new byte[nullCount]; |
1104 | |
1105 | /* In case if buffers (type) altered, indicate to server */ |
1106 | packet.writeByte(this.sendTypesToServer ? (byte) 1 : (byte) 0); |
1107 | |
1108 | if (this.sendTypesToServer) { |
1109 | /* |
1110 | * Store types of parameters in first in first package that is |
1111 | * sent to the server. |
1112 | */ |
1113 | for (int i = 0; i < this.parameterCount; i++) { |
1114 | packet.writeInt(this.parameterBindings[i].bufferType); |
1115 | } |
1116 | } |
1117 | |
1118 | // |
1119 | // store the parameter values |
1120 | // |
1121 | for (int i = 0; i < this.parameterCount; i++) { |
1122 | if (!this.parameterBindings[i].isLongData) { |
1123 | if (!this.parameterBindings[i].isNull) { |
1124 | storeBinding(packet, this.parameterBindings[i], mysql); |
1125 | } else { |
1126 | nullBitsBuffer[i / 8] |= (1 << (i & 7)); |
1127 | } |
1128 | } |
1129 | } |
1130 | |
1131 | // |
1132 | // Go back and write the NULL flags |
1133 | // to the beginning of the packet |
1134 | // |
1135 | int endPosition = packet.getPosition(); |
1136 | packet.setPosition(nullBitsPosition); |
1137 | packet.writeBytesNoNull(nullBitsBuffer); |
1138 | packet.setPosition(endPosition); |
1139 | |
1140 | long begin = 0; |
1141 | |
1142 | if (this.connection.getProfileSql() |
1143 | || this.connection.getLogSlowQueries() |
1144 | || this.connection.getGatherPerformanceMetrics()) { |
1145 | begin = System.currentTimeMillis(); |
1146 | } |
1147 | |
1148 | this.wasCancelled = false; |
1149 | |
1150 | CancelTask timeoutTask = null; |
1151 | |
1152 | try { |
1153 | if (this.timeout != 0 |
1154 | && this.connection.versionMeetsMinimum(5, 0, 0)) { |
1155 | timeoutTask = new CancelTask(); |
1156 | this.connection.getCancelTimer().schedule(timeoutTask, |
1157 | this.timeout); |
1158 | } |
1159 | |
1160 | Buffer resultPacket = mysql.sendCommand(MysqlDefs.COM_EXECUTE, |
1161 | null, packet, false, null); |
1162 | |
1163 | if (timeoutTask != null) { |
1164 | timeoutTask.cancel(); |
1165 | timeoutTask = null; |
1166 | } |
1167 | |
1168 | if (this.wasCancelled) { |
1169 | this.wasCancelled = false; |
1170 | throw new MySQLTimeoutException(); |
1171 | } |
1172 | |
1173 | this.connection.incrementNumberOfPreparedExecutes(); |
1174 | |
1175 | if (this.connection.getProfileSql()) { |
1176 | this.eventSink = ProfileEventSink.getInstance(this.connection); |
1177 | |
1178 | this.eventSink.consumeEvent(new ProfilerEvent( |
1179 | ProfilerEvent.TYPE_EXECUTE, "", this.currentCatalog, //$NON-NLS-1$ |
1180 | this.connectionId, this.statementId, -1, System |
1181 | .currentTimeMillis(), (int) (System |
1182 | .currentTimeMillis() - begin), null, |
1183 | new Throwable(), truncateQueryToLog(asSql(true)))); |
1184 | } |
1185 | |
1186 | com.mysql.jdbc.ResultSet rs = mysql.readAllResults(this, |
1187 | maxRowsToRetrieve, this.resultSetType, |
1188 | this.resultSetConcurrency, createStreamingResultSet, |
1189 | this.currentCatalog, resultPacket, true, this.fieldCount, |
1190 | true); |
1191 | |
1192 | |
1193 | if (!createStreamingResultSet && |
1194 | this.serverNeedsResetBeforeEachExecution) { |
1195 | serverResetStatement(); // clear any long data... |
1196 | } |
1197 | |
1198 | this.sendTypesToServer = false; |
1199 | this.results = rs; |
1200 | |
1201 | if (this.connection.getLogSlowQueries() |
1202 | || this.connection.getGatherPerformanceMetrics()) { |
1203 | long elapsedTime = System.currentTimeMillis() - begin; |
1204 | |
1205 | if (this.connection.getLogSlowQueries() |
1206 | && (elapsedTime >= this.connection |
1207 | .getSlowQueryThresholdMillis())) { |
1208 | StringBuffer mesgBuf = new StringBuffer( |
1209 | 48 + this.originalSql.length()); |
1210 | mesgBuf.append(Messages |
1211 | .getString("ServerPreparedStatement.15")); //$NON-NLS-1$ |
1212 | mesgBuf.append(this.connection |
1213 | .getSlowQueryThresholdMillis()); |
1214 | mesgBuf.append(Messages |
1215 | .getString("ServerPreparedStatement.15a")); //$NON-NLS-1$ |
1216 | mesgBuf.append(elapsedTime); |
1217 | mesgBuf.append(Messages |
1218 | .getString("ServerPreparedStatement.16")); //$NON-NLS-1$ |
1219 | |
1220 | mesgBuf.append("as prepared: "); |
1221 | mesgBuf.append(this.originalSql); |
1222 | mesgBuf.append("\n\n with parameters bound:\n\n"); |
1223 | mesgBuf.append(asSql(true)); |
1224 | |
1225 | this.connection.getLog().logWarn(mesgBuf.toString()); |
1226 | |
1227 | if (this.connection.getExplainSlowQueries()) { |
1228 | String queryAsString = asSql(true); |
1229 | |
1230 | mysql.explainSlowQuery(queryAsString.getBytes(), |
1231 | queryAsString); |
1232 | } |
1233 | } |
1234 | |
1235 | if (this.connection.getGatherPerformanceMetrics()) { |
1236 | this.connection.registerQueryExecutionTime(elapsedTime); |
1237 | } |
1238 | } |
1239 | |
1240 | if (mysql.hadWarnings()) { |
1241 | mysql.scanForAndThrowDataTruncation(); |
1242 | } |
1243 | |
1244 | return rs; |
1245 | } finally { |
1246 | if (timeoutTask != null) { |
1247 | timeoutTask.cancel(); |
1248 | } |
1249 | } |
1250 | } |
1251 | } |
1252 | |
1253 | |
1254 | /** |
1255 | * Sends stream-type data parameters to the server. |
1256 | * |
1257 | * <pre> |
1258 | * |
1259 | * Long data handling: |
1260 | * |
1261 | * - Server gets the long data in pieces with command type 'COM_LONG_DATA'. |
1262 | * - The packet recieved will have the format as: |
1263 | * [COM_LONG_DATA: 1][STMT_ID:4][parameter_number:2][type:2][data] |
1264 | * - Checks if the type is specified by client, and if yes reads the type, |
1265 | * and stores the data in that format. |
1266 | * - It's up to the client to check for read data ended. The server doesn't |
1267 | * care; and also server doesn't notify to the client that it got the |
1268 | * data or not; if there is any error; then during execute; the error |
1269 | * will be returned |
1270 | * |
1271 | * </pre> |
1272 | * |
1273 | * @param parameterIndex |
1274 | * DOCUMENT ME! |
1275 | * @param longData |
1276 | * DOCUMENT ME! |
1277 | * |
1278 | * @throws SQLException |
1279 | * if an error occurs. |
1280 | */ |
1281 | private void serverLongData(int parameterIndex, BindValue longData) |
1282 | throws SQLException { |
1283 | synchronized (this.connection.getMutex()) { |
1284 | MysqlIO mysql = this.connection.getIO(); |
1285 | |
1286 | Buffer packet = mysql.getSharedSendPacket(); |
1287 | |
1288 | Object value = longData.value; |
1289 | |
1290 | if (value instanceof byte[]) { |
1291 | packet.clear(); |
1292 | packet.writeByte((byte) MysqlDefs.COM_LONG_DATA); |
1293 | packet.writeLong(this.serverStatementId); |
1294 | packet.writeInt((parameterIndex)); |
1295 | |
1296 | packet.writeBytesNoNull((byte[]) longData.value); |
1297 | |
1298 | mysql.sendCommand(MysqlDefs.COM_LONG_DATA, null, packet, true, |
1299 | null); |
1300 | } else if (value instanceof InputStream) { |
1301 | storeStream(mysql, parameterIndex, packet, (InputStream) value); |
1302 | } else if (value instanceof java.sql.Blob) { |
1303 | storeStream(mysql, parameterIndex, packet, |
1304 | ((java.sql.Blob) value).getBinaryStream()); |
1305 | } else if (value instanceof Reader) { |
1306 | storeReader(mysql, parameterIndex, packet, (Reader) value); |
1307 | } else { |
1308 | throw SQLError.createSQLException(Messages |
1309 | .getString("ServerPreparedStatement.18") //$NON-NLS-1$ |
1310 | + value.getClass().getName() + "'", //$NON-NLS-1$ |
1311 | SQLError.SQL_STATE_ILLEGAL_ARGUMENT); |
1312 | } |
1313 | } |
1314 | } |
1315 | |
1316 | private void serverPrepare(String sql) throws SQLException { |
1317 | synchronized (this.connection.getMutex()) { |
1318 | MysqlIO mysql = this.connection.getIO(); |
1319 | |
1320 | if (this.connection.getAutoGenerateTestcaseScript()) { |
1321 | dumpPrepareForTestcase(); |
1322 | } |
1323 | |
1324 | try { |
1325 | long begin = 0; |
1326 | |
1327 | if (StringUtils.startsWithIgnoreCaseAndWs(sql, "LOAD DATA")) { //$NON-NLS-1$ |
1328 | this.isLoadDataQuery = true; |
1329 | } else { |
1330 | this.isLoadDataQuery = false; |
1331 | } |
1332 | |
1333 | if (this.connection.getProfileSql()) { |
1334 | begin = System.currentTimeMillis(); |
1335 | } |
1336 | |
1337 | String characterEncoding = null; |
1338 | String connectionEncoding = this.connection.getEncoding(); |
1339 | |
1340 | if (!this.isLoadDataQuery && this.connection.getUseUnicode() |
1341 | && (connectionEncoding != null)) { |
1342 | characterEncoding = connectionEncoding; |
1343 | } |
1344 | |
1345 | Buffer prepareResultPacket = mysql.sendCommand( |
1346 | MysqlDefs.COM_PREPARE, sql, null, false, |
1347 | characterEncoding); |
1348 | |
1349 | if (this.connection.versionMeetsMinimum(4, 1, 1)) { |
1350 | // 4.1.1 and newer use the first byte |
1351 | // as an 'ok' or 'error' flag, so move |
1352 | // the buffer pointer past it to |
1353 | // start reading the statement id. |
1354 | prepareResultPacket.setPosition(1); |
1355 | } else { |
1356 | // 4.1.0 doesn't use the first byte as an |
1357 | // 'ok' or 'error' flag |
1358 | prepareResultPacket.setPosition(0); |
1359 | } |
1360 | |
1361 | this.serverStatementId = prepareResultPacket.readLong(); |
1362 | this.fieldCount = prepareResultPacket.readInt(); |
1363 | this.parameterCount = prepareResultPacket.readInt(); |
1364 | this.parameterBindings = new BindValue[this.parameterCount]; |
1365 | |
1366 | for (int i = 0; i < this.parameterCount; i++) { |
1367 | this.parameterBindings[i] = new BindValue(); |
1368 | } |
1369 | |
1370 | this.connection.incrementNumberOfPrepares(); |
1371 | |
1372 | if (this.connection.getProfileSql()) { |
1373 | this.eventSink = ProfileEventSink |
1374 | .getInstance(this.connection); |
1375 | |
1376 | this.eventSink.consumeEvent(new ProfilerEvent( |
1377 | ProfilerEvent.TYPE_PREPARE, |
1378 | "", this.currentCatalog, //$NON-NLS-1$ |
1379 | this.connectionId, this.statementId, -1, |
1380 | System.currentTimeMillis(), (int) (System |
1381 | .currentTimeMillis() - begin), null, |
1382 | new Throwable(), truncateQueryToLog(sql))); |
1383 | } |
1384 | |
1385 | if (this.parameterCount > 0) { |
1386 | if (this.connection.versionMeetsMinimum(4, 1, 2) |
1387 | && !mysql.isVersion(5, 0, 0)) { |
1388 | this.parameterFields = new Field[this.parameterCount]; |
1389 | |
1390 | Buffer metaDataPacket = mysql.readPacket(); |
1391 | |
1392 | int i = 0; |
1393 | |
1394 | while (!metaDataPacket.isLastDataPacket() |
1395 | && (i < this.parameterCount)) { |
1396 | this.parameterFields[i++] = mysql.unpackField( |
1397 | metaDataPacket, false); |
1398 | metaDataPacket = mysql.readPacket(); |
1399 | } |
1400 | } |
1401 | } |
1402 | |
1403 | if (this.fieldCount > 0) { |
1404 | this.resultFields = new Field[this.fieldCount]; |
1405 | |
1406 | Buffer fieldPacket = mysql.readPacket(); |
1407 | |
1408 | int i = 0; |
1409 | |
1410 | // Read in the result set column information |
1411 | while (!fieldPacket.isLastDataPacket() |
1412 | && (i < this.fieldCount)) { |
1413 | this.resultFields[i++] = mysql.unpackField(fieldPacket, |
1414 | false); |
1415 | fieldPacket = mysql.readPacket(); |
1416 | } |
1417 | } |
1418 | } catch (SQLException sqlEx) { |
1419 | if (this.connection.getDumpQueriesOnException()) { |
1420 | StringBuffer messageBuf = new StringBuffer(this.originalSql |
1421 | .length() + 32); |
1422 | messageBuf |
1423 | .append("\n\nQuery being prepared when exception was thrown:\n\n"); |
1424 | messageBuf.append(this.originalSql); |
1425 | |
1426 | sqlEx = Connection.appendMessageToException(sqlEx, |
1427 | messageBuf.toString()); |
1428 | } |
1429 | |
1430 | throw sqlEx; |
1431 | } finally { |
1432 | // Leave the I/O channel in a known state...there might be |
1433 | // packets out there |
1434 | // that we're not interested in |
1435 | this.connection.getIO().clearInputStream(); |
1436 | } |
1437 | } |
1438 | } |
1439 | |
1440 | private String truncateQueryToLog(String sql) { |
1441 | String query = null; |
1442 | |
1443 | if (sql.length() > this.connection.getMaxQuerySizeToLog()) { |
1444 | StringBuffer queryBuf = new StringBuffer( |
1445 | this.connection.getMaxQuerySizeToLog() + 12); |
1446 | queryBuf.append(sql.substring(0, this.connection.getMaxQuerySizeToLog())); |
1447 | queryBuf.append(Messages.getString("MysqlIO.25")); |
1448 | |
1449 | query = queryBuf.toString(); |
1450 | } else { |
1451 | query = sql; |
1452 | } |
1453 | |
1454 | return query; |
1455 | } |
1456 | |
1457 | private void serverResetStatement() throws SQLException { |
1458 | synchronized (this.connection.getMutex()) { |
1459 | |
1460 | MysqlIO mysql = this.connection.getIO(); |
1461 | |
1462 | Buffer packet = mysql.getSharedSendPacket(); |
1463 | |
1464 | packet.clear(); |
1465 | packet.writeByte((byte) MysqlDefs.COM_RESET_STMT); |
1466 | packet.writeLong(this.serverStatementId); |
1467 | |
1468 | try { |
1469 | mysql.sendCommand(MysqlDefs.COM_RESET_STMT, null, packet, |
1470 | !this.connection.versionMeetsMinimum(4, 1, 2), null); |
1471 | } catch (SQLException sqlEx) { |
1472 | throw sqlEx; |
1473 | } catch (Exception ex) { |
1474 | throw SQLError.createSQLException(ex.toString(), |
1475 | SQLError.SQL_STATE_GENERAL_ERROR); |
1476 | } finally { |
1477 | mysql.clearInputStream(); |
1478 | } |
1479 | } |
1480 | } |
1481 | |
1482 | /** |
1483 | * @see java.sql.PreparedStatement#setArray(int, java.sql.Array) |
1484 | */ |
1485 | public void setArray(int i, Array x) throws SQLException { |
1486 | throw new NotImplemented(); |
1487 | } |
1488 | |
1489 | /** |
1490 | * @see java.sql.PreparedStatement#setAsciiStream(int, java.io.InputStream, |
1491 | * int) |
1492 | */ |
1493 | public void setAsciiStream(int parameterIndex, InputStream x, int length) |
1494 | throws SQLException { |
1495 | checkClosed(); |
1496 | |
1497 | if (x == null) { |
1498 | setNull(parameterIndex, java.sql.Types.BINARY); |
1499 | } else { |
1500 | BindValue binding = getBinding(parameterIndex, true); |
1501 | setType(binding, MysqlDefs.FIELD_TYPE_BLOB); |
1502 | |
1503 | binding.value = x; |
1504 | binding.isNull = false; |
1505 | binding.isLongData = true; |
1506 | |
1507 | if (this.connection.getUseStreamLengthsInPrepStmts()) { |
1508 | binding.bindLength = length; |
1509 | } else { |
1510 | binding.bindLength = -1; |
1511 | } |
1512 | } |
1513 | } |
1514 | |
1515 | /** |
1516 | * @see java.sql.PreparedStatement#setBigDecimal(int, java.math.BigDecimal) |
1517 | */ |
1518 | public void setBigDecimal(int parameterIndex, BigDecimal x) |
1519 | throws SQLException { |
1520 | checkClosed(); |
1521 | |
1522 | if (x == null) { |
1523 | setNull(parameterIndex, java.sql.Types.DECIMAL); |
1524 | } else { |
1525 | setString(parameterIndex, StringUtils.fixDecimalExponent(x |
1526 | .toString())); |
1527 | } |
1528 | } |
1529 | |
1530 | /** |
1531 | * @see java.sql.PreparedStatement#setBinaryStream(int, java.io.InputStream, |
1532 | * int) |
1533 | */ |
1534 | public void setBinaryStream(int parameterIndex, InputStream x, int length) |
1535 | throws SQLException { |
1536 | checkClosed(); |
1537 | |
1538 | if (x == null) { |
1539 | setNull(parameterIndex, java.sql.Types.BINARY); |
1540 | } else { |
1541 | BindValue binding = getBinding(parameterIndex, true); |
1542 | setType(binding, MysqlDefs.FIELD_TYPE_BLOB); |
1543 | |
1544 | binding.value = x; |
1545 | binding.isNull = false; |
1546 | binding.isLongData = true; |
1547 | |
1548 | if (this.connection.getUseStreamLengthsInPrepStmts()) { |
1549 | binding.bindLength = length; |
1550 | } else { |
1551 | binding.bindLength = -1; |
1552 | } |
1553 | } |
1554 | } |
1555 | |
1556 | /** |
1557 | * @see java.sql.PreparedStatement#setBlob(int, java.sql.Blob) |
1558 | */ |
1559 | public void setBlob(int parameterIndex, Blob x) throws SQLException { |
1560 | checkClosed(); |
1561 | |
1562 | if (x == null) { |
1563 | setNull(parameterIndex, java.sql.Types.BINARY); |
1564 | } else { |
1565 | BindValue binding = getBinding(parameterIndex, true); |
1566 | setType(binding, MysqlDefs.FIELD_TYPE_BLOB); |
1567 | |
1568 | binding.value = x; |
1569 | binding.isNull = false; |
1570 | binding.isLongData = true; |
1571 | |
1572 | if (this.connection.getUseStreamLengthsInPrepStmts()) { |
1573 | binding.bindLength = x.length(); |
1574 | } else { |
1575 | binding.bindLength = -1; |
1576 | } |
1577 | } |
1578 | } |
1579 | |
1580 | /** |
1581 | * @see java.sql.PreparedStatement#setBoolean(int, boolean) |
1582 | */ |
1583 | public void setBoolean(int parameterIndex, boolean x) throws SQLException { |
1584 | setByte(parameterIndex, (x ? (byte) 1 : (byte) 0)); |
1585 | } |
1586 | |
1587 | /** |
1588 | * @see java.sql.PreparedStatement#setByte(int, byte) |
1589 | */ |
1590 | public void setByte(int parameterIndex, byte x) throws SQLException { |
1591 | checkClosed(); |
1592 | |
1593 | BindValue binding = getBinding(parameterIndex, false); |
1594 | setType(binding, MysqlDefs.FIELD_TYPE_TINY); |
1595 | |
1596 | binding.value = null; |
1597 | binding.byteBinding = x; |
1598 | binding.isNull = false; |
1599 | binding.isLongData = false; |
1600 | } |
1601 | |
1602 | /** |
1603 | * @see java.sql.PreparedStatement#setBytes(int, byte) |
1604 | */ |
1605 | public void setBytes(int parameterIndex, byte[] x) throws SQLException { |
1606 | checkClosed(); |
1607 | |
1608 | if (x == null) { |
1609 | setNull(parameterIndex, java.sql.Types.BINARY); |
1610 | } else { |
1611 | BindValue binding = getBinding(parameterIndex, false); |
1612 | setType(binding, MysqlDefs.FIELD_TYPE_VAR_STRING); |
1613 | |
1614 | binding.value = x; |
1615 | binding.isNull = false; |
1616 | binding.isLongData = false; |
1617 | } |
1618 | } |
1619 | |
1620 | /** |
1621 | * @see java.sql.PreparedStatement#setCharacterStream(int, java.io.Reader, |
1622 | * int) |
1623 | */ |
1624 | public void setCharacterStream(int parameterIndex, Reader reader, int length) |
1625 | throws SQLException { |
1626 | checkClosed(); |
1627 | |
1628 | if (reader == null) { |
1629 | setNull(parameterIndex, java.sql.Types.BINARY); |
1630 | } else { |
1631 | BindValue binding = getBinding(parameterIndex, true); |
1632 | setType(binding, MysqlDefs.FIELD_TYPE_BLOB); |
1633 | |
1634 | binding.value = reader; |
1635 | binding.isNull = false; |
1636 | binding.isLongData = true; |
1637 | |
1638 | if (this.connection.getUseStreamLengthsInPrepStmts()) { |
1639 | binding.bindLength = length; |
1640 | } else { |
1641 | binding.bindLength = -1; |
1642 | } |
1643 | } |
1644 | } |
1645 | |
1646 | /** |
1647 | * @see java.sql.PreparedStatement#setClob(int, java.sql.Clob) |
1648 | */ |
1649 | public void setClob(int parameterIndex, Clob x) throws SQLException { |
1650 | checkClosed(); |
1651 | |
1652 | if (x == null) { |
1653 | setNull(parameterIndex, java.sql.Types.BINARY); |
1654 | } else { |
1655 | BindValue binding = getBinding(parameterIndex, true); |
1656 | setType(binding, MysqlDefs.FIELD_TYPE_BLOB); |
1657 | |
1658 | binding.value = x.getCharacterStream(); |
1659 | binding.isNull = false; |
1660 | binding.isLongData = true; |
1661 | |
1662 | if (this.connection.getUseStreamLengthsInPrepStmts()) { |
1663 | binding.bindLength = x.length(); |
1664 | } else { |
1665 | binding.bindLength = -1; |
1666 | } |
1667 | } |
1668 | } |
1669 | |
1670 | /** |
1671 | * Set a parameter to a java.sql.Date value. The driver converts this to a |
1672 | * SQL DATE value when it sends it to the database. |
1673 | * |
1674 | * @param parameterIndex |
1675 | * the first parameter is 1, the second is 2, ... |
1676 | * @param x |
1677 | * the parameter value |
1678 | * |
1679 | * @exception SQLException |
1680 | * if a database-access error occurs. |
1681 | */ |
1682 | public void setDate(int parameterIndex, Date x) throws SQLException { |
1683 | setDate(parameterIndex, x, null); |
1684 | } |
1685 | |
1686 | /** |
1687 | * Set a parameter to a java.sql.Date value. The driver converts this to a |
1688 | * SQL DATE value when it sends it to the database. |
1689 | * |
1690 | * @param parameterIndex |
1691 | * the first parameter is 1, the second is 2, ... |
1692 | * @param x |
1693 | * the parameter value |
1694 | * @param cal |
1695 | * the calendar to interpret the date with |
1696 | * |
1697 | * @exception SQLException |
1698 | * if a database-access error occurs. |
1699 | */ |
1700 | public void setDate(int parameterIndex, Date x, Calendar cal) |
1701 | throws SQLException { |
1702 | if (x == null) { |
1703 | setNull(parameterIndex, java.sql.Types.DATE); |
1704 | } else { |
1705 | BindValue binding = getBinding(parameterIndex, false); |
1706 | setType(binding, MysqlDefs.FIELD_TYPE_DATE); |
1707 | |
1708 | binding.value = x; |
1709 | binding.isNull = false; |
1710 | binding.isLongData = false; |
1711 | } |
1712 | } |
1713 | |
1714 | /** |
1715 | * @see java.sql.PreparedStatement#setDouble(int, double) |
1716 | */ |
1717 | public void setDouble(int parameterIndex, double x) throws SQLException { |
1718 | checkClosed(); |
1719 | |
1720 | if (!this.connection.getAllowNanAndInf() |
1721 | && (x == Double.POSITIVE_INFINITY |
1722 | || x == Double.NEGATIVE_INFINITY || Double.isNaN(x))) { |
1723 | throw SQLError.createSQLException("'" + x |
1724 | + "' is not a valid numeric or approximate numeric value", |
1725 | SQLError.SQL_STATE_ILLEGAL_ARGUMENT); |
1726 | |
1727 | } |
1728 | |
1729 | BindValue binding = getBinding(parameterIndex, false); |
1730 | setType(binding, MysqlDefs.FIELD_TYPE_DOUBLE); |
1731 | |
1732 | binding.value = null; |
1733 | binding.doubleBinding = x; |
1734 | binding.isNull = false; |
1735 | binding.isLongData = false; |
1736 | } |
1737 | |
1738 | /** |
1739 | * @see java.sql.PreparedStatement#setFloat(int, float) |
1740 | */ |
1741 | public void setFloat(int parameterIndex, float x) throws SQLException { |
1742 | checkClosed(); |
1743 | |
1744 | BindValue binding = getBinding(parameterIndex, false); |
1745 | setType(binding, MysqlDefs.FIELD_TYPE_FLOAT); |
1746 | |
1747 | binding.value = null; |
1748 | binding.floatBinding = x; |
1749 | binding.isNull = false; |
1750 | binding.isLongData = false; |
1751 | } |
1752 | |
1753 | /** |
1754 | * @see java.sql.PreparedStatement#setInt(int, int) |
1755 | */ |
1756 | public void setInt(int parameterIndex, int x) throws SQLException { |
1757 | checkClosed(); |
1758 | |
1759 | BindValue binding = getBinding(parameterIndex, false); |
1760 | setType(binding, MysqlDefs.FIELD_TYPE_LONG); |
1761 | |
1762 | binding.value = null; |
1763 | binding.intBinding = x; |
1764 | binding.isNull = false; |
1765 | binding.isLongData = false; |
1766 | } |
1767 | |
1768 | /** |
1769 | * @see java.sql.PreparedStatement#setLong(int, long) |
1770 | */ |
1771 | public void setLong(int parameterIndex, long x) throws SQLException { |
1772 | checkClosed(); |
1773 | |
1774 | BindValue binding = getBinding(parameterIndex, false); |
1775 | setType(binding, MysqlDefs.FIELD_TYPE_LONGLONG); |
1776 | |
1777 | binding.value = null; |
1778 | binding.longBinding = x; |
1779 | binding.isNull = false; |
1780 | binding.isLongData = false; |
1781 | } |
1782 | |
1783 | /** |
1784 | * @see java.sql.PreparedStatement#setNull(int, int) |
1785 | */ |
1786 | public void setNull(int parameterIndex, int sqlType) throws SQLException { |
1787 | checkClosed(); |
1788 | |
1789 | BindValue binding = getBinding(parameterIndex, false); |
1790 | |
1791 | // |
1792 | // Don't re-set types, but use something if this |
1793 | // parameter was never specified |
1794 | // |
1795 | if (binding.bufferType == 0) { |
1796 | setType(binding, MysqlDefs.FIELD_TYPE_NULL); |
1797 | } |
1798 | |
1799 | binding.value = null; |
1800 | binding.isNull = true; |
1801 | binding.isLongData = false; |
1802 | } |
1803 | |
1804 | /** |
1805 | * @see java.sql.PreparedStatement#setNull(int, int, java.lang.String) |
1806 | */ |
1807 | public void setNull(int parameterIndex, int sqlType, String typeName) |
1808 | throws SQLException { |
1809 | checkClosed(); |
1810 | |
1811 | BindValue binding = getBinding(parameterIndex, false); |
1812 | |
1813 | // |
1814 | // Don't re-set types, but use something if this |
1815 | // parameter was never specified |
1816 | // |
1817 | if (binding.bufferType == 0) { |
1818 | setType(binding, MysqlDefs.FIELD_TYPE_NULL); |
1819 | } |
1820 | |
1821 | binding.value = null; |
1822 | binding.isNull = true; |
1823 | binding.isLongData = false; |
1824 | } |
1825 | |
1826 | /** |
1827 | * @see java.sql.PreparedStatement#setRef(int, java.sql.Ref) |
1828 | */ |
1829 | public void setRef(int i, Ref x) throws SQLException { |
1830 | throw new NotImplemented(); |
1831 | } |
1832 | |
1833 | /** |
1834 | * @see java.sql.PreparedStatement#setShort(int, short) |
1835 | */ |
1836 | public void setShort(int parameterIndex, short x) throws SQLException { |
1837 | checkClosed(); |
1838 | |
1839 | BindValue binding = getBinding(parameterIndex, false); |
1840 | setType(binding, MysqlDefs.FIELD_TYPE_SHORT); |
1841 | |
1842 | binding.value = null; |
1843 | binding.shortBinding = x; |
1844 | binding.isNull = false; |
1845 | binding.isLongData = false; |
1846 | } |
1847 | |
1848 | /** |
1849 | * @see java.sql.PreparedStatement#setString(int, java.lang.String) |
1850 | */ |
1851 | public void setString(int parameterIndex, String x) throws SQLException { |
1852 | checkClosed(); |
1853 | |
1854 | if (x == null) { |
1855 | setNull(parameterIndex, java.sql.Types.CHAR); |
1856 | } else { |
1857 | BindValue binding = getBinding(parameterIndex, false); |
1858 | |
1859 | setType(binding, this.stringTypeCode); |
1860 | |
1861 | binding.value = x; |
1862 | binding.isNull = false; |
1863 | binding.isLongData = false; |
1864 | } |
1865 | } |
1866 | |
1867 | /** |
1868 | * Set a parameter to a java.sql.Time value. |
1869 | * |
1870 | * @param parameterIndex |
1871 | * the first parameter is 1...)); |
1872 | * @param x |
1873 | * the parameter value |
1874 | * |
1875 | * @throws SQLException |
1876 | * if a database access error occurs |
1877 | */ |
1878 | public void setTime(int parameterIndex, java.sql.Time x) |
1879 | throws SQLException { |
1880 | setTimeInternal(parameterIndex, x, null, TimeZone.getDefault(), false); |
1881 | } |
1882 | |
1883 | /** |
1884 | * Set a parameter to a java.sql.Time value. The driver converts this to a |
1885 | * SQL TIME value when it sends it to the database, using the given |
1886 | * timezone. |
1887 | * |
1888 | * @param parameterIndex |
1889 | * the first parameter is 1...)); |
1890 | * @param x |
1891 | * the parameter value |
1892 | * @param cal |
1893 | * the timezone to use |
1894 | * |
1895 | * @throws SQLException |
1896 | * if a database access error occurs |
1897 | */ |
1898 | public void setTime(int parameterIndex, java.sql.Time x, Calendar cal) |
1899 | throws SQLException { |
1900 | setTimeInternal(parameterIndex, x, cal, cal.getTimeZone(), true); |
1901 | } |
1902 | |
1903 | /** |
1904 | * Set a parameter to a java.sql.Time value. The driver converts this to a |
1905 | * SQL TIME value when it sends it to the database, using the given |
1906 | * timezone. |
1907 | * |
1908 | * @param parameterIndex |
1909 | * the first parameter is 1...)); |
1910 | * @param x |
1911 | * the parameter value |
1912 | * @param tz |
1913 | * the timezone to use |
1914 | * |
1915 | * @throws SQLException |
1916 | * if a database access error occurs |
1917 | */ |
1918 | public void setTimeInternal(int parameterIndex, java.sql.Time x, |
1919 | Calendar targetCalendar, |
1920 | TimeZone tz, boolean rollForward) throws SQLException { |
1921 | if (x == null) { |
1922 | setNull(parameterIndex, java.sql.Types.TIME); |
1923 | } else { |
1924 | BindValue binding = getBinding(parameterIndex, false); |
1925 | setType(binding, MysqlDefs.FIELD_TYPE_TIME); |
1926 | |
1927 | Calendar sessionCalendar = getCalendarInstanceForSessionOrNew(); |
1928 | |
1929 | synchronized (sessionCalendar) { |
1930 | binding.value = TimeUtil.changeTimezone(this.connection, |
1931 | sessionCalendar, |
1932 | targetCalendar, |
1933 | x, tz, |
1934 | this.connection.getServerTimezoneTZ(), |
1935 | rollForward); |
1936 | } |
1937 | |
1938 | binding.isNull = false; |
1939 | binding.isLongData = false; |
1940 | } |
1941 | } |
1942 | |
1943 | /** |
1944 | * Set a parameter to a java.sql.Timestamp value. The driver converts this |
1945 | * to a SQL TIMESTAMP value when it sends it to the database. |
1946 | * |
1947 | * @param parameterIndex |
1948 | * the first parameter is 1, the second is 2, ... |
1949 | * @param x |
1950 | * the parameter value |
1951 | * |
1952 | * @throws SQLException |
1953 | * if a database-access error occurs. |
1954 | */ |
1955 | public void setTimestamp(int parameterIndex, java.sql.Timestamp x) |
1956 | throws SQLException { |
1957 | setTimestampInternal(parameterIndex, x, null, TimeZone.getDefault(), false); |
1958 | } |
1959 | |
1960 | /** |
1961 | * Set a parameter to a java.sql.Timestamp value. The driver converts this |
1962 | * to a SQL TIMESTAMP value when it sends it to the database. |
1963 | * |
1964 | * @param parameterIndex |
1965 | * the first parameter is 1, the second is 2, ... |
1966 | * @param x |
1967 | * the parameter value |
1968 | * @param cal |
1969 | * the timezone to use |
1970 | * |
1971 | * @throws SQLException |
1972 | * if a database-access error occurs. |
1973 | */ |
1974 | public void setTimestamp(int parameterIndex, java.sql.Timestamp x, |
1975 | Calendar cal) throws SQLException { |
1976 | setTimestampInternal(parameterIndex, x, cal, cal.getTimeZone(), true); |
1977 | } |
1978 | |
1979 | protected void setTimestampInternal(int parameterIndex, |
1980 | java.sql.Timestamp x, Calendar targetCalendar, |
1981 | TimeZone tz, boolean rollForward) |
1982 | throws SQLException { |
1983 | if (x == null) { |
1984 | setNull(parameterIndex, java.sql.Types.TIMESTAMP); |
1985 | } else { |
1986 | BindValue binding = getBinding(parameterIndex, false); |
1987 | setType(binding, MysqlDefs.FIELD_TYPE_DATETIME); |
1988 | |
1989 | Calendar sessionCalendar = this.connection.getUseJDBCCompliantTimezoneShift() ? |
1990 | this.connection.getUtcCalendar() : |
1991 | getCalendarInstanceForSessionOrNew(); |
1992 | |
1993 | synchronized (sessionCalendar) { |
1994 | binding.value = TimeUtil.changeTimezone(this.connection, |
1995 | sessionCalendar, |
1996 | targetCalendar, |
1997 | x, tz, |
1998 | this.connection.getServerTimezoneTZ(), |
1999 | rollForward); |
2000 | } |
2001 | |
2002 | binding.isNull = false; |
2003 | binding.isLongData = false; |
2004 | } |
2005 | } |
2006 | |
2007 | private void setType(BindValue oldValue, int bufferType) { |
2008 | if (oldValue.bufferType != bufferType) { |
2009 | this.sendTypesToServer = true; |
2010 | } |
2011 | |
2012 | oldValue.bufferType = bufferType; |
2013 | } |
2014 | |
2015 | /** |
2016 | * DOCUMENT ME! |
2017 | * |
2018 | * @param parameterIndex |
2019 | * DOCUMENT ME! |
2020 | * @param x |
2021 | * DOCUMENT ME! |
2022 | * @param length |
2023 | * DOCUMENT ME! |
2024 | * |
2025 | * @throws SQLException |
2026 | * DOCUMENT ME! |
2027 | * @throws NotImplemented |
2028 | * DOCUMENT ME! |
2029 | * |
2030 | * @see java.sql.PreparedStatement#setUnicodeStream(int, |
2031 | * java.io.InputStream, int) |
2032 | * @deprecated |
2033 | */ |
2034 | public void setUnicodeStream(int parameterIndex, InputStream x, int length) |
2035 | throws SQLException { |
2036 | checkClosed(); |
2037 | |
2038 | throw new NotImplemented(); |
2039 | } |
2040 | |
2041 | /** |
2042 | * @see java.sql.PreparedStatement#setURL(int, java.net.URL) |
2043 | */ |
2044 | public void setURL(int parameterIndex, URL x) throws SQLException { |
2045 | checkClosed(); |
2046 | |
2047 | setString(parameterIndex, x.toString()); |
2048 | } |
2049 | |
2050 | /** |
2051 | * Method storeBinding. |
2052 | * |
2053 | * @param packet |
2054 | * @param bindValue |
2055 | * @param mysql |
2056 | * DOCUMENT ME! |
2057 | * |
2058 | * @throws SQLException |
2059 | * DOCUMENT ME! |
2060 | */ |
2061 | private void storeBinding(Buffer packet, BindValue bindValue, MysqlIO mysql) |
2062 | throws SQLException { |
2063 | try { |
2064 | Object value = bindValue.value; |
2065 | |
2066 | // |
2067 | // Handle primitives first |
2068 | // |
2069 | switch (bindValue.bufferType) { |
2070 | |
2071 | case MysqlDefs.FIELD_TYPE_TINY: |
2072 | packet.writeByte(bindValue.byteBinding); |
2073 | return; |
2074 | case MysqlDefs.FIELD_TYPE_SHORT: |
2075 | packet.ensureCapacity(2); |
2076 | packet.writeInt(bindValue.shortBinding); |
2077 | return; |
2078 | case MysqlDefs.FIELD_TYPE_LONG: |
2079 | packet.ensureCapacity(4); |
2080 | packet.writeLong(bindValue.intBinding); |
2081 | return; |
2082 | case MysqlDefs.FIELD_TYPE_LONGLONG: |
2083 | packet.ensureCapacity(8); |
2084 | packet.writeLongLong(bindValue.longBinding); |
2085 | return; |
2086 | case MysqlDefs.FIELD_TYPE_FLOAT: |
2087 | packet.ensureCapacity(4); |
2088 | packet.writeFloat(bindValue.floatBinding); |
2089 | return; |
2090 | case MysqlDefs.FIELD_TYPE_DOUBLE: |
2091 | packet.ensureCapacity(8); |
2092 | packet.writeDouble(bindValue.doubleBinding); |
2093 | return; |
2094 | case MysqlDefs.FIELD_TYPE_TIME: |
2095 | storeTime(packet, (Time) value); |
2096 | return; |
2097 | case MysqlDefs.FIELD_TYPE_DATE: |
2098 | case MysqlDefs.FIELD_TYPE_DATETIME: |
2099 | case MysqlDefs.FIELD_TYPE_TIMESTAMP: |
2100 | storeDateTime(packet, (java.util.Date) value, mysql); |
2101 | return; |
2102 | case MysqlDefs.FIELD_TYPE_VAR_STRING: |
2103 | case MysqlDefs.FIELD_TYPE_STRING: |
2104 | case MysqlDefs.FIELD_TYPE_VARCHAR: |
2105 | if (value instanceof byte[]) { |
2106 | packet.writeLenBytes((byte[]) value); |
2107 | } else if (!this.isLoadDataQuery) { |
2108 | packet.writeLenString((String) value, this.charEncoding, |
2109 | this.connection.getServerCharacterEncoding(), |
2110 | this.charConverter, this.connection |
2111 | .parserKnowsUnicode(), |
2112 | this.connection); |
2113 | } else { |
2114 | packet.writeLenBytes(((String) value).getBytes()); |
2115 | } |
2116 | |
2117 | return; |
2118 | } |
2119 | |
2120 | |
2121 | } catch (UnsupportedEncodingException uEE) { |
2122 | throw SQLError.createSQLException(Messages |
2123 | .getString("ServerPreparedStatement.22") //$NON-NLS-1$ |
2124 | + this.connection.getEncoding() + "'", //$NON-NLS-1$ |
2125 | SQLError.SQL_STATE_GENERAL_ERROR); |
2126 | } |
2127 | } |
2128 | |
2129 | private void storeDataTime412AndOlder(Buffer intoBuf, java.util.Date dt) |
2130 | throws SQLException { |
2131 | |
2132 | Calendar sessionCalendar = getCalendarInstanceForSessionOrNew(); |
2133 | |
2134 | synchronized (sessionCalendar) { |
2135 | java.util.Date oldTime = sessionCalendar.getTime(); |
2136 | |
2137 | try { |
2138 | intoBuf.ensureCapacity(8); |
2139 | intoBuf.writeByte((byte) 7); // length |
2140 | |
2141 | sessionCalendar.setTime(dt); |
2142 | |
2143 | int year = sessionCalendar.get(Calendar.YEAR); |
2144 | int month = sessionCalendar.get(Calendar.MONTH) + 1; |
2145 | int date = sessionCalendar.get(Calendar.DATE); |
2146 | |
2147 | intoBuf.writeInt(year); |
2148 | intoBuf.writeByte((byte) month); |
2149 | intoBuf.writeByte((byte) date); |
2150 | |
2151 | if (dt instanceof java.sql.Date) { |
2152 | intoBuf.writeByte((byte) 0); |
2153 | intoBuf.writeByte((byte) 0); |
2154 | intoBuf.writeByte((byte) 0); |
2155 | } else { |
2156 | intoBuf.writeByte((byte) sessionCalendar |
2157 | .get(Calendar.HOUR_OF_DAY)); |
2158 | intoBuf.writeByte((byte) sessionCalendar |
2159 | .get(Calendar.MINUTE)); |
2160 | intoBuf.writeByte((byte) sessionCalendar |
2161 | .get(Calendar.SECOND)); |
2162 | } |
2163 | } finally { |
2164 | sessionCalendar.setTime(oldTime); |
2165 | } |
2166 | } |
2167 | } |
2168 | |
2169 | private void storeDateTime(Buffer intoBuf, java.util.Date dt, MysqlIO mysql) |
2170 | throws SQLException { |
2171 | if (this.connection.versionMeetsMinimum(4, 1, 3)) { |
2172 | storeDateTime413AndNewer(intoBuf, dt); |
2173 | } else { |
2174 | storeDataTime412AndOlder(intoBuf, dt); |
2175 | } |
2176 | } |
2177 | |
2178 | private void storeDateTime413AndNewer(Buffer intoBuf, java.util.Date dt) |
2179 | throws SQLException { |
2180 | Calendar sessionCalendar = (dt instanceof Timestamp && |
2181 | this.connection.getUseJDBCCompliantTimezoneShift()) ? |
2182 | this.connection.getUtcCalendar() : getCalendarInstanceForSessionOrNew(); |
2183 | |
2184 | synchronized (sessionCalendar) { |
2185 | java.util.Date oldTime = sessionCalendar.getTime(); |
2186 | |
2187 | |
2188 | try { |
2189 | sessionCalendar.setTime(dt); |
2190 | |
2191 | if (dt instanceof java.sql.Date) { |
2192 | sessionCalendar.set(Calendar.HOUR_OF_DAY, 0); |
2193 | sessionCalendar.set(Calendar.MINUTE, 0); |
2194 | sessionCalendar.set(Calendar.SECOND, 0); |
2195 | } |
2196 | |
2197 | byte length = (byte) 7; |
2198 | |
2199 | intoBuf.ensureCapacity(length); |
2200 | |
2201 | if (dt instanceof java.sql.Timestamp) { |
2202 | length = (byte) 11; |
2203 | } |
2204 | |
2205 | intoBuf.writeByte(length); // length |
2206 | |
2207 | int year = sessionCalendar.get(Calendar.YEAR); |
2208 | int month = sessionCalendar.get(Calendar.MONTH) + 1; |
2209 | int date = sessionCalendar.get(Calendar.DAY_OF_MONTH); |
2210 | |
2211 | intoBuf.writeInt(year); |
2212 | intoBuf.writeByte((byte) month); |
2213 | intoBuf.writeByte((byte) date); |
2214 | |
2215 | if (dt instanceof java.sql.Date) { |
2216 | intoBuf.writeByte((byte) 0); |
2217 | intoBuf.writeByte((byte) 0); |
2218 | intoBuf.writeByte((byte) 0); |
2219 | } else { |
2220 | intoBuf.writeByte((byte) sessionCalendar |
2221 | .get(Calendar.HOUR_OF_DAY)); |
2222 | intoBuf.writeByte((byte) sessionCalendar |
2223 | .get(Calendar.MINUTE)); |
2224 | intoBuf.writeByte((byte) sessionCalendar |
2225 | .get(Calendar.SECOND)); |
2226 | } |
2227 | |
2228 | if (length == 11) { |
2229 | intoBuf.writeLong(((java.sql.Timestamp) dt).getNanos()); |
2230 | } |
2231 | |
2232 | } finally { |
2233 | sessionCalendar.setTime(oldTime); |
2234 | } |
2235 | } |
2236 | } |
2237 | |
2238 | // |
2239 | // TO DO: Investigate using NIO to do this faster |
2240 | // |
2241 | private void storeReader(MysqlIO mysql, int parameterIndex, Buffer packet, |
2242 | Reader inStream) throws SQLException { |
2243 | String forcedEncoding = this.connection.getClobCharacterEncoding(); |
2244 | |
2245 | String clobEncoding = |
2246 | (forcedEncoding == null ? this.connection.getEncoding() : forcedEncoding); |
2247 | |
2248 | int maxBytesChar = 2; |
2249 | |
2250 | if (clobEncoding != null) { |
2251 | if (!clobEncoding.equals("UTF-16")) { |
2252 | maxBytesChar = this.connection.getMaxBytesPerChar(clobEncoding); |
2253 | |
2254 | if (maxBytesChar == 1) { |
2255 | maxBytesChar = 2; // for safety |
2256 | } |
2257 | } else { |
2258 | maxBytesChar = 4; |
2259 | } |
2260 | } |
2261 | |
2262 | char[] buf = new char[BLOB_STREAM_READ_BUF_SIZE / maxBytesChar]; |
2263 | |
2264 | int numRead = 0; |
2265 | |
2266 | int bytesInPacket = 0; |
2267 | int totalBytesRead = 0; |
2268 | int bytesReadAtLastSend = 0; |
2269 | int packetIsFullAt = this.connection.getBlobSendChunkSize(); |
2270 | |
2271 | |
2272 | |
2273 | try { |
2274 | packet.clear(); |
2275 | packet.writeByte((byte) MysqlDefs.COM_LONG_DATA); |
2276 | packet.writeLong(this.serverStatementId); |
2277 | packet.writeInt((parameterIndex)); |
2278 | |
2279 | boolean readAny = false; |
2280 | |
2281 | while ((numRead = inStream.read(buf)) != -1) { |
2282 | readAny = true; |
2283 | |
2284 | byte[] valueAsBytes = StringUtils.getBytes(buf, null, |
2285 | clobEncoding, this.connection |
2286 | .getServerCharacterEncoding(), 0, numRead, |
2287 | this.connection.parserKnowsUnicode()); |
2288 | |
2289 | packet.writeBytesNoNull(valueAsBytes, 0, valueAsBytes.length); |
2290 | |
2291 | bytesInPacket += valueAsBytes.length; |
2292 | totalBytesRead += valueAsBytes.length; |
2293 | |
2294 | if (bytesInPacket >= packetIsFullAt) { |
2295 | bytesReadAtLastSend = totalBytesRead; |
2296 | |
2297 | mysql.sendCommand(MysqlDefs.COM_LONG_DATA, null, packet, |
2298 | true, null); |
2299 | |
2300 | bytesInPacket = 0; |
2301 | packet.clear(); |
2302 | packet.writeByte((byte) MysqlDefs.COM_LONG_DATA); |
2303 | packet.writeLong(this.serverStatementId); |
2304 | packet.writeInt((parameterIndex)); |
2305 | } |
2306 | } |
2307 | |
2308 | if (totalBytesRead != bytesReadAtLastSend) { |
2309 | mysql.sendCommand(MysqlDefs.COM_LONG_DATA, null, packet, true, |
2310 | null); |
2311 | } |
2312 | |
2313 | if (!readAny) { |
2314 | mysql.sendCommand(MysqlDefs.COM_LONG_DATA, null, packet, true, |
2315 | null); |
2316 | } |
2317 | } catch (IOException ioEx) { |
2318 | throw SQLError.createSQLException(Messages |
2319 | .getString("ServerPreparedStatement.24") //$NON-NLS-1$ |
2320 | + ioEx.toString(), SQLError.SQL_STATE_GENERAL_ERROR); |
2321 | } finally { |
2322 | if (this.connection.getAutoClosePStmtStreams()) { |
2323 | if (inStream != null) { |
2324 | try { |
2325 | inStream.close(); |
2326 | } catch (IOException ioEx) { |
2327 | ; // ignore |
2328 | } |
2329 | } |
2330 | } |
2331 | } |
2332 | } |
2333 | |
2334 | private void storeStream(MysqlIO mysql, int parameterIndex, Buffer packet, |
2335 | InputStream inStream) throws SQLException { |
2336 | byte[] buf = new byte[BLOB_STREAM_READ_BUF_SIZE]; |
2337 | |
2338 | int numRead = 0; |
2339 | |
2340 | try { |
2341 | int bytesInPacket = 0; |
2342 | int totalBytesRead = 0; |
2343 | int bytesReadAtLastSend = 0; |
2344 | int packetIsFullAt = this.connection.getBlobSendChunkSize(); |
2345 | |
2346 | packet.clear(); |
2347 | packet.writeByte((byte) MysqlDefs.COM_LONG_DATA); |
2348 | packet.writeLong(this.serverStatementId); |
2349 | packet.writeInt((parameterIndex)); |
2350 | |
2351 | boolean readAny = false; |
2352 | |
2353 | while ((numRead = inStream.read(buf)) != -1) { |
2354 | |
2355 | readAny = true; |
2356 | |
2357 | packet.writeBytesNoNull(buf, 0, numRead); |
2358 | bytesInPacket += numRead; |
2359 | totalBytesRead += numRead; |
2360 | |
2361 | if (bytesInPacket >= packetIsFullAt) { |
2362 | bytesReadAtLastSend = totalBytesRead; |
2363 | |
2364 | mysql.sendCommand(MysqlDefs.COM_LONG_DATA, null, packet, |
2365 | true, null); |
2366 | |
2367 | bytesInPacket = 0; |
2368 | packet.clear(); |
2369 | packet.writeByte((byte) MysqlDefs.COM_LONG_DATA); |
2370 | packet.writeLong(this.serverStatementId); |
2371 | packet.writeInt((parameterIndex)); |
2372 | } |
2373 | } |
2374 | |
2375 | if (totalBytesRead != bytesReadAtLastSend) { |
2376 | mysql.sendCommand(MysqlDefs.COM_LONG_DATA, null, packet, true, |
2377 | null); |
2378 | } |
2379 | |
2380 | if (!readAny) { |
2381 | mysql.sendCommand(MysqlDefs.COM_LONG_DATA, null, packet, true, |
2382 | null); |
2383 | } |
2384 | } catch (IOException ioEx) { |
2385 | throw SQLError.createSQLException(Messages |
2386 | .getString("ServerPreparedStatement.25") //$NON-NLS-1$ |
2387 | + ioEx.toString(), SQLError.SQL_STATE_GENERAL_ERROR); |
2388 | } finally { |
2389 | if (this.connection.getAutoClosePStmtStreams()) { |
2390 | if (inStream != null) { |
2391 | try { |
2392 | inStream.close(); |
2393 | } catch (IOException ioEx) { |
2394 | ; // ignore |
2395 | } |
2396 | } |
2397 | } |
2398 | } |
2399 | } |
2400 | |
2401 | /** |
2402 | * @see java.lang.Object#toString() |
2403 | */ |
2404 | public String toString() { |
2405 | StringBuffer toStringBuf = new StringBuffer(); |
2406 | |
2407 | toStringBuf.append("com.mysql.jdbc.ServerPreparedStatement["); //$NON-NLS-1$ |
2408 | toStringBuf.append(this.serverStatementId); |
2409 | toStringBuf.append("] - "); //$NON-NLS-1$ |
2410 | |
2411 | try { |
2412 | toStringBuf.append(asSql()); |
2413 | } catch (SQLException sqlEx) { |
2414 | toStringBuf.append(Messages.getString("ServerPreparedStatement.6")); //$NON-NLS-1$ |
2415 | toStringBuf.append(sqlEx); |
2416 | } |
2417 | |
2418 | return toStringBuf.toString(); |
2419 | } |
2420 | |
2421 | protected long getServerStatementId() { |
2422 | return serverStatementId; |
2423 | } |
2424 | } |