0001: /*
0002: * Copyright 2004-2008 H2 Group. Licensed under the H2 License, Version 1.0
0003: * (http://h2database.com/html/license.html).
0004: * Initial Developer: H2 Group
0005: */
0006: package org.h2.store;
0007:
0008: import java.io.ByteArrayInputStream;
0009: import java.io.ByteArrayOutputStream;
0010: import java.io.DataInputStream;
0011: import java.io.DataOutputStream;
0012: import java.io.IOException;
0013: import java.io.OutputStream;
0014: import java.sql.SQLException;
0015: import java.util.Comparator;
0016: import java.util.HashSet;
0017: import java.util.Iterator;
0018:
0019: import org.h2.api.DatabaseEventListener;
0020: import org.h2.constant.ErrorCode;
0021: import org.h2.constant.SysProperties;
0022: import org.h2.engine.Constants;
0023: import org.h2.engine.Database;
0024: import org.h2.engine.Session;
0025: import org.h2.log.LogSystem;
0026: import org.h2.log.RedoLogRecord;
0027: import org.h2.message.Message;
0028: import org.h2.message.Trace;
0029: import org.h2.util.BitField;
0030: import org.h2.util.Cache;
0031: import org.h2.util.Cache2Q;
0032: import org.h2.util.CacheLRU;
0033: import org.h2.util.CacheObject;
0034: import org.h2.util.CacheWriter;
0035: import org.h2.util.FileUtils;
0036: import org.h2.util.IntArray;
0037: import org.h2.util.MathUtils;
0038: import org.h2.util.ObjectArray;
0039: import org.h2.util.ObjectUtils;
0040:
0041: /**
0042: * This class represents a file that is usually written to disk. The two main
0043: * files are .data.db and .index.db. For each such file, a number of
0044: * {@link Storage} objects exists. The disk file is responsible for caching;
0045: * each object contains a {@link Cache} object. Changes in the file are logged
0046: * in a {@link LogSystem} object. Reading and writing to the file is delegated
0047: * to the {@link FileStore} class.
0048: * <p>
0049: * There are 'blocks' of 128 bytes (DiskFile.BLOCK_SIZE). Each objects own one
0050: * or more pages; each page size is 64 blocks (DiskFile.BLOCKS_PER_PAGE). That
0051: * is 8 KB page size. However pages are not read or written as one unit; only
0052: * individual objects (multiple blocks at a time) are read or written.
0053: * <p>
0054: * Currently there are no in-place updates. Each row occupies one or multiple
0055: * blocks. Row can occupy multiple pages. Rows are always contiguous (except
0056: * LOBs, they are stored in their own files).
0057: */
0058: public class DiskFile implements CacheWriter {
0059:
0060: /**
0061: * The size of a block in bytes.
0062: * A block is the minimum row size.
0063: */
0064: public static final int BLOCK_SIZE = 128;
0065:
0066: /**
0067: * The size of a page in blocks.
0068: * Each page contains blocks from the same storage.
0069: */
0070: static final int BLOCK_PAGE_PAGE_SHIFT = 6;
0071: public static final int BLOCKS_PER_PAGE = 1 << BLOCK_PAGE_PAGE_SHIFT;
0072: public static final int OFFSET = FileStore.HEADER_LENGTH;
0073: static final int FREE_PAGE = -1;
0074: // TODO storage: header should probably be 4 KB or so
0075: // (to match block size of operating system)
0076: private Database database;
0077: private String fileName;
0078: private FileStore file;
0079: private BitField used;
0080: private BitField deleted;
0081: private HashSet potentiallyFreePages;
0082: private int fileBlockCount;
0083: private IntArray pageOwners;
0084: private Cache cache;
0085: private LogSystem log;
0086: private DataPage rowBuff;
0087: private DataPage freeBlock;
0088: private boolean dataFile;
0089: private boolean logChanges;
0090: private int recordOverhead;
0091: private boolean init, initAlreadyTried;
0092: private ObjectArray redoBuffer;
0093: private int redoBufferSize;
0094: private int readCount, writeCount;
0095: private String mode;
0096: private int nextDeleteId = 1;
0097:
0098: public DiskFile(Database database, String fileName, String mode,
0099: boolean dataFile, boolean logChanges, int cacheSize)
0100: throws SQLException {
0101: reset();
0102: this .database = database;
0103: this .log = database.getLog();
0104: this .fileName = fileName;
0105: this .mode = mode;
0106: this .dataFile = dataFile;
0107: this .logChanges = logChanges;
0108: String cacheType = database.getCacheType();
0109: if (Cache2Q.TYPE_NAME.equals(cacheType)) {
0110: this .cache = new Cache2Q(this , cacheSize);
0111: } else {
0112: this .cache = new CacheLRU(this , cacheSize);
0113: }
0114: rowBuff = DataPage.create(database, BLOCK_SIZE);
0115: // TODO: the overhead is larger in the log file, so this value is too high :-(
0116: recordOverhead = 4 * rowBuff.getIntLen() + 1
0117: + rowBuff.getFillerLength();
0118: freeBlock = DataPage.create(database, BLOCK_SIZE);
0119: freeBlock.fill(BLOCK_SIZE);
0120: freeBlock.updateChecksum();
0121: try {
0122: if (FileUtils.exists(fileName)) {
0123: file = database.openFile(fileName, mode, true);
0124: long length = file.length();
0125: database.notifyFileSize(length);
0126: int blocks = (int) ((length - OFFSET) / BLOCK_SIZE);
0127: setBlockCount(blocks);
0128: } else {
0129: create();
0130: }
0131: } catch (SQLException e) {
0132: close();
0133: throw e;
0134: }
0135: }
0136:
0137: private void reset() {
0138: used = new BitField();
0139: deleted = new BitField();
0140: pageOwners = new IntArray();
0141: // init pageOwners
0142: setBlockCount(fileBlockCount);
0143: redoBuffer = new ObjectArray();
0144: potentiallyFreePages = new HashSet();
0145: }
0146:
0147: private void setBlockCount(int count) {
0148: fileBlockCount = count;
0149: int pages = getPage(count);
0150: while (pages >= pageOwners.size()) {
0151: pageOwners.add(FREE_PAGE);
0152: }
0153: }
0154:
0155: int getBlockCount() {
0156: return fileBlockCount;
0157: }
0158:
0159: private void create() throws SQLException {
0160: file = database.openFile(fileName, mode, false);
0161: DataPage header = DataPage.create(database, OFFSET);
0162: file.seek(FileStore.HEADER_LENGTH);
0163: header.fill(OFFSET);
0164: header.updateChecksum();
0165: file.write(header.getBytes(), 0, OFFSET);
0166: }
0167:
0168: private void freeUnusedPages() throws SQLException {
0169: for (int i = 0; i < pageOwners.size(); i++) {
0170: if (pageOwners.get(i) != FREE_PAGE && isPageFree(i)) {
0171: setPageOwner(i, FREE_PAGE);
0172: }
0173: }
0174: }
0175:
0176: public byte[] getSummary() throws SQLException {
0177: synchronized (database) {
0178: try {
0179: ByteArrayOutputStream buff = new ByteArrayOutputStream();
0180: DataOutputStream out = new DataOutputStream(buff);
0181: int blocks = (int) ((file.length() - OFFSET) / BLOCK_SIZE);
0182: out.writeInt(blocks);
0183: for (int i = 0, x = 0; i < blocks / 8; i++) {
0184: int mask = 0;
0185: for (int j = 0; j < 8; j++) {
0186: if (used.get(x)) {
0187: mask |= 1 << j;
0188: }
0189: x++;
0190: }
0191: out.write(mask);
0192: }
0193: out.writeInt(pageOwners.size());
0194: ObjectArray storages = new ObjectArray();
0195: for (int i = 0; i < pageOwners.size(); i++) {
0196: int s = pageOwners.get(i);
0197: out.writeInt(s);
0198: if (s >= 0
0199: && (s >= storages.size() || storages.get(s) == null)) {
0200: Storage storage = database.getStorage(s, this );
0201: while (storages.size() <= s) {
0202: storages.add(null);
0203: }
0204: storages.set(s, storage);
0205: }
0206: }
0207: for (int i = 0; i < storages.size(); i++) {
0208: Storage storage = (Storage) storages.get(i);
0209: if (storage != null) {
0210: out.writeInt(i);
0211: out.writeInt(storage.getRecordCount());
0212: }
0213: }
0214: out.writeInt(-1);
0215: out.close();
0216: byte[] b2 = buff.toByteArray();
0217: return b2;
0218: } catch (IOException e) {
0219: // will probably never happen, because only in-memory structures are
0220: // used
0221: return null;
0222: }
0223: }
0224: }
0225:
0226: boolean isPageFree(int page) {
0227: for (int i = page * BLOCKS_PER_PAGE; i < (page + 1)
0228: * BLOCKS_PER_PAGE; i++) {
0229: if (used.get(i)) {
0230: return false;
0231: }
0232: }
0233: return true;
0234: }
0235:
0236: public void initFromSummary(byte[] summary) {
0237: synchronized (database) {
0238: if (summary == null || summary.length == 0) {
0239: ObjectArray list = database.getAllStorages();
0240: for (int i = 0; i < list.size(); i++) {
0241: Storage s = (Storage) list.get(i);
0242: if (s != null && s.getDiskFile() == this ) {
0243: database.removeStorage(s.getId(), this );
0244: }
0245: }
0246: reset();
0247: initAlreadyTried = false;
0248: init = false;
0249: return;
0250: }
0251: if (database.getRecovery()
0252: || (initAlreadyTried && (!dataFile || !SysProperties.CHECK))) {
0253: return;
0254: }
0255: initAlreadyTried = true;
0256: int stage = 0;
0257: try {
0258: DataInputStream in = new DataInputStream(
0259: new ByteArrayInputStream(summary));
0260: int b2 = in.readInt();
0261: if (b2 > fileBlockCount) {
0262: database.getTrace(Trace.DATABASE).info(
0263: "unexpected size " + b2
0264: + " when initializing summary for "
0265: + fileName + " expected:"
0266: + fileBlockCount);
0267: return;
0268: }
0269: stage++;
0270: for (int i = 0, x = 0; i < b2 / 8; i++) {
0271: int mask = in.read();
0272: if (init) {
0273: for (int j = 0; j < 8; j++, x++) {
0274: if (used.get(x) != ((mask & (1 << j)) != 0)) {
0275: throw Message
0276: .getInternalError("Redo failure, block: "
0277: + x
0278: + " expected in-use bit: "
0279: + used.get(x));
0280: }
0281: }
0282: } else {
0283: for (int j = 0; j < 8; j++, x++) {
0284: if ((mask & (1 << j)) != 0) {
0285: used.set(x);
0286: }
0287: }
0288: }
0289: }
0290: stage++;
0291: int len = in.readInt();
0292: ObjectArray storages = new ObjectArray();
0293: for (int i = 0; i < len; i++) {
0294: int s = in.readInt();
0295: while (storages.size() <= s) {
0296: storages.add(null);
0297: }
0298: if (init) {
0299: int old = getPageOwner(i);
0300: if (old != -1 && old != s) {
0301: throw Message
0302: .getInternalError("Redo failure, expected page owner: "
0303: + old + " got: " + s);
0304: }
0305: } else {
0306: if (s >= 0) {
0307: Storage storage = database.getStorage(s,
0308: this );
0309: storages.set(s, storage);
0310: storage.addPage(i);
0311: }
0312: setPageOwner(i, s);
0313: }
0314: }
0315: stage++;
0316: while (true) {
0317: int s = in.readInt();
0318: if (s < 0) {
0319: break;
0320: }
0321: int recordCount = in.readInt();
0322: Storage storage = (Storage) storages.get(s);
0323: if (init) {
0324: if (storage != null) {
0325: int current = storage.getRecordCount();
0326: if (current != recordCount) {
0327: throw Message
0328: .getInternalError("Redo failure, expected row count: "
0329: + current
0330: + " got: "
0331: + recordCount);
0332: }
0333: }
0334: } else {
0335: storage.setRecordCount(recordCount);
0336: }
0337: }
0338: stage++;
0339: freeUnusedPages();
0340: init = true;
0341: } catch (Exception e) {
0342: database.getTrace(Trace.DATABASE).error(
0343: "error initializing summary for " + fileName
0344: + " size:" + summary.length + " stage:"
0345: + stage, e);
0346: // ignore - init is still false in this case
0347: }
0348: }
0349: }
0350:
0351: public void init() throws SQLException {
0352: synchronized (database) {
0353: if (init) {
0354: return;
0355: }
0356: ObjectArray storages = database.getAllStorages();
0357: for (int i = 0; i < storages.size(); i++) {
0358: Storage s = (Storage) storages.get(i);
0359: if (s != null && s.getDiskFile() == this ) {
0360: s.setRecordCount(0);
0361: }
0362: }
0363: int blockHeaderLen = Math.max(Constants.FILE_BLOCK_SIZE,
0364: 2 * rowBuff.getIntLen());
0365: byte[] buff = new byte[blockHeaderLen];
0366: DataPage s = DataPage.create(database, buff);
0367: long time = 0;
0368: for (int i = 0; i < fileBlockCount;) {
0369: long t2 = System.currentTimeMillis();
0370: if (t2 > time + 10) {
0371: time = t2;
0372: database.setProgress(
0373: DatabaseEventListener.STATE_SCAN_FILE,
0374: this .fileName, i, fileBlockCount);
0375: }
0376: go(i);
0377: file.readFully(buff, 0, blockHeaderLen);
0378: s.reset();
0379: int blockCount = s.readInt();
0380: if (SysProperties.CHECK && blockCount < 0) {
0381: throw Message.getInternalError();
0382: }
0383: if (blockCount == 0) {
0384: setUnused(null, i, 1);
0385: i++;
0386: } else {
0387: int id = s.readInt();
0388: if (SysProperties.CHECK && id < 0) {
0389: throw Message.getInternalError();
0390: }
0391: Storage storage = database.getStorage(id, this );
0392: setUnused(null, i, blockCount);
0393: setBlockOwner(null, storage, i, blockCount, true);
0394: storage.incrementRecordCount();
0395: i += blockCount;
0396: }
0397: }
0398: database.setProgress(DatabaseEventListener.STATE_SCAN_FILE,
0399: this .fileName, fileBlockCount, fileBlockCount);
0400: init = true;
0401: }
0402: }
0403:
0404: public void flush() throws SQLException {
0405: synchronized (database) {
0406: database.checkPowerOff();
0407: ObjectArray list = cache.getAllChanged();
0408: CacheObject.sort(list);
0409: for (int i = 0; i < list.size(); i++) {
0410: Record rec = (Record) list.get(i);
0411: writeBack(rec);
0412: }
0413: for (int i = 0; i < fileBlockCount; i++) {
0414: i = deleted.nextSetBit(i);
0415: if (i < 0) {
0416: break;
0417: }
0418: if (deleted.get(i)) {
0419: writeDirectDeleted(i, 1);
0420: deleted.clear(i);
0421: }
0422: }
0423: }
0424: }
0425:
0426: // this implementation accesses the file in a linear way
0427: // public void flushNew() throws SQLException {
0428: // int todoTest;
0429: // synchronized (database) {
0430: // database.checkPowerOff();
0431: // ObjectArray list = cache.getAllChanged();
0432: // CacheObject.sort(list);
0433: // int deletePos = deleted.nextSetBit(0);
0434: // int writeIndex = 0;
0435: // Record writeRecord = null;
0436: // while (true) {
0437: // if (writeRecord == null && writeIndex < list.size()) {
0438: // writeRecord = (Record) list.get(writeIndex++);
0439: // }
0440: // if (writeRecord != null &&
0441: // (deletePos < 0 || writeRecord.getPos() < deletePos)) {
0442: // writeBack(writeRecord);
0443: // writeRecord = null;
0444: // } else if (deletePos < fileBlockCount && deletePos >= 0) {
0445: // writeDirectDeleted(deletePos, 1);
0446: // deleted.clear(deletePos);
0447: // deletePos = deleted.nextSetBit(deletePos);
0448: // } else {
0449: // break;
0450: // }
0451: // }
0452: // }
0453: // }
0454:
0455: public void close() throws SQLException {
0456: synchronized (database) {
0457: SQLException closeException = null;
0458: if (!database.getReadOnly()) {
0459: try {
0460: flush();
0461: } catch (SQLException e) {
0462: closeException = e;
0463: }
0464: }
0465: cache.clear();
0466: // continue with close even if flush was not possible (file storage
0467: // problem)
0468: if (file != null) {
0469: file.closeSilently();
0470: file = null;
0471: }
0472: if (closeException != null) {
0473: throw closeException;
0474: }
0475: readCount = writeCount = 0;
0476: }
0477: }
0478:
0479: private void go(int block) throws SQLException {
0480: database.checkPowerOff();
0481: file.seek(getFilePos(block));
0482: }
0483:
0484: private long getFilePos(int block) {
0485: return ((long) block * BLOCK_SIZE) + OFFSET;
0486: }
0487:
0488: Record getRecordIfStored(Session session, int pos,
0489: RecordReader reader, int storageId) throws SQLException {
0490: synchronized (database) {
0491: try {
0492: int owner = getPageOwner(getPage(pos));
0493: if (owner != storageId) {
0494: return null;
0495: }
0496: go(pos);
0497: rowBuff.reset();
0498: byte[] buff = rowBuff.getBytes();
0499: file.readFully(buff, 0, BLOCK_SIZE);
0500: DataPage s = DataPage.create(database, buff);
0501: s.readInt(); // blockCount
0502: int id = s.readInt();
0503: if (id != storageId) {
0504: return null;
0505: }
0506: } catch (Exception e) {
0507: return null;
0508: }
0509: return getRecord(session, pos, reader, storageId);
0510: }
0511: }
0512:
0513: Record getRecord(Session session, int pos, RecordReader reader,
0514: int storageId) throws SQLException {
0515: synchronized (database) {
0516: if (file == null) {
0517: throw Message
0518: .getSQLException(ErrorCode.SIMULATED_POWER_OFF);
0519: }
0520: Record record = (Record) cache.get(pos);
0521: if (record != null) {
0522: return record;
0523: }
0524: readCount++;
0525: go(pos);
0526: rowBuff.reset();
0527: byte[] buff = rowBuff.getBytes();
0528: file.readFully(buff, 0, BLOCK_SIZE);
0529: DataPage s = DataPage.create(database, buff);
0530: int blockCount = s.readInt();
0531: int id = s.readInt();
0532: if (SysProperties.CHECK && storageId != id) {
0533: throw Message.getInternalError("File ID mismatch got="
0534: + id + " expected=" + storageId + " pos=" + pos
0535: + " " + logChanges + " " + this
0536: + " blockCount:" + blockCount);
0537: }
0538: if (SysProperties.CHECK && blockCount == 0) {
0539: throw Message.getInternalError("0 blocks to read pos="
0540: + pos);
0541: }
0542: if (blockCount > 1) {
0543: byte[] b2 = new byte[blockCount * BLOCK_SIZE];
0544: System.arraycopy(buff, 0, b2, 0, BLOCK_SIZE);
0545: buff = b2;
0546: file.readFully(buff, BLOCK_SIZE, blockCount
0547: * BLOCK_SIZE - BLOCK_SIZE);
0548: s = DataPage.create(database, buff);
0549: s.readInt();
0550: s.readInt();
0551: }
0552: s.check(blockCount * BLOCK_SIZE);
0553: Record r = reader.read(session, s);
0554: r.setStorageId(storageId);
0555: r.setPos(pos);
0556: r.setBlockCount(blockCount);
0557: cache.put(r);
0558: return r;
0559: }
0560: }
0561:
0562: int allocate(Storage storage, int blockCount) throws SQLException {
0563: reuseSpace();
0564: synchronized (database) {
0565: if (file == null) {
0566: throw Message
0567: .getSQLException(ErrorCode.SIMULATED_POWER_OFF);
0568: }
0569: blockCount = getPage(blockCount + BLOCKS_PER_PAGE - 1)
0570: * BLOCKS_PER_PAGE;
0571: int lastPage = getPage(getBlockCount());
0572: int pageCount = getPage(blockCount);
0573: int pos = -1;
0574: boolean found = false;
0575: for (int i = 0; i < lastPage; i++) {
0576: found = true;
0577: for (int j = i; j < i + pageCount; j++) {
0578: if (j >= lastPage || getPageOwner(j) != FREE_PAGE) {
0579: found = false;
0580: break;
0581: }
0582: }
0583: if (found) {
0584: pos = i * BLOCKS_PER_PAGE;
0585: break;
0586: }
0587: }
0588: if (!found) {
0589: int max = getBlockCount();
0590: pos = MathUtils.roundUp(max, BLOCKS_PER_PAGE);
0591: if (rowBuff instanceof DataPageText) {
0592: if (pos > max) {
0593: writeDirectDeleted(max, pos - max);
0594: }
0595: writeDirectDeleted(pos, blockCount);
0596: } else {
0597: long min = ((long) pos + blockCount) * BLOCK_SIZE;
0598: min = MathUtils.scaleUp50Percent(
0599: Constants.FILE_MIN_SIZE, min,
0600: Constants.FILE_PAGE_SIZE,
0601: Constants.FILE_MAX_INCREMENT)
0602: + OFFSET;
0603: if (min > file.length()) {
0604: file.setLength(min);
0605: database.notifyFileSize(min);
0606: }
0607: }
0608: }
0609: setBlockOwner(null, storage, pos, blockCount, false);
0610: for (int i = 0; i < blockCount; i++) {
0611: storage.free(i + pos, 1);
0612: }
0613: return pos;
0614: }
0615: }
0616:
0617: private void setBlockOwner(Session session, Storage storage,
0618: int pos, int blockCount, boolean inUse) throws SQLException {
0619: if (pos + blockCount > fileBlockCount) {
0620: setBlockCount(pos + blockCount);
0621: }
0622: if (!inUse) {
0623: setUnused(session, pos, blockCount);
0624: }
0625: for (int i = getPage(pos); i <= getPage(pos + blockCount - 1); i++) {
0626: setPageOwner(i, storage.getId());
0627: }
0628: if (inUse) {
0629: used.setRange(pos, blockCount, true);
0630: deleted.setRange(pos, blockCount, false);
0631: }
0632: }
0633:
0634: private void setUnused(Session session, int pos, int blockCount)
0635: throws SQLException {
0636: if (pos + blockCount > fileBlockCount) {
0637: setBlockCount(pos + blockCount);
0638: }
0639: uncommittedDelete(session);
0640: for (int i = pos; i < pos + blockCount; i++) {
0641: used.clear(i);
0642: if ((i % BLOCKS_PER_PAGE == 0)
0643: && (pos + blockCount >= i + BLOCKS_PER_PAGE)) {
0644: // if this is the first page of a block and if the whole page is free
0645: freePage(getPage(i));
0646: }
0647: }
0648: }
0649:
0650: void reuseSpace() throws SQLException {
0651: if (SysProperties.REUSE_SPACE_QUICKLY) {
0652: if (potentiallyFreePages.size() >= SysProperties.REUSE_SPACE_AFTER) {
0653: Session[] sessions = database.getSessions();
0654: int oldest = 0;
0655: for (int i = 0; i < sessions.length; i++) {
0656: int deleteId = sessions[i]
0657: .getLastUncommittedDelete();
0658: if (oldest == 0
0659: || (deleteId != 0 && deleteId < oldest)) {
0660: oldest = deleteId;
0661: }
0662: }
0663: for (Iterator it = potentiallyFreePages.iterator(); it
0664: .hasNext();) {
0665: int p = ((Integer) it.next()).intValue();
0666: if (oldest == 0) {
0667: setPageOwner(p, FREE_PAGE);
0668: it.remove();
0669: }
0670: }
0671: }
0672: }
0673: }
0674:
0675: void uncommittedDelete(Session session) throws SQLException {
0676: if (session != null && logChanges
0677: && SysProperties.REUSE_SPACE_QUICKLY) {
0678: int deleteId = session.getLastUncommittedDelete();
0679: if (deleteId == 0 || deleteId < nextDeleteId) {
0680: deleteId = ++nextDeleteId;
0681: session.setLastUncommittedDelete(deleteId);
0682: }
0683: }
0684: }
0685:
0686: void freePage(int page) throws SQLException {
0687: if (!logChanges) {
0688: setPageOwner(page, FREE_PAGE);
0689: } else {
0690: if (SysProperties.REUSE_SPACE_QUICKLY) {
0691: potentiallyFreePages.add(ObjectUtils.getInteger(page));
0692: reuseSpace();
0693: }
0694: }
0695: }
0696:
0697: int getPage(int pos) {
0698: return pos >>> BLOCK_PAGE_PAGE_SHIFT;
0699: }
0700:
0701: int getPageOwner(int page) {
0702: if (page * BLOCKS_PER_PAGE > fileBlockCount
0703: || page >= pageOwners.size()) {
0704: return FREE_PAGE;
0705: }
0706: return pageOwners.get(page);
0707: }
0708:
0709: public void setPageOwner(int page, int storageId)
0710: throws SQLException {
0711: int old = pageOwners.get(page);
0712: if (old == storageId) {
0713: return;
0714: }
0715: if (SysProperties.CHECK && old >= 0 && storageId >= 0
0716: && old != storageId) {
0717: for (int i = 0; i < BLOCKS_PER_PAGE; i++) {
0718: if (used.get(i + page * BLOCKS_PER_PAGE)) {
0719: throw Message
0720: .getInternalError("double allocation in file "
0721: + fileName
0722: + " page "
0723: + page
0724: + " blocks "
0725: + (BLOCKS_PER_PAGE * page)
0726: + "-"
0727: + (BLOCKS_PER_PAGE * (page + 1) - 1));
0728: }
0729: }
0730: }
0731: if (old >= 0) {
0732: database.getStorage(old, this ).removePage(page);
0733: if (!logChanges) {
0734: // need to clean the page, otherwise it may never get cleaned
0735: // and can become corrupted
0736: writeDirectDeleted(page * BLOCKS_PER_PAGE,
0737: BLOCKS_PER_PAGE);
0738: }
0739: }
0740: if (storageId >= 0) {
0741: database.getStorage(storageId, this ).addPage(page);
0742: if (SysProperties.REUSE_SPACE_QUICKLY) {
0743: potentiallyFreePages.remove(ObjectUtils
0744: .getInteger(page));
0745: }
0746: }
0747: pageOwners.set(page, storageId);
0748: }
0749:
0750: void setUsed(int pos, int blockCount) {
0751: synchronized (database) {
0752: if (pos + blockCount > fileBlockCount) {
0753: setBlockCount(pos + blockCount);
0754: }
0755: used.setRange(pos, blockCount, true);
0756: deleted.setRange(pos, blockCount, false);
0757: }
0758: }
0759:
0760: public void delete() throws SQLException {
0761: synchronized (database) {
0762: try {
0763: cache.clear();
0764: file.close();
0765: FileUtils.delete(fileName);
0766: } catch (IOException e) {
0767: throw Message.convertIOException(e, fileName);
0768: } finally {
0769: file = null;
0770: fileName = null;
0771: }
0772: }
0773: }
0774:
0775: // private int allocateBest(int start, int blocks) {
0776: // while (true) {
0777: // int p = getLastUsedPlusOne(start, blocks);
0778: // if (p == start) {
0779: // start = p;
0780: // break;
0781: // }
0782: // start = p;
0783: // }
0784: // allocate(start, blocks);
0785: // return start;
0786: // }
0787:
0788: public void writeBack(CacheObject obj) throws SQLException {
0789: synchronized (database) {
0790: writeCount++;
0791: Record record = (Record) obj;
0792: int blockCount = record.getBlockCount();
0793: record.prepareWrite();
0794: go(record.getPos());
0795: DataPage buff = rowBuff;
0796: buff.reset();
0797: buff.checkCapacity(blockCount * BLOCK_SIZE);
0798: buff.writeInt(blockCount);
0799: buff.writeInt(record.getStorageId());
0800: record.write(buff);
0801: buff.fill(blockCount * BLOCK_SIZE);
0802: buff.updateChecksum();
0803: file.write(buff.getBytes(), 0, buff.length());
0804: record.setChanged(false);
0805: }
0806: }
0807:
0808: /*
0809: * Must be synchronized externally
0810: */
0811: BitField getUsed() {
0812: return used;
0813: }
0814:
0815: void updateRecord(Session session, Record record)
0816: throws SQLException {
0817: synchronized (database) {
0818: record.setChanged(true);
0819: int pos = record.getPos();
0820: Record old = (Record) cache.update(pos, record);
0821: if (SysProperties.CHECK) {
0822: if (old != null) {
0823: if (old != record) {
0824: database.checkPowerOff();
0825: throw Message
0826: .getInternalError("old != record old="
0827: + old + " new=" + record);
0828: }
0829: int blockCount = record.getBlockCount();
0830: for (int i = 0; i < blockCount; i++) {
0831: if (deleted.get(i + pos)) {
0832: throw Message
0833: .getInternalError("update marked as deleted: "
0834: + (i + pos));
0835: }
0836: }
0837: }
0838: }
0839: if (logChanges) {
0840: log.add(session, this , record);
0841: }
0842: }
0843: }
0844:
0845: void writeDirectDeleted(int recordId, int blockCount)
0846: throws SQLException {
0847: synchronized (database) {
0848: go(recordId);
0849: for (int i = 0; i < blockCount; i++) {
0850: file.write(freeBlock.getBytes(), 0, freeBlock.length());
0851: }
0852: free(recordId, blockCount);
0853: }
0854: }
0855:
0856: void writeDirect(Storage storage, int pos, byte[] data, int offset)
0857: throws SQLException {
0858: synchronized (database) {
0859: go(pos);
0860: file.write(data, offset, BLOCK_SIZE);
0861: setBlockOwner(null, storage, pos, 1, true);
0862: }
0863: }
0864:
0865: public int copyDirect(int pos, OutputStream out)
0866: throws SQLException {
0867: synchronized (database) {
0868: try {
0869: if (pos < 0) {
0870: // read the header
0871: byte[] buffer = new byte[OFFSET];
0872: file.seek(0);
0873: file.readFullyDirect(buffer, 0, OFFSET);
0874: out.write(buffer);
0875: return 0;
0876: }
0877: if (pos >= fileBlockCount) {
0878: return -1;
0879: }
0880: int blockSize = DiskFile.BLOCK_SIZE;
0881: byte[] buff = new byte[blockSize];
0882: DataPage s = DataPage.create(database, buff);
0883: database.setProgress(
0884: DatabaseEventListener.STATE_BACKUP_FILE,
0885: this .fileName, pos, fileBlockCount);
0886: go(pos);
0887: file.readFully(buff, 0, blockSize);
0888: s.reset();
0889: int blockCount = s.readInt();
0890: if (SysProperties.CHECK && blockCount < 0) {
0891: throw Message.getInternalError();
0892: }
0893: if (blockCount == 0) {
0894: blockCount = 1;
0895: }
0896: int id = s.readInt();
0897: if (SysProperties.CHECK && id < 0) {
0898: throw Message.getInternalError();
0899: }
0900: s.checkCapacity(blockCount * blockSize);
0901: if (blockCount > 1) {
0902: file.readFully(s.getBytes(), blockSize, blockCount
0903: * blockSize - blockSize);
0904: }
0905: if (file.isEncrypted()) {
0906: s.reset();
0907: go(pos);
0908: file.readFullyDirect(s.getBytes(), 0, blockCount
0909: * blockSize);
0910: }
0911: out.write(s.getBytes(), 0, blockCount * blockSize);
0912: return pos + blockCount;
0913: } catch (IOException e) {
0914: throw Message.convertIOException(e, fileName);
0915: }
0916: }
0917: }
0918:
0919: void removeRecord(Session session, int pos, Record record,
0920: int blockCount) throws SQLException {
0921: synchronized (database) {
0922: if (logChanges) {
0923: log.add(session, this , record);
0924: }
0925: cache.remove(pos);
0926: deleted.setRange(pos, blockCount, true);
0927: setUnused(session, pos, blockCount);
0928: }
0929: }
0930:
0931: void addRecord(Session session, Record record) throws SQLException {
0932: synchronized (database) {
0933: if (logChanges) {
0934: log.add(session, this , record);
0935: }
0936: cache.put(record);
0937: }
0938: }
0939:
0940: /*
0941: * Must be synchronized externally
0942: */
0943: public Cache getCache() {
0944: return cache;
0945: }
0946:
0947: void free(int pos, int blockCount) {
0948: synchronized (database) {
0949: used.setRange(pos, blockCount, false);
0950: }
0951: }
0952:
0953: public int getRecordOverhead() {
0954: return recordOverhead;
0955: }
0956:
0957: public void truncateStorage(Session session, Storage storage,
0958: IntArray pages) throws SQLException {
0959: synchronized (database) {
0960: int storageId = storage.getId();
0961: // make sure the cache records of this storage are not flushed to disk
0962: // afterwards
0963: ObjectArray list = cache.getAllChanged();
0964: for (int i = 0; i < list.size(); i++) {
0965: Record r = (Record) list.get(i);
0966: if (r.getStorageId() == storageId) {
0967: r.setChanged(false);
0968: }
0969: }
0970: int[] pagesCopy = new int[pages.size()];
0971: // can not use pages directly, because setUnused removes rows from there
0972: pages.toArray(pagesCopy);
0973: for (int i = 0; i < pagesCopy.length; i++) {
0974: int page = pagesCopy[i];
0975: if (logChanges) {
0976: log.addTruncate(session, this , storageId, page
0977: * BLOCKS_PER_PAGE, BLOCKS_PER_PAGE);
0978: }
0979: for (int j = 0; j < BLOCKS_PER_PAGE; j++) {
0980: Record r = (Record) cache.find(page
0981: * BLOCKS_PER_PAGE + j);
0982: if (r != null) {
0983: cache.remove(r.getPos());
0984: }
0985: }
0986: deleted.setRange(page * BLOCKS_PER_PAGE,
0987: BLOCKS_PER_PAGE, true);
0988: setUnused(session, page * BLOCKS_PER_PAGE,
0989: BLOCKS_PER_PAGE);
0990: }
0991: }
0992: }
0993:
0994: public void sync() {
0995: synchronized (database) {
0996: if (file != null) {
0997: file.sync();
0998: }
0999: }
1000: }
1001:
1002: public boolean isDataFile() {
1003: return dataFile;
1004: }
1005:
1006: public void setLogChanges(boolean b) {
1007: synchronized (database) {
1008: this .logChanges = b;
1009: }
1010: }
1011:
1012: /**
1013: * Add a redo-log entry to the redo buffer.
1014: *
1015: * @param storage the storage
1016: * @param recordId the record id of the entry
1017: * @param blockCount the number of blocks
1018: * @param rec the record
1019: */
1020: public void addRedoLog(Storage storage, int recordId,
1021: int blockCount, DataPage rec) throws SQLException {
1022: synchronized (database) {
1023: byte[] data = null;
1024: if (rec != null) {
1025: DataPage all = rowBuff;
1026: all.reset();
1027: all.writeInt(blockCount);
1028: all.writeInt(storage.getId());
1029: all.writeDataPageNoSize(rec);
1030: // the buffer may have some additional fillers - just ignore them
1031: all.fill(blockCount * BLOCK_SIZE);
1032: all.updateChecksum();
1033: if (SysProperties.CHECK
1034: && all.length() != BLOCK_SIZE * blockCount) {
1035: throw Message.getInternalError("blockCount:"
1036: + blockCount + " length: " + all.length()
1037: * BLOCK_SIZE);
1038: }
1039: data = new byte[all.length()];
1040: System.arraycopy(all.getBytes(), 0, data, 0, all
1041: .length());
1042: }
1043: for (int i = 0; i < blockCount; i++) {
1044: RedoLogRecord log = new RedoLogRecord();
1045: log.recordId = recordId + i;
1046: log.offset = i * BLOCK_SIZE;
1047: log.storage = storage;
1048: log.data = data;
1049: log.sequenceId = redoBuffer.size();
1050: redoBuffer.add(log);
1051: redoBufferSize += log.getSize();
1052: }
1053: if (redoBufferSize > SysProperties.REDO_BUFFER_SIZE) {
1054: flushRedoLog();
1055: }
1056: }
1057: }
1058:
1059: public void flushRedoLog() throws SQLException {
1060: synchronized (database) {
1061: if (redoBuffer.size() == 0) {
1062: return;
1063: }
1064: redoBuffer.sort(new Comparator() {
1065: public int compare(Object o1, Object o2) {
1066: RedoLogRecord e1 = (RedoLogRecord) o1;
1067: RedoLogRecord e2 = (RedoLogRecord) o2;
1068: int comp = e1.recordId - e2.recordId;
1069: if (comp == 0) {
1070: comp = e1.sequenceId - e2.sequenceId;
1071: }
1072: return comp;
1073: }
1074: });
1075: // first write all deleted entries
1076: RedoLogRecord last = null;
1077: for (int i = 0; i < redoBuffer.size(); i++) {
1078: RedoLogRecord entry = (RedoLogRecord) redoBuffer.get(i);
1079: if (entry.data != null) {
1080: continue;
1081: }
1082: if (last != null && entry.recordId != last.recordId) {
1083: writeRedoLog(last);
1084: }
1085: last = entry;
1086: }
1087: if (last != null) {
1088: writeRedoLog(last);
1089: }
1090: // now write the last entry, skipping deleted entries
1091: last = null;
1092: for (int i = 0; i < redoBuffer.size(); i++) {
1093: RedoLogRecord entry = (RedoLogRecord) redoBuffer.get(i);
1094: if (last != null && entry.recordId != last.recordId) {
1095: if (last.data != null) {
1096: writeRedoLog(last);
1097: }
1098: }
1099: last = entry;
1100: }
1101: if (last != null && last.data != null) {
1102: writeRedoLog(last);
1103: }
1104: redoBuffer.clear();
1105: redoBufferSize = 0;
1106: }
1107: }
1108:
1109: private void writeRedoLog(RedoLogRecord entry) throws SQLException {
1110: if (entry.data == null) {
1111: writeDirectDeleted(entry.recordId, 1);
1112: } else {
1113: writeDirect(entry.storage, entry.recordId, entry.data,
1114: entry.offset);
1115: }
1116: }
1117:
1118: public int getWriteCount() {
1119: return writeCount;
1120: }
1121:
1122: public int getReadCount() {
1123: return readCount;
1124: }
1125:
1126: public void flushLog() throws SQLException {
1127: if (log != null) {
1128: log.flush();
1129: }
1130: }
1131:
1132: public String toString() {
1133: return getClass().getName() + ":" + fileName;
1134: }
1135:
1136: }
|