From c178d6ce3719ccd0030f1dc36c616a70e3848e09 Mon Sep 17 00:00:00 2001 From: stroomdev66 Date: Tue, 14 Oct 2025 17:47:06 +0100 Subject: [PATCH] Minimal changes required to implement Foreign Function and Memory API --- pom.xml | 16 +- src/main/java/org/lmdbjava/BufferProxy.java | 18 +- .../java/org/lmdbjava/ByteArrayProxy.java | 43 +- src/main/java/org/lmdbjava/ByteBufProxy.java | 86 +- .../java/org/lmdbjava/ByteBufferProxy.java | 310 +-- src/main/java/org/lmdbjava/Cursor.java | 38 +- src/main/java/org/lmdbjava/Dbi.java | 93 +- .../java/org/lmdbjava/DirectBufferProxy.java | 41 +- src/main/java/org/lmdbjava/Env.java | 121 +- src/main/java/org/lmdbjava/KeyVal.java | 66 +- src/main/java/org/lmdbjava/Library.java | 461 ++--- src/main/java/org/lmdbjava/Lmdb.java | 1678 +++++++++++++++++ .../org/lmdbjava/LmdbInvokeException.java | 51 + src/main/java/org/lmdbjava/Meta.java | 23 +- src/main/java/org/lmdbjava/Txn.java | 42 +- src/main/java/org/lmdbjava/UnsafeAccess.java | 59 - .../UnsignedByteBufferComparator.java | 28 + .../org/lmdbjava/ByteBufferProxyTest.java | 105 +- .../java/org/lmdbjava/ComparatorTest.java | 16 +- .../java/org/lmdbjava/CursorParamTest.java | 11 +- src/test/java/org/lmdbjava/CursorTest.java | 3 +- src/test/java/org/lmdbjava/DbiTest.java | 9 +- src/test/java/org/lmdbjava/LibraryTest.java | 23 +- src/test/java/org/lmdbjava/MetaTest.java | 12 +- src/test/java/org/lmdbjava/TutorialTest.java | 3 +- 25 files changed, 2431 insertions(+), 925 deletions(-) create mode 100644 src/main/java/org/lmdbjava/Lmdb.java create mode 100644 src/main/java/org/lmdbjava/LmdbInvokeException.java delete mode 100644 src/main/java/org/lmdbjava/UnsafeAccess.java create mode 100644 src/main/java/org/lmdbjava/UnsignedByteBufferComparator.java diff --git a/pom.xml b/pom.xml index b3012729..2d4beb25 100644 --- a/pom.xml +++ b/pom.xml @@ -26,9 +26,9 @@ Low latency Java API for the ultra-fast, embedded Symas Lightning Database (LMDB) yyyy-MM-dd'T'HH:mm:ss'Z' - 1.8 - 1.8 - 1.8 + 1.25 + 1.25 + 1.25 3.5.4 UTF-8 false @@ -39,11 +39,6 @@ jnr-constants 0.10.4 - - com.github.jnr - jnr-ffi - 2.2.17 - com.google.guava guava @@ -89,8 +84,8 @@ org.mockito - mockito-inline - 4.11.0 + mockito-core + 5.20.0 test @@ -218,6 +213,7 @@ ${buildNumber} + true diff --git a/src/main/java/org/lmdbjava/BufferProxy.java b/src/main/java/org/lmdbjava/BufferProxy.java index e66031d2..153f1c3b 100644 --- a/src/main/java/org/lmdbjava/BufferProxy.java +++ b/src/main/java/org/lmdbjava/BufferProxy.java @@ -15,14 +15,16 @@ */ package org.lmdbjava; +import org.lmdbjava.Lmdb.MDB_val; + 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.lang.foreign.Arena; import java.util.Comparator; -import jnr.ffi.Pointer; /** * The strategy for mapping memory address to a given buffer type. @@ -107,9 +109,8 @@ 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 */ - protected abstract void in(T buffer, Pointer ptr, long ptrAddr); + protected abstract void in(T buffer, MDB_val ptr); /** * Called when the MDB_val should be set to reflect the passed buffer. @@ -117,27 +118,24 @@ 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 */ - protected abstract void in(T buffer, int size, Pointer ptr, long ptrAddr); + protected abstract void in(T buffer, int size, MDB_val ptr); /** * 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 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(MDB_val ptr); /** * Create a new {@link KeyVal} to hold pointers for this buffer proxy. * * @return a non-null key value holder */ - final KeyVal keyVal() { - return new KeyVal<>(this); + final KeyVal keyVal(final Arena arena) { + return new KeyVal<>(arena,this); } } diff --git a/src/main/java/org/lmdbjava/ByteArrayProxy.java b/src/main/java/org/lmdbjava/ByteArrayProxy.java index 4a22ab83..21d7da3f 100644 --- a/src/main/java/org/lmdbjava/ByteArrayProxy.java +++ b/src/main/java/org/lmdbjava/ByteArrayProxy.java @@ -15,14 +15,17 @@ */ package org.lmdbjava; -import static java.lang.Math.min; -import static java.util.Objects.requireNonNull; -import static org.lmdbjava.Library.RUNTIME; +import org.lmdbjava.Lmdb.MDB_val; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.nio.ByteBuffer; 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; /** * Byte array proxy. @@ -31,15 +34,14 @@ */ public final class ByteArrayProxy extends BufferProxy { - /** 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(); + private final Arena arena; private static final Comparator signedComparator = ByteArrayProxy::compareArraysSigned; private static final Comparator unsignedComparator = ByteArrayProxy::compareArrays; - private ByteArrayProxy() {} + public ByteArrayProxy(final Arena arena) { + this.arena = arena; + } /** * Lexicographically compare two byte arrays. @@ -114,25 +116,22 @@ protected Comparator getUnsignedComparator() { } @Override - 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); - ptr.putAddress(STRUCT_FIELD_OFFSET_DATA, pointer.address()); + protected void in(final byte[] buffer, final MDB_val ptr) { + final MemorySegment segment = arena.allocateFrom(ValueLayout.JAVA_BYTE, buffer); + ptr.mvSize(buffer.length); + ptr.mvData(segment); } @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 MDB_val ptr) { // cannot reserve for byte arrays } @Override - 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); - final byte[] bytes = new byte[size]; - pointer.get(0, bytes, 0, size); + protected byte[] out(final MDB_val ptr) { + final ByteBuffer byteBuffer = ptr.mvData().reinterpret(ptr.mvSize()).asByteBuffer(); + final byte[] bytes = new byte[byteBuffer.remaining()]; + byteBuffer.get(0, bytes, 0, byteBuffer.remaining()); return bytes; } } diff --git a/src/main/java/org/lmdbjava/ByteBufProxy.java b/src/main/java/org/lmdbjava/ByteBufProxy.java index cac5b97b..a62ac0be 100644 --- a/src/main/java/org/lmdbjava/ByteBufProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufProxy.java @@ -15,21 +15,22 @@ */ package org.lmdbjava; -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 io.netty.buffer.Unpooled; +import org.lmdbjava.Lmdb.MDB_val; + +import java.lang.foreign.MemorySegment; +import java.nio.ByteBuffer; import java.util.Comparator; -import jnr.ffi.Pointer; + +import static io.netty.buffer.PooledByteBufAllocator.DEFAULT; +import static java.util.Objects.requireNonNull; /** * 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 netty-buffer in the classpath. */ public final class ByteBufProxy extends BufferProxy { @@ -41,18 +42,14 @@ public final class ByteBufProxy extends BufferProxy { public static final BufferProxy PROXY_NETTY = new ByteBufProxy(); private static final int BUFFER_RETRIES = 10; - 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); + (o1, o2) -> { + requireNonNull(o1); + requireNonNull(o2); - return o1.compareTo(o2); - }; - private final long lengthOffset; - private final long addressOffset; + return o1.compareTo(o2); + }; private final PooledByteBufAllocator nettyAllocator; @@ -68,36 +65,6 @@ private ByteBufProxy() { public ByteBufProxy(final PooledByteBufAllocator allocator) { super(); this.nettyAllocator = allocator; - - try { - final ByteBuf initBuf = this.allocate(); - initBuf.release(); - final Field address = findField(NAME, FIELD_NAME_ADDRESS); - final Field length = findField(NAME, FIELD_NAME_LENGTH); - addressOffset = UNSAFE.objectFieldOffset(address); - lengthOffset = UNSAFE.objectFieldOffset(length); - } catch (final SecurityException e) { - throw new LmdbException("Field access error", e); - } - } - - static Field findField(final String c, final String name) { - Class clazz; - try { - clazz = forName(c); - } catch (final ClassNotFoundException e) { - throw new LmdbException(c + " class unavailable", e); - } - do { - try { - final Field field = clazz.getDeclaredField(name); - field.setAccessible(true); - return field; - } catch (final NoSuchFieldException e) { - clazz = clazz.getSuperclass(); - } - } while (clazz != null); - throw new LmdbException(name + " not found"); } @Override @@ -136,26 +103,21 @@ 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()); + protected void in(final ByteBuf buffer, final MDB_val ptr) { + ptr.mvSize(buffer.writerIndex() - buffer.readerIndex()); + ptr.mvData(MemorySegment.ofBuffer(buffer.nioBuffer())); } @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 MDB_val ptr) { + ptr.mvSize(size); + ptr.mvData(MemorySegment.ofBuffer(buffer.nioBuffer())); } @Override - 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); - UNSAFE.putInt(buffer, lengthOffset, (int) size); - buffer.clear().writerIndex((int) size); - return buffer; + protected ByteBuf out(final MDB_val ptr) { + final long size = ptr.mvSize(); + final ByteBuffer byteBuffer = ptr.mvData().reinterpret(size).asByteBuffer(); + return Unpooled.wrappedBuffer(byteBuffer); } } diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index 2b7cdf0d..8454ce8b 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -15,280 +15,118 @@ */ package org.lmdbjava; -import static java.lang.Long.reverseBytes; +import org.lmdbjava.Lmdb.MDB_val; + +import java.lang.foreign.MemorySegment; +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.nio.ByteOrder.LITTLE_ENDIAN; import static java.util.Objects.requireNonNull; import static org.lmdbjava.Env.SHOULD_CHECK; -import static org.lmdbjava.UnsafeAccess.UNSAFE; - -import java.lang.reflect.Field; -import java.nio.Buffer; -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: - * - *

    - *
  • 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)}. */ -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. - */ - public static final BufferProxy PROXY_OPTIMAL; - - /** 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(); - } +public final class ByteBufferProxy extends BufferProxy { - private ByteBufferProxy() {} + static final ByteBufferProxy INSTANCE = new ByteBufferProxy(); - private static BufferProxy getProxyOptimal() { - try { - return new UnsafeProxy(); - } catch (final RuntimeException e) { - return PROXY_SAFE; - } + private ByteBufferProxy() { } - /** 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. - */ - 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; - - /** - * 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)); - - /** - * Lexicographically compare two buffers. - * - * @param o1 left operand (required) - * @param o2 right operand (required) - * @return as specified by {@link Comparable} interface - */ - 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; - - final boolean reverse1 = o1.order() == LITTLE_ENDIAN; - final boolean reverse2 = o2.order() == LITTLE_ENDIAN; - for (int i = 0; i < minWords * Long.BYTES; i += Long.BYTES) { - final long lw = reverse1 ? reverseBytes(o1.getLong(i)) : o1.getLong(i); - final long rw = reverse2 ? reverseBytes(o2.getLong(i)) : o2.getLong(i); - final int diff = Long.compareUnsigned(lw, rw); - if (diff != 0) { - return diff; - } - } - - for (int i = minWords * Long.BYTES; i < minLength; i++) { - final int lw = Byte.toUnsignedInt(o1.get(i)); - final int rw = Byte.toUnsignedInt(o2.get(i)); - final int result = Integer.compareUnsigned(lw, rw); - if (result != 0) { - return result; - } - } - - return o1.remaining() - o2.remaining(); - } - - static Field findField(final Class c, final String name) { - Class clazz = c; - do { - try { - final Field field = clazz.getDeclaredField(name); - field.setAccessible(true); - return field; - } catch (final NoSuchFieldException e) { - clazz = clazz.getSuperclass(); - } - } while (clazz != null); - throw new LmdbException(name + " not found"); - } - - protected final long address(final ByteBuffer buffer) { - if (SHOULD_CHECK && !buffer.isDirect()) { - throw new BufferMustBeDirectException(); - } - return ((sun.nio.ch.DirectBuffer) buffer).address() + buffer.position(); - } - - @Override - protected final ByteBuffer allocate() { - final ArrayDeque queue = BUFFERS.get(); - final ByteBuffer buffer = queue.poll(); - - if (buffer != null && buffer.capacity() >= 0) { - return buffer; - } else { - return allocateDirect(0); - } - } - - @Override - protected Comparator getSignedComparator() { - return signedComparator; - } - - @Override - protected Comparator getUnsignedComparator() { - return unsignedComparator; - } - - @Override - protected final void deallocate(final ByteBuffer buff) { - buff.order(BIG_ENDIAN); - final ArrayDeque queue = BUFFERS.get(); - queue.offer(buff); - } - - @Override - protected byte[] getBytes(final ByteBuffer buffer) { - final byte[] dest = new byte[buffer.limit()]; - buffer.get(dest, 0, buffer.limit()); - return dest; - } - } + return o1.compareTo(o2); + }; + private static final Comparator unsignedComparator = + new UnsignedByteBufferComparator(); /** - * A proxy that uses Java reflection to modify byte buffer fields, and official JNR-FFF methods to - * manipulate native pointers. + * 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 class ReflectiveProxy extends AbstractByteBufferProxy { + private static final ThreadLocal> BUFFERS = + withInitial(() -> new ArrayDeque<>(16)); - private static final Field ADDRESS_FIELD; - private static final Field CAPACITY_FIELD; + @Override + protected ByteBuffer allocate() { + final ArrayDeque queue = BUFFERS.get(); + final ByteBuffer buffer = queue.poll(); - static { - ADDRESS_FIELD = findField(Buffer.class, FIELD_NAME_ADDRESS); - CAPACITY_FIELD = findField(Buffer.class, FIELD_NAME_CAPACITY); - } - - @Override - 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) { - 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) { - final long addr = ptr.getAddress(STRUCT_FIELD_OFFSET_DATA); - final long size = ptr.getLong(STRUCT_FIELD_OFFSET_SIZE); - try { - ADDRESS_FIELD.set(buffer, addr); - CAPACITY_FIELD.set(buffer, (int) size); - } catch (final IllegalArgumentException | IllegalAccessException e) { - throw new LmdbException("Cannot modify buffer", e); - } - buffer.clear(); + if (buffer != null && buffer.capacity() >= 0) { return buffer; + } else { + return allocateDirect(0); } } - /** - * 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 { + @Override + protected Comparator getSignedComparator() { + return signedComparator; + } - private static final long ADDRESS_OFFSET; - private static final long CAPACITY_OFFSET; + @Override + protected Comparator getUnsignedComparator() { + return unsignedComparator; + } - static { - try { - final Field address = findField(Buffer.class, FIELD_NAME_ADDRESS); - final Field capacity = findField(Buffer.class, FIELD_NAME_CAPACITY); - ADDRESS_OFFSET = UNSAFE.objectFieldOffset(address); - CAPACITY_OFFSET = UNSAFE.objectFieldOffset(capacity); - } catch (final SecurityException e) { - throw new LmdbException("Field access error", e); - } - } + @Override + protected void deallocate(final ByteBuffer buff) { + buff.order(BIG_ENDIAN); + final ArrayDeque queue = BUFFERS.get(); + queue.offer(buff); + } - @Override - 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 byte[] getBytes(final ByteBuffer buffer) { + final byte[] dest = new byte[buffer.limit()]; + buffer.get(dest); + return dest; + } - @Override - 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 void in(final ByteBuffer buffer, final MDB_val ptr) { + if (SHOULD_CHECK && !buffer.isDirect()) { + throw new BufferMustBeDirectException(); } + ptr.mvSize(buffer.remaining()); + ptr.mvData(MemorySegment.ofBuffer(buffer)); + } - @Override - 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); - UNSAFE.putInt(buffer, CAPACITY_OFFSET, (int) size); - buffer.clear(); - return buffer; + @Override + protected void in(final ByteBuffer buffer, final int size, final MDB_val ptr) { + if (SHOULD_CHECK && !buffer.isDirect()) { + throw new BufferMustBeDirectException(); } + ptr.mvSize(size); + ptr.mvData(MemorySegment.ofBuffer(buffer)); + } + + @Override + protected ByteBuffer out(final MDB_val ptr) { + final long size = ptr.mvSize(); + return ptr.mvData().reinterpret(size).asByteBuffer(); } } diff --git a/src/main/java/org/lmdbjava/Cursor.java b/src/main/java/org/lmdbjava/Cursor.java index d49a9bed..38392448 100644 --- a/src/main/java/org/lmdbjava/Cursor.java +++ b/src/main/java/org/lmdbjava/Cursor.java @@ -19,7 +19,6 @@ import static org.lmdbjava.Dbi.KeyExistsException.MDB_KEYEXIST; 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; @@ -32,8 +31,9 @@ import static org.lmdbjava.SeekOp.MDB_NEXT; import static org.lmdbjava.SeekOp.MDB_PREV; -import jnr.ffi.Pointer; -import jnr.ffi.byref.NativeLongByReference; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; /** * A cursor handle. @@ -42,15 +42,17 @@ */ public final class Cursor implements AutoCloseable { + private final Arena arena; private boolean closed; private final KeyVal kv; - private final Pointer ptrCursor; + private final MemorySegment ptrCursor; private Txn txn; private final Env env; - Cursor(final Pointer ptr, final Txn txn, final Env env) { + Cursor(final Arena arena, final MemorySegment ptr, final Txn txn, final Env env) { requireNonNull(ptr); requireNonNull(txn); + this.arena = arena; this.ptrCursor = ptr; this.txn = txn; this.kv = txn.newKeyVal(); @@ -75,7 +77,7 @@ public void close() { txn.checkReady(); } } - LIB.mdb_cursor_close(ptrCursor); + Lmdb.mdb_cursor_close(ptrCursor); closed = true; } @@ -93,9 +95,9 @@ public long count() { checkNotClosed(); txn.checkReady(); } - final NativeLongByReference longByReference = new NativeLongByReference(); - checkRc(LIB.mdb_cursor_count(ptrCursor, longByReference)); - return longByReference.longValue(); + MemorySegment longByReference = arena.allocate(ValueLayout.JAVA_LONG); + checkRc(Lmdb.mdb_cursor_count(ptrCursor, longByReference)); + return longByReference.get(ValueLayout.JAVA_LONG, 0); } /** @@ -113,7 +115,7 @@ public void delete(final PutFlags... f) { txn.checkWritesAllowed(); } final int flags = mask(true, f); - checkRc(LIB.mdb_cursor_del(ptrCursor, flags)); + checkRc(Lmdb.mdb_cursor_del(ptrCursor, flags)); } /** @@ -144,7 +146,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 = Lmdb.mdb_cursor_get(ptrCursor, kv.pointerKey(), kv.pointerVal(), op.getCode()); if (rc == MDB_NOTFOUND) { return false; @@ -174,7 +176,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 = Lmdb.mdb_cursor_get(ptrCursor, kv.pointerKey(), kv.pointerVal(), op.getCode()); if (rc == MDB_NOTFOUND) { return false; @@ -246,7 +248,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 = Lmdb.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 @@ -288,8 +290,8 @@ public void putMultiple(final T key, final T val, final int elements, final PutF throw new IllegalArgumentException("Must set " + MDB_MULTIPLE + " flag"); } 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 Lmdb.MDB_val dataPtr = txn.kv().valInMulti(val, elements); + final int rc = Lmdb.mdb_cursor_put(ptrCursor, txn.kv().pointerKey(), dataPtr, mask); checkRc(rc); ReferenceUtil.reachabilityFence0(key); ReferenceUtil.reachabilityFence0(val); @@ -314,7 +316,7 @@ public void renew(final Txn newTxn) { newTxn.checkReadOnly(); newTxn.checkReady(); } - checkRc(LIB.mdb_cursor_renew(newTxn.pointer(), ptrCursor)); + checkRc(Lmdb.mdb_cursor_renew(newTxn.pointer(), ptrCursor)); this.txn = newTxn; } @@ -343,7 +345,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(Lmdb.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), flags)); kv.valOut(); ReferenceUtil.reachabilityFence0(key); return val(); @@ -363,7 +365,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 = Lmdb.mdb_cursor_get(ptrCursor, kv.pointerKey(), kv.pointerVal(), op.getCode()); if (rc == MDB_NOTFOUND) { return false; diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index ad1bb5a7..86d45a14 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -16,14 +16,10 @@ package org.lmdbjava; import static java.util.Objects.requireNonNull; -import static jnr.ffi.Memory.allocateDirect; -import static jnr.ffi.NativeType.ADDRESS; import static org.lmdbjava.Dbi.KeyExistsException.MDB_KEYEXIST; import static org.lmdbjava.Dbi.KeyNotFoundException.MDB_NOTFOUND; import static org.lmdbjava.Env.SHOULD_CHECK; import static org.lmdbjava.KeyRange.all; -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.PutFlags.MDB_NODUPDATA; @@ -31,15 +27,15 @@ import static org.lmdbjava.PutFlags.MDB_RESERVE; import static org.lmdbjava.ResultCodeMapper.checkRc; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; 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; + +import org.lmdbjava.Lmdb.MDB_val; /** * LMDB Database. @@ -48,14 +44,16 @@ */ public final class Dbi { - private final ComparatorCallback ccb; + private final Arena arena; + private final Lmdb.ComparatorCallback ccb; private boolean cleaned; private final Comparator comparator; private final Env env; private final byte[] name; - private final Pointer ptr; + private final int ptr; Dbi( + final Arena arena, final Env env, final Txn txn, final byte[] name, @@ -67,6 +65,7 @@ public final class Dbi { requireNonNull(txn); txn.checkReady(); } + this.arena = arena; this.env = env; this.name = name == null ? null : Arrays.copyOf(name, name.length); if (comparator == null) { @@ -75,22 +74,28 @@ public final class Dbi { 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); + + final MemorySegment nameSegment; + if (name != null) { + // Ensure null termination for C style strings. + nameSegment = arena.allocate(name.length + 1); + MemorySegment.copy(name, 0, nameSegment, ValueLayout.JAVA_BYTE, 0, name.length); + nameSegment.set(ValueLayout.JAVA_BYTE, name.length, (byte) 0); + } else { + nameSegment = MemorySegment.NULL; + } + + final MemorySegment dbiPtr = arena.allocate(ValueLayout.JAVA_INT); + checkRc(Lmdb.mdb_dbi_open(txn.pointer(), nameSegment, flagsMask, dbiPtr)); + ptr = dbiPtr.get(ValueLayout.JAVA_INT, 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; + final T compKeyA = proxy.out(keyA); + final T compKeyB = proxy.out(keyB); + return this.comparator.compare(compKeyA, compKeyB); }; - LIB.mdb_set_compare(txn.pointer(), ptr, ccb); + Lmdb.mdb_set_compare(txn.pointer(), ptr, ccb, arena); } else { ccb = null; } @@ -110,7 +115,7 @@ public void close() { if (SHOULD_CHECK) { env.checkNotClosed(); } - LIB.mdb_dbi_close(env.pointer(), ptr); + Lmdb.mdb_dbi_close(env.pointer(), ptr); } /** @@ -163,12 +168,12 @@ public boolean delete(final Txn txn, final T key, final T val) { txn.kv().keyIn(key); - Pointer data = null; + MDB_val data = null; if (val != null) { txn.kv().valIn(val); data = txn.kv().pointerVal(); } - final int rc = LIB.mdb_del(txn.pointer(), ptr, txn.kv().pointerKey(), data); + final int rc = Lmdb.mdb_del(txn.pointer(), ptr, txn.kv().pointerKey(), data); if (rc == MDB_NOTFOUND) { return false; } @@ -209,7 +214,7 @@ public void drop(final Txn txn, final boolean delete) { clean(); } final int del = delete ? 1 : 0; - checkRc(LIB.mdb_drop(txn.pointer(), ptr, del)); + checkRc(Lmdb.mdb_drop(txn.pointer(), ptr, del)); } /** @@ -233,7 +238,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 = Lmdb.mdb_get(txn.pointer(), ptr, txn.kv().pointerKey(), txn.kv().pointerVal()); if (rc == MDB_NOTFOUND) { return null; } @@ -288,10 +293,10 @@ public List listFlags(final Txn txn) { if (SHOULD_CHECK) { env.checkNotClosed(); } - final IntByReference resultPtr = new IntByReference(); - checkRc(LIB.mdb_dbi_flags(txn.pointer(), ptr, resultPtr)); + MemorySegment resultPtr = arena.allocate(ValueLayout.JAVA_INT); + checkRc(Lmdb.mdb_dbi_flags(txn.pointer(), ptr, resultPtr)); - final int flags = resultPtr.intValue(); + final int flags = resultPtr.get(ValueLayout.JAVA_INT, 0); final List result = new ArrayList<>(); @@ -324,9 +329,9 @@ public Cursor openCursor(final Txn txn) { env.checkNotClosed(); txn.checkReady(); } - final PointerByReference cursorPtr = new PointerByReference(); - checkRc(LIB.mdb_cursor_open(txn.pointer(), ptr, cursorPtr)); - return new Cursor<>(cursorPtr.getValue(), txn, env); + MemorySegment cursorPtr = arena.allocate(ValueLayout.ADDRESS); + checkRc(Lmdb.mdb_cursor_open(txn.pointer(), ptr, cursorPtr)); + return new Cursor<>(arena, cursorPtr.get(ValueLayout.ADDRESS, 0), txn, env); } /** @@ -370,7 +375,7 @@ public boolean put(final Txn txn, final T key, final T val, final PutFlags... 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); + Lmdb.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 @@ -411,7 +416,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(); - checkRc(LIB.mdb_put(txn.pointer(), ptr, txn.kv().pointerKey(), txn.kv().pointerVal(), flags)); + checkRc(Lmdb.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(); @@ -429,15 +434,15 @@ public Stat stat(final Txn txn) { env.checkNotClosed(); txn.checkReady(); } - final MDB_stat stat = new MDB_stat(RUNTIME); - checkRc(LIB.mdb_stat(txn.pointer(), ptr, stat)); + final Lmdb.MDB_stat stat = new Lmdb.MDB_stat(arena); + checkRc(Lmdb.mdb_stat(txn.pointer(), ptr, stat)); return new Stat( - stat.f0_ms_psize.intValue(), - stat.f1_ms_depth.intValue(), - stat.f2_ms_branch_pages.longValue(), - stat.f3_ms_leaf_pages.longValue(), - stat.f4_ms_overflow_pages.longValue(), - stat.f5_ms_entries.longValue()); + stat.msPsize(), + stat.msDepth(), + stat.msBranchPages(), + stat.msLeafPages(), + stat.msOverflowPages(), + stat.msEntries()); } private void clean() { diff --git a/src/main/java/org/lmdbjava/DirectBufferProxy.java b/src/main/java/org/lmdbjava/DirectBufferProxy.java index 156e60e9..6f4c2b26 100644 --- a/src/main/java/org/lmdbjava/DirectBufferProxy.java +++ b/src/main/java/org/lmdbjava/DirectBufferProxy.java @@ -19,12 +19,12 @@ 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.lang.foreign.MemorySegment; import java.nio.ByteBuffer; import java.util.ArrayDeque; import java.util.Comparator; -import jnr.ffi.Pointer; +import org.lmdbjava.Lmdb.MDB_val; import org.agrona.DirectBuffer; import org.agrona.MutableDirectBuffer; import org.agrona.concurrent.UnsafeBuffer; @@ -32,7 +32,7 @@ /** * A buffer proxy backed by Agrona's {@link DirectBuffer}. * - *

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

This class requires Agrona in the classpath. */ public final class DirectBufferProxy extends BufferProxy { private static final Comparator signedComparator = @@ -134,26 +134,35 @@ protected byte[] getBytes(final DirectBuffer buffer) { } @Override - 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); - UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, size); + protected void in(final DirectBuffer buffer, final MDB_val ptr) { + ptr.mvSize(buffer.capacity()); + ptr.mvData(fromAgronaAddress(buffer)); } @Override 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); + final DirectBuffer buffer, final int size, final MDB_val ptr) { + ptr.mvSize(size); + ptr.mvData(fromAgronaAddress(buffer)); } @Override - 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); + protected DirectBuffer out(final MDB_val ptr) { + final MutableDirectBuffer buffer = new UnsafeBuffer(0, 0); + buffer.wrap(ptr.mvData().address(), (int) ptr.mvSize()); return buffer; } + + /** + * Convert DirectBuffer to MemorySegment using native address + */ + public static MemorySegment fromAgronaAddress(DirectBuffer buffer) { + // Get the native address from Agrona buffer + long address = buffer.addressOffset(); + int capacity = buffer.capacity(); + + // Create MemorySegment from address + // Note: This requires the address to remain valid + return MemorySegment.ofAddress(address).reinterpret(capacity); + } } diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index 3db16119..b09004f0 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -15,30 +15,28 @@ */ package org.lmdbjava; +import org.lmdbjava.Lmdb.MDB_envinfo; +import org.lmdbjava.Lmdb.MDB_stat; + import static java.lang.Boolean.getBoolean; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; -import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; 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.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; 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. @@ -60,30 +58,33 @@ public final class Env implements AutoCloseable { private boolean closed; private final int maxKeySize; private final boolean noSubDir; + private final Arena arena; private final BufferProxy proxy; - private final Pointer ptr; + private final MemorySegment ptr; private final boolean readOnly; private Env( + final Arena arena, final BufferProxy proxy, - final Pointer ptr, + final MemorySegment ptr, final boolean readOnly, final boolean noSubDir) { + this.arena = arena; this.proxy = proxy; this.readOnly = readOnly; this.noSubDir = noSubDir; this.ptr = ptr; // cache max key size to avoid further JNI calls - this.maxKeySize = LIB.mdb_env_get_maxkeysize(ptr); + this.maxKeySize = Lmdb.mdb_env_get_maxkeysize(ptr); } /** - * Create an {@link Env} using the {@link ByteBufferProxy#PROXY_OPTIMAL}. + * Create an {@link Env} using the {@link ByteBufferProxy}. * * @return the environment (never null) */ public static Builder create() { - return new Builder<>(PROXY_OPTIMAL); + return new Builder<>(ByteBufferProxy.INSTANCE); } /** @@ -99,7 +100,7 @@ public static Builder create(final BufferProxy proxy) { /** * Opens an environment with a single default database in 0664 mode using the {@link - * ByteBufferProxy#PROXY_OPTIMAL}. + * ByteBufferProxy}. * * @param path file system destination * @param size size in megabytes @@ -107,7 +108,7 @@ public static Builder create(final BufferProxy proxy) { * @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<>(ByteBufferProxy.INSTANCE).setMapSize(size * 1_024L * 1_024L).open(path, flags); } /** @@ -121,7 +122,7 @@ public void close() { return; } closed = true; - LIB.mdb_env_close(ptr); + Lmdb.mdb_env_close(ptr); } /** @@ -146,7 +147,8 @@ public void copy(final File path, final CopyFlags... flags) { requireNonNull(path); validatePath(path); final int flagsMask = mask(true, flags); - checkRc(LIB.mdb_env_copy2(ptr, path.getAbsolutePath(), flagsMask)); + final MemorySegment pathSegment = arena.allocateFrom(path.getAbsolutePath()); + checkRc(Lmdb.mdb_env_copy2(ptr, pathSegment, flagsMask)); } /** @@ -183,7 +185,7 @@ public List getDbiNames() { * @param mapSize the new size, in bytes */ public void setMapSize(final long mapSize) { - checkRc(LIB.mdb_env_set_mapsize(ptr, mapSize)); + checkRc(Lmdb.mdb_env_set_mapsize(ptr, mapSize)); } /** @@ -204,23 +206,23 @@ public EnvInfo info() { if (closed) { throw new AlreadyClosedException(); } - final MDB_envinfo info = new MDB_envinfo(RUNTIME); - checkRc(LIB.mdb_env_info(ptr, info)); + final MDB_envinfo info = new MDB_envinfo(arena); + checkRc(Lmdb.mdb_env_info(ptr, info)); final long mapAddress; - if (info.f0_me_mapaddr.get() == null) { + if (info.meMapaddr() == null) { mapAddress = 0; } else { - mapAddress = info.f0_me_mapaddr.get().address(); + mapAddress = info.meMapaddr().address(); } return new EnvInfo( mapAddress, - info.f1_me_mapsize.longValue(), - info.f2_me_last_pgno.longValue(), - info.f3_me_last_txnid.longValue(), - info.f4_me_maxreaders.intValue(), - info.f5_me_numreaders.intValue()); + info.meMapsize(), + info.meLastPgno(), + info.meLastTxnid(), + info.meMaxreaders(), + info.meNumreaders()); } /** @@ -372,7 +374,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<>(arena, this, txn, name, comparator, nativeCb, proxy, flags); } /** @@ -384,15 +386,15 @@ public Stat stat() { if (closed) { throw new AlreadyClosedException(); } - final MDB_stat stat = new MDB_stat(RUNTIME); - checkRc(LIB.mdb_env_stat(ptr, stat)); + final MDB_stat stat = new MDB_stat(arena); + checkRc(Lmdb.mdb_env_stat(ptr, stat)); return new Stat( - stat.f0_ms_psize.intValue(), - stat.f1_ms_depth.intValue(), - stat.f2_ms_branch_pages.longValue(), - stat.f3_ms_leaf_pages.longValue(), - stat.f4_ms_overflow_pages.longValue(), - stat.f5_ms_entries.longValue()); + stat.msPsize(), + stat.msDepth(), + stat.msBranchPages(), + stat.msLeafPages(), + stat.msOverflowPages(), + stat.msEntries()); } /** @@ -406,7 +408,7 @@ public void sync(final boolean force) { throw new AlreadyClosedException(); } final int f = force ? 1 : 0; - checkRc(LIB.mdb_env_sync(ptr, f)); + checkRc(Lmdb.mdb_env_sync(ptr, f)); } /** @@ -420,7 +422,7 @@ public Txn txn(final Txn parent, final TxnFlags... flags) { if (closed) { throw new AlreadyClosedException(); } - return new Txn<>(this, parent, proxy, flags); + return new Txn<>(arena, this, parent, proxy, flags); } /** @@ -441,7 +443,7 @@ public Txn txnWrite() { return txn(null); } - Pointer pointer() { + MemorySegment pointer() { return ptr; } @@ -480,9 +482,9 @@ private void validatePath(final File path) { * @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(); + MemorySegment resultPtr = arena.allocate(ValueLayout.JAVA_INT); + checkRc(Lmdb.mdb_reader_check(ptr, resultPtr)); + return resultPtr.get(ValueLayout.JAVA_INT, 0); } /** Object has already been closed and the operation is therefore prohibited. */ @@ -540,21 +542,28 @@ public Env open(final File path, final int mode, final EnvFlags... flags) { throw new AlreadyOpenException(); } opened = true; - final PointerByReference envPtr = new PointerByReference(); - checkRc(LIB.mdb_env_create(envPtr)); - final Pointer ptr = envPtr.getValue(); + final Arena arena = Arena.ofShared(); try { - 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)); - return new Env<>(proxy, ptr, readOnly, noSubDir); - } catch (final LmdbNativeException e) { - LIB.mdb_env_close(ptr); - throw e; + final MemorySegment envPtr = arena.allocate(ValueLayout.ADDRESS); + checkRc(Lmdb.mdb_env_create(envPtr)); + final MemorySegment ptr = envPtr.get(ValueLayout.ADDRESS, 0); + try { + checkRc(Lmdb.mdb_env_set_mapsize(ptr, mapSize)); + checkRc(Lmdb.mdb_env_set_maxdbs(ptr, maxDbs)); + checkRc(Lmdb.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); + final MemorySegment pathSegment = arena.allocateFrom(path.getAbsolutePath()); + checkRc(Lmdb.mdb_env_open(ptr, pathSegment, flagsMask, mode)); + return new Env<>(arena, proxy, ptr, readOnly, noSubDir); + } catch (final LmdbNativeException e) { + Lmdb.mdb_env_close(ptr); + throw e; + } + } catch (final RuntimeException e) { + arena.close(); + throw e; } } diff --git a/src/main/java/org/lmdbjava/KeyVal.java b/src/main/java/org/lmdbjava/KeyVal.java index c6f2aae5..670d30b6 100644 --- a/src/main/java/org/lmdbjava/KeyVal.java +++ b/src/main/java/org/lmdbjava/KeyVal.java @@ -15,13 +15,12 @@ */ package org.lmdbjava; -import static java.util.Objects.requireNonNull; -import static org.lmdbjava.BufferProxy.MDB_VAL_STRUCT_SIZE; -import static org.lmdbjava.BufferProxy.STRUCT_FIELD_OFFSET_SIZE; -import static org.lmdbjava.Library.RUNTIME; +import org.lmdbjava.Lmdb.MDB_arr_val; +import org.lmdbjava.Lmdb.MDB_val; + +import java.lang.foreign.Arena; -import jnr.ffi.Pointer; -import jnr.ffi.provider.MemoryManager; +import static java.util.Objects.requireNonNull; /** * Represents off-heap memory holding a key and value pair. @@ -30,27 +29,23 @@ */ final class KeyVal 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; - private final Pointer ptrVal; - private final long ptrValAddr; + private final MDB_arr_val ptrArray; + private final MDB_val ptrKey; + private final MDB_val ptrVal; private T v; - KeyVal(final BufferProxy proxy) { + KeyVal(final Arena arena, + final BufferProxy proxy) { requireNonNull(proxy); this.proxy = proxy; 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(); + ptrKey = new MDB_val(arena); + ptrArray = new MDB_arr_val(arena); + ptrVal = new MDB_val(ptrArray.segment()); } @Override @@ -68,19 +63,19 @@ T key() { } void keyIn(final T key) { - proxy.in(key, ptrKey, ptrKeyAddr); + proxy.in(key, ptrKey); } T keyOut() { - k = proxy.out(k, ptrKey, ptrKeyAddr); + k = proxy.out(ptrKey); return k; } - Pointer pointerKey() { + MDB_val pointerKey() { return ptrKey; } - Pointer pointerVal() { + MDB_val pointerVal() { return ptrVal; } @@ -89,11 +84,11 @@ T val() { } void valIn(final T val) { - proxy.in(val, ptrVal, ptrValAddr); + proxy.in(val, ptrVal); } void valIn(final int size) { - proxy.in(v, size, ptrVal, ptrValAddr); + proxy.in(v, size, ptrVal); } /** @@ -103,29 +98,28 @@ void valIn(final int size) { *

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 + *
  • ptrVal1.data = pointer to the data address of passed buffer *
  • ptrVal2.size = number of data elements (as passed to this method) + *
  • ptrVal2.data = unused *
* - * @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 */ - 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 - final long totalBufferSize = ptrVal.getLong(STRUCT_FIELD_OFFSET_SIZE); + MDB_val valInMulti(final T val, final int elements) { + ptrArray.mvSize2(elements); // ptrVal2.size + proxy.in(val, ptrVal); // ptrVal1.data + final long totalBufferSize = ptrVal.mvSize(); final long elemSize = totalBufferSize / elements; - ptrVal.putLong(STRUCT_FIELD_OFFSET_SIZE, elemSize); // ptrVal1.size - - return ptrArray; + ptrArray.mvSize1(elemSize); // ptrVal1.size + // Return ptrVal as both array and value share same address. + return ptrVal; } T valOut() { - v = proxy.out(v, ptrVal, ptrValAddr); + v = proxy.out(ptrVal); return v; } } diff --git a/src/main/java/org/lmdbjava/Library.java b/src/main/java/org/lmdbjava/Library.java index ef9b9b35..07694234 100644 --- a/src/main/java/org/lmdbjava/Library.java +++ b/src/main/java/org/lmdbjava/Library.java @@ -15,27 +15,32 @@ */ package org.lmdbjava; -import static java.io.File.createTempFile; -import static java.lang.System.getProperty; -import static java.lang.Thread.currentThread; -import static java.util.Objects.requireNonNull; -import static jnr.ffi.LibraryLoader.create; -import static jnr.ffi.Runtime.getRuntime; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.file.Files; -import jnr.ffi.Pointer; -import jnr.ffi.Struct; -import jnr.ffi.annotations.Delegate; -import jnr.ffi.annotations.In; -import jnr.ffi.annotations.Out; -import jnr.ffi.byref.IntByReference; -import jnr.ffi.byref.NativeLongByReference; -import jnr.ffi.byref.PointerByReference; -import jnr.ffi.types.size_t; +//import static java.io.File.createTempFile; +//import static java.lang.System.getProperty; +//import static java.lang.Thread.currentThread; +//import static java.util.Objects.requireNonNull; +////import static jnr.ffi.LibraryLoader.create; +////import static jnr.ffi.Runtime.getRuntime; +// +//import java.io.File; +//import java.io.IOException; +//import java.io.InputStream; +//import java.io.OutputStream; +//import java.lang.foreign.Arena; +//import java.lang.foreign.Linker; +//import java.lang.foreign.SymbolLookup; +//import java.nio.file.Files; +//import java.nio.file.Path; +//import java.nio.file.Paths; +////import jnr.ffi.Pointer; +////import jnr.ffi.Struct; +////import jnr.ffi.annotations.Delegate; +////import jnr.ffi.annotations.In; +////import jnr.ffi.annotations.Out; +////import jnr.ffi.byref.IntByReference; +////import jnr.ffi.byref.NativeLongByReference; +////import jnr.ffi.byref.PointerByReference; +////import jnr.ffi.types.size_t; /** * JNR-FFI interface to LMDB. @@ -43,198 +48,224 @@ *

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). - */ - 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")); - - static final Lmdb LIB; - static final jnr.ffi.Runtime RUNTIME; - - static { - final String libToLoad; - - if (TargetName.IS_EXTERNAL) { - libToLoad = TargetName.RESOLVED_FILENAME; - } else { - libToLoad = extract(TargetName.RESOLVED_FILENAME); - } - - LIB = create(Lmdb.class).load(libToLoad); - RUNTIME = getRuntime(LIB); - } - - private Library() {} - - private static String extract(final String name) { - final String suffix = name.substring(name.lastIndexOf('.')); - final File file; - try { - final File dir = new File(EXTRACT_DIR); - if (!dir.exists() || !dir.isDirectory()) { - throw new IllegalStateException("Invalid extraction directory " + dir); - } - file = createTempFile("lmdbjava-native-library-", suffix, dir); - file.deleteOnExit(); - final ClassLoader cl = currentThread().getContextClassLoader(); - try (InputStream in = cl.getResourceAsStream(name); - OutputStream out = Files.newOutputStream(file.toPath())) { - requireNonNull(in, "Classpath resource not found"); - int bytes; - final byte[] buffer = new byte[4_096]; - while (-1 != (bytes = in.read(buffer))) { - out.write(buffer, 0, bytes); - } - } - return file.getAbsolutePath(); - } catch (final IOException e) { - throw new LmdbException("Failed to extract " + name, e); - } - } - - /** 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; - public final size_t f1_me_mapsize; - public final size_t f2_me_last_pgno; - public final size_t f3_me_last_txnid; - public final u_int32_t f4_me_maxreaders; - public final u_int32_t f5_me_numreaders; - - MDB_envinfo(final jnr.ffi.Runtime runtime) { - super(runtime); - this.f0_me_mapaddr = new Pointer(); - this.f1_me_mapsize = new size_t(); - this.f2_me_last_pgno = new size_t(); - this.f3_me_last_txnid = new size_t(); - this.f4_me_maxreaders = new u_int32_t(); - this.f5_me_numreaders = new u_int32_t(); - } - } - - /** 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; - public final u_int32_t f1_ms_depth; - public final size_t f2_ms_branch_pages; - public final size_t f3_ms_leaf_pages; - public final size_t f4_ms_overflow_pages; - public final size_t f5_ms_entries; - - MDB_stat(final jnr.ffi.Runtime runtime) { - super(runtime); - this.f0_ms_psize = new u_int32_t(); - this.f1_ms_depth = new u_int32_t(); - this.f2_ms_branch_pages = new size_t(); - this.f3_ms_leaf_pages = new size_t(); - this.f4_ms_overflow_pages = new size_t(); - this.f5_ms_entries = new size_t(); - } - } - - /** 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. */ - public interface Lmdb { - - void mdb_cursor_close(@In Pointer cursor); - - int mdb_cursor_count(@In Pointer cursor, NativeLongByReference countp); - - 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_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_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_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_drop(@In Pointer txn, @In Pointer dbi, int del); - - void mdb_env_close(@In Pointer env); - - int mdb_env_copy2(@In Pointer env, @In String path, int flags); - - int mdb_env_create(PointerByReference envPtr); - - int mdb_env_get_fd(@In Pointer env, @In Pointer fd); - - int mdb_env_get_flags(@In Pointer env, int flags); - - int mdb_env_get_maxkeysize(@In Pointer env); - - int mdb_env_get_maxreaders(@In Pointer env, int readers); - - int mdb_env_get_path(@In Pointer env, String path); - - int mdb_env_info(@In Pointer env, @Out MDB_envinfo info); - - int mdb_env_open(@In Pointer env, @In String path, int flags, int mode); - - int mdb_env_set_flags(@In Pointer env, int flags, int onoff); - - int mdb_env_set_mapsize(@In Pointer env, @size_t long size); - - int mdb_env_set_maxdbs(@In Pointer env, int dbs); - - int mdb_env_set_maxreaders(@In Pointer env, int readers); - - int mdb_env_stat(@In Pointer env, @Out MDB_stat stat); - - 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_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); - - int mdb_set_compare(@In Pointer txn, @In Pointer dbi, ComparatorCallback cb); - - int mdb_stat(@In Pointer txn, @In Pointer dbi, @Out MDB_stat stat); - - String mdb_strerror(int rc); - - void mdb_txn_abort(@In Pointer txn); - - int mdb_txn_begin(@In Pointer env, @In Pointer parentTx, int flags, Pointer txPtr); - - int mdb_txn_commit(@In Pointer txn); - - Pointer mdb_txn_env(@In Pointer txn); - - long mdb_txn_id(@In Pointer txn); - - int mdb_txn_renew(@In Pointer txn); - - void mdb_txn_reset(@In Pointer txn); - - Pointer mdb_version(IntByReference major, IntByReference minor, IntByReference patch); - } +// +// /** +// * 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")); +// +// static final Lmdb LIB = new Lmdb(); +//// static final jnr.ffi.Runtime RUNTIME; +// +// static { +// final String libToLoad; +// +// if (TargetName.IS_EXTERNAL) { +// libToLoad = TargetName.RESOLVED_FILENAME; +// } else { +// libToLoad = extract(TargetName.RESOLVED_FILENAME); +// } +// +// LIB = create(Lmdb.class).load(libToLoad); +//// RUNTIME = getRuntime(LIB); +// +// +// +// +// +// // ============================================================================ +// // LIBRARY INITIALIZATION +// // ============================================================================ +// +// private static final SymbolLookup LIBRARY; +// private static final Linker LINKER = Linker.nativeLinker(); +// +// static { +// try { +// final Path libToLoad; +// +// if (TargetName.IS_EXTERNAL) { +// libToLoad = Paths.get(TargetName.RESOLVED_FILENAME); +// } else { +// libToLoad = extract(TargetName.RESOLVED_FILENAME); +// } +// LIBRARY = SymbolLookup.libraryLookup(libToLoad, Arena.global()); +// } catch (Exception e) { +// throw new ExceptionInInitializerError("Failed to load LMDB library: " + e.getMessage()); +// } +// } +// } +// +// private Library() {} +// +// private static String extract(final String name) { +// final String suffix = name.substring(name.lastIndexOf('.')); +// final File file; +// try { +// final File dir = new File(EXTRACT_DIR); +// if (!dir.exists() || !dir.isDirectory()) { +// throw new IllegalStateException("Invalid extraction directory " + dir); +// } +// file = createTempFile("lmdbjava-native-library-", suffix, dir); +// file.deleteOnExit(); +// final ClassLoader cl = currentThread().getContextClassLoader(); +// try (InputStream in = cl.getResourceAsStream(name); +// OutputStream out = Files.newOutputStream(file.toPath())) { +// requireNonNull(in, "Classpath resource not found"); +// int bytes; +// final byte[] buffer = new byte[4_096]; +// while (-1 != (bytes = in.read(buffer))) { +// out.write(buffer, 0, bytes); +// } +// } +// return file.getAbsolutePath(); +// } catch (final IOException e) { +// throw new LmdbException("Failed to extract " + name, e); +// } +// } +// +// /** 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; +// public final size_t f1_me_mapsize; +// public final size_t f2_me_last_pgno; +// public final size_t f3_me_last_txnid; +// public final u_int32_t f4_me_maxreaders; +// public final u_int32_t f5_me_numreaders; +// +// MDB_envinfo(final jnr.ffi.Runtime runtime) { +// super(runtime); +// this.f0_me_mapaddr = new Pointer(); +// this.f1_me_mapsize = new size_t(); +// this.f2_me_last_pgno = new size_t(); +// this.f3_me_last_txnid = new size_t(); +// this.f4_me_maxreaders = new u_int32_t(); +// this.f5_me_numreaders = new u_int32_t(); +// } +// } +// +// /** 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; +// public final u_int32_t f1_ms_depth; +// public final size_t f2_ms_branch_pages; +// public final size_t f3_ms_leaf_pages; +// public final size_t f4_ms_overflow_pages; +// public final size_t f5_ms_entries; +// +// MDB_stat(final jnr.ffi.Runtime runtime) { +// super(runtime); +// this.f0_ms_psize = new u_int32_t(); +// this.f1_ms_depth = new u_int32_t(); +// this.f2_ms_branch_pages = new size_t(); +// this.f3_ms_leaf_pages = new size_t(); +// this.f4_ms_overflow_pages = new size_t(); +// this.f5_ms_entries = new size_t(); +// } +// } +// +// /** 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. */ +//// public interface Lmdb { +//// +//// void mdb_cursor_close(@In Pointer cursor); +//// +//// int mdb_cursor_count(@In Pointer cursor, NativeLongByReference countp); +//// +//// 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_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_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_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_drop(@In Pointer txn, @In Pointer dbi, int del); +//// +//// void mdb_env_close(@In Pointer env); +//// +//// int mdb_env_copy2(@In Pointer env, @In String path, int flags); +//// +//// int mdb_env_create(PointerByReference envPtr); +//// +//// int mdb_env_get_fd(@In Pointer env, @In Pointer fd); +//// +//// int mdb_env_get_flags(@In Pointer env, int flags); +//// +//// int mdb_env_get_maxkeysize(@In Pointer env); +//// +//// int mdb_env_get_maxreaders(@In Pointer env, int readers); +//// +//// int mdb_env_get_path(@In Pointer env, String path); +//// +//// int mdb_env_info(@In Pointer env, @Out MDB_envinfo info); +//// +//// int mdb_env_open(@In Pointer env, @In String path, int flags, int mode); +//// +//// int mdb_env_set_flags(@In Pointer env, int flags, int onoff); +//// +//// int mdb_env_set_mapsize(@In Pointer env, @size_t long size); +//// +//// int mdb_env_set_maxdbs(@In Pointer env, int dbs); +//// +//// int mdb_env_set_maxreaders(@In Pointer env, int readers); +//// +//// int mdb_env_stat(@In Pointer env, @Out MDB_stat stat); +//// +//// 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_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); +//// +//// int mdb_set_compare(@In Pointer txn, @In Pointer dbi, ComparatorCallback cb); +//// +//// int mdb_stat(@In Pointer txn, @In Pointer dbi, @Out MDB_stat stat); +//// +//// String mdb_strerror(int rc); +//// +//// void mdb_txn_abort(@In Pointer txn); +//// +//// int mdb_txn_begin(@In Pointer env, @In Pointer parentTx, int flags, Pointer txPtr); +//// +//// int mdb_txn_commit(@In Pointer txn); +//// +//// Pointer mdb_txn_env(@In Pointer txn); +//// +//// long mdb_txn_id(@In Pointer txn); +//// +//// int mdb_txn_renew(@In Pointer txn); +//// +//// void mdb_txn_reset(@In Pointer txn); +//// +//// Pointer mdb_version(IntByReference major, IntByReference minor, IntByReference patch); +//// } } diff --git a/src/main/java/org/lmdbjava/Lmdb.java b/src/main/java/org/lmdbjava/Lmdb.java new file mode 100644 index 00000000..b0870e3c --- /dev/null +++ b/src/main/java/org/lmdbjava/Lmdb.java @@ -0,0 +1,1678 @@ +package org.lmdbjava; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.foreign.Arena; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.Linker; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.StructLayout; +import java.lang.foreign.SymbolLookup; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.invoke.VarHandle; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static java.lang.System.getProperty; +import static java.lang.Thread.currentThread; +import static java.util.Objects.requireNonNull; + +/** + * Complete LMDB wrapper using Java Foreign Function & Memory API. + * Requires Java 22+ with --enable-preview or Java 23+. + */ +public class Lmdb { + + /** + * 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 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")); + + // ============================================================================ + // LIBRARY INITIALIZATION + // ============================================================================ + + private static final SymbolLookup LIBRARY; + private static final Linker LINKER = Linker.nativeLinker(); + + static { + try { + final Path libToLoad; + + if (TargetName.IS_EXTERNAL) { + libToLoad = Paths.get(TargetName.RESOLVED_FILENAME); + } else { + libToLoad = extract(TargetName.RESOLVED_FILENAME); + } + LIBRARY = SymbolLookup.libraryLookup(libToLoad, Arena.global()); + } catch (Exception e) { + throw new ExceptionInInitializerError("Failed to load LMDB library: " + e.getMessage()); + } + } + + // ============================================================================ + // CONSTANTS AND FLAGS + // ============================================================================ + + public static final class MDB { + // Environment flags + public static final int FIXEDMAP = 0x01; + public static final int NOSUBDIR = 0x4000; + public static final int NOSYNC = 0x10000; + public static final int RDONLY = 0x20000; + public static final int NOMETASYNC = 0x40000; + public static final int WRITEMAP = 0x80000; + public static final int MAPASYNC = 0x100000; + public static final int NOTLS = 0x200000; + public static final int NOLOCK = 0x400000; + public static final int NORDAHEAD = 0x800000; + public static final int NOMEMINIT = 0x1000000; + public static final int PREVSNAPSHOT = 0x2000000; + + // Database flags + public static final int REVERSEKEY = 0x02; + public static final int DUPSORT = 0x04; + public static final int INTEGERKEY = 0x08; + public static final int DUPFIXED = 0x10; + public static final int INTEGERDUP = 0x20; + public static final int REVERSEDUP = 0x40; + public static final int CREATE = 0x40000; + + // Write flags + public static final int NOOVERWRITE = 0x10; + public static final int NODUPDATA = 0x20; + public static final int CURRENT = 0x40; + public static final int RESERVE = 0x10000; + public static final int APPEND = 0x20000; + public static final int APPENDDUP = 0x40000; + public static final int MULTIPLE = 0x80000; + + // Copy flags + public static final int CP_COMPACT = 0x01; + + // Cursor operations + public static final int FIRST = 0; + public static final int FIRST_DUP = 1; + public static final int GET_BOTH = 2; + public static final int GET_BOTH_RANGE = 3; + public static final int GET_CURRENT = 4; + public static final int GET_MULTIPLE = 5; + public static final int LAST = 6; + public static final int LAST_DUP = 7; + public static final int NEXT = 8; + public static final int NEXT_DUP = 9; + public static final int NEXT_MULTIPLE = 10; + public static final int NEXT_NODUP = 11; + public static final int PREV = 12; + public static final int PREV_DUP = 13; + public static final int PREV_NODUP = 14; + public static final int SET = 15; + public static final int SET_KEY = 16; + public static final int SET_RANGE = 17; + public static final int PREV_MULTIPLE = 18; + + // Return codes + public static final int SUCCESS = 0; + public static final int KEYEXIST = -30799; + public static final int NOTFOUND = -30798; + public static final int PAGE_NOTFOUND = -30797; + public static final int CORRUPTED = -30796; + public static final int PANIC = -30795; + public static final int VERSION_MISMATCH = -30794; + public static final int INVALID = -30793; + public static final int MAP_FULL = -30792; + public static final int DBS_FULL = -30791; + public static final int READERS_FULL = -30790; + public static final int TLS_FULL = -30789; + public static final int TXN_FULL = -30788; + public static final int CURSOR_FULL = -30787; + public static final int PAGE_FULL = -30786; + public static final int MAP_RESIZED = -30785; + public static final int INCOMPATIBLE = -30784; + public static final int BAD_RSLOT = -30783; + public static final int BAD_TXN = -30782; + public static final int BAD_VALSIZE = -30781; + public static final int BAD_DBI = -30780; + } + + // ============================================================================ + // STRUCTURES + // ============================================================================ + + /** + * MDB_val structure + */ + public static final class MDB_val { + private static final StructLayout LAYOUT = MemoryLayout.structLayout( + ValueLayout.JAVA_LONG.withName("mv_size"), + ValueLayout.ADDRESS.withName("mv_data") + ); + + private static final VarHandle MV_SIZE = LAYOUT.varHandle( + MemoryLayout.PathElement.groupElement("mv_size") + ); + + private static final VarHandle MV_DATA = LAYOUT.varHandle( + MemoryLayout.PathElement.groupElement("mv_data") + ); + + private final MemorySegment segment; + + public MDB_val(Arena arena) { + this.segment = arena.allocate(LAYOUT); + } + + public MDB_val(MemorySegment segment) { + this.segment = segment; + } + + public long mvSize() { + return (long) MV_SIZE.get(segment, 0L); + } + + public void mvSize(long value) { + MV_SIZE.set(segment, 0L, value); + } + + public MemorySegment mvData() { + return (MemorySegment) MV_DATA.get(segment, 0L); + } + + public void mvData(MemorySegment value) { + MV_DATA.set(segment, 0L, value); + } + + public MemorySegment segment() { + return segment; + } + + public static StructLayout layout() { + return LAYOUT; + } + } + + /** + * MDB_arr_val structure + */ + public static final class MDB_arr_val { + private static final StructLayout LAYOUT = MemoryLayout.structLayout( + ValueLayout.JAVA_LONG.withName("mv_size1"), + ValueLayout.ADDRESS.withName("mv_data1"), + ValueLayout.JAVA_LONG.withName("mv_size2"), + ValueLayout.ADDRESS.withName("mv_data2") + ); + + private static final VarHandle MV_SIZE1 = LAYOUT.varHandle( + MemoryLayout.PathElement.groupElement("mv_size1") + ); + + private static final VarHandle MV_DATA1 = LAYOUT.varHandle( + MemoryLayout.PathElement.groupElement("mv_data1") + ); + + private static final VarHandle MV_SIZE2 = LAYOUT.varHandle( + MemoryLayout.PathElement.groupElement("mv_size2") + ); + + private static final VarHandle MV_DATA2 = LAYOUT.varHandle( + MemoryLayout.PathElement.groupElement("mv_data2") + ); + + private final MemorySegment segment; + + public MDB_arr_val(Arena arena) { + this.segment = arena.allocate(LAYOUT); + } + + public MDB_arr_val(MemorySegment segment) { + this.segment = segment; + } + + public long mvSize1() { + return (long) MV_SIZE1.get(segment, 0L); + } + + public void mvSize1(long value) { + MV_SIZE1.set(segment, 0L, value); + } + + public MemorySegment mvData1() { + return (MemorySegment) MV_DATA1.get(segment, 0L); + } + + public void mvData1(MemorySegment value) { + MV_DATA1.set(segment, 0L, value); + } + + public long mvSize2() { + return (long) MV_SIZE2.get(segment, 0L); + } + + public void mvSize2(long value) { + MV_SIZE2.set(segment, 0L, value); + } + + public MemorySegment mvData2() { + return (MemorySegment) MV_DATA2.get(segment, 0L); + } + + public void mvData2(MemorySegment value) { + MV_DATA2.set(segment, 0L, value); + } + + public MemorySegment segment() { + return segment; + } + + public static StructLayout layout() { + return LAYOUT; + } + } + + /** + * MDB_stat structure + */ + public static final class MDB_stat { + private static final StructLayout LAYOUT = MemoryLayout.structLayout( + ValueLayout.JAVA_INT.withName("ms_psize"), + ValueLayout.JAVA_INT.withName("ms_depth"), + ValueLayout.JAVA_LONG.withName("ms_branch_pages"), + ValueLayout.JAVA_LONG.withName("ms_leaf_pages"), + ValueLayout.JAVA_LONG.withName("ms_overflow_pages"), + ValueLayout.JAVA_LONG.withName("ms_entries") + ); + + private static final VarHandle MS_PSIZE = LAYOUT.varHandle( + MemoryLayout.PathElement.groupElement("ms_psize") + ); + + private static final VarHandle MS_DEPTH = LAYOUT.varHandle( + MemoryLayout.PathElement.groupElement("ms_depth") + ); + + private static final VarHandle MS_BRANCH_PAGES = LAYOUT.varHandle( + MemoryLayout.PathElement.groupElement("ms_branch_pages") + ); + + private static final VarHandle MS_LEAF_PAGES = LAYOUT.varHandle( + MemoryLayout.PathElement.groupElement("ms_leaf_pages") + ); + + private static final VarHandle MS_OVERFLOW_PAGES = LAYOUT.varHandle( + MemoryLayout.PathElement.groupElement("ms_overflow_pages") + ); + + private static final VarHandle MS_ENTRIES = LAYOUT.varHandle( + MemoryLayout.PathElement.groupElement("ms_entries") + ); + + private final MemorySegment segment; + + public MDB_stat(Arena arena) { + this.segment = arena.allocate(LAYOUT); + } + + public MDB_stat(MemorySegment segment) { + this.segment = segment; + } + + public int msPsize() { + return (int) MS_PSIZE.get(segment, 0L); + } + + public int msDepth() { + return (int) MS_DEPTH.get(segment, 0L); + } + + public long msBranchPages() { + return (long) MS_BRANCH_PAGES.get(segment, 0L); + } + + public long msLeafPages() { + return (long) MS_LEAF_PAGES.get(segment, 0L); + } + + public long msOverflowPages() { + return (long) MS_OVERFLOW_PAGES.get(segment, 0L); + } + + public long msEntries() { + return (long) MS_ENTRIES.get(segment, 0L); + } + + public MemorySegment segment() { + return segment; + } + + public static StructLayout layout() { + return LAYOUT; + } + } + + /** + * MDB_envinfo structure + */ + public static final class MDB_envinfo { + static final StructLayout LAYOUT = MemoryLayout.structLayout( + ValueLayout.ADDRESS.withName("me_mapaddr"), + ValueLayout.JAVA_LONG.withName("me_mapsize"), + ValueLayout.JAVA_LONG.withName("me_last_pgno"), + ValueLayout.JAVA_LONG.withName("me_last_txnid"), + ValueLayout.JAVA_INT.withName("me_maxreaders"), + ValueLayout.JAVA_INT.withName("me_numreaders") + ); + + private static final VarHandle ME_MAPADDR = LAYOUT.varHandle( + MemoryLayout.PathElement.groupElement("me_mapaddr") + ); + + private static final VarHandle ME_MAPSIZE = LAYOUT.varHandle( + MemoryLayout.PathElement.groupElement("me_mapsize") + ); + + private static final VarHandle ME_LAST_PGNO = LAYOUT.varHandle( + MemoryLayout.PathElement.groupElement("me_last_pgno") + ); + + private static final VarHandle ME_LAST_TXNID = LAYOUT.varHandle( + MemoryLayout.PathElement.groupElement("me_last_txnid") + ); + + private static final VarHandle ME_MAXREADERS = LAYOUT.varHandle( + MemoryLayout.PathElement.groupElement("me_maxreaders") + ); + + private static final VarHandle ME_NUMREADERS = LAYOUT.varHandle( + MemoryLayout.PathElement.groupElement("me_numreaders") + ); + + private final MemorySegment segment; + + public MDB_envinfo(Arena arena) { + this.segment = arena.allocate(LAYOUT); + } + + public MDB_envinfo(MemorySegment segment) { + this.segment = segment; + } + + public MemorySegment meMapaddr() { + return (MemorySegment) ME_MAPADDR.get(segment, 0L); + } + + public long meMapsize() { + return (long) ME_MAPSIZE.get(segment, 0L); + } + + public long meLastPgno() { + return (long) ME_LAST_PGNO.get(segment, 0L); + } + + public long meLastTxnid() { + return (long) ME_LAST_TXNID.get(segment, 0L); + } + + public int meMaxreaders() { + return (int) ME_MAXREADERS.get(segment, 0L); + } + + public int meNumreaders() { + return (int) ME_NUMREADERS.get(segment, 0L); + } + + public MemorySegment segment() { + return segment; + } + + public static StructLayout layout() { + return LAYOUT; + } + } + + // ============================================================================ + // FUNCTION DESCRIPTORS + // ============================================================================ + + // Version information + private static final FunctionDescriptor MDB_VERSION_DESC = FunctionDescriptor.of( + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS + ); + + // Environment functions + private static final FunctionDescriptor MDB_ENV_CREATE_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_ENV_OPEN_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT, + ValueLayout.JAVA_INT + ); + + private static final FunctionDescriptor MDB_ENV_COPY_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_ENV_COPYFD_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT + ); + + private static final FunctionDescriptor MDB_ENV_COPY2_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT + ); + + private static final FunctionDescriptor MDB_ENV_COPYFD2_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT, + ValueLayout.JAVA_INT + ); + + private static final FunctionDescriptor MDB_ENV_STAT_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_ENV_INFO_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_ENV_SYNC_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT + ); + + private static final FunctionDescriptor MDB_ENV_CLOSE_DESC = FunctionDescriptor.ofVoid( + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_ENV_SET_FLAGS_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT, + ValueLayout.JAVA_INT + ); + + private static final FunctionDescriptor MDB_ENV_GET_FLAGS_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_ENV_GET_PATH_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_ENV_GET_FD_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_ENV_SET_MAPSIZE_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.JAVA_LONG + ); + + private static final FunctionDescriptor MDB_ENV_SET_MAXREADERS_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT + ); + + private static final FunctionDescriptor MDB_ENV_GET_MAXREADERS_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_ENV_SET_MAXDBS_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT + ); + + private static final FunctionDescriptor MDB_ENV_GET_MAXKEYSIZE_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_ENV_SET_USERCTX_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_ENV_GET_USERCTX_DESC = FunctionDescriptor.of( + ValueLayout.ADDRESS, + ValueLayout.ADDRESS + ); + + // Transaction functions + private static final FunctionDescriptor MDB_TXN_BEGIN_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_TXN_ENV_DESC = FunctionDescriptor.of( + ValueLayout.ADDRESS, + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_TXN_ID_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_LONG, + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_TXN_COMMIT_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_TXN_ABORT_DESC = FunctionDescriptor.ofVoid( + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_TXN_RESET_DESC = FunctionDescriptor.ofVoid( + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_TXN_RENEW_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS + ); + + // Database functions + private static final FunctionDescriptor MDB_DBI_OPEN_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_STAT_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_DBI_FLAGS_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_DBI_CLOSE_DESC = FunctionDescriptor.ofVoid( + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT + ); + + private static final FunctionDescriptor MDB_DROP_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT, + ValueLayout.JAVA_INT + ); + + // Data access functions + private static final FunctionDescriptor MDB_GET_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_PUT_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT + ); + + private static final FunctionDescriptor MDB_DEL_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS + ); + + // Cursor functions + private static final FunctionDescriptor MDB_CURSOR_OPEN_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_CURSOR_CLOSE_DESC = FunctionDescriptor.ofVoid( + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_CURSOR_RENEW_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_CURSOR_TXN_DESC = FunctionDescriptor.of( + ValueLayout.ADDRESS, + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_CURSOR_DBI_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_CURSOR_GET_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT + ); + + private static final FunctionDescriptor MDB_CURSOR_PUT_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT + ); + + private static final FunctionDescriptor MDB_CURSOR_DEL_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT + ); + + private static final FunctionDescriptor MDB_CURSOR_COUNT_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS + ); + + // Comparison functions + private static final FunctionDescriptor MDB_CMP_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_DCMP_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS + ); + + // Set comparison functions + private static final FunctionDescriptor MDB_SET_COMPARE_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_SET_DUPSORT_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS + ); + + // Comparison function callback descriptor (MDB_cmp_func) + private static final FunctionDescriptor MDB_CMP_FUNC_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS + ); + + // Utility functions + private static final FunctionDescriptor MDB_STRERROR_DESC = FunctionDescriptor.of( + ValueLayout.ADDRESS, + ValueLayout.JAVA_INT + ); + + private static final FunctionDescriptor MDB_READER_LIST_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS + ); + + private static final FunctionDescriptor MDB_READER_CHECK_DESC = FunctionDescriptor.of( + ValueLayout.JAVA_INT, + ValueLayout.ADDRESS, + ValueLayout.ADDRESS + ); + + // ============================================================================ + // METHOD HANDLES + // ============================================================================ + + private static final MethodHandle mdb_version; + + private static final MethodHandle mdb_env_create; + private static final MethodHandle mdb_env_open; + private static final MethodHandle mdb_env_copy; + private static final MethodHandle mdb_env_copyfd; + private static final MethodHandle mdb_env_copy2; + private static final MethodHandle mdb_env_copyfd2; + private static final MethodHandle mdb_env_stat; + private static final MethodHandle mdb_env_info; + private static final MethodHandle mdb_env_sync; + private static final MethodHandle mdb_env_close; + private static final MethodHandle mdb_env_set_flags; + private static final MethodHandle mdb_env_get_flags; + private static final MethodHandle mdb_env_get_path; + private static final MethodHandle mdb_env_get_fd; + private static final MethodHandle mdb_env_set_mapsize; + private static final MethodHandle mdb_env_set_maxreaders; + private static final MethodHandle mdb_env_get_maxreaders; + private static final MethodHandle mdb_env_set_maxdbs; + private static final MethodHandle mdb_env_get_maxkeysize; + private static final MethodHandle mdb_env_set_userctx; + private static final MethodHandle mdb_env_get_userctx; + + private static final MethodHandle mdb_txn_begin; + private static final MethodHandle mdb_txn_env; + private static final MethodHandle mdb_txn_id; + private static final MethodHandle mdb_txn_commit; + private static final MethodHandle mdb_txn_abort; + private static final MethodHandle mdb_txn_reset; + private static final MethodHandle mdb_txn_renew; + + private static final MethodHandle mdb_dbi_open; + private static final MethodHandle mdb_stat; + private static final MethodHandle mdb_dbi_flags; + private static final MethodHandle mdb_dbi_close; + private static final MethodHandle mdb_drop; + + private static final MethodHandle mdb_get; + private static final MethodHandle mdb_put; + private static final MethodHandle mdb_del; + + private static final MethodHandle mdb_cursor_open; + private static final MethodHandle mdb_cursor_close; + private static final MethodHandle mdb_cursor_renew; + private static final MethodHandle mdb_cursor_txn; + private static final MethodHandle mdb_cursor_dbi; + private static final MethodHandle mdb_cursor_get; + private static final MethodHandle mdb_cursor_put; + private static final MethodHandle mdb_cursor_del; + private static final MethodHandle mdb_cursor_count; + + private static final MethodHandle mdb_cmp; + private static final MethodHandle mdb_dcmp; + private static final MethodHandle mdb_set_compare; + private static final MethodHandle mdb_set_dupsort; + + private static final MethodHandle mdb_strerror; + private static final MethodHandle mdb_reader_list; + private static final MethodHandle mdb_reader_check; + + static { + try { + mdb_version = LINKER.downcallHandle( + LIBRARY.find("mdb_version").orElseThrow(), + MDB_VERSION_DESC + ); + + mdb_env_create = LINKER.downcallHandle( + LIBRARY.find("mdb_env_create").orElseThrow(), + MDB_ENV_CREATE_DESC + ); + + mdb_env_open = LINKER.downcallHandle( + LIBRARY.find("mdb_env_open").orElseThrow(), + MDB_ENV_OPEN_DESC + ); + + mdb_env_copy = LINKER.downcallHandle( + LIBRARY.find("mdb_env_copy").orElseThrow(), + MDB_ENV_COPY_DESC + ); + + mdb_env_copyfd = LINKER.downcallHandle( + LIBRARY.find("mdb_env_copyfd").orElseThrow(), + MDB_ENV_COPYFD_DESC + ); + + mdb_env_copy2 = LINKER.downcallHandle( + LIBRARY.find("mdb_env_copy2").orElseThrow(), + MDB_ENV_COPY2_DESC + ); + + mdb_env_copyfd2 = LINKER.downcallHandle( + LIBRARY.find("mdb_env_copyfd2").orElseThrow(), + MDB_ENV_COPYFD2_DESC + ); + + mdb_env_stat = LINKER.downcallHandle( + LIBRARY.find("mdb_env_stat").orElseThrow(), + MDB_ENV_STAT_DESC + ); + + mdb_env_info = LINKER.downcallHandle( + LIBRARY.find("mdb_env_info").orElseThrow(), + MDB_ENV_INFO_DESC + ); + + mdb_env_sync = LINKER.downcallHandle( + LIBRARY.find("mdb_env_sync").orElseThrow(), + MDB_ENV_SYNC_DESC + ); + + mdb_env_close = LINKER.downcallHandle( + LIBRARY.find("mdb_env_close").orElseThrow(), + MDB_ENV_CLOSE_DESC + ); + + mdb_env_set_flags = LINKER.downcallHandle( + LIBRARY.find("mdb_env_set_flags").orElseThrow(), + MDB_ENV_SET_FLAGS_DESC + ); + + mdb_env_get_flags = LINKER.downcallHandle( + LIBRARY.find("mdb_env_get_flags").orElseThrow(), + MDB_ENV_GET_FLAGS_DESC + ); + + mdb_env_get_path = LINKER.downcallHandle( + LIBRARY.find("mdb_env_get_path").orElseThrow(), + MDB_ENV_GET_PATH_DESC + ); + + mdb_env_get_fd = LINKER.downcallHandle( + LIBRARY.find("mdb_env_get_fd").orElseThrow(), + MDB_ENV_GET_FD_DESC + ); + + mdb_env_set_mapsize = LINKER.downcallHandle( + LIBRARY.find("mdb_env_set_mapsize").orElseThrow(), + MDB_ENV_SET_MAPSIZE_DESC + ); + + mdb_env_set_maxreaders = LINKER.downcallHandle( + LIBRARY.find("mdb_env_set_maxreaders").orElseThrow(), + MDB_ENV_SET_MAXREADERS_DESC + ); + + mdb_env_get_maxreaders = LINKER.downcallHandle( + LIBRARY.find("mdb_env_get_maxreaders").orElseThrow(), + MDB_ENV_GET_MAXREADERS_DESC + ); + + mdb_env_set_maxdbs = LINKER.downcallHandle( + LIBRARY.find("mdb_env_set_maxdbs").orElseThrow(), + MDB_ENV_SET_MAXDBS_DESC + ); + + mdb_env_get_maxkeysize = LINKER.downcallHandle( + LIBRARY.find("mdb_env_get_maxkeysize").orElseThrow(), + MDB_ENV_GET_MAXKEYSIZE_DESC + ); + + mdb_env_set_userctx = LINKER.downcallHandle( + LIBRARY.find("mdb_env_set_userctx").orElseThrow(), + MDB_ENV_SET_USERCTX_DESC + ); + + mdb_env_get_userctx = LINKER.downcallHandle( + LIBRARY.find("mdb_env_get_userctx").orElseThrow(), + MDB_ENV_GET_USERCTX_DESC + ); + + mdb_txn_begin = LINKER.downcallHandle( + LIBRARY.find("mdb_txn_begin").orElseThrow(), + MDB_TXN_BEGIN_DESC + ); + + mdb_txn_env = LINKER.downcallHandle( + LIBRARY.find("mdb_txn_env").orElseThrow(), + MDB_TXN_ENV_DESC + ); + + mdb_txn_id = LINKER.downcallHandle( + LIBRARY.find("mdb_txn_id").orElseThrow(), + MDB_TXN_ID_DESC + ); + + mdb_txn_commit = LINKER.downcallHandle( + LIBRARY.find("mdb_txn_commit").orElseThrow(), + MDB_TXN_COMMIT_DESC + ); + + mdb_txn_abort = LINKER.downcallHandle( + LIBRARY.find("mdb_txn_abort").orElseThrow(), + MDB_TXN_ABORT_DESC + ); + + mdb_txn_reset = LINKER.downcallHandle( + LIBRARY.find("mdb_txn_reset").orElseThrow(), + MDB_TXN_RESET_DESC + ); + + mdb_txn_renew = LINKER.downcallHandle( + LIBRARY.find("mdb_txn_renew").orElseThrow(), + MDB_TXN_RENEW_DESC + ); + + mdb_dbi_open = LINKER.downcallHandle( + LIBRARY.find("mdb_dbi_open").orElseThrow(), + MDB_DBI_OPEN_DESC + ); + + mdb_stat = LINKER.downcallHandle( + LIBRARY.find("mdb_stat").orElseThrow(), + MDB_STAT_DESC + ); + + mdb_dbi_flags = LINKER.downcallHandle( + LIBRARY.find("mdb_dbi_flags").orElseThrow(), + MDB_DBI_FLAGS_DESC + ); + + mdb_dbi_close = LINKER.downcallHandle( + LIBRARY.find("mdb_dbi_close").orElseThrow(), + MDB_DBI_CLOSE_DESC + ); + + mdb_drop = LINKER.downcallHandle( + LIBRARY.find("mdb_drop").orElseThrow(), + MDB_DROP_DESC + ); + + mdb_get = LINKER.downcallHandle( + LIBRARY.find("mdb_get").orElseThrow(), + MDB_GET_DESC + ); + + mdb_put = LINKER.downcallHandle( + LIBRARY.find("mdb_put").orElseThrow(), + MDB_PUT_DESC + ); + + mdb_del = LINKER.downcallHandle( + LIBRARY.find("mdb_del").orElseThrow(), + MDB_DEL_DESC + ); + + mdb_cursor_open = LINKER.downcallHandle( + LIBRARY.find("mdb_cursor_open").orElseThrow(), + MDB_CURSOR_OPEN_DESC + ); + + mdb_cursor_close = LINKER.downcallHandle( + LIBRARY.find("mdb_cursor_close").orElseThrow(), + MDB_CURSOR_CLOSE_DESC + ); + + mdb_cursor_renew = LINKER.downcallHandle( + LIBRARY.find("mdb_cursor_renew").orElseThrow(), + MDB_CURSOR_RENEW_DESC + ); + + mdb_cursor_txn = LINKER.downcallHandle( + LIBRARY.find("mdb_cursor_txn").orElseThrow(), + MDB_CURSOR_TXN_DESC + ); + + mdb_cursor_dbi = LINKER.downcallHandle( + LIBRARY.find("mdb_cursor_dbi").orElseThrow(), + MDB_CURSOR_DBI_DESC + ); + + mdb_cursor_get = LINKER.downcallHandle( + LIBRARY.find("mdb_cursor_get").orElseThrow(), + MDB_CURSOR_GET_DESC + ); + + mdb_cursor_put = LINKER.downcallHandle( + LIBRARY.find("mdb_cursor_put").orElseThrow(), + MDB_CURSOR_PUT_DESC + ); + + mdb_cursor_del = LINKER.downcallHandle( + LIBRARY.find("mdb_cursor_del").orElseThrow(), + MDB_CURSOR_DEL_DESC + ); + + mdb_cursor_count = LINKER.downcallHandle( + LIBRARY.find("mdb_cursor_count").orElseThrow(), + MDB_CURSOR_COUNT_DESC + ); + + mdb_cmp = LINKER.downcallHandle( + LIBRARY.find("mdb_cmp").orElseThrow(), + MDB_CMP_DESC + ); + + mdb_dcmp = LINKER.downcallHandle( + LIBRARY.find("mdb_dcmp").orElseThrow(), + MDB_DCMP_DESC + ); + + mdb_set_compare = LINKER.downcallHandle( + LIBRARY.find("mdb_set_compare").orElseThrow(), + MDB_SET_COMPARE_DESC + ); + + mdb_set_dupsort = LINKER.downcallHandle( + LIBRARY.find("mdb_set_dupsort").orElseThrow(), + MDB_SET_DUPSORT_DESC + ); + + mdb_strerror = LINKER.downcallHandle( + LIBRARY.find("mdb_strerror").orElseThrow(), + MDB_STRERROR_DESC + ); + + mdb_reader_list = LINKER.downcallHandle( + LIBRARY.find("mdb_reader_list").orElseThrow(), + MDB_READER_LIST_DESC + ); + + mdb_reader_check = LINKER.downcallHandle( + LIBRARY.find("mdb_reader_check").orElseThrow(), + MDB_READER_CHECK_DESC + ); + + } catch (Throwable e) { + throw new ExceptionInInitializerError(e); + } + } + + /** + * Comparator interface for custom key comparison + */ + @FunctionalInterface + public interface ComparatorCallback { + /** + * Compare two MDB_val structures + * + * @return < 0 if a < b, 0 if a == b, > 0 if a > b + */ + int compare(MDB_val a, MDB_val b); + } + + // ============================================================================ + // PUBLIC API METHODS + // ============================================================================ + + // Version + public static String mdb_version(MemorySegment major, MemorySegment minor, MemorySegment patch) { + try { + MemorySegment result = (MemorySegment) mdb_version.invoke(major, minor, patch); + return result.reinterpret(Long.MAX_VALUE).getString(0); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_version", t.getMessage(), t); + } + } + + // Environment functions + public static int mdb_env_create(MemorySegment envPtr) { + try { + return (int) mdb_env_create.invoke(envPtr); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_env_create", t.getMessage(), t); + } + } + + public static int mdb_env_open(MemorySegment env, MemorySegment pathSegment, int flags, int mode) { + try { + return (int) mdb_env_open.invoke(env, pathSegment, flags, mode); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_env_open", t.getMessage(), t); + } + } + + public static int mdb_env_copy(MemorySegment env, MemorySegment pathSegment) { + try { + return (int) mdb_env_copy.invoke(env, pathSegment); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_env_copy", t.getMessage(), t); + } + } + + public static int mdb_env_copyfd(MemorySegment env, int fd) { + try { + return (int) mdb_env_copyfd.invoke(env, fd); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_env_copyfd", t.getMessage(), t); + } + } + + public static int mdb_env_copy2(MemorySegment env, MemorySegment pathSegment, int flags) { + try { + return (int) mdb_env_copy2.invoke(env, pathSegment, flags); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_env_copy2", t.getMessage(), t); + } + } + + public static int mdb_env_copyfd2(MemorySegment env, int fd, int flags) { + try { + return (int) mdb_env_copyfd2.invoke(env, fd, flags); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_env_copyfd2", t.getMessage(), t); + } + } + + public static int mdb_env_stat(MemorySegment env, MDB_stat stat) { + try { + return (int) mdb_env_stat.invoke(env, stat.segment()); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_env_stat", t.getMessage(), t); + } + } + + public static int mdb_env_info(MemorySegment env, MDB_envinfo info) { + try { + return (int) mdb_env_info.invoke(env, info.segment()); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_env_info", t.getMessage(), t); + } + } + + public static int mdb_env_sync(MemorySegment env, int f) { + try { + return (int) mdb_env_sync.invoke(env, f); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_env_sync", t.getMessage(), t); + } + } + + public static void mdb_env_close(MemorySegment env) { + try { + mdb_env_close.invoke(env); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_env_close", t.getMessage(), t); + } + } + + public static int mdb_env_set_flags(MemorySegment env, int flags, boolean onoff) { + try { + return (int) mdb_env_set_flags.invoke(env, flags, onoff ? 1 : 0); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_env_set_flags", t.getMessage(), t); + } + } + +// public static int mdb_env_get_flags(MemorySegment env, Arena arena) { +// try { +// MemorySegment flagsPtr = arena.allocate(ValueLayout.JAVA_INT); +// int rc = (int) mdb_env_get_flags.invoke(env, flagsPtr); +// checkError(rc, "mdb_env_get_flags"); +// return flagsPtr.get(ValueLayout.JAVA_INT, 0); +// } catch (final Throwable t) { +// throw new LmdbInvokeException("mdb_env_get_flags", t.getMessage(), t); +// } +// } + +// public static String mdb_env_get_path(MemorySegment env, Arena arena) { +// try { +// MemorySegment pathPtr = arena.allocate(ValueLayout.ADDRESS); +// int rc = (int) mdb_env_get_path.invoke(env, pathPtr); +// checkError(rc, "mdb_env_get_path"); +// MemorySegment path = pathPtr.get(ValueLayout.ADDRESS, 0); +// return path.reinterpret(Long.MAX_VALUE).getString(0); +// } catch (final Throwable t) { +// throw new LmdbInvokeException("mdb_env_get_path", t.getMessage(), t); +// } +// } + +// public static int mdb_env_get_fd(MemorySegment env, Arena arena) { +// try { +// MemorySegment fdPtr = arena.allocate(ValueLayout.JAVA_INT); +// int rc = (int) mdb_env_get_fd.invoke(env, fdPtr); +// checkError(rc, "mdb_env_get_fd"); +// return fdPtr.get(ValueLayout.JAVA_INT, 0); +// } catch (final Throwable t) { +// throw new LmdbInvokeException("mdb_env_get_fd", t.getMessage(), t); +// } +// } + + public static int mdb_env_set_mapsize(MemorySegment env, long size) { + try { + return (int) mdb_env_set_mapsize.invoke(env, size); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_env_set_mapsize", t.getMessage(), t); + } + } + + public static int mdb_env_set_maxreaders(MemorySegment env, int readers) { + try { + return (int) mdb_env_set_maxreaders.invoke(env, readers); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_env_set_maxreaders", t.getMessage(), t); + } + } + + public static int mdb_env_get_maxreaders(MemorySegment env, Arena arena) { + try { + MemorySegment readersPtr = arena.allocate(ValueLayout.JAVA_INT); + return (int) mdb_env_get_maxreaders.invoke(env, readersPtr); +// checkError(rc, "mdb_env_get_maxreaders"); +// return readersPtr.get(ValueLayout.JAVA_INT, 0); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_env_get_maxreaders", t.getMessage(), t); + } + } + + public static int mdb_env_set_maxdbs(MemorySegment env, int dbs) { + try { + return (int) mdb_env_set_maxdbs.invoke(env, dbs); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_env_set_maxdbs", t.getMessage(), t); + } + } + + public static int mdb_env_get_maxkeysize(MemorySegment env) { + try { + return (int) mdb_env_get_maxkeysize.invoke(env); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_env_get_maxkeysize", t.getMessage(), t); + } + } + + public static int mdb_env_set_userctx(MemorySegment env, MemorySegment ctx) { + try { + return (int) mdb_env_set_userctx.invoke(env, ctx); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_env_set_userctx", t.getMessage(), t); + } + } + + public static MemorySegment mdb_env_get_userctx(MemorySegment env) { + try { + return (MemorySegment) mdb_env_get_userctx.invoke(env); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_env_get_userctx", t.getMessage(), t); + } + } + + // Transaction functions + public static int mdb_txn_begin(MemorySegment env, MemorySegment parent, int flags, MemorySegment txnPtr) { + try { + return (int) mdb_txn_begin.invoke(env, parent, flags, txnPtr); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_txn_begin", t.getMessage(), t); + } + } + + public static MemorySegment mdb_txn_env(MemorySegment txn) { + try { + return (MemorySegment) mdb_txn_env.invoke(txn); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_txn_env", t.getMessage(), t); + } + } + + public static long mdb_txn_id(MemorySegment txn) { + try { + return (long) mdb_txn_id.invoke(txn); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_txn_id", t.getMessage(), t); + } + } + + public static int mdb_txn_commit(MemorySegment txn) { + try { + return (int) mdb_txn_commit.invoke(txn); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_txn_commit", t.getMessage(), t); + } + } + + public static void mdb_txn_abort(MemorySegment txn) { + try { + mdb_txn_abort.invoke(txn); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_txn_abort", t.getMessage(), t); + } + } + + public static void mdb_txn_reset(MemorySegment txn) { + try { + mdb_txn_reset.invoke(txn); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_txn_reset", t.getMessage(), t); + } + } + + public static int mdb_txn_renew(MemorySegment txn) { + try { + return (int) mdb_txn_renew.invoke(txn); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_txn_renew", t.getMessage(), t); + } + } + + // Database functions + public static int mdb_dbi_open(MemorySegment txn, MemorySegment name, int flags, MemorySegment dbiPtr) { + try { + return (int) mdb_dbi_open.invoke(txn, name, flags, dbiPtr); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_dbi_open", t.getMessage(), t); + } + } + + public static int mdb_stat(MemorySegment txn, int dbi, MDB_stat stat) { + try { + return (int) mdb_stat.invoke(txn, dbi, stat.segment()); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_stat", t.getMessage(), t); + } + } + + public static int mdb_dbi_flags(MemorySegment txn, int dbi, MemorySegment flagsPtr) { + try { + return (int) mdb_dbi_flags.invoke(txn, dbi, flagsPtr); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_dbi_flags", t.getMessage(), t); + } + } + + public static void mdb_dbi_close(MemorySegment env, int dbi) { + try { + mdb_dbi_close.invoke(env, dbi); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_dbi_close", t.getMessage(), t); + } + } + + public static int mdb_drop(MemorySegment txn, int dbi, int del) { + try { + return (int) mdb_drop.invoke(txn, dbi, del); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_drop", t.getMessage(), t); + } + } + + // Data access functions + public static int mdb_get(MemorySegment txn, int dbi, MDB_val key, MDB_val val) { + try { + return (int) mdb_get.invoke(txn, dbi, key.segment(), val.segment()); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_get", t.getMessage(), t); + } + } + + public static int mdb_put(MemorySegment txn, int dbi, MDB_val key, MDB_val value, int flags) { + try { + return (int) mdb_put.invoke(txn, dbi, key.segment(), value.segment(), flags); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_put", t.getMessage(), t); + } + } + + public static int mdb_del(MemorySegment txn, int dbi, MDB_val key, MDB_val value) { + try { + return (int) mdb_del.invoke(txn, dbi, key.segment(), + value != null ? value.segment() : MemorySegment.NULL); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_del", t.getMessage(), t); + } + } + + // Cursor functions + public static int mdb_cursor_open(MemorySegment txn, int dbi, MemorySegment cursorPtr) { + try { + return (int) mdb_cursor_open.invoke(txn, dbi, cursorPtr); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_cursor_open", t.getMessage(), t); + } + } + + public static void mdb_cursor_close(MemorySegment cursor) { + try { + mdb_cursor_close.invoke(cursor); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_cursor_close", t.getMessage(), t); + } + } + + public static int mdb_cursor_renew(MemorySegment txn, MemorySegment cursor) { + try { + return (int) mdb_cursor_renew.invoke(txn, cursor); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_cursor_renew", t.getMessage(), t); + } + } + + public static MemorySegment mdb_cursor_txn(MemorySegment cursor) { + try { + return (MemorySegment) mdb_cursor_txn.invoke(cursor); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_cursor_txn", t.getMessage(), t); + } + } + + public static int mdb_cursor_dbi(MemorySegment cursor) { + try { + return (int) mdb_cursor_dbi.invoke(cursor); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_cursor_dbi", t.getMessage(), t); + } + } + + public static int mdb_cursor_get(MemorySegment cursor, MDB_val key, MDB_val data, int op) { + try { + return (int) mdb_cursor_get.invoke(cursor, key.segment(), data.segment(), op); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_cursor_get", t.getMessage(), t); + } + } + + public static int mdb_cursor_put(MemorySegment cursor, MDB_val key, MDB_val data, int flags) { + try { + return (int) mdb_cursor_put.invoke(cursor, key.segment(), data.segment(), flags); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_cursor_put", t.getMessage(), t); + } + } + + public static int mdb_cursor_del(MemorySegment cursor, int flags) { + try { + return (int) mdb_cursor_del.invoke(cursor, flags); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_cursor_del", t.getMessage(), t); + } + } + + public static int mdb_cursor_count(MemorySegment cursor, MemorySegment countPtr) { + try { + return (int) mdb_cursor_count.invoke(cursor, countPtr); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_cursor_count", t.getMessage(), t); + } + } + + // Comparison functions + public static int mdb_cmp(MemorySegment txn, int dbi, MDB_val a, MDB_val b) { + try { + return (int) mdb_cmp.invoke(txn, dbi, a.segment(), b.segment()); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_cmp", t.getMessage(), t); + } + } + + public static int mdb_dcmp(MemorySegment txn, int dbi, MDB_val a, MDB_val b) { + try { + return (int) mdb_dcmp.invoke(txn, dbi, a.segment(), b.segment()); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_dcmp", t.getMessage(), t); + } + } + + /** + * Set a custom key comparison function for a database. + * Must be called before any data access operations. + * The Arena must remain alive for the lifetime of the database. + */ + public static int mdb_set_compare(MemorySegment txn, int dbi, ComparatorCallback comparator, Arena arena) { + try { + // Create a method handle that adapts the comparator + MethodHandle adapterHandle = createComparatorAdapter(comparator); + + // Create upcall stub + MemorySegment stub = LINKER.upcallStub(adapterHandle, MDB_CMP_FUNC_DESC, arena); + + // Set the comparator + return (int) mdb_set_compare.invoke(txn, dbi, stub); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_set_compare", t.getMessage(), t); + } + } + + /** + * Set a custom duplicate data comparison function for a database. + * Only meaningful for databases opened with MDB_DUPSORT flag. + * Must be called before any data access operations. + * The Arena must remain alive for the lifetime of the database. + */ + public static int mdb_set_dupsort(MemorySegment txn, int dbi, ComparatorCallback comparator, Arena arena) { + try { + // Create a method handle that adapts the comparator + MethodHandle adapterHandle = createComparatorAdapter(comparator); + + // Create upcall stub + MemorySegment stub = LINKER.upcallStub(adapterHandle, MDB_CMP_FUNC_DESC, arena); + + // Set the comparator + return (int) mdb_set_dupsort.invoke(txn, dbi, stub); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_set_dupsort", t.getMessage(), t); + } + } + + /** + * Create a method handle adapter for the comparator + */ + private static MethodHandle createComparatorAdapter(ComparatorCallback comparator) throws Throwable { + // Create a lambda that wraps MemorySegment pointers as MDB_val + java.util.function.BiFunction adapter = + (aPtr, bPtr) -> { + MDB_val a = new MDB_val(aPtr.reinterpret(MDB_val.layout().byteSize())); + MDB_val b = new MDB_val(bPtr.reinterpret(MDB_val.layout().byteSize())); + return comparator.compare(a, b); + }; + + // Get method handle for the adapter + return MethodHandles.lookup() + .findVirtual( + java.util.function.BiFunction.class, + "apply", + MethodType.methodType(Object.class, Object.class, Object.class) + ) + .bindTo(adapter) + .asType(MethodType.methodType( + int.class, MemorySegment.class, MemorySegment.class + )); + } + + + // Utility functions + public static String mdb_strerror(int err) { + try { + MemorySegment result = (MemorySegment) mdb_strerror.invoke(err); + return result.reinterpret(Long.MAX_VALUE).getString(0); + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_cmp", t.getMessage(), t); + } + } + + public static int mdb_reader_check(MemorySegment env, MemorySegment resultPtr) { + try { + return (int) mdb_reader_check.invoke(env, resultPtr); + + } catch (final Throwable t) { + throw new LmdbInvokeException("mdb_cmp", t.getMessage(), t); + } + } + + private static Path extract(final String name) { + final String suffix = name.substring(name.lastIndexOf('.')); + final Path file; + try { + final Path dir = Paths.get(EXTRACT_DIR); + if (!Files.exists(dir) || !Files.isDirectory(dir)) { + throw new IllegalStateException("Invalid extraction directory " + dir); + } + file = Files.createTempFile(dir, "lmdbjava-native-library-", suffix); + // Register for deletion on JVM exit + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + try { + Files.deleteIfExists(file); + } catch (IOException e) { + // Log or ignore + } + })); + + final ClassLoader cl = currentThread().getContextClassLoader(); + try (InputStream in = cl.getResourceAsStream(name); + OutputStream out = Files.newOutputStream(file)) { + requireNonNull(in, "Classpath resource not found"); + int bytes; + final byte[] buffer = new byte[4_096]; + while (-1 != (bytes = in.read(buffer))) { + out.write(buffer, 0, bytes); + } + } + return file; + } catch (final IOException e) { + throw new LmdbException("Failed to extract " + name, e); + } + } +} diff --git a/src/main/java/org/lmdbjava/LmdbInvokeException.java b/src/main/java/org/lmdbjava/LmdbInvokeException.java new file mode 100644 index 00000000..f0c0c2e3 --- /dev/null +++ b/src/main/java/org/lmdbjava/LmdbInvokeException.java @@ -0,0 +1,51 @@ +/* + * 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; + +/** Superclass for all LmdbJava custom exceptions. */ +public class LmdbInvokeException extends LmdbException { + + private static final long serialVersionUID = 1L; + + private final String lmdbMethod; + + /** + * Constructs an instance with the provided detailed message. + * + * @param message the detail message + */ + public LmdbInvokeException(final String lmdbMethod, final String message) { + super("Error calling '" + lmdbMethod + "' " + message); + this.lmdbMethod = lmdbMethod; + } + + /** + * Constructs an instance with the provided detailed message and cause. + * + * @param message the detail message + * @param cause original cause + */ + public LmdbInvokeException(final String lmdbMethod, + final String message, + final Throwable cause) { + super("Error calling '" + lmdbMethod + "' " + message, cause); + this.lmdbMethod = lmdbMethod; + } + + public String getLmdbMethod() { + return lmdbMethod; + } +} diff --git a/src/main/java/org/lmdbjava/Meta.java b/src/main/java/org/lmdbjava/Meta.java index 9f51e1af..d0487c4b 100644 --- a/src/main/java/org/lmdbjava/Meta.java +++ b/src/main/java/org/lmdbjava/Meta.java @@ -15,9 +15,9 @@ */ package org.lmdbjava; -import static org.lmdbjava.Library.LIB; - -import jnr.ffi.byref.IntByReference; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; /** LMDB metadata functions. */ public final class Meta { @@ -36,7 +36,7 @@ private Meta() {} * @return the description */ public static String error(final int err) { - return LIB.mdb_strerror(err); + return Lmdb.mdb_strerror(err); } /** @@ -44,14 +44,17 @@ public static String error(final int err) { * * @return the version data */ - public static Version version() { - final IntByReference major = new IntByReference(); - final IntByReference minor = new IntByReference(); - final IntByReference patch = new IntByReference(); + public static Version version(final Arena arena) { + final MemorySegment major = arena.allocate(ValueLayout.JAVA_INT); + final MemorySegment minor = arena.allocate(ValueLayout.JAVA_INT); + final MemorySegment patch = arena.allocate(ValueLayout.JAVA_INT); - LIB.mdb_version(major, minor, patch); + Lmdb.mdb_version(major, minor, patch); - return new Version(major.intValue(), minor.intValue(), patch.intValue()); + return new Version( + major.get(ValueLayout.JAVA_INT, 0), + minor.get(ValueLayout.JAVA_INT, 0), + patch.get(ValueLayout.JAVA_INT, 0)); } /** Immutable return value from {@link #version()}. */ diff --git a/src/main/java/org/lmdbjava/Txn.java b/src/main/java/org/lmdbjava/Txn.java index 05e8ce06..b599be8d 100644 --- a/src/main/java/org/lmdbjava/Txn.java +++ b/src/main/java/org/lmdbjava/Txn.java @@ -15,11 +15,11 @@ */ package org.lmdbjava; -import static jnr.ffi.Memory.allocateDirect; -import static jnr.ffi.NativeType.ADDRESS; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; + 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; @@ -29,8 +29,6 @@ import static org.lmdbjava.Txn.State.RESET; import static org.lmdbjava.TxnFlags.MDB_RDONLY_TXN; -import jnr.ffi.Pointer; - /** * LMDB transaction. * @@ -41,14 +39,16 @@ public final class Txn implements AutoCloseable { private final KeyVal keyVal; private final Txn parent; private final BufferProxy proxy; - private final Pointer ptr; + private final MemorySegment ptr; private final boolean readOnly; + private final Arena arena; private final Env env; private State state; - Txn(final Env env, final Txn parent, final BufferProxy proxy, final TxnFlags... flags) { + Txn(final Arena arena, final Env env, final Txn parent, final BufferProxy proxy, final TxnFlags... flags) { + this.arena = arena; this.proxy = proxy; - this.keyVal = proxy.keyVal(); + this.keyVal = proxy.keyVal(arena); final int flagsMask = mask(true, flags); this.readOnly = isSet(flagsMask, MDB_RDONLY_TXN); if (env.isReadOnly() && !this.readOnly) { @@ -59,10 +59,10 @@ public final class Txn implements AutoCloseable { if (parent != null && parent.isReadOnly() != this.readOnly) { throw new IncompatibleParent(); } - final Pointer txnPtr = allocateDirect(RUNTIME, ADDRESS); - final Pointer txnParentPtr = parent == null ? null : parent.ptr; - checkRc(LIB.mdb_txn_begin(env.pointer(), txnParentPtr, flagsMask, txnPtr)); - ptr = txnPtr.getPointer(0); + final MemorySegment txnPtr = arena.allocate(ValueLayout.ADDRESS); + final MemorySegment txnParentPtr = parent == null ? MemorySegment.NULL : parent.ptr; + checkRc(Lmdb.mdb_txn_begin(env.pointer(), txnParentPtr, flagsMask, txnPtr)); + ptr = txnPtr.get(ValueLayout.ADDRESS, 0); state = READY; } @@ -74,7 +74,7 @@ public void abort() { } checkReady(); state = DONE; - LIB.mdb_txn_abort(ptr); + Lmdb.mdb_txn_abort(ptr); } /** @@ -92,7 +92,7 @@ public void close() { return; } if (state == READY) { - LIB.mdb_txn_abort(ptr); + Lmdb.mdb_txn_abort(ptr); } keyVal.close(); state = RELEASED; @@ -105,7 +105,7 @@ public void commit() { } checkReady(); state = DONE; - checkRc(LIB.mdb_txn_commit(ptr)); + checkRc(Lmdb.mdb_txn_commit(ptr)); } /** @@ -117,7 +117,7 @@ public long getId() { if (SHOULD_CHECK) { env.checkNotClosed(); } - return LIB.mdb_txn_id(ptr); + return Lmdb.mdb_txn_id(ptr); } /** @@ -159,7 +159,7 @@ public void renew() { throw new NotResetException(); } state = DONE; - checkRc(LIB.mdb_txn_renew(ptr)); + checkRc(Lmdb.mdb_txn_renew(ptr)); state = READY; } @@ -176,7 +176,7 @@ public void reset() { throw new ResetException(); } state = RESET; - LIB.mdb_txn_reset(ptr); + Lmdb.mdb_txn_reset(ptr); } /** @@ -223,10 +223,10 @@ KeyVal kv() { } KeyVal newKeyVal() { - return proxy.keyVal(); + return proxy.keyVal(arena); } - Pointer pointer() { + MemorySegment pointer() { return ptr; } diff --git a/src/main/java/org/lmdbjava/UnsafeAccess.java b/src/main/java/org/lmdbjava/UnsafeAccess.java deleted file mode 100644 index 5b3da017..00000000 --- a/src/main/java/org/lmdbjava/UnsafeAccess.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.lang.Boolean.getBoolean; - -import java.lang.reflect.Field; -import sun.misc.Unsafe; - -/** Provides access to Unsafe. */ -final class UnsafeAccess { - - /** 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. */ - 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. - */ - static final Unsafe UNSAFE; - - /** Unsafe field name (used to reflectively obtain the unsafe instance). */ - private static final String FIELD_NAME_THE_UNSAFE = "theUnsafe"; - - static { - if (!ALLOW_UNSAFE) { - throw new LmdbException("Unsafe disabled by user"); - } - try { - 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) { - throw new LmdbException("Unsafe unavailable", e); - } - } - - private UnsafeAccess() {} -} diff --git a/src/main/java/org/lmdbjava/UnsignedByteBufferComparator.java b/src/main/java/org/lmdbjava/UnsignedByteBufferComparator.java new file mode 100644 index 00000000..99851fe3 --- /dev/null +++ b/src/main/java/org/lmdbjava/UnsignedByteBufferComparator.java @@ -0,0 +1,28 @@ +package org.lmdbjava; + +import java.nio.ByteBuffer; +import java.util.Comparator; + +class UnsignedByteBufferComparator implements Comparator { + + @Override + public int compare(final ByteBuffer o1, final ByteBuffer o2) { + // Find the first index where the two buffers don't match. + final int i = o1.mismatch(o2); + + // If the length of both buffers are equal and mismatch is the length then return 0 for equal. + final int thisPos = o1.position(); + final int thisRem = o1.limit() - thisPos; + final int thatPos = o2.position(); + final int thatRem = o2.limit() - thatPos; + if (thisRem == thatRem && i == thatRem) { + return 0; + } + + if (i >= 0 && i < thisRem && i < thatRem) { + return Byte.compareUnsigned(o1.get(thisPos + i), o2.get(thatPos + i)); + } + + return thisRem - thatRem; + } +} diff --git a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java index 8044b9e8..38a2a615 100644 --- a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java +++ b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java @@ -15,45 +15,36 @@ */ package org.lmdbjava; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.lmdbjava.ByteBufferProxy.BufferMustBeDirectException; +import org.lmdbjava.Lmdb.MDB_val; + +import java.io.File; +import java.io.IOException; +import java.lang.foreign.Arena; +import java.nio.ByteBuffer; + import static java.lang.Integer.BYTES; import static java.nio.ByteBuffer.allocate; 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.lmdbjava.BufferProxy.MDB_VAL_STRUCT_SIZE; -import static org.lmdbjava.ByteBufferProxy.AbstractByteBufferProxy.findField; -import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; -import static org.lmdbjava.ByteBufferProxy.PROXY_SAFE; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.Env.create; -import static org.lmdbjava.Library.RUNTIME; import static org.lmdbjava.TestUtils.DB_1; 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.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 { @@ -70,14 +61,15 @@ public void buffersMustBeDirect() throws IOException { @Test public void byteOrderResets() { + final ByteBufferProxy byteBufferProxy = ByteBufferProxy.INSTANCE; final int retries = 100; for (int i = 0; i < retries; i++) { - final ByteBuffer bb = PROXY_OPTIMAL.allocate(); + final ByteBuffer bb = byteBufferProxy.allocate(); bb.order(LITTLE_ENDIAN); - PROXY_OPTIMAL.deallocate(bb); + byteBufferProxy.deallocate(bb); } for (int i = 0; i < retries; i++) { - assertThat(PROXY_OPTIMAL.allocate().order(), is(BIG_ENDIAN)); + assertThat(byteBufferProxy.allocate().order(), is(BIG_ENDIAN)); } } @@ -86,47 +78,9 @@ public void coverPrivateConstructor() { invokePrivateConstructor(ByteBufferProxy.class); } - @Test(expected = LmdbException.class) - public void fieldNeverFound() { - findField(Exception.class, "notARealField"); - } - - @Test - public void fieldSuperclassScan() { - final Field f = findField(ReadersFullException.class, "rc"); - assertThat(f, is(notNullValue())); - } - - @Test - public void inOutBuffersProxyOptimal() { - checkInOut(PROXY_OPTIMAL); - } - - @Test - public void inOutBuffersProxySafe() { - checkInOut(PROXY_SAFE); - } - - @Test - public void optimalAlwaysAvailable() { - final BufferProxy v = PROXY_OPTIMAL; - assertThat(v, is(notNullValue())); - } - @Test - public void safeCanBeForced() { - final BufferProxy v = PROXY_SAFE; - assertThat(v, is(notNullValue())); - assertThat(v.getClass().getSimpleName(), startsWith("Reflect")); - } - - @Test - public void unsafeIsDefault() { - assertThat(ALLOW_UNSAFE, is(true)); - final BufferProxy v = PROXY_OPTIMAL; - assertThat(v, is(notNullValue())); - assertThat(v, is(not(PROXY_SAFE))); - assertThat(v.getClass().getSimpleName(), startsWith("Unsafe")); + public void inOutBuffersProxy() { + checkInOut(ByteBufferProxy.INSTANCE); } private void checkInOut(final BufferProxy v) { @@ -138,14 +92,15 @@ private void checkInOut(final BufferProxy v) { b.flip(); b.position(BYTES); // skip 1 - final Pointer p = MEM_MGR.allocateTemporary(MDB_VAL_STRUCT_SIZE, false); - v.in(b, p, p.address()); + try (final Arena arena = Arena.ofConfined()) { + final MDB_val p = new MDB_val(arena); + v.in(b, p); - final ByteBuffer bb = allocateDirect(1); - v.out(bb, p, p.address()); + final ByteBuffer bb = v.out(p); - assertThat(bb.getInt(), is(2)); - assertThat(bb.getInt(), is(3)); - assertThat(bb.remaining(), is(0)); + assertThat(bb.getInt(), is(2)); + 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 3e265cee..9de22a62 100644 --- a/src/test/java/org/lmdbjava/ComparatorTest.java +++ b/src/test/java/org/lmdbjava/ComparatorTest.java @@ -19,9 +19,7 @@ import static java.nio.charset.StandardCharsets.US_ASCII; import static org.hamcrest.core.Is.is; import static org.junit.Assert.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; @@ -31,6 +29,8 @@ import com.google.common.primitives.SignedBytes; import com.google.common.primitives.UnsignedBytes; import io.netty.buffer.ByteBuf; + +import java.lang.foreign.Arena; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Comparator; @@ -66,7 +66,7 @@ public final class ComparatorTest { public static Object[] data() { final ComparatorRunner string = new StringRunner(); final ComparatorRunner db = new DirectBufferRunner(); - final ComparatorRunner ba = new ByteArrayRunner(); + final ComparatorRunner ba = new ByteArrayRunner(new ByteArrayProxy(Arena.ofAuto())); final ComparatorRunner bb = new ByteBufferRunner(); final ComparatorRunner netty = new NettyRunner(); final ComparatorRunner gub = new GuavaUnsignedBytes(); @@ -133,9 +133,15 @@ public void equalBuffers() { /** Tests {@link ByteArrayProxy}. */ private static final class ByteArrayRunner implements ComparatorRunner { + private final ByteArrayProxy byteArrayProxy; + + public ByteArrayRunner(ByteArrayProxy byteArrayProxy) { + this.byteArrayProxy = byteArrayProxy; + } + @Override public int compare(final byte[] o1, final byte[] o2) { - final Comparator c = PROXY_BA.getComparator(); + final Comparator c = byteArrayProxy.getComparator(); return c.compare(o1, o2); } } @@ -145,7 +151,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 = ByteBufferProxy.INSTANCE.getComparator(); // Convert arrays to buffers that are larger than the array, with // limit set at the array length. One buffer bigger than the other. diff --git a/src/test/java/org/lmdbjava/CursorParamTest.java b/src/test/java/org/lmdbjava/CursorParamTest.java index bd99e709..ef857f00 100644 --- a/src/test/java/org/lmdbjava/CursorParamTest.java +++ b/src/test/java/org/lmdbjava/CursorParamTest.java @@ -21,10 +21,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.collection.IsEmptyCollection.empty; -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.ByteBufferProxy.PROXY_SAFE; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.DbiFlags.MDB_DUPSORT; import static org.lmdbjava.DirectBufferProxy.PROXY_DB; @@ -46,6 +43,7 @@ import io.netty.buffer.ByteBuf; import java.io.File; import java.io.IOException; +import java.lang.foreign.Arena; import java.nio.ByteBuffer; import org.agrona.DirectBuffer; import org.agrona.MutableDirectBuffer; @@ -68,12 +66,11 @@ public final class CursorParamTest { @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 bb1 = new ByteBufferRunner(ByteBufferProxy.INSTANCE); + final BufferRunner ba = new ByteArrayRunner(new ByteArrayProxy(Arena.ofAuto())); final BufferRunner db = new DirectBufferRunner(); final BufferRunner netty = new NettyBufferRunner(); - return new Object[] {bb1, bb2, ba, db, netty}; + return new Object[] {bb1, ba, db, netty}; } @Test diff --git a/src/test/java/org/lmdbjava/CursorTest.java b/src/test/java/org/lmdbjava/CursorTest.java index cf6e4dea..08af972e 100644 --- a/src/test/java/org/lmdbjava/CursorTest.java +++ b/src/test/java/org/lmdbjava/CursorTest.java @@ -23,7 +23,6 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.DbiFlags.MDB_DUPFIXED; import static org.lmdbjava.DbiFlags.MDB_DUPSORT; @@ -71,7 +70,7 @@ public void before() throws IOException { try { final File path = tmp.newFile(); env = - create(PROXY_OPTIMAL) + create(ByteBufferProxy.INSTANCE) .setMapSize(KIBIBYTES.toBytes(1_024)) .setMaxReaders(1) .setMaxDbs(1) diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 1fa80f6e..6281e07c 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -34,8 +34,6 @@ import static org.hamcrest.collection.IsEmptyCollection.empty; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -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; @@ -52,6 +50,7 @@ import java.io.File; import java.io.IOException; +import java.lang.foreign.Arena; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Comparator; @@ -111,7 +110,7 @@ public void close() { public void customComparator() { final Comparator reverseOrder = (o1, o2) -> { - final int lexical = PROXY_OPTIMAL.getComparator().compare(o1, o2); + final int lexical = ByteBufferProxy.INSTANCE.getComparator().compare(o1, o2); if (lexical == 0) { return 0; } @@ -144,7 +143,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 = ByteBufferProxy.INSTANCE.getComparator(flags); final Dbi db = env.openDbi(DB_1, c, true, flags); final List keys = range(0, 1_000).boxed().collect(toList()); @@ -326,7 +325,7 @@ public void putCommitGet() { public void putCommitGetByteArray() throws IOException { final File path = tmp.newFile(); try (Env envBa = - create(PROXY_BA) + create(new ByteArrayProxy(Arena.ofAuto())) .setMapSize(MEBIBYTES.toBytes(64)) .setMaxReaders(1) .setMaxDbs(2) diff --git a/src/test/java/org/lmdbjava/LibraryTest.java b/src/test/java/org/lmdbjava/LibraryTest.java index 6dcfcee9..da684d24 100644 --- a/src/test/java/org/lmdbjava/LibraryTest.java +++ b/src/test/java/org/lmdbjava/LibraryTest.java @@ -15,28 +15,31 @@ */ package org.lmdbjava; -import static java.lang.Long.BYTES; +import org.junit.Test; +import org.lmdbjava.Lmdb.MDB_envinfo; + +import java.lang.foreign.Arena; + import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.lmdbjava.Library.RUNTIME; import static org.lmdbjava.TestUtils.invokePrivateConstructor; -import org.junit.Test; -import org.lmdbjava.Library.MDB_envinfo; - -/** Test {@link Library}. */ +/** + * Test {@link Library}. + */ public final class LibraryTest { @Test public void coverPrivateConstructors() { invokePrivateConstructor(Library.class); - invokePrivateConstructor(UnsafeAccess.class); } @Test public 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)); + try (final Arena arena = Arena.ofConfined()) { + final MDB_envinfo v = new MDB_envinfo(arena); + assertThat(v.meMapaddr().address(), is(0L)); + assertThat(v.meMapsize(), is(0L)); + } } } diff --git a/src/test/java/org/lmdbjava/MetaTest.java b/src/test/java/org/lmdbjava/MetaTest.java index be0faa87..33eb81ed 100644 --- a/src/test/java/org/lmdbjava/MetaTest.java +++ b/src/test/java/org/lmdbjava/MetaTest.java @@ -26,6 +26,8 @@ import org.junit.Test; import org.lmdbjava.Meta.Version; +import java.lang.foreign.Arena; + /** Test {@link Meta}. */ public final class MetaTest { @@ -41,9 +43,11 @@ public void errCode() { @Test public void version() { - final Version v = Meta.version(); - assertThat(v, not(nullValue())); - assertThat(v.major, is(0)); - assertThat(v.minor, is(9)); + try (final Arena arena = Arena.ofConfined()) { + final Version v = Meta.version(arena); + assertThat(v, not(nullValue())); + assertThat(v.major, is(0)); + assertThat(v.minor, is(9)); + } } } diff --git a/src/test/java/org/lmdbjava/TutorialTest.java b/src/test/java/org/lmdbjava/TutorialTest.java index b86c1d2a..f8081243 100644 --- a/src/test/java/org/lmdbjava/TutorialTest.java +++ b/src/test/java/org/lmdbjava/TutorialTest.java @@ -25,7 +25,6 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -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; @@ -404,7 +403,7 @@ public void tutorial5() throws IOException { public void tutorial6() throws IOException { // Note we need to specify the Verifier's DBI_COUNT for the Env. final Env env = - create(PROXY_OPTIMAL) + create(ByteBufferProxy.INSTANCE) .setMapSize(10_485_760) .setMaxDbs(Verifier.DBI_COUNT) .open(tmp.newFolder());