001: /*
002: * Copyright 2004-2008 H2 Group. Licensed under the H2 License, Version 1.0
003: * (http://h2database.com/html/license.html).
004: * Initial Developer: H2 Group
005: */
006: package org.h2.store;
007:
008: import java.sql.SQLException;
009:
010: import org.h2.constant.SysProperties;
011: import org.h2.engine.Database;
012: import org.h2.engine.Session;
013: import org.h2.message.Message;
014: import org.h2.util.BitField;
015: import org.h2.util.IntArray;
016: import org.h2.util.MathUtils;
017:
018: /**
019: * This class represents an persistent container that stores data of a table or
020: * an index. An object contains a list of records, see {@link Record}. For each
021: * storage there is a {@link RecordReader} object that knows how to convert
022: * records into a byte array and vice versa. The data is stored in a
023: * {@link DiskFile}. A storage occupies a number of pages in a file.
024: * File format:
025: *
026: * <pre>
027: * int block size
028: * int storage id
029: * record data
030: * byte checksum
031: * [bytes * fillerLength]
032: * </pre>
033: */
034: public class Storage {
035:
036: public static final int ALLOCATE_POS = -1;
037: private static final int FREE_LIST_SIZE = Math.max(1024,
038: DiskFile.BLOCKS_PER_PAGE * 4);
039: private DiskFile file;
040: private int recordCount;
041: private RecordReader reader;
042: private IntArray freeList = new IntArray();
043: private IntArray pages = new IntArray();
044: private int id;
045: private Database database;
046: private DataPage dummy;
047: private int pageCheckIndex;
048:
049: public Storage(Database database, DiskFile file,
050: RecordReader reader, int id) {
051: this .database = database;
052: this .file = file;
053: this .reader = reader;
054: this .id = id;
055: dummy = DataPage.create(database, 0);
056: }
057:
058: public RecordReader getRecordReader() {
059: return reader;
060: }
061:
062: void incrementRecordCount() {
063: recordCount++;
064: }
065:
066: public Record getRecord(Session session, int pos)
067: throws SQLException {
068: return file.getRecord(session, pos, reader, id);
069: }
070:
071: public Record getRecordIfStored(Session session, int pos)
072: throws SQLException {
073: return file.getRecordIfStored(session, pos, reader, id);
074: }
075:
076: /**
077: * Gets the position of the next record.
078: * @param record the last record (null to get the first record)
079: * @return -1 if no record is found, otherwise the position
080: */
081: public int getNext(Record record) {
082: int next;
083: int lastCheckedPage;
084: int pageIndex = -1;
085: if (record == null) {
086: if (pages.size() == 0) {
087: return -1;
088: }
089: pageIndex = 0;
090: lastCheckedPage = pages.get(0);
091: next = lastCheckedPage * DiskFile.BLOCKS_PER_PAGE;
092: } else {
093: int blockCount = record.getBlockCount();
094: lastCheckedPage = file.getPage(record.getPos());
095: next = record.getPos() + blockCount;
096: }
097: synchronized (database) {
098: BitField used = file.getUsed();
099: while (true) {
100: int page = file.getPage(next);
101: if (lastCheckedPage != page) {
102: if (pageIndex < 0) {
103: pageIndex = pages.findNextValueIndex(page);
104: } else {
105: pageIndex++;
106: }
107: if (pageIndex >= pages.size()) {
108: return -1;
109: }
110: lastCheckedPage = pages.get(pageIndex);
111: next = Math.max(next, DiskFile.BLOCKS_PER_PAGE
112: * lastCheckedPage);
113: }
114: if (used.get(next)) {
115: return next;
116: }
117: if (used.getLong(next) == 0) {
118: next = MathUtils.roundUp(next + 1, 64);
119: } else {
120: next++;
121: }
122: }
123: }
124: }
125:
126: public void updateRecord(Session session, Record record)
127: throws SQLException {
128: record.setDeleted(false);
129: file.updateRecord(session, record);
130: }
131:
132: public void addRecord(Session session, Record record, int pos)
133: throws SQLException {
134: record.setStorageId(id);
135: int size = file.getRecordOverhead()
136: + record.getByteCount(dummy);
137: size = MathUtils.roundUp(size, DiskFile.BLOCK_SIZE);
138: record.setDeleted(false);
139: int blockCount = size / DiskFile.BLOCK_SIZE;
140: if (pos == ALLOCATE_POS) {
141: pos = allocate(blockCount);
142: } else {
143: file.setUsed(pos, blockCount);
144: }
145: record.setPos(pos);
146: record.setBlockCount(blockCount);
147: record.setChanged(true);
148: recordCount++;
149: file.addRecord(session, record);
150: }
151:
152: public void removeRecord(Session session, int pos)
153: throws SQLException {
154: checkOnePage();
155: Record record = getRecord(session, pos);
156: if (SysProperties.CHECK && record.getDeleted()) {
157: throw Message.getInternalError("duplicate delete " + pos);
158: }
159: record.setDeleted(true);
160: int blockCount = record.getBlockCount();
161: file.uncommittedDelete(session);
162: free(pos, blockCount);
163: recordCount--;
164: file.removeRecord(session, pos, record, blockCount);
165: }
166:
167: private boolean isFreeAndMine(int pos, int blocks) {
168: synchronized (database) {
169: BitField used = file.getUsed();
170: for (int i = blocks + pos - 1; i >= pos; i--) {
171: if (file.getPageOwner(file.getPage(i)) != id
172: || used.get(i)) {
173: return false;
174: }
175: }
176: return true;
177: }
178: }
179:
180: public int allocate(int blockCount) throws SQLException {
181: if (freeList.size() > 0) {
182: synchronized (database) {
183: BitField used = file.getUsed();
184: for (int i = 0; i < freeList.size(); i++) {
185: int px = freeList.get(i);
186: if (used.get(px)) {
187: // sometime there may stay some entries in the freeList
188: // that are not free (free 2, free 1, allocate 1+2)
189: // these entries are removed right here
190: freeList.remove(i--);
191: } else {
192: if (isFreeAndMine(px, blockCount)) {
193: int pos = px;
194: freeList.remove(i--);
195: file.setUsed(pos, blockCount);
196: return pos;
197: }
198: }
199: }
200: }
201: }
202: int pos = file.allocate(this , blockCount);
203: file.setUsed(pos, blockCount);
204: return pos;
205: }
206:
207: void free(int pos, int blockCount) {
208: file.free(pos, blockCount);
209: if (freeList.size() < FREE_LIST_SIZE) {
210: freeList.add(pos);
211: }
212: }
213:
214: public void delete(Session session) throws SQLException {
215: truncate(session);
216: }
217:
218: // private int allocateBest(int start, int blocks) {
219: // while (true) {
220: // int p = getLastUsedPlusOne(start, blocks);
221: // if (p == start) {
222: // start = p;
223: // break;
224: // }
225: // start = p;
226: // }
227: // allocate(start, blocks);
228: // return start;
229: // }
230:
231: public int getId() {
232: return id;
233: }
234:
235: public int getRecordCount() {
236: return recordCount;
237: }
238:
239: public void truncate(Session session) throws SQLException {
240: freeList = new IntArray();
241: recordCount = 0;
242: file.truncateStorage(session, this , pages);
243: }
244:
245: public void setReader(RecordReader reader) {
246: this .reader = reader;
247: }
248:
249: public void flushRecord(Record rec) throws SQLException {
250: file.writeBack(rec);
251: }
252:
253: public void flushFile() throws SQLException {
254: file.flush();
255: }
256:
257: public int getRecordOverhead() {
258: return file.getRecordOverhead();
259: }
260:
261: public DiskFile getDiskFile() {
262: return file;
263: }
264:
265: public void setRecordCount(int recordCount) {
266: this .recordCount = recordCount;
267: }
268:
269: void addPage(int i) {
270: pages.addValueSorted(i);
271: }
272:
273: void removePage(int i) {
274: pages.removeValue(i);
275: }
276:
277: private void checkOnePage() throws SQLException {
278: pageCheckIndex = (pageCheckIndex + 1) % pages.size();
279: int page = pages.get(pageCheckIndex);
280: if (file.isPageFree(page) && file.getPageOwner(page) == id) {
281: // file.freePage(page);
282: }
283: }
284:
285: }
|