001: /*
002: * Copyright 2004-2008 H2 Group. Licensed under the H2 License, Version 1.0
003: * (http://h2database.com/html/license.html).
004: * Initial Developer: H2 Group
005: */
006: package org.h2.engine;
007:
008: import java.sql.SQLException;
009: import java.util.ArrayList;
010: import java.util.HashMap;
011: import java.util.HashSet;
012: import java.util.Iterator;
013: import java.util.Random;
014:
015: import org.h2.command.Command;
016: import org.h2.command.CommandInterface;
017: import org.h2.command.Parser;
018: import org.h2.command.Prepared;
019: import org.h2.command.dml.SetTypes;
020: import org.h2.constant.ErrorCode;
021: import org.h2.constant.SysProperties;
022: import org.h2.jdbc.JdbcConnection;
023: import org.h2.log.InDoubtTransaction;
024: import org.h2.log.LogSystem;
025: import org.h2.log.UndoLog;
026: import org.h2.log.UndoLogRecord;
027: import org.h2.message.Message;
028: import org.h2.message.Trace;
029: import org.h2.message.TraceSystem;
030: import org.h2.result.LocalResult;
031: import org.h2.result.Row;
032: import org.h2.schema.Schema;
033: import org.h2.store.DataHandler;
034: import org.h2.table.Table;
035: import org.h2.util.ObjectArray;
036: import org.h2.util.ObjectUtils;
037: import org.h2.value.Value;
038: import org.h2.value.ValueLob;
039: import org.h2.value.ValueLong;
040: import org.h2.value.ValueNull;
041:
042: /**
043: * A session represents a database connection. When using the server mode, this
044: * object resides on the server side and communicates with a RemoteSession on
045: * the client side.
046: */
047: public class Session implements SessionInterface {
048:
049: private User user;
050: private int id;
051: private Database database;
052: private ObjectArray locks = new ObjectArray();
053: private UndoLog undoLog;
054: private boolean autoCommit = true;
055: private Random random;
056: private LogSystem logSystem;
057: private int lockTimeout;
058: private Value lastIdentity = ValueLong.get(0);
059: private int firstUncommittedLog = LogSystem.LOG_WRITTEN;
060: private int firstUncommittedPos = LogSystem.LOG_WRITTEN;
061: private HashMap savepoints;
062: private Exception stackTrace = new Exception();
063: private HashMap localTempTables;
064: private int throttle;
065: private long lastThrottle;
066: private Command currentCommand;
067: private boolean allowLiterals;
068: private String currentSchemaName;
069: private String[] schemaSearchPath;
070: private String traceModuleName;
071: private HashMap unlinkMap;
072: private int tempViewIndex;
073: private HashMap procedures;
074: private static int nextSerialId;
075: private int serialId = nextSerialId++;
076: private boolean undoLogEnabled = true;
077: private boolean autoCommitAtTransactionEnd;
078: private String currentTransactionName;
079: private volatile long cancelAt;
080: private boolean closed;
081: private boolean rollbackMode;
082: private long sessionStart = System.currentTimeMillis();
083: private long currentCommandStart;
084: private HashMap variables;
085: private HashSet temporaryResults;
086: private int queryTimeout = SysProperties.getMaxQueryTimeout();
087: private int lastUncommittedDelete;
088: private boolean commitOrRollbackDisabled;
089:
090: public Session() {
091: }
092:
093: public boolean setCommitOrRollbackDisabled(boolean x) {
094: boolean old = commitOrRollbackDisabled;
095: commitOrRollbackDisabled = x;
096: return old;
097: }
098:
099: private void initVariables() {
100: if (variables == null) {
101: variables = new HashMap();
102: }
103: }
104:
105: public void setVariable(String name, Value value)
106: throws SQLException {
107: initVariables();
108: Value old;
109: if (value == ValueNull.INSTANCE) {
110: old = (Value) variables.remove(name);
111: } else {
112: if (value instanceof ValueLob) {
113: // link it, to make sure we have our own file
114: value = value.link(database, ValueLob.TABLE_ID_SESSION);
115: }
116: old = (Value) variables.put(name, value);
117: }
118: if (old != null) {
119: // close the old value (in case it is a lob)
120: old.unlink();
121: old.close();
122: }
123: }
124:
125: public Value getVariable(String name) {
126: initVariables();
127: Value v = (Value) variables.get(name);
128: return v == null ? ValueNull.INSTANCE : v;
129: }
130:
131: public Table findLocalTempTable(String name) {
132: Table t = null;
133: if (localTempTables != null) {
134: t = (Table) localTempTables.get(name);
135: }
136: return t;
137: }
138:
139: public ObjectArray getLocalTempTables() {
140: if (localTempTables == null) {
141: return new ObjectArray();
142: }
143: ObjectArray list = new ObjectArray(localTempTables.values());
144: return list;
145: }
146:
147: public void addLocalTempTable(Table table) throws SQLException {
148: if (localTempTables == null) {
149: localTempTables = new HashMap();
150: }
151: if (localTempTables.get(table.getName()) != null) {
152: throw Message.getSQLException(
153: ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, table
154: .getSQL());
155: }
156: localTempTables.put(table.getName(), table);
157: }
158:
159: public void removeLocalTempTable(Table table) throws SQLException {
160: localTempTables.remove(table.getName());
161: table.removeChildrenAndResources(this );
162: }
163:
164: protected void finalize() {
165: if (!SysProperties.runFinalize) {
166: return;
167: }
168: if (!closed) {
169: throw Message.getInternalError("not closed", stackTrace);
170: }
171: }
172:
173: public boolean getAutoCommit() {
174: return autoCommit;
175: }
176:
177: public User getUser() {
178: return user;
179: }
180:
181: public void setAutoCommit(boolean b) {
182: autoCommit = b;
183: }
184:
185: public int getLockTimeout() {
186: return lockTimeout;
187: }
188:
189: public void setLockTimeout(int lockTimeout) {
190: this .lockTimeout = lockTimeout;
191: }
192:
193: public SessionInterface createSession(ConnectionInfo ci)
194: throws SQLException {
195: return Engine.getInstance().getSession(ci);
196: }
197:
198: Session(Database database, User user, int id) {
199: this .database = database;
200: this .undoLog = new UndoLog(this );
201: this .user = user;
202: this .id = id;
203: this .logSystem = database.getLog();
204: Setting setting = database.findSetting(SetTypes
205: .getTypeName(SetTypes.DEFAULT_LOCK_TIMEOUT));
206: this .lockTimeout = setting == null ? Constants.INITIAL_LOCK_TIMEOUT
207: : setting.getIntValue();
208: this .currentSchemaName = Constants.SCHEMA_MAIN;
209: }
210:
211: public CommandInterface prepareCommand(String sql, int fetchSize)
212: throws SQLException {
213: return prepareLocal(sql);
214: }
215:
216: public Prepared prepare(String sql) throws SQLException {
217: return prepare(sql, false);
218: }
219:
220: public Prepared prepare(String sql, boolean rightsChecked)
221: throws SQLException {
222: Parser parser = new Parser(this );
223: parser.setRightsChecked(rightsChecked);
224: return parser.prepare(sql);
225: }
226:
227: public Command prepareLocal(String sql) throws SQLException {
228: if (closed) {
229: throw Message.getSQLException(ErrorCode.CONNECTION_BROKEN);
230: }
231: Parser parser = new Parser(this );
232: return parser.prepareCommand(sql);
233: }
234:
235: public Database getDatabase() {
236: return database;
237: }
238:
239: public int getPowerOffCount() {
240: return database.getPowerOffCount();
241: }
242:
243: public void setPowerOffCount(int count) {
244: database.setPowerOffCount(count);
245: }
246:
247: public int getLastUncommittedDelete() {
248: return lastUncommittedDelete;
249: }
250:
251: public void setLastUncommittedDelete(int deleteId) {
252: lastUncommittedDelete = deleteId;
253: }
254:
255: public void commit(boolean ddl) throws SQLException {
256: checkCommitRollback();
257: lastUncommittedDelete = 0;
258: currentTransactionName = null;
259: if (containsUncommitted()) {
260: // need to commit even if rollback is not possible
261: // (create/drop table and so on)
262: logSystem.commit(this );
263: }
264: if (undoLog.size() > 0) {
265: if (database.isMultiVersion()) {
266: ArrayList rows = new ArrayList();
267: synchronized (database) {
268: while (undoLog.size() > 0) {
269: UndoLogRecord entry = undoLog
270: .getAndRemoveLast();
271: entry.commit();
272: rows.add(entry.getRow());
273: }
274: for (int i = 0; i < rows.size(); i++) {
275: Row r = (Row) rows.get(i);
276: r.commit();
277: }
278: }
279: }
280: undoLog.clear();
281: }
282: if (!ddl) {
283: // do not clean the temp tables if the last command was a
284: // create/drop
285: cleanTempTables(false);
286: if (autoCommitAtTransactionEnd) {
287: autoCommit = true;
288: autoCommitAtTransactionEnd = false;
289: }
290: }
291: if (unlinkMap != null && unlinkMap.size() > 0) {
292: // need to flush the log file, because we can't unlink lobs if the
293: // commit record is not written
294: logSystem.flush();
295: Iterator it = unlinkMap.values().iterator();
296: while (it.hasNext()) {
297: Value v = (Value) it.next();
298: v.unlink();
299: }
300: unlinkMap = null;
301: }
302: unlockAll();
303: }
304:
305: private void checkCommitRollback() throws SQLException {
306: if (commitOrRollbackDisabled && locks.size() > 0) {
307: throw Message
308: .getSQLException(ErrorCode.COMMIT_ROLLBACK_NOT_ALLOWED);
309: }
310: }
311:
312: public void rollback() throws SQLException {
313: checkCommitRollback();
314: currentTransactionName = null;
315: boolean needCommit = false;
316: if (undoLog.size() > 0) {
317: rollbackTo(0);
318: needCommit = true;
319: }
320: if (locks.size() > 0 || needCommit) {
321: logSystem.commit(this );
322: }
323: cleanTempTables(false);
324: unlockAll();
325: if (autoCommitAtTransactionEnd) {
326: autoCommit = true;
327: autoCommitAtTransactionEnd = false;
328: }
329: }
330:
331: public void rollbackTo(int index) throws SQLException {
332: while (undoLog.size() > index) {
333: UndoLogRecord entry = undoLog.getAndRemoveLast();
334: rollbackMode = true;
335: try {
336: entry.undo(this );
337: } finally {
338: rollbackMode = false;
339: }
340: }
341: if (savepoints != null) {
342: String[] names = new String[savepoints.size()];
343: savepoints.keySet().toArray(names);
344: for (int i = 0; i < names.length; i++) {
345: String name = names[i];
346: Integer id = (Integer) savepoints.get(names[i]);
347: if (id.intValue() > index) {
348: savepoints.remove(name);
349: }
350: }
351: }
352: }
353:
354: public int getLogId() {
355: return undoLog.size();
356: }
357:
358: public int getId() {
359: return id;
360: }
361:
362: public void cancel() {
363: cancelAt = System.currentTimeMillis();
364: }
365:
366: public void close() throws SQLException {
367: if (!closed) {
368: try {
369: cleanTempTables(true);
370: database.removeSession(this );
371: } finally {
372: closed = true;
373: }
374: }
375: }
376:
377: public void addLock(Table table) {
378: if (SysProperties.CHECK) {
379: if (locks.indexOf(table) >= 0) {
380: throw Message.getInternalError();
381: }
382: }
383: locks.add(table);
384: }
385:
386: /**
387: * Add an undo log entry to this session.
388: *
389: * @param table the table
390: * @param type the operation type (see {@link UndoLogRecord})
391: * @param row the row
392: */
393: public void log(Table table, short type, Row row)
394: throws SQLException {
395: log(new UndoLogRecord(table, type, row));
396: }
397:
398: private void log(UndoLogRecord log) throws SQLException {
399: // called _after_ the row was inserted successfully into the table,
400: // otherwise rollback will try to rollback a not-inserted row
401: if (SysProperties.CHECK) {
402: int lockMode = database.getLockMode();
403: if (lockMode != Constants.LOCK_MODE_OFF
404: && !database.isMultiVersion()) {
405: if (locks.indexOf(log.getTable()) < 0
406: && log.getTable().getTableType() != Table.TABLE_LINK) {
407: throw Message.getInternalError();
408: }
409: }
410: }
411: if (undoLogEnabled) {
412: undoLog.add(log);
413: }
414: }
415:
416: public void unlockReadLocks() {
417: if (database.isMultiVersion()) {
418: // MVCC: keep shared locks (insert / update / delete)
419: return;
420: }
421: for (int i = 0; i < locks.size(); i++) {
422: Table t = (Table) locks.get(i);
423: if (!t.isLockedExclusively()) {
424: synchronized (database) {
425: t.unlock(this );
426: locks.remove(i);
427: }
428: i--;
429: }
430: }
431: }
432:
433: private void unlockAll() throws SQLException {
434: if (SysProperties.CHECK) {
435: if (undoLog.size() > 0) {
436: throw Message.getInternalError();
437: }
438: }
439: synchronized (database) {
440: for (int i = 0; i < locks.size(); i++) {
441: Table t = (Table) locks.get(i);
442: t.unlock(this );
443: }
444: locks.clear();
445: }
446: savepoints = null;
447: }
448:
449: private void cleanTempTables(boolean closeSession)
450: throws SQLException {
451: if (localTempTables != null && localTempTables.size() > 0) {
452: ObjectArray list = new ObjectArray(localTempTables.values());
453: for (int i = 0; i < list.size(); i++) {
454: Table table = (Table) list.get(i);
455: if (closeSession || table.isOnCommitDrop()) {
456: table.setModified();
457: localTempTables.remove(table.getName());
458: table.removeChildrenAndResources(this );
459: } else if (table.isOnCommitTruncate()) {
460: table.truncate(this );
461: }
462: }
463: }
464: }
465:
466: public Random getRandom() {
467: if (random == null) {
468: random = new Random();
469: }
470: return random;
471: }
472:
473: public Trace getTrace() {
474: if (traceModuleName == null) {
475: traceModuleName = Trace.JDBC + "[" + id + "]";
476: }
477: if (closed) {
478: return new TraceSystem(null, false)
479: .getTrace(traceModuleName);
480: }
481: return database.getTrace(traceModuleName);
482: }
483:
484: public void setLastIdentity(Value last) {
485: this .lastIdentity = last;
486: }
487:
488: public Value getLastIdentity() {
489: return lastIdentity;
490: }
491:
492: public void addLogPos(int logId, int pos) {
493: if (firstUncommittedLog == LogSystem.LOG_WRITTEN) {
494: firstUncommittedLog = logId;
495: firstUncommittedPos = pos;
496: }
497: }
498:
499: public int getFirstUncommittedLog() {
500: return firstUncommittedLog;
501: }
502:
503: public int getFirstUncommittedPos() {
504: return firstUncommittedPos;
505: }
506:
507: public void setAllCommitted() {
508: firstUncommittedLog = LogSystem.LOG_WRITTEN;
509: firstUncommittedPos = LogSystem.LOG_WRITTEN;
510: }
511:
512: private boolean containsUncommitted() {
513: return firstUncommittedLog != LogSystem.LOG_WRITTEN;
514: }
515:
516: public void addSavepoint(String name) {
517: if (savepoints == null) {
518: savepoints = new HashMap();
519: }
520: savepoints.put(name, ObjectUtils.getInteger(getLogId()));
521: }
522:
523: public void rollbackToSavepoint(String name) throws SQLException {
524: checkCommitRollback();
525: if (savepoints == null) {
526: throw Message.getSQLException(
527: ErrorCode.SAVEPOINT_IS_INVALID_1, name);
528: }
529: Integer id = (Integer) savepoints.get(name);
530: if (id == null) {
531: throw Message.getSQLException(
532: ErrorCode.SAVEPOINT_IS_INVALID_1, name);
533: }
534: int i = id.intValue();
535: rollbackTo(i);
536: }
537:
538: public void prepareCommit(String transactionName)
539: throws SQLException {
540: if (containsUncommitted()) {
541: // need to commit even if rollback is not possible (create/drop
542: // table and so on)
543: logSystem.prepareCommit(this , transactionName);
544: }
545: currentTransactionName = transactionName;
546: }
547:
548: public void setPreparedTransaction(String transactionName,
549: boolean commit) throws SQLException {
550: if (currentTransactionName != null
551: && currentTransactionName.equals(transactionName)) {
552: if (commit) {
553: commit(false);
554: } else {
555: rollback();
556: }
557: } else {
558: ObjectArray list = logSystem.getInDoubtTransactions();
559: int state = commit ? InDoubtTransaction.COMMIT
560: : InDoubtTransaction.ROLLBACK;
561: boolean found = false;
562: for (int i = 0; list != null && i < list.size(); i++) {
563: InDoubtTransaction p = (InDoubtTransaction) list.get(i);
564: if (p.getTransaction().equals(transactionName)) {
565: p.setState(state);
566: found = true;
567: break;
568: }
569: }
570: if (!found) {
571: throw Message.getSQLException(
572: ErrorCode.TRANSACTION_NOT_FOUND_1,
573: transactionName);
574: }
575: }
576: }
577:
578: public boolean isClosed() {
579: return closed;
580: }
581:
582: public void setThrottle(int throttle) {
583: this .throttle = throttle;
584: }
585:
586: public void throttle() {
587: if (throttle == 0) {
588: return;
589: }
590: long time = System.currentTimeMillis();
591: if (lastThrottle + Constants.THROTTLE_DELAY > time) {
592: return;
593: }
594: lastThrottle = time + throttle;
595: try {
596: Thread.sleep(throttle);
597: } catch (Exception e) {
598: // ignore
599: }
600: }
601:
602: public void setCurrentCommand(Command command, long startTime) {
603: this .currentCommand = command;
604: this .currentCommandStart = startTime;
605: if (queryTimeout > 0) {
606: cancelAt = startTime + queryTimeout;
607: }
608: }
609:
610: public void checkCancelled() throws SQLException {
611: throttle();
612: if (cancelAt == 0) {
613: return;
614: }
615: long time = System.currentTimeMillis();
616: if (time >= cancelAt) {
617: cancelAt = 0;
618: throw Message
619: .getSQLException(ErrorCode.STATEMENT_WAS_CANCELLED);
620: }
621: }
622:
623: public Command getCurrentCommand() {
624: return currentCommand;
625: }
626:
627: public long getCurrentCommandStart() {
628: return currentCommandStart;
629: }
630:
631: public boolean getAllowLiterals() {
632: return allowLiterals;
633: }
634:
635: public void setAllowLiterals(boolean b) {
636: this .allowLiterals = b;
637: }
638:
639: public void setCurrentSchema(Schema schema) {
640: this .currentSchemaName = schema.getName();
641: }
642:
643: public String getCurrentSchemaName() {
644: return currentSchemaName;
645: }
646:
647: public JdbcConnection createConnection(boolean columnList)
648: throws SQLException {
649: String url;
650: if (columnList) {
651: url = Constants.CONN_URL_COLUMNLIST;
652: } else {
653: url = Constants.CONN_URL_INTERNAL;
654: }
655: return new JdbcConnection(this , getUser().getName(), url);
656: }
657:
658: public DataHandler getDataHandler() {
659: return database;
660: }
661:
662: public void unlinkAtCommit(Value v) {
663: if (unlinkMap == null) {
664: unlinkMap = new HashMap();
665: }
666: unlinkMap.put(v.toString(), v);
667: }
668:
669: public void unlinkAtCommitStop(Value v) {
670: if (unlinkMap != null) {
671: unlinkMap.remove(v.toString());
672: }
673: }
674:
675: public String getNextTempViewName() {
676: return "TEMP_VIEW_" + tempViewIndex++;
677: }
678:
679: public void addProcedure(Procedure procedure) {
680: if (procedures == null) {
681: procedures = new HashMap();
682: }
683: procedures.put(procedure.getName(), procedure);
684: }
685:
686: public void removeProcedure(String name) {
687: if (procedures != null) {
688: procedures.remove(name);
689: }
690: }
691:
692: public Procedure getProcedure(String name) {
693: if (procedures == null) {
694: return null;
695: }
696: return (Procedure) procedures.get(name);
697: }
698:
699: public void setSchemaSearchPath(String[] schemas) {
700: this .schemaSearchPath = schemas;
701: }
702:
703: public String[] getSchemaSearchPath() {
704: return schemaSearchPath;
705: }
706:
707: public int hashCode() {
708: return serialId;
709: }
710:
711: public void setUndoLogEnabled(boolean b) {
712: this .undoLogEnabled = b;
713: }
714:
715: public boolean getUndoLogEnabled() {
716: return undoLogEnabled;
717: }
718:
719: public void begin() {
720: autoCommitAtTransactionEnd = true;
721: autoCommit = false;
722: }
723:
724: public boolean getRollbackMode() {
725: return rollbackMode;
726: }
727:
728: public long getSessionStart() {
729: return sessionStart;
730: }
731:
732: public Table[] getLocks() {
733: synchronized (database) {
734: Table[] list = new Table[locks.size()];
735: locks.toArray(list);
736: return list;
737: }
738: }
739:
740: public void waitIfExclusiveModeEnabled() {
741: while (true) {
742: Session exclusive = database.getExclusiveSession();
743: if (exclusive == null || exclusive == this ) {
744: break;
745: }
746: try {
747: Thread.sleep(100);
748: } catch (InterruptedException e) {
749: // ignore
750: }
751: }
752: }
753:
754: public void addTemporaryResult(LocalResult result) {
755: if (temporaryResults == null) {
756: temporaryResults = new HashSet();
757: }
758: temporaryResults.add(result);
759: }
760:
761: public void closeTemporaryResults() {
762: if (temporaryResults != null) {
763: for (Iterator it = temporaryResults.iterator(); it
764: .hasNext();) {
765: LocalResult result = (LocalResult) it.next();
766: result.close();
767: }
768: }
769: }
770:
771: public void setQueryTimeout(int queryTimeout) {
772: int max = SysProperties.getMaxQueryTimeout();
773: if (max != 0 && (max < queryTimeout || queryTimeout == 0)) {
774: // the value must be at most max
775: queryTimeout = max;
776: }
777: this .queryTimeout = queryTimeout;
778: }
779:
780: public int getQueryTimeout() {
781: return queryTimeout;
782: }
783:
784: }
|