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 | */ |
6 | package org.h2.store; |
7 | |
8 | import java.io.IOException; |
9 | import java.io.OutputStream; |
10 | import java.util.ArrayList; |
11 | import java.util.Collections; |
12 | import java.util.HashMap; |
13 | import java.util.zip.CRC32; |
14 | |
15 | import org.h2.api.ErrorCode; |
16 | import org.h2.command.CommandInterface; |
17 | import org.h2.command.ddl.CreateTableData; |
18 | import org.h2.engine.Constants; |
19 | import org.h2.engine.Database; |
20 | import org.h2.engine.Session; |
21 | import org.h2.engine.SysProperties; |
22 | import org.h2.index.Cursor; |
23 | import org.h2.index.Index; |
24 | import org.h2.index.IndexType; |
25 | import org.h2.index.MultiVersionIndex; |
26 | import org.h2.index.PageBtreeIndex; |
27 | import org.h2.index.PageBtreeLeaf; |
28 | import org.h2.index.PageBtreeNode; |
29 | import org.h2.index.PageDataIndex; |
30 | import org.h2.index.PageDataLeaf; |
31 | import org.h2.index.PageDataNode; |
32 | import org.h2.index.PageDataOverflow; |
33 | import org.h2.index.PageDelegateIndex; |
34 | import org.h2.index.PageIndex; |
35 | import org.h2.message.DbException; |
36 | import org.h2.message.Trace; |
37 | import org.h2.result.Row; |
38 | import org.h2.schema.Schema; |
39 | import org.h2.store.fs.FileUtils; |
40 | import org.h2.table.Column; |
41 | import org.h2.table.IndexColumn; |
42 | import org.h2.table.RegularTable; |
43 | import org.h2.table.Table; |
44 | import org.h2.util.BitField; |
45 | import org.h2.util.Cache; |
46 | import org.h2.util.CacheLRU; |
47 | import org.h2.util.CacheObject; |
48 | import org.h2.util.CacheWriter; |
49 | import org.h2.util.IntArray; |
50 | import org.h2.util.IntIntHashMap; |
51 | import org.h2.util.New; |
52 | import org.h2.util.StatementBuilder; |
53 | import org.h2.util.StringUtils; |
54 | import org.h2.value.CompareMode; |
55 | import org.h2.value.Value; |
56 | import org.h2.value.ValueInt; |
57 | import 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 | */ |
81 | public 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 | } |