EMMA Coverage Report (generated Sun Mar 01 22:06:14 CET 2015)
[all classes][org.h2.store]

COVERAGE SUMMARY FOR SOURCE FILE [PageStore.java]

nameclass, %method, %block, %line, %
PageStore.java100% (1/1)77%  (72/93)64%  (3002/4695)69%  (694/1009)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class PageStore100% (1/1)77%  (72/93)64%  (3002/4695)69%  (694/1009)
allocateIfIndexRoot (int, int, Row): void 0%   (0/1)0%   (0/23)0%   (0/6)
copyDirect (int, OutputStream): int 0%   (0/1)0%   (0/41)0%   (0/8)
getCache (): Cache 0%   (0/1)0%   (0/3)0%   (0/1)
getInDoubtTransactions (): ArrayList 0%   (0/1)0%   (0/4)0%   (0/1)
getReadCount (): long 0%   (0/1)0%   (0/3)0%   (0/1)
getWriteCount (): long 0%   (0/1)0%   (0/3)0%   (0/1)
logTruncate (Session, int): void 0%   (0/1)0%   (0/11)0%   (0/4)
prepareCommit (Session, String): void 0%   (0/1)0%   (0/6)0%   (0/2)
redo (int, Row, boolean): void 0%   (0/1)0%   (0/59)0%   (0/12)
redoDelete (int, long): void 0%   (0/1)0%   (0/46)0%   (0/8)
redoTruncate (int): void 0%   (0/1)0%   (0/15)0%   (0/4)
removeFromCache (int): void 0%   (0/1)0%   (0/6)0%   (0/2)
removeMeta (Row): void 0%   (0/1)0%   (0/45)0%   (0/10)
setBackup (boolean): void 0%   (0/1)0%   (0/11)0%   (0/2)
setInDoubtTransactionState (int, int, boolean): void 0%   (0/1)0%   (0/27)0%   (0/6)
setMaxLogSize (long): void 0%   (0/1)0%   (0/4)0%   (0/2)
statisticsEnd (): HashMap 0%   (0/1)0%   (0/8)0%   (0/3)
statisticsIncrement (String): void 0%   (0/1)0%   (0/24)0%   (0/4)
statisticsStart (): void 0%   (0/1)0%   (0/4)0%   (0/2)
swap (int, int, int): void 0%   (0/1)0%   (0/135)0%   (0/26)
sync (): void 0%   (0/1)0%   (0/10)0%   (0/4)
commit (Session): void 100% (1/1)28%  (24/86)32%  (6/19)
removeOldTempIndexes (): void 100% (1/1)29%  (15/52)33%  (4/12)
getPage (int): Page 100% (1/1)30%  (113/376)49%  (34/69)
setLogFirstPage (int, int, int): void 100% (1/1)46%  (16/35)86%  (6/7)
readPage (int, Data): void 100% (1/1)47%  (32/68)60%  (6/10)
compact (int, int): boolean 100% (1/1)47%  (57/121)56%  (10.2/18)
open (): void 100% (1/1)48%  (24/50)60%  (9/15)
removeMetaIndex (Index, Session): void 100% (1/1)50%  (24/48)83%  (5/6)
free (int, boolean): void 100% (1/1)51%  (31/61)62%  (8/13)
compact (int): void 100% (1/1)53%  (283/532)54%  (67.5/126)
writeBack (CacheObject): void 100% (1/1)54%  (13/24)83%  (5/6)
checkOpen (): void 100% (1/1)57%  (4/7)67%  (2/3)
openExisting (): void 100% (1/1)60%  (86/143)63%  (20.7/33)
freeUnused (int): void 100% (1/1)61%  (17/28)83%  (5/6)
lockFile (): void 100% (1/1)62%  (8/13)75%  (3/4)
readStaticHeader (): void 100% (1/1)62%  (40/64)67%  (10/15)
setPageSize (int): void 100% (1/1)68%  (40/59)88%  (14/16)
recover (): boolean 100% (1/1)70%  (130/185)77%  (33.9/44)
update (Page): void 100% (1/1)72%  (38/53)85%  (11/13)
removeMeta (Index, Session): void 100% (1/1)76%  (47/62)88%  (11.5/13)
addMeta (Row, Session, boolean): void 100% (1/1)78%  (283/365)86%  (58.5/68)
close (): void 100% (1/1)82%  (27/33)96%  (9.6/10)
incrementChangeCount (): void 100% (1/1)82%  (14/17)75%  (3/4)
writePage (int, Data): void 100% (1/1)83%  (60/72)85%  (11/13)
addMeta (PageIndex, Session): void 100% (1/1)85%  (188/221)90%  (36/40)
getFirstUncommittedSection (): int 100% (1/1)88%  (37/42)78%  (7/9)
checkpoint (): void 100% (1/1)88%  (120/136)90%  (26/29)
readVariableHeader (): void 100% (1/1)89%  (55/62)92%  (15.7/17)
getFirstFree (int): int 100% (1/1)92%  (22/24)97%  (5.8/6)
allocatePage (BitField, int): int 100% (1/1)94%  (31/33)96%  (9.6/10)
increaseFileSize (): void 100% (1/1)95%  (35/37)89%  (8/9)
getFreeList (int): PageFreeList 100% (1/1)96%  (66/69)94%  (16/17)
writeVariableHeader (): void 100% (1/1)96%  (81/84)94%  (16/17)
logUndo (Page, Data): void 100% (1/1)97%  (33/34)92%  (11/12)
checksumTest (byte [], int, int): boolean 100% (1/1)98%  (116/118)90%  (9/10)
checksumSet (byte [], int): void 100% (1/1)99%  (118/119)92%  (12/13)
PageStore (Database, String, String, int): void 100% (1/1)100% (62/62)100% (17/17)
addIndex (PageIndex): void 100% (1/1)100% (9/9)100% (2/2)
allocatePage (): int 100% (1/1)100% (21/21)100% (6/6)
allocatePage (int): void 100% (1/1)100% (8/8)100% (3/3)
allocatePages (IntArray, int, BitField, int): void 100% (1/1)100% (24/24)100% (6/6)
createData (): Data 100% (1/1)100% (7/7)100% (1/1)
flushLog (): void 100% (1/1)100% (7/7)100% (3/3)
free (int): void 100% (1/1)100% (5/5)100% (2/2)
freePage (int): void 100% (1/1)100% (18/18)100% (5/5)
getChangeCount (): long 100% (1/1)100% (3/3)100% (1/1)
getDatabase (): Database 100% (1/1)100% (3/3)100% (1/1)
getFreeListForPage (int): PageFreeList 100% (1/1)100% (6/6)100% (1/1)
getFreeListId (int): int 100% (1/1)100% (7/7)100% (1/1)
getLogMode (): int 100% (1/1)100% (3/3)100% (1/1)
getObjectIds (): BitField 100% (1/1)100% (31/31)100% (9/9)
getPageCount (): int 100% (1/1)100% (3/3)100% (1/1)
getPageSize (): int 100% (1/1)100% (3/3)100% (1/1)
getPageStoreSession (): Session 100% (1/1)100% (3/3)100% (1/1)
getRootPageId (int): int 100% (1/1)100% (5/5)100% (1/1)
getTrace (): Trace 100% (1/1)100% (3/3)100% (1/1)
getWriteCountTotal (): long 100% (1/1)100% (6/6)100% (1/1)
increaseFileSize (int): void 100% (1/1)100% (42/42)100% (8/8)
isNew (): boolean 100% (1/1)100% (3/3)100% (1/1)
isRecoveryRunning (): boolean 100% (1/1)100% (3/3)100% (1/1)
isUsed (int): boolean 100% (1/1)100% (6/6)100% (1/1)
logAddOrRemoveRow (Session, int, Row, boolean): void 100% (1/1)100% (14/14)100% (4/4)
openForWriting (): void 100% (1/1)100% (40/40)100% (11/11)
openMetaIndex (): void 100% (1/1)100% (118/118)100% (22/22)
openNew (): void 100% (1/1)100% (58/58)100% (16/16)
readMetaData (): void 100% (1/1)100% (57/57)100% (15/15)
readPage (int): Data 100% (1/1)100% (9/9)100% (3/3)
setLockFile (boolean): void 100% (1/1)100% (4/4)100% (2/2)
setLogMode (int): void 100% (1/1)100% (4/4)100% (2/2)
writeBack (): void 100% (1/1)100% (23/23)100% (5/5)
writeIndexRowCounts (): void 100% (1/1)100% (16/16)100% (4/4)
writeStaticHeader (): void 100% (1/1)100% (40/40)100% (8/8)

