1 | /* |
2 | * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0, |
3 | * and the EPL 1.0 (http://h2database.com/html/license.html). |
4 | * Initial Developer: H2 Group |
5 | */ |
6 | package org.h2.jdbc; |
7 | |
8 | import java.io.ByteArrayInputStream; |
9 | import java.io.InputStream; |
10 | import java.io.InputStreamReader; |
11 | import java.io.Reader; |
12 | import java.sql.Array; |
13 | import java.sql.Blob; |
14 | import java.sql.CallableStatement; |
15 | import java.sql.Clob; |
16 | import java.sql.Connection; |
17 | import java.sql.DatabaseMetaData; |
18 | import java.sql.NClob; |
19 | import java.sql.PreparedStatement; |
20 | import java.sql.ResultSet; |
21 | import java.sql.SQLClientInfoException; |
22 | import java.sql.SQLException; |
23 | import java.sql.SQLWarning; |
24 | import java.sql.SQLXML; |
25 | import java.sql.Savepoint; |
26 | import java.sql.Statement; |
27 | import java.sql.Struct; |
28 | import java.util.ArrayList; |
29 | import java.util.Map; |
30 | import java.util.Properties; |
31 | |
32 | import org.h2.api.ErrorCode; |
33 | import org.h2.command.CommandInterface; |
34 | import org.h2.engine.ConnectionInfo; |
35 | import org.h2.engine.Constants; |
36 | import org.h2.engine.SessionInterface; |
37 | import org.h2.engine.SessionRemote; |
38 | import org.h2.engine.SysProperties; |
39 | import org.h2.message.DbException; |
40 | import org.h2.message.TraceObject; |
41 | import org.h2.result.ResultInterface; |
42 | import org.h2.util.CloseWatcher; |
43 | import org.h2.util.JdbcUtils; |
44 | import org.h2.util.Utils; |
45 | import org.h2.value.CompareMode; |
46 | import org.h2.value.Value; |
47 | import org.h2.value.ValueInt; |
48 | import org.h2.value.ValueNull; |
49 | import org.h2.value.ValueString; |
50 | |
51 | //## Java 1.7 ## |
52 | import java.util.concurrent.Executor; |
53 | //*/ |
54 | |
55 | /** |
56 | * <p> |
57 | * Represents a connection (session) to a database. |
58 | * </p> |
59 | * <p> |
60 | * Thread safety: the connection is thread-safe, because access |
61 | * is synchronized. However, for compatibility with other databases, a |
62 | * connection should only be used in one thread at any time. |
63 | * </p> |
64 | */ |
65 | public class JdbcConnection extends TraceObject implements Connection { |
66 | |
67 | private static boolean keepOpenStackTrace; |
68 | |
69 | private final String url; |
70 | private final String user; |
71 | |
72 | // ResultSet.HOLD_CURSORS_OVER_COMMIT |
73 | private int holdability = 1; |
74 | |
75 | private SessionInterface session; |
76 | private CommandInterface commit, rollback; |
77 | private CommandInterface getReadOnly, getGeneratedKeys; |
78 | private CommandInterface setLockMode, getLockMode; |
79 | private CommandInterface setQueryTimeout, getQueryTimeout; |
80 | |
81 | private int savepointId; |
82 | private String catalog; |
83 | private Statement executingStatement; |
84 | private final CompareMode compareMode = CompareMode.getInstance(null, 0); |
85 | private final CloseWatcher watcher; |
86 | private int queryTimeoutCache = -1; |
87 | |
88 | /** |
89 | * INTERNAL |
90 | */ |
91 | public JdbcConnection(String url, Properties info) throws SQLException { |
92 | this(new ConnectionInfo(url, info), true); |
93 | } |
94 | |
95 | /** |
96 | * INTERNAL |
97 | */ |
98 | public JdbcConnection(ConnectionInfo ci, boolean useBaseDir) |
99 | throws SQLException { |
100 | try { |
101 | if (useBaseDir) { |
102 | String baseDir = SysProperties.getBaseDir(); |
103 | if (baseDir != null) { |
104 | ci.setBaseDir(baseDir); |
105 | } |
106 | } |
107 | // this will return an embedded or server connection |
108 | session = new SessionRemote(ci).connectEmbeddedOrServer(false); |
109 | trace = session.getTrace(); |
110 | int id = getNextId(TraceObject.CONNECTION); |
111 | setTrace(trace, TraceObject.CONNECTION, id); |
112 | this.user = ci.getUserName(); |
113 | if (isInfoEnabled()) { |
114 | trace.infoCode("Connection " + getTraceObjectName() |
115 | + " = DriverManager.getConnection(" + quote(ci.getOriginalURL()) |
116 | + ", " + quote(user) + ", \"\");"); |
117 | } |
118 | this.url = ci.getURL(); |
119 | closeOld(); |
120 | watcher = CloseWatcher.register(this, session, keepOpenStackTrace); |
121 | } catch (Exception e) { |
122 | throw logAndConvert(e); |
123 | } |
124 | } |
125 | |
126 | /** |
127 | * INTERNAL |
128 | */ |
129 | public JdbcConnection(JdbcConnection clone) { |
130 | this.session = clone.session; |
131 | trace = session.getTrace(); |
132 | int id = getNextId(TraceObject.CONNECTION); |
133 | setTrace(trace, TraceObject.CONNECTION, id); |
134 | this.user = clone.user; |
135 | this.url = clone.url; |
136 | this.catalog = clone.catalog; |
137 | this.commit = clone.commit; |
138 | this.getGeneratedKeys = clone.getGeneratedKeys; |
139 | this.getLockMode = clone.getLockMode; |
140 | this.getQueryTimeout = clone.getQueryTimeout; |
141 | this.getReadOnly = clone.getReadOnly; |
142 | this.rollback = clone.rollback; |
143 | this.watcher = null; |
144 | } |
145 | |
146 | /** |
147 | * INTERNAL |
148 | */ |
149 | public JdbcConnection(SessionInterface session, String user, String url) { |
150 | this.session = session; |
151 | trace = session.getTrace(); |
152 | int id = getNextId(TraceObject.CONNECTION); |
153 | setTrace(trace, TraceObject.CONNECTION, id); |
154 | this.user = user; |
155 | this.url = url; |
156 | this.watcher = null; |
157 | } |
158 | |
159 | private void closeOld() { |
160 | while (true) { |
161 | CloseWatcher w = CloseWatcher.pollUnclosed(); |
162 | if (w == null) { |
163 | break; |
164 | } |
165 | try { |
166 | w.getCloseable().close(); |
167 | } catch (Exception e) { |
168 | trace.error(e, "closing session"); |
169 | } |
170 | // there was an unclosed object - |
171 | // keep the stack trace from now on |
172 | keepOpenStackTrace = true; |
173 | String s = w.getOpenStackTrace(); |
174 | Exception ex = DbException.get(ErrorCode.TRACE_CONNECTION_NOT_CLOSED); |
175 | trace.error(ex, s); |
176 | } |
177 | } |
178 | |
179 | /** |
180 | * Creates a new statement. |
181 | * |
182 | * @return the new statement |
183 | * @throws SQLException if the connection is closed |
184 | */ |
185 | @Override |
186 | public Statement createStatement() throws SQLException { |
187 | try { |
188 | int id = getNextId(TraceObject.STATEMENT); |
189 | if (isDebugEnabled()) { |
190 | debugCodeAssign("Statement", TraceObject.STATEMENT, id, "createStatement()"); |
191 | } |
192 | checkClosed(); |
193 | return new JdbcStatement(this, id, |
194 | ResultSet.TYPE_FORWARD_ONLY, |
195 | Constants.DEFAULT_RESULT_SET_CONCURRENCY, false); |
196 | } catch (Exception e) { |
197 | throw logAndConvert(e); |
198 | } |
199 | } |
200 | |
201 | /** |
202 | * Creates a statement with the specified result set type and concurrency. |
203 | * |
204 | * @param resultSetType the result set type (ResultSet.TYPE_*) |
205 | * @param resultSetConcurrency the concurrency (ResultSet.CONCUR_*) |
206 | * @return the statement |
207 | * @throws SQLException |
208 | * if the connection is closed or the result set type or |
209 | * concurrency are not supported |
210 | */ |
211 | @Override |
212 | public Statement createStatement(int resultSetType, int resultSetConcurrency) |
213 | throws SQLException { |
214 | try { |
215 | int id = getNextId(TraceObject.STATEMENT); |
216 | if (isDebugEnabled()) { |
217 | debugCodeAssign("Statement", TraceObject.STATEMENT, id, |
218 | "createStatement(" + resultSetType + ", " + resultSetConcurrency + ")"); |
219 | } |
220 | checkTypeConcurrency(resultSetType, resultSetConcurrency); |
221 | checkClosed(); |
222 | return new JdbcStatement(this, id, resultSetType, resultSetConcurrency, false); |
223 | } catch (Exception e) { |
224 | throw logAndConvert(e); |
225 | } |
226 | } |
227 | |
228 | /** |
229 | * Creates a statement with the specified result set type, concurrency, and |
230 | * holdability. |
231 | * |
232 | * @param resultSetType the result set type (ResultSet.TYPE_*) |
233 | * @param resultSetConcurrency the concurrency (ResultSet.CONCUR_*) |
234 | * @param resultSetHoldability the holdability (ResultSet.HOLD* / CLOSE*) |
235 | * @return the statement |
236 | * @throws SQLException if the connection is closed or the result set type, |
237 | * concurrency, or holdability are not supported |
238 | */ |
239 | @Override |
240 | public Statement createStatement(int resultSetType, |
241 | int resultSetConcurrency, int resultSetHoldability) |
242 | throws SQLException { |
243 | try { |
244 | int id = getNextId(TraceObject.STATEMENT); |
245 | if (isDebugEnabled()) { |
246 | debugCodeAssign("Statement", TraceObject.STATEMENT, id, |
247 | "createStatement(" + resultSetType + ", " + |
248 | resultSetConcurrency + ", " + resultSetHoldability + ")"); |
249 | } |
250 | checkTypeConcurrency(resultSetType, resultSetConcurrency); |
251 | checkHoldability(resultSetHoldability); |
252 | checkClosed(); |
253 | return new JdbcStatement(this, id, resultSetType, resultSetConcurrency, false); |
254 | } catch (Exception e) { |
255 | throw logAndConvert(e); |
256 | } |
257 | } |
258 | |
259 | /** |
260 | * Creates a new prepared statement. |
261 | * |
262 | * @param sql the SQL statement |
263 | * @return the prepared statement |
264 | * @throws SQLException if the connection is closed |
265 | */ |
266 | @Override |
267 | public PreparedStatement prepareStatement(String sql) throws SQLException { |
268 | try { |
269 | int id = getNextId(TraceObject.PREPARED_STATEMENT); |
270 | if (isDebugEnabled()) { |
271 | debugCodeAssign("PreparedStatement", |
272 | TraceObject.PREPARED_STATEMENT, id, |
273 | "prepareStatement(" + quote(sql) + ")"); |
274 | } |
275 | checkClosed(); |
276 | sql = translateSQL(sql); |
277 | return new JdbcPreparedStatement(this, sql, id, |
278 | ResultSet.TYPE_FORWARD_ONLY, |
279 | Constants.DEFAULT_RESULT_SET_CONCURRENCY, false); |
280 | } catch (Exception e) { |
281 | throw logAndConvert(e); |
282 | } |
283 | } |
284 | |
285 | /** |
286 | * Prepare a statement that will automatically close when the result set is |
287 | * closed. This method is used to retrieve database meta data. |
288 | * |
289 | * @param sql the SQL statement |
290 | * @return the prepared statement |
291 | */ |
292 | PreparedStatement prepareAutoCloseStatement(String sql) throws SQLException { |
293 | try { |
294 | int id = getNextId(TraceObject.PREPARED_STATEMENT); |
295 | if (isDebugEnabled()) { |
296 | debugCodeAssign("PreparedStatement", |
297 | TraceObject.PREPARED_STATEMENT, id, |
298 | "prepareStatement(" + quote(sql) + ")"); |
299 | } |
300 | checkClosed(); |
301 | sql = translateSQL(sql); |
302 | return new JdbcPreparedStatement(this, sql, id, |
303 | ResultSet.TYPE_FORWARD_ONLY, |
304 | Constants.DEFAULT_RESULT_SET_CONCURRENCY, true); |
305 | } catch (Exception e) { |
306 | throw logAndConvert(e); |
307 | } |
308 | } |
309 | |
310 | /** |
311 | * Gets the database meta data for this database. |
312 | * |
313 | * @return the database meta data |
314 | * @throws SQLException if the connection is closed |
315 | */ |
316 | @Override |
317 | public DatabaseMetaData getMetaData() throws SQLException { |
318 | try { |
319 | int id = getNextId(TraceObject.DATABASE_META_DATA); |
320 | if (isDebugEnabled()) { |
321 | debugCodeAssign("DatabaseMetaData", |
322 | TraceObject.DATABASE_META_DATA, id, "getMetaData()"); |
323 | } |
324 | checkClosed(); |
325 | return new JdbcDatabaseMetaData(this, trace, id); |
326 | } catch (Exception e) { |
327 | throw logAndConvert(e); |
328 | } |
329 | } |
330 | |
331 | /** |
332 | * INTERNAL |
333 | */ |
334 | public SessionInterface getSession() { |
335 | return session; |
336 | } |
337 | |
338 | /** |
339 | * Closes this connection. All open statements, prepared statements and |
340 | * result sets that where created by this connection become invalid after |
341 | * calling this method. If there is an uncommitted transaction, it will be |
342 | * rolled back. |
343 | */ |
344 | @Override |
345 | public synchronized void close() throws SQLException { |
346 | try { |
347 | debugCodeCall("close"); |
348 | if (session == null) { |
349 | return; |
350 | } |
351 | CloseWatcher.unregister(watcher); |
352 | session.cancel(); |
353 | if (executingStatement != null) { |
354 | try { |
355 | executingStatement.cancel(); |
356 | } catch (NullPointerException e) { |
357 | // ignore |
358 | } |
359 | } |
360 | synchronized (session) { |
361 | try { |
362 | if (!session.isClosed()) { |
363 | try { |
364 | if (session.hasPendingTransaction()) { |
365 | // roll back unless that would require to |
366 | // re-connect (the transaction can't be rolled |
367 | // back after re-connecting) |
368 | if (!session.isReconnectNeeded(true)) { |
369 | try { |
370 | rollbackInternal(); |
371 | } catch (DbException e) { |
372 | // ignore if the connection is broken |
373 | // right now |
374 | if (e.getErrorCode() != |
375 | ErrorCode.CONNECTION_BROKEN_1) { |
376 | throw e; |
377 | } |
378 | } |
379 | } |
380 | session.afterWriting(); |
381 | } |
382 | closePreparedCommands(); |
383 | } finally { |
384 | session.close(); |
385 | } |
386 | } |
387 | } finally { |
388 | session = null; |
389 | } |
390 | } |
391 | } catch (Exception e) { |
392 | throw logAndConvert(e); |
393 | } |
394 | } |
395 | |
396 | private void closePreparedCommands() { |
397 | commit = closeAndSetNull(commit); |
398 | rollback = closeAndSetNull(rollback); |
399 | getReadOnly = closeAndSetNull(getReadOnly); |
400 | getGeneratedKeys = closeAndSetNull(getGeneratedKeys); |
401 | getLockMode = closeAndSetNull(getLockMode); |
402 | setLockMode = closeAndSetNull(setLockMode); |
403 | getQueryTimeout = closeAndSetNull(getQueryTimeout); |
404 | setQueryTimeout = closeAndSetNull(setQueryTimeout); |
405 | } |
406 | |
407 | private static CommandInterface closeAndSetNull(CommandInterface command) { |
408 | if (command != null) { |
409 | command.close(); |
410 | } |
411 | return null; |
412 | } |
413 | |
414 | /** |
415 | * Switches auto commit on or off. Enabling it commits an uncommitted |
416 | * transaction, if there is one. |
417 | * |
418 | * @param autoCommit true for auto commit on, false for off |
419 | * @throws SQLException if the connection is closed |
420 | */ |
421 | @Override |
422 | public synchronized void setAutoCommit(boolean autoCommit) |
423 | throws SQLException { |
424 | try { |
425 | if (isDebugEnabled()) { |
426 | debugCode("setAutoCommit(" + autoCommit + ");"); |
427 | } |
428 | checkClosed(); |
429 | if (autoCommit && !session.getAutoCommit()) { |
430 | commit(); |
431 | } |
432 | session.setAutoCommit(autoCommit); |
433 | } catch (Exception e) { |
434 | throw logAndConvert(e); |
435 | } |
436 | } |
437 | |
438 | /** |
439 | * Gets the current setting for auto commit. |
440 | * |
441 | * @return true for on, false for off |
442 | * @throws SQLException if the connection is closed |
443 | */ |
444 | @Override |
445 | public synchronized boolean getAutoCommit() throws SQLException { |
446 | try { |
447 | checkClosed(); |
448 | debugCodeCall("getAutoCommit"); |
449 | return session.getAutoCommit(); |
450 | } catch (Exception e) { |
451 | throw logAndConvert(e); |
452 | } |
453 | } |
454 | |
455 | /** |
456 | * Commits the current transaction. This call has only an effect if auto |
457 | * commit is switched off. |
458 | * |
459 | * @throws SQLException if the connection is closed |
460 | */ |
461 | @Override |
462 | public synchronized void commit() throws SQLException { |
463 | try { |
464 | debugCodeCall("commit"); |
465 | checkClosedForWrite(); |
466 | try { |
467 | commit = prepareCommand("COMMIT", commit); |
468 | commit.executeUpdate(); |
469 | } finally { |
470 | afterWriting(); |
471 | } |
472 | } catch (Exception e) { |
473 | throw logAndConvert(e); |
474 | } |
475 | } |
476 | |
477 | /** |
478 | * Rolls back the current transaction. This call has only an effect if auto |
479 | * commit is switched off. |
480 | * |
481 | * @throws SQLException if the connection is closed |
482 | */ |
483 | @Override |
484 | public synchronized void rollback() throws SQLException { |
485 | try { |
486 | debugCodeCall("rollback"); |
487 | checkClosedForWrite(); |
488 | try { |
489 | rollbackInternal(); |
490 | } finally { |
491 | afterWriting(); |
492 | } |
493 | } catch (Exception e) { |
494 | throw logAndConvert(e); |
495 | } |
496 | } |
497 | |
498 | /** |
499 | * Returns true if this connection has been closed. |
500 | * |
501 | * @return true if close was called |
502 | */ |
503 | @Override |
504 | public boolean isClosed() throws SQLException { |
505 | try { |
506 | debugCodeCall("isClosed"); |
507 | return session == null || session.isClosed(); |
508 | } catch (Exception e) { |
509 | throw logAndConvert(e); |
510 | } |
511 | } |
512 | |
513 | /** |
514 | * Translates a SQL statement into the database grammar. |
515 | * |
516 | * @param sql the SQL statement with or without JDBC escape sequences |
517 | * @return the translated statement |
518 | * @throws SQLException if the connection is closed |
519 | */ |
520 | @Override |
521 | public String nativeSQL(String sql) throws SQLException { |
522 | try { |
523 | debugCodeCall("nativeSQL", sql); |
524 | checkClosed(); |
525 | return translateSQL(sql); |
526 | } catch (Exception e) { |
527 | throw logAndConvert(e); |
528 | } |
529 | } |
530 | |
531 | /** |
532 | * According to the JDBC specs, this setting is only a hint to the database |
533 | * to enable optimizations - it does not cause writes to be prohibited. |
534 | * |
535 | * @param readOnly ignored |
536 | * @throws SQLException if the connection is closed |
537 | */ |
538 | @Override |
539 | public void setReadOnly(boolean readOnly) throws SQLException { |
540 | try { |
541 | if (isDebugEnabled()) { |
542 | debugCode("setReadOnly(" + readOnly + ");"); |
543 | } |
544 | checkClosed(); |
545 | } catch (Exception e) { |
546 | throw logAndConvert(e); |
547 | } |
548 | } |
549 | |
550 | /** |
551 | * Returns true if the database is read-only. |
552 | * |
553 | * @return if the database is read-only |
554 | * @throws SQLException if the connection is closed |
555 | */ |
556 | @Override |
557 | public boolean isReadOnly() throws SQLException { |
558 | try { |
559 | debugCodeCall("isReadOnly"); |
560 | checkClosed(); |
561 | getReadOnly = prepareCommand("CALL READONLY()", getReadOnly); |
562 | ResultInterface result = getReadOnly.executeQuery(0, false); |
563 | result.next(); |
564 | boolean readOnly = result.currentRow()[0].getBoolean().booleanValue(); |
565 | return readOnly; |
566 | } catch (Exception e) { |
567 | throw logAndConvert(e); |
568 | } |
569 | } |
570 | |
571 | /** |
572 | * Set the default catalog name. This call is ignored. |
573 | * |
574 | * @param catalog ignored |
575 | * @throws SQLException if the connection is closed |
576 | */ |
577 | @Override |
578 | public void setCatalog(String catalog) throws SQLException { |
579 | try { |
580 | debugCodeCall("setCatalog", catalog); |
581 | checkClosed(); |
582 | } catch (Exception e) { |
583 | throw logAndConvert(e); |
584 | } |
585 | } |
586 | |
587 | /** |
588 | * Gets the current catalog name. |
589 | * |
590 | * @return the catalog name |
591 | * @throws SQLException if the connection is closed |
592 | */ |
593 | @Override |
594 | public String getCatalog() throws SQLException { |
595 | try { |
596 | debugCodeCall("getCatalog"); |
597 | checkClosed(); |
598 | if (catalog == null) { |
599 | CommandInterface cat = prepareCommand("CALL DATABASE()", Integer.MAX_VALUE); |
600 | ResultInterface result = cat.executeQuery(0, false); |
601 | result.next(); |
602 | catalog = result.currentRow()[0].getString(); |
603 | cat.close(); |
604 | } |
605 | return catalog; |
606 | } catch (Exception e) { |
607 | throw logAndConvert(e); |
608 | } |
609 | } |
610 | |
611 | /** |
612 | * Gets the first warning reported by calls on this object. |
613 | * |
614 | * @return null |
615 | */ |
616 | @Override |
617 | public SQLWarning getWarnings() throws SQLException { |
618 | try { |
619 | debugCodeCall("getWarnings"); |
620 | checkClosed(); |
621 | return null; |
622 | } catch (Exception e) { |
623 | throw logAndConvert(e); |
624 | } |
625 | } |
626 | |
627 | /** |
628 | * Clears all warnings. |
629 | */ |
630 | @Override |
631 | public void clearWarnings() throws SQLException { |
632 | try { |
633 | debugCodeCall("clearWarnings"); |
634 | checkClosed(); |
635 | } catch (Exception e) { |
636 | throw logAndConvert(e); |
637 | } |
638 | } |
639 | |
640 | /** |
641 | * Creates a prepared statement with the specified result set type and |
642 | * concurrency. |
643 | * |
644 | * @param sql the SQL statement |
645 | * @param resultSetType the result set type (ResultSet.TYPE_*) |
646 | * @param resultSetConcurrency the concurrency (ResultSet.CONCUR_*) |
647 | * @return the prepared statement |
648 | * @throws SQLException |
649 | * if the connection is closed or the result set type or |
650 | * concurrency are not supported |
651 | */ |
652 | @Override |
653 | public PreparedStatement prepareStatement(String sql, int resultSetType, |
654 | int resultSetConcurrency) throws SQLException { |
655 | try { |
656 | int id = getNextId(TraceObject.PREPARED_STATEMENT); |
657 | if (isDebugEnabled()) { |
658 | debugCodeAssign("PreparedStatement", TraceObject.PREPARED_STATEMENT, id, |
659 | "prepareStatement(" + quote(sql) + ", " + |
660 | resultSetType + ", " + resultSetConcurrency + |
661 | ")"); |
662 | } |
663 | checkTypeConcurrency(resultSetType, resultSetConcurrency); |
664 | checkClosed(); |
665 | sql = translateSQL(sql); |
666 | return new JdbcPreparedStatement(this, sql, id, resultSetType, |
667 | resultSetConcurrency, false); |
668 | } catch (Exception e) { |
669 | throw logAndConvert(e); |
670 | } |
671 | } |
672 | |
673 | /** |
674 | * Changes the current transaction isolation level. Calling this method will |
675 | * commit an open transaction, even if the new level is the same as the old |
676 | * one, except if the level is not supported. Internally, this method calls |
677 | * SET LOCK_MODE, which affects all connections. |
678 | * The following isolation levels are supported: |
679 | * <ul> |
680 | * <li> Connection.TRANSACTION_READ_UNCOMMITTED = SET LOCK_MODE 0: no |
681 | * locking (should only be used for testing). </li> |
682 | * <li>Connection.TRANSACTION_SERIALIZABLE = SET LOCK_MODE 1: table level |
683 | * locking. </li> |
684 | * <li>Connection.TRANSACTION_READ_COMMITTED = SET LOCK_MODE 3: table |
685 | * level locking, but read locks are released immediately (default). </li> |
686 | * </ul> |
687 | * This setting is not persistent. Please note that using |
688 | * TRANSACTION_READ_UNCOMMITTED while at the same time using multiple |
689 | * connections may result in inconsistent transactions. |
690 | * |
691 | * @param level the new transaction isolation level: |
692 | * Connection.TRANSACTION_READ_UNCOMMITTED, |
693 | * Connection.TRANSACTION_READ_COMMITTED, or |
694 | * Connection.TRANSACTION_SERIALIZABLE |
695 | * @throws SQLException if the connection is closed or the isolation level |
696 | * is not supported |
697 | */ |
698 | @Override |
699 | public void setTransactionIsolation(int level) throws SQLException { |
700 | try { |
701 | debugCodeCall("setTransactionIsolation", level); |
702 | checkClosed(); |
703 | int lockMode; |
704 | switch(level) { |
705 | case Connection.TRANSACTION_READ_UNCOMMITTED: |
706 | lockMode = Constants.LOCK_MODE_OFF; |
707 | break; |
708 | case Connection.TRANSACTION_READ_COMMITTED: |
709 | lockMode = Constants.LOCK_MODE_READ_COMMITTED; |
710 | break; |
711 | case Connection.TRANSACTION_REPEATABLE_READ: |
712 | case Connection.TRANSACTION_SERIALIZABLE: |
713 | lockMode = Constants.LOCK_MODE_TABLE; |
714 | break; |
715 | default: |
716 | throw DbException.getInvalidValueException("level", level); |
717 | } |
718 | commit(); |
719 | setLockMode = prepareCommand("SET LOCK_MODE ?", setLockMode); |
720 | setLockMode.getParameters().get(0).setValue(ValueInt.get(lockMode), false); |
721 | setLockMode.executeUpdate(); |
722 | } catch (Exception e) { |
723 | throw logAndConvert(e); |
724 | } |
725 | } |
726 | |
727 | /** |
728 | * INTERNAL |
729 | */ |
730 | public void setQueryTimeout(int seconds) throws SQLException { |
731 | try { |
732 | debugCodeCall("setQueryTimeout", seconds); |
733 | checkClosed(); |
734 | setQueryTimeout = prepareCommand("SET QUERY_TIMEOUT ?", setQueryTimeout); |
735 | setQueryTimeout.getParameters().get(0). |
736 | setValue(ValueInt.get(seconds * 1000), false); |
737 | setQueryTimeout.executeUpdate(); |
738 | queryTimeoutCache = seconds; |
739 | } catch (Exception e) { |
740 | throw logAndConvert(e); |
741 | } |
742 | } |
743 | |
744 | /** |
745 | * INTERNAL |
746 | */ |
747 | int getQueryTimeout() throws SQLException { |
748 | try { |
749 | if (queryTimeoutCache == -1) { |
750 | checkClosed(); |
751 | getQueryTimeout = prepareCommand( |
752 | "SELECT VALUE FROM INFORMATION_SCHEMA.SETTINGS " + |
753 | "WHERE NAME=?", getQueryTimeout); |
754 | getQueryTimeout.getParameters().get(0). |
755 | setValue(ValueString.get("QUERY_TIMEOUT"), false); |
756 | ResultInterface result = getQueryTimeout.executeQuery(0, false); |
757 | result.next(); |
758 | int queryTimeout = result.currentRow()[0].getInt(); |
759 | result.close(); |
760 | if (queryTimeout != 0) { |
761 | // round to the next second, otherwise 999 millis would |
762 | // return 0 seconds |
763 | queryTimeout = (queryTimeout + 999) / 1000; |
764 | } |
765 | queryTimeoutCache = queryTimeout; |
766 | } |
767 | return queryTimeoutCache; |
768 | } catch (Exception e) { |
769 | throw logAndConvert(e); |
770 | } |
771 | } |
772 | |
773 | /** |
774 | * Returns the current transaction isolation level. |
775 | * |
776 | * @return the isolation level. |
777 | * @throws SQLException if the connection is closed |
778 | */ |
779 | @Override |
780 | public int getTransactionIsolation() throws SQLException { |
781 | try { |
782 | debugCodeCall("getTransactionIsolation"); |
783 | checkClosed(); |
784 | getLockMode = prepareCommand("CALL LOCK_MODE()", getLockMode); |
785 | ResultInterface result = getLockMode.executeQuery(0, false); |
786 | result.next(); |
787 | int lockMode = result.currentRow()[0].getInt(); |
788 | result.close(); |
789 | int transactionIsolationLevel; |
790 | switch(lockMode) { |
791 | case Constants.LOCK_MODE_OFF: |
792 | transactionIsolationLevel = Connection.TRANSACTION_READ_UNCOMMITTED; |
793 | break; |
794 | case Constants.LOCK_MODE_READ_COMMITTED: |
795 | transactionIsolationLevel = Connection.TRANSACTION_READ_COMMITTED; |
796 | break; |
797 | case Constants.LOCK_MODE_TABLE: |
798 | case Constants.LOCK_MODE_TABLE_GC: |
799 | transactionIsolationLevel = Connection.TRANSACTION_SERIALIZABLE; |
800 | break; |
801 | default: |
802 | throw DbException.throwInternalError("lockMode:" + lockMode); |
803 | } |
804 | return transactionIsolationLevel; |
805 | } catch (Exception e) { |
806 | throw logAndConvert(e); |
807 | } |
808 | } |
809 | |
810 | /** |
811 | * Changes the current result set holdability. |
812 | * |
813 | * @param holdability |
814 | * ResultSet.HOLD_CURSORS_OVER_COMMIT or |
815 | * ResultSet.CLOSE_CURSORS_AT_COMMIT; |
816 | * @throws SQLException |
817 | * if the connection is closed or the holdability is not |
818 | * supported |
819 | */ |
820 | @Override |
821 | public void setHoldability(int holdability) throws SQLException { |
822 | try { |
823 | debugCodeCall("setHoldability", holdability); |
824 | checkClosed(); |
825 | checkHoldability(holdability); |
826 | this.holdability = holdability; |
827 | } catch (Exception e) { |
828 | throw logAndConvert(e); |
829 | } |
830 | } |
831 | |
832 | /** |
833 | * Returns the current result set holdability. |
834 | * |
835 | * @return the holdability |
836 | * @throws SQLException if the connection is closed |
837 | */ |
838 | @Override |
839 | public int getHoldability() throws SQLException { |
840 | try { |
841 | debugCodeCall("getHoldability"); |
842 | checkClosed(); |
843 | return holdability; |
844 | } catch (Exception e) { |
845 | throw logAndConvert(e); |
846 | } |
847 | } |
848 | |
849 | /** |
850 | * Gets the type map. |
851 | * |
852 | * @return null |
853 | * @throws SQLException if the connection is closed |
854 | */ |
855 | @Override |
856 | public Map<String, Class<?>> getTypeMap() throws SQLException { |
857 | try { |
858 | debugCodeCall("getTypeMap"); |
859 | checkClosed(); |
860 | return null; |
861 | } catch (Exception e) { |
862 | throw logAndConvert(e); |
863 | } |
864 | } |
865 | |
866 | /** |
867 | * [Partially supported] Sets the type map. This is only supported if the |
868 | * map is empty or null. |
869 | */ |
870 | @Override |
871 | public void setTypeMap(Map<String, Class<?>> map) throws SQLException { |
872 | try { |
873 | debugCode("setTypeMap(" + quoteMap(map) + ");"); |
874 | checkMap(map); |
875 | } catch (Exception e) { |
876 | throw logAndConvert(e); |
877 | } |
878 | } |
879 | |
880 | /** |
881 | * Creates a new callable statement. |
882 | * |
883 | * @param sql the SQL statement |
884 | * @return the callable statement |
885 | * @throws SQLException |
886 | * if the connection is closed or the statement is not valid |
887 | */ |
888 | @Override |
889 | public CallableStatement prepareCall(String sql) throws SQLException { |
890 | try { |
891 | int id = getNextId(TraceObject.CALLABLE_STATEMENT); |
892 | if (isDebugEnabled()) { |
893 | debugCodeAssign("CallableStatement", |
894 | TraceObject.CALLABLE_STATEMENT, id, "prepareCall(" + |
895 | quote(sql) + ")"); |
896 | } |
897 | checkClosed(); |
898 | sql = translateSQL(sql); |
899 | return new JdbcCallableStatement(this, sql, id, |
900 | ResultSet.TYPE_FORWARD_ONLY, |
901 | Constants.DEFAULT_RESULT_SET_CONCURRENCY); |
902 | } catch (Exception e) { |
903 | throw logAndConvert(e); |
904 | } |
905 | } |
906 | |
907 | /** |
908 | * Creates a callable statement with the specified result set type and |
909 | * concurrency. |
910 | * |
911 | * @param sql the SQL statement |
912 | * @param resultSetType the result set type (ResultSet.TYPE_*) |
913 | * @param resultSetConcurrency the concurrency (ResultSet.CONCUR_*) |
914 | * @return the callable statement |
915 | * @throws SQLException |
916 | * if the connection is closed or the result set type or |
917 | * concurrency are not supported |
918 | */ |
919 | @Override |
920 | public CallableStatement prepareCall(String sql, int resultSetType, |
921 | int resultSetConcurrency) throws SQLException { |
922 | try { |
923 | int id = getNextId(TraceObject.CALLABLE_STATEMENT); |
924 | if (isDebugEnabled()) { |
925 | debugCodeAssign("CallableStatement", |
926 | TraceObject.CALLABLE_STATEMENT, id, "prepareCall(" + |
927 | quote(sql) + ", " + resultSetType + ", " + |
928 | resultSetConcurrency + ")"); |
929 | } |
930 | checkTypeConcurrency(resultSetType, resultSetConcurrency); |
931 | checkClosed(); |
932 | sql = translateSQL(sql); |
933 | return new JdbcCallableStatement(this, sql, id, resultSetType, |
934 | resultSetConcurrency); |
935 | } catch (Exception e) { |
936 | throw logAndConvert(e); |
937 | } |
938 | } |
939 | |
940 | /** |
941 | * Creates a callable statement with the specified result set type, |
942 | * concurrency, and holdability. |
943 | * |
944 | * @param sql the SQL statement |
945 | * @param resultSetType the result set type (ResultSet.TYPE_*) |
946 | * @param resultSetConcurrency the concurrency (ResultSet.CONCUR_*) |
947 | * @param resultSetHoldability the holdability (ResultSet.HOLD* / CLOSE*) |
948 | * @return the callable statement |
949 | * @throws SQLException |
950 | * if the connection is closed or the result set type, |
951 | * concurrency, or holdability are not supported |
952 | */ |
953 | @Override |
954 | public CallableStatement prepareCall(String sql, int resultSetType, |
955 | int resultSetConcurrency, int resultSetHoldability) throws SQLException { |
956 | try { |
957 | int id = getNextId(TraceObject.CALLABLE_STATEMENT); |
958 | if (isDebugEnabled()) { |
959 | debugCodeAssign("CallableStatement", |
960 | TraceObject.CALLABLE_STATEMENT, id, "prepareCall(" + |
961 | quote(sql) + ", " + resultSetType + ", " + |
962 | resultSetConcurrency + ", " + |
963 | resultSetHoldability + ")"); |
964 | } |
965 | checkTypeConcurrency(resultSetType, resultSetConcurrency); |
966 | checkHoldability(resultSetHoldability); |
967 | checkClosed(); |
968 | sql = translateSQL(sql); |
969 | return new JdbcCallableStatement(this, sql, id, resultSetType, |
970 | resultSetConcurrency); |
971 | } catch (Exception e) { |
972 | throw logAndConvert(e); |
973 | } |
974 | } |
975 | |
976 | /** |
977 | * Creates a new unnamed savepoint. |
978 | * |
979 | * @return the new savepoint |
980 | */ |
981 | @Override |
982 | public Savepoint setSavepoint() throws SQLException { |
983 | try { |
984 | int id = getNextId(TraceObject.SAVEPOINT); |
985 | if (isDebugEnabled()) { |
986 | debugCodeAssign("Savepoint", TraceObject.SAVEPOINT, id, "setSavepoint()"); |
987 | } |
988 | checkClosed(); |
989 | CommandInterface set = prepareCommand( |
990 | "SAVEPOINT " + JdbcSavepoint.getName(null, savepointId), |
991 | Integer.MAX_VALUE); |
992 | set.executeUpdate(); |
993 | JdbcSavepoint savepoint = new JdbcSavepoint(this, savepointId, null, trace, id); |
994 | savepointId++; |
995 | return savepoint; |
996 | } catch (Exception e) { |
997 | throw logAndConvert(e); |
998 | } |
999 | } |
1000 | |
1001 | /** |
1002 | * Creates a new named savepoint. |
1003 | * |
1004 | * @param name the savepoint name |
1005 | * @return the new savepoint |
1006 | */ |
1007 | @Override |
1008 | public Savepoint setSavepoint(String name) throws SQLException { |
1009 | try { |
1010 | int id = getNextId(TraceObject.SAVEPOINT); |
1011 | if (isDebugEnabled()) { |
1012 | debugCodeAssign("Savepoint", |
1013 | TraceObject.SAVEPOINT, id, "setSavepoint(" + quote(name) + ")"); |
1014 | } |
1015 | checkClosed(); |
1016 | CommandInterface set = prepareCommand( |
1017 | "SAVEPOINT " + JdbcSavepoint.getName(name, 0), Integer.MAX_VALUE); |
1018 | set.executeUpdate(); |
1019 | JdbcSavepoint savepoint = new JdbcSavepoint(this, 0, name, trace, id); |
1020 | return savepoint; |
1021 | } catch (Exception e) { |
1022 | throw logAndConvert(e); |
1023 | } |
1024 | } |
1025 | |
1026 | /** |
1027 | * Rolls back to a savepoint. |
1028 | * |
1029 | * @param savepoint the savepoint |
1030 | */ |
1031 | @Override |
1032 | public void rollback(Savepoint savepoint) throws SQLException { |
1033 | try { |
1034 | JdbcSavepoint sp = convertSavepoint(savepoint); |
1035 | debugCode("rollback(" + sp.getTraceObjectName() + ");"); |
1036 | checkClosedForWrite(); |
1037 | try { |
1038 | sp.rollback(); |
1039 | } finally { |
1040 | afterWriting(); |
1041 | } |
1042 | } catch (Exception e) { |
1043 | throw logAndConvert(e); |
1044 | } |
1045 | } |
1046 | |
1047 | /** |
1048 | * Releases a savepoint. |
1049 | * |
1050 | * @param savepoint the savepoint to release |
1051 | */ |
1052 | @Override |
1053 | public void releaseSavepoint(Savepoint savepoint) throws SQLException { |
1054 | try { |
1055 | debugCode("releaseSavepoint(savepoint);"); |
1056 | checkClosed(); |
1057 | convertSavepoint(savepoint).release(); |
1058 | } catch (Exception e) { |
1059 | throw logAndConvert(e); |
1060 | } |
1061 | } |
1062 | |
1063 | private static JdbcSavepoint convertSavepoint(Savepoint savepoint) { |
1064 | if (!(savepoint instanceof JdbcSavepoint)) { |
1065 | throw DbException.get(ErrorCode.SAVEPOINT_IS_INVALID_1, "" + savepoint); |
1066 | } |
1067 | return (JdbcSavepoint) savepoint; |
1068 | } |
1069 | |
1070 | /** |
1071 | * Creates a prepared statement with the specified result set type, |
1072 | * concurrency, and holdability. |
1073 | * |
1074 | * @param sql the SQL statement |
1075 | * @param resultSetType the result set type (ResultSet.TYPE_*) |
1076 | * @param resultSetConcurrency the concurrency (ResultSet.CONCUR_*) |
1077 | * @param resultSetHoldability the holdability (ResultSet.HOLD* / CLOSE*) |
1078 | * @return the prepared statement |
1079 | * @throws SQLException if the connection is closed or the result set type, |
1080 | * concurrency, or holdability are not supported |
1081 | */ |
1082 | @Override |
1083 | public PreparedStatement prepareStatement(String sql, int resultSetType, |
1084 | int resultSetConcurrency, int resultSetHoldability) |
1085 | throws SQLException { |
1086 | try { |
1087 | int id = getNextId(TraceObject.PREPARED_STATEMENT); |
1088 | if (isDebugEnabled()) { |
1089 | debugCodeAssign("PreparedStatement", TraceObject.PREPARED_STATEMENT, id, |
1090 | "prepareStatement(" + quote(sql) + ", " + |
1091 | resultSetType + ", " + resultSetConcurrency + ", " + |
1092 | resultSetHoldability + ")"); |
1093 | } |
1094 | checkTypeConcurrency(resultSetType, resultSetConcurrency); |
1095 | checkHoldability(resultSetHoldability); |
1096 | checkClosed(); |
1097 | sql = translateSQL(sql); |
1098 | return new JdbcPreparedStatement(this, sql, id, |
1099 | resultSetType, resultSetConcurrency, false); |
1100 | } catch (Exception e) { |
1101 | throw logAndConvert(e); |
1102 | } |
1103 | } |
1104 | |
1105 | /** |
1106 | * Creates a new prepared statement. |
1107 | * This method just calls prepareStatement(String sql) internally. |
1108 | * The method getGeneratedKeys only supports one column. |
1109 | * |
1110 | * @param sql the SQL statement |
1111 | * @param autoGeneratedKeys ignored |
1112 | * @return the prepared statement |
1113 | * @throws SQLException |
1114 | * if the connection is closed |
1115 | */ |
1116 | @Override |
1117 | public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) |
1118 | throws SQLException { |
1119 | try { |
1120 | if (isDebugEnabled()) { |
1121 | debugCode("prepareStatement(" + quote(sql) + ", " + autoGeneratedKeys + ");"); |
1122 | } |
1123 | return prepareStatement(sql); |
1124 | } catch (Exception e) { |
1125 | throw logAndConvert(e); |
1126 | } |
1127 | } |
1128 | |
1129 | /** |
1130 | * Creates a new prepared statement. |
1131 | * This method just calls prepareStatement(String sql) internally. |
1132 | * The method getGeneratedKeys only supports one column. |
1133 | * |
1134 | * @param sql the SQL statement |
1135 | * @param columnIndexes ignored |
1136 | * @return the prepared statement |
1137 | * @throws SQLException |
1138 | * if the connection is closed |
1139 | */ |
1140 | @Override |
1141 | public PreparedStatement prepareStatement(String sql, int[] columnIndexes) |
1142 | throws SQLException { |
1143 | try { |
1144 | if (isDebugEnabled()) { |
1145 | debugCode("prepareStatement(" + quote(sql) + ", " + |
1146 | quoteIntArray(columnIndexes) + ");"); |
1147 | } |
1148 | return prepareStatement(sql); |
1149 | } catch (Exception e) { |
1150 | throw logAndConvert(e); |
1151 | } |
1152 | } |
1153 | |
1154 | /** |
1155 | * Creates a new prepared statement. |
1156 | * This method just calls prepareStatement(String sql) internally. |
1157 | * The method getGeneratedKeys only supports one column. |
1158 | * |
1159 | * @param sql the SQL statement |
1160 | * @param columnNames ignored |
1161 | * @return the prepared statement |
1162 | * @throws SQLException |
1163 | * if the connection is closed |
1164 | */ |
1165 | @Override |
1166 | public PreparedStatement prepareStatement(String sql, String[] columnNames) |
1167 | throws SQLException { |
1168 | try { |
1169 | if (isDebugEnabled()) { |
1170 | debugCode("prepareStatement(" + quote(sql) + ", " + |
1171 | quoteArray(columnNames) + ");"); |
1172 | } |
1173 | return prepareStatement(sql); |
1174 | } catch (Exception e) { |
1175 | throw logAndConvert(e); |
1176 | } |
1177 | } |
1178 | |
1179 | // ============================================================= |
1180 | |
1181 | /** |
1182 | * Prepare an command. This will parse the SQL statement. |
1183 | * |
1184 | * @param sql the SQL statement |
1185 | * @param fetchSize the fetch size (used in remote connections) |
1186 | * @return the command |
1187 | */ |
1188 | CommandInterface prepareCommand(String sql, int fetchSize) { |
1189 | return session.prepareCommand(sql, fetchSize); |
1190 | } |
1191 | |
1192 | private CommandInterface prepareCommand(String sql, CommandInterface old) { |
1193 | return old == null ? session.prepareCommand(sql, Integer.MAX_VALUE) : old; |
1194 | } |
1195 | |
1196 | private static int translateGetEnd(String sql, int i, char c) { |
1197 | int len = sql.length(); |
1198 | switch(c) { |
1199 | case '$': { |
1200 | if (i < len - 1 && sql.charAt(i + 1) == '$' && |
1201 | (i == 0 || sql.charAt(i - 1) <= ' ')) { |
1202 | int j = sql.indexOf("$$", i + 2); |
1203 | if (j < 0) { |
1204 | throw DbException.getSyntaxError(sql, i); |
1205 | } |
1206 | return j + 1; |
1207 | } |
1208 | return i; |
1209 | } |
1210 | case '\'': { |
1211 | int j = sql.indexOf('\'', i + 1); |
1212 | if (j < 0) { |
1213 | throw DbException.getSyntaxError(sql, i); |
1214 | } |
1215 | return j; |
1216 | } |
1217 | case '"': { |
1218 | int j = sql.indexOf('"', i + 1); |
1219 | if (j < 0) { |
1220 | throw DbException.getSyntaxError(sql, i); |
1221 | } |
1222 | return j; |
1223 | } |
1224 | case '/': { |
1225 | checkRunOver(i+1, len, sql); |
1226 | if (sql.charAt(i + 1) == '*') { |
1227 | // block comment |
1228 | int j = sql.indexOf("*/", i + 2); |
1229 | if (j < 0) { |
1230 | throw DbException.getSyntaxError(sql, i); |
1231 | } |
1232 | i = j + 1; |
1233 | } else if (sql.charAt(i + 1) == '/') { |
1234 | // single line comment |
1235 | i += 2; |
1236 | while (i < len && (c = sql.charAt(i)) != '\r' && c != '\n') { |
1237 | i++; |
1238 | } |
1239 | } |
1240 | return i; |
1241 | } |
1242 | case '-': { |
1243 | checkRunOver(i+1, len, sql); |
1244 | if (sql.charAt(i + 1) == '-') { |
1245 | // single line comment |
1246 | i += 2; |
1247 | while (i < len && (c = sql.charAt(i)) != '\r' && c != '\n') { |
1248 | i++; |
1249 | } |
1250 | } |
1251 | return i; |
1252 | } |
1253 | default: |
1254 | throw DbException.throwInternalError("c=" + c); |
1255 | } |
1256 | } |
1257 | |
1258 | /** |
1259 | * Convert JDBC escape sequences in the SQL statement. This |
1260 | * method throws an exception if the SQL statement is null. |
1261 | * |
1262 | * @param sql the SQL statement with or without JDBC escape sequences |
1263 | * @return the SQL statement without JDBC escape sequences |
1264 | */ |
1265 | private static String translateSQL(String sql) { |
1266 | return translateSQL(sql, true); |
1267 | } |
1268 | |
1269 | /** |
1270 | * Convert JDBC escape sequences in the SQL statement if required. This |
1271 | * method throws an exception if the SQL statement is null. |
1272 | * |
1273 | * @param sql the SQL statement with or without JDBC escape sequences |
1274 | * @param escapeProcessing whether escape sequences should be replaced |
1275 | * @return the SQL statement without JDBC escape sequences |
1276 | */ |
1277 | static String translateSQL(String sql, boolean escapeProcessing) { |
1278 | if (sql == null) { |
1279 | throw DbException.getInvalidValueException("SQL", null); |
1280 | } |
1281 | if (!escapeProcessing) { |
1282 | return sql; |
1283 | } |
1284 | if (sql.indexOf('{') < 0) { |
1285 | return sql; |
1286 | } |
1287 | int len = sql.length(); |
1288 | char[] chars = null; |
1289 | int level = 0; |
1290 | for (int i = 0; i < len; i++) { |
1291 | char c = sql.charAt(i); |
1292 | switch (c) { |
1293 | case '\'': |
1294 | case '"': |
1295 | case '/': |
1296 | case '-': |
1297 | i = translateGetEnd(sql, i, c); |
1298 | break; |
1299 | case '{': |
1300 | level++; |
1301 | if (chars == null) { |
1302 | chars = sql.toCharArray(); |
1303 | } |
1304 | chars[i] = ' '; |
1305 | while (Character.isSpaceChar(chars[i])) { |
1306 | i++; |
1307 | checkRunOver(i, len, sql); |
1308 | } |
1309 | int start = i; |
1310 | if (chars[i] >= '0' && chars[i] <= '9') { |
1311 | chars[i - 1] = '{'; |
1312 | while (true) { |
1313 | checkRunOver(i, len, sql); |
1314 | c = chars[i]; |
1315 | if (c == '}') { |
1316 | break; |
1317 | } |
1318 | switch (c) { |
1319 | case '\'': |
1320 | case '"': |
1321 | case '/': |
1322 | case '-': |
1323 | i = translateGetEnd(sql, i, c); |
1324 | break; |
1325 | default: |
1326 | } |
1327 | i++; |
1328 | } |
1329 | level--; |
1330 | break; |
1331 | } else if (chars[i] == '?') { |
1332 | i++; |
1333 | checkRunOver(i, len, sql); |
1334 | while (Character.isSpaceChar(chars[i])) { |
1335 | i++; |
1336 | checkRunOver(i, len, sql); |
1337 | } |
1338 | if (sql.charAt(i) != '=') { |
1339 | throw DbException.getSyntaxError(sql, i, "="); |
1340 | } |
1341 | i++; |
1342 | checkRunOver(i, len, sql); |
1343 | while (Character.isSpaceChar(chars[i])) { |
1344 | i++; |
1345 | checkRunOver(i, len, sql); |
1346 | } |
1347 | } |
1348 | while (!Character.isSpaceChar(chars[i])) { |
1349 | i++; |
1350 | checkRunOver(i, len, sql); |
1351 | } |
1352 | int remove = 0; |
1353 | if (found(sql, start, "fn")) { |
1354 | remove = 2; |
1355 | } else if (found(sql, start, "escape")) { |
1356 | break; |
1357 | } else if (found(sql, start, "call")) { |
1358 | break; |
1359 | } else if (found(sql, start, "oj")) { |
1360 | remove = 2; |
1361 | } else if (found(sql, start, "ts")) { |
1362 | break; |
1363 | } else if (found(sql, start, "t")) { |
1364 | break; |
1365 | } else if (found(sql, start, "d")) { |
1366 | break; |
1367 | } else if (found(sql, start, "params")) { |
1368 | remove = "params".length(); |
1369 | } |
1370 | for (i = start; remove > 0; i++, remove--) { |
1371 | chars[i] = ' '; |
1372 | } |
1373 | break; |
1374 | case '}': |
1375 | if (--level < 0) { |
1376 | throw DbException.getSyntaxError(sql, i); |
1377 | } |
1378 | chars[i] = ' '; |
1379 | break; |
1380 | case '$': |
1381 | i = translateGetEnd(sql, i, c); |
1382 | break; |
1383 | default: |
1384 | } |
1385 | } |
1386 | if (level != 0) { |
1387 | throw DbException.getSyntaxError(sql, sql.length() - 1); |
1388 | } |
1389 | if (chars != null) { |
1390 | sql = new String(chars); |
1391 | } |
1392 | return sql; |
1393 | } |
1394 | |
1395 | private static void checkRunOver(int i, int len, String sql) { |
1396 | if (i >= len) { |
1397 | throw DbException.getSyntaxError(sql, i); |
1398 | } |
1399 | } |
1400 | |
1401 | private static boolean found(String sql, int start, String other) { |
1402 | return sql.regionMatches(true, start, other, 0, other.length()); |
1403 | } |
1404 | |
1405 | private static void checkTypeConcurrency(int resultSetType, |
1406 | int resultSetConcurrency) { |
1407 | switch (resultSetType) { |
1408 | case ResultSet.TYPE_FORWARD_ONLY: |
1409 | case ResultSet.TYPE_SCROLL_INSENSITIVE: |
1410 | case ResultSet.TYPE_SCROLL_SENSITIVE: |
1411 | break; |
1412 | default: |
1413 | throw DbException.getInvalidValueException("resultSetType", |
1414 | resultSetType); |
1415 | } |
1416 | switch (resultSetConcurrency) { |
1417 | case ResultSet.CONCUR_READ_ONLY: |
1418 | case ResultSet.CONCUR_UPDATABLE: |
1419 | break; |
1420 | default: |
1421 | throw DbException.getInvalidValueException("resultSetConcurrency", |
1422 | resultSetConcurrency); |
1423 | } |
1424 | } |
1425 | |
1426 | private static void checkHoldability(int resultSetHoldability) { |
1427 | // TODO compatibility / correctness: DBPool uses |
1428 | // ResultSet.HOLD_CURSORS_OVER_COMMIT |
1429 | if (resultSetHoldability != ResultSet.HOLD_CURSORS_OVER_COMMIT |
1430 | && resultSetHoldability != ResultSet.CLOSE_CURSORS_AT_COMMIT) { |
1431 | throw DbException.getInvalidValueException("resultSetHoldability", |
1432 | resultSetHoldability); |
1433 | } |
1434 | } |
1435 | |
1436 | /** |
1437 | * INTERNAL. |
1438 | * Check if this connection is closed. |
1439 | * The next operation is a read request. |
1440 | * |
1441 | * @throws DbException if the connection or session is closed |
1442 | */ |
1443 | protected void checkClosed() { |
1444 | checkClosed(false); |
1445 | } |
1446 | |
1447 | /** |
1448 | * Check if this connection is closed. |
1449 | * The next operation may be a write request. |
1450 | * |
1451 | * @throws DbException if the connection or session is closed |
1452 | */ |
1453 | private void checkClosedForWrite() { |
1454 | checkClosed(true); |
1455 | } |
1456 | |
1457 | /** |
1458 | * INTERNAL. |
1459 | * Check if this connection is closed. |
1460 | * |
1461 | * @param write if the next operation is possibly writing |
1462 | * @throws DbException if the connection or session is closed |
1463 | */ |
1464 | protected void checkClosed(boolean write) { |
1465 | if (session == null) { |
1466 | throw DbException.get(ErrorCode.OBJECT_CLOSED); |
1467 | } |
1468 | if (session.isClosed()) { |
1469 | throw DbException.get(ErrorCode.DATABASE_CALLED_AT_SHUTDOWN); |
1470 | } |
1471 | if (session.isReconnectNeeded(write)) { |
1472 | trace.debug("reconnect"); |
1473 | closePreparedCommands(); |
1474 | session = session.reconnect(write); |
1475 | trace = session.getTrace(); |
1476 | } |
1477 | } |
1478 | |
1479 | /** |
1480 | * INTERNAL. |
1481 | * Called after executing a command that could have written something. |
1482 | */ |
1483 | protected void afterWriting() { |
1484 | if (session != null) { |
1485 | session.afterWriting(); |
1486 | } |
1487 | } |
1488 | |
1489 | String getURL() { |
1490 | checkClosed(); |
1491 | return url; |
1492 | } |
1493 | |
1494 | String getUser() { |
1495 | checkClosed(); |
1496 | return user; |
1497 | } |
1498 | |
1499 | private void rollbackInternal() { |
1500 | rollback = prepareCommand("ROLLBACK", rollback); |
1501 | rollback.executeUpdate(); |
1502 | } |
1503 | |
1504 | /** |
1505 | * INTERNAL |
1506 | */ |
1507 | public int getPowerOffCount() { |
1508 | return (session == null || session.isClosed()) ? |
1509 | 0 : session.getPowerOffCount(); |
1510 | } |
1511 | |
1512 | /** |
1513 | * INTERNAL |
1514 | */ |
1515 | public void setPowerOffCount(int count) { |
1516 | if (session != null) { |
1517 | session.setPowerOffCount(count); |
1518 | } |
1519 | } |
1520 | |
1521 | /** |
1522 | * INTERNAL |
1523 | */ |
1524 | public void setExecutingStatement(Statement stat) { |
1525 | executingStatement = stat; |
1526 | } |
1527 | |
1528 | /** |
1529 | * INTERNAL |
1530 | */ |
1531 | ResultSet getGeneratedKeys(JdbcStatement stat, int id) { |
1532 | getGeneratedKeys = prepareCommand( |
1533 | "SELECT SCOPE_IDENTITY() " + |
1534 | "WHERE SCOPE_IDENTITY() IS NOT NULL", getGeneratedKeys); |
1535 | ResultInterface result = getGeneratedKeys.executeQuery(0, false); |
1536 | ResultSet rs = new JdbcResultSet(this, stat, result, id, false, true, false); |
1537 | return rs; |
1538 | } |
1539 | |
1540 | /** |
1541 | * Create a new empty Clob object. |
1542 | * |
1543 | * @return the object |
1544 | */ |
1545 | @Override |
1546 | public Clob createClob() throws SQLException { |
1547 | try { |
1548 | int id = getNextId(TraceObject.CLOB); |
1549 | debugCodeAssign("Clob", TraceObject.CLOB, id, "createClob()"); |
1550 | checkClosedForWrite(); |
1551 | try { |
1552 | Value v = session.getDataHandler().getLobStorage().createClob( |
1553 | new InputStreamReader( |
1554 | new ByteArrayInputStream(Utils.EMPTY_BYTES)), 0); |
1555 | session.addTemporaryLob(v); |
1556 | return new JdbcClob(this, v, id); |
1557 | } finally { |
1558 | afterWriting(); |
1559 | } |
1560 | } catch (Exception e) { |
1561 | throw logAndConvert(e); |
1562 | } |
1563 | } |
1564 | |
1565 | /** |
1566 | * Create a new empty Blob object. |
1567 | * |
1568 | * @return the object |
1569 | */ |
1570 | @Override |
1571 | public Blob createBlob() throws SQLException { |
1572 | try { |
1573 | int id = getNextId(TraceObject.BLOB); |
1574 | debugCodeAssign("Blob", TraceObject.BLOB, id, "createClob()"); |
1575 | checkClosedForWrite(); |
1576 | try { |
1577 | Value v = session.getDataHandler().getLobStorage().createBlob( |
1578 | new ByteArrayInputStream(Utils.EMPTY_BYTES), 0); |
1579 | session.addTemporaryLob(v); |
1580 | return new JdbcBlob(this, v, id); |
1581 | } finally { |
1582 | afterWriting(); |
1583 | } |
1584 | } catch (Exception e) { |
1585 | throw logAndConvert(e); |
1586 | } |
1587 | } |
1588 | |
1589 | /** |
1590 | * Create a new empty NClob object. |
1591 | * |
1592 | * @return the object |
1593 | */ |
1594 | @Override |
1595 | public NClob createNClob() throws SQLException { |
1596 | try { |
1597 | int id = getNextId(TraceObject.CLOB); |
1598 | debugCodeAssign("NClob", TraceObject.CLOB, id, "createNClob()"); |
1599 | checkClosedForWrite(); |
1600 | try { |
1601 | Value v = session.getDataHandler().getLobStorage().createClob( |
1602 | new InputStreamReader( |
1603 | new ByteArrayInputStream(Utils.EMPTY_BYTES)), 0); |
1604 | session.addTemporaryLob(v); |
1605 | return new JdbcClob(this, v, id); |
1606 | } finally { |
1607 | afterWriting(); |
1608 | } |
1609 | } catch (Exception e) { |
1610 | throw logAndConvert(e); |
1611 | } |
1612 | } |
1613 | |
1614 | /** |
1615 | * [Not supported] Create a new empty SQLXML object. |
1616 | */ |
1617 | @Override |
1618 | public SQLXML createSQLXML() throws SQLException { |
1619 | throw unsupported("SQLXML"); |
1620 | } |
1621 | |
1622 | /** |
1623 | * [Not supported] Create a new empty Array object. |
1624 | */ |
1625 | @Override |
1626 | public Array createArrayOf(String typeName, Object[] elements) |
1627 | throws SQLException { |
1628 | throw unsupported("createArray"); |
1629 | } |
1630 | |
1631 | /** |
1632 | * [Not supported] Create a new empty Struct object. |
1633 | */ |
1634 | @Override |
1635 | public Struct createStruct(String typeName, Object[] attributes) |
1636 | throws SQLException { |
1637 | throw unsupported("Struct"); |
1638 | } |
1639 | |
1640 | /** |
1641 | * Returns true if this connection is still valid. |
1642 | * |
1643 | * @param timeout the number of seconds to wait for the database to respond |
1644 | * (ignored) |
1645 | * @return true if the connection is valid. |
1646 | */ |
1647 | @Override |
1648 | public synchronized boolean isValid(int timeout) { |
1649 | try { |
1650 | debugCodeCall("isValid", timeout); |
1651 | if (session == null || session.isClosed()) { |
1652 | return false; |
1653 | } |
1654 | // force a network round trip (if networked) |
1655 | getTransactionIsolation(); |
1656 | return true; |
1657 | } catch (Exception e) { |
1658 | // this method doesn't throw an exception, but it logs it |
1659 | logAndConvert(e); |
1660 | return false; |
1661 | } |
1662 | } |
1663 | |
1664 | /** |
1665 | * Set a client property. |
1666 | * This method always throws a SQLClientInfoException. |
1667 | * |
1668 | * @param name the name of the property (ignored) |
1669 | * @param value the value (ignored) |
1670 | */ |
1671 | @Override |
1672 | public void setClientInfo(String name, String value) |
1673 | throws SQLClientInfoException { |
1674 | try { |
1675 | if (isDebugEnabled()) { |
1676 | debugCode("setClientInfo(" |
1677 | +quote(name)+", " |
1678 | +quote(value)+");"); |
1679 | } |
1680 | checkClosed(); |
1681 | // we don't have any client properties, so just throw |
1682 | throw new SQLClientInfoException(); |
1683 | } catch (Exception e) { |
1684 | throw convertToClientInfoException(logAndConvert(e)); |
1685 | } |
1686 | } |
1687 | |
1688 | private static SQLClientInfoException convertToClientInfoException( |
1689 | SQLException x) { |
1690 | if (x instanceof SQLClientInfoException) { |
1691 | return (SQLClientInfoException) x; |
1692 | } |
1693 | return new SQLClientInfoException( |
1694 | x.getMessage(), x.getSQLState(), x.getErrorCode(), null, null); |
1695 | } |
1696 | |
1697 | /** |
1698 | * Set the client properties. |
1699 | * This method always throws a SQLClientInfoException. |
1700 | * |
1701 | * @param properties the properties (ignored) |
1702 | */ |
1703 | @Override |
1704 | public void setClientInfo(Properties properties) throws SQLClientInfoException { |
1705 | try { |
1706 | if (isDebugEnabled()) { |
1707 | debugCode("setClientInfo(properties);"); |
1708 | } |
1709 | checkClosed(); |
1710 | // we don't have any client properties, so just throw |
1711 | throw new SQLClientInfoException(); |
1712 | } catch (Exception e) { |
1713 | throw convertToClientInfoException(logAndConvert(e)); |
1714 | } |
1715 | } |
1716 | |
1717 | /** |
1718 | * Get the client properties. |
1719 | * |
1720 | * @return the property list |
1721 | */ |
1722 | @Override |
1723 | public Properties getClientInfo() throws SQLException { |
1724 | try { |
1725 | if (isDebugEnabled()) { |
1726 | debugCode("getClientInfo();"); |
1727 | } |
1728 | checkClosed(); |
1729 | ArrayList<String> serverList = session.getClusterServers(); |
1730 | Properties p = new Properties(); |
1731 | |
1732 | p.setProperty("numServers", String.valueOf(serverList.size())); |
1733 | for (int i = 0; i < serverList.size(); i++) { |
1734 | p.setProperty("server" + String.valueOf(i), serverList.get(i)); |
1735 | } |
1736 | return p; |
1737 | } catch (Exception e) { |
1738 | throw logAndConvert(e); |
1739 | } |
1740 | } |
1741 | |
1742 | /** |
1743 | * Get a client property. |
1744 | * |
1745 | * @param name the client info name (ignored) |
1746 | * @return the property value |
1747 | */ |
1748 | @Override |
1749 | public String getClientInfo(String name) throws SQLException { |
1750 | try { |
1751 | if (isDebugEnabled()) { |
1752 | debugCodeCall("getClientInfo", name); |
1753 | } |
1754 | checkClosed(); |
1755 | Properties p = getClientInfo(); |
1756 | String s = p.getProperty(name); |
1757 | if (s == null) { |
1758 | throw new SQLClientInfoException(); |
1759 | } |
1760 | return s; |
1761 | } catch (Exception e) { |
1762 | throw logAndConvert(e); |
1763 | } |
1764 | } |
1765 | |
1766 | /** |
1767 | * Return an object of this class if possible. |
1768 | * |
1769 | * @param iface the class |
1770 | * @return this |
1771 | */ |
1772 | @Override |
1773 | @SuppressWarnings("unchecked") |
1774 | public <T> T unwrap(Class<T> iface) throws SQLException { |
1775 | if (isWrapperFor(iface)) { |
1776 | return (T) this; |
1777 | } |
1778 | throw DbException.getInvalidValueException("iface", iface); |
1779 | } |
1780 | |
1781 | /** |
1782 | * Checks if unwrap can return an object of this class. |
1783 | * |
1784 | * @param iface the class |
1785 | * @return whether or not the interface is assignable from this class |
1786 | */ |
1787 | @Override |
1788 | public boolean isWrapperFor(Class<?> iface) throws SQLException { |
1789 | return iface != null && iface.isAssignableFrom(getClass()); |
1790 | } |
1791 | |
1792 | /** |
1793 | * Create a Clob value from this reader. |
1794 | * |
1795 | * @param x the reader |
1796 | * @param length the length (if smaller or equal than 0, all data until the |
1797 | * end of file is read) |
1798 | * @return the value |
1799 | */ |
1800 | public Value createClob(Reader x, long length) { |
1801 | if (x == null) { |
1802 | return ValueNull.INSTANCE; |
1803 | } |
1804 | if (length <= 0) { |
1805 | length = -1; |
1806 | } |
1807 | Value v = session.getDataHandler().getLobStorage().createClob(x, length); |
1808 | session.addTemporaryLob(v); |
1809 | return v; |
1810 | } |
1811 | |
1812 | /** |
1813 | * Create a Blob value from this input stream. |
1814 | * |
1815 | * @param x the input stream |
1816 | * @param length the length (if smaller or equal than 0, all data until the |
1817 | * end of file is read) |
1818 | * @return the value |
1819 | */ |
1820 | public Value createBlob(InputStream x, long length) { |
1821 | if (x == null) { |
1822 | return ValueNull.INSTANCE; |
1823 | } |
1824 | if (length <= 0) { |
1825 | length = -1; |
1826 | } |
1827 | Value v = session.getDataHandler().getLobStorage().createBlob(x, length); |
1828 | session.addTemporaryLob(v); |
1829 | return v; |
1830 | } |
1831 | |
1832 | /** |
1833 | * [Not supported] |
1834 | * |
1835 | * @param schema the schema |
1836 | */ |
1837 | //## Java 1.7 ## |
1838 | @Override |
1839 | public void setSchema(String schema) { |
1840 | // not supported |
1841 | } |
1842 | //*/ |
1843 | |
1844 | /** |
1845 | * [Not supported] |
1846 | */ |
1847 | //## Java 1.7 ## |
1848 | @Override |
1849 | public String getSchema() { |
1850 | return null; |
1851 | } |
1852 | //*/ |
1853 | |
1854 | /** |
1855 | * [Not supported] |
1856 | * |
1857 | * @param executor the executor used by this method |
1858 | */ |
1859 | //## Java 1.7 ## |
1860 | @Override |
1861 | public void abort(Executor executor) { |
1862 | // not supported |
1863 | } |
1864 | //*/ |
1865 | |
1866 | /** |
1867 | * [Not supported] |
1868 | * |
1869 | * @param executor the executor used by this method |
1870 | * @param milliseconds the TCP connection timeout |
1871 | */ |
1872 | //## Java 1.7 ## |
1873 | @Override |
1874 | public void setNetworkTimeout(Executor executor, int milliseconds) { |
1875 | // not supported |
1876 | } |
1877 | //*/ |
1878 | |
1879 | /** |
1880 | * [Not supported] |
1881 | */ |
1882 | //## Java 1.7 ## |
1883 | @Override |
1884 | public int getNetworkTimeout() { |
1885 | return 0; |
1886 | } |
1887 | //*/ |
1888 | |
1889 | /** |
1890 | * Check that the given type map is either null or empty. |
1891 | * |
1892 | * @param map the type map |
1893 | * @throws DbException if the map is not empty |
1894 | */ |
1895 | static void checkMap(Map<String, Class<?>> map) { |
1896 | if (map != null && map.size() > 0) { |
1897 | throw DbException.getUnsupportedException("map.size > 0"); |
1898 | } |
1899 | } |
1900 | |
1901 | /** |
1902 | * INTERNAL |
1903 | */ |
1904 | @Override |
1905 | public String toString() { |
1906 | return getTraceObjectName() + ": url=" + url + " user=" + user; |
1907 | } |
1908 | |
1909 | /** |
1910 | * Convert an object to the default Java object for the given SQL type. For |
1911 | * example, LOB objects are converted to java.sql.Clob / java.sql.Blob. |
1912 | * |
1913 | * @param v the value |
1914 | * @return the object |
1915 | */ |
1916 | Object convertToDefaultObject(Value v) { |
1917 | Object o; |
1918 | switch (v.getType()) { |
1919 | case Value.CLOB: { |
1920 | int id = getNextId(TraceObject.CLOB); |
1921 | o = new JdbcClob(this, v, id); |
1922 | break; |
1923 | } |
1924 | case Value.BLOB: { |
1925 | int id = getNextId(TraceObject.BLOB); |
1926 | o = new JdbcBlob(this, v, id); |
1927 | break; |
1928 | } |
1929 | case Value.JAVA_OBJECT: |
1930 | if (SysProperties.serializeJavaObject) { |
1931 | o = JdbcUtils.deserialize(v.getBytesNoCopy(), session.getDataHandler()); |
1932 | break; |
1933 | } |
1934 | default: |
1935 | o = v.getObject(); |
1936 | } |
1937 | return o; |
1938 | } |
1939 | |
1940 | CompareMode getCompareMode() { |
1941 | return compareMode; |
1942 | } |
1943 | |
1944 | /** |
1945 | * INTERNAL |
1946 | */ |
1947 | public void setTraceLevel(int level) { |
1948 | trace.setLevel(level); |
1949 | } |
1950 | |
1951 | } |