1 package com.quadcap.sql.file; 2 3 40 41 import java.io.ByteArrayOutputStream ; 42 import java.io.BufferedInputStream ; 43 import java.io.File ; 44 import java.io.FileOutputStream ; 45 import java.io.IOException ; 46 import java.io.InputStream ; 47 import java.io.OutputStream ; 48 import java.io.RandomAccessFile ; 49 50 import java.nio.channels.FileChannel ; 52 import java.nio.channels.FileLock ; 53 55 import java.util.HashMap ; 56 import java.util.Iterator ; 57 import java.util.Properties ; 58 59 import com.quadcap.sql.lock.Lock; 60 import com.quadcap.sql.lock.LockManager; 61 import com.quadcap.sql.lock.LockMode; 62 import com.quadcap.sql.lock.Transaction; 63 import com.quadcap.sql.lock.TransactionObserver; 64 65 import com.quadcap.sql.types.Value; 66 67 import com.quadcap.util.collections.LongIterator; 68 import com.quadcap.util.collections.LongMap; 69 70 import com.quadcap.util.ConfigNumber; 71 import com.quadcap.util.ConfigString; 72 import com.quadcap.util.Debug; 73 import com.quadcap.util.Util; 74 75 81 abstract public class Datafile { 82 protected boolean inMemory = false; 83 String fileName; 84 85 protected DatafileRoot root; 86 File dbRootDir; 87 File tempDir; 88 String url; 89 protected String origFileName; 90 91 protected BlockFile file; 93 94 protected BlockFile tempFile; 96 97 98 Log log; 100 101 LockManager locks = null; 103 104 Properties props; 106 107 static { 108 try { 109 Class.forName("com.quadcap.sql.io.Extern"); 110 } catch (Throwable t) {} 111 } 112 113 114 private FileChannel lockChannel = null; 115 private FileLock lockLock = null; 116 117 protected boolean readOnly = false; 118 protected byte[] temp = new byte[256]; 119 120 protected Object fileLock = new Object (); 121 122 private int tempFileRefCount = 0; 123 124 abstract public void maybeBackup() throws IOException ; 125 abstract public void readRoot() throws IOException ; 126 abstract public void createRoot(String fileName, 127 Properties props) throws IOException ; 128 abstract public void flushRoot() throws IOException ; 129 abstract public long getNextTransId() throws IOException ; 130 abstract public void bootFromRoot(boolean restart) throws IOException ; 131 132 140 static final int MIN_CACHE = 32; 141 142 146 static final int GOOD_CACHE = 256; 147 148 151 static final int SCRATCH_CACHE_DEFAULT = 64; 152 153 160 static final int BLOCK_SIZE 161 = ConfigNumber.find("qed.blockSize", Integer.toString(8192)).intValue(); 162 int blockSize = BLOCK_SIZE; 163 164 175 final ConfigNumber cacheSize 176 = ConfigNumber.find("qed.cacheSize", 177 Integer.toString(GOOD_CACHE * BLOCK_SIZE)); 178 int CACHE_SIZE = 179 Math.max(MIN_CACHE, cacheSize.intValue() / BLOCK_SIZE); 180 181 192 final ConfigNumber scratchCacheSize 193 = ConfigNumber.find("qed.scratchCacheSize", 194 Integer.toString(SCRATCH_CACHE_DEFAULT * BLOCK_SIZE)); 195 int SCRATCH_CACHE_SIZE = 196 Math.max(MIN_CACHE, scratchCacheSize.intValue() / BLOCK_SIZE); 197 198 206 final ConfigString defaultMode 207 = ConfigString.find("qed.defaultMode", "rw"); 208 209 218 static final ConfigString caseSensitive 219 = ConfigString.find("qed.isCaseSensitive", "true"); 220 public static boolean isCaseSensitive = 223 caseSensitive.toString().equalsIgnoreCase("true"); 224 225 228 229 233 234 238 239 public Datafile() { 240 } 241 242 public void init(String url, String fileName, Properties props) 243 throws IOException 244 { 245 this.fileName = fileName; 246 this.inMemory = 247 props.getProperty("memoryOnly", "").equalsIgnoreCase("true"); 248 boolean nullStore = 249 props.getProperty("cacheOnly", "").equalsIgnoreCase("true"); 250 if (inMemory || nullStore) { 251 initMemoryDatabase(url, fileName, props); 252 } else { 253 initFileDatabase(url, fileName, props); 254 } 255 } 256 257 public void initMemoryDatabase(String url, String fileName, Properties props) 258 throws IOException 259 { 260 this.inMemory = true; 261 this.props = (Properties )props.clone(); 262 this.url = url; 263 this.origFileName = fileName; 264 this.readOnly = false; 265 266 String ics = props.getProperty("isCaseSensitive", 267 caseSensitive.toString()); 268 isCaseSensitive = ics.equalsIgnoreCase("true"); 269 Value.isCaseSensitive = isCaseSensitive; 270 String bs = props.getProperty("blockSize"); 271 if (bs != null) { 272 blockSize = Integer.parseInt(bs); 273 } 274 String cs = props.getProperty("cacheSize"); 275 if (cs != null) { 276 CACHE_SIZE = Integer.parseInt(cs) / blockSize; 277 } 278 this.locks = new LockManager(); 279 280 this.file = new BlockFile(fileName, "rw", props, 281 blockSize, CACHE_SIZE); 282 this.fileLock = this.file.getLock(); 283 createRoot(fileName, props); 284 try { 285 this.log = new Log3(); 286 } catch (Throwable t) { 287 throw new DatafileException("Error creating logger", t); 288 } 289 log.init(this, true, props); 290 file.setLog(log); 291 292 bootFromRoot(false); 294 } 295 296 public void initFileDatabase(String url, String fileName, Properties props) 297 throws IOException 298 { 299 this.props = (Properties )props.clone(); 300 this.url = url; 301 this.origFileName = fileName; 302 fileName = new File (fileName).getAbsolutePath(); 303 this.dbRootDir = new File (fileName); 304 boolean newDb = false; 305 319 String mode = props.getProperty("mode", defaultMode.toString()); 320 this.readOnly = mode.equalsIgnoreCase("r"); 321 322 331 String ics = props.getProperty("isCaseSensitive", 332 caseSensitive.toString()); 333 isCaseSensitive = ics.equalsIgnoreCase("true"); 334 Value.isCaseSensitive = isCaseSensitive; 335 336 337 346 String create = props.getProperty("create", "false"); 347 if (!dbRootDir.isDirectory()) { 348 if (readOnly) { 349 throw new IOException ("Can't open database: " + fileName); 350 } 351 if (!create.equalsIgnoreCase("true")) { 352 throw new IOException ("Can't open database: " + fileName); 353 } 354 newDb = true; 355 if (!dbRootDir.mkdirs()) { 356 throw new IOException ("Can't create directory: " + fileName); 357 } 358 } 359 360 371 String bs = props.getProperty("blockSize"); 372 if (bs != null) { 373 blockSize = Integer.parseInt(bs); 374 } 375 376 377 387 String cs = props.getProperty("cacheSize"); 388 if (cs != null) { 389 CACHE_SIZE = Integer.parseInt(cs) / blockSize; 390 } 391 392 404 String scs = props.getProperty("scratchCacheSize"); 405 if (scs != null) { 406 SCRATCH_CACHE_SIZE = Integer.parseInt(scs) / blockSize; 407 } 408 409 419 String lf = props.getProperty("useLockFile", "true"); 420 boolean useLockFile = lf.equalsIgnoreCase("true"); 421 422 try { 423 File g = new File (dbRootDir, "datafile"); 424 if (!g.exists()) { 425 if (readOnly) { 426 throw new IOException ("Can't open database: " + fileName); 427 } 428 newDb = true; 429 } 430 431 if (!readOnly && useLockFile) { 432 File k = new File (dbRootDir, "lockfile"); 433 lockChannel = new RandomAccessFile (k, "rw").getChannel(); 435 lockLock = lockChannel.tryLock(); 436 if (lockLock == null) { 437 456 String force = props.getProperty("force", "false"); 457 if (!force.equalsIgnoreCase("true")) { 458 throw new IOException ( 459 "lockfile already exists. Maybe another " + 460 "process is accessing the database?"); 461 } 462 } 463 } 465 makeTempDirectory(props); 466 this.locks = new LockManager(); 467 468 String gFileName = g.getAbsolutePath(); 469 this.file = new BlockFile(gFileName, mode, props, 470 blockSize, CACHE_SIZE); 471 this.fileLock = this.file.getLock(); 472 if (!newDb) { 473 try { 474 readRoot(); 475 } catch (IOException ex) { 476 if (ex.toString().indexOf("magic") > 0) { 477 throw new IOException ("Database Connection Failed: Possible bad authorization"); 478 } else { 479 throw ex; 480 } 481 } 482 } else { 483 createRoot(fileName, props); 484 } 485 if (!readOnly) { 486 495 String ls = props.getProperty("logger"); 496 if (ls == null) ls = "2"; 497 this.log = (Log)( 498 Class.forName("com.quadcap.sql.file.Log" + ls). 499 newInstance()); 500 log.init(this, newDb, props); 501 file.setLog(log); 502 if (!newDb) { 503 log.restoreBlocks(); 504 file.revert(); 505 } 506 } 507 bootFromRoot(!newDb); 508 if (!newDb) { 509 log.restart(); 510 } 511 if (!readOnly) { 512 log.start(); 513 } 514 log.checkpoint(); 515 } catch (IOException e) { 516 if (newDb && !readOnly) deleteHalfBakedDir(); 517 throw e; 518 } catch (RuntimeException e) { 519 if (newDb && !readOnly) deleteHalfBakedDir(); 520 throw new DatafileException(e); 521 } catch (Exception e) { 522 if (newDb && !readOnly) deleteHalfBakedDir(); 523 throw new DatafileException(e); 524 } 525 } 526 527 final private void deleteHalfBakedDir() { 529 new File (dbRootDir, "datafile").delete(); 530 new File (dbRootDir, "lockfile").delete(); 531 } 532 533 536 public BlockFile getFile() { return file; } 537 538 541 public String getURL() { return url; } 542 543 546 File getDbRootDir() { return dbRootDir; } 547 548 551 File getScratchDir() { return tempDir; } 552 553 556 public LockManager getLockManager() { return locks; } 557 558 561 public Object getFileLock() { return fileLock; } 562 563 566 public boolean isReadOnly() { return readOnly; } 567 568 574 public Object getObject(long ref) throws IOException { 575 return file.getObject(ref); 576 } 577 578 579 585 public long putObject(Object obj) throws IOException { 586 return file.putObject(obj); 587 } 588 589 596 public void updateObject(long seg, Object obj) throws IOException { 597 file.updateObject(seg, obj); 598 } 599 600 606 public void removeObject(long ref) throws IOException { 607 file.removeObject(ref); 608 } 609 610 611 615 final void makeTempDirectory(Properties props) throws IOException { 616 628 if (!inMemory) { 629 String tmpDirName = props.getProperty("scratchDir", 630 dbRootDir.getAbsolutePath()); 631 this.tempDir = new File (tmpDirName); 632 if (!tempDir.isDirectory()) { 633 if (!tempDir.mkdirs()) { 634 throw new IOException ("Can't create temp directory: " + 635 tmpDirName); 636 } 637 } 638 } 639 } 640 641 645 public BlockFile getTempFile() throws IOException { 646 return getTempFile(true); 647 } 648 649 public BlockFile getTempFile(boolean incr) throws IOException { 650 synchronized (fileLock) { 651 if (tempFile == null) { 652 if (inMemory) { 653 this.tempFile = new BlockFile(fileName + ".scratch", "rw", 654 props, blockSize, SCRATCH_CACHE_SIZE); 655 } else { 656 File f = new File (tempDir, "scratch"); 657 this.tempFile = new BlockFile(f.getAbsolutePath(), "rw", 658 props, 659 blockSize, 660 SCRATCH_CACHE_SIZE); 661 } 662 this.tempFileRefCount = 0; 663 } 664 if (incr) tempFileRefCount++; 668 } 669 return tempFile; 670 } 671 672 public void releaseTempFile() { 673 synchronized (fileLock) { 674 if (--tempFileRefCount == 0) { 678 clearTempFile(); 679 } 680 } 681 } 682 683 686 void clearTempFile() { 687 if (tempFile != null) { 688 if (inMemory) { 690 tempFile = null; 691 } else if (true) { 692 try { 693 tempFile.close(); 694 } catch (Throwable e8) { 695 } finally { 696 tempFile = null; 697 } 698 try { 699 new File (dbRootDir, "scratch").delete(); 700 } catch (Throwable e9) { 701 } 702 } 703 } 704 } 705 706 711 public void close() { 712 if (file != null) { 713 if (Trace.bit(12)) { 715 Debug.println("Datafile.close(" + dbRootDir.getPath() + ")"); 716 } 717 try { 719 if (log != null) { 720 log.checkpoint(); 721 log.sync(); 722 log.close(); 723 log.remove(); 724 } 725 } catch (Throwable e8) { 726 Debug.print(e8); 727 } finally { 728 log = null; 729 } 730 731 clearTempFile(); 732 733 try { 734 if (file != null) file.close(); 735 } catch (Throwable e2) { 736 } 737 738 file = null; 739 try { 740 if (lockLock != null) { 742 lockLock.release(); 743 if (lockChannel != null) lockChannel.close(); 744 } 746 } catch (Throwable t) { 748 } finally { 749 lockLock = null; 751 lockChannel = null; 752 } 754 } 755 } 756 757 761 public final Transaction makeTransaction(boolean writeLog) 762 throws IOException 763 { 764 Transaction trans = null; 765 long transId = -1; 766 synchronized (fileLock) { 767 transId = getNextTransId(); 768 trans = locks.getTransaction(transId); 769 } 770 if (writeLog) { 771 log.addEntry(new LogEntry(transId, 772 LogEntry.BEGIN_TRANSACTION)); 773 } 774 return trans; 775 } 776 777 780 public void commitTransaction(Transaction trans) throws IOException { 781 long transId = trans.getTransactionId(); 782 log.addEntry(new LogEntry(transId, LogEntry.COMMIT)); 783 log.flushLog(); 784 releaseLocks(trans); 785 } 786 787 790 public void rollbackTransaction(Transaction trans) 791 throws IOException 792 { 793 log.rollbackTransaction(trans); 794 commitTransaction(trans); 795 } 796 797 800 public void rollbackStatement(Transaction trans, int stmtId) 801 throws IOException 802 { 803 log.rollbackStatement(trans, stmtId); 804 } 805 806 810 public void releaseLocks(Transaction trans) { 811 locks.releaseTransaction(trans); 812 } 813 814 817 public Transaction findTransaction(long transId) throws IOException { 818 Transaction t = locks.findTransaction(transId); 819 return t; 820 } 821 822 825 public final Log getLog() { return log; } 826 827 830 public void finalize() throws Throwable { 831 close(); 832 super.finalize(); 833 } 834 835 838 public long getSize() { 839 return file.getSize(); 840 } 841 842 845 public void checkpoint(boolean truncate, boolean fastSync) 846 throws IOException 847 { 848 synchronized (fileLock) { 849 if (tempFile != null) { 850 tempFile.flush(fastSync); 851 } 852 } 853 } 854 855 public void checkpointHandler(LongMap activeTransactions) 856 throws IOException 857 { 858 } 859 860 864 public String toString() { 865 String u = url; 866 if (u.startsWith("jdbc:qed:")) u = u.substring(9); 867 int idx = u.indexOf(';'); 868 if (idx > 0) u = u.substring(0, idx); 869 return u; 870 } 871 873 public void doStep(Transaction t, LogEntry e) 874 throws IOException , DatafileException 875 { 876 log.addEntry(e); 877 e.redo(t, this); 878 } 879 880 public boolean inRecovery() throws IOException { 881 return log != null && log.inRecovery(); 882 } 883 884 } 885 | Popular Tags |