Quadcap Embeddable Database

com/quadcap/sql/Connection.java

Go to the documentation of this file.
00001 package com.quadcap.sql; 00002 00003 /* Copyright 1999 - 2003 Quadcap Software. All rights reserved. 00004 * 00005 * This software is distributed under the Quadcap Free Software License. 00006 * This software may be used or modified for any purpose, personal or 00007 * commercial. Open Source redistributions are permitted. Commercial 00008 * redistribution of larger works derived from, or works which bundle 00009 * this software requires a "Commercial Redistribution License"; see 00010 * http://www.quadcap.com/purchase. 00011 * 00012 * Redistributions qualify as "Open Source" under one of the following terms: 00013 * 00014 * Redistributions are made at no charge beyond the reasonable cost of 00015 * materials and delivery. 00016 * 00017 * Redistributions are accompanied by a copy of the Source Code or by an 00018 * irrevocable offer to provide a copy of the Source Code for up to three 00019 * years at the cost of materials and delivery. Such redistributions 00020 * must allow further use, modification, and redistribution of the Source 00021 * Code under substantially the same terms as this license. 00022 * 00023 * Redistributions of source code must retain the copyright notices as they 00024 * appear in each source code file, these license terms, and the 00025 * disclaimer/limitation of liability set forth as paragraph 6 below. 00026 * 00027 * Redistributions in binary form must reproduce this Copyright Notice, 00028 * these license terms, and the disclaimer/limitation of liability set 00029 * forth as paragraph 6 below, in the documentation and/or other materials 00030 * provided with the distribution. 00031 * 00032 * The Software is provided on an "AS IS" basis. No warranty is 00033 * provided that the Software is free of defects, or fit for a 00034 * particular purpose. 00035 * 00036 * Limitation of Liability. Quadcap Software shall not be liable 00037 * for any damages suffered by the Licensee or any third party resulting 00038 * from use of the Software. 00039 */ 00040 00041 import java.io.IOException; 00042 00043 import java.util.ArrayList; 00044 import java.util.Enumeration; 00045 import java.util.Hashtable; 00046 import java.util.List; 00047 import java.util.Random; 00048 00049 import java.sql.SQLException; 00050 00051 import com.quadcap.sql.index.Btree; 00052 import com.quadcap.sql.index.Comparator; 00053 00054 import com.quadcap.sql.file.BlockFile; 00055 import com.quadcap.sql.file.Log; 00056 00057 import com.quadcap.sql.lock.Lock; 00058 import com.quadcap.sql.lock.LockMode; 00059 import com.quadcap.sql.lock.Transaction; 00060 import com.quadcap.sql.lock.TransactionObserver; 00061 00062 import com.quadcap.util.Debug; 00063 import com.quadcap.util.Util; 00064 00065 /** 00066 * Analagous (and mapped onto) a JDBC <code>Connection</code>, this class 00067 * maintains state and locks on behalf of a single session. 00068 * 00069 * @author Stan Bailes 00070 */ 00071 public class Connection implements TransactionObserver { 00072 Object connLock; 00073 Object fileLock; 00074 00075 Transaction trans = null; 00076 boolean transAborted = false; 00077 long transId = -1; 00078 00079 String auth; 00080 boolean writeLog = true; 00081 boolean autoCommit = true; 00082 Database db; 00083 BlockFile file; 00084 Hashtable rlocks = new Hashtable(); 00085 Hashtable wlocks = new Hashtable(); 00086 00087 Lock db_IS = null; 00088 Lock db_IX = null; 00089 00090 Hashtable transContext = null; 00091 Random random = null; 00092 boolean readOnly = false; 00093 00094 List sessions = new ArrayList(); 00095 int nextSession = -1; 00096 boolean isClosed = false; 00097 00098 long lastInsertId = -1; 00099 00100 int id; 00101 static int lastId = 0; 00102 00103 /** 00104 * Construct a new connection for the specified database with the specified 00105 * authorization 00106 * 00107 * @param db the database 00108 * @param user the authorization id 00109 * @param passwd the password 00110 * 00111 * @exception SQLException thrown if bad authorization 00112 */ 00113 public Connection(Database db, String auth, String passwd) 00114 throws SQLException 00115 { 00116 this.id = lastId++; 00117 this.db = db; 00118 this.file = db.getFile(); 00119 this.connLock = new Object(); 00120 this.fileLock = file.getLock(); 00121 this.readOnly = db.isReadOnly(); 00122 setAuth(auth, passwd); 00123 //#ifdef TRACE 00124 if (Trace.bit(8)) { 00125 Debug.println("Connection() : " + this); 00126 } 00127 //#endif 00128 } 00129 00130 /** 00131 * Create a new session and store it in the session table. 00132 */ 00133 public final Session createSession() throws IOException, SQLException { 00134 int sessionPos = nextSession; 00135 if (nextSession == -1) { 00136 sessionPos = sessions.size(); 00137 sessions.add(null); 00138 } else { 00139 nextSession = ((Number)sessions.get(nextSession)).intValue(); 00140 } 00141 Session session = new Session(this, sessionPos); 00142 sessions.set(sessionPos, session); 00143 //Debug.println("createSession(" + sessionPos + "): " + Util.stackTrace()); 00144 return session; 00145 } 00146 00147 public final void removeSession(Session session) { 00148 if (sessions != null) { 00149 int pos = session.sessionIndex; 00150 Session sess2 = (Session)sessions.get(pos); 00151 if (sess2 != session) { 00152 throw new RuntimeException("Bad session table"); 00153 } 00154 sessions.set(pos, new Integer(nextSession)); 00155 nextSession = pos; 00156 } 00157 } 00158 00159 final Session getSession(int i) { 00160 //- //#ifdef USE_WEAK_REFS2 00161 //- WeakReference ref = (WeakReference)sessions.elementAt(i); 00162 //- if (ref != null) { 00163 //- Object obj = ref.get(); 00164 //- if (obj != null && obj instanceof Session) { 00165 //- return (Session)obj; 00166 //- } 00167 //- } 00168 //- return null; 00169 //#else 00170 Session s = null; 00171 Object obj = sessions.get(i); 00172 if (obj instanceof Session) s = (Session)obj; 00173 return s; 00174 //#endif 00175 } 00176 00177 /** 00178 * Close all the sessions in the table 00179 */ 00180 final void closeSessions() { 00181 for (int i = 0; i < sessions.size(); i++) { 00182 Session session = getSession(i); 00183 try { 00184 if (session != null) session.close(); 00185 } catch (Throwable t) { 00186 //#ifdef DEBUG 00187 Debug.print(t); 00188 //#endif 00189 } 00190 } 00191 sessions = null; 00192 nextSession = -1; 00193 } 00194 00195 /** 00196 * Given a SQL ID, attempt to generate a fully qualified name by 00197 * prepending the current schema name. 00198 * 00199 */ 00200 final String resolveName(String name) { 00201 if (countDots(name) == 0 && auth != null && auth.length() > 0) { 00202 return auth + "." + name; 00203 } else { 00204 return name; 00205 } 00206 } 00207 00208 /** 00209 * Return the number of '.' delimiters in this identifier, 00210 * skipping over any occurrences of '.' within delimted 00211 * identifiers (i.e., literal strings surrounded by double 00212 * quotes.) 00213 */ 00214 static final int countDots(String name) { 00215 int cnt = 0; 00216 boolean quote = false; 00217 for (int i = 0; i < name.length(); i++) { 00218 char c = name.charAt(i); 00219 if (c == '"') quote = !quote; 00220 if (!quote && c == '.') cnt++; 00221 } 00222 return cnt; 00223 } 00224 00225 /** 00226 * Return a fully qualified column name. 00227 * 00228 * Column: <schema>.<table>.<column> 00229 */ 00230 final String resolveColname(String name, Tuple cursorTuple) { 00231 String ret = name; 00232 switch (countDots(name)) { 00233 case 0: 00234 ret = cursorTuple.getName() + "." + name; 00235 if (ret.startsWith(".")) { 00236 ret = ret.substring(1); 00237 } 00238 break; 00239 case 1: 00240 if (auth != null && auth.length() > 0) { 00241 ret = auth + "." + name; 00242 } 00243 break; 00244 default: 00245 } 00246 return ret; 00247 } 00248 00249 /** 00250 * Accessor for my database 00251 */ 00252 public final Database getDatabase() { return db; } 00253 00254 /** 00255 * Accessor for my log 00256 */ 00257 private final Log getLog() { return db.getLog(); } 00258 00259 /** 00260 * Accessor for my file 00261 */ 00262 public final BlockFile getFile() { return file; } 00263 00264 /** 00265 * Accessor for my 'Random' object 00266 */ 00267 Random getRandom() { 00268 if (random == null) random = new Random(); 00269 return random; 00270 } 00271 00272 /** 00273 * The BLOB code (InsertBlob) needs access to the temp file. 00274 */ 00275 final BlockFile getTempFile(boolean holdRef) throws IOException { 00276 return db.getTempFile(holdRef); 00277 } 00278 00279 /** 00280 * At statement end, if autoCommit is TRUE, we commit (or rollback) 00281 * the transaction 00282 */ 00283 void endStatement(Session session, boolean abort) 00284 throws IOException, SQLException 00285 { 00286 if (autoCommit && trans != null) { 00287 if (abort) { 00288 rollbackTransaction(); 00289 } else { 00290 endTransaction(); 00291 } 00292 } 00293 } 00294 00295 /** 00296 * Commit the current transaction 00297 * 00298 * PRE: cursors closed, statements finished. 00299 * POST: transaction commit/rollback, locks released 00300 */ 00301 public final void endTransaction() 00302 throws IOException, SQLException 00303 { 00304 //#ifdef TRACE 00305 if (Trace.bit(9)) { 00306 Debug.println(toString() + ".endTransaction()"); 00307 } 00308 //#endif 00309 SQLException se = null; 00310 IOException io = null; 00311 synchronized (fileLock) { 00312 synchronized (connLock) { 00313 try { 00314 if (transContext != null) { 00315 try { 00316 finishContexts(); 00317 } finally { 00318 transContext = null; 00319 } 00320 } 00321 if (!readOnly && trans != null) { 00322 //#ifdef TRACE 00323 if (Trace.bit(9)) { 00324 Debug.println(toString() + ": db.commitTransaction()"); 00325 } 00326 //#endif 00327 try { 00328 db.commitTransaction(trans); 00329 } catch (Exception e) { 00330 //#ifdef DEBUG 00331 Debug.print(e); 00332 //#endif 00333 throw new RuntimeException(e.toString()); 00334 } 00335 } 00336 } catch (SQLException ex) { 00337 se = ex; 00338 checkAborted(); 00339 db.rollbackTransaction(trans); 00340 } catch (IOException ex) { 00341 io = ex; 00342 checkAborted(); 00343 db.rollbackTransaction(trans); 00344 } finally { 00345 releaseLocks(); 00346 } 00347 if (se != null) throw se; 00348 if (io != null) throw io; 00349 } 00350 } 00351 } 00352 00353 /** 00354 * Roll back this transaction. Toss the pending lists (and statement 00355 * contexts, right? XXX), close any cursors and get the database to 00356 * undo anything we've actually done to the database. 00357 */ 00358 public final void rollbackTransaction() 00359 throws IOException, SQLException 00360 { 00361 //#ifdef TRACE 00362 if (Trace.bit(9)) { 00363 Debug.println(toString() + ".rollbackTransaction()"); 00364 } 00365 //#endif 00366 synchronized (fileLock) { 00367 synchronized (connLock) { 00368 try { 00369 transContext = null; 00370 if (trans != null) { 00371 db.rollbackTransaction(trans); 00372 } 00373 } finally { 00374 releaseLocks(); 00375 } 00376 } 00377 } 00378 } 00379 00380 /** 00381 * TransactionObserver implementation: abort the current transaction 00382 */ 00383 public void abort(Transaction t) throws IOException { 00384 transContext = null; 00385 try { 00386 releaseLocks(); 00387 db.releaseLocks(t); 00388 } finally { 00389 transAborted = true; 00390 } 00391 } 00392 00393 /** 00394 * Statement rollback 00395 */ 00396 final void rollbackStatement(Session s) throws IOException, SQLException { 00397 synchronized (fileLock) { 00398 synchronized (connLock) { 00399 checkAborted(); 00400 if (trans != null) { 00401 db.rollbackStatement(trans, s.getStmtId()); 00402 } 00403 } 00404 } 00405 } 00406 00407 final void checkAborted() throws SQLException { 00408 synchronized (connLock) { 00409 if (transAborted) { 00410 transAborted = false; 00411 throw new SQLException("Transaction aborted"); 00412 } 00413 } 00414 } 00415 00416 /** 00417 * Return the current transaction 00418 */ 00419 public final Transaction getTransaction() { return trans; } 00420 00421 /** 00422 * Return the current transaction id as a long. 00423 */ 00424 public final long getTransactionId() { return transId; } 00425 00426 /** 00427 * Return (lazy create) the current Transaction 00428 */ 00429 final Transaction makeTransaction() throws SQLException 00430 { 00431 if (trans == null) { 00432 synchronized (fileLock) { 00433 synchronized (connLock) { 00434 checkAborted(); 00435 if (trans == null) { 00436 try { 00437 trans = db.makeTransaction(writeLog); 00438 } catch (IOException ex) { 00439 throw DbException.wrapThrowable(ex); 00440 } 00441 trans.setObserver(this); 00442 transId = trans.getTransactionId(); 00443 } 00444 } 00445 } 00446 } 00447 return trans; 00448 } 00449 00450 /** 00451 * Get the database root lock in the specified mode. 00452 */ 00453 final Lock getDbLock(int mode) throws IOException, SQLException { 00454 makeTransaction(); 00455 switch (mode) { 00456 case LockMode.IS: 00457 if (db_IS == null) { 00458 synchronized (connLock) { 00459 if (db_IS == null) { 00460 db_IS = db.getLockManager().getLock(trans, null, "db", LockMode.IS); 00461 } 00462 } 00463 } 00464 return db_IS; 00465 case LockMode.IX: 00466 if (db_IX == null) { 00467 synchronized (connLock) { 00468 if (db_IX == null) { 00469 db_IX = db.getLockManager().getLock(trans, null, "db", LockMode.IX); 00470 } 00471 } 00472 } 00473 return db_IX; 00474 default: 00475 synchronized (connLock) { 00476 return db.getLockManager().getLock(trans, null, "db", mode); 00477 } 00478 } 00479 } 00480 00481 /** 00482 * Helper routine to obtain a write lock on the specified table 00483 */ 00484 final void getTableWriteLock(String tableName) 00485 throws SQLException, IOException 00486 { 00487 if (!readOnly && !inRecovery()) { 00488 synchronized (connLock) { 00489 if (wlocks.get(tableName) == null) { 00490 Lock dbLock = getDbLock(LockMode.IX); 00491 db.getLockManager().getLock(trans, dbLock, tableName, LockMode.X); 00492 wlocks.put(tableName, tableName); 00493 } 00494 } 00495 } 00496 } 00497 00498 /** 00499 * Helper routine to obtain a read lock on the specified table 00500 */ 00501 final void getTableReadLock(String tableName) 00502 throws SQLException, IOException 00503 { 00504 if (!readOnly && !inRecovery()) { 00505 synchronized (connLock) { 00506 if (rlocks.get(tableName) == null) { 00507 Lock dbLock = getDbLock(LockMode.IS); 00508 db.getLockManager().getLock(trans, dbLock, tableName, LockMode.S); 00509 rlocks.put(tableName, tableName); 00510 } 00511 } 00512 } 00513 } 00514 00515 /** 00516 * At transaction end, release our cached locks. 00517 * PRECONDITION: connLock monitor entered 00518 */ 00519 final void releaseLocks() { 00520 if (!readOnly) { 00521 //#ifdef TRACE 00522 if (Trace.bit(9)) { 00523 Debug.println(toString() + ".releaseLocks()"); 00524 } 00525 //#endif 00526 if (trans != null) { 00527 wlocks.clear(); 00528 rlocks.clear(); 00529 db_IS = null; 00530 db_IX = null; 00531 trans = null; 00532 transId = -1; 00533 } 00534 } 00535 } 00536 00537 /** 00538 * Public setter for the 'autoCommit' flag. 00539 */ 00540 public final void setAutoCommit(boolean b) { 00541 autoCommit = b; 00542 } 00543 00544 /** 00545 * Public accessor for the 'autoCommit' flag. 00546 */ 00547 public final boolean getAutoCommit() { 00548 return autoCommit; 00549 } 00550 00551 /** 00552 * Each context object represents some state retained on behalf 00553 * of a constraint. The 'finish()' method of the context is used 00554 * to perform any constraint-specific processing at the end of 00555 * a statement/transaction. 00556 */ 00557 final void finishContexts() 00558 throws SQLException, IOException 00559 { 00560 SQLException se = null; 00561 IOException io = null; 00562 00563 int maxp = 0; 00564 for (int p = 0; p <= maxp; p++) { 00565 Enumeration keys = transContext.keys(); 00566 while (keys.hasMoreElements()) { 00567 Object key = keys.nextElement(); 00568 StatementContext sc = (StatementContext)transContext.get(key); 00569 int sp = sc.priority(); 00570 maxp = Math.max(maxp, sp); 00571 if (sp == p) { 00572 try { 00573 //#ifdef TRACE 00574 if (Trace.bit(10)) { 00575 Debug.println(toString() + ": Finish " + 00576 Table.strip(sc.getClass().getName())); 00577 } 00578 //#endif 00579 sc.finish(false); 00580 } catch (SQLException ex) { 00581 se = ex; 00582 } catch (IOException ex) { 00583 io = ex; 00584 } 00585 } 00586 } 00587 } 00588 if (se != null) throw se; 00589 if (io != null) throw io; 00590 } 00591 00592 /** 00593 * Set the current authorization code 00594 */ 00595 public final void setAuth(String auth, String passwd) throws SQLException { 00596 db.checkAuth(auth, passwd); 00597 this.auth = auth; 00598 this.writeLog = !auth.equals("__SYSTEM"); 00599 } 00600 00601 /** 00602 * Access to 'recovery' flag 00603 */ 00604 final boolean inRecovery() throws IOException { return db.inRecovery(); } 00605 00606 public final String getAuth() { 00607 return auth; 00608 } 00609 00610 /** 00611 * Make a new (temporary) btree structure in the current database. 00612 */ 00613 final Btree makeTempTree(Comparator compare) throws IOException { 00614 BlockFile f = db.getTempFile(); 00615 long root = f.newPage(); 00616 Btree tree = new Btree(f, compare, root, true); 00617 return tree; 00618 } 00619 00620 final Btree makeTempTree() throws IOException { 00621 BlockFile f = db.getTempFile(); 00622 long root = f.newPage(); 00623 Btree tree = new Btree(f, root, true); 00624 return tree; 00625 } 00626 00627 /** 00628 * Return a saved context 00629 */ 00630 final StatementContext getContext(Object obj) { 00631 if (transContext == null) return null; 00632 return (StatementContext)transContext.get(obj); 00633 } 00634 00635 /** 00636 * Add a new statement context 00637 */ 00638 final void putContext(Object key, StatementContext val) { 00639 if (transContext == null) { 00640 transContext = new Hashtable(); 00641 } 00642 transContext.put(key, val); 00643 } 00644 00645 /** 00646 * Close this connection. Close all statement sessions and end the 00647 * transaction. 00648 */ 00649 public final void close() throws SQLException, IOException { 00650 //#ifdef TRACE 00651 if (Trace.bit(8)) { 00652 Debug.println(toString() + ".close()"); 00653 } 00654 //#endif 00655 try { 00656 if (!readOnly) { 00657 try { 00658 closeSessions(); 00659 } finally { 00660 if (trans != null) { 00661 if (autoCommit) { 00662 endTransaction(); 00663 } else { 00664 rollbackTransaction(); 00665 } 00666 } 00667 } 00668 } 00669 } finally { 00670 isClosed = true; 00671 db.removeConnection(); 00672 } 00673 } 00674 00675 /** 00676 * Return true if this compare is closed 00677 */ 00678 public boolean isClosed() { 00679 return isClosed || file == null; 00680 } 00681 00682 /** 00683 * Return true if this connection is read-only 00684 */ 00685 public boolean isReadOnly() { 00686 return readOnly; 00687 } 00688 00689 //#ifdef DEBUG 00690 public String toString() { 00691 return "user=" + auth + ": (" + trans + ") " + db; 00692 } 00693 //#endif 00694 00695 public String getLabel() { 00696 return "Connection " + id + ": (" + trans + ")"; 00697 } 00698 00699 long getLastInsertId() { return lastInsertId; } 00700 void setLastInsertId(long id) { lastInsertId = id; } 00701 } 00702