Quadcap Embeddable Database

com/quadcap/sql/file/Datafile.java

Go to the documentation of this file.
00001 package com.quadcap.sql.file; 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.ByteArrayOutputStream; 00042 import java.io.BufferedInputStream; 00043 import java.io.File; 00044 import java.io.FileOutputStream; 00045 import java.io.IOException; 00046 import java.io.InputStream; 00047 import java.io.OutputStream; 00048 import java.io.RandomAccessFile; 00049 00050 //#ifdef JDK14 00051 import java.nio.channels.FileChannel; 00052 import java.nio.channels.FileLock; 00053 //#endif 00054 00055 import java.util.HashMap; 00056 import java.util.Iterator; 00057 import java.util.Properties; 00058 00059 import com.quadcap.sql.lock.Lock; 00060 import com.quadcap.sql.lock.LockManager; 00061 import com.quadcap.sql.lock.LockMode; 00062 import com.quadcap.sql.lock.Transaction; 00063 import com.quadcap.sql.lock.TransactionObserver; 00064 00065 import com.quadcap.sql.types.Value; 00066 00067 import com.quadcap.util.collections.LongIterator; 00068 import com.quadcap.util.collections.LongMap; 00069 00070 import com.quadcap.util.ConfigNumber; 00071 import com.quadcap.util.ConfigString; 00072 import com.quadcap.util.Debug; 00073 import com.quadcap.util.Util; 00074 00075 /** 00076 * This class brings together the various file-level components of the 00077 * structured (SQL) database. 00078 * 00079 * @author Stan Bailes 00080 */ 00081 abstract public class Datafile { 00082 protected boolean inMemory = false; 00083 String fileName; 00084 00085 protected DatafileRoot root; 00086 File dbRootDir; 00087 File tempDir; 00088 String url; 00089 protected String origFileName; 00090 00091 // "datafile" 00092 protected BlockFile file; 00093 00094 // "scratch" 00095 protected BlockFile tempFile; 00096 00097 00098 // "log" file 00099 Log log; 00100 00101 // read/write table locks 00102 LockManager locks = null; 00103 00104 // Original connection properties 00105 Properties props; 00106 00107 static { 00108 try { 00109 Class.forName("com.quadcap.sql.io.Extern"); 00110 } catch (Throwable t) {} 00111 } 00112 00113 /** True if we've locked the lockfile */ 00114 private FileChannel lockChannel = null; 00115 private FileLock lockLock = null; 00116 00117 protected boolean readOnly = false; 00118 protected byte[] temp = new byte[256]; 00119 00120 protected Object fileLock = new Object(); 00121 00122 private int tempFileRefCount = 0; 00123 00124 abstract public void maybeBackup() throws IOException; 00125 abstract public void readRoot() throws IOException; 00126 abstract public void createRoot(String fileName, 00127 Properties props) throws IOException; 00128 abstract public void flushRoot() throws IOException; 00129 abstract public long getNextTransId() throws IOException; 00130 abstract public void bootFromRoot(boolean restart) throws IOException; 00131 00132 /** 00133 * Minimum cache size: 256K, with 8K blocks. The minimum is 00134 * dependent on various factors, including query type (joins 00135 * need more cache, for example) and concurrency level. 00136 * 00137 * Simple applications which are primarily single threaded will 00138 * be able to get by with 32 blocks or so. 00139 */ 00140 static final int MIN_CACHE = 32; 00141 00142 /** 00143 * Reasonable cache size: 4MB (8K blocks). This cache size should 00144 * support a moderate load; 8-10 busy threads... 00145 */ 00146 static final int GOOD_CACHE = 256; 00147 00148 /** 00149 * Reasonable scratch cache default 00150 */ 00151 static final int SCRATCH_CACHE_DEFAULT = 64; 00152 00153 /*{com.quadcap.util.Config-vars.xml-1000} 00154 * <config-var> 00155 * <config-name>qed.blockSize</config-name> 00156 * <config-dflt>8192</config-dflt> 00157 * <config-desc>The database block size.</config-desc> 00158 * </config-var> 00159 */ 00160 static final int BLOCK_SIZE 00161 = ConfigNumber.find("qed.blockSize", Integer.toString(8192)).intValue(); 00162 int blockSize = BLOCK_SIZE; 00163 00164 /*{com.quadcap.util.Config-vars.xml-1001} 00165 * <config-var> 00166 * <config-name>qed.cacheSize</config-name> 00167 * <config-dflt>256 blocks</config-dflt> 00168 * <config-desc>The database cache size. A certain number of blocks 00169 * are cached in an LRU cache; this parameter controls the size 00170 * of this cache. 32 is probably a the minimum for a single-threaded 00171 * application; more threads increase cache requirements. 00172 * </config-desc> 00173 * </config-var> 00174 */ 00175 final ConfigNumber cacheSize 00176 = ConfigNumber.find("qed.cacheSize", 00177 Integer.toString(GOOD_CACHE * BLOCK_SIZE)); 00178 int CACHE_SIZE = 00179 Math.max(MIN_CACHE, cacheSize.intValue() / BLOCK_SIZE); 00180 00181 /*{com.quadcap.util.Config-vars.xml-1002} 00182 * <config-var> 00183 * <config-name>qed.scratchCacheSize</config-name> 00184 * <config-dflt>32 blocks</config-dflt> 00185 * <config-desc>The "temporary" file cache size. A certain number of 00186 * blocks in the scratch file (used for temporary tables and 00187 * indexes) are cached in an LRU cache; this parameter controls 00188 * the size of this cache. 00189 * </config-desc> 00190 * </config-var> 00191 */ 00192 final ConfigNumber scratchCacheSize 00193 = ConfigNumber.find("qed.scratchCacheSize", 00194 Integer.toString(SCRATCH_CACHE_DEFAULT * BLOCK_SIZE)); 00195 int SCRATCH_CACHE_SIZE = 00196 Math.max(MIN_CACHE, scratchCacheSize.intValue() / BLOCK_SIZE); 00197 00198 /*{com.quadcap.util.Config-vars.xml-1003} 00199 * <config-var> 00200 * <config-name>qed.defaultMode</config-name> 00201 * <config-dflt>rw</config-dflt> 00202 * <config-desc>The default database 'mode'. Either 'r' or 'rw' 00203 * </config-desc> 00204 * </config-var> 00205 */ 00206 final ConfigString defaultMode 00207 = ConfigString.find("qed.defaultMode", "rw"); 00208 00209 /*{com.quadcap.util.Config-vars.xml-1004} 00210 * <config-var> 00211 * <config-name>qed.isCaseSensitive</config-name> 00212 * <config-dflt>true</config-dflt> 00213 * <config-desc>A boolean flag specifying whether string (VARCHAR) 00214 * items are case-sensitive. 00215 * </config-desc> 00216 * </config-var> 00217 */ 00218 static final ConfigString caseSensitive 00219 = ConfigString.find("qed.isCaseSensitive", "true"); 00220 // XXX This should *NOT* be static, but expediency forces it 00221 // XXX to be so for now. 00222 public static boolean isCaseSensitive = 00223 caseSensitive.toString().equalsIgnoreCase("true"); 00224 00225 /** 00226 * Construct a new Datafile object, from the specified filename and mode. 00227 */ 00228 00229 /*{com.quadcap.sql.Datafile-conn.xml-0} 00230 * 00231 * <config> 00232 */ 00233 00234 /*{com.quadcap.sql.Datafile-conn.xml-999999} 00235 * 00236 * </config> 00237 */ 00238 00239 public Datafile() { 00240 } 00241 00242 public void init(String url, String fileName, Properties props) 00243 throws IOException 00244 { 00245 this.fileName = fileName; 00246 this.inMemory = 00247 props.getProperty("memoryOnly", "").equalsIgnoreCase("true"); 00248 boolean nullStore = 00249 props.getProperty("cacheOnly", "").equalsIgnoreCase("true"); 00250 if (inMemory || nullStore) { 00251 initMemoryDatabase(url, fileName, props); 00252 } else { 00253 initFileDatabase(url, fileName, props); 00254 } 00255 } 00256 00257 public void initMemoryDatabase(String url, String fileName, Properties props) 00258 throws IOException 00259 { 00260 this.inMemory = true; 00261 this.props = (Properties)props.clone(); 00262 this.url = url; 00263 this.origFileName = fileName; 00264 this.readOnly = false; 00265 00266 String ics = props.getProperty("isCaseSensitive", 00267 caseSensitive.toString()); 00268 isCaseSensitive = ics.equalsIgnoreCase("true"); 00269 Value.isCaseSensitive = isCaseSensitive; 00270 String bs = props.getProperty("blockSize"); 00271 if (bs != null) { 00272 blockSize = Integer.parseInt(bs); 00273 } 00274 String cs = props.getProperty("cacheSize"); 00275 if (cs != null) { 00276 CACHE_SIZE = Integer.parseInt(cs) / blockSize; 00277 } 00278 this.locks = new LockManager(); 00279 00280 this.file = new BlockFile(fileName, "rw", props, 00281 blockSize, CACHE_SIZE); 00282 this.fileLock = this.file.getLock(); 00283 createRoot(fileName, props); 00284 try { 00285 this.log = new Log3(); 00286 } catch (Throwable t) { 00287 throw new DatafileException("Error creating logger", t); 00288 } 00289 log.init(this, true, props); 00290 file.setLog(log); 00291 00292 //log.restoreBlocks(); 00293 bootFromRoot(false); 00294 } 00295 00296 public void initFileDatabase(String url, String fileName, Properties props) 00297 throws IOException 00298 { 00299 this.props = (Properties)props.clone(); 00300 this.url = url; 00301 this.origFileName = fileName; 00302 fileName = new File(fileName).getAbsolutePath(); 00303 this.dbRootDir = new File(fileName); 00304 boolean newDb = false; 00305 /*{com.quadcap.sql.Datafile-conn.xml-10} 00306 * <config-var> 00307 * <config-name>mode</config-name> 00308 * <config-dflt>from Config: qed.defaultMode</config-dflt> 00309 * <config-desc>Read-only or read-write access to the database: 00310 * <ul> 00311 * <li><code><b>rw</b></code>: Read-write (the default)</li> 00312 * <li><code><b>r</b></code>: 00313 * Read-only, intended for deployment on read-only media 00314 * </li> 00315 * </ul> 00316 * </config-desc> 00317 * </config-var> 00318 */ 00319 String mode = props.getProperty("mode", defaultMode.toString()); 00320 this.readOnly = mode.equalsIgnoreCase("r"); 00321 00322 /*{com.quadcap.sql.Datafile-conn.xml-11} 00323 * <config-var> 00324 * <config-name>isCaseSensitive</config-name> 00325 * <config-dflt>from Config: qed.isCaseSensitive</config-dflt> 00326 * <config-desc>A boolean flag specifying whether string (VARCHAR) 00327 * items are case-sensitive. 00328 * </config-desc> 00329 * </config-var> 00330 */ 00331 String ics = props.getProperty("isCaseSensitive", 00332 caseSensitive.toString()); 00333 isCaseSensitive = ics.equalsIgnoreCase("true"); 00334 Value.isCaseSensitive = isCaseSensitive; 00335 00336 00337 /*{com.quadcap.sql.Datafile-conn.xml-12} 00338 * <config-var> 00339 * <config-name>create</config-name> 00340 * <config-dflt>false</config-dflt> 00341 * <config-desc>If <code>"true"</code>, the database will be 00342 * created if it doesn't already exist. 00343 * </config-desc> 00344 * </config-var> 00345 */ 00346 String create = props.getProperty("create", "false"); 00347 if (!dbRootDir.isDirectory()) { 00348 if (readOnly) { 00349 throw new IOException("Can't open database: " + fileName); 00350 } 00351 if (!create.equalsIgnoreCase("true")) { 00352 throw new IOException("Can't open database: " + fileName); 00353 } 00354 newDb = true; 00355 if (!dbRootDir.mkdirs()) { 00356 throw new IOException("Can't create directory: " + fileName); 00357 } 00358 } 00359 00360 /*{com.quadcap.sql.Datafile-conn.xml-13} 00361 * <config-var> 00362 * <config-name>blockSize</config-name> 00363 * <config-dflt>from Config: 00364 * qed.blockSize</config-dflt> 00365 * <config-desc>The database block size, which must be a 00366 * power of two. 00367 * Maximum size: 32768, Minimum recommended size: 4096. 00368 * </config-desc> 00369 * </config-var> 00370 */ 00371 String bs = props.getProperty("blockSize"); 00372 if (bs != null) { 00373 blockSize = Integer.parseInt(bs); 00374 } 00375 00376 00377 /*{com.quadcap.sql.Datafile-conn.xml-14} 00378 * <config-var> 00379 * <config-name>cacheSize</config-name> 00380 * <config-dflt>from Config: 00381 * qed.cacheSize</config-dflt> 00382 * <config-desc>The database cache size. A certain number of blocks 00383 * are cached in an LRU cache; this parameter controls the size 00384 * of this cache.</config-desc> 00385 * </config-var> 00386 */ 00387 String cs = props.getProperty("cacheSize"); 00388 if (cs != null) { 00389 CACHE_SIZE = Integer.parseInt(cs) / blockSize; 00390 } 00391 00392 /*{com.quadcap.sql.Datafile-conn.xml-15} 00393 * <config-var> 00394 * <config-name>scratchCacheSize</config-name> 00395 * <config-dflt>from Config: 00396 * qed.scratchCacheSize</config-dflt> 00397 * <config-desc>The database scratch cache size, used for caching access 00398 * to the database temporary (or "scratch") file. 00399 * A certain number of blocks are cached in an LRU cache; 00400 * this parameter controls the size 00401 * of this cache.</config-desc> 00402 * </config-var> 00403 */ 00404 String scs = props.getProperty("scratchCacheSize"); 00405 if (scs != null) { 00406 SCRATCH_CACHE_SIZE = Integer.parseInt(scs) / blockSize; 00407 } 00408 00409 /*{com.quadcap.sql.Datafile-conn.xml-16} 00410 * <config-var> 00411 * <config-name>useLockFile</config-name> 00412 * <config-dflt>true</config-dflt> 00413 * <config-desc>A boolean. If <b>true</b>, a lock file will 00414 * be used to protect from database corruption which might 00415 * result from multiple processes accessing the same database 00416 * simultaneously.</config-desc> 00417 * </config-var> 00418 */ 00419 String lf = props.getProperty("useLockFile", "true"); 00420 boolean useLockFile = lf.equalsIgnoreCase("true"); 00421 00422 try { 00423 File g = new File(dbRootDir, "datafile"); 00424 if (!g.exists()) { 00425 if (readOnly) { 00426 throw new IOException("Can't open database: " + fileName); 00427 } 00428 newDb = true; 00429 } 00430 00431 if (!readOnly && useLockFile) { 00432 File k = new File(dbRootDir, "lockfile"); 00433 //#ifdef JDK14 00434 lockChannel = new RandomAccessFile(k, "rw").getChannel(); 00435 lockLock = lockChannel.tryLock(); 00436 if (lockLock == null) { 00437 /*{com.quadcap.sql.Datafile-conn.xml-18} 00438 * <config-var> 00439 * <config-name>force</config-name> 00440 * <config-dflt>false</config-dflt> 00441 * <config-desc>QED uses a lockfile to prevent multiple 00442 * JVMs from accessing the database files 00443 * simultaneously, which could lead to database 00444 * corruption. When the JVM exits normally, this 00445 * lockfile is deleted. If the JVM exits abnormally 00446 * the lockfile may be left behind, and subsequent 00447 * attempts to access the database will fail because 00448 * of the old lockfile. If <b>force</b> is 00449 * <code>true</code>, the database connection will 00450 * succeed even if the lockfile is present. 00451 * This option should be used with extreme care, 00452 * since if there really is another process accessing 00453 * the database, corruption may result.</config-desc> 00454 * </config-var> 00455 */ 00456 String force = props.getProperty("force", "false"); 00457 if (!force.equalsIgnoreCase("true")) { 00458 throw new IOException( 00459 "lockfile already exists. Maybe another " + 00460 "process is accessing the database?"); 00461 } 00462 } 00463 //#endif 00464 } 00465 makeTempDirectory(props); 00466 this.locks = new LockManager(); 00467 00468 String gFileName = g.getAbsolutePath(); 00469 this.file = new BlockFile(gFileName, mode, props, 00470 blockSize, CACHE_SIZE); 00471 this.fileLock = this.file.getLock(); 00472 if (!newDb) { 00473 try { 00474 readRoot(); 00475 } catch (IOException ex) { 00476 if (ex.toString().indexOf("magic") > 0) { 00477 throw new IOException("Database Connection Failed: Possible bad authorization"); 00478 } else { 00479 throw ex; 00480 } 00481 } 00482 } else { 00483 createRoot(fileName, props); 00484 } 00485 if (!readOnly) { 00486 /*{com.quadcap.sql.Datafile-conn.xml-20} 00487 * <config-var> 00488 * <config-name>logger</config-name> 00489 * <config-dflt>2</config-dflt> 00490 * <config-desc>If no transaction logging is required 00491 * (i.e., no rollback or recovery), then use '0'. 00492 * Otherwise, use '2', (the default)</config-desc> 00493 * </config-var> 00494 */ 00495 String ls = props.getProperty("logger"); 00496 if (ls == null) ls = "2"; 00497 this.log = (Log)( 00498 Class.forName("com.quadcap.sql.file.Log" + ls). 00499 newInstance()); 00500 log.init(this, newDb, props); 00501 file.setLog(log); 00502 if (!newDb) { 00503 log.restoreBlocks(); 00504 file.revert(); 00505 } 00506 } 00507 bootFromRoot(!newDb); 00508 if (!newDb) { 00509 log.restart(); 00510 } 00511 if (!readOnly) { 00512 log.start(); 00513 } 00514 log.checkpoint(); 00515 } catch (IOException e) { 00516 if (newDb && !readOnly) deleteHalfBakedDir(); 00517 throw e; 00518 } catch (RuntimeException e) { 00519 if (newDb && !readOnly) deleteHalfBakedDir(); 00520 throw new DatafileException(e); 00521 } catch (Exception e) { 00522 if (newDb && !readOnly) deleteHalfBakedDir(); 00523 throw new DatafileException(e); 00524 } 00525 } 00526 00527 // i am a factor. but at least I have a name! 00528 final private void deleteHalfBakedDir() { 00529 new File(dbRootDir, "datafile").delete(); 00530 new File(dbRootDir, "lockfile").delete(); 00531 } 00532 00533 /** 00534 * Accessor for my underlying file 00535 */ 00536 public BlockFile getFile() { return file; } 00537 00538 /** 00539 * Return the JDBC URL used to connect to this database. 00540 */ 00541 public String getURL() { return url; } 00542 00543 /** 00544 * Return a File representing the database root directory 00545 */ 00546 File getDbRootDir() { return dbRootDir; } 00547 00548 /** 00549 * Return a File representing the database scratch/temp root directory 00550 */ 00551 File getScratchDir() { return tempDir; } 00552 00553 /** 00554 * Accessor for the datbase's lock manager 00555 */ 00556 public LockManager getLockManager() { return locks; } 00557 00558 /** 00559 * Accessor for file lock 00560 */ 00561 public Object getFileLock() { return fileLock; } 00562 00563 /** 00564 * Is db read only? 00565 */ 00566 public boolean isReadOnly() { return readOnly; } 00567 00568 /** 00569 * Return the specified persistent object from the store 00570 * @param ref the block number of the object's root 00571 * @return the object 00572 * @exception IOException may be thrown 00573 */ 00574 public Object getObject(long ref) throws IOException { 00575 return file.getObject(ref); 00576 } 00577 00578 00579 /** 00580 * Write a new object to the store and return its reference 00581 * @param obj the object 00582 * @return the block number of the object's root 00583 * @exception IOException may be thrown 00584 */ 00585 public long putObject(Object obj) throws IOException { 00586 return file.putObject(obj); 00587 } 00588 00589 /** 00590 * Write a new version of a persistent object to the store. 00591 * @param blockNum the address of the object's root page in the store 00592 * @param obj the new object value 00593 * 00594 * @exception IOException may be thrown 00595 */ 00596 public void updateObject(long seg, Object obj) throws IOException { 00597 file.updateObject(seg, obj); 00598 } 00599 00600 /** 00601 * Remove an object from the store 00602 * @param ref the block number of the object's root 00603 * 00604 * @exception IOException may be thrown 00605 */ 00606 public void removeObject(long ref) throws IOException { 00607 file.removeObject(ref); 00608 } 00609 00610 00611 /** 00612 * Hook which allows the scratch files to be written to a separate 00613 * directory. Useful when you want to mount the data files read only 00614 */ 00615 final void makeTempDirectory(Properties props) throws IOException { 00616 /*{com.quadcap.sql.Datafile-conn.xml-22} 00617 * <config-var> 00618 * <config-name>scratchDir</config-name> 00619 * <config-dflt><i>database directory</i></config-dflt> 00620 * <config-desc> 00621 * Specify the directory where scratch files are written. This 00622 * option is most useful in conjunction with <code>mode=r</code>, 00623 * where the database files can be located on read-only media, 00624 * and the scratch directory can be located separately. 00625 * </config-desc> 00626 * </config-var> 00627 */ 00628 if (!inMemory) { 00629 String tmpDirName = props.getProperty("scratchDir", 00630 dbRootDir.getAbsolutePath()); 00631 this.tempDir = new File(tmpDirName); 00632 if (!tempDir.isDirectory()) { 00633 if (!tempDir.mkdirs()) { 00634 throw new IOException("Can't create temp directory: " + 00635 tmpDirName); 00636 } 00637 } 00638 } 00639 } 00640 00641 /** 00642 * The temporary file can be used for storage of non-transactionally-secure 00643 * data 00644 */ 00645 public BlockFile getTempFile() throws IOException { 00646 return getTempFile(true); 00647 } 00648 00649 public BlockFile getTempFile(boolean incr) throws IOException { 00650 synchronized (fileLock) { 00651 if (tempFile == null) { 00652 if (inMemory) { 00653 this.tempFile = new BlockFile(fileName + ".scratch", "rw", 00654 props, blockSize, SCRATCH_CACHE_SIZE); 00655 } else { 00656 File f = new File(tempDir, "scratch"); 00657 this.tempFile = new BlockFile(f.getAbsolutePath(), "rw", 00658 props, 00659 blockSize, 00660 SCRATCH_CACHE_SIZE); 00661 } 00662 this.tempFileRefCount = 0; 00663 } 00664 //Debug.println("Datafile.getTempFile(" + tempFileRefCount + 00665 // (incr ? (" -> " + (tempFileRefCount+1)) : "") + 00666 // ") @\n" + Util.stackTrace()); 00667 if (incr) tempFileRefCount++; 00668 } 00669 return tempFile; 00670 } 00671 00672 public void releaseTempFile() { 00673 synchronized (fileLock) { 00674 //Debug.println("Datafile.releaseTempFile(" + tempFileRefCount + 00675 // " -> " + (tempFileRefCount-1) + ") @\n" + 00676 // Util.stackTrace()); 00677 if (--tempFileRefCount == 0) { 00678 clearTempFile(); 00679 } 00680 } 00681 } 00682 00683 /** 00684 * When nothing is happening, get rid of this guy. Who needs him, eh? 00685 */ 00686 void clearTempFile() { 00687 if (tempFile != null) { 00688 //Debug.println("clearTempFile"); // XXX 00689 if (inMemory) { 00690 tempFile = null; 00691 } else if (true) { 00692 try { 00693 tempFile.close(); 00694 } catch (Throwable e8) { 00695 } finally { 00696 tempFile = null; 00697 } 00698 try { 00699 new File(dbRootDir, "scratch").delete(); 00700 } catch (Throwable e9) { 00701 } 00702 } 00703 } 00704 } 00705 00706 /** 00707 * Close the database. Sync and close the physical file(s), 00708 * delete the tempfile(s) and lockfile. Kill the database 00709 * reference monitor thread. 00710 */ 00711 public void close() { 00712 if (file != null) { 00713 //#ifdef TRACE 00714 if (Trace.bit(12)) { 00715 Debug.println("Datafile.close(" + dbRootDir.getPath() + ")"); 00716 } 00717 //#endif 00718 try { 00719 if (log != null) { 00720 log.checkpoint(); 00721 log.sync(); 00722 log.close(); 00723 log.remove(); 00724 } 00725 } catch (Throwable e8) { 00726 Debug.print(e8); 00727 } finally { 00728 log = null; 00729 } 00730 00731 clearTempFile(); 00732 00733 try { 00734 if (file != null) file.close(); 00735 } catch (Throwable e2) { 00736 } 00737 00738 file = null; 00739 try { 00740 //#ifdef JDK14 00741 if (lockLock != null) { 00742 lockLock.release(); 00743 if (lockChannel != null) lockChannel.close(); 00744 //new File(dbRootDir, "lockfile").delete(); 00745 } 00746 //#endif 00747 } catch (Throwable t) { 00748 } finally { 00749 //#ifdef JDK14 00750 lockLock = null; 00751 lockChannel = null; 00752 //#endif 00753 } 00754 } 00755 } 00756 00757 /** 00758 * Helper to create a new transaction, and optionally to write the 00759 * BEGIN_TRANSACTION log entry. 00760 */ 00761 public final Transaction makeTransaction(boolean writeLog) 00762 throws IOException 00763 { 00764 Transaction trans = null; 00765 long transId = -1; 00766 synchronized (fileLock) { 00767 transId = getNextTransId(); 00768 trans = locks.getTransaction(transId); 00769 } 00770 if (writeLog) { 00771 log.addEntry(new LogEntry(transId, 00772 LogEntry.BEGIN_TRANSACTION)); 00773 } 00774 return trans; 00775 } 00776 00777 /** 00778 * Commit the transaction 00779 */ 00780 public void commitTransaction(Transaction trans) throws IOException { 00781 long transId = trans.getTransactionId(); 00782 log.addEntry(new LogEntry(transId, LogEntry.COMMIT)); 00783 log.flushLog(); 00784 releaseLocks(trans); 00785 } 00786 00787 /** 00788 * Rollback the transaction 00789 */ 00790 public void rollbackTransaction(Transaction trans) 00791 throws IOException 00792 { 00793 log.rollbackTransaction(trans); 00794 commitTransaction(trans); 00795 } 00796 00797 /** 00798 * Rollback the specified statement. 00799 */ 00800 public void rollbackStatement(Transaction trans, int stmtId) 00801 throws IOException 00802 { 00803 log.rollbackStatement(trans, stmtId); 00804 } 00805 00806 /** 00807 * Remove the transaction for the given connection from the 00808 * 'active transactions' table. Flush the log? 00809 */ 00810 public void releaseLocks(Transaction trans) { 00811 locks.releaseTransaction(trans); 00812 } 00813 00814 /** 00815 * Find the specified transaction 00816 */ 00817 public Transaction findTransaction(long transId) throws IOException { 00818 Transaction t = locks.findTransaction(transId); 00819 return t; 00820 } 00821 00822 /** 00823 * Return the log for this database 00824 */ 00825 public final Log getLog() { return log; } 00826 00827 /** 00828 * If we get finalized, and haven't closed yet, then we should try. 00829 */ 00830 public void finalize() throws Throwable { 00831 close(); 00832 super.finalize(); 00833 } 00834 00835 /** 00836 * Return the total size of the datafile, in bytes 00837 */ 00838 public long getSize() { 00839 return file.getSize(); 00840 } 00841 00842 /** 00843 * Reset the temp file (on checkpoint) 00844 */ 00845 public void checkpoint(boolean truncate, boolean fastSync) 00846 throws IOException 00847 { 00848 synchronized (fileLock) { 00849 if (tempFile != null) { 00850 tempFile.flush(fastSync); 00851 } 00852 } 00853 } 00854 00855 public void checkpointHandler(LongMap activeTransactions) 00856 throws IOException 00857 { 00858 } 00859 00860 //#ifdef DEBUG 00861 /** 00862 * Return a displayable representation for debugging purposes 00863 */ 00864 public String toString() { 00865 String u = url; 00866 if (u.startsWith("jdbc:qed:")) u = u.substring(9); 00867 int idx = u.indexOf(';'); 00868 if (idx > 0) u = u.substring(0, idx); 00869 return u; 00870 } 00871 //#endif 00872 00873 public void doStep(Transaction t, LogEntry e) 00874 throws IOException, DatafileException 00875 { 00876 log.addEntry(e); 00877 e.redo(t, this); 00878 } 00879 00880 public boolean inRecovery() throws IOException { 00881 return log != null && log.inRecovery(); 00882 } 00883 00884 }