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