001: package com.quadcap.sql;
002:
003: /* Copyright 1999 - 2003 Quadcap Software. All rights reserved.
004: *
005: * This software is distributed under the Quadcap Free Software License.
006: * This software may be used or modified for any purpose, personal or
007: * commercial. Open Source redistributions are permitted. Commercial
008: * redistribution of larger works derived from, or works which bundle
009: * this software requires a "Commercial Redistribution License"; see
010: * http://www.quadcap.com/purchase.
011: *
012: * Redistributions qualify as "Open Source" under one of the following terms:
013: *
014: * Redistributions are made at no charge beyond the reasonable cost of
015: * materials and delivery.
016: *
017: * Redistributions are accompanied by a copy of the Source Code or by an
018: * irrevocable offer to provide a copy of the Source Code for up to three
019: * years at the cost of materials and delivery. Such redistributions
020: * must allow further use, modification, and redistribution of the Source
021: * Code under substantially the same terms as this license.
022: *
023: * Redistributions of source code must retain the copyright notices as they
024: * appear in each source code file, these license terms, and the
025: * disclaimer/limitation of liability set forth as paragraph 6 below.
026: *
027: * Redistributions in binary form must reproduce this Copyright Notice,
028: * these license terms, and the disclaimer/limitation of liability set
029: * forth as paragraph 6 below, in the documentation and/or other materials
030: * provided with the distribution.
031: *
032: * The Software is provided on an "AS IS" basis. No warranty is
033: * provided that the Software is free of defects, or fit for a
034: * particular purpose.
035: *
036: * Limitation of Liability. Quadcap Software shall not be liable
037: * for any damages suffered by the Licensee or any third party resulting
038: * from use of the Software.
039: */
040:
041: import java.io.IOException;
042:
043: import java.util.ArrayList;
044: import java.util.Enumeration;
045: import java.util.Hashtable;
046: import java.util.List;
047: import java.util.Random;
048:
049: import java.sql.ResultSet;
050: import java.sql.Statement;
051: import java.sql.SQLException;
052:
053: import com.quadcap.sql.index.Btree;
054: import com.quadcap.sql.index.Comparator;
055:
056: import com.quadcap.sql.file.BlockFile;
057: import com.quadcap.sql.file.ByteUtil;
058: import com.quadcap.sql.file.DatafileException;
059: import com.quadcap.sql.file.Log;
060: import com.quadcap.sql.file.LogEntry;
061:
062: import com.quadcap.sql.lock.Transaction;
063:
064: import com.quadcap.sql.io.ObjectOutputStream;
065:
066: import com.quadcap.util.Debug;
067:
068: /**
069: * Analagous (and mapped onto) a JDBC <code>Statement</code>, this class
070: * maintains state and locks on behalf of a single session.
071: *
072: * @author Stan Bailes
073: */
074: public class Session {
075: Connection qConn;
076: Database db;
077: ObjectOutputStream oos;
078:
079: ResultSet rs = null;
080: int updateCount = 0;
081: long lastInsertId = 0;
082:
083: Object plock = new Object();
084: List stmtPendingActions = null;
085:
086: List cursors = null;
087: Hashtable stmtContext = null;
088: int stmtId = 0;
089:
090: /** my position in qConn.sessions */
091: int sessionIndex = -1;
092: static int lastId = 0;
093:
094: boolean viewCheck = false;
095:
096: public boolean getViewCheck() {
097: return viewCheck;
098: }
099:
100: public void setViewCheck() {
101: viewCheck = true;
102: }
103:
104: public void clearViewCheck() {
105: viewCheck = false;
106: }
107:
108: /**
109: * Construct a new session for the specified database with the specified
110: * authorization
111: *
112: * @param db the database
113: * @param auth the authorization id
114: */
115: Session(Connection conn, int sessionIndex) {
116: this .qConn = conn;
117: this .sessionIndex = sessionIndex;
118: this .db = conn.getDatabase();
119: //this.id = lastId++;
120: this .lastInsertId = conn.getLastInsertId();
121: this .oos = new ObjectOutputStream(null);
122: }
123:
124: /**
125: * Accessor for my connection
126: */
127: public final Connection getConnection() {
128: return qConn;
129: }
130:
131: /**
132: * Accessor for my transaction (lazy)
133: */
134: public final Transaction getTransaction() {
135: return qConn.getTransaction();
136: }
137:
138: /**
139: * Accessor for my transaction id (lazy)
140: */
141: public final long getTransactionId() {
142: return qConn.getTransactionId();
143: }
144:
145: /**
146: * Return the transaction id, starting a new transaction if
147: * necessary
148: */
149: public final long makeTransaction() throws SQLException {
150: long trans = getTransactionId();
151: if (trans < 0) {
152: qConn.makeTransaction();
153: trans = qConn.getTransactionId();
154: stmtId = 0;
155: } else {
156: stmtId++;
157: }
158: return trans;
159: }
160:
161: /**
162: * Accessor for my logger
163: */
164: final public Log getLog() {
165: return db.getLog();
166: }
167:
168: /**
169: * Return my session id
170: */
171: public int getSessionId() {
172: return sessionIndex;
173: }
174:
175: /**
176: * Return the current statement number
177: */
178: public int getStmtId() {
179: return stmtId;
180: }
181:
182: /**
183: * Create a temporary btree with the specified comparator
184: */
185: final Btree makeTempTree(Comparator compare) throws IOException {
186: return qConn.makeTempTree(compare);
187: }
188:
189: /**
190: * Create a temporary btree with the default comparator
191: */
192: final Btree makeTempTree() throws IOException {
193: return qConn.makeTempTree();
194: }
195:
196: /**
197: * Accessor for my database
198: */
199: public final Database getDatabase() {
200: return db;
201: }
202:
203: /**
204: * Accessor for my file
205: */
206: public final BlockFile getFile() {
207: return qConn.getFile();
208: }
209:
210: /**
211: * Accessor for my 'Random' object
212: */
213: final Random getRandom() {
214: return qConn.getRandom();
215: }
216:
217: /**
218: * Helper routine to obtain a write lock on the specified table
219: */
220: public final void getTableWriteLock(String tableName)
221: throws SQLException, IOException {
222: qConn.getTableWriteLock(tableName);
223: }
224:
225: /**
226: * Helper routine to obtain a read lock on the specified table
227: */
228: public final void getTableReadLock(String tableName)
229: throws SQLException, IOException {
230: qConn.getTableReadLock(tableName);
231: }
232:
233: /**
234: * When a statement is initiated on a connection, any previous
235: * result set open on that connection is implicitly closed.
236: */
237: final void closeResultSet() {
238: //#ifdef TRACE
239: if (Trace.bit(9)) {
240: Debug.println(toString() + ".closeResultSet()");
241: }
242: //#endif
243: if (rs != null) {
244: try {
245: rs.close();
246: } catch (Throwable t) {
247: //#ifdef DEBUG
248: //Debug.print(t);
249: //#endif
250: } finally {
251: rs = null;
252: }
253: }
254: }
255:
256: /**
257: * Execute a single SQL statement.
258: */
259: public final void doStatement(Stmt s) throws IOException,
260: SQLException {
261: // ---- If the last statement had a result set, then commit
262: // ---- it (if auto commit) and close the result set. This is
263: // ---- mandated by JDBC.
264:
265: //#ifdef TRACE
266: if (Trace.bit(9)) {
267: Debug.println(toString() + ".doStatement(" + s + ")");
268: }
269: //#endif
270:
271: if (rs != null) {
272: closeResultSet();
273: }
274: makeTransaction();
275: if (qConn.readOnly) {
276: if (!(s instanceof SelectStmt)) {
277: throw new SQLException(
278: "Only SELECT statements are permitted in read-only mode");
279: }
280: s.execute(this );
281: } else {
282: updateCount = 0;
283: beginStatement();
284:
285: try {
286: s.execute(this );
287: } catch (IOException e) {
288: //#ifdef DEBUG
289: Debug.print(e);
290: //#endif
291: rollbackStatement();
292: throw e;
293: } catch (SQLException e) {
294: //#ifdef DEBUG
295: if (Trace.bit(1)) {
296: Debug.println("--- Verbose exception report: ");
297: Debug.print(e);
298: }
299: //#endif
300: rollbackStatement();
301: throw e;
302: } catch (Throwable e) {
303: //#ifdef DEBUG
304: Debug.print(e);
305: //#endif
306: rollbackStatement();
307: throw DbException.wrapThrowable(e);
308: }
309:
310: // ---- If no result set, then just commit (statement or
311: // ---- transaction level as required) now.
312: if (rs == null) {
313: endStatement(false);
314: }
315: }
316: }
317:
318: public final void beginStatement() throws IOException {
319: getLog().addEntry(
320: new LogEntry(getTransactionId(), stmtId,
321: LogEntry.BEGIN_STATEMENT));
322: }
323:
324: /**
325: * Called to conclude the execution of this statement.
326: *
327: * @param abort if <b>true</b>, we perform statement-level
328: * rollback, otherwise we conclude the statement (possibly
329: * committing the results if autoCommit is <b>true</b>.
330: */
331: public final void endStatement(boolean abort) throws IOException,
332: SQLException {
333: //#ifdef TRACE
334: if (Trace.bit(9)) {
335: Debug.println(toString() + ".endStatement(" + abort + ")");
336: }
337: //#endif
338: if (getTransactionId() >= 0) {
339: try {
340: closeCursors(abort);
341: } catch (SQLException e) {
342: rollbackStatement();
343: throw e;
344: }
345: qConn.endStatement(this , abort);
346: }
347: }
348:
349: /**
350: * Close any open cursors, aborting or commiting the current transaction
351: */
352: final void closeCursors(boolean abort) throws SQLException,
353: IOException {
354: if (cursors != null) {
355: for (int i = 0; i < cursors.size(); i++) {
356: Cursor c = (Cursor) cursors.get(i);
357: c.close();
358: }
359: cursors = null;
360: }
361: if (abort) {
362: stmtPendingActions = null;
363: } else {
364: doPendingActions();
365: }
366: if (stmtContext != null) {
367: try {
368: finishContexts(stmtContext, abort);
369: } finally {
370: stmtContext = null;
371: }
372: }
373: }
374:
375: /**
376: * Statement-level abort. The current statement has failed, but the
377: * transaction as a whole is still ongoing, so only the effects of
378: * the current statement so far should be undone.
379: */
380: final void rollbackStatement() throws IOException, SQLException {
381: //#ifdef TRACE
382: if (Trace.bit(9)) {
383: Debug.println(toString() + ".rollbackStatement()");
384: }
385: //#endif
386: synchronized (plock) {
387: stmtPendingActions = null;
388: }
389: qConn.rollbackStatement(this );
390: }
391:
392: /**
393: * Log and execute the given unit of work for this session
394: */
395: public final void doStep(LogStep s) throws SQLException,
396: IOException {
397: synchronized (db.getFileLock()) {
398: s.prepare(this );
399: //#ifdef DEBUG
400: if (false && Trace.bit(15)) {
401: Debug.println("[T:" + qConn.getTransactionId()
402: + "].redo(" + s + ")");
403: }
404: //#endif
405: try {
406: db.doStep(getTransaction(), s);
407: } catch (DatafileException e) {
408: //#ifdef DEBUG
409: //Debug.print(e);
410: //#endif
411: throw (SQLException) e.getCause();
412: }
413: }
414: }
415:
416: final void setResultSet(QedResultSet rs) {
417: this .rs = rs;
418: }
419:
420: /**
421: * Return the result set associated with this session
422: */
423: final ResultSet getResultSet() {
424: return rs;
425: }
426:
427: /**
428: * Return the result set associated with this session, after
429: * making sure that the the result set is bound to my Statement!
430: */
431: public final ResultSet getResultSet(Statement stmt) {
432: getResultSet();
433: if (rs != null)
434: ((QedResultSet) rs).setStatement(stmt);
435: return rs;
436: }
437:
438: /**
439: * Return the session update count
440: */
441: public final int getUpdateCount() {
442: return updateCount;
443: }
444:
445: final void setUpdateCount(int c) {
446: this .updateCount = c;
447: }
448:
449: final void incrUpdateCount() {
450: this .updateCount++;
451: }
452:
453: final void decrUpdateCount() {
454: this .updateCount++;
455: }
456:
457: /**
458: * An action to be executed once the currently open cursors are
459: * closed.
460: */
461: final void addPendingAction(LogStep action) {
462: synchronized (plock) {
463: if (stmtPendingActions == null)
464: stmtPendingActions = new ArrayList();
465: stmtPendingActions.add(action);
466: }
467: }
468:
469: /**
470: * At transaction end, execute all of the pending actions
471: */
472: final void doPendingActions() throws SQLException, IOException {
473: synchronized (plock) {
474: if (stmtPendingActions != null) {
475: for (int i = 0; i < stmtPendingActions.size(); i++) {
476: LogStep step = (LogStep) stmtPendingActions.get(i);
477: doStep(step);
478: }
479: stmtPendingActions = null;
480: }
481: }
482: }
483:
484: /**
485: * Another cursor created for this session, which we need to keep
486: * track of.
487: */
488: final void addCursor(Cursor c) {
489: if (cursors == null) {
490: cursors = new ArrayList();
491: }
492: cursors.add(c);
493: }
494:
495: /**
496: * Each context object represents some state retained on behalf
497: * of a constraint. The 'finish()' method of the context is used
498: * to perform any constraint-specific processing at the end of
499: * a statement/transaction.
500: */
501: final void finishContexts(Hashtable context, boolean abort)
502: throws SQLException, IOException {
503: SQLException se = null;
504: IOException io = null;
505:
506: int maxp = 0;
507: for (int p = 0; p <= maxp; p++) {
508: Enumeration keys = context.keys();
509: while (keys.hasMoreElements()) {
510: Object key = keys.nextElement();
511: StatementContext sc = (StatementContext) context
512: .get(key);
513: int sp = sc.priority();
514: maxp = Math.max(maxp, sp);
515: if (sp == p) {
516: try {
517: //#ifdef TRACE
518: if (Trace.bit(10)) {
519: Debug.println(toString()
520: + " Finish "
521: + Table.strip(sc.getClass()
522: .getName()));
523: }
524: //#endif
525: sc.finish(abort);
526: } catch (SQLException ex) {
527: se = ex;
528: } catch (IOException ex) {
529: io = ex;
530: }
531: }
532: }
533: }
534: if (se != null)
535: throw se;
536: if (io != null)
537: throw io;
538: }
539:
540: /**
541: * Return the requested statement context.
542: *
543: * @param obj is the key which uniquely identifies the context
544: * @param deferrable is <b>true</b> if this context's execution can
545: * be deferred until after the current statement.
546: */
547: final StatementContext getContext(Object obj, boolean deferrable) {
548: StatementContext ret = null;
549: if (deferrable) {
550: ret = qConn.getContext(obj);
551: } else if (stmtContext != null) {
552: ret = (StatementContext) stmtContext.get(obj);
553: }
554: return ret;
555: }
556:
557: /**
558: * Add a new context...
559: */
560: final void putContext(Object key, boolean deferrable,
561: StatementContext val) {
562: if (deferrable) {
563: qConn.putContext(key, val);
564: } else {
565: if (stmtContext == null) {
566: stmtContext = new Hashtable();
567: }
568: stmtContext.put(key, val);
569: }
570: }
571:
572: /**
573: * Close this session
574: */
575: public final void close() throws SQLException, IOException {
576: endStatement(false);
577: qConn.removeSession(this );
578: }
579:
580: /**
581: * Set the session's "LAST INSERT ID"
582: */
583: final void setLastInsertId(long insid) {
584: lastInsertId = insid;
585: qConn.setLastInsertId(insid);
586: }
587:
588: /**
589: * Return the session's "LAST INSERT ID"
590: */
591: public long getLastInsertId() {
592: return lastInsertId;
593: }
594:
595: //#ifdef DEBUG
596: /**
597: * Return a string representation for debugging
598: */
599: public String toString() {
600: return "Session " + sessionIndex + ": " + qConn;
601: }
602:
603: //#endif
604:
605: /**
606: * A buffer private to this session for formatting rowIds
607: */
608: byte[] buf8 = new byte[8];
609:
610: public final byte[] getBuf8(long l) {
611: ByteUtil.putLong(buf8, 0, l);
612: return buf8;
613: }
614:
615: public boolean inRecovery() throws IOException {
616: return qConn.inRecovery();
617: }
618: }
|