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

evolvedbinary / elemental / 932

28 Apr 2025 01:10AM UTC coverage: 56.402% (-0.01%) from 56.413%
932

push

circleci

adamretter
[bugfix] Correct release process instructions

28446 of 55846 branches covered (50.94%)

Branch coverage included in aggregate %.

77456 of 131918 relevant lines covered (58.72%)

0.59 hits per line

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

56.34
/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 );
1✔
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);
1✔
125
        LogEntryTypes.addEntryType(LOG_STORE_VALUE, StoreValueLoggable::new);
1✔
126
        LogEntryTypes.addEntryType(LOG_REMOVE_VALUE, RemoveValueLoggable::new);
1✔
127
        LogEntryTypes.addEntryType(LOG_REMOVE_PAGE, RemoveEmptyPageLoggable::new);
1✔
128
        LogEntryTypes.addEntryType(LOG_OVERFLOW_APPEND, OverflowAppendLoggable::new);
1✔
129
        LogEntryTypes.addEntryType(LOG_OVERFLOW_STORE, OverflowStoreLoggable::new);
1✔
130
        LogEntryTypes.addEntryType(LOG_OVERFLOW_CREATE, OverflowCreateLoggable::new);
1✔
131
        LogEntryTypes.addEntryType(LOG_OVERFLOW_MODIFIED, OverflowModifiedLoggable::new);
1✔
132
        LogEntryTypes.addEntryType(LOG_OVERFLOW_CREATE_PAGE, OverflowCreatePageLoggable::new);
1✔
133
        LogEntryTypes.addEntryType(LOG_OVERFLOW_REMOVE, OverflowRemoveLoggable::new);
1✔
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;
1✔
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);
1✔
147
        lockManager = pool.getLockManager();
1✔
148
        fileHeader = (BFileHeader) getFileHeader();
1✔
149
        dataCache = new LRUCache<>(FileUtils.fileName(file), 64, cacheGrowth, thresholdData, Cache.CacheType.DATA);
1✔
150
        cacheManager.registerCache(dataCache);
1✔
151
        minFree = PAGE_MIN_FREE;
1✔
152
        maxValueSize = fileHeader.getWorkSize() / 2;
1✔
153
        
154
        if(exists()) {
1✔
155
            open(fileVersion);
1✔
156
        } else {
1✔
157
            if (LOG.isDebugEnabled()) {
1!
158
                LOG.debug("Creating data file: {}", FileUtils.fileName(getFile()));
×
159
            }
160
            create();
1✔
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());
1✔
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);
1✔
194
    }
195

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

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

208
        try {
209
            // check if key exists already
210
            long p = findValue(key);
1✔
211
            if (p == KEY_NOT_FOUND) {
1✔
212
                // key does not exist:
213
                p = storeValue(transaction, value);
1✔
214
                addValue(transaction, key, p);
1✔
215
                return p;
1✔
216
            }
217
            // key exists: get old data
218
            final long pnum = StorageAddress.pageFromPointer(p);
1✔
219
            final short tid = StorageAddress.tidFromPointer(p);
1✔
220
            final DataPage page = getDataPage(pnum);
1✔
221
            if (page instanceof OverflowPage) {
1✔
222
                ((OverflowPage) page).append(transaction, value);
1✔
223
            } else {
1✔
224
                final int valueLen = value.size();
1✔
225
                final byte[] data = page.getData();
1✔
226
                final int offset = page.findValuePosition(tid);
1✔
227
                if (offset < 0) {
1!
228
                    throw new IOException("tid " + tid + " not found on page " + pnum);
×
229
                }
230
                if (offset + 4 > data.length) {
1!
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);
1✔
235
                //TOUNDERSTAND : unless l can be negative, we should never get there -pb
236
                if (offset + 4 + l > data.length) {
1!
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];
1✔
241
                System.arraycopy(data, offset + 4, newData, 0, l);
1✔
242
                value.copyTo(newData, l);
1✔
243
                p = update(transaction, p, page, key, new FixedByteArray(newData, 0, newData.length));
1✔
244
            }
245
            return p;
1✔
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);
1✔
270
    }
271

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

278
    private SinglePage createDataPage() {
279
        try {
280
            final SinglePage page = new SinglePage();
1✔
281
            dataCache.add(page, 2);
1✔
282
            return page;
1✔
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);
1✔
292
    }
293

294
    @Override
295
    public PageHeader createPageHeader() {
296
        return new BFilePageHeader();
1✔
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(); 
1✔
313
            remove(transaction, query, cb);
1✔
314
            LOG.debug("Found {} items to remove.", cb.count);
1✔
315
            if (cb.count == 0) {
1✔
316
                return;
1✔
317
            }
318
            Arrays.sort(cb.pointers, 0, cb.count - 1);
1✔
319
            for (int i = 0; i < cb.count; i++) {
1✔
320
                remove(transaction, cb.pointers[i]);
1✔
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 {
1✔
329
        long[] pointers = new long[128];
1✔
330
        int count = 0;
1✔
331
        
332
        public boolean indexInfo(final Value value, final long pointer) throws TerminatedException {
333
            if (count == pointers.length) {
1✔
334
                final long[] np = new long[count * 2];
1✔
335
                System.arraycopy(pointers, 0, np, 0, count);
1✔
336
                pointers = np;
1✔
337
            }
338
            pointers[count++] = pointer;
1✔
339
            return true;
1✔
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);
×
353
        query(query, cb);
×
354
        return cb.getValues();
×
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;
1✔
366
        //TODO : consider log operation as a flush ?
367
        if (isRecoveryEnabled()) {
1✔
368
            logManager.ifPresent(l -> l.flush(true, false));
1✔
369
        }
370
        flushed = flushed | dataCache.flush();
1✔
371
        flushed = flushed | super.flush();
1✔
372
        return flushed;
1✔
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();
1✔
386
        final NumberFormat nf = NumberFormat.getPercentInstance();
1✔
387
        final StringBuilder buf = new StringBuilder();
1✔
388
        buf.append(FileUtils.fileName(getFile())).append(" DATA ");
1✔
389
        buf.append("Buffers occupation : ");
1✔
390
        if (dataCache.getBuffers() == 0 && dataCache.getUsedBuffers() == 0) {
1!
391
            buf.append("N/A");
×
392
        } else {
×
393
            buf.append(nf.format(dataCache.getUsedBuffers()/(float)dataCache.getBuffers()));
1✔
394
        }
395
        buf.append(" (").append(dataCache.getUsedBuffers()).append(" out of ").append(dataCache.getBuffers()).append(")");
1✔
396
        //buf.append(dataCache.getBuffers()).append(" / ");
397
        //buf.append(dataCache.getUsedBuffers()).append(" / ");
398
        buf.append(" Cache efficiency : ");
1✔
399
        if (dataCache.getHits() == 0 && dataCache.getFails() == 0) {
1!
400
            buf.append("N/A");
1✔
401
        } else {
1✔
402
            buf.append(nf.format(dataCache.getHits()/(float)(dataCache.getHits() + dataCache.getFails())));
1✔
403
        }
404
        //buf.append(dataCache.getHits()).append(" / ");
405
        //buf.append(dataCache.getFails());
406
        LOGSTATS.info(buf.toString());
1✔
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);
1✔
420
            if (p == KEY_NOT_FOUND) {
1✔
421
                return null;
1✔
422
            }
423
            final long pnum = StorageAddress.pageFromPointer(p);
1✔
424
            final DataPage page = getDataPage(pnum);
1✔
425
            return get(page, p);
1✔
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);
1✔
445
            if (p == KEY_NOT_FOUND) {return null;}           
1✔
446
            final long pnum = StorageAddress.pageFromPointer(p);
1✔
447
            final DataPage page = getDataPage(pnum);
1✔
448
            if (page != null) {
1!
449
                return switch (page.getPageHeader().getStatus()) {
1✔
450
                    case MULTI_PAGE -> ((OverflowPage) page).getDataStream(p);
1✔
451
                    default -> getAsStream(page, p);
1✔
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));
1✔
470
        return switch (page.getPageHeader().getStatus()) {
1!
471
            case MULTI_PAGE -> ((OverflowPage) page).getDataStream(pointer);
×
472
            default -> getAsStream(page, pointer);
1✔
473
        };
474
    }
475

476
    private VariableByteInput getAsStream(final DataPage page, final long pointer) throws IOException {
477
        dataCache.add(page.getFirstPage(), 2);
1✔
478
        final short tid = StorageAddress.tidFromPointer(pointer);
1✔
479
        final int offset = page.findValuePosition(tid);
1✔
480
        if (offset < 0) {
1!
481
            throw new IOException("no data found at tid " + tid + "; page " + page.getPageNum());
×
482
        }
483
        final byte[] data = page.getData();
1✔
484
        final int l = ByteConversion.byteToInt(data, offset);
1✔
485
        final SimplePageInput input = new SimplePageInput(data, offset + 4, l, pointer);
1✔
486
        return input;
1✔
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);
1✔
518
        final int offset = page.findValuePosition(tid);
1✔
519
        final byte[] data = page.getData();
1✔
520
        if (offset < 0 || offset > data.length) {
1!
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);
1✔
525
        if (l + 6 > data.length) {
1!
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());
1✔
530
        final Value v = new Value(data, offset + 4, l);
1✔
531
        v.setAddress(pointer);
1✔
532
        return v;
1✔
533
    }
534

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

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

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

563
    private SinglePage getSinglePage(final long pos, final boolean initialize) throws IOException {
564
        final SinglePage wp = (SinglePage) dataCache.get(pos);
1✔
565
        if (wp == null) {
1!
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;
1✔
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, "");
1✔
586
        final FindCallback cb = new FindCallback(FindCallback.KEYS);
1✔
587
        query(query, cb);
1✔
588
        return cb.getValues();
1✔
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!");
1!
614
        final FixedByteArray buf = new FixedByteArray(data, 0, data.length);
1✔
615
        return put(transaction, key, buf, overwrite);
1✔
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) {
1!
647
            LOG.debug("key is null");
×
648
            return UNKNOWN_ADDRESS;
×
649
        }
650

651
        if (key.getLength() > fileHeader.getWorkSize()) {
1!
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);
1✔
662
                if (p == KEY_NOT_FOUND) {
1✔
663
                    // key does not exist:
664
                    p = storeValue(transaction, value);
1✔
665
                    addValue(transaction, key, p);
1✔
666
                    return p;
1✔
667
                }
668

669
                // if exists, update value
670
                if (overwrite) {
1!
671
                    return update(transaction, p, key, value);
1✔
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
                ioe.printStackTrace();
×
684
                LOG.warn(ioe);
×
685
                return UNKNOWN_ADDRESS;
×
686
            }
687
        } catch (final BTreeException | IOException e) {
×
688
            e.printStackTrace();
×
689
            LOG.warn(e);
×
690
            return UNKNOWN_ADDRESS;
×
691
        }
692
    }
693

694
    public void remove(final Value key) {
695
        remove(null, key);
1✔
696
    }
1✔
697

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

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

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

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

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

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

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

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

953
    public void debugFreeList() {
954
            fileHeader.debugFreeList();
×
955
    }
×
956

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1411
        private final FreeList freeList = new FreeList();
1✔
1412
        
1413
        //public final static int MAX_FREE_LIST_LEN = 128;
1414

1415
        public BFileHeader(final int pageSize) {
1✔
1416
            super(pageSize);
1✔
1417
        }
1✔
1418

1419
        public void addFreeSpace(final FreeSpace freeSpace) {
1420
            freeList.add(freeSpace);
1✔
1421
            setDirty(true);
1✔
1422
        }
1✔
1423

1424
        public FreeSpace findFreeSpace(final int needed) {
1425
                return freeList.find(needed);
1✔
1426
        }
1427

1428
        public FreeSpace getFreeSpace(final long page) {
1429
            return freeList.retrieve(page);
1✔
1430
        }
1431

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

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

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

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

