From 480e984417c17915b31e33de193ff08e118fcec9 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Thu, 6 Mar 2025 16:04:06 +0000 Subject: [PATCH 01/90] gh-249 Remove MDB_UNSIGNEDKEY, let CursorIterable call mdb_cmp There are now essentially three ways of configuring comparators when creating a Dbi. **null comparator** LMDB will use its own comparator & CursorIterable will call down to mdb_cmp for comparisons between the current cursor key and the range start/stop key. **provided comparator** LMDB will use its own comparator & CursorIterable will use the provided comparator for comparisons between the current cursor key and the range start/stop key. **provided comparator with nativeCb==true** LMDB will call back to java for all comparator duties. CursorIterable will use the same provided comparator for comparisons between the current cursor key and the range start/stop key. The methods `getSignedComparator()` and `getUnsignedComparator()` have been made public so users of this library can access them. --- src/main/java/org/lmdbjava/BufferProxy.java | 40 +- .../java/org/lmdbjava/ByteArrayProxy.java | 4 +- src/main/java/org/lmdbjava/ByteBufProxy.java | 4 +- .../java/org/lmdbjava/ByteBufferProxy.java | 4 +- src/main/java/org/lmdbjava/Cursor.java | 4 + .../java/org/lmdbjava/CursorIterable.java | 403 +++++++++++------- src/main/java/org/lmdbjava/Dbi.java | 15 +- src/main/java/org/lmdbjava/DbiFlags.java | 21 +- .../java/org/lmdbjava/DirectBufferProxy.java | 4 +- src/main/java/org/lmdbjava/Env.java | 21 +- src/main/java/org/lmdbjava/Key.java | 74 ++++ src/main/java/org/lmdbjava/KeyRangeType.java | 48 +-- src/main/java/org/lmdbjava/Library.java | 2 + .../java/org/lmdbjava/RangeComparator.java | 19 + .../java/org/lmdbjava/ComparatorTest.java | 8 +- .../org/lmdbjava/CursorIterablePerfTest.java | 163 +++++++ .../java/org/lmdbjava/CursorIterableTest.java | 238 ++++++++--- src/test/java/org/lmdbjava/DbiTest.java | 4 +- src/test/java/org/lmdbjava/KeyRangeTest.java | 5 +- src/test/java/org/lmdbjava/TestUtils.java | 2 + 20 files changed, 768 insertions(+), 315 deletions(-) create mode 100644 src/main/java/org/lmdbjava/Key.java create mode 100644 src/main/java/org/lmdbjava/RangeComparator.java create mode 100644 src/test/java/org/lmdbjava/CursorIterablePerfTest.java diff --git a/src/main/java/org/lmdbjava/BufferProxy.java b/src/main/java/org/lmdbjava/BufferProxy.java index e66031d2..26d9db74 100644 --- a/src/main/java/org/lmdbjava/BufferProxy.java +++ b/src/main/java/org/lmdbjava/BufferProxy.java @@ -16,10 +16,6 @@ package org.lmdbjava; import static java.lang.Long.BYTES; -import static org.lmdbjava.DbiFlags.MDB_INTEGERKEY; -import static org.lmdbjava.DbiFlags.MDB_UNSIGNEDKEY; -import static org.lmdbjava.MaskedFlag.isSet; -import static org.lmdbjava.MaskedFlag.mask; import java.util.Comparator; import jnr.ffi.Pointer; @@ -70,36 +66,25 @@ protected BufferProxy() {} */ protected abstract byte[] getBytes(T buffer); - /** - * Get a suitable default {@link Comparator} given the provided flags. - * - *

The provided comparator must strictly match the lexicographical order of keys in the native - * LMDB database. - * - * @param flags for the database - * @return a comparator that can be used (never null) - */ - protected Comparator getComparator(DbiFlags... flags) { - final int intFlag = mask(flags); - - return isSet(intFlag, MDB_INTEGERKEY) || isSet(intFlag, MDB_UNSIGNEDKEY) - ? getUnsignedComparator() - : getSignedComparator(); - } - /** * Get a suitable default {@link Comparator} to compare numeric key values as signed. * + *

+ * Note: LMDB's default comparator is unsigned so if this is used only for the {@link CursorIterable} + * start/stop key comparisons then its behaviour will differ from the iteration order. Use + * with caution. + *

+ * * @return a comparator that can be used (never null) */ - protected abstract Comparator getSignedComparator(); + public abstract Comparator getSignedComparator(); /** * Get a suitable default {@link Comparator} to compare numeric key values as unsigned. * * @return a comparator that can be used (never null) */ - protected abstract Comparator getUnsignedComparator(); + public abstract Comparator getUnsignedComparator(); /** * Called when the MDB_val should be set to reflect the passed buffer. This buffer @@ -140,4 +125,13 @@ protected Comparator getComparator(DbiFlags... flags) { final KeyVal keyVal() { return new KeyVal<>(this); } + + /** + * Create a new {@link Key} to hold pointers for this buffer proxy. + * + * @return a non-null key holder + */ + final Key key() { + return new Key<>(this); + } } diff --git a/src/main/java/org/lmdbjava/ByteArrayProxy.java b/src/main/java/org/lmdbjava/ByteArrayProxy.java index 4a22ab83..3aeba047 100644 --- a/src/main/java/org/lmdbjava/ByteArrayProxy.java +++ b/src/main/java/org/lmdbjava/ByteArrayProxy.java @@ -104,12 +104,12 @@ protected byte[] getBytes(final byte[] buffer) { } @Override - protected Comparator getSignedComparator() { + public Comparator getSignedComparator() { return signedComparator; } @Override - protected Comparator getUnsignedComparator() { + public Comparator getUnsignedComparator() { return unsignedComparator; } diff --git a/src/main/java/org/lmdbjava/ByteBufProxy.java b/src/main/java/org/lmdbjava/ByteBufProxy.java index cac5b97b..933b52a4 100644 --- a/src/main/java/org/lmdbjava/ByteBufProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufProxy.java @@ -114,12 +114,12 @@ protected ByteBuf allocate() { } @Override - protected Comparator getSignedComparator() { + public Comparator getSignedComparator() { return comparator; } @Override - protected Comparator getUnsignedComparator() { + public Comparator getUnsignedComparator() { return comparator; } diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index 2b7cdf0d..1fce090a 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -182,12 +182,12 @@ protected final ByteBuffer allocate() { } @Override - protected Comparator getSignedComparator() { + public Comparator getSignedComparator() { return signedComparator; } @Override - protected Comparator getUnsignedComparator() { + public Comparator getUnsignedComparator() { return unsignedComparator; } diff --git a/src/main/java/org/lmdbjava/Cursor.java b/src/main/java/org/lmdbjava/Cursor.java index d49a9bed..9070cff6 100644 --- a/src/main/java/org/lmdbjava/Cursor.java +++ b/src/main/java/org/lmdbjava/Cursor.java @@ -196,6 +196,10 @@ public T key() { return kv.key(); } + KeyVal keyVal() { + return kv; + } + /** * Position at last key/data item. * diff --git a/src/main/java/org/lmdbjava/CursorIterable.java b/src/main/java/org/lmdbjava/CursorIterable.java index 6a03bd90..6b92a9cd 100644 --- a/src/main/java/org/lmdbjava/CursorIterable.java +++ b/src/main/java/org/lmdbjava/CursorIterable.java @@ -21,10 +21,14 @@ import static org.lmdbjava.CursorIterable.State.REQUIRES_NEXT_OP; import static org.lmdbjava.CursorIterable.State.TERMINATED; import static org.lmdbjava.GetOp.MDB_SET_RANGE; +import static org.lmdbjava.Library.LIB; import java.util.Comparator; import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.function.Supplier; +import jnr.ffi.Pointer; import org.lmdbjava.KeyRangeType.CursorOp; import org.lmdbjava.KeyRangeType.IteratorOp; @@ -38,185 +42,266 @@ */ public final class CursorIterable implements Iterable>, AutoCloseable { - private final Comparator comparator; - private final Cursor cursor; - private final KeyVal entry; - private boolean iteratorReturned; - private final KeyRange range; - private State state = REQUIRES_INITIAL_OP; - - CursorIterable( - final Txn txn, final Dbi dbi, final KeyRange range, final Comparator comparator) { - this.cursor = dbi.openCursor(txn); - this.range = range; - this.comparator = comparator; - this.entry = new KeyVal<>(); - } - - @Override - public void close() { - cursor.close(); - } - - /** - * Obtain an iterator. - * - *

As iteration of the returned iterator will cause movement of the underlying LMDB cursor, an - * {@link IllegalStateException} is thrown if an attempt is made to obtain the iterator more than - * once. For advanced cursor control (such as being able to iterate over the same data multiple - * times etc) please instead refer to {@link Dbi#openCursor(org.lmdbjava.Txn)}. - * - * @return an iterator - */ - @Override - public Iterator> iterator() { - if (iteratorReturned) { - throw new IllegalStateException("Iterator can only be returned once"); - } - iteratorReturned = true; + // private final Comparator comparator; + private final RangeComparator rangeComparator; + private final Cursor cursor; + private final Dbi dbi; + private final KeyVal entry; + private boolean iteratorReturned; + private final KeyRange range; + private State state = REQUIRES_INITIAL_OP; + private final Key startKey; + private final Key stopKey; - return new Iterator>() { - @Override - public boolean hasNext() { - while (state != RELEASED && state != TERMINATED) { - update(); - } - return state == RELEASED; - } + CursorIterable( + final Txn txn, + final Dbi dbi, + final KeyRange range, + final Comparator comparator, + final BufferProxy proxy) { + this.cursor = dbi.openCursor(txn); + this.dbi = dbi; + this.range = range; + this.entry = new KeyVal<>(); - @Override - public KeyVal next() { - if (!hasNext()) { - throw new NoSuchElementException(); + if (comparator != null) { + // User supplied java-side comparator so use that + this.rangeComparator = createJavaRangeComparator(range, comparator, entry::key); + this.startKey = null; + this.stopKey = null; + } else { + // No java-side comparator so call down to LMDB to do the comparison + this.rangeComparator = createLmdbDbiComparator(txn.pointer(), dbi.pointer()); + // Allocate buffers for use with the start/stop keys if required. + // Saves us copying bytes on each comparison + this.startKey = createKey(range.getStart(), proxy); + this.stopKey = createKey(range.getStop(), proxy); } - state = REQUIRES_NEXT_OP; - return entry; - } - - @Override - public void remove() { - cursor.delete(); - } - }; - } - - private void executeCursorOp(final CursorOp op) { - final boolean found; - switch (op) { - case FIRST: - found = cursor.first(); - break; - case LAST: - found = cursor.last(); - break; - case NEXT: - found = cursor.next(); - break; - case PREV: - found = cursor.prev(); - break; - case GET_START_KEY: - found = cursor.get(range.getStart(), MDB_SET_RANGE); - break; - case GET_START_KEY_BACKWARD: - found = cursor.get(range.getStart(), MDB_SET_RANGE) || cursor.last(); - break; - default: - throw new IllegalStateException("Unknown cursor operation"); } - entry.setK(found ? cursor.key() : null); - entry.setV(found ? cursor.val() : null); - } - - private void executeIteratorOp() { - final IteratorOp op = - range.getType().iteratorOp(range.getStart(), range.getStop(), entry.key(), comparator); - switch (op) { - case CALL_NEXT_OP: - executeCursorOp(range.getType().nextOp()); - state = REQUIRES_ITERATOR_OP; - break; - case TERMINATE: - state = TERMINATED; - break; - case RELEASE: - state = RELEASED; - break; - default: - throw new IllegalStateException("Unknown operation"); + + private Key createKey(final T keyBuffer, final BufferProxy proxy) { + if (keyBuffer != null) { + final Key key = proxy.key(); + key.keyIn(keyBuffer); + return key; + } else { + return null; + } } - } - - private void update() { - switch (state) { - case REQUIRES_INITIAL_OP: - executeCursorOp(range.getType().initialOp()); - state = REQUIRES_ITERATOR_OP; - break; - case REQUIRES_NEXT_OP: - executeCursorOp(range.getType().nextOp()); - state = REQUIRES_ITERATOR_OP; - break; - case REQUIRES_ITERATOR_OP: - executeIteratorOp(); - break; - case TERMINATED: - break; - default: - throw new IllegalStateException("Unknown state"); + + static RangeComparator createJavaRangeComparator( + final KeyRange range, + final Comparator comparator, + final Supplier currentKeySupplier) { + final T start = range.getStart(); + final T stop = range.getStop(); + return new RangeComparator() { + @Override + public int compareToStartKey() { + return comparator.compare(currentKeySupplier.get(), start); + } + + @Override + public int compareToStopKey() { + return comparator.compare(currentKeySupplier.get(), stop); + } + }; } - } - /** - * Holder for a key and value pair. - * - *

The same holder instance will always be returned for a given iterator. The returned keys and - * values may change or point to different memory locations following changes in the iterator, - * cursor or transaction. - * - * @param buffer type - */ - public static final class KeyVal { + /** + * Calls down to mdb_cmp to make use of the comparator that LMDB uses for insertion order. + * + * @param txnPointer The pointer to the transaction. + * @param dbiPointer The pointer to the Dbi so LMDB can use the comparator of the Dbi + */ + private RangeComparator createLmdbDbiComparator( + final Pointer txnPointer, final Pointer dbiPointer) { + Objects.requireNonNull(txnPointer); + Objects.requireNonNull(dbiPointer); + Objects.requireNonNull(cursor); + + return new RangeComparator() { + @Override + public int compareToStartKey() { + return LIB.mdb_cmp(txnPointer, dbiPointer, cursor.keyVal().pointerKey(), startKey.pointerKey()); + } - private T k; - private T v; + @Override + public int compareToStopKey() { + return LIB.mdb_cmp(txnPointer, dbiPointer, cursor.keyVal().pointerKey(), stopKey.pointerKey()); + } + }; + } - /** Explicitly-defined default constructor to avoid warnings. */ - public KeyVal() {} + @Override + public void close() { + cursor.close(); + } /** - * The key. + * Obtain an iterator. + * + *

As iteration of the returned iterator will cause movement of the underlying LMDB cursor, an + * {@link IllegalStateException} is thrown if an attempt is made to obtain the iterator more than + * once. For advanced cursor control (such as being able to iterate over the same data multiple + * times etc) please instead refer to {@link Dbi#openCursor(org.lmdbjava.Txn)}. * - * @return key + * @return an iterator */ - public T key() { - return k; + @Override + public Iterator> iterator() { + if (iteratorReturned) { + throw new IllegalStateException("Iterator can only be returned once"); + } + iteratorReturned = true; + + return new Iterator>() { + @Override + public boolean hasNext() { + while (state != RELEASED && state != TERMINATED) { + update(); + } + return state == RELEASED; + } + + @Override + public KeyVal next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + state = REQUIRES_NEXT_OP; + return entry; + } + + @Override + public void remove() { + cursor.delete(); + } + }; + } + + private void executeCursorOp(final CursorOp op) { + final boolean found; + switch (op) { + case FIRST: + found = cursor.first(); + break; + case LAST: + found = cursor.last(); + break; + case NEXT: + found = cursor.next(); + break; + case PREV: + found = cursor.prev(); + break; + case GET_START_KEY: + found = cursor.get(range.getStart(), MDB_SET_RANGE); + break; + case GET_START_KEY_BACKWARD: + found = cursor.get(range.getStart(), MDB_SET_RANGE) || cursor.last(); + break; + default: + throw new IllegalStateException("Unknown cursor operation"); + } + entry.setK(found ? cursor.key() : null); + entry.setV(found ? cursor.val() : null); + } + + private void executeIteratorOp() { + final IteratorOp op = + range.getType().iteratorOp(range.getStart(), range.getStop(), entry.key(), rangeComparator); + switch (op) { + case CALL_NEXT_OP: + executeCursorOp(range.getType().nextOp()); + state = REQUIRES_ITERATOR_OP; + break; + case TERMINATE: + state = TERMINATED; + break; + case RELEASE: + state = RELEASED; + break; + default: + throw new IllegalStateException("Unknown operation"); + } + } + + private void update() { + switch (state) { + case REQUIRES_INITIAL_OP: + executeCursorOp(range.getType().initialOp()); + state = REQUIRES_ITERATOR_OP; + break; + case REQUIRES_NEXT_OP: + executeCursorOp(range.getType().nextOp()); + state = REQUIRES_ITERATOR_OP; + break; + case REQUIRES_ITERATOR_OP: + executeIteratorOp(); + break; + case TERMINATED: + break; + default: + throw new IllegalStateException("Unknown state"); + } } /** - * The value. + * Holder for a key and value pair. + * + *

The same holder instance will always be returned for a given iterator. The returned keys and + * values may change or point to different memory locations following changes in the iterator, + * cursor or transaction. * - * @return value + * @param buffer type */ - public T val() { - return v; - } + public static final class KeyVal { + + private T k; + private T v; + + /** + * Explicitly-defined default constructor to avoid warnings. + */ + public KeyVal() { + } + + /** + * The key. + * + * @return key + */ + public T key() { + return k; + } + + /** + * The value. + * + * @return value + */ + public T val() { + return v; + } - void setK(final T key) { - this.k = key; + void setK(final T key) { + this.k = key; + } + + void setV(final T val) { + this.v = val; + } } - void setV(final T val) { - this.v = val; + /** + * Represents the internal {@link CursorIterable} state. + */ + enum State { + REQUIRES_INITIAL_OP, + REQUIRES_NEXT_OP, + REQUIRES_ITERATOR_OP, + RELEASED, + TERMINATED } - } - - /** Represents the internal {@link CursorIterable} state. */ - enum State { - REQUIRES_INITIAL_OP, - REQUIRES_NEXT_OP, - REQUIRES_ITERATOR_OP, - RELEASED, - TERMINATED - } } diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index ad1bb5a7..c622462b 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -54,6 +54,7 @@ public final class Dbi { private final Env env; private final byte[] name; private final Pointer ptr; + private final BufferProxy proxy; Dbi( final Env env, @@ -69,16 +70,14 @@ public final class Dbi { } this.env = env; this.name = name == null ? null : Arrays.copyOf(name, name.length); - if (comparator == null) { - this.comparator = proxy.getComparator(flags); - } else { - this.comparator = comparator; - } + this.proxy = proxy; + this.comparator = comparator; final int flagsMask = mask(true, flags); final Pointer dbiPtr = allocateDirect(RUNTIME, ADDRESS); checkRc(LIB.mdb_dbi_open(txn.pointer(), name, flagsMask, dbiPtr)); ptr = dbiPtr.getPointer(0); if (nativeCb) { + requireNonNull(comparator, "comparator cannot be null if nativeCb is set"); this.ccb = (keyA, keyB) -> { final T compKeyA = proxy.allocate(); @@ -96,6 +95,10 @@ public final class Dbi { } } + Pointer pointer() { + return ptr; + } + /** * Close the database handle (normally unnecessary; use with caution). * @@ -275,7 +278,7 @@ public CursorIterable iterate(final Txn txn, final KeyRange range) { env.checkNotClosed(); txn.checkReady(); } - return new CursorIterable<>(txn, this, range, comparator); + return new CursorIterable<>(txn, this, range, comparator, proxy); } /** diff --git a/src/main/java/org/lmdbjava/DbiFlags.java b/src/main/java/org/lmdbjava/DbiFlags.java index 123ec9fd..2f5eadf6 100644 --- a/src/main/java/org/lmdbjava/DbiFlags.java +++ b/src/main/java/org/lmdbjava/DbiFlags.java @@ -55,14 +55,6 @@ public enum DbiFlags implements MaskedFlag { * #MDB_INTEGERKEY} keys. */ MDB_INTEGERDUP(0x20), - /** - * Compare the numeric keys in native byte order and as unsigned. - * - *

This option is applied only to {@link java.nio.ByteBuffer}, {@link org.agrona.DirectBuffer} - * and byte array keys. {@link io.netty.buffer.ByteBuf} keys are always compared in native byte - * order and as unsigned. - */ - MDB_UNSIGNEDKEY(0x30, false), /** * With {@link #MDB_DUPSORT}, use reverse string dups. * @@ -78,24 +70,13 @@ public enum DbiFlags implements MaskedFlag { MDB_CREATE(0x4_0000); private final int mask; - private final boolean propagatedToLmdb; - - DbiFlags(final int mask, final boolean propagatedToLmdb) { - this.mask = mask; - this.propagatedToLmdb = propagatedToLmdb; - } DbiFlags(final int mask) { - this(mask, true); + this.mask = mask; } @Override public int getMask() { return mask; } - - @Override - public boolean isPropagatedToLmdb() { - return propagatedToLmdb; - } } diff --git a/src/main/java/org/lmdbjava/DirectBufferProxy.java b/src/main/java/org/lmdbjava/DirectBufferProxy.java index 156e60e9..5022ed02 100644 --- a/src/main/java/org/lmdbjava/DirectBufferProxy.java +++ b/src/main/java/org/lmdbjava/DirectBufferProxy.java @@ -111,12 +111,12 @@ protected DirectBuffer allocate() { } @Override - protected Comparator getSignedComparator() { + public Comparator getSignedComparator() { return signedComparator; } @Override - protected Comparator getUnsignedComparator() { + public Comparator getUnsignedComparator() { return unsignedComparator; } diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index 3db16119..1543282c 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -256,10 +256,17 @@ public Dbi openDbi(final String name, final DbiFlags... flags) { /** * Convenience method that opens a {@link Dbi} with a UTF-8 database name and associated {@link - * Comparator} that is not invoked from native code. + * Comparator} for use by {@link CursorIterable} when comparing start/stop keys. + *

+ * It is very important that the passed comparator behaves in the same way as the comparator + * LMDB uses for its insertion order (for the type of data that will be stored in the database), + * or you fully understand the implications of them behaving differently. + * LMDB's comparator is unsigned lexicographical, unless {@link DbiFlags#MDB_INTEGERKEY} is used. + *

* * @param name name of the database (or null if no name is required) - * @param comparator custom comparator callback (or null to use default) + * @param comparator custom comparator for cursor start/stop key comparisons. If null, + * LMDB's comparator will be used. * @param flags to open the database with * @return a database that is ready to use */ @@ -271,11 +278,15 @@ public Dbi openDbi( /** * Convenience method that opens a {@link Dbi} with a UTF-8 database name and associated {@link - * Comparator} that may be invoked from native code if specified. + * Comparator}. The comparator will be used by {@link CursorIterable} when comparing start/stop keys + * as a minimum. If nativeCb is {@code true}, this comparator will also be called by LMDB to determine + * insertion/iteration order. Calling back to a java comparator may significantly impact performance. * * @param name name of the database (or null if no name is required) - * @param comparator custom comparator callback (or null to use default) - * @param nativeCb whether native code calls back to the Java comparator + * @param comparator custom comparator for cursor start/stop key comparisons and optionally for + * LMDB to call back to. If null, + * LMDB's comparator will be used. + * @param nativeCb whether LMDB native code calls back to the Java comparator * @param flags to open the database with * @return a database that is ready to use */ diff --git a/src/main/java/org/lmdbjava/Key.java b/src/main/java/org/lmdbjava/Key.java new file mode 100644 index 00000000..7fd8bbe2 --- /dev/null +++ b/src/main/java/org/lmdbjava/Key.java @@ -0,0 +1,74 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +import static java.util.Objects.requireNonNull; +import static org.lmdbjava.BufferProxy.MDB_VAL_STRUCT_SIZE; +import static org.lmdbjava.Library.RUNTIME; + +import jnr.ffi.Pointer; +import jnr.ffi.provider.MemoryManager; + +/** + * Represents off-heap memory holding a key only. + * + * @param buffer type + */ +final class Key implements AutoCloseable { + + private static final MemoryManager MEM_MGR = RUNTIME.getMemoryManager(); + private boolean closed; + private T k; + private final BufferProxy proxy; + private final Pointer ptrArray; + private final Pointer ptrKey; + private final long ptrKeyAddr; + + Key(final BufferProxy proxy) { + requireNonNull(proxy); + this.proxy = proxy; + this.k = proxy.allocate(); + ptrKey = MEM_MGR.allocateTemporary(MDB_VAL_STRUCT_SIZE, false); + ptrKeyAddr = ptrKey.address(); + ptrArray = MEM_MGR.allocateTemporary(MDB_VAL_STRUCT_SIZE * 2, false); + } + + @Override + public void close() { + if (closed) { + return; + } + closed = true; + proxy.deallocate(k); + } + + T key() { + return k; + } + + void keyIn(final T key) { + proxy.in(key, ptrKey, ptrKeyAddr); + } + + T keyOut() { + k = proxy.out(k, ptrKey, ptrKeyAddr); + return k; + } + + Pointer pointerKey() { + return ptrKey; + } +} diff --git a/src/main/java/org/lmdbjava/KeyRangeType.java b/src/main/java/org/lmdbjava/KeyRangeType.java index ad67286d..07123e9a 100644 --- a/src/main/java/org/lmdbjava/KeyRangeType.java +++ b/src/main/java/org/lmdbjava/KeyRangeType.java @@ -322,12 +322,12 @@ CursorOp initialOp() { * @param start start buffer * @param stop stop buffer * @param buffer current key returned by LMDB (may be null) - * @param c comparator (required) + * @param rangeComparator comparator (required) * @return response to this key */ > IteratorOp iteratorOp( - final T start, final T stop, final T buffer, final C c) { - requireNonNull(c, "Comparator required"); + final T start, final T stop, final T buffer, final RangeComparator rangeComparator) { + requireNonNull(rangeComparator, "Comparator required"); if (buffer == null) { return TERMINATE; } @@ -337,55 +337,55 @@ > IteratorOp iteratorOp( case FORWARD_AT_LEAST: return RELEASE; case FORWARD_AT_MOST: - return c.compare(buffer, stop) > 0 ? TERMINATE : RELEASE; + return rangeComparator.compareToStopKey() > 0 ? TERMINATE : RELEASE; case FORWARD_CLOSED: - return c.compare(buffer, stop) > 0 ? TERMINATE : RELEASE; + return rangeComparator.compareToStopKey() > 0 ? TERMINATE : RELEASE; case FORWARD_CLOSED_OPEN: - return c.compare(buffer, stop) >= 0 ? TERMINATE : RELEASE; + return rangeComparator.compareToStopKey() >= 0 ? TERMINATE : RELEASE; case FORWARD_GREATER_THAN: - return c.compare(buffer, start) == 0 ? CALL_NEXT_OP : RELEASE; + return rangeComparator.compareToStartKey() == 0 ? CALL_NEXT_OP : RELEASE; case FORWARD_LESS_THAN: - return c.compare(buffer, stop) >= 0 ? TERMINATE : RELEASE; + return rangeComparator.compareToStopKey() >= 0 ? TERMINATE : RELEASE; case FORWARD_OPEN: - if (c.compare(buffer, start) == 0) { + if (rangeComparator.compareToStartKey() == 0) { return CALL_NEXT_OP; } - return c.compare(buffer, stop) >= 0 ? TERMINATE : RELEASE; + return rangeComparator.compareToStopKey() >= 0 ? TERMINATE : RELEASE; case FORWARD_OPEN_CLOSED: - if (c.compare(buffer, start) == 0) { + if (rangeComparator.compareToStartKey() == 0) { return CALL_NEXT_OP; } - return c.compare(buffer, stop) > 0 ? TERMINATE : RELEASE; + return rangeComparator.compareToStopKey() > 0 ? TERMINATE : RELEASE; case BACKWARD_ALL: return RELEASE; case BACKWARD_AT_LEAST: - return c.compare(buffer, start) > 0 ? CALL_NEXT_OP : RELEASE; // rewind + return rangeComparator.compareToStartKey() > 0 ? CALL_NEXT_OP : RELEASE; // rewind case BACKWARD_AT_MOST: - return c.compare(buffer, stop) >= 0 ? RELEASE : TERMINATE; + return rangeComparator.compareToStopKey() >= 0 ? RELEASE : TERMINATE; case BACKWARD_CLOSED: - if (c.compare(buffer, start) > 0) { + if (rangeComparator.compareToStartKey() > 0) { return CALL_NEXT_OP; // rewind } - return c.compare(buffer, stop) >= 0 ? RELEASE : TERMINATE; + return rangeComparator.compareToStopKey() >= 0 ? RELEASE : TERMINATE; case BACKWARD_CLOSED_OPEN: - if (c.compare(buffer, start) > 0) { + if (rangeComparator.compareToStartKey() > 0) { return CALL_NEXT_OP; // rewind } - return c.compare(buffer, stop) > 0 ? RELEASE : TERMINATE; + return rangeComparator.compareToStopKey() > 0 ? RELEASE : TERMINATE; case BACKWARD_GREATER_THAN: - return c.compare(buffer, start) >= 0 ? CALL_NEXT_OP : RELEASE; + return rangeComparator.compareToStartKey() >= 0 ? CALL_NEXT_OP : RELEASE; case BACKWARD_LESS_THAN: - return c.compare(buffer, stop) > 0 ? RELEASE : TERMINATE; + return rangeComparator.compareToStopKey() > 0 ? RELEASE : TERMINATE; case BACKWARD_OPEN: - if (c.compare(buffer, start) >= 0) { + if (rangeComparator.compareToStartKey() >= 0) { return CALL_NEXT_OP; // rewind } - return c.compare(buffer, stop) > 0 ? RELEASE : TERMINATE; + return rangeComparator.compareToStopKey() > 0 ? RELEASE : TERMINATE; case BACKWARD_OPEN_CLOSED: - if (c.compare(buffer, start) >= 0) { + if (rangeComparator.compareToStartKey() >= 0) { return CALL_NEXT_OP; // rewind } - return c.compare(buffer, stop) >= 0 ? RELEASE : TERMINATE; + return rangeComparator.compareToStopKey() >= 0 ? RELEASE : TERMINATE; default: throw new IllegalStateException("Invalid type"); } diff --git a/src/main/java/org/lmdbjava/Library.java b/src/main/java/org/lmdbjava/Library.java index ef9b9b35..6d8122d2 100644 --- a/src/main/java/org/lmdbjava/Library.java +++ b/src/main/java/org/lmdbjava/Library.java @@ -235,6 +235,8 @@ public interface Lmdb { void mdb_txn_reset(@In Pointer txn); + int mdb_cmp(@In Pointer txn, @In Pointer dbi, @In Pointer key1, @In Pointer key2); + Pointer mdb_version(IntByReference major, IntByReference minor, IntByReference patch); } } diff --git a/src/main/java/org/lmdbjava/RangeComparator.java b/src/main/java/org/lmdbjava/RangeComparator.java new file mode 100644 index 00000000..162584b1 --- /dev/null +++ b/src/main/java/org/lmdbjava/RangeComparator.java @@ -0,0 +1,19 @@ +package org.lmdbjava; + +/** + * For comparing a cursor's current key against a {@link KeyRange}'s start/stop key. + */ +interface RangeComparator { + + /** + * Compare the cursor's current key to the range start key. Equivalent to compareTo(currentKey, + * startKey) + */ + int compareToStartKey(); + + /** + * Compare the cursor's current key to the range stop key. Equivalent to compareTo(currentKey, + * stopKey) + */ + int compareToStopKey(); +} diff --git a/src/test/java/org/lmdbjava/ComparatorTest.java b/src/test/java/org/lmdbjava/ComparatorTest.java index 3e265cee..dad84ed2 100644 --- a/src/test/java/org/lmdbjava/ComparatorTest.java +++ b/src/test/java/org/lmdbjava/ComparatorTest.java @@ -135,7 +135,7 @@ private static final class ByteArrayRunner implements ComparatorRunner { @Override public int compare(final byte[] o1, final byte[] o2) { - final Comparator c = PROXY_BA.getComparator(); + final Comparator c = PROXY_BA.getUnsignedComparator(); return c.compare(o1, o2); } } @@ -145,7 +145,7 @@ private static final class ByteBufferRunner implements ComparatorRunner { @Override public int compare(final byte[] o1, final byte[] o2) { - final Comparator c = PROXY_OPTIMAL.getComparator(); + final Comparator c = PROXY_OPTIMAL.getUnsignedComparator(); // Convert arrays to buffers that are larger than the array, with // limit set at the array length. One buffer bigger than the other. @@ -189,7 +189,7 @@ private static final class DirectBufferRunner implements ComparatorRunner { public int compare(final byte[] o1, final byte[] o2) { final DirectBuffer o1b = new UnsafeBuffer(o1); final DirectBuffer o2b = new UnsafeBuffer(o2); - final Comparator c = PROXY_DB.getComparator(); + final Comparator c = PROXY_DB.getUnsignedComparator(); return c.compare(o1b, o2b); } } @@ -223,7 +223,7 @@ public int compare(final byte[] o1, final byte[] o2) { final ByteBuf o2b = DEFAULT.directBuffer(o2.length); o1b.writeBytes(o1); o2b.writeBytes(o2); - final Comparator c = PROXY_NETTY.getComparator(); + final Comparator c = PROXY_NETTY.getUnsignedComparator(); return c.compare(o1b, o2b); } } diff --git a/src/test/java/org/lmdbjava/CursorIterablePerfTest.java b/src/test/java/org/lmdbjava/CursorIterablePerfTest.java new file mode 100644 index 00000000..a9647d56 --- /dev/null +++ b/src/test/java/org/lmdbjava/CursorIterablePerfTest.java @@ -0,0 +1,163 @@ +package org.lmdbjava; + +import static com.jakewharton.byteunits.BinaryByteUnit.GIBIBYTES; +import static org.lmdbjava.DbiFlags.MDB_CREATE; +import static org.lmdbjava.Env.create; +import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; +import static org.lmdbjava.PutFlags.MDB_APPEND; +import static org.lmdbjava.PutFlags.MDB_NOOVERWRITE; +import static org.lmdbjava.TestUtils.POSIX_MODE; +import static org.lmdbjava.TestUtils.bb; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class CursorIterablePerfTest { + + @Rule + public final TemporaryFolder tmp = new TemporaryFolder(); + +// private static final int ITERATIONS = 5_000_000; + private static final int ITERATIONS = 100_000; +// private static final int ITERATIONS = 10; + + private Dbi dbJavaComparator; + private Dbi dbLmdbComparator; + private Dbi dbCallbackComparator; + private List> dbs = new ArrayList<>(); + private Env env; + private List data = new ArrayList<>(ITERATIONS); + + @Before + public void before() throws IOException { + final File path = tmp.newFile(); + final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; + env = + create(bufferProxy) + .setMapSize(GIBIBYTES.toBytes(1)) + .setMaxReaders(1) + .setMaxDbs(3) + .open(path, POSIX_MODE, MDB_NOSUBDIR); + + // Use a java comparator for start/stop keys only + dbJavaComparator = env.openDbi("JavaComparator", bufferProxy.getUnsignedComparator(), MDB_CREATE); + // Use LMDB comparator for start/stop keys + dbLmdbComparator = env.openDbi("LmdbComparator", MDB_CREATE); + // Use a java comparator for start/stop keys and as a callback comparator + dbCallbackComparator = env.openDbi( + "CallBackComparator", bufferProxy.getUnsignedComparator(), true, MDB_CREATE); + + dbs.add(dbJavaComparator); + dbs.add(dbLmdbComparator); + dbs.add(dbCallbackComparator); + + populateList(); + } + + private void populateList() { + for (int i = 0; i < ITERATIONS * 2; i+=2) { + data.add(i); + } + } + + private void populateDatabases(final boolean randomOrder) { + System.out.println("Clear then populate databases"); + + final List data; + if (randomOrder) { + data = new ArrayList<>(this.data); + Collections.shuffle(data); + } else { + data = this.data; + } + + for (int round = 0; round < 3; round++) { + System.out.println("round: " + round + " -----------------------------------------"); + + for (final Dbi db : dbs) { + // Clean out the db first + try (Txn txn = env.txnWrite(); + final Cursor cursor = db.openCursor(txn)) { + while (cursor.next()) { + cursor.delete(); + } + } + + final String dbName = new String(db.getName(), StandardCharsets.UTF_8); + final Instant start = Instant.now(); + try (Txn txn = env.txnWrite()) { + for (final Integer i : data) { + if (randomOrder) { + db.put(txn, bb(i), bb(i + 1), MDB_NOOVERWRITE); + } else { + db.put(txn, bb(i), bb(i + 1), MDB_NOOVERWRITE, MDB_APPEND); + } + } + txn.commit(); + } + final Duration duration = Duration.between(start, Instant.now()); + System.out.println("DB: " + dbName + + " - Loaded in duration: " + duration + + ", millis: " + duration.toMillis()); + } + } + } + + @After + public void after() { + env.close(); + tmp.delete(); + } + + @Test + public void comparePerf_sequential() { + comparePerf(false); + } + + @Test + public void comparePerf_random() { + comparePerf(true); + } + + public void comparePerf(final boolean randomOrder) { + populateDatabases(randomOrder); + final ByteBuffer startKeyBuf = bb(data.getFirst()); + final ByteBuffer stopKeyBuf = bb(data.getLast()); + final KeyRange keyRange = KeyRange.closed(startKeyBuf, stopKeyBuf); + + System.out.println("\nIterating over all entries"); + for (int round = 0; round < 3; round++) { + System.out.println("round: " + round + " -----------------------------------------"); + for (final Dbi db : dbs) { + final String dbName = new String(db.getName(), StandardCharsets.UTF_8); + + final Instant start = Instant.now(); + int cnt = 0; + // Exercise the stop key comparator on every entry + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn, keyRange)) { + for (final CursorIterable.KeyVal kv : c) { + cnt++; + } + } + final Duration duration = Duration.between(start, Instant.now()); + System.out.println("DB: " + dbName + + " - Iterated in duration: " + duration + + ", millis: " + duration.toMillis() + + ", cnt: " + cnt); + } + } + } +} diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index bd23bc55..96be0816 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -43,6 +43,8 @@ import static org.lmdbjava.KeyRange.openClosedBackward; import static org.lmdbjava.PutFlags.MDB_NOOVERWRITE; import static org.lmdbjava.TestUtils.DB_1; +import static org.lmdbjava.TestUtils.DB_2; +import static org.lmdbjava.TestUtils.DB_3; import static org.lmdbjava.TestUtils.POSIX_MODE; import static org.lmdbjava.TestUtils.bb; @@ -69,7 +71,10 @@ public final class CursorIterableTest { @Rule public final TemporaryFolder tmp = new TemporaryFolder(); - private Dbi db; + private Dbi dbJavaComparator; + private Dbi dbLmdbComparator; + private Dbi dbCallbackComparator; + private List> dbs = new ArrayList<>(); private Env env; private Deque list; @@ -116,19 +121,39 @@ public void atMostTest() { @Before public void before() throws IOException { final File path = tmp.newFile(); + final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; env = - create() + create(bufferProxy) .setMapSize(KIBIBYTES.toBytes(256)) .setMaxReaders(1) - .setMaxDbs(1) + .setMaxDbs(3) .open(path, POSIX_MODE, MDB_NOSUBDIR); - db = env.openDbi(DB_1, MDB_CREATE); - populateDatabase(db); + + // Use a java comparator for start/stop keys only + dbJavaComparator = env.openDbi(DB_1, bufferProxy.getUnsignedComparator(), MDB_CREATE); + // Use LMDB comparator for start/stop keys + dbLmdbComparator = env.openDbi(DB_2, MDB_CREATE); + // Use a java comparator for start/stop keys and as a callback comparaotr + dbCallbackComparator = env.openDbi( + DB_3, bufferProxy.getUnsignedComparator(), true, MDB_CREATE); + + populateList(); + + populateDatabase(dbJavaComparator); + populateDatabase(dbLmdbComparator); + populateDatabase(dbCallbackComparator); + + dbs.add(dbJavaComparator); + dbs.add(dbLmdbComparator); + dbs.add(dbCallbackComparator); } - private void populateDatabase(final Dbi dbi) { + private void populateList() { list = new LinkedList<>(); list.addAll(asList(2, 3, 4, 5, 6, 7, 8, 9)); + } + + private void populateDatabase(final Dbi dbi) { try (Txn txn = env.txnWrite()) { final Cursor c = dbi.openCursor(txn); c.put(bb(2), bb(3), MDB_NOOVERWRITE); @@ -166,6 +191,14 @@ public void closedTest() { verify(closed(bb(1), bb(7)), 2, 4, 6); } + public void closedTest1() { + verify(dbLmdbComparator, closed(bb(3), bb(7)), 4, 6); + } + + public void closedTest2() { + verify(dbJavaComparator, closed(bb(3), bb(7)), 4, 6); + } + @Test public void greaterThanBackwardTest() { verify(greaterThanBackward(bb(6)), 4, 2); @@ -181,30 +214,39 @@ public void greaterThanTest() { @Test(expected = IllegalStateException.class) public void iterableOnlyReturnedOnce() { - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { - c.iterator(); // ok - c.iterator(); // fails + for (final Dbi db : dbs) { + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + c.iterator(); // ok + c.iterator(); // fails + } } } @Test public void iterate() { - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { - for (final KeyVal kv : c) { - assertThat(kv.key().getInt(), is(list.pollFirst())); - assertThat(kv.val().getInt(), is(list.pollFirst())); + for (final Dbi db : dbs) { + populateList(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + + int cnt = 0; + for (final KeyVal kv : c) { + assertThat(kv.key().getInt(), is(list.pollFirst())); + assertThat(kv.val().getInt(), is(list.pollFirst())); + } } } } @Test(expected = IllegalStateException.class) public void iteratorOnlyReturnedOnce() { - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { - c.iterator(); // ok - c.iterator(); // fails + for (final Dbi db : dbs) { + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + c.iterator(); // ok + c.iterator(); // fails + } } } @@ -222,16 +264,19 @@ public void lessThanTest() { @Test(expected = NoSuchElementException.class) public void nextThrowsNoSuchElementExceptionIfNoMoreElements() { - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { - final Iterator> i = c.iterator(); - while (i.hasNext()) { - final KeyVal kv = i.next(); - assertThat(kv.key().getInt(), is(list.pollFirst())); - assertThat(kv.val().getInt(), is(list.pollFirst())); + for (final Dbi db : dbs) { + populateList(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + final Iterator> i = c.iterator(); + while (i.hasNext()) { + final KeyVal kv = i.next(); + assertThat(kv.key().getInt(), is(list.pollFirst())); + assertThat(kv.val().getInt(), is(list.pollFirst())); + } + assertThat(i.hasNext(), is(false)); + i.next(); } - assertThat(i.hasNext(), is(false)); - i.next(); } } @@ -284,81 +329,148 @@ public void openTest() { @Test public void removeOddElements() { - verify(all(), 2, 4, 6, 8); - int idx = -1; - try (Txn txn = env.txnWrite()) { - try (CursorIterable ci = db.iterate(txn)) { - final Iterator> c = ci.iterator(); - while (c.hasNext()) { - c.next(); - idx++; - if (idx % 2 == 0) { - c.remove(); + for (final Dbi db : dbs) { + verify(db, all(), 2, 4, 6, 8); + int idx = -1; + try (Txn txn = env.txnWrite()) { + try (CursorIterable ci = db.iterate(txn)) { + final Iterator> c = ci.iterator(); + while (c.hasNext()) { + c.next(); + idx++; + if (idx % 2 == 0) { + c.remove(); + } } } + txn.commit(); } - txn.commit(); + verify(db, all(), 4, 8); } - verify(all(), 4, 8); } @Test(expected = Env.AlreadyClosedException.class) public void nextWithClosedEnvTest() { - try (Txn txn = env.txnRead()) { - try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { - final Iterator> c = ci.iterator(); + for (final Dbi db : dbs) { + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); - env.close(); - c.next(); + env.close(); + c.next(); + } } } } @Test(expected = Env.AlreadyClosedException.class) public void removeWithClosedEnvTest() { - try (Txn txn = env.txnWrite()) { - try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { - final Iterator> c = ci.iterator(); + for (final Dbi db : dbs) { + try (Txn txn = env.txnWrite()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); - final KeyVal keyVal = c.next(); - assertThat(keyVal, Matchers.notNullValue()); + final KeyVal keyVal = c.next(); + assertThat(keyVal, Matchers.notNullValue()); - env.close(); - c.remove(); + env.close(); + c.remove(); + } } } } @Test(expected = Env.AlreadyClosedException.class) public void hasNextWithClosedEnvTest() { - try (Txn txn = env.txnRead()) { - try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { - final Iterator> c = ci.iterator(); + for (final Dbi db : dbs) { + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); - env.close(); - c.hasNext(); + env.close(); + c.hasNext(); + } } } } @Test(expected = Env.AlreadyClosedException.class) public void forEachRemainingWithClosedEnvTest() { - try (Txn txn = env.txnRead()) { - try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { - final Iterator> c = ci.iterator(); + for (final Dbi db : dbs) { + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); - env.close(); - c.forEachRemaining(keyVal -> {}); + env.close(); + c.forEachRemaining(keyVal -> {}); + } + } + } + } + + @Test + public void testSignedVsUnsigned() { + final ByteBuffer val1 = bb(1); + final ByteBuffer val2 = bb(2); + final ByteBuffer val110 = bb(110); + final ByteBuffer val111 = bb(111); + final ByteBuffer val150 = bb(150); + + final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; + final Comparator unsignedComparator = bufferProxy.getUnsignedComparator(); + final Comparator signedComparator = bufferProxy.getSignedComparator(); + + // Compare the same + assertThat( + unsignedComparator.compare(val1, val2), + Matchers.is(signedComparator.compare(val1, val2))); + + // Compare differently + assertThat( + unsignedComparator.compare(val110, val150), + Matchers.not(signedComparator.compare(val110, val150))); + + // Compare differently + assertThat( + unsignedComparator.compare(val111, val150), + Matchers.not(signedComparator.compare(val111, val150))); + + // This will fail if the db is using a signed comparator for the start/stop keys + for (final Dbi db : dbs) { + db.put(val110, val110); + db.put(val150, val150); + + final ByteBuffer startKeyBuf = val111; + KeyRange keyRange = KeyRange.atLeastBackward(startKeyBuf); + + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn, keyRange)) { + for (final CursorIterable.KeyVal kv : c) { + final int key = kv.key().getInt(); + final int val = kv.val().getInt(); +// System.out.println("key: " + key + " val: " + val); + assertThat(key, is(110)); + break; + } } } } private void verify(final KeyRange range, final int... expected) { - verify(range, db, expected); + // Verify using all comparator types + for (final Dbi db : dbs) { + verify(range, db, expected); + } + } + + private void verify( + final Dbi dbi, final KeyRange range, final int... expected) { + verify(range, dbi, expected); } private void verify( final KeyRange range, final Dbi dbi, final int... expected) { + final List results = new ArrayList<>(); try (Txn txn = env.txnRead(); diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 1fa80f6e..9c5cdb2e 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -111,7 +111,7 @@ public void close() { public void customComparator() { final Comparator reverseOrder = (o1, o2) -> { - final int lexical = PROXY_OPTIMAL.getComparator().compare(o1, o2); + final int lexical = PROXY_OPTIMAL.getUnsignedComparator().compare(o1, o2); if (lexical == 0) { return 0; } @@ -144,7 +144,7 @@ public void dbOpenMaxDatabases() { @Test public void dbiWithComparatorThreadSafety() { final DbiFlags[] flags = new DbiFlags[] {MDB_CREATE, MDB_INTEGERKEY}; - final Comparator c = PROXY_OPTIMAL.getComparator(flags); + final Comparator c = PROXY_OPTIMAL.getUnsignedComparator(); final Dbi db = env.openDbi(DB_1, c, true, flags); final List keys = range(0, 1_000).boxed().collect(toList()); diff --git a/src/test/java/org/lmdbjava/KeyRangeTest.java b/src/test/java/org/lmdbjava/KeyRangeTest.java index 6e104bbf..0197bf11 100644 --- a/src/test/java/org/lmdbjava/KeyRangeTest.java +++ b/src/test/java/org/lmdbjava/KeyRangeTest.java @@ -195,7 +195,10 @@ private void verify(final KeyRange range, final int... expected) { IteratorOp op; do { - op = range.getType().iteratorOp(range.getStart(), range.getStop(), buff, Integer::compare); + final Integer finalBuff = buff; + final RangeComparator rangeComparator = + CursorIterable.createJavaRangeComparator(range, Integer::compareTo, () -> finalBuff); + op = range.getType().iteratorOp(range.getStart(), range.getStop(), buff, rangeComparator); switch (op) { case CALL_NEXT_OP: buff = cursor.apply(range.getType().nextOp(), range.getStart()); diff --git a/src/test/java/org/lmdbjava/TestUtils.java b/src/test/java/org/lmdbjava/TestUtils.java index 42dcf052..f3d3974b 100644 --- a/src/test/java/org/lmdbjava/TestUtils.java +++ b/src/test/java/org/lmdbjava/TestUtils.java @@ -30,6 +30,8 @@ final class TestUtils { public static final String DB_1 = "test-db-1"; + public static final String DB_2 = "test-db-2"; + public static final String DB_3 = "test-db-3"; public static final int POSIX_MODE = 0664; From 46f8d08194d989529f87b447f48fad0214c10e80 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Thu, 6 Mar 2025 17:27:25 +0000 Subject: [PATCH 02/90] gh-249 Remove non-J8 features, use indent size 2 --- .../org/lmdbjava/CursorIterablePerfTest.java | 220 +++++++++--------- 1 file changed, 110 insertions(+), 110 deletions(-) diff --git a/src/test/java/org/lmdbjava/CursorIterablePerfTest.java b/src/test/java/org/lmdbjava/CursorIterablePerfTest.java index a9647d56..ab94f85f 100644 --- a/src/test/java/org/lmdbjava/CursorIterablePerfTest.java +++ b/src/test/java/org/lmdbjava/CursorIterablePerfTest.java @@ -29,135 +29,135 @@ public class CursorIterablePerfTest { @Rule public final TemporaryFolder tmp = new TemporaryFolder(); -// private static final int ITERATIONS = 5_000_000; - private static final int ITERATIONS = 100_000; -// private static final int ITERATIONS = 10; - - private Dbi dbJavaComparator; - private Dbi dbLmdbComparator; - private Dbi dbCallbackComparator; - private List> dbs = new ArrayList<>(); - private Env env; - private List data = new ArrayList<>(ITERATIONS); - - @Before - public void before() throws IOException { - final File path = tmp.newFile(); - final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; - env = - create(bufferProxy) - .setMapSize(GIBIBYTES.toBytes(1)) - .setMaxReaders(1) - .setMaxDbs(3) - .open(path, POSIX_MODE, MDB_NOSUBDIR); - - // Use a java comparator for start/stop keys only + // private static final int ITERATIONS = 5_000_000; + private static final int ITERATIONS = 100_000; + // private static final int ITERATIONS = 10; + + private Dbi dbJavaComparator; + private Dbi dbLmdbComparator; + private Dbi dbCallbackComparator; + private List> dbs = new ArrayList<>(); + private Env env; + private List data = new ArrayList<>(ITERATIONS); + + @Before + public void before() throws IOException { + final File path = tmp.newFile(); + final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; + env = + create(bufferProxy) + .setMapSize(GIBIBYTES.toBytes(1)) + .setMaxReaders(1) + .setMaxDbs(3) + .open(path, POSIX_MODE, MDB_NOSUBDIR); + + // Use a java comparator for start/stop keys only dbJavaComparator = env.openDbi("JavaComparator", bufferProxy.getUnsignedComparator(), MDB_CREATE); - // Use LMDB comparator for start/stop keys - dbLmdbComparator = env.openDbi("LmdbComparator", MDB_CREATE); - // Use a java comparator for start/stop keys and as a callback comparator + // Use LMDB comparator for start/stop keys + dbLmdbComparator = env.openDbi("LmdbComparator", MDB_CREATE); + // Use a java comparator for start/stop keys and as a callback comparator dbCallbackComparator = env.openDbi( "CallBackComparator", bufferProxy.getUnsignedComparator(), true, MDB_CREATE); - dbs.add(dbJavaComparator); - dbs.add(dbLmdbComparator); - dbs.add(dbCallbackComparator); + dbs.add(dbJavaComparator); + dbs.add(dbLmdbComparator); + dbs.add(dbCallbackComparator); - populateList(); + populateList(); + } + + private void populateList() { + for (int i = 0; i < ITERATIONS * 2; i += 2) { + data.add(i); } + } - private void populateList() { - for (int i = 0; i < ITERATIONS * 2; i+=2) { - data.add(i); - } + private void populateDatabases(final boolean randomOrder) { + System.out.println("Clear then populate databases"); + + final List data; + if (randomOrder) { + data = new ArrayList<>(this.data); + Collections.shuffle(data); + } else { + data = this.data; } - private void populateDatabases(final boolean randomOrder) { - System.out.println("Clear then populate databases"); + for (int round = 0; round < 3; round++) { + System.out.println("round: " + round + " -----------------------------------------"); - final List data; - if (randomOrder) { - data = new ArrayList<>(this.data); - Collections.shuffle(data); - } else { - data = this.data; + for (final Dbi db : dbs) { + // Clean out the db first + try (Txn txn = env.txnWrite(); + final Cursor cursor = db.openCursor(txn)) { + while (cursor.next()) { + cursor.delete(); + } } - for (int round = 0; round < 3; round++) { - System.out.println("round: " + round + " -----------------------------------------"); - - for (final Dbi db : dbs) { - // Clean out the db first - try (Txn txn = env.txnWrite(); - final Cursor cursor = db.openCursor(txn)) { - while (cursor.next()) { - cursor.delete(); - } - } - - final String dbName = new String(db.getName(), StandardCharsets.UTF_8); - final Instant start = Instant.now(); - try (Txn txn = env.txnWrite()) { - for (final Integer i : data) { - if (randomOrder) { - db.put(txn, bb(i), bb(i + 1), MDB_NOOVERWRITE); - } else { - db.put(txn, bb(i), bb(i + 1), MDB_NOOVERWRITE, MDB_APPEND); - } - } - txn.commit(); - } - final Duration duration = Duration.between(start, Instant.now()); + final String dbName = new String(db.getName(), StandardCharsets.UTF_8); + final Instant start = Instant.now(); + try (Txn txn = env.txnWrite()) { + for (final Integer i : data) { + if (randomOrder) { + db.put(txn, bb(i), bb(i + 1), MDB_NOOVERWRITE); + } else { + db.put(txn, bb(i), bb(i + 1), MDB_NOOVERWRITE, MDB_APPEND); + } + } + txn.commit(); + } + final Duration duration = Duration.between(start, Instant.now()); System.out.println("DB: " + dbName + " - Loaded in duration: " + duration + ", millis: " + duration.toMillis()); - } - } - } - - @After - public void after() { - env.close(); - tmp.delete(); + } } - - @Test - public void comparePerf_sequential() { - comparePerf(false); - } - - @Test - public void comparePerf_random() { - comparePerf(true); - } - - public void comparePerf(final boolean randomOrder) { - populateDatabases(randomOrder); - final ByteBuffer startKeyBuf = bb(data.getFirst()); - final ByteBuffer stopKeyBuf = bb(data.getLast()); - final KeyRange keyRange = KeyRange.closed(startKeyBuf, stopKeyBuf); - - System.out.println("\nIterating over all entries"); - for (int round = 0; round < 3; round++) { - System.out.println("round: " + round + " -----------------------------------------"); - for (final Dbi db : dbs) { - final String dbName = new String(db.getName(), StandardCharsets.UTF_8); - - final Instant start = Instant.now(); - int cnt = 0; - // Exercise the stop key comparator on every entry - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn, keyRange)) { - for (final CursorIterable.KeyVal kv : c) { - cnt++; - } - } - final Duration duration = Duration.between(start, Instant.now()); + } + + @After + public void after() { + env.close(); + tmp.delete(); + } + + @Test + public void comparePerf_sequential() { + comparePerf(false); + } + + @Test + public void comparePerf_random() { + comparePerf(true); + } + + public void comparePerf(final boolean randomOrder) { + populateDatabases(randomOrder); + final ByteBuffer startKeyBuf = bb(data.get(0)); + final ByteBuffer stopKeyBuf = bb(data.get(data.size() - 1)); + final KeyRange keyRange = KeyRange.closed(startKeyBuf, stopKeyBuf); + + System.out.println("\nIterating over all entries"); + for (int round = 0; round < 3; round++) { + System.out.println("round: " + round + " -----------------------------------------"); + for (final Dbi db : dbs) { + final String dbName = new String(db.getName(), StandardCharsets.UTF_8); + + final Instant start = Instant.now(); + int cnt = 0; + // Exercise the stop key comparator on every entry + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn, keyRange)) { + for (final CursorIterable.KeyVal kv : c) { + cnt++; + } + } + final Duration duration = Duration.between(start, Instant.now()); System.out.println("DB: " + dbName + " - Iterated in duration: " + duration + ", millis: " + duration.toMillis() + ", cnt: " + cnt); - } - } + } } + } } From f92012ecc079149b2414925e0a077a75f82ba043 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Thu, 6 Mar 2025 17:33:40 +0000 Subject: [PATCH 03/90] gh-249 Fix indents --- .../org/lmdbjava/CursorIterablePerfTest.java | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/test/java/org/lmdbjava/CursorIterablePerfTest.java b/src/test/java/org/lmdbjava/CursorIterablePerfTest.java index ab94f85f..99667b1d 100644 --- a/src/test/java/org/lmdbjava/CursorIterablePerfTest.java +++ b/src/test/java/org/lmdbjava/CursorIterablePerfTest.java @@ -26,8 +26,8 @@ public class CursorIterablePerfTest { - @Rule - public final TemporaryFolder tmp = new TemporaryFolder(); + @Rule + public final TemporaryFolder tmp = new TemporaryFolder(); // private static final int ITERATIONS = 5_000_000; private static final int ITERATIONS = 100_000; @@ -52,12 +52,12 @@ public void before() throws IOException { .open(path, POSIX_MODE, MDB_NOSUBDIR); // Use a java comparator for start/stop keys only - dbJavaComparator = env.openDbi("JavaComparator", bufferProxy.getUnsignedComparator(), MDB_CREATE); + dbJavaComparator = env.openDbi("JavaComparator", bufferProxy.getUnsignedComparator(), MDB_CREATE); // Use LMDB comparator for start/stop keys dbLmdbComparator = env.openDbi("LmdbComparator", MDB_CREATE); // Use a java comparator for start/stop keys and as a callback comparator - dbCallbackComparator = env.openDbi( - "CallBackComparator", bufferProxy.getUnsignedComparator(), true, MDB_CREATE); + dbCallbackComparator = env.openDbi( + "CallBackComparator", bufferProxy.getUnsignedComparator(), true, MDB_CREATE); dbs.add(dbJavaComparator); dbs.add(dbLmdbComparator); @@ -89,7 +89,7 @@ private void populateDatabases(final boolean randomOrder) { for (final Dbi db : dbs) { // Clean out the db first try (Txn txn = env.txnWrite(); - final Cursor cursor = db.openCursor(txn)) { + final Cursor cursor = db.openCursor(txn)) { while (cursor.next()) { cursor.delete(); } @@ -108,9 +108,9 @@ private void populateDatabases(final boolean randomOrder) { txn.commit(); } final Duration duration = Duration.between(start, Instant.now()); - System.out.println("DB: " + dbName - + " - Loaded in duration: " + duration - + ", millis: " + duration.toMillis()); + System.out.println("DB: " + dbName + + " - Loaded in duration: " + duration + + ", millis: " + duration.toMillis()); } } } @@ -147,16 +147,16 @@ public void comparePerf(final boolean randomOrder) { int cnt = 0; // Exercise the stop key comparator on every entry try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn, keyRange)) { + CursorIterable c = db.iterate(txn, keyRange)) { for (final CursorIterable.KeyVal kv : c) { cnt++; } } final Duration duration = Duration.between(start, Instant.now()); - System.out.println("DB: " + dbName - + " - Iterated in duration: " + duration - + ", millis: " + duration.toMillis() - + ", cnt: " + cnt); + System.out.println("DB: " + dbName + + " - Iterated in duration: " + duration + + ", millis: " + duration.toMillis() + + ", cnt: " + cnt); } } } From e1756d633d7cbd1d23a33ffa0fcaaaa44c06aacd Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Thu, 6 Mar 2025 19:02:46 +0000 Subject: [PATCH 04/90] gh-249 Tidy code and refactor RangeComparator impls --- src/main/java/org/lmdbjava/BufferProxy.java | 8 +- .../java/org/lmdbjava/CursorIterable.java | 544 ++++++++++-------- src/main/java/org/lmdbjava/Env.java | 23 +- src/main/java/org/lmdbjava/Key.java | 4 +- src/main/java/org/lmdbjava/KeyRangeType.java | 4 +- .../java/org/lmdbjava/RangeComparator.java | 26 +- .../org/lmdbjava/CursorIterablePerfTest.java | 37 +- .../java/org/lmdbjava/CursorIterableTest.java | 18 +- src/test/java/org/lmdbjava/KeyRangeTest.java | 4 +- 9 files changed, 376 insertions(+), 292 deletions(-) diff --git a/src/main/java/org/lmdbjava/BufferProxy.java b/src/main/java/org/lmdbjava/BufferProxy.java index 26d9db74..ab7ba3a4 100644 --- a/src/main/java/org/lmdbjava/BufferProxy.java +++ b/src/main/java/org/lmdbjava/BufferProxy.java @@ -69,11 +69,9 @@ protected BufferProxy() {} /** * Get a suitable default {@link Comparator} to compare numeric key values as signed. * - *

- * Note: LMDB's default comparator is unsigned so if this is used only for the {@link CursorIterable} - * start/stop key comparisons then its behaviour will differ from the iteration order. Use - * with caution. - *

+ *

Note: LMDB's default comparator is unsigned so if this is used only for the {@link + * CursorIterable} start/stop key comparisons then its behaviour will differ from the iteration + * order. Use with caution. * * @return a comparator that can be used (never null) */ diff --git a/src/main/java/org/lmdbjava/CursorIterable.java b/src/main/java/org/lmdbjava/CursorIterable.java index 6b92a9cd..7c487bae 100644 --- a/src/main/java/org/lmdbjava/CursorIterable.java +++ b/src/main/java/org/lmdbjava/CursorIterable.java @@ -42,266 +42,352 @@ */ public final class CursorIterable implements Iterable>, AutoCloseable { - // private final Comparator comparator; - private final RangeComparator rangeComparator; - private final Cursor cursor; - private final Dbi dbi; - private final KeyVal entry; - private boolean iteratorReturned; - private final KeyRange range; - private State state = REQUIRES_INITIAL_OP; - private final Key startKey; - private final Key stopKey; + // private final Comparator comparator; + private final RangeComparator rangeComparator; + private final Cursor cursor; + private final KeyVal entry; + private boolean iteratorReturned; + private final KeyRange range; + private State state = REQUIRES_INITIAL_OP; - CursorIterable( - final Txn txn, - final Dbi dbi, - final KeyRange range, - final Comparator comparator, - final BufferProxy proxy) { - this.cursor = dbi.openCursor(txn); - this.dbi = dbi; - this.range = range; - this.entry = new KeyVal<>(); - - if (comparator != null) { - // User supplied java-side comparator so use that - this.rangeComparator = createJavaRangeComparator(range, comparator, entry::key); - this.startKey = null; - this.stopKey = null; - } else { - // No java-side comparator so call down to LMDB to do the comparison - this.rangeComparator = createLmdbDbiComparator(txn.pointer(), dbi.pointer()); - // Allocate buffers for use with the start/stop keys if required. - // Saves us copying bytes on each comparison - this.startKey = createKey(range.getStart(), proxy); - this.stopKey = createKey(range.getStop(), proxy); - } + CursorIterable( + final Txn txn, + final Dbi dbi, + final KeyRange range, + final Comparator comparator, + final BufferProxy proxy) { + this.cursor = dbi.openCursor(txn); + this.range = range; + this.entry = new KeyVal<>(); + + if (comparator != null) { + // User supplied java-side comparator so use that + this.rangeComparator = new JavaRangeComparator<>(range, comparator, entry::key); + } else { + // No java-side comparator so call down to LMDB to do the comparison + this.rangeComparator = new LmdbRangeComparator<>(txn, dbi, cursor, range, proxy); } + } - private Key createKey(final T keyBuffer, final BufferProxy proxy) { - if (keyBuffer != null) { - final Key key = proxy.key(); - key.keyIn(keyBuffer); - return key; - } else { - return null; + // static RangeComparator createJavaRangeComparator( + // final KeyRange range, + // final Comparator comparator, + // final Supplier currentKeySupplier) { + // final T start = range.getStart(); + // final T stop = range.getStop(); + // return new RangeComparator() { + // @Override + // public int compareToStartKey() { + // return comparator.compare(currentKeySupplier.get(), start); + // } + // + // @Override + // public int compareToStopKey() { + // return comparator.compare(currentKeySupplier.get(), stop); + // } + // }; + // } + + // /** + // * Calls down to mdb_cmp to make use of the comparator that LMDB uses for insertion order. + // * + // * @param txnPointer The pointer to the transaction. + // * @param dbiPointer The pointer to the Dbi so LMDB can use the comparator of the Dbi + // * @param proxy + // */ + // private RangeComparator createLmdbDbiComparator(final Pointer txnPointer, + // final Pointer dbiPointer, + // final Pointer cursorKeyPointer, + // final KeyRange range, + // final BufferProxy proxy) { + // Objects.requireNonNull(txnPointer); + // Objects.requireNonNull(dbiPointer); + // Objects.requireNonNull(range); + // Objects.requireNonNull(cursor); + // // Allocate buffers for use with the start/stop keys if required. + // // Saves us copying bytes on each comparison + // final Key startKey = createKey(range.getStart(), proxy); + // final Key stopKey = createKey(range.getStop(), proxy); + // + // return new RangeComparator() { + // @Override + // public int compareToStartKey() { + // return LIB.mdb_cmp(txnPointer, dbiPointer, cursor.keyVal().pointerKey(), + // startKey.pointer()); + // } + // + // @Override + // public int compareToStopKey() { + // return LIB.mdb_cmp(txnPointer, dbiPointer, cursor.keyVal().pointerKey(), + // stopKey.pointer()); + // } + // }; + // } + + @Override + public void close() { + cursor.close(); + try { + rangeComparator.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Obtain an iterator. + * + *

As iteration of the returned iterator will cause movement of the underlying LMDB cursor, an + * {@link IllegalStateException} is thrown if an attempt is made to obtain the iterator more than + * once. For advanced cursor control (such as being able to iterate over the same data multiple + * times etc) please instead refer to {@link Dbi#openCursor(org.lmdbjava.Txn)}. + * + * @return an iterator + */ + @Override + public Iterator> iterator() { + if (iteratorReturned) { + throw new IllegalStateException("Iterator can only be returned once"); + } + iteratorReturned = true; + + return new Iterator>() { + @Override + public boolean hasNext() { + while (state != RELEASED && state != TERMINATED) { + update(); + } + return state == RELEASED; + } + + @Override + public KeyVal next() { + if (!hasNext()) { + throw new NoSuchElementException(); } + state = REQUIRES_NEXT_OP; + return entry; + } + + @Override + public void remove() { + cursor.delete(); + } + }; + } + + private void executeCursorOp(final CursorOp op) { + final boolean found; + switch (op) { + case FIRST: + found = cursor.first(); + break; + case LAST: + found = cursor.last(); + break; + case NEXT: + found = cursor.next(); + break; + case PREV: + found = cursor.prev(); + break; + case GET_START_KEY: + found = cursor.get(range.getStart(), MDB_SET_RANGE); + break; + case GET_START_KEY_BACKWARD: + found = cursor.get(range.getStart(), MDB_SET_RANGE) || cursor.last(); + break; + default: + throw new IllegalStateException("Unknown cursor operation"); } + entry.setK(found ? cursor.key() : null); + entry.setV(found ? cursor.val() : null); + } - static RangeComparator createJavaRangeComparator( - final KeyRange range, - final Comparator comparator, - final Supplier currentKeySupplier) { - final T start = range.getStart(); - final T stop = range.getStop(); - return new RangeComparator() { - @Override - public int compareToStartKey() { - return comparator.compare(currentKeySupplier.get(), start); - } - - @Override - public int compareToStopKey() { - return comparator.compare(currentKeySupplier.get(), stop); - } - }; + private void executeIteratorOp() { + final IteratorOp op = range.getType().iteratorOp(entry.key(), rangeComparator); + switch (op) { + case CALL_NEXT_OP: + executeCursorOp(range.getType().nextOp()); + state = REQUIRES_ITERATOR_OP; + break; + case TERMINATE: + state = TERMINATED; + break; + case RELEASE: + state = RELEASED; + break; + default: + throw new IllegalStateException("Unknown operation"); } + } + + private void update() { + switch (state) { + case REQUIRES_INITIAL_OP: + executeCursorOp(range.getType().initialOp()); + state = REQUIRES_ITERATOR_OP; + break; + case REQUIRES_NEXT_OP: + executeCursorOp(range.getType().nextOp()); + state = REQUIRES_ITERATOR_OP; + break; + case REQUIRES_ITERATOR_OP: + executeIteratorOp(); + break; + case TERMINATED: + break; + default: + throw new IllegalStateException("Unknown state"); + } + } + + /** + * Holder for a key and value pair. + * + *

The same holder instance will always be returned for a given iterator. The returned keys and + * values may change or point to different memory locations following changes in the iterator, + * cursor or transaction. + * + * @param buffer type + */ + public static final class KeyVal { + + private T k; + private T v; + + /** Explicitly-defined default constructor to avoid warnings. */ + public KeyVal() {} /** - * Calls down to mdb_cmp to make use of the comparator that LMDB uses for insertion order. + * The key. * - * @param txnPointer The pointer to the transaction. - * @param dbiPointer The pointer to the Dbi so LMDB can use the comparator of the Dbi + * @return key */ - private RangeComparator createLmdbDbiComparator( - final Pointer txnPointer, final Pointer dbiPointer) { - Objects.requireNonNull(txnPointer); - Objects.requireNonNull(dbiPointer); - Objects.requireNonNull(cursor); - - return new RangeComparator() { - @Override - public int compareToStartKey() { - return LIB.mdb_cmp(txnPointer, dbiPointer, cursor.keyVal().pointerKey(), startKey.pointerKey()); - } - - @Override - public int compareToStopKey() { - return LIB.mdb_cmp(txnPointer, dbiPointer, cursor.keyVal().pointerKey(), stopKey.pointerKey()); - } - }; - } - - @Override - public void close() { - cursor.close(); + public T key() { + return k; } /** - * Obtain an iterator. + * The value. * - *

As iteration of the returned iterator will cause movement of the underlying LMDB cursor, an - * {@link IllegalStateException} is thrown if an attempt is made to obtain the iterator more than - * once. For advanced cursor control (such as being able to iterate over the same data multiple - * times etc) please instead refer to {@link Dbi#openCursor(org.lmdbjava.Txn)}. - * - * @return an iterator + * @return value */ - @Override - public Iterator> iterator() { - if (iteratorReturned) { - throw new IllegalStateException("Iterator can only be returned once"); - } - iteratorReturned = true; - - return new Iterator>() { - @Override - public boolean hasNext() { - while (state != RELEASED && state != TERMINATED) { - update(); - } - return state == RELEASED; - } - - @Override - public KeyVal next() { - if (!hasNext()) { - throw new NoSuchElementException(); - } - state = REQUIRES_NEXT_OP; - return entry; - } - - @Override - public void remove() { - cursor.delete(); - } - }; + public T val() { + return v; } - private void executeCursorOp(final CursorOp op) { - final boolean found; - switch (op) { - case FIRST: - found = cursor.first(); - break; - case LAST: - found = cursor.last(); - break; - case NEXT: - found = cursor.next(); - break; - case PREV: - found = cursor.prev(); - break; - case GET_START_KEY: - found = cursor.get(range.getStart(), MDB_SET_RANGE); - break; - case GET_START_KEY_BACKWARD: - found = cursor.get(range.getStart(), MDB_SET_RANGE) || cursor.last(); - break; - default: - throw new IllegalStateException("Unknown cursor operation"); - } - entry.setK(found ? cursor.key() : null); - entry.setV(found ? cursor.val() : null); + void setK(final T key) { + this.k = key; } - private void executeIteratorOp() { - final IteratorOp op = - range.getType().iteratorOp(range.getStart(), range.getStop(), entry.key(), rangeComparator); - switch (op) { - case CALL_NEXT_OP: - executeCursorOp(range.getType().nextOp()); - state = REQUIRES_ITERATOR_OP; - break; - case TERMINATE: - state = TERMINATED; - break; - case RELEASE: - state = RELEASED; - break; - default: - throw new IllegalStateException("Unknown operation"); - } + void setV(final T val) { + this.v = val; } + } - private void update() { - switch (state) { - case REQUIRES_INITIAL_OP: - executeCursorOp(range.getType().initialOp()); - state = REQUIRES_ITERATOR_OP; - break; - case REQUIRES_NEXT_OP: - executeCursorOp(range.getType().nextOp()); - state = REQUIRES_ITERATOR_OP; - break; - case REQUIRES_ITERATOR_OP: - executeIteratorOp(); - break; - case TERMINATED: - break; - default: - throw new IllegalStateException("Unknown state"); - } + /** Represents the internal {@link CursorIterable} state. */ + enum State { + REQUIRES_INITIAL_OP, + REQUIRES_NEXT_OP, + REQUIRES_ITERATOR_OP, + RELEASED, + TERMINATED + } + + static class JavaRangeComparator implements RangeComparator { + + private final Comparator comparator; + private final Supplier currentKeySupplier; + private final T start; + private final T stop; + + JavaRangeComparator( + final KeyRange range, + final Comparator comparator, + final Supplier currentKeySupplier) { + this.comparator = comparator; + this.currentKeySupplier = currentKeySupplier; + this.start = range.getStart(); + this.stop = range.getStop(); } - /** - * Holder for a key and value pair. - * - *

The same holder instance will always be returned for a given iterator. The returned keys and - * values may change or point to different memory locations following changes in the iterator, - * cursor or transaction. - * - * @param buffer type - */ - public static final class KeyVal { + @Override + public int compareToStartKey() { + return comparator.compare(currentKeySupplier.get(), start); + } - private T k; - private T v; + @Override + public int compareToStopKey() { + return comparator.compare(currentKeySupplier.get(), stop); + } - /** - * Explicitly-defined default constructor to avoid warnings. - */ - public KeyVal() { - } + @Override + public void close() throws Exception { + // Nothing to close + } + } - /** - * The key. - * - * @return key - */ - public T key() { - return k; - } + /** + * Calls down to mdb_cmp to make use of the comparator that LMDB uses for insertion order. Has a + * very slight overhead as compared to {@link JavaRangeComparator}. + */ + private static class LmdbRangeComparator implements RangeComparator { - /** - * The value. - * - * @return value - */ - public T val() { - return v; - } + private final Pointer txnPointer; + private final Pointer dbiPointer; + private final Pointer cursorKeyPointer; + private final Key startKey; + private final Key stopKey; + private final Pointer startKeyPointer; + private final Pointer stopKeyPointer; - void setK(final T key) { - this.k = key; - } + public LmdbRangeComparator( + final Txn txn, + final Dbi dbi, + final Cursor cursor, + final KeyRange range, + final BufferProxy proxy) { + txnPointer = Objects.requireNonNull(txn).pointer(); + dbiPointer = Objects.requireNonNull(dbi).pointer(); + cursorKeyPointer = Objects.requireNonNull(cursor).keyVal().pointerKey(); + // Allocate buffers for use with the start/stop keys if required. + // Saves us copying bytes on each comparison + Objects.requireNonNull(range); + startKey = createKey(range.getStart(), proxy); + stopKey = createKey(range.getStop(), proxy); + startKeyPointer = startKey != null ? startKey.pointer() : null; + stopKeyPointer = stopKey != null ? stopKey.pointer() : null; + } - void setV(final T val) { - this.v = val; - } + @Override + public int compareToStartKey() { + return LIB.mdb_cmp(txnPointer, dbiPointer, cursorKeyPointer, startKeyPointer); } - /** - * Represents the internal {@link CursorIterable} state. - */ - enum State { - REQUIRES_INITIAL_OP, - REQUIRES_NEXT_OP, - REQUIRES_ITERATOR_OP, - RELEASED, - TERMINATED + @Override + public int compareToStopKey() { + return LIB.mdb_cmp(txnPointer, dbiPointer, cursorKeyPointer, stopKeyPointer); + } + + @Override + public void close() { + if (startKey != null) { + startKey.close(); + } + if (stopKey != null) { + stopKey.close(); + } + } + + private Key createKey(final T keyBuffer, final BufferProxy proxy) { + if (keyBuffer != null) { + final Key key = proxy.key(); + key.keyIn(keyBuffer); + return key; + } else { + return null; + } } + } } diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index 1543282c..2e4822b7 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -257,16 +257,15 @@ public Dbi openDbi(final String name, final DbiFlags... flags) { /** * Convenience method that opens a {@link Dbi} with a UTF-8 database name and associated {@link * Comparator} for use by {@link CursorIterable} when comparing start/stop keys. - *

- * It is very important that the passed comparator behaves in the same way as the comparator + * + *

It is very important that the passed comparator behaves in the same way as the comparator * LMDB uses for its insertion order (for the type of data that will be stored in the database), - * or you fully understand the implications of them behaving differently. - * LMDB's comparator is unsigned lexicographical, unless {@link DbiFlags#MDB_INTEGERKEY} is used. - *

+ * or you fully understand the implications of them behaving differently. LMDB's comparator is + * unsigned lexicographical, unless {@link DbiFlags#MDB_INTEGERKEY} is used. * * @param name name of the database (or null if no name is required) - * @param comparator custom comparator for cursor start/stop key comparisons. If null, - * LMDB's comparator will be used. + * @param comparator custom comparator for cursor start/stop key comparisons. If null, LMDB's + * comparator will be used. * @param flags to open the database with * @return a database that is ready to use */ @@ -278,14 +277,14 @@ public Dbi openDbi( /** * Convenience method that opens a {@link Dbi} with a UTF-8 database name and associated {@link - * Comparator}. The comparator will be used by {@link CursorIterable} when comparing start/stop keys - * as a minimum. If nativeCb is {@code true}, this comparator will also be called by LMDB to determine - * insertion/iteration order. Calling back to a java comparator may significantly impact performance. + * Comparator}. The comparator will be used by {@link CursorIterable} when comparing start/stop + * keys as a minimum. If nativeCb is {@code true}, this comparator will also be called by LMDB to + * determine insertion/iteration order. Calling back to a java comparator may significantly impact + * performance. * * @param name name of the database (or null if no name is required) * @param comparator custom comparator for cursor start/stop key comparisons and optionally for - * LMDB to call back to. If null, - * LMDB's comparator will be used. + * LMDB to call back to. If null, LMDB's comparator will be used. * @param nativeCb whether LMDB native code calls back to the Java comparator * @param flags to open the database with * @return a database that is ready to use diff --git a/src/main/java/org/lmdbjava/Key.java b/src/main/java/org/lmdbjava/Key.java index 7fd8bbe2..12c54290 100644 --- a/src/main/java/org/lmdbjava/Key.java +++ b/src/main/java/org/lmdbjava/Key.java @@ -33,7 +33,6 @@ final class Key implements AutoCloseable { private boolean closed; private T k; private final BufferProxy proxy; - private final Pointer ptrArray; private final Pointer ptrKey; private final long ptrKeyAddr; @@ -43,7 +42,6 @@ final class Key implements AutoCloseable { this.k = proxy.allocate(); ptrKey = MEM_MGR.allocateTemporary(MDB_VAL_STRUCT_SIZE, false); ptrKeyAddr = ptrKey.address(); - ptrArray = MEM_MGR.allocateTemporary(MDB_VAL_STRUCT_SIZE * 2, false); } @Override @@ -68,7 +66,7 @@ T keyOut() { return k; } - Pointer pointerKey() { + Pointer pointer() { return ptrKey; } } diff --git a/src/main/java/org/lmdbjava/KeyRangeType.java b/src/main/java/org/lmdbjava/KeyRangeType.java index 07123e9a..26f09636 100644 --- a/src/main/java/org/lmdbjava/KeyRangeType.java +++ b/src/main/java/org/lmdbjava/KeyRangeType.java @@ -319,14 +319,12 @@ CursorOp initialOp() { * * @param buffer type * @param comparator for the buffers - * @param start start buffer - * @param stop stop buffer * @param buffer current key returned by LMDB (may be null) * @param rangeComparator comparator (required) * @return response to this key */ > IteratorOp iteratorOp( - final T start, final T stop, final T buffer, final RangeComparator rangeComparator) { + final T buffer, final RangeComparator rangeComparator) { requireNonNull(rangeComparator, "Comparator required"); if (buffer == null) { return TERMINATE; diff --git a/src/main/java/org/lmdbjava/RangeComparator.java b/src/main/java/org/lmdbjava/RangeComparator.java index 162584b1..59d46015 100644 --- a/src/main/java/org/lmdbjava/RangeComparator.java +++ b/src/main/java/org/lmdbjava/RangeComparator.java @@ -1,19 +1,17 @@ package org.lmdbjava; -/** - * For comparing a cursor's current key against a {@link KeyRange}'s start/stop key. - */ -interface RangeComparator { +/** For comparing a cursor's current key against a {@link KeyRange}'s start/stop key. */ +interface RangeComparator extends AutoCloseable { - /** - * Compare the cursor's current key to the range start key. Equivalent to compareTo(currentKey, - * startKey) - */ - int compareToStartKey(); + /** + * Compare the cursor's current key to the range start key. Equivalent to compareTo(currentKey, + * startKey) + */ + int compareToStartKey(); - /** - * Compare the cursor's current key to the range stop key. Equivalent to compareTo(currentKey, - * stopKey) - */ - int compareToStopKey(); + /** + * Compare the cursor's current key to the range stop key. Equivalent to compareTo(currentKey, + * stopKey) + */ + int compareToStopKey(); } diff --git a/src/test/java/org/lmdbjava/CursorIterablePerfTest.java b/src/test/java/org/lmdbjava/CursorIterablePerfTest.java index 99667b1d..19e5a9b9 100644 --- a/src/test/java/org/lmdbjava/CursorIterablePerfTest.java +++ b/src/test/java/org/lmdbjava/CursorIterablePerfTest.java @@ -26,8 +26,7 @@ public class CursorIterablePerfTest { - @Rule - public final TemporaryFolder tmp = new TemporaryFolder(); + @Rule public final TemporaryFolder tmp = new TemporaryFolder(); // private static final int ITERATIONS = 5_000_000; private static final int ITERATIONS = 100_000; @@ -52,12 +51,13 @@ public void before() throws IOException { .open(path, POSIX_MODE, MDB_NOSUBDIR); // Use a java comparator for start/stop keys only - dbJavaComparator = env.openDbi("JavaComparator", bufferProxy.getUnsignedComparator(), MDB_CREATE); + dbJavaComparator = + env.openDbi("JavaComparator", bufferProxy.getUnsignedComparator(), MDB_CREATE); // Use LMDB comparator for start/stop keys dbLmdbComparator = env.openDbi("LmdbComparator", MDB_CREATE); // Use a java comparator for start/stop keys and as a callback comparator - dbCallbackComparator = env.openDbi( - "CallBackComparator", bufferProxy.getUnsignedComparator(), true, MDB_CREATE); + dbCallbackComparator = + env.openDbi("CallBackComparator", bufferProxy.getUnsignedComparator(), true, MDB_CREATE); dbs.add(dbJavaComparator); dbs.add(dbLmdbComparator); @@ -89,7 +89,7 @@ private void populateDatabases(final boolean randomOrder) { for (final Dbi db : dbs) { // Clean out the db first try (Txn txn = env.txnWrite(); - final Cursor cursor = db.openCursor(txn)) { + final Cursor cursor = db.openCursor(txn)) { while (cursor.next()) { cursor.delete(); } @@ -108,9 +108,13 @@ private void populateDatabases(final boolean randomOrder) { txn.commit(); } final Duration duration = Duration.between(start, Instant.now()); - System.out.println("DB: " + dbName - + " - Loaded in duration: " + duration - + ", millis: " + duration.toMillis()); + System.out.println( + "DB: " + + dbName + + " - Loaded in duration: " + + duration + + ", millis: " + + duration.toMillis()); } } } @@ -147,16 +151,21 @@ public void comparePerf(final boolean randomOrder) { int cnt = 0; // Exercise the stop key comparator on every entry try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn, keyRange)) { + CursorIterable c = db.iterate(txn, keyRange)) { for (final CursorIterable.KeyVal kv : c) { cnt++; } } final Duration duration = Duration.between(start, Instant.now()); - System.out.println("DB: " + dbName - + " - Iterated in duration: " + duration - + ", millis: " + duration.toMillis() - + ", cnt: " + cnt); + System.out.println( + "DB: " + + dbName + + " - Iterated in duration: " + + duration + + ", millis: " + + duration.toMillis() + + ", cnt: " + + cnt); } } } diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index 96be0816..22cf7361 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -134,8 +134,7 @@ public void before() throws IOException { // Use LMDB comparator for start/stop keys dbLmdbComparator = env.openDbi(DB_2, MDB_CREATE); // Use a java comparator for start/stop keys and as a callback comparaotr - dbCallbackComparator = env.openDbi( - DB_3, bufferProxy.getUnsignedComparator(), true, MDB_CREATE); + dbCallbackComparator = env.openDbi(DB_3, bufferProxy.getUnsignedComparator(), true, MDB_CREATE); populateList(); @@ -422,18 +421,17 @@ public void testSignedVsUnsigned() { // Compare the same assertThat( - unsignedComparator.compare(val1, val2), - Matchers.is(signedComparator.compare(val1, val2))); + unsignedComparator.compare(val1, val2), Matchers.is(signedComparator.compare(val1, val2))); // Compare differently assertThat( - unsignedComparator.compare(val110, val150), - Matchers.not(signedComparator.compare(val110, val150))); + unsignedComparator.compare(val110, val150), + Matchers.not(signedComparator.compare(val110, val150))); // Compare differently assertThat( - unsignedComparator.compare(val111, val150), - Matchers.not(signedComparator.compare(val111, val150))); + unsignedComparator.compare(val111, val150), + Matchers.not(signedComparator.compare(val111, val150))); // This will fail if the db is using a signed comparator for the start/stop keys for (final Dbi db : dbs) { @@ -444,11 +442,11 @@ public void testSignedVsUnsigned() { KeyRange keyRange = KeyRange.atLeastBackward(startKeyBuf); try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn, keyRange)) { + CursorIterable c = db.iterate(txn, keyRange)) { for (final CursorIterable.KeyVal kv : c) { final int key = kv.key().getInt(); final int val = kv.val().getInt(); -// System.out.println("key: " + key + " val: " + val); + // System.out.println("key: " + key + " val: " + val); assertThat(key, is(110)); break; } diff --git a/src/test/java/org/lmdbjava/KeyRangeTest.java b/src/test/java/org/lmdbjava/KeyRangeTest.java index 0197bf11..c474e982 100644 --- a/src/test/java/org/lmdbjava/KeyRangeTest.java +++ b/src/test/java/org/lmdbjava/KeyRangeTest.java @@ -197,8 +197,8 @@ private void verify(final KeyRange range, final int... expected) { do { final Integer finalBuff = buff; final RangeComparator rangeComparator = - CursorIterable.createJavaRangeComparator(range, Integer::compareTo, () -> finalBuff); - op = range.getType().iteratorOp(range.getStart(), range.getStop(), buff, rangeComparator); + new CursorIterable.JavaRangeComparator<>(range, Integer::compareTo, () -> finalBuff); + op = range.getType().iteratorOp(buff, rangeComparator); switch (op) { case CALL_NEXT_OP: buff = cursor.apply(range.getType().nextOp(), range.getStart()); From 9509f6bea115c7839e14881e4ecb4459211820d4 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Thu, 6 Mar 2025 19:05:48 +0000 Subject: [PATCH 05/90] gh-249 Remove commented code --- .../java/org/lmdbjava/CursorIterable.java | 56 ------------------- 1 file changed, 56 deletions(-) diff --git a/src/main/java/org/lmdbjava/CursorIterable.java b/src/main/java/org/lmdbjava/CursorIterable.java index 7c487bae..b0de7d06 100644 --- a/src/main/java/org/lmdbjava/CursorIterable.java +++ b/src/main/java/org/lmdbjava/CursorIterable.java @@ -42,7 +42,6 @@ */ public final class CursorIterable implements Iterable>, AutoCloseable { - // private final Comparator comparator; private final RangeComparator rangeComparator; private final Cursor cursor; private final KeyVal entry; @@ -69,61 +68,6 @@ public final class CursorIterable implements Iterable RangeComparator createJavaRangeComparator( - // final KeyRange range, - // final Comparator comparator, - // final Supplier currentKeySupplier) { - // final T start = range.getStart(); - // final T stop = range.getStop(); - // return new RangeComparator() { - // @Override - // public int compareToStartKey() { - // return comparator.compare(currentKeySupplier.get(), start); - // } - // - // @Override - // public int compareToStopKey() { - // return comparator.compare(currentKeySupplier.get(), stop); - // } - // }; - // } - - // /** - // * Calls down to mdb_cmp to make use of the comparator that LMDB uses for insertion order. - // * - // * @param txnPointer The pointer to the transaction. - // * @param dbiPointer The pointer to the Dbi so LMDB can use the comparator of the Dbi - // * @param proxy - // */ - // private RangeComparator createLmdbDbiComparator(final Pointer txnPointer, - // final Pointer dbiPointer, - // final Pointer cursorKeyPointer, - // final KeyRange range, - // final BufferProxy proxy) { - // Objects.requireNonNull(txnPointer); - // Objects.requireNonNull(dbiPointer); - // Objects.requireNonNull(range); - // Objects.requireNonNull(cursor); - // // Allocate buffers for use with the start/stop keys if required. - // // Saves us copying bytes on each comparison - // final Key startKey = createKey(range.getStart(), proxy); - // final Key stopKey = createKey(range.getStop(), proxy); - // - // return new RangeComparator() { - // @Override - // public int compareToStartKey() { - // return LIB.mdb_cmp(txnPointer, dbiPointer, cursor.keyVal().pointerKey(), - // startKey.pointer()); - // } - // - // @Override - // public int compareToStopKey() { - // return LIB.mdb_cmp(txnPointer, dbiPointer, cursor.keyVal().pointerKey(), - // stopKey.pointer()); - // } - // }; - // } - @Override public void close() { cursor.close(); From 67e2df1002ee86534ad082beb83a1c753e2ab6b5 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Fri, 7 Mar 2025 10:31:12 +0000 Subject: [PATCH 06/90] gh-249 Add CursorIterableIntegerKeyTest --- .../CursorIterableIntegerKeyTest.java | 493 ++++++++++++++++++ .../org/lmdbjava/CursorIterablePerfTest.java | 2 +- src/test/java/org/lmdbjava/TestUtils.java | 15 + 3 files changed, 509 insertions(+), 1 deletion(-) create mode 100644 src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java diff --git a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java new file mode 100644 index 00000000..431c6e51 --- /dev/null +++ b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java @@ -0,0 +1,493 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; +import static java.util.Arrays.asList; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.lmdbjava.DbiFlags.MDB_CREATE; +import static org.lmdbjava.DbiFlags.MDB_INTEGERKEY; +import static org.lmdbjava.Env.create; +import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; +import static org.lmdbjava.KeyRange.all; +import static org.lmdbjava.KeyRange.allBackward; +import static org.lmdbjava.KeyRange.atLeast; +import static org.lmdbjava.KeyRange.atLeastBackward; +import static org.lmdbjava.KeyRange.atMost; +import static org.lmdbjava.KeyRange.atMostBackward; +import static org.lmdbjava.KeyRange.closed; +import static org.lmdbjava.KeyRange.closedBackward; +import static org.lmdbjava.KeyRange.closedOpen; +import static org.lmdbjava.KeyRange.closedOpenBackward; +import static org.lmdbjava.KeyRange.greaterThan; +import static org.lmdbjava.KeyRange.greaterThanBackward; +import static org.lmdbjava.KeyRange.lessThan; +import static org.lmdbjava.KeyRange.lessThanBackward; +import static org.lmdbjava.KeyRange.open; +import static org.lmdbjava.KeyRange.openBackward; +import static org.lmdbjava.KeyRange.openClosed; +import static org.lmdbjava.KeyRange.openClosedBackward; +import static org.lmdbjava.PutFlags.MDB_NOOVERWRITE; +import static org.lmdbjava.TestUtils.DB_1; +import static org.lmdbjava.TestUtils.DB_2; +import static org.lmdbjava.TestUtils.DB_3; +import static org.lmdbjava.TestUtils.POSIX_MODE; +import static org.lmdbjava.TestUtils.bb; +import static org.lmdbjava.TestUtils.bbNative; +import static org.lmdbjava.TestUtils.getNativeInt; + +import com.google.common.primitives.UnsignedBytes; +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Deque; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.NoSuchElementException; +import org.hamcrest.Matchers; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.lmdbjava.CursorIterable.KeyVal; + +/** Test {@link CursorIterable} using {@link DbiFlags#MDB_INTEGERKEY} to ensure that + * comparators work with native order integer keys. */ +public final class CursorIterableIntegerKeyTest { + + @Rule public final TemporaryFolder tmp = new TemporaryFolder(); + private Dbi dbJavaComparator; + private Dbi dbLmdbComparator; + private Dbi dbCallbackComparator; + private List> dbs = new ArrayList<>(); + private Env env; + private Deque list; + + @After + public void after() { + env.close(); + } + + @Test + public void allBackwardTest() { + verify(allBackward(), 8, 6, 4, 2); + } + + @Test + public void allTest() { + verify(all(), 2, 4, 6, 8); + } + + @Test + public void atLeastBackwardTest() { + verify(atLeastBackward(bbNative(5)), 4, 2); + verify(atLeastBackward(bbNative(6)), 6, 4, 2); + verify(atLeastBackward(bbNative(9)), 8, 6, 4, 2); + } + + @Test + public void atLeastTest() { + verify(atLeast(bbNative(5)), 6, 8); + verify(atLeast(bbNative(6)), 6, 8); + } + + @Test + public void atMostBackwardTest() { + verify(atMostBackward(bbNative(5)), 8, 6); + verify(atMostBackward(bbNative(6)), 8, 6); + } + + @Test + public void atMostTest() { + verify(atMost(bbNative(5)), 2, 4); + verify(atMost(bbNative(6)), 2, 4, 6); + } + + @Before + public void before() throws IOException { + final File path = tmp.newFile(); + final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; + env = + create(bufferProxy) + .setMapSize(KIBIBYTES.toBytes(256)) + .setMaxReaders(1) + .setMaxDbs(3) + .open(path, POSIX_MODE, MDB_NOSUBDIR); + + // Use a java comparator for start/stop keys only + dbJavaComparator = env.openDbi(DB_1, + bufferProxy.getUnsignedComparator(), + MDB_CREATE, + MDB_INTEGERKEY); + // Use LMDB comparator for start/stop keys + dbLmdbComparator = env.openDbi(DB_2, MDB_CREATE, MDB_INTEGERKEY); + // Use a java comparator for start/stop keys and as a callback comparaotr + dbCallbackComparator = env.openDbi(DB_3, + bufferProxy.getUnsignedComparator(), + true, + MDB_CREATE, + MDB_INTEGERKEY); + + populateList(); + + populateDatabase(dbJavaComparator); + populateDatabase(dbLmdbComparator); + populateDatabase(dbCallbackComparator); + + dbs.add(dbJavaComparator); + dbs.add(dbLmdbComparator); + dbs.add(dbCallbackComparator); + } + + private void populateList() { + list = new LinkedList<>(); + list.addAll(asList(2, 3, 4, 5, 6, 7, 8, 9)); + } + + private void populateDatabase(final Dbi dbi) { + try (Txn txn = env.txnWrite()) { + final Cursor c = dbi.openCursor(txn); + c.put(bbNative(2), bb(3), MDB_NOOVERWRITE); + c.put(bbNative(4), bb(5)); + c.put(bbNative(6), bb(7)); + c.put(bbNative(8), bb(9)); + txn.commit(); + } + } + + @Test + public void closedBackwardTest() { + verify(closedBackward(bbNative(7), bbNative(3)), 6, 4); + verify(closedBackward(bbNative(6), bbNative(2)), 6, 4, 2); + verify(closedBackward(bbNative(9), bbNative(3)), 8, 6, 4); + } + + @Test + public void closedOpenBackwardTest() { + verify(closedOpenBackward(bbNative(8), bbNative(3)), 8, 6, 4); + verify(closedOpenBackward(bbNative(7), bbNative(2)), 6, 4); + verify(closedOpenBackward(bbNative(9), bbNative(3)), 8, 6, 4); + } + + @Test + public void closedOpenTest() { + verify(closedOpen(bbNative(3), bbNative(8)), 4, 6); + verify(closedOpen(bbNative(2), bbNative(6)), 2, 4); + } + + @Test + public void closedTest() { + verify(closed(bbNative(3), bbNative(7)), 4, 6); + verify(closed(bbNative(2), bbNative(6)), 2, 4, 6); + verify(closed(bbNative(1), bbNative(7)), 2, 4, 6); + } + + @Test + public void greaterThanBackwardTest() { + verify(greaterThanBackward(bbNative(6)), 4, 2); + verify(greaterThanBackward(bbNative(7)), 6, 4, 2); + verify(greaterThanBackward(bbNative(9)), 8, 6, 4, 2); + } + + @Test + public void greaterThanTest() { + verify(greaterThan(bbNative(4)), 6, 8); + verify(greaterThan(bbNative(3)), 4, 6, 8); + } + + @Test(expected = IllegalStateException.class) + public void iterableOnlyReturnedOnce() { + for (final Dbi db : dbs) { + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + c.iterator(); // ok + c.iterator(); // fails + } + } + } + + @Test + public void iterate() { + for (final Dbi db : dbs) { + populateList(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + + int cnt = 0; + for (final KeyVal kv : c) { + assertThat(getNativeInt(kv.key()), is(list.pollFirst())); + assertThat(kv.val().getInt(), is(list.pollFirst())); + } + } + } + } + + @Test(expected = IllegalStateException.class) + public void iteratorOnlyReturnedOnce() { + for (final Dbi db : dbs) { + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + c.iterator(); // ok + c.iterator(); // fails + } + } + } + + @Test + public void lessThanBackwardTest() { + verify(lessThanBackward(bbNative(5)), 8, 6); + verify(lessThanBackward(bbNative(2)), 8, 6, 4); + } + + @Test + public void lessThanTest() { + verify(lessThan(bbNative(5)), 2, 4); + verify(lessThan(bbNative(8)), 2, 4, 6); + } + + @Test(expected = NoSuchElementException.class) + public void nextThrowsNoSuchElementExceptionIfNoMoreElements() { + for (final Dbi db : dbs) { + populateList(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + final Iterator> i = c.iterator(); + while (i.hasNext()) { + final KeyVal kv = i.next(); + assertThat(TestUtils.getNativeInt(kv.key()), is(list.pollFirst())); + assertThat(kv.val().getInt(), is(list.pollFirst())); + } + assertThat(i.hasNext(), is(false)); + i.next(); + } + } + } + + @Test + public void openBackwardTest() { + verify(openBackward(bbNative(7), bbNative(2)), 6, 4); + verify(openBackward(bbNative(8), bbNative(1)), 6, 4, 2); + verify(openBackward(bbNative(9), bbNative(4)), 8, 6); + } + + @Test + public void openClosedBackwardTest() { + verify(openClosedBackward(bbNative(7), bbNative(2)), 6, 4, 2); + verify(openClosedBackward(bbNative(8), bbNative(4)), 6, 4); + verify(openClosedBackward(bbNative(9), bbNative(4)), 8, 6, 4); + } + + @Test + public void openClosedBackwardTestWithGuava() { + final Comparator guava = UnsignedBytes.lexicographicalComparator(); + final Comparator comparator = + (bb1, bb2) -> { + final byte[] array1 = new byte[bb1.remaining()]; + final byte[] array2 = new byte[bb2.remaining()]; + bb1.mark(); + bb2.mark(); + bb1.get(array1); + bb2.get(array2); + bb1.reset(); + bb2.reset(); + return guava.compare(array1, array2); + }; + final Dbi guavaDbi = env.openDbi(DB_1, comparator, MDB_CREATE); + populateDatabase(guavaDbi); + verify(openClosedBackward(bbNative(7), bbNative(2)), guavaDbi, 6, 4, 2); + verify(openClosedBackward(bbNative(8), bbNative(4)), guavaDbi, 6, 4); + } + + @Test + public void openClosedTest() { + verify(openClosed(bbNative(3), bbNative(8)), 4, 6, 8); + verify(openClosed(bbNative(2), bbNative(6)), 4, 6); + } + + @Test + public void openTest() { + verify(open(bbNative(3), bbNative(7)), 4, 6); + verify(open(bbNative(2), bbNative(8)), 4, 6); + } + + @Test + public void removeOddElements() { + for (final Dbi db : dbs) { + verify(db, all(), 2, 4, 6, 8); + int idx = -1; + try (Txn txn = env.txnWrite()) { + try (CursorIterable ci = db.iterate(txn)) { + final Iterator> c = ci.iterator(); + while (c.hasNext()) { + c.next(); + idx++; + if (idx % 2 == 0) { + c.remove(); + } + } + } + txn.commit(); + } + verify(db, all(), 4, 8); + } + } + + @Test(expected = Env.AlreadyClosedException.class) + public void nextWithClosedEnvTest() { + for (final Dbi db : dbs) { + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + + env.close(); + c.next(); + } + } + } + } + + @Test(expected = Env.AlreadyClosedException.class) + public void removeWithClosedEnvTest() { + for (final Dbi db : dbs) { + try (Txn txn = env.txnWrite()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + + final KeyVal keyVal = c.next(); + assertThat(keyVal, Matchers.notNullValue()); + + env.close(); + c.remove(); + } + } + } + } + + @Test(expected = Env.AlreadyClosedException.class) + public void hasNextWithClosedEnvTest() { + for (final Dbi db : dbs) { + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + + env.close(); + c.hasNext(); + } + } + } + } + + @Test(expected = Env.AlreadyClosedException.class) + public void forEachRemainingWithClosedEnvTest() { + for (final Dbi db : dbs) { + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + + env.close(); + c.forEachRemaining(keyVal -> {}); + } + } + } + } + + @Test + public void testSignedVsUnsigned() { + final ByteBuffer val1 = bbNative(1); + final ByteBuffer val2 = bbNative(2); + final ByteBuffer val110 = bbNative(110); + final ByteBuffer val111 = bbNative(111); + final ByteBuffer val150 = bbNative(150); + + final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; + final Comparator unsignedComparator = bufferProxy.getUnsignedComparator(); + final Comparator signedComparator = bufferProxy.getSignedComparator(); + + // Compare the same + assertThat( + unsignedComparator.compare(val1, val2), Matchers.is(signedComparator.compare(val1, val2))); + + // Compare differently + assertThat( + unsignedComparator.compare(val110, val150), + Matchers.not(signedComparator.compare(val110, val150))); + + // Compare differently + assertThat( + unsignedComparator.compare(val111, val150), + Matchers.not(signedComparator.compare(val111, val150))); + + // This will fail if the db is using a signed comparator for the start/stop keys + for (final Dbi db : dbs) { + db.put(val110, val110); + db.put(val150, val150); + + final ByteBuffer startKeyBuf = val111; + KeyRange keyRange = KeyRange.atLeastBackward(startKeyBuf); + + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn, keyRange)) { + for (final KeyVal kv : c) { + final int key = getNativeInt(kv.key()); + final int val = kv.val().getInt(); + // System.out.println("key: " + key + " val: " + val); + assertThat(key, is(110)); + break; + } + } + } + } + + private void verify(final KeyRange range, final int... expected) { + // Verify using all comparator types + for (final Dbi db : dbs) { + verify(range, db, expected); + } + } + + private void verify( + final Dbi dbi, final KeyRange range, final int... expected) { + verify(range, dbi, expected); + } + + private void verify( + final KeyRange range, final Dbi dbi, final int... expected) { + + final List results = new ArrayList<>(); + + try (Txn txn = env.txnRead(); + CursorIterable c = dbi.iterate(txn, range)) { + for (final KeyVal kv : c) { + final int key = kv.key().order(ByteOrder.nativeOrder()).getInt(); + final int val = kv.val().getInt(); + results.add(key); + assertThat(val, is(key + 1)); + } + } + + assertThat(results, hasSize(expected.length)); + for (int idx = 0; idx < results.size(); idx++) { + assertThat(results.get(idx), is(expected[idx])); + } + } +} diff --git a/src/test/java/org/lmdbjava/CursorIterablePerfTest.java b/src/test/java/org/lmdbjava/CursorIterablePerfTest.java index 19e5a9b9..257ee705 100644 --- a/src/test/java/org/lmdbjava/CursorIterablePerfTest.java +++ b/src/test/java/org/lmdbjava/CursorIterablePerfTest.java @@ -73,7 +73,7 @@ private void populateList() { } private void populateDatabases(final boolean randomOrder) { - System.out.println("Clear then populate databases"); + System.out.println("Clear then populate databases (randomOrder=" + randomOrder + ")"); final List data; if (randomOrder) { diff --git a/src/test/java/org/lmdbjava/TestUtils.java b/src/test/java/org/lmdbjava/TestUtils.java index f3d3974b..ff84946f 100644 --- a/src/test/java/org/lmdbjava/TestUtils.java +++ b/src/test/java/org/lmdbjava/TestUtils.java @@ -23,6 +23,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import org.agrona.MutableDirectBuffer; import org.agrona.concurrent.UnsafeBuffer; @@ -49,6 +50,20 @@ static ByteBuffer bb(final int value) { return bb; } + static ByteBuffer bbNative(final int value) { + final ByteBuffer bb = allocateDirect(Long.BYTES) + .order(ByteOrder.nativeOrder()); + bb.putInt(value).flip(); + return bb; + } + + static int getNativeInt(final ByteBuffer bb) { + final int val = bb.order(ByteOrder.nativeOrder()) + .getInt(); + bb.rewind(); + return val; + } + static void invokePrivateConstructor(final Class clazz) { try { final Constructor c = clazz.getDeclaredConstructor(); From 620a89fc4bdc292b3f5cace5631a5af6afd6f11e Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Thu, 5 Jun 2025 10:03:12 +0100 Subject: [PATCH 07/90] gh-249 Remove MaskedFlag.isPropagatedToLmdb, add DbiBuilder WIP --- src/main/java/org/lmdbjava/Cursor.java | 8 +- src/main/java/org/lmdbjava/Dbi.java | 6 +- src/main/java/org/lmdbjava/DbiBuilder.java | 335 +++++++++++++++++++++ src/main/java/org/lmdbjava/DbiFlags.java | 3 + src/main/java/org/lmdbjava/Env.java | 27 +- src/main/java/org/lmdbjava/MaskedFlag.java | 72 +---- src/main/java/org/lmdbjava/Txn.java | 2 +- 7 files changed, 382 insertions(+), 71 deletions(-) create mode 100644 src/main/java/org/lmdbjava/DbiBuilder.java diff --git a/src/main/java/org/lmdbjava/Cursor.java b/src/main/java/org/lmdbjava/Cursor.java index 9070cff6..69f84c88 100644 --- a/src/main/java/org/lmdbjava/Cursor.java +++ b/src/main/java/org/lmdbjava/Cursor.java @@ -112,7 +112,7 @@ public void delete(final PutFlags... f) { txn.checkReady(); txn.checkWritesAllowed(); } - final int flags = mask(true, f); + final int flags = mask(f); checkRc(LIB.mdb_cursor_del(ptrCursor, flags)); } @@ -249,7 +249,7 @@ public boolean put(final T key, final T val, final PutFlags... op) { } kv.keyIn(key); kv.valIn(val); - final int mask = mask(true, op); + final int mask = mask(op); final int rc = LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), mask); if (rc == MDB_KEYEXIST) { if (isSet(mask, MDB_NOOVERWRITE)) { @@ -287,7 +287,7 @@ public void putMultiple(final T key, final T val, final int elements, final PutF txn.checkReady(); txn.checkWritesAllowed(); } - final int mask = mask(true, op); + final int mask = mask(op); if (SHOULD_CHECK && !isSet(mask, MDB_MULTIPLE)) { throw new IllegalArgumentException("Must set " + MDB_MULTIPLE + " flag"); } @@ -346,7 +346,7 @@ public T reserve(final T key, final int size, final PutFlags... op) { } kv.keyIn(key); kv.valIn(size); - final int flags = mask(true, op) | MDB_RESERVE.getMask(); + final int flags = mask(op) | MDB_RESERVE.getMask(); checkRc(LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), flags)); kv.valOut(); ReferenceUtil.reachabilityFence0(key); diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index c622462b..8560e34c 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -72,7 +72,7 @@ public final class Dbi { this.name = name == null ? null : Arrays.copyOf(name, name.length); this.proxy = proxy; this.comparator = comparator; - final int flagsMask = mask(true, flags); + final int flagsMask = mask(flags); final Pointer dbiPtr = allocateDirect(RUNTIME, ADDRESS); checkRc(LIB.mdb_dbi_open(txn.pointer(), name, flagsMask, dbiPtr)); ptr = dbiPtr.getPointer(0); @@ -371,7 +371,7 @@ public boolean put(final Txn txn, final T key, final T val, final PutFlags... } txn.kv().keyIn(key); txn.kv().valIn(val); - final int mask = mask(true, flags); + final int mask = mask(flags); final int rc = LIB.mdb_put(txn.pointer(), ptr, txn.kv().pointerKey(), txn.kv().pointerVal(), mask); if (rc == MDB_KEYEXIST) { @@ -413,7 +413,7 @@ public T reserve(final Txn txn, final T key, final int size, final PutFlags.. } txn.kv().keyIn(key); txn.kv().valIn(size); - final int flags = mask(true, op) | MDB_RESERVE.getMask(); + final int flags = mask(op) | MDB_RESERVE.getMask(); checkRc(LIB.mdb_put(txn.pointer(), ptr, txn.kv().pointerKey(), txn.kv().pointerVal(), flags)); txn.kv().valOut(); // marked as in,out in LMDB C docs ReferenceUtil.reachabilityFence0(key); diff --git a/src/main/java/org/lmdbjava/DbiBuilder.java b/src/main/java/org/lmdbjava/DbiBuilder.java new file mode 100644 index 00000000..a33ca746 --- /dev/null +++ b/src/main/java/org/lmdbjava/DbiBuilder.java @@ -0,0 +1,335 @@ +package org.lmdbjava; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.Objects; +import java.util.Set; + +/** + * Staged builder for building a {@link Dbi} + * + * @param buffer type + */ +public class DbiBuilder { + + private final Env env; + private final BufferProxy proxy; + private final boolean readOnly; + private byte[] name; + + DbiBuilder(final Env env, + final BufferProxy proxy, + final boolean readOnly) { + this.env = Objects.requireNonNull(env); + this.proxy = Objects.requireNonNull(proxy); + this.readOnly = readOnly; + } + + /** + *

+ * Create the {@link Dbi} with the passed name. + *

+ *

+ * The name will be converted into bytes using {@link StandardCharsets#UTF_8}. + *

+ */ + public RequireComparator withDbName(final String name) { + // Null name is allowed so no null check + final byte[] nameBytes = name == null + ? null + : name.getBytes(StandardCharsets.UTF_8); + return withDbName(nameBytes); + } + + /** + * Create the {@link Dbi} with the passed name in byte[] form. + */ + public RequireComparator withDbName(final byte[] name) { + // Null name is allowed so no null check + this.name = name; + return new RequireComparator<>(this); + } + + /** + *

+ * Create the {@link Dbi} without a name. + *

+ *

+ * Equivalent to passing null to + * {@link DbiBuilder#withDbName(String)} or {@link DbiBuilder#withDbName(byte[])}. + *

+ */ + public RequireComparator withoutDbName() { + return withDbName((byte[]) null); + } + + + // -------------------------------------------------------------------------------- + + + /** + * Intermediate builder stage for constructing a {@link Dbi}. + * + * @param buffer type + */ + public static class RequireComparator { + + private final DbiBuilder dbiBuilder; + + private Comparator comparator; + private boolean useNativeCallback; + + private RequireComparator(final DbiBuilder dbiBuilder) { + this.dbiBuilder = dbiBuilder; + } + + /** + *

+ * {@link CursorIterable} will call down to LMDB's {@code mdb_cmp} method when + * comparing entries to start/stop keys. This ensures LmdbJava is comparing start/stop + * keys using the same comparator that is used for insert order into the db. + *

+ *

+ * This option may be slightly less performant than when using + * {@link RequireComparator#withDefaultJavaComparator()} as it need to call down + * to LMDB to perform the comparisons, however it guarantees that {@link CursorIterable} + * key comparison matches LMDB key comparison. + *

+ *

+ * If you do not intend to use {@link CursorIterable} then it doesn't matter whether + * you choose {@link RequireComparator#withNativeComparator()}, + * {@link RequireComparator#withDefaultJavaComparator()} or + * {@link RequireComparator#withIteratorComparator(Comparator)} as these comparators will + * never be used. + *

+ * + * @return this builder instance. + */ + public FinalStage withNativeComparator() { + this.comparator = null; + this.useNativeCallback = false; + return new FinalStage<>(this); + } + + /** + *

+ * {@link CursorIterable} will make use of the default Java-side comparators when + * comparing entries to start/stop keys. + *

+ *

+ * This option may be slightly more performant than when using + * {@link RequireComparator#withNativeComparator()} but it relies on the default comparator + * in LmdbJava behaving identically to the comparator in LMDB. + *

+ *

+ * If you do not intend to use {@link CursorIterable} then it doesn't matter whether + * you choose {@link RequireComparator#withNativeComparator()}, + * {@link RequireComparator#withDefaultJavaComparator()} or + * {@link RequireComparator#withIteratorComparator(Comparator)} as these comparators will + * never be used. + *

+ * + * @return this builder instance. + */ + public FinalStage withDefaultJavaComparator() { + this.comparator = dbiBuilder.proxy.getUnsignedComparator(); + this.useNativeCallback = false; + return new FinalStage<>(this); + } + + /** + * Provide a java-side {@link Comparator} that LMDB will call back to in order to + * manage database insertion/iteration order. It will also be used for {@link CursorIterable} + * start/stop key comparisons. + *

+ * Due to calling back to java, this will be less performant than using LMDB's + * default comparator, but allows for total control over the order in which entries + * are stored in the database. + *

+ * + * @param comparator for all key comparison operations. + * @return this builder instance. + */ + public FinalStage withCallbackComparator(final Comparator comparator) { + this.comparator = Objects.requireNonNull(comparator); + this.useNativeCallback = true; + return new FinalStage<>(this); + } + + /** + *

+ * {@link CursorIterable} will make use of the passed comparator for + * comparing entries to start/stop keys. It has NO bearing on the insert/iteration + * order of the db. + *

+ *

+ * WARNING: Only call this method if you fully understand the implications + * of using a comparator for the {@link CursorIterable} start/stop keys that behaves + * differently to the comparator in LMDB that controls the insert/iteration order. + *

+ *

+ * This option may be slightly less performant than when using + * {@link RequireComparator#withDefaultJavaComparator()} as it need to call down + * to LMDB to perform the comparisons, however it guarantees that {@link CursorIterable} + * key comparison matches LMDB key comparison. + *

+ *

+ * If you do not intend to use {@link CursorIterable} then it doesn't matter whether + * you choose {@link RequireComparator#withNativeComparator()}, + * {@link RequireComparator#withDefaultJavaComparator()} or + * {@link RequireComparator#withIteratorComparator(Comparator)} as these comparators will + * never be used. + *

+ * + * @param comparator The comparator to use with {@link CursorIterable}. + * @return this builder instance. + */ + public FinalStage withIteratorComparator(final Comparator comparator) { + this.comparator = Objects.requireNonNull(comparator); + this.useNativeCallback = false; + return new FinalStage<>(this); + } + } + + + // -------------------------------------------------------------------------------- + + + /** + * Final stage builder for constructing a {@link Dbi}. + * + * @param buffer type + */ + public static class FinalStage { + + private final RequireComparator requireComparator; + private Set dbiFlags = null; + private Txn txn = null; + + private FinalStage(RequireComparator requireComparator) { + this.requireComparator = requireComparator; + } + + private void initDbiFlags() { + if (dbiFlags == null) { + dbiFlags = EnumSet.noneOf(DbiFlags.class); + } + } + + /** + *

+ * Apply all the dbi flags supplied in dbiFlags. + *

+ *

+ * Replaces any flags applies in previous calls to + * {@link FinalStage#withDbiFlags(Collection)}, {@link FinalStage#withDbiFlags(DbiFlags...)} + * or {@link FinalStage#addDbiFlag(DbiFlags)}. + *

+ * + * @param dbiFlags to open the database with. + */ + public FinalStage withDbiFlags(final Collection dbiFlags) { + initDbiFlags(); + if (dbiFlags != null) { + this.dbiFlags.addAll(dbiFlags); + } + return this; + } + + /** + *

+ * Apply all the dbi flags supplied in dbiFlags. + *

+ *

+ * Replaces any flags applies in previous calls to + * {@link FinalStage#withDbiFlags(Collection)}, {@link FinalStage#withDbiFlags(DbiFlags...)} + * or {@link FinalStage#addDbiFlag(DbiFlags)}. + *

+ * + * @param dbiFlags to open the database with. + */ + public FinalStage withDbiFlags(final DbiFlags... dbiFlags) { + initDbiFlags(); + if (dbiFlags != null) { + Arrays.stream(dbiFlags) + .filter(Objects::nonNull) + .forEach(this.dbiFlags::add); + } + return this; + } + + /** + * Adds dbiFlag to those flags already added to this builder. + * + * @param dbiFlag to open the database with. + * @return this builder instance. + */ + public FinalStage addDbiFlag(final DbiFlags dbiFlag) { + initDbiFlags(); + if (dbiFlags != null) { + this.dbiFlags.add(dbiFlag); + } + return this; + } + + /** + * Use the supplied transaction to open the {@link Dbi}. + *

+ * The caller must commit the transaction after calling {@link FinalStage#open()} + * in order to retain the Dbi in the Env. + *

+ * + * @param txn transaction to use (required; not closed) + * @return this builder instance. + */ + public FinalStage withTxn(final Txn txn) { + this.txn = Objects.requireNonNull(txn); + return this; + } + + /** + * Construct and open the {@link Dbi}. + *

+ * If a {@link Txn} was supplied to the builder, it should be committed upon return from + * this method. + *

+ * + * @return A newly constructed and opened {@link Dbi}. + */ + public Dbi open() { + final DbiBuilder dbiBuilder = requireComparator.dbiBuilder; + if (txn == null) { + try (final Txn txn = getTxn(dbiBuilder)) { + return open(txn, dbiBuilder); + } + } else { + return open(txn, dbiBuilder); + } + } + + private Txn getTxn(final DbiBuilder dbiBuilder) { + return dbiBuilder.readOnly + ? dbiBuilder.env.txnRead() + : dbiBuilder.env.txnWrite(); + } + + private Dbi open(final Txn txn, + final DbiBuilder dbiBuilder) { + final DbiFlags[] dbiFlagsArr = dbiFlags != null && !dbiFlags.isEmpty() + ? this.dbiFlags.toArray(new DbiFlags[0]) + : null; + + return new Dbi<>( + dbiBuilder.env, + txn, + dbiBuilder.name, + requireComparator.comparator, + requireComparator.useNativeCallback, + dbiBuilder.proxy, + dbiFlagsArr); + } + } +} diff --git a/src/main/java/org/lmdbjava/DbiFlags.java b/src/main/java/org/lmdbjava/DbiFlags.java index 2f5eadf6..af6eaeaa 100644 --- a/src/main/java/org/lmdbjava/DbiFlags.java +++ b/src/main/java/org/lmdbjava/DbiFlags.java @@ -31,6 +31,9 @@ public enum DbiFlags implements MaskedFlag { *

Duplicate keys may be used in the database. Or, from another perspective, keys may have * multiple data items, stored in sorted order. By default keys must be unique and may have only a * single data item. + *

+ * + *

*/ MDB_DUPSORT(0x04), /** diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index 2e4822b7..d7c7b269 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -145,7 +145,7 @@ public void close() { public void copy(final File path, final CopyFlags... flags) { requireNonNull(path); validatePath(path); - final int flagsMask = mask(true, flags); + final int flagsMask = mask(flags); checkRc(LIB.mdb_env_copy2(ptr, path.getAbsolutePath(), flagsMask)); } @@ -241,6 +241,15 @@ public boolean isReadOnly() { return readOnly; } + /** + * Open (and optionally creates, if {@link DbiFlags#MDB_CREATE} is set) + * a {@link Dbi} using a builder. + * @return A new builder instance for creating/opening a {@link Dbi}. + */ + public DbiBuilder buildDbi() { + return new DbiBuilder<>(this, proxy, readOnly); + } + /** * Convenience method that opens a {@link Dbi} with a UTF-8 database name and default {@link * Comparator} that is not invoked from native code. @@ -248,7 +257,9 @@ public boolean isReadOnly() { * @param name name of the database (or null if no name is required) * @param flags to open the database with * @return a database that is ready to use + * @deprecated Instead use {@link Env#buildDbi()} */ + @Deprecated() public Dbi openDbi(final String name, final DbiFlags... flags) { final byte[] nameBytes = name == null ? null : name.getBytes(UTF_8); return openDbi(nameBytes, null, false, flags); @@ -268,7 +279,9 @@ public Dbi openDbi(final String name, final DbiFlags... flags) { * comparator will be used. * @param flags to open the database with * @return a database that is ready to use + * @deprecated Instead use {@link Env#buildDbi()} */ + @Deprecated() public Dbi openDbi( final String name, final Comparator comparator, final DbiFlags... flags) { final byte[] nameBytes = name == null ? null : name.getBytes(UTF_8); @@ -288,7 +301,9 @@ public Dbi openDbi( * @param nativeCb whether LMDB native code calls back to the Java comparator * @param flags to open the database with * @return a database that is ready to use + * @deprecated Instead use {@link Env#buildDbi()} */ + @Deprecated() public Dbi openDbi( final String name, final Comparator comparator, @@ -305,7 +320,9 @@ public Dbi openDbi( * @param name name of the database (or null if no name is required) * @param flags to open the database with * @return a database that is ready to use + * @deprecated Instead use {@link Env#buildDbi()} */ + @Deprecated() public Dbi openDbi(final byte[] name, final DbiFlags... flags) { return openDbi(name, null, false, flags); } @@ -318,7 +335,9 @@ public Dbi openDbi(final byte[] name, final DbiFlags... flags) { * @param comparator custom comparator callback (or null to use LMDB default) * @param flags to open the database with * @return a database that is ready to use + * @deprecated Instead use {@link Env#buildDbi()} */ + @Deprecated() public Dbi openDbi( final byte[] name, final Comparator comparator, final DbiFlags... flags) { return openDbi(name, comparator, false, flags); @@ -336,7 +355,9 @@ public Dbi openDbi( * @param nativeCb whether native code calls back to the Java comparator * @param flags to open the database with * @return a database that is ready to use + * @deprecated Instead use {@link Env#buildDbi()} */ + @Deprecated() public Dbi openDbi( final byte[] name, final Comparator comparator, @@ -375,7 +396,9 @@ public Dbi openDbi( * @param nativeCb whether native code should call back to the comparator * @param flags to open the database with * @return a database that is ready to use + * @deprecated Instead use {@link Env#buildDbi()} */ + @Deprecated() public Dbi openDbi( final Txn txn, final byte[] name, @@ -557,7 +580,7 @@ public Env open(final File path, final int mode, final EnvFlags... flags) { checkRc(LIB.mdb_env_set_mapsize(ptr, mapSize)); checkRc(LIB.mdb_env_set_maxdbs(ptr, maxDbs)); checkRc(LIB.mdb_env_set_maxreaders(ptr, maxReaders)); - final int flagsMask = mask(true, flags); + final int flagsMask = mask(flags); final boolean readOnly = isSet(flagsMask, MDB_RDONLY_ENV); final boolean noSubDir = isSet(flagsMask, MDB_NOSUBDIR); checkRc(LIB.mdb_env_open(ptr, path.getAbsolutePath(), flagsMask, mode)); diff --git a/src/main/java/org/lmdbjava/MaskedFlag.java b/src/main/java/org/lmdbjava/MaskedFlag.java index 00556ecb..4dc47b20 100644 --- a/src/main/java/org/lmdbjava/MaskedFlag.java +++ b/src/main/java/org/lmdbjava/MaskedFlag.java @@ -17,11 +17,6 @@ import static java.util.Objects.requireNonNull; -import java.util.Arrays; -import java.util.Objects; -import java.util.function.Predicate; -import java.util.stream.Stream; - /** Indicates an enum that can provide integers for each of its values. */ public interface MaskedFlag { @@ -32,15 +27,6 @@ public interface MaskedFlag { */ int getMask(); - /** - * Indicates if the flag must be propagated to the underlying C code of LMDB or not. - * - * @return the boolean value indicating the propagation - */ - default boolean isPropagatedToLmdb() { - return true; - } - /** * Fetch the integer mask for all presented flags. * @@ -50,54 +36,18 @@ default boolean isPropagatedToLmdb() { */ @SafeVarargs static int mask(final M... flags) { - return mask(false, flags); - } - - /** - * Fetch the integer mask for all presented flags. - * - * @param flag type - * @param flags to mask (null or empty returns zero) - * @return the integer mask for use in C - */ - static int mask(final Stream flags) { - return mask(false, flags); - } - - /** - * Fetch the integer mask for the presented flags. - * - * @param flag type - * @param onlyPropagatedToLmdb if to include only the flags which are also propagate to the C code - * or all of them - * @param flags to mask (null or empty returns zero) - * @return the integer mask for use in C - */ - @SafeVarargs - static int mask(final boolean onlyPropagatedToLmdb, final M... flags) { - return flags == null ? 0 : mask(onlyPropagatedToLmdb, Arrays.stream(flags)); - } - - /** - * Fetch the integer mask for all presented flags. - * - * @param flag type - * @param onlyPropagatedToLmdb if to include only the flags which are also propagate to the C code - * or all of them - * @param flags to mask - * @return the integer mask for use in C - */ - static int mask( - final boolean onlyPropagatedToLmdb, final Stream flags) { - final Predicate filter = onlyPropagatedToLmdb ? MaskedFlag::isPropagatedToLmdb : f -> true; + if (flags == null || flags.length == 0) { + return 0; + } - return flags == null - ? 0 - : flags - .filter(Objects::nonNull) - .filter(filter) - .map(M::getMask) - .reduce(0, (f1, f2) -> f1 | f2); + int result = 0; + for (MaskedFlag flag : flags) { + if (flag == null) { + continue; + } + result |= flag.getMask(); + } + return result; } /** diff --git a/src/main/java/org/lmdbjava/Txn.java b/src/main/java/org/lmdbjava/Txn.java index 05e8ce06..1d5d4860 100644 --- a/src/main/java/org/lmdbjava/Txn.java +++ b/src/main/java/org/lmdbjava/Txn.java @@ -49,7 +49,7 @@ public final class Txn implements AutoCloseable { Txn(final Env env, final Txn parent, final BufferProxy proxy, final TxnFlags... flags) { this.proxy = proxy; this.keyVal = proxy.keyVal(); - final int flagsMask = mask(true, flags); + final int flagsMask = mask(flags); this.readOnly = isSet(flagsMask, MDB_RDONLY_TXN); if (env.isReadOnly() && !this.readOnly) { throw new EnvIsReadOnly(); From bfbf223031191e0bb13c7c32a58930c85e880f5c Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Mon, 27 Oct 2025 15:23:46 +0000 Subject: [PATCH 08/90] Add FlagSet, DbiFlagSet, PutFlagSet Refactor DbiBuilder and Dbi ctor to use DbiFlagSet. --- src/main/java/org/lmdbjava/Dbi.java | 29 ++- src/main/java/org/lmdbjava/DbiBuilder.java | 42 ++-- src/main/java/org/lmdbjava/DbiFlagSet.java | 32 +++ src/main/java/org/lmdbjava/Env.java | 2 +- src/main/java/org/lmdbjava/FlagSet.java | 192 ++++++++++++++++++ src/main/java/org/lmdbjava/MaskedFlag.java | 38 +++- src/main/java/org/lmdbjava/PutFlagSet.java | 32 +++ .../java/org/lmdbjava/DbiFlagSetTest.java | 101 +++++++++ .../java/org/lmdbjava/PutFlagSetTest.java | 101 +++++++++ 9 files changed, 531 insertions(+), 38 deletions(-) create mode 100644 src/main/java/org/lmdbjava/DbiFlagSet.java create mode 100644 src/main/java/org/lmdbjava/FlagSet.java create mode 100644 src/main/java/org/lmdbjava/PutFlagSet.java create mode 100644 src/test/java/org/lmdbjava/DbiFlagSetTest.java create mode 100644 src/test/java/org/lmdbjava/PutFlagSetTest.java diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index ba20c1a4..17fa4c46 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -31,6 +31,7 @@ import static org.lmdbjava.PutFlags.MDB_RESERVE; import static org.lmdbjava.ResultCodeMapper.checkRc; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -55,6 +56,7 @@ public final class Dbi { private final byte[] name; private final Pointer ptr; private final BufferProxy proxy; + private final DbiFlagSet dbiFlagSet; Dbi( final Env env, @@ -63,7 +65,7 @@ public final class Dbi { final Comparator comparator, final boolean nativeCb, final BufferProxy proxy, - final DbiFlags... flags) { + final DbiFlagSet dbiFlagSet) { if (SHOULD_CHECK) { requireNonNull(txn); txn.checkReady(); @@ -72,9 +74,9 @@ public final class Dbi { this.name = name == null ? null : Arrays.copyOf(name, name.length); this.proxy = proxy; this.comparator = comparator; - final int flagsMask = mask(flags); + this.dbiFlagSet = dbiFlagSet; final Pointer dbiPtr = allocateDirect(RUNTIME, ADDRESS); - checkRc(LIB.mdb_dbi_open(txn.pointer(), name, flagsMask, dbiPtr)); + checkRc(LIB.mdb_dbi_open(txn.pointer(), name, dbiFlagSet.getMask(), dbiPtr)); ptr = dbiPtr.getPointer(0); if (nativeCb) { requireNonNull(comparator, "comparator cannot be null if nativeCb is set"); @@ -291,6 +293,7 @@ public CursorIterable iterate(final Txn txn, final KeyRange range) { * @return the list of flags this Dbi was created with */ public List listFlags(final Txn txn) { + // TODO we could just return what is in dbiFlagSet, rather than hitting LMDB. if (SHOULD_CHECK) { env.checkNotClosed(); } @@ -457,6 +460,26 @@ private void clean() { cleaned = true; } + private String getNameAsString() { + if (name == null) { + return ""; + } else { + try { + return new String(name, StandardCharsets.UTF_8); + } catch (Exception e) { + return "?"; + } + } + } + + @Override + public String toString() { + return "Dbi{" + + "name=" + getNameAsString() + + ", dbiFlagSet=" + dbiFlagSet + + '}'; + } + /** The specified DBI was changed unexpectedly. */ public static final class BadDbiException extends LmdbNativeException { diff --git a/src/main/java/org/lmdbjava/DbiBuilder.java b/src/main/java/org/lmdbjava/DbiBuilder.java index 5deb2e38..1803d142 100644 --- a/src/main/java/org/lmdbjava/DbiBuilder.java +++ b/src/main/java/org/lmdbjava/DbiBuilder.java @@ -19,9 +19,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Comparator; -import java.util.EnumSet; import java.util.Objects; -import java.util.Set; /** * Staged builder for building a {@link Dbi} @@ -224,19 +222,13 @@ public DbiBuilderStage3 withIteratorComparator(final Comparator comparator public static class DbiBuilderStage3 { private final DbiBuilderStage2 dbiBuilderStage2; - private Set dbiFlags = null; + private final FlagSet.Builder flagSetBuilder = DbiFlagSet.builder(); private Txn txn = null; private DbiBuilderStage3(DbiBuilderStage2 dbiBuilderStage2) { this.dbiBuilderStage2 = dbiBuilderStage2; } - private void initDbiFlags() { - if (dbiFlags == null) { - dbiFlags = EnumSet.noneOf(DbiFlags.class); - } - } - /** *

* Apply all the dbi flags supplied in dbiFlags. @@ -244,15 +236,14 @@ private void initDbiFlags() { *

* Replaces any flags applies in previous calls to * {@link DbiBuilderStage3#withDbiFlags(Collection)}, {@link DbiBuilderStage3#withDbiFlags(DbiFlags...)} - * or {@link DbiBuilderStage3#addDbiFlag(DbiFlags)}. + * or {@link DbiBuilderStage3#setDbiFlag(DbiFlags)}. *

* * @param dbiFlags to open the database with. */ public DbiBuilderStage3 withDbiFlags(final Collection dbiFlags) { - initDbiFlags(); if (dbiFlags != null) { - this.dbiFlags.stream() + dbiFlags.stream() .filter(Objects::nonNull) .forEach(dbiFlags::add); } @@ -265,36 +256,35 @@ public DbiBuilderStage3 withDbiFlags(final Collection dbiFlags) { *

*

* Replaces any flags applies in previous calls to - * {@link DbiBuilderStage3#withDbiFlags(Collection)}, {@link DbiBuilderStage3#withDbiFlags(DbiFlags...)} - * or {@link DbiBuilderStage3#addDbiFlag(DbiFlags)}. + * {@link DbiBuilderStage3#withDbiFlags(Collection)}, + * {@link DbiBuilderStage3#withDbiFlags(DbiFlags...)} + * or {@link DbiBuilderStage3#setDbiFlag(DbiFlags)}. *

* * @param dbiFlags to open the database with. * A null array is a no-op. Null items are ignored. */ public DbiBuilderStage3 withDbiFlags(final DbiFlags... dbiFlags) { - initDbiFlags(); + flagSetBuilder.clear(); if (dbiFlags != null) { Arrays.stream(dbiFlags) .filter(Objects::nonNull) - .forEach(this.dbiFlags::add); + .forEach(this.flagSetBuilder::setFlag); } return this; } /** * Adds dbiFlag to those flags already added to this builder by - * {@link DbiBuilderStage3#withDbiFlags(DbiFlags...)}, {@link DbiBuilderStage3#withDbiFlags(Collection)} - * or {@link DbiBuilderStage3#addDbiFlag(DbiFlags)}. + * {@link DbiBuilderStage3#withDbiFlags(DbiFlags...)}, + * {@link DbiBuilderStage3#withDbiFlags(Collection)} + * or {@link DbiBuilderStage3#setDbiFlag(DbiFlags)}. * * @param dbiFlag to open the database with. A null value is a no-op. * @return this builder instance. */ - public DbiBuilderStage3 addDbiFlag(final DbiFlags dbiFlag) { - initDbiFlags(); - if (dbiFlags != null) { - this.dbiFlags.add(dbiFlag); - } + public DbiBuilderStage3 setDbiFlag(final DbiFlags dbiFlag) { + this.flagSetBuilder.setFlag(dbiFlag); return this; } @@ -341,9 +331,7 @@ private Txn getTxn(final DbiBuilder dbiBuilder) { private Dbi open(final Txn txn, final DbiBuilder dbiBuilder) { - final DbiFlags[] dbiFlagsArr = dbiFlags != null && !dbiFlags.isEmpty() - ? this.dbiFlags.toArray(new DbiFlags[0]) - : null; + final DbiFlagSet dbiFlagSet = flagSetBuilder.build(); return new Dbi<>( dbiBuilder.env, @@ -352,7 +340,7 @@ private Dbi open(final Txn txn, dbiBuilderStage2.comparator, dbiBuilderStage2.useNativeCallback, dbiBuilder.proxy, - dbiFlagsArr); + dbiFlagSet); } } } diff --git a/src/main/java/org/lmdbjava/DbiFlagSet.java b/src/main/java/org/lmdbjava/DbiFlagSet.java new file mode 100644 index 00000000..2c6c5ac7 --- /dev/null +++ b/src/main/java/org/lmdbjava/DbiFlagSet.java @@ -0,0 +1,32 @@ +package org.lmdbjava; + +import java.util.EnumSet; +import java.util.Objects; + +public class DbiFlagSet extends FlagSet { + + public static final DbiFlagSet EMPTY = new DbiFlagSet(EnumSet.noneOf(DbiFlags.class)); + + private DbiFlagSet(final EnumSet flags) { + super(flags); + } + + public static DbiFlagSet empty() { + return EMPTY; + } + + public static DbiFlagSet of(final DbiFlags putFlag) { + Objects.requireNonNull(putFlag); + return new DbiFlagSet(EnumSet.of(putFlag)); + } + + public static DbiFlagSet of(final DbiFlags... DbiFlags) { + return builder() + .withFlags(DbiFlags) + .build(); + } + + public static Builder builder() { + return new Builder<>(DbiFlags.class, DbiFlagSet::new); + } +} diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index d7c7b269..5de0a7fd 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -405,7 +405,7 @@ public Dbi openDbi( final Comparator comparator, final boolean nativeCb, final DbiFlags... flags) { - return new Dbi<>(this, txn, name, comparator, nativeCb, proxy, flags); + return new Dbi<>(this, txn, name, comparator, nativeCb, proxy, DbiFlagSet.of(flags)); } /** diff --git a/src/main/java/org/lmdbjava/FlagSet.java b/src/main/java/org/lmdbjava/FlagSet.java new file mode 100644 index 00000000..21132f8c --- /dev/null +++ b/src/main/java/org/lmdbjava/FlagSet.java @@ -0,0 +1,192 @@ +package org.lmdbjava; + +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumSet; +import java.util.Iterator; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Encapsulates an immutable set of flags and the associated bit mask for the flags in the set. + * + * @param + */ +public abstract class FlagSet & MaskedFlag> implements Iterable { + + private final Set flags; + private final int mask; + + protected FlagSet(final EnumSet flags) { + Objects.requireNonNull(flags); + this.mask = MaskedFlag.mask(flags); + this.flags = Collections.unmodifiableSet(Objects.requireNonNull(flags)); + } + + /** + * @return THe combined bit mask for all flags in the set. + */ + int getMask() { + return mask; + } + + /** + * @return All flags in the set. + */ + public Set getFlags() { + return flags; + } + + /** + * @return True if flag has been set, i.e. is contained in this set. + */ + public boolean isSet(final T flag) { + return flag != null + && flags.contains(flag); + } + + /** + * @return The number of flags in this set. + */ + public int size() { + return flags.size(); + } + + /** + * @return True if this set is empty. + */ + public boolean isEmpty() { + return flags.isEmpty(); + } + + /** + * @return The {@link Iterator} for this set. + */ + @Override + public Iterator iterator() { + return flags.iterator(); + } + + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (object == null || getClass() != object.getClass()) return false; + FlagSet flagSet = (FlagSet) object; + return mask == flagSet.mask && Objects.equals(flags, flagSet.flags); + } + + @Override + public int hashCode() { + return Objects.hash(flags, mask); + } + + @Override + public String toString() { + final String flagsStr = flags.stream() + .sorted(Comparator.comparing(MaskedFlag::getMask)) + .map(MaskedFlag::name) + .collect(Collectors.joining(", ")); + return "FlagSet{" + + "flags=[" + flagsStr + + "], mask=" + mask + + '}'; + } + + + // -------------------------------------------------------------------------------- + + + /** + * A builder for creating a {@link FlagSet}. + * + * @param The type of flag to be held in the {@link FlagSet} + * @param The type of the {@link FlagSet} implementation. + */ + public static class Builder & MaskedFlag, S extends FlagSet> { + + final Class type; + final EnumSet enumSet; + final Function, S> constructor; + + protected Builder(final Class type, + final Function, S> constructor) { + this.type = type; + this.enumSet = EnumSet.noneOf(type); + this.constructor = constructor; + } + + /** + * Replaces any flags already set in the builder with the contents of the passed flags {@link Collection} + * + * @param flags The flags to set in the builder. + * @return this builder instance. + */ + public Builder withFlags(final Collection flags) { + enumSet.clear(); + if (flags != null) { + for (E flag : flags) { + if (flag != null) { + enumSet.add(flag); + } + } + } + return this; + } + + /** + * @param flags The flags to set in the builder. + * @return this builder instance. + */ + @SafeVarargs + public final Builder withFlags(final E... flags) { + enumSet.clear(); + if (flags != null) { + for (E flag : flags) { + if (flag != null) { + if (!type.equals(flag.getClass())) { + throw new IllegalArgumentException("Unexpected type " + flag.getClass()); + } + enumSet.add(flag); + } + } + } + return this; + } + + /** + * Sets a single flag in the builder. + * + * @param flag The flag to set in the builder. + * @return this builder instance. + */ + public Builder setFlag(final E flag) { + if (flag != null) { + enumSet.add(flag); + } + return this; + } + + /** + * Clears any flags already set in this {@link Builder} + * + * @return this builder instance. + */ + public Builder clear() { + enumSet.clear(); + return this; + } + + /** + * Build the {@link DbiFlagSet} + * + * @return A + */ + public S build() { + return constructor.apply(enumSet); + } + } +} + diff --git a/src/main/java/org/lmdbjava/MaskedFlag.java b/src/main/java/org/lmdbjava/MaskedFlag.java index 4dc47b20..f2f08274 100644 --- a/src/main/java/org/lmdbjava/MaskedFlag.java +++ b/src/main/java/org/lmdbjava/MaskedFlag.java @@ -17,9 +17,13 @@ import static java.util.Objects.requireNonNull; +import java.util.Collection; + /** Indicates an enum that can provide integers for each of its values. */ public interface MaskedFlag { + int EMPTY_MASK = 0; + /** * Obtains the integer value for this enum which can be included in a mask. * @@ -27,6 +31,11 @@ public interface MaskedFlag { */ int getMask(); + /** + * @return The name of the flag. + */ + String name(); + /** * Fetch the integer mask for all presented flags. * @@ -37,17 +46,32 @@ public interface MaskedFlag { @SafeVarargs static int mask(final M... flags) { if (flags == null || flags.length == 0) { - return 0; + return EMPTY_MASK; + } else { + int result = EMPTY_MASK; + for (MaskedFlag flag : flags) { + if (flag == null) { + continue; + } + result |= flag.getMask(); + } + return result; } + } - int result = 0; - for (MaskedFlag flag : flags) { - if (flag == null) { - continue; + static int mask(final Collection flags) { + if (flags == null || flags.isEmpty()) { + return EMPTY_MASK; + } else { + int result = EMPTY_MASK; + for (MaskedFlag flag : flags) { + if (flag == null) { + continue; + } + result |= flag.getMask(); } - result |= flag.getMask(); + return result; } - return result; } /** diff --git a/src/main/java/org/lmdbjava/PutFlagSet.java b/src/main/java/org/lmdbjava/PutFlagSet.java new file mode 100644 index 00000000..290f9729 --- /dev/null +++ b/src/main/java/org/lmdbjava/PutFlagSet.java @@ -0,0 +1,32 @@ +package org.lmdbjava; + +import java.util.EnumSet; +import java.util.Objects; + +public class PutFlagSet extends FlagSet { + + public static final PutFlagSet EMPTY = new PutFlagSet(EnumSet.noneOf(PutFlags.class)); + + private PutFlagSet(final EnumSet flags) { + super(flags); + } + + public static PutFlagSet empty() { + return EMPTY; + } + + public static PutFlagSet of(final PutFlags putFlag) { + Objects.requireNonNull(putFlag); + return new org.lmdbjava.PutFlagSet(EnumSet.of(putFlag)); + } + + public static PutFlagSet of(final PutFlags... putFlags) { + return builder() + .withFlags(putFlags) + .build(); + } + + public static Builder builder() { + return new Builder<>(PutFlags.class, PutFlagSet::new); + } +} diff --git a/src/test/java/org/lmdbjava/DbiFlagSetTest.java b/src/test/java/org/lmdbjava/DbiFlagSetTest.java new file mode 100644 index 00000000..cfbda600 --- /dev/null +++ b/src/test/java/org/lmdbjava/DbiFlagSetTest.java @@ -0,0 +1,101 @@ +package org.lmdbjava; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.util.Arrays; +import java.util.HashSet; +import org.junit.Test; + +public class DbiFlagSetTest { + + @Test + public void testEmpty() { + final DbiFlagSet putFlagSet = DbiFlagSet.empty(); + assertThat( + putFlagSet.getMask(), + is(0)); + assertThat( + putFlagSet.size(), + is(0)); + assertThat( + putFlagSet.isEmpty(), + is(true)); + assertThat( + putFlagSet.isSet(DbiFlags.MDB_REVERSEDUP), + is(false)); + } + + @Test + public void testOf() { + final DbiFlags putFlag = DbiFlags.MDB_CREATE; + final DbiFlagSet putFlagSet = DbiFlagSet.of(putFlag); + assertThat( + putFlagSet.getMask(), + is(MaskedFlag.mask(putFlag))); + assertThat( + putFlagSet.size(), + is(1)); + assertThat( + putFlagSet.isSet(DbiFlags.MDB_REVERSEDUP), + is(false)); + for (DbiFlags flag : putFlagSet) { + assertThat( + putFlagSet.isSet(flag), + is(true)); + } + } + + @Test + public void testOf2() { + final DbiFlags putFlag1 = DbiFlags.MDB_CREATE; + final DbiFlags putFlag2 = DbiFlags.MDB_INTEGERKEY; + final DbiFlagSet putFlagSet = DbiFlagSet.of(putFlag1, putFlag2); + assertThat( + putFlagSet.getMask(), + is(MaskedFlag.mask(putFlag1, putFlag2))); + assertThat( + putFlagSet.size(), + is(2)); + assertThat( + putFlagSet.isSet(DbiFlags.MDB_REVERSEDUP), + is(false)); + for (DbiFlags flag : putFlagSet) { + assertThat( + putFlagSet.isSet(flag), + is(true)); + } + } + + @Test + public void testBuilder() { + final DbiFlags putFlag1 = DbiFlags.MDB_CREATE; + final DbiFlags putFlag2 = DbiFlags.MDB_INTEGERKEY; + final DbiFlagSet putFlagSet = DbiFlagSet.builder() + .setFlag(putFlag1) + .setFlag(putFlag2) + .build(); + assertThat( + putFlagSet.getMask(), + is(MaskedFlag.mask(putFlag1, putFlag2))); + assertThat( + putFlagSet.size(), + is(2)); + assertThat( + putFlagSet.isSet(DbiFlags.MDB_REVERSEDUP), + is(false)); + for (DbiFlags flag : putFlagSet) { + assertThat( + putFlagSet.isSet(flag), + is(true)); + } + final DbiFlagSet putFlagSet2 = DbiFlagSet.builder() + .withFlags(putFlag1, putFlag2) + .build(); + final DbiFlagSet putFlagSet3 = DbiFlagSet.builder() + .withFlags(new HashSet<>(Arrays.asList(putFlag1, putFlag2))) + .build(); + assertThat(putFlagSet, is(putFlagSet2)); + assertThat(putFlagSet, is(putFlagSet3)); + } +} diff --git a/src/test/java/org/lmdbjava/PutFlagSetTest.java b/src/test/java/org/lmdbjava/PutFlagSetTest.java new file mode 100644 index 00000000..4826f436 --- /dev/null +++ b/src/test/java/org/lmdbjava/PutFlagSetTest.java @@ -0,0 +1,101 @@ +package org.lmdbjava; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.util.Arrays; +import java.util.HashSet; +import org.junit.Test; + +public class PutFlagSetTest { + + @Test + public void testEmpty() { + final PutFlagSet putFlagSet = PutFlagSet.empty(); + assertThat( + putFlagSet.getMask(), + is(0)); + assertThat( + putFlagSet.size(), + is(0)); + assertThat( + putFlagSet.isEmpty(), + is(true)); + assertThat( + putFlagSet.isSet(PutFlags.MDB_MULTIPLE), + is(false)); + } + + @Test + public void testOf() { + final PutFlags putFlag = PutFlags.MDB_APPEND; + final PutFlagSet putFlagSet = PutFlagSet.of(putFlag); + assertThat( + putFlagSet.getMask(), + is(MaskedFlag.mask(putFlag))); + assertThat( + putFlagSet.size(), + is(1)); + assertThat( + putFlagSet.isSet(PutFlags.MDB_MULTIPLE), + is(false)); + for (PutFlags flag : putFlagSet) { + assertThat( + putFlagSet.isSet(flag), + is(true)); + } + } + + @Test + public void testOf2() { + final PutFlags putFlag1 = PutFlags.MDB_APPEND; + final PutFlags putFlag2 = PutFlags.MDB_NOOVERWRITE; + final PutFlagSet putFlagSet = PutFlagSet.of(putFlag1, putFlag2); + assertThat( + putFlagSet.getMask(), + is(MaskedFlag.mask(putFlag1, putFlag2))); + assertThat( + putFlagSet.size(), + is(2)); + assertThat( + putFlagSet.isSet(PutFlags.MDB_MULTIPLE), + is(false)); + for (PutFlags flag : putFlagSet) { + assertThat( + putFlagSet.isSet(flag), + is(true)); + } + } + + @Test + public void testBuilder() { + final PutFlags putFlag1 = PutFlags.MDB_APPEND; + final PutFlags putFlag2 = PutFlags.MDB_NOOVERWRITE; + final PutFlagSet putFlagSet = PutFlagSet.builder() + .setFlag(putFlag1) + .setFlag(putFlag2) + .build(); + assertThat( + putFlagSet.getMask(), + is(MaskedFlag.mask(putFlag1, putFlag2))); + assertThat( + putFlagSet.size(), + is(2)); + assertThat( + putFlagSet.isSet(PutFlags.MDB_MULTIPLE), + is(false)); + for (PutFlags flag : putFlagSet) { + assertThat( + putFlagSet.isSet(flag), + is(true)); + } + final PutFlagSet putFlagSet2 = PutFlagSet.builder() + .withFlags(putFlag1, putFlag2) + .build(); + final PutFlagSet putFlagSet3 = PutFlagSet.builder() + .withFlags(new HashSet<>(Arrays.asList(putFlag1, putFlag2))) + .build(); + assertThat(putFlagSet, is(putFlagSet2)); + assertThat(putFlagSet, is(putFlagSet3)); + } +} From 0f66aaf7021ebb25a295dc95ba56a8c78c6370d5 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Mon, 27 Oct 2025 15:27:35 +0000 Subject: [PATCH 09/90] Rename FlagSet to AbstractFlagSet --- .../{FlagSet.java => AbstractFlagSet.java} | 14 +++++++------- src/main/java/org/lmdbjava/DbiBuilder.java | 2 +- src/main/java/org/lmdbjava/DbiFlagSet.java | 2 +- src/main/java/org/lmdbjava/PutFlagSet.java | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) rename src/main/java/org/lmdbjava/{FlagSet.java => AbstractFlagSet.java} (90%) diff --git a/src/main/java/org/lmdbjava/FlagSet.java b/src/main/java/org/lmdbjava/AbstractFlagSet.java similarity index 90% rename from src/main/java/org/lmdbjava/FlagSet.java rename to src/main/java/org/lmdbjava/AbstractFlagSet.java index 21132f8c..3c21fb15 100644 --- a/src/main/java/org/lmdbjava/FlagSet.java +++ b/src/main/java/org/lmdbjava/AbstractFlagSet.java @@ -15,12 +15,12 @@ * * @param */ -public abstract class FlagSet & MaskedFlag> implements Iterable { +public abstract class AbstractFlagSet & MaskedFlag> implements Iterable { private final Set flags; private final int mask; - protected FlagSet(final EnumSet flags) { + protected AbstractFlagSet(final EnumSet flags) { Objects.requireNonNull(flags); this.mask = MaskedFlag.mask(flags); this.flags = Collections.unmodifiableSet(Objects.requireNonNull(flags)); @@ -74,7 +74,7 @@ public Iterator iterator() { public boolean equals(Object object) { if (this == object) return true; if (object == null || getClass() != object.getClass()) return false; - FlagSet flagSet = (FlagSet) object; + AbstractFlagSet flagSet = (AbstractFlagSet) object; return mask == flagSet.mask && Objects.equals(flags, flagSet.flags); } @@ -100,12 +100,12 @@ public String toString() { /** - * A builder for creating a {@link FlagSet}. + * A builder for creating a {@link AbstractFlagSet}. * - * @param The type of flag to be held in the {@link FlagSet} - * @param The type of the {@link FlagSet} implementation. + * @param The type of flag to be held in the {@link AbstractFlagSet} + * @param The type of the {@link AbstractFlagSet} implementation. */ - public static class Builder & MaskedFlag, S extends FlagSet> { + public static class Builder & MaskedFlag, S extends AbstractFlagSet> { final Class type; final EnumSet enumSet; diff --git a/src/main/java/org/lmdbjava/DbiBuilder.java b/src/main/java/org/lmdbjava/DbiBuilder.java index 1803d142..7d05a51e 100644 --- a/src/main/java/org/lmdbjava/DbiBuilder.java +++ b/src/main/java/org/lmdbjava/DbiBuilder.java @@ -222,7 +222,7 @@ public DbiBuilderStage3 withIteratorComparator(final Comparator comparator public static class DbiBuilderStage3 { private final DbiBuilderStage2 dbiBuilderStage2; - private final FlagSet.Builder flagSetBuilder = DbiFlagSet.builder(); + private final AbstractFlagSet.Builder flagSetBuilder = DbiFlagSet.builder(); private Txn txn = null; private DbiBuilderStage3(DbiBuilderStage2 dbiBuilderStage2) { diff --git a/src/main/java/org/lmdbjava/DbiFlagSet.java b/src/main/java/org/lmdbjava/DbiFlagSet.java index 2c6c5ac7..cd1db934 100644 --- a/src/main/java/org/lmdbjava/DbiFlagSet.java +++ b/src/main/java/org/lmdbjava/DbiFlagSet.java @@ -3,7 +3,7 @@ import java.util.EnumSet; import java.util.Objects; -public class DbiFlagSet extends FlagSet { +public class DbiFlagSet extends AbstractFlagSet { public static final DbiFlagSet EMPTY = new DbiFlagSet(EnumSet.noneOf(DbiFlags.class)); diff --git a/src/main/java/org/lmdbjava/PutFlagSet.java b/src/main/java/org/lmdbjava/PutFlagSet.java index 290f9729..8820fe92 100644 --- a/src/main/java/org/lmdbjava/PutFlagSet.java +++ b/src/main/java/org/lmdbjava/PutFlagSet.java @@ -3,7 +3,7 @@ import java.util.EnumSet; import java.util.Objects; -public class PutFlagSet extends FlagSet { +public class PutFlagSet extends AbstractFlagSet { public static final PutFlagSet EMPTY = new PutFlagSet(EnumSet.noneOf(PutFlags.class)); From aa000a1389755dc8f2de4152d149fa6da09b6d22 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Mon, 27 Oct 2025 21:19:27 +0000 Subject: [PATCH 10/90] Add remaining FlagSet impls Replace Env#copy(File, CopyFlags...) with copy(File, CopyFlagSet). As there is only one flag this should not be a breaking change. Deprecate Env#txn(Txn, TxnFlags...) as there is now Env#txn(Txn) Env#txn(Txn, TxnFlags) Env#txn(Txn, TxnFlagSet) --- .../java/org/lmdbjava/AbstractFlagSet.java | 186 +++++++++++++++--- src/main/java/org/lmdbjava/CopyFlagSet.java | 59 ++++++ src/main/java/org/lmdbjava/CopyFlags.java | 30 ++- src/main/java/org/lmdbjava/DbiFlagSet.java | 63 ++++-- src/main/java/org/lmdbjava/DbiFlags.java | 30 ++- src/main/java/org/lmdbjava/Env.java | 77 +++++++- src/main/java/org/lmdbjava/EnvFlagSet.java | 57 ++++++ src/main/java/org/lmdbjava/EnvFlags.java | 30 ++- src/main/java/org/lmdbjava/FlagSet.java | 61 ++++++ src/main/java/org/lmdbjava/PutFlagSet.java | 53 +++-- src/main/java/org/lmdbjava/PutFlags.java | 30 ++- src/main/java/org/lmdbjava/Txn.java | 12 +- src/main/java/org/lmdbjava/TxnFlagSet.java | 63 ++++++ src/main/java/org/lmdbjava/TxnFlags.java | 30 ++- .../java/org/lmdbjava/CopyFlagSetTest.java | 88 +++++++++ .../java/org/lmdbjava/DbiFlagSetTest.java | 93 +++++---- .../java/org/lmdbjava/EnvFlagSetTest.java | 116 +++++++++++ .../java/org/lmdbjava/PutFlagSetTest.java | 15 ++ .../java/org/lmdbjava/TxnFlagSetTest.java | 88 +++++++++ 19 files changed, 1066 insertions(+), 115 deletions(-) create mode 100644 src/main/java/org/lmdbjava/CopyFlagSet.java create mode 100644 src/main/java/org/lmdbjava/EnvFlagSet.java create mode 100644 src/main/java/org/lmdbjava/FlagSet.java create mode 100644 src/main/java/org/lmdbjava/TxnFlagSet.java create mode 100644 src/test/java/org/lmdbjava/CopyFlagSetTest.java create mode 100644 src/test/java/org/lmdbjava/EnvFlagSetTest.java create mode 100644 src/test/java/org/lmdbjava/TxnFlagSetTest.java diff --git a/src/main/java/org/lmdbjava/AbstractFlagSet.java b/src/main/java/org/lmdbjava/AbstractFlagSet.java index 3c21fb15..7ea413fb 100644 --- a/src/main/java/org/lmdbjava/AbstractFlagSet.java +++ b/src/main/java/org/lmdbjava/AbstractFlagSet.java @@ -2,20 +2,19 @@ import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.EnumSet; import java.util.Iterator; import java.util.Objects; import java.util.Set; import java.util.function.Function; -import java.util.stream.Collectors; +import java.util.function.Supplier; /** * Encapsulates an immutable set of flags and the associated bit mask for the flags in the set. * * @param */ -public abstract class AbstractFlagSet & MaskedFlag> implements Iterable { +public abstract class AbstractFlagSet & MaskedFlag> implements FlagSet { private final Set flags; private final int mask; @@ -29,13 +28,15 @@ protected AbstractFlagSet(final EnumSet flags) { /** * @return THe combined bit mask for all flags in the set. */ - int getMask() { + @Override + public int getMask() { return mask; } /** * @return All flags in the set. */ + @Override public Set getFlags() { return flags; } @@ -43,14 +44,18 @@ public Set getFlags() { /** * @return True if flag has been set, i.e. is contained in this set. */ + @Override public boolean isSet(final T flag) { + // Probably cheaper to compare the masks than to use EnumSet.contains() return flag != null - && flags.contains(flag); + && MaskedFlag.isSet(mask, flag); + } /** * @return The number of flags in this set. */ + @Override public int size() { return flags.size(); } @@ -58,6 +63,7 @@ public int size() { /** * @return True if this set is empty. */ + @Override public boolean isEmpty() { return flags.isEmpty(); } @@ -73,9 +79,8 @@ public Iterator iterator() { @Override public boolean equals(Object object) { if (this == object) return true; - if (object == null || getClass() != object.getClass()) return false; - AbstractFlagSet flagSet = (AbstractFlagSet) object; - return mask == flagSet.mask && Objects.equals(flags, flagSet.flags); +// if (object == null || getClass() != object.getClass()) return false; + return FlagSet.equals(this, (FlagSet) object); } @Override @@ -85,14 +90,137 @@ public int hashCode() { @Override public String toString() { - final String flagsStr = flags.stream() - .sorted(Comparator.comparing(MaskedFlag::getMask)) - .map(MaskedFlag::name) - .collect(Collectors.joining(", ")); - return "FlagSet{" + - "flags=[" + flagsStr + - "], mask=" + mask + - '}'; + return FlagSet.asString(this); + } + + + // -------------------------------------------------------------------------------- + + + static abstract class AbstractSingleFlagSet & MaskedFlag> implements FlagSet { + + private final T flag; + // Only holding this for iterator() and getFlags() so make it lazy. + private EnumSet enumSet; + + public AbstractSingleFlagSet(final T flag) { + this.flag = Objects.requireNonNull(flag); + } + + @Override + public int getMask() { + return flag.getMask(); + } + + @Override + public Set getFlags() { + if (enumSet == null) { + return initSet(); + } else { + return this.enumSet; + } + } + + @Override + public boolean isSet(final T flag) { + return this.flag == flag; + } + + @Override + public int size() { + return 1; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public Iterator iterator() { + if (enumSet == null) { + return initSet().iterator(); + } else { + return this.enumSet.iterator(); + } + } + + @Override + public String toString() { + return FlagSet.asString(this); + } + + @Override + public boolean equals(Object object) { + if (this == object) return true; +// if (object == null || getClass() != object.getClass()) return false; + return FlagSet.equals(this, (FlagSet) object); + } + + @Override + public int hashCode() { + return Objects.hash(flag, getFlags()); + } + + private Set initSet() { + final EnumSet set = EnumSet.of(this.flag); + this.enumSet = set; + return set; + } + } + + + // -------------------------------------------------------------------------------- + + + static class AbstractEmptyFlagSet implements FlagSet { + + @Override + public int getMask() { + return MaskedFlag.EMPTY_MASK; + } + + @Override + public Set getFlags() { + return Collections.emptySet(); + } + + @Override + public boolean isSet(final T flag) { + return false; + } + + @Override + public int size() { + return 0; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public Iterator iterator() { + return Collections.emptyIterator(); + } + + @Override + public String toString() { + return FlagSet.asString(this); + } + + @Override + public boolean equals(Object object) { + if (this == object) return true; +// if (object == null || getClass() != object.getClass()) return false; + return FlagSet.equals(this, (FlagSet) object); + } + + @Override + public int hashCode() { + return Objects.hash(getMask(), getFlags()); + } } @@ -105,17 +233,23 @@ public String toString() { * @param The type of flag to be held in the {@link AbstractFlagSet} * @param The type of the {@link AbstractFlagSet} implementation. */ - public static class Builder & MaskedFlag, S extends AbstractFlagSet> { + public static class Builder & MaskedFlag, S extends FlagSet> { final Class type; final EnumSet enumSet; final Function, S> constructor; + final Function singletonSetConstructor; + final Supplier emptySetSupplier; protected Builder(final Class type, - final Function, S> constructor) { + final Function, S> constructor, + final Function singletonSetConstructor, + final Supplier emptySetSupplier) { this.type = type; this.enumSet = EnumSet.noneOf(type); - this.constructor = constructor; + this.constructor = Objects.requireNonNull(constructor); + this.singletonSetConstructor = Objects.requireNonNull(singletonSetConstructor); + this.emptySetSupplier = Objects.requireNonNull(emptySetSupplier); } /** @@ -125,7 +259,7 @@ protected Builder(final Class type, * @return this builder instance. */ public Builder withFlags(final Collection flags) { - enumSet.clear(); + clear(); if (flags != null) { for (E flag : flags) { if (flag != null) { @@ -142,7 +276,7 @@ public Builder withFlags(final Collection flags) { */ @SafeVarargs public final Builder withFlags(final E... flags) { - enumSet.clear(); + clear(); if (flags != null) { for (E flag : flags) { if (flag != null) { @@ -185,8 +319,14 @@ public Builder clear() { * @return A */ public S build() { - return constructor.apply(enumSet); + final int size = enumSet.size(); + if (size == 0) { + return emptySetSupplier.get(); + } else if (size == 1) { + return singletonSetConstructor.apply(enumSet.stream().findFirst().get()); + } else { + return constructor.apply(enumSet); + } } } } - diff --git a/src/main/java/org/lmdbjava/CopyFlagSet.java b/src/main/java/org/lmdbjava/CopyFlagSet.java new file mode 100644 index 00000000..62c73c8d --- /dev/null +++ b/src/main/java/org/lmdbjava/CopyFlagSet.java @@ -0,0 +1,59 @@ +package org.lmdbjava; + +import java.util.Collection; +import java.util.EnumSet; +import java.util.Objects; + +public interface CopyFlagSet extends FlagSet { + + static CopyFlagSet EMPTY = CopyFlagSetImpl.EMPTY; + + static CopyFlagSet empty() { + return CopyFlagSetImpl.EMPTY; + } + + static CopyFlagSet of(final CopyFlags dbiFlag) { + Objects.requireNonNull(dbiFlag); + return dbiFlag; + } + + static CopyFlagSet of(final CopyFlags... CopyFlags) { + return builder() + .withFlags(CopyFlags) + .build(); + } + + static CopyFlagSet of(final Collection CopyFlags) { + return builder() + .withFlags(CopyFlags) + .build(); + } + + static AbstractFlagSet.Builder builder() { + return new AbstractFlagSet.Builder<>( + CopyFlags.class, + CopyFlagSetImpl::new, + copyFlag -> copyFlag, + () -> CopyFlagSetImpl.EMPTY); + } + + + // -------------------------------------------------------------------------------- + + + class CopyFlagSetImpl extends AbstractFlagSet implements CopyFlagSet { + + static final CopyFlagSet EMPTY = new EmptyCopyFlagSet(); + + private CopyFlagSetImpl(final EnumSet flags) { + super(flags); + } + } + + + // -------------------------------------------------------------------------------- + + + class EmptyCopyFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet implements CopyFlagSet { + } +} diff --git a/src/main/java/org/lmdbjava/CopyFlags.java b/src/main/java/org/lmdbjava/CopyFlags.java index 4365563c..fbd4d171 100644 --- a/src/main/java/org/lmdbjava/CopyFlags.java +++ b/src/main/java/org/lmdbjava/CopyFlags.java @@ -15,8 +15,11 @@ */ package org.lmdbjava; +import java.util.EnumSet; +import java.util.Set; + /** Flags for use when performing a {@link Env#copy(java.io.File, org.lmdbjava.CopyFlags...)}. */ -public enum CopyFlags implements MaskedFlag { +public enum CopyFlags implements MaskedFlag, CopyFlagSet { /** Compacting copy: Omit free space from copy, and renumber all pages sequentially. */ MDB_CP_COMPACT(0x01); @@ -31,4 +34,29 @@ public enum CopyFlags implements MaskedFlag { public int getMask() { return mask; } + + @Override + public Set getFlags() { + return EnumSet.of(this); + } + + @Override + public boolean isSet(final CopyFlags flag) { + return flag != null && mask == flag.getMask(); + } + + @Override + public int size() { + return 1; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public String toString() { + return FlagSet.asString(this); + } } diff --git a/src/main/java/org/lmdbjava/DbiFlagSet.java b/src/main/java/org/lmdbjava/DbiFlagSet.java index cd1db934..e5c97544 100644 --- a/src/main/java/org/lmdbjava/DbiFlagSet.java +++ b/src/main/java/org/lmdbjava/DbiFlagSet.java @@ -1,32 +1,57 @@ package org.lmdbjava; +import java.util.Collection; import java.util.EnumSet; import java.util.Objects; -public class DbiFlagSet extends AbstractFlagSet { +public interface DbiFlagSet extends FlagSet { - public static final DbiFlagSet EMPTY = new DbiFlagSet(EnumSet.noneOf(DbiFlags.class)); + static DbiFlagSet empty() { + return DbiFlagSetImpl.EMPTY; + } - private DbiFlagSet(final EnumSet flags) { - super(flags); - } + static DbiFlagSet of(final DbiFlags dbiFlag) { + Objects.requireNonNull(dbiFlag); + return dbiFlag; + } - public static DbiFlagSet empty() { - return EMPTY; - } + static DbiFlagSet of(final DbiFlags... DbiFlags) { + return builder() + .withFlags(DbiFlags) + .build(); + } - public static DbiFlagSet of(final DbiFlags putFlag) { - Objects.requireNonNull(putFlag); - return new DbiFlagSet(EnumSet.of(putFlag)); - } + static DbiFlagSet of(final Collection DbiFlags) { + return builder() + .withFlags(DbiFlags) + .build(); + } + + static AbstractFlagSet.Builder builder() { + return new AbstractFlagSet.Builder<>( + DbiFlags.class, + DbiFlagSetImpl::new, + dbiFlag -> dbiFlag, + () -> DbiFlagSetImpl.EMPTY); + } + + + // -------------------------------------------------------------------------------- - public static DbiFlagSet of(final DbiFlags... DbiFlags) { - return builder() - .withFlags(DbiFlags) - .build(); - } - public static Builder builder() { - return new Builder<>(DbiFlags.class, DbiFlagSet::new); + class DbiFlagSetImpl extends AbstractFlagSet implements DbiFlagSet { + + static final DbiFlagSet EMPTY = new EmptyDbiFlagSet(); + + private DbiFlagSetImpl(final EnumSet flags) { + super(flags); } + } + + + // -------------------------------------------------------------------------------- + + + class EmptyDbiFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet implements DbiFlagSet { + } } diff --git a/src/main/java/org/lmdbjava/DbiFlags.java b/src/main/java/org/lmdbjava/DbiFlags.java index af6eaeaa..6e4b723d 100644 --- a/src/main/java/org/lmdbjava/DbiFlags.java +++ b/src/main/java/org/lmdbjava/DbiFlags.java @@ -15,8 +15,11 @@ */ package org.lmdbjava; +import java.util.EnumSet; +import java.util.Set; + /** Flags for use when opening a {@link Dbi}. */ -public enum DbiFlags implements MaskedFlag { +public enum DbiFlags implements MaskedFlag, DbiFlagSet { /** * Use reverse string keys. @@ -82,4 +85,29 @@ public enum DbiFlags implements MaskedFlag { public int getMask() { return mask; } + + @Override + public Set getFlags() { + return EnumSet.of(this); + } + + @Override + public boolean isSet(final DbiFlags flag) { + return flag != null && mask == flag.getMask(); + } + + @Override + public int size() { + return 1; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public String toString() { + return FlagSet.asString(this); + } } diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index 5de0a7fd..2ad929ad 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -26,7 +26,6 @@ import static org.lmdbjava.MaskedFlag.isSet; import static org.lmdbjava.MaskedFlag.mask; import static org.lmdbjava.ResultCodeMapper.checkRc; -import static org.lmdbjava.TxnFlags.MDB_RDONLY_TXN; import java.io.File; import java.nio.ByteBuffer; @@ -124,6 +123,27 @@ public void close() { LIB.mdb_env_close(ptr); } + /** + * Copies an LMDB environment to the specified destination path. + * + *

This function may be used to make a backup of an existing environment. No lockfile is + * created, since it gets recreated at need. + * + *

If this environment was created using {@link EnvFlags#MDB_NOSUBDIR}, the destination path + * must be a directory that exists but contains no files. If {@link EnvFlags#MDB_NOSUBDIR} was + * used, the destination path must not exist, but it must be possible to create a file at the + * provided path. + * + *

Note: This call can trigger significant file size growth if run in parallel with write + * transactions, because it employs a read-only transaction. See long-lived transactions under + * "Caveats" in the LMDB native documentation. + * + * @param path writable destination path as described above + */ + public void copy(final File path) { + copy(path, CopyFlagSet.EMPTY); + } + /** * Copies an LMDB environment to the specified destination path. * @@ -142,11 +162,10 @@ public void close() { * @param path writable destination path as described above * @param flags special options for this copy */ - public void copy(final File path, final CopyFlags... flags) { + public void copy(final File path, final CopyFlagSet flags) { requireNonNull(path); validatePath(path); - final int flagsMask = mask(flags); - checkRc(LIB.mdb_env_copy2(ptr, path.getAbsolutePath(), flagsMask)); + checkRc(LIB.mdb_env_copy2(ptr, path.getAbsolutePath(), flags.getMask())); } /** @@ -443,16 +462,54 @@ public void sync(final boolean force) { } /** + * @deprecated Instead use {@link Env#txn(Txn, TxnFlagSet)} + * * Obtain a transaction with the requested parent and flags. * * @param parent parent transaction (may be null if no parent) * @param flags applicable flags (eg for a reusable, read-only transaction) * @return a transaction (never null) */ + @Deprecated public Txn txn(final Txn parent, final TxnFlags... flags) { - if (closed) { - throw new AlreadyClosedException(); - } + checkNotClosed(); + return new Txn<>(this, parent, proxy, TxnFlagSet.of(flags)); + } + + /** + * Obtain a transaction with the requested parent and flags. + * + * @param parent parent transaction (may be null if no parent) + * @return a transaction (never null) + */ + public Txn txn(final Txn parent) { + checkNotClosed(); + return new Txn<>(this, parent, proxy, TxnFlagSet.EMPTY); + } + + /** + * Obtain a transaction with the requested parent and flags. + * + * @param parent parent transaction (may be null if no parent) + * @param flag applicable flag (eg for a reusable, read-only transaction) + * @return a transaction (never null) + */ + public Txn txn(final Txn parent, final TxnFlags flag) { + checkNotClosed(); + return new Txn<>(this, parent, proxy, flag); + } + + /** + * Obtain a transaction with the requested parent and flags. + * + * @param parent parent transaction (may be null if no parent) + * @param flags applicable flags (e.g. for a reusable, read-only transaction). + * If the set of flags is used frequently it is recommended to hold + * a static instance of the {@link TxnFlagSet} for re-use. + * @return a transaction (never null) + */ + public Txn txn(final Txn parent, final TxnFlagSet flags) { + checkNotClosed(); return new Txn<>(this, parent, proxy, flags); } @@ -462,7 +519,8 @@ public Txn txn(final Txn parent, final TxnFlags... flags) { * @return a read-only transaction */ public Txn txnRead() { - return txn(null, MDB_RDONLY_TXN); + checkNotClosed(); + return new Txn<>(this, null, proxy, TxnFlags.MDB_RDONLY_TXN); } /** @@ -471,7 +529,8 @@ public Txn txnRead() { * @return a read-write transaction */ public Txn txnWrite() { - return txn(null); + checkNotClosed(); + return new Txn<>(this, null, proxy, TxnFlagSet.EMPTY); } Pointer pointer() { diff --git a/src/main/java/org/lmdbjava/EnvFlagSet.java b/src/main/java/org/lmdbjava/EnvFlagSet.java new file mode 100644 index 00000000..944496e6 --- /dev/null +++ b/src/main/java/org/lmdbjava/EnvFlagSet.java @@ -0,0 +1,57 @@ +package org.lmdbjava; + +import java.util.Collection; +import java.util.EnumSet; +import java.util.Objects; + +public interface EnvFlagSet extends FlagSet { + + static EnvFlagSet empty() { + return EnvFlagSetImpl.EMPTY; + } + + static EnvFlagSet of(final EnvFlags envFlag) { + Objects.requireNonNull(envFlag); + return envFlag; + } + + static EnvFlagSet of(final EnvFlags... EnvFlags) { + return builder() + .withFlags(EnvFlags) + .build(); + } + + static EnvFlagSet of(final Collection EnvFlags) { + return builder() + .withFlags(EnvFlags) + .build(); + } + + static AbstractFlagSet.Builder builder() { + return new AbstractFlagSet.Builder<>( + EnvFlags.class, + EnvFlagSetImpl::new, + envFlag -> envFlag, + () -> EnvFlagSetImpl.EMPTY); + } + + + // -------------------------------------------------------------------------------- + + + class EnvFlagSetImpl extends AbstractFlagSet implements EnvFlagSet { + + static final EnvFlagSet EMPTY = new EmptyEnvFlagSet(); + + private EnvFlagSetImpl(final EnumSet flags) { + super(flags); + } + } + + + // -------------------------------------------------------------------------------- + + + class EmptyEnvFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet implements EnvFlagSet { + } +} diff --git a/src/main/java/org/lmdbjava/EnvFlags.java b/src/main/java/org/lmdbjava/EnvFlags.java index 4ce555a8..7fb4a29b 100644 --- a/src/main/java/org/lmdbjava/EnvFlags.java +++ b/src/main/java/org/lmdbjava/EnvFlags.java @@ -15,8 +15,11 @@ */ package org.lmdbjava; +import java.util.EnumSet; +import java.util.Set; + /** Flags for use when opening the {@link Env}. */ -public enum EnvFlags implements MaskedFlag { +public enum EnvFlags implements MaskedFlag, EnvFlagSet { /** * Mmap at a fixed address (experimental). @@ -144,4 +147,29 @@ public enum EnvFlags implements MaskedFlag { public int getMask() { return mask; } + + @Override + public Set getFlags() { + return EnumSet.of(this); + } + + @Override + public boolean isSet(final EnvFlags flag) { + return flag != null && mask == flag.getMask(); + } + + @Override + public int size() { + return 1; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public String toString() { + return FlagSet.asString(this); + } } diff --git a/src/main/java/org/lmdbjava/FlagSet.java b/src/main/java/org/lmdbjava/FlagSet.java new file mode 100644 index 00000000..80a4c19e --- /dev/null +++ b/src/main/java/org/lmdbjava/FlagSet.java @@ -0,0 +1,61 @@ +package org.lmdbjava; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * A set of flags, each with a bit mask value. + * Flags can be combined in a set such that the set has a combined bit mask value. + * @param + */ +public interface FlagSet extends Iterable { + + int getMask(); + + Set getFlags(); + + boolean isSet(T flag); + + default int size() { + return getFlags().size(); + } + + default boolean isEmpty() { + return getFlags().isEmpty(); + } + + default Iterator iterator() { + return getFlags().iterator(); + } + + static String asString(final FlagSet flagSet) { + Objects.requireNonNull(flagSet); + final String flagsStr = flagSet.getFlags() + .stream() + .sorted(Comparator.comparing(MaskedFlag::getMask)) + .map(MaskedFlag::name) + .collect(Collectors.joining(", ")); + return "FlagSet{" + + "flags=[" + flagsStr + + "], mask=" + flagSet.getMask() + + '}'; + } + + static boolean equals(final FlagSet flagSet1, + final FlagSet flagSet2) { + if (flagSet1 == flagSet2) { + return true; + } else if (flagSet1 != null && flagSet2 == null) { + return false; + } else if (flagSet1 == null) { + return false; + } else { + return flagSet1.getMask() == flagSet2.getMask() + && Objects.equals(flagSet1.getFlags(), flagSet2.getFlags()); + } + } + +} diff --git a/src/main/java/org/lmdbjava/PutFlagSet.java b/src/main/java/org/lmdbjava/PutFlagSet.java index 8820fe92..1eedaf10 100644 --- a/src/main/java/org/lmdbjava/PutFlagSet.java +++ b/src/main/java/org/lmdbjava/PutFlagSet.java @@ -1,32 +1,57 @@ package org.lmdbjava; +import java.util.Collection; import java.util.EnumSet; import java.util.Objects; -public class PutFlagSet extends AbstractFlagSet { +public interface PutFlagSet extends FlagSet { - public static final PutFlagSet EMPTY = new PutFlagSet(EnumSet.noneOf(PutFlags.class)); - - private PutFlagSet(final EnumSet flags) { - super(flags); - } - - public static PutFlagSet empty() { - return EMPTY; + static PutFlagSet empty() { + return PutFlagSetImpl.EMPTY; } - public static PutFlagSet of(final PutFlags putFlag) { + static PutFlagSet of(final PutFlags putFlag) { Objects.requireNonNull(putFlag); - return new org.lmdbjava.PutFlagSet(EnumSet.of(putFlag)); + return putFlag; } - public static PutFlagSet of(final PutFlags... putFlags) { + static PutFlagSet of(final PutFlags... putFlags) { return builder() .withFlags(putFlags) .build(); } - public static Builder builder() { - return new Builder<>(PutFlags.class, PutFlagSet::new); + static PutFlagSet of(final Collection putFlags) { + return builder() + .withFlags(putFlags) + .build(); + } + + static AbstractFlagSet.Builder builder() { + return new AbstractFlagSet.Builder<>( + PutFlags.class, + PutFlagSetImpl::new, + putFlag -> putFlag, + EmptyPutFlagSet::new); + } + + + // -------------------------------------------------------------------------------- + + + class PutFlagSetImpl extends AbstractFlagSet implements PutFlagSet { + + public static final PutFlagSet EMPTY = new EmptyPutFlagSet(); + + private PutFlagSetImpl(final EnumSet flags) { + super(flags); + } + } + + + // -------------------------------------------------------------------------------- + + + class EmptyPutFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet implements PutFlagSet { } } diff --git a/src/main/java/org/lmdbjava/PutFlags.java b/src/main/java/org/lmdbjava/PutFlags.java index 809103de..03fa916a 100644 --- a/src/main/java/org/lmdbjava/PutFlags.java +++ b/src/main/java/org/lmdbjava/PutFlags.java @@ -15,8 +15,11 @@ */ package org.lmdbjava; +import java.util.EnumSet; +import java.util.Set; + /** Flags for use when performing a "put". */ -public enum PutFlags implements MaskedFlag { +public enum PutFlags implements MaskedFlag, PutFlagSet { /** For put: Don't write if the key already exists. */ MDB_NOOVERWRITE(0x10), @@ -49,4 +52,29 @@ public enum PutFlags implements MaskedFlag { public int getMask() { return mask; } + + @Override + public Set getFlags() { + return EnumSet.of(this); + } + + @Override + public boolean isSet(PutFlags flag) { + return flag != null && mask == flag.getMask(); + } + + @Override + public int size() { + return 1; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public String toString() { + return FlagSet.asString(this); + } } diff --git a/src/main/java/org/lmdbjava/Txn.java b/src/main/java/org/lmdbjava/Txn.java index 1d5d4860..5c8440fd 100644 --- a/src/main/java/org/lmdbjava/Txn.java +++ b/src/main/java/org/lmdbjava/Txn.java @@ -20,8 +20,6 @@ import static org.lmdbjava.Env.SHOULD_CHECK; import static org.lmdbjava.Library.LIB; import static org.lmdbjava.Library.RUNTIME; -import static org.lmdbjava.MaskedFlag.isSet; -import static org.lmdbjava.MaskedFlag.mask; import static org.lmdbjava.ResultCodeMapper.checkRc; import static org.lmdbjava.Txn.State.DONE; import static org.lmdbjava.Txn.State.READY; @@ -46,11 +44,13 @@ public final class Txn implements AutoCloseable { private final Env env; private State state; - Txn(final Env env, final Txn parent, final BufferProxy proxy, final TxnFlags... flags) { + Txn(final Env env, final Txn parent, final BufferProxy proxy, final TxnFlagSet flags) { + final TxnFlagSet flagSet = flags != null + ? flags + : TxnFlagSet.EMPTY; this.proxy = proxy; this.keyVal = proxy.keyVal(); - final int flagsMask = mask(flags); - this.readOnly = isSet(flagsMask, MDB_RDONLY_TXN); + this.readOnly = flagSet.isSet(MDB_RDONLY_TXN); if (env.isReadOnly() && !this.readOnly) { throw new EnvIsReadOnly(); } @@ -61,7 +61,7 @@ public final class Txn implements AutoCloseable { } final Pointer txnPtr = allocateDirect(RUNTIME, ADDRESS); final Pointer txnParentPtr = parent == null ? null : parent.ptr; - checkRc(LIB.mdb_txn_begin(env.pointer(), txnParentPtr, flagsMask, txnPtr)); + checkRc(LIB.mdb_txn_begin(env.pointer(), txnParentPtr, flagSet.getMask(), txnPtr)); ptr = txnPtr.getPointer(0); state = READY; diff --git a/src/main/java/org/lmdbjava/TxnFlagSet.java b/src/main/java/org/lmdbjava/TxnFlagSet.java new file mode 100644 index 00000000..6320eece --- /dev/null +++ b/src/main/java/org/lmdbjava/TxnFlagSet.java @@ -0,0 +1,63 @@ +package org.lmdbjava; + +import java.util.EnumSet; +import java.util.Objects; + +public interface TxnFlagSet extends FlagSet { + + TxnFlagSet EMPTY = TxnFlagSetImpl.EMPTY; + + static TxnFlagSet empty() { + return TxnFlagSetImpl.EMPTY; + } + + static TxnFlagSet of(final TxnFlags putFlag) { + Objects.requireNonNull(putFlag); + return new SingleTxnFlagSet(putFlag); + } + + static TxnFlagSet of(final TxnFlags... TxnFlags) { + return builder() + .withFlags(TxnFlags) + .build(); + } + + static AbstractFlagSet.Builder builder() { + return new AbstractFlagSet.Builder<>( + TxnFlags.class, + TxnFlagSetImpl::new, + SingleTxnFlagSet::new, + () -> TxnFlagSetImpl.EMPTY); + } + + + // -------------------------------------------------------------------------------- + + + class TxnFlagSetImpl extends AbstractFlagSet implements TxnFlagSet { + + static final TxnFlagSet EMPTY = new EmptyTxnFlagSet(); + + private TxnFlagSetImpl(final EnumSet flags) { + super(flags); + } + } + + + // -------------------------------------------------------------------------------- + + + class SingleTxnFlagSet extends AbstractFlagSet.AbstractSingleFlagSet implements TxnFlagSet { + + SingleTxnFlagSet(final TxnFlags flag) { + super(flag); + } + } + + + // -------------------------------------------------------------------------------- + + + class EmptyTxnFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet implements TxnFlagSet { + } +} diff --git a/src/main/java/org/lmdbjava/TxnFlags.java b/src/main/java/org/lmdbjava/TxnFlags.java index 26caf6f1..8e4ea757 100644 --- a/src/main/java/org/lmdbjava/TxnFlags.java +++ b/src/main/java/org/lmdbjava/TxnFlags.java @@ -15,8 +15,11 @@ */ package org.lmdbjava; +import java.util.EnumSet; +import java.util.Set; + /** Flags for use when creating a {@link Txn}. */ -public enum TxnFlags implements MaskedFlag { +public enum TxnFlags implements MaskedFlag, TxnFlagSet { /** Read only. */ MDB_RDONLY_TXN(0x2_0000); @@ -30,4 +33,29 @@ public enum TxnFlags implements MaskedFlag { public int getMask() { return mask; } + + @Override + public Set getFlags() { + return EnumSet.of(this); + } + + @Override + public boolean isSet(final TxnFlags flag) { + return flag != null && mask == flag.getMask(); + } + + @Override + public int size() { + return 1; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public String toString() { + return FlagSet.asString(this); + } } diff --git a/src/test/java/org/lmdbjava/CopyFlagSetTest.java b/src/test/java/org/lmdbjava/CopyFlagSetTest.java new file mode 100644 index 00000000..1ea44b7e --- /dev/null +++ b/src/test/java/org/lmdbjava/CopyFlagSetTest.java @@ -0,0 +1,88 @@ +package org.lmdbjava; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.util.Collections; +import java.util.HashSet; +import org.junit.Test; + +public class CopyFlagSetTest { + + @Test + public void testEmpty() { + final CopyFlagSet copyFlagSet = CopyFlagSet.empty(); + assertThat( + copyFlagSet.getMask(), + is(0)); + assertThat( + copyFlagSet.size(), + is(0)); + assertThat( + copyFlagSet.isEmpty(), + is(true)); + assertThat( + copyFlagSet.isSet(CopyFlags.MDB_CP_COMPACT), + is(false)); + final CopyFlagSet copyFlagSet2 = CopyFlagSet.builder() + .build(); + assertThat(copyFlagSet, is(copyFlagSet2)); + assertThat(copyFlagSet, not(CopyFlagSet.of(CopyFlags.MDB_CP_COMPACT))); + assertThat(copyFlagSet, not(CopyFlagSet.builder() + .setFlag(CopyFlags.MDB_CP_COMPACT) + .build())); + } + + @Test + public void testOf() { + final CopyFlags copyFlag = CopyFlags.MDB_CP_COMPACT; + final CopyFlagSet copyFlagSet = CopyFlagSet.of(copyFlag); + assertThat( + copyFlagSet.getMask(), + is(MaskedFlag.mask(copyFlag))); + assertThat( + copyFlagSet.size(), + is(1)); + for (CopyFlags flag : copyFlagSet) { + assertThat( + copyFlagSet.isSet(flag), + is(true)); + } + + final CopyFlagSet copyFlagSet2 = CopyFlagSet.builder() + .setFlag(copyFlag) + .build(); + assertThat(copyFlagSet, is(copyFlagSet2)); + } + + @Test + public void testBuilder() { + final CopyFlags copyFlag1 = CopyFlags.MDB_CP_COMPACT; + final CopyFlagSet copyFlagSet = CopyFlagSet.builder() + .setFlag(copyFlag1) + .build(); + assertThat( + copyFlagSet.getMask(), + is(MaskedFlag.mask(copyFlag1))); + assertThat( + copyFlagSet.size(), + is(1)); + assertThat( + copyFlagSet.isSet(CopyFlags.MDB_CP_COMPACT), + is(true)); + for (CopyFlags flag : copyFlagSet) { + assertThat( + copyFlagSet.isSet(flag), + is(true)); + } + final CopyFlagSet copyFlagSet2 = CopyFlagSet.builder() + .withFlags(copyFlag1) + .build(); + final CopyFlagSet copyFlagSet3 = CopyFlagSet.builder() + .withFlags(new HashSet<>(Collections.singletonList(copyFlag1))) + .build(); + assertThat(copyFlagSet, is(copyFlagSet2)); + assertThat(copyFlagSet, is(copyFlagSet3)); + } +} diff --git a/src/test/java/org/lmdbjava/DbiFlagSetTest.java b/src/test/java/org/lmdbjava/DbiFlagSetTest.java index cfbda600..323a2ed4 100644 --- a/src/test/java/org/lmdbjava/DbiFlagSetTest.java +++ b/src/test/java/org/lmdbjava/DbiFlagSetTest.java @@ -1,6 +1,7 @@ package org.lmdbjava; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import java.util.Arrays; @@ -11,91 +12,105 @@ public class DbiFlagSetTest { @Test public void testEmpty() { - final DbiFlagSet putFlagSet = DbiFlagSet.empty(); + final DbiFlagSet dbiFlagSet = DbiFlagSet.empty(); assertThat( - putFlagSet.getMask(), + dbiFlagSet.getMask(), is(0)); assertThat( - putFlagSet.size(), + dbiFlagSet.size(), is(0)); assertThat( - putFlagSet.isEmpty(), + dbiFlagSet.isEmpty(), is(true)); assertThat( - putFlagSet.isSet(DbiFlags.MDB_REVERSEDUP), + dbiFlagSet.isSet(DbiFlags.MDB_REVERSEDUP), is(false)); + final DbiFlagSet dbiFlagSet2 = DbiFlagSet.builder() + .build(); + assertThat(dbiFlagSet, is(dbiFlagSet2)); + assertThat(dbiFlagSet, not(DbiFlagSet.of(DbiFlags.MDB_CREATE))); + assertThat(dbiFlagSet, not(DbiFlagSet.of(DbiFlags.MDB_CREATE, DbiFlags.MDB_DUPSORT))); + assertThat(dbiFlagSet, not(DbiFlagSet.builder() + .setFlag(DbiFlags.MDB_CREATE) + .setFlag(DbiFlags.MDB_DUPFIXED) + .build())); } @Test public void testOf() { - final DbiFlags putFlag = DbiFlags.MDB_CREATE; - final DbiFlagSet putFlagSet = DbiFlagSet.of(putFlag); + final DbiFlags dbiFlag = DbiFlags.MDB_CREATE; + final DbiFlagSet dbiFlagSet = DbiFlagSet.of(dbiFlag); assertThat( - putFlagSet.getMask(), - is(MaskedFlag.mask(putFlag))); + dbiFlagSet.getMask(), + is(MaskedFlag.mask(dbiFlag))); assertThat( - putFlagSet.size(), + dbiFlagSet.size(), is(1)); assertThat( - putFlagSet.isSet(DbiFlags.MDB_REVERSEDUP), + dbiFlagSet.isSet(DbiFlags.MDB_REVERSEDUP), is(false)); - for (DbiFlags flag : putFlagSet) { + for (DbiFlags flag : dbiFlagSet) { assertThat( - putFlagSet.isSet(flag), + dbiFlagSet.isSet(flag), is(true)); } + + final DbiFlagSet dbiFlagSet2 = DbiFlagSet.builder() + .setFlag(dbiFlag) + .build(); + assertThat(dbiFlagSet, is(dbiFlagSet2)); } @Test public void testOf2() { - final DbiFlags putFlag1 = DbiFlags.MDB_CREATE; - final DbiFlags putFlag2 = DbiFlags.MDB_INTEGERKEY; - final DbiFlagSet putFlagSet = DbiFlagSet.of(putFlag1, putFlag2); + final DbiFlags dbiFlag1 = DbiFlags.MDB_CREATE; + final DbiFlags dbiFlag2 = DbiFlags.MDB_INTEGERKEY; + final DbiFlagSet dbiFlagSet = DbiFlagSet.of(dbiFlag1, dbiFlag2); assertThat( - putFlagSet.getMask(), - is(MaskedFlag.mask(putFlag1, putFlag2))); + dbiFlagSet.getMask(), + is(MaskedFlag.mask(dbiFlag1, dbiFlag2))); assertThat( - putFlagSet.size(), + dbiFlagSet.size(), is(2)); assertThat( - putFlagSet.isSet(DbiFlags.MDB_REVERSEDUP), + dbiFlagSet.isSet(DbiFlags.MDB_REVERSEDUP), is(false)); - for (DbiFlags flag : putFlagSet) { + for (DbiFlags flag : dbiFlagSet) { assertThat( - putFlagSet.isSet(flag), + dbiFlagSet.isSet(flag), is(true)); } } @Test public void testBuilder() { - final DbiFlags putFlag1 = DbiFlags.MDB_CREATE; - final DbiFlags putFlag2 = DbiFlags.MDB_INTEGERKEY; - final DbiFlagSet putFlagSet = DbiFlagSet.builder() - .setFlag(putFlag1) - .setFlag(putFlag2) + final DbiFlags dbiFlag1 = DbiFlags.MDB_CREATE; + final DbiFlags dbiFlag2 = DbiFlags.MDB_INTEGERKEY; + final DbiFlagSet dbiFlagSet = DbiFlagSet.builder() + .setFlag(dbiFlag1) + .setFlag(dbiFlag2) .build(); assertThat( - putFlagSet.getMask(), - is(MaskedFlag.mask(putFlag1, putFlag2))); + dbiFlagSet.getMask(), + is(MaskedFlag.mask(dbiFlag1, dbiFlag2))); assertThat( - putFlagSet.size(), + dbiFlagSet.size(), is(2)); assertThat( - putFlagSet.isSet(DbiFlags.MDB_REVERSEDUP), + dbiFlagSet.isSet(DbiFlags.MDB_REVERSEDUP), is(false)); - for (DbiFlags flag : putFlagSet) { + for (DbiFlags flag : dbiFlagSet) { assertThat( - putFlagSet.isSet(flag), + dbiFlagSet.isSet(flag), is(true)); } - final DbiFlagSet putFlagSet2 = DbiFlagSet.builder() - .withFlags(putFlag1, putFlag2) + final DbiFlagSet dbiFlagSet2 = DbiFlagSet.builder() + .withFlags(dbiFlag1, dbiFlag2) .build(); - final DbiFlagSet putFlagSet3 = DbiFlagSet.builder() - .withFlags(new HashSet<>(Arrays.asList(putFlag1, putFlag2))) + final DbiFlagSet dbiFlagSet3 = DbiFlagSet.builder() + .withFlags(new HashSet<>(Arrays.asList(dbiFlag1, dbiFlag2))) .build(); - assertThat(putFlagSet, is(putFlagSet2)); - assertThat(putFlagSet, is(putFlagSet3)); + assertThat(dbiFlagSet, is(dbiFlagSet2)); + assertThat(dbiFlagSet, is(dbiFlagSet3)); } } diff --git a/src/test/java/org/lmdbjava/EnvFlagSetTest.java b/src/test/java/org/lmdbjava/EnvFlagSetTest.java new file mode 100644 index 00000000..ed6a0fea --- /dev/null +++ b/src/test/java/org/lmdbjava/EnvFlagSetTest.java @@ -0,0 +1,116 @@ +package org.lmdbjava; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.util.Arrays; +import java.util.HashSet; +import org.junit.Test; + +public class EnvFlagSetTest { + + @Test + public void testEmpty() { + final EnvFlagSet envFlagSet = EnvFlagSet.empty(); + assertThat( + envFlagSet.getMask(), + is(0)); + assertThat( + envFlagSet.size(), + is(0)); + assertThat( + envFlagSet.isEmpty(), + is(true)); + assertThat( + envFlagSet.isSet(EnvFlags.MDB_NOSUBDIR), + is(false)); + final EnvFlagSet envFlagSet2 = EnvFlagSet.builder() + .build(); + assertThat(envFlagSet, is(envFlagSet2)); + assertThat(envFlagSet, not(EnvFlagSet.of(EnvFlags.MDB_FIXEDMAP))); + assertThat(envFlagSet, not(EnvFlagSet.of(EnvFlags.MDB_FIXEDMAP, EnvFlags.MDB_NORDAHEAD))); + assertThat(envFlagSet, not(EnvFlagSet.builder() + .setFlag(EnvFlags.MDB_FIXEDMAP) + .setFlag(EnvFlags.MDB_NORDAHEAD) + .build())); + } + + @Test + public void testOf() { + final EnvFlags envFlag = EnvFlags.MDB_FIXEDMAP; + final EnvFlagSet envFlagSet = EnvFlagSet.of(envFlag); + assertThat( + envFlagSet.getMask(), + is(MaskedFlag.mask(envFlag))); + assertThat( + envFlagSet.size(), + is(1)); + assertThat( + envFlagSet.isSet(EnvFlags.MDB_NOSUBDIR), + is(false)); + for (EnvFlags flag : envFlagSet) { + assertThat( + envFlagSet.isSet(flag), + is(true)); + } + + final EnvFlagSet envFlagSet2 = EnvFlagSet.builder() + .setFlag(envFlag) + .build(); + assertThat(envFlagSet, is(envFlagSet2)); + } + + @Test + public void testOf2() { + final EnvFlags envFlag1 = EnvFlags.MDB_FIXEDMAP; + final EnvFlags envFlag2 = EnvFlags.MDB_NORDAHEAD; + final EnvFlagSet envFlagSet = EnvFlagSet.of(envFlag1, envFlag2); + assertThat( + envFlagSet.getMask(), + is(MaskedFlag.mask(envFlag1, envFlag2))); + assertThat( + envFlagSet.size(), + is(2)); + assertThat( + envFlagSet.isSet(EnvFlags.MDB_WRITEMAP), + is(false)); + for (EnvFlags flag : envFlagSet) { + assertThat( + envFlagSet.isSet(flag), + is(true)); + } + } + + @Test + public void testBuilder() { + final EnvFlags envFlag1 = EnvFlags.MDB_FIXEDMAP; + final EnvFlags envFlag2 = EnvFlags.MDB_NORDAHEAD; + final EnvFlagSet envFlagSet = EnvFlagSet.builder() + .setFlag(envFlag1) + .setFlag(envFlag2) + .build(); + assertThat( + envFlagSet.getMask(), + is(MaskedFlag.mask(envFlag1, envFlag2))); + assertThat( + envFlagSet.size(), + is(2)); + assertThat( + envFlagSet.isSet(EnvFlags.MDB_NOTLS), + is(false)); + for (EnvFlags flag : envFlagSet) { + assertThat( + envFlagSet.isSet(flag), + is(true)); + } + final EnvFlagSet envFlagSet2 = EnvFlagSet.builder() + .withFlags(envFlag1, envFlag2) + .build(); + final EnvFlagSet envFlagSet3 = EnvFlagSet.builder() + .withFlags(new HashSet<>(Arrays.asList(envFlag1, envFlag2))) + .build(); + assertThat(envFlagSet, is(envFlagSet2)); + assertThat(envFlagSet, is(envFlagSet3)); + } +} diff --git a/src/test/java/org/lmdbjava/PutFlagSetTest.java b/src/test/java/org/lmdbjava/PutFlagSetTest.java index 4826f436..8cf1efe0 100644 --- a/src/test/java/org/lmdbjava/PutFlagSetTest.java +++ b/src/test/java/org/lmdbjava/PutFlagSetTest.java @@ -1,6 +1,7 @@ package org.lmdbjava; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import java.util.Arrays; @@ -24,6 +25,15 @@ public void testEmpty() { assertThat( putFlagSet.isSet(PutFlags.MDB_MULTIPLE), is(false)); + final PutFlagSet putFlagSet2 = PutFlagSet.builder() + .build(); + assertThat(putFlagSet, is(putFlagSet2)); + assertThat(putFlagSet, not(PutFlagSet.of(PutFlags.MDB_APPEND))); + assertThat(putFlagSet, not(PutFlagSet.of(PutFlags.MDB_APPEND, PutFlags.MDB_RESERVE))); + assertThat(putFlagSet, not(PutFlagSet.builder() + .setFlag(PutFlags.MDB_CURRENT) + .setFlag(PutFlags.MDB_MULTIPLE) + .build())); } @Test @@ -44,6 +54,11 @@ public void testOf() { putFlagSet.isSet(flag), is(true)); } + + final PutFlagSet putFlagSet2 = PutFlagSet.builder() + .setFlag(putFlag) + .build(); + assertThat(putFlagSet, is(putFlagSet2)); } @Test diff --git a/src/test/java/org/lmdbjava/TxnFlagSetTest.java b/src/test/java/org/lmdbjava/TxnFlagSetTest.java new file mode 100644 index 00000000..b526ceeb --- /dev/null +++ b/src/test/java/org/lmdbjava/TxnFlagSetTest.java @@ -0,0 +1,88 @@ +package org.lmdbjava; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; + +import java.util.Collections; +import java.util.HashSet; +import org.junit.Test; + +public class TxnFlagSetTest { + + @Test + public void testEmpty() { + final TxnFlagSet txnFlagSet = TxnFlagSet.empty(); + assertThat( + txnFlagSet.getMask(), + is(0)); + assertThat( + txnFlagSet.size(), + is(0)); + assertThat( + txnFlagSet.isEmpty(), + is(true)); + assertThat( + txnFlagSet.isSet(TxnFlags.MDB_RDONLY_TXN), + is(false)); + final TxnFlagSet txnFlagSet2 = TxnFlagSet.builder() + .build(); + assertThat(txnFlagSet, is(txnFlagSet2)); + assertThat(txnFlagSet, not(TxnFlagSet.of(TxnFlags.MDB_RDONLY_TXN))); + assertThat(txnFlagSet, not(TxnFlagSet.builder() + .setFlag(TxnFlags.MDB_RDONLY_TXN) + .build())); + } + + @Test + public void testOf() { + final TxnFlags txnFlag = TxnFlags.MDB_RDONLY_TXN; + final TxnFlagSet txnFlagSet = TxnFlagSet.of(txnFlag); + assertThat( + txnFlagSet.getMask(), + is(MaskedFlag.mask(txnFlag))); + assertThat( + txnFlagSet.size(), + is(1)); + for (TxnFlags flag : txnFlagSet) { + assertThat( + txnFlagSet.isSet(flag), + is(true)); + } + + final TxnFlagSet txnFlagSet2 = TxnFlagSet.builder() + .setFlag(txnFlag) + .build(); + assertThat(txnFlagSet, is(txnFlagSet2)); + } + + @Test + public void testBuilder() { + final TxnFlags txnFlag1 = TxnFlags.MDB_RDONLY_TXN; + final TxnFlagSet txnFlagSet = TxnFlagSet.builder() + .setFlag(txnFlag1) + .build(); + assertThat( + txnFlagSet.getMask(), + is(MaskedFlag.mask(txnFlag1))); + assertThat( + txnFlagSet.size(), + is(1)); + assertThat( + txnFlagSet.isSet(TxnFlags.MDB_RDONLY_TXN), + is(true)); + for (TxnFlags flag : txnFlagSet) { + assertThat( + txnFlagSet.isSet(flag), + is(true)); + } + final TxnFlagSet txnFlagSet2 = TxnFlagSet.builder() + .withFlags(txnFlag1) + .build(); + final TxnFlagSet txnFlagSet3 = TxnFlagSet.builder() + .withFlags(new HashSet<>(Collections.singletonList(txnFlag1))) + .build(); + assertThat(txnFlagSet, is(txnFlagSet2)); + assertThat(txnFlagSet, is(txnFlagSet3)); + } +} From 667dab37ccce1c8d79307c01ab1f1d130082cd84 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Mon, 27 Oct 2025 21:25:11 +0000 Subject: [PATCH 11/90] Fix Javadoc --- src/main/java/org/lmdbjava/CopyFlags.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/lmdbjava/CopyFlags.java b/src/main/java/org/lmdbjava/CopyFlags.java index fbd4d171..b45dc87c 100644 --- a/src/main/java/org/lmdbjava/CopyFlags.java +++ b/src/main/java/org/lmdbjava/CopyFlags.java @@ -15,10 +15,11 @@ */ package org.lmdbjava; +import java.io.File; import java.util.EnumSet; import java.util.Set; -/** Flags for use when performing a {@link Env#copy(java.io.File, org.lmdbjava.CopyFlags...)}. */ +/** Flags for use when performing a {@link Env#copy(File, CopyFlagSet)}. */ public enum CopyFlags implements MaskedFlag, CopyFlagSet { /** Compacting copy: Omit free space from copy, and renumber all pages sequentially. */ From b46c9164c4154bcb0dc78b675556b25d85390b1d Mon Sep 17 00:00:00 2001 From: stroomdev66 Date: Tue, 28 Oct 2025 11:40:20 +0000 Subject: [PATCH 12/90] Add more cursor tests to protect against future regression --- .../org/lmdbjava/CursorIterableRangeTest.java | 188 ++++++++++++++++++ .../java/org/lmdbjava/CursorIterableTest.java | 52 ++--- .../testSignedComparator.actual | 49 +++++ .../testSignedComparator.expected | 49 +++++ .../testSignedComparatorDupsort.actual | 49 +++++ .../testSignedComparatorDupsort.expected | 49 +++++ .../testUnsignedComparator.actual | 49 +++++ .../testUnsignedComparator.expected | 49 +++++ .../testUnsignedComparatorDupsort.actual | 49 +++++ .../testUnsignedComparatorDupsort.expected | 49 +++++ .../CursorIterableRangeTest/tests.csv | 49 +++++ 11 files changed, 655 insertions(+), 26 deletions(-) create mode 100644 src/test/java/org/lmdbjava/CursorIterableRangeTest.java create mode 100644 src/test/resources/CursorIterableRangeTest/testSignedComparator.actual create mode 100644 src/test/resources/CursorIterableRangeTest/testSignedComparator.expected create mode 100644 src/test/resources/CursorIterableRangeTest/testSignedComparatorDupsort.actual create mode 100644 src/test/resources/CursorIterableRangeTest/testSignedComparatorDupsort.expected create mode 100644 src/test/resources/CursorIterableRangeTest/testUnsignedComparator.actual create mode 100644 src/test/resources/CursorIterableRangeTest/testUnsignedComparator.expected create mode 100644 src/test/resources/CursorIterableRangeTest/testUnsignedComparatorDupsort.actual create mode 100644 src/test/resources/CursorIterableRangeTest/testUnsignedComparatorDupsort.expected create mode 100644 src/test/resources/CursorIterableRangeTest/tests.csv diff --git a/src/test/java/org/lmdbjava/CursorIterableRangeTest.java b/src/test/java/org/lmdbjava/CursorIterableRangeTest.java new file mode 100644 index 00000000..03f9a82d --- /dev/null +++ b/src/test/java/org/lmdbjava/CursorIterableRangeTest.java @@ -0,0 +1,188 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.lmdbjava; + +import org.junit.Test; +import org.lmdbjava.ByteBufferProxy.AbstractByteBufferProxy; +import org.lmdbjava.CursorIterable.KeyVal; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.stream.Stream; + +import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.lmdbjava.DbiFlags.MDB_CREATE; +import static org.lmdbjava.DbiFlags.MDB_DUPSORT; +import static org.lmdbjava.Env.create; +import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; +import static org.lmdbjava.TestUtils.DB_1; +import static org.lmdbjava.TestUtils.POSIX_MODE; +import static org.lmdbjava.TestUtils.bb; + +/** + * Test {@link CursorIterable}. + */ +public final class CursorIterableRangeTest { + + @Test + public void testSignedComparator() throws IOException { + test(ByteBuffer::compareTo, true, "testSignedComparator", 1, MDB_CREATE); + } + + @Test + public void testUnsignedComparator() throws IOException { + test(AbstractByteBufferProxy::compareBuff, false, "testUnsignedComparator", 1, MDB_CREATE); + } + + @Test + public void testSignedComparatorDupsort() throws IOException { + test(ByteBuffer::compareTo, true, "testSignedComparatorDupsort", 2,MDB_CREATE, MDB_DUPSORT); + } + + @Test + public void testUnsignedComparatorDupsort() throws IOException { + test(AbstractByteBufferProxy::compareBuff, false, "testUnsignedComparatorDupsort",2, MDB_CREATE, MDB_DUPSORT); + } + + private void test(final Comparator comparator, + final boolean nativeCb, + final String testName, + final int copies, + final DbiFlags... flags) throws IOException { + final Path dbPath = Files.createTempFile("test", "db"); + try (final Env env = + create() + .setMapSize(KIBIBYTES.toBytes(256)) + .setMaxReaders(1) + .setMaxDbs(1) + .open(dbPath.toFile(), POSIX_MODE, MDB_NOSUBDIR)) { + final Dbi dbi = env.openDbi(DB_1, comparator, nativeCb, flags); + populateDatabase(env, dbi, copies); + + final File tests = new File("src/test/resources/CursorIterableRangeTest/tests.csv"); + final File actual = tests.getParentFile().toPath().resolve(testName + ".actual").toFile(); + final File expected = tests.getParentFile().toPath().resolve(testName + ".expected").toFile(); + final String csv = readFile(tests); + final String[] parts = csv.split("\n"); + try (final Writer writer = new FileWriter(actual)) { + for (final String part : parts) { + writer.append(part); + writer.append(" =>"); + + final String[] params = part.split(","); + final KeyRangeType keyRangeType = KeyRangeType.valueOf(params[0].trim()); + ByteBuffer start = null; + ByteBuffer stop = null; + if (params.length > 1 && params[1].trim().length() > 0) { + start = bb(Integer.parseInt(params[1].trim())); + } + if (params.length > 2 && params[2].trim().length() > 0) { + stop = bb(Integer.parseInt(params[2].trim())); + } + final KeyRange keyRange = new KeyRange<>(keyRangeType, start, stop); + boolean first = true; + try (Txn txn = env.txnRead(); + CursorIterable c = dbi.iterate(txn, keyRange)) { + for (final KeyVal kv : c) { + if (first) { + first = false; + } else { + writer.append(","); + } + + final int key = kv.key().getInt(); + final int val = kv.val().getInt(); + writer.append(" ["); + writer.append(String.valueOf(key)); + writer.append(","); + writer.append(String.valueOf(val)); + writer.append("]"); + } + } + writer.append("\n"); + } + } + + // Compare files. + final String act = readFile(actual); + final String exp = readFile(expected); + assertThat("Files are not equal", act.equals(exp)); + } finally { + deleteFile(dbPath); + } + } + + private void populateDatabase(final Env env, + final Dbi dbi, + final int copies) { + try (Txn txn = env.txnWrite()) { + final Cursor c = dbi.openCursor(txn); + for (int i = 0; i < copies; i++) { + c.put(bb(0), bb(1 + i)); + c.put(bb(2), bb(3 + i)); + c.put(bb(4), bb(5 + i)); + c.put(bb(6), bb(7 + i)); + c.put(bb(8), bb(9 + i)); + c.put(bb(-2), bb(-1 + i)); + } + txn.commit(); + } + } + + private String readFile(final File file) throws IOException { + final StringBuilder result = new StringBuilder(); + try (final Reader reader = new FileReader(file)) { + final char[] cbuf = new char[4096]; + int nread; + while ((nread = reader.read(cbuf, 0, cbuf.length)) != -1) { + result.append(cbuf, 0, nread); + } + } + return result.toString(); + } + + private boolean recursiveDeleteFiles(Path file) { + if (deleteFile(file)) { + return true; + } else { + try (final Stream stream = Files.list(file)) { + stream.forEach(this::recursiveDeleteFiles); + } catch (final IOException e) { + return false; + } + return deleteFile(file); + } + } + + private boolean deleteFile(Path file) { + try { + Files.delete(file); + } catch (final IOException e) { + return false; + } + return true; + } +} diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index bd23bc55..595898e2 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -73,6 +73,32 @@ public final class CursorIterableTest { private Env env; private Deque list; + @Before + public void before() throws IOException { + final File path = tmp.newFile(); + env = + create() + .setMapSize(KIBIBYTES.toBytes(256)) + .setMaxReaders(1) + .setMaxDbs(1) + .open(path, POSIX_MODE, MDB_NOSUBDIR); + db = env.openDbi(DB_1, MDB_CREATE); + populateDatabase(db); + } + + private void populateDatabase(final Dbi dbi) { + list = new LinkedList<>(); + list.addAll(asList(2, 3, 4, 5, 6, 7, 8, 9)); + try (Txn txn = env.txnWrite()) { + final Cursor c = dbi.openCursor(txn); + c.put(bb(2), bb(3), MDB_NOOVERWRITE); + c.put(bb(4), bb(5)); + c.put(bb(6), bb(7)); + c.put(bb(8), bb(9)); + txn.commit(); + } + } + @After public void after() { env.close(); @@ -113,32 +139,6 @@ public void atMostTest() { verify(atMost(bb(6)), 2, 4, 6); } - @Before - public void before() throws IOException { - final File path = tmp.newFile(); - env = - create() - .setMapSize(KIBIBYTES.toBytes(256)) - .setMaxReaders(1) - .setMaxDbs(1) - .open(path, POSIX_MODE, MDB_NOSUBDIR); - db = env.openDbi(DB_1, MDB_CREATE); - populateDatabase(db); - } - - private void populateDatabase(final Dbi dbi) { - list = new LinkedList<>(); - list.addAll(asList(2, 3, 4, 5, 6, 7, 8, 9)); - try (Txn txn = env.txnWrite()) { - final Cursor c = dbi.openCursor(txn); - c.put(bb(2), bb(3), MDB_NOOVERWRITE); - c.put(bb(4), bb(5)); - c.put(bb(6), bb(7)); - c.put(bb(8), bb(9)); - txn.commit(); - } - } - @Test public void closedBackwardTest() { verify(closedBackward(bb(7), bb(3)), 6, 4); diff --git a/src/test/resources/CursorIterableRangeTest/testSignedComparator.actual b/src/test/resources/CursorIterableRangeTest/testSignedComparator.actual new file mode 100644 index 00000000..5968c07f --- /dev/null +++ b/src/test/resources/CursorIterableRangeTest/testSignedComparator.actual @@ -0,0 +1,49 @@ +FORWARD_ALL, , => [-2,-1], [0,1], [2,3], [4,5], [6,7], [8,9] +FORWARD_AT_LEAST, 5, => [6,7], [8,9] +FORWARD_AT_LEAST, 6, => [6,7], [8,9] +FORWARD_AT_MOST, , 5 => [-2,-1], [0,1], [2,3], [4,5] +FORWARD_AT_MOST, , 6 => [-2,-1], [0,1], [2,3], [4,5], [6,7] +FORWARD_CLOSED, 3, 7 => [4,5], [6,7] +FORWARD_CLOSED, 2, 6 => [2,3], [4,5], [6,7] +FORWARD_CLOSED, 1, 7 => [2,3], [4,5], [6,7] +FORWARD_CLOSED_OPEN, 3, 8 => [4,5], [6,7] +FORWARD_CLOSED_OPEN, 2, 6 => [2,3], [4,5] +FORWARD_GREATER_THAN, 4, => [6,7], [8,9] +FORWARD_GREATER_THAN, 3, => [4,5], [6,7], [8,9] +FORWARD_LESS_THAN, , 5 => [-2,-1], [0,1], [2,3], [4,5] +FORWARD_LESS_THAN, , 8 => [-2,-1], [0,1], [2,3], [4,5], [6,7] +FORWARD_OPEN, 3, 7 => [4,5], [6,7] +FORWARD_OPEN, 2, 8 => [4,5], [6,7] +FORWARD_OPEN_CLOSED, 3, 8 => [4,5], [6,7], [8,9] +FORWARD_OPEN_CLOSED, 2, 6 => [4,5], [6,7] +BACKWARD_ALL, , => [8,9], [6,7], [4,5], [2,3], [0,1], [-2,-1] +BACKWARD_AT_LEAST, 5, => [4,5], [2,3], [0,1], [-2,-1] +BACKWARD_AT_LEAST, 6, => [6,7], [4,5], [2,3], [0,1], [-2,-1] +BACKWARD_AT_LEAST, 9, => [8,9], [6,7], [4,5], [2,3], [0,1], [-2,-1] +BACKWARD_AT_LEAST, -1, => [-2,-1] +BACKWARD_AT_MOST, , 5 => [8,9], [6,7] +BACKWARD_AT_MOST, , 6 => [8,9], [6,7] +BACKWARD_AT_MOST, , -1 => [8,9], [6,7], [4,5], [2,3], [0,1] +BACKWARD_CLOSED, 7, 3 => [6,7], [4,5] +BACKWARD_CLOSED, 6, 2 => [6,7], [4,5], [2,3] +BACKWARD_CLOSED, 9, 3 => [8,9], [6,7], [4,5] +BACKWARD_CLOSED, 9, -1 => [8,9], [6,7], [4,5], [2,3], [0,1] +BACKWARD_CLOSED_OPEN, 8, 3 => [8,9], [6,7], [4,5] +BACKWARD_CLOSED_OPEN, 7, 2 => [6,7], [4,5] +BACKWARD_CLOSED_OPEN, 9, 3 => [8,9], [6,7], [4,5] +BACKWARD_CLOSED_OPEN, 9, -1 => [8,9], [6,7], [4,5], [2,3], [0,1] +BACKWARD_GREATER_THAN, 6, => [4,5], [2,3], [0,1], [-2,-1] +BACKWARD_GREATER_THAN, 7, => [6,7], [4,5], [2,3], [0,1], [-2,-1] +BACKWARD_GREATER_THAN, 9, => [8,9], [6,7], [4,5], [2,3], [0,1], [-2,-1] +BACKWARD_GREATER_THAN, -1, => [-2,-1] +BACKWARD_LESS_THAN, , 5 => [8,9], [6,7] +BACKWARD_LESS_THAN, , 2 => [8,9], [6,7], [4,5] +BACKWARD_LESS_THAN, , -1 => [8,9], [6,7], [4,5], [2,3], [0,1] +BACKWARD_OPEN, 7, 2 => [6,7], [4,5] +BACKWARD_OPEN, 8, 1 => [6,7], [4,5], [2,3] +BACKWARD_OPEN, 9, 4 => [8,9], [6,7] +BACKWARD_OPEN, 9, -1 => [8,9], [6,7], [4,5], [2,3], [0,1] +BACKWARD_OPEN_CLOSED, 7, 2 => [6,7], [4,5], [2,3] +BACKWARD_OPEN_CLOSED, 8, 4 => [6,7], [4,5] +BACKWARD_OPEN_CLOSED, 9, 4 => [8,9], [6,7], [4,5] +BACKWARD_OPEN_CLOSED, 9, -1 => [8,9], [6,7], [4,5], [2,3], [0,1] diff --git a/src/test/resources/CursorIterableRangeTest/testSignedComparator.expected b/src/test/resources/CursorIterableRangeTest/testSignedComparator.expected new file mode 100644 index 00000000..5968c07f --- /dev/null +++ b/src/test/resources/CursorIterableRangeTest/testSignedComparator.expected @@ -0,0 +1,49 @@ +FORWARD_ALL, , => [-2,-1], [0,1], [2,3], [4,5], [6,7], [8,9] +FORWARD_AT_LEAST, 5, => [6,7], [8,9] +FORWARD_AT_LEAST, 6, => [6,7], [8,9] +FORWARD_AT_MOST, , 5 => [-2,-1], [0,1], [2,3], [4,5] +FORWARD_AT_MOST, , 6 => [-2,-1], [0,1], [2,3], [4,5], [6,7] +FORWARD_CLOSED, 3, 7 => [4,5], [6,7] +FORWARD_CLOSED, 2, 6 => [2,3], [4,5], [6,7] +FORWARD_CLOSED, 1, 7 => [2,3], [4,5], [6,7] +FORWARD_CLOSED_OPEN, 3, 8 => [4,5], [6,7] +FORWARD_CLOSED_OPEN, 2, 6 => [2,3], [4,5] +FORWARD_GREATER_THAN, 4, => [6,7], [8,9] +FORWARD_GREATER_THAN, 3, => [4,5], [6,7], [8,9] +FORWARD_LESS_THAN, , 5 => [-2,-1], [0,1], [2,3], [4,5] +FORWARD_LESS_THAN, , 8 => [-2,-1], [0,1], [2,3], [4,5], [6,7] +FORWARD_OPEN, 3, 7 => [4,5], [6,7] +FORWARD_OPEN, 2, 8 => [4,5], [6,7] +FORWARD_OPEN_CLOSED, 3, 8 => [4,5], [6,7], [8,9] +FORWARD_OPEN_CLOSED, 2, 6 => [4,5], [6,7] +BACKWARD_ALL, , => [8,9], [6,7], [4,5], [2,3], [0,1], [-2,-1] +BACKWARD_AT_LEAST, 5, => [4,5], [2,3], [0,1], [-2,-1] +BACKWARD_AT_LEAST, 6, => [6,7], [4,5], [2,3], [0,1], [-2,-1] +BACKWARD_AT_LEAST, 9, => [8,9], [6,7], [4,5], [2,3], [0,1], [-2,-1] +BACKWARD_AT_LEAST, -1, => [-2,-1] +BACKWARD_AT_MOST, , 5 => [8,9], [6,7] +BACKWARD_AT_MOST, , 6 => [8,9], [6,7] +BACKWARD_AT_MOST, , -1 => [8,9], [6,7], [4,5], [2,3], [0,1] +BACKWARD_CLOSED, 7, 3 => [6,7], [4,5] +BACKWARD_CLOSED, 6, 2 => [6,7], [4,5], [2,3] +BACKWARD_CLOSED, 9, 3 => [8,9], [6,7], [4,5] +BACKWARD_CLOSED, 9, -1 => [8,9], [6,7], [4,5], [2,3], [0,1] +BACKWARD_CLOSED_OPEN, 8, 3 => [8,9], [6,7], [4,5] +BACKWARD_CLOSED_OPEN, 7, 2 => [6,7], [4,5] +BACKWARD_CLOSED_OPEN, 9, 3 => [8,9], [6,7], [4,5] +BACKWARD_CLOSED_OPEN, 9, -1 => [8,9], [6,7], [4,5], [2,3], [0,1] +BACKWARD_GREATER_THAN, 6, => [4,5], [2,3], [0,1], [-2,-1] +BACKWARD_GREATER_THAN, 7, => [6,7], [4,5], [2,3], [0,1], [-2,-1] +BACKWARD_GREATER_THAN, 9, => [8,9], [6,7], [4,5], [2,3], [0,1], [-2,-1] +BACKWARD_GREATER_THAN, -1, => [-2,-1] +BACKWARD_LESS_THAN, , 5 => [8,9], [6,7] +BACKWARD_LESS_THAN, , 2 => [8,9], [6,7], [4,5] +BACKWARD_LESS_THAN, , -1 => [8,9], [6,7], [4,5], [2,3], [0,1] +BACKWARD_OPEN, 7, 2 => [6,7], [4,5] +BACKWARD_OPEN, 8, 1 => [6,7], [4,5], [2,3] +BACKWARD_OPEN, 9, 4 => [8,9], [6,7] +BACKWARD_OPEN, 9, -1 => [8,9], [6,7], [4,5], [2,3], [0,1] +BACKWARD_OPEN_CLOSED, 7, 2 => [6,7], [4,5], [2,3] +BACKWARD_OPEN_CLOSED, 8, 4 => [6,7], [4,5] +BACKWARD_OPEN_CLOSED, 9, 4 => [8,9], [6,7], [4,5] +BACKWARD_OPEN_CLOSED, 9, -1 => [8,9], [6,7], [4,5], [2,3], [0,1] diff --git a/src/test/resources/CursorIterableRangeTest/testSignedComparatorDupsort.actual b/src/test/resources/CursorIterableRangeTest/testSignedComparatorDupsort.actual new file mode 100644 index 00000000..9f7a6709 --- /dev/null +++ b/src/test/resources/CursorIterableRangeTest/testSignedComparatorDupsort.actual @@ -0,0 +1,49 @@ +FORWARD_ALL, , => [-2,0], [-2,-1], [0,1], [0,2], [2,3], [2,4], [4,5], [4,6], [6,7], [6,8], [8,9], [8,10] +FORWARD_AT_LEAST, 5, => [6,7], [6,8], [8,9], [8,10] +FORWARD_AT_LEAST, 6, => [6,7], [6,8], [8,9], [8,10] +FORWARD_AT_MOST, , 5 => [-2,0], [-2,-1], [0,1], [0,2], [2,3], [2,4], [4,5], [4,6] +FORWARD_AT_MOST, , 6 => [-2,0], [-2,-1], [0,1], [0,2], [2,3], [2,4], [4,5], [4,6], [6,7], [6,8] +FORWARD_CLOSED, 3, 7 => [4,5], [4,6], [6,7], [6,8] +FORWARD_CLOSED, 2, 6 => [2,3], [2,4], [4,5], [4,6], [6,7], [6,8] +FORWARD_CLOSED, 1, 7 => [2,3], [2,4], [4,5], [4,6], [6,7], [6,8] +FORWARD_CLOSED_OPEN, 3, 8 => [4,5], [4,6], [6,7], [6,8] +FORWARD_CLOSED_OPEN, 2, 6 => [2,3], [2,4], [4,5], [4,6] +FORWARD_GREATER_THAN, 4, => [6,7], [6,8], [8,9], [8,10] +FORWARD_GREATER_THAN, 3, => [4,5], [4,6], [6,7], [6,8], [8,9], [8,10] +FORWARD_LESS_THAN, , 5 => [-2,0], [-2,-1], [0,1], [0,2], [2,3], [2,4], [4,5], [4,6] +FORWARD_LESS_THAN, , 8 => [-2,0], [-2,-1], [0,1], [0,2], [2,3], [2,4], [4,5], [4,6], [6,7], [6,8] +FORWARD_OPEN, 3, 7 => [4,5], [4,6], [6,7], [6,8] +FORWARD_OPEN, 2, 8 => [4,5], [4,6], [6,7], [6,8] +FORWARD_OPEN_CLOSED, 3, 8 => [4,5], [4,6], [6,7], [6,8], [8,9], [8,10] +FORWARD_OPEN_CLOSED, 2, 6 => [4,5], [4,6], [6,7], [6,8] +BACKWARD_ALL, , => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1], [-2,-1], [-2,0] +BACKWARD_AT_LEAST, 5, => [4,6], [4,5], [2,4], [2,3], [0,2], [0,1], [-2,-1], [-2,0] +BACKWARD_AT_LEAST, 6, => [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1], [-2,-1], [-2,0] +BACKWARD_AT_LEAST, 9, => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1], [-2,-1], [-2,0] +BACKWARD_AT_LEAST, -1, => [-2,-1], [-2,0] +BACKWARD_AT_MOST, , 5 => [8,10], [8,9], [6,8], [6,7] +BACKWARD_AT_MOST, , 6 => [8,10], [8,9], [6,8], [6,7] +BACKWARD_AT_MOST, , -1 => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] +BACKWARD_CLOSED, 7, 3 => [6,8], [6,7], [4,6], [4,5] +BACKWARD_CLOSED, 6, 2 => [6,7], [4,6], [4,5], [2,4], [2,3] +BACKWARD_CLOSED, 9, 3 => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5] +BACKWARD_CLOSED, 9, -1 => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] +BACKWARD_CLOSED_OPEN, 8, 3 => [8,9], [6,8], [6,7], [4,6], [4,5] +BACKWARD_CLOSED_OPEN, 7, 2 => [6,8], [6,7], [4,6], [4,5] +BACKWARD_CLOSED_OPEN, 9, 3 => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5] +BACKWARD_CLOSED_OPEN, 9, -1 => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] +BACKWARD_GREATER_THAN, 6, => [4,6], [4,5], [2,4], [2,3], [0,2], [0,1], [-2,-1], [-2,0] +BACKWARD_GREATER_THAN, 7, => [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1], [-2,-1], [-2,0] +BACKWARD_GREATER_THAN, 9, => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1], [-2,-1], [-2,0] +BACKWARD_GREATER_THAN, -1, => [-2,-1], [-2,0] +BACKWARD_LESS_THAN, , 5 => [8,10], [8,9], [6,8], [6,7] +BACKWARD_LESS_THAN, , 2 => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5] +BACKWARD_LESS_THAN, , -1 => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] +BACKWARD_OPEN, 7, 2 => [6,8], [6,7], [4,6], [4,5] +BACKWARD_OPEN, 8, 1 => [6,8], [6,7], [4,6], [4,5], [2,4], [2,3] +BACKWARD_OPEN, 9, 4 => [8,10], [8,9], [6,8], [6,7] +BACKWARD_OPEN, 9, -1 => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] +BACKWARD_OPEN_CLOSED, 7, 2 => [6,8], [6,7], [4,6], [4,5], [2,4], [2,3] +BACKWARD_OPEN_CLOSED, 8, 4 => [6,8], [6,7], [4,6], [4,5] +BACKWARD_OPEN_CLOSED, 9, 4 => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5] +BACKWARD_OPEN_CLOSED, 9, -1 => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] diff --git a/src/test/resources/CursorIterableRangeTest/testSignedComparatorDupsort.expected b/src/test/resources/CursorIterableRangeTest/testSignedComparatorDupsort.expected new file mode 100644 index 00000000..9f7a6709 --- /dev/null +++ b/src/test/resources/CursorIterableRangeTest/testSignedComparatorDupsort.expected @@ -0,0 +1,49 @@ +FORWARD_ALL, , => [-2,0], [-2,-1], [0,1], [0,2], [2,3], [2,4], [4,5], [4,6], [6,7], [6,8], [8,9], [8,10] +FORWARD_AT_LEAST, 5, => [6,7], [6,8], [8,9], [8,10] +FORWARD_AT_LEAST, 6, => [6,7], [6,8], [8,9], [8,10] +FORWARD_AT_MOST, , 5 => [-2,0], [-2,-1], [0,1], [0,2], [2,3], [2,4], [4,5], [4,6] +FORWARD_AT_MOST, , 6 => [-2,0], [-2,-1], [0,1], [0,2], [2,3], [2,4], [4,5], [4,6], [6,7], [6,8] +FORWARD_CLOSED, 3, 7 => [4,5], [4,6], [6,7], [6,8] +FORWARD_CLOSED, 2, 6 => [2,3], [2,4], [4,5], [4,6], [6,7], [6,8] +FORWARD_CLOSED, 1, 7 => [2,3], [2,4], [4,5], [4,6], [6,7], [6,8] +FORWARD_CLOSED_OPEN, 3, 8 => [4,5], [4,6], [6,7], [6,8] +FORWARD_CLOSED_OPEN, 2, 6 => [2,3], [2,4], [4,5], [4,6] +FORWARD_GREATER_THAN, 4, => [6,7], [6,8], [8,9], [8,10] +FORWARD_GREATER_THAN, 3, => [4,5], [4,6], [6,7], [6,8], [8,9], [8,10] +FORWARD_LESS_THAN, , 5 => [-2,0], [-2,-1], [0,1], [0,2], [2,3], [2,4], [4,5], [4,6] +FORWARD_LESS_THAN, , 8 => [-2,0], [-2,-1], [0,1], [0,2], [2,3], [2,4], [4,5], [4,6], [6,7], [6,8] +FORWARD_OPEN, 3, 7 => [4,5], [4,6], [6,7], [6,8] +FORWARD_OPEN, 2, 8 => [4,5], [4,6], [6,7], [6,8] +FORWARD_OPEN_CLOSED, 3, 8 => [4,5], [4,6], [6,7], [6,8], [8,9], [8,10] +FORWARD_OPEN_CLOSED, 2, 6 => [4,5], [4,6], [6,7], [6,8] +BACKWARD_ALL, , => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1], [-2,-1], [-2,0] +BACKWARD_AT_LEAST, 5, => [4,6], [4,5], [2,4], [2,3], [0,2], [0,1], [-2,-1], [-2,0] +BACKWARD_AT_LEAST, 6, => [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1], [-2,-1], [-2,0] +BACKWARD_AT_LEAST, 9, => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1], [-2,-1], [-2,0] +BACKWARD_AT_LEAST, -1, => [-2,-1], [-2,0] +BACKWARD_AT_MOST, , 5 => [8,10], [8,9], [6,8], [6,7] +BACKWARD_AT_MOST, , 6 => [8,10], [8,9], [6,8], [6,7] +BACKWARD_AT_MOST, , -1 => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] +BACKWARD_CLOSED, 7, 3 => [6,8], [6,7], [4,6], [4,5] +BACKWARD_CLOSED, 6, 2 => [6,7], [4,6], [4,5], [2,4], [2,3] +BACKWARD_CLOSED, 9, 3 => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5] +BACKWARD_CLOSED, 9, -1 => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] +BACKWARD_CLOSED_OPEN, 8, 3 => [8,9], [6,8], [6,7], [4,6], [4,5] +BACKWARD_CLOSED_OPEN, 7, 2 => [6,8], [6,7], [4,6], [4,5] +BACKWARD_CLOSED_OPEN, 9, 3 => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5] +BACKWARD_CLOSED_OPEN, 9, -1 => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] +BACKWARD_GREATER_THAN, 6, => [4,6], [4,5], [2,4], [2,3], [0,2], [0,1], [-2,-1], [-2,0] +BACKWARD_GREATER_THAN, 7, => [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1], [-2,-1], [-2,0] +BACKWARD_GREATER_THAN, 9, => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1], [-2,-1], [-2,0] +BACKWARD_GREATER_THAN, -1, => [-2,-1], [-2,0] +BACKWARD_LESS_THAN, , 5 => [8,10], [8,9], [6,8], [6,7] +BACKWARD_LESS_THAN, , 2 => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5] +BACKWARD_LESS_THAN, , -1 => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] +BACKWARD_OPEN, 7, 2 => [6,8], [6,7], [4,6], [4,5] +BACKWARD_OPEN, 8, 1 => [6,8], [6,7], [4,6], [4,5], [2,4], [2,3] +BACKWARD_OPEN, 9, 4 => [8,10], [8,9], [6,8], [6,7] +BACKWARD_OPEN, 9, -1 => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] +BACKWARD_OPEN_CLOSED, 7, 2 => [6,8], [6,7], [4,6], [4,5], [2,4], [2,3] +BACKWARD_OPEN_CLOSED, 8, 4 => [6,8], [6,7], [4,6], [4,5] +BACKWARD_OPEN_CLOSED, 9, 4 => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5] +BACKWARD_OPEN_CLOSED, 9, -1 => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] diff --git a/src/test/resources/CursorIterableRangeTest/testUnsignedComparator.actual b/src/test/resources/CursorIterableRangeTest/testUnsignedComparator.actual new file mode 100644 index 00000000..1be8d951 --- /dev/null +++ b/src/test/resources/CursorIterableRangeTest/testUnsignedComparator.actual @@ -0,0 +1,49 @@ +FORWARD_ALL, , => [0,1], [2,3], [4,5], [6,7], [8,9], [-2,-1] +FORWARD_AT_LEAST, 5, => [6,7], [8,9], [-2,-1] +FORWARD_AT_LEAST, 6, => [6,7], [8,9], [-2,-1] +FORWARD_AT_MOST, , 5 => [0,1], [2,3], [4,5] +FORWARD_AT_MOST, , 6 => [0,1], [2,3], [4,5], [6,7] +FORWARD_CLOSED, 3, 7 => [4,5], [6,7] +FORWARD_CLOSED, 2, 6 => [2,3], [4,5], [6,7] +FORWARD_CLOSED, 1, 7 => [2,3], [4,5], [6,7] +FORWARD_CLOSED_OPEN, 3, 8 => [4,5], [6,7] +FORWARD_CLOSED_OPEN, 2, 6 => [2,3], [4,5] +FORWARD_GREATER_THAN, 4, => [6,7], [8,9], [-2,-1] +FORWARD_GREATER_THAN, 3, => [4,5], [6,7], [8,9], [-2,-1] +FORWARD_LESS_THAN, , 5 => [0,1], [2,3], [4,5] +FORWARD_LESS_THAN, , 8 => [0,1], [2,3], [4,5], [6,7] +FORWARD_OPEN, 3, 7 => [4,5], [6,7] +FORWARD_OPEN, 2, 8 => [4,5], [6,7] +FORWARD_OPEN_CLOSED, 3, 8 => [4,5], [6,7], [8,9] +FORWARD_OPEN_CLOSED, 2, 6 => [4,5], [6,7] +BACKWARD_ALL, , => [-2,-1], [8,9], [6,7], [4,5], [2,3], [0,1] +BACKWARD_AT_LEAST, 5, => [4,5], [2,3], [0,1] +BACKWARD_AT_LEAST, 6, => [6,7], [4,5], [2,3], [0,1] +BACKWARD_AT_LEAST, 9, => [8,9], [6,7], [4,5], [2,3], [0,1] +BACKWARD_AT_LEAST, -1, => [-2,-1], [8,9], [6,7], [4,5], [2,3], [0,1] +BACKWARD_AT_MOST, , 5 => [-2,-1], [8,9], [6,7] +BACKWARD_AT_MOST, , 6 => [-2,-1], [8,9], [6,7] +BACKWARD_AT_MOST, , -1 => +BACKWARD_CLOSED, 7, 3 => [6,7], [4,5] +BACKWARD_CLOSED, 6, 2 => [6,7], [4,5], [2,3] +BACKWARD_CLOSED, 9, 3 => [8,9], [6,7], [4,5] +BACKWARD_CLOSED, 9, -1 => +BACKWARD_CLOSED_OPEN, 8, 3 => [8,9], [6,7], [4,5] +BACKWARD_CLOSED_OPEN, 7, 2 => [6,7], [4,5] +BACKWARD_CLOSED_OPEN, 9, 3 => [8,9], [6,7], [4,5] +BACKWARD_CLOSED_OPEN, 9, -1 => +BACKWARD_GREATER_THAN, 6, => [4,5], [2,3], [0,1] +BACKWARD_GREATER_THAN, 7, => [6,7], [4,5], [2,3], [0,1] +BACKWARD_GREATER_THAN, 9, => [8,9], [6,7], [4,5], [2,3], [0,1] +BACKWARD_GREATER_THAN, -1, => [-2,-1], [8,9], [6,7], [4,5], [2,3], [0,1] +BACKWARD_LESS_THAN, , 5 => [-2,-1], [8,9], [6,7] +BACKWARD_LESS_THAN, , 2 => [-2,-1], [8,9], [6,7], [4,5] +BACKWARD_LESS_THAN, , -1 => +BACKWARD_OPEN, 7, 2 => [6,7], [4,5] +BACKWARD_OPEN, 8, 1 => [6,7], [4,5], [2,3] +BACKWARD_OPEN, 9, 4 => [8,9], [6,7] +BACKWARD_OPEN, 9, -1 => +BACKWARD_OPEN_CLOSED, 7, 2 => [6,7], [4,5], [2,3] +BACKWARD_OPEN_CLOSED, 8, 4 => [6,7], [4,5] +BACKWARD_OPEN_CLOSED, 9, 4 => [8,9], [6,7], [4,5] +BACKWARD_OPEN_CLOSED, 9, -1 => diff --git a/src/test/resources/CursorIterableRangeTest/testUnsignedComparator.expected b/src/test/resources/CursorIterableRangeTest/testUnsignedComparator.expected new file mode 100644 index 00000000..1be8d951 --- /dev/null +++ b/src/test/resources/CursorIterableRangeTest/testUnsignedComparator.expected @@ -0,0 +1,49 @@ +FORWARD_ALL, , => [0,1], [2,3], [4,5], [6,7], [8,9], [-2,-1] +FORWARD_AT_LEAST, 5, => [6,7], [8,9], [-2,-1] +FORWARD_AT_LEAST, 6, => [6,7], [8,9], [-2,-1] +FORWARD_AT_MOST, , 5 => [0,1], [2,3], [4,5] +FORWARD_AT_MOST, , 6 => [0,1], [2,3], [4,5], [6,7] +FORWARD_CLOSED, 3, 7 => [4,5], [6,7] +FORWARD_CLOSED, 2, 6 => [2,3], [4,5], [6,7] +FORWARD_CLOSED, 1, 7 => [2,3], [4,5], [6,7] +FORWARD_CLOSED_OPEN, 3, 8 => [4,5], [6,7] +FORWARD_CLOSED_OPEN, 2, 6 => [2,3], [4,5] +FORWARD_GREATER_THAN, 4, => [6,7], [8,9], [-2,-1] +FORWARD_GREATER_THAN, 3, => [4,5], [6,7], [8,9], [-2,-1] +FORWARD_LESS_THAN, , 5 => [0,1], [2,3], [4,5] +FORWARD_LESS_THAN, , 8 => [0,1], [2,3], [4,5], [6,7] +FORWARD_OPEN, 3, 7 => [4,5], [6,7] +FORWARD_OPEN, 2, 8 => [4,5], [6,7] +FORWARD_OPEN_CLOSED, 3, 8 => [4,5], [6,7], [8,9] +FORWARD_OPEN_CLOSED, 2, 6 => [4,5], [6,7] +BACKWARD_ALL, , => [-2,-1], [8,9], [6,7], [4,5], [2,3], [0,1] +BACKWARD_AT_LEAST, 5, => [4,5], [2,3], [0,1] +BACKWARD_AT_LEAST, 6, => [6,7], [4,5], [2,3], [0,1] +BACKWARD_AT_LEAST, 9, => [8,9], [6,7], [4,5], [2,3], [0,1] +BACKWARD_AT_LEAST, -1, => [-2,-1], [8,9], [6,7], [4,5], [2,3], [0,1] +BACKWARD_AT_MOST, , 5 => [-2,-1], [8,9], [6,7] +BACKWARD_AT_MOST, , 6 => [-2,-1], [8,9], [6,7] +BACKWARD_AT_MOST, , -1 => +BACKWARD_CLOSED, 7, 3 => [6,7], [4,5] +BACKWARD_CLOSED, 6, 2 => [6,7], [4,5], [2,3] +BACKWARD_CLOSED, 9, 3 => [8,9], [6,7], [4,5] +BACKWARD_CLOSED, 9, -1 => +BACKWARD_CLOSED_OPEN, 8, 3 => [8,9], [6,7], [4,5] +BACKWARD_CLOSED_OPEN, 7, 2 => [6,7], [4,5] +BACKWARD_CLOSED_OPEN, 9, 3 => [8,9], [6,7], [4,5] +BACKWARD_CLOSED_OPEN, 9, -1 => +BACKWARD_GREATER_THAN, 6, => [4,5], [2,3], [0,1] +BACKWARD_GREATER_THAN, 7, => [6,7], [4,5], [2,3], [0,1] +BACKWARD_GREATER_THAN, 9, => [8,9], [6,7], [4,5], [2,3], [0,1] +BACKWARD_GREATER_THAN, -1, => [-2,-1], [8,9], [6,7], [4,5], [2,3], [0,1] +BACKWARD_LESS_THAN, , 5 => [-2,-1], [8,9], [6,7] +BACKWARD_LESS_THAN, , 2 => [-2,-1], [8,9], [6,7], [4,5] +BACKWARD_LESS_THAN, , -1 => +BACKWARD_OPEN, 7, 2 => [6,7], [4,5] +BACKWARD_OPEN, 8, 1 => [6,7], [4,5], [2,3] +BACKWARD_OPEN, 9, 4 => [8,9], [6,7] +BACKWARD_OPEN, 9, -1 => +BACKWARD_OPEN_CLOSED, 7, 2 => [6,7], [4,5], [2,3] +BACKWARD_OPEN_CLOSED, 8, 4 => [6,7], [4,5] +BACKWARD_OPEN_CLOSED, 9, 4 => [8,9], [6,7], [4,5] +BACKWARD_OPEN_CLOSED, 9, -1 => diff --git a/src/test/resources/CursorIterableRangeTest/testUnsignedComparatorDupsort.actual b/src/test/resources/CursorIterableRangeTest/testUnsignedComparatorDupsort.actual new file mode 100644 index 00000000..15dcb9f9 --- /dev/null +++ b/src/test/resources/CursorIterableRangeTest/testUnsignedComparatorDupsort.actual @@ -0,0 +1,49 @@ +FORWARD_ALL, , => [0,1], [0,2], [2,3], [2,4], [4,5], [4,6], [6,7], [6,8], [8,9], [8,10], [-2,0], [-2,-1] +FORWARD_AT_LEAST, 5, => [6,7], [6,8], [8,9], [8,10], [-2,0], [-2,-1] +FORWARD_AT_LEAST, 6, => [6,7], [6,8], [8,9], [8,10], [-2,0], [-2,-1] +FORWARD_AT_MOST, , 5 => [0,1], [0,2], [2,3], [2,4], [4,5], [4,6] +FORWARD_AT_MOST, , 6 => [0,1], [0,2], [2,3], [2,4], [4,5], [4,6], [6,7], [6,8] +FORWARD_CLOSED, 3, 7 => [4,5], [4,6], [6,7], [6,8] +FORWARD_CLOSED, 2, 6 => [2,3], [2,4], [4,5], [4,6], [6,7], [6,8] +FORWARD_CLOSED, 1, 7 => [2,3], [2,4], [4,5], [4,6], [6,7], [6,8] +FORWARD_CLOSED_OPEN, 3, 8 => [4,5], [4,6], [6,7], [6,8] +FORWARD_CLOSED_OPEN, 2, 6 => [2,3], [2,4], [4,5], [4,6] +FORWARD_GREATER_THAN, 4, => [6,7], [6,8], [8,9], [8,10], [-2,0], [-2,-1] +FORWARD_GREATER_THAN, 3, => [4,5], [4,6], [6,7], [6,8], [8,9], [8,10], [-2,0], [-2,-1] +FORWARD_LESS_THAN, , 5 => [0,1], [0,2], [2,3], [2,4], [4,5], [4,6] +FORWARD_LESS_THAN, , 8 => [0,1], [0,2], [2,3], [2,4], [4,5], [4,6], [6,7], [6,8] +FORWARD_OPEN, 3, 7 => [4,5], [4,6], [6,7], [6,8] +FORWARD_OPEN, 2, 8 => [4,5], [4,6], [6,7], [6,8] +FORWARD_OPEN_CLOSED, 3, 8 => [4,5], [4,6], [6,7], [6,8], [8,9], [8,10] +FORWARD_OPEN_CLOSED, 2, 6 => [4,5], [4,6], [6,7], [6,8] +BACKWARD_ALL, , => [-2,-1], [-2,0], [8,10], [8,9], [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] +BACKWARD_AT_LEAST, 5, => [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] +BACKWARD_AT_LEAST, 6, => [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] +BACKWARD_AT_LEAST, 9, => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] +BACKWARD_AT_LEAST, -1, => [-2,-1], [-2,0], [8,10], [8,9], [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] +BACKWARD_AT_MOST, , 5 => [-2,-1], [-2,0], [8,10], [8,9], [6,8], [6,7] +BACKWARD_AT_MOST, , 6 => [-2,-1], [-2,0], [8,10], [8,9], [6,8], [6,7] +BACKWARD_AT_MOST, , -1 => +BACKWARD_CLOSED, 7, 3 => [6,8], [6,7], [4,6], [4,5] +BACKWARD_CLOSED, 6, 2 => [6,7], [4,6], [4,5], [2,4], [2,3] +BACKWARD_CLOSED, 9, 3 => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5] +BACKWARD_CLOSED, 9, -1 => +BACKWARD_CLOSED_OPEN, 8, 3 => [8,9], [6,8], [6,7], [4,6], [4,5] +BACKWARD_CLOSED_OPEN, 7, 2 => [6,8], [6,7], [4,6], [4,5] +BACKWARD_CLOSED_OPEN, 9, 3 => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5] +BACKWARD_CLOSED_OPEN, 9, -1 => +BACKWARD_GREATER_THAN, 6, => [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] +BACKWARD_GREATER_THAN, 7, => [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] +BACKWARD_GREATER_THAN, 9, => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] +BACKWARD_GREATER_THAN, -1, => [-2,-1], [-2,0], [8,10], [8,9], [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] +BACKWARD_LESS_THAN, , 5 => [-2,-1], [-2,0], [8,10], [8,9], [6,8], [6,7] +BACKWARD_LESS_THAN, , 2 => [-2,-1], [-2,0], [8,10], [8,9], [6,8], [6,7], [4,6], [4,5] +BACKWARD_LESS_THAN, , -1 => +BACKWARD_OPEN, 7, 2 => [6,8], [6,7], [4,6], [4,5] +BACKWARD_OPEN, 8, 1 => [6,8], [6,7], [4,6], [4,5], [2,4], [2,3] +BACKWARD_OPEN, 9, 4 => [8,10], [8,9], [6,8], [6,7] +BACKWARD_OPEN, 9, -1 => +BACKWARD_OPEN_CLOSED, 7, 2 => [6,8], [6,7], [4,6], [4,5], [2,4], [2,3] +BACKWARD_OPEN_CLOSED, 8, 4 => [6,8], [6,7], [4,6], [4,5] +BACKWARD_OPEN_CLOSED, 9, 4 => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5] +BACKWARD_OPEN_CLOSED, 9, -1 => diff --git a/src/test/resources/CursorIterableRangeTest/testUnsignedComparatorDupsort.expected b/src/test/resources/CursorIterableRangeTest/testUnsignedComparatorDupsort.expected new file mode 100644 index 00000000..15dcb9f9 --- /dev/null +++ b/src/test/resources/CursorIterableRangeTest/testUnsignedComparatorDupsort.expected @@ -0,0 +1,49 @@ +FORWARD_ALL, , => [0,1], [0,2], [2,3], [2,4], [4,5], [4,6], [6,7], [6,8], [8,9], [8,10], [-2,0], [-2,-1] +FORWARD_AT_LEAST, 5, => [6,7], [6,8], [8,9], [8,10], [-2,0], [-2,-1] +FORWARD_AT_LEAST, 6, => [6,7], [6,8], [8,9], [8,10], [-2,0], [-2,-1] +FORWARD_AT_MOST, , 5 => [0,1], [0,2], [2,3], [2,4], [4,5], [4,6] +FORWARD_AT_MOST, , 6 => [0,1], [0,2], [2,3], [2,4], [4,5], [4,6], [6,7], [6,8] +FORWARD_CLOSED, 3, 7 => [4,5], [4,6], [6,7], [6,8] +FORWARD_CLOSED, 2, 6 => [2,3], [2,4], [4,5], [4,6], [6,7], [6,8] +FORWARD_CLOSED, 1, 7 => [2,3], [2,4], [4,5], [4,6], [6,7], [6,8] +FORWARD_CLOSED_OPEN, 3, 8 => [4,5], [4,6], [6,7], [6,8] +FORWARD_CLOSED_OPEN, 2, 6 => [2,3], [2,4], [4,5], [4,6] +FORWARD_GREATER_THAN, 4, => [6,7], [6,8], [8,9], [8,10], [-2,0], [-2,-1] +FORWARD_GREATER_THAN, 3, => [4,5], [4,6], [6,7], [6,8], [8,9], [8,10], [-2,0], [-2,-1] +FORWARD_LESS_THAN, , 5 => [0,1], [0,2], [2,3], [2,4], [4,5], [4,6] +FORWARD_LESS_THAN, , 8 => [0,1], [0,2], [2,3], [2,4], [4,5], [4,6], [6,7], [6,8] +FORWARD_OPEN, 3, 7 => [4,5], [4,6], [6,7], [6,8] +FORWARD_OPEN, 2, 8 => [4,5], [4,6], [6,7], [6,8] +FORWARD_OPEN_CLOSED, 3, 8 => [4,5], [4,6], [6,7], [6,8], [8,9], [8,10] +FORWARD_OPEN_CLOSED, 2, 6 => [4,5], [4,6], [6,7], [6,8] +BACKWARD_ALL, , => [-2,-1], [-2,0], [8,10], [8,9], [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] +BACKWARD_AT_LEAST, 5, => [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] +BACKWARD_AT_LEAST, 6, => [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] +BACKWARD_AT_LEAST, 9, => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] +BACKWARD_AT_LEAST, -1, => [-2,-1], [-2,0], [8,10], [8,9], [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] +BACKWARD_AT_MOST, , 5 => [-2,-1], [-2,0], [8,10], [8,9], [6,8], [6,7] +BACKWARD_AT_MOST, , 6 => [-2,-1], [-2,0], [8,10], [8,9], [6,8], [6,7] +BACKWARD_AT_MOST, , -1 => +BACKWARD_CLOSED, 7, 3 => [6,8], [6,7], [4,6], [4,5] +BACKWARD_CLOSED, 6, 2 => [6,7], [4,6], [4,5], [2,4], [2,3] +BACKWARD_CLOSED, 9, 3 => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5] +BACKWARD_CLOSED, 9, -1 => +BACKWARD_CLOSED_OPEN, 8, 3 => [8,9], [6,8], [6,7], [4,6], [4,5] +BACKWARD_CLOSED_OPEN, 7, 2 => [6,8], [6,7], [4,6], [4,5] +BACKWARD_CLOSED_OPEN, 9, 3 => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5] +BACKWARD_CLOSED_OPEN, 9, -1 => +BACKWARD_GREATER_THAN, 6, => [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] +BACKWARD_GREATER_THAN, 7, => [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] +BACKWARD_GREATER_THAN, 9, => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] +BACKWARD_GREATER_THAN, -1, => [-2,-1], [-2,0], [8,10], [8,9], [6,8], [6,7], [4,6], [4,5], [2,4], [2,3], [0,2], [0,1] +BACKWARD_LESS_THAN, , 5 => [-2,-1], [-2,0], [8,10], [8,9], [6,8], [6,7] +BACKWARD_LESS_THAN, , 2 => [-2,-1], [-2,0], [8,10], [8,9], [6,8], [6,7], [4,6], [4,5] +BACKWARD_LESS_THAN, , -1 => +BACKWARD_OPEN, 7, 2 => [6,8], [6,7], [4,6], [4,5] +BACKWARD_OPEN, 8, 1 => [6,8], [6,7], [4,6], [4,5], [2,4], [2,3] +BACKWARD_OPEN, 9, 4 => [8,10], [8,9], [6,8], [6,7] +BACKWARD_OPEN, 9, -1 => +BACKWARD_OPEN_CLOSED, 7, 2 => [6,8], [6,7], [4,6], [4,5], [2,4], [2,3] +BACKWARD_OPEN_CLOSED, 8, 4 => [6,8], [6,7], [4,6], [4,5] +BACKWARD_OPEN_CLOSED, 9, 4 => [8,10], [8,9], [6,8], [6,7], [4,6], [4,5] +BACKWARD_OPEN_CLOSED, 9, -1 => diff --git a/src/test/resources/CursorIterableRangeTest/tests.csv b/src/test/resources/CursorIterableRangeTest/tests.csv new file mode 100644 index 00000000..0e103152 --- /dev/null +++ b/src/test/resources/CursorIterableRangeTest/tests.csv @@ -0,0 +1,49 @@ +FORWARD_ALL, , +FORWARD_AT_LEAST, 5, +FORWARD_AT_LEAST, 6, +FORWARD_AT_MOST, , 5 +FORWARD_AT_MOST, , 6 +FORWARD_CLOSED, 3, 7 +FORWARD_CLOSED, 2, 6 +FORWARD_CLOSED, 1, 7 +FORWARD_CLOSED_OPEN, 3, 8 +FORWARD_CLOSED_OPEN, 2, 6 +FORWARD_GREATER_THAN, 4, +FORWARD_GREATER_THAN, 3, +FORWARD_LESS_THAN, , 5 +FORWARD_LESS_THAN, , 8 +FORWARD_OPEN, 3, 7 +FORWARD_OPEN, 2, 8 +FORWARD_OPEN_CLOSED, 3, 8 +FORWARD_OPEN_CLOSED, 2, 6 +BACKWARD_ALL, , +BACKWARD_AT_LEAST, 5, +BACKWARD_AT_LEAST, 6, +BACKWARD_AT_LEAST, 9, +BACKWARD_AT_LEAST, -1, +BACKWARD_AT_MOST, , 5 +BACKWARD_AT_MOST, , 6 +BACKWARD_AT_MOST, , -1 +BACKWARD_CLOSED, 7, 3 +BACKWARD_CLOSED, 6, 2 +BACKWARD_CLOSED, 9, 3 +BACKWARD_CLOSED, 9, -1 +BACKWARD_CLOSED_OPEN, 8, 3 +BACKWARD_CLOSED_OPEN, 7, 2 +BACKWARD_CLOSED_OPEN, 9, 3 +BACKWARD_CLOSED_OPEN, 9, -1 +BACKWARD_GREATER_THAN, 6, +BACKWARD_GREATER_THAN, 7, +BACKWARD_GREATER_THAN, 9, +BACKWARD_GREATER_THAN, -1, +BACKWARD_LESS_THAN, , 5 +BACKWARD_LESS_THAN, , 2 +BACKWARD_LESS_THAN, , -1 +BACKWARD_OPEN, 7, 2 +BACKWARD_OPEN, 8, 1 +BACKWARD_OPEN, 9, 4 +BACKWARD_OPEN, 9, -1 +BACKWARD_OPEN_CLOSED, 7, 2 +BACKWARD_OPEN_CLOSED, 8, 4 +BACKWARD_OPEN_CLOSED, 9, 4 +BACKWARD_OPEN_CLOSED, 9, -1 \ No newline at end of file From 0234d323244f874ccf12945949e6352879c5a3b3 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Tue, 28 Oct 2025 16:34:03 +0000 Subject: [PATCH 13/90] Replace get(Uns|S)ignedComparator with getComparator(DbiFlagSet) Also improve javadoc and refactor some tests to use DbiBuilder. Some tests are failing. --- src/main/java/org/lmdbjava/BufferProxy.java | 45 +++- .../java/org/lmdbjava/ByteArrayProxy.java | 17 +- src/main/java/org/lmdbjava/ByteBufProxy.java | 15 +- .../java/org/lmdbjava/ByteBufferProxy.java | 46 +++- .../java/org/lmdbjava/CursorIterable.java | 4 +- src/main/java/org/lmdbjava/Dbi.java | 7 +- src/main/java/org/lmdbjava/DbiBuilder.java | 196 +++++++++++++----- src/main/java/org/lmdbjava/DbiFlags.java | 20 +- .../java/org/lmdbjava/DirectBufferProxy.java | 17 +- src/main/java/org/lmdbjava/Env.java | 20 +- src/main/java/org/lmdbjava/Txn.java | 2 +- src/main/java/org/lmdbjava/TxnFlags.java | 1 + .../java/org/lmdbjava/ComparatorTest.java | 10 +- .../CursorIterableIntegerKeyTest.java | 121 ++++++----- .../org/lmdbjava/CursorIterablePerfTest.java | 22 +- .../java/org/lmdbjava/CursorIterableTest.java | 111 +++++----- src/test/java/org/lmdbjava/DbiTest.java | 8 +- .../java/org/lmdbjava/TestDbiBuilder.java | 6 +- 18 files changed, 448 insertions(+), 220 deletions(-) diff --git a/src/main/java/org/lmdbjava/BufferProxy.java b/src/main/java/org/lmdbjava/BufferProxy.java index 2ff5a8fc..60272209 100644 --- a/src/main/java/org/lmdbjava/BufferProxy.java +++ b/src/main/java/org/lmdbjava/BufferProxy.java @@ -66,26 +66,49 @@ protected BufferProxy() {} protected abstract byte[] getBytes(T buffer); /** - * Get a suitable default {@link Comparator} to compare numeric key values as signed. + * Get a suitable default {@link Comparator} given the provided flags. * - *

Note: LMDB's default comparator is unsigned so if this is used only for the {@link - * CursorIterable} start/stop key comparisons then its behaviour will differ from the iteration - * order. Use with caution. + *

The provided comparator must strictly match the lexicographical order of keys in the native + * LMDB database. * + * @param dbiFlagSet The {@link DbiFlags} set for the database. * @return a comparator that can be used (never null) */ - public abstract Comparator getSignedComparator(); + public abstract Comparator getComparator(final DbiFlagSet dbiFlagSet); /** - * Get a suitable default {@link Comparator} to compare numeric key values as unsigned. - *

- * This should match the behaviour of the LMDB's mdb_cmp comparator as it may be used for - * {@link CursorIterable} start/stop keys comparisons, which must match LMDB's insertion order. - *

+ * Get a suitable default {@link Comparator} + * + *

The provided comparator must strictly match the lexicographical order of keys in the native + * LMDB database. * * @return a comparator that can be used (never null) */ - public abstract Comparator getUnsignedComparator(); + public Comparator getComparator() { + return getComparator(DbiFlagSet.empty()); + } + +// /** +// * Get a suitable default {@link Comparator} to compare numeric key values as signed. +// * +// *

Note: LMDB's default comparator is unsigned so if this is used only for the {@link +// * CursorIterable} start/stop key comparisons then its behaviour will differ from the iteration +// * order. Use with caution. +// * +// * @return a comparator that can be used (never null) +// */ +// public abstract Comparator getSignedComparator(); +// +// /** +// * Get a suitable default {@link Comparator} to compare numeric key values as unsigned. +// *

+// * This should match the behaviour of the LMDB's mdb_cmp comparator as it may be used for +// * {@link CursorIterable} start/stop keys comparisons, which must match LMDB's insertion order. +// *

+// * +// * @return a comparator that can be used (never null) +// */ +// public abstract Comparator getUnsignedComparator(final DbiFlagSet dbiFlagSet); /** * Called when the MDB_val should be set to reflect the passed buffer. This buffer diff --git a/src/main/java/org/lmdbjava/ByteArrayProxy.java b/src/main/java/org/lmdbjava/ByteArrayProxy.java index b4fb1e7b..5231ed51 100644 --- a/src/main/java/org/lmdbjava/ByteArrayProxy.java +++ b/src/main/java/org/lmdbjava/ByteArrayProxy.java @@ -104,15 +104,20 @@ protected byte[] getBytes(final byte[] buffer) { } @Override - public Comparator getSignedComparator() { - return signedComparator; - } - - @Override - public Comparator getUnsignedComparator() { + public Comparator getComparator(final DbiFlagSet dbiFlagSet) { return unsignedComparator; } + // @Override +// public Comparator getSignedComparator() { +// return signedComparator; +// } +// +// @Override +// public Comparator getUnsignedComparator() { +// return unsignedComparator; +// } + @Override protected Pointer in(final byte[] buffer, final Pointer ptr) { final Pointer pointer = MEM_MGR.allocateDirect(buffer.length); diff --git a/src/main/java/org/lmdbjava/ByteBufProxy.java b/src/main/java/org/lmdbjava/ByteBufProxy.java index e26bcb07..fc14b58f 100644 --- a/src/main/java/org/lmdbjava/ByteBufProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufProxy.java @@ -114,14 +114,19 @@ protected ByteBuf allocate() { } @Override - public Comparator getSignedComparator() { + public Comparator getComparator(final DbiFlagSet dbiFlagSet) { return comparator; } - @Override - public Comparator getUnsignedComparator() { - return comparator; - } + // @Override +// public Comparator getSignedComparator() { +// return comparator; +// } +// +// @Override +// public Comparator getUnsignedComparator() { +// return comparator; +// } @Override protected void deallocate(final ByteBuf buff) { diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index d9edb6a8..4875572b 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -148,6 +148,35 @@ public static int compareBuff(final ByteBuffer o1, final ByteBuffer o2) { return o1.remaining() - o2.remaining(); } +// /** +// * Possible compareBuff method specifically for 4/8 byte keys when using MDB_INTEGER_KEY +// */ +// public static int compareBuff(final ByteBuffer o1, final ByteBuffer o2) { +// requireNonNull(o1); +// requireNonNull(o2); +// // Both buffers should be same len +// final int len1 = o1.limit(); +// final int len2 = o2.limit(); +// if (len1 != len2) { +// throw new RuntimeException("Length mismatch, len1: " + len1 + ", len2: " + len2 +// + ". Lengths must be identical and either 4 or 8 bytes."); +// } +// final boolean reverse1 = o1.order() == LITTLE_ENDIAN; +// final boolean reverse2 = o2.order() == LITTLE_ENDIAN; +// if (len1 == 8) { +// final long lw = reverse1 ? Long.reverseBytes(o1.getLong()) : o1.getLong(); +// final long rw = reverse2 ? Long.reverseBytes(o2.getLong()) : o2.getLong(); +// return Long.compareUnsigned(lw, rw); +// } else if (len1 == 4) { +// final int lw = reverse1 ? Integer.reverseBytes(o1.getInt()) : o1.getInt(); +// final int rw = reverse2 ? Integer.reverseBytes(o2.getInt()) : o2.getInt(); +// return Integer.compareUnsigned(lw, rw); +// } else { +// throw new RuntimeException("Unexpected length len1: " + len1 + ", len2: " + len2 +// + ". Lengths must be identical and either 4 or 8 bytes."); +// } +// } + static Field findField(final Class c, final String name) { Class clazz = c; do { @@ -182,15 +211,20 @@ protected final ByteBuffer allocate() { } @Override - public Comparator getSignedComparator() { - return signedComparator; - } - - @Override - public Comparator getUnsignedComparator() { + public Comparator getComparator(DbiFlagSet dbiFlagSet) { return unsignedComparator; } + // @Override +// public Comparator getSignedComparator() { +// return signedComparator; +// } +// +// @Override +// public Comparator getUnsignedComparator(final DbiFlagSet dbiFlagSet) { +// return unsignedComparator; +// } + @Override protected final void deallocate(final ByteBuffer buff) { buff.order(BIG_ENDIAN); diff --git a/src/main/java/org/lmdbjava/CursorIterable.java b/src/main/java/org/lmdbjava/CursorIterable.java index dd11a468..69d43fcd 100644 --- a/src/main/java/org/lmdbjava/CursorIterable.java +++ b/src/main/java/org/lmdbjava/CursorIterable.java @@ -60,10 +60,10 @@ public final class CursorIterable implements Iterable(); if (comparator != null) { - // User supplied java-side comparator so use that + // User supplied Java-side comparator so use that this.rangeComparator = new JavaRangeComparator<>(range, comparator, entry::key); } else { - // No java-side comparator so call down to LMDB to do the comparison + // No Java-side comparator, so call down to LMDB to do the comparison this.rangeComparator = new LmdbRangeComparator<>(txn, dbi, cursor, range, proxy); } } diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index 17fa4c46..9cdc03ee 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -51,6 +51,7 @@ public final class Dbi { private final ComparatorCallback ccb; private boolean cleaned; + // Used for CursorIterable KeyRange testing and/or native callbacks private final Comparator comparator; private final Env env; private final byte[] name; @@ -80,6 +81,7 @@ public final class Dbi { ptr = dbiPtr.getPointer(0); if (nativeCb) { requireNonNull(comparator, "comparator cannot be null if nativeCb is set"); + // LMDB will call back to this comparator for insertion/iteration order this.ccb = (keyA, keyB) -> { final T compKeyA = proxy.out(proxy.allocate(), keyA); @@ -465,6 +467,7 @@ private String getNameAsString() { return ""; } else { try { + // Assume a UTF8 encoding as we don't know, thus swallow if it fails return new String(name, StandardCharsets.UTF_8); } catch (Exception e) { return "?"; @@ -475,8 +478,8 @@ private String getNameAsString() { @Override public String toString() { return "Dbi{" + - "name=" + getNameAsString() + - ", dbiFlagSet=" + dbiFlagSet + + "name='" + getNameAsString() + + "', dbiFlagSet=" + dbiFlagSet + '}'; } diff --git a/src/main/java/org/lmdbjava/DbiBuilder.java b/src/main/java/org/lmdbjava/DbiBuilder.java index 7d05a51e..f2f925ce 100644 --- a/src/main/java/org/lmdbjava/DbiBuilder.java +++ b/src/main/java/org/lmdbjava/DbiBuilder.java @@ -98,8 +98,8 @@ public static class DbiBuilderStage2 { private final DbiBuilder dbiBuilder; - private Comparator comparator; - private boolean useNativeCallback; + private java.util.Comparator customComparator; + private ComparatorType comparatorType; private DbiBuilderStage2(final DbiBuilder dbiBuilder) { this.dbiBuilder = dbiBuilder; @@ -107,95 +107,110 @@ private DbiBuilderStage2(final DbiBuilder dbiBuilder) { /** *

- * {@link CursorIterable} will call down to LMDB's {@code mdb_cmp} method when - * comparing entries to start/stop keys. This ensures LmdbJava is comparing start/stop - * keys using the same comparator that is used for insert order into the db. + * This is the default choice when it comes to choosing a comparator. + * If you are not sure of the implications of the other methods then use this one as it + * is likely what you want and also probably the most performant. *

*

- * This option may be slightly less performant than when using - * {@link DbiBuilderStage2#withDefaultIteratorComparator()} as it need to call down - * to LMDB to perform the comparisons, however it guarantees that {@link CursorIterable} - * key comparison matches LMDB key comparison. + * With this option, {@link CursorIterable} will make use of the LmdbJava's default + * Java-side comparators when comparing iteration keys to the start/stop keys. + * LMDB will use its own comparator for controlling insertion order in the database. + * The two comparators are functionally identical. + *

+ *

+ * This option may be slightly more performant than when using + * {@link DbiBuilderStage2#withNativeComparator()} which calls down to LMDB for ALL + * comparison operations. *

*

* If you do not intend to use {@link CursorIterable} then it doesn't matter whether * you choose {@link DbiBuilderStage2#withNativeComparator()}, - * {@link DbiBuilderStage2#withDefaultIteratorComparator()} or + * {@link DbiBuilderStage2#withDefaultComparator()} or * {@link DbiBuilderStage2#withIteratorComparator(Comparator)} as these comparators will * never be used. *

* * @return The next builder stage. */ - public DbiBuilderStage3 withNativeComparator() { - this.comparator = null; - this.useNativeCallback = false; + public DbiBuilderStage3 withDefaultComparator() { + this.comparatorType = ComparatorType.DEFAULT; return new DbiBuilderStage3<>(this); } /** *

- * {@link CursorIterable} will make use of the default Java-side comparators when - * comparing entries to start/stop keys. + * With this option, {@link CursorIterable} will call down to LMDB's {@code mdb_cmp} method when + * comparing iteration keys to start/stop keys. This ensures LmdbJava is comparing start/stop + * keys using the same comparator that is used for insertion order into the db. *

*

- * This option may be slightly more performant than when using - * {@link DbiBuilderStage2#withNativeComparator()} but it relies on the default comparator - * in LmdbJava behaving identically to the comparator in LMDB. + * This option may be slightly less performant than when using + * {@link DbiBuilderStage2#withDefaultComparator()} as it needs to call down + * to LMDB to perform the comparisons, however it guarantees that {@link CursorIterable} + * key comparison matches LMDB key comparison. *

*

* If you do not intend to use {@link CursorIterable} then it doesn't matter whether * you choose {@link DbiBuilderStage2#withNativeComparator()}, - * {@link DbiBuilderStage2#withDefaultIteratorComparator()} or + * {@link DbiBuilderStage2#withDefaultComparator()} or * {@link DbiBuilderStage2#withIteratorComparator(Comparator)} as these comparators will * never be used. *

* * @return The next builder stage. */ - public DbiBuilderStage3 withDefaultIteratorComparator() { - this.comparator = dbiBuilder.proxy.getUnsignedComparator(); - this.useNativeCallback = false; + public DbiBuilderStage3 withNativeComparator() { + this.comparatorType = ComparatorType.NATIVE; return new DbiBuilderStage3<>(this); } + /** - * Provide a java-side {@link Comparator} that LMDB will call back to in order to - * manage database insertion/iteration order. It will also be used for {@link CursorIterable} - * start/stop key comparisons. + * Provide a java-side {@link Comparator} that LMDB will call back to for all + * comparison operations. + * Therefore, it will be called by LMDB to manage database insertion/iteration order. + * It will also be used for {@link CursorIterable} start/stop key comparisons. *

- * Due to calling back to java, this will be less performant than using LMDB's - * default comparator, but allows for total control over the order in which entries + * It can be useful if you need to sort your database using some other method, + * e.g. signed keys or case-insensitive order. + * Note, if you need keys stored in reverse order, see {@link DbiFlags#MDB_REVERSEKEY} + * and {@link DbiFlags#MDB_REVERSEDUP}. + *

+ *

+ * As this requires LMDB to call back to java, this will be less performant than using LMDB's + * default comparators, but allows for total control over the order in which entries * are stored in the database. *

* * @param comparator for all key comparison operations. * @return The next builder stage. */ - public DbiBuilderStage3 withCallbackIteratorComparator(final Comparator comparator) { - this.comparator = Objects.requireNonNull(comparator); - this.useNativeCallback = true; + public DbiBuilderStage3 withCallbackComparator(final Comparator comparator) { + this.customComparator = Objects.requireNonNull(comparator); + this.comparatorType = ComparatorType.CALLBACK; return new DbiBuilderStage3<>(this); } /** + *
*

- * {@link CursorIterable} will make use of the passed comparator for - * comparing entries to start/stop keys. It has NO bearing on the insert/iteration - * order of the db. + * WARNING: Only use this if you fully understand the risks and implications. *

+ *
*

- * WARNING: Only call this method if you fully understand the implications - * of using a comparator for the {@link CursorIterable} start/stop keys that behaves - * differently to the comparator in LMDB that controls the insert/iteration order. + * With this option, {@link CursorIterable} will make use of the passed comparator for + * comparing iteration keys to start/stop keys. It has NO bearing on the + * insert/iteration order of the database (which is controlled by LMDB's own comparators). *

*

- * The supplied {@link Comparator} should match the behaviour of LMDB's mdb_cmp comparator. + * It is vital that this comparator is functionally identical to the one + * used internally in LMDB for insertion/iteration order, else you will see unexpected behaviour + * when using {@link CursorIterable}. *

*

* If you do not intend to use {@link CursorIterable} then it doesn't matter whether * you choose {@link DbiBuilderStage2#withNativeComparator()}, - * {@link DbiBuilderStage2#withDefaultIteratorComparator()} or + * {@link DbiBuilderStage2#withDefaultComparator()} or * {@link DbiBuilderStage2#withIteratorComparator(Comparator)} as these comparators will * never be used. *

@@ -204,8 +219,8 @@ public DbiBuilderStage3 withCallbackIteratorComparator(final Comparator co * @return The next builder stage. */ public DbiBuilderStage3 withIteratorComparator(final Comparator comparator) { - this.comparator = Objects.requireNonNull(comparator); - this.useNativeCallback = false; + this.customComparator = Objects.requireNonNull(comparator); + this.comparatorType = ComparatorType.ITERATOR; return new DbiBuilderStage3<>(this); } } @@ -234,14 +249,18 @@ private DbiBuilderStage3(DbiBuilderStage2 dbiBuilderStage2) { * Apply all the dbi flags supplied in dbiFlags. *

*

- * Replaces any flags applies in previous calls to - * {@link DbiBuilderStage3#withDbiFlags(Collection)}, {@link DbiBuilderStage3#withDbiFlags(DbiFlags...)} + * Clears all flags currently set by previous calls to + * {@link DbiBuilderStage3#withDbiFlags(Collection)}, + * {@link DbiBuilderStage3#withDbiFlags(DbiFlags...)} * or {@link DbiBuilderStage3#setDbiFlag(DbiFlags)}. *

* * @param dbiFlags to open the database with. + * A null {@link Collection} will just clear all set flags. + * Null items are ignored. */ public DbiBuilderStage3 withDbiFlags(final Collection dbiFlags) { + flagSetBuilder.clear(); if (dbiFlags != null) { dbiFlags.stream() .filter(Objects::nonNull) @@ -255,14 +274,15 @@ public DbiBuilderStage3 withDbiFlags(final Collection dbiFlags) { * Apply all the dbi flags supplied in dbiFlags. *

*

- * Replaces any flags applies in previous calls to + * Clears all flags currently set by previous calls to * {@link DbiBuilderStage3#withDbiFlags(Collection)}, * {@link DbiBuilderStage3#withDbiFlags(DbiFlags...)} * or {@link DbiBuilderStage3#setDbiFlag(DbiFlags)}. *

* * @param dbiFlags to open the database with. - * A null array is a no-op. Null items are ignored. + * A null array will just clear all set flags. + * Null items are ignored. */ public DbiBuilderStage3 withDbiFlags(final DbiFlags... dbiFlags) { flagSetBuilder.clear(); @@ -275,7 +295,29 @@ public DbiBuilderStage3 withDbiFlags(final DbiFlags... dbiFlags) { } /** - * Adds dbiFlag to those flags already added to this builder by + *

+ * Apply all the dbi flags supplied in dbiFlags. + *

+ *

+ * Clears all flags currently set by previous calls to + * {@link DbiBuilderStage3#withDbiFlags(Collection)}, + * {@link DbiBuilderStage3#withDbiFlags(DbiFlags...)} + * or {@link DbiBuilderStage3#setDbiFlag(DbiFlags)}. + *

+ * + * @param dbiFlagSet to open the database with. + * A null value will just clear all set flags. + */ + public DbiBuilderStage3 withDbiFlags(final DbiFlagSet dbiFlagSet) { + flagSetBuilder.clear(); + if (dbiFlagSet != null) { + this.flagSetBuilder.withFlags(dbiFlagSet.getFlags()); + } + return this; + } + + /** + * Adds a dbiFlag to those flags already added to this builder by * {@link DbiBuilderStage3#withDbiFlags(DbiFlags...)}, * {@link DbiBuilderStage3#withDbiFlags(Collection)} * or {@link DbiBuilderStage3#setDbiFlag(DbiFlags)}. @@ -294,8 +336,15 @@ public DbiBuilderStage3 setDbiFlag(final DbiFlags dbiFlag) { * The caller MUST commit the transaction after calling {@link DbiBuilderStage3#open()}, * in order to retain the Dbi in the Env. *

+ *

+ * If you don't call this method to supply a {@link Txn}, a {@link Txn} will be opened for the purpose + * of creating and opening the {@link Dbi}, then closed. Therefore, if you already have a transaction + * open, you should supply that to avoid one blocking the other. + *

* - * @param txn transaction to use (required; not closed) + * @param txn transaction to use (required; not closed). If the {@link Env} was opened + * with the {@link EnvFlags#MDB_RDONLY_ENV} flag, the {@link Txn} can be read-only, + * else it needs to be a read/write {@link Txn}. * @return this builder instance. */ public DbiBuilderStage3 withTxn(final Txn txn) { @@ -329,18 +378,69 @@ private Txn getTxn(final DbiBuilder dbiBuilder) { : dbiBuilder.env.txnWrite(); } + private Comparator getComparator(final DbiBuilder dbiBuilder, + final ComparatorType comparatorType, + final DbiFlagSet dbiFlagSet) { + Comparator comparator = null; + switch (comparatorType) { + case DEFAULT: + // Get the appropriate default CursorIterable comparator based on the DbiFlags, + // e.g. MDB_INTEGERKEY may benefit from an optimised comparator. + comparator = dbiBuilder.proxy.getComparator(dbiFlagSet); + break; + case CALLBACK: + case ITERATOR: + comparator = dbiBuilderStage2.customComparator; + break; + case NATIVE: + break; + default: + throw new IllegalStateException("Unexpected comparatorType " + comparatorType); + } + return comparator; + } + private Dbi open(final Txn txn, final DbiBuilder dbiBuilder) { final DbiFlagSet dbiFlagSet = flagSetBuilder.build(); + final ComparatorType comparatorType = dbiBuilderStage2.comparatorType; + final Comparator comparator = getComparator(dbiBuilder, comparatorType, dbiFlagSet); + final boolean useNativeCallback = comparatorType == ComparatorType.CALLBACK; return new Dbi<>( dbiBuilder.env, txn, dbiBuilder.name, - dbiBuilderStage2.comparator, - dbiBuilderStage2.useNativeCallback, + comparator, + useNativeCallback, dbiBuilder.proxy, dbiFlagSet); } } + + + // -------------------------------------------------------------------------------- + + + private enum ComparatorType { + /** + * Default Java comparator for {@link CursorIterable} KeyRange testing, + * LMDB comparator for insertion/iteration order. + */ + DEFAULT, + /** + * Use LMDB native comparator for everything. + */ + NATIVE, + /** + * Use the supplied custom Java-side comparator for everything. + */ + CALLBACK, + /** + * Use the supplied custom Java-side comparator for {@link CursorIterable} KeyRange testing, + * LMDB comparator for insertion/iteration order. + */ + ITERATOR, + ; + } } diff --git a/src/main/java/org/lmdbjava/DbiFlags.java b/src/main/java/org/lmdbjava/DbiFlags.java index 6e4b723d..10952da9 100644 --- a/src/main/java/org/lmdbjava/DbiFlags.java +++ b/src/main/java/org/lmdbjava/DbiFlags.java @@ -32,7 +32,7 @@ public enum DbiFlags implements MaskedFlag, DbiFlagSet { * Use sorted duplicates. * *

Duplicate keys may be used in the database. Or, from another perspective, keys may have - * multiple data items, stored in sorted order. By default keys must be unique and may have only a + * multiple data items, stored in sorted order. By default, keys must be unique and may have only a * single data item. *

* @@ -40,8 +40,22 @@ public enum DbiFlags implements MaskedFlag, DbiFlagSet { */ MDB_DUPSORT(0x04), /** - * Numeric keys in native byte order: either unsigned int or size_t. The keys must all be of the - * same size. + * Numeric keys in native byte order: either unsigned int or size_t. + * The keys must all be of the same size. + *

+ * This is an optimisation that is available when your keys are 4 or 8 byte unsigned numeric values. + * There are performance benefits for both ordered and un-ordered puts as compared to not using + * this flag. + *

+ *

+ * When writing the key to the buffer you must write it in native order and subsequently read any + * keys retrieved from LMDB (via cursor or get method) also using native order. + *

+ *

+ * For more information, see + * Numeric Keys + * in the LmdbJava wiki. + *

*/ MDB_INTEGERKEY(0x08), /** diff --git a/src/main/java/org/lmdbjava/DirectBufferProxy.java b/src/main/java/org/lmdbjava/DirectBufferProxy.java index 1aab2573..514c04ab 100644 --- a/src/main/java/org/lmdbjava/DirectBufferProxy.java +++ b/src/main/java/org/lmdbjava/DirectBufferProxy.java @@ -111,15 +111,20 @@ protected DirectBuffer allocate() { } @Override - public Comparator getSignedComparator() { - return signedComparator; - } - - @Override - public Comparator getUnsignedComparator() { + public Comparator getComparator(final DbiFlagSet dbiFlagSet) { return unsignedComparator; } + // @Override +// public Comparator getSignedComparator() { +// return signedComparator; +// } +// +// @Override +// public Comparator getUnsignedComparator(final DbiFlagSet dbiFlagSet) { +// return unsignedComparator; +// } + @Override protected void deallocate(final DirectBuffer buff) { final ArrayDeque q = BUFFERS.get(); diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index 2ad929ad..efc4e240 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -270,13 +270,13 @@ public DbiBuilder buildDbi() { } /** + * @deprecated Instead use {@link Env#buildDbi()} * Convenience method that opens a {@link Dbi} with a UTF-8 database name and default {@link * Comparator} that is not invoked from native code. * * @param name name of the database (or null if no name is required) * @param flags to open the database with * @return a database that is ready to use - * @deprecated Instead use {@link Env#buildDbi()} */ @Deprecated() public Dbi openDbi(final String name, final DbiFlags... flags) { @@ -285,6 +285,7 @@ public Dbi openDbi(final String name, final DbiFlags... flags) { } /** + * @deprecated Instead use {@link Env#buildDbi()} * Convenience method that opens a {@link Dbi} with a UTF-8 database name and associated {@link * Comparator} for use by {@link CursorIterable} when comparing start/stop keys. * @@ -298,7 +299,6 @@ public Dbi openDbi(final String name, final DbiFlags... flags) { * comparator will be used. * @param flags to open the database with * @return a database that is ready to use - * @deprecated Instead use {@link Env#buildDbi()} */ @Deprecated() public Dbi openDbi( @@ -308,6 +308,7 @@ public Dbi openDbi( } /** + * @deprecated Instead use {@link Env#buildDbi()} * Convenience method that opens a {@link Dbi} with a UTF-8 database name and associated {@link * Comparator}. The comparator will be used by {@link CursorIterable} when comparing start/stop * keys as a minimum. If nativeCb is {@code true}, this comparator will also be called by LMDB to @@ -320,7 +321,6 @@ public Dbi openDbi( * @param nativeCb whether LMDB native code calls back to the Java comparator * @param flags to open the database with * @return a database that is ready to use - * @deprecated Instead use {@link Env#buildDbi()} */ @Deprecated() public Dbi openDbi( @@ -333,13 +333,13 @@ public Dbi openDbi( } /** + * @deprecated Instead use {@link Env#buildDbi()} * Convenience method that opens a {@link Dbi} with a default {@link Comparator} that is not * invoked from native code. * * @param name name of the database (or null if no name is required) * @param flags to open the database with * @return a database that is ready to use - * @deprecated Instead use {@link Env#buildDbi()} */ @Deprecated() public Dbi openDbi(final byte[] name, final DbiFlags... flags) { @@ -347,6 +347,7 @@ public Dbi openDbi(final byte[] name, final DbiFlags... flags) { } /** + * @deprecated Instead use {@link Env#buildDbi()} * Convenience method that opens a {@link Dbi} with an associated {@link Comparator} that is not * invoked from native code. * @@ -354,7 +355,6 @@ public Dbi openDbi(final byte[] name, final DbiFlags... flags) { * @param comparator custom comparator callback (or null to use LMDB default) * @param flags to open the database with * @return a database that is ready to use - * @deprecated Instead use {@link Env#buildDbi()} */ @Deprecated() public Dbi openDbi( @@ -363,6 +363,7 @@ public Dbi openDbi( } /** + * @deprecated Instead use {@link Env#buildDbi()} * Convenience method that opens a {@link Dbi} with an associated {@link Comparator} that may be * invoked from native code if specified. * @@ -374,7 +375,6 @@ public Dbi openDbi( * @param nativeCb whether native code calls back to the Java comparator * @param flags to open the database with * @return a database that is ready to use - * @deprecated Instead use {@link Env#buildDbi()} */ @Deprecated() public Dbi openDbi( @@ -390,6 +390,7 @@ public Dbi openDbi( } /** + * @deprecated Instead use {@link Env#buildDbi()} * Open the {@link Dbi} using the passed {@link Txn}. * *

The caller must commit the transaction after this method returns in order to retain the @@ -412,10 +413,9 @@ public Dbi openDbi( * @param txn transaction to use (required; not closed) * @param name name of the database (or null if no name is required) * @param comparator custom comparator callback (or null to use LMDB default) - * @param nativeCb whether native code should call back to the comparator + * @param nativeCb whether native LMDB code should call back to the Java comparator * @param flags to open the database with * @return a database that is ready to use - * @deprecated Instead use {@link Env#buildDbi()} */ @Deprecated() public Dbi openDbi( @@ -424,6 +424,10 @@ public Dbi openDbi( final Comparator comparator, final boolean nativeCb, final DbiFlags... flags) { + + if (nativeCb && comparator == null) { + throw new IllegalArgumentException("Is nativeCb is true, you must supply a comparator"); + } return new Dbi<>(this, txn, name, comparator, nativeCb, proxy, DbiFlagSet.of(flags)); } diff --git a/src/main/java/org/lmdbjava/Txn.java b/src/main/java/org/lmdbjava/Txn.java index 5c8440fd..dc57fc66 100644 --- a/src/main/java/org/lmdbjava/Txn.java +++ b/src/main/java/org/lmdbjava/Txn.java @@ -164,7 +164,7 @@ public void renew() { } /** - * Aborts this read-only transaction and resets the transaction handle so it can be reused upon + * Aborts this read-only transaction and resets the transaction handle, so it can be reused upon * calling {@link #renew()}. */ public void reset() { diff --git a/src/main/java/org/lmdbjava/TxnFlags.java b/src/main/java/org/lmdbjava/TxnFlags.java index 8e4ea757..94112957 100644 --- a/src/main/java/org/lmdbjava/TxnFlags.java +++ b/src/main/java/org/lmdbjava/TxnFlags.java @@ -20,6 +20,7 @@ /** Flags for use when creating a {@link Txn}. */ public enum TxnFlags implements MaskedFlag, TxnFlagSet { + /** Read only. */ MDB_RDONLY_TXN(0x2_0000); diff --git a/src/test/java/org/lmdbjava/ComparatorTest.java b/src/test/java/org/lmdbjava/ComparatorTest.java index 446b8545..1b00c71c 100644 --- a/src/test/java/org/lmdbjava/ComparatorTest.java +++ b/src/test/java/org/lmdbjava/ComparatorTest.java @@ -136,7 +136,7 @@ private static final class ByteArrayRunner implements ComparatorRunner { @Override public int compare(final byte[] o1, final byte[] o2) { - final Comparator c = PROXY_BA.getUnsignedComparator(); + final Comparator c = PROXY_BA.getComparator(); return c.compare(o1, o2); } } @@ -146,7 +146,7 @@ private static final class UnsignedByteArrayRunner implements ComparatorRunner { @Override public int compare(final byte[] o1, final byte[] o2) { - final Comparator c = PROXY_BA.getUnsignedComparator(); + final Comparator c = PROXY_BA.getComparator(); return c.compare(o1, o2); } } @@ -156,7 +156,7 @@ private static final class ByteBufferRunner implements ComparatorRunner { @Override public int compare(final byte[] o1, final byte[] o2) { - final Comparator c = PROXY_OPTIMAL.getUnsignedComparator(); + final Comparator c = PROXY_OPTIMAL.getComparator(); // Convert arrays to buffers that are larger than the array, with // limit set at the array length. One buffer bigger than the other. @@ -200,7 +200,7 @@ private static final class DirectBufferRunner implements ComparatorRunner { public int compare(final byte[] o1, final byte[] o2) { final DirectBuffer o1b = new UnsafeBuffer(o1); final DirectBuffer o2b = new UnsafeBuffer(o2); - final Comparator c = PROXY_DB.getUnsignedComparator(); + final Comparator c = PROXY_DB.getComparator(); return c.compare(o1b, o2b); } } @@ -234,7 +234,7 @@ public int compare(final byte[] o1, final byte[] o2) { final ByteBuf o2b = DEFAULT.directBuffer(o2.length); o1b.writeBytes(o1); o2b.writeBytes(o2); - final Comparator c = PROXY_NETTY.getUnsignedComparator(); + final Comparator c = PROXY_NETTY.getComparator(); return c.compare(o1b, o2b); } } diff --git a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java index 431c6e51..aefe9d43 100644 --- a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java @@ -135,18 +135,27 @@ public void before() throws IOException { .open(path, POSIX_MODE, MDB_NOSUBDIR); // Use a java comparator for start/stop keys only - dbJavaComparator = env.openDbi(DB_1, - bufferProxy.getUnsignedComparator(), - MDB_CREATE, - MDB_INTEGERKEY); + DbiFlagSet dbiFlagSet = DbiFlagSet.of(MDB_CREATE, MDB_INTEGERKEY); + + dbJavaComparator = env.buildDbi() + .withDbName(DB_1) + .withIteratorComparator(bufferProxy.getComparator(dbiFlagSet)) + .withDbiFlags(dbiFlagSet) + .open(); + // Use LMDB comparator for start/stop keys - dbLmdbComparator = env.openDbi(DB_2, MDB_CREATE, MDB_INTEGERKEY); + dbLmdbComparator = env.buildDbi() + .withDbName(DB_2) + .withDefaultComparator() + .withDbiFlags(dbiFlagSet) + .open(); + // Use a java comparator for start/stop keys and as a callback comparaotr - dbCallbackComparator = env.openDbi(DB_3, - bufferProxy.getUnsignedComparator(), - true, - MDB_CREATE, - MDB_INTEGERKEY); + dbCallbackComparator = env.buildDbi() + .withDbName(DB_3) + .withCallbackComparator(bufferProxy.getComparator(dbiFlagSet)) + .withDbiFlags(dbiFlagSet) + .open(); populateList(); @@ -411,52 +420,52 @@ public void forEachRemainingWithClosedEnvTest() { } } - @Test - public void testSignedVsUnsigned() { - final ByteBuffer val1 = bbNative(1); - final ByteBuffer val2 = bbNative(2); - final ByteBuffer val110 = bbNative(110); - final ByteBuffer val111 = bbNative(111); - final ByteBuffer val150 = bbNative(150); - - final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; - final Comparator unsignedComparator = bufferProxy.getUnsignedComparator(); - final Comparator signedComparator = bufferProxy.getSignedComparator(); - - // Compare the same - assertThat( - unsignedComparator.compare(val1, val2), Matchers.is(signedComparator.compare(val1, val2))); - - // Compare differently - assertThat( - unsignedComparator.compare(val110, val150), - Matchers.not(signedComparator.compare(val110, val150))); - - // Compare differently - assertThat( - unsignedComparator.compare(val111, val150), - Matchers.not(signedComparator.compare(val111, val150))); - - // This will fail if the db is using a signed comparator for the start/stop keys - for (final Dbi db : dbs) { - db.put(val110, val110); - db.put(val150, val150); - - final ByteBuffer startKeyBuf = val111; - KeyRange keyRange = KeyRange.atLeastBackward(startKeyBuf); - - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn, keyRange)) { - for (final KeyVal kv : c) { - final int key = getNativeInt(kv.key()); - final int val = kv.val().getInt(); - // System.out.println("key: " + key + " val: " + val); - assertThat(key, is(110)); - break; - } - } - } - } +// @Test +// public void testSignedVsUnsigned() { +// final ByteBuffer val1 = bbNative(1); +// final ByteBuffer val2 = bbNative(2); +// final ByteBuffer val110 = bbNative(110); +// final ByteBuffer val111 = bbNative(111); +// final ByteBuffer val150 = bbNative(150); +// +// final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; +// final Comparator unsignedComparator = bufferProxy.getUnsignedComparator(); +// final Comparator signedComparator = bufferProxy.getSignedComparator(); +// +// // Compare the same +// assertThat( +// unsignedComparator.compare(val1, val2), Matchers.is(signedComparator.compare(val1, val2))); +// +// // Compare differently +// assertThat( +// unsignedComparator.compare(val110, val150), +// Matchers.not(signedComparator.compare(val110, val150))); +// +// // Compare differently +// assertThat( +// unsignedComparator.compare(val111, val150), +// Matchers.not(signedComparator.compare(val111, val150))); +// +// // This will fail if the db is using a signed comparator for the start/stop keys +// for (final Dbi db : dbs) { +// db.put(val110, val110); +// db.put(val150, val150); +// +// final ByteBuffer startKeyBuf = val111; +// KeyRange keyRange = KeyRange.atLeastBackward(startKeyBuf); +// +// try (Txn txn = env.txnRead(); +// CursorIterable c = db.iterate(txn, keyRange)) { +// for (final KeyVal kv : c) { +// final int key = getNativeInt(kv.key()); +// final int val = kv.val().getInt(); +// // System.out.println("key: " + key + " val: " + val); +// assertThat(key, is(110)); +// break; +// } +// } +// } +// } private void verify(final KeyRange range, final int... expected) { // Verify using all comparator types diff --git a/src/test/java/org/lmdbjava/CursorIterablePerfTest.java b/src/test/java/org/lmdbjava/CursorIterablePerfTest.java index f77ac4d6..e2c54346 100644 --- a/src/test/java/org/lmdbjava/CursorIterablePerfTest.java +++ b/src/test/java/org/lmdbjava/CursorIterablePerfTest.java @@ -65,14 +65,26 @@ public void before() throws IOException { .setMaxDbs(3) .open(path, POSIX_MODE, MDB_NOSUBDIR); + final DbiFlagSet dbiFlagSet = MDB_CREATE; // Use a java comparator for start/stop keys only - dbJavaComparator = - env.openDbi("JavaComparator", bufferProxy.getUnsignedComparator(), MDB_CREATE); + dbJavaComparator = env.buildDbi() + .withDbName("JavaComparator") + .withDefaultComparator() + .withDbiFlags(dbiFlagSet) + .open(); // Use LMDB comparator for start/stop keys - dbLmdbComparator = env.openDbi("LmdbComparator", MDB_CREATE); + dbLmdbComparator = env.buildDbi() + .withDbName("LmdbComparator") + .withNativeComparator() + .withDbiFlags(dbiFlagSet) + .open(); + // Use a java comparator for start/stop keys and as a callback comparator - dbCallbackComparator = - env.openDbi("CallBackComparator", bufferProxy.getUnsignedComparator(), true, MDB_CREATE); + dbCallbackComparator = env.buildDbi() + .withDbName("CallBackComparator") + .withCallbackComparator(bufferProxy.getComparator(dbiFlagSet)) + .withDbiFlags(dbiFlagSet) + .open(); dbs.add(dbJavaComparator); dbs.add(dbLmdbComparator); diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index 22cf7361..79a9a34c 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -129,12 +129,25 @@ public void before() throws IOException { .setMaxDbs(3) .open(path, POSIX_MODE, MDB_NOSUBDIR); + final DbiFlagSet dbiFlagSet = MDB_CREATE; // Use a java comparator for start/stop keys only - dbJavaComparator = env.openDbi(DB_1, bufferProxy.getUnsignedComparator(), MDB_CREATE); + dbJavaComparator = env.buildDbi() + .withDbName(DB_1) + .withDefaultComparator() + .withDbiFlags(dbiFlagSet) + .open(); // Use LMDB comparator for start/stop keys - dbLmdbComparator = env.openDbi(DB_2, MDB_CREATE); + dbLmdbComparator = env.buildDbi() + .withDbName(DB_2) + .withNativeComparator() + .withDbiFlags(dbiFlagSet) + .open(); // Use a java comparator for start/stop keys and as a callback comparaotr - dbCallbackComparator = env.openDbi(DB_3, bufferProxy.getUnsignedComparator(), true, MDB_CREATE); + dbCallbackComparator = env.buildDbi() + .withDbName(DB_3) + .withCallbackComparator(bufferProxy.getComparator(dbiFlagSet)) + .withDbiFlags(dbiFlagSet) + .open(); populateList(); @@ -407,52 +420,52 @@ public void forEachRemainingWithClosedEnvTest() { } } - @Test - public void testSignedVsUnsigned() { - final ByteBuffer val1 = bb(1); - final ByteBuffer val2 = bb(2); - final ByteBuffer val110 = bb(110); - final ByteBuffer val111 = bb(111); - final ByteBuffer val150 = bb(150); - - final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; - final Comparator unsignedComparator = bufferProxy.getUnsignedComparator(); - final Comparator signedComparator = bufferProxy.getSignedComparator(); - - // Compare the same - assertThat( - unsignedComparator.compare(val1, val2), Matchers.is(signedComparator.compare(val1, val2))); - - // Compare differently - assertThat( - unsignedComparator.compare(val110, val150), - Matchers.not(signedComparator.compare(val110, val150))); - - // Compare differently - assertThat( - unsignedComparator.compare(val111, val150), - Matchers.not(signedComparator.compare(val111, val150))); - - // This will fail if the db is using a signed comparator for the start/stop keys - for (final Dbi db : dbs) { - db.put(val110, val110); - db.put(val150, val150); - - final ByteBuffer startKeyBuf = val111; - KeyRange keyRange = KeyRange.atLeastBackward(startKeyBuf); - - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn, keyRange)) { - for (final CursorIterable.KeyVal kv : c) { - final int key = kv.key().getInt(); - final int val = kv.val().getInt(); - // System.out.println("key: " + key + " val: " + val); - assertThat(key, is(110)); - break; - } - } - } - } +// @Test +// public void testSignedVsUnsigned() { +// final ByteBuffer val1 = bb(1); +// final ByteBuffer val2 = bb(2); +// final ByteBuffer val110 = bb(110); +// final ByteBuffer val111 = bb(111); +// final ByteBuffer val150 = bb(150); +// +// final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; +// final Comparator unsignedComparator = bufferProxy.getUnsignedComparator(); +// final Comparator signedComparator = bufferProxy.getSignedComparator(); +// +// // Compare the same +// assertThat( +// unsignedComparator.compare(val1, val2), Matchers.is(signedComparator.compare(val1, val2))); +// +// // Compare differently +// assertThat( +// unsignedComparator.compare(val110, val150), +// Matchers.not(signedComparator.compare(val110, val150))); +// +// // Compare differently +// assertThat( +// unsignedComparator.compare(val111, val150), +// Matchers.not(signedComparator.compare(val111, val150))); +// +// // This will fail if the db is using a signed comparator for the start/stop keys +// for (final Dbi db : dbs) { +// db.put(val110, val110); +// db.put(val150, val150); +// +// final ByteBuffer startKeyBuf = val111; +// KeyRange keyRange = KeyRange.atLeastBackward(startKeyBuf); +// +// try (Txn txn = env.txnRead(); +// CursorIterable c = db.iterate(txn, keyRange)) { +// for (final CursorIterable.KeyVal kv : c) { +// final int key = kv.key().getInt(); +// final int val = kv.val().getInt(); +// // System.out.println("key: " + key + " val: " + val); +// assertThat(key, is(110)); +// break; +// } +// } +// } +// } private void verify(final KeyRange range, final int... expected) { // Verify using all comparator types diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 37f141e9..2209e614 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -118,7 +118,7 @@ public void close() { public void customComparator() { final Comparator reverseOrder = (o1, o2) -> { - final int lexical = PROXY_OPTIMAL.getUnsignedComparator().compare(o1, o2); + final int lexical = PROXY_OPTIMAL.getComparator().compare(o1, o2); if (lexical == 0) { return 0; } @@ -131,7 +131,7 @@ public void customComparator() { public void customComparatorByteArray() { final Comparator reverseOrder = (o1, o2) -> { - final int lexical = PROXY_BA.getUnsignedComparator().compare(o1, o2); + final int lexical = PROXY_BA.getComparator().compare(o1, o2); if (lexical == 0) { return 0; } @@ -172,13 +172,13 @@ public void dbOpenMaxDatabases() { @Test public void dbiWithComparatorThreadSafety() { doDbiWithComparatorThreadSafety( - env, PROXY_OPTIMAL::getUnsignedComparator, TestUtils::bb, ByteBuffer::getInt); + env, PROXY_OPTIMAL::getComparator, TestUtils::bb, ByteBuffer::getInt); } @Test public void dbiWithComparatorThreadSafetyByteArray() { doDbiWithComparatorThreadSafety( - envBa, PROXY_BA::getUnsignedComparator, TestUtils::ba, TestUtils::fromBa); + envBa, PROXY_BA::getComparator, TestUtils::ba, TestUtils::fromBa); } public void doDbiWithComparatorThreadSafety( diff --git a/src/test/java/org/lmdbjava/TestDbiBuilder.java b/src/test/java/org/lmdbjava/TestDbiBuilder.java index 15b70812..7a1e8947 100644 --- a/src/test/java/org/lmdbjava/TestDbiBuilder.java +++ b/src/test/java/org/lmdbjava/TestDbiBuilder.java @@ -10,7 +10,6 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; -import junit.framework.TestCase; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Before; @@ -31,6 +30,7 @@ public void after() { @Before public void before() throws IOException { + System.out.println("before"); final File path = tmp.newFile(); env = create() @@ -44,7 +44,7 @@ public void before() throws IOException { public void unnamed() { final Dbi dbi = env.buildDbi() .withoutDbName() - .withDefaultIteratorComparator() + .withDefaultComparator() .withDbiFlags(DbiFlags.MDB_CREATE) .open(); @@ -58,7 +58,7 @@ public void unnamed() { public void named() { final Dbi dbi = env.buildDbi() .withDbName("foo") - .withDefaultIteratorComparator() + .withDefaultComparator() .withDbiFlags(DbiFlags.MDB_CREATE) .open(); From ef0c852ad9a631ae0f194f4d63dce62e3d1b549f Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Tue, 28 Oct 2025 17:31:32 +0000 Subject: [PATCH 14/90] Add missing txn commit in DbiBuilder --- .../java/org/lmdbjava/AbstractFlagSet.java | 15 +++ src/main/java/org/lmdbjava/CopyFlagSet.java | 15 +++ src/main/java/org/lmdbjava/Dbi.java | 11 +-- src/main/java/org/lmdbjava/DbiBuilder.java | 15 +-- src/main/java/org/lmdbjava/DbiFlagSet.java | 15 +++ src/main/java/org/lmdbjava/EnvFlagSet.java | 15 +++ src/main/java/org/lmdbjava/FlagSet.java | 15 +++ src/main/java/org/lmdbjava/PutFlagSet.java | 15 +++ src/main/java/org/lmdbjava/ReferenceUtil.java | 1 + src/main/java/org/lmdbjava/Txn.java | 7 +- src/main/java/org/lmdbjava/TxnFlagSet.java | 15 +++ .../java/org/lmdbjava/CopyFlagSetTest.java | 15 +++ .../java/org/lmdbjava/DbiBuilderTest.java | 97 +++++++++++++++++++ .../java/org/lmdbjava/DbiFlagSetTest.java | 15 +++ .../java/org/lmdbjava/EnvFlagSetTest.java | 15 +++ .../java/org/lmdbjava/PutFlagSetTest.java | 15 +++ .../java/org/lmdbjava/TestDbiBuilder.java | 83 ---------------- .../java/org/lmdbjava/TxnFlagSetTest.java | 15 +++ 18 files changed, 296 insertions(+), 98 deletions(-) create mode 100644 src/test/java/org/lmdbjava/DbiBuilderTest.java delete mode 100644 src/test/java/org/lmdbjava/TestDbiBuilder.java diff --git a/src/main/java/org/lmdbjava/AbstractFlagSet.java b/src/main/java/org/lmdbjava/AbstractFlagSet.java index 7ea413fb..5e62b437 100644 --- a/src/main/java/org/lmdbjava/AbstractFlagSet.java +++ b/src/main/java/org/lmdbjava/AbstractFlagSet.java @@ -1,3 +1,18 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.lmdbjava; import java.util.Collection; diff --git a/src/main/java/org/lmdbjava/CopyFlagSet.java b/src/main/java/org/lmdbjava/CopyFlagSet.java index 62c73c8d..5f7901de 100644 --- a/src/main/java/org/lmdbjava/CopyFlagSet.java +++ b/src/main/java/org/lmdbjava/CopyFlagSet.java @@ -1,3 +1,18 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.lmdbjava; import java.util.Collection; diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index 9cdc03ee..c66dc780 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -49,7 +49,7 @@ */ public final class Dbi { - private final ComparatorCallback ccb; + private final ComparatorCallback callbackComparator; private boolean cleaned; // Used for CursorIterable KeyRange testing and/or native callbacks private final Comparator comparator; @@ -82,7 +82,7 @@ public final class Dbi { if (nativeCb) { requireNonNull(comparator, "comparator cannot be null if nativeCb is set"); // LMDB will call back to this comparator for insertion/iteration order - this.ccb = + this.callbackComparator = (keyA, keyB) -> { final T compKeyA = proxy.out(proxy.allocate(), keyA); final T compKeyB = proxy.out(proxy.allocate(), keyB); @@ -91,9 +91,9 @@ public final class Dbi { proxy.deallocate(compKeyB); return result; }; - LIB.mdb_set_compare(txn.pointer(), ptr, ccb); + LIB.mdb_set_compare(txn.pointer(), ptr, callbackComparator); } else { - ccb = null; + callbackComparator = null; } } @@ -380,8 +380,7 @@ public boolean put(final Txn txn, final T key, final T val, final PutFlags... final Pointer transientKey = txn.kv().keyIn(key); final Pointer transientVal = txn.kv().valIn(val); final int mask = mask(flags); - final int rc = - LIB.mdb_put(txn.pointer(), ptr, txn.kv().pointerKey(), txn.kv().pointerVal(), mask); + final int rc = LIB.mdb_put(txn.pointer(), ptr, txn.kv().pointerKey(), txn.kv().pointerVal(), mask); if (rc == MDB_KEYEXIST) { if (isSet(mask, MDB_NOOVERWRITE)) { txn.kv().valOut(); // marked as in,out in LMDB C docs diff --git a/src/main/java/org/lmdbjava/DbiBuilder.java b/src/main/java/org/lmdbjava/DbiBuilder.java index f2f925ce..dcf34d0a 100644 --- a/src/main/java/org/lmdbjava/DbiBuilder.java +++ b/src/main/java/org/lmdbjava/DbiBuilder.java @@ -355,20 +355,23 @@ public DbiBuilderStage3 withTxn(final Txn txn) { /** * Construct and open the {@link Dbi}. *

- * If a {@link Txn} was supplied to the builder, it should be committed upon return from - * this method. + * If a {@link Txn} was supplied to the builder, it is the callers responsibility to + * commit and close the txn upon return from this method, else the created DB won't be retained. *

* * @return A newly constructed and opened {@link Dbi}. */ public Dbi open() { final DbiBuilder dbiBuilder = dbiBuilderStage2.dbiBuilder; - if (txn == null) { + if (txn != null) { + return open(txn, dbiBuilder); + } else { try (final Txn txn = getTxn(dbiBuilder)) { - return open(txn, dbiBuilder); + final Dbi dbi = open(txn, dbiBuilder); + // even RO Txns require a commit to retain Dbi in Env + txn.commit(); + return dbi; } - } else { - return open(txn, dbiBuilder); } } diff --git a/src/main/java/org/lmdbjava/DbiFlagSet.java b/src/main/java/org/lmdbjava/DbiFlagSet.java index e5c97544..28f5e4f1 100644 --- a/src/main/java/org/lmdbjava/DbiFlagSet.java +++ b/src/main/java/org/lmdbjava/DbiFlagSet.java @@ -1,3 +1,18 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.lmdbjava; import java.util.Collection; diff --git a/src/main/java/org/lmdbjava/EnvFlagSet.java b/src/main/java/org/lmdbjava/EnvFlagSet.java index 944496e6..f1bab2d0 100644 --- a/src/main/java/org/lmdbjava/EnvFlagSet.java +++ b/src/main/java/org/lmdbjava/EnvFlagSet.java @@ -1,3 +1,18 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.lmdbjava; import java.util.Collection; diff --git a/src/main/java/org/lmdbjava/FlagSet.java b/src/main/java/org/lmdbjava/FlagSet.java index 80a4c19e..89b955a0 100644 --- a/src/main/java/org/lmdbjava/FlagSet.java +++ b/src/main/java/org/lmdbjava/FlagSet.java @@ -1,3 +1,18 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.lmdbjava; import java.util.Comparator; diff --git a/src/main/java/org/lmdbjava/PutFlagSet.java b/src/main/java/org/lmdbjava/PutFlagSet.java index 1eedaf10..85de014b 100644 --- a/src/main/java/org/lmdbjava/PutFlagSet.java +++ b/src/main/java/org/lmdbjava/PutFlagSet.java @@ -1,3 +1,18 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.lmdbjava; import java.util.Collection; diff --git a/src/main/java/org/lmdbjava/ReferenceUtil.java b/src/main/java/org/lmdbjava/ReferenceUtil.java index 2b9f211e..70b3c338 100644 --- a/src/main/java/org/lmdbjava/ReferenceUtil.java +++ b/src/main/java/org/lmdbjava/ReferenceUtil.java @@ -41,6 +41,7 @@ private ReferenceUtil() {} */ public static void reachabilityFence0(final Object ref) { if (ref != null) { + //noinspection EmptySynchronizedStatement synchronized (ref) { // Empty synchronized is ok: https://stackoverflow.com/a/31933260/1151521 } diff --git a/src/main/java/org/lmdbjava/Txn.java b/src/main/java/org/lmdbjava/Txn.java index dc57fc66..99439bf7 100644 --- a/src/main/java/org/lmdbjava/Txn.java +++ b/src/main/java/org/lmdbjava/Txn.java @@ -42,15 +42,16 @@ public final class Txn implements AutoCloseable { private final Pointer ptr; private final boolean readOnly; private final Env env; + private final TxnFlagSet flags; private State state; Txn(final Env env, final Txn parent, final BufferProxy proxy, final TxnFlagSet flags) { - final TxnFlagSet flagSet = flags != null + this.flags = flags != null ? flags : TxnFlagSet.EMPTY; this.proxy = proxy; this.keyVal = proxy.keyVal(); - this.readOnly = flagSet.isSet(MDB_RDONLY_TXN); + this.readOnly = this.flags.isSet(MDB_RDONLY_TXN); if (env.isReadOnly() && !this.readOnly) { throw new EnvIsReadOnly(); } @@ -61,7 +62,7 @@ public final class Txn implements AutoCloseable { } final Pointer txnPtr = allocateDirect(RUNTIME, ADDRESS); final Pointer txnParentPtr = parent == null ? null : parent.ptr; - checkRc(LIB.mdb_txn_begin(env.pointer(), txnParentPtr, flagSet.getMask(), txnPtr)); + checkRc(LIB.mdb_txn_begin(env.pointer(), txnParentPtr, this.flags.getMask(), txnPtr)); ptr = txnPtr.getPointer(0); state = READY; diff --git a/src/main/java/org/lmdbjava/TxnFlagSet.java b/src/main/java/org/lmdbjava/TxnFlagSet.java index 6320eece..8e6310b3 100644 --- a/src/main/java/org/lmdbjava/TxnFlagSet.java +++ b/src/main/java/org/lmdbjava/TxnFlagSet.java @@ -1,3 +1,18 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.lmdbjava; import java.util.EnumSet; diff --git a/src/test/java/org/lmdbjava/CopyFlagSetTest.java b/src/test/java/org/lmdbjava/CopyFlagSetTest.java index 1ea44b7e..66e89ccb 100644 --- a/src/test/java/org/lmdbjava/CopyFlagSetTest.java +++ b/src/test/java/org/lmdbjava/CopyFlagSetTest.java @@ -1,3 +1,18 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.lmdbjava; import static org.hamcrest.CoreMatchers.is; diff --git a/src/test/java/org/lmdbjava/DbiBuilderTest.java b/src/test/java/org/lmdbjava/DbiBuilderTest.java new file mode 100644 index 00000000..da9341c6 --- /dev/null +++ b/src/test/java/org/lmdbjava/DbiBuilderTest.java @@ -0,0 +1,97 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +import static com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.lmdbjava.Env.create; +import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; +import static org.lmdbjava.TestUtils.bb; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import org.hamcrest.Matchers; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class DbiBuilderTest { + + @Rule + public final TemporaryFolder tmp = new TemporaryFolder(); + private Env env; + + @After + public void after() { + env.close(); + } + + @Before + public void before() throws IOException { + System.out.println("before"); + final File path = tmp.newFile(); + env = create() + .setMapSize(MEBIBYTES.toBytes(64)) + .setMaxReaders(2) + .setMaxDbs(2) + .open(path, MDB_NOSUBDIR); + } + + @Test + public void unnamed() { + final Dbi dbi = env.buildDbi() + .withoutDbName() + .withDefaultComparator() + .withDbiFlags(DbiFlags.MDB_CREATE) + .open(); + + assertThat(env.getDbiNames().size(), Matchers.is(0)); + + assertPutAndGet(dbi); + } + + + @Test + public void named() { + final Dbi dbi = env.buildDbi() + .withDbName("foo") + .withDefaultComparator() + .withDbiFlags(DbiFlags.MDB_CREATE) + .open(); + + assertPutAndGet(dbi); + + assertThat(env.getDbiNames().size(), Matchers.is(1)); + assertThat(new String(env.getDbiNames().get(0), StandardCharsets.UTF_8), Matchers.is("foo")); + } + + private void assertPutAndGet(Dbi dbi) { + try (Txn writeTxn = env.txnWrite()) { + dbi.put(writeTxn, bb(123), bb(123_000)); + writeTxn.commit(); + } + + try (Txn readTxn = env.txnRead()) { + final ByteBuffer byteBuffer = dbi.get(readTxn, bb(123)); + final int val = byteBuffer.getInt(); + assertThat(val, Matchers.is(123_000)); + } + } +} diff --git a/src/test/java/org/lmdbjava/DbiFlagSetTest.java b/src/test/java/org/lmdbjava/DbiFlagSetTest.java index 323a2ed4..457e4718 100644 --- a/src/test/java/org/lmdbjava/DbiFlagSetTest.java +++ b/src/test/java/org/lmdbjava/DbiFlagSetTest.java @@ -1,3 +1,18 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.lmdbjava; import static org.hamcrest.CoreMatchers.is; diff --git a/src/test/java/org/lmdbjava/EnvFlagSetTest.java b/src/test/java/org/lmdbjava/EnvFlagSetTest.java index ed6a0fea..2ecce3c2 100644 --- a/src/test/java/org/lmdbjava/EnvFlagSetTest.java +++ b/src/test/java/org/lmdbjava/EnvFlagSetTest.java @@ -1,3 +1,18 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.lmdbjava; import static org.hamcrest.CoreMatchers.is; diff --git a/src/test/java/org/lmdbjava/PutFlagSetTest.java b/src/test/java/org/lmdbjava/PutFlagSetTest.java index 8cf1efe0..3e402732 100644 --- a/src/test/java/org/lmdbjava/PutFlagSetTest.java +++ b/src/test/java/org/lmdbjava/PutFlagSetTest.java @@ -1,3 +1,18 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.lmdbjava; import static org.hamcrest.CoreMatchers.is; diff --git a/src/test/java/org/lmdbjava/TestDbiBuilder.java b/src/test/java/org/lmdbjava/TestDbiBuilder.java deleted file mode 100644 index 7a1e8947..00000000 --- a/src/test/java/org/lmdbjava/TestDbiBuilder.java +++ /dev/null @@ -1,83 +0,0 @@ -package org.lmdbjava; - -import static com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.lmdbjava.Env.create; -import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; -import static org.lmdbjava.TestUtils.bb; - -import java.io.File; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import org.hamcrest.Matchers; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -public class TestDbiBuilder { - - @Rule - public final TemporaryFolder tmp = new TemporaryFolder(); - private Env env; - - @After - public void after() { - env.close(); - } - - @Before - public void before() throws IOException { - System.out.println("before"); - final File path = tmp.newFile(); - env = - create() - .setMapSize(MEBIBYTES.toBytes(64)) - .setMaxReaders(2) - .setMaxDbs(2) - .open(path, MDB_NOSUBDIR); - } - - @Test - public void unnamed() { - final Dbi dbi = env.buildDbi() - .withoutDbName() - .withDefaultComparator() - .withDbiFlags(DbiFlags.MDB_CREATE) - .open(); - - assertThat(env.getDbiNames().size(), Matchers.is(0)); - - assertPutAndGet(dbi); - } - - - @Test - public void named() { - final Dbi dbi = env.buildDbi() - .withDbName("foo") - .withDefaultComparator() - .withDbiFlags(DbiFlags.MDB_CREATE) - .open(); - - assertPutAndGet(dbi); - - assertThat(env.getDbiNames().size(), Matchers.is(1)); - assertThat(new String(env.getDbiNames().get(0), StandardCharsets.UTF_8), Matchers.is("foo")); - } - - private void assertPutAndGet(Dbi dbi) { - try (Txn writeTxn = env.txnWrite()) { - dbi.put(writeTxn, bb(123), bb(123_000)); - writeTxn.commit(); - } - - try (Txn readTxn = env.txnRead()) { - final ByteBuffer byteBuffer = dbi.get(readTxn, bb(123)); - final int val = byteBuffer.getInt(); - assertThat(val, Matchers.is(123_000)); - } - } -} diff --git a/src/test/java/org/lmdbjava/TxnFlagSetTest.java b/src/test/java/org/lmdbjava/TxnFlagSetTest.java index b526ceeb..2bb3790a 100644 --- a/src/test/java/org/lmdbjava/TxnFlagSetTest.java +++ b/src/test/java/org/lmdbjava/TxnFlagSetTest.java @@ -1,3 +1,18 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.lmdbjava; import static org.hamcrest.CoreMatchers.is; From 1b3f94df197b0889e6612418d3a43a9f496ee452 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Tue, 28 Oct 2025 18:21:58 +0000 Subject: [PATCH 15/90] Change CursorIterableTest to use Parameterized --- .../java/org/lmdbjava/CursorIterableTest.java | 205 ++++++++++-------- src/test/java/org/lmdbjava/TestUtils.java | 1 + 2 files changed, 116 insertions(+), 90 deletions(-) diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index 79a9a34c..bf2eb9eb 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -45,6 +45,7 @@ import static org.lmdbjava.TestUtils.DB_1; import static org.lmdbjava.TestUtils.DB_2; import static org.lmdbjava.TestUtils.DB_3; +import static org.lmdbjava.TestUtils.DB_4; import static org.lmdbjava.TestUtils.POSIX_MODE; import static org.lmdbjava.TestUtils.bb; @@ -59,25 +60,83 @@ import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; +import java.util.function.Function; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; import org.lmdbjava.CursorIterable.KeyVal; -/** Test {@link CursorIterable}. */ +/** + * Test {@link CursorIterable}. + */ +@RunWith(Parameterized.class) public final class CursorIterableTest { - @Rule public final TemporaryFolder tmp = new TemporaryFolder(); - private Dbi dbJavaComparator; - private Dbi dbLmdbComparator; - private Dbi dbCallbackComparator; - private List> dbs = new ArrayList<>(); + private static final DbiFlagSet dbiFlagSet = MDB_CREATE; + private static final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; + + @Rule + public final TemporaryFolder tmp = new TemporaryFolder(); + private Env env; private Deque list; + /** Injected by {@link #data()} with appropriate runner. */ + @Parameterized.Parameter + public DbiFactory dbiFactory; + + @Before + public void before() throws IOException { + final File path = tmp.newFile(); + final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; + env = + create(bufferProxy) + .setMapSize(KIBIBYTES.toBytes(256)) + .setMaxReaders(1) + .setMaxDbs(3) + .open(path, POSIX_MODE, MDB_NOSUBDIR); + + populateTestDataList(); + } + + @Parameterized.Parameters(name = "{index}: dbi: {0}") + public static Object[] data() { + final DbiFactory defaultComparator = new DbiFactory("defaultComparator", env -> + env.buildDbi() + .withDbName(DB_1) + .withDefaultComparator() + .withDbiFlags(dbiFlagSet) + .open()); + final DbiFactory nativeComparator = new DbiFactory("nativeComparator", env -> + env.buildDbi() + .withDbName(DB_2) + .withNativeComparator() + .withDbiFlags(dbiFlagSet) + .open()); + final DbiFactory callbackComparator = new DbiFactory("callbackComparator", env -> + env.buildDbi() + .withDbName(DB_3) + .withCallbackComparator(bufferProxy.getComparator(dbiFlagSet)) + .withDbiFlags(dbiFlagSet) + .open()); + final DbiFactory iteratorComparator = new DbiFactory("iteratorComparator", env -> + env.buildDbi() + .withDbName(DB_4) + .withIteratorComparator(bufferProxy.getComparator(dbiFlagSet)) + .withDbiFlags(dbiFlagSet) + .open()); + return new Object[] { + defaultComparator, + nativeComparator, + callbackComparator, + iteratorComparator}; + } + @After public void after() { env.close(); @@ -118,49 +177,8 @@ public void atMostTest() { verify(atMost(bb(6)), 2, 4, 6); } - @Before - public void before() throws IOException { - final File path = tmp.newFile(); - final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; - env = - create(bufferProxy) - .setMapSize(KIBIBYTES.toBytes(256)) - .setMaxReaders(1) - .setMaxDbs(3) - .open(path, POSIX_MODE, MDB_NOSUBDIR); - final DbiFlagSet dbiFlagSet = MDB_CREATE; - // Use a java comparator for start/stop keys only - dbJavaComparator = env.buildDbi() - .withDbName(DB_1) - .withDefaultComparator() - .withDbiFlags(dbiFlagSet) - .open(); - // Use LMDB comparator for start/stop keys - dbLmdbComparator = env.buildDbi() - .withDbName(DB_2) - .withNativeComparator() - .withDbiFlags(dbiFlagSet) - .open(); - // Use a java comparator for start/stop keys and as a callback comparaotr - dbCallbackComparator = env.buildDbi() - .withDbName(DB_3) - .withCallbackComparator(bufferProxy.getComparator(dbiFlagSet)) - .withDbiFlags(dbiFlagSet) - .open(); - - populateList(); - - populateDatabase(dbJavaComparator); - populateDatabase(dbLmdbComparator); - populateDatabase(dbCallbackComparator); - - dbs.add(dbJavaComparator); - dbs.add(dbLmdbComparator); - dbs.add(dbCallbackComparator); - } - - private void populateList() { + private void populateTestDataList() { list = new LinkedList<>(); list.addAll(asList(2, 3, 4, 5, 6, 7, 8, 9)); } @@ -203,14 +221,6 @@ public void closedTest() { verify(closed(bb(1), bb(7)), 2, 4, 6); } - public void closedTest1() { - verify(dbLmdbComparator, closed(bb(3), bb(7)), 4, 6); - } - - public void closedTest2() { - verify(dbJavaComparator, closed(bb(3), bb(7)), 4, 6); - } - @Test public void greaterThanBackwardTest() { verify(greaterThanBackward(bb(6)), 4, 2); @@ -226,21 +236,19 @@ public void greaterThanTest() { @Test(expected = IllegalStateException.class) public void iterableOnlyReturnedOnce() { - for (final Dbi db : dbs) { - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { + final Dbi db = getDb(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { c.iterator(); // ok c.iterator(); // fails } - } } @Test public void iterate() { - for (final Dbi db : dbs) { - populateList(); + final Dbi db = getDb(); try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { + CursorIterable c = db.iterate(txn)) { int cnt = 0; for (final KeyVal kv : c) { @@ -248,18 +256,16 @@ public void iterate() { assertThat(kv.val().getInt(), is(list.pollFirst())); } } - } } @Test(expected = IllegalStateException.class) public void iteratorOnlyReturnedOnce() { - for (final Dbi db : dbs) { + final Dbi db = getDb(); try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { + CursorIterable c = db.iterate(txn)) { c.iterator(); // ok c.iterator(); // fails } - } } @Test @@ -276,10 +282,10 @@ public void lessThanTest() { @Test(expected = NoSuchElementException.class) public void nextThrowsNoSuchElementExceptionIfNoMoreElements() { - for (final Dbi db : dbs) { - populateList(); + final Dbi db = getDb(); + populateTestDataList(); try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { + CursorIterable c = db.iterate(txn)) { final Iterator> i = c.iterator(); while (i.hasNext()) { final KeyVal kv = i.next(); @@ -289,7 +295,6 @@ public void nextThrowsNoSuchElementExceptionIfNoMoreElements() { assertThat(i.hasNext(), is(false)); i.next(); } - } } @Test @@ -341,8 +346,8 @@ public void openTest() { @Test public void removeOddElements() { - for (final Dbi db : dbs) { - verify(db, all(), 2, 4, 6, 8); + final Dbi db = getDb(); + verify(db, all(), 2, 4, 6, 8); int idx = -1; try (Txn txn = env.txnWrite()) { try (CursorIterable ci = db.iterate(txn)) { @@ -358,12 +363,11 @@ public void removeOddElements() { txn.commit(); } verify(db, all(), 4, 8); - } } @Test(expected = Env.AlreadyClosedException.class) public void nextWithClosedEnvTest() { - for (final Dbi db : dbs) { + final Dbi db = getDb(); try (Txn txn = env.txnRead()) { try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { final Iterator> c = ci.iterator(); @@ -372,12 +376,11 @@ public void nextWithClosedEnvTest() { c.next(); } } - } } @Test(expected = Env.AlreadyClosedException.class) public void removeWithClosedEnvTest() { - for (final Dbi db : dbs) { + final Dbi db = getDb(); try (Txn txn = env.txnWrite()) { try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { final Iterator> c = ci.iterator(); @@ -389,12 +392,11 @@ public void removeWithClosedEnvTest() { c.remove(); } } - } } @Test(expected = Env.AlreadyClosedException.class) public void hasNextWithClosedEnvTest() { - for (final Dbi db : dbs) { + final Dbi db = getDb(); try (Txn txn = env.txnRead()) { try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { final Iterator> c = ci.iterator(); @@ -403,21 +405,20 @@ public void hasNextWithClosedEnvTest() { c.hasNext(); } } - } } @Test(expected = Env.AlreadyClosedException.class) public void forEachRemainingWithClosedEnvTest() { - for (final Dbi db : dbs) { + final Dbi db = getDb(); try (Txn txn = env.txnRead()) { try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { final Iterator> c = ci.iterator(); env.close(); - c.forEachRemaining(keyVal -> {}); + c.forEachRemaining(keyVal -> { + }); } } - } } // @Test @@ -468,10 +469,8 @@ public void forEachRemainingWithClosedEnvTest() { // } private void verify(final KeyRange range, final int... expected) { - // Verify using all comparator types - for (final Dbi db : dbs) { - verify(range, db, expected); - } + final Dbi db = getDb(); + verify(range, db, expected); } private void verify( @@ -479,13 +478,14 @@ private void verify( verify(range, dbi, expected); } - private void verify( - final KeyRange range, final Dbi dbi, final int... expected) { + private void verify(final KeyRange range, + final Dbi dbi, + final int... expected) { final List results = new ArrayList<>(); try (Txn txn = env.txnRead(); - CursorIterable c = dbi.iterate(txn, range)) { + CursorIterable c = dbi.iterate(txn, range)) { for (final KeyVal kv : c) { final int key = kv.key().getInt(); final int val = kv.val().getInt(); @@ -499,4 +499,29 @@ private void verify( assertThat(results.get(idx), is(expected[idx])); } } + + private Dbi getDb() { + final Dbi dbi = dbiFactory.factory.apply(env); + populateDatabase(dbi); + return dbi; + } + + + // -------------------------------------------------------------------------------- + + + private static class DbiFactory { + private final String name; + private final Function, Dbi> factory; + + private DbiFactory(String name, Function, Dbi> factory) { + this.name = name; + this.factory = factory; + } + + @Override + public String toString() { + return name; + } + } } diff --git a/src/test/java/org/lmdbjava/TestUtils.java b/src/test/java/org/lmdbjava/TestUtils.java index bc9561ed..c26c1c52 100644 --- a/src/test/java/org/lmdbjava/TestUtils.java +++ b/src/test/java/org/lmdbjava/TestUtils.java @@ -33,6 +33,7 @@ final class TestUtils { public static final String DB_1 = "test-db-1"; public static final String DB_2 = "test-db-2"; public static final String DB_3 = "test-db-3"; + public static final String DB_4 = "test-db-2"; public static final int POSIX_MODE = 0664; From c0bbe73cb1d128ebb04baf842b9ed98b941a8e7f Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Tue, 28 Oct 2025 21:13:21 +0000 Subject: [PATCH 16/90] Deprecate methods using varargs flags --- src/main/java/org/lmdbjava/Cursor.java | 181 ++++++++++++++++-- src/main/java/org/lmdbjava/Dbi.java | 99 +++++++--- src/main/java/org/lmdbjava/DbiBuilder.java | 14 +- src/main/java/org/lmdbjava/DbiFlagSet.java | 2 + src/main/java/org/lmdbjava/Env.java | 179 +++++++++-------- src/main/java/org/lmdbjava/EnvFlagSet.java | 2 + src/main/java/org/lmdbjava/FlagSet.java | 33 ++++ src/main/java/org/lmdbjava/MaskedFlag.java | 4 + src/main/java/org/lmdbjava/PutFlagSet.java | 2 + .../org/lmdbjava/CursorIterablePerfTest.java | 6 +- 10 files changed, 392 insertions(+), 130 deletions(-) diff --git a/src/main/java/org/lmdbjava/Cursor.java b/src/main/java/org/lmdbjava/Cursor.java index 6d31a3d6..c1ac7374 100644 --- a/src/main/java/org/lmdbjava/Cursor.java +++ b/src/main/java/org/lmdbjava/Cursor.java @@ -20,8 +20,6 @@ import static org.lmdbjava.Dbi.KeyNotFoundException.MDB_NOTFOUND; import static org.lmdbjava.Env.SHOULD_CHECK; import static org.lmdbjava.Library.LIB; -import static org.lmdbjava.MaskedFlag.isSet; -import static org.lmdbjava.MaskedFlag.mask; import static org.lmdbjava.PutFlags.MDB_MULTIPLE; import static org.lmdbjava.PutFlags.MDB_NODUPDATA; import static org.lmdbjava.PutFlags.MDB_NOOVERWRITE; @@ -97,23 +95,49 @@ public long count() { checkRc(LIB.mdb_cursor_count(ptrCursor, longByReference)); return longByReference.longValue(); } + /** + * @deprecated Instead use {@link Cursor#delete(PutFlagSet)}. + *
+ * Delete current key/data pair. + * + *

This function deletes the key/data pair to which the cursor refers. + * + * @param flags flags (either null or {@link PutFlags#MDB_NODUPDATA} + */ + @Deprecated + public void delete(final PutFlags... flags) { + delete(PutFlagSet.of(flags)); + } + + /** + * @deprecated Instead use {@link Cursor#delete(PutFlagSet)}. + *


+ * Delete current key/data pair. + * + *

This function deletes the key/data pair to which the cursor refers. + */ + public void delete() { + delete(PutFlagSet.EMPTY); + } /** * Delete current key/data pair. * *

This function deletes the key/data pair to which the cursor refers. * - * @param f flags (either null or {@link PutFlags#MDB_NODUPDATA} + * @param flags flags (either null or {@link PutFlags#MDB_NODUPDATA} */ - public void delete(final PutFlags... f) { + public void delete(final PutFlagSet flags) { if (SHOULD_CHECK) { env.checkNotClosed(); checkNotClosed(); txn.checkReady(); txn.checkWritesAllowed(); } - final int flags = mask(f); - checkRc(LIB.mdb_cursor_del(ptrCursor, flags)); + final PutFlagSet putFlagSet = flags != null + ? flags + : PutFlagSet.EMPTY; + checkRc(LIB.mdb_cursor_del(ptrCursor, putFlagSet.getMask())); } /** @@ -235,17 +259,49 @@ public boolean prev() { } /** + * @deprecated Use {@link Cursor#put(Object, Object, PutFlagSet)} instead. + *


* Store by cursor. * *

This function stores key/data pairs into the database. * * @param key key to store * @param val data to store - * @param op options for this operation + * @param flags options for this operation + * @return true if the value was put, false if MDB_NOOVERWRITE or MDB_NODUPDATA were set and the + * key/value existed already. + */ + @Deprecated + public boolean put(final T key, final T val, final PutFlags... flags) { + return put(key, val, PutFlagSet.of(flags)); + } + + /** + * Store by cursor. + * + *

This function stores key/data pairs into the database. + * + * @param key key to store + * @param val data to store + * @return true if the value was put, false if MDB_NOOVERWRITE or MDB_NODUPDATA were set and the + * key/value existed already. + */ + public boolean put(final T key, final T val) { + return put(key, val, PutFlagSet.EMPTY); + } + + /** + * Store by cursor. + * + *

This function stores key/data pairs into the database. + * + * @param key key to store + * @param val data to store + * @param flags options for this operation * @return true if the value was put, false if MDB_NOOVERWRITE or MDB_NODUPDATA were set and the * key/value existed already. */ - public boolean put(final T key, final T val, final PutFlags... op) { + public boolean put(final T key, final T val, final PutFlagSet flags) { if (SHOULD_CHECK) { requireNonNull(key); requireNonNull(val); @@ -256,12 +312,14 @@ public boolean put(final T key, final T val, final PutFlags... op) { } final Pointer transientKey = kv.keyIn(key); final Pointer transientVal = kv.valIn(val); - final int mask = mask(op); - final int rc = LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), mask); + final PutFlagSet putFlagSet = flags != null + ? flags + : PutFlagSet.EMPTY; + final int rc = LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), putFlagSet.getMask()); if (rc == MDB_KEYEXIST) { - if (isSet(mask, MDB_NOOVERWRITE)) { + if (putFlagSet.isSet(MDB_NOOVERWRITE)) { kv.valOut(); // marked as in,out in LMDB C docs - } else if (!isSet(mask, MDB_NODUPDATA)) { + } else if (!putFlagSet.isSet(MDB_NODUPDATA)) { checkRc(rc); } return false; @@ -274,6 +332,42 @@ public boolean put(final T key, final T val, final PutFlags... op) { return true; } + /** + * @deprecated Use {@link Cursor#put(Object, Object, PutFlagSet)} instead. + *


+ * Put multiple values into the database in one MDB_MULTIPLE operation. + * + *

The database must have been opened with {@link DbiFlags#MDB_DUPFIXED}. The buffer must + * contain fixed-sized values to be inserted. The size of each element is calculated from the + * buffer's size divided by the given element count. For example, to populate 10 X 4 byte integers + * at once, present a buffer of 40 bytes and specify the element as 10. + * + * @param key key to store in the database (not null) + * @param val value to store in the database (not null) + * @param elements number of elements contained in the passed value buffer + * @param flags options for operation (must set MDB_MULTIPLE) + */ + @Deprecated + public void putMultiple(final T key, final T val, final int elements, final PutFlags... flags) { + putMultiple(key, val, elements, PutFlagSet.of(flags)); + } + + /** + * Put multiple values into the database in one MDB_MULTIPLE operation. + * + *

The database must have been opened with {@link DbiFlags#MDB_DUPFIXED}. The buffer must + * contain fixed-sized values to be inserted. The size of each element is calculated from the + * buffer's size divided by the given element count. For example, to populate 10 X 4 byte integers + * at once, present a buffer of 40 bytes and specify the element as 10. + * + * @param key key to store in the database (not null) + * @param val value to store in the database (not null) + * @param elements number of elements contained in the passed value buffer + */ + public void putMultiple(final T key, final T val, final int elements) { + putMultiple(key, val, elements, PutFlagSet.EMPTY); + } + /** * Put multiple values into the database in one MDB_MULTIPLE operation. * @@ -285,9 +379,10 @@ public boolean put(final T key, final T val, final PutFlags... op) { * @param key key to store in the database (not null) * @param val value to store in the database (not null) * @param elements number of elements contained in the passed value buffer - * @param op options for operation (must set MDB_MULTIPLE) + * @param flags options for operation (must set MDB_MULTIPLE) + * Either a {@link PutFlagSet} or a single {@link PutFlags}. */ - public void putMultiple(final T key, final T val, final int elements, final PutFlags... op) { + public void putMultiple(final T key, final T val, final int elements, final PutFlagSet flags) { if (SHOULD_CHECK) { requireNonNull(txn); requireNonNull(key); @@ -296,13 +391,15 @@ public void putMultiple(final T key, final T val, final int elements, final PutF txn.checkReady(); txn.checkWritesAllowed(); } - final int mask = mask(op); - if (SHOULD_CHECK && !isSet(mask, MDB_MULTIPLE)) { + final PutFlagSet putFlagSet = flags != null + ? flags + : PutFlagSet.EMPTY; + if (SHOULD_CHECK && !putFlagSet.isSet(MDB_MULTIPLE)) { throw new IllegalArgumentException("Must set " + MDB_MULTIPLE + " flag"); } final Pointer transientKey = txn.kv().keyIn(key); final Pointer dataPtr = txn.kv().valInMulti(val, elements); - final int rc = LIB.mdb_cursor_put(ptrCursor, txn.kv().pointerKey(), dataPtr, mask); + final int rc = LIB.mdb_cursor_put(ptrCursor, txn.kv().pointerKey(), dataPtr, putFlagSet.getMask()); checkRc(rc); ReferenceUtil.reachabilityFence0(transientKey); ReferenceUtil.reachabilityFence0(dataPtr); @@ -334,6 +431,8 @@ public void renew(final Txn newTxn) { } /** + * @deprecated Use {@link Cursor#reserve(Object, int, PutFlagSet)} instead. + *


* Reserve space for data of the given size, but don't copy the given val. Instead, return a * pointer to the reserved space, which the caller can fill in later - before the next update * operation or the transaction ends. This saves an extra memcpy if the data is being generated @@ -344,10 +443,46 @@ public void renew(final Txn newTxn) { * * @param key key to store in the database (not null) * @param size size of the value to be stored in the database (not null) - * @param op options for this operation + * @param flags options for this operation + * @return a buffer that can be used to modify the value + */ + @Deprecated + public T reserve(final T key, final int size, final PutFlags... flags) { + return reserve(key, size, PutFlagSet.of(flags)); + } + + /** + * Reserve space for data of the given size, but don't copy the given val. Instead, return a + * pointer to the reserved space, which the caller can fill in later - before the next update + * operation or the transaction ends. This saves an extra {@code memcpy} if the data is being generated + * later. LMDB does nothing else with this memory, the caller is expected to modify all the + * space requested. + * + *

This flag must not be specified if the database was opened with MDB_DUPSORT + * + * @param key key to store in the database (not null) + * @param size size of the value to be stored in the database (not null) + * @return a buffer that can be used to modify the value + */ + public T reserve(final T key, final int size) { + return reserve(key, size, PutFlagSet.EMPTY); + } + + /** + * Reserve space for data of the given size, but don't copy the given val. Instead, return a + * pointer to the reserved space, which the caller can fill in later - before the next update + * operation or the transaction ends. This saves an extra memcpy if the data is being generated + * later. LMDB does nothing else with this memory, the caller is expected to modify all of the + * space requested. + * + *

This flag must not be specified if the database was opened with MDB_DUPSORT + * + * @param key key to store in the database (not null) + * @param size size of the value to be stored in the database (not null) + * @param flags options for this operation * @return a buffer that can be used to modify the value */ - public T reserve(final T key, final int size, final PutFlags... op) { + public T reserve(final T key, final int size, final PutFlagSet flags) { if (SHOULD_CHECK) { requireNonNull(key); env.checkNotClosed(); @@ -357,8 +492,12 @@ public T reserve(final T key, final int size, final PutFlags... op) { } final Pointer transientKey = kv.keyIn(key); final Pointer transientVal = kv.valIn(size); - final int flags = mask(op) | MDB_RESERVE.getMask(); - checkRc(LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), flags)); + final PutFlagSet putFlagSet = flags != null + ? flags + : PutFlagSet.EMPTY; + // This is inconsistent with putMultiple which require MDB_MULTIPLE to be in the set. + final int flagsMask = putFlagSet.getMaskWith(MDB_RESERVE); + checkRc(LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), flagsMask)); kv.valOut(); ReferenceUtil.reachabilityFence0(transientKey); ReferenceUtil.reachabilityFence0(transientVal); diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index c66dc780..bcb1ccfc 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -31,7 +31,7 @@ import static org.lmdbjava.PutFlags.MDB_RESERVE; import static org.lmdbjava.ResultCodeMapper.checkRc; -import java.nio.charset.StandardCharsets; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -261,6 +261,31 @@ public byte[] getName() { return name == null ? null : Arrays.copyOf(name, name.length); } + public String getNameAsString() { + return getNameAsString(Env.DEFAULT_NAME_CHARSET); + } + + + /** + * Obtains the name of this database, using the supplied {@link Charset}. + * + * @return The name of the database. If this is the unnamed database an empty + * string will be returned. + * @throws RuntimeException if the name can't be decoded. + */ + public String getNameAsString(final Charset charset) { + if (name == null) { + return ""; + } else { + // Assume a UTF8 encoding as we don't know, thus swallow if it fails + try { + return new String(name, requireNonNull(charset)); + } catch (Exception e) { + throw new RuntimeException("Unable to decode database name using charset " + charset); + } + } + } + /** * Iterate the database from the first item and forwards. * @@ -345,18 +370,22 @@ public Cursor openCursor(final Txn txn) { * * @param key key to store in the database (not null) * @param val value to store in the database (not null) - * @see #put(org.lmdbjava.Txn, java.lang.Object, java.lang.Object, org.lmdbjava.PutFlags...) + * @see #put(Txn, Object, Object, PutFlagSet) */ public void put(final T key, final T val) { try (Txn txn = env.txnWrite()) { - put(txn, key, val); + put(txn, key, val, PutFlagSet.EMPTY); txn.commit(); } } /** + * @deprecated Use {@link Dbi#put(Txn, Object, Object, PutFlagSet)} instead, with a statically + * held {@link PutFlagSet}. + *


+ *

* Store a key/value pair in the database. - * + *

*

This function stores key/data pairs in the database. The default behavior is to enter the * new key/data pair, replacing any previously existing key if duplicates are disallowed, or * adding a duplicate data item if duplicates are allowed ({@link DbiFlags#MDB_DUPSORT}). @@ -368,7 +397,40 @@ public void put(final T key, final T val) { * @return true if the value was put, false if MDB_NOOVERWRITE or MDB_NODUPDATA were set and the * key/value existed already. */ + @Deprecated public boolean put(final Txn txn, final T key, final T val, final PutFlags... flags) { + return put(txn, key, val, PutFlagSet.of(flags)); + } + + /** + * Store a key/value pair in the database. + * + * @param txn transaction handle (not null; not committed; must be R-W) + * @param key key to store in the database (not null) + * @param val value to store in the database (not null) + * @return true if the value was put, false if MDB_NOOVERWRITE or MDB_NODUPDATA were set and the + * key/value existed already. + * @see #put(Txn, Object, Object, PutFlagSet) + */ + public boolean put(final Txn txn, final T key, final T val) { + return put(txn, key, val, PutFlagSet.EMPTY); + } + + /** + * Store a key/value pair in the database. + * + *

This function stores key/data pairs in the database. The default behavior is to enter the + * new key/data pair, replacing any previously existing key if duplicates are disallowed, or + * adding a duplicate data item if duplicates are allowed ({@link DbiFlags#MDB_DUPSORT}). + * + * @param txn transaction handle (not null; not committed; must be R-W) + * @param key key to store in the database (not null) + * @param val value to store in the database (not null) + * @param flags Special options for this operation. + * @return true if the value was put, false if MDB_NOOVERWRITE or MDB_NODUPDATA were set and the + * key/value existed already. + */ + public boolean put(final Txn txn, final T key, final T val, final PutFlagSet flags) { if (SHOULD_CHECK) { requireNonNull(txn); requireNonNull(key); @@ -377,14 +439,14 @@ public boolean put(final Txn txn, final T key, final T val, final PutFlags... txn.checkReady(); txn.checkWritesAllowed(); } + final PutFlagSet flagSet = flags != null ? flags : PutFlagSet.empty(); final Pointer transientKey = txn.kv().keyIn(key); final Pointer transientVal = txn.kv().valIn(val); - final int mask = mask(flags); - final int rc = LIB.mdb_put(txn.pointer(), ptr, txn.kv().pointerKey(), txn.kv().pointerVal(), mask); + final int rc = LIB.mdb_put(txn.pointer(), ptr, txn.kv().pointerKey(), txn.kv().pointerVal(), flagSet.getMask()); if (rc == MDB_KEYEXIST) { - if (isSet(mask, MDB_NOOVERWRITE)) { + if (flagSet.isSet(MDB_NOOVERWRITE)) { txn.kv().valOut(); // marked as in,out in LMDB C docs - } else if (!isSet(mask, MDB_NODUPDATA)) { + } else if (!flagSet.isSet(MDB_NODUPDATA)) { checkRc(rc); } return false; @@ -461,23 +523,16 @@ private void clean() { cleaned = true; } - private String getNameAsString() { - if (name == null) { - return ""; - } else { - try { - // Assume a UTF8 encoding as we don't know, thus swallow if it fails - return new String(name, StandardCharsets.UTF_8); - } catch (Exception e) { - return "?"; - } - } - } - @Override public String toString() { + String name; + try { + name = getNameAsString(); + } catch (Exception e) { + name = "?"; + } return "Dbi{" + - "name='" + getNameAsString() + + "name='" + name + "', dbiFlagSet=" + dbiFlagSet + '}'; } diff --git a/src/main/java/org/lmdbjava/DbiBuilder.java b/src/main/java/org/lmdbjava/DbiBuilder.java index dcf34d0a..2b4e6ad8 100644 --- a/src/main/java/org/lmdbjava/DbiBuilder.java +++ b/src/main/java/org/lmdbjava/DbiBuilder.java @@ -28,6 +28,7 @@ */ public class DbiBuilder { + private final Env env; private final BufferProxy proxy; private final boolean readOnly; @@ -56,7 +57,7 @@ public DbiBuilderStage2 withDbName(final String name) { // Null name is allowed so no null check final byte[] nameBytes = name == null ? null - : name.getBytes(StandardCharsets.UTF_8); + : name.getBytes(Env.DEFAULT_NAME_CHARSET); return withDbName(nameBytes); } @@ -252,7 +253,7 @@ private DbiBuilderStage3(DbiBuilderStage2 dbiBuilderStage2) { * Clears all flags currently set by previous calls to * {@link DbiBuilderStage3#withDbiFlags(Collection)}, * {@link DbiBuilderStage3#withDbiFlags(DbiFlags...)} - * or {@link DbiBuilderStage3#setDbiFlag(DbiFlags)}. + * or {@link DbiBuilderStage3#addDbiFlag(DbiFlags)}. *

* * @param dbiFlags to open the database with. @@ -277,7 +278,7 @@ public DbiBuilderStage3 withDbiFlags(final Collection dbiFlags) { * Clears all flags currently set by previous calls to * {@link DbiBuilderStage3#withDbiFlags(Collection)}, * {@link DbiBuilderStage3#withDbiFlags(DbiFlags...)} - * or {@link DbiBuilderStage3#setDbiFlag(DbiFlags)}. + * or {@link DbiBuilderStage3#addDbiFlag(DbiFlags)}. *

* * @param dbiFlags to open the database with. @@ -302,7 +303,7 @@ public DbiBuilderStage3 withDbiFlags(final DbiFlags... dbiFlags) { * Clears all flags currently set by previous calls to * {@link DbiBuilderStage3#withDbiFlags(Collection)}, * {@link DbiBuilderStage3#withDbiFlags(DbiFlags...)} - * or {@link DbiBuilderStage3#setDbiFlag(DbiFlags)}. + * or {@link DbiBuilderStage3#addDbiFlag(DbiFlags)}. *

* * @param dbiFlagSet to open the database with. @@ -320,12 +321,12 @@ public DbiBuilderStage3 withDbiFlags(final DbiFlagSet dbiFlagSet) { * Adds a dbiFlag to those flags already added to this builder by * {@link DbiBuilderStage3#withDbiFlags(DbiFlags...)}, * {@link DbiBuilderStage3#withDbiFlags(Collection)} - * or {@link DbiBuilderStage3#setDbiFlag(DbiFlags)}. + * or {@link DbiBuilderStage3#addDbiFlag(DbiFlags)}. * * @param dbiFlag to open the database with. A null value is a no-op. * @return this builder instance. */ - public DbiBuilderStage3 setDbiFlag(final DbiFlags dbiFlag) { + public DbiBuilderStage3 addDbiFlag(final DbiFlags dbiFlag) { this.flagSetBuilder.setFlag(dbiFlag); return this; } @@ -409,7 +410,6 @@ private Dbi open(final Txn txn, final ComparatorType comparatorType = dbiBuilderStage2.comparatorType; final Comparator comparator = getComparator(dbiBuilder, comparatorType, dbiFlagSet); final boolean useNativeCallback = comparatorType == ComparatorType.CALLBACK; - return new Dbi<>( dbiBuilder.env, txn, diff --git a/src/main/java/org/lmdbjava/DbiFlagSet.java b/src/main/java/org/lmdbjava/DbiFlagSet.java index 28f5e4f1..5edf10c7 100644 --- a/src/main/java/org/lmdbjava/DbiFlagSet.java +++ b/src/main/java/org/lmdbjava/DbiFlagSet.java @@ -21,6 +21,8 @@ public interface DbiFlagSet extends FlagSet { + DbiFlagSet EMPTY = DbiFlagSetImpl.EMPTY; + static DbiFlagSet empty() { return DbiFlagSetImpl.EMPTY; } diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index efc4e240..55c88ec1 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -29,6 +29,8 @@ import java.io.File; import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -46,8 +48,11 @@ */ public final class Env implements AutoCloseable { - /** Java system property name that can be set to disable optional checks. */ + /** + * Java system property name that can be set to disable optional checks. + */ public static final String DISABLE_CHECKS_PROP = "lmdbjava.disable.checks"; + public static final Charset DEFAULT_NAME_CHARSET = StandardCharsets.UTF_8; /** * Indicates whether optional checks should be applied in LmdbJava. Optional checks are only @@ -88,7 +93,7 @@ public static Builder create() { /** * Create an {@link Env} using the passed {@link BufferProxy}. * - * @param buffer type + * @param buffer type * @param proxy the proxy to use (required) * @return the environment (never null) */ @@ -100,13 +105,15 @@ public static Builder create(final BufferProxy proxy) { * Opens an environment with a single default database in 0664 mode using the {@link * ByteBufferProxy#PROXY_OPTIMAL}. * - * @param path file system destination - * @param size size in megabytes + * @param path file system destination + * @param size size in megabytes * @param flags the flags for this new environment * @return env the environment (never null) */ public static Env open(final File path, final int size, final EnvFlags... flags) { - return new Builder<>(PROXY_OPTIMAL).setMapSize(size * 1_024L * 1_024L).open(path, flags); + return new Builder<>(PROXY_OPTIMAL) + .setMapSize(size * 1_024L * 1_024L) + .open(path, flags); } /** @@ -159,7 +166,7 @@ public void copy(final File path) { * transactions, because it employs a read-only transaction. See long-lived transactions under * "Caveats" in the LMDB native documentation. * - * @param path writable destination path as described above + * @param path writable destination path as described above * @param flags special options for this copy */ public void copy(final File path, final CopyFlagSet flags) { @@ -183,7 +190,7 @@ public List getDbiNames() { final List result = new ArrayList<>(); final Dbi names = openDbi((byte[]) null); try (Txn txn = txnRead(); - Cursor cursor = names.openCursor(txn)) { + Cursor cursor = names.openCursor(txn)) { if (!cursor.first()) { return Collections.emptyList(); } @@ -263,6 +270,7 @@ public boolean isReadOnly() { /** * Open (and optionally creates, if {@link DbiFlags#MDB_CREATE} is set) * a {@link Dbi} using a builder. + * * @return A new builder instance for creating/opening a {@link Dbi}. */ public DbiBuilder buildDbi() { @@ -270,13 +278,12 @@ public DbiBuilder buildDbi() { } /** + * @param name name of the database (or null if no name is required) + * @param flags to open the database with + * @return a database that is ready to use * @deprecated Instead use {@link Env#buildDbi()} * Convenience method that opens a {@link Dbi} with a UTF-8 database name and default {@link * Comparator} that is not invoked from native code. - * - * @param name name of the database (or null if no name is required) - * @param flags to open the database with - * @return a database that is ready to use */ @Deprecated() public Dbi openDbi(final String name, final DbiFlags... flags) { @@ -285,6 +292,11 @@ public Dbi openDbi(final String name, final DbiFlags... flags) { } /** + * @param name name of the database (or null if no name is required) + * @param comparator custom comparator for cursor start/stop key comparisons. If null, LMDB's + * comparator will be used. + * @param flags to open the database with + * @return a database that is ready to use * @deprecated Instead use {@link Env#buildDbi()} * Convenience method that opens a {@link Dbi} with a UTF-8 database name and associated {@link * Comparator} for use by {@link CursorIterable} when comparing start/stop keys. @@ -293,88 +305,83 @@ public Dbi openDbi(final String name, final DbiFlags... flags) { * LMDB uses for its insertion order (for the type of data that will be stored in the database), * or you fully understand the implications of them behaving differently. LMDB's comparator is * unsigned lexicographical, unless {@link DbiFlags#MDB_INTEGERKEY} is used. - * - * @param name name of the database (or null if no name is required) - * @param comparator custom comparator for cursor start/stop key comparisons. If null, LMDB's - * comparator will be used. - * @param flags to open the database with - * @return a database that is ready to use */ @Deprecated() - public Dbi openDbi( - final String name, final Comparator comparator, final DbiFlags... flags) { + public Dbi openDbi(final String name, + final Comparator comparator, + final DbiFlags... flags) { final byte[] nameBytes = name == null ? null : name.getBytes(UTF_8); return openDbi(nameBytes, comparator, false, flags); } /** + * @param name name of the database (or null if no name is required) + * @param comparator custom comparator for cursor start/stop key comparisons and optionally for + * LMDB to call back to. If null, LMDB's comparator will be used. + * @param nativeCb whether LMDB native code calls back to the Java comparator + * @param flags to open the database with + * @return a database that is ready to use * @deprecated Instead use {@link Env#buildDbi()} * Convenience method that opens a {@link Dbi} with a UTF-8 database name and associated {@link * Comparator}. The comparator will be used by {@link CursorIterable} when comparing start/stop * keys as a minimum. If nativeCb is {@code true}, this comparator will also be called by LMDB to * determine insertion/iteration order. Calling back to a java comparator may significantly impact * performance. - * - * @param name name of the database (or null if no name is required) - * @param comparator custom comparator for cursor start/stop key comparisons and optionally for - * LMDB to call back to. If null, LMDB's comparator will be used. - * @param nativeCb whether LMDB native code calls back to the Java comparator - * @param flags to open the database with - * @return a database that is ready to use */ @Deprecated() - public Dbi openDbi( - final String name, - final Comparator comparator, - final boolean nativeCb, - final DbiFlags... flags) { + public Dbi openDbi(final String name, + final Comparator comparator, + final boolean nativeCb, + final DbiFlags... flags) { final byte[] nameBytes = name == null ? null : name.getBytes(UTF_8); return openDbi(nameBytes, comparator, nativeCb, flags); } /** + * @param name name of the database (or null if no name is required) + * @param flags to open the database with + * @return a database that is ready to use * @deprecated Instead use {@link Env#buildDbi()} + *
* Convenience method that opens a {@link Dbi} with a default {@link Comparator} that is not * invoked from native code. - * - * @param name name of the database (or null if no name is required) - * @param flags to open the database with - * @return a database that is ready to use */ @Deprecated() - public Dbi openDbi(final byte[] name, final DbiFlags... flags) { + public Dbi openDbi(final byte[] name, + final DbiFlags... flags) { return openDbi(name, null, false, flags); } /** + * @param name name of the database (or null if no name is required) + * @param comparator custom comparator callback (or null to use LMDB default) + * @param flags to open the database with + * @return a database that is ready to use * @deprecated Instead use {@link Env#buildDbi()} + *
* Convenience method that opens a {@link Dbi} with an associated {@link Comparator} that is not * invoked from native code. - * - * @param name name of the database (or null if no name is required) - * @param comparator custom comparator callback (or null to use LMDB default) - * @param flags to open the database with - * @return a database that is ready to use */ @Deprecated() - public Dbi openDbi( - final byte[] name, final Comparator comparator, final DbiFlags... flags) { + public Dbi openDbi(final byte[] name, + final Comparator comparator, + final DbiFlags... flags) { return openDbi(name, comparator, false, flags); } /** + * @param name name of the database (or null if no name is required) + * @param comparator custom comparator callback (or null to use LMDB default) + * @param nativeCb whether native code calls back to the Java comparator + * @param flags to open the database with + * @return a database that is ready to use * @deprecated Instead use {@link Env#buildDbi()} + *
* Convenience method that opens a {@link Dbi} with an associated {@link Comparator} that may be * invoked from native code if specified. * *

This method will automatically commit the private transaction before returning. This ensures * the Dbi is available in the Env. - * - * @param name name of the database (or null if no name is required) - * @param comparator custom comparator callback (or null to use LMDB default) - * @param nativeCb whether native code calls back to the Java comparator - * @param flags to open the database with - * @return a database that is ready to use */ @Deprecated() public Dbi openDbi( @@ -390,6 +397,12 @@ public Dbi openDbi( } /** + * @param txn transaction to use (required; not closed) + * @param name name of the database (or null if no name is required) + * @param comparator custom comparator callback (or null to use LMDB default) + * @param nativeCb whether native LMDB code should call back to the Java comparator + * @param flags to open the database with + * @return a database that is ready to use * @deprecated Instead use {@link Env#buildDbi()} * Open the {@link Dbi} using the passed {@link Txn}. * @@ -409,13 +422,6 @@ public Dbi openDbi( * *

This method (and its overloaded convenience variants) must not be called from concurrent * threads. - * - * @param txn transaction to use (required; not closed) - * @param name name of the database (or null if no name is required) - * @param comparator custom comparator callback (or null to use LMDB default) - * @param nativeCb whether native LMDB code should call back to the Java comparator - * @param flags to open the database with - * @return a database that is ready to use */ @Deprecated() public Dbi openDbi( @@ -455,7 +461,7 @@ public Stat stat() { * Flushes the data buffers to disk. * * @param force force a synchronous flush (otherwise if the environment has the MDB_NOSYNC flag - * set the flushes will be omitted, and with MDB_MAPASYNC they will be asynchronous) + * set the flushes will be omitted, and with MDB_MAPASYNC they will be asynchronous) */ public void sync(final boolean force) { if (closed) { @@ -466,13 +472,12 @@ public void sync(final boolean force) { } /** - * @deprecated Instead use {@link Env#txn(Txn, TxnFlagSet)} - * - * Obtain a transaction with the requested parent and flags. - * * @param parent parent transaction (may be null if no parent) - * @param flags applicable flags (eg for a reusable, read-only transaction) + * @param flags applicable flags (eg for a reusable, read-only transaction) * @return a transaction (never null) + * @deprecated Instead use {@link Env#txn(Txn, TxnFlagSet)} + *

+ * Obtain a transaction with the requested parent and flags. */ @Deprecated public Txn txn(final Txn parent, final TxnFlags... flags) { @@ -495,7 +500,7 @@ public Txn txn(final Txn parent) { * Obtain a transaction with the requested parent and flags. * * @param parent parent transaction (may be null if no parent) - * @param flag applicable flag (eg for a reusable, read-only transaction) + * @param flag applicable flag (eg for a reusable, read-only transaction) * @return a transaction (never null) */ public Txn txn(final Txn parent, final TxnFlags flag) { @@ -507,9 +512,9 @@ public Txn txn(final Txn parent, final TxnFlags flag) { * Obtain a transaction with the requested parent and flags. * * @param parent parent transaction (may be null if no parent) - * @param flags applicable flags (e.g. for a reusable, read-only transaction). - * If the set of flags is used frequently it is recommended to hold - * a static instance of the {@link TxnFlagSet} for re-use. + * @param flags applicable flags (e.g. for a reusable, read-only transaction). + * If the set of flags is used frequently it is recommended to hold + * a static instance of the {@link TxnFlagSet} for re-use. * @return a transaction (never null) */ public Txn txn(final Txn parent, final TxnFlagSet flags) { @@ -581,23 +586,31 @@ public int readerCheck() { return resultPtr.intValue(); } - /** Object has already been closed and the operation is therefore prohibited. */ + /** + * Object has already been closed and the operation is therefore prohibited. + */ public static final class AlreadyClosedException extends LmdbException { private static final long serialVersionUID = 1L; - /** Creates a new instance. */ + /** + * Creates a new instance. + */ public AlreadyClosedException() { super("Environment has already been closed"); } } - /** Object has already been opened and the operation is therefore prohibited. */ + /** + * Object has already been opened and the operation is therefore prohibited. + */ public static final class AlreadyOpenException extends LmdbException { private static final long serialVersionUID = 1L; - /** Creates a new instance. */ + /** + * Creates a new instance. + */ public AlreadyOpenException() { super("Environment has already been opened"); } @@ -625,8 +638,8 @@ public static final class Builder { /** * Opens the environment. * - * @param path file system destination - * @param mode Unix permissions to set on created files and semaphores + * @param path file system destination + * @param mode Unix permissions to set on created files and semaphores * @param flags the flags for this new environment * @return an environment ready for use */ @@ -657,7 +670,7 @@ public Env open(final File path, final int mode, final EnvFlags... flags) { /** * Opens the environment with 0664 mode. * - * @param path file system destination + * @param path file system destination * @param flags the flags for this new environment * @return an environment ready for use */ @@ -711,7 +724,9 @@ public Builder setMaxReaders(final int readers) { } } - /** File is not a valid LMDB file. */ + /** + * File is not a valid LMDB file. + */ public static final class FileInvalidException extends LmdbNativeException { static final int MDB_INVALID = -30_793; @@ -722,7 +737,9 @@ public static final class FileInvalidException extends LmdbNativeException { } } - /** The specified copy destination is invalid. */ + /** + * The specified copy destination is invalid. + */ public static final class InvalidCopyDestination extends LmdbException { private static final long serialVersionUID = 1L; @@ -737,7 +754,9 @@ public InvalidCopyDestination(final String message) { } } - /** Environment mapsize reached. */ + /** + * Environment mapsize reached. + */ public static final class MapFullException extends LmdbNativeException { static final int MDB_MAP_FULL = -30_792; @@ -748,7 +767,9 @@ public static final class MapFullException extends LmdbNativeException { } } - /** Environment maxreaders reached. */ + /** + * Environment maxreaders reached. + */ public static final class ReadersFullException extends LmdbNativeException { static final int MDB_READERS_FULL = -30_790; @@ -759,7 +780,9 @@ public static final class ReadersFullException extends LmdbNativeException { } } - /** Environment version mismatch. */ + /** + * Environment version mismatch. + */ public static final class VersionMismatchException extends LmdbNativeException { static final int MDB_VERSION_MISMATCH = -30_794; diff --git a/src/main/java/org/lmdbjava/EnvFlagSet.java b/src/main/java/org/lmdbjava/EnvFlagSet.java index f1bab2d0..a3c8d1fa 100644 --- a/src/main/java/org/lmdbjava/EnvFlagSet.java +++ b/src/main/java/org/lmdbjava/EnvFlagSet.java @@ -21,6 +21,8 @@ public interface EnvFlagSet extends FlagSet { + EnvFlagSet EMPTY = EnvFlagSetImpl.EMPTY; + static EnvFlagSet empty() { return EnvFlagSetImpl.EMPTY; } diff --git a/src/main/java/org/lmdbjava/FlagSet.java b/src/main/java/org/lmdbjava/FlagSet.java index 89b955a0..668e33ba 100644 --- a/src/main/java/org/lmdbjava/FlagSet.java +++ b/src/main/java/org/lmdbjava/FlagSet.java @@ -28,24 +28,57 @@ */ public interface FlagSet extends Iterable { + /** + * @return The combined mask for this flagSet. + */ int getMask(); + /** + * @return The result of combining the mask of this {@link FlagSet} + * with the mask of the other {@link FlagSet}. + */ + default int getMaskWith(final FlagSet other) { + if (other != null) { + return MaskedFlag.mask(getMask(), other.getMask()); + } else { + return getMask(); + } + } + + /** + * @return The set of flags in this {@link FlagSet}. + */ Set getFlags(); + /** + * @return True if flag is non-null and included in this {@link FlagSet}. + */ boolean isSet(T flag); + /** + * @return The size of this {@link FlagSet} + */ default int size() { return getFlags().size(); } + /** + * @return True if this {@link FlagSet} is empty. + */ default boolean isEmpty() { return getFlags().isEmpty(); } + /** + * @return The {@link Iterator} (in no particular order) for the flags in this {@link FlagSet}. + */ default Iterator iterator() { return getFlags().iterator(); } + /** + * Convert this {@link FlagSet} to a string for use in toString methods. + */ static String asString(final FlagSet flagSet) { Objects.requireNonNull(flagSet); final String flagsStr = flagSet.getFlags() diff --git a/src/main/java/org/lmdbjava/MaskedFlag.java b/src/main/java/org/lmdbjava/MaskedFlag.java index f2f08274..271bb122 100644 --- a/src/main/java/org/lmdbjava/MaskedFlag.java +++ b/src/main/java/org/lmdbjava/MaskedFlag.java @@ -59,6 +59,10 @@ static int mask(final M... flags) { } } + static int mask(final int mask1, final int mask2) { + return mask1 | mask2; + } + static int mask(final Collection flags) { if (flags == null || flags.isEmpty()) { return EMPTY_MASK; diff --git a/src/main/java/org/lmdbjava/PutFlagSet.java b/src/main/java/org/lmdbjava/PutFlagSet.java index 85de014b..9d3a7288 100644 --- a/src/main/java/org/lmdbjava/PutFlagSet.java +++ b/src/main/java/org/lmdbjava/PutFlagSet.java @@ -21,6 +21,8 @@ public interface PutFlagSet extends FlagSet { + PutFlagSet EMPTY = PutFlagSetImpl.EMPTY; + static PutFlagSet empty() { return PutFlagSetImpl.EMPTY; } diff --git a/src/test/java/org/lmdbjava/CursorIterablePerfTest.java b/src/test/java/org/lmdbjava/CursorIterablePerfTest.java index e2c54346..c5240215 100644 --- a/src/test/java/org/lmdbjava/CursorIterablePerfTest.java +++ b/src/test/java/org/lmdbjava/CursorIterablePerfTest.java @@ -110,6 +110,8 @@ private void populateDatabases(final boolean randomOrder) { data = this.data; } + final PutFlagSet noOverwriteAndAppendFlagSet = PutFlagSet.of(MDB_NOOVERWRITE, MDB_APPEND); + for (int round = 0; round < 3; round++) { System.out.println("round: " + round + " -----------------------------------------"); @@ -122,14 +124,14 @@ private void populateDatabases(final boolean randomOrder) { } } - final String dbName = new String(db.getName(), StandardCharsets.UTF_8); + final String dbName = db.getNameAsString(StandardCharsets.UTF_8); final Instant start = Instant.now(); try (Txn txn = env.txnWrite()) { for (final Integer i : data) { if (randomOrder) { db.put(txn, bb(i), bb(i + 1), MDB_NOOVERWRITE); } else { - db.put(txn, bb(i), bb(i + 1), MDB_NOOVERWRITE, MDB_APPEND); + db.put(txn, bb(i), bb(i + 1), noOverwriteAndAppendFlagSet); } } txn.commit(); From 4fd89fff1755e1572929da3097809dfdad5fa1bb Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Wed, 29 Oct 2025 11:32:38 +0000 Subject: [PATCH 17/90] Add int key compare method to (Direct|Byte)BufferProxy --- .../java/org/lmdbjava/AbstractFlagSet.java | 27 +- src/main/java/org/lmdbjava/BufferProxy.java | 27 +- .../java/org/lmdbjava/ByteArrayProxy.java | 31 -- src/main/java/org/lmdbjava/ByteBufProxy.java | 10 - .../java/org/lmdbjava/ByteBufferProxy.java | 122 ++++---- .../java/org/lmdbjava/DirectBufferProxy.java | 60 ++-- src/main/java/org/lmdbjava/FlagSet.java | 39 ++- .../org/lmdbjava/ByteBufferProxyTest.java | 90 +++++- .../CursorIterableIntegerKeyTest.java | 293 +++++++++++------- .../java/org/lmdbjava/CursorIterableTest.java | 45 +-- .../java/org/lmdbjava/DbiBuilderTest.java | 1 - 11 files changed, 453 insertions(+), 292 deletions(-) diff --git a/src/main/java/org/lmdbjava/AbstractFlagSet.java b/src/main/java/org/lmdbjava/AbstractFlagSet.java index 5e62b437..25aa328b 100644 --- a/src/main/java/org/lmdbjava/AbstractFlagSet.java +++ b/src/main/java/org/lmdbjava/AbstractFlagSet.java @@ -64,7 +64,6 @@ public boolean isSet(final T flag) { // Probably cheaper to compare the masks than to use EnumSet.contains() return flag != null && MaskedFlag.isSet(mask, flag); - } /** @@ -93,9 +92,7 @@ public Iterator iterator() { @Override public boolean equals(Object object) { - if (this == object) return true; -// if (object == null || getClass() != object.getClass()) return false; - return FlagSet.equals(this, (FlagSet) object); + return FlagSet.equals(this, object); } @Override @@ -141,6 +138,15 @@ public boolean isSet(final T flag) { return this.flag == flag; } + @Override + public boolean areAnySet(FlagSet flags) { + if (flags == null) { + return false; + } else { + return flags.isSet(this.flag); + } + } + @Override public int size() { return 1; @@ -167,9 +173,7 @@ public String toString() { @Override public boolean equals(Object object) { - if (this == object) return true; -// if (object == null || getClass() != object.getClass()) return false; - return FlagSet.equals(this, (FlagSet) object); + return FlagSet.equals(this, object); } @Override @@ -205,6 +209,11 @@ public boolean isSet(final T flag) { return false; } + @Override + public boolean areAnySet(final FlagSet flags) { + return false; + } + @Override public int size() { return 0; @@ -227,9 +236,7 @@ public String toString() { @Override public boolean equals(Object object) { - if (this == object) return true; -// if (object == null || getClass() != object.getClass()) return false; - return FlagSet.equals(this, (FlagSet) object); + return FlagSet.equals(this, object); } @Override diff --git a/src/main/java/org/lmdbjava/BufferProxy.java b/src/main/java/org/lmdbjava/BufferProxy.java index 60272209..af0c7f06 100644 --- a/src/main/java/org/lmdbjava/BufferProxy.java +++ b/src/main/java/org/lmdbjava/BufferProxy.java @@ -40,6 +40,11 @@ public abstract class BufferProxy { /** Offset from a pointer of the MDB_val.mv_size field. */ protected static final int STRUCT_FIELD_OFFSET_SIZE = 0; + /** The set of {@link DbiFlags} that indicate unsigned integer keys are being used. */ + protected static final DbiFlagSet INTEGER_KEY_FLAGS = DbiFlagSet.of( + DbiFlags.MDB_INTEGERKEY, + DbiFlags.MDB_INTEGERDUP); + /** Explicitly-defined default constructor to avoid warnings. */ protected BufferProxy() {} @@ -88,28 +93,6 @@ public Comparator getComparator() { return getComparator(DbiFlagSet.empty()); } -// /** -// * Get a suitable default {@link Comparator} to compare numeric key values as signed. -// * -// *

Note: LMDB's default comparator is unsigned so if this is used only for the {@link -// * CursorIterable} start/stop key comparisons then its behaviour will differ from the iteration -// * order. Use with caution. -// * -// * @return a comparator that can be used (never null) -// */ -// public abstract Comparator getSignedComparator(); -// -// /** -// * Get a suitable default {@link Comparator} to compare numeric key values as unsigned. -// *

-// * This should match the behaviour of the LMDB's mdb_cmp comparator as it may be used for -// * {@link CursorIterable} start/stop keys comparisons, which must match LMDB's insertion order. -// *

-// * -// * @return a comparator that can be used (never null) -// */ -// public abstract Comparator getUnsignedComparator(final DbiFlagSet dbiFlagSet); - /** * Called when the MDB_val should be set to reflect the passed buffer. This buffer * will have been created by end users, not {@link #allocate()}. diff --git a/src/main/java/org/lmdbjava/ByteArrayProxy.java b/src/main/java/org/lmdbjava/ByteArrayProxy.java index 5231ed51..d7c23919 100644 --- a/src/main/java/org/lmdbjava/ByteArrayProxy.java +++ b/src/main/java/org/lmdbjava/ByteArrayProxy.java @@ -36,7 +36,6 @@ public final class ByteArrayProxy extends BufferProxy { private static final MemoryManager MEM_MGR = RUNTIME.getMemoryManager(); - private static final Comparator signedComparator = ByteArrayProxy::compareArraysSigned; private static final Comparator unsignedComparator = ByteArrayProxy::compareArrays; private ByteArrayProxy() {} @@ -68,26 +67,6 @@ public static int compareArrays(final byte[] o1, final byte[] o2) { return o1.length - o2.length; } - /** - * Compare two byte arrays. - * - * @param b1 left operand (required) - * @param b2 right operand (required) - * @return as specified by {@link Comparable} interface - */ - public static int compareArraysSigned(final byte[] b1, final byte[] b2) { - requireNonNull(b1); - requireNonNull(b2); - - if (b1 == b2) return 0; - - for (int i = 0; i < min(b1.length, b2.length); ++i) { - if (b1[i] != b2[i]) return b1[i] - b2[i]; - } - - return b1.length - b2.length; - } - @Override protected byte[] allocate() { return new byte[0]; @@ -108,16 +87,6 @@ public Comparator getComparator(final DbiFlagSet dbiFlagSet) { return unsignedComparator; } - // @Override -// public Comparator getSignedComparator() { -// return signedComparator; -// } -// -// @Override -// public Comparator getUnsignedComparator() { -// return unsignedComparator; -// } - @Override protected Pointer in(final byte[] buffer, final Pointer ptr) { final Pointer pointer = MEM_MGR.allocateDirect(buffer.length); diff --git a/src/main/java/org/lmdbjava/ByteBufProxy.java b/src/main/java/org/lmdbjava/ByteBufProxy.java index fc14b58f..319256fb 100644 --- a/src/main/java/org/lmdbjava/ByteBufProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufProxy.java @@ -118,16 +118,6 @@ public Comparator getComparator(final DbiFlagSet dbiFlagSet) { return comparator; } - // @Override -// public Comparator getSignedComparator() { -// return comparator; -// } -// -// @Override -// public Comparator getUnsignedComparator() { -// return comparator; -// } - @Override protected void deallocate(final ByteBuf buff) { buff.release(); diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index 4875572b..ca4deba3 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -54,15 +54,19 @@ public final class ByteBufferProxy { */ public static final BufferProxy PROXY_OPTIMAL; - /** The safe, reflective {@link ByteBuffer} proxy for this system. Guaranteed to never be null. */ + /** + * The safe, reflective {@link ByteBuffer} proxy for this system. Guaranteed to never be null. + */ public static final BufferProxy PROXY_SAFE; + static { PROXY_SAFE = new ReflectiveProxy(); PROXY_OPTIMAL = getProxyOptimal(); } - private ByteBufferProxy() {} + private ByteBufferProxy() { + } private static BufferProxy getProxyOptimal() { try { @@ -72,17 +76,25 @@ private static BufferProxy getProxyOptimal() { } } - /** The buffer must be a direct buffer (not heap allocated). */ + /** + * The buffer must be a direct buffer (not heap allocated). + */ public static final class BufferMustBeDirectException extends LmdbException { private static final long serialVersionUID = 1L; - /** Creates a new instance. */ + /** + * Creates a new instance. + */ public BufferMustBeDirectException() { super("The buffer must be a direct buffer (not heap allocated"); } } + + // -------------------------------------------------------------------------------- + + /** * Provides {@link ByteBuffer} pooling and address resolution for concrete {@link BufferProxy} * implementations. @@ -92,16 +104,6 @@ abstract static class AbstractByteBufferProxy extends BufferProxy { protected static final String FIELD_NAME_ADDRESS = "address"; protected static final String FIELD_NAME_CAPACITY = "capacity"; - private static final Comparator signedComparator = - (o1, o2) -> { - requireNonNull(o1); - requireNonNull(o2); - - return o1.compareTo(o2); - }; - private static final Comparator unsignedComparator = - AbstractByteBufferProxy::compareBuff; - /** * A thread-safe pool for a given length. If the buffer found is valid (ie not of a negative * length) then that buffer is used. If no valid buffer is found, a new buffer is created. @@ -116,7 +118,7 @@ abstract static class AbstractByteBufferProxy extends BufferProxy { * @param o2 right operand (required) * @return as specified by {@link Comparable} interface */ - public static int compareBuff(final ByteBuffer o1, final ByteBuffer o2) { + public static int compareLexicographically(final ByteBuffer o1, final ByteBuffer o2) { requireNonNull(o1); requireNonNull(o2); if (o1.equals(o2)) { @@ -148,34 +150,42 @@ public static int compareBuff(final ByteBuffer o1, final ByteBuffer o2) { return o1.remaining() - o2.remaining(); } -// /** -// * Possible compareBuff method specifically for 4/8 byte keys when using MDB_INTEGER_KEY -// */ -// public static int compareBuff(final ByteBuffer o1, final ByteBuffer o2) { -// requireNonNull(o1); -// requireNonNull(o2); -// // Both buffers should be same len -// final int len1 = o1.limit(); -// final int len2 = o2.limit(); -// if (len1 != len2) { -// throw new RuntimeException("Length mismatch, len1: " + len1 + ", len2: " + len2 -// + ". Lengths must be identical and either 4 or 8 bytes."); -// } -// final boolean reverse1 = o1.order() == LITTLE_ENDIAN; -// final boolean reverse2 = o2.order() == LITTLE_ENDIAN; -// if (len1 == 8) { -// final long lw = reverse1 ? Long.reverseBytes(o1.getLong()) : o1.getLong(); -// final long rw = reverse2 ? Long.reverseBytes(o2.getLong()) : o2.getLong(); -// return Long.compareUnsigned(lw, rw); -// } else if (len1 == 4) { -// final int lw = reverse1 ? Integer.reverseBytes(o1.getInt()) : o1.getInt(); -// final int rw = reverse2 ? Integer.reverseBytes(o2.getInt()) : o2.getInt(); -// return Integer.compareUnsigned(lw, rw); -// } else { -// throw new RuntimeException("Unexpected length len1: " + len1 + ", len2: " + len2 -// + ". Lengths must be identical and either 4 or 8 bytes."); -// } -// } + /** + * Buffer comparator specifically for 4/8 byte keys that are unsigned ints/longs, + * i.e. when using MDB_INTEGER_KEY/MDB_INTEGERDUP. Compares the buffers numerically. + *

+ * Both buffer must have 4 or 8 bytes remaining + *

+ * + * @param o1 left operand (required) + * @param o2 right operand (required) + * @return as specified by {@link Comparable} interface + */ + public static int compareAsIntegerKeys(final ByteBuffer o1, final ByteBuffer o2) { + requireNonNull(o1); + requireNonNull(o2); + // Both buffers should be same len + final int len1 = o1.limit(); + final int len2 = o2.limit(); + if (len1 != len2) { + throw new RuntimeException("Length mismatch, len1: " + len1 + ", len2: " + len2 + + ". Lengths must be identical and either 4 or 8 bytes."); + } + final boolean reverse1 = o1.order() == LITTLE_ENDIAN; + final boolean reverse2 = o2.order() == LITTLE_ENDIAN; + if (len1 == 8) { + final long lw = reverse1 ? Long.reverseBytes(o1.getLong(0)) : o1.getLong(0); + final long rw = reverse2 ? Long.reverseBytes(o2.getLong(0)) : o2.getLong(0); + return Long.compareUnsigned(lw, rw); + } else if (len1 == 4) { + final int lw = reverse1 ? Integer.reverseBytes(o1.getInt(0)) : o1.getInt(0); + final int rw = reverse2 ? Integer.reverseBytes(o2.getInt(0)) : o2.getInt(0); + return Integer.compareUnsigned(lw, rw); + } else { + throw new RuntimeException("Unexpected length1: " + len1 + + ". Lengths must be identical and either 4 or 8 bytes."); + } + } static Field findField(final Class c, final String name) { Class clazz = c; @@ -211,20 +221,14 @@ protected final ByteBuffer allocate() { } @Override - public Comparator getComparator(DbiFlagSet dbiFlagSet) { - return unsignedComparator; + public Comparator getComparator(final DbiFlagSet dbiFlagSet) { + if (dbiFlagSet.areAnySet(INTEGER_KEY_FLAGS)) { + return AbstractByteBufferProxy::compareAsIntegerKeys; + } else { + return AbstractByteBufferProxy::compareLexicographically; + } } - // @Override -// public Comparator getSignedComparator() { -// return signedComparator; -// } -// -// @Override -// public Comparator getUnsignedComparator(final DbiFlagSet dbiFlagSet) { -// return unsignedComparator; -// } - @Override protected final void deallocate(final ByteBuffer buff) { buff.order(BIG_ENDIAN); @@ -240,6 +244,10 @@ protected byte[] getBytes(final ByteBuffer buffer) { } } + + // -------------------------------------------------------------------------------- + + /** * A proxy that uses Java reflection to modify byte buffer fields, and official JNR-FFF methods to * manipulate native pointers. @@ -284,6 +292,10 @@ protected ByteBuffer out(final ByteBuffer buffer, final Pointer ptr) { } } + + // -------------------------------------------------------------------------------- + + /** * A proxy that uses Java's "unsafe" class to directly manipulate byte buffer fields and JNR-FFF * allocated memory pointers. diff --git a/src/main/java/org/lmdbjava/DirectBufferProxy.java b/src/main/java/org/lmdbjava/DirectBufferProxy.java index 514c04ab..9c90d98b 100644 --- a/src/main/java/org/lmdbjava/DirectBufferProxy.java +++ b/src/main/java/org/lmdbjava/DirectBufferProxy.java @@ -35,14 +35,6 @@ *

This class requires {@link UnsafeAccess} and Agrona must be in the classpath. */ public final class DirectBufferProxy extends BufferProxy { - private static final Comparator signedComparator = - (o1, o2) -> { - requireNonNull(o1); - requireNonNull(o2); - - return o1.compareTo(o2); - }; - private static final Comparator unsignedComparator = DirectBufferProxy::compareBuff; /** * The {@link MutableDirectBuffer} proxy. Guaranteed to never be null, although a class @@ -67,7 +59,7 @@ private DirectBufferProxy() {} * @param o2 right operand (required) * @return as specified by {@link Comparable} interface */ - public static int compareBuff(final DirectBuffer o1, final DirectBuffer o2) { + public static int compareLexicographically(final DirectBuffer o1, final DirectBuffer o2) { requireNonNull(o1); requireNonNull(o2); if (o1.equals(o2)) { @@ -97,6 +89,40 @@ public static int compareBuff(final DirectBuffer o1, final DirectBuffer o2) { return o1.capacity() - o2.capacity(); } + /** + * Buffer comparator specifically for 4/8 byte keys that are unsigned ints/longs, + * i.e. when using MDB_INTEGER_KEY/MDB_INTEGERDUP. Compares the buffers numerically. + *

+ * Both buffer must have 4 or 8 bytes remaining + *

+ * @param o1 left operand (required) + * @param o2 right operand (required) + * @return as specified by {@link Comparable} interface + */ + public static int compareAsIntegerKeys(final DirectBuffer o1, final DirectBuffer o2) { + requireNonNull(o1); + requireNonNull(o2); + // Both buffers should be same len + final int len1 = o1.capacity(); + final int len2 = o2.capacity(); + if (len1 != len2) { + throw new RuntimeException("Length mismatch, len1: " + len1 + ", len2: " + len2 + + ". Lengths must be identical and either 4 or 8 bytes."); + } + if (len1 == 8) { + final long lw = o1.getLong(0, BIG_ENDIAN); + final long rw = o2.getLong(0, BIG_ENDIAN); + return Long.compareUnsigned(lw, rw); + } else if (len1 == 4) { + final int lw = o1.getInt(0, BIG_ENDIAN); + final int rw = o2.getInt(0, BIG_ENDIAN); + return Integer.compareUnsigned(lw, rw); + } else { + throw new RuntimeException("Unexpected length len1: " + len1 + ", len2: " + len2 + + ". Lengths must be identical and either 4 or 8 bytes."); + } + } + @Override protected DirectBuffer allocate() { final ArrayDeque q = BUFFERS.get(); @@ -112,19 +138,13 @@ protected DirectBuffer allocate() { @Override public Comparator getComparator(final DbiFlagSet dbiFlagSet) { - return unsignedComparator; + if (dbiFlagSet.areAnySet(INTEGER_KEY_FLAGS)) { + return DirectBufferProxy::compareAsIntegerKeys; + } else { + return DirectBufferProxy::compareLexicographically; + } } - // @Override -// public Comparator getSignedComparator() { -// return signedComparator; -// } -// -// @Override -// public Comparator getUnsignedComparator(final DbiFlagSet dbiFlagSet) { -// return unsignedComparator; -// } - @Override protected void deallocate(final DirectBuffer buff) { final ArrayDeque q = BUFFERS.get(); diff --git a/src/main/java/org/lmdbjava/FlagSet.java b/src/main/java/org/lmdbjava/FlagSet.java index 668e33ba..27513fcd 100644 --- a/src/main/java/org/lmdbjava/FlagSet.java +++ b/src/main/java/org/lmdbjava/FlagSet.java @@ -55,6 +55,22 @@ default int getMaskWith(final FlagSet other) { */ boolean isSet(T flag); + /** + * @return True if at least one of flags are included in thie {@link FlagSet} + */ + default boolean areAnySet(final FlagSet flags) { + if (flags == null) { + return false; + } else { + for (final T flag : flags) { + if (isSet(flag)) { + return true; + } + } + } + return false; + } + /** * @return The size of this {@link FlagSet} */ @@ -92,17 +108,20 @@ static String asString(final FlagSet flagSet) { '}'; } - static boolean equals(final FlagSet flagSet1, - final FlagSet flagSet2) { - if (flagSet1 == flagSet2) { - return true; - } else if (flagSet1 != null && flagSet2 == null) { - return false; - } else if (flagSet1 == null) { - return false; + static boolean equals(final FlagSet flagSet, + final Object other) { + if (other instanceof FlagSet) { + final FlagSet flagSet2 = (FlagSet) other; + if (flagSet == flagSet2) { + return true; + } else if (flagSet == null) { + return false; + } else { + return flagSet.getMask() == flagSet2.getMask() + && Objects.equals(flagSet.getFlags(), flagSet2.getFlags()); + } } else { - return flagSet1.getMask() == flagSet2.getMask() - && Objects.equals(flagSet1.getFlags(), flagSet2.getFlags()); + return false; } } diff --git a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java index b68f39ef..1372b74a 100644 --- a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java +++ b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java @@ -40,20 +40,31 @@ import java.io.IOException; import java.lang.reflect.Field; import java.nio.ByteBuffer; +import java.time.Duration; +import java.time.Instant; +import java.util.Comparator; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Random; +import java.util.Set; import jnr.ffi.Pointer; import jnr.ffi.provider.MemoryManager; +import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.lmdbjava.ByteBufferProxy.BufferMustBeDirectException; import org.lmdbjava.Env.ReadersFullException; -/** Test {@link ByteBufferProxy}. */ +/** + * Test {@link ByteBufferProxy}. + */ public final class ByteBufferProxyTest { static final MemoryManager MEM_MGR = RUNTIME.getMemoryManager(); - @Rule public final TemporaryFolder tmp = new TemporaryFolder(); + @Rule + public final TemporaryFolder tmp = new TemporaryFolder(); @Test(expected = BufferMustBeDirectException.class) public void buffersMustBeDirect() throws IOException { @@ -129,6 +140,81 @@ public void unsafeIsDefault() { assertThat(v.getClass().getSimpleName(), startsWith("Unsafe")); } + /** + * For 100 rounds of 1,000,000 comparisons + * compareAsIntegerKeys: PT0.267813487S + * compareLexicographically: PT0.644165235S + */ + @Test + public void comparatorPerformance() { + final Random random = new Random(); + final ByteBuffer buffer1 = ByteBuffer.allocateDirect(Long.BYTES); + final ByteBuffer buffer2 = ByteBuffer.allocateDirect(Long.BYTES); + buffer1.limit(Long.BYTES); + buffer2.limit(Long.BYTES); + final long[] values = random.longs(1_000_000).toArray(); + + Instant time = Instant.now(); + int x = 0; + for (int rounds = 0; rounds < 100; rounds++) { + for (int i = 1; i < values.length; i++) { + buffer1.putLong(0, values[i - 1]); + buffer2.putLong(0, values[i]); + final int result = ByteBufferProxy.AbstractByteBufferProxy.compareAsIntegerKeys(buffer1, buffer2); + x += result; + } + } + System.out.println("compareAsIntegerKeys: " + Duration.between(time, Instant.now())); + + time = Instant.now(); + x = 0; + for (int rounds = 0; rounds < 100; rounds++) { + for (int i = 1; i < values.length; i++) { + buffer1.putLong(0, values[i - 1]); + buffer2.putLong(0, values[i]); + final int result = ByteBufferProxy.AbstractByteBufferProxy.compareLexicographically(buffer1, buffer2); + x += result; + } + } + System.out.println("compareLexicographically: " + Duration.between(time, Instant.now())); + } + + @Test + public void verifyComparators() { + final Random random = new Random(203948); + final ByteBuffer buffer1 = ByteBuffer.allocateDirect(Long.BYTES); + final ByteBuffer buffer2 = ByteBuffer.allocateDirect(Long.BYTES); + buffer1.limit(Long.BYTES); + buffer2.limit(Long.BYTES); + final long[] values = random.longs(10_000_000).toArray(); + + final LinkedHashMap> comparators = new LinkedHashMap<>(); + comparators.put("compareAsIntegerKeys", ByteBufferProxy.AbstractByteBufferProxy::compareAsIntegerKeys); + comparators.put("compareLexicographically", ByteBufferProxy.AbstractByteBufferProxy::compareLexicographically); + + final LinkedHashMap results = new LinkedHashMap<>(comparators.size()); + final Set uniqueResults = new HashSet<>(comparators.size()); + + for (int i = 1; i < values.length; i++) { + final long val1 = values[i - 1]; + final long val2 = values[i]; + buffer1.putLong(0, val1); + buffer2.putLong(0, val2); + uniqueResults.clear(); + + // Make sure all comparators give the same result for the same inputs + comparators.forEach((name, comparator) -> { + final int result = comparator.compare(buffer1, buffer2); + results.put(name, result); + uniqueResults.add(result); + }); + + if (uniqueResults.size() != 1) { + Assert.fail("Comparator mismatch for values: " + val1 + " and " + val2 + ". Results: " + results); + } + } + } + private void checkInOut(final BufferProxy v) { // allocate a buffer larger than max key size final ByteBuffer b = allocateDirect(1_000); diff --git a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java index aefe9d43..85c0a567 100644 --- a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java @@ -46,6 +46,7 @@ import static org.lmdbjava.TestUtils.DB_1; import static org.lmdbjava.TestUtils.DB_2; import static org.lmdbjava.TestUtils.DB_3; +import static org.lmdbjava.TestUtils.DB_4; import static org.lmdbjava.TestUtils.POSIX_MODE; import static org.lmdbjava.TestUtils.bb; import static org.lmdbjava.TestUtils.bbNative; @@ -63,26 +64,128 @@ import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; +import java.util.function.Function; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; import org.lmdbjava.CursorIterable.KeyVal; -/** Test {@link CursorIterable} using {@link DbiFlags#MDB_INTEGERKEY} to ensure that - * comparators work with native order integer keys. */ +/** + * Test {@link CursorIterable} using {@link DbiFlags#MDB_INTEGERKEY} to ensure that + * comparators work with native order integer keys. + */ +@RunWith(Parameterized.class) public final class CursorIterableIntegerKeyTest { - @Rule public final TemporaryFolder tmp = new TemporaryFolder(); - private Dbi dbJavaComparator; - private Dbi dbLmdbComparator; - private Dbi dbCallbackComparator; - private List> dbs = new ArrayList<>(); + private static final DbiFlagSet DBI_FLAGS = DbiFlagSet.of(MDB_CREATE, MDB_INTEGERKEY); + private static final BufferProxy BUFFER_PROXY = ByteBufferProxy.PROXY_OPTIMAL; + + @Rule + public final TemporaryFolder tmp = new TemporaryFolder(); + private Env env; private Deque list; + /** + * Injected by {@link #data()} with appropriate runner. + */ + @SuppressWarnings("ClassEscapesDefinedScope") + @Parameterized.Parameter + public DbiFactory dbiFactory; + + @Parameterized.Parameters(name = "{index}: dbi: {0}") + public static Object[] data() { + final DbiFactory defaultComparator = new DbiFactory("defaultComparator", env -> + env.buildDbi() + .withDbName(DB_1) + .withDefaultComparator() + .withDbiFlags(DBI_FLAGS) + .open()); + final DbiFactory nativeComparator = new DbiFactory("nativeComparator", env -> + env.buildDbi() + .withDbName(DB_2) + .withNativeComparator() + .withDbiFlags(DBI_FLAGS) + .open()); + final DbiFactory callbackComparator = new DbiFactory("callbackComparator", env -> + env.buildDbi() + .withDbName(DB_3) + .withCallbackComparator(BUFFER_PROXY.getComparator(DBI_FLAGS)) + .withDbiFlags(DBI_FLAGS) + .open()); + final DbiFactory iteratorComparator = new DbiFactory("iteratorComparator", env -> + env.buildDbi() + .withDbName(DB_4) + .withIteratorComparator(BUFFER_PROXY.getComparator(DBI_FLAGS)) + .withDbiFlags(DBI_FLAGS) + .open()); + return new Object[]{ + defaultComparator, + nativeComparator, + callbackComparator, + iteratorComparator}; + } + + @Before + public void before() throws IOException { + final File path = tmp.newFile(); + final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; + env = + create(bufferProxy) + .setMapSize(KIBIBYTES.toBytes(256)) + .setMaxReaders(1) + .setMaxDbs(3) + .open(path, POSIX_MODE, MDB_NOSUBDIR); + + populateTestDataList(); +// final File path = tmp.newFile(); +// final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; +// env = +// create(bufferProxy) +// .setMapSize(KIBIBYTES.toBytes(256)) +// .setMaxReaders(1) +// .setMaxDbs(3) +// .open(path, POSIX_MODE, MDB_NOSUBDIR); +// +// // Use a java comparator for start/stop keys only +// DbiFlagSet dbiFlagSet = DbiFlagSet.of(MDB_CREATE, MDB_INTEGERKEY); +// +// dbJavaComparator = env.buildDbi() +// .withDbName(DB_1) +// .withIteratorComparator(bufferProxy.getComparator(dbiFlagSet)) +// .withDbiFlags(dbiFlagSet) +// .open(); +// +// // Use LMDB comparator for start/stop keys +// dbLmdbComparator = env.buildDbi() +// .withDbName(DB_2) +// .withDefaultComparator() +// .withDbiFlags(dbiFlagSet) +// .open(); +// +// // Use a java comparator for start/stop keys and as a callback comparaotr +// dbCallbackComparator = env.buildDbi() +// .withDbName(DB_3) +// .withCallbackComparator(bufferProxy.getComparator(dbiFlagSet)) +// .withDbiFlags(dbiFlagSet) +// .open(); +// +// populateTestDataList(); +// +// populateDatabase(dbJavaComparator); +// populateDatabase(dbLmdbComparator); +// populateDatabase(dbCallbackComparator); +// +// dbs.add(dbJavaComparator); +// dbs.add(dbLmdbComparator); +// dbs.add(dbCallbackComparator); + } + @After public void after() { env.close(); @@ -123,52 +226,8 @@ public void atMostTest() { verify(atMost(bbNative(6)), 2, 4, 6); } - @Before - public void before() throws IOException { - final File path = tmp.newFile(); - final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; - env = - create(bufferProxy) - .setMapSize(KIBIBYTES.toBytes(256)) - .setMaxReaders(1) - .setMaxDbs(3) - .open(path, POSIX_MODE, MDB_NOSUBDIR); - - // Use a java comparator for start/stop keys only - DbiFlagSet dbiFlagSet = DbiFlagSet.of(MDB_CREATE, MDB_INTEGERKEY); - - dbJavaComparator = env.buildDbi() - .withDbName(DB_1) - .withIteratorComparator(bufferProxy.getComparator(dbiFlagSet)) - .withDbiFlags(dbiFlagSet) - .open(); - - // Use LMDB comparator for start/stop keys - dbLmdbComparator = env.buildDbi() - .withDbName(DB_2) - .withDefaultComparator() - .withDbiFlags(dbiFlagSet) - .open(); - // Use a java comparator for start/stop keys and as a callback comparaotr - dbCallbackComparator = env.buildDbi() - .withDbName(DB_3) - .withCallbackComparator(bufferProxy.getComparator(dbiFlagSet)) - .withDbiFlags(dbiFlagSet) - .open(); - - populateList(); - - populateDatabase(dbJavaComparator); - populateDatabase(dbLmdbComparator); - populateDatabase(dbCallbackComparator); - - dbs.add(dbJavaComparator); - dbs.add(dbLmdbComparator); - dbs.add(dbCallbackComparator); - } - - private void populateList() { + private void populateTestDataList() { list = new LinkedList<>(); list.addAll(asList(2, 3, 4, 5, 6, 7, 8, 9)); } @@ -226,40 +285,37 @@ public void greaterThanTest() { @Test(expected = IllegalStateException.class) public void iterableOnlyReturnedOnce() { - for (final Dbi db : dbs) { - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { - c.iterator(); // ok - c.iterator(); // fails - } + final Dbi db = getDb(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + c.iterator(); // ok + c.iterator(); // fails } } @Test public void iterate() { - for (final Dbi db : dbs) { - populateList(); - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { + populateTestDataList(); + final Dbi db = getDb(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { - int cnt = 0; - for (final KeyVal kv : c) { - assertThat(getNativeInt(kv.key()), is(list.pollFirst())); - assertThat(kv.val().getInt(), is(list.pollFirst())); - } + int cnt = 0; + for (final KeyVal kv : c) { + assertThat(getNativeInt(kv.key()), is(list.pollFirst())); + assertThat(kv.val().getInt(), is(list.pollFirst())); } } } @Test(expected = IllegalStateException.class) public void iteratorOnlyReturnedOnce() { - for (final Dbi db : dbs) { + final Dbi db = getDb(); try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { + CursorIterable c = db.iterate(txn)) { c.iterator(); // ok c.iterator(); // fails } - } } @Test @@ -276,19 +332,18 @@ public void lessThanTest() { @Test(expected = NoSuchElementException.class) public void nextThrowsNoSuchElementExceptionIfNoMoreElements() { - for (final Dbi db : dbs) { - populateList(); - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { - final Iterator> i = c.iterator(); - while (i.hasNext()) { - final KeyVal kv = i.next(); - assertThat(TestUtils.getNativeInt(kv.key()), is(list.pollFirst())); - assertThat(kv.val().getInt(), is(list.pollFirst())); - } - assertThat(i.hasNext(), is(false)); - i.next(); + populateTestDataList(); + final Dbi db = getDb(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + final Iterator> i = c.iterator(); + while (i.hasNext()) { + final KeyVal kv = i.next(); + assertThat(TestUtils.getNativeInt(kv.key()), is(list.pollFirst())); + assertThat(kv.val().getInt(), is(list.pollFirst())); } + assertThat(i.hasNext(), is(false)); + i.next(); } } @@ -341,29 +396,28 @@ public void openTest() { @Test public void removeOddElements() { - for (final Dbi db : dbs) { - verify(db, all(), 2, 4, 6, 8); - int idx = -1; - try (Txn txn = env.txnWrite()) { - try (CursorIterable ci = db.iterate(txn)) { - final Iterator> c = ci.iterator(); - while (c.hasNext()) { - c.next(); - idx++; - if (idx % 2 == 0) { - c.remove(); - } + final Dbi db = getDb(); + verify(db, all(), 2, 4, 6, 8); + int idx = -1; + try (Txn txn = env.txnWrite()) { + try (CursorIterable ci = db.iterate(txn)) { + final Iterator> c = ci.iterator(); + while (c.hasNext()) { + c.next(); + idx++; + if (idx % 2 == 0) { + c.remove(); } } - txn.commit(); } - verify(db, all(), 4, 8); + txn.commit(); } + verify(db, all(), 4, 8); } @Test(expected = Env.AlreadyClosedException.class) public void nextWithClosedEnvTest() { - for (final Dbi db : dbs) { + final Dbi db = getDb(); try (Txn txn = env.txnRead()) { try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { final Iterator> c = ci.iterator(); @@ -372,12 +426,11 @@ public void nextWithClosedEnvTest() { c.next(); } } - } } @Test(expected = Env.AlreadyClosedException.class) public void removeWithClosedEnvTest() { - for (final Dbi db : dbs) { + final Dbi db = getDb(); try (Txn txn = env.txnWrite()) { try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { final Iterator> c = ci.iterator(); @@ -389,12 +442,11 @@ public void removeWithClosedEnvTest() { c.remove(); } } - } } @Test(expected = Env.AlreadyClosedException.class) public void hasNextWithClosedEnvTest() { - for (final Dbi db : dbs) { + final Dbi db = getDb(); try (Txn txn = env.txnRead()) { try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { final Iterator> c = ci.iterator(); @@ -403,21 +455,20 @@ public void hasNextWithClosedEnvTest() { c.hasNext(); } } - } } @Test(expected = Env.AlreadyClosedException.class) public void forEachRemainingWithClosedEnvTest() { - for (final Dbi db : dbs) { + final Dbi db = getDb(); try (Txn txn = env.txnRead()) { try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { final Iterator> c = ci.iterator(); env.close(); - c.forEachRemaining(keyVal -> {}); + c.forEachRemaining(keyVal -> { + }); } } - } } // @Test @@ -469,9 +520,8 @@ public void forEachRemainingWithClosedEnvTest() { private void verify(final KeyRange range, final int... expected) { // Verify using all comparator types - for (final Dbi db : dbs) { - verify(range, db, expected); - } + final Dbi db = getDb(); + verify(range, db, expected); } private void verify( @@ -485,7 +535,7 @@ private void verify( final List results = new ArrayList<>(); try (Txn txn = env.txnRead(); - CursorIterable c = dbi.iterate(txn, range)) { + CursorIterable c = dbi.iterate(txn, range)) { for (final KeyVal kv : c) { final int key = kv.key().order(ByteOrder.nativeOrder()).getInt(); final int val = kv.val().getInt(); @@ -499,4 +549,29 @@ private void verify( assertThat(results.get(idx), is(expected[idx])); } } + + private Dbi getDb() { + final Dbi dbi = dbiFactory.factory.apply(env); + populateDatabase(dbi); + return dbi; + } + + + // -------------------------------------------------------------------------------- + + + private static class DbiFactory { + private final String name; + private final Function, Dbi> factory; + + private DbiFactory(String name, Function, Dbi> factory) { + this.name = name; + this.factory = factory; + } + + @Override + public String toString() { + return name; + } + } } diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index bf2eb9eb..7bcbd851 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -77,8 +77,8 @@ @RunWith(Parameterized.class) public final class CursorIterableTest { - private static final DbiFlagSet dbiFlagSet = MDB_CREATE; - private static final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; + private static final DbiFlagSet DBI_FLAGS = MDB_CREATE; + private static final BufferProxy BUFFER_PROXY = ByteBufferProxy.PROXY_OPTIMAL; @Rule public final TemporaryFolder tmp = new TemporaryFolder(); @@ -87,48 +87,35 @@ public final class CursorIterableTest { private Deque list; /** Injected by {@link #data()} with appropriate runner. */ + @SuppressWarnings("ClassEscapesDefinedScope") @Parameterized.Parameter public DbiFactory dbiFactory; - @Before - public void before() throws IOException { - final File path = tmp.newFile(); - final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; - env = - create(bufferProxy) - .setMapSize(KIBIBYTES.toBytes(256)) - .setMaxReaders(1) - .setMaxDbs(3) - .open(path, POSIX_MODE, MDB_NOSUBDIR); - - populateTestDataList(); - } - @Parameterized.Parameters(name = "{index}: dbi: {0}") public static Object[] data() { final DbiFactory defaultComparator = new DbiFactory("defaultComparator", env -> env.buildDbi() .withDbName(DB_1) .withDefaultComparator() - .withDbiFlags(dbiFlagSet) + .withDbiFlags(DBI_FLAGS) .open()); final DbiFactory nativeComparator = new DbiFactory("nativeComparator", env -> env.buildDbi() .withDbName(DB_2) .withNativeComparator() - .withDbiFlags(dbiFlagSet) + .withDbiFlags(DBI_FLAGS) .open()); final DbiFactory callbackComparator = new DbiFactory("callbackComparator", env -> env.buildDbi() .withDbName(DB_3) - .withCallbackComparator(bufferProxy.getComparator(dbiFlagSet)) - .withDbiFlags(dbiFlagSet) + .withCallbackComparator(BUFFER_PROXY.getComparator(DBI_FLAGS)) + .withDbiFlags(DBI_FLAGS) .open()); final DbiFactory iteratorComparator = new DbiFactory("iteratorComparator", env -> env.buildDbi() .withDbName(DB_4) - .withIteratorComparator(bufferProxy.getComparator(dbiFlagSet)) - .withDbiFlags(dbiFlagSet) + .withIteratorComparator(BUFFER_PROXY.getComparator(DBI_FLAGS)) + .withDbiFlags(DBI_FLAGS) .open()); return new Object[] { defaultComparator, @@ -137,6 +124,20 @@ public static Object[] data() { iteratorComparator}; } + @Before + public void before() throws IOException { + final File path = tmp.newFile(); + final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; + env = + create(bufferProxy) + .setMapSize(KIBIBYTES.toBytes(256)) + .setMaxReaders(1) + .setMaxDbs(3) + .open(path, POSIX_MODE, MDB_NOSUBDIR); + + populateTestDataList(); + } + @After public void after() { env.close(); diff --git a/src/test/java/org/lmdbjava/DbiBuilderTest.java b/src/test/java/org/lmdbjava/DbiBuilderTest.java index da9341c6..74fbd8f5 100644 --- a/src/test/java/org/lmdbjava/DbiBuilderTest.java +++ b/src/test/java/org/lmdbjava/DbiBuilderTest.java @@ -45,7 +45,6 @@ public void after() { @Before public void before() throws IOException { - System.out.println("before"); final File path = tmp.newFile(); env = create() .setMapSize(MEBIBYTES.toBytes(64)) From 58dcc6e8bb1b393188ef3c1d86015ae15b50e1fb Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Wed, 29 Oct 2025 16:09:53 +0000 Subject: [PATCH 18/90] Tidy code --- .../java/org/lmdbjava/ByteArrayProxy.java | 6 +- .../java/org/lmdbjava/ByteBufferProxy.java | 2 +- .../CursorIterableIntegerDupTest.java | 563 ++++++++++++++++++ .../CursorIterableIntegerKeyTest.java | 89 --- 4 files changed, 566 insertions(+), 94 deletions(-) create mode 100644 src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java diff --git a/src/main/java/org/lmdbjava/ByteArrayProxy.java b/src/main/java/org/lmdbjava/ByteArrayProxy.java index d7c23919..82b7721c 100644 --- a/src/main/java/org/lmdbjava/ByteArrayProxy.java +++ b/src/main/java/org/lmdbjava/ByteArrayProxy.java @@ -36,8 +36,6 @@ public final class ByteArrayProxy extends BufferProxy { private static final MemoryManager MEM_MGR = RUNTIME.getMemoryManager(); - private static final Comparator unsignedComparator = ByteArrayProxy::compareArrays; - private ByteArrayProxy() {} /** @@ -47,7 +45,7 @@ private ByteArrayProxy() {} * @param o2 right operand (required) * @return as specified by {@link Comparable} interface */ - public static int compareArrays(final byte[] o1, final byte[] o2) { + public static int compareLexicographically(final byte[] o1, final byte[] o2) { requireNonNull(o1); requireNonNull(o2); if (o1 == o2) { @@ -84,7 +82,7 @@ protected byte[] getBytes(final byte[] buffer) { @Override public Comparator getComparator(final DbiFlagSet dbiFlagSet) { - return unsignedComparator; + return ByteArrayProxy::compareLexicographically; } @Override diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index ca4deba3..89931587 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -154,7 +154,7 @@ public static int compareLexicographically(final ByteBuffer o1, final ByteBuffer * Buffer comparator specifically for 4/8 byte keys that are unsigned ints/longs, * i.e. when using MDB_INTEGER_KEY/MDB_INTEGERDUP. Compares the buffers numerically. *

- * Both buffer must have 4 or 8 bytes remaining + * Both buffers must have 4 or 8 bytes remaining *

* * @param o1 left operand (required) diff --git a/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java b/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java new file mode 100644 index 00000000..1acf4328 --- /dev/null +++ b/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java @@ -0,0 +1,563 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasSize; +import static org.lmdbjava.DbiFlags.MDB_CREATE; +import static org.lmdbjava.DbiFlags.MDB_DUPSORT; +import static org.lmdbjava.DbiFlags.MDB_INTEGERDUP; +import static org.lmdbjava.Env.create; +import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; +import static org.lmdbjava.KeyRange.all; +import static org.lmdbjava.KeyRange.allBackward; +import static org.lmdbjava.KeyRange.atLeast; +import static org.lmdbjava.KeyRange.atLeastBackward; +import static org.lmdbjava.KeyRange.atMost; +import static org.lmdbjava.KeyRange.atMostBackward; +import static org.lmdbjava.KeyRange.closed; +import static org.lmdbjava.KeyRange.closedBackward; +import static org.lmdbjava.KeyRange.closedOpen; +import static org.lmdbjava.KeyRange.closedOpenBackward; +import static org.lmdbjava.KeyRange.greaterThan; +import static org.lmdbjava.KeyRange.greaterThanBackward; +import static org.lmdbjava.KeyRange.lessThan; +import static org.lmdbjava.KeyRange.lessThanBackward; +import static org.lmdbjava.KeyRange.open; +import static org.lmdbjava.KeyRange.openBackward; +import static org.lmdbjava.KeyRange.openClosed; +import static org.lmdbjava.KeyRange.openClosedBackward; +import static org.lmdbjava.TestUtils.DB_1; +import static org.lmdbjava.TestUtils.DB_2; +import static org.lmdbjava.TestUtils.DB_3; +import static org.lmdbjava.TestUtils.DB_4; +import static org.lmdbjava.TestUtils.POSIX_MODE; +import static org.lmdbjava.TestUtils.bb; +import static org.lmdbjava.TestUtils.bbNative; +import static org.lmdbjava.TestUtils.getNativeInt; + +import com.google.common.primitives.UnsignedBytes; +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Deque; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.hamcrest.CoreMatchers; +import org.hamcrest.Matchers; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.lmdbjava.CursorIterable.KeyVal; + +/** + * Test {@link CursorIterable} using {@link DbiFlags#MDB_INTEGERKEY} to ensure that + * comparators work with native order integer keys. + */ +@Ignore // Waiting for the merge of stroomdev66's cursor tests +@RunWith(Parameterized.class) +public final class CursorIterableIntegerDupTest { + + private static final DbiFlagSet DBI_FLAGS = DbiFlagSet.of(MDB_CREATE, MDB_INTEGERDUP, MDB_DUPSORT); + private static final BufferProxy BUFFER_PROXY = ByteBufferProxy.PROXY_OPTIMAL; + private static final List> INPUT_DATA; + + static { + // 2 => 21 + // 2 => 22 + // 3 => 31 + // ... + // 9 => 92 + INPUT_DATA = new ArrayList<>(); + for (int i = 2; i <= 9; i++) { + final int val1 = (i * 10) + 1; + final int val2 = (i * 10) + 2; + INPUT_DATA.add(new AbstractMap.SimpleEntry<>(i, val1)); + INPUT_DATA.add(new AbstractMap.SimpleEntry<>(i, val2)); + } + } + + @Rule + public final TemporaryFolder tmp = new TemporaryFolder(); + + private Env env; + private Deque> expectedEntriesDeque; + + /** + * Injected by {@link #data()} with appropriate runner. + */ + @SuppressWarnings("ClassEscapesDefinedScope") + @Parameterized.Parameter + public DbiFactory dbiFactory; + + @Parameterized.Parameters(name = "{index}: dbi: {0}") + public static Object[] data() { + final DbiFactory defaultComparator = new DbiFactory("defaultComparator", env -> + env.buildDbi() + .withDbName(DB_1) + .withDefaultComparator() + .withDbiFlags(DBI_FLAGS) + .open()); + final DbiFactory nativeComparator = new DbiFactory("nativeComparator", env -> + env.buildDbi() + .withDbName(DB_2) + .withNativeComparator() + .withDbiFlags(DBI_FLAGS) + .open()); + final DbiFactory callbackComparator = new DbiFactory("callbackComparator", env -> + env.buildDbi() + .withDbName(DB_3) + .withCallbackComparator(BUFFER_PROXY.getComparator(DBI_FLAGS)) + .withDbiFlags(DBI_FLAGS) + .open()); + final DbiFactory iteratorComparator = new DbiFactory("iteratorComparator", env -> + env.buildDbi() + .withDbName(DB_4) + .withIteratorComparator(BUFFER_PROXY.getComparator(DBI_FLAGS)) + .withDbiFlags(DBI_FLAGS) + .open()); + return new Object[]{ + defaultComparator, + nativeComparator, + callbackComparator, + iteratorComparator}; + } + + @Before + public void before() throws IOException { + final File path = tmp.newFile(); + final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; + env = + create(bufferProxy) + .setMapSize(KIBIBYTES.toBytes(256)) + .setMaxReaders(1) + .setMaxDbs(3) + .open(path, POSIX_MODE, MDB_NOSUBDIR); + + populateExpectedEntriesDeque(); + } + + @After + public void after() { + env.close(); + } + + @Test + public void allBackwardTest() { + verify(allBackward(), 8, 6, 4, 2); + } + + @Test + public void allTest() { + verify(all(), 2, 4, 6, 8); + } + + @Test + public void atLeastBackwardTest() { + verify(atLeastBackward(bbNative(5)), 4, 2); + verify(atLeastBackward(bbNative(6)), 6, 4, 2); + verify(atLeastBackward(bbNative(9)), 8, 6, 4, 2); + } + + @Test + public void atLeastTest() { + verify(atLeast(bbNative(5)), 6, 8); + verify(atLeast(bbNative(6)), 6, 8); + } + + @Test + public void atMostBackwardTest() { + verify(atMostBackward(bbNative(5)), 8, 6); + verify(atMostBackward(bbNative(6)), 8, 6); + } + + @Test + public void atMostTest() { + verify(atMost(bbNative(5)), 2, 4); + verify(atMost(bbNative(6)), 2, 4, 6); + } + + private void populateExpectedEntriesDeque() { + expectedEntriesDeque = new LinkedList<>(); + expectedEntriesDeque.addAll(INPUT_DATA); + } + + private void populateDatabase(final Dbi dbi) { + try (Txn txn = env.txnWrite()) { + final Cursor c = dbi.openCursor(txn); + for (Map.Entry entry : INPUT_DATA) { + c.put(bbNative(entry.getKey()), bb(entry.getValue())); + } + txn.commit(); + } + + try (Txn txn = env.txnRead(); + CursorIterable c = dbi.iterate(txn)) { + + for (final KeyVal kv : c) { + System.out.print(getNativeInt(kv.key()) + " => " + kv.val().getInt()); + System.out.print(", "); + } + System.out.println(); + } + } + + private int[] rangeInc(final int fromInc, final int toInc) { + int idx = 0; + if (fromInc <= toInc) { + // Forwards + final int[] arr = new int[toInc - fromInc + 1]; + for (int i = fromInc; i <= toInc; i++) { + arr[idx++] = i; + } + return arr; + } else { + // Backwards + final int[] arr = new int[fromInc - toInc + 1]; + for (int i = fromInc; i >= toInc; i--) { + arr[idx++] = i; + } + return arr; + } + } + + @Test + public void closedBackwardTest() { + verify(closedBackward(bbNative(7), bbNative(3)), rangeInc(7, 3)); + verify(closedBackward(bbNative(6), bbNative(2)), rangeInc(6, 2)); + verify(closedBackward(bbNative(9), bbNative(3)), rangeInc(9, 3)); + } + + @Test + public void closedOpenBackwardTest() { + verify(closedOpenBackward(bbNative(8), bbNative(3)), rangeInc(8, 4)); + verify(closedOpenBackward(bbNative(7), bbNative(2)), rangeInc(7, 3)); + verify(closedOpenBackward(bbNative(9), bbNative(3)), rangeInc(9, 4)); + } + + @Test + public void closedOpenTest() { + verify(closedOpen(bbNative(3), bbNative(8)), rangeInc(3, 7)); + verify(closedOpen(bbNative(2), bbNative(6)), rangeInc(2, 5)); + } + + @Test + public void closedTest() { + verify(closed(bbNative(3), bbNative(7)), rangeInc(3, 7)); + verify(closed(bbNative(2), bbNative(6)), rangeInc(2, 6)); + verify(closed(bbNative(1), bbNative(7)), rangeInc(2, 7)); + } + + @Test + public void greaterThanBackwardTest() { + verify(greaterThanBackward(bbNative(6)), rangeInc(5, 2)); + verify(greaterThanBackward(bbNative(7)), rangeInc(6, 2)); + verify(greaterThanBackward(bbNative(9)), rangeInc(8, 2)); + } + + @Test + public void greaterThanTest() { + verify(greaterThan(bbNative(4)), rangeInc(5, 9)); + verify(greaterThan(bbNative(3)), rangeInc(4, 9)); + } + + @Test(expected = IllegalStateException.class) + public void iterableOnlyReturnedOnce() { + final Dbi db = getDb(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + c.iterator(); // ok + c.iterator(); // fails + } + } + + @Test + public void iterate() { + populateExpectedEntriesDeque(); + final Dbi db = getDb(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + + for (final KeyVal kv : c) { + final Map.Entry entry = expectedEntriesDeque.pollFirst(); +// System.out.println(entry.getKey() + " => " + entry.getValue()); + assertThat(getNativeInt(kv.key()), is(entry.getKey())); + assertThat(kv.val().getInt(), is(entry.getValue())); + } + } + } + + @Test(expected = IllegalStateException.class) + public void iteratorOnlyReturnedOnce() { + final Dbi db = getDb(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + c.iterator(); // ok + c.iterator(); // fails + } + } + + @Test + public void lessThanBackwardTest() { + verify(lessThanBackward(bbNative(5)), 8, 6); + verify(lessThanBackward(bbNative(2)), 8, 6, 4); + } + + @Test + public void lessThanTest() { + verify(lessThan(bbNative(5)), 2, 4); + verify(lessThan(bbNative(8)), 2, 4, 6); + } + + @Test(expected = NoSuchElementException.class) + public void nextThrowsNoSuchElementExceptionIfNoMoreElements() { + populateExpectedEntriesDeque(); + final Dbi db = getDb(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + final Iterator> i = c.iterator(); + while (i.hasNext()) { + final KeyVal kv = i.next(); + assertThat(getNativeInt(kv.key()), is(expectedEntriesDeque.pollFirst())); + assertThat(kv.val().getInt(), is(expectedEntriesDeque.pollFirst())); + } + assertThat(i.hasNext(), is(false)); + i.next(); + } + } + + @Test + public void openBackwardTest() { + verify(openBackward(bbNative(7), bbNative(2)), 6, 4); + verify(openBackward(bbNative(8), bbNative(1)), 6, 4, 2); + verify(openBackward(bbNative(9), bbNative(4)), 8, 6); + } + + @Test + public void openClosedBackwardTest() { + verify(openClosedBackward(bbNative(7), bbNative(2)), 6, 4, 2); + verify(openClosedBackward(bbNative(8), bbNative(4)), 6, 4); + verify(openClosedBackward(bbNative(9), bbNative(4)), 8, 6, 4); + } + + @Test + public void openClosedBackwardTestWithGuava() { + final Comparator guava = UnsignedBytes.lexicographicalComparator(); + final Comparator comparator = + (bb1, bb2) -> { + final byte[] array1 = new byte[bb1.remaining()]; + final byte[] array2 = new byte[bb2.remaining()]; + bb1.mark(); + bb2.mark(); + bb1.get(array1); + bb2.get(array2); + bb1.reset(); + bb2.reset(); + return guava.compare(array1, array2); + }; + final Dbi guavaDbi = env.openDbi(DB_1, comparator, MDB_CREATE); + populateDatabase(guavaDbi); + verify(openClosedBackward(bbNative(7), bbNative(2)), guavaDbi, 6, 4, 2); + verify(openClosedBackward(bbNative(8), bbNative(4)), guavaDbi, 6, 4); + } + + @Test + public void openClosedTest() { + verify(openClosed(bbNative(3), bbNative(8)), 4, 6, 8); + verify(openClosed(bbNative(2), bbNative(6)), 4, 6); + } + + @Test + public void openTest() { + verify(open(bbNative(3), bbNative(7)), 4, 6); + verify(open(bbNative(2), bbNative(8)), 4, 6); + } + + @Test + public void removeOddElements() { + final Dbi db = getDb(); + verify(db, all(), 2, 4, 6, 8); + int idx = -1; + try (Txn txn = env.txnWrite()) { + try (CursorIterable ci = db.iterate(txn)) { + final Iterator> c = ci.iterator(); + while (c.hasNext()) { + c.next(); + idx++; + if (idx % 2 == 0) { + c.remove(); + } + } + } + txn.commit(); + } + verify(db, all(), 4, 8); + } + + @Test(expected = Env.AlreadyClosedException.class) + public void nextWithClosedEnvTest() { + final Dbi db = getDb(); + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + + env.close(); + c.next(); + } + } + } + + @Test(expected = Env.AlreadyClosedException.class) + public void removeWithClosedEnvTest() { + final Dbi db = getDb(); + try (Txn txn = env.txnWrite()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + + final KeyVal keyVal = c.next(); + assertThat(keyVal, Matchers.notNullValue()); + + env.close(); + c.remove(); + } + } + } + + @Test(expected = Env.AlreadyClosedException.class) + public void hasNextWithClosedEnvTest() { + final Dbi db = getDb(); + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + + env.close(); + c.hasNext(); + } + } + } + + @Test(expected = Env.AlreadyClosedException.class) + public void forEachRemainingWithClosedEnvTest() { + final Dbi db = getDb(); + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + + env.close(); + c.forEachRemaining(keyVal -> { + }); + } + } + } + + private void verify(final KeyRange range, final int... expectedKeys) { + // Verify using all comparator types + final Dbi db = getDb(); + verify(range, db, expectedKeys); + } + + private void verify(final Dbi dbi, + final KeyRange range, + final int... expectedKeys) { + verify(range, dbi, expectedKeys); + } + + private void verify(final KeyRange range, + final Dbi dbi, + final int... expectedKeys) { + final boolean isForward = range.getType().isDirectionForward(); + + final List expectedValues = Arrays.stream(expectedKeys) + .boxed() + .flatMap(key -> { + final int base = key * 10; + return isForward + ? Stream.of(base + 1, base + 2) + : Stream.of(base + 2, base + 1); + }) + .collect(Collectors.toList()); + + final List results = new ArrayList<>(); + System.out.println(rangeToString(range) + ", expected: " + expectedValues); + + try (Txn txn = env.txnRead(); + CursorIterable c = dbi.iterate(txn, range)) { + for (final KeyVal kv : c) { + final int key = getNativeInt(kv.key()); + final int val = kv.val().getInt(); + System.out.println(key + " => " + val); + results.add(val); + assertThat(val, CoreMatchers.anyOf( + CoreMatchers.is((key * 10) + 1), + CoreMatchers.is((key * 10) + 2))); + } + } + + assertThat(results, hasSize(expectedValues.size())); + for (int idx = 0; idx < results.size(); idx++) { + assertThat(results.get(idx), is(expectedValues.get(idx))); + } + } + + private String rangeToString(final KeyRange range) { + final ByteBuffer start = range.getStart(); + final ByteBuffer stop = range.getStop(); + return range.getType() + " start: " + (start != null ? getNativeInt(start) : "") + + " stop: " + (stop != null ? getNativeInt(stop) : ""); + } + + private Dbi getDb() { + final Dbi dbi = dbiFactory.factory.apply(env); + populateDatabase(dbi); + return dbi; + } + + + // -------------------------------------------------------------------------------- + + + private static class DbiFactory { + private final String name; + private final Function, Dbi> factory; + + private DbiFactory(String name, Function, Dbi> factory) { + this.name = name; + this.factory = factory; + } + + @Override + public String toString() { + return name; + } + } +} diff --git a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java index 85c0a567..bac95e13 100644 --- a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java @@ -143,47 +143,6 @@ public void before() throws IOException { .open(path, POSIX_MODE, MDB_NOSUBDIR); populateTestDataList(); -// final File path = tmp.newFile(); -// final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; -// env = -// create(bufferProxy) -// .setMapSize(KIBIBYTES.toBytes(256)) -// .setMaxReaders(1) -// .setMaxDbs(3) -// .open(path, POSIX_MODE, MDB_NOSUBDIR); -// -// // Use a java comparator for start/stop keys only -// DbiFlagSet dbiFlagSet = DbiFlagSet.of(MDB_CREATE, MDB_INTEGERKEY); -// -// dbJavaComparator = env.buildDbi() -// .withDbName(DB_1) -// .withIteratorComparator(bufferProxy.getComparator(dbiFlagSet)) -// .withDbiFlags(dbiFlagSet) -// .open(); -// -// // Use LMDB comparator for start/stop keys -// dbLmdbComparator = env.buildDbi() -// .withDbName(DB_2) -// .withDefaultComparator() -// .withDbiFlags(dbiFlagSet) -// .open(); -// -// // Use a java comparator for start/stop keys and as a callback comparaotr -// dbCallbackComparator = env.buildDbi() -// .withDbName(DB_3) -// .withCallbackComparator(bufferProxy.getComparator(dbiFlagSet)) -// .withDbiFlags(dbiFlagSet) -// .open(); -// -// populateTestDataList(); -// -// populateDatabase(dbJavaComparator); -// populateDatabase(dbLmdbComparator); -// populateDatabase(dbCallbackComparator); -// -// dbs.add(dbJavaComparator); -// dbs.add(dbLmdbComparator); -// dbs.add(dbCallbackComparator); } @After @@ -226,7 +185,6 @@ public void atMostTest() { verify(atMost(bbNative(6)), 2, 4, 6); } - private void populateTestDataList() { list = new LinkedList<>(); list.addAll(asList(2, 3, 4, 5, 6, 7, 8, 9)); @@ -471,53 +429,6 @@ public void forEachRemainingWithClosedEnvTest() { } } -// @Test -// public void testSignedVsUnsigned() { -// final ByteBuffer val1 = bbNative(1); -// final ByteBuffer val2 = bbNative(2); -// final ByteBuffer val110 = bbNative(110); -// final ByteBuffer val111 = bbNative(111); -// final ByteBuffer val150 = bbNative(150); -// -// final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; -// final Comparator unsignedComparator = bufferProxy.getUnsignedComparator(); -// final Comparator signedComparator = bufferProxy.getSignedComparator(); -// -// // Compare the same -// assertThat( -// unsignedComparator.compare(val1, val2), Matchers.is(signedComparator.compare(val1, val2))); -// -// // Compare differently -// assertThat( -// unsignedComparator.compare(val110, val150), -// Matchers.not(signedComparator.compare(val110, val150))); -// -// // Compare differently -// assertThat( -// unsignedComparator.compare(val111, val150), -// Matchers.not(signedComparator.compare(val111, val150))); -// -// // This will fail if the db is using a signed comparator for the start/stop keys -// for (final Dbi db : dbs) { -// db.put(val110, val110); -// db.put(val150, val150); -// -// final ByteBuffer startKeyBuf = val111; -// KeyRange keyRange = KeyRange.atLeastBackward(startKeyBuf); -// -// try (Txn txn = env.txnRead(); -// CursorIterable c = db.iterate(txn, keyRange)) { -// for (final KeyVal kv : c) { -// final int key = getNativeInt(kv.key()); -// final int val = kv.val().getInt(); -// // System.out.println("key: " + key + " val: " + val); -// assertThat(key, is(110)); -// break; -// } -// } -// } -// } - private void verify(final KeyRange range, final int... expected) { // Verify using all comparator types final Dbi db = getDb(); From 26665ba0cfaafaa082d3e22feeb4080cb0bc7849 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Wed, 29 Oct 2025 17:45:21 +0000 Subject: [PATCH 19/90] Fix byte order issues with compareAsIntegerKeys --- src/main/java/org/lmdbjava/BufferProxy.java | 4 - .../java/org/lmdbjava/ByteBufferProxy.java | 32 ++++--- src/main/java/org/lmdbjava/Dbi.java | 30 ++++-- src/main/java/org/lmdbjava/DbiFlagSet.java | 6 ++ src/main/java/org/lmdbjava/DbiFlags.java | 2 +- .../java/org/lmdbjava/DirectBufferProxy.java | 2 +- .../org/lmdbjava/ByteBufferProxyTest.java | 39 +++++--- .../CursorIterableIntegerKeyTest.java | 96 +++++++++++++++++++ src/test/java/org/lmdbjava/TestUtils.java | 33 ++++++- 9 files changed, 204 insertions(+), 40 deletions(-) diff --git a/src/main/java/org/lmdbjava/BufferProxy.java b/src/main/java/org/lmdbjava/BufferProxy.java index af0c7f06..f857ade7 100644 --- a/src/main/java/org/lmdbjava/BufferProxy.java +++ b/src/main/java/org/lmdbjava/BufferProxy.java @@ -40,10 +40,6 @@ public abstract class BufferProxy { /** Offset from a pointer of the MDB_val.mv_size field. */ protected static final int STRUCT_FIELD_OFFSET_SIZE = 0; - /** The set of {@link DbiFlags} that indicate unsigned integer keys are being used. */ - protected static final DbiFlagSet INTEGER_KEY_FLAGS = DbiFlagSet.of( - DbiFlags.MDB_INTEGERKEY, - DbiFlags.MDB_INTEGERDUP); /** Explicitly-defined default constructor to avoid warnings. */ protected BufferProxy() {} diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index 89931587..5d5aa2ca 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -27,6 +27,7 @@ import java.lang.reflect.Field; import java.nio.Buffer; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.ArrayDeque; import java.util.Comparator; import jnr.ffi.Pointer; @@ -153,9 +154,6 @@ public static int compareLexicographically(final ByteBuffer o1, final ByteBuffer /** * Buffer comparator specifically for 4/8 byte keys that are unsigned ints/longs, * i.e. when using MDB_INTEGER_KEY/MDB_INTEGERDUP. Compares the buffers numerically. - *

- * Both buffers must have 4 or 8 bytes remaining - *

* * @param o1 left operand (required) * @param o2 right operand (required) @@ -164,26 +162,34 @@ public static int compareLexicographically(final ByteBuffer o1, final ByteBuffer public static int compareAsIntegerKeys(final ByteBuffer o1, final ByteBuffer o2) { requireNonNull(o1); requireNonNull(o2); - // Both buffers should be same len + // Both buffers should be same lenght according to LMDB API. final int len1 = o1.limit(); final int len2 = o2.limit(); if (len1 != len2) { throw new RuntimeException("Length mismatch, len1: " + len1 + ", len2: " + len2 + ". Lengths must be identical and either 4 or 8 bytes."); } - final boolean reverse1 = o1.order() == LITTLE_ENDIAN; - final boolean reverse2 = o2.order() == LITTLE_ENDIAN; + // Keys for MDB_INTEGER_KEY are written in native order so ensure we read them in that order + o1.order(ByteOrder.nativeOrder()); + o2.order(ByteOrder.nativeOrder()); + // TODO it might be worth the DbiBuilder having a method to capture fixedKeyLength() or -1 + // for variable length keys. This can be passed to getComparator(..) so it can return a + // comparator that doesn't need to test the length every time. There may be other benefits + // to the Dbi knowing the key length if it is fixed. if (len1 == 8) { - final long lw = reverse1 ? Long.reverseBytes(o1.getLong(0)) : o1.getLong(0); - final long rw = reverse2 ? Long.reverseBytes(o2.getLong(0)) : o2.getLong(0); + final long lw = o1.getLong(0); + final long rw = o2.getLong(0); return Long.compareUnsigned(lw, rw); } else if (len1 == 4) { - final int lw = reverse1 ? Integer.reverseBytes(o1.getInt(0)) : o1.getInt(0); - final int rw = reverse2 ? Integer.reverseBytes(o2.getInt(0)) : o2.getInt(0); + final int lw = o1.getInt(0); + final int rw = o2.getInt(0); return Integer.compareUnsigned(lw, rw); } else { - throw new RuntimeException("Unexpected length1: " + len1 - + ". Lengths must be identical and either 4 or 8 bytes."); + // size_t and int are likely to be 8bytes and 4bytes respectively on 64bit. + // If 32bit then would be 4/2 respectively. + // Short.compareUnsigned is not available in Java8. + // For now just fall back to our standard comparator + return compareLexicographically(o1, o2); } } @@ -222,7 +228,7 @@ protected final ByteBuffer allocate() { @Override public Comparator getComparator(final DbiFlagSet dbiFlagSet) { - if (dbiFlagSet.areAnySet(INTEGER_KEY_FLAGS)) { + if (dbiFlagSet.areAnySet(DbiFlagSet.INTEGER_KEY_FLAGS)) { return AbstractByteBufferProxy::compareAsIntegerKeys; } else { return AbstractByteBufferProxy::compareLexicographically; diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index bcb1ccfc..5e8fa2f2 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -82,15 +82,27 @@ public final class Dbi { if (nativeCb) { requireNonNull(comparator, "comparator cannot be null if nativeCb is set"); // LMDB will call back to this comparator for insertion/iteration order - this.callbackComparator = - (keyA, keyB) -> { - final T compKeyA = proxy.out(proxy.allocate(), keyA); - final T compKeyB = proxy.out(proxy.allocate(), keyB); - final int result = this.comparator.compare(compKeyA, compKeyB); - proxy.deallocate(compKeyA); - proxy.deallocate(compKeyB); - return result; - }; +// if (dbiFlagSet.areAnySet(DbiFlagSet.INTEGER_KEY_FLAGS)) { +// this.callbackComparator = +// (keyA, keyB) -> { +// final T compKeyA = proxy.out(proxy.allocate(), keyA); +// final T compKeyB = proxy.out(proxy.allocate(), keyB); +// final int result = this.comparator.compare(compKeyA, compKeyB); +// proxy.deallocate(compKeyA); +// proxy.deallocate(compKeyB); +// return result; +// }; +// } else { + this.callbackComparator = + (keyA, keyB) -> { + final T compKeyA = proxy.out(proxy.allocate(), keyA); + final T compKeyB = proxy.out(proxy.allocate(), keyB); + final int result = this.comparator.compare(compKeyA, compKeyB); + proxy.deallocate(compKeyA); + proxy.deallocate(compKeyB); + return result; + }; +// } LIB.mdb_set_compare(txn.pointer(), ptr, callbackComparator); } else { callbackComparator = null; diff --git a/src/main/java/org/lmdbjava/DbiFlagSet.java b/src/main/java/org/lmdbjava/DbiFlagSet.java index 5edf10c7..5a0bc83e 100644 --- a/src/main/java/org/lmdbjava/DbiFlagSet.java +++ b/src/main/java/org/lmdbjava/DbiFlagSet.java @@ -21,8 +21,14 @@ public interface DbiFlagSet extends FlagSet { + /** An immutable empty {@link DbiFlagSet}. */ DbiFlagSet EMPTY = DbiFlagSetImpl.EMPTY; + /** The set of {@link DbiFlags} that indicate unsigned integer keys are being used. */ + DbiFlagSet INTEGER_KEY_FLAGS = DbiFlagSet.of( + DbiFlags.MDB_INTEGERKEY, + DbiFlags.MDB_INTEGERDUP); + static DbiFlagSet empty() { return DbiFlagSetImpl.EMPTY; } diff --git a/src/main/java/org/lmdbjava/DbiFlags.java b/src/main/java/org/lmdbjava/DbiFlags.java index 10952da9..7c4b6794 100644 --- a/src/main/java/org/lmdbjava/DbiFlags.java +++ b/src/main/java/org/lmdbjava/DbiFlags.java @@ -41,7 +41,7 @@ public enum DbiFlags implements MaskedFlag, DbiFlagSet { MDB_DUPSORT(0x04), /** * Numeric keys in native byte order: either unsigned int or size_t. - * The keys must all be of the same size. + * The keys must all be of the same size. *

* This is an optimisation that is available when your keys are 4 or 8 byte unsigned numeric values. * There are performance benefits for both ordered and un-ordered puts as compared to not using diff --git a/src/main/java/org/lmdbjava/DirectBufferProxy.java b/src/main/java/org/lmdbjava/DirectBufferProxy.java index 9c90d98b..180eee0a 100644 --- a/src/main/java/org/lmdbjava/DirectBufferProxy.java +++ b/src/main/java/org/lmdbjava/DirectBufferProxy.java @@ -138,7 +138,7 @@ protected DirectBuffer allocate() { @Override public Comparator getComparator(final DbiFlagSet dbiFlagSet) { - if (dbiFlagSet.areAnySet(INTEGER_KEY_FLAGS)) { + if (dbiFlagSet.areAnySet(DbiFlagSet.INTEGER_KEY_FLAGS)) { return DirectBufferProxy::compareAsIntegerKeys; } else { return DirectBufferProxy::compareLexicographically; diff --git a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java index 1372b74a..c7d8333f 100644 --- a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java +++ b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java @@ -40,6 +40,7 @@ import java.io.IOException; import java.lang.reflect.Field; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.time.Duration; import java.time.Instant; import java.util.Comparator; @@ -158,8 +159,10 @@ public void comparatorPerformance() { int x = 0; for (int rounds = 0; rounds < 100; rounds++) { for (int i = 1; i < values.length; i++) { - buffer1.putLong(0, values[i - 1]); - buffer2.putLong(0, values[i]); + buffer1.order(ByteOrder.nativeOrder()) + .putLong(0, values[i - 1]); + buffer2.order(ByteOrder.nativeOrder()) + .putLong(0, values[i]); final int result = ByteBufferProxy.AbstractByteBufferProxy.compareAsIntegerKeys(buffer1, buffer2); x += result; } @@ -170,8 +173,10 @@ public void comparatorPerformance() { x = 0; for (int rounds = 0; rounds < 100; rounds++) { for (int i = 1; i < values.length; i++) { - buffer1.putLong(0, values[i - 1]); - buffer2.putLong(0, values[i]); + buffer1.order(BIG_ENDIAN) + .putLong(0, values[i - 1]); + buffer2.order(BIG_ENDIAN) + .putLong(0, values[i]); final int result = ByteBufferProxy.AbstractByteBufferProxy.compareLexicographically(buffer1, buffer2); x += result; } @@ -182,10 +187,14 @@ public void comparatorPerformance() { @Test public void verifyComparators() { final Random random = new Random(203948); - final ByteBuffer buffer1 = ByteBuffer.allocateDirect(Long.BYTES); - final ByteBuffer buffer2 = ByteBuffer.allocateDirect(Long.BYTES); - buffer1.limit(Long.BYTES); - buffer2.limit(Long.BYTES); + final ByteBuffer buffer1native = ByteBuffer.allocateDirect(Long.BYTES).order(ByteOrder.nativeOrder()); + final ByteBuffer buffer2native = ByteBuffer.allocateDirect(Long.BYTES).order(ByteOrder.nativeOrder()); + final ByteBuffer buffer1be = ByteBuffer.allocateDirect(Long.BYTES).order(BIG_ENDIAN); + final ByteBuffer buffer2be = ByteBuffer.allocateDirect(Long.BYTES).order(BIG_ENDIAN); + buffer1native.limit(Long.BYTES); + buffer2native.limit(Long.BYTES); + buffer1be.limit(Long.BYTES); + buffer2be.limit(Long.BYTES); final long[] values = random.longs(10_000_000).toArray(); final LinkedHashMap> comparators = new LinkedHashMap<>(); @@ -198,13 +207,21 @@ public void verifyComparators() { for (int i = 1; i < values.length; i++) { final long val1 = values[i - 1]; final long val2 = values[i]; - buffer1.putLong(0, val1); - buffer2.putLong(0, val2); + buffer1native.putLong(0, val1); + buffer2native.putLong(0, val2); + buffer1be.putLong(0, val1); + buffer2be.putLong(0, val2); uniqueResults.clear(); // Make sure all comparators give the same result for the same inputs comparators.forEach((name, comparator) -> { - final int result = comparator.compare(buffer1, buffer2); + final int result; + // IntegerKey comparator expects keys to have been written in native order so need different buffers. + if (name.equals("compareAsIntegerKeys")) { + result = comparator.compare(buffer1native, buffer2native); + } else { + result = comparator.compare(buffer1be, buffer2be); + } results.put(name, result); uniqueResults.add(result); }); diff --git a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java index bac95e13..be91ea4c 100644 --- a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java @@ -51,20 +51,24 @@ import static org.lmdbjava.TestUtils.bb; import static org.lmdbjava.TestUtils.bbNative; import static org.lmdbjava.TestUtils.getNativeInt; +import static org.lmdbjava.TestUtils.getString; import com.google.common.primitives.UnsignedBytes; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.AbstractMap; import java.util.ArrayList; import java.util.Comparator; import java.util.Deque; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.NoSuchElementException; import java.util.function.Function; +import java.util.stream.Collectors; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Before; @@ -150,6 +154,98 @@ public void after() { env.close(); } + @Test + public void testNumericOrderLong() { + final Dbi dbi = dbiFactory.factory.apply(env); + + try (Txn txn = env.txnWrite()) { + final Cursor c = dbi.openCursor(txn); + long i = 1; + while (true) { +// System.out.println("putting " + i); + c.put(bbNative(i), bb(i + "-long")); + final long i2 = i * 10; + if (i2 < i) { + // Overflowed + break; + } + i = i2; + } + txn.commit(); + } + + final List> entries = new ArrayList<>(); + try (Txn txn = env.txnRead()) { + try (CursorIterable iterable = dbi.iterate(txn)) { + for (KeyVal keyVal : iterable) { + final String val = getString(keyVal.val()); + final long key = TestUtils.getNativeLong(keyVal.key()); + entries.add(new AbstractMap.SimpleEntry<>(key, val)); +// System.out.println(val); + } + } + } + + final List dbKeys = entries.stream() + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + final List dbKeysSorted = entries.stream() + .map(Map.Entry::getKey) + .sorted() + .collect(Collectors.toList()); + for (int i = 0; i < dbKeys.size(); i++) { + final long dbKey1 = dbKeys.get(i); + final long dbKey2 = dbKeysSorted.get(i); + assertThat(dbKey1, is(dbKey2)); + } + } + + @Test + public void testNumericOrderInt() { + final Dbi dbi = dbiFactory.factory.apply(env); + + try (Txn txn = env.txnWrite()) { + final Cursor c = dbi.openCursor(txn); + int i = 1; + while (true) { +// System.out.println("putting " + i); + c.put(bbNative(i), bb(i + "-int")); + final int i2 = i * 10; + if (i2 < i) { + // Overflowed + break; + } + i = i2; + } + txn.commit(); + } + + final List> entries = new ArrayList<>(); + try (Txn txn = env.txnRead()) { + try (CursorIterable iterable = dbi.iterate(txn)) { + for (KeyVal keyVal : iterable) { + final String val = getString(keyVal.val()); + final int key = TestUtils.getNativeInt(keyVal.key()); + entries.add(new AbstractMap.SimpleEntry<>(key, val)); +// System.out.println(val); + } + } + } + + final List dbKeys = entries.stream() + .map(Map.Entry::getKey) + .collect(Collectors.toList()); + final List dbKeysSorted = entries.stream() + .map(Map.Entry::getKey) + .sorted() + .collect(Collectors.toList()); + for (int i = 0; i < dbKeys.size(); i++) { + final long dbKey1 = dbKeys.get(i); + final long dbKey2 = dbKeysSorted.get(i); + assertThat(dbKey1, is(dbKey2)); + } + } + @Test public void allBackwardTest() { verify(allBackward(), 8, 6, 4, 2); diff --git a/src/test/java/org/lmdbjava/TestUtils.java b/src/test/java/org/lmdbjava/TestUtils.java index c26c1c52..511619fe 100644 --- a/src/test/java/org/lmdbjava/TestUtils.java +++ b/src/test/java/org/lmdbjava/TestUtils.java @@ -24,6 +24,7 @@ import java.lang.reflect.InvocationTargetException; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; import org.agrona.MutableDirectBuffer; import org.agrona.concurrent.UnsafeBuffer; @@ -55,13 +56,29 @@ static ByteBuffer bb(final int value) { return bb; } + static ByteBuffer bb(final String value) { + final ByteBuffer bb = allocateDirect(100); + if (value != null) { + bb.put(value.getBytes(StandardCharsets.UTF_8)); + bb.flip(); + } + return bb; + } + static ByteBuffer bbNative(final int value) { - final ByteBuffer bb = allocateDirect(Long.BYTES) + final ByteBuffer bb = allocateDirect(Integer.BYTES) .order(ByteOrder.nativeOrder()); bb.putInt(value).flip(); return bb; } + static ByteBuffer bbNative(final long value) { + final ByteBuffer bb = allocateDirect(Long.BYTES) + .order(ByteOrder.nativeOrder()); + bb.putLong(value).flip(); + return bb; + } + static int getNativeInt(final ByteBuffer bb) { final int val = bb.order(ByteOrder.nativeOrder()) .getInt(); @@ -69,6 +86,20 @@ static int getNativeInt(final ByteBuffer bb) { return val; } + static long getNativeLong(final ByteBuffer bb) { + final long val = bb.order(ByteOrder.nativeOrder()) + .getLong(); + bb.rewind(); + return val; + } + + static String getString(final ByteBuffer bb) { + final String str = StandardCharsets.UTF_8.decode(bb) + .toString(); + bb.rewind(); + return str; + } + static void invokePrivateConstructor(final Class clazz) { try { final Constructor c = clazz.getDeclaredConstructor(); From dc0b96b4ab5489bd986f58895a35b653166016fb Mon Sep 17 00:00:00 2001 From: stroomdev66 Date: Wed, 29 Oct 2025 19:02:36 +0000 Subject: [PATCH 20/90] #267 Fix to establish start key when iterating backwards from a start key using DUPSORT --- .gitignore | 2 +- cross-compile.sh | 1 + .../java/org/lmdbjava/CursorIterable.java | 34 +- src/main/java/org/lmdbjava/KeyRangeType.java | 64 +- .../org/lmdbjava/ByteBufferProxyTest.java | 23 +- .../org/lmdbjava/CursorIterableRangeTest.java | 430 +++++----- .../java/org/lmdbjava/CursorIterableTest.java | 9 +- .../java/org/lmdbjava/CursorParamTest.java | 3 +- src/test/java/org/lmdbjava/CursorTest.java | 7 +- src/test/java/org/lmdbjava/DbiTest.java | 49 +- src/test/java/org/lmdbjava/EnvTest.java | 546 ++++++------- src/test/java/org/lmdbjava/FileUtil.java | 41 - .../org/lmdbjava/GarbageCollectionTest.java | 97 +-- src/test/java/org/lmdbjava/TempDir.java | 57 ++ src/test/java/org/lmdbjava/TestUtils.java | 9 + src/test/java/org/lmdbjava/TutorialTest.java | 734 +++++++++--------- src/test/java/org/lmdbjava/TxnTest.java | 10 +- src/test/java/org/lmdbjava/VerifierTest.java | 27 +- .../testSignedComparatorDupsort.csv | 2 +- .../testUnsignedComparatorDupsort.csv | 13 +- 20 files changed, 1097 insertions(+), 1061 deletions(-) create mode 100644 src/test/java/org/lmdbjava/TempDir.java diff --git a/.gitignore b/.gitignore index f46b8b6f..44fe1d51 100644 --- a/.gitignore +++ b/.gitignore @@ -16,5 +16,5 @@ dependency-reduced-pom.xml gpg-sign.json mvn-sync.json secrets.tar -lmdb +openldap pom.xml.versionsBackup diff --git a/cross-compile.sh b/cross-compile.sh index 5243feec..18d4ce07 100755 --- a/cross-compile.sh +++ b/cross-compile.sh @@ -20,6 +20,7 @@ set -o errexit rm -rf openldap git clone --depth 1 --branch LMDB_0.9.33 https://git.openldap.org/openldap/openldap.git +rm -rf openldap/.git pushd openldap/libraries/liblmdb trap popd SIGINT diff --git a/src/main/java/org/lmdbjava/CursorIterable.java b/src/main/java/org/lmdbjava/CursorIterable.java index 6a03bd90..b6d58e3d 100644 --- a/src/main/java/org/lmdbjava/CursorIterable.java +++ b/src/main/java/org/lmdbjava/CursorIterable.java @@ -15,11 +15,7 @@ */ package org.lmdbjava; -import static org.lmdbjava.CursorIterable.State.RELEASED; -import static org.lmdbjava.CursorIterable.State.REQUIRES_INITIAL_OP; -import static org.lmdbjava.CursorIterable.State.REQUIRES_ITERATOR_OP; -import static org.lmdbjava.CursorIterable.State.REQUIRES_NEXT_OP; -import static org.lmdbjava.CursorIterable.State.TERMINATED; +import static org.lmdbjava.CursorIterable.State.*; import static org.lmdbjava.GetOp.MDB_SET_RANGE; import java.util.Comparator; @@ -101,7 +97,7 @@ public void remove() { } private void executeCursorOp(final CursorOp op) { - final boolean found; + boolean found; switch (op) { case FIRST: found = cursor.first(); @@ -119,7 +115,31 @@ private void executeCursorOp(final CursorOp op) { found = cursor.get(range.getStart(), MDB_SET_RANGE); break; case GET_START_KEY_BACKWARD: - found = cursor.get(range.getStart(), MDB_SET_RANGE) || cursor.last(); + found = cursor.get(range.getStart(), MDB_SET_RANGE); + if (found) { + if (!range.getType().isDirectionForward() + && range.getType().isStartKeyRequired() + && range.getType().isStartKeyInclusive()) { + // We need to ensure we move to the last matching key if using DUPSORT, see issue 267 + boolean loop = true; + while (loop) { + if (comparator.compare(cursor.key(), range.getStart()) <= 0) { + found = cursor.next(); + if (!found) { + // We got to the end so move last. + found = cursor.last(); + loop = false; + } + } else { + // We have moved past so go back one. + found = cursor.prev(); + loop = false; + } + } + } + } else { + found = cursor.last(); + } break; default: throw new IllegalStateException("Unknown cursor operation"); diff --git a/src/main/java/org/lmdbjava/KeyRangeType.java b/src/main/java/org/lmdbjava/KeyRangeType.java index ad67286d..0514cbf2 100644 --- a/src/main/java/org/lmdbjava/KeyRangeType.java +++ b/src/main/java/org/lmdbjava/KeyRangeType.java @@ -46,7 +46,7 @@ public enum KeyRangeType { * *

In our example, the returned keys would be 2, 4, 6 and 8. */ - FORWARD_ALL(true, false, false), + FORWARD_ALL(true, false, false, false, false), /** * Start on the passed key (or the first key immediately after it) and iterate forward until no * keys remain. @@ -56,7 +56,7 @@ public enum KeyRangeType { *

In our example and with a passed search key of 5, the returned keys would be 6 and 8. With a * passed key of 6, the returned keys would be 6 and 8. */ - FORWARD_AT_LEAST(true, true, false), + FORWARD_AT_LEAST(true, true, true, false, false), /** * Start on the first key and iterate forward until a key equal to it (or the first key * immediately after it) is reached. @@ -66,7 +66,7 @@ public enum KeyRangeType { *

In our example and with a passed search key of 5, the returned keys would be 2 and 4. With a * passed key of 6, the returned keys would be 2, 4 and 6. */ - FORWARD_AT_MOST(true, false, true), + FORWARD_AT_MOST(true, false, false, true, true), /** * Iterate forward between the passed keys, matching on the first keys directly equal to the * passed key (or immediately following it in the case of the "start" key, or immediately @@ -77,7 +77,7 @@ public enum KeyRangeType { *

In our example and with a passed search range of 3 - 7, the returned keys would be 4 and 6. * With a range of 2 - 6, the keys would be 2, 4 and 6. */ - FORWARD_CLOSED(true, true, true), + FORWARD_CLOSED(true, true, true, true, true), /** * Iterate forward between the passed keys, matching on the first keys directly equal to the * passed key (or immediately following it in the case of the "start" key, or immediately @@ -88,7 +88,7 @@ public enum KeyRangeType { *

In our example and with a passed search range of 3 - 8, the returned keys would be 4 and 6. * With a range of 2 - 6, the keys would be 2 and 4. */ - FORWARD_CLOSED_OPEN(true, true, true), + FORWARD_CLOSED_OPEN(true, true, true, true, false), /** * Start after the passed key (but not equal to it) and iterate forward until no keys remain. * @@ -97,7 +97,7 @@ public enum KeyRangeType { *

In our example and with a passed search key of 4, the returned keys would be 6 and 8. With a * passed key of 3, the returned keys would be 4, 6 and 8. */ - FORWARD_GREATER_THAN(true, true, false), + FORWARD_GREATER_THAN(true, true, false, false, false), /** * Start on the first key and iterate forward until a key the passed key has been reached (but do * not return that key). @@ -107,7 +107,7 @@ public enum KeyRangeType { *

In our example and with a passed search key of 5, the returned keys would be 2 and 4. With a * passed key of 8, the returned keys would be 2, 4 and 6. */ - FORWARD_LESS_THAN(true, false, true), + FORWARD_LESS_THAN(true, false, false, true, false), /** * Iterate forward between the passed keys but not equal to either of them. * @@ -116,7 +116,7 @@ public enum KeyRangeType { *

In our example and with a passed search range of 3 - 7, the returned keys would be 4 and 6. * With a range of 2 - 8, the key would be 4 and 6. */ - FORWARD_OPEN(true, true, true), + FORWARD_OPEN(true, true, false, true, false), /** * Iterate forward between the passed keys. Do not return the "start" key, but do return the * "stop" key. @@ -126,7 +126,7 @@ public enum KeyRangeType { *

In our example and with a passed search range of 3 - 8, the returned keys would be 4, 6 and * 8. With a range of 2 - 6, the keys would be 4 and 6. */ - FORWARD_OPEN_CLOSED(true, true, true), + FORWARD_OPEN_CLOSED(true, true, false, true, true), /** * Start on the last key and iterate backward until no keys remain. * @@ -134,7 +134,7 @@ public enum KeyRangeType { * *

In our example, the returned keys would be 8, 6, 4 and 2. */ - BACKWARD_ALL(false, false, false), + BACKWARD_ALL(false, false, false, false, false), /** * Start on the passed key (or the first key immediately preceding it) and iterate backward until * no keys remain. @@ -145,7 +145,7 @@ public enum KeyRangeType { * passed key of 6, the returned keys would be 6, 4 and 2. With a passed key of 9, the returned * keys would be 8, 6, 4 and 2. */ - BACKWARD_AT_LEAST(false, true, false), + BACKWARD_AT_LEAST(false, true, true, false, false), /** * Start on the last key and iterate backward until a key equal to it (or the first key * immediately preceding it it) is reached. @@ -155,7 +155,7 @@ public enum KeyRangeType { *

In our example and with a passed search key of 5, the returned keys would be 8 and 6. With a * passed key of 6, the returned keys would be 8 and 6. */ - BACKWARD_AT_MOST(false, false, true), + BACKWARD_AT_MOST(false, false, false, true, true), /** * Iterate backward between the passed keys, matching on the first keys directly equal to the * passed key (or immediately preceding it in the case of the "start" key, or immediately @@ -167,7 +167,7 @@ public enum KeyRangeType { * With a range of 6 - 2, the keys would be 6, 4 and 2. With a range of 9 - 3, the returned keys * would be 8, 6 and 4. */ - BACKWARD_CLOSED(false, true, true), + BACKWARD_CLOSED(false, true, true, true, true), /** * Iterate backward between the passed keys, matching on the first keys directly equal to the * passed key (or immediately preceding it in the case of the "start" key, or immediately @@ -179,7 +179,7 @@ public enum KeyRangeType { * 4. With a range of 7 - 2, the keys would be 6 and 4. With a range of 9 - 3, the keys would be * 8, 6 and 4. */ - BACKWARD_CLOSED_OPEN(false, true, true), + BACKWARD_CLOSED_OPEN(false, true, true, true, false), /** * Start immediate prior to the passed key (but not equal to it) and iterate backward until no * keys remain. @@ -190,7 +190,7 @@ public enum KeyRangeType { * passed key of 7, the returned keys would be 6, 4 and 2. With a passed key of 9, the returned * keys would be 8, 6, 4 and 2. */ - BACKWARD_GREATER_THAN(false, true, false), + BACKWARD_GREATER_THAN(false, true, false, false, false), /** * Start on the last key and iterate backward until the last key greater than the passed "stop" * key is reached. Do not return the "stop" key. @@ -200,7 +200,7 @@ public enum KeyRangeType { *

In our example and with a passed search key of 5, the returned keys would be 8 and 6. With a * passed key of 2, the returned keys would be 8, 6 and 4 */ - BACKWARD_LESS_THAN(false, false, true), + BACKWARD_LESS_THAN(false, false, false, true, false), /** * Iterate backward between the passed keys, but do not return the passed keys. * @@ -210,7 +210,7 @@ public enum KeyRangeType { * With a range of 8 - 1, the keys would be 6, 4 and 2. With a range of 9 - 4, the keys would be 8 * and 6. */ - BACKWARD_OPEN(false, true, true), + BACKWARD_OPEN(false, true, false, true, false), /** * Iterate backward between the passed keys. Do not return the "start" key, but do return the * "stop" key. @@ -221,19 +221,25 @@ public enum KeyRangeType { * 2. With a range of 8 - 4, the keys would be 6 and 4. With a range of 9 - 4, the keys would be * 8, 6 and 4. */ - BACKWARD_OPEN_CLOSED(false, true, true); + BACKWARD_OPEN_CLOSED(false, true, false, true, true); private final boolean directionForward; private final boolean startKeyRequired; + private final boolean startKeyInclusive; private final boolean stopKeyRequired; + private final boolean stopKeyInclusive; KeyRangeType( final boolean directionForward, final boolean startKeyRequired, - final boolean stopKeyRequired) { + final boolean startKeyInclusive, + final boolean stopKeyRequired, + final boolean stopKeyInclusive) { this.directionForward = directionForward; this.startKeyRequired = startKeyRequired; + this.startKeyInclusive = startKeyInclusive; this.stopKeyRequired = stopKeyRequired; + this.stopKeyInclusive = stopKeyInclusive; } /** @@ -254,6 +260,16 @@ public boolean isStartKeyRequired() { return startKeyRequired; } + /** + * Is the start key to be treated as inclusive in the range. + * + * @return true if start key is inclusive. False if not inclusive or no start key is required by + * the range type. + */ + public boolean isStartKeyInclusive() { + return startKeyInclusive; + } + /** * Whether the iteration requires a "stop" key. * @@ -263,6 +279,16 @@ public boolean isStopKeyRequired() { return stopKeyRequired; } + /** + * Is the stop key to be treated as inclusive in the range. + * + * @return true if stop key is inclusive. False if not inclusive or no stop key is required by the + * range type. + */ + public boolean isStopKeyInclusive() { + return stopKeyInclusive; + } + /** * Determine the iterator action to take when iterator first begins. * diff --git a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java index 82c0abce..d5638fe2 100644 --- a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java +++ b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java @@ -36,6 +36,7 @@ import java.lang.reflect.Field; import java.nio.ByteBuffer; +import java.nio.file.Path; import jnr.ffi.Pointer; import jnr.ffi.provider.MemoryManager; import org.junit.jupiter.api.Test; @@ -51,17 +52,17 @@ public final class ByteBufferProxyTest { void buffersMustBeDirect() { assertThatThrownBy( () -> { - FileUtil.useTempDir( - dir -> { - try (Env env = create().setMaxReaders(1).open(dir.toFile())) { - final Dbi db = env.openDbi(DB_1, MDB_CREATE); - final ByteBuffer key = allocate(100); - key.putInt(1).flip(); - final ByteBuffer val = allocate(100); - val.putInt(1).flip(); - db.put(key, val); // error - } - }); + try (final TempDir tempDir = new TempDir()) { + final Path dir = tempDir.createTempDir(); + try (final Env env = create().setMaxReaders(1).open(dir.toFile())) { + final Dbi db = env.openDbi(DB_1, MDB_CREATE); + final ByteBuffer key = allocate(100); + key.putInt(1).flip(); + final ByteBuffer val = allocate(100); + val.putInt(1).flip(); + db.put(key, val); // error + } + } }) .isInstanceOf(BufferMustBeDirectException.class); } diff --git a/src/test/java/org/lmdbjava/CursorIterableRangeTest.java b/src/test/java/org/lmdbjava/CursorIterableRangeTest.java index bff6593e..dd33e885 100644 --- a/src/test/java/org/lmdbjava/CursorIterableRangeTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableRangeTest.java @@ -16,162 +16,175 @@ package org.lmdbjava; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvFileSource; -import org.lmdbjava.ByteBufferProxy.AbstractByteBufferProxy; -import org.lmdbjava.CursorIterable.KeyVal; +import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; +import static java.nio.ByteBuffer.allocateDirect; +import static org.assertj.core.api.Assertions.assertThat; +import static org.lmdbjava.DbiFlags.*; +import static org.lmdbjava.Env.create; +import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; +import static org.lmdbjava.TestUtils.*; import java.io.*; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.nio.file.Files; import java.nio.file.Path; import java.util.Comparator; import java.util.EnumSet; import java.util.function.BiConsumer; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvFileSource; +import org.lmdbjava.ByteBufferProxy.AbstractByteBufferProxy; +import org.lmdbjava.CursorIterable.KeyVal; -import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; -import static java.nio.ByteBuffer.allocateDirect; -import static org.assertj.core.api.Assertions.assertThat; -import static org.lmdbjava.DbiFlags.*; -import static org.lmdbjava.Env.create; -import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; -import static org.lmdbjava.TestUtils.*; - -/** - * Test {@link CursorIterable}. - */ +/** Test {@link CursorIterable}. */ public final class CursorIterableRangeTest { @ParameterizedTest(name = "{index} => {0}: ({1}, {2})") @CsvFileSource(resources = "/CursorIterableRangeTest/testSignedComparator.csv") - void testSignedComparator(final String keyType, final String startKey, final String stopKey, final String expectedKV) { - testCSV(ByteBuffer::compareTo, - true, - createBasicDBPopulator(), - EnumSet.of(MDB_CREATE), - keyType, - startKey, - stopKey, - expectedKV); + void testSignedComparator( + final String keyType, final String startKey, final String stopKey, final String expectedKV) { + testCSV( + ByteBuffer::compareTo, + true, + createBasicDBPopulator(), + EnumSet.of(MDB_CREATE), + keyType, + startKey, + stopKey, + expectedKV); } @ParameterizedTest(name = "{index} => {0}: ({1}, {2})") @CsvFileSource(resources = "/CursorIterableRangeTest/testUnsignedComparator.csv") - void testUnsignedComparator(final String keyType, final String startKey, final String stopKey, final String expectedKV) { - testCSV(AbstractByteBufferProxy::compareBuff, - false, - createBasicDBPopulator(), - EnumSet.of(MDB_CREATE), - keyType, - startKey, - stopKey, - expectedKV); + void testUnsignedComparator( + final String keyType, final String startKey, final String stopKey, final String expectedKV) { + testCSV( + AbstractByteBufferProxy::compareBuff, + false, + createBasicDBPopulator(), + EnumSet.of(MDB_CREATE), + keyType, + startKey, + stopKey, + expectedKV); } @ParameterizedTest(name = "{index} => {0}: ({1}, {2})") @CsvFileSource(resources = "/CursorIterableRangeTest/testSignedComparatorDupsort.csv") - void testSignedComparatorDupsort(final String keyType, final String startKey, final String stopKey, final String expectedKV) { - testCSV(ByteBuffer::compareTo, - true, - createMultiDBPopulator(2), - EnumSet.of(MDB_CREATE, MDB_DUPSORT), - keyType, - startKey, - stopKey, - expectedKV); + void testSignedComparatorDupsort( + final String keyType, final String startKey, final String stopKey, final String expectedKV) { + testCSV( + ByteBuffer::compareTo, + true, + createMultiDBPopulator(2), + EnumSet.of(MDB_CREATE, MDB_DUPSORT), + keyType, + startKey, + stopKey, + expectedKV); } @ParameterizedTest(name = "{index} => {0}: ({1}, {2})") @CsvFileSource(resources = "/CursorIterableRangeTest/testUnsignedComparatorDupsort.csv") - void testUnsignedComparatorDupsort(final String keyType, final String startKey, final String stopKey, final String expectedKV) { - testCSV(AbstractByteBufferProxy::compareBuff, - false, - createMultiDBPopulator(2), - EnumSet.of(MDB_CREATE, MDB_DUPSORT), - keyType, - startKey, - stopKey, - expectedKV); + void testUnsignedComparatorDupsort( + final String keyType, final String startKey, final String stopKey, final String expectedKV) { + testCSV( + AbstractByteBufferProxy::compareBuff, + false, + createMultiDBPopulator(2), + EnumSet.of(MDB_CREATE, MDB_DUPSORT), + keyType, + startKey, + stopKey, + expectedKV); } @ParameterizedTest(name = "{index} => {0}: ({1}, {2})") @CsvFileSource(resources = "/CursorIterableRangeTest/testIntegerKey.csv") - void testIntegerKey(final String keyType, final String startKey, final String stopKey, final String expectedKV) { - testCSV(AbstractByteBufferProxy::compareBuff, - false, - createIntegerDBPopulator(), - EnumSet.of(MDB_CREATE, MDB_INTEGERKEY), - keyType, - startKey, - stopKey, - expectedKV, - Integer.BYTES, - ByteOrder.LITTLE_ENDIAN); + void testIntegerKey( + final String keyType, final String startKey, final String stopKey, final String expectedKV) { + testCSV( + AbstractByteBufferProxy::compareBuff, + false, + createIntegerDBPopulator(), + EnumSet.of(MDB_CREATE, MDB_INTEGERKEY), + keyType, + startKey, + stopKey, + expectedKV, + Integer.BYTES, + ByteOrder.LITTLE_ENDIAN); } @ParameterizedTest(name = "{index} => {0}: ({1}, {2})") @CsvFileSource(resources = "/CursorIterableRangeTest/testLongKey.csv") - void testLongKey(final String keyType, final String startKey, final String stopKey, final String expectedKV) { - testCSV(AbstractByteBufferProxy::compareBuff, - false, - createLongDBPopulator(), - EnumSet.of(MDB_CREATE, MDB_INTEGERKEY), - keyType, - startKey, - stopKey, - expectedKV, - Long.BYTES, - ByteOrder.LITTLE_ENDIAN); + void testLongKey( + final String keyType, final String startKey, final String stopKey, final String expectedKV) { + testCSV( + AbstractByteBufferProxy::compareBuff, + false, + createLongDBPopulator(), + EnumSet.of(MDB_CREATE, MDB_INTEGERKEY), + keyType, + startKey, + stopKey, + expectedKV, + Long.BYTES, + ByteOrder.LITTLE_ENDIAN); } - - private void testCSV(final Comparator comparator, - final boolean nativeCb, - final BiConsumer, Dbi> dbPopulator, - final EnumSet flags, - final String keyType, - final String startKey, - final String stopKey, - final String expectedKV) { - testCSV(comparator, nativeCb, dbPopulator, flags, keyType, startKey, stopKey, expectedKV, Integer.BYTES, ByteOrder.BIG_ENDIAN); + private void testCSV( + final Comparator comparator, + final boolean nativeCb, + final BiConsumer, Dbi> dbPopulator, + final EnumSet flags, + final String keyType, + final String startKey, + final String stopKey, + final String expectedKV) { + testCSV( + comparator, + nativeCb, + dbPopulator, + flags, + keyType, + startKey, + stopKey, + expectedKV, + Integer.BYTES, + ByteOrder.BIG_ENDIAN); } - private void testCSV(final Comparator comparator, - final boolean nativeCb, - final BiConsumer, Dbi> dbPopulator, - final EnumSet flags, - final String keyType, - final String startKey, - final String stopKey, - final String expectedKV, - final int keyLen, - final ByteOrder byteOrder) { - FileUtil.useTempFile(file -> { + private void testCSV( + final Comparator comparator, + final boolean nativeCb, + final BiConsumer, Dbi> dbPopulator, + final EnumSet flags, + final String keyType, + final String startKey, + final String stopKey, + final String expectedKV, + final int keyLen, + final ByteOrder byteOrder) { + try (final TempDir tempDir = new TempDir()) { + final Path file = tempDir.createTempFile(); try (final Env env = - create() - .setMapSize(KIBIBYTES.toBytes(256)) - .setMaxReaders(1) - .setMaxDbs(1) - .open(file.toFile(), POSIX_MODE, MDB_NOSUBDIR)) { - final Dbi dbi = env.openDbi(DB_1, comparator, nativeCb, flags.toArray(new DbiFlags[0])); + create() + .setMapSize(KIBIBYTES.toBytes(256)) + .setMaxReaders(1) + .setMaxDbs(1) + .open(file.toFile(), POSIX_MODE, MDB_NOSUBDIR)) { + final Dbi dbi = + env.openDbi(DB_1, comparator, nativeCb, flags.toArray(new DbiFlags[0])); dbPopulator.accept(env, dbi); - -// final File tests = new File("src/test/resources/CursorIterableRangeTest/tests.csv"); -// final File actual = tests.getParentFile().toPath().resolve(testName + ".actual").toFile(); -// final File expected = tests.getParentFile().toPath().resolve(testName + ".expected").toFile(); -// final String csv = readFile(tests); -// final String[] parts = csv.split("\n"); try (final Writer writer = new StringWriter()) { -// for (final String part : parts) { -// final String[] params = part.split(","); final KeyRangeType keyRangeType = KeyRangeType.valueOf(keyType.trim()); ByteBuffer start = parseKey(startKey, keyLen, byteOrder); ByteBuffer stop = parseKey(stopKey, keyLen, byteOrder); final KeyRange keyRange = new KeyRange<>(keyRangeType, start, stop); try (Txn txn = env.txnRead(); - CursorIterable c = dbi.iterate(txn, keyRange)) { + CursorIterable c = dbi.iterate(txn, keyRange)) { for (final KeyVal kv : c) { final long key = getLong(kv.key(), byteOrder); final long val = getLong(kv.val(), byteOrder); @@ -186,19 +199,11 @@ private void testCSV(final Comparator comparator, } catch (final IOException e) { throw new UncheckedIOException(e); } - - -// // Compare files. -// final String act = readFile(actual); -// final String exp = readFile(expected); -// assertThat(act).withFailMessage("Files are not equal").isEqualTo(exp); } - }); + } } - private ByteBuffer parseKey(final String key, - final int keyLen, - final ByteOrder byteOrder) { + private ByteBuffer parseKey(final String key, final int keyLen, final ByteOrder byteOrder) { if (key != null) { if (ByteOrder.LITTLE_ENDIAN.equals(byteOrder)) { if (keyLen == Integer.BYTES) { @@ -217,8 +222,7 @@ private ByteBuffer parseKey(final String key, return null; } - private long getLong(final ByteBuffer byteBuffer, - final ByteOrder byteOrder) { + private long getLong(final ByteBuffer byteBuffer, final ByteOrder byteOrder) { byteBuffer.order(byteOrder); if (byteBuffer.remaining() == Integer.BYTES) { return byteBuffer.getInt(); @@ -227,92 +231,95 @@ private long getLong(final ByteBuffer byteBuffer, } } -// -// @Test -// void testSignedComparator() throws IOException { -// test(ByteBuffer::compareTo, true, "testSignedComparator", 1, MDB_CREATE); -// } -// -// @Test -// void testUnsignedComparator() throws IOException { -// test(AbstractByteBufferProxy::compareBuff, false, "testUnsignedComparator", 1, MDB_CREATE); -// } -// -// @Test -// void testSignedComparatorDupsort() throws IOException { -// test(ByteBuffer::compareTo, true, "testSignedComparatorDupsort", 2, MDB_CREATE, MDB_DUPSORT); -// } -// -// @Test -// void testUnsignedComparatorDupsort() throws IOException { -// test(AbstractByteBufferProxy::compareBuff, false, "testUnsignedComparatorDupsort", 2, MDB_CREATE, MDB_DUPSORT); -// } - - private void test(final Comparator comparator, - final boolean nativeCb, - final String testName, - final BiConsumer, Dbi> dbPopulator, - final DbiFlags... flags) throws IOException { - final Path dbPath = Files.createTempFile("test", "db"); - try (final Env env = - create() - .setMapSize(KIBIBYTES.toBytes(256)) - .setMaxReaders(1) - .setMaxDbs(1) - .open(dbPath.toFile(), POSIX_MODE, MDB_NOSUBDIR)) { - final Dbi dbi = env.openDbi(DB_1, comparator, nativeCb, flags); - dbPopulator.accept(env, dbi); - - final File tests = new File("src/test/resources/CursorIterableRangeTest/tests.csv"); - final File actual = tests.getParentFile().toPath().resolve(testName + ".actual").toFile(); - final File expected = tests.getParentFile().toPath().resolve(testName + ".expected").toFile(); - final String csv = readFile(tests); - final String[] parts = csv.split("\n"); - try (final Writer writer = new FileWriter(actual)) { - for (final String part : parts) { - final String[] params = part.split(","); - final KeyRangeType keyRangeType = KeyRangeType.valueOf(params[0].trim()); - ByteBuffer start = null; - ByteBuffer stop = null; - if (params.length > 1 && params[1].trim().length() > 0) { - start = bb(Integer.parseInt(params[1].trim())); - } - if (params.length > 2 && params[2].trim().length() > 0) { - stop = bb(Integer.parseInt(params[2].trim())); - } - - for (int i = 0; i < 3; i++) { - if (params.length > i) { - writer.append(params[i].trim()); - } - writer.append(","); - } - - final KeyRange keyRange = new KeyRange<>(keyRangeType, start, stop); - try (Txn txn = env.txnRead(); - CursorIterable c = dbi.iterate(txn, keyRange)) { - for (final KeyVal kv : c) { - final int key = kv.key().getInt(); - final int val = kv.val().getInt(); - writer.append("["); - writer.append(String.valueOf(key)); - writer.append(" "); - writer.append(String.valueOf(val)); - writer.append("]"); - } - } - writer.append("\n"); - } - } - - // Compare files. - final String act = readFile(actual); - final String exp = readFile(expected); - assertThat(act).withFailMessage("Files are not equal").isEqualTo(exp); - } finally { - FileUtil.deleteFile(dbPath); - } - } + // + // @Test + // void testSignedComparator() throws IOException { + // test(ByteBuffer::compareTo, true, "testSignedComparator", 1, MDB_CREATE); + // } + // + // @Test + // void testUnsignedComparator() throws IOException { + // test(AbstractByteBufferProxy::compareBuff, false, "testUnsignedComparator", 1, MDB_CREATE); + // } + // + // @Test + // void testSignedComparatorDupsort() throws IOException { + // test(ByteBuffer::compareTo, true, "testSignedComparatorDupsort", 2, MDB_CREATE, + // MDB_DUPSORT); + // } + // + // @Test + // void testUnsignedComparatorDupsort() throws IOException { + // test(AbstractByteBufferProxy::compareBuff, false, "testUnsignedComparatorDupsort", 2, + // MDB_CREATE, MDB_DUPSORT); + // } + // + // private void test(final Comparator comparator, + // final boolean nativeCb, + // final String testName, + // final BiConsumer, Dbi> dbPopulator, + // final DbiFlags... flags) throws IOException { + // final Path dbPath = Files.createTempFile("test", "db"); + // try (final Env env = + // create() + // .setMapSize(KIBIBYTES.toBytes(256)) + // .setMaxReaders(1) + // .setMaxDbs(1) + // .open(dbPath.toFile(), POSIX_MODE, MDB_NOSUBDIR)) { + // final Dbi dbi = env.openDbi(DB_1, comparator, nativeCb, flags); + // dbPopulator.accept(env, dbi); + // + // final File tests = new File("src/test/resources/CursorIterableRangeTest/tests.csv"); + // final File actual = tests.getParentFile().toPath().resolve(testName + ".actual").toFile(); + // final File expected = tests.getParentFile().toPath().resolve(testName + + // ".expected").toFile(); + // final String csv = readFile(tests); + // final String[] parts = csv.split("\n"); + // try (final Writer writer = new FileWriter(actual)) { + // for (final String part : parts) { + // final String[] params = part.split(","); + // final KeyRangeType keyRangeType = KeyRangeType.valueOf(params[0].trim()); + // ByteBuffer start = null; + // ByteBuffer stop = null; + // if (params.length > 1 && params[1].trim().length() > 0) { + // start = bb(Integer.parseInt(params[1].trim())); + // } + // if (params.length > 2 && params[2].trim().length() > 0) { + // stop = bb(Integer.parseInt(params[2].trim())); + // } + // + // for (int i = 0; i < 3; i++) { + // if (params.length > i) { + // writer.append(params[i].trim()); + // } + // writer.append(","); + // } + // + // final KeyRange keyRange = new KeyRange<>(keyRangeType, start, stop); + // try (Txn txn = env.txnRead(); + // CursorIterable c = dbi.iterate(txn, keyRange)) { + // for (final KeyVal kv : c) { + // final int key = kv.key().getInt(); + // final int val = kv.val().getInt(); + // writer.append("["); + // writer.append(String.valueOf(key)); + // writer.append(" "); + // writer.append(String.valueOf(val)); + // writer.append("]"); + // } + // } + // writer.append("\n"); + // } + // } + // + // // Compare files. + // final String act = readFile(actual); + // final String exp = readFile(expected); + // assertThat(act).withFailMessage("Files are not equal").isEqualTo(exp); + // } finally { + // FileUtil.deleteFile(dbPath); + // } + // } private BiConsumer, Dbi> createBasicDBPopulator() { return (env, dbi) -> { @@ -367,16 +374,15 @@ private BiConsumer, Dbi> createLongDBPopulator() { c.put(bbLeLong(Long.MIN_VALUE), bb(1)); c.put(bbLeLong(-1000), bb(2)); c.put(bbLeLong(0), bb(3)); - c.put(bbLeLong( 1000), bb(4)); + c.put(bbLeLong(1000), bb(4)); c.put(bbLeLong(Long.MAX_VALUE), bb(5)); txn.commit(); } }; } - private void populateDatabase(final Env env, - final Dbi dbi, - final int copies) { + private void populateDatabase( + final Env env, final Dbi dbi, final int copies) { try (Txn txn = env.txnWrite()) { final Cursor c = dbi.openCursor(txn); for (int i = 0; i < copies; i++) { diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index 9cceb437..5e515fee 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -48,7 +48,6 @@ import com.google.common.primitives.UnsignedBytes; import java.nio.ByteBuffer; -import java.nio.file.Path; import java.util.ArrayList; import java.util.Comparator; import java.util.Deque; @@ -64,20 +63,20 @@ /** Test {@link CursorIterable}. */ public final class CursorIterableTest { - private Path file; + private TempDir tempDir; private Dbi db; private Env env; private Deque list; @BeforeEach void beforeEach() { - file = FileUtil.createTempFile(); + tempDir = new TempDir(); env = create() .setMapSize(KIBIBYTES.toBytes(256)) .setMaxReaders(1) .setMaxDbs(1) - .open(file.toFile(), POSIX_MODE, MDB_NOSUBDIR); + .open(tempDir.createTempFile().toFile(), POSIX_MODE, MDB_NOSUBDIR); db = env.openDbi(DB_1, MDB_CREATE); populateDatabase(db); } @@ -98,7 +97,7 @@ private void populateDatabase(final Dbi dbi) { @AfterEach void afterEach() { env.close(); - FileUtil.deleteFile(file); + tempDir.cleanup(); } @Test diff --git a/src/test/java/org/lmdbjava/CursorParamTest.java b/src/test/java/org/lmdbjava/CursorParamTest.java index 1a3604be..7f0bad8a 100644 --- a/src/test/java/org/lmdbjava/CursorParamTest.java +++ b/src/test/java/org/lmdbjava/CursorParamTest.java @@ -58,7 +58,8 @@ public final class CursorParamTest { static Stream data() { return Stream.of( - Arguments.argumentSet("ByteBufferRunner(PROXY_OPTIMAL)", new ByteBufferRunner(PROXY_OPTIMAL)), + Arguments.argumentSet( + "ByteBufferRunner(PROXY_OPTIMAL)", new ByteBufferRunner(PROXY_OPTIMAL)), Arguments.argumentSet("ByteBufferRunner(PROXY_SAFE)", new ByteBufferRunner(PROXY_SAFE)), Arguments.argumentSet("ByteArrayRunner(PROXY_BA)", new ByteArrayRunner(PROXY_BA)), Arguments.argumentSet("DirectBufferRunner", new DirectBufferRunner()), diff --git a/src/test/java/org/lmdbjava/CursorTest.java b/src/test/java/org/lmdbjava/CursorTest.java index f44a9a44..530b2e80 100644 --- a/src/test/java/org/lmdbjava/CursorTest.java +++ b/src/test/java/org/lmdbjava/CursorTest.java @@ -54,12 +54,13 @@ /** Test {@link Cursor}. */ public final class CursorTest { - private Path file; private Env env; + private TempDir tempDir; @BeforeEach void beforeEach() { - file = FileUtil.createTempFile(); + tempDir = new TempDir(); + Path file = tempDir.createTempFile(); env = create(PROXY_OPTIMAL) .setMapSize(MEBIBYTES.toBytes(1)) @@ -71,7 +72,7 @@ void beforeEach() { @AfterEach void afterEach() { env.close(); - FileUtil.deleteFile(file); + tempDir.cleanup(); } @Test diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index cb106550..2c4a90b7 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -62,21 +62,21 @@ /** Test {@link Dbi}. */ public final class DbiTest { - private Path file; + private TempDir tempDir; private Env env; - private Path fileBa; private Env envBa; @BeforeEach void beforeEach() { - file = FileUtil.createTempFile(); + tempDir = new TempDir(); + final Path file = tempDir.createTempFile(); env = create() .setMapSize(MEBIBYTES.toBytes(64)) .setMaxReaders(2) .setMaxDbs(2) .open(file.toFile(), MDB_NOSUBDIR); - fileBa = FileUtil.createTempFile(); + final Path fileBa = tempDir.createTempFile(); envBa = create(PROXY_BA) .setMapSize(MEBIBYTES.toBytes(64)) @@ -89,8 +89,7 @@ void beforeEach() { void afterEach() { env.close(); envBa.close(); - FileUtil.deleteFile(file); - FileUtil.deleteFile(fileBa); + tempDir.cleanup(); } @Test @@ -362,26 +361,24 @@ void putCommitGet() { @Test void putCommitGetByteArray() { - FileUtil.useTempFile( - file -> { - try (Env envBa = - create(PROXY_BA) - .setMapSize(MEBIBYTES.toBytes(64)) - .setMaxReaders(1) - .setMaxDbs(2) - .open(file.toFile(), MDB_NOSUBDIR)) { - final Dbi db = envBa.openDbi(DB_1, MDB_CREATE); - try (Txn txn = envBa.txnWrite()) { - db.put(txn, ba(5), ba(5)); - txn.commit(); - } - try (Txn txn = envBa.txnWrite()) { - final byte[] found = db.get(txn, ba(5)); - assertThat(found).isNotNull(); - assertThat(fromBa(txn.val())).isEqualTo(5); - } - } - }); + final Path file = tempDir.createTempFile(); + try (Env envBa = + create(PROXY_BA) + .setMapSize(MEBIBYTES.toBytes(64)) + .setMaxReaders(1) + .setMaxDbs(2) + .open(file.toFile(), MDB_NOSUBDIR)) { + final Dbi db = envBa.openDbi(DB_1, MDB_CREATE); + try (Txn txn = envBa.txnWrite()) { + db.put(txn, ba(5), ba(5)); + txn.commit(); + } + try (Txn txn = envBa.txnWrite()) { + final byte[] found = db.get(txn, ba(5)); + assertThat(found).isNotNull(); + assertThat(fromBa(txn.val())).isEqualTo(5); + } + } } @Test diff --git a/src/test/java/org/lmdbjava/EnvTest.java b/src/test/java/org/lmdbjava/EnvTest.java index 904159bb..d66aa3a3 100644 --- a/src/test/java/org/lmdbjava/EnvTest.java +++ b/src/test/java/org/lmdbjava/EnvTest.java @@ -37,43 +37,49 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Random; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.lmdbjava.Env.AlreadyClosedException; -import org.lmdbjava.Env.AlreadyOpenException; -import org.lmdbjava.Env.Builder; -import org.lmdbjava.Env.InvalidCopyDestination; -import org.lmdbjava.Env.MapFullException; +import org.lmdbjava.Env.*; import org.lmdbjava.Txn.BadReaderLockException; /** Test {@link Env}. */ public final class EnvTest { + private TempDir tempDir; + + @BeforeEach + void beforeEach() { + tempDir = new TempDir(); + } + + @AfterEach + void afterEach() { + tempDir.cleanup(); + } + @Test void byteUnit() { - FileUtil.useTempFile( - file -> { - try (Env env = - create() - .setMaxReaders(1) - .setMapSize(MEBIBYTES.toBytes(1)) - .open(file.toFile(), MDB_NOSUBDIR)) { - final EnvInfo info = env.info(); - assertThat(info.mapSize).isEqualTo(MEBIBYTES.toBytes(1)); - } - }); + final Path file = tempDir.createTempFile(); + try (Env env = + create() + .setMaxReaders(1) + .setMapSize(MEBIBYTES.toBytes(1)) + .open(file.toFile(), MDB_NOSUBDIR)) { + final EnvInfo info = env.info(); + assertThat(info.mapSize).isEqualTo(MEBIBYTES.toBytes(1)); + } } @Test void cannotChangeMapSizeAfterOpen() { assertThatThrownBy( () -> { - FileUtil.useTempFile( - file -> { - final Builder builder = create().setMaxReaders(1); - try (Env env = builder.open(file.toFile(), MDB_NOSUBDIR)) { - builder.setMapSize(1); - } - }); + final Path file = tempDir.createTempFile(); + final Builder builder = create().setMaxReaders(1); + try (Env env = builder.open(file.toFile(), MDB_NOSUBDIR)) { + builder.setMapSize(1); + } }) .isInstanceOf(AlreadyOpenException.class); } @@ -82,13 +88,11 @@ void cannotChangeMapSizeAfterOpen() { void cannotChangeMaxDbsAfterOpen() { assertThatThrownBy( () -> { - FileUtil.useTempFile( - file -> { - final Builder builder = create().setMaxReaders(1); - try (Env env = builder.open(file.toFile(), MDB_NOSUBDIR)) { - builder.setMaxDbs(1); - } - }); + final Path file = tempDir.createTempFile(); + final Builder builder = create().setMaxReaders(1); + try (Env env = builder.open(file.toFile(), MDB_NOSUBDIR)) { + builder.setMaxDbs(1); + } }) .isInstanceOf(AlreadyOpenException.class); } @@ -97,13 +101,11 @@ void cannotChangeMaxDbsAfterOpen() { void cannotChangeMaxReadersAfterOpen() { assertThatThrownBy( () -> { - FileUtil.useTempFile( - file -> { - final Builder builder = create().setMaxReaders(1); - try (Env env = builder.open(file.toFile(), MDB_NOSUBDIR)) { - builder.setMaxReaders(1); - } - }); + final Path file = tempDir.createTempFile(); + final Builder builder = create().setMaxReaders(1); + try (Env env = builder.open(file.toFile(), MDB_NOSUBDIR)) { + builder.setMaxReaders(1); + } }) .isInstanceOf(AlreadyOpenException.class); } @@ -112,13 +114,11 @@ void cannotChangeMaxReadersAfterOpen() { void cannotInfoOnceClosed() { assertThatThrownBy( () -> { - FileUtil.useTempFile( - file -> { - final Env env = - create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR); - env.close(); - env.info(); - }); + final Path file = tempDir.createTempFile(); + final Env env = + create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR); + env.close(); + env.info(); }) .isInstanceOf(AlreadyClosedException.class); } @@ -127,12 +127,10 @@ void cannotInfoOnceClosed() { void cannotOpenTwice() { assertThatThrownBy( () -> { - FileUtil.useTempFile( - file -> { - final Builder builder = create().setMaxReaders(1); - builder.open(file.toFile(), MDB_NOSUBDIR).close(); - builder.open(file.toFile(), MDB_NOSUBDIR); - }); + final Path file = tempDir.createTempFile(); + final Builder builder = create().setMaxReaders(1); + builder.open(file.toFile(), MDB_NOSUBDIR).close(); + builder.open(file.toFile(), MDB_NOSUBDIR); }) .isInstanceOf(AlreadyOpenException.class); } @@ -153,13 +151,11 @@ void cannotOverflowMapSize() { void cannotStatOnceClosed() { assertThatThrownBy( () -> { - FileUtil.useTempFile( - file -> { - final Env env = - create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR); - env.close(); - env.stat(); - }); + final Path file = tempDir.createTempFile(); + final Env env = + create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR); + env.close(); + env.stat(); }) .isInstanceOf(AlreadyClosedException.class); } @@ -168,48 +164,38 @@ void cannotStatOnceClosed() { void cannotSyncOnceClosed() { assertThatThrownBy( () -> { - FileUtil.useTempFile( - file -> { - final Env env = - create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR); - env.close(); - env.sync(false); - }); + final Path file = tempDir.createTempFile(); + final Env env = + create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR); + env.close(); + env.sync(false); }) .isInstanceOf(AlreadyClosedException.class); } @Test void copyDirectoryBased() { - FileUtil.useTempDir( - dest -> { - assertThat(Files.exists(dest)).isTrue(); - assertThat(Files.isDirectory(dest)).isTrue(); - assertThat(FileUtil.count(dest)).isEqualTo(0); - FileUtil.useTempDir( - src -> { - try (Env env = create().setMaxReaders(1).open(src.toFile())) { - env.copy(dest.toFile(), MDB_CP_COMPACT); - assertThat(FileUtil.count(dest)).isEqualTo(1); - } - }); - }); + final Path dest = tempDir.createTempDir(); + assertThat(Files.exists(dest)).isTrue(); + assertThat(Files.isDirectory(dest)).isTrue(); + assertThat(FileUtil.count(dest)).isEqualTo(0); + final Path src = tempDir.createTempDir(); + try (Env env = create().setMaxReaders(1).open(src.toFile())) { + env.copy(dest.toFile(), MDB_CP_COMPACT); + assertThat(FileUtil.count(dest)).isEqualTo(1); + } } @Test void copyDirectoryRejectsFileDestination() { assertThatThrownBy( () -> { - FileUtil.useTempDir( - dest -> { - FileUtil.deleteDir(dest); - FileUtil.useTempDir( - src -> { - try (Env env = create().setMaxReaders(1).open(src.toFile())) { - env.copy(dest.toFile(), MDB_CP_COMPACT); - } - }); - }); + final Path dest = tempDir.createTempDir(); + FileUtil.deleteDir(dest); + final Path src = tempDir.createTempDir(); + try (Env env = create().setMaxReaders(1).open(src.toFile())) { + env.copy(dest.toFile(), MDB_CP_COMPACT); + } }) .isInstanceOf(InvalidCopyDestination.class); } @@ -218,21 +204,16 @@ void copyDirectoryRejectsFileDestination() { void copyDirectoryRejectsMissingDestination() { assertThatThrownBy( () -> { - FileUtil.useTempDir( - dest -> { - try { - Files.delete(dest); - FileUtil.useTempDir( - src -> { - try (Env env = - create().setMaxReaders(1).open(src.toFile())) { - env.copy(dest.toFile(), MDB_CP_COMPACT); - } - }); - } catch (final IOException e) { - throw new UncheckedIOException(e); - } - }); + final Path dest = tempDir.createTempDir(); + try { + Files.delete(dest); + final Path src = tempDir.createTempDir(); + try (Env env = create().setMaxReaders(1).open(src.toFile())) { + env.copy(dest.toFile(), MDB_CP_COMPACT); + } + } catch (final IOException e) { + throw new UncheckedIOException(e); + } }) .isInstanceOf(InvalidCopyDestination.class); } @@ -241,260 +222,227 @@ void copyDirectoryRejectsMissingDestination() { void copyDirectoryRejectsNonEmptyDestination() { assertThatThrownBy( () -> { - FileUtil.useTempDir( - dest -> { - try { - final Path subDir = dest.resolve("hello"); - Files.createDirectory(subDir); - assertThat(Files.isDirectory(subDir)).isTrue(); - FileUtil.useTempDir( - src -> { - try (Env env = - create().setMaxReaders(1).open(src.toFile())) { - env.copy(dest.toFile(), MDB_CP_COMPACT); - } - }); - } catch (final IOException e) { - throw new UncheckedIOException(e); - } - }); + final Path dest = tempDir.createTempDir(); + try { + final Path subDir = dest.resolve("hello"); + Files.createDirectory(subDir); + assertThat(Files.isDirectory(subDir)).isTrue(); + final Path src = tempDir.createTempDir(); + try (Env env = create().setMaxReaders(1).open(src.toFile())) { + env.copy(dest.toFile(), MDB_CP_COMPACT); + } + } catch (final IOException e) { + throw new UncheckedIOException(e); + } }) .isInstanceOf(InvalidCopyDestination.class); } @Test void copyFileBased() { - FileUtil.useTempFile( - dest -> { - FileUtil.deleteFile(dest); - assertThat(Files.exists(dest)).isFalse(); - FileUtil.useTempFile( - src -> { - try (Env env = - create().setMaxReaders(1).open(src.toFile(), MDB_NOSUBDIR)) { - env.copy(dest.toFile(), MDB_CP_COMPACT); - } - assertThat(FileUtil.size(dest)).isGreaterThan(0L); - }); - }); + final Path dest = tempDir.createTempFile(); + assertThat(Files.exists(dest)).isFalse(); + final Path src = tempDir.createTempFile(); + try (Env env = create().setMaxReaders(1).open(src.toFile(), MDB_NOSUBDIR)) { + env.copy(dest.toFile(), MDB_CP_COMPACT); + } + assertThat(FileUtil.size(dest)).isGreaterThan(0L); } @Test void copyFileRejectsExistingDestination() { assertThatThrownBy( () -> { - FileUtil.useTempFile( - dest -> { - assertThat(Files.exists(dest)).isTrue(); - FileUtil.useTempFile( - src -> { - try (Env env = - create().setMaxReaders(1).open(src.toFile(), MDB_NOSUBDIR)) { - env.copy(dest.toFile(), MDB_CP_COMPACT); - } - }); - }); + final Path dest = tempDir.createTempFile(); + Files.createFile(dest); + assertThat(Files.exists(dest)).isTrue(); + final Path src = tempDir.createTempFile(); + try (Env env = + create().setMaxReaders(1).open(src.toFile(), MDB_NOSUBDIR)) { + env.copy(dest.toFile(), MDB_CP_COMPACT); + } }) .isInstanceOf(InvalidCopyDestination.class); } @Test void createAsDirectory() { - FileUtil.useTempDir( - dest -> { - final Env env = create().setMaxReaders(1).open(dest.toFile()); - assertThat(Files.isDirectory(dest)).isTrue(); - env.sync(false); - env.close(); - assertThat(env.isClosed()).isTrue(); - env.close(); // safe to repeat - }); + final Path dest = tempDir.createTempDir(); + final Env env = create().setMaxReaders(1).open(dest.toFile()); + assertThat(Files.isDirectory(dest)).isTrue(); + env.sync(false); + env.close(); + assertThat(env.isClosed()).isTrue(); + env.close(); // safe to repeat } @Test void createAsFile() { - FileUtil.useTempFile( - file -> { - try (Env env = - create() - .setMapSize(MEBIBYTES.toBytes(1)) - .setMaxDbs(1) - .setMaxReaders(1) - .open(file.toFile(), MDB_NOSUBDIR)) { - env.sync(true); - assertThat(Files.isRegularFile(file)).isTrue(); - } - }); + final Path file = tempDir.createTempFile(); + try (Env env = + create() + .setMapSize(MEBIBYTES.toBytes(1)) + .setMaxDbs(1) + .setMaxReaders(1) + .open(file.toFile(), MDB_NOSUBDIR)) { + env.sync(true); + assertThat(Files.isRegularFile(file)).isTrue(); + } } @Test void detectTransactionThreadViolation() { assertThatThrownBy( () -> { - FileUtil.useTempFile( - file -> { - try (Env env = - create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR)) { - env.txnRead(); - env.txnRead(); - } - }); + final Path file = tempDir.createTempFile(); + try (Env env = + create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR)) { + env.txnRead(); + env.txnRead(); + } }) .isInstanceOf(BadReaderLockException.class); } @Test void info() { - FileUtil.useTempFile( - file -> { - try (Env env = - create().setMaxReaders(4).setMapSize(123_456).open(file.toFile(), MDB_NOSUBDIR)) { - final EnvInfo info = env.info(); - assertThat(info).isNotNull(); - assertThat(info.lastPageNumber).isEqualTo(1L); - assertThat(info.lastTransactionId).isEqualTo(0L); - assertThat(info.mapAddress).isEqualTo(0L); - assertThat(info.mapSize).isEqualTo(123_456L); - assertThat(info.maxReaders).isEqualTo(4); - assertThat(info.numReaders).isEqualTo(0); - assertThat(info.toString()).contains("maxReaders="); - assertThat(env.getMaxKeySize()).isEqualTo(511); - } - }); + final Path file = tempDir.createTempFile(); + try (Env env = + create().setMaxReaders(4).setMapSize(123_456).open(file.toFile(), MDB_NOSUBDIR)) { + final EnvInfo info = env.info(); + assertThat(info).isNotNull(); + assertThat(info.lastPageNumber).isEqualTo(1L); + assertThat(info.lastTransactionId).isEqualTo(0L); + assertThat(info.mapAddress).isEqualTo(0L); + assertThat(info.mapSize).isEqualTo(123_456L); + assertThat(info.maxReaders).isEqualTo(4); + assertThat(info.numReaders).isEqualTo(0); + assertThat(info.toString()).contains("maxReaders="); + assertThat(env.getMaxKeySize()).isEqualTo(511); + } } @Test void mapFull() { assertThatThrownBy( () -> { - FileUtil.useTempDir( - dir -> { - final byte[] k = new byte[500]; - final ByteBuffer key = allocateDirect(500); - final ByteBuffer val = allocateDirect(1_024); - final Random rnd = new Random(); - try (Env env = - create() - .setMaxReaders(1) - .setMapSize(MEBIBYTES.toBytes(8)) - .setMaxDbs(1) - .open(dir.toFile())) { - final Dbi db = env.openDbi(DB_1, MDB_CREATE); - for (; ; ) { - rnd.nextBytes(k); - key.clear(); - key.put(k).flip(); - val.clear(); - db.put(key, val); - } - } - }); + final Path dir = tempDir.createTempDir(); + final byte[] k = new byte[500]; + final ByteBuffer key = allocateDirect(500); + final ByteBuffer val = allocateDirect(1_024); + final Random rnd = new Random(); + try (Env env = + create() + .setMaxReaders(1) + .setMapSize(MEBIBYTES.toBytes(8)) + .setMaxDbs(1) + .open(dir.toFile())) { + final Dbi db = env.openDbi(DB_1, MDB_CREATE); + for (; ; ) { + rnd.nextBytes(k); + key.clear(); + key.put(k).flip(); + val.clear(); + db.put(key, val); + } + } }) .isInstanceOf(MapFullException.class); } @Test void readOnlySupported() { - FileUtil.useTempDir( - dir -> { - try (Env rwEnv = create().setMaxReaders(1).open(dir.toFile())) { - final Dbi rwDb = rwEnv.openDbi(DB_1, MDB_CREATE); - rwDb.put(bb(1), bb(42)); - } - try (Env roEnv = - create().setMaxReaders(1).open(dir.toFile(), MDB_RDONLY_ENV)) { - final Dbi roDb = roEnv.openDbi(DB_1); - try (Txn roTxn = roEnv.txnRead()) { - assertThat(roDb.get(roTxn, bb(1))).isNotNull(); - } - } - }); + final Path dir = tempDir.createTempDir(); + try (Env rwEnv = create().setMaxReaders(1).open(dir.toFile())) { + final Dbi rwDb = rwEnv.openDbi(DB_1, MDB_CREATE); + rwDb.put(bb(1), bb(42)); + } + try (Env roEnv = create().setMaxReaders(1).open(dir.toFile(), MDB_RDONLY_ENV)) { + final Dbi roDb = roEnv.openDbi(DB_1); + try (Txn roTxn = roEnv.txnRead()) { + assertThat(roDb.get(roTxn, bb(1))).isNotNull(); + } + } } @Test void setMapSize() { - FileUtil.useTempDir( - dir -> { - final byte[] k = new byte[500]; - final ByteBuffer key = allocateDirect(500); - final ByteBuffer val = allocateDirect(1_024); - final Random rnd = new Random(); - try (Env env = - create() - .setMaxReaders(1) - .setMapSize(KIBIBYTES.toBytes(256)) - .setMaxDbs(1) - .open(dir.toFile())) { - final Dbi db = env.openDbi(DB_1, MDB_CREATE); - - db.put(bb(1), bb(42)); - boolean mapFullExThrown = false; - try { - for (int i = 0; i < 70; i++) { - rnd.nextBytes(k); - key.clear(); - key.put(k).flip(); - val.clear(); - db.put(key, val); - } - } catch (final MapFullException mfE) { - mapFullExThrown = true; - } - assertThat(mapFullExThrown).isTrue(); - - env.setMapSize(KIBIBYTES.toBytes(1024)); - - try (Txn roTxn = env.txnRead()) { - final ByteBuffer byteBuffer = db.get(roTxn, bb(1)); - assertThat(byteBuffer).isNotNull(); - assertThat(byteBuffer.getInt()).isEqualTo(42); - } - - mapFullExThrown = false; - try { - for (int i = 0; i < 70; i++) { - rnd.nextBytes(k); - key.clear(); - key.put(k).flip(); - val.clear(); - db.put(key, val); - } - } catch (final MapFullException mfE) { - mapFullExThrown = true; - } - assertThat(mapFullExThrown).isFalse(); - } - }); + final Path dir = tempDir.createTempDir(); + final byte[] k = new byte[500]; + final ByteBuffer key = allocateDirect(500); + final ByteBuffer val = allocateDirect(1_024); + final Random rnd = new Random(); + try (Env env = + create() + .setMaxReaders(1) + .setMapSize(KIBIBYTES.toBytes(256)) + .setMaxDbs(1) + .open(dir.toFile())) { + final Dbi db = env.openDbi(DB_1, MDB_CREATE); + + db.put(bb(1), bb(42)); + boolean mapFullExThrown = false; + try { + for (int i = 0; i < 70; i++) { + rnd.nextBytes(k); + key.clear(); + key.put(k).flip(); + val.clear(); + db.put(key, val); + } + } catch (final MapFullException mfE) { + mapFullExThrown = true; + } + assertThat(mapFullExThrown).isTrue(); + + env.setMapSize(KIBIBYTES.toBytes(1024)); + + try (Txn roTxn = env.txnRead()) { + final ByteBuffer byteBuffer = db.get(roTxn, bb(1)); + assertThat(byteBuffer).isNotNull(); + assertThat(byteBuffer.getInt()).isEqualTo(42); + } + + mapFullExThrown = false; + try { + for (int i = 0; i < 70; i++) { + rnd.nextBytes(k); + key.clear(); + key.put(k).flip(); + val.clear(); + db.put(key, val); + } + } catch (final MapFullException mfE) { + mapFullExThrown = true; + } + assertThat(mapFullExThrown).isFalse(); + } } @Test void stats() { - FileUtil.useTempFile( - file -> { - try (Env env = create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR)) { - final Stat stat = env.stat(); - assertThat(stat).isNotNull(); - assertThat(stat.branchPages).isEqualTo(0L); - assertThat(stat.depth).isEqualTo(0); - assertThat(stat.entries).isEqualTo(0L); - assertThat(stat.leafPages).isEqualTo(0L); - assertThat(stat.overflowPages).isEqualTo(0L); - assertThat(stat.pageSize % 4_096).isEqualTo(0); - assertThat(stat.toString()).contains("pageSize="); - } - }); + final Path file = tempDir.createTempFile(); + try (Env env = create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR)) { + final Stat stat = env.stat(); + assertThat(stat).isNotNull(); + assertThat(stat.branchPages).isEqualTo(0L); + assertThat(stat.depth).isEqualTo(0); + assertThat(stat.entries).isEqualTo(0L); + assertThat(stat.leafPages).isEqualTo(0L); + assertThat(stat.overflowPages).isEqualTo(0L); + assertThat(stat.pageSize % 4_096).isEqualTo(0); + assertThat(stat.toString()).contains("pageSize="); + } } @Test void testDefaultOpen() { - FileUtil.useTempDir( - dir -> { - try (Env env = open(dir.toFile(), 10)) { - final EnvInfo info = env.info(); - assertThat(info.maxReaders).isEqualTo(MAX_READERS_DEFAULT); - final Dbi db = env.openDbi("test", MDB_CREATE); - db.put(allocateDirect(1), allocateDirect(1)); - } - }); + final Path dir = tempDir.createTempDir(); + try (Env env = open(dir.toFile(), 10)) { + final EnvInfo info = env.info(); + assertThat(info.maxReaders).isEqualTo(MAX_READERS_DEFAULT); + final Dbi db = env.openDbi("test", MDB_CREATE); + db.put(allocateDirect(1), allocateDirect(1)); + } } } diff --git a/src/test/java/org/lmdbjava/FileUtil.java b/src/test/java/org/lmdbjava/FileUtil.java index 2343c670..cc7ed635 100644 --- a/src/test/java/org/lmdbjava/FileUtil.java +++ b/src/test/java/org/lmdbjava/FileUtil.java @@ -25,53 +25,12 @@ import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.util.EnumSet; -import java.util.function.Consumer; import java.util.stream.Stream; final class FileUtil { private FileUtil() {} - static Path createTempDir() { - try { - return Files.createTempDirectory("lmdbjava"); - } catch (final IOException e) { - throw new UncheckedIOException(e); - } - } - - static Path createTempFile() { - try { - return Files.createTempFile("lmdbjava", "db"); - } catch (final IOException e) { - throw new UncheckedIOException(e); - } - } - - static void useTempDir(final Consumer consumer) { - Path path = null; - try { - path = createTempDir(); - consumer.accept(path); - } finally { - if (path != null) { - deleteDir(path); - } - } - } - - static void useTempFile(final Consumer consumer) { - Path path = null; - try { - path = createTempFile(); - consumer.accept(path); - } finally { - if (path != null) { - deleteIfExists(path); - } - } - } - public static long size(final Path path) { try { return Files.size(path); diff --git a/src/test/java/org/lmdbjava/GarbageCollectionTest.java b/src/test/java/org/lmdbjava/GarbageCollectionTest.java index f0aa64e4..36273559 100644 --- a/src/test/java/org/lmdbjava/GarbageCollectionTest.java +++ b/src/test/java/org/lmdbjava/GarbageCollectionTest.java @@ -23,6 +23,7 @@ import static org.lmdbjava.Env.create; import java.nio.ByteBuffer; +import java.nio.file.Path; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; @@ -35,61 +36,61 @@ public class GarbageCollectionTest { @Test void buffersNotGarbageCollectedTest() { - FileUtil.useTempDir( - dir -> { - try (Env env = - create().setMapSize(2_085_760_999).setMaxDbs(1).open(dir.toFile())) { - final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); + try (final TempDir tempDir = new TempDir()) { + final Path dir = tempDir.createTempDir(); + try (Env env = + create().setMapSize(2_085_760_999).setMaxDbs(1).open(dir.toFile())) { + final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); - try (Txn txn = env.txnWrite()) { - for (int i = 0; i < 5_000; i++) { - putBuffer(db, txn, i); - } - txn.commit(); - } + try (Txn txn = env.txnWrite()) { + for (int i = 0; i < 5_000; i++) { + putBuffer(db, txn, i); + } + txn.commit(); + } - // Call GC before writing to LMDB and after last reference to buffer by - // changing the behavior of mask - try (MockedStatic mockedStatic = Mockito.mockStatic(MaskedFlag.class)) { - mockedStatic - .when(MaskedFlag::mask) - .thenAnswer( - invocationOnMock -> { - System.gc(); - return 0; - }); - final int gcRecordWrites = Integer.getInteger("gcRecordWrites", 50); - try (Txn txn = env.txnWrite()) { - for (int i = 0; i < gcRecordWrites; i++) { - putBuffer(db, txn, i); - } - txn.commit(); - } + // Call GC before writing to LMDB and after last reference to buffer by + // changing the behavior of mask + try (MockedStatic mockedStatic = Mockito.mockStatic(MaskedFlag.class)) { + mockedStatic + .when(MaskedFlag::mask) + .thenAnswer( + invocationOnMock -> { + System.gc(); + return 0; + }); + final int gcRecordWrites = Integer.getInteger("gcRecordWrites", 50); + try (Txn txn = env.txnWrite()) { + for (int i = 0; i < gcRecordWrites; i++) { + putBuffer(db, txn, i); } + txn.commit(); + } + } - // Find corrupt keys - try (Txn txn = env.txnRead()) { - try (Cursor c = db.openCursor(txn)) { - if (c.first()) { - do { - final byte[] rkey = new byte[c.key().remaining()]; - c.key().get(rkey); - final byte[] rval = new byte[c.val().remaining()]; - c.val().get(rval); - final String skey = new String(rkey, UTF_8); - final String sval = new String(rval, UTF_8); - if (!skey.startsWith("Uncorruptedkey")) { - fail("Found corrupt key " + skey); - } - if (!sval.startsWith("Uncorruptedval")) { - fail("Found corrupt val " + sval); - } - } while (c.next()); + // Find corrupt keys + try (Txn txn = env.txnRead()) { + try (Cursor c = db.openCursor(txn)) { + if (c.first()) { + do { + final byte[] rkey = new byte[c.key().remaining()]; + c.key().get(rkey); + final byte[] rval = new byte[c.val().remaining()]; + c.val().get(rval); + final String skey = new String(rkey, UTF_8); + final String sval = new String(rval, UTF_8); + if (!skey.startsWith("Uncorruptedkey")) { + fail("Found corrupt key " + skey); + } + if (!sval.startsWith("Uncorruptedval")) { + fail("Found corrupt val " + sval); } - } + } while (c.next()); } } - }); + } + } + } } private void putBuffer(final Dbi db, final Txn txn, final int i) { diff --git a/src/test/java/org/lmdbjava/TempDir.java b/src/test/java/org/lmdbjava/TempDir.java new file mode 100644 index 00000000..7d2977b9 --- /dev/null +++ b/src/test/java/org/lmdbjava/TempDir.java @@ -0,0 +1,57 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.UUID; + +public class TempDir implements AutoCloseable { + private final Path root; + + public TempDir() { + try { + root = Files.createTempDirectory("lmdb"); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } + + public Path createTempFile() { + return root.resolve(UUID.randomUUID().toString()); + } + + public Path createTempDir() { + try { + final Path dir = root.resolve(UUID.randomUUID().toString()); + Files.createDirectory(dir); + return dir; + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } + + public void cleanup() { + FileUtil.deleteDir(root); + } + + @Override + public void close() { + cleanup(); + } +} diff --git a/src/test/java/org/lmdbjava/TestUtils.java b/src/test/java/org/lmdbjava/TestUtils.java index 4c5e46ee..23606862 100644 --- a/src/test/java/org/lmdbjava/TestUtils.java +++ b/src/test/java/org/lmdbjava/TestUtils.java @@ -56,6 +56,15 @@ static ByteBuffer bb(final long value) { return bb; } + static byte[] getBytes(final ByteBuffer byteBuffer) { + if (byteBuffer == null) { + return null; + } + final byte[] bytes = new byte[byteBuffer.remaining()]; + byteBuffer.duplicate().get(bytes); + return bytes; + } + static void invokePrivateConstructor(final Class clazz) { try { final Constructor c = clazz.getDeclaredConstructor(); diff --git a/src/test/java/org/lmdbjava/TutorialTest.java b/src/test/java/org/lmdbjava/TutorialTest.java index 1b42b327..8376fbe7 100644 --- a/src/test/java/org/lmdbjava/TutorialTest.java +++ b/src/test/java/org/lmdbjava/TutorialTest.java @@ -27,9 +27,7 @@ import static org.lmdbjava.DirectBufferProxy.PROXY_DB; import static org.lmdbjava.Env.create; import static org.lmdbjava.GetOp.MDB_SET; -import static org.lmdbjava.SeekOp.MDB_FIRST; -import static org.lmdbjava.SeekOp.MDB_LAST; -import static org.lmdbjava.SeekOp.MDB_PREV; +import static org.lmdbjava.SeekOp.*; import java.nio.ByteBuffer; import java.nio.file.Path; @@ -37,6 +35,8 @@ import org.agrona.DirectBuffer; import org.agrona.MutableDirectBuffer; import org.agrona.concurrent.UnsafeBuffer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.lmdbjava.CursorIterable.KeyVal; @@ -57,147 +57,155 @@ public final class TutorialTest { private static final String DB_NAME = "my DB"; + private TempDir tempDir; + + @BeforeEach + void beforeEach() { + tempDir = new TempDir(); + } + + @AfterEach + void afterEach() { + tempDir.cleanup(); + } + /** In this first tutorial we will use LmdbJava with some basic defaults. */ @Test void tutorial1() { // We need a storage directory first. // The path cannot be on a remote file system. - FileUtil.useTempDir( - dir -> { - - // We always need an Env. An Env owns a physical on-disk storage file. One - // Env can store many different databases (ie sorted maps). - final Env env = - create() - // LMDB also needs to know how large our DB might be. Over-estimating is OK. - .setMapSize(10_485_760) - // LMDB also needs to know how many DBs (Dbi) we want to store in this Env. - .setMaxDbs(1) - // Now let's open the Env. The same path can be concurrently opened and - // used in different processes, but do not open the same path twice in - // the same process at the same time. - .open(dir.toFile()); - - // We need a Dbi for each DB. A Dbi roughly equates to a sorted map. The - // MDB_CREATE flag causes the DB to be created if it doesn't already exist. - final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); - - // We want to store some data, so we will need a direct ByteBuffer. - // Note that LMDB keys cannot exceed maxKeySize bytes (511 bytes by default). - // Values can be larger. - final ByteBuffer key = allocateDirect(env.getMaxKeySize()); - final ByteBuffer val = allocateDirect(700); - key.put("greeting".getBytes(UTF_8)).flip(); - val.put("Hello world".getBytes(UTF_8)).flip(); - final int valSize = val.remaining(); - - // Now store it. Dbi.put() internally begins and commits a transaction (Txn). - db.put(key, val); - - // To fetch any data from LMDB we need a Txn. A Txn is very important in - // LmdbJava because it offers ACID characteristics and internally holds a - // read-only key buffer and read-only value buffer. These read-only buffers - // are always the same two Java objects, but point to different LMDB-managed - // memory as we use Dbi (and Cursor) methods. These read-only buffers remain - // valid only until the Txn is released or the next Dbi or Cursor call. If - // you need data afterwards, you should copy the bytes to your own buffer. - try (Txn txn = env.txnRead()) { - final ByteBuffer found = db.get(txn, key); - assertThat(found).isNotNull(); - - // The fetchedVal is read-only and points to LMDB memory - final ByteBuffer fetchedVal = txn.val(); - assertThat(fetchedVal.remaining()).isEqualTo(valSize); - - // Let's double-check the fetched value is correct - assertThat(UTF_8.decode(fetchedVal).toString()).isEqualTo("Hello world"); - } - - // We can also delete. The simplest way is to let Dbi allocate a new Txn... - db.delete(key); - - // Now if we try to fetch the deleted row, it won't be present - try (Txn txn = env.txnRead()) { - assertThat(db.get(txn, key)).isNull(); - } - - env.close(); - }); + final Path dir = tempDir.createTempDir(); + + // We always need an Env. An Env owns a physical on-disk storage file. One + // Env can store many different databases (ie sorted maps). + final Env env = + create() + // LMDB also needs to know how large our DB might be. Over-estimating is OK. + .setMapSize(10_485_760) + // LMDB also needs to know how many DBs (Dbi) we want to store in this Env. + .setMaxDbs(1) + // Now let's open the Env. The same path can be concurrently opened and + // used in different processes, but do not open the same path twice in + // the same process at the same time. + .open(dir.toFile()); + + // We need a Dbi for each DB. A Dbi roughly equates to a sorted map. The + // MDB_CREATE flag causes the DB to be created if it doesn't already exist. + final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); + + // We want to store some data, so we will need a direct ByteBuffer. + // Note that LMDB keys cannot exceed maxKeySize bytes (511 bytes by default). + // Values can be larger. + final ByteBuffer key = allocateDirect(env.getMaxKeySize()); + final ByteBuffer val = allocateDirect(700); + key.put("greeting".getBytes(UTF_8)).flip(); + val.put("Hello world".getBytes(UTF_8)).flip(); + final int valSize = val.remaining(); + + // Now store it. Dbi.put() internally begins and commits a transaction (Txn). + db.put(key, val); + + // To fetch any data from LMDB we need a Txn. A Txn is very important in + // LmdbJava because it offers ACID characteristics and internally holds a + // read-only key buffer and read-only value buffer. These read-only buffers + // are always the same two Java objects, but point to different LMDB-managed + // memory as we use Dbi (and Cursor) methods. These read-only buffers remain + // valid only until the Txn is released or the next Dbi or Cursor call. If + // you need data afterwards, you should copy the bytes to your own buffer. + try (Txn txn = env.txnRead()) { + final ByteBuffer found = db.get(txn, key); + assertThat(found).isNotNull(); + + // The fetchedVal is read-only and points to LMDB memory + final ByteBuffer fetchedVal = txn.val(); + assertThat(fetchedVal.remaining()).isEqualTo(valSize); + + // Let's double-check the fetched value is correct + assertThat(UTF_8.decode(fetchedVal).toString()).isEqualTo("Hello world"); + } + + // We can also delete. The simplest way is to let Dbi allocate a new Txn... + db.delete(key); + + // Now if we try to fetch the deleted row, it won't be present + try (Txn txn = env.txnRead()) { + assertThat(db.get(txn, key)).isNull(); + } + + env.close(); } /** In this second tutorial we'll learn more about LMDB's ACID Txns. */ @Test void tutorial2() { - FileUtil.useTempDir( - dir -> { - try { - final Env env = createSimpleEnv(dir); - final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); - final ByteBuffer key = allocateDirect(env.getMaxKeySize()); - final ByteBuffer val = allocateDirect(700); - - // Let's write and commit "key1" via a Txn. A Txn can include multiple Dbis. - // Note write Txns block other write Txns, due to writes being serialized. - // It's therefore important to avoid unnecessarily long-lived write Txns. + final Path dir = tempDir.createTempDir(); + try { + final Env env = createSimpleEnv(dir); + final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); + final ByteBuffer key = allocateDirect(env.getMaxKeySize()); + final ByteBuffer val = allocateDirect(700); + + // Let's write and commit "key1" via a Txn. A Txn can include multiple Dbis. + // Note write Txns block other write Txns, due to writes being serialized. + // It's therefore important to avoid unnecessarily long-lived write Txns. + try (Txn txn = env.txnWrite()) { + key.put("key1".getBytes(UTF_8)).flip(); + val.put("lmdb".getBytes(UTF_8)).flip(); + db.put(txn, key, val); + + // We can read data too, even though this is a write Txn. + final ByteBuffer found = db.get(txn, key); + assertThat(found).isNotNull(); + + // An explicit commit is required, otherwise Txn.close() rolls it back. + txn.commit(); + } + + // Open a read-only Txn. It only sees data that existed at Txn creation time. + final Txn rtx = env.txnRead(); + + // Our read Txn can fetch key1 without problem, as it existed at Txn creation. + ByteBuffer found = db.get(rtx, key); + assertThat(found).isNotNull(); + + // Note that our main test thread holds the Txn. Only one Txn per thread is + // typically permitted (the exception is a read-only Env with MDB_NOTLS). + // + // Let's write out a "key2" via a new write Txn in a different thread. + final ExecutorService es = newCachedThreadPool(); + es.execute( + () -> { try (Txn txn = env.txnWrite()) { - key.put("key1".getBytes(UTF_8)).flip(); - val.put("lmdb".getBytes(UTF_8)).flip(); + key.clear(); + key.put("key2".getBytes(UTF_8)).flip(); db.put(txn, key, val); - - // We can read data too, even though this is a write Txn. - final ByteBuffer found = db.get(txn, key); - assertThat(found).isNotNull(); - - // An explicit commit is required, otherwise Txn.close() rolls it back. txn.commit(); } - - // Open a read-only Txn. It only sees data that existed at Txn creation time. - final Txn rtx = env.txnRead(); - - // Our read Txn can fetch key1 without problem, as it existed at Txn creation. - ByteBuffer found = db.get(rtx, key); - assertThat(found).isNotNull(); - - // Note that our main test thread holds the Txn. Only one Txn per thread is - // typically permitted (the exception is a read-only Env with MDB_NOTLS). - // - // Let's write out a "key2" via a new write Txn in a different thread. - final ExecutorService es = newCachedThreadPool(); - es.execute( - () -> { - try (Txn txn = env.txnWrite()) { - key.clear(); - key.put("key2".getBytes(UTF_8)).flip(); - db.put(txn, key, val); - txn.commit(); - } - }); - es.shutdown(); - es.awaitTermination(10, SECONDS); - - // Even though key2 has been committed, our read Txn still can't see it. - found = db.get(rtx, key); - assertThat(found).isNull(); - - // To see key2, we could create a new Txn. But a reset/renew is much faster. - // Reset/renew is also important to avoid long-lived read Txns, as these - // prevent the re-use of free pages by write Txns (ie the DB will grow). - rtx.reset(); - // ... potentially long operation here ... - rtx.renew(); - found = db.get(rtx, key); - assertThat(found).isNotNull(); - - // Don't forget to close the read Txn now we're completely finished. We could - // have avoided this if we used a try-with-resources block, but we wanted to - // play around with multiple concurrent Txns to demonstrate the "I" in ACID. - rtx.close(); - env.close(); - } catch (final InterruptedException e) { - throw new RuntimeException(e); - } - }); + }); + es.shutdown(); + es.awaitTermination(10, SECONDS); + + // Even though key2 has been committed, our read Txn still can't see it. + found = db.get(rtx, key); + assertThat(found).isNull(); + + // To see key2, we could create a new Txn. But a reset/renew is much faster. + // Reset/renew is also important to avoid long-lived read Txns, as these + // prevent the re-use of free pages by write Txns (ie the DB will grow). + rtx.reset(); + // ... potentially long operation here ... + rtx.renew(); + found = db.get(rtx, key); + assertThat(found).isNotNull(); + + // Don't forget to close the read Txn now we're completely finished. We could + // have avoided this if we used a try-with-resources block, but we wanted to + // play around with multiple concurrent Txns to demonstrate the "I" in ACID. + rtx.close(); + env.close(); + } catch (final InterruptedException e) { + throw new RuntimeException(e); + } } /** @@ -207,73 +215,71 @@ void tutorial2() { */ @Test void tutorial3() { - FileUtil.useTempDir( - dir -> { - final Env env = createSimpleEnv(dir); - final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); - final ByteBuffer key = allocateDirect(env.getMaxKeySize()); - final ByteBuffer val = allocateDirect(700); - - try (Txn txn = env.txnWrite()) { - // A cursor always belongs to a particular Dbi. - final Cursor c = db.openCursor(txn); - - // We can put via a Cursor. Note we're adding keys in a strange order, - // as we want to show you that LMDB returns them in sorted order. - key.put("zzz".getBytes(UTF_8)).flip(); - val.put("lmdb".getBytes(UTF_8)).flip(); - c.put(key, val); - key.clear(); - key.put("aaa".getBytes(UTF_8)).flip(); - c.put(key, val); - key.clear(); - key.put("ccc".getBytes(UTF_8)).flip(); - c.put(key, val); - - // We can read from the Cursor by key. - c.get(key, MDB_SET); - assertThat(UTF_8.decode(c.key()).toString()).isEqualTo("ccc"); - - // Let's see that LMDB provides the keys in appropriate order.... - c.seek(MDB_FIRST); - assertThat(UTF_8.decode(c.key()).toString()).isEqualTo("aaa"); - - c.seek(MDB_LAST); - assertThat(UTF_8.decode(c.key()).toString()).isEqualTo("zzz"); - - c.seek(MDB_PREV); - assertThat(UTF_8.decode(c.key()).toString()).isEqualTo("ccc"); - - // Cursors can also delete the current key. - c.delete(); - - c.close(); - txn.commit(); - } - - // A read-only Cursor can survive its original Txn being closed. This is - // useful if you want to close the original Txn (eg maybe you created the - // Cursor during the constructor of a singleton with a throw-away Txn). Of - // course, you cannot use the Cursor if its Txn is closed or currently reset. - final Txn tx1 = env.txnRead(); - final Cursor c = db.openCursor(tx1); - tx1.close(); - - // The Cursor becomes usable again by "renewing" it with an active read Txn. - final Txn tx2 = env.txnRead(); - c.renew(tx2); - c.seek(MDB_FIRST); - - // As usual with read Txns, we can reset and renew them. The Cursor does - // not need any special handling if we do this. - tx2.reset(); - // ... potentially long operation here ... - tx2.renew(); - c.seek(MDB_LAST); - - tx2.close(); - env.close(); - }); + final Path dir = tempDir.createTempDir(); + final Env env = createSimpleEnv(dir); + final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); + final ByteBuffer key = allocateDirect(env.getMaxKeySize()); + final ByteBuffer val = allocateDirect(700); + + try (Txn txn = env.txnWrite()) { + // A cursor always belongs to a particular Dbi. + final Cursor c = db.openCursor(txn); + + // We can put via a Cursor. Note we're adding keys in a strange order, + // as we want to show you that LMDB returns them in sorted order. + key.put("zzz".getBytes(UTF_8)).flip(); + val.put("lmdb".getBytes(UTF_8)).flip(); + c.put(key, val); + key.clear(); + key.put("aaa".getBytes(UTF_8)).flip(); + c.put(key, val); + key.clear(); + key.put("ccc".getBytes(UTF_8)).flip(); + c.put(key, val); + + // We can read from the Cursor by key. + c.get(key, MDB_SET); + assertThat(UTF_8.decode(c.key()).toString()).isEqualTo("ccc"); + + // Let's see that LMDB provides the keys in appropriate order.... + c.seek(MDB_FIRST); + assertThat(UTF_8.decode(c.key()).toString()).isEqualTo("aaa"); + + c.seek(MDB_LAST); + assertThat(UTF_8.decode(c.key()).toString()).isEqualTo("zzz"); + + c.seek(MDB_PREV); + assertThat(UTF_8.decode(c.key()).toString()).isEqualTo("ccc"); + + // Cursors can also delete the current key. + c.delete(); + + c.close(); + txn.commit(); + } + + // A read-only Cursor can survive its original Txn being closed. This is + // useful if you want to close the original Txn (eg maybe you created the + // Cursor during the constructor of a singleton with a throw-away Txn). Of + // course, you cannot use the Cursor if its Txn is closed or currently reset. + final Txn tx1 = env.txnRead(); + final Cursor c = db.openCursor(tx1); + tx1.close(); + + // The Cursor becomes usable again by "renewing" it with an active read Txn. + final Txn tx2 = env.txnRead(); + c.renew(tx2); + c.seek(MDB_FIRST); + + // As usual with read Txns, we can reset and renew them. The Cursor does + // not need any special handling if we do this. + tx2.reset(); + // ... potentially long operation here ... + tx2.renew(); + c.seek(MDB_LAST); + + tx2.close(); + env.close(); } /** @@ -282,109 +288,105 @@ void tutorial3() { */ @Test void tutorial4() { - FileUtil.useTempDir( - dir -> { - final Env env = createSimpleEnv(dir); - final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); - - try (Txn txn = env.txnWrite()) { - final ByteBuffer key = allocateDirect(env.getMaxKeySize()); - final ByteBuffer val = allocateDirect(700); - - // Insert some data. Note that ByteBuffer order defaults to Big Endian. - // LMDB does not persist the byte order, but it's critical to sort keys. - // If your numeric keys don't sort as expected, review buffer byte order. - val.putInt(100); - key.putInt(1); - db.put(txn, key, val); - key.clear(); - key.putInt(2); - db.put(txn, key, val); - key.clear(); - - // Each iterable uses a cursor and must be closed when finished. Iterate - // forward in terms of key ordering starting with the first key. - try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { - for (final KeyVal kv : ci) { - assertThat(kv.key()).isNotNull(); - assertThat(kv.val()).isNotNull(); - } - } - - // Iterate backward in terms of key ordering starting with the last key. - try (CursorIterable ci = db.iterate(txn, KeyRange.allBackward())) { - for (final KeyVal kv : ci) { - assertThat(kv.key()).isNotNull(); - assertThat(kv.val()).isNotNull(); - } - } - - // There are many ways to control the desired key range via KeyRange, such - // as arbitrary start and stop values, direction etc. We've adopted Guava's - // terminology for our range classes (see KeyRangeType for further details). - key.putInt(1); - final KeyRange range = KeyRange.atLeastBackward(key); - try (CursorIterable ci = db.iterate(txn, range)) { - for (final KeyVal kv : ci) { - assertThat(kv.key()).isNotNull(); - assertThat(kv.val()).isNotNull(); - } - } - } - - env.close(); - }); + final Path dir = tempDir.createTempDir(); + final Env env = createSimpleEnv(dir); + final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); + + try (Txn txn = env.txnWrite()) { + final ByteBuffer key = allocateDirect(env.getMaxKeySize()); + final ByteBuffer val = allocateDirect(700); + + // Insert some data. Note that ByteBuffer order defaults to Big Endian. + // LMDB does not persist the byte order, but it's critical to sort keys. + // If your numeric keys don't sort as expected, review buffer byte order. + val.putInt(100); + key.putInt(1); + db.put(txn, key, val); + key.clear(); + key.putInt(2); + db.put(txn, key, val); + key.clear(); + + // Each iterable uses a cursor and must be closed when finished. Iterate + // forward in terms of key ordering starting with the first key. + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + for (final KeyVal kv : ci) { + assertThat(kv.key()).isNotNull(); + assertThat(kv.val()).isNotNull(); + } + } + + // Iterate backward in terms of key ordering starting with the last key. + try (CursorIterable ci = db.iterate(txn, KeyRange.allBackward())) { + for (final KeyVal kv : ci) { + assertThat(kv.key()).isNotNull(); + assertThat(kv.val()).isNotNull(); + } + } + + // There are many ways to control the desired key range via KeyRange, such + // as arbitrary start and stop values, direction etc. We've adopted Guava's + // terminology for our range classes (see KeyRangeType for further details). + key.putInt(1); + final KeyRange range = KeyRange.atLeastBackward(key); + try (CursorIterable ci = db.iterate(txn, range)) { + for (final KeyVal kv : ci) { + assertThat(kv.key()).isNotNull(); + assertThat(kv.val()).isNotNull(); + } + } + } + + env.close(); } /** In this fifth tutorial we'll explore multiple values sharing a single key. */ @Test void tutorial5() { - FileUtil.useTempDir( - dir -> { - final Env env = createSimpleEnv(dir); - - // This time we're going to tell the Dbi it can store > 1 value per key. - // There are other flags available if we're storing integers etc. - final Dbi db = env.openDbi(DB_NAME, MDB_CREATE, MDB_DUPSORT); - - // Duplicate support requires both keys and values to be <= max key size. - final ByteBuffer key = allocateDirect(env.getMaxKeySize()); - final ByteBuffer val = allocateDirect(env.getMaxKeySize()); - - try (Txn txn = env.txnWrite()) { - final Cursor c = db.openCursor(txn); - - // Store one key, but many values, and in non-natural order. - key.put("key".getBytes(UTF_8)).flip(); - val.put("xxx".getBytes(UTF_8)).flip(); - c.put(key, val); - val.clear(); - val.put("kkk".getBytes(UTF_8)).flip(); - c.put(key, val); - val.clear(); - val.put("lll".getBytes(UTF_8)).flip(); - c.put(key, val); - - // Cursor can tell us how many values the current key has. - final long count = c.count(); - assertThat(count).isEqualTo(3L); - - // Let's position the Cursor. Note sorting still works. - c.seek(MDB_FIRST); - assertThat(UTF_8.decode(c.val()).toString()).isEqualTo("kkk"); - - c.seek(MDB_LAST); - assertThat(UTF_8.decode(c.val()).toString()).isEqualTo("xxx"); - - c.seek(MDB_PREV); - assertThat(UTF_8.decode(c.val()).toString()).isEqualTo("lll"); - - c.close(); - txn.commit(); - } - - env.close(); - }); + final Path dir = tempDir.createTempDir(); + final Env env = createSimpleEnv(dir); + + // This time we're going to tell the Dbi it can store > 1 value per key. + // There are other flags available if we're storing integers etc. + final Dbi db = env.openDbi(DB_NAME, MDB_CREATE, MDB_DUPSORT); + + // Duplicate support requires both keys and values to be <= max key size. + final ByteBuffer key = allocateDirect(env.getMaxKeySize()); + final ByteBuffer val = allocateDirect(env.getMaxKeySize()); + + try (Txn txn = env.txnWrite()) { + final Cursor c = db.openCursor(txn); + + // Store one key, but many values, and in non-natural order. + key.put("key".getBytes(UTF_8)).flip(); + val.put("xxx".getBytes(UTF_8)).flip(); + c.put(key, val); + val.clear(); + val.put("kkk".getBytes(UTF_8)).flip(); + c.put(key, val); + val.clear(); + val.put("lll".getBytes(UTF_8)).flip(); + c.put(key, val); + + // Cursor can tell us how many values the current key has. + final long count = c.count(); + assertThat(count).isEqualTo(3L); + + // Let's position the Cursor. Note sorting still works. + c.seek(MDB_FIRST); + assertThat(UTF_8.decode(c.val()).toString()).isEqualTo("kkk"); + + c.seek(MDB_LAST); + assertThat(UTF_8.decode(c.val()).toString()).isEqualTo("xxx"); + + c.seek(MDB_PREV); + assertThat(UTF_8.decode(c.val()).toString()).isEqualTo("lll"); + + c.close(); + txn.commit(); + } + + env.close(); } /** @@ -393,85 +395,79 @@ void tutorial5() { */ @Test void tutorial6() { - FileUtil.useTempDir( - dir -> { - // Note we need to specify the Verifier's DBI_COUNT for the Env. - final Env env = - create(PROXY_OPTIMAL) - .setMapSize(10_485_760) - .setMaxDbs(Verifier.DBI_COUNT) - .open(dir.toFile()); - - // Create a Verifier (it's a Callable for those needing full control). - final Verifier v = new Verifier(env); - - // We now run the verifier for 3 seconds; it raises an exception on failure. - // The method returns the number of entries it successfully verified. - v.runFor(3, SECONDS); - - env.close(); - }); + final Path dir = tempDir.createTempDir(); + // Note we need to specify the Verifier's DBI_COUNT for the Env. + final Env env = + create(PROXY_OPTIMAL) + .setMapSize(10_485_760) + .setMaxDbs(Verifier.DBI_COUNT) + .open(dir.toFile()); + + // Create a Verifier (it's a Callable for those needing full control). + final Verifier v = new Verifier(env); + + // We now run the verifier for 3 seconds; it raises an exception on failure. + // The method returns the number of entries it successfully verified. + v.runFor(3, SECONDS); + + env.close(); } /** In this final tutorial we'll look at using Agrona's DirectBuffer. */ @Test void tutorial7() { - FileUtil.useTempDir( - dir -> { - // The critical difference is we pass the PROXY_DB field to Env.create(). - // There's also a PROXY_SAFE if you want to stop ByteBuffer's Unsafe use. - // Aside from that and a different type argument, it's the same as usual... - final Env env = - create(PROXY_DB).setMapSize(10_485_760).setMaxDbs(1).open(dir.toFile()); - - final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); - - final ByteBuffer keyBb = allocateDirect(env.getMaxKeySize()); - final MutableDirectBuffer key = new UnsafeBuffer(keyBb); - final MutableDirectBuffer val = new UnsafeBuffer(allocateDirect(700)); - - try (Txn txn = env.txnWrite()) { - try (Cursor c = db.openCursor(txn)) { - // Agrona is faster than ByteBuffer and its methods are nicer... - val.putStringWithoutLengthUtf8(0, "The Value"); - key.putStringWithoutLengthUtf8(0, "yyy"); - c.put(key, val); - - key.putStringWithoutLengthUtf8(0, "ggg"); - c.put(key, val); - - c.seek(MDB_FIRST); - assertThat(c.key().getStringWithoutLengthUtf8(0, env.getMaxKeySize())) - .startsWith("ggg"); - - c.seek(MDB_LAST); - assertThat(c.key().getStringWithoutLengthUtf8(0, env.getMaxKeySize())) - .startsWith("yyy"); - - // DirectBuffer has no position concept. Often you don't want to store - // the unnecessary bytes of a varying-size buffer. Let's have a look... - final int keyLen = key.putStringWithoutLengthUtf8(0, "12characters"); - assertThat(keyLen).isEqualTo(12); - assertThat(key.capacity()).isEqualTo(env.getMaxKeySize()); - - // To only store the 12 characters, we simply call wrap: - key.wrap(key, 0, keyLen); - assertThat(key.capacity()).isEqualTo(keyLen); - c.put(key, val); - c.seek(MDB_FIRST); - assertThat(c.key().capacity()).isEqualTo(keyLen); - assertThat(c.key().getStringWithoutLengthUtf8(0, c.key().capacity())) - .isEqualTo("12characters"); - - // To store bigger values again, just wrap the original buffer. - key.wrap(keyBb); - assertThat(key.capacity()).isEqualTo(env.getMaxKeySize()); - } - txn.commit(); - } - - env.close(); - }); + final Path dir = tempDir.createTempDir(); + // The critical difference is we pass the PROXY_DB field to Env.create(). + // There's also a PROXY_SAFE if you want to stop ByteBuffer's Unsafe use. + // Aside from that and a different type argument, it's the same as usual... + final Env env = + create(PROXY_DB).setMapSize(10_485_760).setMaxDbs(1).open(dir.toFile()); + + final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); + + final ByteBuffer keyBb = allocateDirect(env.getMaxKeySize()); + final MutableDirectBuffer key = new UnsafeBuffer(keyBb); + final MutableDirectBuffer val = new UnsafeBuffer(allocateDirect(700)); + + try (Txn txn = env.txnWrite()) { + try (Cursor c = db.openCursor(txn)) { + // Agrona is faster than ByteBuffer and its methods are nicer... + val.putStringWithoutLengthUtf8(0, "The Value"); + key.putStringWithoutLengthUtf8(0, "yyy"); + c.put(key, val); + + key.putStringWithoutLengthUtf8(0, "ggg"); + c.put(key, val); + + c.seek(MDB_FIRST); + assertThat(c.key().getStringWithoutLengthUtf8(0, env.getMaxKeySize())).startsWith("ggg"); + + c.seek(MDB_LAST); + assertThat(c.key().getStringWithoutLengthUtf8(0, env.getMaxKeySize())).startsWith("yyy"); + + // DirectBuffer has no position concept. Often you don't want to store + // the unnecessary bytes of a varying-size buffer. Let's have a look... + final int keyLen = key.putStringWithoutLengthUtf8(0, "12characters"); + assertThat(keyLen).isEqualTo(12); + assertThat(key.capacity()).isEqualTo(env.getMaxKeySize()); + + // To only store the 12 characters, we simply call wrap: + key.wrap(key, 0, keyLen); + assertThat(key.capacity()).isEqualTo(keyLen); + c.put(key, val); + c.seek(MDB_FIRST); + assertThat(c.key().capacity()).isEqualTo(keyLen); + assertThat(c.key().getStringWithoutLengthUtf8(0, c.key().capacity())) + .isEqualTo("12characters"); + + // To store bigger values again, just wrap the original buffer. + key.wrap(keyBb); + assertThat(key.capacity()).isEqualTo(env.getMaxKeySize()); + } + txn.commit(); + } + + env.close(); } // You've finished! There are lots of other neat things we could show you (eg diff --git a/src/test/java/org/lmdbjava/TxnTest.java b/src/test/java/org/lmdbjava/TxnTest.java index b2f04ff6..ebb06137 100644 --- a/src/test/java/org/lmdbjava/TxnTest.java +++ b/src/test/java/org/lmdbjava/TxnTest.java @@ -35,7 +35,6 @@ import static org.lmdbjava.Txn.State.RESET; import static org.lmdbjava.TxnFlags.MDB_RDONLY_TXN; -import java.io.IOException; import java.nio.ByteBuffer; import java.nio.file.Path; import java.util.ArrayList; @@ -60,9 +59,12 @@ public final class TxnTest { private Path file; private Env env; + private TempDir tempDir; + @BeforeEach void beforeEach() { - file = FileUtil.createTempFile(); + tempDir = new TempDir(); + file = tempDir.createTempFile(); env = create() .setMapSize(KIBIBYTES.toBytes(256)) @@ -74,11 +76,11 @@ void beforeEach() { @AfterEach void afterEach() { env.close(); - FileUtil.deleteFile(file); + tempDir.cleanup(); } @Test - void largeKeysRejected() throws IOException { + void largeKeysRejected() { assertThatThrownBy( () -> { final Dbi dbi = env.openDbi(DB_1, MDB_CREATE); diff --git a/src/test/java/org/lmdbjava/VerifierTest.java b/src/test/java/org/lmdbjava/VerifierTest.java index 64214568..9d127cf8 100644 --- a/src/test/java/org/lmdbjava/VerifierTest.java +++ b/src/test/java/org/lmdbjava/VerifierTest.java @@ -22,6 +22,7 @@ import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; import java.nio.ByteBuffer; +import java.nio.file.Path; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; @@ -30,18 +31,18 @@ public final class VerifierTest { @Test void verification() { - FileUtil.useTempFile( - file -> { - try (Env env = - create() - .setMaxReaders(1) - .setMaxDbs(Verifier.DBI_COUNT) - .setMapSize(MEBIBYTES.toBytes(10)) - .open(file.toFile(), MDB_NOSUBDIR)) { - final Verifier v = new Verifier(env); - final int seconds = Integer.getInteger("verificationSeconds", 2); - assertThat(v.runFor(seconds, TimeUnit.SECONDS)).isGreaterThan(1L); - } - }); + try (final TempDir tempDir = new TempDir()) { + final Path file = tempDir.createTempFile(); + try (Env env = + create() + .setMaxReaders(1) + .setMaxDbs(Verifier.DBI_COUNT) + .setMapSize(MEBIBYTES.toBytes(10)) + .open(file.toFile(), MDB_NOSUBDIR)) { + final Verifier v = new Verifier(env); + final int seconds = Integer.getInteger("verificationSeconds", 2); + assertThat(v.runFor(seconds, TimeUnit.SECONDS)).isGreaterThan(1L); + } + } } } diff --git a/src/test/resources/CursorIterableRangeTest/testSignedComparatorDupsort.csv b/src/test/resources/CursorIterableRangeTest/testSignedComparatorDupsort.csv index f35dbedc..1a18f426 100644 --- a/src/test/resources/CursorIterableRangeTest/testSignedComparatorDupsort.csv +++ b/src/test/resources/CursorIterableRangeTest/testSignedComparatorDupsort.csv @@ -28,7 +28,7 @@ BACKWARD_CLOSED,7,3,[6 8][6 7][4 6][4 5] BACKWARD_CLOSED,6,2,[6 8][6 7][4 6][4 5][2 4][2 3] BACKWARD_CLOSED,9,3,[8 10][8 9][6 8][6 7][4 6][4 5] BACKWARD_CLOSED,9,-1,[8 10][8 9][6 8][6 7][4 6][4 5][2 4][2 3][0 2][0 1] -BACKWARD_CLOSED_OPEN,8,3,[8 9][6 8][6 7][4 6][4 5] +BACKWARD_CLOSED_OPEN,8,3,[8 10][8 9][6 8][6 7][4 6][4 5] BACKWARD_CLOSED_OPEN,7,2,[6 8][6 7][4 6][4 5] BACKWARD_CLOSED_OPEN,9,3,[8 10][8 9][6 8][6 7][4 6][4 5] BACKWARD_CLOSED_OPEN,9,-1,[8 10][8 9][6 8][6 7][4 6][4 5][2 4][2 3][0 2][0 1] diff --git a/src/test/resources/CursorIterableRangeTest/testUnsignedComparatorDupsort.csv b/src/test/resources/CursorIterableRangeTest/testUnsignedComparatorDupsort.csv index 01cddbad..e9054cbd 100644 --- a/src/test/resources/CursorIterableRangeTest/testUnsignedComparatorDupsort.csv +++ b/src/test/resources/CursorIterableRangeTest/testUnsignedComparatorDupsort.csv @@ -28,7 +28,7 @@ BACKWARD_CLOSED,7,3,[6 8][6 7][4 6][4 5] BACKWARD_CLOSED,6,2,[6 8][6 7][4 6][4 5][2 4][2 3] BACKWARD_CLOSED,9,3,[8 10][8 9][6 8][6 7][4 6][4 5] BACKWARD_CLOSED,9,-1, -BACKWARD_CLOSED_OPEN,8,3,[8 9][6 8][6 7][4 6][4 5] +BACKWARD_CLOSED_OPEN,8,3,[8 10][8 9][6 8][6 7][4 6][4 5] BACKWARD_CLOSED_OPEN,7,2,[6 8][6 7][4 6][4 5] BACKWARD_CLOSED_OPEN,9,3,[8 10][8 9][6 8][6 7][4 6][4 5] BACKWARD_CLOSED_OPEN,9,-1, @@ -47,3 +47,14 @@ BACKWARD_OPEN_CLOSED,7,2,[6 8][6 7][4 6][4 5][2 4][2 3] BACKWARD_OPEN_CLOSED,8,4,[6 8][6 7][4 6][4 5] BACKWARD_OPEN_CLOSED,9,4,[8 10][8 9][6 8][6 7][4 6][4 5] BACKWARD_OPEN_CLOSED,9,-1, +# +# TEST gh-267 +BACKWARD_AT_LEAST,6,,[6 8][6 7][4 6][4 5][2 4][2 3][0 2][0 1] +BACKWARD_AT_LEAST,-2,,[-2 -1][-2 0][8 10][8 9][6 8][6 7][4 6][4 5][2 4][2 3][0 2][0 1] +BACKWARD_AT_LEAST,9,,[8 10][8 9][6 8][6 7][4 6][4 5][2 4][2 3][0 2][0 1] +BACKWARD_CLOSED,6,3,[6 8][6 7][4 6][4 5] +BACKWARD_CLOSED,-2,2,[-2 -1][-2 0][8 10][8 9][6 8][6 7][4 6][4 5][2 4][2 3] +BACKWARD_CLOSED,9,3,[8 10][8 9][6 8][6 7][4 6][4 5] +BACKWARD_CLOSED_OPEN,6,2,[6 8][6 7][4 6][4 5] +BACKWARD_CLOSED_OPEN,-2,3,[-2 -1][-2 0][8 10][8 9][6 8][6 7][4 6][4 5] +BACKWARD_CLOSED_OPEN,9,-1, \ No newline at end of file From c4278017c23b10fe2e052e27ecf4fe6702cad5f8 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Tue, 4 Nov 2025 15:01:16 +0000 Subject: [PATCH 21/90] Add/refactor tests --- .../java/org/lmdbjava/AbstractFlagSet.java | 13 + .../java/org/lmdbjava/ByteBufferProxy.java | 4 +- src/main/java/org/lmdbjava/Cursor.java | 2 +- src/main/java/org/lmdbjava/DbiBuilder.java | 21 +- .../org/lmdbjava/ByteBufferProxyTest.java | 17 +- .../CursorIterableIntegerDupTest.java | 39 ++- .../CursorIterableIntegerKeyTest.java | 154 ++++++++---- .../java/org/lmdbjava/CursorIterableTest.java | 16 +- .../java/org/lmdbjava/DbiBuilderTest.java | 6 +- src/test/java/org/lmdbjava/DbiTest.java | 34 +-- .../java/org/lmdbjava/PutFlagSetTest.java | 225 ++++++++++-------- src/test/java/org/lmdbjava/TestUtils.java | 8 + 12 files changed, 351 insertions(+), 188 deletions(-) diff --git a/src/main/java/org/lmdbjava/AbstractFlagSet.java b/src/main/java/org/lmdbjava/AbstractFlagSet.java index 25aa328b..6e9729fb 100644 --- a/src/main/java/org/lmdbjava/AbstractFlagSet.java +++ b/src/main/java/org/lmdbjava/AbstractFlagSet.java @@ -325,6 +325,19 @@ public Builder setFlag(final E flag) { return this; } + /** + * Sets multiple flag in the builder. + * + * @param flags The flags to set in the builder. + * @return this builder instance. + */ + public Builder setFlags(final Collection flags) { + if (flags != null) { + enumSet.addAll(flags); + } + return this; + } + /** * Clears any flags already set in this {@link Builder} * diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index 5d5aa2ca..27ae375e 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -162,7 +162,9 @@ public static int compareLexicographically(final ByteBuffer o1, final ByteBuffer public static int compareAsIntegerKeys(final ByteBuffer o1, final ByteBuffer o2) { requireNonNull(o1); requireNonNull(o2); - // Both buffers should be same lenght according to LMDB API. + // Both buffers should be same length according to LMDB API. + // From the LMDB docs for MDB_INTEGER_KEY + // numeric keys in native byte order: either unsigned int or size_t. The keys must all be of the same size. final int len1 = o1.limit(); final int len2 = o2.limit(); if (len1 != len2) { diff --git a/src/main/java/org/lmdbjava/Cursor.java b/src/main/java/org/lmdbjava/Cursor.java index c1ac7374..127fffc0 100644 --- a/src/main/java/org/lmdbjava/Cursor.java +++ b/src/main/java/org/lmdbjava/Cursor.java @@ -472,7 +472,7 @@ public T reserve(final T key, final int size) { * Reserve space for data of the given size, but don't copy the given val. Instead, return a * pointer to the reserved space, which the caller can fill in later - before the next update * operation or the transaction ends. This saves an extra memcpy if the data is being generated - * later. LMDB does nothing else with this memory, the caller is expected to modify all of the + * later. LMDB does nothing else with this memory, the caller is expected to modify all the * space requested. * *

This flag must not be specified if the database was opened with MDB_DUPSORT diff --git a/src/main/java/org/lmdbjava/DbiBuilder.java b/src/main/java/org/lmdbjava/DbiBuilder.java index 2b4e6ad8..1bf2489d 100644 --- a/src/main/java/org/lmdbjava/DbiBuilder.java +++ b/src/main/java/org/lmdbjava/DbiBuilder.java @@ -80,6 +80,9 @@ public DbiBuilderStage2 withDbName(final byte[] name) { * Equivalent to passing null to * {@link DbiBuilder#withDbName(String)} or {@link DbiBuilder#withDbName(byte[])}. *

+ *

Note: The 'unnamed database' is used by LMDB to store the names of named databases, with + * the database name being the key. Use of the unnamed database is intended for simple applications + * with only one database.

* @return The next builder stage. */ public DbiBuilderStage2 withoutDbName() { @@ -323,7 +326,7 @@ public DbiBuilderStage3 withDbiFlags(final DbiFlagSet dbiFlagSet) { * {@link DbiBuilderStage3#withDbiFlags(Collection)} * or {@link DbiBuilderStage3#addDbiFlag(DbiFlags)}. * - * @param dbiFlag to open the database with. A null value is a no-op. + * @param dbiFlag to add to any existing flags. A null value is a no-op. * @return this builder instance. */ public DbiBuilderStage3 addDbiFlag(final DbiFlags dbiFlag) { @@ -331,6 +334,22 @@ public DbiBuilderStage3 addDbiFlag(final DbiFlags dbiFlag) { return this; } + /** + * Adds a dbiFlag to those flags already added to this builder by + * {@link DbiBuilderStage3#withDbiFlags(DbiFlags...)}, + * {@link DbiBuilderStage3#withDbiFlags(Collection)} + * or {@link DbiBuilderStage3#addDbiFlag(DbiFlags)}. + * + * @param dbiFlagSet to add to any existing flags. A null value is a no-op. + * @return this builder instance. + */ + public DbiBuilderStage3 addDbiFlags(final DbiFlagSet dbiFlagSet) { + if (dbiFlagSet != null) { + flagSetBuilder.setFlags(dbiFlagSet.getFlags()); + } + return this; + } + /** * Use the supplied transaction to open the {@link Dbi}. *

diff --git a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java index c7d8333f..fb736f41 100644 --- a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java +++ b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java @@ -43,6 +43,7 @@ import java.nio.ByteOrder; import java.time.Duration; import java.time.Instant; +import java.util.Arrays; import java.util.Comparator; import java.util.HashSet; import java.util.LinkedHashMap; @@ -142,9 +143,9 @@ public void unsafeIsDefault() { } /** - * For 100 rounds of 1,000,000 comparisons - * compareAsIntegerKeys: PT0.267813487S - * compareLexicographically: PT0.644165235S + * For 100 rounds of 5,000,000 comparisons + * compareAsIntegerKeys: PT1.600525631S + * compareLexicographically: PT3.381935001S */ @Test public void comparatorPerformance() { @@ -153,7 +154,7 @@ public void comparatorPerformance() { final ByteBuffer buffer2 = ByteBuffer.allocateDirect(Long.BYTES); buffer1.limit(Long.BYTES); buffer2.limit(Long.BYTES); - final long[] values = random.longs(1_000_000).toArray(); + final long[] values = random.longs(5_000_000).toArray(); Instant time = Instant.now(); int x = 0; @@ -195,7 +196,13 @@ public void verifyComparators() { buffer2native.limit(Long.BYTES); buffer1be.limit(Long.BYTES); buffer2be.limit(Long.BYTES); - final long[] values = random.longs(10_000_000).toArray(); + final long[] values = random.longs() + .filter(i -> i >= 0) + .limit(5_000_000) + .toArray(); + System.out.println("stats: " + Arrays.stream(values) + .summaryStatistics() + .toString()); final LinkedHashMap> comparators = new LinkedHashMap<>(); comparators.put("compareAsIntegerKeys", ByteBufferProxy.AbstractByteBufferProxy::compareAsIntegerKeys); diff --git a/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java b/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java index 1acf4328..189aad24 100644 --- a/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java @@ -50,6 +50,7 @@ import static org.lmdbjava.TestUtils.bb; import static org.lmdbjava.TestUtils.bbNative; import static org.lmdbjava.TestUtils.getNativeInt; +import static org.lmdbjava.TestUtils.getNativeIntOrLong; import com.google.common.primitives.UnsignedBytes; import java.io.File; @@ -71,6 +72,7 @@ import org.hamcrest.CoreMatchers; import org.hamcrest.Matchers; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; @@ -88,7 +90,10 @@ @RunWith(Parameterized.class) public final class CursorIterableIntegerDupTest { - private static final DbiFlagSet DBI_FLAGS = DbiFlagSet.of(MDB_CREATE, MDB_INTEGERDUP, MDB_DUPSORT); + private static final DbiFlagSet DBI_FLAGS = DbiFlagSet.of( + MDB_CREATE, + MDB_INTEGERDUP, + MDB_DUPSORT); private static final BufferProxy BUFFER_PROXY = ByteBufferProxy.PROXY_OPTIMAL; private static final List> INPUT_DATA; @@ -122,35 +127,47 @@ public final class CursorIterableIntegerDupTest { @Parameterized.Parameters(name = "{index}: dbi: {0}") public static Object[] data() { - final DbiFactory defaultComparator = new DbiFactory("defaultComparator", env -> + final DbiFactory defaultComparatorDb = new DbiFactory("defaultComparator", env -> env.buildDbi() .withDbName(DB_1) .withDefaultComparator() .withDbiFlags(DBI_FLAGS) .open()); - final DbiFactory nativeComparator = new DbiFactory("nativeComparator", env -> + final DbiFactory nativeComparatorDb = new DbiFactory("nativeComparator", env -> env.buildDbi() .withDbName(DB_2) .withNativeComparator() .withDbiFlags(DBI_FLAGS) .open()); - final DbiFactory callbackComparator = new DbiFactory("callbackComparator", env -> + final DbiFactory callbackComparatorDb = new DbiFactory("callbackComparator", env -> env.buildDbi() .withDbName(DB_3) - .withCallbackComparator(BUFFER_PROXY.getComparator(DBI_FLAGS)) + .withCallbackComparator(buildComparator()) .withDbiFlags(DBI_FLAGS) .open()); - final DbiFactory iteratorComparator = new DbiFactory("iteratorComparator", env -> + final DbiFactory iteratorComparatorDb = new DbiFactory("iteratorComparator", env -> env.buildDbi() .withDbName(DB_4) - .withIteratorComparator(BUFFER_PROXY.getComparator(DBI_FLAGS)) + .withIteratorComparator(buildComparator()) .withDbiFlags(DBI_FLAGS) .open()); return new Object[]{ - defaultComparator, - nativeComparator, - callbackComparator, - iteratorComparator}; + defaultComparatorDb, + nativeComparatorDb, + callbackComparatorDb, + iteratorComparatorDb}; + } + + private static Comparator buildComparator() { + final Comparator baseComparator = BUFFER_PROXY.getComparator(DBI_FLAGS); + return (o1, o2) -> { + if (o1.remaining() != o2.remaining()) { + // Make sure LMDB is always giving us consistent key lengths. + Assert.fail("o1: " + o1 + " " + getNativeIntOrLong(o1) + + ", o2: " + o2 + " " + getNativeIntOrLong(o2)); + } + return baseComparator.compare(o1, o2); + }; } @Before diff --git a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java index be91ea4c..1e664fb1 100644 --- a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java @@ -51,6 +51,8 @@ import static org.lmdbjava.TestUtils.bb; import static org.lmdbjava.TestUtils.bbNative; import static org.lmdbjava.TestUtils.getNativeInt; +import static org.lmdbjava.TestUtils.getNativeIntOrLong; +import static org.lmdbjava.TestUtils.getNativeLong; import static org.lmdbjava.TestUtils.getString; import com.google.common.primitives.UnsignedBytes; @@ -71,6 +73,7 @@ import java.util.stream.Collectors; import org.hamcrest.Matchers; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -104,35 +107,49 @@ public final class CursorIterableIntegerKeyTest { @Parameterized.Parameters(name = "{index}: dbi: {0}") public static Object[] data() { - final DbiFactory defaultComparator = new DbiFactory("defaultComparator", env -> + final DbiFactory defaultComparatorDb = new DbiFactory("defaultComparator", env -> env.buildDbi() .withDbName(DB_1) .withDefaultComparator() .withDbiFlags(DBI_FLAGS) .open()); - final DbiFactory nativeComparator = new DbiFactory("nativeComparator", env -> + final DbiFactory nativeComparatorDb = new DbiFactory("nativeComparator", env -> env.buildDbi() .withDbName(DB_2) .withNativeComparator() .withDbiFlags(DBI_FLAGS) .open()); - final DbiFactory callbackComparator = new DbiFactory("callbackComparator", env -> + final Comparator comparator = buildComparator(); + + final DbiFactory callbackComparatorDb = new DbiFactory("callbackComparator", env -> env.buildDbi() .withDbName(DB_3) - .withCallbackComparator(BUFFER_PROXY.getComparator(DBI_FLAGS)) + .withCallbackComparator(comparator) .withDbiFlags(DBI_FLAGS) .open()); - final DbiFactory iteratorComparator = new DbiFactory("iteratorComparator", env -> + final DbiFactory iteratorComparatorDb = new DbiFactory("iteratorComparator", env -> env.buildDbi() .withDbName(DB_4) - .withIteratorComparator(BUFFER_PROXY.getComparator(DBI_FLAGS)) + .withIteratorComparator(comparator) .withDbiFlags(DBI_FLAGS) .open()); return new Object[]{ - defaultComparator, - nativeComparator, - callbackComparator, - iteratorComparator}; + defaultComparatorDb, + nativeComparatorDb, + callbackComparatorDb, + iteratorComparatorDb}; + } + + private static Comparator buildComparator() { + final Comparator baseComparator = BUFFER_PROXY.getComparator(DBI_FLAGS); + return (o1, o2) -> { + if (o1.remaining() != o2.remaining()) { + // Make sure LMDB is always giving us consistent key lengths. + Assert.fail("o1: " + o1 + " " + getNativeIntOrLong(o1) + + ", o2: " + o2 + " " + getNativeIntOrLong(o2)); + } + return baseComparator.compare(o1, o2); + }; } @Before @@ -162,13 +179,13 @@ public void testNumericOrderLong() { final Cursor c = dbi.openCursor(txn); long i = 1; while (true) { -// System.out.println("putting " + i); + System.out.println("putting " + i); c.put(bbNative(i), bb(i + "-long")); - final long i2 = i * 10; - if (i2 < i) { - // Overflowed - break; - } + final long i2 = i * 10; + if (i2 < i) { + // Overflowed + break; + } i = i2; } txn.commit(); @@ -178,8 +195,9 @@ public void testNumericOrderLong() { try (Txn txn = env.txnRead()) { try (CursorIterable iterable = dbi.iterate(txn)) { for (KeyVal keyVal : iterable) { + assertThat(keyVal.key().remaining(), is(Long.BYTES)); final String val = getString(keyVal.val()); - final long key = TestUtils.getNativeLong(keyVal.key()); + final long key = getNativeLong(keyVal.key()); entries.add(new AbstractMap.SimpleEntry<>(key, val)); // System.out.println(val); } @@ -208,7 +226,7 @@ public void testNumericOrderInt() { final Cursor c = dbi.openCursor(txn); int i = 1; while (true) { -// System.out.println("putting " + i); + System.out.println("putting " + i); c.put(bbNative(i), bb(i + "-int")); final int i2 = i * 10; if (i2 < i) { @@ -224,6 +242,7 @@ public void testNumericOrderInt() { try (Txn txn = env.txnRead()) { try (CursorIterable iterable = dbi.iterate(txn)) { for (KeyVal keyVal : iterable) { + assertThat(keyVal.key().remaining(), is(Integer.BYTES)); final String val = getString(keyVal.val()); final int key = TestUtils.getNativeInt(keyVal.key()); entries.add(new AbstractMap.SimpleEntry<>(key, val)); @@ -246,6 +265,41 @@ public void testNumericOrderInt() { } } + @Test + public void testIntegerKeyKeySize() { + final Dbi db = dbiFactory.factory.apply(env); + long maxIntAsLong = Integer.MAX_VALUE; + + try (Txn txn = env.txnWrite()) { + System.out.println("Flags: " + db.listFlags(txn)); + int val = 0; + db.put(txn, bbNative(0L), bb("val_" + ++val)); + db.put(txn, bbNative(10L), bb("val_" + ++val)); + db.put(txn, bbNative(maxIntAsLong - 1_111_111_111), bb("val_" + ++val)); + db.put(txn, bbNative(maxIntAsLong - 111_111_111), bb("val_" + ++val)); + db.put(txn, bbNative(maxIntAsLong - 111_111), bb("val_" + ++val)); + db.put(txn, bbNative(maxIntAsLong - 111), bb("val_" + ++val)); + db.put(txn, bbNative(maxIntAsLong + 111), bb("val_" + ++val)); + db.put(txn, bbNative(maxIntAsLong + 111_111), bb("val_" + ++val)); + db.put(txn, bbNative(maxIntAsLong + 111_111_111), bb("val_" + ++val)); + db.put(txn, bbNative(maxIntAsLong + 1_111_111_111), bb("val_" + ++val)); + db.put(txn, bbNative(Long.MAX_VALUE), bb("val_" + ++val)); + txn.commit(); + } + + try (Txn txn = env.txnRead()) { + try (CursorIterable iterable = db.iterate(txn)) { + for (KeyVal keyVal : iterable) { + final String val = getString(keyVal.val()); + final long key = getNativeLong(keyVal.key()); + final int remaining = keyVal.key().remaining(); + System.out.println("key: " + key + ", val: " + val + ", remaining: " + remaining); + } + } + } + + } + @Test public void allBackwardTest() { verify(allBackward(), 8, 6, 4, 2); @@ -365,11 +419,11 @@ public void iterate() { @Test(expected = IllegalStateException.class) public void iteratorOnlyReturnedOnce() { final Dbi db = getDb(); - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { - c.iterator(); // ok - c.iterator(); // fails - } + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + c.iterator(); // ok + c.iterator(); // fails + } } @Test @@ -472,57 +526,57 @@ public void removeOddElements() { @Test(expected = Env.AlreadyClosedException.class) public void nextWithClosedEnvTest() { final Dbi db = getDb(); - try (Txn txn = env.txnRead()) { - try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { - final Iterator> c = ci.iterator(); + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); - env.close(); - c.next(); - } + env.close(); + c.next(); } + } } @Test(expected = Env.AlreadyClosedException.class) public void removeWithClosedEnvTest() { final Dbi db = getDb(); - try (Txn txn = env.txnWrite()) { - try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { - final Iterator> c = ci.iterator(); + try (Txn txn = env.txnWrite()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); - final KeyVal keyVal = c.next(); - assertThat(keyVal, Matchers.notNullValue()); + final KeyVal keyVal = c.next(); + assertThat(keyVal, Matchers.notNullValue()); - env.close(); - c.remove(); - } + env.close(); + c.remove(); } + } } @Test(expected = Env.AlreadyClosedException.class) public void hasNextWithClosedEnvTest() { final Dbi db = getDb(); - try (Txn txn = env.txnRead()) { - try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { - final Iterator> c = ci.iterator(); + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); - env.close(); - c.hasNext(); - } + env.close(); + c.hasNext(); } + } } @Test(expected = Env.AlreadyClosedException.class) public void forEachRemainingWithClosedEnvTest() { final Dbi db = getDb(); - try (Txn txn = env.txnRead()) { - try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { - final Iterator> c = ci.iterator(); + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); - env.close(); - c.forEachRemaining(keyVal -> { - }); - } + env.close(); + c.forEachRemaining(keyVal -> { + }); } + } } private void verify(final KeyRange range, final int... expected) { diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index 7bcbd851..a6b5d7d1 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -93,35 +93,35 @@ public final class CursorIterableTest { @Parameterized.Parameters(name = "{index}: dbi: {0}") public static Object[] data() { - final DbiFactory defaultComparator = new DbiFactory("defaultComparator", env -> + final DbiFactory defaultComparatorDb = new DbiFactory("defaultComparator", env -> env.buildDbi() .withDbName(DB_1) .withDefaultComparator() .withDbiFlags(DBI_FLAGS) .open()); - final DbiFactory nativeComparator = new DbiFactory("nativeComparator", env -> + final DbiFactory nativeComparatorDb = new DbiFactory("nativeComparator", env -> env.buildDbi() .withDbName(DB_2) .withNativeComparator() .withDbiFlags(DBI_FLAGS) .open()); - final DbiFactory callbackComparator = new DbiFactory("callbackComparator", env -> + final DbiFactory callbackComparatorDb = new DbiFactory("callbackComparator", env -> env.buildDbi() .withDbName(DB_3) .withCallbackComparator(BUFFER_PROXY.getComparator(DBI_FLAGS)) .withDbiFlags(DBI_FLAGS) .open()); - final DbiFactory iteratorComparator = new DbiFactory("iteratorComparator", env -> + final DbiFactory iteratorComparatorDb = new DbiFactory("iteratorComparator", env -> env.buildDbi() .withDbName(DB_4) .withIteratorComparator(BUFFER_PROXY.getComparator(DBI_FLAGS)) .withDbiFlags(DBI_FLAGS) .open()); return new Object[] { - defaultComparator, - nativeComparator, - callbackComparator, - iteratorComparator}; + defaultComparatorDb, + nativeComparatorDb, + callbackComparatorDb, + iteratorComparatorDb}; } @Before diff --git a/src/test/java/org/lmdbjava/DbiBuilderTest.java b/src/test/java/org/lmdbjava/DbiBuilderTest.java index 74fbd8f5..6f6a4f39 100644 --- a/src/test/java/org/lmdbjava/DbiBuilderTest.java +++ b/src/test/java/org/lmdbjava/DbiBuilderTest.java @@ -60,13 +60,12 @@ public void unnamed() { .withDefaultComparator() .withDbiFlags(DbiFlags.MDB_CREATE) .open(); - + assertThat(dbi.getName(), Matchers.nullValue()); + assertThat(dbi.getNameAsString(), Matchers.emptyString()); assertThat(env.getDbiNames().size(), Matchers.is(0)); - assertPutAndGet(dbi); } - @Test public void named() { final Dbi dbi = env.buildDbi() @@ -89,6 +88,7 @@ private void assertPutAndGet(Dbi dbi) { try (Txn readTxn = env.txnRead()) { final ByteBuffer byteBuffer = dbi.get(readTxn, bb(123)); + assertThat(byteBuffer, Matchers.notNullValue()); final int val = byteBuffer.getInt(); assertThat(val, Matchers.is(123_000)); } diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 2209e614..962aacab 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -74,10 +74,13 @@ import org.lmdbjava.Env.MapFullException; import org.lmdbjava.LmdbNativeException.ConstantDerivedException; -/** Test {@link Dbi}. */ +/** + * Test {@link Dbi}. + */ public final class DbiTest { - @Rule public final TemporaryFolder tmp = new TemporaryFolder(); + @Rule + public final TemporaryFolder tmp = new TemporaryFolder(); private Env env; private Env envBa; @@ -105,10 +108,13 @@ public void before() throws IOException { } - @Test(expected = ConstantDerivedException.class) public void close() { - final Dbi db = env.openDbi(DB_1, MDB_CREATE); + final Dbi db = env.buildDbi() + .withDbName(DB_1) + .withDefaultComparator() + .addDbiFlag(MDB_CREATE) + .open(); db.put(bb(1), bb(42)); db.close(); db.put(bb(2), bb(42)); // error @@ -154,7 +160,7 @@ private void doCustomComparator( txn.commit(); } try (Txn txn = env.txnRead(); - CursorIterable ci = db.iterate(txn, atMost(serializer.apply(4)))) { + CursorIterable ci = db.iterate(txn, atMost(serializer.apply(4)))) { final Iterator> iter = ci.iterator(); assertThat(deserializer.applyAsInt(iter.next().key()), is(8)); assertThat(deserializer.applyAsInt(iter.next().key()), is(6)); @@ -186,7 +192,7 @@ public void doDbiWithComparatorThreadSafety( Supplier> comparatorSupplier, IntFunction serializer, ToIntFunction deserializer) { - final DbiFlags[] flags = new DbiFlags[] {MDB_CREATE, MDB_INTEGERKEY}; + final DbiFlags[] flags = new DbiFlags[]{MDB_CREATE, MDB_INTEGERKEY}; final Comparator c = comparatorSupplier.get(); final Dbi db = env.openDbi(DB_1, c, true, flags); @@ -212,7 +218,7 @@ public void doDbiWithComparatorThreadSafety( } try (Txn txn = env.txnRead(); - CursorIterable ci = db.iterate(txn)) { + CursorIterable ci = db.iterate(txn)) { final Iterator> iter = ci.iterator(); final List result = new ArrayList<>(); while (iter.hasNext()) { @@ -292,8 +298,8 @@ public void getName() { @Test public void getNamesWhenDbisPresent() { - final byte[] dbHello = new byte[] {'h', 'e', 'l', 'l', 'o'}; - final byte[] dbWorld = new byte[] {'w', 'o', 'r', 'l', 'd'}; + final byte[] dbHello = new byte[]{'h', 'e', 'l', 'l', 'o'}; + final byte[] dbWorld = new byte[]{'w', 'o', 'r', 'l', 'd'}; env.openDbi(dbHello, MDB_CREATE); env.openDbi(dbWorld, MDB_CREATE); final List dbiNames = env.getDbiNames(); @@ -369,11 +375,11 @@ public void putCommitGet() { public void putCommitGetByteArray() throws IOException { final File path = tmp.newFile(); try (Env envBa = - create(PROXY_BA) - .setMapSize(MEBIBYTES.toBytes(64)) - .setMaxReaders(1) - .setMaxDbs(2) - .open(path, MDB_NOSUBDIR)) { + create(PROXY_BA) + .setMapSize(MEBIBYTES.toBytes(64)) + .setMaxReaders(1) + .setMaxDbs(2) + .open(path, MDB_NOSUBDIR)) { final Dbi db = envBa.openDbi(DB_1, MDB_CREATE); try (Txn txn = envBa.txnWrite()) { db.put(txn, ba(5), ba(5)); diff --git a/src/test/java/org/lmdbjava/PutFlagSetTest.java b/src/test/java/org/lmdbjava/PutFlagSetTest.java index 3e402732..b8b44254 100644 --- a/src/test/java/org/lmdbjava/PutFlagSetTest.java +++ b/src/test/java/org/lmdbjava/PutFlagSetTest.java @@ -19,113 +19,150 @@ import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; +import java.time.Duration; +import java.time.Instant; import java.util.Arrays; import java.util.HashSet; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.junit.Test; public class PutFlagSetTest { - @Test - public void testEmpty() { - final PutFlagSet putFlagSet = PutFlagSet.empty(); - assertThat( - putFlagSet.getMask(), - is(0)); - assertThat( - putFlagSet.size(), - is(0)); - assertThat( - putFlagSet.isEmpty(), - is(true)); - assertThat( - putFlagSet.isSet(PutFlags.MDB_MULTIPLE), - is(false)); - final PutFlagSet putFlagSet2 = PutFlagSet.builder() - .build(); - assertThat(putFlagSet, is(putFlagSet2)); - assertThat(putFlagSet, not(PutFlagSet.of(PutFlags.MDB_APPEND))); - assertThat(putFlagSet, not(PutFlagSet.of(PutFlags.MDB_APPEND, PutFlags.MDB_RESERVE))); - assertThat(putFlagSet, not(PutFlagSet.builder() - .setFlag(PutFlags.MDB_CURRENT) - .setFlag(PutFlags.MDB_MULTIPLE) - .build())); + @Test + public void testEmpty() { + final PutFlagSet putFlagSet = PutFlagSet.empty(); + assertThat( + putFlagSet.getMask(), + is(0)); + assertThat( + putFlagSet.size(), + is(0)); + assertThat( + putFlagSet.isEmpty(), + is(true)); + assertThat( + putFlagSet.isSet(PutFlags.MDB_MULTIPLE), + is(false)); + final PutFlagSet putFlagSet2 = PutFlagSet.builder() + .build(); + assertThat(putFlagSet, is(putFlagSet2)); + assertThat(putFlagSet, not(PutFlagSet.of(PutFlags.MDB_APPEND))); + assertThat(putFlagSet, not(PutFlagSet.of(PutFlags.MDB_APPEND, PutFlags.MDB_RESERVE))); + assertThat(putFlagSet, not(PutFlagSet.builder() + .setFlag(PutFlags.MDB_CURRENT) + .setFlag(PutFlags.MDB_MULTIPLE) + .build())); + } + + @Test + public void testOf() { + final PutFlags putFlag = PutFlags.MDB_APPEND; + final PutFlagSet putFlagSet = PutFlagSet.of(putFlag); + assertThat( + putFlagSet.getMask(), + is(MaskedFlag.mask(putFlag))); + assertThat( + putFlagSet.size(), + is(1)); + assertThat( + putFlagSet.isSet(PutFlags.MDB_MULTIPLE), + is(false)); + for (PutFlags flag : putFlagSet) { + assertThat( + putFlagSet.isSet(flag), + is(true)); } - @Test - public void testOf() { - final PutFlags putFlag = PutFlags.MDB_APPEND; - final PutFlagSet putFlagSet = PutFlagSet.of(putFlag); - assertThat( - putFlagSet.getMask(), - is(MaskedFlag.mask(putFlag))); - assertThat( - putFlagSet.size(), - is(1)); - assertThat( - putFlagSet.isSet(PutFlags.MDB_MULTIPLE), - is(false)); - for (PutFlags flag : putFlagSet) { - assertThat( - putFlagSet.isSet(flag), - is(true)); - } + final PutFlagSet putFlagSet2 = PutFlagSet.builder() + .setFlag(putFlag) + .build(); + assertThat(putFlagSet, is(putFlagSet2)); + } - final PutFlagSet putFlagSet2 = PutFlagSet.builder() - .setFlag(putFlag) - .build(); - assertThat(putFlagSet, is(putFlagSet2)); + @Test + public void testOf2() { + final PutFlags putFlag1 = PutFlags.MDB_APPEND; + final PutFlags putFlag2 = PutFlags.MDB_NOOVERWRITE; + final PutFlagSet putFlagSet = PutFlagSet.of(putFlag1, putFlag2); + assertThat( + putFlagSet.getMask(), + is(MaskedFlag.mask(putFlag1, putFlag2))); + assertThat( + putFlagSet.size(), + is(2)); + assertThat( + putFlagSet.isSet(PutFlags.MDB_MULTIPLE), + is(false)); + for (PutFlags flag : putFlagSet) { + assertThat( + putFlagSet.isSet(flag), + is(true)); } + } - @Test - public void testOf2() { - final PutFlags putFlag1 = PutFlags.MDB_APPEND; - final PutFlags putFlag2 = PutFlags.MDB_NOOVERWRITE; - final PutFlagSet putFlagSet = PutFlagSet.of(putFlag1, putFlag2); - assertThat( - putFlagSet.getMask(), - is(MaskedFlag.mask(putFlag1, putFlag2))); - assertThat( - putFlagSet.size(), - is(2)); - assertThat( - putFlagSet.isSet(PutFlags.MDB_MULTIPLE), - is(false)); - for (PutFlags flag : putFlagSet) { - assertThat( - putFlagSet.isSet(flag), - is(true)); - } + @Test + public void testBuilder() { + final PutFlags putFlag1 = PutFlags.MDB_APPEND; + final PutFlags putFlag2 = PutFlags.MDB_NOOVERWRITE; + final PutFlagSet putFlagSet = PutFlagSet.builder() + .setFlag(putFlag1) + .setFlag(putFlag2) + .build(); + assertThat( + putFlagSet.getMask(), + is(MaskedFlag.mask(putFlag1, putFlag2))); + assertThat( + putFlagSet.size(), + is(2)); + assertThat( + putFlagSet.isSet(PutFlags.MDB_MULTIPLE), + is(false)); + for (PutFlags flag : putFlagSet) { + assertThat( + putFlagSet.isSet(flag), + is(true)); } + final PutFlagSet putFlagSet2 = PutFlagSet.builder() + .withFlags(putFlag1, putFlag2) + .build(); + final PutFlagSet putFlagSet3 = PutFlagSet.builder() + .withFlags(new HashSet<>(Arrays.asList(putFlag1, putFlag2))) + .build(); + assertThat(putFlagSet, is(putFlagSet2)); + assertThat(putFlagSet, is(putFlagSet3)); + } - @Test - public void testBuilder() { - final PutFlags putFlag1 = PutFlags.MDB_APPEND; - final PutFlags putFlag2 = PutFlags.MDB_NOOVERWRITE; - final PutFlagSet putFlagSet = PutFlagSet.builder() - .setFlag(putFlag1) - .setFlag(putFlag2) - .build(); - assertThat( - putFlagSet.getMask(), - is(MaskedFlag.mask(putFlag1, putFlag2))); - assertThat( - putFlagSet.size(), - is(2)); - assertThat( - putFlagSet.isSet(PutFlags.MDB_MULTIPLE), - is(false)); - for (PutFlags flag : putFlagSet) { - assertThat( - putFlagSet.isSet(flag), - is(true)); + @Test + public void testAddFlagVsCheckPresence() { + + final int cnt = 10_000_000; + final int[] arr = new int[cnt]; + final List flagSets = IntStream.range(0, cnt) + .boxed() + .map(i -> PutFlagSet.of(PutFlags.MDB_APPEND, PutFlags.MDB_NOOVERWRITE, PutFlags.MDB_RESERVE)) + .collect(Collectors.toList()); + + Instant time; + for (int i = 0; i < 5; i++) { + time = Instant.now(); + for (int j = 0; j < flagSets.size(); j++) { + PutFlagSet flagSet = flagSets.get(j); + if (!flagSet.isSet(PutFlags.MDB_RESERVE)) { + throw new RuntimeException("Not set"); } - final PutFlagSet putFlagSet2 = PutFlagSet.builder() - .withFlags(putFlag1, putFlag2) - .build(); - final PutFlagSet putFlagSet3 = PutFlagSet.builder() - .withFlags(new HashSet<>(Arrays.asList(putFlag1, putFlag2))) - .build(); - assertThat(putFlagSet, is(putFlagSet2)); - assertThat(putFlagSet, is(putFlagSet3)); + arr[j] = flagSet.getMask(); + } + System.out.println("Check: " + Duration.between(time, Instant.now())); + + time = Instant.now(); + for (int j = 0; j < flagSets.size(); j++) { + PutFlagSet flagSet = flagSets.get(j); + final int mask = flagSet.getMaskWith(PutFlags.MDB_RESERVE); + arr[j] = mask; + } + System.out.println("Append:" + Duration.between(time, Instant.now())); } + } } diff --git a/src/test/java/org/lmdbjava/TestUtils.java b/src/test/java/org/lmdbjava/TestUtils.java index 511619fe..94ceb3a7 100644 --- a/src/test/java/org/lmdbjava/TestUtils.java +++ b/src/test/java/org/lmdbjava/TestUtils.java @@ -93,6 +93,14 @@ static long getNativeLong(final ByteBuffer bb) { return val; } + static long getNativeIntOrLong(final ByteBuffer bb) { + if (bb.remaining() == BYTES) { + return getNativeInt(bb); + } else { + return getNativeLong(bb); + } + } + static String getString(final ByteBuffer bb) { final String str = StandardCharsets.UTF_8.decode(bb) .toString(); From e2be6bf145a100fb2b8d40446dd3b70a54f51e2d Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Wed, 5 Nov 2025 13:06:57 +0000 Subject: [PATCH 22/90] Add integer key comparator tests --- .../java/org/lmdbjava/AbstractFlagSet.java | 2 - src/main/java/org/lmdbjava/ByteBufProxy.java | 74 +++- .../java/org/lmdbjava/ByteBufferProxy.java | 5 +- src/main/java/org/lmdbjava/DbiBuilder.java | 38 +- .../java/org/lmdbjava/DirectBufferProxy.java | 18 +- src/main/java/org/lmdbjava/Env.java | 185 +++++++-- .../org/lmdbjava/ByteBufferProxyTest.java | 17 +- .../lmdbjava/ComparatorIntegerKeyTest.java | 369 ++++++++++++++++++ .../java/org/lmdbjava/ComparatorTest.java | 10 + .../CursorIterableIntegerDupTest.java | 30 +- .../CursorIterableIntegerKeyTest.java | 32 +- .../org/lmdbjava/CursorIterablePerfTest.java | 12 +- .../java/org/lmdbjava/CursorIterableTest.java | 56 +-- .../java/org/lmdbjava/DbiBuilderTest.java | 125 +++++- src/test/java/org/lmdbjava/DbiTest.java | 2 +- src/test/java/org/lmdbjava/TestUtils.java | 35 ++ .../java/org/lmdbjava/TxnFlagSetTest.java | 111 +++--- 17 files changed, 906 insertions(+), 215 deletions(-) create mode 100644 src/test/java/org/lmdbjava/ComparatorIntegerKeyTest.java diff --git a/src/main/java/org/lmdbjava/AbstractFlagSet.java b/src/main/java/org/lmdbjava/AbstractFlagSet.java index 6e9729fb..058969de 100644 --- a/src/main/java/org/lmdbjava/AbstractFlagSet.java +++ b/src/main/java/org/lmdbjava/AbstractFlagSet.java @@ -26,8 +26,6 @@ /** * Encapsulates an immutable set of flags and the associated bit mask for the flags in the set. - * - * @param */ public abstract class AbstractFlagSet & MaskedFlag> implements FlagSet { diff --git a/src/main/java/org/lmdbjava/ByteBufProxy.java b/src/main/java/org/lmdbjava/ByteBufProxy.java index 319256fb..19d94392 100644 --- a/src/main/java/org/lmdbjava/ByteBufProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufProxy.java @@ -23,6 +23,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.PooledByteBufAllocator; import java.lang.reflect.Field; +import java.nio.ByteOrder; import java.util.Comparator; import jnr.ffi.Pointer; @@ -44,13 +45,6 @@ public final class ByteBufProxy extends BufferProxy { private static final String FIELD_NAME_ADDRESS = "memoryAddress"; private static final String FIELD_NAME_LENGTH = "length"; private static final String NAME = "io.netty.buffer.PooledUnsafeDirectByteBuf"; - private static final Comparator comparator = - (o1, o2) -> { - requireNonNull(o1); - requireNonNull(o2); - - return o1.compareTo(o2); - }; private final long lengthOffset; private final long addressOffset; @@ -81,6 +75,66 @@ public ByteBufProxy(final PooledByteBufAllocator allocator) { } } + /** + * Lexicographically compare two buffers. + * + * @param o1 left operand (required) + * @param o2 right operand (required) + * @return as specified by {@link Comparable} interface + */ + public static int compareLexicographically(final ByteBuf o1, final ByteBuf o2) { + requireNonNull(o1); + requireNonNull(o2); + return o1.compareTo(o2); + } + + /** + * Buffer comparator specifically for 4/8 byte keys that are unsigned ints/longs, + * i.e. when using MDB_INTEGER_KEY/MDB_INTEGERDUP. Compares the buffers numerically. + * + * @param o1 left operand (required) + * @param o2 right operand (required) + * @return as specified by {@link Comparable} interface + */ + public static int compareAsIntegerKeys(final ByteBuf o1, final ByteBuf o2) { + requireNonNull(o1); + requireNonNull(o2); + // Both buffers should be same length according to LMDB API. + // From the LMDB docs for MDB_INTEGER_KEY + // numeric keys in native byte order: either unsigned int or size_t. The keys must all be of the same size. + final int len1 = o1.readableBytes(); + final int len2 = o2.readableBytes(); + if (len1 != len2) { + throw new RuntimeException("Length mismatch, len1: " + len1 + ", len2: " + len2 + + ". Lengths must be identical and either 4 or 8 bytes."); + } + if (len1 == 8) { + final long lw; + final long rw; + if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) { + lw = o1.readLongLE(); + rw = o2.readLongLE(); + } else { + lw = o1.readLong(); + rw = o2.readLong(); + } + return Long.compareUnsigned(lw, rw); + } else if (len1 == 4) { + final int lw; + final int rw; + if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) { + lw = o1.readIntLE(); + rw = o2.readIntLE(); + } else { + lw = o1.readInt(); + rw = o2.readInt(); + } + return Integer.compareUnsigned(lw, rw); + } else { + return compareLexicographically(o1, o2); + } + } + static Field findField(final String c, final String name) { Class clazz; try { @@ -115,7 +169,11 @@ protected ByteBuf allocate() { @Override public Comparator getComparator(final DbiFlagSet dbiFlagSet) { - return comparator; + if (dbiFlagSet.areAnySet(DbiFlagSet.INTEGER_KEY_FLAGS)) { + return ByteBufProxy::compareAsIntegerKeys; + } else { + return ByteBufProxy::compareLexicographically; + } } @Override diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index 27ae375e..c682bec8 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -60,6 +60,7 @@ public final class ByteBufferProxy { */ public static final BufferProxy PROXY_SAFE; + private static final ByteOrder NATIVE_ORDER = ByteOrder.nativeOrder(); static { PROXY_SAFE = new ReflectiveProxy(); @@ -172,8 +173,8 @@ public static int compareAsIntegerKeys(final ByteBuffer o1, final ByteBuffer o2) + ". Lengths must be identical and either 4 or 8 bytes."); } // Keys for MDB_INTEGER_KEY are written in native order so ensure we read them in that order - o1.order(ByteOrder.nativeOrder()); - o2.order(ByteOrder.nativeOrder()); + o1.order(NATIVE_ORDER); + o2.order(NATIVE_ORDER); // TODO it might be worth the DbiBuilder having a method to capture fixedKeyLength() or -1 // for variable length keys. This can be passed to getComparator(..) so it can return a // comparator that doesn't need to test the length every time. There may be other benefits diff --git a/src/main/java/org/lmdbjava/DbiBuilder.java b/src/main/java/org/lmdbjava/DbiBuilder.java index 1bf2489d..f94118a7 100644 --- a/src/main/java/org/lmdbjava/DbiBuilder.java +++ b/src/main/java/org/lmdbjava/DbiBuilder.java @@ -53,12 +53,12 @@ public class DbiBuilder { * (see also {@link DbiBuilder#withoutDbName()}) * @return The next builder stage. */ - public DbiBuilderStage2 withDbName(final String name) { + public DbiBuilderStage2 setDbName(final String name) { // Null name is allowed so no null check final byte[] nameBytes = name == null ? null : name.getBytes(Env.DEFAULT_NAME_CHARSET); - return withDbName(nameBytes); + return setDbName(nameBytes); } /** @@ -66,7 +66,7 @@ public DbiBuilderStage2 withDbName(final String name) { * @param name The name of the database in byte form. * @return The next builder stage. */ - public DbiBuilderStage2 withDbName(final byte[] name) { + public DbiBuilderStage2 setDbName(final byte[] name) { // Null name is allowed so no null check this.name = name; return new DbiBuilderStage2<>(this); @@ -78,7 +78,7 @@ public DbiBuilderStage2 withDbName(final byte[] name) { *

*

* Equivalent to passing null to - * {@link DbiBuilder#withDbName(String)} or {@link DbiBuilder#withDbName(byte[])}. + * {@link DbiBuilder#setDbName(String)} or {@link DbiBuilder#setDbName(byte[])}. *

*

Note: The 'unnamed database' is used by LMDB to store the names of named databases, with * the database name being the key. Use of the unnamed database is intended for simple applications @@ -86,7 +86,7 @@ public DbiBuilderStage2 withDbName(final byte[] name) { * @return The next builder stage. */ public DbiBuilderStage2 withoutDbName() { - return withDbName((byte[]) null); + return setDbName((byte[]) null); } @@ -254,8 +254,8 @@ private DbiBuilderStage3(DbiBuilderStage2 dbiBuilderStage2) { *

*

* Clears all flags currently set by previous calls to - * {@link DbiBuilderStage3#withDbiFlags(Collection)}, - * {@link DbiBuilderStage3#withDbiFlags(DbiFlags...)} + * {@link DbiBuilderStage3#setDbiFlags(Collection)}, + * {@link DbiBuilderStage3#setDbiFlags(DbiFlags...)} * or {@link DbiBuilderStage3#addDbiFlag(DbiFlags)}. *

* @@ -263,7 +263,7 @@ private DbiBuilderStage3(DbiBuilderStage2 dbiBuilderStage2) { * A null {@link Collection} will just clear all set flags. * Null items are ignored. */ - public DbiBuilderStage3 withDbiFlags(final Collection dbiFlags) { + public DbiBuilderStage3 setDbiFlags(final Collection dbiFlags) { flagSetBuilder.clear(); if (dbiFlags != null) { dbiFlags.stream() @@ -279,8 +279,8 @@ public DbiBuilderStage3 withDbiFlags(final Collection dbiFlags) { *

*

* Clears all flags currently set by previous calls to - * {@link DbiBuilderStage3#withDbiFlags(Collection)}, - * {@link DbiBuilderStage3#withDbiFlags(DbiFlags...)} + * {@link DbiBuilderStage3#setDbiFlags(Collection)}, + * {@link DbiBuilderStage3#setDbiFlags(DbiFlags...)} * or {@link DbiBuilderStage3#addDbiFlag(DbiFlags)}. *

* @@ -288,7 +288,7 @@ public DbiBuilderStage3 withDbiFlags(final Collection dbiFlags) { * A null array will just clear all set flags. * Null items are ignored. */ - public DbiBuilderStage3 withDbiFlags(final DbiFlags... dbiFlags) { + public DbiBuilderStage3 setDbiFlags(final DbiFlags... dbiFlags) { flagSetBuilder.clear(); if (dbiFlags != null) { Arrays.stream(dbiFlags) @@ -304,15 +304,15 @@ public DbiBuilderStage3 withDbiFlags(final DbiFlags... dbiFlags) { *

*

* Clears all flags currently set by previous calls to - * {@link DbiBuilderStage3#withDbiFlags(Collection)}, - * {@link DbiBuilderStage3#withDbiFlags(DbiFlags...)} + * {@link DbiBuilderStage3#setDbiFlags(Collection)}, + * {@link DbiBuilderStage3#setDbiFlags(DbiFlags...)} * or {@link DbiBuilderStage3#addDbiFlag(DbiFlags)}. *

* * @param dbiFlagSet to open the database with. * A null value will just clear all set flags. */ - public DbiBuilderStage3 withDbiFlags(final DbiFlagSet dbiFlagSet) { + public DbiBuilderStage3 setDbiFlags(final DbiFlagSet dbiFlagSet) { flagSetBuilder.clear(); if (dbiFlagSet != null) { this.flagSetBuilder.withFlags(dbiFlagSet.getFlags()); @@ -322,8 +322,8 @@ public DbiBuilderStage3 withDbiFlags(final DbiFlagSet dbiFlagSet) { /** * Adds a dbiFlag to those flags already added to this builder by - * {@link DbiBuilderStage3#withDbiFlags(DbiFlags...)}, - * {@link DbiBuilderStage3#withDbiFlags(Collection)} + * {@link DbiBuilderStage3#setDbiFlags(DbiFlags...)}, + * {@link DbiBuilderStage3#setDbiFlags(Collection)} * or {@link DbiBuilderStage3#addDbiFlag(DbiFlags)}. * * @param dbiFlag to add to any existing flags. A null value is a no-op. @@ -336,8 +336,8 @@ public DbiBuilderStage3 addDbiFlag(final DbiFlags dbiFlag) { /** * Adds a dbiFlag to those flags already added to this builder by - * {@link DbiBuilderStage3#withDbiFlags(DbiFlags...)}, - * {@link DbiBuilderStage3#withDbiFlags(Collection)} + * {@link DbiBuilderStage3#setDbiFlags(DbiFlags...)}, + * {@link DbiBuilderStage3#setDbiFlags(Collection)} * or {@link DbiBuilderStage3#addDbiFlag(DbiFlags)}. * * @param dbiFlagSet to add to any existing flags. A null value is a no-op. @@ -367,7 +367,7 @@ public DbiBuilderStage3 addDbiFlags(final DbiFlagSet dbiFlagSet) { * else it needs to be a read/write {@link Txn}. * @return this builder instance. */ - public DbiBuilderStage3 withTxn(final Txn txn) { + public DbiBuilderStage3 setTxn(final Txn txn) { this.txn = Objects.requireNonNull(txn); return this; } diff --git a/src/main/java/org/lmdbjava/DirectBufferProxy.java b/src/main/java/org/lmdbjava/DirectBufferProxy.java index 180eee0a..46bdfd22 100644 --- a/src/main/java/org/lmdbjava/DirectBufferProxy.java +++ b/src/main/java/org/lmdbjava/DirectBufferProxy.java @@ -22,6 +22,7 @@ import static org.lmdbjava.UnsafeAccess.UNSAFE; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.ArrayDeque; import java.util.Comparator; import jnr.ffi.Pointer; @@ -50,6 +51,8 @@ public final class DirectBufferProxy extends BufferProxy { private static final ThreadLocal> BUFFERS = withInitial(() -> new ArrayDeque<>(16)); + private static final ByteOrder NATIVE_ORDER = ByteOrder.nativeOrder(); + private DirectBufferProxy() {} /** @@ -110,16 +113,19 @@ public static int compareAsIntegerKeys(final DirectBuffer o1, final DirectBuffer + ". Lengths must be identical and either 4 or 8 bytes."); } if (len1 == 8) { - final long lw = o1.getLong(0, BIG_ENDIAN); - final long rw = o2.getLong(0, BIG_ENDIAN); + final long lw = o1.getLong(0, NATIVE_ORDER); + final long rw = o2.getLong(0, NATIVE_ORDER); return Long.compareUnsigned(lw, rw); } else if (len1 == 4) { - final int lw = o1.getInt(0, BIG_ENDIAN); - final int rw = o2.getInt(0, BIG_ENDIAN); + final int lw = o1.getInt(0, NATIVE_ORDER); + final int rw = o2.getInt(0, NATIVE_ORDER); return Integer.compareUnsigned(lw, rw); } else { - throw new RuntimeException("Unexpected length len1: " + len1 + ", len2: " + len2 - + ". Lengths must be identical and either 4 or 8 bytes."); + // size_t and int are likely to be 8bytes and 4bytes respectively on 64bit. + // If 32bit then would be 4/2 respectively. + // Short.compareUnsigned is not available in Java8. + // For now just fall back to our standard comparator + return compareLexicographically(o1, o2); } } diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index 8ea08ccb..b8003cbb 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -23,18 +23,20 @@ import static org.lmdbjava.EnvFlags.MDB_RDONLY_ENV; import static org.lmdbjava.Library.LIB; import static org.lmdbjava.Library.RUNTIME; -import static org.lmdbjava.MaskedFlag.isSet; -import static org.lmdbjava.MaskedFlag.mask; import static org.lmdbjava.ResultCodeMapper.checkRc; import java.io.File; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.nio.file.Path; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.Objects; import jnr.ffi.Pointer; import jnr.ffi.byref.IntByReference; import jnr.ffi.byref.PointerByReference; @@ -102,6 +104,8 @@ public static Builder create(final BufferProxy proxy) { } /** + * @deprecated Instead use {@link Env#create()} or {@link Env#create(BufferProxy)} + *

* Opens an environment with a single default database in 0664 mode using the {@link * ByteBufferProxy#PROXY_OPTIMAL}. * @@ -110,6 +114,7 @@ public static Builder create(final BufferProxy proxy) { * @param flags the flags for this new environment * @return env the environment (never null) */ + @Deprecated public static Env open(final File path, final int size, final EnvFlags... flags) { return new Builder<>(PROXY_OPTIMAL) .setMapSize(size * 1_024L * 1_024L) @@ -496,18 +501,6 @@ public Txn txn(final Txn parent) { return new Txn<>(this, parent, proxy, TxnFlagSet.EMPTY); } - /** - * Obtain a transaction with the requested parent and flags. - * - * @param parent parent transaction (may be null if no parent) - * @param flag applicable flag (eg for a reusable, read-only transaction) - * @return a transaction (never null) - */ - public Txn txn(final Txn parent, final TxnFlags flag) { - checkNotClosed(); - return new Txn<>(this, parent, proxy, flag); - } - /** * Obtain a transaction with the requested parent and flags. * @@ -616,6 +609,10 @@ public AlreadyOpenException() { } } + + // -------------------------------------------------------------------------------- + + /** * Builder for configuring and opening Env. * @@ -629,6 +626,8 @@ public static final class Builder { private int maxReaders = MAX_READERS_DEFAULT; private boolean opened; private final BufferProxy proxy; + private int mode = 0664; + private AbstractFlagSet.Builder flagSetBuilder = EnvFlagSet.builder(); Builder(final BufferProxy proxy) { requireNonNull(proxy); @@ -642,10 +641,49 @@ public static final class Builder { * @param mode Unix permissions to set on created files and semaphores * @param flags the flags for this new environment * @return an environment ready for use + * @deprecated Instead use {@link Builder#open(Path)}, {@link Builder#setFilePermissions(int)} + * and {@link Builder#setEnvFlags(EnvFlags...)}. */ + @Deprecated public Env open(final File path, final int mode, final EnvFlags... flags) { - // TODO Use EnvFlagSet and deprecate - // TODO Make setUnixPermissions(int) method on builder. + setFilePermissions(mode); + setEnvFlags(flags); + return open(requireNonNull(path).toPath()); + } + + /** + * Opens the environment. + * + * @param path file system destination + * @return an environment ready for use + * @deprecated Instead use {@link Builder#open(Path)} + */ + @Deprecated + public Env open(final File path) { + return open(requireNonNull(path).toPath()); + } + + /** + * Opens the environment with 0664 mode. + * + * @param path file system destination + * @param flags the flags for this new environment + * @return an environment ready for use + * @deprecated Instead use {@link Builder#open(Path)} and {@link Builder#setEnvFlags(EnvFlags...)}. + */ + @Deprecated + public Env open(final File path, final EnvFlags... flags) { + setEnvFlags(flags); + return open(requireNonNull(path).toPath()); + } + + /** + * Opens the environment. + * + * @param path file system destination + * @return an environment ready for use + */ + public Env open(final Path path) { requireNonNull(path); if (opened) { throw new AlreadyOpenException(); @@ -658,10 +696,10 @@ public Env open(final File path, final int mode, final EnvFlags... flags) { checkRc(LIB.mdb_env_set_mapsize(ptr, mapSize)); checkRc(LIB.mdb_env_set_maxdbs(ptr, maxDbs)); checkRc(LIB.mdb_env_set_maxreaders(ptr, maxReaders)); - final int flagsMask = mask(flags); - final boolean readOnly = isSet(flagsMask, MDB_RDONLY_ENV); - final boolean noSubDir = isSet(flagsMask, MDB_NOSUBDIR); - checkRc(LIB.mdb_env_open(ptr, path.getAbsolutePath(), flagsMask, mode)); + final EnvFlagSet flags = flagSetBuilder.build(); + final boolean readOnly = flags.isSet(MDB_RDONLY_ENV); + final boolean noSubDir = flags.isSet(MDB_NOSUBDIR); + checkRc(LIB.mdb_env_open(ptr, path.toAbsolutePath().toString(), flags.getMask(), mode)); return new Env<>(proxy, ptr, readOnly, noSubDir); } catch (final LmdbNativeException e) { LIB.mdb_env_close(ptr); @@ -670,19 +708,7 @@ public Env open(final File path, final int mode, final EnvFlags... flags) { } /** - * Opens the environment with 0664 mode. - * - * @param path file system destination - * @param flags the flags for this new environment - * @return an environment ready for use - */ - public Env open(final File path, final EnvFlags... flags) { - // TODO make constant - return open(path, 0664, flags); - } - - /** - * Sets the map size. + * Sets the map size in bytes. * * @param mapSize new limit in bytes * @return the builder @@ -725,6 +751,99 @@ public Builder setMaxReaders(final int readers) { this.maxReaders = readers; return this; } + + /** + * Sets the Unix file permissions to use on created files and semaphores, e.g. {@code 0664}. + * If this method is not called, the default of {@code 0664} will be used. + * + * @param mode Unix permissions to set on created files and semaphores + * @return the builder + */ + public Builder setFilePermissions(final int mode) { + if (opened) { + throw new AlreadyOpenException(); + } + this.mode = mode; + return this; + } + + /** + * Sets all the flags used to open this {@link Env}. + * + * @param envFlags The flags to use. + * Clears any existing flags. + * A null value results in no flags being set. + * @return this builder instance. + */ + public Builder setEnvFlags(final Collection envFlags) { + flagSetBuilder.clear(); + if (envFlags != null) { + envFlags.stream() + .filter(Objects::nonNull) + .forEach(envFlags::add); + } + return this; + } + + /** + * Sets all the flags used to open this {@link Env}. + * + * @param envFlags The flags to use. + * Clears any existing flags. + * A null value results in no flags being set. + * @return this builder instance. + */ + public Builder setEnvFlags(final EnvFlags... envFlags) { + flagSetBuilder.clear(); + if (envFlags != null) { + Arrays.stream(envFlags) + .filter(Objects::nonNull) + .forEach(this.flagSetBuilder::setFlag); + } + return this; + } + + /** + * Sets all the flags used to open this {@link Env}. + * + * @param envFlagSet The flags to use. + * Clears any existing flags. + * A null value results in no flags being set. + * @return this builder instance. + */ + public Builder setEnvFlags(final EnvFlagSet envFlagSet) { + flagSetBuilder.clear(); + if (envFlagSet != null) { + this.flagSetBuilder.withFlags(envFlagSet.getFlags()); + } + return this; + } + + /** + * Adds a single {@link EnvFlags} to any existing flags. + * + * @param dbiFlag The flag to add to any existing flags. + * A null value is a no-op. + * @return this builder instance. + */ + public Builder addEnvFlag(final EnvFlags dbiFlag) { + this.flagSetBuilder.setFlag(dbiFlag); + return this; + } + + /** + * Adds a set of {@link EnvFlags} to any existing flags. + * + * @param dbiFlagSet The set of flags to add to any existing flags. + * A null value is a no-op. + * @return this builder instance. + */ + public Builder addEnvFlags(final EnvFlagSet dbiFlagSet) { + if (dbiFlagSet != null) { + flagSetBuilder.setFlags(dbiFlagSet.getFlags()); + } + return this; + } } /** diff --git a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java index 0c83f31e..dc034f7f 100644 --- a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java +++ b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java @@ -39,7 +39,6 @@ import java.nio.ByteOrder; import java.time.Duration; import java.time.Instant; -import java.util.Arrays; import java.util.Comparator; import java.util.HashSet; import java.util.LinkedHashMap; @@ -65,8 +64,14 @@ void buffersMustBeDirect() { () -> { FileUtil.useTempDir( dir -> { - try (Env env = create().setMaxReaders(1).open(dir.toFile())) { - final Dbi db = env.openDbi(DB_1, MDB_CREATE); + try (Env env = create() + .setMaxReaders(1) + .open(dir)) { + final Dbi db = env.buildDbi() + .setDbName(DB_1) + .withDefaultComparator() + .setDbiFlags(MDB_CREATE) + .open(); final ByteBuffer key = allocate(100); key.putInt(1).flip(); final ByteBuffer val = allocate(100); @@ -201,9 +206,9 @@ public void verifyComparators() { .filter(i -> i >= 0) .limit(5_000_000) .toArray(); - System.out.println("stats: " + Arrays.stream(values) - .summaryStatistics() - .toString()); +// System.out.println("stats: " + Arrays.stream(values) +// .summaryStatistics() +// .toString()); final LinkedHashMap> comparators = new LinkedHashMap<>(); comparators.put("compareAsIntegerKeys", ByteBufferProxy.AbstractByteBufferProxy::compareAsIntegerKeys); diff --git a/src/test/java/org/lmdbjava/ComparatorIntegerKeyTest.java b/src/test/java/org/lmdbjava/ComparatorIntegerKeyTest.java new file mode 100644 index 00000000..5b9e0761 --- /dev/null +++ b/src/test/java/org/lmdbjava/ComparatorIntegerKeyTest.java @@ -0,0 +1,369 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.lmdbjava; + +import static io.netty.buffer.PooledByteBufAllocator.DEFAULT; +import static org.assertj.core.api.Assertions.assertThat; +import static org.lmdbjava.ByteBufProxy.PROXY_NETTY; +import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; +import static org.lmdbjava.ComparatorTest.ComparatorResult.EQUAL_TO; +import static org.lmdbjava.ComparatorTest.ComparatorResult.GREATER_THAN; +import static org.lmdbjava.ComparatorTest.ComparatorResult.LESS_THAN; +import static org.lmdbjava.ComparatorTest.ComparatorResult.get; +import static org.lmdbjava.DirectBufferProxy.PROXY_DB; + +import io.netty.buffer.ByteBuf; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Random; +import java.util.stream.Stream; +import org.agrona.DirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * Tests comparator functions are consistent across buffers. + */ +public final class ComparatorIntegerKeyTest { + + static Stream comparatorProvider() { + return Stream.of( + Arguments.argumentSet("LongRunner", new DirectBufferRunner()), + Arguments.argumentSet("DirectBufferRunner", new DirectBufferRunner()), + Arguments.argumentSet("ByteBufferRunner", new ByteBufferRunner()), + Arguments.argumentSet("NettyRunner", new NettyRunner())); + } + + private static byte[] buffer(final int... bytes) { + final byte[] array = new byte[bytes.length]; + for (int i = 0; i < bytes.length; i++) { + array[i] = (byte) bytes[i]; + } + return array; + } + + @ParameterizedTest + @MethodSource("comparatorProvider") + void testLong(final ComparatorRunner comparator) { + + assertThat(get(comparator.compare(0L, 0L))).isEqualTo(EQUAL_TO); + assertThat(get(comparator.compare(Long.MAX_VALUE, Long.MAX_VALUE))).isEqualTo(EQUAL_TO); + + assertThat(get(comparator.compare(0L, 1L))).isEqualTo(LESS_THAN); + assertThat(get(comparator.compare(0L, Long.MAX_VALUE))).isEqualTo(LESS_THAN); + assertThat(get(comparator.compare(0L, 10L))).isEqualTo(LESS_THAN); + assertThat(get(comparator.compare(10L, 100L))).isEqualTo(LESS_THAN); + assertThat(get(comparator.compare(10L, 100L))).isEqualTo(LESS_THAN); + assertThat(get(comparator.compare(10L, 1000L))).isEqualTo(LESS_THAN); + + assertThat(get(comparator.compare(1L, 0L))).isEqualTo(GREATER_THAN); + assertThat(get(comparator.compare(Long.MAX_VALUE, 0L))).isEqualTo(GREATER_THAN); + } + + @ParameterizedTest + @MethodSource("comparatorProvider") + void testInt(final ComparatorRunner comparator) { + + assertThat(get(comparator.compare(0, 0))).isEqualTo(EQUAL_TO); + assertThat(get(comparator.compare(Integer.MAX_VALUE, Integer.MAX_VALUE))).isEqualTo(EQUAL_TO); + + assertThat(get(comparator.compare(0, 1))).isEqualTo(LESS_THAN); + assertThat(get(comparator.compare(0, Integer.MAX_VALUE))).isEqualTo(LESS_THAN); + assertThat(get(comparator.compare(0, 10))).isEqualTo(LESS_THAN); + assertThat(get(comparator.compare(10, 100))).isEqualTo(LESS_THAN); + assertThat(get(comparator.compare(10, 100))).isEqualTo(LESS_THAN); + assertThat(get(comparator.compare(10, 1000))).isEqualTo(LESS_THAN); + + assertThat(get(comparator.compare(1, 0))).isEqualTo(GREATER_THAN); + assertThat(get(comparator.compare(Integer.MAX_VALUE, 0))).isEqualTo(GREATER_THAN); + } + + @Test + void testRandomLong() { + final Random random = new Random(3239480); + final Map nameToRunnerMap = new LinkedHashMap<>(); + nameToRunnerMap.put("DirectBufferRunner", new DirectBufferRunner()); + nameToRunnerMap.put("ByteBufferRunner", new ByteBufferRunner()); + nameToRunnerMap.put("NettyRunner", new NettyRunner()); + + // 5mil random longs to compare + final long[] values = random.longs() + .filter(i -> i >= 0) + .limit(5_000_000) + .toArray(); + + for (int i = 1; i < values.length; i++) { + final long long1 = values[i - 1]; + final long long2 = values[i]; + for (Map.Entry entry : nameToRunnerMap.entrySet()) { + final String name = entry.getKey(); + final ComparatorRunner runner = entry.getValue(); + // Make sure the comparator under test gives the same outcome as just comparing two longs + final ComparatorTest.ComparatorResult result = get(runner.compare(long1, long2)); + final ComparatorTest.ComparatorResult expectedResult = get(Long.compare(long1, long2)); + + assertThat(result) + .withFailMessage(() -> "Compare mismatch for " + name + " - long1: " + long1 + + ", long2: " + long2 + + ", expected: " + expectedResult + + ", actual: " + result) + .isEqualTo(expectedResult); + + final ComparatorTest.ComparatorResult result2 = get(runner.compare(long2, long1)); + final ComparatorTest.ComparatorResult expectedResult2 = expectedResult.opposite(); + + assertThat(result) + .withFailMessage(() -> "Compare mismatch for " + name + " - long2: " + long2 + + ", long1: " + long1 + + ", expected2: " + expectedResult2 + + ", actual2: " + result2) + .isEqualTo(expectedResult); + } + } + } + + @Test + void testRandomInt() { + final Random random = new Random(3239480); + final Map nameToRunnerMap = new LinkedHashMap<>(); + nameToRunnerMap.put("DirectBufferRunner", new DirectBufferRunner()); + nameToRunnerMap.put("ByteBufferRunner", new ByteBufferRunner()); + nameToRunnerMap.put("NettyRunner", new NettyRunner()); + + // 5mil random ints to compare + final int[] values = random.ints() + .filter(i -> i >= 0) + .limit(5_000_000) + .toArray(); + + for (int i = 1; i < values.length; i++) { + final int int1 = values[i - 1]; + final int int2 = values[i]; + for (Map.Entry entry : nameToRunnerMap.entrySet()) { + final String name = entry.getKey(); + final ComparatorRunner runner = entry.getValue(); + // Make sure the comparator under test gives the same outcome as just comparing two ints + final ComparatorTest.ComparatorResult result = get(runner.compare(int1, int2)); + final ComparatorTest.ComparatorResult expectedResult = get(Integer.compare(int1, int2)); + + assertThat(result) + .withFailMessage(() -> "Compare mismatch for " + name + " - int1: " + int1 + + ", int2: " + int2 + + ", expected: " + expectedResult + + ", actual: " + result) + .isEqualTo(expectedResult); + + final ComparatorTest.ComparatorResult result2 = get(runner.compare(int2, int1)); + final ComparatorTest.ComparatorResult expectedResult2 = expectedResult.opposite(); + + assertThat(result) + .withFailMessage(() -> "Compare mismatch for " + name + " - int2: " + int2 + + ", int1: " + int1 + + ", expected2: " + expectedResult2 + + ", actual2: " + result2) + .isEqualTo(expectedResult); + } + } + } + + + // -------------------------------------------------------------------------------- + + + /** + * Tests {@link ByteBufferProxy}. + */ + private static final class ByteBufferRunner implements ComparatorRunner { + + private static final Comparator COMPARATOR = PROXY_OPTIMAL.getComparator(DbiFlags.MDB_INTEGERKEY); + + @Override + public int compare(long long1, long long2) { + // Convert arrays to buffers that are larger than the array, with + // limit set at the array length. One buffer bigger than the other. + ByteBuffer o1b = longToBuffer(long1, Long.BYTES * 3); + ByteBuffer o2b = longToBuffer(long2, Long.BYTES * 2); + final int result = COMPARATOR.compare(o1b, o2b); + + // Now swap which buffer is bigger + o1b = longToBuffer(long1, Long.BYTES * 2); + o2b = longToBuffer(long2, Long.BYTES * 3); + final int result2 = COMPARATOR.compare(o1b, o2b); + + assertThat(result2).isEqualTo(result); + + // Now try with buffers sized to the array. + o1b = longToBuffer(long1, Long.BYTES); + o2b = longToBuffer(long2, Long.BYTES); + final int result3 = COMPARATOR.compare(o1b, o2b); + + assertThat(result3).isEqualTo(result); + return result; + } + + @Override + public int compare(int int1, int int2) { + // Convert arrays to buffers that are larger than the array, with + // limit set at the array length. One buffer bigger than the other. + ByteBuffer o1b = intToBuffer(int1, Integer.BYTES * 3); + ByteBuffer o2b = intToBuffer(int2, Integer.BYTES * 2); + final int result = COMPARATOR.compare(o1b, o2b); + + // Now swap which buffer is bigger + o1b = intToBuffer(int1, Integer.BYTES * 2); + o2b = intToBuffer(int2, Integer.BYTES * 3); + final int result2 = COMPARATOR.compare(o1b, o2b); + + assertThat(result2).isEqualTo(result); + + // Now try with buffers sized to the array. + o1b = intToBuffer(int1, Integer.BYTES); + o2b = intToBuffer(int2, Integer.BYTES); + final int result3 = COMPARATOR.compare(o1b, o2b); + + assertThat(result3).isEqualTo(result); + return result; + } + + private ByteBuffer longToBuffer(final long val, final int bufferCapacity) { + final ByteBuffer byteBuffer = ByteBuffer.allocate(bufferCapacity); + byteBuffer.order(ByteOrder.nativeOrder()); + byteBuffer.putLong(0, val); + byteBuffer.limit(Long.BYTES); + byteBuffer.position(0); + return byteBuffer; + } + + private ByteBuffer intToBuffer(final int val, final int bufferCapacity) { + final ByteBuffer byteBuffer = ByteBuffer.allocate(bufferCapacity); + byteBuffer.order(ByteOrder.nativeOrder()); + byteBuffer.putInt(0, val); + byteBuffer.limit(Integer.BYTES); + byteBuffer.position(0); + return byteBuffer; + } + } + + + // -------------------------------------------------------------------------------- + + + /** + * Tests {@link DirectBufferProxy}. + */ + private static final class DirectBufferRunner implements ComparatorRunner { + private static final Comparator COMPARATOR = PROXY_DB.getComparator(DbiFlags.MDB_INTEGERKEY); + + @Override + public int compare(long long1, long long2) { + final UnsafeBuffer o1b = new UnsafeBuffer(new byte[Long.BYTES]); + final UnsafeBuffer o2b = new UnsafeBuffer(new byte[Long.BYTES]); + o1b.putLong(0, long1, ByteOrder.nativeOrder()); + o2b.putLong(0, long2, ByteOrder.nativeOrder()); + return COMPARATOR.compare(o1b, o2b); + } + + @Override + public int compare(int int1, int int2) { + final UnsafeBuffer o1b = new UnsafeBuffer(new byte[Integer.BYTES]); + final UnsafeBuffer o2b = new UnsafeBuffer(new byte[Integer.BYTES]); + o1b.putInt(0, int1, ByteOrder.nativeOrder()); + o2b.putInt(0, int2, ByteOrder.nativeOrder()); + return COMPARATOR.compare(o1b, o2b); + } + } + + /** + * Tests {@link ByteBufProxy}. + */ + private static final class NettyRunner implements ComparatorRunner { + + private static final Comparator COMPARATOR = PROXY_NETTY.getComparator(DbiFlags.MDB_INTEGERKEY); + + @Override + public int compare(long long1, long long2) { + final ByteBuf o1b = DEFAULT.directBuffer(Long.BYTES); + final ByteBuf o2b = DEFAULT.directBuffer(Long.BYTES); + if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) { + o1b.writeLongLE(long1); + o2b.writeLongLE(long2); + } else { + o1b.writeLong(long1); + o2b.writeLong(long2); + } + o1b.resetReaderIndex(); + o2b.resetReaderIndex(); + final int res = COMPARATOR.compare(o1b, o2b); + o1b.release(); + o2b.release(); + return res; + } + + @Override + public int compare(int int1, int int2) { + final ByteBuf o1b = DEFAULT.directBuffer(Integer.BYTES); + final ByteBuf o2b = DEFAULT.directBuffer(Integer.BYTES); + if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) { + o1b.writeIntLE(int1); + o2b.writeIntLE(int2); + } else { + o1b.writeInt(int1); + o2b.writeInt(int2); + } + o1b.resetReaderIndex(); + o2b.resetReaderIndex(); + final int res = COMPARATOR.compare(o1b, o2b); + o1b.release(); + o2b.release(); + return res; + } + } + + + // -------------------------------------------------------------------------------- + + + /** + * Interface that can test a {@link BufferProxy} compare method. + */ + private interface ComparatorRunner { + + /** + * Write the two longs to a buffer using native order and compare the resulting buffers. + * + * @param long1 lhs value + * @param long2 rhs value + * @return as per {@link Comparable} + */ + int compare(final long long1, final long long2); + + /** + * Write the two int to a buffer using native order and compare the resulting buffers. + * + * @param int1 lhs value + * @param int2 rhs value + * @return as per {@link Comparable} + */ + int compare(final int int1, final int int2); + } +} diff --git a/src/test/java/org/lmdbjava/ComparatorTest.java b/src/test/java/org/lmdbjava/ComparatorTest.java index bc7ddefe..1d4ed4c8 100644 --- a/src/test/java/org/lmdbjava/ComparatorTest.java +++ b/src/test/java/org/lmdbjava/ComparatorTest.java @@ -259,6 +259,16 @@ static ComparatorResult get(final int comparatorResult) { } return comparatorResult < 0 ? LESS_THAN : GREATER_THAN; } + + ComparatorResult opposite() { + if (this == LESS_THAN) { + return GREATER_THAN; + } else if (this == GREATER_THAN) { + return LESS_THAN; + } else { + return EQUAL_TO; + } + } } /** Interface that can test a {@link BufferProxy} compare method. */ diff --git a/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java b/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java index c55687c4..393065a3 100644 --- a/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java @@ -191,11 +191,11 @@ private void populateDatabase(final Dbi dbi) { try (Txn txn = env.txnRead(); CursorIterable c = dbi.iterate(txn)) { - for (final KeyVal kv : c) { - System.out.print(getNativeInt(kv.key()) + " => " + kv.val().getInt()); - System.out.print(", "); - } - System.out.println(); +// for (final KeyVal kv : c) { +// System.out.print(getNativeInt(kv.key()) + " => " + kv.val().getInt()); +// System.out.print(", "); +// } +// System.out.println(); } } @@ -480,14 +480,14 @@ private void verify(final KeyRange range, .collect(Collectors.toList()); final List results = new ArrayList<>(); - System.out.println(rangeToString(range) + ", expected: " + expectedValues); +// System.out.println(rangeToString(range) + ", expected: " + expectedValues); try (Txn txn = env.txnRead(); CursorIterable c = dbi.iterate(txn, range)) { for (final KeyVal kv : c) { final int key = getNativeInt(kv.key()); final int val = kv.val().getInt(); - System.out.println(key + " => " + val); +// System.out.println(key + " => " + val); results.add(val); assertThat(val).satisfiesAnyOf( v -> assertThat(v).isEqualTo((key * 10) + 1), @@ -544,27 +544,27 @@ public Stream provideArguments(ParameterDeclarations parame ExtensionContext context) throws Exception { final DbiFactory defaultComparatorDb = new DbiFactory("defaultComparator", env -> env.buildDbi() - .withDbName(DB_1) + .setDbName(DB_1) .withDefaultComparator() - .withDbiFlags(DBI_FLAGS) + .setDbiFlags(DBI_FLAGS) .open()); final DbiFactory nativeComparatorDb = new DbiFactory("nativeComparator", env -> env.buildDbi() - .withDbName(DB_2) + .setDbName(DB_2) .withNativeComparator() - .withDbiFlags(DBI_FLAGS) + .setDbiFlags(DBI_FLAGS) .open()); final DbiFactory callbackComparatorDb = new DbiFactory("callbackComparator", env -> env.buildDbi() - .withDbName(DB_3) + .setDbName(DB_3) .withCallbackComparator(buildComparator()) - .withDbiFlags(DBI_FLAGS) + .setDbiFlags(DBI_FLAGS) .open()); final DbiFactory iteratorComparatorDb = new DbiFactory("iteratorComparator", env -> env.buildDbi() - .withDbName(DB_4) + .setDbName(DB_4) .withIteratorComparator(buildComparator()) - .withDbiFlags(DBI_FLAGS) + .setDbiFlags(DBI_FLAGS) .open()); return Stream.of( defaultComparatorDb, diff --git a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java index 21d34d30..aa23235a 100644 --- a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java @@ -20,7 +20,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.DbiFlags.MDB_INTEGERKEY; -import static org.lmdbjava.Env.create; import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; import static org.lmdbjava.KeyRange.all; import static org.lmdbjava.KeyRange.allBackward; @@ -45,7 +44,6 @@ import static org.lmdbjava.TestUtils.DB_2; import static org.lmdbjava.TestUtils.DB_3; import static org.lmdbjava.TestUtils.DB_4; -import static org.lmdbjava.TestUtils.POSIX_MODE; import static org.lmdbjava.TestUtils.bb; import static org.lmdbjava.TestUtils.bbNative; import static org.lmdbjava.TestUtils.getNativeInt; @@ -101,16 +99,16 @@ public final class CursorIterableIntegerKeyTest { @Parameter public DbiFactory dbiFactory; - @BeforeEach public void before() throws IOException { file = FileUtil.createTempFile(); final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; - env = create(bufferProxy) + env = Env.create(bufferProxy) .setMapSize(KIBIBYTES.toBytes(256)) .setMaxReaders(1) .setMaxDbs(3) - .open(file.toFile(), POSIX_MODE, MDB_NOSUBDIR); + .setEnvFlags(MDB_NOSUBDIR) + .open(file); populateTestDataList(); } @@ -129,7 +127,7 @@ public void testNumericOrderLong() { final Cursor c = dbi.openCursor(txn); long i = 1; while (true) { - System.out.println("putting " + i); +// System.out.println("putting " + i); c.put(bbNative(i), bb(i + "-long")); final long i2 = i * 10; if (i2 < i) { @@ -176,7 +174,7 @@ public void testNumericOrderInt() { final Cursor c = dbi.openCursor(txn); int i = 1; while (true) { - System.out.println("putting " + i); +// System.out.println("putting " + i); c.put(bbNative(i), bb(i + "-int")); final int i2 = i * 10; if (i2 < i) { @@ -221,7 +219,7 @@ public void testIntegerKeyKeySize() { long maxIntAsLong = Integer.MAX_VALUE; try (Txn txn = env.txnWrite()) { - System.out.println("Flags: " + db.listFlags(txn)); +// System.out.println("Flags: " + db.listFlags(txn)); int val = 0; db.put(txn, bbNative(0L), bb("val_" + ++val)); db.put(txn, bbNative(10L), bb("val_" + ++val)); @@ -243,7 +241,7 @@ public void testIntegerKeyKeySize() { final String val = getString(keyVal.val()); final long key = getNativeLong(keyVal.key()); final int remaining = keyVal.key().remaining(); - System.out.println("key: " + key + ", val: " + val + ", remaining: " + remaining); +// System.out.println("key: " + key + ", val: " + val + ", remaining: " + remaining); } } } @@ -603,29 +601,29 @@ public Stream provideArguments(ParameterDeclarations parame ExtensionContext context) throws Exception { final DbiFactory defaultComparatorDb = new DbiFactory("defaultComparator", env -> env.buildDbi() - .withDbName(DB_1) + .setDbName(DB_1) .withDefaultComparator() - .withDbiFlags(DBI_FLAGS) + .setDbiFlags(DBI_FLAGS) .open()); final DbiFactory nativeComparatorDb = new DbiFactory("nativeComparator", env -> env.buildDbi() - .withDbName(DB_2) + .setDbName(DB_2) .withNativeComparator() - .withDbiFlags(DBI_FLAGS) + .setDbiFlags(DBI_FLAGS) .open()); final Comparator comparator = buildComparator(); final DbiFactory callbackComparatorDb = new DbiFactory("callbackComparator", env -> env.buildDbi() - .withDbName(DB_3) + .setDbName(DB_3) .withCallbackComparator(comparator) - .withDbiFlags(DBI_FLAGS) + .setDbiFlags(DBI_FLAGS) .open()); final DbiFactory iteratorComparatorDb = new DbiFactory("iteratorComparator", env -> env.buildDbi() - .withDbName(DB_4) + .setDbName(DB_4) .withIteratorComparator(comparator) - .withDbiFlags(DBI_FLAGS) + .setDbiFlags(DBI_FLAGS) .open()); return Stream.of( defaultComparatorDb, diff --git a/src/test/java/org/lmdbjava/CursorIterablePerfTest.java b/src/test/java/org/lmdbjava/CursorIterablePerfTest.java index 6bf1ee71..008695fd 100644 --- a/src/test/java/org/lmdbjava/CursorIterablePerfTest.java +++ b/src/test/java/org/lmdbjava/CursorIterablePerfTest.java @@ -65,22 +65,22 @@ public void before() throws IOException { final DbiFlagSet dbiFlagSet = MDB_CREATE; // Use a java comparator for start/stop keys only dbJavaComparator = env.buildDbi() - .withDbName("JavaComparator") + .setDbName("JavaComparator") .withDefaultComparator() - .withDbiFlags(dbiFlagSet) + .setDbiFlags(dbiFlagSet) .open(); // Use LMDB comparator for start/stop keys dbLmdbComparator = env.buildDbi() - .withDbName("LmdbComparator") + .setDbName("LmdbComparator") .withNativeComparator() - .withDbiFlags(dbiFlagSet) + .setDbiFlags(dbiFlagSet) .open(); // Use a java comparator for start/stop keys and as a callback comparator dbCallbackComparator = env.buildDbi() - .withDbName("CallBackComparator") + .setDbName("CallBackComparator") .withCallbackComparator(bufferProxy.getComparator(dbiFlagSet)) - .withDbiFlags(dbiFlagSet) + .setDbiFlags(dbiFlagSet) .open(); dbs.add(dbJavaComparator); diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index 30894a9c..0286fa88 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -46,7 +46,6 @@ import static org.lmdbjava.TestUtils.DB_2; import static org.lmdbjava.TestUtils.DB_3; import static org.lmdbjava.TestUtils.DB_4; -import static org.lmdbjava.TestUtils.POSIX_MODE; import static org.lmdbjava.TestUtils.bb; import com.google.common.primitives.UnsignedBytes; @@ -95,42 +94,6 @@ public final class CursorIterableTest { @Parameter public DbiFactory dbiFactory; -// ArgumentsSource - -// @Parameterized.Parameters(name = "{index}: dbi: {0}") - -// public static Object[] data() { -// final DbiFactory defaultComparatorDb = new DbiFactory("defaultComparator", env -> -// env.buildDbi() -// .withDbName(DB_1) -// .withDefaultComparator() -// .withDbiFlags(DBI_FLAGS) -// .open()); -// final DbiFactory nativeComparatorDb = new DbiFactory("nativeComparator", env -> -// env.buildDbi() -// .withDbName(DB_2) -// .withNativeComparator() -// .withDbiFlags(DBI_FLAGS) -// .open()); -// final DbiFactory callbackComparatorDb = new DbiFactory("callbackComparator", env -> -// env.buildDbi() -// .withDbName(DB_3) -// .withCallbackComparator(BUFFER_PROXY.getComparator(DBI_FLAGS)) -// .withDbiFlags(DBI_FLAGS) -// .open()); -// final DbiFactory iteratorComparatorDb = new DbiFactory("iteratorComparator", env -> -// env.buildDbi() -// .withDbName(DB_4) -// .withIteratorComparator(BUFFER_PROXY.getComparator(DBI_FLAGS)) -// .withDbiFlags(DBI_FLAGS) -// .open()); -// return new Object[]{ -// defaultComparatorDb, -// nativeComparatorDb, -// callbackComparatorDb, -// iteratorComparatorDb}; -// } - @BeforeEach void beforeEach() { file = FileUtil.createTempFile(); @@ -139,7 +102,8 @@ void beforeEach() { .setMapSize(KIBIBYTES.toBytes(256)) .setMaxReaders(1) .setMaxDbs(3) - .open(file.toFile(), POSIX_MODE, MDB_NOSUBDIR); + .setEnvFlags(MDB_NOSUBDIR) + .open(file); populateTestDataList(); } @@ -571,27 +535,27 @@ public Stream provideArguments(ParameterDeclarations parame ExtensionContext context) throws Exception { final DbiFactory defaultComparatorDb = new DbiFactory("defaultComparator", env -> env.buildDbi() - .withDbName(DB_1) + .setDbName(DB_1) .withDefaultComparator() - .withDbiFlags(DBI_FLAGS) + .setDbiFlags(DBI_FLAGS) .open()); final DbiFactory nativeComparatorDb = new DbiFactory("nativeComparator", env -> env.buildDbi() - .withDbName(DB_2) + .setDbName(DB_2) .withNativeComparator() - .withDbiFlags(DBI_FLAGS) + .setDbiFlags(DBI_FLAGS) .open()); final DbiFactory callbackComparatorDb = new DbiFactory("callbackComparator", env -> env.buildDbi() - .withDbName(DB_3) + .setDbName(DB_3) .withCallbackComparator(BUFFER_PROXY.getComparator(DBI_FLAGS)) - .withDbiFlags(DBI_FLAGS) + .setDbiFlags(DBI_FLAGS) .open()); final DbiFactory iteratorComparatorDb = new DbiFactory("iteratorComparator", env -> env.buildDbi() - .withDbName(DB_4) + .setDbName(DB_4) .withIteratorComparator(BUFFER_PROXY.getComparator(DBI_FLAGS)) - .withDbiFlags(DBI_FLAGS) + .setDbiFlags(DBI_FLAGS) .open()); return Stream.of( defaultComparatorDb, diff --git a/src/test/java/org/lmdbjava/DbiBuilderTest.java b/src/test/java/org/lmdbjava/DbiBuilderTest.java index fa0a4792..25e622bf 100644 --- a/src/test/java/org/lmdbjava/DbiBuilderTest.java +++ b/src/test/java/org/lmdbjava/DbiBuilderTest.java @@ -20,11 +20,15 @@ import static org.lmdbjava.Env.create; import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; import static org.lmdbjava.TestUtils.bb; +import static org.lmdbjava.TestUtils.getString; -import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -35,18 +39,20 @@ public class DbiBuilderTest { private Env env; @BeforeEach - public void before() throws IOException { + public void before() { file = FileUtil.createTempFile(); env = create() .setMapSize(MEBIBYTES.toBytes(64)) .setMaxReaders(2) .setMaxDbs(2) - .open(file.toFile(), MDB_NOSUBDIR); + .setEnvFlags(MDB_NOSUBDIR) + .open(file); } @AfterEach public void after() { env.close(); + FileUtil.delete(file); } @Test @@ -54,7 +60,7 @@ public void unnamed() { final Dbi dbi = env.buildDbi() .withoutDbName() .withDefaultComparator() - .withDbiFlags(DbiFlags.MDB_CREATE) + .setDbiFlags(DbiFlags.MDB_CREATE) .open(); assertThat(dbi.getName()).isNull(); assertThat(dbi.getNameAsString()).isEmpty(); @@ -65,15 +71,120 @@ public void unnamed() { @Test public void named() { final Dbi dbi = env.buildDbi() - .withDbName("foo") + .setDbName("foo") .withDefaultComparator() - .withDbiFlags(DbiFlags.MDB_CREATE) + .setDbiFlags(DbiFlags.MDB_CREATE) .open(); assertPutAndGet(dbi); assertThat(env.getDbiNames()).hasSize(1); - assertThat(new String(env.getDbiNames().get(0), StandardCharsets.UTF_8)).isEqualTo("foo"); + assertThat(new String(env.getDbiNames().get(0), StandardCharsets.UTF_8)) + .isEqualTo("foo"); + assertThat(dbi.getNameAsString()) + .isEqualTo("foo"); + assertThat(dbi.getNameAsString(StandardCharsets.UTF_8)) + .isEqualTo("foo"); + } + + @Test + public void named2() { + final Dbi dbi = env.buildDbi() + .setDbName("foo".getBytes(StandardCharsets.US_ASCII)) + .withDefaultComparator() + .setDbiFlags(DbiFlags.MDB_CREATE) + .open(); + + assertPutAndGet(dbi); + + assertThat(env.getDbiNames()).hasSize(1); + assertThat(new String(env.getDbiNames().get(0), StandardCharsets.US_ASCII)) + .isEqualTo("foo"); + assertThat(dbi.getNameAsString()) + .isEqualTo("foo"); + assertThat(dbi.getNameAsString(StandardCharsets.US_ASCII)) + .isEqualTo("foo"); + } + + @Test + public void nativeComparator() { + final Dbi dbi = env.buildDbi() + .setDbName("foo") + .withNativeComparator() + .addDbiFlags(DbiFlags.MDB_CREATE) + .open(); + + assertPutAndGet(dbi); + assertThat(env.getDbiNames()).hasSize(1); + } + + @Test + public void callback() { + final Comparator proxyOptimal = ByteBufferProxy.PROXY_OPTIMAL.getComparator(); + // Compare on key length, falling back to default + final Comparator comparator = (o1, o2) -> { + final int res = Integer.compare(o1.remaining(), o2.remaining()); + if (res == 0) { + return proxyOptimal.compare(o1, o2); + } else { + return res; + } + }; + + final Dbi dbi = env.buildDbi() + .setDbName("foo") + .withCallbackComparator(comparator) + .addDbiFlags(DbiFlags.MDB_CREATE) + .open(); + + TestUtils.doWithWriteTxn(env, txn -> { + dbi.put(txn, bb("fox"), bb("val_1")); + dbi.put(txn, bb("rabbit"), bb("val_2")); + dbi.put(txn, bb("deer"), bb("val_3")); + dbi.put(txn, bb("badger"), bb("val_4")); + txn.commit(); + }); + + final List keys = new ArrayList<>(); + TestUtils.doWithReadTxn(env, txn -> { + try (CursorIterable cursorIterable = dbi.iterate(txn)) { + final Iterator> iterator = cursorIterable.iterator(); + iterator.forEachRemaining(keyVal -> { + keys.add(getString(keyVal.key())); + }); + } + }); + assertThat(keys).containsExactly( + "fox", + "deer", + "badger", + "rabbit"); + } + + @Test + public void flags() { + final Dbi dbi = env.buildDbi() + .setDbName("foo") + .withDefaultComparator() + .setDbiFlags(DbiFlags.MDB_DUPSORT, DbiFlags.MDB_DUPFIXED) // Will get overwritten + .setDbiFlags() // clear them + .addDbiFlags(DbiFlags.MDB_CREATE) // Not a dbi flag as far as lmdb is concerned. + .addDbiFlags(DbiFlags.MDB_INTEGERKEY) + .addDbiFlags(DbiFlags.MDB_REVERSEKEY) + .open(); + + assertPutAndGet(dbi); + + assertThat(env.getDbiNames()).hasSize(1); + assertThat(new String(env.getDbiNames().get(0), StandardCharsets.UTF_8)) + .isEqualTo("foo"); + + TestUtils.doWithReadTxn(env, readTxn -> { + assertThat(dbi.listFlags(readTxn)) + .containsExactlyInAnyOrder( + DbiFlags.MDB_INTEGERKEY, + DbiFlags.MDB_REVERSEKEY); + }); } private void assertPutAndGet(Dbi dbi) { diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index c369c193..7302018c 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -110,7 +110,7 @@ void close() { assertThatThrownBy( () -> { final Dbi db = env.buildDbi() - .withDbName(DB_1) + .setDbName(DB_1) .withDefaultComparator() .addDbiFlag(MDB_CREATE) .open(); diff --git a/src/test/java/org/lmdbjava/TestUtils.java b/src/test/java/org/lmdbjava/TestUtils.java index 94ceb3a7..68d988d1 100644 --- a/src/test/java/org/lmdbjava/TestUtils.java +++ b/src/test/java/org/lmdbjava/TestUtils.java @@ -25,6 +25,9 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; import org.agrona.MutableDirectBuffer; import org.agrona.concurrent.UnsafeBuffer; @@ -133,4 +136,36 @@ static ByteBuf nb(final int value) { b.writeInt(value); return b; } + + static void doWithReadTxn(final Env env, final Consumer> work) { + Objects.requireNonNull(env); + Objects.requireNonNull(work); + try (Txn readTxn = env.txnRead()) { + work.accept(readTxn); + } + } + + static R getWithReadTxn(final Env env, final Function, R> work) { + Objects.requireNonNull(env); + Objects.requireNonNull(work); + try (Txn readTxn = env.txnRead()) { + return work.apply(readTxn); + } + } + + static void doWithWriteTxn(final Env env, final Consumer> work) { + Objects.requireNonNull(env); + Objects.requireNonNull(work); + try (Txn readTxn = env.txnWrite()) { + work.accept(readTxn); + } + } + + static R getWithWriteTxn(final Env env, final Function, R> work) { + Objects.requireNonNull(env); + Objects.requireNonNull(work); + try (Txn readTxn = env.txnWrite()) { + return work.apply(readTxn); + } + } } diff --git a/src/test/java/org/lmdbjava/TxnFlagSetTest.java b/src/test/java/org/lmdbjava/TxnFlagSetTest.java index bdaf9320..58f75aa6 100644 --- a/src/test/java/org/lmdbjava/TxnFlagSetTest.java +++ b/src/test/java/org/lmdbjava/TxnFlagSetTest.java @@ -23,57 +23,74 @@ public class TxnFlagSetTest { - @Test - public void testEmpty() { - final TxnFlagSet txnFlagSet = TxnFlagSet.empty(); - assertThat(txnFlagSet.getMask()).isEqualTo(0); - assertThat(txnFlagSet.size()).isEqualTo(0); - assertThat(txnFlagSet.isEmpty()).isEqualTo(true); - assertThat(txnFlagSet.isSet(TxnFlags.MDB_RDONLY_TXN)).isEqualTo(false); - final TxnFlagSet txnFlagSet2 = TxnFlagSet.builder() - .build(); - assertThat(txnFlagSet).isEqualTo(txnFlagSet2); - assertThat(txnFlagSet).isNotEqualTo(TxnFlagSet.of(TxnFlags.MDB_RDONLY_TXN)); - assertThat(txnFlagSet).isNotEqualTo(TxnFlagSet.builder() - .setFlag(TxnFlags.MDB_RDONLY_TXN) - .build()); + @Test + void testSingleEnum() { + final TxnFlagSet txnFlagSet = TxnFlags.MDB_RDONLY_TXN; + assertThat(txnFlagSet.getMask()).isEqualTo(MaskedFlag.mask(TxnFlags.MDB_RDONLY_TXN)); + assertThat(txnFlagSet.size()).isEqualTo(1); + for (TxnFlags flag : txnFlagSet) { + assertThat(txnFlagSet.isSet(flag)).isEqualTo(true); } - @Test - public void testOf() { - final TxnFlags txnFlag = TxnFlags.MDB_RDONLY_TXN; - final TxnFlagSet txnFlagSet = TxnFlagSet.of(txnFlag); - assertThat(txnFlagSet.getMask()).isEqualTo(MaskedFlag.mask(txnFlag)); - assertThat(txnFlagSet.size()).isEqualTo(1); - for (TxnFlags flag : txnFlagSet) { - assertThat(txnFlagSet.isSet(flag)).isEqualTo(true); - } + final TxnFlagSet txnFlagSet2 = TxnFlagSet.builder() + .setFlag(TxnFlags.MDB_RDONLY_TXN) + .build(); + assertThat(txnFlagSet2.getFlags()).containsExactlyElementsOf(txnFlagSet.getFlags()); + assertThat(txnFlagSet.areAnySet(TxnFlags.MDB_RDONLY_TXN)).isTrue(); + assertThat(txnFlagSet.areAnySet(TxnFlagSet.empty())).isFalse(); + } - final TxnFlagSet txnFlagSet2 = TxnFlagSet.builder() - .setFlag(txnFlag) - .build(); - assertThat(txnFlagSet).isEqualTo(txnFlagSet2); + @Test + public void testEmpty() { + final TxnFlagSet txnFlagSet = TxnFlagSet.empty(); + assertThat(txnFlagSet.getMask()).isEqualTo(0); + assertThat(txnFlagSet.size()).isEqualTo(0); + assertThat(txnFlagSet.isEmpty()).isEqualTo(true); + assertThat(txnFlagSet.isSet(TxnFlags.MDB_RDONLY_TXN)).isEqualTo(false); + final TxnFlagSet txnFlagSet2 = TxnFlagSet.builder() + .build(); + assertThat(txnFlagSet).isEqualTo(txnFlagSet2); + assertThat(txnFlagSet).isNotEqualTo(TxnFlagSet.of(TxnFlags.MDB_RDONLY_TXN)); + assertThat(txnFlagSet).isNotEqualTo(TxnFlagSet.builder() + .setFlag(TxnFlags.MDB_RDONLY_TXN) + .build()); + } + + @Test + public void testOf() { + final TxnFlags txnFlag = TxnFlags.MDB_RDONLY_TXN; + final TxnFlagSet txnFlagSet = TxnFlagSet.of(txnFlag); + assertThat(txnFlagSet.getMask()).isEqualTo(MaskedFlag.mask(txnFlag)); + assertThat(txnFlagSet.size()).isEqualTo(1); + for (TxnFlags flag : txnFlagSet) { + assertThat(txnFlagSet.isSet(flag)).isEqualTo(true); } - @Test - public void testBuilder() { - final TxnFlags txnFlag1 = TxnFlags.MDB_RDONLY_TXN; - final TxnFlagSet txnFlagSet = TxnFlagSet.builder() - .setFlag(txnFlag1) - .build(); - assertThat(txnFlagSet.getMask()).isEqualTo(MaskedFlag.mask(txnFlag1)); - assertThat(txnFlagSet.size()).isEqualTo(1); - assertThat(txnFlagSet.isSet(TxnFlags.MDB_RDONLY_TXN)).isEqualTo(true); - for (TxnFlags flag : txnFlagSet) { - assertThat(txnFlagSet.isSet(flag)).isEqualTo(true); - } - final TxnFlagSet txnFlagSet2 = TxnFlagSet.builder() - .withFlags(txnFlag1) - .build(); - final TxnFlagSet txnFlagSet3 = TxnFlagSet.builder() - .withFlags(new HashSet<>(Collections.singletonList(txnFlag1))) - .build(); - assertThat(txnFlagSet).isEqualTo(txnFlagSet2); - assertThat(txnFlagSet).isEqualTo(txnFlagSet3); + final TxnFlagSet txnFlagSet2 = TxnFlagSet.builder() + .setFlag(txnFlag) + .build(); + assertThat(txnFlagSet).isEqualTo(txnFlagSet2); + } + + @Test + public void testBuilder() { + final TxnFlags txnFlag1 = TxnFlags.MDB_RDONLY_TXN; + final TxnFlagSet txnFlagSet = TxnFlagSet.builder() + .setFlag(txnFlag1) + .build(); + assertThat(txnFlagSet.getMask()).isEqualTo(MaskedFlag.mask(txnFlag1)); + assertThat(txnFlagSet.size()).isEqualTo(1); + assertThat(txnFlagSet.isSet(TxnFlags.MDB_RDONLY_TXN)).isEqualTo(true); + for (TxnFlags flag : txnFlagSet) { + assertThat(txnFlagSet.isSet(flag)).isEqualTo(true); } + final TxnFlagSet txnFlagSet2 = TxnFlagSet.builder() + .withFlags(txnFlag1) + .build(); + final TxnFlagSet txnFlagSet3 = TxnFlagSet.builder() + .withFlags(new HashSet<>(Collections.singletonList(txnFlag1))) + .build(); + assertThat(txnFlagSet).isEqualTo(txnFlagSet2); + assertThat(txnFlagSet).isEqualTo(txnFlagSet3); + } } From 7374fde065fd2c71ce5b90ac785a3989a0a92207 Mon Sep 17 00:00:00 2001 From: stroomdev66 Date: Wed, 5 Nov 2025 13:21:08 +0000 Subject: [PATCH 23/90] #267 Fix to establish start key when iterating backwards from a start key using DUPSORT --- .../java/org/lmdbjava/ComparatorTest.java | 35 ++++++++++------- .../java/org/lmdbjava/CursorParamTest.java | 38 +++++++++---------- 2 files changed, 40 insertions(+), 33 deletions(-) diff --git a/src/test/java/org/lmdbjava/ComparatorTest.java b/src/test/java/org/lmdbjava/ComparatorTest.java index a5e010ab..674f4059 100644 --- a/src/test/java/org/lmdbjava/ComparatorTest.java +++ b/src/test/java/org/lmdbjava/ComparatorTest.java @@ -34,9 +34,12 @@ import java.util.stream.Stream; import org.agrona.DirectBuffer; import org.agrona.concurrent.UnsafeBuffer; +import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.support.ParameterDeclarations; /** Tests comparator functions are consistent across buffers. */ public final class ComparatorTest { @@ -54,16 +57,20 @@ public final class ComparatorTest { private static final byte[] LX = buffer(0); private static final byte[] XX = buffer(); - static Stream comparatorProvider() { - return Stream.of( - Arguments.argumentSet("StringRunner", new StringRunner()), - Arguments.argumentSet("DirectBufferRunner", new DirectBufferRunner()), - Arguments.argumentSet("ByteArrayRunner", new ByteArrayRunner()), - Arguments.argumentSet("UnsignedByteArrayRunner", new UnsignedByteArrayRunner()), - Arguments.argumentSet("ByteBufferRunner", new ByteBufferRunner()), - Arguments.argumentSet("NettyRunner", new NettyRunner()), - Arguments.argumentSet("GuavaUnsignedBytes", new GuavaUnsignedBytes()), - Arguments.argumentSet("GuavaSignedBytes", new GuavaSignedBytes())); + static class MyArgumentProvider implements ArgumentsProvider { + @Override + public Stream provideArguments( + ParameterDeclarations parameters, ExtensionContext context) { + return Stream.of( + Arguments.argumentSet("StringRunner", new StringRunner()), + Arguments.argumentSet("DirectBufferRunner", new DirectBufferRunner()), + Arguments.argumentSet("ByteArrayRunner", new ByteArrayRunner()), + Arguments.argumentSet("UnsignedByteArrayRunner", new UnsignedByteArrayRunner()), + Arguments.argumentSet("ByteBufferRunner", new ByteBufferRunner()), + Arguments.argumentSet("NettyRunner", new NettyRunner()), + Arguments.argumentSet("GuavaUnsignedBytes", new GuavaUnsignedBytes()), + Arguments.argumentSet("GuavaSignedBytes", new GuavaSignedBytes())); + } } private static byte[] buffer(final int... bytes) { @@ -75,7 +82,7 @@ private static byte[] buffer(final int... bytes) { } @ParameterizedTest - @MethodSource("comparatorProvider") + @ArgumentsSource(MyArgumentProvider.class) void atLeastOneBufferHasEightBytes(final ComparatorRunner comparator) { assertThat(get(comparator.compare(HLLLLLLL, LLLLLLLL))).isEqualTo(GREATER_THAN); assertThat(get(comparator.compare(LLLLLLLL, HLLLLLLL))).isEqualTo(LESS_THAN); @@ -94,7 +101,7 @@ void atLeastOneBufferHasEightBytes(final ComparatorRunner comparator) { } @ParameterizedTest - @MethodSource("comparatorProvider") + @ArgumentsSource(MyArgumentProvider.class) void buffersOfTwoBytes(final ComparatorRunner comparator) { assertThat(get(comparator.compare(LL, XX))).isEqualTo(GREATER_THAN); assertThat(get(comparator.compare(XX, LL))).isEqualTo(LESS_THAN); @@ -110,7 +117,7 @@ void buffersOfTwoBytes(final ComparatorRunner comparator) { } @ParameterizedTest - @MethodSource("comparatorProvider") + @ArgumentsSource(MyArgumentProvider.class) void equalBuffers(final ComparatorRunner comparator) { assertThat(get(comparator.compare(LL, LL))).isEqualTo(EQUAL_TO); assertThat(get(comparator.compare(HX, HX))).isEqualTo(EQUAL_TO); diff --git a/src/test/java/org/lmdbjava/CursorParamTest.java b/src/test/java/org/lmdbjava/CursorParamTest.java index 7f0bad8a..6a0b01cd 100644 --- a/src/test/java/org/lmdbjava/CursorParamTest.java +++ b/src/test/java/org/lmdbjava/CursorParamTest.java @@ -32,15 +32,8 @@ import static org.lmdbjava.GetOp.MDB_SET_KEY; import static org.lmdbjava.GetOp.MDB_SET_RANGE; import static org.lmdbjava.PutFlags.MDB_NOOVERWRITE; -import static org.lmdbjava.SeekOp.MDB_FIRST; -import static org.lmdbjava.SeekOp.MDB_LAST; -import static org.lmdbjava.SeekOp.MDB_NEXT; -import static org.lmdbjava.SeekOp.MDB_PREV; -import static org.lmdbjava.TestUtils.DB_1; -import static org.lmdbjava.TestUtils.POSIX_MODE; -import static org.lmdbjava.TestUtils.bb; -import static org.lmdbjava.TestUtils.mdb; -import static org.lmdbjava.TestUtils.nb; +import static org.lmdbjava.SeekOp.*; +import static org.lmdbjava.TestUtils.*; import io.netty.buffer.ByteBuf; import java.nio.ByteBuffer; @@ -48,26 +41,33 @@ import java.util.stream.Stream; import org.agrona.DirectBuffer; import org.agrona.MutableDirectBuffer; +import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.support.ParameterDeclarations; /** Test {@link Cursor} with different buffer implementations. */ public final class CursorParamTest { - static Stream data() { - return Stream.of( - Arguments.argumentSet( - "ByteBufferRunner(PROXY_OPTIMAL)", new ByteBufferRunner(PROXY_OPTIMAL)), - Arguments.argumentSet("ByteBufferRunner(PROXY_SAFE)", new ByteBufferRunner(PROXY_SAFE)), - Arguments.argumentSet("ByteArrayRunner(PROXY_BA)", new ByteArrayRunner(PROXY_BA)), - Arguments.argumentSet("DirectBufferRunner", new DirectBufferRunner()), - Arguments.argumentSet("NettyBufferRunner", new NettyBufferRunner())); + static class MyArgumentProvider implements ArgumentsProvider { + @Override + public Stream provideArguments( + ParameterDeclarations parameters, ExtensionContext context) { + return Stream.of( + Arguments.argumentSet( + "ByteBufferRunner(PROXY_OPTIMAL)", new ByteBufferRunner(PROXY_OPTIMAL)), + Arguments.argumentSet("ByteBufferRunner(PROXY_SAFE)", new ByteBufferRunner(PROXY_SAFE)), + Arguments.argumentSet("ByteArrayRunner(PROXY_BA)", new ByteArrayRunner(PROXY_BA)), + Arguments.argumentSet("DirectBufferRunner", new DirectBufferRunner()), + Arguments.argumentSet("NettyBufferRunner", new NettyBufferRunner())); + } } @ParameterizedTest - @MethodSource("data") + @ArgumentsSource(MyArgumentProvider.class) void execute(final BufferRunner runner, @TempDir final Path tmp) { runner.execute(tmp); } From 787d6d38aa7f161cf2fd8ee3e10d466e69ab20eb Mon Sep 17 00:00:00 2001 From: stroomdev66 Date: Wed, 5 Nov 2025 13:22:45 +0000 Subject: [PATCH 24/90] #267 Fix to establish start key when iterating backwards from a start key using DUPSORT --- src/main/java/org/lmdbjava/MaskedFlag.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/lmdbjava/MaskedFlag.java b/src/main/java/org/lmdbjava/MaskedFlag.java index 58d67d8c..87deb3c9 100644 --- a/src/main/java/org/lmdbjava/MaskedFlag.java +++ b/src/main/java/org/lmdbjava/MaskedFlag.java @@ -17,7 +17,6 @@ import static java.util.Objects.requireNonNull; -import java.util.Arrays; import java.util.Objects; import java.util.function.Predicate; import java.util.stream.Stream; From c289763641e81d0b8c3e94e73725eb3abb36d9f2 Mon Sep 17 00:00:00 2001 From: stroomdev66 Date: Wed, 5 Nov 2025 13:43:06 +0000 Subject: [PATCH 25/90] #267 Fix to establish start key when iterating backwards from a start key using DUPSORT --- .gitignore | 1 - .../java/org/lmdbjava/CursorIterable.java | 6 +- .../java/org/lmdbjava/ComparatorTest.java | 5 +- .../org/lmdbjava/CursorIterableRangeTest.java | 106 +++--------------- .../java/org/lmdbjava/CursorParamTest.java | 11 +- src/test/java/org/lmdbjava/DbiTest.java | 16 ++- src/test/java/org/lmdbjava/EnvTest.java | 6 +- src/test/java/org/lmdbjava/TutorialTest.java | 4 +- 8 files changed, 52 insertions(+), 103 deletions(-) diff --git a/.gitignore b/.gitignore index 44fe1d51..5f3ff273 100644 --- a/.gitignore +++ b/.gitignore @@ -16,5 +16,4 @@ dependency-reduced-pom.xml gpg-sign.json mvn-sync.json secrets.tar -openldap pom.xml.versionsBackup diff --git a/src/main/java/org/lmdbjava/CursorIterable.java b/src/main/java/org/lmdbjava/CursorIterable.java index b6d58e3d..80fd5ef3 100644 --- a/src/main/java/org/lmdbjava/CursorIterable.java +++ b/src/main/java/org/lmdbjava/CursorIterable.java @@ -15,7 +15,11 @@ */ package org.lmdbjava; -import static org.lmdbjava.CursorIterable.State.*; +import static org.lmdbjava.CursorIterable.State.RELEASED; +import static org.lmdbjava.CursorIterable.State.REQUIRES_INITIAL_OP; +import static org.lmdbjava.CursorIterable.State.REQUIRES_ITERATOR_OP; +import static org.lmdbjava.CursorIterable.State.REQUIRES_NEXT_OP; +import static org.lmdbjava.CursorIterable.State.TERMINATED; import static org.lmdbjava.GetOp.MDB_SET_RANGE; import java.util.Comparator; diff --git a/src/test/java/org/lmdbjava/ComparatorTest.java b/src/test/java/org/lmdbjava/ComparatorTest.java index 674f4059..2fabb3da 100644 --- a/src/test/java/org/lmdbjava/ComparatorTest.java +++ b/src/test/java/org/lmdbjava/ComparatorTest.java @@ -22,7 +22,10 @@ import static org.lmdbjava.ByteArrayProxy.PROXY_BA; import static org.lmdbjava.ByteBufProxy.PROXY_NETTY; import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; -import static org.lmdbjava.ComparatorTest.ComparatorResult.*; +import static org.lmdbjava.ComparatorTest.ComparatorResult.EQUAL_TO; +import static org.lmdbjava.ComparatorTest.ComparatorResult.GREATER_THAN; +import static org.lmdbjava.ComparatorTest.ComparatorResult.LESS_THAN; +import static org.lmdbjava.ComparatorTest.ComparatorResult.get; import static org.lmdbjava.DirectBufferProxy.PROXY_DB; import com.google.common.primitives.SignedBytes; diff --git a/src/test/java/org/lmdbjava/CursorIterableRangeTest.java b/src/test/java/org/lmdbjava/CursorIterableRangeTest.java index dd33e885..0802c047 100644 --- a/src/test/java/org/lmdbjava/CursorIterableRangeTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableRangeTest.java @@ -19,12 +19,22 @@ import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; import static java.nio.ByteBuffer.allocateDirect; import static org.assertj.core.api.Assertions.assertThat; -import static org.lmdbjava.DbiFlags.*; +import static org.lmdbjava.DbiFlags.MDB_CREATE; +import static org.lmdbjava.DbiFlags.MDB_DUPSORT; +import static org.lmdbjava.DbiFlags.MDB_INTEGERKEY; import static org.lmdbjava.Env.create; import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; -import static org.lmdbjava.TestUtils.*; +import static org.lmdbjava.TestUtils.DB_1; +import static org.lmdbjava.TestUtils.POSIX_MODE; +import static org.lmdbjava.TestUtils.bb; -import java.io.*; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; +import java.io.StringWriter; +import java.io.UncheckedIOException; +import java.io.Writer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.file.Path; @@ -231,96 +241,6 @@ private long getLong(final ByteBuffer byteBuffer, final ByteOrder byteOrder) { } } - // - // @Test - // void testSignedComparator() throws IOException { - // test(ByteBuffer::compareTo, true, "testSignedComparator", 1, MDB_CREATE); - // } - // - // @Test - // void testUnsignedComparator() throws IOException { - // test(AbstractByteBufferProxy::compareBuff, false, "testUnsignedComparator", 1, MDB_CREATE); - // } - // - // @Test - // void testSignedComparatorDupsort() throws IOException { - // test(ByteBuffer::compareTo, true, "testSignedComparatorDupsort", 2, MDB_CREATE, - // MDB_DUPSORT); - // } - // - // @Test - // void testUnsignedComparatorDupsort() throws IOException { - // test(AbstractByteBufferProxy::compareBuff, false, "testUnsignedComparatorDupsort", 2, - // MDB_CREATE, MDB_DUPSORT); - // } - // - // private void test(final Comparator comparator, - // final boolean nativeCb, - // final String testName, - // final BiConsumer, Dbi> dbPopulator, - // final DbiFlags... flags) throws IOException { - // final Path dbPath = Files.createTempFile("test", "db"); - // try (final Env env = - // create() - // .setMapSize(KIBIBYTES.toBytes(256)) - // .setMaxReaders(1) - // .setMaxDbs(1) - // .open(dbPath.toFile(), POSIX_MODE, MDB_NOSUBDIR)) { - // final Dbi dbi = env.openDbi(DB_1, comparator, nativeCb, flags); - // dbPopulator.accept(env, dbi); - // - // final File tests = new File("src/test/resources/CursorIterableRangeTest/tests.csv"); - // final File actual = tests.getParentFile().toPath().resolve(testName + ".actual").toFile(); - // final File expected = tests.getParentFile().toPath().resolve(testName + - // ".expected").toFile(); - // final String csv = readFile(tests); - // final String[] parts = csv.split("\n"); - // try (final Writer writer = new FileWriter(actual)) { - // for (final String part : parts) { - // final String[] params = part.split(","); - // final KeyRangeType keyRangeType = KeyRangeType.valueOf(params[0].trim()); - // ByteBuffer start = null; - // ByteBuffer stop = null; - // if (params.length > 1 && params[1].trim().length() > 0) { - // start = bb(Integer.parseInt(params[1].trim())); - // } - // if (params.length > 2 && params[2].trim().length() > 0) { - // stop = bb(Integer.parseInt(params[2].trim())); - // } - // - // for (int i = 0; i < 3; i++) { - // if (params.length > i) { - // writer.append(params[i].trim()); - // } - // writer.append(","); - // } - // - // final KeyRange keyRange = new KeyRange<>(keyRangeType, start, stop); - // try (Txn txn = env.txnRead(); - // CursorIterable c = dbi.iterate(txn, keyRange)) { - // for (final KeyVal kv : c) { - // final int key = kv.key().getInt(); - // final int val = kv.val().getInt(); - // writer.append("["); - // writer.append(String.valueOf(key)); - // writer.append(" "); - // writer.append(String.valueOf(val)); - // writer.append("]"); - // } - // } - // writer.append("\n"); - // } - // } - // - // // Compare files. - // final String act = readFile(actual); - // final String exp = readFile(expected); - // assertThat(act).withFailMessage("Files are not equal").isEqualTo(exp); - // } finally { - // FileUtil.deleteFile(dbPath); - // } - // } - private BiConsumer, Dbi> createBasicDBPopulator() { return (env, dbi) -> { try (Txn txn = env.txnWrite()) { diff --git a/src/test/java/org/lmdbjava/CursorParamTest.java b/src/test/java/org/lmdbjava/CursorParamTest.java index 6a0b01cd..e228e968 100644 --- a/src/test/java/org/lmdbjava/CursorParamTest.java +++ b/src/test/java/org/lmdbjava/CursorParamTest.java @@ -32,8 +32,15 @@ import static org.lmdbjava.GetOp.MDB_SET_KEY; import static org.lmdbjava.GetOp.MDB_SET_RANGE; import static org.lmdbjava.PutFlags.MDB_NOOVERWRITE; -import static org.lmdbjava.SeekOp.*; -import static org.lmdbjava.TestUtils.*; +import static org.lmdbjava.SeekOp.MDB_FIRST; +import static org.lmdbjava.SeekOp.MDB_LAST; +import static org.lmdbjava.SeekOp.MDB_NEXT; +import static org.lmdbjava.SeekOp.MDB_PREV; +import static org.lmdbjava.TestUtils.DB_1; +import static org.lmdbjava.TestUtils.POSIX_MODE; +import static org.lmdbjava.TestUtils.bb; +import static org.lmdbjava.TestUtils.mdb; +import static org.lmdbjava.TestUtils.nb; import io.netty.buffer.ByteBuf; import java.nio.ByteBuffer; diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 2c4a90b7..563215c4 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -29,14 +29,20 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.lmdbjava.ByteArrayProxy.PROXY_BA; import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; -import static org.lmdbjava.DbiFlags.*; +import static org.lmdbjava.DbiFlags.MDB_CREATE; +import static org.lmdbjava.DbiFlags.MDB_DUPSORT; +import static org.lmdbjava.DbiFlags.MDB_INTEGERKEY; +import static org.lmdbjava.DbiFlags.MDB_REVERSEKEY; import static org.lmdbjava.Env.create; import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; import static org.lmdbjava.GetOp.MDB_SET_KEY; import static org.lmdbjava.KeyRange.atMost; import static org.lmdbjava.PutFlags.MDB_NODUPDATA; import static org.lmdbjava.PutFlags.MDB_NOOVERWRITE; -import static org.lmdbjava.TestUtils.*; +import static org.lmdbjava.TestUtils.DB_1; +import static org.lmdbjava.TestUtils.ba; +import static org.lmdbjava.TestUtils.bb; +import static org.lmdbjava.TestUtils.fromBa; import java.nio.ByteBuffer; import java.nio.file.Path; @@ -44,7 +50,11 @@ import java.util.Comparator; import java.util.Iterator; import java.util.List; -import java.util.concurrent.*; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiConsumer; import java.util.function.Function; diff --git a/src/test/java/org/lmdbjava/EnvTest.java b/src/test/java/org/lmdbjava/EnvTest.java index d66aa3a3..6c24c406 100644 --- a/src/test/java/org/lmdbjava/EnvTest.java +++ b/src/test/java/org/lmdbjava/EnvTest.java @@ -40,7 +40,11 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.lmdbjava.Env.*; +import org.lmdbjava.Env.AlreadyClosedException; +import org.lmdbjava.Env.AlreadyOpenException; +import org.lmdbjava.Env.Builder; +import org.lmdbjava.Env.InvalidCopyDestination; +import org.lmdbjava.Env.MapFullException; import org.lmdbjava.Txn.BadReaderLockException; /** Test {@link Env}. */ diff --git a/src/test/java/org/lmdbjava/TutorialTest.java b/src/test/java/org/lmdbjava/TutorialTest.java index 8376fbe7..6af2599b 100644 --- a/src/test/java/org/lmdbjava/TutorialTest.java +++ b/src/test/java/org/lmdbjava/TutorialTest.java @@ -27,7 +27,9 @@ import static org.lmdbjava.DirectBufferProxy.PROXY_DB; import static org.lmdbjava.Env.create; import static org.lmdbjava.GetOp.MDB_SET; -import static org.lmdbjava.SeekOp.*; +import static org.lmdbjava.SeekOp.MDB_FIRST; +import static org.lmdbjava.SeekOp.MDB_LAST; +import static org.lmdbjava.SeekOp.MDB_PREV; import java.nio.ByteBuffer; import java.nio.file.Path; From 1acd9711efb752013b6588e5e3d106474afa5822 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Wed, 5 Nov 2025 16:57:43 +0000 Subject: [PATCH 26/90] Add ComparatorFactory --- src/main/java/org/lmdbjava/DbiBuilder.java | 50 +++++--- .../lmdbjava/ComparatorIntegerKeyTest.java | 109 ++++++++---------- .../CursorIterableIntegerDupTest.java | 6 +- .../CursorIterableIntegerKeyTest.java | 8 +- .../org/lmdbjava/CursorIterablePerfTest.java | 2 +- .../java/org/lmdbjava/CursorIterableTest.java | 11 +- .../java/org/lmdbjava/DbiBuilderTest.java | 2 +- 7 files changed, 96 insertions(+), 92 deletions(-) diff --git a/src/main/java/org/lmdbjava/DbiBuilder.java b/src/main/java/org/lmdbjava/DbiBuilder.java index f94118a7..9cb85616 100644 --- a/src/main/java/org/lmdbjava/DbiBuilder.java +++ b/src/main/java/org/lmdbjava/DbiBuilder.java @@ -49,8 +49,9 @@ public class DbiBuilder { *

* The name will be converted into bytes using {@link StandardCharsets#UTF_8}. *

+ * * @param name The name of the database or null for the unnamed database - * (see also {@link DbiBuilder#withoutDbName()}) + * (see also {@link DbiBuilder#withoutDbName()}) * @return The next builder stage. */ public DbiBuilderStage2 setDbName(final String name) { @@ -63,6 +64,7 @@ public DbiBuilderStage2 setDbName(final String name) { /** * Create the {@link Dbi} with the passed name in byte[] form. + * * @param name The name of the database in byte form. * @return The next builder stage. */ @@ -83,6 +85,7 @@ public DbiBuilderStage2 setDbName(final byte[] name) { *

Note: The 'unnamed database' is used by LMDB to store the names of named databases, with * the database name being the key. Use of the unnamed database is intended for simple applications * with only one database.

+ * * @return The next builder stage. */ public DbiBuilderStage2 withoutDbName() { @@ -102,7 +105,7 @@ public static class DbiBuilderStage2 { private final DbiBuilder dbiBuilder; - private java.util.Comparator customComparator; + private ComparatorFactory comparatorFactory; private ComparatorType comparatorType; private DbiBuilderStage2(final DbiBuilder dbiBuilder) { @@ -130,7 +133,7 @@ private DbiBuilderStage2(final DbiBuilder dbiBuilder) { * If you do not intend to use {@link CursorIterable} then it doesn't matter whether * you choose {@link DbiBuilderStage2#withNativeComparator()}, * {@link DbiBuilderStage2#withDefaultComparator()} or - * {@link DbiBuilderStage2#withIteratorComparator(Comparator)} as these comparators will + * {@link DbiBuilderStage2#withIteratorComparator(ComparatorFactory)} as these comparators will * never be used. *

* @@ -157,7 +160,7 @@ public DbiBuilderStage3 withDefaultComparator() { * If you do not intend to use {@link CursorIterable} then it doesn't matter whether * you choose {@link DbiBuilderStage2#withNativeComparator()}, * {@link DbiBuilderStage2#withDefaultComparator()} or - * {@link DbiBuilderStage2#withIteratorComparator(Comparator)} as these comparators will + * {@link DbiBuilderStage2#withIteratorComparator(ComparatorFactory)} as these comparators will * never be used. *

* @@ -186,11 +189,13 @@ public DbiBuilderStage3 withNativeComparator() { * are stored in the database. *

* - * @param comparator for all key comparison operations. + * @param comparatorFactory A factory to create a comparator. {@link ComparatorFactory#create(DbiFlagSet)} + * will be called once during the initialisation of the {@link Dbi}. It must + * not return null. * @return The next builder stage. */ - public DbiBuilderStage3 withCallbackComparator(final Comparator comparator) { - this.customComparator = Objects.requireNonNull(comparator); + public DbiBuilderStage3 withCallbackComparator(final ComparatorFactory comparatorFactory) { + this.comparatorFactory = Objects.requireNonNull(comparatorFactory); this.comparatorType = ComparatorType.CALLBACK; return new DbiBuilderStage3<>(this); } @@ -215,15 +220,17 @@ public DbiBuilderStage3 withCallbackComparator(final Comparator comparator * If you do not intend to use {@link CursorIterable} then it doesn't matter whether * you choose {@link DbiBuilderStage2#withNativeComparator()}, * {@link DbiBuilderStage2#withDefaultComparator()} or - * {@link DbiBuilderStage2#withIteratorComparator(Comparator)} as these comparators will + * {@link DbiBuilderStage2#withIteratorComparator(ComparatorFactory)} as these comparators will * never be used. *

* - * @param comparator The comparator to use with {@link CursorIterable}. + * @param comparatorFactory The comparator to use with {@link CursorIterable}. + * {@link ComparatorFactory#create(DbiFlagSet)} will be called once during the + * initialisation of the {@link Dbi}. It must not return null. * @return The next builder stage. */ - public DbiBuilderStage3 withIteratorComparator(final Comparator comparator) { - this.customComparator = Objects.requireNonNull(comparator); + public DbiBuilderStage3 withIteratorComparator(final ComparatorFactory comparatorFactory) { + this.comparatorFactory = Objects.requireNonNull(comparatorFactory); this.comparatorType = ComparatorType.ITERATOR; return new DbiBuilderStage3<>(this); } @@ -267,8 +274,8 @@ public DbiBuilderStage3 setDbiFlags(final Collection dbiFlags) { flagSetBuilder.clear(); if (dbiFlags != null) { dbiFlags.stream() - .filter(Objects::nonNull) - .forEach(dbiFlags::add); + .filter(Objects::nonNull) + .forEach(dbiFlags::add); } return this; } @@ -310,7 +317,7 @@ public DbiBuilderStage3 setDbiFlags(final DbiFlags... dbiFlags) { *

* * @param dbiFlagSet to open the database with. - * A null value will just clear all set flags. + * A null value will just clear all set flags. */ public DbiBuilderStage3 setDbiFlags(final DbiFlagSet dbiFlagSet) { flagSetBuilder.clear(); @@ -413,7 +420,9 @@ private Comparator getComparator(final DbiBuilder dbiBuilder, break; case CALLBACK: case ITERATOR: - comparator = dbiBuilderStage2.customComparator; + comparator = Objects.requireNonNull( + dbiBuilderStage2.comparatorFactory.create(dbiFlagSet), + () -> "comparatorFactory returned null"); break; case NATIVE: break; @@ -465,4 +474,15 @@ private enum ComparatorType { ITERATOR, ; } + + + // -------------------------------------------------------------------------------- + + + @FunctionalInterface + public interface ComparatorFactory { + + Comparator create(final DbiFlagSet dbiFlagSet); + + } } diff --git a/src/test/java/org/lmdbjava/ComparatorIntegerKeyTest.java b/src/test/java/org/lmdbjava/ComparatorIntegerKeyTest.java index 5b9e0761..32e0c8c5 100644 --- a/src/test/java/org/lmdbjava/ComparatorIntegerKeyTest.java +++ b/src/test/java/org/lmdbjava/ComparatorIntegerKeyTest.java @@ -30,13 +30,10 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Comparator; -import java.util.LinkedHashMap; -import java.util.Map; import java.util.Random; import java.util.stream.Stream; import org.agrona.DirectBuffer; import org.agrona.concurrent.UnsafeBuffer; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -98,13 +95,10 @@ void testInt(final ComparatorRunner comparator) { assertThat(get(comparator.compare(Integer.MAX_VALUE, 0))).isEqualTo(GREATER_THAN); } - @Test - void testRandomLong() { + @ParameterizedTest + @MethodSource("comparatorProvider") + void testRandomLong(final ComparatorRunner runner) { final Random random = new Random(3239480); - final Map nameToRunnerMap = new LinkedHashMap<>(); - nameToRunnerMap.put("DirectBufferRunner", new DirectBufferRunner()); - nameToRunnerMap.put("ByteBufferRunner", new ByteBufferRunner()); - nameToRunnerMap.put("NettyRunner", new NettyRunner()); // 5mil random longs to compare final long[] values = random.longs() @@ -115,40 +109,33 @@ void testRandomLong() { for (int i = 1; i < values.length; i++) { final long long1 = values[i - 1]; final long long2 = values[i]; - for (Map.Entry entry : nameToRunnerMap.entrySet()) { - final String name = entry.getKey(); - final ComparatorRunner runner = entry.getValue(); - // Make sure the comparator under test gives the same outcome as just comparing two longs - final ComparatorTest.ComparatorResult result = get(runner.compare(long1, long2)); - final ComparatorTest.ComparatorResult expectedResult = get(Long.compare(long1, long2)); - - assertThat(result) - .withFailMessage(() -> "Compare mismatch for " + name + " - long1: " + long1 - + ", long2: " + long2 - + ", expected: " + expectedResult - + ", actual: " + result) - .isEqualTo(expectedResult); - - final ComparatorTest.ComparatorResult result2 = get(runner.compare(long2, long1)); - final ComparatorTest.ComparatorResult expectedResult2 = expectedResult.opposite(); - - assertThat(result) - .withFailMessage(() -> "Compare mismatch for " + name + " - long2: " + long2 - + ", long1: " + long1 - + ", expected2: " + expectedResult2 - + ", actual2: " + result2) - .isEqualTo(expectedResult); - } + // Make sure the comparator under test gives the same outcome as just comparing two longs + final ComparatorTest.ComparatorResult result = get(runner.compare(long1, long2)); + final ComparatorTest.ComparatorResult expectedResult = get(Long.compare(long1, long2)); + + assertThat(result) + .withFailMessage(() -> "Compare mismatch - long1: " + long1 + + ", long2: " + long2 + + ", expected: " + expectedResult + + ", actual: " + result) + .isEqualTo(expectedResult); + + final ComparatorTest.ComparatorResult result2 = get(runner.compare(long2, long1)); + final ComparatorTest.ComparatorResult expectedResult2 = expectedResult.opposite(); + + assertThat(result) + .withFailMessage(() -> "Compare mismatch for - long2: " + long2 + + ", long1: " + long1 + + ", expected2: " + expectedResult2 + + ", actual2: " + result2) + .isEqualTo(expectedResult); } } - @Test - void testRandomInt() { + @ParameterizedTest + @MethodSource("comparatorProvider") + void testRandomInt(final ComparatorRunner runner) { final Random random = new Random(3239480); - final Map nameToRunnerMap = new LinkedHashMap<>(); - nameToRunnerMap.put("DirectBufferRunner", new DirectBufferRunner()); - nameToRunnerMap.put("ByteBufferRunner", new ByteBufferRunner()); - nameToRunnerMap.put("NettyRunner", new NettyRunner()); // 5mil random ints to compare final int[] values = random.ints() @@ -159,30 +146,26 @@ void testRandomInt() { for (int i = 1; i < values.length; i++) { final int int1 = values[i - 1]; final int int2 = values[i]; - for (Map.Entry entry : nameToRunnerMap.entrySet()) { - final String name = entry.getKey(); - final ComparatorRunner runner = entry.getValue(); - // Make sure the comparator under test gives the same outcome as just comparing two ints - final ComparatorTest.ComparatorResult result = get(runner.compare(int1, int2)); - final ComparatorTest.ComparatorResult expectedResult = get(Integer.compare(int1, int2)); - - assertThat(result) - .withFailMessage(() -> "Compare mismatch for " + name + " - int1: " + int1 - + ", int2: " + int2 - + ", expected: " + expectedResult - + ", actual: " + result) - .isEqualTo(expectedResult); - - final ComparatorTest.ComparatorResult result2 = get(runner.compare(int2, int1)); - final ComparatorTest.ComparatorResult expectedResult2 = expectedResult.opposite(); - - assertThat(result) - .withFailMessage(() -> "Compare mismatch for " + name + " - int2: " + int2 - + ", int1: " + int1 - + ", expected2: " + expectedResult2 - + ", actual2: " + result2) - .isEqualTo(expectedResult); - } + // Make sure the comparator under test gives the same outcome as just comparing two ints + final ComparatorTest.ComparatorResult result = get(runner.compare(int1, int2)); + final ComparatorTest.ComparatorResult expectedResult = get(Integer.compare(int1, int2)); + + assertThat(result) + .withFailMessage(() -> "Compare mismatch for - int1: " + int1 + + ", int2: " + int2 + + ", expected: " + expectedResult + + ", actual: " + result) + .isEqualTo(expectedResult); + + final ComparatorTest.ComparatorResult result2 = get(runner.compare(int2, int1)); + final ComparatorTest.ComparatorResult expectedResult2 = expectedResult.opposite(); + + assertThat(result) + .withFailMessage(() -> "Compare mismatch for - int2: " + int2 + + ", int1: " + int1 + + ", expected2: " + expectedResult2 + + ", actual2: " + result2) + .isEqualTo(expectedResult); } } diff --git a/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java b/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java index 393065a3..775ac4bc 100644 --- a/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java @@ -557,13 +557,13 @@ public Stream provideArguments(ParameterDeclarations parame final DbiFactory callbackComparatorDb = new DbiFactory("callbackComparator", env -> env.buildDbi() .setDbName(DB_3) - .withCallbackComparator(buildComparator()) + .withCallbackComparator(MyArgumentProvider::buildComparator) .setDbiFlags(DBI_FLAGS) .open()); final DbiFactory iteratorComparatorDb = new DbiFactory("iteratorComparator", env -> env.buildDbi() .setDbName(DB_4) - .withIteratorComparator(buildComparator()) + .withIteratorComparator(MyArgumentProvider::buildComparator) .setDbiFlags(DBI_FLAGS) .open()); return Stream.of( @@ -574,7 +574,7 @@ public Stream provideArguments(ParameterDeclarations parame .map(Arguments::of); } - private static Comparator buildComparator() { + private static Comparator buildComparator(final DbiFlagSet dbiFlagSet) { final Comparator baseComparator = BUFFER_PROXY.getComparator(DBI_FLAGS); return (o1, o2) -> { if (o1.remaining() != o2.remaining()) { diff --git a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java index aa23235a..a0d9ab0c 100644 --- a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java @@ -611,18 +611,16 @@ public Stream provideArguments(ParameterDeclarations parame .withNativeComparator() .setDbiFlags(DBI_FLAGS) .open()); - final Comparator comparator = buildComparator(); - final DbiFactory callbackComparatorDb = new DbiFactory("callbackComparator", env -> env.buildDbi() .setDbName(DB_3) - .withCallbackComparator(comparator) + .withCallbackComparator(MyArgumentProvider::buildComparator) .setDbiFlags(DBI_FLAGS) .open()); final DbiFactory iteratorComparatorDb = new DbiFactory("iteratorComparator", env -> env.buildDbi() .setDbName(DB_4) - .withIteratorComparator(comparator) + .withIteratorComparator(MyArgumentProvider::buildComparator) .setDbiFlags(DBI_FLAGS) .open()); return Stream.of( @@ -633,7 +631,7 @@ public Stream provideArguments(ParameterDeclarations parame .map(Arguments::of); } - private static Comparator buildComparator() { + private static Comparator buildComparator(final DbiFlagSet dbiFlagSet) { final Comparator baseComparator = BUFFER_PROXY.getComparator(DBI_FLAGS); return (o1, o2) -> { if (o1.remaining() != o2.remaining()) { diff --git a/src/test/java/org/lmdbjava/CursorIterablePerfTest.java b/src/test/java/org/lmdbjava/CursorIterablePerfTest.java index 008695fd..e0a40fd9 100644 --- a/src/test/java/org/lmdbjava/CursorIterablePerfTest.java +++ b/src/test/java/org/lmdbjava/CursorIterablePerfTest.java @@ -79,7 +79,7 @@ public void before() throws IOException { // Use a java comparator for start/stop keys and as a callback comparator dbCallbackComparator = env.buildDbi() .setDbName("CallBackComparator") - .withCallbackComparator(bufferProxy.getComparator(dbiFlagSet)) + .withCallbackComparator(bufferProxy::getComparator) .setDbiFlags(dbiFlagSet) .open(); diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index 0286fa88..e48f1e69 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -83,7 +83,6 @@ public final class CursorIterableTest { private static final BufferProxy BUFFER_PROXY = ByteBufferProxy.PROXY_OPTIMAL; private Path file; - private Dbi db; private Env env; private Deque list; @@ -309,7 +308,11 @@ void openClosedBackwardTestWithGuava() { bb2.reset(); return guava.compare(array1, array2); }; - final Dbi guavaDbi = env.openDbi(DB_1, comparator, MDB_CREATE); + final Dbi guavaDbi = env.buildDbi() + .setDbName(DB_1) + .withDefaultComparator() + .setDbiFlags(MDB_CREATE) + .open(); populateDatabase(guavaDbi); verify(openClosedBackward(bb(7), bb(2)), guavaDbi, 6, 4, 2); verify(openClosedBackward(bb(8), bb(4)), guavaDbi, 6, 4); @@ -548,13 +551,13 @@ public Stream provideArguments(ParameterDeclarations parame final DbiFactory callbackComparatorDb = new DbiFactory("callbackComparator", env -> env.buildDbi() .setDbName(DB_3) - .withCallbackComparator(BUFFER_PROXY.getComparator(DBI_FLAGS)) + .withCallbackComparator(BUFFER_PROXY::getComparator) .setDbiFlags(DBI_FLAGS) .open()); final DbiFactory iteratorComparatorDb = new DbiFactory("iteratorComparator", env -> env.buildDbi() .setDbName(DB_4) - .withIteratorComparator(BUFFER_PROXY.getComparator(DBI_FLAGS)) + .withIteratorComparator(BUFFER_PROXY::getComparator) .setDbiFlags(DBI_FLAGS) .open()); return Stream.of( diff --git a/src/test/java/org/lmdbjava/DbiBuilderTest.java b/src/test/java/org/lmdbjava/DbiBuilderTest.java index 25e622bf..d01f6417 100644 --- a/src/test/java/org/lmdbjava/DbiBuilderTest.java +++ b/src/test/java/org/lmdbjava/DbiBuilderTest.java @@ -133,7 +133,7 @@ public void callback() { final Dbi dbi = env.buildDbi() .setDbName("foo") - .withCallbackComparator(comparator) + .withCallbackComparator(ignored -> comparator) .addDbiFlags(DbiFlags.MDB_CREATE) .open(); From 69eb5b742dcaad89275c9896bc05366a62374101 Mon Sep 17 00:00:00 2001 From: stroomdev66 Date: Wed, 5 Nov 2025 17:01:14 +0000 Subject: [PATCH 27/90] #269 Initial iterator performance enhancements and testing. --- .../org/lmdbjava/CursorIterableRangeTest.java | 40 +++---- .../CursorIterableRangeTest/testLongKey.csv | 108 +++++++++--------- 2 files changed, 74 insertions(+), 74 deletions(-) diff --git a/src/test/java/org/lmdbjava/CursorIterableRangeTest.java b/src/test/java/org/lmdbjava/CursorIterableRangeTest.java index 0802c047..f0471bab 100644 --- a/src/test/java/org/lmdbjava/CursorIterableRangeTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableRangeTest.java @@ -123,7 +123,7 @@ void testIntegerKey( stopKey, expectedKV, Integer.BYTES, - ByteOrder.LITTLE_ENDIAN); + ByteOrder.nativeOrder()); } @ParameterizedTest(name = "{index} => {0}: ({1}, {2})") @@ -140,7 +140,7 @@ void testLongKey( stopKey, expectedKV, Long.BYTES, - ByteOrder.LITTLE_ENDIAN); + ByteOrder.nativeOrder()); } private void testCSV( @@ -197,7 +197,7 @@ private void testCSV( CursorIterable c = dbi.iterate(txn, keyRange)) { for (final KeyVal kv : c) { final long key = getLong(kv.key(), byteOrder); - final long val = getLong(kv.val(), byteOrder); + final long val = getLong(kv.val(), ByteOrder.BIG_ENDIAN); writer.append("["); writer.append(String.valueOf(key)); writer.append(" "); @@ -215,11 +215,11 @@ private void testCSV( private ByteBuffer parseKey(final String key, final int keyLen, final ByteOrder byteOrder) { if (key != null) { - if (ByteOrder.LITTLE_ENDIAN.equals(byteOrder)) { + if (ByteOrder.nativeOrder().equals(byteOrder)) { if (keyLen == Integer.BYTES) { - return bbLeInt(Integer.parseInt(key.trim())); + return bbNativeInt(Integer.parseInt(key.trim())); } else { - return bbLeLong(Long.parseLong(key.trim())); + return bbNativeLong(Long.parseLong(key.trim())); } } else { if (keyLen == Integer.BYTES) { @@ -277,11 +277,11 @@ private BiConsumer, Dbi> createIntegerDBPopulator() return (env, dbi) -> { try (Txn txn = env.txnWrite()) { final Cursor c = dbi.openCursor(txn); - c.put(bbLeInt(Integer.MIN_VALUE), bb(1)); - c.put(bbLeInt(-1000), bb(2)); - c.put(bbLeInt(0), bb(3)); - c.put(bbLeInt(1000), bb(4)); - c.put(bbLeInt(Integer.MAX_VALUE), bb(5)); + c.put(bbNativeInt(0), bb(1)); + c.put(bbNativeInt(1000), bb(2)); + c.put(bbNativeInt(1000000), bb(3)); + c.put(bbNativeInt(-1000000), bb(4)); + c.put(bbNativeInt(-1000), bb(5)); txn.commit(); } }; @@ -291,11 +291,11 @@ private BiConsumer, Dbi> createLongDBPopulator() { return (env, dbi) -> { try (Txn txn = env.txnWrite()) { final Cursor c = dbi.openCursor(txn); - c.put(bbLeLong(Long.MIN_VALUE), bb(1)); - c.put(bbLeLong(-1000), bb(2)); - c.put(bbLeLong(0), bb(3)); - c.put(bbLeLong(1000), bb(4)); - c.put(bbLeLong(Long.MAX_VALUE), bb(5)); + c.put(bbNativeLong(0), bb(1)); + c.put(bbNativeLong(1000), bb(2)); + c.put(bbNativeLong(1000000), bb(3)); + c.put(bbNativeLong(-1000000), bb(4)); + c.put(bbNativeLong(-1000), bb(5)); txn.commit(); } }; @@ -329,14 +329,14 @@ private String readFile(final File file) throws IOException { return result.toString(); } - static ByteBuffer bbLeInt(final int value) { - final ByteBuffer bb = allocateDirect(Integer.BYTES).order(ByteOrder.LITTLE_ENDIAN); + static ByteBuffer bbNativeInt(final int value) { + final ByteBuffer bb = allocateDirect(Integer.BYTES).order(ByteOrder.nativeOrder()); bb.putInt(value).flip(); return bb; } - static ByteBuffer bbLeLong(final long value) { - final ByteBuffer bb = allocateDirect(Long.BYTES).order(ByteOrder.LITTLE_ENDIAN); + static ByteBuffer bbNativeLong(final long value) { + final ByteBuffer bb = allocateDirect(Long.BYTES).order(ByteOrder.nativeOrder()); bb.putLong(value).flip(); return bb; } diff --git a/src/test/resources/CursorIterableRangeTest/testLongKey.csv b/src/test/resources/CursorIterableRangeTest/testLongKey.csv index f3504662..d8d6dc35 100644 --- a/src/test/resources/CursorIterableRangeTest/testLongKey.csv +++ b/src/test/resources/CursorIterableRangeTest/testLongKey.csv @@ -1,55 +1,55 @@ -FORWARD_ALL,,,[0 50331648][1000 67108864][9223372036854775807 83886080][-9223372036854775808 16777216][-1000 33554432] -FORWARD_AT_LEAST,999,,[1000 67108864][9223372036854775807 83886080][-9223372036854775808 16777216][-1000 33554432] -FORWARD_AT_LEAST,1000,,[1000 67108864][9223372036854775807 83886080][-9223372036854775808 16777216][-1000 33554432] -FORWARD_AT_LEAST,1001,,[9223372036854775807 83886080][-9223372036854775808 16777216][-1000 33554432] -FORWARD_AT_MOST,,999,[0 50331648] -FORWARD_AT_MOST,,1000,[0 50331648][1000 67108864] -FORWARD_AT_MOST,,1001,[0 50331648][1000 67108864] -FORWARD_CLOSED,999,1001,[1000 67108864] -FORWARD_CLOSED,1000,1000,[1000 67108864] -FORWARD_CLOSED_OPEN,999,1001,[1000 67108864] +FORWARD_ALL,,,[0 1][1000 2][1000000 3][-1000000 4][-1000 5] +FORWARD_AT_LEAST,999,,[1000 2][1000000 3][-1000000 4][-1000 5] +FORWARD_AT_LEAST,1000,,[1000 2][1000000 3][-1000000 4][-1000 5] +FORWARD_AT_LEAST,1001,,[1000000 3][-1000000 4][-1000 5] +FORWARD_AT_MOST,,999,[0 1] +FORWARD_AT_MOST,,1000,[0 1][1000 2] +FORWARD_AT_MOST,,1001,[0 1][1000 2] +FORWARD_CLOSED,999,1001,[1000 2] +FORWARD_CLOSED,1000,1000,[1000 2] +FORWARD_CLOSED_OPEN,999,1001,[1000 2] FORWARD_CLOSED_OPEN,1000,1000, -FORWARD_CLOSED_OPEN,1000,1001,[1000 67108864] -FORWARD_GREATER_THAN,999,,[1000 67108864][9223372036854775807 83886080][-9223372036854775808 16777216][-1000 33554432] -FORWARD_GREATER_THAN,1000,,[9223372036854775807 83886080][-9223372036854775808 16777216][-1000 33554432] -FORWARD_GREATER_THAN,1001,,[9223372036854775807 83886080][-9223372036854775808 16777216][-1000 33554432] -FORWARD_LESS_THAN,,999,[0 50331648] -FORWARD_LESS_THAN,,1000,[0 50331648] -FORWARD_LESS_THAN,,1001,[0 50331648][1000 67108864] - - -#FORWARD_OPEN,3,7,[4 5][6 7] -#FORWARD_OPEN,2,8,[4 5][6 7] -#FORWARD_OPEN_CLOSED,3,8,[4 5][6 7][8 9] -#FORWARD_OPEN_CLOSED,2,6,[4 5][6 7] -#BACKWARD_ALL,,,[-2 -1][8 9][6 7][4 5][2 3][0 1] -#BACKWARD_AT_LEAST,5,,[4 5][2 3][0 1] -#BACKWARD_AT_LEAST,6,,[6 7][4 5][2 3][0 1] -#BACKWARD_AT_LEAST,9,,[8 9][6 7][4 5][2 3][0 1] -#BACKWARD_AT_LEAST,-1,,[-2 -1][8 9][6 7][4 5][2 3][0 1] -#BACKWARD_AT_MOST,,5,[-2 -1][8 9][6 7] -#BACKWARD_AT_MOST,,6,[-2 -1][8 9][6 7] -#BACKWARD_AT_MOST,,-1, -#BACKWARD_CLOSED,7,3,[6 7][4 5] -#BACKWARD_CLOSED,6,2,[6 7][4 5][2 3] -#BACKWARD_CLOSED,9,3,[8 9][6 7][4 5] -#BACKWARD_CLOSED,9,-1, -#BACKWARD_CLOSED_OPEN,8,3,[8 9][6 7][4 5] -#BACKWARD_CLOSED_OPEN,7,2,[6 7][4 5] -#BACKWARD_CLOSED_OPEN,9,3,[8 9][6 7][4 5] -#BACKWARD_CLOSED_OPEN,9,-1, -#BACKWARD_GREATER_THAN,6,,[4 5][2 3][0 1] -#BACKWARD_GREATER_THAN,7,,[6 7][4 5][2 3][0 1] -#BACKWARD_GREATER_THAN,9,,[8 9][6 7][4 5][2 3][0 1] -#BACKWARD_GREATER_THAN,-1,,[-2 -1][8 9][6 7][4 5][2 3][0 1] -#BACKWARD_LESS_THAN,,5,[-2 -1][8 9][6 7] -#BACKWARD_LESS_THAN,,2,[-2 -1][8 9][6 7][4 5] -#BACKWARD_LESS_THAN,,-1, -#BACKWARD_OPEN,7,2,[6 7][4 5] -#BACKWARD_OPEN,8,1,[6 7][4 5][2 3] -#BACKWARD_OPEN,9,4,[8 9][6 7] -#BACKWARD_OPEN,9,-1, -#BACKWARD_OPEN_CLOSED,7,2,[6 7][4 5][2 3] -#BACKWARD_OPEN_CLOSED,8,4,[6 7][4 5] -#BACKWARD_OPEN_CLOSED,9,4,[8 9][6 7][4 5] -#BACKWARD_OPEN_CLOSED,9,-1, +FORWARD_CLOSED_OPEN,1000,1001,[1000 2] +FORWARD_GREATER_THAN,999,,[1000 2][1000000 3][-1000000 4][-1000 5] +FORWARD_GREATER_THAN,1000,,[1000000 3][-1000000 4][-1000 5] +FORWARD_GREATER_THAN,1001,,[1000000 3][-1000000 4][-1000 5] +FORWARD_LESS_THAN,,999,[0 1] +FORWARD_LESS_THAN,,1000,[0 1] +FORWARD_LESS_THAN,,1001,[0 1][1000 2] +FORWARD_OPEN,999,1001,[1000 2] +FORWARD_OPEN,999,1000, +FORWARD_OPEN,1000,1000, +FORWARD_OPEN,1000,1001, +FORWARD_OPEN_CLOSED,999,1001,[1000 2] +FORWARD_OPEN_CLOSED,999,1000,[1000 2] +FORWARD_OPEN_CLOSED,1000,1000, +FORWARD_OPEN_CLOSED,1000,1001, +BACKWARD_ALL,,,[-1000 5][-1000000 4][1000000 3][1000 2][0 1] +BACKWARD_AT_LEAST,999,,[0 1] +BACKWARD_AT_LEAST,1000,,[1000 2][0 1] +BACKWARD_AT_LEAST,1001,,[1000 2][0 1] +BACKWARD_AT_MOST,,999,[-1000 5][-1000000 4][1000000 3][1000 2] +BACKWARD_AT_MOST,,1000,[-1000 5][-1000000 4][1000000 3][1000 2] +BACKWARD_AT_MOST,,1001,[-1000 5][-1000000 4][1000000 3] +BACKWARD_CLOSED,1001,999,[1000 2] +BACKWARD_CLOSED,1000,1000,[1000 2] +BACKWARD_CLOSED_OPEN,1001,999,[1000 2] +BACKWARD_CLOSED_OPEN,1000,999,[1000 2] +BACKWARD_CLOSED_OPEN,1000,1000, +BACKWARD_CLOSED_OPEN,1001,1000, +BACKWARD_GREATER_THAN,999,,[0 1] +BACKWARD_GREATER_THAN,1000,,[0 1] +BACKWARD_GREATER_THAN,1001,,[1000 2][0 1] +BACKWARD_LESS_THAN,,999,[-1000 5][-1000000 4][1000000 3][1000 2] +BACKWARD_LESS_THAN,,1000,[-1000 5][-1000000 4][1000000 3] +BACKWARD_LESS_THAN,,1001,[-1000 5][-1000000 4][1000000 3] +BACKWARD_OPEN,999,1001,[1000 2] +BACKWARD_OPEN,999,1000, +BACKWARD_OPEN,999,1001,[1000 2] +BACKWARD_OPEN,1000,1000, +BACKWARD_OPEN,1000,1001, +BACKWARD_OPEN_CLOSED,999,1001,[1000 2] +BACKWARD_OPEN_CLOSED,999,1000,[1000 2] +BACKWARD_OPEN_CLOSED,1000,1000, +BACKWARD_OPEN_CLOSED,1000,1001, +BACKWARD_OPEN_CLOSED,1000,1001, From 915edce19bdb9e30d719fdd9af0906df25723ec4 Mon Sep 17 00:00:00 2001 From: stroomdev66 Date: Wed, 5 Nov 2025 17:01:55 +0000 Subject: [PATCH 28/90] #269 Initial iterator performance enhancements and testing. --- .../testIntegerKey.csv | 104 +++++++++--------- 1 file changed, 55 insertions(+), 49 deletions(-) diff --git a/src/test/resources/CursorIterableRangeTest/testIntegerKey.csv b/src/test/resources/CursorIterableRangeTest/testIntegerKey.csv index ceda992a..d8d6dc35 100644 --- a/src/test/resources/CursorIterableRangeTest/testIntegerKey.csv +++ b/src/test/resources/CursorIterableRangeTest/testIntegerKey.csv @@ -1,49 +1,55 @@ -FORWARD_ALL,,,[0 50331648][1000 67108864][2147483647 83886080][-2147483648 16777216][-1000 33554432] -#FORWARD_AT_LEAST,5,,[1000 67108864][2147483647 83886080][-2147483648 16777216][-1000 33554432] -#FORWARD_AT_LEAST,6,,[1000 67108864][2147483647 83886080][-2147483648 16777216][-1000 33554432] -#FORWARD_AT_MOST,,5,[0 50331648] -#FORWARD_AT_MOST,,2147483647,[0 50331648] -#FORWARD_CLOSED,3,7, -#FORWARD_CLOSED,2,6, -#FORWARD_CLOSED,1,7, -#FORWARD_CLOSED_OPEN,3,8,[4 5][6 7] -#FORWARD_CLOSED_OPEN,2,6,[2 3][4 5] -#FORWARD_GREATER_THAN,4,,[6 7][8 9][-2 -1] -#FORWARD_GREATER_THAN,3,,[4 5][6 7][8 9][-2 -1] -#FORWARD_LESS_THAN,,5,[0 1][2 3][4 5] -#FORWARD_LESS_THAN,,8,[0 1][2 3][4 5][6 7] -#FORWARD_OPEN,3,7,[4 5][6 7] -#FORWARD_OPEN,2,8,[4 5][6 7] -#FORWARD_OPEN_CLOSED,3,8,[4 5][6 7][8 9] -#FORWARD_OPEN_CLOSED,2,6,[4 5][6 7] -#BACKWARD_ALL,,,[-2 -1][8 9][6 7][4 5][2 3][0 1] -#BACKWARD_AT_LEAST,5,,[4 5][2 3][0 1] -#BACKWARD_AT_LEAST,6,,[6 7][4 5][2 3][0 1] -#BACKWARD_AT_LEAST,9,,[8 9][6 7][4 5][2 3][0 1] -#BACKWARD_AT_LEAST,-1,,[-2 -1][8 9][6 7][4 5][2 3][0 1] -#BACKWARD_AT_MOST,,5,[-2 -1][8 9][6 7] -#BACKWARD_AT_MOST,,6,[-2 -1][8 9][6 7] -#BACKWARD_AT_MOST,,-1, -#BACKWARD_CLOSED,7,3,[6 7][4 5] -#BACKWARD_CLOSED,6,2,[6 7][4 5][2 3] -#BACKWARD_CLOSED,9,3,[8 9][6 7][4 5] -#BACKWARD_CLOSED,9,-1, -#BACKWARD_CLOSED_OPEN,8,3,[8 9][6 7][4 5] -#BACKWARD_CLOSED_OPEN,7,2,[6 7][4 5] -#BACKWARD_CLOSED_OPEN,9,3,[8 9][6 7][4 5] -#BACKWARD_CLOSED_OPEN,9,-1, -#BACKWARD_GREATER_THAN,6,,[4 5][2 3][0 1] -#BACKWARD_GREATER_THAN,7,,[6 7][4 5][2 3][0 1] -#BACKWARD_GREATER_THAN,9,,[8 9][6 7][4 5][2 3][0 1] -#BACKWARD_GREATER_THAN,-1,,[-2 -1][8 9][6 7][4 5][2 3][0 1] -#BACKWARD_LESS_THAN,,5,[-2 -1][8 9][6 7] -#BACKWARD_LESS_THAN,,2,[-2 -1][8 9][6 7][4 5] -#BACKWARD_LESS_THAN,,-1, -#BACKWARD_OPEN,7,2,[6 7][4 5] -#BACKWARD_OPEN,8,1,[6 7][4 5][2 3] -#BACKWARD_OPEN,9,4,[8 9][6 7] -#BACKWARD_OPEN,9,-1, -#BACKWARD_OPEN_CLOSED,7,2,[6 7][4 5][2 3] -#BACKWARD_OPEN_CLOSED,8,4,[6 7][4 5] -#BACKWARD_OPEN_CLOSED,9,4,[8 9][6 7][4 5] -#BACKWARD_OPEN_CLOSED,9,-1, +FORWARD_ALL,,,[0 1][1000 2][1000000 3][-1000000 4][-1000 5] +FORWARD_AT_LEAST,999,,[1000 2][1000000 3][-1000000 4][-1000 5] +FORWARD_AT_LEAST,1000,,[1000 2][1000000 3][-1000000 4][-1000 5] +FORWARD_AT_LEAST,1001,,[1000000 3][-1000000 4][-1000 5] +FORWARD_AT_MOST,,999,[0 1] +FORWARD_AT_MOST,,1000,[0 1][1000 2] +FORWARD_AT_MOST,,1001,[0 1][1000 2] +FORWARD_CLOSED,999,1001,[1000 2] +FORWARD_CLOSED,1000,1000,[1000 2] +FORWARD_CLOSED_OPEN,999,1001,[1000 2] +FORWARD_CLOSED_OPEN,1000,1000, +FORWARD_CLOSED_OPEN,1000,1001,[1000 2] +FORWARD_GREATER_THAN,999,,[1000 2][1000000 3][-1000000 4][-1000 5] +FORWARD_GREATER_THAN,1000,,[1000000 3][-1000000 4][-1000 5] +FORWARD_GREATER_THAN,1001,,[1000000 3][-1000000 4][-1000 5] +FORWARD_LESS_THAN,,999,[0 1] +FORWARD_LESS_THAN,,1000,[0 1] +FORWARD_LESS_THAN,,1001,[0 1][1000 2] +FORWARD_OPEN,999,1001,[1000 2] +FORWARD_OPEN,999,1000, +FORWARD_OPEN,1000,1000, +FORWARD_OPEN,1000,1001, +FORWARD_OPEN_CLOSED,999,1001,[1000 2] +FORWARD_OPEN_CLOSED,999,1000,[1000 2] +FORWARD_OPEN_CLOSED,1000,1000, +FORWARD_OPEN_CLOSED,1000,1001, +BACKWARD_ALL,,,[-1000 5][-1000000 4][1000000 3][1000 2][0 1] +BACKWARD_AT_LEAST,999,,[0 1] +BACKWARD_AT_LEAST,1000,,[1000 2][0 1] +BACKWARD_AT_LEAST,1001,,[1000 2][0 1] +BACKWARD_AT_MOST,,999,[-1000 5][-1000000 4][1000000 3][1000 2] +BACKWARD_AT_MOST,,1000,[-1000 5][-1000000 4][1000000 3][1000 2] +BACKWARD_AT_MOST,,1001,[-1000 5][-1000000 4][1000000 3] +BACKWARD_CLOSED,1001,999,[1000 2] +BACKWARD_CLOSED,1000,1000,[1000 2] +BACKWARD_CLOSED_OPEN,1001,999,[1000 2] +BACKWARD_CLOSED_OPEN,1000,999,[1000 2] +BACKWARD_CLOSED_OPEN,1000,1000, +BACKWARD_CLOSED_OPEN,1001,1000, +BACKWARD_GREATER_THAN,999,,[0 1] +BACKWARD_GREATER_THAN,1000,,[0 1] +BACKWARD_GREATER_THAN,1001,,[1000 2][0 1] +BACKWARD_LESS_THAN,,999,[-1000 5][-1000000 4][1000000 3][1000 2] +BACKWARD_LESS_THAN,,1000,[-1000 5][-1000000 4][1000000 3] +BACKWARD_LESS_THAN,,1001,[-1000 5][-1000000 4][1000000 3] +BACKWARD_OPEN,999,1001,[1000 2] +BACKWARD_OPEN,999,1000, +BACKWARD_OPEN,999,1001,[1000 2] +BACKWARD_OPEN,1000,1000, +BACKWARD_OPEN,1000,1001, +BACKWARD_OPEN_CLOSED,999,1001,[1000 2] +BACKWARD_OPEN_CLOSED,999,1000,[1000 2] +BACKWARD_OPEN_CLOSED,1000,1000, +BACKWARD_OPEN_CLOSED,1000,1001, +BACKWARD_OPEN_CLOSED,1000,1001, From 5a62965505ee651bd5a89dbae8c1173a522dd521 Mon Sep 17 00:00:00 2001 From: stroomdev66 Date: Wed, 5 Nov 2025 18:24:10 +0000 Subject: [PATCH 29/90] Merged new comparator code --- .../java/org/lmdbjava/AbstractFlagSet.java | 359 ++++++++++ src/main/java/org/lmdbjava/BufferProxy.java | 37 +- .../java/org/lmdbjava/ByteArrayProxy.java | 34 +- src/main/java/org/lmdbjava/ByteBufProxy.java | 86 ++- .../java/org/lmdbjava/ByteBufferProxy.java | 83 ++- src/main/java/org/lmdbjava/CopyFlagSet.java | 63 ++ src/main/java/org/lmdbjava/CopyFlags.java | 33 +- src/main/java/org/lmdbjava/Cursor.java | 167 ++++- .../java/org/lmdbjava/CursorIterable.java | 131 +++- src/main/java/org/lmdbjava/Dbi.java | 133 +++- src/main/java/org/lmdbjava/DbiBuilder.java | 426 +++++++++++ src/main/java/org/lmdbjava/DbiFlagSet.java | 67 ++ src/main/java/org/lmdbjava/DbiFlags.java | 66 +- .../java/org/lmdbjava/DirectBufferProxy.java | 67 +- src/main/java/org/lmdbjava/Env.java | 344 +++++++-- src/main/java/org/lmdbjava/EnvFlagSet.java | 63 ++ src/main/java/org/lmdbjava/EnvFlags.java | 30 +- src/main/java/org/lmdbjava/FlagSet.java | 122 ++++ src/main/java/org/lmdbjava/Key.java | 72 ++ src/main/java/org/lmdbjava/KeyRangeType.java | 50 +- src/main/java/org/lmdbjava/Library.java | 2 + src/main/java/org/lmdbjava/MaskedFlag.java | 86 +-- src/main/java/org/lmdbjava/PutFlagSet.java | 63 ++ src/main/java/org/lmdbjava/PutFlags.java | 30 +- .../java/org/lmdbjava/RangeComparator.java | 32 + src/main/java/org/lmdbjava/Txn.java | 13 +- src/main/java/org/lmdbjava/TxnFlagSet.java | 68 ++ src/main/java/org/lmdbjava/TxnFlags.java | 31 +- .../org/lmdbjava/ByteBufferProxyTest.java | 117 +++- .../lmdbjava/ComparatorIntegerKeyTest.java | 357 ++++++++++ .../java/org/lmdbjava/ComparatorTest.java | 12 +- .../java/org/lmdbjava/CopyFlagSetTest.java | 72 ++ .../CursorIterableIntegerDupTest.java | 615 ++++++++++++++++ .../CursorIterableIntegerKeyTest.java | 662 ++++++++++++++++++ .../org/lmdbjava/CursorIterablePerfTest.java | 201 ++++++ .../org/lmdbjava/CursorIterableRangeTest.java | 8 +- .../java/org/lmdbjava/CursorIterableTest.java | 197 +++++- .../java/org/lmdbjava/DbiBuilderTest.java | 204 ++++++ .../java/org/lmdbjava/DbiFlagSetTest.java | 90 +++ src/test/java/org/lmdbjava/DbiTest.java | 13 +- .../java/org/lmdbjava/EnvFlagSetTest.java | 91 +++ src/test/java/org/lmdbjava/KeyRangeTest.java | 5 +- .../java/org/lmdbjava/PutFlagSetTest.java | 131 ++++ src/test/java/org/lmdbjava/TestUtils.java | 87 +++ .../java/org/lmdbjava/TxnFlagSetTest.java | 85 +++ 45 files changed, 5335 insertions(+), 370 deletions(-) create mode 100644 src/main/java/org/lmdbjava/AbstractFlagSet.java create mode 100644 src/main/java/org/lmdbjava/CopyFlagSet.java create mode 100644 src/main/java/org/lmdbjava/DbiBuilder.java create mode 100644 src/main/java/org/lmdbjava/DbiFlagSet.java create mode 100644 src/main/java/org/lmdbjava/EnvFlagSet.java create mode 100644 src/main/java/org/lmdbjava/FlagSet.java create mode 100644 src/main/java/org/lmdbjava/Key.java create mode 100644 src/main/java/org/lmdbjava/PutFlagSet.java create mode 100644 src/main/java/org/lmdbjava/RangeComparator.java create mode 100644 src/main/java/org/lmdbjava/TxnFlagSet.java create mode 100644 src/test/java/org/lmdbjava/ComparatorIntegerKeyTest.java create mode 100644 src/test/java/org/lmdbjava/CopyFlagSetTest.java create mode 100644 src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java create mode 100644 src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java create mode 100644 src/test/java/org/lmdbjava/CursorIterablePerfTest.java create mode 100644 src/test/java/org/lmdbjava/DbiBuilderTest.java create mode 100644 src/test/java/org/lmdbjava/DbiFlagSetTest.java create mode 100644 src/test/java/org/lmdbjava/EnvFlagSetTest.java create mode 100644 src/test/java/org/lmdbjava/PutFlagSetTest.java create mode 100644 src/test/java/org/lmdbjava/TxnFlagSetTest.java diff --git a/src/main/java/org/lmdbjava/AbstractFlagSet.java b/src/main/java/org/lmdbjava/AbstractFlagSet.java new file mode 100644 index 00000000..65f84f55 --- /dev/null +++ b/src/main/java/org/lmdbjava/AbstractFlagSet.java @@ -0,0 +1,359 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Iterator; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; + +/** Encapsulates an immutable set of flags and the associated bit mask for the flags in the set. */ +public abstract class AbstractFlagSet & MaskedFlag> implements FlagSet { + + private final Set flags; + private final int mask; + + protected AbstractFlagSet(final EnumSet flags) { + Objects.requireNonNull(flags); + this.mask = MaskedFlag.mask(flags); + this.flags = Collections.unmodifiableSet(Objects.requireNonNull(flags)); + } + + /** + * @return THe combined bit mask for all flags in the set. + */ + @Override + public int getMask() { + return mask; + } + + /** + * @return All flags in the set. + */ + @Override + public Set getFlags() { + return flags; + } + + /** + * @return True if flag has been set, i.e. is contained in this set. + */ + @Override + public boolean isSet(final T flag) { + // Probably cheaper to compare the masks than to use EnumSet.contains() + return flag != null && MaskedFlag.isSet(mask, flag); + } + + /** + * @return The number of flags in this set. + */ + @Override + public int size() { + return flags.size(); + } + + /** + * @return True if this set is empty. + */ + @Override + public boolean isEmpty() { + return flags.isEmpty(); + } + + /** + * @return The {@link Iterator} for this set. + */ + @Override + public Iterator iterator() { + return flags.iterator(); + } + + @Override + public boolean equals(Object object) { + return FlagSet.equals(this, object); + } + + @Override + public int hashCode() { + return Objects.hash(flags, mask); + } + + @Override + public String toString() { + return FlagSet.asString(this); + } + + // -------------------------------------------------------------------------------- + + abstract static class AbstractSingleFlagSet & MaskedFlag> + implements FlagSet { + + private final T flag; + // Only holding this for iterator() and getFlags() so make it lazy. + private EnumSet enumSet; + + public AbstractSingleFlagSet(final T flag) { + this.flag = Objects.requireNonNull(flag); + } + + @Override + public int getMask() { + return flag.getMask(); + } + + @Override + public Set getFlags() { + if (enumSet == null) { + return initSet(); + } else { + return this.enumSet; + } + } + + @Override + public boolean isSet(final T flag) { + return this.flag == flag; + } + + @Override + public boolean areAnySet(FlagSet flags) { + if (flags == null) { + return false; + } else { + return flags.isSet(this.flag); + } + } + + @Override + public int size() { + return 1; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public Iterator iterator() { + if (enumSet == null) { + return initSet().iterator(); + } else { + return this.enumSet.iterator(); + } + } + + @Override + public String toString() { + return FlagSet.asString(this); + } + + @Override + public boolean equals(Object object) { + return FlagSet.equals(this, object); + } + + @Override + public int hashCode() { + return Objects.hash(flag, getFlags()); + } + + private Set initSet() { + final EnumSet set = EnumSet.of(this.flag); + this.enumSet = set; + return set; + } + } + + // -------------------------------------------------------------------------------- + + static class AbstractEmptyFlagSet implements FlagSet { + + @Override + public int getMask() { + return MaskedFlag.EMPTY_MASK; + } + + @Override + public Set getFlags() { + return Collections.emptySet(); + } + + @Override + public boolean isSet(final T flag) { + return false; + } + + @Override + public boolean areAnySet(final FlagSet flags) { + return false; + } + + @Override + public int size() { + return 0; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public Iterator iterator() { + return Collections.emptyIterator(); + } + + @Override + public String toString() { + return FlagSet.asString(this); + } + + @Override + public boolean equals(Object object) { + return FlagSet.equals(this, object); + } + + @Override + public int hashCode() { + return Objects.hash(getMask(), getFlags()); + } + } + + // -------------------------------------------------------------------------------- + + /** + * A builder for creating a {@link AbstractFlagSet}. + * + * @param The type of flag to be held in the {@link AbstractFlagSet} + * @param The type of the {@link AbstractFlagSet} implementation. + */ + public static class Builder & MaskedFlag, S extends FlagSet> { + + final Class type; + final EnumSet enumSet; + final Function, S> constructor; + final Function singletonSetConstructor; + final Supplier emptySetSupplier; + + protected Builder( + final Class type, + final Function, S> constructor, + final Function singletonSetConstructor, + final Supplier emptySetSupplier) { + this.type = type; + this.enumSet = EnumSet.noneOf(type); + this.constructor = Objects.requireNonNull(constructor); + this.singletonSetConstructor = Objects.requireNonNull(singletonSetConstructor); + this.emptySetSupplier = Objects.requireNonNull(emptySetSupplier); + } + + /** + * Replaces any flags already set in the builder with the contents of the passed flags {@link + * Collection} + * + * @param flags The flags to set in the builder. + * @return this builder instance. + */ + public Builder withFlags(final Collection flags) { + clear(); + if (flags != null) { + for (E flag : flags) { + if (flag != null) { + enumSet.add(flag); + } + } + } + return this; + } + + /** + * @param flags The flags to set in the builder. + * @return this builder instance. + */ + @SafeVarargs + public final Builder withFlags(final E... flags) { + clear(); + if (flags != null) { + for (E flag : flags) { + if (flag != null) { + if (!type.equals(flag.getClass())) { + throw new IllegalArgumentException("Unexpected type " + flag.getClass()); + } + enumSet.add(flag); + } + } + } + return this; + } + + /** + * Sets a single flag in the builder. + * + * @param flag The flag to set in the builder. + * @return this builder instance. + */ + public Builder setFlag(final E flag) { + if (flag != null) { + enumSet.add(flag); + } + return this; + } + + /** + * Sets multiple flag in the builder. + * + * @param flags The flags to set in the builder. + * @return this builder instance. + */ + public Builder setFlags(final Collection flags) { + if (flags != null) { + enumSet.addAll(flags); + } + return this; + } + + /** + * Clears any flags already set in this {@link Builder} + * + * @return this builder instance. + */ + public Builder clear() { + enumSet.clear(); + return this; + } + + /** + * Build the {@link DbiFlagSet} + * + * @return A + */ + public S build() { + final int size = enumSet.size(); + if (size == 0) { + return emptySetSupplier.get(); + } else if (size == 1) { + return singletonSetConstructor.apply(enumSet.stream().findFirst().get()); + } else { + return constructor.apply(enumSet); + } + } + } +} diff --git a/src/main/java/org/lmdbjava/BufferProxy.java b/src/main/java/org/lmdbjava/BufferProxy.java index d4503731..a3c339bf 100644 --- a/src/main/java/org/lmdbjava/BufferProxy.java +++ b/src/main/java/org/lmdbjava/BufferProxy.java @@ -16,10 +16,6 @@ package org.lmdbjava; import static java.lang.Long.BYTES; -import static org.lmdbjava.DbiFlags.MDB_INTEGERKEY; -import static org.lmdbjava.DbiFlags.MDB_UNSIGNEDKEY; -import static org.lmdbjava.MaskedFlag.isSet; -import static org.lmdbjava.MaskedFlag.mask; import java.util.Comparator; import jnr.ffi.Pointer; @@ -75,30 +71,22 @@ protected BufferProxy() {} *

The provided comparator must strictly match the lexicographical order of keys in the native * LMDB database. * - * @param flags for the database + * @param dbiFlagSet The {@link DbiFlags} set for the database. * @return a comparator that can be used (never null) */ - protected Comparator getComparator(DbiFlags... flags) { - final int intFlag = mask(flags); - - return isSet(intFlag, MDB_INTEGERKEY) || isSet(intFlag, MDB_UNSIGNEDKEY) - ? getUnsignedComparator() - : getSignedComparator(); - } + public abstract Comparator getComparator(final DbiFlagSet dbiFlagSet); /** - * Get a suitable default {@link Comparator} to compare numeric key values as signed. + * Get a suitable default {@link Comparator} * - * @return a comparator that can be used (never null) - */ - protected abstract Comparator getSignedComparator(); - - /** - * Get a suitable default {@link Comparator} to compare numeric key values as unsigned. + *

The provided comparator must strictly match the lexicographical order of keys in the native + * LMDB database. * * @return a comparator that can be used (never null) */ - protected abstract Comparator getUnsignedComparator(); + public Comparator getComparator() { + return getComparator(DbiFlagSet.empty()); + } /** * Called when the MDB_val should be set to reflect the passed buffer. This buffer @@ -138,4 +126,13 @@ protected Comparator getComparator(DbiFlags... flags) { final KeyVal keyVal() { return new KeyVal<>(this); } + + /** + * Create a new {@link Key} to hold pointers for this buffer proxy. + * + * @return a non-null key holder + */ + final Key key() { + return new Key<>(this); + } } diff --git a/src/main/java/org/lmdbjava/ByteArrayProxy.java b/src/main/java/org/lmdbjava/ByteArrayProxy.java index 853521e0..82b7721c 100644 --- a/src/main/java/org/lmdbjava/ByteArrayProxy.java +++ b/src/main/java/org/lmdbjava/ByteArrayProxy.java @@ -36,9 +36,6 @@ public final class ByteArrayProxy extends BufferProxy { private static final MemoryManager MEM_MGR = RUNTIME.getMemoryManager(); - private static final Comparator signedComparator = ByteArrayProxy::compareArraysSigned; - private static final Comparator unsignedComparator = ByteArrayProxy::compareArrays; - private ByteArrayProxy() {} /** @@ -48,7 +45,7 @@ private ByteArrayProxy() {} * @param o2 right operand (required) * @return as specified by {@link Comparable} interface */ - public static int compareArrays(final byte[] o1, final byte[] o2) { + public static int compareLexicographically(final byte[] o1, final byte[] o2) { requireNonNull(o1); requireNonNull(o2); if (o1 == o2) { @@ -68,26 +65,6 @@ public static int compareArrays(final byte[] o1, final byte[] o2) { return o1.length - o2.length; } - /** - * Compare two byte arrays. - * - * @param b1 left operand (required) - * @param b2 right operand (required) - * @return as specified by {@link Comparable} interface - */ - public static int compareArraysSigned(final byte[] b1, final byte[] b2) { - requireNonNull(b1); - requireNonNull(b2); - - if (b1 == b2) return 0; - - for (int i = 0; i < min(b1.length, b2.length); ++i) { - if (b1[i] != b2[i]) return b1[i] - b2[i]; - } - - return b1.length - b2.length; - } - @Override protected byte[] allocate() { return new byte[0]; @@ -104,13 +81,8 @@ protected byte[] getBytes(final byte[] buffer) { } @Override - protected Comparator getSignedComparator() { - return signedComparator; - } - - @Override - protected Comparator getUnsignedComparator() { - return unsignedComparator; + public Comparator getComparator(final DbiFlagSet dbiFlagSet) { + return ByteArrayProxy::compareLexicographically; } @Override diff --git a/src/main/java/org/lmdbjava/ByteBufProxy.java b/src/main/java/org/lmdbjava/ByteBufProxy.java index 2866e874..bcbb6ebf 100644 --- a/src/main/java/org/lmdbjava/ByteBufProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufProxy.java @@ -23,6 +23,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.PooledByteBufAllocator; import java.lang.reflect.Field; +import java.nio.ByteOrder; import java.util.Comparator; import jnr.ffi.Pointer; @@ -44,13 +45,6 @@ public final class ByteBufProxy extends BufferProxy { private static final String FIELD_NAME_ADDRESS = "memoryAddress"; private static final String FIELD_NAME_LENGTH = "length"; private static final String NAME = "io.netty.buffer.PooledUnsafeDirectByteBuf"; - private static final Comparator comparator = - (o1, o2) -> { - requireNonNull(o1); - requireNonNull(o2); - - return o1.compareTo(o2); - }; private final long lengthOffset; private final long addressOffset; @@ -81,6 +75,71 @@ public ByteBufProxy(final PooledByteBufAllocator allocator) { } } + /** + * Lexicographically compare two buffers. + * + * @param o1 left operand (required) + * @param o2 right operand (required) + * @return as specified by {@link Comparable} interface + */ + public static int compareLexicographically(final ByteBuf o1, final ByteBuf o2) { + requireNonNull(o1); + requireNonNull(o2); + return o1.compareTo(o2); + } + + /** + * Buffer comparator specifically for 4/8 byte keys that are unsigned ints/longs, i.e. when using + * MDB_INTEGER_KEY/MDB_INTEGERDUP. Compares the buffers numerically. + * + * @param o1 left operand (required) + * @param o2 right operand (required) + * @return as specified by {@link Comparable} interface + */ + public static int compareAsIntegerKeys(final ByteBuf o1, final ByteBuf o2) { + requireNonNull(o1); + requireNonNull(o2); + // Both buffers should be same length according to LMDB API. + // From the LMDB docs for MDB_INTEGER_KEY + // numeric keys in native byte order: either unsigned int or size_t. The keys must all be of the + // same size. + final int len1 = o1.readableBytes(); + final int len2 = o2.readableBytes(); + if (len1 != len2) { + throw new RuntimeException( + "Length mismatch, len1: " + + len1 + + ", len2: " + + len2 + + ". Lengths must be identical and either 4 or 8 bytes."); + } + if (len1 == 8) { + final long lw; + final long rw; + if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) { + lw = o1.readLongLE(); + rw = o2.readLongLE(); + } else { + lw = o1.readLong(); + rw = o2.readLong(); + } + return Long.compareUnsigned(lw, rw); + } else if (len1 == 4) { + final int lw; + final int rw; + if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) { + lw = o1.readIntLE(); + rw = o2.readIntLE(); + } else { + lw = o1.readInt(); + rw = o2.readInt(); + } + return Integer.compareUnsigned(lw, rw); + } else { + return compareLexicographically(o1, o2); + } + } + static Field findField(final String c, final String name) { Class clazz; try { @@ -114,13 +173,12 @@ protected ByteBuf allocate() { } @Override - protected Comparator getSignedComparator() { - return comparator; - } - - @Override - protected Comparator getUnsignedComparator() { - return comparator; + public Comparator getComparator(final DbiFlagSet dbiFlagSet) { + if (dbiFlagSet.areAnySet(DbiFlagSet.INTEGER_KEY_FLAGS)) { + return ByteBufProxy::compareAsIntegerKeys; + } else { + return ByteBufProxy::compareLexicographically; + } } @Override diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index 3c80b995..52bfc924 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -27,6 +27,7 @@ import java.lang.reflect.Field; import java.nio.Buffer; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.ArrayDeque; import java.util.Comparator; import jnr.ffi.Pointer; @@ -57,6 +58,8 @@ public final class ByteBufferProxy { /** The safe, reflective {@link ByteBuffer} proxy for this system. Guaranteed to never be null. */ public static final BufferProxy PROXY_SAFE; + private static final ByteOrder NATIVE_ORDER = ByteOrder.nativeOrder(); + static { PROXY_SAFE = new ReflectiveProxy(); PROXY_OPTIMAL = getProxyOptimal(); @@ -83,6 +86,8 @@ public BufferMustBeDirectException() { } } + // -------------------------------------------------------------------------------- + /** * Provides {@link ByteBuffer} pooling and address resolution for concrete {@link BufferProxy} * implementations. @@ -92,16 +97,6 @@ abstract static class AbstractByteBufferProxy extends BufferProxy { protected static final String FIELD_NAME_ADDRESS = "address"; protected static final String FIELD_NAME_CAPACITY = "capacity"; - private static final Comparator signedComparator = - (o1, o2) -> { - requireNonNull(o1); - requireNonNull(o2); - - return o1.compareTo(o2); - }; - private static final Comparator unsignedComparator = - AbstractByteBufferProxy::compareBuff; - /** * A thread-safe pool for a given length. If the buffer found is valid (ie not of a negative * length) then that buffer is used. If no valid buffer is found, a new buffer is created. @@ -116,7 +111,7 @@ abstract static class AbstractByteBufferProxy extends BufferProxy { * @param o2 right operand (required) * @return as specified by {@link Comparable} interface */ - public static int compareBuff(final ByteBuffer o1, final ByteBuffer o2) { + public static int compareLexicographically(final ByteBuffer o1, final ByteBuffer o2) { requireNonNull(o1); requireNonNull(o2); final int minLength = Math.min(o1.limit(), o2.limit()); @@ -145,6 +140,55 @@ public static int compareBuff(final ByteBuffer o1, final ByteBuffer o2) { return o1.remaining() - o2.remaining(); } + /** + * Buffer comparator specifically for 4/8 byte keys that are unsigned ints/longs, i.e. when + * using MDB_INTEGER_KEY/MDB_INTEGERDUP. Compares the buffers numerically. + * + * @param o1 left operand (required) + * @param o2 right operand (required) + * @return as specified by {@link Comparable} interface + */ + public static int compareAsIntegerKeys(final ByteBuffer o1, final ByteBuffer o2) { + requireNonNull(o1); + requireNonNull(o2); + // Both buffers should be same length according to LMDB API. + // From the LMDB docs for MDB_INTEGER_KEY + // numeric keys in native byte order: either unsigned int or size_t. The keys must all be of + // the same size. + final int len1 = o1.limit(); + final int len2 = o2.limit(); + if (len1 != len2) { + throw new RuntimeException( + "Length mismatch, len1: " + + len1 + + ", len2: " + + len2 + + ". Lengths must be identical and either 4 or 8 bytes."); + } + // Keys for MDB_INTEGER_KEY are written in native order so ensure we read them in that order + o1.order(NATIVE_ORDER); + o2.order(NATIVE_ORDER); + // TODO it might be worth the DbiBuilder having a method to capture fixedKeyLength() or -1 + // for variable length keys. This can be passed to getComparator(..) so it can return a + // comparator that doesn't need to test the length every time. There may be other benefits + // to the Dbi knowing the key length if it is fixed. + if (len1 == 8) { + final long lw = o1.getLong(0); + final long rw = o2.getLong(0); + return Long.compareUnsigned(lw, rw); + } else if (len1 == 4) { + final int lw = o1.getInt(0); + final int rw = o2.getInt(0); + return Integer.compareUnsigned(lw, rw); + } else { + // size_t and int are likely to be 8bytes and 4bytes respectively on 64bit. + // If 32bit then would be 4/2 respectively. + // Short.compareUnsigned is not available in Java8. + // For now just fall back to our standard comparator + return compareLexicographically(o1, o2); + } + } + static Field findField(final Class c, final String name) { Class clazz = c; do { @@ -179,13 +223,12 @@ protected final ByteBuffer allocate() { } @Override - protected Comparator getSignedComparator() { - return signedComparator; - } - - @Override - protected Comparator getUnsignedComparator() { - return unsignedComparator; + public Comparator getComparator(final DbiFlagSet dbiFlagSet) { + if (dbiFlagSet.areAnySet(DbiFlagSet.INTEGER_KEY_FLAGS)) { + return AbstractByteBufferProxy::compareAsIntegerKeys; + } else { + return AbstractByteBufferProxy::compareLexicographically; + } } @Override @@ -203,6 +246,8 @@ protected byte[] getBytes(final ByteBuffer buffer) { } } + // -------------------------------------------------------------------------------- + /** * A proxy that uses Java reflection to modify byte buffer fields, and official JNR-FFF methods to * manipulate native pointers. @@ -247,6 +292,8 @@ protected ByteBuffer out(final ByteBuffer buffer, final Pointer ptr) { } } + // -------------------------------------------------------------------------------- + /** * A proxy that uses Java's "unsafe" class to directly manipulate byte buffer fields and JNR-FFF * allocated memory pointers. diff --git a/src/main/java/org/lmdbjava/CopyFlagSet.java b/src/main/java/org/lmdbjava/CopyFlagSet.java new file mode 100644 index 00000000..b80e5b38 --- /dev/null +++ b/src/main/java/org/lmdbjava/CopyFlagSet.java @@ -0,0 +1,63 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +import java.util.Collection; +import java.util.EnumSet; +import java.util.Objects; + +public interface CopyFlagSet extends FlagSet { + + static CopyFlagSet EMPTY = CopyFlagSetImpl.EMPTY; + + static CopyFlagSet empty() { + return CopyFlagSetImpl.EMPTY; + } + + static CopyFlagSet of(final CopyFlags dbiFlag) { + Objects.requireNonNull(dbiFlag); + return dbiFlag; + } + + static CopyFlagSet of(final CopyFlags... CopyFlags) { + return builder().withFlags(CopyFlags).build(); + } + + static CopyFlagSet of(final Collection CopyFlags) { + return builder().withFlags(CopyFlags).build(); + } + + static AbstractFlagSet.Builder builder() { + return new AbstractFlagSet.Builder<>( + CopyFlags.class, CopyFlagSetImpl::new, copyFlag -> copyFlag, () -> CopyFlagSetImpl.EMPTY); + } + + // -------------------------------------------------------------------------------- + + class CopyFlagSetImpl extends AbstractFlagSet implements CopyFlagSet { + + static final CopyFlagSet EMPTY = new EmptyCopyFlagSet(); + + private CopyFlagSetImpl(final EnumSet flags) { + super(flags); + } + } + + // -------------------------------------------------------------------------------- + + class EmptyCopyFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet + implements CopyFlagSet {} +} diff --git a/src/main/java/org/lmdbjava/CopyFlags.java b/src/main/java/org/lmdbjava/CopyFlags.java index 4365563c..b45dc87c 100644 --- a/src/main/java/org/lmdbjava/CopyFlags.java +++ b/src/main/java/org/lmdbjava/CopyFlags.java @@ -15,8 +15,12 @@ */ package org.lmdbjava; -/** Flags for use when performing a {@link Env#copy(java.io.File, org.lmdbjava.CopyFlags...)}. */ -public enum CopyFlags implements MaskedFlag { +import java.io.File; +import java.util.EnumSet; +import java.util.Set; + +/** Flags for use when performing a {@link Env#copy(File, CopyFlagSet)}. */ +public enum CopyFlags implements MaskedFlag, CopyFlagSet { /** Compacting copy: Omit free space from copy, and renumber all pages sequentially. */ MDB_CP_COMPACT(0x01); @@ -31,4 +35,29 @@ public enum CopyFlags implements MaskedFlag { public int getMask() { return mask; } + + @Override + public Set getFlags() { + return EnumSet.of(this); + } + + @Override + public boolean isSet(final CopyFlags flag) { + return flag != null && mask == flag.getMask(); + } + + @Override + public int size() { + return 1; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public String toString() { + return FlagSet.asString(this); + } } diff --git a/src/main/java/org/lmdbjava/Cursor.java b/src/main/java/org/lmdbjava/Cursor.java index f7fcbc41..f29c734f 100644 --- a/src/main/java/org/lmdbjava/Cursor.java +++ b/src/main/java/org/lmdbjava/Cursor.java @@ -20,8 +20,6 @@ import static org.lmdbjava.Dbi.KeyNotFoundException.MDB_NOTFOUND; import static org.lmdbjava.Env.SHOULD_CHECK; import static org.lmdbjava.Library.LIB; -import static org.lmdbjava.MaskedFlag.isSet; -import static org.lmdbjava.MaskedFlag.mask; import static org.lmdbjava.PutFlags.MDB_MULTIPLE; import static org.lmdbjava.PutFlags.MDB_NODUPDATA; import static org.lmdbjava.PutFlags.MDB_NOOVERWRITE; @@ -98,22 +96,40 @@ public long count() { return longByReference.longValue(); } + /** + * @deprecated Instead use {@link Cursor#delete(PutFlagSet)}.


Delete current key/data pair. + *

This function deletes the key/data pair to which the cursor refers. + * @param flags flags (either null or {@link PutFlags#MDB_NODUPDATA} + */ + @Deprecated + public void delete(final PutFlags... flags) { + delete(PutFlagSet.of(flags)); + } + + /** + * @deprecated Instead use {@link Cursor#delete(PutFlagSet)}.


Delete current key/data pair. + *

This function deletes the key/data pair to which the cursor refers. + */ + public void delete() { + delete(PutFlagSet.EMPTY); + } + /** * Delete current key/data pair. * *

This function deletes the key/data pair to which the cursor refers. * - * @param f flags (either null or {@link PutFlags#MDB_NODUPDATA} + * @param flags flags (either null or {@link PutFlags#MDB_NODUPDATA} */ - public void delete(final PutFlags... f) { + public void delete(final PutFlagSet flags) { if (SHOULD_CHECK) { env.checkNotClosed(); checkNotClosed(); txn.checkReady(); txn.checkWritesAllowed(); } - final int flags = mask(true, f); - checkRc(LIB.mdb_cursor_del(ptrCursor, flags)); + final PutFlagSet putFlagSet = flags != null ? flags : PutFlagSet.EMPTY; + checkRc(LIB.mdb_cursor_del(ptrCursor, putFlagSet.getMask())); } /** @@ -203,6 +219,10 @@ public T key() { return kv.key(); } + KeyVal keyVal() { + return kv; + } + /** * Position at last key/data item. * @@ -230,6 +250,20 @@ public boolean prev() { return seek(MDB_PREV); } + /** + * @deprecated Use {@link Cursor#put(Object, Object, PutFlagSet)} instead.


Store by cursor. + *

This function stores key/data pairs into the database. + * @param key key to store + * @param val data to store + * @param flags options for this operation + * @return true if the value was put, false if MDB_NOOVERWRITE or MDB_NODUPDATA were set and the + * key/value existed already. + */ + @Deprecated + public boolean put(final T key, final T val, final PutFlags... flags) { + return put(key, val, PutFlagSet.of(flags)); + } + /** * Store by cursor. * @@ -237,11 +271,25 @@ public boolean prev() { * * @param key key to store * @param val data to store - * @param op options for this operation * @return true if the value was put, false if MDB_NOOVERWRITE or MDB_NODUPDATA were set and the * key/value existed already. */ - public boolean put(final T key, final T val, final PutFlags... op) { + public boolean put(final T key, final T val) { + return put(key, val, PutFlagSet.EMPTY); + } + + /** + * Store by cursor. + * + *

This function stores key/data pairs into the database. + * + * @param key key to store + * @param val data to store + * @param flags options for this operation + * @return true if the value was put, false if MDB_NOOVERWRITE or MDB_NODUPDATA were set and the + * key/value existed already. + */ + public boolean put(final T key, final T val, final PutFlagSet flags) { if (SHOULD_CHECK) { requireNonNull(key); requireNonNull(val); @@ -252,12 +300,13 @@ public boolean put(final T key, final T val, final PutFlags... op) { } final Pointer transientKey = kv.keyIn(key); final Pointer transientVal = kv.valIn(val); - final int mask = mask(true, op); - final int rc = LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), mask); + final PutFlagSet putFlagSet = flags != null ? flags : PutFlagSet.EMPTY; + final int rc = + LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), putFlagSet.getMask()); if (rc == MDB_KEYEXIST) { - if (isSet(mask, MDB_NOOVERWRITE)) { + if (putFlagSet.isSet(MDB_NOOVERWRITE)) { kv.valOut(); // marked as in,out in LMDB C docs - } else if (!isSet(mask, MDB_NODUPDATA)) { + } else if (!putFlagSet.isSet(MDB_NODUPDATA)) { checkRc(rc); } return false; @@ -270,6 +319,39 @@ public boolean put(final T key, final T val, final PutFlags... op) { return true; } + /** + * @deprecated Use {@link Cursor#put(Object, Object, PutFlagSet)} instead.


Put multiple + * values into the database in one MDB_MULTIPLE operation. + *

The database must have been opened with {@link DbiFlags#MDB_DUPFIXED}. The buffer must + * contain fixed-sized values to be inserted. The size of each element is calculated from the + * buffer's size divided by the given element count. For example, to populate 10 X 4 byte + * integers at once, present a buffer of 40 bytes and specify the element as 10. + * @param key key to store in the database (not null) + * @param val value to store in the database (not null) + * @param elements number of elements contained in the passed value buffer + * @param flags options for operation (must set MDB_MULTIPLE) + */ + @Deprecated + public void putMultiple(final T key, final T val, final int elements, final PutFlags... flags) { + putMultiple(key, val, elements, PutFlagSet.of(flags)); + } + + /** + * Put multiple values into the database in one MDB_MULTIPLE operation. + * + *

The database must have been opened with {@link DbiFlags#MDB_DUPFIXED}. The buffer must + * contain fixed-sized values to be inserted. The size of each element is calculated from the + * buffer's size divided by the given element count. For example, to populate 10 X 4 byte integers + * at once, present a buffer of 40 bytes and specify the element as 10. + * + * @param key key to store in the database (not null) + * @param val value to store in the database (not null) + * @param elements number of elements contained in the passed value buffer + */ + public void putMultiple(final T key, final T val, final int elements) { + putMultiple(key, val, elements, PutFlagSet.EMPTY); + } + /** * Put multiple values into the database in one MDB_MULTIPLE operation. * @@ -281,9 +363,10 @@ public boolean put(final T key, final T val, final PutFlags... op) { * @param key key to store in the database (not null) * @param val value to store in the database (not null) * @param elements number of elements contained in the passed value buffer - * @param op options for operation (must set MDB_MULTIPLE) + * @param flags options for operation (must set MDB_MULTIPLE) Either a {@link + * PutFlagSet} or a single {@link PutFlags}. */ - public void putMultiple(final T key, final T val, final int elements, final PutFlags... op) { + public void putMultiple(final T key, final T val, final int elements, final PutFlagSet flags) { if (SHOULD_CHECK) { requireNonNull(txn); requireNonNull(key); @@ -292,13 +375,14 @@ public void putMultiple(final T key, final T val, final int elements, final PutF txn.checkReady(); txn.checkWritesAllowed(); } - final int mask = mask(true, op); - if (SHOULD_CHECK && !isSet(mask, MDB_MULTIPLE)) { + final PutFlagSet putFlagSet = flags != null ? flags : PutFlagSet.EMPTY; + if (SHOULD_CHECK && !putFlagSet.isSet(MDB_MULTIPLE)) { throw new IllegalArgumentException("Must set " + MDB_MULTIPLE + " flag"); } final Pointer transientKey = txn.kv().keyIn(key); final Pointer dataPtr = txn.kv().valInMulti(val, elements); - final int rc = LIB.mdb_cursor_put(ptrCursor, txn.kv().pointerKey(), dataPtr, mask); + final int rc = + LIB.mdb_cursor_put(ptrCursor, txn.kv().pointerKey(), dataPtr, putFlagSet.getMask()); checkRc(rc); ReferenceUtil.reachabilityFence0(transientKey); ReferenceUtil.reachabilityFence0(dataPtr); @@ -329,21 +413,56 @@ public void renew(final Txn newTxn) { this.txn = newTxn; } + /** + * @deprecated Use {@link Cursor#reserve(Object, int, PutFlagSet)} instead.


Reserve space for + * data of the given size, but don't copy the given val. Instead, return a pointer to the + * reserved space, which the caller can fill in later - before the next update operation or + * the transaction ends. This saves an extra memcpy if the data is being generated later. LMDB + * does nothing else with this memory, the caller is expected to modify all of the space + * requested. + *

This flag must not be specified if the database was opened with MDB_DUPSORT + * @param key key to store in the database (not null) + * @param size size of the value to be stored in the database (not null) + * @param flags options for this operation + * @return a buffer that can be used to modify the value + */ + @Deprecated + public T reserve(final T key, final int size, final PutFlags... flags) { + return reserve(key, size, PutFlagSet.of(flags)); + } + + /** + * Reserve space for data of the given size, but don't copy the given val. Instead, return a + * pointer to the reserved space, which the caller can fill in later - before the next update + * operation or the transaction ends. This saves an extra {@code memcpy} if the data is being + * generated later. LMDB does nothing else with this memory, the caller is expected to modify all + * the space requested. + * + *

This flag must not be specified if the database was opened with MDB_DUPSORT + * + * @param key key to store in the database (not null) + * @param size size of the value to be stored in the database (not null) + * @return a buffer that can be used to modify the value + */ + public T reserve(final T key, final int size) { + return reserve(key, size, PutFlagSet.EMPTY); + } + /** * Reserve space for data of the given size, but don't copy the given val. Instead, return a * pointer to the reserved space, which the caller can fill in later - before the next update * operation or the transaction ends. This saves an extra memcpy if the data is being generated - * later. LMDB does nothing else with this memory, the caller is expected to modify all of the - * space requested. + * later. LMDB does nothing else with this memory, the caller is expected to modify all the space + * requested. * *

This flag must not be specified if the database was opened with MDB_DUPSORT * * @param key key to store in the database (not null) * @param size size of the value to be stored in the database (not null) - * @param op options for this operation + * @param flags options for this operation * @return a buffer that can be used to modify the value */ - public T reserve(final T key, final int size, final PutFlags... op) { + public T reserve(final T key, final int size, final PutFlagSet flags) { if (SHOULD_CHECK) { requireNonNull(key); env.checkNotClosed(); @@ -353,8 +472,10 @@ public T reserve(final T key, final int size, final PutFlags... op) { } final Pointer transientKey = kv.keyIn(key); final Pointer transientVal = kv.valIn(size); - final int flags = mask(true, op) | MDB_RESERVE.getMask(); - checkRc(LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), flags)); + final PutFlagSet putFlagSet = flags != null ? flags : PutFlagSet.EMPTY; + // This is inconsistent with putMultiple which require MDB_MULTIPLE to be in the set. + final int flagsMask = putFlagSet.getMaskWith(MDB_RESERVE); + checkRc(LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), flagsMask)); kv.valOut(); ReferenceUtil.reachabilityFence0(transientKey); ReferenceUtil.reachabilityFence0(transientVal); diff --git a/src/main/java/org/lmdbjava/CursorIterable.java b/src/main/java/org/lmdbjava/CursorIterable.java index 80fd5ef3..5c7a1b3f 100644 --- a/src/main/java/org/lmdbjava/CursorIterable.java +++ b/src/main/java/org/lmdbjava/CursorIterable.java @@ -21,10 +21,14 @@ import static org.lmdbjava.CursorIterable.State.REQUIRES_NEXT_OP; import static org.lmdbjava.CursorIterable.State.TERMINATED; import static org.lmdbjava.GetOp.MDB_SET_RANGE; +import static org.lmdbjava.Library.LIB; import java.util.Comparator; import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.function.Supplier; +import jnr.ffi.Pointer; import org.lmdbjava.KeyRangeType.CursorOp; import org.lmdbjava.KeyRangeType.IteratorOp; @@ -38,7 +42,7 @@ */ public final class CursorIterable implements Iterable>, AutoCloseable { - private final Comparator comparator; + private final RangeComparator rangeComparator; private final Cursor cursor; private final KeyVal entry; private boolean iteratorReturned; @@ -46,16 +50,32 @@ public final class CursorIterable implements Iterable txn, final Dbi dbi, final KeyRange range, final Comparator comparator) { + final Txn txn, + final Dbi dbi, + final KeyRange range, + final Comparator comparator, + final BufferProxy proxy) { this.cursor = dbi.openCursor(txn); this.range = range; - this.comparator = comparator; this.entry = new KeyVal<>(); + + if (comparator != null) { + // User supplied Java-side comparator so use that + this.rangeComparator = new JavaRangeComparator<>(range, comparator, cursor::key); + } else { + // No Java-side comparator, so call down to LMDB to do the comparison + this.rangeComparator = new LmdbRangeComparator<>(txn, dbi, cursor, range, proxy); + } } @Override public void close() { cursor.close(); + try { + rangeComparator.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } } /** @@ -127,7 +147,7 @@ private void executeCursorOp(final CursorOp op) { // We need to ensure we move to the last matching key if using DUPSORT, see issue 267 boolean loop = true; while (loop) { - if (comparator.compare(cursor.key(), range.getStart()) <= 0) { + if (rangeComparator.compareToStartKey() <= 0) { found = cursor.next(); if (!found) { // We got to the end so move last. @@ -153,8 +173,7 @@ private void executeCursorOp(final CursorOp op) { } private void executeIteratorOp() { - final IteratorOp op = - range.getType().iteratorOp(range.getStart(), range.getStop(), entry.key(), comparator); + final IteratorOp op = range.getType().iteratorOp(entry.key(), rangeComparator); switch (op) { case CALL_NEXT_OP: executeCursorOp(range.getType().nextOp()); @@ -243,4 +262,104 @@ enum State { RELEASED, TERMINATED } + + // -------------------------------------------------------------------------------- + + static class JavaRangeComparator implements RangeComparator { + + private final Comparator comparator; + private final Supplier currentKeySupplier; + private final T start; + private final T stop; + + JavaRangeComparator( + final KeyRange range, + final Comparator comparator, + final Supplier currentKeySupplier) { + this.comparator = comparator; + this.currentKeySupplier = currentKeySupplier; + this.start = range.getStart(); + this.stop = range.getStop(); + } + + @Override + public int compareToStartKey() { + return comparator.compare(currentKeySupplier.get(), start); + } + + @Override + public int compareToStopKey() { + return comparator.compare(currentKeySupplier.get(), stop); + } + + @Override + public void close() throws Exception { + // Nothing to close + } + } + + // -------------------------------------------------------------------------------- + + /** + * Calls down to mdb_cmp to make use of the comparator that LMDB uses for insertion order. Has a + * very slight overhead as compared to {@link JavaRangeComparator}. + */ + private static class LmdbRangeComparator implements RangeComparator { + + private final Pointer txnPointer; + private final Pointer dbiPointer; + private final Pointer cursorKeyPointer; + private final Key startKey; + private final Key stopKey; + private final Pointer startKeyPointer; + private final Pointer stopKeyPointer; + + public LmdbRangeComparator( + final Txn txn, + final Dbi dbi, + final Cursor cursor, + final KeyRange range, + final BufferProxy proxy) { + txnPointer = Objects.requireNonNull(txn).pointer(); + dbiPointer = Objects.requireNonNull(dbi).pointer(); + cursorKeyPointer = Objects.requireNonNull(cursor).keyVal().pointerKey(); + // Allocate buffers for use with the start/stop keys if required. + // Saves us copying bytes on each comparison + Objects.requireNonNull(range); + startKey = createKey(range.getStart(), proxy); + stopKey = createKey(range.getStop(), proxy); + startKeyPointer = startKey != null ? startKey.pointer() : null; + stopKeyPointer = stopKey != null ? stopKey.pointer() : null; + } + + @Override + public int compareToStartKey() { + return LIB.mdb_cmp(txnPointer, dbiPointer, cursorKeyPointer, startKeyPointer); + } + + @Override + public int compareToStopKey() { + return LIB.mdb_cmp(txnPointer, dbiPointer, cursorKeyPointer, stopKeyPointer); + } + + @Override + public void close() { + if (startKey != null) { + startKey.close(); + } + if (stopKey != null) { + stopKey.close(); + } + } + + private Key createKey(final T keyBuffer, final BufferProxy proxy) { + if (keyBuffer != null) { + final Key key = proxy.key(); + key.keyIn(keyBuffer); + return key; + } else { + return null; + } + } + } } diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index 5449c172..43fb335a 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -31,6 +31,7 @@ import static org.lmdbjava.PutFlags.MDB_RESERVE; import static org.lmdbjava.ResultCodeMapper.checkRc; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -48,12 +49,15 @@ */ public final class Dbi { - private final ComparatorCallback ccb; + private final ComparatorCallback callbackComparator; private boolean cleaned; + // Used for CursorIterable KeyRange testing and/or native callbacks private final Comparator comparator; private final Env env; private final byte[] name; private final Pointer ptr; + private final BufferProxy proxy; + private final DbiFlagSet dbiFlagSet; Dbi( final Env env, @@ -62,24 +66,34 @@ public final class Dbi { final Comparator comparator, final boolean nativeCb, final BufferProxy proxy, - final DbiFlags... flags) { + final DbiFlagSet dbiFlagSet) { if (SHOULD_CHECK) { requireNonNull(txn); txn.checkReady(); } this.env = env; this.name = name == null ? null : Arrays.copyOf(name, name.length); - if (comparator == null) { - this.comparator = proxy.getComparator(flags); - } else { - this.comparator = comparator; - } - final int flagsMask = mask(true, flags); + this.proxy = proxy; + this.comparator = comparator; + this.dbiFlagSet = dbiFlagSet; final Pointer dbiPtr = allocateDirect(RUNTIME, ADDRESS); - checkRc(LIB.mdb_dbi_open(txn.pointer(), name, flagsMask, dbiPtr)); + checkRc(LIB.mdb_dbi_open(txn.pointer(), name, dbiFlagSet.getMask(), dbiPtr)); ptr = dbiPtr.getPointer(0); if (nativeCb) { - this.ccb = + requireNonNull(comparator, "comparator cannot be null if nativeCb is set"); + // LMDB will call back to this comparator for insertion/iteration order + // if (dbiFlagSet.areAnySet(DbiFlagSet.INTEGER_KEY_FLAGS)) { + // this.callbackComparator = + // (keyA, keyB) -> { + // final T compKeyA = proxy.out(proxy.allocate(), keyA); + // final T compKeyB = proxy.out(proxy.allocate(), keyB); + // final int result = this.comparator.compare(compKeyA, compKeyB); + // proxy.deallocate(compKeyA); + // proxy.deallocate(compKeyB); + // return result; + // }; + // } else { + this.callbackComparator = (keyA, keyB) -> { final T compKeyA = proxy.out(proxy.allocate(), keyA); final T compKeyB = proxy.out(proxy.allocate(), keyB); @@ -88,12 +102,17 @@ public final class Dbi { proxy.deallocate(compKeyB); return result; }; - LIB.mdb_set_compare(txn.pointer(), ptr, ccb); + // } + LIB.mdb_set_compare(txn.pointer(), ptr, callbackComparator); } else { - ccb = null; + callbackComparator = null; } } + Pointer pointer() { + return ptr; + } + /** * Close the database handle (normally unnecessary; use with caution). * @@ -254,6 +273,30 @@ public byte[] getName() { return name == null ? null : Arrays.copyOf(name, name.length); } + public String getNameAsString() { + return getNameAsString(Env.DEFAULT_NAME_CHARSET); + } + + /** + * Obtains the name of this database, using the supplied {@link Charset}. + * + * @return The name of the database. If this is the unnamed database an empty string will be + * returned. + * @throws RuntimeException if the name can't be decoded. + */ + public String getNameAsString(final Charset charset) { + if (name == null) { + return ""; + } else { + // Assume a UTF8 encoding as we don't know, thus swallow if it fails + try { + return new String(name, requireNonNull(charset)); + } catch (Exception e) { + throw new RuntimeException("Unable to decode database name using charset " + charset); + } + } + } + /** * Iterate the database from the first item and forwards. * @@ -278,7 +321,7 @@ public CursorIterable iterate(final Txn txn, final KeyRange range) { env.checkNotClosed(); txn.checkReady(); } - return new CursorIterable<>(txn, this, range, comparator); + return new CursorIterable<>(txn, this, range, comparator, proxy); } /** @@ -288,6 +331,7 @@ public CursorIterable iterate(final Txn txn, final KeyRange range) { * @return the list of flags this Dbi was created with */ public List listFlags(final Txn txn) { + // TODO we could just return what is in dbiFlagSet, rather than hitting LMDB. if (SHOULD_CHECK) { env.checkNotClosed(); } @@ -337,15 +381,48 @@ public Cursor openCursor(final Txn txn) { * * @param key key to store in the database (not null) * @param val value to store in the database (not null) - * @see #put(org.lmdbjava.Txn, java.lang.Object, java.lang.Object, org.lmdbjava.PutFlags...) + * @see #put(Txn, Object, Object, PutFlagSet) */ public void put(final T key, final T val) { try (Txn txn = env.txnWrite()) { - put(txn, key, val); + put(txn, key, val, PutFlagSet.EMPTY); txn.commit(); } } + /** + * @deprecated Use {@link Dbi#put(Txn, Object, Object, PutFlagSet)} instead, with a statically + * held {@link PutFlagSet}.


+ *

Store a key/value pair in the database. + *

This function stores key/data pairs in the database. The default behavior is to enter + * the new key/data pair, replacing any previously existing key if duplicates are disallowed, + * or adding a duplicate data item if duplicates are allowed ({@link DbiFlags#MDB_DUPSORT}). + * @param txn transaction handle (not null; not committed; must be R-W) + * @param key key to store in the database (not null) + * @param val value to store in the database (not null) + * @param flags Special options for this operation + * @return true if the value was put, false if MDB_NOOVERWRITE or MDB_NODUPDATA were set and the + * key/value existed already. + */ + @Deprecated + public boolean put(final Txn txn, final T key, final T val, final PutFlags... flags) { + return put(txn, key, val, PutFlagSet.of(flags)); + } + + /** + * Store a key/value pair in the database. + * + * @param txn transaction handle (not null; not committed; must be R-W) + * @param key key to store in the database (not null) + * @param val value to store in the database (not null) + * @return true if the value was put, false if MDB_NOOVERWRITE or MDB_NODUPDATA were set and the + * key/value existed already. + * @see #put(Txn, Object, Object, PutFlagSet) + */ + public boolean put(final Txn txn, final T key, final T val) { + return put(txn, key, val, PutFlagSet.EMPTY); + } + /** * Store a key/value pair in the database. * @@ -356,11 +433,11 @@ public void put(final T key, final T val) { * @param txn transaction handle (not null; not committed; must be R-W) * @param key key to store in the database (not null) * @param val value to store in the database (not null) - * @param flags Special options for this operation + * @param flags Special options for this operation. * @return true if the value was put, false if MDB_NOOVERWRITE or MDB_NODUPDATA were set and the * key/value existed already. */ - public boolean put(final Txn txn, final T key, final T val, final PutFlags... flags) { + public boolean put(final Txn txn, final T key, final T val, final PutFlagSet flags) { if (SHOULD_CHECK) { requireNonNull(txn); requireNonNull(key); @@ -369,15 +446,16 @@ public boolean put(final Txn txn, final T key, final T val, final PutFlags... txn.checkReady(); txn.checkWritesAllowed(); } + final PutFlagSet flagSet = flags != null ? flags : PutFlagSet.empty(); final Pointer transientKey = txn.kv().keyIn(key); final Pointer transientVal = txn.kv().valIn(val); - final int mask = mask(true, flags); final int rc = - LIB.mdb_put(txn.pointer(), ptr, txn.kv().pointerKey(), txn.kv().pointerVal(), mask); + LIB.mdb_put( + txn.pointer(), ptr, txn.kv().pointerKey(), txn.kv().pointerVal(), flagSet.getMask()); if (rc == MDB_KEYEXIST) { - if (isSet(mask, MDB_NOOVERWRITE)) { + if (flagSet.isSet(MDB_NOOVERWRITE)) { txn.kv().valOut(); // marked as in,out in LMDB C docs - } else if (!isSet(mask, MDB_NODUPDATA)) { + } else if (!flagSet.isSet(MDB_NODUPDATA)) { checkRc(rc); } return false; @@ -415,7 +493,7 @@ public T reserve(final Txn txn, final T key, final int size, final PutFlags.. } final Pointer transientKey = txn.kv().keyIn(key); final Pointer transientVal = txn.kv().valIn(size); - final int flags = mask(true, op) | MDB_RESERVE.getMask(); + final int flags = mask(op) | MDB_RESERVE.getMask(); checkRc(LIB.mdb_put(txn.pointer(), ptr, txn.kv().pointerKey(), txn.kv().pointerVal(), flags)); txn.kv().valOut(); // marked as in,out in LMDB C docs ReferenceUtil.reachabilityFence0(transientKey); @@ -454,6 +532,17 @@ private void clean() { cleaned = true; } + @Override + public String toString() { + String name; + try { + name = getNameAsString(); + } catch (Exception e) { + name = "?"; + } + return "Dbi{" + "name='" + name + "', dbiFlagSet=" + dbiFlagSet + '}'; + } + /** The specified DBI was changed unexpectedly. */ public static final class BadDbiException extends LmdbNativeException { diff --git a/src/main/java/org/lmdbjava/DbiBuilder.java b/src/main/java/org/lmdbjava/DbiBuilder.java new file mode 100644 index 00000000..bf318f75 --- /dev/null +++ b/src/main/java/org/lmdbjava/DbiBuilder.java @@ -0,0 +1,426 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Objects; + +/** + * Staged builder for building a {@link Dbi} + * + * @param buffer type + */ +public class DbiBuilder { + + private final Env env; + private final BufferProxy proxy; + private final boolean readOnly; + private byte[] name; + + DbiBuilder(final Env env, final BufferProxy proxy, final boolean readOnly) { + this.env = Objects.requireNonNull(env); + this.proxy = Objects.requireNonNull(proxy); + this.readOnly = readOnly; + } + + /** + * Create the {@link Dbi} with the passed name. + * + *

The name will be converted into bytes using {@link StandardCharsets#UTF_8}. + * + * @param name The name of the database or null for the unnamed database (see also {@link + * DbiBuilder#withoutDbName()}) + * @return The next builder stage. + */ + public DbiBuilderStage2 setDbName(final String name) { + // Null name is allowed so no null check + final byte[] nameBytes = name == null ? null : name.getBytes(Env.DEFAULT_NAME_CHARSET); + return setDbName(nameBytes); + } + + /** + * Create the {@link Dbi} with the passed name in byte[] form. + * + * @param name The name of the database in byte form. + * @return The next builder stage. + */ + public DbiBuilderStage2 setDbName(final byte[] name) { + // Null name is allowed so no null check + this.name = name; + return new DbiBuilderStage2<>(this); + } + + /** + * Create the {@link Dbi} without a name. + * + *

Equivalent to passing null to {@link DbiBuilder#setDbName(String)} or {@link + * DbiBuilder#setDbName(byte[])}. + * + *

Note: The 'unnamed database' is used by LMDB to store the names of named databases, with the + * database name being the key. Use of the unnamed database is intended for simple applications + * with only one database. + * + * @return The next builder stage. + */ + public DbiBuilderStage2 withoutDbName() { + return setDbName((byte[]) null); + } + + // -------------------------------------------------------------------------------- + + /** + * Intermediate builder stage for constructing a {@link Dbi}. + * + * @param buffer type + */ + public static class DbiBuilderStage2 { + + private final DbiBuilder dbiBuilder; + + private ComparatorFactory comparatorFactory; + private ComparatorType comparatorType; + + private DbiBuilderStage2(final DbiBuilder dbiBuilder) { + this.dbiBuilder = dbiBuilder; + } + + /** + * This is the default choice when it comes to choosing a comparator. If you + * are not sure of the implications of the other methods then use this one as it is likely what + * you want and also probably the most performant. + * + *

With this option, {@link CursorIterable} will make use of the LmdbJava's default Java-side + * comparators when comparing iteration keys to the start/stop keys. LMDB will use its own + * comparator for controlling insertion order in the database. The two comparators are + * functionally identical. + * + *

This option may be slightly more performant than when using {@link + * DbiBuilderStage2#withNativeComparator()} which calls down to LMDB for ALL comparison + * operations. + * + *

If you do not intend to use {@link CursorIterable} then it doesn't matter whether you + * choose {@link DbiBuilderStage2#withNativeComparator()}, {@link + * DbiBuilderStage2#withDefaultComparator()} or {@link + * DbiBuilderStage2#withIteratorComparator(ComparatorFactory)} as these comparators will never + * be used. + * + * @return The next builder stage. + */ + public DbiBuilderStage3 withDefaultComparator() { + this.comparatorType = ComparatorType.DEFAULT; + return new DbiBuilderStage3<>(this); + } + + /** + * With this option, {@link CursorIterable} will call down to LMDB's {@code mdb_cmp} method when + * comparing iteration keys to start/stop keys. This ensures LmdbJava is comparing start/stop + * keys using the same comparator that is used for insertion order into the db. + * + *

This option may be slightly less performant than when using {@link + * DbiBuilderStage2#withDefaultComparator()} as it needs to call down to LMDB to perform the + * comparisons, however it guarantees that {@link CursorIterable} key comparison matches LMDB + * key comparison. + * + *

If you do not intend to use {@link CursorIterable} then it doesn't matter whether you + * choose {@link DbiBuilderStage2#withNativeComparator()}, {@link + * DbiBuilderStage2#withDefaultComparator()} or {@link + * DbiBuilderStage2#withIteratorComparator(ComparatorFactory)} as these comparators will never + * be used. + * + * @return The next builder stage. + */ + public DbiBuilderStage3 withNativeComparator() { + this.comparatorType = ComparatorType.NATIVE; + return new DbiBuilderStage3<>(this); + } + + /** + * Provide a java-side {@link Comparator} that LMDB will call back to for all + * comparison operations. Therefore, it will be called by LMDB to manage database + * insertion/iteration order. It will also be used for {@link CursorIterable} start/stop key + * comparisons. + * + *

It can be useful if you need to sort your database using some other method, e.g. signed + * keys or case-insensitive order. Note, if you need keys stored in reverse order, see {@link + * DbiFlags#MDB_REVERSEKEY} and {@link DbiFlags#MDB_REVERSEDUP}. + * + *

As this requires LMDB to call back to java, this will be less performant than using LMDB's + * default comparators, but allows for total control over the order in which entries are stored + * in the database. + * + * @param comparatorFactory A factory to create a comparator. {@link + * ComparatorFactory#create(DbiFlagSet)} will be called once during the initialisation of + * the {@link Dbi}. It must not return null. + * @return The next builder stage. + */ + public DbiBuilderStage3 withCallbackComparator( + final ComparatorFactory comparatorFactory) { + this.comparatorFactory = Objects.requireNonNull(comparatorFactory); + this.comparatorType = ComparatorType.CALLBACK; + return new DbiBuilderStage3<>(this); + } + + /** + *


+ * + *

WARNING: Only use this if you fully understand the risks and + * implications.


+ * + *

With this option, {@link CursorIterable} will make use of the passed comparator for + * comparing iteration keys to start/stop keys. It has NO bearing on the + * insert/iteration order of the database (which is controlled by LMDB's own comparators). + * + *

It is vital that this comparator is functionally identical to the one + * used internally in LMDB for insertion/iteration order, else you will see unexpected behaviour + * when using {@link CursorIterable}. + * + *

If you do not intend to use {@link CursorIterable} then it doesn't matter whether you + * choose {@link DbiBuilderStage2#withNativeComparator()}, {@link + * DbiBuilderStage2#withDefaultComparator()} or {@link + * DbiBuilderStage2#withIteratorComparator(ComparatorFactory)} as these comparators will never + * be used. + * + * @param comparatorFactory The comparator to use with {@link CursorIterable}. {@link + * ComparatorFactory#create(DbiFlagSet)} will be called once during the initialisation of + * the {@link Dbi}. It must not return null. + * @return The next builder stage. + */ + public DbiBuilderStage3 withIteratorComparator( + final ComparatorFactory comparatorFactory) { + this.comparatorFactory = Objects.requireNonNull(comparatorFactory); + this.comparatorType = ComparatorType.ITERATOR; + return new DbiBuilderStage3<>(this); + } + } + + // -------------------------------------------------------------------------------- + + /** + * Final stage builder for constructing a {@link Dbi}. + * + * @param buffer type + */ + public static class DbiBuilderStage3 { + + private final DbiBuilderStage2 dbiBuilderStage2; + private final AbstractFlagSet.Builder flagSetBuilder = + DbiFlagSet.builder(); + private Txn txn = null; + + private DbiBuilderStage3(DbiBuilderStage2 dbiBuilderStage2) { + this.dbiBuilderStage2 = dbiBuilderStage2; + } + + /** + * Apply all the dbi flags supplied in dbiFlags. + * + *

Clears all flags currently set by previous calls to {@link + * DbiBuilderStage3#setDbiFlags(Collection)}, {@link DbiBuilderStage3#setDbiFlags(DbiFlags...)} + * or {@link DbiBuilderStage3#addDbiFlag(DbiFlags)}. + * + * @param dbiFlags to open the database with. A null {@link Collection} will just clear all set + * flags. Null items are ignored. + */ + public DbiBuilderStage3 setDbiFlags(final Collection dbiFlags) { + flagSetBuilder.clear(); + if (dbiFlags != null) { + dbiFlags.stream().filter(Objects::nonNull).forEach(dbiFlags::add); + } + return this; + } + + /** + * Apply all the dbi flags supplied in dbiFlags. + * + *

Clears all flags currently set by previous calls to {@link + * DbiBuilderStage3#setDbiFlags(Collection)}, {@link DbiBuilderStage3#setDbiFlags(DbiFlags...)} + * or {@link DbiBuilderStage3#addDbiFlag(DbiFlags)}. + * + * @param dbiFlags to open the database with. A null array will just clear all set flags. Null + * items are ignored. + */ + public DbiBuilderStage3 setDbiFlags(final DbiFlags... dbiFlags) { + flagSetBuilder.clear(); + if (dbiFlags != null) { + Arrays.stream(dbiFlags).filter(Objects::nonNull).forEach(this.flagSetBuilder::setFlag); + } + return this; + } + + /** + * Apply all the dbi flags supplied in dbiFlags. + * + *

Clears all flags currently set by previous calls to {@link + * DbiBuilderStage3#setDbiFlags(Collection)}, {@link DbiBuilderStage3#setDbiFlags(DbiFlags...)} + * or {@link DbiBuilderStage3#addDbiFlag(DbiFlags)}. + * + * @param dbiFlagSet to open the database with. A null value will just clear all set flags. + */ + public DbiBuilderStage3 setDbiFlags(final DbiFlagSet dbiFlagSet) { + flagSetBuilder.clear(); + if (dbiFlagSet != null) { + this.flagSetBuilder.withFlags(dbiFlagSet.getFlags()); + } + return this; + } + + /** + * Adds a dbiFlag to those flags already added to this builder by {@link + * DbiBuilderStage3#setDbiFlags(DbiFlags...)}, {@link DbiBuilderStage3#setDbiFlags(Collection)} + * or {@link DbiBuilderStage3#addDbiFlag(DbiFlags)}. + * + * @param dbiFlag to add to any existing flags. A null value is a no-op. + * @return this builder instance. + */ + public DbiBuilderStage3 addDbiFlag(final DbiFlags dbiFlag) { + this.flagSetBuilder.setFlag(dbiFlag); + return this; + } + + /** + * Adds a dbiFlag to those flags already added to this builder by {@link + * DbiBuilderStage3#setDbiFlags(DbiFlags...)}, {@link DbiBuilderStage3#setDbiFlags(Collection)} + * or {@link DbiBuilderStage3#addDbiFlag(DbiFlags)}. + * + * @param dbiFlagSet to add to any existing flags. A null value is a no-op. + * @return this builder instance. + */ + public DbiBuilderStage3 addDbiFlags(final DbiFlagSet dbiFlagSet) { + if (dbiFlagSet != null) { + flagSetBuilder.setFlags(dbiFlagSet.getFlags()); + } + return this; + } + + /** + * Use the supplied transaction to open the {@link Dbi}. + * + *

The caller MUST commit the transaction after calling {@link DbiBuilderStage3#open()}, in + * order to retain the Dbi in the Env. + * + *

If you don't call this method to supply a {@link Txn}, a {@link Txn} will be opened for + * the purpose of creating and opening the {@link Dbi}, then closed. Therefore, if you already + * have a transaction open, you should supply that to avoid one blocking the other. + * + * @param txn transaction to use (required; not closed). If the {@link Env} was opened with the + * {@link EnvFlags#MDB_RDONLY_ENV} flag, the {@link Txn} can be read-only, else it needs to + * be a read/write {@link Txn}. + * @return this builder instance. + */ + public DbiBuilderStage3 setTxn(final Txn txn) { + this.txn = Objects.requireNonNull(txn); + return this; + } + + /** + * Construct and open the {@link Dbi}. + * + *

If a {@link Txn} was supplied to the builder, it is the callers responsibility to commit + * and close the txn upon return from this method, else the created DB won't be retained. + * + * @return A newly constructed and opened {@link Dbi}. + */ + public Dbi open() { + final DbiBuilder dbiBuilder = dbiBuilderStage2.dbiBuilder; + if (txn != null) { + return open(txn, dbiBuilder); + } else { + try (final Txn txn = getTxn(dbiBuilder)) { + final Dbi dbi = open(txn, dbiBuilder); + // even RO Txns require a commit to retain Dbi in Env + txn.commit(); + return dbi; + } + } + } + + private Txn getTxn(final DbiBuilder dbiBuilder) { + return dbiBuilder.readOnly ? dbiBuilder.env.txnRead() : dbiBuilder.env.txnWrite(); + } + + private Comparator getComparator( + final DbiBuilder dbiBuilder, + final ComparatorType comparatorType, + final DbiFlagSet dbiFlagSet) { + Comparator comparator = null; + switch (comparatorType) { + case DEFAULT: + // Get the appropriate default CursorIterable comparator based on the DbiFlags, + // e.g. MDB_INTEGERKEY may benefit from an optimised comparator. + comparator = dbiBuilder.proxy.getComparator(dbiFlagSet); + break; + case CALLBACK: + case ITERATOR: + comparator = + Objects.requireNonNull( + dbiBuilderStage2.comparatorFactory.create(dbiFlagSet), + () -> "comparatorFactory returned null"); + break; + case NATIVE: + break; + default: + throw new IllegalStateException("Unexpected comparatorType " + comparatorType); + } + return comparator; + } + + private Dbi open(final Txn txn, final DbiBuilder dbiBuilder) { + final DbiFlagSet dbiFlagSet = flagSetBuilder.build(); + final ComparatorType comparatorType = dbiBuilderStage2.comparatorType; + final Comparator comparator = getComparator(dbiBuilder, comparatorType, dbiFlagSet); + final boolean useNativeCallback = comparatorType == ComparatorType.CALLBACK; + return new Dbi<>( + dbiBuilder.env, + txn, + dbiBuilder.name, + comparator, + useNativeCallback, + dbiBuilder.proxy, + dbiFlagSet); + } + } + + // -------------------------------------------------------------------------------- + + private enum ComparatorType { + /** + * Default Java comparator for {@link CursorIterable} KeyRange testing, LMDB comparator for + * insertion/iteration order. + */ + DEFAULT, + /** Use LMDB native comparator for everything. */ + NATIVE, + /** Use the supplied custom Java-side comparator for everything. */ + CALLBACK, + /** + * Use the supplied custom Java-side comparator for {@link CursorIterable} KeyRange testing, + * LMDB comparator for insertion/iteration order. + */ + ITERATOR, + ; + } + + // -------------------------------------------------------------------------------- + + @FunctionalInterface + public interface ComparatorFactory { + + Comparator create(final DbiFlagSet dbiFlagSet); + } +} diff --git a/src/main/java/org/lmdbjava/DbiFlagSet.java b/src/main/java/org/lmdbjava/DbiFlagSet.java new file mode 100644 index 00000000..6fcdfd37 --- /dev/null +++ b/src/main/java/org/lmdbjava/DbiFlagSet.java @@ -0,0 +1,67 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +import java.util.Collection; +import java.util.EnumSet; +import java.util.Objects; + +public interface DbiFlagSet extends FlagSet { + + /** An immutable empty {@link DbiFlagSet}. */ + DbiFlagSet EMPTY = DbiFlagSetImpl.EMPTY; + + /** The set of {@link DbiFlags} that indicate unsigned integer keys are being used. */ + DbiFlagSet INTEGER_KEY_FLAGS = DbiFlagSet.of(DbiFlags.MDB_INTEGERKEY, DbiFlags.MDB_INTEGERDUP); + + static DbiFlagSet empty() { + return DbiFlagSetImpl.EMPTY; + } + + static DbiFlagSet of(final DbiFlags dbiFlag) { + Objects.requireNonNull(dbiFlag); + return dbiFlag; + } + + static DbiFlagSet of(final DbiFlags... DbiFlags) { + return builder().withFlags(DbiFlags).build(); + } + + static DbiFlagSet of(final Collection DbiFlags) { + return builder().withFlags(DbiFlags).build(); + } + + static AbstractFlagSet.Builder builder() { + return new AbstractFlagSet.Builder<>( + DbiFlags.class, DbiFlagSetImpl::new, dbiFlag -> dbiFlag, () -> DbiFlagSetImpl.EMPTY); + } + + // -------------------------------------------------------------------------------- + + class DbiFlagSetImpl extends AbstractFlagSet implements DbiFlagSet { + + static final DbiFlagSet EMPTY = new EmptyDbiFlagSet(); + + private DbiFlagSetImpl(final EnumSet flags) { + super(flags); + } + } + + // -------------------------------------------------------------------------------- + + class EmptyDbiFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet + implements DbiFlagSet {} +} diff --git a/src/main/java/org/lmdbjava/DbiFlags.java b/src/main/java/org/lmdbjava/DbiFlags.java index 123ec9fd..f8ccbe20 100644 --- a/src/main/java/org/lmdbjava/DbiFlags.java +++ b/src/main/java/org/lmdbjava/DbiFlags.java @@ -15,8 +15,11 @@ */ package org.lmdbjava; +import java.util.EnumSet; +import java.util.Set; + /** Flags for use when opening a {@link Dbi}. */ -public enum DbiFlags implements MaskedFlag { +public enum DbiFlags implements MaskedFlag, DbiFlagSet { /** * Use reverse string keys. @@ -29,13 +32,26 @@ public enum DbiFlags implements MaskedFlag { * Use sorted duplicates. * *

Duplicate keys may be used in the database. Or, from another perspective, keys may have - * multiple data items, stored in sorted order. By default keys must be unique and may have only a - * single data item. + * multiple data items, stored in sorted order. By default, keys must be unique and may have only + * a single data item. + * + *

*/ MDB_DUPSORT(0x04), /** - * Numeric keys in native byte order: either unsigned int or size_t. The keys must all be of the - * same size. + * Numeric keys in native byte order: either unsigned int or size_t. The keys must all be + * of the same size. + * + *

This is an optimisation that is available when your keys are 4 or 8 byte unsigned numeric + * values. There are performance benefits for both ordered and un-ordered puts as compared to not + * using this flag. + * + *

When writing the key to the buffer you must write it in native order and subsequently read + * any keys retrieved from LMDB (via cursor or get method) also using native order. + * + *

For more information, see Numeric Keys in the + * LmdbJava wiki. */ MDB_INTEGERKEY(0x08), /** @@ -55,14 +71,6 @@ public enum DbiFlags implements MaskedFlag { * #MDB_INTEGERKEY} keys. */ MDB_INTEGERDUP(0x20), - /** - * Compare the numeric keys in native byte order and as unsigned. - * - *

This option is applied only to {@link java.nio.ByteBuffer}, {@link org.agrona.DirectBuffer} - * and byte array keys. {@link io.netty.buffer.ByteBuf} keys are always compared in native byte - * order and as unsigned. - */ - MDB_UNSIGNEDKEY(0x30, false), /** * With {@link #MDB_DUPSORT}, use reverse string dups. * @@ -78,15 +86,9 @@ public enum DbiFlags implements MaskedFlag { MDB_CREATE(0x4_0000); private final int mask; - private final boolean propagatedToLmdb; - - DbiFlags(final int mask, final boolean propagatedToLmdb) { - this.mask = mask; - this.propagatedToLmdb = propagatedToLmdb; - } DbiFlags(final int mask) { - this(mask, true); + this.mask = mask; } @Override @@ -95,7 +97,27 @@ public int getMask() { } @Override - public boolean isPropagatedToLmdb() { - return propagatedToLmdb; + public Set getFlags() { + return EnumSet.of(this); + } + + @Override + public boolean isSet(final DbiFlags flag) { + return flag != null && mask == flag.getMask(); + } + + @Override + public int size() { + return 1; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public String toString() { + return FlagSet.asString(this); } } diff --git a/src/main/java/org/lmdbjava/DirectBufferProxy.java b/src/main/java/org/lmdbjava/DirectBufferProxy.java index 524b81b8..af918943 100644 --- a/src/main/java/org/lmdbjava/DirectBufferProxy.java +++ b/src/main/java/org/lmdbjava/DirectBufferProxy.java @@ -22,6 +22,7 @@ import static org.lmdbjava.UnsafeAccess.UNSAFE; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.util.ArrayDeque; import java.util.Comparator; import jnr.ffi.Pointer; @@ -35,14 +36,6 @@ *

This class requires {@link UnsafeAccess} and Agrona must be in the classpath. */ public final class DirectBufferProxy extends BufferProxy { - private static final Comparator signedComparator = - (o1, o2) -> { - requireNonNull(o1); - requireNonNull(o2); - - return o1.compareTo(o2); - }; - private static final Comparator unsignedComparator = DirectBufferProxy::compareBuff; /** * The {@link MutableDirectBuffer} proxy. Guaranteed to never be null, although a class @@ -58,6 +51,8 @@ public final class DirectBufferProxy extends BufferProxy { private static final ThreadLocal> BUFFERS = withInitial(() -> new ArrayDeque<>(16)); + private static final ByteOrder NATIVE_ORDER = ByteOrder.nativeOrder(); + private DirectBufferProxy() {} /** @@ -67,7 +62,7 @@ private DirectBufferProxy() {} * @param o2 right operand (required) * @return as specified by {@link Comparable} interface */ - public static int compareBuff(final DirectBuffer o1, final DirectBuffer o2) { + public static int compareLexicographically(final DirectBuffer o1, final DirectBuffer o2) { requireNonNull(o1); requireNonNull(o2); @@ -95,6 +90,47 @@ public static int compareBuff(final DirectBuffer o1, final DirectBuffer o2) { return o1.capacity() - o2.capacity(); } + /** + * Buffer comparator specifically for 4/8 byte keys that are unsigned ints/longs, i.e. when using + * MDB_INTEGER_KEY/MDB_INTEGERDUP. Compares the buffers numerically. + * + *

Both buffer must have 4 or 8 bytes remaining + * + * @param o1 left operand (required) + * @param o2 right operand (required) + * @return as specified by {@link Comparable} interface + */ + public static int compareAsIntegerKeys(final DirectBuffer o1, final DirectBuffer o2) { + requireNonNull(o1); + requireNonNull(o2); + // Both buffers should be same len + final int len1 = o1.capacity(); + final int len2 = o2.capacity(); + if (len1 != len2) { + throw new RuntimeException( + "Length mismatch, len1: " + + len1 + + ", len2: " + + len2 + + ". Lengths must be identical and either 4 or 8 bytes."); + } + if (len1 == 8) { + final long lw = o1.getLong(0, NATIVE_ORDER); + final long rw = o2.getLong(0, NATIVE_ORDER); + return Long.compareUnsigned(lw, rw); + } else if (len1 == 4) { + final int lw = o1.getInt(0, NATIVE_ORDER); + final int rw = o2.getInt(0, NATIVE_ORDER); + return Integer.compareUnsigned(lw, rw); + } else { + // size_t and int are likely to be 8bytes and 4bytes respectively on 64bit. + // If 32bit then would be 4/2 respectively. + // Short.compareUnsigned is not available in Java8. + // For now just fall back to our standard comparator + return compareLexicographically(o1, o2); + } + } + @Override protected DirectBuffer allocate() { final ArrayDeque q = BUFFERS.get(); @@ -109,13 +145,12 @@ protected DirectBuffer allocate() { } @Override - protected Comparator getSignedComparator() { - return signedComparator; - } - - @Override - protected Comparator getUnsignedComparator() { - return unsignedComparator; + public Comparator getComparator(final DbiFlagSet dbiFlagSet) { + if (dbiFlagSet.areAnySet(DbiFlagSet.INTEGER_KEY_FLAGS)) { + return DirectBufferProxy::compareAsIntegerKeys; + } else { + return DirectBufferProxy::compareLexicographically; + } } @Override diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index 3db16119..480ee8b7 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -23,17 +23,20 @@ import static org.lmdbjava.EnvFlags.MDB_RDONLY_ENV; import static org.lmdbjava.Library.LIB; import static org.lmdbjava.Library.RUNTIME; -import static org.lmdbjava.MaskedFlag.isSet; -import static org.lmdbjava.MaskedFlag.mask; import static org.lmdbjava.ResultCodeMapper.checkRc; -import static org.lmdbjava.TxnFlags.MDB_RDONLY_TXN; import java.io.File; import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.Objects; import jnr.ffi.Pointer; import jnr.ffi.byref.IntByReference; import jnr.ffi.byref.PointerByReference; @@ -50,6 +53,8 @@ public final class Env implements AutoCloseable { /** Java system property name that can be set to disable optional checks. */ public static final String DISABLE_CHECKS_PROP = "lmdbjava.disable.checks"; + public static final Charset DEFAULT_NAME_CHARSET = StandardCharsets.UTF_8; + /** * Indicates whether optional checks should be applied in LmdbJava. Optional checks are only * disabled in critical paths (see package-level JavaDocs). Non-critical paths have optional @@ -98,14 +103,15 @@ public static Builder create(final BufferProxy proxy) { } /** - * Opens an environment with a single default database in 0664 mode using the {@link - * ByteBufferProxy#PROXY_OPTIMAL}. - * + * @deprecated Instead use {@link Env#create()} or {@link Env#create(BufferProxy)} + *

Opens an environment with a single default database in 0664 mode using the {@link + * ByteBufferProxy#PROXY_OPTIMAL}. * @param path file system destination * @param size size in megabytes * @param flags the flags for this new environment * @return env the environment (never null) */ + @Deprecated public static Env open(final File path, final int size, final EnvFlags... flags) { return new Builder<>(PROXY_OPTIMAL).setMapSize(size * 1_024L * 1_024L).open(path, flags); } @@ -124,6 +130,27 @@ public void close() { LIB.mdb_env_close(ptr); } + /** + * Copies an LMDB environment to the specified destination path. + * + *

This function may be used to make a backup of an existing environment. No lockfile is + * created, since it gets recreated at need. + * + *

If this environment was created using {@link EnvFlags#MDB_NOSUBDIR}, the destination path + * must be a directory that exists but contains no files. If {@link EnvFlags#MDB_NOSUBDIR} was + * used, the destination path must not exist, but it must be possible to create a file at the + * provided path. + * + *

Note: This call can trigger significant file size growth if run in parallel with write + * transactions, because it employs a read-only transaction. See long-lived transactions under + * "Caveats" in the LMDB native documentation. + * + * @param path writable destination path as described above + */ + public void copy(final File path) { + copy(path, CopyFlagSet.EMPTY); + } + /** * Copies an LMDB environment to the specified destination path. * @@ -142,11 +169,10 @@ public void close() { * @param path writable destination path as described above * @param flags special options for this copy */ - public void copy(final File path, final CopyFlags... flags) { + public void copy(final File path, final CopyFlagSet flags) { requireNonNull(path); validatePath(path); - final int flagsMask = mask(true, flags); - checkRc(LIB.mdb_env_copy2(ptr, path.getAbsolutePath(), flagsMask)); + checkRc(LIB.mdb_env_copy2(ptr, path.getAbsolutePath(), flags.getMask())); } /** @@ -242,27 +268,44 @@ public boolean isReadOnly() { } /** - * Convenience method that opens a {@link Dbi} with a UTF-8 database name and default {@link - * Comparator} that is not invoked from native code. + * Open (and optionally creates, if {@link DbiFlags#MDB_CREATE} is set) a {@link Dbi} using a + * builder. * + * @return A new builder instance for creating/opening a {@link Dbi}. + */ + public DbiBuilder buildDbi() { + return new DbiBuilder<>(this, proxy, readOnly); + } + + /** * @param name name of the database (or null if no name is required) * @param flags to open the database with * @return a database that is ready to use + * @deprecated Instead use {@link Env#buildDbi()} Convenience method that opens a {@link Dbi} with + * a UTF-8 database name and default {@link Comparator} that is not invoked from native code. */ + @Deprecated() public Dbi openDbi(final String name, final DbiFlags... flags) { final byte[] nameBytes = name == null ? null : name.getBytes(UTF_8); return openDbi(nameBytes, null, false, flags); } /** - * Convenience method that opens a {@link Dbi} with a UTF-8 database name and associated {@link - * Comparator} that is not invoked from native code. - * * @param name name of the database (or null if no name is required) - * @param comparator custom comparator callback (or null to use default) + * @param comparator custom comparator for cursor start/stop key comparisons. If null, LMDB's + * comparator will be used. * @param flags to open the database with * @return a database that is ready to use + * @deprecated Instead use {@link Env#buildDbi()} Convenience method that opens a {@link Dbi} with + * a UTF-8 database name and associated {@link Comparator} for use by {@link CursorIterable} + * when comparing start/stop keys. + *

It is very important that the passed comparator behaves in the same way as the + * comparator LMDB uses for its insertion order (for the type of data that will be stored in + * the database), or you fully understand the implications of them behaving differently. + * LMDB's comparator is unsigned lexicographical, unless {@link DbiFlags#MDB_INTEGERKEY} is + * used. */ + @Deprecated() public Dbi openDbi( final String name, final Comparator comparator, final DbiFlags... flags) { final byte[] nameBytes = name == null ? null : name.getBytes(UTF_8); @@ -270,15 +313,19 @@ public Dbi openDbi( } /** - * Convenience method that opens a {@link Dbi} with a UTF-8 database name and associated {@link - * Comparator} that may be invoked from native code if specified. - * * @param name name of the database (or null if no name is required) - * @param comparator custom comparator callback (or null to use default) - * @param nativeCb whether native code calls back to the Java comparator + * @param comparator custom comparator for cursor start/stop key comparisons and optionally for + * LMDB to call back to. If null, LMDB's comparator will be used. + * @param nativeCb whether LMDB native code calls back to the Java comparator * @param flags to open the database with * @return a database that is ready to use + * @deprecated Instead use {@link Env#buildDbi()} Convenience method that opens a {@link Dbi} with + * a UTF-8 database name and associated {@link Comparator}. The comparator will be used by + * {@link CursorIterable} when comparing start/stop keys as a minimum. If nativeCb is {@code + * true}, this comparator will also be called by LMDB to determine insertion/iteration order. + * Calling back to a java comparator may significantly impact performance. */ + @Deprecated() public Dbi openDbi( final String name, final Comparator comparator, @@ -289,44 +336,43 @@ public Dbi openDbi( } /** - * Convenience method that opens a {@link Dbi} with a default {@link Comparator} that is not - * invoked from native code. - * * @param name name of the database (or null if no name is required) * @param flags to open the database with * @return a database that is ready to use + * @deprecated Instead use {@link Env#buildDbi()}


Convenience method that opens a {@link Dbi} + * with a default {@link Comparator} that is not invoked from native code. */ + @Deprecated() public Dbi openDbi(final byte[] name, final DbiFlags... flags) { return openDbi(name, null, false, flags); } /** - * Convenience method that opens a {@link Dbi} with an associated {@link Comparator} that is not - * invoked from native code. - * * @param name name of the database (or null if no name is required) * @param comparator custom comparator callback (or null to use LMDB default) * @param flags to open the database with * @return a database that is ready to use + * @deprecated Instead use {@link Env#buildDbi()}
Convenience method that opens a {@link Dbi} + * with an associated {@link Comparator} that is not invoked from native code. */ + @Deprecated() public Dbi openDbi( final byte[] name, final Comparator comparator, final DbiFlags... flags) { return openDbi(name, comparator, false, flags); } /** - * Convenience method that opens a {@link Dbi} with an associated {@link Comparator} that may be - * invoked from native code if specified. - * - *

This method will automatically commit the private transaction before returning. This ensures - * the Dbi is available in the Env. - * * @param name name of the database (or null if no name is required) * @param comparator custom comparator callback (or null to use LMDB default) * @param nativeCb whether native code calls back to the Java comparator * @param flags to open the database with * @return a database that is ready to use + * @deprecated Instead use {@link Env#buildDbi()}


Convenience method that opens a {@link Dbi} + * with an associated {@link Comparator} that may be invoked from native code if specified. + *

This method will automatically commit the private transaction before returning. This + * ensures the Dbi is available in the Env. */ + @Deprecated() public Dbi openDbi( final byte[] name, final Comparator comparator, @@ -340,39 +386,40 @@ public Dbi openDbi( } /** - * Open the {@link Dbi} using the passed {@link Txn}. - * - *

The caller must commit the transaction after this method returns in order to retain the - * Dbi in the Env. - * - *

A {@link Comparator} may be provided when calling this method. Such comparator is primarily - * used by {@link CursorIterable} instances. A secondary (but uncommon) use of the comparator is - * to act as a callback from the native library if nativeCb is true. - * This is usually avoided due to the overhead of native code calling back into Java. It is - * instead highly recommended to set the correct {@link DbiFlags} to allow the native library to - * correctly order the intended keys. - * - *

A default comparator will be provided if null is passed as the comparator. If a - * custom comparator is provided, it must strictly match the lexicographical order of keys in the - * native LMDB database. - * - *

This method (and its overloaded convenience variants) must not be called from concurrent - * threads. - * * @param txn transaction to use (required; not closed) * @param name name of the database (or null if no name is required) * @param comparator custom comparator callback (or null to use LMDB default) - * @param nativeCb whether native code should call back to the comparator + * @param nativeCb whether native LMDB code should call back to the Java comparator * @param flags to open the database with * @return a database that is ready to use + * @deprecated Instead use {@link Env#buildDbi()} Open the {@link Dbi} using the passed {@link + * Txn}. + *

The caller must commit the transaction after this method returns in order to retain the + * Dbi in the Env. + *

A {@link Comparator} may be provided when calling this method. Such comparator is + * primarily used by {@link CursorIterable} instances. A secondary (but uncommon) use of the + * comparator is to act as a callback from the native library if nativeCb is + * true. This is usually avoided due to the overhead of native code calling back + * into Java. It is instead highly recommended to set the correct {@link DbiFlags} to allow + * the native library to correctly order the intended keys. + *

A default comparator will be provided if null is passed as the comparator. + * If a custom comparator is provided, it must strictly match the lexicographical order of + * keys in the native LMDB database. + *

This method (and its overloaded convenience variants) must not be called from concurrent + * threads. */ + @Deprecated() public Dbi openDbi( final Txn txn, final byte[] name, final Comparator comparator, final boolean nativeCb, final DbiFlags... flags) { - return new Dbi<>(this, txn, name, comparator, nativeCb, proxy, flags); + + if (nativeCb && comparator == null) { + throw new IllegalArgumentException("Is nativeCb is true, you must supply a comparator"); + } + return new Dbi<>(this, txn, name, comparator, nativeCb, proxy, DbiFlagSet.of(flags)); } /** @@ -410,16 +457,40 @@ public void sync(final boolean force) { } /** - * Obtain a transaction with the requested parent and flags. - * * @param parent parent transaction (may be null if no parent) * @param flags applicable flags (eg for a reusable, read-only transaction) * @return a transaction (never null) + * @deprecated Instead use {@link Env#txn(Txn, TxnFlagSet)} + *

Obtain a transaction with the requested parent and flags. */ + @Deprecated public Txn txn(final Txn parent, final TxnFlags... flags) { - if (closed) { - throw new AlreadyClosedException(); - } + checkNotClosed(); + return new Txn<>(this, parent, proxy, TxnFlagSet.of(flags)); + } + + /** + * Obtain a transaction with the requested parent and flags. + * + * @param parent parent transaction (may be null if no parent) + * @return a transaction (never null) + */ + public Txn txn(final Txn parent) { + checkNotClosed(); + return new Txn<>(this, parent, proxy, TxnFlagSet.EMPTY); + } + + /** + * Obtain a transaction with the requested parent and flags. + * + * @param parent parent transaction (may be null if no parent) + * @param flags applicable flags (e.g. for a reusable, read-only transaction). If the set of flags + * is used frequently it is recommended to hold a static instance of the {@link TxnFlagSet} + * for re-use. + * @return a transaction (never null) + */ + public Txn txn(final Txn parent, final TxnFlagSet flags) { + checkNotClosed(); return new Txn<>(this, parent, proxy, flags); } @@ -429,7 +500,8 @@ public Txn txn(final Txn parent, final TxnFlags... flags) { * @return a read-only transaction */ public Txn txnRead() { - return txn(null, MDB_RDONLY_TXN); + checkNotClosed(); + return new Txn<>(this, null, proxy, TxnFlags.MDB_RDONLY_TXN); } /** @@ -438,7 +510,8 @@ public Txn txnRead() { * @return a read-write transaction */ public Txn txnWrite() { - return txn(null); + checkNotClosed(); + return new Txn<>(this, null, proxy, TxnFlagSet.EMPTY); } Pointer pointer() { @@ -507,6 +580,8 @@ public AlreadyOpenException() { } } + // -------------------------------------------------------------------------------- + /** * Builder for configuring and opening Env. * @@ -520,6 +595,8 @@ public static final class Builder { private int maxReaders = MAX_READERS_DEFAULT; private boolean opened; private final BufferProxy proxy; + private int mode = 0664; + private AbstractFlagSet.Builder flagSetBuilder = EnvFlagSet.builder(); Builder(final BufferProxy proxy) { requireNonNull(proxy); @@ -533,8 +610,50 @@ public static final class Builder { * @param mode Unix permissions to set on created files and semaphores * @param flags the flags for this new environment * @return an environment ready for use + * @deprecated Instead use {@link Builder#open(Path)}, {@link Builder#setFilePermissions(int)} + * and {@link Builder#setEnvFlags(EnvFlags...)}. */ + @Deprecated public Env open(final File path, final int mode, final EnvFlags... flags) { + setFilePermissions(mode); + setEnvFlags(flags); + return open(requireNonNull(path).toPath()); + } + + /** + * Opens the environment. + * + * @param path file system destination + * @return an environment ready for use + * @deprecated Instead use {@link Builder#open(Path)} + */ + @Deprecated + public Env open(final File path) { + return open(requireNonNull(path).toPath()); + } + + /** + * Opens the environment with 0664 mode. + * + * @param path file system destination + * @param flags the flags for this new environment + * @return an environment ready for use + * @deprecated Instead use {@link Builder#open(Path)} and {@link + * Builder#setEnvFlags(EnvFlags...)}. + */ + @Deprecated + public Env open(final File path, final EnvFlags... flags) { + setEnvFlags(flags); + return open(requireNonNull(path).toPath()); + } + + /** + * Opens the environment. + * + * @param path file system destination + * @return an environment ready for use + */ + public Env open(final Path path) { requireNonNull(path); if (opened) { throw new AlreadyOpenException(); @@ -547,10 +666,10 @@ public Env open(final File path, final int mode, final EnvFlags... flags) { checkRc(LIB.mdb_env_set_mapsize(ptr, mapSize)); checkRc(LIB.mdb_env_set_maxdbs(ptr, maxDbs)); checkRc(LIB.mdb_env_set_maxreaders(ptr, maxReaders)); - final int flagsMask = mask(true, flags); - final boolean readOnly = isSet(flagsMask, MDB_RDONLY_ENV); - final boolean noSubDir = isSet(flagsMask, MDB_NOSUBDIR); - checkRc(LIB.mdb_env_open(ptr, path.getAbsolutePath(), flagsMask, mode)); + final EnvFlagSet flags = flagSetBuilder.build(); + final boolean readOnly = flags.isSet(MDB_RDONLY_ENV); + final boolean noSubDir = flags.isSet(MDB_NOSUBDIR); + checkRc(LIB.mdb_env_open(ptr, path.toAbsolutePath().toString(), flags.getMask(), mode)); return new Env<>(proxy, ptr, readOnly, noSubDir); } catch (final LmdbNativeException e) { LIB.mdb_env_close(ptr); @@ -559,18 +678,7 @@ public Env open(final File path, final int mode, final EnvFlags... flags) { } /** - * Opens the environment with 0664 mode. - * - * @param path file system destination - * @param flags the flags for this new environment - * @return an environment ready for use - */ - public Env open(final File path, final EnvFlags... flags) { - return open(path, 0664, flags); - } - - /** - * Sets the map size. + * Sets the map size in bytes. * * @param mapSize new limit in bytes * @return the builder @@ -613,6 +721,90 @@ public Builder setMaxReaders(final int readers) { this.maxReaders = readers; return this; } + + /** + * Sets the Unix file permissions to use on created files and semaphores, e.g. {@code 0664}. If + * this method is not called, the default of {@code 0664} will be used. + * + * @param mode Unix permissions to set on created files and semaphores + * @return the builder + */ + public Builder setFilePermissions(final int mode) { + if (opened) { + throw new AlreadyOpenException(); + } + this.mode = mode; + return this; + } + + /** + * Sets all the flags used to open this {@link Env}. + * + * @param envFlags The flags to use. Clears any existing flags. A null value results in no flags + * being set. + * @return this builder instance. + */ + public Builder setEnvFlags(final Collection envFlags) { + flagSetBuilder.clear(); + if (envFlags != null) { + envFlags.stream().filter(Objects::nonNull).forEach(envFlags::add); + } + return this; + } + + /** + * Sets all the flags used to open this {@link Env}. + * + * @param envFlags The flags to use. Clears any existing flags. A null value results in no flags + * being set. + * @return this builder instance. + */ + public Builder setEnvFlags(final EnvFlags... envFlags) { + flagSetBuilder.clear(); + if (envFlags != null) { + Arrays.stream(envFlags).filter(Objects::nonNull).forEach(this.flagSetBuilder::setFlag); + } + return this; + } + + /** + * Sets all the flags used to open this {@link Env}. + * + * @param envFlagSet The flags to use. Clears any existing flags. A null value results in no + * flags being set. + * @return this builder instance. + */ + public Builder setEnvFlags(final EnvFlagSet envFlagSet) { + flagSetBuilder.clear(); + if (envFlagSet != null) { + this.flagSetBuilder.withFlags(envFlagSet.getFlags()); + } + return this; + } + + /** + * Adds a single {@link EnvFlags} to any existing flags. + * + * @param dbiFlag The flag to add to any existing flags. A null value is a no-op. + * @return this builder instance. + */ + public Builder addEnvFlag(final EnvFlags dbiFlag) { + this.flagSetBuilder.setFlag(dbiFlag); + return this; + } + + /** + * Adds a set of {@link EnvFlags} to any existing flags. + * + * @param dbiFlagSet The set of flags to add to any existing flags. A null value is a no-op. + * @return this builder instance. + */ + public Builder addEnvFlags(final EnvFlagSet dbiFlagSet) { + if (dbiFlagSet != null) { + flagSetBuilder.setFlags(dbiFlagSet.getFlags()); + } + return this; + } } /** File is not a valid LMDB file. */ diff --git a/src/main/java/org/lmdbjava/EnvFlagSet.java b/src/main/java/org/lmdbjava/EnvFlagSet.java new file mode 100644 index 00000000..2ee90e11 --- /dev/null +++ b/src/main/java/org/lmdbjava/EnvFlagSet.java @@ -0,0 +1,63 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +import java.util.Collection; +import java.util.EnumSet; +import java.util.Objects; + +public interface EnvFlagSet extends FlagSet { + + EnvFlagSet EMPTY = EnvFlagSetImpl.EMPTY; + + static EnvFlagSet empty() { + return EnvFlagSetImpl.EMPTY; + } + + static EnvFlagSet of(final EnvFlags envFlag) { + Objects.requireNonNull(envFlag); + return envFlag; + } + + static EnvFlagSet of(final EnvFlags... EnvFlags) { + return builder().withFlags(EnvFlags).build(); + } + + static EnvFlagSet of(final Collection EnvFlags) { + return builder().withFlags(EnvFlags).build(); + } + + static AbstractFlagSet.Builder builder() { + return new AbstractFlagSet.Builder<>( + EnvFlags.class, EnvFlagSetImpl::new, envFlag -> envFlag, () -> EnvFlagSetImpl.EMPTY); + } + + // -------------------------------------------------------------------------------- + + class EnvFlagSetImpl extends AbstractFlagSet implements EnvFlagSet { + + static final EnvFlagSet EMPTY = new EmptyEnvFlagSet(); + + private EnvFlagSetImpl(final EnumSet flags) { + super(flags); + } + } + + // -------------------------------------------------------------------------------- + + class EmptyEnvFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet + implements EnvFlagSet {} +} diff --git a/src/main/java/org/lmdbjava/EnvFlags.java b/src/main/java/org/lmdbjava/EnvFlags.java index 4ce555a8..7fb4a29b 100644 --- a/src/main/java/org/lmdbjava/EnvFlags.java +++ b/src/main/java/org/lmdbjava/EnvFlags.java @@ -15,8 +15,11 @@ */ package org.lmdbjava; +import java.util.EnumSet; +import java.util.Set; + /** Flags for use when opening the {@link Env}. */ -public enum EnvFlags implements MaskedFlag { +public enum EnvFlags implements MaskedFlag, EnvFlagSet { /** * Mmap at a fixed address (experimental). @@ -144,4 +147,29 @@ public enum EnvFlags implements MaskedFlag { public int getMask() { return mask; } + + @Override + public Set getFlags() { + return EnumSet.of(this); + } + + @Override + public boolean isSet(final EnvFlags flag) { + return flag != null && mask == flag.getMask(); + } + + @Override + public int size() { + return 1; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public String toString() { + return FlagSet.asString(this); + } } diff --git a/src/main/java/org/lmdbjava/FlagSet.java b/src/main/java/org/lmdbjava/FlagSet.java new file mode 100644 index 00000000..184694e2 --- /dev/null +++ b/src/main/java/org/lmdbjava/FlagSet.java @@ -0,0 +1,122 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * A set of flags, each with a bit mask value. Flags can be combined in a set such that the set has + * a combined bit mask value. + * + * @param + */ +public interface FlagSet extends Iterable { + + /** + * @return The combined mask for this flagSet. + */ + int getMask(); + + /** + * @return The result of combining the mask of this {@link FlagSet} with the mask of the other + * {@link FlagSet}. + */ + default int getMaskWith(final FlagSet other) { + if (other != null) { + return MaskedFlag.mask(getMask(), other.getMask()); + } else { + return getMask(); + } + } + + /** + * @return The set of flags in this {@link FlagSet}. + */ + Set getFlags(); + + /** + * @return True if flag is non-null and included in this {@link FlagSet}. + */ + boolean isSet(T flag); + + /** + * @return True if at least one of flags are included in thie {@link FlagSet} + */ + default boolean areAnySet(final FlagSet flags) { + if (flags == null) { + return false; + } else { + for (final T flag : flags) { + if (isSet(flag)) { + return true; + } + } + } + return false; + } + + /** + * @return The size of this {@link FlagSet} + */ + default int size() { + return getFlags().size(); + } + + /** + * @return True if this {@link FlagSet} is empty. + */ + default boolean isEmpty() { + return getFlags().isEmpty(); + } + + /** + * @return The {@link Iterator} (in no particular order) for the flags in this {@link FlagSet}. + */ + default Iterator iterator() { + return getFlags().iterator(); + } + + /** Convert this {@link FlagSet} to a string for use in toString methods. */ + static String asString(final FlagSet flagSet) { + Objects.requireNonNull(flagSet); + final String flagsStr = + flagSet.getFlags().stream() + .sorted(Comparator.comparing(MaskedFlag::getMask)) + .map(MaskedFlag::name) + .collect(Collectors.joining(", ")); + return "FlagSet{" + "flags=[" + flagsStr + "], mask=" + flagSet.getMask() + '}'; + } + + static boolean equals(final FlagSet flagSet, final Object other) { + if (other instanceof FlagSet) { + final FlagSet flagSet2 = (FlagSet) other; + if (flagSet == flagSet2) { + return true; + } else if (flagSet == null) { + return false; + } else { + return flagSet.getMask() == flagSet2.getMask() + && Objects.equals(flagSet.getFlags(), flagSet2.getFlags()); + } + } else { + return false; + } + } +} diff --git a/src/main/java/org/lmdbjava/Key.java b/src/main/java/org/lmdbjava/Key.java new file mode 100644 index 00000000..ac25f65d --- /dev/null +++ b/src/main/java/org/lmdbjava/Key.java @@ -0,0 +1,72 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +import static java.util.Objects.requireNonNull; +import static org.lmdbjava.BufferProxy.MDB_VAL_STRUCT_SIZE; +import static org.lmdbjava.Library.RUNTIME; + +import jnr.ffi.Pointer; +import jnr.ffi.provider.MemoryManager; + +/** + * Represents off-heap memory holding a key only. + * + * @param buffer type + */ +final class Key implements AutoCloseable { + + private static final MemoryManager MEM_MGR = RUNTIME.getMemoryManager(); + private boolean closed; + private T k; + private final BufferProxy proxy; + private final Pointer ptrKey; + private final long ptrKeyAddr; + + Key(final BufferProxy proxy) { + requireNonNull(proxy); + this.proxy = proxy; + this.k = proxy.allocate(); + ptrKey = MEM_MGR.allocateTemporary(MDB_VAL_STRUCT_SIZE, false); + ptrKeyAddr = ptrKey.address(); + } + + @Override + public void close() { + if (closed) { + return; + } + closed = true; + proxy.deallocate(k); + } + + T key() { + return k; + } + + void keyIn(final T key) { + proxy.in(key, ptrKey); + } + + T keyOut() { + k = proxy.out(k, ptrKey); + return k; + } + + Pointer pointer() { + return ptrKey; + } +} diff --git a/src/main/java/org/lmdbjava/KeyRangeType.java b/src/main/java/org/lmdbjava/KeyRangeType.java index 0514cbf2..4cb1cc9b 100644 --- a/src/main/java/org/lmdbjava/KeyRangeType.java +++ b/src/main/java/org/lmdbjava/KeyRangeType.java @@ -345,15 +345,13 @@ CursorOp initialOp() { * * @param buffer type * @param comparator for the buffers - * @param start start buffer - * @param stop stop buffer * @param buffer current key returned by LMDB (may be null) - * @param c comparator (required) + * @param rangeComparator comparator (required) * @return response to this key */ > IteratorOp iteratorOp( - final T start, final T stop, final T buffer, final C c) { - requireNonNull(c, "Comparator required"); + final T buffer, final RangeComparator rangeComparator) { + requireNonNull(rangeComparator, "Comparator required"); if (buffer == null) { return TERMINATE; } @@ -363,55 +361,55 @@ > IteratorOp iteratorOp( case FORWARD_AT_LEAST: return RELEASE; case FORWARD_AT_MOST: - return c.compare(buffer, stop) > 0 ? TERMINATE : RELEASE; + return rangeComparator.compareToStopKey() > 0 ? TERMINATE : RELEASE; case FORWARD_CLOSED: - return c.compare(buffer, stop) > 0 ? TERMINATE : RELEASE; + return rangeComparator.compareToStopKey() > 0 ? TERMINATE : RELEASE; case FORWARD_CLOSED_OPEN: - return c.compare(buffer, stop) >= 0 ? TERMINATE : RELEASE; + return rangeComparator.compareToStopKey() >= 0 ? TERMINATE : RELEASE; case FORWARD_GREATER_THAN: - return c.compare(buffer, start) == 0 ? CALL_NEXT_OP : RELEASE; + return rangeComparator.compareToStartKey() == 0 ? CALL_NEXT_OP : RELEASE; case FORWARD_LESS_THAN: - return c.compare(buffer, stop) >= 0 ? TERMINATE : RELEASE; + return rangeComparator.compareToStopKey() >= 0 ? TERMINATE : RELEASE; case FORWARD_OPEN: - if (c.compare(buffer, start) == 0) { + if (rangeComparator.compareToStartKey() == 0) { return CALL_NEXT_OP; } - return c.compare(buffer, stop) >= 0 ? TERMINATE : RELEASE; + return rangeComparator.compareToStopKey() >= 0 ? TERMINATE : RELEASE; case FORWARD_OPEN_CLOSED: - if (c.compare(buffer, start) == 0) { + if (rangeComparator.compareToStartKey() == 0) { return CALL_NEXT_OP; } - return c.compare(buffer, stop) > 0 ? TERMINATE : RELEASE; + return rangeComparator.compareToStopKey() > 0 ? TERMINATE : RELEASE; case BACKWARD_ALL: return RELEASE; case BACKWARD_AT_LEAST: - return c.compare(buffer, start) > 0 ? CALL_NEXT_OP : RELEASE; // rewind + return rangeComparator.compareToStartKey() > 0 ? CALL_NEXT_OP : RELEASE; // rewind case BACKWARD_AT_MOST: - return c.compare(buffer, stop) >= 0 ? RELEASE : TERMINATE; + return rangeComparator.compareToStopKey() >= 0 ? RELEASE : TERMINATE; case BACKWARD_CLOSED: - if (c.compare(buffer, start) > 0) { + if (rangeComparator.compareToStartKey() > 0) { return CALL_NEXT_OP; // rewind } - return c.compare(buffer, stop) >= 0 ? RELEASE : TERMINATE; + return rangeComparator.compareToStopKey() >= 0 ? RELEASE : TERMINATE; case BACKWARD_CLOSED_OPEN: - if (c.compare(buffer, start) > 0) { + if (rangeComparator.compareToStartKey() > 0) { return CALL_NEXT_OP; // rewind } - return c.compare(buffer, stop) > 0 ? RELEASE : TERMINATE; + return rangeComparator.compareToStopKey() > 0 ? RELEASE : TERMINATE; case BACKWARD_GREATER_THAN: - return c.compare(buffer, start) >= 0 ? CALL_NEXT_OP : RELEASE; + return rangeComparator.compareToStartKey() >= 0 ? CALL_NEXT_OP : RELEASE; case BACKWARD_LESS_THAN: - return c.compare(buffer, stop) > 0 ? RELEASE : TERMINATE; + return rangeComparator.compareToStopKey() > 0 ? RELEASE : TERMINATE; case BACKWARD_OPEN: - if (c.compare(buffer, start) >= 0) { + if (rangeComparator.compareToStartKey() >= 0) { return CALL_NEXT_OP; // rewind } - return c.compare(buffer, stop) > 0 ? RELEASE : TERMINATE; + return rangeComparator.compareToStopKey() > 0 ? RELEASE : TERMINATE; case BACKWARD_OPEN_CLOSED: - if (c.compare(buffer, start) >= 0) { + if (rangeComparator.compareToStartKey() >= 0) { return CALL_NEXT_OP; // rewind } - return c.compare(buffer, stop) >= 0 ? RELEASE : TERMINATE; + return rangeComparator.compareToStopKey() >= 0 ? RELEASE : TERMINATE; default: throw new IllegalStateException("Invalid type"); } diff --git a/src/main/java/org/lmdbjava/Library.java b/src/main/java/org/lmdbjava/Library.java index ef9b9b35..6d8122d2 100644 --- a/src/main/java/org/lmdbjava/Library.java +++ b/src/main/java/org/lmdbjava/Library.java @@ -235,6 +235,8 @@ public interface Lmdb { void mdb_txn_reset(@In Pointer txn); + int mdb_cmp(@In Pointer txn, @In Pointer dbi, @In Pointer key1, @In Pointer key2); + Pointer mdb_version(IntByReference major, IntByReference minor, IntByReference patch); } } diff --git a/src/main/java/org/lmdbjava/MaskedFlag.java b/src/main/java/org/lmdbjava/MaskedFlag.java index 87deb3c9..400f2f30 100644 --- a/src/main/java/org/lmdbjava/MaskedFlag.java +++ b/src/main/java/org/lmdbjava/MaskedFlag.java @@ -17,13 +17,13 @@ import static java.util.Objects.requireNonNull; -import java.util.Objects; -import java.util.function.Predicate; -import java.util.stream.Stream; +import java.util.Collection; /** Indicates an enum that can provide integers for each of its values. */ public interface MaskedFlag { + int EMPTY_MASK = 0; + /** * Obtains the integer value for this enum which can be included in a mask. * @@ -32,13 +32,9 @@ public interface MaskedFlag { int getMask(); /** - * Indicates if the flag must be propagated to the underlying C code of LMDB or not. - * - * @return the boolean value indicating the propagation + * @return The name of the flag. */ - default boolean isPropagatedToLmdb() { - return true; - } + String name(); /** * Fetch the integer mask for all presented flags. @@ -49,67 +45,45 @@ default boolean isPropagatedToLmdb() { */ @SafeVarargs static int mask(final M... flags) { - return mask(false, flags); + if (flags == null || flags.length == 0) { + return EMPTY_MASK; + } else { + int result = EMPTY_MASK; + for (MaskedFlag flag : flags) { + if (flag == null) { + continue; + } + result |= flag.getMask(); + } + return result; + } } - /** - * Fetch the integer mask for all presented flags. - * - * @param flag type - * @param flags to mask (null or empty returns zero) - * @return the integer mask for use in C - */ - static int mask(final Stream flags) { - return mask(false, flags); + /** Combine the two masks into a single mask value, i.e. when combining two {@link FlagSet}s. */ + static int mask(final int mask1, final int mask2) { + return mask1 | mask2; } /** * Fetch the integer mask for the presented flags. * * @param flag type - * @param onlyPropagatedToLmdb if to include only the flags which are also propagate to the C code - * or all of them * @param flags to mask (null or empty returns zero) * @return the integer mask for use in C */ - @SafeVarargs - static int mask(final boolean onlyPropagatedToLmdb, final M... flags) { - if (flags == null || flags.length == 0) { - return 0; - } - - int result = 0; - for (final M flag : flags) { - if (flag == null) { - continue; - } - if (!onlyPropagatedToLmdb || flag.isPropagatedToLmdb()) { + static int mask(final Collection flags) { + if (flags == null || flags.isEmpty()) { + return EMPTY_MASK; + } else { + int result = EMPTY_MASK; + for (MaskedFlag flag : flags) { + if (flag == null) { + continue; + } result |= flag.getMask(); } + return result; } - return result; - } - - /** - * Fetch the integer mask for all presented flags. - * - * @param flag type - * @param onlyPropagatedToLmdb if to include only the flags which are also propagate to the C code - * or all of them - * @param flags to mask - * @return the integer mask for use in C - */ - static int mask( - final boolean onlyPropagatedToLmdb, final Stream flags) { - final Predicate filter = onlyPropagatedToLmdb ? MaskedFlag::isPropagatedToLmdb : f -> true; - - return flags == null - ? 0 - : flags - .filter(Objects::nonNull) - .filter(filter) - .map(M::getMask) - .reduce(0, (f1, f2) -> f1 | f2); } /** diff --git a/src/main/java/org/lmdbjava/PutFlagSet.java b/src/main/java/org/lmdbjava/PutFlagSet.java new file mode 100644 index 00000000..be34f654 --- /dev/null +++ b/src/main/java/org/lmdbjava/PutFlagSet.java @@ -0,0 +1,63 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +import java.util.Collection; +import java.util.EnumSet; +import java.util.Objects; + +public interface PutFlagSet extends FlagSet { + + PutFlagSet EMPTY = PutFlagSetImpl.EMPTY; + + static PutFlagSet empty() { + return PutFlagSetImpl.EMPTY; + } + + static PutFlagSet of(final PutFlags putFlag) { + Objects.requireNonNull(putFlag); + return putFlag; + } + + static PutFlagSet of(final PutFlags... putFlags) { + return builder().withFlags(putFlags).build(); + } + + static PutFlagSet of(final Collection putFlags) { + return builder().withFlags(putFlags).build(); + } + + static AbstractFlagSet.Builder builder() { + return new AbstractFlagSet.Builder<>( + PutFlags.class, PutFlagSetImpl::new, putFlag -> putFlag, EmptyPutFlagSet::new); + } + + // -------------------------------------------------------------------------------- + + class PutFlagSetImpl extends AbstractFlagSet implements PutFlagSet { + + public static final PutFlagSet EMPTY = new EmptyPutFlagSet(); + + private PutFlagSetImpl(final EnumSet flags) { + super(flags); + } + } + + // -------------------------------------------------------------------------------- + + class EmptyPutFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet + implements PutFlagSet {} +} diff --git a/src/main/java/org/lmdbjava/PutFlags.java b/src/main/java/org/lmdbjava/PutFlags.java index 809103de..03fa916a 100644 --- a/src/main/java/org/lmdbjava/PutFlags.java +++ b/src/main/java/org/lmdbjava/PutFlags.java @@ -15,8 +15,11 @@ */ package org.lmdbjava; +import java.util.EnumSet; +import java.util.Set; + /** Flags for use when performing a "put". */ -public enum PutFlags implements MaskedFlag { +public enum PutFlags implements MaskedFlag, PutFlagSet { /** For put: Don't write if the key already exists. */ MDB_NOOVERWRITE(0x10), @@ -49,4 +52,29 @@ public enum PutFlags implements MaskedFlag { public int getMask() { return mask; } + + @Override + public Set getFlags() { + return EnumSet.of(this); + } + + @Override + public boolean isSet(PutFlags flag) { + return flag != null && mask == flag.getMask(); + } + + @Override + public int size() { + return 1; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public String toString() { + return FlagSet.asString(this); + } } diff --git a/src/main/java/org/lmdbjava/RangeComparator.java b/src/main/java/org/lmdbjava/RangeComparator.java new file mode 100644 index 00000000..f2626a59 --- /dev/null +++ b/src/main/java/org/lmdbjava/RangeComparator.java @@ -0,0 +1,32 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +/** For comparing a cursor's current key against a {@link KeyRange}'s start/stop key. */ +interface RangeComparator extends AutoCloseable { + + /** + * Compare the cursor's current key to the range start key. Equivalent to compareTo(currentKey, + * startKey) + */ + int compareToStartKey(); + + /** + * Compare the cursor's current key to the range stop key. Equivalent to compareTo(currentKey, + * stopKey) + */ + int compareToStopKey(); +} diff --git a/src/main/java/org/lmdbjava/Txn.java b/src/main/java/org/lmdbjava/Txn.java index 05e8ce06..432b47a8 100644 --- a/src/main/java/org/lmdbjava/Txn.java +++ b/src/main/java/org/lmdbjava/Txn.java @@ -20,8 +20,6 @@ import static org.lmdbjava.Env.SHOULD_CHECK; import static org.lmdbjava.Library.LIB; import static org.lmdbjava.Library.RUNTIME; -import static org.lmdbjava.MaskedFlag.isSet; -import static org.lmdbjava.MaskedFlag.mask; import static org.lmdbjava.ResultCodeMapper.checkRc; import static org.lmdbjava.Txn.State.DONE; import static org.lmdbjava.Txn.State.READY; @@ -44,13 +42,14 @@ public final class Txn implements AutoCloseable { private final Pointer ptr; private final boolean readOnly; private final Env env; + private final TxnFlagSet flags; private State state; - Txn(final Env env, final Txn parent, final BufferProxy proxy, final TxnFlags... flags) { + Txn(final Env env, final Txn parent, final BufferProxy proxy, final TxnFlagSet flags) { + this.flags = flags != null ? flags : TxnFlagSet.EMPTY; this.proxy = proxy; this.keyVal = proxy.keyVal(); - final int flagsMask = mask(true, flags); - this.readOnly = isSet(flagsMask, MDB_RDONLY_TXN); + this.readOnly = this.flags.isSet(MDB_RDONLY_TXN); if (env.isReadOnly() && !this.readOnly) { throw new EnvIsReadOnly(); } @@ -61,7 +60,7 @@ public final class Txn implements AutoCloseable { } final Pointer txnPtr = allocateDirect(RUNTIME, ADDRESS); final Pointer txnParentPtr = parent == null ? null : parent.ptr; - checkRc(LIB.mdb_txn_begin(env.pointer(), txnParentPtr, flagsMask, txnPtr)); + checkRc(LIB.mdb_txn_begin(env.pointer(), txnParentPtr, this.flags.getMask(), txnPtr)); ptr = txnPtr.getPointer(0); state = READY; @@ -164,7 +163,7 @@ public void renew() { } /** - * Aborts this read-only transaction and resets the transaction handle so it can be reused upon + * Aborts this read-only transaction and resets the transaction handle, so it can be reused upon * calling {@link #renew()}. */ public void reset() { diff --git a/src/main/java/org/lmdbjava/TxnFlagSet.java b/src/main/java/org/lmdbjava/TxnFlagSet.java new file mode 100644 index 00000000..8cfe61f9 --- /dev/null +++ b/src/main/java/org/lmdbjava/TxnFlagSet.java @@ -0,0 +1,68 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +import java.util.EnumSet; +import java.util.Objects; + +public interface TxnFlagSet extends FlagSet { + + TxnFlagSet EMPTY = TxnFlagSetImpl.EMPTY; + + static TxnFlagSet empty() { + return TxnFlagSetImpl.EMPTY; + } + + static TxnFlagSet of(final TxnFlags putFlag) { + Objects.requireNonNull(putFlag); + return new SingleTxnFlagSet(putFlag); + } + + static TxnFlagSet of(final TxnFlags... TxnFlags) { + return builder().withFlags(TxnFlags).build(); + } + + static AbstractFlagSet.Builder builder() { + return new AbstractFlagSet.Builder<>( + TxnFlags.class, TxnFlagSetImpl::new, SingleTxnFlagSet::new, () -> TxnFlagSetImpl.EMPTY); + } + + // -------------------------------------------------------------------------------- + + class TxnFlagSetImpl extends AbstractFlagSet implements TxnFlagSet { + + static final TxnFlagSet EMPTY = new EmptyTxnFlagSet(); + + private TxnFlagSetImpl(final EnumSet flags) { + super(flags); + } + } + + // -------------------------------------------------------------------------------- + + class SingleTxnFlagSet extends AbstractFlagSet.AbstractSingleFlagSet + implements TxnFlagSet { + + SingleTxnFlagSet(final TxnFlags flag) { + super(flag); + } + } + + // -------------------------------------------------------------------------------- + + class EmptyTxnFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet + implements TxnFlagSet {} +} diff --git a/src/main/java/org/lmdbjava/TxnFlags.java b/src/main/java/org/lmdbjava/TxnFlags.java index 26caf6f1..94112957 100644 --- a/src/main/java/org/lmdbjava/TxnFlags.java +++ b/src/main/java/org/lmdbjava/TxnFlags.java @@ -15,8 +15,12 @@ */ package org.lmdbjava; +import java.util.EnumSet; +import java.util.Set; + /** Flags for use when creating a {@link Txn}. */ -public enum TxnFlags implements MaskedFlag { +public enum TxnFlags implements MaskedFlag, TxnFlagSet { + /** Read only. */ MDB_RDONLY_TXN(0x2_0000); @@ -30,4 +34,29 @@ public enum TxnFlags implements MaskedFlag { public int getMask() { return mask; } + + @Override + public Set getFlags() { + return EnumSet.of(this); + } + + @Override + public boolean isSet(final TxnFlags flag) { + return flag != null && mask == flag.getMask(); + } + + @Override + public int size() { + return 1; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public String toString() { + return FlagSet.asString(this); + } } diff --git a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java index d5638fe2..332598fe 100644 --- a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java +++ b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java @@ -36,9 +36,18 @@ import java.lang.reflect.Field; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.file.Path; +import java.time.Duration; +import java.time.Instant; +import java.util.Comparator; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Random; +import java.util.Set; import jnr.ffi.Pointer; import jnr.ffi.provider.MemoryManager; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import org.lmdbjava.ByteBufferProxy.BufferMustBeDirectException; import org.lmdbjava.Env.ReadersFullException; @@ -54,8 +63,13 @@ void buffersMustBeDirect() { () -> { try (final TempDir tempDir = new TempDir()) { final Path dir = tempDir.createTempDir(); - try (final Env env = create().setMaxReaders(1).open(dir.toFile())) { - final Dbi db = env.openDbi(DB_1, MDB_CREATE); + try (Env env = create().setMaxReaders(1).open(dir)) { + final Dbi db = + env.buildDbi() + .setDbName(DB_1) + .withDefaultComparator() + .setDbiFlags(MDB_CREATE) + .open(); final ByteBuffer key = allocate(100); key.putInt(1).flip(); final ByteBuffer val = allocate(100); @@ -132,6 +146,105 @@ void unsafeIsDefault() { assertThat(v.getClass().getSimpleName()).startsWith("Unsafe"); } + /** + * For 100 rounds of 5,000,000 comparisons compareAsIntegerKeys: PT1.600525631S + * compareLexicographically: PT3.381935001S + */ + @Test + public void comparatorPerformance() { + final Random random = new Random(); + final ByteBuffer buffer1 = ByteBuffer.allocateDirect(Long.BYTES); + final ByteBuffer buffer2 = ByteBuffer.allocateDirect(Long.BYTES); + buffer1.limit(Long.BYTES); + buffer2.limit(Long.BYTES); + final long[] values = random.longs(5_000_000).toArray(); + + Instant time = Instant.now(); + int x = 0; + for (int rounds = 0; rounds < 100; rounds++) { + for (int i = 1; i < values.length; i++) { + buffer1.order(ByteOrder.nativeOrder()).putLong(0, values[i - 1]); + buffer2.order(ByteOrder.nativeOrder()).putLong(0, values[i]); + final int result = + ByteBufferProxy.AbstractByteBufferProxy.compareAsIntegerKeys(buffer1, buffer2); + x += result; + } + } + System.out.println("compareAsIntegerKeys: " + Duration.between(time, Instant.now())); + + time = Instant.now(); + x = 0; + for (int rounds = 0; rounds < 100; rounds++) { + for (int i = 1; i < values.length; i++) { + buffer1.order(BIG_ENDIAN).putLong(0, values[i - 1]); + buffer2.order(BIG_ENDIAN).putLong(0, values[i]); + final int result = + ByteBufferProxy.AbstractByteBufferProxy.compareLexicographically(buffer1, buffer2); + x += result; + } + } + System.out.println("compareLexicographically: " + Duration.between(time, Instant.now())); + } + + @Test + public void verifyComparators() { + final Random random = new Random(203948); + final ByteBuffer buffer1native = + ByteBuffer.allocateDirect(Long.BYTES).order(ByteOrder.nativeOrder()); + final ByteBuffer buffer2native = + ByteBuffer.allocateDirect(Long.BYTES).order(ByteOrder.nativeOrder()); + final ByteBuffer buffer1be = ByteBuffer.allocateDirect(Long.BYTES).order(BIG_ENDIAN); + final ByteBuffer buffer2be = ByteBuffer.allocateDirect(Long.BYTES).order(BIG_ENDIAN); + buffer1native.limit(Long.BYTES); + buffer2native.limit(Long.BYTES); + buffer1be.limit(Long.BYTES); + buffer2be.limit(Long.BYTES); + final long[] values = random.longs().filter(i -> i >= 0).limit(5_000_000).toArray(); + // System.out.println("stats: " + Arrays.stream(values) + // .summaryStatistics() + // .toString()); + + final LinkedHashMap> comparators = new LinkedHashMap<>(); + comparators.put( + "compareAsIntegerKeys", ByteBufferProxy.AbstractByteBufferProxy::compareAsIntegerKeys); + comparators.put( + "compareLexicographically", + ByteBufferProxy.AbstractByteBufferProxy::compareLexicographically); + + final LinkedHashMap results = new LinkedHashMap<>(comparators.size()); + final Set uniqueResults = new HashSet<>(comparators.size()); + + for (int i = 1; i < values.length; i++) { + final long val1 = values[i - 1]; + final long val2 = values[i]; + buffer1native.putLong(0, val1); + buffer2native.putLong(0, val2); + buffer1be.putLong(0, val1); + buffer2be.putLong(0, val2); + uniqueResults.clear(); + + // Make sure all comparators give the same result for the same inputs + comparators.forEach( + (name, comparator) -> { + final int result; + // IntegerKey comparator expects keys to have been written in native order so need + // different buffers. + if (name.equals("compareAsIntegerKeys")) { + result = comparator.compare(buffer1native, buffer2native); + } else { + result = comparator.compare(buffer1be, buffer2be); + } + results.put(name, result); + uniqueResults.add(result); + }); + + if (uniqueResults.size() != 1) { + Assertions.fail( + "Comparator mismatch for values: " + val1 + " and " + val2 + ". Results: " + results); + } + } + } + private void checkInOut(final BufferProxy v) { // allocate a buffer larger than max key size final ByteBuffer b = allocateDirect(1_000); diff --git a/src/test/java/org/lmdbjava/ComparatorIntegerKeyTest.java b/src/test/java/org/lmdbjava/ComparatorIntegerKeyTest.java new file mode 100644 index 00000000..00c0ce28 --- /dev/null +++ b/src/test/java/org/lmdbjava/ComparatorIntegerKeyTest.java @@ -0,0 +1,357 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.lmdbjava; + +import static io.netty.buffer.PooledByteBufAllocator.DEFAULT; +import static org.assertj.core.api.Assertions.assertThat; +import static org.lmdbjava.ByteBufProxy.PROXY_NETTY; +import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; +import static org.lmdbjava.ComparatorTest.ComparatorResult.EQUAL_TO; +import static org.lmdbjava.ComparatorTest.ComparatorResult.GREATER_THAN; +import static org.lmdbjava.ComparatorTest.ComparatorResult.LESS_THAN; +import static org.lmdbjava.ComparatorTest.ComparatorResult.get; +import static org.lmdbjava.DirectBufferProxy.PROXY_DB; + +import io.netty.buffer.ByteBuf; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Comparator; +import java.util.Random; +import java.util.stream.Stream; +import org.agrona.DirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** Tests comparator functions are consistent across buffers. */ +public final class ComparatorIntegerKeyTest { + + static Stream comparatorProvider() { + return Stream.of( + Arguments.argumentSet("LongRunner", new DirectBufferRunner()), + Arguments.argumentSet("DirectBufferRunner", new DirectBufferRunner()), + Arguments.argumentSet("ByteBufferRunner", new ByteBufferRunner()), + Arguments.argumentSet("NettyRunner", new NettyRunner())); + } + + private static byte[] buffer(final int... bytes) { + final byte[] array = new byte[bytes.length]; + for (int i = 0; i < bytes.length; i++) { + array[i] = (byte) bytes[i]; + } + return array; + } + + @ParameterizedTest + @MethodSource("comparatorProvider") + void testLong(final ComparatorRunner comparator) { + + assertThat(get(comparator.compare(0L, 0L))).isEqualTo(EQUAL_TO); + assertThat(get(comparator.compare(Long.MAX_VALUE, Long.MAX_VALUE))).isEqualTo(EQUAL_TO); + + assertThat(get(comparator.compare(0L, 1L))).isEqualTo(LESS_THAN); + assertThat(get(comparator.compare(0L, Long.MAX_VALUE))).isEqualTo(LESS_THAN); + assertThat(get(comparator.compare(0L, 10L))).isEqualTo(LESS_THAN); + assertThat(get(comparator.compare(10L, 100L))).isEqualTo(LESS_THAN); + assertThat(get(comparator.compare(10L, 100L))).isEqualTo(LESS_THAN); + assertThat(get(comparator.compare(10L, 1000L))).isEqualTo(LESS_THAN); + + assertThat(get(comparator.compare(1L, 0L))).isEqualTo(GREATER_THAN); + assertThat(get(comparator.compare(Long.MAX_VALUE, 0L))).isEqualTo(GREATER_THAN); + } + + @ParameterizedTest + @MethodSource("comparatorProvider") + void testInt(final ComparatorRunner comparator) { + + assertThat(get(comparator.compare(0, 0))).isEqualTo(EQUAL_TO); + assertThat(get(comparator.compare(Integer.MAX_VALUE, Integer.MAX_VALUE))).isEqualTo(EQUAL_TO); + + assertThat(get(comparator.compare(0, 1))).isEqualTo(LESS_THAN); + assertThat(get(comparator.compare(0, Integer.MAX_VALUE))).isEqualTo(LESS_THAN); + assertThat(get(comparator.compare(0, 10))).isEqualTo(LESS_THAN); + assertThat(get(comparator.compare(10, 100))).isEqualTo(LESS_THAN); + assertThat(get(comparator.compare(10, 100))).isEqualTo(LESS_THAN); + assertThat(get(comparator.compare(10, 1000))).isEqualTo(LESS_THAN); + + assertThat(get(comparator.compare(1, 0))).isEqualTo(GREATER_THAN); + assertThat(get(comparator.compare(Integer.MAX_VALUE, 0))).isEqualTo(GREATER_THAN); + } + + @ParameterizedTest + @MethodSource("comparatorProvider") + void testRandomLong(final ComparatorRunner runner) { + final Random random = new Random(3239480); + + // 5mil random longs to compare + final long[] values = random.longs().filter(i -> i >= 0).limit(5_000_000).toArray(); + + for (int i = 1; i < values.length; i++) { + final long long1 = values[i - 1]; + final long long2 = values[i]; + // Make sure the comparator under test gives the same outcome as just comparing two longs + final ComparatorTest.ComparatorResult result = get(runner.compare(long1, long2)); + final ComparatorTest.ComparatorResult expectedResult = get(Long.compare(long1, long2)); + + assertThat(result) + .withFailMessage( + () -> + "Compare mismatch - long1: " + + long1 + + ", long2: " + + long2 + + ", expected: " + + expectedResult + + ", actual: " + + result) + .isEqualTo(expectedResult); + + final ComparatorTest.ComparatorResult result2 = get(runner.compare(long2, long1)); + final ComparatorTest.ComparatorResult expectedResult2 = expectedResult.opposite(); + + assertThat(result) + .withFailMessage( + () -> + "Compare mismatch for - long2: " + + long2 + + ", long1: " + + long1 + + ", expected2: " + + expectedResult2 + + ", actual2: " + + result2) + .isEqualTo(expectedResult); + } + } + + @ParameterizedTest + @MethodSource("comparatorProvider") + void testRandomInt(final ComparatorRunner runner) { + final Random random = new Random(3239480); + + // 5mil random ints to compare + final int[] values = random.ints().filter(i -> i >= 0).limit(5_000_000).toArray(); + + for (int i = 1; i < values.length; i++) { + final int int1 = values[i - 1]; + final int int2 = values[i]; + // Make sure the comparator under test gives the same outcome as just comparing two ints + final ComparatorTest.ComparatorResult result = get(runner.compare(int1, int2)); + final ComparatorTest.ComparatorResult expectedResult = get(Integer.compare(int1, int2)); + + assertThat(result) + .withFailMessage( + () -> + "Compare mismatch for - int1: " + + int1 + + ", int2: " + + int2 + + ", expected: " + + expectedResult + + ", actual: " + + result) + .isEqualTo(expectedResult); + + final ComparatorTest.ComparatorResult result2 = get(runner.compare(int2, int1)); + final ComparatorTest.ComparatorResult expectedResult2 = expectedResult.opposite(); + + assertThat(result) + .withFailMessage( + () -> + "Compare mismatch for - int2: " + + int2 + + ", int1: " + + int1 + + ", expected2: " + + expectedResult2 + + ", actual2: " + + result2) + .isEqualTo(expectedResult); + } + } + + // -------------------------------------------------------------------------------- + + /** Tests {@link ByteBufferProxy}. */ + private static final class ByteBufferRunner implements ComparatorRunner { + + private static final Comparator COMPARATOR = + PROXY_OPTIMAL.getComparator(DbiFlags.MDB_INTEGERKEY); + + @Override + public int compare(long long1, long long2) { + // Convert arrays to buffers that are larger than the array, with + // limit set at the array length. One buffer bigger than the other. + ByteBuffer o1b = longToBuffer(long1, Long.BYTES * 3); + ByteBuffer o2b = longToBuffer(long2, Long.BYTES * 2); + final int result = COMPARATOR.compare(o1b, o2b); + + // Now swap which buffer is bigger + o1b = longToBuffer(long1, Long.BYTES * 2); + o2b = longToBuffer(long2, Long.BYTES * 3); + final int result2 = COMPARATOR.compare(o1b, o2b); + + assertThat(result2).isEqualTo(result); + + // Now try with buffers sized to the array. + o1b = longToBuffer(long1, Long.BYTES); + o2b = longToBuffer(long2, Long.BYTES); + final int result3 = COMPARATOR.compare(o1b, o2b); + + assertThat(result3).isEqualTo(result); + return result; + } + + @Override + public int compare(int int1, int int2) { + // Convert arrays to buffers that are larger than the array, with + // limit set at the array length. One buffer bigger than the other. + ByteBuffer o1b = intToBuffer(int1, Integer.BYTES * 3); + ByteBuffer o2b = intToBuffer(int2, Integer.BYTES * 2); + final int result = COMPARATOR.compare(o1b, o2b); + + // Now swap which buffer is bigger + o1b = intToBuffer(int1, Integer.BYTES * 2); + o2b = intToBuffer(int2, Integer.BYTES * 3); + final int result2 = COMPARATOR.compare(o1b, o2b); + + assertThat(result2).isEqualTo(result); + + // Now try with buffers sized to the array. + o1b = intToBuffer(int1, Integer.BYTES); + o2b = intToBuffer(int2, Integer.BYTES); + final int result3 = COMPARATOR.compare(o1b, o2b); + + assertThat(result3).isEqualTo(result); + return result; + } + + private ByteBuffer longToBuffer(final long val, final int bufferCapacity) { + final ByteBuffer byteBuffer = ByteBuffer.allocate(bufferCapacity); + byteBuffer.order(ByteOrder.nativeOrder()); + byteBuffer.putLong(0, val); + byteBuffer.limit(Long.BYTES); + byteBuffer.position(0); + return byteBuffer; + } + + private ByteBuffer intToBuffer(final int val, final int bufferCapacity) { + final ByteBuffer byteBuffer = ByteBuffer.allocate(bufferCapacity); + byteBuffer.order(ByteOrder.nativeOrder()); + byteBuffer.putInt(0, val); + byteBuffer.limit(Integer.BYTES); + byteBuffer.position(0); + return byteBuffer; + } + } + + // -------------------------------------------------------------------------------- + + /** Tests {@link DirectBufferProxy}. */ + private static final class DirectBufferRunner implements ComparatorRunner { + private static final Comparator COMPARATOR = + PROXY_DB.getComparator(DbiFlags.MDB_INTEGERKEY); + + @Override + public int compare(long long1, long long2) { + final UnsafeBuffer o1b = new UnsafeBuffer(new byte[Long.BYTES]); + final UnsafeBuffer o2b = new UnsafeBuffer(new byte[Long.BYTES]); + o1b.putLong(0, long1, ByteOrder.nativeOrder()); + o2b.putLong(0, long2, ByteOrder.nativeOrder()); + return COMPARATOR.compare(o1b, o2b); + } + + @Override + public int compare(int int1, int int2) { + final UnsafeBuffer o1b = new UnsafeBuffer(new byte[Integer.BYTES]); + final UnsafeBuffer o2b = new UnsafeBuffer(new byte[Integer.BYTES]); + o1b.putInt(0, int1, ByteOrder.nativeOrder()); + o2b.putInt(0, int2, ByteOrder.nativeOrder()); + return COMPARATOR.compare(o1b, o2b); + } + } + + /** Tests {@link ByteBufProxy}. */ + private static final class NettyRunner implements ComparatorRunner { + + private static final Comparator COMPARATOR = + PROXY_NETTY.getComparator(DbiFlags.MDB_INTEGERKEY); + + @Override + public int compare(long long1, long long2) { + final ByteBuf o1b = DEFAULT.directBuffer(Long.BYTES); + final ByteBuf o2b = DEFAULT.directBuffer(Long.BYTES); + if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) { + o1b.writeLongLE(long1); + o2b.writeLongLE(long2); + } else { + o1b.writeLong(long1); + o2b.writeLong(long2); + } + o1b.resetReaderIndex(); + o2b.resetReaderIndex(); + final int res = COMPARATOR.compare(o1b, o2b); + o1b.release(); + o2b.release(); + return res; + } + + @Override + public int compare(int int1, int int2) { + final ByteBuf o1b = DEFAULT.directBuffer(Integer.BYTES); + final ByteBuf o2b = DEFAULT.directBuffer(Integer.BYTES); + if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) { + o1b.writeIntLE(int1); + o2b.writeIntLE(int2); + } else { + o1b.writeInt(int1); + o2b.writeInt(int2); + } + o1b.resetReaderIndex(); + o2b.resetReaderIndex(); + final int res = COMPARATOR.compare(o1b, o2b); + o1b.release(); + o2b.release(); + return res; + } + } + + // -------------------------------------------------------------------------------- + + /** Interface that can test a {@link BufferProxy} compare method. */ + private interface ComparatorRunner { + + /** + * Write the two longs to a buffer using native order and compare the resulting buffers. + * + * @param long1 lhs value + * @param long2 rhs value + * @return as per {@link Comparable} + */ + int compare(final long long1, final long long2); + + /** + * Write the two int to a buffer using native order and compare the resulting buffers. + * + * @param int1 lhs value + * @param int2 rhs value + * @return as per {@link Comparable} + */ + int compare(final int int1, final int int2); + } +} diff --git a/src/test/java/org/lmdbjava/ComparatorTest.java b/src/test/java/org/lmdbjava/ComparatorTest.java index 2fabb3da..f255ec2a 100644 --- a/src/test/java/org/lmdbjava/ComparatorTest.java +++ b/src/test/java/org/lmdbjava/ComparatorTest.java @@ -150,7 +150,7 @@ private static final class UnsignedByteArrayRunner implements ComparatorRunner { @Override public int compare(final byte[] o1, final byte[] o2) { - final Comparator c = PROXY_BA.getUnsignedComparator(); + final Comparator c = PROXY_BA.getComparator(); return c.compare(o1, o2); } } @@ -269,6 +269,16 @@ static ComparatorResult get(final int comparatorResult) { } return comparatorResult < 0 ? LESS_THAN : GREATER_THAN; } + + ComparatorResult opposite() { + if (this == LESS_THAN) { + return GREATER_THAN; + } else if (this == GREATER_THAN) { + return LESS_THAN; + } else { + return EQUAL_TO; + } + } } /** Interface that can test a {@link BufferProxy} compare method. */ diff --git a/src/test/java/org/lmdbjava/CopyFlagSetTest.java b/src/test/java/org/lmdbjava/CopyFlagSetTest.java new file mode 100644 index 00000000..922554d7 --- /dev/null +++ b/src/test/java/org/lmdbjava/CopyFlagSetTest.java @@ -0,0 +1,72 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; +import java.util.HashSet; +import org.junit.jupiter.api.Test; + +public class CopyFlagSetTest { + + @Test + public void testEmpty() { + final CopyFlagSet copyFlagSet = CopyFlagSet.empty(); + assertThat(copyFlagSet.getMask()).isEqualTo(0); + assertThat(copyFlagSet.size()).isEqualTo(0); + assertThat(copyFlagSet.isEmpty()).isEqualTo(true); + assertThat(copyFlagSet.isSet(CopyFlags.MDB_CP_COMPACT)).isEqualTo(false); + final CopyFlagSet copyFlagSet2 = CopyFlagSet.builder().build(); + assertThat(copyFlagSet).isEqualTo(copyFlagSet2); + assertThat(copyFlagSet).isNotEqualTo(CopyFlagSet.of(CopyFlags.MDB_CP_COMPACT)); + assertThat(copyFlagSet) + .isNotEqualTo(CopyFlagSet.builder().setFlag(CopyFlags.MDB_CP_COMPACT).build()); + } + + @Test + public void testOf() { + final CopyFlags copyFlag = CopyFlags.MDB_CP_COMPACT; + final CopyFlagSet copyFlagSet = CopyFlagSet.of(copyFlag); + assertThat(copyFlagSet.getMask()).isEqualTo(MaskedFlag.mask(copyFlag)); + assertThat(copyFlagSet.size()).isEqualTo(1); + for (CopyFlags flag : copyFlagSet) { + assertThat(copyFlagSet.isSet(flag)).isEqualTo(true); + } + + final CopyFlagSet copyFlagSet2 = CopyFlagSet.builder().setFlag(copyFlag).build(); + assertThat(copyFlagSet).isEqualTo(copyFlagSet2); + } + + @Test + public void testBuilder() { + final CopyFlags copyFlag1 = CopyFlags.MDB_CP_COMPACT; + final CopyFlagSet copyFlagSet = CopyFlagSet.builder().setFlag(copyFlag1).build(); + assertThat(copyFlagSet.getMask()).isEqualTo(MaskedFlag.mask(copyFlag1)); + assertThat(copyFlagSet.size()).isEqualTo(1); + assertThat(copyFlagSet.isSet(CopyFlags.MDB_CP_COMPACT)).isEqualTo(true); + for (CopyFlags flag : copyFlagSet) { + assertThat(copyFlagSet.isSet(flag)).isEqualTo(true); + } + final CopyFlagSet copyFlagSet2 = CopyFlagSet.builder().withFlags(copyFlag1).build(); + final CopyFlagSet copyFlagSet3 = + CopyFlagSet.builder() + .withFlags(new HashSet<>(Collections.singletonList(copyFlag1))) + .build(); + assertThat(copyFlagSet).isEqualTo(copyFlagSet2); + assertThat(copyFlagSet).isEqualTo(copyFlagSet3); + } +} diff --git a/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java b/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java new file mode 100644 index 00000000..5765ac52 --- /dev/null +++ b/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java @@ -0,0 +1,615 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; +import static org.assertj.core.api.Assertions.assertThat; +import static org.lmdbjava.DbiFlags.MDB_CREATE; +import static org.lmdbjava.DbiFlags.MDB_DUPSORT; +import static org.lmdbjava.DbiFlags.MDB_INTEGERDUP; +import static org.lmdbjava.Env.create; +import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; +import static org.lmdbjava.KeyRange.all; +import static org.lmdbjava.KeyRange.allBackward; +import static org.lmdbjava.KeyRange.atLeast; +import static org.lmdbjava.KeyRange.atLeastBackward; +import static org.lmdbjava.KeyRange.atMost; +import static org.lmdbjava.KeyRange.atMostBackward; +import static org.lmdbjava.KeyRange.closed; +import static org.lmdbjava.KeyRange.closedBackward; +import static org.lmdbjava.KeyRange.closedOpen; +import static org.lmdbjava.KeyRange.closedOpenBackward; +import static org.lmdbjava.KeyRange.greaterThan; +import static org.lmdbjava.KeyRange.greaterThanBackward; +import static org.lmdbjava.KeyRange.lessThan; +import static org.lmdbjava.KeyRange.lessThanBackward; +import static org.lmdbjava.KeyRange.open; +import static org.lmdbjava.KeyRange.openBackward; +import static org.lmdbjava.KeyRange.openClosed; +import static org.lmdbjava.KeyRange.openClosedBackward; +import static org.lmdbjava.TestUtils.DB_1; +import static org.lmdbjava.TestUtils.DB_2; +import static org.lmdbjava.TestUtils.DB_3; +import static org.lmdbjava.TestUtils.DB_4; +import static org.lmdbjava.TestUtils.POSIX_MODE; +import static org.lmdbjava.TestUtils.bb; +import static org.lmdbjava.TestUtils.bbNative; +import static org.lmdbjava.TestUtils.getNativeInt; +import static org.lmdbjava.TestUtils.getNativeIntOrLong; + +import com.google.common.primitives.UnsignedBytes; +import java.nio.ByteBuffer; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Deque; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.Parameter; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.support.ParameterDeclarations; +import org.lmdbjava.CursorIterable.KeyVal; + +/** + * Test {@link CursorIterable} using {@link DbiFlags#MDB_INTEGERKEY} to ensure that comparators work + * with native order integer keys. + */ +@Disabled // Waiting for the merge of stroomdev66's cursor tests +@ParameterizedClass(name = "{index}: dbi: {0}") +@ArgumentsSource(CursorIterableTest.MyArgumentProvider.class) +public final class CursorIterableIntegerDupTest { + + private static final DbiFlagSet DBI_FLAGS = + DbiFlagSet.of(MDB_CREATE, MDB_INTEGERDUP, MDB_DUPSORT); + private static final BufferProxy BUFFER_PROXY = ByteBufferProxy.PROXY_OPTIMAL; + private static final List> INPUT_DATA; + + static { + // 2 => 21 + // 2 => 22 + // 3 => 31 + // ... + // 9 => 92 + INPUT_DATA = new ArrayList<>(); + for (int i = 2; i <= 9; i++) { + final int val1 = (i * 10) + 1; + final int val2 = (i * 10) + 2; + INPUT_DATA.add(new AbstractMap.SimpleEntry<>(i, val1)); + INPUT_DATA.add(new AbstractMap.SimpleEntry<>(i, val2)); + } + } + + private TempDir tempDir; + private Env env; + private Deque> expectedEntriesDeque; + + @Parameter public DbiFactory dbiFactory; + + @BeforeEach + public void before() { + tempDir = new TempDir(); + final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; + env = + create(bufferProxy) + .setMapSize(KIBIBYTES.toBytes(256)) + .setMaxReaders(1) + .setMaxDbs(3) + .setFilePermissions(POSIX_MODE) + .setEnvFlags(MDB_NOSUBDIR) + .open(tempDir.createTempFile()); + + populateExpectedEntriesDeque(); + } + + @AfterEach + public void after() { + env.close(); + tempDir.cleanup(); + } + + @Test + public void allBackwardTest() { + verify(allBackward(), 8, 6, 4, 2); + } + + @Test + public void allTest() { + verify(all(), 2, 4, 6, 8); + } + + @Test + public void atLeastBackwardTest() { + verify(atLeastBackward(bbNative(5)), 4, 2); + verify(atLeastBackward(bbNative(6)), 6, 4, 2); + verify(atLeastBackward(bbNative(9)), 8, 6, 4, 2); + } + + @Test + public void atLeastTest() { + verify(atLeast(bbNative(5)), 6, 8); + verify(atLeast(bbNative(6)), 6, 8); + } + + @Test + public void atMostBackwardTest() { + verify(atMostBackward(bbNative(5)), 8, 6); + verify(atMostBackward(bbNative(6)), 8, 6); + } + + @Test + public void atMostTest() { + verify(atMost(bbNative(5)), 2, 4); + verify(atMost(bbNative(6)), 2, 4, 6); + } + + private void populateExpectedEntriesDeque() { + expectedEntriesDeque = new LinkedList<>(); + expectedEntriesDeque.addAll(INPUT_DATA); + } + + private void populateDatabase(final Dbi dbi) { + try (Txn txn = env.txnWrite()) { + final Cursor c = dbi.openCursor(txn); + for (Map.Entry entry : INPUT_DATA) { + c.put(bbNative(entry.getKey()), bb(entry.getValue())); + } + txn.commit(); + } + + try (Txn txn = env.txnRead(); + CursorIterable c = dbi.iterate(txn)) { + + // for (final KeyVal kv : c) { + // System.out.print(getNativeInt(kv.key()) + " => " + kv.val().getInt()); + // System.out.print(", "); + // } + // System.out.println(); + } + } + + private int[] rangeInc(final int fromInc, final int toInc) { + int idx = 0; + if (fromInc <= toInc) { + // Forwards + final int[] arr = new int[toInc - fromInc + 1]; + for (int i = fromInc; i <= toInc; i++) { + arr[idx++] = i; + } + return arr; + } else { + // Backwards + final int[] arr = new int[fromInc - toInc + 1]; + for (int i = fromInc; i >= toInc; i--) { + arr[idx++] = i; + } + return arr; + } + } + + @Test + public void closedBackwardTest() { + verify(closedBackward(bbNative(7), bbNative(3)), rangeInc(7, 3)); + verify(closedBackward(bbNative(6), bbNative(2)), rangeInc(6, 2)); + verify(closedBackward(bbNative(9), bbNative(3)), rangeInc(9, 3)); + } + + @Test + public void closedOpenBackwardTest() { + verify(closedOpenBackward(bbNative(8), bbNative(3)), rangeInc(8, 4)); + verify(closedOpenBackward(bbNative(7), bbNative(2)), rangeInc(7, 3)); + verify(closedOpenBackward(bbNative(9), bbNative(3)), rangeInc(9, 4)); + } + + @Test + public void closedOpenTest() { + verify(closedOpen(bbNative(3), bbNative(8)), rangeInc(3, 7)); + verify(closedOpen(bbNative(2), bbNative(6)), rangeInc(2, 5)); + } + + @Test + public void closedTest() { + verify(closed(bbNative(3), bbNative(7)), rangeInc(3, 7)); + verify(closed(bbNative(2), bbNative(6)), rangeInc(2, 6)); + verify(closed(bbNative(1), bbNative(7)), rangeInc(2, 7)); + } + + @Test + public void greaterThanBackwardTest() { + verify(greaterThanBackward(bbNative(6)), rangeInc(5, 2)); + verify(greaterThanBackward(bbNative(7)), rangeInc(6, 2)); + verify(greaterThanBackward(bbNative(9)), rangeInc(8, 2)); + } + + @Test + public void greaterThanTest() { + verify(greaterThan(bbNative(4)), rangeInc(5, 9)); + verify(greaterThan(bbNative(3)), rangeInc(4, 9)); + } + + public void iterableOnlyReturnedOnce() { + Assertions.assertThatThrownBy( + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + c.iterator(); // ok + c.iterator(); // fails + } + }) + .isInstanceOf(IllegalStateException.class); + } + + @Test + public void iterate() { + populateExpectedEntriesDeque(); + final Dbi db = getDb(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + + for (final KeyVal kv : c) { + final Map.Entry entry = expectedEntriesDeque.pollFirst(); + // System.out.println(entry.getKey() + " => " + entry.getValue()); + assertThat(getNativeInt(kv.key())).isEqualTo(entry.getKey()); + assertThat(kv.val().getInt()).isEqualTo(entry.getValue()); + } + } + } + + public void iteratorOnlyReturnedOnce() { + Assertions.assertThatThrownBy( + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + c.iterator(); // ok + c.iterator(); // fails + } + }) + .isInstanceOf(IllegalStateException.class); + } + + @Test + public void lessThanBackwardTest() { + verify(lessThanBackward(bbNative(5)), 8, 6); + verify(lessThanBackward(bbNative(2)), 8, 6, 4); + } + + @Test + public void lessThanTest() { + verify(lessThan(bbNative(5)), 2, 4); + verify(lessThan(bbNative(8)), 2, 4, 6); + } + + public void nextThrowsNoSuchElementExceptionIfNoMoreElements() { + Assertions.assertThatThrownBy( + () -> { + populateExpectedEntriesDeque(); + final Dbi db = getDb(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + final Iterator> i = c.iterator(); + while (i.hasNext()) { + final KeyVal kv = i.next(); + assertThat(getNativeInt(kv.key())) + .isEqualTo(expectedEntriesDeque.pollFirst().getKey()); + assertThat(kv.val().getInt()) + .isEqualTo(expectedEntriesDeque.pollFirst().getValue()); + } + assertThat(i.hasNext()).isEqualTo(false); + i.next(); + } + }) + .isInstanceOf(NoSuchElementException.class); + } + + @Test + public void openBackwardTest() { + verify(openBackward(bbNative(7), bbNative(2)), 6, 4); + verify(openBackward(bbNative(8), bbNative(1)), 6, 4, 2); + verify(openBackward(bbNative(9), bbNative(4)), 8, 6); + } + + @Test + public void openClosedBackwardTest() { + verify(openClosedBackward(bbNative(7), bbNative(2)), 6, 4, 2); + verify(openClosedBackward(bbNative(8), bbNative(4)), 6, 4); + verify(openClosedBackward(bbNative(9), bbNative(4)), 8, 6, 4); + } + + @Test + public void openClosedBackwardTestWithGuava() { + final Comparator guava = UnsignedBytes.lexicographicalComparator(); + final Comparator comparator = + (bb1, bb2) -> { + final byte[] array1 = new byte[bb1.remaining()]; + final byte[] array2 = new byte[bb2.remaining()]; + bb1.mark(); + bb2.mark(); + bb1.get(array1); + bb2.get(array2); + bb1.reset(); + bb2.reset(); + return guava.compare(array1, array2); + }; + final Dbi guavaDbi = env.openDbi(DB_1, comparator, MDB_CREATE); + populateDatabase(guavaDbi); + verify(openClosedBackward(bbNative(7), bbNative(2)), guavaDbi, 6, 4, 2); + verify(openClosedBackward(bbNative(8), bbNative(4)), guavaDbi, 6, 4); + } + + @Test + public void openClosedTest() { + verify(openClosed(bbNative(3), bbNative(8)), 4, 6, 8); + verify(openClosed(bbNative(2), bbNative(6)), 4, 6); + } + + @Test + public void openTest() { + verify(open(bbNative(3), bbNative(7)), 4, 6); + verify(open(bbNative(2), bbNative(8)), 4, 6); + } + + @Test + public void removeOddElements() { + final Dbi db = getDb(); + verify(db, all(), 2, 4, 6, 8); + int idx = -1; + try (Txn txn = env.txnWrite()) { + try (CursorIterable ci = db.iterate(txn)) { + final Iterator> c = ci.iterator(); + while (c.hasNext()) { + c.next(); + idx++; + if (idx % 2 == 0) { + c.remove(); + } + } + } + txn.commit(); + } + verify(db, all(), 4, 8); + } + + public void nextWithClosedEnvTest() { + Assertions.assertThatThrownBy( + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + + env.close(); + c.next(); + } + } + }) + .isInstanceOf(Env.AlreadyClosedException.class); + } + + public void removeWithClosedEnvTest() { + Assertions.assertThatThrownBy( + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnWrite()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + final KeyVal keyVal = c.next(); + assertThat(keyVal).isNotNull(); + env.close(); + c.remove(); + } + } + }) + .isInstanceOf(Env.AlreadyClosedException.class); + } + + public void hasNextWithClosedEnvTest() { + Assertions.assertThatThrownBy( + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + + env.close(); + c.hasNext(); + } + } + }) + .isInstanceOf(Env.AlreadyClosedException.class); + } + + public void forEachRemainingWithClosedEnvTest() { + Assertions.assertThatThrownBy( + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + + env.close(); + c.forEachRemaining(keyVal -> {}); + } + } + }) + .isInstanceOf(Env.AlreadyClosedException.class); + } + + private void verify(final KeyRange range, final int... expectedKeys) { + // Verify using all comparator types + final Dbi db = getDb(); + verify(range, db, expectedKeys); + } + + private void verify( + final Dbi dbi, final KeyRange range, final int... expectedKeys) { + verify(range, dbi, expectedKeys); + } + + private void verify( + final KeyRange range, final Dbi dbi, final int... expectedKeys) { + final boolean isForward = range.getType().isDirectionForward(); + + final List expectedValues = + Arrays.stream(expectedKeys) + .boxed() + .flatMap( + key -> { + final int base = key * 10; + return isForward ? Stream.of(base + 1, base + 2) : Stream.of(base + 2, base + 1); + }) + .collect(Collectors.toList()); + + final List results = new ArrayList<>(); + // System.out.println(rangeToString(range) + ", expected: " + expectedValues); + + try (Txn txn = env.txnRead(); + CursorIterable c = dbi.iterate(txn, range)) { + for (final KeyVal kv : c) { + final int key = getNativeInt(kv.key()); + final int val = kv.val().getInt(); + // System.out.println(key + " => " + val); + results.add(val); + assertThat(val) + .satisfiesAnyOf( + v -> assertThat(v).isEqualTo((key * 10) + 1), + v -> assertThat(v).isEqualTo((key * 10) + 2)); + } + } + + assertThat(results).hasSize(expectedValues.size()); + for (int idx = 0; idx < results.size(); idx++) { + assertThat(results.get(idx)).isEqualTo(expectedValues.get(idx)); + } + } + + private String rangeToString(final KeyRange range) { + final ByteBuffer start = range.getStart(); + final ByteBuffer stop = range.getStop(); + return range.getType() + + " start: " + + (start != null ? getNativeInt(start) : "") + + " stop: " + + (stop != null ? getNativeInt(stop) : ""); + } + + private Dbi getDb() { + final Dbi dbi = dbiFactory.factory.apply(env); + populateDatabase(dbi); + return dbi; + } + + // -------------------------------------------------------------------------------- + + private static class DbiFactory { + private final String name; + private final Function, Dbi> factory; + + private DbiFactory(String name, Function, Dbi> factory) { + this.name = name; + this.factory = factory; + } + + @Override + public String toString() { + return name; + } + } + + // -------------------------------------------------------------------------------- + + static class MyArgumentProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments( + ParameterDeclarations parameters, ExtensionContext context) throws Exception { + final DbiFactory defaultComparatorDb = + new DbiFactory( + "defaultComparator", + env -> + env.buildDbi() + .setDbName(DB_1) + .withDefaultComparator() + .setDbiFlags(DBI_FLAGS) + .open()); + final DbiFactory nativeComparatorDb = + new DbiFactory( + "nativeComparator", + env -> + env.buildDbi() + .setDbName(DB_2) + .withNativeComparator() + .setDbiFlags(DBI_FLAGS) + .open()); + final DbiFactory callbackComparatorDb = + new DbiFactory( + "callbackComparator", + env -> + env.buildDbi() + .setDbName(DB_3) + .withCallbackComparator(MyArgumentProvider::buildComparator) + .setDbiFlags(DBI_FLAGS) + .open()); + final DbiFactory iteratorComparatorDb = + new DbiFactory( + "iteratorComparator", + env -> + env.buildDbi() + .setDbName(DB_4) + .withIteratorComparator(MyArgumentProvider::buildComparator) + .setDbiFlags(DBI_FLAGS) + .open()); + return Stream.of( + defaultComparatorDb, nativeComparatorDb, callbackComparatorDb, iteratorComparatorDb) + .map(Arguments::of); + } + + private static Comparator buildComparator(final DbiFlagSet dbiFlagSet) { + final Comparator baseComparator = BUFFER_PROXY.getComparator(DBI_FLAGS); + return (o1, o2) -> { + if (o1.remaining() != o2.remaining()) { + // Make sure LMDB is always giving us consistent key lengths. + Assertions.fail( + "o1: " + + o1 + + " " + + getNativeIntOrLong(o1) + + ", o2: " + + o2 + + " " + + getNativeIntOrLong(o2)); + } + return baseComparator.compare(o1, o2); + }; + } + } +} diff --git a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java new file mode 100644 index 00000000..e8f039cf --- /dev/null +++ b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java @@ -0,0 +1,662 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.lmdbjava.DbiFlags.MDB_CREATE; +import static org.lmdbjava.DbiFlags.MDB_INTEGERKEY; +import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; +import static org.lmdbjava.KeyRange.all; +import static org.lmdbjava.KeyRange.allBackward; +import static org.lmdbjava.KeyRange.atLeast; +import static org.lmdbjava.KeyRange.atLeastBackward; +import static org.lmdbjava.KeyRange.atMost; +import static org.lmdbjava.KeyRange.atMostBackward; +import static org.lmdbjava.KeyRange.closed; +import static org.lmdbjava.KeyRange.closedBackward; +import static org.lmdbjava.KeyRange.closedOpen; +import static org.lmdbjava.KeyRange.closedOpenBackward; +import static org.lmdbjava.KeyRange.greaterThan; +import static org.lmdbjava.KeyRange.greaterThanBackward; +import static org.lmdbjava.KeyRange.lessThan; +import static org.lmdbjava.KeyRange.lessThanBackward; +import static org.lmdbjava.KeyRange.open; +import static org.lmdbjava.KeyRange.openBackward; +import static org.lmdbjava.KeyRange.openClosed; +import static org.lmdbjava.KeyRange.openClosedBackward; +import static org.lmdbjava.PutFlags.MDB_NOOVERWRITE; +import static org.lmdbjava.TestUtils.DB_1; +import static org.lmdbjava.TestUtils.DB_2; +import static org.lmdbjava.TestUtils.DB_3; +import static org.lmdbjava.TestUtils.DB_4; +import static org.lmdbjava.TestUtils.bb; +import static org.lmdbjava.TestUtils.bbNative; +import static org.lmdbjava.TestUtils.getNativeInt; +import static org.lmdbjava.TestUtils.getNativeIntOrLong; +import static org.lmdbjava.TestUtils.getNativeLong; +import static org.lmdbjava.TestUtils.getString; + +import com.google.common.primitives.UnsignedBytes; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Deque; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.Parameter; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.support.ParameterDeclarations; +import org.lmdbjava.CursorIterable.KeyVal; + +/** + * Test {@link CursorIterable} using {@link DbiFlags#MDB_INTEGERKEY} to ensure that comparators work + * with native order integer keys. + */ +@ParameterizedClass(name = "{index}: dbi: {0}") +@ArgumentsSource(CursorIterableIntegerKeyTest.MyArgumentProvider.class) +public final class CursorIterableIntegerKeyTest { + + private static final DbiFlagSet DBI_FLAGS = DbiFlagSet.of(MDB_CREATE, MDB_INTEGERKEY); + private static final BufferProxy BUFFER_PROXY = ByteBufferProxy.PROXY_OPTIMAL; + + private TempDir tempDir; + private Env env; + private Deque list; + + @Parameter public DbiFactory dbiFactory; + + @BeforeEach + public void before() { + tempDir = new TempDir(); + final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; + env = + Env.create(bufferProxy) + .setMapSize(KIBIBYTES.toBytes(256)) + .setMaxReaders(1) + .setMaxDbs(3) + .setEnvFlags(MDB_NOSUBDIR) + .open(tempDir.createTempFile()); + + populateTestDataList(); + } + + @AfterEach + public void after() { + env.close(); + tempDir.cleanup(); + } + + @Test + public void testNumericOrderLong() { + final Dbi dbi = dbiFactory.factory.apply(env); + + try (Txn txn = env.txnWrite()) { + final Cursor c = dbi.openCursor(txn); + long i = 1; + while (true) { + // System.out.println("putting " + i); + c.put(bbNative(i), bb(i + "-long")); + final long i2 = i * 10; + if (i2 < i) { + // Overflowed + break; + } + i = i2; + } + txn.commit(); + } + + final List> entries = new ArrayList<>(); + try (Txn txn = env.txnRead()) { + try (CursorIterable iterable = dbi.iterate(txn)) { + for (KeyVal keyVal : iterable) { + assertThat(keyVal.key().remaining()).isEqualTo(Long.BYTES); + final String val = getString(keyVal.val()); + final long key = getNativeLong(keyVal.key()); + entries.add(new AbstractMap.SimpleEntry<>(key, val)); + // System.out.println(val); + } + } + } + + final List dbKeys = entries.stream().map(Map.Entry::getKey).collect(Collectors.toList()); + final List dbKeysSorted = + entries.stream().map(Map.Entry::getKey).sorted().collect(Collectors.toList()); + for (int i = 0; i < dbKeys.size(); i++) { + final long dbKey1 = dbKeys.get(i); + final long dbKey2 = dbKeysSorted.get(i); + assertThat(dbKey1).isEqualTo(dbKey2); + } + } + + @Test + public void testNumericOrderInt() { + final Dbi dbi = dbiFactory.factory.apply(env); + + try (Txn txn = env.txnWrite()) { + final Cursor c = dbi.openCursor(txn); + int i = 1; + while (true) { + // System.out.println("putting " + i); + c.put(bbNative(i), bb(i + "-int")); + final int i2 = i * 10; + if (i2 < i) { + // Overflowed + break; + } + i = i2; + } + txn.commit(); + } + + final List> entries = new ArrayList<>(); + try (Txn txn = env.txnRead()) { + try (CursorIterable iterable = dbi.iterate(txn)) { + for (KeyVal keyVal : iterable) { + assertThat(keyVal.key().remaining()).isEqualTo(Integer.BYTES); + final String val = getString(keyVal.val()); + final int key = TestUtils.getNativeInt(keyVal.key()); + entries.add(new AbstractMap.SimpleEntry<>(key, val)); + // System.out.println(val); + } + } + } + + final List dbKeys = + entries.stream().map(Map.Entry::getKey).collect(Collectors.toList()); + final List dbKeysSorted = + entries.stream().map(Map.Entry::getKey).sorted().collect(Collectors.toList()); + for (int i = 0; i < dbKeys.size(); i++) { + final long dbKey1 = dbKeys.get(i); + final long dbKey2 = dbKeysSorted.get(i); + assertThat(dbKey1).isEqualTo(dbKey2); + } + } + + @Test + public void testIntegerKeyKeySize() { + final Dbi db = dbiFactory.factory.apply(env); + long maxIntAsLong = Integer.MAX_VALUE; + + try (Txn txn = env.txnWrite()) { + // System.out.println("Flags: " + db.listFlags(txn)); + int val = 0; + db.put(txn, bbNative(0L), bb("val_" + ++val)); + db.put(txn, bbNative(10L), bb("val_" + ++val)); + db.put(txn, bbNative(maxIntAsLong - 1_111_111_111), bb("val_" + ++val)); + db.put(txn, bbNative(maxIntAsLong - 111_111_111), bb("val_" + ++val)); + db.put(txn, bbNative(maxIntAsLong - 111_111), bb("val_" + ++val)); + db.put(txn, bbNative(maxIntAsLong - 111), bb("val_" + ++val)); + db.put(txn, bbNative(maxIntAsLong + 111), bb("val_" + ++val)); + db.put(txn, bbNative(maxIntAsLong + 111_111), bb("val_" + ++val)); + db.put(txn, bbNative(maxIntAsLong + 111_111_111), bb("val_" + ++val)); + db.put(txn, bbNative(maxIntAsLong + 1_111_111_111), bb("val_" + ++val)); + db.put(txn, bbNative(Long.MAX_VALUE), bb("val_" + ++val)); + txn.commit(); + } + + try (Txn txn = env.txnRead()) { + try (CursorIterable iterable = db.iterate(txn)) { + for (KeyVal keyVal : iterable) { + final String val = getString(keyVal.val()); + final long key = getNativeLong(keyVal.key()); + final int remaining = keyVal.key().remaining(); + // System.out.println("key: " + key + ", val: " + val + ", remaining: " + + // remaining); + } + } + } + } + + @Test + public void allBackwardTest() { + verify(allBackward(), 8, 6, 4, 2); + } + + @Test + public void allTest() { + verify(all(), 2, 4, 6, 8); + } + + @Test + public void atLeastBackwardTest() { + verify(atLeastBackward(bbNative(5)), 4, 2); + verify(atLeastBackward(bbNative(6)), 6, 4, 2); + verify(atLeastBackward(bbNative(9)), 8, 6, 4, 2); + } + + @Test + public void atLeastTest() { + verify(atLeast(bbNative(5)), 6, 8); + verify(atLeast(bbNative(6)), 6, 8); + } + + @Test + public void atMostBackwardTest() { + verify(atMostBackward(bbNative(5)), 8, 6); + verify(atMostBackward(bbNative(6)), 8, 6); + } + + @Test + public void atMostTest() { + verify(atMost(bbNative(5)), 2, 4); + verify(atMost(bbNative(6)), 2, 4, 6); + } + + private void populateTestDataList() { + list = new LinkedList<>(); + list.addAll(asList(2, 3, 4, 5, 6, 7, 8, 9)); + } + + private void populateDatabase(final Dbi dbi) { + try (Txn txn = env.txnWrite()) { + final Cursor c = dbi.openCursor(txn); + c.put(bbNative(2), bb(3), MDB_NOOVERWRITE); + c.put(bbNative(4), bb(5)); + c.put(bbNative(6), bb(7)); + c.put(bbNative(8), bb(9)); + txn.commit(); + } + } + + @Test + public void closedBackwardTest() { + verify(closedBackward(bbNative(7), bbNative(3)), 6, 4); + verify(closedBackward(bbNative(6), bbNative(2)), 6, 4, 2); + verify(closedBackward(bbNative(9), bbNative(3)), 8, 6, 4); + } + + @Test + public void closedOpenBackwardTest() { + verify(closedOpenBackward(bbNative(8), bbNative(3)), 8, 6, 4); + verify(closedOpenBackward(bbNative(7), bbNative(2)), 6, 4); + verify(closedOpenBackward(bbNative(9), bbNative(3)), 8, 6, 4); + } + + @Test + public void closedOpenTest() { + verify(closedOpen(bbNative(3), bbNative(8)), 4, 6); + verify(closedOpen(bbNative(2), bbNative(6)), 2, 4); + } + + @Test + public void closedTest() { + verify(closed(bbNative(3), bbNative(7)), 4, 6); + verify(closed(bbNative(2), bbNative(6)), 2, 4, 6); + verify(closed(bbNative(1), bbNative(7)), 2, 4, 6); + } + + @Test + public void greaterThanBackwardTest() { + verify(greaterThanBackward(bbNative(6)), 4, 2); + verify(greaterThanBackward(bbNative(7)), 6, 4, 2); + verify(greaterThanBackward(bbNative(9)), 8, 6, 4, 2); + } + + @Test + public void greaterThanTest() { + verify(greaterThan(bbNative(4)), 6, 8); + verify(greaterThan(bbNative(3)), 4, 6, 8); + } + + public void iterableOnlyReturnedOnce() { + Assertions.assertThatThrownBy( + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + c.iterator(); // ok + c.iterator(); // fails + } + }) + .isInstanceOf(IllegalStateException.class); + } + + @Test + public void iterate() { + populateTestDataList(); + final Dbi db = getDb(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + + for (final KeyVal kv : c) { + assertThat(getNativeInt(kv.key())).isEqualTo(list.pollFirst()); + assertThat(kv.val().getInt()).isEqualTo(list.pollFirst()); + } + } + } + + public void iteratorOnlyReturnedOnce() { + Assertions.assertThatThrownBy( + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + c.iterator(); // ok + c.iterator(); // fails + } + }) + .isInstanceOf(IllegalStateException.class); + } + + @Test + public void lessThanBackwardTest() { + verify(lessThanBackward(bbNative(5)), 8, 6); + verify(lessThanBackward(bbNative(2)), 8, 6, 4); + } + + @Test + public void lessThanTest() { + verify(lessThan(bbNative(5)), 2, 4); + verify(lessThan(bbNative(8)), 2, 4, 6); + } + + public void nextThrowsNoSuchElementExceptionIfNoMoreElements() { + Assertions.assertThatThrownBy( + () -> { + populateTestDataList(); + final Dbi db = getDb(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + final Iterator> i = c.iterator(); + while (i.hasNext()) { + final KeyVal kv = i.next(); + assertThat(getNativeInt(kv.key())).isEqualTo(list.pollFirst()); + assertThat(kv.val().getInt()).isEqualTo(list.pollFirst()); + } + assertThat(i.hasNext()).isEqualTo(false); + i.next(); + } + }) + .isInstanceOf(NoSuchElementException.class); + } + + @Test + public void openBackwardTest() { + verify(openBackward(bbNative(7), bbNative(2)), 6, 4); + verify(openBackward(bbNative(8), bbNative(1)), 6, 4, 2); + verify(openBackward(bbNative(9), bbNative(4)), 8, 6); + } + + @Test + public void openClosedBackwardTest() { + verify(openClosedBackward(bbNative(7), bbNative(2)), 6, 4, 2); + verify(openClosedBackward(bbNative(8), bbNative(4)), 6, 4); + verify(openClosedBackward(bbNative(9), bbNative(4)), 8, 6, 4); + } + + @Test + public void openClosedBackwardTestWithGuava() { + final Comparator guava = UnsignedBytes.lexicographicalComparator(); + final Comparator comparator = + (bb1, bb2) -> { + final byte[] array1 = new byte[bb1.remaining()]; + final byte[] array2 = new byte[bb2.remaining()]; + bb1.mark(); + bb2.mark(); + bb1.get(array1); + bb2.get(array2); + bb1.reset(); + bb2.reset(); + return guava.compare(array1, array2); + }; + final Dbi guavaDbi = env.openDbi(DB_1, comparator, MDB_CREATE); + populateDatabase(guavaDbi); + verify(openClosedBackward(bbNative(7), bbNative(2)), guavaDbi, 6, 4, 2); + verify(openClosedBackward(bbNative(8), bbNative(4)), guavaDbi, 6, 4); + } + + @Test + public void openClosedTest() { + verify(openClosed(bbNative(3), bbNative(8)), 4, 6, 8); + verify(openClosed(bbNative(2), bbNative(6)), 4, 6); + } + + @Test + public void openTest() { + verify(open(bbNative(3), bbNative(7)), 4, 6); + verify(open(bbNative(2), bbNative(8)), 4, 6); + } + + @Test + public void removeOddElements() { + final Dbi db = getDb(); + verify(db, all(), 2, 4, 6, 8); + int idx = -1; + try (Txn txn = env.txnWrite()) { + try (CursorIterable ci = db.iterate(txn)) { + final Iterator> c = ci.iterator(); + while (c.hasNext()) { + c.next(); + idx++; + if (idx % 2 == 0) { + c.remove(); + } + } + } + txn.commit(); + } + verify(db, all(), 4, 8); + } + + public void nextWithClosedEnvTest() { + Assertions.assertThatThrownBy( + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + + env.close(); + c.next(); + } + } + }) + .isInstanceOf(Env.AlreadyClosedException.class); + } + + public void removeWithClosedEnvTest() { + Assertions.assertThatThrownBy( + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnWrite()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + + final KeyVal keyVal = c.next(); + assertThat(keyVal).isNotNull(); + + env.close(); + c.remove(); + } + } + }) + .isInstanceOf(Env.AlreadyClosedException.class); + } + + public void hasNextWithClosedEnvTest() { + Assertions.assertThatThrownBy( + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + + env.close(); + c.hasNext(); + } + } + }) + .isInstanceOf(Env.AlreadyClosedException.class); + } + + public void forEachRemainingWithClosedEnvTest() { + Assertions.assertThatThrownBy( + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + + env.close(); + c.forEachRemaining(keyVal -> {}); + } + } + }) + .isInstanceOf(Env.AlreadyClosedException.class); + } + + private void verify(final KeyRange range, final int... expected) { + // Verify using all comparator types + final Dbi db = getDb(); + verify(range, db, expected); + } + + private void verify( + final Dbi dbi, final KeyRange range, final int... expected) { + verify(range, dbi, expected); + } + + private void verify( + final KeyRange range, final Dbi dbi, final int... expected) { + + final List results = new ArrayList<>(); + + try (Txn txn = env.txnRead(); + CursorIterable c = dbi.iterate(txn, range)) { + for (final KeyVal kv : c) { + final int key = kv.key().order(ByteOrder.nativeOrder()).getInt(); + final int val = kv.val().getInt(); + results.add(key); + assertThat(val).isEqualTo(key + 1); + } + } + + assertThat(results).hasSize(expected.length); + for (int idx = 0; idx < results.size(); idx++) { + assertThat(results.get(idx)).isEqualTo(expected[idx]); + } + } + + private Dbi getDb() { + final Dbi dbi = dbiFactory.factory.apply(env); + populateDatabase(dbi); + return dbi; + } + + // -------------------------------------------------------------------------------- + + private static class DbiFactory { + private final String name; + private final Function, Dbi> factory; + + private DbiFactory(String name, Function, Dbi> factory) { + this.name = name; + this.factory = factory; + } + + @Override + public String toString() { + return name; + } + } + + // -------------------------------------------------------------------------------- + + static class MyArgumentProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments( + ParameterDeclarations parameters, ExtensionContext context) throws Exception { + final DbiFactory defaultComparatorDb = + new DbiFactory( + "defaultComparator", + env -> + env.buildDbi() + .setDbName(DB_1) + .withDefaultComparator() + .setDbiFlags(DBI_FLAGS) + .open()); + final DbiFactory nativeComparatorDb = + new DbiFactory( + "nativeComparator", + env -> + env.buildDbi() + .setDbName(DB_2) + .withNativeComparator() + .setDbiFlags(DBI_FLAGS) + .open()); + final DbiFactory callbackComparatorDb = + new DbiFactory( + "callbackComparator", + env -> + env.buildDbi() + .setDbName(DB_3) + .withCallbackComparator(MyArgumentProvider::buildComparator) + .setDbiFlags(DBI_FLAGS) + .open()); + final DbiFactory iteratorComparatorDb = + new DbiFactory( + "iteratorComparator", + env -> + env.buildDbi() + .setDbName(DB_4) + .withIteratorComparator(MyArgumentProvider::buildComparator) + .setDbiFlags(DBI_FLAGS) + .open()); + return Stream.of( + defaultComparatorDb, nativeComparatorDb, callbackComparatorDb, iteratorComparatorDb) + .map(Arguments::of); + } + + private static Comparator buildComparator(final DbiFlagSet dbiFlagSet) { + final Comparator baseComparator = BUFFER_PROXY.getComparator(DBI_FLAGS); + return (o1, o2) -> { + if (o1.remaining() != o2.remaining()) { + // Make sure LMDB is always giving us consistent key lengths. + Assertions.fail( + "o1: " + + o1 + + " " + + getNativeIntOrLong(o1) + + ", o2: " + + o2 + + " " + + getNativeIntOrLong(o2)); + } + return baseComparator.compare(o1, o2); + }; + } + } +} diff --git a/src/test/java/org/lmdbjava/CursorIterablePerfTest.java b/src/test/java/org/lmdbjava/CursorIterablePerfTest.java new file mode 100644 index 00000000..f5210f3c --- /dev/null +++ b/src/test/java/org/lmdbjava/CursorIterablePerfTest.java @@ -0,0 +1,201 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +import static com.jakewharton.byteunits.BinaryByteUnit.GIBIBYTES; +import static org.lmdbjava.DbiFlags.MDB_CREATE; +import static org.lmdbjava.Env.create; +import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; +import static org.lmdbjava.PutFlags.MDB_APPEND; +import static org.lmdbjava.PutFlags.MDB_NOOVERWRITE; +import static org.lmdbjava.TestUtils.POSIX_MODE; +import static org.lmdbjava.TestUtils.bb; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class CursorIterablePerfTest { + + // private static final int ITERATIONS = 5_000_000; + private static final int ITERATIONS = 100_000; + // private static final int ITERATIONS = 10; + + private TempDir tempDir; + private Dbi dbJavaComparator; + private Dbi dbLmdbComparator; + private Dbi dbCallbackComparator; + private List> dbs = new ArrayList<>(); + private Env env; + private List data = new ArrayList<>(ITERATIONS); + + @BeforeEach + public void before() { + tempDir = new TempDir(); + final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; + env = + create(bufferProxy) + .setMapSize(GIBIBYTES.toBytes(1)) + .setMaxReaders(1) + .setMaxDbs(3) + .setFilePermissions(POSIX_MODE) + .setEnvFlags(MDB_NOSUBDIR) + .open(tempDir.createTempFile()); + + final DbiFlagSet dbiFlagSet = MDB_CREATE; + // Use a java comparator for start/stop keys only + dbJavaComparator = + env.buildDbi() + .setDbName("JavaComparator") + .withDefaultComparator() + .setDbiFlags(dbiFlagSet) + .open(); + // Use LMDB comparator for start/stop keys + dbLmdbComparator = + env.buildDbi() + .setDbName("LmdbComparator") + .withNativeComparator() + .setDbiFlags(dbiFlagSet) + .open(); + + // Use a java comparator for start/stop keys and as a callback comparator + dbCallbackComparator = + env.buildDbi() + .setDbName("CallBackComparator") + .withCallbackComparator(bufferProxy::getComparator) + .setDbiFlags(dbiFlagSet) + .open(); + + dbs.add(dbJavaComparator); + dbs.add(dbLmdbComparator); + dbs.add(dbCallbackComparator); + + populateList(); + } + + @AfterEach + public void after() { + env.close(); + tempDir.cleanup(); + } + + private void populateList() { + for (int i = 0; i < ITERATIONS * 2; i += 2) { + data.add(i); + } + } + + private void populateDatabases(final boolean randomOrder) { + System.out.println("Clear then populate databases (randomOrder=" + randomOrder + ")"); + + final List data; + if (randomOrder) { + data = new ArrayList<>(this.data); + Collections.shuffle(data); + } else { + data = this.data; + } + + final PutFlagSet noOverwriteAndAppendFlagSet = PutFlagSet.of(MDB_NOOVERWRITE, MDB_APPEND); + + for (int round = 0; round < 3; round++) { + System.out.println("round: " + round + " -----------------------------------------"); + + for (final Dbi db : dbs) { + // Clean out the db first + try (Txn txn = env.txnWrite(); + final Cursor cursor = db.openCursor(txn)) { + while (cursor.next()) { + cursor.delete(); + } + } + + final String dbName = db.getNameAsString(StandardCharsets.UTF_8); + final Instant start = Instant.now(); + try (Txn txn = env.txnWrite()) { + for (final Integer i : data) { + if (randomOrder) { + db.put(txn, bb(i), bb(i + 1), MDB_NOOVERWRITE); + } else { + db.put(txn, bb(i), bb(i + 1), noOverwriteAndAppendFlagSet); + } + } + txn.commit(); + } + final Duration duration = Duration.between(start, Instant.now()); + System.out.println( + "DB: " + + dbName + + " - Loaded in duration: " + + duration + + ", millis: " + + duration.toMillis()); + } + } + } + + @Test + public void comparePerf_sequential() { + comparePerf(false); + } + + @Test + public void comparePerf_random() { + comparePerf(true); + } + + public void comparePerf(final boolean randomOrder) { + populateDatabases(randomOrder); + final ByteBuffer startKeyBuf = bb(data.get(0)); + final ByteBuffer stopKeyBuf = bb(data.get(data.size() - 1)); + final KeyRange keyRange = KeyRange.closed(startKeyBuf, stopKeyBuf); + + System.out.println("\nIterating over all entries"); + for (int round = 0; round < 3; round++) { + System.out.println("round: " + round + " -----------------------------------------"); + for (final Dbi db : dbs) { + final String dbName = new String(db.getName(), StandardCharsets.UTF_8); + + final Instant start = Instant.now(); + int cnt = 0; + // Exercise the stop key comparator on every entry + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn, keyRange)) { + for (final CursorIterable.KeyVal kv : c) { + cnt++; + } + } + final Duration duration = Duration.between(start, Instant.now()); + System.out.println( + "DB: " + + dbName + + " - Iterated in duration: " + + duration + + ", millis: " + + duration.toMillis() + + ", cnt: " + + cnt); + } + } + } +} diff --git a/src/test/java/org/lmdbjava/CursorIterableRangeTest.java b/src/test/java/org/lmdbjava/CursorIterableRangeTest.java index f0471bab..4fa036f5 100644 --- a/src/test/java/org/lmdbjava/CursorIterableRangeTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableRangeTest.java @@ -69,7 +69,7 @@ void testSignedComparator( void testUnsignedComparator( final String keyType, final String startKey, final String stopKey, final String expectedKV) { testCSV( - AbstractByteBufferProxy::compareBuff, + AbstractByteBufferProxy::compareLexicographically, false, createBasicDBPopulator(), EnumSet.of(MDB_CREATE), @@ -99,7 +99,7 @@ void testSignedComparatorDupsort( void testUnsignedComparatorDupsort( final String keyType, final String startKey, final String stopKey, final String expectedKV) { testCSV( - AbstractByteBufferProxy::compareBuff, + AbstractByteBufferProxy::compareLexicographically, false, createMultiDBPopulator(2), EnumSet.of(MDB_CREATE, MDB_DUPSORT), @@ -114,7 +114,7 @@ void testUnsignedComparatorDupsort( void testIntegerKey( final String keyType, final String startKey, final String stopKey, final String expectedKV) { testCSV( - AbstractByteBufferProxy::compareBuff, + AbstractByteBufferProxy::compareLexicographically, false, createIntegerDBPopulator(), EnumSet.of(MDB_CREATE, MDB_INTEGERKEY), @@ -131,7 +131,7 @@ void testIntegerKey( void testLongKey( final String keyType, final String startKey, final String stopKey, final String expectedKV) { testCSV( - AbstractByteBufferProxy::compareBuff, + AbstractByteBufferProxy::compareLexicographically, false, createLongDBPopulator(), EnumSet.of(MDB_CREATE, MDB_INTEGERKEY), diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index 5e515fee..4a8214e4 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -43,7 +43,9 @@ import static org.lmdbjava.KeyRange.openClosedBackward; import static org.lmdbjava.PutFlags.MDB_NOOVERWRITE; import static org.lmdbjava.TestUtils.DB_1; -import static org.lmdbjava.TestUtils.POSIX_MODE; +import static org.lmdbjava.TestUtils.DB_2; +import static org.lmdbjava.TestUtils.DB_3; +import static org.lmdbjava.TestUtils.DB_4; import static org.lmdbjava.TestUtils.bb; import com.google.common.primitives.UnsignedBytes; @@ -55,35 +57,65 @@ import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; +import java.util.function.Function; +import java.util.stream.Stream; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.params.Parameter; +import org.junit.jupiter.params.ParameterizedClass; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.ArgumentsProvider; +import org.junit.jupiter.params.provider.ArgumentsSource; +import org.junit.jupiter.params.support.ParameterDeclarations; import org.lmdbjava.CursorIterable.KeyVal; /** Test {@link CursorIterable}. */ +@ParameterizedClass(name = "{index}: dbi: {0}") +@ArgumentsSource(CursorIterableTest.MyArgumentProvider.class) public final class CursorIterableTest { + private static final DbiFlagSet DBI_FLAGS = MDB_CREATE; + private static final BufferProxy BUFFER_PROXY = ByteBufferProxy.PROXY_OPTIMAL; + private TempDir tempDir; - private Dbi db; private Env env; private Deque list; + // /** + // * Injected by {@link #data()} with appropriate runner. + // */ + // @SuppressWarnings("ClassEscapesDefinedScope") + @Parameter public DbiFactory dbiFactory; + @BeforeEach void beforeEach() { tempDir = new TempDir(); + final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; env = - create() + create(bufferProxy) .setMapSize(KIBIBYTES.toBytes(256)) .setMaxReaders(1) - .setMaxDbs(1) - .open(tempDir.createTempFile().toFile(), POSIX_MODE, MDB_NOSUBDIR); - db = env.openDbi(DB_1, MDB_CREATE); - populateDatabase(db); + .setMaxDbs(3) + .setEnvFlags(MDB_NOSUBDIR) + .open(tempDir.createTempFile()); + + populateTestDataList(); } - private void populateDatabase(final Dbi dbi) { + @AfterEach + void afterEach() { + env.close(); + tempDir.cleanup(); + } + + private void populateTestDataList() { list = new LinkedList<>(); list.addAll(asList(2, 3, 4, 5, 6, 7, 8, 9)); + } + + private void populateDatabase(final Dbi dbi) { try (Txn txn = env.txnWrite()) { final Cursor c = dbi.openCursor(txn); c.put(bb(2), bb(3), MDB_NOOVERWRITE); @@ -94,12 +126,6 @@ private void populateDatabase(final Dbi dbi) { } } - @AfterEach - void afterEach() { - env.close(); - tempDir.cleanup(); - } - @Test void allBackwardTest() { verify(allBackward(), 8, 6, 4, 2); @@ -179,6 +205,7 @@ void greaterThanTest() { void iterableOnlyReturnedOnce() { assertThatThrownBy( () -> { + final Dbi db = getDb(); try (Txn txn = env.txnRead(); CursorIterable c = db.iterate(txn)) { c.iterator(); // ok @@ -190,8 +217,10 @@ void iterableOnlyReturnedOnce() { @Test void iterate() { + final Dbi db = getDb(); try (Txn txn = env.txnRead(); CursorIterable c = db.iterate(txn)) { + for (final KeyVal kv : c) { assertThat(kv.key().getInt()).isEqualTo(list.pollFirst()); assertThat(kv.val().getInt()).isEqualTo(list.pollFirst()); @@ -203,6 +232,7 @@ void iterate() { void iteratorOnlyReturnedOnce() { assertThatThrownBy( () -> { + final Dbi db = getDb(); try (Txn txn = env.txnRead(); CursorIterable c = db.iterate(txn)) { c.iterator(); // ok @@ -228,6 +258,8 @@ void lessThanTest() { void nextThrowsNoSuchElementExceptionIfNoMoreElements() { assertThatThrownBy( () -> { + final Dbi db = getDb(); + populateTestDataList(); try (Txn txn = env.txnRead(); CursorIterable c = db.iterate(txn)) { final Iterator> i = c.iterator(); @@ -272,7 +304,8 @@ void openClosedBackwardTestWithGuava() { bb2.reset(); return guava.compare(array1, array2); }; - final Dbi guavaDbi = env.openDbi(DB_1, comparator, MDB_CREATE); + final Dbi guavaDbi = + env.buildDbi().setDbName(DB_1).withDefaultComparator().setDbiFlags(MDB_CREATE).open(); populateDatabase(guavaDbi); verify(openClosedBackward(bb(7), bb(2)), guavaDbi, 6, 4, 2); verify(openClosedBackward(bb(8), bb(4)), guavaDbi, 6, 4); @@ -292,6 +325,7 @@ void openTest() { @Test void removeOddElements() { + final Dbi db = getDb(); verify(all(), 2, 4, 6, 8); int idx = -1; try (Txn txn = env.txnWrite()) { @@ -307,13 +341,14 @@ void removeOddElements() { } txn.commit(); } - verify(all(), 4, 8); + verify(db, all(), 4, 8); } @Test void nextWithClosedEnvTest() { assertThatThrownBy( () -> { + final Dbi db = getDb(); try (Txn txn = env.txnRead()) { try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { final Iterator> c = ci.iterator(); @@ -330,6 +365,7 @@ void nextWithClosedEnvTest() { void removeWithClosedEnvTest() { assertThatThrownBy( () -> { + final Dbi db = getDb(); try (Txn txn = env.txnWrite()) { try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { final Iterator> c = ci.iterator(); @@ -349,6 +385,7 @@ void removeWithClosedEnvTest() { void hasNextWithClosedEnvTest() { assertThatThrownBy( () -> { + final Dbi db = getDb(); try (Txn txn = env.txnRead()) { try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { final Iterator> c = ci.iterator(); @@ -365,6 +402,7 @@ void hasNextWithClosedEnvTest() { void forEachRemainingWithClosedEnvTest() { assertThatThrownBy( () -> { + final Dbi db = getDb(); try (Txn txn = env.txnRead()) { try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { final Iterator> c = ci.iterator(); @@ -377,12 +415,67 @@ void forEachRemainingWithClosedEnvTest() { .isInstanceOf(Env.AlreadyClosedException.class); } + // @Test + // public void testSignedVsUnsigned() { + // final ByteBuffer val1 = bb(1); + // final ByteBuffer val2 = bb(2); + // final ByteBuffer val110 = bb(110); + // final ByteBuffer val111 = bb(111); + // final ByteBuffer val150 = bb(150); + // + // final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; + // final Comparator unsignedComparator = bufferProxy.getUnsignedComparator(); + // final Comparator signedComparator = bufferProxy.getSignedComparator(); + // + // // Compare the same + // assertThat( + // unsignedComparator.compare(val1, val2), Matchers.is(signedComparator.compare(val1, + // val2))); + // + // // Compare differently + // assertThat( + // unsignedComparator.compare(val110, val150), + // Matchers.not(signedComparator.compare(val110, val150))); + // + // // Compare differently + // assertThat( + // unsignedComparator.compare(val111, val150), + // Matchers.not(signedComparator.compare(val111, val150))); + // + // // This will fail if the db is using a signed comparator for the start/stop keys + // for (final Dbi db : dbs) { + // db.put(val110, val110); + // db.put(val150, val150); + // + // final ByteBuffer startKeyBuf = val111; + // KeyRange keyRange = KeyRange.atLeastBackward(startKeyBuf); + // + // try (Txn txn = env.txnRead(); + // CursorIterable c = db.iterate(txn, keyRange)) { + // for (final CursorIterable.KeyVal kv : c) { + // final int key = kv.key().getInt(); + // final int val = kv.val().getInt(); + // // System.out.println("key: " + key + " val: " + val); + // assertThat(key, is(110)); + // break; + // } + // } + // } + // } + private void verify(final KeyRange range, final int... expected) { + final Dbi db = getDb(); verify(range, db, expected); } + private void verify( + final Dbi dbi, final KeyRange range, final int... expected) { + verify(range, dbi, expected); + } + private void verify( final KeyRange range, final Dbi dbi, final int... expected) { + final List results = new ArrayList<>(); try (Txn txn = env.txnRead(); @@ -400,4 +493,76 @@ private void verify( assertThat(results.get(idx)).isEqualTo(expected[idx]); } } + + private Dbi getDb() { + final Dbi dbi = dbiFactory.factory.apply(env); + populateDatabase(dbi); + return dbi; + } + + // -------------------------------------------------------------------------------- + + private static class DbiFactory { + private final String name; + private final Function, Dbi> factory; + + private DbiFactory(String name, Function, Dbi> factory) { + this.name = name; + this.factory = factory; + } + + @Override + public String toString() { + return name; + } + } + + // -------------------------------------------------------------------------------- + + static class MyArgumentProvider implements ArgumentsProvider { + + @Override + public Stream provideArguments( + ParameterDeclarations parameters, ExtensionContext context) throws Exception { + final DbiFactory defaultComparatorDb = + new DbiFactory( + "defaultComparator", + env -> + env.buildDbi() + .setDbName(DB_1) + .withDefaultComparator() + .setDbiFlags(DBI_FLAGS) + .open()); + final DbiFactory nativeComparatorDb = + new DbiFactory( + "nativeComparator", + env -> + env.buildDbi() + .setDbName(DB_2) + .withNativeComparator() + .setDbiFlags(DBI_FLAGS) + .open()); + final DbiFactory callbackComparatorDb = + new DbiFactory( + "callbackComparator", + env -> + env.buildDbi() + .setDbName(DB_3) + .withCallbackComparator(BUFFER_PROXY::getComparator) + .setDbiFlags(DBI_FLAGS) + .open()); + final DbiFactory iteratorComparatorDb = + new DbiFactory( + "iteratorComparator", + env -> + env.buildDbi() + .setDbName(DB_4) + .withIteratorComparator(BUFFER_PROXY::getComparator) + .setDbiFlags(DBI_FLAGS) + .open()); + return Stream.of( + defaultComparatorDb, nativeComparatorDb, callbackComparatorDb, iteratorComparatorDb) + .map(Arguments::of); + } + } } diff --git a/src/test/java/org/lmdbjava/DbiBuilderTest.java b/src/test/java/org/lmdbjava/DbiBuilderTest.java new file mode 100644 index 00000000..4ef11f2a --- /dev/null +++ b/src/test/java/org/lmdbjava/DbiBuilderTest.java @@ -0,0 +1,204 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +import static com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; +import static org.assertj.core.api.Assertions.assertThat; +import static org.lmdbjava.Env.create; +import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; +import static org.lmdbjava.TestUtils.bb; +import static org.lmdbjava.TestUtils.getString; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class DbiBuilderTest { + + private TempDir tempDir; + private Env env; + + @BeforeEach + public void before() { + tempDir = new TempDir(); + env = + create() + .setMapSize(MEBIBYTES.toBytes(64)) + .setMaxReaders(2) + .setMaxDbs(2) + .setEnvFlags(MDB_NOSUBDIR) + .open(tempDir.createTempFile()); + } + + @AfterEach + public void after() { + env.close(); + tempDir.cleanup(); + } + + @Test + public void unnamed() { + final Dbi dbi = + env.buildDbi() + .withoutDbName() + .withDefaultComparator() + .setDbiFlags(DbiFlags.MDB_CREATE) + .open(); + assertThat(dbi.getName()).isNull(); + assertThat(dbi.getNameAsString()).isEmpty(); + assertThat(env.getDbiNames()).isEmpty(); + assertPutAndGet(dbi); + } + + @Test + public void named() { + final Dbi dbi = + env.buildDbi() + .setDbName("foo") + .withDefaultComparator() + .setDbiFlags(DbiFlags.MDB_CREATE) + .open(); + + assertPutAndGet(dbi); + + assertThat(env.getDbiNames()).hasSize(1); + assertThat(new String(env.getDbiNames().get(0), StandardCharsets.UTF_8)).isEqualTo("foo"); + assertThat(dbi.getNameAsString()).isEqualTo("foo"); + assertThat(dbi.getNameAsString(StandardCharsets.UTF_8)).isEqualTo("foo"); + } + + @Test + public void named2() { + final Dbi dbi = + env.buildDbi() + .setDbName("foo".getBytes(StandardCharsets.US_ASCII)) + .withDefaultComparator() + .setDbiFlags(DbiFlags.MDB_CREATE) + .open(); + + assertPutAndGet(dbi); + + assertThat(env.getDbiNames()).hasSize(1); + assertThat(new String(env.getDbiNames().get(0), StandardCharsets.US_ASCII)).isEqualTo("foo"); + assertThat(dbi.getNameAsString()).isEqualTo("foo"); + assertThat(dbi.getNameAsString(StandardCharsets.US_ASCII)).isEqualTo("foo"); + } + + @Test + public void nativeComparator() { + final Dbi dbi = + env.buildDbi() + .setDbName("foo") + .withNativeComparator() + .addDbiFlags(DbiFlags.MDB_CREATE) + .open(); + + assertPutAndGet(dbi); + assertThat(env.getDbiNames()).hasSize(1); + } + + @Test + public void callback() { + final Comparator proxyOptimal = ByteBufferProxy.PROXY_OPTIMAL.getComparator(); + // Compare on key length, falling back to default + final Comparator comparator = + (o1, o2) -> { + final int res = Integer.compare(o1.remaining(), o2.remaining()); + if (res == 0) { + return proxyOptimal.compare(o1, o2); + } else { + return res; + } + }; + + final Dbi dbi = + env.buildDbi() + .setDbName("foo") + .withCallbackComparator(ignored -> comparator) + .addDbiFlags(DbiFlags.MDB_CREATE) + .open(); + + TestUtils.doWithWriteTxn( + env, + txn -> { + dbi.put(txn, bb("fox"), bb("val_1")); + dbi.put(txn, bb("rabbit"), bb("val_2")); + dbi.put(txn, bb("deer"), bb("val_3")); + dbi.put(txn, bb("badger"), bb("val_4")); + txn.commit(); + }); + + final List keys = new ArrayList<>(); + TestUtils.doWithReadTxn( + env, + txn -> { + try (CursorIterable cursorIterable = dbi.iterate(txn)) { + final Iterator> iterator = cursorIterable.iterator(); + iterator.forEachRemaining( + keyVal -> { + keys.add(getString(keyVal.key())); + }); + } + }); + assertThat(keys).containsExactly("fox", "deer", "badger", "rabbit"); + } + + @Test + public void flags() { + final Dbi dbi = + env.buildDbi() + .setDbName("foo") + .withDefaultComparator() + .setDbiFlags(DbiFlags.MDB_DUPSORT, DbiFlags.MDB_DUPFIXED) // Will get overwritten + .setDbiFlags() // clear them + .addDbiFlags(DbiFlags.MDB_CREATE) // Not a dbi flag as far as lmdb is concerned. + .addDbiFlags(DbiFlags.MDB_INTEGERKEY) + .addDbiFlags(DbiFlags.MDB_REVERSEKEY) + .open(); + + assertPutAndGet(dbi); + + assertThat(env.getDbiNames()).hasSize(1); + assertThat(new String(env.getDbiNames().get(0), StandardCharsets.UTF_8)).isEqualTo("foo"); + + TestUtils.doWithReadTxn( + env, + readTxn -> { + assertThat(dbi.listFlags(readTxn)) + .containsExactlyInAnyOrder(DbiFlags.MDB_INTEGERKEY, DbiFlags.MDB_REVERSEKEY); + }); + } + + private void assertPutAndGet(Dbi dbi) { + try (Txn writeTxn = env.txnWrite()) { + dbi.put(writeTxn, bb(123), bb(123_000)); + writeTxn.commit(); + } + + try (Txn readTxn = env.txnRead()) { + final ByteBuffer byteBuffer = dbi.get(readTxn, bb(123)); + assertThat(byteBuffer).isNotNull(); + final int val = byteBuffer.getInt(); + assertThat(val).isEqualTo(123_000); + } + } +} diff --git a/src/test/java/org/lmdbjava/DbiFlagSetTest.java b/src/test/java/org/lmdbjava/DbiFlagSetTest.java new file mode 100644 index 00000000..12536957 --- /dev/null +++ b/src/test/java/org/lmdbjava/DbiFlagSetTest.java @@ -0,0 +1,90 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.HashSet; +import org.junit.jupiter.api.Test; + +public class DbiFlagSetTest { + + @Test + public void testEmpty() { + final DbiFlagSet dbiFlagSet = DbiFlagSet.empty(); + assertThat(dbiFlagSet.getMask()).isEqualTo(0); + assertThat(dbiFlagSet.size()).isEqualTo(0); + assertThat(dbiFlagSet.isEmpty()).isEqualTo(true); + assertThat(dbiFlagSet.isSet(DbiFlags.MDB_REVERSEDUP)).isEqualTo(false); + final DbiFlagSet dbiFlagSet2 = DbiFlagSet.builder().build(); + assertThat(dbiFlagSet).isEqualTo(dbiFlagSet2); + assertThat(dbiFlagSet).isNotEqualTo(DbiFlagSet.of(DbiFlags.MDB_CREATE)); + assertThat(dbiFlagSet).isNotEqualTo(DbiFlagSet.of(DbiFlags.MDB_CREATE, DbiFlags.MDB_DUPSORT)); + assertThat(dbiFlagSet) + .isNotEqualTo( + DbiFlagSet.builder() + .setFlag(DbiFlags.MDB_CREATE) + .setFlag(DbiFlags.MDB_DUPFIXED) + .build()); + } + + @Test + public void testOf() { + final DbiFlags dbiFlag = DbiFlags.MDB_CREATE; + final DbiFlagSet dbiFlagSet = DbiFlagSet.of(dbiFlag); + assertThat(dbiFlagSet.getMask()).isEqualTo(MaskedFlag.mask(dbiFlag)); + assertThat(dbiFlagSet.size()).isEqualTo(1); + assertThat(dbiFlagSet.isSet(DbiFlags.MDB_REVERSEDUP)).isEqualTo(false); + for (DbiFlags flag : dbiFlagSet) { + assertThat(dbiFlagSet.isSet(flag)).isEqualTo(true); + } + + final DbiFlagSet dbiFlagSet2 = DbiFlagSet.builder().setFlag(dbiFlag).build(); + assertThat(dbiFlagSet).isEqualTo(dbiFlagSet2); + } + + @Test + public void testOf2() { + final DbiFlags dbiFlag1 = DbiFlags.MDB_CREATE; + final DbiFlags dbiFlag2 = DbiFlags.MDB_INTEGERKEY; + final DbiFlagSet dbiFlagSet = DbiFlagSet.of(dbiFlag1, dbiFlag2); + assertThat(dbiFlagSet.getMask()).isEqualTo(MaskedFlag.mask(dbiFlag1, dbiFlag2)); + assertThat(dbiFlagSet.size()).isEqualTo(2); + assertThat(dbiFlagSet.isSet(DbiFlags.MDB_REVERSEDUP)).isEqualTo(false); + for (DbiFlags flag : dbiFlagSet) { + assertThat(dbiFlagSet.isSet(flag)).isEqualTo(true); + } + } + + @Test + public void testBuilder() { + final DbiFlags dbiFlag1 = DbiFlags.MDB_CREATE; + final DbiFlags dbiFlag2 = DbiFlags.MDB_INTEGERKEY; + final DbiFlagSet dbiFlagSet = DbiFlagSet.builder().setFlag(dbiFlag1).setFlag(dbiFlag2).build(); + assertThat(dbiFlagSet.getMask()).isEqualTo(MaskedFlag.mask(dbiFlag1, dbiFlag2)); + assertThat(dbiFlagSet.size()).isEqualTo(2); + assertThat(dbiFlagSet.isSet(DbiFlags.MDB_REVERSEDUP)).isEqualTo(false); + for (DbiFlags flag : dbiFlagSet) { + assertThat(dbiFlagSet.isSet(flag)).isEqualTo(true); + } + final DbiFlagSet dbiFlagSet2 = DbiFlagSet.builder().withFlags(dbiFlag1, dbiFlag2).build(); + final DbiFlagSet dbiFlagSet3 = + DbiFlagSet.builder().withFlags(new HashSet<>(Arrays.asList(dbiFlag1, dbiFlag2))).build(); + assertThat(dbiFlagSet).isEqualTo(dbiFlagSet2); + assertThat(dbiFlagSet).isEqualTo(dbiFlagSet3); + } +} diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 563215c4..632d51b5 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -57,8 +57,8 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiConsumer; -import java.util.function.Function; import java.util.function.IntFunction; +import java.util.function.Supplier; import java.util.function.ToIntFunction; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -106,7 +106,12 @@ void afterEach() { void close() { assertThatThrownBy( () -> { - final Dbi db = env.openDbi(DB_1, MDB_CREATE); + final Dbi db = + env.buildDbi() + .setDbName(DB_1) + .withDefaultComparator() + .addDbiFlag(MDB_CREATE) + .open(); db.put(bb(1), bb(42)); db.close(); db.put(bb(2), bb(42)); // error @@ -187,11 +192,11 @@ void dbiWithComparatorThreadSafetyByteArray() { private void doDbiWithComparatorThreadSafety( Env env, - Function> comparator, + Supplier> comparatorSupplier, IntFunction serializer, ToIntFunction deserializer) { final DbiFlags[] flags = new DbiFlags[] {MDB_CREATE, MDB_INTEGERKEY}; - final Comparator c = comparator.apply(flags); + final Comparator c = comparatorSupplier.get(); final Dbi db = env.openDbi(DB_1, c, true, flags); final List keys = range(0, 1_000).boxed().collect(toList()); diff --git a/src/test/java/org/lmdbjava/EnvFlagSetTest.java b/src/test/java/org/lmdbjava/EnvFlagSetTest.java new file mode 100644 index 00000000..f270507b --- /dev/null +++ b/src/test/java/org/lmdbjava/EnvFlagSetTest.java @@ -0,0 +1,91 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.HashSet; +import org.junit.jupiter.api.Test; + +public class EnvFlagSetTest { + + @Test + public void testEmpty() { + final EnvFlagSet envFlagSet = EnvFlagSet.empty(); + assertThat(envFlagSet.getMask()).isEqualTo(0); + assertThat(envFlagSet.size()).isEqualTo(0); + assertThat(envFlagSet.isEmpty()).isEqualTo(true); + assertThat(envFlagSet.isSet(EnvFlags.MDB_NOSUBDIR)).isEqualTo(false); + final EnvFlagSet envFlagSet2 = EnvFlagSet.builder().build(); + assertThat(envFlagSet).isEqualTo(envFlagSet2); + assertThat(envFlagSet).isNotEqualTo(EnvFlagSet.of(EnvFlags.MDB_FIXEDMAP)); + assertThat(envFlagSet) + .isNotEqualTo(EnvFlagSet.of(EnvFlags.MDB_FIXEDMAP, EnvFlags.MDB_NORDAHEAD)); + assertThat(envFlagSet) + .isNotEqualTo( + EnvFlagSet.builder() + .setFlag(EnvFlags.MDB_FIXEDMAP) + .setFlag(EnvFlags.MDB_NORDAHEAD) + .build()); + } + + @Test + public void testOf() { + final EnvFlags envFlag = EnvFlags.MDB_FIXEDMAP; + final EnvFlagSet envFlagSet = EnvFlagSet.of(envFlag); + assertThat(envFlagSet.getMask()).isEqualTo(MaskedFlag.mask(envFlag)); + assertThat(envFlagSet.size()).isEqualTo(1); + assertThat(envFlagSet.isSet(EnvFlags.MDB_NOSUBDIR)).isEqualTo(false); + for (EnvFlags flag : envFlagSet) { + assertThat(envFlagSet.isSet(flag)).isEqualTo(true); + } + + final EnvFlagSet envFlagSet2 = EnvFlagSet.builder().setFlag(envFlag).build(); + assertThat(envFlagSet).isEqualTo(envFlagSet2); + } + + @Test + public void testOf2() { + final EnvFlags envFlag1 = EnvFlags.MDB_FIXEDMAP; + final EnvFlags envFlag2 = EnvFlags.MDB_NORDAHEAD; + final EnvFlagSet envFlagSet = EnvFlagSet.of(envFlag1, envFlag2); + assertThat(envFlagSet.getMask()).isEqualTo(MaskedFlag.mask(envFlag1, envFlag2)); + assertThat(envFlagSet.size()).isEqualTo(2); + assertThat(envFlagSet.isSet(EnvFlags.MDB_WRITEMAP)).isEqualTo(false); + for (EnvFlags flag : envFlagSet) { + assertThat(envFlagSet.isSet(flag)).isEqualTo(true); + } + } + + @Test + public void testBuilder() { + final EnvFlags envFlag1 = EnvFlags.MDB_FIXEDMAP; + final EnvFlags envFlag2 = EnvFlags.MDB_NORDAHEAD; + final EnvFlagSet envFlagSet = EnvFlagSet.builder().setFlag(envFlag1).setFlag(envFlag2).build(); + assertThat(envFlagSet.getMask()).isEqualTo(MaskedFlag.mask(envFlag1, envFlag2)); + assertThat(envFlagSet.size()).isEqualTo(2); + assertThat(envFlagSet.isSet(EnvFlags.MDB_NOTLS)).isEqualTo(false); + for (EnvFlags flag : envFlagSet) { + assertThat(envFlagSet.isSet(flag)).isEqualTo(true); + } + final EnvFlagSet envFlagSet2 = EnvFlagSet.builder().withFlags(envFlag1, envFlag2).build(); + final EnvFlagSet envFlagSet3 = + EnvFlagSet.builder().withFlags(new HashSet<>(Arrays.asList(envFlag1, envFlag2))).build(); + assertThat(envFlagSet).isEqualTo(envFlagSet2); + assertThat(envFlagSet).isEqualTo(envFlagSet3); + } +} diff --git a/src/test/java/org/lmdbjava/KeyRangeTest.java b/src/test/java/org/lmdbjava/KeyRangeTest.java index 0f77b92f..2e8854b9 100644 --- a/src/test/java/org/lmdbjava/KeyRangeTest.java +++ b/src/test/java/org/lmdbjava/KeyRangeTest.java @@ -194,7 +194,10 @@ private void verify(final KeyRange range, final int... expected) { IteratorOp op; do { - op = range.getType().iteratorOp(range.getStart(), range.getStop(), buff, Integer::compare); + final Integer finalBuff = buff; + final RangeComparator rangeComparator = + new CursorIterable.JavaRangeComparator<>(range, Integer::compareTo, () -> finalBuff); + op = range.getType().iteratorOp(buff, rangeComparator); switch (op) { case CALL_NEXT_OP: buff = cursor.apply(range.getType().nextOp(), range.getStart()); diff --git a/src/test/java/org/lmdbjava/PutFlagSetTest.java b/src/test/java/org/lmdbjava/PutFlagSetTest.java new file mode 100644 index 00000000..c7634769 --- /dev/null +++ b/src/test/java/org/lmdbjava/PutFlagSetTest.java @@ -0,0 +1,131 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Duration; +import java.time.Instant; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import org.junit.jupiter.api.Test; + +public class PutFlagSetTest { + + @Test + public void testEmpty() { + final PutFlagSet putFlagSet = PutFlagSet.empty(); + assertThat(putFlagSet.getMask()).isEqualTo(0); + assertThat(putFlagSet.size()).isEqualTo(0); + assertThat(putFlagSet.isEmpty()).isEqualTo(true); + assertThat(putFlagSet.isSet(PutFlags.MDB_MULTIPLE)).isEqualTo(false); + final PutFlagSet putFlagSet2 = PutFlagSet.builder().build(); + assertThat(putFlagSet).isEqualTo(putFlagSet2); + assertThat(putFlagSet).isNotEqualTo(PutFlagSet.of(PutFlags.MDB_APPEND)); + assertThat(putFlagSet).isNotEqualTo(PutFlagSet.of(PutFlags.MDB_APPEND, PutFlags.MDB_RESERVE)); + assertThat(putFlagSet) + .isNotEqualTo( + PutFlagSet.builder() + .setFlag(PutFlags.MDB_CURRENT) + .setFlag(PutFlags.MDB_MULTIPLE) + .build()); + } + + @Test + public void testOf() { + final PutFlags putFlag = PutFlags.MDB_APPEND; + final PutFlagSet putFlagSet = PutFlagSet.of(putFlag); + assertThat(putFlagSet.getMask()).isEqualTo(MaskedFlag.mask(putFlag)); + assertThat(putFlagSet.size()).isEqualTo(1); + assertThat(putFlagSet.isSet(PutFlags.MDB_MULTIPLE)).isEqualTo(false); + for (PutFlags flag : putFlagSet) { + assertThat(putFlagSet.isSet(flag)).isEqualTo(true); + } + + final PutFlagSet putFlagSet2 = PutFlagSet.builder().setFlag(putFlag).build(); + assertThat(putFlagSet).isEqualTo(putFlagSet2); + } + + @Test + public void testOf2() { + final PutFlags putFlag1 = PutFlags.MDB_APPEND; + final PutFlags putFlag2 = PutFlags.MDB_NOOVERWRITE; + final PutFlagSet putFlagSet = PutFlagSet.of(putFlag1, putFlag2); + assertThat(putFlagSet.getMask()).isEqualTo(MaskedFlag.mask(putFlag1, putFlag2)); + assertThat(putFlagSet.size()).isEqualTo(2); + assertThat(putFlagSet.isSet(PutFlags.MDB_MULTIPLE)).isEqualTo(false); + for (PutFlags flag : putFlagSet) { + assertThat(putFlagSet.isSet(flag)).isEqualTo(true); + } + } + + @Test + public void testBuilder() { + final PutFlags putFlag1 = PutFlags.MDB_APPEND; + final PutFlags putFlag2 = PutFlags.MDB_NOOVERWRITE; + final PutFlagSet putFlagSet = PutFlagSet.builder().setFlag(putFlag1).setFlag(putFlag2).build(); + assertThat(putFlagSet.getMask()).isEqualTo(MaskedFlag.mask(putFlag1, putFlag2)); + assertThat(putFlagSet.size()).isEqualTo(2); + assertThat(putFlagSet.isSet(PutFlags.MDB_MULTIPLE)).isEqualTo(false); + for (PutFlags flag : putFlagSet) { + assertThat(putFlagSet.isSet(flag)).isEqualTo(true); + } + final PutFlagSet putFlagSet2 = PutFlagSet.builder().withFlags(putFlag1, putFlag2).build(); + final PutFlagSet putFlagSet3 = + PutFlagSet.builder().withFlags(new HashSet<>(Arrays.asList(putFlag1, putFlag2))).build(); + assertThat(putFlagSet).isEqualTo(putFlagSet2); + assertThat(putFlagSet).isEqualTo(putFlagSet3); + } + + @Test + public void testAddFlagVsCheckPresence() { + + final int cnt = 10_000_000; + final int[] arr = new int[cnt]; + final List flagSets = + IntStream.range(0, cnt) + .boxed() + .map( + i -> + PutFlagSet.of( + PutFlags.MDB_APPEND, PutFlags.MDB_NOOVERWRITE, PutFlags.MDB_RESERVE)) + .collect(Collectors.toList()); + + Instant time; + for (int i = 0; i < 5; i++) { + time = Instant.now(); + for (int j = 0; j < flagSets.size(); j++) { + PutFlagSet flagSet = flagSets.get(j); + if (!flagSet.isSet(PutFlags.MDB_RESERVE)) { + throw new RuntimeException("Not set"); + } + arr[j] = flagSet.getMask(); + } + System.out.println("Check: " + Duration.between(time, Instant.now())); + + time = Instant.now(); + for (int j = 0; j < flagSets.size(); j++) { + PutFlagSet flagSet = flagSets.get(j); + final int mask = flagSet.getMaskWith(PutFlags.MDB_RESERVE); + arr[j] = mask; + } + System.out.println("Append:" + Duration.between(time, Instant.now())); + } + } +} diff --git a/src/test/java/org/lmdbjava/TestUtils.java b/src/test/java/org/lmdbjava/TestUtils.java index 23606862..abaf63bb 100644 --- a/src/test/java/org/lmdbjava/TestUtils.java +++ b/src/test/java/org/lmdbjava/TestUtils.java @@ -22,6 +22,11 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; import org.agrona.MutableDirectBuffer; import org.agrona.concurrent.UnsafeBuffer; @@ -29,6 +34,9 @@ final class TestUtils { public static final String DB_1 = "test-db-1"; + public static final String DB_2 = "test-db-2"; + public static final String DB_3 = "test-db-3"; + public static final String DB_4 = "test-db-2"; public static final int POSIX_MODE = 0664; @@ -56,6 +64,53 @@ static ByteBuffer bb(final long value) { return bb; } + static ByteBuffer bb(final String value) { + final ByteBuffer bb = allocateDirect(100); + if (value != null) { + bb.put(value.getBytes(StandardCharsets.UTF_8)); + bb.flip(); + } + return bb; + } + + static ByteBuffer bbNative(final int value) { + final ByteBuffer bb = allocateDirect(Integer.BYTES).order(ByteOrder.nativeOrder()); + bb.putInt(value).flip(); + return bb; + } + + static ByteBuffer bbNative(final long value) { + final ByteBuffer bb = allocateDirect(Long.BYTES).order(ByteOrder.nativeOrder()); + bb.putLong(value).flip(); + return bb; + } + + static int getNativeInt(final ByteBuffer bb) { + final int val = bb.order(ByteOrder.nativeOrder()).getInt(); + bb.rewind(); + return val; + } + + static long getNativeLong(final ByteBuffer bb) { + final long val = bb.order(ByteOrder.nativeOrder()).getLong(); + bb.rewind(); + return val; + } + + static long getNativeIntOrLong(final ByteBuffer bb) { + if (bb.remaining() == Integer.BYTES) { + return getNativeInt(bb); + } else { + return getNativeLong(bb); + } + } + + static String getString(final ByteBuffer bb) { + final String str = StandardCharsets.UTF_8.decode(bb).toString(); + bb.rewind(); + return str; + } + static byte[] getBytes(final ByteBuffer byteBuffer) { if (byteBuffer == null) { return null; @@ -90,4 +145,36 @@ static ByteBuf nb(final int value) { b.writeInt(value); return b; } + + static void doWithReadTxn(final Env env, final Consumer> work) { + Objects.requireNonNull(env); + Objects.requireNonNull(work); + try (Txn readTxn = env.txnRead()) { + work.accept(readTxn); + } + } + + static R getWithReadTxn(final Env env, final Function, R> work) { + Objects.requireNonNull(env); + Objects.requireNonNull(work); + try (Txn readTxn = env.txnRead()) { + return work.apply(readTxn); + } + } + + static void doWithWriteTxn(final Env env, final Consumer> work) { + Objects.requireNonNull(env); + Objects.requireNonNull(work); + try (Txn readTxn = env.txnWrite()) { + work.accept(readTxn); + } + } + + static R getWithWriteTxn(final Env env, final Function, R> work) { + Objects.requireNonNull(env); + Objects.requireNonNull(work); + try (Txn readTxn = env.txnWrite()) { + return work.apply(readTxn); + } + } } diff --git a/src/test/java/org/lmdbjava/TxnFlagSetTest.java b/src/test/java/org/lmdbjava/TxnFlagSetTest.java new file mode 100644 index 00000000..08788c63 --- /dev/null +++ b/src/test/java/org/lmdbjava/TxnFlagSetTest.java @@ -0,0 +1,85 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; +import java.util.HashSet; +import org.junit.jupiter.api.Test; + +public class TxnFlagSetTest { + + @Test + void testSingleEnum() { + final TxnFlagSet txnFlagSet = TxnFlags.MDB_RDONLY_TXN; + assertThat(txnFlagSet.getMask()).isEqualTo(MaskedFlag.mask(TxnFlags.MDB_RDONLY_TXN)); + assertThat(txnFlagSet.size()).isEqualTo(1); + for (TxnFlags flag : txnFlagSet) { + assertThat(txnFlagSet.isSet(flag)).isEqualTo(true); + } + + final TxnFlagSet txnFlagSet2 = TxnFlagSet.builder().setFlag(TxnFlags.MDB_RDONLY_TXN).build(); + assertThat(txnFlagSet2.getFlags()).containsExactlyElementsOf(txnFlagSet.getFlags()); + assertThat(txnFlagSet.areAnySet(TxnFlags.MDB_RDONLY_TXN)).isTrue(); + assertThat(txnFlagSet.areAnySet(TxnFlagSet.empty())).isFalse(); + } + + @Test + public void testEmpty() { + final TxnFlagSet txnFlagSet = TxnFlagSet.empty(); + assertThat(txnFlagSet.getMask()).isEqualTo(0); + assertThat(txnFlagSet.size()).isEqualTo(0); + assertThat(txnFlagSet.isEmpty()).isEqualTo(true); + assertThat(txnFlagSet.isSet(TxnFlags.MDB_RDONLY_TXN)).isEqualTo(false); + final TxnFlagSet txnFlagSet2 = TxnFlagSet.builder().build(); + assertThat(txnFlagSet).isEqualTo(txnFlagSet2); + assertThat(txnFlagSet).isNotEqualTo(TxnFlagSet.of(TxnFlags.MDB_RDONLY_TXN)); + assertThat(txnFlagSet) + .isNotEqualTo(TxnFlagSet.builder().setFlag(TxnFlags.MDB_RDONLY_TXN).build()); + } + + @Test + public void testOf() { + final TxnFlags txnFlag = TxnFlags.MDB_RDONLY_TXN; + final TxnFlagSet txnFlagSet = TxnFlagSet.of(txnFlag); + assertThat(txnFlagSet.getMask()).isEqualTo(MaskedFlag.mask(txnFlag)); + assertThat(txnFlagSet.size()).isEqualTo(1); + for (TxnFlags flag : txnFlagSet) { + assertThat(txnFlagSet.isSet(flag)).isEqualTo(true); + } + + final TxnFlagSet txnFlagSet2 = TxnFlagSet.builder().setFlag(txnFlag).build(); + assertThat(txnFlagSet).isEqualTo(txnFlagSet2); + } + + @Test + public void testBuilder() { + final TxnFlags txnFlag1 = TxnFlags.MDB_RDONLY_TXN; + final TxnFlagSet txnFlagSet = TxnFlagSet.builder().setFlag(txnFlag1).build(); + assertThat(txnFlagSet.getMask()).isEqualTo(MaskedFlag.mask(txnFlag1)); + assertThat(txnFlagSet.size()).isEqualTo(1); + assertThat(txnFlagSet.isSet(TxnFlags.MDB_RDONLY_TXN)).isEqualTo(true); + for (TxnFlags flag : txnFlagSet) { + assertThat(txnFlagSet.isSet(flag)).isEqualTo(true); + } + final TxnFlagSet txnFlagSet2 = TxnFlagSet.builder().withFlags(txnFlag1).build(); + final TxnFlagSet txnFlagSet3 = + TxnFlagSet.builder().withFlags(new HashSet<>(Collections.singletonList(txnFlag1))).build(); + assertThat(txnFlagSet).isEqualTo(txnFlagSet2); + assertThat(txnFlagSet).isEqualTo(txnFlagSet3); + } +} From 484a772b03ad439ba5977d2afdca6e1d8bbf0118 Mon Sep 17 00:00:00 2001 From: stroomdev66 Date: Wed, 5 Nov 2025 18:41:39 +0000 Subject: [PATCH 30/90] Merged new comparator code --- .../java/org/lmdbjava/CursorIterableRangeTest.java | 8 +++++--- .../CursorIterableRangeTest/testIntegerKey.csv | 14 ++++++-------- .../CursorIterableRangeTest/testLongKey.csv | 14 ++++++-------- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/test/java/org/lmdbjava/CursorIterableRangeTest.java b/src/test/java/org/lmdbjava/CursorIterableRangeTest.java index 4fa036f5..f835c494 100644 --- a/src/test/java/org/lmdbjava/CursorIterableRangeTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableRangeTest.java @@ -114,7 +114,7 @@ void testUnsignedComparatorDupsort( void testIntegerKey( final String keyType, final String startKey, final String stopKey, final String expectedKV) { testCSV( - AbstractByteBufferProxy::compareLexicographically, + AbstractByteBufferProxy::compareAsIntegerKeys, false, createIntegerDBPopulator(), EnumSet.of(MDB_CREATE, MDB_INTEGERKEY), @@ -131,7 +131,7 @@ void testIntegerKey( void testLongKey( final String keyType, final String startKey, final String stopKey, final String expectedKV) { testCSV( - AbstractByteBufferProxy::compareLexicographically, + AbstractByteBufferProxy::compareAsIntegerKeys, false, createLongDBPopulator(), EnumSet.of(MDB_CREATE, MDB_INTEGERKEY), @@ -183,7 +183,9 @@ private void testCSV( .setMapSize(KIBIBYTES.toBytes(256)) .setMaxReaders(1) .setMaxDbs(1) - .open(file.toFile(), POSIX_MODE, MDB_NOSUBDIR)) { + .setFilePermissions(POSIX_MODE) + .setEnvFlags(MDB_NOSUBDIR) + .open(file)) { final Dbi dbi = env.openDbi(DB_1, comparator, nativeCb, flags.toArray(new DbiFlags[0])); dbPopulator.accept(env, dbi); diff --git a/src/test/resources/CursorIterableRangeTest/testIntegerKey.csv b/src/test/resources/CursorIterableRangeTest/testIntegerKey.csv index d8d6dc35..df61bf73 100644 --- a/src/test/resources/CursorIterableRangeTest/testIntegerKey.csv +++ b/src/test/resources/CursorIterableRangeTest/testIntegerKey.csv @@ -43,13 +43,11 @@ BACKWARD_GREATER_THAN,1001,,[1000 2][0 1] BACKWARD_LESS_THAN,,999,[-1000 5][-1000000 4][1000000 3][1000 2] BACKWARD_LESS_THAN,,1000,[-1000 5][-1000000 4][1000000 3] BACKWARD_LESS_THAN,,1001,[-1000 5][-1000000 4][1000000 3] -BACKWARD_OPEN,999,1001,[1000 2] -BACKWARD_OPEN,999,1000, -BACKWARD_OPEN,999,1001,[1000 2] +BACKWARD_OPEN,1001,999,[1000 2] +BACKWARD_OPEN,1000,999, BACKWARD_OPEN,1000,1000, -BACKWARD_OPEN,1000,1001, -BACKWARD_OPEN_CLOSED,999,1001,[1000 2] -BACKWARD_OPEN_CLOSED,999,1000,[1000 2] +BACKWARD_OPEN,1001,1000, +BACKWARD_OPEN_CLOSED,1001,999,[1000 2] +BACKWARD_OPEN_CLOSED,1000,999, BACKWARD_OPEN_CLOSED,1000,1000, -BACKWARD_OPEN_CLOSED,1000,1001, -BACKWARD_OPEN_CLOSED,1000,1001, +BACKWARD_OPEN_CLOSED,1001,1000,[1000 2] diff --git a/src/test/resources/CursorIterableRangeTest/testLongKey.csv b/src/test/resources/CursorIterableRangeTest/testLongKey.csv index d8d6dc35..df61bf73 100644 --- a/src/test/resources/CursorIterableRangeTest/testLongKey.csv +++ b/src/test/resources/CursorIterableRangeTest/testLongKey.csv @@ -43,13 +43,11 @@ BACKWARD_GREATER_THAN,1001,,[1000 2][0 1] BACKWARD_LESS_THAN,,999,[-1000 5][-1000000 4][1000000 3][1000 2] BACKWARD_LESS_THAN,,1000,[-1000 5][-1000000 4][1000000 3] BACKWARD_LESS_THAN,,1001,[-1000 5][-1000000 4][1000000 3] -BACKWARD_OPEN,999,1001,[1000 2] -BACKWARD_OPEN,999,1000, -BACKWARD_OPEN,999,1001,[1000 2] +BACKWARD_OPEN,1001,999,[1000 2] +BACKWARD_OPEN,1000,999, BACKWARD_OPEN,1000,1000, -BACKWARD_OPEN,1000,1001, -BACKWARD_OPEN_CLOSED,999,1001,[1000 2] -BACKWARD_OPEN_CLOSED,999,1000,[1000 2] +BACKWARD_OPEN,1001,1000, +BACKWARD_OPEN_CLOSED,1001,999,[1000 2] +BACKWARD_OPEN_CLOSED,1000,999, BACKWARD_OPEN_CLOSED,1000,1000, -BACKWARD_OPEN_CLOSED,1000,1001, -BACKWARD_OPEN_CLOSED,1000,1001, +BACKWARD_OPEN_CLOSED,1001,1000,[1000 2] From ad75cb2351ecf10b452e80005d68b04eb1af11f9 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Wed, 5 Nov 2025 21:08:15 +0000 Subject: [PATCH 31/90] Fix CodeQL issues, add setMapSizeXx methods --- .../java/org/lmdbjava/CursorIterable.java | 2 +- src/main/java/org/lmdbjava/Dbi.java | 120 +++--- src/main/java/org/lmdbjava/DbiBuilder.java | 8 +- src/main/java/org/lmdbjava/Env.java | 125 +++++- src/main/java/org/lmdbjava/FlagSet.java | 1 + src/main/java/org/lmdbjava/Verifier.java | 2 +- .../org/lmdbjava/ByteBufferProxyTest.java | 21 +- .../java/org/lmdbjava/CursorParamTest.java | 2 +- src/test/java/org/lmdbjava/CursorTest.java | 16 +- src/test/java/org/lmdbjava/DbiTest.java | 8 +- src/test/java/org/lmdbjava/EnvTest.java | 387 +++++++++--------- src/test/java/org/lmdbjava/TutorialTest.java | 2 +- 12 files changed, 404 insertions(+), 290 deletions(-) diff --git a/src/main/java/org/lmdbjava/CursorIterable.java b/src/main/java/org/lmdbjava/CursorIterable.java index 69d43fcd..89dc5e84 100644 --- a/src/main/java/org/lmdbjava/CursorIterable.java +++ b/src/main/java/org/lmdbjava/CursorIterable.java @@ -115,7 +115,7 @@ public KeyVal next() { @Override public void remove() { - cursor.delete(); + cursor.delete(PutFlags.EMPTY); } }; } diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index 5e8fa2f2..7347605f 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -49,7 +49,6 @@ */ public final class Dbi { - private final ComparatorCallback callbackComparator; private boolean cleaned; // Used for CursorIterable KeyRange testing and/or native callbacks private final Comparator comparator; @@ -59,6 +58,14 @@ public final class Dbi { private final BufferProxy proxy; private final DbiFlagSet dbiFlagSet; + Dbi(final Env env, + final Txn txn, + final byte[] name, + final BufferProxy proxy, + final DbiFlagSet dbiFlagSet) { + this(env, txn, name, null, false, proxy, dbiFlagSet); + } + Dbi( final Env env, final Txn txn, @@ -67,7 +74,11 @@ public final class Dbi { final boolean nativeCb, final BufferProxy proxy, final DbiFlagSet dbiFlagSet) { + if (SHOULD_CHECK) { + if (nativeCb && comparator == null) { + throw new IllegalArgumentException("Is nativeCb is true, you must supply a comparator"); + } requireNonNull(txn); txn.checkReady(); } @@ -75,40 +86,32 @@ public final class Dbi { this.name = name == null ? null : Arrays.copyOf(name, name.length); this.proxy = proxy; this.comparator = comparator; - this.dbiFlagSet = dbiFlagSet; + this.dbiFlagSet = dbiFlagSet == null ? DbiFlagSet.EMPTY : dbiFlagSet; final Pointer dbiPtr = allocateDirect(RUNTIME, ADDRESS); - checkRc(LIB.mdb_dbi_open(txn.pointer(), name, dbiFlagSet.getMask(), dbiPtr)); + checkRc(LIB.mdb_dbi_open(txn.pointer(), name, this.dbiFlagSet.getMask(), dbiPtr)); ptr = dbiPtr.getPointer(0); + ComparatorCallback callbackComparator; if (nativeCb) { - requireNonNull(comparator, "comparator cannot be null if nativeCb is set"); // LMDB will call back to this comparator for insertion/iteration order -// if (dbiFlagSet.areAnySet(DbiFlagSet.INTEGER_KEY_FLAGS)) { -// this.callbackComparator = -// (keyA, keyB) -> { -// final T compKeyA = proxy.out(proxy.allocate(), keyA); -// final T compKeyB = proxy.out(proxy.allocate(), keyB); -// final int result = this.comparator.compare(compKeyA, compKeyB); -// proxy.deallocate(compKeyA); -// proxy.deallocate(compKeyB); -// return result; -// }; -// } else { - this.callbackComparator = - (keyA, keyB) -> { - final T compKeyA = proxy.out(proxy.allocate(), keyA); - final T compKeyB = proxy.out(proxy.allocate(), keyB); - final int result = this.comparator.compare(compKeyA, compKeyB); - proxy.deallocate(compKeyA); - proxy.deallocate(compKeyB); - return result; - }; -// } + callbackComparator = createCallbackComparator(proxy); LIB.mdb_set_compare(txn.pointer(), ptr, callbackComparator); - } else { - callbackComparator = null; } } + private ComparatorCallback createCallbackComparator(BufferProxy proxy) { + ComparatorCallback callbackComparator; + callbackComparator = + (keyA, keyB) -> { + final T compKeyA = proxy.out(proxy.allocate(), keyA); + final T compKeyB = proxy.out(proxy.allocate(), keyB); + final int result = this.comparator.compare(compKeyA, compKeyB); + proxy.deallocate(compKeyA); + proxy.deallocate(compKeyB); + return result; + }; + return callbackComparator; + } + Pointer pointer() { return ptr; } @@ -215,7 +218,7 @@ public void drop(final Txn txn) { * closed. See {@link #close()} for implication of handle close. Otherwise, only the data in this * database will be dropped. * - * @param txn transaction handle (not null; not committed; must be R-W) + * @param txn transaction handle (not null; not committed; must be R-W) * @param delete whether database should be deleted. */ public void drop(final Txn txn, final boolean delete) { @@ -311,7 +314,7 @@ public CursorIterable iterate(final Txn txn) { /** * Iterate the database in accordance with the provided {@link KeyRange}. * - * @param txn transaction handle (not null; not committed) + * @param txn transaction handle (not null; not committed) * @param range range of acceptable keys (not null) * @return iterator (never null) */ @@ -392,6 +395,12 @@ public void put(final T key, final T val) { } /** + * @param txn transaction handle (not null; not committed; must be R-W) + * @param key key to store in the database (not null) + * @param val value to store in the database (not null) + * @param flags Special options for this operation + * @return true if the value was put, false if MDB_NOOVERWRITE or MDB_NODUPDATA were set and the + * key/value existed already. * @deprecated Use {@link Dbi#put(Txn, Object, Object, PutFlagSet)} instead, with a statically * held {@link PutFlagSet}. *


@@ -401,13 +410,6 @@ public void put(final T key, final T val) { *

This function stores key/data pairs in the database. The default behavior is to enter the * new key/data pair, replacing any previously existing key if duplicates are disallowed, or * adding a duplicate data item if duplicates are allowed ({@link DbiFlags#MDB_DUPSORT}). - * - * @param txn transaction handle (not null; not committed; must be R-W) - * @param key key to store in the database (not null) - * @param val value to store in the database (not null) - * @param flags Special options for this operation - * @return true if the value was put, false if MDB_NOOVERWRITE or MDB_NODUPDATA were set and the - * key/value existed already. */ @Deprecated public boolean put(final Txn txn, final T key, final T val, final PutFlags... flags) { @@ -421,7 +423,7 @@ public boolean put(final Txn txn, final T key, final T val, final PutFlags... * @param key key to store in the database (not null) * @param val value to store in the database (not null) * @return true if the value was put, false if MDB_NOOVERWRITE or MDB_NODUPDATA were set and the - * key/value existed already. + * key/value existed already. * @see #put(Txn, Object, Object, PutFlagSet) */ public boolean put(final Txn txn, final T key, final T val) { @@ -435,12 +437,12 @@ public boolean put(final Txn txn, final T key, final T val) { * new key/data pair, replacing any previously existing key if duplicates are disallowed, or * adding a duplicate data item if duplicates are allowed ({@link DbiFlags#MDB_DUPSORT}). * - * @param txn transaction handle (not null; not committed; must be R-W) - * @param key key to store in the database (not null) - * @param val value to store in the database (not null) + * @param txn transaction handle (not null; not committed; must be R-W) + * @param key key to store in the database (not null) + * @param val value to store in the database (not null) * @param flags Special options for this operation. * @return true if the value was put, false if MDB_NOOVERWRITE or MDB_NODUPDATA were set and the - * key/value existed already. + * key/value existed already. */ public boolean put(final Txn txn, final T key, final T val, final PutFlagSet flags) { if (SHOULD_CHECK) { @@ -480,10 +482,10 @@ public boolean put(final Txn txn, final T key, final T val, final PutFlagSet * *

This flag must not be specified if the database was opened with MDB_DUPSORT * - * @param txn transaction handle (not null; not committed; must be R-W) - * @param key key to store in the database (not null) + * @param txn transaction handle (not null; not committed; must be R-W) + * @param key key to store in the database (not null) * @param size size of the value to be stored in the database - * @param op options for this operation + * @param op options for this operation * @return a buffer that can be used to modify the value */ public T reserve(final Txn txn, final T key, final int size, final PutFlags... op) { @@ -544,12 +546,14 @@ public String toString() { name = "?"; } return "Dbi{" + - "name='" + name + - "', dbiFlagSet=" + dbiFlagSet + - '}'; + "name='" + name + + "', dbiFlagSet=" + dbiFlagSet + + '}'; } - /** The specified DBI was changed unexpectedly. */ + /** + * The specified DBI was changed unexpectedly. + */ public static final class BadDbiException extends LmdbNativeException { static final int MDB_BAD_DBI = -30_780; @@ -560,7 +564,9 @@ public static final class BadDbiException extends LmdbNativeException { } } - /** Unsupported size of key/DB name/data, or wrong DUPFIXED size. */ + /** + * Unsupported size of key/DB name/data, or wrong DUPFIXED size. + */ public static final class BadValueSizeException extends LmdbNativeException { static final int MDB_BAD_VALSIZE = -30_781; @@ -571,7 +577,9 @@ public static final class BadValueSizeException extends LmdbNativeException { } } - /** Environment maxdbs reached. */ + /** + * Environment maxdbs reached. + */ public static final class DbFullException extends LmdbNativeException { static final int MDB_DBS_FULL = -30_791; @@ -604,7 +612,9 @@ public static final class IncompatibleException extends LmdbNativeException { } } - /** Key/data pair already exists. */ + /** + * Key/data pair already exists. + */ public static final class KeyExistsException extends LmdbNativeException { static final int MDB_KEYEXIST = -30_799; @@ -615,7 +625,9 @@ public static final class KeyExistsException extends LmdbNativeException { } } - /** Key/data pair not found (EOF). */ + /** + * Key/data pair not found (EOF). + */ public static final class KeyNotFoundException extends LmdbNativeException { static final int MDB_NOTFOUND = -30_798; @@ -626,7 +638,9 @@ public static final class KeyNotFoundException extends LmdbNativeException { } } - /** Database contents grew beyond environment mapsize. */ + /** + * Database contents grew beyond environment mapsize. + */ public static final class MapResizedException extends LmdbNativeException { static final int MDB_MAP_RESIZED = -30_785; diff --git a/src/main/java/org/lmdbjava/DbiBuilder.java b/src/main/java/org/lmdbjava/DbiBuilder.java index 9cb85616..062e77f4 100644 --- a/src/main/java/org/lmdbjava/DbiBuilder.java +++ b/src/main/java/org/lmdbjava/DbiBuilder.java @@ -391,10 +391,10 @@ public DbiBuilderStage3 setTxn(final Txn txn) { public Dbi open() { final DbiBuilder dbiBuilder = dbiBuilderStage2.dbiBuilder; if (txn != null) { - return open(txn, dbiBuilder); + return openDbi(txn, dbiBuilder); } else { try (final Txn txn = getTxn(dbiBuilder)) { - final Dbi dbi = open(txn, dbiBuilder); + final Dbi dbi = openDbi(txn, dbiBuilder); // even RO Txns require a commit to retain Dbi in Env txn.commit(); return dbi; @@ -432,8 +432,8 @@ private Comparator getComparator(final DbiBuilder dbiBuilder, return comparator; } - private Dbi open(final Txn txn, - final DbiBuilder dbiBuilder) { + private Dbi openDbi(final Txn txn, + final DbiBuilder dbiBuilder) { final DbiFlagSet dbiFlagSet = flagSetBuilder.build(); final ComparatorType comparatorType = dbiBuilderStage2.comparatorType; final Comparator comparator = getComparator(dbiBuilder, comparatorType, dbiFlagSet); diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index b8003cbb..e01de06b 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -63,6 +63,11 @@ public final class Env implements AutoCloseable { */ public static final boolean SHOULD_CHECK = !getBoolean(DISABLE_CHECKS_PROP); + private static final long KIBIBYTES = 1_024; + private static final long MEBIBYTES = KIBIBYTES * 1_024; + private static final long GIBIBYTES = MEBIBYTES * 1_024; + private static final long TEBIBYTES = GIBIBYTES * 1_024; + private boolean closed; private final int maxKeySize; private final boolean noSubDir; @@ -104,20 +109,19 @@ public static Builder create(final BufferProxy proxy) { } /** - * @deprecated Instead use {@link Env#create()} or {@link Env#create(BufferProxy)} - *

- * Opens an environment with a single default database in 0664 mode using the {@link - * ByteBufferProxy#PROXY_OPTIMAL}. - * * @param path file system destination * @param size size in megabytes * @param flags the flags for this new environment * @return env the environment (never null) + * @deprecated Instead use {@link Env#create()} or {@link Env#create(BufferProxy)} + *

+ * Opens an environment with a single default database in 0664 mode using the {@link + * ByteBufferProxy#PROXY_OPTIMAL}. */ @Deprecated public static Env open(final File path, final int size, final EnvFlags... flags) { return new Builder<>(PROXY_OPTIMAL) - .setMapSize(size * 1_024L * 1_024L) + .setMapSize(size * MEBIBYTES) .open(path, flags); } @@ -193,9 +197,10 @@ public void copy(final File path, final CopyFlagSet flags) { */ public List getDbiNames() { final List result = new ArrayList<>(); - final Dbi names = openDbi((byte[]) null); - try (Txn txn = txnRead(); - Cursor cursor = names.openCursor(txn)) { + // The unnamed DB is special so the names of the named DBs are held as keys in it. + final Dbi unnamedDb = openDbi((byte[]) null, DbiFlagSet.EMPTY); + try (final Txn txn = txnRead(); + final Cursor cursor = unnamedDb.openCursor(txn)) { if (!cursor.first()) { return Collections.emptyList(); } @@ -204,7 +209,6 @@ public List getDbiNames() { result.add(name); } while (cursor.next()); } - return result; } @@ -282,11 +286,49 @@ public DbiBuilder buildDbi() { return new DbiBuilder<>(this, proxy, readOnly); } + /** + * Convenience method that opens a {@link Dbi} with a UTF-8 database name and default + * {@link Comparator} that is not invoked from native code. + *

+ * For more options when opening a {@link Dbi} see {@link Env#buildDbi()}. + *

+ * @param name name of the database (or null if no name is required) + * @param dbiFlagSet Flags to open the database with + * @return a database that is ready to use + */ + public Dbi openDbi(final String name, final DbiFlagSet dbiFlagSet) { + final byte[] nameBytes = name == null ? null : name.getBytes(UTF_8); + try (Txn txn = readOnly ? txnRead() : txnWrite()) { + final Dbi dbi = new Dbi<>(this, txn, nameBytes, proxy, dbiFlagSet); + txn.commit(); // even RO Txns require a commit to retain Dbi in Env + return dbi; + } + } + + /** + * Convenience method that opens a {@link Dbi} with a default + * {@link Comparator} that is not invoked from native code. + *

+ * For more options when opening a {@link Dbi} see {@link Env#buildDbi()}. + *

+ * @param name name of the database (or null if no name is required) + * @param dbiFlagSet Flags to open the database with + * @return a database that is ready to use + */ + public Dbi openDbi(final byte[] name, + final DbiFlagSet dbiFlagSet) { + try (Txn txn = readOnly ? txnRead() : txnWrite()) { + final Dbi dbi = new Dbi<>(this, txn, name, proxy, dbiFlagSet); + txn.commit(); // even RO Txns require a commit to retain Dbi in Env + return dbi; + } + } + /** * @param name name of the database (or null if no name is required) * @param flags to open the database with * @return a database that is ready to use - * @deprecated Instead use {@link Env#buildDbi()} + * @deprecated Instead use {@link Env#buildDbi()} or {@link Env#openDbi(String, DbiFlagSet)} * Convenience method that opens a {@link Dbi} with a UTF-8 database name and default {@link * Comparator} that is not invoked from native code. */ @@ -315,7 +357,7 @@ public Dbi openDbi(final String name, final DbiFlags... flags) { public Dbi openDbi(final String name, final Comparator comparator, final DbiFlags... flags) { - final byte[] nameBytes = name == null ? null : name.getBytes(UTF_8); + final byte[] nameBytes = name == null ? null : name.getBytes(DEFAULT_NAME_CHARSET); return openDbi(nameBytes, comparator, false, flags); } @@ -338,7 +380,7 @@ public Dbi openDbi(final String name, final Comparator comparator, final boolean nativeCb, final DbiFlags... flags) { - final byte[] nameBytes = name == null ? null : name.getBytes(UTF_8); + final byte[] nameBytes = name == null ? null : name.getBytes(DEFAULT_NAME_CHARSET); return openDbi(nameBytes, comparator, nativeCb, flags); } @@ -354,7 +396,11 @@ public Dbi openDbi(final String name, @Deprecated() public Dbi openDbi(final byte[] name, final DbiFlags... flags) { - return openDbi(name, null, false, flags); + return buildDbi() + .setDbName(name) + .withDefaultComparator() + .setDbiFlags(flags) + .open(); } /** @@ -371,7 +417,12 @@ public Dbi openDbi(final byte[] name, public Dbi openDbi(final byte[] name, final Comparator comparator, final DbiFlags... flags) { - return openDbi(name, comparator, false, flags); + requireNonNull(comparator); + return buildDbi() + .setDbName(name) + .withIteratorComparator(ignored -> comparator) + .setDbiFlags(flags) + .open(); } /** @@ -435,10 +486,6 @@ public Dbi openDbi( final Comparator comparator, final boolean nativeCb, final DbiFlags... flags) { - - if (nativeCb && comparator == null) { - throw new IllegalArgumentException("Is nativeCb is true, you must supply a comparator"); - } return new Dbi<>(this, txn, name, comparator, nativeCb, proxy, DbiFlagSet.of(flags)); } @@ -724,6 +771,46 @@ public Builder setMapSize(final long mapSize) { return this; } + /** + * Sets the map size in kibibytes + * + * @param mapSizeKb new limit in kibibytes + * @return the builder + */ + public Builder setMapSizeKb(final long mapSizeKb) { + return setMapSize(mapSizeKb * KIBIBYTES); + } + + /** + * Sets the map size in mebibytes. + * + * @param mapSizeMb new limit in mebibytes. + * @return the builder + */ + public Builder setMapSizeMb(final long mapSizeMb) { + return setMapSize(mapSizeMb * MEBIBYTES); + } + + /** + * Sets the map size in gibibytes + * + * @param mapSizeGb new limit in gibibytes + * @return the builder + */ + public Builder setMapSizeGb(final long mapSizeGb) { + return setMapSize(mapSizeGb * GIBIBYTES); + } + + /** + * Sets the map size in tebibytes. + * + * @param mapSizeTb new limit in tebibytes. + * @return the builder + */ + public Builder setMapSizeTb(final long mapSizeTb) { + return setMapSize(mapSizeTb * TEBIBYTES); + } + /** * Sets the maximum number of databases (ie {@link Dbi}s permitted. * diff --git a/src/main/java/org/lmdbjava/FlagSet.java b/src/main/java/org/lmdbjava/FlagSet.java index 27513fcd..a9cffd47 100644 --- a/src/main/java/org/lmdbjava/FlagSet.java +++ b/src/main/java/org/lmdbjava/FlagSet.java @@ -88,6 +88,7 @@ default boolean isEmpty() { /** * @return The {@link Iterator} (in no particular order) for the flags in this {@link FlagSet}. */ + @Override default Iterator iterator() { return getFlags().iterator(); } diff --git a/src/main/java/org/lmdbjava/Verifier.java b/src/main/java/org/lmdbjava/Verifier.java index ff9b28f8..cbe99b81 100644 --- a/src/main/java/org/lmdbjava/Verifier.java +++ b/src/main/java/org/lmdbjava/Verifier.java @@ -175,7 +175,7 @@ private void createDbis() { private void deleteDbis() { for (final byte[] existingDbiName : env.getDbiNames()) { - final Dbi existingDbi = env.openDbi(existingDbiName); + final Dbi existingDbi = env.openDbi(existingDbiName, DbiFlagSet.EMPTY); try (Txn txn = env.txnWrite()) { existingDbi.drop(txn, true); txn.commit(); diff --git a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java index dc034f7f..e035911b 100644 --- a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java +++ b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java @@ -155,29 +155,32 @@ void unsafeIsDefault() { */ @Test public void comparatorPerformance() { - final Random random = new Random(); + final Random random = new Random(345098); final ByteBuffer buffer1 = ByteBuffer.allocateDirect(Long.BYTES); final ByteBuffer buffer2 = ByteBuffer.allocateDirect(Long.BYTES); buffer1.limit(Long.BYTES); buffer2.limit(Long.BYTES); - final long[] values = random.longs(5_000_000).toArray(); + final long[] values = random.longs(20_000_000).toArray(); Instant time = Instant.now(); + // x is to ensure result is used by the jvm int x = 0; for (int rounds = 0; rounds < 100; rounds++) { for (int i = 1; i < values.length; i++) { - buffer1.order(ByteOrder.nativeOrder()) +// buffer1.order(ByteOrder.nativeOrder()) + buffer1 .putLong(0, values[i - 1]); - buffer2.order(ByteOrder.nativeOrder()) +// buffer2.order(ByteOrder.nativeOrder()) + buffer2 .putLong(0, values[i]); final int result = ByteBufferProxy.AbstractByteBufferProxy.compareAsIntegerKeys(buffer1, buffer2); x += result; } } - System.out.println("compareAsIntegerKeys: " + Duration.between(time, Instant.now())); + System.out.println("compareAsIntegerKeys: " + Duration.between(time, Instant.now()) + ", x: " + x); time = Instant.now(); - x = 0; + int y = 0; for (int rounds = 0; rounds < 100; rounds++) { for (int i = 1; i < values.length; i++) { buffer1.order(BIG_ENDIAN) @@ -185,10 +188,12 @@ public void comparatorPerformance() { buffer2.order(BIG_ENDIAN) .putLong(0, values[i]); final int result = ByteBufferProxy.AbstractByteBufferProxy.compareLexicographically(buffer1, buffer2); - x += result; + y += result; } } - System.out.println("compareLexicographically: " + Duration.between(time, Instant.now())); + System.out.println("compareLexicographically: " + Duration.between(time, Instant.now()) + ", y: " + y); + + assertThat(y).isEqualTo(x); } @Test diff --git a/src/test/java/org/lmdbjava/CursorParamTest.java b/src/test/java/org/lmdbjava/CursorParamTest.java index 1a3604be..5b701382 100644 --- a/src/test/java/org/lmdbjava/CursorParamTest.java +++ b/src/test/java/org/lmdbjava/CursorParamTest.java @@ -88,7 +88,7 @@ protected AbstractBufferRunner(final BufferProxy proxy) { public final void execute(final Path tmp) { try (Env env = env(tmp)) { assertThat(env.getDbiNames()).isEmpty(); - final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); + final Dbi db = env.openDbi(DB_1, DbiFlagSet.of(MDB_CREATE, MDB_DUPSORT)); assertThat(env.getDbiNames().get(0)).isEqualTo(DB_1.getBytes(UTF_8)); try (Txn txn = env.txnWrite(); Cursor c = db.openCursor(txn)) { diff --git a/src/test/java/org/lmdbjava/CursorTest.java b/src/test/java/org/lmdbjava/CursorTest.java index a0c78855..41893ae6 100644 --- a/src/test/java/org/lmdbjava/CursorTest.java +++ b/src/test/java/org/lmdbjava/CursorTest.java @@ -175,7 +175,7 @@ void closedEnvRejectsDeleteCall() { @Test void count() { - final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); + final Dbi db = env.openDbi(DB_1, DbiFlagSet.of(MDB_CREATE, MDB_DUPSORT)); try (Txn txn = env.txnWrite(); Cursor c = db.openCursor(txn)) { c.put(bb(1), bb(2), MDB_APPENDDUP); @@ -193,7 +193,7 @@ void count() { void cursorCannotCloseIfTransactionCommitted() { assertThatThrownBy( () -> { - final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); + final Dbi db = env.openDbi(DB_1, DbiFlagSet.of(MDB_CREATE, MDB_DUPSORT)); try (Txn txn = env.txnWrite()) { try (Cursor c = db.openCursor(txn); ) { c.put(bb(1), bb(2), MDB_APPENDDUP); @@ -238,7 +238,7 @@ void cursorFirstLastNextPrev() { @Test void delete() { - final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); + final Dbi db = env.openDbi(DB_1, DbiFlagSet.of(MDB_CREATE, MDB_DUPSORT)); try (Txn txn = env.txnWrite(); Cursor c = db.openCursor(txn)) { c.put(bb(1), bb(2), MDB_NOOVERWRITE); @@ -257,7 +257,7 @@ void delete() { @Test void getKeyVal() { - final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); + final Dbi db = env.openDbi(DB_1, DbiFlagSet.of(MDB_CREATE, MDB_DUPSORT)); try (Txn txn = env.txnWrite(); Cursor c = db.openCursor(txn)) { c.put(bb(1), bb(2), MDB_APPENDDUP); @@ -278,7 +278,7 @@ void getKeyVal() { @Test void putMultiple() { - final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT, MDB_DUPFIXED); + final Dbi db = env.openDbi(DB_1, DbiFlagSet.of(MDB_CREATE, MDB_DUPSORT, MDB_DUPFIXED)); final int elemCount = 20; final ByteBuffer values = allocateDirect(Integer.BYTES * elemCount); @@ -300,7 +300,7 @@ void putMultiple() { void putMultipleWithoutMdbMultipleFlag() { assertThatThrownBy( () -> { - final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); + final Dbi db = env.openDbi(DB_1, DbiFlagSet.of(MDB_CREATE, MDB_DUPSORT)); try (Txn txn = env.txnWrite(); Cursor c = db.openCursor(txn)) { c.putMultiple(bb(100), bb(1), 1); @@ -345,7 +345,7 @@ void renewTxRw() { @Test void repeatedCloseCausesNotError() { - final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); + final Dbi db = env.openDbi(DB_1, DbiFlagSet.of(MDB_CREATE, MDB_DUPSORT)); try (Txn txn = env.txnWrite()) { final Cursor c = db.openCursor(txn); c.close(); @@ -375,7 +375,7 @@ void reserve() { @Test void returnValueForNoDupData() { - final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); + final Dbi db = env.openDbi(DB_1, DbiFlagSet.of(MDB_CREATE, MDB_DUPSORT)); try (Txn txn = env.txnWrite(); Cursor c = db.openCursor(txn)) { // ok diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 7302018c..3e876f67 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -264,7 +264,7 @@ void drop() { @Test void dropAndDelete() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); - final Dbi nameDb = env.openDbi((byte[]) null); + final Dbi nameDb = env.openDbi((byte[]) null, DbiFlagSet.EMPTY); final byte[] dbNameBytes = DB_1.getBytes(UTF_8); final ByteBuffer dbNameBuffer = allocateDirect(dbNameBytes.length); dbNameBuffer.put(dbNameBytes).flip(); @@ -321,7 +321,7 @@ void getNamesWhenEmpty() { @Test void listsFlags() { - final Dbi dbi = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT, MDB_REVERSEKEY); + final Dbi dbi = env.openDbi(DB_1, DbiFlagSet.of(MDB_CREATE, MDB_DUPSORT, MDB_REVERSEKEY)); try (Txn txn = env.txnRead()) { final List flags = dbi.listFlags(txn); @@ -415,7 +415,7 @@ void putDelete() { @Test void putDuplicateDelete() { - final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); + final Dbi db = env.openDbi(DB_1, DbiFlagSet.of(MDB_CREATE, MDB_DUPSORT)); try (Txn txn = env.txnWrite()) { db.put(txn, bb(5), bb(5)); @@ -474,7 +474,7 @@ void putZeroByteValueForNonMdbDupSortDatabase() { @Test void returnValueForNoDupData() { - final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); + final Dbi db = env.openDbi(DB_1, DbiFlagSet.of(MDB_CREATE, MDB_DUPSORT)); try (Txn txn = env.txnWrite()) { // ok assertThat(db.put(txn, bb(5), bb(6), MDB_NODUPDATA)).isTrue(); diff --git a/src/test/java/org/lmdbjava/EnvTest.java b/src/test/java/org/lmdbjava/EnvTest.java index 87edd049..eb925b42 100644 --- a/src/test/java/org/lmdbjava/EnvTest.java +++ b/src/test/java/org/lmdbjava/EnvTest.java @@ -24,8 +24,6 @@ import static org.lmdbjava.CopyFlags.MDB_CP_COMPACT; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.Env.Builder.MAX_READERS_DEFAULT; -import static org.lmdbjava.Env.create; -import static org.lmdbjava.Env.open; import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; import static org.lmdbjava.EnvFlags.MDB_RDONLY_ENV; import static org.lmdbjava.TestUtils.DB_1; @@ -45,7 +43,9 @@ import org.lmdbjava.Env.MapFullException; import org.lmdbjava.Txn.BadReaderLockException; -/** Test {@link Env}. */ +/** + * Test {@link Env}. + */ public final class EnvTest { @Test @@ -53,10 +53,10 @@ void byteUnit() { FileUtil.useTempFile( file -> { try (Env env = - create() - .setMaxReaders(1) - .setMapSize(MEBIBYTES.toBytes(1)) - .open(file.toFile(), MDB_NOSUBDIR)) { + Env.create() + .setMaxReaders(1) + .setMapSize(MEBIBYTES.toBytes(1)) + .open(file.toFile(), MDB_NOSUBDIR)) { final EnvInfo info = env.info(); assertThat(info.mapSize).isEqualTo(MEBIBYTES.toBytes(1)); } @@ -66,116 +66,116 @@ void byteUnit() { @Test void cannotChangeMapSizeAfterOpen() { assertThatThrownBy( - () -> { - FileUtil.useTempFile( - file -> { - final Builder builder = create().setMaxReaders(1); - try (Env env = builder.open(file.toFile(), MDB_NOSUBDIR)) { - builder.setMapSize(1); - } - }); - }) + () -> { + FileUtil.useTempFile( + file -> { + final Builder builder = Env.create().setMaxReaders(1); + try (Env env = builder.open(file.toFile(), MDB_NOSUBDIR)) { + builder.setMapSize(1); + } + }); + }) .isInstanceOf(AlreadyOpenException.class); } @Test void cannotChangeMaxDbsAfterOpen() { assertThatThrownBy( - () -> { - FileUtil.useTempFile( - file -> { - final Builder builder = create().setMaxReaders(1); - try (Env env = builder.open(file.toFile(), MDB_NOSUBDIR)) { - builder.setMaxDbs(1); - } - }); - }) + () -> { + FileUtil.useTempFile( + file -> { + final Builder builder = Env.create().setMaxReaders(1); + try (Env env = builder.open(file.toFile(), MDB_NOSUBDIR)) { + builder.setMaxDbs(1); + } + }); + }) .isInstanceOf(AlreadyOpenException.class); } @Test void cannotChangeMaxReadersAfterOpen() { assertThatThrownBy( - () -> { - FileUtil.useTempFile( - file -> { - final Builder builder = create().setMaxReaders(1); - try (Env env = builder.open(file.toFile(), MDB_NOSUBDIR)) { - builder.setMaxReaders(1); - } - }); - }) + () -> { + FileUtil.useTempFile( + file -> { + final Builder builder = Env.create().setMaxReaders(1); + try (Env env = builder.open(file.toFile(), MDB_NOSUBDIR)) { + builder.setMaxReaders(1); + } + }); + }) .isInstanceOf(AlreadyOpenException.class); } @Test void cannotInfoOnceClosed() { assertThatThrownBy( - () -> { - FileUtil.useTempFile( - file -> { - final Env env = - create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR); - env.close(); - env.info(); - }); - }) + () -> { + FileUtil.useTempFile( + file -> { + final Env env = + Env.create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR); + env.close(); + env.info(); + }); + }) .isInstanceOf(AlreadyClosedException.class); } @Test void cannotOpenTwice() { assertThatThrownBy( - () -> { - FileUtil.useTempFile( - file -> { - final Builder builder = create().setMaxReaders(1); - builder.open(file.toFile(), MDB_NOSUBDIR).close(); - builder.open(file.toFile(), MDB_NOSUBDIR); - }); - }) + () -> { + FileUtil.useTempFile( + file -> { + final Builder builder = Env.create().setMaxReaders(1); + builder.open(file.toFile(), MDB_NOSUBDIR).close(); + builder.open(file.toFile(), MDB_NOSUBDIR); + }); + }) .isInstanceOf(AlreadyOpenException.class); } @Test void cannotOverflowMapSize() { assertThatThrownBy( - () -> { - final Builder builder = create().setMaxReaders(1); - final int mb = 1_024 * 1_024; - final int size = mb * 2_048; // as per issue 18 - builder.setMapSize(size); - }) + () -> { + final Builder builder = Env.create().setMaxReaders(1); + final int mb = 1_024 * 1_024; + final int size = mb * 2_048; // as per issue 18 + builder.setMapSize(size); + }) .isInstanceOf(IllegalArgumentException.class); } @Test void cannotStatOnceClosed() { assertThatThrownBy( - () -> { - FileUtil.useTempFile( - file -> { - final Env env = - create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR); - env.close(); - env.stat(); - }); - }) + () -> { + FileUtil.useTempFile( + file -> { + final Env env = + Env.create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR); + env.close(); + env.stat(); + }); + }) .isInstanceOf(AlreadyClosedException.class); } @Test void cannotSyncOnceClosed() { assertThatThrownBy( - () -> { - FileUtil.useTempFile( - file -> { - final Env env = - create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR); - env.close(); - env.sync(false); - }); - }) + () -> { + FileUtil.useTempFile( + file -> { + final Env env = + Env.create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR); + env.close(); + env.sync(false); + }); + }) .isInstanceOf(AlreadyClosedException.class); } @@ -188,7 +188,7 @@ void copyDirectoryBased() { assertThat(FileUtil.count(dest)).isEqualTo(0); FileUtil.useTempDir( src -> { - try (Env env = create().setMaxReaders(1).open(src.toFile())) { + try (Env env = Env.create().setMaxReaders(1).open(src.toFile())) { env.copy(dest.toFile(), MDB_CP_COMPACT); assertThat(FileUtil.count(dest)).isEqualTo(1); } @@ -199,66 +199,66 @@ void copyDirectoryBased() { @Test void copyDirectoryRejectsFileDestination() { assertThatThrownBy( - () -> { - FileUtil.useTempDir( - dest -> { - FileUtil.deleteDir(dest); - FileUtil.useTempDir( - src -> { - try (Env env = create().setMaxReaders(1).open(src.toFile())) { - env.copy(dest.toFile(), MDB_CP_COMPACT); - } - }); - }); - }) + () -> { + FileUtil.useTempDir( + dest -> { + FileUtil.deleteDir(dest); + FileUtil.useTempDir( + src -> { + try (Env env = Env.create().setMaxReaders(1).open(src.toFile())) { + env.copy(dest.toFile(), MDB_CP_COMPACT); + } + }); + }); + }) .isInstanceOf(InvalidCopyDestination.class); } @Test void copyDirectoryRejectsMissingDestination() { assertThatThrownBy( - () -> { - FileUtil.useTempDir( - dest -> { - try { - Files.delete(dest); - FileUtil.useTempDir( - src -> { - try (Env env = - create().setMaxReaders(1).open(src.toFile())) { - env.copy(dest.toFile(), MDB_CP_COMPACT); - } - }); - } catch (final IOException e) { - throw new UncheckedIOException(e); - } - }); - }) + () -> { + FileUtil.useTempDir( + dest -> { + try { + Files.delete(dest); + FileUtil.useTempDir( + src -> { + try (Env env = + Env.create().setMaxReaders(1).open(src.toFile())) { + env.copy(dest.toFile(), MDB_CP_COMPACT); + } + }); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + }); + }) .isInstanceOf(InvalidCopyDestination.class); } @Test void copyDirectoryRejectsNonEmptyDestination() { assertThatThrownBy( - () -> { - FileUtil.useTempDir( - dest -> { - try { - final Path subDir = dest.resolve("hello"); - Files.createDirectory(subDir); - assertThat(Files.isDirectory(subDir)).isTrue(); - FileUtil.useTempDir( - src -> { - try (Env env = - create().setMaxReaders(1).open(src.toFile())) { - env.copy(dest.toFile(), MDB_CP_COMPACT); - } - }); - } catch (final IOException e) { - throw new UncheckedIOException(e); - } - }); - }) + () -> { + FileUtil.useTempDir( + dest -> { + try { + final Path subDir = dest.resolve("hello"); + Files.createDirectory(subDir); + assertThat(Files.isDirectory(subDir)).isTrue(); + FileUtil.useTempDir( + src -> { + try (Env env = + Env.create().setMaxReaders(1).open(src.toFile())) { + env.copy(dest.toFile(), MDB_CP_COMPACT); + } + }); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + }); + }) .isInstanceOf(InvalidCopyDestination.class); } @@ -271,7 +271,7 @@ void copyFileBased() { FileUtil.useTempFile( src -> { try (Env env = - create().setMaxReaders(1).open(src.toFile(), MDB_NOSUBDIR)) { + Env.create().setMaxReaders(1).open(src.toFile(), MDB_NOSUBDIR)) { env.copy(dest.toFile(), MDB_CP_COMPACT); } assertThat(FileUtil.size(dest)).isGreaterThan(0L); @@ -282,19 +282,19 @@ void copyFileBased() { @Test void copyFileRejectsExistingDestination() { assertThatThrownBy( - () -> { - FileUtil.useTempFile( - dest -> { - assertThat(Files.exists(dest)).isTrue(); - FileUtil.useTempFile( - src -> { - try (Env env = - create().setMaxReaders(1).open(src.toFile(), MDB_NOSUBDIR)) { - env.copy(dest.toFile(), MDB_CP_COMPACT); - } - }); - }); - }) + () -> { + FileUtil.useTempFile( + dest -> { + assertThat(Files.exists(dest)).isTrue(); + FileUtil.useTempFile( + src -> { + try (Env env = + Env.create().setMaxReaders(1).open(src.toFile(), MDB_NOSUBDIR)) { + env.copy(dest.toFile(), MDB_CP_COMPACT); + } + }); + }); + }) .isInstanceOf(InvalidCopyDestination.class); } @@ -302,7 +302,7 @@ void copyFileRejectsExistingDestination() { void createAsDirectory() { FileUtil.useTempDir( dest -> { - final Env env = create().setMaxReaders(1).open(dest.toFile()); + final Env env = Env.create().setMaxReaders(1).open(dest.toFile()); assertThat(Files.isDirectory(dest)).isTrue(); env.sync(false); env.close(); @@ -316,11 +316,11 @@ void createAsFile() { FileUtil.useTempFile( file -> { try (Env env = - create() - .setMapSize(MEBIBYTES.toBytes(1)) - .setMaxDbs(1) - .setMaxReaders(1) - .open(file.toFile(), MDB_NOSUBDIR)) { + Env.create() + .setMapSize(MEBIBYTES.toBytes(1)) + .setMaxDbs(1) + .setMaxReaders(1) + .open(file.toFile(), MDB_NOSUBDIR)) { env.sync(true); assertThat(Files.isRegularFile(file)).isTrue(); } @@ -330,16 +330,16 @@ void createAsFile() { @Test void detectTransactionThreadViolation() { assertThatThrownBy( - () -> { - FileUtil.useTempFile( - file -> { - try (Env env = - create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR)) { - env.txnRead(); - env.txnRead(); - } - }); - }) + () -> { + FileUtil.useTempFile( + file -> { + try (Env env = + Env.create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR)) { + env.txnRead(); + env.txnRead(); + } + }); + }) .isInstanceOf(BadReaderLockException.class); } @@ -348,7 +348,7 @@ void info() { FileUtil.useTempFile( file -> { try (Env env = - create().setMaxReaders(4).setMapSize(123_456).open(file.toFile(), MDB_NOSUBDIR)) { + Env.create().setMaxReaders(4).setMapSize(123_456).open(file.toFile(), MDB_NOSUBDIR)) { final EnvInfo info = env.info(); assertThat(info).isNotNull(); assertThat(info.lastPageNumber).isEqualTo(1L); @@ -366,30 +366,30 @@ void info() { @Test void mapFull() { assertThatThrownBy( - () -> { - FileUtil.useTempDir( - dir -> { - final byte[] k = new byte[500]; - final ByteBuffer key = allocateDirect(500); - final ByteBuffer val = allocateDirect(1_024); - final Random rnd = new Random(); - try (Env env = - create() - .setMaxReaders(1) - .setMapSize(MEBIBYTES.toBytes(8)) - .setMaxDbs(1) - .open(dir.toFile())) { - final Dbi db = env.openDbi(DB_1, MDB_CREATE); - for (; ; ) { - rnd.nextBytes(k); - key.clear(); - key.put(k).flip(); - val.clear(); - db.put(key, val); - } - } - }); - }) + () -> { + FileUtil.useTempDir( + dir -> { + final byte[] k = new byte[500]; + final ByteBuffer key = allocateDirect(500); + final ByteBuffer val = allocateDirect(1_024); + final Random rnd = new Random(); + try (Env env = + Env.create() + .setMaxReaders(1) + .setMapSize(MEBIBYTES.toBytes(8)) + .setMaxDbs(1) + .open(dir.toFile())) { + final Dbi db = env.openDbi(DB_1, MDB_CREATE); + for (; ; ) { + rnd.nextBytes(k); + key.clear(); + key.put(k).flip(); + val.clear(); + db.put(key, val); + } + } + }); + }) .isInstanceOf(MapFullException.class); } @@ -397,13 +397,17 @@ void mapFull() { void readOnlySupported() { FileUtil.useTempDir( dir -> { - try (Env rwEnv = create().setMaxReaders(1).open(dir.toFile())) { + try (Env rwEnv = Env.create() + .setMaxReaders(1) + .open(dir)) { final Dbi rwDb = rwEnv.openDbi(DB_1, MDB_CREATE); rwDb.put(bb(1), bb(42)); } - try (Env roEnv = - create().setMaxReaders(1).open(dir.toFile(), MDB_RDONLY_ENV)) { - final Dbi roDb = roEnv.openDbi(DB_1); + try (Env roEnv = Env.create() + .setMaxReaders(1) + .setEnvFlags(MDB_RDONLY_ENV) + .open(dir)) { + final Dbi roDb = roEnv.openDbi(DB_1, DbiFlagSet.EMPTY); try (Txn roTxn = roEnv.txnRead()) { assertThat(roDb.get(roTxn, bb(1))).isNotNull(); } @@ -420,11 +424,11 @@ void setMapSize() { final ByteBuffer val = allocateDirect(1_024); final Random rnd = new Random(); try (Env env = - create() - .setMaxReaders(1) - .setMapSize(KIBIBYTES.toBytes(256)) - .setMaxDbs(1) - .open(dir.toFile())) { + Env.create() + .setMaxReaders(1) + .setMapSize(KIBIBYTES.toBytes(256)) + .setMaxDbs(1) + .open(dir)) { final Dbi db = env.openDbi(DB_1, MDB_CREATE); db.put(bb(1), bb(42)); @@ -471,7 +475,10 @@ void setMapSize() { void stats() { FileUtil.useTempFile( file -> { - try (Env env = create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR)) { + try (Env env = Env.create() + .setMaxReaders(1) + .setEnvFlags(MDB_NOSUBDIR) + .open(file)) { final Stat stat = env.stat(); assertThat(stat).isNotNull(); assertThat(stat.branchPages).isEqualTo(0L); @@ -489,7 +496,7 @@ void stats() { void testDefaultOpen() { FileUtil.useTempDir( dir -> { - try (Env env = open(dir.toFile(), 10)) { + try (Env env = Env.create().setMapSizeMb(10).open(dir)) { final EnvInfo info = env.info(); assertThat(info.maxReaders).isEqualTo(MAX_READERS_DEFAULT); final Dbi db = env.openDbi("test", MDB_CREATE); diff --git a/src/test/java/org/lmdbjava/TutorialTest.java b/src/test/java/org/lmdbjava/TutorialTest.java index 1b42b327..35e2c169 100644 --- a/src/test/java/org/lmdbjava/TutorialTest.java +++ b/src/test/java/org/lmdbjava/TutorialTest.java @@ -345,7 +345,7 @@ void tutorial5() { // This time we're going to tell the Dbi it can store > 1 value per key. // There are other flags available if we're storing integers etc. - final Dbi db = env.openDbi(DB_NAME, MDB_CREATE, MDB_DUPSORT); + final Dbi db = env.openDbi(DB_NAME, DbiFlagSet.of(MDB_CREATE, MDB_DUPSORT)); // Duplicate support requires both keys and values to be <= max key size. final ByteBuffer key = allocateDirect(env.getMaxKeySize()); From 77a3494a4008a516b2d1c3fe517b3171b638fac3 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Wed, 5 Nov 2025 21:35:51 +0000 Subject: [PATCH 32/90] Fix failing test --- src/main/java/org/lmdbjava/Cursor.java | 2 -- src/main/java/org/lmdbjava/Dbi.java | 28 +++++++++---------- src/main/java/org/lmdbjava/DbiBuilder.java | 5 ++-- .../CursorIterableIntegerKeyTest.java | 20 ++++++------- 4 files changed, 25 insertions(+), 30 deletions(-) diff --git a/src/main/java/org/lmdbjava/Cursor.java b/src/main/java/org/lmdbjava/Cursor.java index 127fffc0..245a5a9a 100644 --- a/src/main/java/org/lmdbjava/Cursor.java +++ b/src/main/java/org/lmdbjava/Cursor.java @@ -110,8 +110,6 @@ public void delete(final PutFlags... flags) { } /** - * @deprecated Instead use {@link Cursor#delete(PutFlagSet)}. - *
* Delete current key/data pair. * *

This function deletes the key/data pair to which the cursor refers. diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index 7347605f..65527224 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -49,6 +49,8 @@ */ public final class Dbi { + @SuppressWarnings("FieldCanBeLocal") // Needs to be instance variable for FFI + private final ComparatorCallback callbackComparator; private boolean cleaned; // Used for CursorIterable KeyRange testing and/or native callbacks private final Comparator comparator; @@ -90,26 +92,24 @@ public final class Dbi { final Pointer dbiPtr = allocateDirect(RUNTIME, ADDRESS); checkRc(LIB.mdb_dbi_open(txn.pointer(), name, this.dbiFlagSet.getMask(), dbiPtr)); ptr = dbiPtr.getPointer(0); - ComparatorCallback callbackComparator; if (nativeCb) { // LMDB will call back to this comparator for insertion/iteration order - callbackComparator = createCallbackComparator(proxy); + this.callbackComparator = createCallbackComparator(proxy); LIB.mdb_set_compare(txn.pointer(), ptr, callbackComparator); + } else { + callbackComparator = null; } } - private ComparatorCallback createCallbackComparator(BufferProxy proxy) { - ComparatorCallback callbackComparator; - callbackComparator = - (keyA, keyB) -> { - final T compKeyA = proxy.out(proxy.allocate(), keyA); - final T compKeyB = proxy.out(proxy.allocate(), keyB); - final int result = this.comparator.compare(compKeyA, compKeyB); - proxy.deallocate(compKeyA); - proxy.deallocate(compKeyB); - return result; - }; - return callbackComparator; + private ComparatorCallback createCallbackComparator(final BufferProxy proxy) { + return (keyA, keyB) -> { + final T compKeyA = proxy.out(proxy.allocate(), keyA); + final T compKeyB = proxy.out(proxy.allocate(), keyB); + final int result = this.comparator.compare(compKeyA, compKeyB); + proxy.deallocate(compKeyA); + proxy.deallocate(compKeyB); + return result; + }; } Pointer pointer() { diff --git a/src/main/java/org/lmdbjava/DbiBuilder.java b/src/main/java/org/lmdbjava/DbiBuilder.java index 062e77f4..1abff0a2 100644 --- a/src/main/java/org/lmdbjava/DbiBuilder.java +++ b/src/main/java/org/lmdbjava/DbiBuilder.java @@ -420,9 +420,8 @@ private Comparator getComparator(final DbiBuilder dbiBuilder, break; case CALLBACK: case ITERATOR: - comparator = Objects.requireNonNull( - dbiBuilderStage2.comparatorFactory.create(dbiFlagSet), - () -> "comparatorFactory returned null"); + comparator = dbiBuilderStage2.comparatorFactory.create(dbiFlagSet); + Objects.requireNonNull(comparator, "comparatorFactory returned null"); break; case NATIVE: break; diff --git a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java index a0d9ab0c..87f7a952 100644 --- a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java @@ -234,18 +234,16 @@ public void testIntegerKeyKeySize() { db.put(txn, bbNative(Long.MAX_VALUE), bb("val_" + ++val)); txn.commit(); } - - try (Txn txn = env.txnRead()) { - try (CursorIterable iterable = db.iterate(txn)) { - for (KeyVal keyVal : iterable) { - final String val = getString(keyVal.val()); - final long key = getNativeLong(keyVal.key()); - final int remaining = keyVal.key().remaining(); +// try (Txn txn = env.txnRead()) { +// try (CursorIterable iterable = db.iterate(txn)) { +// for (KeyVal keyVal : iterable) { +// final String val = getString(keyVal.val()); +// final long key = getNativeLong(keyVal.key()); +// final int remaining = keyVal.key().remaining(); // System.out.println("key: " + key + ", val: " + val + ", remaining: " + remaining); - } - } - } - +// } +// } +// } } @Test From 1d46e0b09251767be3f730a37c981535d1a11d5d Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Thu, 6 Nov 2025 10:24:19 +0000 Subject: [PATCH 33/90] Refactor deprecated method calls --- src/main/java/org/lmdbjava/Env.java | 8 +- .../org/lmdbjava/ByteBufferProxyTest.java | 64 +++-- .../CursorIterableIntegerDupTest.java | 42 +-- .../CursorIterableIntegerKeyTest.java | 6 +- .../org/lmdbjava/CursorIterablePerfTest.java | 27 +- .../java/org/lmdbjava/CursorParamTest.java | 4 +- src/test/java/org/lmdbjava/CursorTest.java | 4 +- src/test/java/org/lmdbjava/DbiTest.java | 244 +++++++++--------- src/test/java/org/lmdbjava/EnvTest.java | 83 ++++-- .../org/lmdbjava/GarbageCollectionTest.java | 6 +- src/test/java/org/lmdbjava/TutorialTest.java | 82 +++--- src/test/java/org/lmdbjava/TxnTest.java | 10 +- 12 files changed, 308 insertions(+), 272 deletions(-) diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index e01de06b..7ccebb4e 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -63,10 +63,10 @@ public final class Env implements AutoCloseable { */ public static final boolean SHOULD_CHECK = !getBoolean(DISABLE_CHECKS_PROP); - private static final long KIBIBYTES = 1_024; - private static final long MEBIBYTES = KIBIBYTES * 1_024; - private static final long GIBIBYTES = MEBIBYTES * 1_024; - private static final long TEBIBYTES = GIBIBYTES * 1_024; + private static final long KIBIBYTES = 1_024L; + private static final long MEBIBYTES = KIBIBYTES * 1_024L; + private static final long GIBIBYTES = MEBIBYTES * 1_024L; + private static final long TEBIBYTES = GIBIBYTES * 1_024L; private boolean closed; private final int maxKeySize; diff --git a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java index e035911b..a91fbf75 100644 --- a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java +++ b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java @@ -148,11 +148,6 @@ void unsafeIsDefault() { assertThat(v.getClass().getSimpleName()).startsWith("Unsafe"); } - /** - * For 100 rounds of 5,000,000 comparisons - * compareAsIntegerKeys: PT1.600525631S - * compareLexicographically: PT3.381935001S - */ @Test public void comparatorPerformance() { final Random random = new Random(345098); @@ -160,40 +155,41 @@ public void comparatorPerformance() { final ByteBuffer buffer2 = ByteBuffer.allocateDirect(Long.BYTES); buffer1.limit(Long.BYTES); buffer2.limit(Long.BYTES); - final long[] values = random.longs(20_000_000).toArray(); + final long[] values = random.longs(10_000_000).toArray(); + final int rounds = 100; - Instant time = Instant.now(); - // x is to ensure result is used by the jvm - int x = 0; - for (int rounds = 0; rounds < 100; rounds++) { - for (int i = 1; i < values.length; i++) { -// buffer1.order(ByteOrder.nativeOrder()) - buffer1 - .putLong(0, values[i - 1]); -// buffer2.order(ByteOrder.nativeOrder()) - buffer2 - .putLong(0, values[i]); - final int result = ByteBufferProxy.AbstractByteBufferProxy.compareAsIntegerKeys(buffer1, buffer2); - x += result; + for (int run = 0; run < 3; run++) { + Instant time = Instant.now(); + // x is to ensure result is used by the jvm + int x = 0; + for (int round = 0; round < rounds; round++) { + for (int i = 1; i < values.length; i++) { + buffer1.order(ByteOrder.nativeOrder()) + .putLong(0, values[i - 1]); + buffer2.order(ByteOrder.nativeOrder()) + .putLong(0, values[i]); + final int result = ByteBufferProxy.AbstractByteBufferProxy.compareAsIntegerKeys(buffer1, buffer2); + x += result; + } } - } - System.out.println("compareAsIntegerKeys: " + Duration.between(time, Instant.now()) + ", x: " + x); + System.out.println("compareAsIntegerKeys: " + Duration.between(time, Instant.now()) + ", x: " + x); - time = Instant.now(); - int y = 0; - for (int rounds = 0; rounds < 100; rounds++) { - for (int i = 1; i < values.length; i++) { - buffer1.order(BIG_ENDIAN) - .putLong(0, values[i - 1]); - buffer2.order(BIG_ENDIAN) - .putLong(0, values[i]); - final int result = ByteBufferProxy.AbstractByteBufferProxy.compareLexicographically(buffer1, buffer2); - y += result; + time = Instant.now(); + int y = 0; + for (int round = 0; round < rounds; round++) { + for (int i = 1; i < values.length; i++) { + buffer1.order(BIG_ENDIAN) + .putLong(0, values[i - 1]); + buffer2.order(BIG_ENDIAN) + .putLong(0, values[i]); + final int result = ByteBufferProxy.AbstractByteBufferProxy.compareLexicographically(buffer1, buffer2); + y += result; + } } - } - System.out.println("compareLexicographically: " + Duration.between(time, Instant.now()) + ", y: " + y); + System.out.println("compareLexicographically: " + Duration.between(time, Instant.now()) + ", y: " + y); - assertThat(y).isEqualTo(x); + assertThat(y).isEqualTo(x); + } } @Test diff --git a/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java b/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java index 775ac4bc..1098ca5c 100644 --- a/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java @@ -44,7 +44,6 @@ import static org.lmdbjava.TestUtils.DB_2; import static org.lmdbjava.TestUtils.DB_3; import static org.lmdbjava.TestUtils.DB_4; -import static org.lmdbjava.TestUtils.POSIX_MODE; import static org.lmdbjava.TestUtils.bb; import static org.lmdbjava.TestUtils.bbNative; import static org.lmdbjava.TestUtils.getNativeInt; @@ -63,7 +62,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.NoSuchElementException; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -87,7 +85,7 @@ */ @Disabled // Waiting for the merge of stroomdev66's cursor tests @ParameterizedClass(name = "{index}: dbi: {0}") -@ArgumentsSource(CursorIterableTest.MyArgumentProvider.class) +@ArgumentsSource(CursorIterableIntegerDupTest.MyArgumentProvider.class) public final class CursorIterableIntegerDupTest { private static final DbiFlagSet DBI_FLAGS = DbiFlagSet.of( @@ -128,7 +126,8 @@ public void before() throws IOException { .setMapSize(KIBIBYTES.toBytes(256)) .setMaxReaders(1) .setMaxDbs(3) - .open(file.toFile(), POSIX_MODE, MDB_NOSUBDIR); + .setEnvFlags(MDB_NOSUBDIR) + .open(file); populateExpectedEntriesDeque(); } @@ -187,16 +186,15 @@ private void populateDatabase(final Dbi dbi) { } txn.commit(); } - - try (Txn txn = env.txnRead(); - CursorIterable c = dbi.iterate(txn)) { - +// try (Txn txn = env.txnRead(); +// CursorIterable c = dbi.iterate(txn)) { +// // for (final KeyVal kv : c) { // System.out.print(getNativeInt(kv.key()) + " => " + kv.val().getInt()); // System.out.print(", "); // } // System.out.println(); - } +// } } private int[] rangeInc(final int fromInc, final int toInc) { @@ -278,6 +276,7 @@ public void iterate() { for (final KeyVal kv : c) { final Map.Entry entry = expectedEntriesDeque.pollFirst(); + assertThat(entry).isNotNull(); // System.out.println(entry.getKey() + " => " + entry.getValue()); assertThat(getNativeInt(kv.key())).isEqualTo(entry.getKey()); assertThat(kv.val().getInt()).isEqualTo(entry.getValue()); @@ -308,24 +307,6 @@ public void lessThanTest() { verify(lessThan(bbNative(8)), 2, 4, 6); } - public void nextThrowsNoSuchElementExceptionIfNoMoreElements() { - Assertions.assertThatThrownBy(() -> { - populateExpectedEntriesDeque(); - final Dbi db = getDb(); - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { - final Iterator> i = c.iterator(); - while (i.hasNext()) { - final KeyVal kv = i.next(); - assertThat(getNativeInt(kv.key())).isEqualTo(expectedEntriesDeque.pollFirst().getKey()); - assertThat(kv.val().getInt()).isEqualTo(expectedEntriesDeque.pollFirst().getValue()); - } - assertThat(i.hasNext()).isEqualTo(false); - i.next(); - } - }).isInstanceOf(NoSuchElementException.class); - } - @Test public void openBackwardTest() { verify(openBackward(bbNative(7), bbNative(2)), 6, 4); @@ -355,7 +336,12 @@ public void openClosedBackwardTestWithGuava() { bb2.reset(); return guava.compare(array1, array2); }; - final Dbi guavaDbi = env.openDbi(DB_1, comparator, MDB_CREATE); + + final Dbi guavaDbi = env.buildDbi() + .setDbName(DB_1).withIteratorComparator(ignored -> comparator) + .setDbiFlags(MDB_CREATE) + .open(); + populateDatabase(guavaDbi); verify(openClosedBackward(bbNative(7), bbNative(2)), guavaDbi, 6, 4, 2); verify(openClosedBackward(bbNative(8), bbNative(4)), guavaDbi, 6, 4); diff --git a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java index 87f7a952..007b5e29 100644 --- a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java @@ -432,7 +432,11 @@ public void openClosedBackwardTestWithGuava() { bb2.reset(); return guava.compare(array1, array2); }; - final Dbi guavaDbi = env.openDbi(DB_1, comparator, MDB_CREATE); + final Dbi guavaDbi = env.buildDbi() + .setDbName(DB_1) + .withIteratorComparator(ignored -> comparator) + .setDbiFlags(MDB_CREATE) + .open(); populateDatabase(guavaDbi); verify(openClosedBackward(bbNative(7), bbNative(2)), guavaDbi, 6, 4, 2); verify(openClosedBackward(bbNative(8), bbNative(4)), guavaDbi, 6, 4); diff --git a/src/test/java/org/lmdbjava/CursorIterablePerfTest.java b/src/test/java/org/lmdbjava/CursorIterablePerfTest.java index e0a40fd9..80555966 100644 --- a/src/test/java/org/lmdbjava/CursorIterablePerfTest.java +++ b/src/test/java/org/lmdbjava/CursorIterablePerfTest.java @@ -21,7 +21,6 @@ import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; import static org.lmdbjava.PutFlags.MDB_APPEND; import static org.lmdbjava.PutFlags.MDB_NOOVERWRITE; -import static org.lmdbjava.TestUtils.POSIX_MODE; import static org.lmdbjava.TestUtils.bb; import java.io.IOException; @@ -39,17 +38,12 @@ public class CursorIterablePerfTest { - // private static final int ITERATIONS = 5_000_000; private static final int ITERATIONS = 100_000; - // private static final int ITERATIONS = 10; - private Path file; - private Dbi dbJavaComparator; - private Dbi dbLmdbComparator; - private Dbi dbCallbackComparator; - private List> dbs = new ArrayList<>(); + private final List> dbs = new ArrayList<>(); + private final List data = new ArrayList<>(ITERATIONS); private Env env; - private List data = new ArrayList<>(ITERATIONS); + private Path file; @BeforeEach public void before() throws IOException { @@ -60,24 +54,25 @@ public void before() throws IOException { .setMapSize(GIBIBYTES.toBytes(1)) .setMaxReaders(1) .setMaxDbs(3) - .open(file.toFile(), POSIX_MODE, MDB_NOSUBDIR); + .setEnvFlags(MDB_NOSUBDIR) + .open(file); final DbiFlagSet dbiFlagSet = MDB_CREATE; // Use a java comparator for start/stop keys only - dbJavaComparator = env.buildDbi() + Dbi dbJavaComparator = env.buildDbi() .setDbName("JavaComparator") .withDefaultComparator() .setDbiFlags(dbiFlagSet) .open(); // Use LMDB comparator for start/stop keys - dbLmdbComparator = env.buildDbi() + Dbi dbLmdbComparator = env.buildDbi() .setDbName("LmdbComparator") .withNativeComparator() .setDbiFlags(dbiFlagSet) .open(); // Use a java comparator for start/stop keys and as a callback comparator - dbCallbackComparator = env.buildDbi() + Dbi dbCallbackComparator = env.buildDbi() .setDbName("CallBackComparator") .withCallbackComparator(bufferProxy::getComparator) .setDbiFlags(dbiFlagSet) @@ -171,14 +166,14 @@ public void comparePerf(final boolean randomOrder) { for (int round = 0; round < 3; round++) { System.out.println("round: " + round + " -----------------------------------------"); for (final Dbi db : dbs) { - final String dbName = new String(db.getName(), StandardCharsets.UTF_8); + final String dbName = db.getNameAsString(); final Instant start = Instant.now(); int cnt = 0; // Exercise the stop key comparator on every entry try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn, keyRange)) { - for (final CursorIterable.KeyVal kv : c) { + CursorIterable cursorIterable = db.iterate(txn, keyRange)) { + for (final CursorIterable.KeyVal ignored : cursorIterable) { cnt++; } } diff --git a/src/test/java/org/lmdbjava/CursorParamTest.java b/src/test/java/org/lmdbjava/CursorParamTest.java index 5b701382..4dd52b6f 100644 --- a/src/test/java/org/lmdbjava/CursorParamTest.java +++ b/src/test/java/org/lmdbjava/CursorParamTest.java @@ -37,7 +37,6 @@ import static org.lmdbjava.SeekOp.MDB_NEXT; import static org.lmdbjava.SeekOp.MDB_PREV; import static org.lmdbjava.TestUtils.DB_1; -import static org.lmdbjava.TestUtils.POSIX_MODE; import static org.lmdbjava.TestUtils.bb; import static org.lmdbjava.TestUtils.mdb; import static org.lmdbjava.TestUtils.nb; @@ -162,7 +161,8 @@ private Env env(final Path tmp) { .setMapSize(MEBIBYTES.toBytes(1)) .setMaxReaders(1) .setMaxDbs(1) - .open(tmp.resolve("db").toFile(), POSIX_MODE, MDB_NOSUBDIR); + .setEnvFlags(MDB_NOSUBDIR) + .open(tmp.resolve("db")); } } diff --git a/src/test/java/org/lmdbjava/CursorTest.java b/src/test/java/org/lmdbjava/CursorTest.java index 41893ae6..9126026f 100644 --- a/src/test/java/org/lmdbjava/CursorTest.java +++ b/src/test/java/org/lmdbjava/CursorTest.java @@ -37,7 +37,6 @@ import static org.lmdbjava.SeekOp.MDB_LAST; import static org.lmdbjava.SeekOp.MDB_NEXT; import static org.lmdbjava.TestUtils.DB_1; -import static org.lmdbjava.TestUtils.POSIX_MODE; import static org.lmdbjava.TestUtils.bb; import java.nio.ByteBuffer; @@ -65,7 +64,8 @@ void beforeEach() { .setMapSize(MEBIBYTES.toBytes(1)) .setMaxReaders(1) .setMaxDbs(1) - .open(file.toFile(), POSIX_MODE, MDB_NOSUBDIR); + .setEnvFlags(MDB_NOSUBDIR) + .open(file); } @AfterEach diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 3e876f67..95da87a4 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -108,16 +108,16 @@ void afterEach() { @Test void close() { assertThatThrownBy( - () -> { - final Dbi db = env.buildDbi() - .setDbName(DB_1) - .withDefaultComparator() - .addDbiFlag(MDB_CREATE) - .open(); - db.put(bb(1), bb(42)); - db.close(); - db.put(bb(2), bb(42)); // error - }) + () -> { + final Dbi db = env.buildDbi() + .setDbName(DB_1) + .withDefaultComparator() + .addDbiFlag(MDB_CREATE) + .open(); + db.put(bb(1), bb(42)); + db.close(); + db.put(bb(2), bb(42)); // error + }) .isInstanceOf(ConstantDerivedException.class); } @@ -152,7 +152,11 @@ private void doCustomComparator( Comparator comparator, IntFunction serializer, ToIntFunction deserializer) { - final Dbi db = env.openDbi(DB_1, comparator, true, MDB_CREATE); + final Dbi db = env.buildDbi() + .setDbName(DB_1) + .withCallbackComparator(ignored -> comparator) + .setDbiFlags(MDB_CREATE) + .open(); try (Txn txn = env.txnWrite()) { assertThat(db.put(txn, serializer.apply(2), serializer.apply(3))).isTrue(); assertThat(db.put(txn, serializer.apply(4), serializer.apply(6))).isTrue(); @@ -172,11 +176,11 @@ private void doCustomComparator( @Test void dbOpenMaxDatabases() { assertThatThrownBy( - () -> { - env.openDbi("db1 is OK", MDB_CREATE); - env.openDbi("db2 is OK", MDB_CREATE); - env.openDbi("db3 fails", MDB_CREATE); - }) + () -> { + env.openDbi("db1 is OK", MDB_CREATE); + env.openDbi("db2 is OK", MDB_CREATE); + env.openDbi("db3 fails", MDB_CREATE); + }) .isInstanceOf(DbFullException.class); } @@ -197,49 +201,56 @@ private void doDbiWithComparatorThreadSafety( Supplier> comparatorSupplier, IntFunction serializer, ToIntFunction deserializer) { - final DbiFlags[] flags = new DbiFlags[]{MDB_CREATE, MDB_INTEGERKEY}; - final Comparator c = comparatorSupplier.get(); - final Dbi db = env.openDbi(DB_1, c, true, flags); - - final List keys = range(0, 1_000).boxed().collect(toList()); - - final ExecutorService pool = Executors.newCachedThreadPool(); - final AtomicBoolean proceed = new AtomicBoolean(true); - final Future reader = - pool.submit( - () -> { - while (proceed.get()) { - try (Txn txn = env.txnRead()) { - db.get(txn, serializer.apply(50)); + final DbiFlagSet flags = DbiFlagSet.of(MDB_CREATE, MDB_INTEGERKEY); + final Comparator comparator = comparatorSupplier.get(); + final Dbi db = env.buildDbi() + .setDbName(DB_1) + .withCallbackComparator(ignored -> comparator) + .setDbiFlags(flags) + .open(); + + final List keys = range(0, 1_000) + .boxed() + .collect(toList()); + + try (ExecutorService pool = Executors.newCachedThreadPool()) { + final AtomicBoolean proceed = new AtomicBoolean(true); + final Future reader = + pool.submit( + () -> { + while (proceed.get()) { + try (Txn txn = env.txnRead()) { + db.get(txn, serializer.apply(50)); + } } - } - }); + }); - for (final Integer key : keys) { - try (Txn txn = env.txnWrite()) { - db.put(txn, serializer.apply(key), serializer.apply(3)); - txn.commit(); + for (final Integer key : keys) { + try (Txn txn = env.txnWrite()) { + db.put(txn, serializer.apply(key), serializer.apply(3)); + txn.commit(); + } } - } - try (Txn txn = env.txnRead(); - CursorIterable ci = db.iterate(txn)) { - final Iterator> iter = ci.iterator(); - final List result = new ArrayList<>(); - while (iter.hasNext()) { - result.add(deserializer.applyAsInt(iter.next().key())); - } + try (Txn txn = env.txnRead(); + CursorIterable ci = db.iterate(txn)) { + final Iterator> iter = ci.iterator(); + final List result = new ArrayList<>(); + while (iter.hasNext()) { + result.add(deserializer.applyAsInt(iter.next().key())); + } - assertThat(result).contains(keys.toArray(new Integer[0])); - } + assertThat(result).contains(keys.toArray(new Integer[0])); + } - proceed.set(false); - try { - reader.get(1, SECONDS); - pool.shutdown(); - pool.awaitTermination(1, SECONDS); - } catch (ExecutionException | InterruptedException | TimeoutException e) { - throw new IllegalStateException(e); + proceed.set(false); + try { + reader.get(1, SECONDS); + pool.shutdown(); + pool.awaitTermination(1, SECONDS); + } catch (ExecutionException | InterruptedException | TimeoutException e) { + throw new IllegalStateException(e); + } } } @@ -280,7 +291,7 @@ void dropAndDelete() { @Test void dropAndDeleteAnonymousDb() { env.openDbi(DB_1, MDB_CREATE); - final Dbi nameDb = env.openDbi((byte[]) null); + final Dbi nameDb = env.openDbi((byte[]) null, DbiFlagSet.EMPTY); final byte[] dbNameBytes = DB_1.getBytes(UTF_8); final ByteBuffer dbNameBuffer = allocateDirect(dbNameBytes.length); dbNameBuffer.put(dbNameBytes).flip(); @@ -380,12 +391,12 @@ void putCommitGet() { void putCommitGetByteArray() { FileUtil.useTempFile( file -> { - try (Env envBa = - create(PROXY_BA) - .setMapSize(MEBIBYTES.toBytes(64)) - .setMaxReaders(1) - .setMaxDbs(2) - .open(file.toFile(), MDB_NOSUBDIR)) { + try (Env envBa = create(PROXY_BA) + .setMapSize(MEBIBYTES.toBytes(64)) + .setMaxReaders(1) + .setMaxDbs(2) + .setEnvFlags(MDB_NOSUBDIR) + .open(file)) { final Dbi db = envBa.openDbi(DB_1, MDB_CREATE); try (Txn txn = envBa.txnWrite()) { db.put(txn, ba(5), ba(5)); @@ -517,19 +528,19 @@ void stats() { @Test void testMapFullException() { assertThatThrownBy( - () -> { - final Dbi db = env.openDbi(DB_1, MDB_CREATE); - try (Txn txn = env.txnWrite()) { - final ByteBuffer v; - try { - v = allocateDirect(1_024 * 1_024 * 1_024); - } catch (final OutOfMemoryError e) { - // Travis CI OS X build cannot allocate this much memory, so assume OK - throw new MapFullException(); - } - db.put(txn, bb(1), v); - } - }) + () -> { + final Dbi db = env.openDbi(DB_1, MDB_CREATE); + try (Txn txn = env.txnWrite()) { + final ByteBuffer v; + try { + v = allocateDirect(1_024 * 1_024 * 1_024); + } catch (final OutOfMemoryError e) { + // Travis CI OS X build cannot allocate this much memory, so assume OK + throw new MapFullException(); + } + db.put(txn, bb(1), v); + } + }) .isInstanceOf(MapFullException.class); } @@ -554,109 +565,110 @@ void testParallelWritesStress() { @Test void closedEnvRejectsOpenCall() { assertThatThrownBy( - () -> { - env.close(); - env.openDbi(DB_1, MDB_CREATE); - }) + () -> { + env.close(); + env.openDbi(DB_1, MDB_CREATE); + }) .isInstanceOf(AlreadyClosedException.class); } @Test void closedEnvRejectsCloseCall() { assertThatThrownBy( - () -> { - doEnvClosedTest(null, (db, txn) -> db.close()); - }) + () -> { + doEnvClosedTest(null, (db, txn) -> db.close()); + }) .isInstanceOf(AlreadyClosedException.class); } @Test void closedEnvRejectsGetCall() { assertThatThrownBy( - () -> { - doEnvClosedTest( - (db, txn) -> { - final ByteBuffer valBuf = db.get(txn, bb(1)); - assertThat(valBuf.getInt()).isEqualTo(10); - }, - (db, txn) -> db.get(txn, bb(2))); - }) + () -> { + doEnvClosedTest( + (db, txn) -> { + final ByteBuffer valBuf = db.get(txn, bb(1)); + assertThat(valBuf).isNotNull(); + assertThat(valBuf.getInt()).isEqualTo(10); + }, + (db, txn) -> db.get(txn, bb(2))); + }) .isInstanceOf(AlreadyClosedException.class); } @Test void closedEnvRejectsPutCall() { assertThatThrownBy( - () -> { - doEnvClosedTest(null, (db, txn) -> db.put(bb(5), bb(50))); - }) + () -> { + doEnvClosedTest(null, (db, txn) -> db.put(bb(5), bb(50))); + }) .isInstanceOf(AlreadyClosedException.class); } @Test void closedEnvRejectsPutWithTxnCall() { assertThatThrownBy( - () -> { - doEnvClosedTest( - null, - (db, txn) -> { - db.put(txn, bb(5), bb(50)); - }); - }) + () -> { + doEnvClosedTest( + null, + (db, txn) -> { + db.put(txn, bb(5), bb(50)); + }); + }) .isInstanceOf(AlreadyClosedException.class); } @Test void closedEnvRejectsIterateCall() { assertThatThrownBy( - () -> { - doEnvClosedTest(null, Dbi::iterate); - }) + () -> { + doEnvClosedTest(null, Dbi::iterate); + }) .isInstanceOf(AlreadyClosedException.class); } @Test void closedEnvRejectsDropCall() { assertThatThrownBy( - () -> { - doEnvClosedTest(null, Dbi::drop); - }) + () -> { + doEnvClosedTest(null, Dbi::drop); + }) .isInstanceOf(AlreadyClosedException.class); } @Test void closedEnvRejectsDropAndDeleteCall() { assertThatThrownBy( - () -> { - doEnvClosedTest(null, (db, txn) -> db.drop(txn, true)); - }) + () -> { + doEnvClosedTest(null, (db, txn) -> db.drop(txn, true)); + }) .isInstanceOf(AlreadyClosedException.class); } @Test void closedEnvRejectsOpenCursorCall() { assertThatThrownBy( - () -> { - doEnvClosedTest(null, Dbi::openCursor); - }) + () -> { + doEnvClosedTest(null, Dbi::openCursor); + }) .isInstanceOf(AlreadyClosedException.class); } @Test void closedEnvRejectsReserveCall() { assertThatThrownBy( - () -> { - doEnvClosedTest(null, (db, txn) -> db.reserve(txn, bb(1), 32, MDB_NOOVERWRITE)); - }) + () -> { + doEnvClosedTest(null, (db, txn) -> db.reserve(txn, bb(1), 32, MDB_NOOVERWRITE)); + }) .isInstanceOf(AlreadyClosedException.class); } @Test void closedEnvRejectsStatCall() { assertThatThrownBy( - () -> { - doEnvClosedTest(null, Dbi::stat); - }) + () -> { + doEnvClosedTest(null, Dbi::stat); + }) .isInstanceOf(AlreadyClosedException.class); } diff --git a/src/test/java/org/lmdbjava/EnvTest.java b/src/test/java/org/lmdbjava/EnvTest.java index eb925b42..69c20c1b 100644 --- a/src/test/java/org/lmdbjava/EnvTest.java +++ b/src/test/java/org/lmdbjava/EnvTest.java @@ -56,7 +56,8 @@ void byteUnit() { Env.create() .setMaxReaders(1) .setMapSize(MEBIBYTES.toBytes(1)) - .open(file.toFile(), MDB_NOSUBDIR)) { + .setEnvFlags(MDB_NOSUBDIR) + .open(file)) { final EnvInfo info = env.info(); assertThat(info.mapSize).isEqualTo(MEBIBYTES.toBytes(1)); } @@ -69,8 +70,10 @@ void cannotChangeMapSizeAfterOpen() { () -> { FileUtil.useTempFile( file -> { - final Builder builder = Env.create().setMaxReaders(1); - try (Env env = builder.open(file.toFile(), MDB_NOSUBDIR)) { + final Builder builder = Env.create() + .setMaxReaders(1) + .setEnvFlags(MDB_NOSUBDIR); + try (Env ignored = builder.open(file)) { builder.setMapSize(1); } }); @@ -84,8 +87,10 @@ void cannotChangeMaxDbsAfterOpen() { () -> { FileUtil.useTempFile( file -> { - final Builder builder = Env.create().setMaxReaders(1); - try (Env env = builder.open(file.toFile(), MDB_NOSUBDIR)) { + final Builder builder = Env.create() + .setMaxReaders(1) + .setEnvFlags(MDB_NOSUBDIR); + try (Env ignored = builder.open(file)) { builder.setMaxDbs(1); } }); @@ -99,8 +104,10 @@ void cannotChangeMaxReadersAfterOpen() { () -> { FileUtil.useTempFile( file -> { - final Builder builder = Env.create().setMaxReaders(1); - try (Env env = builder.open(file.toFile(), MDB_NOSUBDIR)) { + final Builder builder = Env.create() + .setMaxReaders(1) + .setEnvFlags(MDB_NOSUBDIR); + try (Env ignored = builder.open(file)) { builder.setMaxReaders(1); } }); @@ -114,8 +121,10 @@ void cannotInfoOnceClosed() { () -> { FileUtil.useTempFile( file -> { - final Env env = - Env.create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR); + final Env env = Env.create() + .setMaxReaders(1) + .setEnvFlags(MDB_NOSUBDIR) + .open(file); env.close(); env.info(); }); @@ -129,9 +138,12 @@ void cannotOpenTwice() { () -> { FileUtil.useTempFile( file -> { - final Builder builder = Env.create().setMaxReaders(1); - builder.open(file.toFile(), MDB_NOSUBDIR).close(); - builder.open(file.toFile(), MDB_NOSUBDIR); + final Builder builder = Env.create() + .setMaxReaders(1) + .setEnvFlags(MDB_NOSUBDIR); + builder.open(file).close(); + //noinspection resource // This will fail to open + builder.open(file); }); }) .isInstanceOf(AlreadyOpenException.class); @@ -143,20 +155,33 @@ void cannotOverflowMapSize() { () -> { final Builder builder = Env.create().setMaxReaders(1); final int mb = 1_024 * 1_024; + //noinspection NumericOverflow // Intentional overflow final int size = mb * 2_048; // as per issue 18 builder.setMapSize(size); }) .isInstanceOf(IllegalArgumentException.class); } + @Test + void negativeMapSize() { + assertThatThrownBy( + () -> { + final Builder builder = Env.create().setMaxReaders(1); + builder.setMapSize(-1); + }) + .isInstanceOf(IllegalArgumentException.class); + } + @Test void cannotStatOnceClosed() { assertThatThrownBy( () -> { FileUtil.useTempFile( file -> { - final Env env = - Env.create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR); + final Env env = Env.create() + .setMaxReaders(1) + .setEnvFlags(MDB_NOSUBDIR) + .open(file); env.close(); env.stat(); }); @@ -170,8 +195,10 @@ void cannotSyncOnceClosed() { () -> { FileUtil.useTempFile( file -> { - final Env env = - Env.create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR); + final Env env = Env.create() + .setMaxReaders(1) + .setEnvFlags(MDB_NOSUBDIR) + .open(file); env.close(); env.sync(false); }); @@ -188,7 +215,7 @@ void copyDirectoryBased() { assertThat(FileUtil.count(dest)).isEqualTo(0); FileUtil.useTempDir( src -> { - try (Env env = Env.create().setMaxReaders(1).open(src.toFile())) { + try (Env env = Env.create().setMaxReaders(1).open(src)) { env.copy(dest.toFile(), MDB_CP_COMPACT); assertThat(FileUtil.count(dest)).isEqualTo(1); } @@ -205,7 +232,7 @@ void copyDirectoryRejectsFileDestination() { FileUtil.deleteDir(dest); FileUtil.useTempDir( src -> { - try (Env env = Env.create().setMaxReaders(1).open(src.toFile())) { + try (Env env = Env.create().setMaxReaders(1).open(src)) { env.copy(dest.toFile(), MDB_CP_COMPACT); } }); @@ -225,7 +252,7 @@ void copyDirectoryRejectsMissingDestination() { FileUtil.useTempDir( src -> { try (Env env = - Env.create().setMaxReaders(1).open(src.toFile())) { + Env.create().setMaxReaders(1).open(src)) { env.copy(dest.toFile(), MDB_CP_COMPACT); } }); @@ -250,7 +277,7 @@ void copyDirectoryRejectsNonEmptyDestination() { FileUtil.useTempDir( src -> { try (Env env = - Env.create().setMaxReaders(1).open(src.toFile())) { + Env.create().setMaxReaders(1).open(src)) { env.copy(dest.toFile(), MDB_CP_COMPACT); } }); @@ -271,7 +298,7 @@ void copyFileBased() { FileUtil.useTempFile( src -> { try (Env env = - Env.create().setMaxReaders(1).open(src.toFile(), MDB_NOSUBDIR)) { + Env.create().setMaxReaders(1).setEnvFlags(MDB_NOSUBDIR).open(src)) { env.copy(dest.toFile(), MDB_CP_COMPACT); } assertThat(FileUtil.size(dest)).isGreaterThan(0L); @@ -289,7 +316,7 @@ void copyFileRejectsExistingDestination() { FileUtil.useTempFile( src -> { try (Env env = - Env.create().setMaxReaders(1).open(src.toFile(), MDB_NOSUBDIR)) { + Env.create().setMaxReaders(1).setEnvFlags(MDB_NOSUBDIR).open(src)) { env.copy(dest.toFile(), MDB_CP_COMPACT); } }); @@ -302,7 +329,7 @@ void copyFileRejectsExistingDestination() { void createAsDirectory() { FileUtil.useTempDir( dest -> { - final Env env = Env.create().setMaxReaders(1).open(dest.toFile()); + final Env env = Env.create().setMaxReaders(1).open(dest); assertThat(Files.isDirectory(dest)).isTrue(); env.sync(false); env.close(); @@ -320,7 +347,8 @@ void createAsFile() { .setMapSize(MEBIBYTES.toBytes(1)) .setMaxDbs(1) .setMaxReaders(1) - .open(file.toFile(), MDB_NOSUBDIR)) { + .setEnvFlags(MDB_NOSUBDIR) + .open(file)) { env.sync(true); assertThat(Files.isRegularFile(file)).isTrue(); } @@ -334,7 +362,7 @@ void detectTransactionThreadViolation() { FileUtil.useTempFile( file -> { try (Env env = - Env.create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR)) { + Env.create().setMaxReaders(1).setEnvFlags(MDB_NOSUBDIR).open(file)) { env.txnRead(); env.txnRead(); } @@ -348,7 +376,7 @@ void info() { FileUtil.useTempFile( file -> { try (Env env = - Env.create().setMaxReaders(4).setMapSize(123_456).open(file.toFile(), MDB_NOSUBDIR)) { + Env.create().setMaxReaders(4).setMapSize(123_456).setEnvFlags(MDB_NOSUBDIR).open(file)) { final EnvInfo info = env.info(); assertThat(info).isNotNull(); assertThat(info.lastPageNumber).isEqualTo(1L); @@ -378,8 +406,9 @@ void mapFull() { .setMaxReaders(1) .setMapSize(MEBIBYTES.toBytes(8)) .setMaxDbs(1) - .open(dir.toFile())) { + .open(dir)) { final Dbi db = env.openDbi(DB_1, MDB_CREATE); + //noinspection InfiniteLoopStatement // Needs infinite loop to fill the env for (; ; ) { rnd.nextBytes(k); key.clear(); diff --git a/src/test/java/org/lmdbjava/GarbageCollectionTest.java b/src/test/java/org/lmdbjava/GarbageCollectionTest.java index f0aa64e4..21ef5182 100644 --- a/src/test/java/org/lmdbjava/GarbageCollectionTest.java +++ b/src/test/java/org/lmdbjava/GarbageCollectionTest.java @@ -37,8 +37,10 @@ public class GarbageCollectionTest { void buffersNotGarbageCollectedTest() { FileUtil.useTempDir( dir -> { - try (Env env = - create().setMapSize(2_085_760_999).setMaxDbs(1).open(dir.toFile())) { + try (Env env = create() + .setMapSize(2_085_760_999) + .setMaxDbs(1) + .open(dir)) { final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); try (Txn txn = env.txnWrite()) { diff --git a/src/test/java/org/lmdbjava/TutorialTest.java b/src/test/java/org/lmdbjava/TutorialTest.java index 35e2c169..93c4a031 100644 --- a/src/test/java/org/lmdbjava/TutorialTest.java +++ b/src/test/java/org/lmdbjava/TutorialTest.java @@ -16,16 +16,13 @@ package org.lmdbjava; -import static java.nio.ByteBuffer.allocateDirect; import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.TimeUnit.SECONDS; import static org.assertj.core.api.Assertions.assertThat; import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.DbiFlags.MDB_DUPSORT; import static org.lmdbjava.DirectBufferProxy.PROXY_DB; -import static org.lmdbjava.Env.create; import static org.lmdbjava.GetOp.MDB_SET; import static org.lmdbjava.SeekOp.MDB_FIRST; import static org.lmdbjava.SeekOp.MDB_LAST; @@ -34,6 +31,7 @@ import java.nio.ByteBuffer; import java.nio.file.Path; import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import org.agrona.DirectBuffer; import org.agrona.MutableDirectBuffer; import org.agrona.concurrent.UnsafeBuffer; @@ -57,7 +55,9 @@ public final class TutorialTest { private static final String DB_NAME = "my DB"; - /** In this first tutorial we will use LmdbJava with some basic defaults. */ + /** + * In this first tutorial we will use LmdbJava with some basic defaults. + */ @Test void tutorial1() { // We need a storage directory first. @@ -67,16 +67,15 @@ void tutorial1() { // We always need an Env. An Env owns a physical on-disk storage file. One // Env can store many different databases (ie sorted maps). - final Env env = - create() - // LMDB also needs to know how large our DB might be. Over-estimating is OK. - .setMapSize(10_485_760) - // LMDB also needs to know how many DBs (Dbi) we want to store in this Env. - .setMaxDbs(1) - // Now let's open the Env. The same path can be concurrently opened and - // used in different processes, but do not open the same path twice in - // the same process at the same time. - .open(dir.toFile()); + final Env env = Env.create() + // LMDB also needs to know how large our DB might be. Over-estimating is OK. + .setMapSize(10_485_760) + // LMDB also needs to know how many DBs (Dbi) we want to store in this Env. + .setMaxDbs(1) + // Now let's open the Env. The same path can be concurrently opened and + // used in different processes, but do not open the same path twice in + // the same process at the same time. + .open(dir); // We need a Dbi for each DB. A Dbi roughly equates to a sorted map. The // MDB_CREATE flag causes the DB to be created if it doesn't already exist. @@ -85,8 +84,8 @@ void tutorial1() { // We want to store some data, so we will need a direct ByteBuffer. // Note that LMDB keys cannot exceed maxKeySize bytes (511 bytes by default). // Values can be larger. - final ByteBuffer key = allocateDirect(env.getMaxKeySize()); - final ByteBuffer val = allocateDirect(700); + final ByteBuffer key = ByteBuffer.allocateDirect(env.getMaxKeySize()); + final ByteBuffer val = ByteBuffer.allocateDirect(700); key.put("greeting".getBytes(UTF_8)).flip(); val.put("Hello world".getBytes(UTF_8)).flip(); final int valSize = val.remaining(); @@ -125,7 +124,9 @@ void tutorial1() { }); } - /** In this second tutorial we'll learn more about LMDB's ACID Txns. */ + /** + * In this second tutorial we'll learn more about LMDB's ACID Txns. + */ @Test void tutorial2() { FileUtil.useTempDir( @@ -133,8 +134,8 @@ void tutorial2() { try { final Env env = createSimpleEnv(dir); final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); - final ByteBuffer key = allocateDirect(env.getMaxKeySize()); - final ByteBuffer val = allocateDirect(700); + final ByteBuffer key = ByteBuffer.allocateDirect(env.getMaxKeySize()); + final ByteBuffer val = ByteBuffer.allocateDirect(700); // Let's write and commit "key1" via a Txn. A Txn can include multiple Dbis. // Note write Txns block other write Txns, due to writes being serialized. @@ -163,7 +164,7 @@ void tutorial2() { // typically permitted (the exception is a read-only Env with MDB_NOTLS). // // Let's write out a "key2" via a new write Txn in a different thread. - final ExecutorService es = newCachedThreadPool(); + final ExecutorService es = Executors.newCachedThreadPool(); es.execute( () -> { try (Txn txn = env.txnWrite()) { @@ -211,8 +212,8 @@ void tutorial3() { dir -> { final Env env = createSimpleEnv(dir); final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); - final ByteBuffer key = allocateDirect(env.getMaxKeySize()); - final ByteBuffer val = allocateDirect(700); + final ByteBuffer key = ByteBuffer.allocateDirect(env.getMaxKeySize()); + final ByteBuffer val = ByteBuffer.allocateDirect(700); try (Txn txn = env.txnWrite()) { // A cursor always belongs to a particular Dbi. @@ -288,8 +289,8 @@ void tutorial4() { final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); try (Txn txn = env.txnWrite()) { - final ByteBuffer key = allocateDirect(env.getMaxKeySize()); - final ByteBuffer val = allocateDirect(700); + final ByteBuffer key = ByteBuffer.allocateDirect(env.getMaxKeySize()); + final ByteBuffer val = ByteBuffer.allocateDirect(700); // Insert some data. Note that ByteBuffer order defaults to Big Endian. // LMDB does not persist the byte order, but it's critical to sort keys. @@ -336,7 +337,9 @@ void tutorial4() { }); } - /** In this fifth tutorial we'll explore multiple values sharing a single key. */ + /** + * In this fifth tutorial we'll explore multiple values sharing a single key. + */ @Test void tutorial5() { FileUtil.useTempDir( @@ -348,8 +351,8 @@ void tutorial5() { final Dbi db = env.openDbi(DB_NAME, DbiFlagSet.of(MDB_CREATE, MDB_DUPSORT)); // Duplicate support requires both keys and values to be <= max key size. - final ByteBuffer key = allocateDirect(env.getMaxKeySize()); - final ByteBuffer val = allocateDirect(env.getMaxKeySize()); + final ByteBuffer key = ByteBuffer.allocateDirect(env.getMaxKeySize()); + final ByteBuffer val = ByteBuffer.allocateDirect(env.getMaxKeySize()); try (Txn txn = env.txnWrite()) { final Cursor c = db.openCursor(txn); @@ -396,11 +399,10 @@ void tutorial6() { FileUtil.useTempDir( dir -> { // Note we need to specify the Verifier's DBI_COUNT for the Env. - final Env env = - create(PROXY_OPTIMAL) + final Env env = Env.create(PROXY_OPTIMAL) .setMapSize(10_485_760) .setMaxDbs(Verifier.DBI_COUNT) - .open(dir.toFile()); + .open(dir); // Create a Verifier (it's a Callable for those needing full control). final Verifier v = new Verifier(env); @@ -413,7 +415,9 @@ void tutorial6() { }); } - /** In this final tutorial we'll look at using Agrona's DirectBuffer. */ + /** + * In this final tutorial we'll look at using Agrona's DirectBuffer. + */ @Test void tutorial7() { FileUtil.useTempDir( @@ -421,14 +425,16 @@ void tutorial7() { // The critical difference is we pass the PROXY_DB field to Env.create(). // There's also a PROXY_SAFE if you want to stop ByteBuffer's Unsafe use. // Aside from that and a different type argument, it's the same as usual... - final Env env = - create(PROXY_DB).setMapSize(10_485_760).setMaxDbs(1).open(dir.toFile()); + final Env env = Env.create(PROXY_DB) + .setMapSize(10_485_760) + .setMaxDbs(1) + .open(dir); final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); - final ByteBuffer keyBb = allocateDirect(env.getMaxKeySize()); + final ByteBuffer keyBb = ByteBuffer.allocateDirect(env.getMaxKeySize()); final MutableDirectBuffer key = new UnsafeBuffer(keyBb); - final MutableDirectBuffer val = new UnsafeBuffer(allocateDirect(700)); + final MutableDirectBuffer val = new UnsafeBuffer(ByteBuffer.allocateDirect(700)); try (Txn txn = env.txnWrite()) { try (Cursor c = db.openCursor(txn)) { @@ -479,6 +485,10 @@ void tutorial7() { // or reverse ordered keys, using Env.DISABLE_CHECKS_PROP etc), but you now // know enough to tackle the JavaDocs with confidence. Have fun! private Env createSimpleEnv(final Path path) { - return create().setMapSize(10_485_760).setMaxDbs(1).setMaxReaders(1).open(path.toFile()); + return Env.create() + .setMapSize(10_485_760) + .setMaxDbs(1) + .setMaxReaders(1) + .open(path); } } diff --git a/src/test/java/org/lmdbjava/TxnTest.java b/src/test/java/org/lmdbjava/TxnTest.java index 46ffeb70..f81b525e 100644 --- a/src/test/java/org/lmdbjava/TxnTest.java +++ b/src/test/java/org/lmdbjava/TxnTest.java @@ -27,7 +27,6 @@ import static org.lmdbjava.EnvFlags.MDB_RDONLY_ENV; import static org.lmdbjava.KeyRange.closed; import static org.lmdbjava.TestUtils.DB_1; -import static org.lmdbjava.TestUtils.POSIX_MODE; import static org.lmdbjava.TestUtils.bb; import static org.lmdbjava.Txn.State.DONE; import static org.lmdbjava.Txn.State.READY; @@ -68,7 +67,8 @@ void beforeEach() { .setMapSize(KIBIBYTES.toBytes(256)) .setMaxReaders(1) .setMaxDbs(2) - .open(file.toFile(), POSIX_MODE, MDB_NOSUBDIR); + .setEnvFlags(MDB_NOSUBDIR) + .open(file); } @AfterEach @@ -126,8 +126,10 @@ void rangeSearch() { @Test void readOnlyTxnAllowedInReadOnlyEnv() { env.openDbi(DB_1, MDB_CREATE); - try (Env roEnv = - create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR, MDB_RDONLY_ENV)) { + try (Env roEnv = create() + .setMaxReaders(1) + .setEnvFlags(MDB_NOSUBDIR, MDB_RDONLY_ENV) + .open(file)) { assertThat(roEnv.txnRead()).isNotNull(); } } From 577062f6d5df8e0cde054d11334e507419c63414 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Thu, 6 Nov 2025 10:33:06 +0000 Subject: [PATCH 34/90] Fix compile issue --- .../org/lmdbjava/AbstractFlagSetTest.java | 20 +++++ src/test/java/org/lmdbjava/DbiTest.java | 75 ++++++++++--------- 2 files changed, 58 insertions(+), 37 deletions(-) create mode 100644 src/test/java/org/lmdbjava/AbstractFlagSetTest.java diff --git a/src/test/java/org/lmdbjava/AbstractFlagSetTest.java b/src/test/java/org/lmdbjava/AbstractFlagSetTest.java new file mode 100644 index 00000000..f986fa7f --- /dev/null +++ b/src/test/java/org/lmdbjava/AbstractFlagSetTest.java @@ -0,0 +1,20 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +public abstract class AbstractFlagSetTest { + +} diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 95da87a4..6d86bd85 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -213,44 +213,45 @@ private void doDbiWithComparatorThreadSafety( .boxed() .collect(toList()); - try (ExecutorService pool = Executors.newCachedThreadPool()) { - final AtomicBoolean proceed = new AtomicBoolean(true); - final Future reader = - pool.submit( - () -> { - while (proceed.get()) { - try (Txn txn = env.txnRead()) { - db.get(txn, serializer.apply(50)); - } + // TODO surround with try-with-resources in J19+ + //noinspection resource // Not in J8 + ExecutorService pool = Executors.newCachedThreadPool(); + final AtomicBoolean proceed = new AtomicBoolean(true); + final Future reader = + pool.submit( + () -> { + while (proceed.get()) { + try (Txn txn = env.txnRead()) { + db.get(txn, serializer.apply(50)); } - }); + } + }); - for (final Integer key : keys) { - try (Txn txn = env.txnWrite()) { - db.put(txn, serializer.apply(key), serializer.apply(3)); - txn.commit(); - } + for (final Integer key : keys) { + try (Txn txn = env.txnWrite()) { + db.put(txn, serializer.apply(key), serializer.apply(3)); + txn.commit(); } + } - try (Txn txn = env.txnRead(); - CursorIterable ci = db.iterate(txn)) { - final Iterator> iter = ci.iterator(); - final List result = new ArrayList<>(); - while (iter.hasNext()) { - result.add(deserializer.applyAsInt(iter.next().key())); - } - - assertThat(result).contains(keys.toArray(new Integer[0])); + try (Txn txn = env.txnRead(); + CursorIterable ci = db.iterate(txn)) { + final Iterator> iter = ci.iterator(); + final List result = new ArrayList<>(); + while (iter.hasNext()) { + result.add(deserializer.applyAsInt(iter.next().key())); } - proceed.set(false); - try { - reader.get(1, SECONDS); - pool.shutdown(); - pool.awaitTermination(1, SECONDS); - } catch (ExecutionException | InterruptedException | TimeoutException e) { - throw new IllegalStateException(e); - } + assertThat(result).contains(keys.toArray(new Integer[0])); + } + + proceed.set(false); + try { + reader.get(1, SECONDS); + pool.shutdown(); + pool.awaitTermination(1, SECONDS); + } catch (ExecutionException | InterruptedException | TimeoutException e) { + throw new IllegalStateException(e); } } @@ -392,11 +393,11 @@ void putCommitGetByteArray() { FileUtil.useTempFile( file -> { try (Env envBa = create(PROXY_BA) - .setMapSize(MEBIBYTES.toBytes(64)) - .setMaxReaders(1) - .setMaxDbs(2) - .setEnvFlags(MDB_NOSUBDIR) - .open(file)) { + .setMapSize(MEBIBYTES.toBytes(64)) + .setMaxReaders(1) + .setMaxDbs(2) + .setEnvFlags(MDB_NOSUBDIR) + .open(file)) { final Dbi db = envBa.openDbi(DB_1, MDB_CREATE); try (Txn txn = envBa.txnWrite()) { db.put(txn, ba(5), ba(5)); From 12a9679899aefe8e27a9af9da4b688189ba0085f Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Thu, 6 Nov 2025 11:31:32 +0000 Subject: [PATCH 35/90] Improve FlagSet tests --- .../java/org/lmdbjava/AbstractFlagSet.java | 12 +- src/main/java/org/lmdbjava/CopyFlagSet.java | 4 +- src/main/java/org/lmdbjava/DbiBuilder.java | 8 +- src/main/java/org/lmdbjava/DbiFlagSet.java | 4 +- src/main/java/org/lmdbjava/Env.java | 8 +- src/main/java/org/lmdbjava/EnvFlagSet.java | 4 +- src/main/java/org/lmdbjava/PutFlagSet.java | 4 +- src/main/java/org/lmdbjava/TxnFlagSet.java | 15 +- .../org/lmdbjava/AbstractFlagSetTest.java | 153 ++++++++++++++++-- .../java/org/lmdbjava/CopyFlagSetTest.java | 85 ++++------ .../java/org/lmdbjava/DbiFlagSetTest.java | 99 ++++-------- .../java/org/lmdbjava/EnvFlagSetTest.java | 99 ++++-------- .../java/org/lmdbjava/PutFlagSetTest.java | 96 ++++------- .../java/org/lmdbjava/TxnFlagSetTest.java | 96 ++++------- 14 files changed, 324 insertions(+), 363 deletions(-) diff --git a/src/main/java/org/lmdbjava/AbstractFlagSet.java b/src/main/java/org/lmdbjava/AbstractFlagSet.java index 058969de..849bbe73 100644 --- a/src/main/java/org/lmdbjava/AbstractFlagSet.java +++ b/src/main/java/org/lmdbjava/AbstractFlagSet.java @@ -278,7 +278,7 @@ protected Builder(final Class type, * @param flags The flags to set in the builder. * @return this builder instance. */ - public Builder withFlags(final Collection flags) { + public Builder setFlags(final Collection flags) { clear(); if (flags != null) { for (E flag : flags) { @@ -295,7 +295,7 @@ public Builder withFlags(final Collection flags) { * @return this builder instance. */ @SafeVarargs - public final Builder withFlags(final E... flags) { + public final Builder setFlags(final E... flags) { clear(); if (flags != null) { for (E flag : flags) { @@ -311,12 +311,12 @@ public final Builder withFlags(final E... flags) { } /** - * Sets a single flag in the builder. + * Adds a single flag in the builder. * * @param flag The flag to set in the builder. * @return this builder instance. */ - public Builder setFlag(final E flag) { + public Builder addFlag(final E flag) { if (flag != null) { enumSet.add(flag); } @@ -324,12 +324,12 @@ public Builder setFlag(final E flag) { } /** - * Sets multiple flag in the builder. + * Adds multiple flag in the builder. * * @param flags The flags to set in the builder. * @return this builder instance. */ - public Builder setFlags(final Collection flags) { + public Builder addFlags(final Collection flags) { if (flags != null) { enumSet.addAll(flags); } diff --git a/src/main/java/org/lmdbjava/CopyFlagSet.java b/src/main/java/org/lmdbjava/CopyFlagSet.java index 5f7901de..e21680dd 100644 --- a/src/main/java/org/lmdbjava/CopyFlagSet.java +++ b/src/main/java/org/lmdbjava/CopyFlagSet.java @@ -34,13 +34,13 @@ static CopyFlagSet of(final CopyFlags dbiFlag) { static CopyFlagSet of(final CopyFlags... CopyFlags) { return builder() - .withFlags(CopyFlags) + .setFlags(CopyFlags) .build(); } static CopyFlagSet of(final Collection CopyFlags) { return builder() - .withFlags(CopyFlags) + .setFlags(CopyFlags) .build(); } diff --git a/src/main/java/org/lmdbjava/DbiBuilder.java b/src/main/java/org/lmdbjava/DbiBuilder.java index 1abff0a2..b478c9df 100644 --- a/src/main/java/org/lmdbjava/DbiBuilder.java +++ b/src/main/java/org/lmdbjava/DbiBuilder.java @@ -300,7 +300,7 @@ public DbiBuilderStage3 setDbiFlags(final DbiFlags... dbiFlags) { if (dbiFlags != null) { Arrays.stream(dbiFlags) .filter(Objects::nonNull) - .forEach(this.flagSetBuilder::setFlag); + .forEach(this.flagSetBuilder::addFlag); } return this; } @@ -322,7 +322,7 @@ public DbiBuilderStage3 setDbiFlags(final DbiFlags... dbiFlags) { public DbiBuilderStage3 setDbiFlags(final DbiFlagSet dbiFlagSet) { flagSetBuilder.clear(); if (dbiFlagSet != null) { - this.flagSetBuilder.withFlags(dbiFlagSet.getFlags()); + this.flagSetBuilder.setFlags(dbiFlagSet.getFlags()); } return this; } @@ -337,7 +337,7 @@ public DbiBuilderStage3 setDbiFlags(final DbiFlagSet dbiFlagSet) { * @return this builder instance. */ public DbiBuilderStage3 addDbiFlag(final DbiFlags dbiFlag) { - this.flagSetBuilder.setFlag(dbiFlag); + this.flagSetBuilder.addFlag(dbiFlag); return this; } @@ -352,7 +352,7 @@ public DbiBuilderStage3 addDbiFlag(final DbiFlags dbiFlag) { */ public DbiBuilderStage3 addDbiFlags(final DbiFlagSet dbiFlagSet) { if (dbiFlagSet != null) { - flagSetBuilder.setFlags(dbiFlagSet.getFlags()); + flagSetBuilder.addFlags(dbiFlagSet.getFlags()); } return this; } diff --git a/src/main/java/org/lmdbjava/DbiFlagSet.java b/src/main/java/org/lmdbjava/DbiFlagSet.java index 5a0bc83e..8ffd2499 100644 --- a/src/main/java/org/lmdbjava/DbiFlagSet.java +++ b/src/main/java/org/lmdbjava/DbiFlagSet.java @@ -40,13 +40,13 @@ static DbiFlagSet of(final DbiFlags dbiFlag) { static DbiFlagSet of(final DbiFlags... DbiFlags) { return builder() - .withFlags(DbiFlags) + .setFlags(DbiFlags) .build(); } static DbiFlagSet of(final Collection DbiFlags) { return builder() - .withFlags(DbiFlags) + .setFlags(DbiFlags) .build(); } diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index 7ccebb4e..542b67c3 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -885,7 +885,7 @@ public Builder setEnvFlags(final EnvFlags... envFlags) { if (envFlags != null) { Arrays.stream(envFlags) .filter(Objects::nonNull) - .forEach(this.flagSetBuilder::setFlag); + .forEach(this.flagSetBuilder::addFlag); } return this; } @@ -901,7 +901,7 @@ public Builder setEnvFlags(final EnvFlags... envFlags) { public Builder setEnvFlags(final EnvFlagSet envFlagSet) { flagSetBuilder.clear(); if (envFlagSet != null) { - this.flagSetBuilder.withFlags(envFlagSet.getFlags()); + this.flagSetBuilder.setFlags(envFlagSet.getFlags()); } return this; } @@ -914,7 +914,7 @@ public Builder setEnvFlags(final EnvFlagSet envFlagSet) { * @return this builder instance. */ public Builder addEnvFlag(final EnvFlags dbiFlag) { - this.flagSetBuilder.setFlag(dbiFlag); + this.flagSetBuilder.addFlag(dbiFlag); return this; } @@ -927,7 +927,7 @@ public Builder addEnvFlag(final EnvFlags dbiFlag) { */ public Builder addEnvFlags(final EnvFlagSet dbiFlagSet) { if (dbiFlagSet != null) { - flagSetBuilder.setFlags(dbiFlagSet.getFlags()); + flagSetBuilder.addFlags(dbiFlagSet.getFlags()); } return this; } diff --git a/src/main/java/org/lmdbjava/EnvFlagSet.java b/src/main/java/org/lmdbjava/EnvFlagSet.java index a3c8d1fa..c104edd9 100644 --- a/src/main/java/org/lmdbjava/EnvFlagSet.java +++ b/src/main/java/org/lmdbjava/EnvFlagSet.java @@ -34,13 +34,13 @@ static EnvFlagSet of(final EnvFlags envFlag) { static EnvFlagSet of(final EnvFlags... EnvFlags) { return builder() - .withFlags(EnvFlags) + .setFlags(EnvFlags) .build(); } static EnvFlagSet of(final Collection EnvFlags) { return builder() - .withFlags(EnvFlags) + .setFlags(EnvFlags) .build(); } diff --git a/src/main/java/org/lmdbjava/PutFlagSet.java b/src/main/java/org/lmdbjava/PutFlagSet.java index 9d3a7288..aaba7cb7 100644 --- a/src/main/java/org/lmdbjava/PutFlagSet.java +++ b/src/main/java/org/lmdbjava/PutFlagSet.java @@ -34,13 +34,13 @@ static PutFlagSet of(final PutFlags putFlag) { static PutFlagSet of(final PutFlags... putFlags) { return builder() - .withFlags(putFlags) + .setFlags(putFlags) .build(); } static PutFlagSet of(final Collection putFlags) { return builder() - .withFlags(putFlags) + .setFlags(putFlags) .build(); } diff --git a/src/main/java/org/lmdbjava/TxnFlagSet.java b/src/main/java/org/lmdbjava/TxnFlagSet.java index 8e6310b3..0943b362 100644 --- a/src/main/java/org/lmdbjava/TxnFlagSet.java +++ b/src/main/java/org/lmdbjava/TxnFlagSet.java @@ -15,6 +15,7 @@ */ package org.lmdbjava; +import java.util.Collection; import java.util.EnumSet; import java.util.Objects; @@ -26,14 +27,20 @@ static TxnFlagSet empty() { return TxnFlagSetImpl.EMPTY; } - static TxnFlagSet of(final TxnFlags putFlag) { - Objects.requireNonNull(putFlag); - return new SingleTxnFlagSet(putFlag); + static TxnFlagSet of(final TxnFlags putflag) { + Objects.requireNonNull(putflag); + return new SingleTxnFlagSet(putflag); } static TxnFlagSet of(final TxnFlags... TxnFlags) { return builder() - .withFlags(TxnFlags) + .setFlags(TxnFlags) + .build(); + } + + static TxnFlagSet of(final Collection txnFlags) { + return builder() + .setFlags(txnFlags) .build(); } diff --git a/src/test/java/org/lmdbjava/AbstractFlagSetTest.java b/src/test/java/org/lmdbjava/AbstractFlagSetTest.java index f986fa7f..e09d72c6 100644 --- a/src/test/java/org/lmdbjava/AbstractFlagSetTest.java +++ b/src/test/java/org/lmdbjava/AbstractFlagSetTest.java @@ -1,20 +1,141 @@ -/* - * Copyright © 2016-2025 The LmdbJava Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ package org.lmdbjava; -public abstract class AbstractFlagSetTest { +import static org.assertj.core.api.Assertions.assertThat; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.junit.jupiter.api.Test; + +public abstract class AbstractFlagSetTest & MaskedFlag & FlagSet, F extends FlagSet> { + + abstract List getAllFlags(); + + abstract F getEmptyFlagSet(); + + abstract AbstractFlagSet.Builder getBuilder(); + + abstract F getFlagSet(final Collection flags); + + abstract F getFlagSet(final T[] flags); + + abstract F getFlagSet(final T flag); + + abstract Class getFlagType(); + + T getFirst() { + return getAllFlags().get(0); + } + + int getFlagCount() { + return getAllFlags().size(); + } + + @Test + void testEmpty() { + final F emptyFlagSet = getEmptyFlagSet(); + assertThat(emptyFlagSet.getMask()) + .isEqualTo(0); + assertThat(emptyFlagSet.getFlags()) + .isEmpty(); + assertThat(emptyFlagSet.isEmpty()) + .isTrue(); + assertThat(emptyFlagSet.size()) + .isEqualTo(0); + assertThat(emptyFlagSet.isSet(getFirst())) + .isFalse(); + assertThat(getBuilder().build().getFlags()) + .isEqualTo(emptyFlagSet.getFlags()); + } + + @Test + void testSingleFlagSet() { + final List allFlags = getAllFlags(); + for (T flag : allFlags) { + final F flagSet = getBuilder() + .addFlag(flag) + .build(); + assertThat(flagSet.getMask()) + .isEqualTo(flag.getMask()); + assertThat(flagSet.getMask()) + .isEqualTo(MaskedFlag.mask(flag)); + assertThat(flagSet.getFlags()) + .containsExactly(flag); + assertThat(flagSet.size()) + .isEqualTo(1); + assertThat(FlagSet.equals(flagSet, flag)) + .isTrue(); + assertThat(FlagSet.equals(flagSet, getFlagSet(flag))) + .isTrue(); + assertThat(FlagSet.equals(flagSet, getFlagSet(flagSet.getFlags()))) + .isTrue(); + assertThat(flagSet.areAnySet(flag)) + .isTrue(); + assertThat(flagSet.isSet(getFirst())) + .isEqualTo(getFirst() == flag); + if (getFirst() == flag) { + assertThat(flagSet.getMask()) + .isEqualTo(MaskedFlag.mask(getFirst())); + } else { + assertThat(flagSet.getMask()) + .isNotEqualTo(MaskedFlag.mask(getFirst())); + } + assertThat(flagSet.toString()) + .isNotNull(); + assertThat(flag.name()) + .isNotNull(); + } + } + + @Test + void testAllFlags() { + final List allFlags = getAllFlags(); + final List flags = new ArrayList<>(allFlags.size()); + final Set masks = new HashSet<>(); + final T firstFlag = getFirst(); + for (T flag : allFlags) { + flags.add(flag); + final F flagSet = getBuilder() + .setFlags(flags) + .build(); + final int flagSetMask = flagSet.getMask(); + + assertThat(masks) + .doesNotContain(flagSetMask); + masks.add(flagSetMask); + assertThat(flagSetMask) + .isEqualTo(MaskedFlag.mask(flags)); + final T[] flagsArr = flags.stream().toArray(this::toArray); + assertThat(flagSetMask) + .isEqualTo(MaskedFlag.mask(flagsArr)); + assertThat(flagSet.getFlags()) + .containsExactlyElementsOf(flags); + assertThat(flagSet) + .isNotEmpty(); + assertThat(FlagSet.equals(flagSet, getBuilder().setFlags(flagsArr).build())) + .isTrue(); + assertThat(FlagSet.equals(flagSet, getFlagSet(flags))) + .isTrue(); + assertThat(FlagSet.equals(flagSet, getFlagSet(flagsArr))) + .isTrue(); + assertThat(flagSet.size()) + .isEqualTo(flags.size()); + assertThat(flagSet.isSet(getFirst())) + .isEqualTo(true); + + final int maskWith = flagSet.getMaskWith(firstFlag); + final List combinedList = new ArrayList<>(flags); + combinedList.add(firstFlag); + assertThat(maskWith) + .isEqualTo(MaskedFlag.mask(combinedList)); + } + } + + private T[] toArray(final int cnt) { + //noinspection unchecked + return (T[]) Array.newInstance(getFlagType(), cnt); + } } diff --git a/src/test/java/org/lmdbjava/CopyFlagSetTest.java b/src/test/java/org/lmdbjava/CopyFlagSetTest.java index 2782fe81..e53c7508 100644 --- a/src/test/java/org/lmdbjava/CopyFlagSetTest.java +++ b/src/test/java/org/lmdbjava/CopyFlagSetTest.java @@ -15,65 +15,46 @@ */ package org.lmdbjava; -import static org.assertj.core.api.Assertions.assertThat; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; -import java.util.Collections; -import java.util.HashSet; -import org.junit.jupiter.api.Test; +public class CopyFlagSetTest extends AbstractFlagSetTest { -public class CopyFlagSetTest { + @Override + List getAllFlags() { + return Arrays.stream(CopyFlags.values()) + .collect(Collectors.toList()); + } + + @Override + CopyFlagSet getEmptyFlagSet() { + return CopyFlagSet.empty(); + } - @Test - public void testEmpty() { - final CopyFlagSet copyFlagSet = CopyFlagSet.empty(); - assertThat(copyFlagSet.getMask()).isEqualTo(0); - assertThat(copyFlagSet.size()).isEqualTo(0); - assertThat(copyFlagSet.isEmpty()).isEqualTo(true); - assertThat(copyFlagSet.isSet(CopyFlags.MDB_CP_COMPACT)).isEqualTo(false); - final CopyFlagSet copyFlagSet2 = CopyFlagSet.builder() - .build(); - assertThat(copyFlagSet).isEqualTo(copyFlagSet2); - assertThat(copyFlagSet).isNotEqualTo(CopyFlagSet.of(CopyFlags.MDB_CP_COMPACT)); - assertThat(copyFlagSet).isNotEqualTo(CopyFlagSet.builder() - .setFlag(CopyFlags.MDB_CP_COMPACT) - .build()); + @Override + AbstractFlagSet.Builder getBuilder() { + return CopyFlagSet.builder(); } - @Test - public void testOf() { - final CopyFlags copyFlag = CopyFlags.MDB_CP_COMPACT; - final CopyFlagSet copyFlagSet = CopyFlagSet.of(copyFlag); - assertThat(copyFlagSet.getMask()).isEqualTo(MaskedFlag.mask(copyFlag)); - assertThat(copyFlagSet.size()).isEqualTo(1); - for (CopyFlags flag : copyFlagSet) { - assertThat(copyFlagSet.isSet(flag)).isEqualTo(true); - } + @Override + CopyFlagSet getFlagSet(Collection flags) { + return CopyFlagSet.of(flags); + } + + @Override + CopyFlagSet getFlagSet(CopyFlags[] flags) { + return CopyFlagSet.of(flags); + } - final CopyFlagSet copyFlagSet2 = CopyFlagSet.builder() - .setFlag(copyFlag) - .build(); - assertThat(copyFlagSet).isEqualTo(copyFlagSet2); + @Override + CopyFlagSet getFlagSet(CopyFlags flag) { + return CopyFlagSet.of(flag); } - @Test - public void testBuilder() { - final CopyFlags copyFlag1 = CopyFlags.MDB_CP_COMPACT; - final CopyFlagSet copyFlagSet = CopyFlagSet.builder() - .setFlag(copyFlag1) - .build(); - assertThat(copyFlagSet.getMask()).isEqualTo(MaskedFlag.mask(copyFlag1)); - assertThat(copyFlagSet.size()).isEqualTo(1); - assertThat(copyFlagSet.isSet(CopyFlags.MDB_CP_COMPACT)).isEqualTo(true); - for (CopyFlags flag : copyFlagSet) { - assertThat(copyFlagSet.isSet(flag)).isEqualTo(true); - } - final CopyFlagSet copyFlagSet2 = CopyFlagSet.builder() - .withFlags(copyFlag1) - .build(); - final CopyFlagSet copyFlagSet3 = CopyFlagSet.builder() - .withFlags(new HashSet<>(Collections.singletonList(copyFlag1))) - .build(); - assertThat(copyFlagSet).isEqualTo(copyFlagSet2); - assertThat(copyFlagSet).isEqualTo(copyFlagSet3); + @Override + Class getFlagType() { + return CopyFlags.class; } } diff --git a/src/test/java/org/lmdbjava/DbiFlagSetTest.java b/src/test/java/org/lmdbjava/DbiFlagSetTest.java index aa66313f..06236497 100644 --- a/src/test/java/org/lmdbjava/DbiFlagSetTest.java +++ b/src/test/java/org/lmdbjava/DbiFlagSetTest.java @@ -15,83 +15,46 @@ */ package org.lmdbjava; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.Arrays; -import java.util.HashSet; -import org.junit.jupiter.api.Test; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +public class DbiFlagSetTest extends AbstractFlagSetTest { + + @Override + List getAllFlags() { + return Arrays.stream(DbiFlags.values()) + .collect(Collectors.toList()); + } -public class DbiFlagSetTest { + @Override + DbiFlagSet getEmptyFlagSet() { + return DbiFlagSet.empty(); + } - @Test - public void testEmpty() { - final DbiFlagSet dbiFlagSet = DbiFlagSet.empty(); - assertThat(dbiFlagSet.getMask()).isEqualTo(0); - assertThat(dbiFlagSet.size()).isEqualTo(0); - assertThat(dbiFlagSet.isEmpty()).isEqualTo(true); - assertThat(dbiFlagSet.isSet(DbiFlags.MDB_REVERSEDUP)).isEqualTo(false); - final DbiFlagSet dbiFlagSet2 = DbiFlagSet.builder() - .build(); - assertThat(dbiFlagSet).isEqualTo(dbiFlagSet2); - assertThat(dbiFlagSet).isNotEqualTo(DbiFlagSet.of(DbiFlags.MDB_CREATE)); - assertThat(dbiFlagSet).isNotEqualTo(DbiFlagSet.of(DbiFlags.MDB_CREATE, DbiFlags.MDB_DUPSORT)); - assertThat(dbiFlagSet).isNotEqualTo(DbiFlagSet.builder() - .setFlag(DbiFlags.MDB_CREATE) - .setFlag(DbiFlags.MDB_DUPFIXED) - .build()); + @Override + AbstractFlagSet.Builder getBuilder() { + return DbiFlagSet.builder(); } - @Test - public void testOf() { - final DbiFlags dbiFlag = DbiFlags.MDB_CREATE; - final DbiFlagSet dbiFlagSet = DbiFlagSet.of(dbiFlag); - assertThat(dbiFlagSet.getMask()).isEqualTo(MaskedFlag.mask(dbiFlag)); - assertThat(dbiFlagSet.size()).isEqualTo(1); - assertThat(dbiFlagSet.isSet(DbiFlags.MDB_REVERSEDUP)).isEqualTo(false); - for (DbiFlags flag : dbiFlagSet) { - assertThat(dbiFlagSet.isSet(flag)).isEqualTo(true); - } + @Override + Class getFlagType() { + return DbiFlags.class; + } - final DbiFlagSet dbiFlagSet2 = DbiFlagSet.builder() - .setFlag(dbiFlag) - .build(); - assertThat(dbiFlagSet).isEqualTo(dbiFlagSet2); + @Override + DbiFlagSet getFlagSet(Collection flags) { + return DbiFlagSet.of(flags); } - @Test - public void testOf2() { - final DbiFlags dbiFlag1 = DbiFlags.MDB_CREATE; - final DbiFlags dbiFlag2 = DbiFlags.MDB_INTEGERKEY; - final DbiFlagSet dbiFlagSet = DbiFlagSet.of(dbiFlag1, dbiFlag2); - assertThat(dbiFlagSet.getMask()).isEqualTo(MaskedFlag.mask(dbiFlag1, dbiFlag2)); - assertThat(dbiFlagSet.size()).isEqualTo(2); - assertThat(dbiFlagSet.isSet(DbiFlags.MDB_REVERSEDUP)).isEqualTo(false); - for (DbiFlags flag : dbiFlagSet) { - assertThat(dbiFlagSet.isSet(flag)).isEqualTo(true); - } + @Override + DbiFlagSet getFlagSet(DbiFlags[] flags) { + return DbiFlagSet.of(flags); } - @Test - public void testBuilder() { - final DbiFlags dbiFlag1 = DbiFlags.MDB_CREATE; - final DbiFlags dbiFlag2 = DbiFlags.MDB_INTEGERKEY; - final DbiFlagSet dbiFlagSet = DbiFlagSet.builder() - .setFlag(dbiFlag1) - .setFlag(dbiFlag2) - .build(); - assertThat(dbiFlagSet.getMask()).isEqualTo(MaskedFlag.mask(dbiFlag1, dbiFlag2)); - assertThat(dbiFlagSet.size()).isEqualTo(2); - assertThat(dbiFlagSet.isSet(DbiFlags.MDB_REVERSEDUP)).isEqualTo(false); - for (DbiFlags flag : dbiFlagSet) { - assertThat(dbiFlagSet.isSet(flag)).isEqualTo(true); - } - final DbiFlagSet dbiFlagSet2 = DbiFlagSet.builder() - .withFlags(dbiFlag1, dbiFlag2) - .build(); - final DbiFlagSet dbiFlagSet3 = DbiFlagSet.builder() - .withFlags(new HashSet<>(Arrays.asList(dbiFlag1, dbiFlag2))) - .build(); - assertThat(dbiFlagSet).isEqualTo(dbiFlagSet2); - assertThat(dbiFlagSet).isEqualTo(dbiFlagSet3); + @Override + DbiFlagSet getFlagSet(DbiFlags flag) { + return DbiFlagSet.of(flag); } } diff --git a/src/test/java/org/lmdbjava/EnvFlagSetTest.java b/src/test/java/org/lmdbjava/EnvFlagSetTest.java index 485aae13..98a033f5 100644 --- a/src/test/java/org/lmdbjava/EnvFlagSetTest.java +++ b/src/test/java/org/lmdbjava/EnvFlagSetTest.java @@ -15,83 +15,46 @@ */ package org.lmdbjava; -import static org.assertj.core.api.Assertions.assertThat; - import java.util.Arrays; -import java.util.HashSet; -import org.junit.jupiter.api.Test; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +public class EnvFlagSetTest extends AbstractFlagSetTest { + + @Override + List getAllFlags() { + return Arrays.stream(EnvFlags.values()) + .collect(Collectors.toList()); + } -public class EnvFlagSetTest { + @Override + EnvFlagSet getEmptyFlagSet() { + return EnvFlagSet.empty(); + } - @Test - public void testEmpty() { - final EnvFlagSet envFlagSet = EnvFlagSet.empty(); - assertThat(envFlagSet.getMask()).isEqualTo(0); - assertThat(envFlagSet.size()).isEqualTo(0); - assertThat(envFlagSet.isEmpty()).isEqualTo(true); - assertThat(envFlagSet.isSet(EnvFlags.MDB_NOSUBDIR)).isEqualTo(false); - final EnvFlagSet envFlagSet2 = EnvFlagSet.builder() - .build(); - assertThat(envFlagSet).isEqualTo(envFlagSet2); - assertThat(envFlagSet).isNotEqualTo(EnvFlagSet.of(EnvFlags.MDB_FIXEDMAP)); - assertThat(envFlagSet).isNotEqualTo(EnvFlagSet.of(EnvFlags.MDB_FIXEDMAP, EnvFlags.MDB_NORDAHEAD)); - assertThat(envFlagSet).isNotEqualTo(EnvFlagSet.builder() - .setFlag(EnvFlags.MDB_FIXEDMAP) - .setFlag(EnvFlags.MDB_NORDAHEAD) - .build()); + @Override + AbstractFlagSet.Builder getBuilder() { + return EnvFlagSet.builder(); } - @Test - public void testOf() { - final EnvFlags envFlag = EnvFlags.MDB_FIXEDMAP; - final EnvFlagSet envFlagSet = EnvFlagSet.of(envFlag); - assertThat(envFlagSet.getMask()).isEqualTo(MaskedFlag.mask(envFlag)); - assertThat(envFlagSet.size()).isEqualTo(1); - assertThat(envFlagSet.isSet(EnvFlags.MDB_NOSUBDIR)).isEqualTo(false); - for (EnvFlags flag : envFlagSet) { - assertThat(envFlagSet.isSet(flag)).isEqualTo(true); - } + @Override + EnvFlagSet getFlagSet(Collection flags) { + return EnvFlagSet.of(flags); + } - final EnvFlagSet envFlagSet2 = EnvFlagSet.builder() - .setFlag(envFlag) - .build(); - assertThat(envFlagSet).isEqualTo(envFlagSet2); + @Override + EnvFlagSet getFlagSet(EnvFlags[] flags) { + return EnvFlagSet.of(flags); } - @Test - public void testOf2() { - final EnvFlags envFlag1 = EnvFlags.MDB_FIXEDMAP; - final EnvFlags envFlag2 = EnvFlags.MDB_NORDAHEAD; - final EnvFlagSet envFlagSet = EnvFlagSet.of(envFlag1, envFlag2); - assertThat(envFlagSet.getMask()).isEqualTo(MaskedFlag.mask(envFlag1, envFlag2)); - assertThat(envFlagSet.size()).isEqualTo(2); - assertThat(envFlagSet.isSet(EnvFlags.MDB_WRITEMAP)).isEqualTo(false); - for (EnvFlags flag : envFlagSet) { - assertThat(envFlagSet.isSet(flag)).isEqualTo(true); - } + @Override + EnvFlagSet getFlagSet(EnvFlags flag) { + return EnvFlagSet.of(flag); } - @Test - public void testBuilder() { - final EnvFlags envFlag1 = EnvFlags.MDB_FIXEDMAP; - final EnvFlags envFlag2 = EnvFlags.MDB_NORDAHEAD; - final EnvFlagSet envFlagSet = EnvFlagSet.builder() - .setFlag(envFlag1) - .setFlag(envFlag2) - .build(); - assertThat(envFlagSet.getMask()).isEqualTo(MaskedFlag.mask(envFlag1, envFlag2)); - assertThat(envFlagSet.size()).isEqualTo(2); - assertThat(envFlagSet.isSet(EnvFlags.MDB_NOTLS)).isEqualTo(false); - for (EnvFlags flag : envFlagSet) { - assertThat(envFlagSet.isSet(flag)).isEqualTo(true); - } - final EnvFlagSet envFlagSet2 = EnvFlagSet.builder() - .withFlags(envFlag1, envFlag2) - .build(); - final EnvFlagSet envFlagSet3 = EnvFlagSet.builder() - .withFlags(new HashSet<>(Arrays.asList(envFlag1, envFlag2))) - .build(); - assertThat(envFlagSet).isEqualTo(envFlagSet2); - assertThat(envFlagSet).isEqualTo(envFlagSet3); + @Override + Class getFlagType() { + return EnvFlags.class; } } diff --git a/src/test/java/org/lmdbjava/PutFlagSetTest.java b/src/test/java/org/lmdbjava/PutFlagSetTest.java index 23cf65a4..9991b870 100644 --- a/src/test/java/org/lmdbjava/PutFlagSetTest.java +++ b/src/test/java/org/lmdbjava/PutFlagSetTest.java @@ -15,89 +15,51 @@ */ package org.lmdbjava; -import static org.assertj.core.api.Assertions.assertThat; - import java.time.Duration; import java.time.Instant; import java.util.Arrays; -import java.util.HashSet; +import java.util.Collection; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; import org.junit.jupiter.api.Test; -public class PutFlagSetTest { +public class PutFlagSetTest extends AbstractFlagSetTest { - @Test - public void testEmpty() { - final PutFlagSet putFlagSet = PutFlagSet.empty(); - assertThat(putFlagSet.getMask()).isEqualTo(0); - assertThat(putFlagSet.size()).isEqualTo(0); - assertThat(putFlagSet.isEmpty()).isEqualTo(true); - assertThat(putFlagSet.isSet(PutFlags.MDB_MULTIPLE)).isEqualTo(false); - final PutFlagSet putFlagSet2 = PutFlagSet.builder() - .build(); - assertThat(putFlagSet).isEqualTo(putFlagSet2); - assertThat(putFlagSet).isNotEqualTo(PutFlagSet.of(PutFlags.MDB_APPEND)); - assertThat(putFlagSet).isNotEqualTo(PutFlagSet.of(PutFlags.MDB_APPEND, PutFlags.MDB_RESERVE)); - assertThat(putFlagSet).isNotEqualTo(PutFlagSet.builder() - .setFlag(PutFlags.MDB_CURRENT) - .setFlag(PutFlags.MDB_MULTIPLE) - .build()); + @Override + List getAllFlags() { + return Arrays.stream(PutFlags.values()) + .collect(Collectors.toList()); } - @Test - public void testOf() { - final PutFlags putFlag = PutFlags.MDB_APPEND; - final PutFlagSet putFlagSet = PutFlagSet.of(putFlag); - assertThat(putFlagSet.getMask()).isEqualTo(MaskedFlag.mask(putFlag)); - assertThat(putFlagSet.size()).isEqualTo(1); - assertThat(putFlagSet.isSet(PutFlags.MDB_MULTIPLE)).isEqualTo(false); - for (PutFlags flag : putFlagSet) { - assertThat(putFlagSet.isSet(flag)).isEqualTo(true); - } + @Override + PutFlagSet getEmptyFlagSet() { + return PutFlagSet.empty(); + } - final PutFlagSet putFlagSet2 = PutFlagSet.builder() - .setFlag(putFlag) - .build(); - assertThat(putFlagSet).isEqualTo(putFlagSet2); + @Override + AbstractFlagSet.Builder getBuilder() { + return PutFlagSet.builder(); } - @Test - public void testOf2() { - final PutFlags putFlag1 = PutFlags.MDB_APPEND; - final PutFlags putFlag2 = PutFlags.MDB_NOOVERWRITE; - final PutFlagSet putFlagSet = PutFlagSet.of(putFlag1, putFlag2); - assertThat(putFlagSet.getMask()).isEqualTo(MaskedFlag.mask(putFlag1, putFlag2)); - assertThat(putFlagSet.size()).isEqualTo(2); - assertThat(putFlagSet.isSet(PutFlags.MDB_MULTIPLE)).isEqualTo(false); - for (PutFlags flag : putFlagSet) { - assertThat(putFlagSet.isSet(flag)).isEqualTo(true); - } + @Override + PutFlagSet getFlagSet(Collection flags) { + return PutFlagSet.of(flags); } - @Test - public void testBuilder() { - final PutFlags putFlag1 = PutFlags.MDB_APPEND; - final PutFlags putFlag2 = PutFlags.MDB_NOOVERWRITE; - final PutFlagSet putFlagSet = PutFlagSet.builder() - .setFlag(putFlag1) - .setFlag(putFlag2) - .build(); - assertThat(putFlagSet.getMask()).isEqualTo(MaskedFlag.mask(putFlag1, putFlag2)); - assertThat(putFlagSet.size()).isEqualTo(2); - assertThat(putFlagSet.isSet(PutFlags.MDB_MULTIPLE)).isEqualTo(false); - for (PutFlags flag : putFlagSet) { - assertThat(putFlagSet.isSet(flag)).isEqualTo(true); - } - final PutFlagSet putFlagSet2 = PutFlagSet.builder() - .withFlags(putFlag1, putFlag2) - .build(); - final PutFlagSet putFlagSet3 = PutFlagSet.builder() - .withFlags(new HashSet<>(Arrays.asList(putFlag1, putFlag2))) - .build(); - assertThat(putFlagSet).isEqualTo(putFlagSet2); - assertThat(putFlagSet).isEqualTo(putFlagSet3); + @Override + PutFlagSet getFlagSet(PutFlags[] flags) { + return PutFlagSet.of(flags); + } + + @Override + PutFlagSet getFlagSet(PutFlags flag) { + return PutFlagSet.of(flag); + } + + @Override + Class getFlagType() { + return PutFlags.class; } @Test diff --git a/src/test/java/org/lmdbjava/TxnFlagSetTest.java b/src/test/java/org/lmdbjava/TxnFlagSetTest.java index 58f75aa6..87455cb7 100644 --- a/src/test/java/org/lmdbjava/TxnFlagSetTest.java +++ b/src/test/java/org/lmdbjava/TxnFlagSetTest.java @@ -15,82 +15,46 @@ */ package org.lmdbjava; -import static org.assertj.core.api.Assertions.assertThat; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; -import java.util.Collections; -import java.util.HashSet; -import org.junit.jupiter.api.Test; +public class TxnFlagSetTest extends AbstractFlagSetTest { -public class TxnFlagSetTest { + @Override + List getAllFlags() { + return Arrays.stream(TxnFlags.values()) + .collect(Collectors.toList()); + } - @Test - void testSingleEnum() { - final TxnFlagSet txnFlagSet = TxnFlags.MDB_RDONLY_TXN; - assertThat(txnFlagSet.getMask()).isEqualTo(MaskedFlag.mask(TxnFlags.MDB_RDONLY_TXN)); - assertThat(txnFlagSet.size()).isEqualTo(1); - for (TxnFlags flag : txnFlagSet) { - assertThat(txnFlagSet.isSet(flag)).isEqualTo(true); - } + @Override + TxnFlagSet getEmptyFlagSet() { + return TxnFlagSet.empty(); + } - final TxnFlagSet txnFlagSet2 = TxnFlagSet.builder() - .setFlag(TxnFlags.MDB_RDONLY_TXN) - .build(); - assertThat(txnFlagSet2.getFlags()).containsExactlyElementsOf(txnFlagSet.getFlags()); - assertThat(txnFlagSet.areAnySet(TxnFlags.MDB_RDONLY_TXN)).isTrue(); - assertThat(txnFlagSet.areAnySet(TxnFlagSet.empty())).isFalse(); + @Override + AbstractFlagSet.Builder getBuilder() { + return TxnFlagSet.builder(); } - @Test - public void testEmpty() { - final TxnFlagSet txnFlagSet = TxnFlagSet.empty(); - assertThat(txnFlagSet.getMask()).isEqualTo(0); - assertThat(txnFlagSet.size()).isEqualTo(0); - assertThat(txnFlagSet.isEmpty()).isEqualTo(true); - assertThat(txnFlagSet.isSet(TxnFlags.MDB_RDONLY_TXN)).isEqualTo(false); - final TxnFlagSet txnFlagSet2 = TxnFlagSet.builder() - .build(); - assertThat(txnFlagSet).isEqualTo(txnFlagSet2); - assertThat(txnFlagSet).isNotEqualTo(TxnFlagSet.of(TxnFlags.MDB_RDONLY_TXN)); - assertThat(txnFlagSet).isNotEqualTo(TxnFlagSet.builder() - .setFlag(TxnFlags.MDB_RDONLY_TXN) - .build()); + @Override + TxnFlagSet getFlagSet(Collection flags) { + return TxnFlagSet.of(flags); } - @Test - public void testOf() { - final TxnFlags txnFlag = TxnFlags.MDB_RDONLY_TXN; - final TxnFlagSet txnFlagSet = TxnFlagSet.of(txnFlag); - assertThat(txnFlagSet.getMask()).isEqualTo(MaskedFlag.mask(txnFlag)); - assertThat(txnFlagSet.size()).isEqualTo(1); - for (TxnFlags flag : txnFlagSet) { - assertThat(txnFlagSet.isSet(flag)).isEqualTo(true); - } + @Override + TxnFlagSet getFlagSet(TxnFlags[] flags) { + return TxnFlagSet.of(flags); + } - final TxnFlagSet txnFlagSet2 = TxnFlagSet.builder() - .setFlag(txnFlag) - .build(); - assertThat(txnFlagSet).isEqualTo(txnFlagSet2); + @Override + TxnFlagSet getFlagSet(TxnFlags flag) { + return TxnFlagSet.of(flag); } - @Test - public void testBuilder() { - final TxnFlags txnFlag1 = TxnFlags.MDB_RDONLY_TXN; - final TxnFlagSet txnFlagSet = TxnFlagSet.builder() - .setFlag(txnFlag1) - .build(); - assertThat(txnFlagSet.getMask()).isEqualTo(MaskedFlag.mask(txnFlag1)); - assertThat(txnFlagSet.size()).isEqualTo(1); - assertThat(txnFlagSet.isSet(TxnFlags.MDB_RDONLY_TXN)).isEqualTo(true); - for (TxnFlags flag : txnFlagSet) { - assertThat(txnFlagSet.isSet(flag)).isEqualTo(true); - } - final TxnFlagSet txnFlagSet2 = TxnFlagSet.builder() - .withFlags(txnFlag1) - .build(); - final TxnFlagSet txnFlagSet3 = TxnFlagSet.builder() - .withFlags(new HashSet<>(Collections.singletonList(txnFlag1))) - .build(); - assertThat(txnFlagSet).isEqualTo(txnFlagSet2); - assertThat(txnFlagSet).isEqualTo(txnFlagSet3); + @Override + Class getFlagType() { + return TxnFlags.class; } } From 7a6910b545363fabaa17b9a6f4017671c72968e6 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Thu, 6 Nov 2025 11:33:35 +0000 Subject: [PATCH 36/90] Add licence header --- .../java/org/lmdbjava/AbstractFlagSetTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/test/java/org/lmdbjava/AbstractFlagSetTest.java b/src/test/java/org/lmdbjava/AbstractFlagSetTest.java index e09d72c6..298ee2fe 100644 --- a/src/test/java/org/lmdbjava/AbstractFlagSetTest.java +++ b/src/test/java/org/lmdbjava/AbstractFlagSetTest.java @@ -1,3 +1,18 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.lmdbjava; import static org.assertj.core.api.Assertions.assertThat; From 167cdadf3059ad0626e13349a5f84a65d8f454be Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Thu, 6 Nov 2025 11:35:58 +0000 Subject: [PATCH 37/90] Rename buildDbi to createDbi to be consistent with Env.create() --- src/main/java/org/lmdbjava/Env.java | 24 +++++++++---------- .../org/lmdbjava/ByteBufferProxyTest.java | 2 +- .../CursorIterableIntegerDupTest.java | 10 ++++---- .../CursorIterableIntegerKeyTest.java | 10 ++++---- .../org/lmdbjava/CursorIterablePerfTest.java | 6 ++--- .../java/org/lmdbjava/CursorIterableTest.java | 10 ++++---- .../java/org/lmdbjava/DbiBuilderTest.java | 12 +++++----- src/test/java/org/lmdbjava/DbiTest.java | 6 ++--- 8 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index 542b67c3..b414101a 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -282,7 +282,7 @@ public boolean isReadOnly() { * * @return A new builder instance for creating/opening a {@link Dbi}. */ - public DbiBuilder buildDbi() { + public DbiBuilder createDbi() { return new DbiBuilder<>(this, proxy, readOnly); } @@ -290,7 +290,7 @@ public DbiBuilder buildDbi() { * Convenience method that opens a {@link Dbi} with a UTF-8 database name and default * {@link Comparator} that is not invoked from native code. *

- * For more options when opening a {@link Dbi} see {@link Env#buildDbi()}. + * For more options when opening a {@link Dbi} see {@link Env#createDbi()}. *

* @param name name of the database (or null if no name is required) * @param dbiFlagSet Flags to open the database with @@ -309,7 +309,7 @@ public Dbi openDbi(final String name, final DbiFlagSet dbiFlagSet) { * Convenience method that opens a {@link Dbi} with a default * {@link Comparator} that is not invoked from native code. *

- * For more options when opening a {@link Dbi} see {@link Env#buildDbi()}. + * For more options when opening a {@link Dbi} see {@link Env#createDbi()}. *

* @param name name of the database (or null if no name is required) * @param dbiFlagSet Flags to open the database with @@ -328,7 +328,7 @@ public Dbi openDbi(final byte[] name, * @param name name of the database (or null if no name is required) * @param flags to open the database with * @return a database that is ready to use - * @deprecated Instead use {@link Env#buildDbi()} or {@link Env#openDbi(String, DbiFlagSet)} + * @deprecated Instead use {@link Env#createDbi()} or {@link Env#openDbi(String, DbiFlagSet)} * Convenience method that opens a {@link Dbi} with a UTF-8 database name and default {@link * Comparator} that is not invoked from native code. */ @@ -344,7 +344,7 @@ public Dbi openDbi(final String name, final DbiFlags... flags) { * comparator will be used. * @param flags to open the database with * @return a database that is ready to use - * @deprecated Instead use {@link Env#buildDbi()} + * @deprecated Instead use {@link Env#createDbi()} * Convenience method that opens a {@link Dbi} with a UTF-8 database name and associated {@link * Comparator} for use by {@link CursorIterable} when comparing start/stop keys. * @@ -368,7 +368,7 @@ public Dbi openDbi(final String name, * @param nativeCb whether LMDB native code calls back to the Java comparator * @param flags to open the database with * @return a database that is ready to use - * @deprecated Instead use {@link Env#buildDbi()} + * @deprecated Instead use {@link Env#createDbi()} * Convenience method that opens a {@link Dbi} with a UTF-8 database name and associated {@link * Comparator}. The comparator will be used by {@link CursorIterable} when comparing start/stop * keys as a minimum. If nativeCb is {@code true}, this comparator will also be called by LMDB to @@ -388,7 +388,7 @@ public Dbi openDbi(final String name, * @param name name of the database (or null if no name is required) * @param flags to open the database with * @return a database that is ready to use - * @deprecated Instead use {@link Env#buildDbi()} + * @deprecated Instead use {@link Env#createDbi()} *
* Convenience method that opens a {@link Dbi} with a default {@link Comparator} that is not * invoked from native code. @@ -396,7 +396,7 @@ public Dbi openDbi(final String name, @Deprecated() public Dbi openDbi(final byte[] name, final DbiFlags... flags) { - return buildDbi() + return createDbi() .setDbName(name) .withDefaultComparator() .setDbiFlags(flags) @@ -408,7 +408,7 @@ public Dbi openDbi(final byte[] name, * @param comparator custom comparator callback (or null to use LMDB default) * @param flags to open the database with * @return a database that is ready to use - * @deprecated Instead use {@link Env#buildDbi()} + * @deprecated Instead use {@link Env#createDbi()} *
* Convenience method that opens a {@link Dbi} with an associated {@link Comparator} that is not * invoked from native code. @@ -418,7 +418,7 @@ public Dbi openDbi(final byte[] name, final Comparator comparator, final DbiFlags... flags) { requireNonNull(comparator); - return buildDbi() + return createDbi() .setDbName(name) .withIteratorComparator(ignored -> comparator) .setDbiFlags(flags) @@ -431,7 +431,7 @@ public Dbi openDbi(final byte[] name, * @param nativeCb whether native code calls back to the Java comparator * @param flags to open the database with * @return a database that is ready to use - * @deprecated Instead use {@link Env#buildDbi()} + * @deprecated Instead use {@link Env#createDbi()} *
* Convenience method that opens a {@link Dbi} with an associated {@link Comparator} that may be * invoked from native code if specified. @@ -459,7 +459,7 @@ public Dbi openDbi( * @param nativeCb whether native LMDB code should call back to the Java comparator * @param flags to open the database with * @return a database that is ready to use - * @deprecated Instead use {@link Env#buildDbi()} + * @deprecated Instead use {@link Env#createDbi()} * Open the {@link Dbi} using the passed {@link Txn}. * *

The caller must commit the transaction after this method returns in order to retain the diff --git a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java index a91fbf75..d9312cce 100644 --- a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java +++ b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java @@ -67,7 +67,7 @@ void buffersMustBeDirect() { try (Env env = create() .setMaxReaders(1) .open(dir)) { - final Dbi db = env.buildDbi() + final Dbi db = env.createDbi() .setDbName(DB_1) .withDefaultComparator() .setDbiFlags(MDB_CREATE) diff --git a/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java b/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java index 1098ca5c..091f5768 100644 --- a/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java @@ -337,7 +337,7 @@ public void openClosedBackwardTestWithGuava() { return guava.compare(array1, array2); }; - final Dbi guavaDbi = env.buildDbi() + final Dbi guavaDbi = env.createDbi() .setDbName(DB_1).withIteratorComparator(ignored -> comparator) .setDbiFlags(MDB_CREATE) .open(); @@ -529,25 +529,25 @@ static class MyArgumentProvider implements ArgumentsProvider { public Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context) throws Exception { final DbiFactory defaultComparatorDb = new DbiFactory("defaultComparator", env -> - env.buildDbi() + env.createDbi() .setDbName(DB_1) .withDefaultComparator() .setDbiFlags(DBI_FLAGS) .open()); final DbiFactory nativeComparatorDb = new DbiFactory("nativeComparator", env -> - env.buildDbi() + env.createDbi() .setDbName(DB_2) .withNativeComparator() .setDbiFlags(DBI_FLAGS) .open()); final DbiFactory callbackComparatorDb = new DbiFactory("callbackComparator", env -> - env.buildDbi() + env.createDbi() .setDbName(DB_3) .withCallbackComparator(MyArgumentProvider::buildComparator) .setDbiFlags(DBI_FLAGS) .open()); final DbiFactory iteratorComparatorDb = new DbiFactory("iteratorComparator", env -> - env.buildDbi() + env.createDbi() .setDbName(DB_4) .withIteratorComparator(MyArgumentProvider::buildComparator) .setDbiFlags(DBI_FLAGS) diff --git a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java index 007b5e29..e4f89302 100644 --- a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java @@ -432,7 +432,7 @@ public void openClosedBackwardTestWithGuava() { bb2.reset(); return guava.compare(array1, array2); }; - final Dbi guavaDbi = env.buildDbi() + final Dbi guavaDbi = env.createDbi() .setDbName(DB_1) .withIteratorComparator(ignored -> comparator) .setDbiFlags(MDB_CREATE) @@ -602,25 +602,25 @@ static class MyArgumentProvider implements ArgumentsProvider { public Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context) throws Exception { final DbiFactory defaultComparatorDb = new DbiFactory("defaultComparator", env -> - env.buildDbi() + env.createDbi() .setDbName(DB_1) .withDefaultComparator() .setDbiFlags(DBI_FLAGS) .open()); final DbiFactory nativeComparatorDb = new DbiFactory("nativeComparator", env -> - env.buildDbi() + env.createDbi() .setDbName(DB_2) .withNativeComparator() .setDbiFlags(DBI_FLAGS) .open()); final DbiFactory callbackComparatorDb = new DbiFactory("callbackComparator", env -> - env.buildDbi() + env.createDbi() .setDbName(DB_3) .withCallbackComparator(MyArgumentProvider::buildComparator) .setDbiFlags(DBI_FLAGS) .open()); final DbiFactory iteratorComparatorDb = new DbiFactory("iteratorComparator", env -> - env.buildDbi() + env.createDbi() .setDbName(DB_4) .withIteratorComparator(MyArgumentProvider::buildComparator) .setDbiFlags(DBI_FLAGS) diff --git a/src/test/java/org/lmdbjava/CursorIterablePerfTest.java b/src/test/java/org/lmdbjava/CursorIterablePerfTest.java index 80555966..cc91a8fb 100644 --- a/src/test/java/org/lmdbjava/CursorIterablePerfTest.java +++ b/src/test/java/org/lmdbjava/CursorIterablePerfTest.java @@ -59,20 +59,20 @@ public void before() throws IOException { final DbiFlagSet dbiFlagSet = MDB_CREATE; // Use a java comparator for start/stop keys only - Dbi dbJavaComparator = env.buildDbi() + Dbi dbJavaComparator = env.createDbi() .setDbName("JavaComparator") .withDefaultComparator() .setDbiFlags(dbiFlagSet) .open(); // Use LMDB comparator for start/stop keys - Dbi dbLmdbComparator = env.buildDbi() + Dbi dbLmdbComparator = env.createDbi() .setDbName("LmdbComparator") .withNativeComparator() .setDbiFlags(dbiFlagSet) .open(); // Use a java comparator for start/stop keys and as a callback comparator - Dbi dbCallbackComparator = env.buildDbi() + Dbi dbCallbackComparator = env.createDbi() .setDbName("CallBackComparator") .withCallbackComparator(bufferProxy::getComparator) .setDbiFlags(dbiFlagSet) diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index e48f1e69..b991b809 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -308,7 +308,7 @@ void openClosedBackwardTestWithGuava() { bb2.reset(); return guava.compare(array1, array2); }; - final Dbi guavaDbi = env.buildDbi() + final Dbi guavaDbi = env.createDbi() .setDbName(DB_1) .withDefaultComparator() .setDbiFlags(MDB_CREATE) @@ -537,25 +537,25 @@ static class MyArgumentProvider implements ArgumentsProvider { public Stream provideArguments(ParameterDeclarations parameters, ExtensionContext context) throws Exception { final DbiFactory defaultComparatorDb = new DbiFactory("defaultComparator", env -> - env.buildDbi() + env.createDbi() .setDbName(DB_1) .withDefaultComparator() .setDbiFlags(DBI_FLAGS) .open()); final DbiFactory nativeComparatorDb = new DbiFactory("nativeComparator", env -> - env.buildDbi() + env.createDbi() .setDbName(DB_2) .withNativeComparator() .setDbiFlags(DBI_FLAGS) .open()); final DbiFactory callbackComparatorDb = new DbiFactory("callbackComparator", env -> - env.buildDbi() + env.createDbi() .setDbName(DB_3) .withCallbackComparator(BUFFER_PROXY::getComparator) .setDbiFlags(DBI_FLAGS) .open()); final DbiFactory iteratorComparatorDb = new DbiFactory("iteratorComparator", env -> - env.buildDbi() + env.createDbi() .setDbName(DB_4) .withIteratorComparator(BUFFER_PROXY::getComparator) .setDbiFlags(DBI_FLAGS) diff --git a/src/test/java/org/lmdbjava/DbiBuilderTest.java b/src/test/java/org/lmdbjava/DbiBuilderTest.java index d01f6417..87b1db46 100644 --- a/src/test/java/org/lmdbjava/DbiBuilderTest.java +++ b/src/test/java/org/lmdbjava/DbiBuilderTest.java @@ -57,7 +57,7 @@ public void after() { @Test public void unnamed() { - final Dbi dbi = env.buildDbi() + final Dbi dbi = env.createDbi() .withoutDbName() .withDefaultComparator() .setDbiFlags(DbiFlags.MDB_CREATE) @@ -70,7 +70,7 @@ public void unnamed() { @Test public void named() { - final Dbi dbi = env.buildDbi() + final Dbi dbi = env.createDbi() .setDbName("foo") .withDefaultComparator() .setDbiFlags(DbiFlags.MDB_CREATE) @@ -89,7 +89,7 @@ public void named() { @Test public void named2() { - final Dbi dbi = env.buildDbi() + final Dbi dbi = env.createDbi() .setDbName("foo".getBytes(StandardCharsets.US_ASCII)) .withDefaultComparator() .setDbiFlags(DbiFlags.MDB_CREATE) @@ -108,7 +108,7 @@ public void named2() { @Test public void nativeComparator() { - final Dbi dbi = env.buildDbi() + final Dbi dbi = env.createDbi() .setDbName("foo") .withNativeComparator() .addDbiFlags(DbiFlags.MDB_CREATE) @@ -131,7 +131,7 @@ public void callback() { } }; - final Dbi dbi = env.buildDbi() + final Dbi dbi = env.createDbi() .setDbName("foo") .withCallbackComparator(ignored -> comparator) .addDbiFlags(DbiFlags.MDB_CREATE) @@ -163,7 +163,7 @@ public void callback() { @Test public void flags() { - final Dbi dbi = env.buildDbi() + final Dbi dbi = env.createDbi() .setDbName("foo") .withDefaultComparator() .setDbiFlags(DbiFlags.MDB_DUPSORT, DbiFlags.MDB_DUPFIXED) // Will get overwritten diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 6d86bd85..2d84b467 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -109,7 +109,7 @@ void afterEach() { void close() { assertThatThrownBy( () -> { - final Dbi db = env.buildDbi() + final Dbi db = env.createDbi() .setDbName(DB_1) .withDefaultComparator() .addDbiFlag(MDB_CREATE) @@ -152,7 +152,7 @@ private void doCustomComparator( Comparator comparator, IntFunction serializer, ToIntFunction deserializer) { - final Dbi db = env.buildDbi() + final Dbi db = env.createDbi() .setDbName(DB_1) .withCallbackComparator(ignored -> comparator) .setDbiFlags(MDB_CREATE) @@ -203,7 +203,7 @@ private void doDbiWithComparatorThreadSafety( ToIntFunction deserializer) { final DbiFlagSet flags = DbiFlagSet.of(MDB_CREATE, MDB_INTEGERKEY); final Comparator comparator = comparatorSupplier.get(); - final Dbi db = env.buildDbi() + final Dbi db = env.createDbi() .setDbName(DB_1) .withCallbackComparator(ignored -> comparator) .setDbiFlags(flags) From d47694ec249c3b48d51090f566e3d7b20eefb84b Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Thu, 6 Nov 2025 12:20:47 +0000 Subject: [PATCH 38/90] Remove ByteUnits dep, add ByteUnit enum, add setMapSize() overload --- pom.xml | 7 -- src/main/java/org/lmdbjava/ByteUnit.java | 90 +++++++++++++++++++ src/main/java/org/lmdbjava/Env.java | 66 ++++++-------- src/test/java/org/lmdbjava/ByteUnitTest.java | 64 +++++++++++++ .../CursorIterableIntegerDupTest.java | 3 +- .../CursorIterableIntegerKeyTest.java | 3 +- .../org/lmdbjava/CursorIterablePerfTest.java | 3 +- .../java/org/lmdbjava/CursorIterableTest.java | 3 +- .../java/org/lmdbjava/CursorParamTest.java | 3 +- src/test/java/org/lmdbjava/CursorTest.java | 3 +- .../java/org/lmdbjava/DbiBuilderTest.java | 3 +- src/test/java/org/lmdbjava/DbiTest.java | 27 +++--- src/test/java/org/lmdbjava/EnvTest.java | 42 ++++----- src/test/java/org/lmdbjava/TxnTest.java | 3 +- src/test/java/org/lmdbjava/VerifierTest.java | 17 ++-- 15 files changed, 228 insertions(+), 109 deletions(-) create mode 100644 src/main/java/org/lmdbjava/ByteUnit.java create mode 100644 src/test/java/org/lmdbjava/ByteUnitTest.java diff --git a/pom.xml b/pom.xml index 4d60b903..11dbdeb5 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,6 @@ 1.22.0 3.27.6 3.2.1 - 0.9.1 0.9.0 2.29 1.28.0 @@ -85,12 +84,6 @@ ${guava.version} test - - com.jakewharton.byteunits - byteunits - ${byteunits.version} - test - io.netty netty-buffer diff --git a/src/main/java/org/lmdbjava/ByteUnit.java b/src/main/java/org/lmdbjava/ByteUnit.java new file mode 100644 index 00000000..96d0dba7 --- /dev/null +++ b/src/main/java/org/lmdbjava/ByteUnit.java @@ -0,0 +1,90 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +/** + * Simple {@link Enum} for converting various IEC and SI byte units down a number of bytes. + */ +public enum ByteUnit { + + BYTES(1L), + + /** + * IEC byte unit for 1024 bytes. + */ + KIBIBYTES(1024L), + /** + * IEC byte unit for 1024^2 bytes. + */ + MEBIBYTES(1_0485_76L), + /** + * IEC byte unit for 1024^3 bytes. + */ + GIBIBYTES(1_073_741_824L), + /** + * IEC byte unit for 1024^4 bytes. + */ + TEBIBYTES(1_099_511_627_776L), + /** + * IEC byte unit for 1024^5 bytes. + */ + PEBIBYTES(1_125_899_906_842_624L), + + /** + * SI byte unit for 1000 bytes. + */ + KILOBYTES(1_000L), + /** + * SI byte unit for 1000^2 bytes. + */ + MEGABYTES(1_000_000L), + /** + * SI byte unit for 1000^3 bytes. + */ + GIGABYTES(1_000_000_000L), + /** + * SI byte unit for 1000^4 bytes. + */ + TERABYTES(1_000_000_000_000L), + /** + * SI byte unit for 1000^5 bytes. + */ + PETABYTES(1_000_000_000_000_000L), + ; + + private final long factor; + + ByteUnit(long factor) { + this.factor = factor; + } + + /** + * Convert the value in this byte unit into bytes. + * + * @param value The value to convert. + * @return The number of bytes. + */ + public long toBytes(final long value) { + return value * factor; + } + + /** + * @return The factor to apply when converting this unit into bytes. + */ + public long getFactor() { + return factor; + } +} diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index b414101a..2565bc65 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -63,11 +63,6 @@ public final class Env implements AutoCloseable { */ public static final boolean SHOULD_CHECK = !getBoolean(DISABLE_CHECKS_PROP); - private static final long KIBIBYTES = 1_024L; - private static final long MEBIBYTES = KIBIBYTES * 1_024L; - private static final long GIBIBYTES = MEBIBYTES * 1_024L; - private static final long TEBIBYTES = GIBIBYTES * 1_024L; - private boolean closed; private final int maxKeySize; private final boolean noSubDir; @@ -120,8 +115,9 @@ public static Builder create(final BufferProxy proxy) { */ @Deprecated public static Env open(final File path, final int size, final EnvFlags... flags) { + return new Builder<>(PROXY_OPTIMAL) - .setMapSize(size * MEBIBYTES) + .setMapSize(size, ByteUnit.MEBIBYTES) .open(path, flags); } @@ -218,9 +214,22 @@ public List getDbiNames() { * @param mapSize the new size, in bytes */ public void setMapSize(final long mapSize) { + if (mapSize < 0) { + throw new IllegalArgumentException("Negative value; overflow?"); + } checkRc(LIB.mdb_env_set_mapsize(ptr, mapSize)); } + /** + * Set the size of the data memory map. + * + * @param mapSize the new size, in bytes + */ + public void setMapSize(final long mapSize, final ByteUnit byteUnit) { + requireNonNull(byteUnit); + setMapSize(byteUnit.toBytes(mapSize)); + } + /** * Get the maximum size of keys and MDB_DUPSORT data we can write. * @@ -668,7 +677,8 @@ public AlreadyOpenException() { public static final class Builder { static final int MAX_READERS_DEFAULT = 126; - private long mapSize = 1_024 * 1_024; + static final long MAP_SIZE_DEFAULT = ByteUnit.MEBIBYTES.toBytes(1); + private long mapSize = MAP_SIZE_DEFAULT; private int maxDbs = 1; private int maxReaders = MAX_READERS_DEFAULT; private boolean opened; @@ -772,43 +782,17 @@ public Builder setMapSize(final long mapSize) { } /** - * Sets the map size in kibibytes - * - * @param mapSizeKb new limit in kibibytes - * @return the builder - */ - public Builder setMapSizeKb(final long mapSizeKb) { - return setMapSize(mapSizeKb * KIBIBYTES); - } - - /** - * Sets the map size in mebibytes. + * Sets the map size in the supplied unit. * - * @param mapSizeMb new limit in mebibytes. + * @param mapSize new limit in * @return the builder */ - public Builder setMapSizeMb(final long mapSizeMb) { - return setMapSize(mapSizeMb * MEBIBYTES); - } - - /** - * Sets the map size in gibibytes - * - * @param mapSizeGb new limit in gibibytes - * @return the builder - */ - public Builder setMapSizeGb(final long mapSizeGb) { - return setMapSize(mapSizeGb * GIBIBYTES); - } - - /** - * Sets the map size in tebibytes. - * - * @param mapSizeTb new limit in tebibytes. - * @return the builder - */ - public Builder setMapSizeTb(final long mapSizeTb) { - return setMapSize(mapSizeTb * TEBIBYTES); + public Builder setMapSize(final long mapSize, final ByteUnit byteUnit) { + requireNonNull(byteUnit); + if (mapSize < 0) { + throw new IllegalArgumentException("Negative value; overflow?"); + } + return setMapSize(byteUnit.toBytes(mapSize)); } /** diff --git a/src/test/java/org/lmdbjava/ByteUnitTest.java b/src/test/java/org/lmdbjava/ByteUnitTest.java new file mode 100644 index 00000000..a899cbc3 --- /dev/null +++ b/src/test/java/org/lmdbjava/ByteUnitTest.java @@ -0,0 +1,64 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +class ByteUnitTest { + + @Test + void test() { + Assertions.assertThat(ByteUnit.BYTES.toBytes(2)).isEqualTo(2); + + // BYTES + Assertions.assertThat(ByteUnit.BYTES.toBytes(2)).isEqualTo(2L); + Assertions.assertThat(ByteUnit.BYTES.toBytes(0)).isEqualTo(0L); + + // IEC Units + Assertions.assertThat(ByteUnit.KIBIBYTES.toBytes(1)).isEqualTo(1024L); + Assertions.assertThat(ByteUnit.KIBIBYTES.toBytes(2)).isEqualTo(2048L); + + Assertions.assertThat(ByteUnit.MEBIBYTES.toBytes(1)).isEqualTo(1048576L); + Assertions.assertThat(ByteUnit.MEBIBYTES.toBytes(2)).isEqualTo(2097152L); + + Assertions.assertThat(ByteUnit.GIBIBYTES.toBytes(1)).isEqualTo(1073741824L); + Assertions.assertThat(ByteUnit.GIBIBYTES.toBytes(2)).isEqualTo(2147483648L); + + Assertions.assertThat(ByteUnit.TEBIBYTES.toBytes(1)).isEqualTo(1099511627776L); + Assertions.assertThat(ByteUnit.TEBIBYTES.toBytes(2)).isEqualTo(2199023255552L); + + Assertions.assertThat(ByteUnit.PEBIBYTES.toBytes(1)).isEqualTo(1125899906842624L); + Assertions.assertThat(ByteUnit.PEBIBYTES.toBytes(2)).isEqualTo(2251799813685248L); + + // SI Units + Assertions.assertThat(ByteUnit.KILOBYTES.toBytes(1)).isEqualTo(1000L); + Assertions.assertThat(ByteUnit.KILOBYTES.toBytes(2)).isEqualTo(2000L); + + Assertions.assertThat(ByteUnit.MEGABYTES.toBytes(1)).isEqualTo(1000000L); + Assertions.assertThat(ByteUnit.MEGABYTES.toBytes(2)).isEqualTo(2000000L); + + Assertions.assertThat(ByteUnit.GIGABYTES.toBytes(1)).isEqualTo(1000000000L); + Assertions.assertThat(ByteUnit.GIGABYTES.toBytes(2)).isEqualTo(2000000000L); + + Assertions.assertThat(ByteUnit.TERABYTES.toBytes(1)).isEqualTo(1000000000000L); + Assertions.assertThat(ByteUnit.TERABYTES.toBytes(2)).isEqualTo(2000000000000L); + + Assertions.assertThat(ByteUnit.PETABYTES.toBytes(1)).isEqualTo(1000000000000000L); + Assertions.assertThat(ByteUnit.PETABYTES.toBytes(2)).isEqualTo(2000000000000000L); + + } +} diff --git a/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java b/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java index 091f5768..0e3d4e3e 100644 --- a/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java @@ -15,7 +15,6 @@ */ package org.lmdbjava; -import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; import static org.assertj.core.api.Assertions.assertThat; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.DbiFlags.MDB_DUPSORT; @@ -123,7 +122,7 @@ public void before() throws IOException { final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; env = create(bufferProxy) - .setMapSize(KIBIBYTES.toBytes(256)) + .setMapSize(256, ByteUnit.KIBIBYTES) .setMaxReaders(1) .setMaxDbs(3) .setEnvFlags(MDB_NOSUBDIR) diff --git a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java index e4f89302..3ff4364f 100644 --- a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java @@ -15,7 +15,6 @@ */ package org.lmdbjava; -import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.lmdbjava.DbiFlags.MDB_CREATE; @@ -104,7 +103,7 @@ public void before() throws IOException { file = FileUtil.createTempFile(); final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; env = Env.create(bufferProxy) - .setMapSize(KIBIBYTES.toBytes(256)) + .setMapSize(256, ByteUnit.KIBIBYTES) .setMaxReaders(1) .setMaxDbs(3) .setEnvFlags(MDB_NOSUBDIR) diff --git a/src/test/java/org/lmdbjava/CursorIterablePerfTest.java b/src/test/java/org/lmdbjava/CursorIterablePerfTest.java index cc91a8fb..82bf337d 100644 --- a/src/test/java/org/lmdbjava/CursorIterablePerfTest.java +++ b/src/test/java/org/lmdbjava/CursorIterablePerfTest.java @@ -15,7 +15,6 @@ */ package org.lmdbjava; -import static com.jakewharton.byteunits.BinaryByteUnit.GIBIBYTES; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.Env.create; import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; @@ -51,7 +50,7 @@ public void before() throws IOException { final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; env = create(bufferProxy) - .setMapSize(GIBIBYTES.toBytes(1)) + .setMapSize(1, ByteUnit.GIBIBYTES) .setMaxReaders(1) .setMaxDbs(3) .setEnvFlags(MDB_NOSUBDIR) diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index b991b809..e57e8ca4 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -16,7 +16,6 @@ package org.lmdbjava; -import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -98,7 +97,7 @@ void beforeEach() { file = FileUtil.createTempFile(); final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; env = create(bufferProxy) - .setMapSize(KIBIBYTES.toBytes(256)) + .setMapSize(256, ByteUnit.KIBIBYTES) .setMaxReaders(1) .setMaxDbs(3) .setEnvFlags(MDB_NOSUBDIR) diff --git a/src/test/java/org/lmdbjava/CursorParamTest.java b/src/test/java/org/lmdbjava/CursorParamTest.java index 4dd52b6f..f2fc5fbb 100644 --- a/src/test/java/org/lmdbjava/CursorParamTest.java +++ b/src/test/java/org/lmdbjava/CursorParamTest.java @@ -16,7 +16,6 @@ package org.lmdbjava; -import static com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; import static java.lang.Long.BYTES; import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; @@ -158,7 +157,7 @@ public final void execute(final Path tmp) { private Env env(final Path tmp) { return create(proxy) - .setMapSize(MEBIBYTES.toBytes(1)) + .setMapSize(1, ByteUnit.MEBIBYTES) .setMaxReaders(1) .setMaxDbs(1) .setEnvFlags(MDB_NOSUBDIR) diff --git a/src/test/java/org/lmdbjava/CursorTest.java b/src/test/java/org/lmdbjava/CursorTest.java index 9126026f..7177000a 100644 --- a/src/test/java/org/lmdbjava/CursorTest.java +++ b/src/test/java/org/lmdbjava/CursorTest.java @@ -16,7 +16,6 @@ package org.lmdbjava; -import static com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; import static java.lang.Long.BYTES; import static java.lang.Long.MIN_VALUE; import static java.nio.ByteBuffer.allocateDirect; @@ -61,7 +60,7 @@ void beforeEach() { file = FileUtil.createTempFile(); env = create(PROXY_OPTIMAL) - .setMapSize(MEBIBYTES.toBytes(1)) + .setMapSize(1, ByteUnit.MEBIBYTES) .setMaxReaders(1) .setMaxDbs(1) .setEnvFlags(MDB_NOSUBDIR) diff --git a/src/test/java/org/lmdbjava/DbiBuilderTest.java b/src/test/java/org/lmdbjava/DbiBuilderTest.java index 87b1db46..ede387c9 100644 --- a/src/test/java/org/lmdbjava/DbiBuilderTest.java +++ b/src/test/java/org/lmdbjava/DbiBuilderTest.java @@ -15,7 +15,6 @@ */ package org.lmdbjava; -import static com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; import static org.assertj.core.api.Assertions.assertThat; import static org.lmdbjava.Env.create; import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; @@ -42,7 +41,7 @@ public class DbiBuilderTest { public void before() { file = FileUtil.createTempFile(); env = create() - .setMapSize(MEBIBYTES.toBytes(64)) + .setMapSize(64, ByteUnit.MEBIBYTES) .setMaxReaders(2) .setMaxDbs(2) .setEnvFlags(MDB_NOSUBDIR) diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 2d84b467..8b8a461b 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -16,7 +16,6 @@ package org.lmdbjava; -import static com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; import static java.lang.Long.MAX_VALUE; import static java.lang.System.getProperty; import static java.nio.ByteBuffer.allocateDirect; @@ -82,19 +81,19 @@ public final class DbiTest { @BeforeEach void beforeEach() { file = FileUtil.createTempFile(); - env = - create() - .setMapSize(MEBIBYTES.toBytes(64)) - .setMaxReaders(2) - .setMaxDbs(2) - .open(file.toFile(), MDB_NOSUBDIR); + env = create() + .setMapSize(64, ByteUnit.MEBIBYTES) + .setMaxReaders(2) + .setMaxDbs(2) + .setEnvFlags(MDB_NOSUBDIR) + .open(file); fileBa = FileUtil.createTempFile(); - envBa = - create(PROXY_BA) - .setMapSize(MEBIBYTES.toBytes(64)) - .setMaxReaders(2) - .setMaxDbs(2) - .open(fileBa.toFile(), MDB_NOSUBDIR); + envBa = create(PROXY_BA) + .setMapSize(64, ByteUnit.MEBIBYTES) + .setMaxReaders(2) + .setMaxDbs(2) + .setEnvFlags(MDB_NOSUBDIR) + .open(fileBa); } @AfterEach @@ -393,7 +392,7 @@ void putCommitGetByteArray() { FileUtil.useTempFile( file -> { try (Env envBa = create(PROXY_BA) - .setMapSize(MEBIBYTES.toBytes(64)) + .setMapSize(64, ByteUnit.MEBIBYTES) .setMaxReaders(1) .setMaxDbs(2) .setEnvFlags(MDB_NOSUBDIR) diff --git a/src/test/java/org/lmdbjava/EnvTest.java b/src/test/java/org/lmdbjava/EnvTest.java index 69c20c1b..6332ddc8 100644 --- a/src/test/java/org/lmdbjava/EnvTest.java +++ b/src/test/java/org/lmdbjava/EnvTest.java @@ -16,7 +16,6 @@ package org.lmdbjava; -import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; import static com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; import static java.nio.ByteBuffer.allocateDirect; import static org.assertj.core.api.Assertions.assertThat; @@ -52,12 +51,11 @@ public final class EnvTest { void byteUnit() { FileUtil.useTempFile( file -> { - try (Env env = - Env.create() - .setMaxReaders(1) - .setMapSize(MEBIBYTES.toBytes(1)) - .setEnvFlags(MDB_NOSUBDIR) - .open(file)) { + try (Env env = Env.create() + .setMaxReaders(1) + .setMapSize(1, ByteUnit.MEBIBYTES) + .setEnvFlags(MDB_NOSUBDIR) + .open(file)) { final EnvInfo info = env.info(); assertThat(info.mapSize).isEqualTo(MEBIBYTES.toBytes(1)); } @@ -342,13 +340,12 @@ void createAsDirectory() { void createAsFile() { FileUtil.useTempFile( file -> { - try (Env env = - Env.create() - .setMapSize(MEBIBYTES.toBytes(1)) - .setMaxDbs(1) - .setMaxReaders(1) - .setEnvFlags(MDB_NOSUBDIR) - .open(file)) { + try (Env env = Env.create() + .setMapSize(1, ByteUnit.MEBIBYTES) + .setMaxDbs(1) + .setMaxReaders(1) + .setEnvFlags(MDB_NOSUBDIR) + .open(file)) { env.sync(true); assertThat(Files.isRegularFile(file)).isTrue(); } @@ -404,7 +401,7 @@ void mapFull() { try (Env env = Env.create() .setMaxReaders(1) - .setMapSize(MEBIBYTES.toBytes(8)) + .setMapSize(8, ByteUnit.MEBIBYTES) .setMaxDbs(1) .open(dir)) { final Dbi db = env.openDbi(DB_1, MDB_CREATE); @@ -452,12 +449,11 @@ void setMapSize() { final ByteBuffer key = allocateDirect(500); final ByteBuffer val = allocateDirect(1_024); final Random rnd = new Random(); - try (Env env = - Env.create() - .setMaxReaders(1) - .setMapSize(KIBIBYTES.toBytes(256)) - .setMaxDbs(1) - .open(dir)) { + try (Env env = Env.create() + .setMaxReaders(1) + .setMapSize(256, ByteUnit.KIBIBYTES) + .setMaxDbs(1) + .open(dir)) { final Dbi db = env.openDbi(DB_1, MDB_CREATE); db.put(bb(1), bb(42)); @@ -475,7 +471,7 @@ void setMapSize() { } assertThat(mapFullExThrown).isTrue(); - env.setMapSize(KIBIBYTES.toBytes(1024)); + env.setMapSize(1024, ByteUnit.KIBIBYTES); try (Txn roTxn = env.txnRead()) { final ByteBuffer byteBuffer = db.get(roTxn, bb(1)); @@ -525,7 +521,7 @@ void stats() { void testDefaultOpen() { FileUtil.useTempDir( dir -> { - try (Env env = Env.create().setMapSizeMb(10).open(dir)) { + try (Env env = Env.create().setMapSize(10, ByteUnit.MEBIBYTES).open(dir)) { final EnvInfo info = env.info(); assertThat(info.maxReaders).isEqualTo(MAX_READERS_DEFAULT); final Dbi db = env.openDbi("test", MDB_CREATE); diff --git a/src/test/java/org/lmdbjava/TxnTest.java b/src/test/java/org/lmdbjava/TxnTest.java index f81b525e..ba8e55d3 100644 --- a/src/test/java/org/lmdbjava/TxnTest.java +++ b/src/test/java/org/lmdbjava/TxnTest.java @@ -16,7 +16,6 @@ package org.lmdbjava; -import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; import static java.nio.ByteBuffer.allocateDirect; import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; @@ -64,7 +63,7 @@ void beforeEach() { file = FileUtil.createTempFile(); env = create() - .setMapSize(KIBIBYTES.toBytes(256)) + .setMapSize(256, ByteUnit.KIBIBYTES) .setMaxReaders(1) .setMaxDbs(2) .setEnvFlags(MDB_NOSUBDIR) diff --git a/src/test/java/org/lmdbjava/VerifierTest.java b/src/test/java/org/lmdbjava/VerifierTest.java index 64214568..75812e18 100644 --- a/src/test/java/org/lmdbjava/VerifierTest.java +++ b/src/test/java/org/lmdbjava/VerifierTest.java @@ -16,7 +16,6 @@ package org.lmdbjava; -import static com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; import static org.assertj.core.api.Assertions.assertThat; import static org.lmdbjava.Env.create; import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; @@ -25,19 +24,21 @@ import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; -/** Test {@link Verifier}. */ +/** + * Test {@link Verifier}. + */ public final class VerifierTest { @Test void verification() { FileUtil.useTempFile( file -> { - try (Env env = - create() - .setMaxReaders(1) - .setMaxDbs(Verifier.DBI_COUNT) - .setMapSize(MEBIBYTES.toBytes(10)) - .open(file.toFile(), MDB_NOSUBDIR)) { + try (Env env = create() + .setMaxReaders(1) + .setMaxDbs(Verifier.DBI_COUNT) + .setMapSize(10, ByteUnit.MEBIBYTES) + .setEnvFlags(MDB_NOSUBDIR) + .open(file)) { final Verifier v = new Verifier(env); final int seconds = Integer.getInteger("verificationSeconds", 2); assertThat(v.runFor(seconds, TimeUnit.SECONDS)).isGreaterThan(1L); From fb087797f5154dfe77d568253ea1c72e11817973 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Thu, 6 Nov 2025 12:25:47 +0000 Subject: [PATCH 39/90] Fix compile errors --- src/test/java/org/lmdbjava/EnvTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/java/org/lmdbjava/EnvTest.java b/src/test/java/org/lmdbjava/EnvTest.java index 6332ddc8..a2e3cb47 100644 --- a/src/test/java/org/lmdbjava/EnvTest.java +++ b/src/test/java/org/lmdbjava/EnvTest.java @@ -16,7 +16,6 @@ package org.lmdbjava; -import static com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; import static java.nio.ByteBuffer.allocateDirect; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -57,7 +56,7 @@ void byteUnit() { .setEnvFlags(MDB_NOSUBDIR) .open(file)) { final EnvInfo info = env.info(); - assertThat(info.mapSize).isEqualTo(MEBIBYTES.toBytes(1)); + assertThat(info.mapSize).isEqualTo(ByteUnit.MEBIBYTES.toBytes(1)); } }); } From a82d7f234d33f35ae15cf0340fe9068af6d967c1 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Thu, 6 Nov 2025 12:38:47 +0000 Subject: [PATCH 40/90] Add more test coverage --- src/main/java/org/lmdbjava/Env.java | 7 ++++--- src/main/java/org/lmdbjava/FlagSet.java | 15 ++++++--------- .../java/org/lmdbjava/AbstractFlagSetTest.java | 18 ++++++++++++++---- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index 2565bc65..9b2a4360 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -115,7 +115,6 @@ public static Builder create(final BufferProxy proxy) { */ @Deprecated public static Env open(final File path, final int size, final EnvFlags... flags) { - return new Builder<>(PROXY_OPTIMAL) .setMapSize(size, ByteUnit.MEBIBYTES) .open(path, flags); @@ -678,13 +677,15 @@ public static final class Builder { static final int MAX_READERS_DEFAULT = 126; static final long MAP_SIZE_DEFAULT = ByteUnit.MEBIBYTES.toBytes(1); + static final int POSIX_MODE_DEFAULT = 0664; + private long mapSize = MAP_SIZE_DEFAULT; private int maxDbs = 1; private int maxReaders = MAX_READERS_DEFAULT; private boolean opened; private final BufferProxy proxy; - private int mode = 0664; - private AbstractFlagSet.Builder flagSetBuilder = EnvFlagSet.builder(); + private int mode = POSIX_MODE_DEFAULT; + private final AbstractFlagSet.Builder flagSetBuilder = EnvFlagSet.builder(); Builder(final BufferProxy proxy) { requireNonNull(proxy); diff --git a/src/main/java/org/lmdbjava/FlagSet.java b/src/main/java/org/lmdbjava/FlagSet.java index a9cffd47..94cb3dca 100644 --- a/src/main/java/org/lmdbjava/FlagSet.java +++ b/src/main/java/org/lmdbjava/FlagSet.java @@ -55,6 +55,11 @@ default int getMaskWith(final FlagSet other) { */ boolean isSet(T flag); + /** + * @return The number of flags in this set. + */ + int size(); + /** * @return True if at least one of flags are included in thie {@link FlagSet} */ @@ -71,19 +76,11 @@ default boolean areAnySet(final FlagSet flags) { return false; } - /** - * @return The size of this {@link FlagSet} - */ - default int size() { - return getFlags().size(); - } /** * @return True if this {@link FlagSet} is empty. */ - default boolean isEmpty() { - return getFlags().isEmpty(); - } + boolean isEmpty(); /** * @return The {@link Iterator} (in no particular order) for the flags in this {@link FlagSet}. diff --git a/src/test/java/org/lmdbjava/AbstractFlagSetTest.java b/src/test/java/org/lmdbjava/AbstractFlagSetTest.java index 298ee2fe..011d3efd 100644 --- a/src/test/java/org/lmdbjava/AbstractFlagSetTest.java +++ b/src/test/java/org/lmdbjava/AbstractFlagSetTest.java @@ -45,10 +45,6 @@ T getFirst() { return getAllFlags().get(0); } - int getFlagCount() { - return getAllFlags().size(); - } - @Test void testEmpty() { final F emptyFlagSet = getEmptyFlagSet(); @@ -81,6 +77,12 @@ void testSingleFlagSet() { .containsExactly(flag); assertThat(flagSet.size()) .isEqualTo(1); + assertThat(FlagSet.equals(flagSet, new Object())) + .isFalse(); + assertThat(FlagSet.equals(flagSet, null)) + .isFalse(); + assertThat(FlagSet.equals(flag, flag)) + .isTrue(); assertThat(FlagSet.equals(flagSet, flag)) .isTrue(); assertThat(FlagSet.equals(flagSet, getFlagSet(flag))) @@ -89,6 +91,10 @@ void testSingleFlagSet() { .isTrue(); assertThat(flagSet.areAnySet(flag)) .isTrue(); + assertThat(flagSet.areAnySet(null)) + .isFalse(); + assertThat(flagSet.areAnySet(getEmptyFlagSet())) + .isFalse(); assertThat(flagSet.isSet(getFirst())) .isEqualTo(getFirst() == flag); if (getFirst() == flag) { @@ -97,11 +103,15 @@ void testSingleFlagSet() { } else { assertThat(flagSet.getMask()) .isNotEqualTo(MaskedFlag.mask(getFirst())); + assertThat(flagSet.getMaskWith(getFirst())) + .isEqualTo(MaskedFlag.mask(flag, getFirst())); } assertThat(flagSet.toString()) .isNotNull(); assertThat(flag.name()) .isNotNull(); + assertThat(flagSet.getMaskWith(null)) + .isEqualTo(flagSet.getMask()); } } From 534b76ba3ba890810a99f2c4a13c5db4de672a23 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Thu, 6 Nov 2025 18:34:20 +0000 Subject: [PATCH 41/90] Fix codeQL issue --- src/main/java/org/lmdbjava/DbiBuilder.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/lmdbjava/DbiBuilder.java b/src/main/java/org/lmdbjava/DbiBuilder.java index b478c9df..7824458b 100644 --- a/src/main/java/org/lmdbjava/DbiBuilder.java +++ b/src/main/java/org/lmdbjava/DbiBuilder.java @@ -393,10 +393,10 @@ public Dbi open() { if (txn != null) { return openDbi(txn, dbiBuilder); } else { - try (final Txn txn = getTxn(dbiBuilder)) { - final Dbi dbi = openDbi(txn, dbiBuilder); + try (final Txn localTxn = getTxn(dbiBuilder)) { + final Dbi dbi = openDbi(localTxn, dbiBuilder); // even RO Txns require a commit to retain Dbi in Env - txn.commit(); + localTxn.commit(); return dbi; } } From e9f9ac4f8879ac9f0ca667b66a0cf4eacf7b424e Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Thu, 6 Nov 2025 18:46:25 +0000 Subject: [PATCH 42/90] Add test coverage on FlagSet --- src/main/java/org/lmdbjava/CopyFlagSet.java | 2 +- .../org/lmdbjava/AbstractFlagSetTest.java | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/lmdbjava/CopyFlagSet.java b/src/main/java/org/lmdbjava/CopyFlagSet.java index e21680dd..5350bada 100644 --- a/src/main/java/org/lmdbjava/CopyFlagSet.java +++ b/src/main/java/org/lmdbjava/CopyFlagSet.java @@ -21,7 +21,7 @@ public interface CopyFlagSet extends FlagSet { - static CopyFlagSet EMPTY = CopyFlagSetImpl.EMPTY; + CopyFlagSet EMPTY = CopyFlagSetImpl.EMPTY; static CopyFlagSet empty() { return CopyFlagSetImpl.EMPTY; diff --git a/src/test/java/org/lmdbjava/AbstractFlagSetTest.java b/src/test/java/org/lmdbjava/AbstractFlagSetTest.java index 011d3efd..7358416b 100644 --- a/src/test/java/org/lmdbjava/AbstractFlagSetTest.java +++ b/src/test/java/org/lmdbjava/AbstractFlagSetTest.java @@ -159,6 +159,39 @@ void testAllFlags() { } } + /** + * Test as an enum instance rather than a {@link FlagSet} + */ + @Test + void testAsFlag() { + final T flag = getFirst(); + assertThat(flag.size()) + .isEqualTo(1); + assertThat(flag.getFlags()) + .hasSize(1); + final T flag2 = flag.getFlags().iterator().next(); + assertThat(flag2 == flag) + .isTrue(); + assertThat(flag.getMask()) + .isEqualTo(MaskedFlag.mask(flag)); + assertThat(flag.isEmpty()) + .isFalse(); + assertThat(flag.toString()) + .isNotNull(); + assertThat(flag.isSet(flag)) + .isTrue(); + assertThat(flag.isSet(flag2)) + .isTrue(); + assertThat(flag.isSet(null)) + .isFalse(); + final List allFlags = getAllFlags(); + if (allFlags.size() > 1) { + T secondFlag = allFlags.get(1); + assertThat(flag.isSet(secondFlag)) + .isFalse(); + } + } + private T[] toArray(final int cnt) { //noinspection unchecked return (T[]) Array.newInstance(getFlagType(), cnt); From b0112bb788347043ba4d19c3f138c06404d0fb35 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Thu, 6 Nov 2025 19:02:00 +0000 Subject: [PATCH 43/90] Add tests --- src/test/java/org/lmdbjava/EnvTest.java | 52 ++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/lmdbjava/EnvTest.java b/src/test/java/org/lmdbjava/EnvTest.java index a2e3cb47..e1e72401 100644 --- a/src/test/java/org/lmdbjava/EnvTest.java +++ b/src/test/java/org/lmdbjava/EnvTest.java @@ -32,6 +32,7 @@ import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; import java.util.Random; import org.junit.jupiter.api.Test; import org.lmdbjava.Env.AlreadyClosedException; @@ -371,8 +372,11 @@ void detectTransactionThreadViolation() { void info() { FileUtil.useTempFile( file -> { - try (Env env = - Env.create().setMaxReaders(4).setMapSize(123_456).setEnvFlags(MDB_NOSUBDIR).open(file)) { + try (Env env = Env.create() + .setMaxReaders(4) + .setMapSize(123_456) + .setEnvFlags(MDB_NOSUBDIR) + .open(file)) { final EnvInfo info = env.info(); assertThat(info).isNotNull(); assertThat(info.lastPageNumber).isEqualTo(1L); @@ -470,6 +474,10 @@ void setMapSize() { } assertThat(mapFullExThrown).isTrue(); + assertThatThrownBy(() -> { + env.setMapSize(-1, ByteUnit.KIBIBYTES); + }).isInstanceOf(IllegalArgumentException.class); + env.setMapSize(1024, ByteUnit.KIBIBYTES); try (Txn roTxn = env.txnRead()) { @@ -528,4 +536,44 @@ void testDefaultOpen() { } }); } + + @Test + void testDefaultOpenNoName1() { + FileUtil.useTempDir( + dir -> { + try (Env env = Env.create().setMapSize(10, ByteUnit.MEBIBYTES).open(dir)) { + final EnvInfo info = env.info(); + assertThat(info.maxReaders).isEqualTo(MAX_READERS_DEFAULT); + final Dbi db = env.openDbi((String) null, MDB_CREATE); + db.put(bb("abc"), allocateDirect(1)); + db.put(bb("def"), allocateDirect(1)); + + // As this is the unnamed database it returns all keys in the unnamed db + final List dbiNames = env.getDbiNames(); + assertThat(dbiNames) + .hasSize(2); + assertThat(dbiNames.get(0)) + .isEqualTo("abc".getBytes(Env.DEFAULT_NAME_CHARSET)); + } + }); + } + + @Test + void testDefaultOpenNoName2() { + FileUtil.useTempDir( + dir -> { + try (Env env = Env.create().setMapSize(10, ByteUnit.MEBIBYTES).open(dir)) { + final EnvInfo info = env.info(); + assertThat(info.maxReaders).isEqualTo(MAX_READERS_DEFAULT); + final Dbi db = env.openDbi((byte[]) null, MDB_CREATE); + + // As this is the unnamed database it returns all keys in the unnamed db + final List dbiNames = env.getDbiNames(); + assertThat(dbiNames) + .hasSize(1); + assertThat(dbiNames.get(0)) + .isEqualTo(new byte[0]); + } + }); + } } From c25a7d243d2ca9103f527f784185d35ceae4c330 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Fri, 7 Nov 2025 10:09:54 +0000 Subject: [PATCH 44/90] Run format plugin --- .../java/org/lmdbjava/AbstractFlagSet.java | 28 +- src/main/java/org/lmdbjava/BufferProxy.java | 1 - src/main/java/org/lmdbjava/ByteBufProxy.java | 15 +- .../java/org/lmdbjava/ByteBufferProxy.java | 36 +- src/main/java/org/lmdbjava/ByteUnit.java | 45 +- src/main/java/org/lmdbjava/CopyFlagSet.java | 21 +- src/main/java/org/lmdbjava/Cursor.java | 85 ++-- .../java/org/lmdbjava/CursorIterable.java | 4 - src/main/java/org/lmdbjava/Dbi.java | 83 ++-- src/main/java/org/lmdbjava/DbiBuilder.java | 305 +++++------- src/main/java/org/lmdbjava/DbiFlagSet.java | 25 +- src/main/java/org/lmdbjava/DbiFlags.java | 36 +- .../java/org/lmdbjava/DirectBufferProxy.java | 18 +- src/main/java/org/lmdbjava/Env.java | 306 +++++------- src/main/java/org/lmdbjava/EnvFlagSet.java | 21 +- src/main/java/org/lmdbjava/FlagSet.java | 33 +- src/main/java/org/lmdbjava/MaskedFlag.java | 4 +- src/main/java/org/lmdbjava/PutFlagSet.java | 65 ++- src/main/java/org/lmdbjava/Txn.java | 4 +- src/main/java/org/lmdbjava/TxnFlagSet.java | 26 +- .../org/lmdbjava/AbstractFlagSetTest.java | 156 ++---- .../org/lmdbjava/ByteBufferProxyTest.java | 124 ++--- src/test/java/org/lmdbjava/ByteUnitTest.java | 1 - .../lmdbjava/ComparatorIntegerKeyTest.java | 101 ++-- .../java/org/lmdbjava/CopyFlagSetTest.java | 69 ++- .../CursorIterableIntegerDupTest.java | 310 ++++++------ .../CursorIterableIntegerKeyTest.java | 335 ++++++------- .../org/lmdbjava/CursorIterablePerfTest.java | 33 +- .../java/org/lmdbjava/CursorIterableTest.java | 368 ++++++++------- .../java/org/lmdbjava/CursorParamTest.java | 3 +- src/test/java/org/lmdbjava/CursorTest.java | 3 +- .../java/org/lmdbjava/DbiBuilderTest.java | 182 +++---- .../java/org/lmdbjava/DbiFlagSetTest.java | 69 ++- src/test/java/org/lmdbjava/DbiTest.java | 225 ++++----- .../java/org/lmdbjava/EnvFlagSetTest.java | 69 ++- src/test/java/org/lmdbjava/EnvTest.java | 444 +++++++++--------- .../org/lmdbjava/GarbageCollectionTest.java | 5 +- .../java/org/lmdbjava/PutFlagSetTest.java | 15 +- src/test/java/org/lmdbjava/TestUtils.java | 15 +- src/test/java/org/lmdbjava/TutorialTest.java | 50 +- .../java/org/lmdbjava/TxnFlagSetTest.java | 3 +- src/test/java/org/lmdbjava/TxnTest.java | 6 +- src/test/java/org/lmdbjava/VerifierTest.java | 17 +- 43 files changed, 1736 insertions(+), 2028 deletions(-) diff --git a/src/main/java/org/lmdbjava/AbstractFlagSet.java b/src/main/java/org/lmdbjava/AbstractFlagSet.java index 849bbe73..fbaa4c6f 100644 --- a/src/main/java/org/lmdbjava/AbstractFlagSet.java +++ b/src/main/java/org/lmdbjava/AbstractFlagSet.java @@ -24,9 +24,7 @@ import java.util.function.Function; import java.util.function.Supplier; -/** - * Encapsulates an immutable set of flags and the associated bit mask for the flags in the set. - */ +/** Encapsulates an immutable set of flags and the associated bit mask for the flags in the set. */ public abstract class AbstractFlagSet & MaskedFlag> implements FlagSet { private final Set flags; @@ -60,8 +58,7 @@ public Set getFlags() { @Override public boolean isSet(final T flag) { // Probably cheaper to compare the masks than to use EnumSet.contains() - return flag != null - && MaskedFlag.isSet(mask, flag); + return flag != null && MaskedFlag.isSet(mask, flag); } /** @@ -103,11 +100,10 @@ public String toString() { return FlagSet.asString(this); } - // -------------------------------------------------------------------------------- - - static abstract class AbstractSingleFlagSet & MaskedFlag> implements FlagSet { + abstract static class AbstractSingleFlagSet & MaskedFlag> + implements FlagSet { private final T flag; // Only holding this for iterator() and getFlags() so make it lazy. @@ -186,10 +182,8 @@ private Set initSet() { } } - // -------------------------------------------------------------------------------- - static class AbstractEmptyFlagSet implements FlagSet { @Override @@ -243,10 +237,8 @@ public int hashCode() { } } - // -------------------------------------------------------------------------------- - /** * A builder for creating a {@link AbstractFlagSet}. * @@ -261,10 +253,11 @@ public static class Builder & MaskedFlag, S extends FlagSet final Function singletonSetConstructor; final Supplier emptySetSupplier; - protected Builder(final Class type, - final Function, S> constructor, - final Function singletonSetConstructor, - final Supplier emptySetSupplier) { + protected Builder( + final Class type, + final Function, S> constructor, + final Function singletonSetConstructor, + final Supplier emptySetSupplier) { this.type = type; this.enumSet = EnumSet.noneOf(type); this.constructor = Objects.requireNonNull(constructor); @@ -273,7 +266,8 @@ protected Builder(final Class type, } /** - * Replaces any flags already set in the builder with the contents of the passed flags {@link Collection} + * Replaces any flags already set in the builder with the contents of the passed flags {@link + * Collection} * * @param flags The flags to set in the builder. * @return this builder instance. diff --git a/src/main/java/org/lmdbjava/BufferProxy.java b/src/main/java/org/lmdbjava/BufferProxy.java index f857ade7..a3c339bf 100644 --- a/src/main/java/org/lmdbjava/BufferProxy.java +++ b/src/main/java/org/lmdbjava/BufferProxy.java @@ -40,7 +40,6 @@ public abstract class BufferProxy { /** Offset from a pointer of the MDB_val.mv_size field. */ protected static final int STRUCT_FIELD_OFFSET_SIZE = 0; - /** Explicitly-defined default constructor to avoid warnings. */ protected BufferProxy() {} diff --git a/src/main/java/org/lmdbjava/ByteBufProxy.java b/src/main/java/org/lmdbjava/ByteBufProxy.java index 19d94392..bcbb6ebf 100644 --- a/src/main/java/org/lmdbjava/ByteBufProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufProxy.java @@ -89,8 +89,8 @@ public static int compareLexicographically(final ByteBuf o1, final ByteBuf o2) { } /** - * Buffer comparator specifically for 4/8 byte keys that are unsigned ints/longs, - * i.e. when using MDB_INTEGER_KEY/MDB_INTEGERDUP. Compares the buffers numerically. + * Buffer comparator specifically for 4/8 byte keys that are unsigned ints/longs, i.e. when using + * MDB_INTEGER_KEY/MDB_INTEGERDUP. Compares the buffers numerically. * * @param o1 left operand (required) * @param o2 right operand (required) @@ -101,12 +101,17 @@ public static int compareAsIntegerKeys(final ByteBuf o1, final ByteBuf o2) { requireNonNull(o2); // Both buffers should be same length according to LMDB API. // From the LMDB docs for MDB_INTEGER_KEY - // numeric keys in native byte order: either unsigned int or size_t. The keys must all be of the same size. + // numeric keys in native byte order: either unsigned int or size_t. The keys must all be of the + // same size. final int len1 = o1.readableBytes(); final int len2 = o2.readableBytes(); if (len1 != len2) { - throw new RuntimeException("Length mismatch, len1: " + len1 + ", len2: " + len2 - + ". Lengths must be identical and either 4 or 8 bytes."); + throw new RuntimeException( + "Length mismatch, len1: " + + len1 + + ", len2: " + + len2 + + ". Lengths must be identical and either 4 or 8 bytes."); } if (len1 == 8) { final long lw; diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index 1f82e953..52bfc924 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -55,9 +55,7 @@ public final class ByteBufferProxy { */ public static final BufferProxy PROXY_OPTIMAL; - /** - * The safe, reflective {@link ByteBuffer} proxy for this system. Guaranteed to never be null. - */ + /** The safe, reflective {@link ByteBuffer} proxy for this system. Guaranteed to never be null. */ public static final BufferProxy PROXY_SAFE; private static final ByteOrder NATIVE_ORDER = ByteOrder.nativeOrder(); @@ -67,8 +65,7 @@ public final class ByteBufferProxy { PROXY_OPTIMAL = getProxyOptimal(); } - private ByteBufferProxy() { - } + private ByteBufferProxy() {} private static BufferProxy getProxyOptimal() { try { @@ -78,25 +75,19 @@ private static BufferProxy getProxyOptimal() { } } - /** - * The buffer must be a direct buffer (not heap allocated). - */ + /** The buffer must be a direct buffer (not heap allocated). */ public static final class BufferMustBeDirectException extends LmdbException { private static final long serialVersionUID = 1L; - /** - * Creates a new instance. - */ + /** Creates a new instance. */ public BufferMustBeDirectException() { super("The buffer must be a direct buffer (not heap allocated"); } } - // -------------------------------------------------------------------------------- - /** * Provides {@link ByteBuffer} pooling and address resolution for concrete {@link BufferProxy} * implementations. @@ -150,8 +141,8 @@ public static int compareLexicographically(final ByteBuffer o1, final ByteBuffer } /** - * Buffer comparator specifically for 4/8 byte keys that are unsigned ints/longs, - * i.e. when using MDB_INTEGER_KEY/MDB_INTEGERDUP. Compares the buffers numerically. + * Buffer comparator specifically for 4/8 byte keys that are unsigned ints/longs, i.e. when + * using MDB_INTEGER_KEY/MDB_INTEGERDUP. Compares the buffers numerically. * * @param o1 left operand (required) * @param o2 right operand (required) @@ -162,12 +153,17 @@ public static int compareAsIntegerKeys(final ByteBuffer o1, final ByteBuffer o2) requireNonNull(o2); // Both buffers should be same length according to LMDB API. // From the LMDB docs for MDB_INTEGER_KEY - // numeric keys in native byte order: either unsigned int or size_t. The keys must all be of the same size. + // numeric keys in native byte order: either unsigned int or size_t. The keys must all be of + // the same size. final int len1 = o1.limit(); final int len2 = o2.limit(); if (len1 != len2) { - throw new RuntimeException("Length mismatch, len1: " + len1 + ", len2: " + len2 - + ". Lengths must be identical and either 4 or 8 bytes."); + throw new RuntimeException( + "Length mismatch, len1: " + + len1 + + ", len2: " + + len2 + + ". Lengths must be identical and either 4 or 8 bytes."); } // Keys for MDB_INTEGER_KEY are written in native order so ensure we read them in that order o1.order(NATIVE_ORDER); @@ -250,10 +246,8 @@ protected byte[] getBytes(final ByteBuffer buffer) { } } - // -------------------------------------------------------------------------------- - /** * A proxy that uses Java reflection to modify byte buffer fields, and official JNR-FFF methods to * manipulate native pointers. @@ -298,10 +292,8 @@ protected ByteBuffer out(final ByteBuffer buffer, final Pointer ptr) { } } - // -------------------------------------------------------------------------------- - /** * A proxy that uses Java's "unsafe" class to directly manipulate byte buffer fields and JNR-FFF * allocated memory pointers. diff --git a/src/main/java/org/lmdbjava/ByteUnit.java b/src/main/java/org/lmdbjava/ByteUnit.java index 96d0dba7..ed4b1ca8 100644 --- a/src/main/java/org/lmdbjava/ByteUnit.java +++ b/src/main/java/org/lmdbjava/ByteUnit.java @@ -15,53 +15,30 @@ */ package org.lmdbjava; -/** - * Simple {@link Enum} for converting various IEC and SI byte units down a number of bytes. - */ +/** Simple {@link Enum} for converting various IEC and SI byte units down a number of bytes. */ public enum ByteUnit { - BYTES(1L), - /** - * IEC byte unit for 1024 bytes. - */ + /** IEC byte unit for 1024 bytes. */ KIBIBYTES(1024L), - /** - * IEC byte unit for 1024^2 bytes. - */ + /** IEC byte unit for 1024^2 bytes. */ MEBIBYTES(1_0485_76L), - /** - * IEC byte unit for 1024^3 bytes. - */ + /** IEC byte unit for 1024^3 bytes. */ GIBIBYTES(1_073_741_824L), - /** - * IEC byte unit for 1024^4 bytes. - */ + /** IEC byte unit for 1024^4 bytes. */ TEBIBYTES(1_099_511_627_776L), - /** - * IEC byte unit for 1024^5 bytes. - */ + /** IEC byte unit for 1024^5 bytes. */ PEBIBYTES(1_125_899_906_842_624L), - /** - * SI byte unit for 1000 bytes. - */ + /** SI byte unit for 1000 bytes. */ KILOBYTES(1_000L), - /** - * SI byte unit for 1000^2 bytes. - */ + /** SI byte unit for 1000^2 bytes. */ MEGABYTES(1_000_000L), - /** - * SI byte unit for 1000^3 bytes. - */ + /** SI byte unit for 1000^3 bytes. */ GIGABYTES(1_000_000_000L), - /** - * SI byte unit for 1000^4 bytes. - */ + /** SI byte unit for 1000^4 bytes. */ TERABYTES(1_000_000_000_000L), - /** - * SI byte unit for 1000^5 bytes. - */ + /** SI byte unit for 1000^5 bytes. */ PETABYTES(1_000_000_000_000_000L), ; diff --git a/src/main/java/org/lmdbjava/CopyFlagSet.java b/src/main/java/org/lmdbjava/CopyFlagSet.java index 5350bada..1aed9878 100644 --- a/src/main/java/org/lmdbjava/CopyFlagSet.java +++ b/src/main/java/org/lmdbjava/CopyFlagSet.java @@ -33,29 +33,20 @@ static CopyFlagSet of(final CopyFlags dbiFlag) { } static CopyFlagSet of(final CopyFlags... CopyFlags) { - return builder() - .setFlags(CopyFlags) - .build(); + return builder().setFlags(CopyFlags).build(); } static CopyFlagSet of(final Collection CopyFlags) { - return builder() - .setFlags(CopyFlags) - .build(); + return builder().setFlags(CopyFlags).build(); } static AbstractFlagSet.Builder builder() { return new AbstractFlagSet.Builder<>( - CopyFlags.class, - CopyFlagSetImpl::new, - copyFlag -> copyFlag, - () -> CopyFlagSetImpl.EMPTY); + CopyFlags.class, CopyFlagSetImpl::new, copyFlag -> copyFlag, () -> CopyFlagSetImpl.EMPTY); } - // -------------------------------------------------------------------------------- - class CopyFlagSetImpl extends AbstractFlagSet implements CopyFlagSet { static final CopyFlagSet EMPTY = new EmptyCopyFlagSet(); @@ -65,10 +56,8 @@ private CopyFlagSetImpl(final EnumSet flags) { } } - // -------------------------------------------------------------------------------- - - class EmptyCopyFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet implements CopyFlagSet { - } + class EmptyCopyFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet + implements CopyFlagSet {} } diff --git a/src/main/java/org/lmdbjava/Cursor.java b/src/main/java/org/lmdbjava/Cursor.java index 245a5a9a..09c461e2 100644 --- a/src/main/java/org/lmdbjava/Cursor.java +++ b/src/main/java/org/lmdbjava/Cursor.java @@ -95,13 +95,10 @@ public long count() { checkRc(LIB.mdb_cursor_count(ptrCursor, longByReference)); return longByReference.longValue(); } + /** - * @deprecated Instead use {@link Cursor#delete(PutFlagSet)}. - *


- * Delete current key/data pair. - * - *

This function deletes the key/data pair to which the cursor refers. - * + * @deprecated Instead use {@link Cursor#delete(PutFlagSet)}.


Delete current key/data pair. + *

This function deletes the key/data pair to which the cursor refers. * @param flags flags (either null or {@link PutFlags#MDB_NODUPDATA} */ @Deprecated @@ -132,9 +129,7 @@ public void delete(final PutFlagSet flags) { txn.checkReady(); txn.checkWritesAllowed(); } - final PutFlagSet putFlagSet = flags != null - ? flags - : PutFlagSet.EMPTY; + final PutFlagSet putFlagSet = flags != null ? flags : PutFlagSet.EMPTY; checkRc(LIB.mdb_cursor_del(ptrCursor, putFlagSet.getMask())); } @@ -257,12 +252,8 @@ public boolean prev() { } /** - * @deprecated Use {@link Cursor#put(Object, Object, PutFlagSet)} instead. - *


- * Store by cursor. - * - *

This function stores key/data pairs into the database. - * + * @deprecated Use {@link Cursor#put(Object, Object, PutFlagSet)} instead.


Store by cursor. + *

This function stores key/data pairs into the database. * @param key key to store * @param val data to store * @param flags options for this operation @@ -310,10 +301,9 @@ public boolean put(final T key, final T val, final PutFlagSet flags) { } final Pointer transientKey = kv.keyIn(key); final Pointer transientVal = kv.valIn(val); - final PutFlagSet putFlagSet = flags != null - ? flags - : PutFlagSet.EMPTY; - final int rc = LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), putFlagSet.getMask()); + final PutFlagSet putFlagSet = flags != null ? flags : PutFlagSet.EMPTY; + final int rc = + LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), putFlagSet.getMask()); if (rc == MDB_KEYEXIST) { if (putFlagSet.isSet(MDB_NOOVERWRITE)) { kv.valOut(); // marked as in,out in LMDB C docs @@ -331,15 +321,12 @@ public boolean put(final T key, final T val, final PutFlagSet flags) { } /** - * @deprecated Use {@link Cursor#put(Object, Object, PutFlagSet)} instead. - *


- * Put multiple values into the database in one MDB_MULTIPLE operation. - * - *

The database must have been opened with {@link DbiFlags#MDB_DUPFIXED}. The buffer must - * contain fixed-sized values to be inserted. The size of each element is calculated from the - * buffer's size divided by the given element count. For example, to populate 10 X 4 byte integers - * at once, present a buffer of 40 bytes and specify the element as 10. - * + * @deprecated Use {@link Cursor#put(Object, Object, PutFlagSet)} instead.


Put multiple + * values into the database in one MDB_MULTIPLE operation. + *

The database must have been opened with {@link DbiFlags#MDB_DUPFIXED}. The buffer must + * contain fixed-sized values to be inserted. The size of each element is calculated from the + * buffer's size divided by the given element count. For example, to populate 10 X 4 byte + * integers at once, present a buffer of 40 bytes and specify the element as 10. * @param key key to store in the database (not null) * @param val value to store in the database (not null) * @param elements number of elements contained in the passed value buffer @@ -377,8 +364,8 @@ public void putMultiple(final T key, final T val, final int elements) { * @param key key to store in the database (not null) * @param val value to store in the database (not null) * @param elements number of elements contained in the passed value buffer - * @param flags options for operation (must set MDB_MULTIPLE) - * Either a {@link PutFlagSet} or a single {@link PutFlags}. + * @param flags options for operation (must set MDB_MULTIPLE) Either a {@link + * PutFlagSet} or a single {@link PutFlags}. */ public void putMultiple(final T key, final T val, final int elements, final PutFlagSet flags) { if (SHOULD_CHECK) { @@ -389,15 +376,14 @@ public void putMultiple(final T key, final T val, final int elements, final PutF txn.checkReady(); txn.checkWritesAllowed(); } - final PutFlagSet putFlagSet = flags != null - ? flags - : PutFlagSet.EMPTY; + final PutFlagSet putFlagSet = flags != null ? flags : PutFlagSet.EMPTY; if (SHOULD_CHECK && !putFlagSet.isSet(MDB_MULTIPLE)) { throw new IllegalArgumentException("Must set " + MDB_MULTIPLE + " flag"); } final Pointer transientKey = txn.kv().keyIn(key); final Pointer dataPtr = txn.kv().valInMulti(val, elements); - final int rc = LIB.mdb_cursor_put(ptrCursor, txn.kv().pointerKey(), dataPtr, putFlagSet.getMask()); + final int rc = + LIB.mdb_cursor_put(ptrCursor, txn.kv().pointerKey(), dataPtr, putFlagSet.getMask()); checkRc(rc); ReferenceUtil.reachabilityFence0(transientKey); ReferenceUtil.reachabilityFence0(dataPtr); @@ -429,16 +415,13 @@ public void renew(final Txn newTxn) { } /** - * @deprecated Use {@link Cursor#reserve(Object, int, PutFlagSet)} instead. - *


- * Reserve space for data of the given size, but don't copy the given val. Instead, return a - * pointer to the reserved space, which the caller can fill in later - before the next update - * operation or the transaction ends. This saves an extra memcpy if the data is being generated - * later. LMDB does nothing else with this memory, the caller is expected to modify all of the - * space requested. - * - *

This flag must not be specified if the database was opened with MDB_DUPSORT - * + * @deprecated Use {@link Cursor#reserve(Object, int, PutFlagSet)} instead.


Reserve space for + * data of the given size, but don't copy the given val. Instead, return a pointer to the + * reserved space, which the caller can fill in later - before the next update operation or + * the transaction ends. This saves an extra memcpy if the data is being generated later. LMDB + * does nothing else with this memory, the caller is expected to modify all of the space + * requested. + *

This flag must not be specified if the database was opened with MDB_DUPSORT * @param key key to store in the database (not null) * @param size size of the value to be stored in the database (not null) * @param flags options for this operation @@ -452,9 +435,9 @@ public T reserve(final T key, final int size, final PutFlags... flags) { /** * Reserve space for data of the given size, but don't copy the given val. Instead, return a * pointer to the reserved space, which the caller can fill in later - before the next update - * operation or the transaction ends. This saves an extra {@code memcpy} if the data is being generated - * later. LMDB does nothing else with this memory, the caller is expected to modify all the - * space requested. + * operation or the transaction ends. This saves an extra {@code memcpy} if the data is being + * generated later. LMDB does nothing else with this memory, the caller is expected to modify all + * the space requested. * *

This flag must not be specified if the database was opened with MDB_DUPSORT * @@ -470,8 +453,8 @@ public T reserve(final T key, final int size) { * Reserve space for data of the given size, but don't copy the given val. Instead, return a * pointer to the reserved space, which the caller can fill in later - before the next update * operation or the transaction ends. This saves an extra memcpy if the data is being generated - * later. LMDB does nothing else with this memory, the caller is expected to modify all the - * space requested. + * later. LMDB does nothing else with this memory, the caller is expected to modify all the space + * requested. * *

This flag must not be specified if the database was opened with MDB_DUPSORT * @@ -490,9 +473,7 @@ public T reserve(final T key, final int size, final PutFlagSet flags) { } final Pointer transientKey = kv.keyIn(key); final Pointer transientVal = kv.valIn(size); - final PutFlagSet putFlagSet = flags != null - ? flags - : PutFlagSet.EMPTY; + final PutFlagSet putFlagSet = flags != null ? flags : PutFlagSet.EMPTY; // This is inconsistent with putMultiple which require MDB_MULTIPLE to be in the set. final int flagsMask = putFlagSet.getMaskWith(MDB_RESERVE); checkRc(LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), flagsMask)); diff --git a/src/main/java/org/lmdbjava/CursorIterable.java b/src/main/java/org/lmdbjava/CursorIterable.java index 89dc5e84..2289f130 100644 --- a/src/main/java/org/lmdbjava/CursorIterable.java +++ b/src/main/java/org/lmdbjava/CursorIterable.java @@ -239,10 +239,8 @@ enum State { TERMINATED } - // -------------------------------------------------------------------------------- - static class JavaRangeComparator implements RangeComparator { private final Comparator comparator; @@ -276,10 +274,8 @@ public void close() throws Exception { } } - // -------------------------------------------------------------------------------- - /** * Calls down to mdb_cmp to make use of the comparator that LMDB uses for insertion order. Has a * very slight overhead as compared to {@link JavaRangeComparator}. diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index 65527224..12ff6a36 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -51,6 +51,7 @@ public final class Dbi { @SuppressWarnings("FieldCanBeLocal") // Needs to be instance variable for FFI private final ComparatorCallback callbackComparator; + private boolean cleaned; // Used for CursorIterable KeyRange testing and/or native callbacks private final Comparator comparator; @@ -60,7 +61,8 @@ public final class Dbi { private final BufferProxy proxy; private final DbiFlagSet dbiFlagSet; - Dbi(final Env env, + Dbi( + final Env env, final Txn txn, final byte[] name, final BufferProxy proxy, @@ -218,7 +220,7 @@ public void drop(final Txn txn) { * closed. See {@link #close()} for implication of handle close. Otherwise, only the data in this * database will be dropped. * - * @param txn transaction handle (not null; not committed; must be R-W) + * @param txn transaction handle (not null; not committed; must be R-W) * @param delete whether database should be deleted. */ public void drop(final Txn txn, final boolean delete) { @@ -280,12 +282,11 @@ public String getNameAsString() { return getNameAsString(Env.DEFAULT_NAME_CHARSET); } - /** * Obtains the name of this database, using the supplied {@link Charset}. * - * @return The name of the database. If this is the unnamed database an empty - * string will be returned. + * @return The name of the database. If this is the unnamed database an empty string will be + * returned. * @throws RuntimeException if the name can't be decoded. */ public String getNameAsString(final Charset charset) { @@ -314,7 +315,7 @@ public CursorIterable iterate(final Txn txn) { /** * Iterate the database in accordance with the provided {@link KeyRange}. * - * @param txn transaction handle (not null; not committed) + * @param txn transaction handle (not null; not committed) * @param range range of acceptable keys (not null) * @return iterator (never null) */ @@ -395,21 +396,18 @@ public void put(final T key, final T val) { } /** - * @param txn transaction handle (not null; not committed; must be R-W) - * @param key key to store in the database (not null) - * @param val value to store in the database (not null) + * @param txn transaction handle (not null; not committed; must be R-W) + * @param key key to store in the database (not null) + * @param val value to store in the database (not null) * @param flags Special options for this operation * @return true if the value was put, false if MDB_NOOVERWRITE or MDB_NODUPDATA were set and the - * key/value existed already. + * key/value existed already. * @deprecated Use {@link Dbi#put(Txn, Object, Object, PutFlagSet)} instead, with a statically - * held {@link PutFlagSet}. - *


- *

- * Store a key/value pair in the database. - *

- *

This function stores key/data pairs in the database. The default behavior is to enter the - * new key/data pair, replacing any previously existing key if duplicates are disallowed, or - * adding a duplicate data item if duplicates are allowed ({@link DbiFlags#MDB_DUPSORT}). + * held {@link PutFlagSet}.


+ *

Store a key/value pair in the database. + *

This function stores key/data pairs in the database. The default behavior is to enter + * the new key/data pair, replacing any previously existing key if duplicates are disallowed, + * or adding a duplicate data item if duplicates are allowed ({@link DbiFlags#MDB_DUPSORT}). */ @Deprecated public boolean put(final Txn txn, final T key, final T val, final PutFlags... flags) { @@ -423,7 +421,7 @@ public boolean put(final Txn txn, final T key, final T val, final PutFlags... * @param key key to store in the database (not null) * @param val value to store in the database (not null) * @return true if the value was put, false if MDB_NOOVERWRITE or MDB_NODUPDATA were set and the - * key/value existed already. + * key/value existed already. * @see #put(Txn, Object, Object, PutFlagSet) */ public boolean put(final Txn txn, final T key, final T val) { @@ -437,12 +435,12 @@ public boolean put(final Txn txn, final T key, final T val) { * new key/data pair, replacing any previously existing key if duplicates are disallowed, or * adding a duplicate data item if duplicates are allowed ({@link DbiFlags#MDB_DUPSORT}). * - * @param txn transaction handle (not null; not committed; must be R-W) - * @param key key to store in the database (not null) - * @param val value to store in the database (not null) + * @param txn transaction handle (not null; not committed; must be R-W) + * @param key key to store in the database (not null) + * @param val value to store in the database (not null) * @param flags Special options for this operation. * @return true if the value was put, false if MDB_NOOVERWRITE or MDB_NODUPDATA were set and the - * key/value existed already. + * key/value existed already. */ public boolean put(final Txn txn, final T key, final T val, final PutFlagSet flags) { if (SHOULD_CHECK) { @@ -456,7 +454,9 @@ public boolean put(final Txn txn, final T key, final T val, final PutFlagSet final PutFlagSet flagSet = flags != null ? flags : PutFlagSet.empty(); final Pointer transientKey = txn.kv().keyIn(key); final Pointer transientVal = txn.kv().valIn(val); - final int rc = LIB.mdb_put(txn.pointer(), ptr, txn.kv().pointerKey(), txn.kv().pointerVal(), flagSet.getMask()); + final int rc = + LIB.mdb_put( + txn.pointer(), ptr, txn.kv().pointerKey(), txn.kv().pointerVal(), flagSet.getMask()); if (rc == MDB_KEYEXIST) { if (flagSet.isSet(MDB_NOOVERWRITE)) { txn.kv().valOut(); // marked as in,out in LMDB C docs @@ -482,10 +482,10 @@ public boolean put(final Txn txn, final T key, final T val, final PutFlagSet * *

This flag must not be specified if the database was opened with MDB_DUPSORT * - * @param txn transaction handle (not null; not committed; must be R-W) - * @param key key to store in the database (not null) + * @param txn transaction handle (not null; not committed; must be R-W) + * @param key key to store in the database (not null) * @param size size of the value to be stored in the database - * @param op options for this operation + * @param op options for this operation * @return a buffer that can be used to modify the value */ public T reserve(final Txn txn, final T key, final int size, final PutFlags... op) { @@ -545,15 +545,10 @@ public String toString() { } catch (Exception e) { name = "?"; } - return "Dbi{" + - "name='" + name + - "', dbiFlagSet=" + dbiFlagSet + - '}'; + return "Dbi{" + "name='" + name + "', dbiFlagSet=" + dbiFlagSet + '}'; } - /** - * The specified DBI was changed unexpectedly. - */ + /** The specified DBI was changed unexpectedly. */ public static final class BadDbiException extends LmdbNativeException { static final int MDB_BAD_DBI = -30_780; @@ -564,9 +559,7 @@ public static final class BadDbiException extends LmdbNativeException { } } - /** - * Unsupported size of key/DB name/data, or wrong DUPFIXED size. - */ + /** Unsupported size of key/DB name/data, or wrong DUPFIXED size. */ public static final class BadValueSizeException extends LmdbNativeException { static final int MDB_BAD_VALSIZE = -30_781; @@ -577,9 +570,7 @@ public static final class BadValueSizeException extends LmdbNativeException { } } - /** - * Environment maxdbs reached. - */ + /** Environment maxdbs reached. */ public static final class DbFullException extends LmdbNativeException { static final int MDB_DBS_FULL = -30_791; @@ -612,9 +603,7 @@ public static final class IncompatibleException extends LmdbNativeException { } } - /** - * Key/data pair already exists. - */ + /** Key/data pair already exists. */ public static final class KeyExistsException extends LmdbNativeException { static final int MDB_KEYEXIST = -30_799; @@ -625,9 +614,7 @@ public static final class KeyExistsException extends LmdbNativeException { } } - /** - * Key/data pair not found (EOF). - */ + /** Key/data pair not found (EOF). */ public static final class KeyNotFoundException extends LmdbNativeException { static final int MDB_NOTFOUND = -30_798; @@ -638,9 +625,7 @@ public static final class KeyNotFoundException extends LmdbNativeException { } } - /** - * Database contents grew beyond environment mapsize. - */ + /** Database contents grew beyond environment mapsize. */ public static final class MapResizedException extends LmdbNativeException { static final int MDB_MAP_RESIZED = -30_785; diff --git a/src/main/java/org/lmdbjava/DbiBuilder.java b/src/main/java/org/lmdbjava/DbiBuilder.java index 7824458b..d5d1af8c 100644 --- a/src/main/java/org/lmdbjava/DbiBuilder.java +++ b/src/main/java/org/lmdbjava/DbiBuilder.java @@ -28,37 +28,29 @@ */ public class DbiBuilder { - private final Env env; private final BufferProxy proxy; private final boolean readOnly; private byte[] name; - DbiBuilder(final Env env, - final BufferProxy proxy, - final boolean readOnly) { + DbiBuilder(final Env env, final BufferProxy proxy, final boolean readOnly) { this.env = Objects.requireNonNull(env); this.proxy = Objects.requireNonNull(proxy); this.readOnly = readOnly; } /** - *

* Create the {@link Dbi} with the passed name. - *

- *

- * The name will be converted into bytes using {@link StandardCharsets#UTF_8}. - *

* - * @param name The name of the database or null for the unnamed database - * (see also {@link DbiBuilder#withoutDbName()}) + *

The name will be converted into bytes using {@link StandardCharsets#UTF_8}. + * + * @param name The name of the database or null for the unnamed database (see also {@link + * DbiBuilder#withoutDbName()}) * @return The next builder stage. */ public DbiBuilderStage2 setDbName(final String name) { // Null name is allowed so no null check - final byte[] nameBytes = name == null - ? null - : name.getBytes(Env.DEFAULT_NAME_CHARSET); + final byte[] nameBytes = name == null ? null : name.getBytes(Env.DEFAULT_NAME_CHARSET); return setDbName(nameBytes); } @@ -75,16 +67,14 @@ public DbiBuilderStage2 setDbName(final byte[] name) { } /** - *

* Create the {@link Dbi} without a name. - *

- *

- * Equivalent to passing null to - * {@link DbiBuilder#setDbName(String)} or {@link DbiBuilder#setDbName(byte[])}. - *

- *

Note: The 'unnamed database' is used by LMDB to store the names of named databases, with - * the database name being the key. Use of the unnamed database is intended for simple applications - * with only one database.

+ * + *

Equivalent to passing null to {@link DbiBuilder#setDbName(String)} or {@link + * DbiBuilder#setDbName(byte[])}. + * + *

Note: The 'unnamed database' is used by LMDB to store the names of named databases, with the + * database name being the key. Use of the unnamed database is intended for simple applications + * with only one database. * * @return The next builder stage. */ @@ -92,10 +82,8 @@ public DbiBuilderStage2 withoutDbName() { return setDbName((byte[]) null); } - // -------------------------------------------------------------------------------- - /** * Intermediate builder stage for constructing a {@link Dbi}. * @@ -113,29 +101,24 @@ private DbiBuilderStage2(final DbiBuilder dbiBuilder) { } /** - *

- * This is the default choice when it comes to choosing a comparator. - * If you are not sure of the implications of the other methods then use this one as it - * is likely what you want and also probably the most performant. - *

- *

- * With this option, {@link CursorIterable} will make use of the LmdbJava's default - * Java-side comparators when comparing iteration keys to the start/stop keys. - * LMDB will use its own comparator for controlling insertion order in the database. - * The two comparators are functionally identical. - *

- *

- * This option may be slightly more performant than when using - * {@link DbiBuilderStage2#withNativeComparator()} which calls down to LMDB for ALL - * comparison operations. - *

- *

- * If you do not intend to use {@link CursorIterable} then it doesn't matter whether - * you choose {@link DbiBuilderStage2#withNativeComparator()}, - * {@link DbiBuilderStage2#withDefaultComparator()} or - * {@link DbiBuilderStage2#withIteratorComparator(ComparatorFactory)} as these comparators will - * never be used. - *

+ * This is the default choice when it comes to choosing a comparator. If you + * are not sure of the implications of the other methods then use this one as it is likely what + * you want and also probably the most performant. + * + *

With this option, {@link CursorIterable} will make use of the LmdbJava's default Java-side + * comparators when comparing iteration keys to the start/stop keys. LMDB will use its own + * comparator for controlling insertion order in the database. The two comparators are + * functionally identical. + * + *

This option may be slightly more performant than when using {@link + * DbiBuilderStage2#withNativeComparator()} which calls down to LMDB for ALL comparison + * operations. + * + *

If you do not intend to use {@link CursorIterable} then it doesn't matter whether you + * choose {@link DbiBuilderStage2#withNativeComparator()}, {@link + * DbiBuilderStage2#withDefaultComparator()} or {@link + * DbiBuilderStage2#withIteratorComparator(ComparatorFactory)} as these comparators will never + * be used. * * @return The next builder stage. */ @@ -145,24 +128,20 @@ public DbiBuilderStage3 withDefaultComparator() { } /** - *

* With this option, {@link CursorIterable} will call down to LMDB's {@code mdb_cmp} method when * comparing iteration keys to start/stop keys. This ensures LmdbJava is comparing start/stop * keys using the same comparator that is used for insertion order into the db. - *

- *

- * This option may be slightly less performant than when using - * {@link DbiBuilderStage2#withDefaultComparator()} as it needs to call down - * to LMDB to perform the comparisons, however it guarantees that {@link CursorIterable} - * key comparison matches LMDB key comparison. - *

- *

- * If you do not intend to use {@link CursorIterable} then it doesn't matter whether - * you choose {@link DbiBuilderStage2#withNativeComparator()}, - * {@link DbiBuilderStage2#withDefaultComparator()} or - * {@link DbiBuilderStage2#withIteratorComparator(ComparatorFactory)} as these comparators will - * never be used. - *

+ * + *

This option may be slightly less performant than when using {@link + * DbiBuilderStage2#withDefaultComparator()} as it needs to call down to LMDB to perform the + * comparisons, however it guarantees that {@link CursorIterable} key comparison matches LMDB + * key comparison. + * + *

If you do not intend to use {@link CursorIterable} then it doesn't matter whether you + * choose {@link DbiBuilderStage2#withNativeComparator()}, {@link + * DbiBuilderStage2#withDefaultComparator()} or {@link + * DbiBuilderStage2#withIteratorComparator(ComparatorFactory)} as these comparators will never + * be used. * * @return The next builder stage. */ @@ -171,30 +150,27 @@ public DbiBuilderStage3 withNativeComparator() { return new DbiBuilderStage3<>(this); } - /** * Provide a java-side {@link Comparator} that LMDB will call back to for all - * comparison operations. - * Therefore, it will be called by LMDB to manage database insertion/iteration order. - * It will also be used for {@link CursorIterable} start/stop key comparisons. - *

- * It can be useful if you need to sort your database using some other method, - * e.g. signed keys or case-insensitive order. - * Note, if you need keys stored in reverse order, see {@link DbiFlags#MDB_REVERSEKEY} - * and {@link DbiFlags#MDB_REVERSEDUP}. - *

- *

- * As this requires LMDB to call back to java, this will be less performant than using LMDB's - * default comparators, but allows for total control over the order in which entries - * are stored in the database. - *

+ * comparison operations. Therefore, it will be called by LMDB to manage database + * insertion/iteration order. It will also be used for {@link CursorIterable} start/stop key + * comparisons. + * + *

It can be useful if you need to sort your database using some other method, e.g. signed + * keys or case-insensitive order. Note, if you need keys stored in reverse order, see {@link + * DbiFlags#MDB_REVERSEKEY} and {@link DbiFlags#MDB_REVERSEDUP}. * - * @param comparatorFactory A factory to create a comparator. {@link ComparatorFactory#create(DbiFlagSet)} - * will be called once during the initialisation of the {@link Dbi}. It must - * not return null. + *

As this requires LMDB to call back to java, this will be less performant than using LMDB's + * default comparators, but allows for total control over the order in which entries are stored + * in the database. + * + * @param comparatorFactory A factory to create a comparator. {@link + * ComparatorFactory#create(DbiFlagSet)} will be called once during the initialisation of + * the {@link Dbi}. It must not return null. * @return The next builder stage. */ - public DbiBuilderStage3 withCallbackComparator(final ComparatorFactory comparatorFactory) { + public DbiBuilderStage3 withCallbackComparator( + final ComparatorFactory comparatorFactory) { this.comparatorFactory = Objects.requireNonNull(comparatorFactory); this.comparatorType = ComparatorType.CALLBACK; return new DbiBuilderStage3<>(this); @@ -202,44 +178,39 @@ public DbiBuilderStage3 withCallbackComparator(final ComparatorFactory com /** *


- *

- * WARNING: Only use this if you fully understand the risks and implications. - *

- *
- *

- * With this option, {@link CursorIterable} will make use of the passed comparator for + * + *

WARNING: Only use this if you fully understand the risks and + * implications.


+ * + *

With this option, {@link CursorIterable} will make use of the passed comparator for * comparing iteration keys to start/stop keys. It has NO bearing on the * insert/iteration order of the database (which is controlled by LMDB's own comparators). - *

- *

- * It is vital that this comparator is functionally identical to the one + * + *

It is vital that this comparator is functionally identical to the one * used internally in LMDB for insertion/iteration order, else you will see unexpected behaviour * when using {@link CursorIterable}. - *

- *

- * If you do not intend to use {@link CursorIterable} then it doesn't matter whether - * you choose {@link DbiBuilderStage2#withNativeComparator()}, - * {@link DbiBuilderStage2#withDefaultComparator()} or - * {@link DbiBuilderStage2#withIteratorComparator(ComparatorFactory)} as these comparators will - * never be used. - *

* - * @param comparatorFactory The comparator to use with {@link CursorIterable}. - * {@link ComparatorFactory#create(DbiFlagSet)} will be called once during the - * initialisation of the {@link Dbi}. It must not return null. + *

If you do not intend to use {@link CursorIterable} then it doesn't matter whether you + * choose {@link DbiBuilderStage2#withNativeComparator()}, {@link + * DbiBuilderStage2#withDefaultComparator()} or {@link + * DbiBuilderStage2#withIteratorComparator(ComparatorFactory)} as these comparators will never + * be used. + * + * @param comparatorFactory The comparator to use with {@link CursorIterable}. {@link + * ComparatorFactory#create(DbiFlagSet)} will be called once during the initialisation of + * the {@link Dbi}. It must not return null. * @return The next builder stage. */ - public DbiBuilderStage3 withIteratorComparator(final ComparatorFactory comparatorFactory) { + public DbiBuilderStage3 withIteratorComparator( + final ComparatorFactory comparatorFactory) { this.comparatorFactory = Objects.requireNonNull(comparatorFactory); this.comparatorType = ComparatorType.ITERATOR; return new DbiBuilderStage3<>(this); } } - // -------------------------------------------------------------------------------- - /** * Final stage builder for constructing a {@link Dbi}. * @@ -248,7 +219,8 @@ public DbiBuilderStage3 withIteratorComparator(final ComparatorFactory com public static class DbiBuilderStage3 { private final DbiBuilderStage2 dbiBuilderStage2; - private final AbstractFlagSet.Builder flagSetBuilder = DbiFlagSet.builder(); + private final AbstractFlagSet.Builder flagSetBuilder = + DbiFlagSet.builder(); private Txn txn = null; private DbiBuilderStage3(DbiBuilderStage2 dbiBuilderStage2) { @@ -256,68 +228,49 @@ private DbiBuilderStage3(DbiBuilderStage2 dbiBuilderStage2) { } /** - *

* Apply all the dbi flags supplied in dbiFlags. - *

- *

- * Clears all flags currently set by previous calls to - * {@link DbiBuilderStage3#setDbiFlags(Collection)}, - * {@link DbiBuilderStage3#setDbiFlags(DbiFlags...)} + * + *

Clears all flags currently set by previous calls to {@link + * DbiBuilderStage3#setDbiFlags(Collection)}, {@link DbiBuilderStage3#setDbiFlags(DbiFlags...)} * or {@link DbiBuilderStage3#addDbiFlag(DbiFlags)}. - *

* - * @param dbiFlags to open the database with. - * A null {@link Collection} will just clear all set flags. - * Null items are ignored. + * @param dbiFlags to open the database with. A null {@link Collection} will just clear all set + * flags. Null items are ignored. */ public DbiBuilderStage3 setDbiFlags(final Collection dbiFlags) { flagSetBuilder.clear(); if (dbiFlags != null) { - dbiFlags.stream() - .filter(Objects::nonNull) - .forEach(dbiFlags::add); + dbiFlags.stream().filter(Objects::nonNull).forEach(dbiFlags::add); } return this; } /** - *

* Apply all the dbi flags supplied in dbiFlags. - *

- *

- * Clears all flags currently set by previous calls to - * {@link DbiBuilderStage3#setDbiFlags(Collection)}, - * {@link DbiBuilderStage3#setDbiFlags(DbiFlags...)} + * + *

Clears all flags currently set by previous calls to {@link + * DbiBuilderStage3#setDbiFlags(Collection)}, {@link DbiBuilderStage3#setDbiFlags(DbiFlags...)} * or {@link DbiBuilderStage3#addDbiFlag(DbiFlags)}. - *

* - * @param dbiFlags to open the database with. - * A null array will just clear all set flags. - * Null items are ignored. + * @param dbiFlags to open the database with. A null array will just clear all set flags. Null + * items are ignored. */ public DbiBuilderStage3 setDbiFlags(final DbiFlags... dbiFlags) { flagSetBuilder.clear(); if (dbiFlags != null) { - Arrays.stream(dbiFlags) - .filter(Objects::nonNull) - .forEach(this.flagSetBuilder::addFlag); + Arrays.stream(dbiFlags).filter(Objects::nonNull).forEach(this.flagSetBuilder::addFlag); } return this; } /** - *

* Apply all the dbi flags supplied in dbiFlags. - *

- *

- * Clears all flags currently set by previous calls to - * {@link DbiBuilderStage3#setDbiFlags(Collection)}, - * {@link DbiBuilderStage3#setDbiFlags(DbiFlags...)} + * + *

Clears all flags currently set by previous calls to {@link + * DbiBuilderStage3#setDbiFlags(Collection)}, {@link DbiBuilderStage3#setDbiFlags(DbiFlags...)} * or {@link DbiBuilderStage3#addDbiFlag(DbiFlags)}. - *

* - * @param dbiFlagSet to open the database with. - * A null value will just clear all set flags. + * @param dbiFlagSet to open the database with. A null value will just clear all set flags. */ public DbiBuilderStage3 setDbiFlags(final DbiFlagSet dbiFlagSet) { flagSetBuilder.clear(); @@ -328,9 +281,8 @@ public DbiBuilderStage3 setDbiFlags(final DbiFlagSet dbiFlagSet) { } /** - * Adds a dbiFlag to those flags already added to this builder by - * {@link DbiBuilderStage3#setDbiFlags(DbiFlags...)}, - * {@link DbiBuilderStage3#setDbiFlags(Collection)} + * Adds a dbiFlag to those flags already added to this builder by {@link + * DbiBuilderStage3#setDbiFlags(DbiFlags...)}, {@link DbiBuilderStage3#setDbiFlags(Collection)} * or {@link DbiBuilderStage3#addDbiFlag(DbiFlags)}. * * @param dbiFlag to add to any existing flags. A null value is a no-op. @@ -342,9 +294,8 @@ public DbiBuilderStage3 addDbiFlag(final DbiFlags dbiFlag) { } /** - * Adds a dbiFlag to those flags already added to this builder by - * {@link DbiBuilderStage3#setDbiFlags(DbiFlags...)}, - * {@link DbiBuilderStage3#setDbiFlags(Collection)} + * Adds a dbiFlag to those flags already added to this builder by {@link + * DbiBuilderStage3#setDbiFlags(DbiFlags...)}, {@link DbiBuilderStage3#setDbiFlags(Collection)} * or {@link DbiBuilderStage3#addDbiFlag(DbiFlags)}. * * @param dbiFlagSet to add to any existing flags. A null value is a no-op. @@ -359,19 +310,17 @@ public DbiBuilderStage3 addDbiFlags(final DbiFlagSet dbiFlagSet) { /** * Use the supplied transaction to open the {@link Dbi}. - *

- * The caller MUST commit the transaction after calling {@link DbiBuilderStage3#open()}, - * in order to retain the Dbi in the Env. - *

- *

- * If you don't call this method to supply a {@link Txn}, a {@link Txn} will be opened for the purpose - * of creating and opening the {@link Dbi}, then closed. Therefore, if you already have a transaction - * open, you should supply that to avoid one blocking the other. - *

* - * @param txn transaction to use (required; not closed). If the {@link Env} was opened - * with the {@link EnvFlags#MDB_RDONLY_ENV} flag, the {@link Txn} can be read-only, - * else it needs to be a read/write {@link Txn}. + *

The caller MUST commit the transaction after calling {@link DbiBuilderStage3#open()}, in + * order to retain the Dbi in the Env. + * + *

If you don't call this method to supply a {@link Txn}, a {@link Txn} will be opened for + * the purpose of creating and opening the {@link Dbi}, then closed. Therefore, if you already + * have a transaction open, you should supply that to avoid one blocking the other. + * + * @param txn transaction to use (required; not closed). If the {@link Env} was opened with the + * {@link EnvFlags#MDB_RDONLY_ENV} flag, the {@link Txn} can be read-only, else it needs to + * be a read/write {@link Txn}. * @return this builder instance. */ public DbiBuilderStage3 setTxn(final Txn txn) { @@ -381,10 +330,9 @@ public DbiBuilderStage3 setTxn(final Txn txn) { /** * Construct and open the {@link Dbi}. - *

- * If a {@link Txn} was supplied to the builder, it is the callers responsibility to - * commit and close the txn upon return from this method, else the created DB won't be retained. - *

+ * + *

If a {@link Txn} was supplied to the builder, it is the callers responsibility to commit + * and close the txn upon return from this method, else the created DB won't be retained. * * @return A newly constructed and opened {@link Dbi}. */ @@ -403,14 +351,13 @@ public Dbi open() { } private Txn getTxn(final DbiBuilder dbiBuilder) { - return dbiBuilder.readOnly - ? dbiBuilder.env.txnRead() - : dbiBuilder.env.txnWrite(); + return dbiBuilder.readOnly ? dbiBuilder.env.txnRead() : dbiBuilder.env.txnWrite(); } - private Comparator getComparator(final DbiBuilder dbiBuilder, - final ComparatorType comparatorType, - final DbiFlagSet dbiFlagSet) { + private Comparator getComparator( + final DbiBuilder dbiBuilder, + final ComparatorType comparatorType, + final DbiFlagSet dbiFlagSet) { Comparator comparator = null; switch (comparatorType) { case DEFAULT: @@ -431,8 +378,7 @@ private Comparator getComparator(final DbiBuilder dbiBuilder, return comparator; } - private Dbi openDbi(final Txn txn, - final DbiBuilder dbiBuilder) { + private Dbi openDbi(final Txn txn, final DbiBuilder dbiBuilder) { final DbiFlagSet dbiFlagSet = flagSetBuilder.build(); final ComparatorType comparatorType = dbiBuilderStage2.comparatorType; final Comparator comparator = getComparator(dbiBuilder, comparatorType, dbiFlagSet); @@ -448,23 +394,17 @@ private Dbi openDbi(final Txn txn, } } - // -------------------------------------------------------------------------------- - private enum ComparatorType { /** - * Default Java comparator for {@link CursorIterable} KeyRange testing, - * LMDB comparator for insertion/iteration order. + * Default Java comparator for {@link CursorIterable} KeyRange testing, LMDB comparator for + * insertion/iteration order. */ DEFAULT, - /** - * Use LMDB native comparator for everything. - */ + /** Use LMDB native comparator for everything. */ NATIVE, - /** - * Use the supplied custom Java-side comparator for everything. - */ + /** Use the supplied custom Java-side comparator for everything. */ CALLBACK, /** * Use the supplied custom Java-side comparator for {@link CursorIterable} KeyRange testing, @@ -474,14 +414,11 @@ private enum ComparatorType { ; } - // -------------------------------------------------------------------------------- - @FunctionalInterface public interface ComparatorFactory { Comparator create(final DbiFlagSet dbiFlagSet); - } } diff --git a/src/main/java/org/lmdbjava/DbiFlagSet.java b/src/main/java/org/lmdbjava/DbiFlagSet.java index 8ffd2499..6f2d3c81 100644 --- a/src/main/java/org/lmdbjava/DbiFlagSet.java +++ b/src/main/java/org/lmdbjava/DbiFlagSet.java @@ -25,9 +25,7 @@ public interface DbiFlagSet extends FlagSet { DbiFlagSet EMPTY = DbiFlagSetImpl.EMPTY; /** The set of {@link DbiFlags} that indicate unsigned integer keys are being used. */ - DbiFlagSet INTEGER_KEY_FLAGS = DbiFlagSet.of( - DbiFlags.MDB_INTEGERKEY, - DbiFlags.MDB_INTEGERDUP); + DbiFlagSet INTEGER_KEY_FLAGS = DbiFlagSet.of(DbiFlags.MDB_INTEGERKEY, DbiFlags.MDB_INTEGERDUP); static DbiFlagSet empty() { return DbiFlagSetImpl.EMPTY; @@ -39,29 +37,20 @@ static DbiFlagSet of(final DbiFlags dbiFlag) { } static DbiFlagSet of(final DbiFlags... DbiFlags) { - return builder() - .setFlags(DbiFlags) - .build(); + return builder().setFlags(DbiFlags).build(); } static DbiFlagSet of(final Collection DbiFlags) { - return builder() - .setFlags(DbiFlags) - .build(); + return builder().setFlags(DbiFlags).build(); } static AbstractFlagSet.Builder builder() { return new AbstractFlagSet.Builder<>( - DbiFlags.class, - DbiFlagSetImpl::new, - dbiFlag -> dbiFlag, - () -> DbiFlagSetImpl.EMPTY); + DbiFlags.class, DbiFlagSetImpl::new, dbiFlag -> dbiFlag, () -> DbiFlagSetImpl.EMPTY); } - // -------------------------------------------------------------------------------- - class DbiFlagSetImpl extends AbstractFlagSet implements DbiFlagSet { static final DbiFlagSet EMPTY = new EmptyDbiFlagSet(); @@ -71,10 +60,8 @@ private DbiFlagSetImpl(final EnumSet flags) { } } - // -------------------------------------------------------------------------------- - - class EmptyDbiFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet implements DbiFlagSet { - } + class EmptyDbiFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet + implements DbiFlagSet {} } diff --git a/src/main/java/org/lmdbjava/DbiFlags.java b/src/main/java/org/lmdbjava/DbiFlags.java index 7c4b6794..f8ccbe20 100644 --- a/src/main/java/org/lmdbjava/DbiFlags.java +++ b/src/main/java/org/lmdbjava/DbiFlags.java @@ -32,30 +32,26 @@ public enum DbiFlags implements MaskedFlag, DbiFlagSet { * Use sorted duplicates. * *

Duplicate keys may be used in the database. Or, from another perspective, keys may have - * multiple data items, stored in sorted order. By default, keys must be unique and may have only a - * single data item. - *

+ * multiple data items, stored in sorted order. By default, keys must be unique and may have only + * a single data item. * - *

+ *

*/ MDB_DUPSORT(0x04), /** - * Numeric keys in native byte order: either unsigned int or size_t. - * The keys must all be of the same size. - *

- * This is an optimisation that is available when your keys are 4 or 8 byte unsigned numeric values. - * There are performance benefits for both ordered and un-ordered puts as compared to not using - * this flag. - *

- *

- * When writing the key to the buffer you must write it in native order and subsequently read any - * keys retrieved from LMDB (via cursor or get method) also using native order. - *

- *

- * For more information, see - * Numeric Keys - * in the LmdbJava wiki. - *

+ * Numeric keys in native byte order: either unsigned int or size_t. The keys must all be + * of the same size. + * + *

This is an optimisation that is available when your keys are 4 or 8 byte unsigned numeric + * values. There are performance benefits for both ordered and un-ordered puts as compared to not + * using this flag. + * + *

When writing the key to the buffer you must write it in native order and subsequently read + * any keys retrieved from LMDB (via cursor or get method) also using native order. + * + *

For more information, see Numeric Keys in the + * LmdbJava wiki. */ MDB_INTEGERKEY(0x08), /** diff --git a/src/main/java/org/lmdbjava/DirectBufferProxy.java b/src/main/java/org/lmdbjava/DirectBufferProxy.java index 3ddda467..af918943 100644 --- a/src/main/java/org/lmdbjava/DirectBufferProxy.java +++ b/src/main/java/org/lmdbjava/DirectBufferProxy.java @@ -91,11 +91,11 @@ public static int compareLexicographically(final DirectBuffer o1, final DirectBu } /** - * Buffer comparator specifically for 4/8 byte keys that are unsigned ints/longs, - * i.e. when using MDB_INTEGER_KEY/MDB_INTEGERDUP. Compares the buffers numerically. - *

- * Both buffer must have 4 or 8 bytes remaining - *

+ * Buffer comparator specifically for 4/8 byte keys that are unsigned ints/longs, i.e. when using + * MDB_INTEGER_KEY/MDB_INTEGERDUP. Compares the buffers numerically. + * + *

Both buffer must have 4 or 8 bytes remaining + * * @param o1 left operand (required) * @param o2 right operand (required) * @return as specified by {@link Comparable} interface @@ -107,8 +107,12 @@ public static int compareAsIntegerKeys(final DirectBuffer o1, final DirectBuffer final int len1 = o1.capacity(); final int len2 = o2.capacity(); if (len1 != len2) { - throw new RuntimeException("Length mismatch, len1: " + len1 + ", len2: " + len2 - + ". Lengths must be identical and either 4 or 8 bytes."); + throw new RuntimeException( + "Length mismatch, len1: " + + len1 + + ", len2: " + + len2 + + ". Lengths must be identical and either 4 or 8 bytes."); } if (len1 == 8) { final long lw = o1.getLong(0, NATIVE_ORDER); diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index 9b2a4360..54115b33 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -50,10 +50,9 @@ */ public final class Env implements AutoCloseable { - /** - * Java system property name that can be set to disable optional checks. - */ + /** Java system property name that can be set to disable optional checks. */ public static final String DISABLE_CHECKS_PROP = "lmdbjava.disable.checks"; + public static final Charset DEFAULT_NAME_CHARSET = StandardCharsets.UTF_8; /** @@ -95,7 +94,7 @@ public static Builder create() { /** * Create an {@link Env} using the passed {@link BufferProxy}. * - * @param buffer type + * @param buffer type * @param proxy the proxy to use (required) * @return the environment (never null) */ @@ -104,20 +103,17 @@ public static Builder create(final BufferProxy proxy) { } /** - * @param path file system destination - * @param size size in megabytes + * @param path file system destination + * @param size size in megabytes * @param flags the flags for this new environment * @return env the environment (never null) * @deprecated Instead use {@link Env#create()} or {@link Env#create(BufferProxy)} - *

- * Opens an environment with a single default database in 0664 mode using the {@link - * ByteBufferProxy#PROXY_OPTIMAL}. + *

Opens an environment with a single default database in 0664 mode using the {@link + * ByteBufferProxy#PROXY_OPTIMAL}. */ @Deprecated public static Env open(final File path, final int size, final EnvFlags... flags) { - return new Builder<>(PROXY_OPTIMAL) - .setMapSize(size, ByteUnit.MEBIBYTES) - .open(path, flags); + return new Builder<>(PROXY_OPTIMAL).setMapSize(size, ByteUnit.MEBIBYTES).open(path, flags); } /** @@ -170,7 +166,7 @@ public void copy(final File path) { * transactions, because it employs a read-only transaction. See long-lived transactions under * "Caveats" in the LMDB native documentation. * - * @param path writable destination path as described above + * @param path writable destination path as described above * @param flags special options for this copy */ public void copy(final File path, final CopyFlagSet flags) { @@ -195,7 +191,7 @@ public List getDbiNames() { // The unnamed DB is special so the names of the named DBs are held as keys in it. final Dbi unnamedDb = openDbi((byte[]) null, DbiFlagSet.EMPTY); try (final Txn txn = txnRead(); - final Cursor cursor = unnamedDb.openCursor(txn)) { + final Cursor cursor = unnamedDb.openCursor(txn)) { if (!cursor.first()) { return Collections.emptyList(); } @@ -285,8 +281,8 @@ public boolean isReadOnly() { } /** - * Open (and optionally creates, if {@link DbiFlags#MDB_CREATE} is set) - * a {@link Dbi} using a builder. + * Open (and optionally creates, if {@link DbiFlags#MDB_CREATE} is set) a {@link Dbi} using a + * builder. * * @return A new builder instance for creating/opening a {@link Dbi}. */ @@ -295,12 +291,12 @@ public DbiBuilder createDbi() { } /** - * Convenience method that opens a {@link Dbi} with a UTF-8 database name and default - * {@link Comparator} that is not invoked from native code. - *

- * For more options when opening a {@link Dbi} see {@link Env#createDbi()}. - *

- * @param name name of the database (or null if no name is required) + * Convenience method that opens a {@link Dbi} with a UTF-8 database name and default {@link + * Comparator} that is not invoked from native code. + * + *

For more options when opening a {@link Dbi} see {@link Env#createDbi()}. + * + * @param name name of the database (or null if no name is required) * @param dbiFlagSet Flags to open the database with * @return a database that is ready to use */ @@ -314,17 +310,16 @@ public Dbi openDbi(final String name, final DbiFlagSet dbiFlagSet) { } /** - * Convenience method that opens a {@link Dbi} with a default - * {@link Comparator} that is not invoked from native code. - *

- * For more options when opening a {@link Dbi} see {@link Env#createDbi()}. - *

- * @param name name of the database (or null if no name is required) + * Convenience method that opens a {@link Dbi} with a default {@link Comparator} that is not + * invoked from native code. + * + *

For more options when opening a {@link Dbi} see {@link Env#createDbi()}. + * + * @param name name of the database (or null if no name is required) * @param dbiFlagSet Flags to open the database with * @return a database that is ready to use */ - public Dbi openDbi(final byte[] name, - final DbiFlagSet dbiFlagSet) { + public Dbi openDbi(final byte[] name, final DbiFlagSet dbiFlagSet) { try (Txn txn = readOnly ? txnRead() : txnWrite()) { final Dbi dbi = new Dbi<>(this, txn, name, proxy, dbiFlagSet); txn.commit(); // even RO Txns require a commit to retain Dbi in Env @@ -333,12 +328,12 @@ public Dbi openDbi(final byte[] name, } /** - * @param name name of the database (or null if no name is required) + * @param name name of the database (or null if no name is required) * @param flags to open the database with * @return a database that is ready to use * @deprecated Instead use {@link Env#createDbi()} or {@link Env#openDbi(String, DbiFlagSet)} - * Convenience method that opens a {@link Dbi} with a UTF-8 database name and default {@link - * Comparator} that is not invoked from native code. + * Convenience method that opens a {@link Dbi} with a UTF-8 database name and default {@link + * Comparator} that is not invoked from native code. */ @Deprecated() public Dbi openDbi(final String name, final DbiFlags... flags) { @@ -347,84 +342,73 @@ public Dbi openDbi(final String name, final DbiFlags... flags) { } /** - * @param name name of the database (or null if no name is required) + * @param name name of the database (or null if no name is required) * @param comparator custom comparator for cursor start/stop key comparisons. If null, LMDB's - * comparator will be used. - * @param flags to open the database with + * comparator will be used. + * @param flags to open the database with * @return a database that is ready to use - * @deprecated Instead use {@link Env#createDbi()} - * Convenience method that opens a {@link Dbi} with a UTF-8 database name and associated {@link - * Comparator} for use by {@link CursorIterable} when comparing start/stop keys. - * - *

It is very important that the passed comparator behaves in the same way as the comparator - * LMDB uses for its insertion order (for the type of data that will be stored in the database), - * or you fully understand the implications of them behaving differently. LMDB's comparator is - * unsigned lexicographical, unless {@link DbiFlags#MDB_INTEGERKEY} is used. + * @deprecated Instead use {@link Env#createDbi()} Convenience method that opens a {@link Dbi} + * with a UTF-8 database name and associated {@link Comparator} for use by {@link + * CursorIterable} when comparing start/stop keys. + *

It is very important that the passed comparator behaves in the same way as the + * comparator LMDB uses for its insertion order (for the type of data that will be stored in + * the database), or you fully understand the implications of them behaving differently. + * LMDB's comparator is unsigned lexicographical, unless {@link DbiFlags#MDB_INTEGERKEY} is + * used. */ @Deprecated() - public Dbi openDbi(final String name, - final Comparator comparator, - final DbiFlags... flags) { + public Dbi openDbi( + final String name, final Comparator comparator, final DbiFlags... flags) { final byte[] nameBytes = name == null ? null : name.getBytes(DEFAULT_NAME_CHARSET); return openDbi(nameBytes, comparator, false, flags); } /** - * @param name name of the database (or null if no name is required) + * @param name name of the database (or null if no name is required) * @param comparator custom comparator for cursor start/stop key comparisons and optionally for - * LMDB to call back to. If null, LMDB's comparator will be used. - * @param nativeCb whether LMDB native code calls back to the Java comparator - * @param flags to open the database with + * LMDB to call back to. If null, LMDB's comparator will be used. + * @param nativeCb whether LMDB native code calls back to the Java comparator + * @param flags to open the database with * @return a database that is ready to use - * @deprecated Instead use {@link Env#createDbi()} - * Convenience method that opens a {@link Dbi} with a UTF-8 database name and associated {@link - * Comparator}. The comparator will be used by {@link CursorIterable} when comparing start/stop - * keys as a minimum. If nativeCb is {@code true}, this comparator will also be called by LMDB to - * determine insertion/iteration order. Calling back to a java comparator may significantly impact - * performance. + * @deprecated Instead use {@link Env#createDbi()} Convenience method that opens a {@link Dbi} + * with a UTF-8 database name and associated {@link Comparator}. The comparator will be used + * by {@link CursorIterable} when comparing start/stop keys as a minimum. If nativeCb is + * {@code true}, this comparator will also be called by LMDB to determine insertion/iteration + * order. Calling back to a java comparator may significantly impact performance. */ @Deprecated() - public Dbi openDbi(final String name, - final Comparator comparator, - final boolean nativeCb, - final DbiFlags... flags) { + public Dbi openDbi( + final String name, + final Comparator comparator, + final boolean nativeCb, + final DbiFlags... flags) { final byte[] nameBytes = name == null ? null : name.getBytes(DEFAULT_NAME_CHARSET); return openDbi(nameBytes, comparator, nativeCb, flags); } /** - * @param name name of the database (or null if no name is required) + * @param name name of the database (or null if no name is required) * @param flags to open the database with * @return a database that is ready to use - * @deprecated Instead use {@link Env#createDbi()} - *


- * Convenience method that opens a {@link Dbi} with a default {@link Comparator} that is not - * invoked from native code. + * @deprecated Instead use {@link Env#createDbi()}
Convenience method that opens a {@link + * Dbi} with a default {@link Comparator} that is not invoked from native code. */ @Deprecated() - public Dbi openDbi(final byte[] name, - final DbiFlags... flags) { - return createDbi() - .setDbName(name) - .withDefaultComparator() - .setDbiFlags(flags) - .open(); + public Dbi openDbi(final byte[] name, final DbiFlags... flags) { + return createDbi().setDbName(name).withDefaultComparator().setDbiFlags(flags).open(); } /** - * @param name name of the database (or null if no name is required) + * @param name name of the database (or null if no name is required) * @param comparator custom comparator callback (or null to use LMDB default) - * @param flags to open the database with + * @param flags to open the database with * @return a database that is ready to use - * @deprecated Instead use {@link Env#createDbi()} - *
- * Convenience method that opens a {@link Dbi} with an associated {@link Comparator} that is not - * invoked from native code. + * @deprecated Instead use {@link Env#createDbi()}
Convenience method that opens a {@link + * Dbi} with an associated {@link Comparator} that is not invoked from native code. */ @Deprecated() - public Dbi openDbi(final byte[] name, - final Comparator comparator, - final DbiFlags... flags) { + public Dbi openDbi( + final byte[] name, final Comparator comparator, final DbiFlags... flags) { requireNonNull(comparator); return createDbi() .setDbName(name) @@ -434,18 +418,16 @@ public Dbi openDbi(final byte[] name, } /** - * @param name name of the database (or null if no name is required) + * @param name name of the database (or null if no name is required) * @param comparator custom comparator callback (or null to use LMDB default) - * @param nativeCb whether native code calls back to the Java comparator - * @param flags to open the database with + * @param nativeCb whether native code calls back to the Java comparator + * @param flags to open the database with * @return a database that is ready to use - * @deprecated Instead use {@link Env#createDbi()} - *
- * Convenience method that opens a {@link Dbi} with an associated {@link Comparator} that may be - * invoked from native code if specified. - * - *

This method will automatically commit the private transaction before returning. This ensures - * the Dbi is available in the Env. + * @deprecated Instead use {@link Env#createDbi()}


Convenience method that opens a {@link + * Dbi} with an associated {@link Comparator} that may be invoked from native code if + * specified. + *

This method will automatically commit the private transaction before returning. This + * ensures the Dbi is available in the Env. */ @Deprecated() public Dbi openDbi( @@ -461,31 +443,27 @@ public Dbi openDbi( } /** - * @param txn transaction to use (required; not closed) - * @param name name of the database (or null if no name is required) + * @param txn transaction to use (required; not closed) + * @param name name of the database (or null if no name is required) * @param comparator custom comparator callback (or null to use LMDB default) - * @param nativeCb whether native LMDB code should call back to the Java comparator - * @param flags to open the database with + * @param nativeCb whether native LMDB code should call back to the Java comparator + * @param flags to open the database with * @return a database that is ready to use - * @deprecated Instead use {@link Env#createDbi()} - * Open the {@link Dbi} using the passed {@link Txn}. - * - *

The caller must commit the transaction after this method returns in order to retain the - * Dbi in the Env. - * - *

A {@link Comparator} may be provided when calling this method. Such comparator is primarily - * used by {@link CursorIterable} instances. A secondary (but uncommon) use of the comparator is - * to act as a callback from the native library if nativeCb is true. - * This is usually avoided due to the overhead of native code calling back into Java. It is - * instead highly recommended to set the correct {@link DbiFlags} to allow the native library to - * correctly order the intended keys. - * - *

A default comparator will be provided if null is passed as the comparator. If a - * custom comparator is provided, it must strictly match the lexicographical order of keys in the - * native LMDB database. - * - *

This method (and its overloaded convenience variants) must not be called from concurrent - * threads. + * @deprecated Instead use {@link Env#createDbi()} Open the {@link Dbi} using the passed {@link + * Txn}. + *

The caller must commit the transaction after this method returns in order to retain the + * Dbi in the Env. + *

A {@link Comparator} may be provided when calling this method. Such comparator is + * primarily used by {@link CursorIterable} instances. A secondary (but uncommon) use of the + * comparator is to act as a callback from the native library if nativeCb is + * true. This is usually avoided due to the overhead of native code calling back + * into Java. It is instead highly recommended to set the correct {@link DbiFlags} to allow + * the native library to correctly order the intended keys. + *

A default comparator will be provided if null is passed as the comparator. + * If a custom comparator is provided, it must strictly match the lexicographical order of + * keys in the native LMDB database. + *

This method (and its overloaded convenience variants) must not be called from concurrent + * threads. */ @Deprecated() public Dbi openDbi( @@ -521,7 +499,7 @@ public Stat stat() { * Flushes the data buffers to disk. * * @param force force a synchronous flush (otherwise if the environment has the MDB_NOSYNC flag - * set the flushes will be omitted, and with MDB_MAPASYNC they will be asynchronous) + * set the flushes will be omitted, and with MDB_MAPASYNC they will be asynchronous) */ public void sync(final boolean force) { if (closed) { @@ -533,11 +511,10 @@ public void sync(final boolean force) { /** * @param parent parent transaction (may be null if no parent) - * @param flags applicable flags (eg for a reusable, read-only transaction) + * @param flags applicable flags (eg for a reusable, read-only transaction) * @return a transaction (never null) * @deprecated Instead use {@link Env#txn(Txn, TxnFlagSet)} - *

- * Obtain a transaction with the requested parent and flags. + *

Obtain a transaction with the requested parent and flags. */ @Deprecated public Txn txn(final Txn parent, final TxnFlags... flags) { @@ -560,9 +537,9 @@ public Txn txn(final Txn parent) { * Obtain a transaction with the requested parent and flags. * * @param parent parent transaction (may be null if no parent) - * @param flags applicable flags (e.g. for a reusable, read-only transaction). - * If the set of flags is used frequently it is recommended to hold - * a static instance of the {@link TxnFlagSet} for re-use. + * @param flags applicable flags (e.g. for a reusable, read-only transaction). If the set of flags + * is used frequently it is recommended to hold a static instance of the {@link TxnFlagSet} + * for re-use. * @return a transaction (never null) */ public Txn txn(final Txn parent, final TxnFlagSet flags) { @@ -634,40 +611,30 @@ public int readerCheck() { return resultPtr.intValue(); } - /** - * Object has already been closed and the operation is therefore prohibited. - */ + /** Object has already been closed and the operation is therefore prohibited. */ public static final class AlreadyClosedException extends LmdbException { private static final long serialVersionUID = 1L; - /** - * Creates a new instance. - */ + /** Creates a new instance. */ public AlreadyClosedException() { super("Environment has already been closed"); } } - /** - * Object has already been opened and the operation is therefore prohibited. - */ + /** Object has already been opened and the operation is therefore prohibited. */ public static final class AlreadyOpenException extends LmdbException { private static final long serialVersionUID = 1L; - /** - * Creates a new instance. - */ + /** Creates a new instance. */ public AlreadyOpenException() { super("Environment has already been opened"); } } - // -------------------------------------------------------------------------------- - /** * Builder for configuring and opening Env. * @@ -685,7 +652,8 @@ public static final class Builder { private boolean opened; private final BufferProxy proxy; private int mode = POSIX_MODE_DEFAULT; - private final AbstractFlagSet.Builder flagSetBuilder = EnvFlagSet.builder(); + private final AbstractFlagSet.Builder flagSetBuilder = + EnvFlagSet.builder(); Builder(final BufferProxy proxy) { requireNonNull(proxy); @@ -695,12 +663,12 @@ public static final class Builder { /** * Opens the environment. * - * @param path file system destination - * @param mode Unix permissions to set on created files and semaphores + * @param path file system destination + * @param mode Unix permissions to set on created files and semaphores * @param flags the flags for this new environment * @return an environment ready for use * @deprecated Instead use {@link Builder#open(Path)}, {@link Builder#setFilePermissions(int)} - * and {@link Builder#setEnvFlags(EnvFlags...)}. + * and {@link Builder#setEnvFlags(EnvFlags...)}. */ @Deprecated public Env open(final File path, final int mode, final EnvFlags... flags) { @@ -724,10 +692,11 @@ public Env open(final File path) { /** * Opens the environment with 0664 mode. * - * @param path file system destination + * @param path file system destination * @param flags the flags for this new environment * @return an environment ready for use - * @deprecated Instead use {@link Builder#open(Path)} and {@link Builder#setEnvFlags(EnvFlags...)}. + * @deprecated Instead use {@link Builder#open(Path)} and {@link + * Builder#setEnvFlags(EnvFlags...)}. */ @Deprecated public Env open(final File path, final EnvFlags... flags) { @@ -825,8 +794,8 @@ public Builder setMaxReaders(final int readers) { } /** - * Sets the Unix file permissions to use on created files and semaphores, e.g. {@code 0664}. - * If this method is not called, the default of {@code 0664} will be used. + * Sets the Unix file permissions to use on created files and semaphores, e.g. {@code 0664}. If + * this method is not called, the default of {@code 0664} will be used. * * @param mode Unix permissions to set on created files and semaphores * @return the builder @@ -842,17 +811,14 @@ public Builder setFilePermissions(final int mode) { /** * Sets all the flags used to open this {@link Env}. * - * @param envFlags The flags to use. - * Clears any existing flags. - * A null value results in no flags being set. + * @param envFlags The flags to use. Clears any existing flags. A null value results in no flags + * being set. * @return this builder instance. */ public Builder setEnvFlags(final Collection envFlags) { flagSetBuilder.clear(); if (envFlags != null) { - envFlags.stream() - .filter(Objects::nonNull) - .forEach(envFlags::add); + envFlags.stream().filter(Objects::nonNull).forEach(envFlags::add); } return this; } @@ -860,17 +826,14 @@ public Builder setEnvFlags(final Collection envFlags) { /** * Sets all the flags used to open this {@link Env}. * - * @param envFlags The flags to use. - * Clears any existing flags. - * A null value results in no flags being set. + * @param envFlags The flags to use. Clears any existing flags. A null value results in no flags + * being set. * @return this builder instance. */ public Builder setEnvFlags(final EnvFlags... envFlags) { flagSetBuilder.clear(); if (envFlags != null) { - Arrays.stream(envFlags) - .filter(Objects::nonNull) - .forEach(this.flagSetBuilder::addFlag); + Arrays.stream(envFlags).filter(Objects::nonNull).forEach(this.flagSetBuilder::addFlag); } return this; } @@ -878,9 +841,8 @@ public Builder setEnvFlags(final EnvFlags... envFlags) { /** * Sets all the flags used to open this {@link Env}. * - * @param envFlagSet The flags to use. - * Clears any existing flags. - * A null value results in no flags being set. + * @param envFlagSet The flags to use. Clears any existing flags. A null value results in no + * flags being set. * @return this builder instance. */ public Builder setEnvFlags(final EnvFlagSet envFlagSet) { @@ -894,8 +856,7 @@ public Builder setEnvFlags(final EnvFlagSet envFlagSet) { /** * Adds a single {@link EnvFlags} to any existing flags. * - * @param dbiFlag The flag to add to any existing flags. - * A null value is a no-op. + * @param dbiFlag The flag to add to any existing flags. A null value is a no-op. * @return this builder instance. */ public Builder addEnvFlag(final EnvFlags dbiFlag) { @@ -906,8 +867,7 @@ public Builder addEnvFlag(final EnvFlags dbiFlag) { /** * Adds a set of {@link EnvFlags} to any existing flags. * - * @param dbiFlagSet The set of flags to add to any existing flags. - * A null value is a no-op. + * @param dbiFlagSet The set of flags to add to any existing flags. A null value is a no-op. * @return this builder instance. */ public Builder addEnvFlags(final EnvFlagSet dbiFlagSet) { @@ -918,9 +878,7 @@ public Builder addEnvFlags(final EnvFlagSet dbiFlagSet) { } } - /** - * File is not a valid LMDB file. - */ + /** File is not a valid LMDB file. */ public static final class FileInvalidException extends LmdbNativeException { static final int MDB_INVALID = -30_793; @@ -931,9 +889,7 @@ public static final class FileInvalidException extends LmdbNativeException { } } - /** - * The specified copy destination is invalid. - */ + /** The specified copy destination is invalid. */ public static final class InvalidCopyDestination extends LmdbException { private static final long serialVersionUID = 1L; @@ -948,9 +904,7 @@ public InvalidCopyDestination(final String message) { } } - /** - * Environment mapsize reached. - */ + /** Environment mapsize reached. */ public static final class MapFullException extends LmdbNativeException { static final int MDB_MAP_FULL = -30_792; @@ -961,9 +915,7 @@ public static final class MapFullException extends LmdbNativeException { } } - /** - * Environment maxreaders reached. - */ + /** Environment maxreaders reached. */ public static final class ReadersFullException extends LmdbNativeException { static final int MDB_READERS_FULL = -30_790; @@ -974,9 +926,7 @@ public static final class ReadersFullException extends LmdbNativeException { } } - /** - * Environment version mismatch. - */ + /** Environment version mismatch. */ public static final class VersionMismatchException extends LmdbNativeException { static final int MDB_VERSION_MISMATCH = -30_794; diff --git a/src/main/java/org/lmdbjava/EnvFlagSet.java b/src/main/java/org/lmdbjava/EnvFlagSet.java index c104edd9..771f5e2c 100644 --- a/src/main/java/org/lmdbjava/EnvFlagSet.java +++ b/src/main/java/org/lmdbjava/EnvFlagSet.java @@ -33,29 +33,20 @@ static EnvFlagSet of(final EnvFlags envFlag) { } static EnvFlagSet of(final EnvFlags... EnvFlags) { - return builder() - .setFlags(EnvFlags) - .build(); + return builder().setFlags(EnvFlags).build(); } static EnvFlagSet of(final Collection EnvFlags) { - return builder() - .setFlags(EnvFlags) - .build(); + return builder().setFlags(EnvFlags).build(); } static AbstractFlagSet.Builder builder() { return new AbstractFlagSet.Builder<>( - EnvFlags.class, - EnvFlagSetImpl::new, - envFlag -> envFlag, - () -> EnvFlagSetImpl.EMPTY); + EnvFlags.class, EnvFlagSetImpl::new, envFlag -> envFlag, () -> EnvFlagSetImpl.EMPTY); } - // -------------------------------------------------------------------------------- - class EnvFlagSetImpl extends AbstractFlagSet implements EnvFlagSet { static final EnvFlagSet EMPTY = new EmptyEnvFlagSet(); @@ -65,10 +56,8 @@ private EnvFlagSetImpl(final EnumSet flags) { } } - // -------------------------------------------------------------------------------- - - class EmptyEnvFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet implements EnvFlagSet { - } + class EmptyEnvFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet + implements EnvFlagSet {} } diff --git a/src/main/java/org/lmdbjava/FlagSet.java b/src/main/java/org/lmdbjava/FlagSet.java index 94cb3dca..e5e1c6a4 100644 --- a/src/main/java/org/lmdbjava/FlagSet.java +++ b/src/main/java/org/lmdbjava/FlagSet.java @@ -22,8 +22,9 @@ import java.util.stream.Collectors; /** - * A set of flags, each with a bit mask value. - * Flags can be combined in a set such that the set has a combined bit mask value. + * A set of flags, each with a bit mask value. Flags can be combined in a set such that the set has + * a combined bit mask value. + * * @param */ public interface FlagSet extends Iterable { @@ -34,8 +35,8 @@ public interface FlagSet extends Iterable { int getMask(); /** - * @return The result of combining the mask of this {@link FlagSet} - * with the mask of the other {@link FlagSet}. + * @return The result of combining the mask of this {@link FlagSet} with the mask of the other + * {@link FlagSet}. */ default int getMaskWith(final FlagSet other) { if (other != null) { @@ -76,7 +77,6 @@ default boolean areAnySet(final FlagSet flags) { return false; } - /** * @return True if this {@link FlagSet} is empty. */ @@ -90,24 +90,18 @@ default Iterator iterator() { return getFlags().iterator(); } - /** - * Convert this {@link FlagSet} to a string for use in toString methods. - */ + /** Convert this {@link FlagSet} to a string for use in toString methods. */ static String asString(final FlagSet flagSet) { Objects.requireNonNull(flagSet); - final String flagsStr = flagSet.getFlags() - .stream() - .sorted(Comparator.comparing(MaskedFlag::getMask)) - .map(MaskedFlag::name) - .collect(Collectors.joining(", ")); - return "FlagSet{" + - "flags=[" + flagsStr + - "], mask=" + flagSet.getMask() + - '}'; + final String flagsStr = + flagSet.getFlags().stream() + .sorted(Comparator.comparing(MaskedFlag::getMask)) + .map(MaskedFlag::name) + .collect(Collectors.joining(", ")); + return "FlagSet{" + "flags=[" + flagsStr + "], mask=" + flagSet.getMask() + '}'; } - static boolean equals(final FlagSet flagSet, - final Object other) { + static boolean equals(final FlagSet flagSet, final Object other) { if (other instanceof FlagSet) { final FlagSet flagSet2 = (FlagSet) other; if (flagSet == flagSet2) { @@ -122,5 +116,4 @@ static boolean equals(final FlagSet flagSet, return false; } } - } diff --git a/src/main/java/org/lmdbjava/MaskedFlag.java b/src/main/java/org/lmdbjava/MaskedFlag.java index 1c531ac8..400f2f30 100644 --- a/src/main/java/org/lmdbjava/MaskedFlag.java +++ b/src/main/java/org/lmdbjava/MaskedFlag.java @@ -59,9 +59,7 @@ static int mask(final M... flags) { } } - /** - * Combine the two masks into a single mask value, i.e. when combining two {@link FlagSet}s. - */ + /** Combine the two masks into a single mask value, i.e. when combining two {@link FlagSet}s. */ static int mask(final int mask1, final int mask2) { return mask1 | mask2; } diff --git a/src/main/java/org/lmdbjava/PutFlagSet.java b/src/main/java/org/lmdbjava/PutFlagSet.java index aaba7cb7..ee2a9826 100644 --- a/src/main/java/org/lmdbjava/PutFlagSet.java +++ b/src/main/java/org/lmdbjava/PutFlagSet.java @@ -21,54 +21,43 @@ public interface PutFlagSet extends FlagSet { - PutFlagSet EMPTY = PutFlagSetImpl.EMPTY; + PutFlagSet EMPTY = PutFlagSetImpl.EMPTY; - static PutFlagSet empty() { - return PutFlagSetImpl.EMPTY; - } - - static PutFlagSet of(final PutFlags putFlag) { - Objects.requireNonNull(putFlag); - return putFlag; - } - - static PutFlagSet of(final PutFlags... putFlags) { - return builder() - .setFlags(putFlags) - .build(); - } + static PutFlagSet empty() { + return PutFlagSetImpl.EMPTY; + } - static PutFlagSet of(final Collection putFlags) { - return builder() - .setFlags(putFlags) - .build(); - } + static PutFlagSet of(final PutFlags putFlag) { + Objects.requireNonNull(putFlag); + return putFlag; + } - static AbstractFlagSet.Builder builder() { - return new AbstractFlagSet.Builder<>( - PutFlags.class, - PutFlagSetImpl::new, - putFlag -> putFlag, - EmptyPutFlagSet::new); - } + static PutFlagSet of(final PutFlags... putFlags) { + return builder().setFlags(putFlags).build(); + } + static PutFlagSet of(final Collection putFlags) { + return builder().setFlags(putFlags).build(); + } - // -------------------------------------------------------------------------------- + static AbstractFlagSet.Builder builder() { + return new AbstractFlagSet.Builder<>( + PutFlags.class, PutFlagSetImpl::new, putFlag -> putFlag, EmptyPutFlagSet::new); + } + // -------------------------------------------------------------------------------- - class PutFlagSetImpl extends AbstractFlagSet implements PutFlagSet { + class PutFlagSetImpl extends AbstractFlagSet implements PutFlagSet { - public static final PutFlagSet EMPTY = new EmptyPutFlagSet(); + public static final PutFlagSet EMPTY = new EmptyPutFlagSet(); - private PutFlagSetImpl(final EnumSet flags) { - super(flags); - } + private PutFlagSetImpl(final EnumSet flags) { + super(flags); } + } + // -------------------------------------------------------------------------------- - // -------------------------------------------------------------------------------- - - - class EmptyPutFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet implements PutFlagSet { - } + class EmptyPutFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet + implements PutFlagSet {} } diff --git a/src/main/java/org/lmdbjava/Txn.java b/src/main/java/org/lmdbjava/Txn.java index 99439bf7..432b47a8 100644 --- a/src/main/java/org/lmdbjava/Txn.java +++ b/src/main/java/org/lmdbjava/Txn.java @@ -46,9 +46,7 @@ public final class Txn implements AutoCloseable { private State state; Txn(final Env env, final Txn parent, final BufferProxy proxy, final TxnFlagSet flags) { - this.flags = flags != null - ? flags - : TxnFlagSet.EMPTY; + this.flags = flags != null ? flags : TxnFlagSet.EMPTY; this.proxy = proxy; this.keyVal = proxy.keyVal(); this.readOnly = this.flags.isSet(MDB_RDONLY_TXN); diff --git a/src/main/java/org/lmdbjava/TxnFlagSet.java b/src/main/java/org/lmdbjava/TxnFlagSet.java index 0943b362..1fa34d32 100644 --- a/src/main/java/org/lmdbjava/TxnFlagSet.java +++ b/src/main/java/org/lmdbjava/TxnFlagSet.java @@ -33,29 +33,20 @@ static TxnFlagSet of(final TxnFlags putflag) { } static TxnFlagSet of(final TxnFlags... TxnFlags) { - return builder() - .setFlags(TxnFlags) - .build(); + return builder().setFlags(TxnFlags).build(); } static TxnFlagSet of(final Collection txnFlags) { - return builder() - .setFlags(txnFlags) - .build(); + return builder().setFlags(txnFlags).build(); } static AbstractFlagSet.Builder builder() { return new AbstractFlagSet.Builder<>( - TxnFlags.class, - TxnFlagSetImpl::new, - SingleTxnFlagSet::new, - () -> TxnFlagSetImpl.EMPTY); + TxnFlags.class, TxnFlagSetImpl::new, SingleTxnFlagSet::new, () -> TxnFlagSetImpl.EMPTY); } - // -------------------------------------------------------------------------------- - class TxnFlagSetImpl extends AbstractFlagSet implements TxnFlagSet { static final TxnFlagSet EMPTY = new EmptyTxnFlagSet(); @@ -65,21 +56,18 @@ private TxnFlagSetImpl(final EnumSet flags) { } } - // -------------------------------------------------------------------------------- - - class SingleTxnFlagSet extends AbstractFlagSet.AbstractSingleFlagSet implements TxnFlagSet { + class SingleTxnFlagSet extends AbstractFlagSet.AbstractSingleFlagSet + implements TxnFlagSet { SingleTxnFlagSet(final TxnFlags flag) { super(flag); } } - // -------------------------------------------------------------------------------- - - class EmptyTxnFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet implements TxnFlagSet { - } + class EmptyTxnFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet + implements TxnFlagSet {} } diff --git a/src/test/java/org/lmdbjava/AbstractFlagSetTest.java b/src/test/java/org/lmdbjava/AbstractFlagSetTest.java index 7358416b..dbe32e14 100644 --- a/src/test/java/org/lmdbjava/AbstractFlagSetTest.java +++ b/src/test/java/org/lmdbjava/AbstractFlagSetTest.java @@ -25,7 +25,8 @@ import java.util.Set; import org.junit.jupiter.api.Test; -public abstract class AbstractFlagSetTest & MaskedFlag & FlagSet, F extends FlagSet> { +public abstract class AbstractFlagSetTest< + T extends Enum & MaskedFlag & FlagSet, F extends FlagSet> { abstract List getAllFlags(); @@ -48,70 +49,42 @@ T getFirst() { @Test void testEmpty() { final F emptyFlagSet = getEmptyFlagSet(); - assertThat(emptyFlagSet.getMask()) - .isEqualTo(0); - assertThat(emptyFlagSet.getFlags()) - .isEmpty(); - assertThat(emptyFlagSet.isEmpty()) - .isTrue(); - assertThat(emptyFlagSet.size()) - .isEqualTo(0); - assertThat(emptyFlagSet.isSet(getFirst())) - .isFalse(); - assertThat(getBuilder().build().getFlags()) - .isEqualTo(emptyFlagSet.getFlags()); + assertThat(emptyFlagSet.getMask()).isEqualTo(0); + assertThat(emptyFlagSet.getFlags()).isEmpty(); + assertThat(emptyFlagSet.isEmpty()).isTrue(); + assertThat(emptyFlagSet.size()).isEqualTo(0); + assertThat(emptyFlagSet.isSet(getFirst())).isFalse(); + assertThat(getBuilder().build().getFlags()).isEqualTo(emptyFlagSet.getFlags()); } @Test void testSingleFlagSet() { final List allFlags = getAllFlags(); for (T flag : allFlags) { - final F flagSet = getBuilder() - .addFlag(flag) - .build(); - assertThat(flagSet.getMask()) - .isEqualTo(flag.getMask()); - assertThat(flagSet.getMask()) - .isEqualTo(MaskedFlag.mask(flag)); - assertThat(flagSet.getFlags()) - .containsExactly(flag); - assertThat(flagSet.size()) - .isEqualTo(1); - assertThat(FlagSet.equals(flagSet, new Object())) - .isFalse(); - assertThat(FlagSet.equals(flagSet, null)) - .isFalse(); - assertThat(FlagSet.equals(flag, flag)) - .isTrue(); - assertThat(FlagSet.equals(flagSet, flag)) - .isTrue(); - assertThat(FlagSet.equals(flagSet, getFlagSet(flag))) - .isTrue(); - assertThat(FlagSet.equals(flagSet, getFlagSet(flagSet.getFlags()))) - .isTrue(); - assertThat(flagSet.areAnySet(flag)) - .isTrue(); - assertThat(flagSet.areAnySet(null)) - .isFalse(); - assertThat(flagSet.areAnySet(getEmptyFlagSet())) - .isFalse(); - assertThat(flagSet.isSet(getFirst())) - .isEqualTo(getFirst() == flag); + final F flagSet = getBuilder().addFlag(flag).build(); + assertThat(flagSet.getMask()).isEqualTo(flag.getMask()); + assertThat(flagSet.getMask()).isEqualTo(MaskedFlag.mask(flag)); + assertThat(flagSet.getFlags()).containsExactly(flag); + assertThat(flagSet.size()).isEqualTo(1); + assertThat(FlagSet.equals(flagSet, new Object())).isFalse(); + assertThat(FlagSet.equals(flagSet, null)).isFalse(); + assertThat(FlagSet.equals(flag, flag)).isTrue(); + assertThat(FlagSet.equals(flagSet, flag)).isTrue(); + assertThat(FlagSet.equals(flagSet, getFlagSet(flag))).isTrue(); + assertThat(FlagSet.equals(flagSet, getFlagSet(flagSet.getFlags()))).isTrue(); + assertThat(flagSet.areAnySet(flag)).isTrue(); + assertThat(flagSet.areAnySet(null)).isFalse(); + assertThat(flagSet.areAnySet(getEmptyFlagSet())).isFalse(); + assertThat(flagSet.isSet(getFirst())).isEqualTo(getFirst() == flag); if (getFirst() == flag) { - assertThat(flagSet.getMask()) - .isEqualTo(MaskedFlag.mask(getFirst())); + assertThat(flagSet.getMask()).isEqualTo(MaskedFlag.mask(getFirst())); } else { - assertThat(flagSet.getMask()) - .isNotEqualTo(MaskedFlag.mask(getFirst())); - assertThat(flagSet.getMaskWith(getFirst())) - .isEqualTo(MaskedFlag.mask(flag, getFirst())); + assertThat(flagSet.getMask()).isNotEqualTo(MaskedFlag.mask(getFirst())); + assertThat(flagSet.getMaskWith(getFirst())).isEqualTo(MaskedFlag.mask(flag, getFirst())); } - assertThat(flagSet.toString()) - .isNotNull(); - assertThat(flag.name()) - .isNotNull(); - assertThat(flagSet.getMaskWith(null)) - .isEqualTo(flagSet.getMask()); + assertThat(flagSet.toString()).isNotNull(); + assertThat(flag.name()).isNotNull(); + assertThat(flagSet.getMaskWith(null)).isEqualTo(flagSet.getMask()); } } @@ -123,72 +96,47 @@ void testAllFlags() { final T firstFlag = getFirst(); for (T flag : allFlags) { flags.add(flag); - final F flagSet = getBuilder() - .setFlags(flags) - .build(); + final F flagSet = getBuilder().setFlags(flags).build(); final int flagSetMask = flagSet.getMask(); - assertThat(masks) - .doesNotContain(flagSetMask); + assertThat(masks).doesNotContain(flagSetMask); masks.add(flagSetMask); - assertThat(flagSetMask) - .isEqualTo(MaskedFlag.mask(flags)); + assertThat(flagSetMask).isEqualTo(MaskedFlag.mask(flags)); final T[] flagsArr = flags.stream().toArray(this::toArray); - assertThat(flagSetMask) - .isEqualTo(MaskedFlag.mask(flagsArr)); - assertThat(flagSet.getFlags()) - .containsExactlyElementsOf(flags); - assertThat(flagSet) - .isNotEmpty(); - assertThat(FlagSet.equals(flagSet, getBuilder().setFlags(flagsArr).build())) - .isTrue(); - assertThat(FlagSet.equals(flagSet, getFlagSet(flags))) - .isTrue(); - assertThat(FlagSet.equals(flagSet, getFlagSet(flagsArr))) - .isTrue(); - assertThat(flagSet.size()) - .isEqualTo(flags.size()); - assertThat(flagSet.isSet(getFirst())) - .isEqualTo(true); + assertThat(flagSetMask).isEqualTo(MaskedFlag.mask(flagsArr)); + assertThat(flagSet.getFlags()).containsExactlyElementsOf(flags); + assertThat(flagSet).isNotEmpty(); + assertThat(FlagSet.equals(flagSet, getBuilder().setFlags(flagsArr).build())).isTrue(); + assertThat(FlagSet.equals(flagSet, getFlagSet(flags))).isTrue(); + assertThat(FlagSet.equals(flagSet, getFlagSet(flagsArr))).isTrue(); + assertThat(flagSet.size()).isEqualTo(flags.size()); + assertThat(flagSet.isSet(getFirst())).isEqualTo(true); final int maskWith = flagSet.getMaskWith(firstFlag); final List combinedList = new ArrayList<>(flags); combinedList.add(firstFlag); - assertThat(maskWith) - .isEqualTo(MaskedFlag.mask(combinedList)); + assertThat(maskWith).isEqualTo(MaskedFlag.mask(combinedList)); } } - /** - * Test as an enum instance rather than a {@link FlagSet} - */ + /** Test as an enum instance rather than a {@link FlagSet} */ @Test void testAsFlag() { final T flag = getFirst(); - assertThat(flag.size()) - .isEqualTo(1); - assertThat(flag.getFlags()) - .hasSize(1); + assertThat(flag.size()).isEqualTo(1); + assertThat(flag.getFlags()).hasSize(1); final T flag2 = flag.getFlags().iterator().next(); - assertThat(flag2 == flag) - .isTrue(); - assertThat(flag.getMask()) - .isEqualTo(MaskedFlag.mask(flag)); - assertThat(flag.isEmpty()) - .isFalse(); - assertThat(flag.toString()) - .isNotNull(); - assertThat(flag.isSet(flag)) - .isTrue(); - assertThat(flag.isSet(flag2)) - .isTrue(); - assertThat(flag.isSet(null)) - .isFalse(); + assertThat(flag2 == flag).isTrue(); + assertThat(flag.getMask()).isEqualTo(MaskedFlag.mask(flag)); + assertThat(flag.isEmpty()).isFalse(); + assertThat(flag.toString()).isNotNull(); + assertThat(flag.isSet(flag)).isTrue(); + assertThat(flag.isSet(flag2)).isTrue(); + assertThat(flag.isSet(null)).isFalse(); final List allFlags = getAllFlags(); if (allFlags.size() > 1) { T secondFlag = allFlags.get(1); - assertThat(flag.isSet(secondFlag)) - .isFalse(); + assertThat(flag.isSet(secondFlag)).isFalse(); } } diff --git a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java index d9312cce..6ee8f874 100644 --- a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java +++ b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java @@ -51,9 +51,7 @@ import org.lmdbjava.ByteBufferProxy.BufferMustBeDirectException; import org.lmdbjava.Env.ReadersFullException; -/** - * Test {@link ByteBufferProxy}. - */ +/** Test {@link ByteBufferProxy}. */ public final class ByteBufferProxyTest { static final MemoryManager MEM_MGR = RUNTIME.getMemoryManager(); @@ -61,25 +59,24 @@ public final class ByteBufferProxyTest { @Test void buffersMustBeDirect() { assertThatThrownBy( - () -> { - FileUtil.useTempDir( - dir -> { - try (Env env = create() - .setMaxReaders(1) - .open(dir)) { - final Dbi db = env.createDbi() - .setDbName(DB_1) - .withDefaultComparator() - .setDbiFlags(MDB_CREATE) - .open(); - final ByteBuffer key = allocate(100); - key.putInt(1).flip(); - final ByteBuffer val = allocate(100); - val.putInt(1).flip(); - db.put(key, val); // error - } - }); - }) + () -> { + FileUtil.useTempDir( + dir -> { + try (Env env = create().setMaxReaders(1).open(dir)) { + final Dbi db = + env.createDbi() + .setDbName(DB_1) + .withDefaultComparator() + .setDbiFlags(MDB_CREATE) + .open(); + final ByteBuffer key = allocate(100); + key.putInt(1).flip(); + final ByteBuffer val = allocate(100); + val.putInt(1).flip(); + db.put(key, val); // error + } + }); + }) .isInstanceOf(BufferMustBeDirectException.class); } @@ -104,9 +101,9 @@ void coverPrivateConstructor() { @Test void fieldNeverFound() { assertThatThrownBy( - () -> { - findField(Exception.class, "notARealField"); - }) + () -> { + findField(Exception.class, "notARealField"); + }) .isInstanceOf(LmdbException.class); } @@ -164,29 +161,29 @@ public void comparatorPerformance() { int x = 0; for (int round = 0; round < rounds; round++) { for (int i = 1; i < values.length; i++) { - buffer1.order(ByteOrder.nativeOrder()) - .putLong(0, values[i - 1]); - buffer2.order(ByteOrder.nativeOrder()) - .putLong(0, values[i]); - final int result = ByteBufferProxy.AbstractByteBufferProxy.compareAsIntegerKeys(buffer1, buffer2); + buffer1.order(ByteOrder.nativeOrder()).putLong(0, values[i - 1]); + buffer2.order(ByteOrder.nativeOrder()).putLong(0, values[i]); + final int result = + ByteBufferProxy.AbstractByteBufferProxy.compareAsIntegerKeys(buffer1, buffer2); x += result; } } - System.out.println("compareAsIntegerKeys: " + Duration.between(time, Instant.now()) + ", x: " + x); + System.out.println( + "compareAsIntegerKeys: " + Duration.between(time, Instant.now()) + ", x: " + x); time = Instant.now(); int y = 0; for (int round = 0; round < rounds; round++) { for (int i = 1; i < values.length; i++) { - buffer1.order(BIG_ENDIAN) - .putLong(0, values[i - 1]); - buffer2.order(BIG_ENDIAN) - .putLong(0, values[i]); - final int result = ByteBufferProxy.AbstractByteBufferProxy.compareLexicographically(buffer1, buffer2); + buffer1.order(BIG_ENDIAN).putLong(0, values[i - 1]); + buffer2.order(BIG_ENDIAN).putLong(0, values[i]); + final int result = + ByteBufferProxy.AbstractByteBufferProxy.compareLexicographically(buffer1, buffer2); y += result; } } - System.out.println("compareLexicographically: " + Duration.between(time, Instant.now()) + ", y: " + y); + System.out.println( + "compareLexicographically: " + Duration.between(time, Instant.now()) + ", y: " + y); assertThat(y).isEqualTo(x); } @@ -195,25 +192,27 @@ public void comparatorPerformance() { @Test public void verifyComparators() { final Random random = new Random(203948); - final ByteBuffer buffer1native = ByteBuffer.allocateDirect(Long.BYTES).order(ByteOrder.nativeOrder()); - final ByteBuffer buffer2native = ByteBuffer.allocateDirect(Long.BYTES).order(ByteOrder.nativeOrder()); + final ByteBuffer buffer1native = + ByteBuffer.allocateDirect(Long.BYTES).order(ByteOrder.nativeOrder()); + final ByteBuffer buffer2native = + ByteBuffer.allocateDirect(Long.BYTES).order(ByteOrder.nativeOrder()); final ByteBuffer buffer1be = ByteBuffer.allocateDirect(Long.BYTES).order(BIG_ENDIAN); final ByteBuffer buffer2be = ByteBuffer.allocateDirect(Long.BYTES).order(BIG_ENDIAN); buffer1native.limit(Long.BYTES); buffer2native.limit(Long.BYTES); buffer1be.limit(Long.BYTES); buffer2be.limit(Long.BYTES); - final long[] values = random.longs() - .filter(i -> i >= 0) - .limit(5_000_000) - .toArray(); -// System.out.println("stats: " + Arrays.stream(values) -// .summaryStatistics() -// .toString()); + final long[] values = random.longs().filter(i -> i >= 0).limit(5_000_000).toArray(); + // System.out.println("stats: " + Arrays.stream(values) + // .summaryStatistics() + // .toString()); final LinkedHashMap> comparators = new LinkedHashMap<>(); - comparators.put("compareAsIntegerKeys", ByteBufferProxy.AbstractByteBufferProxy::compareAsIntegerKeys); - comparators.put("compareLexicographically", ByteBufferProxy.AbstractByteBufferProxy::compareLexicographically); + comparators.put( + "compareAsIntegerKeys", ByteBufferProxy.AbstractByteBufferProxy::compareAsIntegerKeys); + comparators.put( + "compareLexicographically", + ByteBufferProxy.AbstractByteBufferProxy::compareLexicographically); final LinkedHashMap results = new LinkedHashMap<>(comparators.size()); final Set uniqueResults = new HashSet<>(comparators.size()); @@ -228,22 +227,23 @@ public void verifyComparators() { uniqueResults.clear(); // Make sure all comparators give the same result for the same inputs - comparators.forEach((name, comparator) -> { - final int result; - // IntegerKey comparator expects keys to have been written in native order so need different buffers. - if (name.equals("compareAsIntegerKeys")) { - result = comparator.compare(buffer1native, buffer2native); - } else { - result = comparator.compare(buffer1be, buffer2be); - } - results.put(name, result); - uniqueResults.add(result); - }); + comparators.forEach( + (name, comparator) -> { + final int result; + // IntegerKey comparator expects keys to have been written in native order so need + // different buffers. + if (name.equals("compareAsIntegerKeys")) { + result = comparator.compare(buffer1native, buffer2native); + } else { + result = comparator.compare(buffer1be, buffer2be); + } + results.put(name, result); + uniqueResults.add(result); + }); if (uniqueResults.size() != 1) { - Assertions.fail("Comparator mismatch for values: " - + val1 + " and " - + val2 + ". Results: " + results); + Assertions.fail( + "Comparator mismatch for values: " + val1 + " and " + val2 + ". Results: " + results); } } } diff --git a/src/test/java/org/lmdbjava/ByteUnitTest.java b/src/test/java/org/lmdbjava/ByteUnitTest.java index a899cbc3..5724429d 100644 --- a/src/test/java/org/lmdbjava/ByteUnitTest.java +++ b/src/test/java/org/lmdbjava/ByteUnitTest.java @@ -59,6 +59,5 @@ void test() { Assertions.assertThat(ByteUnit.PETABYTES.toBytes(1)).isEqualTo(1000000000000000L); Assertions.assertThat(ByteUnit.PETABYTES.toBytes(2)).isEqualTo(2000000000000000L); - } } diff --git a/src/test/java/org/lmdbjava/ComparatorIntegerKeyTest.java b/src/test/java/org/lmdbjava/ComparatorIntegerKeyTest.java index 32e0c8c5..00c0ce28 100644 --- a/src/test/java/org/lmdbjava/ComparatorIntegerKeyTest.java +++ b/src/test/java/org/lmdbjava/ComparatorIntegerKeyTest.java @@ -38,9 +38,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -/** - * Tests comparator functions are consistent across buffers. - */ +/** Tests comparator functions are consistent across buffers. */ public final class ComparatorIntegerKeyTest { static Stream comparatorProvider() { @@ -101,10 +99,7 @@ void testRandomLong(final ComparatorRunner runner) { final Random random = new Random(3239480); // 5mil random longs to compare - final long[] values = random.longs() - .filter(i -> i >= 0) - .limit(5_000_000) - .toArray(); + final long[] values = random.longs().filter(i -> i >= 0).limit(5_000_000).toArray(); for (int i = 1; i < values.length; i++) { final long long1 = values[i - 1]; @@ -114,20 +109,32 @@ void testRandomLong(final ComparatorRunner runner) { final ComparatorTest.ComparatorResult expectedResult = get(Long.compare(long1, long2)); assertThat(result) - .withFailMessage(() -> "Compare mismatch - long1: " + long1 - + ", long2: " + long2 - + ", expected: " + expectedResult - + ", actual: " + result) + .withFailMessage( + () -> + "Compare mismatch - long1: " + + long1 + + ", long2: " + + long2 + + ", expected: " + + expectedResult + + ", actual: " + + result) .isEqualTo(expectedResult); final ComparatorTest.ComparatorResult result2 = get(runner.compare(long2, long1)); final ComparatorTest.ComparatorResult expectedResult2 = expectedResult.opposite(); assertThat(result) - .withFailMessage(() -> "Compare mismatch for - long2: " + long2 - + ", long1: " + long1 - + ", expected2: " + expectedResult2 - + ", actual2: " + result2) + .withFailMessage( + () -> + "Compare mismatch for - long2: " + + long2 + + ", long1: " + + long1 + + ", expected2: " + + expectedResult2 + + ", actual2: " + + result2) .isEqualTo(expectedResult); } } @@ -138,10 +145,7 @@ void testRandomInt(final ComparatorRunner runner) { final Random random = new Random(3239480); // 5mil random ints to compare - final int[] values = random.ints() - .filter(i -> i >= 0) - .limit(5_000_000) - .toArray(); + final int[] values = random.ints().filter(i -> i >= 0).limit(5_000_000).toArray(); for (int i = 1; i < values.length; i++) { final int int1 = values[i - 1]; @@ -151,34 +155,43 @@ void testRandomInt(final ComparatorRunner runner) { final ComparatorTest.ComparatorResult expectedResult = get(Integer.compare(int1, int2)); assertThat(result) - .withFailMessage(() -> "Compare mismatch for - int1: " + int1 - + ", int2: " + int2 - + ", expected: " + expectedResult - + ", actual: " + result) + .withFailMessage( + () -> + "Compare mismatch for - int1: " + + int1 + + ", int2: " + + int2 + + ", expected: " + + expectedResult + + ", actual: " + + result) .isEqualTo(expectedResult); final ComparatorTest.ComparatorResult result2 = get(runner.compare(int2, int1)); final ComparatorTest.ComparatorResult expectedResult2 = expectedResult.opposite(); assertThat(result) - .withFailMessage(() -> "Compare mismatch for - int2: " + int2 - + ", int1: " + int1 - + ", expected2: " + expectedResult2 - + ", actual2: " + result2) + .withFailMessage( + () -> + "Compare mismatch for - int2: " + + int2 + + ", int1: " + + int1 + + ", expected2: " + + expectedResult2 + + ", actual2: " + + result2) .isEqualTo(expectedResult); } } - // -------------------------------------------------------------------------------- - - /** - * Tests {@link ByteBufferProxy}. - */ + /** Tests {@link ByteBufferProxy}. */ private static final class ByteBufferRunner implements ComparatorRunner { - private static final Comparator COMPARATOR = PROXY_OPTIMAL.getComparator(DbiFlags.MDB_INTEGERKEY); + private static final Comparator COMPARATOR = + PROXY_OPTIMAL.getComparator(DbiFlags.MDB_INTEGERKEY); @Override public int compare(long long1, long long2) { @@ -247,15 +260,12 @@ private ByteBuffer intToBuffer(final int val, final int bufferCapacity) { } } - // -------------------------------------------------------------------------------- - - /** - * Tests {@link DirectBufferProxy}. - */ + /** Tests {@link DirectBufferProxy}. */ private static final class DirectBufferRunner implements ComparatorRunner { - private static final Comparator COMPARATOR = PROXY_DB.getComparator(DbiFlags.MDB_INTEGERKEY); + private static final Comparator COMPARATOR = + PROXY_DB.getComparator(DbiFlags.MDB_INTEGERKEY); @Override public int compare(long long1, long long2) { @@ -276,12 +286,11 @@ public int compare(int int1, int int2) { } } - /** - * Tests {@link ByteBufProxy}. - */ + /** Tests {@link ByteBufProxy}. */ private static final class NettyRunner implements ComparatorRunner { - private static final Comparator COMPARATOR = PROXY_NETTY.getComparator(DbiFlags.MDB_INTEGERKEY); + private static final Comparator COMPARATOR = + PROXY_NETTY.getComparator(DbiFlags.MDB_INTEGERKEY); @Override public int compare(long long1, long long2) { @@ -322,13 +331,9 @@ public int compare(int int1, int int2) { } } - // -------------------------------------------------------------------------------- - - /** - * Interface that can test a {@link BufferProxy} compare method. - */ + /** Interface that can test a {@link BufferProxy} compare method. */ private interface ComparatorRunner { /** diff --git a/src/test/java/org/lmdbjava/CopyFlagSetTest.java b/src/test/java/org/lmdbjava/CopyFlagSetTest.java index e53c7508..898da499 100644 --- a/src/test/java/org/lmdbjava/CopyFlagSetTest.java +++ b/src/test/java/org/lmdbjava/CopyFlagSetTest.java @@ -22,39 +22,38 @@ public class CopyFlagSetTest extends AbstractFlagSetTest { - @Override - List getAllFlags() { - return Arrays.stream(CopyFlags.values()) - .collect(Collectors.toList()); - } - - @Override - CopyFlagSet getEmptyFlagSet() { - return CopyFlagSet.empty(); - } - - @Override - AbstractFlagSet.Builder getBuilder() { - return CopyFlagSet.builder(); - } - - @Override - CopyFlagSet getFlagSet(Collection flags) { - return CopyFlagSet.of(flags); - } - - @Override - CopyFlagSet getFlagSet(CopyFlags[] flags) { - return CopyFlagSet.of(flags); - } - - @Override - CopyFlagSet getFlagSet(CopyFlags flag) { - return CopyFlagSet.of(flag); - } - - @Override - Class getFlagType() { - return CopyFlags.class; - } + @Override + List getAllFlags() { + return Arrays.stream(CopyFlags.values()).collect(Collectors.toList()); + } + + @Override + CopyFlagSet getEmptyFlagSet() { + return CopyFlagSet.empty(); + } + + @Override + AbstractFlagSet.Builder getBuilder() { + return CopyFlagSet.builder(); + } + + @Override + CopyFlagSet getFlagSet(Collection flags) { + return CopyFlagSet.of(flags); + } + + @Override + CopyFlagSet getFlagSet(CopyFlags[] flags) { + return CopyFlagSet.of(flags); + } + + @Override + CopyFlagSet getFlagSet(CopyFlags flag) { + return CopyFlagSet.of(flag); + } + + @Override + Class getFlagType() { + return CopyFlags.class; + } } diff --git a/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java b/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java index 0e3d4e3e..7a2515b2 100644 --- a/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java @@ -79,18 +79,16 @@ import org.lmdbjava.CursorIterable.KeyVal; /** - * Test {@link CursorIterable} using {@link DbiFlags#MDB_INTEGERKEY} to ensure that - * comparators work with native order integer keys. + * Test {@link CursorIterable} using {@link DbiFlags#MDB_INTEGERKEY} to ensure that comparators work + * with native order integer keys. */ @Disabled // Waiting for the merge of stroomdev66's cursor tests @ParameterizedClass(name = "{index}: dbi: {0}") @ArgumentsSource(CursorIterableIntegerDupTest.MyArgumentProvider.class) public final class CursorIterableIntegerDupTest { - private static final DbiFlagSet DBI_FLAGS = DbiFlagSet.of( - MDB_CREATE, - MDB_INTEGERDUP, - MDB_DUPSORT); + private static final DbiFlagSet DBI_FLAGS = + DbiFlagSet.of(MDB_CREATE, MDB_INTEGERDUP, MDB_DUPSORT); private static final BufferProxy BUFFER_PROXY = ByteBufferProxy.PROXY_OPTIMAL; private static final List> INPUT_DATA; @@ -113,8 +111,7 @@ public final class CursorIterableIntegerDupTest { private Env env; private Deque> expectedEntriesDeque; - @Parameter - public DbiFactory dbiFactory; + @Parameter public DbiFactory dbiFactory; @BeforeEach public void before() throws IOException { @@ -185,15 +182,15 @@ private void populateDatabase(final Dbi dbi) { } txn.commit(); } -// try (Txn txn = env.txnRead(); -// CursorIterable c = dbi.iterate(txn)) { -// -// for (final KeyVal kv : c) { -// System.out.print(getNativeInt(kv.key()) + " => " + kv.val().getInt()); -// System.out.print(", "); -// } -// System.out.println(); -// } + // try (Txn txn = env.txnRead(); + // CursorIterable c = dbi.iterate(txn)) { + // + // for (final KeyVal kv : c) { + // System.out.print(getNativeInt(kv.key()) + " => " + kv.val().getInt()); + // System.out.print(", "); + // } + // System.out.println(); + // } } private int[] rangeInc(final int fromInc, final int toInc) { @@ -256,14 +253,16 @@ public void greaterThanTest() { } public void iterableOnlyReturnedOnce() { - Assertions.assertThatThrownBy(() -> { - final Dbi db = getDb(); - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { - c.iterator(); // ok - c.iterator(); // fails - } - }).isInstanceOf(IllegalStateException.class); + Assertions.assertThatThrownBy( + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + c.iterator(); // ok + c.iterator(); // fails + } + }) + .isInstanceOf(IllegalStateException.class); } @Test @@ -271,12 +270,12 @@ public void iterate() { populateExpectedEntriesDeque(); final Dbi db = getDb(); try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { + CursorIterable c = db.iterate(txn)) { for (final KeyVal kv : c) { final Map.Entry entry = expectedEntriesDeque.pollFirst(); assertThat(entry).isNotNull(); -// System.out.println(entry.getKey() + " => " + entry.getValue()); + // System.out.println(entry.getKey() + " => " + entry.getValue()); assertThat(getNativeInt(kv.key())).isEqualTo(entry.getKey()); assertThat(kv.val().getInt()).isEqualTo(entry.getValue()); } @@ -284,14 +283,16 @@ public void iterate() { } public void iteratorOnlyReturnedOnce() { - Assertions.assertThatThrownBy(() -> { - final Dbi db = getDb(); - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { - c.iterator(); // ok - c.iterator(); // fails - } - }).isInstanceOf(IllegalStateException.class); + Assertions.assertThatThrownBy( + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + c.iterator(); // ok + c.iterator(); // fails + } + }) + .isInstanceOf(IllegalStateException.class); } @Test @@ -336,10 +337,12 @@ public void openClosedBackwardTestWithGuava() { return guava.compare(array1, array2); }; - final Dbi guavaDbi = env.createDbi() - .setDbName(DB_1).withIteratorComparator(ignored -> comparator) - .setDbiFlags(MDB_CREATE) - .open(); + final Dbi guavaDbi = + env.createDbi() + .setDbName(DB_1) + .withIteratorComparator(ignored -> comparator) + .setDbiFlags(MDB_CREATE) + .open(); populateDatabase(guavaDbi); verify(openClosedBackward(bbNative(7), bbNative(2)), guavaDbi, 6, 4, 2); @@ -380,61 +383,68 @@ public void removeOddElements() { } public void nextWithClosedEnvTest() { - Assertions.assertThatThrownBy(() -> { - final Dbi db = getDb(); - try (Txn txn = env.txnRead()) { - try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { - final Iterator> c = ci.iterator(); + Assertions.assertThatThrownBy( + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); - env.close(); - c.next(); - } - } - }).isInstanceOf(Env.AlreadyClosedException.class); + env.close(); + c.next(); + } + } + }) + .isInstanceOf(Env.AlreadyClosedException.class); } public void removeWithClosedEnvTest() { - Assertions.assertThatThrownBy(() -> { - final Dbi db = getDb(); - try (Txn txn = env.txnWrite()) { - try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { - final Iterator> c = ci.iterator(); - final KeyVal keyVal = c.next(); - assertThat(keyVal).isNotNull(); - env.close(); - c.remove(); - } - } - }).isInstanceOf(Env.AlreadyClosedException.class); + Assertions.assertThatThrownBy( + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnWrite()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + final KeyVal keyVal = c.next(); + assertThat(keyVal).isNotNull(); + env.close(); + c.remove(); + } + } + }) + .isInstanceOf(Env.AlreadyClosedException.class); } public void hasNextWithClosedEnvTest() { - Assertions.assertThatThrownBy(() -> { - final Dbi db = getDb(); - try (Txn txn = env.txnRead()) { - try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { - final Iterator> c = ci.iterator(); - - env.close(); - c.hasNext(); - } - } - }).isInstanceOf(Env.AlreadyClosedException.class); + Assertions.assertThatThrownBy( + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + + env.close(); + c.hasNext(); + } + } + }) + .isInstanceOf(Env.AlreadyClosedException.class); } public void forEachRemainingWithClosedEnvTest() { - Assertions.assertThatThrownBy(() -> { - final Dbi db = getDb(); - try (Txn txn = env.txnRead()) { - try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { - final Iterator> c = ci.iterator(); - - env.close(); - c.forEachRemaining(keyVal -> { - }); - } - } - }).isInstanceOf(Env.AlreadyClosedException.class); + Assertions.assertThatThrownBy( + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + + env.close(); + c.forEachRemaining(keyVal -> {}); + } + } + }) + .isInstanceOf(Env.AlreadyClosedException.class); } private void verify(final KeyRange range, final int... expectedKeys) { @@ -443,40 +453,39 @@ private void verify(final KeyRange range, final int... expectedKeys) verify(range, db, expectedKeys); } - private void verify(final Dbi dbi, - final KeyRange range, - final int... expectedKeys) { + private void verify( + final Dbi dbi, final KeyRange range, final int... expectedKeys) { verify(range, dbi, expectedKeys); } - private void verify(final KeyRange range, - final Dbi dbi, - final int... expectedKeys) { + private void verify( + final KeyRange range, final Dbi dbi, final int... expectedKeys) { final boolean isForward = range.getType().isDirectionForward(); - final List expectedValues = Arrays.stream(expectedKeys) - .boxed() - .flatMap(key -> { - final int base = key * 10; - return isForward - ? Stream.of(base + 1, base + 2) - : Stream.of(base + 2, base + 1); - }) - .collect(Collectors.toList()); + final List expectedValues = + Arrays.stream(expectedKeys) + .boxed() + .flatMap( + key -> { + final int base = key * 10; + return isForward ? Stream.of(base + 1, base + 2) : Stream.of(base + 2, base + 1); + }) + .collect(Collectors.toList()); final List results = new ArrayList<>(); -// System.out.println(rangeToString(range) + ", expected: " + expectedValues); + // System.out.println(rangeToString(range) + ", expected: " + expectedValues); try (Txn txn = env.txnRead(); - CursorIterable c = dbi.iterate(txn, range)) { + CursorIterable c = dbi.iterate(txn, range)) { for (final KeyVal kv : c) { final int key = getNativeInt(kv.key()); final int val = kv.val().getInt(); -// System.out.println(key + " => " + val); + // System.out.println(key + " => " + val); results.add(val); - assertThat(val).satisfiesAnyOf( - v -> assertThat(v).isEqualTo((key * 10) + 1), - v -> assertThat(v).isEqualTo((key * 10) + 2)); + assertThat(val) + .satisfiesAnyOf( + v -> assertThat(v).isEqualTo((key * 10) + 1), + v -> assertThat(v).isEqualTo((key * 10) + 2)); } } @@ -489,8 +498,11 @@ private void verify(final KeyRange range, private String rangeToString(final KeyRange range) { final ByteBuffer start = range.getStart(); final ByteBuffer stop = range.getStop(); - return range.getType() + " start: " + (start != null ? getNativeInt(start) : "") - + " stop: " + (stop != null ? getNativeInt(stop) : ""); + return range.getType() + + " start: " + + (start != null ? getNativeInt(start) : "") + + " stop: " + + (stop != null ? getNativeInt(stop) : ""); } private Dbi getDb() { @@ -499,10 +511,8 @@ private Dbi getDb() { return dbi; } - // -------------------------------------------------------------------------------- - private static class DbiFactory { private final String name; private final Function, Dbi> factory; @@ -518,44 +528,51 @@ public String toString() { } } - // -------------------------------------------------------------------------------- - static class MyArgumentProvider implements ArgumentsProvider { @Override - public Stream provideArguments(ParameterDeclarations parameters, - ExtensionContext context) throws Exception { - final DbiFactory defaultComparatorDb = new DbiFactory("defaultComparator", env -> - env.createDbi() - .setDbName(DB_1) - .withDefaultComparator() - .setDbiFlags(DBI_FLAGS) - .open()); - final DbiFactory nativeComparatorDb = new DbiFactory("nativeComparator", env -> - env.createDbi() - .setDbName(DB_2) - .withNativeComparator() - .setDbiFlags(DBI_FLAGS) - .open()); - final DbiFactory callbackComparatorDb = new DbiFactory("callbackComparator", env -> - env.createDbi() - .setDbName(DB_3) - .withCallbackComparator(MyArgumentProvider::buildComparator) - .setDbiFlags(DBI_FLAGS) - .open()); - final DbiFactory iteratorComparatorDb = new DbiFactory("iteratorComparator", env -> - env.createDbi() - .setDbName(DB_4) - .withIteratorComparator(MyArgumentProvider::buildComparator) - .setDbiFlags(DBI_FLAGS) - .open()); + public Stream provideArguments( + ParameterDeclarations parameters, ExtensionContext context) throws Exception { + final DbiFactory defaultComparatorDb = + new DbiFactory( + "defaultComparator", + env -> + env.createDbi() + .setDbName(DB_1) + .withDefaultComparator() + .setDbiFlags(DBI_FLAGS) + .open()); + final DbiFactory nativeComparatorDb = + new DbiFactory( + "nativeComparator", + env -> + env.createDbi() + .setDbName(DB_2) + .withNativeComparator() + .setDbiFlags(DBI_FLAGS) + .open()); + final DbiFactory callbackComparatorDb = + new DbiFactory( + "callbackComparator", + env -> + env.createDbi() + .setDbName(DB_3) + .withCallbackComparator(MyArgumentProvider::buildComparator) + .setDbiFlags(DBI_FLAGS) + .open()); + final DbiFactory iteratorComparatorDb = + new DbiFactory( + "iteratorComparator", + env -> + env.createDbi() + .setDbName(DB_4) + .withIteratorComparator(MyArgumentProvider::buildComparator) + .setDbiFlags(DBI_FLAGS) + .open()); return Stream.of( - defaultComparatorDb, - nativeComparatorDb, - callbackComparatorDb, - iteratorComparatorDb) + defaultComparatorDb, nativeComparatorDb, callbackComparatorDb, iteratorComparatorDb) .map(Arguments::of); } @@ -564,8 +581,15 @@ private static Comparator buildComparator(final DbiFlagSet dbiFlagSe return (o1, o2) -> { if (o1.remaining() != o2.remaining()) { // Make sure LMDB is always giving us consistent key lengths. - Assertions.fail("o1: " + o1 + " " + getNativeIntOrLong(o1) - + ", o2: " + o2 + " " + getNativeIntOrLong(o2)); + Assertions.fail( + "o1: " + + o1 + + " " + + getNativeIntOrLong(o1) + + ", o2: " + + o2 + + " " + + getNativeIntOrLong(o2)); } return baseComparator.compare(o1, o2); }; diff --git a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java index 3ff4364f..c35767c0 100644 --- a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java @@ -81,8 +81,8 @@ import org.lmdbjava.CursorIterable.KeyVal; /** - * Test {@link CursorIterable} using {@link DbiFlags#MDB_INTEGERKEY} to ensure that - * comparators work with native order integer keys. + * Test {@link CursorIterable} using {@link DbiFlags#MDB_INTEGERKEY} to ensure that comparators work + * with native order integer keys. */ @ParameterizedClass(name = "{index}: dbi: {0}") @ArgumentsSource(CursorIterableIntegerKeyTest.MyArgumentProvider.class) @@ -95,19 +95,19 @@ public final class CursorIterableIntegerKeyTest { private Env env; private Deque list; - @Parameter - public DbiFactory dbiFactory; + @Parameter public DbiFactory dbiFactory; @BeforeEach public void before() throws IOException { file = FileUtil.createTempFile(); final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; - env = Env.create(bufferProxy) - .setMapSize(256, ByteUnit.KIBIBYTES) - .setMaxReaders(1) - .setMaxDbs(3) - .setEnvFlags(MDB_NOSUBDIR) - .open(file); + env = + Env.create(bufferProxy) + .setMapSize(256, ByteUnit.KIBIBYTES) + .setMaxReaders(1) + .setMaxDbs(3) + .setEnvFlags(MDB_NOSUBDIR) + .open(file); populateTestDataList(); } @@ -126,7 +126,7 @@ public void testNumericOrderLong() { final Cursor c = dbi.openCursor(txn); long i = 1; while (true) { -// System.out.println("putting " + i); + // System.out.println("putting " + i); c.put(bbNative(i), bb(i + "-long")); final long i2 = i * 10; if (i2 < i) { @@ -146,18 +146,14 @@ public void testNumericOrderLong() { final String val = getString(keyVal.val()); final long key = getNativeLong(keyVal.key()); entries.add(new AbstractMap.SimpleEntry<>(key, val)); -// System.out.println(val); + // System.out.println(val); } } } - final List dbKeys = entries.stream() - .map(Map.Entry::getKey) - .collect(Collectors.toList()); - final List dbKeysSorted = entries.stream() - .map(Map.Entry::getKey) - .sorted() - .collect(Collectors.toList()); + final List dbKeys = entries.stream().map(Map.Entry::getKey).collect(Collectors.toList()); + final List dbKeysSorted = + entries.stream().map(Map.Entry::getKey).sorted().collect(Collectors.toList()); for (int i = 0; i < dbKeys.size(); i++) { final long dbKey1 = dbKeys.get(i); final long dbKey2 = dbKeysSorted.get(i); @@ -173,7 +169,7 @@ public void testNumericOrderInt() { final Cursor c = dbi.openCursor(txn); int i = 1; while (true) { -// System.out.println("putting " + i); + // System.out.println("putting " + i); c.put(bbNative(i), bb(i + "-int")); final int i2 = i * 10; if (i2 < i) { @@ -193,18 +189,15 @@ public void testNumericOrderInt() { final String val = getString(keyVal.val()); final int key = TestUtils.getNativeInt(keyVal.key()); entries.add(new AbstractMap.SimpleEntry<>(key, val)); -// System.out.println(val); + // System.out.println(val); } } } - final List dbKeys = entries.stream() - .map(Map.Entry::getKey) - .collect(Collectors.toList()); - final List dbKeysSorted = entries.stream() - .map(Map.Entry::getKey) - .sorted() - .collect(Collectors.toList()); + final List dbKeys = + entries.stream().map(Map.Entry::getKey).collect(Collectors.toList()); + final List dbKeysSorted = + entries.stream().map(Map.Entry::getKey).sorted().collect(Collectors.toList()); for (int i = 0; i < dbKeys.size(); i++) { final long dbKey1 = dbKeys.get(i); final long dbKey2 = dbKeysSorted.get(i); @@ -218,7 +211,7 @@ public void testIntegerKeyKeySize() { long maxIntAsLong = Integer.MAX_VALUE; try (Txn txn = env.txnWrite()) { -// System.out.println("Flags: " + db.listFlags(txn)); + // System.out.println("Flags: " + db.listFlags(txn)); int val = 0; db.put(txn, bbNative(0L), bb("val_" + ++val)); db.put(txn, bbNative(10L), bb("val_" + ++val)); @@ -233,16 +226,16 @@ public void testIntegerKeyKeySize() { db.put(txn, bbNative(Long.MAX_VALUE), bb("val_" + ++val)); txn.commit(); } -// try (Txn txn = env.txnRead()) { -// try (CursorIterable iterable = db.iterate(txn)) { -// for (KeyVal keyVal : iterable) { -// final String val = getString(keyVal.val()); -// final long key = getNativeLong(keyVal.key()); -// final int remaining = keyVal.key().remaining(); -// System.out.println("key: " + key + ", val: " + val + ", remaining: " + remaining); -// } -// } -// } + // try (Txn txn = env.txnRead()) { + // try (CursorIterable iterable = db.iterate(txn)) { + // for (KeyVal keyVal : iterable) { + // final String val = getString(keyVal.val()); + // final long key = getNativeLong(keyVal.key()); + // final int remaining = keyVal.key().remaining(); + // System.out.println("key: " + key + ", val: " + val + ", remaining: " + remaining); + // } + // } + // } } @Test @@ -337,14 +330,16 @@ public void greaterThanTest() { } public void iterableOnlyReturnedOnce() { - Assertions.assertThatThrownBy(() -> { - final Dbi db = getDb(); - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { - c.iterator(); // ok - c.iterator(); // fails - } - }).isInstanceOf(IllegalStateException.class); + Assertions.assertThatThrownBy( + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + c.iterator(); // ok + c.iterator(); // fails + } + }) + .isInstanceOf(IllegalStateException.class); } @Test @@ -352,7 +347,7 @@ public void iterate() { populateTestDataList(); final Dbi db = getDb(); try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { + CursorIterable c = db.iterate(txn)) { for (final KeyVal kv : c) { assertThat(getNativeInt(kv.key())).isEqualTo(list.pollFirst()); @@ -362,14 +357,16 @@ public void iterate() { } public void iteratorOnlyReturnedOnce() { - Assertions.assertThatThrownBy(() -> { - final Dbi db = getDb(); - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { - c.iterator(); // ok - c.iterator(); // fails - } - }).isInstanceOf(IllegalStateException.class); + Assertions.assertThatThrownBy( + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + c.iterator(); // ok + c.iterator(); // fails + } + }) + .isInstanceOf(IllegalStateException.class); } @Test @@ -385,21 +382,23 @@ public void lessThanTest() { } public void nextThrowsNoSuchElementExceptionIfNoMoreElements() { - Assertions.assertThatThrownBy(() -> { - populateTestDataList(); - final Dbi db = getDb(); - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { - final Iterator> i = c.iterator(); - while (i.hasNext()) { - final KeyVal kv = i.next(); - assertThat(getNativeInt(kv.key())).isEqualTo(list.pollFirst()); - assertThat(kv.val().getInt()).isEqualTo(list.pollFirst()); - } - assertThat(i.hasNext()).isEqualTo(false); - i.next(); - } - }).isInstanceOf(NoSuchElementException.class); + Assertions.assertThatThrownBy( + () -> { + populateTestDataList(); + final Dbi db = getDb(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + final Iterator> i = c.iterator(); + while (i.hasNext()) { + final KeyVal kv = i.next(); + assertThat(getNativeInt(kv.key())).isEqualTo(list.pollFirst()); + assertThat(kv.val().getInt()).isEqualTo(list.pollFirst()); + } + assertThat(i.hasNext()).isEqualTo(false); + i.next(); + } + }) + .isInstanceOf(NoSuchElementException.class); } @Test @@ -431,11 +430,12 @@ public void openClosedBackwardTestWithGuava() { bb2.reset(); return guava.compare(array1, array2); }; - final Dbi guavaDbi = env.createDbi() - .setDbName(DB_1) - .withIteratorComparator(ignored -> comparator) - .setDbiFlags(MDB_CREATE) - .open(); + final Dbi guavaDbi = + env.createDbi() + .setDbName(DB_1) + .withIteratorComparator(ignored -> comparator) + .setDbiFlags(MDB_CREATE) + .open(); populateDatabase(guavaDbi); verify(openClosedBackward(bbNative(7), bbNative(2)), guavaDbi, 6, 4, 2); verify(openClosedBackward(bbNative(8), bbNative(4)), guavaDbi, 6, 4); @@ -475,63 +475,70 @@ public void removeOddElements() { } public void nextWithClosedEnvTest() { - Assertions.assertThatThrownBy(() -> { - final Dbi db = getDb(); - try (Txn txn = env.txnRead()) { - try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { - final Iterator> c = ci.iterator(); + Assertions.assertThatThrownBy( + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); - env.close(); - c.next(); - } - } - }).isInstanceOf(Env.AlreadyClosedException.class); + env.close(); + c.next(); + } + } + }) + .isInstanceOf(Env.AlreadyClosedException.class); } public void removeWithClosedEnvTest() { - Assertions.assertThatThrownBy(() -> { - final Dbi db = getDb(); - try (Txn txn = env.txnWrite()) { - try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { - final Iterator> c = ci.iterator(); + Assertions.assertThatThrownBy( + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnWrite()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); - final KeyVal keyVal = c.next(); - assertThat(keyVal).isNotNull(); + final KeyVal keyVal = c.next(); + assertThat(keyVal).isNotNull(); - env.close(); - c.remove(); - } - } - }).isInstanceOf(Env.AlreadyClosedException.class); + env.close(); + c.remove(); + } + } + }) + .isInstanceOf(Env.AlreadyClosedException.class); } public void hasNextWithClosedEnvTest() { - Assertions.assertThatThrownBy(() -> { - final Dbi db = getDb(); - try (Txn txn = env.txnRead()) { - try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { - final Iterator> c = ci.iterator(); - - env.close(); - c.hasNext(); - } - } - }).isInstanceOf(Env.AlreadyClosedException.class); + Assertions.assertThatThrownBy( + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + + env.close(); + c.hasNext(); + } + } + }) + .isInstanceOf(Env.AlreadyClosedException.class); } public void forEachRemainingWithClosedEnvTest() { - Assertions.assertThatThrownBy(() -> { - final Dbi db = getDb(); - try (Txn txn = env.txnRead()) { - try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { - final Iterator> c = ci.iterator(); - - env.close(); - c.forEachRemaining(keyVal -> { - }); - } - } - }).isInstanceOf(Env.AlreadyClosedException.class); + Assertions.assertThatThrownBy( + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + + env.close(); + c.forEachRemaining(keyVal -> {}); + } + } + }) + .isInstanceOf(Env.AlreadyClosedException.class); } private void verify(final KeyRange range, final int... expected) { @@ -551,7 +558,7 @@ private void verify( final List results = new ArrayList<>(); try (Txn txn = env.txnRead(); - CursorIterable c = dbi.iterate(txn, range)) { + CursorIterable c = dbi.iterate(txn, range)) { for (final KeyVal kv : c) { final int key = kv.key().order(ByteOrder.nativeOrder()).getInt(); final int val = kv.val().getInt(); @@ -572,10 +579,8 @@ private Dbi getDb() { return dbi; } - // -------------------------------------------------------------------------------- - private static class DbiFactory { private final String name; private final Function, Dbi> factory; @@ -591,44 +596,51 @@ public String toString() { } } - // -------------------------------------------------------------------------------- - static class MyArgumentProvider implements ArgumentsProvider { @Override - public Stream provideArguments(ParameterDeclarations parameters, - ExtensionContext context) throws Exception { - final DbiFactory defaultComparatorDb = new DbiFactory("defaultComparator", env -> - env.createDbi() - .setDbName(DB_1) - .withDefaultComparator() - .setDbiFlags(DBI_FLAGS) - .open()); - final DbiFactory nativeComparatorDb = new DbiFactory("nativeComparator", env -> - env.createDbi() - .setDbName(DB_2) - .withNativeComparator() - .setDbiFlags(DBI_FLAGS) - .open()); - final DbiFactory callbackComparatorDb = new DbiFactory("callbackComparator", env -> - env.createDbi() - .setDbName(DB_3) - .withCallbackComparator(MyArgumentProvider::buildComparator) - .setDbiFlags(DBI_FLAGS) - .open()); - final DbiFactory iteratorComparatorDb = new DbiFactory("iteratorComparator", env -> - env.createDbi() - .setDbName(DB_4) - .withIteratorComparator(MyArgumentProvider::buildComparator) - .setDbiFlags(DBI_FLAGS) - .open()); + public Stream provideArguments( + ParameterDeclarations parameters, ExtensionContext context) throws Exception { + final DbiFactory defaultComparatorDb = + new DbiFactory( + "defaultComparator", + env -> + env.createDbi() + .setDbName(DB_1) + .withDefaultComparator() + .setDbiFlags(DBI_FLAGS) + .open()); + final DbiFactory nativeComparatorDb = + new DbiFactory( + "nativeComparator", + env -> + env.createDbi() + .setDbName(DB_2) + .withNativeComparator() + .setDbiFlags(DBI_FLAGS) + .open()); + final DbiFactory callbackComparatorDb = + new DbiFactory( + "callbackComparator", + env -> + env.createDbi() + .setDbName(DB_3) + .withCallbackComparator(MyArgumentProvider::buildComparator) + .setDbiFlags(DBI_FLAGS) + .open()); + final DbiFactory iteratorComparatorDb = + new DbiFactory( + "iteratorComparator", + env -> + env.createDbi() + .setDbName(DB_4) + .withIteratorComparator(MyArgumentProvider::buildComparator) + .setDbiFlags(DBI_FLAGS) + .open()); return Stream.of( - defaultComparatorDb, - nativeComparatorDb, - callbackComparatorDb, - iteratorComparatorDb) + defaultComparatorDb, nativeComparatorDb, callbackComparatorDb, iteratorComparatorDb) .map(Arguments::of); } @@ -637,8 +649,15 @@ private static Comparator buildComparator(final DbiFlagSet dbiFlagSe return (o1, o2) -> { if (o1.remaining() != o2.remaining()) { // Make sure LMDB is always giving us consistent key lengths. - Assertions.fail("o1: " + o1 + " " + getNativeIntOrLong(o1) - + ", o2: " + o2 + " " + getNativeIntOrLong(o2)); + Assertions.fail( + "o1: " + + o1 + + " " + + getNativeIntOrLong(o1) + + ", o2: " + + o2 + + " " + + getNativeIntOrLong(o2)); } return baseComparator.compare(o1, o2); }; diff --git a/src/test/java/org/lmdbjava/CursorIterablePerfTest.java b/src/test/java/org/lmdbjava/CursorIterablePerfTest.java index 82bf337d..9470abb2 100644 --- a/src/test/java/org/lmdbjava/CursorIterablePerfTest.java +++ b/src/test/java/org/lmdbjava/CursorIterablePerfTest.java @@ -58,24 +58,27 @@ public void before() throws IOException { final DbiFlagSet dbiFlagSet = MDB_CREATE; // Use a java comparator for start/stop keys only - Dbi dbJavaComparator = env.createDbi() - .setDbName("JavaComparator") - .withDefaultComparator() - .setDbiFlags(dbiFlagSet) - .open(); + Dbi dbJavaComparator = + env.createDbi() + .setDbName("JavaComparator") + .withDefaultComparator() + .setDbiFlags(dbiFlagSet) + .open(); // Use LMDB comparator for start/stop keys - Dbi dbLmdbComparator = env.createDbi() - .setDbName("LmdbComparator") - .withNativeComparator() - .setDbiFlags(dbiFlagSet) - .open(); + Dbi dbLmdbComparator = + env.createDbi() + .setDbName("LmdbComparator") + .withNativeComparator() + .setDbiFlags(dbiFlagSet) + .open(); // Use a java comparator for start/stop keys and as a callback comparator - Dbi dbCallbackComparator = env.createDbi() - .setDbName("CallBackComparator") - .withCallbackComparator(bufferProxy::getComparator) - .setDbiFlags(dbiFlagSet) - .open(); + Dbi dbCallbackComparator = + env.createDbi() + .setDbName("CallBackComparator") + .withCallbackComparator(bufferProxy::getComparator) + .setDbiFlags(dbiFlagSet) + .open(); dbs.add(dbJavaComparator); dbs.add(dbLmdbComparator); diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index e57e8ca4..7c1aa227 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -71,9 +71,7 @@ import org.junit.jupiter.params.support.ParameterDeclarations; import org.lmdbjava.CursorIterable.KeyVal; -/** - * Test {@link CursorIterable}. - */ +/** Test {@link CursorIterable}. */ @ParameterizedClass(name = "{index}: dbi: {0}") @ArgumentsSource(CursorIterableTest.MyArgumentProvider.class) public final class CursorIterableTest { @@ -85,23 +83,23 @@ public final class CursorIterableTest { private Env env; private Deque list; -// /** -// * Injected by {@link #data()} with appropriate runner. -// */ -// @SuppressWarnings("ClassEscapesDefinedScope") - @Parameter - public DbiFactory dbiFactory; + // /** + // * Injected by {@link #data()} with appropriate runner. + // */ + // @SuppressWarnings("ClassEscapesDefinedScope") + @Parameter public DbiFactory dbiFactory; @BeforeEach void beforeEach() { file = FileUtil.createTempFile(); final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; - env = create(bufferProxy) - .setMapSize(256, ByteUnit.KIBIBYTES) - .setMaxReaders(1) - .setMaxDbs(3) - .setEnvFlags(MDB_NOSUBDIR) - .open(file); + env = + create(bufferProxy) + .setMapSize(256, ByteUnit.KIBIBYTES) + .setMaxReaders(1) + .setMaxDbs(3) + .setEnvFlags(MDB_NOSUBDIR) + .open(file); populateTestDataList(); } @@ -112,7 +110,6 @@ void afterEach() { FileUtil.delete(file); } - private void populateTestDataList() { list = new LinkedList<>(); list.addAll(asList(2, 3, 4, 5, 6, 7, 8, 9)); @@ -207,14 +204,14 @@ void greaterThanTest() { @Test void iterableOnlyReturnedOnce() { assertThatThrownBy( - () -> { - final Dbi db = getDb(); - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { - c.iterator(); // ok - c.iterator(); // fails - } - }) + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + c.iterator(); // ok + c.iterator(); // fails + } + }) .isInstanceOf(IllegalStateException.class); } @@ -222,7 +219,7 @@ void iterableOnlyReturnedOnce() { void iterate() { final Dbi db = getDb(); try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { + CursorIterable c = db.iterate(txn)) { for (final KeyVal kv : c) { assertThat(kv.key().getInt()).isEqualTo(list.pollFirst()); @@ -234,14 +231,14 @@ void iterate() { @Test void iteratorOnlyReturnedOnce() { assertThatThrownBy( - () -> { - final Dbi db = getDb(); - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { - c.iterator(); // ok - c.iterator(); // fails - } - }) + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + c.iterator(); // ok + c.iterator(); // fails + } + }) .isInstanceOf(IllegalStateException.class); } @@ -260,21 +257,21 @@ void lessThanTest() { @Test void nextThrowsNoSuchElementExceptionIfNoMoreElements() { assertThatThrownBy( - () -> { - final Dbi db = getDb(); - populateTestDataList(); - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { - final Iterator> i = c.iterator(); - while (i.hasNext()) { - final KeyVal kv = i.next(); - assertThat(kv.key().getInt()).isEqualTo(list.pollFirst()); - assertThat(kv.val().getInt()).isEqualTo(list.pollFirst()); - } - assertThat(i.hasNext()).isFalse(); - i.next(); - } - }) + () -> { + final Dbi db = getDb(); + populateTestDataList(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + final Iterator> i = c.iterator(); + while (i.hasNext()) { + final KeyVal kv = i.next(); + assertThat(kv.key().getInt()).isEqualTo(list.pollFirst()); + assertThat(kv.val().getInt()).isEqualTo(list.pollFirst()); + } + assertThat(i.hasNext()).isFalse(); + i.next(); + } + }) .isInstanceOf(NoSuchElementException.class); } @@ -307,11 +304,8 @@ void openClosedBackwardTestWithGuava() { bb2.reset(); return guava.compare(array1, array2); }; - final Dbi guavaDbi = env.createDbi() - .setDbName(DB_1) - .withDefaultComparator() - .setDbiFlags(MDB_CREATE) - .open(); + final Dbi guavaDbi = + env.createDbi().setDbName(DB_1).withDefaultComparator().setDbiFlags(MDB_CREATE).open(); populateDatabase(guavaDbi); verify(openClosedBackward(bb(7), bb(2)), guavaDbi, 6, 4, 2); verify(openClosedBackward(bb(8), bb(4)), guavaDbi, 6, 4); @@ -353,121 +347,121 @@ void removeOddElements() { @Test void nextWithClosedEnvTest() { assertThatThrownBy( - () -> { - final Dbi db = getDb(); - try (Txn txn = env.txnRead()) { - try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { - final Iterator> c = ci.iterator(); - - env.close(); - c.next(); - } - } - }) + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + + env.close(); + c.next(); + } + } + }) .isInstanceOf(Env.AlreadyClosedException.class); } @Test void removeWithClosedEnvTest() { assertThatThrownBy( - () -> { - final Dbi db = getDb(); - try (Txn txn = env.txnWrite()) { - try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { - final Iterator> c = ci.iterator(); - - final KeyVal keyVal = c.next(); - assertThat(keyVal).isNotNull(); - - env.close(); - c.remove(); - } - } - }) + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnWrite()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + + final KeyVal keyVal = c.next(); + assertThat(keyVal).isNotNull(); + + env.close(); + c.remove(); + } + } + }) .isInstanceOf(Env.AlreadyClosedException.class); } @Test void hasNextWithClosedEnvTest() { assertThatThrownBy( - () -> { - final Dbi db = getDb(); - try (Txn txn = env.txnRead()) { - try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { - final Iterator> c = ci.iterator(); - - env.close(); - c.hasNext(); - } - } - }) + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + + env.close(); + c.hasNext(); + } + } + }) .isInstanceOf(Env.AlreadyClosedException.class); } @Test void forEachRemainingWithClosedEnvTest() { assertThatThrownBy( - () -> { - final Dbi db = getDb(); - try (Txn txn = env.txnRead()) { - try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { - final Iterator> c = ci.iterator(); - - env.close(); - c.forEachRemaining(keyVal -> { - }); - } - } - }) + () -> { + final Dbi db = getDb(); + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + + env.close(); + c.forEachRemaining(keyVal -> {}); + } + } + }) .isInstanceOf(Env.AlreadyClosedException.class); } -// @Test -// public void testSignedVsUnsigned() { -// final ByteBuffer val1 = bb(1); -// final ByteBuffer val2 = bb(2); -// final ByteBuffer val110 = bb(110); -// final ByteBuffer val111 = bb(111); -// final ByteBuffer val150 = bb(150); -// -// final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; -// final Comparator unsignedComparator = bufferProxy.getUnsignedComparator(); -// final Comparator signedComparator = bufferProxy.getSignedComparator(); -// -// // Compare the same -// assertThat( -// unsignedComparator.compare(val1, val2), Matchers.is(signedComparator.compare(val1, val2))); -// -// // Compare differently -// assertThat( -// unsignedComparator.compare(val110, val150), -// Matchers.not(signedComparator.compare(val110, val150))); -// -// // Compare differently -// assertThat( -// unsignedComparator.compare(val111, val150), -// Matchers.not(signedComparator.compare(val111, val150))); -// -// // This will fail if the db is using a signed comparator for the start/stop keys -// for (final Dbi db : dbs) { -// db.put(val110, val110); -// db.put(val150, val150); -// -// final ByteBuffer startKeyBuf = val111; -// KeyRange keyRange = KeyRange.atLeastBackward(startKeyBuf); -// -// try (Txn txn = env.txnRead(); -// CursorIterable c = db.iterate(txn, keyRange)) { -// for (final CursorIterable.KeyVal kv : c) { -// final int key = kv.key().getInt(); -// final int val = kv.val().getInt(); -// // System.out.println("key: " + key + " val: " + val); -// assertThat(key, is(110)); -// break; -// } -// } -// } -// } + // @Test + // public void testSignedVsUnsigned() { + // final ByteBuffer val1 = bb(1); + // final ByteBuffer val2 = bb(2); + // final ByteBuffer val110 = bb(110); + // final ByteBuffer val111 = bb(111); + // final ByteBuffer val150 = bb(150); + // + // final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; + // final Comparator unsignedComparator = bufferProxy.getUnsignedComparator(); + // final Comparator signedComparator = bufferProxy.getSignedComparator(); + // + // // Compare the same + // assertThat( + // unsignedComparator.compare(val1, val2), Matchers.is(signedComparator.compare(val1, + // val2))); + // + // // Compare differently + // assertThat( + // unsignedComparator.compare(val110, val150), + // Matchers.not(signedComparator.compare(val110, val150))); + // + // // Compare differently + // assertThat( + // unsignedComparator.compare(val111, val150), + // Matchers.not(signedComparator.compare(val111, val150))); + // + // // This will fail if the db is using a signed comparator for the start/stop keys + // for (final Dbi db : dbs) { + // db.put(val110, val110); + // db.put(val150, val150); + // + // final ByteBuffer startKeyBuf = val111; + // KeyRange keyRange = KeyRange.atLeastBackward(startKeyBuf); + // + // try (Txn txn = env.txnRead(); + // CursorIterable c = db.iterate(txn, keyRange)) { + // for (final CursorIterable.KeyVal kv : c) { + // final int key = kv.key().getInt(); + // final int val = kv.val().getInt(); + // // System.out.println("key: " + key + " val: " + val); + // assertThat(key, is(110)); + // break; + // } + // } + // } + // } private void verify(final KeyRange range, final int... expected) { final Dbi db = getDb(); @@ -479,14 +473,13 @@ private void verify( verify(range, dbi, expected); } - private void verify(final KeyRange range, - final Dbi dbi, - final int... expected) { + private void verify( + final KeyRange range, final Dbi dbi, final int... expected) { final List results = new ArrayList<>(); try (Txn txn = env.txnRead(); - CursorIterable c = dbi.iterate(txn, range)) { + CursorIterable c = dbi.iterate(txn, range)) { for (final KeyVal kv : c) { final int key = kv.key().getInt(); final int val = kv.val().getInt(); @@ -507,10 +500,8 @@ private Dbi getDb() { return dbi; } - // -------------------------------------------------------------------------------- - private static class DbiFactory { private final String name; private final Function, Dbi> factory; @@ -526,44 +517,51 @@ public String toString() { } } - // -------------------------------------------------------------------------------- - static class MyArgumentProvider implements ArgumentsProvider { @Override - public Stream provideArguments(ParameterDeclarations parameters, - ExtensionContext context) throws Exception { - final DbiFactory defaultComparatorDb = new DbiFactory("defaultComparator", env -> - env.createDbi() - .setDbName(DB_1) - .withDefaultComparator() - .setDbiFlags(DBI_FLAGS) - .open()); - final DbiFactory nativeComparatorDb = new DbiFactory("nativeComparator", env -> - env.createDbi() - .setDbName(DB_2) - .withNativeComparator() - .setDbiFlags(DBI_FLAGS) - .open()); - final DbiFactory callbackComparatorDb = new DbiFactory("callbackComparator", env -> - env.createDbi() - .setDbName(DB_3) - .withCallbackComparator(BUFFER_PROXY::getComparator) - .setDbiFlags(DBI_FLAGS) - .open()); - final DbiFactory iteratorComparatorDb = new DbiFactory("iteratorComparator", env -> - env.createDbi() - .setDbName(DB_4) - .withIteratorComparator(BUFFER_PROXY::getComparator) - .setDbiFlags(DBI_FLAGS) - .open()); + public Stream provideArguments( + ParameterDeclarations parameters, ExtensionContext context) throws Exception { + final DbiFactory defaultComparatorDb = + new DbiFactory( + "defaultComparator", + env -> + env.createDbi() + .setDbName(DB_1) + .withDefaultComparator() + .setDbiFlags(DBI_FLAGS) + .open()); + final DbiFactory nativeComparatorDb = + new DbiFactory( + "nativeComparator", + env -> + env.createDbi() + .setDbName(DB_2) + .withNativeComparator() + .setDbiFlags(DBI_FLAGS) + .open()); + final DbiFactory callbackComparatorDb = + new DbiFactory( + "callbackComparator", + env -> + env.createDbi() + .setDbName(DB_3) + .withCallbackComparator(BUFFER_PROXY::getComparator) + .setDbiFlags(DBI_FLAGS) + .open()); + final DbiFactory iteratorComparatorDb = + new DbiFactory( + "iteratorComparator", + env -> + env.createDbi() + .setDbName(DB_4) + .withIteratorComparator(BUFFER_PROXY::getComparator) + .setDbiFlags(DBI_FLAGS) + .open()); return Stream.of( - defaultComparatorDb, - nativeComparatorDb, - callbackComparatorDb, - iteratorComparatorDb) + defaultComparatorDb, nativeComparatorDb, callbackComparatorDb, iteratorComparatorDb) .map(Arguments::of); } } diff --git a/src/test/java/org/lmdbjava/CursorParamTest.java b/src/test/java/org/lmdbjava/CursorParamTest.java index f2fc5fbb..3dac152e 100644 --- a/src/test/java/org/lmdbjava/CursorParamTest.java +++ b/src/test/java/org/lmdbjava/CursorParamTest.java @@ -56,7 +56,8 @@ public final class CursorParamTest { static Stream data() { return Stream.of( - Arguments.argumentSet("ByteBufferRunner(PROXY_OPTIMAL)", new ByteBufferRunner(PROXY_OPTIMAL)), + Arguments.argumentSet( + "ByteBufferRunner(PROXY_OPTIMAL)", new ByteBufferRunner(PROXY_OPTIMAL)), Arguments.argumentSet("ByteBufferRunner(PROXY_SAFE)", new ByteBufferRunner(PROXY_SAFE)), Arguments.argumentSet("ByteArrayRunner(PROXY_BA)", new ByteArrayRunner(PROXY_BA)), Arguments.argumentSet("DirectBufferRunner", new DirectBufferRunner()), diff --git a/src/test/java/org/lmdbjava/CursorTest.java b/src/test/java/org/lmdbjava/CursorTest.java index 7177000a..3d6b5b86 100644 --- a/src/test/java/org/lmdbjava/CursorTest.java +++ b/src/test/java/org/lmdbjava/CursorTest.java @@ -277,7 +277,8 @@ void getKeyVal() { @Test void putMultiple() { - final Dbi db = env.openDbi(DB_1, DbiFlagSet.of(MDB_CREATE, MDB_DUPSORT, MDB_DUPFIXED)); + final Dbi db = + env.openDbi(DB_1, DbiFlagSet.of(MDB_CREATE, MDB_DUPSORT, MDB_DUPFIXED)); final int elemCount = 20; final ByteBuffer values = allocateDirect(Integer.BYTES * elemCount); diff --git a/src/test/java/org/lmdbjava/DbiBuilderTest.java b/src/test/java/org/lmdbjava/DbiBuilderTest.java index ede387c9..f7484f00 100644 --- a/src/test/java/org/lmdbjava/DbiBuilderTest.java +++ b/src/test/java/org/lmdbjava/DbiBuilderTest.java @@ -40,12 +40,13 @@ public class DbiBuilderTest { @BeforeEach public void before() { file = FileUtil.createTempFile(); - env = create() - .setMapSize(64, ByteUnit.MEBIBYTES) - .setMaxReaders(2) - .setMaxDbs(2) - .setEnvFlags(MDB_NOSUBDIR) - .open(file); + env = + create() + .setMapSize(64, ByteUnit.MEBIBYTES) + .setMaxReaders(2) + .setMaxDbs(2) + .setEnvFlags(MDB_NOSUBDIR) + .open(file); } @AfterEach @@ -56,11 +57,12 @@ public void after() { @Test public void unnamed() { - final Dbi dbi = env.createDbi() - .withoutDbName() - .withDefaultComparator() - .setDbiFlags(DbiFlags.MDB_CREATE) - .open(); + final Dbi dbi = + env.createDbi() + .withoutDbName() + .withDefaultComparator() + .setDbiFlags(DbiFlags.MDB_CREATE) + .open(); assertThat(dbi.getName()).isNull(); assertThat(dbi.getNameAsString()).isEmpty(); assertThat(env.getDbiNames()).isEmpty(); @@ -69,49 +71,46 @@ public void unnamed() { @Test public void named() { - final Dbi dbi = env.createDbi() - .setDbName("foo") - .withDefaultComparator() - .setDbiFlags(DbiFlags.MDB_CREATE) - .open(); + final Dbi dbi = + env.createDbi() + .setDbName("foo") + .withDefaultComparator() + .setDbiFlags(DbiFlags.MDB_CREATE) + .open(); assertPutAndGet(dbi); assertThat(env.getDbiNames()).hasSize(1); - assertThat(new String(env.getDbiNames().get(0), StandardCharsets.UTF_8)) - .isEqualTo("foo"); - assertThat(dbi.getNameAsString()) - .isEqualTo("foo"); - assertThat(dbi.getNameAsString(StandardCharsets.UTF_8)) - .isEqualTo("foo"); + assertThat(new String(env.getDbiNames().get(0), StandardCharsets.UTF_8)).isEqualTo("foo"); + assertThat(dbi.getNameAsString()).isEqualTo("foo"); + assertThat(dbi.getNameAsString(StandardCharsets.UTF_8)).isEqualTo("foo"); } @Test public void named2() { - final Dbi dbi = env.createDbi() - .setDbName("foo".getBytes(StandardCharsets.US_ASCII)) - .withDefaultComparator() - .setDbiFlags(DbiFlags.MDB_CREATE) - .open(); + final Dbi dbi = + env.createDbi() + .setDbName("foo".getBytes(StandardCharsets.US_ASCII)) + .withDefaultComparator() + .setDbiFlags(DbiFlags.MDB_CREATE) + .open(); assertPutAndGet(dbi); assertThat(env.getDbiNames()).hasSize(1); - assertThat(new String(env.getDbiNames().get(0), StandardCharsets.US_ASCII)) - .isEqualTo("foo"); - assertThat(dbi.getNameAsString()) - .isEqualTo("foo"); - assertThat(dbi.getNameAsString(StandardCharsets.US_ASCII)) - .isEqualTo("foo"); + assertThat(new String(env.getDbiNames().get(0), StandardCharsets.US_ASCII)).isEqualTo("foo"); + assertThat(dbi.getNameAsString()).isEqualTo("foo"); + assertThat(dbi.getNameAsString(StandardCharsets.US_ASCII)).isEqualTo("foo"); } @Test public void nativeComparator() { - final Dbi dbi = env.createDbi() - .setDbName("foo") - .withNativeComparator() - .addDbiFlags(DbiFlags.MDB_CREATE) - .open(); + final Dbi dbi = + env.createDbi() + .setDbName("foo") + .withNativeComparator() + .addDbiFlags(DbiFlags.MDB_CREATE) + .open(); assertPutAndGet(dbi); assertThat(env.getDbiNames()).hasSize(1); @@ -121,69 +120,72 @@ public void nativeComparator() { public void callback() { final Comparator proxyOptimal = ByteBufferProxy.PROXY_OPTIMAL.getComparator(); // Compare on key length, falling back to default - final Comparator comparator = (o1, o2) -> { - final int res = Integer.compare(o1.remaining(), o2.remaining()); - if (res == 0) { - return proxyOptimal.compare(o1, o2); - } else { - return res; - } - }; - - final Dbi dbi = env.createDbi() - .setDbName("foo") - .withCallbackComparator(ignored -> comparator) - .addDbiFlags(DbiFlags.MDB_CREATE) - .open(); - - TestUtils.doWithWriteTxn(env, txn -> { - dbi.put(txn, bb("fox"), bb("val_1")); - dbi.put(txn, bb("rabbit"), bb("val_2")); - dbi.put(txn, bb("deer"), bb("val_3")); - dbi.put(txn, bb("badger"), bb("val_4")); - txn.commit(); - }); + final Comparator comparator = + (o1, o2) -> { + final int res = Integer.compare(o1.remaining(), o2.remaining()); + if (res == 0) { + return proxyOptimal.compare(o1, o2); + } else { + return res; + } + }; + + final Dbi dbi = + env.createDbi() + .setDbName("foo") + .withCallbackComparator(ignored -> comparator) + .addDbiFlags(DbiFlags.MDB_CREATE) + .open(); + + TestUtils.doWithWriteTxn( + env, + txn -> { + dbi.put(txn, bb("fox"), bb("val_1")); + dbi.put(txn, bb("rabbit"), bb("val_2")); + dbi.put(txn, bb("deer"), bb("val_3")); + dbi.put(txn, bb("badger"), bb("val_4")); + txn.commit(); + }); final List keys = new ArrayList<>(); - TestUtils.doWithReadTxn(env, txn -> { - try (CursorIterable cursorIterable = dbi.iterate(txn)) { - final Iterator> iterator = cursorIterable.iterator(); - iterator.forEachRemaining(keyVal -> { - keys.add(getString(keyVal.key())); + TestUtils.doWithReadTxn( + env, + txn -> { + try (CursorIterable cursorIterable = dbi.iterate(txn)) { + final Iterator> iterator = cursorIterable.iterator(); + iterator.forEachRemaining( + keyVal -> { + keys.add(getString(keyVal.key())); + }); + } }); - } - }); - assertThat(keys).containsExactly( - "fox", - "deer", - "badger", - "rabbit"); + assertThat(keys).containsExactly("fox", "deer", "badger", "rabbit"); } @Test public void flags() { - final Dbi dbi = env.createDbi() - .setDbName("foo") - .withDefaultComparator() - .setDbiFlags(DbiFlags.MDB_DUPSORT, DbiFlags.MDB_DUPFIXED) // Will get overwritten - .setDbiFlags() // clear them - .addDbiFlags(DbiFlags.MDB_CREATE) // Not a dbi flag as far as lmdb is concerned. - .addDbiFlags(DbiFlags.MDB_INTEGERKEY) - .addDbiFlags(DbiFlags.MDB_REVERSEKEY) - .open(); + final Dbi dbi = + env.createDbi() + .setDbName("foo") + .withDefaultComparator() + .setDbiFlags(DbiFlags.MDB_DUPSORT, DbiFlags.MDB_DUPFIXED) // Will get overwritten + .setDbiFlags() // clear them + .addDbiFlags(DbiFlags.MDB_CREATE) // Not a dbi flag as far as lmdb is concerned. + .addDbiFlags(DbiFlags.MDB_INTEGERKEY) + .addDbiFlags(DbiFlags.MDB_REVERSEKEY) + .open(); assertPutAndGet(dbi); assertThat(env.getDbiNames()).hasSize(1); - assertThat(new String(env.getDbiNames().get(0), StandardCharsets.UTF_8)) - .isEqualTo("foo"); - - TestUtils.doWithReadTxn(env, readTxn -> { - assertThat(dbi.listFlags(readTxn)) - .containsExactlyInAnyOrder( - DbiFlags.MDB_INTEGERKEY, - DbiFlags.MDB_REVERSEKEY); - }); + assertThat(new String(env.getDbiNames().get(0), StandardCharsets.UTF_8)).isEqualTo("foo"); + + TestUtils.doWithReadTxn( + env, + readTxn -> { + assertThat(dbi.listFlags(readTxn)) + .containsExactlyInAnyOrder(DbiFlags.MDB_INTEGERKEY, DbiFlags.MDB_REVERSEKEY); + }); } private void assertPutAndGet(Dbi dbi) { diff --git a/src/test/java/org/lmdbjava/DbiFlagSetTest.java b/src/test/java/org/lmdbjava/DbiFlagSetTest.java index 06236497..1c44f0c0 100644 --- a/src/test/java/org/lmdbjava/DbiFlagSetTest.java +++ b/src/test/java/org/lmdbjava/DbiFlagSetTest.java @@ -22,39 +22,38 @@ public class DbiFlagSetTest extends AbstractFlagSetTest { - @Override - List getAllFlags() { - return Arrays.stream(DbiFlags.values()) - .collect(Collectors.toList()); - } - - @Override - DbiFlagSet getEmptyFlagSet() { - return DbiFlagSet.empty(); - } - - @Override - AbstractFlagSet.Builder getBuilder() { - return DbiFlagSet.builder(); - } - - @Override - Class getFlagType() { - return DbiFlags.class; - } - - @Override - DbiFlagSet getFlagSet(Collection flags) { - return DbiFlagSet.of(flags); - } - - @Override - DbiFlagSet getFlagSet(DbiFlags[] flags) { - return DbiFlagSet.of(flags); - } - - @Override - DbiFlagSet getFlagSet(DbiFlags flag) { - return DbiFlagSet.of(flag); - } + @Override + List getAllFlags() { + return Arrays.stream(DbiFlags.values()).collect(Collectors.toList()); + } + + @Override + DbiFlagSet getEmptyFlagSet() { + return DbiFlagSet.empty(); + } + + @Override + AbstractFlagSet.Builder getBuilder() { + return DbiFlagSet.builder(); + } + + @Override + Class getFlagType() { + return DbiFlags.class; + } + + @Override + DbiFlagSet getFlagSet(Collection flags) { + return DbiFlagSet.of(flags); + } + + @Override + DbiFlagSet getFlagSet(DbiFlags[] flags) { + return DbiFlagSet.of(flags); + } + + @Override + DbiFlagSet getFlagSet(DbiFlags flag) { + return DbiFlagSet.of(flag); + } } diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 8b8a461b..f9e7a033 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -68,9 +68,7 @@ import org.lmdbjava.Env.MapFullException; import org.lmdbjava.LmdbNativeException.ConstantDerivedException; -/** - * Test {@link Dbi}. - */ +/** Test {@link Dbi}. */ public final class DbiTest { private Path file; @@ -81,19 +79,21 @@ public final class DbiTest { @BeforeEach void beforeEach() { file = FileUtil.createTempFile(); - env = create() - .setMapSize(64, ByteUnit.MEBIBYTES) - .setMaxReaders(2) - .setMaxDbs(2) - .setEnvFlags(MDB_NOSUBDIR) - .open(file); + env = + create() + .setMapSize(64, ByteUnit.MEBIBYTES) + .setMaxReaders(2) + .setMaxDbs(2) + .setEnvFlags(MDB_NOSUBDIR) + .open(file); fileBa = FileUtil.createTempFile(); - envBa = create(PROXY_BA) - .setMapSize(64, ByteUnit.MEBIBYTES) - .setMaxReaders(2) - .setMaxDbs(2) - .setEnvFlags(MDB_NOSUBDIR) - .open(fileBa); + envBa = + create(PROXY_BA) + .setMapSize(64, ByteUnit.MEBIBYTES) + .setMaxReaders(2) + .setMaxDbs(2) + .setEnvFlags(MDB_NOSUBDIR) + .open(fileBa); } @AfterEach @@ -107,16 +107,17 @@ void afterEach() { @Test void close() { assertThatThrownBy( - () -> { - final Dbi db = env.createDbi() - .setDbName(DB_1) - .withDefaultComparator() - .addDbiFlag(MDB_CREATE) - .open(); - db.put(bb(1), bb(42)); - db.close(); - db.put(bb(2), bb(42)); // error - }) + () -> { + final Dbi db = + env.createDbi() + .setDbName(DB_1) + .withDefaultComparator() + .addDbiFlag(MDB_CREATE) + .open(); + db.put(bb(1), bb(42)); + db.close(); + db.put(bb(2), bb(42)); // error + }) .isInstanceOf(ConstantDerivedException.class); } @@ -151,11 +152,12 @@ private void doCustomComparator( Comparator comparator, IntFunction serializer, ToIntFunction deserializer) { - final Dbi db = env.createDbi() - .setDbName(DB_1) - .withCallbackComparator(ignored -> comparator) - .setDbiFlags(MDB_CREATE) - .open(); + final Dbi db = + env.createDbi() + .setDbName(DB_1) + .withCallbackComparator(ignored -> comparator) + .setDbiFlags(MDB_CREATE) + .open(); try (Txn txn = env.txnWrite()) { assertThat(db.put(txn, serializer.apply(2), serializer.apply(3))).isTrue(); assertThat(db.put(txn, serializer.apply(4), serializer.apply(6))).isTrue(); @@ -164,7 +166,7 @@ private void doCustomComparator( txn.commit(); } try (Txn txn = env.txnRead(); - CursorIterable ci = db.iterate(txn, atMost(serializer.apply(4)))) { + CursorIterable ci = db.iterate(txn, atMost(serializer.apply(4)))) { final Iterator> iter = ci.iterator(); assertThat(deserializer.applyAsInt(iter.next().key())).isEqualTo(8); assertThat(deserializer.applyAsInt(iter.next().key())).isEqualTo(6); @@ -175,11 +177,11 @@ private void doCustomComparator( @Test void dbOpenMaxDatabases() { assertThatThrownBy( - () -> { - env.openDbi("db1 is OK", MDB_CREATE); - env.openDbi("db2 is OK", MDB_CREATE); - env.openDbi("db3 fails", MDB_CREATE); - }) + () -> { + env.openDbi("db1 is OK", MDB_CREATE); + env.openDbi("db2 is OK", MDB_CREATE); + env.openDbi("db3 fails", MDB_CREATE); + }) .isInstanceOf(DbFullException.class); } @@ -202,15 +204,14 @@ private void doDbiWithComparatorThreadSafety( ToIntFunction deserializer) { final DbiFlagSet flags = DbiFlagSet.of(MDB_CREATE, MDB_INTEGERKEY); final Comparator comparator = comparatorSupplier.get(); - final Dbi db = env.createDbi() - .setDbName(DB_1) - .withCallbackComparator(ignored -> comparator) - .setDbiFlags(flags) - .open(); + final Dbi db = + env.createDbi() + .setDbName(DB_1) + .withCallbackComparator(ignored -> comparator) + .setDbiFlags(flags) + .open(); - final List keys = range(0, 1_000) - .boxed() - .collect(toList()); + final List keys = range(0, 1_000).boxed().collect(toList()); // TODO surround with try-with-resources in J19+ //noinspection resource // Not in J8 @@ -234,7 +235,7 @@ private void doDbiWithComparatorThreadSafety( } try (Txn txn = env.txnRead(); - CursorIterable ci = db.iterate(txn)) { + CursorIterable ci = db.iterate(txn)) { final Iterator> iter = ci.iterator(); final List result = new ArrayList<>(); while (iter.hasNext()) { @@ -314,8 +315,8 @@ void getName() { @Test void getNamesWhenDbisPresent() { - final byte[] dbHello = new byte[]{'h', 'e', 'l', 'l', 'o'}; - final byte[] dbWorld = new byte[]{'w', 'o', 'r', 'l', 'd'}; + final byte[] dbHello = new byte[] {'h', 'e', 'l', 'l', 'o'}; + final byte[] dbWorld = new byte[] {'w', 'o', 'r', 'l', 'd'}; env.openDbi(dbHello, MDB_CREATE); env.openDbi(dbWorld, MDB_CREATE); final List dbiNames = env.getDbiNames(); @@ -332,7 +333,8 @@ void getNamesWhenEmpty() { @Test void listsFlags() { - final Dbi dbi = env.openDbi(DB_1, DbiFlagSet.of(MDB_CREATE, MDB_DUPSORT, MDB_REVERSEKEY)); + final Dbi dbi = + env.openDbi(DB_1, DbiFlagSet.of(MDB_CREATE, MDB_DUPSORT, MDB_REVERSEKEY)); try (Txn txn = env.txnRead()) { final List flags = dbi.listFlags(txn); @@ -391,12 +393,13 @@ void putCommitGet() { void putCommitGetByteArray() { FileUtil.useTempFile( file -> { - try (Env envBa = create(PROXY_BA) - .setMapSize(64, ByteUnit.MEBIBYTES) - .setMaxReaders(1) - .setMaxDbs(2) - .setEnvFlags(MDB_NOSUBDIR) - .open(file)) { + try (Env envBa = + create(PROXY_BA) + .setMapSize(64, ByteUnit.MEBIBYTES) + .setMaxReaders(1) + .setMaxDbs(2) + .setEnvFlags(MDB_NOSUBDIR) + .open(file)) { final Dbi db = envBa.openDbi(DB_1, MDB_CREATE); try (Txn txn = envBa.txnWrite()) { db.put(txn, ba(5), ba(5)); @@ -528,19 +531,19 @@ void stats() { @Test void testMapFullException() { assertThatThrownBy( - () -> { - final Dbi db = env.openDbi(DB_1, MDB_CREATE); - try (Txn txn = env.txnWrite()) { - final ByteBuffer v; - try { - v = allocateDirect(1_024 * 1_024 * 1_024); - } catch (final OutOfMemoryError e) { - // Travis CI OS X build cannot allocate this much memory, so assume OK - throw new MapFullException(); - } - db.put(txn, bb(1), v); - } - }) + () -> { + final Dbi db = env.openDbi(DB_1, MDB_CREATE); + try (Txn txn = env.txnWrite()) { + final ByteBuffer v; + try { + v = allocateDirect(1_024 * 1_024 * 1_024); + } catch (final OutOfMemoryError e) { + // Travis CI OS X build cannot allocate this much memory, so assume OK + throw new MapFullException(); + } + db.put(txn, bb(1), v); + } + }) .isInstanceOf(MapFullException.class); } @@ -565,110 +568,110 @@ void testParallelWritesStress() { @Test void closedEnvRejectsOpenCall() { assertThatThrownBy( - () -> { - env.close(); - env.openDbi(DB_1, MDB_CREATE); - }) + () -> { + env.close(); + env.openDbi(DB_1, MDB_CREATE); + }) .isInstanceOf(AlreadyClosedException.class); } @Test void closedEnvRejectsCloseCall() { assertThatThrownBy( - () -> { - doEnvClosedTest(null, (db, txn) -> db.close()); - }) + () -> { + doEnvClosedTest(null, (db, txn) -> db.close()); + }) .isInstanceOf(AlreadyClosedException.class); } @Test void closedEnvRejectsGetCall() { assertThatThrownBy( - () -> { - doEnvClosedTest( - (db, txn) -> { - final ByteBuffer valBuf = db.get(txn, bb(1)); - assertThat(valBuf).isNotNull(); - assertThat(valBuf.getInt()).isEqualTo(10); - }, - (db, txn) -> db.get(txn, bb(2))); - }) + () -> { + doEnvClosedTest( + (db, txn) -> { + final ByteBuffer valBuf = db.get(txn, bb(1)); + assertThat(valBuf).isNotNull(); + assertThat(valBuf.getInt()).isEqualTo(10); + }, + (db, txn) -> db.get(txn, bb(2))); + }) .isInstanceOf(AlreadyClosedException.class); } @Test void closedEnvRejectsPutCall() { assertThatThrownBy( - () -> { - doEnvClosedTest(null, (db, txn) -> db.put(bb(5), bb(50))); - }) + () -> { + doEnvClosedTest(null, (db, txn) -> db.put(bb(5), bb(50))); + }) .isInstanceOf(AlreadyClosedException.class); } @Test void closedEnvRejectsPutWithTxnCall() { assertThatThrownBy( - () -> { - doEnvClosedTest( - null, - (db, txn) -> { - db.put(txn, bb(5), bb(50)); - }); - }) + () -> { + doEnvClosedTest( + null, + (db, txn) -> { + db.put(txn, bb(5), bb(50)); + }); + }) .isInstanceOf(AlreadyClosedException.class); } @Test void closedEnvRejectsIterateCall() { assertThatThrownBy( - () -> { - doEnvClosedTest(null, Dbi::iterate); - }) + () -> { + doEnvClosedTest(null, Dbi::iterate); + }) .isInstanceOf(AlreadyClosedException.class); } @Test void closedEnvRejectsDropCall() { assertThatThrownBy( - () -> { - doEnvClosedTest(null, Dbi::drop); - }) + () -> { + doEnvClosedTest(null, Dbi::drop); + }) .isInstanceOf(AlreadyClosedException.class); } @Test void closedEnvRejectsDropAndDeleteCall() { assertThatThrownBy( - () -> { - doEnvClosedTest(null, (db, txn) -> db.drop(txn, true)); - }) + () -> { + doEnvClosedTest(null, (db, txn) -> db.drop(txn, true)); + }) .isInstanceOf(AlreadyClosedException.class); } @Test void closedEnvRejectsOpenCursorCall() { assertThatThrownBy( - () -> { - doEnvClosedTest(null, Dbi::openCursor); - }) + () -> { + doEnvClosedTest(null, Dbi::openCursor); + }) .isInstanceOf(AlreadyClosedException.class); } @Test void closedEnvRejectsReserveCall() { assertThatThrownBy( - () -> { - doEnvClosedTest(null, (db, txn) -> db.reserve(txn, bb(1), 32, MDB_NOOVERWRITE)); - }) + () -> { + doEnvClosedTest(null, (db, txn) -> db.reserve(txn, bb(1), 32, MDB_NOOVERWRITE)); + }) .isInstanceOf(AlreadyClosedException.class); } @Test void closedEnvRejectsStatCall() { assertThatThrownBy( - () -> { - doEnvClosedTest(null, Dbi::stat); - }) + () -> { + doEnvClosedTest(null, Dbi::stat); + }) .isInstanceOf(AlreadyClosedException.class); } diff --git a/src/test/java/org/lmdbjava/EnvFlagSetTest.java b/src/test/java/org/lmdbjava/EnvFlagSetTest.java index 98a033f5..bd181ce8 100644 --- a/src/test/java/org/lmdbjava/EnvFlagSetTest.java +++ b/src/test/java/org/lmdbjava/EnvFlagSetTest.java @@ -22,39 +22,38 @@ public class EnvFlagSetTest extends AbstractFlagSetTest { - @Override - List getAllFlags() { - return Arrays.stream(EnvFlags.values()) - .collect(Collectors.toList()); - } - - @Override - EnvFlagSet getEmptyFlagSet() { - return EnvFlagSet.empty(); - } - - @Override - AbstractFlagSet.Builder getBuilder() { - return EnvFlagSet.builder(); - } - - @Override - EnvFlagSet getFlagSet(Collection flags) { - return EnvFlagSet.of(flags); - } - - @Override - EnvFlagSet getFlagSet(EnvFlags[] flags) { - return EnvFlagSet.of(flags); - } - - @Override - EnvFlagSet getFlagSet(EnvFlags flag) { - return EnvFlagSet.of(flag); - } - - @Override - Class getFlagType() { - return EnvFlags.class; - } + @Override + List getAllFlags() { + return Arrays.stream(EnvFlags.values()).collect(Collectors.toList()); + } + + @Override + EnvFlagSet getEmptyFlagSet() { + return EnvFlagSet.empty(); + } + + @Override + AbstractFlagSet.Builder getBuilder() { + return EnvFlagSet.builder(); + } + + @Override + EnvFlagSet getFlagSet(Collection flags) { + return EnvFlagSet.of(flags); + } + + @Override + EnvFlagSet getFlagSet(EnvFlags[] flags) { + return EnvFlagSet.of(flags); + } + + @Override + EnvFlagSet getFlagSet(EnvFlags flag) { + return EnvFlagSet.of(flag); + } + + @Override + Class getFlagType() { + return EnvFlags.class; + } } diff --git a/src/test/java/org/lmdbjava/EnvTest.java b/src/test/java/org/lmdbjava/EnvTest.java index e1e72401..ab7c02ca 100644 --- a/src/test/java/org/lmdbjava/EnvTest.java +++ b/src/test/java/org/lmdbjava/EnvTest.java @@ -42,20 +42,19 @@ import org.lmdbjava.Env.MapFullException; import org.lmdbjava.Txn.BadReaderLockException; -/** - * Test {@link Env}. - */ +/** Test {@link Env}. */ public final class EnvTest { @Test void byteUnit() { FileUtil.useTempFile( file -> { - try (Env env = Env.create() - .setMaxReaders(1) - .setMapSize(1, ByteUnit.MEBIBYTES) - .setEnvFlags(MDB_NOSUBDIR) - .open(file)) { + try (Env env = + Env.create() + .setMaxReaders(1) + .setMapSize(1, ByteUnit.MEBIBYTES) + .setEnvFlags(MDB_NOSUBDIR) + .open(file)) { final EnvInfo info = env.info(); assertThat(info.mapSize).isEqualTo(ByteUnit.MEBIBYTES.toBytes(1)); } @@ -65,142 +64,132 @@ void byteUnit() { @Test void cannotChangeMapSizeAfterOpen() { assertThatThrownBy( - () -> { - FileUtil.useTempFile( - file -> { - final Builder builder = Env.create() - .setMaxReaders(1) - .setEnvFlags(MDB_NOSUBDIR); - try (Env ignored = builder.open(file)) { - builder.setMapSize(1); - } - }); - }) + () -> { + FileUtil.useTempFile( + file -> { + final Builder builder = + Env.create().setMaxReaders(1).setEnvFlags(MDB_NOSUBDIR); + try (Env ignored = builder.open(file)) { + builder.setMapSize(1); + } + }); + }) .isInstanceOf(AlreadyOpenException.class); } @Test void cannotChangeMaxDbsAfterOpen() { assertThatThrownBy( - () -> { - FileUtil.useTempFile( - file -> { - final Builder builder = Env.create() - .setMaxReaders(1) - .setEnvFlags(MDB_NOSUBDIR); - try (Env ignored = builder.open(file)) { - builder.setMaxDbs(1); - } - }); - }) + () -> { + FileUtil.useTempFile( + file -> { + final Builder builder = + Env.create().setMaxReaders(1).setEnvFlags(MDB_NOSUBDIR); + try (Env ignored = builder.open(file)) { + builder.setMaxDbs(1); + } + }); + }) .isInstanceOf(AlreadyOpenException.class); } @Test void cannotChangeMaxReadersAfterOpen() { assertThatThrownBy( - () -> { - FileUtil.useTempFile( - file -> { - final Builder builder = Env.create() - .setMaxReaders(1) - .setEnvFlags(MDB_NOSUBDIR); - try (Env ignored = builder.open(file)) { - builder.setMaxReaders(1); - } - }); - }) + () -> { + FileUtil.useTempFile( + file -> { + final Builder builder = + Env.create().setMaxReaders(1).setEnvFlags(MDB_NOSUBDIR); + try (Env ignored = builder.open(file)) { + builder.setMaxReaders(1); + } + }); + }) .isInstanceOf(AlreadyOpenException.class); } @Test void cannotInfoOnceClosed() { assertThatThrownBy( - () -> { - FileUtil.useTempFile( - file -> { - final Env env = Env.create() - .setMaxReaders(1) - .setEnvFlags(MDB_NOSUBDIR) - .open(file); - env.close(); - env.info(); - }); - }) + () -> { + FileUtil.useTempFile( + file -> { + final Env env = + Env.create().setMaxReaders(1).setEnvFlags(MDB_NOSUBDIR).open(file); + env.close(); + env.info(); + }); + }) .isInstanceOf(AlreadyClosedException.class); } @Test void cannotOpenTwice() { assertThatThrownBy( - () -> { - FileUtil.useTempFile( - file -> { - final Builder builder = Env.create() - .setMaxReaders(1) - .setEnvFlags(MDB_NOSUBDIR); - builder.open(file).close(); - //noinspection resource // This will fail to open - builder.open(file); - }); - }) + () -> { + FileUtil.useTempFile( + file -> { + final Builder builder = + Env.create().setMaxReaders(1).setEnvFlags(MDB_NOSUBDIR); + builder.open(file).close(); + //noinspection resource // This will fail to open + builder.open(file); + }); + }) .isInstanceOf(AlreadyOpenException.class); } @Test void cannotOverflowMapSize() { assertThatThrownBy( - () -> { - final Builder builder = Env.create().setMaxReaders(1); - final int mb = 1_024 * 1_024; - //noinspection NumericOverflow // Intentional overflow - final int size = mb * 2_048; // as per issue 18 - builder.setMapSize(size); - }) + () -> { + final Builder builder = Env.create().setMaxReaders(1); + final int mb = 1_024 * 1_024; + //noinspection NumericOverflow // Intentional overflow + final int size = mb * 2_048; // as per issue 18 + builder.setMapSize(size); + }) .isInstanceOf(IllegalArgumentException.class); } @Test void negativeMapSize() { assertThatThrownBy( - () -> { - final Builder builder = Env.create().setMaxReaders(1); - builder.setMapSize(-1); - }) + () -> { + final Builder builder = Env.create().setMaxReaders(1); + builder.setMapSize(-1); + }) .isInstanceOf(IllegalArgumentException.class); } @Test void cannotStatOnceClosed() { assertThatThrownBy( - () -> { - FileUtil.useTempFile( - file -> { - final Env env = Env.create() - .setMaxReaders(1) - .setEnvFlags(MDB_NOSUBDIR) - .open(file); - env.close(); - env.stat(); - }); - }) + () -> { + FileUtil.useTempFile( + file -> { + final Env env = + Env.create().setMaxReaders(1).setEnvFlags(MDB_NOSUBDIR).open(file); + env.close(); + env.stat(); + }); + }) .isInstanceOf(AlreadyClosedException.class); } @Test void cannotSyncOnceClosed() { assertThatThrownBy( - () -> { - FileUtil.useTempFile( - file -> { - final Env env = Env.create() - .setMaxReaders(1) - .setEnvFlags(MDB_NOSUBDIR) - .open(file); - env.close(); - env.sync(false); - }); - }) + () -> { + FileUtil.useTempFile( + file -> { + final Env env = + Env.create().setMaxReaders(1).setEnvFlags(MDB_NOSUBDIR).open(file); + env.close(); + env.sync(false); + }); + }) .isInstanceOf(AlreadyClosedException.class); } @@ -224,66 +213,64 @@ void copyDirectoryBased() { @Test void copyDirectoryRejectsFileDestination() { assertThatThrownBy( - () -> { - FileUtil.useTempDir( - dest -> { - FileUtil.deleteDir(dest); - FileUtil.useTempDir( - src -> { - try (Env env = Env.create().setMaxReaders(1).open(src)) { - env.copy(dest.toFile(), MDB_CP_COMPACT); - } - }); - }); - }) + () -> { + FileUtil.useTempDir( + dest -> { + FileUtil.deleteDir(dest); + FileUtil.useTempDir( + src -> { + try (Env env = Env.create().setMaxReaders(1).open(src)) { + env.copy(dest.toFile(), MDB_CP_COMPACT); + } + }); + }); + }) .isInstanceOf(InvalidCopyDestination.class); } @Test void copyDirectoryRejectsMissingDestination() { assertThatThrownBy( - () -> { - FileUtil.useTempDir( - dest -> { - try { - Files.delete(dest); - FileUtil.useTempDir( - src -> { - try (Env env = - Env.create().setMaxReaders(1).open(src)) { - env.copy(dest.toFile(), MDB_CP_COMPACT); - } - }); - } catch (final IOException e) { - throw new UncheckedIOException(e); - } - }); - }) + () -> { + FileUtil.useTempDir( + dest -> { + try { + Files.delete(dest); + FileUtil.useTempDir( + src -> { + try (Env env = Env.create().setMaxReaders(1).open(src)) { + env.copy(dest.toFile(), MDB_CP_COMPACT); + } + }); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + }); + }) .isInstanceOf(InvalidCopyDestination.class); } @Test void copyDirectoryRejectsNonEmptyDestination() { assertThatThrownBy( - () -> { - FileUtil.useTempDir( - dest -> { - try { - final Path subDir = dest.resolve("hello"); - Files.createDirectory(subDir); - assertThat(Files.isDirectory(subDir)).isTrue(); - FileUtil.useTempDir( - src -> { - try (Env env = - Env.create().setMaxReaders(1).open(src)) { - env.copy(dest.toFile(), MDB_CP_COMPACT); - } - }); - } catch (final IOException e) { - throw new UncheckedIOException(e); - } - }); - }) + () -> { + FileUtil.useTempDir( + dest -> { + try { + final Path subDir = dest.resolve("hello"); + Files.createDirectory(subDir); + assertThat(Files.isDirectory(subDir)).isTrue(); + FileUtil.useTempDir( + src -> { + try (Env env = Env.create().setMaxReaders(1).open(src)) { + env.copy(dest.toFile(), MDB_CP_COMPACT); + } + }); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + }); + }) .isInstanceOf(InvalidCopyDestination.class); } @@ -296,7 +283,7 @@ void copyFileBased() { FileUtil.useTempFile( src -> { try (Env env = - Env.create().setMaxReaders(1).setEnvFlags(MDB_NOSUBDIR).open(src)) { + Env.create().setMaxReaders(1).setEnvFlags(MDB_NOSUBDIR).open(src)) { env.copy(dest.toFile(), MDB_CP_COMPACT); } assertThat(FileUtil.size(dest)).isGreaterThan(0L); @@ -307,19 +294,19 @@ void copyFileBased() { @Test void copyFileRejectsExistingDestination() { assertThatThrownBy( - () -> { - FileUtil.useTempFile( - dest -> { - assertThat(Files.exists(dest)).isTrue(); - FileUtil.useTempFile( - src -> { - try (Env env = - Env.create().setMaxReaders(1).setEnvFlags(MDB_NOSUBDIR).open(src)) { - env.copy(dest.toFile(), MDB_CP_COMPACT); - } - }); - }); - }) + () -> { + FileUtil.useTempFile( + dest -> { + assertThat(Files.exists(dest)).isTrue(); + FileUtil.useTempFile( + src -> { + try (Env env = + Env.create().setMaxReaders(1).setEnvFlags(MDB_NOSUBDIR).open(src)) { + env.copy(dest.toFile(), MDB_CP_COMPACT); + } + }); + }); + }) .isInstanceOf(InvalidCopyDestination.class); } @@ -340,12 +327,13 @@ void createAsDirectory() { void createAsFile() { FileUtil.useTempFile( file -> { - try (Env env = Env.create() - .setMapSize(1, ByteUnit.MEBIBYTES) - .setMaxDbs(1) - .setMaxReaders(1) - .setEnvFlags(MDB_NOSUBDIR) - .open(file)) { + try (Env env = + Env.create() + .setMapSize(1, ByteUnit.MEBIBYTES) + .setMaxDbs(1) + .setMaxReaders(1) + .setEnvFlags(MDB_NOSUBDIR) + .open(file)) { env.sync(true); assertThat(Files.isRegularFile(file)).isTrue(); } @@ -355,16 +343,16 @@ void createAsFile() { @Test void detectTransactionThreadViolation() { assertThatThrownBy( - () -> { - FileUtil.useTempFile( - file -> { - try (Env env = - Env.create().setMaxReaders(1).setEnvFlags(MDB_NOSUBDIR).open(file)) { - env.txnRead(); - env.txnRead(); - } - }); - }) + () -> { + FileUtil.useTempFile( + file -> { + try (Env env = + Env.create().setMaxReaders(1).setEnvFlags(MDB_NOSUBDIR).open(file)) { + env.txnRead(); + env.txnRead(); + } + }); + }) .isInstanceOf(BadReaderLockException.class); } @@ -372,11 +360,12 @@ void detectTransactionThreadViolation() { void info() { FileUtil.useTempFile( file -> { - try (Env env = Env.create() - .setMaxReaders(4) - .setMapSize(123_456) - .setEnvFlags(MDB_NOSUBDIR) - .open(file)) { + try (Env env = + Env.create() + .setMaxReaders(4) + .setMapSize(123_456) + .setEnvFlags(MDB_NOSUBDIR) + .open(file)) { final EnvInfo info = env.info(); assertThat(info).isNotNull(); assertThat(info.lastPageNumber).isEqualTo(1L); @@ -394,31 +383,31 @@ void info() { @Test void mapFull() { assertThatThrownBy( - () -> { - FileUtil.useTempDir( - dir -> { - final byte[] k = new byte[500]; - final ByteBuffer key = allocateDirect(500); - final ByteBuffer val = allocateDirect(1_024); - final Random rnd = new Random(); - try (Env env = - Env.create() - .setMaxReaders(1) - .setMapSize(8, ByteUnit.MEBIBYTES) - .setMaxDbs(1) - .open(dir)) { - final Dbi db = env.openDbi(DB_1, MDB_CREATE); - //noinspection InfiniteLoopStatement // Needs infinite loop to fill the env - for (; ; ) { - rnd.nextBytes(k); - key.clear(); - key.put(k).flip(); - val.clear(); - db.put(key, val); - } - } - }); - }) + () -> { + FileUtil.useTempDir( + dir -> { + final byte[] k = new byte[500]; + final ByteBuffer key = allocateDirect(500); + final ByteBuffer val = allocateDirect(1_024); + final Random rnd = new Random(); + try (Env env = + Env.create() + .setMaxReaders(1) + .setMapSize(8, ByteUnit.MEBIBYTES) + .setMaxDbs(1) + .open(dir)) { + final Dbi db = env.openDbi(DB_1, MDB_CREATE); + //noinspection InfiniteLoopStatement // Needs infinite loop to fill the env + for (; ; ) { + rnd.nextBytes(k); + key.clear(); + key.put(k).flip(); + val.clear(); + db.put(key, val); + } + } + }); + }) .isInstanceOf(MapFullException.class); } @@ -426,16 +415,12 @@ void mapFull() { void readOnlySupported() { FileUtil.useTempDir( dir -> { - try (Env rwEnv = Env.create() - .setMaxReaders(1) - .open(dir)) { + try (Env rwEnv = Env.create().setMaxReaders(1).open(dir)) { final Dbi rwDb = rwEnv.openDbi(DB_1, MDB_CREATE); rwDb.put(bb(1), bb(42)); } - try (Env roEnv = Env.create() - .setMaxReaders(1) - .setEnvFlags(MDB_RDONLY_ENV) - .open(dir)) { + try (Env roEnv = + Env.create().setMaxReaders(1).setEnvFlags(MDB_RDONLY_ENV).open(dir)) { final Dbi roDb = roEnv.openDbi(DB_1, DbiFlagSet.EMPTY); try (Txn roTxn = roEnv.txnRead()) { assertThat(roDb.get(roTxn, bb(1))).isNotNull(); @@ -452,11 +437,12 @@ void setMapSize() { final ByteBuffer key = allocateDirect(500); final ByteBuffer val = allocateDirect(1_024); final Random rnd = new Random(); - try (Env env = Env.create() - .setMaxReaders(1) - .setMapSize(256, ByteUnit.KIBIBYTES) - .setMaxDbs(1) - .open(dir)) { + try (Env env = + Env.create() + .setMaxReaders(1) + .setMapSize(256, ByteUnit.KIBIBYTES) + .setMaxDbs(1) + .open(dir)) { final Dbi db = env.openDbi(DB_1, MDB_CREATE); db.put(bb(1), bb(42)); @@ -474,9 +460,11 @@ void setMapSize() { } assertThat(mapFullExThrown).isTrue(); - assertThatThrownBy(() -> { - env.setMapSize(-1, ByteUnit.KIBIBYTES); - }).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy( + () -> { + env.setMapSize(-1, ByteUnit.KIBIBYTES); + }) + .isInstanceOf(IllegalArgumentException.class); env.setMapSize(1024, ByteUnit.KIBIBYTES); @@ -507,10 +495,8 @@ void setMapSize() { void stats() { FileUtil.useTempFile( file -> { - try (Env env = Env.create() - .setMaxReaders(1) - .setEnvFlags(MDB_NOSUBDIR) - .open(file)) { + try (Env env = + Env.create().setMaxReaders(1).setEnvFlags(MDB_NOSUBDIR).open(file)) { final Stat stat = env.stat(); assertThat(stat).isNotNull(); assertThat(stat.branchPages).isEqualTo(0L); @@ -550,10 +536,8 @@ void testDefaultOpenNoName1() { // As this is the unnamed database it returns all keys in the unnamed db final List dbiNames = env.getDbiNames(); - assertThat(dbiNames) - .hasSize(2); - assertThat(dbiNames.get(0)) - .isEqualTo("abc".getBytes(Env.DEFAULT_NAME_CHARSET)); + assertThat(dbiNames).hasSize(2); + assertThat(dbiNames.get(0)).isEqualTo("abc".getBytes(Env.DEFAULT_NAME_CHARSET)); } }); } @@ -569,10 +553,8 @@ void testDefaultOpenNoName2() { // As this is the unnamed database it returns all keys in the unnamed db final List dbiNames = env.getDbiNames(); - assertThat(dbiNames) - .hasSize(1); - assertThat(dbiNames.get(0)) - .isEqualTo(new byte[0]); + assertThat(dbiNames).hasSize(1); + assertThat(dbiNames.get(0)).isEqualTo(new byte[0]); } }); } diff --git a/src/test/java/org/lmdbjava/GarbageCollectionTest.java b/src/test/java/org/lmdbjava/GarbageCollectionTest.java index 21ef5182..71523c66 100644 --- a/src/test/java/org/lmdbjava/GarbageCollectionTest.java +++ b/src/test/java/org/lmdbjava/GarbageCollectionTest.java @@ -37,10 +37,7 @@ public class GarbageCollectionTest { void buffersNotGarbageCollectedTest() { FileUtil.useTempDir( dir -> { - try (Env env = create() - .setMapSize(2_085_760_999) - .setMaxDbs(1) - .open(dir)) { + try (Env env = create().setMapSize(2_085_760_999).setMaxDbs(1).open(dir)) { final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); try (Txn txn = env.txnWrite()) { diff --git a/src/test/java/org/lmdbjava/PutFlagSetTest.java b/src/test/java/org/lmdbjava/PutFlagSetTest.java index 9991b870..bc1a24e0 100644 --- a/src/test/java/org/lmdbjava/PutFlagSetTest.java +++ b/src/test/java/org/lmdbjava/PutFlagSetTest.java @@ -28,8 +28,7 @@ public class PutFlagSetTest extends AbstractFlagSetTest { @Override List getAllFlags() { - return Arrays.stream(PutFlags.values()) - .collect(Collectors.toList()); + return Arrays.stream(PutFlags.values()).collect(Collectors.toList()); } @Override @@ -67,10 +66,14 @@ public void testAddFlagVsCheckPresence() { final int cnt = 10_000_000; final int[] arr = new int[cnt]; - final List flagSets = IntStream.range(0, cnt) - .boxed() - .map(i -> PutFlagSet.of(PutFlags.MDB_APPEND, PutFlags.MDB_NOOVERWRITE, PutFlags.MDB_RESERVE)) - .collect(Collectors.toList()); + final List flagSets = + IntStream.range(0, cnt) + .boxed() + .map( + i -> + PutFlagSet.of( + PutFlags.MDB_APPEND, PutFlags.MDB_NOOVERWRITE, PutFlags.MDB_RESERVE)) + .collect(Collectors.toList()); Instant time; for (int i = 0; i < 5; i++) { diff --git a/src/test/java/org/lmdbjava/TestUtils.java b/src/test/java/org/lmdbjava/TestUtils.java index 68d988d1..2545fecf 100644 --- a/src/test/java/org/lmdbjava/TestUtils.java +++ b/src/test/java/org/lmdbjava/TestUtils.java @@ -69,29 +69,25 @@ static ByteBuffer bb(final String value) { } static ByteBuffer bbNative(final int value) { - final ByteBuffer bb = allocateDirect(Integer.BYTES) - .order(ByteOrder.nativeOrder()); + final ByteBuffer bb = allocateDirect(Integer.BYTES).order(ByteOrder.nativeOrder()); bb.putInt(value).flip(); return bb; } static ByteBuffer bbNative(final long value) { - final ByteBuffer bb = allocateDirect(Long.BYTES) - .order(ByteOrder.nativeOrder()); + final ByteBuffer bb = allocateDirect(Long.BYTES).order(ByteOrder.nativeOrder()); bb.putLong(value).flip(); return bb; } static int getNativeInt(final ByteBuffer bb) { - final int val = bb.order(ByteOrder.nativeOrder()) - .getInt(); + final int val = bb.order(ByteOrder.nativeOrder()).getInt(); bb.rewind(); return val; } static long getNativeLong(final ByteBuffer bb) { - final long val = bb.order(ByteOrder.nativeOrder()) - .getLong(); + final long val = bb.order(ByteOrder.nativeOrder()).getLong(); bb.rewind(); return val; } @@ -105,8 +101,7 @@ static long getNativeIntOrLong(final ByteBuffer bb) { } static String getString(final ByteBuffer bb) { - final String str = StandardCharsets.UTF_8.decode(bb) - .toString(); + final String str = StandardCharsets.UTF_8.decode(bb).toString(); bb.rewind(); return str; } diff --git a/src/test/java/org/lmdbjava/TutorialTest.java b/src/test/java/org/lmdbjava/TutorialTest.java index 93c4a031..5b875edf 100644 --- a/src/test/java/org/lmdbjava/TutorialTest.java +++ b/src/test/java/org/lmdbjava/TutorialTest.java @@ -55,9 +55,7 @@ public final class TutorialTest { private static final String DB_NAME = "my DB"; - /** - * In this first tutorial we will use LmdbJava with some basic defaults. - */ + /** In this first tutorial we will use LmdbJava with some basic defaults. */ @Test void tutorial1() { // We need a storage directory first. @@ -67,15 +65,16 @@ void tutorial1() { // We always need an Env. An Env owns a physical on-disk storage file. One // Env can store many different databases (ie sorted maps). - final Env env = Env.create() - // LMDB also needs to know how large our DB might be. Over-estimating is OK. - .setMapSize(10_485_760) - // LMDB also needs to know how many DBs (Dbi) we want to store in this Env. - .setMaxDbs(1) - // Now let's open the Env. The same path can be concurrently opened and - // used in different processes, but do not open the same path twice in - // the same process at the same time. - .open(dir); + final Env env = + Env.create() + // LMDB also needs to know how large our DB might be. Over-estimating is OK. + .setMapSize(10_485_760) + // LMDB also needs to know how many DBs (Dbi) we want to store in this Env. + .setMaxDbs(1) + // Now let's open the Env. The same path can be concurrently opened and + // used in different processes, but do not open the same path twice in + // the same process at the same time. + .open(dir); // We need a Dbi for each DB. A Dbi roughly equates to a sorted map. The // MDB_CREATE flag causes the DB to be created if it doesn't already exist. @@ -124,9 +123,7 @@ void tutorial1() { }); } - /** - * In this second tutorial we'll learn more about LMDB's ACID Txns. - */ + /** In this second tutorial we'll learn more about LMDB's ACID Txns. */ @Test void tutorial2() { FileUtil.useTempDir( @@ -337,9 +334,7 @@ void tutorial4() { }); } - /** - * In this fifth tutorial we'll explore multiple values sharing a single key. - */ + /** In this fifth tutorial we'll explore multiple values sharing a single key. */ @Test void tutorial5() { FileUtil.useTempDir( @@ -399,7 +394,8 @@ void tutorial6() { FileUtil.useTempDir( dir -> { // Note we need to specify the Verifier's DBI_COUNT for the Env. - final Env env = Env.create(PROXY_OPTIMAL) + final Env env = + Env.create(PROXY_OPTIMAL) .setMapSize(10_485_760) .setMaxDbs(Verifier.DBI_COUNT) .open(dir); @@ -415,9 +411,7 @@ void tutorial6() { }); } - /** - * In this final tutorial we'll look at using Agrona's DirectBuffer. - */ + /** In this final tutorial we'll look at using Agrona's DirectBuffer. */ @Test void tutorial7() { FileUtil.useTempDir( @@ -425,10 +419,8 @@ void tutorial7() { // The critical difference is we pass the PROXY_DB field to Env.create(). // There's also a PROXY_SAFE if you want to stop ByteBuffer's Unsafe use. // Aside from that and a different type argument, it's the same as usual... - final Env env = Env.create(PROXY_DB) - .setMapSize(10_485_760) - .setMaxDbs(1) - .open(dir); + final Env env = + Env.create(PROXY_DB).setMapSize(10_485_760).setMaxDbs(1).open(dir); final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); @@ -485,10 +477,6 @@ void tutorial7() { // or reverse ordered keys, using Env.DISABLE_CHECKS_PROP etc), but you now // know enough to tackle the JavaDocs with confidence. Have fun! private Env createSimpleEnv(final Path path) { - return Env.create() - .setMapSize(10_485_760) - .setMaxDbs(1) - .setMaxReaders(1) - .open(path); + return Env.create().setMapSize(10_485_760).setMaxDbs(1).setMaxReaders(1).open(path); } } diff --git a/src/test/java/org/lmdbjava/TxnFlagSetTest.java b/src/test/java/org/lmdbjava/TxnFlagSetTest.java index 87455cb7..4cf1e879 100644 --- a/src/test/java/org/lmdbjava/TxnFlagSetTest.java +++ b/src/test/java/org/lmdbjava/TxnFlagSetTest.java @@ -24,8 +24,7 @@ public class TxnFlagSetTest extends AbstractFlagSetTest { @Override List getAllFlags() { - return Arrays.stream(TxnFlags.values()) - .collect(Collectors.toList()); + return Arrays.stream(TxnFlags.values()).collect(Collectors.toList()); } @Override diff --git a/src/test/java/org/lmdbjava/TxnTest.java b/src/test/java/org/lmdbjava/TxnTest.java index ba8e55d3..157ba94d 100644 --- a/src/test/java/org/lmdbjava/TxnTest.java +++ b/src/test/java/org/lmdbjava/TxnTest.java @@ -125,10 +125,8 @@ void rangeSearch() { @Test void readOnlyTxnAllowedInReadOnlyEnv() { env.openDbi(DB_1, MDB_CREATE); - try (Env roEnv = create() - .setMaxReaders(1) - .setEnvFlags(MDB_NOSUBDIR, MDB_RDONLY_ENV) - .open(file)) { + try (Env roEnv = + create().setMaxReaders(1).setEnvFlags(MDB_NOSUBDIR, MDB_RDONLY_ENV).open(file)) { assertThat(roEnv.txnRead()).isNotNull(); } } diff --git a/src/test/java/org/lmdbjava/VerifierTest.java b/src/test/java/org/lmdbjava/VerifierTest.java index 75812e18..7f6b3dff 100644 --- a/src/test/java/org/lmdbjava/VerifierTest.java +++ b/src/test/java/org/lmdbjava/VerifierTest.java @@ -24,21 +24,20 @@ import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; -/** - * Test {@link Verifier}. - */ +/** Test {@link Verifier}. */ public final class VerifierTest { @Test void verification() { FileUtil.useTempFile( file -> { - try (Env env = create() - .setMaxReaders(1) - .setMaxDbs(Verifier.DBI_COUNT) - .setMapSize(10, ByteUnit.MEBIBYTES) - .setEnvFlags(MDB_NOSUBDIR) - .open(file)) { + try (Env env = + create() + .setMaxReaders(1) + .setMaxDbs(Verifier.DBI_COUNT) + .setMapSize(10, ByteUnit.MEBIBYTES) + .setEnvFlags(MDB_NOSUBDIR) + .open(file)) { final Verifier v = new Verifier(env); final int seconds = Integer.getInteger("verificationSeconds", 2); assertThat(v.runFor(seconds, TimeUnit.SECONDS)).isGreaterThan(1L); From cf3a11e0a3a201f3b49f3163ff8ca53fb61d40b8 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Fri, 7 Nov 2025 10:13:08 +0000 Subject: [PATCH 45/90] Remove class separators --- src/main/java/org/lmdbjava/AbstractFlagSet.java | 9 +++------ src/main/java/org/lmdbjava/ByteBufferProxy.java | 9 +++------ src/main/java/org/lmdbjava/CopyFlagSet.java | 6 ++---- src/main/java/org/lmdbjava/CursorIterable.java | 6 ++---- src/main/java/org/lmdbjava/DbiBuilder.java | 11 +++-------- src/main/java/org/lmdbjava/DbiFlagSet.java | 6 ++---- src/main/java/org/lmdbjava/Env.java | 3 +-- src/main/java/org/lmdbjava/EnvFlagSet.java | 6 ++---- src/main/java/org/lmdbjava/PutFlagSet.java | 6 ++---- src/main/java/org/lmdbjava/TxnFlagSet.java | 9 +++------ .../java/org/lmdbjava/ComparatorIntegerKeyTest.java | 9 +++------ .../org/lmdbjava/CursorIterableIntegerDupTest.java | 6 ++---- .../org/lmdbjava/CursorIterableIntegerKeyTest.java | 6 ++---- src/test/java/org/lmdbjava/CursorIterableTest.java | 6 ++---- 14 files changed, 32 insertions(+), 66 deletions(-) diff --git a/src/main/java/org/lmdbjava/AbstractFlagSet.java b/src/main/java/org/lmdbjava/AbstractFlagSet.java index fbaa4c6f..673dac08 100644 --- a/src/main/java/org/lmdbjava/AbstractFlagSet.java +++ b/src/main/java/org/lmdbjava/AbstractFlagSet.java @@ -100,8 +100,7 @@ public String toString() { return FlagSet.asString(this); } - // -------------------------------------------------------------------------------- - + abstract static class AbstractSingleFlagSet & MaskedFlag> implements FlagSet { @@ -182,8 +181,7 @@ private Set initSet() { } } - // -------------------------------------------------------------------------------- - + static class AbstractEmptyFlagSet implements FlagSet { @Override @@ -237,8 +235,7 @@ public int hashCode() { } } - // -------------------------------------------------------------------------------- - + /** * A builder for creating a {@link AbstractFlagSet}. * diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index 52bfc924..7272206a 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -86,8 +86,7 @@ public BufferMustBeDirectException() { } } - // -------------------------------------------------------------------------------- - + /** * Provides {@link ByteBuffer} pooling and address resolution for concrete {@link BufferProxy} * implementations. @@ -246,8 +245,7 @@ protected byte[] getBytes(final ByteBuffer buffer) { } } - // -------------------------------------------------------------------------------- - + /** * A proxy that uses Java reflection to modify byte buffer fields, and official JNR-FFF methods to * manipulate native pointers. @@ -292,8 +290,7 @@ protected ByteBuffer out(final ByteBuffer buffer, final Pointer ptr) { } } - // -------------------------------------------------------------------------------- - + /** * A proxy that uses Java's "unsafe" class to directly manipulate byte buffer fields and JNR-FFF * allocated memory pointers. diff --git a/src/main/java/org/lmdbjava/CopyFlagSet.java b/src/main/java/org/lmdbjava/CopyFlagSet.java index 1aed9878..663a367d 100644 --- a/src/main/java/org/lmdbjava/CopyFlagSet.java +++ b/src/main/java/org/lmdbjava/CopyFlagSet.java @@ -45,8 +45,7 @@ static AbstractFlagSet.Builder builder() { CopyFlags.class, CopyFlagSetImpl::new, copyFlag -> copyFlag, () -> CopyFlagSetImpl.EMPTY); } - // -------------------------------------------------------------------------------- - + class CopyFlagSetImpl extends AbstractFlagSet implements CopyFlagSet { static final CopyFlagSet EMPTY = new EmptyCopyFlagSet(); @@ -56,8 +55,7 @@ private CopyFlagSetImpl(final EnumSet flags) { } } - // -------------------------------------------------------------------------------- - + class EmptyCopyFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet implements CopyFlagSet {} } diff --git a/src/main/java/org/lmdbjava/CursorIterable.java b/src/main/java/org/lmdbjava/CursorIterable.java index 2289f130..f5ec4fc0 100644 --- a/src/main/java/org/lmdbjava/CursorIterable.java +++ b/src/main/java/org/lmdbjava/CursorIterable.java @@ -239,8 +239,7 @@ enum State { TERMINATED } - // -------------------------------------------------------------------------------- - + static class JavaRangeComparator implements RangeComparator { private final Comparator comparator; @@ -274,8 +273,7 @@ public void close() throws Exception { } } - // -------------------------------------------------------------------------------- - + /** * Calls down to mdb_cmp to make use of the comparator that LMDB uses for insertion order. Has a * very slight overhead as compared to {@link JavaRangeComparator}. diff --git a/src/main/java/org/lmdbjava/DbiBuilder.java b/src/main/java/org/lmdbjava/DbiBuilder.java index d5d1af8c..c8dacd08 100644 --- a/src/main/java/org/lmdbjava/DbiBuilder.java +++ b/src/main/java/org/lmdbjava/DbiBuilder.java @@ -82,8 +82,6 @@ public DbiBuilderStage2 withoutDbName() { return setDbName((byte[]) null); } - // -------------------------------------------------------------------------------- - /** * Intermediate builder stage for constructing a {@link Dbi}. * @@ -209,8 +207,7 @@ public DbiBuilderStage3 withIteratorComparator( } } - // -------------------------------------------------------------------------------- - + /** * Final stage builder for constructing a {@link Dbi}. * @@ -394,8 +391,7 @@ private Dbi openDbi(final Txn txn, final DbiBuilder dbiBuilder) { } } - // -------------------------------------------------------------------------------- - + private enum ComparatorType { /** * Default Java comparator for {@link CursorIterable} KeyRange testing, LMDB comparator for @@ -414,8 +410,7 @@ private enum ComparatorType { ; } - // -------------------------------------------------------------------------------- - + @FunctionalInterface public interface ComparatorFactory { diff --git a/src/main/java/org/lmdbjava/DbiFlagSet.java b/src/main/java/org/lmdbjava/DbiFlagSet.java index 6f2d3c81..0452f068 100644 --- a/src/main/java/org/lmdbjava/DbiFlagSet.java +++ b/src/main/java/org/lmdbjava/DbiFlagSet.java @@ -49,8 +49,7 @@ static AbstractFlagSet.Builder builder() { DbiFlags.class, DbiFlagSetImpl::new, dbiFlag -> dbiFlag, () -> DbiFlagSetImpl.EMPTY); } - // -------------------------------------------------------------------------------- - + class DbiFlagSetImpl extends AbstractFlagSet implements DbiFlagSet { static final DbiFlagSet EMPTY = new EmptyDbiFlagSet(); @@ -60,8 +59,7 @@ private DbiFlagSetImpl(final EnumSet flags) { } } - // -------------------------------------------------------------------------------- - + class EmptyDbiFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet implements DbiFlagSet {} } diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index 54115b33..370fbc8c 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -633,8 +633,7 @@ public AlreadyOpenException() { } } - // -------------------------------------------------------------------------------- - + /** * Builder for configuring and opening Env. * diff --git a/src/main/java/org/lmdbjava/EnvFlagSet.java b/src/main/java/org/lmdbjava/EnvFlagSet.java index 771f5e2c..e0c4d21c 100644 --- a/src/main/java/org/lmdbjava/EnvFlagSet.java +++ b/src/main/java/org/lmdbjava/EnvFlagSet.java @@ -45,8 +45,7 @@ static AbstractFlagSet.Builder builder() { EnvFlags.class, EnvFlagSetImpl::new, envFlag -> envFlag, () -> EnvFlagSetImpl.EMPTY); } - // -------------------------------------------------------------------------------- - + class EnvFlagSetImpl extends AbstractFlagSet implements EnvFlagSet { static final EnvFlagSet EMPTY = new EmptyEnvFlagSet(); @@ -56,8 +55,7 @@ private EnvFlagSetImpl(final EnumSet flags) { } } - // -------------------------------------------------------------------------------- - + class EmptyEnvFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet implements EnvFlagSet {} } diff --git a/src/main/java/org/lmdbjava/PutFlagSet.java b/src/main/java/org/lmdbjava/PutFlagSet.java index ee2a9826..ee2855a6 100644 --- a/src/main/java/org/lmdbjava/PutFlagSet.java +++ b/src/main/java/org/lmdbjava/PutFlagSet.java @@ -45,8 +45,7 @@ static AbstractFlagSet.Builder builder() { PutFlags.class, PutFlagSetImpl::new, putFlag -> putFlag, EmptyPutFlagSet::new); } - // -------------------------------------------------------------------------------- - + class PutFlagSetImpl extends AbstractFlagSet implements PutFlagSet { public static final PutFlagSet EMPTY = new EmptyPutFlagSet(); @@ -56,8 +55,7 @@ private PutFlagSetImpl(final EnumSet flags) { } } - // -------------------------------------------------------------------------------- - + class EmptyPutFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet implements PutFlagSet {} } diff --git a/src/main/java/org/lmdbjava/TxnFlagSet.java b/src/main/java/org/lmdbjava/TxnFlagSet.java index 1fa34d32..c15ebec1 100644 --- a/src/main/java/org/lmdbjava/TxnFlagSet.java +++ b/src/main/java/org/lmdbjava/TxnFlagSet.java @@ -45,8 +45,7 @@ static AbstractFlagSet.Builder builder() { TxnFlags.class, TxnFlagSetImpl::new, SingleTxnFlagSet::new, () -> TxnFlagSetImpl.EMPTY); } - // -------------------------------------------------------------------------------- - + class TxnFlagSetImpl extends AbstractFlagSet implements TxnFlagSet { static final TxnFlagSet EMPTY = new EmptyTxnFlagSet(); @@ -56,8 +55,7 @@ private TxnFlagSetImpl(final EnumSet flags) { } } - // -------------------------------------------------------------------------------- - + class SingleTxnFlagSet extends AbstractFlagSet.AbstractSingleFlagSet implements TxnFlagSet { @@ -66,8 +64,7 @@ class SingleTxnFlagSet extends AbstractFlagSet.AbstractSingleFlagSet } } - // -------------------------------------------------------------------------------- - + class EmptyTxnFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet implements TxnFlagSet {} } diff --git a/src/test/java/org/lmdbjava/ComparatorIntegerKeyTest.java b/src/test/java/org/lmdbjava/ComparatorIntegerKeyTest.java index 00c0ce28..e35d7d27 100644 --- a/src/test/java/org/lmdbjava/ComparatorIntegerKeyTest.java +++ b/src/test/java/org/lmdbjava/ComparatorIntegerKeyTest.java @@ -185,8 +185,7 @@ void testRandomInt(final ComparatorRunner runner) { } } - // -------------------------------------------------------------------------------- - + /** Tests {@link ByteBufferProxy}. */ private static final class ByteBufferRunner implements ComparatorRunner { @@ -260,8 +259,7 @@ private ByteBuffer intToBuffer(final int val, final int bufferCapacity) { } } - // -------------------------------------------------------------------------------- - + /** Tests {@link DirectBufferProxy}. */ private static final class DirectBufferRunner implements ComparatorRunner { private static final Comparator COMPARATOR = @@ -331,8 +329,7 @@ public int compare(int int1, int int2) { } } - // -------------------------------------------------------------------------------- - + /** Interface that can test a {@link BufferProxy} compare method. */ private interface ComparatorRunner { diff --git a/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java b/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java index 7a2515b2..da0b86f2 100644 --- a/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java @@ -511,8 +511,7 @@ private Dbi getDb() { return dbi; } - // -------------------------------------------------------------------------------- - + private static class DbiFactory { private final String name; private final Function, Dbi> factory; @@ -528,8 +527,7 @@ public String toString() { } } - // -------------------------------------------------------------------------------- - + static class MyArgumentProvider implements ArgumentsProvider { @Override diff --git a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java index c35767c0..509b5bbb 100644 --- a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java @@ -579,8 +579,7 @@ private Dbi getDb() { return dbi; } - // -------------------------------------------------------------------------------- - + private static class DbiFactory { private final String name; private final Function, Dbi> factory; @@ -596,8 +595,7 @@ public String toString() { } } - // -------------------------------------------------------------------------------- - + static class MyArgumentProvider implements ArgumentsProvider { @Override diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index 7c1aa227..75358b56 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -500,8 +500,7 @@ private Dbi getDb() { return dbi; } - // -------------------------------------------------------------------------------- - + private static class DbiFactory { private final String name; private final Function, Dbi> factory; @@ -517,8 +516,7 @@ public String toString() { } } - // -------------------------------------------------------------------------------- - + static class MyArgumentProvider implements ArgumentsProvider { @Override From 0c97cf469952ee263b3b1d7b85fcd6f3320ccabc Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Fri, 7 Nov 2025 10:14:02 +0000 Subject: [PATCH 46/90] Run format plugin --- src/main/java/org/lmdbjava/AbstractFlagSet.java | 3 --- src/main/java/org/lmdbjava/ByteBufferProxy.java | 3 --- src/main/java/org/lmdbjava/CopyFlagSet.java | 2 -- src/main/java/org/lmdbjava/CursorIterable.java | 2 -- src/main/java/org/lmdbjava/DbiBuilder.java | 3 --- src/main/java/org/lmdbjava/DbiFlagSet.java | 2 -- src/main/java/org/lmdbjava/Env.java | 1 - src/main/java/org/lmdbjava/EnvFlagSet.java | 2 -- src/main/java/org/lmdbjava/PutFlagSet.java | 2 -- src/main/java/org/lmdbjava/TxnFlagSet.java | 3 --- src/test/java/org/lmdbjava/ComparatorIntegerKeyTest.java | 3 --- src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java | 2 -- src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java | 2 -- src/test/java/org/lmdbjava/CursorIterableTest.java | 2 -- 14 files changed, 32 deletions(-) diff --git a/src/main/java/org/lmdbjava/AbstractFlagSet.java b/src/main/java/org/lmdbjava/AbstractFlagSet.java index 673dac08..655369e1 100644 --- a/src/main/java/org/lmdbjava/AbstractFlagSet.java +++ b/src/main/java/org/lmdbjava/AbstractFlagSet.java @@ -100,7 +100,6 @@ public String toString() { return FlagSet.asString(this); } - abstract static class AbstractSingleFlagSet & MaskedFlag> implements FlagSet { @@ -181,7 +180,6 @@ private Set initSet() { } } - static class AbstractEmptyFlagSet implements FlagSet { @Override @@ -235,7 +233,6 @@ public int hashCode() { } } - /** * A builder for creating a {@link AbstractFlagSet}. * diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index 7272206a..25218fe4 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -86,7 +86,6 @@ public BufferMustBeDirectException() { } } - /** * Provides {@link ByteBuffer} pooling and address resolution for concrete {@link BufferProxy} * implementations. @@ -245,7 +244,6 @@ protected byte[] getBytes(final ByteBuffer buffer) { } } - /** * A proxy that uses Java reflection to modify byte buffer fields, and official JNR-FFF methods to * manipulate native pointers. @@ -290,7 +288,6 @@ protected ByteBuffer out(final ByteBuffer buffer, final Pointer ptr) { } } - /** * A proxy that uses Java's "unsafe" class to directly manipulate byte buffer fields and JNR-FFF * allocated memory pointers. diff --git a/src/main/java/org/lmdbjava/CopyFlagSet.java b/src/main/java/org/lmdbjava/CopyFlagSet.java index 663a367d..cea2fce9 100644 --- a/src/main/java/org/lmdbjava/CopyFlagSet.java +++ b/src/main/java/org/lmdbjava/CopyFlagSet.java @@ -45,7 +45,6 @@ static AbstractFlagSet.Builder builder() { CopyFlags.class, CopyFlagSetImpl::new, copyFlag -> copyFlag, () -> CopyFlagSetImpl.EMPTY); } - class CopyFlagSetImpl extends AbstractFlagSet implements CopyFlagSet { static final CopyFlagSet EMPTY = new EmptyCopyFlagSet(); @@ -55,7 +54,6 @@ private CopyFlagSetImpl(final EnumSet flags) { } } - class EmptyCopyFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet implements CopyFlagSet {} } diff --git a/src/main/java/org/lmdbjava/CursorIterable.java b/src/main/java/org/lmdbjava/CursorIterable.java index f5ec4fc0..fe379151 100644 --- a/src/main/java/org/lmdbjava/CursorIterable.java +++ b/src/main/java/org/lmdbjava/CursorIterable.java @@ -239,7 +239,6 @@ enum State { TERMINATED } - static class JavaRangeComparator implements RangeComparator { private final Comparator comparator; @@ -273,7 +272,6 @@ public void close() throws Exception { } } - /** * Calls down to mdb_cmp to make use of the comparator that LMDB uses for insertion order. Has a * very slight overhead as compared to {@link JavaRangeComparator}. diff --git a/src/main/java/org/lmdbjava/DbiBuilder.java b/src/main/java/org/lmdbjava/DbiBuilder.java index c8dacd08..450fb3c7 100644 --- a/src/main/java/org/lmdbjava/DbiBuilder.java +++ b/src/main/java/org/lmdbjava/DbiBuilder.java @@ -207,7 +207,6 @@ public DbiBuilderStage3 withIteratorComparator( } } - /** * Final stage builder for constructing a {@link Dbi}. * @@ -391,7 +390,6 @@ private Dbi openDbi(final Txn txn, final DbiBuilder dbiBuilder) { } } - private enum ComparatorType { /** * Default Java comparator for {@link CursorIterable} KeyRange testing, LMDB comparator for @@ -410,7 +408,6 @@ private enum ComparatorType { ; } - @FunctionalInterface public interface ComparatorFactory { diff --git a/src/main/java/org/lmdbjava/DbiFlagSet.java b/src/main/java/org/lmdbjava/DbiFlagSet.java index 0452f068..d0b13793 100644 --- a/src/main/java/org/lmdbjava/DbiFlagSet.java +++ b/src/main/java/org/lmdbjava/DbiFlagSet.java @@ -49,7 +49,6 @@ static AbstractFlagSet.Builder builder() { DbiFlags.class, DbiFlagSetImpl::new, dbiFlag -> dbiFlag, () -> DbiFlagSetImpl.EMPTY); } - class DbiFlagSetImpl extends AbstractFlagSet implements DbiFlagSet { static final DbiFlagSet EMPTY = new EmptyDbiFlagSet(); @@ -59,7 +58,6 @@ private DbiFlagSetImpl(final EnumSet flags) { } } - class EmptyDbiFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet implements DbiFlagSet {} } diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index 370fbc8c..8fa9e901 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -633,7 +633,6 @@ public AlreadyOpenException() { } } - /** * Builder for configuring and opening Env. * diff --git a/src/main/java/org/lmdbjava/EnvFlagSet.java b/src/main/java/org/lmdbjava/EnvFlagSet.java index e0c4d21c..4ce5eadc 100644 --- a/src/main/java/org/lmdbjava/EnvFlagSet.java +++ b/src/main/java/org/lmdbjava/EnvFlagSet.java @@ -45,7 +45,6 @@ static AbstractFlagSet.Builder builder() { EnvFlags.class, EnvFlagSetImpl::new, envFlag -> envFlag, () -> EnvFlagSetImpl.EMPTY); } - class EnvFlagSetImpl extends AbstractFlagSet implements EnvFlagSet { static final EnvFlagSet EMPTY = new EmptyEnvFlagSet(); @@ -55,7 +54,6 @@ private EnvFlagSetImpl(final EnumSet flags) { } } - class EmptyEnvFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet implements EnvFlagSet {} } diff --git a/src/main/java/org/lmdbjava/PutFlagSet.java b/src/main/java/org/lmdbjava/PutFlagSet.java index ee2855a6..8c587138 100644 --- a/src/main/java/org/lmdbjava/PutFlagSet.java +++ b/src/main/java/org/lmdbjava/PutFlagSet.java @@ -45,7 +45,6 @@ static AbstractFlagSet.Builder builder() { PutFlags.class, PutFlagSetImpl::new, putFlag -> putFlag, EmptyPutFlagSet::new); } - class PutFlagSetImpl extends AbstractFlagSet implements PutFlagSet { public static final PutFlagSet EMPTY = new EmptyPutFlagSet(); @@ -55,7 +54,6 @@ private PutFlagSetImpl(final EnumSet flags) { } } - class EmptyPutFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet implements PutFlagSet {} } diff --git a/src/main/java/org/lmdbjava/TxnFlagSet.java b/src/main/java/org/lmdbjava/TxnFlagSet.java index c15ebec1..4bfdf2e3 100644 --- a/src/main/java/org/lmdbjava/TxnFlagSet.java +++ b/src/main/java/org/lmdbjava/TxnFlagSet.java @@ -45,7 +45,6 @@ static AbstractFlagSet.Builder builder() { TxnFlags.class, TxnFlagSetImpl::new, SingleTxnFlagSet::new, () -> TxnFlagSetImpl.EMPTY); } - class TxnFlagSetImpl extends AbstractFlagSet implements TxnFlagSet { static final TxnFlagSet EMPTY = new EmptyTxnFlagSet(); @@ -55,7 +54,6 @@ private TxnFlagSetImpl(final EnumSet flags) { } } - class SingleTxnFlagSet extends AbstractFlagSet.AbstractSingleFlagSet implements TxnFlagSet { @@ -64,7 +62,6 @@ class SingleTxnFlagSet extends AbstractFlagSet.AbstractSingleFlagSet } } - class EmptyTxnFlagSet extends AbstractFlagSet.AbstractEmptyFlagSet implements TxnFlagSet {} } diff --git a/src/test/java/org/lmdbjava/ComparatorIntegerKeyTest.java b/src/test/java/org/lmdbjava/ComparatorIntegerKeyTest.java index e35d7d27..dbe00fa0 100644 --- a/src/test/java/org/lmdbjava/ComparatorIntegerKeyTest.java +++ b/src/test/java/org/lmdbjava/ComparatorIntegerKeyTest.java @@ -185,7 +185,6 @@ void testRandomInt(final ComparatorRunner runner) { } } - /** Tests {@link ByteBufferProxy}. */ private static final class ByteBufferRunner implements ComparatorRunner { @@ -259,7 +258,6 @@ private ByteBuffer intToBuffer(final int val, final int bufferCapacity) { } } - /** Tests {@link DirectBufferProxy}. */ private static final class DirectBufferRunner implements ComparatorRunner { private static final Comparator COMPARATOR = @@ -329,7 +327,6 @@ public int compare(int int1, int int2) { } } - /** Interface that can test a {@link BufferProxy} compare method. */ private interface ComparatorRunner { diff --git a/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java b/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java index da0b86f2..703865d0 100644 --- a/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableIntegerDupTest.java @@ -511,7 +511,6 @@ private Dbi getDb() { return dbi; } - private static class DbiFactory { private final String name; private final Function, Dbi> factory; @@ -527,7 +526,6 @@ public String toString() { } } - static class MyArgumentProvider implements ArgumentsProvider { @Override diff --git a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java index 509b5bbb..6baa1907 100644 --- a/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableIntegerKeyTest.java @@ -579,7 +579,6 @@ private Dbi getDb() { return dbi; } - private static class DbiFactory { private final String name; private final Function, Dbi> factory; @@ -595,7 +594,6 @@ public String toString() { } } - static class MyArgumentProvider implements ArgumentsProvider { @Override diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index 75358b56..c853a8ab 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -500,7 +500,6 @@ private Dbi getDb() { return dbi; } - private static class DbiFactory { private final String name; private final Function, Dbi> factory; @@ -516,7 +515,6 @@ public String toString() { } } - static class MyArgumentProvider implements ArgumentsProvider { @Override From 72d4e3f40f9e734986a7f6e97caca341f7fefdae Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Fri, 7 Nov 2025 11:24:42 +0000 Subject: [PATCH 47/90] Remove binaries accidentally added, fix test --- .../org/lmdbjava/aarch64-linux-gnu.so | Bin 302728 -> 0 bytes .../org/lmdbjava/aarch64-macos-none.so | Bin 135224 -> 0 bytes .../resources/org/lmdbjava/x86_64-linux-gnu.so | Bin 308688 -> 0 bytes .../org/lmdbjava/x86_64-macos-none.so | Bin 100150 -> 0 bytes .../org/lmdbjava/x86_64-windows-gnu.dll | Bin 260096 -> 0 bytes src/test/java/org/lmdbjava/EnvTest.java | 8 ++++++-- 6 files changed, 6 insertions(+), 2 deletions(-) delete mode 100755 src/main/resources/org/lmdbjava/aarch64-linux-gnu.so delete mode 100755 src/main/resources/org/lmdbjava/aarch64-macos-none.so delete mode 100755 src/main/resources/org/lmdbjava/x86_64-linux-gnu.so delete mode 100755 src/main/resources/org/lmdbjava/x86_64-macos-none.so delete mode 100755 src/main/resources/org/lmdbjava/x86_64-windows-gnu.dll diff --git a/src/main/resources/org/lmdbjava/aarch64-linux-gnu.so b/src/main/resources/org/lmdbjava/aarch64-linux-gnu.so deleted file mode 100755 index 7e911756099eb50521ad1767c0974d8313f614f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 302728 zcmd443wTu3x%j>IOagl*7eWXmTr|T)of%LOLIO0%1cD}9@~-#3-nDkzGH>Bc4oy?`W)&Oa#?uwLjtNU*B_o33>Jy;6-) zgMiALfc5s`veW9!E_JJ(fY0}60pkk2$b7I%Wnm@l61i-+UStknmj~ZA+u3D&JHfSc ztXaQvtkbAymqivbfmE}--OjwokB@NomcZyW$VZvgn> z0pKMAz#{{|e>wpC-~jL!27v!z0C?{J@Ijb?%h7Gz0Ptx8z!whyFBt%S_Wbp9g?Hgdw>c9rg|Y|H%OGe;)w;hXLTn27o_@k-r>1OEGengI5m#-#!3* z#{lr}4FKOe0Q_GEfFBwFes}=*p9g@yHvs(90Prgf`;e?Q4M*VlS#DHqG3c4B4kVax zkMY*@a_K5rE%|_k{3coO6iR{He%0>hI;;QwTh02(R{fb4JfY-5_`e(SmaK9{nBz8C z^-oy%G`?ciSLSQ=lGV>k7XE9k`fGSE`0V_I#9hgkibcA6dJTky{<`17xs^$RU{ zvenNfYrc&Z{5w|tH!b=swBTzkd=6On@3Y`{Tm1wrd?r}%J=S~|{=*#C?&qH^cyPUi zk6EbAhWsb1Jyw02o(>D2Q`WrfxMDox82qy0o-x+A5-te5)`HvoI%B~nTqF4Tk~qP z=xopT-!1qqab)}s8viPFr`1o}U=u#sf_GEOe4k!qcKm?(TBTWd)>`vTv+7T^`oG&6 z*QVRQS@mcC!2{N~i!J!{7fra$-&GcTp#`_) z=RYhw-+bAuzuf9)w8i(r-<$A43qFrW!86U0+bj$I2MeBXz^tER$)VrE|Fos=2dw&E zx9};n@Uitv=q>#xTYPD@>aVu&+-=d>W5N3@c;iKOCdtBIU4;Lu7Js`kOhVpm^>f<7 zC&%KiE&qP2pCU_-Y&^yM3Z7nz4gss5Yps5gE%`iZ!Pi^xNxwDe^SD{4$1MCkmfqR+ z_M2A!uYA?4m}9MrlC6GrSqr*;tAB+7cPQt%|IXhF++W3+ zk5Ug(&;AWk0nZYUayaJFTOdtKa7bs#<5XIDpvkD^gU6b3rm4Uj1D62Md0aPf&F2yu zwv6j8uCH;GaaD3vbJcOl8g2vEX0EMV{<_)euIS&spa1jQKA)M>Kd%3er4K#+Q(yD& z8~)IDTxD z;Ag+Q{YTl$A3If6ci*>v_RE<^okQ>W;py`N_{Z136$(x6&KkGi?T^}CD1GR``$lZId*f%hQ_E{^dA;tD z%6nRu9e=v{mhV&_9>3~Ozs-E*y@rQE)!oe-KiGb6U4v`f#-AKmGGX6?Kl|k`OQzlO z#{-4^>CKN+dRH%*FlYSR-+pn`k_qp;Q&m^}_Mg(5Z~XE1j(qx#O8-1IG`Z;;zwUjl zW`#cW>lKgZWNmo;g`fR$!3`C43!hpt;eS#cjS6VtdF zxkNVX-<{S?i1KExCD!v4N{QjFvYw?7UR=p_KUX%F@W=k$#*L3_CD(GU0xtRGn}xw! z;rn8)ITp-Axy`zdFYd7(lC08gl_EPLA0j)eESP4Mc2Q&9Or;#gW&1Pxz4(pQ)iXN{oxU47M>)y92&LtSNE-I_Xe z?nY{?T(#DCTVdZ+uD-9dKD2tJx~HPNq1?DzW&B%OO7qogN^94wEN_rb?=fG}Nkil6 z(z?pkmG`T)4Z*s~@`}<`>l-Q?rE1MN(7lxn^=m6vR^3yzQmv|7wQ_Cf+^hP^+I!SJ zD_1wvo&%X3tY3XW7xnR7SmUm!ytit#T3KF8O|{A@c(Sg}xu^0KYwFHFtz5HeRh9W@j=6SiE-nNnL?Us+zaGHCQ@G@?fQ>#EiDmFEGqrK>9|O=PUjDk^J*hBi`07mceb z>+Y>oYwxeCYN%AT^_7)1%D6XYBz#>7Z+HhM)-t*JQ2oj^tM4(8Ft}dXP`avoZGF`S zsI`7Ix9XmTy7jAJHvoBDRbFvlRlP~e^DP=HR@7hANuvk>vUd*5ko0{Ccnzcyd8}B! zR*G8lDPA;cDnmBf7syt5LqlC@)oKWTDHyV>tq+#hL9@%gtXs2UJ9cRmP(slT61JbYqfh)`sq>m~xI3$XkU)={5CLjitzNd2Q9btIt<3 zCDbOJQQxMV4Xj>+!kbbqSe+loK*N5Va>3KN2V|r4p4#$zFBHuSAyQ{O3b?YN@mxC* z;EQ2Wue|=8rpm}tZEbmjso6H*9#ePaQ3U8*pH-{xSz|mv!ctomR@;X7Tvd5{o(b}F z9^Hz%HKzR*T(CrIk$R&`c}ATQOz9$YY@reCv zi-X(ooV;s~gU82)&&I*+_*d#5jDx!cVEsDd;CB2kue##k@iF1+ad3%|?BCHixU6IB zUw0flKDO?d2bX+<{qx4bCI4XmCda`GZ6M|JIQWJ*cpwgLnR4T8Q5@V}+sTV1aqty!{Vb1z z+c_0^T^0wAUtb2};FIF|SsMqp*TnL=F%E98bp_rO2ajK~Hpjv3HL}#-6$jra0PD9W z4j#WwZi|DjwJTG$$HAw>!JmzTPmO~gjDy?jX?fil2cH&Kzbg)I`*3;vdK^4HFK{#t zE^B@J*Bu9s&l~i_!Drf)DL;sVORmBG^~b^O9GCI#>htnH%dQ+({-yJ8zqb9mlrigP z!|bZ|^NsTc4H~y1d#+^doKC09gtWEw=6EXY4L5h6wdVAvc|KjykTXqYW2My;>NgU_ppqpFlsL;xOrab`6kOM*R5ww zRarsz_f~pVuW9g{uRC|~vSqg|DVayt3nk59*Sf#F-gAH58b)adt*x}Wy`^~WOwVm= zLABDe<{rSkJcQg;G+RY9>Is%t@TQ@>)?=nYWYV|KTXt*l;#*1= z72mq3pk!{*Oiv*-R+QIQUeMG^qj*+T)f>|_5GcOo_JW1QV0sgyd8+D-sg$qwETm)4 zJ;01wiwc&M-gMi-g)=?#;&8E+*`C@eQU@LgOeAemt8mV(m#r_0?8{VNHm{(NIb6Pp zFpfp|Wtx>Nw40O|`I>7$%p`uts>i8UZ+uf4#?8tq286 zmfvCxUcy?lzI>&mhM;x*`Gyr$bqyhd+x604Ts7%?Mb*6qq2}JU?AFE0tiFm@H&oUU z=$ovfmAN(s!SaAheo^tf{N?)UPdHd7e4T=G|I+mk8WB&a=>v zBJu50hEL!7d4>`;viOj$Fmbr@;Eh#7|p0jY?OwZ!Am32mP%u`Mq({O& z3x(py@=t~qjRs#yb{Lv%bYiMu;cZK9DqcRX(5QQ^x57EaU=tJ)8;L&p0t6nbD?nV7 z*jUF_TyIU@vwn4DBUKr+Hl%J{zj9?|eLZr=q#M@Pt)A(btVA+v)Kxc3uJBy7q0w`# zTTQtx_qwS$*D0G)hBkXvuBoUrUro7Ad119k;(e7C`RaXc<>5!#OA3#fr8I9FS65ZI zjV5uVmImhry=udHw=yVj9SH|@y`W^=8LB?{x*69^y!79+3JJ6=uQP1!@amhv>^1C+@=P3@svNZCm_;d)JZS+FHc)>H-M zvy_dL@7<`WmngdnHI>Z5t*}T_C6trq182dukTQXV*scYdnogOuL{pDbKFtR5E0l6x zr=Rlq+cj0h!Ype!cv9}VQ&Z1UzRCXY2Lk_^rY5iuTUZKylzN$_4pKIhYpRFx%t}qk z9F-7hAO5eU zoJ6^cGC=t(WeMfozthxi%6BM_Qs%!3J}l%Xy#{?KKX{$~DVvTkKg!8(Am5amC|`$e z&6Mv^mc6AZ4-31DqsS#?7G)#l>y-N_k5YEZ`?odqCS`!Khw^U9Gn7HfH0a()Ie~Hy z<#gc#z@`g;pO6((Pi_G8D765Ni3$=PXzsp>C(F2r0I?VqPC843NRE6vr-)Hi zVQQ0hck&fwk2rQGd~eYA^&hzRB>yn=(X_|XAJ6#lh#zISogZk)+0;o@Z@l@(xBhhW z?f?9*cgz9uX^zQN3E0yIh=0}c)Fma!S1h@>s~@NTD8m_WBxf%vIp4`&{;UEtxtsPy z+2}JRxRfpb0#qEYBxjc`Ki`Gmv7Ba@1EqVAHNl}Ub?p~VreKcV+@V70&k0$7sjDN4S=^GL#v2mMH2lAP@+Vj_R} za|N%;sP_Q%jPrd(UrYnHqYUZOhv^ymaD9ZHsb}dU^-=n0eT;skennA|+u01EFQ!sq zlE_aNkcu z$=Ou7kbQ=n7SdM*eP!igZ}7WcCI@b3K0Lg1j?&LQ`gtWEJ4gBD{nQRftJAdanT;JZ z@!&Z=3o9Sb?>Hb z#=m0^FIV^R^L53p$oas>soQDRoxCvlipd5_`j6c|N*4M~UTC0V@$4Y=C*hkWNPT|7 z$5(*m1M>)gpTN3-6~)0sujQ;;2{7Xa`&&?zI>2!LmDoYYu$u#5Oi@4-GEH z6)IeaYX$Yo0_X9XOtVH{XXr!p@P!ld;~TT{rDsx@fG&4cfY$Ip@YQji2x?iN87X0uCKMX!YnSjBKzx-M7 zDx~h4)FpJV@iKXb$rbLooehj&V`UO@0#!HBZebUGR>oKuZ$oO&*$~11Y3c=#*Fv-Z zbM&_GcNE&BvTS!5ZI04r2{bp(oh}@}ZkkTOvFGwu$FeySoEKS7V*(-v`NX{qAhpxf zN5ShHjoi*o^v~p=G!UBBQvdav;W_u`bjPG<(YXopO6akNx(P+Z8Ya&MuxIX)i|kop zk{fEo`63xxXxKx4weyMf2jGz*1m{_#q|F!|KTh12{%RCyPOLJ9eHQ=1;;`w{vG4F$|C@q^~yx-`j}Y(0lOx!nnijT#|6n zqzx?+`5r^O{q$X|cP^NNX#<=S>^WfUsT<&0Nd0G7ujQEaFF)>iK3qiU!X%-?F8XLg zNSBy&_>%a|(L6x-X2_DP7oPq)aWefvhjVsALP5i;JYZ|+AK#Km1gMrqaG6mGug&ql za1KN|m&`%vUqT-{%US2LKKU~GJ1f#J>cx}^=||Sd-Syya+Ltek{hSpSn<=xOH2Qgu ze)8|P;!vIiCTs3VD7NU2{KV!YCu7fm8E4nc80wO+!0l*{S6ovJB~&=C%mOp~)!p>B zeB-$|;}ZH4&(YBGa&SNbCYkqc+Uc9|uL(SSiTpZ?E+f7)3vj(dKY^|I%P-}n^Weq7 zlE{PjHV?UlQ`^Z=aIeZP^0}s7;;d{VXzJhusxGJ9z6V%4NIN3Ka|@Z}_A%@Kt_BAN=z;u(c-0(7y~|li|4$J2iZn|Bxm01!fRVoAkJwe%7{FIza!Exz+-c z@hrN=*#tA1lVHrU2!;Wpx_X*__R-IB3rB$+e#&dIgwwCxReyRB^Ga?y+Q* z@cf**9yO#u`qRnToqCA4gM0Z2Y>edbzH=dLGO&dg!3u$Gx(K!$SS|CkWkGbg7MSGA zmYA~IrjZaBKv;W{vw3ZY@o)d9;7P1^5zNB#CHh@X{5_dx`NtkaS3fC-}jQ)<~)zh)c|fYer03Qx|ueTW3_cc z_$mHV=zrRrL$@=2oobmL%qeh_P=?^qu88X>ZQuMZI$_$BFNq_ZFF^q6oF}-Q2NGIN zAWzudim&`4KWrtG-o%kyKuJTw@W1p^OFz%=CSPyPM`DZ1vfsJuB9+1!2`?m{SxBC_ zmV12BdAeHh3?}KkGo^}`tD82PXk*C6MeB8FXD`={_sJR19GQ7m`j5AHCf^*?w^(gX zN_Z5YO%}ZFH`{byHkTlaQr_6qwSDU*+P+EK_sq5z+I>S?9nZ%pt28rmg$HPNl>GZX zlaAugE{Ri|o1o!&Z)UU=UVcE^B69S5@QGg*Tc8(-1KGCeu{XeVS^Tu15S0D@NXKUo7fnl6is$d89ycIT`A zvwY7&>L$Nt*~`MzHqBTN8%rLRT|6QH!iY5bZKUo?>`5%)UVam}b^=ooE2n77s?i)` z8It;!Kf^vFzup5*?cCcx>GKuZ%Xg4>TR`cv8(1eWu?g@yE>B4k!|GGo>gcZPavrJ+5Bohyk`GO=xIsT=tHx!lnO`745L?}0?{Uw{no|r5>f(D9rEf&#Km2!k{Du4kRDC8ulC1JT~hy`yF? zEvnYX1fn}s)3R}idq;IU^~megXP!BKpp-t&Zama3eaka+oOUJ_%j`zQ^i9l-cEzf# zZnoyFE&yNLUq+|H#3kl*967tu5k9lV8UF0vgm8apV)&Cg28B;A9vnWqUA1PU1)^E> z@sU$SemE`=U9L6f&2Xv%9;c)ASAz3sHO-qHh`O}E!#h%4Ez_a6Y6Q)qN#POXQ=e=RQVeF zo_cW9GgaQ{(nrk(_<2aRW@|}(EB_HI2~yuhdsm{G;X=NQXH7x(Kt~UH)eNnw+|XBZ z^__s$nfiCr38gabaD6wu+c_-qHMVc%`BP-H+nMPqK_PQ)VK&v2?YriMeE+iB0oTzxjmfC>yU@<$aS2`I;N(=yIr> zUU2k}ceXA_EIjxO?R|-AsOaog^sd*b`yU(?=s-?t$TyUnbh`ZLWlipAcZ*v!J$%MF zBC_|p$p?Rzn%w_K=#QO}^iIYO+PIu5=Zte?#6`VND5c(Es~$8T>YZ!i(|o1sc+{yn zhRVF5Z(j2} zoxsZ(Pu_hD-1AAyz-b5XT-YVAHqISX&AI1o(XZgM2EQHf-HF~O7`CZM4N3QDDe2QR z-B0S}fEOE3h0Q3|+UkaD!~HKfT-9aJFk2f^*9AO`-g>m=QQMer5IZ(3ClLJv8w@=n z_X6v8Y7u#M4QpEF9o)2RB&E=1W=#0&QM~*X} z7p7v{Tz(f4Q5*$n<39V^ihUf2S=!A`yg!Qc5IWP4*IADch1tT4;c1B?1_HO zVqe*yrdh)VtEf*KGbOk|b-1>0CF(7W@N?Yzo8H(=pF#Ar+nE)C7CFd4#Dy)dMpu1R z!Rfxx73sy6K37psbkhm%qt48TcgTW%kF#@D*|z5VX6T-QUxJtZX8QCvRkRJ;+gx|W zt>xQ_`t`ADr~}%R^G?P(jy`({JKNAh!s3i3>;Y%QGXx*e^^KGQ6Z_mF_7VFh@Gbv{ z{h$q=1-~I!+cH=LxDH<>b@0`?0K;{w+nH$v@=qMem*6}wlN=70-A0acN zZ)jRJ20kW$2k$=ojCrOcs*>e{)sdWyjr|R=*nwulzNruOHBHObJDVS6izFimZmv*c z{|@Go5t`orCVG|ekJy23=kUl4;0TQlh)g-?U;I)UFtO`Z@aPBhr;i`=7=K{_9%r>Y zf*$U;Qbn7Qk2}uB4s?zi)Os9VIc{uP=Ahk2ynhe<5;}=36&jviJde2*GRL{hbq;eD z8vKVrgV>82K34&>;)IE3$pssrg?>RqI-?Y3+lCw+Mbw z<}d;|%S%*8gwKMvYXLZGBU*fkBU|)>uQlr0&?(;I*^M)x(_6n$M>zAGbiEVe`_w}y?X0$7B?p{aHy6xaC@3UR1!|?Cm zuWM?`mT^OTIl4FR^x~V~%RKl~2%qM{uQ~9rAPoNuUnqU`II{BA)5p&pfsXs|QTL|@ zqBGGEk-?r7&=xz=y+TFr8mxwvI!DxiOG*2(nJp@jcNu|D&p$GAC0-={F?`^|;QcXp z^LOc@!C35B^`kjwgUD{#jq0q}1J`DwZqyeVo$lIEwry|f-idn=Pe=w*;90E`T=~J;4;I&DZ~FXG_~XAEVnvP z4qeq#s#V^rv$1KygR8j@B8ydqEgcpa%e-~wC_dt5VlD9t3V%|L&g%FK2YUSB4JKT4 zTi>Yg(SaGRuLPoA;y5Fo<6ch;%NGkj@murlFNM3(kR^P}jx?1c@sS(d_#HA_ovLycpz90i`&zCoF12nVNWizM7O;0H{Pe-xw=2wk=pmy zM76XT|5gSLuhI8M@Uj6tX(o0T{{eqW-XwW_jXFH-#&(Ji90$Jd;&buqQI0GbG&gAGR3*3ZZUiy6%t6{eH%PT& zA6s7lk0-H>!UJE``su~7!MQmDYSZ5|EEmv1v_r1)@LZo#^G>yHs>9_x60RAH@C1*ffdTWd2K}p7A^^ zq79}!_Zl>k`fKsm;$x8Y)-Uoi!YkpW_@o-jEyT)Q&^r0fn*N=!!Flt6CEw}q7hBuw zbo<3V2C)OgydoRX!|2)!nH%kqy(8^nlX#C$87l8*Ya{!ff~I;X)L%q@nP&^$5dSK^ zaTEBrqg$V$ciGygzPa$eJ$+P**)Fs1(Aijt*udMV7pC59>hzw8l@!r;+g&~RZ5w`> z{~0m8#NYONSzrGJTyT!VGhO8ZaS6$stxpX4W&s5j+m$IhX&KM_Ib2IL`PaWDyJuaqMn0H0z-h8B69pYc_uqwlUu3i68iQGI1hx zhfqiS^vB4W9Z6pabd=r1JkbS1 zrZjhw{uR}6jDAIa-!{q&{}OO6!^ZybAF<4{8-Ep!Vka+Nhm0T=IyOkHLyk)_&6vUgpNcvH1@x3#SY+zKgo#vnz1^F3qEFDH{;eoxP;i@--yYZ z9G$aR!$lv&X181!h=wJ8a5T+oCzgv4=RHHb=c1iAMMeJ#o~B%z@!~h>vyJ-L`~zF9 zc%uoJ#DEg_M)6&C>}$upJx+%|dlSBQ-nbV3BzBX5OzQ92Mz2cC%hnZfUSLL-L(j!dMCPEMldnK7J%d`Sx(Bs7 zc^{LG6NZ-s|j)w=Q26?t5MT+|SI zEg-ICyDLN5PNhxLhp}n54^}xRuPF?F$~-2*uUhy&)~GYW{{VU?e$M|*6`GXdZt*9& zTPAK*q2{Ypv=o|1{bX#_r_am{uSyAoYKN;RXE298gbdcEsL;eK=ntJ@%XvyIF_+!{ zLt2Xag#xv1E3`@(9tiE%Mo&o@u0mdDrc>7oy(T%*auGKW}s0#3uvx94EnB!NZ%9+>&xt zAo>%=|BmwJNj<5bOuHcQ+Z@I&CFfnx6D#p0Cbtye4}8c2{2JX3E~-GauBYy`^m85W z_tDp@yniS$1v~?xA8IKr*E$0ox#Lyqe(o^hw;KFS<^*>_{O zg|QYmRdixfAhaMcEpLlcTi52oKO(CU=iU8J7`l=f@xjYda_%R|@y{nG@6qOeX55p1 zFV=QE>Oq!0@bl+`H-+yYhLW}134Hg@(aYZa(u7zAn;IBKI@ze%7Te4n6PS_PhH9U;I~z zo*4*PSuBIuzHf#>L87V<<4pJB8eoIRzG zds|laC42Iafe!szH8cYq4>GpS+C});jGYs?-Qv`%56x92LcdyP()^u&8j{DlKJNsw z^9=MWRY`pXS*qk$vzzjKLkni~&Q8k}{`$b7RJr=fGgXQ3SK^%a$nBkkJ|)OW$y{}y zXl!DuE7Z`~J4a31F-R?)JxkRUqYq61#zXfe%?do69I9=s4X9~8VxAi0->x&3SVMBF zXPHj~I-Lfe6HjTaj|>ZRy!(jguq$#qc*uJDfAgni)gNQinr|f5&VxU$Hy+JPZj$`= z(3nH7nFxJ-vaSWU;%T0|-h0KqfD>!wVl69IAsf>7*c5WWe~3+!KBR8!Ui={K!c*}R zJioH> zo%pX1tVwObH&MlbWTMf|Db(<>4ahos6T;z=vTM%iy)- zHv2XN8*AvdZ-ciHIcSyfYG_x(Tx6d@a+^9d3jzyReak!@*eE{JM2-u?qKm)4w@iml zZy_%t1F{bK9Q!Zt4L^#kPaUhZuAZBnz7To*ZE;$9&4ytADs1B-&71eWWrMNXEjzId zwjJKN^`S?1rl{7P(Jc?Jx&qur213thNelLlo!IAM4#C0M=~Z93GW`U0$VZ>qL)AJb zYhthV%~9g(Tw3asV-B^By~9z*kW0ZQpsSqsnNtpWCwx5NbVs&Ahj;1YJ!rBi9lniG zhOhG?^Zv2ehH2EI# z-Y~K8Z}c^UnDs+!%aJpKb6cUq4Rh4e8@{34I(yvY8XxP5v>O8tzaCoKxJyfG8S3W! zpzQS7?9UW@9xEA|8VIF1hPI@QQ>`CdrJ{et?;gaCd*N9RcGhe0XCip?C6R;IcP*=i zKfw_4fgaRUPHg?;8r4xm-s6SqZwx14A6u}EY@!a;@kKSxB=;6gTXzR~?IF%FWgs(> zK1}WIMIN>|N7w0&UCZ2R*E04YM}3kRh<=`}I_@KHBldX&ICVI`)<1(aj)&(bUQ|b( zM5f+#X8GT|R_zW9G5I0!A8nrNVSfB=+q`)GjDkNK*c-@YE;;m3zu~_A|N3mp8^JlM zwP&y*_GGUNyCm!HKIaI3G0)xD9myr1VDG|f;d&SPITJpXI$eEj@UCckASCflw&t#p zwO!G$f*C#k7@S))WJHUOPtMkc^_9cJBIoE9H~zVYHH2QZv9XkV1M3Wl`8tB+nD9$c zFFZ_whkfv{W|XOGrHKn_R=~f$!Nl};RyS7T9~WSkY7(>4w;<13;okycAKAm6k9~7P zt3ScN&$9y^LMx%uapo1=qTM>|O~%%Sq&r!^ehaz@?Z2w&xkBTU$bdd}Q0qzhXYIM) z;p&_EVKoByHG&XOKt3PqIeXiGASwT=eI_h_|Or zME+}tr6v+fef7z`xt=Fu)A~lU2Z}%0LT_M zB~P5~7u#2GG*5K0u_GzMGN5ar8gVsX87J8q&s_$U25LV-rO#^~j&;Q{cY_*2KEydCE@c z@JmYZ^Ev3N(Brf-$uBvg`NRg;rAFez+5Rwn z2rYcT<=I7y@iee6&dClwqEDC?mb|R&Ys6v+;n?*m+WXYtu+AEI0@00dm}OC8r~DPXSUN8 z**K8LTzdby>5Xu8RsU>F>np?Fe2Q<$)-wCZ?Ux**uGIN6WtiNGw1bWZB=*7X^-0cp zK6)Kq=5LvZtV+&t0eK_wdp_#e^?rlTlDIx#KMgME$I^D_v}F|6On5#7{U^`p9^1BzT*M>TkH}|Sy5@Fs z4kGe7S5GGUSu0eloMq@84Bv+!SET`o;rr$gGZ_4RSMtBvkV4`}v1>Uyslwv<_bB=h~=N$fWl(=a&yjvC5*6ZqeRQ#AL<>g0D zI`x-zjlD=;(qkhuSBu!K+_T{ayZ*lU4WWh1*Pz9CbP;~Yd{0<&7uv|&|Hz!${u};e zl1p@q>^q5nk=Rll4s7Uh?0wXAw)sJw7&wsPY7rVb=%br9|LGXsg4|^qI!c}y8k5U0 z?YEYg>Hj@-x=+NW39Z_Ma>5nJ-HnIgio>y!kF)Kk_$J^zuGroqQ?#=oyh_ zi$;jOfyr&;?4m~moo1Zly5d9nWzle2UniIQ~ zl9gA4kCi>9{udO!!PL7mls$9Fd16;Y7kh~B^tq~)ydvjEvi&{a+wV+UAT&M&UMZ@* zF3KMHai=1EtTNtAQ@I~N8@v9e&e4$_M{-+&&S5RjIfk{oMZfurRCu;Ct%jui&gIoYOnkDm zIRxL1Ika{7i;^Jnm#wArIX{b)+#ZePNFKbLJ_F=yBvw0t?8tg!7JQR@>F@pc4bD?# zrxEY5KW5b9JvPceojT+Xf7%V+23&Yb{F)0~cp$XQR>S**p4}hEN}7`TfBvhu{@{7Z zHHEMWj(fq zbCN$9u0nm_AaV3-(6t=hsD~#4m%f{}9?Mr>{kQxUYtKXa7M(v%tkSa;yRC*T5V`Ye zdQz`*7%zN%W>pxHZkEzY;8aGMa~uV(7%i^e6DI8!SgTv;BCq%%PK1b%%%^pJ^<5qoR(;Hnp zvlgyVHQ{`>3PjyCzWx?ydK^0;dq-{X;PTI+uHFY)_-V9nKE6{_l=L_Cf!?%0bQwXhC$ zCGN>fE+EDouUbz*H#u|Ihi|K4KV!r!&JJu1?B9kDg>OerIJe~=->&W7s?FKIEv0kT zKKx1FT*Y2pa`g%4H}mCeDraf;pS+n^ZS&?gBJ86asIEGgskvX4{K31>3|r@S3~5@n z8@h1BeO(juYZ}y_JAr3iWR@Js0aCLABSzmk5v(m81d>ADr)A9 zv;Ca$%Gl?h;|mje=>_V@GtkMUxfamJ(k)tYo#?5=jp~r^Y67{+OSse(;mIyd=heYcH@i2`6TxT1v6@SUkabgXy4AB zikwODv)7O^d0rU5+IoDnqhmh%RE z`wA^Odn9$pC!f;Na{Fli5c^ZJ&yEh)J~6m8wp|OK%1a2>vaVcpd{FDDJZE?zG5CC9 z?&A~5v7a3i_PlUU#B@&xMl<~4C|J6@ul+qy^ z8hbayFS~n)b6;d0Ui7aA|7xGZ`W&BmxpP>$E^EOA&R{wH9~*lja)wbwnvo~jH}S2% z(%3WUN-vnv?o@flu2x2FR?Z-Z&7BWU9J-&@#X72s*kI&UB7-CF_wqg~@&fPWoMR*X z>Dcrl{`YqGrxF8FXDsvTcM!J#>$*wgCVNWPO{(J+)klYUUbMicJyP1<8c{`odFlwKWIx( zIiB?XJc9<~5?gy%yPjkY6WRZn#9FpSThcFiz8Y|pJS6r^blN!ofPQm++THgAcE{XT zCx1YmWh-=>8S*v?-Ri)how$APO={h-haI7E));%xD>*9>&rh441|5aZ%xg6BYK+F7 zGx&y#;42K?2@i@Aym^v$>SF)oWAaSR8_1<{#^wpU&_0xRD*^FSIcE zF+3tU2l<|%m$P9$_EwZu{F!mj-inuR8)TnwuUoBCS%D2bf7&}Lz`CAjgzq}LP2Z{d@g(183i4> zIh(3eSMsXy zzg}{a!BmyI%NlPDJQQE6rxncb0(Ug0*>0WI)Z&K-o@tkry872bv&ae?3Aw6r{8s)*PS!SVad72Cs_ zYgpt~UT{3KM>xD))o3x0nCzhjAu zYqp`II}%e{YT#v3q3ZB~%MN@hF;-~O;Iur!iTq;74^CciI?ld;;7|pPBsVfg&Xz+1 zeGp?>^la8Vxh}@{;!jvFryKbf<}BY1NWA?dxg5K0J9|()$iZ0PL2L|vX3n5#k*4x& zeq=yv;X$u8XU5KvHD`v@WgHj0*`cXi$z@8eP}afXN53Za2AgWqDkCEL)&o!4L%v4I zTZx^Nw#5!*#11ZKckbK(w2)ZhG;xHfTgr%Kl9;2+qXxTDbyegs^s9B^1QnKZxq`1b zzWC_ui1?T91K(lDk>t{cMf|cqGTiJ#?D~HC@gV~>@TC^MRMQ{(CQDxf&sR66jQ;D? zF;jMGsYyF|UIhJHu=x|Alk8K-++|ENCdiI#$LGlS%e-oLBk$wSqI#3jdmTO!-+7hZ zGM{yT4o{1rn?_6~^m=k7@hx~i$-2A~`_oP?MsOBeDYmUBK}GE|tm3N&I^U#2$1M}E zkJwOM%gPnsGvFB_r8tv(>G`5Rvu`W1o8e*4c0(2UM?JTieSxF|SId0pQ-dzehb9u& za>lJj&e`e6unymB+3Inmm>@E0Z?}k2TJ5=@ zoiizdqm0#c=6VA!J%K*r`l_d|AbkbVzbADg&JR@+b5}W-JACuOw`TbEG5Pbc@J?)& z%{%^IuDjbfw<5eVeHY)H!M|d7HwC4+t21{<_ z+xQ~!-LuE5b)rXyu-kIpMq)kjY1s0b?$fc7v69meloeCIRASmx-5J3o#gw3A6ws#9SAZ`72|l( zKlY?EgZMqMiBeDe>?5*I2c3IPU!U8;d%^GPlrhe9h>!Z@4hMe9X?&j`v=BOe%=oNT zFNufS!8geNCNC_$mAK5zab-sw(CC}&kF^a}(;~c+da|~cxGl_gPN&e{$RKQdqMG7E z$J?3jMD*MXPuTZKvg5b~=yWxE7L$B|8Lt>|W_4pOK7m*w4VAqguaw(VNaDZy$T2h!Zwn0%BQwqHc}sht zrI&qO+dsRYnZyfYx9X8Qp)oOxDaXXaBF7@{GKSE7_Ez@W!BN)ee)i_(v)1vmHz#!D zf4^+RetvMM0+(v==pv`O_bwmf@?K&?WTY3_A%~k(gCjpJ@`h@x1Yni1zJ-&ylZ4H*d2ivhdHCne#z3q zoZsld1_b$Lql~fg7|U3%T7Y=e7K$c|hm~%elU@rY8P~sDpC(tpB zH4$sy&}{7GcI;&pIY8ekV=a(`?K@D#{tEG$aXy%H&Daq6UY{5M|6lAn{HWN5%ux2N z!ENLmwal6@eF#r04f!#@&p(YFYh(|$Ikm9AnR&aBGxEL0_b{?=F6)y8@T!LIVBGXe zPT`2G0VzLZ-X#ma8R*}~cM#|j-&OefKSNG>iM=I9D890j?*=%dBI^<2f#Jp;5No>U zWRHF>b%ft8jXWZ2ym3Yxpd#00Kx5=$1o@#{$yucb*7d}`y(~Jed6YhEM9Zi|_E=e` z&Q46PgAT-yf#^m@AezZpxyNKbk+p!tB#$$089K;*#!~XxN8Tc?7|AycvM$(2e&N43 z>s~gVGw_^`m-@S@ALO~0I0iaIOUXmZnIoN?vh4R7XH^(izB4RhFGKqLzI+2kzEz{G z6bv-81KWlXqtYC?F?KNW`G-*QE`_akzG zgSBYTK2KrgbtR|!d79c?PVP%`xuOf7^4!gwgV4xDed5X~=*oJ*w`1$as*pzc|tCQU31A)*uY=p$Du4G~r))EU6h?h98n}b~)j&J_{9M#&jVSVEc zd>#A`Erh;1XJx0~PwucRRh7gz2ly&{;hT}rgZQW#&AlKtZek7V;u5d;CH%dtsYTy2 zwz?uOBZKGThioH1{eWs)Ci3yAA>$GgzSMr$sWPNLiDTGzNb(hEw-&op4!J%fK9m@_ z3|^`f$1VOFHa~odxV;Y_ATeDH`9RrUYa&0fRrX5q%U93wj!j*ZucfB2USE{&OkHJ^ ziK*bu^WfBL$yYDRccngLlu4=Y8)ZuBBF-Z(${&)t>g?d$3G{t!z9V(Nmbu^|&df+Z z?*qR!e~<}F6j);FLk7%k!dwEQe*>0k!cqk0N_~~{YS-qco3Nn*OG;g3z($xb^l?#s zN@|J$8)d@KS;6Q1erbE92}55ck1sZ?mOd!U-JF-h5V7&poO&wym`3>S_|FL zy`R>&RI7vabhReue>rc44$f!1YUtGuj$d{zO8i;~-}Nc`^dEC}=s9rtIqR0M@U2H1 z_{n-h_Pyn~9Qz(bCs@ZEaG}rD>{|)0Rh(-nM(?XQ+fu}u_bhEnDaF_I4Cj1i#i(0* zUsSEKx9;csEU~)(#9$R2<%w6NsiGc;|%an8ORbBwMz%y@l#bM_fF z``}$0_?CWDQuwZ_em7?n<;+IeQ1ctMQs?Lzu{|5u8-IB?df-sGUe3zo-+}%zuDu^3 zYlL>r_5b_dV?Su${2ua3;uv?H_e)xFSl z0lLbW)SLxdOZ)dallmT?+Vbe9@GJigzRP5+9YdH0^yq;$GKVKt%6@2e4*U5h=2>1LSd;ojB*z?8<)?diE8(V}-|~CH19k9i2TX-7+K7deBiW?(G~fos2l(Kf5sW#3`V-~Z=Q5@KzL-@ zHoo;Qz3!glk|JVwz@>T|-f8y@~ z^)QxqCEv=EPaeUTW&FJq`I`c=_a%Re;$~p_rpRNG_ZY3ImnHY|Yj7$ff9;(em?3*< zUF68R9iv;s21_3Fue^_?tB{P(_n*`B7yaam_%4{f?Df%c^tX{Um(=miCC;P{xthK? zNvec%VSd3o_*r(Y7kqu3yU4sof9yZm1Lhl)1;tMHg4yVOJ2ZKcxwx?prAyU^+c;xj z#O^#7L;v}lBYEbo>V9PTL*j&?6ThkYx1uu$dGs*3`rmQMTz&sk5S{--f$~s1e`0Vr=@+3R8Q498x2Xt~K zxaMrx8!vwrd+u7!JuLvoeTyc(yc2x)ZdD)3xs!z-f8*i(+VB?1bKm7mPTF6tTL0=y zj{NH_6I)&zsy?h4$~gzl0k?f~_Wu1_RcirrV&AxLY(Ia?2EJzm)REEXcL{vx=I;i@ z^Hbv9X6kgq=WkI;4)(MaABnG)wV>^%#Xn2lZwEf|A=ao9H8*EXym=4euZ!;tHhu^D z{(EHp6n_(Cl~d1s71>Y0r@e}v?<034@>I=Qvd`%yKGgixWBn85d_eK$VWWvZAJLai zeQX@(KU4vFpxiA-!B@r-AL4VVr8`FPEr&x-(zzEuB02a|1;h>o1#fVsdFUg=5PbWR za|+(Pk6)JkKv@^;$3MWUBLT)@t_OaCpZG3oSZQ;NeBjzCdq=&dr6t`Gy1r4)jmUUg zi9NBYckCU-|+OVW%aN+Nfgic=&?+m<#8i|i7hSm}no4E}w;{Ci} z#!U9bkn?KRekERF5%~sw4tUDBtYYL`zBOyVT7OLBtrw&ozRzm8|=+WFD-u6*!ygrV){wn1jx96nuzNIV&^L zS~rDQH;pjXP0i?V5Lx*Za=ekeyPPpC5*y>l;(O8Ik!)v1pX6_3O=!jIu zdY*gt*Bw;mym z+#R4!&y4}{V5TkYNeZx!#rhbTe2n+Uu%&yG_zsmmg_e%ZV@69~?Z{^sIlTiIHgLiG zbyuYS3jHkQJ2d!PGK!ehdj+-vK0BsuepvGDQ|a%7W7L99$EcPkxI{KijPrB8OZLEr zM<()KXi+Y@GtP+n4SH^6Y|if<0r#l1aq;&Dd|KdVuF#DBW^Aa~Q}#li6b+Wz=lVnb}{$**c*v8Td|!|*}qEN36ZuFBtaYR0x69-%@{Kvx$wOvi@FS%`7q zq&|&3C*z3y68Ue&H~oNmayI9aJFNX&Cx6GskR277P3+cg@u3YqV0RAa$W}Xi$P)ZF zKY>rvBjm7Omhb6BwlX-6G=edOFNusPI?@hbnytM)rAD0danpD&>+LP@M(7c5V`We7 zlRIQxWS;H$qFl=;?1ad?tVwhDn>4p$A3lZ`VdBy%biWvRmpCi%l*Dv8IqwYr!;IZ2 z>oO}pCuisRHsEC!YvwPq3G%Jq9q37w!;|*|zGoSUPKi!`j-K_?_i=1RJ$;{qf5@l5 zhdmUD_s65pz3g!vca9caMCV8L$+s;MD~i36_GV0y!8uGl^1nUc_jznukX(hAb5CMF zdifqm{Cf{$9KugaY)CvAy_!CXuaUi=(UD2$?!={&TAF!BoQi!J8L7sG@Qq)e!VX=_ zJN7lBr<}=tY`J`Ec7*cCUd}&nM|c0VV@vp%@a*t@_;`%7@_o)B5&mXM~ams}%i~MVueG1-tVE z-z0vQQsyw3@5xT^?xD~(9##(=zvU43{}+4j9v@Y8?*H#S6JRD6ASB!ZZ7yKV40z`v zHkL_1n+qx+ZAE)d0;tVo07X#~s3rlmfdMrZtHqv^fIazp^ZVnQ*K4x(+I#JFeb#eZ&-1L1nSuRvMtEd_ zap)~~M!TEmDFI{kTbafo8{*Zl!agIi%35EaVP`yV z)FJT4 zgeDgvUmw3s@#`i4pOb;X9DED1p&cXJiPOlX!FGS03yi;{G1!puo*_2fzo_=!iV%Ftyi$efXkxFMwCX zhPtMJ@g|0)e1O-}kKUi9)b)MY)zvp*Saaa`y+t+Xygx+W6Md5Bm9DOCbPIm;W75Cl z+r6d)II`@ms9$3${G)N5&lu+2&3+o==VR==_-}i67@Zm`XPmsq??LsS{#ZIQ`cXss z%JYpGDtqT-$(^a~?@_jN9ckW%#p%9=#cxp0n>@e4l@@;7Obb7U-lJz&aYx8Bn_a`r zj>8!N+~Ob_917Nhcy3FX4@DV6p-nvj9GqeT8Rgkb4Q((|nXNR}uSVbxlnz zV|sjRa9RvH*nCgWUt13EmJhu4l4tWZ=n?4q48Db;YGiH~!cnU*HF?Q^A7gz8f!!Q)wbEM@oumq8bQ z3k|=JaqXuKo6&0>rT(XZ={{h&#YhhogKvZb+mX?K?~cvRHB&-=%^K-bKlUM)O205b zyRTM1&Ox5~3wVzHd66AF%!Q9}mEOS9xM{lwf8133aciM5C!vieSXbT^z=-St`Q`|9 zE8g3O&_mA(+5AuR|CDkC&o9CAAEr&xZOe!5gnM$k)qmCi_L{7@VgBlR&!i$`ot#{F zO1^18XK|Q(yJ>qHdtV;6r^0pnq)C6_{b6)J-Ms5@Pij9&of$kUWo$J*f2H1mSBfLA zGhguEnLcw;MGRa}%Gef?ejoYkU2MEHPo<0_`*!-N7C33)*WPSPv!<_)8#&=A8Pvn;6eRRpAJZ- zpf4IP_2+^0g|+JYbF^a>y06vj(Jx><;#YeATgZSNy(!JlQSaWyeMS2k_ZPhd4cw3Y z{@!6`$KJ-hMf=TP?P@*zT2bq;mZG=*F4~Z4@$fan)50r9TJ)j0oV9e1)*<*&a?`zA zD`VPMsb?)d4}Ey-jmp}~(WOb|?1O$t&s1&M*~a69)nl6}y^Qo3T1zvL0kw7t#~O;| znOTT_Z@$~&(}{h_9<(2x<9j7_HQ*nW6Xg3+-k0%RDc=-=BTM-PnMkpo>S`DZ;bP%U z@NH%(zZ2k3#kmQx7hR36y#pVZYRN-3KUfXT;;fqN?| z=Itq2tgp~Y?)Mn(5PFU!EnJs_tCjX~MRB<(L-BLK)yG&T(u3@QKYf$_gunp{_;uXb zlJgd_qK9|)oe`)#M7xOdcKpUaoL&1CbMvM<)87rxC3^n?@9KH}rhBwsHsV%2!nO3{ z7-d2u{AW>DXqb6r*houTLw@-?=(*nO-ErC>pZ0F}m9t$L6&=XoZ}aXsu4>a!%BDT! zA6;?Womnx6eqkAXTDOoyUaA42{U`}(KNB+LSA4iI{bx4`erb&U?~gV$94`R3>fjpLCUhDUag zM>za-+A}FX5V7d*e@?ab$4jxRo&+3uz;nMOc0<$Ex5R`4ykFqiMDd@Vir_T#0nic2 zs=be-M4$~z`X&_*M1MBFE(WgXeR6)C;yQ@V3$F?vtpyHGe7U5n|K_qe@JEZjG1H>+ zi#~)dMWNfhPo~s8iZ7A$f*)RK4174x)f{~V-O##QV%_AiVhpi9u%Go;17{zun;Clw zo^sbm*n0qrQPNe&qvBbLfZHf-_;3n1k#8Mcq1Ke@UIrZ$Pw;EL$3JSD^71@W&sr}r zW-eg7v~GlxbIt6~yYMYfeD8sr(ra#s`S^+NMwVG}@~wFh;>OTd`hq>g=JmJKR$_Ns zz&nd~0w2wxEah|3$~%K+PI?diP|q-adkW%vB-%ffEE-vT`~9i#3~BHTqVI38_U