From 5332a0e4a9d764e783029792c2edac18f17dfc8f Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Sun, 16 Feb 2025 23:34:56 +0000 Subject: [PATCH 001/139] Uplift jnr-ffi to 2.2.17 (#235) and upload-artifact / download-artifact actions * Uplift jnr-ffi to 2.2.16 Potentially fixes this https://github.com/jnr/jffi/issues/138 * Uplift jnr-ffi to 2.2.17 * Uplift actions/upload-artifact to v4 * Uplift download-artifact action to v4 * Uplift upload-artifact action to v4 --- .github/workflows/maven.yml | 8 ++++---- pom.xml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 32668e32..4fc1b22c 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -32,7 +32,7 @@ jobs: run: mvn -B verify -DgcRecordWrites=1000 - name: Store built native libraries for later jobs - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: native-libraries path: | @@ -64,7 +64,7 @@ jobs: cache: maven - name: Fetch built native libraries - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: native-libraries path: src/main/resources/org/lmdbjava @@ -73,7 +73,7 @@ jobs: run: mvn -B test -Dtest=VerifierTest -DverificationSeconds=10 - name: Upload Surefire reports on test failure - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: name: surefire-test-log @@ -116,7 +116,7 @@ jobs: MAVEN_CENTRAL_TOKEN: ${{ secrets.nexus_password }} - name: Debug settings.xml - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: name: settings.xml diff --git a/pom.xml b/pom.xml index 75ce0fe6..9ef2ea4f 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ com.github.jnr jnr-ffi - 2.2.15 + 2.2.17 com.google.code.findbugs From b5dfb253767ca0d7ffcdfcdb8a00a50a31c84c7b Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Sun, 16 Feb 2025 23:37:16 +0000 Subject: [PATCH 002/139] Uplift lmdb from 0.9.29 to 0.9.31 (#245) * Uplidt LMDB lib from 0.9.29 to 0.9.31 * Uplift upload-artifact action to v4 --- cross-compile.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cross-compile.sh b/cross-compile.sh index 1ebf92f6..ebf92d72 100755 --- a/cross-compile.sh +++ b/cross-compile.sh @@ -3,7 +3,7 @@ set -o errexit rm -rf lmdb -git clone --depth 1 --branch LMDB_0.9.29 https://github.com/LMDB/lmdb.git +git clone --depth 1 --branch LMDB_0.9.31 https://github.com/LMDB/lmdb.git pushd lmdb/libraries/liblmdb trap popd SIGINT From 68f0a4465d91a05cc3bebaf602f6afbe8ba3b44b Mon Sep 17 00:00:00 2001 From: Cosimo Damiano Prete <8491864+cdprete@users.noreply.github.com> Date: Mon, 17 Feb 2025 00:46:30 +0100 Subject: [PATCH 003/139] Regression: Add DbiFlags#MDB_UNSIGNEDKEY to allow to compare byte array, ByteBuffer and DirectBuffer keys as unsigned like in versions prior to 0.9.0 (#237) Co-authored-by: Cosimo Damiano Prete --- src/main/java/org/lmdbjava/BufferProxy.java | 22 ++++++- .../java/org/lmdbjava/ByteArrayProxy.java | 50 +++++++++++---- src/main/java/org/lmdbjava/ByteBufProxy.java | 28 ++++++--- .../java/org/lmdbjava/ByteBufferProxy.java | 28 ++++----- src/main/java/org/lmdbjava/Cursor.java | 10 +-- src/main/java/org/lmdbjava/Dbi.java | 38 +++++++----- src/main/java/org/lmdbjava/DbiFlags.java | 21 ++++++- .../java/org/lmdbjava/DirectBufferProxy.java | 40 +++++++----- src/main/java/org/lmdbjava/Env.java | 42 +++++-------- src/main/java/org/lmdbjava/MaskedFlag.java | 62 +++++++++++++++---- src/main/java/org/lmdbjava/Txn.java | 6 +- 11 files changed, 233 insertions(+), 114 deletions(-) diff --git a/src/main/java/org/lmdbjava/BufferProxy.java b/src/main/java/org/lmdbjava/BufferProxy.java index aff732bc..f42da7ff 100644 --- a/src/main/java/org/lmdbjava/BufferProxy.java +++ b/src/main/java/org/lmdbjava/BufferProxy.java @@ -21,6 +21,10 @@ 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; @@ -72,8 +76,24 @@ public abstract class BufferProxy { * @param flags for the database * @return a comparator that can be used (never null) */ - protected abstract Comparator getComparator(DbiFlags... flags); + 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 unsigned. + * + * @return a comparator that can be used (never null) + */ + protected abstract Comparator getUnsignedComparator(); + /** + * Get a suitable default {@link Comparator} to compare numeric key values as signed. + * + * @return a comparator that can be used (never null) + */ + protected abstract Comparator getSignedComparator(); /** * Deallocate a buffer that was previously provided by {@link #allocate()}. * diff --git a/src/main/java/org/lmdbjava/ByteArrayProxy.java b/src/main/java/org/lmdbjava/ByteArrayProxy.java index 3fe8184a..2066b679 100644 --- a/src/main/java/org/lmdbjava/ByteArrayProxy.java +++ b/src/main/java/org/lmdbjava/ByteArrayProxy.java @@ -20,14 +20,15 @@ package org.lmdbjava; -import static java.util.Objects.requireNonNull; -import static org.lmdbjava.Library.RUNTIME; +import jnr.ffi.Pointer; +import jnr.ffi.provider.MemoryManager; import java.util.Arrays; import java.util.Comparator; -import jnr.ffi.Pointer; -import jnr.ffi.provider.MemoryManager; +import static java.lang.Math.min; +import static java.util.Objects.requireNonNull; +import static org.lmdbjava.Library.RUNTIME; /** * Byte array proxy. @@ -43,7 +44,10 @@ public final class ByteArrayProxy extends BufferProxy { private static final MemoryManager MEM_MGR = RUNTIME.getMemoryManager(); - private ByteArrayProxy() { + private static final Comparator signedComparator = ByteArrayProxy::compareArraysSigned; + private static final Comparator unsignedComparator = ByteArrayProxy::compareArrays; + + private ByteArrayProxy() { } /** @@ -60,7 +64,7 @@ public static int compareArrays(final byte[] o1, final byte[] o2) { if (o1 == o2) { return 0; } - final int minLength = Math.min(o1.length, o2.length); + final int minLength = min(o1.length, o2.length); for (int i = 0; i < minLength; i++) { final int lw = Byte.toUnsignedInt(o1[i]); @@ -74,15 +78,32 @@ 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 + */ + @SuppressWarnings("PMD.CompareObjectsWithEquals") + 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]; } - protected int compare(final byte[] o1, final byte[] o2) { - return compareArrays(o1, o2); - } - @Override protected void deallocate(final byte[] buff) { // byte arrays cannot be allocated @@ -94,8 +115,13 @@ protected byte[] getBytes(final byte[] buffer) { } @Override - protected Comparator getComparator(final DbiFlags... flags) { - return this::compare; + protected Comparator getSignedComparator() { + return signedComparator; + } + + @Override + protected Comparator getUnsignedComparator() { + return unsignedComparator; } @Override diff --git a/src/main/java/org/lmdbjava/ByteBufProxy.java b/src/main/java/org/lmdbjava/ByteBufProxy.java index 26351676..ed3d71e1 100644 --- a/src/main/java/org/lmdbjava/ByteBufProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufProxy.java @@ -20,16 +20,17 @@ package org.lmdbjava; -import static io.netty.buffer.PooledByteBufAllocator.DEFAULT; -import static java.lang.Class.forName; -import static org.lmdbjava.UnsafeAccess.UNSAFE; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.PooledByteBufAllocator; +import jnr.ffi.Pointer; import java.lang.reflect.Field; import java.util.Comparator; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.PooledByteBufAllocator; -import jnr.ffi.Pointer; +import static io.netty.buffer.PooledByteBufAllocator.DEFAULT; +import static java.lang.Class.forName; +import static java.util.Objects.requireNonNull; +import static org.lmdbjava.UnsafeAccess.UNSAFE; /** * A buffer proxy backed by Netty's {@link ByteBuf}. @@ -51,6 +52,12 @@ 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; @@ -107,13 +114,14 @@ protected ByteBuf allocate() { throw new IllegalStateException("Netty buffer must be " + NAME); } - protected int compare(final ByteBuf o1, final ByteBuf o2) { - return o1.compareTo(o2); + @Override + protected Comparator getSignedComparator() { + return comparator; } @Override - protected Comparator getComparator(final DbiFlags... flags) { - return this::compare; + protected Comparator getUnsignedComparator() { + return comparator; } @Override diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index 8eb95da8..9983d76e 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -27,6 +27,7 @@ import static java.nio.ByteOrder.LITTLE_ENDIAN; import static java.util.Objects.requireNonNull; import static org.lmdbjava.DbiFlags.MDB_INTEGERKEY; +import static org.lmdbjava.DbiFlags.MDB_UNSIGNEDKEY; import static org.lmdbjava.Env.SHOULD_CHECK; import static org.lmdbjava.MaskedFlag.isSet; import static org.lmdbjava.MaskedFlag.mask; @@ -111,6 +112,14 @@ 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 @@ -193,22 +202,13 @@ protected final ByteBuffer allocate() { } @Override - protected Comparator getComparator(final DbiFlags... flags) { - final int flagInt = mask(flags); - if (isSet(flagInt, MDB_INTEGERKEY)) { - return this::compareCustom; - } - return this::compareDefault; + protected Comparator getSignedComparator() { + return signedComparator; } - protected final int compareDefault(final ByteBuffer o1, - final ByteBuffer o2) { - return o1.compareTo(o2); - } - - protected final int compareCustom(final ByteBuffer o1, - final ByteBuffer o2) { - return compareBuff(o1, o2); + @Override + protected Comparator getUnsignedComparator() { + return unsignedComparator; } @Override diff --git a/src/main/java/org/lmdbjava/Cursor.java b/src/main/java/org/lmdbjava/Cursor.java index ed7c1848..cb9130e3 100644 --- a/src/main/java/org/lmdbjava/Cursor.java +++ b/src/main/java/org/lmdbjava/Cursor.java @@ -40,6 +40,8 @@ import jnr.ffi.Pointer; import jnr.ffi.byref.NativeLongByReference; +import java.util.Arrays; + /** * A cursor handle. * @@ -120,7 +122,7 @@ public void delete(final PutFlags... f) { txn.checkReady(); txn.checkWritesAllowed(); } - final int flags = mask(f); + final int flags = mask(true, f); checkRc(LIB.mdb_cursor_del(ptrCursor, flags)); } @@ -256,7 +258,7 @@ public boolean put(final T key, final T val, final PutFlags... op) { } kv.keyIn(key); kv.valIn(val); - final int mask = mask(op); + final int mask = mask(true, op); final int rc = LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), mask); if (rc == MDB_KEYEXIST) { @@ -299,7 +301,7 @@ public void putMultiple(final T key, final T val, final int elements, txn.checkReady(); txn.checkWritesAllowed(); } - final int mask = mask(op); + final int mask = mask(true, op); if (SHOULD_CHECK && !isSet(mask, MDB_MULTIPLE)) { throw new IllegalArgumentException("Must set " + MDB_MULTIPLE + " flag"); } @@ -364,7 +366,7 @@ public T reserve(final T key, final int size, final PutFlags... op) { } kv.keyIn(key); kv.valIn(size); - final int flags = mask(op) | MDB_RESERVE.getMask(); + final int flags = mask(true, op) | MDB_RESERVE.getMask(); checkRc(LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), flags)); kv.valOut(); diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index ef8ec315..c5be4f85 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -20,6 +20,17 @@ package org.lmdbjava; +import jnr.ffi.Pointer; +import jnr.ffi.byref.IntByReference; +import jnr.ffi.byref.PointerByReference; +import org.lmdbjava.Library.ComparatorCallback; +import org.lmdbjava.Library.MDB_stat; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + import static java.util.Objects.requireNonNull; import static jnr.ffi.Memory.allocateDirect; import static jnr.ffi.NativeType.ADDRESS; @@ -36,17 +47,6 @@ import static org.lmdbjava.PutFlags.MDB_RESERVE; import static org.lmdbjava.ResultCodeMapper.checkRc; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; - -import jnr.ffi.Pointer; -import jnr.ffi.byref.IntByReference; -import jnr.ffi.byref.PointerByReference; -import org.lmdbjava.Library.ComparatorCallback; -import org.lmdbjava.Library.MDB_stat; - /** * LMDB Database. * @@ -64,10 +64,18 @@ public final class Dbi { Dbi(final Env env, final Txn txn, final byte[] name, final Comparator comparator, final boolean nativeCb, final BufferProxy proxy, final DbiFlags... flags) { + if (SHOULD_CHECK) { + requireNonNull(txn); + txn.checkReady(); + } this.env = env; this.name = name == null ? null : Arrays.copyOf(name, name.length); - this.comparator = comparator; - final int flagsMask = mask(flags); + if(comparator == null) { + this.comparator = proxy.getComparator(flags); + } else { + 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); @@ -377,7 +385,7 @@ public boolean put(final Txn txn, final T key, final T val, } txn.kv().keyIn(key); txn.kv().valIn(val); - final int mask = mask(flags); + final int mask = mask(true, flags); final int rc = LIB.mdb_put(txn.pointer(), ptr, txn.kv().pointerKey(), txn .kv().pointerVal(), mask); if (rc == MDB_KEYEXIST) { @@ -422,7 +430,7 @@ public T reserve(final Txn txn, final T key, final int size, } txn.kv().keyIn(key); txn.kv().valIn(size); - final int flags = mask(op) | MDB_RESERVE.getMask(); + final int flags = mask(true, 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 diff --git a/src/main/java/org/lmdbjava/DbiFlags.java b/src/main/java/org/lmdbjava/DbiFlags.java index 081fb80e..7277b55b 100644 --- a/src/main/java/org/lmdbjava/DbiFlags.java +++ b/src/main/java/org/lmdbjava/DbiFlags.java @@ -68,6 +68,15 @@ public enum DbiFlags implements MaskedFlag { * similar to {@link #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. * @@ -86,9 +95,15 @@ public enum DbiFlags implements MaskedFlag { MDB_CREATE(0x4_0000); private final int mask; + private final boolean propagatedToLmdb; - DbiFlags(final int mask) { + DbiFlags(final int mask, final boolean propagatedToLmdb) { this.mask = mask; + this.propagatedToLmdb = propagatedToLmdb; + } + + DbiFlags(final int mask) { + this(mask, true); } @Override @@ -96,4 +111,8 @@ 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 62b49095..3bde81ec 100644 --- a/src/main/java/org/lmdbjava/DirectBufferProxy.java +++ b/src/main/java/org/lmdbjava/DirectBufferProxy.java @@ -20,20 +20,20 @@ package org.lmdbjava; -import static java.lang.ThreadLocal.withInitial; -import static java.nio.ByteBuffer.allocateDirect; -import static java.nio.ByteOrder.BIG_ENDIAN; -import static java.util.Objects.requireNonNull; -import static org.lmdbjava.UnsafeAccess.UNSAFE; +import jnr.ffi.Pointer; +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; import java.nio.ByteBuffer; import java.util.ArrayDeque; import java.util.Comparator; -import jnr.ffi.Pointer; -import org.agrona.DirectBuffer; -import org.agrona.MutableDirectBuffer; -import org.agrona.concurrent.UnsafeBuffer; +import static java.lang.ThreadLocal.withInitial; +import static java.nio.ByteBuffer.allocateDirect; +import static java.nio.ByteOrder.BIG_ENDIAN; +import static java.util.Objects.requireNonNull; +import static org.lmdbjava.UnsafeAccess.UNSAFE; /** * A buffer proxy backed by Agrona's {@link DirectBuffer}. @@ -42,6 +42,13 @@ * 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, @@ -112,8 +119,14 @@ protected DirectBuffer allocate() { } } - protected int compare(final DirectBuffer o1, final DirectBuffer o2) { - return compareBuff(o1, o2); + @Override + protected Comparator getSignedComparator() { + return signedComparator; + } + + @Override + protected Comparator getUnsignedComparator() { + return unsignedComparator; } @Override @@ -129,11 +142,6 @@ protected byte[] getBytes(final DirectBuffer buffer) { return dest; } - @Override - protected Comparator getComparator(final DbiFlags... flags) { - return this::compare; - } - @Override protected void in(final DirectBuffer buffer, final Pointer ptr, final long ptrAddr) { diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index db8b0f4a..ae7e46c9 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -20,6 +20,19 @@ package org.lmdbjava; +import jnr.ffi.Pointer; +import jnr.ffi.byref.IntByReference; +import jnr.ffi.byref.PointerByReference; +import org.lmdbjava.Library.MDB_envinfo; +import org.lmdbjava.Library.MDB_stat; + +import java.io.File; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + import static java.lang.Boolean.getBoolean; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; @@ -33,19 +46,6 @@ import static org.lmdbjava.ResultCodeMapper.checkRc; import static org.lmdbjava.TxnFlags.MDB_RDONLY_TXN; -import java.io.File; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -import jnr.ffi.Pointer; -import jnr.ffi.byref.IntByReference; -import jnr.ffi.byref.PointerByReference; -import org.lmdbjava.Library.MDB_envinfo; -import org.lmdbjava.Library.MDB_stat; - /** * LMDB environment. * @@ -159,7 +159,7 @@ public void close() { public void copy(final File path, final CopyFlags... flags) { requireNonNull(path); validatePath(path); - final int flagsMask = mask(flags); + final int flagsMask = mask(true, flags); checkRc(LIB.mdb_env_copy2(ptr, path.getAbsolutePath(), flagsMask)); } @@ -388,17 +388,7 @@ public Dbi openDbi(final byte[] name, final Comparator comparator, public Dbi openDbi(final Txn txn, final byte[] name, final Comparator comparator, final boolean nativeCb, final DbiFlags... flags) { - if (SHOULD_CHECK) { - requireNonNull(txn); - txn.checkReady(); - } - final Comparator useComparator; - if (comparator == null) { - useComparator = proxy.getComparator(flags); - } else { - useComparator = comparator; - } - return new Dbi<>(this, txn, name, useComparator, nativeCb, proxy, flags); + return new Dbi<>(this, txn, name, comparator, nativeCb, proxy, flags); } /** @@ -583,7 +573,7 @@ public Env open(final File path, final int mode, 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 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)); diff --git a/src/main/java/org/lmdbjava/MaskedFlag.java b/src/main/java/org/lmdbjava/MaskedFlag.java index 9bdef636..242ca589 100644 --- a/src/main/java/org/lmdbjava/MaskedFlag.java +++ b/src/main/java/org/lmdbjava/MaskedFlag.java @@ -20,6 +20,11 @@ package org.lmdbjava; +import java.util.Arrays; +import java.util.Objects; +import java.util.function.Predicate; +import java.util.stream.Stream; + import static java.util.Objects.requireNonNull; /** @@ -34,25 +39,58 @@ 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. + * + * @param flags to mask (null or empty returns zero) + * @return the integer mask for use in C + */ + @SafeVarargs + static int mask(final M... flags) { + return mask(false, flags); + } + /** * Fetch the integer mask for all presented flags. * * @param flags to mask (null or empty returns zero) * @return the integer mask for use in C */ - static int mask(final MaskedFlag... flags) { - if (flags == null || flags.length == 0) { - return 0; - } + static int mask(final Stream flags) { + return mask(false, flags); + } + + /** + * Fetch the integer mask for the presented flags. + * + * @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 onlyPropagatedToLmdb if to include only the flags which are also propagate to the C code or all of them + * @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; - int result = 0; - for (final MaskedFlag flag : flags) { - if (flag == null) { - continue; - } - result |= flag.getMask(); - } - return result; + 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/Txn.java b/src/main/java/org/lmdbjava/Txn.java index b6d28917..3092c7c4 100644 --- a/src/main/java/org/lmdbjava/Txn.java +++ b/src/main/java/org/lmdbjava/Txn.java @@ -20,6 +20,8 @@ package org.lmdbjava; +import jnr.ffi.Pointer; + import static jnr.ffi.Memory.allocateDirect; import static jnr.ffi.NativeType.ADDRESS; import static org.lmdbjava.Env.SHOULD_CHECK; @@ -34,8 +36,6 @@ import static org.lmdbjava.Txn.State.RESET; import static org.lmdbjava.TxnFlags.MDB_RDONLY_TXN; -import jnr.ffi.Pointer; - /** * LMDB transaction. * @@ -55,7 +55,7 @@ public final class Txn implements AutoCloseable { final TxnFlags... flags) { this.proxy = proxy; this.keyVal = proxy.keyVal(); - final int flagsMask = mask(flags); + final int flagsMask = mask(true, flags); this.readOnly = isSet(flagsMask, MDB_RDONLY_TXN); if (env.isReadOnly() && !this.readOnly) { throw new EnvIsReadOnly(); From 309d8d9be4d2ff84109fa0b140afd2910d1f49d3 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 17 Feb 2025 13:32:42 +1100 Subject: [PATCH 004/139] Refactor project build configuration This commit makes no functional changes to any LmdbJava code. It is purely confined to build system related adjustments. These are aimed at simplifying long-term maintenance and ease of third party contributions. The changes include: * Removal of parent POM * Removal of PMD, SpotBugs and Checkstyle * Replacement of license plugin * Automatic code formatting (using Google style) * Update to Codecov integration --- .github/workflows/maven.yml | 4 +- README.md | 5 + cross-compile.sh | 16 + pom.xml | 351 ++++++++++++++++-- src/main/java/org/lmdbjava/BufferProxy.java | 115 +++--- .../java/org/lmdbjava/ByteArrayProxy.java | 52 +-- src/main/java/org/lmdbjava/ByteBufProxy.java | 79 ++-- .../java/org/lmdbjava/ByteBufferProxy.java | 119 +++--- src/main/java/org/lmdbjava/CopyFlags.java | 28 +- src/main/java/org/lmdbjava/Cursor.java | 127 +++---- .../java/org/lmdbjava/CursorIterable.java | 70 ++-- src/main/java/org/lmdbjava/Dbi.java | 246 ++++++------ src/main/java/org/lmdbjava/DbiFlags.java | 69 ++-- .../java/org/lmdbjava/DirectBufferProxy.java | 80 ++-- src/main/java/org/lmdbjava/Env.java | 319 +++++++--------- src/main/java/org/lmdbjava/EnvFlags.java | 166 ++++----- src/main/java/org/lmdbjava/EnvInfo.java | 74 ++-- src/main/java/org/lmdbjava/GetOp.java | 36 +- src/main/java/org/lmdbjava/KeyRange.java | 78 ++-- src/main/java/org/lmdbjava/KeyRangeType.java | 337 +++++++---------- src/main/java/org/lmdbjava/KeyVal.java | 35 +- src/main/java/org/lmdbjava/Library.java | 103 ++--- src/main/java/org/lmdbjava/LmdbException.java | 24 +- .../org/lmdbjava/LmdbNativeException.java | 57 +-- src/main/java/org/lmdbjava/MaskedFlag.java | 52 +-- src/main/java/org/lmdbjava/Meta.java | 54 +-- src/main/java/org/lmdbjava/PutFlags.java | 45 +-- src/main/java/org/lmdbjava/ReferenceUtil.java | 56 +-- .../java/org/lmdbjava/ResultCodeMapper.java | 31 +- src/main/java/org/lmdbjava/SeekOp.java | 87 ++--- src/main/java/org/lmdbjava/Stat.java | 75 ++-- src/main/java/org/lmdbjava/TargetName.java | 108 +++--- src/main/java/org/lmdbjava/Txn.java | 140 +++---- src/main/java/org/lmdbjava/TxnFlags.java | 26 +- src/main/java/org/lmdbjava/UnsafeAccess.java | 52 +-- src/main/java/org/lmdbjava/Verifier.java | 111 +++--- src/main/java/org/lmdbjava/package-info.java | 69 ++-- src/misc/license-template.txt | 13 + .../org/lmdbjava/ByteBufferProxyTest.java | 30 +- .../java/org/lmdbjava/ComparatorTest.java | 79 ++-- .../java/org/lmdbjava/CursorIterableTest.java | 83 ++--- .../java/org/lmdbjava/CursorParamTest.java | 68 ++-- src/test/java/org/lmdbjava/CursorTest.java | 88 ++--- src/test/java/org/lmdbjava/DbiTest.java | 137 +++---- src/test/java/org/lmdbjava/EnvTest.java | 136 ++----- .../org/lmdbjava/GarbageCollectionTest.java | 47 +-- src/test/java/org/lmdbjava/KeyRangeTest.java | 36 +- src/test/java/org/lmdbjava/LibraryTest.java | 21 +- .../java/org/lmdbjava/MaskedFlagTest.java | 25 +- src/test/java/org/lmdbjava/MetaTest.java | 25 +- .../org/lmdbjava/ResultCodeMapperTest.java | 23 +- .../java/org/lmdbjava/TargetNameTest.java | 21 +- src/test/java/org/lmdbjava/TestUtils.java | 38 +- src/test/java/org/lmdbjava/TutorialTest.java | 131 +++---- src/test/java/org/lmdbjava/TxnTest.java | 53 +-- src/test/java/org/lmdbjava/VerifierTest.java | 37 +- src/test/java/org/lmdbjava/package-info.java | 20 +- 57 files changed, 1991 insertions(+), 2616 deletions(-) create mode 100644 src/misc/license-template.txt diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 4fc1b22c..b28e8481 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -40,7 +40,9 @@ jobs: src/main/resources/org/lmdbjava/*.dll - name: Upload code coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v5 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_ORG_TOKEN }} compatibility-checks: name: Java ${{ matrix.java }} on ${{ matrix.os }} Compatibility diff --git a/README.md b/README.md index 048e57bc..62c5abe1 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,11 @@ system package managers, running `cross-compile.sh` on a supported system, or copying it from the `org/lmdbjava` directory of any recent, officially released LmdbJava JAR. +### Releasing + +GitHub Actions will perform an official release whenever a developer executes +`mvn release:clean release:prepare`. + ### Contributing Contributions are welcome! Please see the [Contributing Guidelines](CONTRIBUTING.md). diff --git a/cross-compile.sh b/cross-compile.sh index ebf92d72..aa8815d5 100755 --- a/cross-compile.sh +++ b/cross-compile.sh @@ -1,4 +1,20 @@ #!/bin/bash +# +# 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. +# + set -o errexit diff --git a/pom.xml b/pom.xml index 9ef2ea4f..9e695d0f 100644 --- a/pom.xml +++ b/pom.xml @@ -1,11 +1,23 @@ + 4.0.0 - - au.com.acegi - acegi-standard-project - 0.7.0 - org.lmdbjava lmdbjava 0.9.1-SNAPSHOT @@ -13,9 +25,13 @@ LmdbJava Low latency Java API for the ultra-fast, embedded Symas Lightning Database (LMDB) - lmdbjava - lmdbjava - apache_v2 + yyyy-MM-dd'T'HH:mm:ss'Z' + 1.8 + 1.8 + 1.8 + 3.5.4 + UTF-8 + false @@ -28,10 +44,6 @@ jnr-ffi 2.2.17 - - com.google.code.findbugs - annotations - com.google.guava guava @@ -59,6 +71,14 @@ junit junit + 4.13.2 + test + + + org.hamcrest + hamcrest-core + + org.agrona @@ -69,6 +89,8 @@ org.hamcrest hamcrest + 2.2 + test org.mockito @@ -80,73 +102,246 @@ - au.com.acegi - xml-format-maven-plugin + com.mycila + license-maven-plugin + 4.6 + + + +
src/misc/license-template.txt
+ + LICENSE.txt + **/*.md + lmdb/** + +
+
+ true +
+ + + com.mycila + license-maven-plugin-git + 4.6 + + + + + apply-license + + format + + package + + 2 + + + +
+ + org.apache.maven.plugins + maven-clean-plugin + 3.4.0 + + false + - com.github.spotbugs - spotbugs-maven-plugin + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 org.apache.maven.plugins maven-dependency-plugin - - - com.github.jnr:jffi - org.mockito:mockito-core - org.mockito:mockito-inline - - + 3.8.1 + + + + analyze-only + properties + + + false + + com.github.jnr:jffi + org.mockito:mockito-core + org.mockito:mockito-inline + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.3 org.apache.maven.plugins maven-enforcer-plugin + 3.5.0 + + + config-enforcer + + enforce + + + + + + [${maven.enforcer.mvn},) + + + [${maven.enforcer.java},) + + + + + true + + + + + + + + + org.apache.maven.plugins + maven-install-plugin + 3.1.3 org.apache.maven.plugins maven-jar-plugin + 3.4.2 + + true + - org.lmdbjava + ${buildNumber} org.apache.maven.plugins - maven-pmd-plugin + maven-javadoc-plugin + 3.11.1 + + + attach-javadocs + + jar + + + org.apache.maven.plugins - maven-surefire-plugin + maven-release-plugin + 3.1.1 - 1 - false + clean + clean + false + true - org.codehaus.mojo - buildnumber-maven-plugin + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + + org.apache.maven.plugins + maven-site-plugin + 3.21.0 + + true + + + + org.apache.maven.plugins + maven-source-plugin + 3.3.1 + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.2 org.codehaus.mojo - license-maven-plugin + buildnumber-maven-plugin + 3.2.1 + + false + false + UNKNOWN + + jgit + + true + 7 + + + + org.apache.maven.scm + maven-scm-provider-jgit + 2.1.0 + + + + + + create + + initialize + + org.codehaus.mojo versions-maven-plugin + 2.18.0 org.jacoco jacoco-maven-plugin + 0.8.12 + + + config-jacoco-prepare-agent + + prepare-agent + + + + config-jacoco-report + + report + + +
2016 The LmdbJava Open Source Project - https://github.com/${github.org} + https://github.com/lmdbjava @@ -173,19 +368,29 @@ - scm:git:git@github.com:${github.org}/${github.repo}.git - scm:git:git@github.com:${github.org}/${github.repo}.git - git@github.com:${github.org}/${github.repo}.git + scm:git:git@github.com:lmdbjava/lmdbjava.git + scm:git:git@github.com:lmdbjava/lmdbjava.git + git@github.com:lmdbjava/lmdbjava.git HEAD GitHub Issues - https://github.com/${github.org}/${github.repo}/issues + https://github.com/lmdbjava/lmdbjava/issues GitHub Actions - https://github.com/${github.org}/${github.repo}/actions + https://github.com/lmdbjava/lmdbjava/actions + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + add-opens-on-java-9-and-above @@ -198,7 +403,7 @@ org.apache.maven.plugins maven-surefire-plugin - --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED + @{argLine} --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED 1 false @@ -216,14 +421,78 @@ com.github.ekryd.sortpom sortpom-maven-plugin + 4.0.0 + + ${project.basedir}/pom.xml + ${project.build.sourceEncoding} + custom_1 + true + groupId,artifactId + groupId,artifactId + true + false + false + \n + false + + + + + sort + + prepare-package + + + + com.spotify.fmt + fmt-maven-plugin + 2.25 + + + + format + + + + + + + + + ossrh-deploy + + org.apache.maven.plugins - maven-checkstyle-plugin + maven-gpg-plugin + 3.2.7 + + + sign-artifacts + + sign + + verify + + + --pinentry-mode + loopback + + + + - org.basepom.maven - duplicate-finder-maven-plugin + org.sonatype.plugins + nexus-staging-maven-plugin + 1.7.0 + true + + ossrh + https://oss.sonatype.org/ + true + diff --git a/src/main/java/org/lmdbjava/BufferProxy.java b/src/main/java/org/lmdbjava/BufferProxy.java index f42da7ff..e66031d2 100644 --- a/src/main/java/org/lmdbjava/BufferProxy.java +++ b/src/main/java/org/lmdbjava/BufferProxy.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static java.lang.Long.BYTES; @@ -27,51 +22,59 @@ import static org.lmdbjava.MaskedFlag.mask; import java.util.Comparator; - import jnr.ffi.Pointer; /** * The strategy for mapping memory address to a given buffer type. * - *

- * The proxy is passed to the {@link Env#create(org.lmdbjava.BufferProxy)} - * method and is subsequently used by every {@link Txn}, {@link Dbi} and - * {@link Cursor} associated with the {@link Env}. + *

The proxy is passed to the {@link Env#create(org.lmdbjava.BufferProxy)} method and is + * subsequently used by every {@link Txn}, {@link Dbi} and {@link Cursor} associated with the {@link + * Env}. * * @param buffer type */ -@SuppressWarnings("checkstyle:AbstractClassName") public abstract class BufferProxy { - /** - * Size of a MDB_val pointer in bytes. - */ + /** Size of a MDB_val pointer in bytes. */ protected static final int MDB_VAL_STRUCT_SIZE = BYTES * 2; - /** - * Offset from a pointer of the MDB_val.mv_data field. - */ + /** Offset from a pointer of the MDB_val.mv_data field. */ protected static final int STRUCT_FIELD_OFFSET_DATA = BYTES; - /** - * Offset from a pointer of the MDB_val.mv_size field. - */ + /** 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() {} + /** - * Allocate a new buffer suitable for passing to - * {@link #out(java.lang.Object, jnr.ffi.Pointer, long)}. + * Allocate a new buffer suitable for passing to {@link #out(java.lang.Object, jnr.ffi.Pointer, + * long)}. * * @return a buffer for passing to the out method */ protected abstract T allocate(); + /** + * Deallocate a buffer that was previously provided by {@link #allocate()}. + * + * @param buff the buffer to deallocate (required) + */ + protected abstract void deallocate(T buff); + + /** + * Obtain a copy of the bytes contained within the passed buffer. + * + * @param buffer a non-null buffer created by this proxy instance + * @return a copy of the bytes this buffer is currently representing + */ + 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. + *

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) @@ -79,64 +82,51 @@ public abstract class BufferProxy { protected Comparator getComparator(DbiFlags... flags) { final int intFlag = mask(flags); - return isSet(intFlag, MDB_INTEGERKEY) || isSet(intFlag, MDB_UNSIGNEDKEY) ? getUnsignedComparator() : getSignedComparator(); + return isSet(intFlag, MDB_INTEGERKEY) || isSet(intFlag, MDB_UNSIGNEDKEY) + ? getUnsignedComparator() + : 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(); /** * Get a suitable default {@link Comparator} to compare numeric key values as signed. * * @return a comparator that can be used (never null) */ protected abstract Comparator getSignedComparator(); - /** - * Deallocate a buffer that was previously provided by {@link #allocate()}. - * - * @param buff the buffer to deallocate (required) - */ - protected abstract void deallocate(T buff); /** - * Obtain a copy of the bytes contained within the passed buffer. + * Get a suitable default {@link Comparator} to compare numeric key values as unsigned. * - * @param buffer a non-null buffer created by this proxy instance - * @return a copy of the bytes this buffer is currently representing + * @return a comparator that can be used (never null) */ - protected abstract byte[] getBytes(T buffer); + protected abstract Comparator getUnsignedComparator(); /** - * 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()}. + * 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()}. * - * @param buffer the buffer to write to MDB_val - * @param ptr the pointer to the MDB_val + * @param buffer the buffer to write to MDB_val + * @param ptr the pointer to the MDB_val * @param ptrAddr the address of the MDB_val pointer */ protected abstract void in(T buffer, Pointer ptr, long ptrAddr); /** - * Called when the MDB_val should be set to reflect the passed - * buffer. + * Called when the MDB_val should be set to reflect the passed buffer. * - * @param buffer the buffer to write to MDB_val - * @param size the buffer size to write to MDB_val - * @param ptr the pointer to the MDB_val + * @param buffer the buffer to write to MDB_val + * @param size the buffer size to write to MDB_val + * @param ptr the pointer to the MDB_val * @param ptrAddr the address of the MDB_val pointer */ protected abstract void in(T buffer, int size, Pointer ptr, long ptrAddr); /** - * Called when the MDB_val may have changed and the passed buffer - * should be modified to reflect the new MDB_val. + * Called when the MDB_val may have changed and the passed buffer should be modified + * to reflect the new MDB_val. * - * @param buffer the buffer to write to MDB_val - * @param ptr the pointer to the MDB_val + * @param buffer the buffer to write to MDB_val + * @param ptr the pointer to the MDB_val * @param ptrAddr the address of the MDB_val pointer * @return the buffer for MDB_val */ @@ -150,5 +140,4 @@ protected Comparator getComparator(DbiFlags... flags) { final KeyVal keyVal() { return new KeyVal<>(this); } - } diff --git a/src/main/java/org/lmdbjava/ByteArrayProxy.java b/src/main/java/org/lmdbjava/ByteArrayProxy.java index 2066b679..4a22ab83 100644 --- a/src/main/java/org/lmdbjava/ByteArrayProxy.java +++ b/src/main/java/org/lmdbjava/ByteArrayProxy.java @@ -1,45 +1,37 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; -import jnr.ffi.Pointer; -import jnr.ffi.provider.MemoryManager; - -import java.util.Arrays; -import java.util.Comparator; - import static java.lang.Math.min; import static java.util.Objects.requireNonNull; import static org.lmdbjava.Library.RUNTIME; +import java.util.Arrays; +import java.util.Comparator; +import jnr.ffi.Pointer; +import jnr.ffi.provider.MemoryManager; + /** * Byte array proxy. * - * {@link Env#create(org.lmdbjava.BufferProxy)}. + *

{@link Env#create(org.lmdbjava.BufferProxy)}. */ public final class ByteArrayProxy extends BufferProxy { - /** - * The byte array proxy. Guaranteed to never be null. - */ + /** The byte array proxy. Guaranteed to never be null. */ public static final BufferProxy PROXY_BA = new ByteArrayProxy(); private static final MemoryManager MEM_MGR = RUNTIME.getMemoryManager(); @@ -47,8 +39,7 @@ public final class ByteArrayProxy extends BufferProxy { private static final Comparator signedComparator = ByteArrayProxy::compareArraysSigned; private static final Comparator unsignedComparator = ByteArrayProxy::compareArrays; - private ByteArrayProxy() { - } + private ByteArrayProxy() {} /** * Lexicographically compare two byte arrays. @@ -57,7 +48,6 @@ private ByteArrayProxy() { * @param o2 right operand (required) * @return as specified by {@link Comparable} interface */ - @SuppressWarnings("PMD.CompareObjectsWithEquals") public static int compareArrays(final byte[] o1, final byte[] o2) { requireNonNull(o1); requireNonNull(o2); @@ -85,15 +75,14 @@ public static int compareArrays(final byte[] o1, final byte[] o2) { * @param b2 right operand (required) * @return as specified by {@link Comparable} interface */ - @SuppressWarnings("PMD.CompareObjectsWithEquals") 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]; + 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; @@ -125,8 +114,7 @@ protected Comparator getUnsignedComparator() { } @Override - protected void in(final byte[] buffer, final Pointer ptr, - final long ptrAddr) { + protected void in(final byte[] buffer, final Pointer ptr, final long ptrAddr) { final Pointer pointer = MEM_MGR.allocateDirect(buffer.length); pointer.put(0, buffer, 0, buffer.length); ptr.putLong(STRUCT_FIELD_OFFSET_SIZE, buffer.length); @@ -134,14 +122,12 @@ protected void in(final byte[] buffer, final Pointer ptr, } @Override - protected void in(final byte[] buffer, final int size, final Pointer ptr, - final long ptrAddr) { + protected void in(final byte[] buffer, final int size, final Pointer ptr, final long ptrAddr) { // cannot reserve for byte arrays } @Override - protected byte[] out(final byte[] buffer, final Pointer ptr, - final long ptrAddr) { + protected byte[] out(final byte[] buffer, final Pointer ptr, final long ptrAddr) { final long addr = ptr.getAddress(STRUCT_FIELD_OFFSET_DATA); final int size = (int) ptr.getLong(STRUCT_FIELD_OFFSET_SIZE); final Pointer pointer = MEM_MGR.newPointer(addr, size); diff --git a/src/main/java/org/lmdbjava/ByteBufProxy.java b/src/main/java/org/lmdbjava/ByteBufProxy.java index ed3d71e1..cac5b97b 100644 --- a/src/main/java/org/lmdbjava/ByteBufProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufProxy.java @@ -1,50 +1,42 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.PooledByteBufAllocator; -import jnr.ffi.Pointer; - -import java.lang.reflect.Field; -import java.util.Comparator; - import static io.netty.buffer.PooledByteBufAllocator.DEFAULT; import static java.lang.Class.forName; import static java.util.Objects.requireNonNull; import static org.lmdbjava.UnsafeAccess.UNSAFE; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.PooledByteBufAllocator; +import java.lang.reflect.Field; +import java.util.Comparator; +import jnr.ffi.Pointer; + /** * A buffer proxy backed by Netty's {@link ByteBuf}. * - *

- * This class requires {@link UnsafeAccess} and netty-buffer must be in the - * classpath. + *

This class requires {@link UnsafeAccess} and netty-buffer must be in the classpath. */ public final class ByteBufProxy extends BufferProxy { /** - * A proxy for using Netty {@link ByteBuf}. Guaranteed to never be null, - * although a class initialization exception will occur if an attempt is made - * to access this field when Netty is unavailable. + * A proxy for using Netty {@link ByteBuf}. Guaranteed to never be null, although a class + * initialization exception will occur if an attempt is made to access this field when Netty is + * unavailable. */ public static final BufferProxy PROXY_NETTY = new ByteBufProxy(); @@ -52,22 +44,29 @@ 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); + private static final Comparator comparator = + (o1, o2) -> { + requireNonNull(o1); + requireNonNull(o2); - return o1.compareTo(o2); - }; + return o1.compareTo(o2); + }; private final long lengthOffset; private final long addressOffset; - + private final PooledByteBufAllocator nettyAllocator; private ByteBufProxy() { this(DEFAULT); } + /** + * Constructs a buffer proxy for use with Netty. + * + * @param allocator the Netty allocator to obtain the {@link ByteBuf} from + */ public ByteBufProxy(final PooledByteBufAllocator allocator) { + super(); this.nettyAllocator = allocator; try { @@ -138,24 +137,20 @@ protected byte[] getBytes(final ByteBuf buffer) { @Override protected void in(final ByteBuf buffer, final Pointer ptr, final long ptrAddr) { - UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, - buffer.writerIndex() - buffer.readerIndex()); - UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA, - buffer.memoryAddress() + buffer.readerIndex()); + UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, buffer.writerIndex() - buffer.readerIndex()); + UNSAFE.putLong( + ptrAddr + STRUCT_FIELD_OFFSET_DATA, buffer.memoryAddress() + buffer.readerIndex()); } @Override - protected void in(final ByteBuf buffer, final int size, final Pointer ptr, - final long ptrAddr) { - UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, - size); - UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA, - buffer.memoryAddress() + buffer.readerIndex()); + protected void in(final ByteBuf buffer, final int size, final Pointer ptr, final long ptrAddr) { + UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, size); + UNSAFE.putLong( + ptrAddr + STRUCT_FIELD_OFFSET_DATA, buffer.memoryAddress() + buffer.readerIndex()); } @Override - protected ByteBuf out(final ByteBuf buffer, final Pointer ptr, - final long ptrAddr) { + protected ByteBuf out(final ByteBuf buffer, final Pointer ptr, final long ptrAddr) { final long addr = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA); final long size = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE); UNSAFE.putLong(buffer, addressOffset, addr); diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index 9983d76e..2b7cdf0d 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static java.lang.Long.reverseBytes; @@ -26,11 +21,7 @@ import static java.nio.ByteOrder.BIG_ENDIAN; import static java.nio.ByteOrder.LITTLE_ENDIAN; import static java.util.Objects.requireNonNull; -import static org.lmdbjava.DbiFlags.MDB_INTEGERKEY; -import static org.lmdbjava.DbiFlags.MDB_UNSIGNEDKEY; import static org.lmdbjava.Env.SHOULD_CHECK; -import static org.lmdbjava.MaskedFlag.isSet; -import static org.lmdbjava.MaskedFlag.mask; import static org.lmdbjava.UnsafeAccess.UNSAFE; import java.lang.reflect.Field; @@ -38,38 +29,32 @@ import java.nio.ByteBuffer; import java.util.ArrayDeque; import java.util.Comparator; - import jnr.ffi.Pointer; /** * {@link ByteBuffer}-based proxy. * - *

- * There are two concrete {@link ByteBuffer} proxy implementations available: + *

There are two concrete {@link ByteBuffer} proxy implementations available: + * *

    - *
  • A "fast" implementation: {@link UnsafeProxy}
  • - *
  • A "safe" implementation: {@link ReflectiveProxy}
  • + *
  • A "fast" implementation: {@link UnsafeProxy} + *
  • A "safe" implementation: {@link ReflectiveProxy} *
* - *

- * Users nominate which implementation they prefer by referencing the - * {@link #PROXY_OPTIMAL} or {@link #PROXY_SAFE} field when invoking - * {@link Env#create(org.lmdbjava.BufferProxy)}. + *

Users nominate which implementation they prefer by referencing the {@link #PROXY_OPTIMAL} or + * {@link #PROXY_SAFE} field when invoking {@link Env#create(org.lmdbjava.BufferProxy)}. */ public final class ByteBufferProxy { /** - * The fastest {@link ByteBuffer} proxy that is available on this platform. - * This will always be the same instance as {@link #PROXY_SAFE} if the - * {@link UnsafeAccess#DISABLE_UNSAFE_PROP} has been set to true - * and/or {@link UnsafeAccess} is unavailable. Guaranteed to never be null. + * The fastest {@link ByteBuffer} proxy that is available on this platform. This will always be + * the same instance as {@link #PROXY_SAFE} if the {@link UnsafeAccess#DISABLE_UNSAFE_PROP} has + * been set to true and/or {@link UnsafeAccess} is unavailable. Guaranteed to never + * be null. */ 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 { @@ -77,8 +62,7 @@ public final class ByteBufferProxy { PROXY_OPTIMAL = getProxyOptimal(); } - private ByteBufferProxy() { - } + private ByteBufferProxy() {} private static BufferProxy getProxyOptimal() { try { @@ -88,45 +72,42 @@ 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. + * Provides {@link ByteBuffer} pooling and address resolution for concrete {@link BufferProxy} + * implementations. */ 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); + private static final Comparator signedComparator = + (o1, o2) -> { + requireNonNull(o1); + requireNonNull(o2); - return o1.compareTo(o2); - }; - private static final Comparator unsignedComparator = AbstractByteBufferProxy::compareBuff; + 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. + * 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. */ - private static final ThreadLocal> BUFFERS - = withInitial(() -> new ArrayDeque<>(16)); + private static final ThreadLocal> BUFFERS = + withInitial(() -> new ArrayDeque<>(16)); /** * Lexicographically compare two buffers. @@ -135,7 +116,6 @@ abstract static class AbstractByteBufferProxy extends BufferProxy { * @param o2 right operand (required) * @return as specified by {@link Comparable} interface */ - @SuppressWarnings("PMD.CyclomaticComplexity") public static int compareBuff(final ByteBuffer o1, final ByteBuffer o2) { requireNonNull(o1); requireNonNull(o2); @@ -224,12 +204,11 @@ protected byte[] getBytes(final ByteBuffer buffer) { buffer.get(dest, 0, buffer.limit()); return dest; } - } /** - * A proxy that uses Java reflection to modify byte buffer fields, and - * official JNR-FFF methods to manipulate native pointers. + * A proxy that uses Java reflection to modify byte buffer fields, and official JNR-FFF methods to + * manipulate native pointers. */ private static final class ReflectiveProxy extends AbstractByteBufferProxy { @@ -242,22 +221,20 @@ private static final class ReflectiveProxy extends AbstractByteBufferProxy { } @Override - protected void in(final ByteBuffer buffer, final Pointer ptr, - final long ptrAddr) { + protected void in(final ByteBuffer buffer, final Pointer ptr, final long ptrAddr) { ptr.putAddress(STRUCT_FIELD_OFFSET_DATA, address(buffer)); ptr.putLong(STRUCT_FIELD_OFFSET_SIZE, buffer.remaining()); } @Override - protected void in(final ByteBuffer buffer, final int size, final Pointer ptr, - final long ptrAddr) { + protected void in( + final ByteBuffer buffer, final int size, final Pointer ptr, final long ptrAddr) { ptr.putLong(STRUCT_FIELD_OFFSET_SIZE, size); ptr.putAddress(STRUCT_FIELD_OFFSET_DATA, address(buffer)); } @Override - protected ByteBuffer out(final ByteBuffer buffer, final Pointer ptr, - final long ptrAddr) { + protected ByteBuffer out(final ByteBuffer buffer, final Pointer ptr, final long ptrAddr) { final long addr = ptr.getAddress(STRUCT_FIELD_OFFSET_DATA); final long size = ptr.getLong(STRUCT_FIELD_OFFSET_SIZE); try { @@ -269,12 +246,11 @@ protected ByteBuffer out(final ByteBuffer buffer, final Pointer ptr, buffer.clear(); return buffer; } - } /** - * A proxy that uses Java's "unsafe" class to directly manipulate byte buffer - * fields and JNR-FFF allocated memory pointers. + * A proxy that uses Java's "unsafe" class to directly manipulate byte buffer fields and JNR-FFF + * allocated memory pointers. */ private static final class UnsafeProxy extends AbstractByteBufferProxy { @@ -293,22 +269,20 @@ private static final class UnsafeProxy extends AbstractByteBufferProxy { } @Override - protected void in(final ByteBuffer buffer, final Pointer ptr, - final long ptrAddr) { + protected void in(final ByteBuffer buffer, final Pointer ptr, final long ptrAddr) { UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, buffer.remaining()); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA, address(buffer)); } @Override - protected void in(final ByteBuffer buffer, final int size, final Pointer ptr, - final long ptrAddr) { + protected void in( + final ByteBuffer buffer, final int size, final Pointer ptr, final long ptrAddr) { UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, size); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA, address(buffer)); } @Override - protected ByteBuffer out(final ByteBuffer buffer, final Pointer ptr, - final long ptrAddr) { + protected ByteBuffer out(final ByteBuffer buffer, final Pointer ptr, final long ptrAddr) { final long addr = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA); final long size = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE); UNSAFE.putLong(buffer, ADDRESS_OFFSET, addr); @@ -317,5 +291,4 @@ protected ByteBuffer out(final ByteBuffer buffer, final Pointer ptr, return buffer; } } - } diff --git a/src/main/java/org/lmdbjava/CopyFlags.java b/src/main/java/org/lmdbjava/CopyFlags.java index 88649a04..4365563c 100644 --- a/src/main/java/org/lmdbjava/CopyFlags.java +++ b/src/main/java/org/lmdbjava/CopyFlags.java @@ -1,35 +1,24 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; -/** - * Flags for use when performing a - * {@link Env#copy(java.io.File, org.lmdbjava.CopyFlags...)}. - */ +/** Flags for use when performing a {@link Env#copy(java.io.File, org.lmdbjava.CopyFlags...)}. */ public enum CopyFlags implements MaskedFlag { - /** - * Compacting copy: Omit free space from copy, and renumber all pages - * sequentially. - */ + /** Compacting copy: Omit free space from copy, and renumber all pages sequentially. */ MDB_CP_COMPACT(0x01); private final int mask; @@ -42,5 +31,4 @@ public enum CopyFlags implements MaskedFlag { public int getMask() { return mask; } - } diff --git a/src/main/java/org/lmdbjava/Cursor.java b/src/main/java/org/lmdbjava/Cursor.java index cb9130e3..d49a9bed 100644 --- a/src/main/java/org/lmdbjava/Cursor.java +++ b/src/main/java/org/lmdbjava/Cursor.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static java.util.Objects.requireNonNull; @@ -40,8 +35,6 @@ import jnr.ffi.Pointer; import jnr.ffi.byref.NativeLongByReference; -import java.util.Arrays; - /** * A cursor handle. * @@ -67,9 +60,8 @@ public final class Cursor implements AutoCloseable { /** * Close a cursor handle. * - *

- * The cursor handle will be freed and must not be used again after this call. - * Its transaction must still be live if it is a write-transaction. + *

The cursor handle will be freed and must not be used again after this call. Its transaction + * must still be live if it is a write-transaction. */ @Override public void close() { @@ -90,9 +82,8 @@ public void close() { /** * Return count of duplicates for current key. * - *

- * This call is only valid on databases that support sorted duplicate data - * items {@link DbiFlags#MDB_DUPSORT}. + *

This call is only valid on databases that support sorted duplicate data items {@link + * DbiFlags#MDB_DUPSORT}. * * @return count of duplicates for current key */ @@ -110,8 +101,7 @@ public long count() { /** * Delete current key/data pair. * - *

- * This function deletes the key/data pair to which the cursor refers. + *

This function deletes the key/data pair to which the cursor refers. * * @param f flags (either null or {@link PutFlags#MDB_NODUPDATA} */ @@ -138,9 +128,9 @@ public boolean first() { /** * Reposition the key/value buffers based on the passed key and operation. * - * @param key to search for + * @param key to search for * @param data to search for - * @param op options for this operation + * @param op options for this operation * @return false if key not found */ public boolean get(final T key, final T data, final SeekOp op) { @@ -154,8 +144,7 @@ public boolean get(final T key, final T data, final SeekOp op) { kv.keyIn(key); kv.valIn(data); - final int rc = LIB.mdb_cursor_get(ptrCursor, kv.pointerKey(), kv - .pointerVal(), op.getCode()); + final int rc = LIB.mdb_cursor_get(ptrCursor, kv.pointerKey(), kv.pointerVal(), op.getCode()); if (rc == MDB_NOTFOUND) { return false; @@ -172,7 +161,7 @@ public boolean get(final T key, final T data, final SeekOp op) { * Reposition the key/value buffers based on the passed key and operation. * * @param key to search for - * @param op options for this operation + * @param op options for this operation * @return false if key not found */ public boolean get(final T key, final GetOp op) { @@ -185,8 +174,7 @@ public boolean get(final T key, final GetOp op) { } kv.keyIn(key); - final int rc = LIB.mdb_cursor_get(ptrCursor, kv.pointerKey(), kv - .pointerVal(), op.getCode()); + final int rc = LIB.mdb_cursor_get(ptrCursor, kv.pointerKey(), kv.pointerVal(), op.getCode()); if (rc == MDB_NOTFOUND) { return false; @@ -238,14 +226,13 @@ public boolean prev() { /** * Store by cursor. * - *

- * This function stores key/data pairs into the database. + *

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 - * @return true if the value was put, false if MDB_NOOVERWRITE or - * MDB_NODUPDATA were set and the key/value existed already. + * @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) { if (SHOULD_CHECK) { @@ -259,8 +246,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 rc = LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), - kv.pointerVal(), mask); + final int rc = LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), mask); if (rc == MDB_KEYEXIST) { if (isSet(mask, MDB_NOOVERWRITE)) { kv.valOut(); // marked as in,out in LMDB C docs @@ -276,23 +262,19 @@ public boolean put(final T key, final T val, final PutFlags... op) { } /** - * Put multiple values into the database in one MDB_MULTIPLE - * operation. + * 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. + *

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 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 op options for operation (must set MDB_MULTIPLE) */ - 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 PutFlags... op) { if (SHOULD_CHECK) { requireNonNull(txn); requireNonNull(key); @@ -307,8 +289,7 @@ public void putMultiple(final T key, final T val, final int elements, } 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, mask); checkRc(rc); ReferenceUtil.reachabilityFence0(key); ReferenceUtil.reachabilityFence0(val); @@ -317,13 +298,10 @@ public void putMultiple(final T key, final T val, final int elements, /** * Renew a cursor handle. * - *

- * A cursor is associated with a specific transaction and database. Cursors - * that are only used in read-only transactions may be re-used, to avoid - * unnecessary malloc/free overhead. The cursor may be associated with a new - * read-only transaction, and referencing the same database handle as it was - * created with. This may be done whether the previous transaction is live or - * dead. + *

A cursor is associated with a specific transaction and database. Cursors that are only used + * in read-only transactions may be re-used, to avoid unnecessary malloc/free overhead. The cursor + * may be associated with a new read-only transaction, and referencing the same database handle as + * it was created with. This may be done whether the previous transaction is live or dead. * * @param newTxn transaction handle */ @@ -341,19 +319,17 @@ public void renew(final Txn newTxn) { } /** - * 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 + * 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 + *

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 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 op 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) { @@ -367,8 +343,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(); - checkRc(LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), - flags)); + checkRc(LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), flags)); kv.valOut(); ReferenceUtil.reachabilityFence0(key); return val(); @@ -388,8 +363,7 @@ public boolean seek(final SeekOp op) { txn.checkReady(); } - final int rc = LIB.mdb_cursor_get(ptrCursor, kv.pointerKey(), kv - .pointerVal(), op.getCode()); + final int rc = LIB.mdb_cursor_get(ptrCursor, kv.pointerKey(), kv.pointerVal(), op.getCode()); if (rc == MDB_NOTFOUND) { return false; @@ -416,24 +390,18 @@ private void checkNotClosed() { } } - /** - * Cursor has already been closed. - */ + /** Cursor has already been closed. */ public static final class ClosedException extends LmdbException { private static final long serialVersionUID = 1L; - /** - * Creates a new instance. - */ + /** Creates a new instance. */ public ClosedException() { super("Cursor has already been closed"); } } - /** - * Cursor stack too deep - internal error. - */ + /** Cursor stack too deep - internal error. */ public static final class FullException extends LmdbNativeException { static final int MDB_CURSOR_FULL = -30_787; @@ -443,5 +411,4 @@ public static final class FullException extends LmdbNativeException { super(MDB_CURSOR_FULL, "Cursor stack too deep - internal error"); } } - } diff --git a/src/main/java/org/lmdbjava/CursorIterable.java b/src/main/java/org/lmdbjava/CursorIterable.java index 39a6d58c..6a03bd90 100644 --- a/src/main/java/org/lmdbjava/CursorIterable.java +++ b/src/main/java/org/lmdbjava/CursorIterable.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static org.lmdbjava.CursorIterable.State.RELEASED; @@ -30,21 +25,18 @@ import java.util.Comparator; import java.util.Iterator; import java.util.NoSuchElementException; - import org.lmdbjava.KeyRangeType.CursorOp; import org.lmdbjava.KeyRangeType.IteratorOp; /** - * {@link Iterable} that creates a single {@link Iterator} that will iterate - * over a {@link Cursor} as specified by a {@link KeyRange}. + * {@link Iterable} that creates a single {@link Iterator} that will iterate over a {@link Cursor} + * as specified by a {@link KeyRange}. * - *

- * An instance will create and close its own cursor. + *

An instance will create and close its own cursor. * * @param buffer type */ -public final class CursorIterable implements - Iterable>, AutoCloseable { +public final class CursorIterable implements Iterable>, AutoCloseable { private final Comparator comparator; private final Cursor cursor; @@ -53,8 +45,8 @@ public final class CursorIterable implements private final KeyRange range; private State state = REQUIRES_INITIAL_OP; - CursorIterable(final Txn txn, final Dbi dbi, final KeyRange range, - final Comparator comparator) { + CursorIterable( + final Txn txn, final Dbi dbi, final KeyRange range, final Comparator comparator) { this.cursor = dbi.openCursor(txn); this.range = range; this.comparator = comparator; @@ -69,17 +61,14 @@ public void 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)}. + *

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 - @SuppressWarnings("checkstyle:AnonInnerLength") public Iterator> iterator() { if (iteratorReturned) { throw new IllegalStateException("Iterator can only be returned once"); @@ -111,7 +100,6 @@ public void remove() { }; } - @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NullAssignment"}) private void executeCursorOp(final CursorOp op) { final boolean found; switch (op) { @@ -141,9 +129,8 @@ 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(range.getStart(), range.getStop(), entry.key(), comparator); switch (op) { case CALL_NEXT_OP: executeCursorOp(range.getType().nextOp()); @@ -183,10 +170,9 @@ private void update() { /** * 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. + *

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 */ @@ -195,6 +181,9 @@ public static final class KeyVal { private T k; private T v; + /** Explicitly-defined default constructor to avoid warnings. */ + public KeyVal() {} + /** * The key. * @@ -220,15 +209,14 @@ void setK(final T key) { void setV(final T val) { this.v = val; } - } - /** - * Represents the internal {@link CursorIterable} state. - */ + /** Represents the internal {@link CursorIterable} state. */ enum State { - REQUIRES_INITIAL_OP, REQUIRES_NEXT_OP, REQUIRES_ITERATOR_OP, RELEASED, + 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 c5be4f85..ad1bb5a7 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -1,36 +1,20 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; -import jnr.ffi.Pointer; -import jnr.ffi.byref.IntByReference; -import jnr.ffi.byref.PointerByReference; -import org.lmdbjava.Library.ComparatorCallback; -import org.lmdbjava.Library.MDB_stat; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; - import static java.util.Objects.requireNonNull; import static jnr.ffi.Memory.allocateDirect; import static jnr.ffi.NativeType.ADDRESS; @@ -47,6 +31,16 @@ import static org.lmdbjava.PutFlags.MDB_RESERVE; import static org.lmdbjava.ResultCodeMapper.checkRc; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import jnr.ffi.Pointer; +import jnr.ffi.byref.IntByReference; +import jnr.ffi.byref.PointerByReference; +import org.lmdbjava.Library.ComparatorCallback; +import org.lmdbjava.Library.MDB_stat; + /** * LMDB Database. * @@ -61,16 +55,21 @@ public final class Dbi { private final byte[] name; private final Pointer ptr; - Dbi(final Env env, final Txn txn, final byte[] name, - final Comparator comparator, final boolean nativeCb, - final BufferProxy proxy, final DbiFlags... flags) { + Dbi( + final Env env, + final Txn txn, + final byte[] name, + final Comparator comparator, + final boolean nativeCb, + final BufferProxy proxy, + final DbiFlags... flags) { if (SHOULD_CHECK) { requireNonNull(txn); txn.checkReady(); } this.env = env; this.name = name == null ? null : Arrays.copyOf(name, name.length); - if(comparator == null) { + if (comparator == null) { this.comparator = proxy.getComparator(flags); } else { this.comparator = comparator; @@ -80,16 +79,17 @@ public final class Dbi { checkRc(LIB.mdb_dbi_open(txn.pointer(), name, flagsMask, dbiPtr)); ptr = dbiPtr.getPointer(0); if (nativeCb) { - this.ccb = (keyA, keyB) -> { - final T compKeyA = proxy.allocate(); - final T compKeyB = proxy.allocate(); - proxy.out(compKeyA, keyA, keyA.address()); - proxy.out(compKeyB, keyB, keyB.address()); - final int result = this.comparator.compare(compKeyA, compKeyB); - proxy.deallocate(compKeyA); - proxy.deallocate(compKeyB); - return result; - }; + this.ccb = + (keyA, keyB) -> { + final T compKeyA = proxy.allocate(); + final T compKeyB = proxy.allocate(); + proxy.out(compKeyA, keyA, keyA.address()); + proxy.out(compKeyB, keyB, keyB.address()); + final int result = this.comparator.compare(compKeyA, compKeyB); + proxy.deallocate(compKeyA); + proxy.deallocate(compKeyB); + return result; + }; LIB.mdb_set_compare(txn.pointer(), ptr, ccb); } else { ccb = null; @@ -99,13 +99,11 @@ public final class Dbi { /** * Close the database handle (normally unnecessary; use with caution). * - *

- * It is very rare that closing a database handle is useful. There are also - * many warnings/restrictions if closing a database handle (refer to the LMDB - * C documentation). As such this is non-routine usage and this class does not - * track the open/closed state of the {@link Dbi}. Advanced users are expected - * to have specific reasons for using this method and will manage their own - * state accordingly. + *

It is very rare that closing a database handle is useful. There are also many + * warnings/restrictions if closing a database handle (refer to the LMDB C documentation). As such + * this is non-routine usage and this class does not track the open/closed state of the {@link + * Dbi}. Advanced users are expected to have specific reasons for using this method and will + * manage their own state accordingly. */ public void close() { clean(); @@ -120,7 +118,6 @@ public void close() { * * @param key key to delete from the database (not null) * @return true if the key/data pair was found, false otherwise - * * @see #delete(org.lmdbjava.Txn, java.lang.Object, java.lang.Object) */ public boolean delete(final T key) { @@ -137,7 +134,6 @@ public boolean delete(final T key) { * @param txn transaction handle (not null; not committed; must be R-W) * @param key key to delete from the database (not null) * @return true if the key/data pair was found, false otherwise - * * @see #delete(org.lmdbjava.Txn, java.lang.Object, java.lang.Object) */ public boolean delete(final Txn txn, final T key) { @@ -147,12 +143,10 @@ public boolean delete(final Txn txn, final T key) { /** * Removes key/data pairs from the database. * - *

- * If the database does not support sorted duplicate data items - * ({@link DbiFlags#MDB_DUPSORT}) the value parameter is ignored. If the - * database supports sorted duplicates and the value parameter is null, all of - * the duplicate data items for the key will be deleted. Otherwise, if the - * data parameter is non-null only the matching data item will be deleted. + *

If the database does not support sorted duplicate data items ({@link DbiFlags#MDB_DUPSORT}) + * the value parameter is ignored. If the database supports sorted duplicates and the value + * parameter is null, all of the duplicate data items for the key will be deleted. Otherwise, if + * the data parameter is non-null only the matching data item will be deleted. * * @param txn transaction handle (not null; not committed; must be R-W) * @param key key to delete from the database (not null) @@ -187,10 +181,8 @@ public boolean delete(final Txn txn, final T key, final T val) { /** * Drops the data in this database, leaving the database open for further use. * - *

- * This method slightly differs from the LMDB C API in that it does not - * provide support for also closing the DB handle. If closing the DB handle is - * required, please see {@link #close()}. + *

This method slightly differs from the LMDB C API in that it does not provide support for + * also closing the DB handle. If closing the DB handle is required, please see {@link #close()}. * * @param txn transaction handle (not null; not committed; must be R-W) */ @@ -199,11 +191,11 @@ public void drop(final Txn txn) { } /** - * Drops the database. If delete is set to true, the database will be deleted - * and handle will be closed. See {@link #close()} for implication of handle - * close. Otherwise, only the data in this database will be dropped. + * Drops the database. If delete is set to true, the database will be deleted and handle will be + * 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) { @@ -223,12 +215,10 @@ public void drop(final Txn txn, final boolean delete) { /** * Get items from a database, moving the {@link Txn#val()} to the value. * - *

- * This function retrieves key/data pairs from the database. The address and - * length of the data associated with the specified \b key are returned in the - * structure to which \b data refers. If the database supports duplicate keys - * ({@link org.lmdbjava.DbiFlags#MDB_DUPSORT}) then the first data item for - * the key will be returned. Retrieval of other items requires the use of + *

This function retrieves key/data pairs from the database. The address and length of the data + * associated with the specified \b key are returned in the structure to which \b data refers. If + * the database supports duplicate keys ({@link org.lmdbjava.DbiFlags#MDB_DUPSORT}) then the first + * data item for the key will be returned. Retrieval of other items requires the use of * #mdb_cursor_get(). * * @param txn transaction handle (not null; not committed) @@ -243,8 +233,7 @@ public T get(final Txn txn, final T key) { txn.checkReady(); } txn.kv().keyIn(key); - final int rc = LIB.mdb_get(txn.pointer(), ptr, txn.kv().pointerKey(), txn - .kv().pointerVal()); + final int rc = LIB.mdb_get(txn.pointer(), ptr, txn.kv().pointerKey(), txn.kv().pointerVal()); if (rc == MDB_NOTFOUND) { return null; } @@ -275,7 +264,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) */ @@ -289,11 +278,11 @@ public CursorIterable iterate(final Txn txn, final KeyRange range) { return new CursorIterable<>(txn, this, range, comparator); } - /* - * Return DbiFlags for this Dbi. - * - * @param txn transaction handle (not null; not committed) - * @return the list of flags this Dbi was created with + /** + * Return DbiFlags for this Dbi. + * + * @param txn transaction handle (not null; not committed) + * @return the list of flags this Dbi was created with */ public List listFlags(final Txn txn) { if (SHOULD_CHECK) { @@ -318,15 +307,13 @@ public List listFlags(final Txn txn) { /** * Create a cursor handle. * - *

- * A cursor is associated with a specific transaction and database. A cursor - * cannot be used when its database handle is closed. Nor when its transaction - * has ended, except with {@link Cursor#renew(org.lmdbjava.Txn)}. It can be - * discarded with {@link Cursor#close()}. A cursor in a write-transaction can - * be closed before its transaction ends, and will otherwise be closed when - * its transaction ends. A cursor in a read-only transaction must be closed - * explicitly, before or after its transaction ends. It can be reused with - * {@link Cursor#renew(org.lmdbjava.Txn)} before finally closing it. + *

A cursor is associated with a specific transaction and database. A cursor cannot be used + * when its database handle is closed. Nor when its transaction has ended, except with {@link + * Cursor#renew(org.lmdbjava.Txn)}. It can be discarded with {@link Cursor#close()}. A cursor in a + * write-transaction can be closed before its transaction ends, and will otherwise be closed when + * its transaction ends. A cursor in a read-only transaction must be closed explicitly, before or + * after its transaction ends. It can be reused with {@link Cursor#renew(org.lmdbjava.Txn)} before + * finally closing it. * * @param txn transaction handle (not null; not committed) * @return cursor handle @@ -347,8 +334,7 @@ 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(org.lmdbjava.Txn, java.lang.Object, java.lang.Object, org.lmdbjava.PutFlags...) */ public void put(final T key, final T val) { try (Txn txn = env.txnWrite()) { @@ -360,21 +346,18 @@ public void put(final T key, final T val) { /** * 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}). + *

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 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. + * @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 PutFlags... flags) { if (SHOULD_CHECK) { requireNonNull(txn); requireNonNull(key); @@ -386,8 +369,8 @@ public boolean put(final Txn txn, final T key, final T val, txn.kv().keyIn(key); 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); + 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 @@ -403,24 +386,21 @@ public boolean put(final Txn txn, final T key, final T val, } /** - * 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 + * 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 + *

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) { + public T reserve(final Txn txn, final T key, final int size, final PutFlags... op) { if (SHOULD_CHECK) { requireNonNull(txn); requireNonNull(key); @@ -431,8 +411,7 @@ public T reserve(final Txn txn, final T key, final int size, txn.kv().keyIn(key); txn.kv().valIn(size); final int flags = mask(true, op) | MDB_RESERVE.getMask(); - checkRc(LIB.mdb_put(txn.pointer(), ptr, txn.kv().pointerKey(), txn.kv() - .pointerVal(), flags)); + 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); return txn.val(); @@ -468,9 +447,7 @@ private void clean() { cleaned = true; } - /** - * 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; @@ -481,23 +458,18 @@ 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; private static final long serialVersionUID = 1L; BadValueSizeException() { - super(MDB_BAD_VALSIZE, - "Unsupported size of key/DB name/data, or wrong DUPFIXED size"); + super(MDB_BAD_VALSIZE, "Unsupported size of key/DB name/data, or wrong DUPFIXED size"); } } - /** - * Environment maxdbs reached. - */ + /** Environment maxdbs reached. */ public static final class DbFullException extends LmdbNativeException { static final int MDB_DBS_FULL = -30_791; @@ -511,14 +483,13 @@ public static final class DbFullException extends LmdbNativeException { /** * Operation and DB incompatible, or DB type changed. * - *

- * This can mean: + *

This can mean: + * *

    - *
  • The operation expects an MDB_DUPSORT / MDB_DUPFIXED database.
  • - *
  • Opening a named DB when the unnamed DB has MDB_DUPSORT / - * MDB_INTEGERKEY.
  • - *
  • Accessing a data record as a database, or vice versa.
  • - *
  • The database was dropped and recreated with different flags.
  • + *
  • The operation expects an MDB_DUPSORT / MDB_DUPFIXED database. + *
  • Opening a named DB when the unnamed DB has MDB_DUPSORT / MDB_INTEGERKEY. + *
  • Accessing a data record as a database, or vice versa. + *
  • The database was dropped and recreated with different flags. *
*/ public static final class IncompatibleException extends LmdbNativeException { @@ -527,14 +498,11 @@ public static final class IncompatibleException extends LmdbNativeException { private static final long serialVersionUID = 1L; IncompatibleException() { - super(MDB_INCOMPATIBLE, - "Operation and DB incompatible, or DB type changed"); + super(MDB_INCOMPATIBLE, "Operation and DB incompatible, or DB type changed"); } } - /** - * Key/data pair already exists. - */ + /** Key/data pair already exists. */ public static final class KeyExistsException extends LmdbNativeException { static final int MDB_KEYEXIST = -30_799; @@ -545,9 +513,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; @@ -558,9 +524,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/DbiFlags.java b/src/main/java/org/lmdbjava/DbiFlags.java index 7277b55b..123ec9fd 100644 --- a/src/main/java/org/lmdbjava/DbiFlags.java +++ b/src/main/java/org/lmdbjava/DbiFlags.java @@ -1,96 +1,79 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; -/** - * Flags for use when opening a {@link Dbi}. - */ +/** Flags for use when opening a {@link Dbi}. */ public enum DbiFlags implements MaskedFlag { /** * Use reverse string keys. * - *

- * Keys are strings to be compared in reverse order, from the end of the - * strings to the beginning. By default, keys are treated as strings and - * compared from beginning to end. + *

Keys are strings to be compared in reverse order, from the end of the strings to the + * beginning. By default, keys are treated as strings and compared from beginning to end. */ MDB_REVERSEKEY(0x02), /** * 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. + *

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), /** - * 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. */ MDB_INTEGERKEY(0x08), /** * With {@link #MDB_DUPSORT}, sorted dup items have fixed size. * - *

- * This flag may only be used in combination with {@link #MDB_DUPSORT}. This - * option tells the library that the data items for this database are all the - * same size, which allows further optimizations in storage and retrieval. - * When all data items are the same size, the {@link SeekOp#MDB_GET_MULTIPLE} - * and {@link SeekOp#MDB_NEXT_MULTIPLE} cursor operations may be used to + *

This flag may only be used in combination with {@link #MDB_DUPSORT}. This option tells the + * library that the data items for this database are all the same size, which allows further + * optimizations in storage and retrieval. When all data items are the same size, the {@link + * SeekOp#MDB_GET_MULTIPLE} and {@link SeekOp#MDB_NEXT_MULTIPLE} cursor operations may be used to * retrieve multiple items at once. */ MDB_DUPFIXED(0x10), /** * With {@link #MDB_DUPSORT}, dups are {@link #MDB_INTEGERKEY}-style integers. * - *

- * This option specifies that duplicate data items are binary integers, - * similar to {@link #MDB_INTEGERKEY} keys. + *

This option specifies that duplicate data items are binary integers, similar to {@link + * #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. - *

+ *

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. * - *

- * This option specifies that duplicate data items should be compared as - * strings in reverse order. + *

This option specifies that duplicate data items should be compared as strings in reverse + * order. */ MDB_REVERSEDUP(0x40), /** * Create the named database if it doesn't exist. * - *

- * This option is not allowed in a read-only transaction or a read-only - * environment. + *

This option is not allowed in a read-only transaction or a read-only environment. */ MDB_CREATE(0x4_0000); diff --git a/src/main/java/org/lmdbjava/DirectBufferProxy.java b/src/main/java/org/lmdbjava/DirectBufferProxy.java index 3bde81ec..156e60e9 100644 --- a/src/main/java/org/lmdbjava/DirectBufferProxy.java +++ b/src/main/java/org/lmdbjava/DirectBufferProxy.java @@ -1,73 +1,64 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; -import jnr.ffi.Pointer; -import org.agrona.DirectBuffer; -import org.agrona.MutableDirectBuffer; -import org.agrona.concurrent.UnsafeBuffer; - -import java.nio.ByteBuffer; -import java.util.ArrayDeque; -import java.util.Comparator; - import static java.lang.ThreadLocal.withInitial; import static java.nio.ByteBuffer.allocateDirect; import static java.nio.ByteOrder.BIG_ENDIAN; import static java.util.Objects.requireNonNull; import static org.lmdbjava.UnsafeAccess.UNSAFE; +import java.nio.ByteBuffer; +import java.util.ArrayDeque; +import java.util.Comparator; +import jnr.ffi.Pointer; +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; + /** * A buffer proxy backed by Agrona's {@link DirectBuffer}. * - *

- * This class requires {@link UnsafeAccess} and Agrona must be in the classpath. + *

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); + private static final Comparator signedComparator = + (o1, o2) -> { + requireNonNull(o1); + requireNonNull(o2); - return o1.compareTo(o2); - }; + return o1.compareTo(o2); + }; private static final Comparator unsignedComparator = DirectBufferProxy::compareBuff; /** - * The {@link MutableDirectBuffer} proxy. Guaranteed to never be null, - * although a class initialization exception will occur if an attempt is made - * to access this field when unsafe or Agrona is unavailable. + * The {@link MutableDirectBuffer} proxy. Guaranteed to never be null, although a class + * initialization exception will occur if an attempt is made to access this field when unsafe or + * Agrona is unavailable. */ - public static final BufferProxy PROXY_DB - = new DirectBufferProxy(); + public static final BufferProxy PROXY_DB = new DirectBufferProxy(); /** - * 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. + * 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. */ - private static final ThreadLocal> BUFFERS - = withInitial(() -> new ArrayDeque<>(16)); + private static final ThreadLocal> BUFFERS = + withInitial(() -> new ArrayDeque<>(16)); - private DirectBufferProxy() { - } + private DirectBufferProxy() {} /** * Lexicographically compare two buffers. @@ -143,8 +134,7 @@ protected byte[] getBytes(final DirectBuffer buffer) { } @Override - protected void in(final DirectBuffer buffer, final Pointer ptr, - final long ptrAddr) { + protected void in(final DirectBuffer buffer, final Pointer ptr, final long ptrAddr) { final long addr = buffer.addressOffset(); final long size = buffer.capacity(); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA, addr); @@ -152,20 +142,18 @@ protected void in(final DirectBuffer buffer, final Pointer ptr, } @Override - protected void in(final DirectBuffer buffer, final int size, final Pointer ptr, - final long ptrAddr) { + protected void in( + final DirectBuffer buffer, final int size, final Pointer ptr, final long ptrAddr) { final long addr = buffer.addressOffset(); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA, addr); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, size); } @Override - protected DirectBuffer out(final DirectBuffer buffer, final Pointer ptr, - final long ptrAddr) { + protected DirectBuffer out(final DirectBuffer buffer, final Pointer ptr, final long ptrAddr) { final long addr = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA); final long size = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE); buffer.wrap(addr, (int) size); return buffer; } - } diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index ae7e46c9..3db16119 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -1,38 +1,20 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; -import jnr.ffi.Pointer; -import jnr.ffi.byref.IntByReference; -import jnr.ffi.byref.PointerByReference; -import org.lmdbjava.Library.MDB_envinfo; -import org.lmdbjava.Library.MDB_stat; - -import java.io.File; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - import static java.lang.Boolean.getBoolean; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; @@ -46,24 +28,32 @@ import static org.lmdbjava.ResultCodeMapper.checkRc; import static org.lmdbjava.TxnFlags.MDB_RDONLY_TXN; +import java.io.File; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import jnr.ffi.Pointer; +import jnr.ffi.byref.IntByReference; +import jnr.ffi.byref.PointerByReference; +import org.lmdbjava.Library.MDB_envinfo; +import org.lmdbjava.Library.MDB_stat; + /** * LMDB environment. * * @param buffer type */ -@SuppressWarnings("PMD.GodClass") 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"; /** - * 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 checks performed at all times, regardless - * of this property. + * 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 + * checks performed at all times, regardless of this property. */ public static final boolean SHOULD_CHECK = !getBoolean(DISABLE_CHECKS_PROP); @@ -74,8 +64,11 @@ public final class Env implements AutoCloseable { private final Pointer ptr; private final boolean readOnly; - private Env(final BufferProxy proxy, final Pointer ptr, - final boolean readOnly, final boolean noSubDir) { + private Env( + final BufferProxy proxy, + final Pointer ptr, + final boolean readOnly, + final boolean noSubDir) { this.proxy = proxy; this.readOnly = readOnly; this.noSubDir = noSubDir; @@ -96,7 +89,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) */ @@ -105,26 +98,22 @@ public static Builder create(final BufferProxy proxy) { } /** - * 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}. * - * @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); + 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); } /** * Close the handle. * - *

- * Will silently return if already closed or never opened. + *

Will silently return if already closed or never opened. */ @Override public void close() { @@ -138,22 +127,19 @@ public void close() { /** * 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. + *

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. + *

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. + *

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 + * @param path writable destination path as described above * @param flags special options for this copy */ public void copy(final File path, final CopyFlags... flags) { @@ -166,13 +152,11 @@ public void copy(final File path, final CopyFlags... flags) { /** * Obtain the DBI names. * - *

- * This method is only compatible with {@link Env}s that use named databases. - * If an unnamed {@link Dbi} is being used to store data, this method will - * attempt to return all such keys from the unnamed database. + *

This method is only compatible with {@link Env}s that use named databases. If an unnamed + * {@link Dbi} is being used to store data, this method will attempt to return all such keys from + * the unnamed database. * - *

- * This method must not be called from concurrent threads. + *

This method must not be called from concurrent threads. * * @return a list of DBI names (never null) */ @@ -180,7 +164,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(); } @@ -249,8 +233,7 @@ public boolean isClosed() { } /** - * Indicates if this environment was opened with - * {@link EnvFlags#MDB_RDONLY_ENV}. + * Indicates if this environment was opened with {@link EnvFlags#MDB_RDONLY_ENV}. * * @return true if read-only */ @@ -259,10 +242,10 @@ 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. + * 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 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 */ @@ -272,42 +255,44 @@ 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. + * 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 name name of the database (or null if no name is required) * @param comparator custom comparator callback (or null to use default) - * @param flags to open the database with + * @param flags to open the database with * @return a database that is ready to use */ - 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); } /** - * 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. + * 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 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 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 */ - 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); } /** - * Convenience method that opens a {@link Dbi} with a default - * {@link Comparator} that is not invoked from native code. + * 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 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 */ @@ -316,36 +301,37 @@ public Dbi openDbi(final byte[] name, final DbiFlags... flags) { } /** - * Convenience method that opens a {@link Dbi} with an associated - * {@link Comparator} that is not invoked from native code. + * 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 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 */ - 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); } /** - * Convenience method that opens a {@link Dbi} with an associated - * {@link Comparator} that may be invoked from native code if specified. + * 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. + *

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 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 */ - public Dbi openDbi(final byte[] name, final Comparator comparator, - final boolean nativeCb, final DbiFlags... flags) { + public Dbi openDbi( + final byte[] name, + final Comparator comparator, + final boolean nativeCb, + final DbiFlags... flags) { try (Txn txn = readOnly ? txnRead() : txnWrite()) { final Dbi dbi = openDbi(txn, name, comparator, nativeCb, flags); txn.commit(); // even RO Txns require a commit to retain Dbi in Env @@ -356,38 +342,36 @@ public Dbi openDbi(final byte[] name, final Comparator comparator, /** * 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) + *

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 flags to open the database with + * @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 */ - public Dbi openDbi(final Txn txn, final byte[] name, - final Comparator comparator, final boolean nativeCb, - final DbiFlags... flags) { + 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); } @@ -414,9 +398,8 @@ 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) + * @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) */ public void sync(final boolean force) { if (closed) { @@ -430,7 +413,7 @@ 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) + * @param flags applicable flags (eg for a reusable, read-only transaction) * @return a transaction (never null) */ public Txn txn(final Txn parent, final TxnFlags... flags) { @@ -491,46 +474,39 @@ private void validatePath(final File path) { validateDirectoryEmpty(path); } - - /* Check for stale entries in the reader lock table. */ + /** + * Check for stale entries in the reader lock table. + * + * @return 0 on success, non-zero on failure + */ public int readerCheck() { final IntByReference resultPtr = new IntByReference(); checkRc(LIB.mdb_reader_check(ptr, resultPtr)); 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. * @@ -553,14 +529,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 */ - @SuppressWarnings("PMD.AccessorClassGeneration") - public Env open(final File path, final int mode, - final EnvFlags... flags) { + public Env open(final File path, final int mode, final EnvFlags... flags) { requireNonNull(path); if (opened) { throw new AlreadyOpenException(); @@ -587,16 +561,14 @@ public Env open(final File path, final int mode, /** * 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 */ - @SuppressWarnings("PMD.AvoidUsingOctalValues") public Env open(final File path, final EnvFlags... flags) { return open(path, 0664, flags); } - /** * Sets the map size. * @@ -643,9 +615,7 @@ 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; @@ -656,9 +626,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; @@ -673,9 +641,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; @@ -686,9 +652,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; @@ -699,9 +663,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; @@ -711,5 +673,4 @@ public static final class VersionMismatchException extends LmdbNativeException { super(MDB_VERSION_MISMATCH, "Environment version mismatch"); } } - } diff --git a/src/main/java/org/lmdbjava/EnvFlags.java b/src/main/java/org/lmdbjava/EnvFlags.java index ab54917a..4ce555a8 100644 --- a/src/main/java/org/lmdbjava/EnvFlags.java +++ b/src/main/java/org/lmdbjava/EnvFlags.java @@ -1,169 +1,136 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; -/** - * Flags for use when opening the {@link Env}. - */ +/** Flags for use when opening the {@link Env}. */ public enum EnvFlags implements MaskedFlag { /** * Mmap at a fixed address (experimental). * - *

- * Use a fixed address for the mmap region. This flag must be specified when - * creating the environment, and is stored persistently in the environment. If - * successful, the memory map will always reside at the same virtual address - * and pointers used to reference data items in the database will be constant - * across multiple invocations. This option may not always work, depending on - * how the operating system has allocated memory to shared libraries and other - * uses. The feature is highly experimental. + *

Use a fixed address for the mmap region. This flag must be specified when creating the + * environment, and is stored persistently in the environment. If successful, the memory map will + * always reside at the same virtual address and pointers used to reference data items in the + * database will be constant across multiple invocations. This option may not always work, + * depending on how the operating system has allocated memory to shared libraries and other uses. + * The feature is highly experimental. */ MDB_FIXEDMAP(0x01), /** * No environment directory. * - *

- * By default, LMDB creates its environment in a directory whose pathname is - * given in path, and creates its data and lock files under that directory. - * With this option, path is used as-is for the database main data file. The - * database lock file is the path with "-lock" appended. + *

By default, LMDB creates its environment in a directory whose pathname is given in path, and + * creates its data and lock files under that directory. With this option, path is used as-is for + * the database main data file. The database lock file is the path with "-lock" appended. */ MDB_NOSUBDIR(0x4000), /** * Open the environment in read-only mode. * - *

- * No write operations will be allowed. LMDB will still modify the lock file - - * except on read-only filesystems, where LMDB does not use locks. + *

No write operations will be allowed. LMDB will still modify the lock file - except on + * read-only filesystems, where LMDB does not use locks. */ MDB_RDONLY_ENV(0x2_0000), /** * Use a writeable memory map unless {@link #MDB_RDONLY_ENV} is set. * - *

- * This is faster and uses fewer mallocs, but loses protection from - * application bugs like wild pointer writes and other bad updates into the - * database. Incompatible with nested transactions. Do not mix processes with - * and without {@link #MDB_WRITEMAP} on the same environment. This can defeat - * durability ({@link Env#sync(boolean)} etc). + *

This is faster and uses fewer mallocs, but loses protection from application bugs like wild + * pointer writes and other bad updates into the database. Incompatible with nested transactions. + * Do not mix processes with and without {@link #MDB_WRITEMAP} on the same environment. This can + * defeat durability ({@link Env#sync(boolean)} etc). */ MDB_WRITEMAP(0x8_0000), /** * Don't fsync metapage after commit. * - *

- * Flush system buffers to disk only once per transaction, omit the metadata - * flush. Defer that until the system flushes files to disk, or next - * non-{@link #MDB_RDONLY_ENV} commit or {@link Env#sync(boolean)}. This - * optimization* maintains database integrity, but a system crash may undo the - * last* committed transaction. I.e. it preserves the ACI (atomicity, - * consistency, isolation) but not D (durability) database property. + *

Flush system buffers to disk only once per transaction, omit the metadata flush. Defer that + * until the system flushes files to disk, or next non-{@link #MDB_RDONLY_ENV} commit or {@link + * Env#sync(boolean)}. This optimization* maintains database integrity, but a system crash may + * undo the last* committed transaction. I.e. it preserves the ACI (atomicity, consistency, + * isolation) but not D (durability) database property. */ MDB_NOMETASYNC(0x4_0000), /** * Don't fsync after commit. * - *

- * Don't flush system buffers to disk when committing a transaction. This - * optimization means a system crash can corrupt the database or lose the last - * transactions if buffers are not yet flushed to disk. The risk is governed - * by how often the system flushes dirty buffers to disk and how often - * {@link Env#sync(boolean)} is called. However, if the filesystem preserves - * write order and the {@link #MDB_WRITEMAP} flag is not used, transactions - * exhibit ACI (atomicity, consistency, isolation) properties and only lose D - * (durability). I.e. database integrity is maintained, but a system crash may - * undo the final transactions. Note that - * ({@link #MDB_NOSYNC} | {@link #MDB_WRITEMAP}) leaves the system with no - * hint for when to write transactions to disk, unless - * {@link Env#sync(boolean)} is called. - * ({@link #MDB_MAPASYNC} | {@link #MDB_WRITEMAP}) may be preferable. + *

Don't flush system buffers to disk when committing a transaction. This optimization means a + * system crash can corrupt the database or lose the last transactions if buffers are not yet + * flushed to disk. The risk is governed by how often the system flushes dirty buffers to disk and + * how often {@link Env#sync(boolean)} is called. However, if the filesystem preserves write order + * and the {@link #MDB_WRITEMAP} flag is not used, transactions exhibit ACI (atomicity, + * consistency, isolation) properties and only lose D (durability). I.e. database integrity is + * maintained, but a system crash may undo the final transactions. Note that ({@link #MDB_NOSYNC} + * | {@link #MDB_WRITEMAP}) leaves the system with no hint for when to write transactions to disk, + * unless {@link Env#sync(boolean)} is called. ({@link #MDB_MAPASYNC} | {@link #MDB_WRITEMAP}) may + * be preferable. */ MDB_NOSYNC(0x1_0000), /** * Use asynchronous msync when {@link #MDB_WRITEMAP} is used. * - *

- * When using {@link #MDB_WRITEMAP}, use asynchronous flushes to disk. - * As with {@link #MDB_NOSYNC}, a system crash can then corrupt the database - * or lose the last transactions. Calling {@link Env#sync(boolean)} ensures - * on-disk database integrity until next commit. + *

When using {@link #MDB_WRITEMAP}, use asynchronous flushes to disk. As with {@link + * #MDB_NOSYNC}, a system crash can then corrupt the database or lose the last transactions. + * Calling {@link Env#sync(boolean)} ensures on-disk database integrity until next commit. */ MDB_MAPASYNC(0x10_0000), /** * Tie reader locktable slots to {@link Txn} objects instead of to threads. * - *

- * Don't use Thread-Local Storage. Tie reader locktable slots to {@link Txn} - * objects instead of to threads. I.e. {@link Txn#reset()} keeps the slot - * reseved for the {@link Txn} object. A thread may use parallel read-only - * transactions. A read-only transaction may span threads if the user - * synchronizes its use. Applications that multiplex many user threads over - * individual OS threads need this option. Such an application must also - * serialize the write transactions in an OS thread, since LMDB's write - * locking is unaware of the user threads. + *

Don't use Thread-Local Storage. Tie reader locktable slots to {@link Txn} objects instead of + * to threads. I.e. {@link Txn#reset()} keeps the slot reseved for the {@link Txn} object. A + * thread may use parallel read-only transactions. A read-only transaction may span threads if the + * user synchronizes its use. Applications that multiplex many user threads over individual OS + * threads need this option. Such an application must also serialize the write transactions in an + * OS thread, since LMDB's write locking is unaware of the user threads. */ MDB_NOTLS(0x20_0000), /** * Don't do any locking, caller must manage their own locks. * - *

- * Don't do any locking. If concurrent access is anticipated, the caller must - * manage all concurrency itself. For proper operation the caller must enforce - * single-writer semantics, and must ensure that no readers are using old - * transactions while a writer is active. The simplest approach is to use an - * exclusive lock so that no readers may be active at all when a writer + *

Don't do any locking. If concurrent access is anticipated, the caller must manage all + * concurrency itself. For proper operation the caller must enforce single-writer semantics, and + * must ensure that no readers are using old transactions while a writer is active. The simplest + * approach is to use an exclusive lock so that no readers may be active at all when a writer * begins. */ MDB_NOLOCK(0x40_0000), /** * Don't do readahead (no effect on Windows). * - *

- * Turn off readahead. Most operating systems perform readahead on read - * requests by default. This option turns it off if the OS supports it. - * Turning it off may help random read performance when the DB is larger than - * RAM and system RAM is full. The option is not implemented on Windows. + *

Turn off readahead. Most operating systems perform readahead on read requests by default. + * This option turns it off if the OS supports it. Turning it off may help random read performance + * when the DB is larger than RAM and system RAM is full. The option is not implemented on + * Windows. */ MDB_NORDAHEAD(0x80_0000), /** * Don't initialize malloc'd memory before writing to datafile. * - *

- * Don't initialize malloc'd memory before writing to unused spaces in the - * data file. By default, memory for pages written to the data file is - * obtained using malloc. While these pages may be reused in subsequent - * transactions, freshly malloc'd pages will be initialized to zeroes before - * use. This avoids persisting leftover data from other code (that used the - * heap and subsequently freed the memory) into the data file. Note that many - * other system libraries may allocate and free memory from the heap for - * arbitrary uses. E.g., stdio may use the heap for file I/O buffers. This - * initialization step has a modest performance cost so some applications may - * want to disable it using this flag. This option can be a problem for - * applications which handle sensitive data like passwords, and it makes - * memory checkers like Valgrind noisy. This flag is not needed with - * {@link #MDB_WRITEMAP}, which writes directly to the mmap instead of using - * malloc for pages. The initialization is also skipped if - * {@link PutFlags#MDB_RESERVE} is used; the caller is expected to overwrite - * all of the memory that was reserved in that case. + *

Don't initialize malloc'd memory before writing to unused spaces in the data file. By + * default, memory for pages written to the data file is obtained using malloc. While these pages + * may be reused in subsequent transactions, freshly malloc'd pages will be initialized to zeroes + * before use. This avoids persisting leftover data from other code (that used the heap and + * subsequently freed the memory) into the data file. Note that many other system libraries may + * allocate and free memory from the heap for arbitrary uses. E.g., stdio may use the heap for + * file I/O buffers. This initialization step has a modest performance cost so some applications + * may want to disable it using this flag. This option can be a problem for applications which + * handle sensitive data like passwords, and it makes memory checkers like Valgrind noisy. This + * flag is not needed with {@link #MDB_WRITEMAP}, which writes directly to the mmap instead of + * using malloc for pages. The initialization is also skipped if {@link PutFlags#MDB_RESERVE} is + * used; the caller is expected to overwrite all of the memory that was reserved in that case. */ MDB_NOMEMINIT(0x100_0000); @@ -177,5 +144,4 @@ public enum EnvFlags implements MaskedFlag { public int getMask() { return mask; } - } diff --git a/src/main/java/org/lmdbjava/EnvInfo.java b/src/main/java/org/lmdbjava/EnvInfo.java index a1ae62ba..71169d90 100644 --- a/src/main/java/org/lmdbjava/EnvInfo.java +++ b/src/main/java/org/lmdbjava/EnvInfo.java @@ -1,63 +1,48 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; -/** - * Environment information, as returned by {@link Env#info()}. - */ +/** Environment information, as returned by {@link Env#info()}. */ public final class EnvInfo { - /** - * ID of the last used page. - */ + /** ID of the last used page. */ public final long lastPageNumber; - /** - * ID of the last committed transaction. - */ + /** ID of the last committed transaction. */ public final long lastTransactionId; - /** - * Address of map, if fixed. - */ + /** Address of map, if fixed. */ public final long mapAddress; - /** - * Size of the data memory map. - */ + /** Size of the data memory map. */ public final long mapSize; - /** - * Max reader slots in the environment. - */ + /** Max reader slots in the environment. */ public final int maxReaders; - /** - * Max reader slots used in the environment. - */ + /** Max reader slots used in the environment. */ public final int numReaders; - EnvInfo(final long mapAddress, final long mapSize, final long lastPageNumber, - final long lastTransactionId, final int maxReaders, - final int numReaders) { + EnvInfo( + final long mapAddress, + final long mapSize, + final long lastPageNumber, + final long lastTransactionId, + final int maxReaders, + final int numReaders) { this.mapAddress = mapAddress; this.mapSize = mapSize; this.lastPageNumber = lastPageNumber; @@ -68,10 +53,19 @@ public final class EnvInfo { @Override public String toString() { - return "EnvInfo{" + "lastPageNumber=" + lastPageNumber - + ", lastTransactionId=" + lastTransactionId + ", mapAddress=" - + mapAddress + ", mapSize=" + mapSize + ", maxReaders=" - + maxReaders + ", numReaders=" + numReaders + '}'; + return "EnvInfo{" + + "lastPageNumber=" + + lastPageNumber + + ", lastTransactionId=" + + lastTransactionId + + ", mapAddress=" + + mapAddress + + ", mapSize=" + + mapSize + + ", maxReaders=" + + maxReaders + + ", numReaders=" + + numReaders + + '}'; } - } diff --git a/src/main/java/org/lmdbjava/GetOp.java b/src/main/java/org/lmdbjava/GetOp.java index 9d36c204..666f692c 100644 --- a/src/main/java/org/lmdbjava/GetOp.java +++ b/src/main/java/org/lmdbjava/GetOp.java @@ -1,45 +1,32 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; /** - * Flags for use when performing a - * {@link Cursor#get(java.lang.Object, org.lmdbjava.GetOp)}. + * Flags for use when performing a {@link Cursor#get(java.lang.Object, org.lmdbjava.GetOp)}. * - *

- * Unlike most other LMDB enums, this enum is not bit masked. + *

Unlike most other LMDB enums, this enum is not bit masked. */ public enum GetOp { - /** - * Position at specified key. - */ + /** Position at specified key. */ MDB_SET(15), - /** - * Position at specified key, return key + data. - */ + /** Position at specified key, return key + data. */ MDB_SET_KEY(16), - /** - * Position at first key greater than or equal to specified key. - */ + /** Position at first key greater than or equal to specified key. */ MDB_SET_RANGE(17); private final int code; @@ -56,5 +43,4 @@ public enum GetOp { public int getCode() { return code; } - } diff --git a/src/main/java/org/lmdbjava/KeyRange.java b/src/main/java/org/lmdbjava/KeyRange.java index d47c444c..553346e6 100644 --- a/src/main/java/org/lmdbjava/KeyRange.java +++ b/src/main/java/org/lmdbjava/KeyRange.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static java.util.Objects.requireNonNull; @@ -27,8 +22,7 @@ /** * Limits the range and direction of keys to iterate. * - *

- * Immutable once created (although the buffers themselves may not be). + *

Immutable once created (although the buffers themselves may not be). * * @param buffer type */ @@ -43,13 +37,12 @@ public final class KeyRange { /** * Construct a key range. * - *

- * End user code may find it more expressive to use one of the static methods - * provided on this class. + *

End user code may find it more expressive to use one of the static methods provided on this + * class. * - * @param type key type + * @param type key type * @param start start key (required if applicable for the passed range type) - * @param stop stop key (required if applicable for the passed range type) + * @param stop stop key (required if applicable for the passed range type) */ public KeyRange(final KeyRangeType type, final T start, final T stop) { requireNonNull(type, "Key range type is required"); @@ -87,7 +80,7 @@ public static KeyRange allBackward() { /** * Create a {@link KeyRangeType#FORWARD_AT_LEAST} range. * - * @param buffer type + * @param buffer type * @param start start key (required) * @return a key range (never null) */ @@ -98,7 +91,7 @@ public static KeyRange atLeast(final T start) { /** * Create a {@link KeyRangeType#BACKWARD_AT_LEAST} range. * - * @param buffer type + * @param buffer type * @param start start key (required) * @return a key range (never null) */ @@ -109,7 +102,7 @@ public static KeyRange atLeastBackward(final T start) { /** * Create a {@link KeyRangeType#FORWARD_AT_MOST} range. * - * @param buffer type + * @param buffer type * @param stop stop key (required) * @return a key range (never null) */ @@ -120,7 +113,7 @@ public static KeyRange atMost(final T stop) { /** * Create a {@link KeyRangeType#BACKWARD_AT_MOST} range. * - * @param buffer type + * @param buffer type * @param stop stop key (required) * @return a key range (never null) */ @@ -131,9 +124,9 @@ public static KeyRange atMostBackward(final T stop) { /** * Create a {@link KeyRangeType#FORWARD_CLOSED} range. * - * @param buffer type + * @param buffer type * @param start start key (required) - * @param stop stop key (required) + * @param stop stop key (required) * @return a key range (never null) */ public static KeyRange closed(final T start, final T stop) { @@ -143,9 +136,9 @@ public static KeyRange closed(final T start, final T stop) { /** * Create a {@link KeyRangeType#BACKWARD_CLOSED} range. * - * @param buffer type + * @param buffer type * @param start start key (required) - * @param stop stop key (required) + * @param stop stop key (required) * @return a key range (never null) */ public static KeyRange closedBackward(final T start, final T stop) { @@ -155,9 +148,9 @@ public static KeyRange closedBackward(final T start, final T stop) { /** * Create a {@link KeyRangeType#FORWARD_CLOSED_OPEN} range. * - * @param buffer type + * @param buffer type * @param start start key (required) - * @param stop stop key (required) + * @param stop stop key (required) * @return a key range (never null) */ public static KeyRange closedOpen(final T start, final T stop) { @@ -167,9 +160,9 @@ public static KeyRange closedOpen(final T start, final T stop) { /** * Create a {@link KeyRangeType#BACKWARD_CLOSED_OPEN} range. * - * @param buffer type + * @param buffer type * @param start start key (required) - * @param stop stop key (required) + * @param stop stop key (required) * @return a key range (never null) */ public static KeyRange closedOpenBackward(final T start, final T stop) { @@ -179,7 +172,7 @@ public static KeyRange closedOpenBackward(final T start, final T stop) { /** * Create a {@link KeyRangeType#FORWARD_GREATER_THAN} range. * - * @param buffer type + * @param buffer type * @param start start key (required) * @return a key range (never null) */ @@ -190,7 +183,7 @@ public static KeyRange greaterThan(final T start) { /** * Create a {@link KeyRangeType#BACKWARD_GREATER_THAN} range. * - * @param buffer type + * @param buffer type * @param start start key (required) * @return a key range (never null) */ @@ -201,7 +194,7 @@ public static KeyRange greaterThanBackward(final T start) { /** * Create a {@link KeyRangeType#FORWARD_LESS_THAN} range. * - * @param buffer type + * @param buffer type * @param stop stop key (required) * @return a key range (never null) */ @@ -212,7 +205,7 @@ public static KeyRange lessThan(final T stop) { /** * Create a {@link KeyRangeType#BACKWARD_LESS_THAN} range. * - * @param buffer type + * @param buffer type * @param stop stop key (required) * @return a key range (never null) */ @@ -223,9 +216,9 @@ public static KeyRange lessThanBackward(final T stop) { /** * Create a {@link KeyRangeType#FORWARD_OPEN} range. * - * @param buffer type + * @param buffer type * @param start start key (required) - * @param stop stop key (required) + * @param stop stop key (required) * @return a key range (never null) */ public static KeyRange open(final T start, final T stop) { @@ -235,9 +228,9 @@ public static KeyRange open(final T start, final T stop) { /** * Create a {@link KeyRangeType#BACKWARD_OPEN} range. * - * @param buffer type + * @param buffer type * @param start start key (required) - * @param stop stop key (required) + * @param stop stop key (required) * @return a key range (never null) */ public static KeyRange openBackward(final T start, final T stop) { @@ -247,9 +240,9 @@ public static KeyRange openBackward(final T start, final T stop) { /** * Create a {@link KeyRangeType#FORWARD_OPEN_CLOSED} range. * - * @param buffer type + * @param buffer type * @param start start key (required) - * @param stop stop key (required) + * @param stop stop key (required) * @return a key range (never null) */ public static KeyRange openClosed(final T start, final T stop) { @@ -259,9 +252,9 @@ public static KeyRange openClosed(final T start, final T stop) { /** * Create a {@link KeyRangeType#BACKWARD_OPEN_CLOSED} range. * - * @param buffer type + * @param buffer type * @param start start key (required) - * @param stop stop key (required) + * @param stop stop key (required) * @return a key range (never null) */ public static KeyRange openClosedBackward(final T start, final T stop) { @@ -294,5 +287,4 @@ public T getStop() { public KeyRangeType getType() { return type; } - } diff --git a/src/main/java/org/lmdbjava/KeyRangeType.java b/src/main/java/org/lmdbjava/KeyRangeType.java index 267e45c8..ad67286d 100644 --- a/src/main/java/org/lmdbjava/KeyRangeType.java +++ b/src/main/java/org/lmdbjava/KeyRangeType.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static java.util.Objects.requireNonNull; @@ -36,241 +31,195 @@ /** * Key range type. * - *

- * The terminology used in this class is adapted from Google Guava's ranges. - * Refer to the - * Ranges Explained wiki page for more information. LmddJava prepends either - * "FORWARD" or "BACKWARD" to denote the iterator order. + *

The terminology used in this class is adapted from Google Guava's ranges. Refer to the Ranges Explained wiki page for + * more information. LmddJava prepends either "FORWARD" or "BACKWARD" to denote the iterator order. * - *

- * In the examples below, it is assumed the table has keys 2, 4, 6 and 8. + *

In the examples below, it is assumed the table has keys 2, 4, 6 and 8. */ -@SuppressWarnings("PMD.CyclomaticComplexity") public enum KeyRangeType { /** * Starting on the first key and iterate forward until no keys remain. * - *

- * The "start" and "stop" values are ignored. + *

The "start" and "stop" values are ignored. * - *

- * In our example, the returned keys would be 2, 4, 6 and 8. + *

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

- * The "start" value is required. The "stop" value is ignored. + *

The "start" value is required. The "stop" value is ignored. * - *

- * 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. + *

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), /** - * Start on the first key and iterate forward until a key equal to it (or the - * first key immediately after it) is reached. + * Start on the first key and iterate forward until a key equal to it (or the first key + * immediately after it) is reached. * - *

- * The "stop" value is required. The "start" value is ignored. + *

The "stop" value is required. The "start" value is ignored. * - *

- * 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. + *

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), /** - * 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 preceding it in the case of the "stop" - * key). + * 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 + * preceding it in the case of the "stop" key). * - *

- * The "start" and "stop" values are both required. + *

The "start" and "stop" values are both required. * - *

- * 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. + *

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), /** - * 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 preceding it in the case of the "stop" - * key). Do not return the "stop" key. + * 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 + * preceding it in the case of the "stop" key). Do not return the "stop" key. * - *

- * The "start" and "stop" values are both required. + *

The "start" and "stop" values are both required. * - *

- * 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. + *

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), /** - * Start after the passed key (but not equal to it) and iterate forward until - * no keys remain. + * Start after the passed key (but not equal to it) and iterate forward until no keys remain. * - *

- * The "start" value is required. The "stop" value is ignored. + *

The "start" value is required. The "stop" value is ignored. * - *

- * 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. + *

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), /** - * Start on the first key and iterate forward until a key the passed key has - * been reached (but do not return that key). + * Start on the first key and iterate forward until a key the passed key has been reached (but do + * not return that key). * - *

- * The "stop" value is required. The "start" value is ignored. + *

The "stop" value is required. The "start" value is ignored. * - *

- * 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. + *

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), /** * Iterate forward between the passed keys but not equal to either of them. * - *

- * The "start" and "stop" values are both required. + *

The "start" and "stop" values are both required. * - *

- * 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. + *

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), /** - * Iterate forward between the passed keys. Do not return the "start" key, but - * do return the "stop" key. + * Iterate forward between the passed keys. Do not return the "start" key, but do return the + * "stop" key. * - *

- * The "start" and "stop" values are both required. + *

The "start" and "stop" values are both required. * - *

- * 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. + *

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), /** * Start on the last key and iterate backward until no keys remain. * - *

- * The "start" and "stop" values are ignored. + *

The "start" and "stop" values are ignored. * - *

- * In our example, the returned keys would be 8, 6, 4 and 2. + *

In our example, the returned keys would be 8, 6, 4 and 2. */ BACKWARD_ALL(false, false, false), /** - * Start on the passed key (or the first key immediately preceding it) and - * iterate backward until no keys remain. + * Start on the passed key (or the first key immediately preceding it) and iterate backward until + * no keys remain. * - *

- * The "start" value is required. The "stop" value is ignored. + *

The "start" value is required. The "stop" value is ignored. * - *

- * In our example and with a passed search key of 5, the returned keys would - * be 4 and 2. With a 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. + *

In our example and with a passed search key of 5, the returned keys would be 4 and 2. With a + * 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), /** - * Start on the last key and iterate backward until a key equal to it (or the - * first key immediately preceding it it) is reached. + * Start on the last key and iterate backward until a key equal to it (or the first key + * immediately preceding it it) is reached. * - *

- * The "stop" value is required. The "start" value is ignored. + *

The "stop" value is required. The "start" value is ignored. * - *

- * 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. + *

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), /** - * 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 following it in the case of the "stop" - * key). - * - *

- * The "start" and "stop" values are both required. - * - *

- * In our example and with a passed search range of 7 - 3, the returned keys - * would be 6 and 4. 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. + * 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 + * following it in the case of the "stop" key). + * + *

The "start" and "stop" values are both required. + * + *

In our example and with a passed search range of 7 - 3, the returned keys would be 6 and 4. + * 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), /** - * 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 following it in the case of the "stop" - * key). Do not return the "stop" key. - * - *

- * The "start" and "stop" values are both required. - * - *

- * In our example and with a passed search range of 8 - 3, the returned keys - * would be 8, 6 and 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. + * 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 + * following it in the case of the "stop" key). Do not return the "stop" key. + * + *

The "start" and "stop" values are both required. + * + *

In our example and with a passed search range of 8 - 3, the returned keys would be 8, 6 and + * 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), /** - * Start immediate prior to the passed key (but not equal to it) and iterate - * backward until no keys remain. + * Start immediate prior to the passed key (but not equal to it) and iterate backward until no + * keys remain. * - *

- * The "start" value is required. The "stop" value is ignored. + *

The "start" value is required. The "stop" value is ignored. * - *

- * In our example and with a passed search key of 6, the returned keys would - * be 4 and 2. With a 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. + *

In our example and with a passed search key of 6, the returned keys would be 4 and 2. With a + * 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), /** - * 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. + * 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. * - *

- * The "stop" value is required. The "start" value is ignored. + *

The "stop" value is required. The "start" value is ignored. * - *

- * 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 + *

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), /** - * Iterate backward between the passed keys, but do not return the passed - * keys. + * Iterate backward between the passed keys, but do not return the passed keys. * - *

- * The "start" and "stop" values are both required. + *

The "start" and "stop" values are both required. * - *

- * In our example and with a passed search range of 7 - 2, the returned keys - * would be 6 and 4. 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. + *

In our example and with a passed search range of 7 - 2, the returned keys would be 6 and 4. + * 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), /** - * Iterate backward between the passed keys. Do not return the "start" key, - * but do return the "stop" key. + * Iterate backward between the passed keys. Do not return the "start" key, but do return the + * "stop" key. * - *

- * The "start" and "stop" values are both required. + *

The "start" and "stop" values are both required. * - *

- * In our example and with a passed search range of 7 - 2, the returned keys - * would be 6, 4 and 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. + *

In our example and with a passed search range of 7 - 2, the returned keys would be 6, 4 and + * 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); @@ -278,8 +227,10 @@ public enum KeyRangeType { private final boolean startKeyRequired; private final boolean stopKeyRequired; - KeyRangeType(final boolean directionForward, final boolean startKeyRequired, - final boolean stopKeyRequired) { + KeyRangeType( + final boolean directionForward, + final boolean startKeyRequired, + final boolean stopKeyRequired) { this.directionForward = directionForward; this.startKeyRequired = startKeyRequired; this.stopKeyRequired = stopKeyRequired; @@ -315,9 +266,8 @@ public boolean isStopKeyRequired() { /** * Determine the iterator action to take when iterator first begins. * - *

- * The iterator will perform this action and present the resulting key to - * {@link #iteratorOp(java.util.Comparator, java.lang.Object)} for decision. + *

The iterator will perform this action and present the resulting key to {@link + * #iteratorOp(java.util.Comparator, java.lang.Object)} for decision. * * @return appropriate action in response to this buffer */ @@ -367,16 +317,16 @@ CursorOp initialOp() { /** * Determine the iterator's response to the presented key. * - * @param buffer type - * @param comparator for the buffers - * @param start start buffer - * @param stop stop buffer + * @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 c comparator (required) * @return response to this key */ - > IteratorOp iteratorOp(final T start, final T stop, - final T buffer, final C c) { + > IteratorOp iteratorOp( + final T start, final T stop, final T buffer, final C c) { requireNonNull(c, "Comparator required"); if (buffer == null) { return TERMINATE; @@ -442,12 +392,11 @@ > IteratorOp iteratorOp(final T start, final T stop, } /** - * Determine the iterator action to take when "next" is called or upon request - * of {@link #iteratorOp(java.util.Comparator, java.lang.Object)}. + * Determine the iterator action to take when "next" is called or upon request of {@link + * #iteratorOp(java.util.Comparator, java.lang.Object)}. * - *

- * The iterator will perform this action and present the resulting key to - * {@link #iteratorOp(java.util.Comparator, java.lang.Object)} for decision. + *

The iterator will perform this action and present the resulting key to {@link + * #iteratorOp(java.util.Comparator, java.lang.Object)} for decision. * * @return appropriate action for this key range type */ @@ -455,51 +404,29 @@ CursorOp nextOp() { return isDirectionForward() ? NEXT : PREV; } - /** - * Action now required with the iterator. - */ + /** Action now required with the iterator. */ enum IteratorOp { - /** - * Consider iterator completed. - */ + /** Consider iterator completed. */ TERMINATE, - /** - * Call {@link KeyRange#nextOp()} again and try again. - */ + /** Call {@link KeyRange#nextOp()} again and try again. */ CALL_NEXT_OP, - /** - * Return the key to the user. - */ + /** Return the key to the user. */ RELEASE } - /** - * Action now required with the cursor. - */ + /** Action now required with the cursor. */ enum CursorOp { - /** - * Move to first. - */ + /** Move to first. */ FIRST, - /** - * Move to last. - */ + /** Move to last. */ LAST, - /** - * Get "start" key with {@link GetOp#MDB_SET_RANGE}. - */ + /** Get "start" key with {@link GetOp#MDB_SET_RANGE}. */ GET_START_KEY, - /** - * Get "start" key with {@link GetOp#MDB_SET_RANGE}, fall back to LAST. - */ + /** Get "start" key with {@link GetOp#MDB_SET_RANGE}, fall back to LAST. */ GET_START_KEY_BACKWARD, - /** - * Move forward. - */ + /** Move forward. */ NEXT, - /** - * Move backward. - */ + /** Move backward. */ PREV } } diff --git a/src/main/java/org/lmdbjava/KeyVal.java b/src/main/java/org/lmdbjava/KeyVal.java index 12b9f9af..c6f2aae5 100644 --- a/src/main/java/org/lmdbjava/KeyVal.java +++ b/src/main/java/org/lmdbjava/KeyVal.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static java.util.Objects.requireNonNull; @@ -102,20 +97,19 @@ void valIn(final int size) { } /** - * Prepares an array suitable for presentation as the data argument to a - * MDB_MULTIPLE put. + * Prepares an array suitable for presentation as the data argument to a MDB_MULTIPLE + * put. * - *

- * The returned array is equivalent of two MDB_vals as follows: + *

The returned array is equivalent of two MDB_vals as follows: * *

    - *
  • ptrVal1.data = pointer to the data address of passed buffer
  • - *
  • ptrVal1.size = size of each individual data element
  • - *
  • ptrVal2.data = unused
  • - *
  • ptrVal2.size = number of data elements (as passed to this method)
  • + *
  • ptrVal1.data = pointer to the data address of passed buffer + *
  • ptrVal1.size = size of each individual data element + *
  • ptrVal2.data = unused + *
  • ptrVal2.size = number of data elements (as passed to this method) *
* - * @param val a user-provided buffer with data elements (required) + * @param val a user-provided buffer with data elements (required) * @param elements number of data elements the user has provided * @return a properly-prepared pointer to an array for the operation */ @@ -134,5 +128,4 @@ T valOut() { v = proxy.out(v, ptrVal, ptrValAddr); return v; } - } diff --git a/src/main/java/org/lmdbjava/Library.java b/src/main/java/org/lmdbjava/Library.java index 22308600..ef9b9b35 100644 --- a/src/main/java/org/lmdbjava/Library.java +++ b/src/main/java/org/lmdbjava/Library.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static java.io.File.createTempFile; @@ -32,8 +27,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import jnr.ffi.Pointer; import jnr.ffi.Struct; import jnr.ffi.annotations.Delegate; @@ -47,25 +40,23 @@ /** * JNR-FFI interface to LMDB. * - *

- * For performance reasons pointers are used rather than structs. + *

For performance reasons pointers are used rather than structs. */ final class Library { /** - * Java system property name that can be set to the path of an existing - * directory into which the LMDB system library will be extracted from the - * LmdbJava JAR. If unspecified the LMDB system library is extracted to the - * java.io.tmpdir. Ignored if the LMDB system library is not - * being extracted from the LmdbJava JAR (as would be the case if other - * system properties defined in TargetName have been set). + * Java system property name that can be set to the path of an existing directory into which the + * LMDB system library will be extracted from the LmdbJava JAR. If unspecified the LMDB system + * library is extracted to the java.io.tmpdir. Ignored if the LMDB system library is + * not being extracted from the LmdbJava JAR (as would be the case if other system properties + * defined in TargetName have been set). */ public static final String LMDB_EXTRACT_DIR_PROP = "lmdbjava.extract.dir"; - /** - * Indicates the directory where the LMDB system library will be extracted. - */ - static final String EXTRACT_DIR = getProperty(LMDB_EXTRACT_DIR_PROP, - getProperty("java.io.tmpdir")); + + /** Indicates the directory where the LMDB system library will be extracted. */ + static final String EXTRACT_DIR = + getProperty(LMDB_EXTRACT_DIR_PROP, getProperty("java.io.tmpdir")); + static final Lmdb LIB; static final jnr.ffi.Runtime RUNTIME; @@ -82,10 +73,8 @@ final class Library { RUNTIME = getRuntime(LIB); } - private Library() { - } + private Library() {} - @SuppressFBWarnings("OBL_UNSATISFIED_OBLIGATION") // Spotbugs issue #432 private static String extract(final String name) { final String suffix = name.substring(name.lastIndexOf('.')); final File file; @@ -98,7 +87,7 @@ private static String extract(final String name) { file.deleteOnExit(); final ClassLoader cl = currentThread().getContextClassLoader(); try (InputStream in = cl.getResourceAsStream(name); - OutputStream out = Files.newOutputStream(file.toPath())) { + OutputStream out = Files.newOutputStream(file.toPath())) { requireNonNull(in, "Classpath resource not found"); int bytes; final byte[] buffer = new byte[4_096]; @@ -112,11 +101,7 @@ private static String extract(final String name) { } } - /** - * Structure to wrap a native MDB_envinfo. Not for external use. - */ - @SuppressWarnings({"checkstyle:TypeName", "checkstyle:VisibilityModifier", - "checkstyle:MemberName"}) + /** Structure to wrap a native MDB_envinfo. Not for external use. */ public static final class MDB_envinfo extends Struct { public final Pointer f0_me_mapaddr; @@ -137,11 +122,7 @@ public static final class MDB_envinfo extends Struct { } } - /** - * Structure to wrap a native MDB_stat. Not for external use. - */ - @SuppressWarnings({"checkstyle:TypeName", "checkstyle:VisibilityModifier", - "checkstyle:MemberName"}) + /** Structure to wrap a native MDB_stat. Not for external use. */ public static final class MDB_stat extends Struct { public final u_int32_t f0_ms_psize; @@ -162,20 +143,14 @@ public static final class MDB_stat extends Struct { } } - /** - * Custom comparator callback used by mdb_set_compare. - */ + /** Custom comparator callback used by mdb_set_compare. */ public interface ComparatorCallback { @Delegate int compare(@In Pointer keyA, @In Pointer keyB); - } - /** - * JNR API for MDB-defined C functions. Not for external use. - */ - @SuppressWarnings("checkstyle:MethodName") + /** JNR API for MDB-defined C functions. Not for external use. */ public interface Lmdb { void mdb_cursor_close(@In Pointer cursor); @@ -184,27 +159,21 @@ public interface Lmdb { int mdb_cursor_del(@In Pointer cursor, int flags); - int mdb_cursor_get(@In Pointer cursor, Pointer k, @Out Pointer v, - int cursorOp); + int mdb_cursor_get(@In Pointer cursor, Pointer k, @Out Pointer v, int cursorOp); - int mdb_cursor_open(@In Pointer txn, @In Pointer dbi, - PointerByReference cursorPtr); + int mdb_cursor_open(@In Pointer txn, @In Pointer dbi, PointerByReference cursorPtr); - int mdb_cursor_put(@In Pointer cursor, @In Pointer key, @In Pointer data, - int flags); + int mdb_cursor_put(@In Pointer cursor, @In Pointer key, @In Pointer data, int flags); int mdb_cursor_renew(@In Pointer txn, @In Pointer cursor); void mdb_dbi_close(@In Pointer env, @In Pointer dbi); - int mdb_dbi_flags(@In Pointer txn, @In Pointer dbi, - @Out IntByReference flags); + int mdb_dbi_flags(@In Pointer txn, @In Pointer dbi, @Out IntByReference flags); - int mdb_dbi_open(@In Pointer txn, @In byte[] name, int flags, - @In Pointer dbiPtr); + int mdb_dbi_open(@In Pointer txn, @In byte[] name, int flags, @In Pointer dbiPtr); - int mdb_del(@In Pointer txn, @In Pointer dbi, @In Pointer key, - @In Pointer data); + int mdb_del(@In Pointer txn, @In Pointer dbi, @In Pointer key, @In Pointer data); int mdb_drop(@In Pointer txn, @In Pointer dbi, int del); @@ -240,12 +209,9 @@ int mdb_del(@In Pointer txn, @In Pointer dbi, @In Pointer key, int mdb_env_sync(@In Pointer env, int f); - int mdb_get(@In Pointer txn, @In Pointer dbi, @In Pointer key, - @Out Pointer data); + int mdb_get(@In Pointer txn, @In Pointer dbi, @In Pointer key, @Out Pointer data); - int mdb_put(@In Pointer txn, @In Pointer dbi, @In Pointer key, - @In Pointer data, - int flags); + int mdb_put(@In Pointer txn, @In Pointer dbi, @In Pointer key, @In Pointer data, int flags); int mdb_reader_check(@In Pointer env, @Out IntByReference dead); @@ -257,8 +223,7 @@ int mdb_put(@In Pointer txn, @In Pointer dbi, @In Pointer key, void mdb_txn_abort(@In Pointer txn); - int mdb_txn_begin(@In Pointer env, @In Pointer parentTx, int flags, - Pointer txPtr); + int mdb_txn_begin(@In Pointer env, @In Pointer parentTx, int flags, Pointer txPtr); int mdb_txn_commit(@In Pointer txn); @@ -270,8 +235,6 @@ int mdb_txn_begin(@In Pointer env, @In Pointer parentTx, int flags, void mdb_txn_reset(@In Pointer txn); - Pointer mdb_version(IntByReference major, IntByReference minor, - IntByReference patch); - + Pointer mdb_version(IntByReference major, IntByReference minor, IntByReference patch); } } diff --git a/src/main/java/org/lmdbjava/LmdbException.java b/src/main/java/org/lmdbjava/LmdbException.java index b3aea56e..c2624f95 100644 --- a/src/main/java/org/lmdbjava/LmdbException.java +++ b/src/main/java/org/lmdbjava/LmdbException.java @@ -1,28 +1,21 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; -/** - * Superclass for all LmdbJava custom exceptions. - */ +/** Superclass for all LmdbJava custom exceptions. */ public class LmdbException extends RuntimeException { private static final long serialVersionUID = 1L; @@ -40,10 +33,9 @@ public LmdbException(final String message) { * Constructs an instance with the provided detailed message and cause. * * @param message the detail message - * @param cause original cause + * @param cause original cause */ public LmdbException(final String message, final Throwable cause) { super(message, cause); } - } diff --git a/src/main/java/org/lmdbjava/LmdbNativeException.java b/src/main/java/org/lmdbjava/LmdbNativeException.java index 3f559806..5c094e77 100644 --- a/src/main/java/org/lmdbjava/LmdbNativeException.java +++ b/src/main/java/org/lmdbjava/LmdbNativeException.java @@ -1,44 +1,35 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static java.lang.String.format; -/** - * Superclass for all exceptions that originate from a native C call. - */ +/** Superclass for all exceptions that originate from a native C call. */ public class LmdbNativeException extends LmdbException { private static final long serialVersionUID = 1L; - /** - * Result code returned by the LMDB C function. - */ + /** Result code returned by the LMDB C function. */ private final int rc; /** * Constructs an instance with the provided detailed message. * * @param msg the detail message. - * @param rc the result code. + * @param rc the result code. */ LmdbNativeException(final int rc, final String msg) { super(format(msg + " (%d)", rc)); @@ -54,9 +45,7 @@ public final int getResultCode() { return rc; } - /** - * Exception raised from a system constant table lookup. - */ + /** Exception raised from a system constant table lookup. */ public static final class ConstantDerivedException extends LmdbNativeException { private static final long serialVersionUID = 1L; @@ -66,9 +55,7 @@ public static final class ConstantDerivedException extends LmdbNativeException { } } - /** - * Located page was wrong type. - */ + /** Located page was wrong type. */ public static final class PageCorruptedException extends LmdbNativeException { static final int MDB_CORRUPTED = -30_796; @@ -79,9 +66,7 @@ public static final class PageCorruptedException extends LmdbNativeException { } } - /** - * Page has not enough space - internal error. - */ + /** Page has not enough space - internal error. */ public static final class PageFullException extends LmdbNativeException { static final int MDB_PAGE_FULL = -30_786; @@ -92,37 +77,29 @@ public static final class PageFullException extends LmdbNativeException { } } - /** - * Requested page not found - this usually indicates corruption. - */ + /** Requested page not found - this usually indicates corruption. */ public static final class PageNotFoundException extends LmdbNativeException { static final int MDB_PAGE_NOTFOUND = -30_797; private static final long serialVersionUID = 1L; PageNotFoundException() { - super(MDB_PAGE_NOTFOUND, - "Requested page not found - this usually indicates corruption"); + super(MDB_PAGE_NOTFOUND, "Requested page not found - this usually indicates corruption"); } } - /** - * Update of meta page failed or environment had fatal error. - */ + /** Update of meta page failed or environment had fatal error. */ public static final class PanicException extends LmdbNativeException { static final int MDB_PANIC = -30_795; private static final long serialVersionUID = 1L; PanicException() { - super(MDB_PANIC, - "Update of meta page failed or environment had fatal error"); + super(MDB_PANIC, "Update of meta page failed or environment had fatal error"); } } - /** - * Too many TLS keys in use - Windows only. - */ + /** Too many TLS keys in use - Windows only. */ public static final class TlsFullException extends LmdbNativeException { static final int MDB_TLS_FULL = -30_789; diff --git a/src/main/java/org/lmdbjava/MaskedFlag.java b/src/main/java/org/lmdbjava/MaskedFlag.java index 242ca589..00556ecb 100644 --- a/src/main/java/org/lmdbjava/MaskedFlag.java +++ b/src/main/java/org/lmdbjava/MaskedFlag.java @@ -1,35 +1,28 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; +import static java.util.Objects.requireNonNull; + import java.util.Arrays; import java.util.Objects; import java.util.function.Predicate; import java.util.stream.Stream; -import static java.util.Objects.requireNonNull; - -/** - * Indicates an enum that can provide integers for each of its values. - */ +/** Indicates an enum that can provide integers for each of its values. */ public interface MaskedFlag { /** @@ -51,6 +44,7 @@ default boolean isPropagatedToLmdb() { /** * 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 */ @@ -62,6 +56,7 @@ static int mask(final M... 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 */ @@ -72,7 +67,9 @@ static int mask(final Stream flags) { /** * Fetch the integer mask for the presented flags. * - * @param onlyPropagatedToLmdb if to include only the flags which are also propagate to the C code or all of them + * @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 */ @@ -84,21 +81,30 @@ static int mask(final boolean onlyPropagatedToLmdb, final /** * Fetch the integer mask for all presented flags. * - * @param onlyPropagatedToLmdb if to include only the flags which are also propagate to the C code or all of them + * @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) { + 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); + return flags == null + ? 0 + : flags + .filter(Objects::nonNull) + .filter(filter) + .map(M::getMask) + .reduce(0, (f1, f2) -> f1 | f2); } /** * Indicates whether the passed flag has the relevant masked flag high. * - * @param flags to evaluate (usually produced by - * {@link #mask(org.lmdbjava.MaskedFlag...)} - * @param test the flag being sought (required) + * @param flags to evaluate (usually produced by {@link #mask(org.lmdbjava.MaskedFlag...)} + * @param test the flag being sought (required) * @return true if set. */ static boolean isSet(final int flags, final MaskedFlag test) { diff --git a/src/main/java/org/lmdbjava/Meta.java b/src/main/java/org/lmdbjava/Meta.java index 6f392701..9f51e1af 100644 --- a/src/main/java/org/lmdbjava/Meta.java +++ b/src/main/java/org/lmdbjava/Meta.java @@ -1,46 +1,36 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static org.lmdbjava.Library.LIB; import jnr.ffi.byref.IntByReference; -/** - * LMDB metadata functions. - */ +/** LMDB metadata functions. */ public final class Meta { - private Meta() { - } + private Meta() {} /** * Fetches the LMDB error code description. * - *

- * End users should not need this method, as LmdbJava converts all LMDB - * exceptions into a typed Java exception that incorporates the error code. - * However it is provided here for verification and troubleshooting (eg if the - * user wishes to see the original LMDB description of the error code, or - * there is a newer library version etc). + *

End users should not need this method, as LmdbJava converts all LMDB exceptions into a typed + * Java exception that incorporates the error code. However it is provided here for verification + * and troubleshooting (eg if the user wishes to see the original LMDB description of the error + * code, or there is a newer library version etc). * * @param err the error code returned from LMDB * @return the description @@ -61,28 +51,19 @@ public static Version version() { LIB.mdb_version(major, minor, patch); - return new Version(major.intValue(), minor.intValue(), patch. - intValue()); + return new Version(major.intValue(), minor.intValue(), patch.intValue()); } - /** - * Immutable return value from {@link #version()}. - */ + /** Immutable return value from {@link #version()}. */ public static final class Version { - /** - * LMDC native library major version number. - */ + /** LMDC native library major version number. */ public final int major; - /** - * LMDC native library patch version number. - */ + /** LMDC native library patch version number. */ public final int minor; - /** - * LMDC native library patch version number. - */ + /** LMDC native library patch version number. */ public final int patch; Version(final int major, final int minor, final int patch) { @@ -91,5 +72,4 @@ public static final class Version { this.patch = patch; } } - } diff --git a/src/main/java/org/lmdbjava/PutFlags.java b/src/main/java/org/lmdbjava/PutFlags.java index 48bc243b..809103de 100644 --- a/src/main/java/org/lmdbjava/PutFlags.java +++ b/src/main/java/org/lmdbjava/PutFlags.java @@ -1,33 +1,24 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; -/** - * Flags for use when performing a "put". - */ +/** Flags for use when performing a "put". */ public enum PutFlags implements MaskedFlag { - /** - * For put: Don't write if the key already exists. - */ + /** For put: Don't write if the key already exists. */ MDB_NOOVERWRITE(0x10), /** * Only for #MDB_DUPSORT
@@ -35,26 +26,17 @@ public enum PutFlags implements MaskedFlag { * For mdb_cursor_del: remove all duplicate data items. */ MDB_NODUPDATA(0x20), - /** - * For mdb_cursor_put: overwrite the current key/data pair. - */ + /** For mdb_cursor_put: overwrite the current key/data pair. */ MDB_CURRENT(0x40), /** - * For put: Just reserve space for data, don't copy it. Return a pointer to - * the reserved space. + * For put: Just reserve space for data, don't copy it. Return a pointer to the reserved space. */ MDB_RESERVE(0x1_0000), - /** - * Data is being appended, don't split full pages. - */ + /** Data is being appended, don't split full pages. */ MDB_APPEND(0x2_0000), - /** - * Duplicate data is being appended, don't split full pages. - */ + /** Duplicate data is being appended, don't split full pages. */ MDB_APPENDDUP(0x4_0000), - /** - * Store multiple data items in one call. Only for #MDB_DUPFIXED. - */ + /** Store multiple data items in one call. Only for #MDB_DUPFIXED. */ MDB_MULTIPLE(0x8_0000); private final int mask; @@ -67,5 +49,4 @@ public enum PutFlags implements MaskedFlag { public int getMask() { return mask; } - } diff --git a/src/main/java/org/lmdbjava/ReferenceUtil.java b/src/main/java/org/lmdbjava/ReferenceUtil.java index 36bf9247..2b9f211e 100644 --- a/src/main/java/org/lmdbjava/ReferenceUtil.java +++ b/src/main/java/org/lmdbjava/ReferenceUtil.java @@ -1,62 +1,44 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; - -/** - * Supports creating strong references in manner compatible with Java 8. - */ +/** Supports creating strong references in manner compatible with Java 8. */ public final class ReferenceUtil { - private ReferenceUtil() { - } + private ReferenceUtil() {} /** - * Ensures that the object referenced by the given reference remains - * strongly reachable, regardless of any prior actions of the program - * that might otherwise cause the object to become unreachable. Thus, the - * referenced object is not reclaimable by garbage collection at least until - * after the invocation of this method. + * Ensures that the object referenced by the given reference remains strongly reachable, + * regardless of any prior actions of the program that might otherwise cause the object to become + * unreachable. Thus, the referenced object is not reclaimable by garbage collection at least + * until after the invocation of this method. * - *

- * Recent versions of the JDK have a nasty habit of prematurely deciding - * objects are unreachable (eg - * StackOverflow question - * 26642153. + *

Recent versions of the JDK have a nasty habit of prematurely deciding objects are + * unreachable (eg StackOverflow question 26642153. * - *

- * java.lang.ref.Reference.reachabilityFence offers a solution to - * this problem, but it was only introduced in Java 9. LmdbJava presently - * supports Java 8 and therefore this method provides an alternative. + *

java.lang.ref.Reference.reachabilityFence offers a solution to this problem, + * but it was only introduced in Java 9. LmdbJava presently supports Java 8 and therefore this + * method provides an alternative. * - *

- * This method is always implemented as a synchronization on {@code ref}. - * It is the caller's responsibility to ensure that this synchronization - * will not cause deadlock. + *

This method is always implemented as a synchronization on {@code ref}. It is the caller's + * responsibility to ensure that this synchronization will not cause deadlock. * * @param ref the reference (null is acceptable but has no effect) * @see Netty PR 8410 */ - @SuppressFBWarnings({"ESync_EMPTY_SYNC", "UC_USELESS_VOID_METHOD"}) public static void reachabilityFence0(final Object ref) { if (ref != null) { synchronized (ref) { diff --git a/src/main/java/org/lmdbjava/ResultCodeMapper.java b/src/main/java/org/lmdbjava/ResultCodeMapper.java index 809628ac..1e4d841e 100644 --- a/src/main/java/org/lmdbjava/ResultCodeMapper.java +++ b/src/main/java/org/lmdbjava/ResultCodeMapper.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static jnr.constants.ConstantSet.getConstantSet; @@ -31,16 +26,12 @@ /** * Maps a LMDB C result code to the equivalent Java exception. * - *

- * The immutable nature of all LMDB exceptions means the mapper internally - * maintains a table of them. + *

The immutable nature of all LMDB exceptions means the mapper internally maintains a table of + * them. */ -@SuppressWarnings("PMD.CyclomaticComplexity") final class ResultCodeMapper { - /** - * Successful result. - */ + /** Successful result. */ static final int MDB_SUCCESS = 0; private static final ConstantSet CONSTANTS; @@ -50,8 +41,7 @@ final class ResultCodeMapper { CONSTANTS = getConstantSet(POSIX_ERR_NO); } - private ResultCodeMapper() { - } + private ResultCodeMapper() {} /** * Checks the result code and raises an exception is not {@link #MDB_SUCCESS}. @@ -113,5 +103,4 @@ static void checkRc(final int rc) { final String msg = constant.name() + " " + constant.toString(); throw new LmdbNativeException.ConstantDerivedException(rc, msg); } - } diff --git a/src/main/java/org/lmdbjava/SeekOp.java b/src/main/java/org/lmdbjava/SeekOp.java index ed5fb47c..162aef49 100644 --- a/src/main/java/org/lmdbjava/SeekOp.java +++ b/src/main/java/org/lmdbjava/SeekOp.java @@ -1,100 +1,62 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; /** * Flags for use when performing a {@link Cursor#seek(org.lmdbjava.SeekOp)}. * - *

- * Unlike most other LMDB enums, this enum is not bit masked. + *

Unlike most other LMDB enums, this enum is not bit masked. */ public enum SeekOp { - /** - * Position at first key/data item. - */ + /** Position at first key/data item. */ MDB_FIRST(0), - /** - * Position at first data item of current key. Only for - * {@link DbiFlags#MDB_DUPSORT}. - */ + /** Position at first data item of current key. Only for {@link DbiFlags#MDB_DUPSORT}. */ MDB_FIRST_DUP(1), - /** - * Position at key/data pair. Only for {@link DbiFlags#MDB_DUPSORT}. - */ + /** Position at key/data pair. Only for {@link DbiFlags#MDB_DUPSORT}. */ MDB_GET_BOTH(2), - /** - * position at key, nearest data. Only for {@link DbiFlags#MDB_DUPSORT}. - */ + /** position at key, nearest data. Only for {@link DbiFlags#MDB_DUPSORT}. */ MDB_GET_BOTH_RANGE(3), - /** - * Return key/data at current cursor position. - */ + /** Return key/data at current cursor position. */ MDB_GET_CURRENT(4), /** - * Return key and up to a page of duplicate data items from current cursor - * position. Move cursor to prepare for {@link #MDB_NEXT_MULTIPLE}. Only for - * {@link DbiFlags#MDB_DUPSORT}. + * Return key and up to a page of duplicate data items from current cursor position. Move cursor + * to prepare for {@link #MDB_NEXT_MULTIPLE}. Only for {@link DbiFlags#MDB_DUPSORT}. */ MDB_GET_MULTIPLE(5), - /** - * Position at last key/data item. - */ + /** Position at last key/data item. */ MDB_LAST(6), - /** - * Position at last data item of current key. Only for - * {@link DbiFlags#MDB_DUPSORT}. - */ + /** Position at last data item of current key. Only for {@link DbiFlags#MDB_DUPSORT}. */ MDB_LAST_DUP(7), - /** - * Position at next data item. - */ + /** Position at next data item. */ MDB_NEXT(8), - /** - * Position at next data item of current key. Only for - * {@link DbiFlags#MDB_DUPSORT}. - */ + /** Position at next data item of current key. Only for {@link DbiFlags#MDB_DUPSORT}. */ MDB_NEXT_DUP(9), /** - * Return key and up to a page of duplicate data items from next cursor - * position. Move cursor to prepare for {@link #MDB_NEXT_MULTIPLE}. Only for - * {@link DbiFlags#MDB_DUPSORT}. + * Return key and up to a page of duplicate data items from next cursor position. Move cursor to + * prepare for {@link #MDB_NEXT_MULTIPLE}. Only for {@link DbiFlags#MDB_DUPSORT}. */ MDB_NEXT_MULTIPLE(10), - /** - * Position at first data item of next key. - */ + /** Position at first data item of next key. */ MDB_NEXT_NODUP(11), - /** - * Position at previous data item. - */ + /** Position at previous data item. */ MDB_PREV(12), - /** - * Position at previous data item of current key. - * {@link DbiFlags#MDB_DUPSORT}. - */ + /** Position at previous data item of current key. {@link DbiFlags#MDB_DUPSORT}. */ MDB_PREV_DUP(13), - /** - * Position at last data item of previous key. - */ + /** Position at last data item of previous key. */ MDB_PREV_NODUP(14); private final int code; @@ -111,5 +73,4 @@ public enum SeekOp { public int getCode() { return code; } - } diff --git a/src/main/java/org/lmdbjava/Stat.java b/src/main/java/org/lmdbjava/Stat.java index 42b344fb..d4995324 100644 --- a/src/main/java/org/lmdbjava/Stat.java +++ b/src/main/java/org/lmdbjava/Stat.java @@ -1,64 +1,48 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; -/** - * Statistics, as returned by {@link Env#stat()} and - * {@link Dbi#stat(org.lmdbjava.Txn)}. - */ +/** Statistics, as returned by {@link Env#stat()} and {@link Dbi#stat(org.lmdbjava.Txn)}. */ public final class Stat { - /** - * Number of internal (non-leaf) pages. - */ + /** Number of internal (non-leaf) pages. */ public final long branchPages; - /** - * Depth (height) of the B-tree. - */ + /** Depth (height) of the B-tree. */ public final int depth; - /** - * Number of data items. - */ + /** Number of data items. */ public final long entries; - /** - * Number of leaf pages. - */ + /** Number of leaf pages. */ public final long leafPages; - /** - * Number of overflow pages. - */ + /** Number of overflow pages. */ public final long overflowPages; - /** - * Size of a database page. This is currently the same for all databases. - */ + /** Size of a database page. This is currently the same for all databases. */ public final int pageSize; - Stat(final int pageSize, final int depth, final long branchPages, - final long leafPages, - final long overflowPages, final long entries) { + Stat( + final int pageSize, + final int depth, + final long branchPages, + final long leafPages, + final long overflowPages, + final long entries) { this.pageSize = pageSize; this.depth = depth; this.branchPages = branchPages; @@ -69,10 +53,19 @@ public final class Stat { @Override public String toString() { - return "Stat{" + "branchPages=" + branchPages + ", depth=" + depth - + ", entries=" + entries + ", leafPages=" + leafPages - + ", overflowPages=" + overflowPages + ", pageSize=" + pageSize - + '}'; + return "Stat{" + + "branchPages=" + + branchPages + + ", depth=" + + depth + + ", entries=" + + entries + + ", leafPages=" + + leafPages + + ", overflowPages=" + + overflowPages + + ", pageSize=" + + pageSize + + '}'; } - } diff --git a/src/main/java/org/lmdbjava/TargetName.java b/src/main/java/org/lmdbjava/TargetName.java index 446ea5b5..023ecba5 100644 --- a/src/main/java/org/lmdbjava/TargetName.java +++ b/src/main/java/org/lmdbjava/TargetName.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static java.lang.System.getProperty; @@ -26,44 +21,41 @@ /** * Determines the name of the target LMDB native library. * - *

- * Users will typically use an LMDB native library that is embedded within the - * LmdbJava JAR. Embedded libraries are built by a Zig cross-compilation step as - * part of the release process. The naming convention reflects the Zig target - * name plus a common filename extension. This simplifies support for future Zig - * targets (eg with different toolchains etc). + *

Users will typically use an LMDB native library that is embedded within the LmdbJava JAR. + * Embedded libraries are built by a Zig cross-compilation step as part of the release process. The + * naming convention reflects the Zig target name plus a common filename extension. This simplifies + * support for future Zig targets (eg with different toolchains etc). * - *

- * Users can set two system properties to override the automatic resolution of - * an embedded library. Setting {@link #LMDB_NATIVE_LIB_PROP} will force use of - * that external LMDB library. Setting {@link #LMDB_EMBEDDED_LIB_PROP} will - * force use of that embedded LMDB library. If both are set, the former property - * will take precedence. Most users do not need to set either property. + *

Users can set two system properties to override the automatic resolution of an embedded + * library. Setting {@link #LMDB_NATIVE_LIB_PROP} will force use of that external LMDB library. + * Setting {@link #LMDB_EMBEDDED_LIB_PROP} will force use of that embedded LMDB library. If both are + * set, the former property will take precedence. Most users do not need to set either property. */ public final class TargetName { /** - * True if the resolved native filename is an external file (conversely false - * indicates the file should be considered a classpath resource). + * True if the resolved native filename is an external file (conversely false indicates the file + * should be considered a classpath resource). */ public static final boolean IS_EXTERNAL; /** - * Java system property name that can be set to override the embedded library - * that will be used. This is likely to be required if automatic resolution - * fails but the user still prefers to use an LmdbJava-bundled library. This - * path must include the classpath prefix (usually org/lmdbjava). + * Java system property name that can be set to override the embedded library that will be used. + * This is likely to be required if automatic resolution fails but the user still prefers to use + * an LmdbJava-bundled library. This path must include the classpath prefix (usually + * org/lmdbjava). */ public static final String LMDB_EMBEDDED_LIB_PROP = "lmdbjava.embedded.lib"; + /** - * Java system property name that can be set to provide a custom path to an - * external LMDB system library. + * Java system property name that can be set to provide a custom path to an external LMDB system + * library. */ public static final String LMDB_NATIVE_LIB_PROP = "lmdbjava.native.lib"; - /** - * Resolved target native filename or fully-qualified classpath location. - */ + + /** Resolved target native filename or fully-qualified classpath location. */ public static final String RESOLVED_FILENAME; + private static final String ARCH = getProperty("os.arch"); private static final String EMBED = getProperty(LMDB_EMBEDDED_LIB_PROP); private static final String EXTERNAL = getProperty(LMDB_NATIVE_LIB_PROP); @@ -74,9 +66,14 @@ public final class TargetName { RESOLVED_FILENAME = resolveFilename(EXTERNAL, EMBED, ARCH, OS); } - private TargetName() { - } + private TargetName() {} + /** + * Resolves the filename extension of the bundled LMDB library for a given operating system. + * + * @param os typically the os.name system property + * @return extension of the LMDB system library bundled with LmdbJava + */ public static String resolveExtension(final String os) { return check(os, "Windows") ? "dll" : "so"; } @@ -85,8 +82,8 @@ static boolean isExternal(final String external) { return external != null && !external.isEmpty(); } - static String resolveFilename(final String external, final String embed, - final String arch, final String os) { + static String resolveFilename( + final String external, final String embed, final String arch, final String os) { if (external != null && !external.isEmpty()) { return external; } @@ -96,20 +93,25 @@ static String resolveFilename(final String external, final String embed, } final String pkg = TargetName.class.getPackage().getName().replace('.', '/'); - return pkg + "/" + resolveArch(arch) + "-" + resolveOs(os) + "-" - + resolveToolchain(os) + "." + resolveExtension(os); + return pkg + + "/" + + resolveArch(arch) + + "-" + + resolveOs(os) + + "-" + + resolveToolchain(os) + + "." + + resolveExtension(os); } /** - * Case insensitively checks whether the passed string starts with any of the - * candidate strings. + * Case insensitively checks whether the passed string starts with any of the candidate strings. * - * @param string the string being checked + * @param string the string being checked * @param candidates one or more candidate strings * @return true if the string starts with any of the candidates */ - private static boolean check(final String string, - final String... candidates) { + private static boolean check(final String string, final String... candidates) { if (string == null) { return false; } @@ -124,10 +126,17 @@ private static boolean check(final String string, } private static String err(final String reason) { - return reason + " (please set system property " + LMDB_NATIVE_LIB_PROP - + " to the path of an external LMDB native library or property " - + LMDB_EMBEDDED_LIB_PROP + " to the name of an LmdbJava embedded" - + " library; os.arch='" + ARCH + "' os.name='" + OS + "')"; + return reason + + " (please set system property " + + LMDB_NATIVE_LIB_PROP + + " to the path of an external LMDB native library or property " + + LMDB_EMBEDDED_LIB_PROP + + " to the name of an LmdbJava embedded" + + " library; os.arch='" + + ARCH + + "' os.name='" + + OS + + "')"; } private static String resolveArch(final String arch) { @@ -153,5 +162,4 @@ private static String resolveOs(final String os) { private static String resolveToolchain(final String os) { return check(os, "Mac OS") ? "none" : "gnu"; } - } diff --git a/src/main/java/org/lmdbjava/Txn.java b/src/main/java/org/lmdbjava/Txn.java index 3092c7c4..05e8ce06 100644 --- a/src/main/java/org/lmdbjava/Txn.java +++ b/src/main/java/org/lmdbjava/Txn.java @@ -1,27 +1,20 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; -import jnr.ffi.Pointer; - import static jnr.ffi.Memory.allocateDirect; import static jnr.ffi.NativeType.ADDRESS; import static org.lmdbjava.Env.SHOULD_CHECK; @@ -36,6 +29,8 @@ import static org.lmdbjava.Txn.State.RESET; import static org.lmdbjava.TxnFlags.MDB_RDONLY_TXN; +import jnr.ffi.Pointer; + /** * LMDB transaction. * @@ -51,8 +46,7 @@ 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 TxnFlags... flags) { this.proxy = proxy; this.keyVal = proxy.keyVal(); final int flagsMask = mask(true, flags); @@ -73,9 +67,7 @@ public final class Txn implements AutoCloseable { state = READY; } - /** - * Aborts this transaction. - */ + /** Aborts this transaction. */ public void abort() { if (SHOULD_CHECK) { env.checkNotClosed(); @@ -88,10 +80,8 @@ public void abort() { /** * Closes this transaction by aborting if not already committed. * - *

- * Closing the transaction will invoke - * {@link BufferProxy#deallocate(java.lang.Object)} for each read-only buffer - * (ie the key and value). + *

Closing the transaction will invoke {@link BufferProxy#deallocate(java.lang.Object)} for + * each read-only buffer (ie the key and value). */ @Override public void close() { @@ -108,9 +98,7 @@ public void close() { state = RELEASED; } - /** - * Commits this transaction. - */ + /** Commits this transaction. */ public void commit() { if (SHOULD_CHECK) { env.checkNotClosed(); @@ -151,10 +139,10 @@ public boolean isReadOnly() { } /** - * Fetch the buffer which holds a read-only view of the LMDI allocated memory. - * Any use of this buffer must comply with the standard LMDB C "mdb_get" - * contract (ie do not modify, do not attempt to release the memory, do not - * use once the transaction or cursor closes, do not use after a write etc). + * Fetch the buffer which holds a read-only view of the LMDI allocated memory. Any use of this + * buffer must comply with the standard LMDB C "mdb_get" contract (ie do not modify, do not + * attempt to release the memory, do not use once the transaction or cursor closes, do not use + * after a write etc). * * @return the key buffer (never null) */ @@ -162,9 +150,7 @@ public T key() { return keyVal.key(); } - /** - * Renews a read-only transaction previously released by {@link #reset()}. - */ + /** Renews a read-only transaction previously released by {@link #reset()}. */ public void renew() { if (SHOULD_CHECK) { env.checkNotClosed(); @@ -178,8 +164,8 @@ public void renew() { } /** - * Aborts this read-only transaction and resets the transaction handle so it - * can be reused upon calling {@link #renew()}. + * Aborts this read-only transaction and resets the transaction handle so it can be reused upon + * calling {@link #renew()}. */ public void reset() { if (SHOULD_CHECK) { @@ -194,10 +180,10 @@ public void reset() { } /** - * Fetch the buffer which holds a read-only view of the LMDI allocated memory. - * Any use of this buffer must comply with the standard LMDB C "mdb_get" - * contract (ie do not modify, do not attempt to release the memory, do not - * use once the transaction or cursor closes, do not use after a write etc). + * Fetch the buffer which holds a read-only view of the LMDI allocated memory. Any use of this + * buffer must comply with the standard LMDB C "mdb_get" contract (ie do not modify, do not + * attempt to release the memory, do not use once the transaction or cursor closes, do not use + * after a write etc). * * @return the value buffer (never null) */ @@ -244,9 +230,7 @@ Pointer pointer() { return ptr; } - /** - * Transaction must abort, has a child, or is invalid. - */ + /** Transaction must abort, has a child, or is invalid. */ public static final class BadException extends LmdbNativeException { static final int MDB_BAD_TXN = -30_782; @@ -257,9 +241,7 @@ public static final class BadException extends LmdbNativeException { } } - /** - * Invalid reuse of reader locktable slot. - */ + /** Invalid reuse of reader locktable slot. */ public static final class BadReaderLockException extends LmdbNativeException { static final int MDB_BAD_RSLOT = -30_783; @@ -270,114 +252,84 @@ public static final class BadReaderLockException extends LmdbNativeException { } } - /** - * The proposed R-W transaction is incompatible with a R-O Env. - */ + /** The proposed R-W transaction is incompatible with a R-O Env. */ public static class EnvIsReadOnly extends LmdbException { private static final long serialVersionUID = 1L; - /** - * Creates a new instance. - */ + /** Creates a new instance. */ public EnvIsReadOnly() { super("Read-write Txn incompatible with read-only Env"); } } - /** - * The proposed transaction is incompatible with its parent transaction. - */ + /** The proposed transaction is incompatible with its parent transaction. */ public static class IncompatibleParent extends LmdbException { private static final long serialVersionUID = 1L; - /** - * Creates a new instance. - */ + /** Creates a new instance. */ public IncompatibleParent() { super("Transaction incompatible with its parent transaction"); } } - /** - * Transaction is not in a READY state. - */ + /** Transaction is not in a READY state. */ public static final class NotReadyException extends LmdbException { private static final long serialVersionUID = 1L; - /** - * Creates a new instance. - */ + /** Creates a new instance. */ public NotReadyException() { super("Transaction is not in ready state"); } } - /** - * The current transaction has not been reset. - */ + /** The current transaction has not been reset. */ public static class NotResetException extends LmdbException { private static final long serialVersionUID = 1L; - /** - * Creates a new instance. - */ + /** Creates a new instance. */ public NotResetException() { super("Transaction has not been reset"); } } - /** - * The current transaction is not a read-only transaction. - */ + /** The current transaction is not a read-only transaction. */ public static class ReadOnlyRequiredException extends LmdbException { private static final long serialVersionUID = 1L; - /** - * Creates a new instance. - */ + /** Creates a new instance. */ public ReadOnlyRequiredException() { super("Not a read-only transaction"); } } - /** - * The current transaction is not a read-write transaction. - */ + /** The current transaction is not a read-write transaction. */ public static class ReadWriteRequiredException extends LmdbException { private static final long serialVersionUID = 1L; - /** - * Creates a new instance. - */ + /** Creates a new instance. */ public ReadWriteRequiredException() { super("Not a read-write transaction"); } } - /** - * The current transaction has already been reset. - */ + /** The current transaction has already been reset. */ public static class ResetException extends LmdbException { private static final long serialVersionUID = 1L; - /** - * Creates a new instance. - */ + /** Creates a new instance. */ public ResetException() { super("Transaction has already been reset"); } } - /** - * Transaction has too many dirty pages. - */ + /** Transaction has too many dirty pages. */ public static final class TxFullException extends LmdbNativeException { static final int MDB_TXN_FULL = -30_788; @@ -388,11 +340,11 @@ public static final class TxFullException extends LmdbNativeException { } } - /** - * Transaction states. - */ + /** Transaction states. */ enum State { - READY, DONE, RESET, RELEASED + READY, + DONE, + RESET, + RELEASED } - } diff --git a/src/main/java/org/lmdbjava/TxnFlags.java b/src/main/java/org/lmdbjava/TxnFlags.java index 0379bfa7..26caf6f1 100644 --- a/src/main/java/org/lmdbjava/TxnFlags.java +++ b/src/main/java/org/lmdbjava/TxnFlags.java @@ -1,32 +1,23 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; -/** - * Flags for use when creating a {@link Txn}. - */ +/** Flags for use when creating a {@link Txn}. */ public enum TxnFlags implements MaskedFlag { - /** - * Read only. - */ + /** Read only. */ MDB_RDONLY_TXN(0x2_0000); private final int mask; @@ -39,5 +30,4 @@ public enum TxnFlags implements MaskedFlag { public int getMask() { return mask; } - } diff --git a/src/main/java/org/lmdbjava/UnsafeAccess.java b/src/main/java/org/lmdbjava/UnsafeAccess.java index 836cda75..5b3da017 100644 --- a/src/main/java/org/lmdbjava/UnsafeAccess.java +++ b/src/main/java/org/lmdbjava/UnsafeAccess.java @@ -1,56 +1,42 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static java.lang.Boolean.getBoolean; import java.lang.reflect.Field; - import sun.misc.Unsafe; -/** - * Provides access to Unsafe. - */ +/** Provides access to Unsafe. */ final class UnsafeAccess { - /** - * Java system property name that can be set to disable unsafe. - */ + /** Java system property name that can be set to disable unsafe. */ public static final String DISABLE_UNSAFE_PROP = "lmdbjava.disable.unsafe"; - /** - * Indicates whether unsafe use is allowed. - */ + /** Indicates whether unsafe use is allowed. */ public static final boolean ALLOW_UNSAFE = !getBoolean(DISABLE_UNSAFE_PROP); /** - * The actual unsafe. Guaranteed to be non-null if this class can access - * unsafe and {@link #ALLOW_UNSAFE} is true. In other words, this entire class - * will fail to initialize if unsafe is unavailable. This avoids callers from - * needing to deal with null checks. + * The actual unsafe. Guaranteed to be non-null if this class can access unsafe and {@link + * #ALLOW_UNSAFE} is true. In other words, this entire class will fail to initialize if unsafe is + * unavailable. This avoids callers from needing to deal with null checks. */ static final Unsafe UNSAFE; - /** - * Unsafe field name (used to reflectively obtain the unsafe instance). - */ + + /** Unsafe field name (used to reflectively obtain the unsafe instance). */ private static final String FIELD_NAME_THE_UNSAFE = "theUnsafe"; static { @@ -61,13 +47,13 @@ final class UnsafeAccess { final Field field = Unsafe.class.getDeclaredField(FIELD_NAME_THE_UNSAFE); field.setAccessible(true); UNSAFE = (Unsafe) field.get(null); - } catch (final NoSuchFieldException | SecurityException - | IllegalArgumentException | IllegalAccessException e) { + } catch (final NoSuchFieldException + | SecurityException + | IllegalArgumentException + | IllegalAccessException e) { throw new LmdbException("Unsafe unavailable", e); } } - private UnsafeAccess() { - } - + private UnsafeAccess() {} } diff --git a/src/main/java/org/lmdbjava/Verifier.java b/src/main/java/org/lmdbjava/Verifier.java index 9807d0b8..ff9b28f8 100644 --- a/src/main/java/org/lmdbjava/Verifier.java +++ b/src/main/java/org/lmdbjava/Verifier.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static java.nio.ByteOrder.BIG_ENDIAN; @@ -37,53 +32,42 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.zip.CRC32; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; - /** * Verifies correct operation of LmdbJava in a given environment. * - *

- * Due to the large variety of operating systems and Java platforms typically - * used with LmdbJava, this class provides a convenient verification of correct - * operating behavior through a potentially long duration set of tests that - * carefully verify correct storage and retrieval of successively larger - * database entries. + *

Due to the large variety of operating systems and Java platforms typically used with LmdbJava, + * this class provides a convenient verification of correct operating behavior through a potentially + * long duration set of tests that carefully verify correct storage and retrieval of successively + * larger database entries. * - *

- * The verifier currently operates by incrementing a long - * identifier that deterministically maps to a given {@link Dbi} and value size. - * The key is simply the long identifier. The value commences with - * a CRC that includes the identifier and the random bytes of the value. Each - * entry is written out, and then the prior entry is retrieved using its key. - * The prior entry's value is evaluated for accuracy and then deleted. - * Transactions are committed in batches to ensure successive transactions - * correctly retrieve the results of earlier transactions. + *

The verifier currently operates by incrementing a long identifier that + * deterministically maps to a given {@link Dbi} and value size. The key is simply the long + * identifier. The value commences with a CRC that includes the identifier and the random + * bytes of the value. Each entry is written out, and then the prior entry is retrieved using its + * key. The prior entry's value is evaluated for accuracy and then deleted. Transactions are + * committed in batches to ensure successive transactions correctly retrieve the results of earlier + * transactions. * - *

- * Please note the verification approach may be modified in the future. + *

Please note the verification approach may be modified in the future. * - *

- * If an exception is raised by this class, please: + *

If an exception is raised by this class, please: * *

    - *
  1. Ensure the {@link Env} passed at construction time complies with the - * requirements specified at {@link #Verifier(org.lmdbjava.Env)}
  2. - *
  3. Attempt to use a different file system to store the database (be - * especially careful to not use network file systems, remote file systems, - * read-only file systems etc)
  4. - *
  5. Record the full exception message and stack trace, then run the verifier - * again to see if it fails at the same or a different point
  6. - *
  7. Raise a ticket on the LmdbJava Issue Tracker that confirms the above - * details along with the failing operating system and Java version
  8. + *
  9. Ensure the {@link Env} passed at construction time complies with the requirements specified + * at {@link #Verifier(org.lmdbjava.Env)} + *
  10. Attempt to use a different file system to store the database (be especially careful to not + * use network file systems, remote file systems, read-only file systems etc) + *
  11. Record the full exception message and stack trace, then run the verifier again to see if it + * fails at the same or a different point + *
  12. Raise a ticket on the LmdbJava Issue Tracker that confirms the above details along with the + * failing operating system and Java version *
- * */ public final class Verifier implements Callable { - /** - * Number of DBIs the created environment should allow. - */ + /** Number of DBIs the created environment should allow. */ public static final int DBI_COUNT = 5; + private static final int BATCH_SIZE = 64; private static final int BUFFER_LEN = 1_024 * BATCH_SIZE; private static final int CRC_LENGTH = Long.BYTES; @@ -102,19 +86,15 @@ public final class Verifier implements Callable { /** * Create an instance of the verifier. * - *

- * The caller must provide an {@link Env} configured with a suitable local - * storage location, maximum DBIs equal to {@link #DBI_COUNT}, and a - * map size large enough to accommodate the intended verification duration. + *

The caller must provide an {@link Env} configured with a suitable local storage location, + * maximum DBIs equal to {@link #DBI_COUNT}, and a map size large enough to accommodate the + * intended verification duration. * - *

- * ALL EXISTING DATA IN THE DATABASE WILL BE DELETED. The caller must not - * interact with the Env in any way (eg querying, transactions - * etc) while the verifier is executing. + *

ALL EXISTING DATA IN THE DATABASE WILL BE DELETED. The caller must not interact with the + * Env in any way (eg querying, transactions etc) while the verifier is executing. * * @param env target that complies with the above requirements (required) */ - @SuppressFBWarnings("EI_EXPOSE_REP2") public Verifier(final Env env) { requireNonNull(env); this.env = env; @@ -127,10 +107,8 @@ public Verifier(final Env env) { /** * Run the verifier until {@link #stop()} is called or an exception occurs. * - *

- * Successful return of this method indicates no faults were detected. If any - * fault was detected the exception message will detail the exact point that - * the fault was encountered. + *

Successful return of this method indicates no faults were detected. If any fault was + * detected the exception message will detail the exact point that the fault was encountered. * * @return number of database rows successfully verified */ @@ -159,12 +137,11 @@ public Long call() { /** * Execute the verifier for the given duration. * - *

- * This provides a simple way to execute the verifier for those applications - * which do not wish to manage threads directly. + *

This provides a simple way to execute the verifier for those applications which do not wish + * to manage threads directly. * * @param duration amount of time to execute - * @param unit units used to express the duration + * @param unit units used to express the duration * @return number of database rows successfully verified */ public long runFor(final long duration, final TimeUnit unit) { @@ -233,14 +210,11 @@ private Dbi getDbi(final long forId) { return dbis.get((int) (forId % dbis.size())); } - /** - * Request the verifier to stop execution. - */ + /** Request the verifier to stop execution. */ private void stop() { proceed.set(false); } - @SuppressFBWarnings("DMI_RANDOM_USED_ONLY_ONCE") private void transactionControl() { if (id % BATCH_SIZE == 0) { if (txn != null) { @@ -282,8 +256,8 @@ private void verifyValue(final long forId, final ByteBuffer bb) { final int rndSize = valueSize(forId); final int expected = rndSize + CRC_LENGTH; if (bb.limit() != expected) { - throw new IllegalStateException("Limit error id=" + forId + " exp=" - + expected + " limit=" + bb.limit()); + throw new IllegalStateException( + "Limit error id=" + forId + " exp=" + expected + " limit=" + bb.limit()); } final long crcRead = bb.getLong(); @@ -308,5 +282,4 @@ private void write(final long forId) { throw new IllegalStateException("DB put id=" + forId, ex); } } - } diff --git a/src/main/java/org/lmdbjava/package-info.java b/src/main/java/org/lmdbjava/package-info.java index 52c8551a..9589c470 100644 --- a/src/main/java/org/lmdbjava/package-info.java +++ b/src/main/java/org/lmdbjava/package-info.java @@ -1,63 +1,52 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - /** * Lightning Memory Database (LMDB) for Java (LmdbJava). * - *

- * LmdbJava is intended for extremely low latency use cases. Users are required - * to understand and comply with the LMDB C API contract (eg handle usage - * patterns, thread binding, process rules). + *

LmdbJava is intended for extremely low latency use cases. Users are required to understand and + * comply with the LMDB C API contract (eg handle usage patterns, thread binding, process rules). + * + *

Priorities: * - *

- * Priorities: *

    - *
  1. Minimize latency, particularly on any critical path (see below)
  2. - *
  3. Preserve the LMDB C API model as far as practical
  4. - *
  5. Apply Java idioms only when not in conflict with the above
  6. - *
  7. Fully encapsulate (hide) the native call library and patterns
  8. - *
  9. Don't require runtime dependencies beyond the native call library
  10. - *
  11. Support official JVMs running on typical 64-bit operating systems
  12. - *
  13. Prepare for Java 9 (eg Unsafe, native call technology roadmap etc)
  14. + *
  15. Minimize latency, particularly on any critical path (see below) + *
  16. Preserve the LMDB C API model as far as practical + *
  17. Apply Java idioms only when not in conflict with the above + *
  18. Fully encapsulate (hide) the native call library and patterns + *
  19. Don't require runtime dependencies beyond the native call library + *
  20. Support official JVMs running on typical 64-bit operating systems + *
  21. Prepare for Java 9 (eg Unsafe, native call technology roadmap etc) *
* - *

- * Critical paths of special latency focus: + *

Critical paths of special latency focus: + * *

    - *
  • Releasing and renewing a read-only transaction
  • - *
  • Any operation that uses a cursor
  • + *
  • Releasing and renewing a read-only transaction + *
  • Any operation that uses a cursor *
* - *

- * The classes in LmdbJava DO NOT provide any concurrency guarantees. Instead - * you MUST observe LMDB's specific thread rules (eg do not share transactions - * between threads). LmdbJava does not shield you from these requirements, as - * doing so would impose locking overhead on use cases that may not require it - * or have already carefully implemented application threading (as most low + *

The classes in LmdbJava DO NOT provide any concurrency guarantees. Instead you MUST observe + * LMDB's specific thread rules (eg do not share transactions between threads). LmdbJava does not + * shield you from these requirements, as doing so would impose locking overhead on use cases that + * may not require it or have already carefully implemented application threading (as most low * latency applications do to optimize the memory hierarchy, core pinning etc). * - *

- * Most methods in this package will throw a standard Java exception for failing - * preconditions (eg {@link NullPointerException} if a mandatory argument was - * missing) or a subclass of {@link LmdbException} for precondition or LMDB C - * failures. The majority of LMDB exceptions indicate an API usage or - * {@link Env} configuration issues, and as such are typically unrecoverable. + *

Most methods in this package will throw a standard Java exception for failing preconditions + * (eg {@link NullPointerException} if a mandatory argument was missing) or a subclass of {@link + * LmdbException} for precondition or LMDB C failures. The majority of LMDB exceptions indicate an + * API usage or {@link Env} configuration issues, and as such are typically unrecoverable. */ package org.lmdbjava; diff --git a/src/misc/license-template.txt b/src/misc/license-template.txt new file mode 100644 index 00000000..046ef77f --- /dev/null +++ b/src/misc/license-template.txt @@ -0,0 +1,13 @@ +Copyright © ${license.git.copyrightYears} ${owner} + +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. \ No newline at end of file diff --git a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java index 3daaea5f..8044b9e8 100644 --- a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java +++ b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static java.lang.Integer.BYTES; @@ -45,7 +40,6 @@ import java.io.IOException; import java.lang.reflect.Field; import java.nio.ByteBuffer; - import jnr.ffi.Pointer; import jnr.ffi.provider.MemoryManager; import org.junit.Rule; @@ -54,22 +48,17 @@ 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 { final File path = tmp.newFolder(); - try (Env env = create() - .setMaxReaders(1) - .open(path)) { + try (Env env = create().setMaxReaders(1).open(path)) { final Dbi db = env.openDbi(DB_1, MDB_CREATE); final ByteBuffer key = allocate(100); key.putInt(1).flip(); @@ -159,5 +148,4 @@ private void checkInOut(final BufferProxy v) { assertThat(bb.getInt(), is(3)); assertThat(bb.remaining(), is(0)); } - } diff --git a/src/test/java/org/lmdbjava/ComparatorTest.java b/src/test/java/org/lmdbjava/ComparatorTest.java index 8371a1ae..3e265cee 100644 --- a/src/test/java/org/lmdbjava/ComparatorTest.java +++ b/src/test/java/org/lmdbjava/ComparatorTest.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static io.netty.buffer.PooledByteBufAllocator.DEFAULT; @@ -33,13 +28,12 @@ import static org.lmdbjava.ComparatorTest.ComparatorResult.get; import static org.lmdbjava.DirectBufferProxy.PROXY_DB; -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.Comparator; - import com.google.common.primitives.SignedBytes; import com.google.common.primitives.UnsignedBytes; import io.netty.buffer.ByteBuf; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Comparator; import org.agrona.DirectBuffer; import org.agrona.concurrent.UnsafeBuffer; import org.junit.Test; @@ -48,9 +42,7 @@ import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; -/** - * Tests comparator functions are consistent across buffers. - */ +/** Tests comparator functions are consistent across buffers. */ @RunWith(Parameterized.class) public final class ComparatorTest { @@ -66,11 +58,9 @@ public final class ComparatorTest { private static final byte[] LLLLLLLX = buffer(0, 0, 0, 0, 0, 0, 0); private static final byte[] LX = buffer(0); private static final byte[] XX = buffer(); - /** - * Injected by {@link #data()} with appropriate runner. - */ - @Parameter - public ComparatorRunner comparator; + + /** Injected by {@link #data()} with appropriate runner. */ + @Parameter public ComparatorRunner comparator; @Parameters(name = "{index}: comparable: {0}") public static Object[] data() { @@ -81,7 +71,7 @@ public static Object[] data() { final ComparatorRunner netty = new NettyRunner(); final ComparatorRunner gub = new GuavaUnsignedBytes(); final ComparatorRunner gsb = new GuavaSignedBytes(); - return new Object[]{string, db, ba, bb, netty, gub, gsb}; + return new Object[] {string, db, ba, bb, netty, gub, gsb}; } private static byte[] buffer(final int... bytes) { @@ -140,9 +130,7 @@ public void equalBuffers() { assertThat(get(comparator.compare(LLLLLLLX, LLLLLLLX)), is(EQUAL_TO)); } - /** - * Tests {@link ByteArrayProxy}. - */ + /** Tests {@link ByteArrayProxy}. */ private static final class ByteArrayRunner implements ComparatorRunner { @Override @@ -152,9 +140,7 @@ public int compare(final byte[] o1, final byte[] o2) { } } - /** - * Tests {@link ByteBufferProxy}. - */ + /** Tests {@link ByteBufferProxy}. */ private static final class ByteBufferRunner implements ComparatorRunner { @Override @@ -196,9 +182,7 @@ private ByteBuffer arrayToBuffer(final byte[] arr, final int bufferCapacity) { } } - /** - * Tests {@link DirectBufferProxy}. - */ + /** Tests {@link DirectBufferProxy}. */ private static final class DirectBufferRunner implements ComparatorRunner { @Override @@ -210,9 +194,7 @@ public int compare(final byte[] o1, final byte[] o2) { } } - /** - * Tests using Guava's {@link SignedBytes} comparator. - */ + /** Tests using Guava's {@link SignedBytes} comparator. */ private static final class GuavaSignedBytes implements ComparatorRunner { @Override @@ -222,9 +204,7 @@ public int compare(final byte[] o1, final byte[] o2) { } } - /** - * Tests using Guava's {@link UnsignedBytes} comparator. - */ + /** Tests using Guava's {@link UnsignedBytes} comparator. */ private static final class GuavaUnsignedBytes implements ComparatorRunner { @Override @@ -234,9 +214,7 @@ public int compare(final byte[] o1, final byte[] o2) { } } - /** - * Tests {@link ByteBufProxy}. - */ + /** Tests {@link ByteBufProxy}. */ private static final class NettyRunner implements ComparatorRunner { @Override @@ -251,8 +229,8 @@ public int compare(final byte[] o1, final byte[] o2) { } /** - * Tests {@link String} by providing a reference implementation of what a - * comparator involving ASCII-encoded bytes should return. + * Tests {@link String} by providing a reference implementation of what a comparator involving + * ASCII-encoded bytes should return. */ private static final class StringRunner implements ComparatorRunner { @@ -264,9 +242,7 @@ public int compare(final byte[] o1, final byte[] o2) { } } - /** - * Converts an integer result code into its contractual meaning. - */ + /** Converts an integer result code into its contractual meaning. */ enum ComparatorResult { LESS_THAN, EQUAL_TO, @@ -280,14 +256,12 @@ static ComparatorResult get(final int comparatorResult) { } } - /** - * Interface that can test a {@link BufferProxy} compare method. - */ + /** Interface that can test a {@link BufferProxy} compare method. */ private interface ComparatorRunner { /** - * Convert the passed byte arrays into the proxy's relevant buffer type and - * then invoke the comparator. + * Convert the passed byte arrays into the proxy's relevant buffer type and then invoke the + * comparator. * * @param o1 lhs buffer content * @param o2 rhs buffer content @@ -295,5 +269,4 @@ private interface ComparatorRunner { */ int compare(byte[] o1, byte[] o2); } - } diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index db28405a..bd23bc55 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; @@ -51,6 +46,7 @@ import static org.lmdbjava.TestUtils.POSIX_MODE; import static org.lmdbjava.TestUtils.bb; +import com.google.common.primitives.UnsignedBytes; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; @@ -61,8 +57,6 @@ import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; - -import com.google.common.primitives.UnsignedBytes; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Before; @@ -71,13 +65,10 @@ import org.junit.rules.TemporaryFolder; import org.lmdbjava.CursorIterable.KeyVal; -/** - * Test {@link CursorIterable}. - */ +/** Test {@link CursorIterable}. */ public final class CursorIterableTest { - @Rule - public final TemporaryFolder tmp = new TemporaryFolder(); + @Rule public final TemporaryFolder tmp = new TemporaryFolder(); private Dbi db; private Env env; private Deque list; @@ -125,11 +116,12 @@ public void atMostTest() { @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); + 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); } @@ -190,7 +182,7 @@ public void greaterThanTest() { @Test(expected = IllegalStateException.class) public void iterableOnlyReturnedOnce() { try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { + CursorIterable c = db.iterate(txn)) { c.iterator(); // ok c.iterator(); // fails } @@ -199,7 +191,7 @@ public void iterableOnlyReturnedOnce() { @Test public void iterate() { try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { + CursorIterable c = db.iterate(txn)) { for (final KeyVal kv : c) { assertThat(kv.key().getInt(), is(list.pollFirst())); assertThat(kv.val().getInt(), is(list.pollFirst())); @@ -210,7 +202,7 @@ public void iterate() { @Test(expected = IllegalStateException.class) public void iteratorOnlyReturnedOnce() { try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { + CursorIterable c = db.iterate(txn)) { c.iterator(); // ok c.iterator(); // fails } @@ -231,7 +223,7 @@ public void lessThanTest() { @Test(expected = NoSuchElementException.class) public void nextThrowsNoSuchElementExceptionIfNoMoreElements() { 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(); @@ -260,17 +252,18 @@ public void openClosedBackwardTest() { @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 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(bb(7), bb(2)), guavaDbi, 6, 4, 2); @@ -355,24 +348,21 @@ public void forEachRemainingWithClosedEnvTest() { final Iterator> c = ci.iterator(); env.close(); - c.forEachRemaining(keyVal -> { - - }); + c.forEachRemaining(keyVal -> {}); } } } - private void verify(final KeyRange range, - final int... expected) { + private void verify(final KeyRange range, final int... expected) { verify(range, db, 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(); @@ -386,5 +376,4 @@ private void verify(final KeyRange range, assertThat(results.get(idx), is(expected[idx])); } } - } diff --git a/src/test/java/org/lmdbjava/CursorParamTest.java b/src/test/java/org/lmdbjava/CursorParamTest.java index b9fd60e3..bd99e709 100644 --- a/src/test/java/org/lmdbjava/CursorParamTest.java +++ b/src/test/java/org/lmdbjava/CursorParamTest.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; @@ -48,11 +43,10 @@ import static org.lmdbjava.TestUtils.mdb; import static org.lmdbjava.TestUtils.nb; +import io.netty.buffer.ByteBuf; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; - -import io.netty.buffer.ByteBuf; import org.agrona.DirectBuffer; import org.agrona.MutableDirectBuffer; import org.junit.Rule; @@ -63,20 +57,14 @@ import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; -/** - * Test {@link Cursor} with different buffer implementations. - */ +/** Test {@link Cursor} with different buffer implementations. */ @RunWith(Parameterized.class) public final class CursorParamTest { - /** - * Injected by {@link #data()} with appropriate runner. - */ - @Parameter - public BufferRunner runner; + /** Injected by {@link #data()} with appropriate runner. */ + @Parameter public BufferRunner runner; - @Rule - public final TemporaryFolder tmp = new TemporaryFolder(); + @Rule public final TemporaryFolder tmp = new TemporaryFolder(); @Parameters(name = "{index}: buffer adapter: {0}") public static Object[] data() { @@ -85,7 +73,7 @@ public static Object[] data() { final BufferRunner ba = new ByteArrayRunner(PROXY_BA); final BufferRunner db = new DirectBufferRunner(); final BufferRunner netty = new NettyBufferRunner(); - return new Object[]{bb1, bb2, ba, db, netty}; + return new Object[] {bb1, bb2, ba, db, netty}; } @Test @@ -98,8 +86,7 @@ public void execute() { * * @param buffer type */ - private abstract static class AbstractBufferRunner implements - BufferRunner { + private abstract static class AbstractBufferRunner implements BufferRunner { final BufferProxy proxy; @@ -114,7 +101,7 @@ public final void execute(final TemporaryFolder tmp) { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); assertThat(env.getDbiNames().get(0), is(DB_1.getBytes(UTF_8))); try (Txn txn = env.txnWrite(); - Cursor c = db.openCursor(txn)) { + Cursor c = db.openCursor(txn)) { // populate data c.put(set(1), set(2), MDB_NOOVERWRITE); c.put(set(3), set(4)); @@ -192,12 +179,9 @@ private Env env(final TemporaryFolder tmp) { throw new LmdbException("IO failure", e); } } - } - /** - * {@link BufferRunner} for Java byte buffers. - */ + /** {@link BufferRunner} for Java byte buffers. */ private static class ByteArrayRunner extends AbstractBufferRunner { ByteArrayRunner(final BufferProxy proxy) { @@ -207,9 +191,9 @@ private static class ByteArrayRunner extends AbstractBufferRunner { @Override public int get(final byte[] buff) { return (buff[0] & 0xFF) << 24 - | (buff[1] & 0xFF) << 16 - | (buff[2] & 0xFF) << 8 - | (buff[3] & 0xFF); + | (buff[1] & 0xFF) << 16 + | (buff[2] & 0xFF) << 8 + | (buff[3] & 0xFF); } @Override @@ -231,9 +215,7 @@ public void set(final byte[] buff, final int val) { } } - /** - * {@link BufferRunner} for Java byte buffers. - */ + /** {@link BufferRunner} for Java byte buffers. */ private static class ByteBufferRunner extends AbstractBufferRunner { ByteBufferRunner(final BufferProxy proxy) { @@ -254,12 +236,9 @@ public ByteBuffer set(final int val) { public void set(final ByteBuffer buff, final int val) { buff.putInt(val); } - } - /** - * {@link BufferRunner} for Agrona direct buffer. - */ + /** {@link BufferRunner} for Agrona direct buffer. */ private static class DirectBufferRunner extends AbstractBufferRunner { DirectBufferRunner() { @@ -280,12 +259,9 @@ public DirectBuffer set(final int val) { public void set(final DirectBuffer buff, final int val) { ((MutableDirectBuffer) buff).putInt(0, val); } - } - /** - * {@link BufferRunner} for Netty byte buf. - */ + /** {@link BufferRunner} for Netty byte buf. */ private static class NettyBufferRunner extends AbstractBufferRunner { NettyBufferRunner() { @@ -306,7 +282,6 @@ public ByteBuf set(final int val) { public void set(final ByteBuf buff, final int val) { buff.setInt(0, val); } - } /** @@ -324,5 +299,4 @@ private interface BufferRunner { int get(T buff); } - } diff --git a/src/test/java/org/lmdbjava/CursorTest.java b/src/test/java/org/lmdbjava/CursorTest.java index 17fbacc7..cf6e4dea 100644 --- a/src/test/java/org/lmdbjava/CursorTest.java +++ b/src/test/java/org/lmdbjava/CursorTest.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; @@ -50,7 +45,6 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.util.function.Consumer; - import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -60,13 +54,10 @@ import org.lmdbjava.Txn.NotReadyException; import org.lmdbjava.Txn.ReadOnlyRequiredException; -/** - * Test {@link Cursor}. - */ +/** Test {@link Cursor}. */ public final class CursorTest { - @Rule - public final TemporaryFolder tmp = new TemporaryFolder(); + @Rule public final TemporaryFolder tmp = new TemporaryFolder(); private Env env; @@ -79,11 +70,12 @@ public void after() { public void before() throws IOException { try { final File path = tmp.newFile(); - env = create(PROXY_OPTIMAL) - .setMapSize(KIBIBYTES.toBytes(1_024)) - .setMaxReaders(1) - .setMaxDbs(1) - .open(path, POSIX_MODE, MDB_NOSUBDIR); + env = + create(PROXY_OPTIMAL) + .setMapSize(KIBIBYTES.toBytes(1_024)) + .setMaxReaders(1) + .setMaxDbs(1) + .open(path, POSIX_MODE, MDB_NOSUBDIR); } catch (final IOException e) { throw new LmdbException("IO failure", e); } @@ -132,31 +124,31 @@ public void closedEnvRejectsLastCall() { @Test(expected = Env.AlreadyClosedException.class) public void closedEnvRejectsPrevCall() { doEnvClosedTest( - c -> { - c.first(); - assertThat(c.key().getInt(), is(1)); - assertThat(c.val().getInt(), is(10)); - c.next(); - }, - Cursor::prev); + c -> { + c.first(); + assertThat(c.key().getInt(), is(1)); + assertThat(c.val().getInt(), is(10)); + c.next(); + }, + Cursor::prev); } @Test(expected = Env.AlreadyClosedException.class) public void closedEnvRejectsDeleteCall() { doEnvClosedTest( - c -> { - c.first(); - assertThat(c.key().getInt(), is(1)); - assertThat(c.val().getInt(), is(10)); - }, - Cursor::delete); + c -> { + c.first(); + assertThat(c.key().getInt(), is(1)); + assertThat(c.val().getInt(), is(10)); + }, + Cursor::delete); } @Test public void count() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); try (Txn txn = env.txnWrite(); - Cursor c = db.openCursor(txn)) { + Cursor c = db.openCursor(txn)) { c.put(bb(1), bb(2), MDB_APPENDDUP); assertThat(c.count(), is(1L)); c.put(bb(1), bb(4), MDB_APPENDDUP); @@ -172,7 +164,7 @@ public void count() { public void cursorCannotCloseIfTransactionCommitted() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); try (Txn txn = env.txnWrite()) { - try (Cursor c = db.openCursor(txn);) { + try (Cursor c = db.openCursor(txn); ) { c.put(bb(1), bb(2), MDB_APPENDDUP); assertThat(c.count(), is(1L)); c.put(bb(1), bb(4), MDB_APPENDDUP); @@ -186,7 +178,7 @@ public void cursorCannotCloseIfTransactionCommitted() { public void cursorFirstLastNextPrev() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite(); - Cursor c = db.openCursor(txn)) { + Cursor c = db.openCursor(txn)) { c.put(bb(1), bb(2), MDB_NOOVERWRITE); c.put(bb(3), bb(4)); c.put(bb(5), bb(6)); @@ -215,7 +207,7 @@ public void cursorFirstLastNextPrev() { public void delete() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); try (Txn txn = env.txnWrite(); - Cursor c = db.openCursor(txn)) { + Cursor c = db.openCursor(txn)) { c.put(bb(1), bb(2), MDB_NOOVERWRITE); c.put(bb(3), bb(4)); assertThat(c.seek(MDB_FIRST), is(true)); @@ -234,7 +226,7 @@ public void delete() { public void getKeyVal() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); try (Txn txn = env.txnWrite(); - Cursor c = db.openCursor(txn)) { + Cursor c = db.openCursor(txn)) { c.put(bb(1), bb(2), MDB_APPENDDUP); c.put(bb(1), bb(4), MDB_APPENDDUP); c.put(bb(1), bb(6), MDB_APPENDDUP); @@ -253,8 +245,7 @@ public void getKeyVal() { @Test public void putMultiple() { - final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT, - MDB_DUPFIXED); + final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT, MDB_DUPFIXED); final int elemCount = 20; final ByteBuffer values = allocateDirect(Integer.BYTES * elemCount); @@ -266,7 +257,7 @@ public void putMultiple() { final int key = 100; final ByteBuffer k = bb(key); try (Txn txn = env.txnWrite(); - Cursor c = db.openCursor(txn)) { + Cursor c = db.openCursor(txn)) { c.putMultiple(k, values, elemCount, MDB_MULTIPLE); assertThat(c.count(), is((long) elemCount)); } @@ -276,7 +267,7 @@ public void putMultiple() { public void putMultipleWithoutMdbMultipleFlag() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); try (Txn txn = env.txnWrite(); - Cursor c = db.openCursor(txn)) { + Cursor c = db.openCursor(txn)) { c.putMultiple(bb(100), bb(1), 1); } } @@ -345,7 +336,7 @@ public void reserve() { public void returnValueForNoDupData() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); try (Txn txn = env.txnWrite(); - Cursor c = db.openCursor(txn)) { + Cursor c = db.openCursor(txn)) { // ok assertThat(c.put(bb(5), bb(6), MDB_NODUPDATA), is(true)); assertThat(c.put(bb(5), bb(7), MDB_NODUPDATA), is(true)); @@ -357,7 +348,7 @@ public void returnValueForNoDupData() { public void returnValueForNoOverwrite() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite(); - Cursor c = db.openCursor(txn)) { + Cursor c = db.openCursor(txn)) { // ok assertThat(c.put(bb(5), bb(6), MDB_NOOVERWRITE), is(true)); // fails, but gets exist val @@ -395,8 +386,9 @@ public void testCursorByteBufferDuplicate() { } } - private void doEnvClosedTest(final Consumer> workBeforeEnvClosed, - final Consumer> workAfterEnvClose) { + private void doEnvClosedTest( + final Consumer> workBeforeEnvClosed, + final Consumer> workAfterEnvClose) { final Dbi db = env.openDbi(DB_1, MDB_CREATE); db.put(bb(1), bb(10)); diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 5a8cf549..1fa80f6e 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; @@ -69,7 +64,6 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiConsumer; - import org.agrona.concurrent.UnsafeBuffer; import org.hamcrest.Matchers; import org.junit.After; @@ -83,13 +77,10 @@ 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; @After @@ -100,11 +91,12 @@ public void after() { @Before public void before() throws IOException { final File path = tmp.newFile(); - env = create() - .setMapSize(MEBIBYTES.toBytes(64)) - .setMaxReaders(2) - .setMaxDbs(2) - .open(path, MDB_NOSUBDIR); + env = + create() + .setMapSize(MEBIBYTES.toBytes(64)) + .setMaxReaders(2) + .setMaxDbs(2) + .open(path, MDB_NOSUBDIR); } @Test(expected = ConstantDerivedException.class) @@ -117,13 +109,14 @@ public void close() { @Test public void customComparator() { - final Comparator reverseOrder = (o1, o2) -> { - final int lexical = PROXY_OPTIMAL.getComparator().compare(o1, o2); - if (lexical == 0) { - return 0; - } - return lexical * -1; - }; + final Comparator reverseOrder = + (o1, o2) -> { + final int lexical = PROXY_OPTIMAL.getComparator().compare(o1, o2); + if (lexical == 0) { + return 0; + } + return lexical * -1; + }; final Dbi db = env.openDbi(DB_1, reverseOrder, true, MDB_CREATE); try (Txn txn = env.txnWrite()) { assertThat(db.put(txn, bb(2), bb(3)), is(true)); @@ -133,7 +126,7 @@ public void customComparator() { txn.commit(); } try (Txn txn = env.txnRead(); - CursorIterable ci = db.iterate(txn, atMost(bb(4)))) { + CursorIterable ci = db.iterate(txn, atMost(bb(4)))) { final Iterator> iter = ci.iterator(); assertThat(iter.next().key().getInt(), is(8)); assertThat(iter.next().key().getInt(), is(6)); @@ -142,7 +135,6 @@ public void customComparator() { } @Test(expected = DbFullException.class) - @SuppressWarnings("ResultOfObjectAllocationIgnored") public void dbOpenMaxDatabases() { env.openDbi("db1 is OK", MDB_CREATE); env.openDbi("db2 is OK", MDB_CREATE); @@ -159,13 +151,15 @@ public void dbiWithComparatorThreadSafety() { 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, bb(50)); - } - } - }); + final Future reader = + pool.submit( + () -> { + while (proceed.get()) { + try (Txn txn = env.txnRead()) { + db.get(txn, bb(50)); + } + } + }); for (final Integer key : keys) { try (Txn txn = env.txnWrite()) { @@ -175,7 +169,7 @@ public void dbiWithComparatorThreadSafety() { } 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()) { @@ -255,8 +249,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(); @@ -273,8 +267,7 @@ public void getNamesWhenEmpty() { @Test public void listsFlags() { - final Dbi dbi = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT, - MDB_REVERSEKEY); + final Dbi dbi = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT, MDB_REVERSEKEY); try (Txn txn = env.txnRead()) { final List flags = dbi.listFlags(txn); @@ -332,11 +325,12 @@ public void putCommitGet() { @Test 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)) { + try (Env envBa = + 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)); @@ -464,7 +458,6 @@ public void stats() { } @Test(expected = MapFullException.class) - @SuppressWarnings("PMD.PreserveStackTrace") public void testMapFullException() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite()) { @@ -489,11 +482,12 @@ public void testParallelWritesStress() { // Travis CI has 1.5 cores for legacy builds nCopies(2, null).parallelStream() - .forEach(ignored -> { - for (int i = 0; i < 15_000; i++) { - db.put(bb(i), bb(i)); - } - }); + .forEach( + ignored -> { + for (int i = 0; i < 15_000; i++) { + db.put(bb(i), bb(i)); + } + }); } @Test(expected = AlreadyClosedException.class) @@ -504,26 +498,22 @@ public void closedEnvRejectsOpenCall() { @Test(expected = AlreadyClosedException.class) public void closedEnvRejectsCloseCall() { - doEnvClosedTest( - null, - (db, txn) -> db.close()); + doEnvClosedTest(null, (db, txn) -> db.close()); } @Test(expected = AlreadyClosedException.class) public void closedEnvRejectsGetCall() { doEnvClosedTest( (db, txn) -> { - final ByteBuffer valBuf = db.get(txn, bb(1)); - assertThat(valBuf.getInt(), is(10)); - }, + final ByteBuffer valBuf = db.get(txn, bb(1)); + assertThat(valBuf.getInt(), is(10)); + }, (db, txn) -> db.get(txn, bb(2))); } @Test(expected = AlreadyClosedException.class) public void closedEnvRejectsPutCall() { - doEnvClosedTest( - null, - (db, txn) -> db.put(bb(5), bb(50))); + doEnvClosedTest(null, (db, txn) -> db.put(bb(5), bb(50))); } @Test(expected = AlreadyClosedException.class) @@ -531,8 +521,8 @@ public void closedEnvRejectsPutWithTxnCall() { doEnvClosedTest( null, (db, txn) -> { - db.put(txn, bb(5), bb(50)); - }); + db.put(txn, bb(5), bb(50)); + }); } @Test(expected = AlreadyClosedException.class) @@ -542,30 +532,22 @@ public void closedEnvRejectsIterateCall() { @Test(expected = AlreadyClosedException.class) public void closedEnvRejectsDropCall() { - doEnvClosedTest( - null, - Dbi::drop); + doEnvClosedTest(null, Dbi::drop); } @Test(expected = AlreadyClosedException.class) public void closedEnvRejectsDropAndDeleteCall() { - doEnvClosedTest( - null, - (db, txn) -> db.drop(txn, true)); + doEnvClosedTest(null, (db, txn) -> db.drop(txn, true)); } @Test(expected = AlreadyClosedException.class) public void closedEnvRejectsOpenCursorCall() { - doEnvClosedTest( - null, - Dbi::openCursor); + doEnvClosedTest(null, Dbi::openCursor); } @Test(expected = AlreadyClosedException.class) public void closedEnvRejectsReserveCall() { - doEnvClosedTest( - null, - (db, txn) -> db.reserve(txn, bb(1), 32, MDB_NOOVERWRITE)); + doEnvClosedTest(null, (db, txn) -> db.reserve(txn, bb(1), 32, MDB_NOOVERWRITE)); } @Test(expected = AlreadyClosedException.class) @@ -596,5 +578,4 @@ private void doEnvClosedTest( } } } - } diff --git a/src/test/java/org/lmdbjava/EnvTest.java b/src/test/java/org/lmdbjava/EnvTest.java index 46d8766f..e71c6ae9 100644 --- a/src/test/java/org/lmdbjava/EnvTest.java +++ b/src/test/java/org/lmdbjava/EnvTest.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; @@ -42,8 +37,6 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.util.Random; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -54,21 +47,16 @@ import org.lmdbjava.Env.MapFullException; import org.lmdbjava.Txn.BadReaderLockException; -/** - * Test {@link Env}. - */ +/** Test {@link Env}. */ public final class EnvTest { - @Rule - public final TemporaryFolder tmp = new TemporaryFolder(); + @Rule public final TemporaryFolder tmp = new TemporaryFolder(); @Test public void byteUnit() throws IOException { final File path = tmp.newFile(); - try (Env env = create() - .setMaxReaders(1) - .setMapSize(MEBIBYTES.toBytes(1)) - .open(path, MDB_NOSUBDIR)) { + try (Env env = + create().setMaxReaders(1).setMapSize(MEBIBYTES.toBytes(1)).open(path, MDB_NOSUBDIR)) { final EnvInfo info = env.info(); assertThat(info.mapSize, is(MEBIBYTES.toBytes(1))); } @@ -77,8 +65,7 @@ public void byteUnit() throws IOException { @Test(expected = AlreadyOpenException.class) public void cannotChangeMapSizeAfterOpen() throws IOException { final File path = tmp.newFile(); - final Builder builder = create() - .setMaxReaders(1); + final Builder builder = create().setMaxReaders(1); try (Env env = builder.open(path, MDB_NOSUBDIR)) { builder.setMapSize(1); } @@ -87,8 +74,7 @@ public void cannotChangeMapSizeAfterOpen() throws IOException { @Test(expected = AlreadyOpenException.class) public void cannotChangeMaxDbsAfterOpen() throws IOException { final File path = tmp.newFile(); - final Builder builder = create() - .setMaxReaders(1); + final Builder builder = create().setMaxReaders(1); try (Env env = builder.open(path, MDB_NOSUBDIR)) { builder.setMaxDbs(1); } @@ -97,8 +83,7 @@ public void cannotChangeMaxDbsAfterOpen() throws IOException { @Test(expected = AlreadyOpenException.class) public void cannotChangeMaxReadersAfterOpen() throws IOException { final File path = tmp.newFile(); - final Builder builder = create() - .setMaxReaders(1); + final Builder builder = create().setMaxReaders(1); try (Env env = builder.open(path, MDB_NOSUBDIR)) { builder.setMaxReaders(1); } @@ -107,9 +92,7 @@ public void cannotChangeMaxReadersAfterOpen() throws IOException { @Test(expected = AlreadyClosedException.class) public void cannotInfoOnceClosed() throws IOException { final File path = tmp.newFile(); - final Env env = create() - .setMaxReaders(1) - .open(path, MDB_NOSUBDIR); + final Env env = create().setMaxReaders(1).open(path, MDB_NOSUBDIR); env.close(); env.info(); } @@ -117,8 +100,7 @@ public void cannotInfoOnceClosed() throws IOException { @Test(expected = AlreadyOpenException.class) public void cannotOpenTwice() throws IOException { final File path = tmp.newFile(); - final Builder builder = create() - .setMaxReaders(1); + final Builder builder = create().setMaxReaders(1); builder.open(path, MDB_NOSUBDIR).close(); builder.open(path, MDB_NOSUBDIR); @@ -126,8 +108,7 @@ public void cannotOpenTwice() throws IOException { @Test(expected = IllegalArgumentException.class) public void cannotOverflowMapSize() { - final Builder builder = create() - .setMaxReaders(1); + 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); @@ -136,9 +117,7 @@ public void cannotOverflowMapSize() { @Test(expected = AlreadyClosedException.class) public void cannotStatOnceClosed() throws IOException { final File path = tmp.newFile(); - final Env env = create() - .setMaxReaders(1) - .open(path, MDB_NOSUBDIR); + final Env env = create().setMaxReaders(1).open(path, MDB_NOSUBDIR); env.close(); env.stat(); } @@ -146,9 +125,7 @@ public void cannotStatOnceClosed() throws IOException { @Test(expected = AlreadyClosedException.class) public void cannotSyncOnceClosed() throws IOException { final File path = tmp.newFile(); - final Env env = create() - .setMaxReaders(1) - .open(path, MDB_NOSUBDIR); + final Env env = create().setMaxReaders(1).open(path, MDB_NOSUBDIR); env.close(); env.sync(false); } @@ -160,9 +137,7 @@ public void copyDirectoryBased() throws IOException { assertThat(dest.isDirectory(), is(true)); assertThat(dest.list().length, is(0)); final File src = tmp.newFolder(); - try (Env env = create() - .setMaxReaders(1) - .open(src)) { + try (Env env = create().setMaxReaders(1).open(src)) { env.copy(dest, MDB_CP_COMPACT); assertThat(dest.list().length, is(1)); } @@ -172,9 +147,7 @@ public void copyDirectoryBased() throws IOException { public void copyDirectoryRejectsFileDestination() throws IOException { final File dest = tmp.newFile(); final File src = tmp.newFolder(); - try (Env env = create() - .setMaxReaders(1) - .open(src)) { + try (Env env = create().setMaxReaders(1).open(src)) { env.copy(dest, MDB_CP_COMPACT); } } @@ -184,9 +157,7 @@ public void copyDirectoryRejectsMissingDestination() throws IOException { final File dest = tmp.newFolder(); assertThat(dest.delete(), is(true)); final File src = tmp.newFolder(); - try (Env env = create() - .setMaxReaders(1) - .open(src)) { + try (Env env = create().setMaxReaders(1).open(src)) { env.copy(dest, MDB_CP_COMPACT); } } @@ -197,9 +168,7 @@ public void copyDirectoryRejectsNonEmptyDestination() throws IOException { final File subDir = new File(dest, "hello"); assertThat(subDir.mkdir(), is(true)); final File src = tmp.newFolder(); - try (Env env = create() - .setMaxReaders(1) - .open(src)) { + try (Env env = create().setMaxReaders(1).open(src)) { env.copy(dest, MDB_CP_COMPACT); } } @@ -210,9 +179,7 @@ public void copyFileBased() throws IOException { assertThat(dest.delete(), is(true)); assertThat(dest.exists(), is(false)); final File src = tmp.newFile(); - try (Env env = create() - .setMaxReaders(1) - .open(src, MDB_NOSUBDIR)) { + try (Env env = create().setMaxReaders(1).open(src, MDB_NOSUBDIR)) { env.copy(dest, MDB_CP_COMPACT); } assertThat(dest.length(), greaterThan(0L)); @@ -223,9 +190,7 @@ public void copyFileRejectsExistingDestination() throws IOException { final File dest = tmp.newFile(); assertThat(dest.exists(), is(true)); final File src = tmp.newFile(); - try (Env env = create() - .setMaxReaders(1) - .open(src, MDB_NOSUBDIR)) { + try (Env env = create().setMaxReaders(1).open(src, MDB_NOSUBDIR)) { env.copy(dest, MDB_CP_COMPACT); } } @@ -233,9 +198,7 @@ public void copyFileRejectsExistingDestination() throws IOException { @Test public void createAsDirectory() throws IOException { final File path = tmp.newFolder(); - final Env env = create() - .setMaxReaders(1) - .open(path); + final Env env = create().setMaxReaders(1).open(path); assertThat(path.isDirectory(), is(true)); env.sync(false); env.close(); @@ -246,11 +209,8 @@ public void createAsDirectory() throws IOException { @Test public void createAsFile() throws IOException { final File path = tmp.newFile(); - try (Env env = create() - .setMapSize(1_024 * 1_024) - .setMaxDbs(1) - .setMaxReaders(1) - .open(path, MDB_NOSUBDIR)) { + try (Env env = + create().setMapSize(1_024 * 1_024).setMaxDbs(1).setMaxReaders(1).open(path, MDB_NOSUBDIR)) { env.sync(true); assertThat(path.isFile(), is(true)); } @@ -259,9 +219,7 @@ public void createAsFile() throws IOException { @Test(expected = BadReaderLockException.class) public void detectTransactionThreadViolation() throws IOException { final File path = tmp.newFile(); - try (Env env = create() - .setMaxReaders(1) - .open(path, MDB_NOSUBDIR)) { + try (Env env = create().setMaxReaders(1).open(path, MDB_NOSUBDIR)) { env.txnRead(); env.txnRead(); } @@ -270,10 +228,8 @@ public void detectTransactionThreadViolation() throws IOException { @Test public void info() throws IOException { final File path = tmp.newFile(); - try (Env env = create() - .setMaxReaders(4) - .setMapSize(123_456) - .open(path, MDB_NOSUBDIR)) { + try (Env env = + create().setMaxReaders(4).setMapSize(123_456).open(path, MDB_NOSUBDIR)) { final EnvInfo info = env.info(); assertThat(info, is(notNullValue())); assertThat(info.lastPageNumber, is(1L)); @@ -288,19 +244,16 @@ public void info() throws IOException { } @Test(expected = MapFullException.class) - @SuppressFBWarnings("DMI_RANDOM_USED_ONLY_ONCE") public void mapFull() throws IOException { final File path = tmp.newFolder(); 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(path)) { + try (Env env = + create().setMaxReaders(1).setMapSize(MEBIBYTES.toBytes(8)).setMaxDbs(1).open(path)) { final Dbi db = env.openDbi(DB_1, MDB_CREATE); - for (;;) { + for (; ; ) { rnd.nextBytes(k); key.clear(); key.put(k).flip(); @@ -313,15 +266,11 @@ public void mapFull() throws IOException { @Test public void readOnlySupported() throws IOException { final File path = tmp.newFolder(); - try (Env rwEnv = create() - .setMaxReaders(1) - .open(path)) { + try (Env rwEnv = create().setMaxReaders(1).open(path)) { final Dbi rwDb = rwEnv.openDbi(DB_1, MDB_CREATE); rwDb.put(bb(1), bb(42)); } - try (Env roEnv = create() - .setMaxReaders(1) - .open(path, MDB_RDONLY_ENV)) { + try (Env roEnv = create().setMaxReaders(1).open(path, MDB_RDONLY_ENV)) { final Dbi roDb = roEnv.openDbi(DB_1); try (Txn roTxn = roEnv.txnRead()) { assertThat(roDb.get(roTxn, bb(1)), notNullValue()); @@ -330,18 +279,14 @@ public void readOnlySupported() throws IOException { } @Test - @SuppressFBWarnings("DMI_RANDOM_USED_ONLY_ONCE") public void setMapSize() throws IOException { final File path = tmp.newFolder(); 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(path)) { + try (Env env = + create().setMaxReaders(1).setMapSize(KIBIBYTES.toBytes(256)).setMaxDbs(1).open(path)) { final Dbi db = env.openDbi(DB_1, MDB_CREATE); db.put(bb(1), bb(42)); @@ -359,7 +304,7 @@ public void setMapSize() throws IOException { } assertThat(mapFullExThrown, is(true)); - env.setMapSize(KIBIBYTES.toBytes(512)); + env.setMapSize(KIBIBYTES.toBytes(1024)); try (Txn roTxn = env.txnRead()) { assertThat(db.get(roTxn, bb(1)).getInt(), is(42)); @@ -384,9 +329,7 @@ public void setMapSize() throws IOException { @Test public void stats() throws IOException { final File path = tmp.newFile(); - try (Env env = create() - .setMaxReaders(1) - .open(path, MDB_NOSUBDIR)) { + try (Env env = create().setMaxReaders(1).open(path, MDB_NOSUBDIR)) { final Stat stat = env.stat(); assertThat(stat, is(notNullValue())); assertThat(stat.branchPages, is(0L)); @@ -409,5 +352,4 @@ public void testDefaultOpen() throws IOException { db.put(allocateDirect(1), allocateDirect(1)); } } - } diff --git a/src/test/java/org/lmdbjava/GarbageCollectionTest.java b/src/test/java/org/lmdbjava/GarbageCollectionTest.java index 49d56b1d..7615f7f0 100644 --- a/src/test/java/org/lmdbjava/GarbageCollectionTest.java +++ b/src/test/java/org/lmdbjava/GarbageCollectionTest.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static java.nio.ByteBuffer.allocateDirect; @@ -29,32 +24,24 @@ import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.mockito.MockedStatic; import org.mockito.Mockito; -@SuppressFBWarnings({"DM_GC", "RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT"}) -@SuppressWarnings("PMD.DoNotCallGarbageCollectionExplicitly") public class GarbageCollectionTest { private static final String DB_NAME = "my DB"; private static final String KEY_PREFIX = "Uncorruptedkey"; private static final String VAL_PREFIX = "Uncorruptedval"; - @Rule - public final TemporaryFolder tmp = new TemporaryFolder(); + @Rule public final TemporaryFolder tmp = new TemporaryFolder(); @Test public void buffersNotGarbageCollectedTest() throws IOException { final File path = tmp.newFolder(); - try (Env env = create() - .setMapSize(2_085_760_999) - .setMaxDbs(1) - .open(path)) { + try (Env env = create().setMapSize(2_085_760_999).setMaxDbs(1).open(path)) { final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); try (Txn txn = env.txnWrite()) { @@ -66,12 +53,14 @@ public void buffersNotGarbageCollectedTest() throws IOException { // 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; - }); + 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++) { @@ -105,13 +94,11 @@ public void buffersNotGarbageCollectedTest() throws IOException { } } - private void putBuffer(final Dbi db, final Txn txn, - final int i) { + private void putBuffer(final Dbi db, final Txn txn, final int i) { final ByteBuffer key = allocateDirect(24); final ByteBuffer val = allocateDirect(24); key.put((KEY_PREFIX + i).getBytes(UTF_8)).flip(); val.put((VAL_PREFIX + i).getBytes(UTF_8)).flip(); db.put(txn, key, val); } - } diff --git a/src/test/java/org/lmdbjava/KeyRangeTest.java b/src/test/java/org/lmdbjava/KeyRangeTest.java index 3ea8c146..6e104bbf 100644 --- a/src/test/java/org/lmdbjava/KeyRangeTest.java +++ b/src/test/java/org/lmdbjava/KeyRangeTest.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static org.hamcrest.CoreMatchers.is; @@ -45,7 +40,6 @@ import java.util.ArrayList; import java.util.List; - import org.junit.Before; import org.junit.Test; import org.lmdbjava.KeyRangeType.CursorOp; @@ -54,10 +48,8 @@ /** * Test {@link KeyRange}. * - *

- * This test case focuses on the contractual correctness detailed in - * {@link KeyRangeType}. It does this using integers as per the JavaDoc - * examples. + *

This test case focuses on the contractual correctness detailed in {@link KeyRangeType}. It + * does this using integers as per the JavaDoc examples. */ public final class KeyRangeTest { @@ -203,8 +195,7 @@ private void verify(final KeyRange range, final int... expected) { IteratorOp op; do { - op = range.getType().iteratorOp(range.getStart(), range.getStop(), buff, - Integer::compare); + op = range.getType().iteratorOp(range.getStart(), range.getStop(), buff, Integer::compare); switch (op) { case CALL_NEXT_OP: buff = cursor.apply(range.getType().nextOp(), range.getStart()); @@ -229,13 +220,12 @@ private void verify(final KeyRange range, final int... expected) { /** * Cursor that behaves like an LMDB cursor would. * - *

- * We use Integer rather than the primitive to represent a - * null buffer. + *

We use Integer rather than the primitive to represent a null + * buffer. */ private static final class FakeCursor { - private static final int[] KEYS = new int[]{2, 4, 6, 8}; + private static final int[] KEYS = new int[] {2, 4, 6, 8}; private int position; Integer apply(final CursorOp op, final Integer startKey) { @@ -302,7 +292,5 @@ Integer prev() { void reset() { position = 0; } - } - } diff --git a/src/test/java/org/lmdbjava/LibraryTest.java b/src/test/java/org/lmdbjava/LibraryTest.java index df5ae89d..6dcfcee9 100644 --- a/src/test/java/org/lmdbjava/LibraryTest.java +++ b/src/test/java/org/lmdbjava/LibraryTest.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static java.lang.Long.BYTES; @@ -29,9 +24,7 @@ import org.junit.Test; import org.lmdbjava.Library.MDB_envinfo; -/** - * Test {@link Library}. - */ +/** Test {@link Library}. */ public final class LibraryTest { @Test diff --git a/src/test/java/org/lmdbjava/MaskedFlagTest.java b/src/test/java/org/lmdbjava/MaskedFlagTest.java index 0dbc8b62..918bf922 100644 --- a/src/test/java/org/lmdbjava/MaskedFlagTest.java +++ b/src/test/java/org/lmdbjava/MaskedFlagTest.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static org.hamcrest.CoreMatchers.is; @@ -31,9 +26,7 @@ import org.junit.Test; -/** - * Test {@link MaskedFlag}. - */ +/** Test {@link MaskedFlag}. */ public final class MaskedFlagTest { @Test @@ -61,10 +54,10 @@ public void masking() { final EnvFlags[] nullFlags = null; assertThat(mask(nullFlags), is(0)); - final EnvFlags[] emptyFlags = new EnvFlags[]{}; + final EnvFlags[] emptyFlags = new EnvFlags[] {}; assertThat(mask(emptyFlags), is(0)); - final EnvFlags[] nullElementZero = new EnvFlags[]{null}; + final EnvFlags[] nullElementZero = new EnvFlags[] {null}; assertThat(nullElementZero, is(arrayWithSize(1))); assertThat(mask(nullElementZero), is(0)); diff --git a/src/test/java/org/lmdbjava/MetaTest.java b/src/test/java/org/lmdbjava/MetaTest.java index 922c4368..be0faa87 100644 --- a/src/test/java/org/lmdbjava/MetaTest.java +++ b/src/test/java/org/lmdbjava/MetaTest.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static org.hamcrest.CoreMatchers.is; @@ -31,9 +26,7 @@ import org.junit.Test; import org.lmdbjava.Meta.Version; -/** - * Test {@link Meta}. - */ +/** Test {@link Meta}. */ public final class MetaTest { @Test @@ -43,8 +36,7 @@ public void coverPrivateConstructors() { @Test public void errCode() { - assertThat(error(MDB_CORRUPTED), is( - "MDB_CORRUPTED: Located page was wrong type")); + assertThat(error(MDB_CORRUPTED), is("MDB_CORRUPTED: Located page was wrong type")); } @Test @@ -54,5 +46,4 @@ public void version() { assertThat(v.major, is(0)); assertThat(v.minor, is(9)); } - } diff --git a/src/test/java/org/lmdbjava/ResultCodeMapperTest.java b/src/test/java/org/lmdbjava/ResultCodeMapperTest.java index 723c22ca..7a3764ed 100644 --- a/src/test/java/org/lmdbjava/ResultCodeMapperTest.java +++ b/src/test/java/org/lmdbjava/ResultCodeMapperTest.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static java.lang.Integer.MAX_VALUE; @@ -33,7 +28,6 @@ import java.util.HashSet; import java.util.Set; - import org.junit.Test; import org.lmdbjava.Cursor.FullException; import org.lmdbjava.Dbi.BadDbiException; @@ -57,9 +51,7 @@ import org.lmdbjava.Txn.BadReaderLockException; import org.lmdbjava.Txn.TxFullException; -/** - * Test {@link ResultCodeMapper} and {@link LmdbException}. - */ +/** Test {@link ResultCodeMapper} and {@link LmdbException}. */ public final class ResultCodeMapperTest { private static final Set EXCEPTIONS = new HashSet<>(); @@ -161,5 +153,4 @@ public void mapperReturnsUnique() { public void noDuplicateResultCodes() { assertThat(RESULT_CODES.size(), is(EXCEPTIONS.size())); } - } diff --git a/src/test/java/org/lmdbjava/TargetNameTest.java b/src/test/java/org/lmdbjava/TargetNameTest.java index eec38233..b598be55 100644 --- a/src/test/java/org/lmdbjava/TargetNameTest.java +++ b/src/test/java/org/lmdbjava/TargetNameTest.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static org.hamcrest.CoreMatchers.is; @@ -28,9 +23,7 @@ import org.junit.Test; -/** - * Test {@link TargetName}. - */ +/** Test {@link TargetName}. */ public final class TargetNameTest { private static final String NONE = ""; diff --git a/src/test/java/org/lmdbjava/TestUtils.java b/src/test/java/org/lmdbjava/TestUtils.java index 05dfb0cc..42dcf052 100644 --- a/src/test/java/org/lmdbjava/TestUtils.java +++ b/src/test/java/org/lmdbjava/TestUtils.java @@ -1,50 +1,39 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static io.netty.buffer.PooledByteBufAllocator.DEFAULT; import static java.lang.Integer.BYTES; import static java.nio.ByteBuffer.allocateDirect; +import io.netty.buffer.ByteBuf; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.nio.ByteBuffer; - -import io.netty.buffer.ByteBuf; import org.agrona.MutableDirectBuffer; import org.agrona.concurrent.UnsafeBuffer; -/** - * Static constants and methods that are convenient when writing LMDB-related - * tests. - */ +/** Static constants and methods that are convenient when writing LMDB-related tests. */ final class TestUtils { public static final String DB_1 = "test-db-1"; - @SuppressWarnings("PMD.AvoidUsingOctalValues") public static final int POSIX_MODE = 0664; - private TestUtils() { - } + private TestUtils() {} static byte[] ba(final int value) { final MutableDirectBuffer b = new UnsafeBuffer(new byte[4]); @@ -63,9 +52,11 @@ static void invokePrivateConstructor(final Class clazz) { final Constructor c = clazz.getDeclaredConstructor(); c.setAccessible(true); c.newInstance(); - } catch (final NoSuchMethodException | InstantiationException - | IllegalAccessException | IllegalArgumentException - | InvocationTargetException e) { + } catch (final NoSuchMethodException + | InstantiationException + | IllegalAccessException + | IllegalArgumentException + | InvocationTargetException e) { throw new LmdbException("Private construction failed", e); } } @@ -81,5 +72,4 @@ static ByteBuf nb(final int value) { b.writeInt(value); return b; } - } diff --git a/src/test/java/org/lmdbjava/TutorialTest.java b/src/test/java/org/lmdbjava/TutorialTest.java index 0385d952..b86c1d2a 100644 --- a/src/test/java/org/lmdbjava/TutorialTest.java +++ b/src/test/java/org/lmdbjava/TutorialTest.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static java.nio.ByteBuffer.allocateDirect; @@ -44,7 +39,6 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.ExecutorService; - import org.agrona.DirectBuffer; import org.agrona.MutableDirectBuffer; import org.agrona.concurrent.UnsafeBuffer; @@ -56,25 +50,20 @@ /** * Welcome to LmdbJava! * - *

- * This short tutorial will walk you through using LmdbJava step-by-step. + *

This short tutorial will walk you through using LmdbJava step-by-step. * - *

- * If you are using a 64-bit Windows, Linux or OS X machine, you can simply run - * this tutorial by adding the LmdbJava JAR to your classpath. It includes the - * required system libraries. If you are using another 64-bit platform, you'll - * need to install the LMDB system library yourself. 32-bit platforms are not - * supported. + *

If you are using a 64-bit Windows, Linux or OS X machine, you can simply run this tutorial by + * adding the LmdbJava JAR to your classpath. It includes the required system libraries. If you are + * using another 64-bit platform, you'll need to install the LMDB system library yourself. 32-bit + * platforms are not supported. * - *

- * Start the JVM with arguments --add-opens java.base/java.nio=ALL-UNNAMED + *

Start the JVM with arguments --add-opens java.base/java.nio=ALL-UNNAMED * --add-opens java.base/sun.nio.ch=ALL-UNNAMED to suppress JVM warnings. */ public final class TutorialTest { private static final String DB_NAME = "my DB"; - @Rule - public final TemporaryFolder tmp = new TemporaryFolder(); + @Rule public final TemporaryFolder tmp = new TemporaryFolder(); /** * In this first tutorial we will use LmdbJava with some basic defaults. @@ -82,7 +71,6 @@ public final class TutorialTest { * @throws IOException if a path was unavailable for memory mapping */ @Test - @SuppressWarnings("ConvertToTryWithResources") public void tutorial1() throws IOException { // We need a storage directory first. // The path cannot be on a remote file system. @@ -90,15 +78,16 @@ public void tutorial1() throws IOException { // 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(path); + 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(path); // 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. @@ -149,11 +138,10 @@ public void tutorial1() throws IOException { /** * In this second tutorial we'll learn more about LMDB's ACID Txns. * - * @throws IOException if a path was unavailable for memory mapping + * @throws IOException if a path was unavailable for memory mapping * @throws InterruptedException if executor shutdown interrupted */ @Test - @SuppressWarnings("ConvertToTryWithResources") public void tutorial2() throws IOException, InterruptedException { final Env env = createSimpleEnv(tmp.newFolder()); final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); @@ -188,14 +176,15 @@ public void tutorial2() throws IOException, InterruptedException { // // 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.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); @@ -220,14 +209,13 @@ public void tutorial2() throws IOException, InterruptedException { } /** - * In this third tutorial we'll have a look at the Cursor. Up until now we've - * just used Dbi, which is good enough for simple cases but unsuitable if you - * don't know the key to fetch, or want to iterate over all the data etc. + * In this third tutorial we'll have a look at the Cursor. Up until now we've just used Dbi, which + * is good enough for simple cases but unsuitable if you don't know the key to fetch, or want to + * iterate over all the data etc. * * @throws IOException if a path was unavailable for memory mapping */ @Test - @SuppressWarnings("ConvertToTryWithResources") public void tutorial3() throws IOException { final Env env = createSimpleEnv(tmp.newFolder()); final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); @@ -296,13 +284,12 @@ public void tutorial3() throws IOException { } /** - * In this fourth tutorial we'll take a quick look at the iterators. These are - * a more Java idiomatic form of using the Cursors we looked at in tutorial 3. + * In this fourth tutorial we'll take a quick look at the iterators. These are a more Java + * idiomatic form of using the Cursors we looked at in tutorial 3. * * @throws IOException if a path was unavailable for memory mapping */ @Test - @SuppressWarnings("ConvertToTryWithResources") public void tutorial4() throws IOException { final Env env = createSimpleEnv(tmp.newFolder()); final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); @@ -332,8 +319,7 @@ public void tutorial4() throws IOException { } // Iterate backward in terms of key ordering starting with the last key. - try (CursorIterable ci = db.iterate(txn, - KeyRange.allBackward())) { + try (CursorIterable ci = db.iterate(txn, KeyRange.allBackward())) { for (final KeyVal kv : ci) { assertThat(kv.key(), notNullValue()); assertThat(kv.val(), notNullValue()); @@ -362,7 +348,6 @@ public void tutorial4() throws IOException { * @throws IOException if a path was unavailable for memory mapping */ @Test - @SuppressWarnings("ConvertToTryWithResources") public void tutorial5() throws IOException { final Env env = createSimpleEnv(tmp.newFolder()); @@ -410,20 +395,19 @@ public void tutorial5() throws IOException { } /** - * Next up we'll show you how to easily check your platform (operating system - * and Java version) is working properly with LmdbJava and the embedded LMDB - * native library. + * Next up we'll show you how to easily check your platform (operating system and Java version) is + * working properly with LmdbJava and the embedded LMDB native library. * * @throws IOException if a path was unavailable for memory mapping */ @Test - @SuppressWarnings("ConvertToTryWithResources") public void tutorial6() throws IOException { // 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(tmp.newFolder()); + final Env env = + create(PROXY_OPTIMAL) + .setMapSize(10_485_760) + .setMaxDbs(Verifier.DBI_COUNT) + .open(tmp.newFolder()); // Create a Verifier (it's a Callable for those needing full control). final Verifier v = new Verifier(env); @@ -441,15 +425,12 @@ public void tutorial6() throws IOException { * @throws IOException if a path was unavailable for memory mapping */ @Test - @SuppressWarnings("ConvertToTryWithResources") public void tutorial7() throws IOException { // 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(tmp.newFolder()); + final Env env = + create(PROXY_DB).setMapSize(10_485_760).setMaxDbs(1).open(tmp.newFolder()); final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); @@ -468,12 +449,10 @@ public void tutorial7() throws IOException { c.put(key, val); c.seek(MDB_FIRST); - assertThat(c.key().getStringWithoutLengthUtf8(0, env.getMaxKeySize()), - startsWith("ggg")); + assertThat(c.key().getStringWithoutLengthUtf8(0, env.getMaxKeySize()), startsWith("ggg")); c.seek(MDB_LAST); - assertThat(c.key().getStringWithoutLengthUtf8(0, env.getMaxKeySize()), - startsWith("yyy")); + 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... @@ -487,8 +466,7 @@ public void tutorial7() throws IOException { c.put(key, val); c.seek(MDB_FIRST); assertThat(c.key().capacity(), is(keyLen)); - assertThat(c.key().getStringWithoutLengthUtf8(0, c.key().capacity()), - is("12characters")); + assertThat(c.key().getStringWithoutLengthUtf8(0, c.key().capacity()), is("12characters")); // To store bigger values again, just wrap the original buffer. key.wrap(keyBb); @@ -505,11 +483,6 @@ public void tutorial7() throws IOException { // 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 File path) { - return create() - .setMapSize(10_485_760) - .setMaxDbs(1) - .setMaxReaders(1) - .open(path); + return 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 775a10a6..e768e424 100644 --- a/src/test/java/org/lmdbjava/TxnTest.java +++ b/src/test/java/org/lmdbjava/TxnTest.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; @@ -49,7 +44,6 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicLong; - import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -65,13 +59,10 @@ import org.lmdbjava.Txn.ReadWriteRequiredException; import org.lmdbjava.Txn.ResetException; -/** - * Test {@link Txn}. - */ +/** Test {@link Txn}. */ public final class TxnTest { - @Rule - public final TemporaryFolder tmp = new TemporaryFolder(); + @Rule public final TemporaryFolder tmp = new TemporaryFolder(); private Env env; private File path; @@ -83,11 +74,12 @@ public void after() { @Before public void before() throws IOException { path = tmp.newFile(); - env = create() - .setMapSize(KIBIBYTES.toBytes(256)) - .setMaxReaders(1) - .setMaxDbs(2) - .open(path, POSIX_MODE, MDB_NOSUBDIR); + env = + create() + .setMapSize(KIBIBYTES.toBytes(256)) + .setMaxReaders(1) + .setMaxDbs(2) + .open(path, POSIX_MODE, MDB_NOSUBDIR); } @Test(expected = BadValueSizeException.class) @@ -129,16 +121,14 @@ public void rangeSearch() { } assertEquals(3, keysFound.size()); - } } @Test public void readOnlyTxnAllowedInReadOnlyEnv() { env.openDbi(DB_1, MDB_CREATE); - try (Env roEnv = create() - .setMaxReaders(1) - .open(path, MDB_NOSUBDIR, MDB_RDONLY_ENV)) { + try (Env roEnv = + create().setMaxReaders(1).open(path, MDB_NOSUBDIR, MDB_RDONLY_ENV)) { assertThat(roEnv.txnRead(), is(notNullValue())); } } @@ -147,9 +137,8 @@ public void readOnlyTxnAllowedInReadOnlyEnv() { public void readWriteTxnDeniedInReadOnlyEnv() { env.openDbi(DB_1, MDB_CREATE); env.close(); - try (Env roEnv = create() - .setMaxReaders(1) - .open(path, MDB_NOSUBDIR, MDB_RDONLY_ENV)) { + try (Env roEnv = + create().setMaxReaders(1).open(path, MDB_NOSUBDIR, MDB_RDONLY_ENV)) { roEnv.txnWrite(); // error } } @@ -224,7 +213,6 @@ public void txCannotCommitTwice() { } @Test(expected = AlreadyClosedException.class) - @SuppressWarnings("ResultOfObjectAllocationIgnored") public void txConstructionDeniedIfEnvClosed() { env.close(); env.txnRead(); @@ -269,7 +257,7 @@ public void txResetDeniedIfEnvClosed() { @Test public void txParent() { try (Txn txRoot = env.txnWrite(); - Txn txChild = env.txn(txRoot)) { + Txn txChild = env.txn(txRoot)) { assertThat(txRoot.getParent(), is(nullValue())); assertThat(txChild.getParent(), is(txRoot)); } @@ -278,7 +266,7 @@ public void txParent() { @Test(expected = AlreadyClosedException.class) public void txParentDeniedIfEnvClosed() { try (Txn txRoot = env.txnWrite(); - Txn txChild = env.txn(txRoot)) { + Txn txChild = env.txn(txRoot)) { env.close(); assertThat(txChild.getParent(), is(txRoot)); } @@ -363,5 +351,4 @@ public void zeroByteKeysRejected() throws IOException { assertThat(key.remaining(), is(0)); // because key.flip() skipped dbi.put(key, bb(2)); } - } diff --git a/src/test/java/org/lmdbjava/VerifierTest.java b/src/test/java/org/lmdbjava/VerifierTest.java index 41f4e774..c57e26dc 100644 --- a/src/test/java/org/lmdbjava/VerifierTest.java +++ b/src/test/java/org/lmdbjava/VerifierTest.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% */ - package org.lmdbjava; import static com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; @@ -30,31 +25,27 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.TimeUnit; - import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -/** - * Test {@link Verifier}. - */ +/** Test {@link Verifier}. */ public final class VerifierTest { - @Rule - public final TemporaryFolder tmp = new TemporaryFolder(); + @Rule public final TemporaryFolder tmp = new TemporaryFolder(); @Test public void verification() throws IOException { final File path = tmp.newFile(); - try (Env env = create() - .setMaxReaders(1) - .setMaxDbs(Verifier.DBI_COUNT) - .setMapSize(MEBIBYTES.toBytes(10)) - .open(path, MDB_NOSUBDIR)) { + try (Env env = + create() + .setMaxReaders(1) + .setMaxDbs(Verifier.DBI_COUNT) + .setMapSize(MEBIBYTES.toBytes(10)) + .open(path, MDB_NOSUBDIR)) { final Verifier v = new Verifier(env); final int seconds = Integer.getInteger("verificationSeconds", 2); assertThat(v.runFor(seconds, TimeUnit.SECONDS), greaterThan(1L)); } } - } diff --git a/src/test/java/org/lmdbjava/package-info.java b/src/test/java/org/lmdbjava/package-info.java index baaacbe8..950fc2db 100644 --- a/src/test/java/org/lmdbjava/package-info.java +++ b/src/test/java/org/lmdbjava/package-info.java @@ -1,24 +1,16 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * 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 - * + * + * 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. - * #L% - */ - -/** - * Lightning Memory Database (LMDB) for Java (LmdbJava) tests. */ package org.lmdbjava; From bdde404e16f6b2324b2d2f9043e3cd9fe5f6676b Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 17 Feb 2025 14:20:15 +1100 Subject: [PATCH 005/139] Update to CodeQL v3 --- .github/workflows/codeql.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index dd93488f..b503370a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -27,15 +27,15 @@ jobs: uses: actions/checkout@v3 - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} queries: +security-and-quality - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: category: "/language:${{ matrix.language }}" From 27123765132331540e60adbaadb4d06f63516f1d Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 17 Feb 2025 15:03:45 +1100 Subject: [PATCH 006/139] Update contributor docs to reflect build system configuration --- CONTRIBUTING.md | 10 ++++++---- README.md | 5 ----- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 53a6b14e..c37bf055 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,11 +7,13 @@ This will run: * Tests * Initial Test Coverage -* Checkstyle -* PMD -* FindBugs -* XML Formatting +* Source Code Formatting * License Header Management `mvn clean verify` is also run by CI, but it's quicker and easier to run before submitting. + +### Releasing + +GitHub Actions will perform an official release whenever a developer executes +`mvn release:clean release:prepare`. diff --git a/README.md b/README.md index 62c5abe1..048e57bc 100644 --- a/README.md +++ b/README.md @@ -73,11 +73,6 @@ system package managers, running `cross-compile.sh` on a supported system, or copying it from the `org/lmdbjava` directory of any recent, officially released LmdbJava JAR. -### Releasing - -GitHub Actions will perform an official release whenever a developer executes -`mvn release:clean release:prepare`. - ### Contributing Contributions are welcome! Please see the [Contributing Guidelines](CONTRIBUTING.md). From 6ca74b9f71695057991d9940564345e0074f0fc0 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 17 Feb 2025 15:14:22 +1100 Subject: [PATCH 007/139] Update to Guava 33.4.0-jre --- pom.xml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 9e695d0f..8eb9523d 100644 --- a/pom.xml +++ b/pom.xml @@ -47,14 +47,8 @@ com.google.guava guava - 32.1.3-jre + 33.4.0-jre test - - - com.google.code.findbugs - jsr305 - - com.jakewharton.byteunits From f0d61735382c37674826bebeacf3a6d3486d668d Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 17 Feb 2025 15:14:31 +1100 Subject: [PATCH 008/139] Update to Netty 4.1.118.Final --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8eb9523d..616ea85d 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ io.netty netty-buffer - 4.1.101.Final + 4.1.118.Final true From ce6451f8e1087ebd1165d3576d553b86d596fb14 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 17 Feb 2025 15:14:42 +1100 Subject: [PATCH 009/139] Update to Agrona 2.0.1 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 616ea85d..4dd7cd61 100644 --- a/pom.xml +++ b/pom.xml @@ -77,7 +77,7 @@ org.agrona agrona - 1.20.0 + 2.0.1 true @@ -397,7 +397,7 @@ org.apache.maven.plugins maven-surefire-plugin - @{argLine} --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED + @{argLine} --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/jdk.internal.misc=ALL-UNNAMED 1 false From 9cd5c42ae0d51948cd12cc914fc448243dff314c Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 17 Feb 2025 15:23:28 +1100 Subject: [PATCH 010/139] Update to Agrona 1.22.0 and note JDK 17+ for later versions --- pom.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 4dd7cd61..6f53c90e 100644 --- a/pom.xml +++ b/pom.xml @@ -77,7 +77,8 @@ org.agrona agrona - 2.0.1 + + 1.22.0 true @@ -397,7 +398,7 @@ org.apache.maven.plugins maven-surefire-plugin - @{argLine} --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/jdk.internal.misc=ALL-UNNAMED + @{argLine} --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED 1 false From 928cf7baaabf40914a43a137ebc7d47797ead695 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Thu, 20 Feb 2025 12:26:13 +1100 Subject: [PATCH 011/139] Add url element to pom.xml to satisfy OSSRH deployment rules --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 6f53c90e..e71e2271 100644 --- a/pom.xml +++ b/pom.xml @@ -333,6 +333,7 @@ + https://github.com/lmdbjava/lmdbjava 2016 The LmdbJava Open Source Project From 0025a4875c2b08645e93c8acc6f2225b2a7c4046 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Thu, 20 Feb 2025 12:28:16 +1100 Subject: [PATCH 012/139] [maven-release-plugin] prepare release lmdbjava-0.9.1 --- pom.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index e71e2271..e7d1d298 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.lmdbjava lmdbjava - 0.9.1-SNAPSHOT + 0.9.1 jar LmdbJava Low latency Java API for the ultra-fast, embedded Symas Lightning Database (LMDB) @@ -184,19 +184,19 @@ - + [${maven.enforcer.mvn},) [${maven.enforcer.java},) - - + + true - + @@ -351,7 +351,7 @@ Kristoffer Sjogren stoffe -at- gmail.com http://stoffe.deephacks.org/ - + +1 @@ -367,7 +367,7 @@ scm:git:git@github.com:lmdbjava/lmdbjava.git scm:git:git@github.com:lmdbjava/lmdbjava.git git@github.com:lmdbjava/lmdbjava.git - HEAD + lmdbjava-0.9.1 GitHub Issues From 154df33aa6b196a96e8abe7a3e145b62cc08f382 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Thu, 20 Feb 2025 12:28:23 +1100 Subject: [PATCH 013/139] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index e7d1d298..b3012729 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.lmdbjava lmdbjava - 0.9.1 + 0.9.2-SNAPSHOT jar LmdbJava Low latency Java API for the ultra-fast, embedded Symas Lightning Database (LMDB) @@ -367,7 +367,7 @@ scm:git:git@github.com:lmdbjava/lmdbjava.git scm:git:git@github.com:lmdbjava/lmdbjava.git git@github.com:lmdbjava/lmdbjava.git - lmdbjava-0.9.1 + HEAD GitHub Issues 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 014/139] 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 015/139] 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 016/139] 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 017/139] 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 018/139] 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 019/139] 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 020/139] 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 ddca4bd528089d54b1e744ffac2ecde5525f203e Mon Sep 17 00:00:00 2001 From: LambdAurora Date: Tue, 8 Jul 2025 18:10:00 +0200 Subject: [PATCH 021/139] Fix comparator lost buffer ref from proxy in Dbi. TODO: address failing dbiWithComparatorThreadSafetyByteArray test. --- src/main/java/org/lmdbjava/Dbi.java | 8 +- .../java/org/lmdbjava/ComparatorTest.java | 13 ++- src/test/java/org/lmdbjava/DbiTest.java | 94 ++++++++++++++----- src/test/java/org/lmdbjava/TestUtils.java | 5 + 4 files changed, 89 insertions(+), 31 deletions(-) diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index ad1bb5a7..4a4cc350 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -81,10 +81,10 @@ public final class Dbi { if (nativeCb) { this.ccb = (keyA, keyB) -> { - final T compKeyA = proxy.allocate(); - final T compKeyB = proxy.allocate(); - proxy.out(compKeyA, keyA, keyA.address()); - proxy.out(compKeyB, keyB, keyB.address()); + T compKeyA = proxy.allocate(); + T compKeyB = proxy.allocate(); + compKeyA = proxy.out(compKeyA, keyA, keyA.address()); + compKeyB = proxy.out(compKeyB, keyB, keyB.address()); final int result = this.comparator.compare(compKeyA, compKeyB); proxy.deallocate(compKeyA); proxy.deallocate(compKeyB); diff --git a/src/test/java/org/lmdbjava/ComparatorTest.java b/src/test/java/org/lmdbjava/ComparatorTest.java index 3e265cee..3c7e7a4d 100644 --- a/src/test/java/org/lmdbjava/ComparatorTest.java +++ b/src/test/java/org/lmdbjava/ComparatorTest.java @@ -67,11 +67,12 @@ public static Object[] data() { final ComparatorRunner string = new StringRunner(); final ComparatorRunner db = new DirectBufferRunner(); final ComparatorRunner ba = new ByteArrayRunner(); + final ComparatorRunner baUnsigned = new UnsignedByteArrayRunner(); final ComparatorRunner bb = new ByteBufferRunner(); final ComparatorRunner netty = new NettyRunner(); final ComparatorRunner gub = new GuavaUnsignedBytes(); final ComparatorRunner gsb = new GuavaSignedBytes(); - return new Object[] {string, db, ba, bb, netty, gub, gsb}; + return new Object[] {string, db, ba, baUnsigned, bb, netty, gub, gsb}; } private static byte[] buffer(final int... bytes) { @@ -140,6 +141,16 @@ public int compare(final byte[] o1, final byte[] o2) { } } + /** Tests {@link ByteArrayProxy} (unsigned). */ + private static final class UnsignedByteArrayRunner implements ComparatorRunner { + + @Override + public int compare(final byte[] o1, final byte[] o2) { + final Comparator c = PROXY_BA.getUnsignedComparator(); + return c.compare(o1, o2); + } + } + /** Tests {@link ByteBufferProxy}. */ private static final class ByteBufferRunner implements ComparatorRunner { diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 1fa80f6e..2e87830c 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -46,9 +46,7 @@ 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.DB_1; -import static org.lmdbjava.TestUtils.ba; -import static org.lmdbjava.TestUtils.bb; +import static org.lmdbjava.TestUtils.*; import java.io.File; import java.io.IOException; @@ -63,7 +61,7 @@ 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.*; import org.agrona.concurrent.UnsafeBuffer; import org.hamcrest.Matchers; import org.junit.After; @@ -82,6 +80,7 @@ public final class DbiTest { @Rule public final TemporaryFolder tmp = new TemporaryFolder(); private Env env; + private Env envBa; @After public void after() { @@ -97,6 +96,13 @@ public void before() throws IOException { .setMaxReaders(2) .setMaxDbs(2) .open(path, MDB_NOSUBDIR); + final File pathBa = tmp.newFile(); + envBa = + create(PROXY_BA) + .setMapSize(MEBIBYTES.toBytes(64)) + .setMaxReaders(2) + .setMaxDbs(2) + .open(pathBa, MDB_NOSUBDIR); } @Test(expected = ConstantDerivedException.class) @@ -117,20 +123,41 @@ public void customComparator() { } return lexical * -1; }; - final Dbi db = env.openDbi(DB_1, reverseOrder, true, MDB_CREATE); - try (Txn txn = env.txnWrite()) { - assertThat(db.put(txn, bb(2), bb(3)), is(true)); - assertThat(db.put(txn, bb(4), bb(6)), is(true)); - assertThat(db.put(txn, bb(6), bb(7)), is(true)); - assertThat(db.put(txn, bb(8), bb(7)), is(true)); + doCustomComparator(env, reverseOrder, TestUtils::bb, ByteBuffer::getInt); + } + + @Test + public void customComparatorByteArray() { + final Comparator reverseOrder = + (o1, o2) -> { + final int lexical = PROXY_BA.getComparator().compare(o1, o2); + if (lexical == 0) { + return 0; + } + return lexical * -1; + }; + doCustomComparator(envBa, reverseOrder, TestUtils::ba, TestUtils::fromBa); + } + + private void doCustomComparator( + Env env, + Comparator comparator, + IntFunction serializer, + ToIntFunction deserializer) { + final Dbi db = env.openDbi(DB_1, comparator, true, MDB_CREATE); + try (Txn txn = env.txnWrite()) { + assertThat(db.put(txn, serializer.apply(2), serializer.apply(3)), is(true)); + assertThat(db.put(txn, serializer.apply(4), serializer.apply(6)), is(true)); + assertThat(db.put(txn, serializer.apply(6), serializer.apply(7)), is(true)); + assertThat(db.put(txn, serializer.apply(8), serializer.apply(7)), is(true)); txn.commit(); } - try (Txn txn = env.txnRead(); - CursorIterable ci = db.iterate(txn, atMost(bb(4)))) { - final Iterator> iter = ci.iterator(); - assertThat(iter.next().key().getInt(), is(8)); - assertThat(iter.next().key().getInt(), is(6)); - assertThat(iter.next().key().getInt(), is(4)); + try (Txn txn = env.txnRead(); + 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)); + assertThat(deserializer.applyAsInt(iter.next().key()), is(4)); } } @@ -143,9 +170,24 @@ public void dbOpenMaxDatabases() { @Test public void dbiWithComparatorThreadSafety() { + doDbiWithComparatorThreadSafety( + env, PROXY_OPTIMAL::getComparator, TestUtils::bb, ByteBuffer::getInt); + } + + @Test + public void dbiWithComparatorThreadSafetyByteArray() { + doDbiWithComparatorThreadSafety( + envBa, PROXY_BA::getComparator, TestUtils::ba, TestUtils::fromBa); + } + + public void doDbiWithComparatorThreadSafety( + Env env, + Function> comparator, + IntFunction serializer, + ToIntFunction deserializer) { final DbiFlags[] flags = new DbiFlags[] {MDB_CREATE, MDB_INTEGERKEY}; - final Comparator c = PROXY_OPTIMAL.getComparator(flags); - final Dbi db = env.openDbi(DB_1, c, true, flags); + final Comparator c = comparator.apply(flags); + final Dbi db = env.openDbi(DB_1, c, true, flags); final List keys = range(0, 1_000).boxed().collect(toList()); @@ -155,25 +197,25 @@ public void dbiWithComparatorThreadSafety() { pool.submit( () -> { while (proceed.get()) { - try (Txn txn = env.txnRead()) { - db.get(txn, bb(50)); + try (Txn txn = env.txnRead()) { + db.get(txn, serializer.apply(50)); } } }); for (final Integer key : keys) { - try (Txn txn = env.txnWrite()) { - db.put(txn, bb(key), bb(3)); + 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(); + 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(iter.next().key().getInt()); + result.add(deserializer.applyAsInt(iter.next().key())); } assertThat(result, Matchers.contains(keys.toArray(new Integer[0]))); diff --git a/src/test/java/org/lmdbjava/TestUtils.java b/src/test/java/org/lmdbjava/TestUtils.java index 42dcf052..fd070a31 100644 --- a/src/test/java/org/lmdbjava/TestUtils.java +++ b/src/test/java/org/lmdbjava/TestUtils.java @@ -41,6 +41,11 @@ static byte[] ba(final int value) { return b.byteArray(); } + static int fromBa(final byte[] ba) { + final MutableDirectBuffer b = new UnsafeBuffer(ba); + return b.getInt(0); + } + static ByteBuffer bb(final int value) { final ByteBuffer bb = allocateDirect(BYTES); bb.putInt(value).flip(); From 236b7c9b912e271bfd6a57c612d12ca641b91d13 Mon Sep 17 00:00:00 2001 From: LambdAurora Date: Wed, 9 Jul 2025 10:50:24 +0200 Subject: [PATCH 022/139] Use ByteBuffer for int serialization into bytes. This proves to be more reliable than Agreno's UnsafeBuffers. --- src/test/java/org/lmdbjava/DbiTest.java | 3 +-- src/test/java/org/lmdbjava/TestUtils.java | 9 ++++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 2e87830c..75c23931 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -62,7 +62,6 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.*; -import org.agrona.concurrent.UnsafeBuffer; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Before; @@ -381,7 +380,7 @@ public void putCommitGetByteArray() throws IOException { try (Txn txn = envBa.txnWrite()) { final byte[] found = db.get(txn, ba(5)); assertNotNull(found); - assertThat(new UnsafeBuffer(txn.val()).getInt(0), is(5)); + assertThat(fromBa(txn.val()), is(5)); } } } diff --git a/src/test/java/org/lmdbjava/TestUtils.java b/src/test/java/org/lmdbjava/TestUtils.java index fd070a31..c0203264 100644 --- a/src/test/java/org/lmdbjava/TestUtils.java +++ b/src/test/java/org/lmdbjava/TestUtils.java @@ -36,14 +36,13 @@ final class TestUtils { private TestUtils() {} static byte[] ba(final int value) { - final MutableDirectBuffer b = new UnsafeBuffer(new byte[4]); - b.putInt(0, value); - return b.byteArray(); + byte[] bytes = new byte[4]; + ByteBuffer.wrap(bytes).putInt(value); + return bytes; } static int fromBa(final byte[] ba) { - final MutableDirectBuffer b = new UnsafeBuffer(ba); - return b.getInt(0); + return ByteBuffer.wrap(ba).getInt(); } static ByteBuffer bb(final int value) { From 741b6f295c73cbc827d13811f43e8589adf2f7fb Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Fri, 24 Oct 2025 10:06:45 +0100 Subject: [PATCH 023/139] Add OpenLDAP-2.8.txt --- licences/OpenLDAP-2.8.txt | 47 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 licences/OpenLDAP-2.8.txt diff --git a/licences/OpenLDAP-2.8.txt b/licences/OpenLDAP-2.8.txt new file mode 100644 index 00000000..05ad7571 --- /dev/null +++ b/licences/OpenLDAP-2.8.txt @@ -0,0 +1,47 @@ +The OpenLDAP Public License + Version 2.8, 17 August 2003 + +Redistribution and use of this software and associated documentation +("Software"), with or without modification, are permitted provided +that the following conditions are met: + +1. Redistributions in source form must retain copyright statements + and notices, + +2. Redistributions in binary form must reproduce applicable copyright + statements and notices, this list of conditions, and the following + disclaimer in the documentation and/or other materials provided + with the distribution, and + +3. Redistributions must contain a verbatim copy of this document. + +The OpenLDAP Foundation may revise this license from time to time. +Each revision is distinguished by a version number. You may use +this Software under terms of this license revision or under the +terms of any subsequent revision of the license. + +THIS SOFTWARE IS PROVIDED BY THE OPENLDAP FOUNDATION AND ITS +CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +SHALL THE OPENLDAP FOUNDATION, ITS CONTRIBUTORS, OR THE AUTHOR(S) +OR OWNER(S) OF THE SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +The names of the authors and copyright holders must not be used in +advertising or otherwise to promote the sale, use or other dealing +in this Software without specific, written prior permission. Title +to copyright in this Software shall at all times remain with copyright +holders. + +OpenLDAP is a registered trademark of the OpenLDAP Foundation. + +Copyright 1999-2003 The OpenLDAP Foundation, Redwood City, +California, USA. All Rights Reserved. Permission to copy and +distribute verbatim copies of this document is granted. From 180b91f3f8afded358d8308544929c0697cfc77c Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Fri, 24 Oct 2025 10:07:50 +0100 Subject: [PATCH 024/139] Create Apache-2.0 --- licences/Apache-2.0 | 51 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 licences/Apache-2.0 diff --git a/licences/Apache-2.0 b/licences/Apache-2.0 new file mode 100644 index 00000000..6ca24c75 --- /dev/null +++ b/licences/Apache-2.0 @@ -0,0 +1,51 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of this License; and +You must cause any modified files to carry prominent notices stating that You changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. +You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS From 0f202f303db0b3f78132ebaaf764264bd37e4aa7 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Fri, 24 Oct 2025 10:15:34 +0100 Subject: [PATCH 025/139] Create NOTICE.md --- NOTICE.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 NOTICE.md diff --git a/NOTICE.md b/NOTICE.md new file mode 100644 index 00000000..d5727a9d --- /dev/null +++ b/NOTICE.md @@ -0,0 +1,17 @@ +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](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. + +--- + +This project distribution JAR includes a distribution of [LMDB](https://www.symas.com/mdb), which is licensed under the [OpenLDAP Public License](./licences/OpenLDAP-2.8.txt). From 856ab0e282736d9d94d8409d9d13f66c18e387ff Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Fri, 24 Oct 2025 10:37:39 +0100 Subject: [PATCH 026/139] Update NOTICE.md --- NOTICE.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/NOTICE.md b/NOTICE.md index d5727a9d..0145c1b3 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -14,4 +14,23 @@ limitations under the License. --- -This project distribution JAR includes a distribution of [LMDB](https://www.symas.com/mdb), which is licensed under the [OpenLDAP Public License](./licences/OpenLDAP-2.8.txt). +The LmdbJava project distribution JAR file includes a distribution of [LMDB](https://www.symas.com/mdb), which is licensed under the [OpenLDAP Public License](https://www.openldap.org/software/release/license.html). + +--- + +# Dependencies + +LmdbJava uses the following libraries. +All licenese files can also be found in the `licences` directory in the root of this repository. + +| Dependency | License | +|-------------|----------| +| `com.github.jnr:jnr-constants` | [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) | +| `com.github.jnr:jnr-ffi` | [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) | +| `com.google.guava:guava` | [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) | +| `com.jakewharton.byteunits:byteunits` | [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) | +| `io.netty:netty-buffer` | [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) | +| `junit:junit` | [Eclipse Public License 1.0](https://www.eclipse.org/org/documents/epl-v10.html) | +| `org.agrona:agrona` | [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) | +| `org.hamcrest:hamcrest` | [BSD 3-Clause License](https://opensource.org/license/bsd-3-clause) | +| `org.mockito:mockito-inline` | [MIT License](https://opensource.org/license/mit) | From 3a8d27995c511f9b94073324a4a60616695ead0c Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 25 Oct 2025 15:02:22 +1100 Subject: [PATCH 027/139] Prevent license plugin from modifying license texts --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index b3012729..b270c4b0 100644 --- a/pom.xml +++ b/pom.xml @@ -108,6 +108,7 @@ LICENSE.txt **/*.md lmdb/** + licenses/** From 9d7b32eee7537b3807e29de84358dfa7eaa302eb Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 25 Oct 2025 15:03:13 +1100 Subject: [PATCH 028/139] Automatic POM formatting only --- pom.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index b270c4b0..454fc3d3 100644 --- a/pom.xml +++ b/pom.xml @@ -185,19 +185,19 @@ - + [${maven.enforcer.mvn},) [${maven.enforcer.java},) - - + + true - + @@ -352,7 +352,7 @@ Kristoffer Sjogren stoffe -at- gmail.com http://stoffe.deephacks.org/ - + +1 From 8ab46ee4636a11cfea019004021334e9c3c99e4a Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 25 Oct 2025 15:03:43 +1100 Subject: [PATCH 029/139] JaCoCo Maven Plugin update to 0.8.14 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 454fc3d3..3a71e33c 100644 --- a/pom.xml +++ b/pom.xml @@ -316,7 +316,7 @@ org.jacoco jacoco-maven-plugin - 0.8.12 + 0.8.14 config-jacoco-prepare-agent From 79c2382d5eaabee1ae818288fc0c065345fe0024 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 25 Oct 2025 15:04:35 +1100 Subject: [PATCH 030/139] Format Maven Plugin update to 2.29 (inc Java 25 workaround) --- pom.xml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 3a71e33c..0863fdf7 100644 --- a/pom.xml +++ b/pom.xml @@ -442,16 +442,17 @@ + com.spotify.fmt fmt-maven-plugin - 2.25 - - - - format - - - + 2.29 + + + com.google.googlejavaformat + google-java-format + 1.28.0 + + From 40192dbfef98c2f60dd90d3e2b4b6981971e8419 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 25 Oct 2025 15:07:56 +1100 Subject: [PATCH 031/139] Rename licences -> licenses for consistency This matches the existing LICENSE.TXT spelling and the spelling of the license we are using: https://httpd.apache.org/docs/trunk/license.html --- NOTICE.md | 2 +- {licences => licenses}/Apache-2.0 | 0 {licences => licenses}/OpenLDAP-2.8.txt | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename {licences => licenses}/Apache-2.0 (100%) rename {licences => licenses}/OpenLDAP-2.8.txt (100%) diff --git a/NOTICE.md b/NOTICE.md index 0145c1b3..77fad436 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -21,7 +21,7 @@ The LmdbJava project distribution JAR file includes a distribution of [LMDB](htt # Dependencies LmdbJava uses the following libraries. -All licenese files can also be found in the `licences` directory in the root of this repository. +All license files can also be found in the `licenses` directory in the root of this repository. | Dependency | License | |-------------|----------| diff --git a/licences/Apache-2.0 b/licenses/Apache-2.0 similarity index 100% rename from licences/Apache-2.0 rename to licenses/Apache-2.0 diff --git a/licences/OpenLDAP-2.8.txt b/licenses/OpenLDAP-2.8.txt similarity index 100% rename from licences/OpenLDAP-2.8.txt rename to licenses/OpenLDAP-2.8.txt From e598e21a0837fafc398747fa625de7b8cbfb4a36 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 25 Oct 2025 15:09:51 +1100 Subject: [PATCH 032/139] Migrate to Maven Central Portal Publisher API --- .github/workflows/maven.yml | 16 ++++++++-------- README.md | 2 +- pom.xml | 23 ++++++----------------- 3 files changed, 15 insertions(+), 26 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index b28e8481..a3311ce7 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -82,7 +82,7 @@ jobs: path: target/surefire-reports deploy: - name: Deploy to OSSRH + name: Deploy to Central Portal needs: [build, compatibility-checks] if: github.event_name == 'push' runs-on: ubuntu-latest @@ -98,9 +98,9 @@ jobs: # Java version 8 required due to https://github.com/lmdbjava/lmdbjava/issues/116 java-version: 8 cache: maven - server-id: ossrh - server-username: MAVEN_USERNAME - server-password: MAVEN_CENTRAL_TOKEN + server-id: central + server-username: MAVEN_CENTRAL_USERNAME + server-password: MAVEN_CENTRAL_PASSWORD gpg-private-key: ${{ secrets.gpg_private_key }} gpg-passphrase: MAVEN_GPG_PASSPHRASE @@ -111,15 +111,15 @@ jobs: run: ./cross-compile.sh - name: Publish Maven package - run: mvn -B -Possrh-deploy deploy -DskipTests + run: mvn -B -Pcentral-deploy deploy -DskipTests env: MAVEN_GPG_PASSPHRASE: ${{ secrets.gpg_passphrase }} - MAVEN_USERNAME: ${{ secrets.nexus_username }} - MAVEN_CENTRAL_TOKEN: ${{ secrets.nexus_password }} + MAVEN_CENTRAL_USERNAME: ${{ secrets.central_username }} + MAVEN_CENTRAL_PASSWORD: ${{ secrets.central_password }} - name: Debug settings.xml uses: actions/upload-artifact@v4 if: failure() with: name: settings.xml - path: $HOME/.m2/settings.xml + path: ~/.m2/settings.xml diff --git a/README.md b/README.md index 048e57bc..cde62cd8 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ * Low latency design (allocation-free; buffer pools; optional checks can be easily disabled in production etc) * Mature code (commenced in 2016) and used for heavy production workloads (eg > 500 TB of HFT data) * Actively maintained and with a "Zero Bug Policy" before every release (see [issues](https://github.com/lmdbjava/lmdbjava/issues)) -* Available from [Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.lmdbjava%22%20AND%20a%3A%22lmdbjava%22) and [OSS Sonatype Snapshots](https://oss.sonatype.org/content/repositories/snapshots/org/lmdbjava/lmdbjava) +* Available from [Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.lmdbjava%22%20AND%20a%3A%22lmdbjava%22) and [Central Portal Snapshots](https://central.sonatype.com/repository/maven-snapshots/org/lmdbjava/lmdbjava) * [Continuous integration](https://github.com/lmdbjava/lmdbjava/actions) testing on Linux, Windows and macOS with Java 8, 11, 17 and 21 ### Performance diff --git a/pom.xml b/pom.xml index 0863fdf7..12af842d 100644 --- a/pom.xml +++ b/pom.xml @@ -378,16 +378,6 @@ GitHub Actions https://github.com/lmdbjava/lmdbjava/actions - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - add-opens-on-java-9-and-above @@ -458,7 +448,7 @@ - ossrh-deploy + central-deploy @@ -482,14 +472,13 @@ - org.sonatype.plugins - nexus-staging-maven-plugin - 1.7.0 + org.sonatype.central + central-publishing-maven-plugin + 0.9.0 true - ossrh - https://oss.sonatype.org/ - true + central + true From 3a4b9acf010ac3a992f310458034e5801ae86df6 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 25 Oct 2025 15:43:31 +1100 Subject: [PATCH 033/139] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cde62cd8..af13364d 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ updated (eg following a new official release of the upstream LMDB library). If you do not wish to install Zig and/or use an operating system which cannot easily execute the `cross-compile.sh` script, you can download the compiled LMDB native library for your platform from a location of your choice and set the -`lmdbjava.native.lib` system property to the resulting file system system +`lmdbjava.native.lib` system property to the resulting file system location. Possible sources of a compiled LMDB native library include operating system package managers, running `cross-compile.sh` on a supported system, or copying it from the `org/lmdbjava` directory of any recent, officially released From 1ea59025e61702c40cbd445b10d458e5889881b0 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 25 Oct 2025 15:44:58 +1100 Subject: [PATCH 034/139] Add Java 25 testing --- .github/workflows/maven.yml | 4 ++-- README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index a3311ce7..3b97a0c6 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -19,7 +19,7 @@ jobs: uses: actions/setup-java@v3 with: distribution: zulu - java-version: 21 + java-version: 25 cache: maven - name: Install Zig @@ -52,7 +52,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - java: [8, 11, 17, 21] + java: [8, 11, 17, 21, 25] steps: - name: Check out Git repository diff --git a/README.md b/README.md index af13364d..99ccf54f 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ * Mature code (commenced in 2016) and used for heavy production workloads (eg > 500 TB of HFT data) * Actively maintained and with a "Zero Bug Policy" before every release (see [issues](https://github.com/lmdbjava/lmdbjava/issues)) * Available from [Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.lmdbjava%22%20AND%20a%3A%22lmdbjava%22) and [Central Portal Snapshots](https://central.sonatype.com/repository/maven-snapshots/org/lmdbjava/lmdbjava) -* [Continuous integration](https://github.com/lmdbjava/lmdbjava/actions) testing on Linux, Windows and macOS with Java 8, 11, 17 and 21 +* [Continuous integration](https://github.com/lmdbjava/lmdbjava/actions) testing on Linux, Windows and macOS with Java 8, 11, 17, 21 and 25 ### Performance From c91893a876e7edaaea503f689066cf4a6e96f156 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 25 Oct 2025 19:23:06 +1100 Subject: [PATCH 035/139] Fix transient pointer deallocation in ByteArrayProxy JNR-FFI's TransientNativeMemory was prematurely deallocating off-heap memory before LMDB syscalls completed. Return transient pointers from BufferProxy.in() and use reachability fencing at all call sites to prevent premature GC. BufferProxy and KeyVal were also simplified to remove pointer address passing, given this is inexpensively available from the Pointer.address() accessor (which is backed by a final field). This is an API breaking change if external users implemented their own BufferProxy, however it is considered unlikely many (if any) users would have ever done this. Fixes #252 --- src/main/java/org/lmdbjava/BufferProxy.java | 11 ++++--- .../java/org/lmdbjava/ByteArrayProxy.java | 8 +++-- src/main/java/org/lmdbjava/ByteBufProxy.java | 11 +++++-- .../java/org/lmdbjava/ByteBufferProxy.java | 22 +++++++++----- src/main/java/org/lmdbjava/Cursor.java | 29 ++++++++++++++----- src/main/java/org/lmdbjava/Dbi.java | 29 ++++++++++++------- .../java/org/lmdbjava/DirectBufferProxy.java | 12 +++++--- src/main/java/org/lmdbjava/KeyVal.java | 22 ++++++-------- .../org/lmdbjava/ByteBufferProxyTest.java | 4 +-- 9 files changed, 91 insertions(+), 57 deletions(-) diff --git a/src/main/java/org/lmdbjava/BufferProxy.java b/src/main/java/org/lmdbjava/BufferProxy.java index e66031d2..96d6aa1e 100644 --- a/src/main/java/org/lmdbjava/BufferProxy.java +++ b/src/main/java/org/lmdbjava/BufferProxy.java @@ -107,9 +107,9 @@ protected Comparator getComparator(DbiFlags... flags) { * * @param buffer the buffer to write to MDB_val * @param ptr the pointer to the MDB_val - * @param ptrAddr the address of the MDB_val pointer + * @return a transient pointer that must be kept alive, or null if none */ - protected abstract void in(T buffer, Pointer ptr, long ptrAddr); + protected abstract Pointer in(T buffer, Pointer ptr); /** * Called when the MDB_val should be set to reflect the passed buffer. @@ -117,9 +117,9 @@ protected Comparator getComparator(DbiFlags... flags) { * @param buffer the buffer to write to MDB_val * @param size the buffer size to write to MDB_val * @param ptr the pointer to the MDB_val - * @param ptrAddr the address of the MDB_val pointer + * @return a transient pointer that must be kept alive, or null if none */ - protected abstract void in(T buffer, int size, Pointer ptr, long ptrAddr); + protected abstract Pointer in(T buffer, int size, Pointer ptr); /** * Called when the MDB_val may have changed and the passed buffer should be modified @@ -127,10 +127,9 @@ protected Comparator getComparator(DbiFlags... flags) { * * @param buffer the buffer to write to MDB_val * @param ptr the pointer to the MDB_val - * @param ptrAddr the address of the MDB_val pointer * @return the buffer for MDB_val */ - protected abstract T out(T buffer, Pointer ptr, long ptrAddr); + protected abstract T out(T buffer, Pointer ptr); /** * Create a new {@link KeyVal} to hold pointers for this buffer proxy. diff --git a/src/main/java/org/lmdbjava/ByteArrayProxy.java b/src/main/java/org/lmdbjava/ByteArrayProxy.java index 4a22ab83..853521e0 100644 --- a/src/main/java/org/lmdbjava/ByteArrayProxy.java +++ b/src/main/java/org/lmdbjava/ByteArrayProxy.java @@ -114,20 +114,22 @@ protected Comparator getUnsignedComparator() { } @Override - protected void in(final byte[] buffer, final Pointer ptr, final long ptrAddr) { + protected Pointer in(final byte[] buffer, final Pointer ptr) { final Pointer pointer = MEM_MGR.allocateDirect(buffer.length); pointer.put(0, buffer, 0, buffer.length); ptr.putLong(STRUCT_FIELD_OFFSET_SIZE, buffer.length); ptr.putAddress(STRUCT_FIELD_OFFSET_DATA, pointer.address()); + return pointer; } @Override - protected void in(final byte[] buffer, final int size, final Pointer ptr, final long ptrAddr) { + protected Pointer in(final byte[] buffer, final int size, final Pointer ptr) { // cannot reserve for byte arrays + return null; } @Override - protected byte[] out(final byte[] buffer, final Pointer ptr, final long ptrAddr) { + protected byte[] out(final byte[] buffer, final Pointer ptr) { final long addr = ptr.getAddress(STRUCT_FIELD_OFFSET_DATA); final int size = (int) ptr.getLong(STRUCT_FIELD_OFFSET_SIZE); final Pointer pointer = MEM_MGR.newPointer(addr, size); diff --git a/src/main/java/org/lmdbjava/ByteBufProxy.java b/src/main/java/org/lmdbjava/ByteBufProxy.java index cac5b97b..2866e874 100644 --- a/src/main/java/org/lmdbjava/ByteBufProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufProxy.java @@ -136,21 +136,26 @@ protected byte[] getBytes(final ByteBuf buffer) { } @Override - protected void in(final ByteBuf buffer, final Pointer ptr, final long ptrAddr) { + protected Pointer in(final ByteBuf buffer, final Pointer ptr) { + final long ptrAddr = ptr.address(); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, buffer.writerIndex() - buffer.readerIndex()); UNSAFE.putLong( ptrAddr + STRUCT_FIELD_OFFSET_DATA, buffer.memoryAddress() + buffer.readerIndex()); + return null; } @Override - protected void in(final ByteBuf buffer, final int size, final Pointer ptr, final long ptrAddr) { + protected Pointer in(final ByteBuf buffer, final int size, final Pointer ptr) { + final long ptrAddr = ptr.address(); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, size); UNSAFE.putLong( ptrAddr + STRUCT_FIELD_OFFSET_DATA, buffer.memoryAddress() + buffer.readerIndex()); + return null; } @Override - protected ByteBuf out(final ByteBuf buffer, final Pointer ptr, final long ptrAddr) { + protected ByteBuf out(final ByteBuf buffer, final Pointer ptr) { + final long ptrAddr = ptr.address(); final long addr = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA); final long size = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE); UNSAFE.putLong(buffer, addressOffset, addr); diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index 2b7cdf0d..9f30f576 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -221,20 +221,22 @@ private static final class ReflectiveProxy extends AbstractByteBufferProxy { } @Override - protected void in(final ByteBuffer buffer, final Pointer ptr, final long ptrAddr) { + protected Pointer in(final ByteBuffer buffer, final Pointer ptr) { ptr.putAddress(STRUCT_FIELD_OFFSET_DATA, address(buffer)); ptr.putLong(STRUCT_FIELD_OFFSET_SIZE, buffer.remaining()); + return null; } @Override - protected void in( - final ByteBuffer buffer, final int size, final Pointer ptr, final long ptrAddr) { + protected Pointer in(final ByteBuffer buffer, final int size, final Pointer ptr) { ptr.putLong(STRUCT_FIELD_OFFSET_SIZE, size); ptr.putAddress(STRUCT_FIELD_OFFSET_DATA, address(buffer)); + return null; } @Override - protected ByteBuffer out(final ByteBuffer buffer, final Pointer ptr, final long ptrAddr) { + protected ByteBuffer out(final ByteBuffer buffer, final Pointer ptr) { + final long ptrAddr = ptr.address(); final long addr = ptr.getAddress(STRUCT_FIELD_OFFSET_DATA); final long size = ptr.getLong(STRUCT_FIELD_OFFSET_SIZE); try { @@ -269,20 +271,24 @@ private static final class UnsafeProxy extends AbstractByteBufferProxy { } @Override - protected void in(final ByteBuffer buffer, final Pointer ptr, final long ptrAddr) { + protected Pointer in(final ByteBuffer buffer, final Pointer ptr) { + final long ptrAddr = ptr.address(); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, buffer.remaining()); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA, address(buffer)); + return null; } @Override - protected void in( - final ByteBuffer buffer, final int size, final Pointer ptr, final long ptrAddr) { + protected Pointer in(final ByteBuffer buffer, final int size, final Pointer ptr) { + final long ptrAddr = ptr.address(); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, size); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA, address(buffer)); + return null; } @Override - protected ByteBuffer out(final ByteBuffer buffer, final Pointer ptr, final long ptrAddr) { + protected ByteBuffer out(final ByteBuffer buffer, final Pointer ptr) { + final long ptrAddr = ptr.address(); final long addr = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA); final long size = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE); UNSAFE.putLong(buffer, ADDRESS_OFFSET, addr); diff --git a/src/main/java/org/lmdbjava/Cursor.java b/src/main/java/org/lmdbjava/Cursor.java index d49a9bed..f7fcbc41 100644 --- a/src/main/java/org/lmdbjava/Cursor.java +++ b/src/main/java/org/lmdbjava/Cursor.java @@ -141,8 +141,8 @@ public boolean get(final T key, final T data, final SeekOp op) { checkNotClosed(); txn.checkReady(); } - kv.keyIn(key); - kv.valIn(data); + final Pointer transientKey = kv.keyIn(key); + final Pointer transientVal = kv.valIn(data); final int rc = LIB.mdb_cursor_get(ptrCursor, kv.pointerKey(), kv.pointerVal(), op.getCode()); @@ -153,6 +153,10 @@ public boolean get(final T key, final T data, final SeekOp op) { checkRc(rc); kv.keyOut(); kv.valOut(); + ReferenceUtil.reachabilityFence0(transientKey); + ReferenceUtil.reachabilityFence0(transientVal); + ReferenceUtil.reachabilityFence0(kv.key()); + ReferenceUtil.reachabilityFence0(kv.val()); ReferenceUtil.reachabilityFence0(key); return true; } @@ -172,7 +176,7 @@ public boolean get(final T key, final GetOp op) { checkNotClosed(); txn.checkReady(); } - kv.keyIn(key); + final Pointer transientKey = kv.keyIn(key); final int rc = LIB.mdb_cursor_get(ptrCursor, kv.pointerKey(), kv.pointerVal(), op.getCode()); @@ -183,6 +187,9 @@ public boolean get(final T key, final GetOp op) { checkRc(rc); kv.keyOut(); kv.valOut(); + ReferenceUtil.reachabilityFence0(transientKey); + ReferenceUtil.reachabilityFence0(kv.key()); + ReferenceUtil.reachabilityFence0(kv.val()); ReferenceUtil.reachabilityFence0(key); return true; } @@ -243,8 +250,8 @@ public boolean put(final T key, final T val, final PutFlags... op) { txn.checkReady(); txn.checkWritesAllowed(); } - kv.keyIn(key); - kv.valIn(val); + 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); if (rc == MDB_KEYEXIST) { @@ -256,6 +263,8 @@ public boolean put(final T key, final T val, final PutFlags... op) { return false; } checkRc(rc); + ReferenceUtil.reachabilityFence0(transientKey); + ReferenceUtil.reachabilityFence0(transientVal); ReferenceUtil.reachabilityFence0(key); ReferenceUtil.reachabilityFence0(val); return true; @@ -287,10 +296,12 @@ public void putMultiple(final T key, final T val, final int elements, final PutF if (SHOULD_CHECK && !isSet(mask, MDB_MULTIPLE)) { throw new IllegalArgumentException("Must set " + MDB_MULTIPLE + " flag"); } - txn.kv().keyIn(key); + 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); checkRc(rc); + ReferenceUtil.reachabilityFence0(transientKey); + ReferenceUtil.reachabilityFence0(dataPtr); ReferenceUtil.reachabilityFence0(key); ReferenceUtil.reachabilityFence0(val); } @@ -340,11 +351,13 @@ public T reserve(final T key, final int size, final PutFlags... op) { txn.checkReady(); txn.checkWritesAllowed(); } - kv.keyIn(key); - kv.valIn(size); + 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)); kv.valOut(); + ReferenceUtil.reachabilityFence0(transientKey); + ReferenceUtil.reachabilityFence0(transientVal); ReferenceUtil.reachabilityFence0(key); return val(); } diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index ad1bb5a7..2fa7f185 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -83,8 +83,8 @@ public final class Dbi { (keyA, keyB) -> { final T compKeyA = proxy.allocate(); final T compKeyB = proxy.allocate(); - proxy.out(compKeyA, keyA, keyA.address()); - proxy.out(compKeyB, keyB, keyB.address()); + proxy.out(compKeyA, keyA); + proxy.out(compKeyB, keyB); final int result = this.comparator.compare(compKeyA, compKeyB); proxy.deallocate(compKeyA); proxy.deallocate(compKeyB); @@ -161,11 +161,12 @@ public boolean delete(final Txn txn, final T key, final T val) { txn.checkWritesAllowed(); } - txn.kv().keyIn(key); + final Pointer transientKey = txn.kv().keyIn(key); Pointer data = null; + Pointer transientVal = null; if (val != null) { - txn.kv().valIn(val); + transientVal = txn.kv().valIn(val); data = txn.kv().pointerVal(); } final int rc = LIB.mdb_del(txn.pointer(), ptr, txn.kv().pointerKey(), data); @@ -173,6 +174,8 @@ public boolean delete(final Txn txn, final T key, final T val) { return false; } checkRc(rc); + ReferenceUtil.reachabilityFence0(transientKey); + ReferenceUtil.reachabilityFence0(transientVal); ReferenceUtil.reachabilityFence0(key); ReferenceUtil.reachabilityFence0(val); return true; @@ -232,14 +235,16 @@ public T get(final Txn txn, final T key) { env.checkNotClosed(); txn.checkReady(); } - txn.kv().keyIn(key); + final Pointer transientKey = txn.kv().keyIn(key); final int rc = LIB.mdb_get(txn.pointer(), ptr, txn.kv().pointerKey(), txn.kv().pointerVal()); if (rc == MDB_NOTFOUND) { return null; } checkRc(rc); + final T result = txn.kv().valOut(); // marked as out in LMDB C docs + ReferenceUtil.reachabilityFence0(transientKey); ReferenceUtil.reachabilityFence0(key); - return txn.kv().valOut(); // marked as out in LMDB C docs + return result; } /** @@ -366,8 +371,8 @@ public boolean put(final Txn txn, final T key, final T val, final PutFlags... txn.checkReady(); txn.checkWritesAllowed(); } - txn.kv().keyIn(key); - txn.kv().valIn(val); + 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); @@ -380,6 +385,8 @@ public boolean put(final Txn txn, final T key, final T val, final PutFlags... return false; } checkRc(rc); + ReferenceUtil.reachabilityFence0(transientKey); + ReferenceUtil.reachabilityFence0(transientVal); ReferenceUtil.reachabilityFence0(key); ReferenceUtil.reachabilityFence0(val); return true; @@ -408,11 +415,13 @@ public T reserve(final Txn txn, final T key, final int size, final PutFlags.. txn.checkReady(); txn.checkWritesAllowed(); } - txn.kv().keyIn(key); - txn.kv().valIn(size); + final Pointer transientKey = txn.kv().keyIn(key); + final Pointer transientVal = txn.kv().valIn(size); final int flags = mask(true, 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); + ReferenceUtil.reachabilityFence0(transientVal); ReferenceUtil.reachabilityFence0(key); return txn.val(); } diff --git a/src/main/java/org/lmdbjava/DirectBufferProxy.java b/src/main/java/org/lmdbjava/DirectBufferProxy.java index 156e60e9..17b59b3d 100644 --- a/src/main/java/org/lmdbjava/DirectBufferProxy.java +++ b/src/main/java/org/lmdbjava/DirectBufferProxy.java @@ -134,23 +134,27 @@ protected byte[] getBytes(final DirectBuffer buffer) { } @Override - protected void in(final DirectBuffer buffer, final Pointer ptr, final long ptrAddr) { + protected Pointer in(final DirectBuffer buffer, final Pointer ptr) { + final long ptrAddr = ptr.address(); final long addr = buffer.addressOffset(); final long size = buffer.capacity(); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA, addr); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, size); + return null; } @Override - protected void in( - final DirectBuffer buffer, final int size, final Pointer ptr, final long ptrAddr) { + protected Pointer in(final DirectBuffer buffer, final int size, final Pointer ptr) { + final long ptrAddr = ptr.address(); final long addr = buffer.addressOffset(); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA, addr); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, size); + return null; } @Override - protected DirectBuffer out(final DirectBuffer buffer, final Pointer ptr, final long ptrAddr) { + protected DirectBuffer out(final DirectBuffer buffer, final Pointer ptr) { + final long ptrAddr = ptr.address(); final long addr = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA); final long size = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE); buffer.wrap(addr, (int) size); diff --git a/src/main/java/org/lmdbjava/KeyVal.java b/src/main/java/org/lmdbjava/KeyVal.java index c6f2aae5..e2d2529f 100644 --- a/src/main/java/org/lmdbjava/KeyVal.java +++ b/src/main/java/org/lmdbjava/KeyVal.java @@ -36,9 +36,7 @@ final class KeyVal implements AutoCloseable { private final BufferProxy proxy; private final Pointer ptrArray; private final Pointer ptrKey; - private final long ptrKeyAddr; private final Pointer ptrVal; - private final long ptrValAddr; private T v; KeyVal(final BufferProxy proxy) { @@ -47,10 +45,8 @@ final class KeyVal implements AutoCloseable { this.k = proxy.allocate(); this.v = proxy.allocate(); ptrKey = MEM_MGR.allocateTemporary(MDB_VAL_STRUCT_SIZE, false); - ptrKeyAddr = ptrKey.address(); ptrArray = MEM_MGR.allocateTemporary(MDB_VAL_STRUCT_SIZE * 2, false); ptrVal = ptrArray.slice(0, MDB_VAL_STRUCT_SIZE); - ptrValAddr = ptrVal.address(); } @Override @@ -67,12 +63,12 @@ T key() { return k; } - void keyIn(final T key) { - proxy.in(key, ptrKey, ptrKeyAddr); + Pointer keyIn(final T key) { + return proxy.in(key, ptrKey); } T keyOut() { - k = proxy.out(k, ptrKey, ptrKeyAddr); + k = proxy.out(k, ptrKey); return k; } @@ -88,12 +84,12 @@ T val() { return v; } - void valIn(final T val) { - proxy.in(val, ptrVal, ptrValAddr); + Pointer valIn(final T val) { + return proxy.in(val, ptrVal); } - void valIn(final int size) { - proxy.in(v, size, ptrVal, ptrValAddr); + Pointer valIn(final int size) { + return proxy.in(v, size, ptrVal); } /** @@ -116,7 +112,7 @@ void valIn(final int size) { Pointer valInMulti(final T val, final int elements) { final long ptrVal2SizeOff = MDB_VAL_STRUCT_SIZE + STRUCT_FIELD_OFFSET_SIZE; ptrArray.putLong(ptrVal2SizeOff, elements); // ptrVal2.size - proxy.in(val, ptrVal, ptrValAddr); // ptrVal1.data + proxy.in(val, ptrVal); // ptrVal1.data final long totalBufferSize = ptrVal.getLong(STRUCT_FIELD_OFFSET_SIZE); final long elemSize = totalBufferSize / elements; ptrVal.putLong(STRUCT_FIELD_OFFSET_SIZE, elemSize); // ptrVal1.size @@ -125,7 +121,7 @@ Pointer valInMulti(final T val, final int elements) { } T valOut() { - v = proxy.out(v, ptrVal, ptrValAddr); + v = proxy.out(v, ptrVal); return v; } } diff --git a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java index 8044b9e8..b68f39ef 100644 --- a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java +++ b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java @@ -139,10 +139,10 @@ private void checkInOut(final BufferProxy v) { b.position(BYTES); // skip 1 final Pointer p = MEM_MGR.allocateTemporary(MDB_VAL_STRUCT_SIZE, false); - v.in(b, p, p.address()); + v.in(b, p); final ByteBuffer bb = allocateDirect(1); - v.out(bb, p, p.address()); + v.out(bb, p); assertThat(bb.getInt(), is(2)); assertThat(bb.getInt(), is(3)); From a510be752f3d3546cd9fd7d016a374306bec1997 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 25 Oct 2025 19:37:00 +1100 Subject: [PATCH 036/139] Fix JavaDoc link --- src/main/java/org/lmdbjava/BufferProxy.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/lmdbjava/BufferProxy.java b/src/main/java/org/lmdbjava/BufferProxy.java index 96d6aa1e..d4503731 100644 --- a/src/main/java/org/lmdbjava/BufferProxy.java +++ b/src/main/java/org/lmdbjava/BufferProxy.java @@ -48,8 +48,7 @@ public abstract class BufferProxy { protected BufferProxy() {} /** - * Allocate a new buffer suitable for passing to {@link #out(java.lang.Object, jnr.ffi.Pointer, - * long)}. + * Allocate a new buffer suitable for passing to {@link #out(java.lang.Object, jnr.ffi.Pointer)}. * * @return a buffer for passing to the out method */ From a892768db1fedb5247e5ac8cc0346a8481f284ee Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sun, 26 Oct 2025 09:27:28 +1100 Subject: [PATCH 037/139] Update GitHub Actions versions and zulu -> temurin JDK --- .github/workflows/maven.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 3b97a0c6..ee5726ce 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -13,17 +13,17 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v3 + uses: actions/checkout@v5 - name: Set up Java and Maven - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: - distribution: zulu + distribution: temurin java-version: 25 cache: maven - name: Install Zig - uses: goto-bus-stop/setup-zig@v2 + uses: mlugg/setup-zig@v2 - name: Cross compile using Zig run: ./cross-compile.sh @@ -56,12 +56,12 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v3 + uses: actions/checkout@v5 - name: Set up Java and Maven - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: - distribution: zulu + distribution: temurin java-version: ${{ matrix.java }} cache: maven @@ -89,12 +89,12 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v3 + uses: actions/checkout@v5 - name: Set up Java and Maven - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: - distribution: zulu + distribution: temurin # Java version 8 required due to https://github.com/lmdbjava/lmdbjava/issues/116 java-version: 8 cache: maven @@ -105,7 +105,7 @@ jobs: gpg-passphrase: MAVEN_GPG_PASSPHRASE - name: Install Zig - uses: goto-bus-stop/setup-zig@v2 + uses: mlugg/setup-zig@v2 - name: Cross compile using Zig run: ./cross-compile.sh From 60f5f17b54416495447ad8ee98c529ac8554dcd5 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sun, 26 Oct 2025 09:33:42 +1100 Subject: [PATCH 038/139] Switch GitHub Actions back to zulu (needed for Java 8) --- .github/workflows/maven.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index ee5726ce..372c0c88 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -18,7 +18,7 @@ jobs: - name: Set up Java and Maven uses: actions/setup-java@v5 with: - distribution: temurin + distribution: zulu java-version: 25 cache: maven @@ -61,7 +61,7 @@ jobs: - name: Set up Java and Maven uses: actions/setup-java@v5 with: - distribution: temurin + distribution: zulu java-version: ${{ matrix.java }} cache: maven @@ -94,7 +94,7 @@ jobs: - name: Set up Java and Maven uses: actions/setup-java@v5 with: - distribution: temurin + distribution: zulu # Java version 8 required due to https://github.com/lmdbjava/lmdbjava/issues/116 java-version: 8 cache: maven From d109ee43b9a7e88f744223e314d5dec717a1abee Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sun, 26 Oct 2025 09:47:37 +1100 Subject: [PATCH 039/139] POM to use property interpolation and version plugin --- .gitignore | 1 + CONTRIBUTING.md | 8 ++- pom.xml | 126 +++++++++++++++++++++++++++++++++++------------- 3 files changed, 100 insertions(+), 35 deletions(-) diff --git a/.gitignore b/.gitignore index 0c771329..f46b8b6f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ gpg-sign.json mvn-sync.json secrets.tar lmdb +pom.xml.versionsBackup diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c37bf055..09448c03 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,13 +6,19 @@ We welcome patches and pull requests to improve LmdbJava. This will run: * Tests -* Initial Test Coverage * Source Code Formatting * License Header Management `mvn clean verify` is also run by CI, but it's quicker and easier to run before submitting. +### Version Management + +Update all dependency and plugin versions: +```bash +mvn versions:update-properties +``` + ### Releasing GitHub Actions will perform an official release whenever a developer executes diff --git a/pom.xml b/pom.xml index 12af842d..f2a44fa3 100644 --- a/pom.xml +++ b/pom.xml @@ -25,47 +25,80 @@ LmdbJava Low latency Java API for the ultra-fast, embedded Symas Lightning Database (LMDB) + + 1.22.0 + 3.2.1 + 0.9.1 + 0.9.0 + 2.29 + 1.28.0 + 33.5.0-jre + 2.2 + 0.8.14 + 0.10.4 + 2.2.18 + 4.13.2 + 4.6 + 3.5.0 + 3.14.1 + 3.9.0 + 3.1.4 + 3.6.2 + 3.2.7 + 3.1.4 + 3.4.2 + 3.12.0 + 3.1.1 + 3.3.1 + 2.2.1 + 3.21.0 + 3.3.1 + 3.5.4 yyyy-MM-dd'T'HH:mm:ss'Z' 1.8 1.8 1.8 3.5.4 + 4.11.0 + 4.2.7.Final UTF-8 + 4.0.0 false + 2.19.1 com.github.jnr jnr-constants - 0.10.4 + ${jnr-constants.version} com.github.jnr jnr-ffi - 2.2.17 + ${jnr-ffi.version} com.google.guava guava - 33.4.0-jre + ${guava.version} test com.jakewharton.byteunits byteunits - 0.9.1 + ${byteunits.version} test io.netty netty-buffer - 4.1.118.Final + ${netty-buffer.version} true junit junit - 4.13.2 + ${junit.version} test @@ -77,20 +110,19 @@ org.agrona agrona - - 1.22.0 + ${agrona.version} true org.hamcrest hamcrest - 2.2 + ${hamcrest.version} test org.mockito mockito-inline - 4.11.0 + ${mockito.version} test @@ -99,7 +131,7 @@ com.mycila license-maven-plugin - 4.6 + ${license-maven-plugin.version} @@ -118,7 +150,7 @@ com.mycila license-maven-plugin-git - 4.6 + ${license-maven-plugin.version}
@@ -137,7 +169,7 @@ org.apache.maven.plugins maven-clean-plugin - 3.4.0 + ${maven-clean-plugin.version} false @@ -145,12 +177,12 @@ org.apache.maven.plugins maven-compiler-plugin - 3.13.0 + ${maven-compiler-plugin.version} org.apache.maven.plugins maven-dependency-plugin - 3.8.1 + ${maven-dependency-plugin.version} @@ -171,12 +203,12 @@ org.apache.maven.plugins maven-deploy-plugin - 3.1.3 + ${maven-deploy-plugin.version} org.apache.maven.plugins maven-enforcer-plugin - 3.5.0 + ${maven-enforcer-plugin.version} config-enforcer @@ -206,12 +238,12 @@ org.apache.maven.plugins maven-install-plugin - 3.1.3 + ${maven-install-plugin.version} org.apache.maven.plugins maven-jar-plugin - 3.4.2 + ${maven-jar-plugin.version} @@ -226,7 +258,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.11.1 + ${maven-javadoc-plugin.version} attach-javadocs @@ -239,7 +271,7 @@ org.apache.maven.plugins maven-release-plugin - 3.1.1 + ${maven-release-plugin.version} clean clean @@ -250,12 +282,12 @@ org.apache.maven.plugins maven-resources-plugin - 3.3.1 + ${maven-resources-plugin.version} org.apache.maven.plugins maven-site-plugin - 3.21.0 + ${maven-site-plugin.version} true @@ -263,7 +295,7 @@ org.apache.maven.plugins maven-source-plugin - 3.3.1 + ${maven-source-plugin.version} attach-sources @@ -276,12 +308,12 @@ org.apache.maven.plugins maven-surefire-plugin - 3.5.2 + ${maven-surefire-plugin.version} org.codehaus.mojo buildnumber-maven-plugin - 3.2.1 + ${buildnumber-maven-plugin.version} false false @@ -296,7 +328,7 @@ org.apache.maven.scm maven-scm-provider-jgit - 2.1.0 + ${maven-scm-provider-jgit.version} @@ -311,12 +343,38 @@ org.codehaus.mojo versions-maven-plugin - 2.18.0 + ${versions-maven-plugin.version} + + false + false + false + agrona.version + + + + regex + .*-alpha.* + + + regex + .*-beta.* + + + regex + .*-M.* + + + regex + .*-RC.* + + + + org.jacoco jacoco-maven-plugin - 0.8.14 + ${jacoco-maven-plugin.version} config-jacoco-prepare-agent @@ -408,7 +466,7 @@ com.github.ekryd.sortpom sortpom-maven-plugin - 4.0.0 + ${sortpom-maven-plugin.version} ${project.basedir}/pom.xml ${project.build.sourceEncoding} @@ -435,12 +493,12 @@ com.spotify.fmt fmt-maven-plugin - 2.29 + ${fmt-maven-plugin.version} com.google.googlejavaformat google-java-format - 1.28.0 + ${google-java-format.version} @@ -454,7 +512,7 @@ org.apache.maven.plugins maven-gpg-plugin - 3.2.7 + ${maven-gpg-plugin.version} sign-artifacts @@ -474,7 +532,7 @@ org.sonatype.central central-publishing-maven-plugin - 0.9.0 + ${central-publishing-maven-plugin.version} true central From 0b741e8a72febcf37b1d3debc2a91aa3b2782ed9 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sun, 26 Oct 2025 10:01:55 +1100 Subject: [PATCH 040/139] Pin Netty at 4.1.118.Final for now --- pom.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index f2a44fa3..d59d1a1d 100644 --- a/pom.xml +++ b/pom.xml @@ -60,7 +60,8 @@ 1.8 3.5.4 4.11.0 - 4.2.7.Final + + 4.1.118.Final UTF-8 4.0.0 false @@ -348,7 +349,7 @@ false false false - agrona.version + agrona.version,netty-buffer.version From 58a489f84ad5651caacc79c2019372535f085343 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Sun, 26 Oct 2025 20:12:13 +0000 Subject: [PATCH 041/139] Fix accidental change to import order --- src/main/java/org/lmdbjava/Dbi.java | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index f4d061f7..6b6eb266 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -15,17 +15,6 @@ */ package org.lmdbjava; -import jnr.ffi.Pointer; -import jnr.ffi.byref.IntByReference; -import jnr.ffi.byref.PointerByReference; -import org.lmdbjava.Library.ComparatorCallback; -import org.lmdbjava.Library.MDB_stat; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; - import static java.util.Objects.requireNonNull; import static jnr.ffi.Memory.allocateDirect; import static jnr.ffi.NativeType.ADDRESS; @@ -42,6 +31,16 @@ import static org.lmdbjava.PutFlags.MDB_RESERVE; import static org.lmdbjava.ResultCodeMapper.checkRc; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import jnr.ffi.Pointer; +import jnr.ffi.byref.IntByReference; +import jnr.ffi.byref.PointerByReference; +import org.lmdbjava.Library.ComparatorCallback; +import org.lmdbjava.Library.MDB_stat; + /** * LMDB Database. * 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 042/139] 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 043/139] 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 044/139] 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 045/139] 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 046/139] 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 047/139] 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 048/139] 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 2fb1c02b1ac5642292839590757b71822e5f55fb Mon Sep 17 00:00:00 2001 From: stroomdev66 Date: Tue, 28 Oct 2025 18:20:30 +0000 Subject: [PATCH 049/139] Upgrade to JUnit 5 and replace Hamcrest with AssertJ --- pom.xml | 34 +- src/main/java/org/lmdbjava/Dbi.java | 2 +- .../org/lmdbjava/ByteBufferProxyTest.java | 94 +-- .../java/org/lmdbjava/ComparatorTest.java | 120 ++- .../java/org/lmdbjava/CursorIterableTest.java | 287 ++++--- .../java/org/lmdbjava/CursorParamTest.java | 111 ++- src/test/java/org/lmdbjava/CursorTest.java | 359 ++++---- src/test/java/org/lmdbjava/DbiTest.java | 519 ++++++------ src/test/java/org/lmdbjava/EnvTest.java | 653 +++++++++------ src/test/java/org/lmdbjava/FileUtil.java | 153 ++++ .../org/lmdbjava/GarbageCollectionTest.java | 110 ++- src/test/java/org/lmdbjava/KeyRangeTest.java | 85 +- src/test/java/org/lmdbjava/LibraryTest.java | 14 +- .../java/org/lmdbjava/MaskedFlagTest.java | 47 +- src/test/java/org/lmdbjava/MetaTest.java | 20 +- .../org/lmdbjava/ResultCodeMapperTest.java | 56 +- .../java/org/lmdbjava/TargetNameTest.java | 32 +- src/test/java/org/lmdbjava/TutorialTest.java | 774 +++++++++--------- src/test/java/org/lmdbjava/TxnTest.java | 405 +++++---- src/test/java/org/lmdbjava/VerifierTest.java | 38 +- 20 files changed, 2188 insertions(+), 1725 deletions(-) create mode 100644 src/test/java/org/lmdbjava/FileUtil.java diff --git a/pom.xml b/pom.xml index d59d1a1d..3cb7ccc5 100644 --- a/pom.xml +++ b/pom.xml @@ -27,17 +27,17 @@ 1.22.0 + 3.27.6 3.2.1 0.9.1 0.9.0 2.29 1.28.0 33.5.0-jre - 2.2 0.8.14 0.10.4 2.2.18 - 4.13.2 + 5.14.0 4.6 3.5.0 3.14.1 @@ -96,18 +96,6 @@ ${netty-buffer.version} true
- - junit - junit - ${junit.version} - test - - - org.hamcrest - hamcrest-core - - - org.agrona agrona @@ -115,9 +103,21 @@ true - org.hamcrest - hamcrest - ${hamcrest.version} + org.assertj + assertj-core + ${assertj.version} + test + + + org.junit.jupiter + junit-jupiter-api + ${junit.version} + test + + + org.junit.jupiter + junit-jupiter-params + ${junit.version} test diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index 6b6eb266..5449c172 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -81,7 +81,7 @@ public final class Dbi { if (nativeCb) { this.ccb = (keyA, keyB) -> { - final T compKeyA = proxy.out(proxy.allocate(), keyA); + 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); diff --git a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java index b68f39ef..82c0abce 100644 --- a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java +++ b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.lmdbjava; import static java.lang.Integer.BYTES; @@ -20,11 +21,8 @@ import static java.nio.ByteBuffer.allocateDirect; import static java.nio.ByteOrder.BIG_ENDIAN; import static java.nio.ByteOrder.LITTLE_ENDIAN; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.CoreMatchers.startsWith; -import static org.hamcrest.MatcherAssert.assertThat; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.lmdbjava.BufferProxy.MDB_VAL_STRUCT_SIZE; import static org.lmdbjava.ByteBufferProxy.AbstractByteBufferProxy.findField; import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; @@ -36,15 +34,11 @@ import static org.lmdbjava.TestUtils.invokePrivateConstructor; import static org.lmdbjava.UnsafeAccess.ALLOW_UNSAFE; -import java.io.File; -import java.io.IOException; import java.lang.reflect.Field; import java.nio.ByteBuffer; import jnr.ffi.Pointer; import jnr.ffi.provider.MemoryManager; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.Test; import org.lmdbjava.ByteBufferProxy.BufferMustBeDirectException; import org.lmdbjava.Env.ReadersFullException; @@ -53,23 +47,27 @@ public final class ByteBufferProxyTest { static final MemoryManager MEM_MGR = RUNTIME.getMemoryManager(); - @Rule public final TemporaryFolder tmp = new TemporaryFolder(); - - @Test(expected = BufferMustBeDirectException.class) - public void buffersMustBeDirect() throws IOException { - final File path = tmp.newFolder(); - try (Env env = create().setMaxReaders(1).open(path)) { - 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 - } + @Test + 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 + } + }); + }) + .isInstanceOf(BufferMustBeDirectException.class); } @Test - public void byteOrderResets() { + void byteOrderResets() { final int retries = 100; for (int i = 0; i < retries; i++) { final ByteBuffer bb = PROXY_OPTIMAL.allocate(); @@ -77,56 +75,60 @@ public void byteOrderResets() { PROXY_OPTIMAL.deallocate(bb); } for (int i = 0; i < retries; i++) { - assertThat(PROXY_OPTIMAL.allocate().order(), is(BIG_ENDIAN)); + assertThat(PROXY_OPTIMAL.allocate().order()).isEqualTo(BIG_ENDIAN); } } @Test - public void coverPrivateConstructor() { + void coverPrivateConstructor() { invokePrivateConstructor(ByteBufferProxy.class); } - @Test(expected = LmdbException.class) - public void fieldNeverFound() { - findField(Exception.class, "notARealField"); + @Test + void fieldNeverFound() { + assertThatThrownBy( + () -> { + findField(Exception.class, "notARealField"); + }) + .isInstanceOf(LmdbException.class); } @Test - public void fieldSuperclassScan() { + void fieldSuperclassScan() { final Field f = findField(ReadersFullException.class, "rc"); - assertThat(f, is(notNullValue())); + assertThat(f).isNotNull(); } @Test - public void inOutBuffersProxyOptimal() { + void inOutBuffersProxyOptimal() { checkInOut(PROXY_OPTIMAL); } @Test - public void inOutBuffersProxySafe() { + void inOutBuffersProxySafe() { checkInOut(PROXY_SAFE); } @Test - public void optimalAlwaysAvailable() { + void optimalAlwaysAvailable() { final BufferProxy v = PROXY_OPTIMAL; - assertThat(v, is(notNullValue())); + assertThat(v).isNotNull(); } @Test - public void safeCanBeForced() { + void safeCanBeForced() { final BufferProxy v = PROXY_SAFE; - assertThat(v, is(notNullValue())); - assertThat(v.getClass().getSimpleName(), startsWith("Reflect")); + assertThat(v).isNotNull(); + assertThat(v.getClass().getSimpleName()).startsWith("Reflect"); } @Test - public void unsafeIsDefault() { - assertThat(ALLOW_UNSAFE, is(true)); + void unsafeIsDefault() { + assertThat(ALLOW_UNSAFE).isTrue(); final BufferProxy v = PROXY_OPTIMAL; - assertThat(v, is(notNullValue())); - assertThat(v, is(not(PROXY_SAFE))); - assertThat(v.getClass().getSimpleName(), startsWith("Unsafe")); + assertThat(v).isNotNull(); + assertThat(v).isNotEqualTo(PROXY_SAFE); + assertThat(v.getClass().getSimpleName()).startsWith("Unsafe"); } private void checkInOut(final BufferProxy v) { @@ -144,8 +146,8 @@ private void checkInOut(final BufferProxy v) { final ByteBuffer bb = allocateDirect(1); v.out(bb, p); - assertThat(bb.getInt(), is(2)); - assertThat(bb.getInt(), is(3)); - assertThat(bb.remaining(), is(0)); + assertThat(bb.getInt()).isEqualTo(2); + assertThat(bb.getInt()).isEqualTo(3); + assertThat(bb.remaining()).isEqualTo(0); } } diff --git a/src/test/java/org/lmdbjava/ComparatorTest.java b/src/test/java/org/lmdbjava/ComparatorTest.java index 3c7e7a4d..b4946a26 100644 --- a/src/test/java/org/lmdbjava/ComparatorTest.java +++ b/src/test/java/org/lmdbjava/ComparatorTest.java @@ -13,19 +13,16 @@ * 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 java.nio.charset.StandardCharsets.US_ASCII; -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; +import static org.assertj.core.api.Assertions.assertThat; 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.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.ComparatorTest.ComparatorResult.*; import static org.lmdbjava.DirectBufferProxy.PROXY_DB; import com.google.common.primitives.SignedBytes; @@ -34,16 +31,14 @@ import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Comparator; +import java.util.stream.Stream; import org.agrona.DirectBuffer; import org.agrona.concurrent.UnsafeBuffer; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; +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. */ -@RunWith(Parameterized.class) public final class ComparatorTest { // H = 1 (high), L = 0 (low), X = byte not set in buffer @@ -59,20 +54,16 @@ public final class ComparatorTest { private static final byte[] LX = buffer(0); private static final byte[] XX = buffer(); - /** Injected by {@link #data()} with appropriate runner. */ - @Parameter public ComparatorRunner comparator; - - @Parameters(name = "{index}: comparable: {0}") - public static Object[] data() { - final ComparatorRunner string = new StringRunner(); - final ComparatorRunner db = new DirectBufferRunner(); - final ComparatorRunner ba = new ByteArrayRunner(); - final ComparatorRunner baUnsigned = new UnsignedByteArrayRunner(); - final ComparatorRunner bb = new ByteBufferRunner(); - final ComparatorRunner netty = new NettyRunner(); - final ComparatorRunner gub = new GuavaUnsignedBytes(); - final ComparatorRunner gsb = new GuavaSignedBytes(); - return new Object[] {string, db, ba, baUnsigned, bb, netty, gub, gsb}; + static Stream comparatorProvider() { + return Stream.of( + Arguments.arguments("StringRunner", new StringRunner()), + Arguments.arguments("DirectBufferRunner", new DirectBufferRunner()), + Arguments.arguments("ByteArrayRunner", new ByteArrayRunner()), + Arguments.arguments("UnsignedByteArrayRunner", new UnsignedByteArrayRunner()), + Arguments.arguments("ByteBufferRunner", new ByteBufferRunner()), + Arguments.arguments("NettyRunner", new NettyRunner()), + Arguments.arguments("GuavaUnsignedBytes", new GuavaUnsignedBytes()), + Arguments.arguments("GuavaSignedBytes", new GuavaSignedBytes())); } private static byte[] buffer(final int... bytes) { @@ -83,52 +74,55 @@ private static byte[] buffer(final int... bytes) { return array; } - @Test - public void atLeastOneBufferHasEightBytes() { - assertThat(get(comparator.compare(HLLLLLLL, LLLLLLLL)), is(GREATER_THAN)); - assertThat(get(comparator.compare(LLLLLLLL, HLLLLLLL)), is(LESS_THAN)); + @ParameterizedTest + @MethodSource("comparatorProvider") + void atLeastOneBufferHasEightBytes(final String str, final ComparatorRunner comparator) { + assertThat(get(comparator.compare(HLLLLLLL, LLLLLLLL))).isEqualTo(GREATER_THAN); + assertThat(get(comparator.compare(LLLLLLLL, HLLLLLLL))).isEqualTo(LESS_THAN); - assertThat(get(comparator.compare(LHLLLLLL, LLLLLLLL)), is(GREATER_THAN)); - assertThat(get(comparator.compare(LLLLLLLL, LHLLLLLL)), is(LESS_THAN)); + assertThat(get(comparator.compare(LHLLLLLL, LLLLLLLL))).isEqualTo(GREATER_THAN); + assertThat(get(comparator.compare(LLLLLLLL, LHLLLLLL))).isEqualTo(LESS_THAN); - assertThat(get(comparator.compare(LLLLLLLL, LLLLLLLX)), is(GREATER_THAN)); - assertThat(get(comparator.compare(LLLLLLLX, LLLLLLLL)), is(LESS_THAN)); + assertThat(get(comparator.compare(LLLLLLLL, LLLLLLLX))).isEqualTo(GREATER_THAN); + assertThat(get(comparator.compare(LLLLLLLX, LLLLLLLL))).isEqualTo(LESS_THAN); - assertThat(get(comparator.compare(HLLLLLLL, HLLLLLLX)), is(GREATER_THAN)); - assertThat(get(comparator.compare(HLLLLLLX, HLLLLLLL)), is(LESS_THAN)); + assertThat(get(comparator.compare(HLLLLLLL, HLLLLLLX))).isEqualTo(GREATER_THAN); + assertThat(get(comparator.compare(HLLLLLLX, HLLLLLLL))).isEqualTo(LESS_THAN); - assertThat(get(comparator.compare(HLLLLLLX, LHLLLLLL)), is(GREATER_THAN)); - assertThat(get(comparator.compare(LHLLLLLL, HLLLLLLX)), is(LESS_THAN)); + assertThat(get(comparator.compare(HLLLLLLX, LHLLLLLL))).isEqualTo(GREATER_THAN); + assertThat(get(comparator.compare(LHLLLLLL, HLLLLLLX))).isEqualTo(LESS_THAN); } - @Test - public void buffersOfTwoBytes() { - assertThat(get(comparator.compare(LL, XX)), is(GREATER_THAN)); - assertThat(get(comparator.compare(XX, LL)), is(LESS_THAN)); + @ParameterizedTest + @MethodSource("comparatorProvider") + void buffersOfTwoBytes(final String str, final ComparatorRunner comparator) { + assertThat(get(comparator.compare(LL, XX))).isEqualTo(GREATER_THAN); + assertThat(get(comparator.compare(XX, LL))).isEqualTo(LESS_THAN); - assertThat(get(comparator.compare(LL, LX)), is(GREATER_THAN)); - assertThat(get(comparator.compare(LX, LL)), is(LESS_THAN)); + assertThat(get(comparator.compare(LL, LX))).isEqualTo(GREATER_THAN); + assertThat(get(comparator.compare(LX, LL))).isEqualTo(LESS_THAN); - assertThat(get(comparator.compare(LH, LX)), is(GREATER_THAN)); - assertThat(get(comparator.compare(LX, HL)), is(LESS_THAN)); + assertThat(get(comparator.compare(LH, LX))).isEqualTo(GREATER_THAN); + assertThat(get(comparator.compare(LX, HL))).isEqualTo(LESS_THAN); - assertThat(get(comparator.compare(HX, LL)), is(GREATER_THAN)); - assertThat(get(comparator.compare(LH, HX)), is(LESS_THAN)); + assertThat(get(comparator.compare(HX, LL))).isEqualTo(GREATER_THAN); + assertThat(get(comparator.compare(LH, HX))).isEqualTo(LESS_THAN); } - @Test - public void equalBuffers() { - assertThat(get(comparator.compare(LL, LL)), is(EQUAL_TO)); - assertThat(get(comparator.compare(HX, HX)), is(EQUAL_TO)); - assertThat(get(comparator.compare(LH, LH)), is(EQUAL_TO)); - assertThat(get(comparator.compare(LL, LL)), is(EQUAL_TO)); - assertThat(get(comparator.compare(LX, LX)), is(EQUAL_TO)); - - assertThat(get(comparator.compare(HLLLLLLL, HLLLLLLL)), is(EQUAL_TO)); - assertThat(get(comparator.compare(HLLLLLLX, HLLLLLLX)), is(EQUAL_TO)); - assertThat(get(comparator.compare(LHLLLLLL, LHLLLLLL)), is(EQUAL_TO)); - assertThat(get(comparator.compare(LLLLLLLL, LLLLLLLL)), is(EQUAL_TO)); - assertThat(get(comparator.compare(LLLLLLLX, LLLLLLLX)), is(EQUAL_TO)); + @ParameterizedTest + @MethodSource("comparatorProvider") + void equalBuffers(final String str, final ComparatorRunner comparator) { + assertThat(get(comparator.compare(LL, LL))).isEqualTo(EQUAL_TO); + assertThat(get(comparator.compare(HX, HX))).isEqualTo(EQUAL_TO); + assertThat(get(comparator.compare(LH, LH))).isEqualTo(EQUAL_TO); + assertThat(get(comparator.compare(LL, LL))).isEqualTo(EQUAL_TO); + assertThat(get(comparator.compare(LX, LX))).isEqualTo(EQUAL_TO); + + assertThat(get(comparator.compare(HLLLLLLL, HLLLLLLL))).isEqualTo(EQUAL_TO); + assertThat(get(comparator.compare(HLLLLLLX, HLLLLLLX))).isEqualTo(EQUAL_TO); + assertThat(get(comparator.compare(LHLLLLLL, LHLLLLLL))).isEqualTo(EQUAL_TO); + assertThat(get(comparator.compare(LLLLLLLL, LLLLLLLL))).isEqualTo(EQUAL_TO); + assertThat(get(comparator.compare(LLLLLLLX, LLLLLLLX))).isEqualTo(EQUAL_TO); } /** Tests {@link ByteArrayProxy}. */ @@ -169,14 +163,14 @@ public int compare(final byte[] o1, final byte[] o2) { o2b = arrayToBuffer(o2, o2.length * 3); final int result2 = c.compare(o1b, o2b); - assertThat(result2, is(result)); + assertThat(result2).isEqualTo(result); // Now try with buffers sized to the array. o1b = ByteBuffer.wrap(o1); o2b = ByteBuffer.wrap(o2); final int result3 = c.compare(o1b, o2b); - assertThat(result3, is(result)); + assertThat(result3).isEqualTo(result); return result; } diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index bd23bc55..22f5f7c7 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -13,13 +13,13 @@ * 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.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.Env.create; import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; @@ -47,9 +47,8 @@ import static org.lmdbjava.TestUtils.bb; import com.google.common.primitives.UnsignedBytes; -import java.io.File; -import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Comparator; import java.util.Deque; @@ -57,200 +56,210 @@ 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.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.lmdbjava.CursorIterable.KeyVal; /** Test {@link CursorIterable}. */ public final class CursorIterableTest { - @Rule public final TemporaryFolder tmp = new TemporaryFolder(); + private Path file; private Dbi db; private Env env; private Deque list; - @After - public void after() { + @BeforeEach + void beforeEach() { + file = FileUtil.createTempFile(); + env = + create() + .setMapSize(KIBIBYTES.toBytes(256)) + .setMaxReaders(1) + .setMaxDbs(1) + .open(file.toFile(), 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(); + } + } + + @AfterEach + void afterEach() { env.close(); + FileUtil.delete(file); } @Test - public void allBackwardTest() { + void allBackwardTest() { verify(allBackward(), 8, 6, 4, 2); } @Test - public void allTest() { + void allTest() { verify(all(), 2, 4, 6, 8); } @Test - public void atLeastBackwardTest() { + void atLeastBackwardTest() { verify(atLeastBackward(bb(5)), 4, 2); verify(atLeastBackward(bb(6)), 6, 4, 2); verify(atLeastBackward(bb(9)), 8, 6, 4, 2); } @Test - public void atLeastTest() { + void atLeastTest() { verify(atLeast(bb(5)), 6, 8); verify(atLeast(bb(6)), 6, 8); } @Test - public void atMostBackwardTest() { + void atMostBackwardTest() { verify(atMostBackward(bb(5)), 8, 6); verify(atMostBackward(bb(6)), 8, 6); } @Test - public void atMostTest() { + void atMostTest() { verify(atMost(bb(5)), 2, 4); 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() { + void closedBackwardTest() { verify(closedBackward(bb(7), bb(3)), 6, 4); verify(closedBackward(bb(6), bb(2)), 6, 4, 2); verify(closedBackward(bb(9), bb(3)), 8, 6, 4); } @Test - public void closedOpenBackwardTest() { + void closedOpenBackwardTest() { verify(closedOpenBackward(bb(8), bb(3)), 8, 6, 4); verify(closedOpenBackward(bb(7), bb(2)), 6, 4); verify(closedOpenBackward(bb(9), bb(3)), 8, 6, 4); } @Test - public void closedOpenTest() { + void closedOpenTest() { verify(closedOpen(bb(3), bb(8)), 4, 6); verify(closedOpen(bb(2), bb(6)), 2, 4); } @Test - public void closedTest() { + void closedTest() { verify(closed(bb(3), bb(7)), 4, 6); verify(closed(bb(2), bb(6)), 2, 4, 6); verify(closed(bb(1), bb(7)), 2, 4, 6); } @Test - public void greaterThanBackwardTest() { + void greaterThanBackwardTest() { verify(greaterThanBackward(bb(6)), 4, 2); verify(greaterThanBackward(bb(7)), 6, 4, 2); verify(greaterThanBackward(bb(9)), 8, 6, 4, 2); } @Test - public void greaterThanTest() { + void greaterThanTest() { verify(greaterThan(bb(4)), 6, 8); verify(greaterThan(bb(3)), 4, 6, 8); } - @Test(expected = IllegalStateException.class) - public void iterableOnlyReturnedOnce() { - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { - c.iterator(); // ok - c.iterator(); // fails - } + @Test + void iterableOnlyReturnedOnce() { + assertThatThrownBy( + () -> { + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + c.iterator(); // ok + c.iterator(); // fails + } + }) + .isInstanceOf(IllegalStateException.class); } @Test - public void iterate() { + 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())); + assertThat(kv.key().getInt()).isEqualTo(list.pollFirst()); + assertThat(kv.val().getInt()).isEqualTo(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 - } + @Test + void iteratorOnlyReturnedOnce() { + assertThatThrownBy( + () -> { + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + c.iterator(); // ok + c.iterator(); // fails + } + }) + .isInstanceOf(IllegalStateException.class); } @Test - public void lessThanBackwardTest() { + void lessThanBackwardTest() { verify(lessThanBackward(bb(5)), 8, 6); verify(lessThanBackward(bb(2)), 8, 6, 4); } @Test - public void lessThanTest() { + void lessThanTest() { verify(lessThan(bb(5)), 2, 4); verify(lessThan(bb(8)), 2, 4, 6); } - @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())); - } - assertThat(i.hasNext(), is(false)); - i.next(); - } + @Test + void nextThrowsNoSuchElementExceptionIfNoMoreElements() { + assertThatThrownBy( + () -> { + 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); } @Test - public void openBackwardTest() { + void openBackwardTest() { verify(openBackward(bb(7), bb(2)), 6, 4); verify(openBackward(bb(8), bb(1)), 6, 4, 2); verify(openBackward(bb(9), bb(4)), 8, 6); } @Test - public void openClosedBackwardTest() { + void openClosedBackwardTest() { verify(openClosedBackward(bb(7), bb(2)), 6, 4, 2); verify(openClosedBackward(bb(8), bb(4)), 6, 4); verify(openClosedBackward(bb(9), bb(4)), 8, 6, 4); } @Test - public void openClosedBackwardTestWithGuava() { + void openClosedBackwardTestWithGuava() { final Comparator guava = UnsignedBytes.lexicographicalComparator(); final Comparator comparator = (bb1, bb2) -> { @@ -271,19 +280,19 @@ public void openClosedBackwardTestWithGuava() { } @Test - public void openClosedTest() { + void openClosedTest() { verify(openClosed(bb(3), bb(8)), 4, 6, 8); verify(openClosed(bb(2), bb(6)), 4, 6); } @Test - public void openTest() { + void openTest() { verify(open(bb(3), bb(7)), 4, 6); verify(open(bb(2), bb(8)), 4, 6); } @Test - public void removeOddElements() { + void removeOddElements() { verify(all(), 2, 4, 6, 8); int idx = -1; try (Txn txn = env.txnWrite()) { @@ -302,55 +311,71 @@ public void removeOddElements() { 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(); + @Test + void nextWithClosedEnvTest() { + assertThatThrownBy( + () -> { + 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(); + } + } + }) + .isInstanceOf(Env.AlreadyClosedException.class); } - @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(); + @Test + void removeWithClosedEnvTest() { + assertThatThrownBy( + () -> { + 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).isNotNull(); - env.close(); - c.remove(); - } - } + env.close(); + c.remove(); + } + } + }) + .isInstanceOf(Env.AlreadyClosedException.class); } - @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(); + @Test + void hasNextWithClosedEnvTest() { + assertThatThrownBy( + () -> { + 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(); + } + } + }) + .isInstanceOf(Env.AlreadyClosedException.class); } - @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(); + @Test + void forEachRemainingWithClosedEnvTest() { + assertThatThrownBy( + () -> { + 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 -> {}); + } + } + }) + .isInstanceOf(Env.AlreadyClosedException.class); } private void verify(final KeyRange range, final int... expected) { @@ -367,13 +392,13 @@ private void verify( final int key = kv.key().getInt(); final int val = kv.val().getInt(); results.add(key); - assertThat(val, is(key + 1)); + assertThat(val).isEqualTo(key + 1); } } - assertThat(results, hasSize(expected.length)); + assertThat(results.size()).isEqualTo(expected.length); for (int idx = 0; idx < results.size(); idx++) { - assertThat(results.get(idx), is(expected[idx])); + assertThat(results.get(idx)).isEqualTo(expected[idx]); } } } diff --git a/src/test/java/org/lmdbjava/CursorParamTest.java b/src/test/java/org/lmdbjava/CursorParamTest.java index bd99e709..0b2df19e 100644 --- a/src/test/java/org/lmdbjava/CursorParamTest.java +++ b/src/test/java/org/lmdbjava/CursorParamTest.java @@ -13,14 +13,13 @@ * 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 com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; import static java.lang.Long.BYTES; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.collection.IsEmptyCollection.empty; +import static org.assertj.core.api.Assertions.assertThat; import static org.lmdbjava.ByteArrayProxy.PROXY_BA; import static org.lmdbjava.ByteBufProxy.PROXY_NETTY; import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; @@ -44,40 +43,31 @@ import static org.lmdbjava.TestUtils.nb; import io.netty.buffer.ByteBuf; -import java.io.File; -import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.file.Path; +import java.util.stream.Stream; import org.agrona.DirectBuffer; import org.agrona.MutableDirectBuffer; -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.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; +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; /** Test {@link Cursor} with different buffer implementations. */ -@RunWith(Parameterized.class) public final class CursorParamTest { - /** Injected by {@link #data()} with appropriate runner. */ - @Parameter public BufferRunner runner; - - @Rule public final TemporaryFolder tmp = new TemporaryFolder(); - - @Parameters(name = "{index}: buffer adapter: {0}") - public static Object[] data() { - final BufferRunner bb1 = new ByteBufferRunner(PROXY_OPTIMAL); - final BufferRunner bb2 = new ByteBufferRunner(PROXY_SAFE); - final BufferRunner ba = new ByteArrayRunner(PROXY_BA); - final BufferRunner db = new DirectBufferRunner(); - final BufferRunner netty = new NettyBufferRunner(); - return new Object[] {bb1, bb2, ba, db, netty}; + static Stream data() { + return Stream.of( + Arguments.arguments("ByteBufferRunner(PROXY_OPTIMAL)", new ByteBufferRunner(PROXY_OPTIMAL)), + Arguments.arguments("ByteBufferRunner(PROXY_SAFE)", new ByteBufferRunner(PROXY_SAFE)), + Arguments.arguments("ByteArrayRunner(PROXY_BA)", new ByteArrayRunner(PROXY_BA)), + Arguments.arguments("DirectBufferRunner", new DirectBufferRunner()), + Arguments.arguments("NettyBufferRunner", new NettyBufferRunner())); } - @Test - public void execute() { + @ParameterizedTest + @MethodSource("data") + void execute(final String name, final BufferRunner runner, @TempDir final Path tmp) { runner.execute(tmp); } @@ -95,11 +85,11 @@ protected AbstractBufferRunner(final BufferProxy proxy) { } @Override - public final void execute(final TemporaryFolder tmp) { + public final void execute(final Path tmp) { try (Env env = env(tmp)) { - assertThat(env.getDbiNames(), empty()); + assertThat(env.getDbiNames()).isEmpty(); final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); - assertThat(env.getDbiNames().get(0), is(DB_1.getBytes(UTF_8))); + assertThat(env.getDbiNames().get(0)).isEqualTo(DB_1.getBytes(UTF_8)); try (Txn txn = env.txnWrite(); Cursor c = db.openCursor(txn)) { // populate data @@ -113,71 +103,66 @@ public final void execute(final TemporaryFolder tmp) { // check MDB_SET operations final T key3 = set(3); - assertThat(c.get(key3, MDB_SET_KEY), is(true)); - assertThat(get(c.key()), is(3)); - assertThat(get(c.val()), is(4)); + assertThat(c.get(key3, MDB_SET_KEY)).isTrue(); + assertThat(get(c.key())).isEqualTo(3); + assertThat(get(c.val())).isEqualTo(4); final T key6 = set(6); - assertThat(c.get(key6, MDB_SET_RANGE), is(true)); - assertThat(get(c.key()), is(7)); + assertThat(c.get(key6, MDB_SET_RANGE)).isTrue(); + assertThat(get(c.key())).isEqualTo(7); if (!(this instanceof ByteArrayRunner)) { - assertThat(get(c.val()), is(8)); + assertThat(get(c.val())).isEqualTo(8); } final T key999 = set(999); - assertThat(c.get(key999, MDB_SET_KEY), is(false)); + assertThat(c.get(key999, MDB_SET_KEY)).isFalse(); // check MDB navigation operations - assertThat(c.seek(MDB_LAST), is(true)); + assertThat(c.seek(MDB_LAST)).isTrue(); final int mdb1 = get(c.key()); final int mdb2 = get(c.val()); - assertThat(c.seek(MDB_PREV), is(true)); + assertThat(c.seek(MDB_PREV)).isTrue(); final int mdb3 = get(c.key()); final int mdb4 = get(c.val()); - assertThat(c.seek(MDB_NEXT), is(true)); + assertThat(c.seek(MDB_NEXT)).isTrue(); final int mdb5 = get(c.key()); final int mdb6 = get(c.val()); - assertThat(c.seek(MDB_FIRST), is(true)); + assertThat(c.seek(MDB_FIRST)).isTrue(); final int mdb7 = get(c.key()); final int mdb8 = get(c.val()); // assert afterwards to ensure memory address from LMDB // are valid within same txn and across cursor movement // MDB_LAST - assertThat(mdb1, is(7)); + assertThat(mdb1).isEqualTo(7); if (!(this instanceof ByteArrayRunner)) { - assertThat(mdb2, is(8)); + assertThat(mdb2).isEqualTo(8); } // MDB_PREV - assertThat(mdb3, is(5)); - assertThat(mdb4, is(6)); + assertThat(mdb3).isEqualTo(5); + assertThat(mdb4).isEqualTo(6); // MDB_NEXT - assertThat(mdb5, is(7)); + assertThat(mdb5).isEqualTo(7); if (!(this instanceof ByteArrayRunner)) { - assertThat(mdb6, is(8)); + assertThat(mdb6).isEqualTo(8); } // MDB_FIRST - assertThat(mdb7, is(1)); - assertThat(mdb8, is(2)); + assertThat(mdb7).isEqualTo(1); + assertThat(mdb8).isEqualTo(2); } } } - private Env env(final TemporaryFolder tmp) { - try { - final File path = tmp.newFile(); - return create(proxy) - .setMapSize(KIBIBYTES.toBytes(1_024)) - .setMaxReaders(1) - .setMaxDbs(1) - .open(path, POSIX_MODE, MDB_NOSUBDIR); - } catch (final IOException e) { - throw new LmdbException("IO failure", e); - } + private Env env(final Path tmp) { + return create(proxy) + .setMapSize(MEBIBYTES.toBytes(1)) + .setMaxReaders(1) + .setMaxDbs(1) + .open(tmp.resolve("db").toFile(), POSIX_MODE, MDB_NOSUBDIR); } } @@ -291,7 +276,7 @@ public void set(final ByteBuf buff, final int val) { */ private interface BufferRunner { - void execute(TemporaryFolder tmp); + void execute(Path tmp); T set(int val); diff --git a/src/test/java/org/lmdbjava/CursorTest.java b/src/test/java/org/lmdbjava/CursorTest.java index cf6e4dea..a0c78855 100644 --- a/src/test/java/org/lmdbjava/CursorTest.java +++ b/src/test/java/org/lmdbjava/CursorTest.java @@ -13,16 +13,15 @@ * 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 com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; import static java.lang.Long.BYTES; import static java.lang.Long.MIN_VALUE; import static java.nio.ByteBuffer.allocateDirect; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.DbiFlags.MDB_DUPFIXED; @@ -41,141 +40,175 @@ 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.file.Path; import java.util.function.Consumer; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.lmdbjava.Cursor.ClosedException; +import org.lmdbjava.Env.AlreadyClosedException; import org.lmdbjava.Txn.NotReadyException; import org.lmdbjava.Txn.ReadOnlyRequiredException; /** Test {@link Cursor}. */ public final class CursorTest { - @Rule public final TemporaryFolder tmp = new TemporaryFolder(); - + private Path file; private Env env; - @After - public void after() { - env.close(); + @BeforeEach + void beforeEach() { + file = FileUtil.createTempFile(); + env = + create(PROXY_OPTIMAL) + .setMapSize(MEBIBYTES.toBytes(1)) + .setMaxReaders(1) + .setMaxDbs(1) + .open(file.toFile(), POSIX_MODE, MDB_NOSUBDIR); } - @Before - public void before() throws IOException { - try { - final File path = tmp.newFile(); - env = - create(PROXY_OPTIMAL) - .setMapSize(KIBIBYTES.toBytes(1_024)) - .setMaxReaders(1) - .setMaxDbs(1) - .open(path, POSIX_MODE, MDB_NOSUBDIR); - } catch (final IOException e) { - throw new LmdbException("IO failure", e); - } + @AfterEach + void afterEach() { + env.close(); + FileUtil.delete(file); } - @Test(expected = ClosedException.class) - public void closedCursorRejectsSubsequentGets() { - final Dbi db = env.openDbi(DB_1, MDB_CREATE); - try (Txn txn = env.txnWrite()) { - final Cursor c = db.openCursor(txn); - c.close(); - c.seek(MDB_FIRST); - } + @Test + void closedCursorRejectsSubsequentGets() { + assertThatThrownBy( + () -> { + final Dbi db = env.openDbi(DB_1, MDB_CREATE); + try (Txn txn = env.txnWrite()) { + final Cursor c = db.openCursor(txn); + c.close(); + c.seek(MDB_FIRST); + } + }) + .isInstanceOf(ClosedException.class); } - @Test(expected = Env.AlreadyClosedException.class) - public void closedEnvRejectsSeekFirstCall() { - doEnvClosedTest(null, c -> c.seek(MDB_FIRST)); + @Test + void closedEnvRejectsSeekFirstCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest(null, c -> c.seek(MDB_FIRST)); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = Env.AlreadyClosedException.class) - public void closedEnvRejectsSeekLastCall() { - doEnvClosedTest(null, c -> c.seek(MDB_LAST)); + @Test + void closedEnvRejectsSeekLastCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest(null, c -> c.seek(MDB_LAST)); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = Env.AlreadyClosedException.class) - public void closedEnvRejectsSeekNextCall() { - doEnvClosedTest(null, c -> c.seek(MDB_NEXT)); + @Test + void closedEnvRejectsSeekNextCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest(null, c -> c.seek(MDB_NEXT)); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = Env.AlreadyClosedException.class) - public void closedEnvRejectsCloseCall() { - doEnvClosedTest(null, Cursor::close); + @Test + void closedEnvRejectsCloseCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest(null, Cursor::close); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = Env.AlreadyClosedException.class) - public void closedEnvRejectsFirstCall() { - doEnvClosedTest(null, Cursor::first); + @Test + void closedEnvRejectsFirstCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest(null, Cursor::first); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = Env.AlreadyClosedException.class) - public void closedEnvRejectsLastCall() { - doEnvClosedTest(null, Cursor::last); + @Test + void closedEnvRejectsLastCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest(null, Cursor::last); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = Env.AlreadyClosedException.class) - public void closedEnvRejectsPrevCall() { - doEnvClosedTest( - c -> { - c.first(); - assertThat(c.key().getInt(), is(1)); - assertThat(c.val().getInt(), is(10)); - c.next(); - }, - Cursor::prev); + @Test + void closedEnvRejectsPrevCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest( + c -> { + c.first(); + assertThat(c.key().getInt()).isEqualTo(1); + assertThat(c.val().getInt()).isEqualTo(10); + c.next(); + }, + Cursor::prev); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = Env.AlreadyClosedException.class) - public void closedEnvRejectsDeleteCall() { - doEnvClosedTest( - c -> { - c.first(); - assertThat(c.key().getInt(), is(1)); - assertThat(c.val().getInt(), is(10)); - }, - Cursor::delete); + @Test + void closedEnvRejectsDeleteCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest( + c -> { + c.first(); + assertThat(c.key().getInt()).isEqualTo(1); + assertThat(c.val().getInt()).isEqualTo(10); + }, + Cursor::delete); + }) + .isInstanceOf(AlreadyClosedException.class); } @Test - public void count() { + void count() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); try (Txn txn = env.txnWrite(); Cursor c = db.openCursor(txn)) { c.put(bb(1), bb(2), MDB_APPENDDUP); - assertThat(c.count(), is(1L)); + assertThat(c.count()).isEqualTo(1L); c.put(bb(1), bb(4), MDB_APPENDDUP); c.put(bb(1), bb(6), MDB_APPENDDUP); - assertThat(c.count(), is(3L)); + assertThat(c.count()).isEqualTo(3L); c.put(bb(2), bb(1), MDB_APPENDDUP); c.put(bb(2), bb(2), MDB_APPENDDUP); - assertThat(c.count(), is(2L)); + assertThat(c.count()).isEqualTo(2L); } } - @Test(expected = NotReadyException.class) - public void cursorCannotCloseIfTransactionCommitted() { - final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); - try (Txn txn = env.txnWrite()) { - try (Cursor c = db.openCursor(txn); ) { - c.put(bb(1), bb(2), MDB_APPENDDUP); - assertThat(c.count(), is(1L)); - c.put(bb(1), bb(4), MDB_APPENDDUP); - assertThat(c.count(), is(2L)); - txn.commit(); - } - } + @Test + void cursorCannotCloseIfTransactionCommitted() { + assertThatThrownBy( + () -> { + final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); + try (Txn txn = env.txnWrite()) { + try (Cursor c = db.openCursor(txn); ) { + c.put(bb(1), bb(2), MDB_APPENDDUP); + assertThat(c.count()).isEqualTo(1L); + c.put(bb(1), bb(4), MDB_APPENDDUP); + assertThat(c.count()).isEqualTo(2L); + txn.commit(); + } + } + }) + .isInstanceOf(NotReadyException.class); } @Test - public void cursorFirstLastNextPrev() { + void cursorFirstLastNextPrev() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite(); Cursor c = db.openCursor(txn)) { @@ -184,46 +217,46 @@ public void cursorFirstLastNextPrev() { c.put(bb(5), bb(6)); c.put(bb(7), bb(8)); - assertThat(c.first(), is(true)); - assertThat(c.key().getInt(0), is(1)); - assertThat(c.val().getInt(0), is(2)); + assertThat(c.first()).isTrue(); + assertThat(c.key().getInt(0)).isEqualTo(1); + assertThat(c.val().getInt(0)).isEqualTo(2); - assertThat(c.last(), is(true)); - assertThat(c.key().getInt(0), is(7)); - assertThat(c.val().getInt(0), is(8)); + assertThat(c.last()).isTrue(); + assertThat(c.key().getInt(0)).isEqualTo(7); + assertThat(c.val().getInt(0)).isEqualTo(8); - assertThat(c.prev(), is(true)); - assertThat(c.key().getInt(0), is(5)); - assertThat(c.val().getInt(0), is(6)); + assertThat(c.prev()).isTrue(); + assertThat(c.key().getInt(0)).isEqualTo(5); + assertThat(c.val().getInt(0)).isEqualTo(6); - assertThat(c.first(), is(true)); - assertThat(c.next(), is(true)); - assertThat(c.key().getInt(0), is(3)); - assertThat(c.val().getInt(0), is(4)); + assertThat(c.first()).isTrue(); + assertThat(c.next()).isTrue(); + assertThat(c.key().getInt(0)).isEqualTo(3); + assertThat(c.val().getInt(0)).isEqualTo(4); } } @Test - public void delete() { + void delete() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); try (Txn txn = env.txnWrite(); Cursor c = db.openCursor(txn)) { c.put(bb(1), bb(2), MDB_NOOVERWRITE); c.put(bb(3), bb(4)); - assertThat(c.seek(MDB_FIRST), is(true)); - assertThat(c.key().getInt(), is(1)); - assertThat(c.val().getInt(), is(2)); + assertThat(c.seek(MDB_FIRST)).isTrue(); + assertThat(c.key().getInt()).isEqualTo(1); + assertThat(c.val().getInt()).isEqualTo(2); c.delete(); - assertThat(c.seek(MDB_FIRST), is(true)); - assertThat(c.key().getInt(), is(3)); - assertThat(c.val().getInt(), is(4)); + assertThat(c.seek(MDB_FIRST)).isTrue(); + assertThat(c.key().getInt()).isEqualTo(3); + assertThat(c.val().getInt()).isEqualTo(4); c.delete(); - assertThat(c.seek(MDB_FIRST), is(false)); + assertThat(c.seek(MDB_FIRST)).isFalse(); } } @Test - public void getKeyVal() { + void getKeyVal() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); try (Txn txn = env.txnWrite(); Cursor c = db.openCursor(txn)) { @@ -234,17 +267,17 @@ public void getKeyVal() { c.put(bb(2), bb(2), MDB_APPENDDUP); c.put(bb(2), bb(3), MDB_APPENDDUP); c.put(bb(2), bb(4), MDB_APPENDDUP); - assertThat(c.get(bb(1), bb(2), MDB_GET_BOTH), is(true)); - assertThat(c.count(), is(3L)); - assertThat(c.get(bb(1), bb(3), MDB_GET_BOTH), is(false)); - assertThat(c.get(bb(2), bb(1), MDB_GET_BOTH), is(true)); - assertThat(c.count(), is(4L)); - assertThat(c.get(bb(2), bb(0), MDB_GET_BOTH), is(false)); + assertThat(c.get(bb(1), bb(2), MDB_GET_BOTH)).isTrue(); + assertThat(c.count()).isEqualTo(3L); + assertThat(c.get(bb(1), bb(3), MDB_GET_BOTH)).isFalse(); + assertThat(c.get(bb(2), bb(1), MDB_GET_BOTH)).isTrue(); + assertThat(c.count()).isEqualTo(4L); + assertThat(c.get(bb(2), bb(0), MDB_GET_BOTH)).isFalse(); } } @Test - public void putMultiple() { + void putMultiple() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT, MDB_DUPFIXED); final int elemCount = 20; @@ -259,21 +292,25 @@ public void putMultiple() { try (Txn txn = env.txnWrite(); Cursor c = db.openCursor(txn)) { c.putMultiple(k, values, elemCount, MDB_MULTIPLE); - assertThat(c.count(), is((long) elemCount)); + assertThat(c.count()).isEqualTo((long) elemCount); } } - @Test(expected = IllegalArgumentException.class) - public void putMultipleWithoutMdbMultipleFlag() { - final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); - try (Txn txn = env.txnWrite(); - Cursor c = db.openCursor(txn)) { - c.putMultiple(bb(100), bb(1), 1); - } + @Test + void putMultipleWithoutMdbMultipleFlag() { + assertThatThrownBy( + () -> { + final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); + try (Txn txn = env.txnWrite(); + Cursor c = db.openCursor(txn)) { + c.putMultiple(bb(100), bb(1), 1); + } + }) + .isInstanceOf(IllegalArgumentException.class); } @Test - public void renewTxRo() { + void renewTxRo() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); final Cursor c; @@ -290,20 +327,24 @@ public void renewTxRo() { c.close(); } - @Test(expected = ReadOnlyRequiredException.class) - public void renewTxRw() { - final Dbi db = env.openDbi(DB_1, MDB_CREATE); - try (Txn txn = env.txnWrite()) { - assertThat(txn.isReadOnly(), is(false)); - - try (Cursor c = db.openCursor(txn)) { - c.renew(txn); - } - } + @Test + void renewTxRw() { + assertThatThrownBy( + () -> { + final Dbi db = env.openDbi(DB_1, MDB_CREATE); + try (Txn txn = env.txnWrite()) { + assertThat(txn.isReadOnly()).isFalse(); + + try (Cursor c = db.openCursor(txn)) { + c.renew(txn); + } + } + }) + .isInstanceOf(ReadOnlyRequiredException.class); } @Test - public void repeatedCloseCausesNotError() { + void repeatedCloseCausesNotError() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); try (Txn txn = env.txnWrite()) { final Cursor c = db.openCursor(txn); @@ -313,52 +354,52 @@ public void repeatedCloseCausesNotError() { } @Test - public void reserve() { + void reserve() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); final ByteBuffer key = bb(5); try (Txn txn = env.txnWrite()) { - assertNull(db.get(txn, key)); + assertThat(db.get(txn, key)).isNull(); try (Cursor c = db.openCursor(txn)) { final ByteBuffer val = c.reserve(key, BYTES * 2); - assertNotNull(db.get(txn, key)); + assertThat(db.get(txn, key)).isNotNull(); val.putLong(MIN_VALUE).flip(); } txn.commit(); } try (Txn txn = env.txnWrite()) { final ByteBuffer val = db.get(txn, key); - assertThat(val.capacity(), is(BYTES * 2)); - assertThat(val.getLong(), is(MIN_VALUE)); + assertThat(val.capacity()).isEqualTo(BYTES * 2); + assertThat(val.getLong()).isEqualTo(MIN_VALUE); } } @Test - public void returnValueForNoDupData() { + void returnValueForNoDupData() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); try (Txn txn = env.txnWrite(); Cursor c = db.openCursor(txn)) { // ok - assertThat(c.put(bb(5), bb(6), MDB_NODUPDATA), is(true)); - assertThat(c.put(bb(5), bb(7), MDB_NODUPDATA), is(true)); - assertThat(c.put(bb(5), bb(6), MDB_NODUPDATA), is(false)); + assertThat(c.put(bb(5), bb(6), MDB_NODUPDATA)).isTrue(); + assertThat(c.put(bb(5), bb(7), MDB_NODUPDATA)).isTrue(); + assertThat(c.put(bb(5), bb(6), MDB_NODUPDATA)).isFalse(); } } @Test - public void returnValueForNoOverwrite() { + void returnValueForNoOverwrite() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite(); Cursor c = db.openCursor(txn)) { // ok - assertThat(c.put(bb(5), bb(6), MDB_NOOVERWRITE), is(true)); + assertThat(c.put(bb(5), bb(6), MDB_NOOVERWRITE)).isTrue(); // fails, but gets exist val - assertThat(c.put(bb(5), bb(8), MDB_NOOVERWRITE), is(false)); - assertThat(c.val().getInt(0), is(6)); + assertThat(c.put(bb(5), bb(8), MDB_NOOVERWRITE)).isFalse(); + assertThat(c.val().getInt(0)).isEqualTo(6); } } @Test - public void testCursorByteBufferDuplicate() { + void testCursorByteBufferDuplicate() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite()) { try (Cursor c = db.openCursor(txn)) { @@ -377,11 +418,11 @@ public void testCursorByteBufferDuplicate() { final ByteBuffer key2 = c.key().duplicate(); final ByteBuffer val2 = c.val().duplicate(); - assertThat(key1.getInt(0), is(1)); - assertThat(val1.getInt(0), is(2)); + assertThat(key1.getInt(0)).isEqualTo(1); + assertThat(val1.getInt(0)).isEqualTo(2); - assertThat(key2.getInt(0), is(3)); - assertThat(val2.getInt(0), is(4)); + assertThat(key2.getInt(0)).isEqualTo(3); + assertThat(val2.getInt(0)).isEqualTo(4); } } } diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 75c23931..844a819e 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.lmdbjava; import static com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; @@ -24,16 +25,8 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.stream.Collectors.toList; import static java.util.stream.IntStream.range; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.collection.IsEmptyCollection.empty; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; +import static org.assertj.core.api.Assertions.assertThat; +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.MDB_CREATE; @@ -46,11 +39,13 @@ 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.io.File; -import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Comparator; import java.util.Iterator; @@ -61,13 +56,13 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.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 java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.function.ToIntFunction; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.lmdbjava.CursorIterable.KeyVal; import org.lmdbjava.Dbi.DbFullException; import org.lmdbjava.Env.AlreadyClosedException; @@ -77,43 +72,51 @@ /** Test {@link Dbi}. */ public final class DbiTest { - @Rule public final TemporaryFolder tmp = new TemporaryFolder(); + private Path file; private Env env; + private Path fileBa; private Env envBa; - @After - public void after() { - env.close(); - } - - @Before - public void before() throws IOException { - final File path = tmp.newFile(); + @BeforeEach + void beforeEach() { + file = FileUtil.createTempFile(); env = create() .setMapSize(MEBIBYTES.toBytes(64)) .setMaxReaders(2) .setMaxDbs(2) - .open(path, MDB_NOSUBDIR); - final File pathBa = tmp.newFile(); + .open(file.toFile(), MDB_NOSUBDIR); + fileBa = FileUtil.createTempFile(); envBa = create(PROXY_BA) .setMapSize(MEBIBYTES.toBytes(64)) .setMaxReaders(2) .setMaxDbs(2) - .open(pathBa, MDB_NOSUBDIR); + .open(fileBa.toFile(), MDB_NOSUBDIR); } - @Test(expected = ConstantDerivedException.class) - public void close() { - final Dbi db = env.openDbi(DB_1, MDB_CREATE); - db.put(bb(1), bb(42)); - db.close(); - db.put(bb(2), bb(42)); // error + @AfterEach + void afterEach() { + env.close(); + envBa.close(); + FileUtil.delete(file); + FileUtil.delete(fileBa); } @Test - public void customComparator() { + void close() { + assertThatThrownBy( + () -> { + final Dbi db = env.openDbi(DB_1, MDB_CREATE); + db.put(bb(1), bb(42)); + db.close(); + db.put(bb(2), bb(42)); // error + }) + .isInstanceOf(ConstantDerivedException.class); + } + + @Test + void customComparator() { final Comparator reverseOrder = (o1, o2) -> { final int lexical = PROXY_OPTIMAL.getComparator().compare(o1, o2); @@ -126,7 +129,7 @@ public void customComparator() { } @Test - public void customComparatorByteArray() { + void customComparatorByteArray() { final Comparator reverseOrder = (o1, o2) -> { final int lexical = PROXY_BA.getComparator().compare(o1, o2); @@ -145,41 +148,45 @@ private void doCustomComparator( ToIntFunction deserializer) { final Dbi db = env.openDbi(DB_1, comparator, true, MDB_CREATE); try (Txn txn = env.txnWrite()) { - assertThat(db.put(txn, serializer.apply(2), serializer.apply(3)), is(true)); - assertThat(db.put(txn, serializer.apply(4), serializer.apply(6)), is(true)); - assertThat(db.put(txn, serializer.apply(6), serializer.apply(7)), is(true)); - assertThat(db.put(txn, serializer.apply(8), serializer.apply(7)), is(true)); + assertThat(db.put(txn, serializer.apply(2), serializer.apply(3))).isTrue(); + assertThat(db.put(txn, serializer.apply(4), serializer.apply(6))).isTrue(); + assertThat(db.put(txn, serializer.apply(6), serializer.apply(7))).isTrue(); + assertThat(db.put(txn, serializer.apply(8), serializer.apply(7))).isTrue(); txn.commit(); } try (Txn txn = env.txnRead(); 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)); - assertThat(deserializer.applyAsInt(iter.next().key()), is(4)); + assertThat(deserializer.applyAsInt(iter.next().key())).isEqualTo(8); + assertThat(deserializer.applyAsInt(iter.next().key())).isEqualTo(6); + assertThat(deserializer.applyAsInt(iter.next().key())).isEqualTo(4); } } - @Test(expected = DbFullException.class) - public void dbOpenMaxDatabases() { - env.openDbi("db1 is OK", MDB_CREATE); - env.openDbi("db2 is OK", MDB_CREATE); - env.openDbi("db3 fails", MDB_CREATE); + @Test + void dbOpenMaxDatabases() { + assertThatThrownBy( + () -> { + env.openDbi("db1 is OK", MDB_CREATE); + env.openDbi("db2 is OK", MDB_CREATE); + env.openDbi("db3 fails", MDB_CREATE); + }) + .isInstanceOf(DbFullException.class); } @Test - public void dbiWithComparatorThreadSafety() { + void dbiWithComparatorThreadSafety() { doDbiWithComparatorThreadSafety( env, PROXY_OPTIMAL::getComparator, TestUtils::bb, ByteBuffer::getInt); } @Test - public void dbiWithComparatorThreadSafetyByteArray() { + void dbiWithComparatorThreadSafetyByteArray() { doDbiWithComparatorThreadSafety( envBa, PROXY_BA::getComparator, TestUtils::ba, TestUtils::fromBa); } - public void doDbiWithComparatorThreadSafety( + private void doDbiWithComparatorThreadSafety( Env env, Function> comparator, IntFunction serializer, @@ -190,66 +197,67 @@ public void doDbiWithComparatorThreadSafety( 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)); + try (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)); + } } - } - }); + }); - 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, Matchers.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); + } } } @Test - public void drop() { + void drop() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite()) { db.put(txn, bb(1), bb(42)); db.put(txn, bb(2), bb(42)); - assertThat(db.get(txn, bb(1)), not(nullValue())); - assertThat(db.get(txn, bb(2)), not(nullValue())); + assertThat(db.get(txn, bb(1))).isNotNull(); + assertThat(db.get(txn, bb(2))).isNotNull(); db.drop(txn); - assertThat(db.get(txn, bb(1)), is(nullValue())); // data gone - assertThat(db.get(txn, bb(2)), is(nullValue())); + assertThat(db.get(txn, bb(1))).isNull(); // data gone + assertThat(db.get(txn, bb(2))).isNull(); db.put(txn, bb(1), bb(42)); // ensure DB still works db.put(txn, bb(2), bb(42)); - assertThat(db.get(txn, bb(1)), not(nullValue())); - assertThat(db.get(txn, bb(2)), not(nullValue())); + assertThat(db.get(txn, bb(1))).isNotNull(); + assertThat(db.get(txn, bb(2))).isNotNull(); } } @Test - public void dropAndDelete() { + void dropAndDelete() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); final Dbi nameDb = env.openDbi((byte[]) null); final byte[] dbNameBytes = DB_1.getBytes(UTF_8); @@ -257,15 +265,15 @@ public void dropAndDelete() { dbNameBuffer.put(dbNameBytes).flip(); try (Txn txn = env.txnWrite()) { - assertThat(nameDb.get(txn, dbNameBuffer), not(nullValue())); + assertThat(nameDb.get(txn, dbNameBuffer)).isNotNull(); db.drop(txn, true); - assertThat(nameDb.get(txn, dbNameBuffer), is(nullValue())); + assertThat(nameDb.get(txn, dbNameBuffer)).isNull(); txn.commit(); } } @Test - public void dropAndDeleteAnonymousDb() { + void dropAndDeleteAnonymousDb() { env.openDbi(DB_1, MDB_CREATE); final Dbi nameDb = env.openDbi((byte[]) null); final byte[] dbNameBytes = DB_1.getBytes(UTF_8); @@ -273,9 +281,9 @@ public void dropAndDeleteAnonymousDb() { dbNameBuffer.put(dbNameBytes).flip(); try (Txn txn = env.txnWrite()) { - assertThat(nameDb.get(txn, dbNameBuffer), not(nullValue())); + assertThat(nameDb.get(txn, dbNameBuffer)).isNotNull(); nameDb.drop(txn, true); - assertThat(nameDb.get(txn, dbNameBuffer), is(nullValue())); + assertThat(nameDb.get(txn, dbNameBuffer)).isNull(); txn.commit(); } @@ -283,41 +291,41 @@ public void dropAndDeleteAnonymousDb() { } @Test - public void getName() { + void getName() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); - assertThat(db.getName(), is(DB_1.getBytes(UTF_8))); + assertThat(db.getName()).isEqualTo(DB_1.getBytes(UTF_8)); } @Test - public void getNamesWhenDbisPresent() { + void getNamesWhenDbisPresent() { 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(); - assertThat(dbiNames, hasSize(2)); - assertThat(dbiNames.get(0), is(dbHello)); - assertThat(dbiNames.get(1), is(dbWorld)); + assertThat(dbiNames).hasSize(2); + assertThat(dbiNames.get(0)).isEqualTo(dbHello); + assertThat(dbiNames.get(1)).isEqualTo(dbWorld); } @Test - public void getNamesWhenEmpty() { + void getNamesWhenEmpty() { final List dbiNames = env.getDbiNames(); - assertThat(dbiNames, empty()); + assertThat(dbiNames).isEmpty(); } @Test - public void listsFlags() { + void listsFlags() { final Dbi dbi = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT, MDB_REVERSEKEY); try (Txn txn = env.txnRead()) { final List flags = dbi.listFlags(txn); - assertThat(flags, containsInAnyOrder(MDB_DUPSORT, MDB_REVERSEKEY)); + assertThat(flags).containsExactlyInAnyOrder(MDB_DUPSORT, MDB_REVERSEKEY); } } @Test - public void putAbortGet() { + void putAbortGet() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite()) { @@ -326,30 +334,30 @@ public void putAbortGet() { } try (Txn txn = env.txnWrite()) { - assertNull(db.get(txn, bb(5))); + assertThat(db.get(txn, bb(5))).isNull(); } } @Test - public void putAndGetAndDeleteWithInternalTx() { + void putAndGetAndDeleteWithInternalTx() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); db.put(bb(5), bb(5)); try (Txn txn = env.txnRead()) { final ByteBuffer found = db.get(txn, bb(5)); - assertNotNull(found); - assertThat(txn.val().getInt(), is(5)); + assertThat(found).isNotNull(); + assertThat(txn.val().getInt()).isEqualTo(5); } - assertThat(db.delete(bb(5)), is(true)); - assertThat(db.delete(bb(5)), is(false)); + assertThat(db.delete(bb(5))).isTrue(); + assertThat(db.delete(bb(5))).isFalse(); try (Txn txn = env.txnRead()) { - assertNull(db.get(txn, bb(5))); + assertThat(db.get(txn, bb(5))).isNull(); } } @Test - public void putCommitGet() { + void putCommitGet() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite()) { db.put(txn, bb(5), bb(5)); @@ -358,90 +366,93 @@ public void putCommitGet() { try (Txn txn = env.txnWrite()) { final ByteBuffer found = db.get(txn, bb(5)); - assertNotNull(found); - assertThat(txn.val().getInt(), is(5)); + assertThat(found).isNotNull(); + assertThat(txn.val().getInt()).isEqualTo(5); } } @Test - 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)) { - 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)); - assertNotNull(found); - assertThat(fromBa(txn.val()), is(5)); - } - } + 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); + } + } + }); } @Test - public void putDelete() { + void putDelete() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite()) { db.put(txn, bb(5), bb(5)); - assertThat(db.delete(txn, bb(5)), is(true)); + assertThat(db.delete(txn, bb(5))).isTrue(); - assertNull(db.get(txn, bb(5))); + assertThat(db.get(txn, bb(5))).isNull(); txn.abort(); } } @Test - public void putDuplicateDelete() { + void putDuplicateDelete() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); try (Txn txn = env.txnWrite()) { db.put(txn, bb(5), bb(5)); db.put(txn, bb(5), bb(6)); db.put(txn, bb(5), bb(7)); - assertThat(db.delete(txn, bb(5), bb(6)), is(true)); - assertThat(db.delete(txn, bb(5), bb(6)), is(false)); - assertThat(db.delete(txn, bb(5), bb(5)), is(true)); - assertThat(db.delete(txn, bb(5), bb(5)), is(false)); + assertThat(db.delete(txn, bb(5), bb(6))).isTrue(); + assertThat(db.delete(txn, bb(5), bb(6))).isFalse(); + assertThat(db.delete(txn, bb(5), bb(5))).isTrue(); + assertThat(db.delete(txn, bb(5), bb(5))).isFalse(); try (Cursor cursor = db.openCursor(txn)) { final ByteBuffer key = bb(5); cursor.get(key, MDB_SET_KEY); - assertThat(cursor.count(), is(1L)); + assertThat(cursor.count()).isEqualTo(1L); } txn.abort(); } } @Test - public void putReserve() { + void putReserve() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); final ByteBuffer key = bb(5); try (Txn txn = env.txnWrite()) { - assertNull(db.get(txn, key)); + assertThat(db.get(txn, key)).isNull(); final ByteBuffer val = db.reserve(txn, key, 32, MDB_NOOVERWRITE); val.putLong(MAX_VALUE); - assertNotNull(db.get(txn, key)); + assertThat(db.get(txn, key)).isNotNull(); txn.commit(); } try (Txn txn = env.txnWrite()) { final ByteBuffer val = db.get(txn, key); - assertThat(val.capacity(), is(32)); - assertThat(val.getLong(), is(MAX_VALUE)); - assertThat(val.getLong(8), is(0L)); + assertThat(val).isNotNull(); + assertThat(val.capacity()).isEqualTo(32); + assertThat(val.getLong()).isEqualTo(MAX_VALUE); + assertThat(val.getLong(8)).isEqualTo(0L); } } @Test - public void putZeroByteValueForNonMdbDupSortDatabase() { + void putZeroByteValueForNonMdbDupSortDatabase() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite()) { final ByteBuffer val = allocateDirect(0); @@ -451,36 +462,36 @@ public void putZeroByteValueForNonMdbDupSortDatabase() { try (Txn txn = env.txnRead()) { final ByteBuffer found = db.get(txn, bb(5)); - assertNotNull(found); - assertThat(txn.val().capacity(), is(0)); + assertThat(found).isNotNull(); + assertThat(txn.val().capacity()).isEqualTo(0); } } @Test - public void returnValueForNoDupData() { + void returnValueForNoDupData() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); try (Txn txn = env.txnWrite()) { // ok - assertThat(db.put(txn, bb(5), bb(6), MDB_NODUPDATA), is(true)); - assertThat(db.put(txn, bb(5), bb(7), MDB_NODUPDATA), is(true)); - assertThat(db.put(txn, bb(5), bb(6), MDB_NODUPDATA), is(false)); + assertThat(db.put(txn, bb(5), bb(6), MDB_NODUPDATA)).isTrue(); + assertThat(db.put(txn, bb(5), bb(7), MDB_NODUPDATA)).isTrue(); + assertThat(db.put(txn, bb(5), bb(6), MDB_NODUPDATA)).isFalse(); } } @Test - public void returnValueForNoOverwrite() { + void returnValueForNoOverwrite() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite()) { // ok - assertThat(db.put(txn, bb(5), bb(6), MDB_NOOVERWRITE), is(true)); + assertThat(db.put(txn, bb(5), bb(6), MDB_NOOVERWRITE)).isTrue(); // fails, but gets exist val - assertThat(db.put(txn, bb(5), bb(8), MDB_NOOVERWRITE), is(false)); - assertThat(txn.val().getInt(0), is(6)); + assertThat(db.put(txn, bb(5), bb(8), MDB_NOOVERWRITE)).isFalse(); + assertThat(txn.val().getInt(0)).isEqualTo(6); } } @Test - public void stats() { + void stats() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); db.put(bb(1), bb(42)); db.put(bb(2), bb(42)); @@ -489,32 +500,36 @@ public void stats() { try (Txn txn = env.txnRead()) { stat = db.stat(txn); } - assertThat(stat, is(notNullValue())); - assertThat(stat.branchPages, is(0L)); - assertThat(stat.depth, is(1)); - assertThat(stat.entries, is(3L)); - assertThat(stat.leafPages, is(1L)); - assertThat(stat.overflowPages, is(0L)); - assertThat(stat.pageSize % 4_096, is(0)); + assertThat(stat).isNotNull(); + assertThat(stat.branchPages).isEqualTo(0L); + assertThat(stat.depth).isEqualTo(1); + assertThat(stat.entries).isEqualTo(3L); + assertThat(stat.leafPages).isEqualTo(1L); + assertThat(stat.overflowPages).isEqualTo(0L); + assertThat(stat.pageSize % 4_096).isEqualTo(0); } - @Test(expected = MapFullException.class) - public void testMapFullException() { - 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); - } + @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); + } + }) + .isInstanceOf(MapFullException.class); } @Test - public void testParallelWritesStress() { + void testParallelWritesStress() { if (getProperty("os.name").startsWith("Windows")) { return; // Windows VMs run this test too slowly } @@ -531,69 +546,113 @@ public void testParallelWritesStress() { }); } - @Test(expected = AlreadyClosedException.class) - public void closedEnvRejectsOpenCall() { - env.close(); - env.openDbi(DB_1, MDB_CREATE); + @Test + void closedEnvRejectsOpenCall() { + assertThatThrownBy( + () -> { + env.close(); + env.openDbi(DB_1, MDB_CREATE); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void closedEnvRejectsCloseCall() { - doEnvClosedTest(null, (db, txn) -> db.close()); + @Test + void closedEnvRejectsCloseCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest(null, (db, txn) -> db.close()); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void closedEnvRejectsGetCall() { - doEnvClosedTest( - (db, txn) -> { - final ByteBuffer valBuf = db.get(txn, bb(1)); - assertThat(valBuf.getInt(), is(10)); - }, - (db, txn) -> db.get(txn, bb(2))); + @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))); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void closedEnvRejectsPutCall() { - doEnvClosedTest(null, (db, txn) -> db.put(bb(5), bb(50))); + @Test + void closedEnvRejectsPutCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest(null, (db, txn) -> db.put(bb(5), bb(50))); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void closedEnvRejectsPutWithTxnCall() { - doEnvClosedTest( - null, - (db, txn) -> { - db.put(txn, bb(5), bb(50)); - }); + @Test + void closedEnvRejectsPutWithTxnCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest( + null, + (db, txn) -> { + db.put(txn, bb(5), bb(50)); + }); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void closedEnvRejectsIterateCall() { - doEnvClosedTest(null, Dbi::iterate); + @Test + void closedEnvRejectsIterateCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest(null, Dbi::iterate); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void closedEnvRejectsDropCall() { - doEnvClosedTest(null, Dbi::drop); + @Test + void closedEnvRejectsDropCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest(null, Dbi::drop); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void closedEnvRejectsDropAndDeleteCall() { - doEnvClosedTest(null, (db, txn) -> db.drop(txn, true)); + @Test + void closedEnvRejectsDropAndDeleteCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest(null, (db, txn) -> db.drop(txn, true)); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void closedEnvRejectsOpenCursorCall() { - doEnvClosedTest(null, Dbi::openCursor); + @Test + void closedEnvRejectsOpenCursorCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest(null, Dbi::openCursor); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void closedEnvRejectsReserveCall() { - doEnvClosedTest(null, (db, txn) -> db.reserve(txn, bb(1), 32, MDB_NOOVERWRITE)); + @Test + void closedEnvRejectsReserveCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest(null, (db, txn) -> db.reserve(txn, bb(1), 32, MDB_NOOVERWRITE)); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void closedEnvRejectsStatCall() { - doEnvClosedTest(null, Dbi::stat); + @Test + void closedEnvRejectsStatCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest(null, Dbi::stat); + }) + .isInstanceOf(AlreadyClosedException.class); } private void doEnvClosedTest( diff --git a/src/test/java/org/lmdbjava/EnvTest.java b/src/test/java/org/lmdbjava/EnvTest.java index e71c6ae9..87edd049 100644 --- a/src/test/java/org/lmdbjava/EnvTest.java +++ b/src/test/java/org/lmdbjava/EnvTest.java @@ -13,16 +13,14 @@ * 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 com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; import static java.nio.ByteBuffer.allocateDirect; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.greaterThan; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.lmdbjava.CopyFlags.MDB_CP_COMPACT; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.Env.Builder.MAX_READERS_DEFAULT; @@ -33,13 +31,13 @@ import static org.lmdbjava.TestUtils.DB_1; import static org.lmdbjava.TestUtils.bb; -import java.io.File; import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Random; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.Test; import org.lmdbjava.Env.AlreadyClosedException; import org.lmdbjava.Env.AlreadyOpenException; import org.lmdbjava.Env.Builder; @@ -50,306 +48,453 @@ /** Test {@link Env}. */ public final class EnvTest { - @Rule public final TemporaryFolder tmp = new TemporaryFolder(); - @Test - public void byteUnit() throws IOException { - final File path = tmp.newFile(); - try (Env env = - create().setMaxReaders(1).setMapSize(MEBIBYTES.toBytes(1)).open(path, MDB_NOSUBDIR)) { - final EnvInfo info = env.info(); - assertThat(info.mapSize, is(MEBIBYTES.toBytes(1))); - } + 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)); + } + }); } - @Test(expected = AlreadyOpenException.class) - public void cannotChangeMapSizeAfterOpen() throws IOException { - final File path = tmp.newFile(); - final Builder builder = create().setMaxReaders(1); - try (Env env = builder.open(path, MDB_NOSUBDIR)) { - builder.setMapSize(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); + } + }); + }) + .isInstanceOf(AlreadyOpenException.class); } - @Test(expected = AlreadyOpenException.class) - public void cannotChangeMaxDbsAfterOpen() throws IOException { - final File path = tmp.newFile(); - final Builder builder = create().setMaxReaders(1); - try (Env env = builder.open(path, MDB_NOSUBDIR)) { - builder.setMaxDbs(1); - } + @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); + } + }); + }) + .isInstanceOf(AlreadyOpenException.class); } - @Test(expected = AlreadyOpenException.class) - public void cannotChangeMaxReadersAfterOpen() throws IOException { - final File path = tmp.newFile(); - final Builder builder = create().setMaxReaders(1); - try (Env env = builder.open(path, MDB_NOSUBDIR)) { - builder.setMaxReaders(1); - } + @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); + } + }); + }) + .isInstanceOf(AlreadyOpenException.class); } - @Test(expected = AlreadyClosedException.class) - public void cannotInfoOnceClosed() throws IOException { - final File path = tmp.newFile(); - final Env env = create().setMaxReaders(1).open(path, MDB_NOSUBDIR); - env.close(); - env.info(); + @Test + void cannotInfoOnceClosed() { + assertThatThrownBy( + () -> { + FileUtil.useTempFile( + file -> { + final Env env = + create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR); + env.close(); + env.info(); + }); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyOpenException.class) - public void cannotOpenTwice() throws IOException { - final File path = tmp.newFile(); - final Builder builder = create().setMaxReaders(1); - - builder.open(path, MDB_NOSUBDIR).close(); - builder.open(path, MDB_NOSUBDIR); + @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); + }); + }) + .isInstanceOf(AlreadyOpenException.class); } - @Test(expected = IllegalArgumentException.class) - public void cannotOverflowMapSize() { - 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); + @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); + }) + .isInstanceOf(IllegalArgumentException.class); } - @Test(expected = AlreadyClosedException.class) - public void cannotStatOnceClosed() throws IOException { - final File path = tmp.newFile(); - final Env env = create().setMaxReaders(1).open(path, MDB_NOSUBDIR); - env.close(); - env.stat(); + @Test + void cannotStatOnceClosed() { + assertThatThrownBy( + () -> { + FileUtil.useTempFile( + file -> { + final Env env = + create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR); + env.close(); + env.stat(); + }); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void cannotSyncOnceClosed() throws IOException { - final File path = tmp.newFile(); - final Env env = create().setMaxReaders(1).open(path, MDB_NOSUBDIR); - env.close(); - env.sync(false); + @Test + void cannotSyncOnceClosed() { + assertThatThrownBy( + () -> { + FileUtil.useTempFile( + file -> { + final Env env = + create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR); + env.close(); + env.sync(false); + }); + }) + .isInstanceOf(AlreadyClosedException.class); } @Test - public void copyDirectoryBased() throws IOException { - final File dest = tmp.newFolder(); - assertThat(dest.exists(), is(true)); - assertThat(dest.isDirectory(), is(true)); - assertThat(dest.list().length, is(0)); - final File src = tmp.newFolder(); - try (Env env = create().setMaxReaders(1).open(src)) { - env.copy(dest, MDB_CP_COMPACT); - assertThat(dest.list().length, is(1)); - } + 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); + } + }); + }); } - @Test(expected = InvalidCopyDestination.class) - public void copyDirectoryRejectsFileDestination() throws IOException { - final File dest = tmp.newFile(); - final File src = tmp.newFolder(); - try (Env env = create().setMaxReaders(1).open(src)) { - env.copy(dest, MDB_CP_COMPACT); - } + @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); + } + }); + }); + }) + .isInstanceOf(InvalidCopyDestination.class); } - @Test(expected = InvalidCopyDestination.class) - public void copyDirectoryRejectsMissingDestination() throws IOException { - final File dest = tmp.newFolder(); - assertThat(dest.delete(), is(true)); - final File src = tmp.newFolder(); - try (Env env = create().setMaxReaders(1).open(src)) { - env.copy(dest, MDB_CP_COMPACT); - } + @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); + } + }); + }) + .isInstanceOf(InvalidCopyDestination.class); } - @Test(expected = InvalidCopyDestination.class) - public void copyDirectoryRejectsNonEmptyDestination() throws IOException { - final File dest = tmp.newFolder(); - final File subDir = new File(dest, "hello"); - assertThat(subDir.mkdir(), is(true)); - final File src = tmp.newFolder(); - try (Env env = create().setMaxReaders(1).open(src)) { - env.copy(dest, MDB_CP_COMPACT); - } + @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); + } + }); + }) + .isInstanceOf(InvalidCopyDestination.class); } @Test - public void copyFileBased() throws IOException { - final File dest = tmp.newFile(); - assertThat(dest.delete(), is(true)); - assertThat(dest.exists(), is(false)); - final File src = tmp.newFile(); - try (Env env = create().setMaxReaders(1).open(src, MDB_NOSUBDIR)) { - env.copy(dest, MDB_CP_COMPACT); - } - assertThat(dest.length(), greaterThan(0L)); + void copyFileBased() { + FileUtil.useTempFile( + dest -> { + FileUtil.delete(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); + }); + }); } - @Test(expected = InvalidCopyDestination.class) - public void copyFileRejectsExistingDestination() throws IOException { - final File dest = tmp.newFile(); - assertThat(dest.exists(), is(true)); - final File src = tmp.newFile(); - try (Env env = create().setMaxReaders(1).open(src, MDB_NOSUBDIR)) { - env.copy(dest, MDB_CP_COMPACT); - } + @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); + } + }); + }); + }) + .isInstanceOf(InvalidCopyDestination.class); } @Test - public void createAsDirectory() throws IOException { - final File path = tmp.newFolder(); - final Env env = create().setMaxReaders(1).open(path); - assertThat(path.isDirectory(), is(true)); - env.sync(false); - env.close(); - assertThat(env.isClosed(), is(true)); - env.close(); // safe to repeat + 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 + }); } @Test - public void createAsFile() throws IOException { - final File path = tmp.newFile(); - try (Env env = - create().setMapSize(1_024 * 1_024).setMaxDbs(1).setMaxReaders(1).open(path, MDB_NOSUBDIR)) { - env.sync(true); - assertThat(path.isFile(), is(true)); - } + 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(); + } + }); } - @Test(expected = BadReaderLockException.class) - public void detectTransactionThreadViolation() throws IOException { - final File path = tmp.newFile(); - try (Env env = create().setMaxReaders(1).open(path, MDB_NOSUBDIR)) { - env.txnRead(); - env.txnRead(); - } + @Test + void detectTransactionThreadViolation() { + assertThatThrownBy( + () -> { + FileUtil.useTempFile( + file -> { + try (Env env = + create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR)) { + env.txnRead(); + env.txnRead(); + } + }); + }) + .isInstanceOf(BadReaderLockException.class); } @Test - public void info() throws IOException { - final File path = tmp.newFile(); - try (Env env = - create().setMaxReaders(4).setMapSize(123_456).open(path, MDB_NOSUBDIR)) { - final EnvInfo info = env.info(); - assertThat(info, is(notNullValue())); - assertThat(info.lastPageNumber, is(1L)); - assertThat(info.lastTransactionId, is(0L)); - assertThat(info.mapAddress, is(0L)); - assertThat(info.mapSize, is(123_456L)); - assertThat(info.maxReaders, is(4)); - assertThat(info.numReaders, is(0)); - assertThat(info.toString(), containsString("maxReaders=")); - assertThat(env.getMaxKeySize(), is(511)); - } + 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); + } + }); } - @Test(expected = MapFullException.class) - public void mapFull() throws IOException { - final File path = tmp.newFolder(); - 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(path)) { - 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); - } - } + @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); + } + } + }); + }) + .isInstanceOf(MapFullException.class); } @Test - public void readOnlySupported() throws IOException { - final File path = tmp.newFolder(); - try (Env rwEnv = create().setMaxReaders(1).open(path)) { - final Dbi rwDb = rwEnv.openDbi(DB_1, MDB_CREATE); - rwDb.put(bb(1), bb(42)); - } - try (Env roEnv = create().setMaxReaders(1).open(path, MDB_RDONLY_ENV)) { - final Dbi roDb = roEnv.openDbi(DB_1); - try (Txn roTxn = roEnv.txnRead()) { - assertThat(roDb.get(roTxn, bb(1)), notNullValue()); - } - } + 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(); + } + } + }); } @Test - public void setMapSize() throws IOException { - final File path = tmp.newFolder(); - 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(path)) { - 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, is(true)); - - env.setMapSize(KIBIBYTES.toBytes(1024)); - - try (Txn roTxn = env.txnRead()) { - assertThat(db.get(roTxn, bb(1)).getInt(), is(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, is(false)); - } + 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(); + } + }); } @Test - public void stats() throws IOException { - final File path = tmp.newFile(); - try (Env env = create().setMaxReaders(1).open(path, MDB_NOSUBDIR)) { - final Stat stat = env.stat(); - assertThat(stat, is(notNullValue())); - assertThat(stat.branchPages, is(0L)); - assertThat(stat.depth, is(0)); - assertThat(stat.entries, is(0L)); - assertThat(stat.leafPages, is(0L)); - assertThat(stat.overflowPages, is(0L)); - assertThat(stat.pageSize % 4_096, is(0)); - assertThat(stat.toString(), containsString("pageSize=")); - } + 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="); + } + }); } @Test - public void testDefaultOpen() throws IOException { - final File path = tmp.newFolder(); - try (Env env = open(path, 10)) { - final EnvInfo info = env.info(); - assertThat(info.maxReaders, is(MAX_READERS_DEFAULT)); - final Dbi db = env.openDbi("test", MDB_CREATE); - db.put(allocateDirect(1), allocateDirect(1)); - } + 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)); + } + }); } } diff --git a/src/test/java/org/lmdbjava/FileUtil.java b/src/test/java/org/lmdbjava/FileUtil.java new file mode 100644 index 00000000..3e459f14 --- /dev/null +++ b/src/test/java/org/lmdbjava/FileUtil.java @@ -0,0 +1,153 @@ +/* + * 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.FileVisitOption; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; +import java.nio.file.Files; +import java.nio.file.NotDirectoryException; +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); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } + + public static void delete(final Path path) { + try { + Files.delete(path); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } + + public static void deleteDir(final Path path) { + if (path != null && Files.isDirectory(path)) { + recursiveDelete(path); + deleteIfExists(path); + } + } + + private static void recursiveDelete(final Path path) { + try { + Files.walkFileTree( + path, + EnumSet.of(FileVisitOption.FOLLOW_LINKS), + Integer.MAX_VALUE, + new FileVisitor() { + @Override + public FileVisitResult preVisitDirectory( + final Path dir, final BasicFileAttributes attrs) throws IOException { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(final Path file, final IOException exc) + throws IOException { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) { + deleteIfExists(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) { + if (!dir.equals(path)) { + deleteIfExists(dir); + } + return FileVisitResult.CONTINUE; + } + }); + } catch (final NotDirectoryException e) { + // Ignore. + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } + + private static void deleteIfExists(final Path path) { + try { + Files.deleteIfExists(path); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } + + public static long count(final Path path) { + try (final Stream stream = Files.list(path)) { + return stream.count(); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/src/test/java/org/lmdbjava/GarbageCollectionTest.java b/src/test/java/org/lmdbjava/GarbageCollectionTest.java index 7615f7f0..f0aa64e4 100644 --- a/src/test/java/org/lmdbjava/GarbageCollectionTest.java +++ b/src/test/java/org/lmdbjava/GarbageCollectionTest.java @@ -13,20 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.lmdbjava; import static java.nio.ByteBuffer.allocateDirect; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.junit.Assert.fail; +import static org.assertj.core.api.Assertions.fail; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.Env.create; -import java.io.File; -import java.io.IOException; import java.nio.ByteBuffer; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; @@ -36,62 +33,63 @@ public class GarbageCollectionTest { private static final String KEY_PREFIX = "Uncorruptedkey"; private static final String VAL_PREFIX = "Uncorruptedval"; - @Rule public final TemporaryFolder tmp = new TemporaryFolder(); - @Test - public void buffersNotGarbageCollectedTest() throws IOException { - final File path = tmp.newFolder(); - try (Env env = create().setMapSize(2_085_760_999).setMaxDbs(1).open(path)) { - 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(); - } + 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); - // 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(); - } - } + try (Txn txn = env.txnWrite()) { + for (int i = 0; i < 5_000; 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); + // 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(); } - if (!sval.startsWith("Uncorruptedval")) { - fail("Found corrupt val " + sval); + } + + // 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()); + } } - } while (c.next()); + } } - } - } - } + }); } private void putBuffer(final Dbi db, final Txn txn, final int i) { diff --git a/src/test/java/org/lmdbjava/KeyRangeTest.java b/src/test/java/org/lmdbjava/KeyRangeTest.java index 6e104bbf..0f77b92f 100644 --- a/src/test/java/org/lmdbjava/KeyRangeTest.java +++ b/src/test/java/org/lmdbjava/KeyRangeTest.java @@ -13,11 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.lmdbjava; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import static org.lmdbjava.KeyRange.all; import static org.lmdbjava.KeyRange.allBackward; import static org.lmdbjava.KeyRange.atLeast; @@ -40,8 +39,8 @@ import java.util.ArrayList; import java.util.List; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.lmdbjava.KeyRangeType.CursorOp; import org.lmdbjava.KeyRangeType.IteratorOp; @@ -55,135 +54,135 @@ public final class KeyRangeTest { private final FakeCursor cursor = new FakeCursor(); + @BeforeEach + void beforeEach() { + cursor.reset(); + } + @Test - public void allBackwardTest() { + void allBackwardTest() { verify(allBackward(), 8, 6, 4, 2); } @Test - public void allTest() { + void allTest() { verify(all(), 2, 4, 6, 8); } @Test - public void atLeastBackwardTest() { + void atLeastBackwardTest() { verify(atLeastBackward(5), 4, 2); verify(atLeastBackward(6), 6, 4, 2); verify(atLeastBackward(9), 8, 6, 4, 2); } @Test - public void atLeastTest() { + void atLeastTest() { verify(atLeast(5), 6, 8); verify(atLeast(6), 6, 8); } @Test - public void atMostBackwardTest() { + void atMostBackwardTest() { verify(atMostBackward(5), 8, 6); verify(atMostBackward(6), 8, 6); } @Test - public void atMostTest() { + void atMostTest() { verify(atMost(5), 2, 4); verify(atMost(6), 2, 4, 6); } - @Before - public void before() { - cursor.reset(); - } - @Test - public void closedBackwardTest() { + void closedBackwardTest() { verify(closedBackward(7, 3), 6, 4); verify(closedBackward(6, 2), 6, 4, 2); verify(closedBackward(9, 3), 8, 6, 4); } @Test - public void closedOpenBackwardTest() { + void closedOpenBackwardTest() { verify(closedOpenBackward(8, 3), 8, 6, 4); verify(closedOpenBackward(7, 2), 6, 4); verify(closedOpenBackward(9, 3), 8, 6, 4); } @Test - public void closedOpenTest() { + void closedOpenTest() { verify(closedOpen(3, 8), 4, 6); verify(closedOpen(2, 6), 2, 4); } @Test - public void closedTest() { + void closedTest() { verify(closed(3, 7), 4, 6); verify(closed(2, 6), 2, 4, 6); } @Test - public void fakeCursor() { - assertThat(cursor.first(), is(2)); - assertThat(cursor.next(), is(4)); - assertThat(cursor.next(), is(6)); - assertThat(cursor.next(), is(8)); - assertThat(cursor.next(), nullValue()); - assertThat(cursor.first(), is(2)); - assertThat(cursor.prev(), nullValue()); - assertThat(cursor.getWithSetRange(3), is(4)); - assertThat(cursor.next(), is(6)); - assertThat(cursor.getWithSetRange(1), is(2)); - assertThat(cursor.last(), is(8)); - assertThat(cursor.getWithSetRange(100), nullValue()); + void fakeCursor() { + assertThat(cursor.first()).isEqualTo(2); + assertThat(cursor.next()).isEqualTo(4); + assertThat(cursor.next()).isEqualTo(6); + assertThat(cursor.next()).isEqualTo(8); + assertThat(cursor.next()).isNull(); + assertThat(cursor.first()).isEqualTo(2); + assertThat(cursor.prev()).isNull(); + assertThat(cursor.getWithSetRange(3)).isEqualTo(4); + assertThat(cursor.next()).isEqualTo(6); + assertThat(cursor.getWithSetRange(1)).isEqualTo(2); + assertThat(cursor.last()).isEqualTo(8); + assertThat(cursor.getWithSetRange(100)).isNull(); } @Test - public void greaterThanBackwardTest() { + void greaterThanBackwardTest() { verify(greaterThanBackward(6), 4, 2); verify(greaterThanBackward(7), 6, 4, 2); verify(greaterThanBackward(9), 8, 6, 4, 2); } @Test - public void greaterThanTest() { + void greaterThanTest() { verify(greaterThan(4), 6, 8); verify(greaterThan(3), 4, 6, 8); } @Test - public void lessThanBackwardTest() { + void lessThanBackwardTest() { verify(lessThanBackward(5), 8, 6); verify(lessThanBackward(2), 8, 6, 4); } @Test - public void lessThanTest() { + void lessThanTest() { verify(lessThan(5), 2, 4); verify(lessThan(8), 2, 4, 6); } @Test - public void openBackwardTest() { + void openBackwardTest() { verify(openBackward(7, 2), 6, 4); verify(openBackward(8, 1), 6, 4, 2); verify(openBackward(9, 4), 8, 6); } @Test - public void openClosedBackwardTest() { + void openClosedBackwardTest() { verify(openClosedBackward(7, 2), 6, 4, 2); verify(openClosedBackward(8, 4), 6, 4); verify(openClosedBackward(9, 4), 8, 6, 4); } @Test - public void openClosedTest() { + void openClosedTest() { verify(openClosed(3, 8), 4, 6, 8); verify(openClosed(2, 6), 4, 6); } @Test - public void openTest() { + void openTest() { verify(open(3, 7), 4, 6); verify(open(2, 8), 4, 6); } @@ -212,9 +211,9 @@ private void verify(final KeyRange range, final int... expected) { } while (op != TERMINATE); for (int idx = 0; idx < results.size(); idx++) { - assertThat("idx " + idx, results.get(idx), is(expected[idx])); + assertThat(results.get(idx)).withFailMessage("idx " + idx).isEqualTo(expected[idx]); } - assertThat(results.size(), is(expected.length)); + assertThat(results.size()).isEqualTo(expected.length); } /** diff --git a/src/test/java/org/lmdbjava/LibraryTest.java b/src/test/java/org/lmdbjava/LibraryTest.java index 6dcfcee9..3b492d75 100644 --- a/src/test/java/org/lmdbjava/LibraryTest.java +++ b/src/test/java/org/lmdbjava/LibraryTest.java @@ -13,30 +13,30 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.lmdbjava; import static java.lang.Long.BYTES; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import static org.lmdbjava.Library.RUNTIME; import static org.lmdbjava.TestUtils.invokePrivateConstructor; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.lmdbjava.Library.MDB_envinfo; /** Test {@link Library}. */ public final class LibraryTest { @Test - public void coverPrivateConstructors() { + void coverPrivateConstructors() { invokePrivateConstructor(Library.class); invokePrivateConstructor(UnsafeAccess.class); } @Test - public void structureFieldOrder() { + void structureFieldOrder() { final MDB_envinfo v = new MDB_envinfo(RUNTIME); - assertThat(v.f0_me_mapaddr.offset(), is(0L)); - assertThat(v.f1_me_mapsize.offset(), is((long) BYTES)); + assertThat(v.f0_me_mapaddr.offset()).isEqualTo(0L); + assertThat(v.f1_me_mapsize.offset()).isEqualTo(BYTES); } } diff --git a/src/test/java/org/lmdbjava/MaskedFlagTest.java b/src/test/java/org/lmdbjava/MaskedFlagTest.java index 918bf922..25c23797 100644 --- a/src/test/java/org/lmdbjava/MaskedFlagTest.java +++ b/src/test/java/org/lmdbjava/MaskedFlagTest.java @@ -13,57 +13,56 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.lmdbjava; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.arrayWithSize; +import static org.assertj.core.api.Assertions.assertThat; import static org.lmdbjava.EnvFlags.MDB_FIXEDMAP; import static org.lmdbjava.EnvFlags.MDB_NOSYNC; import static org.lmdbjava.EnvFlags.MDB_RDONLY_ENV; import static org.lmdbjava.MaskedFlag.isSet; import static org.lmdbjava.MaskedFlag.mask; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** Test {@link MaskedFlag}. */ public final class MaskedFlagTest { @Test - public void isSetOperates() { - assertThat(isSet(0, MDB_NOSYNC), is(false)); - assertThat(isSet(0, MDB_FIXEDMAP), is(false)); - assertThat(isSet(0, MDB_RDONLY_ENV), is(false)); + void isSetOperates() { + assertThat(isSet(0, MDB_NOSYNC)).isFalse(); + assertThat(isSet(0, MDB_FIXEDMAP)).isFalse(); + assertThat(isSet(0, MDB_RDONLY_ENV)).isFalse(); - assertThat(isSet(MDB_FIXEDMAP.getMask(), MDB_NOSYNC), is(false)); - assertThat(isSet(MDB_FIXEDMAP.getMask(), MDB_FIXEDMAP), is(true)); - assertThat(isSet(MDB_FIXEDMAP.getMask(), MDB_RDONLY_ENV), is(false)); + assertThat(isSet(MDB_FIXEDMAP.getMask(), MDB_NOSYNC)).isFalse(); + assertThat(isSet(MDB_FIXEDMAP.getMask(), MDB_FIXEDMAP)).isTrue(); + assertThat(isSet(MDB_FIXEDMAP.getMask(), MDB_RDONLY_ENV)).isFalse(); - assertThat(isSet(MDB_NOSYNC.getMask(), MDB_NOSYNC), is(true)); - assertThat(isSet(MDB_NOSYNC.getMask(), MDB_FIXEDMAP), is(false)); - assertThat(isSet(MDB_NOSYNC.getMask(), MDB_RDONLY_ENV), is(false)); + assertThat(isSet(MDB_NOSYNC.getMask(), MDB_NOSYNC)).isTrue(); + assertThat(isSet(MDB_NOSYNC.getMask(), MDB_FIXEDMAP)).isFalse(); + assertThat(isSet(MDB_NOSYNC.getMask(), MDB_RDONLY_ENV)).isFalse(); final int syncFixed = mask(MDB_NOSYNC, MDB_FIXEDMAP); - assertThat(isSet(syncFixed, MDB_NOSYNC), is(true)); - assertThat(isSet(syncFixed, MDB_FIXEDMAP), is(true)); - assertThat(isSet(syncFixed, MDB_RDONLY_ENV), is(false)); + assertThat(isSet(syncFixed, MDB_NOSYNC)).isTrue(); + assertThat(isSet(syncFixed, MDB_FIXEDMAP)).isTrue(); + assertThat(isSet(syncFixed, MDB_RDONLY_ENV)).isFalse(); } @Test - public void masking() { + void masking() { final EnvFlags[] nullFlags = null; - assertThat(mask(nullFlags), is(0)); + assertThat(mask(nullFlags)).isEqualTo(0); final EnvFlags[] emptyFlags = new EnvFlags[] {}; - assertThat(mask(emptyFlags), is(0)); + assertThat(mask(emptyFlags)).isEqualTo(0); final EnvFlags[] nullElementZero = new EnvFlags[] {null}; - assertThat(nullElementZero, is(arrayWithSize(1))); - assertThat(mask(nullElementZero), is(0)); + assertThat(nullElementZero.length).isEqualTo(1); + assertThat(mask(nullElementZero)).isEqualTo(0); - assertThat(mask(MDB_NOSYNC), is(MDB_NOSYNC.getMask())); + assertThat(mask(MDB_NOSYNC)).isEqualTo(MDB_NOSYNC.getMask()); final int expected = MDB_NOSYNC.getMask() + MDB_FIXEDMAP.getMask(); - assertThat(mask(MDB_NOSYNC, MDB_FIXEDMAP), is(expected)); + assertThat(mask(MDB_NOSYNC, MDB_FIXEDMAP)).isEqualTo(expected); } } diff --git a/src/test/java/org/lmdbjava/MetaTest.java b/src/test/java/org/lmdbjava/MetaTest.java index be0faa87..3e893561 100644 --- a/src/test/java/org/lmdbjava/MetaTest.java +++ b/src/test/java/org/lmdbjava/MetaTest.java @@ -13,17 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.lmdbjava; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import static org.lmdbjava.LmdbNativeException.PageCorruptedException.MDB_CORRUPTED; import static org.lmdbjava.Meta.error; import static org.lmdbjava.TestUtils.invokePrivateConstructor; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.lmdbjava.Meta.Version; /** Test {@link Meta}. */ @@ -35,15 +33,15 @@ public void coverPrivateConstructors() { } @Test - public void errCode() { - assertThat(error(MDB_CORRUPTED), is("MDB_CORRUPTED: Located page was wrong type")); + void errCode() { + assertThat(error(MDB_CORRUPTED)).isEqualTo("MDB_CORRUPTED: Located page was wrong type"); } @Test - public void version() { + void version() { final Version v = Meta.version(); - assertThat(v, not(nullValue())); - assertThat(v.major, is(0)); - assertThat(v.minor, is(9)); + assertThat(v).isNotNull(); + assertThat(v.major).isEqualTo(0); + assertThat(v.minor).isEqualTo(9); } } diff --git a/src/test/java/org/lmdbjava/ResultCodeMapperTest.java b/src/test/java/org/lmdbjava/ResultCodeMapperTest.java index 7a3764ed..c363d60a 100644 --- a/src/test/java/org/lmdbjava/ResultCodeMapperTest.java +++ b/src/test/java/org/lmdbjava/ResultCodeMapperTest.java @@ -13,22 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.lmdbjava; import static java.lang.Integer.MAX_VALUE; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.hasSize; -import static org.junit.Assert.fail; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.fail; import static org.lmdbjava.Cursor.FullException.MDB_CURSOR_FULL; import static org.lmdbjava.ResultCodeMapper.checkRc; import static org.lmdbjava.TestUtils.invokePrivateConstructor; import java.util.HashSet; import java.util.Set; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.lmdbjava.Cursor.FullException; import org.lmdbjava.Dbi.BadDbiException; import org.lmdbjava.Dbi.BadValueSizeException; @@ -86,71 +84,71 @@ public final class ResultCodeMapperTest { } @Test - public void checkErrAll() { + void checkErrAll() { for (final Integer rc : RESULT_CODES) { try { checkRc(rc); fail("Exception expected for RC " + rc); } catch (final LmdbNativeException e) { - assertThat(e.getResultCode(), is(rc)); + assertThat(e.getResultCode()).isEqualTo(rc); } } } - @Test(expected = ConstantDerivedException.class) - public void checkErrConstantDerived() { - checkRc(20); + @Test + void checkErrConstantDerived() { + assertThatThrownBy(() -> checkRc(20)).isInstanceOf(ConstantDerivedException.class); } @Test - public void checkErrConstantDerivedMessage() { + void checkErrConstantDerivedMessage() { try { checkRc(2); fail("Should have raised exception"); } catch (final ConstantDerivedException ex) { - assertThat(ex.getMessage(), containsString("No such file or directory")); + assertThat(ex.getMessage()).contains("No such file or directory"); } } - @Test(expected = FullException.class) - public void checkErrCursorFull() { - checkRc(MDB_CURSOR_FULL); + @Test + void checkErrCursorFull() { + assertThatThrownBy(() -> checkRc(MDB_CURSOR_FULL)).isInstanceOf(FullException.class); } - @Test(expected = IllegalArgumentException.class) - public void checkErrUnknownResultCode() { - checkRc(MAX_VALUE); + @Test + void checkErrUnknownResultCode() { + assertThatThrownBy(() -> checkRc(MAX_VALUE)).isInstanceOf(IllegalArgumentException.class); } @Test - public void coverPrivateConstructors() { + void coverPrivateConstructors() { invokePrivateConstructor(ResultCodeMapper.class); } @Test - public void lmdbExceptionPreservesRootCause() { + void lmdbExceptionPreservesRootCause() { final Exception cause = new IllegalStateException("root cause"); final LmdbException e = new LmdbException("test", cause); - assertThat(e.getCause(), is(cause)); - assertThat(e.getMessage(), is("test")); + assertThat(e.getCause()).isEqualTo(cause); + assertThat(e.getMessage()).isEqualTo("test"); } @Test - public void mapperReturnsUnique() { + void mapperReturnsUnique() { final Set seen = new HashSet<>(); for (final Integer rc : RESULT_CODES) { try { checkRc(rc); } catch (final LmdbNativeException ex) { - assertThat(ex, is(notNullValue())); + assertThat(ex).isNotNull(); seen.add(ex); } } - assertThat(seen, hasSize(RESULT_CODES.size())); + assertThat(seen).hasSize(RESULT_CODES.size()); } @Test - public void noDuplicateResultCodes() { - assertThat(RESULT_CODES.size(), is(EXCEPTIONS.size())); + void noDuplicateResultCodes() { + assertThat(RESULT_CODES.size()).isEqualTo(EXCEPTIONS.size()); } } diff --git a/src/test/java/org/lmdbjava/TargetNameTest.java b/src/test/java/org/lmdbjava/TargetNameTest.java index b598be55..e766aaa6 100644 --- a/src/test/java/org/lmdbjava/TargetNameTest.java +++ b/src/test/java/org/lmdbjava/TargetNameTest.java @@ -13,15 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.lmdbjava; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import static org.lmdbjava.TargetName.isExternal; import static org.lmdbjava.TargetName.resolveFilename; import static org.lmdbjava.TestUtils.invokePrivateConstructor; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** Test {@link TargetName}. */ public final class TargetNameTest { @@ -29,18 +29,18 @@ public final class TargetNameTest { private static final String NONE = ""; @Test - public void coverPrivateConstructors() { + void coverPrivateConstructors() { invokePrivateConstructor(TargetName.class); } @Test - public void customEmbedded() { - assertThat(resolveFilename(NONE, "x/y.so", NONE, NONE), is("x/y.so")); - assertThat(isExternal(NONE), is(false)); + void customEmbedded() { + assertThat(resolveFilename(NONE, "x/y.so", NONE, NONE)).isEqualTo("x/y.so"); + assertThat(isExternal(NONE)).isFalse(); } @Test - public void embeddedNameResolution() { + void embeddedNameResolution() { embed("aarch64-linux-gnu.so", "aarch64", "Linux"); embed("aarch64-macos-none.so", "aarch64", "Mac OS"); embed("x86_64-linux-gnu.so", "x86_64", "Linux"); @@ -49,19 +49,19 @@ public void embeddedNameResolution() { } @Test - public void externalLibrary() { - assertThat(resolveFilename("/l.so", NONE, NONE, NONE), is("/l.so")); - assertThat(TargetName.isExternal("/l.so"), is(true)); + void externalLibrary() { + assertThat(resolveFilename("/l.so", NONE, NONE, NONE)).isEqualTo("/l.so"); + assertThat(TargetName.isExternal("/l.so")).isTrue(); } @Test - public void externalTakesPriority() { - assertThat(resolveFilename("/lm.so", "x/y.so", NONE, NONE), is("/lm.so")); - assertThat(isExternal("/lm.so"), is(true)); + void externalTakesPriority() { + assertThat(resolveFilename("/lm.so", "x/y.so", NONE, NONE)).isEqualTo("/lm.so"); + assertThat(isExternal("/lm.so")).isTrue(); } private void embed(final String lib, final String arch, final String os) { - assertThat(resolveFilename(NONE, NONE, arch, os), is("org/lmdbjava/" + lib)); - assertThat(isExternal(NONE), is(false)); + assertThat(resolveFilename(NONE, NONE, arch, os)).isEqualTo("org/lmdbjava/" + lib); + assertThat(isExternal(NONE)).isFalse(); } } diff --git a/src/test/java/org/lmdbjava/TutorialTest.java b/src/test/java/org/lmdbjava/TutorialTest.java index b86c1d2a..1b42b327 100644 --- a/src/test/java/org/lmdbjava/TutorialTest.java +++ b/src/test/java/org/lmdbjava/TutorialTest.java @@ -13,18 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + 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.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.CoreMatchers.startsWith; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; +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; @@ -35,16 +31,13 @@ import static org.lmdbjava.SeekOp.MDB_LAST; import static org.lmdbjava.SeekOp.MDB_PREV; -import java.io.File; -import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.file.Path; import java.util.concurrent.ExecutorService; import org.agrona.DirectBuffer; import org.agrona.MutableDirectBuffer; import org.agrona.concurrent.UnsafeBuffer; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.Test; import org.lmdbjava.CursorIterable.KeyVal; /** @@ -63,426 +56,429 @@ public final class TutorialTest { private static final String DB_NAME = "my DB"; - @Rule public final TemporaryFolder tmp = new TemporaryFolder(); - /** - * In this first tutorial we will use LmdbJava with some basic defaults. - * - * @throws IOException if a path was unavailable for memory mapping - */ + /** In this first tutorial we will use LmdbJava with some basic defaults. */ @Test - public void tutorial1() throws IOException { + void tutorial1() { // We need a storage directory first. // The path cannot be on a remote file system. - final File path = tmp.newFolder(); - - // 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(path); - - // 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); - assertNotNull(found); - - // The fetchedVal is read-only and points to LMDB memory - final ByteBuffer fetchedVal = txn.val(); - assertThat(fetchedVal.remaining(), is(valSize)); - - // Let's double-check the fetched value is correct - assertThat(UTF_8.decode(fetchedVal).toString(), is("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()) { - assertNull(db.get(txn, key)); - } - - env.close(); + 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(); + }); } - /** - * In this second tutorial we'll learn more about LMDB's ACID Txns. - * - * @throws IOException if a path was unavailable for memory mapping - * @throws InterruptedException if executor shutdown interrupted - */ + /** In this second tutorial we'll learn more about LMDB's ACID Txns. */ @Test - public void tutorial2() throws IOException, InterruptedException { - final Env env = createSimpleEnv(tmp.newFolder()); - 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); - assertNotNull(found); - - // 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); - assertNotNull(found); - - // 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(); + 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. + 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.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); - assertNull(found); - - // 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); - assertNotNull(found); - - // 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(); } /** * In this third tutorial we'll have a look at the Cursor. Up until now we've just used Dbi, which * is good enough for simple cases but unsuitable if you don't know the key to fetch, or want to * iterate over all the data etc. - * - * @throws IOException if a path was unavailable for memory mapping */ @Test - public void tutorial3() throws IOException { - final Env env = createSimpleEnv(tmp.newFolder()); - 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(), is("ccc")); - - // Let's see that LMDB provides the keys in appropriate order.... - c.seek(MDB_FIRST); - assertThat(UTF_8.decode(c.key()).toString(), is("aaa")); - - c.seek(MDB_LAST); - assertThat(UTF_8.decode(c.key()).toString(), is("zzz")); - - c.seek(MDB_PREV); - assertThat(UTF_8.decode(c.key()).toString(), is("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(); + 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(); + }); } /** * In this fourth tutorial we'll take a quick look at the iterators. These are a more Java * idiomatic form of using the Cursors we looked at in tutorial 3. - * - * @throws IOException if a path was unavailable for memory mapping */ @Test - public void tutorial4() throws IOException { - final Env env = createSimpleEnv(tmp.newFolder()); - 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(), notNullValue()); - assertThat(kv.val(), notNullValue()); - } - } - - // 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(), notNullValue()); - assertThat(kv.val(), notNullValue()); - } - } - - // 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(), notNullValue()); - assertThat(kv.val(), notNullValue()); - } - } - } - - env.close(); + 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(); + }); } - /** - * In this fifth tutorial we'll explore multiple values sharing a single key. - * - * @throws IOException if a path was unavailable for memory mapping - */ + /** In this fifth tutorial we'll explore multiple values sharing a single key. */ @Test - public void tutorial5() throws IOException { - final Env env = createSimpleEnv(tmp.newFolder()); - - // 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, is(3L)); - - // Let's position the Cursor. Note sorting still works. - c.seek(MDB_FIRST); - assertThat(UTF_8.decode(c.val()).toString(), is("kkk")); - - c.seek(MDB_LAST); - assertThat(UTF_8.decode(c.val()).toString(), is("xxx")); - - c.seek(MDB_PREV); - assertThat(UTF_8.decode(c.val()).toString(), is("lll")); - - c.close(); - txn.commit(); - } - - env.close(); + 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(); + }); } /** * Next up we'll show you how to easily check your platform (operating system and Java version) is * working properly with LmdbJava and the embedded LMDB native library. - * - * @throws IOException if a path was unavailable for memory mapping */ @Test - public void tutorial6() throws IOException { - // 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(tmp.newFolder()); - - // 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(); + 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(); + }); } - /** - * In this final tutorial we'll look at using Agrona's DirectBuffer. - * - * @throws IOException if a path was unavailable for memory mapping - */ + /** In this final tutorial we'll look at using Agrona's DirectBuffer. */ @Test - public void tutorial7() throws IOException { - // 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(tmp.newFolder()); - - 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, is(12)); - assertThat(key.capacity(), is(env.getMaxKeySize())); - - // To only store the 12 characters, we simply call wrap: - key.wrap(key, 0, keyLen); - assertThat(key.capacity(), is(keyLen)); - c.put(key, val); - c.seek(MDB_FIRST); - assertThat(c.key().capacity(), is(keyLen)); - assertThat(c.key().getStringWithoutLengthUtf8(0, c.key().capacity()), is("12characters")); - - // To store bigger values again, just wrap the original buffer. - key.wrap(keyBb); - assertThat(key.capacity(), is(env.getMaxKeySize())); - } - txn.commit(); - } - - env.close(); + 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(); + }); } // You've finished! There are lots of other neat things we could show you (eg // how to speed up inserts by appending them in key order, using integer // 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 File path) { - return create().setMapSize(10_485_760).setMaxDbs(1).setMaxReaders(1).open(path); + private Env createSimpleEnv(final Path path) { + return create().setMapSize(10_485_760).setMaxDbs(1).setMaxReaders(1).open(path.toFile()); } } diff --git a/src/test/java/org/lmdbjava/TxnTest.java b/src/test/java/org/lmdbjava/TxnTest.java index e768e424..46ffeb70 100644 --- a/src/test/java/org/lmdbjava/TxnTest.java +++ b/src/test/java/org/lmdbjava/TxnTest.java @@ -13,17 +13,14 @@ * 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.nio.ByteBuffer.allocateDirect; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.Env.create; import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; @@ -38,17 +35,15 @@ import static org.lmdbjava.Txn.State.RESET; import static org.lmdbjava.TxnFlags.MDB_RDONLY_TXN; -import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicLong; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.lmdbjava.Dbi.BadValueSizeException; import org.lmdbjava.Env.AlreadyClosedException; import org.lmdbjava.Txn.EnvIsReadOnly; @@ -62,36 +57,40 @@ /** Test {@link Txn}. */ public final class TxnTest { - @Rule public final TemporaryFolder tmp = new TemporaryFolder(); + private Path file; private Env env; - private File path; - @After - public void after() { - env.close(); - } - - @Before - public void before() throws IOException { - path = tmp.newFile(); + @BeforeEach + void beforeEach() { + file = FileUtil.createTempFile(); env = create() .setMapSize(KIBIBYTES.toBytes(256)) .setMaxReaders(1) .setMaxDbs(2) - .open(path, POSIX_MODE, MDB_NOSUBDIR); + .open(file.toFile(), POSIX_MODE, MDB_NOSUBDIR); } - @Test(expected = BadValueSizeException.class) - public void largeKeysRejected() throws IOException { - final Dbi dbi = env.openDbi(DB_1, MDB_CREATE); - final ByteBuffer key = allocateDirect(env.getMaxKeySize() + 1); - key.limit(key.capacity()); - dbi.put(key, bb(2)); + @AfterEach + void afterEach() { + env.close(); + FileUtil.delete(file); } @Test - public void rangeSearch() { + void largeKeysRejected() throws IOException { + assertThatThrownBy( + () -> { + final Dbi dbi = env.openDbi(DB_1, MDB_CREATE); + final ByteBuffer key = allocateDirect(env.getMaxKeySize() + 1); + key.limit(key.capacity()); + dbi.put(key, bb(2)); + }) + .isInstanceOf(BadValueSizeException.class); + } + + @Test + void rangeSearch() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); final ByteBuffer key = allocateDirect(env.getMaxKeySize()); @@ -120,53 +119,69 @@ public void rangeSearch() { } } - assertEquals(3, keysFound.size()); + assertThat(keysFound.size()).isEqualTo(3); } } @Test - public void readOnlyTxnAllowedInReadOnlyEnv() { + void readOnlyTxnAllowedInReadOnlyEnv() { env.openDbi(DB_1, MDB_CREATE); try (Env roEnv = - create().setMaxReaders(1).open(path, MDB_NOSUBDIR, MDB_RDONLY_ENV)) { - assertThat(roEnv.txnRead(), is(notNullValue())); + create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR, MDB_RDONLY_ENV)) { + assertThat(roEnv.txnRead()).isNotNull(); } } - @Test(expected = EnvIsReadOnly.class) - public void readWriteTxnDeniedInReadOnlyEnv() { - env.openDbi(DB_1, MDB_CREATE); - env.close(); - try (Env roEnv = - create().setMaxReaders(1).open(path, MDB_NOSUBDIR, MDB_RDONLY_ENV)) { - roEnv.txnWrite(); // error - } + @Test + void readWriteTxnDeniedInReadOnlyEnv() { + assertThatThrownBy( + () -> { + env.openDbi(DB_1, MDB_CREATE); + env.close(); + try (Env roEnv = + create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR, MDB_RDONLY_ENV)) { + roEnv.txnWrite(); // error + } + }) + .isInstanceOf(EnvIsReadOnly.class); } - @Test(expected = NotReadyException.class) - public void testCheckNotCommitted() { - try (Txn txn = env.txnRead()) { - txn.commit(); - txn.checkReady(); - } + @Test + void testCheckNotCommitted() { + assertThatThrownBy( + () -> { + try (Txn txn = env.txnRead()) { + txn.commit(); + txn.checkReady(); + } + }) + .isInstanceOf(NotReadyException.class); } - @Test(expected = ReadOnlyRequiredException.class) - public void testCheckReadOnly() { - try (Txn txn = env.txnWrite()) { - txn.checkReadOnly(); - } + @Test + void testCheckReadOnly() { + assertThatThrownBy( + () -> { + try (Txn txn = env.txnWrite()) { + txn.checkReadOnly(); + } + }) + .isInstanceOf(ReadOnlyRequiredException.class); } - @Test(expected = ReadWriteRequiredException.class) - public void testCheckWritesAllowed() { - try (Txn txn = env.txnRead()) { - txn.checkWritesAllowed(); - } + @Test + void testCheckWritesAllowed() { + assertThatThrownBy( + () -> { + try (Txn txn = env.txnRead()) { + txn.checkWritesAllowed(); + } + }) + .isInstanceOf(ReadWriteRequiredException.class); } @Test - public void testGetId() { + void testGetId() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); final AtomicLong txId1 = new AtomicLong(); @@ -182,173 +197,233 @@ public void testGetId() { txId2.set(tx2.getId()); } // should not see the same snapshot - assertThat(txId1.get(), is(not(txId2.get()))); + assertThat(txId1.get()).isNotEqualTo(txId2.get()); } @Test - public void txCanCommitThenCloseWithoutError() { + void txCanCommitThenCloseWithoutError() { try (Txn txn = env.txnRead()) { - assertThat(txn.getState(), is(READY)); + assertThat(txn.getState()).isEqualTo(READY); txn.commit(); - assertThat(txn.getState(), is(DONE)); + assertThat(txn.getState()).isEqualTo(DONE); } } - @Test(expected = NotReadyException.class) - public void txCannotAbortIfAlreadyCommitted() { - try (Txn txn = env.txnRead()) { - assertThat(txn.getState(), is(READY)); - txn.commit(); - assertThat(txn.getState(), is(DONE)); - txn.abort(); - } + @Test + void txCannotAbortIfAlreadyCommitted() { + assertThatThrownBy( + () -> { + try (Txn txn = env.txnRead()) { + assertThat(txn.getState()).isEqualTo(READY); + txn.commit(); + assertThat(txn.getState()).isEqualTo(DONE); + txn.abort(); + } + }) + .isInstanceOf(NotReadyException.class); } - @Test(expected = NotReadyException.class) - public void txCannotCommitTwice() { - try (Txn txn = env.txnRead()) { - txn.commit(); - txn.commit(); // error - } + @Test + void txCannotCommitTwice() { + assertThatThrownBy( + () -> { + try (Txn txn = env.txnRead()) { + txn.commit(); + txn.commit(); // error + } + }) + .isInstanceOf(NotReadyException.class); } - @Test(expected = AlreadyClosedException.class) - public void txConstructionDeniedIfEnvClosed() { - env.close(); - env.txnRead(); + @Test + void txConstructionDeniedIfEnvClosed() { + assertThatThrownBy( + () -> { + env.close(); + env.txnRead(); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void txRenewDeniedIfEnvClosed() { - final Txn txnRead = env.txnRead(); - txnRead.close(); - env.close(); - txnRead.renew(); + @Test + void txRenewDeniedIfEnvClosed() { + assertThatThrownBy( + () -> { + final Txn txnRead = env.txnRead(); + txnRead.close(); + env.close(); + txnRead.renew(); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void txCloseDeniedIfEnvClosed() { - final Txn txnRead = env.txnRead(); - env.close(); - txnRead.close(); + @Test + void txCloseDeniedIfEnvClosed() { + assertThatThrownBy( + () -> { + final Txn txnRead = env.txnRead(); + env.close(); + txnRead.close(); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void txCommitDeniedIfEnvClosed() { - final Txn txnRead = env.txnRead(); - env.close(); - txnRead.commit(); + @Test + void txCommitDeniedIfEnvClosed() { + assertThatThrownBy( + () -> { + final Txn txnRead = env.txnRead(); + env.close(); + txnRead.commit(); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void txAbortDeniedIfEnvClosed() { - final Txn txnRead = env.txnRead(); - env.close(); - txnRead.abort(); + @Test + void txAbortDeniedIfEnvClosed() { + assertThatThrownBy( + () -> { + final Txn txnRead = env.txnRead(); + env.close(); + txnRead.abort(); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void txResetDeniedIfEnvClosed() { - final Txn txnRead = env.txnRead(); - env.close(); - txnRead.reset(); + @Test + void txResetDeniedIfEnvClosed() { + assertThatThrownBy( + () -> { + final Txn txnRead = env.txnRead(); + env.close(); + txnRead.reset(); + }) + .isInstanceOf(AlreadyClosedException.class); } @Test public void txParent() { try (Txn txRoot = env.txnWrite(); Txn txChild = env.txn(txRoot)) { - assertThat(txRoot.getParent(), is(nullValue())); - assertThat(txChild.getParent(), is(txRoot)); + assertThat(txRoot.getParent()).isNull(); + assertThat(txChild.getParent()).isEqualTo(txRoot); } } - @Test(expected = AlreadyClosedException.class) - public void txParentDeniedIfEnvClosed() { - try (Txn txRoot = env.txnWrite(); - Txn txChild = env.txn(txRoot)) { - env.close(); - assertThat(txChild.getParent(), is(txRoot)); - } + @Test + void txParentDeniedIfEnvClosed() { + assertThatThrownBy( + () -> { + try (Txn txRoot = env.txnWrite(); + Txn txChild = env.txn(txRoot)) { + env.close(); + assertThat(txChild.getParent()).isEqualTo(txRoot); + } + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = IncompatibleParent.class) - public void txParentROChildRWIncompatible() { - try (Txn txRoot = env.txnRead()) { - env.txn(txRoot); // error - } + @Test + void txParentROChildRWIncompatible() { + assertThatThrownBy( + () -> { + try (Txn txRoot = env.txnRead()) { + env.txn(txRoot); // error + } + }) + .isInstanceOf(IncompatibleParent.class); } - @Test(expected = IncompatibleParent.class) - public void txParentRWChildROIncompatible() { - try (Txn txRoot = env.txnWrite()) { - env.txn(txRoot, MDB_RDONLY_TXN); // error - } + @Test + void txParentRWChildROIncompatible() { + assertThatThrownBy( + () -> { + try (Txn txRoot = env.txnWrite()) { + env.txn(txRoot, MDB_RDONLY_TXN); // error + } + }) + .isInstanceOf(IncompatibleParent.class); } @Test - public void txReadOnly() { + void txReadOnly() { try (Txn txn = env.txnRead()) { - assertThat(txn.getParent(), is(nullValue())); - assertThat(txn.getState(), is(READY)); - assertThat(txn.isReadOnly(), is(true)); + assertThat(txn.getParent()).isNull(); + assertThat(txn.getState()).isEqualTo(READY); + assertThat(txn.isReadOnly()).isTrue(); txn.checkReady(); txn.checkReadOnly(); txn.reset(); - assertThat(txn.getState(), is(RESET)); + assertThat(txn.getState()).isEqualTo(RESET); txn.renew(); - assertThat(txn.getState(), is(READY)); + assertThat(txn.getState()).isEqualTo(READY); txn.commit(); - assertThat(txn.getState(), is(DONE)); + assertThat(txn.getState()).isEqualTo(DONE); txn.close(); - assertThat(txn.getState(), is(RELEASED)); + assertThat(txn.getState()).isEqualTo(RELEASED); } } @Test - public void txReadWrite() { + void txReadWrite() { final Txn txn = env.txnWrite(); - assertThat(txn.getParent(), is(nullValue())); - assertThat(txn.getState(), is(READY)); - assertThat(txn.isReadOnly(), is(false)); + assertThat(txn.getParent()).isNull(); + assertThat(txn.getState()).isEqualTo(READY); + assertThat(txn.isReadOnly()).isFalse(); txn.checkReady(); txn.checkWritesAllowed(); txn.commit(); - assertThat(txn.getState(), is(DONE)); + assertThat(txn.getState()).isEqualTo(DONE); txn.close(); - assertThat(txn.getState(), is(RELEASED)); + assertThat(txn.getState()).isEqualTo(RELEASED); } - @Test(expected = NotResetException.class) - public void txRenewDeniedWithoutPriorReset() { - try (Txn txn = env.txnRead()) { - txn.renew(); - } + @Test + void txRenewDeniedWithoutPriorReset() { + assertThatThrownBy( + () -> { + try (Txn txn = env.txnRead()) { + txn.renew(); + } + }) + .isInstanceOf(NotResetException.class); } - @Test(expected = ResetException.class) - public void txResetDeniedForAlreadyResetTransaction() { - try (Txn txn = env.txnRead()) { - txn.reset(); - txn.renew(); - txn.reset(); - txn.reset(); - } + @Test + void txResetDeniedForAlreadyResetTransaction() { + assertThatThrownBy( + () -> { + try (Txn txn = env.txnRead()) { + txn.reset(); + txn.renew(); + txn.reset(); + txn.reset(); + } + }) + .isInstanceOf(ResetException.class); } - @Test(expected = ReadOnlyRequiredException.class) - public void txResetDeniedForReadWriteTransaction() { - try (Txn txn = env.txnWrite()) { - txn.reset(); - } + @Test + void txResetDeniedForReadWriteTransaction() { + assertThatThrownBy( + () -> { + try (Txn txn = env.txnWrite()) { + txn.reset(); + } + }) + .isInstanceOf(ReadOnlyRequiredException.class); } - @Test(expected = BadValueSizeException.class) - public void zeroByteKeysRejected() throws IOException { - final Dbi dbi = env.openDbi(DB_1, MDB_CREATE); - final ByteBuffer key = allocateDirect(4); - key.putInt(1); - assertThat(key.remaining(), is(0)); // because key.flip() skipped - dbi.put(key, bb(2)); + @Test + void zeroByteKeysRejected() { + assertThatThrownBy( + () -> { + final Dbi dbi = env.openDbi(DB_1, MDB_CREATE); + final ByteBuffer key = allocateDirect(4); + key.putInt(1); + assertThat(key.remaining()).isEqualTo(0); // because key.flip() skipped + dbi.put(key, bb(2)); + }) + .isInstanceOf(BadValueSizeException.class); } } diff --git a/src/test/java/org/lmdbjava/VerifierTest.java b/src/test/java/org/lmdbjava/VerifierTest.java index c57e26dc..64214568 100644 --- a/src/test/java/org/lmdbjava/VerifierTest.java +++ b/src/test/java/org/lmdbjava/VerifierTest.java @@ -13,39 +13,35 @@ * 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.hamcrest.Matchers.greaterThan; +import static org.assertj.core.api.Assertions.assertThat; import static org.lmdbjava.Env.create; import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; -import java.io.File; -import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.TimeUnit; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.Test; /** Test {@link Verifier}. */ public final class VerifierTest { - @Rule public final TemporaryFolder tmp = new TemporaryFolder(); - @Test - public void verification() throws IOException { - final File path = tmp.newFile(); - try (Env env = - create() - .setMaxReaders(1) - .setMaxDbs(Verifier.DBI_COUNT) - .setMapSize(MEBIBYTES.toBytes(10)) - .open(path, MDB_NOSUBDIR)) { - final Verifier v = new Verifier(env); - final int seconds = Integer.getInteger("verificationSeconds", 2); - assertThat(v.runFor(seconds, TimeUnit.SECONDS), greaterThan(1L)); - } + 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); + } + }); } } 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 050/139] 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 05314e8b2bdda8cb71d68eb9c7baeeffe60788fe Mon Sep 17 00:00:00 2001 From: stroomdev66 Date: Tue, 28 Oct 2025 18:32:38 +0000 Subject: [PATCH 051/139] Upgrade to JUnit 5 and replace Hamcrest with AssertJ --- src/test/java/org/lmdbjava/DbiTest.java | 79 +++++++++++-------------- 1 file changed, 34 insertions(+), 45 deletions(-) diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 844a819e..23b6790e 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -29,20 +29,14 @@ 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.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.DbiFlags.*; 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.DB_1; -import static org.lmdbjava.TestUtils.ba; -import static org.lmdbjava.TestUtils.bb; -import static org.lmdbjava.TestUtils.fromBa; +import static org.lmdbjava.TestUtils.*; import java.nio.ByteBuffer; import java.nio.file.Path; @@ -50,11 +44,7 @@ import java.util.Comparator; import java.util.Iterator; import java.util.List; -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.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiConsumer; import java.util.function.Function; @@ -197,44 +187,43 @@ private void doDbiWithComparatorThreadSafety( final List keys = range(0, 1_000).boxed().collect(toList()); - try (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 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); } } From bc48cff3284719ee477d7c471cf72c48aa212c66 Mon Sep 17 00:00:00 2001 From: stroomdev66 Date: Tue, 28 Oct 2025 18:56:14 +0000 Subject: [PATCH 052/139] Upgrade LMDB C lib to 0.9.33 direct from the OpenLDAP repository --- cross-compile.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cross-compile.sh b/cross-compile.sh index aa8815d5..5243feec 100755 --- a/cross-compile.sh +++ b/cross-compile.sh @@ -18,9 +18,9 @@ set -o errexit -rm -rf lmdb -git clone --depth 1 --branch LMDB_0.9.31 https://github.com/LMDB/lmdb.git -pushd lmdb/libraries/liblmdb +rm -rf openldap +git clone --depth 1 --branch LMDB_0.9.33 https://git.openldap.org/openldap/openldap.git +pushd openldap/libraries/liblmdb trap popd SIGINT # zig targets | jq -r '.libc[]' From f2de1bdc6f9586466f31b607209e70e0edf4cf32 Mon Sep 17 00:00:00 2001 From: stroomdev66 Date: Tue, 28 Oct 2025 19:01:56 +0000 Subject: [PATCH 053/139] Upgrade to JUnit 5 and replace Hamcrest with AssertJ --- .../java/org/lmdbjava/ComparatorTest.java | 22 +++++++++---------- .../java/org/lmdbjava/CursorParamTest.java | 12 +++++----- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/test/java/org/lmdbjava/ComparatorTest.java b/src/test/java/org/lmdbjava/ComparatorTest.java index b4946a26..a5e010ab 100644 --- a/src/test/java/org/lmdbjava/ComparatorTest.java +++ b/src/test/java/org/lmdbjava/ComparatorTest.java @@ -56,14 +56,14 @@ public final class ComparatorTest { static Stream comparatorProvider() { return Stream.of( - Arguments.arguments("StringRunner", new StringRunner()), - Arguments.arguments("DirectBufferRunner", new DirectBufferRunner()), - Arguments.arguments("ByteArrayRunner", new ByteArrayRunner()), - Arguments.arguments("UnsignedByteArrayRunner", new UnsignedByteArrayRunner()), - Arguments.arguments("ByteBufferRunner", new ByteBufferRunner()), - Arguments.arguments("NettyRunner", new NettyRunner()), - Arguments.arguments("GuavaUnsignedBytes", new GuavaUnsignedBytes()), - Arguments.arguments("GuavaSignedBytes", new GuavaSignedBytes())); + 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) { @@ -76,7 +76,7 @@ private static byte[] buffer(final int... bytes) { @ParameterizedTest @MethodSource("comparatorProvider") - void atLeastOneBufferHasEightBytes(final String str, final ComparatorRunner comparator) { + void atLeastOneBufferHasEightBytes(final ComparatorRunner comparator) { assertThat(get(comparator.compare(HLLLLLLL, LLLLLLLL))).isEqualTo(GREATER_THAN); assertThat(get(comparator.compare(LLLLLLLL, HLLLLLLL))).isEqualTo(LESS_THAN); @@ -95,7 +95,7 @@ void atLeastOneBufferHasEightBytes(final String str, final ComparatorRunner comp @ParameterizedTest @MethodSource("comparatorProvider") - void buffersOfTwoBytes(final String str, final ComparatorRunner comparator) { + void buffersOfTwoBytes(final ComparatorRunner comparator) { assertThat(get(comparator.compare(LL, XX))).isEqualTo(GREATER_THAN); assertThat(get(comparator.compare(XX, LL))).isEqualTo(LESS_THAN); @@ -111,7 +111,7 @@ void buffersOfTwoBytes(final String str, final ComparatorRunner comparator) { @ParameterizedTest @MethodSource("comparatorProvider") - void equalBuffers(final String str, final ComparatorRunner comparator) { + void equalBuffers(final ComparatorRunner comparator) { assertThat(get(comparator.compare(LL, LL))).isEqualTo(EQUAL_TO); assertThat(get(comparator.compare(HX, HX))).isEqualTo(EQUAL_TO); assertThat(get(comparator.compare(LH, LH))).isEqualTo(EQUAL_TO); diff --git a/src/test/java/org/lmdbjava/CursorParamTest.java b/src/test/java/org/lmdbjava/CursorParamTest.java index 0b2df19e..1a3604be 100644 --- a/src/test/java/org/lmdbjava/CursorParamTest.java +++ b/src/test/java/org/lmdbjava/CursorParamTest.java @@ -58,16 +58,16 @@ public final class CursorParamTest { static Stream data() { return Stream.of( - Arguments.arguments("ByteBufferRunner(PROXY_OPTIMAL)", new ByteBufferRunner(PROXY_OPTIMAL)), - Arguments.arguments("ByteBufferRunner(PROXY_SAFE)", new ByteBufferRunner(PROXY_SAFE)), - Arguments.arguments("ByteArrayRunner(PROXY_BA)", new ByteArrayRunner(PROXY_BA)), - Arguments.arguments("DirectBufferRunner", new DirectBufferRunner()), - Arguments.arguments("NettyBufferRunner", new NettyBufferRunner())); + 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") - void execute(final String name, final BufferRunner runner, @TempDir final Path tmp) { + void execute(final BufferRunner runner, @TempDir final Path tmp) { runner.execute(tmp); } 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 054/139] 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 055/139] 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 056/139] 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 057/139] 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 058/139] #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 e634b3f48fd2dcc6790f580085b8c44dd5d7077b Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Thu, 30 Oct 2025 09:00:45 +1100 Subject: [PATCH 059/139] Fix major performance regression LmdbJava Benchmarking infrastructure was updated in #262. This uncovered a major regression in releases 0.9.0 and 0.9.1. To identify the source of the regression, the updated LmdbJava Benchmarks were executed with 1,000,000 entries using 100 byte values and 4 byte integer keys. The sequential read benchmark was retrospectively executed against LmdbJava commits compiled using Java 25 as detailed in the following table. To rule out native library variability, LMDB 0.9.33 from the official Arch Linux x86_64 native package was used via the lmdbjava.native.lib system property. Benchmarks were run on workstation-grade hardware (64 GB RAM, AMD Ryzen 9 7900 12-Core Processor) to a 32 GB /tmp directory backed by tmpfs. The machine was not running any other workloads. Tests were executed for 1 minute per warmup, 1 minute per iteration, 3 warmups, 3 iterations, and 3 forks. As such each commit was tested for approximately 18 minutes to overcome jitter and noise found at shorter durations. It was not possible to test intermediate releases 0.6.3 or 0.7.0 due to JNR-FFI incompatibilities. As shown, the regression was introduced in commit 0c636f1 (#207). This followed the 0.8.3 release and was included in releases 0.9.0 and 0.9.1. The fix provided in this commit has been verified with the benchmark result shown below. In addition to the automated tests passing, a script was written that executed "mvn test -Dtest=GarbageCollectionTest" (which was provided in #207) for 1000 iterations with both Java 17 and 25. This comprehensively confirms this fix does not introduce the adverse behaviour initially identified in #207. The logical basis for the fix is also confirmed in the updated JavaDocs for ReferenceUtil. While there remains a 4.60% regression relative to the 0.0.1 baseline, this is lower than any release since 0.0.5. Further work to follow on automating benchmarking infrastructure and addressing other performance improvement opportunities. Commit Release Date Score (ms) Delta Notes ------------------------------------------------------------------------ 57f355f v0.0.1 2016-07-07 43.22 Baseline 6d001e8 v0.0.5 2017-02-08 43.97 +1.74% d3572ca v0.5.0 2017-07-05 45.29 +4.79% be2a15b v0.8.3 2023-02-04 45.26 +4.72% 3524995 2023-02-04 45.41 +5.07% Refactor Comparator handling 9cf97a5 2023-03-09 45.04 +4.21% mdb_reader_check API fix 0c636f1 2023-03-23 47.83 +10.67% Hold strong reference (REGRESSION) cbdaee6 2023-04-24 47.39 +9.65% Update formatting/GC test c5e02f7 2023-04-24 48.24 +11.61% Merge fixcorruptionbug f17b63f v0.9.0 2023-12-05 48.54 +12.31% 0025a48 v0.9.1 2025-02-20 47.61 +10.16% ddca4bd 2025-07-08 47.55 +10.02% Fix comparator lost buffer ref c91893a 2025-10-25 58.67 +35.74% Fix transient pointer (PEAK REGRESSION) 43ac84f 2025-10-26 55.08 +27.43% a15f4a7 2025-10-29 55.43 +28.24% Master before fix 19b0250 2025-10-30 45.21 +4.60% Empty method fix (REGRESSION ELIMINATED) --- src/main/java/org/lmdbjava/ReferenceUtil.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/lmdbjava/ReferenceUtil.java b/src/main/java/org/lmdbjava/ReferenceUtil.java index 2b9f211e..67273d05 100644 --- a/src/main/java/org/lmdbjava/ReferenceUtil.java +++ b/src/main/java/org/lmdbjava/ReferenceUtil.java @@ -33,17 +33,18 @@ private ReferenceUtil() {} * but it was only introduced in Java 9. LmdbJava presently supports Java 8 and therefore this * method provides an alternative. * - *

This method is always implemented as a synchronization on {@code ref}. It is the caller's - * responsibility to ensure that this synchronization will not cause deadlock. + *

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 fadcb33abad5d495e20495f76e1f4e4d7e4442f9 Mon Sep 17 00:00:00 2001 From: stroomdev66 Date: Tue, 4 Nov 2025 21:19:15 +0000 Subject: [PATCH 064/139] #273 Improve ByteBufferProxy comparison performance --- src/main/java/org/lmdbjava/ByteBufferProxy.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index 9f30f576..3c80b995 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -119,9 +119,6 @@ abstract static class AbstractByteBufferProxy extends BufferProxy { public static int compareBuff(final ByteBuffer o1, final ByteBuffer o2) { requireNonNull(o1); requireNonNull(o2); - if (o1.equals(o2)) { - return 0; - } final int minLength = Math.min(o1.limit(), o2.limit()); final int minWords = minLength / Long.BYTES; From 89634ca9fe058f7c7fdab45f3133a070c43015f4 Mon Sep 17 00:00:00 2001 From: stroomdev66 Date: Wed, 5 Nov 2025 10:18:11 +0000 Subject: [PATCH 065/139] #273 Improve DirectBufferProxy comparison performance --- src/main/java/org/lmdbjava/DirectBufferProxy.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/org/lmdbjava/DirectBufferProxy.java b/src/main/java/org/lmdbjava/DirectBufferProxy.java index 17b59b3d..524b81b8 100644 --- a/src/main/java/org/lmdbjava/DirectBufferProxy.java +++ b/src/main/java/org/lmdbjava/DirectBufferProxy.java @@ -70,9 +70,7 @@ private DirectBufferProxy() {} public static int compareBuff(final DirectBuffer o1, final DirectBuffer o2) { requireNonNull(o1); requireNonNull(o2); - if (o1.equals(o2)) { - return 0; - } + final int minLength = Math.min(o1.capacity(), o2.capacity()); final int minWords = minLength / Long.BYTES; 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 066/139] 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 067/139] #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 068/139] #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 069/139] #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 070/139] 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 071/139] #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 072/139] #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 073/139] 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 074/139] 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 075/139] 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 076/139] 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 077/139] 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 078/139] 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 079/139] 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 080/139] 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 081/139] 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 082/139] 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 083/139] 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 084/139] 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 085/139] 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 086/139] 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 087/139] 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 088/139] 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 089/139] 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 090/139] 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 091/139] 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

This method works because HotSpot JIT-compilers prune dead locals based on method bytecode + * analysis rather than optimized IR. As Vladimir Ivanov explains: "any usage of a local extends + * its live range, even if that usage is eliminated in generated code". The method call at the + * bytecode level is sufficient to keep the object alive through safepoints, preventing premature + * garbage collection during native operations. * - * @param ref the reference (null is acceptable but has no effect) - * @see Netty PR 8410 + * @param ref the reference + * @see + * Vladimir Ivanov on reachabilityFence implementation */ public static void reachabilityFence0(final Object ref) { - if (ref != null) { - synchronized (ref) { - // Empty synchronized is ok: https://stackoverflow.com/a/31933260/1151521 - } - } + // Empty method body is intentional - the method call itself at bytecode level + // extends the object's live range per HotSpot JIT behavior } } From e5b510c00a693f3746db6afbba1b51fe1dacc1dc Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Thu, 30 Oct 2025 15:56:03 +1100 Subject: [PATCH 060/139] Replace Zig cross-compilation with native library artifact Commit 288bb09 introduced Zig cross-compilation of LMDB (#217). Benchmarking as part of #262 revealed the cross-compiled libraries are significantly slower than LMDB compiled with native toolchains. Using the methodology from e634b3f: Library Source Score (ms) Delta vs Baseline -------------------------------------------------------------- System Arch Linux (GCC) 45.00 Baseline Fetched Arch Linux (GCC) 45.03 +0.05% Zig unoptimized (debug) 59.01 +31.12% Zig optimized (-O2 stripped) 58.82 +30.70% This removes Zig cross-compilation and adopts the org.lmdbjava:native artifact, which packages LMDB libraries compiled with native toolchains from distribution package managers. This simplifies building as Zig installation and cross-compile.sh are no longer required. --- .github/workflows/maven.yml | 26 ----------- README.md | 19 ++------ cross-compile.sh | 43 ------------------- pom.xml | 23 ++++------ src/main/java/org/lmdbjava/TargetName.java | 4 +- src/main/resources/org/lmdbjava/.gitignore | 2 - .../java/org/lmdbjava/TargetNameTest.java | 2 +- 7 files changed, 16 insertions(+), 103 deletions(-) delete mode 100755 cross-compile.sh delete mode 100644 src/main/resources/org/lmdbjava/.gitignore diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 372c0c88..38f6472b 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -22,23 +22,9 @@ jobs: java-version: 25 cache: maven - - name: Install Zig - uses: mlugg/setup-zig@v2 - - - name: Cross compile using Zig - run: ./cross-compile.sh - - name: Build with Maven run: mvn -B verify -DgcRecordWrites=1000 - - name: Store built native libraries for later jobs - uses: actions/upload-artifact@v4 - with: - name: native-libraries - path: | - src/main/resources/org/lmdbjava/*.so - src/main/resources/org/lmdbjava/*.dll - - name: Upload code coverage to Codecov uses: codecov/codecov-action@v5 env: @@ -65,12 +51,6 @@ jobs: java-version: ${{ matrix.java }} cache: maven - - name: Fetch built native libraries - uses: actions/download-artifact@v4 - with: - name: native-libraries - path: src/main/resources/org/lmdbjava - - name: Execute verifier run: mvn -B test -Dtest=VerifierTest -DverificationSeconds=10 @@ -104,12 +84,6 @@ jobs: gpg-private-key: ${{ secrets.gpg_private_key }} gpg-passphrase: MAVEN_GPG_PASSPHRASE - - name: Install Zig - uses: mlugg/setup-zig@v2 - - - name: Cross compile using Zig - run: ./cross-compile.sh - - name: Publish Maven package run: mvn -B -Pcentral-deploy deploy -DskipTests env: diff --git a/README.md b/README.md index 99ccf54f..77a0c164 100644 --- a/README.md +++ b/README.md @@ -57,21 +57,10 @@ any questions. ### Building -This project uses [Zig](https://ziglang.org/) to cross-compile the LMDB native -library for all supported architectures. To locally build LmdbJava you must -firstly install a recent version of Zig and then execute the project's -[cross-compile.sh](https://github.com/lmdbjava/lmdbjava/tree/master/cross-compile.sh) -script. This only needs to be repeated when the `cross-compile.sh` script is -updated (eg following a new official release of the upstream LMDB library). - -If you do not wish to install Zig and/or use an operating system which cannot -easily execute the `cross-compile.sh` script, you can download the compiled -LMDB native library for your platform from a location of your choice and set the -`lmdbjava.native.lib` system property to the resulting file system -location. Possible sources of a compiled LMDB native library include operating -system package managers, running `cross-compile.sh` on a supported system, or -copying it from the `org/lmdbjava` directory of any recent, officially released -LmdbJava JAR. +LmdbJava uses a standard Maven build. Its native libraries are provided by the +[`org.lmdbjava:native`](https://github.com/lmdbjava/native) dependency. + +To use a different LMDB library, set `lmdbjava.native.lib` system property to the file path. ### Contributing diff --git a/cross-compile.sh b/cross-compile.sh deleted file mode 100755 index 5243feec..00000000 --- a/cross-compile.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/bash -# -# 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. -# - - -set -o errexit - -rm -rf openldap -git clone --depth 1 --branch LMDB_0.9.33 https://git.openldap.org/openldap/openldap.git -pushd openldap/libraries/liblmdb -trap popd SIGINT - -# zig targets | jq -r '.libc[]' -for target in aarch64-linux-gnu \ - aarch64-macos-none \ - x86_64-linux-gnu \ - x86_64-macos-none \ - x86_64-windows-gnu -do - echo "##### Building $target ####" - make -e clean liblmdb.so CC="zig cc -target $target" AR="zig ar" - if [[ "$target" == *-windows-* ]]; then - extension="dll" - else - extension="so" - fi - cp -v liblmdb.so ../../../src/main/resources/org/lmdbjava/$target.$extension -done - -ls -l ../../../src/main/resources/org/lmdbjava diff --git a/pom.xml b/pom.xml index 3cb7ccc5..4d60b903 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,7 @@ 2.2.18 5.14.0 4.6 + 0.9.33-2 3.5.0 3.14.1 3.9.0 @@ -120,6 +121,11 @@ ${junit.version} test + + org.lmdbjava + native + ${lmdbjava-native.version} + org.mockito mockito-inline @@ -407,20 +413,9 @@ - krisskross - Kristoffer Sjogren - stoffe -at- gmail.com - http://stoffe.deephacks.org/ - - +1 - - - benalexau - Ben Alex - ben.alex@acegi.com.au - https://github.com/benalexau/ - Acegi Technology Pty Limited - +10 + lmdbjava + The LmdbJava Open Source Project + https://github.com/lmdbjava diff --git a/src/main/java/org/lmdbjava/TargetName.java b/src/main/java/org/lmdbjava/TargetName.java index 023ecba5..e3bb5db0 100644 --- a/src/main/java/org/lmdbjava/TargetName.java +++ b/src/main/java/org/lmdbjava/TargetName.java @@ -43,7 +43,7 @@ public final class TargetName { * Java system property name that can be set to override the embedded library that will be used. * This is likely to be required if automatic resolution fails but the user still prefers to use * an LmdbJava-bundled library. This path must include the classpath prefix (usually - * org/lmdbjava). + * org/lmdbjava/native). */ public static final String LMDB_EMBEDDED_LIB_PROP = "lmdbjava.embedded.lib"; @@ -94,7 +94,7 @@ static String resolveFilename( final String pkg = TargetName.class.getPackage().getName().replace('.', '/'); return pkg - + "/" + + "/native/" + resolveArch(arch) + "-" + resolveOs(os) diff --git a/src/main/resources/org/lmdbjava/.gitignore b/src/main/resources/org/lmdbjava/.gitignore deleted file mode 100644 index 661f98b3..00000000 --- a/src/main/resources/org/lmdbjava/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.so -*.dll diff --git a/src/test/java/org/lmdbjava/TargetNameTest.java b/src/test/java/org/lmdbjava/TargetNameTest.java index e766aaa6..04caea83 100644 --- a/src/test/java/org/lmdbjava/TargetNameTest.java +++ b/src/test/java/org/lmdbjava/TargetNameTest.java @@ -61,7 +61,7 @@ void externalTakesPriority() { } private void embed(final String lib, final String arch, final String os) { - assertThat(resolveFilename(NONE, NONE, arch, os)).isEqualTo("org/lmdbjava/" + lib); + assertThat(resolveFilename(NONE, NONE, arch, os)).isEqualTo("org/lmdbjava/native/" + lib); assertThat(isExternal(NONE)).isFalse(); } } From dc24f4b6010e3cbcd498a3d227ddde9e5983a265 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 31 Oct 2025 11:20:59 +1100 Subject: [PATCH 061/139] Generalise benchmark link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 77a0c164..adf3fbbf 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ **LmdbJava** adds Java-specific features to LMDB: -* [Extremely fast](https://github.com/lmdbjava/benchmarks/blob/master/results/20160710/README.md) across a broad range of benchmarks, data sizes and access patterns +* [Extremely fast](https://github.com/lmdbjava/benchmarks) across a broad range of benchmarks, data sizes and access patterns * Modern, idiomatic Java API (including iterators, key ranges, enums, exceptions etc) * Nothing to install (the JAR embeds the latest LMDB libraries for Linux, OS X and Windows) * Buffer agnostic (Java `ByteBuffer`, Agrona `DirectBuffer`, Netty `ByteBuf`, your own buffer) From ab6a6a0462a29b8bdc42ca63d506111e4936141f Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Tue, 4 Nov 2025 14:00:22 +1100 Subject: [PATCH 062/139] Resolve write performance regression (fixes #268) Bisection using the LmdbJava Benchmarks project identified a 40% write performance regression introduced in commit 68f0a44: Commit Date Score Change Message =============================================================================== be2a15b 2023-02-04 00:28:16 89.07 [maven-release-plugin] pr 1cc7e6c 2023-12-04 07:42:08 94.49 +6.09% Test on Java 21 f17b63f 2023-12-05 01:34:17 91.57 -3.09% [maven-release-plugin] pr b5dfb25 2025-02-16 23:37:16 91.38 -0.21% Uplift lmdb from 0.9.29 t * 68f0a44 2025-02-16 23:46:30 128.63 +40.76% Add DbiFlags#MDB_UNSIGNED 928cf7b 2025-02-20 01:26:13 129.12 +0.38% Add url element to pom.xm dc24f4b 2025-10-31 00:20:59 122.03 -5.49% Generalise benchmark link Root cause: MaskedFlag.mask() was changed from a simple for-loop to Stream API in commit 68f0a44. This method is invoked once per write operation (Cursor.put() line 255), causing significant overhead from stream creation, lambda allocation, and boxing/unboxing. Fix: Restore for-loop implementation while preserving the onlyPropagatedToLmdb filtering logic added in 68f0a44. Performance verified with 1M entry write benchmark: - Before fix: 122.03 ms/op - After fix: 90.43 ms/op (within 2% of baseline 89.07 ms/op) --- src/main/java/org/lmdbjava/MaskedFlag.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/lmdbjava/MaskedFlag.java b/src/main/java/org/lmdbjava/MaskedFlag.java index 00556ecb..58d67d8c 100644 --- a/src/main/java/org/lmdbjava/MaskedFlag.java +++ b/src/main/java/org/lmdbjava/MaskedFlag.java @@ -75,7 +75,20 @@ static int mask(final Stream flags) { */ @SafeVarargs static int mask(final boolean onlyPropagatedToLmdb, final M... flags) { - return flags == null ? 0 : mask(onlyPropagatedToLmdb, Arrays.stream(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()) { + result |= flag.getMask(); + } + } + return result; } /** 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 063/139] 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. * *