00001
package com.quadcap.sql.file;
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
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
00051
import java.nio.channels.FileChannel;
00052
import java.nio.channels.FileLock;
00053
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
00077
00078
00079
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
00092 protected BlockFile file;
00093
00094
00095 protected BlockFile tempFile;
00096
00097
00098
00099 Log log;
00100
00101
00102 LockManager
locks = null;
00103
00104
00105 Properties
props;
00106
00107
static {
00108
try {
00109 Class.forName(
"com.quadcap.sql.io.Extern");
00110 }
catch (Throwable t) {}
00111 }
00112
00113
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
00134
00135
00136
00137
00138
00139
00140 static final
int MIN_CACHE = 32;
00141
00142
00143
00144
00145
00146 static final
int GOOD_CACHE = 256;
00147
00148
00149
00150
00151 static final
int SCRATCH_CACHE_DEFAULT = 64;
00152
00153
00154
00155
00156
00157
00158
00159
00160 static final
int BLOCK_SIZE
00161 = ConfigNumber.find("qed.blockSize", Integer.toString(8192)).intValue();
00162 int blockSize = BLOCK_SIZE;
00163
00164
00165
00166
00167
00168
00169
00170
00171
00172
00173
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
00182
00183
00184
00185
00186
00187
00188
00189
00190
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
00199
00200
00201
00202
00203
00204
00205
00206 final ConfigString defaultMode
00207 = ConfigString.find("qed.defaultMode", "rw");
00208
00209
00210
00211
00212
00213
00214
00215
00216
00217
00218 static final ConfigString caseSensitive
00219 = ConfigString.find("qed.isCaseSensitive", "true");
00220
00221
00222 public static
boolean isCaseSensitive =
00223 caseSensitive.toString().equalsIgnoreCase("true");
00224
00225
00226
00227
00228
00229
00230
00231
00232
00233
00234
00235
00236
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
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
00306
00307
00308
00309
00310
00311
00312
00313
00314
00315
00316
00317
00318
00319 String mode =
props.getProperty(
"mode",
defaultMode.toString());
00320
this.readOnly = mode.equalsIgnoreCase(
"r");
00321
00322
00323
00324
00325
00326
00327
00328
00329
00330
00331 String ics =
props.getProperty(
"isCaseSensitive",
00332
caseSensitive.toString());
00333
isCaseSensitive = ics.equalsIgnoreCase(
"true");
00334
Value.isCaseSensitive =
isCaseSensitive;
00335
00336
00337
00338
00339
00340
00341
00342
00343
00344
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
00361
00362
00363
00364
00365
00366
00367
00368
00369
00370
00371 String bs =
props.getProperty(
"blockSize");
00372
if (bs != null) {
00373
blockSize = Integer.parseInt(bs);
00374 }
00375
00376
00377
00378
00379
00380
00381
00382
00383
00384
00385
00386
00387 String cs =
props.getProperty(
"cacheSize");
00388
if (cs != null) {
00389
CACHE_SIZE = Integer.parseInt(cs) /
blockSize;
00390 }
00391
00392
00393
00394
00395
00396
00397
00398
00399
00400
00401
00402
00403
00404 String scs =
props.getProperty(
"scratchCacheSize");
00405
if (scs != null) {
00406
SCRATCH_CACHE_SIZE = Integer.parseInt(scs) /
blockSize;
00407 }
00408
00409
00410
00411
00412
00413
00414
00415
00416
00417
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
00434
lockChannel =
new RandomAccessFile(k,
"rw").getChannel();
00435
lockLock =
lockChannel.tryLock();
00436
if (
lockLock == null) {
00437
00438
00439
00440
00441
00442
00443
00444
00445
00446
00447
00448
00449
00450
00451
00452
00453
00454
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
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
00487
00488
00489
00490
00491
00492
00493
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
00528 final private void deleteHalfBakedDir() {
00529
new File(
dbRootDir,
"datafile").delete();
00530
new File(
dbRootDir,
"lockfile").delete();
00531 }
00532
00533
00534
00535
00536 public BlockFile getFile() {
return file; }
00537
00538
00539
00540
00541 public String
getURL() {
return url; }
00542
00543
00544
00545
00546 File
getDbRootDir() {
return dbRootDir; }
00547
00548
00549
00550
00551 File
getScratchDir() {
return tempDir; }
00552
00553
00554
00555
00556 public LockManager
getLockManager() {
return locks; }
00557
00558
00559
00560
00561 public Object
getFileLock() {
return fileLock; }
00562
00563
00564
00565
00566 public boolean isReadOnly() {
return readOnly; }
00567
00568
00569
00570
00571
00572
00573
00574 public Object
getObject(
long ref)
throws IOException {
00575
return file.
getObject(ref);
00576 }
00577
00578
00579
00580
00581
00582
00583
00584
00585 public long putObject(Object obj)
throws IOException {
00586
return file.
putObject(obj);
00587 }
00588
00589
00590
00591
00592
00593
00594
00595
00596 public void updateObject(
long seg, Object obj)
throws IOException {
00597
file.
updateObject(seg, obj);
00598 }
00599
00600
00601
00602
00603
00604
00605
00606 public void removeObject(
long ref)
throws IOException {
00607
file.
removeObject(ref);
00608 }
00609
00610
00611
00612
00613
00614
00615 final void makeTempDirectory(Properties props)
throws IOException {
00616
00617
00618
00619
00620
00621
00622
00623
00624
00625
00626
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
00643
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
00665
00666
00667
if (incr)
tempFileRefCount++;
00668 }
00669
return tempFile;
00670 }
00671
00672 public void releaseTempFile() {
00673
synchronized (
fileLock) {
00674
00675
00676
00677
if (--
tempFileRefCount == 0) {
00678
clearTempFile();
00679 }
00680 }
00681 }
00682
00683
00684
00685
00686 void clearTempFile() {
00687
if (
tempFile != null) {
00688
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
00708
00709
00710
00711 public void close() {
00712
if (
file != null) {
00713
00714
if (
Trace.bit(12)) {
00715
Debug.println(
"Datafile.close(" +
dbRootDir.getPath() +
")");
00716 }
00717
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
00741
if (
lockLock != null) {
00742
lockLock.release();
00743
if (
lockChannel != null)
lockChannel.close();
00744
00745 }
00746
00747 }
catch (Throwable t) {
00748 } finally {
00749
00750
lockLock = null;
00751
lockChannel = null;
00752
00753 }
00754 }
00755 }
00756
00757
00758
00759
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
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
00789
00790 public void rollbackTransaction(
Transaction trans)
00791
throws IOException
00792 {
00793
log.
rollbackTransaction(trans);
00794 commitTransaction(trans);
00795 }
00796
00797
00798
00799
00800 public void rollbackStatement(
Transaction trans,
int stmtId)
00801
throws IOException
00802 {
00803
log.
rollbackStatement(trans, stmtId);
00804 }
00805
00806
00807
00808
00809
00810 public void releaseLocks(
Transaction trans) {
00811
locks.releaseTransaction(trans);
00812 }
00813
00814
00815
00816
00817 public Transaction findTransaction(
long transId)
throws IOException {
00818
Transaction t =
locks.findTransaction(transId);
00819
return t;
00820 }
00821
00822
00823
00824
00825 public final Log getLog() {
return log; }
00826
00827
00828
00829
00830 public void finalize() throws Throwable {
00831
close();
00832 super.finalize();
00833 }
00834
00835
00836
00837
00838 public long getSize() {
00839
return file.
getSize();
00840 }
00841
00842
00843
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
00861
00862
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
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 }