Quadcap Embeddable Database

com/quadcap/sql/Database.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.BufferedWriter; 00042 import java.io.File; 00043 import java.io.FileOutputStream; 00044 import java.io.IOException; 00045 import java.io.OutputStream; 00046 import java.io.OutputStreamWriter; 00047 import java.io.PrintWriter; 00048 00049 import java.util.ArrayList; 00050 import java.util.Calendar; 00051 import java.util.Collection; 00052 import java.util.Enumeration; 00053 import java.util.Iterator; 00054 import java.util.HashMap; 00055 import java.util.List; 00056 import java.util.Map; 00057 import java.util.Properties; 00058 00059 import java.util.zip.GZIPOutputStream; 00060 00061 import java.sql.SQLException; 00062 00063 import com.quadcap.sql.file.BlockFile; 00064 import com.quadcap.sql.file.ByteUtil; 00065 import com.quadcap.sql.file.Datafile; 00066 import com.quadcap.sql.file.Log; 00067 import com.quadcap.sql.file.SubPageManager; 00068 00069 import com.quadcap.sql.index.BCursor; 00070 import com.quadcap.sql.index.Btree; 00071 00072 import com.quadcap.sql.lock.Transaction; 00073 00074 import com.quadcap.util.collections.LongIterator; 00075 import com.quadcap.util.collections.LongMap; 00076 00077 import com.quadcap.util.ConfigString; 00078 import com.quadcap.util.Debug; 00079 import com.quadcap.util.Util; 00080 00081 /** 00082 * This class implements the QED SQL database outer API. 00083 * 00084 * @author Stan Bailes 00085 */ 00086 public class Database extends Datafile { 00087 QDriver driver; 00088 Btree tableIndex; 00089 // map index name to table name 00090 Btree indexIndex; 00091 Btree blobPermRefCounts; 00092 Map tableMap = new HashMap(); 00093 MultiSet forwardDeps = null; 00094 MultiSet reverseDeps = null; 00095 long rootBlock = 0; 00096 public DatabaseRoot nroot; 00097 00098 /** The Morgue; BLOB/CLOBs with zero (persistent) refcounts: 00099 blobRef -> any */ 00100 LongMap blobMorgue = new LongMap(32); 00101 00102 /** Map blob -> Vector<transid> */ 00103 LongMap blobActivity = new LongMap(128); 00104 00105 00106 int connectionCount = 0; 00107 int closeCount = 0; 00108 long nextTransId = 0; 00109 00110 public Object driverLock; 00111 00112 static final ConfigString backupClass 00113 = ConfigString.find("qed.backupClass", 00114 "com.quadcap.sql.tools.XmlDump"); 00115 /** 00116 * Constructor 00117 */ 00118 public Database() {} 00119 00120 public void init(QDriver driver, String url, 00121 String fileName, Properties props) 00122 throws IOException, SQLException 00123 { 00124 this.driver = driver; 00125 init(url, fileName, props); 00126 } 00127 00128 /** 00129 * Create a new root block 00130 */ 00131 public void createRoot(String fileName, Properties props) 00132 throws IOException 00133 { 00134 this.rootBlock = getFile().getUserBlock(0); 00135 this.root = this.nroot = new DatabaseRoot(this, fileName, props); 00136 if (!readOnly && !inMemory) { 00137 new File(fileName, "logfile").delete(); 00138 flushRoot(); 00139 } 00140 } 00141 00142 /** 00143 * Read the root block from an existing database 00144 */ 00145 public void readRoot() throws IOException { 00146 this.rootBlock = getFile().getUserBlock(0); 00147 root = nroot = (DatabaseRoot)file.getObject(rootBlock); 00148 nroot.setDatabase(this); 00149 } 00150 00151 /** 00152 * Write the database root 00153 */ 00154 public void flushRoot() throws IOException { 00155 if (!inMemory /*?*/ && !readOnly) { 00156 getFile().updateObject(rootBlock, root); 00157 } 00158 } 00159 00160 /** 00161 * Get ready to rumble!!! 00162 */ 00163 public void bootFromRoot(boolean restart) throws IOException { 00164 nextTransId = root.getNextTransId(); 00165 nroot.setNextTransId((nextTransId | 0xff) + 1); 00166 00167 blobPermRefCounts = new Btree(file, 00168 nroot.getBlobRefCountRoot(), !restart); 00169 tableIndex = new Btree(file, nroot.getRelationIndexNode(), !restart); 00170 indexIndex = new Btree(file, nroot.getIndexIndexNode(), !restart); 00171 forwardDeps = 00172 new MultiSet(new Btree(file, nroot.getForwardDepsNode(), 00173 !restart)); 00174 reverseDeps = 00175 new MultiSet(new Btree(file, nroot.getReverseDepsNode(), 00176 !restart)); 00177 } 00178 00179 /** 00180 * Return the next available transaction ID. 00181 */ 00182 public long getNextTransId() throws IOException { 00183 synchronized (fileLock) { 00184 long t = nextTransId++; 00185 if (!inMemory && (nextTransId & 0xff) == 0) { 00186 nroot.setNextTransId(nextTransId + 0x100); 00187 flushRoot(); 00188 } 00189 return t; 00190 } 00191 } 00192 00193 /** 00194 * Figure out if it's time to do a backup. 00195 */ 00196 public void maybeBackup() throws IOException { 00197 if (inMemory) return; 00198 // are backups enabled? 00199 int days = getBackupDays(); 00200 if (days == 0) return; 00201 00202 // is today the day? 00203 Calendar now = Calendar.getInstance(); 00204 int nowDay = now.get(Calendar.DAY_OF_WEEK); 00205 if (((days >> nowDay) & 1) == 0) return; 00206 00207 // is now the time? 00208 int time = getBackupTime(); 00209 int today = now.get(Calendar.DAY_OF_YEAR); 00210 int nowTime = 00211 now.get(Calendar.HOUR_OF_DAY) * 60 + 00212 now.get(Calendar.MINUTE); 00213 if (nowTime < time || today == getBackupLastDay()) return; 00214 00215 // cycle the old backup files 00216 int count = getBackupCount(); 00217 for (int i = count-2; i >= 0; i--) { 00218 makeBackupFile(i).renameTo(makeBackupFile(i+1)); 00219 } 00220 00221 // write new backup in correct format 00222 OutputStream w = new FileOutputStream(makeBackupFile(0)); 00223 if (getBackupFormat().equals("xml.gz")) { 00224 w = new GZIPOutputStream(w); 00225 } 00226 OutputStreamWriter ow = new OutputStreamWriter(w); 00227 BufferedWriter bw = new BufferedWriter(ow); 00228 00229 // create the backup helper and do it. 00230 try { 00231 java.sql.Connection conn = 00232 driver.makeConnection(this, "backup", null); 00233 Class c = Class.forName(backupClass.toString()); 00234 Backup b = (Backup)c.newInstance(); 00235 b.backup(conn, bw); 00236 bw.close(); 00237 } catch (IOException e) { 00238 throw e; 00239 } catch (Throwable t) { 00240 throw new IOException(t.toString()); 00241 } 00242 setBackupLastDay(today); 00243 } 00244 00245 File makeBackupFile(int n) throws IOException { 00246 File bdir = new File(getBackupDir()); 00247 if (!bdir.exists()) { 00248 if (!bdir.mkdirs()) { 00249 throw new IOException("can't create backup directory: " + 00250 bdir.getAbsolutePath()); 00251 } 00252 } 00253 String name = "backup-" + n + "." + getBackupFormat(); 00254 return new File(bdir, name); 00255 } 00256 00257 /** 00258 * TODO: XXX Unicode!!! 00259 * Add a new named index for the specified table. 00260 */ 00261 public void addIndex(String indexName, String tableName) 00262 throws IOException 00263 { 00264 synchronized (fileLock) { 00265 indexIndex.set(indexName.getBytes(), tableName.getBytes()); 00266 } 00267 } 00268 00269 00270 /** 00271 * Return the table for the index of the specified name. 00272 */ 00273 public String getTableForIndex(String indexName) throws IOException { 00274 synchronized (fileLock) { 00275 byte[] ret = indexIndex.get(indexName.getBytes()); 00276 if (ret == null) return null; 00277 return new String(ret); 00278 } 00279 } 00280 00281 /** 00282 * Remove the specified index 00283 */ 00284 public void deleteIndex(String indexName) throws IOException { 00285 synchronized (fileLock) { 00286 indexIndex.delete(indexName.getBytes()); 00287 } 00288 } 00289 00290 /** 00291 * RENAME TABLE percolates into the index index... 00292 */ 00293 void renameIndexedTable(String oldN, String newN) throws IOException { 00294 synchronized (fileLock) { 00295 BCursor bc = indexIndex.getCursor(false); 00296 if (bc != null) { 00297 try { 00298 while (bc.next()) { 00299 String name = new String(bc.getVal()); 00300 if (name.equals(oldN)) { 00301 bc.prev(); 00302 bc.replace(newN.getBytes()); 00303 bc.next(); 00304 } 00305 } 00306 } finally { 00307 bc.release(); 00308 } 00309 } 00310 } 00311 } 00312 00313 /** 00314 * Return the table identity for the table 00315 * 00316 * @exception SQLException if the table can't be located 00317 */ 00318 public long getTableIdentity(String name) 00319 throws IOException, SQLException 00320 { 00321 synchronized (fileLock) { 00322 if (inMemory) { 00323 Table t = (Table)getRelation(name); 00324 return t.tableIdentity; 00325 } else { 00326 BCursor tc = tableIndex.getCursor(true); 00327 try { 00328 if (tc.seek(name.getBytes()) && 00329 tc.getValLen() >= 16) { 00330 return ByteUtil.getLong(tc.getValBuf(), 8); 00331 } else { 00332 throw new SQLException("getTableIdentity(" + 00333 name + "): no table"); 00334 } 00335 } finally { 00336 tc.release(); 00337 } 00338 } 00339 } 00340 } 00341 00342 /** 00343 * The "table identity" is a long value stored along with the table 00344 * stream pointer, so that it can easily be updated as long as the index 00345 * is in memory, without touching any data pages. 00346 */ 00347 public void updateTableIdentity(String name, long num) 00348 throws IOException, SQLException 00349 { 00350 synchronized (fileLock) { 00351 if (inMemory) { 00352 Table t = (Table)getRelation(name); 00353 t.tableIdentity = num; 00354 } else { 00355 BCursor tc = tableIndex.getCursor(true); 00356 try { 00357 if (tc.seek(name.getBytes())) { 00358 int cnt = tc.getVal(temp); 00359 ByteUtil.putLong(temp, 8, num); 00360 tc.replace(temp, 0, cnt); 00361 return; 00362 } else { 00363 throw new SQLException("getTableIdentity(" + 00364 name + "): no table"); 00365 } 00366 } finally { 00367 tc.release(); 00368 } 00369 } 00370 } 00371 } 00372 00373 /** 00374 * Helper to execute a single SQL statement (in its own connection) 00375 * and return a result set. 00376 */ 00377 public java.sql.ResultSet execute(String sql) 00378 throws SQLException, IOException 00379 { 00380 Connection c = new Connection(this, "__NO_AUTH__", null); 00381 Session session = c.createSession(); 00382 return execute(session, sql); 00383 } 00384 00385 /** 00386 * Helper to execute a single SQL statement for the given session. 00387 */ 00388 public java.sql.ResultSet execute(Session session, String sql) 00389 throws IOException, SQLException 00390 { 00391 SQLParser p = new SQLParser(session, sql, true); 00392 Stmt s = null; 00393 try { 00394 s = p.statement(); 00395 } catch (Throwable t) { 00396 Debug.print(t); 00397 } 00398 00399 if (s == null) { 00400 throw new SQLException("Parse error", "42000"); 00401 } 00402 session.doStatement(s); 00403 return session.getResultSet(); 00404 } 00405 00406 /** 00407 * Return the table/view with the specified name 00408 * 00409 * XXX Fix this "cache" implementation 00410 */ 00411 public Relation getRelation(String name) 00412 throws IOException 00413 { 00414 Relation t = null; 00415 synchronized (fileLock) { 00416 t = (Relation)tableMap.get(name); 00417 if (!inMemory && t == null) { 00418 BCursor tc = tableIndex.getCursor(true); 00419 try { 00420 if (tc.seek(name.getBytes())) { 00421 byte[] d = tc.getValBuf(); 00422 t = (Relation)getFile().getObject(ByteUtil.getLong(d, 0)); 00423 if (t != null) { 00424 tableMap.put(name, t); 00425 } 00426 } 00427 } finally { 00428 tc.release(); 00429 } 00430 } 00431 } 00432 //#ifdef DEBUG 00433 if (Trace.bit(0)) { 00434 Debug.println("getRelation(" + name + ") = " + 00435 ((t == null) ? "NOT FOUND" : "FOUND")); 00436 } 00437 //#endif 00438 return t; 00439 } 00440 00441 /** 00442 * Add a new table/view 00443 ' */ 00444 public void addRelation(Relation table) 00445 throws IOException, SQLException 00446 { 00447 synchronized (fileLock) { 00448 String name = table.getName(); 00449 //#ifdef TRACE 00450 if (Trace.bit(0)) { 00451 Debug.println("addRelation(" + name + ")"); 00452 } 00453 //#endif 00454 if (tableMap.get(name) == null) { 00455 tableMap.put(name, table); 00456 if (!inMemory) { 00457 BCursor tc = tableIndex.getCursor(true); 00458 try { 00459 byte[] tkey = name.getBytes(); 00460 if (!tc.seek(name.getBytes())) { 00461 long ref = getFile().putObject(table); 00462 ByteUtil.putLong(temp, 0, ref); 00463 ByteUtil.putLong(temp, 8, 1); // start counting at '1' 00464 tc.insert(tkey, tkey.length, temp, 0, 16); 00465 } 00466 } finally { 00467 tc.release(); 00468 } 00469 } 00470 } else { 00471 throw new SQLException("Table/view already exists: " + name, 00472 "42000"); 00473 } 00474 } 00475 } 00476 00477 /** 00478 * Delete the specified table/view 00479 */ 00480 public void removeRelation(String name) 00481 throws IOException, SQLException 00482 { 00483 synchronized (fileLock) { 00484 //#ifdef DEBUG 00485 if (Trace.bit(0)) { 00486 Debug.println("removeRelation(" + name + ")"); 00487 } 00488 //#endif 00489 Relation t = getRelation(name); 00490 if (t == null) { 00491 throw new SQLException("No such table/view: " + name, "42000"); 00492 } 00493 removeViewDependencies(name); 00494 tableMap.remove(name); 00495 if (!inMemory) { 00496 BCursor tc = tableIndex.getCursor(true); 00497 try { 00498 if (tc.seek(name.getBytes())) { 00499 getFile().removeObject(tc.getValAsLong()); 00500 tc.delete(); 00501 } 00502 } finally { 00503 tc.release(); 00504 } 00505 } 00506 } 00507 //#ifdef DEBUG 00508 if (getRelation(name) != null) { 00509 throw new SQLException("Relation not removed!!!"); 00510 } 00511 //#endif 00512 } 00513 00514 /** 00515 * Rename a relation. This involves: 00516 * 00517 * 1. Changing the cached relation 00518 * 2. Changing the btree index to the relation 00519 * 3. Changing any indexIndex entries referring to the rel 00520 * 4. Changing any forwardDeps or backwardDeps which 00521 * refer to the relation 00522 */ 00523 public void renameRelation(Relation r, String newN) throws IOException { 00524 synchronized (fileLock) { 00525 String oldN = r.getName(); 00526 r.setName(newN); 00527 tableMap.remove(oldN); 00528 tableMap.put(newN, r); 00529 if (!inMemory) { 00530 BCursor tc = tableIndex.getCursor(true); 00531 try { 00532 if (tc.seek(oldN.getBytes())) { 00533 byte[] d = tc.getValBuf(); 00534 long ref = ByteUtil.getLong(d, 0); 00535 tc.delete(); 00536 byte[] nb = newN.getBytes(); 00537 if (!tc.seek(nb)) { 00538 tc.insert(nb, nb.length, d, 0, 16); 00539 getFile().updateObject(ref, r); 00540 } 00541 } 00542 } finally { 00543 tc.release(); 00544 } 00545 } 00546 if (true) { 00547 forwardDeps.rename(oldN, newN, reverseDeps); 00548 reverseDeps.rename(oldN, newN, forwardDeps); 00549 } 00550 if (true) { 00551 renameIndexedTable(oldN, newN); 00552 } 00553 } 00554 } 00555 00556 /** 00557 * Store a modified version of the specified view/table to the 00558 * store 00559 */ 00560 public void updateRelation(Relation table) throws IOException { 00561 synchronized (fileLock) { 00562 //#ifdef TRACE 00563 if (Trace.bit(0)) { 00564 Debug.println("updateRelation(" + table.getName() + ")"); 00565 //Debug.println("updateRelation(" + table.getName() + ": " + table + ")"); // + Util.stackTrace()); 00566 } 00567 //#endif 00568 tableMap.put(table.getName(), table); 00569 if (!inMemory) { 00570 BCursor tc = tableIndex.getCursor(true); 00571 try { 00572 if (tc.seek(table.getName().getBytes())) { 00573 getFile().updateObject(tc.getValAsLong(), table); 00574 } else { 00575 throw new IOException("Not found: " + table.getName()); 00576 } 00577 } finally { 00578 tc.release(); 00579 } 00580 } 00581 } 00582 } 00583 00584 /** 00585 * Establish the dependency link between a view and a base table 00586 */ 00587 public void addViewDependency(String base, String view) 00588 throws IOException 00589 { 00590 //#ifdef TRACE 00591 if (Trace.bit(0)) { 00592 Debug.println("addViewDependency(" + base + ", " + view + ")"); 00593 } 00594 //#endif 00595 00596 synchronized (fileLock) { 00597 forwardDeps.put(base, view); 00598 reverseDeps.put(view, base); 00599 } 00600 } 00601 00602 /** 00603 * Make sure that the specified view dependency is valid 00604 */ 00605 public void checkViewDependency(String base, String view) 00606 throws SQLException, IOException 00607 { 00608 Relation baseR = getRelation(base); 00609 if (baseR == null) { 00610 throw new SQLException("Base table not found: " + base, "42000"); 00611 } 00612 if (baseR instanceof Table) { 00613 Table baseT = (Table)baseR; 00614 int modifiers = baseT.getModifiers(); 00615 if ((modifiers & Table.TEMPORARY) != 0) { 00616 throw new SQLException("Can't create view of temporary " + 00617 "table " + base, "42000"); 00618 } 00619 } 00620 } 00621 00622 /** 00623 * Return an enumeration of all views for the specified base table 00624 */ 00625 public Enumeration getViews(String base) throws IOException { 00626 synchronized (fileLock) { 00627 return forwardDeps.get(base); 00628 } 00629 } 00630 00631 /** 00632 * Return an enumeration containing all base tables for the specified 00633 * view 00634 */ 00635 public Enumeration getBases(String view) throws IOException { 00636 synchronized (fileLock) { 00637 return reverseDeps.get(view); 00638 } 00639 } 00640 00641 public void removeViewDependencies(String view) 00642 throws SQLException, IOException 00643 { 00644 //#ifdef TRACE 00645 if (Trace.bit(0)) { 00646 Debug.println("removeViewDependencies(" + view + ")"); 00647 } 00648 //#endif 00649 synchronized (fileLock) { 00650 Enumeration views = getViews(view); 00651 if (views.hasMoreElements()) { 00652 throw new SQLException("view has dependencies: " + view, 00653 "42000"); 00654 } 00655 Enumeration bases = getBases(view); 00656 while (bases.hasMoreElements()) { 00657 forwardDeps.delete(bases.nextElement().toString(), view); 00658 } 00659 } 00660 } 00661 00662 /** 00663 * This hacky crack of encapsulation is for sql.meta.MetaTables, to 00664 * support DatabaseMetaData.getTables(). All he really needs is 00665 * an Iterator which returns the table names.... 00666 */ 00667 // public Btree getRelationIndex() { 00668 // return tableIndex; 00669 // } 00670 00671 public Iterator getRelationNameIterator() throws IOException { 00672 if (inMemory) { 00673 return tableMap.keySet().iterator(); 00674 } else { 00675 final BCursor tc = tableIndex.getCursor(false); 00676 return new Iterator() { 00677 boolean called = false; 00678 boolean last = false; 00679 boolean closed = false; 00680 public boolean hasNext() { 00681 try { 00682 if (!closed && !called) { 00683 last = tc.next(); 00684 called = true; 00685 } 00686 } catch (IOException ex) { 00687 last = false; 00688 } 00689 if (!last && !closed) { 00690 closed = true; 00691 try { 00692 tc.release(); 00693 } catch (Throwable t) { 00694 Debug.print(t); 00695 } 00696 } 00697 return last; 00698 } 00699 public Object next() { 00700 Object ret = null; 00701 try { 00702 if (!called && !closed) { 00703 last = tc.next(); 00704 } 00705 if (!last) { 00706 if (!closed) { 00707 closed = true; 00708 try { 00709 tc.release(); 00710 } catch (Throwable t) { 00711 Debug.print(t); 00712 } 00713 } 00714 } else { 00715 ret = new String(tc.getKey()); 00716 called = false; 00717 } 00718 } catch (IOException ex) { 00719 ret = "<Exception: " + ex + ">"; 00720 } 00721 return ret; 00722 } 00723 public void remove() { 00724 try { 00725 tc.delete(); 00726 } catch (IOException e) { 00727 Debug.print(e); 00728 throw new RuntimeException("Error deleting record", e); 00729 } 00730 } 00731 }; 00732 } 00733 } 00734 00735 // public Collection getRelationNames() throws IOException { 00736 // ArrayList list = new ArrayList(); 00737 // synchronized (fileLock) { 00738 // if (inMemory) { 00739 // return tableMap.keySet(); 00740 // } 00741 // BCursor tc = tableIndex.getCursor(false); 00742 // try { 00743 // while (tc.next()) { 00744 // list.add(new String(tc.getKey())); 00745 // } 00746 // } finally { 00747 // tc.release(); 00748 // } 00749 // } 00750 // return list; 00751 // } 00752 00753 /** 00754 * Add a new connection (just remember the count...) 00755 */ 00756 public int addConnection() { 00757 synchronized (driverLock) { 00758 //#ifdef TRACE 00759 if (Trace.bit(8)) { 00760 Debug.println("addConnection, count now: " + (connectionCount+1)); 00761 } 00762 //#endif 00763 return ++connectionCount; 00764 } 00765 } 00766 00767 /** 00768 * Sleight of hand to get my driver interface 00769 */ 00770 public QDriver getDriver() { 00771 return driver; 00772 } 00773 00774 /** 00775 * Remove a connection. If the connection count drops to zero, we 00776 * get crafty. 00777 */ 00778 public void removeConnection() { 00779 synchronized (driverLock) { 00780 if (--connectionCount == 0 && closeCount++ < 1) { 00781 try { 00782 getLog().checkpoint(); 00783 } catch (Throwable e) { 00784 } 00785 } 00786 //#ifdef TRACE 00787 if (Trace.bit(8)) { 00788 Debug.println("removeConnection, count now: " + (connectionCount)); 00789 } 00790 //#endif 00791 if (connectionCount == 0 && closeCount == 1) { 00792 driver.closeDatabase(origFileName); 00793 } 00794 } 00795 } 00796 00797 /** 00798 * Return the number of active connections 00799 */ 00800 public int getConnectionCount() { 00801 return connectionCount; 00802 } 00803 00804 /** 00805 * Return the backup directory name 00806 */ 00807 public String getBackupDir() { 00808 return nroot.getBackupDir(); 00809 } 00810 00811 /** 00812 * Set the backup directory 00813 */ 00814 public void setBackupDir(String str) throws IOException { 00815 nroot.setBackupDir(str); 00816 flushRoot(); 00817 } 00818 00819 /** 00820 * Return the backup count 00821 */ 00822 public int getBackupCount() { 00823 return nroot.getBackupCount(); 00824 } 00825 00826 /** 00827 * Set the backup count 00828 */ 00829 public void setBackupCount(int count) throws IOException { 00830 nroot.setBackupCount(count); 00831 flushRoot(); 00832 } 00833 00834 /** 00835 * Return the backup days 00836 */ 00837 public int getBackupDays() { 00838 return nroot.getBackupDays(); 00839 } 00840 00841 /** 00842 * Set the backup days 00843 */ 00844 public void setBackupDays(int count) throws IOException { 00845 nroot.setBackupDays(count); 00846 flushRoot(); 00847 } 00848 00849 /** 00850 * Return the backup last day 00851 */ 00852 public int getBackupLastDay() { 00853 return nroot.getBackupLastDay(); 00854 } 00855 00856 /** 00857 * Set the backup last day 00858 */ 00859 public void setBackupLastDay(int day) throws IOException { 00860 nroot.setBackupLastDay(day); 00861 flushRoot(); 00862 } 00863 00864 /** 00865 * Return the backup time 00866 */ 00867 public int getBackupTime() { 00868 return nroot.getBackupTime(); 00869 } 00870 00871 /** 00872 * Set the backup time 00873 */ 00874 public void setBackupTime(int time) throws IOException { 00875 nroot.setBackupTime(time); 00876 flushRoot(); 00877 } 00878 00879 /** 00880 * Return the backup format 00881 */ 00882 public String getBackupFormat() { 00883 return nroot.getBackupFormat(); 00884 } 00885 00886 /** 00887 * Set the backup format 00888 */ 00889 public void setBackupFormat(String str) throws IOException { 00890 nroot.setBackupFormat(str); 00891 flushRoot(); 00892 } 00893 00894 /** 00895 * Check the specified authentication credentials; throw an exception 00896 * if authorization shouldn't be granted. 00897 * 00898 * We really don't care about passwords. This is an embedded database, 00899 * remember; it's really just a fancy API on top of a simple file! 00900 */ 00901 public void checkAuth(String user, String passwd) throws SQLException { 00902 // XXX impl 00903 } 00904 00905 /** 00906 * Return the current persistent refcount for the specified blob. 00907 */ 00908 public int getPermBlobRefCount(long blob) throws IOException { 00909 synchronized (fileLock) { 00910 ByteUtil.putLong(key, 0, blob); 00911 byte[] val = blobPermRefCounts.get(key); 00912 int refcount = val == null ? 0 : ByteUtil.getInt(val, 0); 00913 return refcount; 00914 } 00915 } 00916 00917 00918 //------------------------------------------------------------------ 00919 // private functions 00920 //------------------------------------------------------------------ 00921 00922 private byte[] key = new byte[8]; 00923 00924 public int refCountBlob(long transId, long blob, int incr) 00925 throws IOException 00926 { 00927 synchronized (fileLock) { 00928 // find the current refcount 00929 ByteUtil.putLong(key, 0, blob); 00930 byte[] val = blobPermRefCounts.get(key); 00931 int refcount = val == null ? 0 : ByteUtil.getInt(val, 0); 00932 int preRefcount = refcount; 00933 00934 // if refcount 0 -> 1, remove from morgue 00935 if (refcount == 0 && incr == 1) { 00936 val = new byte[4]; 00937 delMorgue(blob); 00938 } 00939 refcount += incr; 00940 if (refcount < 0) { 00941 throw new RuntimeException("freePermBlobRef(" + blob + 00942 "), refcount negative"); 00943 } 00944 00945 if (refcount <= 0) { 00946 if (preRefcount > 0) { 00947 addMorgue(blob, transId); 00948 } 00949 blobPermRefCounts.delete(key); 00950 } else { 00951 // otherwise, update perm refcount 00952 ByteUtil.putInt(val, 0, refcount); 00953 blobPermRefCounts.set(key, val); 00954 } 00955 00956 return refcount; 00957 } 00958 } 00959 00960 /** 00961 * Add a blob/clob to the morgue 00962 */ 00963 public void addMorgue(long blob, long transId) { 00964 synchronized (fileLock) { 00965 blobMorgue.put(blob, new Long(transId)); 00966 } 00967 } 00968 00969 /** 00970 * Remove a blob/clob from the morgue 00971 */ 00972 public void delMorgue(long blob) { 00973 synchronized (fileLock) { 00974 blobMorgue.remove(blob); 00975 } 00976 } 00977 00978 /** 00979 * Enumerate the morgue. 00980 */ 00981 public LongIterator enumerateMorgue() { 00982 return blobMorgue.keys(); 00983 } 00984 00985 /** 00986 * Called during a checkpoint operation. 00987 * 00988 * @param activeTransactions is a map whose keys are the set of 00989 * ids of active transactions. 00990 */ 00991 public void checkpointHandler(LongMap activeTransactions) 00992 throws IOException 00993 { 00994 emptyMorgue(activeTransactions); 00995 } 00996 00997 /** 00998 * Empty the BLOB morgue. Remove any BLOBS in the morge that were 00999 * deleted by transactions which have committed. 01000 */ 01001 public void emptyMorgue(LongMap activeTransactions) 01002 throws IOException 01003 { 01004 // ---- temporarily disassociate the log from the file so we don't 01005 // ---- log the file writes we're about to do. This is OK because 01006 // ---- we're doing a checkpoint now. 01007 Log saveLog = getLog(); 01008 getFile().setLog(null); 01009 try { 01010 synchronized (fileLock) { 01011 LongIterator iter = enumerateMorgue(); 01012 while (iter.hasNext()) { 01013 long blob = iter.nextLong(); 01014 long transId = ((Long)(blobMorgue.get(blob))).longValue(); 01015 if (activeTransactions.get(transId) == null) { 01016 try { 01017 getFile().freeStream(blob); 01018 } catch (Throwable t) { 01019 Debug.println("Error removing blob: " + blob); 01020 Debug.print(t); 01021 } 01022 iter.remove(); 01023 } 01024 } 01025 } 01026 } finally { 01027 // ---- restore the {file <--> log} association 01028 getFile().setLog(saveLog); 01029 } 01030 } 01031 01032 Session zSession = null; 01033 Connection zConn = null; 01034 01035 final Session getSession(Transaction t) throws IOException, SQLException { 01036 if (zSession == null) { 01037 synchronized (this) { 01038 if (zSession == null) { 01039 zConn = new Connection(this, "__SYSTEM", null); 01040 zSession = zConn.createSession(); 01041 } 01042 } 01043 } 01044 zConn.transId = t.getTransactionId(); // XXX what about sync? 01045 zConn.trans = t; 01046 return zSession; 01047 } 01048 01049 //#ifdef DEBUG 01050 public void display(PrintWriter w) throws IOException { 01051 w.println("Root:"); 01052 w.println(" blobs: " + nroot.getBlobRefCountRoot()); 01053 w.println(" relations: " + nroot.getRelationIndexNode()); 01054 w.println(" indexes: " + nroot.getIndexIndexNode()); 01055 w.println(" forward deps: " + nroot.getForwardDepsNode()); 01056 w.println(" reverse deps: " + nroot.getReverseDepsNode()); 01057 w.println(" next trans: " + nroot.getNextTransId()); 01058 if (true) {// iterate tables, build node map 01059 } 01060 if (true) { // display indexes, update node map 01061 w.println("indexIndex:"); 01062 indexIndex.display(w); 01063 w.println("tableIndex:"); 01064 tableIndex.display(w); 01065 } 01066 if (true) { // traverse freelists, update node map 01067 w.println("forwardDeps:"); 01068 forwardDeps.display(w); 01069 w.println("reverseDeps:"); 01070 reverseDeps.display(w); 01071 } 01072 if (true) { // search for blocks missing from node map 01073 } 01074 if (true) { // dump node map in block order 01075 } 01076 // traverse block file with node map used for annotations. 01077 } 01078 //#endif 01079 01080 /** 01081 * Convenience method to write a new row to the database 01082 */ 01083 public long putRow(Session session, BlockFile f, 01084 Tuple t, Row row) 01085 throws SQLException, IOException 01086 { 01087 long rowId; 01088 if (inMemory) { 01089 rowId = f.putObject(row = new Row(row, t)); 01090 } else { 01091 byte[] buf = LazyRow.writeRow(session, t, row); 01092 rowId = f.putBytes(buf); 01093 } 01094 //#ifdef TRACE 01095 if (Trace.bit(11)) { 01096 Debug.println((f == this.file ? "file" : "temp") + "." + 01097 ((t == null) ? "temp" : 01098 ("Table[" + t.getName() + "]")) + 01099 ".putRow(" + row + "): " + rowId); 01100 } 01101 //#endif 01102 return rowId; 01103 } 01104 01105 /** 01106 * Convenience method to update a table row 01107 */ 01108 public final void putRow(Session session, Tuple t, 01109 long rowId, Row row) 01110 throws SQLException, IOException 01111 { 01112 //#ifdef TRACE 01113 if (Trace.bit(11)) { 01114 Debug.println("Table[" + t.getName() + "].putRow(" + row + ")"); 01115 } 01116 //#endif 01117 if (inMemory) { 01118 getFile().updateObject(rowId, new Row(row, t)); 01119 } else { 01120 byte[] ret = LazyRow.writeRow(session, t, row); 01121 getFile().updateBytes(rowId, ret); 01122 } 01123 } 01124 01125 public void getRow(long rowId, LazyRow row, boolean isTemp) 01126 throws IOException, SQLException 01127 { 01128 BlockFile f = isTemp ? getTempFile(false) : getFile(); 01129 if (inMemory) { 01130 Row r = (Row)f.getObject(rowId); 01131 if (r == null) { 01132 throw new IOException("No row: " + rowId); 01133 } 01134 if (r.size() != row.size()) { 01135 //#ifdef DEBUG 01136 Debug.println((isTemp ? "temp" : "file") + ".getRow(" + 01137 rowId + ") = " + row); 01138 Debug.println("Bad row size in getRow: " + r.size() + 01139 " vs " + row.size()); 01140 //#endif 01141 throw new IOException("Bad row size in getRow: " + r.size() + 01142 " vs " + row.size()); 01143 } 01144 for (int i = 1; i <= row.size(); i++) { 01145 row.set(i, r.getItem(i)); 01146 } 01147 } else { 01148 byte[] rowBytes = f.getBytes(rowId); 01149 row.reset(rowBytes, this); 01150 } 01151 //#ifdef TRACE 01152 if (Trace.bit(11)) { 01153 Debug.println((isTemp ? "temp" : "file") + ".getRow(" + 01154 rowId + ") = " + row); 01155 } 01156 //#endif 01157 } 01158 01159 public void removeRow(long rowId) throws IOException { 01160 removeRow(getFile(), rowId); 01161 } 01162 01163 public void removeRow(BlockFile file, long rowId) throws IOException { 01164 if (inMemory) { 01165 file.removeObject(rowId); 01166 } else { 01167 file.freeStream(rowId); 01168 } 01169 } 01170 01171 public boolean inMemory() { 01172 return inMemory; 01173 } 01174 } 01175