0001: package com.quadcap.sql;
0002:
0003: /* Copyright 1999 - 2003 Quadcap Software. All rights reserved.
0004: *
0005: * This software is distributed under the Quadcap Free Software License.
0006: * This software may be used or modified for any purpose, personal or
0007: * commercial. Open Source redistributions are permitted. Commercial
0008: * redistribution of larger works derived from, or works which bundle
0009: * this software requires a "Commercial Redistribution License"; see
0010: * http://www.quadcap.com/purchase.
0011: *
0012: * Redistributions qualify as "Open Source" under one of the following terms:
0013: *
0014: * Redistributions are made at no charge beyond the reasonable cost of
0015: * materials and delivery.
0016: *
0017: * Redistributions are accompanied by a copy of the Source Code or by an
0018: * irrevocable offer to provide a copy of the Source Code for up to three
0019: * years at the cost of materials and delivery. Such redistributions
0020: * must allow further use, modification, and redistribution of the Source
0021: * Code under substantially the same terms as this license.
0022: *
0023: * Redistributions of source code must retain the copyright notices as they
0024: * appear in each source code file, these license terms, and the
0025: * disclaimer/limitation of liability set forth as paragraph 6 below.
0026: *
0027: * Redistributions in binary form must reproduce this Copyright Notice,
0028: * these license terms, and the disclaimer/limitation of liability set
0029: * forth as paragraph 6 below, in the documentation and/or other materials
0030: * provided with the distribution.
0031: *
0032: * The Software is provided on an "AS IS" basis. No warranty is
0033: * provided that the Software is free of defects, or fit for a
0034: * particular purpose.
0035: *
0036: * Limitation of Liability. Quadcap Software shall not be liable
0037: * for any damages suffered by the Licensee or any third party resulting
0038: * from use of the Software.
0039: */
0040:
0041: import java.io.BufferedWriter;
0042: import java.io.File;
0043: import java.io.FileOutputStream;
0044: import java.io.IOException;
0045: import java.io.OutputStream;
0046: import java.io.OutputStreamWriter;
0047: import java.io.PrintWriter;
0048:
0049: import java.util.ArrayList;
0050: import java.util.Calendar;
0051: import java.util.Collection;
0052: import java.util.Enumeration;
0053: import java.util.Iterator;
0054: import java.util.HashMap;
0055: import java.util.List;
0056: import java.util.Map;
0057: import java.util.Properties;
0058:
0059: import java.util.zip.GZIPOutputStream;
0060:
0061: import java.sql.SQLException;
0062:
0063: import com.quadcap.sql.file.BlockFile;
0064: import com.quadcap.sql.file.ByteUtil;
0065: import com.quadcap.sql.file.Datafile;
0066: import com.quadcap.sql.file.Log;
0067: import com.quadcap.sql.file.SubPageManager;
0068:
0069: import com.quadcap.sql.index.BCursor;
0070: import com.quadcap.sql.index.Btree;
0071:
0072: import com.quadcap.sql.lock.Transaction;
0073:
0074: import com.quadcap.util.collections.LongIterator;
0075: import com.quadcap.util.collections.LongMap;
0076:
0077: import com.quadcap.util.ConfigString;
0078: import com.quadcap.util.Debug;
0079: import com.quadcap.util.Util;
0080:
0081: /**
0082: * This class implements the QED SQL database outer API.
0083: *
0084: * @author Stan Bailes
0085: */
0086: public class Database extends Datafile {
0087: QDriver driver;
0088: Btree tableIndex;
0089: // map index name to table name
0090: Btree indexIndex;
0091: Btree blobPermRefCounts;
0092: Map tableMap = new HashMap();
0093: MultiSet forwardDeps = null;
0094: MultiSet reverseDeps = null;
0095: long rootBlock = 0;
0096: public DatabaseRoot nroot;
0097:
0098: /** The Morgue; BLOB/CLOBs with zero (persistent) refcounts:
0099: blobRef -> any */
0100: LongMap blobMorgue = new LongMap(32);
0101:
0102: /** Map blob -> Vector<transid> */
0103: LongMap blobActivity = new LongMap(128);
0104:
0105: int connectionCount = 0;
0106: int closeCount = 0;
0107: long nextTransId = 0;
0108:
0109: public Object driverLock;
0110:
0111: static final ConfigString backupClass = ConfigString.find(
0112: "qed.backupClass", "com.quadcap.sql.tools.XmlDump");
0113:
0114: /**
0115: * Constructor
0116: */
0117: public Database() {
0118: }
0119:
0120: public void init(QDriver driver, String url, String fileName,
0121: Properties props) throws IOException, SQLException {
0122: this .driver = driver;
0123: init(url, fileName, props);
0124: }
0125:
0126: /**
0127: * Create a new root block
0128: */
0129: public void createRoot(String fileName, Properties props)
0130: throws IOException {
0131: this .rootBlock = getFile().getUserBlock(0);
0132: this .root = this .nroot = new DatabaseRoot(this , fileName, props);
0133: if (!readOnly && !inMemory) {
0134: new File(fileName, "logfile").delete();
0135: flushRoot();
0136: }
0137: }
0138:
0139: /**
0140: * Read the root block from an existing database
0141: */
0142: public void readRoot() throws IOException {
0143: this .rootBlock = getFile().getUserBlock(0);
0144: root = nroot = (DatabaseRoot) file.getObject(rootBlock);
0145: nroot.setDatabase(this );
0146: }
0147:
0148: /**
0149: * Write the database root
0150: */
0151: public void flushRoot() throws IOException {
0152: if (!inMemory /*?*/&& !readOnly) {
0153: getFile().updateObject(rootBlock, root);
0154: }
0155: }
0156:
0157: /**
0158: * Get ready to rumble!!!
0159: */
0160: public void bootFromRoot(boolean restart) throws IOException {
0161: nextTransId = root.getNextTransId();
0162: nroot.setNextTransId((nextTransId | 0xff) + 1);
0163:
0164: blobPermRefCounts = new Btree(file,
0165: nroot.getBlobRefCountRoot(), !restart);
0166: tableIndex = new Btree(file, nroot.getRelationIndexNode(),
0167: !restart);
0168: indexIndex = new Btree(file, nroot.getIndexIndexNode(),
0169: !restart);
0170: forwardDeps = new MultiSet(new Btree(file, nroot
0171: .getForwardDepsNode(), !restart));
0172: reverseDeps = new MultiSet(new Btree(file, nroot
0173: .getReverseDepsNode(), !restart));
0174: }
0175:
0176: /**
0177: * Return the next available transaction ID.
0178: */
0179: public long getNextTransId() throws IOException {
0180: synchronized (fileLock) {
0181: long t = nextTransId++;
0182: if (!inMemory && (nextTransId & 0xff) == 0) {
0183: nroot.setNextTransId(nextTransId + 0x100);
0184: flushRoot();
0185: }
0186: return t;
0187: }
0188: }
0189:
0190: /**
0191: * Figure out if it's time to do a backup.
0192: */
0193: public void maybeBackup() throws IOException {
0194: if (inMemory)
0195: return;
0196: // are backups enabled?
0197: int days = getBackupDays();
0198: if (days == 0)
0199: return;
0200:
0201: // is today the day?
0202: Calendar now = Calendar.getInstance();
0203: int nowDay = now.get(Calendar.DAY_OF_WEEK);
0204: if (((days >> nowDay) & 1) == 0)
0205: return;
0206:
0207: // is now the time?
0208: int time = getBackupTime();
0209: int today = now.get(Calendar.DAY_OF_YEAR);
0210: int nowTime = now.get(Calendar.HOUR_OF_DAY) * 60
0211: + now.get(Calendar.MINUTE);
0212: if (nowTime < time || today == getBackupLastDay())
0213: return;
0214:
0215: // cycle the old backup files
0216: int count = getBackupCount();
0217: for (int i = count - 2; i >= 0; i--) {
0218: makeBackupFile(i).renameTo(makeBackupFile(i + 1));
0219: }
0220:
0221: // write new backup in correct format
0222: OutputStream w = new FileOutputStream(makeBackupFile(0));
0223: if (getBackupFormat().equals("xml.gz")) {
0224: w = new GZIPOutputStream(w);
0225: }
0226: OutputStreamWriter ow = new OutputStreamWriter(w);
0227: BufferedWriter bw = new BufferedWriter(ow);
0228:
0229: // create the backup helper and do it.
0230: try {
0231: java.sql.Connection conn = driver.makeConnection(this ,
0232: "backup", null);
0233: Class c = Class.forName(backupClass.toString());
0234: Backup b = (Backup) c.newInstance();
0235: b.backup(conn, bw);
0236: bw.close();
0237: } catch (IOException e) {
0238: throw e;
0239: } catch (Throwable t) {
0240: throw new IOException(t.toString());
0241: }
0242: setBackupLastDay(today);
0243: }
0244:
0245: File makeBackupFile(int n) throws IOException {
0246: File bdir = new File(getBackupDir());
0247: if (!bdir.exists()) {
0248: if (!bdir.mkdirs()) {
0249: throw new IOException("can't create backup directory: "
0250: + bdir.getAbsolutePath());
0251: }
0252: }
0253: String name = "backup-" + n + "." + getBackupFormat();
0254: return new File(bdir, name);
0255: }
0256:
0257: /**
0258: * TODO: XXX Unicode!!!
0259: * Add a new named index for the specified table.
0260: */
0261: public void addIndex(String indexName, String tableName)
0262: throws IOException {
0263: synchronized (fileLock) {
0264: indexIndex.set(indexName.getBytes(), tableName.getBytes());
0265: }
0266: }
0267:
0268: /**
0269: * Return the table for the index of the specified name.
0270: */
0271: public String getTableForIndex(String indexName) throws IOException {
0272: synchronized (fileLock) {
0273: byte[] ret = indexIndex.get(indexName.getBytes());
0274: if (ret == null)
0275: return null;
0276: return new String(ret);
0277: }
0278: }
0279:
0280: /**
0281: * Remove the specified index
0282: */
0283: public void deleteIndex(String indexName) throws IOException {
0284: synchronized (fileLock) {
0285: indexIndex.delete(indexName.getBytes());
0286: }
0287: }
0288:
0289: /**
0290: * RENAME TABLE percolates into the index index...
0291: */
0292: void renameIndexedTable(String oldN, String newN)
0293: throws IOException {
0294: synchronized (fileLock) {
0295: BCursor bc = indexIndex.getCursor(false);
0296: if (bc != null) {
0297: try {
0298: while (bc.next()) {
0299: String name = new String(bc.getVal());
0300: if (name.equals(oldN)) {
0301: bc.prev();
0302: bc.replace(newN.getBytes());
0303: bc.next();
0304: }
0305: }
0306: } finally {
0307: bc.release();
0308: }
0309: }
0310: }
0311: }
0312:
0313: /**
0314: * Return the table identity for the table
0315: *
0316: * @exception SQLException if the table can't be located
0317: */
0318: public long getTableIdentity(String name) throws IOException,
0319: SQLException {
0320: synchronized (fileLock) {
0321: if (inMemory) {
0322: Table t = (Table) getRelation(name);
0323: return t.tableIdentity;
0324: } else {
0325: BCursor tc = tableIndex.getCursor(true);
0326: try {
0327: if (tc.seek(name.getBytes())
0328: && tc.getValLen() >= 16) {
0329: return ByteUtil.getLong(tc.getValBuf(), 8);
0330: } else {
0331: throw new SQLException("getTableIdentity("
0332: + name + "): no table");
0333: }
0334: } finally {
0335: tc.release();
0336: }
0337: }
0338: }
0339: }
0340:
0341: /**
0342: * The "table identity" is a long value stored along with the table
0343: * stream pointer, so that it can easily be updated as long as the index
0344: * is in memory, without touching any data pages.
0345: */
0346: public void updateTableIdentity(String name, long num)
0347: throws IOException, SQLException {
0348: synchronized (fileLock) {
0349: if (inMemory) {
0350: Table t = (Table) getRelation(name);
0351: t.tableIdentity = num;
0352: } else {
0353: BCursor tc = tableIndex.getCursor(true);
0354: try {
0355: if (tc.seek(name.getBytes())) {
0356: int cnt = tc.getVal(temp);
0357: ByteUtil.putLong(temp, 8, num);
0358: tc.replace(temp, 0, cnt);
0359: return;
0360: } else {
0361: throw new SQLException("getTableIdentity("
0362: + name + "): no table");
0363: }
0364: } finally {
0365: tc.release();
0366: }
0367: }
0368: }
0369: }
0370:
0371: /**
0372: * Helper to execute a single SQL statement (in its own connection)
0373: * and return a result set.
0374: */
0375: public java.sql.ResultSet execute(String sql) throws SQLException,
0376: IOException {
0377: Connection c = new Connection(this , "__NO_AUTH__", null);
0378: Session session = c.createSession();
0379: return execute(session, sql);
0380: }
0381:
0382: /**
0383: * Helper to execute a single SQL statement for the given session.
0384: */
0385: public java.sql.ResultSet execute(Session session, String sql)
0386: throws IOException, SQLException {
0387: SQLParser p = new SQLParser(session, sql, true);
0388: Stmt s = null;
0389: try {
0390: s = p.statement();
0391: } catch (Throwable t) {
0392: Debug.print(t);
0393: }
0394:
0395: if (s == null) {
0396: throw new SQLException("Parse error", "42000");
0397: }
0398: session.doStatement(s);
0399: return session.getResultSet();
0400: }
0401:
0402: /**
0403: * Return the table/view with the specified name
0404: *
0405: * XXX Fix this "cache" implementation
0406: */
0407: public Relation getRelation(String name) throws IOException {
0408: Relation t = null;
0409: synchronized (fileLock) {
0410: t = (Relation) tableMap.get(name);
0411: if (!inMemory && t == null) {
0412: BCursor tc = tableIndex.getCursor(true);
0413: try {
0414: if (tc.seek(name.getBytes())) {
0415: byte[] d = tc.getValBuf();
0416: t = (Relation) getFile().getObject(
0417: ByteUtil.getLong(d, 0));
0418: if (t != null) {
0419: tableMap.put(name, t);
0420: }
0421: }
0422: } finally {
0423: tc.release();
0424: }
0425: }
0426: }
0427: //#ifdef DEBUG
0428: if (Trace.bit(0)) {
0429: Debug.println("getRelation(" + name + ") = "
0430: + ((t == null) ? "NOT FOUND" : "FOUND"));
0431: }
0432: //#endif
0433: return t;
0434: }
0435:
0436: /**
0437: * Add a new table/view
0438: ' */
0439: public void addRelation(Relation table) throws IOException,
0440: SQLException {
0441: synchronized (fileLock) {
0442: String name = table.getName();
0443: //#ifdef TRACE
0444: if (Trace.bit(0)) {
0445: Debug.println("addRelation(" + name + ")");
0446: }
0447: //#endif
0448: if (tableMap.get(name) == null) {
0449: tableMap.put(name, table);
0450: if (!inMemory) {
0451: BCursor tc = tableIndex.getCursor(true);
0452: try {
0453: byte[] tkey = name.getBytes();
0454: if (!tc.seek(name.getBytes())) {
0455: long ref = getFile().putObject(table);
0456: ByteUtil.putLong(temp, 0, ref);
0457: ByteUtil.putLong(temp, 8, 1); // start counting at '1'
0458: tc.insert(tkey, tkey.length, temp, 0, 16);
0459: }
0460: } finally {
0461: tc.release();
0462: }
0463: }
0464: } else {
0465: throw new SQLException("Table/view already exists: "
0466: + name, "42000");
0467: }
0468: }
0469: }
0470:
0471: /**
0472: * Delete the specified table/view
0473: */
0474: public void removeRelation(String name) throws IOException,
0475: SQLException {
0476: synchronized (fileLock) {
0477: //#ifdef DEBUG
0478: if (Trace.bit(0)) {
0479: Debug.println("removeRelation(" + name + ")");
0480: }
0481: //#endif
0482: Relation t = getRelation(name);
0483: if (t == null) {
0484: throw new SQLException("No such table/view: " + name,
0485: "42000");
0486: }
0487: removeViewDependencies(name);
0488: tableMap.remove(name);
0489: if (!inMemory) {
0490: BCursor tc = tableIndex.getCursor(true);
0491: try {
0492: if (tc.seek(name.getBytes())) {
0493: getFile().removeObject(tc.getValAsLong());
0494: tc.delete();
0495: }
0496: } finally {
0497: tc.release();
0498: }
0499: }
0500: }
0501: //#ifdef DEBUG
0502: if (getRelation(name) != null) {
0503: throw new SQLException("Relation not removed!!!");
0504: }
0505: //#endif
0506: }
0507:
0508: /**
0509: * Rename a relation. This involves:
0510: *
0511: * 1. Changing the cached relation
0512: * 2. Changing the btree index to the relation
0513: * 3. Changing any indexIndex entries referring to the rel
0514: * 4. Changing any forwardDeps or backwardDeps which
0515: * refer to the relation
0516: */
0517: public void renameRelation(Relation r, String newN)
0518: throws IOException {
0519: synchronized (fileLock) {
0520: String oldN = r.getName();
0521: r.setName(newN);
0522: tableMap.remove(oldN);
0523: tableMap.put(newN, r);
0524: if (!inMemory) {
0525: BCursor tc = tableIndex.getCursor(true);
0526: try {
0527: if (tc.seek(oldN.getBytes())) {
0528: byte[] d = tc.getValBuf();
0529: long ref = ByteUtil.getLong(d, 0);
0530: tc.delete();
0531: byte[] nb = newN.getBytes();
0532: if (!tc.seek(nb)) {
0533: tc.insert(nb, nb.length, d, 0, 16);
0534: getFile().updateObject(ref, r);
0535: }
0536: }
0537: } finally {
0538: tc.release();
0539: }
0540: }
0541: if (true) {
0542: forwardDeps.rename(oldN, newN, reverseDeps);
0543: reverseDeps.rename(oldN, newN, forwardDeps);
0544: }
0545: if (true) {
0546: renameIndexedTable(oldN, newN);
0547: }
0548: }
0549: }
0550:
0551: /**
0552: * Store a modified version of the specified view/table to the
0553: * store
0554: */
0555: public void updateRelation(Relation table) throws IOException {
0556: synchronized (fileLock) {
0557: //#ifdef TRACE
0558: if (Trace.bit(0)) {
0559: Debug
0560: .println("updateRelation(" + table.getName()
0561: + ")");
0562: //Debug.println("updateRelation(" + table.getName() + ": " + table + ")"); // + Util.stackTrace());
0563: }
0564: //#endif
0565: tableMap.put(table.getName(), table);
0566: if (!inMemory) {
0567: BCursor tc = tableIndex.getCursor(true);
0568: try {
0569: if (tc.seek(table.getName().getBytes())) {
0570: getFile()
0571: .updateObject(tc.getValAsLong(), table);
0572: } else {
0573: throw new IOException("Not found: "
0574: + table.getName());
0575: }
0576: } finally {
0577: tc.release();
0578: }
0579: }
0580: }
0581: }
0582:
0583: /**
0584: * Establish the dependency link between a view and a base table
0585: */
0586: public void addViewDependency(String base, String view)
0587: throws IOException {
0588: //#ifdef TRACE
0589: if (Trace.bit(0)) {
0590: Debug.println("addViewDependency(" + base + ", " + view
0591: + ")");
0592: }
0593: //#endif
0594:
0595: synchronized (fileLock) {
0596: forwardDeps.put(base, view);
0597: reverseDeps.put(view, base);
0598: }
0599: }
0600:
0601: /**
0602: * Make sure that the specified view dependency is valid
0603: */
0604: public void checkViewDependency(String base, String view)
0605: throws SQLException, IOException {
0606: Relation baseR = getRelation(base);
0607: if (baseR == null) {
0608: throw new SQLException("Base table not found: " + base,
0609: "42000");
0610: }
0611: if (baseR instanceof Table) {
0612: Table baseT = (Table) baseR;
0613: int modifiers = baseT.getModifiers();
0614: if ((modifiers & Table.TEMPORARY) != 0) {
0615: throw new SQLException(
0616: "Can't create view of temporary " + "table "
0617: + base, "42000");
0618: }
0619: }
0620: }
0621:
0622: /**
0623: * Return an enumeration of all views for the specified base table
0624: */
0625: public Enumeration getViews(String base) throws IOException {
0626: synchronized (fileLock) {
0627: return forwardDeps.get(base);
0628: }
0629: }
0630:
0631: /**
0632: * Return an enumeration containing all base tables for the specified
0633: * view
0634: */
0635: public Enumeration getBases(String view) throws IOException {
0636: synchronized (fileLock) {
0637: return reverseDeps.get(view);
0638: }
0639: }
0640:
0641: public void removeViewDependencies(String view)
0642: throws SQLException, IOException {
0643: //#ifdef TRACE
0644: if (Trace.bit(0)) {
0645: Debug.println("removeViewDependencies(" + view + ")");
0646: }
0647: //#endif
0648: synchronized (fileLock) {
0649: Enumeration views = getViews(view);
0650: if (views.hasMoreElements()) {
0651: throw new SQLException(
0652: "view has dependencies: " + view, "42000");
0653: }
0654: Enumeration bases = getBases(view);
0655: while (bases.hasMoreElements()) {
0656: forwardDeps
0657: .delete(bases.nextElement().toString(), view);
0658: }
0659: }
0660: }
0661:
0662: /**
0663: * This hacky crack of encapsulation is for sql.meta.MetaTables, to
0664: * support DatabaseMetaData.getTables(). All he really needs is
0665: * an Iterator which returns the table names....
0666: */
0667: // public Btree getRelationIndex() {
0668: // return tableIndex;
0669: // }
0670: public Iterator getRelationNameIterator() throws IOException {
0671: if (inMemory) {
0672: return tableMap.keySet().iterator();
0673: } else {
0674: final BCursor tc = tableIndex.getCursor(false);
0675: return new Iterator() {
0676: boolean called = false;
0677: boolean last = false;
0678: boolean closed = false;
0679:
0680: public boolean hasNext() {
0681: try {
0682: if (!closed && !called) {
0683: last = tc.next();
0684: called = true;
0685: }
0686: } catch (IOException ex) {
0687: last = false;
0688: }
0689: if (!last && !closed) {
0690: closed = true;
0691: try {
0692: tc.release();
0693: } catch (Throwable t) {
0694: Debug.print(t);
0695: }
0696: }
0697: return last;
0698: }
0699:
0700: public Object next() {
0701: Object ret = null;
0702: try {
0703: if (!called && !closed) {
0704: last = tc.next();
0705: }
0706: if (!last) {
0707: if (!closed) {
0708: closed = true;
0709: try {
0710: tc.release();
0711: } catch (Throwable t) {
0712: Debug.print(t);
0713: }
0714: }
0715: } else {
0716: ret = new String(tc.getKey());
0717: called = false;
0718: }
0719: } catch (IOException ex) {
0720: ret = "<Exception: " + ex + ">";
0721: }
0722: return ret;
0723: }
0724:
0725: public void remove() {
0726: try {
0727: tc.delete();
0728: } catch (IOException e) {
0729: Debug.print(e);
0730: throw new RuntimeException(
0731: "Error deleting record", e);
0732: }
0733: }
0734: };
0735: }
0736: }
0737:
0738: // public Collection getRelationNames() throws IOException {
0739: // ArrayList list = new ArrayList();
0740: // synchronized (fileLock) {
0741: // if (inMemory) {
0742: // return tableMap.keySet();
0743: // }
0744: // BCursor tc = tableIndex.getCursor(false);
0745: // try {
0746: // while (tc.next()) {
0747: // list.add(new String(tc.getKey()));
0748: // }
0749: // } finally {
0750: // tc.release();
0751: // }
0752: // }
0753: // return list;
0754: // }
0755:
0756: /**
0757: * Add a new connection (just remember the count...)
0758: */
0759: public int addConnection() {
0760: synchronized (driverLock) {
0761: //#ifdef TRACE
0762: if (Trace.bit(8)) {
0763: Debug.println("addConnection, count now: "
0764: + (connectionCount + 1));
0765: }
0766: //#endif
0767: return ++connectionCount;
0768: }
0769: }
0770:
0771: /**
0772: * Sleight of hand to get my driver interface
0773: */
0774: public QDriver getDriver() {
0775: return driver;
0776: }
0777:
0778: /**
0779: * Remove a connection. If the connection count drops to zero, we
0780: * get crafty.
0781: */
0782: public void removeConnection() {
0783: synchronized (driverLock) {
0784: if (--connectionCount == 0 && closeCount++ < 1) {
0785: try {
0786: getLog().checkpoint();
0787: } catch (Throwable e) {
0788: }
0789: }
0790: //#ifdef TRACE
0791: if (Trace.bit(8)) {
0792: Debug.println("removeConnection, count now: "
0793: + (connectionCount));
0794: }
0795: //#endif
0796: if (connectionCount == 0 && closeCount == 1) {
0797: driver.closeDatabase(origFileName);
0798: }
0799: }
0800: }
0801:
0802: /**
0803: * Return the number of active connections
0804: */
0805: public int getConnectionCount() {
0806: return connectionCount;
0807: }
0808:
0809: /**
0810: * Return the backup directory name
0811: */
0812: public String getBackupDir() {
0813: return nroot.getBackupDir();
0814: }
0815:
0816: /**
0817: * Set the backup directory
0818: */
0819: public void setBackupDir(String str) throws IOException {
0820: nroot.setBackupDir(str);
0821: flushRoot();
0822: }
0823:
0824: /**
0825: * Return the backup count
0826: */
0827: public int getBackupCount() {
0828: return nroot.getBackupCount();
0829: }
0830:
0831: /**
0832: * Set the backup count
0833: */
0834: public void setBackupCount(int count) throws IOException {
0835: nroot.setBackupCount(count);
0836: flushRoot();
0837: }
0838:
0839: /**
0840: * Return the backup days
0841: */
0842: public int getBackupDays() {
0843: return nroot.getBackupDays();
0844: }
0845:
0846: /**
0847: * Set the backup days
0848: */
0849: public void setBackupDays(int count) throws IOException {
0850: nroot.setBackupDays(count);
0851: flushRoot();
0852: }
0853:
0854: /**
0855: * Return the backup last day
0856: */
0857: public int getBackupLastDay() {
0858: return nroot.getBackupLastDay();
0859: }
0860:
0861: /**
0862: * Set the backup last day
0863: */
0864: public void setBackupLastDay(int day) throws IOException {
0865: nroot.setBackupLastDay(day);
0866: flushRoot();
0867: }
0868:
0869: /**
0870: * Return the backup time
0871: */
0872: public int getBackupTime() {
0873: return nroot.getBackupTime();
0874: }
0875:
0876: /**
0877: * Set the backup time
0878: */
0879: public void setBackupTime(int time) throws IOException {
0880: nroot.setBackupTime(time);
0881: flushRoot();
0882: }
0883:
0884: /**
0885: * Return the backup format
0886: */
0887: public String getBackupFormat() {
0888: return nroot.getBackupFormat();
0889: }
0890:
0891: /**
0892: * Set the backup format
0893: */
0894: public void setBackupFormat(String str) throws IOException {
0895: nroot.setBackupFormat(str);
0896: flushRoot();
0897: }
0898:
0899: /**
0900: * Check the specified authentication credentials; throw an exception
0901: * if authorization shouldn't be granted.
0902: *
0903: * We really don't care about passwords. This is an embedded database,
0904: * remember; it's really just a fancy API on top of a simple file!
0905: */
0906: public void checkAuth(String user, String passwd)
0907: throws SQLException {
0908: // XXX impl
0909: }
0910:
0911: /**
0912: * Return the current persistent refcount for the specified blob.
0913: */
0914: public int getPermBlobRefCount(long blob) throws IOException {
0915: synchronized (fileLock) {
0916: ByteUtil.putLong(key, 0, blob);
0917: byte[] val = blobPermRefCounts.get(key);
0918: int refcount = val == null ? 0 : ByteUtil.getInt(val, 0);
0919: return refcount;
0920: }
0921: }
0922:
0923: //------------------------------------------------------------------
0924: // private functions
0925: //------------------------------------------------------------------
0926:
0927: private byte[] key = new byte[8];
0928:
0929: public int refCountBlob(long transId, long blob, int incr)
0930: throws IOException {
0931: synchronized (fileLock) {
0932: // find the current refcount
0933: ByteUtil.putLong(key, 0, blob);
0934: byte[] val = blobPermRefCounts.get(key);
0935: int refcount = val == null ? 0 : ByteUtil.getInt(val, 0);
0936: int preRefcount = refcount;
0937:
0938: // if refcount 0 -> 1, remove from morgue
0939: if (refcount == 0 && incr == 1) {
0940: val = new byte[4];
0941: delMorgue(blob);
0942: }
0943: refcount += incr;
0944: if (refcount < 0) {
0945: throw new RuntimeException("freePermBlobRef(" + blob
0946: + "), refcount negative");
0947: }
0948:
0949: if (refcount <= 0) {
0950: if (preRefcount > 0) {
0951: addMorgue(blob, transId);
0952: }
0953: blobPermRefCounts.delete(key);
0954: } else {
0955: // otherwise, update perm refcount
0956: ByteUtil.putInt(val, 0, refcount);
0957: blobPermRefCounts.set(key, val);
0958: }
0959:
0960: return refcount;
0961: }
0962: }
0963:
0964: /**
0965: * Add a blob/clob to the morgue
0966: */
0967: public void addMorgue(long blob, long transId) {
0968: synchronized (fileLock) {
0969: blobMorgue.put(blob, new Long(transId));
0970: }
0971: }
0972:
0973: /**
0974: * Remove a blob/clob from the morgue
0975: */
0976: public void delMorgue(long blob) {
0977: synchronized (fileLock) {
0978: blobMorgue.remove(blob);
0979: }
0980: }
0981:
0982: /**
0983: * Enumerate the morgue.
0984: */
0985: public LongIterator enumerateMorgue() {
0986: return blobMorgue.keys();
0987: }
0988:
0989: /**
0990: * Called during a checkpoint operation.
0991: *
0992: * @param activeTransactions is a map whose keys are the set of
0993: * ids of active transactions.
0994: */
0995: public void checkpointHandler(LongMap activeTransactions)
0996: throws IOException {
0997: emptyMorgue(activeTransactions);
0998: }
0999:
1000: /**
1001: * Empty the BLOB morgue. Remove any BLOBS in the morge that were
1002: * deleted by transactions which have committed.
1003: */
1004: public void emptyMorgue(LongMap activeTransactions)
1005: throws IOException {
1006: // ---- temporarily disassociate the log from the file so we don't
1007: // ---- log the file writes we're about to do. This is OK because
1008: // ---- we're doing a checkpoint now.
1009: Log saveLog = getLog();
1010: getFile().setLog(null);
1011: try {
1012: synchronized (fileLock) {
1013: LongIterator iter = enumerateMorgue();
1014: while (iter.hasNext()) {
1015: long blob = iter.nextLong();
1016: long transId = ((Long) (blobMorgue.get(blob)))
1017: .longValue();
1018: if (activeTransactions.get(transId) == null) {
1019: try {
1020: getFile().freeStream(blob);
1021: } catch (Throwable t) {
1022: Debug.println("Error removing blob: "
1023: + blob);
1024: Debug.print(t);
1025: }
1026: iter.remove();
1027: }
1028: }
1029: }
1030: } finally {
1031: // ---- restore the {file <--> log} association
1032: getFile().setLog(saveLog);
1033: }
1034: }
1035:
1036: Session zSession = null;
1037: Connection zConn = null;
1038:
1039: final Session getSession(Transaction t) throws IOException,
1040: SQLException {
1041: if (zSession == null) {
1042: synchronized (this ) {
1043: if (zSession == null) {
1044: zConn = new Connection(this , "__SYSTEM", null);
1045: zSession = zConn.createSession();
1046: }
1047: }
1048: }
1049: zConn.transId = t.getTransactionId(); // XXX what about sync?
1050: zConn.trans = t;
1051: return zSession;
1052: }
1053:
1054: //#ifdef DEBUG
1055: public void display(PrintWriter w) throws IOException {
1056: w.println("Root:");
1057: w.println(" blobs: " + nroot.getBlobRefCountRoot());
1058: w.println(" relations: " + nroot.getRelationIndexNode());
1059: w.println(" indexes: " + nroot.getIndexIndexNode());
1060: w.println(" forward deps: " + nroot.getForwardDepsNode());
1061: w.println(" reverse deps: " + nroot.getReverseDepsNode());
1062: w.println(" next trans: " + nroot.getNextTransId());
1063: if (true) {// iterate tables, build node map
1064: }
1065: if (true) { // display indexes, update node map
1066: w.println("indexIndex:");
1067: indexIndex.display(w);
1068: w.println("tableIndex:");
1069: tableIndex.display(w);
1070: }
1071: if (true) { // traverse freelists, update node map
1072: w.println("forwardDeps:");
1073: forwardDeps.display(w);
1074: w.println("reverseDeps:");
1075: reverseDeps.display(w);
1076: }
1077: if (true) { // search for blocks missing from node map
1078: }
1079: if (true) { // dump node map in block order
1080: }
1081: // traverse block file with node map used for annotations.
1082: }
1083:
1084: //#endif
1085:
1086: /**
1087: * Convenience method to write a new row to the database
1088: */
1089: public long putRow(Session session, BlockFile f, Tuple t, Row row)
1090: throws SQLException, IOException {
1091: long rowId;
1092: if (inMemory) {
1093: rowId = f.putObject(row = new Row(row, t));
1094: } else {
1095: byte[] buf = LazyRow.writeRow(session, t, row);
1096: rowId = f.putBytes(buf);
1097: }
1098: //#ifdef TRACE
1099: if (Trace.bit(11)) {
1100: Debug.println((f == this .file ? "file" : "temp")
1101: + "."
1102: + ((t == null) ? "temp"
1103: : ("Table[" + t.getName() + "]"))
1104: + ".putRow(" + row + "): " + rowId);
1105: }
1106: //#endif
1107: return rowId;
1108: }
1109:
1110: /**
1111: * Convenience method to update a table row
1112: */
1113: public final void putRow(Session session, Tuple t, long rowId,
1114: Row row) throws SQLException, IOException {
1115: //#ifdef TRACE
1116: if (Trace.bit(11)) {
1117: Debug.println("Table[" + t.getName() + "].putRow(" + row
1118: + ")");
1119: }
1120: //#endif
1121: if (inMemory) {
1122: getFile().updateObject(rowId, new Row(row, t));
1123: } else {
1124: byte[] ret = LazyRow.writeRow(session, t, row);
1125: getFile().updateBytes(rowId, ret);
1126: }
1127: }
1128:
1129: public void getRow(long rowId, LazyRow row, boolean isTemp)
1130: throws IOException, SQLException {
1131: BlockFile f = isTemp ? getTempFile(false) : getFile();
1132: if (inMemory) {
1133: Row r = (Row) f.getObject(rowId);
1134: if (r == null) {
1135: throw new IOException("No row: " + rowId);
1136: }
1137: if (r.size() != row.size()) {
1138: //#ifdef DEBUG
1139: Debug.println((isTemp ? "temp" : "file") + ".getRow("
1140: + rowId + ") = " + row);
1141: Debug.println("Bad row size in getRow: " + r.size()
1142: + " vs " + row.size());
1143: //#endif
1144: throw new IOException("Bad row size in getRow: "
1145: + r.size() + " vs " + row.size());
1146: }
1147: for (int i = 1; i <= row.size(); i++) {
1148: row.set(i, r.getItem(i));
1149: }
1150: } else {
1151: byte[] rowBytes = f.getBytes(rowId);
1152: row.reset(rowBytes, this );
1153: }
1154: //#ifdef TRACE
1155: if (Trace.bit(11)) {
1156: Debug.println((isTemp ? "temp" : "file") + ".getRow("
1157: + rowId + ") = " + row);
1158: }
1159: //#endif
1160: }
1161:
1162: public void removeRow(long rowId) throws IOException {
1163: removeRow(getFile(), rowId);
1164: }
1165:
1166: public void removeRow(BlockFile file, long rowId)
1167: throws IOException {
1168: if (inMemory) {
1169: file.removeObject(rowId);
1170: } else {
1171: file.freeStream(rowId);
1172: }
1173: }
1174:
1175: public boolean inMemory() {
1176: return inMemory;
1177: }
1178: }
|