001: package com.quadcap.sql.file;
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.ByteArrayOutputStream;
042: import java.io.BufferedInputStream;
043: import java.io.File;
044: import java.io.FileOutputStream;
045: import java.io.IOException;
046: import java.io.InputStream;
047: import java.io.OutputStream;
048: import java.io.RandomAccessFile;
049:
050: //#ifdef JDK14
051: import java.nio.channels.FileChannel;
052: import java.nio.channels.FileLock; //#endif
053:
054: import java.util.HashMap;
055: import java.util.Iterator;
056: import java.util.Properties;
057:
058: import com.quadcap.sql.lock.Lock;
059: import com.quadcap.sql.lock.LockManager;
060: import com.quadcap.sql.lock.LockMode;
061: import com.quadcap.sql.lock.Transaction;
062: import com.quadcap.sql.lock.TransactionObserver;
063:
064: import com.quadcap.sql.types.Value;
065:
066: import com.quadcap.util.collections.LongIterator;
067: import com.quadcap.util.collections.LongMap;
068:
069: import com.quadcap.util.ConfigNumber;
070: import com.quadcap.util.ConfigString;
071: import com.quadcap.util.Debug;
072: import com.quadcap.util.Util;
073:
074: /**
075: * This class brings together the various file-level components of the
076: * structured (SQL) database.
077: *
078: * @author Stan Bailes
079: */
080: abstract public class Datafile {
081: protected boolean inMemory = false;
082: String fileName;
083:
084: protected DatafileRoot root;
085: File dbRootDir;
086: File tempDir;
087: String url;
088: protected String origFileName;
089:
090: // "datafile"
091: protected BlockFile file;
092:
093: // "scratch"
094: protected BlockFile tempFile;
095:
096: // "log" file
097: Log log;
098:
099: // read/write table locks
100: LockManager locks = null;
101:
102: // Original connection properties
103: Properties props;
104:
105: static {
106: try {
107: Class.forName("com.quadcap.sql.io.Extern");
108: } catch (Throwable t) {
109: }
110: }
111:
112: /** True if we've locked the lockfile */
113: private FileChannel lockChannel = null;
114: private FileLock lockLock = null;
115:
116: protected boolean readOnly = false;
117: protected byte[] temp = new byte[256];
118:
119: protected Object fileLock = new Object();
120:
121: private int tempFileRefCount = 0;
122:
123: abstract public void maybeBackup() throws IOException;
124:
125: abstract public void readRoot() throws IOException;
126:
127: abstract public void createRoot(String fileName, Properties props)
128: throws IOException;
129:
130: abstract public void flushRoot() throws IOException;
131:
132: abstract public long getNextTransId() throws IOException;
133:
134: abstract public void bootFromRoot(boolean restart)
135: throws IOException;
136:
137: /**
138: * Minimum cache size: 256K, with 8K blocks. The minimum is
139: * dependent on various factors, including query type (joins
140: * need more cache, for example) and concurrency level.
141: *
142: * Simple applications which are primarily single threaded will
143: * be able to get by with 32 blocks or so.
144: */
145: static final int MIN_CACHE = 32;
146:
147: /**
148: * Reasonable cache size: 4MB (8K blocks). This cache size should
149: * support a moderate load; 8-10 busy threads...
150: */
151: static final int GOOD_CACHE = 256;
152:
153: /**
154: * Reasonable scratch cache default
155: */
156: static final int SCRATCH_CACHE_DEFAULT = 64;
157:
158: /*{com.quadcap.util.Config-vars.xml-1000}
159: * <config-var>
160: * <config-name>qed.blockSize</config-name>
161: * <config-dflt>8192</config-dflt>
162: * <config-desc>The database block size.</config-desc>
163: * </config-var>
164: */
165: static final int BLOCK_SIZE = ConfigNumber.find("qed.blockSize",
166: Integer.toString(8192)).intValue();
167: int blockSize = BLOCK_SIZE;
168:
169: /*{com.quadcap.util.Config-vars.xml-1001}
170: * <config-var>
171: * <config-name>qed.cacheSize</config-name>
172: * <config-dflt>256 blocks</config-dflt>
173: * <config-desc>The database cache size. A certain number of blocks
174: * are cached in an LRU cache; this parameter controls the size
175: * of this cache. 32 is probably a the minimum for a single-threaded
176: * application; more threads increase cache requirements.
177: * </config-desc>
178: * </config-var>
179: */
180: final ConfigNumber cacheSize = ConfigNumber.find("qed.cacheSize",
181: Integer.toString(GOOD_CACHE * BLOCK_SIZE));
182: int CACHE_SIZE = Math.max(MIN_CACHE, cacheSize.intValue()
183: / BLOCK_SIZE);
184:
185: /*{com.quadcap.util.Config-vars.xml-1002}
186: * <config-var>
187: * <config-name>qed.scratchCacheSize</config-name>
188: * <config-dflt>32 blocks</config-dflt>
189: * <config-desc>The "temporary" file cache size. A certain number of
190: * blocks in the scratch file (used for temporary tables and
191: * indexes) are cached in an LRU cache; this parameter controls
192: * the size of this cache.
193: * </config-desc>
194: * </config-var>
195: */
196: final ConfigNumber scratchCacheSize = ConfigNumber.find(
197: "qed.scratchCacheSize", Integer
198: .toString(SCRATCH_CACHE_DEFAULT * BLOCK_SIZE));
199: int SCRATCH_CACHE_SIZE = Math.max(MIN_CACHE, scratchCacheSize
200: .intValue()
201: / BLOCK_SIZE);
202:
203: /*{com.quadcap.util.Config-vars.xml-1003}
204: * <config-var>
205: * <config-name>qed.defaultMode</config-name>
206: * <config-dflt>rw</config-dflt>
207: * <config-desc>The default database 'mode'. Either 'r' or 'rw'
208: * </config-desc>
209: * </config-var>
210: */
211: final ConfigString defaultMode = ConfigString.find(
212: "qed.defaultMode", "rw");
213:
214: /*{com.quadcap.util.Config-vars.xml-1004}
215: * <config-var>
216: * <config-name>qed.isCaseSensitive</config-name>
217: * <config-dflt>true</config-dflt>
218: * <config-desc>A boolean flag specifying whether string (VARCHAR)
219: * items are case-sensitive.
220: * </config-desc>
221: * </config-var>
222: */
223: static final ConfigString caseSensitive = ConfigString.find(
224: "qed.isCaseSensitive", "true");
225: // XXX This should *NOT* be static, but expediency forces it
226: // XXX to be so for now.
227: public static boolean isCaseSensitive = caseSensitive.toString()
228: .equalsIgnoreCase("true");
229:
230: /**
231: * Construct a new Datafile object, from the specified filename and mode.
232: */
233:
234: /*{com.quadcap.sql.Datafile-conn.xml-0}
235: *
236: * <config>
237: */
238:
239: /*{com.quadcap.sql.Datafile-conn.xml-999999}
240: *
241: * </config>
242: */
243:
244: public Datafile() {
245: }
246:
247: public void init(String url, String fileName, Properties props)
248: throws IOException {
249: this .fileName = fileName;
250: this .inMemory = props.getProperty("memoryOnly", "")
251: .equalsIgnoreCase("true");
252: boolean nullStore = props.getProperty("cacheOnly", "")
253: .equalsIgnoreCase("true");
254: if (inMemory || nullStore) {
255: initMemoryDatabase(url, fileName, props);
256: } else {
257: initFileDatabase(url, fileName, props);
258: }
259: }
260:
261: public void initMemoryDatabase(String url, String fileName,
262: Properties props) throws IOException {
263: this .inMemory = true;
264: this .props = (Properties) props.clone();
265: this .url = url;
266: this .origFileName = fileName;
267: this .readOnly = false;
268:
269: String ics = props.getProperty("isCaseSensitive", caseSensitive
270: .toString());
271: isCaseSensitive = ics.equalsIgnoreCase("true");
272: Value.isCaseSensitive = isCaseSensitive;
273: String bs = props.getProperty("blockSize");
274: if (bs != null) {
275: blockSize = Integer.parseInt(bs);
276: }
277: String cs = props.getProperty("cacheSize");
278: if (cs != null) {
279: CACHE_SIZE = Integer.parseInt(cs) / blockSize;
280: }
281: this .locks = new LockManager();
282:
283: this .file = new BlockFile(fileName, "rw", props, blockSize,
284: CACHE_SIZE);
285: this .fileLock = this .file.getLock();
286: createRoot(fileName, props);
287: try {
288: this .log = new Log3();
289: } catch (Throwable t) {
290: throw new DatafileException("Error creating logger", t);
291: }
292: log.init(this , true, props);
293: file.setLog(log);
294:
295: //log.restoreBlocks();
296: bootFromRoot(false);
297: }
298:
299: public void initFileDatabase(String url, String fileName,
300: Properties props) throws IOException {
301: this .props = (Properties) props.clone();
302: this .url = url;
303: this .origFileName = fileName;
304: fileName = new File(fileName).getAbsolutePath();
305: this .dbRootDir = new File(fileName);
306: boolean newDb = false;
307: /*{com.quadcap.sql.Datafile-conn.xml-10}
308: * <config-var>
309: * <config-name>mode</config-name>
310: * <config-dflt>from Config: qed.defaultMode</config-dflt>
311: * <config-desc>Read-only or read-write access to the database:
312: * <ul>
313: * <li><code><b>rw</b></code>: Read-write (the default)</li>
314: * <li><code><b>r</b></code>:
315: * Read-only, intended for deployment on read-only media
316: * </li>
317: * </ul>
318: * </config-desc>
319: * </config-var>
320: */
321: String mode = props.getProperty("mode", defaultMode.toString());
322: this .readOnly = mode.equalsIgnoreCase("r");
323:
324: /*{com.quadcap.sql.Datafile-conn.xml-11}
325: * <config-var>
326: * <config-name>isCaseSensitive</config-name>
327: * <config-dflt>from Config: qed.isCaseSensitive</config-dflt>
328: * <config-desc>A boolean flag specifying whether string (VARCHAR)
329: * items are case-sensitive.
330: * </config-desc>
331: * </config-var>
332: */
333: String ics = props.getProperty("isCaseSensitive", caseSensitive
334: .toString());
335: isCaseSensitive = ics.equalsIgnoreCase("true");
336: Value.isCaseSensitive = isCaseSensitive;
337:
338: /*{com.quadcap.sql.Datafile-conn.xml-12}
339: * <config-var>
340: * <config-name>create</config-name>
341: * <config-dflt>false</config-dflt>
342: * <config-desc>If <code>"true"</code>, the database will be
343: * created if it doesn't already exist.
344: * </config-desc>
345: * </config-var>
346: */
347: String create = props.getProperty("create", "false");
348: if (!dbRootDir.isDirectory()) {
349: if (readOnly) {
350: throw new IOException("Can't open database: "
351: + fileName);
352: }
353: if (!create.equalsIgnoreCase("true")) {
354: throw new IOException("Can't open database: "
355: + fileName);
356: }
357: newDb = true;
358: if (!dbRootDir.mkdirs()) {
359: throw new IOException("Can't create directory: "
360: + fileName);
361: }
362: }
363:
364: /*{com.quadcap.sql.Datafile-conn.xml-13}
365: * <config-var>
366: * <config-name>blockSize</config-name>
367: * <config-dflt>from Config:
368: * qed.blockSize</config-dflt>
369: * <config-desc>The database block size, which must be a
370: * power of two.
371: * Maximum size: 32768, Minimum recommended size: 4096.
372: * </config-desc>
373: * </config-var>
374: */
375: String bs = props.getProperty("blockSize");
376: if (bs != null) {
377: blockSize = Integer.parseInt(bs);
378: }
379:
380: /*{com.quadcap.sql.Datafile-conn.xml-14}
381: * <config-var>
382: * <config-name>cacheSize</config-name>
383: * <config-dflt>from Config:
384: * qed.cacheSize</config-dflt>
385: * <config-desc>The database cache size. A certain number of blocks
386: * are cached in an LRU cache; this parameter controls the size
387: * of this cache.</config-desc>
388: * </config-var>
389: */
390: String cs = props.getProperty("cacheSize");
391: if (cs != null) {
392: CACHE_SIZE = Integer.parseInt(cs) / blockSize;
393: }
394:
395: /*{com.quadcap.sql.Datafile-conn.xml-15}
396: * <config-var>
397: * <config-name>scratchCacheSize</config-name>
398: * <config-dflt>from Config:
399: * qed.scratchCacheSize</config-dflt>
400: * <config-desc>The database scratch cache size, used for caching access
401: * to the database temporary (or "scratch") file.
402: * A certain number of blocks are cached in an LRU cache;
403: * this parameter controls the size
404: * of this cache.</config-desc>
405: * </config-var>
406: */
407: String scs = props.getProperty("scratchCacheSize");
408: if (scs != null) {
409: SCRATCH_CACHE_SIZE = Integer.parseInt(scs) / blockSize;
410: }
411:
412: /*{com.quadcap.sql.Datafile-conn.xml-16}
413: * <config-var>
414: * <config-name>useLockFile</config-name>
415: * <config-dflt>true</config-dflt>
416: * <config-desc>A boolean. If <b>true</b>, a lock file will
417: * be used to protect from database corruption which might
418: * result from multiple processes accessing the same database
419: * simultaneously.</config-desc>
420: * </config-var>
421: */
422: String lf = props.getProperty("useLockFile", "true");
423: boolean useLockFile = lf.equalsIgnoreCase("true");
424:
425: try {
426: File g = new File(dbRootDir, "datafile");
427: if (!g.exists()) {
428: if (readOnly) {
429: throw new IOException("Can't open database: "
430: + fileName);
431: }
432: newDb = true;
433: }
434:
435: if (!readOnly && useLockFile) {
436: File k = new File(dbRootDir, "lockfile");
437: //#ifdef JDK14
438: lockChannel = new RandomAccessFile(k, "rw")
439: .getChannel();
440: lockLock = lockChannel.tryLock();
441: if (lockLock == null) {
442: /*{com.quadcap.sql.Datafile-conn.xml-18}
443: * <config-var>
444: * <config-name>force</config-name>
445: * <config-dflt>false</config-dflt>
446: * <config-desc>QED uses a lockfile to prevent multiple
447: * JVMs from accessing the database files
448: * simultaneously, which could lead to database
449: * corruption. When the JVM exits normally, this
450: * lockfile is deleted. If the JVM exits abnormally
451: * the lockfile may be left behind, and subsequent
452: * attempts to access the database will fail because
453: * of the old lockfile. If <b>force</b> is
454: * <code>true</code>, the database connection will
455: * succeed even if the lockfile is present.
456: * This option should be used with extreme care,
457: * since if there really is another process accessing
458: * the database, corruption may result.</config-desc>
459: * </config-var>
460: */
461: String force = props.getProperty("force", "false");
462: if (!force.equalsIgnoreCase("true")) {
463: throw new IOException(
464: "lockfile already exists. Maybe another "
465: + "process is accessing the database?");
466: }
467: }
468: //#endif
469: }
470: makeTempDirectory(props);
471: this .locks = new LockManager();
472:
473: String gFileName = g.getAbsolutePath();
474: this .file = new BlockFile(gFileName, mode, props,
475: blockSize, CACHE_SIZE);
476: this .fileLock = this .file.getLock();
477: if (!newDb) {
478: try {
479: readRoot();
480: } catch (IOException ex) {
481: if (ex.toString().indexOf("magic") > 0) {
482: throw new IOException(
483: "Database Connection Failed: Possible bad authorization");
484: } else {
485: throw ex;
486: }
487: }
488: } else {
489: createRoot(fileName, props);
490: }
491: if (!readOnly) {
492: /*{com.quadcap.sql.Datafile-conn.xml-20}
493: * <config-var>
494: * <config-name>logger</config-name>
495: * <config-dflt>2</config-dflt>
496: * <config-desc>If no transaction logging is required
497: * (i.e., no rollback or recovery), then use '0'.
498: * Otherwise, use '2', (the default)</config-desc>
499: * </config-var>
500: */
501: String ls = props.getProperty("logger");
502: if (ls == null)
503: ls = "2";
504: this .log = (Log) (Class
505: .forName("com.quadcap.sql.file.Log" + ls)
506: .newInstance());
507: log.init(this , newDb, props);
508: file.setLog(log);
509: if (!newDb) {
510: log.restoreBlocks();
511: file.revert();
512: }
513: }
514: bootFromRoot(!newDb);
515: if (!newDb) {
516: log.restart();
517: }
518: if (!readOnly) {
519: log.start();
520: }
521: log.checkpoint();
522: } catch (IOException e) {
523: if (newDb && !readOnly)
524: deleteHalfBakedDir();
525: throw e;
526: } catch (RuntimeException e) {
527: if (newDb && !readOnly)
528: deleteHalfBakedDir();
529: throw new DatafileException(e);
530: } catch (Exception e) {
531: if (newDb && !readOnly)
532: deleteHalfBakedDir();
533: throw new DatafileException(e);
534: }
535: }
536:
537: // i am a factor. but at least I have a name!
538: final private void deleteHalfBakedDir() {
539: new File(dbRootDir, "datafile").delete();
540: new File(dbRootDir, "lockfile").delete();
541: }
542:
543: /**
544: * Accessor for my underlying file
545: */
546: public BlockFile getFile() {
547: return file;
548: }
549:
550: /**
551: * Return the JDBC URL used to connect to this database.
552: */
553: public String getURL() {
554: return url;
555: }
556:
557: /**
558: * Return a File representing the database root directory
559: */
560: File getDbRootDir() {
561: return dbRootDir;
562: }
563:
564: /**
565: * Return a File representing the database scratch/temp root directory
566: */
567: File getScratchDir() {
568: return tempDir;
569: }
570:
571: /**
572: * Accessor for the datbase's lock manager
573: */
574: public LockManager getLockManager() {
575: return locks;
576: }
577:
578: /**
579: * Accessor for file lock
580: */
581: public Object getFileLock() {
582: return fileLock;
583: }
584:
585: /**
586: * Is db read only?
587: */
588: public boolean isReadOnly() {
589: return readOnly;
590: }
591:
592: /**
593: * Return the specified persistent object from the store
594: * @param ref the block number of the object's root
595: * @return the object
596: * @exception IOException may be thrown
597: */
598: public Object getObject(long ref) throws IOException {
599: return file.getObject(ref);
600: }
601:
602: /**
603: * Write a new object to the store and return its reference
604: * @param obj the object
605: * @return the block number of the object's root
606: * @exception IOException may be thrown
607: */
608: public long putObject(Object obj) throws IOException {
609: return file.putObject(obj);
610: }
611:
612: /**
613: * Write a new version of a persistent object to the store.
614: * @param blockNum the address of the object's root page in the store
615: * @param obj the new object value
616: *
617: * @exception IOException may be thrown
618: */
619: public void updateObject(long seg, Object obj) throws IOException {
620: file.updateObject(seg, obj);
621: }
622:
623: /**
624: * Remove an object from the store
625: * @param ref the block number of the object's root
626: *
627: * @exception IOException may be thrown
628: */
629: public void removeObject(long ref) throws IOException {
630: file.removeObject(ref);
631: }
632:
633: /**
634: * Hook which allows the scratch files to be written to a separate
635: * directory. Useful when you want to mount the data files read only
636: */
637: final void makeTempDirectory(Properties props) throws IOException {
638: /*{com.quadcap.sql.Datafile-conn.xml-22}
639: * <config-var>
640: * <config-name>scratchDir</config-name>
641: * <config-dflt><i>database directory</i></config-dflt>
642: * <config-desc>
643: * Specify the directory where scratch files are written. This
644: * option is most useful in conjunction with <code>mode=r</code>,
645: * where the database files can be located on read-only media,
646: * and the scratch directory can be located separately.
647: * </config-desc>
648: * </config-var>
649: */
650: if (!inMemory) {
651: String tmpDirName = props.getProperty("scratchDir",
652: dbRootDir.getAbsolutePath());
653: this .tempDir = new File(tmpDirName);
654: if (!tempDir.isDirectory()) {
655: if (!tempDir.mkdirs()) {
656: throw new IOException(
657: "Can't create temp directory: "
658: + tmpDirName);
659: }
660: }
661: }
662: }
663:
664: /**
665: * The temporary file can be used for storage of non-transactionally-secure
666: * data
667: */
668: public BlockFile getTempFile() throws IOException {
669: return getTempFile(true);
670: }
671:
672: public BlockFile getTempFile(boolean incr) throws IOException {
673: synchronized (fileLock) {
674: if (tempFile == null) {
675: if (inMemory) {
676: this .tempFile = new BlockFile(
677: fileName + ".scratch", "rw", props,
678: blockSize, SCRATCH_CACHE_SIZE);
679: } else {
680: File f = new File(tempDir, "scratch");
681: this .tempFile = new BlockFile(f.getAbsolutePath(),
682: "rw", props, blockSize, SCRATCH_CACHE_SIZE);
683: }
684: this .tempFileRefCount = 0;
685: }
686: //Debug.println("Datafile.getTempFile(" + tempFileRefCount +
687: // (incr ? (" -> " + (tempFileRefCount+1)) : "") +
688: // ") @\n" + Util.stackTrace());
689: if (incr)
690: tempFileRefCount++;
691: }
692: return tempFile;
693: }
694:
695: public void releaseTempFile() {
696: synchronized (fileLock) {
697: //Debug.println("Datafile.releaseTempFile(" + tempFileRefCount +
698: // " -> " + (tempFileRefCount-1) + ") @\n" +
699: // Util.stackTrace());
700: if (--tempFileRefCount == 0) {
701: clearTempFile();
702: }
703: }
704: }
705:
706: /**
707: * When nothing is happening, get rid of this guy. Who needs him, eh?
708: */
709: void clearTempFile() {
710: if (tempFile != null) {
711: //Debug.println("clearTempFile"); // XXX
712: if (inMemory) {
713: tempFile = null;
714: } else if (true) {
715: try {
716: tempFile.close();
717: } catch (Throwable e8) {
718: } finally {
719: tempFile = null;
720: }
721: try {
722: new File(dbRootDir, "scratch").delete();
723: } catch (Throwable e9) {
724: }
725: }
726: }
727: }
728:
729: /**
730: * Close the database. Sync and close the physical file(s),
731: * delete the tempfile(s) and lockfile. Kill the database
732: * reference monitor thread.
733: */
734: public void close() {
735: if (file != null) {
736: //#ifdef TRACE
737: if (Trace.bit(12)) {
738: Debug.println("Datafile.close(" + dbRootDir.getPath()
739: + ")");
740: }
741: //#endif
742: try {
743: if (log != null) {
744: log.checkpoint();
745: log.sync();
746: log.close();
747: log.remove();
748: }
749: } catch (Throwable e8) {
750: Debug.print(e8);
751: } finally {
752: log = null;
753: }
754:
755: clearTempFile();
756:
757: try {
758: if (file != null)
759: file.close();
760: } catch (Throwable e2) {
761: }
762:
763: file = null;
764: try {
765: //#ifdef JDK14
766: if (lockLock != null) {
767: lockLock.release();
768: if (lockChannel != null)
769: lockChannel.close();
770: //new File(dbRootDir, "lockfile").delete();
771: }
772: //#endif
773: } catch (Throwable t) {
774: } finally {
775: //#ifdef JDK14
776: lockLock = null;
777: lockChannel = null;
778: //#endif
779: }
780: }
781: }
782:
783: /**
784: * Helper to create a new transaction, and optionally to write the
785: * BEGIN_TRANSACTION log entry.
786: */
787: public final Transaction makeTransaction(boolean writeLog)
788: throws IOException {
789: Transaction trans = null;
790: long transId = -1;
791: synchronized (fileLock) {
792: transId = getNextTransId();
793: trans = locks.getTransaction(transId);
794: }
795: if (writeLog) {
796: log.addEntry(new LogEntry(transId,
797: LogEntry.BEGIN_TRANSACTION));
798: }
799: return trans;
800: }
801:
802: /**
803: * Commit the transaction
804: */
805: public void commitTransaction(Transaction trans) throws IOException {
806: long transId = trans.getTransactionId();
807: log.addEntry(new LogEntry(transId, LogEntry.COMMIT));
808: log.flushLog();
809: releaseLocks(trans);
810: }
811:
812: /**
813: * Rollback the transaction
814: */
815: public void rollbackTransaction(Transaction trans)
816: throws IOException {
817: log.rollbackTransaction(trans);
818: commitTransaction(trans);
819: }
820:
821: /**
822: * Rollback the specified statement.
823: */
824: public void rollbackStatement(Transaction trans, int stmtId)
825: throws IOException {
826: log.rollbackStatement(trans, stmtId);
827: }
828:
829: /**
830: * Remove the transaction for the given connection from the
831: * 'active transactions' table. Flush the log?
832: */
833: public void releaseLocks(Transaction trans) {
834: locks.releaseTransaction(trans);
835: }
836:
837: /**
838: * Find the specified transaction
839: */
840: public Transaction findTransaction(long transId) throws IOException {
841: Transaction t = locks.findTransaction(transId);
842: return t;
843: }
844:
845: /**
846: * Return the log for this database
847: */
848: public final Log getLog() {
849: return log;
850: }
851:
852: /**
853: * If we get finalized, and haven't closed yet, then we should try.
854: */
855: public void finalize() throws Throwable {
856: close();
857: super .finalize();
858: }
859:
860: /**
861: * Return the total size of the datafile, in bytes
862: */
863: public long getSize() {
864: return file.getSize();
865: }
866:
867: /**
868: * Reset the temp file (on checkpoint)
869: */
870: public void checkpoint(boolean truncate, boolean fastSync)
871: throws IOException {
872: synchronized (fileLock) {
873: if (tempFile != null) {
874: tempFile.flush(fastSync);
875: }
876: }
877: }
878:
879: public void checkpointHandler(LongMap activeTransactions)
880: throws IOException {
881: }
882:
883: //#ifdef DEBUG
884: /**
885: * Return a displayable representation for debugging purposes
886: */
887: public String toString() {
888: String u = url;
889: if (u.startsWith("jdbc:qed:"))
890: u = u.substring(9);
891: int idx = u.indexOf(';');
892: if (idx > 0)
893: u = u.substring(0, idx);
894: return u;
895: }
896:
897: //#endif
898:
899: public void doStep(Transaction t, LogEntry e) throws IOException,
900: DatafileException {
901: log.addEntry(e);
902: e.redo(t, this );
903: }
904:
905: public boolean inRecovery() throws IOException {
906: return log != null && log.inRecovery();
907: }
908:
909: }
|