Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/main/java/org/lmdbjava/ByteArrayProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,11 @@ byte[] incrementLeastSignificantByte(final byte[] buffer) {

// Check if byte is not at max unsigned value (0xFF = 255 = -1 in signed)
if (b != (byte) 0xFF) {
final byte[] oneBigger = new byte[buffer.length];
System.arraycopy(buffer, 0, oneBigger, 0, buffer.length);
// Copy up to and including index i, dropping any trailing 0xFF bytes, then increment.
// This yields the tight prefix successor (e.g. {0x01,0xFF} -> {0x02}, not {0x02,0xFF}),
// so a reverse prefix scan does not over-shoot onto an unrelated higher key.
final byte[] oneBigger = new byte[i + 1];
System.arraycopy(buffer, 0, oneBigger, 0, i + 1);
oneBigger[i] = (byte) (b + 1);
return oneBigger;
}
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/org/lmdbjava/ByteBufProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,9 @@ ByteBuf incrementLeastSignificantByte(final ByteBuf buffer) {

// Check if byte is not at max unsigned value (0xFF = 255 = -1 in signed)
if (b != (byte) 0xFF) {
final ByteBuf oneBigger = buffer.copy();
// Copy up to and including index i, dropping any trailing 0xFF bytes, then increment.
// This yields the tight prefix successor (e.g. {0x01,0xFF} -> {0x02}, not {0x02,0xFF}).
final ByteBuf oneBigger = buffer.copy(0, i + 1);
oneBigger.setByte(i, (byte) (b + 1));
return oneBigger;
}
Expand Down
11 changes: 8 additions & 3 deletions src/main/java/org/lmdbjava/ByteBufferProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -322,10 +322,15 @@ ByteBuffer incrementLeastSignificantByte(final ByteBuffer buffer) {

// Check if byte is not at max unsigned value (0xFF = 255 = -1 in signed)
if (b != (byte) 0xFF) {
final ByteBuffer oneBigger = ByteBuffer.allocateDirect(buffer.remaining());
oneBigger.put(buffer.duplicate());
// Copy up to and including index i, dropping any trailing 0xFF bytes, then increment.
// This yields the tight prefix successor (e.g. {0x01,0xFF} -> {0x02}, not {0x02,0xFF}).
final int len = i - buffer.position() + 1;
final ByteBuffer src = buffer.duplicate();
src.limit(i + 1);
final ByteBuffer oneBigger = ByteBuffer.allocateDirect(len);
oneBigger.put(src);
oneBigger.flip();
oneBigger.put(i - buffer.position(), (byte) (b + 1));
oneBigger.put(len - 1, (byte) (b + 1));
return oneBigger;
}
}
Expand Down
56 changes: 47 additions & 9 deletions src/main/java/org/lmdbjava/Dbi.java
Original file line number Diff line number Diff line change
Expand Up @@ -290,22 +290,17 @@ public String getNameAsString() {
* Obtains the name of this database, using the supplied {@link Charset}.
*
* @param charset The {@link Charset} to use when converting the DB from a byte[] to a {@link
* String}.
* String} (not null). Unmappable bytes are replaced, as per {@link String#String(byte[],
* 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) {
requireNonNull(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);
}
}
return new String(name, charset);
}

private RangeComparator createRangeComparator(
Expand Down Expand Up @@ -353,10 +348,29 @@ public CursorIterable<T> iterate(final Txn<T> txn, final KeyRange<T> range) {
}
}

/**
* Iterate all entries in this database, forwards. See {@link #newIterate(Txn, KeyRange)} for
* important notes on single-use, closing, and the reused entry holder.
*
* @param txn transaction handle (not null; not committed)
* @return a single-use, closeable iterable (never null)
*/
public LmdbIterable<T> newIterate(final Txn<T> txn) {
return newIterate(txn, KeyRange.all());
}

