• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

eXist-db / exist / 40903079005

29 Oct 2026 03:35PM UTC coverage: 58.179% (-0.01%) from 58.193%
40903079005

push

github

web-flow
Merge pull request #6944 from eXist-db/dependabot/maven/greenmail.version-2.1.15

Bump greenmail.version from 2.1.14 to 2.1.15

32492 of 61720 branches covered (52.64%)

Branch coverage included in aggregate %.

85570 of 141209 relevant lines covered (60.6%)

2.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

58.3
/exist-core/src/main/java/org/exist/storage/index/BFile.java
1
/*
2
 * eXist-db Open Source Native XML Database
3
 * Copyright (C) 2001 The eXist-db Authors
4
 *
5
 * info@exist-db.org
6
 * http://www.exist-db.org
7
 *
8
 * This library is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU Lesser General Public
10
 * License as published by the Free Software Foundation; either
11
 * version 2.1 of the License, or (at your option) any later version.
12
 *
13
 * This library is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
 * Lesser General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU Lesser General Public
19
 * License along with this library; if not, write to the Free Software
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21
 */
22
package org.exist.storage.index;
23

24
import org.apache.logging.log4j.LogManager;
25
import org.apache.logging.log4j.Logger;
26

27
import org.exist.storage.BrokerPool;
28
import org.exist.storage.BufferStats;
29
import org.exist.storage.DefaultCacheManager;
30
import org.exist.storage.NativeBroker;
31
import org.exist.storage.StorageAddress;
32
import org.exist.storage.btree.BTree;
33
import org.exist.storage.btree.BTreeCallback;
34
import org.exist.storage.btree.BTreeException;
35
import org.exist.storage.btree.DBException;
36
import org.exist.storage.btree.IndexQuery;
37
import org.exist.storage.btree.Value;
38
import org.exist.storage.cache.Cache;
39
import org.exist.storage.cache.Cacheable;
40
import org.exist.storage.cache.LRUCache;
41
import org.exist.storage.io.VariableByteArrayInput;
42
import org.exist.storage.io.VariableByteInput;
43
import org.exist.storage.io.VariableByteOutputStream;
44
import org.exist.storage.journal.JournalException;
45
import org.exist.storage.journal.LogEntryTypes;
46
import org.exist.storage.journal.Loggable;
47
import org.exist.storage.journal.Lsn;
48
import org.exist.storage.lock.LockManager;
49
import org.exist.storage.lock.ManagedLock;
50
import org.exist.storage.txn.Txn;
51
import org.exist.util.*;
52
import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
53
import org.exist.util.sanity.SanityCheck;
54
import org.exist.xquery.Constants;
55
import org.exist.xquery.TerminatedException;
56

57
import java.io.EOFException;
58
import java.io.IOException;
59
import java.nio.file.Path;
60
import java.text.NumberFormat;
61
import java.util.ArrayList;
62
import java.util.Arrays;
63
import java.util.concurrent.locks.ReentrantLock;
64

65
import static java.nio.charset.StandardCharsets.UTF_8;
66

67

68
/**
69
 * Data store for variable size values.
70
 * 
71
 * This class maps keys to values of variable size. Keys are stored in the
72
 * b+-tree. B+-tree values are pointers to the logical storage address of the
73
 * value in the data section. The pointer consists of the page number and a
74
 * logical tuple identifier.
75
 * 
76
 * If a value is larger than the internal page size (4K), it is split into
77
 * overflow pages. Appending data to a overflow page is very fast. Only the
78
 * first and the last data page are loaded.
79
 * 
80
 * Data pages are buffered.
81
 * 
82
 * @author <a href="mailto:wolfgang@exist-db.org">Wolfgang Meier</a>
83
 */
