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();