/**
* Iterate the entries in this database over the given {@link KeyRange}.
*
* <p>The returned {@link LmdbIterable} may be iterated only once and holds a cursor open, so it
* MUST be closed (use try-with-resources). As with {@link CursorIterable}, each returned {@link
* CursorIterable.KeyVal} is a single reused holder whose contents change as iteration advances;
* copy the key/value out if you need to retain it beyond the current step.
*
* @param txn transaction handle (not null; not committed)
* @param keyRange range of keys to iterate (not null)
* @return a single-use, closeable iterable (never null)
*/
public LmdbIterable<T> newIterate(final Txn<T> txn, final KeyRange<T> keyRange) {
if (SHOULD_CHECK) {
requireNonNull(txn);
Expand Down Expand Up @@ -393,10 +407,34 @@ public void newIterate(
}
}

/**
* Stream all entries in this database, forwards. See {@link #stream(Txn, KeyRange)} for important
* notes on the reused entry holder and closing the stream.
*
* @param txn transaction handle (not null; not committed)
* @return a closeable stream of key/value holders (never null)
*/
public Stream<CursorIterable.KeyVal<T>> stream(final Txn<T> txn) {
return stream(txn, KeyRange.all());
}

/**
* Stream the entries in this database over the given {@link KeyRange}.
*
* <p><strong>Important:</strong> the stream emits a single, reused {@link CursorIterable.KeyVal}
* holder — every element is the same object, mutated as the cursor advances. This is safe for
* one-at-a-time terminal operations (e.g. {@code forEach}), but any operation that retains or
* buffers more than one element (e.g. {@code collect(toList())}, {@code sorted()}, {@code
* distinct()}) will observe aliased entries. Extract the key/value into your own object within
* the pipeline if you need to retain them. The stream is {@code ORDERED} but not {@code SORTED}.
*
* <p>The returned stream holds a cursor open and MUST be closed; use it in a try-with-resources
* block. Valid only for the life of the passed read {@link Txn}.
*
* @param txn transaction handle (not null; not committed)
* @param keyRange range of keys to stream (not null)
* @return a closeable stream of key/value holders (never null)
*/
public Stream<CursorIterable.KeyVal<T>> stream(final Txn<T> txn, final KeyRange<T> keyRange) {
if (SHOULD_CHECK) {
requireNonNull(txn);
Expand Down
11 changes: 8 additions & 3 deletions src/main/java/org/lmdbjava/DirectBufferProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -268,10 +268,15 @@ DirectBuffer incrementLeastSignificantByte(final DirectBuffer directBuffer) {

// Check if byte is not at max unsigned value (0xFF = 255 = -1 in signed)
if (b != (byte) 0xFF) {
final ByteBuffer oneBigger = ByteBuffer.allocateDirect(buffer.remaining());
oneBigger.put(buffer.duplicate());
// Copy up to and including index i, dropping any trailing 0xFF bytes, then increment.
// This yields the tight prefix successor (e.g. {0x01,0xFF} -> {0x02}, not {0x02,0xFF}).
final int len = i - buffer.position() + 1;
final ByteBuffer src = buffer.duplicate();
src.limit(i + 1);
final ByteBuffer oneBigger = ByteBuffer.allocateDirect(len);
oneBigger.put(src);
oneBigger.flip();
oneBigger.put(i - buffer.position(), (byte) (b + 1));
oneBigger.put(len - 1, (byte) (b + 1));
return new UnsafeBuffer(oneBigger);
}
}
Expand Down
36 changes: 36 additions & 0 deletions src/main/java/org/lmdbjava/KeyRange.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,42 @@ private KeyRange(
this.startKeyInclusive = startKeyInclusive;
this.stopKeyInclusive = stopKeyInclusive;
this.directionForward = directionForward;
// Derive the equivalent KeyRangeType so getType() is never null for builder-created ranges
// (a null type would NPE the legacy CursorIterable path).
this.type = deriveType(start, stop, startKeyInclusive, stopKeyInclusive, directionForward);
}

private static KeyRangeType deriveType(
final Object start,
final Object stop,
final boolean startInclusive,
final boolean stopInclusive,
final boolean forward) {
if (start == null && stop == null) {
return forward ? FORWARD_ALL : BACKWARD_ALL;
}
if (stop == null) {
if (forward) {
return startInclusive ? KeyRangeType.FORWARD_AT_LEAST : KeyRangeType.FORWARD_GREATER_THAN;
}
return startInclusive ? KeyRangeType.BACKWARD_AT_LEAST : KeyRangeType.BACKWARD_GREATER_THAN;
}
if (start == null) {
if (forward) {
return stopInclusive ? KeyRangeType.FORWARD_AT_MOST : KeyRangeType.FORWARD_LESS_THAN;
}
return stopInclusive ? KeyRangeType.BACKWARD_AT_MOST : KeyRangeType.BACKWARD_LESS_THAN;
}
if (forward) {
if (startInclusive) {
return stopInclusive ? KeyRangeType.FORWARD_CLOSED : KeyRangeType.FORWARD_CLOSED_OPEN;
}
return stopInclusive ? KeyRangeType.FORWARD_OPEN_CLOSED : KeyRangeType.FORWARD_OPEN;
}
if (startInclusive) {
return stopInclusive ? KeyRangeType.BACKWARD_CLOSED : KeyRangeType.BACKWARD_CLOSED_OPEN;
}
return stopInclusive ? KeyRangeType.BACKWARD_OPEN_CLOSED : KeyRangeType.BACKWARD_OPEN;
}

private KeyRange(final T prefix) {
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/lmdbjava/LmdbRangeComparator.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

/**
* 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 CursorIterable.JavaRangeComparator}.
* very slight overhead as compared to {@link JavaRangeComparator}.
*/
class LmdbRangeComparator<T> implements RangeComparator {

Expand Down
60 changes: 17 additions & 43 deletions src/main/java/org/lmdbjava/LmdbStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ static <T> LmdbSpliterator<T> createSpliterator(
new LmdbRangeSpliterator<>(
cursor,
rangeComparator,
createEntryComparator(rangeComparator),
keyRange.getStart(),
keyRange.getStop(),
keyRange.isStartKeyInclusive(),
Expand All @@ -72,20 +71,16 @@ static <T> LmdbSpliterator<T> createSpliterator(
new LmdbRangeReversedSpliterator<>(
cursor,
rangeComparator,
createReversedEntryComparator(rangeComparator),
keyRange.getStart(),
keyRange.getStop(),
keyRange.isStartKeyInclusive(),
keyRange.isStopKeyInclusive());
}
} else {
if (keyRange.directionForward) {
spliterator =
new LmdbSpliterator<>(cursor, rangeComparator, createEntryComparator(rangeComparator));
spliterator = new LmdbSpliterator<>(cursor, rangeComparator);
} else {
spliterator =
new LmdbReversedSpliterator<>(
cursor, rangeComparator, createReversedEntryComparator(rangeComparator));
spliterator = new LmdbReversedSpliterator<>(cursor, rangeComparator);
}
}
return spliterator;
Expand All @@ -97,15 +92,10 @@ static class LmdbSpliterator<T> implements Spliterator<KeyVal<T>> {
Boolean isFound;
final KeyVal<T> entry = new KeyVal<>();
final RangeComparator rangeComparator;
final Comparator<KeyVal<T>> entryComparator;

private LmdbSpliterator(
final Cursor<T> cursor,
final RangeComparator rangeComparator,
final Comparator<KeyVal<T>> entryComparator) {
private LmdbSpliterator(final Cursor<T> cursor, final RangeComparator rangeComparator) {
this.cursor = cursor;
this.rangeComparator = rangeComparator;
this.entryComparator = entryComparator;
}

@Override
Expand Down Expand Up @@ -152,34 +142,25 @@ public final long estimateSize() {

@Override
public final int characteristics() {
return Spliterator.ORDERED | Spliterator.DISTINCT | Spliterator.SORTED | Spliterator.NONNULL;
// Entries are produced in the DB's key order, but we do not expose a KeyVal comparator, so we
// must not advertise SORTED: a SORTED spliterator with a null getComparator() implies the
// elements use natural ordering, yet KeyVal does not implement Comparable. DISTINCT is also
// dropped because DUPSORT databases can yield multiple entries sharing the same key.
return Spliterator.ORDERED | Spliterator.NONNULL;
}

@Override
public Comparator<? super KeyVal<T>> getComparator() {
return entryComparator;
// This spliterator does not report SORTED, so per the Spliterator contract getComparator()
// must throw rather than return null.
throw new IllegalStateException("Spliterator is ORDERED but not SORTED");
}
}

private static <T> Comparator<KeyVal<T>> createEntryComparator(
final RangeComparator rangeComparator) {
return null;
// return (o1, o2) -> comparator.compare(o1.key(), o2.key());
}

private static <T> Comparator<KeyVal<T>> createReversedEntryComparator(
final RangeComparator rangeComparator) {
return null;
// return (o1, o2) -> comparator.compare(o1.key(), o2.key());
}

private static class LmdbReversedSpliterator<T> extends LmdbSpliterator<T> {

private LmdbReversedSpliterator(
final Cursor<T> cursor,
final RangeComparator rangeComparator,
final Comparator<KeyVal<T>> entryComparator) {
super(cursor, rangeComparator, entryComparator);
private LmdbReversedSpliterator(final Cursor<T> cursor, final RangeComparator rangeComparator) {
super(cursor, rangeComparator);
}

@Override
Expand All @@ -191,11 +172,6 @@ boolean hasNext() {
}
return isFound;
}

@Override
public Comparator<? super KeyVal<T>> getComparator() {
return entryComparator;
}
}

private static class LmdbRangeSpliterator<T> extends LmdbSpliterator<T> {
Expand All @@ -209,12 +185,11 @@ private static class LmdbRangeSpliterator<T> extends LmdbSpliterator<T> {
private LmdbRangeSpliterator(
final Cursor<T> cursor,
final RangeComparator rangeComparator,
final Comparator<KeyVal<T>> entryComparator,
final T start,
final T stop,
final boolean startInclusive,
final boolean stopInclusive) {
super(cursor, rangeComparator, entryComparator);
super(cursor, rangeComparator);
this.rangeComparator = rangeComparator;
this.start = start;
this.stop = stop;
Expand Down Expand Up @@ -262,12 +237,11 @@ private static class LmdbRangeReversedSpliterator<T> extends LmdbReversedSpliter
private LmdbRangeReversedSpliterator(
final Cursor<T> cursor,
final RangeComparator rangeComparator,
final Comparator<KeyVal<T>> entryComparator,
final T start,
final T stop,
final boolean startInclusive,
final boolean stopInclusive) {
super(cursor, rangeComparator, entryComparator);
super(cursor, rangeComparator);
this.rangeComparator = rangeComparator;
this.start = start;
this.stop = stop;
Expand Down Expand Up @@ -334,7 +308,7 @@ private LmdbPrefixSpliterator(
final RangeComparator rangeComparator,
final BufferProxy<T> proxy,
final T prefix) {
super(cursor, rangeComparator, createEntryComparator(rangeComparator));
super(cursor, rangeComparator);
this.proxy = proxy;
this.prefix = prefix;
}
Expand Down Expand Up @@ -366,7 +340,7 @@ private LmdbPrefixReversedSpliterator(
final RangeComparator rangeComparator,
final BufferProxy<T> proxy,
final T prefix) {
super(cursor, rangeComparator, createReversedEntryComparator(rangeComparator));
super(cursor, rangeComparator);
this.proxy = proxy;
this.prefix = prefix;

Expand Down
Loading