Skip to content

Commit 4d6a2a7

Browse files
committed
Dbi and CursorIterator changes to use KeyRange (#7)
Existing code should continue to work without modification, as confirmed by the unmodified CursorIteratorTest.
1 parent aa08abd commit 4d6a2a7

File tree

3 files changed

+146
-65
lines changed

3 files changed

+146
-65
lines changed

src/main/java/org/lmdbjava/CursorIterator.java

Lines changed: 89 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -20,35 +20,41 @@
2020

2121
package org.lmdbjava;
2222

23+
import java.util.Comparator;
2324
import java.util.Iterator;
2425
import java.util.NoSuchElementException;
25-
import static org.lmdbjava.CursorIterator.IteratorType.FORWARD;
26-
import static org.lmdbjava.CursorIterator.State.DONE;
27-
import static org.lmdbjava.CursorIterator.State.NOT_READY;
28-
import static org.lmdbjava.CursorIterator.State.READY;
26+
import static org.lmdbjava.CursorIterator.State.RELEASED;
27+
import static org.lmdbjava.CursorIterator.State.REQUIRES_INITIAL_OP;
28+
import static org.lmdbjava.CursorIterator.State.REQUIRES_ITERATOR_OP;
29+
import static org.lmdbjava.CursorIterator.State.REQUIRES_NEXT_OP;
30+
import static org.lmdbjava.CursorIterator.State.TERMINATED;
2931
import static org.lmdbjava.GetOp.MDB_SET_RANGE;
32+
import org.lmdbjava.KeyRange.CursorOp;
33+
import org.lmdbjava.KeyRange.IteratorOp;
3034

3135
/**
32-
* Iterator for entries that follow the same semantics as Cursors with regards
33-
* to read and write transactions and how they are closed.
36+
* {@link Iterator} that iterates over a {@link Cursor} as specified by a
37+
* {@link KeyRange}.
38+
*
39+
* <p>
40+
* An instance will create and close its own cursor.
3441
*
3542
* @param <T> buffer type
3643
*/
3744
public final class CursorIterator<T> implements
38-
Iterator<CursorIterator.KeyVal<T>>,
39-
AutoCloseable {
45+
Iterator<CursorIterator.KeyVal<T>>, AutoCloseable {
4046

47+
private final Comparator<T> comparator;
4148
private final Cursor<T> cursor;
4249
private final KeyVal<T> entry;
43-
private boolean first = true;
44-
private final T key;
45-
private State state = NOT_READY;
46-
private final IteratorType type;
47-
48-
CursorIterator(final Cursor<T> cursor, final T key, final IteratorType type) {
49-
this.cursor = cursor;
50-
this.type = type;
51-
this.key = key;
50+
private final KeyRange<T> range;
51+
private State state = REQUIRES_INITIAL_OP;
52+
53+
CursorIterator(final Txn<T> txn, final Dbi<T> dbi, final KeyRange<T> range,
54+
final Comparator<T> comparator) {
55+
this.cursor = dbi.openCursor(txn);
56+
this.range = range;
57+
this.comparator = comparator;
5258
this.entry = new KeyVal<>();
5359
}
5460

@@ -60,14 +66,10 @@ public void close() {
6066
@Override
6167
@SuppressWarnings("checkstyle:returncount")
6268
public boolean hasNext() {
63-
switch (state) {
64-
case DONE:
65-
return false;
66-
case READY:
67-
return true;
68-
default:
69+
while (state != RELEASED && state != TERMINATED) {
70+
update();
6971
}
70-
return tryToComputeNext();
72+
return state == RELEASED;
7173
}
7274

7375
/**
@@ -84,7 +86,7 @@ public KeyVal<T> next() throws NoSuchElementException {
8486
if (!hasNext()) {
8587
throw new NoSuchElementException();
8688
}
87-
state = NOT_READY;
89+
state = REQUIRES_NEXT_OP;
8890
return entry;
8991
}
9092

@@ -93,44 +95,67 @@ public void remove() {
9395
throw new UnsupportedOperationException();
9496
}
9597

96-
private void setEntry(final boolean success) {
97-
if (success) {
98-
this.entry.setKey(cursor.key());
99-
this.entry.setVal(cursor.val());
100-
} else {
101-
this.entry.setKey(null);
102-
this.entry.setVal(null);
98+
private void executeCursorOp(final CursorOp op) {
99+
final boolean found;
100+
switch (op) {
101+
case FIRST:
102+
found = cursor.first();
103+
break;
104+
case LAST:
105+
found = cursor.last();
106+
break;
107+
case NEXT:
108+
found = cursor.next();
109+
break;
110+
case PREV:
111+
found = cursor.prev();
112+
break;
113+
case GET_START_KEY:
114+
found = cursor.get(range.getStart(), MDB_SET_RANGE);
115+
break;
116+
default:
117+
throw new IllegalStateException("Unknown cursor operation");
103118
}
119+
entry.setK(found ? cursor.key() : null);
120+
entry.setV(found ? cursor.val() : null);
104121
}
105122

106-
@SuppressWarnings("checkstyle:returncount")
107-
private boolean tryToComputeNext() {
108-
if (first) {
109-
if (key != null) { // NOPMD
110-
setEntry(cursor.get(key, MDB_SET_RANGE));
111-
} else if (type == FORWARD) {
112-
setEntry(cursor.first());
113-
} else {
114-
setEntry(cursor.last());
115-
}
116-
first = false;
117-
if (entry.isEmpty()) {
118-
state = DONE;
119-
return false;
120-
}
121-
} else {
122-
if (type == FORWARD) {
123-
setEntry(cursor.next());
124-
} else {
125-
setEntry(cursor.prev());
126-
}
127-
if (entry.isEmpty()) {
128-
state = DONE;
129-
return false;
130-
}
123+
private void executeIteratorOp() {
124+
final IteratorOp op = range.iteratorOp(comparator, entry.key());
125+
switch (op) {
126+
case CALL_NEXT_OP:
127+
executeCursorOp(range.nextOp());
128+
state = REQUIRES_ITERATOR_OP;
129+
break;
130+
case TERMINATE:
131+
state = TERMINATED;
132+
break;
133+
case RELEASE:
134+
state = RELEASED;
135+
break;
136+
default:
137+
throw new IllegalStateException("Unknown operation");
138+
}
139+
}
140+
141+
private void update() {
142+
switch (state) {
143+
case REQUIRES_INITIAL_OP:
144+
executeCursorOp(range.initialOp());
145+
state = REQUIRES_ITERATOR_OP;
146+
break;
147+
case REQUIRES_NEXT_OP:
148+
executeCursorOp(range.nextOp());
149+
state = REQUIRES_ITERATOR_OP;
150+
break;
151+
case REQUIRES_ITERATOR_OP:
152+
executeIteratorOp();
153+
break;
154+
case TERMINATED:
155+
break;
156+
default:
157+
throw new IllegalStateException("Unknown state");
131158
}
132-
state = READY;
133-
return true;
134159
}
135160

136161
/**
@@ -174,13 +199,14 @@ void setV(final T val) {
174199
this.v = val;
175200
}
176201

177-
boolean isEmpty() {
178-
return this.k == null && this.v == null;
179202
}
180203

181204
/**
182205
* Direction in terms of key ordering for CursorIterator.
206+
*
207+
* @deprecated use {@link KeyRange} instead
183208
*/
209+
@Deprecated
184210
public enum IteratorType {
185211
/**
186212
* Move forward.
@@ -196,7 +222,8 @@ public enum IteratorType {
196222
* Represents the internal {@link CursorIterator} state.
197223
*/
198224
enum State {
199-
READY, NOT_READY, DONE, FAILED,
225+
REQUIRES_INITIAL_OP, REQUIRES_NEXT_OP, REQUIRES_ITERATOR_OP, RELEASED,
226+
TERMINATED
200227
}
201228

202229
}

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

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
package org.lmdbjava;
2222

2323
import java.util.Arrays;
24+
import java.util.Comparator;
2425
import static java.util.Objects.requireNonNull;
2526
import static jnr.ffi.Memory.allocateDirect;
2627
import static jnr.ffi.NativeType.ADDRESS;
@@ -210,7 +211,7 @@ public byte[] getName() {
210211
* @return iterator
211212
*/
212213
public CursorIterator<T> iterate(final Txn<T> txn) {
213-
return iterate(txn, null, FORWARD);
214+
return iterate(txn, KeyRange.forward());
214215
}
215216

216217
/**
@@ -219,9 +220,16 @@ public CursorIterator<T> iterate(final Txn<T> txn) {
219220
* @param txn transaction handle (not null; not committed)
220221
* @param type direction of iterator (not null)
221222
* @return iterator (never null)
223+
* @deprecated use iterate method with a {@link KeyRange} instead
222224
*/
225+
@Deprecated
223226
public CursorIterator<T> iterate(final Txn<T> txn, final IteratorType type) {
224-
return iterate(txn, null, type);
227+
if (SHOULD_CHECK) {
228+
requireNonNull(type);
229+
}
230+
final KeyRange<T> range = type == FORWARD
231+
? KeyRange.forward() : KeyRange.backward();
232+
return iterate(txn, range);
225233
}
226234

227235
/**
@@ -232,14 +240,55 @@ public CursorIterator<T> iterate(final Txn<T> txn, final IteratorType type) {
232240
* @param key the key to search from (may be null to denote first record)
233241
* @param type direction of iterator (not null)
234242
* @return iterator (never null)
243+
* @deprecated use iterate method with a {@link KeyRange} instead
235244
*/
245+
@Deprecated
236246
public CursorIterator<T> iterate(final Txn<T> txn, final T key,
237247
final IteratorType type) {
248+
if (SHOULD_CHECK) {
249+
requireNonNull(type);
250+
}
251+
252+
final KeyRange<T> range;
253+
if (type == FORWARD) {
254+
range = key == null ? KeyRange.forward() : KeyRange.atLeast(key);
255+
} else {
256+
range = key == null ? KeyRange.backward() : KeyRange.atLeastBackward(key);
257+
}
258+
259+
return iterate(txn, range);
260+
}
261+
262+
/**
263+
* Iterate the database in accordance with the provided {@link KeyRange} and
264+
* default {@link Comparator} for this buffer type.
265+
*
266+
* @param txn transaction handle (not null; not committed)
267+
* @param range range of acceptable keys (not null)
268+
* @return iterator (never null)
269+
*/
270+
public CursorIterator<T> iterate(final Txn<T> txn, final KeyRange<T> range) {
271+
return iterate(txn, range, txn.comparator());
272+
}
273+
274+
/**
275+
* Iterate the database in accordance with the provided {@link KeyRange} and
276+
* {@link Comparator}.
277+
*
278+
* @param txn transaction handle (not null; not committed)
279+
* @param range range of acceptable keys (not null)
280+
* @param comparator custom comparator for keys (not null)
281+
* @return iterator (never null)
282+
*/
283+
public CursorIterator<T> iterate(final Txn<T> txn, final KeyRange<T> range,
284+
final Comparator<T> comparator) {
238285
if (SHOULD_CHECK) {
239286
requireNonNull(txn);
287+
requireNonNull(range);
288+
requireNonNull(comparator);
240289
txn.checkReady();
241290
}
242-
return new CursorIterator<>(openCursor(txn), key, type);
291+
return new CursorIterator<>(txn, this, range, comparator);
243292
}
244293

245294
/**

src/main/java/org/lmdbjava/Txn.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
package org.lmdbjava;
2222

23+
import java.util.Comparator;
2324
import static jnr.ffi.Memory.allocateDirect;
2425
import static jnr.ffi.NativeType.ADDRESS;
2526
import jnr.ffi.Pointer;
@@ -201,6 +202,10 @@ void checkWritesAllowed() throws ReadWriteRequiredException {
201202
}
202203
}
203204

205+
Comparator<T> comparator() {
206+
return proxy::compare;
207+
}
208+
204209
/**
205210
* Return the state of the transaction.
206211
*

0 commit comments

Comments
 (0)