store.java
来自「RESIN 3.2 最新源码」· Java 代码 · 共 1,996 行 · 第 1/4 页
JAVA
1,996 行
/* * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */package com.caucho.db.store;import com.caucho.db.Database;import com.caucho.lifecycle.Lifecycle;import com.caucho.log.Log;import com.caucho.sql.SQLExceptionWrapper;import com.caucho.util.L10N;import com.caucho.vfs.Path;import com.caucho.vfs.RandomAccessStream;import java.io.IOException;import java.lang.ref.SoftReference;import java.sql.SQLException;import java.util.logging.Level;import java.util.logging.Logger;/** * The store manages the block-based persistent store file. Each table * will have its own store file, table.db. * * The store is block-based, where each block is 64k. Block allocation * is tracked by a free block, block 0. Each block is represented as a * two-byte value. The first byte is the allocation code: free, row, * or used. The second byte is a fragment allocation mask. * * Since 64k stores 32k entries, the allocation block can handle * a 2G database size. If the database is larger, another free block * occurs at block 32k handling another 2G. * * The blocks are marked as free (00), row (01), used (10) or fragment(11). * Row-blocks are table rows, so a table iterator will only look at * the row blocks. Used blocks are for special blocks like the * free list. Fragments are for blobs. * * Each store has a unique id in the database. The store id is merged with * the block number in the store to create a unique block id. There are * 64k allowed stores (and therefore 64k tables), leaving 64 - 16 = 48 bits * for the blocks in a table, i.e. 2 ^ 48 blocks = 256T blocks. * * block index: the block number in the file. * * address: the address of a byte within the store, treating the file as a * flat file. * * block id: the unique id of the block in the database. * * <h3>Blobs and fragments</h3> * * Fragments are stored in 8k chunks with a single byte prefix indicating * its use. * * <h3>Transactions</h3> * * Fragments are not associated with transactions. The rollback is * associated with a transaction. */public class Store { private final static Logger log = Log.open(Store.class); private final static L10N L = new L10N(Store.class); public final static int BLOCK_BITS = 16; public final static int BLOCK_SIZE = 1 << BLOCK_BITS; public final static long BLOCK_INDEX_MASK = BLOCK_SIZE - 1; public final static long BLOCK_MASK = ~ BLOCK_INDEX_MASK; public final static long BLOCK_OFFSET_MASK = BLOCK_SIZE - 1; private final static int ALLOC_BYTES_PER_BLOCK = 2; private final static int ALLOC_CHUNK_SIZE = 1024 * ALLOC_BYTES_PER_BLOCK; public final static int ALLOC_FREE = 0x00; public final static int ALLOC_ROW = 0x01; public final static int ALLOC_USED = 0x02; public final static int ALLOC_FRAGMENT = 0x03; public final static int ALLOC_INDEX = 0x04; public final static int ALLOC_MINI_FRAG = 0x05; public final static int ALLOC_MASK = 0x0f; public final static int FRAGMENT_SIZE = 8 * 1024; public final static int FRAGMENT_PER_BLOCK = (int) (BLOCK_SIZE / FRAGMENT_SIZE); public final static int MINI_FRAG_SIZE = 256; public final static int MINI_FRAG_PER_BLOCK = (int) ((BLOCK_SIZE - 64) / MINI_FRAG_SIZE); public final static int MINI_FRAG_ALLOC_OFFSET = MINI_FRAG_PER_BLOCK * MINI_FRAG_SIZE; public final static long DATA_START = BLOCK_SIZE; public final static int STORE_CREATE_END = 1024; protected final Database _database; protected final BlockManager _blockManager; private final String _name; private int _id; private Path _path; // If true, dirty blocks are written at the end of the transaction. // Otherwise, they are buffered private boolean _isFlushDirtyBlocksOnCommit = true; private long _fileSize; private long _blockCount; private final Object _allocationLock = new Object(); private byte []_allocationTable; private final Object _allocationWriteLock = new Object(); private int _allocDirtyMin = Integer.MAX_VALUE; private int _allocDirtyMax; private final Object _fragmentLock = new Object(); private final Object _miniFragLock = new Object(); private final Object _statLock = new Object(); // number of fragments currently used private long _fragmentUseCount; // number of minifragments currently used private long _miniFragmentUseCount; private Object _fileLock = new Object(); private SoftReference<RandomAccessWrapper> _cachedRowFile; private Lock _rowLock; private boolean _isCorrupted; private final Lifecycle _lifecycle = new Lifecycle(); public Store(Database database, String name, Lock tableLock) { this(database, name, tableLock, database.getPath().lookup(name + ".db")); } /** * Creates a new store. * * @param database the owning database. * @param name the store name * @param lock the table lock * @param path the path to the files */ public Store(Database database, String name, Lock rowLock, Path path) { _database = database; _blockManager = _database.getBlockManager(); _name = name; _path = path; if (path == null) throw new NullPointerException(); _id = _blockManager.allocateStoreId(); if (rowLock == null) rowLock = new Lock("row-lock:" + _name + ":" + _id); _rowLock = rowLock; } /** * Creates an independent store. */ public static Store create(Path path) throws IOException, SQLException { Database db = new Database(); db.init(); Store store = new Store(db, "temp", null, path); if (path.canRead()) store.init(); else store.create(); return store; } /** * If true, dirty blocks are written at commit time. */ public void setFlushDirtyBlocksOnCommit(boolean flushOnCommit) { _isFlushDirtyBlocksOnCommit = flushOnCommit; } /** * If true, dirty blocks are written at commit time. */ public boolean isFlushDirtyBlocksOnCommit() { return _isFlushDirtyBlocksOnCommit; } /** * Returns the store's name. */ public String getName() { return _name; } /** * Returns the store's id. */ public int getId() { return _id; } /** * Returns the table's lock. */ public Lock getLock() { return _rowLock; } /** * Returns the block manager. */ public BlockManager getBlockManager() { return _blockManager; } public void setCorrupted(boolean isCorrupted) { _isCorrupted = isCorrupted; } public boolean isCorrupted() { return _isCorrupted; } /** * Returns the file size. */ public long getFileSize() { return _fileSize; } /** * Returns the block count. */ public long getBlockCount() { return _blockCount; } /** * Converts from the block index to the address for database * storage. */ private static long blockIndexToAddr(long blockIndex) { return blockIndex << BLOCK_BITS; } /** * Converts from the block index to the unique block id. */ private final long blockIndexToBlockId(long blockIndex) { return (blockIndex << BLOCK_BITS) + _id; } /** * Converts from the block index to the address for database * storage. */ private static long blockIdToIndex(long blockId) { return blockId >> BLOCK_BITS; } /** * Converts from the block index to the unique block id. */ public final long addressToBlockId(long address) { return (address & BLOCK_MASK) + _id; } /** * Converts from the block index to the unique block id. */ public static long blockIdToAddress(long blockId) { return (blockId & BLOCK_MASK); } /** * Converts from the block index to the unique block id. */ public static long blockIdToAddress(long blockId, int offset) { return (blockId & BLOCK_MASK) + offset; } /** * Returns the current number of fragments used. */ public long getTotalFragmentSize() { return _fragmentUseCount * FRAGMENT_SIZE; } /** * Creates the store. */ public void create() throws IOException, SQLException { if (! _lifecycle.toActive()) return; log.finer(this + " create"); _path.getParent().mkdirs(); if (_path.exists()) throw new SQLException(L.l("Table '{0}' already exists. CREATE can not override an existing table.", _name)); _allocationTable = new byte[ALLOC_CHUNK_SIZE]; // allocates the allocation table itself setAllocation(0, ALLOC_USED); // allocates the header information setAllocation(1, ALLOC_USED); byte []buffer = new byte[BLOCK_SIZE]; writeBlock(0, buffer, 0, BLOCK_SIZE); writeBlock(BLOCK_SIZE, buffer, 0, BLOCK_SIZE); writeBlock(0, _allocationTable, 0, _allocationTable.length); _blockCount = 2; } public void init() throws IOException { if (! _lifecycle.toActive()) return; log.finer(this + " init"); RandomAccessWrapper wrapper = openRowFile(); try { RandomAccessStream file = wrapper.getFile(); _fileSize = file.getLength(); _blockCount = ((_fileSize + BLOCK_SIZE - 1) / BLOCK_SIZE); int allocCount = (int) (_blockCount * ALLOC_BYTES_PER_BLOCK); allocCount += ALLOC_CHUNK_SIZE - allocCount % ALLOC_CHUNK_SIZE; _allocationTable = new byte[allocCount]; for (int i = 0; i < allocCount; i += BLOCK_SIZE) { int len = allocCount - i; if (BLOCK_SIZE < len) len = BLOCK_SIZE; readBlock((long) i / ALLOC_BYTES_PER_BLOCK * BLOCK_SIZE, _allocationTable, i, len); } } finally { wrapper.close(); } } public void remove() throws SQLException { try { Path path = _path; _path = null; close(); if (path != null) path.remove(); } catch (IOException e) { throw new SQLExceptionWrapper(e); } } /** * Returns the first block id which contains a row. * * @return the block id of the first row block */ public long firstRow(long blockId) throws IOException { return firstBlock(blockId, ALLOC_ROW); } /** * Returns the first block id which contains a fragment. * * @return the block id of the first row block */ public long firstFragment(long blockId) throws IOException { return firstBlock(blockId, ALLOC_FRAGMENT); } /** * Returns the first block id which contains a row. * * @return the block id of the first row block */ public long firstBlock(long blockId, int type) throws IOException { if (blockId <= BLOCK_SIZE) blockId = BLOCK_SIZE; long blockIndex = blockId >> BLOCK_BITS; synchronized (_allocationLock) { for (; blockIndex < _blockCount; blockIndex++) { if (getAllocation(blockIndex) == type) return blockIndexToBlockId(blockIndex); } } return -1; } /** * Returns the matching block. */ public final Block readBlock(long blockAddress) throws IOException { long blockId = addressToBlockId(blockAddress); Block block = _blockManager.getBlock(this, blockId); try { block.read(); return block; } catch (IOException e) { block.free(); throw e; } catch (RuntimeException e) { block.free(); throw e; } }
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?