1457
    private static final class BFilePageHeader extends BTreePageHeader {
1458

1459
        private int dataLen = 0;
1✔
1460

1461
        private long lastInChain = -1L;
1✔
1462

1463
        private long nextInChain = -1L;
1✔
1464

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

1469
        private short records = 0;
1✔
1470

1471
        public BFilePageHeader() {
1472
            super();
1✔
1473
        }
1✔
1474

1475
        public void decRecordCount() {
1476
            records--;
1✔
1477
        }
1✔
1478

1479
        public int getDataLength() {
1480
            return dataLen;
1✔
1481
        }
1482

1483
        public long getLastInChain() {
1484
            return lastInChain;
1✔
1485
        }
1486

1487
        public long getNextInChain() {
1488
            return nextInChain;
1✔
1489
        }
1490

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

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

1506
        public short getRecordCount() {
1507
            return records;
×
1508
        }
1509

1510
        public void incRecordCount() {
1511
            records++;
1✔
1512
        }
1✔
1513

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

1529
        public void setDataLength(final int len) {
1530
            dataLen = len;
1✔
1531
        }
1✔
1532

1533
        public void setLastInChain(final long p) {
1534
            lastInChain = p;
1✔
1535
        }
1✔
1536

1537
        public void setNextInChain(final long b) {
1538
            nextInChain = b;
1✔
1539
        }
1✔
1540

1541
        public void setRecordCount(final short recs) {
1542
            records = recs;
1✔
1543
        }
1✔
1544

1545
        public void setTID(final short tid) {
1546
            this.nextTID = tid;
1✔
1547
        }
1✔
1548

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

1565
    private abstract class DataPage implements Comparable, Cacheable {
1✔
1566
        private int refCount = 0;
1✔
1567
        private int timestamp = 0;
1✔
1568
        private boolean saved = true;
1✔
1569

1570
        public abstract void delete() throws IOException;
1571

1572
        public abstract byte[] getData() throws IOException;
1573

1574
        public abstract BFilePageHeader getPageHeader();
1575

1576
        public abstract String getPageInfo();
1577

1578
        public abstract long getPageNum();
1579

1580
        public abstract int findValuePosition(short tid) throws IOException;
1581

1582
        public abstract short getNextTID();
1583

1584
        public abstract void removeTID(short tid, int length) throws IOException;
1585

1586
        public abstract void setOffset(short tid, int offset);
1587

1588
        @Override
1589
        public long getKey() {
1590
            return getPageNum();
1✔
1591
        }
1592

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

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

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

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

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

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

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

1640
        @Override
1641
        public boolean isDirty() {
1642
            return !saved;
1✔
1643
        }
1644

1645
        @Override
1646
        public boolean allowUnload() {
1647
            return true;
1✔
1648
        }
1649

1650
        public abstract void setData(byte[] buf);
1651

1652
        public abstract SinglePage getFirstPage();
1653

1654
        public void setDirty(final boolean dirty) {
1655
            saved = !dirty;
1✔
1656
            getPageHeader().setDirty(dirty);
1✔
1657
        }
1✔
1658

1659
        public abstract void write() throws IOException;
1660

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

1673
    private final class FilterCallback implements BTreeCallback {
1674
        private final BFileCallback callback;
1675

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

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

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

1704
        private final int mode;
1705
        private final IndexCallback callback;
1706
        private final ArrayList<Value> values;
1707

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

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

1720
        public ArrayList<Value> getValues() {
1721
            return values;
1✔
1722
        }
1723

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

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

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

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

1789
            return false;
×
1790
        }
1791
    }
1792

1793
    private final class OverflowPage extends DataPage {
1794
        private final SinglePage firstPage;
1795
        private byte[] data = null;
1✔
1796

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

1812
        public OverflowPage(final DataPage page) {
1✔
1813
            firstPage = (SinglePage) page;
1✔
1814
        }
1✔
1815

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

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

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

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

1945
        public VariableByteInput getDataStream(final long pointer) {
1946
            final MultiPageInput input = new MultiPageInput(firstPage, pointer);
1✔
1947
            return input;
1✔
1948
        }
1949

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

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

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

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

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

1987
        @Override
1988
        public BFilePageHeader getPageHeader() {
1989
            return firstPage.getPageHeader();
1✔
1990
        }
1991

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

1997
        @Override
1998
        public long getPageNum() {
1999
            return firstPage.getPageNum();
1✔
2000
        }
2001

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

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

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

2021
        public void write(final Txn transaction) throws IOException {
2022
            if (data == null) {
1!
2023
                return;
×
2024
            }
2025

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

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

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

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

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

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

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

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

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

2164
        @Override
2165
        public long position() {
2166
            return position;
×
2167
        }
2168

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

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

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

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

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

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

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

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

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

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

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

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

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

2322

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

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

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

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

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

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

2380
            read(data);
1✔
2381

2382
            return new String(data, UTF_8);
1✔
2383
        }
2384

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

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

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

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

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

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

2462
        // the raw working data of this page (without page header)
2463
        byte[] data = null;
1✔
2464

2465
        // the low-level page
2466
        final Page page;
2467

2468
        // the page header
2469
        final BFilePageHeader ph;
2470

2471
        // table mapping record ids (tids) to offsets
2472
        short[] offsets = null;
1✔
2473

2474
        public SinglePage() throws IOException {
2475
            this(true);
1✔
2476
        }
1✔
2477

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

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

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

2514
        @Override
2515
        public final int findValuePosition(final short tid) throws IOException {
2516
            return offsets[tid];
1✔
2517
        }
2518

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

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

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

2575
        public void clear() {
2576
            Arrays.fill(data, (byte) 0);
1✔
2577
        }
1✔
2578

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

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

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

2625
        @Override
2626
        public SinglePage getFirstPage() {
2627
            return this;
1✔
2628
        }
2629

2630
        @Override
2631
        public byte[] getData() {
2632
            return data;
1✔
2633
        }
2634

2635
        @Override
2636
        public BFilePageHeader getPageHeader() {
2637
            return ph;
1✔
2638
        }
2639

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

2645
        @Override
2646
        public long getPageNum() {
2647
            return page.getPageNum();
1✔
2648
        }
2649

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

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

© 2025 Coveralls, Inc