Skip to content

Commit 47b4c4f

Browse files
committed
Merge branch 'master' into mdb_cmp
2 parents 75d87d0 + 43ac84f commit 47b4c4f

File tree

10 files changed

+292
-88
lines changed

10 files changed

+292
-88
lines changed

src/main/java/org/lmdbjava/BufferProxy.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ protected BufferProxy() {}
7878

7979
/**
8080
* Get a suitable default {@link Comparator} to compare numeric key values as unsigned.
81+
* <p>
82+
* This should match the behaviour of the LMDB's mdb_cmp comparator as it may be used for
83+
* {@link CursorIterable} start/stop keys comparisons, which must match LMDB's insertion order.
84+
* </p>
8185
*
8286
* @return a comparator that can be used (never null)
8387
*/

src/main/java/org/lmdbjava/CursorIterable.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,10 @@ enum State {
239239
TERMINATED
240240
}
241241

242+
243+
// --------------------------------------------------------------------------------
244+
245+
242246
static class JavaRangeComparator<T> implements RangeComparator {
243247

244248
private final Comparator<T> comparator;
@@ -272,6 +276,10 @@ public void close() throws Exception {
272276
}
273277
}
274278

279+
280+
// --------------------------------------------------------------------------------
281+
282+
275283
/**
276284
* Calls down to mdb_cmp to make use of the comparator that LMDB uses for insertion order. Has a
277285
* very slight overhead as compared to {@link JavaRangeComparator}.

src/main/java/org/lmdbjava/Dbi.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,8 @@ public final class Dbi<T> {
8080
requireNonNull(comparator, "comparator cannot be null if nativeCb is set");
8181
this.ccb =
8282
(keyA, keyB) -> {
83-
final T compKeyA = proxy.allocate();
84-
final T compKeyB = proxy.allocate();
85-
proxy.out(compKeyA, keyA);
86-
proxy.out(compKeyB, keyB);
83+
final T compKeyA = proxy.out(proxy.allocate(), keyA);
84+
final T compKeyB = proxy.out(proxy.allocate(), keyB);
8785
final int result = this.comparator.compare(compKeyA, compKeyB);
8886
proxy.deallocate(compKeyA);
8987
proxy.deallocate(compKeyB);

src/main/java/org/lmdbjava/DbiBuilder.java

Lines changed: 75 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
/*
2+
* Copyright © 2016-2025 The LmdbJava Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
116
package org.lmdbjava;
217

318
import java.nio.charset.StandardCharsets;
@@ -35,8 +50,11 @@ public class DbiBuilder<T> {
3550
* <p>
3651
* The name will be converted into bytes using {@link StandardCharsets#UTF_8}.
3752
* </p>
53+
* @param name The name of the database or null for the unnamed database
54+
* (see also {@link DbiBuilder#withoutDbName()})
55+
* @return The next builder stage.
3856
*/
39-
public RequireComparator<T> withDbName(final String name) {
57+
public DbiBuilderStage2<T> withDbName(final String name) {
4058
// Null name is allowed so no null check
4159
final byte[] nameBytes = name == null
4260
? null
@@ -46,11 +64,13 @@ public RequireComparator<T> withDbName(final String name) {
4664

4765
/**
4866
* Create the {@link Dbi} with the passed name in byte[] form.
67+
* @param name The name of the database in byte form.
68+
* @return The next builder stage.
4969
*/
50-
public RequireComparator<T> withDbName(final byte[] name) {
70+
public DbiBuilderStage2<T> withDbName(final byte[] name) {
5171
// Null name is allowed so no null check
5272
this.name = name;
53-
return new RequireComparator<>(this);
73+
return new DbiBuilderStage2<>(this);
5474
}
5575

5676
/**
@@ -61,8 +81,9 @@ public RequireComparator<T> withDbName(final byte[] name) {
6181
* Equivalent to passing null to
6282
* {@link DbiBuilder#withDbName(String)} or {@link DbiBuilder#withDbName(byte[])}.
6383
* </p>
84+
* @return The next builder stage.
6485
*/
65-
public RequireComparator<T> withoutDbName() {
86+
public DbiBuilderStage2<T> withoutDbName() {
6687
return withDbName((byte[]) null);
6788
}
6889

@@ -75,14 +96,14 @@ public RequireComparator<T> withoutDbName() {
7596
*
7697
* @param <T> buffer type
7798
*/
78-
public static class RequireComparator<T> {
99+
public static class DbiBuilderStage2<T> {
79100

80101
private final DbiBuilder<T> dbiBuilder;
81102

82103
private Comparator<T> comparator;
83104
private boolean useNativeCallback;
84105

85-
private RequireComparator(final DbiBuilder<T> dbiBuilder) {
106+
private DbiBuilderStage2(final DbiBuilder<T> dbiBuilder) {
86107
this.dbiBuilder = dbiBuilder;
87108
}
88109

@@ -94,24 +115,24 @@ private RequireComparator(final DbiBuilder<T> dbiBuilder) {
94115
* </p>
95116
* <p>
96117
* This option may be slightly less performant than when using
97-
* {@link RequireComparator#withDefaultJavaComparator()} as it need to call down
118+
* {@link DbiBuilderStage2#withDefaultIteratorComparator()} as it need to call down
98119
* to LMDB to perform the comparisons, however it guarantees that {@link CursorIterable}
99120
* key comparison matches LMDB key comparison.
100121
* </p>
101122
* <p>
102123
* If you do not intend to use {@link CursorIterable} then it doesn't matter whether
103-
* you choose {@link RequireComparator#withNativeComparator()},
104-
* {@link RequireComparator#withDefaultJavaComparator()} or
105-
* {@link RequireComparator#withIteratorComparator(Comparator)} as these comparators will
124+
* you choose {@link DbiBuilderStage2#withNativeComparator()},
125+
* {@link DbiBuilderStage2#withDefaultIteratorComparator()} or
126+
* {@link DbiBuilderStage2#withIteratorComparator(Comparator)} as these comparators will
106127
* never be used.
107128
* </p>
108129
*
109-
* @return this builder instance.
130+
* @return The next builder stage.
110131
*/
111-
public FinalStage<T> withNativeComparator() {
132+
public DbiBuilderStage3<T> withNativeComparator() {
112133
this.comparator = null;
113134
this.useNativeCallback = false;
114-
return new FinalStage<>(this);
135+
return new DbiBuilderStage3<>(this);
115136
}
116137

117138
/**
@@ -121,23 +142,23 @@ public FinalStage<T> withNativeComparator() {
121142
* </p>
122143
* <p>
123144
* This option may be slightly more performant than when using
124-
* {@link RequireComparator#withNativeComparator()} but it relies on the default comparator
145+
* {@link DbiBuilderStage2#withNativeComparator()} but it relies on the default comparator
125146
* in LmdbJava behaving identically to the comparator in LMDB.
126147
* </p>
127148
* <p>
128149
* If you do not intend to use {@link CursorIterable} then it doesn't matter whether
129-
* you choose {@link RequireComparator#withNativeComparator()},
130-
* {@link RequireComparator#withDefaultJavaComparator()} or
131-
* {@link RequireComparator#withIteratorComparator(Comparator)} as these comparators will
150+
* you choose {@link DbiBuilderStage2#withNativeComparator()},
151+
* {@link DbiBuilderStage2#withDefaultIteratorComparator()} or
152+
* {@link DbiBuilderStage2#withIteratorComparator(Comparator)} as these comparators will
132153
* never be used.
133154
* </p>
134155
*
135-
* @return this builder instance.
156+
* @return The next builder stage.
136157
*/
137-
public FinalStage<T> withDefaultJavaComparator() {
158+
public DbiBuilderStage3<T> withDefaultIteratorComparator() {
138159
this.comparator = dbiBuilder.proxy.getUnsignedComparator();
139160
this.useNativeCallback = false;
140-
return new FinalStage<>(this);
161+
return new DbiBuilderStage3<>(this);
141162
}
142163

143164
/**
@@ -151,12 +172,12 @@ public FinalStage<T> withDefaultJavaComparator() {
151172
* </p>
152173
*
153174
* @param comparator for all key comparison operations.
154-
* @return this builder instance.
175+
* @return The next builder stage.
155176
*/
156-
public FinalStage<T> withCallbackComparator(final Comparator<T> comparator) {
177+
public DbiBuilderStage3<T> withCallbackIteratorComparator(final Comparator<T> comparator) {
157178
this.comparator = Objects.requireNonNull(comparator);
158179
this.useNativeCallback = true;
159-
return new FinalStage<>(this);
180+
return new DbiBuilderStage3<>(this);
160181
}
161182

162183
/**
@@ -171,26 +192,23 @@ public FinalStage<T> withCallbackComparator(final Comparator<T> comparator) {
171192
* differently to the comparator in LMDB that controls the insert/iteration order.
172193
* </p>
173194
* <p>
174-
* This option may be slightly less performant than when using
175-
* {@link RequireComparator#withDefaultJavaComparator()} as it need to call down
176-
* to LMDB to perform the comparisons, however it guarantees that {@link CursorIterable}
177-
* key comparison matches LMDB key comparison.
195+
* The supplied {@link Comparator} should match the behaviour of LMDB's mdb_cmp comparator.
178196
* </p>
179197
* <p>
180198
* If you do not intend to use {@link CursorIterable} then it doesn't matter whether
181-
* you choose {@link RequireComparator#withNativeComparator()},
182-
* {@link RequireComparator#withDefaultJavaComparator()} or
183-
* {@link RequireComparator#withIteratorComparator(Comparator)} as these comparators will
199+
* you choose {@link DbiBuilderStage2#withNativeComparator()},
200+
* {@link DbiBuilderStage2#withDefaultIteratorComparator()} or
201+
* {@link DbiBuilderStage2#withIteratorComparator(Comparator)} as these comparators will
184202
* never be used.
185203
* </p>
186204
*
187205
* @param comparator The comparator to use with {@link CursorIterable}.
188-
* @return this builder instance.
206+
* @return The next builder stage.
189207
*/
190-
public FinalStage<T> withIteratorComparator(final Comparator<T> comparator) {
208+
public DbiBuilderStage3<T> withIteratorComparator(final Comparator<T> comparator) {
191209
this.comparator = Objects.requireNonNull(comparator);
192210
this.useNativeCallback = false;
193-
return new FinalStage<>(this);
211+
return new DbiBuilderStage3<>(this);
194212
}
195213
}
196214

@@ -203,14 +221,14 @@ public FinalStage<T> withIteratorComparator(final Comparator<T> comparator) {
203221
*
204222
* @param <T> buffer type
205223
*/
206-
public static class FinalStage<T> {
224+
public static class DbiBuilderStage3<T> {
207225

208-
private final RequireComparator<T> requireComparator;
226+
private final DbiBuilderStage2<T> dbiBuilderStage2;
209227
private Set<DbiFlags> dbiFlags = null;
210228
private Txn<T> txn = null;
211229

212-
private FinalStage(RequireComparator<T> requireComparator) {
213-
this.requireComparator = requireComparator;
230+
private DbiBuilderStage3(DbiBuilderStage2<T> dbiBuilderStage2) {
231+
this.dbiBuilderStage2 = dbiBuilderStage2;
214232
}
215233

216234
private void initDbiFlags() {
@@ -225,16 +243,18 @@ private void initDbiFlags() {
225243
* </p>
226244
* <p>
227245
* Replaces any flags applies in previous calls to
228-
* {@link FinalStage#withDbiFlags(Collection)}, {@link FinalStage#withDbiFlags(DbiFlags...)}
229-
* or {@link FinalStage#addDbiFlag(DbiFlags)}.
246+
* {@link DbiBuilderStage3#withDbiFlags(Collection)}, {@link DbiBuilderStage3#withDbiFlags(DbiFlags...)}
247+
* or {@link DbiBuilderStage3#addDbiFlag(DbiFlags)}.
230248
* </p>
231249
*
232250
* @param dbiFlags to open the database with.
233251
*/
234-
public FinalStage<T> withDbiFlags(final Collection<DbiFlags> dbiFlags) {
252+
public DbiBuilderStage3<T> withDbiFlags(final Collection<DbiFlags> dbiFlags) {
235253
initDbiFlags();
236254
if (dbiFlags != null) {
237-
this.dbiFlags.addAll(dbiFlags);
255+
this.dbiFlags.stream()
256+
.filter(Objects::nonNull)
257+
.forEach(dbiFlags::add);
238258
}
239259
return this;
240260
}
@@ -245,13 +265,14 @@ public FinalStage<T> withDbiFlags(final Collection<DbiFlags> dbiFlags) {
245265
* </p>
246266
* <p>
247267
* Replaces any flags applies in previous calls to
248-
* {@link FinalStage#withDbiFlags(Collection)}, {@link FinalStage#withDbiFlags(DbiFlags...)}
249-
* or {@link FinalStage#addDbiFlag(DbiFlags)}.
268+
* {@link DbiBuilderStage3#withDbiFlags(Collection)}, {@link DbiBuilderStage3#withDbiFlags(DbiFlags...)}
269+
* or {@link DbiBuilderStage3#addDbiFlag(DbiFlags)}.
250270
* </p>
251271
*
252272
* @param dbiFlags to open the database with.
273+
* A null array is a no-op. Null items are ignored.
253274
*/
254-
public FinalStage<T> withDbiFlags(final DbiFlags... dbiFlags) {
275+
public DbiBuilderStage3<T> withDbiFlags(final DbiFlags... dbiFlags) {
255276
initDbiFlags();
256277
if (dbiFlags != null) {
257278
Arrays.stream(dbiFlags)
@@ -262,12 +283,14 @@ public FinalStage<T> withDbiFlags(final DbiFlags... dbiFlags) {
262283
}
263284

264285
/**
265-
* Adds dbiFlag to those flags already added to this builder.
286+
* Adds dbiFlag to those flags already added to this builder by
287+
* {@link DbiBuilderStage3#withDbiFlags(DbiFlags...)}, {@link DbiBuilderStage3#withDbiFlags(Collection)}
288+
* or {@link DbiBuilderStage3#addDbiFlag(DbiFlags)}.
266289
*
267-
* @param dbiFlag to open the database with.
290+
* @param dbiFlag to open the database with. A null value is a no-op.
268291
* @return this builder instance.
269292
*/
270-
public FinalStage<T> addDbiFlag(final DbiFlags dbiFlag) {
293+
public DbiBuilderStage3<T> addDbiFlag(final DbiFlags dbiFlag) {
271294
initDbiFlags();
272295
if (dbiFlags != null) {
273296
this.dbiFlags.add(dbiFlag);
@@ -278,14 +301,14 @@ public FinalStage<T> addDbiFlag(final DbiFlags dbiFlag) {
278301
/**
279302
* Use the supplied transaction to open the {@link Dbi}.
280303
* <p>
281-
* The caller must commit the transaction after calling {@link FinalStage#open()}
304+
* The caller MUST commit the transaction after calling {@link DbiBuilderStage3#open()},
282305
* in order to retain the <code>Dbi</code> in the <code>Env</code>.
283306
* </p>
284307
*
285308
* @param txn transaction to use (required; not closed)
286309
* @return this builder instance.
287310
*/
288-
public FinalStage<T> withTxn(final Txn<T> txn) {
311+
public DbiBuilderStage3<T> withTxn(final Txn<T> txn) {
289312
this.txn = Objects.requireNonNull(txn);
290313
return this;
291314
}
@@ -300,7 +323,7 @@ public FinalStage<T> withTxn(final Txn<T> txn) {
300323
* @return A newly constructed and opened {@link Dbi}.
301324
*/
302325
public Dbi<T> open() {
303-
final DbiBuilder<T> dbiBuilder = requireComparator.dbiBuilder;
326+
final DbiBuilder<T> dbiBuilder = dbiBuilderStage2.dbiBuilder;
304327
if (txn == null) {
305328
try (final Txn<T> txn = getTxn(dbiBuilder)) {
306329
return open(txn, dbiBuilder);
@@ -326,8 +349,8 @@ private Dbi<T> open(final Txn<T> txn,
326349
dbiBuilder.env,
327350
txn,
328351
dbiBuilder.name,
329-
requireComparator.comparator,
330-
requireComparator.useNativeCallback,
352+
dbiBuilderStage2.comparator,
353+
dbiBuilderStage2.useNativeCallback,
331354
dbiBuilder.proxy,
332355
dbiFlagsArr);
333356
}

src/main/java/org/lmdbjava/RangeComparator.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
/*
2+
* Copyright © 2016-2025 The LmdbJava Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
116
package org.lmdbjava;
217

318
/** For comparing a cursor's current key against a {@link KeyRange}'s start/stop key. */

src/test/java/org/lmdbjava/ComparatorTest.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,12 @@ public static Object[] data() {
6767
final ComparatorRunner string = new StringRunner();
6868
final ComparatorRunner db = new DirectBufferRunner();
6969
final ComparatorRunner ba = new ByteArrayRunner();
70+
final ComparatorRunner baUnsigned = new UnsignedByteArrayRunner();
7071
final ComparatorRunner bb = new ByteBufferRunner();
7172
final ComparatorRunner netty = new NettyRunner();
7273
final ComparatorRunner gub = new GuavaUnsignedBytes();
7374
final ComparatorRunner gsb = new GuavaSignedBytes();
74-
return new Object[] {string, db, ba, bb, netty, gub, gsb};
75+
return new Object[] {string, db, ba, baUnsigned, bb, netty, gub, gsb};
7576
}
7677

7778
private static byte[] buffer(final int... bytes) {
@@ -140,6 +141,16 @@ public int compare(final byte[] o1, final byte[] o2) {
140141
}
141142
}
142143

144+
/** Tests {@link ByteArrayProxy} (unsigned). */
145+
private static final class UnsignedByteArrayRunner implements ComparatorRunner {
146+
147+
@Override
148+
public int compare(final byte[] o1, final byte[] o2) {
149+
final Comparator<byte[]> c = PROXY_BA.getUnsignedComparator();
150+
return c.compare(o1, o2);
151+
}
152+
}
153+
143154
/** Tests {@link ByteBufferProxy}. */
144155
private static final class ByteBufferRunner implements ComparatorRunner {
145156

0 commit comments

Comments
 (0)