Skip to content

Commit 46a79d1

Browse files
committed
Resolves #1076: [bug] Continuation unused in scanIndex with EndpointType.PREFIX_STRING
1 parent e1c387d commit 46a79d1

File tree

2 files changed

+75
-0
lines changed

2 files changed

+75
-0
lines changed

fdb-record-layer-core/src/main/java/com/apple/foundationdb/record/provider/foundationdb/KeyValueCursorBase.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,11 @@ protected void prepare() {
246246
// left (inclusive) and another on the right (exclusive).
247247
prefixLength = calculatePrefixLength();
248248

249+
// When both endpoints are prefix strings, we should strip off the last byte \x00 from Tuple
250+
if (lowEndpoint == EndpointType.PREFIX_STRING && highEndpoint == EndpointType.PREFIX_STRING) {
251+
prefixLength--;
252+
}
253+
249254
reverse = scanProperties.isReverse();
250255
if (continuation != null) {
251256
final byte[] continuationBytes = new byte[prefixLength + continuation.length];
@@ -260,6 +265,7 @@ protected void prepare() {
260265
}
261266
}
262267
final Range byteRange = TupleRange.toRange(lowBytes, highBytes, lowEndpoint, highEndpoint);
268+
System.out.println("Range: " + byteRange);
263269
lowBytes = byteRange.begin;
264270
highBytes = byteRange.end;
265271

fdb-record-layer-core/src/test/java/com/apple/foundationdb/record/provider/foundationdb/KeyValueCursorTest.java

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,75 @@ public void inclusiveRange() {
198198
});
199199
}
200200

201+
private KeyValueCursor scanPrefixString(FDBRecordContext context, Tuple prefixRange, ScanProperties scanProperties, byte[] continuation) {
202+
TupleRange range = new TupleRange(
203+
prefixRange,
204+
prefixRange,
205+
EndpointType.PREFIX_STRING,
206+
EndpointType.PREFIX_STRING
207+
);
208+
KeyValueCursor cursor = KeyValueCursor.Builder.withSubspace(subspace)
209+
.setContext(context)
210+
.setRange(range)
211+
.setContinuation(continuation)
212+
.setScanProperties(scanProperties)
213+
.build();
214+
return cursor;
215+
}
216+
217+
private void assertKeyValue(KeyValue kv, Tuple key, Tuple value) {
218+
assertArrayEquals(subspace.pack(key), kv.getKey());
219+
assertArrayEquals(value.pack(), kv.getValue());
220+
}
221+
222+
@Test
223+
public void prefixString() {
224+
// Populate data
225+
fdb.database().run(tr -> {
226+
for (int i = 0; i < 5; i++) {
227+
tr.set(subspace.pack(Tuple.from("apple", i)), Tuple.from("apple", i).pack());
228+
tr.set(subspace.pack(Tuple.from("banana", i)), Tuple.from("banana", i).pack());
229+
tr.set(subspace.pack(Tuple.from("app", i)), Tuple.from("app", i).pack());
230+
tr.set(subspace.pack(Tuple.from("a", i)), Tuple.from("a", i).pack());
231+
}
232+
return null;
233+
});
234+
fdb.run(context -> {
235+
// Scan by empty prefix string, limit 10 rows
236+
KeyValueCursor cursor = scanPrefixString(context, Tuple.from(""), new ScanProperties(ExecuteProperties.newBuilder().setReturnedRowLimit(10).build()), null);
237+
for (int i = 0; i < 5; i++) {
238+
assertKeyValue(cursor.getNext().get(), Tuple.from("a", i), Tuple.from("a", i));
239+
}
240+
for (int i = 0; i < 5; i++) {
241+
assertKeyValue(cursor.getNext().get(), Tuple.from("app", i), Tuple.from("app", i));
242+
}
243+
// Continue scan next 10 rows
244+
cursor = scanPrefixString(context, Tuple.from(""), new ScanProperties(ExecuteProperties.newBuilder().setReturnedRowLimit(10).build()), cursor.getNext().getContinuation().toBytes());
245+
for (int i = 0; i < 5; i++) {
246+
assertKeyValue(cursor.getNext().get(), Tuple.from("apple", i), Tuple.from("apple", i));
247+
}
248+
for (int i = 0; i < 5; i++) {
249+
assertKeyValue(cursor.getNext().get(), Tuple.from("banana", i), Tuple.from("banana", i));
250+
}
251+
// Reverse scan with "app" prefix
252+
cursor = scanPrefixString(context, Tuple.from("app"), new ScanProperties(ExecuteProperties.newBuilder().setReturnedRowLimit(5).build(), true), null);
253+
for (int i = 4; i >= 0; i--) {
254+
assertKeyValue(cursor.getNext().get(), Tuple.from("apple", i), Tuple.from("apple", i));
255+
}
256+
cursor = scanPrefixString(context, Tuple.from("app"), ScanProperties.REVERSE_SCAN, cursor.getNext().getContinuation().toBytes());
257+
for (int i = 4; i >= 0; i--) {
258+
assertKeyValue(cursor.getNext().get(), Tuple.from("app", i), Tuple.from("app", i));
259+
}
260+
// Scan with null character
261+
cursor = scanPrefixString(context, Tuple.from("a\0"), ScanProperties.FORWARD_SCAN, null);
262+
assertEquals(0, cursor.getCount().join());
263+
cursor = scanPrefixString(context, Tuple.from("ap\0le"), ScanProperties.FORWARD_SCAN, null);
264+
assertEquals(0, cursor.getCount().join());
265+
266+
return null;
267+
});
268+
}
269+
201270
@Test
202271
public void exclusiveRange() {
203272
fdb.run(context -> {

0 commit comments

Comments
 (0)