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.SQLException;
050:
051: import com.quadcap.sql.index.Btree;
052: import com.quadcap.sql.index.Comparator;
053:
054: import com.quadcap.sql.file.BlockFile;
055: import com.quadcap.sql.file.Log;
056:
057: import com.quadcap.sql.lock.Lock;
058: import com.quadcap.sql.lock.LockMode;
059: import com.quadcap.sql.lock.Transaction;
060: import com.quadcap.sql.lock.TransactionObserver;
061:
062: import com.quadcap.util.Debug;
063: import com.quadcap.util.Util;
064:
065: /**
066: * Analagous (and mapped onto) a JDBC <code>Connection</code>, this class
067: * maintains state and locks on behalf of a single session.
068: *
069: * @author Stan Bailes
070: */
071: public class Connection implements TransactionObserver {
072: Object connLock;
073: Object fileLock;
074:
075: Transaction trans = null;
076: boolean transAborted = false;
077: long transId = -1;
078:
079: String auth;
080: boolean writeLog = true;
081: boolean autoCommit = true;
082: Database db;
083: BlockFile file;
084: Hashtable rlocks = new Hashtable();
085: Hashtable wlocks = new Hashtable();
086:
087: Lock db_IS = null;
088: Lock db_IX = null;
089:
090: Hashtable transContext = null;
091: Random random = null;
092: boolean readOnly = false;
093:
094: List sessions = new ArrayList();
095: int nextSession = -1;
096: boolean isClosed = false;
097:
098: long lastInsertId = -1;
099:
100: int id;
101: static int lastId = 0;
102:
103: /**
104: * Construct a new connection for the specified database with the specified
105: * authorization
106: *
107: * @param db the database
108: * @param user the authorization id
109: * @param passwd the password
110: *
111: * @exception SQLException thrown if bad authorization
112: */
113: public Connection(Database db, String auth, String passwd)
114: throws SQLException {
115: this .id = lastId++;
116: this .db = db;
117: this .file = db.getFile();
118: this .connLock = new Object();
119: this .fileLock = file.getLock();
120: this .readOnly = db.isReadOnly();
121: setAuth(auth, passwd);
122: //#ifdef TRACE
123: if (Trace.bit(8)) {
124: Debug.println("Connection() : " + this );
125: }
126: //#endif
127: }
128:
129: /**
130: * Create a new session and store it in the session table.
131: */
132: public final Session createSession() throws IOException,
133: SQLException {
134: int sessionPos = nextSession;
135: if (nextSession == -1) {
136: sessionPos = sessions.size();
137: sessions.add(null);
138: } else {
139: nextSession = ((Number) sessions.get(nextSession))
140: .intValue();
141: }
142: Session session = new Session(this , sessionPos);
143: sessions.set(sessionPos, session);
144: //Debug.println("createSession(" + sessionPos + "): " + Util.stackTrace());
145: return session;
146: }
147:
148: public final void removeSession(Session session) {
149: if (sessions != null) {
150: int pos = session.sessionIndex;
151: Session sess2 = (Session) sessions.get(pos);
152: if (sess2 != session) {
153: throw new RuntimeException("Bad session table");
154: }
155: sessions.set(pos, new Integer(nextSession));
156: nextSession = pos;
157: }
158: }
159:
160: final Session getSession(int i) {
161: //- //#ifdef USE_WEAK_REFS2
162: //- WeakReference ref = (WeakReference)sessions.elementAt(i);
163: //- if (ref != null) {
164: //- Object obj = ref.get();
165: //- if (obj != null && obj instanceof Session) {
166: //- return (Session)obj;
167: //- }
168: //- }
169: //- return null;
170: //#else
171: Session s = null;
172: Object obj = sessions.get(i);
173: if (obj instanceof Session)
174: s = (Session) obj;
175: return s;
176: //#endif
177: }
178:
179: /**
180: * Close all the sessions in the table
181: */
182: final void closeSessions() {
183: for (int i = 0; i < sessions.size(); i++) {
184: Session session = getSession(i);
185: try {
186: if (session != null)
187: session.close();
188: } catch (Throwable t) {
189: //#ifdef DEBUG
190: Debug.print(t);
191: //#endif
192: }
193: }
194: sessions = null;
195: nextSession = -1;
196: }
197:
198: /**
199: * Given a SQL ID, attempt to generate a fully qualified name by
200: * prepending the current schema name.
201: *
202: */
203: final String resolveName(String name) {
204: if (countDots(name) == 0 && auth != null && auth.length() > 0) {
205: return auth + "." + name;
206: } else {
207: return name;
208: }
209: }
210:
211: /**
212: * Return the number of '.' delimiters in this identifier,
213: * skipping over any occurrences of '.' within delimted
214: * identifiers (i.e., literal strings surrounded by double
215: * quotes.)
216: */
217: static final int countDots(String name) {
218: int cnt = 0;
219: boolean quote = false;
220: for (int i = 0; i < name.length(); i++) {
221: char c = name.charAt(i);
222: if (c == '"')
223: quote = !quote;
224: if (!quote && c == '.')
225: cnt++;
226: }
227: return cnt;
228: }
229:
230: /**
231: * Return a fully qualified column name.
232: *
233: * Column: <schema>.<table>.<column>
234: */
235: final String resolveColname(String name, Tuple cursorTuple) {
236: String ret = name;
237: switch (countDots(name)) {
238: case 0:
239: ret = cursorTuple.getName() + "." + name;
240: if (ret.startsWith(".")) {
241: ret = ret.substring(1);
242: }
243: break;
244: case 1:
245: if (auth != null && auth.length() > 0) {
246: ret = auth + "." + name;
247: }
248: break;
249: default:
250: }
251: return ret;
252: }
253:
254: /**
255: * Accessor for my database
256: */
257: public final Database getDatabase() {
258: return db;
259: }
260:
261: /**
262: * Accessor for my log
263: */
264: private final Log getLog() {
265: return db.getLog();
266: }
267:
268: /**
269: * Accessor for my file
270: */
271: public final BlockFile getFile() {
272: return file;
273: }
274:
275: /**
276: * Accessor for my 'Random' object
277: */
278: Random getRandom() {
279: if (random == null)
280: random = new Random();
281: return random;
282: }
283:
284: /**
285: * The BLOB code (InsertBlob) needs access to the temp file.
286: */
287: final BlockFile getTempFile(boolean holdRef) throws IOException {
288: return db.getTempFile(holdRef);
289: }
290:
291: /**
292: * At statement end, if autoCommit is TRUE, we commit (or rollback)
293: * the transaction
294: */
295: void endStatement(Session session, boolean abort)
296: throws IOException, SQLException {
297: if (autoCommit && trans != null) {
298: if (abort) {
299: rollbackTransaction();
300: } else {
301: endTransaction();
302: }
303: }
304: }
305:
306: /**
307: * Commit the current transaction
308: *
309: * PRE: cursors closed, statements finished.
310: * POST: transaction commit/rollback, locks released
311: */
312: public final void endTransaction() throws IOException, SQLException {
313: //#ifdef TRACE
314: if (Trace.bit(9)) {
315: Debug.println(toString() + ".endTransaction()");
316: }
317: //#endif
318: SQLException se = null;
319: IOException io = null;
320: synchronized (fileLock) {
321: synchronized (connLock) {
322: try {
323: if (transContext != null) {
324: try {
325: finishContexts();
326: } finally {
327: transContext = null;
328: }
329: }
330: if (!readOnly && trans != null) {
331: //#ifdef TRACE
332: if (Trace.bit(9)) {
333: Debug.println(toString()
334: + ": db.commitTransaction()");
335: }
336: //#endif
337: try {
338: db.commitTransaction(trans);
339: } catch (Exception e) {
340: //#ifdef DEBUG
341: Debug.print(e);
342: //#endif
343: throw new RuntimeException(e.toString());
344: }
345: }
346: } catch (SQLException ex) {
347: se = ex;
348: checkAborted();
349: db.rollbackTransaction(trans);
350: } catch (IOException ex) {
351: io = ex;
352: checkAborted();
353: db.rollbackTransaction(trans);
354: } finally {
355: releaseLocks();
356: }
357: if (se != null)
358: throw se;
359: if (io != null)
360: throw io;
361: }
362: }
363: }
364:
365: /**
366: * Roll back this transaction. Toss the pending lists (and statement
367: * contexts, right? XXX), close any cursors and get the database to
368: * undo anything we've actually done to the database.
369: */
370: public final void rollbackTransaction() throws IOException,
371: SQLException {
372: //#ifdef TRACE
373: if (Trace.bit(9)) {
374: Debug.println(toString() + ".rollbackTransaction()");
375: }
376: //#endif
377: synchronized (fileLock) {
378: synchronized (connLock) {
379: try {
380: transContext = null;
381: if (trans != null) {
382: db.rollbackTransaction(trans);
383: }
384: } finally {
385: releaseLocks();
386: }
387: }
388: }
389: }
390:
391: /**
392: * TransactionObserver implementation: abort the current transaction
393: */
394: public void abort(Transaction t) throws IOException {
395: transContext = null;
396: try {
397: releaseLocks();
398: db.releaseLocks(t);
399: } finally {
400: transAborted = true;
401: }
402: }
403:
404: /**
405: * Statement rollback
406: */
407: final void rollbackStatement(Session s) throws IOException,
408: SQLException {
409: synchronized (fileLock) {
410: synchronized (connLock) {
411: checkAborted();
412: if (trans != null) {
413: db.rollbackStatement(trans, s.getStmtId());
414: }
415: }
416: }
417: }
418:
419: final void checkAborted() throws SQLException {
420: synchronized (connLock) {
421: if (transAborted) {
422: transAborted = false;
423: throw new SQLException("Transaction aborted");
424: }
425: }
426: }
427:
428: /**
429: * Return the current transaction
430: */
431: public final Transaction getTransaction() {
432: return trans;
433: }
434:
435: /**
436: * Return the current transaction id as a long.
437: */
438: public final long getTransactionId() {
439: return transId;
440: }
441:
442: /**
443: * Return (lazy create) the current Transaction
444: */
445: final Transaction makeTransaction() throws SQLException {
446: if (trans == null) {
447: synchronized (fileLock) {
448: synchronized (connLock) {
449: checkAborted();
450: if (trans == null) {
451: try {
452: trans = db.makeTransaction(writeLog);
453: } catch (IOException ex) {
454: throw DbException.wrapThrowable(ex);
455: }
456: trans.setObserver(this );
457: transId = trans.getTransactionId();
458: }
459: }
460: }
461: }
462: return trans;
463: }
464:
465: /**
466: * Get the database root lock in the specified mode.
467: */
468: final Lock getDbLock(int mode) throws IOException, SQLException {
469: makeTransaction();
470: switch (mode) {
471: case LockMode.IS:
472: if (db_IS == null) {
473: synchronized (connLock) {
474: if (db_IS == null) {
475: db_IS = db.getLockManager().getLock(trans,
476: null, "db", LockMode.IS);
477: }
478: }
479: }
480: return db_IS;
481: case LockMode.IX:
482: if (db_IX == null) {
483: synchronized (connLock) {
484: if (db_IX == null) {
485: db_IX = db.getLockManager().getLock(trans,
486: null, "db", LockMode.IX);
487: }
488: }
489: }
490: return db_IX;
491: default:
492: synchronized (connLock) {
493: return db.getLockManager().getLock(trans, null, "db",
494: mode);
495: }
496: }
497: }
498:
499: /**
500: * Helper routine to obtain a write lock on the specified table
501: */
502: final void getTableWriteLock(String tableName) throws SQLException,
503: IOException {
504: if (!readOnly && !inRecovery()) {
505: synchronized (connLock) {
506: if (wlocks.get(tableName) == null) {
507: Lock dbLock = getDbLock(LockMode.IX);
508: db.getLockManager().getLock(trans, dbLock,
509: tableName, LockMode.X);
510: wlocks.put(tableName, tableName);
511: }
512: }
513: }
514: }
515:
516: /**
517: * Helper routine to obtain a read lock on the specified table
518: */
519: final void getTableReadLock(String tableName) throws SQLException,
520: IOException {
521: if (!readOnly && !inRecovery()) {
522: synchronized (connLock) {
523: if (rlocks.get(tableName) == null) {
524: Lock dbLock = getDbLock(LockMode.IS);
525: db.getLockManager().getLock(trans, dbLock,
526: tableName, LockMode.S);
527: rlocks.put(tableName, tableName);
528: }
529: }
530: }
531: }
532:
533: /**
534: * At transaction end, release our cached locks.
535: * PRECONDITION: connLock monitor entered
536: */
537: final void releaseLocks() {
538: if (!readOnly) {
539: //#ifdef TRACE
540: if (Trace.bit(9)) {
541: Debug.println(toString() + ".releaseLocks()");
542: }
543: //#endif
544: if (trans != null) {
545: wlocks.clear();
546: rlocks.clear();
547: db_IS = null;
548: db_IX = null;
549: trans = null;
550: transId = -1;
551: }
552: }
553: }
554:
555: /**
556: * Public setter for the 'autoCommit' flag.
557: */
558: public final void setAutoCommit(boolean b) {
559: autoCommit = b;
560: }
561:
562: /**
563: * Public accessor for the 'autoCommit' flag.
564: */
565: public final boolean getAutoCommit() {
566: return autoCommit;
567: }
568:
569: /**
570: * Each context object represents some state retained on behalf
571: * of a constraint. The 'finish()' method of the context is used
572: * to perform any constraint-specific processing at the end of
573: * a statement/transaction.
574: */
575: final void finishContexts() throws SQLException, IOException {
576: SQLException se = null;
577: IOException io = null;
578:
579: int maxp = 0;
580: for (int p = 0; p <= maxp; p++) {
581: Enumeration keys = transContext.keys();
582: while (keys.hasMoreElements()) {
583: Object key = keys.nextElement();
584: StatementContext sc = (StatementContext) transContext
585: .get(key);
586: int sp = sc.priority();
587: maxp = Math.max(maxp, sp);
588: if (sp == p) {
589: try {
590: //#ifdef TRACE
591: if (Trace.bit(10)) {
592: Debug.println(toString()
593: + ": Finish "
594: + Table.strip(sc.getClass()
595: .getName()));
596: }
597: //#endif
598: sc.finish(false);
599: } catch (SQLException ex) {
600: se = ex;
601: } catch (IOException ex) {
602: io = ex;
603: }
604: }
605: }
606: }
607: if (se != null)
608: throw se;
609: if (io != null)
610: throw io;
611: }
612:
613: /**
614: * Set the current authorization code
615: */
616: public final void setAuth(String auth, String passwd)
617: throws SQLException {
618: db.checkAuth(auth, passwd);
619: this .auth = auth;
620: this .writeLog = !auth.equals("__SYSTEM");
621: }
622:
623: /**
624: * Access to 'recovery' flag
625: */
626: final boolean inRecovery() throws IOException {
627: return db.inRecovery();
628: }
629:
630: public final String getAuth() {
631: return auth;
632: }
633:
634: /**
635: * Make a new (temporary) btree structure in the current database.
636: */
637: final Btree makeTempTree(Comparator compare) throws IOException {
638: BlockFile f = db.getTempFile();
639: long root = f.newPage();
640: Btree tree = new Btree(f, compare, root, true);
641: return tree;
642: }
643:
644: final Btree makeTempTree() throws IOException {
645: BlockFile f = db.getTempFile();
646: long root = f.newPage();
647: Btree tree = new Btree(f, root, true);
648: return tree;
649: }
650:
651: /**
652: * Return a saved context
653: */
654: final StatementContext getContext(Object obj) {
655: if (transContext == null)
656: return null;
657: return (StatementContext) transContext.get(obj);
658: }
659:
660: /**
661: * Add a new statement context
662: */
663: final void putContext(Object key, StatementContext val) {
664: if (transContext == null) {
665: transContext = new Hashtable();
666: }
667: transContext.put(key, val);
668: }
669:
670: /**
671: * Close this connection. Close all statement sessions and end the
672: * transaction.
673: */
674: public final void close() throws SQLException, IOException {
675: //#ifdef TRACE
676: if (Trace.bit(8)) {
677: Debug.println(toString() + ".close()");
678: }
679: //#endif
680: try {
681: if (!readOnly) {
682: try {
683: closeSessions();
684: } finally {
685: if (trans != null) {
686: if (autoCommit) {
687: endTransaction();
688: } else {
689: rollbackTransaction();
690: }
691: }
692: }
693: }
694: } finally {
695: isClosed = true;
696: db.removeConnection();
697: }
698: }
699:
700: /**
701: * Return true if this compare is closed
702: */
703: public boolean isClosed() {
704: return isClosed || file == null;
705: }
706:
707: /**
708: * Return true if this connection is read-only
709: */
710: public boolean isReadOnly() {
711: return readOnly;
712: }
713:
714: //#ifdef DEBUG
715: public String toString() {
716: return "user=" + auth + ": (" + trans + ") " + db;
717: }
718:
719: //#endif
720:
721: public String getLabel() {
722: return "Connection " + id + ": (" + trans + ")";
723: }
724:
725: long getLastInsertId() {
726: return lastInsertId;
727: }
728:
729: void setLastInsertId(long id) {
730: lastInsertId = id;
731: }
732: }
|