1/*
2 * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
3 * and the EPL 1.0 (http://h2database.com/html/license.html).
4 * Initial Developer: H2 Group
5 */
6package org.h2.store;
7 
8import java.io.IOException;
9import java.io.OutputStream;
10import java.util.ArrayList;
11import java.util.Collections;
12import java.util.HashMap;
13import java.util.zip.CRC32;
14 
15import org.h2.api.ErrorCode;
16import org.h2.command.CommandInterface;
17import org.h2.command.ddl.CreateTableData;
18import org.h2.engine.Constants;
19import org.h2.engine.Database;
20import org.h2.engine.Session;
21import org.h2.engine.SysProperties;
22import org.h2.index.Cursor;
23import org.h2.index.Index;
24import org.h2.index.IndexType;
25import org.h2.index.MultiVersionIndex;
26import org.h2.index.PageBtreeIndex;
27import org.h2.index.PageBtreeLeaf;
28import org.h2.index.PageBtreeNode;
29import org.h2.index.PageDataIndex;
30import org.h2.index.PageDataLeaf;
31import org.h2.index.PageDataNode;
32import org.h2.index.PageDataOverflow;
33import org.h2.index.PageDelegateIndex;
34import org.h2.index.PageIndex;
35import org.h2.message.DbException;
36import org.h2.message.Trace;
37import org.h2.result.Row;
38import org.h2.schema.Schema;
39import org.h2.store.fs.FileUtils;
40import org.h2.table.Column;
41import org.h2.table.IndexColumn;
42import org.h2.table.RegularTable;
43import org.h2.table.Table;
44import org.h2.util.BitField;
45import org.h2.util.Cache;
46import org.h2.util.CacheLRU;
47import org.h2.util.CacheObject;
48import org.h2.util.CacheWriter;
49import org.h2.util.IntArray;
50import org.h2.util.IntIntHashMap;
51import org.h2.util.New;
52import org.h2.util.StatementBuilder;
53import org.h2.util.StringUtils;
54import org.h2.value.CompareMode;
55import org.h2.value.Value;
56import org.h2.value.ValueInt;
57import org.h2.value.ValueString;
58 
59/**
60 * This class represents a file that is organized as a number of pages. Page 0
61 * contains a static file header, and pages 1 and 2 both contain the variable
62 * file header (page 2 is a copy of page 1 and is only read if the checksum of
63 * page 1 is invalid). The format of page 0 is:
64 * <ul>
65 * <li>0-47: file header (3 time "-- H2 0.5/B -- \n")</li>
66 * <li>48-51: page size in bytes (512 - 32768, must be a power of 2)</li>
67 * <li>52: write version (read-only if larger than 1)</li>
68 * <li>53: read version (opening fails if larger than 1)</li>
69 * </ul>
70 * The format of page 1 and 2 is:
71 * <ul>
72 * <li>CRC32 of the remaining data: int (0-3)</li>
73 * <li>write counter (incremented on each write): long (4-11)</li>
74 * <li>log trunk key: int (12-15)</li>
75 * <li>log trunk page (0 for none): int (16-19)</li>
76 * <li>log data page (0 for none): int (20-23)</li>
77 * </ul>
78 * Page 3 contains the first free list page.
79 * Page 4 contains the meta table root page.
80 */
81public class PageStore implements CacheWriter {
82 
83    // TODO test running out of disk space (using a special file system)
84    // TODO unused pages should be freed once in a while
85    // TODO node row counts are incorrect (it's not splitting row counts)
86    // TODO after opening the database, delay writing until required
87    // TODO optimization: try to avoid allocating a byte array per page
88    // TODO optimization: check if calling Data.getValueLen slows things down
89    // TODO order pages so that searching for a key only seeks forward
90    // TODO optimization: update: only log the key and changed values
91    // TODO index creation: use less space (ordered, split at insertion point)
92    // TODO detect circles in linked lists
93    // (input stream, free list, extend pages...)
94    // at runtime and recovery
95    // TODO remove trace or use isDebugEnabled
96    // TODO recover tool: support syntax to delete a row with a key
97    // TODO don't store default values (store a special value)
98    // TODO check for file size (exception if not exact size expected)
99    // TODO online backup using bsdiff
100 
101    /**
102     * The smallest possible page size.
103     */
104    public static final int PAGE_SIZE_MIN = 64;
105 
106    /**
107     * The biggest possible page size.
108     */
109    public static final int PAGE_SIZE_MAX = 32768;
110 
111    /**
112     * This log mode means the transaction log is not used.
113     */
114    public static final int LOG_MODE_OFF = 0;
115 
116    /**
117     * This log mode means the transaction log is used and FileDescriptor.sync()
118     * is called for each checkpoint. This is the default level.
119     */
120    public static final int LOG_MODE_SYNC = 2;
121    private static final int PAGE_ID_FREE_LIST_ROOT = 3;
122    private static final int PAGE_ID_META_ROOT = 4;
123    private static final int MIN_PAGE_COUNT = 5;
124    private static final int INCREMENT_KB = 1024;
125    private static final int INCREMENT_PERCENT_MIN = 35;
126    private static final int READ_VERSION = 3;
127    private static final int WRITE_VERSION = 3;
128    private static final int META_TYPE_DATA_INDEX = 0;
129    private static final int META_TYPE_BTREE_INDEX = 1;
130    private static final int META_TABLE_ID = -1;
131    private static final int COMPACT_BLOCK_SIZE = 1536;
132    private final Database database;
133    private final Trace trace;
134    private final String fileName;
135    private FileStore file;
136    private String accessMode;
137    private int pageSize = Constants.DEFAULT_PAGE_SIZE;
138    private int pageSizeShift;
139    private long writeCountBase, writeCount, readCount;
140    private int logKey, logFirstTrunkPage, logFirstDataPage;
141    private final Cache cache;
142    private int freeListPagesPerList;
143    private boolean recoveryRunning;
144    private boolean ignoreBigLog;
145 
146    /**
147     * The index to the first free-list page that potentially has free space.
148     */
149    private int firstFreeListIndex;
150 
151    /**
152     * The file size in bytes.
153     */
154    private long fileLength;
155 
156    /**
157     * Number of pages (including free pages).
158     */
159    private int pageCount;
160 
161    private PageLog log;
162    private Schema metaSchema;
163    private RegularTable metaTable;
164    private PageDataIndex metaIndex;
165    private final IntIntHashMap metaRootPageId = new IntIntHashMap();
166    private final HashMap<Integer, PageIndex> metaObjects = New.hashMap();
167    private HashMap<Integer, PageIndex> tempObjects;
168 
169    /**
170     * The map of reserved pages, to ensure index head pages
171     * are not used for regular data during recovery. The key is the page id,
172     * and the value the latest transaction position where this page is used.
173     */
174    private HashMap<Integer, Integer> reservedPages;
175    private boolean isNew;
176    private long maxLogSize = Constants.DEFAULT_MAX_LOG_SIZE;
177    private final Session pageStoreSession;
178 
179    /**
180     * Each free page is marked with a set bit.
181     */
182    private final BitField freed = new BitField();
183    private final ArrayList<PageFreeList> freeLists = New.arrayList();
184 
185    private boolean recordPageReads;
186    private ArrayList<Integer> recordedPagesList;
187    private IntIntHashMap recordedPagesIndex;
188 
189    /**
190     * The change count is something like a "micro-transaction-id".
191     * It is used to ensure that changed pages are not written to the file
192     * before the the current operation is not finished. This is only a problem
193     * when using a very small cache size. The value starts at 1 so that
194     * pages with change count 0 can be evicted from the cache.
195     */
196    private long changeCount = 1;
197 
198    private Data emptyPage;
199    private long logSizeBase;
200    private HashMap<String, Integer> statistics;
201    private int logMode = LOG_MODE_SYNC;
202    private boolean lockFile;
203    private boolean readMode;
204    private int backupLevel;
205 
206    /**
207     * Create a new page store object.
208     *
209     * @param database the database
210     * @param fileName the file name
211     * @param accessMode the access mode
212     * @param cacheSizeDefault the default cache size
213     */
214    public PageStore(Database database, String fileName, String accessMode,
215            int cacheSizeDefault) {
216        this.fileName = fileName;
217        this.accessMode = accessMode;
218        this.database = database;
219        trace = database.getTrace(Trace.PAGE_STORE);
220        // if (fileName.endsWith("X.h2.db"))
221        // trace.setLevel(TraceSystem.DEBUG);
222        String cacheType = database.getCacheType();
223        this.cache = CacheLRU.getCache(this, cacheType, cacheSizeDefault);
224        pageStoreSession = new Session(database, null, 0);
225    }
226 
227    /**
228     * Start collecting statistics.
229     */
230    public void statisticsStart() {
231        statistics = New.hashMap();
232    }
233 
234    /**
235     * Stop collecting statistics.
236     *
237     * @return the statistics
238     */
239    public HashMap<String, Integer> statisticsEnd() {
240        HashMap<String, Integer> result = statistics;
241        statistics = null;
242        return result;
243    }
244 
245    private void statisticsIncrement(String key) {
246        if (statistics != null) {
247            Integer old = statistics.get(key);
248            statistics.put(key, old == null ? 1 : old + 1);
249        }
250    }
251 
252    /**
253     * Copy the next page to the output stream.
254     *
255     * @param pageId the page to copy
256     * @param out the output stream
257     * @return the new position, or -1 if there is no more data to copy
258     */
259    public synchronized int copyDirect(int pageId, OutputStream out)
260            throws IOException {
261        byte[] buffer = new byte[pageSize];
262        if (pageId >= pageCount) {
263            return -1;
264        }
265        file.seek((long) pageId << pageSizeShift);
266        file.readFullyDirect(buffer, 0, pageSize);
267        readCount++;
268        out.write(buffer, 0, pageSize);
269        return pageId + 1;
270    }
271 
272    /**
273     * Open the file and read the header.
274     */
275    public synchronized void open() {
276        try {
277            metaRootPageId.put(META_TABLE_ID, PAGE_ID_META_ROOT);
278            if (FileUtils.exists(fileName)) {
279                long length = FileUtils.size(fileName);
280                if (length < MIN_PAGE_COUNT * PAGE_SIZE_MIN) {
281                    if (database.isReadOnly()) {
282                        throw DbException.get(
283                                ErrorCode.FILE_CORRUPTED_1, fileName + " length: " + length);
284                    }
285                    // the database was not fully created
286                    openNew();
287                } else {
288                    openExisting();
289                }
290            } else {
291                openNew();
292            }
293        } catch (DbException e) {
294            close();
295            throw e;
296        }
297    }
298 
299    private void openNew() {
300        setPageSize(pageSize);
301        freeListPagesPerList = PageFreeList.getPagesAddressed(pageSize);
302        file = database.openFile(fileName, accessMode, false);
303        lockFile();
304        recoveryRunning = true;
305        writeStaticHeader();
306        writeVariableHeader();
307        log = new PageLog(this);
308        increaseFileSize(MIN_PAGE_COUNT);
309        openMetaIndex();
310        logFirstTrunkPage = allocatePage();
311        log.openForWriting(logFirstTrunkPage, false);
312        isNew = true;
313        recoveryRunning = false;
314        increaseFileSize();
315    }
316 
317    private void lockFile() {
318        if (lockFile) {
319            if (!file.tryLock()) {
320                throw DbException.get(
321                        ErrorCode.DATABASE_ALREADY_OPEN_1, fileName);
322            }
323        }
324    }
325 
326    private void openExisting() {
327        try {
328            file = database.openFile(fileName, accessMode, true);
329        } catch (DbException e) {
330            if (e.getErrorCode() == ErrorCode.IO_EXCEPTION_2) {
331                if (e.getMessage().contains("locked")) {
332                    // in Windows, you can't open a locked file
333                    // (in other operating systems, you can)
334                    // the exact error message is:
335                    // "The process cannot access the file because
336                    // another process has locked a portion of the file"
337                    throw DbException.get(
338                            ErrorCode.DATABASE_ALREADY_OPEN_1, e, fileName);
339                }
340            }
341            throw e;
342        }
343        lockFile();
344        readStaticHeader();
345        freeListPagesPerList = PageFreeList.getPagesAddressed(pageSize);
346        fileLength = file.length();
347        pageCount = (int) (fileLength / pageSize);
348        if (pageCount < MIN_PAGE_COUNT) {
349            if (database.isReadOnly()) {
350                throw DbException.get(ErrorCode.FILE_CORRUPTED_1,
351                        fileName + " pageCount: " + pageCount);
352            }
353            file.releaseLock();
354            file.close();
355            FileUtils.delete(fileName);
356            openNew();
357            return;
358        }
359        readVariableHeader();
360        log = new PageLog(this);
361        log.openForReading(logKey, logFirstTrunkPage, logFirstDataPage);
362        boolean old = database.isMultiVersion();
363        // temporarily disabling multi-version concurrency, because
364        // the multi-version index sometimes compares rows
365        // and the LOB storage is not yet available.
366        database.setMultiVersion(false);
367        boolean isEmpty = recover();
368        database.setMultiVersion(old);
369        if (!database.isReadOnly()) {
370            readMode = true;
371            if (!isEmpty || !SysProperties.MODIFY_ON_WRITE || tempObjects != null) {
372                openForWriting();
373                removeOldTempIndexes();
374            }
375        }
376    }
377 
378    private void openForWriting() {
379        if (!readMode || database.isReadOnly()) {
380            return;
381        }
382        readMode = false;
383        recoveryRunning = true;
384        log.free();
385        logFirstTrunkPage = allocatePage();
386        log.openForWriting(logFirstTrunkPage, false);
387        recoveryRunning = false;
388        freed.set(0, pageCount, true);
389        checkpoint();
390    }
391 
392    private void removeOldTempIndexes() {
393        if (tempObjects != null) {
394            metaObjects.putAll(tempObjects);
395            for (PageIndex index: tempObjects.values()) {
396                if (index.getTable().isTemporary()) {
397                    index.truncate(pageStoreSession);
398                    index.remove(pageStoreSession);
399                }
400            }
401            pageStoreSession.commit(true);
402            tempObjects = null;
403        }
404        metaObjects.clear();
405        metaObjects.put(-1, metaIndex);
406    }
407 
408    private void writeIndexRowCounts() {
409        for (PageIndex index: metaObjects.values()) {
410            index.writeRowCount();
411        }
412    }
413 
414    private void writeBack() {
415        ArrayList<CacheObject> list = cache.getAllChanged();
416        Collections.sort(list);
417        for (int i = 0, size = list.size(); i < size; i++) {
418            writeBack(list.get(i));
419        }
420    }
421 
422    /**
423     * Flush all pending changes to disk, and switch the new transaction log.
424     */
425    public synchronized void checkpoint() {
426        trace.debug("checkpoint");
427        if (log == null || readMode || database.isReadOnly() || backupLevel > 0) {
428            // the file was never fully opened, or is read-only,
429            // or checkpoint is currently disabled
430            return;
431        }
432        database.checkPowerOff();
433        writeIndexRowCounts();
434 
435        log.checkpoint();
436        writeBack();
437 
438        int firstUncommittedSection = getFirstUncommittedSection();
439 
440        log.removeUntil(firstUncommittedSection);
441 
442        // write back the free list
443        writeBack();
444 
445        // ensure the free list is backed up again
446        log.checkpoint();
447 
448        if (trace.isDebugEnabled()) {
449            trace.debug("writeFree");
450        }
451        byte[] test = new byte[16];
452        byte[] empty = new byte[pageSize];
453        for (int i = PAGE_ID_FREE_LIST_ROOT; i < pageCount; i++) {
454            if (isUsed(i)) {
455                freed.clear(i);
456            } else if (!freed.get(i)) {
457                if (trace.isDebugEnabled()) {
458                    trace.debug("free " + i);
459                }
460                file.seek((long) i << pageSizeShift);
461                file.readFully(test, 0, 16);
462                if (test[0] != 0) {
463                    file.seek((long) i << pageSizeShift);
464                    file.write(empty, 0, pageSize);
465                    writeCount++;
466                }
467                freed.set(i);
468            }
469        }
470    }
471 
472    /**
473     * Shrink the file so there are no empty pages at the end.
474     *
475     * @param compactMode 0 if no compacting should happen, otherwise
476     * TransactionCommand.SHUTDOWN_COMPACT or TransactionCommand.SHUTDOWN_DEFRAG
477     */
478    public synchronized void compact(int compactMode) {
479        if (!database.getSettings().pageStoreTrim) {
480            return;
481        }
482        if (SysProperties.MODIFY_ON_WRITE && readMode &&
483                compactMode == 0) {
484            return;
485        }
486        openForWriting();
487        // find the last used page
488        int lastUsed = -1;
489        for (int i = getFreeListId(pageCount); i >= 0; i--) {
490            lastUsed = getFreeList(i).getLastUsed();
491            if (lastUsed != -1) {
492                break;
493            }
494        }
495        // open a new log at the very end
496        // (to be truncated later)
497        writeBack();
498        log.free();
499        recoveryRunning = true;
500        try {
501            logFirstTrunkPage = lastUsed + 1;
502            allocatePage(logFirstTrunkPage);
503            log.openForWriting(logFirstTrunkPage, true);
504            // ensure the free list is backed up again
505            log.checkpoint();
506        } finally {
507            recoveryRunning = false;
508        }
509        long start = System.currentTimeMillis();
510        boolean isCompactFully = compactMode ==
511                CommandInterface.SHUTDOWN_COMPACT;
512        boolean isDefrag = compactMode ==
513                CommandInterface.SHUTDOWN_DEFRAG;
514 
515        if (database.getSettings().defragAlways) {
516            isCompactFully = isDefrag = true;
517        }
518 
519        int maxCompactTime = database.getSettings().maxCompactTime;
520        int maxMove = database.getSettings().maxCompactCount;
521 
522        if (isCompactFully || isDefrag) {
523            maxCompactTime = Integer.MAX_VALUE;
524            maxMove = Integer.MAX_VALUE;
525        }
526        int blockSize = isCompactFully ? COMPACT_BLOCK_SIZE : 1;
527        int firstFree = MIN_PAGE_COUNT;
528        for (int x = lastUsed, j = 0; x > MIN_PAGE_COUNT &&
529                j < maxMove; x -= blockSize) {
530            for (int full = x - blockSize + 1; full <= x; full++) {
531                if (full > MIN_PAGE_COUNT && isUsed(full)) {
532                    synchronized (this) {
533                        firstFree = getFirstFree(firstFree);
534                        if (firstFree == -1 || firstFree >= full) {
535                            j = maxMove;
536                            break;
537                        }
538                        if (compact(full, firstFree)) {
539                            j++;
540                            long now = System.currentTimeMillis();
541                            if (now > start + maxCompactTime) {
542                                j = maxMove;
543                                break;
544                            }
545                        }
546                    }
547                }
548            }
549        }
550        if (isDefrag) {
551            log.checkpoint();
552            writeBack();
553            cache.clear();
554            ArrayList<Table> tables = database.getAllTablesAndViews(false);
555            recordedPagesList = New.arrayList();
556            recordedPagesIndex = new IntIntHashMap();
557            recordPageReads = true;
558            Session sysSession = database.getSystemSession();
559            for (Table table : tables) {
560                if (!table.isTemporary() && Table.TABLE.equals(table.getTableType())) {
561                    Index scanIndex = table.getScanIndex(sysSession);
562                    Cursor cursor = scanIndex.find(sysSession, null, null);
563                    while (cursor.next()) {
564                        cursor.get();
565                    }
566                    for (Index index : table.getIndexes()) {
567                        if (index != scanIndex && index.canScan()) {
568                            cursor = index.find(sysSession, null, null);
569                            while (cursor.next()) {
570                                // the data is already read
571                            }
572                        }
573                    }
574                }
575            }
576            recordPageReads = false;
577            int target = MIN_PAGE_COUNT - 1;
578            int temp = 0;
579            for (int i = 0, size = recordedPagesList.size(); i < size; i++) {
580                log.checkpoint();
581                writeBack();
582                int source = recordedPagesList.get(i);
583                Page pageSource = getPage(source);
584                if (!pageSource.canMove()) {
585                    continue;
586                }
587                while (true) {
588                    Page pageTarget = getPage(++target);
589                    if (pageTarget == null || pageTarget.canMove()) {
590                        break;
591                    }
592                }
593                if (target == source) {
594                    continue;
595                }
596                temp = getFirstFree(temp);
597                if (temp == -1) {
598                    DbException.throwInternalError("no free page for defrag");
599                }
600                cache.clear();
601                swap(source, target, temp);
602                int index = recordedPagesIndex.get(target);
603                if (index != IntIntHashMap.NOT_FOUND) {
604                    recordedPagesList.set(index, source);
605                    recordedPagesIndex.put(source, index);
606                }
607                recordedPagesList.set(i, target);
608                recordedPagesIndex.put(target, i);
609            }
610            recordedPagesList = null;
611            recordedPagesIndex = null;
612        }
613        // TODO can most likely be simplified
614        checkpoint();
615        log.checkpoint();
616        writeIndexRowCounts();
617        log.checkpoint();
618        writeBack();
619        commit(pageStoreSession);
620        writeBack();
621        log.checkpoint();
622 
623        log.free();
624        // truncate the log
625        recoveryRunning = true;
626        try {
627            setLogFirstPage(++logKey, 0, 0);
628        } finally {
629            recoveryRunning = false;
630        }
631        writeBack();
632        for (int i = getFreeListId(pageCount); i >= 0; i--) {
633            lastUsed = getFreeList(i).getLastUsed();
634            if (lastUsed != -1) {
635                break;
636            }
637        }
638        int newPageCount = lastUsed + 1;
639        if (newPageCount < pageCount) {
640            freed.set(newPageCount, pageCount, false);
641        }
642        pageCount = newPageCount;
643        // the easiest way to remove superfluous entries
644        freeLists.clear();
645        trace.debug("pageCount: " + pageCount);
646        long newLength = (long) pageCount << pageSizeShift;
647        if (file.length() != newLength) {
648            file.setLength(newLength);
649            writeCount++;
650        }
651    }
652 
653    private int getFirstFree(int start) {
654        int free = -1;
655        for (int id = getFreeListId(start); start < pageCount; id++) {
656            free = getFreeList(id).getFirstFree(start);
657            if (free != -1) {
658                break;
659            }
660        }
661        return free;
662    }
663 
664    private void swap(int a, int b, int free) {
665        if (a < MIN_PAGE_COUNT || b < MIN_PAGE_COUNT) {
666            System.out.println(isUsed(a) + " " + isUsed(b));
667            DbException.throwInternalError("can't swap " + a + " and " + b);
668        }
669        Page f = (Page) cache.get(free);
670        if (f != null) {
671            DbException.throwInternalError("not free: " + f);
672        }
673        if (trace.isDebugEnabled()) {
674            trace.debug("swap " + a + " and " + b + " via " + free);
675        }
676        Page pageA = null;
677        if (isUsed(a)) {
678            pageA = getPage(a);
679            if (pageA != null) {
680                pageA.moveTo(pageStoreSession, free);
681            }
682            free(a);
683        }
684        if (free != b) {
685            if (isUsed(b)) {
686                Page pageB = getPage(b);
687                if (pageB != null) {
688                    pageB.moveTo(pageStoreSession, a);
689                }
690                free(b);
691            }
692            if (pageA != null) {
693                f = getPage(free);
694                if (f != null) {
695                    f.moveTo(pageStoreSession, b);
696                }
697                free(free);
698            }
699        }
700    }
701 
702    private boolean compact(int full, int free) {
703        if (full < MIN_PAGE_COUNT || free == -1 || free >= full || !isUsed(full)) {
704            return false;
705        }
706        Page f = (Page) cache.get(free);
707        if (f != null) {
708            DbException.throwInternalError("not free: " + f);
709        }
710        Page p = getPage(full);
711        if (p == null) {
712            freePage(full);
713        } else if (p instanceof PageStreamData || p instanceof PageStreamTrunk) {
714            if (p.getPos() < log.getMinPageId()) {
715                // an old transaction log page
716                // probably a leftover from a crash
717                freePage(full);
718            }
719        } else {
720            if (trace.isDebugEnabled()) {
721                trace.debug("move " + p.getPos() + " to " + free);
722            }
723            try {
724                p.moveTo(pageStoreSession, free);
725            } finally {
726                changeCount++;
727                if (SysProperties.CHECK && changeCount < 0) {
728                    throw DbException.throwInternalError(
729                            "changeCount has wrapped");
730                }
731            }
732        }
733        return true;
734    }
735 
736    /**
737     * Read a page from the store.
738     *
739     * @param pageId the page id
740     * @return the page
741     */
742    public synchronized Page getPage(int pageId) {
743        Page p = (Page) cache.get(pageId);
744        if (p != null) {
745            return p;
746        }
747 
748        Data data = createData();
749        readPage(pageId, data);
750        int type = data.readByte();
751        if (type == Page.TYPE_EMPTY) {
752            return null;
753        }
754        data.readShortInt();
755        data.readInt();
756        if (!checksumTest(data.getBytes(), pageId, pageSize)) {
757            throw DbException.get(ErrorCode.FILE_CORRUPTED_1,
758                    "wrong checksum");
759        }
760        switch (type & ~Page.FLAG_LAST) {
761        case Page.TYPE_FREE_LIST:
762            p = PageFreeList.read(this, data, pageId);
763            break;
764        case Page.TYPE_DATA_LEAF: {
765            int indexId = data.readVarInt();
766            PageIndex idx = metaObjects.get(indexId);
767            if (idx == null) {
768                throw DbException.get(ErrorCode.FILE_CORRUPTED_1,
769                        "index not found " + indexId);
770            }
771            if (!(idx instanceof PageDataIndex)) {
772                throw DbException.get(ErrorCode.FILE_CORRUPTED_1,
773                        "not a data index " + indexId + " " + idx);
774            }
775            PageDataIndex index = (PageDataIndex) idx;
776            if (statistics != null) {
777                statisticsIncrement(index.getTable().getName() + "." +
778                        index.getName() + " read");
779            }
780            p = PageDataLeaf.read(index, data, pageId);
781            break;
782        }
783        case Page.TYPE_DATA_NODE: {
784            int indexId = data.readVarInt();
785            PageIndex idx = metaObjects.get(indexId);
786            if (idx == null) {
787                throw DbException.get(ErrorCode.FILE_CORRUPTED_1,
788                        "index not found " + indexId);
789            }
790            if (!(idx instanceof PageDataIndex)) {
791                throw DbException.get(ErrorCode.FILE_CORRUPTED_1,
792                        "not a data index " + indexId + " " + idx);
793            }
794            PageDataIndex index = (PageDataIndex) idx;
795            if (statistics != null) {
796                statisticsIncrement(index.getTable().getName() + "." +
797                        index.getName() + " read");
798            }
799            p = PageDataNode.read(index, data, pageId);
800            break;
801        }
802        case Page.TYPE_DATA_OVERFLOW: {
803            p = PageDataOverflow.read(this, data, pageId);
804            if (statistics != null) {
805                statisticsIncrement("overflow read");
806            }
807            break;
808        }
809        case Page.TYPE_BTREE_LEAF: {
810            int indexId = data.readVarInt();
811            PageIndex idx = metaObjects.get(indexId);
812            if (idx == null) {
813                throw DbException.get(ErrorCode.FILE_CORRUPTED_1,
814                        "index not found " + indexId);
815            }
816            if (!(idx instanceof PageBtreeIndex)) {
817                throw DbException.get(ErrorCode.FILE_CORRUPTED_1,
818                        "not a btree index " + indexId + " " + idx);
819            }
820            PageBtreeIndex index = (PageBtreeIndex) idx;
821            if (statistics != null) {
822                statisticsIncrement(index.getTable().getName() + "." +
823                        index.getName() + " read");
824            }
825            p = PageBtreeLeaf.read(index, data, pageId);
826            break;
827        }
828        case Page.TYPE_BTREE_NODE: {
829            int indexId = data.readVarInt();
830            PageIndex idx = metaObjects.get(indexId);
831            if (idx == null) {
832                throw DbException.get(ErrorCode.FILE_CORRUPTED_1,
833                        "index not found " + indexId);
834            }
835            if (!(idx instanceof PageBtreeIndex)) {
836                throw DbException.get(ErrorCode.FILE_CORRUPTED_1,
837                        "not a btree index " + indexId + " " + idx);
838            }
839            PageBtreeIndex index = (PageBtreeIndex) idx;
840            if (statistics != null) {
841                statisticsIncrement(index.getTable().getName() +
842                        "." + index.getName() + " read");
843            }
844            p = PageBtreeNode.read(index, data, pageId);
845            break;
846        }
847        case Page.TYPE_STREAM_TRUNK:
848            p = PageStreamTrunk.read(this, data, pageId);
849            break;
850        case Page.TYPE_STREAM_DATA:
851            p = PageStreamData.read(this, data, pageId);
852            break;
853        default:
854            throw DbException.get(ErrorCode.FILE_CORRUPTED_1,
855                    "page=" + pageId + " type=" + type);
856        }
857        cache.put(p);
858        return p;
859    }
860 
861    private int getFirstUncommittedSection() {
862        trace.debug("getFirstUncommittedSection");
863        Session[] sessions = database.getSessions(true);
864        int firstUncommittedSection = log.getLogSectionId();
865        for (Session session : sessions) {
866            int firstUncommitted = session.getFirstUncommittedLog();
867            if (firstUncommitted != Session.LOG_WRITTEN) {
868                if (firstUncommitted < firstUncommittedSection) {
869                    firstUncommittedSection = firstUncommitted;
870                }
871            }
872        }
873        return firstUncommittedSection;
874    }
875 
876    private void readStaticHeader() {
877        file.seek(FileStore.HEADER_LENGTH);
878        Data page = Data.create(database,
879                new byte[PAGE_SIZE_MIN - FileStore.HEADER_LENGTH]);
880        file.readFully(page.getBytes(), 0,
881                PAGE_SIZE_MIN - FileStore.HEADER_LENGTH);
882        readCount++;
883        setPageSize(page.readInt());
884        int writeVersion = page.readByte();
885        int readVersion = page.readByte();
886        if (readVersion > READ_VERSION) {
887            throw DbException.get(
888                    ErrorCode.FILE_VERSION_ERROR_1, fileName);
889        }
890        if (writeVersion > WRITE_VERSION) {
891            close();
892            database.setReadOnly(true);
893            accessMode = "r";
894            file = database.openFile(fileName, accessMode, true);
895        }
896    }
897 
898    private void readVariableHeader() {
899        Data page = createData();
900        for (int i = 1;; i++) {
901            if (i == 3) {
902                throw DbException.get(
903                        ErrorCode.FILE_CORRUPTED_1, fileName);
904            }
905            page.reset();
906            readPage(i, page);
907            CRC32 crc = new CRC32();
908            crc.update(page.getBytes(), 4, pageSize - 4);
909            int expected = (int) crc.getValue();
910            int got = page.readInt();
911            if (expected == got) {
912                writeCountBase = page.readLong();
913                logKey = page.readInt();
914                logFirstTrunkPage = page.readInt();
915                logFirstDataPage = page.readInt();
916                break;
917            }
918        }
919    }
920 
921    /**
922     * Set the page size. The size must be a power of two. This method must be
923     * called before opening.
924     *
925     * @param size the page size
926     */
927    public void setPageSize(int size) {
928        if (size < PAGE_SIZE_MIN || size > PAGE_SIZE_MAX) {
929            throw DbException.get(ErrorCode.FILE_CORRUPTED_1,
930                    fileName + " pageSize: " + size);
931        }
932        boolean good = false;
933        int shift = 0;
934        for (int i = 1; i <= size;) {
935            if (size == i) {
936                good = true;
937                break;
938            }
939            shift++;
940            i += i;
941        }
942        if (!good) {
943            throw DbException.get(ErrorCode.FILE_CORRUPTED_1, fileName);
944        }
945        pageSize = size;
946        emptyPage = createData();
947        pageSizeShift = shift;
948    }
949 
950    private void writeStaticHeader() {
951        Data page = Data.create(database, new byte[pageSize - FileStore.HEADER_LENGTH]);
952        page.writeInt(pageSize);
953        page.writeByte((byte) WRITE_VERSION);
954        page.writeByte((byte) READ_VERSION);
955        file.seek(FileStore.HEADER_LENGTH);
956        file.write(page.getBytes(), 0, pageSize - FileStore.HEADER_LENGTH);
957        writeCount++;
958    }
959 
960    /**
961     * Set the trunk page and data page id of the log.
962     *
963     * @param logKey the log key of the trunk page
964     * @param trunkPageId the trunk page id
965     * @param dataPageId the data page id
966     */
967    void setLogFirstPage(int logKey, int trunkPageId, int dataPageId) {
968        if (trace.isDebugEnabled()) {
969            trace.debug("setLogFirstPage key: " + logKey +
970                    " trunk: "+ trunkPageId +" data: " + dataPageId);
971        }
972        this.logKey = logKey;
973        this.logFirstTrunkPage = trunkPageId;
974        this.logFirstDataPage = dataPageId;
975        writeVariableHeader();
976    }
977 
978    private void writeVariableHeader() {
979        trace.debug("writeVariableHeader");
980        if (logMode == LOG_MODE_SYNC) {
981            file.sync();
982        }
983        Data page = createData();
984        page.writeInt(0);
985        page.writeLong(getWriteCountTotal());
986        page.writeInt(logKey);
987        page.writeInt(logFirstTrunkPage);
988        page.writeInt(logFirstDataPage);
989        CRC32 crc = new CRC32();
990        crc.update(page.getBytes(), 4, pageSize - 4);
991        page.setInt(0, (int) crc.getValue());
992        file.seek(pageSize);
993        file.write(page.getBytes(), 0, pageSize);
994        file.seek(pageSize + pageSize);
995        file.write(page.getBytes(), 0, pageSize);
996        // don't increment the write counter, because it was just written
997    }
998 
999    /**
1000     * Close the file without further writing.
1001     */
1002    public synchronized void close() {
1003        trace.debug("close");
1004        if (log != null) {
1005            log.close();
1006            log = null;
1007        }
1008        if (file != null) {
1009            try {
1010                file.releaseLock();
1011                file.close();
1012            } finally {
1013                file = null;
1014            }
1015        }
1016    }
1017 
1018    @Override
1019    public synchronized void flushLog() {
1020        if (file != null) {
1021            log.flush();
1022        }
1023    }
1024 
1025    /**
1026     * Flush the transaction log and sync the file.
1027     */
1028    public synchronized void sync() {
1029        if (file != null) {
1030            log.flush();
1031            file.sync();
1032        }
1033    }
1034 
1035    @Override
1036    public Trace getTrace() {
1037        return trace;
1038    }
1039 
1040    @Override
1041    public synchronized void writeBack(CacheObject obj) {
1042        Page record = (Page) obj;
1043        if (trace.isDebugEnabled()) {
1044            trace.debug("writeBack " + record);
1045        }
1046        record.write();
1047        record.setChanged(false);
1048    }
1049 
1050    /**
1051     * Write an undo log entry if required.
1052     *
1053     * @param page the page
1054     * @param old the old data (if known) or null
1055     */
1056    public synchronized void logUndo(Page page, Data old) {
1057        if (logMode == LOG_MODE_OFF) {
1058            return;
1059        }
1060        checkOpen();
1061        database.checkWritingAllowed();
1062        if (!recoveryRunning) {
1063            int pos = page.getPos();
1064            if (!log.getUndo(pos)) {
1065                if (old == null) {
1066                    old = readPage(pos);
1067                }
1068                openForWriting();
1069                log.addUndo(pos, old);
1070            }
1071        }
1072    }
1073 
1074    /**
1075     * Update a page.
1076     *
1077     * @param page the page
1078     */
1079    public synchronized void update(Page page) {
1080        if (trace.isDebugEnabled()) {
1081            if (!page.isChanged()) {
1082                trace.debug("updateRecord " + page.toString());
1083            }
1084        }
1085        checkOpen();
1086        database.checkWritingAllowed();
1087        page.setChanged(true);
1088        int pos = page.getPos();
1089        if (SysProperties.CHECK && !recoveryRunning) {
1090            // ensure the undo entry is already written
1091            if (logMode != LOG_MODE_OFF) {
1092                log.addUndo(pos, null);
1093            }
1094        }
1095        allocatePage(pos);
1096        cache.update(pos, page);
1097    }
1098 
1099    private int getFreeListId(int pageId) {
1100        return (pageId - PAGE_ID_FREE_LIST_ROOT) / freeListPagesPerList;
1101    }
1102 
1103    private PageFreeList getFreeListForPage(int pageId) {
1104        return getFreeList(getFreeListId(pageId));
1105    }
1106 
1107    private PageFreeList getFreeList(int i) {
1108        PageFreeList list = null;
1109        if (i < freeLists.size()) {
1110            list = freeLists.get(i);
1111            if (list != null) {
1112                return list;
1113            }
1114        }
1115        int p = PAGE_ID_FREE_LIST_ROOT + i * freeListPagesPerList;
1116        while (p >= pageCount) {
1117            increaseFileSize();
1118        }
1119        if (p < pageCount) {
1120            list = (PageFreeList) getPage(p);
1121        }
1122        if (list == null) {
1123            list = PageFreeList.create(this, p);
1124            cache.put(list);
1125        }
1126        while (freeLists.size() <= i) {
1127            freeLists.add(null);
1128        }
1129        freeLists.set(i, list);
1130        return list;
1131    }
1132 
1133    private void freePage(int pageId) {
1134        int index = getFreeListId(pageId);
1135        PageFreeList list = getFreeList(index);
1136        firstFreeListIndex = Math.min(index, firstFreeListIndex);
1137        list.free(pageId);
1138    }
1139 
1140    /**
1141     * Set the bit of an already allocated page.
1142     *
1143     * @param pageId the page to allocate
1144     */
1145    void allocatePage(int pageId) {
1146        PageFreeList list = getFreeListForPage(pageId);
1147        list.allocate(pageId);
1148    }
1149 
1150    private boolean isUsed(int pageId) {
1151        return getFreeListForPage(pageId).isUsed(pageId);
1152    }
1153 
1154    /**
1155     * Allocate a number of pages.
1156     *
1157     * @param list the list where to add the allocated pages
1158     * @param pagesToAllocate the number of pages to allocate
1159     * @param exclude the exclude list
1160     * @param after all allocated pages are higher than this page
1161     */
1162    void allocatePages(IntArray list, int pagesToAllocate, BitField exclude,
1163            int after) {
1164        list.ensureCapacity(list.size() + pagesToAllocate);
1165        for (int i = 0; i < pagesToAllocate; i++) {
1166            int page = allocatePage(exclude, after);
1167            after = page;
1168            list.add(page);
1169        }
1170    }
1171 
1172    /**
1173     * Allocate a page.
1174     *
1175     * @return the page id
1176     */
1177    public synchronized int allocatePage() {
1178        openForWriting();
1179        int pos = allocatePage(null, 0);
1180        if (!recoveryRunning) {
1181            if (logMode != LOG_MODE_OFF) {
1182                log.addUndo(pos, emptyPage);
1183            }
1184        }
1185        return pos;
1186    }
1187 
1188    private int allocatePage(BitField exclude, int first) {
1189        int page;
1190        for (int i = firstFreeListIndex;; i++) {
1191            PageFreeList list = getFreeList(i);
1192            page = list.allocate(exclude, first);
1193            if (page >= 0) {
1194                firstFreeListIndex = i;
1195                break;
1196            }
1197        }
1198        while (page >= pageCount) {
1199            increaseFileSize();
1200        }
1201        if (trace.isDebugEnabled()) {
1202            // trace.debug("allocatePage " + pos);
1203        }
1204        return page;
1205    }
1206 
1207    private void increaseFileSize() {
1208        int increment = INCREMENT_KB * 1024 / pageSize;
1209        int percent = pageCount * INCREMENT_PERCENT_MIN / 100;
1210        if (increment < percent) {
1211            increment = (1 + (percent / increment)) * increment;
1212        }
1213        int max = database.getSettings().pageStoreMaxGrowth;
1214        if (max < increment) {
1215            increment = max;
1216        }
1217        increaseFileSize(increment);
1218    }
1219 
1220    private void increaseFileSize(int increment) {
1221        for (int i = pageCount; i < pageCount + increment; i++) {
1222            freed.set(i);
1223        }
1224        pageCount += increment;
1225        long newLength = (long) pageCount << pageSizeShift;
1226        file.setLength(newLength);
1227        writeCount++;
1228        fileLength = newLength;
1229    }
1230 
1231    /**
1232     * Add a page to the free list. The undo log entry must have been written.
1233     *
1234     * @param pageId the page id
1235     */
1236    public synchronized void free(int pageId) {
1237        free(pageId, true);
1238    }
1239 
1240    /**
1241     * Add a page to the free list.
1242     *
1243     * @param pageId the page id
1244     * @param undo if the undo record must have been written
1245     */
1246    void free(int pageId, boolean undo) {
1247        if (trace.isDebugEnabled()) {
1248            // trace.debug("free " + pageId + " " + undo);
1249        }
1250        cache.remove(pageId);
1251        if (SysProperties.CHECK && !recoveryRunning && undo) {
1252            // ensure the undo entry is already written
1253            if (logMode != LOG_MODE_OFF) {
1254                log.addUndo(pageId, null);
1255            }
1256        }
1257        freePage(pageId);
1258        if (recoveryRunning) {
1259            writePage(pageId, createData());
1260            if (reservedPages != null && reservedPages.containsKey(pageId)) {
1261                // re-allocate the page if it is used later on again
1262                int latestPos = reservedPages.get(pageId);
1263                if (latestPos > log.getLogPos()) {
1264                    allocatePage(pageId);
1265                }
1266            }
1267        }
1268    }
1269 
1270    /**
1271     * Add a page to the free list. The page is not used, therefore doesn't need
1272     * to be overwritten.
1273     *
1274     * @param pageId the page id
1275     */
1276    void freeUnused(int pageId) {
1277        if (trace.isDebugEnabled()) {
1278            trace.debug("freeUnused " + pageId);
1279        }
1280        cache.remove(pageId);
1281        freePage(pageId);
1282        freed.set(pageId);
1283    }
1284 
1285    /**
1286     * Create a data object.
1287     *
1288     * @return the data page.
1289     */
1290    public Data createData() {
1291        return Data.create(database, new byte[pageSize]);
1292    }
1293 
1294    /**
1295     * Read a page.
1296     *
1297     * @param pos the page id
1298     * @return the page
1299     */
1300    public synchronized Data readPage(int pos) {
1301        Data page = createData();
1302        readPage(pos, page);
1303        return page;
1304    }
1305 
1306    /**
1307     * Read a page.
1308     *
1309     * @param pos the page id
1310     * @param page the page
1311     */
1312    void readPage(int pos, Data page) {
1313        if (recordPageReads) {
1314            if (pos >= MIN_PAGE_COUNT &&
1315                    recordedPagesIndex.get(pos) == IntIntHashMap.NOT_FOUND) {
1316                recordedPagesIndex.put(pos, recordedPagesList.size());
1317                recordedPagesList.add(pos);
1318            }
1319        }
1320        if (pos < 0 || pos >= pageCount) {
1321            throw DbException.get(ErrorCode.FILE_CORRUPTED_1, pos +
1322                    " of " + pageCount);
1323        }
1324        file.seek((long) pos << pageSizeShift);
1325        file.readFully(page.getBytes(), 0, pageSize);
1326        readCount++;
1327    }
1328 
1329    /**
1330     * Get the page size.
1331     *
1332     * @return the page size
1333     */
1334    public int getPageSize() {
1335        return pageSize;
1336    }
1337 
1338    /**
1339     * Get the number of pages (including free pages).
1340     *
1341     * @return the page count
1342     */
1343    public int getPageCount() {
1344        return pageCount;
1345    }
1346 
1347    /**
1348     * Write a page.
1349     *
1350     * @param pageId the page id
1351     * @param data the data
1352     */
1353    public synchronized void writePage(int pageId, Data data) {
1354        if (pageId <= 0) {
1355            DbException.throwInternalError("write to page " + pageId);
1356        }
1357        byte[] bytes = data.getBytes();
1358        if (SysProperties.CHECK) {
1359            boolean shouldBeFreeList = (pageId - PAGE_ID_FREE_LIST_ROOT) %
1360                    freeListPagesPerList == 0;
1361            boolean isFreeList = bytes[0] == Page.TYPE_FREE_LIST;
1362            if (bytes[0] != 0 && shouldBeFreeList != isFreeList) {
1363                throw DbException.throwInternalError();
1364            }
1365        }
1366        checksumSet(bytes, pageId);
1367        file.seek((long) pageId << pageSizeShift);
1368        file.write(bytes, 0, pageSize);
1369        writeCount++;
1370    }
1371 
1372    /**
1373     * Remove a page from the cache.
1374     *
1375     * @param pageId the page id
1376     */
1377    public synchronized void removeFromCache(int pageId) {
1378        cache.remove(pageId);
1379    }
1380 
1381    Database getDatabase() {
1382        return database;
1383    }
1384 
1385    /**
1386     * Run recovery.
1387     *
1388     * @return whether the transaction log was empty
1389     */
1390    private boolean recover() {
1391        trace.debug("log recover");
1392        recoveryRunning = true;
1393        boolean isEmpty = true;
1394        isEmpty &= log.recover(PageLog.RECOVERY_STAGE_UNDO);
1395        if (reservedPages != null) {
1396            for (int r : reservedPages.keySet()) {
1397                if (trace.isDebugEnabled()) {
1398                    trace.debug("reserve " + r);
1399                }
1400                allocatePage(r);
1401            }
1402        }
1403        isEmpty &= log.recover(PageLog.RECOVERY_STAGE_ALLOCATE);
1404        openMetaIndex();
1405        readMetaData();
1406        isEmpty &= log.recover(PageLog.RECOVERY_STAGE_REDO);
1407        boolean setReadOnly = false;
1408        if (!database.isReadOnly()) {
1409            if (log.getInDoubtTransactions().size() == 0) {
1410                log.recoverEnd();
1411                int firstUncommittedSection = getFirstUncommittedSection();
1412                log.removeUntil(firstUncommittedSection);
1413            } else {
1414                setReadOnly = true;
1415            }
1416        }
1417        PageDataIndex systemTable = (PageDataIndex) metaObjects.get(0);
1418        isNew = systemTable == null;
1419        for (PageIndex index : metaObjects.values()) {
1420            if (index.getTable().isTemporary()) {
1421                // temporary indexes are removed after opening
1422                if (tempObjects == null) {
1423                    tempObjects = New.hashMap();
1424                }
1425                tempObjects.put(index.getId(), index);
1426            } else {
1427                index.close(pageStoreSession);
1428            }
1429        }
1430 
1431        allocatePage(PAGE_ID_META_ROOT);
1432        writeIndexRowCounts();
1433        recoveryRunning = false;
1434        reservedPages = null;
1435 
1436        writeBack();
1437        // clear the cache because it contains pages with closed indexes
1438        cache.clear();
1439        freeLists.clear();
1440 
1441        metaObjects.clear();
1442        metaObjects.put(-1, metaIndex);
1443 
1444        if (setReadOnly) {
1445            database.setReadOnly(true);
1446        }
1447        trace.debug("log recover done");
1448        return isEmpty;
1449    }
1450 
1451    /**
1452     * A record is added to a table, or removed from a table.
1453     *
1454     * @param session the session
1455     * @param tableId the table id
1456     * @param row the row to add
1457     * @param add true if the row is added, false if it is removed
1458     */
1459    public synchronized void logAddOrRemoveRow(Session session, int tableId,
1460            Row row, boolean add) {
1461        if (logMode != LOG_MODE_OFF) {
1462            if (!recoveryRunning) {
1463                log.logAddOrRemoveRow(session, tableId, row, add);
1464            }
1465        }
1466    }
1467 
1468    /**
1469     * Mark a committed transaction.
1470     *
1471     * @param session the session
1472     */
1473    public synchronized void commit(Session session) {
1474        checkOpen();
1475        openForWriting();
1476        log.commit(session.getId());
1477        long size = log.getSize();
1478        if (size - logSizeBase > maxLogSize / 2) {
1479            int firstSection = log.getLogFirstSectionId();
1480            checkpoint();
1481            int newSection = log.getLogSectionId();
1482            if (newSection - firstSection <= 2) {
1483                // one section is always kept, and checkpoint
1484                // advances two sections each time it is called
1485                return;
1486            }
1487            long newSize = log.getSize();
1488            if (newSize < size || size < maxLogSize) {
1489                ignoreBigLog = false;
1490                return;
1491            }
1492            if (!ignoreBigLog) {
1493                ignoreBigLog = true;
1494                trace.error(null,
1495                        "Transaction log could not be truncated; size: " +
1496                        (newSize / 1024 / 1024) + " MB");
1497            }
1498            logSizeBase = log.getSize();
1499        }
1500    }
1501 
1502    /**
1503     * Prepare a transaction.
1504     *
1505     * @param session the session
1506     * @param transaction the name of the transaction
1507     */
1508    public synchronized void prepareCommit(Session session, String transaction) {
1509        log.prepareCommit(session, transaction);
1510    }
1511 
1512    /**
1513     * Check whether this is a new database.
1514     *
1515     * @return true if it is
1516     */
1517    public boolean isNew() {
1518        return isNew;
1519    }
1520 
1521    /**
1522     * Reserve the page if this is a index root page entry.
1523     *
1524     * @param logPos the redo log position
1525     * @param tableId the table id
1526     * @param row the row
1527     */
1528    void allocateIfIndexRoot(int logPos, int tableId, Row row) {
1529        if (tableId == META_TABLE_ID) {
1530            int rootPageId = row.getValue(3).getInt();
1531            if (reservedPages == null) {
1532                reservedPages = New.hashMap();
1533            }
1534            reservedPages.put(rootPageId, logPos);
1535        }
1536    }
1537 
1538    /**
1539     * Redo a delete in a table.
1540     *
1541     * @param tableId the object id of the table
1542     * @param key the key of the row to delete
1543     */
1544    void redoDelete(int tableId, long key) {
1545        Index index = metaObjects.get(tableId);
1546        PageDataIndex scan = (PageDataIndex) index;
1547        Row row = scan.getRowWithKey(key);
1548        if (row == null || row.getKey() != key) {
1549            trace.error(null, "Entry not found: " + key +
1550                    " found instead: " + row + " - ignoring");
1551            return;
1552        }
1553        redo(tableId, row, false);
1554    }
1555 
1556    /**
1557     * Redo a change in a table.
1558     *
1559     * @param tableId the object id of the table
1560     * @param row the row
1561     * @param add true if the record is added, false if deleted
1562     */
1563    void redo(int tableId, Row row, boolean add) {
1564        if (tableId == META_TABLE_ID) {
1565            if (add) {
1566                addMeta(row, pageStoreSession, true);
1567            } else {
1568                removeMeta(row);
1569            }
1570        }
1571        Index index = metaObjects.get(tableId);
1572        if (index == null) {
1573            throw DbException.throwInternalError(
1574                    "Table not found: " + tableId + " " + row + " " + add);
1575        }
1576        Table table = index.getTable();
1577        if (add) {
1578            table.addRow(pageStoreSession, row);
1579        } else {
1580            table.removeRow(pageStoreSession, row);
1581        }
1582    }
1583 
1584    /**
1585     * Redo a truncate.
1586     *
1587     * @param tableId the object id of the table
1588     */
1589    void redoTruncate(int tableId) {
1590        Index index = metaObjects.get(tableId);
1591        Table table = index.getTable();
1592        table.truncate(pageStoreSession);
1593    }
1594 
1595    private void openMetaIndex() {
1596        CreateTableData data = new CreateTableData();
1597        ArrayList<Column> cols = data.columns;
1598        cols.add(new Column("ID", Value.INT));
1599        cols.add(new Column("TYPE", Value.INT));
1600        cols.add(new Column("PARENT", Value.INT));
1601        cols.add(new Column("HEAD", Value.INT));
1602        cols.add(new Column("OPTIONS", Value.STRING));
1603        cols.add(new Column("COLUMNS", Value.STRING));
1604        metaSchema = new Schema(database, 0, "", null, true);
1605        data.schema = metaSchema;
1606        data.tableName = "PAGE_INDEX";
1607        data.id = META_TABLE_ID;
1608        data.temporary = false;
1609        data.persistData = true;
1610        data.persistIndexes = true;
1611        data.create = false;
1612        data.session = pageStoreSession;
1613        metaTable = new RegularTable(data);
1614        metaIndex = (PageDataIndex) metaTable.getScanIndex(
1615                pageStoreSession);
1616        metaObjects.clear();
1617        metaObjects.put(-1, metaIndex);
1618    }
1619 
1620    private void readMetaData() {
1621        Cursor cursor = metaIndex.find(pageStoreSession, null, null);
1622        // first, create all tables
1623        while (cursor.next()) {
1624            Row row = cursor.get();
1625            int type = row.getValue(1).getInt();
1626            if (type == META_TYPE_DATA_INDEX) {
1627                addMeta(row, pageStoreSession, false);
1628            }
1629        }
1630        // now create all secondary indexes
1631        // otherwise the table might not be created yet
1632        cursor = metaIndex.find(pageStoreSession, null, null);
1633        while (cursor.next()) {
1634            Row row = cursor.get();
1635            int type = row.getValue(1).getInt();
1636            if (type != META_TYPE_DATA_INDEX) {
1637                addMeta(row, pageStoreSession, false);
1638            }
1639        }
1640    }
1641 
1642    private void removeMeta(Row row) {
1643        int id = row.getValue(0).getInt();
1644        PageIndex index = metaObjects.get(id);
1645        index.getTable().removeIndex(index);
1646        if (index instanceof PageBtreeIndex || index instanceof PageDelegateIndex) {
1647            if (index.isTemporary()) {
1648                pageStoreSession.removeLocalTempTableIndex(index);
1649            } else {
1650                index.getSchema().remove(index);
1651            }
1652        }
1653        index.remove(pageStoreSession);
1654        metaObjects.remove(id);
1655    }
1656 
1657    private void addMeta(Row row, Session session, boolean redo) {
1658        int id = row.getValue(0).getInt();
1659        int type = row.getValue(1).getInt();
1660        int parent = row.getValue(2).getInt();
1661        int rootPageId = row.getValue(3).getInt();
1662        String[] options = StringUtils.arraySplit(
1663                row.getValue(4).getString(), ',', false);
1664        String columnList = row.getValue(5).getString();
1665        String[] columns = StringUtils.arraySplit(columnList, ',', false);
1666        Index meta;
1667        if (trace.isDebugEnabled()) {
1668            trace.debug("addMeta id="+ id +" type=" + type +
1669                    " root=" + rootPageId + " parent=" + parent + " columns=" + columnList);
1670        }
1671        if (redo && rootPageId != 0) {
1672            // ensure the page is empty, but not used by regular data
1673            writePage(rootPageId, createData());
1674            allocatePage(rootPageId);
1675        }
1676        metaRootPageId.put(id, rootPageId);
1677        if (type == META_TYPE_DATA_INDEX) {
1678            CreateTableData data = new CreateTableData();
1679            if (SysProperties.CHECK) {
1680                if (columns == null) {
1681                    throw DbException.throwInternalError(row.toString());
1682                }
1683            }
1684            for (int i = 0, len = columns.length; i < len; i++) {
1685                Column col = new Column("C" + i, Value.INT);
1686                data.columns.add(col);
1687            }
1688            data.schema = metaSchema;
1689            data.tableName = "T" + id;
1690            data.id = id;
1691            data.temporary = options[2].equals("temp");
1692            data.persistData = true;
1693            data.persistIndexes = true;
1694            data.create = false;
1695            data.session = session;
1696            RegularTable table = new RegularTable(data);
1697            boolean binaryUnsigned = SysProperties.SORT_BINARY_UNSIGNED;
1698            if (options.length > 3) {
1699                binaryUnsigned = Boolean.parseBoolean(options[3]);
1700            }
1701            CompareMode mode = CompareMode.getInstance(
1702                    options[0], Integer.parseInt(options[1]), binaryUnsigned);
1703            table.setCompareMode(mode);
1704            meta = table.getScanIndex(session);
1705        } else {
1706            Index p = metaObjects.get(parent);
1707            if (p == null) {
1708                throw DbException.get(ErrorCode.FILE_CORRUPTED_1,
1709                        "Table not found:" + parent + " for " + row + " meta:" + metaObjects);
1710            }
1711            RegularTable table = (RegularTable) p.getTable();
1712            Column[] tableCols = table.getColumns();
1713            int len = columns.length;
1714            IndexColumn[] cols = new IndexColumn[len];
1715            for (int i = 0; i < len; i++) {
1716                String c = columns[i];
1717                IndexColumn ic = new IndexColumn();
1718                int idx = c.indexOf('/');
1719                if (idx >= 0) {
1720                    String s = c.substring(idx + 1);
1721                    ic.sortType = Integer.parseInt(s);
1722                    c = c.substring(0, idx);
1723                }
1724                Column column = tableCols[Integer.parseInt(c)];
1725                ic.column = column;
1726                cols[i] = ic;
1727            }
1728            IndexType indexType;
1729            if (options[3].equals("d")) {
1730                indexType = IndexType.createPrimaryKey(true, false);
1731                Column[] tableColumns = table.getColumns();
1732                for (IndexColumn indexColumn : cols) {
1733                    tableColumns[indexColumn.column.getColumnId()].setNullable(false);
1734                }
1735            } else {
1736                indexType = IndexType.createNonUnique(true);
1737            }
1738            meta = table.addIndex(session, "I" + id, id, cols, indexType, false, null);
1739        }
1740        PageIndex index;
1741        if (meta instanceof MultiVersionIndex) {
1742            index = (PageIndex) ((MultiVersionIndex) meta).getBaseIndex();
1743        } else {
1744            index = (PageIndex) meta;
1745        }
1746        metaObjects.put(id, index);
1747    }
1748 
1749    /**
1750     * Add an index to the in-memory index map.
1751     *
1752     * @param index the index
1753     */
1754    public synchronized void addIndex(PageIndex index) {
1755        metaObjects.put(index.getId(), index);
1756    }
1757 
1758    /**
1759     * Add the meta data of an index.
1760     *
1761     * @param index the index to add
1762     * @param session the session
1763     */
1764    public void addMeta(PageIndex index, Session session) {
1765        Table table = index.getTable();
1766        if (SysProperties.CHECK) {
1767            if (!table.isTemporary()) {
1768                // to prevent ABBA locking problems, we need to always take
1769                // the Database lock before we take the PageStore lock
1770                synchronized (database) {
1771                    synchronized (this) {
1772                        database.verifyMetaLocked(session);
1773                    }
1774                }
1775            }
1776        }
1777        synchronized (this) {
1778            int type = index instanceof PageDataIndex ?
1779                    META_TYPE_DATA_INDEX : META_TYPE_BTREE_INDEX;
1780            IndexColumn[] columns = index.getIndexColumns();
1781            StatementBuilder buff = new StatementBuilder();
1782            for (IndexColumn col : columns) {
1783                buff.appendExceptFirst(",");
1784                int id = col.column.getColumnId();
1785                buff.append(id);
1786                int sortType = col.sortType;
1787                if (sortType != 0) {
1788                    buff.append('/');
1789                    buff.append(sortType);
1790                }
1791            }
1792            String columnList = buff.toString();
1793            CompareMode mode = table.getCompareMode();
1794            String options = mode.getName()+ "," + mode.getStrength() + ",";
1795            if (table.isTemporary()) {
1796                options += "temp";
1797            }
1798            options += ",";
1799            if (index instanceof PageDelegateIndex) {
1800                options += "d";
1801            }
1802            options += "," + mode.isBinaryUnsigned();
1803            Row row = metaTable.getTemplateRow();
1804            row.setValue(0, ValueInt.get(index.getId()));
1805            row.setValue(1, ValueInt.get(type));
1806            row.setValue(2, ValueInt.get(table.getId()));
1807            row.setValue(3, ValueInt.get(index.getRootPageId()));
1808            row.setValue(4, ValueString.get(options));
1809            row.setValue(5, ValueString.get(columnList));
1810            row.setKey(index.getId() + 1);
1811            metaIndex.add(session, row);
1812        }
1813    }
1814 
1815    /**
1816     * Remove the meta data of an index.
1817     *
1818     * @param index the index to remove
1819     * @param session the session
1820     */
1821    public void removeMeta(Index index, Session session) {
1822        if (SysProperties.CHECK) {
1823            if (!index.getTable().isTemporary()) {
1824                // to prevent ABBA locking problems, we need to always take
1825                // the Database lock before we take the PageStore lock
1826                synchronized (database) {
1827                    synchronized (this) {
1828                        database.verifyMetaLocked(session);
1829                    }
1830                }
1831            }
1832        }
1833        synchronized (this) {
1834            if (!recoveryRunning) {
1835                removeMetaIndex(index, session);
1836                metaObjects.remove(index.getId());
1837            }
1838        }
1839    }
1840 
1841    private void removeMetaIndex(Index index, Session session) {
1842        int key = index.getId() + 1;
1843        Row row = metaIndex.getRow(session, key);
1844        if (row.getKey() != key) {
1845            throw DbException.get(ErrorCode.FILE_CORRUPTED_1,
1846                    "key: " + key + " index: " + index +
1847                    " table: " + index.getTable() + " row: " + row);
1848        }
1849        metaIndex.remove(session, row);
1850    }
1851 
1852    /**
1853     * Set the maximum transaction log size in megabytes.
1854     *
1855     * @param maxSize the new maximum log size
1856     */
1857    public void setMaxLogSize(long maxSize) {
1858        this.maxLogSize = maxSize;
1859    }
1860 
1861    /**
1862     * Commit or rollback a prepared transaction after opening a database with
1863     * in-doubt transactions.
1864     *
1865     * @param sessionId the session id
1866     * @param pageId the page where the transaction was prepared
1867     * @param commit if the transaction should be committed
1868     */
1869    public synchronized void setInDoubtTransactionState(int sessionId,
1870            int pageId, boolean commit) {
1871        boolean old = database.isReadOnly();
1872        try {
1873            database.setReadOnly(false);
1874            log.setInDoubtTransactionState(sessionId, pageId, commit);
1875        } finally {
1876            database.setReadOnly(old);
1877        }
1878    }
1879 
1880    /**
1881     * Get the list of in-doubt transaction.
1882     *
1883     * @return the list
1884     */
1885    public ArrayList<InDoubtTransaction> getInDoubtTransactions() {
1886        return log.getInDoubtTransactions();
1887    }
1888 
1889    /**
1890     * Check whether the recovery process is currently running.
1891     *
1892     * @return true if it is
1893     */
1894    public boolean isRecoveryRunning() {
1895        return recoveryRunning;
1896    }
1897 
1898    private void checkOpen() {
1899        if (file == null) {
1900            throw DbException.get(ErrorCode.DATABASE_IS_CLOSED);
1901        }
1902    }
1903 
1904    /**
1905     * Get the file write count since the database was created.
1906     *
1907     * @return the write count
1908     */
1909    public long getWriteCountTotal() {
1910        return writeCount + writeCountBase;
1911    }
1912 
1913    /**
1914     * Get the file write count since the database was opened.
1915     *
1916     * @return the write count
1917     */
1918    public long getWriteCount() {
1919        return writeCount;
1920    }
1921 
1922    /**
1923     * Get the file read count since the database was opened.
1924     *
1925     * @return the read count
1926     */
1927    public long getReadCount() {
1928        return readCount;
1929    }
1930 
1931    /**
1932     * A table is truncated.
1933     *
1934     * @param session the session
1935     * @param tableId the table id
1936     */
1937    public synchronized void logTruncate(Session session, int tableId) {
1938        if (!recoveryRunning) {
1939            openForWriting();
1940            log.logTruncate(session, tableId);
1941        }
1942    }
1943 
1944    /**
1945     * Get the root page of an index.
1946     *
1947     * @param indexId the index id
1948     * @return the root page
1949     */
1950    public int getRootPageId(int indexId) {
1951        return metaRootPageId.get(indexId);
1952    }
1953 
1954    public Cache getCache() {
1955        return cache;
1956    }
1957 
1958    private void checksumSet(byte[] d, int pageId) {
1959        int ps = pageSize;
1960        int type = d[0];
1961        if (type == Page.TYPE_EMPTY) {
1962            return;
1963        }
1964        int s1 = 255 + (type & 255), s2 = 255 + s1;
1965        s2 += s1 += d[6] & 255;
1966        s2 += s1 += d[(ps >> 1) - 1] & 255;
1967        s2 += s1 += d[ps >> 1] & 255;
1968        s2 += s1 += d[ps - 2] & 255;
1969        s2 += s1 += d[ps - 1] & 255;
1970        d[1] = (byte) (((s1 & 255) + (s1 >> 8)) ^ pageId);
1971        d[2] = (byte) (((s2 & 255) + (s2 >> 8)) ^ (pageId >> 8));
1972    }
1973 
1974    /**
1975     * Check if the stored checksum is correct
1976     * @param d the data
1977     * @param pageId the page id
1978     * @param pageSize the page size
1979     * @return true if it is correct
1980     */
1981    public static boolean checksumTest(byte[] d, int pageId, int pageSize) {
1982        int ps = pageSize;
1983        int s1 = 255 + (d[0] & 255), s2 = 255 + s1;
1984        s2 += s1 += d[6] & 255;
1985        s2 += s1 += d[(ps >> 1) - 1] & 255;
1986        s2 += s1 += d[ps >> 1] & 255;
1987        s2 += s1 += d[ps - 2] & 255;
1988        s2 += s1 += d[ps - 1] & 255;
1989        if (d[1] != (byte) (((s1 & 255) + (s1 >> 8)) ^ pageId)
1990                || d[2] != (byte) (((s2 & 255) + (s2 >> 8)) ^ (pageId >> 8))) {
1991            return false;
1992        }
1993        return true;
1994    }
1995 
1996    /**
1997     * Increment the change count. To be done after the operation has finished.
1998     */
1999    public void incrementChangeCount() {
2000        changeCount++;
2001        if (SysProperties.CHECK && changeCount < 0) {
2002            throw DbException.throwInternalError("changeCount has wrapped");
2003        }
2004    }
2005 
2006    /**
2007     * Get the current change count. The first value is 1
2008     *
2009     * @return the change count
2010     */
2011    public long getChangeCount() {
2012        return changeCount;
2013    }
2014 
2015    public void setLogMode(int logMode) {
2016        this.logMode = logMode;
2017    }
2018 
2019    public int getLogMode() {
2020        return logMode;
2021    }
2022 
2023    public void setLockFile(boolean lockFile) {
2024        this.lockFile = lockFile;
2025    }
2026 
2027    public BitField getObjectIds() {
2028        BitField f = new BitField();
2029        Cursor cursor = metaIndex.find(pageStoreSession, null, null);
2030        while (cursor.next()) {
2031            Row row = cursor.get();
2032            int id = row.getValue(0).getInt();
2033            if (id > 0) {
2034                f.set(id);
2035            }
2036        }
2037        return f;
2038    }
2039 
2040    public Session getPageStoreSession() {
2041        return pageStoreSession;
2042    }
2043 
2044    public synchronized void setBackup(boolean start) {
2045        backupLevel += start ? 1 : -1;
2046    }
2047 
2048}

[all classes][org.h2.store]
EMMA 2.0.5312 (C) Vladimir Roubtsov