84
public class BFile extends BTree {
85

86
    protected final static Logger LOGSTATS = LogManager.getLogger( NativeBroker.EXIST_STATISTICS_LOGGER );
3 ✔
87
    
88
    public final static long UNKNOWN_ADDRESS = -1;
89

90
    public final static long DATA_SYNC_PERIOD = 15000;
91
    
92
    // minimum free space a page should have to be
93
    // considered for reusing
94
    public final static int PAGE_MIN_FREE = 64;
95

96
    // page signatures
97
    public final static byte RECORD = 20;
98

99
    public final static byte LOB = 21;
100

101
    public final static byte FREE_LIST = 22;
102

103
    public final static byte MULTI_PAGE = 23;
104
    
105
    public static final int LENGTH_RECORDS_COUNT = 2; //sizeof short
106
    public static final int LENGTH_NEXT_TID = 2; //sizeof short
107

108
    /*
109
     * Byte ids for the records written to the log file.
110
     */
111
    public final static byte LOG_CREATE_PAGE = 0x30;
112
    public final static byte LOG_STORE_VALUE = 0x31;
113
    public final static byte LOG_REMOVE_VALUE = 0x32;
114
    public final static byte LOG_REMOVE_PAGE = 0x33;
115
    public final static byte LOG_OVERFLOW_APPEND = 0x34;
116
    public final static byte LOG_OVERFLOW_STORE = 0x35;
117
    public final static byte LOG_OVERFLOW_CREATE = 0x36;
118
    public final static byte LOG_OVERFLOW_MODIFIED = 0x37;
119
    public final static byte LOG_OVERFLOW_CREATE_PAGE = 0x38;
120
    public final static byte LOG_OVERFLOW_REMOVE = 0x39;
121

122
    static {
123
        // register log entry types for this db file
124
        LogEntryTypes.addEntryType(LOG_CREATE_PAGE, CreatePageLoggable::new);
3 ✔
125
        LogEntryTypes.addEntryType(LOG_STORE_VALUE, StoreValueLoggable::new);
3 ✔
126
        LogEntryTypes.addEntryType(LOG_REMOVE_VALUE, RemoveValueLoggable::new);
3 ✔
127
        LogEntryTypes.addEntryType(LOG_REMOVE_PAGE, RemoveEmptyPageLoggable::new);
3 ✔
128
        LogEntryTypes.addEntryType(LOG_OVERFLOW_APPEND, OverflowAppendLoggable::new);
3 ✔
129
        LogEntryTypes.addEntryType(LOG_OVERFLOW_STORE, OverflowStoreLoggable::new);
3 ✔
130
        LogEntryTypes.addEntryType(LOG_OVERFLOW_CREATE, OverflowCreateLoggable::new);
3 ✔
131
        LogEntryTypes.addEntryType(LOG_OVERFLOW_MODIFIED, OverflowModifiedLoggable::new);
3 ✔
132
        LogEntryTypes.addEntryType(LOG_OVERFLOW_CREATE_PAGE, OverflowCreatePageLoggable::new);
3 ✔
133
        LogEntryTypes.addEntryType(LOG_OVERFLOW_REMOVE, OverflowRemoveLoggable::new);
3 ✔
134
    }
1 ✔
135

136
    protected final LockManager lockManager;
137
    protected final BFileHeader fileHeader;
138
    protected final int minFree;
139
    protected final Cache<DataPage> dataCache;
140
    public final int fixedKeyLen = -1;
3 ✔
141
    protected final int maxValueSize;
142

143

144
    public BFile(final BrokerPool pool, final byte fileId, final short fileVersion, final boolean recoveryEnabled, final Path file, final DefaultCacheManager cacheManager,
145
            final double cacheGrowth, final double thresholdData) throws DBException {
146
        super(pool, fileId, fileVersion, recoveryEnabled, cacheManager, file);
8 ✔
147
        lockManager = pool.getLockManager();
4 ✔
148
        fileHeader = (BFileHeader) getFileHeader();
5 ✔
149
        dataCache = new LRUCache<>(FileUtils.fileName(file), 64, cacheGrowth, thresholdData, Cache.CacheType.DATA);
11 ✔
150
        cacheManager.registerCache(dataCache);
4 ✔
151
        minFree = PAGE_MIN_FREE;
3 ✔
152
        maxValueSize = fileHeader.getWorkSize() / 2;
7 ✔
153
        
154
        if(exists()) {
3 ✔
155
            open(fileVersion);
4 ✔
156
        } else {
1 ✔
157
            if (LOG.isDebugEnabled()) {
3 !
158
                LOG.debug("Creating data file: {}", FileUtils.fileName(getFile()));
×
159
            }
160
            create();
3 ✔
161
        }
162
    }
1 ✔
163

164
    /**
165
     * Returns the Lock object responsible for this BFile.
166
     * 
167
     * @return Lock
168
     */
169
    @Override
170
    public String getLockName() {
171
        return FileUtils.fileName(getFile());
4 ✔
172
    }
173

174
    protected long getDataSyncPeriod() {
175
        return DATA_SYNC_PERIOD;
×
176
    }
177

178
    /**
179
     * Append the given data fragment to the value associated
180
     * with the key. A new entry is created if the key does not
181
     * yet exist in the database.
182
     * 
183
     * @param key the key
184
     * @param value the value
185
     *
186
     * @return the pointer to the storage address
187
     *
188
     * @throws ReadOnlyException if the BFile is read-only
189
     * @throws IOException if an I/O error occurs whilst writing to the BFile
190
     */
191
    public long append(final Value key, final ByteArray value)
192
            throws ReadOnlyException, IOException {
193
        return append(null, key, value);
6 ✔
194
    }
195

196
    public long append(final Txn transaction, final Value key, final ByteArray value) throws IOException {
197
        if (key == null) {
2 !
198
            LOG.debug("key is null");
×
199
            return UNKNOWN_ADDRESS;
×
200
        }
201

202
        if (key.getLength() > fileHeader.getMaxKeySize()) {
6 ✔
203
            //TODO : throw an exception ? -pb
204
            LOG.warn("Key length exceeds page size! Skipping key ...");
3 ✔
205
            return UNKNOWN_ADDRESS;
2 ✔
206
        }
207

208
        try {
209
            // check if key exists already
210
            long p = findValue(key);
4 ✔
211
            if (p == KEY_NOT_FOUND) {
4 ✔
212
                // key does not exist:
213
                p = storeValue(transaction, value);
5 ✔
214
                addValue(transaction, key, p);
6 ✔
215
                return p;
2 ✔
216
            }
217
            // key exists: get old data
218
            final long pnum = StorageAddress.pageFromPointer(p);
4 ✔
219
            final short tid = StorageAddress.tidFromPointer(p);
3 ✔
220
            final DataPage page = getDataPage(pnum);
4 ✔
221
            if (page instanceof OverflowPage overflowPage) {
6 ✔
222
                overflowPage.append(transaction, value);
4 ✔
223
            } else {
1 ✔
224
                final int valueLen = value.size();
3 ✔
225
                final byte[] data = page.getData();
3 ✔
226
                final int offset = page.findValuePosition(tid);
4 ✔
227
                if (offset < 0) {
2 !
228
                    throw new IOException("tid " + tid + " not found on page " + pnum);
×
229
                }
230
                if (offset + 4 > data.length) {
6 !
231
                    LOG.error("found invalid pointer in file {} for page{} : tid = {}; offset = {}", FileUtils.fileName(getFile()), page.getPageInfo(), tid, offset);
×
232
                    return UNKNOWN_ADDRESS;
×
233
                }
234
                final int l = ByteConversion.byteToInt(data, offset);
4 ✔
235
                //TOUNDERSTAND : unless l can be negative, we should never get there -pb
236
                if (offset + 4 + l > data.length) {
8 !
237
                    LOG.error("found invalid data record in file {} for page{} : length = {}; required = {}", FileUtils.fileName(getFile()), page.getPageInfo(), data.length, offset + 4 + l);
×
238
                    return UNKNOWN_ADDRESS;
×
239
                }
240
                final byte[] newData = new byte[l + valueLen];
5 ✔
241
                System.arraycopy(data, offset + 4, newData, 0, l);
8 ✔
242
                value.copyTo(newData, l);
4 ✔
243
                p = update(transaction, p, page, key, new FixedByteArray(newData, 0, newData.length));
14 ✔
244
            }
245
            return p;
2 ✔
246
        } catch (final BTreeException bte) {
×
247
            LOG.warn("btree exception while appending value", bte);
×
248
        }
249
        return UNKNOWN_ADDRESS;
×
250
    }
251

252
    /**
253
     * Check, if key is contained in BFile.
254
     * 
255
     * @param key key to look for
256
     * @return true, if key exists
257
     */
258
    public boolean containsKey(final Value key) {
259
        try {
260
            return findValue(key) != KEY_NOT_FOUND;
×
261
        } catch (final BTreeException | IOException e) {
×
262
            LOG.warn(e.getMessage());
×
263
        }
264
        return false;
×
265
    }
266

267
    @Override
268
    public boolean create() throws DBException {
269
        return super.create((short) fixedKeyLen);
4 ✔
270
    }
271

272
    @Override
273
    public void close() throws DBException {
274
        super.close();
2 ✔
275
        cacheManager.deregisterCache(dataCache);
5 ✔
276
    }
1 ✔
277

278
    private SinglePage createDataPage() {
279
        try {
280
            final SinglePage page = new SinglePage();
5 ✔
281
            dataCache.add(page, 2);
5 ✔
282
            return page;
2 ✔
283
        } catch (final IOException ioe) {
×
284
            LOG.warn(ioe);
×
285
            return null;
×
286
        }
287
    }
288

289
    @Override
290
    public FileHeader createFileHeader(final int pageSize) {
291
        return new BFileHeader(pageSize);
6 ✔
292
    }
293

294
    @Override
295
    public PageHeader createPageHeader() {
296
        return new BFilePageHeader();
4 ✔
297
    }
298

299
    /**
300
     * Remove all entries matching the given query.
301
     *
302
     * @param transaction the database transaction
303
     * @param query the removal query
304
     *
305
     * @throws IOException if an I/O error occurs whilst writing to the BFile
306
     * @throws BTreeException if an error occurs with the tree
307
     */
308
    public void removeAll(final Txn transaction, final IndexQuery query) throws IOException, BTreeException {
309
        // first collect the values to remove, then sort them by their page number
310
        // and remove them.
311
        try {
312
            final RemoveCallback cb = new RemoveCallback(); 
4 ✔
313
            remove(transaction, query, cb);
5 ✔
314
            LOG.debug("Found {} items to remove.", cb.count);
6 ✔
315
            if (cb.count == 0) {
3 ✔
316
                return;
1 ✔
317
            }
318
            Arrays.sort(cb.pointers, 0, cb.count - 1);
8 ✔
319
            for (int i = 0; i < cb.count; i++) {
8 ✔
320
                remove(transaction, cb.pointers[i]);
7 ✔
321
            }
322
        } catch (final TerminatedException e) {
1 ✔
323
            // Should never happen during remove
324
            LOG.error("removeAll() - method has been terminated.", e);
×
325
        }
326
    }
1 ✔
327

328
    private static class RemoveCallback implements BTreeCallback {
3 ✔
329
        long[] pointers = new long[128];
4 ✔
330
        int count = 0;
3 ✔
331
        
332
        public boolean indexInfo(final Value value, final long pointer) throws TerminatedException {
333
            if (count == pointers.length) {
6 ✔
334
                final long[] np = new long[count * 2];
6 ✔
335
                System.arraycopy(pointers, 0, np, 0, count);
8 ✔
336
                pointers = np;
3 ✔
337
            }
338
            pointers[count++] = pointer;
11 ✔
339
            return true;
2 ✔
340
        }
341
    }
342

343
    public ArrayList<Value> findEntries(final IndexQuery query) throws IOException,
344
            BTreeException, TerminatedException {
345
        final FindCallback cb = new FindCallback(FindCallback.BOTH);
×
346
        query(query, cb);
×
347
        return cb.getValues();
×
348
    }
349

350
    public ArrayList<Value> findKeys(final IndexQuery query)
351
        throws IOException, BTreeException, TerminatedException {
352
        final FindCallback cb = new FindCallback(FindCallback.KEYS);
6 ✔
353
        query(query, cb);
4 ✔
354
        return cb.getValues();
3 ✔
355
    }
356

357
    public void find(final IndexQuery query, final IndexCallback callback)
358
            throws IOException, BTreeException, TerminatedException {
359
        final FindCallback cb = new FindCallback(callback);
×
360
        query(query, cb);
×
361
    }
×
362

363
    @Override
364
    public boolean flush() throws DBException {
365
        boolean flushed = false;
2 ✔
366
        //TODO : consider log operation as a flush ?
367
        if (isRecoveryEnabled()) {
3 ✔
368
            logManager.ifPresent(l -> l.flush(true, false));
9 ✔
369
        }
370
        flushed = flushed | dataCache.flush();
6 ✔
371
        flushed = flushed | super.flush();
5 ✔
372
        return flushed;
2 ✔
373
    }
374

375
    public BufferStats getDataBufferStats() {
376
        if (dataCache == null) {
×
377
            return null;
×
378
        }
379
        return new BufferStats(dataCache.getBuffers(), dataCache.getUsedBuffers(), 
×
380
            dataCache.getHits(), dataCache.getFails());
×
381
    }
382

383
    @Override
384
    public void printStatistics() {
385
        super.printStatistics();
2 ✔
386
        final NumberFormat nf = NumberFormat.getPercentInstance();
2 ✔
387
        final StringBuilder buf = new StringBuilder();
4 ✔
388
        buf.append(FileUtils.fileName(getFile())).append(" DATA ");
8 ✔
389
        buf.append("Buffers occupation : ");
4 ✔
390
        if (dataCache.getBuffers() == 0 && dataCache.getUsedBuffers() == 0) {
4 !
391
            buf.append("N/A");
×
392
        } else {
×
393
            buf.append(nf.format(dataCache.getUsedBuffers()/(float)dataCache.getBuffers()));
15 ✔
394
        }
395
        buf.append(" (").append(dataCache.getUsedBuffers()).append(" out of ").append(dataCache.getBuffers()).append(")");
16 ✔
396
        //buf.append(dataCache.getBuffers()).append(" / ");
397
        //buf.append(dataCache.getUsedBuffers()).append(" / ");
398
        buf.append(" Cache efficiency : ");
4 ✔
399
        if (dataCache.getHits() == 0 && dataCache.getFails() == 0) {
8 !
400
            buf.append("N/A");
4 ✔
401
        } else {
1 ✔
402
            buf.append(nf.format(dataCache.getHits()/(float)(dataCache.getHits() + dataCache.getFails())));
19 ✔
403
        }
404
        //buf.append(dataCache.getHits()).append(" / ");
405
        //buf.append(dataCache.getFails());
406
        LOGSTATS.info(buf.toString());
4 ✔
407
    }
1 ✔
408

409
    /**
410
     * Get the value data associated with the specified key
411
     * or null if the key could not be found.
412
     * 
413
     * @param key the key
414
     *
415
     * @return the value associated with the key, or null if there is no association.
416
     */
417
    public Value get(final Value key) {
418
        try {
419
            final long p = findValue(key);
4 ✔
420
            if (p == KEY_NOT_FOUND) {
4 ✔
421
                return null;
2 ✔
422
            }
423
            final long pnum = StorageAddress.pageFromPointer(p);
4 ✔
424
            final DataPage page = getDataPage(pnum);
4 ✔
425
            return get(page, p);
5 ✔
426
        } catch (final BTreeException e) {
×
427
            LOG.error("An exception occurred while trying to retrieve key {}: {}", key, e.getMessage(), e);
×
428
        } catch (final IOException e) {
×
429
            LOG.error(e.getMessage(), e);
×
430
        }
431
        return null;
×
432
    }
433

434
    /**
435
     * Get the value data for the given key as a variable byte
436
     * encoded input stream.
437
     * 
438
     * @param key the key
439
     * @return the stream
440
     * @throws IOException if an I/O error occurs
441
     */
442
    public VariableByteInput getAsStream(final Value key) throws IOException {
443
        try {
444
            final long p = findValue(key);
4 ✔
445
            if (p == KEY_NOT_FOUND) {return null;}           
6 ✔
446
            final long pnum = StorageAddress.pageFromPointer(p);
4 ✔
447
            final DataPage page = getDataPage(pnum);
4 ✔
448
            if (page != null) {
2 !
449
                return switch (page.getPageHeader().getStatus()) {
5 ✔
450
                    case MULTI_PAGE -> ((OverflowPage) page).getDataStream(p);
5 ✔
451
                    default -> getAsStream(page, p);
4 ✔
452
                };
453
            }
454
        } catch (final BTreeException e) {
×
455
            LOG.error("An exception occurred while trying to retrieve key {}: {}", key, e.getMessage(), e);
×
456
        }
457
        return null;
×
458
    }
459

460
    /**
461
     * Get the value located at the specified address as a
462
     * variable byte encoded input stream.
463
     * 
464
     * @param pointer the pointer to the value
465
     * @return the stream
466
     * @throws IOException if an I/O error occurs
467
     */
468
    public VariableByteInput getAsStream(final long pointer) throws IOException {
469
        final DataPage page = getDataPage(StorageAddress.pageFromPointer(pointer));
6 ✔
470
        return switch (page.getPageHeader().getStatus()) {
5 !
471
            case MULTI_PAGE -> ((OverflowPage) page).getDataStream(pointer);
×
472
            default -> getAsStream(page, pointer);
4 ✔
473
        };
474
    }
475

476
    private VariableByteInput getAsStream(final DataPage page, final long pointer) throws IOException {
477
        dataCache.add(page.getFirstPage(), 2);
6 ✔
478
        final short tid = StorageAddress.tidFromPointer(pointer);
3 ✔
479
        final int offset = page.findValuePosition(tid);
4 ✔
480
        if (offset < 0) {
2 !
481
            throw new IOException("no data found at tid " + tid + "; page " + page.getPageNum());
×
482
        }
483
        final byte[] data = page.getData();
3 ✔
484
        final int l = ByteConversion.byteToInt(data, offset);
4 ✔
485
        final SimplePageInput input = new SimplePageInput(data, offset + 4, l, pointer);
10 ✔
486
        return input;
2 ✔
487
    }
488

489
    /**
490
     * Returns the value located at the specified address.
491
     * 
492
     * @param pointer the pointer to the value
493
     * @return value located at the specified address
494
     */
495
    public Value get(final long pointer) {
496
        try {
497
            final long pnum = StorageAddress.pageFromPointer(pointer);
×
498
            final DataPage page = getDataPage(pnum);
×
499
            return get(page, pointer);
×
500
        } catch (final IOException e) {
×
501
            LOG.error(e);
×
502
        }
503
        return null;
×
504
    }
505

506
    /**
507
     * Retrieve value at logical address pointer from page
508
     *
509
     * @param page the data page
510
     * @param pointer the pointer to the value
511
     *
512
     * @return the value or null if there is no value
513
     *
514
     * @throws IOException if an I/O error occurs
515
     */
516
    protected Value get(final DataPage page, final long pointer) throws IOException {
517
        final short tid = StorageAddress.tidFromPointer(pointer);
3 ✔
518
        final int offset = page.findValuePosition(tid);
4 ✔
519
        final byte[] data = page.getData();
3 ✔
520
        if (offset < 0 || offset > data.length) {
6 !
521
            LOG.error("wrong pointer (tid: {}{}) in file {}; offset = {}", tid, page.getPageInfo(), FileUtils.fileName(getFile()), offset);
×
522
            return null;
×
523
        }
524
        final int l = ByteConversion.byteToInt(data, offset);
4 ✔
525
        if (l + 6 > data.length) {
6 !
526
            LOG.error("{} wrong data length in page {}: expected={}; found={}", FileUtils.fileName(getFile()), page.getPageNum(), l + 6, data.length);
×
527
            return null;
×
528
        }
529
        dataCache.add(page.getFirstPage());
5 ✔
530
        final Value v = new Value(data, offset + 4, l);
9 ✔
531
        v.setAddress(pointer);
3 ✔
532
        return v;
2 ✔
533
    }
534

535
    private DataPage getDataPage(final long pos) throws IOException {
536
            return getDataPage(pos, true);
5 ✔
537
    }
538

539
    private DataPage getDataPage(final long pos, final boolean initialize) throws IOException {
540
        final DataPage wp = (DataPage) dataCache.get(pos);
6 ✔
541
        if (wp == null) {
2 ✔
542
            final Page page = getPage(pos);
4 ✔
543
            if (page == null) {
2 !
544
                LOG.debug("page {} not found!", pos);
×
545
                return null;
×
546
            }
547
            final byte[] data = page.read();
3 ✔
548
            if (page.getPageHeader().getStatus() == MULTI_PAGE) {
5 ✔
549
                return new OverflowPage(page, data);
7 ✔
550
            }
551
            return new SinglePage(page, data, initialize);
8 ✔
552
        } else if (wp.getPageHeader().getStatus() == MULTI_PAGE) {
5 ✔
553
            return new OverflowPage(wp);
6 ✔
554
        } else {
555
            return wp;
2 ✔
556
        }
557
    }
558

559
    private SinglePage getSinglePage(final long pos) throws IOException {
560
        return getSinglePage(pos, false);
5 ✔
561
    }
562

563
    private SinglePage getSinglePage(final long pos, final boolean initialize) throws IOException {
564
        final SinglePage wp = (SinglePage) dataCache.get(pos);
6 ✔
565
        if (wp == null) {
2 !
566
            final Page page = getPage(pos);
×
567
            if (page == null) {
×
568
                LOG.debug("page {} not found!", pos);
×
569
                return null;
×
570
            }
571
            final byte[] data = page.read();
×
572
            return new SinglePage(page, data, initialize);
×
573
        }
574
        return wp;
2 ✔
575
    }
576

577
    public ArrayList<Value> getEntries() throws IOException, BTreeException, TerminatedException {
578
        final IndexQuery query = new IndexQuery(IndexQuery.ANY, "");
×
579
        final FindCallback cb = new FindCallback(FindCallback.BOTH);
×
580
        query(query, cb);
×
581
        return cb.getValues();
×
582
    }
583

584
    public ArrayList<Value> getKeys() throws IOException, BTreeException, TerminatedException {
585
        final IndexQuery query = new IndexQuery(IndexQuery.ANY, "");
6 ✔
586
        final FindCallback cb = new FindCallback(FindCallback.KEYS);
6 ✔
587
        query(query, cb);
4 ✔
588
        return cb.getValues();
3 ✔
589
    }
590

591
    public ArrayList<Value> getValues() throws IOException, BTreeException, TerminatedException {
592
        final IndexQuery query = new IndexQuery(IndexQuery.ANY, "");
×
593
        final FindCallback cb = new FindCallback(FindCallback.VALUES);
×
594
        query(query, cb);
×
595
        return cb.getValues();
×
596
    }
597

598
    /**
599
     * Put data under given key.
600
     *
601
     * @param key the key
602
     * @param data the data (value) to update
603
     * @param overwrite overwrite if set to true, value will be overwritten if it already exists
604
     *
605
     * @return on success the address of the stored value, else UNKNOWN_ADDRESS
606
     * @throws ReadOnlyException if the BFile is read-only
607
     */
608
    public long put(final Value key, final byte[] data, final boolean overwrite) throws ReadOnlyException {
609
        return put(null, key, data, overwrite);
×
610
    }
611

612
    public long put(final Txn transaction, final Value key, final byte[] data, final boolean overwrite) {
613
        SanityCheck.THROW_ASSERT(key.getLength() <= fileHeader.getWorkSize(), "Key length exceeds page size!");
10 !
614
        final FixedByteArray buf = new FixedByteArray(data, 0, data.length);
8 ✔
615
        return put(transaction, key, buf, overwrite);
7 ✔
616
    }
617

618
    /**
619
     * Convenience method for {@link BFile#put(Value, byte[], boolean)}, overwrite is true.
620
     * 
621
     * @param key with which the data is updated
622
     * @param value value to update
623
     *
624
     * @return on success the address of the stored value, else UNKNOWN_ADDRESS
625
     */
626
    public long put(final Value key, final ByteArray value) {
627
        return put(key, value, true);
×
628
    }
629

630
    /**
631
     * Put a value under given key. The difference of this
632
     * method and {@link BFile#append(Value, ByteArray)} is,
633
     * that the value gets updated and not stored.
634
     * 
635
     * @param key with which the data is updated
636
     * @param value value to update
637
     * @param overwrite if set to true, value will be overwritten if it already exists
638
     *
639
     * @return on success the address of the stored value, else UNKNOWN_ADDRESS
640
     */
641
    public long put(final Value key, final ByteArray value, final boolean overwrite) {
642
        return put(null, key, value, overwrite);
×
643
    }
644

645
    public long put(final Txn transaction, final Value key, final ByteArray value, final boolean overwrite) {
646
        if (key == null) {
2 !
647
            LOG.debug("key is null");
×
648
            return UNKNOWN_ADDRESS;
×
649
        }
650

651
        if (key.getLength() > fileHeader.getWorkSize()) {
6 !
652
            //TODO : exception ? -pb
653
            LOG.warn("Key length exceeds page size! Skipping key ...");
×
654
            return UNKNOWN_ADDRESS;
×
655
        }
656

657
        try {
658
            try {
659
                // check if key exists already
660
                //TODO : rely on a KEY_NOT_FOUND (or maybe VALUE_NOT_FOUND) result ! -pb
661
                long p = findValue(key);
4 ✔
662
                if (p == KEY_NOT_FOUND) {
4 ✔
663
                    // key does not exist:
664
                    p = storeValue(transaction, value);
5 ✔
665
                    addValue(transaction, key, p);
6 ✔
666
                    return p;
2 ✔
667
                }
668

669
                // if exists, update value
670
                if (overwrite) {
2 !
671
                    return update(transaction, p, key, value);
7 ✔
672
                }
673

674
                //TODO : throw an exception ? -pb
675
                return UNKNOWN_ADDRESS;
×
676
            //TODO : why catch an exception here ??? It costs too much ! -pb
677
            } catch (final BTreeException bte) {
×
678
                // key does not exist:
679
                final long p = storeValue(transaction, value);
×
680
                addValue(transaction, key, p);
×
681
                return p;
×
682
            } catch (final IOException ioe) {
×
683
                LOG.warn("Failed to store value while updating key in BFile", ioe);
×
684
                return UNKNOWN_ADDRESS;
×
685
            }
686
        } catch (final BTreeException | IOException e) {
×
687
            LOG.warn("Failed to update value in BFile", e);
×
688
            return UNKNOWN_ADDRESS;
×
689
        }
690
    }
691

692
    public void remove(final Value key) {
693
        remove(null, key);
4 ✔
694
    }
1 ✔
695

696
    public void remove(final Txn transaction, final Value key) {
697
        try {
698
            final long p = findValue(key);
4 ✔
699
            if (p == KEY_NOT_FOUND) {
4 !
700
                return;
×
701
            }
702
            final long pos = StorageAddress.pageFromPointer(p);
4 ✔
703
            final DataPage page = getDataPage(pos);
4 ✔
704
            remove(transaction, page, p);
5 ✔
705
            removeValue(transaction, key);
5 ✔
706
        } catch (final BTreeException | IOException e) {
1 ✔
707
            LOG.error(e);
×
708
        }
709
    }
1 ✔
710

711
    public void remove(final Txn transaction, final long p) {
712
        try {
713
            final long pos = StorageAddress.pageFromPointer(p);
4 ✔
714
            final DataPage page = getDataPage(pos);
4 ✔
715
            remove(transaction, page, p);
5 ✔
716
        } catch (final IOException e) {
1 ✔
717
            LOG.error(e);
×
718
        }
719
    }
1 ✔
720

721
    private void remove(final Txn transaction, final DataPage page, final long p) throws IOException {
722
        if (page.getPageHeader().getStatus() == MULTI_PAGE) {
5 ✔
723
            // overflow page: simply delete the whole page
724
            ((OverflowPage)page).delete(transaction);
4 ✔
725
            return;
1 ✔
726
        }
727
        final short tid = StorageAddress.tidFromPointer(p);
3 ✔
728
        final int offset = page.findValuePosition(tid);
4 ✔
729
        final byte[] data = page.getData();
3 ✔
730
        if (offset > data.length || offset < 0) {
6 !
731
            LOG.error("wrong pointer (tid: {}, {})", tid, page.getPageInfo());
×
732
            return;
×
733
        }
734
        final int l = ByteConversion.byteToInt(data, offset);
4 ✔
735
        if (transaction != null && isRecoveryEnabled()) {
5 ✔
736
            final Loggable loggable = new RemoveValueLoggable(transaction, fileId, page.getPageNum(), tid, data, offset + 4, l);
15 ✔
737
            writeToLog(loggable, page);
4 ✔
738
        }
739
        final BFilePageHeader ph = page.getPageHeader();
3 ✔
740
        final int end = offset + 4 + l;
6 ✔
741
        int len = ph.getDataLength();
3 ✔
742
        // remove old value
743
        System.arraycopy(data, end, data, offset - 2, len - end);
10 ✔
744
        ph.setDirty(true);
3 ✔
745
        ph.decRecordCount();
2 ✔
746
        len = len - l - 6;
6 ✔
747
        ph.setDataLength(len);
3 ✔
748
        page.setDirty(true);
3 ✔
749
        // if this page is empty, remove it
750
        if (len == 0) {
2 ✔
751
            if (transaction != null && isRecoveryEnabled()) {
5 !
752
                final Loggable loggable = new RemoveEmptyPageLoggable(transaction, fileId, page.getPageNum());
9 ✔
753
                writeToLog(loggable, page);
4 ✔
754
            }
755
            fileHeader.removeFreeSpace(fileHeader.getFreeSpace(page.getPageNum()));
8 ✔
756
            dataCache.remove(page);
4 ✔
757
            page.delete();
2 ✔
758
        } else {
1 ✔
759
                page.removeTID(tid, l + 6);
6 ✔
760
            // adjust free space data
761
            final int newFree = fileHeader.getWorkSize() - len;
6 ✔
762
            if (newFree > minFree) {
4 ✔
763
                FreeSpace free = fileHeader.getFreeSpace(page.getPageNum());
6 ✔
764
                if (free == null) {
2 ✔
765
                    free = new FreeSpace(page.getPageNum(), newFree);
7 ✔
766
                    fileHeader.addFreeSpace(free);
4 ✔
767
                } else {
1 ✔
768
                    free.setFree(newFree);
3 ✔
769
                }
770
            }
771
            dataCache.add(page, 2);
5 ✔
772
        }
773
    }
1 ✔
774

775
    private final void saveFreeSpace(final FreeSpace space, final DataPage page) {
776
        final int free = fileHeader.getWorkSize() - page.getPageHeader().getDataLength();
8 ✔
777
        space.setFree(free);
3 ✔
778
        if(free < minFree) {
4 ✔
779
            fileHeader.removeFreeSpace(space);
4 ✔
780
        }
781
    }
1 ✔
782

783
    public long storeValue(final Txn transaction, final ByteArray value) throws IOException {
784
        final int vlen = value.size();
3 ✔
785
        // does value fit into a single page?
786
        if (6 + vlen > maxValueSize) {
6 ✔
787
            final OverflowPage page = new OverflowPage(transaction);
6 ✔
788
            final byte[] data = new byte[vlen + 6];
5 ✔
789
            page.getPageHeader().setDataLength(vlen + 6);
6 ✔
790
            ByteConversion.shortToByte((short) 1, data, 0);
5 ✔
791
            ByteConversion.intToByte(vlen, data, 2);
5 ✔
792
            //System.arraycopy(value, 0, data, 6, vlen);
793
            value.copyTo(data, 6);
4 ✔
794
            page.setData(transaction, data);
4 ✔
795
            page.setDirty(true);
3 ✔
796
            //dataCache.add(page);
797
            return StorageAddress.createPointer((int) page.getPageNum(), (short)1);
6 ✔
798
        }
799
        DataPage page = null;
2 ✔
800
        short tid = -1;
2 ✔
801
        FreeSpace free = null;
2 ✔
802
        // check for available tid
803
        while (tid < 0) {
3 ✔
804
            free = fileHeader.findFreeSpace(vlen + 6);
7 ✔
805
            if (free == null) {
2 ✔
806
                page = createDataPage();
3 ✔
807
                if (transaction != null && isRecoveryEnabled()) {
5 ✔
808
                    final Loggable loggable = new CreatePageLoggable(transaction, fileId, page.getPageNum());
9 ✔
809
                    writeToLog(loggable, page);
4 ✔
810
                }
811
                page.setData(new byte[fileHeader.getWorkSize()]);
6 ✔
812
                free = new FreeSpace(page.getPageNum(), 
6 ✔
813
                        fileHeader.getWorkSize() - page.getPageHeader().getDataLength());
7 ✔
814
                fileHeader.addFreeSpace(free);
4 ✔
815
            } else {
1 ✔
816
                page = getDataPage(free.getPage());
5 ✔
817
                // check if this is really a data page
818
                if (page.getPageHeader().getStatus() != BFile.RECORD) {
5 !
819
                    LOG.warn("page {} is not a data page; removing it", page.getPageNum());
×
820
                    fileHeader.removeFreeSpace(free);
×
821
                    continue;
×
822
                }
823
                // check if the information about free space is really correct
824
                final int realSpace = fileHeader.getWorkSize() - page.getPageHeader().getDataLength();
8 ✔
825
                if (realSpace < 6 + vlen) {
5 !
826
                    // not correct: adjust and continue
827
                    LOG.warn("Wrong data length in list of free pages: adjusting to {}", realSpace);
×
828
                    free.setFree(realSpace);
×
829
                    continue;
×
830
                }
831
            }
832
            tid = page.getNextTID();
3 ✔
833
            if (tid < 0) {
2 !
834
                LOG.info("removing page {} from free pages", page.getPageNum());
×
835
                fileHeader.removeFreeSpace(free);
×
836
            }
837
        }
838
        if (transaction != null && isRecoveryEnabled()) {
5 ✔
839
            final Loggable loggable = new StoreValueLoggable(transaction, fileId, page.getPageNum(), tid, value);
11 ✔
840
            writeToLog(loggable, page);
4 ✔
841
        }
842
        int len = page.getPageHeader().getDataLength();
4 ✔
843
        final byte[] data = page.getData();
3 ✔
844
        // save tid
845
        ByteConversion.shortToByte(tid, data, len);
5 ✔
846
        len += 2;
1 ✔
847
        page.setOffset(tid, len);
4 ✔
848
        // save data length
849
        ByteConversion.intToByte(vlen, data, len);
5 ✔
850
        len += 4;
1 ✔
851
        // save data
852
        value.copyTo(data, len);
4 ✔
853
        len += vlen;
4 ✔
854
        page.getPageHeader().setDataLength(len);
4 ✔
855
        page.getPageHeader().incRecordCount();
3 ✔
856
        saveFreeSpace(free, page);
4 ✔
857
        page.setDirty(true);
3 ✔
858
        dataCache.add(page);
4 ✔
859
        // return pointer from pageNum and offset into page
860
        return StorageAddress.createPointer((int) page.getPageNum(), tid);
6 ✔
861
    }
862

863
    /**
864
     * Update a key/value pair.
865
     * 
866
     * @param key
867
     *                   Description of the Parameter
868
     * @param value
869
     *                   Description of the Parameter
870
     * @return Description of the Return Value
871
     */
872
    public long update(final Value key, final ByteArray value) {
873
        try {
874
            final long p = findValue(key);
×
875
            if (p == KEY_NOT_FOUND) {return UNKNOWN_ADDRESS;}
×
876
            return update(p, key, value);
×
877
        } catch (final BTreeException | IOException bte) {
×
878
            LOG.debug(bte);
×
879
        }
880
        return UNKNOWN_ADDRESS;
×
881
    }
882

883
    /**
884
     * Update the key/value pair found at the logical address p.
885
     * 
886
     * @param p
887
     *                   Description of the Parameter
888
     * @param key
889
     *                   Description of the Parameter
890
     * @param value
891
     *                   Description of the Parameter
892
     * @return Description of the Return Value
893
     */
894
    public long update(final long p, final Value key, final ByteArray value) {
895
        return update(null, p, key, value);
7 ✔
896
    }
897
    
898
    public long update(final Txn transaction, final long p, final Value key, final ByteArray value) {
899
        try {
900
            return update(transaction, p, getDataPage(StorageAddress.pageFromPointer(p)),
10 ✔
901
                    key, value);
2 ✔
902
        } catch (final BTreeException | IOException ioe) {
×
903
            LOG.error(ioe.getMessage(), ioe);
×
904
            return UNKNOWN_ADDRESS;
×
905
        }
906
    }
907

908
    /**
909
     * Update the key/value pair with logical address p and stored in page.
910
     *
911
     * @param transaction the database transaction
912
     * @param p the pointer address
913
     * @param page the data page
914
     * @param key the key
915
     * @param value the value
916
     *
917
     * @return the new pointer
918
     *
919
     * @throws BTreeException if an error occurs updating the tree
920
     * @throws IOException if an I/O error occurs
921
     */
922
    protected long update(final Txn transaction, final long p, final DataPage page, final Value key, final ByteArray value)
923
            throws BTreeException, IOException {
924
        if (page.getPageHeader().getStatus() == MULTI_PAGE) {
5 ✔
925
            final int valueLen = value.size();
3 ✔
926
            // does value fit into a single page?
927
            if (valueLen + 6 < maxValueSize) {
6 ✔
928
                // yes: remove the overflow page
929
                remove(transaction, page, p);
5 ✔
930
                final long np = storeValue(transaction, value);
5 ✔
931
                addValue(transaction, key, np);
6 ✔
932
                return np;
2 ✔
933
            }
934
            // this is an overflow page: simply replace the value
935
            final byte[] data = new byte[valueLen + 6];
5 ✔
936
            // save tid
937
            ByteConversion.shortToByte((short) 1, data, 0);
5 ✔
938
            // save length
939
            ByteConversion.intToByte(valueLen, data, 2);
5 ✔
940
            // save data
941
            value.copyTo(data, 6);
4 ✔
942
            ((OverflowPage)page).setData(transaction, data);
5 ✔
943
            return p;
2 ✔
944
        }
945
        remove(transaction, page, p);
5 ✔
946
        final long np = storeValue(transaction, value);
5 ✔
947
        addValue(transaction, key, np);
6 ✔
948
        return np;
2 ✔
949
    }
950

951
    public void debugFreeList() {
952
            fileHeader.debugFreeList();
×
953
    }
×
954

955
    /* ---------------------------------------------------------------------------------
956
     * Methods used by recovery and transaction management
957
     * --------------------------------------------------------------------------------- */
958
    
959
    /**
960
     * Write loggable to the journal and update the LSN in the page header.
961
     *
962
     * @param loggable the log entry
963
     * @param page the data page
964
     */
965
    private void writeToLog(final Loggable loggable, final DataPage page) {
966
        if(logManager.isPresent()) {
4 !
967
            try {
968
                logManager.get().journal(loggable);
6 ✔
969
                page.getPageHeader().setLsn(loggable.getLsn());
5 ✔
970
            } catch (final JournalException e) {
1 ✔
971
                LOG.warn(e.getMessage(), e);
×
972
            }
973
        }
974
    }
1 ✔
975

976
    private SinglePage getSinglePageForRedo(final Loggable loggable, final long pos) throws IOException {
977
        final SinglePage wp = (SinglePage) dataCache.get(pos);
6 ✔
978
        if (wp == null) {
2 !
979
            final Page page = getPage(pos);
×
980
            final byte[] data = page.read();
×
981
            if (page.getPageHeader().getStatus() < RECORD) {
×
982
                return null;
×
983
            }
984
            if (loggable != null && isUptodate(page, loggable)) {
×
985
                return null;
×
986
            }
987
            return new SinglePage(page, data, true);
×
988
        }
989
        return wp;
2 ✔
990
    }
991

992
    private boolean isUptodate(final Page page, final Loggable loggable) {
993
        return page.getPageHeader().getLsn().compareTo(loggable.getLsn()) >= 0;
9 !
994
    }
995

996
    private boolean requiresRedo(final Loggable loggable, final DataPage page) {
997
        return loggable.getLsn().compareTo(page.getPageHeader().getLsn()) > 0;
11 ✔
998
    }
999

1000
    protected void redoStoreValue(final StoreValueLoggable loggable) {
1001
        try {
1002
            final SinglePage page = getSinglePageForRedo(loggable, loggable.page);
6 ✔
1003
            if (page != null && requiresRedo(loggable, page)) {
7 !
1004
                storeValueHelper(loggable, loggable.tid, loggable.value, page);
8 ✔
1005
            }
1006
        } catch (final IOException e) {
1 ✔
1007
            LOG.warn("An IOException occurred during redo: {}", e.getMessage());
×
1008
        }
1009
    }
1 ✔
1010

1011
    protected void undoStoreValue(final StoreValueLoggable loggable) {
1012
        try {
1013
            final SinglePage page = (SinglePage) getDataPage(loggable.page, true);
7 ✔
1014
            removeValueHelper(null, loggable.tid, page);
6 ✔
1015
        } catch (final IOException e) {
1 ✔
1016
            LOG.warn("An IOException occurred during redo: {}", e.getMessage(), e);
×
1017
        }
1018
    }
1 ✔
1019

1020
    protected void redoCreatePage(final CreatePageLoggable loggable) {
1021
        createPageHelper(loggable, loggable.newPage, false);
7 ✔
1022
    }
1 ✔
1023

1024
    protected void undoCreatePage(final CreatePageLoggable loggable) {
1025
        try {
1026
            final SinglePage page = (SinglePage) getDataPage(loggable.newPage);
6 ✔
1027
            fileHeader.removeFreeSpace(fileHeader.getFreeSpace(page.getPageNum()));
8 ✔
1028
            dataCache.remove(page);
4 ✔
1029
            page.delete();
2 ✔
1030
        } catch (final IOException e) {
1 ✔
1031
            LOG.warn("An IOException occurred during redo: {}", e.getMessage(), e);
×
1032
        }
1033
    }
1 ✔
1034

1035
    protected void redoRemoveValue(final RemoveValueLoggable loggable) {
1036
        try {
1037
            SinglePage wp = (SinglePage) dataCache.get(loggable.page);
7 ✔
1038
            if (wp == null) {
2 ✔
1039
                final Page page = getPage(loggable.page);
5 ✔
1040
                if (page == null) {
2 !
1041
                    LOG.warn("page {} not found!", loggable.page);
×
1042
                    return;
×
1043
                }
1044
                final byte[] data = page.read();
3 ✔
1045
                if (page.getPageHeader().getStatus() < RECORD || isUptodate(page, loggable)) {
10 !
1046
                        // page is obviously deleted later
1047
                        return;
1 ✔
1048
                }
1049
                wp = new SinglePage(page, data, true);
8 ✔
1050
            }
1051
            if (!wp.ph.getLsn().equals(Lsn.LSN_INVALID) && requiresRedo(loggable, wp)) {
11 !
1052
                removeValueHelper(loggable, loggable.tid, wp);
6 ✔
1053
            }
1054
        } catch (final IOException e) {
1 ✔
1055
            LOG.warn("An IOException occurred during redo: {}", e.getMessage(), e);
×
1056
        }
1057
    }
1 ✔
1058

1059
    protected void undoRemoveValue(final RemoveValueLoggable loggable) {
1060
        try {
1061
            final SinglePage page = getSinglePage(loggable.page, true);
6 ✔
1062
            final FixedByteArray data = new FixedByteArray(loggable.oldData);
6 ✔
1063
            storeValueHelper(null, loggable.tid, data, page);
7 ✔
1064
        } catch (final IOException e) {
1 ✔
1065
            LOG.warn("An IOException occurred during undo: {}", e.getMessage(), e);
×
1066
        }
1067
    }
1 ✔
1068
    
1069
    protected void redoRemovePage(final RemoveEmptyPageLoggable loggable) {
1070
        try {
1071
            SinglePage wp = (SinglePage) dataCache.get(loggable.page);
7 ✔
1072
            if (wp == null) {
2 !
1073
                final Page page = getPage(loggable.page);
5 ✔
1074
                if (page == null) {
2 !
1075
                    LOG.warn("page {} not found!", loggable.page);
×
1076
                    return;
×
1077
                }
1078
                final byte[] data = page.read();
3 ✔
1079
                if (page.getPageHeader().getStatus() < RECORD || isUptodate(page, loggable)) {
5 !
1080
                    return;
1 ✔
1081
                }
1082
                wp = new SinglePage(page, data, false);
×
1083
            }
1084
            if (wp.getPageHeader().getLsn().equals(Lsn.LSN_INVALID) || requiresRedo(loggable, wp)) {
×
1085
                fileHeader.removeFreeSpace(fileHeader.getFreeSpace(wp.getPageNum()));
×
1086
                dataCache.remove(wp);
×
1087
                wp.delete();
×
1088
            }
1089
        } catch (final IOException e) {
×
1090
            LOG.warn("An IOException occurred during redo: {}", e.getMessage(), e);
×
1091
        }
1092
    }
×
1093

1094
    protected void undoRemovePage(final RemoveEmptyPageLoggable loggable) {
1095
        createPageHelper(loggable, loggable.page, false);
×
1096
    }
×
1097

1098
    protected void redoCreateOverflow(final OverflowCreateLoggable loggable) {
1099
        try {
1100
            DataPage firstPage = (DataPage) dataCache.get(loggable.pageNum);
×
1101
            if (firstPage == null) {
×
1102
                final Page page = getPage(loggable.pageNum);
×
1103
                byte[] data = page.read();
×
1104
                if (page.getPageHeader().getLsn().equals(Lsn.LSN_INVALID) || requiresRedo(loggable, page)) {
×
1105
                    dropFreePageList();
×
1106
                    final BFilePageHeader ph = (BFilePageHeader) page.getPageHeader();
×
1107
                    ph.setStatus(MULTI_PAGE);
×
1108
                    ph.setNextInChain(0L);
×
1109
                    ph.setLastInChain(0L);
×
1110
                    ph.setDataLength(0);
×
1111
                    ph.nextTID = 32;
×
1112
                    data = new byte[fileHeader.getWorkSize()];
×
1113
                    firstPage = new SinglePage(page, data, true);
×
1114
                    firstPage.setDirty(true);
×
1115
                } else {
×
1116
                    firstPage = new SinglePage(page, data, false);
×
1117
                }
1118
            }
1119
            if (!firstPage.getPageHeader().getLsn().equals(Lsn.LSN_INVALID) && requiresRedo(loggable, firstPage)) {
×
1120
                firstPage.getPageHeader().setLsn(loggable.getLsn());
×
1121
                firstPage.setDirty(true);
×
1122
            }
1123
            dataCache.add(firstPage);
×
1124
        } catch (final IOException e) {
×
1125
            LOG.warn("An IOException occurred during redo: {}", e.getMessage(), e);
×
1126
        }
1127
    }
×
1128

1129
    protected void undoCreateOverflow(final OverflowCreateLoggable loggable) {
1130
        try {
1131
            final SinglePage page = getSinglePage(loggable.pageNum);
×
1132
            dataCache.remove(page);
×
1133
            page.delete();
×
1134
        } catch (final IOException e) {
×
1135
            LOG.warn("An IOException occurred during redo: {}", e.getMessage(), e);
×
1136
        }
1137
    }
×
1138

1139
    protected void redoCreateOverflowPage(final OverflowCreatePageLoggable loggable) {
1140
        createPageHelper(loggable, loggable.newPage, false);
×
1141
        if (loggable.prevPage != Page.NO_PAGE) {
×
1142
            try {
1143
                final SinglePage page = getSinglePageForRedo(null, loggable.prevPage);
×
1144
                SanityCheck.ASSERT(page != null, "Previous page is null");
×
1145
                page.getPageHeader().setNextInChain(loggable.newPage);
×
1146
                page.setDirty(true);
×
1147
                dataCache.add(page);
×
1148
            } catch (final IOException e) {
×
1149
                LOG.warn("An IOException occurred during redo: {}", e.getMessage(), e);
×
1150
            }
1151
        }
1152
    }
×
1153

1154
    protected void undoCreateOverflowPage(final OverflowCreatePageLoggable loggable) {
1155
        try {
1156
            SinglePage page = getSinglePage(loggable.newPage);
×
1157
            dataCache.remove(page);
×
1158
            page.delete();
×
1159
            
1160
            if (loggable.prevPage != Page.NO_PAGE) {
×
1161
                page = getSinglePage(loggable.prevPage);
×
1162
                SanityCheck.ASSERT(page != null, "Previous page is null");
×
1163
                page.getPageHeader().setNextInChain(0);
×
1164
                page.setDirty(true);
×
1165
                dataCache.add(page);
×
1166
            }
1167
        } catch (final IOException e) {
×
1168
            LOG.warn("An IOException occurred during redo: {}", e.getMessage(), e);
×
1169
        }
1170
    }
×
1171

1172
    protected void redoAppendOverflow(final OverflowAppendLoggable loggable) {
1173
        try {
1174
            final SinglePage page = getSinglePageForRedo(loggable, loggable.pageNum);
×
1175
            if (page != null && requiresRedo(loggable, page)) {
×
1176
                final BFilePageHeader ph = page.getPageHeader();
×
1177
                loggable.data.copyTo(0, page.getData(), ph.getDataLength(), loggable.chunkSize);
×
1178
                ph.setDataLength(ph.getDataLength() + loggable.chunkSize);
×
1179
                ph.setLsn(loggable.getLsn());
×
1180
                page.setDirty(true);
×
1181
                dataCache.add(page);
×
1182
            }
1183
        } catch (final IOException e) {
×
1184
            LOG.warn("An IOException occurred during redo: {}", e.getMessage(), e);
×
1185
        }
1186
    }
×
1187

1188
    protected void undoAppendOverflow(final OverflowAppendLoggable loggable) {
1189
        try {
1190
            final SinglePage page = getSinglePage(loggable.pageNum);
×
1191
            final BFilePageHeader ph = page.getPageHeader();
×
1192
            ph.setDataLength(ph.getDataLength() - loggable.chunkSize);
×
1193
            page.setDirty(true);
×
1194
            dataCache.add(page);
×
1195
        } catch (final IOException e) {
×
1196
            LOG.warn("An IOException occurred during redo: {}", e.getMessage(), e);
×
1197
        }
1198
    }
×
1199

1200
    protected void redoStoreOverflow(final OverflowStoreLoggable loggable) {
1201
        try {
1202
            SinglePage page = getSinglePageForRedo(loggable, loggable.pageNum);
×
1203
            if (page != null && requiresRedo(loggable, page)) {
×
1204
                final BFilePageHeader ph = page.getPageHeader();
×
1205
                try {
1206
                    System.arraycopy(loggable.data, 0, page.getData(), 0, loggable.size);
×
1207
                } catch (final ArrayIndexOutOfBoundsException e) {
×
1208
                    LOG.warn("{}; {}; {}; {}", loggable.data.length, page.getData().length, ph.getDataLength(), loggable.size);
×
1209
                    throw e;
×
1210
                }
1211
                ph.setDataLength(loggable.size);
×
1212
                ph.setNextInChain(0);
×
1213
                ph.setLsn(loggable.getLsn());
×
1214
                page.setDirty(true);
×
1215
                dataCache.add(page);
×
1216
                
1217
                if (loggable.prevPage != Page.NO_PAGE) {
×
1218
                    page = getSinglePage(loggable.prevPage);
×
1219
                    SanityCheck.ASSERT(page != null, "Previous page is null");
×
1220
                    page.getPageHeader().setNextInChain(loggable.pageNum);
×
1221
                    page.setDirty(true);
×
1222
                    dataCache.add(page);
×
1223
                }
1224
            }
1225
        } catch (final IOException e) {
×
1226
            LOG.warn("An IOException occurred during redo: {}", e.getMessage(), e);
×
1227
        }
1228
    }
×
1229

1230
    protected void redoModifiedOverflow(final OverflowModifiedLoggable loggable) {
1231
        try {
1232
            final SinglePage page = getSinglePageForRedo(loggable, loggable.pageNum);
×
1233
            if (page != null && requiresRedo(loggable, page)) {
×
1234
                final BFilePageHeader ph = page.getPageHeader();
×
1235
                ph.setDataLength(loggable.length);
×
1236
                ph.setLastInChain(loggable.lastInChain);
×
1237
                // adjust length field in first page
1238
                ByteConversion.intToByte(ph.getDataLength() - 6, page.getData(), 2);
×
1239
                page.setDirty(true);
×
1240
                // keep the first page in cache
1241
                dataCache.add(page, 2);
×
1242
            }
1243
        } catch (final IOException e) {
×
1244
            LOG.warn("An IOException occurred during redo: {}", e.getMessage(), e);
×
1245
        }
1246
    }
×
1247

1248
    protected void undoModifiedOverflow(final OverflowModifiedLoggable loggable) {
1249
        try {
1250
            final SinglePage page = getSinglePage(loggable.pageNum);
×
1251
            final BFilePageHeader ph = page.getPageHeader();
×
1252
            ph.setDataLength(loggable.oldLength);
×
1253
            // adjust length field in first page
1254
            ByteConversion.intToByte(ph.getDataLength() - 6, page.getData(), 2);
×
1255
            page.setDirty(true);
×
1256
            dataCache.add(page);
×
1257
        } catch (final IOException e) {
×
1258
            LOG.warn("An IOException occurred during undo: {}", e.getMessage(), e);
×
1259
        }
1260
    }
×
1261

1262
    protected void redoRemoveOverflow(final OverflowRemoveLoggable loggable) {
1263
        try {
1264
            SinglePage wp = (SinglePage) dataCache.get(loggable.pageNum);
×
1265
            if (wp == null) {
×
1266
                final Page page = getPage(loggable.pageNum);
×
1267
                if (page == null) {
×
1268
                    LOG.warn("page {} not found!", loggable.pageNum);
×
1269
                    return;
×
1270
                }
1271
                final byte[] data = page.read();
×
1272
                if (page.getPageHeader().getStatus() < RECORD || isUptodate(page, loggable))
×
1273
                    {return;}
×
1274
                wp = new SinglePage(page, data, true);
×
1275
            }
1276
            if (requiresRedo(loggable, wp)) {
×
1277
                wp.setDirty(true);
×
1278
                dataCache.remove(wp);
×
1279
                wp.delete();
×
1280
            }
1281
        } catch (final IOException e) {
×
1282
            LOG.warn("An IOException occurred during redo: {}", e.getMessage(), e);
×
1283
        }
1284
    }
×
1285

1286
    protected void undoRemoveOverflow(final OverflowRemoveLoggable loggable) {
1287
        final DataPage page = createPageHelper(loggable, loggable.pageNum, false);
×
1288
        final BFilePageHeader ph = page.getPageHeader();
×
1289
        ph.setStatus(loggable.status);
×
1290
        ph.setDataLength(loggable.length);
×
1291
        ph.setNextInChain(loggable.nextInChain);
×
1292
        page.setData(loggable.data);
×
1293
        page.setDirty(true);
×
1294
        dataCache.add(page);
×
1295
    }
×
1296

1297
    private void storeValueHelper(final Loggable loggable, final short tid, final ByteArray value, final SinglePage page) {
1298
        int len = page.ph.getDataLength();
4 ✔
1299
        // save tid
1300
        ByteConversion.shortToByte(tid, page.data, len);
6 ✔
1301
        len += 2;
1 ✔
1302
        page.adjustTID(tid);
3 ✔
1303
        page.setOffset(tid, len);
4 ✔
1304
        // save data length
1305
        ByteConversion.intToByte(value.size(), page.data, len);
7 ✔
1306
        len += 4;
1 ✔
1307
        // save data
1308
        try {
1309
            value.copyTo(page.data, len);
5 ✔
1310
        } catch (final RuntimeException e) {
1 ✔
1311
            LOG.error("{}: storage error in page: {}; len: {} ; value: {}; max: {}; status: {}", FileUtils.fileName(getFile()), page.getPageNum(), len, value.size(), fileHeader.getWorkSize(), page.ph.getStatus());
×
1312
            LOG.debug(page.printContents());
×
1313
            throw e;
×
1314
        }
1315
        len += value.size();
5 ✔
1316
        page.ph.setDataLength(len);
4 ✔
1317
        page.ph.incRecordCount();
3 ✔
1318
        if (loggable != null) {
2 ✔
1319
            page.ph.setLsn(loggable.getLsn());
5 ✔
1320
        }
1321
        FreeSpace free = fileHeader.getFreeSpace(page.getPageNum());
6 ✔
1322
        if (free == null) {
2 ✔
1323
            free = new FreeSpace(page.getPageNum(), fileHeader.getWorkSize() - len);
11 ✔
1324
        }
1325
        saveFreeSpace(free, page);
4 ✔
1326
        page.setDirty(true);
3 ✔
1327
        dataCache.add(page);
4 ✔
1328
    }
1 ✔
1329

1330
    private void removeValueHelper(final Loggable loggable, final short tid, final SinglePage page) throws IOException {
1331
        final int offset = page.findValuePosition(tid);
4 ✔
1332
        if (offset < 0) {
2 ✔
1333
            LOG.warn("TID: {} not found on page: {}", tid, page.getPageNum());
8 ✔
1334
            return;
1 ✔
1335
        }
1336
        final int l = ByteConversion.byteToInt(page.data, offset);
5 ✔
1337
        final int end = offset + 4 + l;
6 ✔
1338
        int len = page.ph.getDataLength();
4 ✔
1339
        // remove old value
1340
        System.arraycopy(page.data, end, page.data, offset - 2, len - end);
12 ✔
1341
        page.ph.setDirty(true);
4 ✔
1342
        page.ph.decRecordCount();
3 ✔
1343
        len = len - l - 6;
6 ✔
1344
        page.ph.setDataLength(len);
4 ✔
1345
        if (loggable != null) {
2 ✔
1346
            page.ph.setLsn(loggable.getLsn());
5 ✔
1347
        }
1348
        page.setDirty(true);
3 ✔
1349
        if (len > 0) {
2 ✔
1350
            page.removeTID(tid, l + 6);
6 ✔
1351
            // adjust free space data
1352
            final int newFree = fileHeader.getWorkSize() - len;
6 ✔
1353
            if (newFree > minFree) {
4 ✔
1354
                FreeSpace free = fileHeader.getFreeSpace(page.getPageNum());
6 ✔
1355
                if (free == null) {
2 ✔
1356
                    free = new FreeSpace(page.getPageNum(), newFree);
7 ✔
1357
                    fileHeader.addFreeSpace(free);
4 ✔
1358
                } else {
1 ✔
1359
                    free.setFree(newFree);
3 ✔
1360
                }
1361
            }
1362
            dataCache.add(page, 2);
5 ✔
1363
        }
1364
    }
1 ✔
1365

1366
    private DataPage createPageHelper(final Loggable loggable, final long newPage, final boolean reuseDeleted) {
1367
        try {
1368
            DataPage dp = (DataPage) dataCache.get(newPage);
6 ✔
1369
            if (dp == null) {
2 !
1370
                final Page page = getPage(newPage);
4 ✔
1371
                byte[] data = page.read();
3 ✔
1372
                if (page.getPageHeader().getLsn().equals(Lsn.LSN_INVALID) || (loggable != null && requiresRedo(loggable, page)) ) {
13 !
1373
                    if (reuseDeleted) {
2 !
1374
                        reuseDeleted(page);
×
1375
                    } else {
×
1376
                        dropFreePageList();
2 ✔
1377
                    }
1378
                    final BFilePageHeader ph = (BFilePageHeader) page.getPageHeader();
4 ✔
1379
                    ph.setStatus(RECORD);
3 ✔
1380
                    ph.setDataLength(0);
3 ✔
1381
                    ph.setDataLen(fileHeader.getWorkSize());
5 ✔
1382
                    data = new byte[fileHeader.getWorkSize()];
5 ✔
1383
                    ph.nextTID = 32;
3 ✔
1384
                    dp = new SinglePage(page, data, true);
8 ✔
1385
                } else {
1 ✔
1386
                    dp = new SinglePage(page, data, true);
8 ✔
1387
                }
1388
            }
1389
            if (loggable != null && loggable.getLsn().compareTo(dp.getPageHeader().getLsn()) > 0) {
9 !
1390
                dp.getPageHeader().setLsn(loggable.getLsn());
5 ✔
1391
            }
1392
            dp.setDirty(true);
3 ✔
1393
            dataCache.add(dp);
4 ✔
1394
            return dp;
2 ✔
1395
        } catch (final IOException e) {
×
1396
            LOG.warn("An IOException occurred during redo: {}", e.getMessage(), e);
×
1397
        }
1398
        return null;
×
1399
    }
1400

1401
    /**
1402
     * The file header. Most important, the file header stores the list of
1403
     * data pages containing unused space.
1404
     * 
1405
     * @author wolf
1406
     */
1407
    private final class BFileHeader extends BTreeFileHeader {
1408

1409
        private final FreeList freeList = new FreeList();
5 ✔
1410
        
1411
        //public final static int MAX_FREE_LIST_LEN = 128;
1412

1413
        public BFileHeader(final int pageSize) {
3 ✔
1414
            super(pageSize);
4 ✔
1415
        }
1 ✔
1416

1417
        public void addFreeSpace(final FreeSpace freeSpace) {
1418
            freeList.add(freeSpace);
4 ✔
1419
            setDirty(true);
3 ✔
1420
        }
1 ✔
1421

1422
        public FreeSpace findFreeSpace(final int needed) {
1423
                return freeList.find(needed);
5 ✔
1424
        }
1425

1426
        public FreeSpace getFreeSpace(final long page) {
1427
            return freeList.retrieve(page);
5 ✔
1428
        }
1429

1430
        public void removeFreeSpace(final FreeSpace space) {
1431
            if (space == null) {
2 !
1432
                return;
×
1433
            }
1434
            freeList.remove(space);
4 ✔
1435
            setDirty(true);
3 ✔
1436
        }
1 ✔
1437

1438
        public void debugFreeList() {
1439
            LOG.debug("{}: {}", FileUtils.fileName(getFile()), freeList.toString());
×
1440
        }
×
1441

1442
        @Override
1443
        public int read(final byte[] buf) throws IOException {
1444
            final int offset = super.read(buf);
4 ✔
1445
            return freeList.read(buf, offset);
6 ✔
1446
        }
1447

1448
        @Override
1449
        public int write(final byte[] buf) throws IOException {
1450
            final int offset = super.write(buf);
4 ✔
1451
            return freeList.write(buf, offset);
6 ✔
1452
        }
1453
    }
1454

1455
    private static final class BFilePageHeader extends BTreePageHeader {
1456

1457
        private int dataLen = 0;
3 ✔
1458

1459
        private long lastInChain = -1L;
3 ✔
1460

1461
        private long nextInChain = -1L;
3 ✔
1462

1463
        // tuple identifier: used to identify distinct
1464
        // values inside a page
1465
        private short nextTID = -1;
3 ✔
1466

1467
        private short records = 0;
3 ✔
1468

1469
        public BFilePageHeader() {
1470
            super();
2 ✔
1471
        }
1 ✔
1472

1473
        public void decRecordCount() {
1474
            records--;
7 ✔
1475
        }
1 ✔
1476

1477
        public int getDataLength() {
1478
            return dataLen;
3 ✔
1479
        }
1480

1481
        public long getLastInChain() {
1482
            return lastInChain;
3 ✔
1483
        }
1484

1485
        public long getNextInChain() {
1486
            return nextInChain;
3 ✔
1487
        }
1488

1489
        public short getNextTID() {
1490
            if (nextTID == Short.MAX_VALUE) {
×
1491
                LOG.warn("tid limit reached");
×
1492
                return -1;
×
1493
            }
1494
            return ++nextTID;
×
1495
        }
1496

1497
        public short getCurrentTID() {
1498
            if(nextTID == Short.MAX_VALUE) {
×
1499
                return -1;
×
1500
            }
1501
            return nextTID;
×
1502
        }
1503

1504
        public short getRecordCount() {
1505
            return records;
×
1506
        }
1507

1508
        public void incRecordCount() {
1509
            records++;
7 ✔
1510
        }
1 ✔
1511

1512
        @Override
1513
        public int read(final byte[] data, int offset) throws IOException {
1514
            offset = super.read(data, offset);
5 ✔
1515
            records = ByteConversion.byteToShort(data, offset);
5 ✔
1516
            offset += LENGTH_RECORDS_COUNT;
1 ✔
1517
            dataLen = ByteConversion.byteToInt(data, offset);
5 ✔
1518
            offset += 4;
1 ✔
1519
            nextTID = ByteConversion.byteToShort(data, offset);
5 ✔
1520
            offset += LENGTH_NEXT_TID;
1 ✔
1521
            nextInChain = ByteConversion.byteToLong(data, offset);
5 ✔
1522
            offset += 8;
1 ✔
1523
            lastInChain = ByteConversion.byteToLong(data, offset);
5 ✔
1524
            return offset + 8;
4 ✔
1525
        }
1526

1527
        public void setDataLength(final int len) {
1528
            dataLen = len;
3 ✔
1529
        }
1 ✔
1530

1531
        public void setLastInChain(final long p) {
1532
            lastInChain = p;
3 ✔
1533
        }
1 ✔
1534

1535
        public void setNextInChain(final long b) {
1536
            nextInChain = b;
3 ✔
1537
        }
1 ✔
1538

1539
        public void setRecordCount(final short recs) {
1540
            records = recs;
3 ✔
1541
        }
1 ✔
1542

1543
        public void setTID(final short tid) {
1544
            this.nextTID = tid;
3 ✔
1545
        }
1 ✔
1546

1547
        @Override
1548
        public int write(final byte[] data, int offset) throws IOException {
1549
            offset = super.write(data, offset);
5 ✔
1550
            ByteConversion.shortToByte(records, data, offset);
6 ✔
1551
            offset += LENGTH_RECORDS_COUNT;
1 ✔
1552
            ByteConversion.intToByte(dataLen, data, offset);
6 ✔
1553
            offset += 4;
1 ✔
1554
            ByteConversion.shortToByte(nextTID, data, offset);
6 ✔
1555
            offset += LENGTH_NEXT_TID;
1 ✔
1556
            ByteConversion.longToByte(nextInChain, data, offset);
6 ✔
1557
            offset += 8;
1 ✔
1558
            ByteConversion.longToByte(lastInChain, data, offset);
6 ✔
1559
            return offset + 8;
4 ✔
1560
        }
1561
    }
1562

1563
    private abstract class DataPage implements Comparable, Cacheable {
6 ✔
1564
        private int refCount = 0;
3 ✔
1565
        private int timestamp = 0;
3 ✔
1566
        private boolean saved = true;
3 ✔
1567

1568
        public abstract void delete() throws IOException;
1569

1570
        public abstract byte[] getData() throws IOException;
1571

1572
        public abstract BFilePageHeader getPageHeader();
1573

1574
        public abstract String getPageInfo();
1575

1576
        public abstract long getPageNum();
1577

1578
        public abstract int findValuePosition(short tid) throws IOException;
1579

1580
        public abstract short getNextTID();
1581

1582
        public abstract void removeTID(short tid, int length) throws IOException;
1583

1584
        public abstract void setOffset(short tid, int offset);
1585

1586
        @Override
1587
        public long getKey() {
1588
            return getPageNum();
3 ✔
1589
        }
1590

1591
        @Override
1592
        public int getReferenceCount() {
1593
            return refCount;
×
1594
        }
1595

1596
        @Override
1597
        public int incReferenceCount() {
1598
            if (refCount < Cacheable.MAX_REF) {++refCount;}
×
1599
            return refCount;
×
1600
        }
1601

1602
        @Override
1603
        public int decReferenceCount() {
1604
            return refCount > 0 ? --refCount : 0;
×
1605
        }
1606

1607
        @Override
1608
        public void setReferenceCount(final int count) {
1609
            refCount = count;
3 ✔
1610
        }
1 ✔
1611

1612
        @Override
1613
        public void setTimestamp(final int timestamp) {
1614
            this.timestamp = timestamp;
×
1615
        }
×
1616

1617
        @Override
1618
        public int getTimestamp() {
1619
            return timestamp;
×
1620
        }
1621

1622
        @Override
1623
        public boolean sync(final boolean syncJournal) {
1624
            if (isDirty()) {
3 ✔
1625
                try {
1626
                    write();
2 ✔
1627
                    if (isRecoveryEnabled() && syncJournal && logManager.isPresent() && logManager.get().lastWrittenLsn().compareTo(getPageHeader().getLsn()) < 0) {
22 !
1628
                        logManager.ifPresent(l -> l.flush(true, false));
10 ✔
1629
                    }
1630
                    return true;
2 ✔
1631
                } catch (final IOException e) {
×
1632
                    LOG.error("IO exception occurred while saving page {}", getPageNum());
×
1633
                }
1634
            }
1635
            return false;
2 ✔
1636
        }
1637

1638
        @Override
1639
        public boolean isDirty() {
1640
            return !saved;
7 ✔
1641
        }
1642

1643
        @Override
1644
        public boolean allowUnload() {
1645
            return true;
2 ✔
1646
        }
1647

1648
        public abstract void setData(byte[] buf);
1649

1650
        public abstract SinglePage getFirstPage();
1651

1652
        public void setDirty(final boolean dirty) {
1653
            saved = !dirty;
7 ✔
1654
            getPageHeader().setDirty(dirty);
4 ✔
1655
        }
1 ✔
1656

1657
        public abstract void write() throws IOException;
1658

1659
        @Override
1660
        public int compareTo(final Object other) {
1661
            if (getPageNum() == ((DataPage) other).getPageNum()) {
×
1662
                return Constants.EQUAL;
×
1663
            } else if (getPageNum() > ((DataPage) other).getPageNum()) {
×
1664
                return Constants.SUPERIOR;
×
1665
            } else {
1666
                return Constants.INFERIOR;
×
1667
            }
1668
        }
1669
    }
1670

1671
    private final class FilterCallback implements BTreeCallback {
1672
        private final BFileCallback callback;
1673

1674
        public FilterCallback(final BFileCallback callback) {
×
1675
            this.callback = callback;
×
1676
        }
×
1677

1678
        @Override
1679
        public boolean indexInfo(final Value value, final long pointer) throws TerminatedException{
1680
            try {
1681
                final long pos = StorageAddress.pageFromPointer(pointer);
×
1682
                final short tid = StorageAddress.tidFromPointer(pointer);
×
1683
                final DataPage page = getDataPage(pos);
×
1684
                final int offset = page.findValuePosition(tid);
×
1685
                final byte[] data = page.getData();
×
1686
                final int l = ByteConversion.byteToInt(data, offset);
×
1687
                final Value v = new Value(data, offset + 4, l);
×
1688
                callback.info(value, v);
×
1689
                return true;
×
1690
            } catch (final IOException e) {
×
1691
                LOG.error(e.getMessage(), e);
×
1692
                return true;
×
1693
            }
1694
        }
1695
    }
1696

1697
    private final class FindCallback implements BTreeCallback {
1698
        public final static int BOTH = 2;
1699
        public final static int KEYS = 1;
1700
        public final static int VALUES = 0;
1701

1702
        private final int mode;
1703
        private final IndexCallback callback;
1704
        private final ArrayList<Value> values;
1705

1706
        public FindCallback(final int mode) {
5 ✔
1707
            this.mode = mode;
3 ✔
1708
            this.callback = null;
3 ✔
1709
            this.values = new ArrayList<>();
5 ✔
1710
        }
1 ✔
1711

1712
        public FindCallback(final IndexCallback callback) {
×
1713
            this.mode = BOTH;
×
1714
            this.callback = callback;
×
1715
            this.values = null;
×
1716
        }
×
1717

1718
        public ArrayList<Value> getValues() {
1719
            return values;
3 ✔
1720
        }
1721

1722
        public boolean indexInfo(final Value value, final long pointer) throws TerminatedException {
1723
            final long pos;
1724
            final short tid;
1725
            final DataPage page;
1726
            final int offset;
1727
            final int l;
1728
            final Value v;
1729
            byte[] data;
1730
            try {
1731
                switch (mode) {
3 !
1732
                    case VALUES:
1733
                        pos = StorageAddress.pageFromPointer(pointer);
×
1734
                        tid = StorageAddress.tidFromPointer(pointer);
×
1735
                        page = getDataPage(pos);
×
1736
                        dataCache.add(page.getFirstPage());
×
1737
                        offset = page.findValuePosition(tid);
×
1738
                        data = page.getData();
×
1739
                        l = ByteConversion.byteToInt(data, offset);
×
1740
                        v = new Value(data, offset + 4, l);
×
1741
                        v.setAddress(pointer);
×
1742
                        if (callback == null) {
×
1743
                            values.add(v);
×
1744
                        } else {
×
1745
                            return callback.indexInfo(value, v);
×
1746
                        }
1747
                        return true;
×
1748

1749
                    case KEYS:
1750
                        value.setAddress(pointer);
3 ✔
1751
                        if (callback == null) {
3 !
1752
                            values.add(value);
5 ✔
1753
                        } else {
1 ✔
1754
                            return callback.indexInfo(value, null);
×
1755
                        }
1756
                        return true;
2 ✔
1757

1758
                    case BOTH:
1759
                        final Value[] entry = new Value[2];
×
1760
                        entry[0] = value;
×
1761
                        pos = StorageAddress.pageFromPointer(pointer);
×
1762
                        tid = StorageAddress.tidFromPointer(pointer);
×
1763
                        page = getDataPage(pos);
×
1764
                        if (page.getPageHeader().getStatus() == MULTI_PAGE) {
×
1765
                            data = page.getData();
×
1766
                        }
1767
                        dataCache.add(page.getFirstPage());
×
1768
                        offset = page.findValuePosition(tid);
×
1769
                        data = page.getData();
×
1770
                        l = ByteConversion.byteToInt(data, offset);
×
1771
                        v = new Value(data, offset + 4, l);
×
1772
                        v.setAddress(pointer);
×
1773
                        entry[1] = v;
×
1774
                        if (callback == null) {
×
1775
                            values.add(entry[0]);
×
1776
                            values.add(entry[1]);
×
1777
                        } else {
×
1778
                            return callback.indexInfo(value, v);
×
1779
                        }
1780

1781
                        return true;
×
1782
                }
1783
            } catch (final IOException e) {
×
1784
                LOG.error(e.getMessage(), e);
×
1785
            }
1786

1787
            return false;
×
1788
        }
1789
    }
1790

1791
    private final class OverflowPage extends DataPage {
1792
        private final SinglePage firstPage;
1793
        private byte[] data = null;
9 ✔
1794

1795
        public OverflowPage(final Txn transaction) throws IOException {
6 ✔
1796
            firstPage = new SinglePage(false);
7 ✔
1797
            if (transaction != null && isRecoveryEnabled()) {
5 !
1798
                final Loggable loggable = new OverflowCreateLoggable(fileId, transaction, firstPage.getPageNum());
10 ✔
1799
                writeToLog(loggable, firstPage);
5 ✔
1800
            }
1801
            final BFilePageHeader ph = firstPage.getPageHeader();
4 ✔
1802
            ph.setStatus(MULTI_PAGE);
3 ✔
1803
            ph.setNextInChain(0L);
3 ✔
1804
            ph.setLastInChain(0L);
3 ✔
1805
            ph.setDataLength(0);
3 ✔
1806
            firstPage.setData(new byte[fileHeader.getWorkSize()]);
7 ✔
1807
            dataCache.add(firstPage, 3);
6 ✔
1808
        }
1 ✔
1809

1810
        public OverflowPage(final DataPage page) {
6 ✔
1811
            firstPage = (SinglePage) page;
4 ✔
1812
        }
1 ✔
1813

1814
        public OverflowPage(final Page p, final byte[] data) throws IOException {
6 ✔
1815
            firstPage = new SinglePage(p, data, false);
9 ✔
1816
            firstPage.getPageHeader().setStatus(MULTI_PAGE);
5 ✔
1817
        }
1 ✔
1818

1819
        /**
1820
         * Append a new chunk of data to the page
1821
         *
1822
         * @param transaction the database transaction
1823
         * @param chunk chunk of data to append
1824
         */
1825
        public void append(final Txn transaction, final ByteArray chunk) throws IOException {
1826
            SinglePage nextPage;
1827
            BFilePageHeader ph = firstPage.getPageHeader();
4 ✔
1828
            final int newLen = ph.getDataLength() + chunk.size();
6 ✔
1829
            // get the last page and fill it
1830
            final long next = ph.getLastInChain();
3 ✔
1831
            DataPage page;
1832
            if (next > 0) {
4 ✔
1833
                page = getDataPage(next, false);
6 ✔
1834
            } else {
1 ✔
1835
                page = firstPage;
3 ✔
1836
            }
1837
            ph = page.getPageHeader();
3 ✔
1838
            int chunkSize = fileHeader.getWorkSize() - ph.getDataLength();
8 ✔
1839
            final int chunkLen = chunk.size();
3 ✔
1840
            if (chunkLen < chunkSize) {chunkSize = chunkLen;}
5 ✔
1841
            // fill last page
1842
            if (transaction != null && isRecoveryEnabled()) {
6 !
1843
                final Loggable loggable = 
1 ✔
1844
                    new OverflowAppendLoggable(fileId, transaction, page.getPageNum(), chunk, 0, chunkSize);
12 ✔
1845
                writeToLog(loggable, page);
5 ✔
1846
            }
1847
            chunk.copyTo(0, page.getData(), ph.getDataLength(), chunkSize);
8 ✔
1848
            if(page != firstPage) {
4 ✔
1849
                ph.setDataLength(ph.getDataLength() + chunkSize);
6 ✔
1850
            }
1851
            page.setDirty(true);
3 ✔
1852
            // write the remaining chunks to new pages
1853
            int remaining = chunkLen - chunkSize;
4 ✔
1854
            int current = chunkSize;
2 ✔
1855
            chunkSize = fileHeader.getWorkSize();
5 ✔
1856
            if (remaining > 0) {
2 ✔
1857
                // walk through chain of pages
1858
                while (remaining > 0) {
3 ✔
1859
                    if (remaining < chunkSize) {
3 !
1860
                        chunkSize = remaining;
2 ✔
1861
                    }
1862
                    
1863
                    // add a new page to the chain
1864
                    nextPage = createDataPage();
4 ✔
1865
                    if (transaction != null && isRecoveryEnabled()) {
6 !
1866
                        Loggable loggable = new OverflowCreatePageLoggable(transaction, fileId, nextPage.getPageNum(),
10 ✔
1867
                                page.getPageNum());
2 ✔
1868
                        writeToLog(loggable, nextPage);
5 ✔
1869
                        
1870
                        loggable = new OverflowAppendLoggable(fileId, transaction, nextPage.getPageNum(),
10 ✔
1871
                                chunk, current, chunkSize);
3 ✔
1872
                        writeToLog(loggable, page);
5 ✔
1873
                    }
1874
                    nextPage.setData(new byte[fileHeader.getWorkSize()]);
7 ✔
1875
                    page.getPageHeader().setNextInChain(nextPage.getPageNum());
5 ✔
1876
                    page.setDirty(true);
3 ✔
1877
                    dataCache.add(page);
5 ✔
1878
                    page = nextPage;
2 ✔
1879
                    // copy next chunk of data to the page
1880
                    chunk.copyTo(current, page.getData(), 0, chunkSize);
7 ✔
1881
                    page.setDirty(true);
3 ✔
1882
                    if (page != firstPage) {
4 !
1883
                        page.getPageHeader().setDataLength(chunkSize);
4 ✔
1884
                    }
1885
                    remaining = remaining - chunkSize;
4 ✔
1886
                    current += chunkSize;
4 ✔
1887
                }
1888
            }
1889
            ph = firstPage.getPageHeader();
4 ✔
1890
            if (transaction != null && isRecoveryEnabled()) {
6 !
1891
                final Loggable loggable = new OverflowModifiedLoggable(fileId, transaction, firstPage.getPageNum(), 
11 ✔
1892
                        ph.getDataLength() + chunkLen, ph.getDataLength(), page == firstPage ? 0 : page.getPageNum());
14 ✔
1893
                writeToLog(loggable, page);
5 ✔
1894
            }
1895
            if (page != firstPage) {
4 ✔
1896
                // add link to last page
1897
                dataCache.add(page);
5 ✔
1898
                ph.setLastInChain(page.getPageNum());
4 ✔
1899
                
1900
            } else {
1 ✔
1901
                ph.setLastInChain(0L);
3 ✔
1902
            }
1903
            // adjust length field in first page
1904
            ph.setDataLength(newLen);
3 ✔
1905
            ByteConversion.intToByte(firstPage.getPageHeader().getDataLength() - 6, firstPage.getData(), 2);
12 ✔
1906
            firstPage.setDirty(true);
4 ✔
1907
            // keep the first page in cache
1908
            dataCache.add(firstPage, 2);
7 ✔
1909
        }
1 ✔
1910

1911
        @Override
1912
        public void delete() throws IOException {
1913
            delete(null);
×
1914
        }
×
1915

1916
        public void delete(final Txn transaction) throws IOException {
1917
            long next = firstPage.getPageNum();
4 ✔
1918
            SinglePage page = firstPage;
3 ✔
1919
            do {
2 ✔
1920
                next = page.ph.getNextInChain();
4 ✔
1921
                if (transaction != null && isRecoveryEnabled()) {
6 !
1922
                    int dataLen = page.ph.getDataLength();
4 ✔
1923
                    if (dataLen > fileHeader.getWorkSize()) {
6 ✔
1924
                        dataLen = fileHeader.getWorkSize();
5 ✔
1925
                    }
1926
                    final Loggable loggable = new OverflowRemoveLoggable(fileId, transaction, 
8 ✔
1927
                            page.ph.getStatus(), page.getPageNum(),
5 ✔
1928
                            page.getData(), dataLen, 
3 ✔
1929
                            page.ph.getNextInChain());
3 ✔
1930
                    writeToLog(loggable, page);
5 ✔
1931
                }
1932
                
1933
                page.getPageHeader().setNextInChain(-1L);
4 ✔
1934
                page.setDirty(true);
3 ✔
1935
                dataCache.remove(page);
5 ✔
1936
                page.delete();
2 ✔
1937
                if (next > 0) {
4 ✔
1938
                    page = getSinglePage(next);
5 ✔
1939
                }
1940
            } while (next > 0);
2 ✔
1941
        }
1 ✔
1942

1943
        public VariableByteInput getDataStream(final long pointer) {
1944
            final MultiPageInput input = new MultiPageInput(firstPage, pointer);
9 ✔
1945
            return input;
2 ✔
1946
        }
1947

1948
        @Override
1949
        public byte[] getData() throws IOException {
1950
            if (data != null) {
×
1951
                return data;
×
1952
            }
1953

1954
            SinglePage page = firstPage;
×
1955
            long next;
1956
            byte[] temp;
1957
            int len;
1958

1959
            try(final UnsynchronizedByteArrayOutputStream os = new UnsynchronizedByteArrayOutputStream(page.getPageHeader().getDataLength())) {
×
1960
                do {
×
1961
                    temp = page.getData();
×
1962
                    next = page.getPageHeader().getNextInChain();
×
1963
                    len = next > 0 ? fileHeader.getWorkSize() : page
×
1964
                            .getPageHeader().getDataLength();
×
1965
                    os.write(temp, 0, len);
×
1966

1967
                    if (next > 0) {
×
1968
                        page = (SinglePage) getDataPage(next, false);
×
1969
                        dataCache.add(page);
×
1970
                    }
1971
                } while (next > 0);
×
1972
                data = os.toByteArray();
×
1973
                if (data.length != firstPage.getPageHeader().getDataLength()) {
×
1974
                    LOG.warn("{} read={}; expected={}", FileUtils.fileName(getFile()), data.length, firstPage.getPageHeader().getDataLength());
×
1975
                }
1976
                return data;
×
1977
            }
1978
        }
1979

1980
        @Override
1981
        public SinglePage getFirstPage() {
1982
            return firstPage;
×
1983
        }
1984

1985
        @Override
1986
        public BFilePageHeader getPageHeader() {
1987
            return firstPage.getPageHeader();
4 ✔
1988
        }
1989

1990
        @Override
1991
        public String getPageInfo() {
1992
            return "MULTI_PAGE: " + firstPage.getPageInfo();
×
1993
        }
1994

1995
        @Override
1996
        public long getPageNum() {
1997
            return firstPage.getPageNum();
4 ✔
1998
        }
1999

2000
        @Override
2001
        public void setData(final byte[] buf) {
2002
            setData(null, buf);
×
2003
        }
×
2004

2005
        public void setData(final Txn transaction, final byte[] data) {
2006
            this.data = data;
3 ✔
2007
            try {
2008
                write(transaction);
3 ✔
2009
            } catch (final IOException e) {
1 ✔
2010
                LOG.error(e);
×
2011
            }
2012
        }
1 ✔
2013

2014
        @Override
2015
        public void write() throws IOException {
2016
            write(null);
×
2017
        }
×
2018

2019
        public void write(final Txn transaction) throws IOException {
2020
            if (data == null) {
3 !
2021
                return;
×
2022
            }
2023

2024
            int chunkSize = fileHeader.getWorkSize();
5 ✔
2025
            int remaining = data.length;
4 ✔
2026
            int current = 0;
2 ✔
2027
            long next = 0L;
2 ✔
2028
            SinglePage page = firstPage;
3 ✔
2029
            page.getPageHeader().setDataLength(remaining);
4 ✔
2030
            SinglePage nextPage;
2031
            long prevPageNum = Page.NO_PAGE;
2 ✔
2032
            // walk through chain of pages
2033
            while (remaining > 0) {
3 ✔
2034
                if (remaining < chunkSize) {
3 ✔
2035
                    chunkSize = remaining;
2 ✔
2036
                }
2037
                page.clear();
2 ✔
2038
                // copy next chunk of data to the page
2039
                if (transaction != null && isRecoveryEnabled()) {
6 !
2040
                    final Loggable loggable = new OverflowStoreLoggable(fileId, transaction, page.getPageNum(), prevPageNum,
11 ✔
2041
                            data, current, chunkSize);
4 ✔
2042
                    writeToLog(loggable, page);
5 ✔
2043
                }
2044
                System.arraycopy(data, current, page.getData(), 0, chunkSize);
8 ✔
2045
                if (page != firstPage) {
4 ✔
2046
                    page.getPageHeader().setDataLength(chunkSize);
4 ✔
2047
                }
2048
                page.setDirty(true);
3 ✔
2049
                remaining -= chunkSize;
4 ✔
2050
                current += chunkSize;
4 ✔
2051
                next = page.getPageHeader().getNextInChain();
4 ✔
2052
                if (remaining > 0) {
2 ✔
2053
                    if (next > 0) {
4 ✔
2054
                        // load next page in chain
2055
                        nextPage = (SinglePage) getDataPage(next, false);
7 ✔
2056
                        dataCache.add(page);
5 ✔
2057
                        prevPageNum = page.getPageNum();
3 ✔
2058
                        page = nextPage;
2 ✔
2059
                    } else {
1 ✔
2060
                        // add a new page to the chain
2061
                        nextPage = createDataPage();
4 ✔
2062
                        if (transaction != null && isRecoveryEnabled()) {
6 !
2063
                            final Loggable loggable = new CreatePageLoggable(transaction, fileId, nextPage.getPageNum());
10 ✔
2064
                            writeToLog(loggable, nextPage);
5 ✔
2065
                        }
2066
                        nextPage.setData(new byte[fileHeader.getWorkSize()]);
7 ✔
2067
                        nextPage.getPageHeader().setNextInChain(0L);
4 ✔
2068
                        page.getPageHeader().setNextInChain(
3 ✔
2069
                                nextPage.getPageNum());
2 ✔
2070
                        dataCache.add(page);
5 ✔
2071
                        prevPageNum = page.getPageNum();
3 ✔
2072
                        page = nextPage;
2 ✔
2073
                    }
2074
                } else {
1 ✔
2075
                    page.getPageHeader().setNextInChain(0L);
4 ✔
2076
                    if (page != firstPage) {
4 ✔
2077
                        page.setDirty(true);
3 ✔
2078
                        dataCache.add(page);
5 ✔
2079
                        firstPage.getPageHeader().setLastInChain(
4 ✔
2080
                                page.getPageNum());
2 ✔
2081
                    } else {
1 ✔
2082
                        firstPage.getPageHeader().setLastInChain(0L);
5 ✔
2083
                    }
2084
                    firstPage.setDirty(true);
4 ✔
2085
                    dataCache.add(firstPage, 3);
7 ✔
2086
                }
2087
            }
2088
            if (next > 0) {
4 ✔
2089
                // there are more pages in the chain:
2090
                // remove them
2091
                while (next > 0) {
5 ✔
2092
                    nextPage = (SinglePage) getDataPage(next, false);
7 ✔
2093
                    
2094
                    next = nextPage.getPageHeader().getNextInChain();
4 ✔
2095
                    
2096
                    if (transaction != null && isRecoveryEnabled()) {
6 !
2097
                        final Loggable loggable = new OverflowRemoveLoggable(fileId, transaction, 
8 ✔
2098
                                nextPage.getPageHeader().getStatus(), nextPage.getPageNum(),
5 ✔
2099
                                nextPage.getData(), nextPage.getPageHeader().getDataLength(), 
5 ✔
2100
                                nextPage.getPageHeader().getNextInChain());
3 ✔
2101
                        writeToLog(loggable, nextPage);
5 ✔
2102
                    }
2103
                    
2104
                    nextPage.setDirty(true);
3 ✔
2105
                    nextPage.delete();
2 ✔
2106
                    dataCache.remove(nextPage);
5 ✔
2107
                }
2108
            }
2109
            firstPage.getPageHeader().setDataLength(data.length);
7 ✔
2110
            firstPage.setDirty(true);
4 ✔
2111
            dataCache.add(firstPage, 3);
7 ✔
2112
//            LOG.debug(firstPage.getPageNum() + " data length: " + firstPage.ph.getDataLength());
2113
        }
1 ✔
2114

2115
        @Override
2116
        public int findValuePosition(final short tid) throws IOException {
2117
            return 2;
×
2118
        }
2119

2120
        @Override
2121
        public short getNextTID() {
2122
            return 1;
×
2123
        }
2124

2125
        @Override
2126
        public void removeTID(final short tid, final int length) {
2127
            //
2128
        }
×
2129

2130
        @Override
2131
        public void setOffset(final short tid, final int offset) {
2132
            //
2133
        }
×
2134
    }
2135

2136
    public interface PageInputStream {
2137
        long getAddress();
2138
        long position();
2139
        void seek(long position) throws IOException;
2140
    }
2141

2142
    /**
2143
     * Variable byte input stream to read data from a single page.
2144
     * 
2145
     * @author wolf
2146
     */
2147
    private final static class SimplePageInput extends VariableByteArrayInput
2148
            implements PageInputStream {
2149

2150
        private final long address;
2151
        
2152
        public SimplePageInput(final byte[] data, final int start, final int len, final long address) {
2153
            super(data, start, len);
5 ✔
2154
            this.address = address;
3 ✔
2155
        }
1 ✔
2156

2157
        @Override
2158
        public long getAddress() {
2159
            return address;
×
2160
        }
2161

2162
        @Override
2163
        public long position() {
2164
            return position;
×
2165
        }
2166

2167
        @Override
2168
        public void seek(final long pos) throws IOException {
2169
            this.position = (int) pos;
×
2170
        }
×
2171
    }
2172

2173
    /**
2174
     * Variable byte input stream to read a multi-page sequences.
2175
     * 
2176
     * @author wolf
2177
     */
2178
    private final class MultiPageInput implements VariableByteInput, PageInputStream {
2179
        private SinglePage nextPage;
2180
        private int pageLen;
2181
        private short offset = 0;
3 ✔
2182
        private final long address;
2183

2184
        public MultiPageInput(SinglePage first, long address) {
5 ✔
2185
            nextPage = first;
3 ✔
2186
            offset = 6;
3 ✔
2187
            pageLen = first.ph.getDataLength();
5 ✔
2188
            if (pageLen > fileHeader.getWorkSize()) {
6 !
2189
                pageLen = fileHeader.getWorkSize();
5 ✔
2190
            }
2191
            dataCache.add(first, 3);
5 ✔
2192
            this.address = address;
3 ✔
2193
        }
1 ✔
2194

2195
        @Override
2196
        public long getAddress() {
2197
            return address;
×
2198
        }
2199

2200
        @Override
2201
        public final int read() throws IOException {
2202
            if (offset == pageLen) {
5 !
2203
                advance();
×
2204
            }
2205
            return nextPage.data[offset++] & 0xFF;
15 ✔
2206
        }
2207

2208
        @Override
2209
        public final byte readByte() throws IOException {
2210
            if (offset == pageLen) {
×
2211
                advance();
×
2212
            }
2213
            return nextPage.data[offset++];
×
2214
        }
2215

2216
        @Override
2217
        public final short readShort() throws IOException {
2218
            if (offset == pageLen) {
×
2219
                advance();
×
2220
            }
2221
            byte b = nextPage.data[offset++];
×
2222
            short i = (short) (b & 0177);
×
2223
            for (int shift = 7; (b & 0200) != 0; shift += 7) {
×
2224
                if (offset == pageLen) {
×
2225
                    advance();
×
2226
                }
2227
                b = nextPage.data[offset++];
×
2228
                i |= (b & 0177) << shift;
×
2229
            }
2230
            return i;
×
2231
        }
2232

2233
        @Override
2234
        public final int readInt() throws IOException {
2235
            if (offset == pageLen) {
5 !
2236
                advance();
×
2237
            }
2238
            byte b = nextPage.data[offset++];
13 ✔
2239
            int i = b & 0177;
4 ✔
2240
            for (int shift = 7; (b & 0200) != 0; shift += 7) {
8 ✔
2241
                if (offset == pageLen) {
5 !
2242
                    advance();
×
2243
                }
2244
                b = nextPage.data[offset++];
13 ✔
2245
                i |= (b & 0177) << shift;
8 ✔
2246
            }
2247
            return i;
2 ✔
2248
        }
2249

2250
        @Override
2251
        public int readFixedInt() throws IOException {
2252
            if (offset == pageLen) {
×
2253
                advance();
×
2254
            }
2255
            // do we have to read across a page boundary?
2256
            if (offset + 4 < pageLen) {
×
2257
                return ( nextPage.data[offset++] & 0xff ) |
×
2258
                    ( (nextPage.data[offset++] & 0xff) << 8 ) |
×
2259
                    ( (nextPage.data[offset++] & 0xff) << 16 ) |
×
2260
                    ( (nextPage.data[offset++] & 0xff) << 24 );
×
2261
            }
2262
            int r = nextPage.data[offset++] & 0xff;
×
2263
            int shift = 8;
×
2264
            for (int i = 0; i < 3; i++) {
×
2265
                if (offset == pageLen) {
×
2266
                    advance();
×
2267
                }
2268
                r |= (nextPage.data[offset++] & 0xff) << shift;
×
2269
                shift += 8;
×
2270
            }
2271
            return r;
×
2272
        }
2273

2274
        @Override
2275
        public final long readLong() throws IOException {
2276
            if (offset == pageLen) {
5 !
2277
                advance();
×
2278
            }
2279
            byte b = nextPage.data[offset++];
13 ✔
2280
            long i = b & 0177;
5 ✔
2281
            for (int shift = 7; (b & 0200) != 0; shift += 7) {
8 ✔
2282
                if (offset == pageLen) {
5 !
2283
                    advance();
×
2284
                }
2285
                b = nextPage.data[offset++];
13 ✔
2286
                i |= (b & 0177L) << shift;
9 ✔
2287
            }
2288
            return i;
2 ✔
2289
        }
2290

2291
        @Override
2292
        public final void skip(final int count) throws IOException {
2293
            for (int i = 0; i < count; i++) {
×
2294
                do {
×
2295
                    if (offset == pageLen) {
×
2296
                        advance();
×
2297
                    }
2298
                } while ((nextPage.data[offset++] & 0200) > 0);
×
2299
            }
2300
        }
×
2301

2302
        @Override
2303
        public final void skipBytes(final long count) throws IOException {
2304
            for(long i = 0; i < count; i++) {
×
2305
                if (offset == pageLen) {
×
2306
                    advance();
×
2307
                }
2308
                offset++;
×
2309
            }
2310
        }
×
2311

2312
        private final void advance() throws IOException {
2313
            final long next = nextPage.getPageHeader().getNextInChain();
×
2314
            if (next < 1) {
×
2315
                pageLen = -1;
×
2316
                offset = 0;
×
2317
                throw new EOFException();
×
2318
            }
2319

2320

2321
            try(final ManagedLock<ReentrantLock> bfileLock = lockManager.acquireBtreeReadLock(getLockName())) {
×
2322
                nextPage = (SinglePage) getDataPage(next, false);
×
2323
                pageLen = nextPage.ph.getDataLength();
×
2324
                offset = 0;
×
2325
                dataCache.add(nextPage);
×
2326
            } catch (final LockException e) {
×
2327
                throw new IOException("failed to acquire a read lock on "
×
2328
                        + FileUtils.fileName(getFile()));
×
2329
            }
2330
        }
×
2331

2332
        @Override
2333
        public final int available() throws IOException {
2334
            if (pageLen < 0) {
×
2335
                return 0;
×
2336
            }
2337
            int inPage = pageLen - offset;
×
2338
            if (inPage == 0) {
×
2339
                inPage = nextPage.getPageHeader().getNextInChain() > 0 ? 1 : 0;
×
2340
            }
2341
            return inPage;
×
2342
        }
2343

2344
        @Override
2345
        public final int read(final byte[] data) throws IOException {
2346
            return read(data, 0, data.length);
7 ✔
2347
        }
2348

2349
        @Override
2350
        public final int read(final byte[] b, final int off, final int len) throws IOException {
2351
            if (pageLen < 0) {
3 !
2352
                return -1;
×
2353
            }
2354

2355
            for (int i = 0; i < len; i++) {
7 ✔
2356
                if (offset == pageLen) {
5 ✔
2357
                    final long next = nextPage.getPageHeader().getNextInChain();
5 ✔
2358
                    if (next < 1) {
4 !
2359
                        pageLen = -1;
×
2360
                        offset = 0;
×
2361
                        return i;
×
2362
                    }
2363
                    nextPage = (SinglePage) getDataPage(next, false);
8 ✔
2364
                    pageLen = nextPage.ph.getDataLength();
6 ✔
2365
                    offset = 0;
3 ✔
2366
                    dataCache.add(nextPage);
6 ✔
2367
                }
2368
                b[off + i] = nextPage.data[offset++];
17 ✔
2369
            }
2370
            return len;
2 ✔
2371
        }
2372

2373
        @Override
2374
        public final String readUTF() throws IOException {
2375
            final int len = readInt();
3 ✔
2376
            final byte data[] = new byte[len];
3 ✔
2377

2378
            read(data);
4 ✔
2379

2380
            return new String(data, UTF_8);
6 ✔
2381
        }
2382

2383
        @Override
2384
        public final void copyTo(final VariableByteOutputStream os) throws IOException {
2385
            byte more;
2386
            do {
×
2387
                if (offset == pageLen) {
×
2388
                    advance();
×
2389
                }
2390
                more = nextPage.data[offset++];
×
2391
                os.writeByte(more);
×
2392
                more &= 0200;
×
2393
            } while (more > 0);
×
2394
        }
×
2395

2396
        @Override
2397
        public final void copyTo(final VariableByteOutputStream os, final int count) throws IOException {
2398
            byte more;
2399
            for (int i = 0; i < count; i++) {
×
2400
                do {
×
2401
                    if (offset == pageLen) {
×
2402
                        advance();
×
2403
                    }
2404
                    more = nextPage.data[offset++];
×
2405
                    os.writeByte(more);
×
2406
                } while ((more & 0x200) > 0);
×
2407
            }
2408
        }
×
2409

2410
        @Override
2411
        public void copyRaw(final VariableByteOutputStream os, final int count) throws IOException {
2412
            for (int i = count; i != 0; ) {
×
2413
                if (offset == pageLen) {
×
2414
                    advance();
×
2415
                }
2416
                int avail = pageLen - offset;
×
2417
                if (i >= avail) {
×
2418
                    os.write(nextPage.data, offset, avail);
×
2419
                    i -= avail;
×
2420
                    offset = (short) pageLen;
×
2421
                } else {
×
2422
                    os.write(nextPage.data, offset, i);
×
2423
                    offset += i;
×
2424
                    break;
×
2425
                }
2426
                //os.writeByte(nextPage.data[offset++]);
2427
            }
2428
        }
×
2429

2430
        @Override
2431
        public long position() {
2432
            return StorageAddress.createPointer((int) nextPage.getPageNum(), offset);
×
2433
        }
2434

2435
        @Override
2436
        public void seek(final long position) throws IOException {
2437
            final int newPage = StorageAddress.pageFromPointer(position);
×
2438
            final short newOffset = StorageAddress.tidFromPointer(position);
×
2439
            try(final ManagedLock<ReentrantLock> bfileLock =  lockManager.acquireBtreeReadLock(getLockName())) {
×
2440
                nextPage = getSinglePage(newPage);
×
2441
                pageLen = nextPage.ph.getDataLength();
×
2442
                if (pageLen > fileHeader.getWorkSize()) {
×
2443
                    pageLen = fileHeader.getWorkSize();
×
2444
                }
2445
                offset = newOffset;
×
2446
                dataCache.add(nextPage);
×
2447
            } catch (final LockException e) {
×
2448
                throw new IOException("Failed to acquire a read lock on " + FileUtils.fileName(getFile()));
×
2449
            }
2450
        }
×
2451
    }
2452

2453
    /**
2454
     * Represents a single data page (as opposed to a overflow page).
2455
     * 
2456
     * @author <a href="mailto:wolfgang@exist-db.org">Wolfgang Meier</a>
2457
     */
2458
    private final class SinglePage extends DataPage {
2459

2460
        // the raw working data of this page (without page header)
2461
        byte[] data = null;
6 ✔
2462

2463
        // the low-level page
2464
        final Page page;
2465

2466
        // the page header
2467
        final BFilePageHeader ph;
2468

2469
        // table mapping record ids (tids) to offsets
2470
        short[] offsets = null;
6 ✔
2471

2472
        public SinglePage() throws IOException {
2473
            this(true);
4 ✔
2474
        }
1 ✔
2475

2476
        public SinglePage(final boolean compress) throws IOException {
6 ✔
2477
            page = getFreePage();
4 ✔
2478
            ph = (BFilePageHeader) page.getPageHeader();
6 ✔
2479
            ph.setStatus(RECORD);
4 ✔
2480
            ph.setDirty(true);
4 ✔
2481
            ph.setDataLength(0);
4 ✔
2482
            //ph.setNextChunk( -1 );
2483
            data = new byte[fileHeader.getWorkSize()];
6 ✔
2484
            offsets = new short[32];
4 ✔
2485
            ph.nextTID = 32;
4 ✔
2486
            Arrays.fill(offsets, (short)-1);
4 ✔
2487
        }
1 ✔
2488

2489
        public SinglePage(final Page p, final byte[] data, final boolean initialize) throws IOException {
6 ✔
2490
            if (p == null) {
2 !
2491
                throw new IOException("illegal page");
×
2492
            }
2493

2494
            if (!(p.getPageHeader().getStatus() == RECORD || p.getPageHeader()
7 ✔
2495
                    .getStatus() == MULTI_PAGE)) {
3 !
2496
                final IOException e = new IOException("not a data-page: "
×
2497
                        + p.getPageHeader().getStatus());
×
2498
                LOG.debug("not a data-page: {}", p.getPageInfo(), e);
×
2499
                throw e;
×
2500
            }
2501
            this.data = data;
3 ✔
2502
            page = p;
3 ✔
2503
            ph = (BFilePageHeader) page.getPageHeader();
6 ✔
2504
            if(initialize) {
2 ✔
2505
                offsets = new short[ph.nextTID];
6 ✔
2506
                if (ph.getStatus() != MULTI_PAGE) {
5 !
2507
                    readOffsets();
2 ✔
2508
                }
2509
            }
2510
        }
1 ✔
2511

2512
        @Override
2513
        public final int findValuePosition(final short tid) throws IOException {
2514
            return offsets[tid];
5 ✔
2515
        }
2516

2517
        private void readOffsets() {
2518
            //if(offsets.length > 256)
2519
                //LOG.warn("TID size: " + ph.nextTID);
2520
            Arrays.fill(offsets, (short)-1);
4 ✔
2521
            final int dlen = ph.getDataLength();
4 ✔
2522
            for(short pos = 0; pos < dlen; ) {
6 ✔
2523
                final short tid = ByteConversion.byteToShort(data, pos);
5 ✔
2524
                if (tid < 0) {
2 !
2525
                    LOG.error("Invalid tid found: {}; ignoring rest of page ...", tid);
×
2526
                    ph.setDataLength(pos);
×
2527
                    return;
×
2528
                }
2529
                if(tid >= offsets.length) {
5 !
2530
                    LOG.error("Problematic tid found: {}; trying to recover ...", tid);
×
2531
                    final short[] t = new short[tid + 1];
×
2532
                    Arrays.fill(t, (short)-1);
×
2533
                    System.arraycopy(offsets, 0, t, 0, offsets.length);
×
2534
                    offsets = t;
×
2535
                    ph.nextTID = (short)(tid + 1);
×
2536
                }
2537
                offsets[tid] = (short)(pos + 2);
8 ✔
2538
                pos += ByteConversion.byteToInt(data, pos + 2) + 6;
12 ✔
2539
            }
2540
        }
1 ✔
2541

2542
        @Override
2543
        public short getNextTID() {
2544
            for(short i = 0; i < offsets.length; i++) {
13 ✔
2545
                if(offsets[i] == -1) {
6 ✔
2546
                    return i;
2 ✔
2547
                }
2548
            }
2549
            final short tid = (short)offsets.length;
5 ✔
2550
            final short next = (short)(ph.nextTID * 2);
7 ✔
2551
            if(next < 0 || next < ph.nextTID) {
7 !
2552
                return -1;
×
2553
            }
2554
            final short[] t = new short[next];
3 ✔
2555
            Arrays.fill(t, (short)-1);
3 ✔
2556
            System.arraycopy(offsets, 0, t, 0, offsets.length);
9 ✔
2557
            offsets = t;
3 ✔
2558
            ph.nextTID = next;
4 ✔
2559
            return tid;
2 ✔
2560
        }
2561

2562
        public void adjustTID(final short tid) {
2563
            if (tid >= ph.nextTID) {
5 ✔
2564
                final short next = (short)(tid * 2);
5 ✔
2565
                final short[] t = new short[next];
3 ✔
2566
                Arrays.fill(t, (short)-1);
3 ✔
2567
                System.arraycopy(offsets, 0, t, 0, offsets.length);
9 ✔
2568
                offsets = t;
3 ✔
2569
                ph.nextTID = next;
4 ✔
2570
            }
2571
        }
1 ✔
2572

2573
        public void clear() {
2574
            Arrays.fill(data, (byte) 0);
4 ✔
2575
        }
1 ✔
2576

2577
        private String printContents() {
2578
            final StringBuilder buf = new StringBuilder();
×
2579
            for(short i = 0; i < offsets.length; i++) {
×
2580
                if (offsets[i] > -1) {
×
2581
                    buf.append('[').append(i).append(", ").append(offsets[i]);
×
2582
                    final short len = ByteConversion.byteToShort(data, offsets[i]);
×
2583
                    buf.append(", ").append(len).append(']');
×
2584
                }
2585
            }
2586
            return buf.toString();
×
2587
        }
2588

2589
        @Override
2590
        public void setOffset(final short tid, final int offset) {
2591
            if (offsets == null) {
3 !
2592
                LOG.warn("page: {} file: {} status: {}", page.getPageNum(), FileUtils.fileName(getFile()), getPageHeader().getStatus());
×
2593
                throw new RuntimeException("page offsets not initialized");
×
2594
            }
2595
            offsets[tid] = (short)offset;
6 ✔
2596
        }
1 ✔
2597
        
2598
        @Override
2599
        public void removeTID(final short tid, final int length) throws IOException {
2600
            final int offset = offsets[tid] - 2;
7 ✔
2601
            offsets[tid] = -1;
5 ✔
2602
            for(short i = 0; i < offsets.length; i++) {
13 ✔
2603
                if(offsets[i] > offset) {
6 ✔
2604
                    offsets[i] -= length;
9 ✔
2605
                }
2606
            }
2607
            //readOffsets(start);
2608
        }
1 ✔
2609

2610
        @Override
2611
        public void delete() throws IOException {
2612
            // reset page header fields
2613
            ph.setDataLength(0);
4 ✔
2614
            ph.setNextInChain(-1L);
4 ✔
2615
            ph.setLastInChain(-1L);
4 ✔
2616
            ph.setTID((short) -1);
4 ✔
2617
            ph.setRecordCount((short) 0);
4 ✔
2618
            setReferenceCount(0);
3 ✔
2619
            ph.setDirty(true);
4 ✔
2620
            unlinkPages(page);
5 ✔
2621
        }
1 ✔
2622

2623
        @Override
2624
        public SinglePage getFirstPage() {
2625
            return this;
2 ✔
2626
        }
2627

2628
        @Override
2629
        public byte[] getData() {
2630
            return data;
3 ✔
2631
        }
2632

2633
        @Override
2634
        public BFilePageHeader getPageHeader() {
2635
            return ph;
3 ✔
2636
        }
2637

2638
        @Override
2639
        public String getPageInfo() {
2640
            return page.getPageInfo();
×
2641
        }
2642

2643
        @Override
2644
        public long getPageNum() {
2645
            return page.getPageNum();
4 ✔
2646
        }
2647

2648
        @Override
2649
        public void setData(final byte[] buf) {
2650
            data = buf;
3 ✔
2651
        }
1 ✔
2652

2653
        @Override
2654
        public void write() throws IOException {
2655
            //LOG.debug(getPath().getName() + " writing page " + getPageNum());
2656
            writeValue(page, new Value(data));
10 ✔
2657
            setDirty(false);
3 ✔
2658
        }
1 ✔
2659
    }
2660
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc