File D:\maven-qcr-plugin-testsunitaires\qcr4java\h2-1.3.170-sources\target\site\qc\lecode\engine\Session.java.html

1 /*
2 * Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
3 * Version 1.0, and under the Eclipse Public License, Version 1.0
4 * (http://h2database.com/html/license.html).
5 * Initial Developer: H2 Group
6 */
7 package org.h2.engine;
8
9 import java.util.ArrayList;
10 import java.util.HashMap;
11 import java.util.HashSet;
12 import java.util.Random;
13 import org.h2.command.Command;
14 import org.h2.command.CommandInterface;
15 import org.h2.command.Parser;
16 import org.h2.command.Prepared;
17 import org.h2.command.dml.SetTypes;
18 import org.h2.constant.ErrorCode;
19 import org.h2.constant.SysProperties;
20 import org.h2.constraint.Constraint;
21 import org.h2.index.Index;
22 import org.h2.jdbc.JdbcConnection;
23 import org.h2.message.DbException;
24 import org.h2.message.Trace;
25 import org.h2.message.TraceSystem;
26 import org.h2.result.ResultInterface;
27 import org.h2.result.Row;
28 import org.h2.schema.Schema;
29 import org.h2.store.DataHandler;
30 import org.h2.store.InDoubtTransaction;
31 import org.h2.store.LobStorage;
32 import org.h2.table.Table;
33 import org.h2.util.New;
34 import org.h2.util.SmallLRUCache;
35 import org.h2.value.Value;
36 import org.h2.value.ValueLong;
37 import org.h2.value.ValueNull;
38 import org.h2.value.ValueString;
39
40 /**
41 * A session represents an embedded database connection. When using the server
42 * mode, this object resides on the server side and communicates with a
43 * SessionRemote object on the client side.
44 */
45 public class Session extends SessionWithState {
46
47 /**
48 * This special log position means that the log entry has been written.
49 */
50 public static final int LOG_WRITTEN = -1;
51
52 /**
53 * The prefix of generated identifiers. It may not have letters, because
54 * they are case sensitive.
55 */
56 private static final String SYSTEM_IDENTIFIER_PREFIX = "_";
57 private static int nextSerialId;
58
59 private final int serialId = nextSerialId++;
60 private final Database database;
61 private ConnectionInfo connectionInfo;
62 private final User user;
63 private final int id;
64 private final ArrayList<Table> locks = New.arrayList();
65 private final UndoLog undoLog;
66 private boolean autoCommit = true;
67 private Random random;
68 private int lockTimeout;
69 private Value lastIdentity = ValueLong.get(0);
70 private Value lastScopeIdentity = ValueLong.get(0);
71 private int firstUncommittedLog = Session.LOG_WRITTEN;
72 private int firstUncommittedPos = Session.LOG_WRITTEN;
73 private HashMap<String, Integer> savepoints;
74 private HashMap<String, Table> localTempTables;
75 private HashMap<String, Index> localTempTableIndexes;
76 private HashMap<String, Constraint> localTempTableConstraints;
77 private int throttle;
78 private long lastThrottle;
79 private Command currentCommand;
80 private boolean allowLiterals;
81 private String currentSchemaName;
82 private String[] schemaSearchPath;
83 private Trace trace;
84 private HashMap<String, Value> unlinkLobMap;
85 private int systemIdentifier;
86 private HashMap<String, Procedure> procedures;
87 private boolean undoLogEnabled = true;
88 private boolean redoLogBinary = true;
89 private boolean autoCommitAtTransactionEnd;
90 private String currentTransactionName;
91 private volatile long cancelAt;
92 private boolean closed;
93 private final long sessionStart = System.currentTimeMillis();
94 private long transactionStart;
95 private long currentCommandStart;
96 private HashMap<String, Value> variables;
97 private HashSet<ResultInterface> temporaryResults;
98 private int queryTimeout;
99 private boolean commitOrRollbackDisabled;
100 private Table waitForLock;
101 private int modificationId;
102 private int objectId;
103 private final int queryCacheSize;
104 private SmallLRUCache<String, Command> queryCache;
105
106 public Session(Database database, User user, int id) {
107 this.database = database;
108 this.queryTimeout = database.getSettings().maxQueryTimeout;
109 this.queryCacheSize = database.getSettings().queryCacheSize;
110 this.undoLog = new UndoLog(this);
111 this.user = user;
112 this.id = id;
113 Setting setting = database.findSetting(SetTypes.getTypeName(SetTypes.DEFAULT_LOCK_TIMEOUT));
114 this.lockTimeout = setting == null ? Constants.INITIAL_LOCK_TIMEOUT : setting.getIntValue();
115 this.currentSchemaName = Constants.SCHEMA_MAIN;
116 }
117
118 public boolean setCommitOrRollbackDisabled(boolean x) {
119 boolean old = commitOrRollbackDisabled;
120 commitOrRollbackDisabled = x;
121 return old;
122 }
123
124 private void initVariables() {
125 if (variables == null) {
126 variables = database.newStringMap();
127 }
128 }
129
130 /**
131 * Set the value of the given variable for this session.
132 *
133 * @param name the name of the variable (may not be null)
134 * @param value the new value (may not be null)
135 */
136 public void setVariable(String name, Value value) {
137 initVariables();
138 modificationId++;
139 Value old;
140 if (value == ValueNull.INSTANCE) {
141 old = variables.remove(name);
142 } else {
143 // link LOB values, to make sure we have our own object
144 value = value.link(database, LobStorage.TABLE_ID_SESSION_VARIABLE);
145 old = variables.put(name, value);
146 }
147 if (old != null) {
148 // close the old value (in case it is a lob)
149 old.unlink();
150 old.close();
151 }
152 }
153
154 /**
155 * Get the value of the specified user defined variable. This method always
156 * returns a value; it returns ValueNull.INSTANCE if the variable doesn't
157 * exist.
158 *
159 * @param name the variable name
160 * @return the value, or NULL
161 */
162 public Value getVariable(String name) {
163 initVariables();
164 Value v = variables.get(name);
165 return v == null ? ValueNull.INSTANCE : v;
166 }
167
168 /**
169 * Get the list of variable names that are set for this session.
170 *
171 * @return the list of names
172 */
173 public String[] getVariableNames() {
174 if (variables == null) {
175 return new String[0];
176 }
177 String[] list = new String[variables.size()];
178 variables.keySet().toArray(list);
179 return list;
180 }
181
182 /**
183 * Get the local temporary table if one exists with that name, or null if
184 * not.
185 *
186 * @param name the table name
187 * @return the table, or null
188 */
189 public Table findLocalTempTable(String name) {
190 if (localTempTables == null) {
191 return null;
192 }
193 return localTempTables.get(name);
194 }
195
196 public ArrayList<Table> getLocalTempTables() {
197 if (localTempTables == null) {
198 return New.arrayList();
199 }
200 return New.arrayList(localTempTables.values());
201 }
202
203 /**
204 * Add a local temporary table to this session.
205 *
206 * @param table the table to add
207 * @throws DbException if a table with this name already exists
208 */
209 public void addLocalTempTable(Table table) {
210 if (localTempTables == null) {
211 localTempTables = database.newStringMap();
212 }
213 if (localTempTables.get(table.getName()) != null) {
214 throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, table.getSQL());
215 }
216 modificationId++;
217 localTempTables.put(table.getName(), table);
218 }
219
220 /**
221 * Drop and remove the given local temporary table from this session.
222 *
223 * @param table the table
224 */
225 public void removeLocalTempTable(Table table) {
226 modificationId++;
227 localTempTables.remove(table.getName());
228 synchronized (database) {
229 table.removeChildrenAndResources(this);
230 }
231 }
232
233 /**
234 * Get the local temporary index if one exists with that name, or null if
235 * not.
236 *
237 * @param name the table name
238 * @return the table, or null
239 */
240 public Index findLocalTempTableIndex(String name) {
241 if (localTempTableIndexes == null) {
242 return null;
243 }
244 return localTempTableIndexes.get(name);
245 }
246
247 public HashMap<String, Index> getLocalTempTableIndexes() {
248 if (localTempTableIndexes == null) {
249 return New.hashMap();
250 }
251 return localTempTableIndexes;
252 }
253
254 /**
255 * Add a local temporary index to this session.
256 *
257 * @param index the index to add
258 * @throws DbException if a index with this name already exists
259 */
260 public void addLocalTempTableIndex(Index index) {
261 if (localTempTableIndexes == null) {
262 localTempTableIndexes = database.newStringMap();
263 }
264 if (localTempTableIndexes.get(index.getName()) != null) {
265 throw DbException.get(ErrorCode.INDEX_ALREADY_EXISTS_1, index.getSQL());
266 }
267 localTempTableIndexes.put(index.getName(), index);
268 }
269
270 /**
271 * Drop and remove the given local temporary index from this session.
272 *
273 * @param index the index
274 */
275 public void removeLocalTempTableIndex(Index index) {
276 if (localTempTableIndexes != null) {
277 localTempTableIndexes.remove(index.getName());
278 synchronized (database) {
279 index.removeChildrenAndResources(this);
280 }
281 }
282 }
283
284 /**
285 * Get the local temporary constraint if one exists with that name, or
286 * null if not.
287 *
288 * @param name the constraint name
289 * @return the constraint, or null
290 */
291 public Constraint findLocalTempTableConstraint(String name) {
292 if (localTempTableConstraints == null) {
293 return null;
294 }
295 return localTempTableConstraints.get(name);
296 }
297
298 /**
299 * Get the map of constraints for all constraints on local, temporary
300 * tables, if any. The map's keys are the constraints' names.
301 *
302 * @return the map of constraints, or null
303 */
304 public HashMap<String, Constraint> getLocalTempTableConstraints() {
305 if (localTempTableConstraints == null) {
306 return New.hashMap();
307 }
308 return localTempTableConstraints;
309 }
310
311 /**
312 * Add a local temporary constraint to this session.
313 *
314 * @param constraint the constraint to add
315 * @throws DbException if a constraint with the same name already exists
316 */
317 public void addLocalTempTableConstraint(Constraint constraint) {
318 if (localTempTableConstraints == null) {
319 localTempTableConstraints = database.newStringMap();
320 }
321 String name = constraint.getName();
322 if (localTempTableConstraints.get(name) != null) {
323 throw DbException.get(ErrorCode.CONSTRAINT_ALREADY_EXISTS_1, constraint.getSQL());
324 }
325 localTempTableConstraints.put(name, constraint);
326 }
327
328 /**
329 * Drop and remove the given local temporary constraint from this session.
330 *
331 * @param constraint the constraint
332 */
333 void removeLocalTempTableConstraint(Constraint constraint) {
334 if (localTempTableConstraints != null) {
335 localTempTableConstraints.remove(constraint.getName());
336 synchronized (database) {
337 constraint.removeChildrenAndResources(this);
338 }
339 }
340 }
341
342 public boolean getAutoCommit() {
343 return autoCommit;
344 }
345
346 public User getUser() {
347 return user;
348 }
349
350 public void setAutoCommit(boolean b) {
351 autoCommit = b;
352 }
353
354 public int getLockTimeout() {
355 return lockTimeout;
356 }
357
358 public void setLockTimeout(int lockTimeout) {
359 this.lockTimeout = lockTimeout;
360 }
361
362 public synchronized CommandInterface prepareCommand(String sql, int fetchSize) {
363 return prepareLocal(sql);
364 }
365
366 /**
367 * Parse and prepare the given SQL statement. This method also checks the
368 * rights.
369 *
370 * @param sql the SQL statement
371 * @return the prepared statement
372 */
373 public Prepared prepare(String sql) {
374 return prepare(sql, false);
375 }
376
377 /**
378 * Parse and prepare the given SQL statement.
379 *
380 * @param sql the SQL statement
381 * @param rightsChecked true if the rights have already been checked
382 * @return the prepared statement
383 */
384 public Prepared prepare(String sql, boolean rightsChecked) {
385 Parser parser = new Parser(this);
386 parser.setRightsChecked(rightsChecked);
387 return parser.prepare(sql);
388 }
389
390 /**
391 * Parse and prepare the given SQL statement.
392 * This method also checks if the connection has been closed.
393 *
394 * @param sql the SQL statement
395 * @return the prepared statement
396 */
397 public Command prepareLocal(String sql) {
398 if (closed) {
399 throw DbException.get(ErrorCode.CONNECTION_BROKEN_1, "session closed");
400 }
401 Command command;
402 if (queryCacheSize > 0) {
403 if (queryCache == null) {
404 queryCache = SmallLRUCache.newInstance(queryCacheSize);
405 } else {
406 command = queryCache.get(sql);
407 if (command != null && command.canReuse()) {
408 command.reuse();
409 return command;
410 }
411 }
412 }
413 Parser parser = new Parser(this);
414 command = parser.prepareCommand(sql);
415 if (queryCache != null) {
416 if (command.isCacheable()) {
417 queryCache.put(sql, command);
418 }
419 }
420 return command;
421 }
422
423 public Database getDatabase() {
424 return database;
425 }
426
427 public int getPowerOffCount() {
428 return database.getPowerOffCount();
429 }
430
431 public void setPowerOffCount(int count) {
432 database.setPowerOffCount(count);
433 }
434
435 /**
436 * Commit the current transaction. If the statement was not a data
437 * definition statement, and if there are temporary tables that should be
438 * dropped or truncated at commit, this is done as well.
439 *
440 * @param ddl if the statement was a data definition statement
441 */
442 public void commit(boolean ddl) {
443 checkCommitRollback();
444 currentTransactionName = null;
445 transactionStart = 0;
446 if (containsUncommitted()) {
447 // need to commit even if rollback is not possible
448 // (create/drop table and so on)
449 database.commit(this);
450 }
451 if (undoLog.size() > 0) {
452 // commit the rows when using MVCC
453 if (database.isMultiVersion()) {
454 ArrayList<Row> rows = New.arrayList();
455 synchronized (database) {
456 while (undoLog.size() > 0) {
457 UndoLogRecord entry = undoLog.getLast();
458 entry.commit();
459 rows.add(entry.getRow());
460 undoLog.removeLast(false);
461 }
462 for (int i = 0, size = rows.size(); i < size; i++) {
463 Row r = rows.get(i);
464 r.commit();
465 }
466 }
467 }
468 undoLog.clear();
469 }
470 if (!ddl) {
471 // do not clean the temp tables if the last command was a
472 // create/drop
473 cleanTempTables(false);
474 if (autoCommitAtTransactionEnd) {
475 autoCommit = true;
476 autoCommitAtTransactionEnd = false;
477 }
478 }
479 if (unlinkLobMap != null && unlinkLobMap.size() > 0) {
480 // need to flush the transaction log, because we can't unlink lobs if the
481 // commit record is not written
482 database.flush();
483 for (Value v : unlinkLobMap.values()) {
484 v.unlink();
485 v.close();
486 }
487 unlinkLobMap = null;
488 }
489 unlockAll();
490 }
491
492 private void checkCommitRollback() {
493 if (commitOrRollbackDisabled && locks.size() > 0) {
494 throw DbException.get(ErrorCode.COMMIT_ROLLBACK_NOT_ALLOWED);
495 }
496 }
497
498 /**
499 * Fully roll back the current transaction.
500 */
501 public void rollback() {
502 checkCommitRollback();
503 currentTransactionName = null;
504 boolean needCommit = false;
505 if (undoLog.size() > 0) {
506 rollbackTo(0, false);
507 needCommit = true;
508 }
509 if (locks.size() > 0 || needCommit) {
510 database.commit(this);
511 }
512 cleanTempTables(false);
513 unlockAll();
514 if (autoCommitAtTransactionEnd) {
515 autoCommit = true;
516 autoCommitAtTransactionEnd = false;
517 }
518 }
519
520 /**
521 * Partially roll back the current transaction.
522 *
523 * @param index the position to which should be rolled back
524 * @param trimToSize if the list should be trimmed
525 */
526 public void rollbackTo(int index, boolean trimToSize) {
527 while (undoLog.size() > index) {
528 UndoLogRecord entry = undoLog.getLast();
529 entry.undo(this);
530 undoLog.removeLast(trimToSize);
531 }
532 if (savepoints != null) {
533 String[] names = new String[savepoints.size()];
534 savepoints.keySet().toArray(names);
535 for (String name : names) {
536 Integer savepointIndex = savepoints.get(name);
537 if (savepointIndex.intValue() > index) {
538 savepoints.remove(name);
539 }
540 }
541 }
542 }
543
544 public int getUndoLogPos() {
545 return undoLog.size();
546 }
547
548 public int getId() {
549 return id;
550 }
551
552 public void cancel() {
553 cancelAt = System.currentTimeMillis();
554 }
555
556 public void close() {
557 if (!closed) {
558 try {
559 database.checkPowerOff();
560 cleanTempTables(true);
561 undoLog.clear();
562 database.removeSession(this);
563 } finally {
564 closed = true;
565 }
566 }
567 }
568
569 /**
570 * Add a lock for the given table. The object is unlocked on commit or
571 * rollback.
572 *
573 * @param table the table that is locked
574 */
575 public void addLock(Table table) {
576 if (SysProperties.CHECK) {
577 if (locks.indexOf(table) >= 0) {
578 DbException.throwInternalError();
579 }
580 }
581 locks.add(table);
582 }
583
584 /**
585 * Add an undo log entry to this session.
586 *
587 * @param table the table
588 * @param operation the operation type (see {@link UndoLogRecord})
589 * @param row the row
590 */
591 public void log(Table table, short operation, Row row) {
592 if (undoLogEnabled) {
593 UndoLogRecord log = new UndoLogRecord(table, operation, row);
594 // called _after_ the row was inserted successfully into the table,
595 // otherwise rollback will try to rollback a not-inserted row
596 if (SysProperties.CHECK) {
597 int lockMode = database.getLockMode();
598 if (lockMode != Constants.LOCK_MODE_OFF && !database.isMultiVersion()) {
599 String tableType = log.getTable().getTableType();
600 if (locks.indexOf(log.getTable()) < 0
601 && !Table.TABLE_LINK.equals(tableType)
602 && !Table.EXTERNAL_TABLE_ENGINE.equals(tableType)) {
603 DbException.throwInternalError();
604 }
605 }
606 }
607 undoLog.add(log);
608 } else {
609 // see also UndoLogRecord.commit
610 ArrayList<Index> indexes = table.getIndexes();
611 for (int i = 0, size = indexes.size(); i < size; i++) {
612 Index index = indexes.get(i);
613 index.commit(operation, row);
614 }
615 row.commit();
616 }
617 }
618
619 /**
620 * Unlock all read locks. This is done if the transaction isolation mode is
621 * READ_COMMITTED.
622 */
623 public void unlockReadLocks() {
624 if (database.isMultiVersion()) {
625 // MVCC: keep shared locks (insert / update / delete)
626 return;
627 }
628 // locks is modified in the loop
629 for (int i = 0; i < locks.size(); i++) {
630 Table t = locks.get(i);
631 if (!t.isLockedExclusively()) {
632 synchronized (database) {
633 t.unlock(this);
634 locks.remove(i);
635 }
636 i--;
637 }
638 }
639 }
640
641 /**
642 * Unlock just this table.
643 *
644 * @param t the table to unlock
645 */
646 void unlock(Table t) {
647 locks.remove(t);
648 }
649
650 private void unlockAll() {
651 if (SysProperties.CHECK) {
652 if (undoLog.size() > 0) {
653 DbException.throwInternalError();
654 }
655 }
656 if (locks.size() > 0) {
657 synchronized (database) {
658 // don't use the enhanced for loop to save memory
659 for (int i = 0, size = locks.size(); i < size; i++) {
660 Table t = locks.get(i);
661 t.unlock(this);
662 }
663 locks.clear();
664 }
665 }
666 savepoints = null;
667 sessionStateChanged = true;
668 }
669
670 private void cleanTempTables(boolean closeSession) {
671 if (localTempTables != null && localTempTables.size() > 0) {
672 synchronized (database) {
673 for (Table table : New.arrayList(localTempTables.values())) {
674 if (closeSession || table.getOnCommitDrop()) {
675 modificationId++;
676 table.setModified();
677 localTempTables.remove(table.getName());
678 table.removeChildrenAndResources(this);
679 } else if (table.getOnCommitTruncate()) {
680 table.truncate(this);
681 }
682 }
683 }
684 }
685 }
686
687 public Random getRandom() {
688 if (random == null) {
689 random = new Random();
690 }
691 return random;
692 }
693
694 public Trace getTrace() {
695 if (trace != null && !closed) {
696 return trace;
697 }
698 String traceModuleName = Trace.JDBC + "[" + id + "]";
699 if (closed) {
700 return new TraceSystem(null).getTrace(traceModuleName);
701 }
702 trace = database.getTrace(traceModuleName);
703 return trace;
704 }
705
706 public void setLastIdentity(Value last) {
707 this.lastIdentity = last;
708 this.lastScopeIdentity = last;
709 }
710
711 public Value getLastIdentity() {
712 return lastIdentity;
713 }
714
715 public void setLastScopeIdentity(Value last) {
716 this.lastScopeIdentity = last;
717 }
718
719 public Value getLastScopeIdentity() {
720 return lastScopeIdentity;
721 }
722
723 /**
724 * Called when a log entry for this session is added. The session keeps
725 * track of the first entry in the transaction log that is not yet committed.
726 *
727 * @param logId the transaction log id
728 * @param pos the position of the log entry in the transaction log
729 */
730 public void addLogPos(int logId, int pos) {
731 if (firstUncommittedLog == Session.LOG_WRITTEN) {
732 firstUncommittedLog = logId;
733 firstUncommittedPos = pos;
734 }
735 }
736
737 public int getFirstUncommittedLog() {
738 return firstUncommittedLog;
739 }
740
741 /**
742 * This method is called after the transaction log has written the commit
743 * entry for this session.
744 */
745 void setAllCommitted() {
746 firstUncommittedLog = Session.LOG_WRITTEN;
747 firstUncommittedPos = Session.LOG_WRITTEN;
748 }
749
750 private boolean containsUncommitted() {
751 return firstUncommittedLog != Session.LOG_WRITTEN;
752 }
753
754 /**
755 * Create a savepoint that is linked to the current log position.
756 *
757 * @param name the savepoint name
758 */
759 public void addSavepoint(String name) {
760 if (savepoints == null) {
761 savepoints = database.newStringMap();
762 }
763 savepoints.put(name, getUndoLogPos());
764 }
765
766 /**
767 * Undo all operations back to the log position of the given savepoint.
768 *
769 * @param name the savepoint name
770 */
771 public void rollbackToSavepoint(String name) {
772 checkCommitRollback();
773 if (savepoints == null) {
774 throw DbException.get(ErrorCode.SAVEPOINT_IS_INVALID_1, name);
775 }
776 Integer savepointIndex = savepoints.get(name);
777 if (savepointIndex == null) {
778 throw DbException.get(ErrorCode.SAVEPOINT_IS_INVALID_1, name);
779 }
780 int i = savepointIndex.intValue();
781 rollbackTo(i, false);
782 }
783
784 /**
785 * Prepare the given transaction.
786 *
787 * @param transactionName the name of the transaction
788 */
789 public void prepareCommit(String transactionName) {
790 if (containsUncommitted()) {
791 // need to commit even if rollback is not possible (create/drop
792 // table and so on)
793 database.prepareCommit(this, transactionName);
794 }
795 currentTransactionName = transactionName;
796 }
797
798 /**
799 * Commit or roll back the given transaction.
800 *
801 * @param transactionName the name of the transaction
802 * @param commit true for commit, false for rollback
803 */
804 public void setPreparedTransaction(String transactionName, boolean commit) {
805 if (currentTransactionName != null && currentTransactionName.equals(transactionName)) {
806 if (commit) {
807 commit(false);
808 } else {
809 rollback();
810 }
811 } else {
812 ArrayList<InDoubtTransaction> list = database.getInDoubtTransactions();
813 int state = commit ? InDoubtTransaction.COMMIT : InDoubtTransaction.ROLLBACK;
814 boolean found = false;
815 if (list != null) {
816 for (InDoubtTransaction p: list) {
817 if (p.getTransaction().equals(transactionName)) {
818 p.setState(state);
819 found = true;
820 break;
821 }
822 }
823 }
824 if (!found) {
825 throw DbException.get(ErrorCode.TRANSACTION_NOT_FOUND_1, transactionName);
826 }
827 }
828 }
829
830 public boolean isClosed() {
831 return closed;
832 }
833
834 public void setThrottle(int throttle) {
835 this.throttle = throttle;
836 }
837
838 /**
839 * Wait for some time if this session is throttled (slowed down).
840 */
841 public void throttle() {
842 if (currentCommandStart == 0) {
843 currentCommandStart = System.currentTimeMillis();
844 }
845 if (throttle == 0) {
846 return;
847 }
848 long time = System.currentTimeMillis();
849 if (lastThrottle + Constants.THROTTLE_DELAY > time) {
850 return;
851 }
852 lastThrottle = time + throttle;
853 try {
854 Thread.sleep(throttle);
855 } catch (Exception e) {
856 // ignore InterruptedException
857 }
858 }
859
860 /**
861 * Set the current command of this session. This is done just before
862 * executing the statement.
863 *
864 * @param command the command
865 */
866 public void setCurrentCommand(Command command) {
867 this.currentCommand = command;
868 if (queryTimeout > 0 && command != null) {
869 long now = System.currentTimeMillis();
870 currentCommandStart = now;
871 cancelAt = now + queryTimeout;
872 }
873 }
874
875 /**
876 * Check if the current transaction is canceled by calling
877 * Statement.cancel() or because a session timeout was set and expired.
878 *
879 * @throws DbException if the transaction is canceled
880 */
881 public void checkCanceled() {
882 throttle();
883 if (cancelAt == 0) {
884 return;
885 }
886 long time = System.currentTimeMillis();
887 if (time >= cancelAt) {
888 cancelAt = 0;
889 throw DbException.get(ErrorCode.STATEMENT_WAS_CANCELED);
890 }
891 }
892
893 /**
894 * Get the cancel time.
895 *
896 * @return the time or 0 if not set
897 */
898 public long getCancel() {
899 return cancelAt;
900 }
901
902 public Command getCurrentCommand() {
903 return currentCommand;
904 }
905
906 public long getCurrentCommandStart() {
907 return currentCommandStart;
908 }
909
910 public boolean getAllowLiterals() {
911 return allowLiterals;
912 }
913
914 public void setAllowLiterals(boolean b) {
915 this.allowLiterals = b;
916 }
917
918 public void setCurrentSchema(Schema schema) {
919 modificationId++;
920 this.currentSchemaName = schema.getName();
921 }
922
923 public String getCurrentSchemaName() {
924 return currentSchemaName;
925 }
926
927 /**
928 * Create an internal connection. This connection is used when initializing
929 * triggers, and when calling user defined functions.
930 *
931 * @param columnList if the url should be 'jdbc:columnlist:connection'
932 * @return the internal connection
933 */
934 public JdbcConnection createConnection(boolean columnList) {
935 String url;
936 if (columnList) {
937 url = Constants.CONN_URL_COLUMNLIST;
938 } else {
939 url = Constants.CONN_URL_INTERNAL;
940 }
941 return new JdbcConnection(this, getUser().getName(), url);
942 }
943
944 public DataHandler getDataHandler() {
945 return database;
946 }
947
948 /**
949 * Remember that the given LOB value must be un-linked (disconnected from
950 * the table) at commit.
951 *
952 * @param v the value
953 */
954 public void unlinkAtCommit(Value v) {
955 if (SysProperties.CHECK && !v.isLinked()) {
956 DbException.throwInternalError();
957 }
958 if (unlinkLobMap == null) {
959 unlinkLobMap = New.hashMap();
960 }
961 unlinkLobMap.put(v.toString(), v);
962 }
963
964 /**
965 * Do not unlink this LOB value at commit any longer.
966 *
967 * @param v the value
968 */
969 public void unlinkAtCommitStop(Value v) {
970 if (unlinkLobMap != null) {
971 unlinkLobMap.remove(v.toString());
972 }
973 }
974
975 /**
976 * Get the next system generated identifiers. The identifier returned does
977 * not occur within the given SQL statement.
978 *
979 * @param sql the SQL statement
980 * @return the new identifier
981 */
982 public String getNextSystemIdentifier(String sql) {
983 String identifier;
984 do {
985 identifier = SYSTEM_IDENTIFIER_PREFIX + systemIdentifier++;
986 } while (sql.indexOf(identifier) >= 0);
987 return identifier;
988 }
989
990 /**
991 * Add a procedure to this session.
992 *
993 * @param procedure the procedure to add
994 */
995 public void addProcedure(Procedure procedure) {
996 if (procedures == null) {
997 procedures = database.newStringMap();
998 }
999 procedures.put(procedure.getName(), procedure);
1000 }
1001
1002 /**
1003 * Remove a procedure from this session.
1004 *
1005 * @param name the name of the procedure to remove
1006 */
1007 public void removeProcedure(String name) {
1008 if (procedures != null) {
1009 procedures.remove(name);
1010 }
1011 }
1012
1013 /**
1014 * Get the procedure with the given name, or null
1015 * if none exists.
1016 *
1017 * @param name the procedure name
1018 * @return the procedure or null
1019 */
1020 public Procedure getProcedure(String name) {
1021 if (procedures == null) {
1022 return null;
1023 }
1024 return procedures.get(name);
1025 }
1026
1027 public void setSchemaSearchPath(String[] schemas) {
1028 modificationId++;
1029 this.schemaSearchPath = schemas;
1030 }
1031
1032 public String[] getSchemaSearchPath() {
1033 return schemaSearchPath;
1034 }
1035
1036 public int hashCode() {
1037 return serialId;
1038 }
1039
1040 public String toString() {
1041 return "#" + serialId + " (user: " + user.getName() + ")";
1042 }
1043
1044 public void setUndoLogEnabled(boolean b) {
1045 this.undoLogEnabled = b;
1046 }
1047
1048 public void setRedoLogBinary(boolean b) {
1049 this.redoLogBinary = b;
1050 }
1051
1052 public boolean isUndoLogEnabled() {
1053 return undoLogEnabled;
1054 }
1055
1056 /**
1057 * Begin a transaction.
1058 */
1059 public void begin() {
1060 autoCommitAtTransactionEnd = true;
1061 autoCommit = false;
1062 }
1063
1064 public long getSessionStart() {
1065 return sessionStart;
1066 }
1067
1068 public long getTransactionStart() {
1069 if (transactionStart == 0) {
1070 transactionStart = System.currentTimeMillis();
1071 }
1072 return transactionStart;
1073 }
1074
1075 public Table[] getLocks() {
1076 // copy the data without synchronizing
1077 ArrayList<Table> copy = New.arrayList();
1078 for (int i = 0; i < locks.size(); i++) {
1079 try {
1080 copy.add(locks.get(i));
1081 } catch (Exception e) {
1082 // ignore
1083 break;
1084 }
1085 }
1086 Table[] list = new Table[copy.size()];
1087 copy.toArray(list);
1088 return list;
1089 }
1090
1091 /**
1092 * Wait if the exclusive mode has been enabled for another session. This
1093 * method returns as soon as the exclusive mode has been disabled.
1094 */
1095 public void waitIfExclusiveModeEnabled() {
1096 while (true) {
1097 Session exclusive = database.getExclusiveSession();
1098 if (exclusive == null || exclusive == this) {
1099 break;
1100 }
1101 if (Thread.holdsLock(exclusive)) {
1102 // if another connection is used within the connection
1103 break;
1104 }
1105 try {
1106 Thread.sleep(100);
1107 } catch (InterruptedException e) {
1108 // ignore
1109 }
1110 }
1111 }
1112
1113 /**
1114 * Remember the result set and close it as soon as the transaction is
1115 * committed (if it needs to be closed). This is done to delete temporary
1116 * files as soon as possible, and free object ids of temporary tables.
1117 *
1118 * @param result the temporary result set
1119 */
1120 public void addTemporaryResult(ResultInterface result) {
1121 if (!result.needToClose()) {
1122 return;
1123 }
1124 if (temporaryResults == null) {
1125 temporaryResults = New.hashSet();
1126 }
1127 if (temporaryResults.size() < 100) {
1128 // reference at most 100 result sets to avoid memory problems
1129 temporaryResults.add(result);
1130 }
1131 }
1132
1133 /**
1134 * Close all temporary result set. This also deletes all temporary files
1135 * held by the result sets.
1136 */
1137 public void closeTemporaryResults() {
1138 if (temporaryResults != null) {
1139 for (ResultInterface result : temporaryResults) {
1140 result.close();
1141 }
1142 temporaryResults = null;
1143 }
1144 }
1145
1146 public void setQueryTimeout(int queryTimeout) {
1147 int max = database.getSettings().maxQueryTimeout;
1148 if (max != 0 && (max < queryTimeout || queryTimeout == 0)) {
1149 // the value must be at most max
1150 queryTimeout = max;
1151 }
1152 this.queryTimeout = queryTimeout;
1153 // must reset the cancel at here,
1154 // otherwise it is still used
1155 this.cancelAt = 0;
1156 }
1157
1158 public int getQueryTimeout() {
1159 return queryTimeout;
1160 }
1161
1162 public void setWaitForLock(Table table) {
1163 this.waitForLock = table;
1164 }
1165
1166 public Table getWaitForLock() {
1167 return waitForLock;
1168 }
1169
1170 public int getModificationId() {
1171 return modificationId;
1172 }
1173
1174 public boolean isReconnectNeeded(boolean write) {
1175 while (true) {
1176 boolean reconnect = database.isReconnectNeeded();
1177 if (reconnect) {
1178 return true;
1179 }
1180 if (write) {
1181 if (database.beforeWriting()) {
1182 return false;
1183 }
1184 } else {
1185 return false;
1186 }
1187 }
1188 }
1189
1190 public void afterWriting() {
1191 database.afterWriting();
1192 }
1193
1194 public SessionInterface reconnect(boolean write) {
1195 readSessionState();
1196 close();
1197 Session newSession = Engine.getInstance().createSession(connectionInfo);
1198 newSession.sessionState = sessionState;
1199 newSession.recreateSessionState();
1200 if (write) {
1201 while (!newSession.database.beforeWriting()) {
1202 // wait until we are allowed to write
1203 }
1204 }
1205 return newSession;
1206 }
1207
1208 public void setConnectionInfo(ConnectionInfo ci) {
1209 connectionInfo = ci;
1210 }
1211
1212 public Value getTransactionId() {
1213 if (undoLog.size() == 0 || !database.isPersistent()) {
1214 return ValueNull.INSTANCE;
1215 }
1216 return ValueString.get(firstUncommittedLog + "-" + firstUncommittedPos + "-" + id);
1217 }
1218
1219 /**
1220 * Get the next object id.
1221 *
1222 * @return the next object id
1223 */
1224 public int nextObjectId() {
1225 return objectId++;
1226 }
1227
1228 public boolean isRedoLogBinaryEnabled() {
1229 return redoLogBinary;
1230 }
1231
1232 }
1233