From a3b61beb275534993ce885573f2f7abb246d9987 Mon Sep 17 00:00:00 2001 From: Sergio Vega Date: Tue, 24 Jul 2018 16:19:09 -0500 Subject: [PATCH 001/322] add cursor seek for MDB_GET_BOTH --- src/main/java/org/lmdbjava/Cursor.java | 31 ++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/main/java/org/lmdbjava/Cursor.java b/src/main/java/org/lmdbjava/Cursor.java index 9950cde5..c0293a08 100644 --- a/src/main/java/org/lmdbjava/Cursor.java +++ b/src/main/java/org/lmdbjava/Cursor.java @@ -125,6 +125,37 @@ public boolean first() { return seek(MDB_FIRST); } + /** + * Reposition the key/value buffers based on the passed key and operation. + * + * @param key to search for + * @param data to search for + * @param op options for this operation + * @return false if key not found + */ + public boolean get(final T key, final T data, final SeekOp op) { + if (SHOULD_CHECK) { + requireNonNull(key); + requireNonNull(op); + checkNotClosed(); + txn.checkReady(); + } + kv.keyIn(key); + kv.valIn(data); + + final int rc = LIB.mdb_cursor_get(ptrCursor, kv.pointerKey(), kv + .pointerVal(), op.getCode()); + + if (rc == MDB_NOTFOUND) { + return false; + } + + checkRc(rc); + kv.keyOut(); + kv.valOut(); + return true; + } + /** * Reposition the key/value buffers based on the passed key and operation. * From 9815ffe4a3b671aa50934526af9fb47249701f71 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 8 Feb 2019 13:09:21 +1100 Subject: [PATCH 002/322] Manage addresses using Pointer.[g|s]etAddress(..) (#97) --- src/main/java/org/lmdbjava/ByteArrayProxy.java | 4 ++-- src/main/java/org/lmdbjava/ByteBufferProxy.java | 12 ++++++------ src/main/java/org/lmdbjava/DirectBufferProxy.java | 6 +++--- src/test/java/org/lmdbjava/ByteBufProxy.java | 10 +++++----- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/lmdbjava/ByteArrayProxy.java b/src/main/java/org/lmdbjava/ByteArrayProxy.java index 7e64954d..a7213d09 100644 --- a/src/main/java/org/lmdbjava/ByteArrayProxy.java +++ b/src/main/java/org/lmdbjava/ByteArrayProxy.java @@ -94,7 +94,7 @@ protected final void in(final byte[] buffer, final Pointer ptr, final Pointer pointer = MEM_MGR.allocateDirect(buffer.length); pointer.put(0, buffer, 0, buffer.length); ptr.putLong(STRUCT_FIELD_OFFSET_SIZE, buffer.length); - ptr.putLong(STRUCT_FIELD_OFFSET_DATA, pointer.address()); + ptr.putAddress(STRUCT_FIELD_OFFSET_DATA, pointer.address()); } @Override @@ -106,7 +106,7 @@ protected final void in(final byte[] buffer, final int size, final Pointer ptr, @Override protected final byte[] out(final byte[] buffer, final Pointer ptr, final long ptrAddr) { - final long addr = ptr.getLong(STRUCT_FIELD_OFFSET_DATA); + 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]; diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index ba3f8206..ac313897 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -225,7 +225,7 @@ private static final class ReflectiveProxy extends AbstractByteBufferProxy { @Override protected void in(final ByteBuffer buffer, final Pointer ptr, final long ptrAddr) { - ptr.putLong(STRUCT_FIELD_OFFSET_DATA, address(buffer)); + ptr.putAddress(STRUCT_FIELD_OFFSET_DATA, address(buffer)); ptr.putLong(STRUCT_FIELD_OFFSET_SIZE, buffer.remaining()); } @@ -233,13 +233,13 @@ protected void in(final ByteBuffer buffer, final Pointer ptr, protected void in(final ByteBuffer buffer, final int size, final Pointer ptr, final long ptrAddr) { ptr.putLong(STRUCT_FIELD_OFFSET_SIZE, size); - ptr.putLong(STRUCT_FIELD_OFFSET_DATA, address(buffer)); + 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.getLong(STRUCT_FIELD_OFFSET_DATA); + final long addr = ptr.getAddress(STRUCT_FIELD_OFFSET_DATA); final long size = ptr.getLong(STRUCT_FIELD_OFFSET_SIZE); try { ADDRESS_FIELD.set(buffer, addr); @@ -277,20 +277,20 @@ private static final class UnsafeProxy extends AbstractByteBufferProxy { 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)); + UNSAFE.putAddress(ptrAddr + STRUCT_FIELD_OFFSET_DATA, address(buffer)); } @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)); + UNSAFE.putAddress(ptrAddr + STRUCT_FIELD_OFFSET_DATA, address(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 addr = UNSAFE.getAddress(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); diff --git a/src/main/java/org/lmdbjava/DirectBufferProxy.java b/src/main/java/org/lmdbjava/DirectBufferProxy.java index 28aa7ff1..105a74fb 100644 --- a/src/main/java/org/lmdbjava/DirectBufferProxy.java +++ b/src/main/java/org/lmdbjava/DirectBufferProxy.java @@ -130,7 +130,7 @@ 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.putAddress(ptrAddr + STRUCT_FIELD_OFFSET_DATA, addr); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, size); } @@ -138,14 +138,14 @@ protected void in(final DirectBuffer buffer, final Pointer ptr, 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.putAddress(ptrAddr + STRUCT_FIELD_OFFSET_DATA, addr); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, size); } @Override protected DirectBuffer out(final DirectBuffer buffer, final Pointer ptr, final long ptrAddr) { - final long addr = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA); + final long addr = UNSAFE.getAddress(ptrAddr + STRUCT_FIELD_OFFSET_DATA); final long size = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE); buffer.wrap(addr, (int) size); return buffer; diff --git a/src/test/java/org/lmdbjava/ByteBufProxy.java b/src/test/java/org/lmdbjava/ByteBufProxy.java index 8826a76b..a11214fc 100644 --- a/src/test/java/org/lmdbjava/ByteBufProxy.java +++ b/src/test/java/org/lmdbjava/ByteBufProxy.java @@ -121,8 +121,8 @@ protected byte[] getBytes(final ByteBuf buffer) { protected void in(final ByteBuf buffer, final Pointer ptr, final long ptrAddr) { UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, buffer.writerIndex() - buffer.readerIndex()); - UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA, - buffer.memoryAddress() + buffer.readerIndex()); + UNSAFE.putAddress(ptrAddr + STRUCT_FIELD_OFFSET_DATA, + buffer.memoryAddress() + buffer.readerIndex()); } @Override @@ -130,14 +130,14 @@ 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()); + UNSAFE.putAddress(ptrAddr + STRUCT_FIELD_OFFSET_DATA, + buffer.memoryAddress() + buffer.readerIndex()); } @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 addr = UNSAFE.getAddress(ptrAddr + STRUCT_FIELD_OFFSET_DATA); final long size = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE); UNSAFE.putLong(buffer, ADDRESS_OFFSET, addr); UNSAFE.putInt(buffer, LENGTH_OFFSET, (int) size); From c9d48670857657c83d1f282588f5db7cbf38866c Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 8 Feb 2019 13:14:40 +1100 Subject: [PATCH 003/322] AppVeyor test with Java 11 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 156e098d..2623c2b0 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,7 +5,7 @@ image: Visual Studio 2017 clone_depth: 50 environment: - JAVA_HOME: "C:\\Program Files\\Java\\jdk1.8.0" + JAVA_HOME: "C:\\Program Files\\Java\\jdk11" MVN_VER: 3.6.0 PYTHON: "C:\\Python35" From cd48ecf6dd313ae2cd434b0598911e8ab5071097 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 8 Feb 2019 13:17:49 +1100 Subject: [PATCH 004/322] Update to LMDB native 0.9.23-1 (closes #115) --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index ce220f0e..36b83688 100644 --- a/pom.xml +++ b/pom.xml @@ -129,19 +129,19 @@ org.lmdbjava lmdbjava-native-linux-x86_64 - 0.9.22-2 + 0.9.23-1 true org.lmdbjava lmdbjava-native-windows-x86_64 - 0.9.22-2 + 0.9.23-1 true org.lmdbjava lmdbjava-native-osx-x86_64 - 0.9.22-2 + 0.9.23-1 true From a5ef8b55cc4b6a1af89325e6cd59b77ad96802ed Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 8 Feb 2019 14:08:32 +1100 Subject: [PATCH 005/322] Continue to use UNSAFE.[get|put]Long(..) (#97) --- src/main/java/org/lmdbjava/ByteBufferProxy.java | 6 +++--- src/main/java/org/lmdbjava/DirectBufferProxy.java | 6 +++--- src/test/java/org/lmdbjava/ByteBufProxy.java | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index ac313897..3d358856 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -277,20 +277,20 @@ private static final class UnsafeProxy extends AbstractByteBufferProxy { protected void in(final ByteBuffer buffer, final Pointer ptr, final long ptrAddr) { UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, buffer.remaining()); - UNSAFE.putAddress(ptrAddr + STRUCT_FIELD_OFFSET_DATA, address(buffer)); + UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA, address(buffer)); } @Override protected void in(final ByteBuffer buffer, final int size, final Pointer ptr, final long ptrAddr) { UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, size); - UNSAFE.putAddress(ptrAddr + STRUCT_FIELD_OFFSET_DATA, address(buffer)); + UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA, address(buffer)); } @Override protected ByteBuffer out(final ByteBuffer buffer, final Pointer ptr, final long ptrAddr) { - final long addr = UNSAFE.getAddress(ptrAddr + STRUCT_FIELD_OFFSET_DATA); + 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); diff --git a/src/main/java/org/lmdbjava/DirectBufferProxy.java b/src/main/java/org/lmdbjava/DirectBufferProxy.java index 105a74fb..28aa7ff1 100644 --- a/src/main/java/org/lmdbjava/DirectBufferProxy.java +++ b/src/main/java/org/lmdbjava/DirectBufferProxy.java @@ -130,7 +130,7 @@ protected void in(final DirectBuffer buffer, final Pointer ptr, final long ptrAddr) { final long addr = buffer.addressOffset(); final long size = buffer.capacity(); - UNSAFE.putAddress(ptrAddr + STRUCT_FIELD_OFFSET_DATA, addr); + UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA, addr); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, size); } @@ -138,14 +138,14 @@ protected void in(final DirectBuffer buffer, final Pointer ptr, protected void in(final DirectBuffer buffer, final int size, final Pointer ptr, final long ptrAddr) { final long addr = buffer.addressOffset(); - UNSAFE.putAddress(ptrAddr + STRUCT_FIELD_OFFSET_DATA, addr); + UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA, addr); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, size); } @Override protected DirectBuffer out(final DirectBuffer buffer, final Pointer ptr, final long ptrAddr) { - final long addr = UNSAFE.getAddress(ptrAddr + STRUCT_FIELD_OFFSET_DATA); + final long addr = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA); final long size = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE); buffer.wrap(addr, (int) size); return buffer; diff --git a/src/test/java/org/lmdbjava/ByteBufProxy.java b/src/test/java/org/lmdbjava/ByteBufProxy.java index a11214fc..8826a76b 100644 --- a/src/test/java/org/lmdbjava/ByteBufProxy.java +++ b/src/test/java/org/lmdbjava/ByteBufProxy.java @@ -121,8 +121,8 @@ protected byte[] getBytes(final ByteBuf buffer) { protected void in(final ByteBuf buffer, final Pointer ptr, final long ptrAddr) { UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, buffer.writerIndex() - buffer.readerIndex()); - UNSAFE.putAddress(ptrAddr + STRUCT_FIELD_OFFSET_DATA, - buffer.memoryAddress() + buffer.readerIndex()); + UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA, + buffer.memoryAddress() + buffer.readerIndex()); } @Override @@ -130,14 +130,14 @@ protected void in(final ByteBuf buffer, final int size, final Pointer ptr, final long ptrAddr) { UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, size); - UNSAFE.putAddress(ptrAddr + STRUCT_FIELD_OFFSET_DATA, - buffer.memoryAddress() + buffer.readerIndex()); + UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA, + buffer.memoryAddress() + buffer.readerIndex()); } @Override protected ByteBuf out(final ByteBuf buffer, final Pointer ptr, final long ptrAddr) { - final long addr = UNSAFE.getAddress(ptrAddr + STRUCT_FIELD_OFFSET_DATA); + 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, LENGTH_OFFSET, (int) size); From 3e88ba9aaf68b6530332130c0dc0cf35c59bab70 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 8 Feb 2019 14:09:21 +1100 Subject: [PATCH 006/322] Update to Netty 4.1.33 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 36b83688..d7ee1a70 100644 --- a/pom.xml +++ b/pom.xml @@ -111,7 +111,7 @@ io.netty netty-all - 4.1.32.Final + 4.1.33.Final test From 6617d80abe495187eca63bcfc8b2029ee00b07e9 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 8 Feb 2019 14:48:37 +1100 Subject: [PATCH 007/322] Ensure buffer returned by Netty is as expected --- src/test/java/org/lmdbjava/ByteBufProxy.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/lmdbjava/ByteBufProxy.java b/src/test/java/org/lmdbjava/ByteBufProxy.java index 8826a76b..670a5d59 100644 --- a/src/test/java/org/lmdbjava/ByteBufProxy.java +++ b/src/test/java/org/lmdbjava/ByteBufProxy.java @@ -56,7 +56,10 @@ public final class ByteBufProxy extends BufferProxy { static { try { // create buffer (first is SimpleLeakAwareByteBuff but we need PooledUDBB) - DEFAULT.directBuffer(0); + final ByteBuf bb = DEFAULT.directBuffer(0); + if (!NAME.equals(bb.getClass().getName())) { + throw new IllegalStateException("Netty buffer must be " + NAME); + } final Field address = findField(NAME, FIELD_NAME_ADDRESS); final Field length = findField(NAME, FIELD_NAME_LENGTH); ADDRESS_OFFSET = UNSAFE.objectFieldOffset(address); From a7f478bdd8083ee337c22cb9e6f2e7009a64b7a1 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 8 Feb 2019 15:05:14 +1100 Subject: [PATCH 008/322] Update to Agrona 0.9.32 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d7ee1a70..38241b98 100644 --- a/pom.xml +++ b/pom.xml @@ -105,7 +105,7 @@ org.agrona agrona - 0.9.28 + 0.9.32 true From 034f6100401ad233d81f3a180e67ff935f9bd141 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 8 Feb 2019 15:10:32 +1100 Subject: [PATCH 009/322] [maven-release-plugin] prepare release lmdbjava-0.6.3 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 38241b98..0821f8f2 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ org.lmdbjava lmdbjava - 0.6.3-SNAPSHOT + 0.6.3 jar LmdbJava Low latency Java API for the ultra-fast, embedded Symas Lightning Database (LMDB) @@ -45,7 +45,7 @@ scm:git:git@github.com:${github.org}/${github.repo}.git scm:git:git@github.com:${github.org}/${github.repo}.git git@github.com:${github.org}/${github.repo}.git - HEAD + lmdbjava-0.6.3 GitHub Issues From 294dcf8113b5627ba8b27454eb51aed063f5a45a Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 8 Feb 2019 15:11:14 +1100 Subject: [PATCH 010/322] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 0821f8f2..a8bdbd43 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ org.lmdbjava lmdbjava - 0.6.3 + 0.6.4-SNAPSHOT jar LmdbJava Low latency Java API for the ultra-fast, embedded Symas Lightning Database (LMDB) @@ -45,7 +45,7 @@ scm:git:git@github.com:${github.org}/${github.repo}.git scm:git:git@github.com:${github.org}/${github.repo}.git git@github.com:${github.org}/${github.repo}.git - lmdbjava-0.6.3 + HEAD GitHub Issues From 1fee606fdc8a1f99c6ce808c16e148585c87df02 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Thu, 21 Mar 2019 18:10:14 +1100 Subject: [PATCH 011/322] Update to Agrona 0.9.34 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a8bdbd43..90f95710 100644 --- a/pom.xml +++ b/pom.xml @@ -105,7 +105,7 @@ org.agrona agrona - 0.9.32 + 0.9.34 true From ee6ee3d4ce26251b98c26414c4744e741390a50d Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Thu, 21 Mar 2019 18:10:22 +1100 Subject: [PATCH 012/322] Update to Netty 4.1.34 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 90f95710..490b8b07 100644 --- a/pom.xml +++ b/pom.xml @@ -111,7 +111,7 @@ io.netty netty-all - 4.1.33.Final + 4.1.34.Final test From 498db3dbbabbf1141909826434ce6285a94d0411 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Thu, 21 Mar 2019 18:10:29 +1100 Subject: [PATCH 013/322] Update to Guava 27.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 490b8b07..370f1f78 100644 --- a/pom.xml +++ b/pom.xml @@ -117,7 +117,7 @@ com.google.guava guava - 27.0.1-jre + 27.1-jre test From ddf342eebfff0253f8c7a2c86434620c8ab025a1 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Thu, 21 Mar 2019 18:10:41 +1100 Subject: [PATCH 014/322] Update to Acegi Standard Project 0.2.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 370f1f78..ec1e6ac0 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ au.com.acegi acegi-standard-project - 0.1.9 + 0.2.1 org.lmdbjava lmdbjava From a927565b215421e0b5be1fb43a2fa09e0942610d Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 5 Apr 2019 12:46:56 +1100 Subject: [PATCH 015/322] Initial environment verifier (#124) --- src/main/java/org/lmdbjava/Verifier.java | 307 +++++++++++++++++++ src/test/java/org/lmdbjava/TutorialTest.java | 31 +- src/test/java/org/lmdbjava/VerifierTest.java | 57 ++++ 3 files changed, 394 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/lmdbjava/Verifier.java create mode 100644 src/test/java/org/lmdbjava/VerifierTest.java diff --git a/src/main/java/org/lmdbjava/Verifier.java b/src/main/java/org/lmdbjava/Verifier.java new file mode 100644 index 00000000..9ce5c008 --- /dev/null +++ b/src/main/java/org/lmdbjava/Verifier.java @@ -0,0 +1,307 @@ +/*- + * #%L + * LmdbJava + * %% + * Copyright (C) 2016 - 2019 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. + * #L% + */ + +package org.lmdbjava; + +import java.nio.ByteBuffer; +import static java.nio.ByteOrder.BIG_ENDIAN; +import java.util.ArrayList; +import java.util.List; +import static java.util.Objects.requireNonNull; +import java.util.Random; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.zip.CRC32; +import static org.lmdbjava.DbiFlags.MDB_CREATE; + +/** + * Verifies correct operation of LmdbJava in a given environment. + * + *

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

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

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

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

    + *
  1. Ensure the {@link Env} passed at construction time complies with the + * requirements specified at {@link #Verifier(org.lmdbjava.Env)}
  2. + *
  3. Attempt to use a different file system to store the database (be + * especially careful to not use network file systems, remote file systems, + * read-only file systems etc)
  4. + *
  5. Record the full exception message and stack trace, then run the verifier + * again to see if it fails at the same or a different point
  6. + *
  7. Raise a ticket on the LmdbJava Issue Tracker that confirms the above + * details along with the failing operating system and Java version
  8. + *
+ * + */ +public final class Verifier implements Callable { + + /** + * Number of DBIs the created environment should allow. + */ + public static final int DBI_COUNT = 5; + private static final int BATCH_SIZE = 64; + private static final int BUFFER_LEN = 1024 * BATCH_SIZE; + private static final int CRC_LENGTH = Long.BYTES; + private static final int KEY_LENGTH = Long.BYTES; + private final byte[] ba = new byte[BUFFER_LEN]; + private final CRC32 crc = new CRC32(); + private final List> dbis = new ArrayList<>(DBI_COUNT); + private final Env env; + private long id; + private final ByteBuffer key = ByteBuffer.allocateDirect(KEY_LENGTH); + private final AtomicBoolean proceed = new AtomicBoolean(true); + private final Random rnd = new Random(); + private Txn txn; + private final ByteBuffer val = ByteBuffer.allocateDirect(BUFFER_LEN); + + /** + * Create an instance of the verifier. + * + *

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

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

+ * Successful return of this method indicates no faults were detected. If any + * fault was detected the exception message will detail the exact point that + * the fault was encountered. + * + * @return number of database rows successfully verified + */ + @Override + public Long call() { + try { + while (proceed.get()) { + transactionControl(); + + write(id); + + if (id > 0) { + fetchAndDelete(id - 1); + } + + id++; + } + } finally { + if (txn != null) { + txn.close(); + } + } + return id; + } + + /** + * Execute the verifier for the given duration. + * + *

+ * This provides a simple way to execute the verifier for those applications + * which do not wish to manage threads directly. + * + * @param duration amount of time to execute + * @param unit units used to express the duration + * @return number of database rows successfully verified + */ + public long runFor(final long duration, final TimeUnit unit) { + final long deadline = System.currentTimeMillis() + unit.toMillis(duration); + final ExecutorService es = Executors.newSingleThreadExecutor(); + final Future future = es.submit(this); + try { + while (System.currentTimeMillis() < deadline && !future.isDone()) { + Thread.sleep(unit.toMillis(1)); + } + } catch (final InterruptedException ignored) { + } finally { + stop(); + } + final long result; + try { + result = future.get(); + } catch (final InterruptedException | ExecutionException ex) { + throw new IllegalStateException(ex); + } finally { + es.shutdown(); + } + return result; + } + + private void createDbis() { + for (int i = 0; i < DBI_COUNT; i++) { + dbis.add(env.openDbi(Verifier.class.getSimpleName() + i, MDB_CREATE)); + } + } + + private void deleteDbis() { + for (final byte[] existingDbiName : env.getDbiNames()) { + final Dbi existingDbi = env.openDbi(existingDbiName); + try (Txn txn = env.txnWrite()) { + existingDbi.drop(txn, true); + txn.commit(); + } + } + } + + private void fetchAndDelete(final long forId) { + final Dbi dbi = getDbi(forId); + updateKey(forId); + final ByteBuffer fetchedValue; + try { + fetchedValue = dbi.get(txn, key); + } catch (final LmdbException ex) { + throw new IllegalStateException("DB get id=" + forId, ex); + } + + if (fetchedValue == null) { + throw new IllegalStateException("DB not found id=" + forId); + } + + verifyValue(forId, fetchedValue); + + try { + dbi.delete(txn, key); + } catch (final LmdbException ex) { + throw new IllegalStateException("DB del id=" + forId, ex); + } + } + + private Dbi getDbi(final long forId) { + return dbis.get((int) (forId % dbis.size())); + } + + /** + * Request the verifier to stop execution. + */ + private void stop() { + proceed.set(false); + } + + private void transactionControl() { + if (id % BATCH_SIZE == 0) { + if (txn != null) { + txn.commit(); + txn.close(); + } + rnd.nextBytes(ba); + txn = env.txnWrite(); + } + } + + private void updateKey(final long forId) { + key.clear(); + key.putLong(forId); + key.flip(); + } + + private void updateValue(final long forId) { + final int rndSize = valueSize(forId); + crc.reset(); + crc.update((int) forId); + crc.update(ba, CRC_LENGTH, rndSize); + final long crcVal = crc.getValue(); + + val.clear(); + val.putLong(crcVal); + val.put(ba, CRC_LENGTH, rndSize); + val.flip(); + } + + private int valueSize(final long forId) { + final int mod = (int) (forId % BATCH_SIZE); + final int base = 1024 * mod; + final int value = base == 0 ? 512 : base; + return value - CRC_LENGTH - KEY_LENGTH; // aim to minimise partial pages + } + + private void verifyValue(final long forId, final ByteBuffer bb) { + final int rndSize = valueSize(forId); + final int expected = rndSize + CRC_LENGTH; + if (bb.limit() != expected) { + throw new IllegalStateException("Limit error id=" + forId + " exp=" + + expected + " limit=" + bb.limit()); + } + + final long crcRead = bb.getLong(); + crc.reset(); + crc.update((int) forId); + crc.update(bb); + final long crcVal = crc.getValue(); + + if (crcRead != crcVal) { + throw new IllegalStateException("CRC error id=" + forId); + } + } + + private void write(final long forId) { + final Dbi dbi = getDbi(forId); + updateKey(forId); + updateValue(forId); + + try { + dbi.put(txn, key, val); + } catch (final LmdbException ex) { + throw new IllegalStateException("DB put id=" + forId, ex); + } + } + +} diff --git a/src/test/java/org/lmdbjava/TutorialTest.java b/src/test/java/org/lmdbjava/TutorialTest.java index 1f411d85..4b256ca9 100644 --- a/src/test/java/org/lmdbjava/TutorialTest.java +++ b/src/test/java/org/lmdbjava/TutorialTest.java @@ -40,6 +40,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; +import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; import org.lmdbjava.CursorIterator.KeyVal; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.DbiFlags.MDB_DUPSORT; @@ -415,13 +416,41 @@ public void tutorial5() throws IOException { } /** - * In this final tutorial we'll look at using Agrona's DirectBuffer. + * Next up we'll show you how to easily check your platform (operating system + * and Java version) is working properly with LmdbJava and the embedded LMDB + * native library. * * @throws IOException if a path was unavailable for memory mapping */ @Test @SuppressWarnings("ConvertToTryWithResources") public void tutorial6() throws IOException { + final File path = tmp.newFolder(); + + // Note we need to specify the Verifier's DBI_COUNT for the Env. + final Env env = create(PROXY_OPTIMAL) + .setMapSize(10_485_760) + .setMaxDbs(Verifier.DBI_COUNT) + .open(path); + + // Create a Verifier (it's a Callable for those needing full control). + final Verifier v = new Verifier(env); + + // We now run the verifier for 3 seconds; it raises an exception on failure. + // The method returns the number of entries it successfully verified. + v.runFor(3, SECONDS); + + env.close(); + } + + /** + * In this final tutorial we'll look at using Agrona's DirectBuffer. + * + * @throws IOException if a path was unavailable for memory mapping + */ + @Test + @SuppressWarnings("ConvertToTryWithResources") + public void tutorial7() throws IOException { // The critical difference is we pass the PROXY_DB field to Env.create(). // There's also a PROXY_SAFE if you want to stop ByteBuffer's Unsafe use. // Aside from that and a different type argument, it's the same as usual... diff --git a/src/test/java/org/lmdbjava/VerifierTest.java b/src/test/java/org/lmdbjava/VerifierTest.java new file mode 100644 index 00000000..1302fd25 --- /dev/null +++ b/src/test/java/org/lmdbjava/VerifierTest.java @@ -0,0 +1,57 @@ +/*- + * #%L + * LmdbJava + * %% + * Copyright (C) 2016 - 2019 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. + * #L% + */ + +package org.lmdbjava; + +import static com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.TimeUnit; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThan; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import static org.lmdbjava.Env.create; +import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; + +/** + * Test {@link Verifier}. + */ +public final class VerifierTest { + + @Rule + public final TemporaryFolder tmp = new TemporaryFolder(); + + @Test + public void verification() throws IOException { + final File path = tmp.newFile(); + try (Env env = create() + .setMaxReaders(1) + .setMaxDbs(Verifier.DBI_COUNT) + .setMapSize(MEBIBYTES.toBytes(10)) + .open(path, MDB_NOSUBDIR)) { + final Verifier v = new Verifier(env); + assertThat(v.runFor(2, TimeUnit.SECONDS), greaterThan(1L)); + } + } + +} From 6431d6ebdc2488c7868a0bb8cf295f80a7911ee0 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Tue, 9 Apr 2019 10:05:46 +1000 Subject: [PATCH 016/322] Update to Agrona 0.9.35 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ec1e6ac0..f20475c2 100644 --- a/pom.xml +++ b/pom.xml @@ -105,7 +105,7 @@ org.agrona agrona - 0.9.34 + 0.9.35 true From 5912beb1037722c71f3ddc05e70fcbe739dfa272 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Tue, 9 Apr 2019 10:06:20 +1000 Subject: [PATCH 017/322] Move callback to field to survive GC (fixes #125) Without this modification the callback is null if a GC is performed subsequent to the construction of the Dbi instance. --- src/main/java/org/lmdbjava/Dbi.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index dc4ecc31..12b550d1 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -54,6 +54,7 @@ */ public final class Dbi { + private final ComparatorCallback ccb; private boolean cleaned; private final Comparator compFunc; private final T compKeyA; @@ -76,12 +77,13 @@ public final class Dbi { compFunc = null; compKeyA = null; compKeyB = null; + ccb = null; } else { this.proxy = txn.getProxy(); this.compFunc = comparator; this.compKeyA = proxy.allocate(); this.compKeyB = proxy.allocate(); - final ComparatorCallback ccb = (keyA, keyB) -> { + this.ccb = (keyA, keyB) -> { proxy.out(compKeyA, keyA, keyA.address()); proxy.out(compKeyB, keyB, keyB.address()); return compFunc.compare(compKeyA, compKeyB); From 52375e4c938a927b8ec2c194f3f628a95d5acecc Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 31 May 2019 15:31:57 +1000 Subject: [PATCH 018/322] Dbi comparator thread safety (fixes #127) --- src/main/java/org/lmdbjava/Dbi.java | 17 +++---- src/test/java/org/lmdbjava/DbiTest.java | 59 ++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index 12b550d1..d402a282 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -57,8 +57,6 @@ public final class Dbi { private final ComparatorCallback ccb; private boolean cleaned; private final Comparator compFunc; - private final T compKeyA; - private final T compKeyB; private final Env env; private final byte[] name; private final BufferProxy proxy; @@ -75,18 +73,19 @@ public final class Dbi { if (comparator == null) { proxy = null; compFunc = null; - compKeyA = null; - compKeyB = null; ccb = null; } else { this.proxy = txn.getProxy(); this.compFunc = comparator; - this.compKeyA = proxy.allocate(); - this.compKeyB = proxy.allocate(); 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()); - return compFunc.compare(compKeyA, compKeyB); + final int result = compFunc.compare(compKeyA, compKeyB); + proxy.deallocate(compKeyA); + proxy.deallocate(compKeyB); + return result; }; LIB.mdb_set_compare(txn.pointer(), ptr, ccb); } @@ -495,10 +494,6 @@ private void clean() { return; } cleaned = true; - if (compKeyA != null) { - proxy.deallocate(compKeyA); - proxy.deallocate(compKeyB); - } } /** diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 3d9f9745..c15f8c31 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -28,16 +28,27 @@ import java.nio.ByteBuffer; import static java.nio.ByteBuffer.allocateDirect; import static java.nio.charset.StandardCharsets.UTF_8; +import java.util.ArrayList; import static java.util.Collections.nCopies; import java.util.Comparator; import java.util.List; import java.util.Random; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import static java.util.concurrent.TimeUnit.SECONDS; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import static java.util.stream.Collectors.toList; +import static java.util.stream.IntStream.range; import org.agrona.concurrent.UnsafeBuffer; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; +import org.hamcrest.Matchers; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.collection.IsEmptyCollection.empty; import org.junit.After; @@ -47,6 +58,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; +import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; import org.lmdbjava.Dbi.DbFullException; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.DbiFlags.MDB_DUPSORT; @@ -81,7 +93,7 @@ public void before() throws IOException { final File path = tmp.newFile(); env = create() .setMapSize(MEBIBYTES.toBytes(64)) - .setMaxReaders(1) + .setMaxReaders(2) .setMaxDbs(2) .open(path, MDB_NOSUBDIR); } @@ -127,6 +139,51 @@ public void dbOpenMaxDatabases() { env.openDbi("db3 fails", MDB_CREATE); } + @Test + public void dbiWithComparatorThreadSafety() { + final Dbi db = env.openDbi(DB_1, PROXY_OPTIMAL::compare, + MDB_CREATE); + + final List keys = range(0, 1000).boxed().collect(toList()); + + final ExecutorService pool = Executors.newCachedThreadPool(); + final AtomicBoolean proceed = new AtomicBoolean(true); + final Future reader = pool.submit(() -> { + while (proceed.get()) { + try (Txn txn = env.txnRead()) { + db.get(txn, bb(50)); + } + } + }); + + for (final Integer key : keys) { + try (Txn txn = env.txnWrite()) { + db.put(txn, bb(key), bb(3)); + txn.commit(); + } + } + + try (Txn txn = env.txnRead()) { + final CursorIterator iter = db.iterate(txn); + + final List result = new ArrayList<>(); + while (iter.hasNext()) { + result.add(iter.next().key().getInt()); + } + + assertThat(result, Matchers.contains(keys.toArray(new Integer[0]))); + } + + proceed.set(false); + try { + reader.get(1, SECONDS); + pool.shutdown(); + pool.awaitTermination(1, SECONDS); + } catch (ExecutionException | InterruptedException | TimeoutException e) { + throw new IllegalStateException(e); + } + } + @Test public void drop() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); From 90adace01871b54850aaafe95c44dd07e4638a3c Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 31 May 2019 15:42:17 +1000 Subject: [PATCH 019/322] Update to Agrona 1.0.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index f20475c2..b4fb3ec2 100644 --- a/pom.xml +++ b/pom.xml @@ -105,7 +105,7 @@ org.agrona agrona - 0.9.35 + 1.0.1 true From 47fec9e3ff57a411142400f1d4dbdca7d26692e5 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 31 May 2019 15:42:26 +1000 Subject: [PATCH 020/322] Update to Netty 4.1.36 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b4fb3ec2..0b357d70 100644 --- a/pom.xml +++ b/pom.xml @@ -111,7 +111,7 @@ io.netty netty-all - 4.1.34.Final + 4.1.36.Final test From 1fe353d8852f432b346cd21c0dfdafde838152b5 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 1 Jun 2019 14:38:53 +1000 Subject: [PATCH 021/322] Fix IndexOutOfBoundsException due to SimpleLeakAwareByteBuf (fixes #133) --- src/test/java/org/lmdbjava/ByteBufProxy.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/test/java/org/lmdbjava/ByteBufProxy.java b/src/test/java/org/lmdbjava/ByteBufProxy.java index 670a5d59..768c1b5e 100644 --- a/src/test/java/org/lmdbjava/ByteBufProxy.java +++ b/src/test/java/org/lmdbjava/ByteBufProxy.java @@ -48,6 +48,7 @@ public final class ByteBufProxy extends BufferProxy { private static final ThreadLocal> BUFFERS = withInitial(() -> new ArrayDeque<>(16)); + 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 long LENGTH_OFFSET; @@ -55,11 +56,7 @@ public final class ByteBufProxy extends BufferProxy { static { try { - // create buffer (first is SimpleLeakAwareByteBuff but we need PooledUDBB) - final ByteBuf bb = DEFAULT.directBuffer(0); - if (!NAME.equals(bb.getClass().getName())) { - throw new IllegalStateException("Netty buffer must be " + NAME); - } + createBuffer(); final Field address = findField(NAME, FIELD_NAME_ADDRESS); final Field length = findField(NAME, FIELD_NAME_LENGTH); ADDRESS_OFFSET = UNSAFE.objectFieldOffset(address); @@ -88,6 +85,16 @@ static Field findField(final String c, final String name) { throw new LmdbException(name + " not found"); } + private static ByteBuf createBuffer() { + for (int i = 0; i < BUFFER_RETRIES; i++) { + final ByteBuf bb = DEFAULT.directBuffer(0); + if (NAME.equals(bb.getClass().getName())) { + return bb; + } + } + throw new IllegalStateException("Netty buffer must be " + NAME); + } + @Override protected ByteBuf allocate() { final ArrayDeque queue = BUFFERS.get(); @@ -96,7 +103,7 @@ protected ByteBuf allocate() { if (buffer != null && buffer.capacity() >= 0) { return buffer; } else { - return DEFAULT.directBuffer(0); + return createBuffer(); } } From b0085a469ed09df74cb61d86609483c38d6c5608 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 1 Jun 2019 14:42:12 +1000 Subject: [PATCH 022/322] TutorialTest to reduce required readers (fixes #129) --- src/test/java/org/lmdbjava/TutorialTest.java | 34 +++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/test/java/org/lmdbjava/TutorialTest.java b/src/test/java/org/lmdbjava/TutorialTest.java index 4b256ca9..56af2293 100644 --- a/src/test/java/org/lmdbjava/TutorialTest.java +++ b/src/test/java/org/lmdbjava/TutorialTest.java @@ -46,7 +46,6 @@ import static org.lmdbjava.DbiFlags.MDB_DUPSORT; import static org.lmdbjava.DirectBufferProxy.PROXY_DB; import static org.lmdbjava.Env.create; -import static org.lmdbjava.Env.open; import static org.lmdbjava.GetOp.MDB_SET; import static org.lmdbjava.SeekOp.MDB_FIRST; import static org.lmdbjava.SeekOp.MDB_LAST; @@ -151,10 +150,7 @@ public void tutorial1() throws IOException { @SuppressWarnings({"ConvertToTryWithResources", "checkstyle:executablestatementcount"}) public void tutorial2() throws IOException, InterruptedException { - final File path = tmp.newFolder(); - // Here we use a shortcut to open a 10 MB environment with one database. - final Env env = open(path, 10); - + final Env env = createSimpleEnv(tmp.newFolder()); final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); final ByteBuffer key = allocateDirect(env.getMaxKeySize()); final ByteBuffer val = allocateDirect(700); @@ -229,9 +225,7 @@ public void tutorial2() throws IOException, InterruptedException { @SuppressWarnings({"ConvertToTryWithResources", "checkstyle:executablestatementcount"}) public void tutorial3() throws IOException { - // As per tutorial1... - final File path = tmp.newFolder(); - final Env env = open(path, 10); + final Env env = createSimpleEnv(tmp.newFolder()); final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); final ByteBuffer key = allocateDirect(env.getMaxKeySize()); final ByteBuffer val = allocateDirect(700); @@ -306,9 +300,7 @@ public void tutorial3() throws IOException { @Test @SuppressWarnings("ConvertToTryWithResources") public void tutorial4() throws IOException { - // As per tutorial1... - final File path = tmp.newFolder(); - final Env env = open(path, 10); + final Env env = createSimpleEnv(tmp.newFolder()); final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); try (Txn txn = env.txnWrite()) { @@ -368,9 +360,7 @@ public void tutorial4() throws IOException { @Test @SuppressWarnings("ConvertToTryWithResources") public void tutorial5() throws IOException { - // As per tutorial1... - final File path = tmp.newFolder(); - final Env env = open(path, 10); + final Env env = createSimpleEnv(tmp.newFolder()); // This time we're going to tell the Dbi it can store > 1 value per key. // There are other flags available if we're storing integers etc. @@ -425,13 +415,11 @@ public void tutorial5() throws IOException { @Test @SuppressWarnings("ConvertToTryWithResources") public void tutorial6() throws IOException { - final File path = tmp.newFolder(); - // Note we need to specify the Verifier's DBI_COUNT for the Env. final Env env = create(PROXY_OPTIMAL) .setMapSize(10_485_760) .setMaxDbs(Verifier.DBI_COUNT) - .open(path); + .open(tmp.newFolder()); // Create a Verifier (it's a Callable for those needing full control). final Verifier v = new Verifier(env); @@ -454,11 +442,10 @@ public void tutorial7() throws IOException { // The critical difference is we pass the PROXY_DB field to Env.create(). // There's also a PROXY_SAFE if you want to stop ByteBuffer's Unsafe use. // Aside from that and a different type argument, it's the same as usual... - final File path = tmp.newFolder(); final Env env = create(PROXY_DB) .setMapSize(10_485_760) .setMaxDbs(1) - .open(path); + .open(tmp.newFolder()); final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); @@ -513,4 +500,13 @@ public void tutorial7() throws IOException { // how to speed up inserts by appending them in key order, using integer // or reverse ordered keys, using Env.DISABLE_CHECKS_PROP etc), but you now // know enough to tackle the JavaDocs with confidence. Have fun! + + private Env createSimpleEnv(final File path) { + return create() + .setMapSize(10_485_760) + .setMaxDbs(1) + .setMaxReaders(1) + .open(path); + } + } From 8258e4871ecd9b07ec59666357a8cc224af44097 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 1 Jun 2019 15:09:24 +1000 Subject: [PATCH 023/322] Remove unchecked exceptions from method signatures --- src/main/java/org/lmdbjava/Cursor.java | 2 +- src/main/java/org/lmdbjava/CursorIterator.java | 2 +- src/main/java/org/lmdbjava/ResultCodeMapper.java | 3 +-- src/main/java/org/lmdbjava/Txn.java | 6 +++--- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/lmdbjava/Cursor.java b/src/main/java/org/lmdbjava/Cursor.java index 2c6bd7e0..80c5df4f 100644 --- a/src/main/java/org/lmdbjava/Cursor.java +++ b/src/main/java/org/lmdbjava/Cursor.java @@ -355,7 +355,7 @@ public T val() { return kv.val(); } - private void checkNotClosed() throws ClosedException { + private void checkNotClosed() { if (closed) { throw new ClosedException(); } diff --git a/src/main/java/org/lmdbjava/CursorIterator.java b/src/main/java/org/lmdbjava/CursorIterator.java index e6ec5392..d03ae71b 100644 --- a/src/main/java/org/lmdbjava/CursorIterator.java +++ b/src/main/java/org/lmdbjava/CursorIterator.java @@ -88,7 +88,7 @@ public Iterable> iterable() { } @Override - public KeyVal next() throws NoSuchElementException { + public KeyVal next() { if (!hasNext()) { throw new NoSuchElementException(); } diff --git a/src/main/java/org/lmdbjava/ResultCodeMapper.java b/src/main/java/org/lmdbjava/ResultCodeMapper.java index 67946652..67ecaef9 100644 --- a/src/main/java/org/lmdbjava/ResultCodeMapper.java +++ b/src/main/java/org/lmdbjava/ResultCodeMapper.java @@ -56,9 +56,8 @@ private ResultCodeMapper() { * Checks the result code and raises an exception is not {@link #MDB_SUCCESS}. * * @param rc the LMDB result code - * @throws LmdbNativeException the resolved exception */ - static void checkRc(final int rc) throws LmdbNativeException { + static void checkRc(final int rc) { switch (rc) { case MDB_SUCCESS: return; diff --git a/src/main/java/org/lmdbjava/Txn.java b/src/main/java/org/lmdbjava/Txn.java index d8c87b84..233cd6a5 100644 --- a/src/main/java/org/lmdbjava/Txn.java +++ b/src/main/java/org/lmdbjava/Txn.java @@ -184,19 +184,19 @@ public T val() { return keyVal.val(); } - void checkReadOnly() throws ReadOnlyRequiredException { + void checkReadOnly() { if (!readOnly) { throw new ReadOnlyRequiredException(); } } - void checkReady() throws NotReadyException { + void checkReady() { if (state != READY) { throw new NotReadyException(); } } - void checkWritesAllowed() throws ReadWriteRequiredException { + void checkWritesAllowed() { if (readOnly) { throw new ReadWriteRequiredException(); } From a97617b0b9a346ceabeb2ff7c7bc38dafced2dd6 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 1 Jun 2019 15:09:35 +1000 Subject: [PATCH 024/322] Update to Acegi Standard Project 0.2.3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0b357d70..6207e1fb 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ au.com.acegi acegi-standard-project - 0.2.1 + 0.2.3 org.lmdbjava lmdbjava From a8ee24fbd1603ed9cf4f743682334dce6d7e6878 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sun, 2 Jun 2019 10:32:54 +1000 Subject: [PATCH 025/322] Update features list --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3bba0014..611c2f0f 100644 --- a/README.md +++ b/README.md @@ -25,13 +25,16 @@ **LmdbJava** adds Java-specific features to LMDB: * [Extremely fast](https://github.com/lmdbjava/benchmarks/blob/master/results/20160710/README.md) across a broad range of benchmarks, data sizes and access patterns -* Modern, idiomatic Java API (including iterators, enums, exceptions etc) -* Nothing to install (the JAR embeds LMDB libraries for Linux, OS X and Windows) +* Modern, idiomatic Java API (including iterators, key ranges, enums, exceptions etc) +* Nothing to install (the JAR embeds the latest LMDB libraries for Linux, OS X and Windows) * Buffer agnostic (Java `ByteBuffer`, Agrona `DirectBuffer`, Netty `ByteBuf`, your own buffer) * 100% stock-standard, officially-released, widely-tested LMDB C code ([no extra](https://github.com/lmdbjava/native) C/JNI code) * Low latency design (allocation-free; buffer pools; optional checks can be easily disabled in production etc) +* Automatically tested with Java 8 and Java 11 * Easy to use (just work through our step-by-step, CI-tested, fully-executable [tutorial](https://github.com/lmdbjava/lmdbjava/tree/master/src/test/java/org/lmdbjava/TutorialTest.java)) -* Tested with Java 8 through to Java 11 +* Community questions and contributions are welcome through [GitHub tickets](https://github.com/lmdbjava/lmdbjava/issues) +* Mature code (commenced in 2016) and used for heavy production workloads (eg > 500 TB of HFT data) +* Actively maintained and with a "Zero Bug Policy" before every release (see [issues](https://github.com/lmdbjava/lmdbjava/issues)) * Available from [Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.lmdbjava%22%20AND%20a%3A%22lmdbjava%22) and [OSS Sonatype Snapshots](https://oss.sonatype.org/content/repositories/snapshots/org/lmdbjava/lmdbjava) (see [Change Log](https://github.com/lmdbjava/lmdbjava/wiki/Change-Log)) * Comprehensive [JavaDocs](http://www.javadoc.io/doc/org.lmdbjava/lmdbjava) * [Linux](https://travis-ci.org/lmdbjava/lmdbjava), [OS X](https://travis-ci.org/lmdbjava/lmdbjava) and [Windows](https://ci.appveyor.com/project/benalexau/lmdbjava) CI From bc12fd0416e75ff173b45ee19caafe2eddb8c247 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sun, 2 Jun 2019 17:44:54 +1000 Subject: [PATCH 026/322] Automatic formatting only --- src/test/java/org/lmdbjava/TutorialTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/org/lmdbjava/TutorialTest.java b/src/test/java/org/lmdbjava/TutorialTest.java index 56af2293..d631126c 100644 --- a/src/test/java/org/lmdbjava/TutorialTest.java +++ b/src/test/java/org/lmdbjava/TutorialTest.java @@ -500,7 +500,6 @@ public void tutorial7() throws IOException { // how to speed up inserts by appending them in key order, using integer // or reverse ordered keys, using Env.DISABLE_CHECKS_PROP etc), but you now // know enough to tackle the JavaDocs with confidence. Have fun! - private Env createSimpleEnv(final File path) { return create() .setMapSize(10_485_760) From 3a75fd5e6856e485a5d350ced3526d3be9a9363a Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Tue, 4 Jun 2019 14:55:29 +1000 Subject: [PATCH 027/322] [maven-release-plugin] prepare release lmdbjava-0.7.0 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 6207e1fb..4fc7866a 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ org.lmdbjava lmdbjava - 0.6.4-SNAPSHOT + 0.7.0 jar LmdbJava Low latency Java API for the ultra-fast, embedded Symas Lightning Database (LMDB) @@ -45,7 +45,7 @@ scm:git:git@github.com:${github.org}/${github.repo}.git scm:git:git@github.com:${github.org}/${github.repo}.git git@github.com:${github.org}/${github.repo}.git - HEAD + lmdbjava-0.7.0 GitHub Issues From 0591402fd1820512cddc920a1562ff7f8fcf26c2 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Tue, 4 Jun 2019 14:56:08 +1000 Subject: [PATCH 028/322] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 4fc7866a..3b0c2ca8 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ org.lmdbjava lmdbjava - 0.7.0 + 0.7.1-SNAPSHOT jar LmdbJava Low latency Java API for the ultra-fast, embedded Symas Lightning Database (LMDB) @@ -45,7 +45,7 @@ scm:git:git@github.com:${github.org}/${github.repo}.git scm:git:git@github.com:${github.org}/${github.repo}.git git@github.com:${github.org}/${github.repo}.git - lmdbjava-0.7.0 + HEAD GitHub Issues From 7552aaa4196eba6aba9557954eff7937ad273df8 Mon Sep 17 00:00:00 2001 From: Martin Harrigan Date: Thu, 1 Aug 2019 10:46:10 +0100 Subject: [PATCH 029/322] Fix typo in exception class name --- src/main/java/org/lmdbjava/LmdbNativeException.java | 4 ++-- src/main/java/org/lmdbjava/ResultCodeMapper.java | 2 +- src/test/java/org/lmdbjava/DbiTest.java | 4 ++-- src/test/java/org/lmdbjava/ResultCodeMapperTest.java | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/lmdbjava/LmdbNativeException.java b/src/main/java/org/lmdbjava/LmdbNativeException.java index 7ef02a62..b80d9e13 100644 --- a/src/main/java/org/lmdbjava/LmdbNativeException.java +++ b/src/main/java/org/lmdbjava/LmdbNativeException.java @@ -57,11 +57,11 @@ public final int getResultCode() { /** * Exception raised from a system constant table lookup. */ - public static final class ConstantDerviedException extends LmdbNativeException { + public static final class ConstantDerivedException extends LmdbNativeException { private static final long serialVersionUID = 1L; - ConstantDerviedException(final int rc, final String message) { + ConstantDerivedException(final int rc, final String message) { super(rc, "Platform constant error code: " + message); } } diff --git a/src/main/java/org/lmdbjava/ResultCodeMapper.java b/src/main/java/org/lmdbjava/ResultCodeMapper.java index 67ecaef9..678537e5 100644 --- a/src/main/java/org/lmdbjava/ResultCodeMapper.java +++ b/src/main/java/org/lmdbjava/ResultCodeMapper.java @@ -110,7 +110,7 @@ static void checkRc(final int rc) { throw new IllegalArgumentException("Unknown result code " + rc); } final String msg = constant.name() + " " + constant.toString(); - throw new LmdbNativeException.ConstantDerviedException(rc, msg); + throw new LmdbNativeException.ConstantDerivedException(rc, msg); } } diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index c15f8c31..5d454cb8 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -67,7 +67,7 @@ import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; import static org.lmdbjava.GetOp.MDB_SET_KEY; import static org.lmdbjava.KeyRange.atMost; -import org.lmdbjava.LmdbNativeException.ConstantDerviedException; +import org.lmdbjava.LmdbNativeException.ConstantDerivedException; import static org.lmdbjava.PutFlags.MDB_NODUPDATA; import static org.lmdbjava.PutFlags.MDB_NOOVERWRITE; import static org.lmdbjava.TestUtils.DB_1; @@ -98,7 +98,7 @@ public void before() throws IOException { .open(path, MDB_NOSUBDIR); } - @Test(expected = ConstantDerviedException.class) + @Test(expected = ConstantDerivedException.class) public void close() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); db.put(bb(1), bb(42)); diff --git a/src/test/java/org/lmdbjava/ResultCodeMapperTest.java b/src/test/java/org/lmdbjava/ResultCodeMapperTest.java index 75007db7..78adb498 100644 --- a/src/test/java/org/lmdbjava/ResultCodeMapperTest.java +++ b/src/test/java/org/lmdbjava/ResultCodeMapperTest.java @@ -43,7 +43,7 @@ import org.lmdbjava.Env.MapFullException; import org.lmdbjava.Env.ReadersFullException; import org.lmdbjava.Env.VersionMismatchException; -import org.lmdbjava.LmdbNativeException.ConstantDerviedException; +import org.lmdbjava.LmdbNativeException.ConstantDerivedException; import org.lmdbjava.LmdbNativeException.PageCorruptedException; import org.lmdbjava.LmdbNativeException.PageFullException; import org.lmdbjava.LmdbNativeException.PageNotFoundException; @@ -103,7 +103,7 @@ public void checkErrAll() { } } - @Test(expected = ConstantDerviedException.class) + @Test(expected = ConstantDerivedException.class) public void checkErrConstantDerived() { checkRc(20); } @@ -113,7 +113,7 @@ public void checkErrConstantDerivedMessage() { try { checkRc(2); fail("Should have raised exception"); - } catch (final ConstantDerviedException ex) { + } catch (final ConstantDerivedException ex) { assertThat(ex.getMessage(), containsString("No such file or directory")); } } From 83f991cb4e3974235e491c93170b206e14000ed7 Mon Sep 17 00:00:00 2001 From: sidnt Date: Sat, 4 Jan 2020 11:10:44 +0530 Subject: [PATCH 030/322] add documentation from lmdb api doc --- src/main/java/org/lmdbjava/EnvFlags.java | 100 +++++++++++++++++++++-- 1 file changed, 93 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/lmdbjava/EnvFlags.java b/src/main/java/org/lmdbjava/EnvFlags.java index eb267f33..6b1709ba 100644 --- a/src/main/java/org/lmdbjava/EnvFlags.java +++ b/src/main/java/org/lmdbjava/EnvFlags.java @@ -26,50 +26,136 @@ public enum EnvFlags implements MaskedFlag { /** - * Mmap at a fixed address (experimental). + * http://www.lmdb.tech/doc/group__mdb.html#ga32a193c6bf4d7d5c5d579e71f22e9340 */ + /** * Mmap at a fixed address (experimental). + * MDB_FIXEDMAP : Use a fixed address for the mmap region. This flag must be + * specified when creating the environment, and is stored persistently in the + * environment. If successful, the memory map will always reside at the same + * virtual address and pointers used to reference data items in the database will + * be constant across multiple invocations. This option may not always work, + * depending on how the operating system has allocated memory to shared + * libraries and other uses. The feature is highly experimental. */ MDB_FIXEDMAP(0x01), + /** * No environment directory. + * MDB_NOSUBDIR : By default, LMDB creates its environment in a directory whose pathname is + * given in path, and creates its data and lock files under that directory. + * With this option, path is used as-is for the database main data file. The + * database lock file is the path with "-lock" appended. */ MDB_NOSUBDIR(0x4000), + /** - * Don't fsync after commit. + * MDB_RDONLY : Open the environment in read-only mode. No write operations will + * be allowed. LMDB will still modify the lock file - except on read-only + * filesystems, where LMDB does not use locks. */ - MDB_NOSYNC(0x1_0000), + MDB_RDONLY_ENV(0x2_0000), + /** - * Read only. + * MDB_WRITEMAP : Use a writeable memory map unless MDB_RDONLY is set. This is + * faster and uses fewer mallocs, but loses protection from application bugs + * like wild pointer writes and other bad updates into the database. + * Incompatible with nested transactions. Do not mix processes with and without + * MDB_WRITEMAP on the same environment. This can defeat durability (mdb_env_sync etc). */ - MDB_RDONLY_ENV(0x2_0000), + MDB_WRITEMAP(0x8_0000), + /** * Don't fsync metapage after commit. + * MDB_NOMETASYNC : Flush system buffers to disk only once per transaction, omit the + * metadata flush. Defer that until the system flushes files to disk, or next non-MDB_RDONLY + * commit or mdb_env_sync(). This optimization maintains database integrity, but a system + * crash may undo the last committed transaction. I.e. it preserves the ACI (atomicity, + * consistency, isolation) but not D (durability) database property. This flag may be + * changed at any time using mdb_env_set_flags(). */ MDB_NOMETASYNC(0x4_0000), + /** - * Use writable mmap. + * Don't fsync after commit. + * MDB_NOSYNC : Don't flush system buffers to disk when committing a transaction. + * This optimization means a system crash can corrupt the database or lose the last + * transactions if buffers are not yet flushed to disk. The risk is governed by how + * often the system flushes dirty buffers to disk and how often mdb_env_sync() is called. + * However, if the filesystem preserves write order and the MDB_WRITEMAP flag is not used, + * transactions exhibit ACI (atomicity, consistency, isolation) properties and only lose D + * (durability). I.e. database integrity is maintained, but a system crash may undo the + * final transactions. Note that (MDB_NOSYNC | MDB_WRITEMAP) leaves the system with no hint + * for when to write transactions to disk, unless mdb_env_sync() is called. + * (MDB_MAPASYNC | MDB_WRITEMAP) may be preferable. This flag may be changed at any time + * using mdb_env_set_flags(). */ - MDB_WRITEMAP(0x8_0000), + MDB_NOSYNC(0x1_0000), + /** * Use asynchronous msync when {@link #MDB_WRITEMAP} is used. + * MDB_MAPASYNC : When using MDB_WRITEMAP, use asynchronous flushes to disk. + * As with MDB_NOSYNC, a system crash can then corrupt the database or lose the + * last transactions. Calling mdb_env_sync() ensures on-disk database integrity + * until next commit. This flag may be changed at any time using mdb_env_set_flags(). */ MDB_MAPASYNC(0x10_0000), + /** * Tie reader locktable slots to {@link Txn} objects instead of to threads. + * MDB_NOTLS : Don't use Thread-Local Storage. Tie reader locktable slots to + * MDB_txn objects instead of to threads. I.e. mdb_txn_reset() keeps the slot + * reseved for the MDB_txn object. A thread may use parallel read-only transactions. + * A read-only transaction may span threads if the user synchronizes its use. + * Applications that multiplex many user threads over individual OS threads + * need this option. Such an application must also serialize the write + * transactions in an OS thread, since LMDB's write locking is unaware + * of the user threads. */ MDB_NOTLS(0x20_0000), + /** * Don't do any locking, caller must manage their own locks. + * MDB_NOLOCK : Don't do any locking. If concurrent access is anticipated, + * the caller must manage all concurrency itself. For proper operation + * the caller must enforce single-writer semantics, and must ensure + * that no readers are using old transactions while a writer is active. + * The simplest approach is to use an exclusive lock so that no readers + * may be active at all when a writer begins. */ MDB_NOLOCK(0x40_0000), + /** * Don't do readahead (no effect on Windows). + * MDB_NORDAHEAD : Turn off readahead. Most operating systems perform + * readahead on read requests by default. This option turns it off if + * the OS supports it. Turning it off may help random read performance + * when the DB is larger than RAM and system RAM is full. The option + * is not implemented on Windows. */ MDB_NORDAHEAD(0x80_0000), + /** * Don't initialize malloc'd memory before writing to datafile. + * MDB_NOMEMINIT : Don't initialize malloc'd memory before writing to + * unused spaces in the data file. By default, memory for pages written + * to the data file is obtained using malloc. While these pages may be + * reused in subsequent transactions, freshly malloc'd pages will be + * initialized to zeroes before use. This avoids persisting leftover + * data from other code (that used the heap and subsequently freed + * the memory) into the data file. Note that many other system + * libraries may allocate and free memory from the heap for + * arbitrary uses. E.g., stdio may use the heap for file I/O buffers. + * This initialization step has a modest performance cost so some + * applications may want to disable it using this flag. This option + * can be a problem for applications which handle sensitive data + * like passwords, and it makes memory checkers like Valgrind noisy. + * This flag is not needed with MDB_WRITEMAP, which writes directly + * to the mmap instead of using malloc for pages. The initialization + * is also skipped if MDB_RESERVE is used; the caller is expected to + * overwrite all of the memory that was reserved in that case. This + * flag may be changed at any time using mdb_env_set_flags(). */ MDB_NOMEMINIT(0x100_0000); From a02bc60b31b36b42dc86344bec5719d11183f571 Mon Sep 17 00:00:00 2001 From: Justin Heister Date: Tue, 10 Mar 2020 09:47:04 +0000 Subject: [PATCH 031/322] Add support for listing Dbi flags --- src/main/java/org/lmdbjava/Dbi.java | 28 +++++++++++++++++++++++++ src/main/java/org/lmdbjava/Library.java | 2 +- src/test/java/org/lmdbjava/DbiTest.java | 12 +++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index d402a282..eccefd5f 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -20,10 +20,15 @@ package org.lmdbjava; +import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; +import java.util.List; + import static java.util.Objects.requireNonNull; import static jnr.ffi.Memory.allocateDirect; + +import jnr.ffi.NativeType; import static jnr.ffi.NativeType.ADDRESS; import jnr.ffi.Pointer; import jnr.ffi.byref.PointerByReference; @@ -489,6 +494,29 @@ public Stat stat(final Txn txn) { stat.f5_ms_entries.longValue()); } + /* + * Return DbiFlags for this Dbi. + * + * @param txn transaction handle (not null; not committed) + * @return the list of flags this Dbi was created with + */ + public List listFlags(final Txn txn) { + final Pointer resultPtr = allocateDirect(RUNTIME, NativeType.UINT); + checkRc(LIB.mdb_dbi_flags(txn.pointer(), ptr, resultPtr)); + + final int flags = resultPtr.getInt(0); + + final List result = new ArrayList<>(); + + for (final DbiFlags flag : DbiFlags.values()) { + if (MaskedFlag.isSet(flags, flag)) { + result.add(flag); + } + } + + return result; + } + private void clean() { if (cleaned) { return; diff --git a/src/main/java/org/lmdbjava/Library.java b/src/main/java/org/lmdbjava/Library.java index 3cb44fae..60ac2fbc 100644 --- a/src/main/java/org/lmdbjava/Library.java +++ b/src/main/java/org/lmdbjava/Library.java @@ -222,7 +222,7 @@ int mdb_cursor_put(@In Pointer cursor, @In Pointer key, @In Pointer data, void mdb_dbi_close(@In Pointer env, @In Pointer dbi); - int mdb_dbi_flags(@In Pointer txn, @In Pointer dbi, int flags); + int mdb_dbi_flags(@In Pointer txn, @In Pointer dbi, @Out Pointer flags); int mdb_dbi_open(@In Pointer txn, @In byte[] name, int flags, @In Pointer dbiPtr); diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 5d454cb8..c36be6bf 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -49,6 +49,7 @@ import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; import org.hamcrest.Matchers; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.collection.IsEmptyCollection.empty; import org.junit.After; @@ -62,6 +63,7 @@ import org.lmdbjava.Dbi.DbFullException; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.DbiFlags.MDB_DUPSORT; +import static org.lmdbjava.DbiFlags.MDB_REVERSEKEY; import org.lmdbjava.Env.MapFullException; import static org.lmdbjava.Env.create; import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; @@ -474,4 +476,14 @@ public void testParallelWritesStress() { } }); } + + @Test + public void listsFlags() { + final Dbi dbi = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT, MDB_REVERSEKEY); + + try (Txn txn = env.txnRead()) { + final List flags = dbi.listFlags(txn); + assertThat(flags, containsInAnyOrder(MDB_DUPSORT, MDB_REVERSEKEY)); + } + } } From c016449d8b5370178fbe7a10151d2dd886516260 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 11 Apr 2020 15:40:04 +1000 Subject: [PATCH 032/322] Copyright update --- src/main/java/org/lmdbjava/BufferProxy.java | 2 +- src/main/java/org/lmdbjava/ByteArrayProxy.java | 2 +- src/main/java/org/lmdbjava/ByteBufferProxy.java | 2 +- src/main/java/org/lmdbjava/CopyFlags.java | 2 +- src/main/java/org/lmdbjava/Cursor.java | 2 +- src/main/java/org/lmdbjava/CursorIterator.java | 2 +- src/main/java/org/lmdbjava/Dbi.java | 2 +- src/main/java/org/lmdbjava/DbiFlags.java | 2 +- src/main/java/org/lmdbjava/DirectBufferProxy.java | 2 +- src/main/java/org/lmdbjava/Env.java | 2 +- src/main/java/org/lmdbjava/EnvFlags.java | 2 +- src/main/java/org/lmdbjava/EnvInfo.java | 2 +- src/main/java/org/lmdbjava/GetOp.java | 2 +- src/main/java/org/lmdbjava/KeyRange.java | 2 +- src/main/java/org/lmdbjava/KeyRangeType.java | 2 +- src/main/java/org/lmdbjava/KeyVal.java | 2 +- src/main/java/org/lmdbjava/Library.java | 2 +- src/main/java/org/lmdbjava/LmdbException.java | 2 +- src/main/java/org/lmdbjava/LmdbNativeException.java | 2 +- src/main/java/org/lmdbjava/MaskedFlag.java | 2 +- src/main/java/org/lmdbjava/Meta.java | 2 +- src/main/java/org/lmdbjava/PutFlags.java | 2 +- src/main/java/org/lmdbjava/ResultCodeMapper.java | 2 +- src/main/java/org/lmdbjava/SeekOp.java | 2 +- src/main/java/org/lmdbjava/Stat.java | 2 +- src/main/java/org/lmdbjava/Txn.java | 2 +- src/main/java/org/lmdbjava/TxnFlags.java | 2 +- src/main/java/org/lmdbjava/UnsafeAccess.java | 2 +- src/main/java/org/lmdbjava/Verifier.java | 2 +- src/main/java/org/lmdbjava/package-info.java | 2 +- src/test/java/org/lmdbjava/ByteBufProxy.java | 2 +- src/test/java/org/lmdbjava/ByteBufferProxyTest.java | 2 +- src/test/java/org/lmdbjava/ComparatorTest.java | 2 +- src/test/java/org/lmdbjava/CursorIteratorTest.java | 2 +- src/test/java/org/lmdbjava/CursorParamTest.java | 2 +- src/test/java/org/lmdbjava/CursorTest.java | 2 +- src/test/java/org/lmdbjava/DbiTest.java | 2 +- src/test/java/org/lmdbjava/EnvTest.java | 2 +- src/test/java/org/lmdbjava/KeyRangeTest.java | 2 +- src/test/java/org/lmdbjava/LibraryTest.java | 2 +- src/test/java/org/lmdbjava/MaskedFlagTest.java | 2 +- src/test/java/org/lmdbjava/MetaTest.java | 2 +- src/test/java/org/lmdbjava/ResultCodeMapperTest.java | 2 +- src/test/java/org/lmdbjava/TestUtils.java | 2 +- src/test/java/org/lmdbjava/TutorialTest.java | 2 +- src/test/java/org/lmdbjava/TxnTest.java | 2 +- src/test/java/org/lmdbjava/VerifierTest.java | 2 +- src/test/java/org/lmdbjava/package-info.java | 2 +- 48 files changed, 48 insertions(+), 48 deletions(-) diff --git a/src/main/java/org/lmdbjava/BufferProxy.java b/src/main/java/org/lmdbjava/BufferProxy.java index 36f6a9eb..6ad9ef34 100644 --- a/src/main/java/org/lmdbjava/BufferProxy.java +++ b/src/main/java/org/lmdbjava/BufferProxy.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/ByteArrayProxy.java b/src/main/java/org/lmdbjava/ByteArrayProxy.java index a7213d09..237170b4 100644 --- a/src/main/java/org/lmdbjava/ByteArrayProxy.java +++ b/src/main/java/org/lmdbjava/ByteArrayProxy.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index 3d358856..de6db50b 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/CopyFlags.java b/src/main/java/org/lmdbjava/CopyFlags.java index cd471d0e..d85ffb06 100644 --- a/src/main/java/org/lmdbjava/CopyFlags.java +++ b/src/main/java/org/lmdbjava/CopyFlags.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/Cursor.java b/src/main/java/org/lmdbjava/Cursor.java index 80c5df4f..79138a69 100644 --- a/src/main/java/org/lmdbjava/Cursor.java +++ b/src/main/java/org/lmdbjava/Cursor.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/CursorIterator.java b/src/main/java/org/lmdbjava/CursorIterator.java index d03ae71b..c184a55b 100644 --- a/src/main/java/org/lmdbjava/CursorIterator.java +++ b/src/main/java/org/lmdbjava/CursorIterator.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index d402a282..39fbf0ee 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/DbiFlags.java b/src/main/java/org/lmdbjava/DbiFlags.java index b6f23092..0f9573dd 100644 --- a/src/main/java/org/lmdbjava/DbiFlags.java +++ b/src/main/java/org/lmdbjava/DbiFlags.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/DirectBufferProxy.java b/src/main/java/org/lmdbjava/DirectBufferProxy.java index 28aa7ff1..2ba68781 100644 --- a/src/main/java/org/lmdbjava/DirectBufferProxy.java +++ b/src/main/java/org/lmdbjava/DirectBufferProxy.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index 6d329cad..8b5cd1af 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/EnvFlags.java b/src/main/java/org/lmdbjava/EnvFlags.java index eb267f33..50cdf494 100644 --- a/src/main/java/org/lmdbjava/EnvFlags.java +++ b/src/main/java/org/lmdbjava/EnvFlags.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/EnvInfo.java b/src/main/java/org/lmdbjava/EnvInfo.java index 3e2fe54e..fe16e92b 100644 --- a/src/main/java/org/lmdbjava/EnvInfo.java +++ b/src/main/java/org/lmdbjava/EnvInfo.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/GetOp.java b/src/main/java/org/lmdbjava/GetOp.java index 4d0040b3..82550f82 100644 --- a/src/main/java/org/lmdbjava/GetOp.java +++ b/src/main/java/org/lmdbjava/GetOp.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/KeyRange.java b/src/main/java/org/lmdbjava/KeyRange.java index a534d6e7..e8cd57ab 100644 --- a/src/main/java/org/lmdbjava/KeyRange.java +++ b/src/main/java/org/lmdbjava/KeyRange.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/KeyRangeType.java b/src/main/java/org/lmdbjava/KeyRangeType.java index be1f6c74..b286f72c 100644 --- a/src/main/java/org/lmdbjava/KeyRangeType.java +++ b/src/main/java/org/lmdbjava/KeyRangeType.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/KeyVal.java b/src/main/java/org/lmdbjava/KeyVal.java index 700cfc26..6bfd4546 100644 --- a/src/main/java/org/lmdbjava/KeyVal.java +++ b/src/main/java/org/lmdbjava/KeyVal.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/Library.java b/src/main/java/org/lmdbjava/Library.java index 3cb44fae..726f626c 100644 --- a/src/main/java/org/lmdbjava/Library.java +++ b/src/main/java/org/lmdbjava/Library.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/LmdbException.java b/src/main/java/org/lmdbjava/LmdbException.java index e19f8dca..e6004f19 100644 --- a/src/main/java/org/lmdbjava/LmdbException.java +++ b/src/main/java/org/lmdbjava/LmdbException.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/LmdbNativeException.java b/src/main/java/org/lmdbjava/LmdbNativeException.java index b80d9e13..81b46d15 100644 --- a/src/main/java/org/lmdbjava/LmdbNativeException.java +++ b/src/main/java/org/lmdbjava/LmdbNativeException.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/MaskedFlag.java b/src/main/java/org/lmdbjava/MaskedFlag.java index 14bbbabd..21dde5a1 100644 --- a/src/main/java/org/lmdbjava/MaskedFlag.java +++ b/src/main/java/org/lmdbjava/MaskedFlag.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/Meta.java b/src/main/java/org/lmdbjava/Meta.java index 5cd61b5a..46807f3c 100644 --- a/src/main/java/org/lmdbjava/Meta.java +++ b/src/main/java/org/lmdbjava/Meta.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/PutFlags.java b/src/main/java/org/lmdbjava/PutFlags.java index f15a4dd0..87bdacd9 100644 --- a/src/main/java/org/lmdbjava/PutFlags.java +++ b/src/main/java/org/lmdbjava/PutFlags.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/ResultCodeMapper.java b/src/main/java/org/lmdbjava/ResultCodeMapper.java index 678537e5..63310fcd 100644 --- a/src/main/java/org/lmdbjava/ResultCodeMapper.java +++ b/src/main/java/org/lmdbjava/ResultCodeMapper.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/SeekOp.java b/src/main/java/org/lmdbjava/SeekOp.java index 83f1a0f2..1d214adb 100644 --- a/src/main/java/org/lmdbjava/SeekOp.java +++ b/src/main/java/org/lmdbjava/SeekOp.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/Stat.java b/src/main/java/org/lmdbjava/Stat.java index 87dad739..de43577e 100644 --- a/src/main/java/org/lmdbjava/Stat.java +++ b/src/main/java/org/lmdbjava/Stat.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/Txn.java b/src/main/java/org/lmdbjava/Txn.java index 233cd6a5..67c84343 100644 --- a/src/main/java/org/lmdbjava/Txn.java +++ b/src/main/java/org/lmdbjava/Txn.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/TxnFlags.java b/src/main/java/org/lmdbjava/TxnFlags.java index 5fbb0285..a4cf3e8d 100644 --- a/src/main/java/org/lmdbjava/TxnFlags.java +++ b/src/main/java/org/lmdbjava/TxnFlags.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/UnsafeAccess.java b/src/main/java/org/lmdbjava/UnsafeAccess.java index 591cf905..92d1df5e 100644 --- a/src/main/java/org/lmdbjava/UnsafeAccess.java +++ b/src/main/java/org/lmdbjava/UnsafeAccess.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/Verifier.java b/src/main/java/org/lmdbjava/Verifier.java index 9ce5c008..207b9b7f 100644 --- a/src/main/java/org/lmdbjava/Verifier.java +++ b/src/main/java/org/lmdbjava/Verifier.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/main/java/org/lmdbjava/package-info.java b/src/main/java/org/lmdbjava/package-info.java index 9447fb4b..05e35587 100644 --- a/src/main/java/org/lmdbjava/package-info.java +++ b/src/main/java/org/lmdbjava/package-info.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/test/java/org/lmdbjava/ByteBufProxy.java b/src/test/java/org/lmdbjava/ByteBufProxy.java index 768c1b5e..9e220f16 100644 --- a/src/test/java/org/lmdbjava/ByteBufProxy.java +++ b/src/test/java/org/lmdbjava/ByteBufProxy.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java index ef090cce..9399eada 100644 --- a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java +++ b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/test/java/org/lmdbjava/ComparatorTest.java b/src/test/java/org/lmdbjava/ComparatorTest.java index bfd6a8de..a2ea539b 100644 --- a/src/test/java/org/lmdbjava/ComparatorTest.java +++ b/src/test/java/org/lmdbjava/ComparatorTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/test/java/org/lmdbjava/CursorIteratorTest.java b/src/test/java/org/lmdbjava/CursorIteratorTest.java index 7dd2acd0..c787ccb2 100644 --- a/src/test/java/org/lmdbjava/CursorIteratorTest.java +++ b/src/test/java/org/lmdbjava/CursorIteratorTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/test/java/org/lmdbjava/CursorParamTest.java b/src/test/java/org/lmdbjava/CursorParamTest.java index 37e573e9..8756534e 100644 --- a/src/test/java/org/lmdbjava/CursorParamTest.java +++ b/src/test/java/org/lmdbjava/CursorParamTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/test/java/org/lmdbjava/CursorTest.java b/src/test/java/org/lmdbjava/CursorTest.java index cdc73819..8e03ce5f 100644 --- a/src/test/java/org/lmdbjava/CursorTest.java +++ b/src/test/java/org/lmdbjava/CursorTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 5d454cb8..81d49899 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/test/java/org/lmdbjava/EnvTest.java b/src/test/java/org/lmdbjava/EnvTest.java index b111168d..0c580437 100644 --- a/src/test/java/org/lmdbjava/EnvTest.java +++ b/src/test/java/org/lmdbjava/EnvTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/test/java/org/lmdbjava/KeyRangeTest.java b/src/test/java/org/lmdbjava/KeyRangeTest.java index bdbdbf66..68fed292 100644 --- a/src/test/java/org/lmdbjava/KeyRangeTest.java +++ b/src/test/java/org/lmdbjava/KeyRangeTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/test/java/org/lmdbjava/LibraryTest.java b/src/test/java/org/lmdbjava/LibraryTest.java index f2cdbad3..35523272 100644 --- a/src/test/java/org/lmdbjava/LibraryTest.java +++ b/src/test/java/org/lmdbjava/LibraryTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/test/java/org/lmdbjava/MaskedFlagTest.java b/src/test/java/org/lmdbjava/MaskedFlagTest.java index 3bf6990b..e5a63952 100644 --- a/src/test/java/org/lmdbjava/MaskedFlagTest.java +++ b/src/test/java/org/lmdbjava/MaskedFlagTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/test/java/org/lmdbjava/MetaTest.java b/src/test/java/org/lmdbjava/MetaTest.java index d61e4a6e..18967fec 100644 --- a/src/test/java/org/lmdbjava/MetaTest.java +++ b/src/test/java/org/lmdbjava/MetaTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/test/java/org/lmdbjava/ResultCodeMapperTest.java b/src/test/java/org/lmdbjava/ResultCodeMapperTest.java index 78adb498..5d915f84 100644 --- a/src/test/java/org/lmdbjava/ResultCodeMapperTest.java +++ b/src/test/java/org/lmdbjava/ResultCodeMapperTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/test/java/org/lmdbjava/TestUtils.java b/src/test/java/org/lmdbjava/TestUtils.java index 7614f391..afc9c418 100644 --- a/src/test/java/org/lmdbjava/TestUtils.java +++ b/src/test/java/org/lmdbjava/TestUtils.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/test/java/org/lmdbjava/TutorialTest.java b/src/test/java/org/lmdbjava/TutorialTest.java index d631126c..91dfe0f3 100644 --- a/src/test/java/org/lmdbjava/TutorialTest.java +++ b/src/test/java/org/lmdbjava/TutorialTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/test/java/org/lmdbjava/TxnTest.java b/src/test/java/org/lmdbjava/TxnTest.java index 66b7276b..d8fcfb76 100644 --- a/src/test/java/org/lmdbjava/TxnTest.java +++ b/src/test/java/org/lmdbjava/TxnTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/test/java/org/lmdbjava/VerifierTest.java b/src/test/java/org/lmdbjava/VerifierTest.java index 1302fd25..ad0b42d9 100644 --- a/src/test/java/org/lmdbjava/VerifierTest.java +++ b/src/test/java/org/lmdbjava/VerifierTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. diff --git a/src/test/java/org/lmdbjava/package-info.java b/src/test/java/org/lmdbjava/package-info.java index 557d6fab..caa25a12 100644 --- a/src/test/java/org/lmdbjava/package-info.java +++ b/src/test/java/org/lmdbjava/package-info.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2019 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2020 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. From 67faba6d46f96b464b983bbebe5525907658cebc Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 13 Apr 2020 11:22:15 +1000 Subject: [PATCH 033/322] PMD suppressions --- src/main/java/org/lmdbjava/Verifier.java | 1 + src/test/java/org/lmdbjava/CursorIteratorTest.java | 1 + src/test/java/org/lmdbjava/CursorParamTest.java | 2 +- src/test/java/org/lmdbjava/CursorTest.java | 13 +++++++++++++ src/test/java/org/lmdbjava/DbiTest.java | 2 ++ src/test/java/org/lmdbjava/EnvTest.java | 4 ++++ src/test/java/org/lmdbjava/TutorialTest.java | 3 ++- src/test/java/org/lmdbjava/TxnTest.java | 14 ++++++++++++++ 8 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/lmdbjava/Verifier.java b/src/main/java/org/lmdbjava/Verifier.java index 207b9b7f..38a0b531 100644 --- a/src/main/java/org/lmdbjava/Verifier.java +++ b/src/main/java/org/lmdbjava/Verifier.java @@ -163,6 +163,7 @@ public Long call() { * @param unit units used to express the duration * @return number of database rows successfully verified */ + @SuppressWarnings("PMD.DoNotUseThreads") public long runFor(final long duration, final TimeUnit unit) { final long deadline = System.currentTimeMillis() + unit.toMillis(duration); final ExecutorService es = Executors.newSingleThreadExecutor(); diff --git a/src/test/java/org/lmdbjava/CursorIteratorTest.java b/src/test/java/org/lmdbjava/CursorIteratorTest.java index c787ccb2..ae20e57a 100644 --- a/src/test/java/org/lmdbjava/CursorIteratorTest.java +++ b/src/test/java/org/lmdbjava/CursorIteratorTest.java @@ -147,6 +147,7 @@ public void backwardSeekDeprecated() { } @Before + @SuppressWarnings("PMD.CloseResource") public void before() throws IOException { final File path = tmp.newFile(); env = create() diff --git a/src/test/java/org/lmdbjava/CursorParamTest.java b/src/test/java/org/lmdbjava/CursorParamTest.java index 8756534e..75e78a84 100644 --- a/src/test/java/org/lmdbjava/CursorParamTest.java +++ b/src/test/java/org/lmdbjava/CursorParamTest.java @@ -104,7 +104,7 @@ protected AbstractBufferRunner(final BufferProxy proxy) { this.proxy = proxy; } - @SuppressWarnings("checkstyle:executablestatementcount") + @SuppressWarnings({"checkstyle:executablestatementcount", "PMD.CloseResource"}) @Override public final void execute(final TemporaryFolder tmp) { try (Env env = env(tmp)) { diff --git a/src/test/java/org/lmdbjava/CursorTest.java b/src/test/java/org/lmdbjava/CursorTest.java index 8e03ce5f..aea29bbc 100644 --- a/src/test/java/org/lmdbjava/CursorTest.java +++ b/src/test/java/org/lmdbjava/CursorTest.java @@ -84,6 +84,7 @@ public void before() throws IOException { } @Test(expected = ClosedException.class) + @SuppressWarnings("PMD.CloseResource") public void closedCursorRejectsSubsequentGets() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite()) { @@ -94,6 +95,7 @@ public void closedCursorRejectsSubsequentGets() { } @Test + @SuppressWarnings("PMD.CloseResource") public void count() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); try (Txn txn = env.txnWrite()) { @@ -124,6 +126,7 @@ public void cursorCannotCloseIfTransactionCommitted() { } @Test + @SuppressWarnings("PMD.CloseResource") public void cursorFirstLastNextPrev() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite()) { @@ -154,6 +157,7 @@ public void cursorFirstLastNextPrev() { } @Test + @SuppressWarnings("PMD.CloseResource") public void delete() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); try (Txn txn = env.txnWrite()) { @@ -173,6 +177,7 @@ public void delete() { } @Test + @SuppressWarnings("PMD.CloseResource") public void putMultiple() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT, MDB_DUPFIXED); @@ -194,6 +199,7 @@ public void putMultiple() { } @Test(expected = IllegalArgumentException.class) + @SuppressWarnings("PMD.CloseResource") public void putMultipleWithoutMdbMultipleFlag() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); try (Txn txn = env.txnWrite()) { @@ -203,6 +209,7 @@ public void putMultipleWithoutMdbMultipleFlag() { } @Test + @SuppressWarnings("PMD.CloseResource") public void renewTxRo() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); @@ -219,6 +226,7 @@ public void renewTxRo() { } @Test(expected = ReadOnlyRequiredException.class) + @SuppressWarnings("PMD.CloseResource") public void renewTxRw() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite()) { @@ -230,6 +238,7 @@ public void renewTxRw() { } @Test + @SuppressWarnings("PMD.CloseResource") public void repeatedCloseCausesNotError() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); try (Txn txn = env.txnWrite()) { @@ -240,6 +249,7 @@ public void repeatedCloseCausesNotError() { } @Test + @SuppressWarnings("PMD.CloseResource") public void reserve() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); final ByteBuffer key = bb(5); @@ -259,6 +269,7 @@ public void reserve() { } @Test + @SuppressWarnings("PMD.CloseResource") public void returnValueForNoDupData() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); try (Txn txn = env.txnWrite()) { @@ -271,6 +282,7 @@ public void returnValueForNoDupData() { } @Test + @SuppressWarnings("PMD.CloseResource") public void returnValueForNoOverwrite() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite()) { @@ -284,6 +296,7 @@ public void returnValueForNoOverwrite() { } @Test + @SuppressWarnings("PMD.CloseResource") public void testCursorByteBufferDuplicate() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite()) { diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 81d49899..32265ec0 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -107,6 +107,7 @@ public void close() { } @Test + @SuppressWarnings("PMD.CloseResource") public void customComparator() { final Comparator reverseOrder = (o1, o2) -> { final int lexicalOrder = ByteBufferProxy.PROXY_OPTIMAL.compare(o1, o2); @@ -140,6 +141,7 @@ public void dbOpenMaxDatabases() { } @Test + @SuppressWarnings({"PMD.CloseResource", "PMD.DoNotUseThreads"}) public void dbiWithComparatorThreadSafety() { final Dbi db = env.openDbi(DB_1, PROXY_OPTIMAL::compare, MDB_CREATE); diff --git a/src/test/java/org/lmdbjava/EnvTest.java b/src/test/java/org/lmdbjava/EnvTest.java index 0c580437..8b8e067f 100644 --- a/src/test/java/org/lmdbjava/EnvTest.java +++ b/src/test/java/org/lmdbjava/EnvTest.java @@ -100,6 +100,7 @@ public void cannotChangeMaxReadersAfterOpen() throws IOException { } @Test(expected = AlreadyClosedException.class) + @SuppressWarnings("PMD.CloseResource") public void cannotInfoOnceClosed() throws IOException { final File path = tmp.newFile(); final Env env = create() @@ -129,6 +130,7 @@ public void cannotOverflowMapSize() { } @Test(expected = AlreadyClosedException.class) + @SuppressWarnings("PMD.CloseResource") public void cannotStatOnceClosed() throws IOException { final File path = tmp.newFile(); final Env env = create() @@ -139,6 +141,7 @@ public void cannotStatOnceClosed() throws IOException { } @Test(expected = AlreadyClosedException.class) + @SuppressWarnings("PMD.CloseResource") public void cannotSyncOnceClosed() throws IOException { final File path = tmp.newFile(); final Env env = create() @@ -200,6 +203,7 @@ public void copyRejectsNonEmptyDestination() throws IOException { } @Test + @SuppressWarnings("PMD.CloseResource") public void createAsDirectory() throws IOException { final File path = tmp.newFolder(); final Env env = create() diff --git a/src/test/java/org/lmdbjava/TutorialTest.java b/src/test/java/org/lmdbjava/TutorialTest.java index 91dfe0f3..7d4f37f5 100644 --- a/src/test/java/org/lmdbjava/TutorialTest.java +++ b/src/test/java/org/lmdbjava/TutorialTest.java @@ -64,6 +64,7 @@ * need to install the LMDB system library yourself. 32-bit platforms are not * supported. */ +@SuppressWarnings("PMD.CloseResource") public final class TutorialTest { private static final String DB_NAME = "my DB"; @@ -147,7 +148,7 @@ public void tutorial1() throws IOException { * @throws InterruptedException if executor shutdown interrupted */ @Test - @SuppressWarnings({"ConvertToTryWithResources", + @SuppressWarnings({"ConvertToTryWithResources", "PMD.DoNotUseThreads", "checkstyle:executablestatementcount"}) public void tutorial2() throws IOException, InterruptedException { final Env env = createSimpleEnv(tmp.newFolder()); diff --git a/src/test/java/org/lmdbjava/TxnTest.java b/src/test/java/org/lmdbjava/TxnTest.java index d8fcfb76..6f98be86 100644 --- a/src/test/java/org/lmdbjava/TxnTest.java +++ b/src/test/java/org/lmdbjava/TxnTest.java @@ -96,6 +96,7 @@ public void largeKeysRejected() throws IOException { } @Test + @SuppressWarnings("PMD.CloseResource") public void rangeSearch() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); @@ -152,6 +153,7 @@ public void readWriteTxnDeniedInReadOnlyEnv() { } @Test(expected = NotReadyException.class) + @SuppressWarnings("PMD.CloseResource") public void testCheckNotCommitted() { final Txn txn = env.txnRead(); txn.commit(); @@ -159,12 +161,14 @@ public void testCheckNotCommitted() { } @Test(expected = ReadOnlyRequiredException.class) + @SuppressWarnings("PMD.CloseResource") public void testCheckReadOnly() { final Txn txn = env.txnWrite(); txn.checkReadOnly(); } @Test(expected = ReadWriteRequiredException.class) + @SuppressWarnings("PMD.CloseResource") public void testCheckWritesAllowed() { final Txn txn = env.txnRead(); txn.checkWritesAllowed(); @@ -210,6 +214,7 @@ public void txCannotAbortIfAlreadyCommitted() { } @Test(expected = NotReadyException.class) + @SuppressWarnings("PMD.CloseResource") public void txCannotCommitTwice() { final Txn txn = env.txnRead(); txn.commit(); @@ -224,6 +229,7 @@ public void txConstructionDeniedIfEnvClosed() { } @Test + @SuppressWarnings("PMD.CloseResource") public void txParent() { final Txn txRoot = env.txnWrite(); final Txn txChild = env.txn(txRoot); @@ -232,18 +238,21 @@ public void txParent() { } @Test(expected = IncompatibleParent.class) + @SuppressWarnings("PMD.CloseResource") public void txParentROChildRWIncompatible() { final Txn txRoot = env.txnRead(); env.txn(txRoot); // error } @Test(expected = IncompatibleParent.class) + @SuppressWarnings("PMD.CloseResource") public void txParentRWChildROIncompatible() { final Txn txRoot = env.txnWrite(); env.txn(txRoot, MDB_RDONLY_TXN); // error } @Test + @SuppressWarnings("PMD.CloseResource") public void txReadOnly() { final Txn txn = env.txnRead(); assertThat(txn.getParent(), is(nullValue())); @@ -262,6 +271,7 @@ public void txReadOnly() { } @Test + @SuppressWarnings("PMD.CloseResource") public void txReadWrite() { final Txn txn = env.txnWrite(); assertThat(txn.getParent(), is(nullValue())); @@ -276,12 +286,14 @@ public void txReadWrite() { } @Test(expected = NotResetException.class) + @SuppressWarnings("PMD.CloseResource") public void txRenewDeniedWithoutPriorReset() { final Txn txn = env.txnRead(); txn.renew(); } @Test(expected = ResetException.class) + @SuppressWarnings("PMD.CloseResource") public void txResetDeniedForAlreadyResetTransaction() { final Txn txn = env.txnRead(); txn.reset(); @@ -291,12 +303,14 @@ public void txResetDeniedForAlreadyResetTransaction() { } @Test(expected = ReadOnlyRequiredException.class) + @SuppressWarnings("PMD.CloseResource") public void txResetDeniedForReadWriteTransaction() { final Txn txn = env.txnWrite(); txn.reset(); } @Test(expected = BadValueSizeException.class) + @SuppressWarnings("PMD.CloseResource") public void zeroByteKeysRejected() throws IOException { final Dbi dbi = env.openDbi(DB_1, MDB_CREATE); final ByteBuffer key = allocateDirect(4); From a18dfadc1fadf78cf20ec681e40fdd421c2933a2 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 13 Apr 2020 11:22:29 +1000 Subject: [PATCH 034/322] Update to JNR FFI 2.1.12 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3b0c2ca8..c363ccc0 100644 --- a/pom.xml +++ b/pom.xml @@ -72,7 +72,7 @@ com.github.jnr jnr-ffi - 2.1.9 + 2.1.12 com.github.jnr From 9e9d4212b3877ca2bdd1405857476a700529d82a Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 13 Apr 2020 11:22:51 +1000 Subject: [PATCH 035/322] Update to JNR JFFI 1.2.23 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index c363ccc0..b12764d7 100644 --- a/pom.xml +++ b/pom.xml @@ -83,13 +83,13 @@ com.github.jnr jffi - 1.2.18 + 1.2.23 com.github.jnr jffi native - 1.2.18 + 1.2.23 com.github.jnr From 79b32a66e62157c46831fa7a18b1cc0d85f1b106 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 13 Apr 2020 11:23:03 +1000 Subject: [PATCH 036/322] Update to JNR Constants 0.9.15 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b12764d7..d352abc3 100644 --- a/pom.xml +++ b/pom.xml @@ -94,7 +94,7 @@ com.github.jnr jnr-constants - 0.9.12 + 0.9.15 com.jakewharton.byteunits From 11e95bd91b8d505d2c73377e06fd87ea5ebd4cb4 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 13 Apr 2020 11:23:13 +1000 Subject: [PATCH 037/322] Update to Agrona 1.4.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d352abc3..238bb1ce 100644 --- a/pom.xml +++ b/pom.xml @@ -105,7 +105,7 @@ org.agrona agrona - 1.0.1 + 1.4.1 true From f7d9a3bc60b5fc7e60c18b3166d1d5dcfd4beabd Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 13 Apr 2020 11:23:20 +1000 Subject: [PATCH 038/322] Update to Jetty 4.1.48 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 238bb1ce..3eb5c626 100644 --- a/pom.xml +++ b/pom.xml @@ -111,7 +111,7 @@ io.netty netty-all - 4.1.36.Final + 4.1.48.Final test From e9ff5ea021ef507fc5d6c00f8fbafa384765b5ad Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 13 Apr 2020 11:23:29 +1000 Subject: [PATCH 039/322] Update to Guava 28.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3eb5c626..495ab034 100644 --- a/pom.xml +++ b/pom.xml @@ -117,7 +117,7 @@ com.google.guava guava - 27.1-jre + 28.2-jre test From a49f3321213a647147db089014ee1d2fb44ae5fa Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 13 Apr 2020 11:23:38 +1000 Subject: [PATCH 040/322] Update to Acegi Standard Project 0.2.5 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 495ab034..b01b2246 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ au.com.acegi acegi-standard-project - 0.2.3 + 0.2.5 org.lmdbjava lmdbjava From 4da2cd1bc4a6c2d2f7ebd715037ae67a529ada99 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 13 Apr 2020 12:47:54 +1000 Subject: [PATCH 041/322] AppVeyor to skip Checkstyle due to CRFL endings --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 2623c2b0..2c1a5bb4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -22,7 +22,7 @@ install: - pip install codecov build_script: - - mvn -V -B --settings .travis-settings.xml -e verify + - mvn -V -B -Dcheckstyle.skip=true --settings .travis-settings.xml -e verify - codecov -X gcov -f target/site/jacoco/jacoco.xml notifications: From 870802846d3e9065c1b88abd22cbc2dfdf4de493 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 13 Apr 2020 12:48:10 +1000 Subject: [PATCH 042/322] AppVeyor to use Maven 3.6.3 as 3.6.0 no longer available --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 2c1a5bb4..e4fc2013 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,7 +6,7 @@ clone_depth: 50 environment: JAVA_HOME: "C:\\Program Files\\Java\\jdk11" - MVN_VER: 3.6.0 + MVN_VER: 3.6.3 PYTHON: "C:\\Python35" cache: From 7b667f3cfe6a590dead7cf8f3825ac3351997b40 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 13 Apr 2020 13:03:42 +1000 Subject: [PATCH 043/322] Explicitly configure AppVeyor PMD rule set --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index e4fc2013..515a8d1e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -22,7 +22,7 @@ install: - pip install codecov build_script: - - mvn -V -B -Dcheckstyle.skip=true --settings .travis-settings.xml -e verify + - mvn -V -B -Dpmd.ruleset=C:\projects\lmdbjava\target/acegi-standard-resources/au/com/acegi/standard/rule-set-pmd.xml -Dcheckstyle.skip=true --settings .travis-settings.xml -e verify - codecov -X gcov -f target/site/jacoco/jacoco.xml notifications: From 248878a53d3c6ba6f4f750d3997fa22d62df5714 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 13 Apr 2020 13:33:37 +1000 Subject: [PATCH 044/322] Use explicit Netty JARs to mitigate dependency:go-offline failures While https://netty.io/downloads.html states to use netty-all for 4.0.0 and above releases, both Travis and local development environments successfully reproduce missing native artifacts. Given Netty support is only for buffer testing purposes in LmdbJava, it's easier to simply depend on the minimal Netty JARs that we require. This also significantly speeds up builds when the .m2 cache has been invalidated, as numerous unnecessary Netty components are no longer downloaded. --- pom.xml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b01b2246..3c214cf3 100644 --- a/pom.xml +++ b/pom.xml @@ -110,7 +110,13 @@ io.netty - netty-all + netty-buffer + 4.1.48.Final + test + + + io.netty + netty-common 4.1.48.Final test @@ -164,6 +170,7 @@ com.github.jnr:jffi + io.netty:netty-common From f7e685adc3f9b789282206a0f0a6a37963bd12c4 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 13 Apr 2020 13:43:44 +1000 Subject: [PATCH 045/322] Travis to test macOS via xcode 11.3 image --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 37bd63ff..cfd5c1e8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ matrix: jdk: openjdk8 - os: osx language: java - osx_image: xcode10.1 + osx_image: xcode11.3 notifications: email: - ben.alex@acegi.com.au From 08a0b43558e64b7536669f7b1c5b23e15a793e35 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Wed, 15 Apr 2020 10:49:23 +1000 Subject: [PATCH 046/322] Update to Acegi Standard Project 0.3.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3c214cf3..a2c7d507 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ au.com.acegi acegi-standard-project - 0.2.5 + 0.3.1 org.lmdbjava lmdbjava From 43f101d03ea750db2320fef2493a9c1a5f63c481 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Wed, 15 Apr 2020 11:03:03 +1000 Subject: [PATCH 047/322] Migrate from Travis and AppVeyor to GitHub Actions --- .github/workflows/maven.yml | 100 ++++++++++++++++++++++++++++++++++++ .travis-settings.xml | 44 ---------------- .travis.yml | 26 ---------- README.md | 10 ++-- appveyor.yml | 33 ------------ pom.xml | 4 +- 6 files changed, 107 insertions(+), 110 deletions(-) create mode 100644 .github/workflows/maven.yml delete mode 100644 .travis-settings.xml delete mode 100644 .travis.yml delete mode 100644 appveyor.yml diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 00000000..e7d62450 --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,100 @@ +name: Maven Build and Deployment + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + name: Java 11 Build and Verify + runs-on: ubuntu-16.04 + + steps: + - name: Check out Git repository + uses: actions/checkout@v2 + + - name: Set up Java and Maven + uses: actions/setup-java@v1 + with: + java-version: 11 + + - name: Cache Maven packages + uses: actions/cache@v1 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + + - name: Build with Maven + run: mvn -B verify + + - name: Upload code coverage to Codecov + uses: codecov/codecov-action@v1 + + compatibility-checks: + name: Java ${{ matrix.java }} on ${{ matrix.os }} Compatibility + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ubuntu-16.04, macos-latest, windows-latest] + java: [8, 11, 14] + + steps: + - name: Check out Git repository + uses: actions/checkout@v2 + + - name: Set up Java and Maven + uses: actions/setup-java@v1 + with: + java-version: ${{ matrix.java }} + + - name: Cache Maven packages + uses: actions/cache@v1 + with: + path: ~/.m2 + key: ${{ runner.os }}-${{ matrix.java }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-${{ matrix.java }}-m2 + + - name: Test with Maven + run: mvn -B test + + - name: Upload Surefire reports on test failure + uses: actions/upload-artifact@v1 + if: failure() + with: + name: surefire-test-log + path: target/surefire-reports + + deploy: + name: Deploy to OSSRH + needs: [build, compatibility-checks] + if: github.event_name == 'push' + runs-on: ubuntu-latest + + steps: + - name: Check out Git repository + uses: actions/checkout@v2 + + - name: Set up Java and Maven + uses: actions/setup-java@v1 + with: + java-version: 11 + + - name: Cache Maven packages + uses: actions/cache@v1 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + + - name: Publish Maven package + uses: samuelmeuli/action-maven-publish@v1 + with: + gpg_private_key: ${{ secrets.gpg_private_key }} + gpg_passphrase: ${{ secrets.gpg_passphrase }} + nexus_username: ${{ secrets.nexus_username }} + nexus_password: ${{ secrets.nexus_password }} + maven_args: "-DskipTests" diff --git a/.travis-settings.xml b/.travis-settings.xml deleted file mode 100644 index 8fcbfc94..00000000 --- a/.travis-settings.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - ossrh - ${env.CENTRAL_USERNAME} - ${env.CENTRAL_PASSWORD} - - - github - ${env.GITHUB_PASSWORD} - - - - - ossrh - - true - - - ${env.GPG_KEY_NAME} - ${env.GPG_PASSPHRASE} - - - - allow-snapshots - - true - - - - snapshots-repo - https://oss.sonatype.org/content/repositories/snapshots - - false - - - true - - - - - - diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index cfd5c1e8..00000000 --- a/.travis.yml +++ /dev/null @@ -1,26 +0,0 @@ -cache: - directories: - - $HOME/.m2 -matrix: - include: - - os: linux - dist: xenial - language: java - jdk: openjdk8 - - os: osx - language: java - osx_image: xcode11.3 -notifications: - email: - - ben.alex@acegi.com.au - - stoffe@gmail.com -before_install: - - if [ "$TRAVIS_OS_NAME" == "osx" ]; then export JAVA_HOME=$(/usr/libexec/java_home); fi -install: - - mvn -B -Pdeploy --settings .travis-settings.xml -V dependency:go-offline -script: - - mvn -B -Pdeploy --settings .travis-settings.xml -Dgpg.skip=true verify -after_success: - - bash <(curl -s https://codecov.io/bash) - - if [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" == "" ] && [ "$TRAVIS_OS_NAME" == "linux" ]; then echo $GPG_KEY | base64 --decode | gpg --batch --fast-import -; fi - - if [ "$TRAVIS_PULL_REQUEST" == "false" ] && [ "$TRAVIS_TAG" == "" ] && [ "$TRAVIS_OS_NAME" == "linux" ]; then mvn -B -Pdeploy --settings .travis-settings.xml deploy; fi diff --git a/README.md b/README.md index 611c2f0f..e2ee753b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -[![Build Status](https://travis-ci.org/lmdbjava/lmdbjava.svg?branch=master)](https://travis-ci.org/lmdbjava/lmdbjava) -[![Build Status](https://ci.appveyor.com/api/projects/status/0w4yb9ybx22g2pwp?svg=true)](https://ci.appveyor.com/project/benalexau/lmdbjava) +[![Maven Build and Deployment](https://github.com/lmdbjava/lmdbjava/workflows/Maven%20Build%20and%20Deployment/badge.svg)](https://github.com/lmdbjava/lmdbjava/actions) [![codecov](https://codecov.io/gh/lmdbjava/lmdbjava/branch/master/graph/badge.svg)](https://codecov.io/gh/lmdbjava/lmdbjava) [![Javadocs](http://www.javadoc.io/badge/org.lmdbjava/lmdbjava.svg?color=blue)](http://www.javadoc.io/doc/org.lmdbjava/lmdbjava) [![Maven Central](https://img.shields.io/maven-central/v/org.lmdbjava/lmdbjava.svg?maxAge=3600)](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.lmdbjava%22%20AND%20a%3A%22lmdbjava%22) @@ -35,9 +34,10 @@ * Community questions and contributions are welcome through [GitHub tickets](https://github.com/lmdbjava/lmdbjava/issues) * Mature code (commenced in 2016) and used for heavy production workloads (eg > 500 TB of HFT data) * Actively maintained and with a "Zero Bug Policy" before every release (see [issues](https://github.com/lmdbjava/lmdbjava/issues)) -* Available from [Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.lmdbjava%22%20AND%20a%3A%22lmdbjava%22) and [OSS Sonatype Snapshots](https://oss.sonatype.org/content/repositories/snapshots/org/lmdbjava/lmdbjava) (see [Change Log](https://github.com/lmdbjava/lmdbjava/wiki/Change-Log)) -* Comprehensive [JavaDocs](http://www.javadoc.io/doc/org.lmdbjava/lmdbjava) -* [Linux](https://travis-ci.org/lmdbjava/lmdbjava), [OS X](https://travis-ci.org/lmdbjava/lmdbjava) and [Windows](https://ci.appveyor.com/project/benalexau/lmdbjava) CI +* Available from [Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.lmdbjava%22%20AND%20a%3A%22lmdbjava%22) and [OSS Sonatype Snapshots](https://oss.sonatype.org/content/repositories/snapshots/org/lmdbjava/lmdbjava) +* Detailed [JavaDocs](http://www.javadoc.io/doc/org.lmdbjava/lmdbjava) +* [Change Log](https://github.com/lmdbjava/lmdbjava/wiki/Change-Log) to assist with upgrades +* [Continuous integration](https://github.com/lmdbjava/lmdbjava/actions) testing on Linux, Windows and macOS across Java versions 8, 11 and 14 ### Performance diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 515a8d1e..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,33 +0,0 @@ -version: '{build}' - -image: Visual Studio 2017 - -clone_depth: 50 - -environment: - JAVA_HOME: "C:\\Program Files\\Java\\jdk11" - MVN_VER: 3.6.3 - PYTHON: "C:\\Python35" - -cache: - - C:\Users\appveyor\apache-maven-%MVN_VER% -> appveyor.yml - - C:\Users\appveyor\.m2 -> pom.xml - -install: - - if not exist C:\Users\appveyor\apache-maven-%MVN_VER% ( - curl -LsS "http://www.apache.org/dyn/closer.cgi?action=download&filename=maven/maven-3/%MVN_VER%/binaries/apache-maven-%MVN_VER%-bin.zip" > apache-maven.zip && - unzip apache-maven.zip -d C:\Users\appveyor - ) - - SET PATH=%PYTHON%;%PYTHON%\\Scripts;C:\Users\appveyor\apache-maven-%MVN_VER%\bin;%PATH% - - pip install codecov - -build_script: - - mvn -V -B -Dpmd.ruleset=C:\projects\lmdbjava\target/acegi-standard-resources/au/com/acegi/standard/rule-set-pmd.xml -Dcheckstyle.skip=true --settings .travis-settings.xml -e verify - - codecov -X gcov -f target/site/jacoco/jacoco.xml - -notifications: - - provider: Email - to: - - ben.alex@acegi.com.au - - stoffe@gmail.com - on_build_success: false diff --git a/pom.xml b/pom.xml index a2c7d507..827dd173 100644 --- a/pom.xml +++ b/pom.xml @@ -52,8 +52,8 @@ https://github.com/${github.org}/${github.repo}/issues - travis-ci - https://travis-ci.org/${github.org}/${github.repo} + GitHub Actions + https://github.com/${github.org}/${github.repo}/actions lmdbjava From 528dad36948fda48432377aec59c7e4c0b3b05ea Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Wed, 15 Apr 2020 14:03:05 +1000 Subject: [PATCH 048/322] Update to LMDB 0.9.24 snapshot for testing (#148) --- .github/workflows/maven.yml | 18 +++++++++++++++--- pom.xml | 8 ++++---- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index e7d62450..4953db73 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -20,12 +20,16 @@ jobs: with: java-version: 11 + - name: Add OSSRH to the snapshot repositories list + uses: s4u/maven-settings-action@v2.1.0 + with: + sonatypeSnapshots: true + - name: Cache Maven packages uses: actions/cache@v1 with: path: ~/.m2 key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-m2 - name: Build with Maven run: mvn -B verify @@ -51,12 +55,16 @@ jobs: with: java-version: ${{ matrix.java }} + - name: Add OSSRH to the snapshot repositories list + uses: s4u/maven-settings-action@v2.1.0 + with: + sonatypeSnapshots: true + - name: Cache Maven packages uses: actions/cache@v1 with: path: ~/.m2 key: ${{ runner.os }}-${{ matrix.java }}-m2-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-${{ matrix.java }}-m2 - name: Test with Maven run: mvn -B test @@ -83,12 +91,16 @@ jobs: with: java-version: 11 + - name: Add OSSRH to the snapshot repositories list + uses: s4u/maven-settings-action@v2.1.0 + with: + sonatypeSnapshots: true + - name: Cache Maven packages uses: actions/cache@v1 with: path: ~/.m2 key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} - restore-keys: ${{ runner.os }}-m2 - name: Publish Maven package uses: samuelmeuli/action-maven-publish@v1 diff --git a/pom.xml b/pom.xml index 827dd173..6b032ef0 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ org.lmdbjava lmdbjava - 0.7.1-SNAPSHOT + 0.8.0-SNAPSHOT jar LmdbJava Low latency Java API for the ultra-fast, embedded Symas Lightning Database (LMDB) @@ -135,19 +135,19 @@ org.lmdbjava lmdbjava-native-linux-x86_64 - 0.9.23-1 + 0.9.24-1-SNAPSHOT true org.lmdbjava lmdbjava-native-windows-x86_64 - 0.9.23-1 + 0.9.24-1-SNAPSHOT true org.lmdbjava lmdbjava-native-osx-x86_64 - 0.9.23-1 + 0.9.24-1-SNAPSHOT true From e621eb8846539be62dd52bb4538a792a5db74c06 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Wed, 15 Apr 2020 14:37:22 +1000 Subject: [PATCH 049/322] Add Ubuntu 18.04 to CI (#147) --- .github/workflows/maven.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 4953db73..b6899406 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -9,7 +9,7 @@ on: jobs: build: name: Java 11 Build and Verify - runs-on: ubuntu-16.04 + runs-on: ubuntu-latest steps: - name: Check out Git repository @@ -43,7 +43,7 @@ jobs: strategy: matrix: - os: [ubuntu-16.04, macos-latest, windows-latest] + os: [ubuntu-16.04, ubuntu-latest, macos-latest, windows-latest] java: [8, 11, 14] steps: From fed0c5df22ade16e05570f10d624bca5c64221bf Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Wed, 15 Apr 2020 14:44:07 +1000 Subject: [PATCH 050/322] Perform TutorialTest in two phases during CI --- .github/workflows/maven.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index b6899406..a657e88d 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -32,7 +32,7 @@ jobs: key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} - name: Build with Maven - run: mvn -B verify + run: mvn -B -Dtest='!TutorialTest' verify - name: Upload code coverage to Codecov uses: codecov/codecov-action@v1 @@ -67,7 +67,10 @@ jobs: key: ${{ runner.os }}-${{ matrix.java }}-m2-${{ hashFiles('**/pom.xml') }} - name: Test with Maven - run: mvn -B test + run: mvn -B -Dtest='!TutorialTest' test + + - name: Test with Maven 2 + run: mvn -B -Dtest='TutorialTest' test - name: Upload Surefire reports on test failure uses: actions/upload-artifact@v1 From d273a58bbb81781a4c034b2f2be7e106b1edbf4b Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Wed, 15 Apr 2020 15:53:29 +1000 Subject: [PATCH 051/322] Handle resource closing via try-with-resources where possible Certain test cases cannot use a try-with-resources block because the test is fundamentally designed to exercise improper closing sequences. The TutorialTest remains an exception in the interests of readability. However it may be necessary to refactor into individual test classes so the resources can be better managed. --- .../java/org/lmdbjava/CursorParamTest.java | 6 +- src/test/java/org/lmdbjava/CursorTest.java | 55 ++++----- src/test/java/org/lmdbjava/DbiTest.java | 11 +- src/test/java/org/lmdbjava/TxnTest.java | 115 +++++++++--------- 4 files changed, 89 insertions(+), 98 deletions(-) diff --git a/src/test/java/org/lmdbjava/CursorParamTest.java b/src/test/java/org/lmdbjava/CursorParamTest.java index 75e78a84..0e2fa9ee 100644 --- a/src/test/java/org/lmdbjava/CursorParamTest.java +++ b/src/test/java/org/lmdbjava/CursorParamTest.java @@ -104,16 +104,16 @@ protected AbstractBufferRunner(final BufferProxy proxy) { this.proxy = proxy; } - @SuppressWarnings({"checkstyle:executablestatementcount", "PMD.CloseResource"}) + @SuppressWarnings("checkstyle:executablestatementcount") @Override public final void execute(final TemporaryFolder tmp) { try (Env env = env(tmp)) { assertThat(env.getDbiNames(), empty()); final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); assertThat(env.getDbiNames().get(0), is(DB_1.getBytes(UTF_8))); - try (Txn txn = env.txnWrite()) { + try (Txn txn = env.txnWrite(); + Cursor c = db.openCursor(txn)) { // populate data - final Cursor c = db.openCursor(txn); c.put(set(1), set(2), MDB_NOOVERWRITE); c.put(set(3), set(4)); c.put(set(5), set(6)); diff --git a/src/test/java/org/lmdbjava/CursorTest.java b/src/test/java/org/lmdbjava/CursorTest.java index aea29bbc..40dd9f97 100644 --- a/src/test/java/org/lmdbjava/CursorTest.java +++ b/src/test/java/org/lmdbjava/CursorTest.java @@ -95,11 +95,10 @@ public void closedCursorRejectsSubsequentGets() { } @Test - @SuppressWarnings("PMD.CloseResource") public void count() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); - try (Txn txn = env.txnWrite()) { - final Cursor c = db.openCursor(txn); + try (Txn txn = env.txnWrite(); + Cursor c = db.openCursor(txn)) { c.put(bb(1), bb(2), MDB_APPENDDUP); assertThat(c.count(), is(1L)); c.put(bb(1), bb(4), MDB_APPENDDUP); @@ -126,12 +125,10 @@ public void cursorCannotCloseIfTransactionCommitted() { } @Test - @SuppressWarnings("PMD.CloseResource") public void cursorFirstLastNextPrev() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); - try (Txn txn = env.txnWrite()) { - // populate data - final Cursor c = db.openCursor(txn); + try (Txn txn = env.txnWrite(); + Cursor c = db.openCursor(txn)) { c.put(bb(1), bb(2), MDB_NOOVERWRITE); c.put(bb(3), bb(4)); c.put(bb(5), bb(6)); @@ -157,11 +154,10 @@ public void cursorFirstLastNextPrev() { } @Test - @SuppressWarnings("PMD.CloseResource") public void delete() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); - try (Txn txn = env.txnWrite()) { - final Cursor c = db.openCursor(txn); + try (Txn txn = env.txnWrite(); + Cursor c = db.openCursor(txn)) { c.put(bb(1), bb(2), MDB_NOOVERWRITE); c.put(bb(3), bb(4)); assertThat(c.seek(MDB_FIRST), is(true)); @@ -177,7 +173,6 @@ public void delete() { } @Test - @SuppressWarnings("PMD.CloseResource") public void putMultiple() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT, MDB_DUPFIXED); @@ -191,19 +186,18 @@ public void putMultiple() { final int key = 100; final ByteBuffer k = bb(key); - try (Txn txn = env.txnWrite()) { - final Cursor c = db.openCursor(txn); + try (Txn txn = env.txnWrite(); + Cursor c = db.openCursor(txn)) { c.putMultiple(k, values, elemCount, MDB_MULTIPLE); assertThat(c.count(), is((long) elemCount)); } } @Test(expected = IllegalArgumentException.class) - @SuppressWarnings("PMD.CloseResource") public void putMultipleWithoutMdbMultipleFlag() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); - try (Txn txn = env.txnWrite()) { - final Cursor c = db.openCursor(txn); + try (Txn txn = env.txnWrite(); + Cursor c = db.openCursor(txn)) { c.putMultiple(bb(100), bb(1), 1); } } @@ -223,17 +217,19 @@ public void renewTxRo() { c.renew(txn); txn.commit(); } + + c.close(); } @Test(expected = ReadOnlyRequiredException.class) - @SuppressWarnings("PMD.CloseResource") public void renewTxRw() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite()) { assertThat(txn.isReadOnly(), is(false)); - final Cursor c = db.openCursor(txn); - c.renew(txn); + try (Cursor c = db.openCursor(txn)) { + c.renew(txn); + } } } @@ -249,16 +245,16 @@ public void repeatedCloseCausesNotError() { } @Test - @SuppressWarnings("PMD.CloseResource") public void reserve() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); final ByteBuffer key = bb(5); try (Txn txn = env.txnWrite()) { - final Cursor c = db.openCursor(txn); assertNull(db.get(txn, key)); - final ByteBuffer val = c.reserve(key, BYTES * 2); - assertNotNull(db.get(txn, key)); - val.putLong(MIN_VALUE).flip(); + try (Cursor c = db.openCursor(txn)) { + final ByteBuffer val = c.reserve(key, BYTES * 2); + assertNotNull(db.get(txn, key)); + val.putLong(MIN_VALUE).flip(); + } txn.commit(); } try (Txn txn = env.txnWrite()) { @@ -269,11 +265,10 @@ public void reserve() { } @Test - @SuppressWarnings("PMD.CloseResource") public void returnValueForNoDupData() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); - try (Txn txn = env.txnWrite()) { - final Cursor c = db.openCursor(txn); + try (Txn txn = env.txnWrite(); + Cursor c = db.openCursor(txn)) { // ok assertThat(c.put(bb(5), bb(6), MDB_NODUPDATA), is(true)); assertThat(c.put(bb(5), bb(7), MDB_NODUPDATA), is(true)); @@ -282,11 +277,10 @@ public void returnValueForNoDupData() { } @Test - @SuppressWarnings("PMD.CloseResource") public void returnValueForNoOverwrite() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); - try (Txn txn = env.txnWrite()) { - final Cursor c = db.openCursor(txn); + try (Txn txn = env.txnWrite(); + Cursor c = db.openCursor(txn)) { // ok assertThat(c.put(bb(5), bb(6), MDB_NOOVERWRITE), is(true)); // fails, but gets exist val @@ -296,7 +290,6 @@ public void returnValueForNoOverwrite() { } @Test - @SuppressWarnings("PMD.CloseResource") public void testCursorByteBufferDuplicate() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite()) { diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 32265ec0..882d7731 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -107,7 +107,6 @@ public void close() { } @Test - @SuppressWarnings("PMD.CloseResource") public void customComparator() { final Comparator reverseOrder = (o1, o2) -> { final int lexicalOrder = ByteBufferProxy.PROXY_OPTIMAL.compare(o1, o2); @@ -124,8 +123,8 @@ public void customComparator() { assertThat(db.put(txn, bb(8), bb(7)), is(true)); txn.commit(); } - try (Txn txn = env.txnRead()) { - final CursorIterator iter = db.iterate(txn, atMost(bb(4))); + try (Txn txn = env.txnRead(); + CursorIterator iter = db.iterate(txn, atMost(bb(4)))) { assertThat(iter.next().key(), is(bb(8))); assertThat(iter.next().key(), is(bb(6))); assertThat(iter.next().key(), is(bb(4))); @@ -141,7 +140,7 @@ public void dbOpenMaxDatabases() { } @Test - @SuppressWarnings({"PMD.CloseResource", "PMD.DoNotUseThreads"}) + @SuppressWarnings("PMD.DoNotUseThreads") public void dbiWithComparatorThreadSafety() { final Dbi db = env.openDbi(DB_1, PROXY_OPTIMAL::compare, MDB_CREATE); @@ -165,8 +164,8 @@ public void dbiWithComparatorThreadSafety() { } } - try (Txn txn = env.txnRead()) { - final CursorIterator iter = db.iterate(txn); + try (Txn txn = env.txnRead(); + CursorIterator iter = db.iterate(txn)) { final List result = new ArrayList<>(); while (iter.hasNext()) { diff --git a/src/test/java/org/lmdbjava/TxnTest.java b/src/test/java/org/lmdbjava/TxnTest.java index 6f98be86..21b35808 100644 --- a/src/test/java/org/lmdbjava/TxnTest.java +++ b/src/test/java/org/lmdbjava/TxnTest.java @@ -46,6 +46,7 @@ import static org.lmdbjava.Env.create; import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; import static org.lmdbjava.EnvFlags.MDB_RDONLY_ENV; +import static org.lmdbjava.KeyRange.closed; import static org.lmdbjava.TestUtils.DB_1; import static org.lmdbjava.TestUtils.POSIX_MODE; import static org.lmdbjava.TestUtils.bb; @@ -96,7 +97,6 @@ public void largeKeysRejected() throws IOException { } @Test - @SuppressWarnings("PMD.CloseResource") public void rangeSearch() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); @@ -120,10 +120,10 @@ public void rangeSearch() { end.put("z".getBytes(UTF_8)).flip(); final List keysFound = new ArrayList<>(); - final CursorIterator ckr = db.iterate(txn, KeyRange.closed( - start, end)); - for (final CursorIterator.KeyVal kv : ckr.iterable()) { - keysFound.add(UTF_8.decode(kv.key()).toString()); + try (CursorIterator ckr = db.iterate(txn, closed(start, end))) { + for (final CursorIterator.KeyVal kv : ckr.iterable()) { + keysFound.add(UTF_8.decode(kv.key()).toString()); + } } assertEquals(3, keysFound.size()); @@ -153,25 +153,25 @@ public void readWriteTxnDeniedInReadOnlyEnv() { } @Test(expected = NotReadyException.class) - @SuppressWarnings("PMD.CloseResource") public void testCheckNotCommitted() { - final Txn txn = env.txnRead(); - txn.commit(); - txn.checkReady(); + try (Txn txn = env.txnRead()) { + txn.commit(); + txn.checkReady(); + } } @Test(expected = ReadOnlyRequiredException.class) - @SuppressWarnings("PMD.CloseResource") public void testCheckReadOnly() { - final Txn txn = env.txnWrite(); - txn.checkReadOnly(); + try (Txn txn = env.txnWrite()) { + txn.checkReadOnly(); + } } @Test(expected = ReadWriteRequiredException.class) - @SuppressWarnings("PMD.CloseResource") public void testCheckWritesAllowed() { - final Txn txn = env.txnRead(); - txn.checkWritesAllowed(); + try (Txn txn = env.txnRead()) { + txn.checkWritesAllowed(); + } } @Test @@ -214,11 +214,11 @@ public void txCannotAbortIfAlreadyCommitted() { } @Test(expected = NotReadyException.class) - @SuppressWarnings("PMD.CloseResource") public void txCannotCommitTwice() { - final Txn txn = env.txnRead(); - txn.commit(); - txn.commit(); // error + try (Txn txn = env.txnRead()) { + txn.commit(); + txn.commit(); // error + } } @Test(expected = AlreadyClosedException.class) @@ -229,45 +229,45 @@ public void txConstructionDeniedIfEnvClosed() { } @Test - @SuppressWarnings("PMD.CloseResource") public void txParent() { - final Txn txRoot = env.txnWrite(); - final Txn txChild = env.txn(txRoot); - assertThat(txRoot.getParent(), is(nullValue())); - assertThat(txChild.getParent(), is(txRoot)); + try (Txn txRoot = env.txnWrite(); + Txn txChild = env.txn(txRoot)) { + assertThat(txRoot.getParent(), is(nullValue())); + assertThat(txChild.getParent(), is(txRoot)); + } } @Test(expected = IncompatibleParent.class) - @SuppressWarnings("PMD.CloseResource") public void txParentROChildRWIncompatible() { - final Txn txRoot = env.txnRead(); - env.txn(txRoot); // error + try (Txn txRoot = env.txnRead()) { + env.txn(txRoot); // error + } } @Test(expected = IncompatibleParent.class) - @SuppressWarnings("PMD.CloseResource") public void txParentRWChildROIncompatible() { - final Txn txRoot = env.txnWrite(); - env.txn(txRoot, MDB_RDONLY_TXN); // error + try (Txn txRoot = env.txnWrite()) { + env.txn(txRoot, MDB_RDONLY_TXN); // error + } } @Test - @SuppressWarnings("PMD.CloseResource") public void txReadOnly() { - final Txn txn = env.txnRead(); - assertThat(txn.getParent(), is(nullValue())); - assertThat(txn.getState(), is(READY)); - assertThat(txn.isReadOnly(), is(true)); - txn.checkReady(); - txn.checkReadOnly(); - txn.reset(); - assertThat(txn.getState(), is(RESET)); - txn.renew(); - assertThat(txn.getState(), is(READY)); - txn.commit(); - assertThat(txn.getState(), is(DONE)); - txn.close(); - assertThat(txn.getState(), is(RELEASED)); + try (Txn txn = env.txnRead()) { + assertThat(txn.getParent(), is(nullValue())); + assertThat(txn.getState(), is(READY)); + assertThat(txn.isReadOnly(), is(true)); + txn.checkReady(); + txn.checkReadOnly(); + txn.reset(); + assertThat(txn.getState(), is(RESET)); + txn.renew(); + assertThat(txn.getState(), is(READY)); + txn.commit(); + assertThat(txn.getState(), is(DONE)); + txn.close(); + assertThat(txn.getState(), is(RELEASED)); + } } @Test @@ -286,31 +286,30 @@ public void txReadWrite() { } @Test(expected = NotResetException.class) - @SuppressWarnings("PMD.CloseResource") public void txRenewDeniedWithoutPriorReset() { - final Txn txn = env.txnRead(); - txn.renew(); + try (Txn txn = env.txnRead()) { + txn.renew(); + } } @Test(expected = ResetException.class) - @SuppressWarnings("PMD.CloseResource") public void txResetDeniedForAlreadyResetTransaction() { - final Txn txn = env.txnRead(); - txn.reset(); - txn.renew(); - txn.reset(); - txn.reset(); + try (Txn txn = env.txnRead()) { + txn.reset(); + txn.renew(); + txn.reset(); + txn.reset(); + } } @Test(expected = ReadOnlyRequiredException.class) - @SuppressWarnings("PMD.CloseResource") public void txResetDeniedForReadWriteTransaction() { - final Txn txn = env.txnWrite(); - txn.reset(); + try (Txn txn = env.txnWrite()) { + txn.reset(); + } } @Test(expected = BadValueSizeException.class) - @SuppressWarnings("PMD.CloseResource") public void zeroByteKeysRejected() throws IOException { final Dbi dbi = env.openDbi(DB_1, MDB_CREATE); final ByteBuffer key = allocateDirect(4); From fd4b71205a32669bc92a34483cf24b7f79d3d991 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Wed, 15 Apr 2020 16:03:22 +1000 Subject: [PATCH 052/322] Include TutorialTest during CI This reverts commit fed0c5df22ade16e05570f10d624bca5c64221bf. --- .github/workflows/maven.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index a657e88d..b6899406 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -32,7 +32,7 @@ jobs: key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} - name: Build with Maven - run: mvn -B -Dtest='!TutorialTest' verify + run: mvn -B verify - name: Upload code coverage to Codecov uses: codecov/codecov-action@v1 @@ -67,10 +67,7 @@ jobs: key: ${{ runner.os }}-${{ matrix.java }}-m2-${{ hashFiles('**/pom.xml') }} - name: Test with Maven - run: mvn -B -Dtest='!TutorialTest' test - - - name: Test with Maven 2 - run: mvn -B -Dtest='TutorialTest' test + run: mvn -B test - name: Upload Surefire reports on test failure uses: actions/upload-artifact@v1 From b9d27f588ca64106bafb9711627e558a4a06558f Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Wed, 15 Apr 2020 16:33:35 +1000 Subject: [PATCH 053/322] Update to LmdbJava native library 0.9.24-1 official release (closes #148) --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 6b032ef0..21f73801 100644 --- a/pom.xml +++ b/pom.xml @@ -135,19 +135,19 @@ org.lmdbjava lmdbjava-native-linux-x86_64 - 0.9.24-1-SNAPSHOT + 0.9.24-1 true org.lmdbjava lmdbjava-native-windows-x86_64 - 0.9.24-1-SNAPSHOT + 0.9.24-1 true org.lmdbjava lmdbjava-native-osx-x86_64 - 0.9.24-1-SNAPSHOT + 0.9.24-1 true From 77a21a6f6641832b907fcdea5e4c3da190468f74 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Wed, 15 Apr 2020 16:51:21 +1000 Subject: [PATCH 054/322] Maven OSSRH deployment should perform tests as per default --- .github/workflows/maven.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index b6899406..fe457844 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -109,4 +109,3 @@ jobs: gpg_passphrase: ${{ secrets.gpg_passphrase }} nexus_username: ${{ secrets.nexus_username }} nexus_password: ${{ secrets.nexus_password }} - maven_args: "-DskipTests" From a1f73e210c9b07bb4837509b3d9feedec07f4edd Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Wed, 15 Apr 2020 17:01:40 +1000 Subject: [PATCH 055/322] Checkstyle and PMD variations following merging PR #146 --- src/main/java/org/lmdbjava/Dbi.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index 9fbcef1c..b13098ec 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -24,10 +24,8 @@ import java.util.Arrays; import java.util.Comparator; import java.util.List; - import static java.util.Objects.requireNonNull; import static jnr.ffi.Memory.allocateDirect; - import jnr.ffi.NativeType; import static jnr.ffi.NativeType.ADDRESS; import jnr.ffi.Pointer; @@ -57,6 +55,7 @@ * * @param buffer type */ +@SuppressWarnings("PMD.GodClass") public final class Dbi { private final ComparatorCallback ccb; From 46e1781831a64a1f2d47a318f8060ab2e76fa538 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Wed, 15 Apr 2020 17:18:39 +1000 Subject: [PATCH 056/322] Change to IntByReference due to Windows CI failures (#146) --- src/main/java/org/lmdbjava/Dbi.java | 8 ++++---- src/main/java/org/lmdbjava/Library.java | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index b13098ec..86db34b8 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -26,9 +26,9 @@ import java.util.List; import static java.util.Objects.requireNonNull; import static jnr.ffi.Memory.allocateDirect; -import jnr.ffi.NativeType; import static jnr.ffi.NativeType.ADDRESS; import jnr.ffi.Pointer; +import jnr.ffi.byref.IntByReference; import jnr.ffi.byref.PointerByReference; import org.lmdbjava.CursorIterator.IteratorType; import static org.lmdbjava.CursorIterator.IteratorType.FORWARD; @@ -500,15 +500,15 @@ public Stat stat(final Txn txn) { * @return the list of flags this Dbi was created with */ public List listFlags(final Txn txn) { - final Pointer resultPtr = allocateDirect(RUNTIME, NativeType.UINT); + final IntByReference resultPtr = new IntByReference(); checkRc(LIB.mdb_dbi_flags(txn.pointer(), ptr, resultPtr)); - final int flags = resultPtr.getInt(0); + final int flags = resultPtr.intValue(); final List result = new ArrayList<>(); for (final DbiFlags flag : DbiFlags.values()) { - if (MaskedFlag.isSet(flags, flag)) { + if (isSet(flags, flag)) { result.add(flag); } } diff --git a/src/main/java/org/lmdbjava/Library.java b/src/main/java/org/lmdbjava/Library.java index 5834ca44..a458152d 100644 --- a/src/main/java/org/lmdbjava/Library.java +++ b/src/main/java/org/lmdbjava/Library.java @@ -222,7 +222,8 @@ int mdb_cursor_put(@In Pointer cursor, @In Pointer key, @In Pointer data, void mdb_dbi_close(@In Pointer env, @In Pointer dbi); - int mdb_dbi_flags(@In Pointer txn, @In Pointer dbi, @Out Pointer flags); + int mdb_dbi_flags(@In Pointer txn, @In Pointer dbi, + @Out IntByReference flags); int mdb_dbi_open(@In Pointer txn, @In byte[] name, int flags, @In Pointer dbiPtr); From a781bb2ee9c5e588c8c687f404ce7761fb80fbbb Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Wed, 15 Apr 2020 17:39:44 +1000 Subject: [PATCH 057/322] Add test for PR #100 --- src/test/java/org/lmdbjava/CursorTest.java | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/test/java/org/lmdbjava/CursorTest.java b/src/test/java/org/lmdbjava/CursorTest.java index 40dd9f97..3f376bd6 100644 --- a/src/test/java/org/lmdbjava/CursorTest.java +++ b/src/test/java/org/lmdbjava/CursorTest.java @@ -48,6 +48,7 @@ import static org.lmdbjava.PutFlags.MDB_NODUPDATA; import static org.lmdbjava.PutFlags.MDB_NOOVERWRITE; import static org.lmdbjava.SeekOp.MDB_FIRST; +import static org.lmdbjava.SeekOp.MDB_GET_BOTH; import static org.lmdbjava.TestUtils.DB_1; import static org.lmdbjava.TestUtils.POSIX_MODE; import static org.lmdbjava.TestUtils.bb; @@ -172,6 +173,27 @@ public void delete() { } } + @Test + public void getKeyVal() { + final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); + try (Txn txn = env.txnWrite(); + Cursor c = db.openCursor(txn)) { + c.put(bb(1), bb(2), MDB_APPENDDUP); + c.put(bb(1), bb(4), MDB_APPENDDUP); + c.put(bb(1), bb(6), MDB_APPENDDUP); + c.put(bb(2), bb(1), MDB_APPENDDUP); + c.put(bb(2), bb(2), MDB_APPENDDUP); + c.put(bb(2), bb(3), MDB_APPENDDUP); + c.put(bb(2), bb(4), MDB_APPENDDUP); + assertThat(c.get(bb(1), bb(2), MDB_GET_BOTH), is(true)); + assertThat(c.count(), is(3L)); + assertThat(c.get(bb(1), bb(3), MDB_GET_BOTH), is(false)); + assertThat(c.get(bb(2), bb(1), MDB_GET_BOTH), is(true)); + assertThat(c.count(), is(4L)); + assertThat(c.get(bb(2), bb(0), MDB_GET_BOTH), is(false)); + } + } + @Test public void putMultiple() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT, From 11105ff40e2cf7047384af4402642ea4e7506503 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 18 Apr 2020 14:45:28 +1000 Subject: [PATCH 058/322] Update to Acegi Standard Project 0.4.0 --- .github/workflows/maven.yml | 1 + pom.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index fe457844..c8ef2d4a 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -109,3 +109,4 @@ jobs: gpg_passphrase: ${{ secrets.gpg_passphrase }} nexus_username: ${{ secrets.nexus_username }} nexus_password: ${{ secrets.nexus_password }} + maven_profiles: ossrh-deploy diff --git a/pom.xml b/pom.xml index 21f73801..8360a2aa 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ au.com.acegi acegi-standard-project - 0.3.1 + 0.4.0 org.lmdbjava lmdbjava From 344e4ae810be9492b375bf0ab9bc9f009ed38b5f Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 24 Apr 2020 10:21:13 +1000 Subject: [PATCH 059/322] Revert "Use explicit Netty JARs to mitigate dependency:go-offline failures" This reverts commit 248878a53d3c6ba6f4f750d3997fa22d62df5714. With the move to GitHub Actions we no longer use mvn dependency:go-offline. As such the issue with the missing native dependency should be avoided. --- pom.xml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 8360a2aa..0ae1f82c 100644 --- a/pom.xml +++ b/pom.xml @@ -110,13 +110,7 @@ io.netty - netty-buffer - 4.1.48.Final - test - - - io.netty - netty-common + netty-all 4.1.48.Final test @@ -170,7 +164,6 @@ com.github.jnr:jffi - io.netty:netty-common From 07cf8b0c31e66ec8bb5463257cceea6ebc9394ca Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 24 Apr 2020 10:27:11 +1000 Subject: [PATCH 060/322] Update to JNR-FFI 2.1.13 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0ae1f82c..090c7a50 100644 --- a/pom.xml +++ b/pom.xml @@ -72,7 +72,7 @@ com.github.jnr jnr-ffi - 2.1.12 + 2.1.13 com.github.jnr From 4df55d5b66cddc2da40acf4ed91bfa7a8e42b431 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 24 Apr 2020 10:27:33 +1000 Subject: [PATCH 061/322] Update to Guava 29.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 090c7a50..ea238a32 100644 --- a/pom.xml +++ b/pom.xml @@ -117,7 +117,7 @@ com.google.guava guava - 28.2-jre + 29.0-jre test From 4f70b02311f17bdea67650c9289e001f9f7284c7 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 24 Apr 2020 10:27:48 +1000 Subject: [PATCH 062/322] Update to Netty 4.1.49 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ea238a32..dc2d405c 100644 --- a/pom.xml +++ b/pom.xml @@ -111,7 +111,7 @@ io.netty netty-all - 4.1.48.Final + 4.1.49.Final test From 076229fa699c276536204c7396a70d283983113d Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 24 Apr 2020 10:44:58 +1000 Subject: [PATCH 063/322] Promote Netty support from test to main (resolves #152) --- pom.xml | 2 +- src/{test => main}/java/org/lmdbjava/ByteBufProxy.java | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/{test => main}/java/org/lmdbjava/ByteBufProxy.java (100%) diff --git a/pom.xml b/pom.xml index dc2d405c..6825e457 100644 --- a/pom.xml +++ b/pom.xml @@ -112,7 +112,7 @@ io.netty netty-all 4.1.49.Final - test + true com.google.guava diff --git a/src/test/java/org/lmdbjava/ByteBufProxy.java b/src/main/java/org/lmdbjava/ByteBufProxy.java similarity index 100% rename from src/test/java/org/lmdbjava/ByteBufProxy.java rename to src/main/java/org/lmdbjava/ByteBufProxy.java From 1fb91944c2393cab0652874492290db4297cb98b Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 24 Apr 2020 11:01:35 +1000 Subject: [PATCH 064/322] Restructure readme to separately list documentation resources --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e2ee753b..4311c86e 100644 --- a/README.md +++ b/README.md @@ -29,15 +29,10 @@ * Buffer agnostic (Java `ByteBuffer`, Agrona `DirectBuffer`, Netty `ByteBuf`, your own buffer) * 100% stock-standard, officially-released, widely-tested LMDB C code ([no extra](https://github.com/lmdbjava/native) C/JNI code) * Low latency design (allocation-free; buffer pools; optional checks can be easily disabled in production etc) -* Automatically tested with Java 8 and Java 11 -* Easy to use (just work through our step-by-step, CI-tested, fully-executable [tutorial](https://github.com/lmdbjava/lmdbjava/tree/master/src/test/java/org/lmdbjava/TutorialTest.java)) -* Community questions and contributions are welcome through [GitHub tickets](https://github.com/lmdbjava/lmdbjava/issues) * Mature code (commenced in 2016) and used for heavy production workloads (eg > 500 TB of HFT data) * Actively maintained and with a "Zero Bug Policy" before every release (see [issues](https://github.com/lmdbjava/lmdbjava/issues)) * Available from [Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.lmdbjava%22%20AND%20a%3A%22lmdbjava%22) and [OSS Sonatype Snapshots](https://oss.sonatype.org/content/repositories/snapshots/org/lmdbjava/lmdbjava) -* Detailed [JavaDocs](http://www.javadoc.io/doc/org.lmdbjava/lmdbjava) -* [Change Log](https://github.com/lmdbjava/lmdbjava/wiki/Change-Log) to assist with upgrades -* [Continuous integration](https://github.com/lmdbjava/lmdbjava/actions) testing on Linux, Windows and macOS across Java versions 8, 11 and 14 +* [Continuous integration](https://github.com/lmdbjava/lmdbjava/actions) testing on Linux, Windows and macOS with Java 8, 11 and 14 ### Performance @@ -47,6 +42,13 @@ Full details are in the [latest benchmark report](https://github.com/lmdbjava/benchmarks/blob/master/results/20160710/README.md). +### Documentation + +* [Wiki](https://github.com/lmdbjava/lmdbjava/wiki/) +* [Tutorial](https://github.com/lmdbjava/lmdbjava/tree/master/src/test/java/org/lmdbjava/TutorialTest.java) +* [JavaDocs](http://www.javadoc.io/doc/org.lmdbjava/lmdbjava) +* [Change Log](https://github.com/lmdbjava/lmdbjava/wiki/Change-Log) + ### Support We're happy to help you use LmdbJava. Simply From ac5671a03bb64f1938aa6d2ae844dba2c33f2c62 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 24 Apr 2020 15:39:03 +1000 Subject: [PATCH 065/322] CursorIterator.iterable() should only return once (fixes #153) --- src/main/java/org/lmdbjava/CursorIterator.java | 12 +++++++++++- src/test/java/org/lmdbjava/CursorIteratorTest.java | 9 +++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/lmdbjava/CursorIterator.java b/src/main/java/org/lmdbjava/CursorIterator.java index c184a55b..354f8587 100644 --- a/src/main/java/org/lmdbjava/CursorIterator.java +++ b/src/main/java/org/lmdbjava/CursorIterator.java @@ -53,6 +53,7 @@ public final class CursorIterator implements private final Comparator comparator; private final Cursor cursor; private final KeyVal entry; + private boolean iterableReturned; private final KeyRange range; private State state = REQUIRES_INITIAL_OP; @@ -79,11 +80,20 @@ public boolean hasNext() { } /** - * Obtain an iterator. + * Obtain an iterable. + * + *

+ * As iteration of the returned iterable will cause movement of the underlying + * LMDB cursor, an {@link IllegalStateException} is thrown if an attempt is + * made to obtain the iterator more than once. * * @return an iterator */ public Iterable> iterable() { + if (iterableReturned) { + throw new IllegalStateException("Iterable can only be returned once"); + } + iterableReturned = true; return () -> CursorIterator.this; } diff --git a/src/test/java/org/lmdbjava/CursorIteratorTest.java b/src/test/java/org/lmdbjava/CursorIteratorTest.java index ae20e57a..04564004 100644 --- a/src/test/java/org/lmdbjava/CursorIteratorTest.java +++ b/src/test/java/org/lmdbjava/CursorIteratorTest.java @@ -235,6 +235,15 @@ public void greaterThanTest() { verify(greaterThan(bb(3)), 4, 6, 8); } + @Test(expected = IllegalStateException.class) + public void iterableOnlyReturnedOnce() { + try (Txn txn = env.txnRead(); + CursorIterator c = db.iterate(txn)) { + c.iterable(); // ok + c.iterable(); // fails + } + } + @Test public void iterate() { try (Txn txn = env.txnRead(); From f03a6f962cd039fc018e2683ccc98af1877d0418 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 27 Apr 2020 10:31:50 +1000 Subject: [PATCH 066/322] Format documentation and add links (#141) --- src/main/java/org/lmdbjava/EnvFlags.java | 184 ++++++++++++----------- 1 file changed, 96 insertions(+), 88 deletions(-) diff --git a/src/main/java/org/lmdbjava/EnvFlags.java b/src/main/java/org/lmdbjava/EnvFlags.java index 4c3d0a05..5a58c996 100644 --- a/src/main/java/org/lmdbjava/EnvFlags.java +++ b/src/main/java/org/lmdbjava/EnvFlags.java @@ -25,137 +25,145 @@ */ public enum EnvFlags implements MaskedFlag { - /** - * http://www.lmdb.tech/doc/group__mdb.html#ga32a193c6bf4d7d5c5d579e71f22e9340 - */ - /** * Mmap at a fixed address (experimental). - * MDB_FIXEDMAP : Use a fixed address for the mmap region. This flag must be - * specified when creating the environment, and is stored persistently in the - * environment. If successful, the memory map will always reside at the same - * virtual address and pointers used to reference data items in the database will - * be constant across multiple invocations. This option may not always work, - * depending on how the operating system has allocated memory to shared - * libraries and other uses. The feature is highly experimental. + * + *

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

+ * By default, LMDB creates its environment in a directory whose pathname is * given in path, and creates its data and lock files under that directory. * With this option, path is used as-is for the database main data file. The * database lock file is the path with "-lock" appended. */ MDB_NOSUBDIR(0x4000), - /** - * MDB_RDONLY : Open the environment in read-only mode. No write operations will - * be allowed. LMDB will still modify the lock file - except on read-only - * filesystems, where LMDB does not use locks. + * Open the environment in read-only mode. + * + *

+ * No write operations will be allowed. LMDB will still modify the lock file - + * except on read-only filesystems, where LMDB does not use locks. */ MDB_RDONLY_ENV(0x2_0000), - /** - * MDB_WRITEMAP : Use a writeable memory map unless MDB_RDONLY is set. This is - * faster and uses fewer mallocs, but loses protection from application bugs - * like wild pointer writes and other bad updates into the database. - * Incompatible with nested transactions. Do not mix processes with and without - * MDB_WRITEMAP on the same environment. This can defeat durability (mdb_env_sync etc). + * Use a writeable memory map unless {@link #MDB_RDONLY} is set. + * + *

+ * This is faster and uses fewer mallocs, but loses protection from + * application bugs like wild pointer writes and other bad updates into the + * database. Incompatible with nested transactions. Do not mix processes with + * and without {@link #MDB_WRITEMAP} on the same environment. This can defeat + * durability ({@link Env#sync(boolean)} etc). */ MDB_WRITEMAP(0x8_0000), - /** * Don't fsync metapage after commit. - * MDB_NOMETASYNC : Flush system buffers to disk only once per transaction, omit the - * metadata flush. Defer that until the system flushes files to disk, or next non-MDB_RDONLY - * commit or mdb_env_sync(). This optimization maintains database integrity, but a system - * crash may undo the last committed transaction. I.e. it preserves the ACI (atomicity, - * consistency, isolation) but not D (durability) database property. This flag may be - * changed at any time using mdb_env_set_flags(). + * + *

+ * Flush system buffers to disk only once per transaction, omit the metadata + * flush. Defer that until the system flushes files to disk, or next + * non-{@link #MDB_RDONLY} commit or {@link Env#sync(boolean)}. This + * optimization* maintains database integrity, but a system crash may undo the + * last* committed transaction. I.e. it preserves the ACI (atomicity, + * consistency, isolation) but not D (durability) database property. */ MDB_NOMETASYNC(0x4_0000), - /** * Don't fsync after commit. - * MDB_NOSYNC : Don't flush system buffers to disk when committing a transaction. - * This optimization means a system crash can corrupt the database or lose the last - * transactions if buffers are not yet flushed to disk. The risk is governed by how - * often the system flushes dirty buffers to disk and how often mdb_env_sync() is called. - * However, if the filesystem preserves write order and the MDB_WRITEMAP flag is not used, - * transactions exhibit ACI (atomicity, consistency, isolation) properties and only lose D - * (durability). I.e. database integrity is maintained, but a system crash may undo the - * final transactions. Note that (MDB_NOSYNC | MDB_WRITEMAP) leaves the system with no hint - * for when to write transactions to disk, unless mdb_env_sync() is called. - * (MDB_MAPASYNC | MDB_WRITEMAP) may be preferable. This flag may be changed at any time - * using mdb_env_set_flags(). + * + *

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

+ * When using {@link #MDB_WRITEMAP}, use asynchronous flushes to disk. + * As with {@link #MDB_NOSYNC}, a system crash can then corrupt the database + * or lose the last transactions. Calling {@link Env#sync(boolean)} ensures + * on-disk database integrity until next commit. */ MDB_MAPASYNC(0x10_0000), - /** * Tie reader locktable slots to {@link Txn} objects instead of to threads. - * MDB_NOTLS : Don't use Thread-Local Storage. Tie reader locktable slots to - * MDB_txn objects instead of to threads. I.e. mdb_txn_reset() keeps the slot - * reseved for the MDB_txn object. A thread may use parallel read-only transactions. - * A read-only transaction may span threads if the user synchronizes its use. - * Applications that multiplex many user threads over individual OS threads - * need this option. Such an application must also serialize the write - * transactions in an OS thread, since LMDB's write locking is unaware - * of the user threads. + * + *

+ * Don't use Thread-Local Storage. Tie reader locktable slots to {@link Txn} + * objects instead of to threads. I.e. {@link Txn#reset()} keeps the slot + * reseved for the {@link Txn} object. A thread may use parallel read-only + * transactions. A read-only transaction may span threads if the user + * synchronizes its use. Applications that multiplex many user threads over + * individual OS threads need this option. Such an application must also + * serialize the write transactions in an OS thread, since LMDB's write + * locking is unaware of the user threads. */ MDB_NOTLS(0x20_0000), - /** * Don't do any locking, caller must manage their own locks. - * MDB_NOLOCK : Don't do any locking. If concurrent access is anticipated, - * the caller must manage all concurrency itself. For proper operation - * the caller must enforce single-writer semantics, and must ensure - * that no readers are using old transactions while a writer is active. - * The simplest approach is to use an exclusive lock so that no readers - * may be active at all when a writer begins. + * + *

+ * Don't do any locking. If concurrent access is anticipated, the caller must + * manage all concurrency itself. For proper operation the caller must enforce + * single-writer semantics, and must ensure that no readers are using old + * transactions while a writer is active. The simplest approach is to use an + * exclusive lock so that no readers may be active at all when a writer + * begins. */ MDB_NOLOCK(0x40_0000), - /** * Don't do readahead (no effect on Windows). - * MDB_NORDAHEAD : Turn off readahead. Most operating systems perform - * readahead on read requests by default. This option turns it off if - * the OS supports it. Turning it off may help random read performance - * when the DB is larger than RAM and system RAM is full. The option - * is not implemented on Windows. + * + *

+ * Turn off readahead. Most operating systems perform readahead on read + * requests by default. This option turns it off if the OS supports it. + * Turning it off may help random read performance when the DB is larger than + * RAM and system RAM is full. The option is not implemented on Windows. */ MDB_NORDAHEAD(0x80_0000), - /** * Don't initialize malloc'd memory before writing to datafile. - * MDB_NOMEMINIT : Don't initialize malloc'd memory before writing to - * unused spaces in the data file. By default, memory for pages written - * to the data file is obtained using malloc. While these pages may be - * reused in subsequent transactions, freshly malloc'd pages will be - * initialized to zeroes before use. This avoids persisting leftover - * data from other code (that used the heap and subsequently freed - * the memory) into the data file. Note that many other system - * libraries may allocate and free memory from the heap for - * arbitrary uses. E.g., stdio may use the heap for file I/O buffers. - * This initialization step has a modest performance cost so some - * applications may want to disable it using this flag. This option - * can be a problem for applications which handle sensitive data - * like passwords, and it makes memory checkers like Valgrind noisy. - * This flag is not needed with MDB_WRITEMAP, which writes directly - * to the mmap instead of using malloc for pages. The initialization - * is also skipped if MDB_RESERVE is used; the caller is expected to - * overwrite all of the memory that was reserved in that case. This - * flag may be changed at any time using mdb_env_set_flags(). + * + *

+ * Don't initialize malloc'd memory before writing to unused spaces in the + * data file. By default, memory for pages written to the data file is + * obtained using malloc. While these pages may be reused in subsequent + * transactions, freshly malloc'd pages will be initialized to zeroes before + * use. This avoids persisting leftover data from other code (that used the + * heap and subsequently freed the memory) into the data file. Note that many + * other system libraries may allocate and free memory from the heap for + * arbitrary uses. E.g., stdio may use the heap for file I/O buffers. This + * initialization step has a modest performance cost so some applications may + * want to disable it using this flag. This option can be a problem for + * applications which handle sensitive data like passwords, and it makes + * memory checkers like Valgrind noisy. This flag is not needed with + * {@link #MDB_WRITEMAP}, which writes directly to the mmap instead of using + * malloc for pages. The initialization is also skipped if + * {@link PutFlags#MDB_RESERVE} is used; the caller is expected to overwrite + * all of the memory that was reserved in that case. */ MDB_NOMEMINIT(0x100_0000); From 981f7ecd82fa2f5900456e417d12919c692c0c63 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 27 Apr 2020 10:32:56 +1000 Subject: [PATCH 067/322] Formatting only --- src/main/java/org/lmdbjava/Cursor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/lmdbjava/Cursor.java b/src/main/java/org/lmdbjava/Cursor.java index 1bbd8a49..2ae2140d 100644 --- a/src/main/java/org/lmdbjava/Cursor.java +++ b/src/main/java/org/lmdbjava/Cursor.java @@ -128,9 +128,9 @@ public boolean first() { /** * Reposition the key/value buffers based on the passed key and operation. * - * @param key to search for + * @param key to search for * @param data to search for - * @param op options for this operation + * @param op options for this operation * @return false if key not found */ public boolean get(final T key, final T data, final SeekOp op) { From b3aa6bbaa79622ae49a84c07e7a9f97e413ba530 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 27 Apr 2020 11:53:32 +1000 Subject: [PATCH 068/322] Import order --- src/main/java/org/lmdbjava/CursorIterator.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/lmdbjava/CursorIterator.java b/src/main/java/org/lmdbjava/CursorIterator.java index 354f8587..96a570b2 100644 --- a/src/main/java/org/lmdbjava/CursorIterator.java +++ b/src/main/java/org/lmdbjava/CursorIterator.java @@ -23,11 +23,11 @@ import java.util.Comparator; import java.util.Iterator; import java.util.NoSuchElementException; -import static org.lmdbjava.CursorIterator.State.RELEASED; -import static org.lmdbjava.CursorIterator.State.REQUIRES_INITIAL_OP; -import static org.lmdbjava.CursorIterator.State.REQUIRES_ITERATOR_OP; -import static org.lmdbjava.CursorIterator.State.REQUIRES_NEXT_OP; -import static org.lmdbjava.CursorIterator.State.TERMINATED; +import static org.lmdbjava.CursorIterable.State.RELEASED; +import static org.lmdbjava.CursorIterable.State.REQUIRES_INITIAL_OP; +import static org.lmdbjava.CursorIterable.State.REQUIRES_ITERATOR_OP; +import static org.lmdbjava.CursorIterable.State.REQUIRES_NEXT_OP; +import static org.lmdbjava.CursorIterable.State.TERMINATED; import static org.lmdbjava.GetOp.MDB_SET_RANGE; import org.lmdbjava.KeyRangeType.CursorOp; import org.lmdbjava.KeyRangeType.IteratorOp; From c9161e1edf4ca63d093ae1ff870a31948ef678fa Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 27 Apr 2020 11:56:21 +1000 Subject: [PATCH 069/322] Automatic formatting only --- src/main/java/org/lmdbjava/Dbi.java | 46 ++++++++++++------------- src/test/java/org/lmdbjava/DbiTest.java | 20 ++++++----- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index 86db34b8..5e62c235 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -354,6 +354,29 @@ public CursorIterator iterate(final Txn txn, final KeyRange range, return new CursorIterator<>(txn, this, range, useComp); } + /* + * Return DbiFlags for this Dbi. + * + * @param txn transaction handle (not null; not committed) + * @return the list of flags this Dbi was created with + */ + public List listFlags(final Txn txn) { + final IntByReference resultPtr = new IntByReference(); + checkRc(LIB.mdb_dbi_flags(txn.pointer(), ptr, resultPtr)); + + final int flags = resultPtr.intValue(); + + final List result = new ArrayList<>(); + + for (final DbiFlags flag : DbiFlags.values()) { + if (isSet(flags, flag)) { + result.add(flag); + } + } + + return result; + } + /** * Create a cursor handle. * @@ -493,29 +516,6 @@ public Stat stat(final Txn txn) { stat.f5_ms_entries.longValue()); } - /* - * Return DbiFlags for this Dbi. - * - * @param txn transaction handle (not null; not committed) - * @return the list of flags this Dbi was created with - */ - public List listFlags(final Txn txn) { - final IntByReference resultPtr = new IntByReference(); - checkRc(LIB.mdb_dbi_flags(txn.pointer(), ptr, resultPtr)); - - final int flags = resultPtr.intValue(); - - final List result = new ArrayList<>(); - - for (final DbiFlags flag : DbiFlags.values()) { - if (isSet(flags, flag)) { - result.add(flag); - } - } - - return result; - } - private void clean() { if (cleaned) { return; diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 1e842d0f..a5bf4bab 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -263,6 +263,17 @@ public void getNamesWhenEmpty() { assertThat(dbiNames, empty()); } + @Test + public void listsFlags() { + final Dbi dbi = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT, + MDB_REVERSEKEY); + + try (Txn txn = env.txnRead()) { + final List flags = dbi.listFlags(txn); + assertThat(flags, containsInAnyOrder(MDB_DUPSORT, MDB_REVERSEKEY)); + } + } + @Test public void putAbortGet() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); @@ -478,13 +489,4 @@ public void testParallelWritesStress() { }); } - @Test - public void listsFlags() { - final Dbi dbi = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT, MDB_REVERSEKEY); - - try (Txn txn = env.txnRead()) { - final List flags = dbi.listFlags(txn); - assertThat(flags, containsInAnyOrder(MDB_DUPSORT, MDB_REVERSEKEY)); - } - } } From d8224e7ac4d32c4d9382b01cb804ce63d768e4ee Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 27 Apr 2020 12:18:53 +1000 Subject: [PATCH 070/322] Literal formatting only --- src/main/java/org/lmdbjava/Verifier.java | 4 ++-- src/test/java/org/lmdbjava/DbiTest.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/lmdbjava/Verifier.java b/src/main/java/org/lmdbjava/Verifier.java index 38a0b531..04b7a72c 100644 --- a/src/main/java/org/lmdbjava/Verifier.java +++ b/src/main/java/org/lmdbjava/Verifier.java @@ -82,7 +82,7 @@ public final class Verifier implements Callable { */ public static final int DBI_COUNT = 5; private static final int BATCH_SIZE = 64; - private static final int BUFFER_LEN = 1024 * BATCH_SIZE; + private static final int BUFFER_LEN = 1_024 * BATCH_SIZE; private static final int CRC_LENGTH = Long.BYTES; private static final int KEY_LENGTH = Long.BYTES; private final byte[] ba = new byte[BUFFER_LEN]; @@ -269,7 +269,7 @@ private void updateValue(final long forId) { private int valueSize(final long forId) { final int mod = (int) (forId % BATCH_SIZE); - final int base = 1024 * mod; + final int base = 1_024 * mod; final int value = base == 0 ? 512 : base; return value - CRC_LENGTH - KEY_LENGTH; // aim to minimise partial pages } diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index a5bf4bab..e4c9eecd 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -147,7 +147,7 @@ public void dbiWithComparatorThreadSafety() { final Dbi db = env.openDbi(DB_1, PROXY_OPTIMAL::compare, MDB_CREATE); - final List keys = range(0, 1000).boxed().collect(toList()); + final List keys = range(0, 1_000).boxed().collect(toList()); final ExecutorService pool = Executors.newCachedThreadPool(); final AtomicBoolean proceed = new AtomicBoolean(true); From a590f9ad5517b800a68cf22052d3e74c77d69808 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 27 Apr 2020 12:32:58 +1000 Subject: [PATCH 071/322] Refactor CursorIterator (#154) Refactor CursorIterator (#154) --- ...ursorIterator.java => CursorIterable.java} | 71 ++++++++++--------- src/main/java/org/lmdbjava/Dbi.java | 16 ++--- .../java/org/lmdbjava/CursorIteratorTest.java | 63 +++++++++------- src/test/java/org/lmdbjava/DbiTest.java | 9 ++- src/test/java/org/lmdbjava/TutorialTest.java | 18 ++--- src/test/java/org/lmdbjava/TxnTest.java | 4 +- 6 files changed, 100 insertions(+), 81 deletions(-) rename src/main/java/org/lmdbjava/{CursorIterator.java => CursorIterable.java} (79%) diff --git a/src/main/java/org/lmdbjava/CursorIterator.java b/src/main/java/org/lmdbjava/CursorIterable.java similarity index 79% rename from src/main/java/org/lmdbjava/CursorIterator.java rename to src/main/java/org/lmdbjava/CursorIterable.java index 96a570b2..3020b746 100644 --- a/src/main/java/org/lmdbjava/CursorIterator.java +++ b/src/main/java/org/lmdbjava/CursorIterable.java @@ -33,8 +33,8 @@ import org.lmdbjava.KeyRangeType.IteratorOp; /** - * {@link Iterator} that iterates over a {@link Cursor} as specified by a - * {@link KeyRange}. + * {@link Iterable} that creates a single {@link Iterator} that will iterate + * over a {@link Cursor}as specified by a {@link KeyRange}. * *

* An instance will create and close its own cursor. @@ -47,8 +47,8 @@ * * @param buffer type */ -public final class CursorIterator implements - Iterator>, AutoCloseable { +public final class CursorIterable implements + Iterable>, AutoCloseable { private final Comparator comparator; private final Cursor cursor; @@ -57,7 +57,7 @@ public final class CursorIterator implements private final KeyRange range; private State state = REQUIRES_INITIAL_OP; - CursorIterator(final Txn txn, final Dbi dbi, final KeyRange range, + CursorIterable(final Txn txn, final Dbi dbi, final KeyRange range, final Comparator comparator) { this.cursor = dbi.openCursor(txn); this.range = range; @@ -70,45 +70,50 @@ public void close() { cursor.close(); } - @Override - @SuppressWarnings("checkstyle:returncount") - public boolean hasNext() { - while (state != RELEASED && state != TERMINATED) { - update(); - } - return state == RELEASED; - } - /** - * Obtain an iterable. + * Obtain an iterator. * *

- * As iteration of the returned iterable will cause movement of the underlying + * As iteration of the returned iterator will cause movement of the underlying * LMDB cursor, an {@link IllegalStateException} is thrown if an attempt is - * made to obtain the iterator more than once. + * made to obtain the iterator more than once. For advanced cursor control + * (such as being able to iterate over the same data multiple times etc) + * please instead refer to {@link Dbi#openCursor(org.lmdbjava.Txn)}. * * @return an iterator */ - public Iterable> iterable() { + @Override + @SuppressWarnings("checkstyle:AnonInnerLength") + public Iterator> iterator() { if (iterableReturned) { throw new IllegalStateException("Iterable can only be returned once"); } iterableReturned = true; - return () -> CursorIterator.this; - } - @Override - public KeyVal next() { - if (!hasNext()) { - throw new NoSuchElementException(); - } - state = REQUIRES_NEXT_OP; - return entry; - } + return new Iterator>() { + @Override + @SuppressWarnings("checkstyle:returncount") + public boolean hasNext() { + while (state != RELEASED && state != TERMINATED) { + update(); + } + return state == RELEASED; + } - @Override - public void remove() { - cursor.delete(); + @Override + public KeyVal next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + state = REQUIRES_NEXT_OP; + return entry; + } + + @Override + public void remove() { + cursor.delete(); + } + }; } @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NullAssignment"}) @@ -224,9 +229,9 @@ void setV(final T val) { } /** - * Direction in terms of key ordering for CursorIterator. + * Represents the internal {@link CursorIterable} state. * - * @deprecated use {@link KeyRange} instead + * @deprecated use {@link Dbi} iterate method with a {@link KeyRange} instead */ @Deprecated public enum IteratorType { diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index 5e62c235..205de84d 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -30,8 +30,8 @@ import jnr.ffi.Pointer; import jnr.ffi.byref.IntByReference; import jnr.ffi.byref.PointerByReference; -import org.lmdbjava.CursorIterator.IteratorType; -import static org.lmdbjava.CursorIterator.IteratorType.FORWARD; +import org.lmdbjava.CursorIterable.IteratorType; +import static org.lmdbjava.CursorIterable.IteratorType.FORWARD; import static org.lmdbjava.Dbi.KeyExistsException.MDB_KEYEXIST; import static org.lmdbjava.Dbi.KeyNotFoundException.MDB_NOTFOUND; import static org.lmdbjava.Env.SHOULD_CHECK; @@ -259,7 +259,7 @@ public byte[] getName() { * @param txn transaction handle (not null; not committed) * @return iterator */ - public CursorIterator iterate(final Txn txn) { + public CursorIterable iterate(final Txn txn) { return iterate(txn, all()); } @@ -272,7 +272,7 @@ public CursorIterator iterate(final Txn txn) { * @deprecated use iterate method with a {@link KeyRange} instead */ @Deprecated - public CursorIterator iterate(final Txn txn, final IteratorType type) { + public CursorIterable iterate(final Txn txn, final IteratorType type) { if (SHOULD_CHECK) { requireNonNull(type); } @@ -291,7 +291,7 @@ public CursorIterator iterate(final Txn txn, final IteratorType type) { * @deprecated use iterate method with a {@link KeyRange} instead */ @Deprecated - public CursorIterator iterate(final Txn txn, final T key, + public CursorIterable iterate(final Txn txn, final T key, final IteratorType type) { if (SHOULD_CHECK) { requireNonNull(type); @@ -315,7 +315,7 @@ public CursorIterator iterate(final Txn txn, final T key, * @param range range of acceptable keys (not null) * @return iterator (never null) */ - public CursorIterator iterate(final Txn txn, final KeyRange range) { + public CursorIterable iterate(final Txn txn, final KeyRange range) { return iterate(txn, range, null); } @@ -338,7 +338,7 @@ public CursorIterator iterate(final Txn txn, final KeyRange range) { * @param comparator custom comparator for keys (may be null) * @return iterator (never null) */ - public CursorIterator iterate(final Txn txn, final KeyRange range, + public CursorIterable iterate(final Txn txn, final KeyRange range, final Comparator comparator) { if (SHOULD_CHECK) { requireNonNull(txn); @@ -351,7 +351,7 @@ public CursorIterator iterate(final Txn txn, final KeyRange range, } else { useComp = comparator; } - return new CursorIterator<>(txn, this, range, useComp); + return new CursorIterable<>(txn, this, range, useComp); } /* diff --git a/src/test/java/org/lmdbjava/CursorIteratorTest.java b/src/test/java/org/lmdbjava/CursorIteratorTest.java index 04564004..f6b1411a 100644 --- a/src/test/java/org/lmdbjava/CursorIteratorTest.java +++ b/src/test/java/org/lmdbjava/CursorIteratorTest.java @@ -29,6 +29,7 @@ import static java.util.Arrays.asList; import java.util.Comparator; import java.util.Deque; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; @@ -40,9 +41,9 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import static org.lmdbjava.CursorIterator.IteratorType.BACKWARD; -import static org.lmdbjava.CursorIterator.IteratorType.FORWARD; -import org.lmdbjava.CursorIterator.KeyVal; +import static org.lmdbjava.CursorIterable.IteratorType.BACKWARD; +import static org.lmdbjava.CursorIterable.IteratorType.FORWARD; +import org.lmdbjava.CursorIterable.KeyVal; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.Env.create; import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; @@ -70,7 +71,7 @@ import static org.lmdbjava.TestUtils.bb; /** - * Test {@link CursorIterator}. + * Test {@link CursorIterable}. */ public final class CursorIteratorTest { @@ -123,11 +124,10 @@ public void atMostTest() { @Test public void backwardDeprecated() { try (Txn txn = env.txnRead(); - CursorIterator c = db.iterate(txn, BACKWARD)) { - for (final KeyVal kv : c.iterable()) { + CursorIterable c = db.iterate(txn, BACKWARD)) { + for (final KeyVal kv : c) { assertThat(kv.val().getInt(), is(list.pollLast())); assertThat(kv.key().getInt(), is(list.pollLast())); - assertThat(c.hasNext(), is(list.peekFirst() != null)); } } } @@ -138,8 +138,8 @@ public void backwardSeekDeprecated() { list.pollLast(); list.pollLast(); try (Txn txn = env.txnRead(); - CursorIterator c = db.iterate(txn, key, BACKWARD)) { - for (final KeyVal kv : c.iterable()) { + CursorIterable c = db.iterate(txn, key, BACKWARD)) { + for (final KeyVal kv : c) { assertThat(kv.val().getInt(), is(list.pollLast())); assertThat(kv.key().getInt(), is(list.pollLast())); } @@ -198,11 +198,10 @@ public void closedTest() { @Test public void forwardDeprecated() { try (Txn txn = env.txnRead(); - CursorIterator c = db.iterate(txn, FORWARD)) { - for (final KeyVal kv : c.iterable()) { + CursorIterable c = db.iterate(txn, FORWARD)) { + for (final KeyVal kv : c) { assertThat(kv.key().getInt(), is(list.pollFirst())); assertThat(kv.val().getInt(), is(list.pollFirst())); - assertThat(c.hasNext(), is(list.peekFirst() != null)); } } } @@ -214,8 +213,8 @@ public void forwardSeekDeprecated() { list.pollFirst(); try (Txn txn = env.txnRead(); - CursorIterator c = db.iterate(txn, key, FORWARD)) { - for (final KeyVal kv : c.iterable()) { + CursorIterable c = db.iterate(txn, key, FORWARD)) { + for (final KeyVal kv : c) { assertThat(kv.key().getInt(), is(list.pollFirst())); assertThat(kv.val().getInt(), is(list.pollFirst())); } @@ -238,23 +237,32 @@ public void greaterThanTest() { @Test(expected = IllegalStateException.class) public void iterableOnlyReturnedOnce() { try (Txn txn = env.txnRead(); - CursorIterator c = db.iterate(txn)) { - c.iterable(); // ok - c.iterable(); // fails + CursorIterable c = db.iterate(txn)) { + c.iterator(); // ok + c.iterator(); // fails } } @Test public void iterate() { try (Txn txn = env.txnRead(); - CursorIterator c = db.iterate(txn)) { - for (final KeyVal kv : c.iterable()) { + CursorIterable c = db.iterate(txn)) { + for (final KeyVal kv : c) { assertThat(kv.key().getInt(), is(list.pollFirst())); assertThat(kv.val().getInt(), is(list.pollFirst())); } } } + @Test(expected = IllegalStateException.class) + public void iteratorOnlyReturnedOnce() { + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + c.iterator(); // ok + c.iterator(); // fails + } + } + @Test public void lessThanBackwardTest() { verify(lessThanBackward(bb(5)), 8, 6); @@ -270,13 +278,15 @@ public void lessThanTest() { @Test(expected = NoSuchElementException.class) public void nextThrowsNoSuchElementExceptionIfNoMoreElements() { try (Txn txn = env.txnRead(); - CursorIterator c = db.iterate(txn)) { - for (final KeyVal kv : c.iterable()) { + CursorIterable c = db.iterate(txn)) { + final Iterator> i = c.iterator(); + while (i.hasNext()) { + final KeyVal kv = i.next(); assertThat(kv.key().getInt(), is(list.pollFirst())); assertThat(kv.val().getInt(), is(list.pollFirst())); } - assertThat(c.hasNext(), is(false)); - c.next(); + assertThat(i.hasNext(), is(false)); + i.next(); } } @@ -329,7 +339,8 @@ public void removeOddElements() { verify(all(), 2, 4, 6, 8); int idx = -1; try (Txn txn = env.txnWrite()) { - try (CursorIterator c = db.iterate(txn)) { + try (CursorIterable ci = db.iterate(txn)) { + final Iterator> c = ci.iterator(); while (c.hasNext()) { c.next(); idx++; @@ -353,8 +364,8 @@ private void verify(final KeyRange range, final List results = new ArrayList<>(); try (Txn txn = env.txnRead(); - CursorIterator c = db.iterate(txn, range, comparator)) { - for (final KeyVal kv : c.iterable()) { + CursorIterable c = db.iterate(txn, range, comparator)) { + for (final KeyVal kv : c) { final int key = kv.key().getInt(); final int val = kv.val().getInt(); results.add(key); diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index e4c9eecd..6f09739a 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -31,6 +31,7 @@ import java.util.ArrayList; import static java.util.Collections.nCopies; import java.util.Comparator; +import java.util.Iterator; import java.util.List; import java.util.Random; import java.util.concurrent.ExecutionException; @@ -60,6 +61,7 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; +import org.lmdbjava.CursorIterable.KeyVal; import org.lmdbjava.Dbi.DbFullException; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.DbiFlags.MDB_DUPSORT; @@ -126,7 +128,8 @@ public void customComparator() { txn.commit(); } try (Txn txn = env.txnRead(); - CursorIterator iter = db.iterate(txn, atMost(bb(4)))) { + CursorIterable ci = db.iterate(txn, atMost(bb(4)))) { + final Iterator> iter = ci.iterator(); assertThat(iter.next().key(), is(bb(8))); assertThat(iter.next().key(), is(bb(6))); assertThat(iter.next().key(), is(bb(4))); @@ -167,8 +170,8 @@ public void dbiWithComparatorThreadSafety() { } try (Txn txn = env.txnRead(); - CursorIterator iter = db.iterate(txn)) { - + CursorIterable ci = db.iterate(txn)) { + final Iterator> iter = ci.iterator(); final List result = new ArrayList<>(); while (iter.hasNext()) { result.add(iter.next().key().getInt()); diff --git a/src/test/java/org/lmdbjava/TutorialTest.java b/src/test/java/org/lmdbjava/TutorialTest.java index 7d4f37f5..5ed75b17 100644 --- a/src/test/java/org/lmdbjava/TutorialTest.java +++ b/src/test/java/org/lmdbjava/TutorialTest.java @@ -41,7 +41,7 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; -import org.lmdbjava.CursorIterator.KeyVal; +import org.lmdbjava.CursorIterable.KeyVal; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.DbiFlags.MDB_DUPSORT; import static org.lmdbjava.DirectBufferProxy.PROXY_DB; @@ -319,19 +319,19 @@ public void tutorial4() throws IOException { db.put(txn, key, val); key.clear(); - // Each iterator uses a cursor and must be closed when finished. - // Iterate forward in terms of key ordering starting with the first key. - try (CursorIterator it = db.iterate(txn, KeyRange.all())) { - for (final KeyVal kv : it.iterable()) { + // Each iterable uses a cursor and must be closed when finished. Iterate + // forward in terms of key ordering starting with the first key. + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + for (final KeyVal kv : ci) { assertThat(kv.key(), notNullValue()); assertThat(kv.val(), notNullValue()); } } // Iterate backward in terms of key ordering starting with the last key. - try (CursorIterator it = db.iterate(txn, + try (CursorIterable ci = db.iterate(txn, KeyRange.allBackward())) { - for (final KeyVal kv : it.iterable()) { + for (final KeyVal kv : ci) { assertThat(kv.key(), notNullValue()); assertThat(kv.val(), notNullValue()); } @@ -342,8 +342,8 @@ public void tutorial4() throws IOException { // terminology for our range classes (see KeyRangeType for further details). key.putInt(1); final KeyRange range = KeyRange.atLeastBackward(key); - try (CursorIterator it = db.iterate(txn, range)) { - for (final KeyVal kv : it.iterable()) { + try (CursorIterable ci = db.iterate(txn, range)) { + for (final KeyVal kv : ci) { assertThat(kv.key(), notNullValue()); assertThat(kv.val(), notNullValue()); } diff --git a/src/test/java/org/lmdbjava/TxnTest.java b/src/test/java/org/lmdbjava/TxnTest.java index 21b35808..fe25d9a2 100644 --- a/src/test/java/org/lmdbjava/TxnTest.java +++ b/src/test/java/org/lmdbjava/TxnTest.java @@ -120,8 +120,8 @@ public void rangeSearch() { end.put("z".getBytes(UTF_8)).flip(); final List keysFound = new ArrayList<>(); - try (CursorIterator ckr = db.iterate(txn, closed(start, end))) { - for (final CursorIterator.KeyVal kv : ckr.iterable()) { + try (CursorIterable ckr = db.iterate(txn, closed(start, end))) { + for (final CursorIterable.KeyVal kv : ckr) { keysFound.add(UTF_8.decode(kv.key()).toString()); } } From ccf468aba77e465548356c3aa57a22c3a7b53c5f Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 27 Apr 2020 12:29:42 +1000 Subject: [PATCH 072/322] Remove deprecated IteratorType (#154) --- .../java/org/lmdbjava/CursorIterable.java | 17 ------ src/main/java/org/lmdbjava/Dbi.java | 50 ----------------- .../java/org/lmdbjava/CursorIteratorTest.java | 53 ------------------- 3 files changed, 120 deletions(-) diff --git a/src/main/java/org/lmdbjava/CursorIterable.java b/src/main/java/org/lmdbjava/CursorIterable.java index 3020b746..4eec8cd8 100644 --- a/src/main/java/org/lmdbjava/CursorIterable.java +++ b/src/main/java/org/lmdbjava/CursorIterable.java @@ -228,23 +228,6 @@ void setV(final T val) { } - /** - * Represents the internal {@link CursorIterable} state. - * - * @deprecated use {@link Dbi} iterate method with a {@link KeyRange} instead - */ - @Deprecated - public enum IteratorType { - /** - * Move forward. - */ - FORWARD, - /** - * Move backward. - */ - BACKWARD - } - /** * Represents the internal {@link CursorIterator} state. */ diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index 205de84d..2106687d 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -30,15 +30,10 @@ import jnr.ffi.Pointer; import jnr.ffi.byref.IntByReference; import jnr.ffi.byref.PointerByReference; -import org.lmdbjava.CursorIterable.IteratorType; -import static org.lmdbjava.CursorIterable.IteratorType.FORWARD; 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.KeyRange.allBackward; -import static org.lmdbjava.KeyRange.atLeast; -import static org.lmdbjava.KeyRange.atLeastBackward; import org.lmdbjava.Library.ComparatorCallback; import static org.lmdbjava.Library.LIB; import org.lmdbjava.Library.MDB_stat; @@ -55,7 +50,6 @@ * * @param buffer type */ -@SuppressWarnings("PMD.GodClass") public final class Dbi { private final ComparatorCallback ccb; @@ -263,50 +257,6 @@ public CursorIterable iterate(final Txn txn) { return iterate(txn, all()); } - /** - * Iterate the database from the first/last item and forwards/backwards. - * - * @param txn transaction handle (not null; not committed) - * @param type direction of iterator (not null) - * @return iterator (never null) - * @deprecated use iterate method with a {@link KeyRange} instead - */ - @Deprecated - public CursorIterable iterate(final Txn txn, final IteratorType type) { - if (SHOULD_CHECK) { - requireNonNull(type); - } - final KeyRange range = type == FORWARD ? all() : allBackward(); - return iterate(txn, range); - } - - /** - * Iterate the database from the first/last item and forwards/backwards by - * first seeking to the provided key. - * - * @param txn transaction handle (not null; not committed) - * @param key the key to search from (may be null to denote first record) - * @param type direction of iterator (not null) - * @return iterator (never null) - * @deprecated use iterate method with a {@link KeyRange} instead - */ - @Deprecated - public CursorIterable iterate(final Txn txn, final T key, - final IteratorType type) { - if (SHOULD_CHECK) { - requireNonNull(type); - } - - final KeyRange range; - if (type == FORWARD) { - range = key == null ? all() : atLeast(key); - } else { - range = key == null ? allBackward() : atLeastBackward(key); - } - - return iterate(txn, range); - } - /** * Iterate the database in accordance with the provided {@link KeyRange} and * default {@link Comparator}. diff --git a/src/test/java/org/lmdbjava/CursorIteratorTest.java b/src/test/java/org/lmdbjava/CursorIteratorTest.java index f6b1411a..0db8de4b 100644 --- a/src/test/java/org/lmdbjava/CursorIteratorTest.java +++ b/src/test/java/org/lmdbjava/CursorIteratorTest.java @@ -41,8 +41,6 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import static org.lmdbjava.CursorIterable.IteratorType.BACKWARD; -import static org.lmdbjava.CursorIterable.IteratorType.FORWARD; import org.lmdbjava.CursorIterable.KeyVal; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.Env.create; @@ -121,31 +119,6 @@ public void atMostTest() { verify(atMost(bb(6)), 2, 4, 6); } - @Test - public void backwardDeprecated() { - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn, BACKWARD)) { - for (final KeyVal kv : c) { - assertThat(kv.val().getInt(), is(list.pollLast())); - assertThat(kv.key().getInt(), is(list.pollLast())); - } - } - } - - @Test - public void backwardSeekDeprecated() { - final ByteBuffer key = bb(6); - list.pollLast(); - list.pollLast(); - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn, key, BACKWARD)) { - for (final KeyVal kv : c) { - assertThat(kv.val().getInt(), is(list.pollLast())); - assertThat(kv.key().getInt(), is(list.pollLast())); - } - } - } - @Before @SuppressWarnings("PMD.CloseResource") public void before() throws IOException { @@ -195,32 +168,6 @@ public void closedTest() { verify(closed(bb(1), bb(7)), 2, 4, 6); } - @Test - public void forwardDeprecated() { - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn, FORWARD)) { - for (final KeyVal kv : c) { - assertThat(kv.key().getInt(), is(list.pollFirst())); - assertThat(kv.val().getInt(), is(list.pollFirst())); - } - } - } - - @Test - public void forwardSeekDeprecated() { - final ByteBuffer key = bb(4); - list.pollFirst(); - list.pollFirst(); - - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn, key, FORWARD)) { - for (final KeyVal kv : c) { - assertThat(kv.key().getInt(), is(list.pollFirst())); - assertThat(kv.val().getInt(), is(list.pollFirst())); - } - } - } - @Test public void greaterThanBackwardTest() { verify(greaterThanBackward(bb(6)), 4, 2); From b0dadb0f9f15abdf423c0b6cdeb525967d54f5d8 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 27 Apr 2020 12:42:43 +1000 Subject: [PATCH 073/322] Correct JavaDoc typo --- src/main/java/org/lmdbjava/CursorIterable.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/lmdbjava/CursorIterable.java b/src/main/java/org/lmdbjava/CursorIterable.java index 4eec8cd8..4834ebe0 100644 --- a/src/main/java/org/lmdbjava/CursorIterable.java +++ b/src/main/java/org/lmdbjava/CursorIterable.java @@ -34,7 +34,7 @@ /** * {@link Iterable} that creates a single {@link Iterator} that will iterate - * over a {@link Cursor}as specified by a {@link KeyRange}. + * over a {@link Cursor} as specified by a {@link KeyRange}. * *

* An instance will create and close its own cursor. From e27914a3012387d5ff8a228d68f960fdedf49e7e Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 27 Apr 2020 12:43:58 +1000 Subject: [PATCH 074/322] Rename field and improve exception message (#154) --- src/main/java/org/lmdbjava/CursorIterable.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/lmdbjava/CursorIterable.java b/src/main/java/org/lmdbjava/CursorIterable.java index 4834ebe0..72967b7e 100644 --- a/src/main/java/org/lmdbjava/CursorIterable.java +++ b/src/main/java/org/lmdbjava/CursorIterable.java @@ -53,7 +53,7 @@ public final class CursorIterable implements private final Comparator comparator; private final Cursor cursor; private final KeyVal entry; - private boolean iterableReturned; + private boolean iteratorReturned; private final KeyRange range; private State state = REQUIRES_INITIAL_OP; @@ -85,10 +85,10 @@ public void close() { @Override @SuppressWarnings("checkstyle:AnonInnerLength") public Iterator> iterator() { - if (iterableReturned) { - throw new IllegalStateException("Iterable can only be returned once"); + if (iteratorReturned) { + throw new IllegalStateException("Iterator can only be returned once"); } - iterableReturned = true; + iteratorReturned = true; return new Iterator>() { @Override From 718f162bc287c2e7cf539d3295fd939ddade80d5 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 27 Apr 2020 12:53:47 +1000 Subject: [PATCH 075/322] Fix EnvFlags JavaDocs (#141) --- src/main/java/org/lmdbjava/EnvFlags.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/lmdbjava/EnvFlags.java b/src/main/java/org/lmdbjava/EnvFlags.java index 5a58c996..b8883706 100644 --- a/src/main/java/org/lmdbjava/EnvFlags.java +++ b/src/main/java/org/lmdbjava/EnvFlags.java @@ -57,7 +57,7 @@ public enum EnvFlags implements MaskedFlag { */ MDB_RDONLY_ENV(0x2_0000), /** - * Use a writeable memory map unless {@link #MDB_RDONLY} is set. + * Use a writeable memory map unless {@link #MDB_RDONLY_ENV} is set. * *

* This is faster and uses fewer mallocs, but loses protection from @@ -73,7 +73,7 @@ public enum EnvFlags implements MaskedFlag { *

* Flush system buffers to disk only once per transaction, omit the metadata * flush. Defer that until the system flushes files to disk, or next - * non-{@link #MDB_RDONLY} commit or {@link Env#sync(boolean)}. This + * non-{@link #MDB_RDONLY_ENV} commit or {@link Env#sync(boolean)}. This * optimization* maintains database integrity, but a system crash may undo the * last* committed transaction. I.e. it preserves the ACI (atomicity, * consistency, isolation) but not D (durability) database property. From 9c9e8dcb5ec34cfb445ecc62b564eb6097e5b1fd Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 27 Apr 2020 12:54:54 +1000 Subject: [PATCH 076/322] Fix JavaDoc references to CursorIterable (#154) --- src/main/java/org/lmdbjava/CursorIterable.java | 2 +- src/main/java/org/lmdbjava/Env.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/lmdbjava/CursorIterable.java b/src/main/java/org/lmdbjava/CursorIterable.java index 72967b7e..79bb3e38 100644 --- a/src/main/java/org/lmdbjava/CursorIterable.java +++ b/src/main/java/org/lmdbjava/CursorIterable.java @@ -229,7 +229,7 @@ void setV(final T val) { } /** - * Represents the internal {@link CursorIterator} state. + * Represents the internal {@link CursorIterable} state. */ enum State { REQUIRES_INITIAL_OP, REQUIRES_NEXT_OP, REQUIRES_ITERATOR_OP, RELEASED, diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index 8b5cd1af..57381576 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -301,7 +301,7 @@ public Dbi openDbi(final byte[] name, final DbiFlags... flags) { * If a custom comparator is specified, this comparator is called from LMDB * any time it needs to compare two keys. The comparator must be used any time * any time this database is opened, otherwise database corruption may occur. - * The custom comparator will also be used whenever a {@link CursorIterator} + * The custom comparator will also be used whenever a {@link CursorIterable} * is created from the returned {@link Dbi}. If a custom comparator is not * specified, LMDB's native default lexicographical order is used. The default * comparator is typically more efficient (as there is no need for the native From 958690d3bee2eec6b2d77c9aa7dd33b2b2e82898 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 27 Apr 2020 12:56:25 +1000 Subject: [PATCH 077/322] Rename CursorIteratorTest to CursorIterableTest (#154) --- .../{CursorIteratorTest.java => CursorIterableTest.java} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/test/java/org/lmdbjava/{CursorIteratorTest.java => CursorIterableTest.java} (99%) diff --git a/src/test/java/org/lmdbjava/CursorIteratorTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java similarity index 99% rename from src/test/java/org/lmdbjava/CursorIteratorTest.java rename to src/test/java/org/lmdbjava/CursorIterableTest.java index 0db8de4b..ef6c18a6 100644 --- a/src/test/java/org/lmdbjava/CursorIteratorTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -71,7 +71,7 @@ /** * Test {@link CursorIterable}. */ -public final class CursorIteratorTest { +public final class CursorIterableTest { @Rule public final TemporaryFolder tmp = new TemporaryFolder(); From 972dfe74e2c817c71a7978104160011fbed2d800 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 27 Apr 2020 13:41:51 +1000 Subject: [PATCH 078/322] Tests to compare buffer contents, not buffer instances While ByteBuffer does override equals(Object) and compare the contents, an unexpected CI test failure log was unhelpful because it did not reveal the actual contents of the buffer: Expected: is but: was at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20) at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:6) at org.lmdbjava.DbiTest.customComparator(DbiTest.java:133) This change ensures the actual buffer contents are compared and any mismatch is reported in the logs. --- src/test/java/org/lmdbjava/DbiTest.java | 6 +++--- src/test/java/org/lmdbjava/EnvTest.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 6f09739a..1f059022 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -130,9 +130,9 @@ public void customComparator() { try (Txn txn = env.txnRead(); CursorIterable ci = db.iterate(txn, atMost(bb(4)))) { final Iterator> iter = ci.iterator(); - assertThat(iter.next().key(), is(bb(8))); - assertThat(iter.next().key(), is(bb(6))); - assertThat(iter.next().key(), is(bb(4))); + assertThat(iter.next().key().getInt(), is(8)); + assertThat(iter.next().key().getInt(), is(6)); + assertThat(iter.next().key().getInt(), is(4)); } } diff --git a/src/test/java/org/lmdbjava/EnvTest.java b/src/test/java/org/lmdbjava/EnvTest.java index 8b8e067f..2d450c24 100644 --- a/src/test/java/org/lmdbjava/EnvTest.java +++ b/src/test/java/org/lmdbjava/EnvTest.java @@ -333,7 +333,7 @@ public void setMapSize() throws IOException { env.setMapSize(500_000); try (Txn roTxn = env.txnRead()) { - assertThat(db.get(roTxn, bb(1)), is(bb(42))); + assertThat(db.get(roTxn, bb(1)).getInt(), is(42)); } mapFullExThrown = false; From e787da6aa8b8a716b10affcfa597a85aba06c3c4 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 27 Apr 2020 23:11:46 +1000 Subject: [PATCH 079/322] Add system property to specify native library extraction directory (closes #155) --- src/main/java/org/lmdbjava/Library.java | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/lmdbjava/Library.java b/src/main/java/org/lmdbjava/Library.java index a458152d..cea03bab 100644 --- a/src/main/java/org/lmdbjava/Library.java +++ b/src/main/java/org/lmdbjava/Library.java @@ -62,6 +62,15 @@ final class Library { * avoiding the library copy etc). */ public static final String DISABLE_EXTRACT_PROP = "lmdbjava.disable.extract"; + /** + * 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 Library have been set). + */ + public static final String LMDB_EXTRACT_DIR_PROP = "lmdbjava.extract.dir"; /** * Java system property name that can be set to provide a custom path to a * external LMDB system library. If set, the system property @@ -73,6 +82,11 @@ final class Library { * permitted. */ public static final boolean SHOULD_EXTRACT = !getBoolean(DISABLE_EXTRACT_PROP); + /** + * 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; /** @@ -119,7 +133,11 @@ private static String extract(final String name) { final String suffix = name.substring(name.lastIndexOf('.')); final File file; try { - file = createTempFile("lmdbjava-native-library-", suffix); + 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); From 0d70e3358dca43f5760eaac69d5dd1b6f385c8f8 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Tue, 28 Apr 2020 04:34:44 +1000 Subject: [PATCH 080/322] Add LGTM badges --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 4311c86e..0945af36 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ [![codecov](https://codecov.io/gh/lmdbjava/lmdbjava/branch/master/graph/badge.svg)](https://codecov.io/gh/lmdbjava/lmdbjava) [![Javadocs](http://www.javadoc.io/badge/org.lmdbjava/lmdbjava.svg?color=blue)](http://www.javadoc.io/doc/org.lmdbjava/lmdbjava) [![Maven Central](https://img.shields.io/maven-central/v/org.lmdbjava/lmdbjava.svg?maxAge=3600)](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.lmdbjava%22%20AND%20a%3A%22lmdbjava%22) +[![Total alerts](https://img.shields.io/lgtm/alerts/g/lmdbjava/lmdbjava.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/lmdbjava/lmdbjava/alerts/) +[![Language grade: Java](https://img.shields.io/lgtm/grade/java/g/lmdbjava/lmdbjava.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/lmdbjava/lmdbjava/context:java) # LMDB for Java From 530296e7fe09b90444ac2defda27c13d62fc61ca Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Wed, 20 May 2020 09:07:33 +1000 Subject: [PATCH 081/322] ByteArrayProxy should be final --- src/main/java/org/lmdbjava/ByteArrayProxy.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/lmdbjava/ByteArrayProxy.java b/src/main/java/org/lmdbjava/ByteArrayProxy.java index 237170b4..813259be 100644 --- a/src/main/java/org/lmdbjava/ByteArrayProxy.java +++ b/src/main/java/org/lmdbjava/ByteArrayProxy.java @@ -31,7 +31,7 @@ * * {@link Env#create(org.lmdbjava.BufferProxy)}. */ -public class ByteArrayProxy extends BufferProxy { +public final class ByteArrayProxy extends BufferProxy { /** * The byte array proxy. Guaranteed to never be null. @@ -69,28 +69,28 @@ public static int compareArrays(final byte[] o1, final byte[] o2) { } @Override - protected final byte[] allocate() { + protected byte[] allocate() { return new byte[0]; } @Override - protected final int compare(final byte[] o1, final byte[] o2) { + protected int compare(final byte[] o1, final byte[] o2) { return compareArrays(o1, o2); } @Override - protected final void deallocate(final byte[] buff) { + protected void deallocate(final byte[] buff) { // byte arrays cannot be allocated } @Override - protected final byte[] getBytes(final byte[] buffer) { + protected byte[] getBytes(final byte[] buffer) { return Arrays.copyOf(buffer, buffer.length); } @Override - protected final void in(final byte[] buffer, final Pointer ptr, - final long ptrAddr) { + protected void in(final byte[] buffer, final Pointer ptr, + final long ptrAddr) { final Pointer pointer = MEM_MGR.allocateDirect(buffer.length); pointer.put(0, buffer, 0, buffer.length); ptr.putLong(STRUCT_FIELD_OFFSET_SIZE, buffer.length); @@ -98,13 +98,13 @@ protected final void in(final byte[] buffer, final Pointer ptr, } @Override - protected final void in(final byte[] buffer, final int size, final Pointer ptr, + protected void in(final byte[] buffer, final int size, final Pointer ptr, final long ptrAddr) { // cannot reserve for byte arrays } @Override - protected final byte[] out(final byte[] buffer, final Pointer ptr, + 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); From 57b1b5b48e150d47d3184c6681e3a0e98a0e1591 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Wed, 20 May 2020 09:09:14 +1000 Subject: [PATCH 082/322] ByteArrayProxy should be used via its static final field --- src/main/java/org/lmdbjava/ByteArrayProxy.java | 3 +++ src/test/java/org/lmdbjava/DbiTest.java | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/lmdbjava/ByteArrayProxy.java b/src/main/java/org/lmdbjava/ByteArrayProxy.java index 813259be..aeb9048d 100644 --- a/src/main/java/org/lmdbjava/ByteArrayProxy.java +++ b/src/main/java/org/lmdbjava/ByteArrayProxy.java @@ -40,6 +40,9 @@ public final class ByteArrayProxy extends BufferProxy { private static final MemoryManager MEM_MGR = RUNTIME.getMemoryManager(); + private ByteArrayProxy() { + } + /** * Lexicographically compare two byte arrays. * diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 1f059022..dbbcec6e 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -60,6 +60,7 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; +import static org.lmdbjava.ByteArrayProxy.PROXY_BA; import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; import org.lmdbjava.CursorIterable.KeyVal; import org.lmdbjava.Dbi.DbFullException; @@ -327,7 +328,7 @@ public void putCommitGet() { @Test public void putCommitGetByteArray() throws IOException { final File path = tmp.newFile(); - try (Env envBa = create(new ByteArrayProxy()) + try (Env envBa = create(PROXY_BA) .setMapSize(MEBIBYTES.toBytes(64)) .setMaxReaders(1) .setMaxDbs(2) From b60e7d009da57bc3d311e65f0f8c01769b2cf88c Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Wed, 20 May 2020 09:09:31 +1000 Subject: [PATCH 083/322] DirectBufferProxy constructor should be private --- src/main/java/org/lmdbjava/DirectBufferProxy.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/org/lmdbjava/DirectBufferProxy.java b/src/main/java/org/lmdbjava/DirectBufferProxy.java index 2ba68781..d7aeaa57 100644 --- a/src/main/java/org/lmdbjava/DirectBufferProxy.java +++ b/src/main/java/org/lmdbjava/DirectBufferProxy.java @@ -56,6 +56,9 @@ public final class DirectBufferProxy extends BufferProxy { private static final ThreadLocal> BUFFERS = withInitial(() -> new OneToOneConcurrentArrayQueue<>(16)); + private DirectBufferProxy() { + } + /** * Lexicographically compare two buffers. * From c3920de8e8e206cf100419b68e895da3e843e4a2 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Wed, 20 May 2020 09:09:51 +1000 Subject: [PATCH 084/322] Netty ByteBufProxy should be accessed via a static final field (#152) --- src/main/java/org/lmdbjava/ByteBufProxy.java | 10 ++++++++++ src/test/java/org/lmdbjava/ComparatorTest.java | 2 +- src/test/java/org/lmdbjava/CursorParamTest.java | 3 ++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/lmdbjava/ByteBufProxy.java b/src/main/java/org/lmdbjava/ByteBufProxy.java index 9e220f16..042416d8 100644 --- a/src/main/java/org/lmdbjava/ByteBufProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufProxy.java @@ -38,6 +38,13 @@ */ public final class ByteBufProxy extends BufferProxy { + /** + * A proxy for using Netty {@link ByteBuf}. Guaranteed to never be null, + * although a class initialization exception will occur if an attempt is made + * to access this field when Netty is unavailable. + */ + public static final BufferProxy PROXY_NETTY = new ByteBufProxy(); + private static final long ADDRESS_OFFSET; /** @@ -66,6 +73,9 @@ public final class ByteBufProxy extends BufferProxy { } } + private ByteBufProxy() { + } + static Field findField(final String c, final String name) { Class clazz; try { diff --git a/src/test/java/org/lmdbjava/ComparatorTest.java b/src/test/java/org/lmdbjava/ComparatorTest.java index a2ea539b..1f5b8f9a 100644 --- a/src/test/java/org/lmdbjava/ComparatorTest.java +++ b/src/test/java/org/lmdbjava/ComparatorTest.java @@ -208,7 +208,7 @@ public int compare(final byte[] o1, final byte[] o2) { final ByteBuf o2b = DEFAULT.directBuffer(o2.length); o1b.writeBytes(o1); o2b.writeBytes(o2); - return new ByteBufProxy().compare(o1b, o2b); + return ByteBufProxy.PROXY_NETTY.compare(o1b, o2b); } } diff --git a/src/test/java/org/lmdbjava/CursorParamTest.java b/src/test/java/org/lmdbjava/CursorParamTest.java index 0e2fa9ee..cb943857 100644 --- a/src/test/java/org/lmdbjava/CursorParamTest.java +++ b/src/test/java/org/lmdbjava/CursorParamTest.java @@ -40,6 +40,7 @@ import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; 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; @@ -287,7 +288,7 @@ public void set(final DirectBuffer buff, final int val) { private static class NettyBufferRunner extends AbstractBufferRunner { NettyBufferRunner() { - super(new ByteBufProxy()); + super(PROXY_NETTY); } @Override From dd10ae3424820afecb006c127a02ff56ec95cecd Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Wed, 20 May 2020 09:12:12 +1000 Subject: [PATCH 085/322] Automatic formatting only --- src/main/java/org/lmdbjava/ByteArrayProxy.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/lmdbjava/ByteArrayProxy.java b/src/main/java/org/lmdbjava/ByteArrayProxy.java index aeb9048d..d6c13deb 100644 --- a/src/main/java/org/lmdbjava/ByteArrayProxy.java +++ b/src/main/java/org/lmdbjava/ByteArrayProxy.java @@ -102,13 +102,13 @@ protected void in(final byte[] buffer, final Pointer ptr, @Override protected void in(final byte[] buffer, final int size, final Pointer ptr, - final long ptrAddr) { + final long ptrAddr) { // cannot reserve for byte arrays } @Override protected byte[] out(final byte[] buffer, final Pointer ptr, - final long ptrAddr) { + 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); From 6e64befa0aed84500dc1b08f3009d0a0475fc329 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 22 May 2020 17:02:23 +1000 Subject: [PATCH 086/322] Env.copy(..) to support destinations that are files (fixes #157) --- src/main/java/org/lmdbjava/Env.java | 50 ++++++++++++++++++------- src/test/java/org/lmdbjava/EnvTest.java | 35 +++++++++++++++-- 2 files changed, 68 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index 57381576..765c1565 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -32,6 +32,7 @@ import jnr.ffi.Pointer; import jnr.ffi.byref.PointerByReference; 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 org.lmdbjava.Library.MDB_envinfo; @@ -65,14 +66,16 @@ public final class Env implements AutoCloseable { private boolean closed; private final int maxKeySize; + private final boolean noSubDir; private final BufferProxy proxy; private final Pointer ptr; private final boolean readOnly; private Env(final BufferProxy proxy, final Pointer ptr, - final boolean readOnly) { + final boolean readOnly, final boolean noSubDir) { 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); @@ -137,25 +140,22 @@ public void close() { * lockfile is created, since it gets recreated at need. * *

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

* Note: This call can trigger significant file size growth if run in parallel * with write transactions, because it employs a read-only transaction. See * long-lived transactions under "Caveats" in the LMDB native documentation. * - * @param path destination directory, which must exist, be writable and empty + * @param path writable destination path as described above * @param flags special options for this copy */ public void copy(final File path, final CopyFlags... flags) { requireNonNull(path); - if (!path.exists()) { - throw new InvalidCopyDestination("Path must exist"); - } - if (!path.isDirectory()) { - throw new InvalidCopyDestination("Path must be a directory"); - } - final String[] files = path.list(); - if (files != null && files.length > 0) { - throw new InvalidCopyDestination("Path must contain no files"); - } + validatePath(path); final int flagsMask = mask(flags); checkRc(LIB.mdb_env_copy2(ptr, path.getAbsolutePath(), flagsMask)); } @@ -392,6 +392,29 @@ Pointer pointer() { return ptr; } + private void validateDirectoryEmpty(final File path) { + if (!path.exists()) { + throw new InvalidCopyDestination("Path does not exist"); + } + if (!path.isDirectory()) { + throw new InvalidCopyDestination("Path must be a directory"); + } + final String[] files = path.list(); + if (files != null && files.length > 0) { + throw new InvalidCopyDestination("Path must contain no files"); + } + } + + private void validatePath(final File path) { + if (noSubDir) { + if (path.exists()) { + throw new InvalidCopyDestination("Path must not exist for MDB_NOSUBDIR"); + } + return; + } + validateDirectoryEmpty(path); + } + /** * Object has already been closed and the operation is therefore prohibited. */ @@ -466,8 +489,9 @@ public Env open(final File path, final int mode, checkRc(LIB.mdb_env_set_maxreaders(ptr, maxReaders)); final int flagsMask = mask(flags); final boolean readOnly = isSet(flagsMask, MDB_RDONLY_ENV); + final boolean noSubDir = isSet(flagsMask, MDB_NOSUBDIR); checkRc(LIB.mdb_env_open(ptr, path.getAbsolutePath(), flagsMask, mode)); - return new Env<>(proxy, ptr, readOnly); + return new Env<>(proxy, ptr, readOnly, noSubDir); } catch (final LmdbNativeException e) { LIB.mdb_env_close(ptr); throw e; diff --git a/src/test/java/org/lmdbjava/EnvTest.java b/src/test/java/org/lmdbjava/EnvTest.java index 2d450c24..45fc1714 100644 --- a/src/test/java/org/lmdbjava/EnvTest.java +++ b/src/test/java/org/lmdbjava/EnvTest.java @@ -30,6 +30,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThan; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -152,7 +153,7 @@ public void cannotSyncOnceClosed() throws IOException { } @Test - public void copy() throws IOException { + public void copyDirectoryBased() throws IOException { final File dest = tmp.newFolder(); assertThat(dest.exists(), is(true)); assertThat(dest.isDirectory(), is(true)); @@ -167,7 +168,7 @@ public void copy() throws IOException { } @Test(expected = InvalidCopyDestination.class) - public void copyRejectsFileDestination() throws IOException { + public void copyDirectoryRejectsFileDestination() throws IOException { final File dest = tmp.newFile(); final File src = tmp.newFolder(); try (Env env = create() @@ -178,7 +179,7 @@ public void copyRejectsFileDestination() throws IOException { } @Test(expected = InvalidCopyDestination.class) - public void copyRejectsMissingDestination() throws IOException { + public void copyDirectoryRejectsMissingDestination() throws IOException { final File dest = tmp.newFolder(); assertThat(dest.delete(), is(true)); final File src = tmp.newFolder(); @@ -190,7 +191,7 @@ public void copyRejectsMissingDestination() throws IOException { } @Test(expected = InvalidCopyDestination.class) - public void copyRejectsNonEmptyDestination() throws IOException { + public void copyDirectoryRejectsNonEmptyDestination() throws IOException { final File dest = tmp.newFolder(); final File subDir = new File(dest, "hello"); assertThat(subDir.mkdir(), is(true)); @@ -202,6 +203,32 @@ public void copyRejectsNonEmptyDestination() throws IOException { } } + @Test + public void copyFileBased() throws IOException { + final File dest = tmp.newFile(); + assertThat(dest.delete(), is(true)); + assertThat(dest.exists(), is(false)); + final File src = tmp.newFile(); + try (Env env = create() + .setMaxReaders(1) + .open(src, MDB_NOSUBDIR)) { + env.copy(dest, MDB_CP_COMPACT); + } + assertThat(dest.length(), greaterThan(0L)); + } + + @Test(expected = InvalidCopyDestination.class) + public void copyFileRejectsExistingDestination() throws IOException { + final File dest = tmp.newFile(); + assertThat(dest.exists(), is(true)); + final File src = tmp.newFile(); + try (Env env = create() + .setMaxReaders(1) + .open(src, MDB_NOSUBDIR)) { + env.copy(dest, MDB_CP_COMPACT); + } + } + @Test @SuppressWarnings("PMD.CloseResource") public void createAsDirectory() throws IOException { From b8fb7992da917d6fd6f942e03d32615cdd9371fe Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 29 May 2020 11:11:46 +1000 Subject: [PATCH 087/322] Update to Agrona 1.5.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6825e457..7efd286d 100644 --- a/pom.xml +++ b/pom.xml @@ -105,7 +105,7 @@ org.agrona agrona - 1.4.1 + 1.5.1 true From aca3c1f42168e7f8369a1e8de0175d61ad4d33c5 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 29 May 2020 11:11:54 +1000 Subject: [PATCH 088/322] Update to Netty 4.1.50 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7efd286d..a8d66fc5 100644 --- a/pom.xml +++ b/pom.xml @@ -111,7 +111,7 @@ io.netty netty-all - 4.1.49.Final + 4.1.50.Final true From 96c2ead9465f2e03cbfc03bc6aace69e86ee5bb8 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 29 May 2020 11:12:11 +1000 Subject: [PATCH 089/322] Update to JNR-FFI 2.1.15 and simplify JNR dependencies --- pom.xml | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/pom.xml b/pom.xml index a8d66fc5..59de6459 100644 --- a/pom.xml +++ b/pom.xml @@ -72,24 +72,7 @@ com.github.jnr jnr-ffi - 2.1.13 - - - com.github.jnr - jffi - - - - - com.github.jnr - jffi - 1.2.23 - - - com.github.jnr - jffi - native - 1.2.23 + 2.1.15 com.github.jnr From 627388f6510b71aa2bb574fcd09bbd8919f85b9b Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 1 Jun 2020 15:12:59 +1000 Subject: [PATCH 090/322] Update to Acegi Standard Project 0.4.1-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 59de6459..c8ddfd61 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ au.com.acegi acegi-standard-project - 0.4.0 + 0.4.1-SNAPSHOT org.lmdbjava lmdbjava From ab26d239b3b296fa87abdc8e9e1daaf560dbe072 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 1 Jun 2020 12:28:01 +1000 Subject: [PATCH 091/322] Remove unnecessary Checkstyle suppressions --- src/main/java/org/lmdbjava/KeyRange.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/org/lmdbjava/KeyRange.java b/src/main/java/org/lmdbjava/KeyRange.java index e8cd57ab..c3e35864 100644 --- a/src/main/java/org/lmdbjava/KeyRange.java +++ b/src/main/java/org/lmdbjava/KeyRange.java @@ -70,7 +70,6 @@ public KeyRange(final KeyRangeType type, final T start, final T stop) { * @param buffer type * @return a key range (never null) */ - @SuppressWarnings({"checkstyle:SuppressWarnings", "unchecked"}) public static KeyRange all() { return (KeyRange) FW; } @@ -81,7 +80,6 @@ public static KeyRange all() { * @param buffer type * @return a key range (never null) */ - @SuppressWarnings({"checkstyle:SuppressWarnings", "unchecked"}) public static KeyRange allBackward() { return (KeyRange) BK; } From c1aa63ddbe68511e3e4a5b14d866eb033d0cb025 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 1 Jun 2020 12:27:48 +1000 Subject: [PATCH 092/322] Fix capitalisation of Checkstyle suppressions Fix Checkstyle suppression capitalisation --- src/main/java/org/lmdbjava/BufferProxy.java | 2 +- src/main/java/org/lmdbjava/CursorIterable.java | 2 +- src/main/java/org/lmdbjava/Library.java | 10 +++++----- src/test/java/org/lmdbjava/CursorParamTest.java | 2 +- src/test/java/org/lmdbjava/TutorialTest.java | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/lmdbjava/BufferProxy.java b/src/main/java/org/lmdbjava/BufferProxy.java index 6ad9ef34..4f3536ab 100644 --- a/src/main/java/org/lmdbjava/BufferProxy.java +++ b/src/main/java/org/lmdbjava/BufferProxy.java @@ -33,7 +33,7 @@ * * @param buffer type */ -@SuppressWarnings("checkstyle:abstractclassname") +@SuppressWarnings("checkstyle:AbstractClassName") public abstract class BufferProxy { /** diff --git a/src/main/java/org/lmdbjava/CursorIterable.java b/src/main/java/org/lmdbjava/CursorIterable.java index 79bb3e38..e02da2b6 100644 --- a/src/main/java/org/lmdbjava/CursorIterable.java +++ b/src/main/java/org/lmdbjava/CursorIterable.java @@ -92,7 +92,7 @@ public Iterator> iterator() { return new Iterator>() { @Override - @SuppressWarnings("checkstyle:returncount") + @SuppressWarnings("checkstyle:ReturnCount") public boolean hasNext() { while (state != RELEASED && state != TERMINATED) { update(); diff --git a/src/main/java/org/lmdbjava/Library.java b/src/main/java/org/lmdbjava/Library.java index cea03bab..bffcb0b7 100644 --- a/src/main/java/org/lmdbjava/Library.java +++ b/src/main/java/org/lmdbjava/Library.java @@ -158,8 +158,8 @@ private static String extract(final String name) { /** * Structure to wrap a native MDB_envinfo. Not for external use. */ - @SuppressWarnings({"checkstyle:typename", "checkstyle:visibilitymodifier", - "checkstyle:membername"}) + @SuppressWarnings({"checkstyle:TypeName", "checkstyle:VisibilityModifier", + "checkstyle:MemberName"}) public static final class MDB_envinfo extends Struct { public final Pointer f0_me_mapaddr; @@ -183,8 +183,8 @@ public static final class MDB_envinfo extends Struct { /** * Structure to wrap a native MDB_stat. Not for external use. */ - @SuppressWarnings({"checkstyle:typename", "checkstyle:visibilitymodifier", - "checkstyle:membername"}) + @SuppressWarnings({"checkstyle:TypeName", "checkstyle:VisibilityModifier", + "checkstyle:MemberName"}) public static final class MDB_stat extends Struct { public final u_int32_t f0_ms_psize; @@ -218,7 +218,7 @@ public interface ComparatorCallback { /** * JNR API for MDB-defined C functions. Not for external use. */ - @SuppressWarnings("checkstyle:methodname") + @SuppressWarnings("checkstyle:MethodName") public interface Lmdb { void mdb_cursor_close(@In Pointer cursor); diff --git a/src/test/java/org/lmdbjava/CursorParamTest.java b/src/test/java/org/lmdbjava/CursorParamTest.java index cb943857..a0a3260e 100644 --- a/src/test/java/org/lmdbjava/CursorParamTest.java +++ b/src/test/java/org/lmdbjava/CursorParamTest.java @@ -105,7 +105,7 @@ protected AbstractBufferRunner(final BufferProxy proxy) { this.proxy = proxy; } - @SuppressWarnings("checkstyle:executablestatementcount") + @SuppressWarnings("checkstyle:ExecutableStatementCount") @Override public final void execute(final TemporaryFolder tmp) { try (Env env = env(tmp)) { diff --git a/src/test/java/org/lmdbjava/TutorialTest.java b/src/test/java/org/lmdbjava/TutorialTest.java index 5ed75b17..b0a083d8 100644 --- a/src/test/java/org/lmdbjava/TutorialTest.java +++ b/src/test/java/org/lmdbjava/TutorialTest.java @@ -149,7 +149,7 @@ public void tutorial1() throws IOException { */ @Test @SuppressWarnings({"ConvertToTryWithResources", "PMD.DoNotUseThreads", - "checkstyle:executablestatementcount"}) + "checkstyle:ExecutableStatementCount"}) public void tutorial2() throws IOException, InterruptedException { final Env env = createSimpleEnv(tmp.newFolder()); final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); @@ -224,7 +224,7 @@ public void tutorial2() throws IOException, InterruptedException { */ @Test @SuppressWarnings({"ConvertToTryWithResources", - "checkstyle:executablestatementcount"}) + "checkstyle:ExecutableStatementCount"}) public void tutorial3() throws IOException { final Env env = createSimpleEnv(tmp.newFolder()); final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); From e9d77207f58d993d9063e0389ccaa83d9247b8e5 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 1 Jun 2020 14:12:10 +1000 Subject: [PATCH 093/322] Remove unnecessary static analysis warning suppressions --- src/main/java/org/lmdbjava/ByteArrayProxy.java | 1 - src/main/java/org/lmdbjava/ByteBufferProxy.java | 3 +-- src/main/java/org/lmdbjava/CursorIterable.java | 1 - src/main/java/org/lmdbjava/DirectBufferProxy.java | 1 - src/main/java/org/lmdbjava/KeyRangeType.java | 2 -- src/main/java/org/lmdbjava/Library.java | 1 - src/main/java/org/lmdbjava/Verifier.java | 1 - src/test/java/org/lmdbjava/CursorIterableTest.java | 1 - src/test/java/org/lmdbjava/CursorParamTest.java | 1 - src/test/java/org/lmdbjava/CursorTest.java | 3 --- src/test/java/org/lmdbjava/DbiTest.java | 1 - src/test/java/org/lmdbjava/EnvTest.java | 4 ---- src/test/java/org/lmdbjava/KeyRangeTest.java | 1 - src/test/java/org/lmdbjava/TutorialTest.java | 7 ++----- src/test/java/org/lmdbjava/TxnTest.java | 1 - 15 files changed, 3 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/lmdbjava/ByteArrayProxy.java b/src/main/java/org/lmdbjava/ByteArrayProxy.java index d6c13deb..51fee48c 100644 --- a/src/main/java/org/lmdbjava/ByteArrayProxy.java +++ b/src/main/java/org/lmdbjava/ByteArrayProxy.java @@ -50,7 +50,6 @@ private ByteArrayProxy() { * @param o2 right operand (required) * @return as specified by {@link Comparable} interface */ - @SuppressWarnings("checkstyle:ReturnCount") public static int compareArrays(final byte[] o1, final byte[] o2) { requireNonNull(o1); requireNonNull(o2); diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index de6db50b..9d143808 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -49,7 +49,6 @@ * {@link #PROXY_OPTIMAL} or {@link #PROXY_SAFE} field when invoking * {@link Env#create(org.lmdbjava.BufferProxy)}. */ -@SuppressWarnings("PMD.AvoidCatchingGenericException") public final class ByteBufferProxy { /** @@ -121,7 +120,7 @@ abstract static class AbstractByteBufferProxy extends BufferProxy { * @param o2 right operand (required) * @return as specified by {@link Comparable} interface */ - @SuppressWarnings({"checkstyle:ReturnCount", "PMD.CyclomaticComplexity"}) + @SuppressWarnings("PMD.CyclomaticComplexity") public static int compareBuff(final ByteBuffer o1, final ByteBuffer o2) { requireNonNull(o1); requireNonNull(o2); diff --git a/src/main/java/org/lmdbjava/CursorIterable.java b/src/main/java/org/lmdbjava/CursorIterable.java index e02da2b6..1676a3db 100644 --- a/src/main/java/org/lmdbjava/CursorIterable.java +++ b/src/main/java/org/lmdbjava/CursorIterable.java @@ -92,7 +92,6 @@ public Iterator> iterator() { return new Iterator>() { @Override - @SuppressWarnings("checkstyle:ReturnCount") public boolean hasNext() { while (state != RELEASED && state != TERMINATED) { update(); diff --git a/src/main/java/org/lmdbjava/DirectBufferProxy.java b/src/main/java/org/lmdbjava/DirectBufferProxy.java index d7aeaa57..adcbfb7f 100644 --- a/src/main/java/org/lmdbjava/DirectBufferProxy.java +++ b/src/main/java/org/lmdbjava/DirectBufferProxy.java @@ -66,7 +66,6 @@ private DirectBufferProxy() { * @param o2 right operand (required) * @return as specified by {@link Comparable} interface */ - @SuppressWarnings("checkstyle:ReturnCount") public static int compareBuff(final DirectBuffer o1, final DirectBuffer o2) { requireNonNull(o1); requireNonNull(o2); diff --git a/src/main/java/org/lmdbjava/KeyRangeType.java b/src/main/java/org/lmdbjava/KeyRangeType.java index b286f72c..767bdeeb 100644 --- a/src/main/java/org/lmdbjava/KeyRangeType.java +++ b/src/main/java/org/lmdbjava/KeyRangeType.java @@ -320,7 +320,6 @@ public boolean isStopKeyRequired() { * * @return appropriate action in response to this buffer */ - @SuppressWarnings("checkstyle:ReturnCount") CursorOp initialOp() { switch (this) { case FORWARD_ALL: @@ -375,7 +374,6 @@ CursorOp initialOp() { * @param c comparator (required) * @return response to this key */ - @SuppressWarnings("checkstyle:ReturnCount") > IteratorOp iteratorOp(final T start, final T stop, final T buffer, final C c) { requireNonNull(c, "Comparator required"); diff --git a/src/main/java/org/lmdbjava/Library.java b/src/main/java/org/lmdbjava/Library.java index bffcb0b7..dfecb2c4 100644 --- a/src/main/java/org/lmdbjava/Library.java +++ b/src/main/java/org/lmdbjava/Library.java @@ -127,7 +127,6 @@ final class Library { private Library() { } - @SuppressWarnings("PMD.AssignmentInOperand") @SuppressFBWarnings("OBL_UNSATISFIED_OBLIGATION") // Spotbugs issue #432 private static String extract(final String name) { final String suffix = name.substring(name.lastIndexOf('.')); diff --git a/src/main/java/org/lmdbjava/Verifier.java b/src/main/java/org/lmdbjava/Verifier.java index 04b7a72c..b8dbc87b 100644 --- a/src/main/java/org/lmdbjava/Verifier.java +++ b/src/main/java/org/lmdbjava/Verifier.java @@ -163,7 +163,6 @@ public Long call() { * @param unit units used to express the duration * @return number of database rows successfully verified */ - @SuppressWarnings("PMD.DoNotUseThreads") public long runFor(final long duration, final TimeUnit unit) { final long deadline = System.currentTimeMillis() + unit.toMillis(duration); final ExecutorService es = Executors.newSingleThreadExecutor(); diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index ef6c18a6..f3e04d87 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -120,7 +120,6 @@ public void atMostTest() { } @Before - @SuppressWarnings("PMD.CloseResource") public void before() throws IOException { final File path = tmp.newFile(); env = create() diff --git a/src/test/java/org/lmdbjava/CursorParamTest.java b/src/test/java/org/lmdbjava/CursorParamTest.java index a0a3260e..39475285 100644 --- a/src/test/java/org/lmdbjava/CursorParamTest.java +++ b/src/test/java/org/lmdbjava/CursorParamTest.java @@ -105,7 +105,6 @@ protected AbstractBufferRunner(final BufferProxy proxy) { this.proxy = proxy; } - @SuppressWarnings("checkstyle:ExecutableStatementCount") @Override public final void execute(final TemporaryFolder tmp) { try (Env env = env(tmp)) { diff --git a/src/test/java/org/lmdbjava/CursorTest.java b/src/test/java/org/lmdbjava/CursorTest.java index 3f376bd6..55af22ec 100644 --- a/src/test/java/org/lmdbjava/CursorTest.java +++ b/src/test/java/org/lmdbjava/CursorTest.java @@ -85,7 +85,6 @@ public void before() throws IOException { } @Test(expected = ClosedException.class) - @SuppressWarnings("PMD.CloseResource") public void closedCursorRejectsSubsequentGets() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite()) { @@ -225,7 +224,6 @@ public void putMultipleWithoutMdbMultipleFlag() { } @Test - @SuppressWarnings("PMD.CloseResource") public void renewTxRo() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); @@ -256,7 +254,6 @@ public void renewTxRw() { } @Test - @SuppressWarnings("PMD.CloseResource") public void repeatedCloseCausesNotError() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); try (Txn txn = env.txnWrite()) { diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index dbbcec6e..3db1eabf 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -146,7 +146,6 @@ public void dbOpenMaxDatabases() { } @Test - @SuppressWarnings("PMD.DoNotUseThreads") public void dbiWithComparatorThreadSafety() { final Dbi db = env.openDbi(DB_1, PROXY_OPTIMAL::compare, MDB_CREATE); diff --git a/src/test/java/org/lmdbjava/EnvTest.java b/src/test/java/org/lmdbjava/EnvTest.java index 45fc1714..60e1dc27 100644 --- a/src/test/java/org/lmdbjava/EnvTest.java +++ b/src/test/java/org/lmdbjava/EnvTest.java @@ -101,7 +101,6 @@ public void cannotChangeMaxReadersAfterOpen() throws IOException { } @Test(expected = AlreadyClosedException.class) - @SuppressWarnings("PMD.CloseResource") public void cannotInfoOnceClosed() throws IOException { final File path = tmp.newFile(); final Env env = create() @@ -131,7 +130,6 @@ public void cannotOverflowMapSize() { } @Test(expected = AlreadyClosedException.class) - @SuppressWarnings("PMD.CloseResource") public void cannotStatOnceClosed() throws IOException { final File path = tmp.newFile(); final Env env = create() @@ -142,7 +140,6 @@ public void cannotStatOnceClosed() throws IOException { } @Test(expected = AlreadyClosedException.class) - @SuppressWarnings("PMD.CloseResource") public void cannotSyncOnceClosed() throws IOException { final File path = tmp.newFile(); final Env env = create() @@ -230,7 +227,6 @@ public void copyFileRejectsExistingDestination() throws IOException { } @Test - @SuppressWarnings("PMD.CloseResource") public void createAsDirectory() throws IOException { final File path = tmp.newFolder(); final Env env = create() diff --git a/src/test/java/org/lmdbjava/KeyRangeTest.java b/src/test/java/org/lmdbjava/KeyRangeTest.java index 68fed292..d6556272 100644 --- a/src/test/java/org/lmdbjava/KeyRangeTest.java +++ b/src/test/java/org/lmdbjava/KeyRangeTest.java @@ -236,7 +236,6 @@ private static class FakeCursor { private static final int[] KEYS = new int[]{2, 4, 6, 8}; private int position; - @SuppressWarnings("checkstyle:ReturnCount") Integer apply(final CursorOp op, final Integer startKey) { final Integer key; switch (op) { diff --git a/src/test/java/org/lmdbjava/TutorialTest.java b/src/test/java/org/lmdbjava/TutorialTest.java index b0a083d8..74e99c6d 100644 --- a/src/test/java/org/lmdbjava/TutorialTest.java +++ b/src/test/java/org/lmdbjava/TutorialTest.java @@ -64,7 +64,6 @@ * need to install the LMDB system library yourself. 32-bit platforms are not * supported. */ -@SuppressWarnings("PMD.CloseResource") public final class TutorialTest { private static final String DB_NAME = "my DB"; @@ -148,8 +147,7 @@ public void tutorial1() throws IOException { * @throws InterruptedException if executor shutdown interrupted */ @Test - @SuppressWarnings({"ConvertToTryWithResources", "PMD.DoNotUseThreads", - "checkstyle:ExecutableStatementCount"}) + @SuppressWarnings("ConvertToTryWithResources") public void tutorial2() throws IOException, InterruptedException { final Env env = createSimpleEnv(tmp.newFolder()); final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); @@ -223,8 +221,7 @@ public void tutorial2() throws IOException, InterruptedException { * @throws IOException if a path was unavailable for memory mapping */ @Test - @SuppressWarnings({"ConvertToTryWithResources", - "checkstyle:ExecutableStatementCount"}) + @SuppressWarnings("ConvertToTryWithResources") public void tutorial3() throws IOException { final Env env = createSimpleEnv(tmp.newFolder()); final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); diff --git a/src/test/java/org/lmdbjava/TxnTest.java b/src/test/java/org/lmdbjava/TxnTest.java index fe25d9a2..29dae897 100644 --- a/src/test/java/org/lmdbjava/TxnTest.java +++ b/src/test/java/org/lmdbjava/TxnTest.java @@ -271,7 +271,6 @@ public void txReadOnly() { } @Test - @SuppressWarnings("PMD.CloseResource") public void txReadWrite() { final Txn txn = env.txnWrite(); assertThat(txn.getParent(), is(nullValue())); From 25fe08af39e909dcbbbea1be651eb023bc559f90 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 1 Jun 2020 15:13:20 +1000 Subject: [PATCH 094/322] Update import ordering to reflect new standard --- src/main/java/org/lmdbjava/BufferProxy.java | 1 + .../java/org/lmdbjava/ByteArrayProxy.java | 6 ++- src/main/java/org/lmdbjava/ByteBufProxy.java | 6 ++- .../java/org/lmdbjava/ByteBufferProxy.java | 12 +++-- src/main/java/org/lmdbjava/Cursor.java | 5 +- .../java/org/lmdbjava/CursorIterable.java | 8 +-- src/main/java/org/lmdbjava/Dbi.java | 20 +++---- .../java/org/lmdbjava/DirectBufferProxy.java | 6 ++- src/main/java/org/lmdbjava/Env.java | 22 ++++---- src/main/java/org/lmdbjava/KeyRangeType.java | 3 +- src/main/java/org/lmdbjava/KeyVal.java | 5 +- src/main/java/org/lmdbjava/Library.java | 16 +++--- src/main/java/org/lmdbjava/Meta.java | 3 +- .../java/org/lmdbjava/ResultCodeMapper.java | 3 +- src/main/java/org/lmdbjava/Txn.java | 6 ++- src/main/java/org/lmdbjava/UnsafeAccess.java | 2 + src/main/java/org/lmdbjava/Verifier.java | 7 +-- .../org/lmdbjava/ByteBufferProxyTest.java | 24 +++++---- .../java/org/lmdbjava/ComparatorTest.java | 26 +++++----- .../java/org/lmdbjava/CursorIterableTest.java | 36 +++++++------ .../java/org/lmdbjava/CursorParamTest.java | 28 +++++----- src/test/java/org/lmdbjava/CursorTest.java | 20 +++---- src/test/java/org/lmdbjava/DbiTest.java | 52 ++++++++++--------- src/test/java/org/lmdbjava/EnvTest.java | 26 +++++----- src/test/java/org/lmdbjava/KeyRangeTest.java | 12 +++-- src/test/java/org/lmdbjava/LibraryTest.java | 5 +- .../java/org/lmdbjava/MaskedFlagTest.java | 3 +- src/test/java/org/lmdbjava/MetaTest.java | 5 +- .../org/lmdbjava/ResultCodeMapperTest.java | 12 +++-- src/test/java/org/lmdbjava/TestUtils.java | 6 ++- src/test/java/org/lmdbjava/TutorialTest.java | 24 +++++---- src/test/java/org/lmdbjava/TxnTest.java | 38 +++++++------- src/test/java/org/lmdbjava/VerifierTest.java | 10 ++-- 33 files changed, 257 insertions(+), 201 deletions(-) diff --git a/src/main/java/org/lmdbjava/BufferProxy.java b/src/main/java/org/lmdbjava/BufferProxy.java index 4f3536ab..0fe73f3f 100644 --- a/src/main/java/org/lmdbjava/BufferProxy.java +++ b/src/main/java/org/lmdbjava/BufferProxy.java @@ -21,6 +21,7 @@ package org.lmdbjava; import static java.lang.Long.BYTES; + import jnr.ffi.Pointer; /** diff --git a/src/main/java/org/lmdbjava/ByteArrayProxy.java b/src/main/java/org/lmdbjava/ByteArrayProxy.java index 51fee48c..dc964bcc 100644 --- a/src/main/java/org/lmdbjava/ByteArrayProxy.java +++ b/src/main/java/org/lmdbjava/ByteArrayProxy.java @@ -20,11 +20,13 @@ package org.lmdbjava; -import java.util.Arrays; import static java.util.Objects.requireNonNull; +import static org.lmdbjava.Library.RUNTIME; + +import java.util.Arrays; + import jnr.ffi.Pointer; import jnr.ffi.provider.MemoryManager; -import static org.lmdbjava.Library.RUNTIME; /** * Byte array proxy. diff --git a/src/main/java/org/lmdbjava/ByteBufProxy.java b/src/main/java/org/lmdbjava/ByteBufProxy.java index 042416d8..ca580363 100644 --- a/src/main/java/org/lmdbjava/ByteBufProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufProxy.java @@ -20,14 +20,16 @@ package org.lmdbjava; -import io.netty.buffer.ByteBuf; import static io.netty.buffer.PooledByteBufAllocator.DEFAULT; import static java.lang.Class.forName; import static java.lang.ThreadLocal.withInitial; +import static org.lmdbjava.UnsafeAccess.UNSAFE; + import java.lang.reflect.Field; import java.util.ArrayDeque; + +import io.netty.buffer.ByteBuf; import jnr.ffi.Pointer; -import static org.lmdbjava.UnsafeAccess.UNSAFE; /** * A buffer proxy backed by Netty's {@link ByteBuf}. diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index 9d143808..9d1b0f81 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -22,18 +22,20 @@ import static java.lang.Long.reverseBytes; import static java.lang.ThreadLocal.withInitial; -import java.lang.reflect.Field; -import java.nio.Buffer; -import java.nio.ByteBuffer; import static java.nio.ByteBuffer.allocateDirect; import static java.nio.ByteOrder.BIG_ENDIAN; import static java.nio.ByteOrder.LITTLE_ENDIAN; -import java.util.ArrayDeque; import static java.util.Objects.requireNonNull; -import jnr.ffi.Pointer; 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 jnr.ffi.Pointer; + /** * {@link ByteBuffer}-based proxy. * diff --git a/src/main/java/org/lmdbjava/Cursor.java b/src/main/java/org/lmdbjava/Cursor.java index 2ae2140d..5dac5b58 100644 --- a/src/main/java/org/lmdbjava/Cursor.java +++ b/src/main/java/org/lmdbjava/Cursor.java @@ -21,8 +21,6 @@ package org.lmdbjava; import static java.util.Objects.requireNonNull; -import jnr.ffi.Pointer; -import jnr.ffi.byref.NativeLongByReference; import static org.lmdbjava.Dbi.KeyExistsException.MDB_KEYEXIST; import static org.lmdbjava.Dbi.KeyNotFoundException.MDB_NOTFOUND; import static org.lmdbjava.Env.SHOULD_CHECK; @@ -39,6 +37,9 @@ import static org.lmdbjava.SeekOp.MDB_NEXT; import static org.lmdbjava.SeekOp.MDB_PREV; +import jnr.ffi.Pointer; +import jnr.ffi.byref.NativeLongByReference; + /** * A cursor handle. * diff --git a/src/main/java/org/lmdbjava/CursorIterable.java b/src/main/java/org/lmdbjava/CursorIterable.java index 1676a3db..19729443 100644 --- a/src/main/java/org/lmdbjava/CursorIterable.java +++ b/src/main/java/org/lmdbjava/CursorIterable.java @@ -20,15 +20,17 @@ package org.lmdbjava; -import java.util.Comparator; -import java.util.Iterator; -import java.util.NoSuchElementException; import static org.lmdbjava.CursorIterable.State.RELEASED; import static org.lmdbjava.CursorIterable.State.REQUIRES_INITIAL_OP; import static org.lmdbjava.CursorIterable.State.REQUIRES_ITERATOR_OP; import static org.lmdbjava.CursorIterable.State.REQUIRES_NEXT_OP; import static org.lmdbjava.CursorIterable.State.TERMINATED; import static org.lmdbjava.GetOp.MDB_SET_RANGE; + +import java.util.Comparator; +import java.util.Iterator; +import java.util.NoSuchElementException; + import org.lmdbjava.KeyRangeType.CursorOp; import org.lmdbjava.KeyRangeType.IteratorOp; diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index 2106687d..62fbc0f2 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -20,23 +20,14 @@ package org.lmdbjava; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; import static java.util.Objects.requireNonNull; import static jnr.ffi.Memory.allocateDirect; import static jnr.ffi.NativeType.ADDRESS; -import jnr.ffi.Pointer; -import jnr.ffi.byref.IntByReference; -import jnr.ffi.byref.PointerByReference; 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 org.lmdbjava.Library.ComparatorCallback; import static org.lmdbjava.Library.LIB; -import org.lmdbjava.Library.MDB_stat; import static org.lmdbjava.Library.RUNTIME; import static org.lmdbjava.MaskedFlag.isSet; import static org.lmdbjava.MaskedFlag.mask; @@ -45,6 +36,17 @@ import static org.lmdbjava.PutFlags.MDB_RESERVE; import static org.lmdbjava.ResultCodeMapper.checkRc; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +import jnr.ffi.Pointer; +import jnr.ffi.byref.IntByReference; +import jnr.ffi.byref.PointerByReference; +import org.lmdbjava.Library.ComparatorCallback; +import org.lmdbjava.Library.MDB_stat; + /** * LMDB Database. * diff --git a/src/main/java/org/lmdbjava/DirectBufferProxy.java b/src/main/java/org/lmdbjava/DirectBufferProxy.java index adcbfb7f..af911afa 100644 --- a/src/main/java/org/lmdbjava/DirectBufferProxy.java +++ b/src/main/java/org/lmdbjava/DirectBufferProxy.java @@ -21,16 +21,18 @@ package org.lmdbjava; import static java.lang.ThreadLocal.withInitial; -import java.nio.ByteBuffer; import static java.nio.ByteBuffer.allocateDirect; import static java.nio.ByteOrder.BIG_ENDIAN; import static java.util.Objects.requireNonNull; +import static org.lmdbjava.UnsafeAccess.UNSAFE; + +import java.nio.ByteBuffer; + import jnr.ffi.Pointer; import org.agrona.DirectBuffer; import org.agrona.MutableDirectBuffer; import org.agrona.concurrent.OneToOneConcurrentArrayQueue; import org.agrona.concurrent.UnsafeBuffer; -import static org.lmdbjava.UnsafeAccess.UNSAFE; /** * A buffer proxy backed by Agrona's {@link DirectBuffer}. diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index 765c1565..7634a342 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -20,29 +20,31 @@ package org.lmdbjava; -import java.io.File; import static java.lang.Boolean.getBoolean; -import java.nio.ByteBuffer; import static java.nio.charset.StandardCharsets.UTF_8; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; import static java.util.Objects.requireNonNull; -import jnr.ffi.Pointer; -import jnr.ffi.byref.PointerByReference; 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 org.lmdbjava.Library.MDB_envinfo; -import org.lmdbjava.Library.MDB_stat; import static org.lmdbjava.Library.RUNTIME; import static org.lmdbjava.MaskedFlag.isSet; import static org.lmdbjava.MaskedFlag.mask; import static org.lmdbjava.ResultCodeMapper.checkRc; import static org.lmdbjava.TxnFlags.MDB_RDONLY_TXN; +import java.io.File; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import jnr.ffi.Pointer; +import jnr.ffi.byref.PointerByReference; +import org.lmdbjava.Library.MDB_envinfo; +import org.lmdbjava.Library.MDB_stat; + /** * LMDB environment. * diff --git a/src/main/java/org/lmdbjava/KeyRangeType.java b/src/main/java/org/lmdbjava/KeyRangeType.java index 767bdeeb..156daf99 100644 --- a/src/main/java/org/lmdbjava/KeyRangeType.java +++ b/src/main/java/org/lmdbjava/KeyRangeType.java @@ -20,7 +20,6 @@ package org.lmdbjava; -import java.util.Comparator; import static java.util.Objects.requireNonNull; import static org.lmdbjava.KeyRangeType.CursorOp.FIRST; import static org.lmdbjava.KeyRangeType.CursorOp.GET_START_KEY; @@ -32,6 +31,8 @@ import static org.lmdbjava.KeyRangeType.IteratorOp.RELEASE; import static org.lmdbjava.KeyRangeType.IteratorOp.TERMINATE; +import java.util.Comparator; + /** * Key range type. * diff --git a/src/main/java/org/lmdbjava/KeyVal.java b/src/main/java/org/lmdbjava/KeyVal.java index 6bfd4546..481d34ed 100644 --- a/src/main/java/org/lmdbjava/KeyVal.java +++ b/src/main/java/org/lmdbjava/KeyVal.java @@ -21,12 +21,13 @@ package org.lmdbjava; import static java.util.Objects.requireNonNull; -import jnr.ffi.Pointer; -import jnr.ffi.provider.MemoryManager; 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 jnr.ffi.Pointer; +import jnr.ffi.provider.MemoryManager; + /** * Represents off-heap memory holding a key and value pair. * diff --git a/src/main/java/org/lmdbjava/Library.java b/src/main/java/org/lmdbjava/Library.java index dfecb2c4..18d3e717 100644 --- a/src/main/java/org/lmdbjava/Library.java +++ b/src/main/java/org/lmdbjava/Library.java @@ -20,22 +20,24 @@ package org.lmdbjava; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; -import java.io.File; import static java.io.File.createTempFile; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import static java.lang.Boolean.getBoolean; import static java.lang.System.getProperty; import static java.lang.Thread.currentThread; -import java.nio.file.Files; import static java.util.Locale.ENGLISH; import static java.util.Objects.nonNull; import static java.util.Objects.requireNonNull; import static jnr.ffi.LibraryLoader.create; -import jnr.ffi.Pointer; 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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import jnr.ffi.Pointer; import jnr.ffi.Struct; import jnr.ffi.annotations.Delegate; import jnr.ffi.annotations.In; diff --git a/src/main/java/org/lmdbjava/Meta.java b/src/main/java/org/lmdbjava/Meta.java index 46807f3c..699b182d 100644 --- a/src/main/java/org/lmdbjava/Meta.java +++ b/src/main/java/org/lmdbjava/Meta.java @@ -20,9 +20,10 @@ package org.lmdbjava; -import jnr.ffi.byref.IntByReference; import static org.lmdbjava.Library.LIB; +import jnr.ffi.byref.IntByReference; + /** * LMDB metadata functions. */ diff --git a/src/main/java/org/lmdbjava/ResultCodeMapper.java b/src/main/java/org/lmdbjava/ResultCodeMapper.java index 63310fcd..ed209f41 100644 --- a/src/main/java/org/lmdbjava/ResultCodeMapper.java +++ b/src/main/java/org/lmdbjava/ResultCodeMapper.java @@ -20,9 +20,10 @@ package org.lmdbjava; +import static jnr.constants.ConstantSet.getConstantSet; + import jnr.constants.Constant; import jnr.constants.ConstantSet; -import static jnr.constants.ConstantSet.getConstantSet; import org.lmdbjava.Txn.BadException; import org.lmdbjava.Txn.BadReaderLockException; import org.lmdbjava.Txn.TxFullException; diff --git a/src/main/java/org/lmdbjava/Txn.java b/src/main/java/org/lmdbjava/Txn.java index 67c84343..dfdfc60a 100644 --- a/src/main/java/org/lmdbjava/Txn.java +++ b/src/main/java/org/lmdbjava/Txn.java @@ -20,10 +20,8 @@ package org.lmdbjava; -import java.util.Comparator; import static jnr.ffi.Memory.allocateDirect; import static jnr.ffi.NativeType.ADDRESS; -import jnr.ffi.Pointer; import static org.lmdbjava.Library.LIB; import static org.lmdbjava.Library.RUNTIME; import static org.lmdbjava.MaskedFlag.isSet; @@ -35,6 +33,10 @@ import static org.lmdbjava.Txn.State.RESET; import static org.lmdbjava.TxnFlags.MDB_RDONLY_TXN; +import java.util.Comparator; + +import jnr.ffi.Pointer; + /** * LMDB transaction. * diff --git a/src/main/java/org/lmdbjava/UnsafeAccess.java b/src/main/java/org/lmdbjava/UnsafeAccess.java index 92d1df5e..7114e34d 100644 --- a/src/main/java/org/lmdbjava/UnsafeAccess.java +++ b/src/main/java/org/lmdbjava/UnsafeAccess.java @@ -21,7 +21,9 @@ package org.lmdbjava; import static java.lang.Boolean.getBoolean; + import java.lang.reflect.Field; + import sun.misc.Unsafe; /** diff --git a/src/main/java/org/lmdbjava/Verifier.java b/src/main/java/org/lmdbjava/Verifier.java index b8dbc87b..9bc7b89e 100644 --- a/src/main/java/org/lmdbjava/Verifier.java +++ b/src/main/java/org/lmdbjava/Verifier.java @@ -20,11 +20,13 @@ package org.lmdbjava; -import java.nio.ByteBuffer; import static java.nio.ByteOrder.BIG_ENDIAN; +import static java.util.Objects.requireNonNull; +import static org.lmdbjava.DbiFlags.MDB_CREATE; + +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; -import static java.util.Objects.requireNonNull; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; @@ -34,7 +36,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.zip.CRC32; -import static org.lmdbjava.DbiFlags.MDB_CREATE; /** * Verifies correct operation of LmdbJava in a given environment. diff --git a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java index 9399eada..ba882281 100644 --- a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java +++ b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java @@ -20,38 +20,40 @@ package org.lmdbjava; -import java.io.File; -import java.io.IOException; import static java.lang.Integer.BYTES; -import java.lang.reflect.Field; -import java.nio.ByteBuffer; 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 jnr.ffi.Pointer; -import jnr.ffi.provider.MemoryManager; 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 org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; import static org.lmdbjava.BufferProxy.MDB_VAL_STRUCT_SIZE; import static org.lmdbjava.ByteBufferProxy.AbstractByteBufferProxy.findField; -import org.lmdbjava.ByteBufferProxy.BufferMustBeDirectException; import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; import static org.lmdbjava.ByteBufferProxy.PROXY_SAFE; import static org.lmdbjava.DbiFlags.MDB_CREATE; -import org.lmdbjava.Env.ReadersFullException; 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}. */ diff --git a/src/test/java/org/lmdbjava/ComparatorTest.java b/src/test/java/org/lmdbjava/ComparatorTest.java index 1f5b8f9a..e8499838 100644 --- a/src/test/java/org/lmdbjava/ComparatorTest.java +++ b/src/test/java/org/lmdbjava/ComparatorTest.java @@ -20,22 +20,10 @@ package org.lmdbjava; -import com.google.common.primitives.SignedBytes; -import com.google.common.primitives.UnsignedBytes; -import io.netty.buffer.ByteBuf; import static io.netty.buffer.PooledByteBufAllocator.DEFAULT; -import java.nio.ByteBuffer; import static java.nio.charset.StandardCharsets.US_ASCII; -import java.util.Comparator; -import org.agrona.DirectBuffer; -import org.agrona.concurrent.UnsafeBuffer; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; import static org.lmdbjava.ByteArrayProxy.PROXY_BA; import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; import static org.lmdbjava.ComparatorTest.ComparatorResult.EQUAL_TO; @@ -44,6 +32,20 @@ import static org.lmdbjava.ComparatorTest.ComparatorResult.get; import static org.lmdbjava.DirectBufferProxy.PROXY_DB; +import java.nio.ByteBuffer; +import java.util.Comparator; + +import com.google.common.primitives.SignedBytes; +import com.google.common.primitives.UnsignedBytes; +import io.netty.buffer.ByteBuf; +import org.agrona.DirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + /** * Tests comparator functions are consistent across buffers. */ diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index f3e04d87..cc6cd0b6 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -20,28 +20,11 @@ package org.lmdbjava; -import com.google.common.primitives.UnsignedBytes; import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; -import java.io.File; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.ArrayList; import static java.util.Arrays.asList; -import java.util.Comparator; -import java.util.Deque; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.NoSuchElementException; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.lmdbjava.CursorIterable.KeyVal; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.Env.create; import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; @@ -68,6 +51,25 @@ import static org.lmdbjava.TestUtils.POSIX_MODE; import static org.lmdbjava.TestUtils.bb; +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Deque; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.NoSuchElementException; + +import com.google.common.primitives.UnsignedBytes; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.lmdbjava.CursorIterable.KeyVal; + /** * Test {@link CursorIterable}. */ diff --git a/src/test/java/org/lmdbjava/CursorParamTest.java b/src/test/java/org/lmdbjava/CursorParamTest.java index 39475285..c105aae7 100644 --- a/src/test/java/org/lmdbjava/CursorParamTest.java +++ b/src/test/java/org/lmdbjava/CursorParamTest.java @@ -21,24 +21,11 @@ package org.lmdbjava; import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; -import io.netty.buffer.ByteBuf; -import java.io.File; -import java.io.IOException; import static java.lang.Long.BYTES; -import java.nio.ByteBuffer; import static java.nio.charset.StandardCharsets.UTF_8; -import org.agrona.DirectBuffer; -import org.agrona.MutableDirectBuffer; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.collection.IsEmptyCollection.empty; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; import static org.lmdbjava.ByteArrayProxy.PROXY_BA; import static org.lmdbjava.ByteBufProxy.PROXY_NETTY; import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; @@ -61,6 +48,21 @@ import static org.lmdbjava.TestUtils.mdb; import static org.lmdbjava.TestUtils.nb; +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; + +import io.netty.buffer.ByteBuf; +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + /** * Test {@link Cursor} with different buffer implementations. */ diff --git a/src/test/java/org/lmdbjava/CursorTest.java b/src/test/java/org/lmdbjava/CursorTest.java index 55af22ec..137732f5 100644 --- a/src/test/java/org/lmdbjava/CursorTest.java +++ b/src/test/java/org/lmdbjava/CursorTest.java @@ -21,23 +21,14 @@ package org.lmdbjava; import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; -import java.io.File; -import java.io.IOException; import static java.lang.Long.BYTES; import static java.lang.Long.MIN_VALUE; -import java.nio.ByteBuffer; import static java.nio.ByteBuffer.allocateDirect; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import org.junit.After; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; -import org.lmdbjava.Cursor.ClosedException; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.DbiFlags.MDB_DUPFIXED; import static org.lmdbjava.DbiFlags.MDB_DUPSORT; @@ -52,6 +43,17 @@ import static org.lmdbjava.TestUtils.DB_1; import static org.lmdbjava.TestUtils.POSIX_MODE; import static org.lmdbjava.TestUtils.bb; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.lmdbjava.Cursor.ClosedException; import org.lmdbjava.Txn.NotReadyException; import org.lmdbjava.Txn.ReadOnlyRequiredException; diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 3db1eabf..b9bf453c 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -21,64 +21,66 @@ package org.lmdbjava; import static com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; -import java.io.File; -import java.io.IOException; import static java.lang.Long.MAX_VALUE; import static java.lang.System.getProperty; -import java.nio.ByteBuffer; import static java.nio.ByteBuffer.allocateDirect; import static java.nio.charset.StandardCharsets.UTF_8; -import java.util.ArrayList; import static java.util.Collections.nCopies; -import java.util.Comparator; -import java.util.Iterator; -import java.util.List; -import java.util.Random; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; import static java.util.concurrent.TimeUnit.SECONDS; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; import static java.util.stream.Collectors.toList; import static java.util.stream.IntStream.range; -import org.agrona.concurrent.UnsafeBuffer; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; -import org.hamcrest.Matchers; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.collection.IsEmptyCollection.empty; -import org.junit.After; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; import static org.lmdbjava.ByteArrayProxy.PROXY_BA; import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; -import org.lmdbjava.CursorIterable.KeyVal; -import org.lmdbjava.Dbi.DbFullException; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.DbiFlags.MDB_DUPSORT; import static org.lmdbjava.DbiFlags.MDB_REVERSEKEY; -import org.lmdbjava.Env.MapFullException; import static org.lmdbjava.Env.create; import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; import static org.lmdbjava.GetOp.MDB_SET_KEY; import static org.lmdbjava.KeyRange.atMost; -import org.lmdbjava.LmdbNativeException.ConstantDerivedException; import static org.lmdbjava.PutFlags.MDB_NODUPDATA; import static org.lmdbjava.PutFlags.MDB_NOOVERWRITE; import static org.lmdbjava.TestUtils.DB_1; import static org.lmdbjava.TestUtils.ba; import static org.lmdbjava.TestUtils.bb; +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Random; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.agrona.concurrent.UnsafeBuffer; +import org.hamcrest.Matchers; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.lmdbjava.CursorIterable.KeyVal; +import org.lmdbjava.Dbi.DbFullException; +import org.lmdbjava.Env.MapFullException; +import org.lmdbjava.LmdbNativeException.ConstantDerivedException; + /** * Test {@link Dbi}. */ diff --git a/src/test/java/org/lmdbjava/EnvTest.java b/src/test/java/org/lmdbjava/EnvTest.java index 60e1dc27..3e9ece95 100644 --- a/src/test/java/org/lmdbjava/EnvTest.java +++ b/src/test/java/org/lmdbjava/EnvTest.java @@ -21,33 +21,35 @@ package org.lmdbjava; import static com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; -import java.io.File; -import java.io.IOException; -import java.nio.ByteBuffer; import static java.nio.ByteBuffer.allocateDirect; -import java.util.Random; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.greaterThan; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; import static org.lmdbjava.CopyFlags.MDB_CP_COMPACT; import static org.lmdbjava.DbiFlags.MDB_CREATE; -import org.lmdbjava.Env.AlreadyClosedException; -import org.lmdbjava.Env.AlreadyOpenException; -import org.lmdbjava.Env.Builder; import static org.lmdbjava.Env.Builder.MAX_READERS_DEFAULT; -import org.lmdbjava.Env.InvalidCopyDestination; -import org.lmdbjava.Env.MapFullException; import static org.lmdbjava.Env.create; import static org.lmdbjava.Env.open; import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; import static org.lmdbjava.EnvFlags.MDB_RDONLY_ENV; import static org.lmdbjava.TestUtils.DB_1; import static org.lmdbjava.TestUtils.bb; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Random; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.lmdbjava.Env.AlreadyClosedException; +import org.lmdbjava.Env.AlreadyOpenException; +import org.lmdbjava.Env.Builder; +import org.lmdbjava.Env.InvalidCopyDestination; +import org.lmdbjava.Env.MapFullException; import org.lmdbjava.Txn.BadReaderLockException; /** diff --git a/src/test/java/org/lmdbjava/KeyRangeTest.java b/src/test/java/org/lmdbjava/KeyRangeTest.java index d6556272..0cb3d1f5 100644 --- a/src/test/java/org/lmdbjava/KeyRangeTest.java +++ b/src/test/java/org/lmdbjava/KeyRangeTest.java @@ -20,13 +20,9 @@ package org.lmdbjava; -import java.util.ArrayList; -import java.util.List; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; -import org.junit.Before; -import org.junit.Test; import static org.lmdbjava.KeyRange.all; import static org.lmdbjava.KeyRange.allBackward; import static org.lmdbjava.KeyRange.atLeast; @@ -45,9 +41,15 @@ import static org.lmdbjava.KeyRange.openBackward; import static org.lmdbjava.KeyRange.openClosed; import static org.lmdbjava.KeyRange.openClosedBackward; +import static org.lmdbjava.KeyRangeType.IteratorOp.TERMINATE; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; import org.lmdbjava.KeyRangeType.CursorOp; import org.lmdbjava.KeyRangeType.IteratorOp; -import static org.lmdbjava.KeyRangeType.IteratorOp.TERMINATE; /** * Test {@link KeyRange}. diff --git a/src/test/java/org/lmdbjava/LibraryTest.java b/src/test/java/org/lmdbjava/LibraryTest.java index 35523272..08c6ea9f 100644 --- a/src/test/java/org/lmdbjava/LibraryTest.java +++ b/src/test/java/org/lmdbjava/LibraryTest.java @@ -23,11 +23,12 @@ import static java.lang.Long.BYTES; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import org.junit.Test; -import org.lmdbjava.Library.MDB_envinfo; 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}. */ diff --git a/src/test/java/org/lmdbjava/MaskedFlagTest.java b/src/test/java/org/lmdbjava/MaskedFlagTest.java index e5a63952..34acb3f8 100644 --- a/src/test/java/org/lmdbjava/MaskedFlagTest.java +++ b/src/test/java/org/lmdbjava/MaskedFlagTest.java @@ -23,13 +23,14 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.arrayWithSize; -import org.junit.Test; import static org.lmdbjava.EnvFlags.MDB_FIXEDMAP; import static org.lmdbjava.EnvFlags.MDB_NOSYNC; import static org.lmdbjava.EnvFlags.MDB_RDONLY_ENV; import static org.lmdbjava.MaskedFlag.isSet; import static org.lmdbjava.MaskedFlag.mask; +import org.junit.Test; + /** * Test {@link MaskedFlag}. */ diff --git a/src/test/java/org/lmdbjava/MetaTest.java b/src/test/java/org/lmdbjava/MetaTest.java index 18967fec..d20f6510 100644 --- a/src/test/java/org/lmdbjava/MetaTest.java +++ b/src/test/java/org/lmdbjava/MetaTest.java @@ -24,12 +24,13 @@ import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; -import org.junit.Test; import static org.lmdbjava.LmdbNativeException.PageCorruptedException.MDB_CORRUPTED; -import org.lmdbjava.Meta.Version; import static org.lmdbjava.Meta.error; import static org.lmdbjava.TestUtils.invokePrivateConstructor; +import org.junit.Test; +import org.lmdbjava.Meta.Version; + /** * Test {@link Meta}. */ diff --git a/src/test/java/org/lmdbjava/ResultCodeMapperTest.java b/src/test/java/org/lmdbjava/ResultCodeMapperTest.java index 5d915f84..f2382021 100644 --- a/src/test/java/org/lmdbjava/ResultCodeMapperTest.java +++ b/src/test/java/org/lmdbjava/ResultCodeMapperTest.java @@ -21,17 +21,21 @@ package org.lmdbjava; import static java.lang.Integer.MAX_VALUE; -import java.util.HashSet; -import java.util.Set; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; import static org.junit.Assert.fail; +import static org.lmdbjava.Cursor.FullException.MDB_CURSOR_FULL; +import static org.lmdbjava.ResultCodeMapper.checkRc; +import static org.lmdbjava.TestUtils.invokePrivateConstructor; + +import java.util.HashSet; +import java.util.Set; + import org.junit.Test; import org.lmdbjava.Cursor.FullException; -import static org.lmdbjava.Cursor.FullException.MDB_CURSOR_FULL; import org.lmdbjava.Dbi.BadDbiException; import org.lmdbjava.Dbi.BadValueSizeException; import org.lmdbjava.Dbi.DbFullException; @@ -49,8 +53,6 @@ import org.lmdbjava.LmdbNativeException.PageNotFoundException; import org.lmdbjava.LmdbNativeException.PanicException; import org.lmdbjava.LmdbNativeException.TlsFullException; -import static org.lmdbjava.ResultCodeMapper.checkRc; -import static org.lmdbjava.TestUtils.invokePrivateConstructor; import org.lmdbjava.Txn.BadException; import org.lmdbjava.Txn.BadReaderLockException; import org.lmdbjava.Txn.TxFullException; diff --git a/src/test/java/org/lmdbjava/TestUtils.java b/src/test/java/org/lmdbjava/TestUtils.java index afc9c418..1de8218e 100644 --- a/src/test/java/org/lmdbjava/TestUtils.java +++ b/src/test/java/org/lmdbjava/TestUtils.java @@ -20,13 +20,15 @@ package org.lmdbjava; -import io.netty.buffer.ByteBuf; import static io.netty.buffer.PooledByteBufAllocator.DEFAULT; import static java.lang.Integer.BYTES; +import static java.nio.ByteBuffer.allocateDirect; + import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.nio.ByteBuffer; -import static java.nio.ByteBuffer.allocateDirect; + +import io.netty.buffer.ByteBuf; import org.agrona.MutableDirectBuffer; import org.agrona.concurrent.UnsafeBuffer; diff --git a/src/test/java/org/lmdbjava/TutorialTest.java b/src/test/java/org/lmdbjava/TutorialTest.java index 74e99c6d..61154ea3 100644 --- a/src/test/java/org/lmdbjava/TutorialTest.java +++ b/src/test/java/org/lmdbjava/TutorialTest.java @@ -20,28 +20,17 @@ package org.lmdbjava; -import java.io.File; -import java.io.IOException; -import java.nio.ByteBuffer; import static java.nio.ByteBuffer.allocateDirect; import static java.nio.charset.StandardCharsets.UTF_8; -import java.util.concurrent.ExecutorService; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.TimeUnit.SECONDS; -import org.agrona.DirectBuffer; -import org.agrona.MutableDirectBuffer; -import org.agrona.concurrent.UnsafeBuffer; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.startsWith; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; -import org.lmdbjava.CursorIterable.KeyVal; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.DbiFlags.MDB_DUPSORT; import static org.lmdbjava.DirectBufferProxy.PROXY_DB; @@ -51,6 +40,19 @@ import static org.lmdbjava.SeekOp.MDB_LAST; import static org.lmdbjava.SeekOp.MDB_PREV; +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.ExecutorService; + +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.lmdbjava.CursorIterable.KeyVal; + /** * Welcome to LmdbJava! * diff --git a/src/test/java/org/lmdbjava/TxnTest.java b/src/test/java/org/lmdbjava/TxnTest.java index 29dae897..dda4694e 100644 --- a/src/test/java/org/lmdbjava/TxnTest.java +++ b/src/test/java/org/lmdbjava/TxnTest.java @@ -21,28 +21,15 @@ package org.lmdbjava; import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; -import java.io.File; -import java.io.IOException; -import java.nio.ByteBuffer; import static java.nio.ByteBuffer.allocateDirect; import static java.nio.charset.StandardCharsets.UTF_8; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicLong; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; -import org.junit.After; import static org.junit.Assert.assertEquals; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.lmdbjava.Dbi.BadValueSizeException; import static org.lmdbjava.DbiFlags.MDB_CREATE; -import org.lmdbjava.Env.AlreadyClosedException; import static org.lmdbjava.Env.create; import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; import static org.lmdbjava.EnvFlags.MDB_RDONLY_ENV; @@ -50,6 +37,26 @@ import static org.lmdbjava.TestUtils.DB_1; import static org.lmdbjava.TestUtils.POSIX_MODE; import static org.lmdbjava.TestUtils.bb; +import static org.lmdbjava.Txn.State.DONE; +import static org.lmdbjava.Txn.State.READY; +import static org.lmdbjava.Txn.State.RELEASED; +import static org.lmdbjava.Txn.State.RESET; +import static org.lmdbjava.TxnFlags.MDB_RDONLY_TXN; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.lmdbjava.Dbi.BadValueSizeException; +import org.lmdbjava.Env.AlreadyClosedException; import org.lmdbjava.Txn.EnvIsReadOnly; import org.lmdbjava.Txn.IncompatibleParent; import org.lmdbjava.Txn.NotReadyException; @@ -57,11 +64,6 @@ import org.lmdbjava.Txn.ReadOnlyRequiredException; import org.lmdbjava.Txn.ReadWriteRequiredException; import org.lmdbjava.Txn.ResetException; -import static org.lmdbjava.Txn.State.DONE; -import static org.lmdbjava.Txn.State.READY; -import static org.lmdbjava.Txn.State.RELEASED; -import static org.lmdbjava.Txn.State.RESET; -import static org.lmdbjava.TxnFlags.MDB_RDONLY_TXN; /** * Test {@link Txn}. diff --git a/src/test/java/org/lmdbjava/VerifierTest.java b/src/test/java/org/lmdbjava/VerifierTest.java index ad0b42d9..16f228d2 100644 --- a/src/test/java/org/lmdbjava/VerifierTest.java +++ b/src/test/java/org/lmdbjava/VerifierTest.java @@ -21,17 +21,19 @@ package org.lmdbjava; import static com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.greaterThan; +import static org.lmdbjava.Env.create; +import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; + import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.TimeUnit; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.greaterThan; + import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import static org.lmdbjava.Env.create; -import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; /** * Test {@link Verifier}. From ace015470cc0bff72bf2d48cc2e932efec6b5b88 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 1 Jun 2020 17:58:35 +1000 Subject: [PATCH 095/322] Update to Acegi Standard Project 0.5.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c8ddfd61..8991bf80 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ au.com.acegi acegi-standard-project - 0.4.1-SNAPSHOT + 0.5.0 org.lmdbjava lmdbjava From a88c2bc416cdb5a92c8d9228a97b2d87097d0e65 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 1 Jun 2020 17:59:55 +1000 Subject: [PATCH 096/322] Add sortpom plugin and apply resulting changes --- pom.xml | 222 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 114 insertions(+), 108 deletions(-) diff --git a/pom.xml b/pom.xml index 8991bf80..54dec6c0 100644 --- a/pom.xml +++ b/pom.xml @@ -12,49 +12,6 @@ jar LmdbJava Low latency Java API for the ultra-fast, embedded Symas Lightning Database (LMDB) - 2016 - - The LmdbJava Open Source Project - https://github.com/${github.org} - - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - - - - - Kristoffer Sjogren - krisskross - stoffe -at- gmail.com - - http://stoffe.deephacks.org/ - +1 - - - Ben Alex - benalexau - ben.alex@acegi.com.au - https://github.com/benalexau/ - Acegi Technology Pty Limited - +10 - - - - scm:git:git@github.com:${github.org}/${github.repo}.git - scm:git:git@github.com:${github.org}/${github.repo}.git - git@github.com:${github.org}/${github.repo}.git - HEAD - - - GitHub Issues - https://github.com/${github.org}/${github.repo}/issues - - - GitHub Actions - https://github.com/${github.org}/${github.repo}/actions - lmdbjava lmdbjava @@ -62,12 +19,9 @@ - junit - junit - - - org.hamcrest - hamcrest + com.github.jnr + jnr-constants + 0.9.15 com.github.jnr @@ -75,9 +29,20 @@ 2.1.15 - com.github.jnr - jnr-constants - 0.9.15 + com.google.code.findbugs + annotations + + + com.google.guava + guava + 29.0-jre + test + + + com.google.code.findbugs + jsr305 + + com.jakewharton.byteunits @@ -85,12 +50,6 @@ 0.9.1 test - - org.agrona - agrona - 1.5.1 - true - io.netty netty-all @@ -98,26 +57,22 @@ true - com.google.guava - guava - 29.0-jre - test - - - com.google.code.findbugs - jsr305 - - + junit + junit - org.lmdbjava - lmdbjava-native-linux-x86_64 - 0.9.24-1 + org.agrona + agrona + 1.5.1 true + + org.hamcrest + hamcrest + org.lmdbjava - lmdbjava-native-windows-x86_64 + lmdbjava-native-linux-x86_64 0.9.24-1 true @@ -128,16 +83,32 @@ true - com.google.code.findbugs - annotations + org.lmdbjava + lmdbjava-native-windows-x86_64 + 0.9.24-1 + true - maven-enforcer-plugin + au.com.acegi + xml-format-maven-plugin + + + com.github.ekryd.sortpom + sortpom-maven-plugin + + + com.github.spotbugs + spotbugs-maven-plugin + + + org.apache.maven.plugins + maven-checkstyle-plugin + org.apache.maven.plugins maven-dependency-plugin @@ -145,54 +116,26 @@ org.lmdbjava:lmdbjava-native-windows-x86_64 org.lmdbjava:lmdbjava-native-osx-x86_64 - - com.github.jnr:jffi - - maven-checkstyle-plugin + org.apache.maven.plugins + maven-enforcer-plugin + org.apache.maven.plugins maven-pmd-plugin - org.basepom.maven - duplicate-finder-maven-plugin - - - org.codehaus.mojo - versions-maven-plugin - - - org.codehaus.mojo - buildnumber-maven-plugin - - - org.jacoco - jacoco-maven-plugin - - - com.github.spotbugs - spotbugs-maven-plugin - - - au.com.acegi - xml-format-maven-plugin - - - org.codehaus.mojo - license-maven-plugin - - + org.apache.maven.plugins maven-shade-plugin lmdbjava-shade - package shade + package @@ -205,6 +148,69 @@ + + org.basepom.maven + duplicate-finder-maven-plugin + + + org.codehaus.mojo + buildnumber-maven-plugin + + + org.codehaus.mojo + license-maven-plugin + + + org.codehaus.mojo + versions-maven-plugin + + + org.jacoco + jacoco-maven-plugin + + 2016 + + The LmdbJava Open Source Project + https://github.com/${github.org} + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + + + + krisskross + Kristoffer Sjogren + stoffe -at- gmail.com + http://stoffe.deephacks.org/ + + +1 + + + benalexau + Ben Alex + ben.alex@acegi.com.au + https://github.com/benalexau/ + Acegi Technology Pty Limited + +10 + + + + scm:git:git@github.com:${github.org}/${github.repo}.git + scm:git:git@github.com:${github.org}/${github.repo}.git + git@github.com:${github.org}/${github.repo}.git + HEAD + + + GitHub Issues + https://github.com/${github.org}/${github.repo}/issues + + + GitHub Actions + https://github.com/${github.org}/${github.repo}/actions + From 262202bbaf5f80cc91011ff515c4f1177943a7fa Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 1 Jun 2020 18:18:07 +1000 Subject: [PATCH 097/322] Revert to JNR-FFI 2.1.13 Windows CI builds on GitHub fail with 2.1.15. This reverts back to the version before commit 96c2ead9465f2e03cbfc03bc6aace69e86ee5bb8. --- pom.xml | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 54dec6c0..5c89b39e 100644 --- a/pom.xml +++ b/pom.xml @@ -18,6 +18,17 @@ apache_v2 + + com.github.jnr + jffi + 1.2.23 + + + com.github.jnr + jffi + 1.2.23 + native + com.github.jnr jnr-constants @@ -26,7 +37,13 @@ com.github.jnr jnr-ffi - 2.1.15 + 2.1.13 + + + com.github.jnr + jffi + + com.google.code.findbugs @@ -116,6 +133,9 @@ org.lmdbjava:lmdbjava-native-windows-x86_64 org.lmdbjava:lmdbjava-native-osx-x86_64 + + com.github.jnr:jffi + From 165b3f7c0bfbbf02d81d0208b46f35d638f0d291 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Tue, 2 Jun 2020 09:53:31 +1000 Subject: [PATCH 098/322] Test with JNR-FFI 2.1.14 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5c89b39e..1416774e 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,7 @@ com.github.jnr jnr-ffi - 2.1.13 + 2.1.14 com.github.jnr From 72c2d02ba90b025843d8e404fab9fde804fa8cb1 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Tue, 2 Jun 2020 10:05:55 +1000 Subject: [PATCH 099/322] Test again with JNR-FFI 2.1.15 (and explicit jnr-native dependencies) This is following Windows CI failures, but success with 2.1.14. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1416774e..05b8d422 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,7 @@ com.github.jnr jnr-ffi - 2.1.14 + 2.1.15 com.github.jnr From 180ea1d24af92e9f086e712cf8cc8b805aa9b254 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Tue, 2 Jun 2020 10:48:17 +1000 Subject: [PATCH 100/322] Test with JNR-FFI 2.1.15 using jffi scope=runtime GitHub CI tests were successful for commit 72c2d02ba90b025843d8e404fab9fde804fa8cb1, which used JNR-FFI 2.1.15 and explicit JFFI dependencies. The native dependency had the default scope, which is compile. Evaluation of https://github.com/jnr/jnr-ffi/blob/master/pom.xml confirms JNR-FFI uses scope=runtime, so this is to test an equivalent explicit scope given the earlier transitively-acquired dependency (see commit 96c2ead9465f2e03cbfc03bc6aace69e86ee5bb8) did not succeed. This commit reflects the following dependency graph: $ mvn dependency:tree | grep jnr [INFO] +- com.github.jnr:jffi:jar:1.2.23:compile [INFO] +- com.github.jnr:jffi:jar:native:1.2.23:runtime [INFO] +- com.github.jnr:jnr-constants:jar:0.9.15:compile [INFO] +- com.github.jnr:jnr-ffi:jar:2.1.15:compile [INFO] | +- com.github.jnr:jnr-a64asm:jar:1.0.0:compile [INFO] | \- com.github.jnr:jnr-x86asm:jar:1.0.2:compile --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 05b8d422..a9ca7a72 100644 --- a/pom.xml +++ b/pom.xml @@ -28,6 +28,7 @@ jffi 1.2.23 native + runtime com.github.jnr From 4465a3a9c59e846334c9aeaaba62cf1c4bb262e5 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Tue, 2 Jun 2020 11:07:12 +1000 Subject: [PATCH 101/322] Remove explicit JFFI dependencies (fetched transitively via JNR-FFI) Prior commit 180ea1d24af92e9f086e712cf8cc8b805aa9b254 was successful and as per its commit message used the following dependency graph: $ mvn dependency:tree | grep jnr [INFO] +- com.github.jnr:jffi:jar:1.2.23:compile [INFO] +- com.github.jnr:jffi:jar:native:1.2.23:runtime [INFO] +- com.github.jnr:jnr-constants:jar:0.9.15:compile [INFO] +- com.github.jnr:jnr-ffi:jar:2.1.15:compile [INFO] | +- com.github.jnr:jnr-a64asm:jar:1.0.0:compile [INFO] | \- com.github.jnr:jnr-x86asm:jar:1.0.2:compile This commit removes the explicit JFFI dependencies, instead relying on their transitive provision via JNR-FFI. The revised dependency graph: $ mvn dependency:tree | grep jnr [INFO] +- com.github.jnr:jnr-constants:jar:0.9.15:compile [INFO] +- com.github.jnr:jnr-ffi:jar:2.1.15:compile [INFO] | +- com.github.jnr:jffi:jar:1.2.23:compile [INFO] | +- com.github.jnr:jffi:jar:native:1.2.23:runtime [INFO] | +- com.github.jnr:jnr-a64asm:jar:1.0.0:compile [INFO] | \- com.github.jnr:jnr-x86asm:jar:1.0.2:compile --- pom.xml | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/pom.xml b/pom.xml index a9ca7a72..6ab61086 100644 --- a/pom.xml +++ b/pom.xml @@ -18,18 +18,6 @@ apache_v2 - - com.github.jnr - jffi - 1.2.23 - - - com.github.jnr - jffi - 1.2.23 - native - runtime - com.github.jnr jnr-constants @@ -39,12 +27,6 @@ com.github.jnr jnr-ffi 2.1.15 - - - com.github.jnr - jffi - - com.google.code.findbugs From 148279b89e2268d23a07d36b7267a6dfe8db5909 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Tue, 2 Jun 2020 11:50:19 +1000 Subject: [PATCH 102/322] Update to Acegi Standard Project 0.5.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6ab61086..b21598b9 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ au.com.acegi acegi-standard-project - 0.5.0 + 0.5.1 org.lmdbjava lmdbjava From 9041754ad1b1e4993bccdf59fe342160d1af697d Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Tue, 2 Jun 2020 13:21:21 +1000 Subject: [PATCH 103/322] Update to Acegi Standard Project 0.5.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b21598b9..0e06b964 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ au.com.acegi acegi-standard-project - 0.5.1 + 0.5.2 org.lmdbjava lmdbjava From 0bccfdddf4bb0a145e0bdcf5c7591b32687a45ee Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Tue, 2 Jun 2020 14:44:56 +1000 Subject: [PATCH 104/322] [maven-release-plugin] prepare release lmdbjava-0.8.0 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 0e06b964..4210e1cd 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ org.lmdbjava lmdbjava - 0.8.0-SNAPSHOT + 0.8.0 jar LmdbJava Low latency Java API for the ultra-fast, embedded Symas Lightning Database (LMDB) @@ -206,7 +206,7 @@ scm:git:git@github.com:${github.org}/${github.repo}.git scm:git:git@github.com:${github.org}/${github.repo}.git git@github.com:${github.org}/${github.repo}.git - HEAD + lmdbjava-0.8.0 GitHub Issues From 24f136b74d1aa2e6064fb8038f1c5f01a7f34732 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Tue, 2 Jun 2020 14:45:09 +1000 Subject: [PATCH 105/322] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 4210e1cd..1d7b0577 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ org.lmdbjava lmdbjava - 0.8.0 + 0.8.1-SNAPSHOT jar LmdbJava Low latency Java API for the ultra-fast, embedded Symas Lightning Database (LMDB) @@ -206,7 +206,7 @@ scm:git:git@github.com:${github.org}/${github.repo}.git scm:git:git@github.com:${github.org}/${github.repo}.git git@github.com:${github.org}/${github.repo}.git - lmdbjava-0.8.0 + HEAD GitHub Issues From 2f8527b6e6510c92f80e14b9a8ddaab914ad7e87 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Wed, 3 Jun 2020 11:46:25 +1000 Subject: [PATCH 106/322] Build with Java 8 due to ByteBuffer NoSuchMethodError (#116, #158) As detailed at https://github.com/lmdbjava/lmdbjava/issues/116, Java 9 introduced overridden methods with covariant return types in ByteBuffer. The consequence is that Java 8 compatibility is problematic if compiling with Java 9 or above. This commit therefore performs the deployment step using Java 8. The fuller verification step is still completed on Java 11. --- .github/workflows/maven.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index c8ef2d4a..5ec25a8b 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -89,7 +89,8 @@ jobs: - name: Set up Java and Maven uses: actions/setup-java@v1 with: - java-version: 11 + # Java version 8 required due to https://github.com/lmdbjava/lmdbjava/issues/116 + java-version: 8 - name: Add OSSRH to the snapshot repositories list uses: s4u/maven-settings-action@v2.1.0 From d7d89884bdc2922e95a3b6aac37aeb5b789e7963 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Thu, 4 Jun 2020 09:47:34 +1000 Subject: [PATCH 107/322] [maven-release-plugin] prepare release lmdbjava-0.8.1 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 1d7b0577..fe02c7fd 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ org.lmdbjava lmdbjava - 0.8.1-SNAPSHOT + 0.8.1 jar LmdbJava Low latency Java API for the ultra-fast, embedded Symas Lightning Database (LMDB) @@ -206,7 +206,7 @@ scm:git:git@github.com:${github.org}/${github.repo}.git scm:git:git@github.com:${github.org}/${github.repo}.git git@github.com:${github.org}/${github.repo}.git - HEAD + lmdbjava-0.8.1 GitHub Issues From 26d76be888c11d1f645f9c6902954c348e474f79 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Thu, 4 Jun 2020 09:47:46 +1000 Subject: [PATCH 108/322] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index fe02c7fd..1e8874a0 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ org.lmdbjava lmdbjava - 0.8.1 + 0.8.2-SNAPSHOT jar LmdbJava Low latency Java API for the ultra-fast, embedded Symas Lightning Database (LMDB) @@ -206,7 +206,7 @@ scm:git:git@github.com:${github.org}/${github.repo}.git scm:git:git@github.com:${github.org}/${github.repo}.git git@github.com:${github.org}/${github.repo}.git - lmdbjava-0.8.1 + HEAD GitHub Issues From cb19f5919d9200ff4cea7696fefe5a3aef094423 Mon Sep 17 00:00:00 2001 From: Maithem Date: Tue, 18 Aug 2020 01:23:15 -0700 Subject: [PATCH 109/322] Support User Defined PooledByteBufAllocator This patch allows users to pass a PooledByteBufAllocator instance if they wish to not use the DEFAULT instance. Also, this patch fixes a minor bug where a ByteBuf is created during initialization without ever being released. --- src/main/java/org/lmdbjava/ByteBufProxy.java | 64 ++++++++------------ 1 file changed, 24 insertions(+), 40 deletions(-) diff --git a/src/main/java/org/lmdbjava/ByteBufProxy.java b/src/main/java/org/lmdbjava/ByteBufProxy.java index ca580363..271c4e2f 100644 --- a/src/main/java/org/lmdbjava/ByteBufProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufProxy.java @@ -22,13 +22,12 @@ import static io.netty.buffer.PooledByteBufAllocator.DEFAULT; import static java.lang.Class.forName; -import static java.lang.ThreadLocal.withInitial; import static org.lmdbjava.UnsafeAccess.UNSAFE; import java.lang.reflect.Field; -import java.util.ArrayDeque; import io.netty.buffer.ByteBuf; +import io.netty.buffer.PooledByteBufAllocator; import jnr.ffi.Pointer; /** @@ -47,37 +46,34 @@ public final class ByteBufProxy extends BufferProxy { */ public static final BufferProxy PROXY_NETTY = new ByteBufProxy(); - private static final long ADDRESS_OFFSET; - - /** - * A thread-safe pool for a given length. If the buffer found is bigger then - * the buffer in the pool creates a new buffer. If no buffer is found creates - * a new buffer. - */ - private static final ThreadLocal> BUFFERS = withInitial(() - -> new ArrayDeque<>(16)); - 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 long LENGTH_OFFSET; private static final String NAME = "io.netty.buffer.PooledUnsafeDirectByteBuf"; + private final long lengthOffset; + private final long addressOffset; + + private final PooledByteBufAllocator nettyAllocator; + + private ByteBufProxy() { + this(DEFAULT); + } + + public ByteBufProxy(final PooledByteBufAllocator allocator) { + this.nettyAllocator = allocator; - static { try { - createBuffer(); + final ByteBuf initBuf = this.allocate(); + initBuf.release(); final Field address = findField(NAME, FIELD_NAME_ADDRESS); final Field length = findField(NAME, FIELD_NAME_LENGTH); - ADDRESS_OFFSET = UNSAFE.objectFieldOffset(address); - LENGTH_OFFSET = UNSAFE.objectFieldOffset(length); + addressOffset = UNSAFE.objectFieldOffset(address); + lengthOffset = UNSAFE.objectFieldOffset(length); } catch (final SecurityException e) { throw new LmdbException("Field access error", e); } } - private ByteBufProxy() { - } - static Field findField(final String c, final String name) { Class clazz; try { @@ -97,28 +93,19 @@ static Field findField(final String c, final String name) { throw new LmdbException(name + " not found"); } - private static ByteBuf createBuffer() { + @Override + protected ByteBuf allocate() { for (int i = 0; i < BUFFER_RETRIES; i++) { - final ByteBuf bb = DEFAULT.directBuffer(0); + final ByteBuf bb = nettyAllocator.directBuffer(); if (NAME.equals(bb.getClass().getName())) { return bb; + } else { + bb.release(); } } throw new IllegalStateException("Netty buffer must be " + NAME); } - @Override - protected ByteBuf allocate() { - final ArrayDeque queue = BUFFERS.get(); - final ByteBuf buffer = queue.poll(); - - if (buffer != null && buffer.capacity() >= 0) { - return buffer; - } else { - return createBuffer(); - } - } - @Override protected int compare(final ByteBuf o1, final ByteBuf o2) { return o1.compareTo(o2); @@ -126,10 +113,7 @@ protected int compare(final ByteBuf o1, final ByteBuf o2) { @Override protected void deallocate(final ByteBuf buff) { - final ArrayDeque queue = BUFFERS.get(); - if (!queue.offer(buff)) { - buff.release(); - } + buff.release(); } @Override @@ -161,8 +145,8 @@ 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, ADDRESS_OFFSET, addr); - UNSAFE.putInt(buffer, LENGTH_OFFSET, (int) size); + UNSAFE.putLong(buffer, addressOffset, addr); + UNSAFE.putInt(buffer, lengthOffset, (int) size); buffer.writerIndex((int) size).readerIndex(0); return buffer; } From fc0bdb469137ac9d1b41481ea22659d8fbafbecb Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 12 Mar 2021 11:52:10 +1100 Subject: [PATCH 110/322] Copyright year --- src/main/java/org/lmdbjava/BufferProxy.java | 2 +- src/main/java/org/lmdbjava/ByteArrayProxy.java | 2 +- src/main/java/org/lmdbjava/ByteBufProxy.java | 2 +- src/main/java/org/lmdbjava/ByteBufferProxy.java | 2 +- src/main/java/org/lmdbjava/CopyFlags.java | 2 +- src/main/java/org/lmdbjava/Cursor.java | 2 +- src/main/java/org/lmdbjava/CursorIterable.java | 2 +- src/main/java/org/lmdbjava/Dbi.java | 2 +- src/main/java/org/lmdbjava/DbiFlags.java | 2 +- src/main/java/org/lmdbjava/DirectBufferProxy.java | 2 +- src/main/java/org/lmdbjava/Env.java | 2 +- src/main/java/org/lmdbjava/EnvFlags.java | 2 +- src/main/java/org/lmdbjava/EnvInfo.java | 2 +- src/main/java/org/lmdbjava/GetOp.java | 2 +- src/main/java/org/lmdbjava/KeyRange.java | 2 +- src/main/java/org/lmdbjava/KeyRangeType.java | 2 +- src/main/java/org/lmdbjava/KeyVal.java | 2 +- src/main/java/org/lmdbjava/Library.java | 2 +- src/main/java/org/lmdbjava/LmdbException.java | 2 +- src/main/java/org/lmdbjava/LmdbNativeException.java | 2 +- src/main/java/org/lmdbjava/MaskedFlag.java | 2 +- src/main/java/org/lmdbjava/Meta.java | 2 +- src/main/java/org/lmdbjava/PutFlags.java | 2 +- src/main/java/org/lmdbjava/ResultCodeMapper.java | 2 +- src/main/java/org/lmdbjava/SeekOp.java | 2 +- src/main/java/org/lmdbjava/Stat.java | 2 +- src/main/java/org/lmdbjava/Txn.java | 2 +- src/main/java/org/lmdbjava/TxnFlags.java | 2 +- src/main/java/org/lmdbjava/UnsafeAccess.java | 2 +- src/main/java/org/lmdbjava/Verifier.java | 2 +- src/main/java/org/lmdbjava/package-info.java | 2 +- src/test/java/org/lmdbjava/ByteBufferProxyTest.java | 2 +- src/test/java/org/lmdbjava/ComparatorTest.java | 2 +- src/test/java/org/lmdbjava/CursorIterableTest.java | 2 +- src/test/java/org/lmdbjava/CursorParamTest.java | 2 +- src/test/java/org/lmdbjava/CursorTest.java | 2 +- src/test/java/org/lmdbjava/DbiTest.java | 2 +- src/test/java/org/lmdbjava/EnvTest.java | 2 +- src/test/java/org/lmdbjava/KeyRangeTest.java | 2 +- src/test/java/org/lmdbjava/LibraryTest.java | 2 +- src/test/java/org/lmdbjava/MaskedFlagTest.java | 2 +- src/test/java/org/lmdbjava/MetaTest.java | 2 +- src/test/java/org/lmdbjava/ResultCodeMapperTest.java | 2 +- src/test/java/org/lmdbjava/TestUtils.java | 2 +- src/test/java/org/lmdbjava/TutorialTest.java | 2 +- src/test/java/org/lmdbjava/TxnTest.java | 2 +- src/test/java/org/lmdbjava/VerifierTest.java | 2 +- src/test/java/org/lmdbjava/package-info.java | 2 +- 48 files changed, 48 insertions(+), 48 deletions(-) diff --git a/src/main/java/org/lmdbjava/BufferProxy.java b/src/main/java/org/lmdbjava/BufferProxy.java index 0fe73f3f..dc7aaadd 100644 --- a/src/main/java/org/lmdbjava/BufferProxy.java +++ b/src/main/java/org/lmdbjava/BufferProxy.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/ByteArrayProxy.java b/src/main/java/org/lmdbjava/ByteArrayProxy.java index dc964bcc..0bc05376 100644 --- a/src/main/java/org/lmdbjava/ByteArrayProxy.java +++ b/src/main/java/org/lmdbjava/ByteArrayProxy.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/ByteBufProxy.java b/src/main/java/org/lmdbjava/ByteBufProxy.java index 271c4e2f..54d8a44b 100644 --- a/src/main/java/org/lmdbjava/ByteBufProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufProxy.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index 9d1b0f81..6daa5c16 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/CopyFlags.java b/src/main/java/org/lmdbjava/CopyFlags.java index d85ffb06..685df3c3 100644 --- a/src/main/java/org/lmdbjava/CopyFlags.java +++ b/src/main/java/org/lmdbjava/CopyFlags.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/Cursor.java b/src/main/java/org/lmdbjava/Cursor.java index 5dac5b58..f165423b 100644 --- a/src/main/java/org/lmdbjava/Cursor.java +++ b/src/main/java/org/lmdbjava/Cursor.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/CursorIterable.java b/src/main/java/org/lmdbjava/CursorIterable.java index 19729443..5bd86272 100644 --- a/src/main/java/org/lmdbjava/CursorIterable.java +++ b/src/main/java/org/lmdbjava/CursorIterable.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index 62fbc0f2..7ccb1940 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/DbiFlags.java b/src/main/java/org/lmdbjava/DbiFlags.java index 0f9573dd..12ea80f0 100644 --- a/src/main/java/org/lmdbjava/DbiFlags.java +++ b/src/main/java/org/lmdbjava/DbiFlags.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/DirectBufferProxy.java b/src/main/java/org/lmdbjava/DirectBufferProxy.java index af911afa..39911515 100644 --- a/src/main/java/org/lmdbjava/DirectBufferProxy.java +++ b/src/main/java/org/lmdbjava/DirectBufferProxy.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index 7634a342..326763f6 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/EnvFlags.java b/src/main/java/org/lmdbjava/EnvFlags.java index b8883706..a713547d 100644 --- a/src/main/java/org/lmdbjava/EnvFlags.java +++ b/src/main/java/org/lmdbjava/EnvFlags.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/EnvInfo.java b/src/main/java/org/lmdbjava/EnvInfo.java index fe16e92b..de21edd0 100644 --- a/src/main/java/org/lmdbjava/EnvInfo.java +++ b/src/main/java/org/lmdbjava/EnvInfo.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/GetOp.java b/src/main/java/org/lmdbjava/GetOp.java index 82550f82..fa600ebd 100644 --- a/src/main/java/org/lmdbjava/GetOp.java +++ b/src/main/java/org/lmdbjava/GetOp.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/KeyRange.java b/src/main/java/org/lmdbjava/KeyRange.java index c3e35864..61d9e434 100644 --- a/src/main/java/org/lmdbjava/KeyRange.java +++ b/src/main/java/org/lmdbjava/KeyRange.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/KeyRangeType.java b/src/main/java/org/lmdbjava/KeyRangeType.java index 156daf99..d92ec054 100644 --- a/src/main/java/org/lmdbjava/KeyRangeType.java +++ b/src/main/java/org/lmdbjava/KeyRangeType.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/KeyVal.java b/src/main/java/org/lmdbjava/KeyVal.java index 481d34ed..0da61a8e 100644 --- a/src/main/java/org/lmdbjava/KeyVal.java +++ b/src/main/java/org/lmdbjava/KeyVal.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/Library.java b/src/main/java/org/lmdbjava/Library.java index 18d3e717..fc54dda0 100644 --- a/src/main/java/org/lmdbjava/Library.java +++ b/src/main/java/org/lmdbjava/Library.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/LmdbException.java b/src/main/java/org/lmdbjava/LmdbException.java index e6004f19..92ff72e4 100644 --- a/src/main/java/org/lmdbjava/LmdbException.java +++ b/src/main/java/org/lmdbjava/LmdbException.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/LmdbNativeException.java b/src/main/java/org/lmdbjava/LmdbNativeException.java index 81b46d15..1bef719d 100644 --- a/src/main/java/org/lmdbjava/LmdbNativeException.java +++ b/src/main/java/org/lmdbjava/LmdbNativeException.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/MaskedFlag.java b/src/main/java/org/lmdbjava/MaskedFlag.java index 21dde5a1..dfd6df16 100644 --- a/src/main/java/org/lmdbjava/MaskedFlag.java +++ b/src/main/java/org/lmdbjava/MaskedFlag.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/Meta.java b/src/main/java/org/lmdbjava/Meta.java index 699b182d..b157b950 100644 --- a/src/main/java/org/lmdbjava/Meta.java +++ b/src/main/java/org/lmdbjava/Meta.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/PutFlags.java b/src/main/java/org/lmdbjava/PutFlags.java index 87bdacd9..58589e68 100644 --- a/src/main/java/org/lmdbjava/PutFlags.java +++ b/src/main/java/org/lmdbjava/PutFlags.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/ResultCodeMapper.java b/src/main/java/org/lmdbjava/ResultCodeMapper.java index ed209f41..0c076bc7 100644 --- a/src/main/java/org/lmdbjava/ResultCodeMapper.java +++ b/src/main/java/org/lmdbjava/ResultCodeMapper.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/SeekOp.java b/src/main/java/org/lmdbjava/SeekOp.java index 1d214adb..829458a9 100644 --- a/src/main/java/org/lmdbjava/SeekOp.java +++ b/src/main/java/org/lmdbjava/SeekOp.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/Stat.java b/src/main/java/org/lmdbjava/Stat.java index de43577e..66480a08 100644 --- a/src/main/java/org/lmdbjava/Stat.java +++ b/src/main/java/org/lmdbjava/Stat.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/Txn.java b/src/main/java/org/lmdbjava/Txn.java index dfdfc60a..36f8eaf9 100644 --- a/src/main/java/org/lmdbjava/Txn.java +++ b/src/main/java/org/lmdbjava/Txn.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/TxnFlags.java b/src/main/java/org/lmdbjava/TxnFlags.java index a4cf3e8d..906170ba 100644 --- a/src/main/java/org/lmdbjava/TxnFlags.java +++ b/src/main/java/org/lmdbjava/TxnFlags.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/UnsafeAccess.java b/src/main/java/org/lmdbjava/UnsafeAccess.java index 7114e34d..8a853429 100644 --- a/src/main/java/org/lmdbjava/UnsafeAccess.java +++ b/src/main/java/org/lmdbjava/UnsafeAccess.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/Verifier.java b/src/main/java/org/lmdbjava/Verifier.java index 9bc7b89e..0674a069 100644 --- a/src/main/java/org/lmdbjava/Verifier.java +++ b/src/main/java/org/lmdbjava/Verifier.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/main/java/org/lmdbjava/package-info.java b/src/main/java/org/lmdbjava/package-info.java index 05e35587..aa1b4570 100644 --- a/src/main/java/org/lmdbjava/package-info.java +++ b/src/main/java/org/lmdbjava/package-info.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java index ba882281..19b6aba5 100644 --- a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java +++ b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/test/java/org/lmdbjava/ComparatorTest.java b/src/test/java/org/lmdbjava/ComparatorTest.java index e8499838..23b2a80b 100644 --- a/src/test/java/org/lmdbjava/ComparatorTest.java +++ b/src/test/java/org/lmdbjava/ComparatorTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index cc6cd0b6..05819835 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/test/java/org/lmdbjava/CursorParamTest.java b/src/test/java/org/lmdbjava/CursorParamTest.java index c105aae7..6cac2be5 100644 --- a/src/test/java/org/lmdbjava/CursorParamTest.java +++ b/src/test/java/org/lmdbjava/CursorParamTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/test/java/org/lmdbjava/CursorTest.java b/src/test/java/org/lmdbjava/CursorTest.java index 137732f5..373b68e9 100644 --- a/src/test/java/org/lmdbjava/CursorTest.java +++ b/src/test/java/org/lmdbjava/CursorTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index b9bf453c..0c897268 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/test/java/org/lmdbjava/EnvTest.java b/src/test/java/org/lmdbjava/EnvTest.java index 3e9ece95..bd36f2f2 100644 --- a/src/test/java/org/lmdbjava/EnvTest.java +++ b/src/test/java/org/lmdbjava/EnvTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/test/java/org/lmdbjava/KeyRangeTest.java b/src/test/java/org/lmdbjava/KeyRangeTest.java index 0cb3d1f5..8d0077ae 100644 --- a/src/test/java/org/lmdbjava/KeyRangeTest.java +++ b/src/test/java/org/lmdbjava/KeyRangeTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/test/java/org/lmdbjava/LibraryTest.java b/src/test/java/org/lmdbjava/LibraryTest.java index 08c6ea9f..9ac57fba 100644 --- a/src/test/java/org/lmdbjava/LibraryTest.java +++ b/src/test/java/org/lmdbjava/LibraryTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/test/java/org/lmdbjava/MaskedFlagTest.java b/src/test/java/org/lmdbjava/MaskedFlagTest.java index 34acb3f8..d28666ea 100644 --- a/src/test/java/org/lmdbjava/MaskedFlagTest.java +++ b/src/test/java/org/lmdbjava/MaskedFlagTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/test/java/org/lmdbjava/MetaTest.java b/src/test/java/org/lmdbjava/MetaTest.java index d20f6510..3df23f41 100644 --- a/src/test/java/org/lmdbjava/MetaTest.java +++ b/src/test/java/org/lmdbjava/MetaTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/test/java/org/lmdbjava/ResultCodeMapperTest.java b/src/test/java/org/lmdbjava/ResultCodeMapperTest.java index f2382021..37d6e166 100644 --- a/src/test/java/org/lmdbjava/ResultCodeMapperTest.java +++ b/src/test/java/org/lmdbjava/ResultCodeMapperTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/test/java/org/lmdbjava/TestUtils.java b/src/test/java/org/lmdbjava/TestUtils.java index 1de8218e..6ab21584 100644 --- a/src/test/java/org/lmdbjava/TestUtils.java +++ b/src/test/java/org/lmdbjava/TestUtils.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/test/java/org/lmdbjava/TutorialTest.java b/src/test/java/org/lmdbjava/TutorialTest.java index 61154ea3..b92582d0 100644 --- a/src/test/java/org/lmdbjava/TutorialTest.java +++ b/src/test/java/org/lmdbjava/TutorialTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/test/java/org/lmdbjava/TxnTest.java b/src/test/java/org/lmdbjava/TxnTest.java index dda4694e..69f0815f 100644 --- a/src/test/java/org/lmdbjava/TxnTest.java +++ b/src/test/java/org/lmdbjava/TxnTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/test/java/org/lmdbjava/VerifierTest.java b/src/test/java/org/lmdbjava/VerifierTest.java index 16f228d2..4a4c487a 100644 --- a/src/test/java/org/lmdbjava/VerifierTest.java +++ b/src/test/java/org/lmdbjava/VerifierTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. diff --git a/src/test/java/org/lmdbjava/package-info.java b/src/test/java/org/lmdbjava/package-info.java index caa25a12..042d4e65 100644 --- a/src/test/java/org/lmdbjava/package-info.java +++ b/src/test/java/org/lmdbjava/package-info.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2020 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2021 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. From 18fbd77aebedebcf78bec008528418249a2fc2a3 Mon Sep 17 00:00:00 2001 From: Huahai Yang Date: Thu, 11 Mar 2021 16:54:28 -0800 Subject: [PATCH 111/322] fix a typo in ByteBufferProxy (#172) --- src/main/java/org/lmdbjava/ByteBufferProxy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index 6daa5c16..37baf656 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -133,7 +133,7 @@ public static int compareBuff(final ByteBuffer o1, final ByteBuffer o2) { final int minWords = minLength / Long.BYTES; final boolean reverse1 = o1.order() == LITTLE_ENDIAN; - final boolean reverse2 = 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); From 823062383313dc075bf895ea7762cfa35170f376 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Fri, 12 Mar 2021 00:58:54 +0000 Subject: [PATCH 112/322] Fix AbstractByteBufferProxy.compareBuff and Improve ComparatorTest (PR #170) (fixes #169) --- .../java/org/lmdbjava/ByteBufferProxy.java | 2 +- .../java/org/lmdbjava/ComparatorTest.java | 36 +++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index 37baf656..2dd337c0 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -152,7 +152,7 @@ public static int compareBuff(final ByteBuffer o1, final ByteBuffer o2) { } } - return o1.capacity() - o2.capacity(); + return o1.remaining() - o2.remaining(); } static Field findField(final Class c, final String name) { diff --git a/src/test/java/org/lmdbjava/ComparatorTest.java b/src/test/java/org/lmdbjava/ComparatorTest.java index 23b2a80b..a9aa920e 100644 --- a/src/test/java/org/lmdbjava/ComparatorTest.java +++ b/src/test/java/org/lmdbjava/ComparatorTest.java @@ -33,6 +33,7 @@ import static org.lmdbjava.DirectBufferProxy.PROXY_DB; import java.nio.ByteBuffer; +import java.util.Arrays; import java.util.Comparator; import com.google.common.primitives.SignedBytes; @@ -156,9 +157,38 @@ private static class ByteBufferRunner implements ComparatorRunner { @Override public int compare(final byte[] o1, final byte[] o2) { - final ByteBuffer o1b = ByteBuffer.wrap(o1); - final ByteBuffer o2b = ByteBuffer.wrap(o2); - return PROXY_OPTIMAL.compare(o1b, o2b); + // Convert arrays to buffers that are larger than the array, with + // limit set at the array length. One buffer bigger than the other. + ByteBuffer o1b = arrayToBuffer(o1, o1.length * 3); + ByteBuffer o2b = arrayToBuffer(o2, o2.length * 2); + final int result = PROXY_OPTIMAL.compare(o1b, o2b); + + // Now swap which buffer is bigger + o1b = arrayToBuffer(o1, o1.length * 2); + o2b = arrayToBuffer(o2, o2.length * 3); + final int result2 = PROXY_OPTIMAL.compare(o1b, o2b); + + assertThat(result2, is(result)); + + // Now try with buffers sized to the array. + o1b = ByteBuffer.wrap(o1); + o2b = ByteBuffer.wrap(o2); + final int result3 = PROXY_OPTIMAL.compare(o1b, o2b); + + assertThat(result3, is(result)); + + return result; + } + + private ByteBuffer arrayToBuffer(final byte[] arr, final int bufferCapacity) { + if (bufferCapacity < arr.length) { + throw new IllegalArgumentException("bufferCapacity < arr.length"); + } + final byte[] newArr = Arrays.copyOf(arr, bufferCapacity); + final ByteBuffer byteBuffer = ByteBuffer.wrap(newArr); + byteBuffer.limit(arr.length); + byteBuffer.position(0); + return byteBuffer; } } From 958eb115be4e6f6c6a00ed7cb638efb000f2bbaa Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Sat, 13 Mar 2021 02:16:59 +0000 Subject: [PATCH 113/322] Update to JNR-FFI to 2.2.2 (PR #175, Issue #174) * lmdbjava/lmdbjava#174 Uplift jnr-ffi to v2.2.2 * lmdbjava/lmdbjava#174 Add java 15 to github workflows matrix --- .github/workflows/maven.yml | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 5ec25a8b..4112d072 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -44,7 +44,7 @@ jobs: strategy: matrix: os: [ubuntu-16.04, ubuntu-latest, macos-latest, windows-latest] - java: [8, 11, 14] + java: [8, 11, 14, 15] steps: - name: Check out Git repository diff --git a/pom.xml b/pom.xml index 1e8874a0..ff9d4895 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ com.github.jnr jnr-ffi - 2.1.15 + 2.2.2 com.google.code.findbugs From dfba4dabfe68d335ec6c5126900e082deb8d3f24 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 2 Aug 2021 16:36:40 +1000 Subject: [PATCH 114/322] Add JVM options required to test under Java 16 --- pom.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pom.xml b/pom.xml index ff9d4895..de4d3558 100644 --- a/pom.xml +++ b/pom.xml @@ -151,6 +151,13 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED + + org.basepom.maven duplicate-finder-maven-plugin From 29a1543329b6b4f2cc54c519c26236e56095c52a Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 2 Aug 2021 16:37:08 +1000 Subject: [PATCH 115/322] Minor adjustments so FindBugs passes the build --- src/main/java/org/lmdbjava/Verifier.java | 4 ++++ src/test/java/org/lmdbjava/DbiTest.java | 4 +--- src/test/java/org/lmdbjava/EnvTest.java | 3 +++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/lmdbjava/Verifier.java b/src/main/java/org/lmdbjava/Verifier.java index 0674a069..05e5eb83 100644 --- a/src/main/java/org/lmdbjava/Verifier.java +++ b/src/main/java/org/lmdbjava/Verifier.java @@ -37,6 +37,8 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.zip.CRC32; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + /** * Verifies correct operation of LmdbJava in a given environment. * @@ -112,6 +114,7 @@ public final class Verifier implements Callable { * * @param env target that complies with the above requirements (required) */ + @SuppressFBWarnings("EI_EXPOSE_REP2") public Verifier(final Env env) { requireNonNull(env); this.env = env; @@ -237,6 +240,7 @@ private void stop() { proceed.set(false); } + @SuppressFBWarnings("DMI_RANDOM_USED_ONLY_ONCE") private void transactionControl() { if (id % BATCH_SIZE == 0) { if (txn != null) { diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 0c897268..689eab58 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -61,7 +61,6 @@ import java.util.Comparator; import java.util.Iterator; import java.util.List; -import java.util.Random; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -487,9 +486,8 @@ public void testParallelWritesStress() { // Travis CI has 1.5 cores for legacy builds nCopies(2, null).parallelStream() .forEach(ignored -> { - final Random random = new Random(); for (int i = 0; i < 15_000; i++) { - db.put(bb(random.nextInt()), bb(random.nextInt())); + db.put(bb(i), bb(i)); } }); } diff --git a/src/test/java/org/lmdbjava/EnvTest.java b/src/test/java/org/lmdbjava/EnvTest.java index bd36f2f2..bf766ff7 100644 --- a/src/test/java/org/lmdbjava/EnvTest.java +++ b/src/test/java/org/lmdbjava/EnvTest.java @@ -42,6 +42,7 @@ import java.nio.ByteBuffer; import java.util.Random; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; @@ -286,6 +287,7 @@ public void info() throws IOException { } @Test(expected = MapFullException.class) + @SuppressFBWarnings("DMI_RANDOM_USED_ONLY_ONCE") public void mapFull() throws IOException { final File path = tmp.newFolder(); final byte[] k = new byte[500]; @@ -327,6 +329,7 @@ public void readOnlySupported() throws IOException { } @Test + @SuppressFBWarnings("DMI_RANDOM_USED_ONLY_ONCE") public void setMapSize() throws IOException { final File path = tmp.newFolder(); final byte[] k = new byte[500]; From 0282cac96ae751ddffd4aeaeaeff88c04e954203 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 2 Aug 2021 16:40:53 +1000 Subject: [PATCH 116/322] Suppress PMD.CompareObjectsWithEquals warning --- src/main/java/org/lmdbjava/ByteArrayProxy.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/lmdbjava/ByteArrayProxy.java b/src/main/java/org/lmdbjava/ByteArrayProxy.java index 0bc05376..2b72a833 100644 --- a/src/main/java/org/lmdbjava/ByteArrayProxy.java +++ b/src/main/java/org/lmdbjava/ByteArrayProxy.java @@ -52,6 +52,7 @@ private ByteArrayProxy() { * @param o2 right operand (required) * @return as specified by {@link Comparable} interface */ + @SuppressWarnings("PMD.CompareObjectsWithEquals") public static int compareArrays(final byte[] o1, final byte[] o2) { requireNonNull(o1); requireNonNull(o2); From b3bdcef13872504c200e0416ab94bda2056f8912 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 2 Aug 2021 16:41:49 +1000 Subject: [PATCH 117/322] Update to Agrona 1.11.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index de4d3558..621ca6d2 100644 --- a/pom.xml +++ b/pom.xml @@ -63,7 +63,7 @@ org.agrona agrona - 1.5.1 + 1.11.0 true From 70cd28614cc024dd2be01a2e8cdc9618de94b32c Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 2 Aug 2021 16:42:06 +1000 Subject: [PATCH 118/322] Update to Netty 4.1.66 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 621ca6d2..89503468 100644 --- a/pom.xml +++ b/pom.xml @@ -53,7 +53,7 @@ io.netty netty-all - 4.1.50.Final + 4.1.66.Final true From ab59ac5af4fa873bb31ec079d50209a7fe95e24c Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 2 Aug 2021 16:51:06 +1000 Subject: [PATCH 119/322] Remove Ubuntu 16.04 from test matrix as ubuntu-latest is sufficient https://github.blog/changelog/2020-10-29-github-actions-ubuntu-latest-workflows-will-use-ubuntu-20-04/ --- .github/workflows/maven.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 4112d072..c5b57c99 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -43,7 +43,7 @@ jobs: strategy: matrix: - os: [ubuntu-16.04, ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest, macos-latest, windows-latest] java: [8, 11, 14, 15] steps: From 656fd671235f468ad7a26f15590589d179bc9c1c Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 2 Aug 2021 16:54:20 +1000 Subject: [PATCH 120/322] Test on Java LTS versions 8, 11 and 16 --- .github/workflows/maven.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index c5b57c99..a272d491 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -44,7 +44,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - java: [8, 11, 14, 15] + java: [8, 11, 16] steps: - name: Check out Git repository diff --git a/README.md b/README.md index 0945af36..5abc21c0 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ * Mature code (commenced in 2016) and used for heavy production workloads (eg > 500 TB of HFT data) * Actively maintained and with a "Zero Bug Policy" before every release (see [issues](https://github.com/lmdbjava/lmdbjava/issues)) * Available from [Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.lmdbjava%22%20AND%20a%3A%22lmdbjava%22) and [OSS Sonatype Snapshots](https://oss.sonatype.org/content/repositories/snapshots/org/lmdbjava/lmdbjava) -* [Continuous integration](https://github.com/lmdbjava/lmdbjava/actions) testing on Linux, Windows and macOS with Java 8, 11 and 14 +* [Continuous integration](https://github.com/lmdbjava/lmdbjava/actions) testing on Linux, Windows and macOS with Java 8, 11 and 16 ### Performance From 5687bd61ff8de770800fa319b4b98a0f61a92b24 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 2 Aug 2021 16:57:07 +1000 Subject: [PATCH 121/322] Activate Surefire JVM 9+ options via a Maven profile --- pom.xml | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 89503468..a5ca1d50 100644 --- a/pom.xml +++ b/pom.xml @@ -151,13 +151,6 @@ - - org.apache.maven.plugins - maven-surefire-plugin - - --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED - - org.basepom.maven duplicate-finder-maven-plugin @@ -223,4 +216,23 @@ GitHub Actions https://github.com/${github.org}/${github.repo}/actions + + + add-opens-on-java-9-and-above + + [1.9 + + + + + org.apache.maven.plugins + maven-surefire-plugin + + --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED + + + + + + From a7c38f5bb14aecf7666805148962d069dd0cbce5 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 2 Aug 2021 17:14:47 +1000 Subject: [PATCH 122/322] Update to Guava 30.1.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a5ca1d50..a110b527 100644 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,7 @@ com.google.guava guava - 29.0-jre + 30.1.1-jre test From f28d1b196114795570cfd047f4fd1668eaeaae7b Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Wed, 4 Aug 2021 18:37:33 +1000 Subject: [PATCH 123/322] Update to Acegi Standard Project 0.6.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a110b527..916340df 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ au.com.acegi acegi-standard-project - 0.5.2 + 0.6.1 org.lmdbjava lmdbjava From 3666782e1dc6efee2745d3758fe071beef0825ed Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 24 Sep 2021 09:45:56 +1000 Subject: [PATCH 124/322] [maven-release-plugin] prepare release lmdbjava-0.8.2 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 916340df..2c817035 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ org.lmdbjava lmdbjava - 0.8.2-SNAPSHOT + 0.8.2 jar LmdbJava Low latency Java API for the ultra-fast, embedded Symas Lightning Database (LMDB) @@ -206,7 +206,7 @@ scm:git:git@github.com:${github.org}/${github.repo}.git scm:git:git@github.com:${github.org}/${github.repo}.git git@github.com:${github.org}/${github.repo}.git - HEAD + lmdbjava-0.8.2 GitHub Issues From 3d95bf1a81e90b14b8db5e8a0f79d52d5249b248 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 24 Sep 2021 09:46:02 +1000 Subject: [PATCH 125/322] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 2c817035..ce6eeb21 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ org.lmdbjava lmdbjava - 0.8.2 + 0.8.3-SNAPSHOT jar LmdbJava Low latency Java API for the ultra-fast, embedded Symas Lightning Database (LMDB) @@ -206,7 +206,7 @@ scm:git:git@github.com:${github.org}/${github.repo}.git scm:git:git@github.com:${github.org}/${github.repo}.git git@github.com:${github.org}/${github.repo}.git - lmdbjava-0.8.2 + HEAD GitHub Issues From 1d991f8e4b0e19fc003f8a093aeaee37ea3ecf72 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sun, 24 Oct 2021 14:12:20 +1100 Subject: [PATCH 126/322] Support Netty ByteBuf variable sized data (#183) --- src/main/java/org/lmdbjava/ByteBufProxy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/lmdbjava/ByteBufProxy.java b/src/main/java/org/lmdbjava/ByteBufProxy.java index 54d8a44b..51fe0617 100644 --- a/src/main/java/org/lmdbjava/ByteBufProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufProxy.java @@ -147,7 +147,7 @@ protected ByteBuf out(final ByteBuf buffer, final Pointer ptr, final long size = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE); UNSAFE.putLong(buffer, addressOffset, addr); UNSAFE.putInt(buffer, lengthOffset, (int) size); - buffer.writerIndex((int) size).readerIndex(0); + buffer.clear().writerIndex((int) size); return buffer; } } From eb2abea1f85db0b6004756fb75db8f0c6b7d2771 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sun, 7 Nov 2021 14:01:53 +1100 Subject: [PATCH 127/322] Update to Acegi Standard Project 0.6.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ce6eeb21..98e1292f 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ au.com.acegi acegi-standard-project - 0.6.1 + 0.6.2 org.lmdbjava lmdbjava From 680e0a820753695255e1849e710bde1975848125 Mon Sep 17 00:00:00 2001 From: Jan Doms Date: Thu, 13 Jan 2022 23:47:44 +0000 Subject: [PATCH 128/322] fix lmdb link in readme (#189) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5abc21c0..4547b786 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ # LMDB for Java -[LMDB](http://symas.com/mdb/) offers: +[LMDB](http://symas.com/lmdb/) offers: * Transactions (full ACID semantics) * Ordered keys (enabling very fast cursor-based iteration) From b9b331bf21834d8aa9322a8aaf3592bd05f6313e Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Thu, 24 Mar 2022 12:29:13 +1100 Subject: [PATCH 129/322] Update to LMDB 0.9.29 (closes #194) --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 98e1292f..2ed6eb40 100644 --- a/pom.xml +++ b/pom.xml @@ -73,19 +73,19 @@ org.lmdbjava lmdbjava-native-linux-x86_64 - 0.9.24-1 + 0.9.29-1 true org.lmdbjava lmdbjava-native-osx-x86_64 - 0.9.24-1 + 0.9.29-1 true org.lmdbjava lmdbjava-native-windows-x86_64 - 0.9.24-1 + 0.9.29-1 true From da2a849566e34eaa9a1fc79d132fdee1f80dfb29 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 15 Apr 2022 16:06:48 +1000 Subject: [PATCH 130/322] Update copyright year --- src/main/java/org/lmdbjava/BufferProxy.java | 2 +- src/main/java/org/lmdbjava/ByteArrayProxy.java | 2 +- src/main/java/org/lmdbjava/ByteBufProxy.java | 2 +- src/main/java/org/lmdbjava/ByteBufferProxy.java | 2 +- src/main/java/org/lmdbjava/CopyFlags.java | 2 +- src/main/java/org/lmdbjava/Cursor.java | 2 +- src/main/java/org/lmdbjava/CursorIterable.java | 2 +- src/main/java/org/lmdbjava/Dbi.java | 2 +- src/main/java/org/lmdbjava/DbiFlags.java | 2 +- src/main/java/org/lmdbjava/DirectBufferProxy.java | 2 +- src/main/java/org/lmdbjava/Env.java | 2 +- src/main/java/org/lmdbjava/EnvFlags.java | 2 +- src/main/java/org/lmdbjava/EnvInfo.java | 2 +- src/main/java/org/lmdbjava/GetOp.java | 2 +- src/main/java/org/lmdbjava/KeyRange.java | 2 +- src/main/java/org/lmdbjava/KeyRangeType.java | 2 +- src/main/java/org/lmdbjava/KeyVal.java | 2 +- src/main/java/org/lmdbjava/Library.java | 2 +- src/main/java/org/lmdbjava/LmdbException.java | 2 +- src/main/java/org/lmdbjava/LmdbNativeException.java | 2 +- src/main/java/org/lmdbjava/MaskedFlag.java | 2 +- src/main/java/org/lmdbjava/Meta.java | 2 +- src/main/java/org/lmdbjava/PutFlags.java | 2 +- src/main/java/org/lmdbjava/ResultCodeMapper.java | 2 +- src/main/java/org/lmdbjava/SeekOp.java | 2 +- src/main/java/org/lmdbjava/Stat.java | 2 +- src/main/java/org/lmdbjava/Txn.java | 2 +- src/main/java/org/lmdbjava/TxnFlags.java | 2 +- src/main/java/org/lmdbjava/UnsafeAccess.java | 2 +- src/main/java/org/lmdbjava/Verifier.java | 2 +- src/main/java/org/lmdbjava/package-info.java | 2 +- src/test/java/org/lmdbjava/ByteBufferProxyTest.java | 2 +- src/test/java/org/lmdbjava/ComparatorTest.java | 2 +- src/test/java/org/lmdbjava/CursorIterableTest.java | 2 +- src/test/java/org/lmdbjava/CursorParamTest.java | 2 +- src/test/java/org/lmdbjava/CursorTest.java | 2 +- src/test/java/org/lmdbjava/DbiTest.java | 2 +- src/test/java/org/lmdbjava/EnvTest.java | 2 +- src/test/java/org/lmdbjava/KeyRangeTest.java | 2 +- src/test/java/org/lmdbjava/LibraryTest.java | 2 +- src/test/java/org/lmdbjava/MaskedFlagTest.java | 2 +- src/test/java/org/lmdbjava/MetaTest.java | 2 +- src/test/java/org/lmdbjava/ResultCodeMapperTest.java | 2 +- src/test/java/org/lmdbjava/TestUtils.java | 2 +- src/test/java/org/lmdbjava/TutorialTest.java | 2 +- src/test/java/org/lmdbjava/TxnTest.java | 2 +- src/test/java/org/lmdbjava/VerifierTest.java | 2 +- src/test/java/org/lmdbjava/package-info.java | 2 +- 48 files changed, 48 insertions(+), 48 deletions(-) diff --git a/src/main/java/org/lmdbjava/BufferProxy.java b/src/main/java/org/lmdbjava/BufferProxy.java index dc7aaadd..01ff0608 100644 --- a/src/main/java/org/lmdbjava/BufferProxy.java +++ b/src/main/java/org/lmdbjava/BufferProxy.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/ByteArrayProxy.java b/src/main/java/org/lmdbjava/ByteArrayProxy.java index 2b72a833..4afa20ae 100644 --- a/src/main/java/org/lmdbjava/ByteArrayProxy.java +++ b/src/main/java/org/lmdbjava/ByteArrayProxy.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/ByteBufProxy.java b/src/main/java/org/lmdbjava/ByteBufProxy.java index 51fe0617..1029cdf3 100644 --- a/src/main/java/org/lmdbjava/ByteBufProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufProxy.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index 2dd337c0..1292e2bd 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/CopyFlags.java b/src/main/java/org/lmdbjava/CopyFlags.java index 685df3c3..52cbb234 100644 --- a/src/main/java/org/lmdbjava/CopyFlags.java +++ b/src/main/java/org/lmdbjava/CopyFlags.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/Cursor.java b/src/main/java/org/lmdbjava/Cursor.java index f165423b..ad6aaf8b 100644 --- a/src/main/java/org/lmdbjava/Cursor.java +++ b/src/main/java/org/lmdbjava/Cursor.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/CursorIterable.java b/src/main/java/org/lmdbjava/CursorIterable.java index 5bd86272..37edad1b 100644 --- a/src/main/java/org/lmdbjava/CursorIterable.java +++ b/src/main/java/org/lmdbjava/CursorIterable.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index 7ccb1940..426e461e 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/DbiFlags.java b/src/main/java/org/lmdbjava/DbiFlags.java index 12ea80f0..da263868 100644 --- a/src/main/java/org/lmdbjava/DbiFlags.java +++ b/src/main/java/org/lmdbjava/DbiFlags.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/DirectBufferProxy.java b/src/main/java/org/lmdbjava/DirectBufferProxy.java index 39911515..3073fd64 100644 --- a/src/main/java/org/lmdbjava/DirectBufferProxy.java +++ b/src/main/java/org/lmdbjava/DirectBufferProxy.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index 326763f6..ea54b7a3 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/EnvFlags.java b/src/main/java/org/lmdbjava/EnvFlags.java index a713547d..fe942f58 100644 --- a/src/main/java/org/lmdbjava/EnvFlags.java +++ b/src/main/java/org/lmdbjava/EnvFlags.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/EnvInfo.java b/src/main/java/org/lmdbjava/EnvInfo.java index de21edd0..274b341c 100644 --- a/src/main/java/org/lmdbjava/EnvInfo.java +++ b/src/main/java/org/lmdbjava/EnvInfo.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/GetOp.java b/src/main/java/org/lmdbjava/GetOp.java index fa600ebd..7fe2697f 100644 --- a/src/main/java/org/lmdbjava/GetOp.java +++ b/src/main/java/org/lmdbjava/GetOp.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/KeyRange.java b/src/main/java/org/lmdbjava/KeyRange.java index 61d9e434..76fe558e 100644 --- a/src/main/java/org/lmdbjava/KeyRange.java +++ b/src/main/java/org/lmdbjava/KeyRange.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/KeyRangeType.java b/src/main/java/org/lmdbjava/KeyRangeType.java index d92ec054..77a55529 100644 --- a/src/main/java/org/lmdbjava/KeyRangeType.java +++ b/src/main/java/org/lmdbjava/KeyRangeType.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/KeyVal.java b/src/main/java/org/lmdbjava/KeyVal.java index 0da61a8e..f438597b 100644 --- a/src/main/java/org/lmdbjava/KeyVal.java +++ b/src/main/java/org/lmdbjava/KeyVal.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/Library.java b/src/main/java/org/lmdbjava/Library.java index fc54dda0..7de85033 100644 --- a/src/main/java/org/lmdbjava/Library.java +++ b/src/main/java/org/lmdbjava/Library.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/LmdbException.java b/src/main/java/org/lmdbjava/LmdbException.java index 92ff72e4..8dd52ea6 100644 --- a/src/main/java/org/lmdbjava/LmdbException.java +++ b/src/main/java/org/lmdbjava/LmdbException.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/LmdbNativeException.java b/src/main/java/org/lmdbjava/LmdbNativeException.java index 1bef719d..4073cf59 100644 --- a/src/main/java/org/lmdbjava/LmdbNativeException.java +++ b/src/main/java/org/lmdbjava/LmdbNativeException.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/MaskedFlag.java b/src/main/java/org/lmdbjava/MaskedFlag.java index dfd6df16..e3419c13 100644 --- a/src/main/java/org/lmdbjava/MaskedFlag.java +++ b/src/main/java/org/lmdbjava/MaskedFlag.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/Meta.java b/src/main/java/org/lmdbjava/Meta.java index b157b950..bceb11a9 100644 --- a/src/main/java/org/lmdbjava/Meta.java +++ b/src/main/java/org/lmdbjava/Meta.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/PutFlags.java b/src/main/java/org/lmdbjava/PutFlags.java index 58589e68..ff397e54 100644 --- a/src/main/java/org/lmdbjava/PutFlags.java +++ b/src/main/java/org/lmdbjava/PutFlags.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/ResultCodeMapper.java b/src/main/java/org/lmdbjava/ResultCodeMapper.java index 0c076bc7..7566f0b1 100644 --- a/src/main/java/org/lmdbjava/ResultCodeMapper.java +++ b/src/main/java/org/lmdbjava/ResultCodeMapper.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/SeekOp.java b/src/main/java/org/lmdbjava/SeekOp.java index 829458a9..c86211a9 100644 --- a/src/main/java/org/lmdbjava/SeekOp.java +++ b/src/main/java/org/lmdbjava/SeekOp.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/Stat.java b/src/main/java/org/lmdbjava/Stat.java index 66480a08..605b5f4d 100644 --- a/src/main/java/org/lmdbjava/Stat.java +++ b/src/main/java/org/lmdbjava/Stat.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/Txn.java b/src/main/java/org/lmdbjava/Txn.java index 36f8eaf9..a6f5218a 100644 --- a/src/main/java/org/lmdbjava/Txn.java +++ b/src/main/java/org/lmdbjava/Txn.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/TxnFlags.java b/src/main/java/org/lmdbjava/TxnFlags.java index 906170ba..3753d6c2 100644 --- a/src/main/java/org/lmdbjava/TxnFlags.java +++ b/src/main/java/org/lmdbjava/TxnFlags.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/UnsafeAccess.java b/src/main/java/org/lmdbjava/UnsafeAccess.java index 8a853429..b5c06d88 100644 --- a/src/main/java/org/lmdbjava/UnsafeAccess.java +++ b/src/main/java/org/lmdbjava/UnsafeAccess.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/Verifier.java b/src/main/java/org/lmdbjava/Verifier.java index 05e5eb83..493eb17e 100644 --- a/src/main/java/org/lmdbjava/Verifier.java +++ b/src/main/java/org/lmdbjava/Verifier.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/main/java/org/lmdbjava/package-info.java b/src/main/java/org/lmdbjava/package-info.java index aa1b4570..c60e2e1a 100644 --- a/src/main/java/org/lmdbjava/package-info.java +++ b/src/main/java/org/lmdbjava/package-info.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java index 19b6aba5..2469ae7c 100644 --- a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java +++ b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/test/java/org/lmdbjava/ComparatorTest.java b/src/test/java/org/lmdbjava/ComparatorTest.java index a9aa920e..2faa224a 100644 --- a/src/test/java/org/lmdbjava/ComparatorTest.java +++ b/src/test/java/org/lmdbjava/ComparatorTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index 05819835..036f3bf8 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/test/java/org/lmdbjava/CursorParamTest.java b/src/test/java/org/lmdbjava/CursorParamTest.java index 6cac2be5..0ae70dd8 100644 --- a/src/test/java/org/lmdbjava/CursorParamTest.java +++ b/src/test/java/org/lmdbjava/CursorParamTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/test/java/org/lmdbjava/CursorTest.java b/src/test/java/org/lmdbjava/CursorTest.java index 373b68e9..1a685487 100644 --- a/src/test/java/org/lmdbjava/CursorTest.java +++ b/src/test/java/org/lmdbjava/CursorTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 689eab58..640c2905 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/test/java/org/lmdbjava/EnvTest.java b/src/test/java/org/lmdbjava/EnvTest.java index bf766ff7..6fc03021 100644 --- a/src/test/java/org/lmdbjava/EnvTest.java +++ b/src/test/java/org/lmdbjava/EnvTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/test/java/org/lmdbjava/KeyRangeTest.java b/src/test/java/org/lmdbjava/KeyRangeTest.java index 8d0077ae..d1802072 100644 --- a/src/test/java/org/lmdbjava/KeyRangeTest.java +++ b/src/test/java/org/lmdbjava/KeyRangeTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/test/java/org/lmdbjava/LibraryTest.java b/src/test/java/org/lmdbjava/LibraryTest.java index 9ac57fba..2c7ce1c6 100644 --- a/src/test/java/org/lmdbjava/LibraryTest.java +++ b/src/test/java/org/lmdbjava/LibraryTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/test/java/org/lmdbjava/MaskedFlagTest.java b/src/test/java/org/lmdbjava/MaskedFlagTest.java index d28666ea..37202b76 100644 --- a/src/test/java/org/lmdbjava/MaskedFlagTest.java +++ b/src/test/java/org/lmdbjava/MaskedFlagTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/test/java/org/lmdbjava/MetaTest.java b/src/test/java/org/lmdbjava/MetaTest.java index 3df23f41..4d7fd01d 100644 --- a/src/test/java/org/lmdbjava/MetaTest.java +++ b/src/test/java/org/lmdbjava/MetaTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/test/java/org/lmdbjava/ResultCodeMapperTest.java b/src/test/java/org/lmdbjava/ResultCodeMapperTest.java index 37d6e166..b5699749 100644 --- a/src/test/java/org/lmdbjava/ResultCodeMapperTest.java +++ b/src/test/java/org/lmdbjava/ResultCodeMapperTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/test/java/org/lmdbjava/TestUtils.java b/src/test/java/org/lmdbjava/TestUtils.java index 6ab21584..8ef16ec1 100644 --- a/src/test/java/org/lmdbjava/TestUtils.java +++ b/src/test/java/org/lmdbjava/TestUtils.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/test/java/org/lmdbjava/TutorialTest.java b/src/test/java/org/lmdbjava/TutorialTest.java index b92582d0..09536382 100644 --- a/src/test/java/org/lmdbjava/TutorialTest.java +++ b/src/test/java/org/lmdbjava/TutorialTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/test/java/org/lmdbjava/TxnTest.java b/src/test/java/org/lmdbjava/TxnTest.java index 69f0815f..87969e1f 100644 --- a/src/test/java/org/lmdbjava/TxnTest.java +++ b/src/test/java/org/lmdbjava/TxnTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/test/java/org/lmdbjava/VerifierTest.java b/src/test/java/org/lmdbjava/VerifierTest.java index 4a4c487a..fd49ffec 100644 --- a/src/test/java/org/lmdbjava/VerifierTest.java +++ b/src/test/java/org/lmdbjava/VerifierTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. diff --git a/src/test/java/org/lmdbjava/package-info.java b/src/test/java/org/lmdbjava/package-info.java index 042d4e65..c404b678 100644 --- a/src/test/java/org/lmdbjava/package-info.java +++ b/src/test/java/org/lmdbjava/package-info.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2021 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2022 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. From 4b872d285441a6340a6810eddb6d683947722f00 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 15 Apr 2022 17:10:22 +1000 Subject: [PATCH 131/322] Update to JNR-FFI 2.2.12 (fixes #190) Transitive dependency tree now includes JFFI 1.3.9: +- com.github.jnr:jnr-ffi:jar:2.2.12:compile | +- com.github.jnr:jffi:jar:1.3.9:compile | +- com.github.jnr:jffi:jar:native:1.3.9:runtime | +- org.ow2.asm:asm:jar:9.2:compile | +- org.ow2.asm:asm-commons:jar:9.2:compile | +- org.ow2.asm:asm-analysis:jar:9.2:compile | +- org.ow2.asm:asm-tree:jar:9.2:compile | +- org.ow2.asm:asm-util:jar:9.2:compile | +- com.github.jnr:jnr-a64asm:jar:1.0.0:compile | \- com.github.jnr:jnr-x86asm:jar:1.0.2:compile --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2ed6eb40..5d4c0d22 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ com.github.jnr jnr-ffi - 2.2.2 + 2.2.12 com.google.code.findbugs From 301f8d132ee9a85bf331834dee23fe91ed73a67b Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 15 Apr 2022 17:12:25 +1000 Subject: [PATCH 132/322] Update to JNR Constants 0.10.3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5d4c0d22..5b765ea8 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ com.github.jnr jnr-constants - 0.9.15 + 0.10.3 com.github.jnr From a406ef819b71ed26d9de9006edfc15de0b22aec2 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 15 Apr 2022 17:12:34 +1000 Subject: [PATCH 133/322] Update to Guava 31.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5b765ea8..eb2412da 100644 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,7 @@ com.google.guava guava - 30.1.1-jre + 31.1-jre test From 492c7767c7c47d17171fac80b89feb8d96ae69be Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 15 Apr 2022 17:12:45 +1000 Subject: [PATCH 134/322] Update to Netty 4.1.76.Final --- pom.xml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index eb2412da..33cdcc43 100644 --- a/pom.xml +++ b/pom.xml @@ -52,8 +52,14 @@ io.netty - netty-all - 4.1.66.Final + netty-buffer + 4.1.76.Final + true + + + io.netty + netty-buffer + 4.1.76.Final true From 9996c282fb886351577298e11441ff6338ffd99e Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 15 Apr 2022 17:13:03 +1000 Subject: [PATCH 135/322] Update to Agrona 1.15.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 33cdcc43..cb071033 100644 --- a/pom.xml +++ b/pom.xml @@ -69,7 +69,7 @@ org.agrona agrona - 1.11.0 + 1.15.1 true From 6a6331669374189cc9e7cb0bd08a1910c42fef44 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 15 Apr 2022 17:13:21 +1000 Subject: [PATCH 136/322] Update to Acegi Standard Project 0.6.3 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index cb071033..182713e5 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ au.com.acegi acegi-standard-project - 0.6.2 + 0.6.3 org.lmdbjava lmdbjava From fae1fb6a5bb0500ad22f226932d97c41cd512ff4 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 16 Apr 2022 12:06:48 +1000 Subject: [PATCH 137/322] Checkstyle during OSSHR deploy due to JVM incompatibility --- pom.xml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 182713e5..00525364 100644 --- a/pom.xml +++ b/pom.xml @@ -109,10 +109,6 @@ com.github.spotbugs spotbugs-maven-plugin - - org.apache.maven.plugins - maven-checkstyle-plugin - org.apache.maven.plugins maven-dependency-plugin @@ -240,5 +236,19 @@ + + additional-plugins-on-java-11-and-above + + [1.11 + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + + From 610043c9f1e1b7f937bc89138eced6eae9c93333 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 2 Dec 2022 12:16:25 +1100 Subject: [PATCH 138/322] Update to Acegi Standard Project 0.6.4 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 00525364..ca07d4d7 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ au.com.acegi acegi-standard-project - 0.6.3 + 0.6.4 org.lmdbjava lmdbjava From e5d94cb4a87a8f32ac462ee83c5172fa3898e53b Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 2 Dec 2022 12:24:09 +1100 Subject: [PATCH 139/322] sortpom-maven-plugin to only run on Java 11+ --- pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index ca07d4d7..0ab2fb2c 100644 --- a/pom.xml +++ b/pom.xml @@ -101,10 +101,6 @@ au.com.acegi xml-format-maven-plugin - - com.github.ekryd.sortpom - sortpom-maven-plugin - com.github.spotbugs spotbugs-maven-plugin @@ -243,6 +239,10 @@ + + com.github.ekryd.sortpom + sortpom-maven-plugin + org.apache.maven.plugins maven-checkstyle-plugin From a1162e10c24ec54186562e71603d0a3bc7579b88 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 9 Dec 2022 13:12:38 +1100 Subject: [PATCH 140/322] Update GitHub Actions Workflow to modern actions and versions Support for dependencies in the Sonatype snapshot repository has been removed because the maven-settings-action does not presently support editing the settings.xml produced by the setup-java action. The only historical reason to add the Sonatype snapshot repository was to test with the Acegi Standard Project superpom snapshots. This is usually undertaken on a developer machine and then an Acegi Standard Project superpom is released to Maven Central. As such having GitHub Actions able to perform this testing is largely unnecessary and has been removed for maintenance practicality. --- .github/workflows/maven.yml | 77 +++++++++++++++---------------------- 1 file changed, 30 insertions(+), 47 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index a272d491..a551b802 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -13,29 +13,20 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Java and Maven - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: + distribution: zulu java-version: 11 - - - name: Add OSSRH to the snapshot repositories list - uses: s4u/maven-settings-action@v2.1.0 - with: - sonatypeSnapshots: true - - - name: Cache Maven packages - uses: actions/cache@v1 - with: - path: ~/.m2 - key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + cache: maven - name: Build with Maven run: mvn -B verify - name: Upload code coverage to Codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v3 compatibility-checks: name: Java ${{ matrix.java }} on ${{ matrix.os }} Compatibility @@ -48,29 +39,20 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Java and Maven - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: + distribution: zulu java-version: ${{ matrix.java }} - - - name: Add OSSRH to the snapshot repositories list - uses: s4u/maven-settings-action@v2.1.0 - with: - sonatypeSnapshots: true - - - name: Cache Maven packages - uses: actions/cache@v1 - with: - path: ~/.m2 - key: ${{ runner.os }}-${{ matrix.java }}-m2-${{ hashFiles('**/pom.xml') }} + cache: maven - name: Test with Maven run: mvn -B test - name: Upload Surefire reports on test failure - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v3 if: failure() with: name: surefire-test-log @@ -84,30 +66,31 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up Java and Maven - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: + distribution: zulu # Java version 8 required due to https://github.com/lmdbjava/lmdbjava/issues/116 java-version: 8 - - - name: Add OSSRH to the snapshot repositories list - uses: s4u/maven-settings-action@v2.1.0 - with: - sonatypeSnapshots: true - - - name: Cache Maven packages - uses: actions/cache@v1 - with: - path: ~/.m2 - key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + cache: maven + server-id: ossrh + server-username: MAVEN_USERNAME + server-password: MAVEN_CENTRAL_TOKEN + gpg-private-key: ${{ secrets.gpg_private_key }} + gpg-passphrase: MAVEN_GPG_PASSPHRASE - name: Publish Maven package - uses: samuelmeuli/action-maven-publish@v1 + run: mvn -Possrh-deploy deploy + env: + MAVEN_GPG_PASSPHRASE: ${{ secrets.gpg_passphrase }} + MAVEN_USERNAME: ${{ secrets.nexus_username }} + MAVEN_CENTRAL_TOKEN: ${{ secrets.nexus_password }} + + - name: Debug settings.xml + uses: actions/upload-artifact@v3 + if: failure() with: - gpg_private_key: ${{ secrets.gpg_private_key }} - gpg_passphrase: ${{ secrets.gpg_passphrase }} - nexus_username: ${{ secrets.nexus_username }} - nexus_password: ${{ secrets.nexus_password }} - maven_profiles: ossrh-deploy + name: settings.xml + path: $HOME/.m2/settings.xml From bf9e32a5781eed7020c534772c2c2460f5923b16 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Thu, 2 Feb 2023 12:01:57 +1100 Subject: [PATCH 141/322] Copyright year only --- src/main/java/org/lmdbjava/BufferProxy.java | 2 +- src/main/java/org/lmdbjava/ByteArrayProxy.java | 2 +- src/main/java/org/lmdbjava/ByteBufProxy.java | 2 +- src/main/java/org/lmdbjava/ByteBufferProxy.java | 2 +- src/main/java/org/lmdbjava/CopyFlags.java | 2 +- src/main/java/org/lmdbjava/Cursor.java | 2 +- src/main/java/org/lmdbjava/CursorIterable.java | 2 +- src/main/java/org/lmdbjava/Dbi.java | 2 +- src/main/java/org/lmdbjava/DbiFlags.java | 2 +- src/main/java/org/lmdbjava/DirectBufferProxy.java | 2 +- src/main/java/org/lmdbjava/Env.java | 2 +- src/main/java/org/lmdbjava/EnvFlags.java | 2 +- src/main/java/org/lmdbjava/EnvInfo.java | 2 +- src/main/java/org/lmdbjava/GetOp.java | 2 +- src/main/java/org/lmdbjava/KeyRange.java | 2 +- src/main/java/org/lmdbjava/KeyRangeType.java | 2 +- src/main/java/org/lmdbjava/KeyVal.java | 2 +- src/main/java/org/lmdbjava/Library.java | 2 +- src/main/java/org/lmdbjava/LmdbException.java | 2 +- src/main/java/org/lmdbjava/LmdbNativeException.java | 2 +- src/main/java/org/lmdbjava/MaskedFlag.java | 2 +- src/main/java/org/lmdbjava/Meta.java | 2 +- src/main/java/org/lmdbjava/PutFlags.java | 2 +- src/main/java/org/lmdbjava/ResultCodeMapper.java | 2 +- src/main/java/org/lmdbjava/SeekOp.java | 2 +- src/main/java/org/lmdbjava/Stat.java | 2 +- src/main/java/org/lmdbjava/Txn.java | 2 +- src/main/java/org/lmdbjava/TxnFlags.java | 2 +- src/main/java/org/lmdbjava/UnsafeAccess.java | 2 +- src/main/java/org/lmdbjava/Verifier.java | 2 +- src/main/java/org/lmdbjava/package-info.java | 2 +- src/test/java/org/lmdbjava/ByteBufferProxyTest.java | 2 +- src/test/java/org/lmdbjava/ComparatorTest.java | 2 +- src/test/java/org/lmdbjava/CursorIterableTest.java | 2 +- src/test/java/org/lmdbjava/CursorParamTest.java | 2 +- src/test/java/org/lmdbjava/CursorTest.java | 2 +- src/test/java/org/lmdbjava/DbiTest.java | 2 +- src/test/java/org/lmdbjava/EnvTest.java | 2 +- src/test/java/org/lmdbjava/KeyRangeTest.java | 2 +- src/test/java/org/lmdbjava/LibraryTest.java | 2 +- src/test/java/org/lmdbjava/MaskedFlagTest.java | 2 +- src/test/java/org/lmdbjava/MetaTest.java | 2 +- src/test/java/org/lmdbjava/ResultCodeMapperTest.java | 2 +- src/test/java/org/lmdbjava/TestUtils.java | 2 +- src/test/java/org/lmdbjava/TutorialTest.java | 2 +- src/test/java/org/lmdbjava/TxnTest.java | 2 +- src/test/java/org/lmdbjava/VerifierTest.java | 2 +- src/test/java/org/lmdbjava/package-info.java | 2 +- 48 files changed, 48 insertions(+), 48 deletions(-) diff --git a/src/main/java/org/lmdbjava/BufferProxy.java b/src/main/java/org/lmdbjava/BufferProxy.java index 01ff0608..bd58d7b6 100644 --- a/src/main/java/org/lmdbjava/BufferProxy.java +++ b/src/main/java/org/lmdbjava/BufferProxy.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/ByteArrayProxy.java b/src/main/java/org/lmdbjava/ByteArrayProxy.java index 4afa20ae..f6ddbef2 100644 --- a/src/main/java/org/lmdbjava/ByteArrayProxy.java +++ b/src/main/java/org/lmdbjava/ByteArrayProxy.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/ByteBufProxy.java b/src/main/java/org/lmdbjava/ByteBufProxy.java index 1029cdf3..d8bbc452 100644 --- a/src/main/java/org/lmdbjava/ByteBufProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufProxy.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index 1292e2bd..0d1781b3 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/CopyFlags.java b/src/main/java/org/lmdbjava/CopyFlags.java index 52cbb234..88649a04 100644 --- a/src/main/java/org/lmdbjava/CopyFlags.java +++ b/src/main/java/org/lmdbjava/CopyFlags.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/Cursor.java b/src/main/java/org/lmdbjava/Cursor.java index ad6aaf8b..bc6b36e4 100644 --- a/src/main/java/org/lmdbjava/Cursor.java +++ b/src/main/java/org/lmdbjava/Cursor.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/CursorIterable.java b/src/main/java/org/lmdbjava/CursorIterable.java index 37edad1b..681f805b 100644 --- a/src/main/java/org/lmdbjava/CursorIterable.java +++ b/src/main/java/org/lmdbjava/CursorIterable.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index 426e461e..96339873 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/DbiFlags.java b/src/main/java/org/lmdbjava/DbiFlags.java index da263868..081fb80e 100644 --- a/src/main/java/org/lmdbjava/DbiFlags.java +++ b/src/main/java/org/lmdbjava/DbiFlags.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/DirectBufferProxy.java b/src/main/java/org/lmdbjava/DirectBufferProxy.java index 3073fd64..2a6b67c3 100644 --- a/src/main/java/org/lmdbjava/DirectBufferProxy.java +++ b/src/main/java/org/lmdbjava/DirectBufferProxy.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index ea54b7a3..9a67c997 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/EnvFlags.java b/src/main/java/org/lmdbjava/EnvFlags.java index fe942f58..ab54917a 100644 --- a/src/main/java/org/lmdbjava/EnvFlags.java +++ b/src/main/java/org/lmdbjava/EnvFlags.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/EnvInfo.java b/src/main/java/org/lmdbjava/EnvInfo.java index 274b341c..a1ae62ba 100644 --- a/src/main/java/org/lmdbjava/EnvInfo.java +++ b/src/main/java/org/lmdbjava/EnvInfo.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/GetOp.java b/src/main/java/org/lmdbjava/GetOp.java index 7fe2697f..9d36c204 100644 --- a/src/main/java/org/lmdbjava/GetOp.java +++ b/src/main/java/org/lmdbjava/GetOp.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/KeyRange.java b/src/main/java/org/lmdbjava/KeyRange.java index 76fe558e..d47c444c 100644 --- a/src/main/java/org/lmdbjava/KeyRange.java +++ b/src/main/java/org/lmdbjava/KeyRange.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/KeyRangeType.java b/src/main/java/org/lmdbjava/KeyRangeType.java index 77a55529..267e45c8 100644 --- a/src/main/java/org/lmdbjava/KeyRangeType.java +++ b/src/main/java/org/lmdbjava/KeyRangeType.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/KeyVal.java b/src/main/java/org/lmdbjava/KeyVal.java index f438597b..12b9f9af 100644 --- a/src/main/java/org/lmdbjava/KeyVal.java +++ b/src/main/java/org/lmdbjava/KeyVal.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/Library.java b/src/main/java/org/lmdbjava/Library.java index 7de85033..44b17492 100644 --- a/src/main/java/org/lmdbjava/Library.java +++ b/src/main/java/org/lmdbjava/Library.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/LmdbException.java b/src/main/java/org/lmdbjava/LmdbException.java index 8dd52ea6..b3aea56e 100644 --- a/src/main/java/org/lmdbjava/LmdbException.java +++ b/src/main/java/org/lmdbjava/LmdbException.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/LmdbNativeException.java b/src/main/java/org/lmdbjava/LmdbNativeException.java index 4073cf59..3f559806 100644 --- a/src/main/java/org/lmdbjava/LmdbNativeException.java +++ b/src/main/java/org/lmdbjava/LmdbNativeException.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/MaskedFlag.java b/src/main/java/org/lmdbjava/MaskedFlag.java index e3419c13..9bdef636 100644 --- a/src/main/java/org/lmdbjava/MaskedFlag.java +++ b/src/main/java/org/lmdbjava/MaskedFlag.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/Meta.java b/src/main/java/org/lmdbjava/Meta.java index bceb11a9..6f392701 100644 --- a/src/main/java/org/lmdbjava/Meta.java +++ b/src/main/java/org/lmdbjava/Meta.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/PutFlags.java b/src/main/java/org/lmdbjava/PutFlags.java index ff397e54..48bc243b 100644 --- a/src/main/java/org/lmdbjava/PutFlags.java +++ b/src/main/java/org/lmdbjava/PutFlags.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/ResultCodeMapper.java b/src/main/java/org/lmdbjava/ResultCodeMapper.java index 7566f0b1..809628ac 100644 --- a/src/main/java/org/lmdbjava/ResultCodeMapper.java +++ b/src/main/java/org/lmdbjava/ResultCodeMapper.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/SeekOp.java b/src/main/java/org/lmdbjava/SeekOp.java index c86211a9..ed5fb47c 100644 --- a/src/main/java/org/lmdbjava/SeekOp.java +++ b/src/main/java/org/lmdbjava/SeekOp.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/Stat.java b/src/main/java/org/lmdbjava/Stat.java index 605b5f4d..42b344fb 100644 --- a/src/main/java/org/lmdbjava/Stat.java +++ b/src/main/java/org/lmdbjava/Stat.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/Txn.java b/src/main/java/org/lmdbjava/Txn.java index a6f5218a..f21552e7 100644 --- a/src/main/java/org/lmdbjava/Txn.java +++ b/src/main/java/org/lmdbjava/Txn.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/TxnFlags.java b/src/main/java/org/lmdbjava/TxnFlags.java index 3753d6c2..0379bfa7 100644 --- a/src/main/java/org/lmdbjava/TxnFlags.java +++ b/src/main/java/org/lmdbjava/TxnFlags.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/UnsafeAccess.java b/src/main/java/org/lmdbjava/UnsafeAccess.java index b5c06d88..836cda75 100644 --- a/src/main/java/org/lmdbjava/UnsafeAccess.java +++ b/src/main/java/org/lmdbjava/UnsafeAccess.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/Verifier.java b/src/main/java/org/lmdbjava/Verifier.java index 493eb17e..9807d0b8 100644 --- a/src/main/java/org/lmdbjava/Verifier.java +++ b/src/main/java/org/lmdbjava/Verifier.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/main/java/org/lmdbjava/package-info.java b/src/main/java/org/lmdbjava/package-info.java index c60e2e1a..52c8551a 100644 --- a/src/main/java/org/lmdbjava/package-info.java +++ b/src/main/java/org/lmdbjava/package-info.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java index 2469ae7c..3daaea5f 100644 --- a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java +++ b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/test/java/org/lmdbjava/ComparatorTest.java b/src/test/java/org/lmdbjava/ComparatorTest.java index 2faa224a..77c5ca37 100644 --- a/src/test/java/org/lmdbjava/ComparatorTest.java +++ b/src/test/java/org/lmdbjava/ComparatorTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index 036f3bf8..247ef791 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/test/java/org/lmdbjava/CursorParamTest.java b/src/test/java/org/lmdbjava/CursorParamTest.java index 0ae70dd8..b9fd60e3 100644 --- a/src/test/java/org/lmdbjava/CursorParamTest.java +++ b/src/test/java/org/lmdbjava/CursorParamTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/test/java/org/lmdbjava/CursorTest.java b/src/test/java/org/lmdbjava/CursorTest.java index 1a685487..51b78806 100644 --- a/src/test/java/org/lmdbjava/CursorTest.java +++ b/src/test/java/org/lmdbjava/CursorTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 640c2905..e52bd28d 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/test/java/org/lmdbjava/EnvTest.java b/src/test/java/org/lmdbjava/EnvTest.java index 6fc03021..f26d42f3 100644 --- a/src/test/java/org/lmdbjava/EnvTest.java +++ b/src/test/java/org/lmdbjava/EnvTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/test/java/org/lmdbjava/KeyRangeTest.java b/src/test/java/org/lmdbjava/KeyRangeTest.java index d1802072..1b4384f9 100644 --- a/src/test/java/org/lmdbjava/KeyRangeTest.java +++ b/src/test/java/org/lmdbjava/KeyRangeTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/test/java/org/lmdbjava/LibraryTest.java b/src/test/java/org/lmdbjava/LibraryTest.java index 2c7ce1c6..df5ae89d 100644 --- a/src/test/java/org/lmdbjava/LibraryTest.java +++ b/src/test/java/org/lmdbjava/LibraryTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/test/java/org/lmdbjava/MaskedFlagTest.java b/src/test/java/org/lmdbjava/MaskedFlagTest.java index 37202b76..0dbc8b62 100644 --- a/src/test/java/org/lmdbjava/MaskedFlagTest.java +++ b/src/test/java/org/lmdbjava/MaskedFlagTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/test/java/org/lmdbjava/MetaTest.java b/src/test/java/org/lmdbjava/MetaTest.java index 4d7fd01d..922c4368 100644 --- a/src/test/java/org/lmdbjava/MetaTest.java +++ b/src/test/java/org/lmdbjava/MetaTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/test/java/org/lmdbjava/ResultCodeMapperTest.java b/src/test/java/org/lmdbjava/ResultCodeMapperTest.java index b5699749..723c22ca 100644 --- a/src/test/java/org/lmdbjava/ResultCodeMapperTest.java +++ b/src/test/java/org/lmdbjava/ResultCodeMapperTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/test/java/org/lmdbjava/TestUtils.java b/src/test/java/org/lmdbjava/TestUtils.java index 8ef16ec1..05dfb0cc 100644 --- a/src/test/java/org/lmdbjava/TestUtils.java +++ b/src/test/java/org/lmdbjava/TestUtils.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/test/java/org/lmdbjava/TutorialTest.java b/src/test/java/org/lmdbjava/TutorialTest.java index 09536382..92f69bb8 100644 --- a/src/test/java/org/lmdbjava/TutorialTest.java +++ b/src/test/java/org/lmdbjava/TutorialTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/test/java/org/lmdbjava/TxnTest.java b/src/test/java/org/lmdbjava/TxnTest.java index 87969e1f..f8a0b5d3 100644 --- a/src/test/java/org/lmdbjava/TxnTest.java +++ b/src/test/java/org/lmdbjava/TxnTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/test/java/org/lmdbjava/VerifierTest.java b/src/test/java/org/lmdbjava/VerifierTest.java index fd49ffec..27bb598b 100644 --- a/src/test/java/org/lmdbjava/VerifierTest.java +++ b/src/test/java/org/lmdbjava/VerifierTest.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. diff --git a/src/test/java/org/lmdbjava/package-info.java b/src/test/java/org/lmdbjava/package-info.java index c404b678..baaacbe8 100644 --- a/src/test/java/org/lmdbjava/package-info.java +++ b/src/test/java/org/lmdbjava/package-info.java @@ -2,7 +2,7 @@ * #%L * LmdbJava * %% - * Copyright (C) 2016 - 2022 The LmdbJava Open Source Project + * Copyright (C) 2016 - 2023 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. From 0d9ca495c7d5b330994dded7f7c28f4f928afdcf Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Thu, 2 Feb 2023 13:10:23 +1100 Subject: [PATCH 142/322] Remove duplicate Netty dependency declaration --- pom.xml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pom.xml b/pom.xml index 0ab2fb2c..379471ba 100644 --- a/pom.xml +++ b/pom.xml @@ -56,12 +56,6 @@ 4.1.76.Final true - - io.netty - netty-buffer - 4.1.76.Final - true - junit junit From 5b386c21c26a262e46c34767a08f446c411114e2 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 3 Feb 2023 09:05:52 +1100 Subject: [PATCH 143/322] Update to Acegi Standard Project 0.6.7 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 379471ba..cd5291c0 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ au.com.acegi acegi-standard-project - 0.6.4 + 0.6.7 org.lmdbjava lmdbjava From cc0a1739ff9d8613f4aaf1d3e730b35de8cf51c1 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 3 Feb 2023 09:06:00 +1100 Subject: [PATCH 144/322] Remove LGTM badges as service has been discontinued --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 4547b786..3b8f4753 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ [![codecov](https://codecov.io/gh/lmdbjava/lmdbjava/branch/master/graph/badge.svg)](https://codecov.io/gh/lmdbjava/lmdbjava) [![Javadocs](http://www.javadoc.io/badge/org.lmdbjava/lmdbjava.svg?color=blue)](http://www.javadoc.io/doc/org.lmdbjava/lmdbjava) [![Maven Central](https://img.shields.io/maven-central/v/org.lmdbjava/lmdbjava.svg?maxAge=3600)](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.lmdbjava%22%20AND%20a%3A%22lmdbjava%22) -[![Total alerts](https://img.shields.io/lgtm/alerts/g/lmdbjava/lmdbjava.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/lmdbjava/lmdbjava/alerts/) -[![Language grade: Java](https://img.shields.io/lgtm/grade/java/g/lmdbjava/lmdbjava.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/lmdbjava/lmdbjava/context:java) # LMDB for Java From 39a1c230bb72a668656f57b74a3f152fa4b48f8f Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 3 Feb 2023 09:12:55 +1100 Subject: [PATCH 145/322] Refer to JVM startup options (resolves #205) --- src/test/java/org/lmdbjava/TutorialTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test/java/org/lmdbjava/TutorialTest.java b/src/test/java/org/lmdbjava/TutorialTest.java index 92f69bb8..0385d952 100644 --- a/src/test/java/org/lmdbjava/TutorialTest.java +++ b/src/test/java/org/lmdbjava/TutorialTest.java @@ -65,6 +65,10 @@ * required system libraries. If you are using another 64-bit platform, you'll * need to install the LMDB system library yourself. 32-bit platforms are not * supported. + * + *

+ * Start the JVM with arguments --add-opens java.base/java.nio=ALL-UNNAMED + * --add-opens java.base/sun.nio.ch=ALL-UNNAMED to suppress JVM warnings. */ public final class TutorialTest { From 4b141bc80ce41b30ac39869ef87fba1b45162acc Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 3 Feb 2023 09:18:27 +1100 Subject: [PATCH 146/322] Update to JNR Constants 0.10.4 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index cd5291c0..d9979693 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ com.github.jnr jnr-constants - 0.10.3 + 0.10.4 com.github.jnr From 9c19c154d7490d8634143478710c8b414fe4d992 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 3 Feb 2023 09:18:40 +1100 Subject: [PATCH 147/322] Update to JNR FFI 2.2.13 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d9979693..68a88d37 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ com.github.jnr jnr-ffi - 2.2.12 + 2.2.13 com.google.code.findbugs From 9e25aa95b5b7262bc735f881399bdd83f133f5f3 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 3 Feb 2023 09:18:49 +1100 Subject: [PATCH 148/322] Update to Netty 4.1.87.Final --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 68a88d37..536aed04 100644 --- a/pom.xml +++ b/pom.xml @@ -53,7 +53,7 @@ io.netty netty-buffer - 4.1.76.Final + 4.1.87.Final true From bbee8495132ead6d6c6a69514e18a603191eaad6 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 3 Feb 2023 09:18:59 +1100 Subject: [PATCH 149/322] Update to Agrona 1.17.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 536aed04..9067cb38 100644 --- a/pom.xml +++ b/pom.xml @@ -63,7 +63,7 @@ org.agrona agrona - 1.15.1 + 1.17.1 true From bb26a83bc1a92f20f6a6ccabba5e44afcf77ae28 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 3 Feb 2023 10:51:07 +1100 Subject: [PATCH 150/322] Cursor.close() must release KeyVal even if write Txn closed (fixes #197) --- src/main/java/org/lmdbjava/Cursor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/lmdbjava/Cursor.java b/src/main/java/org/lmdbjava/Cursor.java index bc6b36e4..bcbbb61c 100644 --- a/src/main/java/org/lmdbjava/Cursor.java +++ b/src/main/java/org/lmdbjava/Cursor.java @@ -72,11 +72,11 @@ public void close() { if (closed) { return; } + kv.close(); if (SHOULD_CHECK && !txn.isReadOnly()) { txn.checkReady(); } LIB.mdb_cursor_close(ptrCursor); - kv.close(); closed = true; } From 87dab318fe4d1898e99b3fac779c695083f2e0ba Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 3 Feb 2023 11:21:01 +1100 Subject: [PATCH 151/322] Clarify concurrent requirements for Env methods (fixes #195) --- src/main/java/org/lmdbjava/Env.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index 9a67c997..1ca90367 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -170,6 +170,9 @@ public void copy(final File path, final CopyFlags... flags) { * If an unnamed {@link Dbi} is being used to store data, this method will * attempt to return all such keys from the unnamed database. * + *

+ * This method must not be called from concurrent threads. + * * @return a list of DBI names (never null) */ public List getDbiNames() { @@ -284,6 +287,9 @@ public Dbi openDbi(final String name, final Comparator comparator, /** * Open the {@link Dbi}. * + *

+ * This method must not be called from concurrent threads. + * * @param name name of the database (or null if no name is required) * @param flags to open the database with * @return a database that is ready to use From 9f821c0eaa33ec12f226c9396c1a1d6e005c0e33 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 3 Feb 2023 11:50:50 +1100 Subject: [PATCH 152/322] Additional JavaDocs related to #195 --- src/main/java/org/lmdbjava/Env.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index 1ca90367..a9611339 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -285,10 +285,7 @@ public Dbi openDbi(final String name, final Comparator comparator, } /** - * Open the {@link Dbi}. - * - *

- * This method must not be called from concurrent threads. + * Convenience method that opens a {@link Dbi} with a UTF-8 database name. * * @param name name of the database (or null if no name is required) * @param flags to open the database with @@ -315,6 +312,10 @@ public Dbi openDbi(final byte[] name, final DbiFlags... flags) { * comparator is typically more efficient (as there is no need for the native * library to call back into Java for the comparator result). * + *

+ * This method (and its overloaded convenience variants) must not be called + * from concurrent threads. + * * @param name name of the database (or null if no name is required) * @param comparator custom comparator callback (or null to use LMDB default) * @param flags to open the database with From 4182ad1b6b9912708ca2dfc8e054941c2aad83d2 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Fri, 3 Feb 2023 12:11:31 +1100 Subject: [PATCH 153/322] Dbi.openDbi(..) to support user-provided Txn (fixes #192) --- src/main/java/org/lmdbjava/Env.java | 46 +++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index a9611339..5de8bd5a 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -265,13 +265,11 @@ public boolean isReadOnly() { * @return a database that is ready to use */ public Dbi openDbi(final String name, final DbiFlags... flags) { - final byte[] nameBytes = name == null ? null : name.getBytes(UTF_8); - return openDbi(nameBytes, flags); + return openDbi(name, null, flags); } /** - * Convenience method that opens a {@link Dbi} with a UTF-8 database name - * and custom comparator. + * Convenience method that opens a {@link Dbi} with a UTF-8 database name. * * @param name name of the database (or null if no name is required) * @param comparator custom comparator callback (or null to use LMDB default) @@ -285,22 +283,44 @@ public Dbi openDbi(final String name, final Comparator comparator, } /** - * Convenience method that opens a {@link Dbi} with a UTF-8 database name. + * Convenience method that opens a {@link Dbi}. * * @param name name of the database (or null if no name is required) * @param flags to open the database with * @return a database that is ready to use */ public Dbi openDbi(final byte[] name, final DbiFlags... flags) { + return openDbi(name, null, flags); + } + + /** + * Convenience method that opens a {@link Dbi} inside a private transaction. + * + *

+ * This method will automatically commit the private transaction before + * returning. This ensures the Dbi is available in the + * Env. + * + * @param name name of the database (or null if no name is required) + * @param comparator custom comparator callback (or null to use LMDB default) + * @param flags to open the database with + * @return a database that is ready to use + */ + public Dbi openDbi(final byte[] name, final Comparator comparator, + final DbiFlags... flags) { try (Txn txn = readOnly ? txnRead() : txnWrite()) { - final Dbi dbi = new Dbi<>(this, txn, name, null, flags); + final Dbi dbi = openDbi(txn, name, comparator, flags); txn.commit(); // even RO Txns require a commit to retain Dbi in Env return dbi; } } /** - * Open the {@link Dbi}. + * Open the {@link Dbi} using the passed {@link Txn}. + * + *

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

* If a custom comparator is specified, this comparator is called from LMDB @@ -316,18 +336,20 @@ public Dbi openDbi(final byte[] name, final DbiFlags... flags) { * This method (and its overloaded convenience variants) must not be called * from concurrent threads. * + * @param txn transaction to use (required; not closed) * @param name name of the database (or null if no name is required) * @param comparator custom comparator callback (or null to use LMDB default) * @param flags to open the database with * @return a database that is ready to use */ - public Dbi openDbi(final byte[] name, final Comparator comparator, + public Dbi openDbi(final Txn txn, final byte[] name, + final Comparator comparator, final DbiFlags... flags) { - try (Txn txn = readOnly ? txnRead() : txnWrite()) { - final Dbi dbi = new Dbi<>(this, txn, name, comparator, flags); - txn.commit(); // even RO Txns require a commit to retain Dbi in Env - return dbi; + if (SHOULD_CHECK) { + requireNonNull(txn); + txn.checkReady(); } + return new Dbi<>(this, txn, name, comparator, flags); } /** From 7a007fa3660d560c8a8b18daca2e45f0b4eab9ac Mon Sep 17 00:00:00 2001 From: "lgtm-com[bot]" <43144390+lgtm-com[bot]@users.noreply.github.com> Date: Fri, 3 Feb 2023 12:26:24 +1100 Subject: [PATCH 154/322] Add CodeQL workflow for GitHub code scanning (#204) Co-authored-by: LGTM Migrator --- .github/workflows/codeql.yml | 41 ++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..dd93488f --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,41 @@ +name: "CodeQL" + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + schedule: + - cron: "12 1 * * 0" + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ java ] + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + queries: +security-and-quality + + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{ matrix.language }}" From 66d9f7abc83a50f73d5d149b733a3edf6ebc524a Mon Sep 17 00:00:00 2001 From: danielcranford Date: Fri, 3 Feb 2023 16:17:48 -0500 Subject: [PATCH 155/322] Replacing OneToOneConcurrentArrayQueue with ArrayDeque since it is thread local (#201) Co-authored-by: dcranford --- src/main/java/org/lmdbjava/DirectBufferProxy.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/lmdbjava/DirectBufferProxy.java b/src/main/java/org/lmdbjava/DirectBufferProxy.java index 2a6b67c3..ac46dec5 100644 --- a/src/main/java/org/lmdbjava/DirectBufferProxy.java +++ b/src/main/java/org/lmdbjava/DirectBufferProxy.java @@ -27,11 +27,11 @@ import static org.lmdbjava.UnsafeAccess.UNSAFE; import java.nio.ByteBuffer; +import java.util.ArrayDeque; import jnr.ffi.Pointer; import org.agrona.DirectBuffer; import org.agrona.MutableDirectBuffer; -import org.agrona.concurrent.OneToOneConcurrentArrayQueue; import org.agrona.concurrent.UnsafeBuffer; /** @@ -55,8 +55,8 @@ public final class DirectBufferProxy extends BufferProxy { * 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 OneToOneConcurrentArrayQueue<>(16)); + private static final ThreadLocal> BUFFERS + = withInitial(() -> new ArrayDeque<>(16)); private DirectBufferProxy() { } @@ -100,7 +100,7 @@ public static int compareBuff(final DirectBuffer o1, final DirectBuffer o2) { @Override protected DirectBuffer allocate() { - final OneToOneConcurrentArrayQueue q = BUFFERS.get(); + final ArrayDeque q = BUFFERS.get(); final DirectBuffer buffer = q.poll(); if (buffer != null && buffer.capacity() >= 0) { @@ -118,7 +118,7 @@ protected int compare(final DirectBuffer o1, final DirectBuffer o2) { @Override protected void deallocate(final DirectBuffer buff) { - final OneToOneConcurrentArrayQueue q = BUFFERS.get(); + final ArrayDeque q = BUFFERS.get(); q.offer(buff); } From 3db0defb700e4ca337ae9824358d5589057b209e Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 4 Feb 2023 10:06:32 +1100 Subject: [PATCH 156/322] Add protection for closed env (#186) --- pom.xml | 8 ++ src/main/java/org/lmdbjava/Cursor.java | 20 +++- src/main/java/org/lmdbjava/Dbi.java | 15 ++- src/main/java/org/lmdbjava/Env.java | 6 + src/main/java/org/lmdbjava/Txn.java | 21 ++++ .../java/org/lmdbjava/CursorIterableTest.java | 54 +++++++++ src/test/java/org/lmdbjava/CursorTest.java | 81 ++++++++++++++ src/test/java/org/lmdbjava/DbiTest.java | 103 ++++++++++++++++++ src/test/java/org/lmdbjava/TxnTest.java | 45 ++++++++ 9 files changed, 349 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 9067cb38..b1a87cdd 100644 --- a/pom.xml +++ b/pom.xml @@ -143,6 +143,14 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + 1 + false + + org.basepom.maven duplicate-finder-maven-plugin diff --git a/src/main/java/org/lmdbjava/Cursor.java b/src/main/java/org/lmdbjava/Cursor.java index bcbbb61c..aa98a9ea 100644 --- a/src/main/java/org/lmdbjava/Cursor.java +++ b/src/main/java/org/lmdbjava/Cursor.java @@ -51,13 +51,15 @@ public final class Cursor implements AutoCloseable { private final KeyVal kv; private final Pointer ptrCursor; private Txn txn; + private final Env env; - Cursor(final Pointer ptr, final Txn txn) { + Cursor(final Pointer ptr, final Txn txn, final Env env) { requireNonNull(ptr); requireNonNull(txn); this.ptrCursor = ptr; this.txn = txn; this.kv = txn.newKeyVal(); + this.env = env; } /** @@ -73,8 +75,11 @@ public void close() { return; } kv.close(); - if (SHOULD_CHECK && !txn.isReadOnly()) { - txn.checkReady(); + if (SHOULD_CHECK) { + env.checkNotClosed(); + if (!txn.isReadOnly()) { + txn.checkReady(); + } } LIB.mdb_cursor_close(ptrCursor); closed = true; @@ -91,6 +96,7 @@ public void close() { */ public long count() { if (SHOULD_CHECK) { + env.checkNotClosed(); checkNotClosed(); txn.checkReady(); } @@ -109,6 +115,7 @@ public long count() { */ public void delete(final PutFlags... f) { if (SHOULD_CHECK) { + env.checkNotClosed(); checkNotClosed(); txn.checkReady(); txn.checkWritesAllowed(); @@ -138,6 +145,7 @@ public boolean get(final T key, final T data, final SeekOp op) { if (SHOULD_CHECK) { requireNonNull(key); requireNonNull(op); + env.checkNotClosed(); checkNotClosed(); txn.checkReady(); } @@ -168,6 +176,7 @@ public boolean get(final T key, final GetOp op) { if (SHOULD_CHECK) { requireNonNull(key); requireNonNull(op); + env.checkNotClosed(); checkNotClosed(); txn.checkReady(); } @@ -238,6 +247,7 @@ public boolean put(final T key, final T val, final PutFlags... op) { if (SHOULD_CHECK) { requireNonNull(key); requireNonNull(val); + env.checkNotClosed(); checkNotClosed(); txn.checkReady(); txn.checkWritesAllowed(); @@ -281,6 +291,7 @@ public void putMultiple(final T key, final T val, final int elements, requireNonNull(txn); requireNonNull(key); requireNonNull(val); + env.checkNotClosed(); txn.checkReady(); txn.checkWritesAllowed(); } @@ -311,6 +322,7 @@ public void putMultiple(final T key, final T val, final int elements, public void renew(final Txn newTxn) { if (SHOULD_CHECK) { requireNonNull(newTxn); + env.checkNotClosed(); checkNotClosed(); this.txn.checkReadOnly(); // existing newTxn.checkReadOnly(); @@ -339,6 +351,7 @@ public void renew(final Txn newTxn) { public T reserve(final T key, final int size, final PutFlags... op) { if (SHOULD_CHECK) { requireNonNull(key); + env.checkNotClosed(); checkNotClosed(); txn.checkReady(); txn.checkWritesAllowed(); @@ -361,6 +374,7 @@ public T reserve(final T key, final int size, final PutFlags... op) { public boolean seek(final SeekOp op) { if (SHOULD_CHECK) { requireNonNull(op); + env.checkNotClosed(); checkNotClosed(); txn.checkReady(); } diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index 96339873..3f36bbeb 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -104,6 +104,9 @@ public final class Dbi { */ public void close() { clean(); + if (SHOULD_CHECK) { + env.checkNotClosed(); + } LIB.mdb_dbi_close(env.pointer(), ptr); } @@ -199,6 +202,7 @@ public void drop(final Txn txn) { public void drop(final Txn txn, final boolean delete) { if (SHOULD_CHECK) { requireNonNull(txn); + env.checkNotClosed(); txn.checkReady(); txn.checkWritesAllowed(); } @@ -228,6 +232,7 @@ public T get(final Txn txn, final T key) { if (SHOULD_CHECK) { requireNonNull(txn); requireNonNull(key); + env.checkNotClosed(); txn.checkReady(); } txn.kv().keyIn(key); @@ -295,6 +300,7 @@ public CursorIterable iterate(final Txn txn, final KeyRange range, if (SHOULD_CHECK) { requireNonNull(txn); requireNonNull(range); + env.checkNotClosed(); txn.checkReady(); } final Comparator useComp; @@ -313,6 +319,9 @@ public CursorIterable iterate(final Txn txn, final KeyRange range, * @return the list of flags this Dbi was created with */ public List listFlags(final Txn txn) { + if (SHOULD_CHECK) { + env.checkNotClosed(); + } final IntByReference resultPtr = new IntByReference(); checkRc(LIB.mdb_dbi_flags(txn.pointer(), ptr, resultPtr)); @@ -348,11 +357,12 @@ public List listFlags(final Txn txn) { public Cursor openCursor(final Txn txn) { if (SHOULD_CHECK) { requireNonNull(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); + return new Cursor<>(cursorPtr.getValue(), txn, env); } /** @@ -392,6 +402,7 @@ public boolean put(final Txn txn, final T key, final T val, requireNonNull(txn); requireNonNull(key); requireNonNull(val); + env.checkNotClosed(); txn.checkReady(); txn.checkWritesAllowed(); } @@ -434,6 +445,7 @@ public T reserve(final Txn txn, final T key, final int size, if (SHOULD_CHECK) { requireNonNull(txn); requireNonNull(key); + env.checkNotClosed(); txn.checkReady(); txn.checkWritesAllowed(); } @@ -455,6 +467,7 @@ public T reserve(final Txn txn, final T key, final int size, public Stat stat(final Txn txn) { if (SHOULD_CHECK) { requireNonNull(txn); + env.checkNotClosed(); txn.checkReady(); } final MDB_stat stat = new MDB_stat(RUNTIME); diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index 5de8bd5a..45db5064 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -423,6 +423,12 @@ Pointer pointer() { return ptr; } + void checkNotClosed() { + if (closed) { + throw new AlreadyClosedException(); + } + } + private void validateDirectoryEmpty(final File path) { if (!path.exists()) { throw new InvalidCopyDestination("Path does not exist"); diff --git a/src/main/java/org/lmdbjava/Txn.java b/src/main/java/org/lmdbjava/Txn.java index f21552e7..f3071152 100644 --- a/src/main/java/org/lmdbjava/Txn.java +++ b/src/main/java/org/lmdbjava/Txn.java @@ -22,6 +22,7 @@ import static jnr.ffi.Memory.allocateDirect; import static jnr.ffi.NativeType.ADDRESS; +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; @@ -49,6 +50,7 @@ public final class Txn implements AutoCloseable { private final BufferProxy proxy; private final Pointer ptr; private final boolean readOnly; + private final Env env; private State state; Txn(final Env env, final Txn parent, final BufferProxy proxy, @@ -60,6 +62,7 @@ public final class Txn implements AutoCloseable { if (env.isReadOnly() && !this.readOnly) { throw new EnvIsReadOnly(); } + this.env = env; this.parent = parent; if (parent != null && parent.isReadOnly() != this.readOnly) { throw new IncompatibleParent(); @@ -76,6 +79,9 @@ public final class Txn implements AutoCloseable { * Aborts this transaction. */ public void abort() { + if (SHOULD_CHECK) { + env.checkNotClosed(); + } checkReady(); state = DONE; LIB.mdb_txn_abort(ptr); @@ -91,6 +97,9 @@ public void abort() { */ @Override public void close() { + if (SHOULD_CHECK) { + env.checkNotClosed(); + } if (state == RELEASED) { return; } @@ -105,6 +114,9 @@ public void close() { * Commits this transaction. */ public void commit() { + if (SHOULD_CHECK) { + env.checkNotClosed(); + } checkReady(); state = DONE; checkRc(LIB.mdb_txn_commit(ptr)); @@ -116,6 +128,9 @@ public void commit() { * @return A transaction ID, valid if input is an active transaction */ public long getId() { + if (SHOULD_CHECK) { + env.checkNotClosed(); + } return LIB.mdb_txn_id(ptr); } @@ -153,6 +168,9 @@ public T key() { * Renews a read-only transaction previously released by {@link #reset()}. */ public void renew() { + if (SHOULD_CHECK) { + env.checkNotClosed(); + } if (state != RESET) { throw new NotResetException(); } @@ -166,6 +184,9 @@ public void renew() { * can be reused upon calling {@link #renew()}. */ public void reset() { + if (SHOULD_CHECK) { + env.checkNotClosed(); + } checkReadOnly(); if (state != READY && state != DONE) { throw new ResetException(); diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index 247ef791..af7ca27f 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -63,6 +63,7 @@ import java.util.NoSuchElementException; import com.google.common.primitives.UnsignedBytes; +import org.hamcrest.Matchers; import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -302,6 +303,59 @@ public void removeOddElements() { verify(all(), 4, 8); } + @Test(expected = Env.AlreadyClosedException.class) + public void nextWithClosedEnvTest() { + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + + env.close(); + c.next(); + } + } + } + + @Test(expected = Env.AlreadyClosedException.class) + public void removeWithClosedEnvTest() { + try (Txn txn = env.txnWrite()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + + final KeyVal keyVal = c.next(); + assertThat(keyVal, Matchers.notNullValue()); + + env.close(); + c.remove(); + } + } + } + + @Test(expected = Env.AlreadyClosedException.class) + public void hasNextWithClosedEnvTest() { + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + + env.close(); + c.hasNext(); + } + } + } + + @Test(expected = Env.AlreadyClosedException.class) + public void forEachRemainingWithClosedEnvTest() { + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); + + env.close(); + c.forEachRemaining(keyVal -> { + + }); + } + } + } + private void verify(final KeyRange range, final int... expected) { verify(range, null, expected); } diff --git a/src/test/java/org/lmdbjava/CursorTest.java b/src/test/java/org/lmdbjava/CursorTest.java index 51b78806..17fbacc7 100644 --- a/src/test/java/org/lmdbjava/CursorTest.java +++ b/src/test/java/org/lmdbjava/CursorTest.java @@ -40,6 +40,8 @@ import static org.lmdbjava.PutFlags.MDB_NOOVERWRITE; import static org.lmdbjava.SeekOp.MDB_FIRST; import static org.lmdbjava.SeekOp.MDB_GET_BOTH; +import static org.lmdbjava.SeekOp.MDB_LAST; +import static org.lmdbjava.SeekOp.MDB_NEXT; import static org.lmdbjava.TestUtils.DB_1; import static org.lmdbjava.TestUtils.POSIX_MODE; import static org.lmdbjava.TestUtils.bb; @@ -47,6 +49,7 @@ import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.function.Consumer; import org.junit.After; import org.junit.Before; @@ -96,6 +99,59 @@ public void closedCursorRejectsSubsequentGets() { } } + @Test(expected = Env.AlreadyClosedException.class) + public void closedEnvRejectsSeekFirstCall() { + doEnvClosedTest(null, c -> c.seek(MDB_FIRST)); + } + + @Test(expected = Env.AlreadyClosedException.class) + public void closedEnvRejectsSeekLastCall() { + doEnvClosedTest(null, c -> c.seek(MDB_LAST)); + } + + @Test(expected = Env.AlreadyClosedException.class) + public void closedEnvRejectsSeekNextCall() { + doEnvClosedTest(null, c -> c.seek(MDB_NEXT)); + } + + @Test(expected = Env.AlreadyClosedException.class) + public void closedEnvRejectsCloseCall() { + doEnvClosedTest(null, Cursor::close); + } + + @Test(expected = Env.AlreadyClosedException.class) + public void closedEnvRejectsFirstCall() { + doEnvClosedTest(null, Cursor::first); + } + + @Test(expected = Env.AlreadyClosedException.class) + public void closedEnvRejectsLastCall() { + doEnvClosedTest(null, Cursor::last); + } + + @Test(expected = Env.AlreadyClosedException.class) + public void closedEnvRejectsPrevCall() { + doEnvClosedTest( + c -> { + c.first(); + assertThat(c.key().getInt(), is(1)); + assertThat(c.val().getInt(), is(10)); + c.next(); + }, + Cursor::prev); + } + + @Test(expected = Env.AlreadyClosedException.class) + public void closedEnvRejectsDeleteCall() { + doEnvClosedTest( + c -> { + c.first(); + assertThat(c.key().getInt(), is(1)); + assertThat(c.val().getInt(), is(10)); + }, + Cursor::delete); + } + @Test public void count() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); @@ -338,4 +394,29 @@ public void testCursorByteBufferDuplicate() { } } } + + private void doEnvClosedTest(final Consumer> workBeforeEnvClosed, + final Consumer> workAfterEnvClose) { + final Dbi db = env.openDbi(DB_1, MDB_CREATE); + + db.put(bb(1), bb(10)); + db.put(bb(2), bb(20)); + db.put(bb(2), bb(30)); + db.put(bb(4), bb(40)); + + try (Txn txn = env.txnWrite()) { + try (Cursor c = db.openCursor(txn)) { + + if (workBeforeEnvClosed != null) { + workBeforeEnvClosed.accept(c); + } + + env.close(); + + if (workAfterEnvClose != null) { + workAfterEnvClose.accept(c); + } + } + } + } } diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index e52bd28d..6db5a9b8 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -67,6 +67,7 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiConsumer; import org.agrona.concurrent.UnsafeBuffer; import org.hamcrest.Matchers; @@ -77,6 +78,7 @@ import org.junit.rules.TemporaryFolder; import org.lmdbjava.CursorIterable.KeyVal; import org.lmdbjava.Dbi.DbFullException; +import org.lmdbjava.Env.AlreadyClosedException; import org.lmdbjava.Env.MapFullException; import org.lmdbjava.LmdbNativeException.ConstantDerivedException; @@ -492,4 +494,105 @@ public void testParallelWritesStress() { }); } + @Test(expected = AlreadyClosedException.class) + public void closedEnvRejectsOpenCall() { + env.close(); + env.openDbi(DB_1, MDB_CREATE); + } + + @Test(expected = AlreadyClosedException.class) + public void closedEnvRejectsCloseCall() { + doEnvClosedTest( + null, + (db, txn) -> db.close()); + } + + @Test(expected = AlreadyClosedException.class) + public void closedEnvRejectsGetCall() { + doEnvClosedTest( + (db, txn) -> { + final ByteBuffer valBuf = db.get(txn, bb(1)); + assertThat(valBuf.getInt(), is(10)); + }, + (db, txn) -> db.get(txn, bb(2))); + } + + @Test(expected = AlreadyClosedException.class) + public void closedEnvRejectsPutCall() { + doEnvClosedTest( + null, + (db, txn) -> db.put(bb(5), bb(50))); + } + + @Test(expected = AlreadyClosedException.class) + public void closedEnvRejectsPutWithTxnCall() { + doEnvClosedTest( + null, + (db, txn) -> { + db.put(txn, bb(5), bb(50)); + }); + } + + @Test(expected = AlreadyClosedException.class) + public void closedEnvRejectsIterateCall() { + doEnvClosedTest(null, Dbi::iterate); + } + + @Test(expected = AlreadyClosedException.class) + public void closedEnvRejectsDropCall() { + doEnvClosedTest( + null, + Dbi::drop); + } + + @Test(expected = AlreadyClosedException.class) + public void closedEnvRejectsDropAndDeleteCall() { + doEnvClosedTest( + null, + (db, txn) -> db.drop(txn, true)); + } + + @Test(expected = AlreadyClosedException.class) + public void closedEnvRejectsOpenCursorCall() { + doEnvClosedTest( + null, + Dbi::openCursor); + } + + @Test(expected = AlreadyClosedException.class) + public void closedEnvRejectsReserveCall() { + doEnvClosedTest( + null, + (db, txn) -> db.reserve(txn, bb(1), 32, MDB_NOOVERWRITE)); + } + + @Test(expected = AlreadyClosedException.class) + public void closedEnvRejectsStatCall() { + doEnvClosedTest(null, Dbi::stat); + } + + private void doEnvClosedTest( + final BiConsumer, Txn> workBeforeEnvClosed, + final BiConsumer, Txn> workAfterEnvClose) { + final Dbi db = env.openDbi(DB_1, MDB_CREATE); + + db.put(bb(1), bb(10)); + db.put(bb(2), bb(20)); + db.put(bb(2), bb(30)); + db.put(bb(4), bb(40)); + + try (Txn txn = env.txnWrite()) { + + if (workBeforeEnvClosed != null) { + workBeforeEnvClosed.accept(db, txn); + } + + env.close(); + + if (workAfterEnvClose != null) { + workAfterEnvClose.accept(db, txn); + } + } + } + } diff --git a/src/test/java/org/lmdbjava/TxnTest.java b/src/test/java/org/lmdbjava/TxnTest.java index f8a0b5d3..c115be0b 100644 --- a/src/test/java/org/lmdbjava/TxnTest.java +++ b/src/test/java/org/lmdbjava/TxnTest.java @@ -230,6 +230,42 @@ public void txConstructionDeniedIfEnvClosed() { env.txnRead(); } + @Test(expected = AlreadyClosedException.class) + public void txRenewDeniedIfEnvClosed() { + final Txn txnRead = env.txnRead(); + txnRead.close(); + env.close(); + txnRead.renew(); + } + + @Test(expected = AlreadyClosedException.class) + public void txCloseDeniedIfEnvClosed() { + final Txn txnRead = env.txnRead(); + env.close(); + txnRead.close(); + } + + @Test(expected = AlreadyClosedException.class) + public void txCommitDeniedIfEnvClosed() { + final Txn txnRead = env.txnRead(); + env.close(); + txnRead.commit(); + } + + @Test(expected = AlreadyClosedException.class) + public void txAbortDeniedIfEnvClosed() { + final Txn txnRead = env.txnRead(); + env.close(); + txnRead.abort(); + } + + @Test(expected = AlreadyClosedException.class) + public void txResetDeniedIfEnvClosed() { + final Txn txnRead = env.txnRead(); + env.close(); + txnRead.reset(); + } + @Test public void txParent() { try (Txn txRoot = env.txnWrite(); @@ -239,6 +275,15 @@ public void txParent() { } } + @Test(expected = AlreadyClosedException.class) + public void txParentDeniedIfEnvClosed() { + try (Txn txRoot = env.txnWrite(); + Txn txChild = env.txn(txRoot)) { + env.close(); + assertThat(txChild.getParent(), is(txRoot)); + } + } + @Test(expected = IncompatibleParent.class) public void txParentROChildRWIncompatible() { try (Txn txRoot = env.txnRead()) { From d7711ac930860d39c780e20bcde1a3d45641debc Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 4 Feb 2023 10:21:29 +1100 Subject: [PATCH 157/322] GitHub Actions should not use forks --- .github/workflows/maven.yml | 2 +- pom.xml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index a551b802..56d8b1f0 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -82,7 +82,7 @@ jobs: gpg-passphrase: MAVEN_GPG_PASSPHRASE - name: Publish Maven package - run: mvn -Possrh-deploy deploy + run: mvn -B -Possrh-deploy deploy -DskipTests env: MAVEN_GPG_PASSPHRASE: ${{ secrets.gpg_passphrase }} MAVEN_USERNAME: ${{ secrets.nexus_username }} diff --git a/pom.xml b/pom.xml index b1a87cdd..4ce1dab1 100644 --- a/pom.xml +++ b/pom.xml @@ -229,6 +229,8 @@ maven-surefire-plugin --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED + 1 + false From 0621fd4c8cf6f9e7ca2d65083e19b82b78e7c112 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 4 Feb 2023 10:39:02 +1100 Subject: [PATCH 158/322] GitHub Actions Java version tests --- .github/workflows/maven.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 56d8b1f0..011938b1 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -19,7 +19,7 @@ jobs: uses: actions/setup-java@v3 with: distribution: zulu - java-version: 11 + java-version: 17 cache: maven - name: Build with Maven @@ -35,7 +35,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - java: [8, 11, 16] + java: [8, 11, 17, 19] steps: - name: Check out Git repository From be7359ac9d0f32284e720a1ed05460582e143f62 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 4 Feb 2023 10:45:08 +1100 Subject: [PATCH 159/322] Reflect current Java versions tested by CI --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3b8f4753..450f814f 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ * Mature code (commenced in 2016) and used for heavy production workloads (eg > 500 TB of HFT data) * Actively maintained and with a "Zero Bug Policy" before every release (see [issues](https://github.com/lmdbjava/lmdbjava/issues)) * Available from [Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.lmdbjava%22%20AND%20a%3A%22lmdbjava%22) and [OSS Sonatype Snapshots](https://oss.sonatype.org/content/repositories/snapshots/org/lmdbjava/lmdbjava) -* [Continuous integration](https://github.com/lmdbjava/lmdbjava/actions) testing on Linux, Windows and macOS with Java 8, 11 and 16 +* [Continuous integration](https://github.com/lmdbjava/lmdbjava/actions) testing on Linux, Windows and macOS with Java 8, 11, 17 and 19 ### Performance From 9f7435266877eb2441baddc84e31e39ae218be4f Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 4 Feb 2023 11:15:44 +1100 Subject: [PATCH 160/322] GitHub Actions to use Verifier for OS/JVM test matrix The Verifier is designed to test the accuracy of LMDB storage and is therefore a useful test to perform on each target platform. It is noted that other GitHub Actions execute a full Maven verification. --- .github/workflows/maven.yml | 6 +++--- pom.xml | 4 ++-- src/test/java/org/lmdbjava/VerifierTest.java | 3 ++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 011938b1..85d4385f 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -8,7 +8,7 @@ on: jobs: build: - name: Java 11 Build and Verify + name: Latest Java LTS Build and Verify runs-on: ubuntu-latest steps: @@ -48,8 +48,8 @@ jobs: java-version: ${{ matrix.java }} cache: maven - - name: Test with Maven - run: mvn -B test + - name: Execute verifier + run: mvn -B test -Dtest=VerifierTest -DverificationSeconds=10 - name: Upload Surefire reports on test failure uses: actions/upload-artifact@v3 diff --git a/pom.xml b/pom.xml index 4ce1dab1..c1559219 100644 --- a/pom.xml +++ b/pom.xml @@ -229,8 +229,8 @@ maven-surefire-plugin --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED - 1 - false + 1 + false diff --git a/src/test/java/org/lmdbjava/VerifierTest.java b/src/test/java/org/lmdbjava/VerifierTest.java index 27bb598b..41f4e774 100644 --- a/src/test/java/org/lmdbjava/VerifierTest.java +++ b/src/test/java/org/lmdbjava/VerifierTest.java @@ -52,7 +52,8 @@ public void verification() throws IOException { .setMapSize(MEBIBYTES.toBytes(10)) .open(path, MDB_NOSUBDIR)) { final Verifier v = new Verifier(env); - assertThat(v.runFor(2, TimeUnit.SECONDS), greaterThan(1L)); + final int seconds = Integer.getInteger("verificationSeconds", 2); + assertThat(v.runFor(seconds, TimeUnit.SECONDS), greaterThan(1L)); } } From be2a15b348668ca5c97ab58a4ac9b396d21256da Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 4 Feb 2023 11:28:16 +1100 Subject: [PATCH 161/322] [maven-release-plugin] prepare release lmdbjava-0.8.3 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index c1559219..8830cb89 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ org.lmdbjava lmdbjava - 0.8.3-SNAPSHOT + 0.8.3 jar LmdbJava Low latency Java API for the ultra-fast, embedded Symas Lightning Database (LMDB) @@ -206,7 +206,7 @@ scm:git:git@github.com:${github.org}/${github.repo}.git scm:git:git@github.com:${github.org}/${github.repo}.git git@github.com:${github.org}/${github.repo}.git - HEAD + lmdbjava-0.8.3 GitHub Issues From e4cf6e98e52b570baf77af52f253b8a97ae70f4d Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 4 Feb 2023 11:28:23 +1100 Subject: [PATCH 162/322] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 8830cb89..20b02b04 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ org.lmdbjava lmdbjava - 0.8.3 + 0.9.0-SNAPSHOT jar LmdbJava Low latency Java API for the ultra-fast, embedded Symas Lightning Database (LMDB) @@ -206,7 +206,7 @@ scm:git:git@github.com:${github.org}/${github.repo}.git scm:git:git@github.com:${github.org}/${github.repo}.git git@github.com:${github.org}/${github.repo}.git - lmdbjava-0.8.3 + HEAD GitHub Issues From 3524995b97c86e0cd66c64ce648693cad9feaa0c Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 4 Feb 2023 15:23:49 +1100 Subject: [PATCH 163/322] Refactor Comparator handling (fixes #199) --- src/main/java/org/lmdbjava/BufferProxy.java | 15 ++-- .../java/org/lmdbjava/ByteArrayProxy.java | 7 +- src/main/java/org/lmdbjava/ByteBufProxy.java | 7 +- .../java/org/lmdbjava/ByteBufferProxy.java | 20 ++++- src/main/java/org/lmdbjava/Dbi.java | 54 +++--------- .../java/org/lmdbjava/DirectBufferProxy.java | 7 +- src/main/java/org/lmdbjava/Env.java | 88 ++++++++++++++----- src/main/java/org/lmdbjava/Txn.java | 15 ---- .../java/org/lmdbjava/ComparatorTest.java | 18 ++-- .../java/org/lmdbjava/CursorIterableTest.java | 22 +++-- src/test/java/org/lmdbjava/DbiTest.java | 14 +-- 11 files changed, 157 insertions(+), 110 deletions(-) diff --git a/src/main/java/org/lmdbjava/BufferProxy.java b/src/main/java/org/lmdbjava/BufferProxy.java index bd58d7b6..aff732bc 100644 --- a/src/main/java/org/lmdbjava/BufferProxy.java +++ b/src/main/java/org/lmdbjava/BufferProxy.java @@ -22,6 +22,8 @@ import static java.lang.Long.BYTES; +import java.util.Comparator; + import jnr.ffi.Pointer; /** @@ -61,17 +63,16 @@ public abstract class BufferProxy { protected abstract T allocate(); /** - * Compare the two buffers. + * Get a suitable default {@link Comparator} given the provided flags. * *

- * Implemented as a protected method to discourage use of the buffer proxy - * in collections etc (given by design it wraps a temporary value only). + * The provided comparator must strictly match the lexicographical order of + * keys in the native LMDB database. * - * @param o1 left operand - * @param o2 right operand - * @return as per {@link Comparable} + * @param flags for the database + * @return a comparator that can be used (never null) */ - protected abstract int compare(T o1, T o2); + protected abstract Comparator getComparator(DbiFlags... flags); /** * Deallocate a buffer that was previously provided by {@link #allocate()}. diff --git a/src/main/java/org/lmdbjava/ByteArrayProxy.java b/src/main/java/org/lmdbjava/ByteArrayProxy.java index f6ddbef2..3fe8184a 100644 --- a/src/main/java/org/lmdbjava/ByteArrayProxy.java +++ b/src/main/java/org/lmdbjava/ByteArrayProxy.java @@ -24,6 +24,7 @@ import static org.lmdbjava.Library.RUNTIME; import java.util.Arrays; +import java.util.Comparator; import jnr.ffi.Pointer; import jnr.ffi.provider.MemoryManager; @@ -78,7 +79,6 @@ protected byte[] allocate() { return new byte[0]; } - @Override protected int compare(final byte[] o1, final byte[] o2) { return compareArrays(o1, o2); } @@ -93,6 +93,11 @@ protected byte[] getBytes(final byte[] buffer) { return Arrays.copyOf(buffer, buffer.length); } + @Override + protected Comparator getComparator(final DbiFlags... flags) { + return this::compare; + } + @Override protected void in(final byte[] buffer, final Pointer ptr, final long ptrAddr) { diff --git a/src/main/java/org/lmdbjava/ByteBufProxy.java b/src/main/java/org/lmdbjava/ByteBufProxy.java index d8bbc452..26351676 100644 --- a/src/main/java/org/lmdbjava/ByteBufProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufProxy.java @@ -25,6 +25,7 @@ import static org.lmdbjava.UnsafeAccess.UNSAFE; import java.lang.reflect.Field; +import java.util.Comparator; import io.netty.buffer.ByteBuf; import io.netty.buffer.PooledByteBufAllocator; @@ -106,11 +107,15 @@ protected ByteBuf allocate() { throw new IllegalStateException("Netty buffer must be " + NAME); } - @Override protected int compare(final ByteBuf o1, final ByteBuf o2) { return o1.compareTo(o2); } + @Override + protected Comparator getComparator(final DbiFlags... flags) { + return this::compare; + } + @Override protected void deallocate(final ByteBuf buff) { buff.release(); diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index 0d1781b3..8eb95da8 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -26,13 +26,17 @@ import static java.nio.ByteOrder.BIG_ENDIAN; import static java.nio.ByteOrder.LITTLE_ENDIAN; import static java.util.Objects.requireNonNull; +import static org.lmdbjava.DbiFlags.MDB_INTEGERKEY; import static org.lmdbjava.Env.SHOULD_CHECK; +import static org.lmdbjava.MaskedFlag.isSet; +import static org.lmdbjava.MaskedFlag.mask; import static org.lmdbjava.UnsafeAccess.UNSAFE; import java.lang.reflect.Field; import java.nio.Buffer; import java.nio.ByteBuffer; import java.util.ArrayDeque; +import java.util.Comparator; import jnr.ffi.Pointer; @@ -189,7 +193,21 @@ protected final ByteBuffer allocate() { } @Override - protected final int compare(final ByteBuffer o1, final ByteBuffer o2) { + protected Comparator getComparator(final DbiFlags... flags) { + final int flagInt = mask(flags); + if (isSet(flagInt, MDB_INTEGERKEY)) { + return this::compareCustom; + } + return this::compareDefault; + } + + protected final int compareDefault(final ByteBuffer o1, + final ByteBuffer o2) { + return o1.compareTo(o2); + } + + protected final int compareCustom(final ByteBuffer o1, + final ByteBuffer o2) { return compareBuff(o1, o2); } diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index 3f36bbeb..d0eb0a14 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -56,38 +56,35 @@ public final class Dbi { private final ComparatorCallback ccb; private boolean cleaned; - private final Comparator compFunc; + private final Comparator comparator; private final Env env; private final byte[] name; - private final BufferProxy proxy; private final Pointer ptr; Dbi(final Env env, final Txn txn, final byte[] name, - final Comparator comparator, final DbiFlags... flags) { + final Comparator comparator, final boolean nativeCb, + final BufferProxy proxy, final DbiFlags... flags) { this.env = env; this.name = name == null ? null : Arrays.copyOf(name, name.length); + this.comparator = comparator; final int flagsMask = mask(flags); final Pointer dbiPtr = allocateDirect(RUNTIME, ADDRESS); checkRc(LIB.mdb_dbi_open(txn.pointer(), name, flagsMask, dbiPtr)); ptr = dbiPtr.getPointer(0); - if (comparator == null) { - proxy = null; - compFunc = null; - ccb = null; - } else { - this.proxy = txn.getProxy(); - this.compFunc = comparator; + 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 = compFunc.compare(compKeyA, compKeyB); + final int result = this.comparator.compare(compKeyA, compKeyB); proxy.deallocate(compKeyA); proxy.deallocate(compKeyB); return result; }; LIB.mdb_set_compare(txn.pointer(), ptr, ccb); + } else { + ccb = null; } } @@ -265,51 +262,20 @@ public CursorIterable iterate(final Txn txn) { } /** - * Iterate the database in accordance with the provided {@link KeyRange} and - * default {@link Comparator}. + * Iterate the database in accordance with the provided {@link KeyRange}. * * @param txn transaction handle (not null; not committed) * @param range range of acceptable keys (not null) * @return iterator (never null) */ public CursorIterable iterate(final Txn txn, final KeyRange range) { - return iterate(txn, range, null); - } - - /** - * Iterate the database in accordance with the provided {@link KeyRange} and - * {@link Comparator}. - * - *

- * If a comparator is provided, it must reflect the same ordering as LMDB uses - * for cursor operations (eg first, next, last, previous etc). - * - *

- * If a null comparator is provided, any comparator provided when opening the - * database is used. If no database comparator was specified, the buffer's - * default comparator is used. Such buffer comparators reflect LMDB's default - * lexicographical order. - * - * @param txn transaction handle (not null; not committed) - * @param range range of acceptable keys (not null) - * @param comparator custom comparator for keys (may be null) - * @return iterator (never null) - */ - public CursorIterable iterate(final Txn txn, final KeyRange range, - final Comparator comparator) { if (SHOULD_CHECK) { requireNonNull(txn); requireNonNull(range); env.checkNotClosed(); txn.checkReady(); } - final Comparator useComp; - if (comparator == null) { - useComp = compFunc == null ? txn.comparator() : compFunc; - } else { - useComp = comparator; - } - return new CursorIterable<>(txn, this, range, useComp); + return new CursorIterable<>(txn, this, range, comparator); } /* diff --git a/src/main/java/org/lmdbjava/DirectBufferProxy.java b/src/main/java/org/lmdbjava/DirectBufferProxy.java index ac46dec5..62b49095 100644 --- a/src/main/java/org/lmdbjava/DirectBufferProxy.java +++ b/src/main/java/org/lmdbjava/DirectBufferProxy.java @@ -28,6 +28,7 @@ import java.nio.ByteBuffer; import java.util.ArrayDeque; +import java.util.Comparator; import jnr.ffi.Pointer; import org.agrona.DirectBuffer; @@ -111,7 +112,6 @@ protected DirectBuffer allocate() { } } - @Override protected int compare(final DirectBuffer o1, final DirectBuffer o2) { return compareBuff(o1, o2); } @@ -129,6 +129,11 @@ protected byte[] getBytes(final DirectBuffer buffer) { return dest; } + @Override + protected Comparator getComparator(final DbiFlags... flags) { + return this::compare; + } + @Override protected void in(final DirectBuffer buffer, final Pointer ptr, final long ptrAddr) { diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index 45db5064..66ab7cda 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -258,43 +258,79 @@ public boolean isReadOnly() { } /** - * Convenience method that opens a {@link Dbi} with a UTF-8 database name. + * Convenience method that opens a {@link Dbi} with a UTF-8 database name and + * default {@link Comparator} that is not invoked from native code. * * @param name name of the database (or null if no name is required) * @param flags to open the database with * @return a database that is ready to use */ public Dbi openDbi(final String name, final DbiFlags... flags) { - return openDbi(name, null, flags); + final byte[] nameBytes = name == null ? null : name.getBytes(UTF_8); + return openDbi(nameBytes, null, false, flags); } /** - * Convenience method that opens a {@link Dbi} with a UTF-8 database name. + * Convenience method that opens a {@link Dbi} with a UTF-8 database name and + * associated {@link Comparator} that is not invoked from native code. * * @param name name of the database (or null if no name is required) - * @param comparator custom comparator callback (or null to use LMDB default) + * @param comparator custom comparator callback (or null to use default) * @param flags to open the database with * @return a database that is ready to use */ public Dbi openDbi(final String name, final Comparator comparator, final DbiFlags... flags) { final byte[] nameBytes = name == null ? null : name.getBytes(UTF_8); - return openDbi(nameBytes, comparator, flags); + return openDbi(nameBytes, comparator, false, flags); + } + + /** + * Convenience method that opens a {@link Dbi} with a UTF-8 database name and + * associated {@link Comparator} that may be invoked from native code if + * specified. + * + * @param name name of the database (or null if no name is required) + * @param comparator custom comparator callback (or null to use default) + * @param nativeCb whether native code calls back to the Java comparator + * @param flags to open the database with + * @return a database that is ready to use + */ + public Dbi openDbi(final String name, final Comparator comparator, + final boolean nativeCb, final DbiFlags... flags) { + final byte[] nameBytes = name == null ? null : name.getBytes(UTF_8); + return openDbi(nameBytes, comparator, nativeCb, flags); } /** - * Convenience method that opens a {@link Dbi}. + * Convenience method that opens a {@link Dbi} with a default + * {@link Comparator} that is not invoked from native code. * * @param name name of the database (or null if no name is required) * @param flags to open the database with * @return a database that is ready to use */ public Dbi openDbi(final byte[] name, final DbiFlags... flags) { - return openDbi(name, null, flags); + return openDbi(name, null, false, flags); + } + + /** + * Convenience method that opens a {@link Dbi} with an associated + * {@link Comparator} that is not invoked from native code. + * + * @param name name of the database (or null if no name is required) + * @param comparator custom comparator callback (or null to use LMDB default) + * @param flags to open the database with + * @return a database that is ready to use + */ + public Dbi openDbi(final byte[] name, final Comparator comparator, + final DbiFlags... flags) { + return openDbi(name, comparator, false, flags); } /** - * Convenience method that opens a {@link Dbi} inside a private transaction. + * Convenience method that opens a {@link Dbi} with an associated + * {@link Comparator} that may be invoked from native code if specified. * *

* This method will automatically commit the private transaction before @@ -303,13 +339,14 @@ public Dbi openDbi(final byte[] name, final DbiFlags... flags) { * * @param name name of the database (or null if no name is required) * @param comparator custom comparator callback (or null to use LMDB default) + * @param nativeCb whether native code calls back to the Java comparator * @param flags to open the database with * @return a database that is ready to use */ public Dbi openDbi(final byte[] name, final Comparator comparator, - final DbiFlags... flags) { + final boolean nativeCb, final DbiFlags... flags) { try (Txn txn = readOnly ? txnRead() : txnWrite()) { - final Dbi dbi = openDbi(txn, name, comparator, flags); + final Dbi dbi = openDbi(txn, name, comparator, nativeCb, flags); txn.commit(); // even RO Txns require a commit to retain Dbi in Env return dbi; } @@ -323,14 +360,18 @@ public Dbi openDbi(final byte[] name, final Comparator comparator, * to retain the Dbi in the Env. * *

- * If a custom comparator is specified, this comparator is called from LMDB - * any time it needs to compare two keys. The comparator must be used any time - * any time this database is opened, otherwise database corruption may occur. - * The custom comparator will also be used whenever a {@link CursorIterable} - * is created from the returned {@link Dbi}. If a custom comparator is not - * specified, LMDB's native default lexicographical order is used. The default - * comparator is typically more efficient (as there is no need for the native - * library to call back into Java for the comparator result). + * A {@link Comparator} may be provided when calling this method. Such + * comparator is primarily used by {@link CursorIterable} instances. A + * secondary (but uncommon) use of the comparator is to act as a callback from + * the native library if nativeCb is true. This is + * usually avoided due to the overhead of native code calling back into Java. + * It is instead highly recommended to set the correct {@link DbiFlag}s to + * allow the native library to correctly order the intended keys. + * + *

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

* This method (and its overloaded convenience variants) must not be called @@ -339,17 +380,24 @@ public Dbi openDbi(final byte[] name, final Comparator comparator, * @param txn transaction to use (required; not closed) * @param name name of the database (or null if no name is required) * @param comparator custom comparator callback (or null to use LMDB default) + * @param nativeCb whether native code should call back to the comparator * @param flags to open the database with * @return a database that is ready to use */ public Dbi openDbi(final Txn txn, final byte[] name, - final Comparator comparator, + final Comparator comparator, final boolean nativeCb, final DbiFlags... flags) { if (SHOULD_CHECK) { requireNonNull(txn); txn.checkReady(); } - return new Dbi<>(this, txn, name, comparator, flags); + final Comparator useComparator; + if (comparator == null) { + useComparator = proxy.getComparator(flags); + } else { + useComparator = comparator; + } + return new Dbi<>(this, txn, name, useComparator, nativeCb, proxy, flags); } /** diff --git a/src/main/java/org/lmdbjava/Txn.java b/src/main/java/org/lmdbjava/Txn.java index f3071152..b6d28917 100644 --- a/src/main/java/org/lmdbjava/Txn.java +++ b/src/main/java/org/lmdbjava/Txn.java @@ -34,8 +34,6 @@ import static org.lmdbjava.Txn.State.RESET; import static org.lmdbjava.TxnFlags.MDB_RDONLY_TXN; -import java.util.Comparator; - import jnr.ffi.Pointer; /** @@ -225,19 +223,6 @@ void checkWritesAllowed() { } } - Comparator comparator() { - return proxy::compare; - } - - /** - * Obtain the buffer proxy. - * - * @return proxy (never null) - */ - BufferProxy getProxy() { - return proxy; - } - /** * Return the state of the transaction. * diff --git a/src/test/java/org/lmdbjava/ComparatorTest.java b/src/test/java/org/lmdbjava/ComparatorTest.java index 77c5ca37..f21604ee 100644 --- a/src/test/java/org/lmdbjava/ComparatorTest.java +++ b/src/test/java/org/lmdbjava/ComparatorTest.java @@ -25,6 +25,7 @@ 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; @@ -146,7 +147,8 @@ private static class ByteArrayRunner implements ComparatorRunner { @Override public int compare(final byte[] o1, final byte[] o2) { - return PROXY_BA.compare(o1, o2); + final Comparator c = PROXY_BA.getComparator(); + return c.compare(o1, o2); } } @@ -157,23 +159,25 @@ private static class ByteBufferRunner implements ComparatorRunner { @Override public int compare(final byte[] o1, final byte[] o2) { + final Comparator c = PROXY_OPTIMAL.getComparator(); + // Convert arrays to buffers that are larger than the array, with // limit set at the array length. One buffer bigger than the other. ByteBuffer o1b = arrayToBuffer(o1, o1.length * 3); ByteBuffer o2b = arrayToBuffer(o2, o2.length * 2); - final int result = PROXY_OPTIMAL.compare(o1b, o2b); + final int result = c.compare(o1b, o2b); // Now swap which buffer is bigger o1b = arrayToBuffer(o1, o1.length * 2); o2b = arrayToBuffer(o2, o2.length * 3); - final int result2 = PROXY_OPTIMAL.compare(o1b, o2b); + final int result2 = c.compare(o1b, o2b); assertThat(result2, is(result)); // Now try with buffers sized to the array. o1b = ByteBuffer.wrap(o1); o2b = ByteBuffer.wrap(o2); - final int result3 = PROXY_OPTIMAL.compare(o1b, o2b); + final int result3 = c.compare(o1b, o2b); assertThat(result3, is(result)); @@ -201,7 +205,8 @@ private static class DirectBufferRunner implements ComparatorRunner { public int compare(final byte[] o1, final byte[] o2) { final DirectBuffer o1b = new UnsafeBuffer(o1); final DirectBuffer o2b = new UnsafeBuffer(o2); - return PROXY_DB.compare(o1b, o2b); + final Comparator c = PROXY_DB.getComparator(); + return c.compare(o1b, o2b); } } @@ -240,7 +245,8 @@ public int compare(final byte[] o1, final byte[] o2) { final ByteBuf o2b = DEFAULT.directBuffer(o2.length); o1b.writeBytes(o1); o2b.writeBytes(o2); - return ByteBufProxy.PROXY_NETTY.compare(o1b, o2b); + final Comparator c = PROXY_NETTY.getComparator(); + return c.compare(o1b, o2b); } } diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index af7ca27f..e08e9a0c 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -131,10 +131,14 @@ public void before() throws IOException { .setMaxDbs(1) .open(path, POSIX_MODE, MDB_NOSUBDIR); db = env.openDbi(DB_1, MDB_CREATE); + populateDatabase(db); + } + + private void populateDatabase(final Dbi dbi) { list = new LinkedList<>(); list.addAll(asList(2, 3, 4, 5, 6, 7, 8, 9)); try (Txn txn = env.txnWrite()) { - final Cursor c = db.openCursor(txn); + final Cursor c = dbi.openCursor(txn); c.put(bb(2), bb(3), MDB_NOOVERWRITE); c.put(bb(4), bb(5)); c.put(bb(6), bb(7)); @@ -267,8 +271,10 @@ public void openClosedBackwardTestWithGuava() { bb2.reset(); return guava.compare(array1, array2); }; - verify(openClosedBackward(bb(7), bb(2)), comparator, 6, 4, 2); - verify(openClosedBackward(bb(8), bb(4)), comparator, 6, 4); + final Dbi guavaDbi = env.openDbi(DB_1, comparator, MDB_CREATE); + populateDatabase(guavaDbi); + verify(openClosedBackward(bb(7), bb(2)), guavaDbi, 6, 4, 2); + verify(openClosedBackward(bb(8), bb(4)), guavaDbi, 6, 4); } @Test @@ -356,17 +362,17 @@ public void forEachRemainingWithClosedEnvTest() { } } - private void verify(final KeyRange range, final int... expected) { - verify(range, null, expected); + private void verify(final KeyRange range, + final int... expected) { + verify(range, db, expected); } private void verify(final KeyRange range, - final Comparator comparator, - final int... expected) { + final Dbi dbi, final int... expected) { final List results = new ArrayList<>(); try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn, range, comparator)) { + CursorIterable c = dbi.iterate(txn, range)) { for (final KeyVal kv : c) { final int key = kv.key().getInt(); final int val = kv.val().getInt(); diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 6db5a9b8..8646aec2 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -43,6 +43,7 @@ import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.DbiFlags.MDB_DUPSORT; +import static org.lmdbjava.DbiFlags.MDB_INTEGERKEY; import static org.lmdbjava.DbiFlags.MDB_REVERSEKEY; import static org.lmdbjava.Env.create; import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; @@ -117,13 +118,13 @@ public void close() { @Test public void customComparator() { final Comparator reverseOrder = (o1, o2) -> { - final int lexicalOrder = ByteBufferProxy.PROXY_OPTIMAL.compare(o1, o2); - if (lexicalOrder == 0) { + final int lexical = PROXY_OPTIMAL.getComparator().compare(o1, o2); + if (lexical == 0) { return 0; } - return lexicalOrder * -1; + return lexical * -1; }; - final Dbi db = env.openDbi(DB_1, reverseOrder, MDB_CREATE); + final Dbi db = env.openDbi(DB_1, reverseOrder, true, MDB_CREATE); try (Txn txn = env.txnWrite()) { assertThat(db.put(txn, bb(2), bb(3)), is(true)); assertThat(db.put(txn, bb(4), bb(6)), is(true)); @@ -150,8 +151,9 @@ public void dbOpenMaxDatabases() { @Test public void dbiWithComparatorThreadSafety() { - final Dbi db = env.openDbi(DB_1, PROXY_OPTIMAL::compare, - MDB_CREATE); + final DbiFlags[] flags = new DbiFlags[] {MDB_CREATE, MDB_INTEGERKEY}; + final Comparator c = PROXY_OPTIMAL.getComparator(flags); + final Dbi db = env.openDbi(DB_1, c, true, flags); final List keys = range(0, 1_000).boxed().collect(toList()); From 0dc67805a55341d24e49db342c51ff2aecb02f50 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 4 Feb 2023 15:30:18 +1100 Subject: [PATCH 164/322] JavaDoc corrections (#199) --- src/main/java/org/lmdbjava/CursorIterable.java | 6 ------ src/main/java/org/lmdbjava/Env.java | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/main/java/org/lmdbjava/CursorIterable.java b/src/main/java/org/lmdbjava/CursorIterable.java index 681f805b..39a6d58c 100644 --- a/src/main/java/org/lmdbjava/CursorIterable.java +++ b/src/main/java/org/lmdbjava/CursorIterable.java @@ -41,12 +41,6 @@ *

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

- * If iterating over keys stored with {@link DbiFlags#MDB_INTEGERKEY} you must - * provide a Java comparator when constructing the {@link Dbi} or this class. It - * is more efficient to use a comparator only with this class, as this avoids - * LMDB calling back into Java code to perform the integer key comparison. - * * @param buffer type */ public final class CursorIterable implements diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index 66ab7cda..0a4c034b 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -365,7 +365,7 @@ public Dbi openDbi(final byte[] name, final Comparator comparator, * secondary (but uncommon) use of the comparator is to act as a callback from * the native library if nativeCb is true. This is * usually avoided due to the overhead of native code calling back into Java. - * It is instead highly recommended to set the correct {@link DbiFlag}s to + * It is instead highly recommended to set the correct {@link DbiFlags} to * allow the native library to correctly order the intended keys. * *

From 860c827e0b377cb55f6967ff66777428603456a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Lamar=C3=A3o?= Date: Wed, 8 Mar 2023 22:29:36 -0300 Subject: [PATCH 165/322] Define manifest attribute Automatic-Module-Name. (#213) --- pom.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pom.xml b/pom.xml index 20b02b04..09b36edb 100644 --- a/pom.xml +++ b/pom.xml @@ -117,6 +117,17 @@ org.apache.maven.plugins maven-enforcer-plugin + + org.apache.maven.plugins + maven-jar-plugin + + + + org.lmdbjava + + + + org.apache.maven.plugins maven-pmd-plugin From 9cf97a5cd28cbf996a1b206148f586626982880b Mon Sep 17 00:00:00 2001 From: Martin Hristov Date: Thu, 9 Mar 2023 03:31:27 +0200 Subject: [PATCH 166/322] Library.java : fix mdb_reader_check api, Env.java : readerCheck impl (#212) * Library.java : fix mdb_reader_check api, Env.java : readerCheck impl * whitespace in Library.java * upd * update Env.java * remove tabs --- src/main/java/org/lmdbjava/Env.java | 12 ++++++++++++ src/main/java/org/lmdbjava/Library.java | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index 0a4c034b..db8b0f4a 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -41,6 +41,7 @@ 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; @@ -500,6 +501,15 @@ private void validatePath(final File path) { validateDirectoryEmpty(path); } + + /* Check for stale entries in the reader lock table. */ + public int readerCheck() { + final IntByReference resultPtr = new IntByReference(); + checkRc(LIB.mdb_reader_check(ptr, resultPtr)); + return resultPtr.intValue(); + } + + /** * Object has already been closed and the operation is therefore prohibited. */ @@ -530,6 +540,7 @@ public AlreadyOpenException() { } } + /** * Builder for configuring and opening Env. * @@ -595,6 +606,7 @@ public Env open(final File path, final EnvFlags... flags) { return open(path, 0664, flags); } + /** * Sets the map size. * diff --git a/src/main/java/org/lmdbjava/Library.java b/src/main/java/org/lmdbjava/Library.java index 44b17492..e48def73 100644 --- a/src/main/java/org/lmdbjava/Library.java +++ b/src/main/java/org/lmdbjava/Library.java @@ -291,7 +291,7 @@ int mdb_put(@In Pointer txn, @In Pointer dbi, @In Pointer key, @In Pointer data, int flags); - int mdb_reader_check(@In Pointer env, int dead); + int mdb_reader_check(@In Pointer env, @Out IntByReference dead); int mdb_set_compare(@In Pointer txn, @In Pointer dbi, ComparatorCallback cb); From 0c636f1a2718de9d3e01c8afd04699a162265539 Mon Sep 17 00:00:00 2001 From: Ian Kang Date: Thu, 23 Mar 2023 16:16:16 -0400 Subject: [PATCH 167/322] Hold strong reference to all buffers passed to LmdbJava (fixes #207) --- pom.xml | 8 ++ src/main/java/org/lmdbjava/Cursor.java | 7 ++ src/main/java/org/lmdbjava/Dbi.java | 6 + src/main/java/org/lmdbjava/ReferenceUtil.java | 55 +++++++++ .../org/lmdbjava/GarbageCollectionTest.java | 111 ++++++++++++++++++ 5 files changed, 187 insertions(+) create mode 100644 src/main/java/org/lmdbjava/ReferenceUtil.java create mode 100644 src/test/java/org/lmdbjava/GarbageCollectionTest.java diff --git a/pom.xml b/pom.xml index 09b36edb..fa025026 100644 --- a/pom.xml +++ b/pom.xml @@ -88,6 +88,12 @@ 0.9.29-1 true + + org.mockito + mockito-inline + 4.11.0 + test + @@ -110,6 +116,8 @@ com.github.jnr:jffi + org.mockito:mockito-core + org.mockito:mockito-inline diff --git a/src/main/java/org/lmdbjava/Cursor.java b/src/main/java/org/lmdbjava/Cursor.java index aa98a9ea..ed7c1848 100644 --- a/src/main/java/org/lmdbjava/Cursor.java +++ b/src/main/java/org/lmdbjava/Cursor.java @@ -162,6 +162,7 @@ public boolean get(final T key, final T data, final SeekOp op) { checkRc(rc); kv.keyOut(); kv.valOut(); + ReferenceUtil.reachabilityFence0(key); return true; } @@ -192,6 +193,7 @@ public boolean get(final T key, final GetOp op) { checkRc(rc); kv.keyOut(); kv.valOut(); + ReferenceUtil.reachabilityFence0(key); return true; } @@ -266,6 +268,8 @@ public boolean put(final T key, final T val, final PutFlags... op) { return false; } checkRc(rc); + ReferenceUtil.reachabilityFence0(key); + ReferenceUtil.reachabilityFence0(val); return true; } @@ -304,6 +308,8 @@ public void putMultiple(final T key, final T val, final int elements, final int rc = LIB.mdb_cursor_put(ptrCursor, txn.kv().pointerKey(), dataPtr, mask); checkRc(rc); + ReferenceUtil.reachabilityFence0(key); + ReferenceUtil.reachabilityFence0(val); } /** @@ -362,6 +368,7 @@ public T reserve(final T key, final int size, final PutFlags... op) { checkRc(LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), flags)); kv.valOut(); + ReferenceUtil.reachabilityFence0(key); return val(); } diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index d0eb0a14..ef8ec315 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -171,6 +171,8 @@ public boolean delete(final Txn txn, final T key, final T val) { return false; } checkRc(rc); + ReferenceUtil.reachabilityFence0(key); + ReferenceUtil.reachabilityFence0(val); return true; } @@ -239,6 +241,7 @@ public T get(final Txn txn, final T key) { return null; } checkRc(rc); + ReferenceUtil.reachabilityFence0(key); return txn.kv().valOut(); // marked as out in LMDB C docs } @@ -386,6 +389,8 @@ public boolean put(final Txn txn, final T key, final T val, return false; } checkRc(rc); + ReferenceUtil.reachabilityFence0(key); + ReferenceUtil.reachabilityFence0(val); return true; } @@ -421,6 +426,7 @@ public T reserve(final Txn txn, final T key, final int size, checkRc(LIB.mdb_put(txn.pointer(), ptr, txn.kv().pointerKey(), txn.kv() .pointerVal(), flags)); txn.kv().valOut(); // marked as in,out in LMDB C docs + ReferenceUtil.reachabilityFence0(key); return txn.val(); } diff --git a/src/main/java/org/lmdbjava/ReferenceUtil.java b/src/main/java/org/lmdbjava/ReferenceUtil.java new file mode 100644 index 00000000..0002a509 --- /dev/null +++ b/src/main/java/org/lmdbjava/ReferenceUtil.java @@ -0,0 +1,55 @@ +/*- + * #%L + * LmdbJava + * %% + * Copyright (C) 2016 - 2023 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. + * #L% + */ + +package org.lmdbjava; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +public final class ReferenceUtil { + /** + * Ensures that the object referenced by the given reference remains + * strongly reachable, + * regardless of any prior actions of the program that might otherwise cause + * the object to become unreachable; thus, the referenced object is not + * reclaimable by garbage collection at least until after the invocation of + * this method. + * + *

Recent versions of the JDK have a nasty habit of prematurely deciding objects are unreachable. + * see: https://stackoverflow.com/questions/26642153/finalize-called-on-strongly-reachable-object-in-java-8 + * The Java 9 method Reference.reachabilityFence offers a solution to this problem. + * + *

This method is always implemented as a synchronization on {@code ref}, not as + * {@code Reference.reachabilityFence} for consistency across platforms and to allow building on JDK 6-8. + * It is the caller's responsibility to ensure that this synchronization will not cause deadlock. + * + * @param ref the reference. If {@code null}, this method has no effect. + * @see https://github.com/netty/netty/pull/8410 + */ + @SuppressFBWarnings({"ESync_EMPTY_SYNC", "UC_USELESS_VOID_METHOD"}) + public static void reachabilityFence0(Object ref) { + if (ref != null) { + synchronized (ref) { + // Empty synchronized is ok: https://stackoverflow.com/a/31933260/1151521 + } + } + } + + private ReferenceUtil() {} +} diff --git a/src/test/java/org/lmdbjava/GarbageCollectionTest.java b/src/test/java/org/lmdbjava/GarbageCollectionTest.java new file mode 100644 index 00000000..aa8a8856 --- /dev/null +++ b/src/test/java/org/lmdbjava/GarbageCollectionTest.java @@ -0,0 +1,111 @@ +/*- + * #%L + * LmdbJava + * %% + * Copyright (C) 2016 - 2023 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. + * #L% + */ + +package org.lmdbjava; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; + +import static java.nio.ByteBuffer.allocateDirect; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.fail; +import static org.lmdbjava.DbiFlags.MDB_CREATE; +import static org.lmdbjava.Env.create; + +@SuppressFBWarnings({"DM_GC", "RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT"}) +@SuppressWarnings("PMD.DoNotCallGarbageCollectionExplicitly") +public class GarbageCollectionTest { + + private static final String DB_NAME = "my DB"; + private static final String KEY_PREFIX = "Uncorruptedkey"; + private static final String VAL_PREFIX = "Uncorruptedval"; + + @Rule + public final TemporaryFolder tmp = new TemporaryFolder(); + private void putBuffer(Dbi db, Txn txn, int i) { + ByteBuffer key = allocateDirect(24); + ByteBuffer val = allocateDirect(24); + key.put((KEY_PREFIX+i).getBytes(UTF_8)).flip(); + val.put((VAL_PREFIX+i).getBytes(UTF_8)).flip(); + db.put(txn, key, val); + } + @Test + public void buffersNotGarbageCollectedTest() throws IOException { + final File path = tmp.newFolder(); + + final Env env = create() + .setMapSize(2_085_760_999) + .setMaxDbs(1) + .open(path); + final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); + + // Trigger compilation and whatnot + try (Txn txn = env.txnWrite()) { + for (int i = 0; i < 5000; i++) { + putBuffer(db, txn, i); + } + txn.commit(); + } + // Call gc before writing to lmdb and after last reference to buffer by changing the behavior of mask + try (MockedStatic mockedStatic = Mockito.mockStatic(MaskedFlag.class)) { + mockedStatic.when(MaskedFlag::mask).thenAnswer(invocationOnMock -> { + System.gc(); + return 0; + }); + try (Txn txn = env.txnWrite()) { + for (int i = 0; i < 1000; i++) { + putBuffer(db, txn, i); + } + txn.commit(); + } + } + + // Find corrupt keys + try (Txn txn = env.txnRead()) { + try (Cursor c = db.openCursor(txn)) { + if (c.first()) { + do { + byte[] rkey = new byte[c.key().remaining()]; + c.key().get(rkey); + byte[] rval = new byte[c.val().remaining()]; + c.val().get(rval); + String skey = new String(rkey, UTF_8); + String sval = new String(rval, UTF_8); + if (!skey.startsWith("Uncorruptedkey")) { + fail("Found corrupt key " + skey); + } + if (!sval.startsWith("Uncorruptedval")) { + fail("Found corrupt val " + sval); + } + } while (c.next()); + } + } + } + env.close(); + } +} From 288bb09f00fdfe8f4d2d062aabc33e5dcbef8af8 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 24 Apr 2023 10:17:49 +1000 Subject: [PATCH 168/322] Zig cross-compilation of native libraries (#217) This new approach provides a number of benefits: 1. The LmdbJava Native project can be archived. This project required coordination with LmdbJava module releases and added extra artifacts to Maven Central simply to wrap native libraries. The native project also required a great deal of Maven configuration and it was always tedious to support additional platforms. Using Zig eliminates the need to run QEMU emulators in builds to support unusual platforms etc. 2. Zig supports numerous cross-compilation targets straight out of the box. A full list is available via zig targets | jq -r '.libc[]' and this presently lists 64 on my machine. This is likely to accommodate most LmdbJava platform support requests we are likely to receive. 3. Platform naming conventions have now been standardised based on the Zig target name. Support for individual build chains or specific processors is now simplified and much more transparent. 4. The GitHub Action has been amended to perform cross-compilation under Linux and upload the resulting native artifacts for later build steps. The later build steps run the Verifier on a native VM where available (eg Windows, Mac OS) and this therefore tests the cross-compiled libraries. 5. Target name resolution logic has been refactored and externalised in its own class with corresponding unit tests to ensure corner cases are duly considered and any bugs more easily reproduced and permanently rectified. This change is backwards compatible with users who used (and may continue to use) the lmdbjava.native.lib system property. --- .github/workflows/maven.yml | 27 +++ .gitignore | 1 + README.md | 20 ++- cross-compile.sh | 27 +++ pom.xml | 45 ----- src/main/java/org/lmdbjava/Library.java | 52 +----- src/main/java/org/lmdbjava/TargetName.java | 156 ++++++++++++++++++ src/main/resources/org/lmdbjava/.gitignore | 2 + .../java/org/lmdbjava/TargetNameTest.java | 74 +++++++++ 9 files changed, 310 insertions(+), 94 deletions(-) create mode 100755 cross-compile.sh create mode 100644 src/main/java/org/lmdbjava/TargetName.java create mode 100644 src/main/resources/org/lmdbjava/.gitignore create mode 100644 src/test/java/org/lmdbjava/TargetNameTest.java diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 85d4385f..4c3ac069 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -22,14 +22,29 @@ jobs: java-version: 17 cache: maven + - name: Install Zig + uses: goto-bus-stop/setup-zig@v2 + + - name: Cross compile using Zig + run: ./cross-compile.sh + - name: Build with Maven run: mvn -B verify + - name: Store built native libraries for later jobs + uses: actions/upload-artifact@v3 + with: + name: native-libraries + path: | + src/main/resources/org/lmdbjava/*.so + src/main/resources/org/lmdbjava/*.dll + - name: Upload code coverage to Codecov uses: codecov/codecov-action@v3 compatibility-checks: name: Java ${{ matrix.java }} on ${{ matrix.os }} Compatibility + needs: [build] runs-on: ${{ matrix.os }} strategy: @@ -48,6 +63,12 @@ jobs: java-version: ${{ matrix.java }} cache: maven + - name: Fetch built native libraries + uses: actions/download-artifact@v3 + with: + name: native-libraries + path: src/main/resources/org/lmdbjava + - name: Execute verifier run: mvn -B test -Dtest=VerifierTest -DverificationSeconds=10 @@ -81,6 +102,12 @@ jobs: gpg-private-key: ${{ secrets.gpg_private_key }} gpg-passphrase: MAVEN_GPG_PASSPHRASE + - name: Install Zig + uses: goto-bus-stop/setup-zig@v2 + + - name: Cross compile using Zig + run: ./cross-compile.sh + - name: Publish Maven package run: mvn -B -Possrh-deploy deploy -DskipTests env: diff --git a/.gitignore b/.gitignore index fe6430cb..0c771329 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ dependency-reduced-pom.xml gpg-sign.json mvn-sync.json secrets.tar +lmdb diff --git a/README.md b/README.md index 450f814f..16ff78ae 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ * Modern, idiomatic Java API (including iterators, key ranges, enums, exceptions etc) * Nothing to install (the JAR embeds the latest LMDB libraries for Linux, OS X and Windows) * Buffer agnostic (Java `ByteBuffer`, Agrona `DirectBuffer`, Netty `ByteBuf`, your own buffer) -* 100% stock-standard, officially-released, widely-tested LMDB C code ([no extra](https://github.com/lmdbjava/native) C/JNI code) +* 100% stock-standard, officially-released, widely-tested LMDB C code (no extra C/JNI code) * Low latency design (allocation-free; buffer pools; optional checks can be easily disabled in production etc) * Mature code (commenced in 2016) and used for heavy production workloads (eg > 500 TB of HFT data) * Actively maintained and with a "Zero Bug Policy" before every release (see [issues](https://github.com/lmdbjava/lmdbjava/issues)) @@ -55,6 +55,24 @@ We're happy to help you use LmdbJava. Simply [open a GitHub issue](https://github.com/lmdbjava/lmdbjava/issues) if you have any questions. +### Building + +This project uses [Zig](https://ziglang.org/) to cross-compile the LMDB native +library for all supported architectures. To locally build LmdbJava you must +firstly install a recent version of Zig and then execute the project's +[cross-compile.sh](https://github.com/lmdbjava/lmdbjava/tree/master/cross-compile.sh) +script. This only needs to be repeated when the `cross-compile.sh` script is +updated (eg following a new official release of the upstream LMDB library). + +If you do not wish to install Zig and/or use an operating system which cannot +easily execute the `cross-compile.sh` script, you can download the compiled +LMDB native library for your platform from a location of your choice and set the +`lmdbjava.native.lib` system property to the resulting file system system +location. Possible sources of a compiled LMDB native library include operating +system package managers, running `cross-compile.sh` on a supported system, or +copying it from the `org/lmdbjava` directory of any recent, officially released +LmdbJava JAR. + ### Contributing Contributions are welcome! Please see the [Contributing Guidelines](CONTRIBUTING.md). diff --git a/cross-compile.sh b/cross-compile.sh new file mode 100755 index 00000000..1ebf92f6 --- /dev/null +++ b/cross-compile.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +set -o errexit + +rm -rf lmdb +git clone --depth 1 --branch LMDB_0.9.29 https://github.com/LMDB/lmdb.git +pushd lmdb/libraries/liblmdb +trap popd SIGINT + +# zig targets | jq -r '.libc[]' +for target in aarch64-linux-gnu \ + aarch64-macos-none \ + x86_64-linux-gnu \ + x86_64-macos-none \ + x86_64-windows-gnu +do + echo "##### Building $target ####" + make -e clean liblmdb.so CC="zig cc -target $target" AR="zig ar" + if [[ "$target" == *-windows-* ]]; then + extension="dll" + else + extension="so" + fi + cp -v liblmdb.so ../../../src/main/resources/org/lmdbjava/$target.$extension +done + +ls -l ../../../src/main/resources/org/lmdbjava diff --git a/pom.xml b/pom.xml index 09b36edb..4c8f03ba 100644 --- a/pom.xml +++ b/pom.xml @@ -70,24 +70,6 @@ org.hamcrest hamcrest - - org.lmdbjava - lmdbjava-native-linux-x86_64 - 0.9.29-1 - true - - - org.lmdbjava - lmdbjava-native-osx-x86_64 - 0.9.29-1 - true - - - org.lmdbjava - lmdbjava-native-windows-x86_64 - 0.9.29-1 - true - @@ -103,11 +85,6 @@ org.apache.maven.plugins maven-dependency-plugin - - org.lmdbjava:lmdbjava-native-linux-x86_64 - org.lmdbjava:lmdbjava-native-windows-x86_64 - org.lmdbjava:lmdbjava-native-osx-x86_64 - com.github.jnr:jffi @@ -132,28 +109,6 @@ org.apache.maven.plugins maven-pmd-plugin - - org.apache.maven.plugins - maven-shade-plugin - - - lmdbjava-shade - - shade - - package - - - - org.lmdbjava:lmdbjava-native-linux-x86_64 - org.lmdbjava:lmdbjava-native-windows-x86_64 - org.lmdbjava:lmdbjava-native-osx-x86_64 - - - - - - org.apache.maven.plugins maven-surefire-plugin diff --git a/src/main/java/org/lmdbjava/Library.java b/src/main/java/org/lmdbjava/Library.java index e48def73..22308600 100644 --- a/src/main/java/org/lmdbjava/Library.java +++ b/src/main/java/org/lmdbjava/Library.java @@ -21,11 +21,8 @@ package org.lmdbjava; import static java.io.File.createTempFile; -import static java.lang.Boolean.getBoolean; import static java.lang.System.getProperty; import static java.lang.Thread.currentThread; -import static java.util.Locale.ENGLISH; -import static java.util.Objects.nonNull; import static java.util.Objects.requireNonNull; import static jnr.ffi.LibraryLoader.create; import static jnr.ffi.Runtime.getRuntime; @@ -55,35 +52,15 @@ */ final class Library { - /** - * Java system property name that can be set to disable automatic extraction - * of the LMDB system library from the LmdbJava JAR. This may be desirable if - * an operating system-provided LMDB system library is preferred (eg operating - * system package management, vendor support, special compiler flags, security - * auditing, profile guided optimization builds, faster startup time by - * avoiding the library copy etc). - */ - public static final String DISABLE_EXTRACT_PROP = "lmdbjava.disable.extract"; /** * 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 Library have been set). + * system properties defined in TargetName have been set). */ public static final String LMDB_EXTRACT_DIR_PROP = "lmdbjava.extract.dir"; - /** - * Java system property name that can be set to provide a custom path to a - * external LMDB system library. If set, the system property - * DISABLE_EXTRACT_PROP will be overridden. - */ - public static final String LMDB_NATIVE_LIB_PROP = "lmdbjava.native.lib"; - /** - * Indicates whether automatic extraction of the LMDB system library is - * permitted. - */ - public static final boolean SHOULD_EXTRACT = !getBoolean(DISABLE_EXTRACT_PROP); /** * Indicates the directory where the LMDB system library will be extracted. */ @@ -91,35 +68,14 @@ final class Library { getProperty("java.io.tmpdir")); static final Lmdb LIB; static final jnr.ffi.Runtime RUNTIME; - /** - * Indicates whether external LMDB system library is provided. - */ - static final boolean SHOULD_USE_LIB = nonNull( - getProperty(LMDB_NATIVE_LIB_PROP)); - private static final String LIB_NAME = "lmdb"; static { final String libToLoad; - final String arch = getProperty("os.arch"); - final boolean arch64 = "x64".equals(arch) || "amd64".equals(arch) - || "x86_64".equals(arch); - - final String os = getProperty("os.name"); - final boolean linux = os.toLowerCase(ENGLISH).startsWith("linux"); - final boolean osx = os.startsWith("Mac OS X"); - final boolean windows = os.startsWith("Windows"); - - if (SHOULD_USE_LIB) { - libToLoad = getProperty(LMDB_NATIVE_LIB_PROP); - } else if (SHOULD_EXTRACT && arch64 && linux) { - libToLoad = extract("org/lmdbjava/lmdbjava-native-linux-x86_64.so"); - } else if (SHOULD_EXTRACT && arch64 && osx) { - libToLoad = extract("org/lmdbjava/lmdbjava-native-osx-x86_64.dylib"); - } else if (SHOULD_EXTRACT && arch64 && windows) { - libToLoad = extract("org/lmdbjava/lmdbjava-native-windows-x86_64.dll"); + if (TargetName.IS_EXTERNAL) { + libToLoad = TargetName.RESOLVED_FILENAME; } else { - libToLoad = LIB_NAME; + libToLoad = extract(TargetName.RESOLVED_FILENAME); } LIB = create(Lmdb.class).load(libToLoad); diff --git a/src/main/java/org/lmdbjava/TargetName.java b/src/main/java/org/lmdbjava/TargetName.java new file mode 100644 index 00000000..3f8692a6 --- /dev/null +++ b/src/main/java/org/lmdbjava/TargetName.java @@ -0,0 +1,156 @@ +/*- + * #%L + * LmdbJava + * %% + * Copyright (C) 2016 - 2023 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. + * #L% + */ + +package org.lmdbjava; + +import static java.lang.System.getProperty; +import static java.util.Locale.ENGLISH; + +/** + * Determines the name of the target LMDB native library. + * + *

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

+ * Users can set two system properties to override the automatic resolution of + * an embedded library. Setting {@link #LMDB_NATIVE_LIB_PROP} will force use of + * that external LMDB library. Setting {@link #LMDB_EMBEDDED_LIB_PROP} will + * force use of that embedded LMDB library. If both are set, the former property + * will take precedence. Most users do not need to set either property. + */ +public final class TargetName { + + /** + * True if the resolved native filename is an external file (conversely false + * indicates the file should be considered a classpath resource). + */ + public static final boolean IS_EXTERNAL; + + /** + * Java system property name that can be set to override the embedded library + * that will be used. This is likely to required if automatic resolution fails + * but the user still prefers to use an LmdbJava-bundled library. + */ + public static final String LMDB_EMBEDDED_LIB_PROP = "lmdbjava.embedded.lib"; + /** + * Java system property name that can be set to provide a custom path to an + * external LMDB system library. This path must include the classpath prefix + * (usually org/lmdbjava). + */ + public static final String LMDB_NATIVE_LIB_PROP = "lmdbjava.native.lib"; + /** + * Resolved target native filename or fully-qualified classpath location. + */ + public static final String RESOLVED_FILENAME; + private static final String ARCH = getProperty("os.arch"); + private static final String EMBED = getProperty(LMDB_EMBEDDED_LIB_PROP); + private static final String EXTERNAL = getProperty(LMDB_NATIVE_LIB_PROP); + private static final String OS = getProperty("os.name"); + + static { + IS_EXTERNAL = isExternal(EXTERNAL); + RESOLVED_FILENAME = resolveFilename(EXTERNAL, EMBED, ARCH, OS); + } + + private TargetName() { + } + + public static String resolveExtension(final String os) { + return check(os, "Windows") ? "dll" : "so"; + } + + static boolean isExternal(final String external) { + return external != null && !external.isEmpty(); + } + + static String resolveFilename(final String external, final String embed, + final String arch, final String os) { + if (external != null && !external.isEmpty()) { + return external; + } + + if (embed != null && !embed.isEmpty()) { + return embed; + } + + return "org/lmdbjava/" + resolveArch(arch) + "-" + resolveOs(os) + "-" + + resolveToolchain(os) + "." + resolveExtension(os); + } + + /** + * Case insensitively checks whether the passed string starts with any of the + * candidate strings. + * + * @param string the string being checked + * @param candidates one or more candidate strings + * @return true if the string starts with any of the candidates + */ + private static boolean check(final String string, + final String... candidates) { + if (string == null) { + return false; + } + + final String strLower = string.toLowerCase(ENGLISH); + for (final String c : candidates) { + if (strLower.startsWith(c.toLowerCase(ENGLISH))) { + return true; + } + } + return false; + } + + private static String err(final String reason) { + return reason + " (please set system property " + LMDB_NATIVE_LIB_PROP + + " to the path of an external LMDB native library or property " + + LMDB_EMBEDDED_LIB_PROP + " to the name of an LmdbJava embedded" + + " library; os.arch='" + ARCH + "' os.name='" + OS + "')"; + } + + private static String resolveArch(final String arch) { + if (check(arch, "aarch64")) { + return "aarch64"; + } else if (check(arch, "x86_64", "amd64")) { + return "x86_64"; + } + throw new UnsupportedOperationException(err("Unsupported os.arch")); + } + + private static String resolveOs(final String os) { + if (check(os, "Linux")) { + return "linux"; + } else if (check(os, "Mac OS")) { + return "macos"; + } else if (check(os, "Windows")) { + return "windows"; + } + throw new UnsupportedOperationException(err("Unsupported os.name")); + } + + private static String resolveToolchain(final String os) { + return check(os, "Mac OS") ? "none" : "gnu"; + } + +} diff --git a/src/main/resources/org/lmdbjava/.gitignore b/src/main/resources/org/lmdbjava/.gitignore new file mode 100644 index 00000000..661f98b3 --- /dev/null +++ b/src/main/resources/org/lmdbjava/.gitignore @@ -0,0 +1,2 @@ +*.so +*.dll diff --git a/src/test/java/org/lmdbjava/TargetNameTest.java b/src/test/java/org/lmdbjava/TargetNameTest.java new file mode 100644 index 00000000..eec38233 --- /dev/null +++ b/src/test/java/org/lmdbjava/TargetNameTest.java @@ -0,0 +1,74 @@ +/*- + * #%L + * LmdbJava + * %% + * Copyright (C) 2016 - 2023 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. + * #L% + */ + +package org.lmdbjava; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.lmdbjava.TargetName.isExternal; +import static org.lmdbjava.TargetName.resolveFilename; +import static org.lmdbjava.TestUtils.invokePrivateConstructor; + +import org.junit.Test; + +/** + * Test {@link TargetName}. + */ +public final class TargetNameTest { + + private static final String NONE = ""; + + @Test + public void coverPrivateConstructors() { + invokePrivateConstructor(TargetName.class); + } + + @Test + public void customEmbedded() { + assertThat(resolveFilename(NONE, "x/y.so", NONE, NONE), is("x/y.so")); + assertThat(isExternal(NONE), is(false)); + } + + @Test + public void embeddedNameResolution() { + embed("aarch64-linux-gnu.so", "aarch64", "Linux"); + embed("aarch64-macos-none.so", "aarch64", "Mac OS"); + embed("x86_64-linux-gnu.so", "x86_64", "Linux"); + embed("x86_64-macos-none.so", "x86_64", "Mac OS"); + embed("x86_64-windows-gnu.dll", "x86_64", "Windows"); + } + + @Test + public void externalLibrary() { + assertThat(resolveFilename("/l.so", NONE, NONE, NONE), is("/l.so")); + assertThat(TargetName.isExternal("/l.so"), is(true)); + } + + @Test + public void externalTakesPriority() { + assertThat(resolveFilename("/lm.so", "x/y.so", NONE, NONE), is("/lm.so")); + assertThat(isExternal("/lm.so"), is(true)); + } + + private void embed(final String lib, final String arch, final String os) { + assertThat(resolveFilename(NONE, NONE, arch, os), is("org/lmdbjava/" + lib)); + assertThat(isExternal(NONE), is(false)); + } +} From d87bc4b2cef38c28f9917d5f52cc1d4d962c08ea Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 24 Apr 2023 12:55:11 +1000 Subject: [PATCH 169/322] JavaDoc corrections (follows #217) --- src/main/java/org/lmdbjava/TargetName.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/lmdbjava/TargetName.java b/src/main/java/org/lmdbjava/TargetName.java index 3f8692a6..48411044 100644 --- a/src/main/java/org/lmdbjava/TargetName.java +++ b/src/main/java/org/lmdbjava/TargetName.java @@ -50,14 +50,14 @@ public final class TargetName { /** * Java system property name that can be set to override the embedded library - * that will be used. This is likely to required if automatic resolution fails - * but the user still prefers to use an LmdbJava-bundled library. + * that will be used. This is likely to be required if automatic resolution + * fails but the user still prefers to use an LmdbJava-bundled library. This + * path must include the classpath prefix (usually org/lmdbjava). */ public static final String LMDB_EMBEDDED_LIB_PROP = "lmdbjava.embedded.lib"; /** * Java system property name that can be set to provide a custom path to an - * external LMDB system library. This path must include the classpath prefix - * (usually org/lmdbjava). + * external LMDB system library. */ public static final String LMDB_NATIVE_LIB_PROP = "lmdbjava.native.lib"; /** From 98c233d110fb3876d1f3d7421bca86e514af3586 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 24 Apr 2023 13:22:19 +1000 Subject: [PATCH 170/322] Remove hard-coded package name resolution (follows #217) --- src/main/java/org/lmdbjava/TargetName.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/lmdbjava/TargetName.java b/src/main/java/org/lmdbjava/TargetName.java index 48411044..446ea5b5 100644 --- a/src/main/java/org/lmdbjava/TargetName.java +++ b/src/main/java/org/lmdbjava/TargetName.java @@ -95,7 +95,8 @@ static String resolveFilename(final String external, final String embed, return embed; } - return "org/lmdbjava/" + resolveArch(arch) + "-" + resolveOs(os) + "-" + final String pkg = TargetName.class.getPackage().getName().replace('.', '/'); + return pkg + "/" + resolveArch(arch) + "-" + resolveOs(os) + "-" + resolveToolchain(os) + "." + resolveExtension(os); } From cbdaee61fce707aa33fae2738fd8653a7f78c04c Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 24 Apr 2023 14:25:20 +1000 Subject: [PATCH 171/322] Update formatting, documentation and limit GC test write frequency The GarbageCollectionTest performing 1,000 GC operations caused a substantial increase in test execution time. Trial and error found commenting out the new ReferenceUtil.reachabilityFence0 references resulted in the failures being consistently detected with a value of 250. Nevertheless even a value of 250 would result in the new test being approximately 3 times longer in duration than any other test. To resolve this the default value was reduced to 50 and a system property introduced to allow any arbitrary value to be used. In turn the GitHub Action was amended to test with 1,000 GC-inducing writes as per the original code. As an aside on OpenJDK Java 20 the mere presence of a call to ReferenceUtil.reachabilityFence0 was sufficient to pass the tests (even with high values such as 2,000) despite there being no method body remaining in reachabilityFence0. As such it is unverified whether the synchronization is actually required for the strong reachability. This is consistent with https://github.com/apache/datasketches-memory/issues/91#issuecomment-444294778. In any event synchronization has been retained given it is the more conservative approach. --- .github/workflows/maven.yml | 2 +- src/main/java/org/lmdbjava/ReferenceUtil.java | 68 +++++---- .../org/lmdbjava/GarbageCollectionTest.java | 144 +++++++++--------- 3 files changed, 116 insertions(+), 98 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 4c3ac069..2a4f691c 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -29,7 +29,7 @@ jobs: run: ./cross-compile.sh - name: Build with Maven - run: mvn -B verify + run: mvn -B verify -DgcRecordWrites=1000 - name: Store built native libraries for later jobs uses: actions/upload-artifact@v3 diff --git a/src/main/java/org/lmdbjava/ReferenceUtil.java b/src/main/java/org/lmdbjava/ReferenceUtil.java index 0002a509..36bf9247 100644 --- a/src/main/java/org/lmdbjava/ReferenceUtil.java +++ b/src/main/java/org/lmdbjava/ReferenceUtil.java @@ -22,34 +22,46 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +/** + * Supports creating strong references in manner compatible with Java 8. + */ public final class ReferenceUtil { - /** - * Ensures that the object referenced by the given reference remains - * strongly reachable, - * regardless of any prior actions of the program that might otherwise cause - * the object to become unreachable; thus, the referenced object is not - * reclaimable by garbage collection at least until after the invocation of - * this method. - * - *

Recent versions of the JDK have a nasty habit of prematurely deciding objects are unreachable. - * see: https://stackoverflow.com/questions/26642153/finalize-called-on-strongly-reachable-object-in-java-8 - * The Java 9 method Reference.reachabilityFence offers a solution to this problem. - * - *

This method is always implemented as a synchronization on {@code ref}, not as - * {@code Reference.reachabilityFence} for consistency across platforms and to allow building on JDK 6-8. - * It is the caller's responsibility to ensure that this synchronization will not cause deadlock. - * - * @param ref the reference. If {@code null}, this method has no effect. - * @see https://github.com/netty/netty/pull/8410 - */ - @SuppressFBWarnings({"ESync_EMPTY_SYNC", "UC_USELESS_VOID_METHOD"}) - public static void reachabilityFence0(Object ref) { - if (ref != null) { - synchronized (ref) { - // Empty synchronized is ok: https://stackoverflow.com/a/31933260/1151521 - } - } - } - private ReferenceUtil() {} + private ReferenceUtil() { + } + + /** + * Ensures that the object referenced by the given reference remains + * strongly reachable, regardless of any prior actions of the program + * that might otherwise cause the object to become unreachable. Thus, the + * referenced object is not reclaimable by garbage collection at least until + * after the invocation of this method. + * + *

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

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

+ * This method is always implemented as a synchronization on {@code ref}. + * It is the caller's responsibility to ensure that this synchronization + * will not cause deadlock. + * + * @param ref the reference (null is acceptable but has no effect) + * @see Netty PR 8410 + */ + @SuppressFBWarnings({"ESync_EMPTY_SYNC", "UC_USELESS_VOID_METHOD"}) + public static void reachabilityFence0(final Object ref) { + if (ref != null) { + synchronized (ref) { + // Empty synchronized is ok: https://stackoverflow.com/a/31933260/1151521 + } + } + } } diff --git a/src/test/java/org/lmdbjava/GarbageCollectionTest.java b/src/test/java/org/lmdbjava/GarbageCollectionTest.java index aa8a8856..49d56b1d 100644 --- a/src/test/java/org/lmdbjava/GarbageCollectionTest.java +++ b/src/test/java/org/lmdbjava/GarbageCollectionTest.java @@ -20,6 +20,16 @@ package org.lmdbjava; +import static java.nio.ByteBuffer.allocateDirect; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.fail; +import static org.lmdbjava.DbiFlags.MDB_CREATE; +import static org.lmdbjava.Env.create; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; + import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.junit.Rule; import org.junit.Test; @@ -27,85 +37,81 @@ import org.mockito.MockedStatic; import org.mockito.Mockito; -import java.io.File; -import java.io.IOException; -import java.nio.ByteBuffer; - -import static java.nio.ByteBuffer.allocateDirect; -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.junit.Assert.fail; -import static org.lmdbjava.DbiFlags.MDB_CREATE; -import static org.lmdbjava.Env.create; - @SuppressFBWarnings({"DM_GC", "RV_RETURN_VALUE_IGNORED_NO_SIDE_EFFECT"}) @SuppressWarnings("PMD.DoNotCallGarbageCollectionExplicitly") public class GarbageCollectionTest { - private static final String DB_NAME = "my DB"; - private static final String KEY_PREFIX = "Uncorruptedkey"; - private static final String VAL_PREFIX = "Uncorruptedval"; + private static final String DB_NAME = "my DB"; + private static final String KEY_PREFIX = "Uncorruptedkey"; + private static final String VAL_PREFIX = "Uncorruptedval"; - @Rule - public final TemporaryFolder tmp = new TemporaryFolder(); - private void putBuffer(Dbi db, Txn txn, int i) { - ByteBuffer key = allocateDirect(24); - ByteBuffer val = allocateDirect(24); - key.put((KEY_PREFIX+i).getBytes(UTF_8)).flip(); - val.put((VAL_PREFIX+i).getBytes(UTF_8)).flip(); - db.put(txn, key, val); - } - @Test - public void buffersNotGarbageCollectedTest() throws IOException { - final File path = tmp.newFolder(); + @Rule + public final TemporaryFolder tmp = new TemporaryFolder(); - final Env env = create() - .setMapSize(2_085_760_999) - .setMaxDbs(1) - .open(path); - final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); + @Test + public void buffersNotGarbageCollectedTest() throws IOException { + final File path = tmp.newFolder(); + try (Env env = create() + .setMapSize(2_085_760_999) + .setMaxDbs(1) + .open(path)) { + final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); - // Trigger compilation and whatnot - try (Txn txn = env.txnWrite()) { - for (int i = 0; i < 5000; i++) { - putBuffer(db, txn, i); - } - txn.commit(); + try (Txn txn = env.txnWrite()) { + for (int i = 0; i < 5_000; i++) { + putBuffer(db, txn, i); } - // Call gc before writing to lmdb and after last reference to buffer by changing the behavior of mask - try (MockedStatic mockedStatic = Mockito.mockStatic(MaskedFlag.class)) { - mockedStatic.when(MaskedFlag::mask).thenAnswer(invocationOnMock -> { - System.gc(); - return 0; - }); - try (Txn txn = env.txnWrite()) { - for (int i = 0; i < 1000; i++) { - putBuffer(db, txn, i); - } - txn.commit(); - } + txn.commit(); + } + + // Call GC before writing to LMDB and after last reference to buffer by + // changing the behavior of mask + try (MockedStatic mockedStatic = Mockito.mockStatic( + MaskedFlag.class)) { + mockedStatic.when(MaskedFlag::mask).thenAnswer(invocationOnMock -> { + System.gc(); + return 0; + }); + final int gcRecordWrites = Integer.getInteger("gcRecordWrites", 50); + try (Txn txn = env.txnWrite()) { + for (int i = 0; i < gcRecordWrites; i++) { + putBuffer(db, txn, i); + } + txn.commit(); } + } - // Find corrupt keys - try (Txn txn = env.txnRead()) { - try (Cursor c = db.openCursor(txn)) { - if (c.first()) { - do { - byte[] rkey = new byte[c.key().remaining()]; - c.key().get(rkey); - byte[] rval = new byte[c.val().remaining()]; - c.val().get(rval); - String skey = new String(rkey, UTF_8); - String sval = new String(rval, UTF_8); - if (!skey.startsWith("Uncorruptedkey")) { - fail("Found corrupt key " + skey); - } - if (!sval.startsWith("Uncorruptedval")) { - fail("Found corrupt val " + sval); - } - } while (c.next()); - } - } + // Find corrupt keys + try (Txn txn = env.txnRead()) { + try (Cursor c = db.openCursor(txn)) { + if (c.first()) { + do { + final byte[] rkey = new byte[c.key().remaining()]; + c.key().get(rkey); + final byte[] rval = new byte[c.val().remaining()]; + c.val().get(rval); + final String skey = new String(rkey, UTF_8); + final String sval = new String(rval, UTF_8); + if (!skey.startsWith("Uncorruptedkey")) { + fail("Found corrupt key " + skey); + } + if (!sval.startsWith("Uncorruptedval")) { + fail("Found corrupt val " + sval); + } + } while (c.next()); + } } - env.close(); + } } + } + + private void putBuffer(final Dbi db, final Txn txn, + final int i) { + final ByteBuffer key = allocateDirect(24); + final ByteBuffer val = allocateDirect(24); + key.put((KEY_PREFIX + i).getBytes(UTF_8)).flip(); + val.put((VAL_PREFIX + i).getBytes(UTF_8)).flip(); + db.put(txn, key, val); + } + } From 838a8a0e2663a5469f450dea4471054ee138bf8f Mon Sep 17 00:00:00 2001 From: Mark Wardle Date: Tue, 25 Apr 2023 10:47:34 +0100 Subject: [PATCH 172/322] Fix build on aarch64 (e.g. Apple Silicon) (#218) * Increase map size to multiple of larger page size Most architectures such as x86 and x86-64 use 4KiB, while aarch64 systems can use 4, 16 or 64KiB. Apple Silicon machines currently have 16KiB page sizes. The LMDB documentation states that map size should be a multiple of the page size, so a 100KiB default map size for tests fails for systems with a page size that is not 4 KiB. This commit simply increases the map size for tests. Of course, this applies only to these tests, as in production, users can choose the appropriate map size for their requirements and system architecture. It is not straightforward to determine architecture page size at runtime. Otherwise, it might be more appropriate to dynamically determine a minimum map size. * Tweak test of page size The options for determining architecture page size are a) environment variables, b) unsafe and undocumented JDK internals or c) looking to get answer using FFI. Given this test is asking checking the statistics returned by LMDB, on balance it is reasonable to simply validate we're getting a multiple of 4096 in the statistics report. * Fix test for cut-off given large map sizes --- src/test/java/org/lmdbjava/CursorIterableTest.java | 2 +- src/test/java/org/lmdbjava/DbiTest.java | 2 +- src/test/java/org/lmdbjava/EnvTest.java | 11 ++++++----- src/test/java/org/lmdbjava/TxnTest.java | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index e08e9a0c..db28405a 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -126,7 +126,7 @@ public void atMostTest() { public void before() throws IOException { final File path = tmp.newFile(); env = create() - .setMapSize(KIBIBYTES.toBytes(100)) + .setMapSize(KIBIBYTES.toBytes(256)) .setMaxReaders(1) .setMaxDbs(1) .open(path, POSIX_MODE, MDB_NOSUBDIR); diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 8646aec2..5a8cf549 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -460,7 +460,7 @@ public void stats() { assertThat(stat.entries, is(3L)); assertThat(stat.leafPages, is(1L)); assertThat(stat.overflowPages, is(0L)); - assertThat(stat.pageSize, is(4_096)); + assertThat(stat.pageSize % 4_096, is(0)); } @Test(expected = MapFullException.class) diff --git a/src/test/java/org/lmdbjava/EnvTest.java b/src/test/java/org/lmdbjava/EnvTest.java index f26d42f3..46d8766f 100644 --- a/src/test/java/org/lmdbjava/EnvTest.java +++ b/src/test/java/org/lmdbjava/EnvTest.java @@ -20,6 +20,7 @@ package org.lmdbjava; +import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; import static com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; import static java.nio.ByteBuffer.allocateDirect; import static org.hamcrest.CoreMatchers.containsString; @@ -338,7 +339,7 @@ public void setMapSize() throws IOException { final Random rnd = new Random(); try (Env env = create() .setMaxReaders(1) - .setMapSize(50_000) + .setMapSize(KIBIBYTES.toBytes(256)) .setMaxDbs(1) .open(path)) { final Dbi db = env.openDbi(DB_1, MDB_CREATE); @@ -346,7 +347,7 @@ public void setMapSize() throws IOException { db.put(bb(1), bb(42)); boolean mapFullExThrown = false; try { - for (int i = 0; i < 30; i++) { + for (int i = 0; i < 70; i++) { rnd.nextBytes(k); key.clear(); key.put(k).flip(); @@ -358,7 +359,7 @@ public void setMapSize() throws IOException { } assertThat(mapFullExThrown, is(true)); - env.setMapSize(500_000); + env.setMapSize(KIBIBYTES.toBytes(512)); try (Txn roTxn = env.txnRead()) { assertThat(db.get(roTxn, bb(1)).getInt(), is(42)); @@ -366,7 +367,7 @@ public void setMapSize() throws IOException { mapFullExThrown = false; try { - for (int i = 0; i < 30; i++) { + for (int i = 0; i < 70; i++) { rnd.nextBytes(k); key.clear(); key.put(k).flip(); @@ -393,7 +394,7 @@ public void stats() throws IOException { assertThat(stat.entries, is(0L)); assertThat(stat.leafPages, is(0L)); assertThat(stat.overflowPages, is(0L)); - assertThat(stat.pageSize, is(4_096)); + assertThat(stat.pageSize % 4_096, is(0)); assertThat(stat.toString(), containsString("pageSize=")); } } diff --git a/src/test/java/org/lmdbjava/TxnTest.java b/src/test/java/org/lmdbjava/TxnTest.java index c115be0b..775a10a6 100644 --- a/src/test/java/org/lmdbjava/TxnTest.java +++ b/src/test/java/org/lmdbjava/TxnTest.java @@ -84,7 +84,7 @@ public void after() { public void before() throws IOException { path = tmp.newFile(); env = create() - .setMapSize(KIBIBYTES.toBytes(100)) + .setMapSize(KIBIBYTES.toBytes(256)) .setMaxReaders(1) .setMaxDbs(2) .open(path, POSIX_MODE, MDB_NOSUBDIR); From 790212695d21a2a8c38acb7c652eb49cf2295502 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Tue, 22 Aug 2023 16:23:33 +1000 Subject: [PATCH 173/322] private static final classes to be final (checkstyle) --- src/test/java/org/lmdbjava/ComparatorTest.java | 14 +++++++------- src/test/java/org/lmdbjava/KeyRangeTest.java | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/test/java/org/lmdbjava/ComparatorTest.java b/src/test/java/org/lmdbjava/ComparatorTest.java index f21604ee..8371a1ae 100644 --- a/src/test/java/org/lmdbjava/ComparatorTest.java +++ b/src/test/java/org/lmdbjava/ComparatorTest.java @@ -143,7 +143,7 @@ public void equalBuffers() { /** * Tests {@link ByteArrayProxy}. */ - private static class ByteArrayRunner implements ComparatorRunner { + private static final class ByteArrayRunner implements ComparatorRunner { @Override public int compare(final byte[] o1, final byte[] o2) { @@ -155,7 +155,7 @@ public int compare(final byte[] o1, final byte[] o2) { /** * Tests {@link ByteBufferProxy}. */ - private static class ByteBufferRunner implements ComparatorRunner { + private static final class ByteBufferRunner implements ComparatorRunner { @Override public int compare(final byte[] o1, final byte[] o2) { @@ -199,7 +199,7 @@ private ByteBuffer arrayToBuffer(final byte[] arr, final int bufferCapacity) { /** * Tests {@link DirectBufferProxy}. */ - private static class DirectBufferRunner implements ComparatorRunner { + private static final class DirectBufferRunner implements ComparatorRunner { @Override public int compare(final byte[] o1, final byte[] o2) { @@ -213,7 +213,7 @@ public int compare(final byte[] o1, final byte[] o2) { /** * Tests using Guava's {@link SignedBytes} comparator. */ - private static class GuavaSignedBytes implements ComparatorRunner { + private static final class GuavaSignedBytes implements ComparatorRunner { @Override public int compare(final byte[] o1, final byte[] o2) { @@ -225,7 +225,7 @@ public int compare(final byte[] o1, final byte[] o2) { /** * Tests using Guava's {@link UnsignedBytes} comparator. */ - private static class GuavaUnsignedBytes implements ComparatorRunner { + private static final class GuavaUnsignedBytes implements ComparatorRunner { @Override public int compare(final byte[] o1, final byte[] o2) { @@ -237,7 +237,7 @@ public int compare(final byte[] o1, final byte[] o2) { /** * Tests {@link ByteBufProxy}. */ - private static class NettyRunner implements ComparatorRunner { + private static final class NettyRunner implements ComparatorRunner { @Override public int compare(final byte[] o1, final byte[] o2) { @@ -254,7 +254,7 @@ public int compare(final byte[] o1, final byte[] o2) { * Tests {@link String} by providing a reference implementation of what a * comparator involving ASCII-encoded bytes should return. */ - private static class StringRunner implements ComparatorRunner { + private static final class StringRunner implements ComparatorRunner { @Override public int compare(final byte[] o1, final byte[] o2) { diff --git a/src/test/java/org/lmdbjava/KeyRangeTest.java b/src/test/java/org/lmdbjava/KeyRangeTest.java index 1b4384f9..3ea8c146 100644 --- a/src/test/java/org/lmdbjava/KeyRangeTest.java +++ b/src/test/java/org/lmdbjava/KeyRangeTest.java @@ -233,7 +233,7 @@ private void verify(final KeyRange range, final int... expected) { * We use Integer rather than the primitive to represent a * null buffer. */ - private static class FakeCursor { + private static final class FakeCursor { private static final int[] KEYS = new int[]{2, 4, 6, 8}; private int position; From c5f5689394364dea6a75771d4e5a2a0787345db2 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Tue, 22 Aug 2023 16:23:56 +1000 Subject: [PATCH 174/322] Acegi Standard Project 0.7.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 57abcdcd..4f4706ee 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ au.com.acegi acegi-standard-project - 0.6.7 + 0.7.0 org.lmdbjava lmdbjava From e3c33f7649b6676dee94b34799af9d24e436664a Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Tue, 22 Aug 2023 16:25:31 +1000 Subject: [PATCH 175/322] Test on Java 20 instead of Java 19 --- .github/workflows/maven.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 2a4f691c..c7df2ed3 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -50,7 +50,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - java: [8, 11, 17, 19] + java: [8, 11, 17, 20] steps: - name: Check out Git repository diff --git a/README.md b/README.md index 16ff78ae..168e046e 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ * Mature code (commenced in 2016) and used for heavy production workloads (eg > 500 TB of HFT data) * Actively maintained and with a "Zero Bug Policy" before every release (see [issues](https://github.com/lmdbjava/lmdbjava/issues)) * Available from [Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.lmdbjava%22%20AND%20a%3A%22lmdbjava%22) and [OSS Sonatype Snapshots](https://oss.sonatype.org/content/repositories/snapshots/org/lmdbjava/lmdbjava) -* [Continuous integration](https://github.com/lmdbjava/lmdbjava/actions) testing on Linux, Windows and macOS with Java 8, 11, 17 and 19 +* [Continuous integration](https://github.com/lmdbjava/lmdbjava/actions) testing on Linux, Windows and macOS with Java 8, 11, 17 and 20 ### Performance From 9b65ffdf142278fd7da4fc18db8e86c8174bb4eb Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Tue, 22 Aug 2023 16:40:27 +1000 Subject: [PATCH 176/322] Duplicate Finder Maven Plugin only to run on Java 11+ --- pom.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 4f4706ee..429e183a 100644 --- a/pom.xml +++ b/pom.xml @@ -125,10 +125,6 @@ false - - org.basepom.maven - duplicate-finder-maven-plugin - org.codehaus.mojo buildnumber-maven-plugin @@ -225,6 +221,10 @@ org.apache.maven.plugins maven-checkstyle-plugin + + org.basepom.maven + duplicate-finder-maven-plugin + From 1cc7e6c9e4c7e7a1e4dab6012e2d002b9bd05fba Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 4 Dec 2023 18:42:08 +1100 Subject: [PATCH 177/322] Test on Java 21 --- .github/workflows/maven.yml | 4 ++-- README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index c7df2ed3..32668e32 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -19,7 +19,7 @@ jobs: uses: actions/setup-java@v3 with: distribution: zulu - java-version: 17 + java-version: 21 cache: maven - name: Install Zig @@ -50,7 +50,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - java: [8, 11, 17, 20] + java: [8, 11, 17, 21] steps: - name: Check out Git repository diff --git a/README.md b/README.md index 168e046e..048e57bc 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ * Mature code (commenced in 2016) and used for heavy production workloads (eg > 500 TB of HFT data) * Actively maintained and with a "Zero Bug Policy" before every release (see [issues](https://github.com/lmdbjava/lmdbjava/issues)) * Available from [Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.lmdbjava%22%20AND%20a%3A%22lmdbjava%22) and [OSS Sonatype Snapshots](https://oss.sonatype.org/content/repositories/snapshots/org/lmdbjava/lmdbjava) -* [Continuous integration](https://github.com/lmdbjava/lmdbjava/actions) testing on Linux, Windows and macOS with Java 8, 11, 17 and 20 +* [Continuous integration](https://github.com/lmdbjava/lmdbjava/actions) testing on Linux, Windows and macOS with Java 8, 11, 17 and 21 ### Performance From 844c5195e40e1ab629b87633e857522101a958c4 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Tue, 5 Dec 2023 11:36:39 +1100 Subject: [PATCH 178/322] Update to JNR-FFI 2.2.15 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 429e183a..1ea89d76 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ com.github.jnr jnr-ffi - 2.2.13 + 2.2.15 com.google.code.findbugs From bdccf3bc8593ad15342995124ef69bb5233275fd Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Tue, 5 Dec 2023 11:36:47 +1100 Subject: [PATCH 179/322] Update to Guava 32.1.3-JRE --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1ea89d76..542f1b43 100644 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,7 @@ com.google.guava guava - 31.1-jre + 32.1.3-jre test From 7ae7754a627c7d8153de4e907e2163be430cb03d Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Tue, 5 Dec 2023 11:37:02 +1100 Subject: [PATCH 180/322] Update to Netty Buffer 4.1.101.Final --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 542f1b43..205ef433 100644 --- a/pom.xml +++ b/pom.xml @@ -53,7 +53,7 @@ io.netty netty-buffer - 4.1.87.Final + 4.1.101.Final true From 699b735de002dab61f68441279b30ecdb09f09e3 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Tue, 5 Dec 2023 11:37:13 +1100 Subject: [PATCH 181/322] Update to Agrona 1.20.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 205ef433..1946e3e9 100644 --- a/pom.xml +++ b/pom.xml @@ -63,7 +63,7 @@ org.agrona agrona - 1.17.1 + 1.20.0 true From f17b63f0ab8c90946d30f360a75b9e3107886a0c Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Tue, 5 Dec 2023 12:34:17 +1100 Subject: [PATCH 182/322] [maven-release-plugin] prepare release lmdbjava-0.9.0 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 1946e3e9..52bc82f9 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ org.lmdbjava lmdbjava - 0.9.0-SNAPSHOT + 0.9.0 jar LmdbJava Low latency Java API for the ultra-fast, embedded Symas Lightning Database (LMDB) @@ -176,7 +176,7 @@ scm:git:git@github.com:${github.org}/${github.repo}.git scm:git:git@github.com:${github.org}/${github.repo}.git git@github.com:${github.org}/${github.repo}.git - HEAD + lmdbjava-0.9.0 GitHub Issues From 991be9adcef071c3d805c2cf6b7ca128fa63fcf5 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Tue, 5 Dec 2023 12:34:24 +1100 Subject: [PATCH 183/322] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 52bc82f9..75ce0fe6 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ org.lmdbjava lmdbjava - 0.9.0 + 0.9.1-SNAPSHOT jar LmdbJava Low latency Java API for the ultra-fast, embedded Symas Lightning Database (LMDB) @@ -176,7 +176,7 @@ scm:git:git@github.com:${github.org}/${github.repo}.git scm:git:git@github.com:${github.org}/${github.repo}.git git@github.com:${github.org}/${github.repo}.git - lmdbjava-0.9.0 + HEAD GitHub Issues From 5332a0e4a9d764e783029792c2edac18f17dfc8f Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Sun, 16 Feb 2025 23:34:56 +0000 Subject: [PATCH 184/322] Uplift jnr-ffi to 2.2.17 (#235) and upload-artifact / download-artifact actions * Uplift jnr-ffi to 2.2.16 Potentially fixes this https://github.com/jnr/jffi/issues/138 * Uplift jnr-ffi to 2.2.17 * Uplift actions/upload-artifact to v4 * Uplift download-artifact action to v4 * Uplift upload-artifact action to v4 --- .github/workflows/maven.yml | 8 ++++---- pom.xml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 32668e32..4fc1b22c 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -32,7 +32,7 @@ jobs: run: mvn -B verify -DgcRecordWrites=1000 - name: Store built native libraries for later jobs - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: native-libraries path: | @@ -64,7 +64,7 @@ jobs: cache: maven - name: Fetch built native libraries - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: native-libraries path: src/main/resources/org/lmdbjava @@ -73,7 +73,7 @@ jobs: run: mvn -B test -Dtest=VerifierTest -DverificationSeconds=10 - name: Upload Surefire reports on test failure - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: name: surefire-test-log @@ -116,7 +116,7 @@ jobs: MAVEN_CENTRAL_TOKEN: ${{ secrets.nexus_password }} - name: Debug settings.xml - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() with: name: settings.xml diff --git a/pom.xml b/pom.xml index 75ce0fe6..9ef2ea4f 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ com.github.jnr jnr-ffi - 2.2.15 + 2.2.17 com.google.code.findbugs From b5dfb253767ca0d7ffcdfcdb8a00a50a31c84c7b Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Sun, 16 Feb 2025 23:37:16 +0000 Subject: [PATCH 185/322] Uplift lmdb from 0.9.29 to 0.9.31 (#245) * Uplidt LMDB lib from 0.9.29 to 0.9.31 * Uplift upload-artifact action to v4 --- cross-compile.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cross-compile.sh b/cross-compile.sh index 1ebf92f6..ebf92d72 100755 --- a/cross-compile.sh +++ b/cross-compile.sh @@ -3,7 +3,7 @@ set -o errexit rm -rf lmdb -git clone --depth 1 --branch LMDB_0.9.29 https://github.com/LMDB/lmdb.git +git clone --depth 1 --branch LMDB_0.9.31 https://github.com/LMDB/lmdb.git pushd lmdb/libraries/liblmdb trap popd SIGINT From 68f0a4465d91a05cc3bebaf602f6afbe8ba3b44b Mon Sep 17 00:00:00 2001 From: Cosimo Damiano Prete <8491864+cdprete@users.noreply.github.com> Date: Mon, 17 Feb 2025 00:46:30 +0100 Subject: [PATCH 186/322] Regression: Add DbiFlags#MDB_UNSIGNEDKEY to allow to compare byte array, ByteBuffer and DirectBuffer keys as unsigned like in versions prior to 0.9.0 (#237) Co-authored-by: Cosimo Damiano Prete --- src/main/java/org/lmdbjava/BufferProxy.java | 22 ++++++- .../java/org/lmdbjava/ByteArrayProxy.java | 50 +++++++++++---- src/main/java/org/lmdbjava/ByteBufProxy.java | 28 ++++++--- .../java/org/lmdbjava/ByteBufferProxy.java | 28 ++++----- src/main/java/org/lmdbjava/Cursor.java | 10 +-- src/main/java/org/lmdbjava/Dbi.java | 38 +++++++----- src/main/java/org/lmdbjava/DbiFlags.java | 21 ++++++- .../java/org/lmdbjava/DirectBufferProxy.java | 40 +++++++----- src/main/java/org/lmdbjava/Env.java | 42 +++++-------- src/main/java/org/lmdbjava/MaskedFlag.java | 62 +++++++++++++++---- src/main/java/org/lmdbjava/Txn.java | 6 +- 11 files changed, 233 insertions(+), 114 deletions(-) diff --git a/src/main/java/org/lmdbjava/BufferProxy.java b/src/main/java/org/lmdbjava/BufferProxy.java index aff732bc..f42da7ff 100644 --- a/src/main/java/org/lmdbjava/BufferProxy.java +++ b/src/main/java/org/lmdbjava/BufferProxy.java @@ -21,6 +21,10 @@ package org.lmdbjava; import static java.lang.Long.BYTES; +import static org.lmdbjava.DbiFlags.MDB_INTEGERKEY; +import static org.lmdbjava.DbiFlags.MDB_UNSIGNEDKEY; +import static org.lmdbjava.MaskedFlag.isSet; +import static org.lmdbjava.MaskedFlag.mask; import java.util.Comparator; @@ -72,8 +76,24 @@ public abstract class BufferProxy { * @param flags for the database * @return a comparator that can be used (never null) */ - protected abstract Comparator getComparator(DbiFlags... flags); + protected Comparator getComparator(DbiFlags... flags) { + final int intFlag = mask(flags); + return isSet(intFlag, MDB_INTEGERKEY) || isSet(intFlag, MDB_UNSIGNEDKEY) ? getUnsignedComparator() : getSignedComparator(); + } + + /** + * Get a suitable default {@link Comparator} to compare numeric key values as unsigned. + * + * @return a comparator that can be used (never null) + */ + protected abstract Comparator getUnsignedComparator(); + /** + * Get a suitable default {@link Comparator} to compare numeric key values as signed. + * + * @return a comparator that can be used (never null) + */ + protected abstract Comparator getSignedComparator(); /** * Deallocate a buffer that was previously provided by {@link #allocate()}. * diff --git a/src/main/java/org/lmdbjava/ByteArrayProxy.java b/src/main/java/org/lmdbjava/ByteArrayProxy.java index 3fe8184a..2066b679 100644 --- a/src/main/java/org/lmdbjava/ByteArrayProxy.java +++ b/src/main/java/org/lmdbjava/ByteArrayProxy.java @@ -20,14 +20,15 @@ package org.lmdbjava; -import static java.util.Objects.requireNonNull; -import static org.lmdbjava.Library.RUNTIME; +import jnr.ffi.Pointer; +import jnr.ffi.provider.MemoryManager; import java.util.Arrays; import java.util.Comparator; -import jnr.ffi.Pointer; -import jnr.ffi.provider.MemoryManager; +import static java.lang.Math.min; +import static java.util.Objects.requireNonNull; +import static org.lmdbjava.Library.RUNTIME; /** * Byte array proxy. @@ -43,7 +44,10 @@ public final class ByteArrayProxy extends BufferProxy { private static final MemoryManager MEM_MGR = RUNTIME.getMemoryManager(); - private ByteArrayProxy() { + private static final Comparator signedComparator = ByteArrayProxy::compareArraysSigned; + private static final Comparator unsignedComparator = ByteArrayProxy::compareArrays; + + private ByteArrayProxy() { } /** @@ -60,7 +64,7 @@ public static int compareArrays(final byte[] o1, final byte[] o2) { if (o1 == o2) { return 0; } - final int minLength = Math.min(o1.length, o2.length); + final int minLength = min(o1.length, o2.length); for (int i = 0; i < minLength; i++) { final int lw = Byte.toUnsignedInt(o1[i]); @@ -74,15 +78,32 @@ public static int compareArrays(final byte[] o1, final byte[] o2) { return o1.length - o2.length; } + /** + * Compare two byte arrays. + * + * @param b1 left operand (required) + * @param b2 right operand (required) + * @return as specified by {@link Comparable} interface + */ + @SuppressWarnings("PMD.CompareObjectsWithEquals") + public static int compareArraysSigned(final byte[] b1, final byte[] b2) { + requireNonNull(b1); + requireNonNull(b2); + + if (b1 == b2) return 0; + + for(int i = 0; i < min(b1.length, b2.length); ++i) { + if(b1[i] != b2[i]) return b1[i] - b2[i]; + } + + return b1.length - b2.length; + } + @Override protected byte[] allocate() { return new byte[0]; } - protected int compare(final byte[] o1, final byte[] o2) { - return compareArrays(o1, o2); - } - @Override protected void deallocate(final byte[] buff) { // byte arrays cannot be allocated @@ -94,8 +115,13 @@ protected byte[] getBytes(final byte[] buffer) { } @Override - protected Comparator getComparator(final DbiFlags... flags) { - return this::compare; + protected Comparator getSignedComparator() { + return signedComparator; + } + + @Override + protected Comparator getUnsignedComparator() { + return unsignedComparator; } @Override diff --git a/src/main/java/org/lmdbjava/ByteBufProxy.java b/src/main/java/org/lmdbjava/ByteBufProxy.java index 26351676..ed3d71e1 100644 --- a/src/main/java/org/lmdbjava/ByteBufProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufProxy.java @@ -20,16 +20,17 @@ package org.lmdbjava; -import static io.netty.buffer.PooledByteBufAllocator.DEFAULT; -import static java.lang.Class.forName; -import static org.lmdbjava.UnsafeAccess.UNSAFE; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.PooledByteBufAllocator; +import jnr.ffi.Pointer; import java.lang.reflect.Field; import java.util.Comparator; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.PooledByteBufAllocator; -import jnr.ffi.Pointer; +import static io.netty.buffer.PooledByteBufAllocator.DEFAULT; +import static java.lang.Class.forName; +import static java.util.Objects.requireNonNull; +import static org.lmdbjava.UnsafeAccess.UNSAFE; /** * A buffer proxy backed by Netty's {@link ByteBuf}. @@ -51,6 +52,12 @@ public final class ByteBufProxy extends BufferProxy { private static final String FIELD_NAME_ADDRESS = "memoryAddress"; private static final String FIELD_NAME_LENGTH = "length"; private static final String NAME = "io.netty.buffer.PooledUnsafeDirectByteBuf"; + private static final Comparator comparator = (o1, o2) -> { + requireNonNull(o1); + requireNonNull(o2); + + return o1.compareTo(o2); + }; private final long lengthOffset; private final long addressOffset; @@ -107,13 +114,14 @@ protected ByteBuf allocate() { throw new IllegalStateException("Netty buffer must be " + NAME); } - protected int compare(final ByteBuf o1, final ByteBuf o2) { - return o1.compareTo(o2); + @Override + protected Comparator getSignedComparator() { + return comparator; } @Override - protected Comparator getComparator(final DbiFlags... flags) { - return this::compare; + protected Comparator getUnsignedComparator() { + return comparator; } @Override diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index 8eb95da8..9983d76e 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -27,6 +27,7 @@ import static java.nio.ByteOrder.LITTLE_ENDIAN; import static java.util.Objects.requireNonNull; import static org.lmdbjava.DbiFlags.MDB_INTEGERKEY; +import static org.lmdbjava.DbiFlags.MDB_UNSIGNEDKEY; import static org.lmdbjava.Env.SHOULD_CHECK; import static org.lmdbjava.MaskedFlag.isSet; import static org.lmdbjava.MaskedFlag.mask; @@ -111,6 +112,14 @@ abstract static class AbstractByteBufferProxy extends BufferProxy { protected static final String FIELD_NAME_ADDRESS = "address"; protected static final String FIELD_NAME_CAPACITY = "capacity"; + private static final Comparator signedComparator = (o1, o2) -> { + requireNonNull(o1); + requireNonNull(o2); + + return o1.compareTo(o2); + }; + private static final Comparator unsignedComparator = AbstractByteBufferProxy::compareBuff; + /** * A thread-safe pool for a given length. If the buffer found is valid (ie * not of a negative length) then that buffer is used. If no valid buffer is @@ -193,22 +202,13 @@ protected final ByteBuffer allocate() { } @Override - protected Comparator getComparator(final DbiFlags... flags) { - final int flagInt = mask(flags); - if (isSet(flagInt, MDB_INTEGERKEY)) { - return this::compareCustom; - } - return this::compareDefault; + protected Comparator getSignedComparator() { + return signedComparator; } - protected final int compareDefault(final ByteBuffer o1, - final ByteBuffer o2) { - return o1.compareTo(o2); - } - - protected final int compareCustom(final ByteBuffer o1, - final ByteBuffer o2) { - return compareBuff(o1, o2); + @Override + protected Comparator getUnsignedComparator() { + return unsignedComparator; } @Override diff --git a/src/main/java/org/lmdbjava/Cursor.java b/src/main/java/org/lmdbjava/Cursor.java index ed7c1848..cb9130e3 100644 --- a/src/main/java/org/lmdbjava/Cursor.java +++ b/src/main/java/org/lmdbjava/Cursor.java @@ -40,6 +40,8 @@ import jnr.ffi.Pointer; import jnr.ffi.byref.NativeLongByReference; +import java.util.Arrays; + /** * A cursor handle. * @@ -120,7 +122,7 @@ public void delete(final PutFlags... f) { txn.checkReady(); txn.checkWritesAllowed(); } - final int flags = mask(f); + final int flags = mask(true, f); checkRc(LIB.mdb_cursor_del(ptrCursor, flags)); } @@ -256,7 +258,7 @@ public boolean put(final T key, final T val, final PutFlags... op) { } kv.keyIn(key); kv.valIn(val); - final int mask = mask(op); + final int mask = mask(true, op); final int rc = LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), mask); if (rc == MDB_KEYEXIST) { @@ -299,7 +301,7 @@ public void putMultiple(final T key, final T val, final int elements, txn.checkReady(); txn.checkWritesAllowed(); } - final int mask = mask(op); + final int mask = mask(true, op); if (SHOULD_CHECK && !isSet(mask, MDB_MULTIPLE)) { throw new IllegalArgumentException("Must set " + MDB_MULTIPLE + " flag"); } @@ -364,7 +366,7 @@ public T reserve(final T key, final int size, final PutFlags... op) { } kv.keyIn(key); kv.valIn(size); - final int flags = mask(op) | MDB_RESERVE.getMask(); + final int flags = mask(true, op) | MDB_RESERVE.getMask(); checkRc(LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), flags)); kv.valOut(); diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index ef8ec315..c5be4f85 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -20,6 +20,17 @@ package org.lmdbjava; +import jnr.ffi.Pointer; +import jnr.ffi.byref.IntByReference; +import jnr.ffi.byref.PointerByReference; +import org.lmdbjava.Library.ComparatorCallback; +import org.lmdbjava.Library.MDB_stat; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + import static java.util.Objects.requireNonNull; import static jnr.ffi.Memory.allocateDirect; import static jnr.ffi.NativeType.ADDRESS; @@ -36,17 +47,6 @@ import static org.lmdbjava.PutFlags.MDB_RESERVE; import static org.lmdbjava.ResultCodeMapper.checkRc; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; - -import jnr.ffi.Pointer; -import jnr.ffi.byref.IntByReference; -import jnr.ffi.byref.PointerByReference; -import org.lmdbjava.Library.ComparatorCallback; -import org.lmdbjava.Library.MDB_stat; - /** * LMDB Database. * @@ -64,10 +64,18 @@ public final class Dbi { Dbi(final Env env, final Txn txn, final byte[] name, final Comparator comparator, final boolean nativeCb, final BufferProxy proxy, final DbiFlags... flags) { + if (SHOULD_CHECK) { + requireNonNull(txn); + txn.checkReady(); + } this.env = env; this.name = name == null ? null : Arrays.copyOf(name, name.length); - this.comparator = comparator; - final int flagsMask = mask(flags); + if(comparator == null) { + this.comparator = proxy.getComparator(flags); + } else { + this.comparator = comparator; + } + final int flagsMask = mask(true, flags); final Pointer dbiPtr = allocateDirect(RUNTIME, ADDRESS); checkRc(LIB.mdb_dbi_open(txn.pointer(), name, flagsMask, dbiPtr)); ptr = dbiPtr.getPointer(0); @@ -377,7 +385,7 @@ public boolean put(final Txn txn, final T key, final T val, } txn.kv().keyIn(key); txn.kv().valIn(val); - final int mask = mask(flags); + final int mask = mask(true, flags); final int rc = LIB.mdb_put(txn.pointer(), ptr, txn.kv().pointerKey(), txn .kv().pointerVal(), mask); if (rc == MDB_KEYEXIST) { @@ -422,7 +430,7 @@ public T reserve(final Txn txn, final T key, final int size, } txn.kv().keyIn(key); txn.kv().valIn(size); - final int flags = mask(op) | MDB_RESERVE.getMask(); + final int flags = mask(true, op) | MDB_RESERVE.getMask(); checkRc(LIB.mdb_put(txn.pointer(), ptr, txn.kv().pointerKey(), txn.kv() .pointerVal(), flags)); txn.kv().valOut(); // marked as in,out in LMDB C docs diff --git a/src/main/java/org/lmdbjava/DbiFlags.java b/src/main/java/org/lmdbjava/DbiFlags.java index 081fb80e..7277b55b 100644 --- a/src/main/java/org/lmdbjava/DbiFlags.java +++ b/src/main/java/org/lmdbjava/DbiFlags.java @@ -68,6 +68,15 @@ public enum DbiFlags implements MaskedFlag { * similar to {@link #MDB_INTEGERKEY} keys. */ MDB_INTEGERDUP(0x20), + /** + * Compare the numeric keys in native byte order and as unsigned. + * + *

+ * This option is applied only to {@link java.nio.ByteBuffer}, {@link org.agrona.DirectBuffer} and byte array keys. + * {@link io.netty.buffer.ByteBuf} keys are always compared in native byte order and as unsigned. + *

+ */ + MDB_UNSIGNEDKEY(0x30, false), /** * With {@link #MDB_DUPSORT}, use reverse string dups. * @@ -86,9 +95,15 @@ public enum DbiFlags implements MaskedFlag { MDB_CREATE(0x4_0000); private final int mask; + private final boolean propagatedToLmdb; - DbiFlags(final int mask) { + DbiFlags(final int mask, final boolean propagatedToLmdb) { this.mask = mask; + this.propagatedToLmdb = propagatedToLmdb; + } + + DbiFlags(final int mask) { + this(mask, true); } @Override @@ -96,4 +111,8 @@ public int getMask() { return mask; } + @Override + public boolean isPropagatedToLmdb() { + return propagatedToLmdb; + } } diff --git a/src/main/java/org/lmdbjava/DirectBufferProxy.java b/src/main/java/org/lmdbjava/DirectBufferProxy.java index 62b49095..3bde81ec 100644 --- a/src/main/java/org/lmdbjava/DirectBufferProxy.java +++ b/src/main/java/org/lmdbjava/DirectBufferProxy.java @@ -20,20 +20,20 @@ package org.lmdbjava; -import static java.lang.ThreadLocal.withInitial; -import static java.nio.ByteBuffer.allocateDirect; -import static java.nio.ByteOrder.BIG_ENDIAN; -import static java.util.Objects.requireNonNull; -import static org.lmdbjava.UnsafeAccess.UNSAFE; +import jnr.ffi.Pointer; +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; import java.nio.ByteBuffer; import java.util.ArrayDeque; import java.util.Comparator; -import jnr.ffi.Pointer; -import org.agrona.DirectBuffer; -import org.agrona.MutableDirectBuffer; -import org.agrona.concurrent.UnsafeBuffer; +import static java.lang.ThreadLocal.withInitial; +import static java.nio.ByteBuffer.allocateDirect; +import static java.nio.ByteOrder.BIG_ENDIAN; +import static java.util.Objects.requireNonNull; +import static org.lmdbjava.UnsafeAccess.UNSAFE; /** * A buffer proxy backed by Agrona's {@link DirectBuffer}. @@ -42,6 +42,13 @@ * This class requires {@link UnsafeAccess} and Agrona must be in the classpath. */ public final class DirectBufferProxy extends BufferProxy { + private static final Comparator signedComparator = (o1, o2) -> { + requireNonNull(o1); + requireNonNull(o2); + + return o1.compareTo(o2); + }; + private static final Comparator unsignedComparator = DirectBufferProxy::compareBuff; /** * The {@link MutableDirectBuffer} proxy. Guaranteed to never be null, @@ -112,8 +119,14 @@ protected DirectBuffer allocate() { } } - protected int compare(final DirectBuffer o1, final DirectBuffer o2) { - return compareBuff(o1, o2); + @Override + protected Comparator getSignedComparator() { + return signedComparator; + } + + @Override + protected Comparator getUnsignedComparator() { + return unsignedComparator; } @Override @@ -129,11 +142,6 @@ protected byte[] getBytes(final DirectBuffer buffer) { return dest; } - @Override - protected Comparator getComparator(final DbiFlags... flags) { - return this::compare; - } - @Override protected void in(final DirectBuffer buffer, final Pointer ptr, final long ptrAddr) { diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index db8b0f4a..ae7e46c9 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -20,6 +20,19 @@ package org.lmdbjava; +import jnr.ffi.Pointer; +import jnr.ffi.byref.IntByReference; +import jnr.ffi.byref.PointerByReference; +import org.lmdbjava.Library.MDB_envinfo; +import org.lmdbjava.Library.MDB_stat; + +import java.io.File; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + import static java.lang.Boolean.getBoolean; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; @@ -33,19 +46,6 @@ import static org.lmdbjava.ResultCodeMapper.checkRc; import static org.lmdbjava.TxnFlags.MDB_RDONLY_TXN; -import java.io.File; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -import jnr.ffi.Pointer; -import jnr.ffi.byref.IntByReference; -import jnr.ffi.byref.PointerByReference; -import org.lmdbjava.Library.MDB_envinfo; -import org.lmdbjava.Library.MDB_stat; - /** * LMDB environment. * @@ -159,7 +159,7 @@ public void close() { public void copy(final File path, final CopyFlags... flags) { requireNonNull(path); validatePath(path); - final int flagsMask = mask(flags); + final int flagsMask = mask(true, flags); checkRc(LIB.mdb_env_copy2(ptr, path.getAbsolutePath(), flagsMask)); } @@ -388,17 +388,7 @@ public Dbi openDbi(final byte[] name, final Comparator comparator, public Dbi openDbi(final Txn txn, final byte[] name, final Comparator comparator, final boolean nativeCb, final DbiFlags... flags) { - if (SHOULD_CHECK) { - requireNonNull(txn); - txn.checkReady(); - } - final Comparator useComparator; - if (comparator == null) { - useComparator = proxy.getComparator(flags); - } else { - useComparator = comparator; - } - return new Dbi<>(this, txn, name, useComparator, nativeCb, proxy, flags); + return new Dbi<>(this, txn, name, comparator, nativeCb, proxy, flags); } /** @@ -583,7 +573,7 @@ public Env open(final File path, final int mode, checkRc(LIB.mdb_env_set_mapsize(ptr, mapSize)); checkRc(LIB.mdb_env_set_maxdbs(ptr, maxDbs)); checkRc(LIB.mdb_env_set_maxreaders(ptr, maxReaders)); - final int flagsMask = mask(flags); + final int flagsMask = mask(true, flags); final boolean readOnly = isSet(flagsMask, MDB_RDONLY_ENV); final boolean noSubDir = isSet(flagsMask, MDB_NOSUBDIR); checkRc(LIB.mdb_env_open(ptr, path.getAbsolutePath(), flagsMask, mode)); diff --git a/src/main/java/org/lmdbjava/MaskedFlag.java b/src/main/java/org/lmdbjava/MaskedFlag.java index 9bdef636..242ca589 100644 --- a/src/main/java/org/lmdbjava/MaskedFlag.java +++ b/src/main/java/org/lmdbjava/MaskedFlag.java @@ -20,6 +20,11 @@ package org.lmdbjava; +import java.util.Arrays; +import java.util.Objects; +import java.util.function.Predicate; +import java.util.stream.Stream; + import static java.util.Objects.requireNonNull; /** @@ -34,25 +39,58 @@ public interface MaskedFlag { */ int getMask(); + /** + * Indicates if the flag must be propagated to the underlying C code of LMDB or not. + * + * @return the boolean value indicating the propagation + */ + default boolean isPropagatedToLmdb() { + return true; + } + + /** + * Fetch the integer mask for all presented flags. + * + * @param flags to mask (null or empty returns zero) + * @return the integer mask for use in C + */ + @SafeVarargs + static int mask(final M... flags) { + return mask(false, flags); + } + /** * Fetch the integer mask for all presented flags. * * @param flags to mask (null or empty returns zero) * @return the integer mask for use in C */ - static int mask(final MaskedFlag... flags) { - if (flags == null || flags.length == 0) { - return 0; - } + static int mask(final Stream flags) { + return mask(false, flags); + } + + /** + * Fetch the integer mask for the presented flags. + * + * @param onlyPropagatedToLmdb if to include only the flags which are also propagate to the C code or all of them + * @param flags to mask (null or empty returns zero) + * @return the integer mask for use in C + */ + @SafeVarargs + static int mask(final boolean onlyPropagatedToLmdb, final M... flags) { + return flags == null ? 0 : mask(onlyPropagatedToLmdb, Arrays.stream(flags)); + } + + /** + * Fetch the integer mask for all presented flags. + * + * @param onlyPropagatedToLmdb if to include only the flags which are also propagate to the C code or all of them + * @return the integer mask for use in C + */ + static int mask(final boolean onlyPropagatedToLmdb, final Stream flags) { + final Predicate filter = onlyPropagatedToLmdb ? MaskedFlag::isPropagatedToLmdb : f -> true; - int result = 0; - for (final MaskedFlag flag : flags) { - if (flag == null) { - continue; - } - result |= flag.getMask(); - } - return result; + return flags == null ? 0 : flags.filter(Objects::nonNull).filter(filter).map(M::getMask).reduce(0, (f1, f2) -> f1 | f2); } /** diff --git a/src/main/java/org/lmdbjava/Txn.java b/src/main/java/org/lmdbjava/Txn.java index b6d28917..3092c7c4 100644 --- a/src/main/java/org/lmdbjava/Txn.java +++ b/src/main/java/org/lmdbjava/Txn.java @@ -20,6 +20,8 @@ package org.lmdbjava; +import jnr.ffi.Pointer; + import static jnr.ffi.Memory.allocateDirect; import static jnr.ffi.NativeType.ADDRESS; import static org.lmdbjava.Env.SHOULD_CHECK; @@ -34,8 +36,6 @@ import static org.lmdbjava.Txn.State.RESET; import static org.lmdbjava.TxnFlags.MDB_RDONLY_TXN; -import jnr.ffi.Pointer; - /** * LMDB transaction. * @@ -55,7 +55,7 @@ public final class Txn implements AutoCloseable { final TxnFlags... flags) { this.proxy = proxy; this.keyVal = proxy.keyVal(); - final int flagsMask = mask(flags); + final int flagsMask = mask(true, flags); this.readOnly = isSet(flagsMask, MDB_RDONLY_TXN); if (env.isReadOnly() && !this.readOnly) { throw new EnvIsReadOnly(); From 309d8d9be4d2ff84109fa0b140afd2910d1f49d3 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 17 Feb 2025 13:32:42 +1100 Subject: [PATCH 187/322] Refactor project build configuration This commit makes no functional changes to any LmdbJava code. It is purely confined to build system related adjustments. These are aimed at simplifying long-term maintenance and ease of third party contributions. The changes include: * Removal of parent POM * Removal of PMD, SpotBugs and Checkstyle * Replacement of license plugin * Automatic code formatting (using Google style) * Update to Codecov integration --- .github/workflows/maven.yml | 4 +- README.md | 5 + cross-compile.sh | 16 + pom.xml | 351 ++++++++++++++++-- src/main/java/org/lmdbjava/BufferProxy.java | 115 +++--- .../java/org/lmdbjava/ByteArrayProxy.java | 52 +-- src/main/java/org/lmdbjava/ByteBufProxy.java | 79 ++-- .../java/org/lmdbjava/ByteBufferProxy.java | 119 +++--- src/main/java/org/lmdbjava/CopyFlags.java | 28 +- src/main/java/org/lmdbjava/Cursor.java | 127 +++---- .../java/org/lmdbjava/CursorIterable.java | 70 ++-- src/main/java/org/lmdbjava/Dbi.java | 246 ++++++------ src/main/java/org/lmdbjava/DbiFlags.java | 69 ++-- .../java/org/lmdbjava/DirectBufferProxy.java | 80 ++-- src/main/java/org/lmdbjava/Env.java | 319 +++++++--------- src/main/java/org/lmdbjava/EnvFlags.java | 166 ++++----- src/main/java/org/lmdbjava/EnvInfo.java | 74 ++-- src/main/java/org/lmdbjava/GetOp.java | 36 +- src/main/java/org/lmdbjava/KeyRange.java | 78 ++-- src/main/java/org/lmdbjava/KeyRangeType.java | 337 +++++++---------- src/main/java/org/lmdbjava/KeyVal.java | 35 +- src/main/java/org/lmdbjava/Library.java | 103 ++--- src/main/java/org/lmdbjava/LmdbException.java | 24 +- .../org/lmdbjava/LmdbNativeException.java | 57 +-- src/main/java/org/lmdbjava/MaskedFlag.java | 52 +-- src/main/java/org/lmdbjava/Meta.java | 54 +-- src/main/java/org/lmdbjava/PutFlags.java | 45 +-- src/main/java/org/lmdbjava/ReferenceUtil.java | 56 +-- .../java/org/lmdbjava/ResultCodeMapper.java | 31 +- src/main/java/org/lmdbjava/SeekOp.java | 87 ++--- src/main/java/org/lmdbjava/Stat.java | 75 ++-- src/main/java/org/lmdbjava/TargetName.java | 108 +++--- src/main/java/org/lmdbjava/Txn.java | 140 +++---- src/main/java/org/lmdbjava/TxnFlags.java | 26 +- src/main/java/org/lmdbjava/UnsafeAccess.java | 52 +-- src/main/java/org/lmdbjava/Verifier.java | 111 +++--- src/main/java/org/lmdbjava/package-info.java | 69 ++-- src/misc/license-template.txt | 13 + .../org/lmdbjava/ByteBufferProxyTest.java | 30 +- .../java/org/lmdbjava/ComparatorTest.java | 79 ++-- .../java/org/lmdbjava/CursorIterableTest.java | 83 ++--- .../java/org/lmdbjava/CursorParamTest.java | 68 ++-- src/test/java/org/lmdbjava/CursorTest.java | 88 ++--- src/test/java/org/lmdbjava/DbiTest.java | 137 +++---- src/test/java/org/lmdbjava/EnvTest.java | 136 ++----- .../org/lmdbjava/GarbageCollectionTest.java | 47 +-- src/test/java/org/lmdbjava/KeyRangeTest.java | 36 +- src/test/java/org/lmdbjava/LibraryTest.java | 21 +- .../java/org/lmdbjava/MaskedFlagTest.java | 25 +- src/test/java/org/lmdbjava/MetaTest.java | 25 +- .../org/lmdbjava/ResultCodeMapperTest.java | 23 +- .../java/org/lmdbjava/TargetNameTest.java | 21 +- src/test/java/org/lmdbjava/TestUtils.java | 38 +- src/test/java/org/lmdbjava/TutorialTest.java | 131 +++---- src/test/java/org/lmdbjava/TxnTest.java | 53 +-- src/test/java/org/lmdbjava/VerifierTest.java | 37 +- src/test/java/org/lmdbjava/package-info.java | 20 +- 57 files changed, 1991 insertions(+), 2616 deletions(-) create mode 100644 src/misc/license-template.txt diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 4fc1b22c..b28e8481 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -40,7 +40,9 @@ jobs: src/main/resources/org/lmdbjava/*.dll - name: Upload code coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v5 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_ORG_TOKEN }} compatibility-checks: name: Java ${{ matrix.java }} on ${{ matrix.os }} Compatibility diff --git a/README.md b/README.md index 048e57bc..62c5abe1 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,11 @@ system package managers, running `cross-compile.sh` on a supported system, or copying it from the `org/lmdbjava` directory of any recent, officially released LmdbJava JAR. +### Releasing + +GitHub Actions will perform an official release whenever a developer executes +`mvn release:clean release:prepare`. + ### Contributing Contributions are welcome! Please see the [Contributing Guidelines](CONTRIBUTING.md). diff --git a/cross-compile.sh b/cross-compile.sh index ebf92d72..aa8815d5 100755 --- a/cross-compile.sh +++ b/cross-compile.sh @@ -1,4 +1,20 @@ #!/bin/bash +# +# Copyright © 2016-2025 The LmdbJava Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + set -o errexit diff --git a/pom.xml b/pom.xml index 9ef2ea4f..9e695d0f 100644 --- a/pom.xml +++ b/pom.xml @@ -1,11 +1,23 @@ + 4.0.0 - - au.com.acegi - acegi-standard-project - 0.7.0 - org.lmdbjava lmdbjava 0.9.1-SNAPSHOT @@ -13,9 +25,13 @@ LmdbJava Low latency Java API for the ultra-fast, embedded Symas Lightning Database (LMDB) - lmdbjava - lmdbjava - apache_v2 + yyyy-MM-dd'T'HH:mm:ss'Z' + 1.8 + 1.8 + 1.8 + 3.5.4 + UTF-8 + false @@ -28,10 +44,6 @@ jnr-ffi 2.2.17 - - com.google.code.findbugs - annotations - com.google.guava guava @@ -59,6 +71,14 @@ junit junit + 4.13.2 + test + + + org.hamcrest + hamcrest-core + + org.agrona @@ -69,6 +89,8 @@ org.hamcrest hamcrest + 2.2 + test org.mockito @@ -80,73 +102,246 @@ - au.com.acegi - xml-format-maven-plugin + com.mycila + license-maven-plugin + 4.6 + + + +
src/misc/license-template.txt
+ + LICENSE.txt + **/*.md + lmdb/** + +
+
+ true +
+ + + com.mycila + license-maven-plugin-git + 4.6 + + + + + apply-license + + format + + package + + 2 + + + +
+ + org.apache.maven.plugins + maven-clean-plugin + 3.4.0 + + false + - com.github.spotbugs - spotbugs-maven-plugin + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 org.apache.maven.plugins maven-dependency-plugin - - - com.github.jnr:jffi - org.mockito:mockito-core - org.mockito:mockito-inline - - + 3.8.1 + + + + analyze-only + properties + + + false + + com.github.jnr:jffi + org.mockito:mockito-core + org.mockito:mockito-inline + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.3 org.apache.maven.plugins maven-enforcer-plugin + 3.5.0 + + + config-enforcer + + enforce + + + + + + [${maven.enforcer.mvn},) + + + [${maven.enforcer.java},) + + + + + true + + + + + + + + + org.apache.maven.plugins + maven-install-plugin + 3.1.3 org.apache.maven.plugins maven-jar-plugin + 3.4.2 + + true + - org.lmdbjava + ${buildNumber} org.apache.maven.plugins - maven-pmd-plugin + maven-javadoc-plugin + 3.11.1 + + + attach-javadocs + + jar + + + org.apache.maven.plugins - maven-surefire-plugin + maven-release-plugin + 3.1.1 - 1 - false + clean + clean + false + true - org.codehaus.mojo - buildnumber-maven-plugin + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + + org.apache.maven.plugins + maven-site-plugin + 3.21.0 + + true + + + + org.apache.maven.plugins + maven-source-plugin + 3.3.1 + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.2 org.codehaus.mojo - license-maven-plugin + buildnumber-maven-plugin + 3.2.1 + + false + false + UNKNOWN + + jgit + + true + 7 + + + + org.apache.maven.scm + maven-scm-provider-jgit + 2.1.0 + + + + + + create + + initialize + + org.codehaus.mojo versions-maven-plugin + 2.18.0 org.jacoco jacoco-maven-plugin + 0.8.12 + + + config-jacoco-prepare-agent + + prepare-agent + + + + config-jacoco-report + + report + + +
2016 The LmdbJava Open Source Project - https://github.com/${github.org} + https://github.com/lmdbjava @@ -173,19 +368,29 @@ - scm:git:git@github.com:${github.org}/${github.repo}.git - scm:git:git@github.com:${github.org}/${github.repo}.git - git@github.com:${github.org}/${github.repo}.git + scm:git:git@github.com:lmdbjava/lmdbjava.git + scm:git:git@github.com:lmdbjava/lmdbjava.git + git@github.com:lmdbjava/lmdbjava.git HEAD GitHub Issues - https://github.com/${github.org}/${github.repo}/issues + https://github.com/lmdbjava/lmdbjava/issues GitHub Actions - https://github.com/${github.org}/${github.repo}/actions + https://github.com/lmdbjava/lmdbjava/actions + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + add-opens-on-java-9-and-above @@ -198,7 +403,7 @@ org.apache.maven.plugins maven-surefire-plugin - --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED + @{argLine} --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED 1 false @@ -216,14 +421,78 @@ com.github.ekryd.sortpom sortpom-maven-plugin + 4.0.0 + + ${project.basedir}/pom.xml + ${project.build.sourceEncoding} + custom_1 + true + groupId,artifactId + groupId,artifactId + true + false + false + \n + false + + + + + sort + + prepare-package + + + + com.spotify.fmt + fmt-maven-plugin + 2.25 + + + + format + + + + + + + + + ossrh-deploy + + org.apache.maven.plugins - maven-checkstyle-plugin + maven-gpg-plugin + 3.2.7 + + + sign-artifacts + + sign + + verify + + + --pinentry-mode + loopback + + + + - org.basepom.maven - duplicate-finder-maven-plugin + org.sonatype.plugins + nexus-staging-maven-plugin + 1.7.0 + true + + ossrh + https://oss.sonatype.org/ + true + diff --git a/src/main/java/org/lmdbjava/BufferProxy.java b/src/main/java/org/lmdbjava/BufferProxy.java index f42da7ff..e66031d2 100644 --- a/src/main/java/org/lmdbjava/BufferProxy.java +++ b/src/main/java/org/lmdbjava/BufferProxy.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * #L% */ - package org.lmdbjava; import static java.lang.Long.BYTES; @@ -27,51 +22,59 @@ import static org.lmdbjava.MaskedFlag.mask; import java.util.Comparator; - import jnr.ffi.Pointer; /** * The strategy for mapping memory address to a given buffer type. * - *

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

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

- * The provided comparator must strictly match the lexicographical order of - * keys in the native LMDB database. + *

The provided comparator must strictly match the lexicographical order of keys in the native + * LMDB database. * * @param flags for the database * @return a comparator that can be used (never null) @@ -79,64 +82,51 @@ public abstract class BufferProxy { protected Comparator getComparator(DbiFlags... flags) { final int intFlag = mask(flags); - return isSet(intFlag, MDB_INTEGERKEY) || isSet(intFlag, MDB_UNSIGNEDKEY) ? getUnsignedComparator() : getSignedComparator(); + return isSet(intFlag, MDB_INTEGERKEY) || isSet(intFlag, MDB_UNSIGNEDKEY) + ? getUnsignedComparator() + : getSignedComparator(); } - /** - * Get a suitable default {@link Comparator} to compare numeric key values as unsigned. - * - * @return a comparator that can be used (never null) - */ - protected abstract Comparator getUnsignedComparator(); /** * Get a suitable default {@link Comparator} to compare numeric key values as signed. * * @return a comparator that can be used (never null) */ protected abstract Comparator getSignedComparator(); - /** - * Deallocate a buffer that was previously provided by {@link #allocate()}. - * - * @param buff the buffer to deallocate (required) - */ - protected abstract void deallocate(T buff); /** - * Obtain a copy of the bytes contained within the passed buffer. + * Get a suitable default {@link Comparator} to compare numeric key values as unsigned. * - * @param buffer a non-null buffer created by this proxy instance - * @return a copy of the bytes this buffer is currently representing + * @return a comparator that can be used (never null) */ - protected abstract byte[] getBytes(T buffer); + protected abstract Comparator getUnsignedComparator(); /** - * Called when the MDB_val should be set to reflect the passed - * buffer. This buffer will have been created by end users, not - * {@link #allocate()}. + * Called when the MDB_val should be set to reflect the passed buffer. This buffer + * will have been created by end users, not {@link #allocate()}. * - * @param buffer the buffer to write to MDB_val - * @param ptr the pointer to the MDB_val + * @param buffer the buffer to write to MDB_val + * @param ptr the pointer to the MDB_val * @param ptrAddr the address of the MDB_val pointer */ protected abstract void in(T buffer, Pointer ptr, long ptrAddr); /** - * Called when the MDB_val should be set to reflect the passed - * buffer. + * Called when the MDB_val should be set to reflect the passed buffer. * - * @param buffer the buffer to write to MDB_val - * @param size the buffer size to write to MDB_val - * @param ptr the pointer to the MDB_val + * @param buffer the buffer to write to MDB_val + * @param size the buffer size to write to MDB_val + * @param ptr the pointer to the MDB_val * @param ptrAddr the address of the MDB_val pointer */ protected abstract void in(T buffer, int size, Pointer ptr, long ptrAddr); /** - * Called when the MDB_val may have changed and the passed buffer - * should be modified to reflect the new MDB_val. + * Called when the MDB_val may have changed and the passed buffer should be modified + * to reflect the new MDB_val. * - * @param buffer the buffer to write to MDB_val - * @param ptr the pointer to the MDB_val + * @param buffer the buffer to write to MDB_val + * @param ptr the pointer to the MDB_val * @param ptrAddr the address of the MDB_val pointer * @return the buffer for MDB_val */ @@ -150,5 +140,4 @@ protected Comparator getComparator(DbiFlags... flags) { final KeyVal keyVal() { return new KeyVal<>(this); } - } diff --git a/src/main/java/org/lmdbjava/ByteArrayProxy.java b/src/main/java/org/lmdbjava/ByteArrayProxy.java index 2066b679..4a22ab83 100644 --- a/src/main/java/org/lmdbjava/ByteArrayProxy.java +++ b/src/main/java/org/lmdbjava/ByteArrayProxy.java @@ -1,45 +1,37 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * #L% */ - package org.lmdbjava; -import jnr.ffi.Pointer; -import jnr.ffi.provider.MemoryManager; - -import java.util.Arrays; -import java.util.Comparator; - import static java.lang.Math.min; import static java.util.Objects.requireNonNull; import static org.lmdbjava.Library.RUNTIME; +import java.util.Arrays; +import java.util.Comparator; +import jnr.ffi.Pointer; +import jnr.ffi.provider.MemoryManager; + /** * Byte array proxy. * - * {@link Env#create(org.lmdbjava.BufferProxy)}. + *

{@link Env#create(org.lmdbjava.BufferProxy)}. */ public final class ByteArrayProxy extends BufferProxy { - /** - * The byte array proxy. Guaranteed to never be null. - */ + /** The byte array proxy. Guaranteed to never be null. */ public static final BufferProxy PROXY_BA = new ByteArrayProxy(); private static final MemoryManager MEM_MGR = RUNTIME.getMemoryManager(); @@ -47,8 +39,7 @@ public final class ByteArrayProxy extends BufferProxy { private static final Comparator signedComparator = ByteArrayProxy::compareArraysSigned; private static final Comparator unsignedComparator = ByteArrayProxy::compareArrays; - private ByteArrayProxy() { - } + private ByteArrayProxy() {} /** * Lexicographically compare two byte arrays. @@ -57,7 +48,6 @@ private ByteArrayProxy() { * @param o2 right operand (required) * @return as specified by {@link Comparable} interface */ - @SuppressWarnings("PMD.CompareObjectsWithEquals") public static int compareArrays(final byte[] o1, final byte[] o2) { requireNonNull(o1); requireNonNull(o2); @@ -85,15 +75,14 @@ public static int compareArrays(final byte[] o1, final byte[] o2) { * @param b2 right operand (required) * @return as specified by {@link Comparable} interface */ - @SuppressWarnings("PMD.CompareObjectsWithEquals") public static int compareArraysSigned(final byte[] b1, final byte[] b2) { requireNonNull(b1); requireNonNull(b2); if (b1 == b2) return 0; - for(int i = 0; i < min(b1.length, b2.length); ++i) { - if(b1[i] != b2[i]) return b1[i] - b2[i]; + for (int i = 0; i < min(b1.length, b2.length); ++i) { + if (b1[i] != b2[i]) return b1[i] - b2[i]; } return b1.length - b2.length; @@ -125,8 +114,7 @@ protected Comparator getUnsignedComparator() { } @Override - protected void in(final byte[] buffer, final Pointer ptr, - final long ptrAddr) { + protected void in(final byte[] buffer, final Pointer ptr, final long ptrAddr) { final Pointer pointer = MEM_MGR.allocateDirect(buffer.length); pointer.put(0, buffer, 0, buffer.length); ptr.putLong(STRUCT_FIELD_OFFSET_SIZE, buffer.length); @@ -134,14 +122,12 @@ protected void in(final byte[] buffer, final Pointer ptr, } @Override - protected void in(final byte[] buffer, final int size, final Pointer ptr, - final long ptrAddr) { + protected void in(final byte[] buffer, final int size, final Pointer ptr, final long ptrAddr) { // cannot reserve for byte arrays } @Override - protected byte[] out(final byte[] buffer, final Pointer ptr, - final long ptrAddr) { + protected byte[] out(final byte[] buffer, final Pointer ptr, final long ptrAddr) { final long addr = ptr.getAddress(STRUCT_FIELD_OFFSET_DATA); final int size = (int) ptr.getLong(STRUCT_FIELD_OFFSET_SIZE); final Pointer pointer = MEM_MGR.newPointer(addr, size); diff --git a/src/main/java/org/lmdbjava/ByteBufProxy.java b/src/main/java/org/lmdbjava/ByteBufProxy.java index ed3d71e1..cac5b97b 100644 --- a/src/main/java/org/lmdbjava/ByteBufProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufProxy.java @@ -1,50 +1,42 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * #L% */ - package org.lmdbjava; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.PooledByteBufAllocator; -import jnr.ffi.Pointer; - -import java.lang.reflect.Field; -import java.util.Comparator; - import static io.netty.buffer.PooledByteBufAllocator.DEFAULT; import static java.lang.Class.forName; import static java.util.Objects.requireNonNull; import static org.lmdbjava.UnsafeAccess.UNSAFE; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.PooledByteBufAllocator; +import java.lang.reflect.Field; +import java.util.Comparator; +import jnr.ffi.Pointer; + /** * A buffer proxy backed by Netty's {@link ByteBuf}. * - *

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

This class requires {@link UnsafeAccess} and netty-buffer must be in the classpath. */ public final class ByteBufProxy extends BufferProxy { /** - * A proxy for using Netty {@link ByteBuf}. Guaranteed to never be null, - * although a class initialization exception will occur if an attempt is made - * to access this field when Netty is unavailable. + * A proxy for using Netty {@link ByteBuf}. Guaranteed to never be null, although a class + * initialization exception will occur if an attempt is made to access this field when Netty is + * unavailable. */ public static final BufferProxy PROXY_NETTY = new ByteBufProxy(); @@ -52,22 +44,29 @@ public final class ByteBufProxy extends BufferProxy { private static final String FIELD_NAME_ADDRESS = "memoryAddress"; private static final String FIELD_NAME_LENGTH = "length"; private static final String NAME = "io.netty.buffer.PooledUnsafeDirectByteBuf"; - private static final Comparator comparator = (o1, o2) -> { - requireNonNull(o1); - requireNonNull(o2); + private static final Comparator comparator = + (o1, o2) -> { + requireNonNull(o1); + requireNonNull(o2); - return o1.compareTo(o2); - }; + return o1.compareTo(o2); + }; private final long lengthOffset; private final long addressOffset; - + private final PooledByteBufAllocator nettyAllocator; private ByteBufProxy() { this(DEFAULT); } + /** + * Constructs a buffer proxy for use with Netty. + * + * @param allocator the Netty allocator to obtain the {@link ByteBuf} from + */ public ByteBufProxy(final PooledByteBufAllocator allocator) { + super(); this.nettyAllocator = allocator; try { @@ -138,24 +137,20 @@ protected byte[] getBytes(final ByteBuf buffer) { @Override protected void in(final ByteBuf buffer, final Pointer ptr, final long ptrAddr) { - UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, - buffer.writerIndex() - buffer.readerIndex()); - UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA, - buffer.memoryAddress() + buffer.readerIndex()); + UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, buffer.writerIndex() - buffer.readerIndex()); + UNSAFE.putLong( + ptrAddr + STRUCT_FIELD_OFFSET_DATA, buffer.memoryAddress() + buffer.readerIndex()); } @Override - protected void in(final ByteBuf buffer, final int size, final Pointer ptr, - final long ptrAddr) { - UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, - size); - UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA, - buffer.memoryAddress() + buffer.readerIndex()); + protected void in(final ByteBuf buffer, final int size, final Pointer ptr, final long ptrAddr) { + UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, size); + UNSAFE.putLong( + ptrAddr + STRUCT_FIELD_OFFSET_DATA, buffer.memoryAddress() + buffer.readerIndex()); } @Override - protected ByteBuf out(final ByteBuf buffer, final Pointer ptr, - final long ptrAddr) { + protected ByteBuf out(final ByteBuf buffer, final Pointer ptr, final long ptrAddr) { final long addr = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA); final long size = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE); UNSAFE.putLong(buffer, addressOffset, addr); diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index 9983d76e..2b7cdf0d 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * #L% */ - package org.lmdbjava; import static java.lang.Long.reverseBytes; @@ -26,11 +21,7 @@ import static java.nio.ByteOrder.BIG_ENDIAN; import static java.nio.ByteOrder.LITTLE_ENDIAN; import static java.util.Objects.requireNonNull; -import static org.lmdbjava.DbiFlags.MDB_INTEGERKEY; -import static org.lmdbjava.DbiFlags.MDB_UNSIGNEDKEY; import static org.lmdbjava.Env.SHOULD_CHECK; -import static org.lmdbjava.MaskedFlag.isSet; -import static org.lmdbjava.MaskedFlag.mask; import static org.lmdbjava.UnsafeAccess.UNSAFE; import java.lang.reflect.Field; @@ -38,38 +29,32 @@ import java.nio.ByteBuffer; import java.util.ArrayDeque; import java.util.Comparator; - import jnr.ffi.Pointer; /** * {@link ByteBuffer}-based proxy. * - *

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

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

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

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

Users nominate which implementation they prefer by referencing the {@link #PROXY_OPTIMAL} or + * {@link #PROXY_SAFE} field when invoking {@link Env#create(org.lmdbjava.BufferProxy)}. */ public final class ByteBufferProxy { /** - * The fastest {@link ByteBuffer} proxy that is available on this platform. - * This will always be the same instance as {@link #PROXY_SAFE} if the - * {@link UnsafeAccess#DISABLE_UNSAFE_PROP} has been set to true - * and/or {@link UnsafeAccess} is unavailable. Guaranteed to never be null. + * The fastest {@link ByteBuffer} proxy that is available on this platform. This will always be + * the same instance as {@link #PROXY_SAFE} if the {@link UnsafeAccess#DISABLE_UNSAFE_PROP} has + * been set to true and/or {@link UnsafeAccess} is unavailable. Guaranteed to never + * be null. */ public static final BufferProxy PROXY_OPTIMAL; - /** - * The safe, reflective {@link ByteBuffer} proxy for this system. Guaranteed - * to never be null. - */ + /** The safe, reflective {@link ByteBuffer} proxy for this system. Guaranteed to never be null. */ public static final BufferProxy PROXY_SAFE; static { @@ -77,8 +62,7 @@ public final class ByteBufferProxy { PROXY_OPTIMAL = getProxyOptimal(); } - private ByteBufferProxy() { - } + private ByteBufferProxy() {} private static BufferProxy getProxyOptimal() { try { @@ -88,45 +72,42 @@ private static BufferProxy getProxyOptimal() { } } - /** - * The buffer must be a direct buffer (not heap allocated). - */ + /** The buffer must be a direct buffer (not heap allocated). */ public static final class BufferMustBeDirectException extends LmdbException { private static final long serialVersionUID = 1L; - /** - * Creates a new instance. - */ + /** Creates a new instance. */ public BufferMustBeDirectException() { super("The buffer must be a direct buffer (not heap allocated"); } } /** - * Provides {@link ByteBuffer} pooling and address resolution for concrete - * {@link BufferProxy} implementations. + * Provides {@link ByteBuffer} pooling and address resolution for concrete {@link BufferProxy} + * implementations. */ abstract static class AbstractByteBufferProxy extends BufferProxy { protected static final String FIELD_NAME_ADDRESS = "address"; protected static final String FIELD_NAME_CAPACITY = "capacity"; - private static final Comparator signedComparator = (o1, o2) -> { - requireNonNull(o1); - requireNonNull(o2); + private static final Comparator signedComparator = + (o1, o2) -> { + requireNonNull(o1); + requireNonNull(o2); - return o1.compareTo(o2); - }; - private static final Comparator unsignedComparator = AbstractByteBufferProxy::compareBuff; + return o1.compareTo(o2); + }; + private static final Comparator unsignedComparator = + AbstractByteBufferProxy::compareBuff; /** - * A thread-safe pool for a given length. If the buffer found is valid (ie - * not of a negative length) then that buffer is used. If no valid buffer is - * found, a new buffer is created. + * A thread-safe pool for a given length. If the buffer found is valid (ie not of a negative + * length) then that buffer is used. If no valid buffer is found, a new buffer is created. */ - private static final ThreadLocal> BUFFERS - = withInitial(() -> new ArrayDeque<>(16)); + private static final ThreadLocal> BUFFERS = + withInitial(() -> new ArrayDeque<>(16)); /** * Lexicographically compare two buffers. @@ -135,7 +116,6 @@ abstract static class AbstractByteBufferProxy extends BufferProxy { * @param o2 right operand (required) * @return as specified by {@link Comparable} interface */ - @SuppressWarnings("PMD.CyclomaticComplexity") public static int compareBuff(final ByteBuffer o1, final ByteBuffer o2) { requireNonNull(o1); requireNonNull(o2); @@ -224,12 +204,11 @@ protected byte[] getBytes(final ByteBuffer buffer) { buffer.get(dest, 0, buffer.limit()); return dest; } - } /** - * A proxy that uses Java reflection to modify byte buffer fields, and - * official JNR-FFF methods to manipulate native pointers. + * A proxy that uses Java reflection to modify byte buffer fields, and official JNR-FFF methods to + * manipulate native pointers. */ private static final class ReflectiveProxy extends AbstractByteBufferProxy { @@ -242,22 +221,20 @@ private static final class ReflectiveProxy extends AbstractByteBufferProxy { } @Override - protected void in(final ByteBuffer buffer, final Pointer ptr, - final long ptrAddr) { + protected void in(final ByteBuffer buffer, final Pointer ptr, final long ptrAddr) { ptr.putAddress(STRUCT_FIELD_OFFSET_DATA, address(buffer)); ptr.putLong(STRUCT_FIELD_OFFSET_SIZE, buffer.remaining()); } @Override - protected void in(final ByteBuffer buffer, final int size, final Pointer ptr, - final long ptrAddr) { + protected void in( + final ByteBuffer buffer, final int size, final Pointer ptr, final long ptrAddr) { ptr.putLong(STRUCT_FIELD_OFFSET_SIZE, size); ptr.putAddress(STRUCT_FIELD_OFFSET_DATA, address(buffer)); } @Override - protected ByteBuffer out(final ByteBuffer buffer, final Pointer ptr, - final long ptrAddr) { + protected ByteBuffer out(final ByteBuffer buffer, final Pointer ptr, final long ptrAddr) { final long addr = ptr.getAddress(STRUCT_FIELD_OFFSET_DATA); final long size = ptr.getLong(STRUCT_FIELD_OFFSET_SIZE); try { @@ -269,12 +246,11 @@ protected ByteBuffer out(final ByteBuffer buffer, final Pointer ptr, buffer.clear(); return buffer; } - } /** - * A proxy that uses Java's "unsafe" class to directly manipulate byte buffer - * fields and JNR-FFF allocated memory pointers. + * A proxy that uses Java's "unsafe" class to directly manipulate byte buffer fields and JNR-FFF + * allocated memory pointers. */ private static final class UnsafeProxy extends AbstractByteBufferProxy { @@ -293,22 +269,20 @@ private static final class UnsafeProxy extends AbstractByteBufferProxy { } @Override - protected void in(final ByteBuffer buffer, final Pointer ptr, - final long ptrAddr) { + protected void in(final ByteBuffer buffer, final Pointer ptr, final long ptrAddr) { UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, buffer.remaining()); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA, address(buffer)); } @Override - protected void in(final ByteBuffer buffer, final int size, final Pointer ptr, - final long ptrAddr) { + protected void in( + final ByteBuffer buffer, final int size, final Pointer ptr, final long ptrAddr) { UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, size); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA, address(buffer)); } @Override - protected ByteBuffer out(final ByteBuffer buffer, final Pointer ptr, - final long ptrAddr) { + protected ByteBuffer out(final ByteBuffer buffer, final Pointer ptr, final long ptrAddr) { final long addr = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA); final long size = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE); UNSAFE.putLong(buffer, ADDRESS_OFFSET, addr); @@ -317,5 +291,4 @@ protected ByteBuffer out(final ByteBuffer buffer, final Pointer ptr, return buffer; } } - } diff --git a/src/main/java/org/lmdbjava/CopyFlags.java b/src/main/java/org/lmdbjava/CopyFlags.java index 88649a04..4365563c 100644 --- a/src/main/java/org/lmdbjava/CopyFlags.java +++ b/src/main/java/org/lmdbjava/CopyFlags.java @@ -1,35 +1,24 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * #L% */ - package org.lmdbjava; -/** - * Flags for use when performing a - * {@link Env#copy(java.io.File, org.lmdbjava.CopyFlags...)}. - */ +/** Flags for use when performing a {@link Env#copy(java.io.File, org.lmdbjava.CopyFlags...)}. */ public enum CopyFlags implements MaskedFlag { - /** - * Compacting copy: Omit free space from copy, and renumber all pages - * sequentially. - */ + /** Compacting copy: Omit free space from copy, and renumber all pages sequentially. */ MDB_CP_COMPACT(0x01); private final int mask; @@ -42,5 +31,4 @@ public enum CopyFlags implements MaskedFlag { public int getMask() { return mask; } - } diff --git a/src/main/java/org/lmdbjava/Cursor.java b/src/main/java/org/lmdbjava/Cursor.java index cb9130e3..d49a9bed 100644 --- a/src/main/java/org/lmdbjava/Cursor.java +++ b/src/main/java/org/lmdbjava/Cursor.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * #L% */ - package org.lmdbjava; import static java.util.Objects.requireNonNull; @@ -40,8 +35,6 @@ import jnr.ffi.Pointer; import jnr.ffi.byref.NativeLongByReference; -import java.util.Arrays; - /** * A cursor handle. * @@ -67,9 +60,8 @@ public final class Cursor implements AutoCloseable { /** * Close a cursor handle. * - *

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

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

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

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

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

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

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

This function stores key/data pairs into the database. * * @param key key to store * @param val data to store - * @param op options for this operation - * @return true if the value was put, false if MDB_NOOVERWRITE or - * MDB_NODUPDATA were set and the key/value existed already. + * @param op options for this operation + * @return true if the value was put, false if MDB_NOOVERWRITE or MDB_NODUPDATA were set and the + * key/value existed already. */ public boolean put(final T key, final T val, final PutFlags... op) { if (SHOULD_CHECK) { @@ -259,8 +246,7 @@ public boolean put(final T key, final T val, final PutFlags... op) { kv.keyIn(key); kv.valIn(val); final int mask = mask(true, op); - final int rc = LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), - kv.pointerVal(), mask); + final int rc = LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), mask); if (rc == MDB_KEYEXIST) { if (isSet(mask, MDB_NOOVERWRITE)) { kv.valOut(); // marked as in,out in LMDB C docs @@ -276,23 +262,19 @@ public boolean put(final T key, final T val, final PutFlags... op) { } /** - * Put multiple values into the database in one MDB_MULTIPLE - * operation. + * Put multiple values into the database in one MDB_MULTIPLE operation. * - *

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

The database must have been opened with {@link DbiFlags#MDB_DUPFIXED}. The buffer must + * contain fixed-sized values to be inserted. The size of each element is calculated from the + * buffer's size divided by the given element count. For example, to populate 10 X 4 byte integers + * at once, present a buffer of 40 bytes and specify the element as 10. * - * @param key key to store in the database (not null) - * @param val value to store in the database (not null) + * @param key key to store in the database (not null) + * @param val value to store in the database (not null) * @param elements number of elements contained in the passed value buffer - * @param op options for operation (must set MDB_MULTIPLE) + * @param op options for operation (must set MDB_MULTIPLE) */ - public void putMultiple(final T key, final T val, final int elements, - final PutFlags... op) { + public void putMultiple(final T key, final T val, final int elements, final PutFlags... op) { if (SHOULD_CHECK) { requireNonNull(txn); requireNonNull(key); @@ -307,8 +289,7 @@ public void putMultiple(final T key, final T val, final int elements, } txn.kv().keyIn(key); final Pointer dataPtr = txn.kv().valInMulti(val, elements); - final int rc = LIB.mdb_cursor_put(ptrCursor, txn.kv().pointerKey(), - dataPtr, mask); + final int rc = LIB.mdb_cursor_put(ptrCursor, txn.kv().pointerKey(), dataPtr, mask); checkRc(rc); ReferenceUtil.reachabilityFence0(key); ReferenceUtil.reachabilityFence0(val); @@ -317,13 +298,10 @@ public void putMultiple(final T key, final T val, final int elements, /** * Renew a cursor handle. * - *

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

A cursor is associated with a specific transaction and database. Cursors that are only used + * in read-only transactions may be re-used, to avoid unnecessary malloc/free overhead. The cursor + * may be associated with a new read-only transaction, and referencing the same database handle as + * it was created with. This may be done whether the previous transaction is live or dead. * * @param newTxn transaction handle */ @@ -341,19 +319,17 @@ public void renew(final Txn newTxn) { } /** - * Reserve space for data of the given size, but don't copy the given val. - * Instead, return a pointer to the reserved space, which the caller can fill - * in later - before the next update operation or the transaction ends. This - * saves an extra memcpy if the data is being generated later. LMDB does - * nothing else with this memory, the caller is expected to modify all of the + * Reserve space for data of the given size, but don't copy the given val. Instead, return a + * pointer to the reserved space, which the caller can fill in later - before the next update + * operation or the transaction ends. This saves an extra memcpy if the data is being generated + * later. LMDB does nothing else with this memory, the caller is expected to modify all of the * space requested. * - *

- * This flag must not be specified if the database was opened with MDB_DUPSORT + *

This flag must not be specified if the database was opened with MDB_DUPSORT * - * @param key key to store in the database (not null) + * @param key key to store in the database (not null) * @param size size of the value to be stored in the database (not null) - * @param op options for this operation + * @param op options for this operation * @return a buffer that can be used to modify the value */ public T reserve(final T key, final int size, final PutFlags... op) { @@ -367,8 +343,7 @@ public T reserve(final T key, final int size, final PutFlags... op) { kv.keyIn(key); kv.valIn(size); final int flags = mask(true, op) | MDB_RESERVE.getMask(); - checkRc(LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), - flags)); + checkRc(LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), flags)); kv.valOut(); ReferenceUtil.reachabilityFence0(key); return val(); @@ -388,8 +363,7 @@ public boolean seek(final SeekOp op) { txn.checkReady(); } - final int rc = LIB.mdb_cursor_get(ptrCursor, kv.pointerKey(), kv - .pointerVal(), op.getCode()); + final int rc = LIB.mdb_cursor_get(ptrCursor, kv.pointerKey(), kv.pointerVal(), op.getCode()); if (rc == MDB_NOTFOUND) { return false; @@ -416,24 +390,18 @@ private void checkNotClosed() { } } - /** - * Cursor has already been closed. - */ + /** Cursor has already been closed. */ public static final class ClosedException extends LmdbException { private static final long serialVersionUID = 1L; - /** - * Creates a new instance. - */ + /** Creates a new instance. */ public ClosedException() { super("Cursor has already been closed"); } } - /** - * Cursor stack too deep - internal error. - */ + /** Cursor stack too deep - internal error. */ public static final class FullException extends LmdbNativeException { static final int MDB_CURSOR_FULL = -30_787; @@ -443,5 +411,4 @@ public static final class FullException extends LmdbNativeException { super(MDB_CURSOR_FULL, "Cursor stack too deep - internal error"); } } - } diff --git a/src/main/java/org/lmdbjava/CursorIterable.java b/src/main/java/org/lmdbjava/CursorIterable.java index 39a6d58c..6a03bd90 100644 --- a/src/main/java/org/lmdbjava/CursorIterable.java +++ b/src/main/java/org/lmdbjava/CursorIterable.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * #L% */ - package org.lmdbjava; import static org.lmdbjava.CursorIterable.State.RELEASED; @@ -30,21 +25,18 @@ import java.util.Comparator; import java.util.Iterator; import java.util.NoSuchElementException; - import org.lmdbjava.KeyRangeType.CursorOp; import org.lmdbjava.KeyRangeType.IteratorOp; /** - * {@link Iterable} that creates a single {@link Iterator} that will iterate - * over a {@link Cursor} as specified by a {@link KeyRange}. + * {@link Iterable} that creates a single {@link Iterator} that will iterate over a {@link Cursor} + * as specified by a {@link KeyRange}. * - *

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

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

- * As iteration of the returned iterator will cause movement of the underlying - * LMDB cursor, an {@link IllegalStateException} is thrown if an attempt is - * made to obtain the iterator more than once. For advanced cursor control - * (such as being able to iterate over the same data multiple times etc) - * please instead refer to {@link Dbi#openCursor(org.lmdbjava.Txn)}. + *

As iteration of the returned iterator will cause movement of the underlying LMDB cursor, an + * {@link IllegalStateException} is thrown if an attempt is made to obtain the iterator more than + * once. For advanced cursor control (such as being able to iterate over the same data multiple + * times etc) please instead refer to {@link Dbi#openCursor(org.lmdbjava.Txn)}. * * @return an iterator */ @Override - @SuppressWarnings("checkstyle:AnonInnerLength") public Iterator> iterator() { if (iteratorReturned) { throw new IllegalStateException("Iterator can only be returned once"); @@ -111,7 +100,6 @@ public void remove() { }; } - @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NullAssignment"}) private void executeCursorOp(final CursorOp op) { final boolean found; switch (op) { @@ -141,9 +129,8 @@ private void executeCursorOp(final CursorOp op) { } private void executeIteratorOp() { - final IteratorOp op = range.getType().iteratorOp(range.getStart(), - range.getStop(), - entry.key(), comparator); + final IteratorOp op = + range.getType().iteratorOp(range.getStart(), range.getStop(), entry.key(), comparator); switch (op) { case CALL_NEXT_OP: executeCursorOp(range.getType().nextOp()); @@ -183,10 +170,9 @@ private void update() { /** * Holder for a key and value pair. * - *

- * The same holder instance will always be returned for a given iterator. - * The returned keys and values may change or point to different memory - * locations following changes in the iterator, cursor or transaction. + *

The same holder instance will always be returned for a given iterator. The returned keys and + * values may change or point to different memory locations following changes in the iterator, + * cursor or transaction. * * @param buffer type */ @@ -195,6 +181,9 @@ public static final class KeyVal { private T k; private T v; + /** Explicitly-defined default constructor to avoid warnings. */ + public KeyVal() {} + /** * The key. * @@ -220,15 +209,14 @@ void setK(final T key) { void setV(final T val) { this.v = val; } - } - /** - * Represents the internal {@link CursorIterable} state. - */ + /** Represents the internal {@link CursorIterable} state. */ enum State { - REQUIRES_INITIAL_OP, REQUIRES_NEXT_OP, REQUIRES_ITERATOR_OP, RELEASED, + REQUIRES_INITIAL_OP, + REQUIRES_NEXT_OP, + REQUIRES_ITERATOR_OP, + RELEASED, TERMINATED } - } diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index c5be4f85..ad1bb5a7 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -1,36 +1,20 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * #L% */ - package org.lmdbjava; -import jnr.ffi.Pointer; -import jnr.ffi.byref.IntByReference; -import jnr.ffi.byref.PointerByReference; -import org.lmdbjava.Library.ComparatorCallback; -import org.lmdbjava.Library.MDB_stat; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; - import static java.util.Objects.requireNonNull; import static jnr.ffi.Memory.allocateDirect; import static jnr.ffi.NativeType.ADDRESS; @@ -47,6 +31,16 @@ import static org.lmdbjava.PutFlags.MDB_RESERVE; import static org.lmdbjava.ResultCodeMapper.checkRc; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import jnr.ffi.Pointer; +import jnr.ffi.byref.IntByReference; +import jnr.ffi.byref.PointerByReference; +import org.lmdbjava.Library.ComparatorCallback; +import org.lmdbjava.Library.MDB_stat; + /** * LMDB Database. * @@ -61,16 +55,21 @@ public final class Dbi { private final byte[] name; private final Pointer ptr; - Dbi(final Env env, final Txn txn, final byte[] name, - final Comparator comparator, final boolean nativeCb, - final BufferProxy proxy, final DbiFlags... flags) { + Dbi( + final Env env, + final Txn txn, + final byte[] name, + final Comparator comparator, + final boolean nativeCb, + final BufferProxy proxy, + final DbiFlags... flags) { if (SHOULD_CHECK) { requireNonNull(txn); txn.checkReady(); } this.env = env; this.name = name == null ? null : Arrays.copyOf(name, name.length); - if(comparator == null) { + if (comparator == null) { this.comparator = proxy.getComparator(flags); } else { this.comparator = comparator; @@ -80,16 +79,17 @@ public final class Dbi { checkRc(LIB.mdb_dbi_open(txn.pointer(), name, flagsMask, dbiPtr)); ptr = dbiPtr.getPointer(0); if (nativeCb) { - this.ccb = (keyA, keyB) -> { - final T compKeyA = proxy.allocate(); - final T compKeyB = proxy.allocate(); - proxy.out(compKeyA, keyA, keyA.address()); - proxy.out(compKeyB, keyB, keyB.address()); - final int result = this.comparator.compare(compKeyA, compKeyB); - proxy.deallocate(compKeyA); - proxy.deallocate(compKeyB); - return result; - }; + this.ccb = + (keyA, keyB) -> { + final T compKeyA = proxy.allocate(); + final T compKeyB = proxy.allocate(); + proxy.out(compKeyA, keyA, keyA.address()); + proxy.out(compKeyB, keyB, keyB.address()); + final int result = this.comparator.compare(compKeyA, compKeyB); + proxy.deallocate(compKeyA); + proxy.deallocate(compKeyB); + return result; + }; LIB.mdb_set_compare(txn.pointer(), ptr, ccb); } else { ccb = null; @@ -99,13 +99,11 @@ public final class Dbi { /** * Close the database handle (normally unnecessary; use with caution). * - *

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

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

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

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

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

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

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

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

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

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

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

This function stores key/data pairs in the database. The default behavior is to enter the + * new key/data pair, replacing any previously existing key if duplicates are disallowed, or + * adding a duplicate data item if duplicates are allowed ({@link DbiFlags#MDB_DUPSORT}). * - * @param txn transaction handle (not null; not committed; must be R-W) - * @param key key to store in the database (not null) - * @param val value to store in the database (not null) + * @param txn transaction handle (not null; not committed; must be R-W) + * @param key key to store in the database (not null) + * @param val value to store in the database (not null) * @param flags Special options for this operation - * @return true if the value was put, false if MDB_NOOVERWRITE or - * MDB_NODUPDATA were set and the key/value existed already. + * @return true if the value was put, false if MDB_NOOVERWRITE or MDB_NODUPDATA were set and the + * key/value existed already. */ - public boolean put(final Txn txn, final T key, final T val, - final PutFlags... flags) { + public boolean put(final Txn txn, final T key, final T val, final PutFlags... flags) { if (SHOULD_CHECK) { requireNonNull(txn); requireNonNull(key); @@ -386,8 +369,8 @@ public boolean put(final Txn txn, final T key, final T val, txn.kv().keyIn(key); txn.kv().valIn(val); final int mask = mask(true, flags); - final int rc = LIB.mdb_put(txn.pointer(), ptr, txn.kv().pointerKey(), txn - .kv().pointerVal(), mask); + final int rc = + LIB.mdb_put(txn.pointer(), ptr, txn.kv().pointerKey(), txn.kv().pointerVal(), mask); if (rc == MDB_KEYEXIST) { if (isSet(mask, MDB_NOOVERWRITE)) { txn.kv().valOut(); // marked as in,out in LMDB C docs @@ -403,24 +386,21 @@ public boolean put(final Txn txn, final T key, final T val, } /** - * Reserve space for data of the given size, but don't copy the given val. - * Instead, return a pointer to the reserved space, which the caller can fill - * in later - before the next update operation or the transaction ends. This - * saves an extra memcpy if the data is being generated later. LMDB does - * nothing else with this memory, the caller is expected to modify all of the + * Reserve space for data of the given size, but don't copy the given val. Instead, return a + * pointer to the reserved space, which the caller can fill in later - before the next update + * operation or the transaction ends. This saves an extra memcpy if the data is being generated + * later. LMDB does nothing else with this memory, the caller is expected to modify all of the * space requested. * - *

- * This flag must not be specified if the database was opened with MDB_DUPSORT + *

This flag must not be specified if the database was opened with MDB_DUPSORT * - * @param txn transaction handle (not null; not committed; must be R-W) - * @param key key to store in the database (not null) + * @param txn transaction handle (not null; not committed; must be R-W) + * @param key key to store in the database (not null) * @param size size of the value to be stored in the database - * @param op options for this operation + * @param op options for this operation * @return a buffer that can be used to modify the value */ - public T reserve(final Txn txn, final T key, final int size, - final PutFlags... op) { + public T reserve(final Txn txn, final T key, final int size, final PutFlags... op) { if (SHOULD_CHECK) { requireNonNull(txn); requireNonNull(key); @@ -431,8 +411,7 @@ public T reserve(final Txn txn, final T key, final int size, txn.kv().keyIn(key); txn.kv().valIn(size); final int flags = mask(true, op) | MDB_RESERVE.getMask(); - checkRc(LIB.mdb_put(txn.pointer(), ptr, txn.kv().pointerKey(), txn.kv() - .pointerVal(), flags)); + checkRc(LIB.mdb_put(txn.pointer(), ptr, txn.kv().pointerKey(), txn.kv().pointerVal(), flags)); txn.kv().valOut(); // marked as in,out in LMDB C docs ReferenceUtil.reachabilityFence0(key); return txn.val(); @@ -468,9 +447,7 @@ private void clean() { cleaned = true; } - /** - * The specified DBI was changed unexpectedly. - */ + /** The specified DBI was changed unexpectedly. */ public static final class BadDbiException extends LmdbNativeException { static final int MDB_BAD_DBI = -30_780; @@ -481,23 +458,18 @@ public static final class BadDbiException extends LmdbNativeException { } } - /** - * Unsupported size of key/DB name/data, or wrong DUPFIXED size. - */ + /** Unsupported size of key/DB name/data, or wrong DUPFIXED size. */ public static final class BadValueSizeException extends LmdbNativeException { static final int MDB_BAD_VALSIZE = -30_781; private static final long serialVersionUID = 1L; BadValueSizeException() { - super(MDB_BAD_VALSIZE, - "Unsupported size of key/DB name/data, or wrong DUPFIXED size"); + super(MDB_BAD_VALSIZE, "Unsupported size of key/DB name/data, or wrong DUPFIXED size"); } } - /** - * Environment maxdbs reached. - */ + /** Environment maxdbs reached. */ public static final class DbFullException extends LmdbNativeException { static final int MDB_DBS_FULL = -30_791; @@ -511,14 +483,13 @@ public static final class DbFullException extends LmdbNativeException { /** * Operation and DB incompatible, or DB type changed. * - *

- * This can mean: + *

This can mean: + * *

    - *
  • The operation expects an MDB_DUPSORT / MDB_DUPFIXED database.
  • - *
  • Opening a named DB when the unnamed DB has MDB_DUPSORT / - * MDB_INTEGERKEY.
  • - *
  • Accessing a data record as a database, or vice versa.
  • - *
  • The database was dropped and recreated with different flags.
  • + *
  • The operation expects an MDB_DUPSORT / MDB_DUPFIXED database. + *
  • Opening a named DB when the unnamed DB has MDB_DUPSORT / MDB_INTEGERKEY. + *
  • Accessing a data record as a database, or vice versa. + *
  • The database was dropped and recreated with different flags. *
*/ public static final class IncompatibleException extends LmdbNativeException { @@ -527,14 +498,11 @@ public static final class IncompatibleException extends LmdbNativeException { private static final long serialVersionUID = 1L; IncompatibleException() { - super(MDB_INCOMPATIBLE, - "Operation and DB incompatible, or DB type changed"); + super(MDB_INCOMPATIBLE, "Operation and DB incompatible, or DB type changed"); } } - /** - * Key/data pair already exists. - */ + /** Key/data pair already exists. */ public static final class KeyExistsException extends LmdbNativeException { static final int MDB_KEYEXIST = -30_799; @@ -545,9 +513,7 @@ public static final class KeyExistsException extends LmdbNativeException { } } - /** - * Key/data pair not found (EOF). - */ + /** Key/data pair not found (EOF). */ public static final class KeyNotFoundException extends LmdbNativeException { static final int MDB_NOTFOUND = -30_798; @@ -558,9 +524,7 @@ public static final class KeyNotFoundException extends LmdbNativeException { } } - /** - * Database contents grew beyond environment mapsize. - */ + /** Database contents grew beyond environment mapsize. */ public static final class MapResizedException extends LmdbNativeException { static final int MDB_MAP_RESIZED = -30_785; diff --git a/src/main/java/org/lmdbjava/DbiFlags.java b/src/main/java/org/lmdbjava/DbiFlags.java index 7277b55b..123ec9fd 100644 --- a/src/main/java/org/lmdbjava/DbiFlags.java +++ b/src/main/java/org/lmdbjava/DbiFlags.java @@ -1,96 +1,79 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * #L% */ - package org.lmdbjava; -/** - * Flags for use when opening a {@link Dbi}. - */ +/** Flags for use when opening a {@link Dbi}. */ public enum DbiFlags implements MaskedFlag { /** * Use reverse string keys. * - *

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

Keys are strings to be compared in reverse order, from the end of the strings to the + * beginning. By default, keys are treated as strings and compared from beginning to end. */ MDB_REVERSEKEY(0x02), /** * Use sorted duplicates. * - *

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

Duplicate keys may be used in the database. Or, from another perspective, keys may have + * multiple data items, stored in sorted order. By default keys must be unique and may have only a + * single data item. */ MDB_DUPSORT(0x04), /** - * Numeric keys in native byte order: either unsigned int or size_t. The keys - * must all be of the same size. + * Numeric keys in native byte order: either unsigned int or size_t. The keys must all be of the + * same size. */ MDB_INTEGERKEY(0x08), /** * With {@link #MDB_DUPSORT}, sorted dup items have fixed size. * - *

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

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

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

This option specifies that duplicate data items are binary integers, similar to {@link + * #MDB_INTEGERKEY} keys. */ MDB_INTEGERDUP(0x20), /** * Compare the numeric keys in native byte order and as unsigned. * - *

- * This option is applied only to {@link java.nio.ByteBuffer}, {@link org.agrona.DirectBuffer} and byte array keys. - * {@link io.netty.buffer.ByteBuf} keys are always compared in native byte order and as unsigned. - *

+ *

This option is applied only to {@link java.nio.ByteBuffer}, {@link org.agrona.DirectBuffer} + * and byte array keys. {@link io.netty.buffer.ByteBuf} keys are always compared in native byte + * order and as unsigned. */ MDB_UNSIGNEDKEY(0x30, false), /** * With {@link #MDB_DUPSORT}, use reverse string dups. * - *

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

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

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

This option is not allowed in a read-only transaction or a read-only environment. */ MDB_CREATE(0x4_0000); diff --git a/src/main/java/org/lmdbjava/DirectBufferProxy.java b/src/main/java/org/lmdbjava/DirectBufferProxy.java index 3bde81ec..156e60e9 100644 --- a/src/main/java/org/lmdbjava/DirectBufferProxy.java +++ b/src/main/java/org/lmdbjava/DirectBufferProxy.java @@ -1,73 +1,64 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * #L% */ - package org.lmdbjava; -import jnr.ffi.Pointer; -import org.agrona.DirectBuffer; -import org.agrona.MutableDirectBuffer; -import org.agrona.concurrent.UnsafeBuffer; - -import java.nio.ByteBuffer; -import java.util.ArrayDeque; -import java.util.Comparator; - import static java.lang.ThreadLocal.withInitial; import static java.nio.ByteBuffer.allocateDirect; import static java.nio.ByteOrder.BIG_ENDIAN; import static java.util.Objects.requireNonNull; import static org.lmdbjava.UnsafeAccess.UNSAFE; +import java.nio.ByteBuffer; +import java.util.ArrayDeque; +import java.util.Comparator; +import jnr.ffi.Pointer; +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; + /** * A buffer proxy backed by Agrona's {@link DirectBuffer}. * - *

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

This class requires {@link UnsafeAccess} and Agrona must be in the classpath. */ public final class DirectBufferProxy extends BufferProxy { - private static final Comparator signedComparator = (o1, o2) -> { - requireNonNull(o1); - requireNonNull(o2); + private static final Comparator signedComparator = + (o1, o2) -> { + requireNonNull(o1); + requireNonNull(o2); - return o1.compareTo(o2); - }; + return o1.compareTo(o2); + }; private static final Comparator unsignedComparator = DirectBufferProxy::compareBuff; /** - * The {@link MutableDirectBuffer} proxy. Guaranteed to never be null, - * although a class initialization exception will occur if an attempt is made - * to access this field when unsafe or Agrona is unavailable. + * The {@link MutableDirectBuffer} proxy. Guaranteed to never be null, although a class + * initialization exception will occur if an attempt is made to access this field when unsafe or + * Agrona is unavailable. */ - public static final BufferProxy PROXY_DB - = new DirectBufferProxy(); + public static final BufferProxy PROXY_DB = new DirectBufferProxy(); /** - * A thread-safe pool for a given length. If the buffer found is valid (ie not - * of a negative length) then that buffer is used. If no valid buffer is - * found, a new buffer is created. + * A thread-safe pool for a given length. If the buffer found is valid (ie not of a negative + * length) then that buffer is used. If no valid buffer is found, a new buffer is created. */ - private static final ThreadLocal> BUFFERS - = withInitial(() -> new ArrayDeque<>(16)); + private static final ThreadLocal> BUFFERS = + withInitial(() -> new ArrayDeque<>(16)); - private DirectBufferProxy() { - } + private DirectBufferProxy() {} /** * Lexicographically compare two buffers. @@ -143,8 +134,7 @@ protected byte[] getBytes(final DirectBuffer buffer) { } @Override - protected void in(final DirectBuffer buffer, final Pointer ptr, - final long ptrAddr) { + protected void in(final DirectBuffer buffer, final Pointer ptr, final long ptrAddr) { final long addr = buffer.addressOffset(); final long size = buffer.capacity(); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA, addr); @@ -152,20 +142,18 @@ protected void in(final DirectBuffer buffer, final Pointer ptr, } @Override - protected void in(final DirectBuffer buffer, final int size, final Pointer ptr, - final long ptrAddr) { + protected void in( + final DirectBuffer buffer, final int size, final Pointer ptr, final long ptrAddr) { final long addr = buffer.addressOffset(); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA, addr); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, size); } @Override - protected DirectBuffer out(final DirectBuffer buffer, final Pointer ptr, - final long ptrAddr) { + protected DirectBuffer out(final DirectBuffer buffer, final Pointer ptr, final long ptrAddr) { final long addr = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA); final long size = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE); buffer.wrap(addr, (int) size); return buffer; } - } diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index ae7e46c9..3db16119 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -1,38 +1,20 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * #L% */ - package org.lmdbjava; -import jnr.ffi.Pointer; -import jnr.ffi.byref.IntByReference; -import jnr.ffi.byref.PointerByReference; -import org.lmdbjava.Library.MDB_envinfo; -import org.lmdbjava.Library.MDB_stat; - -import java.io.File; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - import static java.lang.Boolean.getBoolean; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; @@ -46,24 +28,32 @@ import static org.lmdbjava.ResultCodeMapper.checkRc; import static org.lmdbjava.TxnFlags.MDB_RDONLY_TXN; +import java.io.File; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import jnr.ffi.Pointer; +import jnr.ffi.byref.IntByReference; +import jnr.ffi.byref.PointerByReference; +import org.lmdbjava.Library.MDB_envinfo; +import org.lmdbjava.Library.MDB_stat; + /** * LMDB environment. * * @param buffer type */ -@SuppressWarnings("PMD.GodClass") public final class Env implements AutoCloseable { - /** - * Java system property name that can be set to disable optional checks. - */ + /** Java system property name that can be set to disable optional checks. */ public static final String DISABLE_CHECKS_PROP = "lmdbjava.disable.checks"; /** - * Indicates whether optional checks should be applied in LmdbJava. Optional - * checks are only disabled in critical paths (see package-level JavaDocs). - * Non-critical paths have optional checks performed at all times, regardless - * of this property. + * Indicates whether optional checks should be applied in LmdbJava. Optional checks are only + * disabled in critical paths (see package-level JavaDocs). Non-critical paths have optional + * checks performed at all times, regardless of this property. */ public static final boolean SHOULD_CHECK = !getBoolean(DISABLE_CHECKS_PROP); @@ -74,8 +64,11 @@ public final class Env implements AutoCloseable { private final Pointer ptr; private final boolean readOnly; - private Env(final BufferProxy proxy, final Pointer ptr, - final boolean readOnly, final boolean noSubDir) { + private Env( + final BufferProxy proxy, + final Pointer ptr, + final boolean readOnly, + final boolean noSubDir) { this.proxy = proxy; this.readOnly = readOnly; this.noSubDir = noSubDir; @@ -96,7 +89,7 @@ public static Builder create() { /** * Create an {@link Env} using the passed {@link BufferProxy}. * - * @param buffer type + * @param buffer type * @param proxy the proxy to use (required) * @return the environment (never null) */ @@ -105,26 +98,22 @@ public static Builder create(final BufferProxy proxy) { } /** - * Opens an environment with a single default database in 0664 mode using the - * {@link ByteBufferProxy#PROXY_OPTIMAL}. + * Opens an environment with a single default database in 0664 mode using the {@link + * ByteBufferProxy#PROXY_OPTIMAL}. * - * @param path file system destination - * @param size size in megabytes + * @param path file system destination + * @param size size in megabytes * @param flags the flags for this new environment * @return env the environment (never null) */ - public static Env open(final File path, final int size, - final EnvFlags... flags) { - return new Builder<>(PROXY_OPTIMAL) - .setMapSize(size * 1_024L * 1_024L) - .open(path, flags); + public static Env open(final File path, final int size, final EnvFlags... flags) { + return new Builder<>(PROXY_OPTIMAL).setMapSize(size * 1_024L * 1_024L).open(path, flags); } /** * Close the handle. * - *

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

Will silently return if already closed or never opened. */ @Override public void close() { @@ -138,22 +127,19 @@ public void close() { /** * Copies an LMDB environment to the specified destination path. * - *

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

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

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

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

- * Note: This call can trigger significant file size growth if run in parallel - * with write transactions, because it employs a read-only transaction. See - * long-lived transactions under "Caveats" in the LMDB native documentation. + *

Note: This call can trigger significant file size growth if run in parallel with write + * transactions, because it employs a read-only transaction. See long-lived transactions under + * "Caveats" in the LMDB native documentation. * - * @param path writable destination path as described above + * @param path writable destination path as described above * @param flags special options for this copy */ public void copy(final File path, final CopyFlags... flags) { @@ -166,13 +152,11 @@ public void copy(final File path, final CopyFlags... flags) { /** * Obtain the DBI names. * - *

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

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

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

This method must not be called from concurrent threads. * * @return a list of DBI names (never null) */ @@ -180,7 +164,7 @@ public List getDbiNames() { final List result = new ArrayList<>(); final Dbi names = openDbi((byte[]) null); try (Txn txn = txnRead(); - Cursor cursor = names.openCursor(txn)) { + Cursor cursor = names.openCursor(txn)) { if (!cursor.first()) { return Collections.emptyList(); } @@ -249,8 +233,7 @@ public boolean isClosed() { } /** - * Indicates if this environment was opened with - * {@link EnvFlags#MDB_RDONLY_ENV}. + * Indicates if this environment was opened with {@link EnvFlags#MDB_RDONLY_ENV}. * * @return true if read-only */ @@ -259,10 +242,10 @@ public boolean isReadOnly() { } /** - * Convenience method that opens a {@link Dbi} with a UTF-8 database name and - * default {@link Comparator} that is not invoked from native code. + * Convenience method that opens a {@link Dbi} with a UTF-8 database name and default {@link + * Comparator} that is not invoked from native code. * - * @param name name of the database (or null if no name is required) + * @param name name of the database (or null if no name is required) * @param flags to open the database with * @return a database that is ready to use */ @@ -272,42 +255,44 @@ public Dbi openDbi(final String name, final DbiFlags... flags) { } /** - * Convenience method that opens a {@link Dbi} with a UTF-8 database name and - * associated {@link Comparator} that is not invoked from native code. + * Convenience method that opens a {@link Dbi} with a UTF-8 database name and associated {@link + * Comparator} that is not invoked from native code. * - * @param name name of the database (or null if no name is required) + * @param name name of the database (or null if no name is required) * @param comparator custom comparator callback (or null to use default) - * @param flags to open the database with + * @param flags to open the database with * @return a database that is ready to use */ - public Dbi openDbi(final String name, final Comparator comparator, - final DbiFlags... flags) { + public Dbi openDbi( + final String name, final Comparator comparator, final DbiFlags... flags) { final byte[] nameBytes = name == null ? null : name.getBytes(UTF_8); return openDbi(nameBytes, comparator, false, flags); } /** - * Convenience method that opens a {@link Dbi} with a UTF-8 database name and - * associated {@link Comparator} that may be invoked from native code if - * specified. + * Convenience method that opens a {@link Dbi} with a UTF-8 database name and associated {@link + * Comparator} that may be invoked from native code if specified. * - * @param name name of the database (or null if no name is required) + * @param name name of the database (or null if no name is required) * @param comparator custom comparator callback (or null to use default) - * @param nativeCb whether native code calls back to the Java comparator - * @param flags to open the database with + * @param nativeCb whether native code calls back to the Java comparator + * @param flags to open the database with * @return a database that is ready to use */ - public Dbi openDbi(final String name, final Comparator comparator, - final boolean nativeCb, final DbiFlags... flags) { + public Dbi openDbi( + final String name, + final Comparator comparator, + final boolean nativeCb, + final DbiFlags... flags) { final byte[] nameBytes = name == null ? null : name.getBytes(UTF_8); return openDbi(nameBytes, comparator, nativeCb, flags); } /** - * Convenience method that opens a {@link Dbi} with a default - * {@link Comparator} that is not invoked from native code. + * Convenience method that opens a {@link Dbi} with a default {@link Comparator} that is not + * invoked from native code. * - * @param name name of the database (or null if no name is required) + * @param name name of the database (or null if no name is required) * @param flags to open the database with * @return a database that is ready to use */ @@ -316,36 +301,37 @@ public Dbi openDbi(final byte[] name, final DbiFlags... flags) { } /** - * Convenience method that opens a {@link Dbi} with an associated - * {@link Comparator} that is not invoked from native code. + * Convenience method that opens a {@link Dbi} with an associated {@link Comparator} that is not + * invoked from native code. * - * @param name name of the database (or null if no name is required) + * @param name name of the database (or null if no name is required) * @param comparator custom comparator callback (or null to use LMDB default) - * @param flags to open the database with + * @param flags to open the database with * @return a database that is ready to use */ - public Dbi openDbi(final byte[] name, final Comparator comparator, - final DbiFlags... flags) { + public Dbi openDbi( + final byte[] name, final Comparator comparator, final DbiFlags... flags) { return openDbi(name, comparator, false, flags); } /** - * Convenience method that opens a {@link Dbi} with an associated - * {@link Comparator} that may be invoked from native code if specified. + * Convenience method that opens a {@link Dbi} with an associated {@link Comparator} that may be + * invoked from native code if specified. * - *

- * This method will automatically commit the private transaction before - * returning. This ensures the Dbi is available in the - * Env. + *

This method will automatically commit the private transaction before returning. This ensures + * the Dbi is available in the Env. * - * @param name name of the database (or null if no name is required) + * @param name name of the database (or null if no name is required) * @param comparator custom comparator callback (or null to use LMDB default) - * @param nativeCb whether native code calls back to the Java comparator - * @param flags to open the database with + * @param nativeCb whether native code calls back to the Java comparator + * @param flags to open the database with * @return a database that is ready to use */ - public Dbi openDbi(final byte[] name, final Comparator comparator, - final boolean nativeCb, final DbiFlags... flags) { + public Dbi openDbi( + final byte[] name, + final Comparator comparator, + final boolean nativeCb, + final DbiFlags... flags) { try (Txn txn = readOnly ? txnRead() : txnWrite()) { final Dbi dbi = openDbi(txn, name, comparator, nativeCb, flags); txn.commit(); // even RO Txns require a commit to retain Dbi in Env @@ -356,38 +342,36 @@ public Dbi openDbi(final byte[] name, final Comparator comparator, /** * Open the {@link Dbi} using the passed {@link Txn}. * - *

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

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

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

- * This method (and its overloaded convenience variants) must not be called - * from concurrent threads. - * - * @param txn transaction to use (required; not closed) - * @param name name of the database (or null if no name is required) + *

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

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

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

This method (and its overloaded convenience variants) must not be called from concurrent + * threads. + * + * @param txn transaction to use (required; not closed) + * @param name name of the database (or null if no name is required) * @param comparator custom comparator callback (or null to use LMDB default) - * @param nativeCb whether native code should call back to the comparator - * @param flags to open the database with + * @param nativeCb whether native code should call back to the comparator + * @param flags to open the database with * @return a database that is ready to use */ - public Dbi openDbi(final Txn txn, final byte[] name, - final Comparator comparator, final boolean nativeCb, - final DbiFlags... flags) { + public Dbi openDbi( + final Txn txn, + final byte[] name, + final Comparator comparator, + final boolean nativeCb, + final DbiFlags... flags) { return new Dbi<>(this, txn, name, comparator, nativeCb, proxy, flags); } @@ -414,9 +398,8 @@ public Stat stat() { /** * Flushes the data buffers to disk. * - * @param force force a synchronous flush (otherwise if the environment has - * the MDB_NOSYNC flag set the flushes will be omitted, and with - * MDB_MAPASYNC they will be asynchronous) + * @param force force a synchronous flush (otherwise if the environment has the MDB_NOSYNC flag + * set the flushes will be omitted, and with MDB_MAPASYNC they will be asynchronous) */ public void sync(final boolean force) { if (closed) { @@ -430,7 +413,7 @@ public void sync(final boolean force) { * Obtain a transaction with the requested parent and flags. * * @param parent parent transaction (may be null if no parent) - * @param flags applicable flags (eg for a reusable, read-only transaction) + * @param flags applicable flags (eg for a reusable, read-only transaction) * @return a transaction (never null) */ public Txn txn(final Txn parent, final TxnFlags... flags) { @@ -491,46 +474,39 @@ private void validatePath(final File path) { validateDirectoryEmpty(path); } - - /* Check for stale entries in the reader lock table. */ + /** + * Check for stale entries in the reader lock table. + * + * @return 0 on success, non-zero on failure + */ public int readerCheck() { final IntByReference resultPtr = new IntByReference(); checkRc(LIB.mdb_reader_check(ptr, resultPtr)); return resultPtr.intValue(); } - - - /** - * Object has already been closed and the operation is therefore prohibited. - */ + + /** Object has already been closed and the operation is therefore prohibited. */ public static final class AlreadyClosedException extends LmdbException { private static final long serialVersionUID = 1L; - /** - * Creates a new instance. - */ + /** Creates a new instance. */ public AlreadyClosedException() { super("Environment has already been closed"); } } - /** - * Object has already been opened and the operation is therefore prohibited. - */ + /** Object has already been opened and the operation is therefore prohibited. */ public static final class AlreadyOpenException extends LmdbException { private static final long serialVersionUID = 1L; - /** - * Creates a new instance. - */ + /** Creates a new instance. */ public AlreadyOpenException() { super("Environment has already been opened"); } } - /** * Builder for configuring and opening Env. * @@ -553,14 +529,12 @@ public static final class Builder { /** * Opens the environment. * - * @param path file system destination - * @param mode Unix permissions to set on created files and semaphores + * @param path file system destination + * @param mode Unix permissions to set on created files and semaphores * @param flags the flags for this new environment * @return an environment ready for use */ - @SuppressWarnings("PMD.AccessorClassGeneration") - public Env open(final File path, final int mode, - final EnvFlags... flags) { + public Env open(final File path, final int mode, final EnvFlags... flags) { requireNonNull(path); if (opened) { throw new AlreadyOpenException(); @@ -587,16 +561,14 @@ public Env open(final File path, final int mode, /** * Opens the environment with 0664 mode. * - * @param path file system destination + * @param path file system destination * @param flags the flags for this new environment * @return an environment ready for use */ - @SuppressWarnings("PMD.AvoidUsingOctalValues") public Env open(final File path, final EnvFlags... flags) { return open(path, 0664, flags); } - /** * Sets the map size. * @@ -643,9 +615,7 @@ public Builder setMaxReaders(final int readers) { } } - /** - * File is not a valid LMDB file. - */ + /** File is not a valid LMDB file. */ public static final class FileInvalidException extends LmdbNativeException { static final int MDB_INVALID = -30_793; @@ -656,9 +626,7 @@ public static final class FileInvalidException extends LmdbNativeException { } } - /** - * The specified copy destination is invalid. - */ + /** The specified copy destination is invalid. */ public static final class InvalidCopyDestination extends LmdbException { private static final long serialVersionUID = 1L; @@ -673,9 +641,7 @@ public InvalidCopyDestination(final String message) { } } - /** - * Environment mapsize reached. - */ + /** Environment mapsize reached. */ public static final class MapFullException extends LmdbNativeException { static final int MDB_MAP_FULL = -30_792; @@ -686,9 +652,7 @@ public static final class MapFullException extends LmdbNativeException { } } - /** - * Environment maxreaders reached. - */ + /** Environment maxreaders reached. */ public static final class ReadersFullException extends LmdbNativeException { static final int MDB_READERS_FULL = -30_790; @@ -699,9 +663,7 @@ public static final class ReadersFullException extends LmdbNativeException { } } - /** - * Environment version mismatch. - */ + /** Environment version mismatch. */ public static final class VersionMismatchException extends LmdbNativeException { static final int MDB_VERSION_MISMATCH = -30_794; @@ -711,5 +673,4 @@ public static final class VersionMismatchException extends LmdbNativeException { super(MDB_VERSION_MISMATCH, "Environment version mismatch"); } } - } diff --git a/src/main/java/org/lmdbjava/EnvFlags.java b/src/main/java/org/lmdbjava/EnvFlags.java index ab54917a..4ce555a8 100644 --- a/src/main/java/org/lmdbjava/EnvFlags.java +++ b/src/main/java/org/lmdbjava/EnvFlags.java @@ -1,169 +1,136 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * #L% */ - package org.lmdbjava; -/** - * Flags for use when opening the {@link Env}. - */ +/** Flags for use when opening the {@link Env}. */ public enum EnvFlags implements MaskedFlag { /** * Mmap at a fixed address (experimental). * - *

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Don't initialize malloc'd memory before writing to unused spaces in the data file. By + * default, memory for pages written to the data file is obtained using malloc. While these pages + * may be reused in subsequent transactions, freshly malloc'd pages will be initialized to zeroes + * before use. This avoids persisting leftover data from other code (that used the heap and + * subsequently freed the memory) into the data file. Note that many other system libraries may + * allocate and free memory from the heap for arbitrary uses. E.g., stdio may use the heap for + * file I/O buffers. This initialization step has a modest performance cost so some applications + * may want to disable it using this flag. This option can be a problem for applications which + * handle sensitive data like passwords, and it makes memory checkers like Valgrind noisy. This + * flag is not needed with {@link #MDB_WRITEMAP}, which writes directly to the mmap instead of + * using malloc for pages. The initialization is also skipped if {@link PutFlags#MDB_RESERVE} is + * used; the caller is expected to overwrite all of the memory that was reserved in that case. */ MDB_NOMEMINIT(0x100_0000); @@ -177,5 +144,4 @@ public enum EnvFlags implements MaskedFlag { public int getMask() { return mask; } - } diff --git a/src/main/java/org/lmdbjava/EnvInfo.java b/src/main/java/org/lmdbjava/EnvInfo.java index a1ae62ba..71169d90 100644 --- a/src/main/java/org/lmdbjava/EnvInfo.java +++ b/src/main/java/org/lmdbjava/EnvInfo.java @@ -1,63 +1,48 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * #L% */ - package org.lmdbjava; -/** - * Environment information, as returned by {@link Env#info()}. - */ +/** Environment information, as returned by {@link Env#info()}. */ public final class EnvInfo { - /** - * ID of the last used page. - */ + /** ID of the last used page. */ public final long lastPageNumber; - /** - * ID of the last committed transaction. - */ + /** ID of the last committed transaction. */ public final long lastTransactionId; - /** - * Address of map, if fixed. - */ + /** Address of map, if fixed. */ public final long mapAddress; - /** - * Size of the data memory map. - */ + /** Size of the data memory map. */ public final long mapSize; - /** - * Max reader slots in the environment. - */ + /** Max reader slots in the environment. */ public final int maxReaders; - /** - * Max reader slots used in the environment. - */ + /** Max reader slots used in the environment. */ public final int numReaders; - EnvInfo(final long mapAddress, final long mapSize, final long lastPageNumber, - final long lastTransactionId, final int maxReaders, - final int numReaders) { + EnvInfo( + final long mapAddress, + final long mapSize, + final long lastPageNumber, + final long lastTransactionId, + final int maxReaders, + final int numReaders) { this.mapAddress = mapAddress; this.mapSize = mapSize; this.lastPageNumber = lastPageNumber; @@ -68,10 +53,19 @@ public final class EnvInfo { @Override public String toString() { - return "EnvInfo{" + "lastPageNumber=" + lastPageNumber - + ", lastTransactionId=" + lastTransactionId + ", mapAddress=" - + mapAddress + ", mapSize=" + mapSize + ", maxReaders=" - + maxReaders + ", numReaders=" + numReaders + '}'; + return "EnvInfo{" + + "lastPageNumber=" + + lastPageNumber + + ", lastTransactionId=" + + lastTransactionId + + ", mapAddress=" + + mapAddress + + ", mapSize=" + + mapSize + + ", maxReaders=" + + maxReaders + + ", numReaders=" + + numReaders + + '}'; } - } diff --git a/src/main/java/org/lmdbjava/GetOp.java b/src/main/java/org/lmdbjava/GetOp.java index 9d36c204..666f692c 100644 --- a/src/main/java/org/lmdbjava/GetOp.java +++ b/src/main/java/org/lmdbjava/GetOp.java @@ -1,45 +1,32 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * #L% */ - package org.lmdbjava; /** - * Flags for use when performing a - * {@link Cursor#get(java.lang.Object, org.lmdbjava.GetOp)}. + * Flags for use when performing a {@link Cursor#get(java.lang.Object, org.lmdbjava.GetOp)}. * - *

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

Unlike most other LMDB enums, this enum is not bit masked. */ public enum GetOp { - /** - * Position at specified key. - */ + /** Position at specified key. */ MDB_SET(15), - /** - * Position at specified key, return key + data. - */ + /** Position at specified key, return key + data. */ MDB_SET_KEY(16), - /** - * Position at first key greater than or equal to specified key. - */ + /** Position at first key greater than or equal to specified key. */ MDB_SET_RANGE(17); private final int code; @@ -56,5 +43,4 @@ public enum GetOp { public int getCode() { return code; } - } diff --git a/src/main/java/org/lmdbjava/KeyRange.java b/src/main/java/org/lmdbjava/KeyRange.java index d47c444c..553346e6 100644 --- a/src/main/java/org/lmdbjava/KeyRange.java +++ b/src/main/java/org/lmdbjava/KeyRange.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * #L% */ - package org.lmdbjava; import static java.util.Objects.requireNonNull; @@ -27,8 +22,7 @@ /** * Limits the range and direction of keys to iterate. * - *

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

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

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

End user code may find it more expressive to use one of the static methods provided on this + * class. * - * @param type key type + * @param type key type * @param start start key (required if applicable for the passed range type) - * @param stop stop key (required if applicable for the passed range type) + * @param stop stop key (required if applicable for the passed range type) */ public KeyRange(final KeyRangeType type, final T start, final T stop) { requireNonNull(type, "Key range type is required"); @@ -87,7 +80,7 @@ public static KeyRange allBackward() { /** * Create a {@link KeyRangeType#FORWARD_AT_LEAST} range. * - * @param buffer type + * @param buffer type * @param start start key (required) * @return a key range (never null) */ @@ -98,7 +91,7 @@ public static KeyRange atLeast(final T start) { /** * Create a {@link KeyRangeType#BACKWARD_AT_LEAST} range. * - * @param buffer type + * @param buffer type * @param start start key (required) * @return a key range (never null) */ @@ -109,7 +102,7 @@ public static KeyRange atLeastBackward(final T start) { /** * Create a {@link KeyRangeType#FORWARD_AT_MOST} range. * - * @param buffer type + * @param buffer type * @param stop stop key (required) * @return a key range (never null) */ @@ -120,7 +113,7 @@ public static KeyRange atMost(final T stop) { /** * Create a {@link KeyRangeType#BACKWARD_AT_MOST} range. * - * @param buffer type + * @param buffer type * @param stop stop key (required) * @return a key range (never null) */ @@ -131,9 +124,9 @@ public static KeyRange atMostBackward(final T stop) { /** * Create a {@link KeyRangeType#FORWARD_CLOSED} range. * - * @param buffer type + * @param buffer type * @param start start key (required) - * @param stop stop key (required) + * @param stop stop key (required) * @return a key range (never null) */ public static KeyRange closed(final T start, final T stop) { @@ -143,9 +136,9 @@ public static KeyRange closed(final T start, final T stop) { /** * Create a {@link KeyRangeType#BACKWARD_CLOSED} range. * - * @param buffer type + * @param buffer type * @param start start key (required) - * @param stop stop key (required) + * @param stop stop key (required) * @return a key range (never null) */ public static KeyRange closedBackward(final T start, final T stop) { @@ -155,9 +148,9 @@ public static KeyRange closedBackward(final T start, final T stop) { /** * Create a {@link KeyRangeType#FORWARD_CLOSED_OPEN} range. * - * @param buffer type + * @param buffer type * @param start start key (required) - * @param stop stop key (required) + * @param stop stop key (required) * @return a key range (never null) */ public static KeyRange closedOpen(final T start, final T stop) { @@ -167,9 +160,9 @@ public static KeyRange closedOpen(final T start, final T stop) { /** * Create a {@link KeyRangeType#BACKWARD_CLOSED_OPEN} range. * - * @param buffer type + * @param buffer type * @param start start key (required) - * @param stop stop key (required) + * @param stop stop key (required) * @return a key range (never null) */ public static KeyRange closedOpenBackward(final T start, final T stop) { @@ -179,7 +172,7 @@ public static KeyRange closedOpenBackward(final T start, final T stop) { /** * Create a {@link KeyRangeType#FORWARD_GREATER_THAN} range. * - * @param buffer type + * @param buffer type * @param start start key (required) * @return a key range (never null) */ @@ -190,7 +183,7 @@ public static KeyRange greaterThan(final T start) { /** * Create a {@link KeyRangeType#BACKWARD_GREATER_THAN} range. * - * @param buffer type + * @param buffer type * @param start start key (required) * @return a key range (never null) */ @@ -201,7 +194,7 @@ public static KeyRange greaterThanBackward(final T start) { /** * Create a {@link KeyRangeType#FORWARD_LESS_THAN} range. * - * @param buffer type + * @param buffer type * @param stop stop key (required) * @return a key range (never null) */ @@ -212,7 +205,7 @@ public static KeyRange lessThan(final T stop) { /** * Create a {@link KeyRangeType#BACKWARD_LESS_THAN} range. * - * @param buffer type + * @param buffer type * @param stop stop key (required) * @return a key range (never null) */ @@ -223,9 +216,9 @@ public static KeyRange lessThanBackward(final T stop) { /** * Create a {@link KeyRangeType#FORWARD_OPEN} range. * - * @param buffer type + * @param buffer type * @param start start key (required) - * @param stop stop key (required) + * @param stop stop key (required) * @return a key range (never null) */ public static KeyRange open(final T start, final T stop) { @@ -235,9 +228,9 @@ public static KeyRange open(final T start, final T stop) { /** * Create a {@link KeyRangeType#BACKWARD_OPEN} range. * - * @param buffer type + * @param buffer type * @param start start key (required) - * @param stop stop key (required) + * @param stop stop key (required) * @return a key range (never null) */ public static KeyRange openBackward(final T start, final T stop) { @@ -247,9 +240,9 @@ public static KeyRange openBackward(final T start, final T stop) { /** * Create a {@link KeyRangeType#FORWARD_OPEN_CLOSED} range. * - * @param buffer type + * @param buffer type * @param start start key (required) - * @param stop stop key (required) + * @param stop stop key (required) * @return a key range (never null) */ public static KeyRange openClosed(final T start, final T stop) { @@ -259,9 +252,9 @@ public static KeyRange openClosed(final T start, final T stop) { /** * Create a {@link KeyRangeType#BACKWARD_OPEN_CLOSED} range. * - * @param buffer type + * @param buffer type * @param start start key (required) - * @param stop stop key (required) + * @param stop stop key (required) * @return a key range (never null) */ public static KeyRange openClosedBackward(final T start, final T stop) { @@ -294,5 +287,4 @@ public T getStop() { public KeyRangeType getType() { return type; } - } diff --git a/src/main/java/org/lmdbjava/KeyRangeType.java b/src/main/java/org/lmdbjava/KeyRangeType.java index 267e45c8..ad67286d 100644 --- a/src/main/java/org/lmdbjava/KeyRangeType.java +++ b/src/main/java/org/lmdbjava/KeyRangeType.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * #L% */ - package org.lmdbjava; import static java.util.Objects.requireNonNull; @@ -36,241 +31,195 @@ /** * Key range type. * - *

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

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

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

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

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

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

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

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

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

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

- * In our example and with a passed search key of 5, the returned keys would - * be 6 and 8. With a passed key of 6, the returned keys would be 6 and 8. + *

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

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

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

- * In our example and with a passed search key of 5, the returned keys would - * be 2 and 4. With a passed key of 6, the returned keys would be 2, 4 and 6. + *

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

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

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

- * In our example and with a passed search range of 3 - 7, the returned keys - * would be 4 and 6. With a range of 2 - 6, the keys would be 2, 4 and 6. + *

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

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

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

- * In our example and with a passed search range of 3 - 8, the returned keys - * would be 4 and 6. With a range of 2 - 6, the keys would be 2 and 4. + *

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

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

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

- * In our example and with a passed search key of 4, the returned keys would - * be 6 and 8. With a passed key of 3, the returned keys would be 4, 6 and 8. + *

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

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

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

- * In our example and with a passed search key of 5, the returned keys would - * be 2 and 4. With a passed key of 8, the returned keys would be 2, 4 and 6. + *

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

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

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

- * In our example and with a passed search range of 3 - 7, the returned keys - * would be 4 and 6. With a range of 2 - 8, the key would be 4 and 6. + *

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

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

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

- * In our example and with a passed search range of 3 - 8, the returned keys - * would be 4, 6 and 8. With a range of 2 - 6, the keys would be 4 and 6. + *

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

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

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

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

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

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

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

- * In our example and with a passed search key of 5, the returned keys would - * be 4 and 2. With a passed key of 6, the returned keys would be 6, 4 and 2. - * With a passed key of 9, the returned keys would be 8, 6, 4 and 2. + *

In our example and with a passed search key of 5, the returned keys would be 4 and 2. With a + * passed key of 6, the returned keys would be 6, 4 and 2. With a passed key of 9, the returned + * keys would be 8, 6, 4 and 2. */ BACKWARD_AT_LEAST(false, true, false), /** - * Start on the last key and iterate backward until a key equal to it (or the - * first key immediately preceding it it) is reached. + * Start on the last key and iterate backward until a key equal to it (or the first key + * immediately preceding it it) is reached. * - *

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

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

- * In our example and with a passed search key of 5, the returned keys would - * be 8 and 6. With a passed key of 6, the returned keys would be 8 and 6. + *

In our example and with a passed search key of 5, the returned keys would be 8 and 6. With a + * passed key of 6, the returned keys would be 8 and 6. */ BACKWARD_AT_MOST(false, false, true), /** - * Iterate backward between the passed keys, matching on the first keys - * directly equal to the passed key (or immediately preceding it in the case - * of the "start" key, or immediately following it in the case of the "stop" - * key). - * - *

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

- * In our example and with a passed search range of 7 - 3, the returned keys - * would be 6 and 4. With a range of 6 - 2, the keys would be 6, 4 and 2. - * With a range of 9 - 3, the returned keys would be 8, 6 and 4. + * Iterate backward between the passed keys, matching on the first keys directly equal to the + * passed key (or immediately preceding it in the case of the "start" key, or immediately + * following it in the case of the "stop" key). + * + *

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

In our example and with a passed search range of 7 - 3, the returned keys would be 6 and 4. + * With a range of 6 - 2, the keys would be 6, 4 and 2. With a range of 9 - 3, the returned keys + * would be 8, 6 and 4. */ BACKWARD_CLOSED(false, true, true), /** - * Iterate backward between the passed keys, matching on the first keys - * directly equal to the passed key (or immediately preceding it in the case - * of the "start" key, or immediately following it in the case of the "stop" - * key). Do not return the "stop" key. - * - *

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

- * In our example and with a passed search range of 8 - 3, the returned keys - * would be 8, 6 and 4. With a range of 7 - 2, the keys would be 6 and 4. - * With a range of 9 - 3, the keys would be 8, 6 and 4. + * Iterate backward between the passed keys, matching on the first keys directly equal to the + * passed key (or immediately preceding it in the case of the "start" key, or immediately + * following it in the case of the "stop" key). Do not return the "stop" key. + * + *

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

In our example and with a passed search range of 8 - 3, the returned keys would be 8, 6 and + * 4. With a range of 7 - 2, the keys would be 6 and 4. With a range of 9 - 3, the keys would be + * 8, 6 and 4. */ BACKWARD_CLOSED_OPEN(false, true, true), /** - * Start immediate prior to the passed key (but not equal to it) and iterate - * backward until no keys remain. + * Start immediate prior to the passed key (but not equal to it) and iterate backward until no + * keys remain. * - *

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

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

- * In our example and with a passed search key of 6, the returned keys would - * be 4 and 2. With a passed key of 7, the returned keys would be 6, 4 and 2. - * With a passed key of 9, the returned keys would be 8, 6, 4 and 2. + *

In our example and with a passed search key of 6, the returned keys would be 4 and 2. With a + * passed key of 7, the returned keys would be 6, 4 and 2. With a passed key of 9, the returned + * keys would be 8, 6, 4 and 2. */ BACKWARD_GREATER_THAN(false, true, false), /** - * Start on the last key and iterate backward until the last key greater than - * the passed "stop" key is reached. Do not return the "stop" key. + * Start on the last key and iterate backward until the last key greater than the passed "stop" + * key is reached. Do not return the "stop" key. * - *

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

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

- * In our example and with a passed search key of 5, the returned keys would - * be 8 and 6. With a passed key of 2, the returned keys would be 8, 6 and 4 + *

In our example and with a passed search key of 5, the returned keys would be 8 and 6. With a + * passed key of 2, the returned keys would be 8, 6 and 4 */ BACKWARD_LESS_THAN(false, false, true), /** - * Iterate backward between the passed keys, but do not return the passed - * keys. + * Iterate backward between the passed keys, but do not return the passed keys. * - *

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

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

- * In our example and with a passed search range of 7 - 2, the returned keys - * would be 6 and 4. With a range of 8 - 1, the keys would be 6, 4 and 2. - * With a range of 9 - 4, the keys would be 8 and 6. + *

In our example and with a passed search range of 7 - 2, the returned keys would be 6 and 4. + * With a range of 8 - 1, the keys would be 6, 4 and 2. With a range of 9 - 4, the keys would be 8 + * and 6. */ BACKWARD_OPEN(false, true, true), /** - * Iterate backward between the passed keys. Do not return the "start" key, - * but do return the "stop" key. + * Iterate backward between the passed keys. Do not return the "start" key, but do return the + * "stop" key. * - *

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

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

- * In our example and with a passed search range of 7 - 2, the returned keys - * would be 6, 4 and 2. With a range of 8 - 4, the keys would be 6 and 4. - * With a range of 9 - 4, the keys would be 8, 6 and 4. + *

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

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

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

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

The iterator will perform this action and present the resulting key to {@link + * #iteratorOp(java.util.Comparator, java.lang.Object)} for decision. * * @return appropriate action for this key range type */ @@ -455,51 +404,29 @@ CursorOp nextOp() { return isDirectionForward() ? NEXT : PREV; } - /** - * Action now required with the iterator. - */ + /** Action now required with the iterator. */ enum IteratorOp { - /** - * Consider iterator completed. - */ + /** Consider iterator completed. */ TERMINATE, - /** - * Call {@link KeyRange#nextOp()} again and try again. - */ + /** Call {@link KeyRange#nextOp()} again and try again. */ CALL_NEXT_OP, - /** - * Return the key to the user. - */ + /** Return the key to the user. */ RELEASE } - /** - * Action now required with the cursor. - */ + /** Action now required with the cursor. */ enum CursorOp { - /** - * Move to first. - */ + /** Move to first. */ FIRST, - /** - * Move to last. - */ + /** Move to last. */ LAST, - /** - * Get "start" key with {@link GetOp#MDB_SET_RANGE}. - */ + /** Get "start" key with {@link GetOp#MDB_SET_RANGE}. */ GET_START_KEY, - /** - * Get "start" key with {@link GetOp#MDB_SET_RANGE}, fall back to LAST. - */ + /** Get "start" key with {@link GetOp#MDB_SET_RANGE}, fall back to LAST. */ GET_START_KEY_BACKWARD, - /** - * Move forward. - */ + /** Move forward. */ NEXT, - /** - * Move backward. - */ + /** Move backward. */ PREV } } diff --git a/src/main/java/org/lmdbjava/KeyVal.java b/src/main/java/org/lmdbjava/KeyVal.java index 12b9f9af..c6f2aae5 100644 --- a/src/main/java/org/lmdbjava/KeyVal.java +++ b/src/main/java/org/lmdbjava/KeyVal.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * #L% */ - package org.lmdbjava; import static java.util.Objects.requireNonNull; @@ -102,20 +97,19 @@ void valIn(final int size) { } /** - * Prepares an array suitable for presentation as the data argument to a - * MDB_MULTIPLE put. + * Prepares an array suitable for presentation as the data argument to a MDB_MULTIPLE + * put. * - *

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

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

    - *
  • ptrVal1.data = pointer to the data address of passed buffer
  • - *
  • ptrVal1.size = size of each individual data element
  • - *
  • ptrVal2.data = unused
  • - *
  • ptrVal2.size = number of data elements (as passed to this method)
  • + *
  • ptrVal1.data = pointer to the data address of passed buffer + *
  • ptrVal1.size = size of each individual data element + *
  • ptrVal2.data = unused + *
  • ptrVal2.size = number of data elements (as passed to this method) *
* - * @param val a user-provided buffer with data elements (required) + * @param val a user-provided buffer with data elements (required) * @param elements number of data elements the user has provided * @return a properly-prepared pointer to an array for the operation */ @@ -134,5 +128,4 @@ T valOut() { v = proxy.out(v, ptrVal, ptrValAddr); return v; } - } diff --git a/src/main/java/org/lmdbjava/Library.java b/src/main/java/org/lmdbjava/Library.java index 22308600..ef9b9b35 100644 --- a/src/main/java/org/lmdbjava/Library.java +++ b/src/main/java/org/lmdbjava/Library.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * #L% */ - package org.lmdbjava; import static java.io.File.createTempFile; @@ -32,8 +27,6 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import jnr.ffi.Pointer; import jnr.ffi.Struct; import jnr.ffi.annotations.Delegate; @@ -47,25 +40,23 @@ /** * JNR-FFI interface to LMDB. * - *

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

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

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

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

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

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

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

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

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

This method is always implemented as a synchronization on {@code ref}. It is the caller's + * responsibility to ensure that this synchronization will not cause deadlock. * * @param ref the reference (null is acceptable but has no effect) * @see Netty PR 8410 */ - @SuppressFBWarnings({"ESync_EMPTY_SYNC", "UC_USELESS_VOID_METHOD"}) public static void reachabilityFence0(final Object ref) { if (ref != null) { synchronized (ref) { diff --git a/src/main/java/org/lmdbjava/ResultCodeMapper.java b/src/main/java/org/lmdbjava/ResultCodeMapper.java index 809628ac..1e4d841e 100644 --- a/src/main/java/org/lmdbjava/ResultCodeMapper.java +++ b/src/main/java/org/lmdbjava/ResultCodeMapper.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * #L% */ - package org.lmdbjava; import static jnr.constants.ConstantSet.getConstantSet; @@ -31,16 +26,12 @@ /** * Maps a LMDB C result code to the equivalent Java exception. * - *

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

The immutable nature of all LMDB exceptions means the mapper internally maintains a table of + * them. */ -@SuppressWarnings("PMD.CyclomaticComplexity") final class ResultCodeMapper { - /** - * Successful result. - */ + /** Successful result. */ static final int MDB_SUCCESS = 0; private static final ConstantSet CONSTANTS; @@ -50,8 +41,7 @@ final class ResultCodeMapper { CONSTANTS = getConstantSet(POSIX_ERR_NO); } - private ResultCodeMapper() { - } + private ResultCodeMapper() {} /** * Checks the result code and raises an exception is not {@link #MDB_SUCCESS}. @@ -113,5 +103,4 @@ static void checkRc(final int rc) { final String msg = constant.name() + " " + constant.toString(); throw new LmdbNativeException.ConstantDerivedException(rc, msg); } - } diff --git a/src/main/java/org/lmdbjava/SeekOp.java b/src/main/java/org/lmdbjava/SeekOp.java index ed5fb47c..162aef49 100644 --- a/src/main/java/org/lmdbjava/SeekOp.java +++ b/src/main/java/org/lmdbjava/SeekOp.java @@ -1,100 +1,62 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * #L% */ - package org.lmdbjava; /** * Flags for use when performing a {@link Cursor#seek(org.lmdbjava.SeekOp)}. * - *

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

Unlike most other LMDB enums, this enum is not bit masked. */ public enum SeekOp { - /** - * Position at first key/data item. - */ + /** Position at first key/data item. */ MDB_FIRST(0), - /** - * Position at first data item of current key. Only for - * {@link DbiFlags#MDB_DUPSORT}. - */ + /** Position at first data item of current key. Only for {@link DbiFlags#MDB_DUPSORT}. */ MDB_FIRST_DUP(1), - /** - * Position at key/data pair. Only for {@link DbiFlags#MDB_DUPSORT}. - */ + /** Position at key/data pair. Only for {@link DbiFlags#MDB_DUPSORT}. */ MDB_GET_BOTH(2), - /** - * position at key, nearest data. Only for {@link DbiFlags#MDB_DUPSORT}. - */ + /** position at key, nearest data. Only for {@link DbiFlags#MDB_DUPSORT}. */ MDB_GET_BOTH_RANGE(3), - /** - * Return key/data at current cursor position. - */ + /** Return key/data at current cursor position. */ MDB_GET_CURRENT(4), /** - * Return key and up to a page of duplicate data items from current cursor - * position. Move cursor to prepare for {@link #MDB_NEXT_MULTIPLE}. Only for - * {@link DbiFlags#MDB_DUPSORT}. + * Return key and up to a page of duplicate data items from current cursor position. Move cursor + * to prepare for {@link #MDB_NEXT_MULTIPLE}. Only for {@link DbiFlags#MDB_DUPSORT}. */ MDB_GET_MULTIPLE(5), - /** - * Position at last key/data item. - */ + /** Position at last key/data item. */ MDB_LAST(6), - /** - * Position at last data item of current key. Only for - * {@link DbiFlags#MDB_DUPSORT}. - */ + /** Position at last data item of current key. Only for {@link DbiFlags#MDB_DUPSORT}. */ MDB_LAST_DUP(7), - /** - * Position at next data item. - */ + /** Position at next data item. */ MDB_NEXT(8), - /** - * Position at next data item of current key. Only for - * {@link DbiFlags#MDB_DUPSORT}. - */ + /** Position at next data item of current key. Only for {@link DbiFlags#MDB_DUPSORT}. */ MDB_NEXT_DUP(9), /** - * Return key and up to a page of duplicate data items from next cursor - * position. Move cursor to prepare for {@link #MDB_NEXT_MULTIPLE}. Only for - * {@link DbiFlags#MDB_DUPSORT}. + * Return key and up to a page of duplicate data items from next cursor position. Move cursor to + * prepare for {@link #MDB_NEXT_MULTIPLE}. Only for {@link DbiFlags#MDB_DUPSORT}. */ MDB_NEXT_MULTIPLE(10), - /** - * Position at first data item of next key. - */ + /** Position at first data item of next key. */ MDB_NEXT_NODUP(11), - /** - * Position at previous data item. - */ + /** Position at previous data item. */ MDB_PREV(12), - /** - * Position at previous data item of current key. - * {@link DbiFlags#MDB_DUPSORT}. - */ + /** Position at previous data item of current key. {@link DbiFlags#MDB_DUPSORT}. */ MDB_PREV_DUP(13), - /** - * Position at last data item of previous key. - */ + /** Position at last data item of previous key. */ MDB_PREV_NODUP(14); private final int code; @@ -111,5 +73,4 @@ public enum SeekOp { public int getCode() { return code; } - } diff --git a/src/main/java/org/lmdbjava/Stat.java b/src/main/java/org/lmdbjava/Stat.java index 42b344fb..d4995324 100644 --- a/src/main/java/org/lmdbjava/Stat.java +++ b/src/main/java/org/lmdbjava/Stat.java @@ -1,64 +1,48 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * #L% */ - package org.lmdbjava; -/** - * Statistics, as returned by {@link Env#stat()} and - * {@link Dbi#stat(org.lmdbjava.Txn)}. - */ +/** Statistics, as returned by {@link Env#stat()} and {@link Dbi#stat(org.lmdbjava.Txn)}. */ public final class Stat { - /** - * Number of internal (non-leaf) pages. - */ + /** Number of internal (non-leaf) pages. */ public final long branchPages; - /** - * Depth (height) of the B-tree. - */ + /** Depth (height) of the B-tree. */ public final int depth; - /** - * Number of data items. - */ + /** Number of data items. */ public final long entries; - /** - * Number of leaf pages. - */ + /** Number of leaf pages. */ public final long leafPages; - /** - * Number of overflow pages. - */ + /** Number of overflow pages. */ public final long overflowPages; - /** - * Size of a database page. This is currently the same for all databases. - */ + /** Size of a database page. This is currently the same for all databases. */ public final int pageSize; - Stat(final int pageSize, final int depth, final long branchPages, - final long leafPages, - final long overflowPages, final long entries) { + Stat( + final int pageSize, + final int depth, + final long branchPages, + final long leafPages, + final long overflowPages, + final long entries) { this.pageSize = pageSize; this.depth = depth; this.branchPages = branchPages; @@ -69,10 +53,19 @@ public final class Stat { @Override public String toString() { - return "Stat{" + "branchPages=" + branchPages + ", depth=" + depth - + ", entries=" + entries + ", leafPages=" + leafPages - + ", overflowPages=" + overflowPages + ", pageSize=" + pageSize - + '}'; + return "Stat{" + + "branchPages=" + + branchPages + + ", depth=" + + depth + + ", entries=" + + entries + + ", leafPages=" + + leafPages + + ", overflowPages=" + + overflowPages + + ", pageSize=" + + pageSize + + '}'; } - } diff --git a/src/main/java/org/lmdbjava/TargetName.java b/src/main/java/org/lmdbjava/TargetName.java index 446ea5b5..023ecba5 100644 --- a/src/main/java/org/lmdbjava/TargetName.java +++ b/src/main/java/org/lmdbjava/TargetName.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * #L% */ - package org.lmdbjava; import static java.lang.System.getProperty; @@ -26,44 +21,41 @@ /** * Determines the name of the target LMDB native library. * - *

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

This provides a simple way to execute the verifier for those applications which do not wish + * to manage threads directly. * * @param duration amount of time to execute - * @param unit units used to express the duration + * @param unit units used to express the duration * @return number of database rows successfully verified */ public long runFor(final long duration, final TimeUnit unit) { @@ -233,14 +210,11 @@ private Dbi getDbi(final long forId) { return dbis.get((int) (forId % dbis.size())); } - /** - * Request the verifier to stop execution. - */ + /** Request the verifier to stop execution. */ private void stop() { proceed.set(false); } - @SuppressFBWarnings("DMI_RANDOM_USED_ONLY_ONCE") private void transactionControl() { if (id % BATCH_SIZE == 0) { if (txn != null) { @@ -282,8 +256,8 @@ private void verifyValue(final long forId, final ByteBuffer bb) { final int rndSize = valueSize(forId); final int expected = rndSize + CRC_LENGTH; if (bb.limit() != expected) { - throw new IllegalStateException("Limit error id=" + forId + " exp=" - + expected + " limit=" + bb.limit()); + throw new IllegalStateException( + "Limit error id=" + forId + " exp=" + expected + " limit=" + bb.limit()); } final long crcRead = bb.getLong(); @@ -308,5 +282,4 @@ private void write(final long forId) { throw new IllegalStateException("DB put id=" + forId, ex); } } - } diff --git a/src/main/java/org/lmdbjava/package-info.java b/src/main/java/org/lmdbjava/package-info.java index 52c8551a..9589c470 100644 --- a/src/main/java/org/lmdbjava/package-info.java +++ b/src/main/java/org/lmdbjava/package-info.java @@ -1,63 +1,52 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * #L% */ - /** * Lightning Memory Database (LMDB) for Java (LmdbJava). * - *

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

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

Priorities: * - *

- * Priorities: *

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

- * Critical paths of special latency focus: + *

Critical paths of special latency focus: + * *

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Start the JVM with arguments --add-opens java.base/java.nio=ALL-UNNAMED * --add-opens java.base/sun.nio.ch=ALL-UNNAMED to suppress JVM warnings. */ public final class TutorialTest { private static final String DB_NAME = "my DB"; - @Rule - public final TemporaryFolder tmp = new TemporaryFolder(); + @Rule public final TemporaryFolder tmp = new TemporaryFolder(); /** * In this first tutorial we will use LmdbJava with some basic defaults. @@ -82,7 +71,6 @@ public final class TutorialTest { * @throws IOException if a path was unavailable for memory mapping */ @Test - @SuppressWarnings("ConvertToTryWithResources") public void tutorial1() throws IOException { // We need a storage directory first. // The path cannot be on a remote file system. @@ -90,15 +78,16 @@ public void tutorial1() throws IOException { // We always need an Env. An Env owns a physical on-disk storage file. One // Env can store many different databases (ie sorted maps). - final Env env = create() - // LMDB also needs to know how large our DB might be. Over-estimating is OK. - .setMapSize(10_485_760) - // LMDB also needs to know how many DBs (Dbi) we want to store in this Env. - .setMaxDbs(1) - // Now let's open the Env. The same path can be concurrently opened and - // used in different processes, but do not open the same path twice in - // the same process at the same time. - .open(path); + final Env env = + create() + // LMDB also needs to know how large our DB might be. Over-estimating is OK. + .setMapSize(10_485_760) + // LMDB also needs to know how many DBs (Dbi) we want to store in this Env. + .setMaxDbs(1) + // Now let's open the Env. The same path can be concurrently opened and + // used in different processes, but do not open the same path twice in + // the same process at the same time. + .open(path); // We need a Dbi for each DB. A Dbi roughly equates to a sorted map. The // MDB_CREATE flag causes the DB to be created if it doesn't already exist. @@ -149,11 +138,10 @@ public void tutorial1() throws IOException { /** * In this second tutorial we'll learn more about LMDB's ACID Txns. * - * @throws IOException if a path was unavailable for memory mapping + * @throws IOException if a path was unavailable for memory mapping * @throws InterruptedException if executor shutdown interrupted */ @Test - @SuppressWarnings("ConvertToTryWithResources") public void tutorial2() throws IOException, InterruptedException { final Env env = createSimpleEnv(tmp.newFolder()); final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); @@ -188,14 +176,15 @@ public void tutorial2() throws IOException, InterruptedException { // // Let's write out a "key2" via a new write Txn in a different thread. final ExecutorService es = newCachedThreadPool(); - es.execute(() -> { - try (Txn txn = env.txnWrite()) { - key.clear(); - key.put("key2".getBytes(UTF_8)).flip(); - db.put(txn, key, val); - txn.commit(); - } - }); + es.execute( + () -> { + try (Txn txn = env.txnWrite()) { + key.clear(); + key.put("key2".getBytes(UTF_8)).flip(); + db.put(txn, key, val); + txn.commit(); + } + }); es.shutdown(); es.awaitTermination(10, SECONDS); @@ -220,14 +209,13 @@ public void tutorial2() throws IOException, InterruptedException { } /** - * In this third tutorial we'll have a look at the Cursor. Up until now we've - * just used Dbi, which is good enough for simple cases but unsuitable if you - * don't know the key to fetch, or want to iterate over all the data etc. + * In this third tutorial we'll have a look at the Cursor. Up until now we've just used Dbi, which + * is good enough for simple cases but unsuitable if you don't know the key to fetch, or want to + * iterate over all the data etc. * * @throws IOException if a path was unavailable for memory mapping */ @Test - @SuppressWarnings("ConvertToTryWithResources") public void tutorial3() throws IOException { final Env env = createSimpleEnv(tmp.newFolder()); final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); @@ -296,13 +284,12 @@ public void tutorial3() throws IOException { } /** - * In this fourth tutorial we'll take a quick look at the iterators. These are - * a more Java idiomatic form of using the Cursors we looked at in tutorial 3. + * In this fourth tutorial we'll take a quick look at the iterators. These are a more Java + * idiomatic form of using the Cursors we looked at in tutorial 3. * * @throws IOException if a path was unavailable for memory mapping */ @Test - @SuppressWarnings("ConvertToTryWithResources") public void tutorial4() throws IOException { final Env env = createSimpleEnv(tmp.newFolder()); final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); @@ -332,8 +319,7 @@ public void tutorial4() throws IOException { } // Iterate backward in terms of key ordering starting with the last key. - try (CursorIterable ci = db.iterate(txn, - KeyRange.allBackward())) { + try (CursorIterable ci = db.iterate(txn, KeyRange.allBackward())) { for (final KeyVal kv : ci) { assertThat(kv.key(), notNullValue()); assertThat(kv.val(), notNullValue()); @@ -362,7 +348,6 @@ public void tutorial4() throws IOException { * @throws IOException if a path was unavailable for memory mapping */ @Test - @SuppressWarnings("ConvertToTryWithResources") public void tutorial5() throws IOException { final Env env = createSimpleEnv(tmp.newFolder()); @@ -410,20 +395,19 @@ public void tutorial5() throws IOException { } /** - * Next up we'll show you how to easily check your platform (operating system - * and Java version) is working properly with LmdbJava and the embedded LMDB - * native library. + * Next up we'll show you how to easily check your platform (operating system and Java version) is + * working properly with LmdbJava and the embedded LMDB native library. * * @throws IOException if a path was unavailable for memory mapping */ @Test - @SuppressWarnings("ConvertToTryWithResources") public void tutorial6() throws IOException { // Note we need to specify the Verifier's DBI_COUNT for the Env. - final Env env = create(PROXY_OPTIMAL) - .setMapSize(10_485_760) - .setMaxDbs(Verifier.DBI_COUNT) - .open(tmp.newFolder()); + final Env env = + create(PROXY_OPTIMAL) + .setMapSize(10_485_760) + .setMaxDbs(Verifier.DBI_COUNT) + .open(tmp.newFolder()); // Create a Verifier (it's a Callable for those needing full control). final Verifier v = new Verifier(env); @@ -441,15 +425,12 @@ public void tutorial6() throws IOException { * @throws IOException if a path was unavailable for memory mapping */ @Test - @SuppressWarnings("ConvertToTryWithResources") public void tutorial7() throws IOException { // The critical difference is we pass the PROXY_DB field to Env.create(). // There's also a PROXY_SAFE if you want to stop ByteBuffer's Unsafe use. // Aside from that and a different type argument, it's the same as usual... - final Env env = create(PROXY_DB) - .setMapSize(10_485_760) - .setMaxDbs(1) - .open(tmp.newFolder()); + final Env env = + create(PROXY_DB).setMapSize(10_485_760).setMaxDbs(1).open(tmp.newFolder()); final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); @@ -468,12 +449,10 @@ public void tutorial7() throws IOException { c.put(key, val); c.seek(MDB_FIRST); - assertThat(c.key().getStringWithoutLengthUtf8(0, env.getMaxKeySize()), - startsWith("ggg")); + assertThat(c.key().getStringWithoutLengthUtf8(0, env.getMaxKeySize()), startsWith("ggg")); c.seek(MDB_LAST); - assertThat(c.key().getStringWithoutLengthUtf8(0, env.getMaxKeySize()), - startsWith("yyy")); + assertThat(c.key().getStringWithoutLengthUtf8(0, env.getMaxKeySize()), startsWith("yyy")); // DirectBuffer has no position concept. Often you don't want to store // the unnecessary bytes of a varying-size buffer. Let's have a look... @@ -487,8 +466,7 @@ public void tutorial7() throws IOException { c.put(key, val); c.seek(MDB_FIRST); assertThat(c.key().capacity(), is(keyLen)); - assertThat(c.key().getStringWithoutLengthUtf8(0, c.key().capacity()), - is("12characters")); + assertThat(c.key().getStringWithoutLengthUtf8(0, c.key().capacity()), is("12characters")); // To store bigger values again, just wrap the original buffer. key.wrap(keyBb); @@ -505,11 +483,6 @@ public void tutorial7() throws IOException { // or reverse ordered keys, using Env.DISABLE_CHECKS_PROP etc), but you now // know enough to tackle the JavaDocs with confidence. Have fun! private Env createSimpleEnv(final File path) { - return create() - .setMapSize(10_485_760) - .setMaxDbs(1) - .setMaxReaders(1) - .open(path); + return create().setMapSize(10_485_760).setMaxDbs(1).setMaxReaders(1).open(path); } - } diff --git a/src/test/java/org/lmdbjava/TxnTest.java b/src/test/java/org/lmdbjava/TxnTest.java index 775a10a6..e768e424 100644 --- a/src/test/java/org/lmdbjava/TxnTest.java +++ b/src/test/java/org/lmdbjava/TxnTest.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * #L% */ - package org.lmdbjava; import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; @@ -49,7 +44,6 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicLong; - import org.junit.After; import org.junit.Before; import org.junit.Rule; @@ -65,13 +59,10 @@ import org.lmdbjava.Txn.ReadWriteRequiredException; import org.lmdbjava.Txn.ResetException; -/** - * Test {@link Txn}. - */ +/** Test {@link Txn}. */ public final class TxnTest { - @Rule - public final TemporaryFolder tmp = new TemporaryFolder(); + @Rule public final TemporaryFolder tmp = new TemporaryFolder(); private Env env; private File path; @@ -83,11 +74,12 @@ public void after() { @Before public void before() throws IOException { path = tmp.newFile(); - env = create() - .setMapSize(KIBIBYTES.toBytes(256)) - .setMaxReaders(1) - .setMaxDbs(2) - .open(path, POSIX_MODE, MDB_NOSUBDIR); + env = + create() + .setMapSize(KIBIBYTES.toBytes(256)) + .setMaxReaders(1) + .setMaxDbs(2) + .open(path, POSIX_MODE, MDB_NOSUBDIR); } @Test(expected = BadValueSizeException.class) @@ -129,16 +121,14 @@ public void rangeSearch() { } assertEquals(3, keysFound.size()); - } } @Test public void readOnlyTxnAllowedInReadOnlyEnv() { env.openDbi(DB_1, MDB_CREATE); - try (Env roEnv = create() - .setMaxReaders(1) - .open(path, MDB_NOSUBDIR, MDB_RDONLY_ENV)) { + try (Env roEnv = + create().setMaxReaders(1).open(path, MDB_NOSUBDIR, MDB_RDONLY_ENV)) { assertThat(roEnv.txnRead(), is(notNullValue())); } } @@ -147,9 +137,8 @@ public void readOnlyTxnAllowedInReadOnlyEnv() { public void readWriteTxnDeniedInReadOnlyEnv() { env.openDbi(DB_1, MDB_CREATE); env.close(); - try (Env roEnv = create() - .setMaxReaders(1) - .open(path, MDB_NOSUBDIR, MDB_RDONLY_ENV)) { + try (Env roEnv = + create().setMaxReaders(1).open(path, MDB_NOSUBDIR, MDB_RDONLY_ENV)) { roEnv.txnWrite(); // error } } @@ -224,7 +213,6 @@ public void txCannotCommitTwice() { } @Test(expected = AlreadyClosedException.class) - @SuppressWarnings("ResultOfObjectAllocationIgnored") public void txConstructionDeniedIfEnvClosed() { env.close(); env.txnRead(); @@ -269,7 +257,7 @@ public void txResetDeniedIfEnvClosed() { @Test public void txParent() { try (Txn txRoot = env.txnWrite(); - Txn txChild = env.txn(txRoot)) { + Txn txChild = env.txn(txRoot)) { assertThat(txRoot.getParent(), is(nullValue())); assertThat(txChild.getParent(), is(txRoot)); } @@ -278,7 +266,7 @@ public void txParent() { @Test(expected = AlreadyClosedException.class) public void txParentDeniedIfEnvClosed() { try (Txn txRoot = env.txnWrite(); - Txn txChild = env.txn(txRoot)) { + Txn txChild = env.txn(txRoot)) { env.close(); assertThat(txChild.getParent(), is(txRoot)); } @@ -363,5 +351,4 @@ public void zeroByteKeysRejected() throws IOException { assertThat(key.remaining(), is(0)); // because key.flip() skipped dbi.put(key, bb(2)); } - } diff --git a/src/test/java/org/lmdbjava/VerifierTest.java b/src/test/java/org/lmdbjava/VerifierTest.java index 41f4e774..c57e26dc 100644 --- a/src/test/java/org/lmdbjava/VerifierTest.java +++ b/src/test/java/org/lmdbjava/VerifierTest.java @@ -1,23 +1,18 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * #L% */ - package org.lmdbjava; import static com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; @@ -30,31 +25,27 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.TimeUnit; - import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -/** - * Test {@link Verifier}. - */ +/** Test {@link Verifier}. */ public final class VerifierTest { - @Rule - public final TemporaryFolder tmp = new TemporaryFolder(); + @Rule public final TemporaryFolder tmp = new TemporaryFolder(); @Test public void verification() throws IOException { final File path = tmp.newFile(); - try (Env env = create() - .setMaxReaders(1) - .setMaxDbs(Verifier.DBI_COUNT) - .setMapSize(MEBIBYTES.toBytes(10)) - .open(path, MDB_NOSUBDIR)) { + try (Env env = + create() + .setMaxReaders(1) + .setMaxDbs(Verifier.DBI_COUNT) + .setMapSize(MEBIBYTES.toBytes(10)) + .open(path, MDB_NOSUBDIR)) { final Verifier v = new Verifier(env); final int seconds = Integer.getInteger("verificationSeconds", 2); assertThat(v.runFor(seconds, TimeUnit.SECONDS), greaterThan(1L)); } } - } diff --git a/src/test/java/org/lmdbjava/package-info.java b/src/test/java/org/lmdbjava/package-info.java index baaacbe8..950fc2db 100644 --- a/src/test/java/org/lmdbjava/package-info.java +++ b/src/test/java/org/lmdbjava/package-info.java @@ -1,24 +1,16 @@ -/*- - * #%L - * LmdbJava - * %% - * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project - * %% +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. - * #L% - */ - -/** - * Lightning Memory Database (LMDB) for Java (LmdbJava) tests. */ package org.lmdbjava; From bdde404e16f6b2324b2d2f9043e3cd9fe5f6676b Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 17 Feb 2025 14:20:15 +1100 Subject: [PATCH 188/322] Update to CodeQL v3 --- .github/workflows/codeql.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index dd93488f..b503370a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -27,15 +27,15 @@ jobs: uses: actions/checkout@v3 - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} queries: +security-and-quality - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: category: "/language:${{ matrix.language }}" From 27123765132331540e60adbaadb4d06f63516f1d Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 17 Feb 2025 15:03:45 +1100 Subject: [PATCH 189/322] Update contributor docs to reflect build system configuration --- CONTRIBUTING.md | 10 ++++++---- README.md | 5 ----- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 53a6b14e..c37bf055 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,11 +7,13 @@ This will run: * Tests * Initial Test Coverage -* Checkstyle -* PMD -* FindBugs -* XML Formatting +* Source Code Formatting * License Header Management `mvn clean verify` is also run by CI, but it's quicker and easier to run before submitting. + +### Releasing + +GitHub Actions will perform an official release whenever a developer executes +`mvn release:clean release:prepare`. diff --git a/README.md b/README.md index 62c5abe1..048e57bc 100644 --- a/README.md +++ b/README.md @@ -73,11 +73,6 @@ system package managers, running `cross-compile.sh` on a supported system, or copying it from the `org/lmdbjava` directory of any recent, officially released LmdbJava JAR. -### Releasing - -GitHub Actions will perform an official release whenever a developer executes -`mvn release:clean release:prepare`. - ### Contributing Contributions are welcome! Please see the [Contributing Guidelines](CONTRIBUTING.md). From 6ca74b9f71695057991d9940564345e0074f0fc0 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 17 Feb 2025 15:14:22 +1100 Subject: [PATCH 190/322] Update to Guava 33.4.0-jre --- pom.xml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 9e695d0f..8eb9523d 100644 --- a/pom.xml +++ b/pom.xml @@ -47,14 +47,8 @@ com.google.guava guava - 32.1.3-jre + 33.4.0-jre test - - - com.google.code.findbugs - jsr305 - - com.jakewharton.byteunits From f0d61735382c37674826bebeacf3a6d3486d668d Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 17 Feb 2025 15:14:31 +1100 Subject: [PATCH 191/322] Update to Netty 4.1.118.Final --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8eb9523d..616ea85d 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ io.netty netty-buffer - 4.1.101.Final + 4.1.118.Final true From ce6451f8e1087ebd1165d3576d553b86d596fb14 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 17 Feb 2025 15:14:42 +1100 Subject: [PATCH 192/322] Update to Agrona 2.0.1 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 616ea85d..4dd7cd61 100644 --- a/pom.xml +++ b/pom.xml @@ -77,7 +77,7 @@ org.agrona agrona - 1.20.0 + 2.0.1 true @@ -397,7 +397,7 @@ org.apache.maven.plugins maven-surefire-plugin - @{argLine} --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED + @{argLine} --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/jdk.internal.misc=ALL-UNNAMED 1 false From 9cd5c42ae0d51948cd12cc914fc448243dff314c Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Mon, 17 Feb 2025 15:23:28 +1100 Subject: [PATCH 193/322] Update to Agrona 1.22.0 and note JDK 17+ for later versions --- pom.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 4dd7cd61..6f53c90e 100644 --- a/pom.xml +++ b/pom.xml @@ -77,7 +77,8 @@ org.agrona agrona - 2.0.1 + + 1.22.0 true @@ -397,7 +398,7 @@ org.apache.maven.plugins maven-surefire-plugin - @{argLine} --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/jdk.internal.misc=ALL-UNNAMED + @{argLine} --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED 1 false From 928cf7baaabf40914a43a137ebc7d47797ead695 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Thu, 20 Feb 2025 12:26:13 +1100 Subject: [PATCH 194/322] Add url element to pom.xml to satisfy OSSRH deployment rules --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 6f53c90e..e71e2271 100644 --- a/pom.xml +++ b/pom.xml @@ -333,6 +333,7 @@ + https://github.com/lmdbjava/lmdbjava 2016 The LmdbJava Open Source Project From 0025a4875c2b08645e93c8acc6f2225b2a7c4046 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Thu, 20 Feb 2025 12:28:16 +1100 Subject: [PATCH 195/322] [maven-release-plugin] prepare release lmdbjava-0.9.1 --- pom.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index e71e2271..e7d1d298 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.lmdbjava lmdbjava - 0.9.1-SNAPSHOT + 0.9.1 jar LmdbJava Low latency Java API for the ultra-fast, embedded Symas Lightning Database (LMDB) @@ -184,19 +184,19 @@ - + [${maven.enforcer.mvn},) [${maven.enforcer.java},) - - + + true - + @@ -351,7 +351,7 @@ Kristoffer Sjogren stoffe -at- gmail.com http://stoffe.deephacks.org/ - + +1 @@ -367,7 +367,7 @@ scm:git:git@github.com:lmdbjava/lmdbjava.git scm:git:git@github.com:lmdbjava/lmdbjava.git git@github.com:lmdbjava/lmdbjava.git - HEAD + lmdbjava-0.9.1 GitHub Issues From 154df33aa6b196a96e8abe7a3e145b62cc08f382 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Thu, 20 Feb 2025 12:28:23 +1100 Subject: [PATCH 196/322] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index e7d1d298..b3012729 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.lmdbjava lmdbjava - 0.9.1 + 0.9.2-SNAPSHOT jar LmdbJava Low latency Java API for the ultra-fast, embedded Symas Lightning Database (LMDB) @@ -367,7 +367,7 @@ scm:git:git@github.com:lmdbjava/lmdbjava.git scm:git:git@github.com:lmdbjava/lmdbjava.git git@github.com:lmdbjava/lmdbjava.git - lmdbjava-0.9.1 + HEAD GitHub Issues From 480e984417c17915b31e33de193ff08e118fcec9 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Thu, 6 Mar 2025 16:04:06 +0000 Subject: [PATCH 197/322] gh-249 Remove MDB_UNSIGNEDKEY, let CursorIterable call mdb_cmp There are now essentially three ways of configuring comparators when creating a Dbi. **null comparator** LMDB will use its own comparator & CursorIterable will call down to mdb_cmp for comparisons between the current cursor key and the range start/stop key. **provided comparator** LMDB will use its own comparator & CursorIterable will use the provided comparator for comparisons between the current cursor key and the range start/stop key. **provided comparator with nativeCb==true** LMDB will call back to java for all comparator duties. CursorIterable will use the same provided comparator for comparisons between the current cursor key and the range start/stop key. The methods `getSignedComparator()` and `getUnsignedComparator()` have been made public so users of this library can access them. --- src/main/java/org/lmdbjava/BufferProxy.java | 40 +- .../java/org/lmdbjava/ByteArrayProxy.java | 4 +- src/main/java/org/lmdbjava/ByteBufProxy.java | 4 +- .../java/org/lmdbjava/ByteBufferProxy.java | 4 +- src/main/java/org/lmdbjava/Cursor.java | 4 + .../java/org/lmdbjava/CursorIterable.java | 403 +++++++++++------- src/main/java/org/lmdbjava/Dbi.java | 15 +- src/main/java/org/lmdbjava/DbiFlags.java | 21 +- .../java/org/lmdbjava/DirectBufferProxy.java | 4 +- src/main/java/org/lmdbjava/Env.java | 21 +- src/main/java/org/lmdbjava/Key.java | 74 ++++ src/main/java/org/lmdbjava/KeyRangeType.java | 48 +-- src/main/java/org/lmdbjava/Library.java | 2 + .../java/org/lmdbjava/RangeComparator.java | 19 + .../java/org/lmdbjava/ComparatorTest.java | 8 +- .../org/lmdbjava/CursorIterablePerfTest.java | 163 +++++++ .../java/org/lmdbjava/CursorIterableTest.java | 238 ++++++++--- src/test/java/org/lmdbjava/DbiTest.java | 4 +- src/test/java/org/lmdbjava/KeyRangeTest.java | 5 +- src/test/java/org/lmdbjava/TestUtils.java | 2 + 20 files changed, 768 insertions(+), 315 deletions(-) create mode 100644 src/main/java/org/lmdbjava/Key.java create mode 100644 src/main/java/org/lmdbjava/RangeComparator.java create mode 100644 src/test/java/org/lmdbjava/CursorIterablePerfTest.java diff --git a/src/main/java/org/lmdbjava/BufferProxy.java b/src/main/java/org/lmdbjava/BufferProxy.java index e66031d2..26d9db74 100644 --- a/src/main/java/org/lmdbjava/BufferProxy.java +++ b/src/main/java/org/lmdbjava/BufferProxy.java @@ -16,10 +16,6 @@ package org.lmdbjava; import static java.lang.Long.BYTES; -import static org.lmdbjava.DbiFlags.MDB_INTEGERKEY; -import static org.lmdbjava.DbiFlags.MDB_UNSIGNEDKEY; -import static org.lmdbjava.MaskedFlag.isSet; -import static org.lmdbjava.MaskedFlag.mask; import java.util.Comparator; import jnr.ffi.Pointer; @@ -70,36 +66,25 @@ protected BufferProxy() {} */ protected abstract byte[] getBytes(T buffer); - /** - * Get a suitable default {@link Comparator} given the provided flags. - * - *

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

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

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

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

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

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

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

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

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

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

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

+ *

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

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

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

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

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

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

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

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

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

+ *

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

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

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

+ *

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

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

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

+ *

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

+ *

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

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

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

+ *

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

+ *

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

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

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

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

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

+ *

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

+ *

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

+ *

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

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

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

+ *

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

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

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

+ *

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

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

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

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

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

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

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

+ * + *

*/ MDB_DUPSORT(0x04), /** diff --git a/src/main/java/org/lmdbjava/Env.java b/src/main/java/org/lmdbjava/Env.java index 2e4822b7..d7c7b269 100644 --- a/src/main/java/org/lmdbjava/Env.java +++ b/src/main/java/org/lmdbjava/Env.java @@ -145,7 +145,7 @@ public void close() { public void copy(final File path, final CopyFlags... flags) { requireNonNull(path); validatePath(path); - final int flagsMask = mask(true, flags); + final int flagsMask = mask(flags); checkRc(LIB.mdb_env_copy2(ptr, path.getAbsolutePath(), flagsMask)); } @@ -241,6 +241,15 @@ public boolean isReadOnly() { return readOnly; } + /** + * Open (and optionally creates, if {@link DbiFlags#MDB_CREATE} is set) + * a {@link Dbi} using a builder. + * @return A new builder instance for creating/opening a {@link Dbi}. + */ + public DbiBuilder buildDbi() { + return new DbiBuilder<>(this, proxy, readOnly); + } + /** * Convenience method that opens a {@link Dbi} with a UTF-8 database name and default {@link * Comparator} that is not invoked from native code. @@ -248,7 +257,9 @@ public boolean isReadOnly() { * @param name name of the database (or null if no name is required) * @param flags to open the database with * @return a database that is ready to use + * @deprecated Instead use {@link Env#buildDbi()} */ + @Deprecated() public Dbi openDbi(final String name, final DbiFlags... flags) { final byte[] nameBytes = name == null ? null : name.getBytes(UTF_8); return openDbi(nameBytes, null, false, flags); @@ -268,7 +279,9 @@ public Dbi openDbi(final String name, final DbiFlags... flags) { * comparator will be used. * @param flags to open the database with * @return a database that is ready to use + * @deprecated Instead use {@link Env#buildDbi()} */ + @Deprecated() public Dbi openDbi( final String name, final Comparator comparator, final DbiFlags... flags) { final byte[] nameBytes = name == null ? null : name.getBytes(UTF_8); @@ -288,7 +301,9 @@ public Dbi openDbi( * @param nativeCb whether LMDB native code calls back to the Java comparator * @param flags to open the database with * @return a database that is ready to use + * @deprecated Instead use {@link Env#buildDbi()} */ + @Deprecated() public Dbi openDbi( final String name, final Comparator comparator, @@ -305,7 +320,9 @@ public Dbi openDbi( * @param name name of the database (or null if no name is required) * @param flags to open the database with * @return a database that is ready to use + * @deprecated Instead use {@link Env#buildDbi()} */ + @Deprecated() public Dbi openDbi(final byte[] name, final DbiFlags... flags) { return openDbi(name, null, false, flags); } @@ -318,7 +335,9 @@ public Dbi openDbi(final byte[] name, final DbiFlags... flags) { * @param comparator custom comparator callback (or null to use LMDB default) * @param flags to open the database with * @return a database that is ready to use + * @deprecated Instead use {@link Env#buildDbi()} */ + @Deprecated() public Dbi openDbi( final byte[] name, final Comparator comparator, final DbiFlags... flags) { return openDbi(name, comparator, false, flags); @@ -336,7 +355,9 @@ public Dbi openDbi( * @param nativeCb whether native code calls back to the Java comparator * @param flags to open the database with * @return a database that is ready to use + * @deprecated Instead use {@link Env#buildDbi()} */ + @Deprecated() public Dbi openDbi( final byte[] name, final Comparator comparator, @@ -375,7 +396,9 @@ public Dbi openDbi( * @param nativeCb whether native code should call back to the comparator * @param flags to open the database with * @return a database that is ready to use + * @deprecated Instead use {@link Env#buildDbi()} */ + @Deprecated() public Dbi openDbi( final Txn txn, final byte[] name, @@ -557,7 +580,7 @@ public Env open(final File path, final int mode, final EnvFlags... flags) { checkRc(LIB.mdb_env_set_mapsize(ptr, mapSize)); checkRc(LIB.mdb_env_set_maxdbs(ptr, maxDbs)); checkRc(LIB.mdb_env_set_maxreaders(ptr, maxReaders)); - final int flagsMask = mask(true, flags); + final int flagsMask = mask(flags); final boolean readOnly = isSet(flagsMask, MDB_RDONLY_ENV); final boolean noSubDir = isSet(flagsMask, MDB_NOSUBDIR); checkRc(LIB.mdb_env_open(ptr, path.getAbsolutePath(), flagsMask, mode)); diff --git a/src/main/java/org/lmdbjava/MaskedFlag.java b/src/main/java/org/lmdbjava/MaskedFlag.java index 00556ecb..4dc47b20 100644 --- a/src/main/java/org/lmdbjava/MaskedFlag.java +++ b/src/main/java/org/lmdbjava/MaskedFlag.java @@ -17,11 +17,6 @@ import static java.util.Objects.requireNonNull; -import java.util.Arrays; -import java.util.Objects; -import java.util.function.Predicate; -import java.util.stream.Stream; - /** Indicates an enum that can provide integers for each of its values. */ public interface MaskedFlag { @@ -32,15 +27,6 @@ public interface MaskedFlag { */ int getMask(); - /** - * Indicates if the flag must be propagated to the underlying C code of LMDB or not. - * - * @return the boolean value indicating the propagation - */ - default boolean isPropagatedToLmdb() { - return true; - } - /** * Fetch the integer mask for all presented flags. * @@ -50,54 +36,18 @@ default boolean isPropagatedToLmdb() { */ @SafeVarargs static int mask(final M... flags) { - return mask(false, flags); - } - - /** - * Fetch the integer mask for all presented flags. - * - * @param flag type - * @param flags to mask (null or empty returns zero) - * @return the integer mask for use in C - */ - static int mask(final Stream flags) { - return mask(false, flags); - } - - /** - * Fetch the integer mask for the presented flags. - * - * @param flag type - * @param onlyPropagatedToLmdb if to include only the flags which are also propagate to the C code - * or all of them - * @param flags to mask (null or empty returns zero) - * @return the integer mask for use in C - */ - @SafeVarargs - static int mask(final boolean onlyPropagatedToLmdb, final M... flags) { - return flags == null ? 0 : mask(onlyPropagatedToLmdb, Arrays.stream(flags)); - } - - /** - * Fetch the integer mask for all presented flags. - * - * @param flag type - * @param onlyPropagatedToLmdb if to include only the flags which are also propagate to the C code - * or all of them - * @param flags to mask - * @return the integer mask for use in C - */ - static int mask( - final boolean onlyPropagatedToLmdb, final Stream flags) { - final Predicate filter = onlyPropagatedToLmdb ? MaskedFlag::isPropagatedToLmdb : f -> true; + if (flags == null || flags.length == 0) { + return 0; + } - return flags == null - ? 0 - : flags - .filter(Objects::nonNull) - .filter(filter) - .map(M::getMask) - .reduce(0, (f1, f2) -> f1 | f2); + int result = 0; + for (MaskedFlag flag : flags) { + if (flag == null) { + continue; + } + result |= flag.getMask(); + } + return result; } /** diff --git a/src/main/java/org/lmdbjava/Txn.java b/src/main/java/org/lmdbjava/Txn.java index 05e8ce06..1d5d4860 100644 --- a/src/main/java/org/lmdbjava/Txn.java +++ b/src/main/java/org/lmdbjava/Txn.java @@ -49,7 +49,7 @@ public final class Txn implements AutoCloseable { Txn(final Env env, final Txn parent, final BufferProxy proxy, final TxnFlags... flags) { this.proxy = proxy; this.keyVal = proxy.keyVal(); - final int flagsMask = mask(true, flags); + final int flagsMask = mask(flags); this.readOnly = isSet(flagsMask, MDB_RDONLY_TXN); if (env.isReadOnly() && !this.readOnly) { throw new EnvIsReadOnly(); From ddca4bd528089d54b1e744ffac2ecde5525f203e Mon Sep 17 00:00:00 2001 From: LambdAurora Date: Tue, 8 Jul 2025 18:10:00 +0200 Subject: [PATCH 204/322] Fix comparator lost buffer ref from proxy in Dbi. TODO: address failing dbiWithComparatorThreadSafetyByteArray test. --- src/main/java/org/lmdbjava/Dbi.java | 8 +- .../java/org/lmdbjava/ComparatorTest.java | 13 ++- src/test/java/org/lmdbjava/DbiTest.java | 94 ++++++++++++++----- src/test/java/org/lmdbjava/TestUtils.java | 5 + 4 files changed, 89 insertions(+), 31 deletions(-) diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index ad1bb5a7..4a4cc350 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -81,10 +81,10 @@ public final class Dbi { if (nativeCb) { this.ccb = (keyA, keyB) -> { - final T compKeyA = proxy.allocate(); - final T compKeyB = proxy.allocate(); - proxy.out(compKeyA, keyA, keyA.address()); - proxy.out(compKeyB, keyB, keyB.address()); + T compKeyA = proxy.allocate(); + T compKeyB = proxy.allocate(); + compKeyA = proxy.out(compKeyA, keyA, keyA.address()); + compKeyB = proxy.out(compKeyB, keyB, keyB.address()); final int result = this.comparator.compare(compKeyA, compKeyB); proxy.deallocate(compKeyA); proxy.deallocate(compKeyB); diff --git a/src/test/java/org/lmdbjava/ComparatorTest.java b/src/test/java/org/lmdbjava/ComparatorTest.java index 3e265cee..3c7e7a4d 100644 --- a/src/test/java/org/lmdbjava/ComparatorTest.java +++ b/src/test/java/org/lmdbjava/ComparatorTest.java @@ -67,11 +67,12 @@ public static Object[] data() { final ComparatorRunner string = new StringRunner(); final ComparatorRunner db = new DirectBufferRunner(); final ComparatorRunner ba = new ByteArrayRunner(); + final ComparatorRunner baUnsigned = new UnsignedByteArrayRunner(); final ComparatorRunner bb = new ByteBufferRunner(); final ComparatorRunner netty = new NettyRunner(); final ComparatorRunner gub = new GuavaUnsignedBytes(); final ComparatorRunner gsb = new GuavaSignedBytes(); - return new Object[] {string, db, ba, bb, netty, gub, gsb}; + return new Object[] {string, db, ba, baUnsigned, bb, netty, gub, gsb}; } private static byte[] buffer(final int... bytes) { @@ -140,6 +141,16 @@ public int compare(final byte[] o1, final byte[] o2) { } } + /** Tests {@link ByteArrayProxy} (unsigned). */ + private static final class UnsignedByteArrayRunner implements ComparatorRunner { + + @Override + public int compare(final byte[] o1, final byte[] o2) { + final Comparator c = PROXY_BA.getUnsignedComparator(); + return c.compare(o1, o2); + } + } + /** Tests {@link ByteBufferProxy}. */ private static final class ByteBufferRunner implements ComparatorRunner { diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 1fa80f6e..2e87830c 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -46,9 +46,7 @@ import static org.lmdbjava.KeyRange.atMost; import static org.lmdbjava.PutFlags.MDB_NODUPDATA; import static org.lmdbjava.PutFlags.MDB_NOOVERWRITE; -import static org.lmdbjava.TestUtils.DB_1; -import static org.lmdbjava.TestUtils.ba; -import static org.lmdbjava.TestUtils.bb; +import static org.lmdbjava.TestUtils.*; import java.io.File; import java.io.IOException; @@ -63,7 +61,7 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.BiConsumer; +import java.util.function.*; import org.agrona.concurrent.UnsafeBuffer; import org.hamcrest.Matchers; import org.junit.After; @@ -82,6 +80,7 @@ public final class DbiTest { @Rule public final TemporaryFolder tmp = new TemporaryFolder(); private Env env; + private Env envBa; @After public void after() { @@ -97,6 +96,13 @@ public void before() throws IOException { .setMaxReaders(2) .setMaxDbs(2) .open(path, MDB_NOSUBDIR); + final File pathBa = tmp.newFile(); + envBa = + create(PROXY_BA) + .setMapSize(MEBIBYTES.toBytes(64)) + .setMaxReaders(2) + .setMaxDbs(2) + .open(pathBa, MDB_NOSUBDIR); } @Test(expected = ConstantDerivedException.class) @@ -117,20 +123,41 @@ public void customComparator() { } return lexical * -1; }; - final Dbi db = env.openDbi(DB_1, reverseOrder, true, MDB_CREATE); - try (Txn txn = env.txnWrite()) { - assertThat(db.put(txn, bb(2), bb(3)), is(true)); - assertThat(db.put(txn, bb(4), bb(6)), is(true)); - assertThat(db.put(txn, bb(6), bb(7)), is(true)); - assertThat(db.put(txn, bb(8), bb(7)), is(true)); + doCustomComparator(env, reverseOrder, TestUtils::bb, ByteBuffer::getInt); + } + + @Test + public void customComparatorByteArray() { + final Comparator reverseOrder = + (o1, o2) -> { + final int lexical = PROXY_BA.getComparator().compare(o1, o2); + if (lexical == 0) { + return 0; + } + return lexical * -1; + }; + doCustomComparator(envBa, reverseOrder, TestUtils::ba, TestUtils::fromBa); + } + + private void doCustomComparator( + Env env, + Comparator comparator, + IntFunction serializer, + ToIntFunction deserializer) { + final Dbi db = env.openDbi(DB_1, comparator, true, MDB_CREATE); + try (Txn txn = env.txnWrite()) { + assertThat(db.put(txn, serializer.apply(2), serializer.apply(3)), is(true)); + assertThat(db.put(txn, serializer.apply(4), serializer.apply(6)), is(true)); + assertThat(db.put(txn, serializer.apply(6), serializer.apply(7)), is(true)); + assertThat(db.put(txn, serializer.apply(8), serializer.apply(7)), is(true)); txn.commit(); } - try (Txn txn = env.txnRead(); - CursorIterable ci = db.iterate(txn, atMost(bb(4)))) { - final Iterator> iter = ci.iterator(); - assertThat(iter.next().key().getInt(), is(8)); - assertThat(iter.next().key().getInt(), is(6)); - assertThat(iter.next().key().getInt(), is(4)); + try (Txn txn = env.txnRead(); + CursorIterable ci = db.iterate(txn, atMost(serializer.apply(4)))) { + final Iterator> iter = ci.iterator(); + assertThat(deserializer.applyAsInt(iter.next().key()), is(8)); + assertThat(deserializer.applyAsInt(iter.next().key()), is(6)); + assertThat(deserializer.applyAsInt(iter.next().key()), is(4)); } } @@ -143,9 +170,24 @@ public void dbOpenMaxDatabases() { @Test public void dbiWithComparatorThreadSafety() { + doDbiWithComparatorThreadSafety( + env, PROXY_OPTIMAL::getComparator, TestUtils::bb, ByteBuffer::getInt); + } + + @Test + public void dbiWithComparatorThreadSafetyByteArray() { + doDbiWithComparatorThreadSafety( + envBa, PROXY_BA::getComparator, TestUtils::ba, TestUtils::fromBa); + } + + public void doDbiWithComparatorThreadSafety( + Env env, + Function> comparator, + IntFunction serializer, + ToIntFunction deserializer) { final DbiFlags[] flags = new DbiFlags[] {MDB_CREATE, MDB_INTEGERKEY}; - final Comparator c = PROXY_OPTIMAL.getComparator(flags); - final Dbi db = env.openDbi(DB_1, c, true, flags); + final Comparator c = comparator.apply(flags); + final Dbi db = env.openDbi(DB_1, c, true, flags); final List keys = range(0, 1_000).boxed().collect(toList()); @@ -155,25 +197,25 @@ public void dbiWithComparatorThreadSafety() { pool.submit( () -> { while (proceed.get()) { - try (Txn txn = env.txnRead()) { - db.get(txn, bb(50)); + try (Txn txn = env.txnRead()) { + db.get(txn, serializer.apply(50)); } } }); for (final Integer key : keys) { - try (Txn txn = env.txnWrite()) { - db.put(txn, bb(key), bb(3)); + try (Txn txn = env.txnWrite()) { + db.put(txn, serializer.apply(key), serializer.apply(3)); txn.commit(); } } - try (Txn txn = env.txnRead(); - CursorIterable ci = db.iterate(txn)) { - final Iterator> iter = ci.iterator(); + try (Txn txn = env.txnRead(); + CursorIterable ci = db.iterate(txn)) { + final Iterator> iter = ci.iterator(); final List result = new ArrayList<>(); while (iter.hasNext()) { - result.add(iter.next().key().getInt()); + result.add(deserializer.applyAsInt(iter.next().key())); } assertThat(result, Matchers.contains(keys.toArray(new Integer[0]))); diff --git a/src/test/java/org/lmdbjava/TestUtils.java b/src/test/java/org/lmdbjava/TestUtils.java index 42dcf052..fd070a31 100644 --- a/src/test/java/org/lmdbjava/TestUtils.java +++ b/src/test/java/org/lmdbjava/TestUtils.java @@ -41,6 +41,11 @@ static byte[] ba(final int value) { return b.byteArray(); } + static int fromBa(final byte[] ba) { + final MutableDirectBuffer b = new UnsafeBuffer(ba); + return b.getInt(0); + } + static ByteBuffer bb(final int value) { final ByteBuffer bb = allocateDirect(BYTES); bb.putInt(value).flip(); From 236b7c9b912e271bfd6a57c612d12ca641b91d13 Mon Sep 17 00:00:00 2001 From: LambdAurora Date: Wed, 9 Jul 2025 10:50:24 +0200 Subject: [PATCH 205/322] Use ByteBuffer for int serialization into bytes. This proves to be more reliable than Agreno's UnsafeBuffers. --- src/test/java/org/lmdbjava/DbiTest.java | 3 +-- src/test/java/org/lmdbjava/TestUtils.java | 9 ++++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 2e87830c..75c23931 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -62,7 +62,6 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.*; -import org.agrona.concurrent.UnsafeBuffer; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Before; @@ -381,7 +380,7 @@ public void putCommitGetByteArray() throws IOException { try (Txn txn = envBa.txnWrite()) { final byte[] found = db.get(txn, ba(5)); assertNotNull(found); - assertThat(new UnsafeBuffer(txn.val()).getInt(0), is(5)); + assertThat(fromBa(txn.val()), is(5)); } } } diff --git a/src/test/java/org/lmdbjava/TestUtils.java b/src/test/java/org/lmdbjava/TestUtils.java index fd070a31..c0203264 100644 --- a/src/test/java/org/lmdbjava/TestUtils.java +++ b/src/test/java/org/lmdbjava/TestUtils.java @@ -36,14 +36,13 @@ final class TestUtils { private TestUtils() {} static byte[] ba(final int value) { - final MutableDirectBuffer b = new UnsafeBuffer(new byte[4]); - b.putInt(0, value); - return b.byteArray(); + byte[] bytes = new byte[4]; + ByteBuffer.wrap(bytes).putInt(value); + return bytes; } static int fromBa(final byte[] ba) { - final MutableDirectBuffer b = new UnsafeBuffer(ba); - return b.getInt(0); + return ByteBuffer.wrap(ba).getInt(); } static ByteBuffer bb(final int value) { From 741b6f295c73cbc827d13811f43e8589adf2f7fb Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Fri, 24 Oct 2025 10:06:45 +0100 Subject: [PATCH 206/322] Add OpenLDAP-2.8.txt --- licences/OpenLDAP-2.8.txt | 47 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 licences/OpenLDAP-2.8.txt diff --git a/licences/OpenLDAP-2.8.txt b/licences/OpenLDAP-2.8.txt new file mode 100644 index 00000000..05ad7571 --- /dev/null +++ b/licences/OpenLDAP-2.8.txt @@ -0,0 +1,47 @@ +The OpenLDAP Public License + Version 2.8, 17 August 2003 + +Redistribution and use of this software and associated documentation +("Software"), with or without modification, are permitted provided +that the following conditions are met: + +1. Redistributions in source form must retain copyright statements + and notices, + +2. Redistributions in binary form must reproduce applicable copyright + statements and notices, this list of conditions, and the following + disclaimer in the documentation and/or other materials provided + with the distribution, and + +3. Redistributions must contain a verbatim copy of this document. + +The OpenLDAP Foundation may revise this license from time to time. +Each revision is distinguished by a version number. You may use +this Software under terms of this license revision or under the +terms of any subsequent revision of the license. + +THIS SOFTWARE IS PROVIDED BY THE OPENLDAP FOUNDATION AND ITS +CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +SHALL THE OPENLDAP FOUNDATION, ITS CONTRIBUTORS, OR THE AUTHOR(S) +OR OWNER(S) OF THE SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +The names of the authors and copyright holders must not be used in +advertising or otherwise to promote the sale, use or other dealing +in this Software without specific, written prior permission. Title +to copyright in this Software shall at all times remain with copyright +holders. + +OpenLDAP is a registered trademark of the OpenLDAP Foundation. + +Copyright 1999-2003 The OpenLDAP Foundation, Redwood City, +California, USA. All Rights Reserved. Permission to copy and +distribute verbatim copies of this document is granted. From 180b91f3f8afded358d8308544929c0697cfc77c Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Fri, 24 Oct 2025 10:07:50 +0100 Subject: [PATCH 207/322] Create Apache-2.0 --- licences/Apache-2.0 | 51 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 licences/Apache-2.0 diff --git a/licences/Apache-2.0 b/licences/Apache-2.0 new file mode 100644 index 00000000..6ca24c75 --- /dev/null +++ b/licences/Apache-2.0 @@ -0,0 +1,51 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of this License; and +You must cause any modified files to carry prominent notices stating that You changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. +You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS From 0f202f303db0b3f78132ebaaf764264bd37e4aa7 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Fri, 24 Oct 2025 10:15:34 +0100 Subject: [PATCH 208/322] Create NOTICE.md --- NOTICE.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 NOTICE.md diff --git a/NOTICE.md b/NOTICE.md new file mode 100644 index 00000000..d5727a9d --- /dev/null +++ b/NOTICE.md @@ -0,0 +1,17 @@ +Copyright © 2016-2025 The LmdbJava Open Source Project + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +--- + +This project distribution JAR includes a distribution of [LMDB](https://www.symas.com/mdb), which is licensed under the [OpenLDAP Public License](./licences/OpenLDAP-2.8.txt). From 856ab0e282736d9d94d8409d9d13f66c18e387ff Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Fri, 24 Oct 2025 10:37:39 +0100 Subject: [PATCH 209/322] Update NOTICE.md --- NOTICE.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/NOTICE.md b/NOTICE.md index d5727a9d..0145c1b3 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -14,4 +14,23 @@ limitations under the License. --- -This project distribution JAR includes a distribution of [LMDB](https://www.symas.com/mdb), which is licensed under the [OpenLDAP Public License](./licences/OpenLDAP-2.8.txt). +The LmdbJava project distribution JAR file includes a distribution of [LMDB](https://www.symas.com/mdb), which is licensed under the [OpenLDAP Public License](https://www.openldap.org/software/release/license.html). + +--- + +# Dependencies + +LmdbJava uses the following libraries. +All licenese files can also be found in the `licences` directory in the root of this repository. + +| Dependency | License | +|-------------|----------| +| `com.github.jnr:jnr-constants` | [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) | +| `com.github.jnr:jnr-ffi` | [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) | +| `com.google.guava:guava` | [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) | +| `com.jakewharton.byteunits:byteunits` | [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) | +| `io.netty:netty-buffer` | [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) | +| `junit:junit` | [Eclipse Public License 1.0](https://www.eclipse.org/org/documents/epl-v10.html) | +| `org.agrona:agrona` | [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0) | +| `org.hamcrest:hamcrest` | [BSD 3-Clause License](https://opensource.org/license/bsd-3-clause) | +| `org.mockito:mockito-inline` | [MIT License](https://opensource.org/license/mit) | From 3a8d27995c511f9b94073324a4a60616695ead0c Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 25 Oct 2025 15:02:22 +1100 Subject: [PATCH 210/322] Prevent license plugin from modifying license texts --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index b3012729..b270c4b0 100644 --- a/pom.xml +++ b/pom.xml @@ -108,6 +108,7 @@ LICENSE.txt **/*.md lmdb/** + licenses/** From 9d7b32eee7537b3807e29de84358dfa7eaa302eb Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 25 Oct 2025 15:03:13 +1100 Subject: [PATCH 211/322] Automatic POM formatting only --- pom.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index b270c4b0..454fc3d3 100644 --- a/pom.xml +++ b/pom.xml @@ -185,19 +185,19 @@ - + [${maven.enforcer.mvn},) [${maven.enforcer.java},) - - + + true - + @@ -352,7 +352,7 @@ Kristoffer Sjogren stoffe -at- gmail.com http://stoffe.deephacks.org/ - + +1 From 8ab46ee4636a11cfea019004021334e9c3c99e4a Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 25 Oct 2025 15:03:43 +1100 Subject: [PATCH 212/322] JaCoCo Maven Plugin update to 0.8.14 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 454fc3d3..3a71e33c 100644 --- a/pom.xml +++ b/pom.xml @@ -316,7 +316,7 @@ org.jacoco jacoco-maven-plugin - 0.8.12 + 0.8.14 config-jacoco-prepare-agent From 79c2382d5eaabee1ae818288fc0c065345fe0024 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 25 Oct 2025 15:04:35 +1100 Subject: [PATCH 213/322] Format Maven Plugin update to 2.29 (inc Java 25 workaround) --- pom.xml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 3a71e33c..0863fdf7 100644 --- a/pom.xml +++ b/pom.xml @@ -442,16 +442,17 @@ + com.spotify.fmt fmt-maven-plugin - 2.25 - - - - format - - - + 2.29 + + + com.google.googlejavaformat + google-java-format + 1.28.0 + + From 40192dbfef98c2f60dd90d3e2b4b6981971e8419 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 25 Oct 2025 15:07:56 +1100 Subject: [PATCH 214/322] Rename licences -> licenses for consistency This matches the existing LICENSE.TXT spelling and the spelling of the license we are using: https://httpd.apache.org/docs/trunk/license.html --- NOTICE.md | 2 +- {licences => licenses}/Apache-2.0 | 0 {licences => licenses}/OpenLDAP-2.8.txt | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename {licences => licenses}/Apache-2.0 (100%) rename {licences => licenses}/OpenLDAP-2.8.txt (100%) diff --git a/NOTICE.md b/NOTICE.md index 0145c1b3..77fad436 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -21,7 +21,7 @@ The LmdbJava project distribution JAR file includes a distribution of [LMDB](htt # Dependencies LmdbJava uses the following libraries. -All licenese files can also be found in the `licences` directory in the root of this repository. +All license files can also be found in the `licenses` directory in the root of this repository. | Dependency | License | |-------------|----------| diff --git a/licences/Apache-2.0 b/licenses/Apache-2.0 similarity index 100% rename from licences/Apache-2.0 rename to licenses/Apache-2.0 diff --git a/licences/OpenLDAP-2.8.txt b/licenses/OpenLDAP-2.8.txt similarity index 100% rename from licences/OpenLDAP-2.8.txt rename to licenses/OpenLDAP-2.8.txt From e598e21a0837fafc398747fa625de7b8cbfb4a36 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 25 Oct 2025 15:09:51 +1100 Subject: [PATCH 215/322] Migrate to Maven Central Portal Publisher API --- .github/workflows/maven.yml | 16 ++++++++-------- README.md | 2 +- pom.xml | 23 ++++++----------------- 3 files changed, 15 insertions(+), 26 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index b28e8481..a3311ce7 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -82,7 +82,7 @@ jobs: path: target/surefire-reports deploy: - name: Deploy to OSSRH + name: Deploy to Central Portal needs: [build, compatibility-checks] if: github.event_name == 'push' runs-on: ubuntu-latest @@ -98,9 +98,9 @@ jobs: # Java version 8 required due to https://github.com/lmdbjava/lmdbjava/issues/116 java-version: 8 cache: maven - server-id: ossrh - server-username: MAVEN_USERNAME - server-password: MAVEN_CENTRAL_TOKEN + server-id: central + server-username: MAVEN_CENTRAL_USERNAME + server-password: MAVEN_CENTRAL_PASSWORD gpg-private-key: ${{ secrets.gpg_private_key }} gpg-passphrase: MAVEN_GPG_PASSPHRASE @@ -111,15 +111,15 @@ jobs: run: ./cross-compile.sh - name: Publish Maven package - run: mvn -B -Possrh-deploy deploy -DskipTests + run: mvn -B -Pcentral-deploy deploy -DskipTests env: MAVEN_GPG_PASSPHRASE: ${{ secrets.gpg_passphrase }} - MAVEN_USERNAME: ${{ secrets.nexus_username }} - MAVEN_CENTRAL_TOKEN: ${{ secrets.nexus_password }} + MAVEN_CENTRAL_USERNAME: ${{ secrets.central_username }} + MAVEN_CENTRAL_PASSWORD: ${{ secrets.central_password }} - name: Debug settings.xml uses: actions/upload-artifact@v4 if: failure() with: name: settings.xml - path: $HOME/.m2/settings.xml + path: ~/.m2/settings.xml diff --git a/README.md b/README.md index 048e57bc..cde62cd8 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ * Low latency design (allocation-free; buffer pools; optional checks can be easily disabled in production etc) * Mature code (commenced in 2016) and used for heavy production workloads (eg > 500 TB of HFT data) * Actively maintained and with a "Zero Bug Policy" before every release (see [issues](https://github.com/lmdbjava/lmdbjava/issues)) -* Available from [Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.lmdbjava%22%20AND%20a%3A%22lmdbjava%22) and [OSS Sonatype Snapshots](https://oss.sonatype.org/content/repositories/snapshots/org/lmdbjava/lmdbjava) +* Available from [Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.lmdbjava%22%20AND%20a%3A%22lmdbjava%22) and [Central Portal Snapshots](https://central.sonatype.com/repository/maven-snapshots/org/lmdbjava/lmdbjava) * [Continuous integration](https://github.com/lmdbjava/lmdbjava/actions) testing on Linux, Windows and macOS with Java 8, 11, 17 and 21 ### Performance diff --git a/pom.xml b/pom.xml index 0863fdf7..12af842d 100644 --- a/pom.xml +++ b/pom.xml @@ -378,16 +378,6 @@ GitHub Actions https://github.com/lmdbjava/lmdbjava/actions - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - add-opens-on-java-9-and-above @@ -458,7 +448,7 @@ - ossrh-deploy + central-deploy @@ -482,14 +472,13 @@ - org.sonatype.plugins - nexus-staging-maven-plugin - 1.7.0 + org.sonatype.central + central-publishing-maven-plugin + 0.9.0 true - ossrh - https://oss.sonatype.org/ - true + central + true From 3a4b9acf010ac3a992f310458034e5801ae86df6 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 25 Oct 2025 15:43:31 +1100 Subject: [PATCH 216/322] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cde62cd8..af13364d 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ updated (eg following a new official release of the upstream LMDB library). If you do not wish to install Zig and/or use an operating system which cannot easily execute the `cross-compile.sh` script, you can download the compiled LMDB native library for your platform from a location of your choice and set the -`lmdbjava.native.lib` system property to the resulting file system system +`lmdbjava.native.lib` system property to the resulting file system location. Possible sources of a compiled LMDB native library include operating system package managers, running `cross-compile.sh` on a supported system, or copying it from the `org/lmdbjava` directory of any recent, officially released From 1ea59025e61702c40cbd445b10d458e5889881b0 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 25 Oct 2025 15:44:58 +1100 Subject: [PATCH 217/322] Add Java 25 testing --- .github/workflows/maven.yml | 4 ++-- README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index a3311ce7..3b97a0c6 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -19,7 +19,7 @@ jobs: uses: actions/setup-java@v3 with: distribution: zulu - java-version: 21 + java-version: 25 cache: maven - name: Install Zig @@ -52,7 +52,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - java: [8, 11, 17, 21] + java: [8, 11, 17, 21, 25] steps: - name: Check out Git repository diff --git a/README.md b/README.md index af13364d..99ccf54f 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ * Mature code (commenced in 2016) and used for heavy production workloads (eg > 500 TB of HFT data) * Actively maintained and with a "Zero Bug Policy" before every release (see [issues](https://github.com/lmdbjava/lmdbjava/issues)) * Available from [Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.lmdbjava%22%20AND%20a%3A%22lmdbjava%22) and [Central Portal Snapshots](https://central.sonatype.com/repository/maven-snapshots/org/lmdbjava/lmdbjava) -* [Continuous integration](https://github.com/lmdbjava/lmdbjava/actions) testing on Linux, Windows and macOS with Java 8, 11, 17 and 21 +* [Continuous integration](https://github.com/lmdbjava/lmdbjava/actions) testing on Linux, Windows and macOS with Java 8, 11, 17, 21 and 25 ### Performance From c91893a876e7edaaea503f689066cf4a6e96f156 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 25 Oct 2025 19:23:06 +1100 Subject: [PATCH 218/322] Fix transient pointer deallocation in ByteArrayProxy JNR-FFI's TransientNativeMemory was prematurely deallocating off-heap memory before LMDB syscalls completed. Return transient pointers from BufferProxy.in() and use reachability fencing at all call sites to prevent premature GC. BufferProxy and KeyVal were also simplified to remove pointer address passing, given this is inexpensively available from the Pointer.address() accessor (which is backed by a final field). This is an API breaking change if external users implemented their own BufferProxy, however it is considered unlikely many (if any) users would have ever done this. Fixes #252 --- src/main/java/org/lmdbjava/BufferProxy.java | 11 ++++--- .../java/org/lmdbjava/ByteArrayProxy.java | 8 +++-- src/main/java/org/lmdbjava/ByteBufProxy.java | 11 +++++-- .../java/org/lmdbjava/ByteBufferProxy.java | 22 +++++++++----- src/main/java/org/lmdbjava/Cursor.java | 29 ++++++++++++++----- src/main/java/org/lmdbjava/Dbi.java | 29 ++++++++++++------- .../java/org/lmdbjava/DirectBufferProxy.java | 12 +++++--- src/main/java/org/lmdbjava/KeyVal.java | 22 ++++++-------- .../org/lmdbjava/ByteBufferProxyTest.java | 4 +-- 9 files changed, 91 insertions(+), 57 deletions(-) diff --git a/src/main/java/org/lmdbjava/BufferProxy.java b/src/main/java/org/lmdbjava/BufferProxy.java index e66031d2..96d6aa1e 100644 --- a/src/main/java/org/lmdbjava/BufferProxy.java +++ b/src/main/java/org/lmdbjava/BufferProxy.java @@ -107,9 +107,9 @@ protected Comparator getComparator(DbiFlags... flags) { * * @param buffer the buffer to write to MDB_val * @param ptr the pointer to the MDB_val - * @param ptrAddr the address of the MDB_val pointer + * @return a transient pointer that must be kept alive, or null if none */ - protected abstract void in(T buffer, Pointer ptr, long ptrAddr); + protected abstract Pointer in(T buffer, Pointer ptr); /** * Called when the MDB_val should be set to reflect the passed buffer. @@ -117,9 +117,9 @@ protected Comparator getComparator(DbiFlags... flags) { * @param buffer the buffer to write to MDB_val * @param size the buffer size to write to MDB_val * @param ptr the pointer to the MDB_val - * @param ptrAddr the address of the MDB_val pointer + * @return a transient pointer that must be kept alive, or null if none */ - protected abstract void in(T buffer, int size, Pointer ptr, long ptrAddr); + protected abstract Pointer in(T buffer, int size, Pointer ptr); /** * Called when the MDB_val may have changed and the passed buffer should be modified @@ -127,10 +127,9 @@ protected Comparator getComparator(DbiFlags... flags) { * * @param buffer the buffer to write to MDB_val * @param ptr the pointer to the MDB_val - * @param ptrAddr the address of the MDB_val pointer * @return the buffer for MDB_val */ - protected abstract T out(T buffer, Pointer ptr, long ptrAddr); + protected abstract T out(T buffer, Pointer ptr); /** * Create a new {@link KeyVal} to hold pointers for this buffer proxy. diff --git a/src/main/java/org/lmdbjava/ByteArrayProxy.java b/src/main/java/org/lmdbjava/ByteArrayProxy.java index 4a22ab83..853521e0 100644 --- a/src/main/java/org/lmdbjava/ByteArrayProxy.java +++ b/src/main/java/org/lmdbjava/ByteArrayProxy.java @@ -114,20 +114,22 @@ protected Comparator getUnsignedComparator() { } @Override - protected void in(final byte[] buffer, final Pointer ptr, final long ptrAddr) { + protected Pointer in(final byte[] buffer, final Pointer ptr) { final Pointer pointer = MEM_MGR.allocateDirect(buffer.length); pointer.put(0, buffer, 0, buffer.length); ptr.putLong(STRUCT_FIELD_OFFSET_SIZE, buffer.length); ptr.putAddress(STRUCT_FIELD_OFFSET_DATA, pointer.address()); + return pointer; } @Override - protected void in(final byte[] buffer, final int size, final Pointer ptr, final long ptrAddr) { + protected Pointer in(final byte[] buffer, final int size, final Pointer ptr) { // cannot reserve for byte arrays + return null; } @Override - protected byte[] out(final byte[] buffer, final Pointer ptr, final long ptrAddr) { + protected byte[] out(final byte[] buffer, final Pointer ptr) { final long addr = ptr.getAddress(STRUCT_FIELD_OFFSET_DATA); final int size = (int) ptr.getLong(STRUCT_FIELD_OFFSET_SIZE); final Pointer pointer = MEM_MGR.newPointer(addr, size); diff --git a/src/main/java/org/lmdbjava/ByteBufProxy.java b/src/main/java/org/lmdbjava/ByteBufProxy.java index cac5b97b..2866e874 100644 --- a/src/main/java/org/lmdbjava/ByteBufProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufProxy.java @@ -136,21 +136,26 @@ protected byte[] getBytes(final ByteBuf buffer) { } @Override - protected void in(final ByteBuf buffer, final Pointer ptr, final long ptrAddr) { + protected Pointer in(final ByteBuf buffer, final Pointer ptr) { + final long ptrAddr = ptr.address(); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, buffer.writerIndex() - buffer.readerIndex()); UNSAFE.putLong( ptrAddr + STRUCT_FIELD_OFFSET_DATA, buffer.memoryAddress() + buffer.readerIndex()); + return null; } @Override - protected void in(final ByteBuf buffer, final int size, final Pointer ptr, final long ptrAddr) { + protected Pointer in(final ByteBuf buffer, final int size, final Pointer ptr) { + final long ptrAddr = ptr.address(); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, size); UNSAFE.putLong( ptrAddr + STRUCT_FIELD_OFFSET_DATA, buffer.memoryAddress() + buffer.readerIndex()); + return null; } @Override - protected ByteBuf out(final ByteBuf buffer, final Pointer ptr, final long ptrAddr) { + protected ByteBuf out(final ByteBuf buffer, final Pointer ptr) { + final long ptrAddr = ptr.address(); final long addr = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA); final long size = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE); UNSAFE.putLong(buffer, addressOffset, addr); diff --git a/src/main/java/org/lmdbjava/ByteBufferProxy.java b/src/main/java/org/lmdbjava/ByteBufferProxy.java index 2b7cdf0d..9f30f576 100644 --- a/src/main/java/org/lmdbjava/ByteBufferProxy.java +++ b/src/main/java/org/lmdbjava/ByteBufferProxy.java @@ -221,20 +221,22 @@ private static final class ReflectiveProxy extends AbstractByteBufferProxy { } @Override - protected void in(final ByteBuffer buffer, final Pointer ptr, final long ptrAddr) { + protected Pointer in(final ByteBuffer buffer, final Pointer ptr) { ptr.putAddress(STRUCT_FIELD_OFFSET_DATA, address(buffer)); ptr.putLong(STRUCT_FIELD_OFFSET_SIZE, buffer.remaining()); + return null; } @Override - protected void in( - final ByteBuffer buffer, final int size, final Pointer ptr, final long ptrAddr) { + protected Pointer in(final ByteBuffer buffer, final int size, final Pointer ptr) { ptr.putLong(STRUCT_FIELD_OFFSET_SIZE, size); ptr.putAddress(STRUCT_FIELD_OFFSET_DATA, address(buffer)); + return null; } @Override - protected ByteBuffer out(final ByteBuffer buffer, final Pointer ptr, final long ptrAddr) { + protected ByteBuffer out(final ByteBuffer buffer, final Pointer ptr) { + final long ptrAddr = ptr.address(); final long addr = ptr.getAddress(STRUCT_FIELD_OFFSET_DATA); final long size = ptr.getLong(STRUCT_FIELD_OFFSET_SIZE); try { @@ -269,20 +271,24 @@ private static final class UnsafeProxy extends AbstractByteBufferProxy { } @Override - protected void in(final ByteBuffer buffer, final Pointer ptr, final long ptrAddr) { + protected Pointer in(final ByteBuffer buffer, final Pointer ptr) { + final long ptrAddr = ptr.address(); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, buffer.remaining()); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA, address(buffer)); + return null; } @Override - protected void in( - final ByteBuffer buffer, final int size, final Pointer ptr, final long ptrAddr) { + protected Pointer in(final ByteBuffer buffer, final int size, final Pointer ptr) { + final long ptrAddr = ptr.address(); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, size); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA, address(buffer)); + return null; } @Override - protected ByteBuffer out(final ByteBuffer buffer, final Pointer ptr, final long ptrAddr) { + protected ByteBuffer out(final ByteBuffer buffer, final Pointer ptr) { + final long ptrAddr = ptr.address(); final long addr = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA); final long size = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE); UNSAFE.putLong(buffer, ADDRESS_OFFSET, addr); diff --git a/src/main/java/org/lmdbjava/Cursor.java b/src/main/java/org/lmdbjava/Cursor.java index d49a9bed..f7fcbc41 100644 --- a/src/main/java/org/lmdbjava/Cursor.java +++ b/src/main/java/org/lmdbjava/Cursor.java @@ -141,8 +141,8 @@ public boolean get(final T key, final T data, final SeekOp op) { checkNotClosed(); txn.checkReady(); } - kv.keyIn(key); - kv.valIn(data); + final Pointer transientKey = kv.keyIn(key); + final Pointer transientVal = kv.valIn(data); final int rc = LIB.mdb_cursor_get(ptrCursor, kv.pointerKey(), kv.pointerVal(), op.getCode()); @@ -153,6 +153,10 @@ public boolean get(final T key, final T data, final SeekOp op) { checkRc(rc); kv.keyOut(); kv.valOut(); + ReferenceUtil.reachabilityFence0(transientKey); + ReferenceUtil.reachabilityFence0(transientVal); + ReferenceUtil.reachabilityFence0(kv.key()); + ReferenceUtil.reachabilityFence0(kv.val()); ReferenceUtil.reachabilityFence0(key); return true; } @@ -172,7 +176,7 @@ public boolean get(final T key, final GetOp op) { checkNotClosed(); txn.checkReady(); } - kv.keyIn(key); + final Pointer transientKey = kv.keyIn(key); final int rc = LIB.mdb_cursor_get(ptrCursor, kv.pointerKey(), kv.pointerVal(), op.getCode()); @@ -183,6 +187,9 @@ public boolean get(final T key, final GetOp op) { checkRc(rc); kv.keyOut(); kv.valOut(); + ReferenceUtil.reachabilityFence0(transientKey); + ReferenceUtil.reachabilityFence0(kv.key()); + ReferenceUtil.reachabilityFence0(kv.val()); ReferenceUtil.reachabilityFence0(key); return true; } @@ -243,8 +250,8 @@ public boolean put(final T key, final T val, final PutFlags... op) { txn.checkReady(); txn.checkWritesAllowed(); } - kv.keyIn(key); - kv.valIn(val); + final Pointer transientKey = kv.keyIn(key); + final Pointer transientVal = kv.valIn(val); final int mask = mask(true, op); final int rc = LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), mask); if (rc == MDB_KEYEXIST) { @@ -256,6 +263,8 @@ public boolean put(final T key, final T val, final PutFlags... op) { return false; } checkRc(rc); + ReferenceUtil.reachabilityFence0(transientKey); + ReferenceUtil.reachabilityFence0(transientVal); ReferenceUtil.reachabilityFence0(key); ReferenceUtil.reachabilityFence0(val); return true; @@ -287,10 +296,12 @@ public void putMultiple(final T key, final T val, final int elements, final PutF if (SHOULD_CHECK && !isSet(mask, MDB_MULTIPLE)) { throw new IllegalArgumentException("Must set " + MDB_MULTIPLE + " flag"); } - txn.kv().keyIn(key); + final Pointer transientKey = txn.kv().keyIn(key); final Pointer dataPtr = txn.kv().valInMulti(val, elements); final int rc = LIB.mdb_cursor_put(ptrCursor, txn.kv().pointerKey(), dataPtr, mask); checkRc(rc); + ReferenceUtil.reachabilityFence0(transientKey); + ReferenceUtil.reachabilityFence0(dataPtr); ReferenceUtil.reachabilityFence0(key); ReferenceUtil.reachabilityFence0(val); } @@ -340,11 +351,13 @@ public T reserve(final T key, final int size, final PutFlags... op) { txn.checkReady(); txn.checkWritesAllowed(); } - kv.keyIn(key); - kv.valIn(size); + final Pointer transientKey = kv.keyIn(key); + final Pointer transientVal = kv.valIn(size); final int flags = mask(true, op) | MDB_RESERVE.getMask(); checkRc(LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), flags)); kv.valOut(); + ReferenceUtil.reachabilityFence0(transientKey); + ReferenceUtil.reachabilityFence0(transientVal); ReferenceUtil.reachabilityFence0(key); return val(); } diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index ad1bb5a7..2fa7f185 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -83,8 +83,8 @@ public final class Dbi { (keyA, keyB) -> { final T compKeyA = proxy.allocate(); final T compKeyB = proxy.allocate(); - proxy.out(compKeyA, keyA, keyA.address()); - proxy.out(compKeyB, keyB, keyB.address()); + proxy.out(compKeyA, keyA); + proxy.out(compKeyB, keyB); final int result = this.comparator.compare(compKeyA, compKeyB); proxy.deallocate(compKeyA); proxy.deallocate(compKeyB); @@ -161,11 +161,12 @@ public boolean delete(final Txn txn, final T key, final T val) { txn.checkWritesAllowed(); } - txn.kv().keyIn(key); + final Pointer transientKey = txn.kv().keyIn(key); Pointer data = null; + Pointer transientVal = null; if (val != null) { - txn.kv().valIn(val); + transientVal = txn.kv().valIn(val); data = txn.kv().pointerVal(); } final int rc = LIB.mdb_del(txn.pointer(), ptr, txn.kv().pointerKey(), data); @@ -173,6 +174,8 @@ public boolean delete(final Txn txn, final T key, final T val) { return false; } checkRc(rc); + ReferenceUtil.reachabilityFence0(transientKey); + ReferenceUtil.reachabilityFence0(transientVal); ReferenceUtil.reachabilityFence0(key); ReferenceUtil.reachabilityFence0(val); return true; @@ -232,14 +235,16 @@ public T get(final Txn txn, final T key) { env.checkNotClosed(); txn.checkReady(); } - txn.kv().keyIn(key); + final Pointer transientKey = txn.kv().keyIn(key); final int rc = LIB.mdb_get(txn.pointer(), ptr, txn.kv().pointerKey(), txn.kv().pointerVal()); if (rc == MDB_NOTFOUND) { return null; } checkRc(rc); + final T result = txn.kv().valOut(); // marked as out in LMDB C docs + ReferenceUtil.reachabilityFence0(transientKey); ReferenceUtil.reachabilityFence0(key); - return txn.kv().valOut(); // marked as out in LMDB C docs + return result; } /** @@ -366,8 +371,8 @@ public boolean put(final Txn txn, final T key, final T val, final PutFlags... txn.checkReady(); txn.checkWritesAllowed(); } - txn.kv().keyIn(key); - txn.kv().valIn(val); + final Pointer transientKey = txn.kv().keyIn(key); + final Pointer transientVal = txn.kv().valIn(val); final int mask = mask(true, flags); final int rc = LIB.mdb_put(txn.pointer(), ptr, txn.kv().pointerKey(), txn.kv().pointerVal(), mask); @@ -380,6 +385,8 @@ public boolean put(final Txn txn, final T key, final T val, final PutFlags... return false; } checkRc(rc); + ReferenceUtil.reachabilityFence0(transientKey); + ReferenceUtil.reachabilityFence0(transientVal); ReferenceUtil.reachabilityFence0(key); ReferenceUtil.reachabilityFence0(val); return true; @@ -408,11 +415,13 @@ public T reserve(final Txn txn, final T key, final int size, final PutFlags.. txn.checkReady(); txn.checkWritesAllowed(); } - txn.kv().keyIn(key); - txn.kv().valIn(size); + final Pointer transientKey = txn.kv().keyIn(key); + final Pointer transientVal = txn.kv().valIn(size); final int flags = mask(true, op) | MDB_RESERVE.getMask(); checkRc(LIB.mdb_put(txn.pointer(), ptr, txn.kv().pointerKey(), txn.kv().pointerVal(), flags)); txn.kv().valOut(); // marked as in,out in LMDB C docs + ReferenceUtil.reachabilityFence0(transientKey); + ReferenceUtil.reachabilityFence0(transientVal); ReferenceUtil.reachabilityFence0(key); return txn.val(); } diff --git a/src/main/java/org/lmdbjava/DirectBufferProxy.java b/src/main/java/org/lmdbjava/DirectBufferProxy.java index 156e60e9..17b59b3d 100644 --- a/src/main/java/org/lmdbjava/DirectBufferProxy.java +++ b/src/main/java/org/lmdbjava/DirectBufferProxy.java @@ -134,23 +134,27 @@ protected byte[] getBytes(final DirectBuffer buffer) { } @Override - protected void in(final DirectBuffer buffer, final Pointer ptr, final long ptrAddr) { + protected Pointer in(final DirectBuffer buffer, final Pointer ptr) { + final long ptrAddr = ptr.address(); final long addr = buffer.addressOffset(); final long size = buffer.capacity(); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA, addr); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, size); + return null; } @Override - protected void in( - final DirectBuffer buffer, final int size, final Pointer ptr, final long ptrAddr) { + protected Pointer in(final DirectBuffer buffer, final int size, final Pointer ptr) { + final long ptrAddr = ptr.address(); final long addr = buffer.addressOffset(); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA, addr); UNSAFE.putLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE, size); + return null; } @Override - protected DirectBuffer out(final DirectBuffer buffer, final Pointer ptr, final long ptrAddr) { + protected DirectBuffer out(final DirectBuffer buffer, final Pointer ptr) { + final long ptrAddr = ptr.address(); final long addr = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_DATA); final long size = UNSAFE.getLong(ptrAddr + STRUCT_FIELD_OFFSET_SIZE); buffer.wrap(addr, (int) size); diff --git a/src/main/java/org/lmdbjava/KeyVal.java b/src/main/java/org/lmdbjava/KeyVal.java index c6f2aae5..e2d2529f 100644 --- a/src/main/java/org/lmdbjava/KeyVal.java +++ b/src/main/java/org/lmdbjava/KeyVal.java @@ -36,9 +36,7 @@ final class KeyVal implements AutoCloseable { private final BufferProxy proxy; private final Pointer ptrArray; private final Pointer ptrKey; - private final long ptrKeyAddr; private final Pointer ptrVal; - private final long ptrValAddr; private T v; KeyVal(final BufferProxy proxy) { @@ -47,10 +45,8 @@ final class KeyVal implements AutoCloseable { this.k = proxy.allocate(); this.v = proxy.allocate(); ptrKey = MEM_MGR.allocateTemporary(MDB_VAL_STRUCT_SIZE, false); - ptrKeyAddr = ptrKey.address(); ptrArray = MEM_MGR.allocateTemporary(MDB_VAL_STRUCT_SIZE * 2, false); ptrVal = ptrArray.slice(0, MDB_VAL_STRUCT_SIZE); - ptrValAddr = ptrVal.address(); } @Override @@ -67,12 +63,12 @@ T key() { return k; } - void keyIn(final T key) { - proxy.in(key, ptrKey, ptrKeyAddr); + Pointer keyIn(final T key) { + return proxy.in(key, ptrKey); } T keyOut() { - k = proxy.out(k, ptrKey, ptrKeyAddr); + k = proxy.out(k, ptrKey); return k; } @@ -88,12 +84,12 @@ T val() { return v; } - void valIn(final T val) { - proxy.in(val, ptrVal, ptrValAddr); + Pointer valIn(final T val) { + return proxy.in(val, ptrVal); } - void valIn(final int size) { - proxy.in(v, size, ptrVal, ptrValAddr); + Pointer valIn(final int size) { + return proxy.in(v, size, ptrVal); } /** @@ -116,7 +112,7 @@ void valIn(final int size) { Pointer valInMulti(final T val, final int elements) { final long ptrVal2SizeOff = MDB_VAL_STRUCT_SIZE + STRUCT_FIELD_OFFSET_SIZE; ptrArray.putLong(ptrVal2SizeOff, elements); // ptrVal2.size - proxy.in(val, ptrVal, ptrValAddr); // ptrVal1.data + proxy.in(val, ptrVal); // ptrVal1.data final long totalBufferSize = ptrVal.getLong(STRUCT_FIELD_OFFSET_SIZE); final long elemSize = totalBufferSize / elements; ptrVal.putLong(STRUCT_FIELD_OFFSET_SIZE, elemSize); // ptrVal1.size @@ -125,7 +121,7 @@ Pointer valInMulti(final T val, final int elements) { } T valOut() { - v = proxy.out(v, ptrVal, ptrValAddr); + v = proxy.out(v, ptrVal); return v; } } diff --git a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java index 8044b9e8..b68f39ef 100644 --- a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java +++ b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java @@ -139,10 +139,10 @@ private void checkInOut(final BufferProxy v) { b.position(BYTES); // skip 1 final Pointer p = MEM_MGR.allocateTemporary(MDB_VAL_STRUCT_SIZE, false); - v.in(b, p, p.address()); + v.in(b, p); final ByteBuffer bb = allocateDirect(1); - v.out(bb, p, p.address()); + v.out(bb, p); assertThat(bb.getInt(), is(2)); assertThat(bb.getInt(), is(3)); From a510be752f3d3546cd9fd7d016a374306bec1997 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sat, 25 Oct 2025 19:37:00 +1100 Subject: [PATCH 219/322] Fix JavaDoc link --- src/main/java/org/lmdbjava/BufferProxy.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/org/lmdbjava/BufferProxy.java b/src/main/java/org/lmdbjava/BufferProxy.java index 96d6aa1e..d4503731 100644 --- a/src/main/java/org/lmdbjava/BufferProxy.java +++ b/src/main/java/org/lmdbjava/BufferProxy.java @@ -48,8 +48,7 @@ public abstract class BufferProxy { protected BufferProxy() {} /** - * Allocate a new buffer suitable for passing to {@link #out(java.lang.Object, jnr.ffi.Pointer, - * long)}. + * Allocate a new buffer suitable for passing to {@link #out(java.lang.Object, jnr.ffi.Pointer)}. * * @return a buffer for passing to the out method */ From a892768db1fedb5247e5ac8cc0346a8481f284ee Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sun, 26 Oct 2025 09:27:28 +1100 Subject: [PATCH 220/322] Update GitHub Actions versions and zulu -> temurin JDK --- .github/workflows/maven.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 3b97a0c6..ee5726ce 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -13,17 +13,17 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v3 + uses: actions/checkout@v5 - name: Set up Java and Maven - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: - distribution: zulu + distribution: temurin java-version: 25 cache: maven - name: Install Zig - uses: goto-bus-stop/setup-zig@v2 + uses: mlugg/setup-zig@v2 - name: Cross compile using Zig run: ./cross-compile.sh @@ -56,12 +56,12 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v3 + uses: actions/checkout@v5 - name: Set up Java and Maven - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: - distribution: zulu + distribution: temurin java-version: ${{ matrix.java }} cache: maven @@ -89,12 +89,12 @@ jobs: steps: - name: Check out Git repository - uses: actions/checkout@v3 + uses: actions/checkout@v5 - name: Set up Java and Maven - uses: actions/setup-java@v3 + uses: actions/setup-java@v5 with: - distribution: zulu + distribution: temurin # Java version 8 required due to https://github.com/lmdbjava/lmdbjava/issues/116 java-version: 8 cache: maven @@ -105,7 +105,7 @@ jobs: gpg-passphrase: MAVEN_GPG_PASSPHRASE - name: Install Zig - uses: goto-bus-stop/setup-zig@v2 + uses: mlugg/setup-zig@v2 - name: Cross compile using Zig run: ./cross-compile.sh From 60f5f17b54416495447ad8ee98c529ac8554dcd5 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sun, 26 Oct 2025 09:33:42 +1100 Subject: [PATCH 221/322] Switch GitHub Actions back to zulu (needed for Java 8) --- .github/workflows/maven.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index ee5726ce..372c0c88 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -18,7 +18,7 @@ jobs: - name: Set up Java and Maven uses: actions/setup-java@v5 with: - distribution: temurin + distribution: zulu java-version: 25 cache: maven @@ -61,7 +61,7 @@ jobs: - name: Set up Java and Maven uses: actions/setup-java@v5 with: - distribution: temurin + distribution: zulu java-version: ${{ matrix.java }} cache: maven @@ -94,7 +94,7 @@ jobs: - name: Set up Java and Maven uses: actions/setup-java@v5 with: - distribution: temurin + distribution: zulu # Java version 8 required due to https://github.com/lmdbjava/lmdbjava/issues/116 java-version: 8 cache: maven From d109ee43b9a7e88f744223e314d5dec717a1abee Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sun, 26 Oct 2025 09:47:37 +1100 Subject: [PATCH 222/322] POM to use property interpolation and version plugin --- .gitignore | 1 + CONTRIBUTING.md | 8 ++- pom.xml | 126 +++++++++++++++++++++++++++++++++++------------- 3 files changed, 100 insertions(+), 35 deletions(-) diff --git a/.gitignore b/.gitignore index 0c771329..f46b8b6f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ gpg-sign.json mvn-sync.json secrets.tar lmdb +pom.xml.versionsBackup diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c37bf055..09448c03 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,13 +6,19 @@ We welcome patches and pull requests to improve LmdbJava. This will run: * Tests -* Initial Test Coverage * Source Code Formatting * License Header Management `mvn clean verify` is also run by CI, but it's quicker and easier to run before submitting. +### Version Management + +Update all dependency and plugin versions: +```bash +mvn versions:update-properties +``` + ### Releasing GitHub Actions will perform an official release whenever a developer executes diff --git a/pom.xml b/pom.xml index 12af842d..f2a44fa3 100644 --- a/pom.xml +++ b/pom.xml @@ -25,47 +25,80 @@ LmdbJava Low latency Java API for the ultra-fast, embedded Symas Lightning Database (LMDB) + + 1.22.0 + 3.2.1 + 0.9.1 + 0.9.0 + 2.29 + 1.28.0 + 33.5.0-jre + 2.2 + 0.8.14 + 0.10.4 + 2.2.18 + 4.13.2 + 4.6 + 3.5.0 + 3.14.1 + 3.9.0 + 3.1.4 + 3.6.2 + 3.2.7 + 3.1.4 + 3.4.2 + 3.12.0 + 3.1.1 + 3.3.1 + 2.2.1 + 3.21.0 + 3.3.1 + 3.5.4 yyyy-MM-dd'T'HH:mm:ss'Z' 1.8 1.8 1.8 3.5.4 + 4.11.0 + 4.2.7.Final UTF-8 + 4.0.0 false + 2.19.1 com.github.jnr jnr-constants - 0.10.4 + ${jnr-constants.version} com.github.jnr jnr-ffi - 2.2.17 + ${jnr-ffi.version} com.google.guava guava - 33.4.0-jre + ${guava.version} test com.jakewharton.byteunits byteunits - 0.9.1 + ${byteunits.version} test io.netty netty-buffer - 4.1.118.Final + ${netty-buffer.version} true junit junit - 4.13.2 + ${junit.version} test @@ -77,20 +110,19 @@ org.agrona agrona - - 1.22.0 + ${agrona.version} true org.hamcrest hamcrest - 2.2 + ${hamcrest.version} test org.mockito mockito-inline - 4.11.0 + ${mockito.version} test @@ -99,7 +131,7 @@ com.mycila license-maven-plugin - 4.6 + ${license-maven-plugin.version} @@ -118,7 +150,7 @@ com.mycila license-maven-plugin-git - 4.6 + ${license-maven-plugin.version}
@@ -137,7 +169,7 @@ org.apache.maven.plugins maven-clean-plugin - 3.4.0 + ${maven-clean-plugin.version} false @@ -145,12 +177,12 @@ org.apache.maven.plugins maven-compiler-plugin - 3.13.0 + ${maven-compiler-plugin.version} org.apache.maven.plugins maven-dependency-plugin - 3.8.1 + ${maven-dependency-plugin.version} @@ -171,12 +203,12 @@ org.apache.maven.plugins maven-deploy-plugin - 3.1.3 + ${maven-deploy-plugin.version} org.apache.maven.plugins maven-enforcer-plugin - 3.5.0 + ${maven-enforcer-plugin.version} config-enforcer @@ -206,12 +238,12 @@ org.apache.maven.plugins maven-install-plugin - 3.1.3 + ${maven-install-plugin.version} org.apache.maven.plugins maven-jar-plugin - 3.4.2 + ${maven-jar-plugin.version} @@ -226,7 +258,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.11.1 + ${maven-javadoc-plugin.version} attach-javadocs @@ -239,7 +271,7 @@ org.apache.maven.plugins maven-release-plugin - 3.1.1 + ${maven-release-plugin.version} clean clean @@ -250,12 +282,12 @@ org.apache.maven.plugins maven-resources-plugin - 3.3.1 + ${maven-resources-plugin.version} org.apache.maven.plugins maven-site-plugin - 3.21.0 + ${maven-site-plugin.version} true @@ -263,7 +295,7 @@ org.apache.maven.plugins maven-source-plugin - 3.3.1 + ${maven-source-plugin.version} attach-sources @@ -276,12 +308,12 @@ org.apache.maven.plugins maven-surefire-plugin - 3.5.2 + ${maven-surefire-plugin.version} org.codehaus.mojo buildnumber-maven-plugin - 3.2.1 + ${buildnumber-maven-plugin.version} false false @@ -296,7 +328,7 @@ org.apache.maven.scm maven-scm-provider-jgit - 2.1.0 + ${maven-scm-provider-jgit.version} @@ -311,12 +343,38 @@ org.codehaus.mojo versions-maven-plugin - 2.18.0 + ${versions-maven-plugin.version} + + false + false + false + agrona.version + + + + regex + .*-alpha.* + + + regex + .*-beta.* + + + regex + .*-M.* + + + regex + .*-RC.* + + + + org.jacoco jacoco-maven-plugin - 0.8.14 + ${jacoco-maven-plugin.version} config-jacoco-prepare-agent @@ -408,7 +466,7 @@ com.github.ekryd.sortpom sortpom-maven-plugin - 4.0.0 + ${sortpom-maven-plugin.version} ${project.basedir}/pom.xml ${project.build.sourceEncoding} @@ -435,12 +493,12 @@ com.spotify.fmt fmt-maven-plugin - 2.29 + ${fmt-maven-plugin.version} com.google.googlejavaformat google-java-format - 1.28.0 + ${google-java-format.version} @@ -454,7 +512,7 @@ org.apache.maven.plugins maven-gpg-plugin - 3.2.7 + ${maven-gpg-plugin.version} sign-artifacts @@ -474,7 +532,7 @@ org.sonatype.central central-publishing-maven-plugin - 0.9.0 + ${central-publishing-maven-plugin.version} true central From 0b741e8a72febcf37b1d3debc2a91aa3b2782ed9 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sun, 26 Oct 2025 10:01:55 +1100 Subject: [PATCH 223/322] Pin Netty at 4.1.118.Final for now --- pom.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index f2a44fa3..d59d1a1d 100644 --- a/pom.xml +++ b/pom.xml @@ -60,7 +60,8 @@ 1.8 3.5.4 4.11.0 - 4.2.7.Final + + 4.1.118.Final UTF-8 4.0.0 false @@ -348,7 +349,7 @@ false false false - agrona.version + agrona.version,netty-buffer.version From 58a489f84ad5651caacc79c2019372535f085343 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Sun, 26 Oct 2025 20:12:13 +0000 Subject: [PATCH 224/322] Fix accidental change to import order --- src/main/java/org/lmdbjava/Dbi.java | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index f4d061f7..6b6eb266 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -15,17 +15,6 @@ */ package org.lmdbjava; -import jnr.ffi.Pointer; -import jnr.ffi.byref.IntByReference; -import jnr.ffi.byref.PointerByReference; -import org.lmdbjava.Library.ComparatorCallback; -import org.lmdbjava.Library.MDB_stat; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; - import static java.util.Objects.requireNonNull; import static jnr.ffi.Memory.allocateDirect; import static jnr.ffi.NativeType.ADDRESS; @@ -42,6 +31,16 @@ import static org.lmdbjava.PutFlags.MDB_RESERVE; import static org.lmdbjava.ResultCodeMapper.checkRc; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import jnr.ffi.Pointer; +import jnr.ffi.byref.IntByReference; +import jnr.ffi.byref.PointerByReference; +import org.lmdbjava.Library.ComparatorCallback; +import org.lmdbjava.Library.MDB_stat; + /** * LMDB Database. * From bfbf223031191e0bb13c7c32a58930c85e880f5c Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Mon, 27 Oct 2025 15:23:46 +0000 Subject: [PATCH 225/322] Add FlagSet, DbiFlagSet, PutFlagSet Refactor DbiBuilder and Dbi ctor to use DbiFlagSet. --- src/main/java/org/lmdbjava/Dbi.java | 29 ++- src/main/java/org/lmdbjava/DbiBuilder.java | 42 ++-- src/main/java/org/lmdbjava/DbiFlagSet.java | 32 +++ src/main/java/org/lmdbjava/Env.java | 2 +- src/main/java/org/lmdbjava/FlagSet.java | 192 ++++++++++++++++++ src/main/java/org/lmdbjava/MaskedFlag.java | 38 +++- src/main/java/org/lmdbjava/PutFlagSet.java | 32 +++ .../java/org/lmdbjava/DbiFlagSetTest.java | 101 +++++++++ .../java/org/lmdbjava/PutFlagSetTest.java | 101 +++++++++ 9 files changed, 531 insertions(+), 38 deletions(-) create mode 100644 src/main/java/org/lmdbjava/DbiFlagSet.java create mode 100644 src/main/java/org/lmdbjava/FlagSet.java create mode 100644 src/main/java/org/lmdbjava/PutFlagSet.java create mode 100644 src/test/java/org/lmdbjava/DbiFlagSetTest.java create mode 100644 src/test/java/org/lmdbjava/PutFlagSetTest.java diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index ba20c1a4..17fa4c46 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -31,6 +31,7 @@ import static org.lmdbjava.PutFlags.MDB_RESERVE; import static org.lmdbjava.ResultCodeMapper.checkRc; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -55,6 +56,7 @@ public final class Dbi { private final byte[] name; private final Pointer ptr; private final BufferProxy proxy; + private final DbiFlagSet dbiFlagSet; Dbi( final Env env, @@ -63,7 +65,7 @@ public final class Dbi { final Comparator comparator, final boolean nativeCb, final BufferProxy proxy, - final DbiFlags... flags) { + final DbiFlagSet dbiFlagSet) { if (SHOULD_CHECK) { requireNonNull(txn); txn.checkReady(); @@ -72,9 +74,9 @@ public final class Dbi { this.name = name == null ? null : Arrays.copyOf(name, name.length); this.proxy = proxy; this.comparator = comparator; - final int flagsMask = mask(flags); + this.dbiFlagSet = dbiFlagSet; final Pointer dbiPtr = allocateDirect(RUNTIME, ADDRESS); - checkRc(LIB.mdb_dbi_open(txn.pointer(), name, flagsMask, dbiPtr)); + checkRc(LIB.mdb_dbi_open(txn.pointer(), name, dbiFlagSet.getMask(), dbiPtr)); ptr = dbiPtr.getPointer(0); if (nativeCb) { requireNonNull(comparator, "comparator cannot be null if nativeCb is set"); @@ -291,6 +293,7 @@ public CursorIterable iterate(final Txn txn, final KeyRange range) { * @return the list of flags this Dbi was created with */ public List listFlags(final Txn txn) { + // TODO we could just return what is in dbiFlagSet, rather than hitting LMDB. if (SHOULD_CHECK) { env.checkNotClosed(); } @@ -457,6 +460,26 @@ private void clean() { cleaned = true; } + private String getNameAsString() { + if (name == null) { + return ""; + } else { + try { + return new String(name, StandardCharsets.UTF_8); + } catch (Exception e) { + return "?"; + } + } + } + + @Override + public String toString() { + return "Dbi{" + + "name=" + getNameAsString() + + ", dbiFlagSet=" + dbiFlagSet + + '}'; + } + /** The specified DBI was changed unexpectedly. */ public static final class BadDbiException extends LmdbNativeException { diff --git a/src/main/java/org/lmdbjava/DbiBuilder.java b/src/main/java/org/lmdbjava/DbiBuilder.java index 5deb2e38..1803d142 100644 --- a/src/main/java/org/lmdbjava/DbiBuilder.java +++ b/src/main/java/org/lmdbjava/DbiBuilder.java @@ -19,9 +19,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Comparator; -import java.util.EnumSet; import java.util.Objects; -import java.util.Set; /** * Staged builder for building a {@link Dbi} @@ -224,19 +222,13 @@ public DbiBuilderStage3 withIteratorComparator(final Comparator comparator public static class DbiBuilderStage3 { private final DbiBuilderStage2 dbiBuilderStage2; - private Set dbiFlags = null; + private final FlagSet.Builder flagSetBuilder = DbiFlagSet.builder(); private Txn txn = null; private DbiBuilderStage3(DbiBuilderStage2 dbiBuilderStage2) { this.dbiBuilderStage2 = dbiBuilderStage2; } - private void initDbiFlags() { - if (dbiFlags == null) { - dbiFlags = EnumSet.noneOf(DbiFlags.class); - } - } - /** *

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

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

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

*

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

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

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

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

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

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

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

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

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

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

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

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

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

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

*

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

+ *

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

*

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

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

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

*

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

*

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

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

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

+ *

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

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

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

+ *
*

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

*

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

*

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

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

*

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

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

*

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

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

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

+ *

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

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

+ *

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

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

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

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

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

+ *

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

+ *

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

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

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

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

* * @return A newly constructed and opened {@link Dbi}. */ public Dbi open() { final DbiBuilder dbiBuilder = dbiBuilderStage2.dbiBuilder; - if (txn == null) { + if (txn != null) { + return open(txn, dbiBuilder); + } else { try (final Txn txn = getTxn(dbiBuilder)) { - return open(txn, dbiBuilder); + final Dbi dbi = open(txn, dbiBuilder); + // even RO Txns require a commit to retain Dbi in Env + txn.commit(); + return dbi; } - } else { - return open(txn, dbiBuilder); } } diff --git a/src/main/java/org/lmdbjava/DbiFlagSet.java b/src/main/java/org/lmdbjava/DbiFlagSet.java index e5c97544..28f5e4f1 100644 --- a/src/main/java/org/lmdbjava/DbiFlagSet.java +++ b/src/main/java/org/lmdbjava/DbiFlagSet.java @@ -1,3 +1,18 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.lmdbjava; import java.util.Collection; diff --git a/src/main/java/org/lmdbjava/EnvFlagSet.java b/src/main/java/org/lmdbjava/EnvFlagSet.java index 944496e6..f1bab2d0 100644 --- a/src/main/java/org/lmdbjava/EnvFlagSet.java +++ b/src/main/java/org/lmdbjava/EnvFlagSet.java @@ -1,3 +1,18 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.lmdbjava; import java.util.Collection; diff --git a/src/main/java/org/lmdbjava/FlagSet.java b/src/main/java/org/lmdbjava/FlagSet.java index 80a4c19e..89b955a0 100644 --- a/src/main/java/org/lmdbjava/FlagSet.java +++ b/src/main/java/org/lmdbjava/FlagSet.java @@ -1,3 +1,18 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.lmdbjava; import java.util.Comparator; diff --git a/src/main/java/org/lmdbjava/PutFlagSet.java b/src/main/java/org/lmdbjava/PutFlagSet.java index 1eedaf10..85de014b 100644 --- a/src/main/java/org/lmdbjava/PutFlagSet.java +++ b/src/main/java/org/lmdbjava/PutFlagSet.java @@ -1,3 +1,18 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.lmdbjava; import java.util.Collection; diff --git a/src/main/java/org/lmdbjava/ReferenceUtil.java b/src/main/java/org/lmdbjava/ReferenceUtil.java index 2b9f211e..70b3c338 100644 --- a/src/main/java/org/lmdbjava/ReferenceUtil.java +++ b/src/main/java/org/lmdbjava/ReferenceUtil.java @@ -41,6 +41,7 @@ private ReferenceUtil() {} */ public static void reachabilityFence0(final Object ref) { if (ref != null) { + //noinspection EmptySynchronizedStatement synchronized (ref) { // Empty synchronized is ok: https://stackoverflow.com/a/31933260/1151521 } diff --git a/src/main/java/org/lmdbjava/Txn.java b/src/main/java/org/lmdbjava/Txn.java index dc57fc66..99439bf7 100644 --- a/src/main/java/org/lmdbjava/Txn.java +++ b/src/main/java/org/lmdbjava/Txn.java @@ -42,15 +42,16 @@ public final class Txn implements AutoCloseable { private final Pointer ptr; private final boolean readOnly; private final Env env; + private final TxnFlagSet flags; private State state; Txn(final Env env, final Txn parent, final BufferProxy proxy, final TxnFlagSet flags) { - final TxnFlagSet flagSet = flags != null + this.flags = flags != null ? flags : TxnFlagSet.EMPTY; this.proxy = proxy; this.keyVal = proxy.keyVal(); - this.readOnly = flagSet.isSet(MDB_RDONLY_TXN); + this.readOnly = this.flags.isSet(MDB_RDONLY_TXN); if (env.isReadOnly() && !this.readOnly) { throw new EnvIsReadOnly(); } @@ -61,7 +62,7 @@ public final class Txn implements AutoCloseable { } final Pointer txnPtr = allocateDirect(RUNTIME, ADDRESS); final Pointer txnParentPtr = parent == null ? null : parent.ptr; - checkRc(LIB.mdb_txn_begin(env.pointer(), txnParentPtr, flagSet.getMask(), txnPtr)); + checkRc(LIB.mdb_txn_begin(env.pointer(), txnParentPtr, this.flags.getMask(), txnPtr)); ptr = txnPtr.getPointer(0); state = READY; diff --git a/src/main/java/org/lmdbjava/TxnFlagSet.java b/src/main/java/org/lmdbjava/TxnFlagSet.java index 6320eece..8e6310b3 100644 --- a/src/main/java/org/lmdbjava/TxnFlagSet.java +++ b/src/main/java/org/lmdbjava/TxnFlagSet.java @@ -1,3 +1,18 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.lmdbjava; import java.util.EnumSet; diff --git a/src/test/java/org/lmdbjava/CopyFlagSetTest.java b/src/test/java/org/lmdbjava/CopyFlagSetTest.java index 1ea44b7e..66e89ccb 100644 --- a/src/test/java/org/lmdbjava/CopyFlagSetTest.java +++ b/src/test/java/org/lmdbjava/CopyFlagSetTest.java @@ -1,3 +1,18 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.lmdbjava; import static org.hamcrest.CoreMatchers.is; diff --git a/src/test/java/org/lmdbjava/DbiBuilderTest.java b/src/test/java/org/lmdbjava/DbiBuilderTest.java new file mode 100644 index 00000000..da9341c6 --- /dev/null +++ b/src/test/java/org/lmdbjava/DbiBuilderTest.java @@ -0,0 +1,97 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +import static com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.lmdbjava.Env.create; +import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; +import static org.lmdbjava.TestUtils.bb; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import org.hamcrest.Matchers; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class DbiBuilderTest { + + @Rule + public final TemporaryFolder tmp = new TemporaryFolder(); + private Env env; + + @After + public void after() { + env.close(); + } + + @Before + public void before() throws IOException { + System.out.println("before"); + final File path = tmp.newFile(); + env = create() + .setMapSize(MEBIBYTES.toBytes(64)) + .setMaxReaders(2) + .setMaxDbs(2) + .open(path, MDB_NOSUBDIR); + } + + @Test + public void unnamed() { + final Dbi dbi = env.buildDbi() + .withoutDbName() + .withDefaultComparator() + .withDbiFlags(DbiFlags.MDB_CREATE) + .open(); + + assertThat(env.getDbiNames().size(), Matchers.is(0)); + + assertPutAndGet(dbi); + } + + + @Test + public void named() { + final Dbi dbi = env.buildDbi() + .withDbName("foo") + .withDefaultComparator() + .withDbiFlags(DbiFlags.MDB_CREATE) + .open(); + + assertPutAndGet(dbi); + + assertThat(env.getDbiNames().size(), Matchers.is(1)); + assertThat(new String(env.getDbiNames().get(0), StandardCharsets.UTF_8), Matchers.is("foo")); + } + + private void assertPutAndGet(Dbi dbi) { + try (Txn writeTxn = env.txnWrite()) { + dbi.put(writeTxn, bb(123), bb(123_000)); + writeTxn.commit(); + } + + try (Txn readTxn = env.txnRead()) { + final ByteBuffer byteBuffer = dbi.get(readTxn, bb(123)); + final int val = byteBuffer.getInt(); + assertThat(val, Matchers.is(123_000)); + } + } +} diff --git a/src/test/java/org/lmdbjava/DbiFlagSetTest.java b/src/test/java/org/lmdbjava/DbiFlagSetTest.java index 323a2ed4..457e4718 100644 --- a/src/test/java/org/lmdbjava/DbiFlagSetTest.java +++ b/src/test/java/org/lmdbjava/DbiFlagSetTest.java @@ -1,3 +1,18 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.lmdbjava; import static org.hamcrest.CoreMatchers.is; diff --git a/src/test/java/org/lmdbjava/EnvFlagSetTest.java b/src/test/java/org/lmdbjava/EnvFlagSetTest.java index ed6a0fea..2ecce3c2 100644 --- a/src/test/java/org/lmdbjava/EnvFlagSetTest.java +++ b/src/test/java/org/lmdbjava/EnvFlagSetTest.java @@ -1,3 +1,18 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.lmdbjava; import static org.hamcrest.CoreMatchers.is; diff --git a/src/test/java/org/lmdbjava/PutFlagSetTest.java b/src/test/java/org/lmdbjava/PutFlagSetTest.java index 8cf1efe0..3e402732 100644 --- a/src/test/java/org/lmdbjava/PutFlagSetTest.java +++ b/src/test/java/org/lmdbjava/PutFlagSetTest.java @@ -1,3 +1,18 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.lmdbjava; import static org.hamcrest.CoreMatchers.is; diff --git a/src/test/java/org/lmdbjava/TestDbiBuilder.java b/src/test/java/org/lmdbjava/TestDbiBuilder.java deleted file mode 100644 index 7a1e8947..00000000 --- a/src/test/java/org/lmdbjava/TestDbiBuilder.java +++ /dev/null @@ -1,83 +0,0 @@ -package org.lmdbjava; - -import static com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.lmdbjava.Env.create; -import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; -import static org.lmdbjava.TestUtils.bb; - -import java.io.File; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import org.hamcrest.Matchers; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -public class TestDbiBuilder { - - @Rule - public final TemporaryFolder tmp = new TemporaryFolder(); - private Env env; - - @After - public void after() { - env.close(); - } - - @Before - public void before() throws IOException { - System.out.println("before"); - final File path = tmp.newFile(); - env = - create() - .setMapSize(MEBIBYTES.toBytes(64)) - .setMaxReaders(2) - .setMaxDbs(2) - .open(path, MDB_NOSUBDIR); - } - - @Test - public void unnamed() { - final Dbi dbi = env.buildDbi() - .withoutDbName() - .withDefaultComparator() - .withDbiFlags(DbiFlags.MDB_CREATE) - .open(); - - assertThat(env.getDbiNames().size(), Matchers.is(0)); - - assertPutAndGet(dbi); - } - - - @Test - public void named() { - final Dbi dbi = env.buildDbi() - .withDbName("foo") - .withDefaultComparator() - .withDbiFlags(DbiFlags.MDB_CREATE) - .open(); - - assertPutAndGet(dbi); - - assertThat(env.getDbiNames().size(), Matchers.is(1)); - assertThat(new String(env.getDbiNames().get(0), StandardCharsets.UTF_8), Matchers.is("foo")); - } - - private void assertPutAndGet(Dbi dbi) { - try (Txn writeTxn = env.txnWrite()) { - dbi.put(writeTxn, bb(123), bb(123_000)); - writeTxn.commit(); - } - - try (Txn readTxn = env.txnRead()) { - final ByteBuffer byteBuffer = dbi.get(readTxn, bb(123)); - final int val = byteBuffer.getInt(); - assertThat(val, Matchers.is(123_000)); - } - } -} diff --git a/src/test/java/org/lmdbjava/TxnFlagSetTest.java b/src/test/java/org/lmdbjava/TxnFlagSetTest.java index b526ceeb..2bb3790a 100644 --- a/src/test/java/org/lmdbjava/TxnFlagSetTest.java +++ b/src/test/java/org/lmdbjava/TxnFlagSetTest.java @@ -1,3 +1,18 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.lmdbjava; import static org.hamcrest.CoreMatchers.is; From 2fb1c02b1ac5642292839590757b71822e5f55fb Mon Sep 17 00:00:00 2001 From: stroomdev66 Date: Tue, 28 Oct 2025 18:20:30 +0000 Subject: [PATCH 232/322] Upgrade to JUnit 5 and replace Hamcrest with AssertJ --- pom.xml | 34 +- src/main/java/org/lmdbjava/Dbi.java | 2 +- .../org/lmdbjava/ByteBufferProxyTest.java | 94 +-- .../java/org/lmdbjava/ComparatorTest.java | 120 ++- .../java/org/lmdbjava/CursorIterableTest.java | 287 ++++--- .../java/org/lmdbjava/CursorParamTest.java | 111 ++- src/test/java/org/lmdbjava/CursorTest.java | 359 ++++---- src/test/java/org/lmdbjava/DbiTest.java | 519 ++++++------ src/test/java/org/lmdbjava/EnvTest.java | 653 +++++++++------ src/test/java/org/lmdbjava/FileUtil.java | 153 ++++ .../org/lmdbjava/GarbageCollectionTest.java | 110 ++- src/test/java/org/lmdbjava/KeyRangeTest.java | 85 +- src/test/java/org/lmdbjava/LibraryTest.java | 14 +- .../java/org/lmdbjava/MaskedFlagTest.java | 47 +- src/test/java/org/lmdbjava/MetaTest.java | 20 +- .../org/lmdbjava/ResultCodeMapperTest.java | 56 +- .../java/org/lmdbjava/TargetNameTest.java | 32 +- src/test/java/org/lmdbjava/TutorialTest.java | 774 +++++++++--------- src/test/java/org/lmdbjava/TxnTest.java | 405 +++++---- src/test/java/org/lmdbjava/VerifierTest.java | 38 +- 20 files changed, 2188 insertions(+), 1725 deletions(-) create mode 100644 src/test/java/org/lmdbjava/FileUtil.java diff --git a/pom.xml b/pom.xml index d59d1a1d..3cb7ccc5 100644 --- a/pom.xml +++ b/pom.xml @@ -27,17 +27,17 @@ 1.22.0 + 3.27.6 3.2.1 0.9.1 0.9.0 2.29 1.28.0 33.5.0-jre - 2.2 0.8.14 0.10.4 2.2.18 - 4.13.2 + 5.14.0 4.6 3.5.0 3.14.1 @@ -96,18 +96,6 @@ ${netty-buffer.version} true
- - junit - junit - ${junit.version} - test - - - org.hamcrest - hamcrest-core - - - org.agrona agrona @@ -115,9 +103,21 @@ true - org.hamcrest - hamcrest - ${hamcrest.version} + org.assertj + assertj-core + ${assertj.version} + test + + + org.junit.jupiter + junit-jupiter-api + ${junit.version} + test + + + org.junit.jupiter + junit-jupiter-params + ${junit.version} test diff --git a/src/main/java/org/lmdbjava/Dbi.java b/src/main/java/org/lmdbjava/Dbi.java index 6b6eb266..5449c172 100644 --- a/src/main/java/org/lmdbjava/Dbi.java +++ b/src/main/java/org/lmdbjava/Dbi.java @@ -81,7 +81,7 @@ public final class Dbi { if (nativeCb) { this.ccb = (keyA, keyB) -> { - final T compKeyA = proxy.out(proxy.allocate(), keyA); + final T compKeyA = proxy.out(proxy.allocate(), keyA); final T compKeyB = proxy.out(proxy.allocate(), keyB); final int result = this.comparator.compare(compKeyA, compKeyB); proxy.deallocate(compKeyA); diff --git a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java index b68f39ef..82c0abce 100644 --- a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java +++ b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.lmdbjava; import static java.lang.Integer.BYTES; @@ -20,11 +21,8 @@ import static java.nio.ByteBuffer.allocateDirect; import static java.nio.ByteOrder.BIG_ENDIAN; import static java.nio.ByteOrder.LITTLE_ENDIAN; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.CoreMatchers.startsWith; -import static org.hamcrest.MatcherAssert.assertThat; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.lmdbjava.BufferProxy.MDB_VAL_STRUCT_SIZE; import static org.lmdbjava.ByteBufferProxy.AbstractByteBufferProxy.findField; import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; @@ -36,15 +34,11 @@ import static org.lmdbjava.TestUtils.invokePrivateConstructor; import static org.lmdbjava.UnsafeAccess.ALLOW_UNSAFE; -import java.io.File; -import java.io.IOException; import java.lang.reflect.Field; import java.nio.ByteBuffer; import jnr.ffi.Pointer; import jnr.ffi.provider.MemoryManager; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.Test; import org.lmdbjava.ByteBufferProxy.BufferMustBeDirectException; import org.lmdbjava.Env.ReadersFullException; @@ -53,23 +47,27 @@ public final class ByteBufferProxyTest { static final MemoryManager MEM_MGR = RUNTIME.getMemoryManager(); - @Rule public final TemporaryFolder tmp = new TemporaryFolder(); - - @Test(expected = BufferMustBeDirectException.class) - public void buffersMustBeDirect() throws IOException { - final File path = tmp.newFolder(); - try (Env env = create().setMaxReaders(1).open(path)) { - final Dbi db = env.openDbi(DB_1, MDB_CREATE); - final ByteBuffer key = allocate(100); - key.putInt(1).flip(); - final ByteBuffer val = allocate(100); - val.putInt(1).flip(); - db.put(key, val); // error - } + @Test + void buffersMustBeDirect() { + assertThatThrownBy( + () -> { + FileUtil.useTempDir( + dir -> { + try (Env env = create().setMaxReaders(1).open(dir.toFile())) { + final Dbi db = env.openDbi(DB_1, MDB_CREATE); + final ByteBuffer key = allocate(100); + key.putInt(1).flip(); + final ByteBuffer val = allocate(100); + val.putInt(1).flip(); + db.put(key, val); // error + } + }); + }) + .isInstanceOf(BufferMustBeDirectException.class); } @Test - public void byteOrderResets() { + void byteOrderResets() { final int retries = 100; for (int i = 0; i < retries; i++) { final ByteBuffer bb = PROXY_OPTIMAL.allocate(); @@ -77,56 +75,60 @@ public void byteOrderResets() { PROXY_OPTIMAL.deallocate(bb); } for (int i = 0; i < retries; i++) { - assertThat(PROXY_OPTIMAL.allocate().order(), is(BIG_ENDIAN)); + assertThat(PROXY_OPTIMAL.allocate().order()).isEqualTo(BIG_ENDIAN); } } @Test - public void coverPrivateConstructor() { + void coverPrivateConstructor() { invokePrivateConstructor(ByteBufferProxy.class); } - @Test(expected = LmdbException.class) - public void fieldNeverFound() { - findField(Exception.class, "notARealField"); + @Test + void fieldNeverFound() { + assertThatThrownBy( + () -> { + findField(Exception.class, "notARealField"); + }) + .isInstanceOf(LmdbException.class); } @Test - public void fieldSuperclassScan() { + void fieldSuperclassScan() { final Field f = findField(ReadersFullException.class, "rc"); - assertThat(f, is(notNullValue())); + assertThat(f).isNotNull(); } @Test - public void inOutBuffersProxyOptimal() { + void inOutBuffersProxyOptimal() { checkInOut(PROXY_OPTIMAL); } @Test - public void inOutBuffersProxySafe() { + void inOutBuffersProxySafe() { checkInOut(PROXY_SAFE); } @Test - public void optimalAlwaysAvailable() { + void optimalAlwaysAvailable() { final BufferProxy v = PROXY_OPTIMAL; - assertThat(v, is(notNullValue())); + assertThat(v).isNotNull(); } @Test - public void safeCanBeForced() { + void safeCanBeForced() { final BufferProxy v = PROXY_SAFE; - assertThat(v, is(notNullValue())); - assertThat(v.getClass().getSimpleName(), startsWith("Reflect")); + assertThat(v).isNotNull(); + assertThat(v.getClass().getSimpleName()).startsWith("Reflect"); } @Test - public void unsafeIsDefault() { - assertThat(ALLOW_UNSAFE, is(true)); + void unsafeIsDefault() { + assertThat(ALLOW_UNSAFE).isTrue(); final BufferProxy v = PROXY_OPTIMAL; - assertThat(v, is(notNullValue())); - assertThat(v, is(not(PROXY_SAFE))); - assertThat(v.getClass().getSimpleName(), startsWith("Unsafe")); + assertThat(v).isNotNull(); + assertThat(v).isNotEqualTo(PROXY_SAFE); + assertThat(v.getClass().getSimpleName()).startsWith("Unsafe"); } private void checkInOut(final BufferProxy v) { @@ -144,8 +146,8 @@ private void checkInOut(final BufferProxy v) { final ByteBuffer bb = allocateDirect(1); v.out(bb, p); - assertThat(bb.getInt(), is(2)); - assertThat(bb.getInt(), is(3)); - assertThat(bb.remaining(), is(0)); + assertThat(bb.getInt()).isEqualTo(2); + assertThat(bb.getInt()).isEqualTo(3); + assertThat(bb.remaining()).isEqualTo(0); } } diff --git a/src/test/java/org/lmdbjava/ComparatorTest.java b/src/test/java/org/lmdbjava/ComparatorTest.java index 3c7e7a4d..b4946a26 100644 --- a/src/test/java/org/lmdbjava/ComparatorTest.java +++ b/src/test/java/org/lmdbjava/ComparatorTest.java @@ -13,19 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.lmdbjava; import static io.netty.buffer.PooledByteBufAllocator.DEFAULT; import static java.nio.charset.StandardCharsets.US_ASCII; -import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import static org.lmdbjava.ByteArrayProxy.PROXY_BA; import static org.lmdbjava.ByteBufProxy.PROXY_NETTY; import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; -import static org.lmdbjava.ComparatorTest.ComparatorResult.EQUAL_TO; -import static org.lmdbjava.ComparatorTest.ComparatorResult.GREATER_THAN; -import static org.lmdbjava.ComparatorTest.ComparatorResult.LESS_THAN; -import static org.lmdbjava.ComparatorTest.ComparatorResult.get; +import static org.lmdbjava.ComparatorTest.ComparatorResult.*; import static org.lmdbjava.DirectBufferProxy.PROXY_DB; import com.google.common.primitives.SignedBytes; @@ -34,16 +31,14 @@ import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Comparator; +import java.util.stream.Stream; import org.agrona.DirectBuffer; import org.agrona.concurrent.UnsafeBuffer; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; /** Tests comparator functions are consistent across buffers. */ -@RunWith(Parameterized.class) public final class ComparatorTest { // H = 1 (high), L = 0 (low), X = byte not set in buffer @@ -59,20 +54,16 @@ public final class ComparatorTest { private static final byte[] LX = buffer(0); private static final byte[] XX = buffer(); - /** Injected by {@link #data()} with appropriate runner. */ - @Parameter public ComparatorRunner comparator; - - @Parameters(name = "{index}: comparable: {0}") - public static Object[] data() { - final ComparatorRunner string = new StringRunner(); - final ComparatorRunner db = new DirectBufferRunner(); - final ComparatorRunner ba = new ByteArrayRunner(); - final ComparatorRunner baUnsigned = new UnsignedByteArrayRunner(); - final ComparatorRunner bb = new ByteBufferRunner(); - final ComparatorRunner netty = new NettyRunner(); - final ComparatorRunner gub = new GuavaUnsignedBytes(); - final ComparatorRunner gsb = new GuavaSignedBytes(); - return new Object[] {string, db, ba, baUnsigned, bb, netty, gub, gsb}; + static Stream comparatorProvider() { + return Stream.of( + Arguments.arguments("StringRunner", new StringRunner()), + Arguments.arguments("DirectBufferRunner", new DirectBufferRunner()), + Arguments.arguments("ByteArrayRunner", new ByteArrayRunner()), + Arguments.arguments("UnsignedByteArrayRunner", new UnsignedByteArrayRunner()), + Arguments.arguments("ByteBufferRunner", new ByteBufferRunner()), + Arguments.arguments("NettyRunner", new NettyRunner()), + Arguments.arguments("GuavaUnsignedBytes", new GuavaUnsignedBytes()), + Arguments.arguments("GuavaSignedBytes", new GuavaSignedBytes())); } private static byte[] buffer(final int... bytes) { @@ -83,52 +74,55 @@ private static byte[] buffer(final int... bytes) { return array; } - @Test - public void atLeastOneBufferHasEightBytes() { - assertThat(get(comparator.compare(HLLLLLLL, LLLLLLLL)), is(GREATER_THAN)); - assertThat(get(comparator.compare(LLLLLLLL, HLLLLLLL)), is(LESS_THAN)); + @ParameterizedTest + @MethodSource("comparatorProvider") + void atLeastOneBufferHasEightBytes(final String str, final ComparatorRunner comparator) { + assertThat(get(comparator.compare(HLLLLLLL, LLLLLLLL))).isEqualTo(GREATER_THAN); + assertThat(get(comparator.compare(LLLLLLLL, HLLLLLLL))).isEqualTo(LESS_THAN); - assertThat(get(comparator.compare(LHLLLLLL, LLLLLLLL)), is(GREATER_THAN)); - assertThat(get(comparator.compare(LLLLLLLL, LHLLLLLL)), is(LESS_THAN)); + assertThat(get(comparator.compare(LHLLLLLL, LLLLLLLL))).isEqualTo(GREATER_THAN); + assertThat(get(comparator.compare(LLLLLLLL, LHLLLLLL))).isEqualTo(LESS_THAN); - assertThat(get(comparator.compare(LLLLLLLL, LLLLLLLX)), is(GREATER_THAN)); - assertThat(get(comparator.compare(LLLLLLLX, LLLLLLLL)), is(LESS_THAN)); + assertThat(get(comparator.compare(LLLLLLLL, LLLLLLLX))).isEqualTo(GREATER_THAN); + assertThat(get(comparator.compare(LLLLLLLX, LLLLLLLL))).isEqualTo(LESS_THAN); - assertThat(get(comparator.compare(HLLLLLLL, HLLLLLLX)), is(GREATER_THAN)); - assertThat(get(comparator.compare(HLLLLLLX, HLLLLLLL)), is(LESS_THAN)); + assertThat(get(comparator.compare(HLLLLLLL, HLLLLLLX))).isEqualTo(GREATER_THAN); + assertThat(get(comparator.compare(HLLLLLLX, HLLLLLLL))).isEqualTo(LESS_THAN); - assertThat(get(comparator.compare(HLLLLLLX, LHLLLLLL)), is(GREATER_THAN)); - assertThat(get(comparator.compare(LHLLLLLL, HLLLLLLX)), is(LESS_THAN)); + assertThat(get(comparator.compare(HLLLLLLX, LHLLLLLL))).isEqualTo(GREATER_THAN); + assertThat(get(comparator.compare(LHLLLLLL, HLLLLLLX))).isEqualTo(LESS_THAN); } - @Test - public void buffersOfTwoBytes() { - assertThat(get(comparator.compare(LL, XX)), is(GREATER_THAN)); - assertThat(get(comparator.compare(XX, LL)), is(LESS_THAN)); + @ParameterizedTest + @MethodSource("comparatorProvider") + void buffersOfTwoBytes(final String str, final ComparatorRunner comparator) { + assertThat(get(comparator.compare(LL, XX))).isEqualTo(GREATER_THAN); + assertThat(get(comparator.compare(XX, LL))).isEqualTo(LESS_THAN); - assertThat(get(comparator.compare(LL, LX)), is(GREATER_THAN)); - assertThat(get(comparator.compare(LX, LL)), is(LESS_THAN)); + assertThat(get(comparator.compare(LL, LX))).isEqualTo(GREATER_THAN); + assertThat(get(comparator.compare(LX, LL))).isEqualTo(LESS_THAN); - assertThat(get(comparator.compare(LH, LX)), is(GREATER_THAN)); - assertThat(get(comparator.compare(LX, HL)), is(LESS_THAN)); + assertThat(get(comparator.compare(LH, LX))).isEqualTo(GREATER_THAN); + assertThat(get(comparator.compare(LX, HL))).isEqualTo(LESS_THAN); - assertThat(get(comparator.compare(HX, LL)), is(GREATER_THAN)); - assertThat(get(comparator.compare(LH, HX)), is(LESS_THAN)); + assertThat(get(comparator.compare(HX, LL))).isEqualTo(GREATER_THAN); + assertThat(get(comparator.compare(LH, HX))).isEqualTo(LESS_THAN); } - @Test - public void equalBuffers() { - assertThat(get(comparator.compare(LL, LL)), is(EQUAL_TO)); - assertThat(get(comparator.compare(HX, HX)), is(EQUAL_TO)); - assertThat(get(comparator.compare(LH, LH)), is(EQUAL_TO)); - assertThat(get(comparator.compare(LL, LL)), is(EQUAL_TO)); - assertThat(get(comparator.compare(LX, LX)), is(EQUAL_TO)); - - assertThat(get(comparator.compare(HLLLLLLL, HLLLLLLL)), is(EQUAL_TO)); - assertThat(get(comparator.compare(HLLLLLLX, HLLLLLLX)), is(EQUAL_TO)); - assertThat(get(comparator.compare(LHLLLLLL, LHLLLLLL)), is(EQUAL_TO)); - assertThat(get(comparator.compare(LLLLLLLL, LLLLLLLL)), is(EQUAL_TO)); - assertThat(get(comparator.compare(LLLLLLLX, LLLLLLLX)), is(EQUAL_TO)); + @ParameterizedTest + @MethodSource("comparatorProvider") + void equalBuffers(final String str, final ComparatorRunner comparator) { + assertThat(get(comparator.compare(LL, LL))).isEqualTo(EQUAL_TO); + assertThat(get(comparator.compare(HX, HX))).isEqualTo(EQUAL_TO); + assertThat(get(comparator.compare(LH, LH))).isEqualTo(EQUAL_TO); + assertThat(get(comparator.compare(LL, LL))).isEqualTo(EQUAL_TO); + assertThat(get(comparator.compare(LX, LX))).isEqualTo(EQUAL_TO); + + assertThat(get(comparator.compare(HLLLLLLL, HLLLLLLL))).isEqualTo(EQUAL_TO); + assertThat(get(comparator.compare(HLLLLLLX, HLLLLLLX))).isEqualTo(EQUAL_TO); + assertThat(get(comparator.compare(LHLLLLLL, LHLLLLLL))).isEqualTo(EQUAL_TO); + assertThat(get(comparator.compare(LLLLLLLL, LLLLLLLL))).isEqualTo(EQUAL_TO); + assertThat(get(comparator.compare(LLLLLLLX, LLLLLLLX))).isEqualTo(EQUAL_TO); } /** Tests {@link ByteArrayProxy}. */ @@ -169,14 +163,14 @@ public int compare(final byte[] o1, final byte[] o2) { o2b = arrayToBuffer(o2, o2.length * 3); final int result2 = c.compare(o1b, o2b); - assertThat(result2, is(result)); + assertThat(result2).isEqualTo(result); // Now try with buffers sized to the array. o1b = ByteBuffer.wrap(o1); o2b = ByteBuffer.wrap(o2); final int result3 = c.compare(o1b, o2b); - assertThat(result3, is(result)); + assertThat(result3).isEqualTo(result); return result; } diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index bd23bc55..22f5f7c7 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.lmdbjava; import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; import static java.util.Arrays.asList; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.hasSize; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.Env.create; import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; @@ -47,9 +47,8 @@ import static org.lmdbjava.TestUtils.bb; import com.google.common.primitives.UnsignedBytes; -import java.io.File; -import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Comparator; import java.util.Deque; @@ -57,200 +56,210 @@ import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; -import org.hamcrest.Matchers; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.lmdbjava.CursorIterable.KeyVal; /** Test {@link CursorIterable}. */ public final class CursorIterableTest { - @Rule public final TemporaryFolder tmp = new TemporaryFolder(); + private Path file; private Dbi db; private Env env; private Deque list; - @After - public void after() { + @BeforeEach + void beforeEach() { + file = FileUtil.createTempFile(); + env = + create() + .setMapSize(KIBIBYTES.toBytes(256)) + .setMaxReaders(1) + .setMaxDbs(1) + .open(file.toFile(), POSIX_MODE, MDB_NOSUBDIR); + db = env.openDbi(DB_1, MDB_CREATE); + populateDatabase(db); + } + + private void populateDatabase(final Dbi dbi) { + list = new LinkedList<>(); + list.addAll(asList(2, 3, 4, 5, 6, 7, 8, 9)); + try (Txn txn = env.txnWrite()) { + final Cursor c = dbi.openCursor(txn); + c.put(bb(2), bb(3), MDB_NOOVERWRITE); + c.put(bb(4), bb(5)); + c.put(bb(6), bb(7)); + c.put(bb(8), bb(9)); + txn.commit(); + } + } + + @AfterEach + void afterEach() { env.close(); + FileUtil.delete(file); } @Test - public void allBackwardTest() { + void allBackwardTest() { verify(allBackward(), 8, 6, 4, 2); } @Test - public void allTest() { + void allTest() { verify(all(), 2, 4, 6, 8); } @Test - public void atLeastBackwardTest() { + void atLeastBackwardTest() { verify(atLeastBackward(bb(5)), 4, 2); verify(atLeastBackward(bb(6)), 6, 4, 2); verify(atLeastBackward(bb(9)), 8, 6, 4, 2); } @Test - public void atLeastTest() { + void atLeastTest() { verify(atLeast(bb(5)), 6, 8); verify(atLeast(bb(6)), 6, 8); } @Test - public void atMostBackwardTest() { + void atMostBackwardTest() { verify(atMostBackward(bb(5)), 8, 6); verify(atMostBackward(bb(6)), 8, 6); } @Test - public void atMostTest() { + void atMostTest() { verify(atMost(bb(5)), 2, 4); verify(atMost(bb(6)), 2, 4, 6); } - @Before - public void before() throws IOException { - final File path = tmp.newFile(); - env = - create() - .setMapSize(KIBIBYTES.toBytes(256)) - .setMaxReaders(1) - .setMaxDbs(1) - .open(path, POSIX_MODE, MDB_NOSUBDIR); - db = env.openDbi(DB_1, MDB_CREATE); - populateDatabase(db); - } - - private void populateDatabase(final Dbi dbi) { - list = new LinkedList<>(); - list.addAll(asList(2, 3, 4, 5, 6, 7, 8, 9)); - try (Txn txn = env.txnWrite()) { - final Cursor c = dbi.openCursor(txn); - c.put(bb(2), bb(3), MDB_NOOVERWRITE); - c.put(bb(4), bb(5)); - c.put(bb(6), bb(7)); - c.put(bb(8), bb(9)); - txn.commit(); - } - } - @Test - public void closedBackwardTest() { + void closedBackwardTest() { verify(closedBackward(bb(7), bb(3)), 6, 4); verify(closedBackward(bb(6), bb(2)), 6, 4, 2); verify(closedBackward(bb(9), bb(3)), 8, 6, 4); } @Test - public void closedOpenBackwardTest() { + void closedOpenBackwardTest() { verify(closedOpenBackward(bb(8), bb(3)), 8, 6, 4); verify(closedOpenBackward(bb(7), bb(2)), 6, 4); verify(closedOpenBackward(bb(9), bb(3)), 8, 6, 4); } @Test - public void closedOpenTest() { + void closedOpenTest() { verify(closedOpen(bb(3), bb(8)), 4, 6); verify(closedOpen(bb(2), bb(6)), 2, 4); } @Test - public void closedTest() { + void closedTest() { verify(closed(bb(3), bb(7)), 4, 6); verify(closed(bb(2), bb(6)), 2, 4, 6); verify(closed(bb(1), bb(7)), 2, 4, 6); } @Test - public void greaterThanBackwardTest() { + void greaterThanBackwardTest() { verify(greaterThanBackward(bb(6)), 4, 2); verify(greaterThanBackward(bb(7)), 6, 4, 2); verify(greaterThanBackward(bb(9)), 8, 6, 4, 2); } @Test - public void greaterThanTest() { + void greaterThanTest() { verify(greaterThan(bb(4)), 6, 8); verify(greaterThan(bb(3)), 4, 6, 8); } - @Test(expected = IllegalStateException.class) - public void iterableOnlyReturnedOnce() { - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { - c.iterator(); // ok - c.iterator(); // fails - } + @Test + void iterableOnlyReturnedOnce() { + assertThatThrownBy( + () -> { + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + c.iterator(); // ok + c.iterator(); // fails + } + }) + .isInstanceOf(IllegalStateException.class); } @Test - public void iterate() { + void iterate() { try (Txn txn = env.txnRead(); CursorIterable c = db.iterate(txn)) { for (final KeyVal kv : c) { - assertThat(kv.key().getInt(), is(list.pollFirst())); - assertThat(kv.val().getInt(), is(list.pollFirst())); + assertThat(kv.key().getInt()).isEqualTo(list.pollFirst()); + assertThat(kv.val().getInt()).isEqualTo(list.pollFirst()); } } } - @Test(expected = IllegalStateException.class) - public void iteratorOnlyReturnedOnce() { - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { - c.iterator(); // ok - c.iterator(); // fails - } + @Test + void iteratorOnlyReturnedOnce() { + assertThatThrownBy( + () -> { + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + c.iterator(); // ok + c.iterator(); // fails + } + }) + .isInstanceOf(IllegalStateException.class); } @Test - public void lessThanBackwardTest() { + void lessThanBackwardTest() { verify(lessThanBackward(bb(5)), 8, 6); verify(lessThanBackward(bb(2)), 8, 6, 4); } @Test - public void lessThanTest() { + void lessThanTest() { verify(lessThan(bb(5)), 2, 4); verify(lessThan(bb(8)), 2, 4, 6); } - @Test(expected = NoSuchElementException.class) - public void nextThrowsNoSuchElementExceptionIfNoMoreElements() { - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { - final Iterator> i = c.iterator(); - while (i.hasNext()) { - final KeyVal kv = i.next(); - assertThat(kv.key().getInt(), is(list.pollFirst())); - assertThat(kv.val().getInt(), is(list.pollFirst())); - } - assertThat(i.hasNext(), is(false)); - i.next(); - } + @Test + void nextThrowsNoSuchElementExceptionIfNoMoreElements() { + assertThatThrownBy( + () -> { + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { + final Iterator> i = c.iterator(); + while (i.hasNext()) { + final KeyVal kv = i.next(); + assertThat(kv.key().getInt()).isEqualTo(list.pollFirst()); + assertThat(kv.val().getInt()).isEqualTo(list.pollFirst()); + } + assertThat(i.hasNext()).isFalse(); + i.next(); + } + }) + .isInstanceOf(NoSuchElementException.class); } @Test - public void openBackwardTest() { + void openBackwardTest() { verify(openBackward(bb(7), bb(2)), 6, 4); verify(openBackward(bb(8), bb(1)), 6, 4, 2); verify(openBackward(bb(9), bb(4)), 8, 6); } @Test - public void openClosedBackwardTest() { + void openClosedBackwardTest() { verify(openClosedBackward(bb(7), bb(2)), 6, 4, 2); verify(openClosedBackward(bb(8), bb(4)), 6, 4); verify(openClosedBackward(bb(9), bb(4)), 8, 6, 4); } @Test - public void openClosedBackwardTestWithGuava() { + void openClosedBackwardTestWithGuava() { final Comparator guava = UnsignedBytes.lexicographicalComparator(); final Comparator comparator = (bb1, bb2) -> { @@ -271,19 +280,19 @@ public void openClosedBackwardTestWithGuava() { } @Test - public void openClosedTest() { + void openClosedTest() { verify(openClosed(bb(3), bb(8)), 4, 6, 8); verify(openClosed(bb(2), bb(6)), 4, 6); } @Test - public void openTest() { + void openTest() { verify(open(bb(3), bb(7)), 4, 6); verify(open(bb(2), bb(8)), 4, 6); } @Test - public void removeOddElements() { + void removeOddElements() { verify(all(), 2, 4, 6, 8); int idx = -1; try (Txn txn = env.txnWrite()) { @@ -302,55 +311,71 @@ public void removeOddElements() { verify(all(), 4, 8); } - @Test(expected = Env.AlreadyClosedException.class) - public void nextWithClosedEnvTest() { - try (Txn txn = env.txnRead()) { - try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { - final Iterator> c = ci.iterator(); + @Test + void nextWithClosedEnvTest() { + assertThatThrownBy( + () -> { + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); - env.close(); - c.next(); - } - } + env.close(); + c.next(); + } + } + }) + .isInstanceOf(Env.AlreadyClosedException.class); } - @Test(expected = Env.AlreadyClosedException.class) - public void removeWithClosedEnvTest() { - try (Txn txn = env.txnWrite()) { - try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { - final Iterator> c = ci.iterator(); + @Test + void removeWithClosedEnvTest() { + assertThatThrownBy( + () -> { + try (Txn txn = env.txnWrite()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); - final KeyVal keyVal = c.next(); - assertThat(keyVal, Matchers.notNullValue()); + final KeyVal keyVal = c.next(); + assertThat(keyVal).isNotNull(); - env.close(); - c.remove(); - } - } + env.close(); + c.remove(); + } + } + }) + .isInstanceOf(Env.AlreadyClosedException.class); } - @Test(expected = Env.AlreadyClosedException.class) - public void hasNextWithClosedEnvTest() { - try (Txn txn = env.txnRead()) { - try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { - final Iterator> c = ci.iterator(); + @Test + void hasNextWithClosedEnvTest() { + assertThatThrownBy( + () -> { + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); - env.close(); - c.hasNext(); - } - } + env.close(); + c.hasNext(); + } + } + }) + .isInstanceOf(Env.AlreadyClosedException.class); } - @Test(expected = Env.AlreadyClosedException.class) - public void forEachRemainingWithClosedEnvTest() { - try (Txn txn = env.txnRead()) { - try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { - final Iterator> c = ci.iterator(); + @Test + void forEachRemainingWithClosedEnvTest() { + assertThatThrownBy( + () -> { + try (Txn txn = env.txnRead()) { + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + final Iterator> c = ci.iterator(); - env.close(); - c.forEachRemaining(keyVal -> {}); - } - } + env.close(); + c.forEachRemaining(keyVal -> {}); + } + } + }) + .isInstanceOf(Env.AlreadyClosedException.class); } private void verify(final KeyRange range, final int... expected) { @@ -367,13 +392,13 @@ private void verify( final int key = kv.key().getInt(); final int val = kv.val().getInt(); results.add(key); - assertThat(val, is(key + 1)); + assertThat(val).isEqualTo(key + 1); } } - assertThat(results, hasSize(expected.length)); + assertThat(results.size()).isEqualTo(expected.length); for (int idx = 0; idx < results.size(); idx++) { - assertThat(results.get(idx), is(expected[idx])); + assertThat(results.get(idx)).isEqualTo(expected[idx]); } } } diff --git a/src/test/java/org/lmdbjava/CursorParamTest.java b/src/test/java/org/lmdbjava/CursorParamTest.java index bd99e709..0b2df19e 100644 --- a/src/test/java/org/lmdbjava/CursorParamTest.java +++ b/src/test/java/org/lmdbjava/CursorParamTest.java @@ -13,14 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.lmdbjava; -import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; +import static com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; import static java.lang.Long.BYTES; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.collection.IsEmptyCollection.empty; +import static org.assertj.core.api.Assertions.assertThat; import static org.lmdbjava.ByteArrayProxy.PROXY_BA; import static org.lmdbjava.ByteBufProxy.PROXY_NETTY; import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; @@ -44,40 +43,31 @@ import static org.lmdbjava.TestUtils.nb; import io.netty.buffer.ByteBuf; -import java.io.File; -import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.file.Path; +import java.util.stream.Stream; import org.agrona.DirectBuffer; import org.agrona.MutableDirectBuffer; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; /** Test {@link Cursor} with different buffer implementations. */ -@RunWith(Parameterized.class) public final class CursorParamTest { - /** Injected by {@link #data()} with appropriate runner. */ - @Parameter public BufferRunner runner; - - @Rule public final TemporaryFolder tmp = new TemporaryFolder(); - - @Parameters(name = "{index}: buffer adapter: {0}") - public static Object[] data() { - final BufferRunner bb1 = new ByteBufferRunner(PROXY_OPTIMAL); - final BufferRunner bb2 = new ByteBufferRunner(PROXY_SAFE); - final BufferRunner ba = new ByteArrayRunner(PROXY_BA); - final BufferRunner db = new DirectBufferRunner(); - final BufferRunner netty = new NettyBufferRunner(); - return new Object[] {bb1, bb2, ba, db, netty}; + static Stream data() { + return Stream.of( + Arguments.arguments("ByteBufferRunner(PROXY_OPTIMAL)", new ByteBufferRunner(PROXY_OPTIMAL)), + Arguments.arguments("ByteBufferRunner(PROXY_SAFE)", new ByteBufferRunner(PROXY_SAFE)), + Arguments.arguments("ByteArrayRunner(PROXY_BA)", new ByteArrayRunner(PROXY_BA)), + Arguments.arguments("DirectBufferRunner", new DirectBufferRunner()), + Arguments.arguments("NettyBufferRunner", new NettyBufferRunner())); } - @Test - public void execute() { + @ParameterizedTest + @MethodSource("data") + void execute(final String name, final BufferRunner runner, @TempDir final Path tmp) { runner.execute(tmp); } @@ -95,11 +85,11 @@ protected AbstractBufferRunner(final BufferProxy proxy) { } @Override - public final void execute(final TemporaryFolder tmp) { + public final void execute(final Path tmp) { try (Env env = env(tmp)) { - assertThat(env.getDbiNames(), empty()); + assertThat(env.getDbiNames()).isEmpty(); final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); - assertThat(env.getDbiNames().get(0), is(DB_1.getBytes(UTF_8))); + assertThat(env.getDbiNames().get(0)).isEqualTo(DB_1.getBytes(UTF_8)); try (Txn txn = env.txnWrite(); Cursor c = db.openCursor(txn)) { // populate data @@ -113,71 +103,66 @@ public final void execute(final TemporaryFolder tmp) { // check MDB_SET operations final T key3 = set(3); - assertThat(c.get(key3, MDB_SET_KEY), is(true)); - assertThat(get(c.key()), is(3)); - assertThat(get(c.val()), is(4)); + assertThat(c.get(key3, MDB_SET_KEY)).isTrue(); + assertThat(get(c.key())).isEqualTo(3); + assertThat(get(c.val())).isEqualTo(4); final T key6 = set(6); - assertThat(c.get(key6, MDB_SET_RANGE), is(true)); - assertThat(get(c.key()), is(7)); + assertThat(c.get(key6, MDB_SET_RANGE)).isTrue(); + assertThat(get(c.key())).isEqualTo(7); if (!(this instanceof ByteArrayRunner)) { - assertThat(get(c.val()), is(8)); + assertThat(get(c.val())).isEqualTo(8); } final T key999 = set(999); - assertThat(c.get(key999, MDB_SET_KEY), is(false)); + assertThat(c.get(key999, MDB_SET_KEY)).isFalse(); // check MDB navigation operations - assertThat(c.seek(MDB_LAST), is(true)); + assertThat(c.seek(MDB_LAST)).isTrue(); final int mdb1 = get(c.key()); final int mdb2 = get(c.val()); - assertThat(c.seek(MDB_PREV), is(true)); + assertThat(c.seek(MDB_PREV)).isTrue(); final int mdb3 = get(c.key()); final int mdb4 = get(c.val()); - assertThat(c.seek(MDB_NEXT), is(true)); + assertThat(c.seek(MDB_NEXT)).isTrue(); final int mdb5 = get(c.key()); final int mdb6 = get(c.val()); - assertThat(c.seek(MDB_FIRST), is(true)); + assertThat(c.seek(MDB_FIRST)).isTrue(); final int mdb7 = get(c.key()); final int mdb8 = get(c.val()); // assert afterwards to ensure memory address from LMDB // are valid within same txn and across cursor movement // MDB_LAST - assertThat(mdb1, is(7)); + assertThat(mdb1).isEqualTo(7); if (!(this instanceof ByteArrayRunner)) { - assertThat(mdb2, is(8)); + assertThat(mdb2).isEqualTo(8); } // MDB_PREV - assertThat(mdb3, is(5)); - assertThat(mdb4, is(6)); + assertThat(mdb3).isEqualTo(5); + assertThat(mdb4).isEqualTo(6); // MDB_NEXT - assertThat(mdb5, is(7)); + assertThat(mdb5).isEqualTo(7); if (!(this instanceof ByteArrayRunner)) { - assertThat(mdb6, is(8)); + assertThat(mdb6).isEqualTo(8); } // MDB_FIRST - assertThat(mdb7, is(1)); - assertThat(mdb8, is(2)); + assertThat(mdb7).isEqualTo(1); + assertThat(mdb8).isEqualTo(2); } } } - private Env env(final TemporaryFolder tmp) { - try { - final File path = tmp.newFile(); - return create(proxy) - .setMapSize(KIBIBYTES.toBytes(1_024)) - .setMaxReaders(1) - .setMaxDbs(1) - .open(path, POSIX_MODE, MDB_NOSUBDIR); - } catch (final IOException e) { - throw new LmdbException("IO failure", e); - } + private Env env(final Path tmp) { + return create(proxy) + .setMapSize(MEBIBYTES.toBytes(1)) + .setMaxReaders(1) + .setMaxDbs(1) + .open(tmp.resolve("db").toFile(), POSIX_MODE, MDB_NOSUBDIR); } } @@ -291,7 +276,7 @@ public void set(final ByteBuf buff, final int val) { */ private interface BufferRunner { - void execute(TemporaryFolder tmp); + void execute(Path tmp); T set(int val); diff --git a/src/test/java/org/lmdbjava/CursorTest.java b/src/test/java/org/lmdbjava/CursorTest.java index cf6e4dea..a0c78855 100644 --- a/src/test/java/org/lmdbjava/CursorTest.java +++ b/src/test/java/org/lmdbjava/CursorTest.java @@ -13,16 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.lmdbjava; -import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; +import static com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; import static java.lang.Long.BYTES; import static java.lang.Long.MIN_VALUE; import static java.nio.ByteBuffer.allocateDirect; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.DbiFlags.MDB_DUPFIXED; @@ -41,141 +40,175 @@ import static org.lmdbjava.TestUtils.POSIX_MODE; import static org.lmdbjava.TestUtils.bb; -import java.io.File; -import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.file.Path; import java.util.function.Consumer; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.lmdbjava.Cursor.ClosedException; +import org.lmdbjava.Env.AlreadyClosedException; import org.lmdbjava.Txn.NotReadyException; import org.lmdbjava.Txn.ReadOnlyRequiredException; /** Test {@link Cursor}. */ public final class CursorTest { - @Rule public final TemporaryFolder tmp = new TemporaryFolder(); - + private Path file; private Env env; - @After - public void after() { - env.close(); + @BeforeEach + void beforeEach() { + file = FileUtil.createTempFile(); + env = + create(PROXY_OPTIMAL) + .setMapSize(MEBIBYTES.toBytes(1)) + .setMaxReaders(1) + .setMaxDbs(1) + .open(file.toFile(), POSIX_MODE, MDB_NOSUBDIR); } - @Before - public void before() throws IOException { - try { - final File path = tmp.newFile(); - env = - create(PROXY_OPTIMAL) - .setMapSize(KIBIBYTES.toBytes(1_024)) - .setMaxReaders(1) - .setMaxDbs(1) - .open(path, POSIX_MODE, MDB_NOSUBDIR); - } catch (final IOException e) { - throw new LmdbException("IO failure", e); - } + @AfterEach + void afterEach() { + env.close(); + FileUtil.delete(file); } - @Test(expected = ClosedException.class) - public void closedCursorRejectsSubsequentGets() { - final Dbi db = env.openDbi(DB_1, MDB_CREATE); - try (Txn txn = env.txnWrite()) { - final Cursor c = db.openCursor(txn); - c.close(); - c.seek(MDB_FIRST); - } + @Test + void closedCursorRejectsSubsequentGets() { + assertThatThrownBy( + () -> { + final Dbi db = env.openDbi(DB_1, MDB_CREATE); + try (Txn txn = env.txnWrite()) { + final Cursor c = db.openCursor(txn); + c.close(); + c.seek(MDB_FIRST); + } + }) + .isInstanceOf(ClosedException.class); } - @Test(expected = Env.AlreadyClosedException.class) - public void closedEnvRejectsSeekFirstCall() { - doEnvClosedTest(null, c -> c.seek(MDB_FIRST)); + @Test + void closedEnvRejectsSeekFirstCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest(null, c -> c.seek(MDB_FIRST)); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = Env.AlreadyClosedException.class) - public void closedEnvRejectsSeekLastCall() { - doEnvClosedTest(null, c -> c.seek(MDB_LAST)); + @Test + void closedEnvRejectsSeekLastCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest(null, c -> c.seek(MDB_LAST)); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = Env.AlreadyClosedException.class) - public void closedEnvRejectsSeekNextCall() { - doEnvClosedTest(null, c -> c.seek(MDB_NEXT)); + @Test + void closedEnvRejectsSeekNextCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest(null, c -> c.seek(MDB_NEXT)); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = Env.AlreadyClosedException.class) - public void closedEnvRejectsCloseCall() { - doEnvClosedTest(null, Cursor::close); + @Test + void closedEnvRejectsCloseCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest(null, Cursor::close); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = Env.AlreadyClosedException.class) - public void closedEnvRejectsFirstCall() { - doEnvClosedTest(null, Cursor::first); + @Test + void closedEnvRejectsFirstCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest(null, Cursor::first); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = Env.AlreadyClosedException.class) - public void closedEnvRejectsLastCall() { - doEnvClosedTest(null, Cursor::last); + @Test + void closedEnvRejectsLastCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest(null, Cursor::last); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = Env.AlreadyClosedException.class) - public void closedEnvRejectsPrevCall() { - doEnvClosedTest( - c -> { - c.first(); - assertThat(c.key().getInt(), is(1)); - assertThat(c.val().getInt(), is(10)); - c.next(); - }, - Cursor::prev); + @Test + void closedEnvRejectsPrevCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest( + c -> { + c.first(); + assertThat(c.key().getInt()).isEqualTo(1); + assertThat(c.val().getInt()).isEqualTo(10); + c.next(); + }, + Cursor::prev); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = Env.AlreadyClosedException.class) - public void closedEnvRejectsDeleteCall() { - doEnvClosedTest( - c -> { - c.first(); - assertThat(c.key().getInt(), is(1)); - assertThat(c.val().getInt(), is(10)); - }, - Cursor::delete); + @Test + void closedEnvRejectsDeleteCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest( + c -> { + c.first(); + assertThat(c.key().getInt()).isEqualTo(1); + assertThat(c.val().getInt()).isEqualTo(10); + }, + Cursor::delete); + }) + .isInstanceOf(AlreadyClosedException.class); } @Test - public void count() { + void count() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); try (Txn txn = env.txnWrite(); Cursor c = db.openCursor(txn)) { c.put(bb(1), bb(2), MDB_APPENDDUP); - assertThat(c.count(), is(1L)); + assertThat(c.count()).isEqualTo(1L); c.put(bb(1), bb(4), MDB_APPENDDUP); c.put(bb(1), bb(6), MDB_APPENDDUP); - assertThat(c.count(), is(3L)); + assertThat(c.count()).isEqualTo(3L); c.put(bb(2), bb(1), MDB_APPENDDUP); c.put(bb(2), bb(2), MDB_APPENDDUP); - assertThat(c.count(), is(2L)); + assertThat(c.count()).isEqualTo(2L); } } - @Test(expected = NotReadyException.class) - public void cursorCannotCloseIfTransactionCommitted() { - final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); - try (Txn txn = env.txnWrite()) { - try (Cursor c = db.openCursor(txn); ) { - c.put(bb(1), bb(2), MDB_APPENDDUP); - assertThat(c.count(), is(1L)); - c.put(bb(1), bb(4), MDB_APPENDDUP); - assertThat(c.count(), is(2L)); - txn.commit(); - } - } + @Test + void cursorCannotCloseIfTransactionCommitted() { + assertThatThrownBy( + () -> { + final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); + try (Txn txn = env.txnWrite()) { + try (Cursor c = db.openCursor(txn); ) { + c.put(bb(1), bb(2), MDB_APPENDDUP); + assertThat(c.count()).isEqualTo(1L); + c.put(bb(1), bb(4), MDB_APPENDDUP); + assertThat(c.count()).isEqualTo(2L); + txn.commit(); + } + } + }) + .isInstanceOf(NotReadyException.class); } @Test - public void cursorFirstLastNextPrev() { + void cursorFirstLastNextPrev() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite(); Cursor c = db.openCursor(txn)) { @@ -184,46 +217,46 @@ public void cursorFirstLastNextPrev() { c.put(bb(5), bb(6)); c.put(bb(7), bb(8)); - assertThat(c.first(), is(true)); - assertThat(c.key().getInt(0), is(1)); - assertThat(c.val().getInt(0), is(2)); + assertThat(c.first()).isTrue(); + assertThat(c.key().getInt(0)).isEqualTo(1); + assertThat(c.val().getInt(0)).isEqualTo(2); - assertThat(c.last(), is(true)); - assertThat(c.key().getInt(0), is(7)); - assertThat(c.val().getInt(0), is(8)); + assertThat(c.last()).isTrue(); + assertThat(c.key().getInt(0)).isEqualTo(7); + assertThat(c.val().getInt(0)).isEqualTo(8); - assertThat(c.prev(), is(true)); - assertThat(c.key().getInt(0), is(5)); - assertThat(c.val().getInt(0), is(6)); + assertThat(c.prev()).isTrue(); + assertThat(c.key().getInt(0)).isEqualTo(5); + assertThat(c.val().getInt(0)).isEqualTo(6); - assertThat(c.first(), is(true)); - assertThat(c.next(), is(true)); - assertThat(c.key().getInt(0), is(3)); - assertThat(c.val().getInt(0), is(4)); + assertThat(c.first()).isTrue(); + assertThat(c.next()).isTrue(); + assertThat(c.key().getInt(0)).isEqualTo(3); + assertThat(c.val().getInt(0)).isEqualTo(4); } } @Test - public void delete() { + void delete() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); try (Txn txn = env.txnWrite(); Cursor c = db.openCursor(txn)) { c.put(bb(1), bb(2), MDB_NOOVERWRITE); c.put(bb(3), bb(4)); - assertThat(c.seek(MDB_FIRST), is(true)); - assertThat(c.key().getInt(), is(1)); - assertThat(c.val().getInt(), is(2)); + assertThat(c.seek(MDB_FIRST)).isTrue(); + assertThat(c.key().getInt()).isEqualTo(1); + assertThat(c.val().getInt()).isEqualTo(2); c.delete(); - assertThat(c.seek(MDB_FIRST), is(true)); - assertThat(c.key().getInt(), is(3)); - assertThat(c.val().getInt(), is(4)); + assertThat(c.seek(MDB_FIRST)).isTrue(); + assertThat(c.key().getInt()).isEqualTo(3); + assertThat(c.val().getInt()).isEqualTo(4); c.delete(); - assertThat(c.seek(MDB_FIRST), is(false)); + assertThat(c.seek(MDB_FIRST)).isFalse(); } } @Test - public void getKeyVal() { + void getKeyVal() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); try (Txn txn = env.txnWrite(); Cursor c = db.openCursor(txn)) { @@ -234,17 +267,17 @@ public void getKeyVal() { c.put(bb(2), bb(2), MDB_APPENDDUP); c.put(bb(2), bb(3), MDB_APPENDDUP); c.put(bb(2), bb(4), MDB_APPENDDUP); - assertThat(c.get(bb(1), bb(2), MDB_GET_BOTH), is(true)); - assertThat(c.count(), is(3L)); - assertThat(c.get(bb(1), bb(3), MDB_GET_BOTH), is(false)); - assertThat(c.get(bb(2), bb(1), MDB_GET_BOTH), is(true)); - assertThat(c.count(), is(4L)); - assertThat(c.get(bb(2), bb(0), MDB_GET_BOTH), is(false)); + assertThat(c.get(bb(1), bb(2), MDB_GET_BOTH)).isTrue(); + assertThat(c.count()).isEqualTo(3L); + assertThat(c.get(bb(1), bb(3), MDB_GET_BOTH)).isFalse(); + assertThat(c.get(bb(2), bb(1), MDB_GET_BOTH)).isTrue(); + assertThat(c.count()).isEqualTo(4L); + assertThat(c.get(bb(2), bb(0), MDB_GET_BOTH)).isFalse(); } } @Test - public void putMultiple() { + void putMultiple() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT, MDB_DUPFIXED); final int elemCount = 20; @@ -259,21 +292,25 @@ public void putMultiple() { try (Txn txn = env.txnWrite(); Cursor c = db.openCursor(txn)) { c.putMultiple(k, values, elemCount, MDB_MULTIPLE); - assertThat(c.count(), is((long) elemCount)); + assertThat(c.count()).isEqualTo((long) elemCount); } } - @Test(expected = IllegalArgumentException.class) - public void putMultipleWithoutMdbMultipleFlag() { - final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); - try (Txn txn = env.txnWrite(); - Cursor c = db.openCursor(txn)) { - c.putMultiple(bb(100), bb(1), 1); - } + @Test + void putMultipleWithoutMdbMultipleFlag() { + assertThatThrownBy( + () -> { + final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); + try (Txn txn = env.txnWrite(); + Cursor c = db.openCursor(txn)) { + c.putMultiple(bb(100), bb(1), 1); + } + }) + .isInstanceOf(IllegalArgumentException.class); } @Test - public void renewTxRo() { + void renewTxRo() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); final Cursor c; @@ -290,20 +327,24 @@ public void renewTxRo() { c.close(); } - @Test(expected = ReadOnlyRequiredException.class) - public void renewTxRw() { - final Dbi db = env.openDbi(DB_1, MDB_CREATE); - try (Txn txn = env.txnWrite()) { - assertThat(txn.isReadOnly(), is(false)); - - try (Cursor c = db.openCursor(txn)) { - c.renew(txn); - } - } + @Test + void renewTxRw() { + assertThatThrownBy( + () -> { + final Dbi db = env.openDbi(DB_1, MDB_CREATE); + try (Txn txn = env.txnWrite()) { + assertThat(txn.isReadOnly()).isFalse(); + + try (Cursor c = db.openCursor(txn)) { + c.renew(txn); + } + } + }) + .isInstanceOf(ReadOnlyRequiredException.class); } @Test - public void repeatedCloseCausesNotError() { + void repeatedCloseCausesNotError() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); try (Txn txn = env.txnWrite()) { final Cursor c = db.openCursor(txn); @@ -313,52 +354,52 @@ public void repeatedCloseCausesNotError() { } @Test - public void reserve() { + void reserve() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); final ByteBuffer key = bb(5); try (Txn txn = env.txnWrite()) { - assertNull(db.get(txn, key)); + assertThat(db.get(txn, key)).isNull(); try (Cursor c = db.openCursor(txn)) { final ByteBuffer val = c.reserve(key, BYTES * 2); - assertNotNull(db.get(txn, key)); + assertThat(db.get(txn, key)).isNotNull(); val.putLong(MIN_VALUE).flip(); } txn.commit(); } try (Txn txn = env.txnWrite()) { final ByteBuffer val = db.get(txn, key); - assertThat(val.capacity(), is(BYTES * 2)); - assertThat(val.getLong(), is(MIN_VALUE)); + assertThat(val.capacity()).isEqualTo(BYTES * 2); + assertThat(val.getLong()).isEqualTo(MIN_VALUE); } } @Test - public void returnValueForNoDupData() { + void returnValueForNoDupData() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); try (Txn txn = env.txnWrite(); Cursor c = db.openCursor(txn)) { // ok - assertThat(c.put(bb(5), bb(6), MDB_NODUPDATA), is(true)); - assertThat(c.put(bb(5), bb(7), MDB_NODUPDATA), is(true)); - assertThat(c.put(bb(5), bb(6), MDB_NODUPDATA), is(false)); + assertThat(c.put(bb(5), bb(6), MDB_NODUPDATA)).isTrue(); + assertThat(c.put(bb(5), bb(7), MDB_NODUPDATA)).isTrue(); + assertThat(c.put(bb(5), bb(6), MDB_NODUPDATA)).isFalse(); } } @Test - public void returnValueForNoOverwrite() { + void returnValueForNoOverwrite() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite(); Cursor c = db.openCursor(txn)) { // ok - assertThat(c.put(bb(5), bb(6), MDB_NOOVERWRITE), is(true)); + assertThat(c.put(bb(5), bb(6), MDB_NOOVERWRITE)).isTrue(); // fails, but gets exist val - assertThat(c.put(bb(5), bb(8), MDB_NOOVERWRITE), is(false)); - assertThat(c.val().getInt(0), is(6)); + assertThat(c.put(bb(5), bb(8), MDB_NOOVERWRITE)).isFalse(); + assertThat(c.val().getInt(0)).isEqualTo(6); } } @Test - public void testCursorByteBufferDuplicate() { + void testCursorByteBufferDuplicate() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite()) { try (Cursor c = db.openCursor(txn)) { @@ -377,11 +418,11 @@ public void testCursorByteBufferDuplicate() { final ByteBuffer key2 = c.key().duplicate(); final ByteBuffer val2 = c.val().duplicate(); - assertThat(key1.getInt(0), is(1)); - assertThat(val1.getInt(0), is(2)); + assertThat(key1.getInt(0)).isEqualTo(1); + assertThat(val1.getInt(0)).isEqualTo(2); - assertThat(key2.getInt(0), is(3)); - assertThat(val2.getInt(0), is(4)); + assertThat(key2.getInt(0)).isEqualTo(3); + assertThat(val2.getInt(0)).isEqualTo(4); } } } diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 75c23931..844a819e 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.lmdbjava; import static com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; @@ -24,16 +25,8 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.stream.Collectors.toList; import static java.util.stream.IntStream.range; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.collection.IsEmptyCollection.empty; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.lmdbjava.ByteArrayProxy.PROXY_BA; import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; import static org.lmdbjava.DbiFlags.MDB_CREATE; @@ -46,11 +39,13 @@ import static org.lmdbjava.KeyRange.atMost; import static org.lmdbjava.PutFlags.MDB_NODUPDATA; import static org.lmdbjava.PutFlags.MDB_NOOVERWRITE; -import static org.lmdbjava.TestUtils.*; +import static org.lmdbjava.TestUtils.DB_1; +import static org.lmdbjava.TestUtils.ba; +import static org.lmdbjava.TestUtils.bb; +import static org.lmdbjava.TestUtils.fromBa; -import java.io.File; -import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Comparator; import java.util.Iterator; @@ -61,13 +56,13 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.*; -import org.hamcrest.Matchers; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.function.ToIntFunction; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.lmdbjava.CursorIterable.KeyVal; import org.lmdbjava.Dbi.DbFullException; import org.lmdbjava.Env.AlreadyClosedException; @@ -77,43 +72,51 @@ /** Test {@link Dbi}. */ public final class DbiTest { - @Rule public final TemporaryFolder tmp = new TemporaryFolder(); + private Path file; private Env env; + private Path fileBa; private Env envBa; - @After - public void after() { - env.close(); - } - - @Before - public void before() throws IOException { - final File path = tmp.newFile(); + @BeforeEach + void beforeEach() { + file = FileUtil.createTempFile(); env = create() .setMapSize(MEBIBYTES.toBytes(64)) .setMaxReaders(2) .setMaxDbs(2) - .open(path, MDB_NOSUBDIR); - final File pathBa = tmp.newFile(); + .open(file.toFile(), MDB_NOSUBDIR); + fileBa = FileUtil.createTempFile(); envBa = create(PROXY_BA) .setMapSize(MEBIBYTES.toBytes(64)) .setMaxReaders(2) .setMaxDbs(2) - .open(pathBa, MDB_NOSUBDIR); + .open(fileBa.toFile(), MDB_NOSUBDIR); } - @Test(expected = ConstantDerivedException.class) - public void close() { - final Dbi db = env.openDbi(DB_1, MDB_CREATE); - db.put(bb(1), bb(42)); - db.close(); - db.put(bb(2), bb(42)); // error + @AfterEach + void afterEach() { + env.close(); + envBa.close(); + FileUtil.delete(file); + FileUtil.delete(fileBa); } @Test - public void customComparator() { + void close() { + assertThatThrownBy( + () -> { + final Dbi db = env.openDbi(DB_1, MDB_CREATE); + db.put(bb(1), bb(42)); + db.close(); + db.put(bb(2), bb(42)); // error + }) + .isInstanceOf(ConstantDerivedException.class); + } + + @Test + void customComparator() { final Comparator reverseOrder = (o1, o2) -> { final int lexical = PROXY_OPTIMAL.getComparator().compare(o1, o2); @@ -126,7 +129,7 @@ public void customComparator() { } @Test - public void customComparatorByteArray() { + void customComparatorByteArray() { final Comparator reverseOrder = (o1, o2) -> { final int lexical = PROXY_BA.getComparator().compare(o1, o2); @@ -145,41 +148,45 @@ private void doCustomComparator( ToIntFunction deserializer) { final Dbi db = env.openDbi(DB_1, comparator, true, MDB_CREATE); try (Txn txn = env.txnWrite()) { - assertThat(db.put(txn, serializer.apply(2), serializer.apply(3)), is(true)); - assertThat(db.put(txn, serializer.apply(4), serializer.apply(6)), is(true)); - assertThat(db.put(txn, serializer.apply(6), serializer.apply(7)), is(true)); - assertThat(db.put(txn, serializer.apply(8), serializer.apply(7)), is(true)); + assertThat(db.put(txn, serializer.apply(2), serializer.apply(3))).isTrue(); + assertThat(db.put(txn, serializer.apply(4), serializer.apply(6))).isTrue(); + assertThat(db.put(txn, serializer.apply(6), serializer.apply(7))).isTrue(); + assertThat(db.put(txn, serializer.apply(8), serializer.apply(7))).isTrue(); txn.commit(); } try (Txn txn = env.txnRead(); CursorIterable ci = db.iterate(txn, atMost(serializer.apply(4)))) { final Iterator> iter = ci.iterator(); - assertThat(deserializer.applyAsInt(iter.next().key()), is(8)); - assertThat(deserializer.applyAsInt(iter.next().key()), is(6)); - assertThat(deserializer.applyAsInt(iter.next().key()), is(4)); + assertThat(deserializer.applyAsInt(iter.next().key())).isEqualTo(8); + assertThat(deserializer.applyAsInt(iter.next().key())).isEqualTo(6); + assertThat(deserializer.applyAsInt(iter.next().key())).isEqualTo(4); } } - @Test(expected = DbFullException.class) - public void dbOpenMaxDatabases() { - env.openDbi("db1 is OK", MDB_CREATE); - env.openDbi("db2 is OK", MDB_CREATE); - env.openDbi("db3 fails", MDB_CREATE); + @Test + void dbOpenMaxDatabases() { + assertThatThrownBy( + () -> { + env.openDbi("db1 is OK", MDB_CREATE); + env.openDbi("db2 is OK", MDB_CREATE); + env.openDbi("db3 fails", MDB_CREATE); + }) + .isInstanceOf(DbFullException.class); } @Test - public void dbiWithComparatorThreadSafety() { + void dbiWithComparatorThreadSafety() { doDbiWithComparatorThreadSafety( env, PROXY_OPTIMAL::getComparator, TestUtils::bb, ByteBuffer::getInt); } @Test - public void dbiWithComparatorThreadSafetyByteArray() { + void dbiWithComparatorThreadSafetyByteArray() { doDbiWithComparatorThreadSafety( envBa, PROXY_BA::getComparator, TestUtils::ba, TestUtils::fromBa); } - public void doDbiWithComparatorThreadSafety( + private void doDbiWithComparatorThreadSafety( Env env, Function> comparator, IntFunction serializer, @@ -190,66 +197,67 @@ public void doDbiWithComparatorThreadSafety( final List keys = range(0, 1_000).boxed().collect(toList()); - final ExecutorService pool = Executors.newCachedThreadPool(); - final AtomicBoolean proceed = new AtomicBoolean(true); - final Future reader = - pool.submit( - () -> { - while (proceed.get()) { - try (Txn txn = env.txnRead()) { - db.get(txn, serializer.apply(50)); + try (final ExecutorService pool = Executors.newCachedThreadPool()) { + final AtomicBoolean proceed = new AtomicBoolean(true); + final Future reader = + pool.submit( + () -> { + while (proceed.get()) { + try (Txn txn = env.txnRead()) { + db.get(txn, serializer.apply(50)); + } } - } - }); + }); - for (final Integer key : keys) { - try (Txn txn = env.txnWrite()) { - db.put(txn, serializer.apply(key), serializer.apply(3)); - txn.commit(); + for (final Integer key : keys) { + try (Txn txn = env.txnWrite()) { + db.put(txn, serializer.apply(key), serializer.apply(3)); + txn.commit(); + } } - } - try (Txn txn = env.txnRead(); - CursorIterable ci = db.iterate(txn)) { - final Iterator> iter = ci.iterator(); - final List result = new ArrayList<>(); - while (iter.hasNext()) { - result.add(deserializer.applyAsInt(iter.next().key())); - } + try (Txn txn = env.txnRead(); + CursorIterable ci = db.iterate(txn)) { + final Iterator> iter = ci.iterator(); + final List result = new ArrayList<>(); + while (iter.hasNext()) { + result.add(deserializer.applyAsInt(iter.next().key())); + } - assertThat(result, Matchers.contains(keys.toArray(new Integer[0]))); - } + assertThat(result).contains(keys.toArray(new Integer[0])); + } - proceed.set(false); - try { - reader.get(1, SECONDS); - pool.shutdown(); - pool.awaitTermination(1, SECONDS); - } catch (ExecutionException | InterruptedException | TimeoutException e) { - throw new IllegalStateException(e); + proceed.set(false); + try { + reader.get(1, SECONDS); + pool.shutdown(); + pool.awaitTermination(1, SECONDS); + } catch (ExecutionException | InterruptedException | TimeoutException e) { + throw new IllegalStateException(e); + } } } @Test - public void drop() { + void drop() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite()) { db.put(txn, bb(1), bb(42)); db.put(txn, bb(2), bb(42)); - assertThat(db.get(txn, bb(1)), not(nullValue())); - assertThat(db.get(txn, bb(2)), not(nullValue())); + assertThat(db.get(txn, bb(1))).isNotNull(); + assertThat(db.get(txn, bb(2))).isNotNull(); db.drop(txn); - assertThat(db.get(txn, bb(1)), is(nullValue())); // data gone - assertThat(db.get(txn, bb(2)), is(nullValue())); + assertThat(db.get(txn, bb(1))).isNull(); // data gone + assertThat(db.get(txn, bb(2))).isNull(); db.put(txn, bb(1), bb(42)); // ensure DB still works db.put(txn, bb(2), bb(42)); - assertThat(db.get(txn, bb(1)), not(nullValue())); - assertThat(db.get(txn, bb(2)), not(nullValue())); + assertThat(db.get(txn, bb(1))).isNotNull(); + assertThat(db.get(txn, bb(2))).isNotNull(); } } @Test - public void dropAndDelete() { + void dropAndDelete() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); final Dbi nameDb = env.openDbi((byte[]) null); final byte[] dbNameBytes = DB_1.getBytes(UTF_8); @@ -257,15 +265,15 @@ public void dropAndDelete() { dbNameBuffer.put(dbNameBytes).flip(); try (Txn txn = env.txnWrite()) { - assertThat(nameDb.get(txn, dbNameBuffer), not(nullValue())); + assertThat(nameDb.get(txn, dbNameBuffer)).isNotNull(); db.drop(txn, true); - assertThat(nameDb.get(txn, dbNameBuffer), is(nullValue())); + assertThat(nameDb.get(txn, dbNameBuffer)).isNull(); txn.commit(); } } @Test - public void dropAndDeleteAnonymousDb() { + void dropAndDeleteAnonymousDb() { env.openDbi(DB_1, MDB_CREATE); final Dbi nameDb = env.openDbi((byte[]) null); final byte[] dbNameBytes = DB_1.getBytes(UTF_8); @@ -273,9 +281,9 @@ public void dropAndDeleteAnonymousDb() { dbNameBuffer.put(dbNameBytes).flip(); try (Txn txn = env.txnWrite()) { - assertThat(nameDb.get(txn, dbNameBuffer), not(nullValue())); + assertThat(nameDb.get(txn, dbNameBuffer)).isNotNull(); nameDb.drop(txn, true); - assertThat(nameDb.get(txn, dbNameBuffer), is(nullValue())); + assertThat(nameDb.get(txn, dbNameBuffer)).isNull(); txn.commit(); } @@ -283,41 +291,41 @@ public void dropAndDeleteAnonymousDb() { } @Test - public void getName() { + void getName() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); - assertThat(db.getName(), is(DB_1.getBytes(UTF_8))); + assertThat(db.getName()).isEqualTo(DB_1.getBytes(UTF_8)); } @Test - public void getNamesWhenDbisPresent() { + void getNamesWhenDbisPresent() { final byte[] dbHello = new byte[] {'h', 'e', 'l', 'l', 'o'}; final byte[] dbWorld = new byte[] {'w', 'o', 'r', 'l', 'd'}; env.openDbi(dbHello, MDB_CREATE); env.openDbi(dbWorld, MDB_CREATE); final List dbiNames = env.getDbiNames(); - assertThat(dbiNames, hasSize(2)); - assertThat(dbiNames.get(0), is(dbHello)); - assertThat(dbiNames.get(1), is(dbWorld)); + assertThat(dbiNames).hasSize(2); + assertThat(dbiNames.get(0)).isEqualTo(dbHello); + assertThat(dbiNames.get(1)).isEqualTo(dbWorld); } @Test - public void getNamesWhenEmpty() { + void getNamesWhenEmpty() { final List dbiNames = env.getDbiNames(); - assertThat(dbiNames, empty()); + assertThat(dbiNames).isEmpty(); } @Test - public void listsFlags() { + void listsFlags() { final Dbi dbi = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT, MDB_REVERSEKEY); try (Txn txn = env.txnRead()) { final List flags = dbi.listFlags(txn); - assertThat(flags, containsInAnyOrder(MDB_DUPSORT, MDB_REVERSEKEY)); + assertThat(flags).containsExactlyInAnyOrder(MDB_DUPSORT, MDB_REVERSEKEY); } } @Test - public void putAbortGet() { + void putAbortGet() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite()) { @@ -326,30 +334,30 @@ public void putAbortGet() { } try (Txn txn = env.txnWrite()) { - assertNull(db.get(txn, bb(5))); + assertThat(db.get(txn, bb(5))).isNull(); } } @Test - public void putAndGetAndDeleteWithInternalTx() { + void putAndGetAndDeleteWithInternalTx() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); db.put(bb(5), bb(5)); try (Txn txn = env.txnRead()) { final ByteBuffer found = db.get(txn, bb(5)); - assertNotNull(found); - assertThat(txn.val().getInt(), is(5)); + assertThat(found).isNotNull(); + assertThat(txn.val().getInt()).isEqualTo(5); } - assertThat(db.delete(bb(5)), is(true)); - assertThat(db.delete(bb(5)), is(false)); + assertThat(db.delete(bb(5))).isTrue(); + assertThat(db.delete(bb(5))).isFalse(); try (Txn txn = env.txnRead()) { - assertNull(db.get(txn, bb(5))); + assertThat(db.get(txn, bb(5))).isNull(); } } @Test - public void putCommitGet() { + void putCommitGet() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite()) { db.put(txn, bb(5), bb(5)); @@ -358,90 +366,93 @@ public void putCommitGet() { try (Txn txn = env.txnWrite()) { final ByteBuffer found = db.get(txn, bb(5)); - assertNotNull(found); - assertThat(txn.val().getInt(), is(5)); + assertThat(found).isNotNull(); + assertThat(txn.val().getInt()).isEqualTo(5); } } @Test - public void putCommitGetByteArray() throws IOException { - final File path = tmp.newFile(); - try (Env envBa = - create(PROXY_BA) - .setMapSize(MEBIBYTES.toBytes(64)) - .setMaxReaders(1) - .setMaxDbs(2) - .open(path, MDB_NOSUBDIR)) { - final Dbi db = envBa.openDbi(DB_1, MDB_CREATE); - try (Txn txn = envBa.txnWrite()) { - db.put(txn, ba(5), ba(5)); - txn.commit(); - } - try (Txn txn = envBa.txnWrite()) { - final byte[] found = db.get(txn, ba(5)); - assertNotNull(found); - assertThat(fromBa(txn.val()), is(5)); - } - } + void putCommitGetByteArray() { + FileUtil.useTempFile( + file -> { + try (Env envBa = + create(PROXY_BA) + .setMapSize(MEBIBYTES.toBytes(64)) + .setMaxReaders(1) + .setMaxDbs(2) + .open(file.toFile(), MDB_NOSUBDIR)) { + final Dbi db = envBa.openDbi(DB_1, MDB_CREATE); + try (Txn txn = envBa.txnWrite()) { + db.put(txn, ba(5), ba(5)); + txn.commit(); + } + try (Txn txn = envBa.txnWrite()) { + final byte[] found = db.get(txn, ba(5)); + assertThat(found).isNotNull(); + assertThat(fromBa(txn.val())).isEqualTo(5); + } + } + }); } @Test - public void putDelete() { + void putDelete() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite()) { db.put(txn, bb(5), bb(5)); - assertThat(db.delete(txn, bb(5)), is(true)); + assertThat(db.delete(txn, bb(5))).isTrue(); - assertNull(db.get(txn, bb(5))); + assertThat(db.get(txn, bb(5))).isNull(); txn.abort(); } } @Test - public void putDuplicateDelete() { + void putDuplicateDelete() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); try (Txn txn = env.txnWrite()) { db.put(txn, bb(5), bb(5)); db.put(txn, bb(5), bb(6)); db.put(txn, bb(5), bb(7)); - assertThat(db.delete(txn, bb(5), bb(6)), is(true)); - assertThat(db.delete(txn, bb(5), bb(6)), is(false)); - assertThat(db.delete(txn, bb(5), bb(5)), is(true)); - assertThat(db.delete(txn, bb(5), bb(5)), is(false)); + assertThat(db.delete(txn, bb(5), bb(6))).isTrue(); + assertThat(db.delete(txn, bb(5), bb(6))).isFalse(); + assertThat(db.delete(txn, bb(5), bb(5))).isTrue(); + assertThat(db.delete(txn, bb(5), bb(5))).isFalse(); try (Cursor cursor = db.openCursor(txn)) { final ByteBuffer key = bb(5); cursor.get(key, MDB_SET_KEY); - assertThat(cursor.count(), is(1L)); + assertThat(cursor.count()).isEqualTo(1L); } txn.abort(); } } @Test - public void putReserve() { + void putReserve() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); final ByteBuffer key = bb(5); try (Txn txn = env.txnWrite()) { - assertNull(db.get(txn, key)); + assertThat(db.get(txn, key)).isNull(); final ByteBuffer val = db.reserve(txn, key, 32, MDB_NOOVERWRITE); val.putLong(MAX_VALUE); - assertNotNull(db.get(txn, key)); + assertThat(db.get(txn, key)).isNotNull(); txn.commit(); } try (Txn txn = env.txnWrite()) { final ByteBuffer val = db.get(txn, key); - assertThat(val.capacity(), is(32)); - assertThat(val.getLong(), is(MAX_VALUE)); - assertThat(val.getLong(8), is(0L)); + assertThat(val).isNotNull(); + assertThat(val.capacity()).isEqualTo(32); + assertThat(val.getLong()).isEqualTo(MAX_VALUE); + assertThat(val.getLong(8)).isEqualTo(0L); } } @Test - public void putZeroByteValueForNonMdbDupSortDatabase() { + void putZeroByteValueForNonMdbDupSortDatabase() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite()) { final ByteBuffer val = allocateDirect(0); @@ -451,36 +462,36 @@ public void putZeroByteValueForNonMdbDupSortDatabase() { try (Txn txn = env.txnRead()) { final ByteBuffer found = db.get(txn, bb(5)); - assertNotNull(found); - assertThat(txn.val().capacity(), is(0)); + assertThat(found).isNotNull(); + assertThat(txn.val().capacity()).isEqualTo(0); } } @Test - public void returnValueForNoDupData() { + void returnValueForNoDupData() { final Dbi db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); try (Txn txn = env.txnWrite()) { // ok - assertThat(db.put(txn, bb(5), bb(6), MDB_NODUPDATA), is(true)); - assertThat(db.put(txn, bb(5), bb(7), MDB_NODUPDATA), is(true)); - assertThat(db.put(txn, bb(5), bb(6), MDB_NODUPDATA), is(false)); + assertThat(db.put(txn, bb(5), bb(6), MDB_NODUPDATA)).isTrue(); + assertThat(db.put(txn, bb(5), bb(7), MDB_NODUPDATA)).isTrue(); + assertThat(db.put(txn, bb(5), bb(6), MDB_NODUPDATA)).isFalse(); } } @Test - public void returnValueForNoOverwrite() { + void returnValueForNoOverwrite() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); try (Txn txn = env.txnWrite()) { // ok - assertThat(db.put(txn, bb(5), bb(6), MDB_NOOVERWRITE), is(true)); + assertThat(db.put(txn, bb(5), bb(6), MDB_NOOVERWRITE)).isTrue(); // fails, but gets exist val - assertThat(db.put(txn, bb(5), bb(8), MDB_NOOVERWRITE), is(false)); - assertThat(txn.val().getInt(0), is(6)); + assertThat(db.put(txn, bb(5), bb(8), MDB_NOOVERWRITE)).isFalse(); + assertThat(txn.val().getInt(0)).isEqualTo(6); } } @Test - public void stats() { + void stats() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); db.put(bb(1), bb(42)); db.put(bb(2), bb(42)); @@ -489,32 +500,36 @@ public void stats() { try (Txn txn = env.txnRead()) { stat = db.stat(txn); } - assertThat(stat, is(notNullValue())); - assertThat(stat.branchPages, is(0L)); - assertThat(stat.depth, is(1)); - assertThat(stat.entries, is(3L)); - assertThat(stat.leafPages, is(1L)); - assertThat(stat.overflowPages, is(0L)); - assertThat(stat.pageSize % 4_096, is(0)); + assertThat(stat).isNotNull(); + assertThat(stat.branchPages).isEqualTo(0L); + assertThat(stat.depth).isEqualTo(1); + assertThat(stat.entries).isEqualTo(3L); + assertThat(stat.leafPages).isEqualTo(1L); + assertThat(stat.overflowPages).isEqualTo(0L); + assertThat(stat.pageSize % 4_096).isEqualTo(0); } - @Test(expected = MapFullException.class) - public void testMapFullException() { - final Dbi db = env.openDbi(DB_1, MDB_CREATE); - try (Txn txn = env.txnWrite()) { - final ByteBuffer v; - try { - v = allocateDirect(1_024 * 1_024 * 1_024); - } catch (final OutOfMemoryError e) { - // Travis CI OS X build cannot allocate this much memory, so assume OK - throw new MapFullException(); - } - db.put(txn, bb(1), v); - } + @Test + void testMapFullException() { + assertThatThrownBy( + () -> { + final Dbi db = env.openDbi(DB_1, MDB_CREATE); + try (Txn txn = env.txnWrite()) { + final ByteBuffer v; + try { + v = allocateDirect(1_024 * 1_024 * 1_024); + } catch (final OutOfMemoryError e) { + // Travis CI OS X build cannot allocate this much memory, so assume OK + throw new MapFullException(); + } + db.put(txn, bb(1), v); + } + }) + .isInstanceOf(MapFullException.class); } @Test - public void testParallelWritesStress() { + void testParallelWritesStress() { if (getProperty("os.name").startsWith("Windows")) { return; // Windows VMs run this test too slowly } @@ -531,69 +546,113 @@ public void testParallelWritesStress() { }); } - @Test(expected = AlreadyClosedException.class) - public void closedEnvRejectsOpenCall() { - env.close(); - env.openDbi(DB_1, MDB_CREATE); + @Test + void closedEnvRejectsOpenCall() { + assertThatThrownBy( + () -> { + env.close(); + env.openDbi(DB_1, MDB_CREATE); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void closedEnvRejectsCloseCall() { - doEnvClosedTest(null, (db, txn) -> db.close()); + @Test + void closedEnvRejectsCloseCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest(null, (db, txn) -> db.close()); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void closedEnvRejectsGetCall() { - doEnvClosedTest( - (db, txn) -> { - final ByteBuffer valBuf = db.get(txn, bb(1)); - assertThat(valBuf.getInt(), is(10)); - }, - (db, txn) -> db.get(txn, bb(2))); + @Test + void closedEnvRejectsGetCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest( + (db, txn) -> { + final ByteBuffer valBuf = db.get(txn, bb(1)); + assertThat(valBuf.getInt()).isEqualTo(10); + }, + (db, txn) -> db.get(txn, bb(2))); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void closedEnvRejectsPutCall() { - doEnvClosedTest(null, (db, txn) -> db.put(bb(5), bb(50))); + @Test + void closedEnvRejectsPutCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest(null, (db, txn) -> db.put(bb(5), bb(50))); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void closedEnvRejectsPutWithTxnCall() { - doEnvClosedTest( - null, - (db, txn) -> { - db.put(txn, bb(5), bb(50)); - }); + @Test + void closedEnvRejectsPutWithTxnCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest( + null, + (db, txn) -> { + db.put(txn, bb(5), bb(50)); + }); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void closedEnvRejectsIterateCall() { - doEnvClosedTest(null, Dbi::iterate); + @Test + void closedEnvRejectsIterateCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest(null, Dbi::iterate); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void closedEnvRejectsDropCall() { - doEnvClosedTest(null, Dbi::drop); + @Test + void closedEnvRejectsDropCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest(null, Dbi::drop); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void closedEnvRejectsDropAndDeleteCall() { - doEnvClosedTest(null, (db, txn) -> db.drop(txn, true)); + @Test + void closedEnvRejectsDropAndDeleteCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest(null, (db, txn) -> db.drop(txn, true)); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void closedEnvRejectsOpenCursorCall() { - doEnvClosedTest(null, Dbi::openCursor); + @Test + void closedEnvRejectsOpenCursorCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest(null, Dbi::openCursor); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void closedEnvRejectsReserveCall() { - doEnvClosedTest(null, (db, txn) -> db.reserve(txn, bb(1), 32, MDB_NOOVERWRITE)); + @Test + void closedEnvRejectsReserveCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest(null, (db, txn) -> db.reserve(txn, bb(1), 32, MDB_NOOVERWRITE)); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void closedEnvRejectsStatCall() { - doEnvClosedTest(null, Dbi::stat); + @Test + void closedEnvRejectsStatCall() { + assertThatThrownBy( + () -> { + doEnvClosedTest(null, Dbi::stat); + }) + .isInstanceOf(AlreadyClosedException.class); } private void doEnvClosedTest( diff --git a/src/test/java/org/lmdbjava/EnvTest.java b/src/test/java/org/lmdbjava/EnvTest.java index e71c6ae9..87edd049 100644 --- a/src/test/java/org/lmdbjava/EnvTest.java +++ b/src/test/java/org/lmdbjava/EnvTest.java @@ -13,16 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.lmdbjava; import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; import static com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; import static java.nio.ByteBuffer.allocateDirect; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.greaterThan; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.lmdbjava.CopyFlags.MDB_CP_COMPACT; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.Env.Builder.MAX_READERS_DEFAULT; @@ -33,13 +31,13 @@ import static org.lmdbjava.TestUtils.DB_1; import static org.lmdbjava.TestUtils.bb; -import java.io.File; import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Random; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.Test; import org.lmdbjava.Env.AlreadyClosedException; import org.lmdbjava.Env.AlreadyOpenException; import org.lmdbjava.Env.Builder; @@ -50,306 +48,453 @@ /** Test {@link Env}. */ public final class EnvTest { - @Rule public final TemporaryFolder tmp = new TemporaryFolder(); - @Test - public void byteUnit() throws IOException { - final File path = tmp.newFile(); - try (Env env = - create().setMaxReaders(1).setMapSize(MEBIBYTES.toBytes(1)).open(path, MDB_NOSUBDIR)) { - final EnvInfo info = env.info(); - assertThat(info.mapSize, is(MEBIBYTES.toBytes(1))); - } + void byteUnit() { + FileUtil.useTempFile( + file -> { + try (Env env = + create() + .setMaxReaders(1) + .setMapSize(MEBIBYTES.toBytes(1)) + .open(file.toFile(), MDB_NOSUBDIR)) { + final EnvInfo info = env.info(); + assertThat(info.mapSize).isEqualTo(MEBIBYTES.toBytes(1)); + } + }); } - @Test(expected = AlreadyOpenException.class) - public void cannotChangeMapSizeAfterOpen() throws IOException { - final File path = tmp.newFile(); - final Builder builder = create().setMaxReaders(1); - try (Env env = builder.open(path, MDB_NOSUBDIR)) { - builder.setMapSize(1); - } + @Test + void cannotChangeMapSizeAfterOpen() { + assertThatThrownBy( + () -> { + FileUtil.useTempFile( + file -> { + final Builder builder = create().setMaxReaders(1); + try (Env env = builder.open(file.toFile(), MDB_NOSUBDIR)) { + builder.setMapSize(1); + } + }); + }) + .isInstanceOf(AlreadyOpenException.class); } - @Test(expected = AlreadyOpenException.class) - public void cannotChangeMaxDbsAfterOpen() throws IOException { - final File path = tmp.newFile(); - final Builder builder = create().setMaxReaders(1); - try (Env env = builder.open(path, MDB_NOSUBDIR)) { - builder.setMaxDbs(1); - } + @Test + void cannotChangeMaxDbsAfterOpen() { + assertThatThrownBy( + () -> { + FileUtil.useTempFile( + file -> { + final Builder builder = create().setMaxReaders(1); + try (Env env = builder.open(file.toFile(), MDB_NOSUBDIR)) { + builder.setMaxDbs(1); + } + }); + }) + .isInstanceOf(AlreadyOpenException.class); } - @Test(expected = AlreadyOpenException.class) - public void cannotChangeMaxReadersAfterOpen() throws IOException { - final File path = tmp.newFile(); - final Builder builder = create().setMaxReaders(1); - try (Env env = builder.open(path, MDB_NOSUBDIR)) { - builder.setMaxReaders(1); - } + @Test + void cannotChangeMaxReadersAfterOpen() { + assertThatThrownBy( + () -> { + FileUtil.useTempFile( + file -> { + final Builder builder = create().setMaxReaders(1); + try (Env env = builder.open(file.toFile(), MDB_NOSUBDIR)) { + builder.setMaxReaders(1); + } + }); + }) + .isInstanceOf(AlreadyOpenException.class); } - @Test(expected = AlreadyClosedException.class) - public void cannotInfoOnceClosed() throws IOException { - final File path = tmp.newFile(); - final Env env = create().setMaxReaders(1).open(path, MDB_NOSUBDIR); - env.close(); - env.info(); + @Test + void cannotInfoOnceClosed() { + assertThatThrownBy( + () -> { + FileUtil.useTempFile( + file -> { + final Env env = + create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR); + env.close(); + env.info(); + }); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyOpenException.class) - public void cannotOpenTwice() throws IOException { - final File path = tmp.newFile(); - final Builder builder = create().setMaxReaders(1); - - builder.open(path, MDB_NOSUBDIR).close(); - builder.open(path, MDB_NOSUBDIR); + @Test + void cannotOpenTwice() { + assertThatThrownBy( + () -> { + FileUtil.useTempFile( + file -> { + final Builder builder = create().setMaxReaders(1); + builder.open(file.toFile(), MDB_NOSUBDIR).close(); + builder.open(file.toFile(), MDB_NOSUBDIR); + }); + }) + .isInstanceOf(AlreadyOpenException.class); } - @Test(expected = IllegalArgumentException.class) - public void cannotOverflowMapSize() { - final Builder builder = create().setMaxReaders(1); - final int mb = 1_024 * 1_024; - final int size = mb * 2_048; // as per issue 18 - builder.setMapSize(size); + @Test + void cannotOverflowMapSize() { + assertThatThrownBy( + () -> { + final Builder builder = create().setMaxReaders(1); + final int mb = 1_024 * 1_024; + final int size = mb * 2_048; // as per issue 18 + builder.setMapSize(size); + }) + .isInstanceOf(IllegalArgumentException.class); } - @Test(expected = AlreadyClosedException.class) - public void cannotStatOnceClosed() throws IOException { - final File path = tmp.newFile(); - final Env env = create().setMaxReaders(1).open(path, MDB_NOSUBDIR); - env.close(); - env.stat(); + @Test + void cannotStatOnceClosed() { + assertThatThrownBy( + () -> { + FileUtil.useTempFile( + file -> { + final Env env = + create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR); + env.close(); + env.stat(); + }); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void cannotSyncOnceClosed() throws IOException { - final File path = tmp.newFile(); - final Env env = create().setMaxReaders(1).open(path, MDB_NOSUBDIR); - env.close(); - env.sync(false); + @Test + void cannotSyncOnceClosed() { + assertThatThrownBy( + () -> { + FileUtil.useTempFile( + file -> { + final Env env = + create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR); + env.close(); + env.sync(false); + }); + }) + .isInstanceOf(AlreadyClosedException.class); } @Test - public void copyDirectoryBased() throws IOException { - final File dest = tmp.newFolder(); - assertThat(dest.exists(), is(true)); - assertThat(dest.isDirectory(), is(true)); - assertThat(dest.list().length, is(0)); - final File src = tmp.newFolder(); - try (Env env = create().setMaxReaders(1).open(src)) { - env.copy(dest, MDB_CP_COMPACT); - assertThat(dest.list().length, is(1)); - } + void copyDirectoryBased() { + FileUtil.useTempDir( + dest -> { + assertThat(Files.exists(dest)).isTrue(); + assertThat(Files.isDirectory(dest)).isTrue(); + assertThat(FileUtil.count(dest)).isEqualTo(0); + FileUtil.useTempDir( + src -> { + try (Env env = create().setMaxReaders(1).open(src.toFile())) { + env.copy(dest.toFile(), MDB_CP_COMPACT); + assertThat(FileUtil.count(dest)).isEqualTo(1); + } + }); + }); } - @Test(expected = InvalidCopyDestination.class) - public void copyDirectoryRejectsFileDestination() throws IOException { - final File dest = tmp.newFile(); - final File src = tmp.newFolder(); - try (Env env = create().setMaxReaders(1).open(src)) { - env.copy(dest, MDB_CP_COMPACT); - } + @Test + void copyDirectoryRejectsFileDestination() { + assertThatThrownBy( + () -> { + FileUtil.useTempDir( + dest -> { + FileUtil.deleteDir(dest); + FileUtil.useTempDir( + src -> { + try (Env env = create().setMaxReaders(1).open(src.toFile())) { + env.copy(dest.toFile(), MDB_CP_COMPACT); + } + }); + }); + }) + .isInstanceOf(InvalidCopyDestination.class); } - @Test(expected = InvalidCopyDestination.class) - public void copyDirectoryRejectsMissingDestination() throws IOException { - final File dest = tmp.newFolder(); - assertThat(dest.delete(), is(true)); - final File src = tmp.newFolder(); - try (Env env = create().setMaxReaders(1).open(src)) { - env.copy(dest, MDB_CP_COMPACT); - } + @Test + void copyDirectoryRejectsMissingDestination() { + assertThatThrownBy( + () -> { + FileUtil.useTempDir( + dest -> { + try { + Files.delete(dest); + FileUtil.useTempDir( + src -> { + try (Env env = + create().setMaxReaders(1).open(src.toFile())) { + env.copy(dest.toFile(), MDB_CP_COMPACT); + } + }); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + }); + }) + .isInstanceOf(InvalidCopyDestination.class); } - @Test(expected = InvalidCopyDestination.class) - public void copyDirectoryRejectsNonEmptyDestination() throws IOException { - final File dest = tmp.newFolder(); - final File subDir = new File(dest, "hello"); - assertThat(subDir.mkdir(), is(true)); - final File src = tmp.newFolder(); - try (Env env = create().setMaxReaders(1).open(src)) { - env.copy(dest, MDB_CP_COMPACT); - } + @Test + void copyDirectoryRejectsNonEmptyDestination() { + assertThatThrownBy( + () -> { + FileUtil.useTempDir( + dest -> { + try { + final Path subDir = dest.resolve("hello"); + Files.createDirectory(subDir); + assertThat(Files.isDirectory(subDir)).isTrue(); + FileUtil.useTempDir( + src -> { + try (Env env = + create().setMaxReaders(1).open(src.toFile())) { + env.copy(dest.toFile(), MDB_CP_COMPACT); + } + }); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + }); + }) + .isInstanceOf(InvalidCopyDestination.class); } @Test - public void copyFileBased() throws IOException { - final File dest = tmp.newFile(); - assertThat(dest.delete(), is(true)); - assertThat(dest.exists(), is(false)); - final File src = tmp.newFile(); - try (Env env = create().setMaxReaders(1).open(src, MDB_NOSUBDIR)) { - env.copy(dest, MDB_CP_COMPACT); - } - assertThat(dest.length(), greaterThan(0L)); + void copyFileBased() { + FileUtil.useTempFile( + dest -> { + FileUtil.delete(dest); + assertThat(Files.exists(dest)).isFalse(); + FileUtil.useTempFile( + src -> { + try (Env env = + create().setMaxReaders(1).open(src.toFile(), MDB_NOSUBDIR)) { + env.copy(dest.toFile(), MDB_CP_COMPACT); + } + assertThat(FileUtil.size(dest)).isGreaterThan(0L); + }); + }); } - @Test(expected = InvalidCopyDestination.class) - public void copyFileRejectsExistingDestination() throws IOException { - final File dest = tmp.newFile(); - assertThat(dest.exists(), is(true)); - final File src = tmp.newFile(); - try (Env env = create().setMaxReaders(1).open(src, MDB_NOSUBDIR)) { - env.copy(dest, MDB_CP_COMPACT); - } + @Test + void copyFileRejectsExistingDestination() { + assertThatThrownBy( + () -> { + FileUtil.useTempFile( + dest -> { + assertThat(Files.exists(dest)).isTrue(); + FileUtil.useTempFile( + src -> { + try (Env env = + create().setMaxReaders(1).open(src.toFile(), MDB_NOSUBDIR)) { + env.copy(dest.toFile(), MDB_CP_COMPACT); + } + }); + }); + }) + .isInstanceOf(InvalidCopyDestination.class); } @Test - public void createAsDirectory() throws IOException { - final File path = tmp.newFolder(); - final Env env = create().setMaxReaders(1).open(path); - assertThat(path.isDirectory(), is(true)); - env.sync(false); - env.close(); - assertThat(env.isClosed(), is(true)); - env.close(); // safe to repeat + void createAsDirectory() { + FileUtil.useTempDir( + dest -> { + final Env env = create().setMaxReaders(1).open(dest.toFile()); + assertThat(Files.isDirectory(dest)).isTrue(); + env.sync(false); + env.close(); + assertThat(env.isClosed()).isTrue(); + env.close(); // safe to repeat + }); } @Test - public void createAsFile() throws IOException { - final File path = tmp.newFile(); - try (Env env = - create().setMapSize(1_024 * 1_024).setMaxDbs(1).setMaxReaders(1).open(path, MDB_NOSUBDIR)) { - env.sync(true); - assertThat(path.isFile(), is(true)); - } + void createAsFile() { + FileUtil.useTempFile( + file -> { + try (Env env = + create() + .setMapSize(MEBIBYTES.toBytes(1)) + .setMaxDbs(1) + .setMaxReaders(1) + .open(file.toFile(), MDB_NOSUBDIR)) { + env.sync(true); + assertThat(Files.isRegularFile(file)).isTrue(); + } + }); } - @Test(expected = BadReaderLockException.class) - public void detectTransactionThreadViolation() throws IOException { - final File path = tmp.newFile(); - try (Env env = create().setMaxReaders(1).open(path, MDB_NOSUBDIR)) { - env.txnRead(); - env.txnRead(); - } + @Test + void detectTransactionThreadViolation() { + assertThatThrownBy( + () -> { + FileUtil.useTempFile( + file -> { + try (Env env = + create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR)) { + env.txnRead(); + env.txnRead(); + } + }); + }) + .isInstanceOf(BadReaderLockException.class); } @Test - public void info() throws IOException { - final File path = tmp.newFile(); - try (Env env = - create().setMaxReaders(4).setMapSize(123_456).open(path, MDB_NOSUBDIR)) { - final EnvInfo info = env.info(); - assertThat(info, is(notNullValue())); - assertThat(info.lastPageNumber, is(1L)); - assertThat(info.lastTransactionId, is(0L)); - assertThat(info.mapAddress, is(0L)); - assertThat(info.mapSize, is(123_456L)); - assertThat(info.maxReaders, is(4)); - assertThat(info.numReaders, is(0)); - assertThat(info.toString(), containsString("maxReaders=")); - assertThat(env.getMaxKeySize(), is(511)); - } + void info() { + FileUtil.useTempFile( + file -> { + try (Env env = + create().setMaxReaders(4).setMapSize(123_456).open(file.toFile(), MDB_NOSUBDIR)) { + final EnvInfo info = env.info(); + assertThat(info).isNotNull(); + assertThat(info.lastPageNumber).isEqualTo(1L); + assertThat(info.lastTransactionId).isEqualTo(0L); + assertThat(info.mapAddress).isEqualTo(0L); + assertThat(info.mapSize).isEqualTo(123_456L); + assertThat(info.maxReaders).isEqualTo(4); + assertThat(info.numReaders).isEqualTo(0); + assertThat(info.toString()).contains("maxReaders="); + assertThat(env.getMaxKeySize()).isEqualTo(511); + } + }); } - @Test(expected = MapFullException.class) - public void mapFull() throws IOException { - final File path = tmp.newFolder(); - final byte[] k = new byte[500]; - final ByteBuffer key = allocateDirect(500); - final ByteBuffer val = allocateDirect(1_024); - final Random rnd = new Random(); - try (Env env = - create().setMaxReaders(1).setMapSize(MEBIBYTES.toBytes(8)).setMaxDbs(1).open(path)) { - final Dbi db = env.openDbi(DB_1, MDB_CREATE); - for (; ; ) { - rnd.nextBytes(k); - key.clear(); - key.put(k).flip(); - val.clear(); - db.put(key, val); - } - } + @Test + void mapFull() { + assertThatThrownBy( + () -> { + FileUtil.useTempDir( + dir -> { + final byte[] k = new byte[500]; + final ByteBuffer key = allocateDirect(500); + final ByteBuffer val = allocateDirect(1_024); + final Random rnd = new Random(); + try (Env env = + create() + .setMaxReaders(1) + .setMapSize(MEBIBYTES.toBytes(8)) + .setMaxDbs(1) + .open(dir.toFile())) { + final Dbi db = env.openDbi(DB_1, MDB_CREATE); + for (; ; ) { + rnd.nextBytes(k); + key.clear(); + key.put(k).flip(); + val.clear(); + db.put(key, val); + } + } + }); + }) + .isInstanceOf(MapFullException.class); } @Test - public void readOnlySupported() throws IOException { - final File path = tmp.newFolder(); - try (Env rwEnv = create().setMaxReaders(1).open(path)) { - final Dbi rwDb = rwEnv.openDbi(DB_1, MDB_CREATE); - rwDb.put(bb(1), bb(42)); - } - try (Env roEnv = create().setMaxReaders(1).open(path, MDB_RDONLY_ENV)) { - final Dbi roDb = roEnv.openDbi(DB_1); - try (Txn roTxn = roEnv.txnRead()) { - assertThat(roDb.get(roTxn, bb(1)), notNullValue()); - } - } + void readOnlySupported() { + FileUtil.useTempDir( + dir -> { + try (Env rwEnv = create().setMaxReaders(1).open(dir.toFile())) { + final Dbi rwDb = rwEnv.openDbi(DB_1, MDB_CREATE); + rwDb.put(bb(1), bb(42)); + } + try (Env roEnv = + create().setMaxReaders(1).open(dir.toFile(), MDB_RDONLY_ENV)) { + final Dbi roDb = roEnv.openDbi(DB_1); + try (Txn roTxn = roEnv.txnRead()) { + assertThat(roDb.get(roTxn, bb(1))).isNotNull(); + } + } + }); } @Test - public void setMapSize() throws IOException { - final File path = tmp.newFolder(); - final byte[] k = new byte[500]; - final ByteBuffer key = allocateDirect(500); - final ByteBuffer val = allocateDirect(1_024); - final Random rnd = new Random(); - try (Env env = - create().setMaxReaders(1).setMapSize(KIBIBYTES.toBytes(256)).setMaxDbs(1).open(path)) { - final Dbi db = env.openDbi(DB_1, MDB_CREATE); - - db.put(bb(1), bb(42)); - boolean mapFullExThrown = false; - try { - for (int i = 0; i < 70; i++) { - rnd.nextBytes(k); - key.clear(); - key.put(k).flip(); - val.clear(); - db.put(key, val); - } - } catch (final MapFullException mfE) { - mapFullExThrown = true; - } - assertThat(mapFullExThrown, is(true)); - - env.setMapSize(KIBIBYTES.toBytes(1024)); - - try (Txn roTxn = env.txnRead()) { - assertThat(db.get(roTxn, bb(1)).getInt(), is(42)); - } - - mapFullExThrown = false; - try { - for (int i = 0; i < 70; i++) { - rnd.nextBytes(k); - key.clear(); - key.put(k).flip(); - val.clear(); - db.put(key, val); - } - } catch (final MapFullException mfE) { - mapFullExThrown = true; - } - assertThat(mapFullExThrown, is(false)); - } + void setMapSize() { + FileUtil.useTempDir( + dir -> { + final byte[] k = new byte[500]; + final ByteBuffer key = allocateDirect(500); + final ByteBuffer val = allocateDirect(1_024); + final Random rnd = new Random(); + try (Env env = + create() + .setMaxReaders(1) + .setMapSize(KIBIBYTES.toBytes(256)) + .setMaxDbs(1) + .open(dir.toFile())) { + final Dbi db = env.openDbi(DB_1, MDB_CREATE); + + db.put(bb(1), bb(42)); + boolean mapFullExThrown = false; + try { + for (int i = 0; i < 70; i++) { + rnd.nextBytes(k); + key.clear(); + key.put(k).flip(); + val.clear(); + db.put(key, val); + } + } catch (final MapFullException mfE) { + mapFullExThrown = true; + } + assertThat(mapFullExThrown).isTrue(); + + env.setMapSize(KIBIBYTES.toBytes(1024)); + + try (Txn roTxn = env.txnRead()) { + final ByteBuffer byteBuffer = db.get(roTxn, bb(1)); + assertThat(byteBuffer).isNotNull(); + assertThat(byteBuffer.getInt()).isEqualTo(42); + } + + mapFullExThrown = false; + try { + for (int i = 0; i < 70; i++) { + rnd.nextBytes(k); + key.clear(); + key.put(k).flip(); + val.clear(); + db.put(key, val); + } + } catch (final MapFullException mfE) { + mapFullExThrown = true; + } + assertThat(mapFullExThrown).isFalse(); + } + }); } @Test - public void stats() throws IOException { - final File path = tmp.newFile(); - try (Env env = create().setMaxReaders(1).open(path, MDB_NOSUBDIR)) { - final Stat stat = env.stat(); - assertThat(stat, is(notNullValue())); - assertThat(stat.branchPages, is(0L)); - assertThat(stat.depth, is(0)); - assertThat(stat.entries, is(0L)); - assertThat(stat.leafPages, is(0L)); - assertThat(stat.overflowPages, is(0L)); - assertThat(stat.pageSize % 4_096, is(0)); - assertThat(stat.toString(), containsString("pageSize=")); - } + void stats() { + FileUtil.useTempFile( + file -> { + try (Env env = create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR)) { + final Stat stat = env.stat(); + assertThat(stat).isNotNull(); + assertThat(stat.branchPages).isEqualTo(0L); + assertThat(stat.depth).isEqualTo(0); + assertThat(stat.entries).isEqualTo(0L); + assertThat(stat.leafPages).isEqualTo(0L); + assertThat(stat.overflowPages).isEqualTo(0L); + assertThat(stat.pageSize % 4_096).isEqualTo(0); + assertThat(stat.toString()).contains("pageSize="); + } + }); } @Test - public void testDefaultOpen() throws IOException { - final File path = tmp.newFolder(); - try (Env env = open(path, 10)) { - final EnvInfo info = env.info(); - assertThat(info.maxReaders, is(MAX_READERS_DEFAULT)); - final Dbi db = env.openDbi("test", MDB_CREATE); - db.put(allocateDirect(1), allocateDirect(1)); - } + void testDefaultOpen() { + FileUtil.useTempDir( + dir -> { + try (Env env = open(dir.toFile(), 10)) { + final EnvInfo info = env.info(); + assertThat(info.maxReaders).isEqualTo(MAX_READERS_DEFAULT); + final Dbi db = env.openDbi("test", MDB_CREATE); + db.put(allocateDirect(1), allocateDirect(1)); + } + }); } } diff --git a/src/test/java/org/lmdbjava/FileUtil.java b/src/test/java/org/lmdbjava/FileUtil.java new file mode 100644 index 00000000..3e459f14 --- /dev/null +++ b/src/test/java/org/lmdbjava/FileUtil.java @@ -0,0 +1,153 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.FileVisitOption; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; +import java.nio.file.Files; +import java.nio.file.NotDirectoryException; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.EnumSet; +import java.util.function.Consumer; +import java.util.stream.Stream; + +final class FileUtil { + + private FileUtil() {} + + static Path createTempDir() { + try { + return Files.createTempDirectory("lmdbjava"); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } + + static Path createTempFile() { + try { + return Files.createTempFile("lmdbjava", "db"); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } + + static void useTempDir(final Consumer consumer) { + Path path = null; + try { + path = createTempDir(); + consumer.accept(path); + } finally { + if (path != null) { + deleteDir(path); + } + } + } + + static void useTempFile(final Consumer consumer) { + Path path = null; + try { + path = createTempFile(); + consumer.accept(path); + } finally { + if (path != null) { + deleteIfExists(path); + } + } + } + + public static long size(final Path path) { + try { + return Files.size(path); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } + + public static void delete(final Path path) { + try { + Files.delete(path); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } + + public static void deleteDir(final Path path) { + if (path != null && Files.isDirectory(path)) { + recursiveDelete(path); + deleteIfExists(path); + } + } + + private static void recursiveDelete(final Path path) { + try { + Files.walkFileTree( + path, + EnumSet.of(FileVisitOption.FOLLOW_LINKS), + Integer.MAX_VALUE, + new FileVisitor() { + @Override + public FileVisitResult preVisitDirectory( + final Path dir, final BasicFileAttributes attrs) throws IOException { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(final Path file, final IOException exc) + throws IOException { + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) { + deleteIfExists(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) { + if (!dir.equals(path)) { + deleteIfExists(dir); + } + return FileVisitResult.CONTINUE; + } + }); + } catch (final NotDirectoryException e) { + // Ignore. + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } + + private static void deleteIfExists(final Path path) { + try { + Files.deleteIfExists(path); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } + + public static long count(final Path path) { + try (final Stream stream = Files.list(path)) { + return stream.count(); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/src/test/java/org/lmdbjava/GarbageCollectionTest.java b/src/test/java/org/lmdbjava/GarbageCollectionTest.java index 7615f7f0..f0aa64e4 100644 --- a/src/test/java/org/lmdbjava/GarbageCollectionTest.java +++ b/src/test/java/org/lmdbjava/GarbageCollectionTest.java @@ -13,20 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.lmdbjava; import static java.nio.ByteBuffer.allocateDirect; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.junit.Assert.fail; +import static org.assertj.core.api.Assertions.fail; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.Env.create; -import java.io.File; -import java.io.IOException; import java.nio.ByteBuffer; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; @@ -36,62 +33,63 @@ public class GarbageCollectionTest { private static final String KEY_PREFIX = "Uncorruptedkey"; private static final String VAL_PREFIX = "Uncorruptedval"; - @Rule public final TemporaryFolder tmp = new TemporaryFolder(); - @Test - public void buffersNotGarbageCollectedTest() throws IOException { - final File path = tmp.newFolder(); - try (Env env = create().setMapSize(2_085_760_999).setMaxDbs(1).open(path)) { - final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); - - try (Txn txn = env.txnWrite()) { - for (int i = 0; i < 5_000; i++) { - putBuffer(db, txn, i); - } - txn.commit(); - } + void buffersNotGarbageCollectedTest() { + FileUtil.useTempDir( + dir -> { + try (Env env = + create().setMapSize(2_085_760_999).setMaxDbs(1).open(dir.toFile())) { + final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); - // Call GC before writing to LMDB and after last reference to buffer by - // changing the behavior of mask - try (MockedStatic mockedStatic = Mockito.mockStatic(MaskedFlag.class)) { - mockedStatic - .when(MaskedFlag::mask) - .thenAnswer( - invocationOnMock -> { - System.gc(); - return 0; - }); - final int gcRecordWrites = Integer.getInteger("gcRecordWrites", 50); - try (Txn txn = env.txnWrite()) { - for (int i = 0; i < gcRecordWrites; i++) { - putBuffer(db, txn, i); - } - txn.commit(); - } - } + try (Txn txn = env.txnWrite()) { + for (int i = 0; i < 5_000; i++) { + putBuffer(db, txn, i); + } + txn.commit(); + } - // Find corrupt keys - try (Txn txn = env.txnRead()) { - try (Cursor c = db.openCursor(txn)) { - if (c.first()) { - do { - final byte[] rkey = new byte[c.key().remaining()]; - c.key().get(rkey); - final byte[] rval = new byte[c.val().remaining()]; - c.val().get(rval); - final String skey = new String(rkey, UTF_8); - final String sval = new String(rval, UTF_8); - if (!skey.startsWith("Uncorruptedkey")) { - fail("Found corrupt key " + skey); + // Call GC before writing to LMDB and after last reference to buffer by + // changing the behavior of mask + try (MockedStatic mockedStatic = Mockito.mockStatic(MaskedFlag.class)) { + mockedStatic + .when(MaskedFlag::mask) + .thenAnswer( + invocationOnMock -> { + System.gc(); + return 0; + }); + final int gcRecordWrites = Integer.getInteger("gcRecordWrites", 50); + try (Txn txn = env.txnWrite()) { + for (int i = 0; i < gcRecordWrites; i++) { + putBuffer(db, txn, i); + } + txn.commit(); } - if (!sval.startsWith("Uncorruptedval")) { - fail("Found corrupt val " + sval); + } + + // Find corrupt keys + try (Txn txn = env.txnRead()) { + try (Cursor c = db.openCursor(txn)) { + if (c.first()) { + do { + final byte[] rkey = new byte[c.key().remaining()]; + c.key().get(rkey); + final byte[] rval = new byte[c.val().remaining()]; + c.val().get(rval); + final String skey = new String(rkey, UTF_8); + final String sval = new String(rval, UTF_8); + if (!skey.startsWith("Uncorruptedkey")) { + fail("Found corrupt key " + skey); + } + if (!sval.startsWith("Uncorruptedval")) { + fail("Found corrupt val " + sval); + } + } while (c.next()); + } } - } while (c.next()); + } } - } - } - } + }); } private void putBuffer(final Dbi db, final Txn txn, final int i) { diff --git a/src/test/java/org/lmdbjava/KeyRangeTest.java b/src/test/java/org/lmdbjava/KeyRangeTest.java index 6e104bbf..0f77b92f 100644 --- a/src/test/java/org/lmdbjava/KeyRangeTest.java +++ b/src/test/java/org/lmdbjava/KeyRangeTest.java @@ -13,11 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.lmdbjava; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import static org.lmdbjava.KeyRange.all; import static org.lmdbjava.KeyRange.allBackward; import static org.lmdbjava.KeyRange.atLeast; @@ -40,8 +39,8 @@ import java.util.ArrayList; import java.util.List; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.lmdbjava.KeyRangeType.CursorOp; import org.lmdbjava.KeyRangeType.IteratorOp; @@ -55,135 +54,135 @@ public final class KeyRangeTest { private final FakeCursor cursor = new FakeCursor(); + @BeforeEach + void beforeEach() { + cursor.reset(); + } + @Test - public void allBackwardTest() { + void allBackwardTest() { verify(allBackward(), 8, 6, 4, 2); } @Test - public void allTest() { + void allTest() { verify(all(), 2, 4, 6, 8); } @Test - public void atLeastBackwardTest() { + void atLeastBackwardTest() { verify(atLeastBackward(5), 4, 2); verify(atLeastBackward(6), 6, 4, 2); verify(atLeastBackward(9), 8, 6, 4, 2); } @Test - public void atLeastTest() { + void atLeastTest() { verify(atLeast(5), 6, 8); verify(atLeast(6), 6, 8); } @Test - public void atMostBackwardTest() { + void atMostBackwardTest() { verify(atMostBackward(5), 8, 6); verify(atMostBackward(6), 8, 6); } @Test - public void atMostTest() { + void atMostTest() { verify(atMost(5), 2, 4); verify(atMost(6), 2, 4, 6); } - @Before - public void before() { - cursor.reset(); - } - @Test - public void closedBackwardTest() { + void closedBackwardTest() { verify(closedBackward(7, 3), 6, 4); verify(closedBackward(6, 2), 6, 4, 2); verify(closedBackward(9, 3), 8, 6, 4); } @Test - public void closedOpenBackwardTest() { + void closedOpenBackwardTest() { verify(closedOpenBackward(8, 3), 8, 6, 4); verify(closedOpenBackward(7, 2), 6, 4); verify(closedOpenBackward(9, 3), 8, 6, 4); } @Test - public void closedOpenTest() { + void closedOpenTest() { verify(closedOpen(3, 8), 4, 6); verify(closedOpen(2, 6), 2, 4); } @Test - public void closedTest() { + void closedTest() { verify(closed(3, 7), 4, 6); verify(closed(2, 6), 2, 4, 6); } @Test - public void fakeCursor() { - assertThat(cursor.first(), is(2)); - assertThat(cursor.next(), is(4)); - assertThat(cursor.next(), is(6)); - assertThat(cursor.next(), is(8)); - assertThat(cursor.next(), nullValue()); - assertThat(cursor.first(), is(2)); - assertThat(cursor.prev(), nullValue()); - assertThat(cursor.getWithSetRange(3), is(4)); - assertThat(cursor.next(), is(6)); - assertThat(cursor.getWithSetRange(1), is(2)); - assertThat(cursor.last(), is(8)); - assertThat(cursor.getWithSetRange(100), nullValue()); + void fakeCursor() { + assertThat(cursor.first()).isEqualTo(2); + assertThat(cursor.next()).isEqualTo(4); + assertThat(cursor.next()).isEqualTo(6); + assertThat(cursor.next()).isEqualTo(8); + assertThat(cursor.next()).isNull(); + assertThat(cursor.first()).isEqualTo(2); + assertThat(cursor.prev()).isNull(); + assertThat(cursor.getWithSetRange(3)).isEqualTo(4); + assertThat(cursor.next()).isEqualTo(6); + assertThat(cursor.getWithSetRange(1)).isEqualTo(2); + assertThat(cursor.last()).isEqualTo(8); + assertThat(cursor.getWithSetRange(100)).isNull(); } @Test - public void greaterThanBackwardTest() { + void greaterThanBackwardTest() { verify(greaterThanBackward(6), 4, 2); verify(greaterThanBackward(7), 6, 4, 2); verify(greaterThanBackward(9), 8, 6, 4, 2); } @Test - public void greaterThanTest() { + void greaterThanTest() { verify(greaterThan(4), 6, 8); verify(greaterThan(3), 4, 6, 8); } @Test - public void lessThanBackwardTest() { + void lessThanBackwardTest() { verify(lessThanBackward(5), 8, 6); verify(lessThanBackward(2), 8, 6, 4); } @Test - public void lessThanTest() { + void lessThanTest() { verify(lessThan(5), 2, 4); verify(lessThan(8), 2, 4, 6); } @Test - public void openBackwardTest() { + void openBackwardTest() { verify(openBackward(7, 2), 6, 4); verify(openBackward(8, 1), 6, 4, 2); verify(openBackward(9, 4), 8, 6); } @Test - public void openClosedBackwardTest() { + void openClosedBackwardTest() { verify(openClosedBackward(7, 2), 6, 4, 2); verify(openClosedBackward(8, 4), 6, 4); verify(openClosedBackward(9, 4), 8, 6, 4); } @Test - public void openClosedTest() { + void openClosedTest() { verify(openClosed(3, 8), 4, 6, 8); verify(openClosed(2, 6), 4, 6); } @Test - public void openTest() { + void openTest() { verify(open(3, 7), 4, 6); verify(open(2, 8), 4, 6); } @@ -212,9 +211,9 @@ private void verify(final KeyRange range, final int... expected) { } while (op != TERMINATE); for (int idx = 0; idx < results.size(); idx++) { - assertThat("idx " + idx, results.get(idx), is(expected[idx])); + assertThat(results.get(idx)).withFailMessage("idx " + idx).isEqualTo(expected[idx]); } - assertThat(results.size(), is(expected.length)); + assertThat(results.size()).isEqualTo(expected.length); } /** diff --git a/src/test/java/org/lmdbjava/LibraryTest.java b/src/test/java/org/lmdbjava/LibraryTest.java index 6dcfcee9..3b492d75 100644 --- a/src/test/java/org/lmdbjava/LibraryTest.java +++ b/src/test/java/org/lmdbjava/LibraryTest.java @@ -13,30 +13,30 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.lmdbjava; import static java.lang.Long.BYTES; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import static org.lmdbjava.Library.RUNTIME; import static org.lmdbjava.TestUtils.invokePrivateConstructor; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.lmdbjava.Library.MDB_envinfo; /** Test {@link Library}. */ public final class LibraryTest { @Test - public void coverPrivateConstructors() { + void coverPrivateConstructors() { invokePrivateConstructor(Library.class); invokePrivateConstructor(UnsafeAccess.class); } @Test - public void structureFieldOrder() { + void structureFieldOrder() { final MDB_envinfo v = new MDB_envinfo(RUNTIME); - assertThat(v.f0_me_mapaddr.offset(), is(0L)); - assertThat(v.f1_me_mapsize.offset(), is((long) BYTES)); + assertThat(v.f0_me_mapaddr.offset()).isEqualTo(0L); + assertThat(v.f1_me_mapsize.offset()).isEqualTo(BYTES); } } diff --git a/src/test/java/org/lmdbjava/MaskedFlagTest.java b/src/test/java/org/lmdbjava/MaskedFlagTest.java index 918bf922..25c23797 100644 --- a/src/test/java/org/lmdbjava/MaskedFlagTest.java +++ b/src/test/java/org/lmdbjava/MaskedFlagTest.java @@ -13,57 +13,56 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.lmdbjava; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.arrayWithSize; +import static org.assertj.core.api.Assertions.assertThat; import static org.lmdbjava.EnvFlags.MDB_FIXEDMAP; import static org.lmdbjava.EnvFlags.MDB_NOSYNC; import static org.lmdbjava.EnvFlags.MDB_RDONLY_ENV; import static org.lmdbjava.MaskedFlag.isSet; import static org.lmdbjava.MaskedFlag.mask; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** Test {@link MaskedFlag}. */ public final class MaskedFlagTest { @Test - public void isSetOperates() { - assertThat(isSet(0, MDB_NOSYNC), is(false)); - assertThat(isSet(0, MDB_FIXEDMAP), is(false)); - assertThat(isSet(0, MDB_RDONLY_ENV), is(false)); + void isSetOperates() { + assertThat(isSet(0, MDB_NOSYNC)).isFalse(); + assertThat(isSet(0, MDB_FIXEDMAP)).isFalse(); + assertThat(isSet(0, MDB_RDONLY_ENV)).isFalse(); - assertThat(isSet(MDB_FIXEDMAP.getMask(), MDB_NOSYNC), is(false)); - assertThat(isSet(MDB_FIXEDMAP.getMask(), MDB_FIXEDMAP), is(true)); - assertThat(isSet(MDB_FIXEDMAP.getMask(), MDB_RDONLY_ENV), is(false)); + assertThat(isSet(MDB_FIXEDMAP.getMask(), MDB_NOSYNC)).isFalse(); + assertThat(isSet(MDB_FIXEDMAP.getMask(), MDB_FIXEDMAP)).isTrue(); + assertThat(isSet(MDB_FIXEDMAP.getMask(), MDB_RDONLY_ENV)).isFalse(); - assertThat(isSet(MDB_NOSYNC.getMask(), MDB_NOSYNC), is(true)); - assertThat(isSet(MDB_NOSYNC.getMask(), MDB_FIXEDMAP), is(false)); - assertThat(isSet(MDB_NOSYNC.getMask(), MDB_RDONLY_ENV), is(false)); + assertThat(isSet(MDB_NOSYNC.getMask(), MDB_NOSYNC)).isTrue(); + assertThat(isSet(MDB_NOSYNC.getMask(), MDB_FIXEDMAP)).isFalse(); + assertThat(isSet(MDB_NOSYNC.getMask(), MDB_RDONLY_ENV)).isFalse(); final int syncFixed = mask(MDB_NOSYNC, MDB_FIXEDMAP); - assertThat(isSet(syncFixed, MDB_NOSYNC), is(true)); - assertThat(isSet(syncFixed, MDB_FIXEDMAP), is(true)); - assertThat(isSet(syncFixed, MDB_RDONLY_ENV), is(false)); + assertThat(isSet(syncFixed, MDB_NOSYNC)).isTrue(); + assertThat(isSet(syncFixed, MDB_FIXEDMAP)).isTrue(); + assertThat(isSet(syncFixed, MDB_RDONLY_ENV)).isFalse(); } @Test - public void masking() { + void masking() { final EnvFlags[] nullFlags = null; - assertThat(mask(nullFlags), is(0)); + assertThat(mask(nullFlags)).isEqualTo(0); final EnvFlags[] emptyFlags = new EnvFlags[] {}; - assertThat(mask(emptyFlags), is(0)); + assertThat(mask(emptyFlags)).isEqualTo(0); final EnvFlags[] nullElementZero = new EnvFlags[] {null}; - assertThat(nullElementZero, is(arrayWithSize(1))); - assertThat(mask(nullElementZero), is(0)); + assertThat(nullElementZero.length).isEqualTo(1); + assertThat(mask(nullElementZero)).isEqualTo(0); - assertThat(mask(MDB_NOSYNC), is(MDB_NOSYNC.getMask())); + assertThat(mask(MDB_NOSYNC)).isEqualTo(MDB_NOSYNC.getMask()); final int expected = MDB_NOSYNC.getMask() + MDB_FIXEDMAP.getMask(); - assertThat(mask(MDB_NOSYNC, MDB_FIXEDMAP), is(expected)); + assertThat(mask(MDB_NOSYNC, MDB_FIXEDMAP)).isEqualTo(expected); } } diff --git a/src/test/java/org/lmdbjava/MetaTest.java b/src/test/java/org/lmdbjava/MetaTest.java index be0faa87..3e893561 100644 --- a/src/test/java/org/lmdbjava/MetaTest.java +++ b/src/test/java/org/lmdbjava/MetaTest.java @@ -13,17 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.lmdbjava; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import static org.lmdbjava.LmdbNativeException.PageCorruptedException.MDB_CORRUPTED; import static org.lmdbjava.Meta.error; import static org.lmdbjava.TestUtils.invokePrivateConstructor; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.lmdbjava.Meta.Version; /** Test {@link Meta}. */ @@ -35,15 +33,15 @@ public void coverPrivateConstructors() { } @Test - public void errCode() { - assertThat(error(MDB_CORRUPTED), is("MDB_CORRUPTED: Located page was wrong type")); + void errCode() { + assertThat(error(MDB_CORRUPTED)).isEqualTo("MDB_CORRUPTED: Located page was wrong type"); } @Test - public void version() { + void version() { final Version v = Meta.version(); - assertThat(v, not(nullValue())); - assertThat(v.major, is(0)); - assertThat(v.minor, is(9)); + assertThat(v).isNotNull(); + assertThat(v.major).isEqualTo(0); + assertThat(v.minor).isEqualTo(9); } } diff --git a/src/test/java/org/lmdbjava/ResultCodeMapperTest.java b/src/test/java/org/lmdbjava/ResultCodeMapperTest.java index 7a3764ed..c363d60a 100644 --- a/src/test/java/org/lmdbjava/ResultCodeMapperTest.java +++ b/src/test/java/org/lmdbjava/ResultCodeMapperTest.java @@ -13,22 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.lmdbjava; import static java.lang.Integer.MAX_VALUE; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.hasSize; -import static org.junit.Assert.fail; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.fail; import static org.lmdbjava.Cursor.FullException.MDB_CURSOR_FULL; import static org.lmdbjava.ResultCodeMapper.checkRc; import static org.lmdbjava.TestUtils.invokePrivateConstructor; import java.util.HashSet; import java.util.Set; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.lmdbjava.Cursor.FullException; import org.lmdbjava.Dbi.BadDbiException; import org.lmdbjava.Dbi.BadValueSizeException; @@ -86,71 +84,71 @@ public final class ResultCodeMapperTest { } @Test - public void checkErrAll() { + void checkErrAll() { for (final Integer rc : RESULT_CODES) { try { checkRc(rc); fail("Exception expected for RC " + rc); } catch (final LmdbNativeException e) { - assertThat(e.getResultCode(), is(rc)); + assertThat(e.getResultCode()).isEqualTo(rc); } } } - @Test(expected = ConstantDerivedException.class) - public void checkErrConstantDerived() { - checkRc(20); + @Test + void checkErrConstantDerived() { + assertThatThrownBy(() -> checkRc(20)).isInstanceOf(ConstantDerivedException.class); } @Test - public void checkErrConstantDerivedMessage() { + void checkErrConstantDerivedMessage() { try { checkRc(2); fail("Should have raised exception"); } catch (final ConstantDerivedException ex) { - assertThat(ex.getMessage(), containsString("No such file or directory")); + assertThat(ex.getMessage()).contains("No such file or directory"); } } - @Test(expected = FullException.class) - public void checkErrCursorFull() { - checkRc(MDB_CURSOR_FULL); + @Test + void checkErrCursorFull() { + assertThatThrownBy(() -> checkRc(MDB_CURSOR_FULL)).isInstanceOf(FullException.class); } - @Test(expected = IllegalArgumentException.class) - public void checkErrUnknownResultCode() { - checkRc(MAX_VALUE); + @Test + void checkErrUnknownResultCode() { + assertThatThrownBy(() -> checkRc(MAX_VALUE)).isInstanceOf(IllegalArgumentException.class); } @Test - public void coverPrivateConstructors() { + void coverPrivateConstructors() { invokePrivateConstructor(ResultCodeMapper.class); } @Test - public void lmdbExceptionPreservesRootCause() { + void lmdbExceptionPreservesRootCause() { final Exception cause = new IllegalStateException("root cause"); final LmdbException e = new LmdbException("test", cause); - assertThat(e.getCause(), is(cause)); - assertThat(e.getMessage(), is("test")); + assertThat(e.getCause()).isEqualTo(cause); + assertThat(e.getMessage()).isEqualTo("test"); } @Test - public void mapperReturnsUnique() { + void mapperReturnsUnique() { final Set seen = new HashSet<>(); for (final Integer rc : RESULT_CODES) { try { checkRc(rc); } catch (final LmdbNativeException ex) { - assertThat(ex, is(notNullValue())); + assertThat(ex).isNotNull(); seen.add(ex); } } - assertThat(seen, hasSize(RESULT_CODES.size())); + assertThat(seen).hasSize(RESULT_CODES.size()); } @Test - public void noDuplicateResultCodes() { - assertThat(RESULT_CODES.size(), is(EXCEPTIONS.size())); + void noDuplicateResultCodes() { + assertThat(RESULT_CODES.size()).isEqualTo(EXCEPTIONS.size()); } } diff --git a/src/test/java/org/lmdbjava/TargetNameTest.java b/src/test/java/org/lmdbjava/TargetNameTest.java index b598be55..e766aaa6 100644 --- a/src/test/java/org/lmdbjava/TargetNameTest.java +++ b/src/test/java/org/lmdbjava/TargetNameTest.java @@ -13,15 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.lmdbjava; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import static org.lmdbjava.TargetName.isExternal; import static org.lmdbjava.TargetName.resolveFilename; import static org.lmdbjava.TestUtils.invokePrivateConstructor; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** Test {@link TargetName}. */ public final class TargetNameTest { @@ -29,18 +29,18 @@ public final class TargetNameTest { private static final String NONE = ""; @Test - public void coverPrivateConstructors() { + void coverPrivateConstructors() { invokePrivateConstructor(TargetName.class); } @Test - public void customEmbedded() { - assertThat(resolveFilename(NONE, "x/y.so", NONE, NONE), is("x/y.so")); - assertThat(isExternal(NONE), is(false)); + void customEmbedded() { + assertThat(resolveFilename(NONE, "x/y.so", NONE, NONE)).isEqualTo("x/y.so"); + assertThat(isExternal(NONE)).isFalse(); } @Test - public void embeddedNameResolution() { + void embeddedNameResolution() { embed("aarch64-linux-gnu.so", "aarch64", "Linux"); embed("aarch64-macos-none.so", "aarch64", "Mac OS"); embed("x86_64-linux-gnu.so", "x86_64", "Linux"); @@ -49,19 +49,19 @@ public void embeddedNameResolution() { } @Test - public void externalLibrary() { - assertThat(resolveFilename("/l.so", NONE, NONE, NONE), is("/l.so")); - assertThat(TargetName.isExternal("/l.so"), is(true)); + void externalLibrary() { + assertThat(resolveFilename("/l.so", NONE, NONE, NONE)).isEqualTo("/l.so"); + assertThat(TargetName.isExternal("/l.so")).isTrue(); } @Test - public void externalTakesPriority() { - assertThat(resolveFilename("/lm.so", "x/y.so", NONE, NONE), is("/lm.so")); - assertThat(isExternal("/lm.so"), is(true)); + void externalTakesPriority() { + assertThat(resolveFilename("/lm.so", "x/y.so", NONE, NONE)).isEqualTo("/lm.so"); + assertThat(isExternal("/lm.so")).isTrue(); } private void embed(final String lib, final String arch, final String os) { - assertThat(resolveFilename(NONE, NONE, arch, os), is("org/lmdbjava/" + lib)); - assertThat(isExternal(NONE), is(false)); + assertThat(resolveFilename(NONE, NONE, arch, os)).isEqualTo("org/lmdbjava/" + lib); + assertThat(isExternal(NONE)).isFalse(); } } diff --git a/src/test/java/org/lmdbjava/TutorialTest.java b/src/test/java/org/lmdbjava/TutorialTest.java index b86c1d2a..1b42b327 100644 --- a/src/test/java/org/lmdbjava/TutorialTest.java +++ b/src/test/java/org/lmdbjava/TutorialTest.java @@ -13,18 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.lmdbjava; import static java.nio.ByteBuffer.allocateDirect; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.concurrent.Executors.newCachedThreadPool; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.CoreMatchers.startsWith; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; +import static org.assertj.core.api.Assertions.assertThat; import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.DbiFlags.MDB_DUPSORT; @@ -35,16 +31,13 @@ import static org.lmdbjava.SeekOp.MDB_LAST; import static org.lmdbjava.SeekOp.MDB_PREV; -import java.io.File; -import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.file.Path; import java.util.concurrent.ExecutorService; import org.agrona.DirectBuffer; import org.agrona.MutableDirectBuffer; import org.agrona.concurrent.UnsafeBuffer; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.Test; import org.lmdbjava.CursorIterable.KeyVal; /** @@ -63,426 +56,429 @@ public final class TutorialTest { private static final String DB_NAME = "my DB"; - @Rule public final TemporaryFolder tmp = new TemporaryFolder(); - /** - * In this first tutorial we will use LmdbJava with some basic defaults. - * - * @throws IOException if a path was unavailable for memory mapping - */ + /** In this first tutorial we will use LmdbJava with some basic defaults. */ @Test - public void tutorial1() throws IOException { + void tutorial1() { // We need a storage directory first. // The path cannot be on a remote file system. - final File path = tmp.newFolder(); - - // We always need an Env. An Env owns a physical on-disk storage file. One - // Env can store many different databases (ie sorted maps). - final Env env = - create() - // LMDB also needs to know how large our DB might be. Over-estimating is OK. - .setMapSize(10_485_760) - // LMDB also needs to know how many DBs (Dbi) we want to store in this Env. - .setMaxDbs(1) - // Now let's open the Env. The same path can be concurrently opened and - // used in different processes, but do not open the same path twice in - // the same process at the same time. - .open(path); - - // We need a Dbi for each DB. A Dbi roughly equates to a sorted map. The - // MDB_CREATE flag causes the DB to be created if it doesn't already exist. - final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); - - // We want to store some data, so we will need a direct ByteBuffer. - // Note that LMDB keys cannot exceed maxKeySize bytes (511 bytes by default). - // Values can be larger. - final ByteBuffer key = allocateDirect(env.getMaxKeySize()); - final ByteBuffer val = allocateDirect(700); - key.put("greeting".getBytes(UTF_8)).flip(); - val.put("Hello world".getBytes(UTF_8)).flip(); - final int valSize = val.remaining(); - - // Now store it. Dbi.put() internally begins and commits a transaction (Txn). - db.put(key, val); - - // To fetch any data from LMDB we need a Txn. A Txn is very important in - // LmdbJava because it offers ACID characteristics and internally holds a - // read-only key buffer and read-only value buffer. These read-only buffers - // are always the same two Java objects, but point to different LMDB-managed - // memory as we use Dbi (and Cursor) methods. These read-only buffers remain - // valid only until the Txn is released or the next Dbi or Cursor call. If - // you need data afterwards, you should copy the bytes to your own buffer. - try (Txn txn = env.txnRead()) { - final ByteBuffer found = db.get(txn, key); - assertNotNull(found); - - // The fetchedVal is read-only and points to LMDB memory - final ByteBuffer fetchedVal = txn.val(); - assertThat(fetchedVal.remaining(), is(valSize)); - - // Let's double-check the fetched value is correct - assertThat(UTF_8.decode(fetchedVal).toString(), is("Hello world")); - } - - // We can also delete. The simplest way is to let Dbi allocate a new Txn... - db.delete(key); - - // Now if we try to fetch the deleted row, it won't be present - try (Txn txn = env.txnRead()) { - assertNull(db.get(txn, key)); - } - - env.close(); + FileUtil.useTempDir( + dir -> { + + // We always need an Env. An Env owns a physical on-disk storage file. One + // Env can store many different databases (ie sorted maps). + final Env env = + create() + // LMDB also needs to know how large our DB might be. Over-estimating is OK. + .setMapSize(10_485_760) + // LMDB also needs to know how many DBs (Dbi) we want to store in this Env. + .setMaxDbs(1) + // Now let's open the Env. The same path can be concurrently opened and + // used in different processes, but do not open the same path twice in + // the same process at the same time. + .open(dir.toFile()); + + // We need a Dbi for each DB. A Dbi roughly equates to a sorted map. The + // MDB_CREATE flag causes the DB to be created if it doesn't already exist. + final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); + + // We want to store some data, so we will need a direct ByteBuffer. + // Note that LMDB keys cannot exceed maxKeySize bytes (511 bytes by default). + // Values can be larger. + final ByteBuffer key = allocateDirect(env.getMaxKeySize()); + final ByteBuffer val = allocateDirect(700); + key.put("greeting".getBytes(UTF_8)).flip(); + val.put("Hello world".getBytes(UTF_8)).flip(); + final int valSize = val.remaining(); + + // Now store it. Dbi.put() internally begins and commits a transaction (Txn). + db.put(key, val); + + // To fetch any data from LMDB we need a Txn. A Txn is very important in + // LmdbJava because it offers ACID characteristics and internally holds a + // read-only key buffer and read-only value buffer. These read-only buffers + // are always the same two Java objects, but point to different LMDB-managed + // memory as we use Dbi (and Cursor) methods. These read-only buffers remain + // valid only until the Txn is released or the next Dbi or Cursor call. If + // you need data afterwards, you should copy the bytes to your own buffer. + try (Txn txn = env.txnRead()) { + final ByteBuffer found = db.get(txn, key); + assertThat(found).isNotNull(); + + // The fetchedVal is read-only and points to LMDB memory + final ByteBuffer fetchedVal = txn.val(); + assertThat(fetchedVal.remaining()).isEqualTo(valSize); + + // Let's double-check the fetched value is correct + assertThat(UTF_8.decode(fetchedVal).toString()).isEqualTo("Hello world"); + } + + // We can also delete. The simplest way is to let Dbi allocate a new Txn... + db.delete(key); + + // Now if we try to fetch the deleted row, it won't be present + try (Txn txn = env.txnRead()) { + assertThat(db.get(txn, key)).isNull(); + } + + env.close(); + }); } - /** - * In this second tutorial we'll learn more about LMDB's ACID Txns. - * - * @throws IOException if a path was unavailable for memory mapping - * @throws InterruptedException if executor shutdown interrupted - */ + /** In this second tutorial we'll learn more about LMDB's ACID Txns. */ @Test - public void tutorial2() throws IOException, InterruptedException { - final Env env = createSimpleEnv(tmp.newFolder()); - final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); - final ByteBuffer key = allocateDirect(env.getMaxKeySize()); - final ByteBuffer val = allocateDirect(700); - - // Let's write and commit "key1" via a Txn. A Txn can include multiple Dbis. - // Note write Txns block other write Txns, due to writes being serialized. - // It's therefore important to avoid unnecessarily long-lived write Txns. - try (Txn txn = env.txnWrite()) { - key.put("key1".getBytes(UTF_8)).flip(); - val.put("lmdb".getBytes(UTF_8)).flip(); - db.put(txn, key, val); - - // We can read data too, even though this is a write Txn. - final ByteBuffer found = db.get(txn, key); - assertNotNull(found); - - // An explicit commit is required, otherwise Txn.close() rolls it back. - txn.commit(); - } - - // Open a read-only Txn. It only sees data that existed at Txn creation time. - final Txn rtx = env.txnRead(); - - // Our read Txn can fetch key1 without problem, as it existed at Txn creation. - ByteBuffer found = db.get(rtx, key); - assertNotNull(found); - - // Note that our main test thread holds the Txn. Only one Txn per thread is - // typically permitted (the exception is a read-only Env with MDB_NOTLS). - // - // Let's write out a "key2" via a new write Txn in a different thread. - final ExecutorService es = newCachedThreadPool(); - es.execute( - () -> { - try (Txn txn = env.txnWrite()) { - key.clear(); - key.put("key2".getBytes(UTF_8)).flip(); - db.put(txn, key, val); - txn.commit(); + void tutorial2() { + FileUtil.useTempDir( + dir -> { + try { + final Env env = createSimpleEnv(dir); + final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); + final ByteBuffer key = allocateDirect(env.getMaxKeySize()); + final ByteBuffer val = allocateDirect(700); + + // Let's write and commit "key1" via a Txn. A Txn can include multiple Dbis. + // Note write Txns block other write Txns, due to writes being serialized. + // It's therefore important to avoid unnecessarily long-lived write Txns. + try (Txn txn = env.txnWrite()) { + key.put("key1".getBytes(UTF_8)).flip(); + val.put("lmdb".getBytes(UTF_8)).flip(); + db.put(txn, key, val); + + // We can read data too, even though this is a write Txn. + final ByteBuffer found = db.get(txn, key); + assertThat(found).isNotNull(); + + // An explicit commit is required, otherwise Txn.close() rolls it back. + txn.commit(); + } + + // Open a read-only Txn. It only sees data that existed at Txn creation time. + final Txn rtx = env.txnRead(); + + // Our read Txn can fetch key1 without problem, as it existed at Txn creation. + ByteBuffer found = db.get(rtx, key); + assertThat(found).isNotNull(); + + // Note that our main test thread holds the Txn. Only one Txn per thread is + // typically permitted (the exception is a read-only Env with MDB_NOTLS). + // + // Let's write out a "key2" via a new write Txn in a different thread. + final ExecutorService es = newCachedThreadPool(); + es.execute( + () -> { + try (Txn txn = env.txnWrite()) { + key.clear(); + key.put("key2".getBytes(UTF_8)).flip(); + db.put(txn, key, val); + txn.commit(); + } + }); + es.shutdown(); + es.awaitTermination(10, SECONDS); + + // Even though key2 has been committed, our read Txn still can't see it. + found = db.get(rtx, key); + assertThat(found).isNull(); + + // To see key2, we could create a new Txn. But a reset/renew is much faster. + // Reset/renew is also important to avoid long-lived read Txns, as these + // prevent the re-use of free pages by write Txns (ie the DB will grow). + rtx.reset(); + // ... potentially long operation here ... + rtx.renew(); + found = db.get(rtx, key); + assertThat(found).isNotNull(); + + // Don't forget to close the read Txn now we're completely finished. We could + // have avoided this if we used a try-with-resources block, but we wanted to + // play around with multiple concurrent Txns to demonstrate the "I" in ACID. + rtx.close(); + env.close(); + } catch (final InterruptedException e) { + throw new RuntimeException(e); } }); - es.shutdown(); - es.awaitTermination(10, SECONDS); - - // Even though key2 has been committed, our read Txn still can't see it. - found = db.get(rtx, key); - assertNull(found); - - // To see key2, we could create a new Txn. But a reset/renew is much faster. - // Reset/renew is also important to avoid long-lived read Txns, as these - // prevent the re-use of free pages by write Txns (ie the DB will grow). - rtx.reset(); - // ... potentially long operation here ... - rtx.renew(); - found = db.get(rtx, key); - assertNotNull(found); - - // Don't forget to close the read Txn now we're completely finished. We could - // have avoided this if we used a try-with-resources block, but we wanted to - // play around with multiple concurrent Txns to demonstrate the "I" in ACID. - rtx.close(); - env.close(); } /** * In this third tutorial we'll have a look at the Cursor. Up until now we've just used Dbi, which * is good enough for simple cases but unsuitable if you don't know the key to fetch, or want to * iterate over all the data etc. - * - * @throws IOException if a path was unavailable for memory mapping */ @Test - public void tutorial3() throws IOException { - final Env env = createSimpleEnv(tmp.newFolder()); - final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); - final ByteBuffer key = allocateDirect(env.getMaxKeySize()); - final ByteBuffer val = allocateDirect(700); - - try (Txn txn = env.txnWrite()) { - // A cursor always belongs to a particular Dbi. - final Cursor c = db.openCursor(txn); - - // We can put via a Cursor. Note we're adding keys in a strange order, - // as we want to show you that LMDB returns them in sorted order. - key.put("zzz".getBytes(UTF_8)).flip(); - val.put("lmdb".getBytes(UTF_8)).flip(); - c.put(key, val); - key.clear(); - key.put("aaa".getBytes(UTF_8)).flip(); - c.put(key, val); - key.clear(); - key.put("ccc".getBytes(UTF_8)).flip(); - c.put(key, val); - - // We can read from the Cursor by key. - c.get(key, MDB_SET); - assertThat(UTF_8.decode(c.key()).toString(), is("ccc")); - - // Let's see that LMDB provides the keys in appropriate order.... - c.seek(MDB_FIRST); - assertThat(UTF_8.decode(c.key()).toString(), is("aaa")); - - c.seek(MDB_LAST); - assertThat(UTF_8.decode(c.key()).toString(), is("zzz")); - - c.seek(MDB_PREV); - assertThat(UTF_8.decode(c.key()).toString(), is("ccc")); - - // Cursors can also delete the current key. - c.delete(); - - c.close(); - txn.commit(); - } - - // A read-only Cursor can survive its original Txn being closed. This is - // useful if you want to close the original Txn (eg maybe you created the - // Cursor during the constructor of a singleton with a throw-away Txn). Of - // course, you cannot use the Cursor if its Txn is closed or currently reset. - final Txn tx1 = env.txnRead(); - final Cursor c = db.openCursor(tx1); - tx1.close(); - - // The Cursor becomes usable again by "renewing" it with an active read Txn. - final Txn tx2 = env.txnRead(); - c.renew(tx2); - c.seek(MDB_FIRST); - - // As usual with read Txns, we can reset and renew them. The Cursor does - // not need any special handling if we do this. - tx2.reset(); - // ... potentially long operation here ... - tx2.renew(); - c.seek(MDB_LAST); - - tx2.close(); - env.close(); + void tutorial3() { + FileUtil.useTempDir( + dir -> { + final Env env = createSimpleEnv(dir); + final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); + final ByteBuffer key = allocateDirect(env.getMaxKeySize()); + final ByteBuffer val = allocateDirect(700); + + try (Txn txn = env.txnWrite()) { + // A cursor always belongs to a particular Dbi. + final Cursor c = db.openCursor(txn); + + // We can put via a Cursor. Note we're adding keys in a strange order, + // as we want to show you that LMDB returns them in sorted order. + key.put("zzz".getBytes(UTF_8)).flip(); + val.put("lmdb".getBytes(UTF_8)).flip(); + c.put(key, val); + key.clear(); + key.put("aaa".getBytes(UTF_8)).flip(); + c.put(key, val); + key.clear(); + key.put("ccc".getBytes(UTF_8)).flip(); + c.put(key, val); + + // We can read from the Cursor by key. + c.get(key, MDB_SET); + assertThat(UTF_8.decode(c.key()).toString()).isEqualTo("ccc"); + + // Let's see that LMDB provides the keys in appropriate order.... + c.seek(MDB_FIRST); + assertThat(UTF_8.decode(c.key()).toString()).isEqualTo("aaa"); + + c.seek(MDB_LAST); + assertThat(UTF_8.decode(c.key()).toString()).isEqualTo("zzz"); + + c.seek(MDB_PREV); + assertThat(UTF_8.decode(c.key()).toString()).isEqualTo("ccc"); + + // Cursors can also delete the current key. + c.delete(); + + c.close(); + txn.commit(); + } + + // A read-only Cursor can survive its original Txn being closed. This is + // useful if you want to close the original Txn (eg maybe you created the + // Cursor during the constructor of a singleton with a throw-away Txn). Of + // course, you cannot use the Cursor if its Txn is closed or currently reset. + final Txn tx1 = env.txnRead(); + final Cursor c = db.openCursor(tx1); + tx1.close(); + + // The Cursor becomes usable again by "renewing" it with an active read Txn. + final Txn tx2 = env.txnRead(); + c.renew(tx2); + c.seek(MDB_FIRST); + + // As usual with read Txns, we can reset and renew them. The Cursor does + // not need any special handling if we do this. + tx2.reset(); + // ... potentially long operation here ... + tx2.renew(); + c.seek(MDB_LAST); + + tx2.close(); + env.close(); + }); } /** * In this fourth tutorial we'll take a quick look at the iterators. These are a more Java * idiomatic form of using the Cursors we looked at in tutorial 3. - * - * @throws IOException if a path was unavailable for memory mapping */ @Test - public void tutorial4() throws IOException { - final Env env = createSimpleEnv(tmp.newFolder()); - final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); - - try (Txn txn = env.txnWrite()) { - final ByteBuffer key = allocateDirect(env.getMaxKeySize()); - final ByteBuffer val = allocateDirect(700); - - // Insert some data. Note that ByteBuffer order defaults to Big Endian. - // LMDB does not persist the byte order, but it's critical to sort keys. - // If your numeric keys don't sort as expected, review buffer byte order. - val.putInt(100); - key.putInt(1); - db.put(txn, key, val); - key.clear(); - key.putInt(2); - db.put(txn, key, val); - key.clear(); - - // Each iterable uses a cursor and must be closed when finished. Iterate - // forward in terms of key ordering starting with the first key. - try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { - for (final KeyVal kv : ci) { - assertThat(kv.key(), notNullValue()); - assertThat(kv.val(), notNullValue()); - } - } - - // Iterate backward in terms of key ordering starting with the last key. - try (CursorIterable ci = db.iterate(txn, KeyRange.allBackward())) { - for (final KeyVal kv : ci) { - assertThat(kv.key(), notNullValue()); - assertThat(kv.val(), notNullValue()); - } - } - - // There are many ways to control the desired key range via KeyRange, such - // as arbitrary start and stop values, direction etc. We've adopted Guava's - // terminology for our range classes (see KeyRangeType for further details). - key.putInt(1); - final KeyRange range = KeyRange.atLeastBackward(key); - try (CursorIterable ci = db.iterate(txn, range)) { - for (final KeyVal kv : ci) { - assertThat(kv.key(), notNullValue()); - assertThat(kv.val(), notNullValue()); - } - } - } - - env.close(); + void tutorial4() { + FileUtil.useTempDir( + dir -> { + final Env env = createSimpleEnv(dir); + final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); + + try (Txn txn = env.txnWrite()) { + final ByteBuffer key = allocateDirect(env.getMaxKeySize()); + final ByteBuffer val = allocateDirect(700); + + // Insert some data. Note that ByteBuffer order defaults to Big Endian. + // LMDB does not persist the byte order, but it's critical to sort keys. + // If your numeric keys don't sort as expected, review buffer byte order. + val.putInt(100); + key.putInt(1); + db.put(txn, key, val); + key.clear(); + key.putInt(2); + db.put(txn, key, val); + key.clear(); + + // Each iterable uses a cursor and must be closed when finished. Iterate + // forward in terms of key ordering starting with the first key. + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + for (final KeyVal kv : ci) { + assertThat(kv.key()).isNotNull(); + assertThat(kv.val()).isNotNull(); + } + } + + // Iterate backward in terms of key ordering starting with the last key. + try (CursorIterable ci = db.iterate(txn, KeyRange.allBackward())) { + for (final KeyVal kv : ci) { + assertThat(kv.key()).isNotNull(); + assertThat(kv.val()).isNotNull(); + } + } + + // There are many ways to control the desired key range via KeyRange, such + // as arbitrary start and stop values, direction etc. We've adopted Guava's + // terminology for our range classes (see KeyRangeType for further details). + key.putInt(1); + final KeyRange range = KeyRange.atLeastBackward(key); + try (CursorIterable ci = db.iterate(txn, range)) { + for (final KeyVal kv : ci) { + assertThat(kv.key()).isNotNull(); + assertThat(kv.val()).isNotNull(); + } + } + } + + env.close(); + }); } - /** - * In this fifth tutorial we'll explore multiple values sharing a single key. - * - * @throws IOException if a path was unavailable for memory mapping - */ + /** In this fifth tutorial we'll explore multiple values sharing a single key. */ @Test - public void tutorial5() throws IOException { - final Env env = createSimpleEnv(tmp.newFolder()); - - // This time we're going to tell the Dbi it can store > 1 value per key. - // There are other flags available if we're storing integers etc. - final Dbi db = env.openDbi(DB_NAME, MDB_CREATE, MDB_DUPSORT); - - // Duplicate support requires both keys and values to be <= max key size. - final ByteBuffer key = allocateDirect(env.getMaxKeySize()); - final ByteBuffer val = allocateDirect(env.getMaxKeySize()); - - try (Txn txn = env.txnWrite()) { - final Cursor c = db.openCursor(txn); - - // Store one key, but many values, and in non-natural order. - key.put("key".getBytes(UTF_8)).flip(); - val.put("xxx".getBytes(UTF_8)).flip(); - c.put(key, val); - val.clear(); - val.put("kkk".getBytes(UTF_8)).flip(); - c.put(key, val); - val.clear(); - val.put("lll".getBytes(UTF_8)).flip(); - c.put(key, val); - - // Cursor can tell us how many values the current key has. - final long count = c.count(); - assertThat(count, is(3L)); - - // Let's position the Cursor. Note sorting still works. - c.seek(MDB_FIRST); - assertThat(UTF_8.decode(c.val()).toString(), is("kkk")); - - c.seek(MDB_LAST); - assertThat(UTF_8.decode(c.val()).toString(), is("xxx")); - - c.seek(MDB_PREV); - assertThat(UTF_8.decode(c.val()).toString(), is("lll")); - - c.close(); - txn.commit(); - } - - env.close(); + void tutorial5() { + FileUtil.useTempDir( + dir -> { + final Env env = createSimpleEnv(dir); + + // This time we're going to tell the Dbi it can store > 1 value per key. + // There are other flags available if we're storing integers etc. + final Dbi db = env.openDbi(DB_NAME, MDB_CREATE, MDB_DUPSORT); + + // Duplicate support requires both keys and values to be <= max key size. + final ByteBuffer key = allocateDirect(env.getMaxKeySize()); + final ByteBuffer val = allocateDirect(env.getMaxKeySize()); + + try (Txn txn = env.txnWrite()) { + final Cursor c = db.openCursor(txn); + + // Store one key, but many values, and in non-natural order. + key.put("key".getBytes(UTF_8)).flip(); + val.put("xxx".getBytes(UTF_8)).flip(); + c.put(key, val); + val.clear(); + val.put("kkk".getBytes(UTF_8)).flip(); + c.put(key, val); + val.clear(); + val.put("lll".getBytes(UTF_8)).flip(); + c.put(key, val); + + // Cursor can tell us how many values the current key has. + final long count = c.count(); + assertThat(count).isEqualTo(3L); + + // Let's position the Cursor. Note sorting still works. + c.seek(MDB_FIRST); + assertThat(UTF_8.decode(c.val()).toString()).isEqualTo("kkk"); + + c.seek(MDB_LAST); + assertThat(UTF_8.decode(c.val()).toString()).isEqualTo("xxx"); + + c.seek(MDB_PREV); + assertThat(UTF_8.decode(c.val()).toString()).isEqualTo("lll"); + + c.close(); + txn.commit(); + } + + env.close(); + }); } /** * Next up we'll show you how to easily check your platform (operating system and Java version) is * working properly with LmdbJava and the embedded LMDB native library. - * - * @throws IOException if a path was unavailable for memory mapping */ @Test - public void tutorial6() throws IOException { - // Note we need to specify the Verifier's DBI_COUNT for the Env. - final Env env = - create(PROXY_OPTIMAL) - .setMapSize(10_485_760) - .setMaxDbs(Verifier.DBI_COUNT) - .open(tmp.newFolder()); - - // Create a Verifier (it's a Callable for those needing full control). - final Verifier v = new Verifier(env); - - // We now run the verifier for 3 seconds; it raises an exception on failure. - // The method returns the number of entries it successfully verified. - v.runFor(3, SECONDS); - - env.close(); + void tutorial6() { + FileUtil.useTempDir( + dir -> { + // Note we need to specify the Verifier's DBI_COUNT for the Env. + final Env env = + create(PROXY_OPTIMAL) + .setMapSize(10_485_760) + .setMaxDbs(Verifier.DBI_COUNT) + .open(dir.toFile()); + + // Create a Verifier (it's a Callable for those needing full control). + final Verifier v = new Verifier(env); + + // We now run the verifier for 3 seconds; it raises an exception on failure. + // The method returns the number of entries it successfully verified. + v.runFor(3, SECONDS); + + env.close(); + }); } - /** - * In this final tutorial we'll look at using Agrona's DirectBuffer. - * - * @throws IOException if a path was unavailable for memory mapping - */ + /** In this final tutorial we'll look at using Agrona's DirectBuffer. */ @Test - public void tutorial7() throws IOException { - // The critical difference is we pass the PROXY_DB field to Env.create(). - // There's also a PROXY_SAFE if you want to stop ByteBuffer's Unsafe use. - // Aside from that and a different type argument, it's the same as usual... - final Env env = - create(PROXY_DB).setMapSize(10_485_760).setMaxDbs(1).open(tmp.newFolder()); - - final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); - - final ByteBuffer keyBb = allocateDirect(env.getMaxKeySize()); - final MutableDirectBuffer key = new UnsafeBuffer(keyBb); - final MutableDirectBuffer val = new UnsafeBuffer(allocateDirect(700)); - - try (Txn txn = env.txnWrite()) { - try (Cursor c = db.openCursor(txn)) { - // Agrona is faster than ByteBuffer and its methods are nicer... - val.putStringWithoutLengthUtf8(0, "The Value"); - key.putStringWithoutLengthUtf8(0, "yyy"); - c.put(key, val); - - key.putStringWithoutLengthUtf8(0, "ggg"); - c.put(key, val); - - c.seek(MDB_FIRST); - assertThat(c.key().getStringWithoutLengthUtf8(0, env.getMaxKeySize()), startsWith("ggg")); - - c.seek(MDB_LAST); - assertThat(c.key().getStringWithoutLengthUtf8(0, env.getMaxKeySize()), startsWith("yyy")); - - // DirectBuffer has no position concept. Often you don't want to store - // the unnecessary bytes of a varying-size buffer. Let's have a look... - final int keyLen = key.putStringWithoutLengthUtf8(0, "12characters"); - assertThat(keyLen, is(12)); - assertThat(key.capacity(), is(env.getMaxKeySize())); - - // To only store the 12 characters, we simply call wrap: - key.wrap(key, 0, keyLen); - assertThat(key.capacity(), is(keyLen)); - c.put(key, val); - c.seek(MDB_FIRST); - assertThat(c.key().capacity(), is(keyLen)); - assertThat(c.key().getStringWithoutLengthUtf8(0, c.key().capacity()), is("12characters")); - - // To store bigger values again, just wrap the original buffer. - key.wrap(keyBb); - assertThat(key.capacity(), is(env.getMaxKeySize())); - } - txn.commit(); - } - - env.close(); + void tutorial7() { + FileUtil.useTempDir( + dir -> { + // The critical difference is we pass the PROXY_DB field to Env.create(). + // There's also a PROXY_SAFE if you want to stop ByteBuffer's Unsafe use. + // Aside from that and a different type argument, it's the same as usual... + final Env env = + create(PROXY_DB).setMapSize(10_485_760).setMaxDbs(1).open(dir.toFile()); + + final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); + + final ByteBuffer keyBb = allocateDirect(env.getMaxKeySize()); + final MutableDirectBuffer key = new UnsafeBuffer(keyBb); + final MutableDirectBuffer val = new UnsafeBuffer(allocateDirect(700)); + + try (Txn txn = env.txnWrite()) { + try (Cursor c = db.openCursor(txn)) { + // Agrona is faster than ByteBuffer and its methods are nicer... + val.putStringWithoutLengthUtf8(0, "The Value"); + key.putStringWithoutLengthUtf8(0, "yyy"); + c.put(key, val); + + key.putStringWithoutLengthUtf8(0, "ggg"); + c.put(key, val); + + c.seek(MDB_FIRST); + assertThat(c.key().getStringWithoutLengthUtf8(0, env.getMaxKeySize())) + .startsWith("ggg"); + + c.seek(MDB_LAST); + assertThat(c.key().getStringWithoutLengthUtf8(0, env.getMaxKeySize())) + .startsWith("yyy"); + + // DirectBuffer has no position concept. Often you don't want to store + // the unnecessary bytes of a varying-size buffer. Let's have a look... + final int keyLen = key.putStringWithoutLengthUtf8(0, "12characters"); + assertThat(keyLen).isEqualTo(12); + assertThat(key.capacity()).isEqualTo(env.getMaxKeySize()); + + // To only store the 12 characters, we simply call wrap: + key.wrap(key, 0, keyLen); + assertThat(key.capacity()).isEqualTo(keyLen); + c.put(key, val); + c.seek(MDB_FIRST); + assertThat(c.key().capacity()).isEqualTo(keyLen); + assertThat(c.key().getStringWithoutLengthUtf8(0, c.key().capacity())) + .isEqualTo("12characters"); + + // To store bigger values again, just wrap the original buffer. + key.wrap(keyBb); + assertThat(key.capacity()).isEqualTo(env.getMaxKeySize()); + } + txn.commit(); + } + + env.close(); + }); } // You've finished! There are lots of other neat things we could show you (eg // how to speed up inserts by appending them in key order, using integer // or reverse ordered keys, using Env.DISABLE_CHECKS_PROP etc), but you now // know enough to tackle the JavaDocs with confidence. Have fun! - private Env createSimpleEnv(final File path) { - return create().setMapSize(10_485_760).setMaxDbs(1).setMaxReaders(1).open(path); + private Env createSimpleEnv(final Path path) { + return create().setMapSize(10_485_760).setMaxDbs(1).setMaxReaders(1).open(path.toFile()); } } diff --git a/src/test/java/org/lmdbjava/TxnTest.java b/src/test/java/org/lmdbjava/TxnTest.java index e768e424..46ffeb70 100644 --- a/src/test/java/org/lmdbjava/TxnTest.java +++ b/src/test/java/org/lmdbjava/TxnTest.java @@ -13,17 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.lmdbjava; import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; import static java.nio.ByteBuffer.allocateDirect; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.Env.create; import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; @@ -38,17 +35,15 @@ import static org.lmdbjava.Txn.State.RESET; import static org.lmdbjava.TxnFlags.MDB_RDONLY_TXN; -import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicLong; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.lmdbjava.Dbi.BadValueSizeException; import org.lmdbjava.Env.AlreadyClosedException; import org.lmdbjava.Txn.EnvIsReadOnly; @@ -62,36 +57,40 @@ /** Test {@link Txn}. */ public final class TxnTest { - @Rule public final TemporaryFolder tmp = new TemporaryFolder(); + private Path file; private Env env; - private File path; - @After - public void after() { - env.close(); - } - - @Before - public void before() throws IOException { - path = tmp.newFile(); + @BeforeEach + void beforeEach() { + file = FileUtil.createTempFile(); env = create() .setMapSize(KIBIBYTES.toBytes(256)) .setMaxReaders(1) .setMaxDbs(2) - .open(path, POSIX_MODE, MDB_NOSUBDIR); + .open(file.toFile(), POSIX_MODE, MDB_NOSUBDIR); } - @Test(expected = BadValueSizeException.class) - public void largeKeysRejected() throws IOException { - final Dbi dbi = env.openDbi(DB_1, MDB_CREATE); - final ByteBuffer key = allocateDirect(env.getMaxKeySize() + 1); - key.limit(key.capacity()); - dbi.put(key, bb(2)); + @AfterEach + void afterEach() { + env.close(); + FileUtil.delete(file); } @Test - public void rangeSearch() { + void largeKeysRejected() throws IOException { + assertThatThrownBy( + () -> { + final Dbi dbi = env.openDbi(DB_1, MDB_CREATE); + final ByteBuffer key = allocateDirect(env.getMaxKeySize() + 1); + key.limit(key.capacity()); + dbi.put(key, bb(2)); + }) + .isInstanceOf(BadValueSizeException.class); + } + + @Test + void rangeSearch() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); final ByteBuffer key = allocateDirect(env.getMaxKeySize()); @@ -120,53 +119,69 @@ public void rangeSearch() { } } - assertEquals(3, keysFound.size()); + assertThat(keysFound.size()).isEqualTo(3); } } @Test - public void readOnlyTxnAllowedInReadOnlyEnv() { + void readOnlyTxnAllowedInReadOnlyEnv() { env.openDbi(DB_1, MDB_CREATE); try (Env roEnv = - create().setMaxReaders(1).open(path, MDB_NOSUBDIR, MDB_RDONLY_ENV)) { - assertThat(roEnv.txnRead(), is(notNullValue())); + create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR, MDB_RDONLY_ENV)) { + assertThat(roEnv.txnRead()).isNotNull(); } } - @Test(expected = EnvIsReadOnly.class) - public void readWriteTxnDeniedInReadOnlyEnv() { - env.openDbi(DB_1, MDB_CREATE); - env.close(); - try (Env roEnv = - create().setMaxReaders(1).open(path, MDB_NOSUBDIR, MDB_RDONLY_ENV)) { - roEnv.txnWrite(); // error - } + @Test + void readWriteTxnDeniedInReadOnlyEnv() { + assertThatThrownBy( + () -> { + env.openDbi(DB_1, MDB_CREATE); + env.close(); + try (Env roEnv = + create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR, MDB_RDONLY_ENV)) { + roEnv.txnWrite(); // error + } + }) + .isInstanceOf(EnvIsReadOnly.class); } - @Test(expected = NotReadyException.class) - public void testCheckNotCommitted() { - try (Txn txn = env.txnRead()) { - txn.commit(); - txn.checkReady(); - } + @Test + void testCheckNotCommitted() { + assertThatThrownBy( + () -> { + try (Txn txn = env.txnRead()) { + txn.commit(); + txn.checkReady(); + } + }) + .isInstanceOf(NotReadyException.class); } - @Test(expected = ReadOnlyRequiredException.class) - public void testCheckReadOnly() { - try (Txn txn = env.txnWrite()) { - txn.checkReadOnly(); - } + @Test + void testCheckReadOnly() { + assertThatThrownBy( + () -> { + try (Txn txn = env.txnWrite()) { + txn.checkReadOnly(); + } + }) + .isInstanceOf(ReadOnlyRequiredException.class); } - @Test(expected = ReadWriteRequiredException.class) - public void testCheckWritesAllowed() { - try (Txn txn = env.txnRead()) { - txn.checkWritesAllowed(); - } + @Test + void testCheckWritesAllowed() { + assertThatThrownBy( + () -> { + try (Txn txn = env.txnRead()) { + txn.checkWritesAllowed(); + } + }) + .isInstanceOf(ReadWriteRequiredException.class); } @Test - public void testGetId() { + void testGetId() { final Dbi db = env.openDbi(DB_1, MDB_CREATE); final AtomicLong txId1 = new AtomicLong(); @@ -182,173 +197,233 @@ public void testGetId() { txId2.set(tx2.getId()); } // should not see the same snapshot - assertThat(txId1.get(), is(not(txId2.get()))); + assertThat(txId1.get()).isNotEqualTo(txId2.get()); } @Test - public void txCanCommitThenCloseWithoutError() { + void txCanCommitThenCloseWithoutError() { try (Txn txn = env.txnRead()) { - assertThat(txn.getState(), is(READY)); + assertThat(txn.getState()).isEqualTo(READY); txn.commit(); - assertThat(txn.getState(), is(DONE)); + assertThat(txn.getState()).isEqualTo(DONE); } } - @Test(expected = NotReadyException.class) - public void txCannotAbortIfAlreadyCommitted() { - try (Txn txn = env.txnRead()) { - assertThat(txn.getState(), is(READY)); - txn.commit(); - assertThat(txn.getState(), is(DONE)); - txn.abort(); - } + @Test + void txCannotAbortIfAlreadyCommitted() { + assertThatThrownBy( + () -> { + try (Txn txn = env.txnRead()) { + assertThat(txn.getState()).isEqualTo(READY); + txn.commit(); + assertThat(txn.getState()).isEqualTo(DONE); + txn.abort(); + } + }) + .isInstanceOf(NotReadyException.class); } - @Test(expected = NotReadyException.class) - public void txCannotCommitTwice() { - try (Txn txn = env.txnRead()) { - txn.commit(); - txn.commit(); // error - } + @Test + void txCannotCommitTwice() { + assertThatThrownBy( + () -> { + try (Txn txn = env.txnRead()) { + txn.commit(); + txn.commit(); // error + } + }) + .isInstanceOf(NotReadyException.class); } - @Test(expected = AlreadyClosedException.class) - public void txConstructionDeniedIfEnvClosed() { - env.close(); - env.txnRead(); + @Test + void txConstructionDeniedIfEnvClosed() { + assertThatThrownBy( + () -> { + env.close(); + env.txnRead(); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void txRenewDeniedIfEnvClosed() { - final Txn txnRead = env.txnRead(); - txnRead.close(); - env.close(); - txnRead.renew(); + @Test + void txRenewDeniedIfEnvClosed() { + assertThatThrownBy( + () -> { + final Txn txnRead = env.txnRead(); + txnRead.close(); + env.close(); + txnRead.renew(); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void txCloseDeniedIfEnvClosed() { - final Txn txnRead = env.txnRead(); - env.close(); - txnRead.close(); + @Test + void txCloseDeniedIfEnvClosed() { + assertThatThrownBy( + () -> { + final Txn txnRead = env.txnRead(); + env.close(); + txnRead.close(); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void txCommitDeniedIfEnvClosed() { - final Txn txnRead = env.txnRead(); - env.close(); - txnRead.commit(); + @Test + void txCommitDeniedIfEnvClosed() { + assertThatThrownBy( + () -> { + final Txn txnRead = env.txnRead(); + env.close(); + txnRead.commit(); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void txAbortDeniedIfEnvClosed() { - final Txn txnRead = env.txnRead(); - env.close(); - txnRead.abort(); + @Test + void txAbortDeniedIfEnvClosed() { + assertThatThrownBy( + () -> { + final Txn txnRead = env.txnRead(); + env.close(); + txnRead.abort(); + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = AlreadyClosedException.class) - public void txResetDeniedIfEnvClosed() { - final Txn txnRead = env.txnRead(); - env.close(); - txnRead.reset(); + @Test + void txResetDeniedIfEnvClosed() { + assertThatThrownBy( + () -> { + final Txn txnRead = env.txnRead(); + env.close(); + txnRead.reset(); + }) + .isInstanceOf(AlreadyClosedException.class); } @Test public void txParent() { try (Txn txRoot = env.txnWrite(); Txn txChild = env.txn(txRoot)) { - assertThat(txRoot.getParent(), is(nullValue())); - assertThat(txChild.getParent(), is(txRoot)); + assertThat(txRoot.getParent()).isNull(); + assertThat(txChild.getParent()).isEqualTo(txRoot); } } - @Test(expected = AlreadyClosedException.class) - public void txParentDeniedIfEnvClosed() { - try (Txn txRoot = env.txnWrite(); - Txn txChild = env.txn(txRoot)) { - env.close(); - assertThat(txChild.getParent(), is(txRoot)); - } + @Test + void txParentDeniedIfEnvClosed() { + assertThatThrownBy( + () -> { + try (Txn txRoot = env.txnWrite(); + Txn txChild = env.txn(txRoot)) { + env.close(); + assertThat(txChild.getParent()).isEqualTo(txRoot); + } + }) + .isInstanceOf(AlreadyClosedException.class); } - @Test(expected = IncompatibleParent.class) - public void txParentROChildRWIncompatible() { - try (Txn txRoot = env.txnRead()) { - env.txn(txRoot); // error - } + @Test + void txParentROChildRWIncompatible() { + assertThatThrownBy( + () -> { + try (Txn txRoot = env.txnRead()) { + env.txn(txRoot); // error + } + }) + .isInstanceOf(IncompatibleParent.class); } - @Test(expected = IncompatibleParent.class) - public void txParentRWChildROIncompatible() { - try (Txn txRoot = env.txnWrite()) { - env.txn(txRoot, MDB_RDONLY_TXN); // error - } + @Test + void txParentRWChildROIncompatible() { + assertThatThrownBy( + () -> { + try (Txn txRoot = env.txnWrite()) { + env.txn(txRoot, MDB_RDONLY_TXN); // error + } + }) + .isInstanceOf(IncompatibleParent.class); } @Test - public void txReadOnly() { + void txReadOnly() { try (Txn txn = env.txnRead()) { - assertThat(txn.getParent(), is(nullValue())); - assertThat(txn.getState(), is(READY)); - assertThat(txn.isReadOnly(), is(true)); + assertThat(txn.getParent()).isNull(); + assertThat(txn.getState()).isEqualTo(READY); + assertThat(txn.isReadOnly()).isTrue(); txn.checkReady(); txn.checkReadOnly(); txn.reset(); - assertThat(txn.getState(), is(RESET)); + assertThat(txn.getState()).isEqualTo(RESET); txn.renew(); - assertThat(txn.getState(), is(READY)); + assertThat(txn.getState()).isEqualTo(READY); txn.commit(); - assertThat(txn.getState(), is(DONE)); + assertThat(txn.getState()).isEqualTo(DONE); txn.close(); - assertThat(txn.getState(), is(RELEASED)); + assertThat(txn.getState()).isEqualTo(RELEASED); } } @Test - public void txReadWrite() { + void txReadWrite() { final Txn txn = env.txnWrite(); - assertThat(txn.getParent(), is(nullValue())); - assertThat(txn.getState(), is(READY)); - assertThat(txn.isReadOnly(), is(false)); + assertThat(txn.getParent()).isNull(); + assertThat(txn.getState()).isEqualTo(READY); + assertThat(txn.isReadOnly()).isFalse(); txn.checkReady(); txn.checkWritesAllowed(); txn.commit(); - assertThat(txn.getState(), is(DONE)); + assertThat(txn.getState()).isEqualTo(DONE); txn.close(); - assertThat(txn.getState(), is(RELEASED)); + assertThat(txn.getState()).isEqualTo(RELEASED); } - @Test(expected = NotResetException.class) - public void txRenewDeniedWithoutPriorReset() { - try (Txn txn = env.txnRead()) { - txn.renew(); - } + @Test + void txRenewDeniedWithoutPriorReset() { + assertThatThrownBy( + () -> { + try (Txn txn = env.txnRead()) { + txn.renew(); + } + }) + .isInstanceOf(NotResetException.class); } - @Test(expected = ResetException.class) - public void txResetDeniedForAlreadyResetTransaction() { - try (Txn txn = env.txnRead()) { - txn.reset(); - txn.renew(); - txn.reset(); - txn.reset(); - } + @Test + void txResetDeniedForAlreadyResetTransaction() { + assertThatThrownBy( + () -> { + try (Txn txn = env.txnRead()) { + txn.reset(); + txn.renew(); + txn.reset(); + txn.reset(); + } + }) + .isInstanceOf(ResetException.class); } - @Test(expected = ReadOnlyRequiredException.class) - public void txResetDeniedForReadWriteTransaction() { - try (Txn txn = env.txnWrite()) { - txn.reset(); - } + @Test + void txResetDeniedForReadWriteTransaction() { + assertThatThrownBy( + () -> { + try (Txn txn = env.txnWrite()) { + txn.reset(); + } + }) + .isInstanceOf(ReadOnlyRequiredException.class); } - @Test(expected = BadValueSizeException.class) - public void zeroByteKeysRejected() throws IOException { - final Dbi dbi = env.openDbi(DB_1, MDB_CREATE); - final ByteBuffer key = allocateDirect(4); - key.putInt(1); - assertThat(key.remaining(), is(0)); // because key.flip() skipped - dbi.put(key, bb(2)); + @Test + void zeroByteKeysRejected() { + assertThatThrownBy( + () -> { + final Dbi dbi = env.openDbi(DB_1, MDB_CREATE); + final ByteBuffer key = allocateDirect(4); + key.putInt(1); + assertThat(key.remaining()).isEqualTo(0); // because key.flip() skipped + dbi.put(key, bb(2)); + }) + .isInstanceOf(BadValueSizeException.class); } } diff --git a/src/test/java/org/lmdbjava/VerifierTest.java b/src/test/java/org/lmdbjava/VerifierTest.java index c57e26dc..64214568 100644 --- a/src/test/java/org/lmdbjava/VerifierTest.java +++ b/src/test/java/org/lmdbjava/VerifierTest.java @@ -13,39 +13,35 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.lmdbjava; import static com.jakewharton.byteunits.BinaryByteUnit.MEBIBYTES; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.greaterThan; +import static org.assertj.core.api.Assertions.assertThat; import static org.lmdbjava.Env.create; import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; -import java.io.File; -import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.TimeUnit; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.Test; /** Test {@link Verifier}. */ public final class VerifierTest { - @Rule public final TemporaryFolder tmp = new TemporaryFolder(); - @Test - public void verification() throws IOException { - final File path = tmp.newFile(); - try (Env env = - create() - .setMaxReaders(1) - .setMaxDbs(Verifier.DBI_COUNT) - .setMapSize(MEBIBYTES.toBytes(10)) - .open(path, MDB_NOSUBDIR)) { - final Verifier v = new Verifier(env); - final int seconds = Integer.getInteger("verificationSeconds", 2); - assertThat(v.runFor(seconds, TimeUnit.SECONDS), greaterThan(1L)); - } + void verification() { + FileUtil.useTempFile( + file -> { + try (Env env = + create() + .setMaxReaders(1) + .setMaxDbs(Verifier.DBI_COUNT) + .setMapSize(MEBIBYTES.toBytes(10)) + .open(file.toFile(), MDB_NOSUBDIR)) { + final Verifier v = new Verifier(env); + final int seconds = Integer.getInteger("verificationSeconds", 2); + assertThat(v.runFor(seconds, TimeUnit.SECONDS)).isGreaterThan(1L); + } + }); } } From 1b3f94df197b0889e6612418d3a43a9f496ee452 Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Tue, 28 Oct 2025 18:21:58 +0000 Subject: [PATCH 233/322] Change CursorIterableTest to use Parameterized --- .../java/org/lmdbjava/CursorIterableTest.java | 205 ++++++++++-------- src/test/java/org/lmdbjava/TestUtils.java | 1 + 2 files changed, 116 insertions(+), 90 deletions(-) diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index 79a9a34c..bf2eb9eb 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -45,6 +45,7 @@ import static org.lmdbjava.TestUtils.DB_1; import static org.lmdbjava.TestUtils.DB_2; import static org.lmdbjava.TestUtils.DB_3; +import static org.lmdbjava.TestUtils.DB_4; import static org.lmdbjava.TestUtils.POSIX_MODE; import static org.lmdbjava.TestUtils.bb; @@ -59,25 +60,83 @@ import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; +import java.util.function.Function; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; import org.lmdbjava.CursorIterable.KeyVal; -/** Test {@link CursorIterable}. */ +/** + * Test {@link CursorIterable}. + */ +@RunWith(Parameterized.class) public final class CursorIterableTest { - @Rule public final TemporaryFolder tmp = new TemporaryFolder(); - private Dbi dbJavaComparator; - private Dbi dbLmdbComparator; - private Dbi dbCallbackComparator; - private List> dbs = new ArrayList<>(); + private static final DbiFlagSet dbiFlagSet = MDB_CREATE; + private static final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; + + @Rule + public final TemporaryFolder tmp = new TemporaryFolder(); + private Env env; private Deque list; + /** Injected by {@link #data()} with appropriate runner. */ + @Parameterized.Parameter + public DbiFactory dbiFactory; + + @Before + public void before() throws IOException { + final File path = tmp.newFile(); + final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; + env = + create(bufferProxy) + .setMapSize(KIBIBYTES.toBytes(256)) + .setMaxReaders(1) + .setMaxDbs(3) + .open(path, POSIX_MODE, MDB_NOSUBDIR); + + populateTestDataList(); + } + + @Parameterized.Parameters(name = "{index}: dbi: {0}") + public static Object[] data() { + final DbiFactory defaultComparator = new DbiFactory("defaultComparator", env -> + env.buildDbi() + .withDbName(DB_1) + .withDefaultComparator() + .withDbiFlags(dbiFlagSet) + .open()); + final DbiFactory nativeComparator = new DbiFactory("nativeComparator", env -> + env.buildDbi() + .withDbName(DB_2) + .withNativeComparator() + .withDbiFlags(dbiFlagSet) + .open()); + final DbiFactory callbackComparator = new DbiFactory("callbackComparator", env -> + env.buildDbi() + .withDbName(DB_3) + .withCallbackComparator(bufferProxy.getComparator(dbiFlagSet)) + .withDbiFlags(dbiFlagSet) + .open()); + final DbiFactory iteratorComparator = new DbiFactory("iteratorComparator", env -> + env.buildDbi() + .withDbName(DB_4) + .withIteratorComparator(bufferProxy.getComparator(dbiFlagSet)) + .withDbiFlags(dbiFlagSet) + .open()); + return new Object[] { + defaultComparator, + nativeComparator, + callbackComparator, + iteratorComparator}; + } + @After public void after() { env.close(); @@ -118,49 +177,8 @@ public void atMostTest() { verify(atMost(bb(6)), 2, 4, 6); } - @Before - public void before() throws IOException { - final File path = tmp.newFile(); - final BufferProxy bufferProxy = ByteBufferProxy.PROXY_OPTIMAL; - env = - create(bufferProxy) - .setMapSize(KIBIBYTES.toBytes(256)) - .setMaxReaders(1) - .setMaxDbs(3) - .open(path, POSIX_MODE, MDB_NOSUBDIR); - final DbiFlagSet dbiFlagSet = MDB_CREATE; - // Use a java comparator for start/stop keys only - dbJavaComparator = env.buildDbi() - .withDbName(DB_1) - .withDefaultComparator() - .withDbiFlags(dbiFlagSet) - .open(); - // Use LMDB comparator for start/stop keys - dbLmdbComparator = env.buildDbi() - .withDbName(DB_2) - .withNativeComparator() - .withDbiFlags(dbiFlagSet) - .open(); - // Use a java comparator for start/stop keys and as a callback comparaotr - dbCallbackComparator = env.buildDbi() - .withDbName(DB_3) - .withCallbackComparator(bufferProxy.getComparator(dbiFlagSet)) - .withDbiFlags(dbiFlagSet) - .open(); - - populateList(); - - populateDatabase(dbJavaComparator); - populateDatabase(dbLmdbComparator); - populateDatabase(dbCallbackComparator); - - dbs.add(dbJavaComparator); - dbs.add(dbLmdbComparator); - dbs.add(dbCallbackComparator); - } - - private void populateList() { + private void populateTestDataList() { list = new LinkedList<>(); list.addAll(asList(2, 3, 4, 5, 6, 7, 8, 9)); } @@ -203,14 +221,6 @@ public void closedTest() { verify(closed(bb(1), bb(7)), 2, 4, 6); } - public void closedTest1() { - verify(dbLmdbComparator, closed(bb(3), bb(7)), 4, 6); - } - - public void closedTest2() { - verify(dbJavaComparator, closed(bb(3), bb(7)), 4, 6); - } - @Test public void greaterThanBackwardTest() { verify(greaterThanBackward(bb(6)), 4, 2); @@ -226,21 +236,19 @@ public void greaterThanTest() { @Test(expected = IllegalStateException.class) public void iterableOnlyReturnedOnce() { - for (final Dbi db : dbs) { - try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { + final Dbi db = getDb(); + try (Txn txn = env.txnRead(); + CursorIterable c = db.iterate(txn)) { c.iterator(); // ok c.iterator(); // fails } - } } @Test public void iterate() { - for (final Dbi db : dbs) { - populateList(); + final Dbi db = getDb(); try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { + CursorIterable c = db.iterate(txn)) { int cnt = 0; for (final KeyVal kv : c) { @@ -248,18 +256,16 @@ public void iterate() { assertThat(kv.val().getInt(), is(list.pollFirst())); } } - } } @Test(expected = IllegalStateException.class) public void iteratorOnlyReturnedOnce() { - for (final Dbi db : dbs) { + final Dbi db = getDb(); try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { + CursorIterable c = db.iterate(txn)) { c.iterator(); // ok c.iterator(); // fails } - } } @Test @@ -276,10 +282,10 @@ public void lessThanTest() { @Test(expected = NoSuchElementException.class) public void nextThrowsNoSuchElementExceptionIfNoMoreElements() { - for (final Dbi db : dbs) { - populateList(); + final Dbi db = getDb(); + populateTestDataList(); try (Txn txn = env.txnRead(); - CursorIterable c = db.iterate(txn)) { + CursorIterable c = db.iterate(txn)) { final Iterator> i = c.iterator(); while (i.hasNext()) { final KeyVal kv = i.next(); @@ -289,7 +295,6 @@ public void nextThrowsNoSuchElementExceptionIfNoMoreElements() { assertThat(i.hasNext(), is(false)); i.next(); } - } } @Test @@ -341,8 +346,8 @@ public void openTest() { @Test public void removeOddElements() { - for (final Dbi db : dbs) { - verify(db, all(), 2, 4, 6, 8); + final Dbi db = getDb(); + verify(db, all(), 2, 4, 6, 8); int idx = -1; try (Txn txn = env.txnWrite()) { try (CursorIterable ci = db.iterate(txn)) { @@ -358,12 +363,11 @@ public void removeOddElements() { txn.commit(); } verify(db, all(), 4, 8); - } } @Test(expected = Env.AlreadyClosedException.class) public void nextWithClosedEnvTest() { - for (final Dbi db : dbs) { + final Dbi db = getDb(); try (Txn txn = env.txnRead()) { try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { final Iterator> c = ci.iterator(); @@ -372,12 +376,11 @@ public void nextWithClosedEnvTest() { c.next(); } } - } } @Test(expected = Env.AlreadyClosedException.class) public void removeWithClosedEnvTest() { - for (final Dbi db : dbs) { + final Dbi db = getDb(); try (Txn txn = env.txnWrite()) { try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { final Iterator> c = ci.iterator(); @@ -389,12 +392,11 @@ public void removeWithClosedEnvTest() { c.remove(); } } - } } @Test(expected = Env.AlreadyClosedException.class) public void hasNextWithClosedEnvTest() { - for (final Dbi db : dbs) { + final Dbi db = getDb(); try (Txn txn = env.txnRead()) { try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { final Iterator> c = ci.iterator(); @@ -403,21 +405,20 @@ public void hasNextWithClosedEnvTest() { c.hasNext(); } } - } } @Test(expected = Env.AlreadyClosedException.class) public void forEachRemainingWithClosedEnvTest() { - for (final Dbi db : dbs) { + final Dbi db = getDb(); try (Txn txn = env.txnRead()) { try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { final Iterator> c = ci.iterator(); env.close(); - c.forEachRemaining(keyVal -> {}); + c.forEachRemaining(keyVal -> { + }); } } - } } // @Test @@ -468,10 +469,8 @@ public void forEachRemainingWithClosedEnvTest() { // } private void verify(final KeyRange range, final int... expected) { - // Verify using all comparator types - for (final Dbi db : dbs) { - verify(range, db, expected); - } + final Dbi db = getDb(); + verify(range, db, expected); } private void verify( @@ -479,13 +478,14 @@ private void verify( verify(range, dbi, expected); } - private void verify( - final KeyRange range, final Dbi dbi, final int... expected) { + private void verify(final KeyRange range, + final Dbi dbi, + final int... expected) { final List results = new ArrayList<>(); try (Txn txn = env.txnRead(); - CursorIterable c = dbi.iterate(txn, range)) { + CursorIterable c = dbi.iterate(txn, range)) { for (final KeyVal kv : c) { final int key = kv.key().getInt(); final int val = kv.val().getInt(); @@ -499,4 +499,29 @@ private void verify( assertThat(results.get(idx), is(expected[idx])); } } + + private Dbi getDb() { + final Dbi dbi = dbiFactory.factory.apply(env); + populateDatabase(dbi); + return dbi; + } + + + // -------------------------------------------------------------------------------- + + + private static class DbiFactory { + private final String name; + private final Function, Dbi> factory; + + private DbiFactory(String name, Function, Dbi> factory) { + this.name = name; + this.factory = factory; + } + + @Override + public String toString() { + return name; + } + } } diff --git a/src/test/java/org/lmdbjava/TestUtils.java b/src/test/java/org/lmdbjava/TestUtils.java index bc9561ed..c26c1c52 100644 --- a/src/test/java/org/lmdbjava/TestUtils.java +++ b/src/test/java/org/lmdbjava/TestUtils.java @@ -33,6 +33,7 @@ final class TestUtils { public static final String DB_1 = "test-db-1"; public static final String DB_2 = "test-db-2"; public static final String DB_3 = "test-db-3"; + public static final String DB_4 = "test-db-2"; public static final int POSIX_MODE = 0664; From 05314e8b2bdda8cb71d68eb9c7baeeffe60788fe Mon Sep 17 00:00:00 2001 From: stroomdev66 Date: Tue, 28 Oct 2025 18:32:38 +0000 Subject: [PATCH 234/322] Upgrade to JUnit 5 and replace Hamcrest with AssertJ --- src/test/java/org/lmdbjava/DbiTest.java | 79 +++++++++++-------------- 1 file changed, 34 insertions(+), 45 deletions(-) diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index 844a819e..23b6790e 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -29,20 +29,14 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.lmdbjava.ByteArrayProxy.PROXY_BA; import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; -import static org.lmdbjava.DbiFlags.MDB_CREATE; -import static org.lmdbjava.DbiFlags.MDB_DUPSORT; -import static org.lmdbjava.DbiFlags.MDB_INTEGERKEY; -import static org.lmdbjava.DbiFlags.MDB_REVERSEKEY; +import static org.lmdbjava.DbiFlags.*; import static org.lmdbjava.Env.create; import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; import static org.lmdbjava.GetOp.MDB_SET_KEY; import static org.lmdbjava.KeyRange.atMost; import static org.lmdbjava.PutFlags.MDB_NODUPDATA; import static org.lmdbjava.PutFlags.MDB_NOOVERWRITE; -import static org.lmdbjava.TestUtils.DB_1; -import static org.lmdbjava.TestUtils.ba; -import static org.lmdbjava.TestUtils.bb; -import static org.lmdbjava.TestUtils.fromBa; +import static org.lmdbjava.TestUtils.*; import java.nio.ByteBuffer; import java.nio.file.Path; @@ -50,11 +44,7 @@ import java.util.Comparator; import java.util.Iterator; import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeoutException; +import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiConsumer; import java.util.function.Function; @@ -197,44 +187,43 @@ private void doDbiWithComparatorThreadSafety( final List keys = range(0, 1_000).boxed().collect(toList()); - try (final ExecutorService pool = Executors.newCachedThreadPool()) { - final AtomicBoolean proceed = new AtomicBoolean(true); - final Future reader = - pool.submit( - () -> { - while (proceed.get()) { - try (Txn txn = env.txnRead()) { - db.get(txn, serializer.apply(50)); - } + final ExecutorService pool = Executors.newCachedThreadPool(); + final AtomicBoolean proceed = new AtomicBoolean(true); + final Future reader = + pool.submit( + () -> { + while (proceed.get()) { + try (Txn txn = env.txnRead()) { + db.get(txn, serializer.apply(50)); } - }); + } + }); - for (final Integer key : keys) { - try (Txn txn = env.txnWrite()) { - db.put(txn, serializer.apply(key), serializer.apply(3)); - txn.commit(); - } + for (final Integer key : keys) { + try (Txn txn = env.txnWrite()) { + db.put(txn, serializer.apply(key), serializer.apply(3)); + txn.commit(); } + } - try (Txn txn = env.txnRead(); - CursorIterable ci = db.iterate(txn)) { - final Iterator> iter = ci.iterator(); - final List result = new ArrayList<>(); - while (iter.hasNext()) { - result.add(deserializer.applyAsInt(iter.next().key())); - } - - assertThat(result).contains(keys.toArray(new Integer[0])); + try (Txn txn = env.txnRead(); + CursorIterable ci = db.iterate(txn)) { + final Iterator> iter = ci.iterator(); + final List result = new ArrayList<>(); + while (iter.hasNext()) { + result.add(deserializer.applyAsInt(iter.next().key())); } - proceed.set(false); - try { - reader.get(1, SECONDS); - pool.shutdown(); - pool.awaitTermination(1, SECONDS); - } catch (ExecutionException | InterruptedException | TimeoutException e) { - throw new IllegalStateException(e); - } + assertThat(result).contains(keys.toArray(new Integer[0])); + } + + proceed.set(false); + try { + reader.get(1, SECONDS); + pool.shutdown(); + pool.awaitTermination(1, SECONDS); + } catch (ExecutionException | InterruptedException | TimeoutException e) { + throw new IllegalStateException(e); } } From bc48cff3284719ee477d7c471cf72c48aa212c66 Mon Sep 17 00:00:00 2001 From: stroomdev66 Date: Tue, 28 Oct 2025 18:56:14 +0000 Subject: [PATCH 235/322] Upgrade LMDB C lib to 0.9.33 direct from the OpenLDAP repository --- cross-compile.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cross-compile.sh b/cross-compile.sh index aa8815d5..5243feec 100755 --- a/cross-compile.sh +++ b/cross-compile.sh @@ -18,9 +18,9 @@ set -o errexit -rm -rf lmdb -git clone --depth 1 --branch LMDB_0.9.31 https://github.com/LMDB/lmdb.git -pushd lmdb/libraries/liblmdb +rm -rf openldap +git clone --depth 1 --branch LMDB_0.9.33 https://git.openldap.org/openldap/openldap.git +pushd openldap/libraries/liblmdb trap popd SIGINT # zig targets | jq -r '.libc[]' From f2de1bdc6f9586466f31b607209e70e0edf4cf32 Mon Sep 17 00:00:00 2001 From: stroomdev66 Date: Tue, 28 Oct 2025 19:01:56 +0000 Subject: [PATCH 236/322] Upgrade to JUnit 5 and replace Hamcrest with AssertJ --- .../java/org/lmdbjava/ComparatorTest.java | 22 +++++++++---------- .../java/org/lmdbjava/CursorParamTest.java | 12 +++++----- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/test/java/org/lmdbjava/ComparatorTest.java b/src/test/java/org/lmdbjava/ComparatorTest.java index b4946a26..a5e010ab 100644 --- a/src/test/java/org/lmdbjava/ComparatorTest.java +++ b/src/test/java/org/lmdbjava/ComparatorTest.java @@ -56,14 +56,14 @@ public final class ComparatorTest { static Stream comparatorProvider() { return Stream.of( - Arguments.arguments("StringRunner", new StringRunner()), - Arguments.arguments("DirectBufferRunner", new DirectBufferRunner()), - Arguments.arguments("ByteArrayRunner", new ByteArrayRunner()), - Arguments.arguments("UnsignedByteArrayRunner", new UnsignedByteArrayRunner()), - Arguments.arguments("ByteBufferRunner", new ByteBufferRunner()), - Arguments.arguments("NettyRunner", new NettyRunner()), - Arguments.arguments("GuavaUnsignedBytes", new GuavaUnsignedBytes()), - Arguments.arguments("GuavaSignedBytes", new GuavaSignedBytes())); + Arguments.argumentSet("StringRunner", new StringRunner()), + Arguments.argumentSet("DirectBufferRunner", new DirectBufferRunner()), + Arguments.argumentSet("ByteArrayRunner", new ByteArrayRunner()), + Arguments.argumentSet("UnsignedByteArrayRunner", new UnsignedByteArrayRunner()), + Arguments.argumentSet("ByteBufferRunner", new ByteBufferRunner()), + Arguments.argumentSet("NettyRunner", new NettyRunner()), + Arguments.argumentSet("GuavaUnsignedBytes", new GuavaUnsignedBytes()), + Arguments.argumentSet("GuavaSignedBytes", new GuavaSignedBytes())); } private static byte[] buffer(final int... bytes) { @@ -76,7 +76,7 @@ private static byte[] buffer(final int... bytes) { @ParameterizedTest @MethodSource("comparatorProvider") - void atLeastOneBufferHasEightBytes(final String str, final ComparatorRunner comparator) { + void atLeastOneBufferHasEightBytes(final ComparatorRunner comparator) { assertThat(get(comparator.compare(HLLLLLLL, LLLLLLLL))).isEqualTo(GREATER_THAN); assertThat(get(comparator.compare(LLLLLLLL, HLLLLLLL))).isEqualTo(LESS_THAN); @@ -95,7 +95,7 @@ void atLeastOneBufferHasEightBytes(final String str, final ComparatorRunner comp @ParameterizedTest @MethodSource("comparatorProvider") - void buffersOfTwoBytes(final String str, final ComparatorRunner comparator) { + void buffersOfTwoBytes(final ComparatorRunner comparator) { assertThat(get(comparator.compare(LL, XX))).isEqualTo(GREATER_THAN); assertThat(get(comparator.compare(XX, LL))).isEqualTo(LESS_THAN); @@ -111,7 +111,7 @@ void buffersOfTwoBytes(final String str, final ComparatorRunner comparator) { @ParameterizedTest @MethodSource("comparatorProvider") - void equalBuffers(final String str, final ComparatorRunner comparator) { + void equalBuffers(final ComparatorRunner comparator) { assertThat(get(comparator.compare(LL, LL))).isEqualTo(EQUAL_TO); assertThat(get(comparator.compare(HX, HX))).isEqualTo(EQUAL_TO); assertThat(get(comparator.compare(LH, LH))).isEqualTo(EQUAL_TO); diff --git a/src/test/java/org/lmdbjava/CursorParamTest.java b/src/test/java/org/lmdbjava/CursorParamTest.java index 0b2df19e..1a3604be 100644 --- a/src/test/java/org/lmdbjava/CursorParamTest.java +++ b/src/test/java/org/lmdbjava/CursorParamTest.java @@ -58,16 +58,16 @@ public final class CursorParamTest { static Stream data() { return Stream.of( - Arguments.arguments("ByteBufferRunner(PROXY_OPTIMAL)", new ByteBufferRunner(PROXY_OPTIMAL)), - Arguments.arguments("ByteBufferRunner(PROXY_SAFE)", new ByteBufferRunner(PROXY_SAFE)), - Arguments.arguments("ByteArrayRunner(PROXY_BA)", new ByteArrayRunner(PROXY_BA)), - Arguments.arguments("DirectBufferRunner", new DirectBufferRunner()), - Arguments.arguments("NettyBufferRunner", new NettyBufferRunner())); + Arguments.argumentSet("ByteBufferRunner(PROXY_OPTIMAL)", new ByteBufferRunner(PROXY_OPTIMAL)), + Arguments.argumentSet("ByteBufferRunner(PROXY_SAFE)", new ByteBufferRunner(PROXY_SAFE)), + Arguments.argumentSet("ByteArrayRunner(PROXY_BA)", new ByteArrayRunner(PROXY_BA)), + Arguments.argumentSet("DirectBufferRunner", new DirectBufferRunner()), + Arguments.argumentSet("NettyBufferRunner", new NettyBufferRunner())); } @ParameterizedTest @MethodSource("data") - void execute(final String name, final BufferRunner runner, @TempDir final Path tmp) { + void execute(final BufferRunner runner, @TempDir final Path tmp) { runner.execute(tmp); } From c0bbe73cb1d128ebb04baf842b9ed98b941a8e7f Mon Sep 17 00:00:00 2001 From: at055612 <22818309+at055612@users.noreply.github.com> Date: Tue, 28 Oct 2025 21:13:21 +0000 Subject: [PATCH 237/322] Deprecate methods using varargs flags --- src/main/java/org/lmdbjava/Cursor.java | 181 ++++++++++++++++-- src/main/java/org/lmdbjava/Dbi.java | 99 +++++++--- src/main/java/org/lmdbjava/DbiBuilder.java | 14 +- src/main/java/org/lmdbjava/DbiFlagSet.java | 2 + src/main/java/org/lmdbjava/Env.java | 179 +++++++++-------- src/main/java/org/lmdbjava/EnvFlagSet.java | 2 + src/main/java/org/lmdbjava/FlagSet.java | 33 ++++ src/main/java/org/lmdbjava/MaskedFlag.java | 4 + src/main/java/org/lmdbjava/PutFlagSet.java | 2 + .../org/lmdbjava/CursorIterablePerfTest.java | 6 +- 10 files changed, 392 insertions(+), 130 deletions(-) diff --git a/src/main/java/org/lmdbjava/Cursor.java b/src/main/java/org/lmdbjava/Cursor.java index 6d31a3d6..c1ac7374 100644 --- a/src/main/java/org/lmdbjava/Cursor.java +++ b/src/main/java/org/lmdbjava/Cursor.java @@ -20,8 +20,6 @@ import static org.lmdbjava.Dbi.KeyNotFoundException.MDB_NOTFOUND; import static org.lmdbjava.Env.SHOULD_CHECK; import static org.lmdbjava.Library.LIB; -import static org.lmdbjava.MaskedFlag.isSet; -import static org.lmdbjava.MaskedFlag.mask; import static org.lmdbjava.PutFlags.MDB_MULTIPLE; import static org.lmdbjava.PutFlags.MDB_NODUPDATA; import static org.lmdbjava.PutFlags.MDB_NOOVERWRITE; @@ -97,23 +95,49 @@ public long count() { checkRc(LIB.mdb_cursor_count(ptrCursor, longByReference)); return longByReference.longValue(); } + /** + * @deprecated Instead use {@link Cursor#delete(PutFlagSet)}. + *
+ * Delete current key/data pair. + * + *

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


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

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

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


* Store by cursor. * *

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

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

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


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

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

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


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

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

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


+ *

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

*

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

In our example and with a passed search key of 5, the returned keys would be 8 and 6. With a * passed key of 2, the returned keys would be 8, 6 and 4 */ - BACKWARD_LESS_THAN(false, false, true), + BACKWARD_LESS_THAN(false, false, false, true, false), /** * Iterate backward between the passed keys, but do not return the passed keys. * @@ -210,7 +210,7 @@ public enum KeyRangeType { * With a range of 8 - 1, the keys would be 6, 4 and 2. With a range of 9 - 4, the keys would be 8 * and 6. */ - BACKWARD_OPEN(false, true, true), + BACKWARD_OPEN(false, true, false, true, false), /** * Iterate backward between the passed keys. Do not return the "start" key, but do return the * "stop" key. @@ -221,19 +221,25 @@ public enum KeyRangeType { * 2. With a range of 8 - 4, the keys would be 6 and 4. With a range of 9 - 4, the keys would be * 8, 6 and 4. */ - BACKWARD_OPEN_CLOSED(false, true, true); + BACKWARD_OPEN_CLOSED(false, true, false, true, true); private final boolean directionForward; private final boolean startKeyRequired; + private final boolean startKeyInclusive; private final boolean stopKeyRequired; + private final boolean stopKeyInclusive; KeyRangeType( final boolean directionForward, final boolean startKeyRequired, - final boolean stopKeyRequired) { + final boolean startKeyInclusive, + final boolean stopKeyRequired, + final boolean stopKeyInclusive) { this.directionForward = directionForward; this.startKeyRequired = startKeyRequired; + this.startKeyInclusive = startKeyInclusive; this.stopKeyRequired = stopKeyRequired; + this.stopKeyInclusive = stopKeyInclusive; } /** @@ -254,6 +260,16 @@ public boolean isStartKeyRequired() { return startKeyRequired; } + /** + * Is the start key to be treated as inclusive in the range. + * + * @return true if start key is inclusive. False if not inclusive or no start key is required by + * the range type. + */ + public boolean isStartKeyInclusive() { + return startKeyInclusive; + } + /** * Whether the iteration requires a "stop" key. * @@ -263,6 +279,16 @@ public boolean isStopKeyRequired() { return stopKeyRequired; } + /** + * Is the stop key to be treated as inclusive in the range. + * + * @return true if stop key is inclusive. False if not inclusive or no stop key is required by the + * range type. + */ + public boolean isStopKeyInclusive() { + return stopKeyInclusive; + } + /** * Determine the iterator action to take when iterator first begins. * diff --git a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java index 82c0abce..d5638fe2 100644 --- a/src/test/java/org/lmdbjava/ByteBufferProxyTest.java +++ b/src/test/java/org/lmdbjava/ByteBufferProxyTest.java @@ -36,6 +36,7 @@ import java.lang.reflect.Field; import java.nio.ByteBuffer; +import java.nio.file.Path; import jnr.ffi.Pointer; import jnr.ffi.provider.MemoryManager; import org.junit.jupiter.api.Test; @@ -51,17 +52,17 @@ public final class ByteBufferProxyTest { void buffersMustBeDirect() { assertThatThrownBy( () -> { - FileUtil.useTempDir( - dir -> { - try (Env env = create().setMaxReaders(1).open(dir.toFile())) { - final Dbi db = env.openDbi(DB_1, MDB_CREATE); - final ByteBuffer key = allocate(100); - key.putInt(1).flip(); - final ByteBuffer val = allocate(100); - val.putInt(1).flip(); - db.put(key, val); // error - } - }); + try (final TempDir tempDir = new TempDir()) { + final Path dir = tempDir.createTempDir(); + try (final Env env = create().setMaxReaders(1).open(dir.toFile())) { + final Dbi db = env.openDbi(DB_1, MDB_CREATE); + final ByteBuffer key = allocate(100); + key.putInt(1).flip(); + final ByteBuffer val = allocate(100); + val.putInt(1).flip(); + db.put(key, val); // error + } + } }) .isInstanceOf(BufferMustBeDirectException.class); } diff --git a/src/test/java/org/lmdbjava/CursorIterableRangeTest.java b/src/test/java/org/lmdbjava/CursorIterableRangeTest.java index bff6593e..dd33e885 100644 --- a/src/test/java/org/lmdbjava/CursorIterableRangeTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableRangeTest.java @@ -16,162 +16,175 @@ package org.lmdbjava; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvFileSource; -import org.lmdbjava.ByteBufferProxy.AbstractByteBufferProxy; -import org.lmdbjava.CursorIterable.KeyVal; +import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; +import static java.nio.ByteBuffer.allocateDirect; +import static org.assertj.core.api.Assertions.assertThat; +import static org.lmdbjava.DbiFlags.*; +import static org.lmdbjava.Env.create; +import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; +import static org.lmdbjava.TestUtils.*; import java.io.*; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.nio.file.Files; import java.nio.file.Path; import java.util.Comparator; import java.util.EnumSet; import java.util.function.BiConsumer; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvFileSource; +import org.lmdbjava.ByteBufferProxy.AbstractByteBufferProxy; +import org.lmdbjava.CursorIterable.KeyVal; -import static com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; -import static java.nio.ByteBuffer.allocateDirect; -import static org.assertj.core.api.Assertions.assertThat; -import static org.lmdbjava.DbiFlags.*; -import static org.lmdbjava.Env.create; -import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; -import static org.lmdbjava.TestUtils.*; - -/** - * Test {@link CursorIterable}. - */ +/** Test {@link CursorIterable}. */ public final class CursorIterableRangeTest { @ParameterizedTest(name = "{index} => {0}: ({1}, {2})") @CsvFileSource(resources = "/CursorIterableRangeTest/testSignedComparator.csv") - void testSignedComparator(final String keyType, final String startKey, final String stopKey, final String expectedKV) { - testCSV(ByteBuffer::compareTo, - true, - createBasicDBPopulator(), - EnumSet.of(MDB_CREATE), - keyType, - startKey, - stopKey, - expectedKV); + void testSignedComparator( + final String keyType, final String startKey, final String stopKey, final String expectedKV) { + testCSV( + ByteBuffer::compareTo, + true, + createBasicDBPopulator(), + EnumSet.of(MDB_CREATE), + keyType, + startKey, + stopKey, + expectedKV); } @ParameterizedTest(name = "{index} => {0}: ({1}, {2})") @CsvFileSource(resources = "/CursorIterableRangeTest/testUnsignedComparator.csv") - void testUnsignedComparator(final String keyType, final String startKey, final String stopKey, final String expectedKV) { - testCSV(AbstractByteBufferProxy::compareBuff, - false, - createBasicDBPopulator(), - EnumSet.of(MDB_CREATE), - keyType, - startKey, - stopKey, - expectedKV); + void testUnsignedComparator( + final String keyType, final String startKey, final String stopKey, final String expectedKV) { + testCSV( + AbstractByteBufferProxy::compareBuff, + false, + createBasicDBPopulator(), + EnumSet.of(MDB_CREATE), + keyType, + startKey, + stopKey, + expectedKV); } @ParameterizedTest(name = "{index} => {0}: ({1}, {2})") @CsvFileSource(resources = "/CursorIterableRangeTest/testSignedComparatorDupsort.csv") - void testSignedComparatorDupsort(final String keyType, final String startKey, final String stopKey, final String expectedKV) { - testCSV(ByteBuffer::compareTo, - true, - createMultiDBPopulator(2), - EnumSet.of(MDB_CREATE, MDB_DUPSORT), - keyType, - startKey, - stopKey, - expectedKV); + void testSignedComparatorDupsort( + final String keyType, final String startKey, final String stopKey, final String expectedKV) { + testCSV( + ByteBuffer::compareTo, + true, + createMultiDBPopulator(2), + EnumSet.of(MDB_CREATE, MDB_DUPSORT), + keyType, + startKey, + stopKey, + expectedKV); } @ParameterizedTest(name = "{index} => {0}: ({1}, {2})") @CsvFileSource(resources = "/CursorIterableRangeTest/testUnsignedComparatorDupsort.csv") - void testUnsignedComparatorDupsort(final String keyType, final String startKey, final String stopKey, final String expectedKV) { - testCSV(AbstractByteBufferProxy::compareBuff, - false, - createMultiDBPopulator(2), - EnumSet.of(MDB_CREATE, MDB_DUPSORT), - keyType, - startKey, - stopKey, - expectedKV); + void testUnsignedComparatorDupsort( + final String keyType, final String startKey, final String stopKey, final String expectedKV) { + testCSV( + AbstractByteBufferProxy::compareBuff, + false, + createMultiDBPopulator(2), + EnumSet.of(MDB_CREATE, MDB_DUPSORT), + keyType, + startKey, + stopKey, + expectedKV); } @ParameterizedTest(name = "{index} => {0}: ({1}, {2})") @CsvFileSource(resources = "/CursorIterableRangeTest/testIntegerKey.csv") - void testIntegerKey(final String keyType, final String startKey, final String stopKey, final String expectedKV) { - testCSV(AbstractByteBufferProxy::compareBuff, - false, - createIntegerDBPopulator(), - EnumSet.of(MDB_CREATE, MDB_INTEGERKEY), - keyType, - startKey, - stopKey, - expectedKV, - Integer.BYTES, - ByteOrder.LITTLE_ENDIAN); + void testIntegerKey( + final String keyType, final String startKey, final String stopKey, final String expectedKV) { + testCSV( + AbstractByteBufferProxy::compareBuff, + false, + createIntegerDBPopulator(), + EnumSet.of(MDB_CREATE, MDB_INTEGERKEY), + keyType, + startKey, + stopKey, + expectedKV, + Integer.BYTES, + ByteOrder.LITTLE_ENDIAN); } @ParameterizedTest(name = "{index} => {0}: ({1}, {2})") @CsvFileSource(resources = "/CursorIterableRangeTest/testLongKey.csv") - void testLongKey(final String keyType, final String startKey, final String stopKey, final String expectedKV) { - testCSV(AbstractByteBufferProxy::compareBuff, - false, - createLongDBPopulator(), - EnumSet.of(MDB_CREATE, MDB_INTEGERKEY), - keyType, - startKey, - stopKey, - expectedKV, - Long.BYTES, - ByteOrder.LITTLE_ENDIAN); + void testLongKey( + final String keyType, final String startKey, final String stopKey, final String expectedKV) { + testCSV( + AbstractByteBufferProxy::compareBuff, + false, + createLongDBPopulator(), + EnumSet.of(MDB_CREATE, MDB_INTEGERKEY), + keyType, + startKey, + stopKey, + expectedKV, + Long.BYTES, + ByteOrder.LITTLE_ENDIAN); } - - private void testCSV(final Comparator comparator, - final boolean nativeCb, - final BiConsumer, Dbi> dbPopulator, - final EnumSet flags, - final String keyType, - final String startKey, - final String stopKey, - final String expectedKV) { - testCSV(comparator, nativeCb, dbPopulator, flags, keyType, startKey, stopKey, expectedKV, Integer.BYTES, ByteOrder.BIG_ENDIAN); + private void testCSV( + final Comparator comparator, + final boolean nativeCb, + final BiConsumer, Dbi> dbPopulator, + final EnumSet flags, + final String keyType, + final String startKey, + final String stopKey, + final String expectedKV) { + testCSV( + comparator, + nativeCb, + dbPopulator, + flags, + keyType, + startKey, + stopKey, + expectedKV, + Integer.BYTES, + ByteOrder.BIG_ENDIAN); } - private void testCSV(final Comparator comparator, - final boolean nativeCb, - final BiConsumer, Dbi> dbPopulator, - final EnumSet flags, - final String keyType, - final String startKey, - final String stopKey, - final String expectedKV, - final int keyLen, - final ByteOrder byteOrder) { - FileUtil.useTempFile(file -> { + private void testCSV( + final Comparator comparator, + final boolean nativeCb, + final BiConsumer, Dbi> dbPopulator, + final EnumSet flags, + final String keyType, + final String startKey, + final String stopKey, + final String expectedKV, + final int keyLen, + final ByteOrder byteOrder) { + try (final TempDir tempDir = new TempDir()) { + final Path file = tempDir.createTempFile(); try (final Env env = - create() - .setMapSize(KIBIBYTES.toBytes(256)) - .setMaxReaders(1) - .setMaxDbs(1) - .open(file.toFile(), POSIX_MODE, MDB_NOSUBDIR)) { - final Dbi dbi = env.openDbi(DB_1, comparator, nativeCb, flags.toArray(new DbiFlags[0])); + create() + .setMapSize(KIBIBYTES.toBytes(256)) + .setMaxReaders(1) + .setMaxDbs(1) + .open(file.toFile(), POSIX_MODE, MDB_NOSUBDIR)) { + final Dbi dbi = + env.openDbi(DB_1, comparator, nativeCb, flags.toArray(new DbiFlags[0])); dbPopulator.accept(env, dbi); - -// final File tests = new File("src/test/resources/CursorIterableRangeTest/tests.csv"); -// final File actual = tests.getParentFile().toPath().resolve(testName + ".actual").toFile(); -// final File expected = tests.getParentFile().toPath().resolve(testName + ".expected").toFile(); -// final String csv = readFile(tests); -// final String[] parts = csv.split("\n"); try (final Writer writer = new StringWriter()) { -// for (final String part : parts) { -// final String[] params = part.split(","); final KeyRangeType keyRangeType = KeyRangeType.valueOf(keyType.trim()); ByteBuffer start = parseKey(startKey, keyLen, byteOrder); ByteBuffer stop = parseKey(stopKey, keyLen, byteOrder); final KeyRange keyRange = new KeyRange<>(keyRangeType, start, stop); try (Txn txn = env.txnRead(); - CursorIterable c = dbi.iterate(txn, keyRange)) { + CursorIterable c = dbi.iterate(txn, keyRange)) { for (final KeyVal kv : c) { final long key = getLong(kv.key(), byteOrder); final long val = getLong(kv.val(), byteOrder); @@ -186,19 +199,11 @@ private void testCSV(final Comparator comparator, } catch (final IOException e) { throw new UncheckedIOException(e); } - - -// // Compare files. -// final String act = readFile(actual); -// final String exp = readFile(expected); -// assertThat(act).withFailMessage("Files are not equal").isEqualTo(exp); } - }); + } } - private ByteBuffer parseKey(final String key, - final int keyLen, - final ByteOrder byteOrder) { + private ByteBuffer parseKey(final String key, final int keyLen, final ByteOrder byteOrder) { if (key != null) { if (ByteOrder.LITTLE_ENDIAN.equals(byteOrder)) { if (keyLen == Integer.BYTES) { @@ -217,8 +222,7 @@ private ByteBuffer parseKey(final String key, return null; } - private long getLong(final ByteBuffer byteBuffer, - final ByteOrder byteOrder) { + private long getLong(final ByteBuffer byteBuffer, final ByteOrder byteOrder) { byteBuffer.order(byteOrder); if (byteBuffer.remaining() == Integer.BYTES) { return byteBuffer.getInt(); @@ -227,92 +231,95 @@ private long getLong(final ByteBuffer byteBuffer, } } -// -// @Test -// void testSignedComparator() throws IOException { -// test(ByteBuffer::compareTo, true, "testSignedComparator", 1, MDB_CREATE); -// } -// -// @Test -// void testUnsignedComparator() throws IOException { -// test(AbstractByteBufferProxy::compareBuff, false, "testUnsignedComparator", 1, MDB_CREATE); -// } -// -// @Test -// void testSignedComparatorDupsort() throws IOException { -// test(ByteBuffer::compareTo, true, "testSignedComparatorDupsort", 2, MDB_CREATE, MDB_DUPSORT); -// } -// -// @Test -// void testUnsignedComparatorDupsort() throws IOException { -// test(AbstractByteBufferProxy::compareBuff, false, "testUnsignedComparatorDupsort", 2, MDB_CREATE, MDB_DUPSORT); -// } - - private void test(final Comparator comparator, - final boolean nativeCb, - final String testName, - final BiConsumer, Dbi> dbPopulator, - final DbiFlags... flags) throws IOException { - final Path dbPath = Files.createTempFile("test", "db"); - try (final Env env = - create() - .setMapSize(KIBIBYTES.toBytes(256)) - .setMaxReaders(1) - .setMaxDbs(1) - .open(dbPath.toFile(), POSIX_MODE, MDB_NOSUBDIR)) { - final Dbi dbi = env.openDbi(DB_1, comparator, nativeCb, flags); - dbPopulator.accept(env, dbi); - - final File tests = new File("src/test/resources/CursorIterableRangeTest/tests.csv"); - final File actual = tests.getParentFile().toPath().resolve(testName + ".actual").toFile(); - final File expected = tests.getParentFile().toPath().resolve(testName + ".expected").toFile(); - final String csv = readFile(tests); - final String[] parts = csv.split("\n"); - try (final Writer writer = new FileWriter(actual)) { - for (final String part : parts) { - final String[] params = part.split(","); - final KeyRangeType keyRangeType = KeyRangeType.valueOf(params[0].trim()); - ByteBuffer start = null; - ByteBuffer stop = null; - if (params.length > 1 && params[1].trim().length() > 0) { - start = bb(Integer.parseInt(params[1].trim())); - } - if (params.length > 2 && params[2].trim().length() > 0) { - stop = bb(Integer.parseInt(params[2].trim())); - } - - for (int i = 0; i < 3; i++) { - if (params.length > i) { - writer.append(params[i].trim()); - } - writer.append(","); - } - - final KeyRange keyRange = new KeyRange<>(keyRangeType, start, stop); - try (Txn txn = env.txnRead(); - CursorIterable c = dbi.iterate(txn, keyRange)) { - for (final KeyVal kv : c) { - final int key = kv.key().getInt(); - final int val = kv.val().getInt(); - writer.append("["); - writer.append(String.valueOf(key)); - writer.append(" "); - writer.append(String.valueOf(val)); - writer.append("]"); - } - } - writer.append("\n"); - } - } - - // Compare files. - final String act = readFile(actual); - final String exp = readFile(expected); - assertThat(act).withFailMessage("Files are not equal").isEqualTo(exp); - } finally { - FileUtil.deleteFile(dbPath); - } - } + // + // @Test + // void testSignedComparator() throws IOException { + // test(ByteBuffer::compareTo, true, "testSignedComparator", 1, MDB_CREATE); + // } + // + // @Test + // void testUnsignedComparator() throws IOException { + // test(AbstractByteBufferProxy::compareBuff, false, "testUnsignedComparator", 1, MDB_CREATE); + // } + // + // @Test + // void testSignedComparatorDupsort() throws IOException { + // test(ByteBuffer::compareTo, true, "testSignedComparatorDupsort", 2, MDB_CREATE, + // MDB_DUPSORT); + // } + // + // @Test + // void testUnsignedComparatorDupsort() throws IOException { + // test(AbstractByteBufferProxy::compareBuff, false, "testUnsignedComparatorDupsort", 2, + // MDB_CREATE, MDB_DUPSORT); + // } + // + // private void test(final Comparator comparator, + // final boolean nativeCb, + // final String testName, + // final BiConsumer, Dbi> dbPopulator, + // final DbiFlags... flags) throws IOException { + // final Path dbPath = Files.createTempFile("test", "db"); + // try (final Env env = + // create() + // .setMapSize(KIBIBYTES.toBytes(256)) + // .setMaxReaders(1) + // .setMaxDbs(1) + // .open(dbPath.toFile(), POSIX_MODE, MDB_NOSUBDIR)) { + // final Dbi dbi = env.openDbi(DB_1, comparator, nativeCb, flags); + // dbPopulator.accept(env, dbi); + // + // final File tests = new File("src/test/resources/CursorIterableRangeTest/tests.csv"); + // final File actual = tests.getParentFile().toPath().resolve(testName + ".actual").toFile(); + // final File expected = tests.getParentFile().toPath().resolve(testName + + // ".expected").toFile(); + // final String csv = readFile(tests); + // final String[] parts = csv.split("\n"); + // try (final Writer writer = new FileWriter(actual)) { + // for (final String part : parts) { + // final String[] params = part.split(","); + // final KeyRangeType keyRangeType = KeyRangeType.valueOf(params[0].trim()); + // ByteBuffer start = null; + // ByteBuffer stop = null; + // if (params.length > 1 && params[1].trim().length() > 0) { + // start = bb(Integer.parseInt(params[1].trim())); + // } + // if (params.length > 2 && params[2].trim().length() > 0) { + // stop = bb(Integer.parseInt(params[2].trim())); + // } + // + // for (int i = 0; i < 3; i++) { + // if (params.length > i) { + // writer.append(params[i].trim()); + // } + // writer.append(","); + // } + // + // final KeyRange keyRange = new KeyRange<>(keyRangeType, start, stop); + // try (Txn txn = env.txnRead(); + // CursorIterable c = dbi.iterate(txn, keyRange)) { + // for (final KeyVal kv : c) { + // final int key = kv.key().getInt(); + // final int val = kv.val().getInt(); + // writer.append("["); + // writer.append(String.valueOf(key)); + // writer.append(" "); + // writer.append(String.valueOf(val)); + // writer.append("]"); + // } + // } + // writer.append("\n"); + // } + // } + // + // // Compare files. + // final String act = readFile(actual); + // final String exp = readFile(expected); + // assertThat(act).withFailMessage("Files are not equal").isEqualTo(exp); + // } finally { + // FileUtil.deleteFile(dbPath); + // } + // } private BiConsumer, Dbi> createBasicDBPopulator() { return (env, dbi) -> { @@ -367,16 +374,15 @@ private BiConsumer, Dbi> createLongDBPopulator() { c.put(bbLeLong(Long.MIN_VALUE), bb(1)); c.put(bbLeLong(-1000), bb(2)); c.put(bbLeLong(0), bb(3)); - c.put(bbLeLong( 1000), bb(4)); + c.put(bbLeLong(1000), bb(4)); c.put(bbLeLong(Long.MAX_VALUE), bb(5)); txn.commit(); } }; } - private void populateDatabase(final Env env, - final Dbi dbi, - final int copies) { + private void populateDatabase( + final Env env, final Dbi dbi, final int copies) { try (Txn txn = env.txnWrite()) { final Cursor c = dbi.openCursor(txn); for (int i = 0; i < copies; i++) { diff --git a/src/test/java/org/lmdbjava/CursorIterableTest.java b/src/test/java/org/lmdbjava/CursorIterableTest.java index 9cceb437..5e515fee 100644 --- a/src/test/java/org/lmdbjava/CursorIterableTest.java +++ b/src/test/java/org/lmdbjava/CursorIterableTest.java @@ -48,7 +48,6 @@ import com.google.common.primitives.UnsignedBytes; import java.nio.ByteBuffer; -import java.nio.file.Path; import java.util.ArrayList; import java.util.Comparator; import java.util.Deque; @@ -64,20 +63,20 @@ /** Test {@link CursorIterable}. */ public final class CursorIterableTest { - private Path file; + private TempDir tempDir; private Dbi db; private Env env; private Deque list; @BeforeEach void beforeEach() { - file = FileUtil.createTempFile(); + tempDir = new TempDir(); env = create() .setMapSize(KIBIBYTES.toBytes(256)) .setMaxReaders(1) .setMaxDbs(1) - .open(file.toFile(), POSIX_MODE, MDB_NOSUBDIR); + .open(tempDir.createTempFile().toFile(), POSIX_MODE, MDB_NOSUBDIR); db = env.openDbi(DB_1, MDB_CREATE); populateDatabase(db); } @@ -98,7 +97,7 @@ private void populateDatabase(final Dbi dbi) { @AfterEach void afterEach() { env.close(); - FileUtil.deleteFile(file); + tempDir.cleanup(); } @Test diff --git a/src/test/java/org/lmdbjava/CursorParamTest.java b/src/test/java/org/lmdbjava/CursorParamTest.java index 1a3604be..7f0bad8a 100644 --- a/src/test/java/org/lmdbjava/CursorParamTest.java +++ b/src/test/java/org/lmdbjava/CursorParamTest.java @@ -58,7 +58,8 @@ public final class CursorParamTest { static Stream data() { return Stream.of( - Arguments.argumentSet("ByteBufferRunner(PROXY_OPTIMAL)", new ByteBufferRunner(PROXY_OPTIMAL)), + Arguments.argumentSet( + "ByteBufferRunner(PROXY_OPTIMAL)", new ByteBufferRunner(PROXY_OPTIMAL)), Arguments.argumentSet("ByteBufferRunner(PROXY_SAFE)", new ByteBufferRunner(PROXY_SAFE)), Arguments.argumentSet("ByteArrayRunner(PROXY_BA)", new ByteArrayRunner(PROXY_BA)), Arguments.argumentSet("DirectBufferRunner", new DirectBufferRunner()), diff --git a/src/test/java/org/lmdbjava/CursorTest.java b/src/test/java/org/lmdbjava/CursorTest.java index f44a9a44..530b2e80 100644 --- a/src/test/java/org/lmdbjava/CursorTest.java +++ b/src/test/java/org/lmdbjava/CursorTest.java @@ -54,12 +54,13 @@ /** Test {@link Cursor}. */ public final class CursorTest { - private Path file; private Env env; + private TempDir tempDir; @BeforeEach void beforeEach() { - file = FileUtil.createTempFile(); + tempDir = new TempDir(); + Path file = tempDir.createTempFile(); env = create(PROXY_OPTIMAL) .setMapSize(MEBIBYTES.toBytes(1)) @@ -71,7 +72,7 @@ void beforeEach() { @AfterEach void afterEach() { env.close(); - FileUtil.deleteFile(file); + tempDir.cleanup(); } @Test diff --git a/src/test/java/org/lmdbjava/DbiTest.java b/src/test/java/org/lmdbjava/DbiTest.java index cb106550..2c4a90b7 100644 --- a/src/test/java/org/lmdbjava/DbiTest.java +++ b/src/test/java/org/lmdbjava/DbiTest.java @@ -62,21 +62,21 @@ /** Test {@link Dbi}. */ public final class DbiTest { - private Path file; + private TempDir tempDir; private Env env; - private Path fileBa; private Env envBa; @BeforeEach void beforeEach() { - file = FileUtil.createTempFile(); + tempDir = new TempDir(); + final Path file = tempDir.createTempFile(); env = create() .setMapSize(MEBIBYTES.toBytes(64)) .setMaxReaders(2) .setMaxDbs(2) .open(file.toFile(), MDB_NOSUBDIR); - fileBa = FileUtil.createTempFile(); + final Path fileBa = tempDir.createTempFile(); envBa = create(PROXY_BA) .setMapSize(MEBIBYTES.toBytes(64)) @@ -89,8 +89,7 @@ void beforeEach() { void afterEach() { env.close(); envBa.close(); - FileUtil.deleteFile(file); - FileUtil.deleteFile(fileBa); + tempDir.cleanup(); } @Test @@ -362,26 +361,24 @@ void putCommitGet() { @Test void putCommitGetByteArray() { - FileUtil.useTempFile( - file -> { - try (Env envBa = - create(PROXY_BA) - .setMapSize(MEBIBYTES.toBytes(64)) - .setMaxReaders(1) - .setMaxDbs(2) - .open(file.toFile(), MDB_NOSUBDIR)) { - final Dbi db = envBa.openDbi(DB_1, MDB_CREATE); - try (Txn txn = envBa.txnWrite()) { - db.put(txn, ba(5), ba(5)); - txn.commit(); - } - try (Txn txn = envBa.txnWrite()) { - final byte[] found = db.get(txn, ba(5)); - assertThat(found).isNotNull(); - assertThat(fromBa(txn.val())).isEqualTo(5); - } - } - }); + final Path file = tempDir.createTempFile(); + try (Env envBa = + create(PROXY_BA) + .setMapSize(MEBIBYTES.toBytes(64)) + .setMaxReaders(1) + .setMaxDbs(2) + .open(file.toFile(), MDB_NOSUBDIR)) { + final Dbi db = envBa.openDbi(DB_1, MDB_CREATE); + try (Txn txn = envBa.txnWrite()) { + db.put(txn, ba(5), ba(5)); + txn.commit(); + } + try (Txn txn = envBa.txnWrite()) { + final byte[] found = db.get(txn, ba(5)); + assertThat(found).isNotNull(); + assertThat(fromBa(txn.val())).isEqualTo(5); + } + } } @Test diff --git a/src/test/java/org/lmdbjava/EnvTest.java b/src/test/java/org/lmdbjava/EnvTest.java index 904159bb..d66aa3a3 100644 --- a/src/test/java/org/lmdbjava/EnvTest.java +++ b/src/test/java/org/lmdbjava/EnvTest.java @@ -37,43 +37,49 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Random; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.lmdbjava.Env.AlreadyClosedException; -import org.lmdbjava.Env.AlreadyOpenException; -import org.lmdbjava.Env.Builder; -import org.lmdbjava.Env.InvalidCopyDestination; -import org.lmdbjava.Env.MapFullException; +import org.lmdbjava.Env.*; import org.lmdbjava.Txn.BadReaderLockException; /** Test {@link Env}. */ public final class EnvTest { + private TempDir tempDir; + + @BeforeEach + void beforeEach() { + tempDir = new TempDir(); + } + + @AfterEach + void afterEach() { + tempDir.cleanup(); + } + @Test void byteUnit() { - FileUtil.useTempFile( - file -> { - try (Env env = - create() - .setMaxReaders(1) - .setMapSize(MEBIBYTES.toBytes(1)) - .open(file.toFile(), MDB_NOSUBDIR)) { - final EnvInfo info = env.info(); - assertThat(info.mapSize).isEqualTo(MEBIBYTES.toBytes(1)); - } - }); + final Path file = tempDir.createTempFile(); + try (Env env = + create() + .setMaxReaders(1) + .setMapSize(MEBIBYTES.toBytes(1)) + .open(file.toFile(), MDB_NOSUBDIR)) { + final EnvInfo info = env.info(); + assertThat(info.mapSize).isEqualTo(MEBIBYTES.toBytes(1)); + } } @Test void cannotChangeMapSizeAfterOpen() { assertThatThrownBy( () -> { - FileUtil.useTempFile( - file -> { - final Builder builder = create().setMaxReaders(1); - try (Env env = builder.open(file.toFile(), MDB_NOSUBDIR)) { - builder.setMapSize(1); - } - }); + final Path file = tempDir.createTempFile(); + final Builder builder = create().setMaxReaders(1); + try (Env env = builder.open(file.toFile(), MDB_NOSUBDIR)) { + builder.setMapSize(1); + } }) .isInstanceOf(AlreadyOpenException.class); } @@ -82,13 +88,11 @@ void cannotChangeMapSizeAfterOpen() { void cannotChangeMaxDbsAfterOpen() { assertThatThrownBy( () -> { - FileUtil.useTempFile( - file -> { - final Builder builder = create().setMaxReaders(1); - try (Env env = builder.open(file.toFile(), MDB_NOSUBDIR)) { - builder.setMaxDbs(1); - } - }); + final Path file = tempDir.createTempFile(); + final Builder builder = create().setMaxReaders(1); + try (Env env = builder.open(file.toFile(), MDB_NOSUBDIR)) { + builder.setMaxDbs(1); + } }) .isInstanceOf(AlreadyOpenException.class); } @@ -97,13 +101,11 @@ void cannotChangeMaxDbsAfterOpen() { void cannotChangeMaxReadersAfterOpen() { assertThatThrownBy( () -> { - FileUtil.useTempFile( - file -> { - final Builder builder = create().setMaxReaders(1); - try (Env env = builder.open(file.toFile(), MDB_NOSUBDIR)) { - builder.setMaxReaders(1); - } - }); + final Path file = tempDir.createTempFile(); + final Builder builder = create().setMaxReaders(1); + try (Env env = builder.open(file.toFile(), MDB_NOSUBDIR)) { + builder.setMaxReaders(1); + } }) .isInstanceOf(AlreadyOpenException.class); } @@ -112,13 +114,11 @@ void cannotChangeMaxReadersAfterOpen() { void cannotInfoOnceClosed() { assertThatThrownBy( () -> { - FileUtil.useTempFile( - file -> { - final Env env = - create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR); - env.close(); - env.info(); - }); + final Path file = tempDir.createTempFile(); + final Env env = + create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR); + env.close(); + env.info(); }) .isInstanceOf(AlreadyClosedException.class); } @@ -127,12 +127,10 @@ void cannotInfoOnceClosed() { void cannotOpenTwice() { assertThatThrownBy( () -> { - FileUtil.useTempFile( - file -> { - final Builder builder = create().setMaxReaders(1); - builder.open(file.toFile(), MDB_NOSUBDIR).close(); - builder.open(file.toFile(), MDB_NOSUBDIR); - }); + final Path file = tempDir.createTempFile(); + final Builder builder = create().setMaxReaders(1); + builder.open(file.toFile(), MDB_NOSUBDIR).close(); + builder.open(file.toFile(), MDB_NOSUBDIR); }) .isInstanceOf(AlreadyOpenException.class); } @@ -153,13 +151,11 @@ void cannotOverflowMapSize() { void cannotStatOnceClosed() { assertThatThrownBy( () -> { - FileUtil.useTempFile( - file -> { - final Env env = - create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR); - env.close(); - env.stat(); - }); + final Path file = tempDir.createTempFile(); + final Env env = + create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR); + env.close(); + env.stat(); }) .isInstanceOf(AlreadyClosedException.class); } @@ -168,48 +164,38 @@ void cannotStatOnceClosed() { void cannotSyncOnceClosed() { assertThatThrownBy( () -> { - FileUtil.useTempFile( - file -> { - final Env env = - create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR); - env.close(); - env.sync(false); - }); + final Path file = tempDir.createTempFile(); + final Env env = + create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR); + env.close(); + env.sync(false); }) .isInstanceOf(AlreadyClosedException.class); } @Test void copyDirectoryBased() { - FileUtil.useTempDir( - dest -> { - assertThat(Files.exists(dest)).isTrue(); - assertThat(Files.isDirectory(dest)).isTrue(); - assertThat(FileUtil.count(dest)).isEqualTo(0); - FileUtil.useTempDir( - src -> { - try (Env env = create().setMaxReaders(1).open(src.toFile())) { - env.copy(dest.toFile(), MDB_CP_COMPACT); - assertThat(FileUtil.count(dest)).isEqualTo(1); - } - }); - }); + final Path dest = tempDir.createTempDir(); + assertThat(Files.exists(dest)).isTrue(); + assertThat(Files.isDirectory(dest)).isTrue(); + assertThat(FileUtil.count(dest)).isEqualTo(0); + final Path src = tempDir.createTempDir(); + try (Env env = create().setMaxReaders(1).open(src.toFile())) { + env.copy(dest.toFile(), MDB_CP_COMPACT); + assertThat(FileUtil.count(dest)).isEqualTo(1); + } } @Test void copyDirectoryRejectsFileDestination() { assertThatThrownBy( () -> { - FileUtil.useTempDir( - dest -> { - FileUtil.deleteDir(dest); - FileUtil.useTempDir( - src -> { - try (Env env = create().setMaxReaders(1).open(src.toFile())) { - env.copy(dest.toFile(), MDB_CP_COMPACT); - } - }); - }); + final Path dest = tempDir.createTempDir(); + FileUtil.deleteDir(dest); + final Path src = tempDir.createTempDir(); + try (Env env = create().setMaxReaders(1).open(src.toFile())) { + env.copy(dest.toFile(), MDB_CP_COMPACT); + } }) .isInstanceOf(InvalidCopyDestination.class); } @@ -218,21 +204,16 @@ void copyDirectoryRejectsFileDestination() { void copyDirectoryRejectsMissingDestination() { assertThatThrownBy( () -> { - FileUtil.useTempDir( - dest -> { - try { - Files.delete(dest); - FileUtil.useTempDir( - src -> { - try (Env env = - create().setMaxReaders(1).open(src.toFile())) { - env.copy(dest.toFile(), MDB_CP_COMPACT); - } - }); - } catch (final IOException e) { - throw new UncheckedIOException(e); - } - }); + final Path dest = tempDir.createTempDir(); + try { + Files.delete(dest); + final Path src = tempDir.createTempDir(); + try (Env env = create().setMaxReaders(1).open(src.toFile())) { + env.copy(dest.toFile(), MDB_CP_COMPACT); + } + } catch (final IOException e) { + throw new UncheckedIOException(e); + } }) .isInstanceOf(InvalidCopyDestination.class); } @@ -241,260 +222,227 @@ void copyDirectoryRejectsMissingDestination() { void copyDirectoryRejectsNonEmptyDestination() { assertThatThrownBy( () -> { - FileUtil.useTempDir( - dest -> { - try { - final Path subDir = dest.resolve("hello"); - Files.createDirectory(subDir); - assertThat(Files.isDirectory(subDir)).isTrue(); - FileUtil.useTempDir( - src -> { - try (Env env = - create().setMaxReaders(1).open(src.toFile())) { - env.copy(dest.toFile(), MDB_CP_COMPACT); - } - }); - } catch (final IOException e) { - throw new UncheckedIOException(e); - } - }); + final Path dest = tempDir.createTempDir(); + try { + final Path subDir = dest.resolve("hello"); + Files.createDirectory(subDir); + assertThat(Files.isDirectory(subDir)).isTrue(); + final Path src = tempDir.createTempDir(); + try (Env env = create().setMaxReaders(1).open(src.toFile())) { + env.copy(dest.toFile(), MDB_CP_COMPACT); + } + } catch (final IOException e) { + throw new UncheckedIOException(e); + } }) .isInstanceOf(InvalidCopyDestination.class); } @Test void copyFileBased() { - FileUtil.useTempFile( - dest -> { - FileUtil.deleteFile(dest); - assertThat(Files.exists(dest)).isFalse(); - FileUtil.useTempFile( - src -> { - try (Env env = - create().setMaxReaders(1).open(src.toFile(), MDB_NOSUBDIR)) { - env.copy(dest.toFile(), MDB_CP_COMPACT); - } - assertThat(FileUtil.size(dest)).isGreaterThan(0L); - }); - }); + final Path dest = tempDir.createTempFile(); + assertThat(Files.exists(dest)).isFalse(); + final Path src = tempDir.createTempFile(); + try (Env env = create().setMaxReaders(1).open(src.toFile(), MDB_NOSUBDIR)) { + env.copy(dest.toFile(), MDB_CP_COMPACT); + } + assertThat(FileUtil.size(dest)).isGreaterThan(0L); } @Test void copyFileRejectsExistingDestination() { assertThatThrownBy( () -> { - FileUtil.useTempFile( - dest -> { - assertThat(Files.exists(dest)).isTrue(); - FileUtil.useTempFile( - src -> { - try (Env env = - create().setMaxReaders(1).open(src.toFile(), MDB_NOSUBDIR)) { - env.copy(dest.toFile(), MDB_CP_COMPACT); - } - }); - }); + final Path dest = tempDir.createTempFile(); + Files.createFile(dest); + assertThat(Files.exists(dest)).isTrue(); + final Path src = tempDir.createTempFile(); + try (Env env = + create().setMaxReaders(1).open(src.toFile(), MDB_NOSUBDIR)) { + env.copy(dest.toFile(), MDB_CP_COMPACT); + } }) .isInstanceOf(InvalidCopyDestination.class); } @Test void createAsDirectory() { - FileUtil.useTempDir( - dest -> { - final Env env = create().setMaxReaders(1).open(dest.toFile()); - assertThat(Files.isDirectory(dest)).isTrue(); - env.sync(false); - env.close(); - assertThat(env.isClosed()).isTrue(); - env.close(); // safe to repeat - }); + final Path dest = tempDir.createTempDir(); + final Env env = create().setMaxReaders(1).open(dest.toFile()); + assertThat(Files.isDirectory(dest)).isTrue(); + env.sync(false); + env.close(); + assertThat(env.isClosed()).isTrue(); + env.close(); // safe to repeat } @Test void createAsFile() { - FileUtil.useTempFile( - file -> { - try (Env env = - create() - .setMapSize(MEBIBYTES.toBytes(1)) - .setMaxDbs(1) - .setMaxReaders(1) - .open(file.toFile(), MDB_NOSUBDIR)) { - env.sync(true); - assertThat(Files.isRegularFile(file)).isTrue(); - } - }); + final Path file = tempDir.createTempFile(); + try (Env env = + create() + .setMapSize(MEBIBYTES.toBytes(1)) + .setMaxDbs(1) + .setMaxReaders(1) + .open(file.toFile(), MDB_NOSUBDIR)) { + env.sync(true); + assertThat(Files.isRegularFile(file)).isTrue(); + } } @Test void detectTransactionThreadViolation() { assertThatThrownBy( () -> { - FileUtil.useTempFile( - file -> { - try (Env env = - create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR)) { - env.txnRead(); - env.txnRead(); - } - }); + final Path file = tempDir.createTempFile(); + try (Env env = + create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR)) { + env.txnRead(); + env.txnRead(); + } }) .isInstanceOf(BadReaderLockException.class); } @Test void info() { - FileUtil.useTempFile( - file -> { - try (Env env = - create().setMaxReaders(4).setMapSize(123_456).open(file.toFile(), MDB_NOSUBDIR)) { - final EnvInfo info = env.info(); - assertThat(info).isNotNull(); - assertThat(info.lastPageNumber).isEqualTo(1L); - assertThat(info.lastTransactionId).isEqualTo(0L); - assertThat(info.mapAddress).isEqualTo(0L); - assertThat(info.mapSize).isEqualTo(123_456L); - assertThat(info.maxReaders).isEqualTo(4); - assertThat(info.numReaders).isEqualTo(0); - assertThat(info.toString()).contains("maxReaders="); - assertThat(env.getMaxKeySize()).isEqualTo(511); - } - }); + final Path file = tempDir.createTempFile(); + try (Env env = + create().setMaxReaders(4).setMapSize(123_456).open(file.toFile(), MDB_NOSUBDIR)) { + final EnvInfo info = env.info(); + assertThat(info).isNotNull(); + assertThat(info.lastPageNumber).isEqualTo(1L); + assertThat(info.lastTransactionId).isEqualTo(0L); + assertThat(info.mapAddress).isEqualTo(0L); + assertThat(info.mapSize).isEqualTo(123_456L); + assertThat(info.maxReaders).isEqualTo(4); + assertThat(info.numReaders).isEqualTo(0); + assertThat(info.toString()).contains("maxReaders="); + assertThat(env.getMaxKeySize()).isEqualTo(511); + } } @Test void mapFull() { assertThatThrownBy( () -> { - FileUtil.useTempDir( - dir -> { - final byte[] k = new byte[500]; - final ByteBuffer key = allocateDirect(500); - final ByteBuffer val = allocateDirect(1_024); - final Random rnd = new Random(); - try (Env env = - create() - .setMaxReaders(1) - .setMapSize(MEBIBYTES.toBytes(8)) - .setMaxDbs(1) - .open(dir.toFile())) { - final Dbi db = env.openDbi(DB_1, MDB_CREATE); - for (; ; ) { - rnd.nextBytes(k); - key.clear(); - key.put(k).flip(); - val.clear(); - db.put(key, val); - } - } - }); + final Path dir = tempDir.createTempDir(); + final byte[] k = new byte[500]; + final ByteBuffer key = allocateDirect(500); + final ByteBuffer val = allocateDirect(1_024); + final Random rnd = new Random(); + try (Env env = + create() + .setMaxReaders(1) + .setMapSize(MEBIBYTES.toBytes(8)) + .setMaxDbs(1) + .open(dir.toFile())) { + final Dbi db = env.openDbi(DB_1, MDB_CREATE); + for (; ; ) { + rnd.nextBytes(k); + key.clear(); + key.put(k).flip(); + val.clear(); + db.put(key, val); + } + } }) .isInstanceOf(MapFullException.class); } @Test void readOnlySupported() { - FileUtil.useTempDir( - dir -> { - try (Env rwEnv = create().setMaxReaders(1).open(dir.toFile())) { - final Dbi rwDb = rwEnv.openDbi(DB_1, MDB_CREATE); - rwDb.put(bb(1), bb(42)); - } - try (Env roEnv = - create().setMaxReaders(1).open(dir.toFile(), MDB_RDONLY_ENV)) { - final Dbi roDb = roEnv.openDbi(DB_1); - try (Txn roTxn = roEnv.txnRead()) { - assertThat(roDb.get(roTxn, bb(1))).isNotNull(); - } - } - }); + final Path dir = tempDir.createTempDir(); + try (Env rwEnv = create().setMaxReaders(1).open(dir.toFile())) { + final Dbi rwDb = rwEnv.openDbi(DB_1, MDB_CREATE); + rwDb.put(bb(1), bb(42)); + } + try (Env roEnv = create().setMaxReaders(1).open(dir.toFile(), MDB_RDONLY_ENV)) { + final Dbi roDb = roEnv.openDbi(DB_1); + try (Txn roTxn = roEnv.txnRead()) { + assertThat(roDb.get(roTxn, bb(1))).isNotNull(); + } + } } @Test void setMapSize() { - FileUtil.useTempDir( - dir -> { - final byte[] k = new byte[500]; - final ByteBuffer key = allocateDirect(500); - final ByteBuffer val = allocateDirect(1_024); - final Random rnd = new Random(); - try (Env env = - create() - .setMaxReaders(1) - .setMapSize(KIBIBYTES.toBytes(256)) - .setMaxDbs(1) - .open(dir.toFile())) { - final Dbi db = env.openDbi(DB_1, MDB_CREATE); - - db.put(bb(1), bb(42)); - boolean mapFullExThrown = false; - try { - for (int i = 0; i < 70; i++) { - rnd.nextBytes(k); - key.clear(); - key.put(k).flip(); - val.clear(); - db.put(key, val); - } - } catch (final MapFullException mfE) { - mapFullExThrown = true; - } - assertThat(mapFullExThrown).isTrue(); - - env.setMapSize(KIBIBYTES.toBytes(1024)); - - try (Txn roTxn = env.txnRead()) { - final ByteBuffer byteBuffer = db.get(roTxn, bb(1)); - assertThat(byteBuffer).isNotNull(); - assertThat(byteBuffer.getInt()).isEqualTo(42); - } - - mapFullExThrown = false; - try { - for (int i = 0; i < 70; i++) { - rnd.nextBytes(k); - key.clear(); - key.put(k).flip(); - val.clear(); - db.put(key, val); - } - } catch (final MapFullException mfE) { - mapFullExThrown = true; - } - assertThat(mapFullExThrown).isFalse(); - } - }); + final Path dir = tempDir.createTempDir(); + final byte[] k = new byte[500]; + final ByteBuffer key = allocateDirect(500); + final ByteBuffer val = allocateDirect(1_024); + final Random rnd = new Random(); + try (Env env = + create() + .setMaxReaders(1) + .setMapSize(KIBIBYTES.toBytes(256)) + .setMaxDbs(1) + .open(dir.toFile())) { + final Dbi db = env.openDbi(DB_1, MDB_CREATE); + + db.put(bb(1), bb(42)); + boolean mapFullExThrown = false; + try { + for (int i = 0; i < 70; i++) { + rnd.nextBytes(k); + key.clear(); + key.put(k).flip(); + val.clear(); + db.put(key, val); + } + } catch (final MapFullException mfE) { + mapFullExThrown = true; + } + assertThat(mapFullExThrown).isTrue(); + + env.setMapSize(KIBIBYTES.toBytes(1024)); + + try (Txn roTxn = env.txnRead()) { + final ByteBuffer byteBuffer = db.get(roTxn, bb(1)); + assertThat(byteBuffer).isNotNull(); + assertThat(byteBuffer.getInt()).isEqualTo(42); + } + + mapFullExThrown = false; + try { + for (int i = 0; i < 70; i++) { + rnd.nextBytes(k); + key.clear(); + key.put(k).flip(); + val.clear(); + db.put(key, val); + } + } catch (final MapFullException mfE) { + mapFullExThrown = true; + } + assertThat(mapFullExThrown).isFalse(); + } } @Test void stats() { - FileUtil.useTempFile( - file -> { - try (Env env = create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR)) { - final Stat stat = env.stat(); - assertThat(stat).isNotNull(); - assertThat(stat.branchPages).isEqualTo(0L); - assertThat(stat.depth).isEqualTo(0); - assertThat(stat.entries).isEqualTo(0L); - assertThat(stat.leafPages).isEqualTo(0L); - assertThat(stat.overflowPages).isEqualTo(0L); - assertThat(stat.pageSize % 4_096).isEqualTo(0); - assertThat(stat.toString()).contains("pageSize="); - } - }); + final Path file = tempDir.createTempFile(); + try (Env env = create().setMaxReaders(1).open(file.toFile(), MDB_NOSUBDIR)) { + final Stat stat = env.stat(); + assertThat(stat).isNotNull(); + assertThat(stat.branchPages).isEqualTo(0L); + assertThat(stat.depth).isEqualTo(0); + assertThat(stat.entries).isEqualTo(0L); + assertThat(stat.leafPages).isEqualTo(0L); + assertThat(stat.overflowPages).isEqualTo(0L); + assertThat(stat.pageSize % 4_096).isEqualTo(0); + assertThat(stat.toString()).contains("pageSize="); + } } @Test void testDefaultOpen() { - FileUtil.useTempDir( - dir -> { - try (Env env = open(dir.toFile(), 10)) { - final EnvInfo info = env.info(); - assertThat(info.maxReaders).isEqualTo(MAX_READERS_DEFAULT); - final Dbi db = env.openDbi("test", MDB_CREATE); - db.put(allocateDirect(1), allocateDirect(1)); - } - }); + final Path dir = tempDir.createTempDir(); + try (Env env = open(dir.toFile(), 10)) { + final EnvInfo info = env.info(); + assertThat(info.maxReaders).isEqualTo(MAX_READERS_DEFAULT); + final Dbi db = env.openDbi("test", MDB_CREATE); + db.put(allocateDirect(1), allocateDirect(1)); + } } } diff --git a/src/test/java/org/lmdbjava/FileUtil.java b/src/test/java/org/lmdbjava/FileUtil.java index 2343c670..cc7ed635 100644 --- a/src/test/java/org/lmdbjava/FileUtil.java +++ b/src/test/java/org/lmdbjava/FileUtil.java @@ -25,53 +25,12 @@ import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.util.EnumSet; -import java.util.function.Consumer; import java.util.stream.Stream; final class FileUtil { private FileUtil() {} - static Path createTempDir() { - try { - return Files.createTempDirectory("lmdbjava"); - } catch (final IOException e) { - throw new UncheckedIOException(e); - } - } - - static Path createTempFile() { - try { - return Files.createTempFile("lmdbjava", "db"); - } catch (final IOException e) { - throw new UncheckedIOException(e); - } - } - - static void useTempDir(final Consumer consumer) { - Path path = null; - try { - path = createTempDir(); - consumer.accept(path); - } finally { - if (path != null) { - deleteDir(path); - } - } - } - - static void useTempFile(final Consumer consumer) { - Path path = null; - try { - path = createTempFile(); - consumer.accept(path); - } finally { - if (path != null) { - deleteIfExists(path); - } - } - } - public static long size(final Path path) { try { return Files.size(path); diff --git a/src/test/java/org/lmdbjava/GarbageCollectionTest.java b/src/test/java/org/lmdbjava/GarbageCollectionTest.java index f0aa64e4..36273559 100644 --- a/src/test/java/org/lmdbjava/GarbageCollectionTest.java +++ b/src/test/java/org/lmdbjava/GarbageCollectionTest.java @@ -23,6 +23,7 @@ import static org.lmdbjava.Env.create; import java.nio.ByteBuffer; +import java.nio.file.Path; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; @@ -35,61 +36,61 @@ public class GarbageCollectionTest { @Test void buffersNotGarbageCollectedTest() { - FileUtil.useTempDir( - dir -> { - try (Env env = - create().setMapSize(2_085_760_999).setMaxDbs(1).open(dir.toFile())) { - final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); + try (final TempDir tempDir = new TempDir()) { + final Path dir = tempDir.createTempDir(); + try (Env env = + create().setMapSize(2_085_760_999).setMaxDbs(1).open(dir.toFile())) { + final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); - try (Txn txn = env.txnWrite()) { - for (int i = 0; i < 5_000; i++) { - putBuffer(db, txn, i); - } - txn.commit(); - } + try (Txn txn = env.txnWrite()) { + for (int i = 0; i < 5_000; i++) { + putBuffer(db, txn, i); + } + txn.commit(); + } - // Call GC before writing to LMDB and after last reference to buffer by - // changing the behavior of mask - try (MockedStatic mockedStatic = Mockito.mockStatic(MaskedFlag.class)) { - mockedStatic - .when(MaskedFlag::mask) - .thenAnswer( - invocationOnMock -> { - System.gc(); - return 0; - }); - final int gcRecordWrites = Integer.getInteger("gcRecordWrites", 50); - try (Txn txn = env.txnWrite()) { - for (int i = 0; i < gcRecordWrites; i++) { - putBuffer(db, txn, i); - } - txn.commit(); - } + // Call GC before writing to LMDB and after last reference to buffer by + // changing the behavior of mask + try (MockedStatic mockedStatic = Mockito.mockStatic(MaskedFlag.class)) { + mockedStatic + .when(MaskedFlag::mask) + .thenAnswer( + invocationOnMock -> { + System.gc(); + return 0; + }); + final int gcRecordWrites = Integer.getInteger("gcRecordWrites", 50); + try (Txn txn = env.txnWrite()) { + for (int i = 0; i < gcRecordWrites; i++) { + putBuffer(db, txn, i); } + txn.commit(); + } + } - // Find corrupt keys - try (Txn txn = env.txnRead()) { - try (Cursor c = db.openCursor(txn)) { - if (c.first()) { - do { - final byte[] rkey = new byte[c.key().remaining()]; - c.key().get(rkey); - final byte[] rval = new byte[c.val().remaining()]; - c.val().get(rval); - final String skey = new String(rkey, UTF_8); - final String sval = new String(rval, UTF_8); - if (!skey.startsWith("Uncorruptedkey")) { - fail("Found corrupt key " + skey); - } - if (!sval.startsWith("Uncorruptedval")) { - fail("Found corrupt val " + sval); - } - } while (c.next()); + // Find corrupt keys + try (Txn txn = env.txnRead()) { + try (Cursor c = db.openCursor(txn)) { + if (c.first()) { + do { + final byte[] rkey = new byte[c.key().remaining()]; + c.key().get(rkey); + final byte[] rval = new byte[c.val().remaining()]; + c.val().get(rval); + final String skey = new String(rkey, UTF_8); + final String sval = new String(rval, UTF_8); + if (!skey.startsWith("Uncorruptedkey")) { + fail("Found corrupt key " + skey); + } + if (!sval.startsWith("Uncorruptedval")) { + fail("Found corrupt val " + sval); } - } + } while (c.next()); } } - }); + } + } + } } private void putBuffer(final Dbi db, final Txn txn, final int i) { diff --git a/src/test/java/org/lmdbjava/TempDir.java b/src/test/java/org/lmdbjava/TempDir.java new file mode 100644 index 00000000..7d2977b9 --- /dev/null +++ b/src/test/java/org/lmdbjava/TempDir.java @@ -0,0 +1,57 @@ +/* + * Copyright © 2016-2025 The LmdbJava Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.lmdbjava; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.UUID; + +public class TempDir implements AutoCloseable { + private final Path root; + + public TempDir() { + try { + root = Files.createTempDirectory("lmdb"); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } + + public Path createTempFile() { + return root.resolve(UUID.randomUUID().toString()); + } + + public Path createTempDir() { + try { + final Path dir = root.resolve(UUID.randomUUID().toString()); + Files.createDirectory(dir); + return dir; + } catch (final IOException e) { + throw new UncheckedIOException(e); + } + } + + public void cleanup() { + FileUtil.deleteDir(root); + } + + @Override + public void close() { + cleanup(); + } +} diff --git a/src/test/java/org/lmdbjava/TestUtils.java b/src/test/java/org/lmdbjava/TestUtils.java index 4c5e46ee..23606862 100644 --- a/src/test/java/org/lmdbjava/TestUtils.java +++ b/src/test/java/org/lmdbjava/TestUtils.java @@ -56,6 +56,15 @@ static ByteBuffer bb(final long value) { return bb; } + static byte[] getBytes(final ByteBuffer byteBuffer) { + if (byteBuffer == null) { + return null; + } + final byte[] bytes = new byte[byteBuffer.remaining()]; + byteBuffer.duplicate().get(bytes); + return bytes; + } + static void invokePrivateConstructor(final Class clazz) { try { final Constructor c = clazz.getDeclaredConstructor(); diff --git a/src/test/java/org/lmdbjava/TutorialTest.java b/src/test/java/org/lmdbjava/TutorialTest.java index 1b42b327..8376fbe7 100644 --- a/src/test/java/org/lmdbjava/TutorialTest.java +++ b/src/test/java/org/lmdbjava/TutorialTest.java @@ -27,9 +27,7 @@ import static org.lmdbjava.DirectBufferProxy.PROXY_DB; import static org.lmdbjava.Env.create; import static org.lmdbjava.GetOp.MDB_SET; -import static org.lmdbjava.SeekOp.MDB_FIRST; -import static org.lmdbjava.SeekOp.MDB_LAST; -import static org.lmdbjava.SeekOp.MDB_PREV; +import static org.lmdbjava.SeekOp.*; import java.nio.ByteBuffer; import java.nio.file.Path; @@ -37,6 +35,8 @@ import org.agrona.DirectBuffer; import org.agrona.MutableDirectBuffer; import org.agrona.concurrent.UnsafeBuffer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.lmdbjava.CursorIterable.KeyVal; @@ -57,147 +57,155 @@ public final class TutorialTest { private static final String DB_NAME = "my DB"; + private TempDir tempDir; + + @BeforeEach + void beforeEach() { + tempDir = new TempDir(); + } + + @AfterEach + void afterEach() { + tempDir.cleanup(); + } + /** In this first tutorial we will use LmdbJava with some basic defaults. */ @Test void tutorial1() { // We need a storage directory first. // The path cannot be on a remote file system. - FileUtil.useTempDir( - dir -> { - - // We always need an Env. An Env owns a physical on-disk storage file. One - // Env can store many different databases (ie sorted maps). - final Env env = - create() - // LMDB also needs to know how large our DB might be. Over-estimating is OK. - .setMapSize(10_485_760) - // LMDB also needs to know how many DBs (Dbi) we want to store in this Env. - .setMaxDbs(1) - // Now let's open the Env. The same path can be concurrently opened and - // used in different processes, but do not open the same path twice in - // the same process at the same time. - .open(dir.toFile()); - - // We need a Dbi for each DB. A Dbi roughly equates to a sorted map. The - // MDB_CREATE flag causes the DB to be created if it doesn't already exist. - final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); - - // We want to store some data, so we will need a direct ByteBuffer. - // Note that LMDB keys cannot exceed maxKeySize bytes (511 bytes by default). - // Values can be larger. - final ByteBuffer key = allocateDirect(env.getMaxKeySize()); - final ByteBuffer val = allocateDirect(700); - key.put("greeting".getBytes(UTF_8)).flip(); - val.put("Hello world".getBytes(UTF_8)).flip(); - final int valSize = val.remaining(); - - // Now store it. Dbi.put() internally begins and commits a transaction (Txn). - db.put(key, val); - - // To fetch any data from LMDB we need a Txn. A Txn is very important in - // LmdbJava because it offers ACID characteristics and internally holds a - // read-only key buffer and read-only value buffer. These read-only buffers - // are always the same two Java objects, but point to different LMDB-managed - // memory as we use Dbi (and Cursor) methods. These read-only buffers remain - // valid only until the Txn is released or the next Dbi or Cursor call. If - // you need data afterwards, you should copy the bytes to your own buffer. - try (Txn txn = env.txnRead()) { - final ByteBuffer found = db.get(txn, key); - assertThat(found).isNotNull(); - - // The fetchedVal is read-only and points to LMDB memory - final ByteBuffer fetchedVal = txn.val(); - assertThat(fetchedVal.remaining()).isEqualTo(valSize); - - // Let's double-check the fetched value is correct - assertThat(UTF_8.decode(fetchedVal).toString()).isEqualTo("Hello world"); - } - - // We can also delete. The simplest way is to let Dbi allocate a new Txn... - db.delete(key); - - // Now if we try to fetch the deleted row, it won't be present - try (Txn txn = env.txnRead()) { - assertThat(db.get(txn, key)).isNull(); - } - - env.close(); - }); + final Path dir = tempDir.createTempDir(); + + // We always need an Env. An Env owns a physical on-disk storage file. One + // Env can store many different databases (ie sorted maps). + final Env env = + create() + // LMDB also needs to know how large our DB might be. Over-estimating is OK. + .setMapSize(10_485_760) + // LMDB also needs to know how many DBs (Dbi) we want to store in this Env. + .setMaxDbs(1) + // Now let's open the Env. The same path can be concurrently opened and + // used in different processes, but do not open the same path twice in + // the same process at the same time. + .open(dir.toFile()); + + // We need a Dbi for each DB. A Dbi roughly equates to a sorted map. The + // MDB_CREATE flag causes the DB to be created if it doesn't already exist. + final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); + + // We want to store some data, so we will need a direct ByteBuffer. + // Note that LMDB keys cannot exceed maxKeySize bytes (511 bytes by default). + // Values can be larger. + final ByteBuffer key = allocateDirect(env.getMaxKeySize()); + final ByteBuffer val = allocateDirect(700); + key.put("greeting".getBytes(UTF_8)).flip(); + val.put("Hello world".getBytes(UTF_8)).flip(); + final int valSize = val.remaining(); + + // Now store it. Dbi.put() internally begins and commits a transaction (Txn). + db.put(key, val); + + // To fetch any data from LMDB we need a Txn. A Txn is very important in + // LmdbJava because it offers ACID characteristics and internally holds a + // read-only key buffer and read-only value buffer. These read-only buffers + // are always the same two Java objects, but point to different LMDB-managed + // memory as we use Dbi (and Cursor) methods. These read-only buffers remain + // valid only until the Txn is released or the next Dbi or Cursor call. If + // you need data afterwards, you should copy the bytes to your own buffer. + try (Txn txn = env.txnRead()) { + final ByteBuffer found = db.get(txn, key); + assertThat(found).isNotNull(); + + // The fetchedVal is read-only and points to LMDB memory + final ByteBuffer fetchedVal = txn.val(); + assertThat(fetchedVal.remaining()).isEqualTo(valSize); + + // Let's double-check the fetched value is correct + assertThat(UTF_8.decode(fetchedVal).toString()).isEqualTo("Hello world"); + } + + // We can also delete. The simplest way is to let Dbi allocate a new Txn... + db.delete(key); + + // Now if we try to fetch the deleted row, it won't be present + try (Txn txn = env.txnRead()) { + assertThat(db.get(txn, key)).isNull(); + } + + env.close(); } /** In this second tutorial we'll learn more about LMDB's ACID Txns. */ @Test void tutorial2() { - FileUtil.useTempDir( - dir -> { - try { - final Env env = createSimpleEnv(dir); - final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); - final ByteBuffer key = allocateDirect(env.getMaxKeySize()); - final ByteBuffer val = allocateDirect(700); - - // Let's write and commit "key1" via a Txn. A Txn can include multiple Dbis. - // Note write Txns block other write Txns, due to writes being serialized. - // It's therefore important to avoid unnecessarily long-lived write Txns. + final Path dir = tempDir.createTempDir(); + try { + final Env env = createSimpleEnv(dir); + final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); + final ByteBuffer key = allocateDirect(env.getMaxKeySize()); + final ByteBuffer val = allocateDirect(700); + + // Let's write and commit "key1" via a Txn. A Txn can include multiple Dbis. + // Note write Txns block other write Txns, due to writes being serialized. + // It's therefore important to avoid unnecessarily long-lived write Txns. + try (Txn txn = env.txnWrite()) { + key.put("key1".getBytes(UTF_8)).flip(); + val.put("lmdb".getBytes(UTF_8)).flip(); + db.put(txn, key, val); + + // We can read data too, even though this is a write Txn. + final ByteBuffer found = db.get(txn, key); + assertThat(found).isNotNull(); + + // An explicit commit is required, otherwise Txn.close() rolls it back. + txn.commit(); + } + + // Open a read-only Txn. It only sees data that existed at Txn creation time. + final Txn rtx = env.txnRead(); + + // Our read Txn can fetch key1 without problem, as it existed at Txn creation. + ByteBuffer found = db.get(rtx, key); + assertThat(found).isNotNull(); + + // Note that our main test thread holds the Txn. Only one Txn per thread is + // typically permitted (the exception is a read-only Env with MDB_NOTLS). + // + // Let's write out a "key2" via a new write Txn in a different thread. + final ExecutorService es = newCachedThreadPool(); + es.execute( + () -> { try (Txn txn = env.txnWrite()) { - key.put("key1".getBytes(UTF_8)).flip(); - val.put("lmdb".getBytes(UTF_8)).flip(); + key.clear(); + key.put("key2".getBytes(UTF_8)).flip(); db.put(txn, key, val); - - // We can read data too, even though this is a write Txn. - final ByteBuffer found = db.get(txn, key); - assertThat(found).isNotNull(); - - // An explicit commit is required, otherwise Txn.close() rolls it back. txn.commit(); } - - // Open a read-only Txn. It only sees data that existed at Txn creation time. - final Txn rtx = env.txnRead(); - - // Our read Txn can fetch key1 without problem, as it existed at Txn creation. - ByteBuffer found = db.get(rtx, key); - assertThat(found).isNotNull(); - - // Note that our main test thread holds the Txn. Only one Txn per thread is - // typically permitted (the exception is a read-only Env with MDB_NOTLS). - // - // Let's write out a "key2" via a new write Txn in a different thread. - final ExecutorService es = newCachedThreadPool(); - es.execute( - () -> { - try (Txn txn = env.txnWrite()) { - key.clear(); - key.put("key2".getBytes(UTF_8)).flip(); - db.put(txn, key, val); - txn.commit(); - } - }); - es.shutdown(); - es.awaitTermination(10, SECONDS); - - // Even though key2 has been committed, our read Txn still can't see it. - found = db.get(rtx, key); - assertThat(found).isNull(); - - // To see key2, we could create a new Txn. But a reset/renew is much faster. - // Reset/renew is also important to avoid long-lived read Txns, as these - // prevent the re-use of free pages by write Txns (ie the DB will grow). - rtx.reset(); - // ... potentially long operation here ... - rtx.renew(); - found = db.get(rtx, key); - assertThat(found).isNotNull(); - - // Don't forget to close the read Txn now we're completely finished. We could - // have avoided this if we used a try-with-resources block, but we wanted to - // play around with multiple concurrent Txns to demonstrate the "I" in ACID. - rtx.close(); - env.close(); - } catch (final InterruptedException e) { - throw new RuntimeException(e); - } - }); + }); + es.shutdown(); + es.awaitTermination(10, SECONDS); + + // Even though key2 has been committed, our read Txn still can't see it. + found = db.get(rtx, key); + assertThat(found).isNull(); + + // To see key2, we could create a new Txn. But a reset/renew is much faster. + // Reset/renew is also important to avoid long-lived read Txns, as these + // prevent the re-use of free pages by write Txns (ie the DB will grow). + rtx.reset(); + // ... potentially long operation here ... + rtx.renew(); + found = db.get(rtx, key); + assertThat(found).isNotNull(); + + // Don't forget to close the read Txn now we're completely finished. We could + // have avoided this if we used a try-with-resources block, but we wanted to + // play around with multiple concurrent Txns to demonstrate the "I" in ACID. + rtx.close(); + env.close(); + } catch (final InterruptedException e) { + throw new RuntimeException(e); + } } /** @@ -207,73 +215,71 @@ void tutorial2() { */ @Test void tutorial3() { - FileUtil.useTempDir( - dir -> { - final Env env = createSimpleEnv(dir); - final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); - final ByteBuffer key = allocateDirect(env.getMaxKeySize()); - final ByteBuffer val = allocateDirect(700); - - try (Txn txn = env.txnWrite()) { - // A cursor always belongs to a particular Dbi. - final Cursor c = db.openCursor(txn); - - // We can put via a Cursor. Note we're adding keys in a strange order, - // as we want to show you that LMDB returns them in sorted order. - key.put("zzz".getBytes(UTF_8)).flip(); - val.put("lmdb".getBytes(UTF_8)).flip(); - c.put(key, val); - key.clear(); - key.put("aaa".getBytes(UTF_8)).flip(); - c.put(key, val); - key.clear(); - key.put("ccc".getBytes(UTF_8)).flip(); - c.put(key, val); - - // We can read from the Cursor by key. - c.get(key, MDB_SET); - assertThat(UTF_8.decode(c.key()).toString()).isEqualTo("ccc"); - - // Let's see that LMDB provides the keys in appropriate order.... - c.seek(MDB_FIRST); - assertThat(UTF_8.decode(c.key()).toString()).isEqualTo("aaa"); - - c.seek(MDB_LAST); - assertThat(UTF_8.decode(c.key()).toString()).isEqualTo("zzz"); - - c.seek(MDB_PREV); - assertThat(UTF_8.decode(c.key()).toString()).isEqualTo("ccc"); - - // Cursors can also delete the current key. - c.delete(); - - c.close(); - txn.commit(); - } - - // A read-only Cursor can survive its original Txn being closed. This is - // useful if you want to close the original Txn (eg maybe you created the - // Cursor during the constructor of a singleton with a throw-away Txn). Of - // course, you cannot use the Cursor if its Txn is closed or currently reset. - final Txn tx1 = env.txnRead(); - final Cursor c = db.openCursor(tx1); - tx1.close(); - - // The Cursor becomes usable again by "renewing" it with an active read Txn. - final Txn tx2 = env.txnRead(); - c.renew(tx2); - c.seek(MDB_FIRST); - - // As usual with read Txns, we can reset and renew them. The Cursor does - // not need any special handling if we do this. - tx2.reset(); - // ... potentially long operation here ... - tx2.renew(); - c.seek(MDB_LAST); - - tx2.close(); - env.close(); - }); + final Path dir = tempDir.createTempDir(); + final Env env = createSimpleEnv(dir); + final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); + final ByteBuffer key = allocateDirect(env.getMaxKeySize()); + final ByteBuffer val = allocateDirect(700); + + try (Txn txn = env.txnWrite()) { + // A cursor always belongs to a particular Dbi. + final Cursor c = db.openCursor(txn); + + // We can put via a Cursor. Note we're adding keys in a strange order, + // as we want to show you that LMDB returns them in sorted order. + key.put("zzz".getBytes(UTF_8)).flip(); + val.put("lmdb".getBytes(UTF_8)).flip(); + c.put(key, val); + key.clear(); + key.put("aaa".getBytes(UTF_8)).flip(); + c.put(key, val); + key.clear(); + key.put("ccc".getBytes(UTF_8)).flip(); + c.put(key, val); + + // We can read from the Cursor by key. + c.get(key, MDB_SET); + assertThat(UTF_8.decode(c.key()).toString()).isEqualTo("ccc"); + + // Let's see that LMDB provides the keys in appropriate order.... + c.seek(MDB_FIRST); + assertThat(UTF_8.decode(c.key()).toString()).isEqualTo("aaa"); + + c.seek(MDB_LAST); + assertThat(UTF_8.decode(c.key()).toString()).isEqualTo("zzz"); + + c.seek(MDB_PREV); + assertThat(UTF_8.decode(c.key()).toString()).isEqualTo("ccc"); + + // Cursors can also delete the current key. + c.delete(); + + c.close(); + txn.commit(); + } + + // A read-only Cursor can survive its original Txn being closed. This is + // useful if you want to close the original Txn (eg maybe you created the + // Cursor during the constructor of a singleton with a throw-away Txn). Of + // course, you cannot use the Cursor if its Txn is closed or currently reset. + final Txn tx1 = env.txnRead(); + final Cursor c = db.openCursor(tx1); + tx1.close(); + + // The Cursor becomes usable again by "renewing" it with an active read Txn. + final Txn tx2 = env.txnRead(); + c.renew(tx2); + c.seek(MDB_FIRST); + + // As usual with read Txns, we can reset and renew them. The Cursor does + // not need any special handling if we do this. + tx2.reset(); + // ... potentially long operation here ... + tx2.renew(); + c.seek(MDB_LAST); + + tx2.close(); + env.close(); } /** @@ -282,109 +288,105 @@ void tutorial3() { */ @Test void tutorial4() { - FileUtil.useTempDir( - dir -> { - final Env env = createSimpleEnv(dir); - final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); - - try (Txn txn = env.txnWrite()) { - final ByteBuffer key = allocateDirect(env.getMaxKeySize()); - final ByteBuffer val = allocateDirect(700); - - // Insert some data. Note that ByteBuffer order defaults to Big Endian. - // LMDB does not persist the byte order, but it's critical to sort keys. - // If your numeric keys don't sort as expected, review buffer byte order. - val.putInt(100); - key.putInt(1); - db.put(txn, key, val); - key.clear(); - key.putInt(2); - db.put(txn, key, val); - key.clear(); - - // Each iterable uses a cursor and must be closed when finished. Iterate - // forward in terms of key ordering starting with the first key. - try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { - for (final KeyVal kv : ci) { - assertThat(kv.key()).isNotNull(); - assertThat(kv.val()).isNotNull(); - } - } - - // Iterate backward in terms of key ordering starting with the last key. - try (CursorIterable ci = db.iterate(txn, KeyRange.allBackward())) { - for (final KeyVal kv : ci) { - assertThat(kv.key()).isNotNull(); - assertThat(kv.val()).isNotNull(); - } - } - - // There are many ways to control the desired key range via KeyRange, such - // as arbitrary start and stop values, direction etc. We've adopted Guava's - // terminology for our range classes (see KeyRangeType for further details). - key.putInt(1); - final KeyRange range = KeyRange.atLeastBackward(key); - try (CursorIterable ci = db.iterate(txn, range)) { - for (final KeyVal kv : ci) { - assertThat(kv.key()).isNotNull(); - assertThat(kv.val()).isNotNull(); - } - } - } - - env.close(); - }); + final Path dir = tempDir.createTempDir(); + final Env env = createSimpleEnv(dir); + final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); + + try (Txn txn = env.txnWrite()) { + final ByteBuffer key = allocateDirect(env.getMaxKeySize()); + final ByteBuffer val = allocateDirect(700); + + // Insert some data. Note that ByteBuffer order defaults to Big Endian. + // LMDB does not persist the byte order, but it's critical to sort keys. + // If your numeric keys don't sort as expected, review buffer byte order. + val.putInt(100); + key.putInt(1); + db.put(txn, key, val); + key.clear(); + key.putInt(2); + db.put(txn, key, val); + key.clear(); + + // Each iterable uses a cursor and must be closed when finished. Iterate + // forward in terms of key ordering starting with the first key. + try (CursorIterable ci = db.iterate(txn, KeyRange.all())) { + for (final KeyVal kv : ci) { + assertThat(kv.key()).isNotNull(); + assertThat(kv.val()).isNotNull(); + } + } + + // Iterate backward in terms of key ordering starting with the last key. + try (CursorIterable ci = db.iterate(txn, KeyRange.allBackward())) { + for (final KeyVal kv : ci) { + assertThat(kv.key()).isNotNull(); + assertThat(kv.val()).isNotNull(); + } + } + + // There are many ways to control the desired key range via KeyRange, such + // as arbitrary start and stop values, direction etc. We've adopted Guava's + // terminology for our range classes (see KeyRangeType for further details). + key.putInt(1); + final KeyRange range = KeyRange.atLeastBackward(key); + try (CursorIterable ci = db.iterate(txn, range)) { + for (final KeyVal kv : ci) { + assertThat(kv.key()).isNotNull(); + assertThat(kv.val()).isNotNull(); + } + } + } + + env.close(); } /** In this fifth tutorial we'll explore multiple values sharing a single key. */ @Test void tutorial5() { - FileUtil.useTempDir( - dir -> { - final Env env = createSimpleEnv(dir); - - // This time we're going to tell the Dbi it can store > 1 value per key. - // There are other flags available if we're storing integers etc. - final Dbi db = env.openDbi(DB_NAME, MDB_CREATE, MDB_DUPSORT); - - // Duplicate support requires both keys and values to be <= max key size. - final ByteBuffer key = allocateDirect(env.getMaxKeySize()); - final ByteBuffer val = allocateDirect(env.getMaxKeySize()); - - try (Txn txn = env.txnWrite()) { - final Cursor c = db.openCursor(txn); - - // Store one key, but many values, and in non-natural order. - key.put("key".getBytes(UTF_8)).flip(); - val.put("xxx".getBytes(UTF_8)).flip(); - c.put(key, val); - val.clear(); - val.put("kkk".getBytes(UTF_8)).flip(); - c.put(key, val); - val.clear(); - val.put("lll".getBytes(UTF_8)).flip(); - c.put(key, val); - - // Cursor can tell us how many values the current key has. - final long count = c.count(); - assertThat(count).isEqualTo(3L); - - // Let's position the Cursor. Note sorting still works. - c.seek(MDB_FIRST); - assertThat(UTF_8.decode(c.val()).toString()).isEqualTo("kkk"); - - c.seek(MDB_LAST); - assertThat(UTF_8.decode(c.val()).toString()).isEqualTo("xxx"); - - c.seek(MDB_PREV); - assertThat(UTF_8.decode(c.val()).toString()).isEqualTo("lll"); - - c.close(); - txn.commit(); - } - - env.close(); - }); + final Path dir = tempDir.createTempDir(); + final Env env = createSimpleEnv(dir); + + // This time we're going to tell the Dbi it can store > 1 value per key. + // There are other flags available if we're storing integers etc. + final Dbi db = env.openDbi(DB_NAME, MDB_CREATE, MDB_DUPSORT); + + // Duplicate support requires both keys and values to be <= max key size. + final ByteBuffer key = allocateDirect(env.getMaxKeySize()); + final ByteBuffer val = allocateDirect(env.getMaxKeySize()); + + try (Txn txn = env.txnWrite()) { + final Cursor c = db.openCursor(txn); + + // Store one key, but many values, and in non-natural order. + key.put("key".getBytes(UTF_8)).flip(); + val.put("xxx".getBytes(UTF_8)).flip(); + c.put(key, val); + val.clear(); + val.put("kkk".getBytes(UTF_8)).flip(); + c.put(key, val); + val.clear(); + val.put("lll".getBytes(UTF_8)).flip(); + c.put(key, val); + + // Cursor can tell us how many values the current key has. + final long count = c.count(); + assertThat(count).isEqualTo(3L); + + // Let's position the Cursor. Note sorting still works. + c.seek(MDB_FIRST); + assertThat(UTF_8.decode(c.val()).toString()).isEqualTo("kkk"); + + c.seek(MDB_LAST); + assertThat(UTF_8.decode(c.val()).toString()).isEqualTo("xxx"); + + c.seek(MDB_PREV); + assertThat(UTF_8.decode(c.val()).toString()).isEqualTo("lll"); + + c.close(); + txn.commit(); + } + + env.close(); } /** @@ -393,85 +395,79 @@ void tutorial5() { */ @Test void tutorial6() { - FileUtil.useTempDir( - dir -> { - // Note we need to specify the Verifier's DBI_COUNT for the Env. - final Env env = - create(PROXY_OPTIMAL) - .setMapSize(10_485_760) - .setMaxDbs(Verifier.DBI_COUNT) - .open(dir.toFile()); - - // Create a Verifier (it's a Callable for those needing full control). - final Verifier v = new Verifier(env); - - // We now run the verifier for 3 seconds; it raises an exception on failure. - // The method returns the number of entries it successfully verified. - v.runFor(3, SECONDS); - - env.close(); - }); + final Path dir = tempDir.createTempDir(); + // Note we need to specify the Verifier's DBI_COUNT for the Env. + final Env env = + create(PROXY_OPTIMAL) + .setMapSize(10_485_760) + .setMaxDbs(Verifier.DBI_COUNT) + .open(dir.toFile()); + + // Create a Verifier (it's a Callable for those needing full control). + final Verifier v = new Verifier(env); + + // We now run the verifier for 3 seconds; it raises an exception on failure. + // The method returns the number of entries it successfully verified. + v.runFor(3, SECONDS); + + env.close(); } /** In this final tutorial we'll look at using Agrona's DirectBuffer. */ @Test void tutorial7() { - FileUtil.useTempDir( - dir -> { - // The critical difference is we pass the PROXY_DB field to Env.create(). - // There's also a PROXY_SAFE if you want to stop ByteBuffer's Unsafe use. - // Aside from that and a different type argument, it's the same as usual... - final Env env = - create(PROXY_DB).setMapSize(10_485_760).setMaxDbs(1).open(dir.toFile()); - - final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); - - final ByteBuffer keyBb = allocateDirect(env.getMaxKeySize()); - final MutableDirectBuffer key = new UnsafeBuffer(keyBb); - final MutableDirectBuffer val = new UnsafeBuffer(allocateDirect(700)); - - try (Txn txn = env.txnWrite()) { - try (Cursor c = db.openCursor(txn)) { - // Agrona is faster than ByteBuffer and its methods are nicer... - val.putStringWithoutLengthUtf8(0, "The Value"); - key.putStringWithoutLengthUtf8(0, "yyy"); - c.put(key, val); - - key.putStringWithoutLengthUtf8(0, "ggg"); - c.put(key, val); - - c.seek(MDB_FIRST); - assertThat(c.key().getStringWithoutLengthUtf8(0, env.getMaxKeySize())) - .startsWith("ggg"); - - c.seek(MDB_LAST); - assertThat(c.key().getStringWithoutLengthUtf8(0, env.getMaxKeySize())) - .startsWith("yyy"); - - // DirectBuffer has no position concept. Often you don't want to store - // the unnecessary bytes of a varying-size buffer. Let's have a look... - final int keyLen = key.putStringWithoutLengthUtf8(0, "12characters"); - assertThat(keyLen).isEqualTo(12); - assertThat(key.capacity()).isEqualTo(env.getMaxKeySize()); - - // To only store the 12 characters, we simply call wrap: - key.wrap(key, 0, keyLen); - assertThat(key.capacity()).isEqualTo(keyLen); - c.put(key, val); - c.seek(MDB_FIRST); - assertThat(c.key().capacity()).isEqualTo(keyLen); - assertThat(c.key().getStringWithoutLengthUtf8(0, c.key().capacity())) - .isEqualTo("12characters"); - - // To store bigger values again, just wrap the original buffer. - key.wrap(keyBb); - assertThat(key.capacity()).isEqualTo(env.getMaxKeySize()); - } - txn.commit(); - } - - env.close(); - }); + final Path dir = tempDir.createTempDir(); + // The critical difference is we pass the PROXY_DB field to Env.create(). + // There's also a PROXY_SAFE if you want to stop ByteBuffer's Unsafe use. + // Aside from that and a different type argument, it's the same as usual... + final Env env = + create(PROXY_DB).setMapSize(10_485_760).setMaxDbs(1).open(dir.toFile()); + + final Dbi db = env.openDbi(DB_NAME, MDB_CREATE); + + final ByteBuffer keyBb = allocateDirect(env.getMaxKeySize()); + final MutableDirectBuffer key = new UnsafeBuffer(keyBb); + final MutableDirectBuffer val = new UnsafeBuffer(allocateDirect(700)); + + try (Txn txn = env.txnWrite()) { + try (Cursor c = db.openCursor(txn)) { + // Agrona is faster than ByteBuffer and its methods are nicer... + val.putStringWithoutLengthUtf8(0, "The Value"); + key.putStringWithoutLengthUtf8(0, "yyy"); + c.put(key, val); + + key.putStringWithoutLengthUtf8(0, "ggg"); + c.put(key, val); + + c.seek(MDB_FIRST); + assertThat(c.key().getStringWithoutLengthUtf8(0, env.getMaxKeySize())).startsWith("ggg"); + + c.seek(MDB_LAST); + assertThat(c.key().getStringWithoutLengthUtf8(0, env.getMaxKeySize())).startsWith("yyy"); + + // DirectBuffer has no position concept. Often you don't want to store + // the unnecessary bytes of a varying-size buffer. Let's have a look... + final int keyLen = key.putStringWithoutLengthUtf8(0, "12characters"); + assertThat(keyLen).isEqualTo(12); + assertThat(key.capacity()).isEqualTo(env.getMaxKeySize()); + + // To only store the 12 characters, we simply call wrap: + key.wrap(key, 0, keyLen); + assertThat(key.capacity()).isEqualTo(keyLen); + c.put(key, val); + c.seek(MDB_FIRST); + assertThat(c.key().capacity()).isEqualTo(keyLen); + assertThat(c.key().getStringWithoutLengthUtf8(0, c.key().capacity())) + .isEqualTo("12characters"); + + // To store bigger values again, just wrap the original buffer. + key.wrap(keyBb); + assertThat(key.capacity()).isEqualTo(env.getMaxKeySize()); + } + txn.commit(); + } + + env.close(); } // You've finished! There are lots of other neat things we could show you (eg diff --git a/src/test/java/org/lmdbjava/TxnTest.java b/src/test/java/org/lmdbjava/TxnTest.java index b2f04ff6..ebb06137 100644 --- a/src/test/java/org/lmdbjava/TxnTest.java +++ b/src/test/java/org/lmdbjava/TxnTest.java @@ -35,7 +35,6 @@ import static org.lmdbjava.Txn.State.RESET; import static org.lmdbjava.TxnFlags.MDB_RDONLY_TXN; -import java.io.IOException; import java.nio.ByteBuffer; import java.nio.file.Path; import java.util.ArrayList; @@ -60,9 +59,12 @@ public final class TxnTest { private Path file; private Env env; + private TempDir tempDir; + @BeforeEach void beforeEach() { - file = FileUtil.createTempFile(); + tempDir = new TempDir(); + file = tempDir.createTempFile(); env = create() .setMapSize(KIBIBYTES.toBytes(256)) @@ -74,11 +76,11 @@ void beforeEach() { @AfterEach void afterEach() { env.close(); - FileUtil.deleteFile(file); + tempDir.cleanup(); } @Test - void largeKeysRejected() throws IOException { + void largeKeysRejected() { assertThatThrownBy( () -> { final Dbi dbi = env.openDbi(DB_1, MDB_CREATE); diff --git a/src/test/java/org/lmdbjava/VerifierTest.java b/src/test/java/org/lmdbjava/VerifierTest.java index 64214568..9d127cf8 100644 --- a/src/test/java/org/lmdbjava/VerifierTest.java +++ b/src/test/java/org/lmdbjava/VerifierTest.java @@ -22,6 +22,7 @@ import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; import java.nio.ByteBuffer; +import java.nio.file.Path; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; @@ -30,18 +31,18 @@ public final class VerifierTest { @Test void verification() { - FileUtil.useTempFile( - file -> { - try (Env env = - create() - .setMaxReaders(1) - .setMaxDbs(Verifier.DBI_COUNT) - .setMapSize(MEBIBYTES.toBytes(10)) - .open(file.toFile(), MDB_NOSUBDIR)) { - final Verifier v = new Verifier(env); - final int seconds = Integer.getInteger("verificationSeconds", 2); - assertThat(v.runFor(seconds, TimeUnit.SECONDS)).isGreaterThan(1L); - } - }); + try (final TempDir tempDir = new TempDir()) { + final Path file = tempDir.createTempFile(); + try (Env env = + create() + .setMaxReaders(1) + .setMaxDbs(Verifier.DBI_COUNT) + .setMapSize(MEBIBYTES.toBytes(10)) + .open(file.toFile(), MDB_NOSUBDIR)) { + final Verifier v = new Verifier(env); + final int seconds = Integer.getInteger("verificationSeconds", 2); + assertThat(v.runFor(seconds, TimeUnit.SECONDS)).isGreaterThan(1L); + } + } } } diff --git a/src/test/resources/CursorIterableRangeTest/testSignedComparatorDupsort.csv b/src/test/resources/CursorIterableRangeTest/testSignedComparatorDupsort.csv index f35dbedc..1a18f426 100644 --- a/src/test/resources/CursorIterableRangeTest/testSignedComparatorDupsort.csv +++ b/src/test/resources/CursorIterableRangeTest/testSignedComparatorDupsort.csv @@ -28,7 +28,7 @@ BACKWARD_CLOSED,7,3,[6 8][6 7][4 6][4 5] BACKWARD_CLOSED,6,2,[6 8][6 7][4 6][4 5][2 4][2 3] BACKWARD_CLOSED,9,3,[8 10][8 9][6 8][6 7][4 6][4 5] BACKWARD_CLOSED,9,-1,[8 10][8 9][6 8][6 7][4 6][4 5][2 4][2 3][0 2][0 1] -BACKWARD_CLOSED_OPEN,8,3,[8 9][6 8][6 7][4 6][4 5] +BACKWARD_CLOSED_OPEN,8,3,[8 10][8 9][6 8][6 7][4 6][4 5] BACKWARD_CLOSED_OPEN,7,2,[6 8][6 7][4 6][4 5] BACKWARD_CLOSED_OPEN,9,3,[8 10][8 9][6 8][6 7][4 6][4 5] BACKWARD_CLOSED_OPEN,9,-1,[8 10][8 9][6 8][6 7][4 6][4 5][2 4][2 3][0 2][0 1] diff --git a/src/test/resources/CursorIterableRangeTest/testUnsignedComparatorDupsort.csv b/src/test/resources/CursorIterableRangeTest/testUnsignedComparatorDupsort.csv index 01cddbad..e9054cbd 100644 --- a/src/test/resources/CursorIterableRangeTest/testUnsignedComparatorDupsort.csv +++ b/src/test/resources/CursorIterableRangeTest/testUnsignedComparatorDupsort.csv @@ -28,7 +28,7 @@ BACKWARD_CLOSED,7,3,[6 8][6 7][4 6][4 5] BACKWARD_CLOSED,6,2,[6 8][6 7][4 6][4 5][2 4][2 3] BACKWARD_CLOSED,9,3,[8 10][8 9][6 8][6 7][4 6][4 5] BACKWARD_CLOSED,9,-1, -BACKWARD_CLOSED_OPEN,8,3,[8 9][6 8][6 7][4 6][4 5] +BACKWARD_CLOSED_OPEN,8,3,[8 10][8 9][6 8][6 7][4 6][4 5] BACKWARD_CLOSED_OPEN,7,2,[6 8][6 7][4 6][4 5] BACKWARD_CLOSED_OPEN,9,3,[8 10][8 9][6 8][6 7][4 6][4 5] BACKWARD_CLOSED_OPEN,9,-1, @@ -47,3 +47,14 @@ BACKWARD_OPEN_CLOSED,7,2,[6 8][6 7][4 6][4 5][2 4][2 3] BACKWARD_OPEN_CLOSED,8,4,[6 8][6 7][4 6][4 5] BACKWARD_OPEN_CLOSED,9,4,[8 10][8 9][6 8][6 7][4 6][4 5] BACKWARD_OPEN_CLOSED,9,-1, +# +# TEST gh-267 +BACKWARD_AT_LEAST,6,,[6 8][6 7][4 6][4 5][2 4][2 3][0 2][0 1] +BACKWARD_AT_LEAST,-2,,[-2 -1][-2 0][8 10][8 9][6 8][6 7][4 6][4 5][2 4][2 3][0 2][0 1] +BACKWARD_AT_LEAST,9,,[8 10][8 9][6 8][6 7][4 6][4 5][2 4][2 3][0 2][0 1] +BACKWARD_CLOSED,6,3,[6 8][6 7][4 6][4 5] +BACKWARD_CLOSED,-2,2,[-2 -1][-2 0][8 10][8 9][6 8][6 7][4 6][4 5][2 4][2 3] +BACKWARD_CLOSED,9,3,[8 10][8 9][6 8][6 7][4 6][4 5] +BACKWARD_CLOSED_OPEN,6,2,[6 8][6 7][4 6][4 5] +BACKWARD_CLOSED_OPEN,-2,3,[-2 -1][-2 0][8 10][8 9][6 8][6 7][4 6][4 5] +BACKWARD_CLOSED_OPEN,9,-1, \ No newline at end of file From e634b3f48fd2dcc6790f580085b8c44dd5d7077b Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Thu, 30 Oct 2025 09:00:45 +1100 Subject: [PATCH 242/322] Fix major performance regression LmdbJava Benchmarking infrastructure was updated in #262. This uncovered a major regression in releases 0.9.0 and 0.9.1. To identify the source of the regression, the updated LmdbJava Benchmarks were executed with 1,000,000 entries using 100 byte values and 4 byte integer keys. The sequential read benchmark was retrospectively executed against LmdbJava commits compiled using Java 25 as detailed in the following table. To rule out native library variability, LMDB 0.9.33 from the official Arch Linux x86_64 native package was used via the lmdbjava.native.lib system property. Benchmarks were run on workstation-grade hardware (64 GB RAM, AMD Ryzen 9 7900 12-Core Processor) to a 32 GB /tmp directory backed by tmpfs. The machine was not running any other workloads. Tests were executed for 1 minute per warmup, 1 minute per iteration, 3 warmups, 3 iterations, and 3 forks. As such each commit was tested for approximately 18 minutes to overcome jitter and noise found at shorter durations. It was not possible to test intermediate releases 0.6.3 or 0.7.0 due to JNR-FFI incompatibilities. As shown, the regression was introduced in commit 0c636f1 (#207). This followed the 0.8.3 release and was included in releases 0.9.0 and 0.9.1. The fix provided in this commit has been verified with the benchmark result shown below. In addition to the automated tests passing, a script was written that executed "mvn test -Dtest=GarbageCollectionTest" (which was provided in #207) for 1000 iterations with both Java 17 and 25. This comprehensively confirms this fix does not introduce the adverse behaviour initially identified in #207. The logical basis for the fix is also confirmed in the updated JavaDocs for ReferenceUtil. While there remains a 4.60% regression relative to the 0.0.1 baseline, this is lower than any release since 0.0.5. Further work to follow on automating benchmarking infrastructure and addressing other performance improvement opportunities. Commit Release Date Score (ms) Delta Notes ------------------------------------------------------------------------ 57f355f v0.0.1 2016-07-07 43.22 Baseline 6d001e8 v0.0.5 2017-02-08 43.97 +1.74% d3572ca v0.5.0 2017-07-05 45.29 +4.79% be2a15b v0.8.3 2023-02-04 45.26 +4.72% 3524995 2023-02-04 45.41 +5.07% Refactor Comparator handling 9cf97a5 2023-03-09 45.04 +4.21% mdb_reader_check API fix 0c636f1 2023-03-23 47.83 +10.67% Hold strong reference (REGRESSION) cbdaee6 2023-04-24 47.39 +9.65% Update formatting/GC test c5e02f7 2023-04-24 48.24 +11.61% Merge fixcorruptionbug f17b63f v0.9.0 2023-12-05 48.54 +12.31% 0025a48 v0.9.1 2025-02-20 47.61 +10.16% ddca4bd 2025-07-08 47.55 +10.02% Fix comparator lost buffer ref c91893a 2025-10-25 58.67 +35.74% Fix transient pointer (PEAK REGRESSION) 43ac84f 2025-10-26 55.08 +27.43% a15f4a7 2025-10-29 55.43 +28.24% Master before fix 19b0250 2025-10-30 45.21 +4.60% Empty method fix (REGRESSION ELIMINATED) --- src/main/java/org/lmdbjava/ReferenceUtil.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/lmdbjava/ReferenceUtil.java b/src/main/java/org/lmdbjava/ReferenceUtil.java index 2b9f211e..67273d05 100644 --- a/src/main/java/org/lmdbjava/ReferenceUtil.java +++ b/src/main/java/org/lmdbjava/ReferenceUtil.java @@ -33,17 +33,18 @@ private ReferenceUtil() {} * but it was only introduced in Java 9. LmdbJava presently supports Java 8 and therefore this * method provides an alternative. * - *

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

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

+ *

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

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

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

*

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

*

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

*

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

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

*

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

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

*

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

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

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

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

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

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

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

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

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

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

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

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

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

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


Delete current key/data pair. + *

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


Delete current key/data pair. + *

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

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


Store by cursor. + *

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

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


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

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

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


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

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

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

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


+ *

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

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

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

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

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

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

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

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

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

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

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

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


+ * + *

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


+ * + *

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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


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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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


Delete current key/data pair. + *

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


- * Store by cursor. - * - *

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


Store by cursor. + *

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


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

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


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

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


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

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


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

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

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

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


- *

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

- *

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


+ *

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

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

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

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

- *

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

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

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

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

- *

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

- *

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

+ * + *

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

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

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

- *

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

- *

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

- *

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

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

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

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

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

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

- *

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

- *

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

+ * + *

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

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

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

- *

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

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

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

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


- *

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

- *
- *

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

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


+ * + *

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

- *

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

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

- *

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

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

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

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

- *

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

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

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

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

- *

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

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

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

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

- *

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

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

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

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

- *

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

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

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

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

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

+ * + *

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

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

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

+ *

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

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

- *

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

- *

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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


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

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

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

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

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

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

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

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

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

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

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

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

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