-
Notifications
You must be signed in to change notification settings - Fork 119
Fix bug of scan aggregate index returning empty non-end continuation #3397
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 31 commits
387a648
0fd847e
3ce0259
78bea95
aefa82c
3fe7ba0
1336975
44a5cb9
cb40b88
d82d31c
8d523c1
d015132
22a6e85
f30c3e3
cc9567b
09907fe
8343661
30cf435
1f27b7e
61ef12b
51e3746
c0c8e72
b151ab4
e170e11
5cb1ca0
dcce7df
022dc23
69fcaf1
7fda4f2
def4939
39075d7
3185c91
15fa168
4d065e4
5866b52
bc2f064
68fe061
0f91a76
4c19031
05890a0
2e7c809
4280bdd
04b34a9
3f89e3d
2909eb9
425073d
db08407
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,6 +33,7 @@ | |
| import com.apple.foundationdb.record.KeyRange; | ||
| import com.apple.foundationdb.record.RecordCoreException; | ||
| import com.apple.foundationdb.record.RecordCursorContinuation; | ||
| import com.apple.foundationdb.record.RecordCursorProto; | ||
| import com.apple.foundationdb.record.RecordCursorResult; | ||
| import com.apple.foundationdb.record.ScanProperties; | ||
| import com.apple.foundationdb.record.TupleRange; | ||
|
|
@@ -42,11 +43,13 @@ | |
| import com.apple.foundationdb.subspace.Subspace; | ||
| import com.apple.foundationdb.tuple.Tuple; | ||
| import com.google.protobuf.ByteString; | ||
| import com.google.protobuf.InvalidProtocolBufferException; | ||
| import com.google.protobuf.ZeroCopyByteString; | ||
|
|
||
| import javax.annotation.Nonnull; | ||
| import javax.annotation.Nullable; | ||
| import java.util.Arrays; | ||
| import java.util.Objects; | ||
| import java.util.Optional; | ||
| import java.util.concurrent.CompletableFuture; | ||
|
|
||
|
|
@@ -61,22 +64,26 @@ public abstract class KeyValueCursorBase<K extends KeyValue> extends AsyncIterat | |
| private final int prefixLength; | ||
| @Nonnull | ||
| private final CursorLimitManager limitManager; | ||
| private int valuesLimit; | ||
| private final int valuesLimit; | ||
| // the pointer may be mutated, but the actual array must never be mutated or continuations will break | ||
| @Nullable | ||
| private byte[] lastKey; | ||
| @Nonnull | ||
| private final SerializationMode serializationMode; | ||
|
|
||
| protected KeyValueCursorBase(@Nonnull final FDBRecordContext context, | ||
| @Nonnull final AsyncIterator<K> iterator, | ||
| int prefixLength, | ||
| @Nonnull final CursorLimitManager limitManager, | ||
| int valuesLimit) { | ||
| int valuesLimit, | ||
| @Nonnull final SerializationMode serializationMode) { | ||
| super(context.getExecutor(), iterator); | ||
|
|
||
| this.context = context; | ||
| this.prefixLength = prefixLength; | ||
| this.limitManager = limitManager; | ||
| this.valuesLimit = valuesLimit; | ||
| this.serializationMode = serializationMode; | ||
|
|
||
| context.instrument(FDBStoreTimer.DetailEvents.GET_SCAN_RANGE_RAW_FIRST_CHUNK, iterator.onHasNext()); | ||
| } | ||
|
|
@@ -131,21 +138,23 @@ public RecordCursorResult<K> getNext() { | |
|
|
||
| @Nonnull | ||
| private RecordCursorContinuation continuationHelper() { | ||
| return new Continuation(lastKey, prefixLength); | ||
| return new Continuation(lastKey, prefixLength, serializationMode); | ||
| } | ||
|
|
||
| private static class Continuation implements RecordCursorContinuation { | ||
| public static class Continuation implements RecordCursorContinuation { | ||
| @Nullable | ||
| private final byte[] lastKey; | ||
| private final int prefixLength; | ||
| private final SerializationMode serializationMode; | ||
|
|
||
| public Continuation(@Nullable final byte[] lastKey, final int prefixLength) { | ||
| public Continuation(@Nullable final byte[] lastKey, final int prefixLength, final SerializationMode serializationMode) { | ||
| // Note that doing this without a full copy is dangerous if the array is ever mutated. | ||
| // Currently, this never happens and the only thing that changes is which array lastKey points to. | ||
| // However, if logic in KeyValueCursor or KeyValue changes, this could break continuations. | ||
| // To resolve it, we could resort to doing a full copy here, although that's somewhat expensive. | ||
| this.lastKey = lastKey; | ||
| this.prefixLength = prefixLength; | ||
| this.serializationMode = serializationMode; | ||
| } | ||
|
|
||
| @Override | ||
|
|
@@ -156,21 +165,83 @@ public boolean isEnd() { | |
| @Nonnull | ||
| @Override | ||
| public ByteString toByteString() { | ||
| if (lastKey == null) { | ||
| return ByteString.EMPTY; | ||
| if (serializationMode == SerializationMode.TO_OLD) { | ||
| // lastKey = null when source iterator hit limit that we passed down. | ||
| if (lastKey == null) { | ||
| return ByteString.EMPTY; | ||
| } | ||
| ByteString base = ZeroCopyByteString.wrap(lastKey); | ||
| // when prefixLength == lastKey.length, toByteString() also returns ByteString.EMPTY | ||
| return base.substring(prefixLength, lastKey.length); | ||
| } else { | ||
| return toProto().toByteString(); | ||
| } | ||
| ByteString base = ZeroCopyByteString.wrap(lastKey); | ||
| return base.substring(prefixLength, lastKey.length); | ||
| } | ||
|
|
||
| @Nullable | ||
| @Override | ||
| public byte[] toBytes() { | ||
| if (lastKey == null) { | ||
| return null; | ||
| } | ||
| ByteString byteString = toByteString(); | ||
| return byteString.isEmpty() ? new byte[0] : byteString.toByteArray(); | ||
| } | ||
|
|
||
| @Nullable | ||
| public byte[] getInnerContinuationInBytes() { | ||
| if (lastKey == null) { | ||
| return null; | ||
| } | ||
| return Arrays.copyOfRange(lastKey, prefixLength, lastKey.length); | ||
| } | ||
|
|
||
| @Nonnull | ||
| public ByteString getInnerContinuationInByteString() { | ||
| if (lastKey == null) { | ||
| return ByteString.EMPTY; | ||
| } | ||
| ByteString base = ZeroCopyByteString.wrap(lastKey); | ||
| return base.substring(prefixLength, lastKey.length); | ||
| } | ||
|
|
||
| public static byte[] fromRawBytes(@Nullable byte[] rawBytes, SerializationMode serializationMode) { | ||
| if (rawBytes == null) { | ||
| return null; | ||
| } | ||
| if (serializationMode == SerializationMode.TO_OLD) { | ||
| return rawBytes; | ||
| } | ||
alecgrieser marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| try { | ||
| RecordCursorProto.KeyValueCursorContinuation continuationProto = RecordCursorProto.KeyValueCursorContinuation.parseFrom(rawBytes); | ||
| if (continuationProto.hasPrefixLength()) { | ||
| return continuationProto.getContinuation().toByteArray(); | ||
| } else { | ||
| // parseFrom can parse an old serialization result as the new proto, wrong deserialization | ||
| return rawBytes; | ||
| } | ||
| } catch (InvalidProtocolBufferException ipbe) { | ||
| return rawBytes; | ||
|
||
| } | ||
| } | ||
|
|
||
| @Nonnull | ||
| private RecordCursorProto.KeyValueCursorContinuation toProto() { | ||
| RecordCursorProto.KeyValueCursorContinuation.Builder builder = RecordCursorProto.KeyValueCursorContinuation.newBuilder(); | ||
| if (lastKey == null) { | ||
alecgrieser marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // proto.hasContinuation() = false when lastKey = null | ||
| return builder.setPrefixLength(prefixLength).build(); | ||
| } else { | ||
| ByteString base = ZeroCopyByteString.wrap(Objects.requireNonNull(lastKey)); | ||
| // proto.hasContinuation() = ByteString.EMPTY when prefixLength = lastKey.length | ||
|
||
| return builder.setContinuation(base.substring(prefixLength, lastKey.length)).setPrefixLength(prefixLength).build(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public enum SerializationMode { | ||
| TO_OLD, | ||
| TO_NEW | ||
alecgrieser marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| /** | ||
|
|
@@ -208,9 +279,11 @@ public abstract static class Builder<T extends Builder<T>> { | |
| private StreamingMode streamingMode; | ||
| private KeySelector begin; | ||
| private KeySelector end; | ||
| protected SerializationMode serializationMode; | ||
alecgrieser marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| protected Builder(@Nonnull Subspace subspace) { | ||
| this.subspace = subspace; | ||
| this.serializationMode = SerializationMode.TO_OLD; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -247,10 +320,12 @@ protected void prepare() { | |
| prefixLength = calculatePrefixLength(); | ||
|
|
||
| reverse = scanProperties.isReverse(); | ||
|
|
||
| if (continuation != null) { | ||
| final byte[] continuationBytes = new byte[prefixLength + continuation.length]; | ||
| byte[] realContinuation = KeyValueCursorBase.Continuation.fromRawBytes(continuation, serializationMode); | ||
| final byte[] continuationBytes = new byte[prefixLength + realContinuation.length]; | ||
| System.arraycopy(lowBytes, 0, continuationBytes, 0, prefixLength); | ||
| System.arraycopy(continuation, 0, continuationBytes, prefixLength, continuation.length); | ||
| System.arraycopy(realContinuation, 0, continuationBytes, prefixLength, realContinuation.length); | ||
| if (reverse) { | ||
| highBytes = continuationBytes; | ||
| highEndpoint = EndpointType.CONTINUATION; | ||
|
|
@@ -334,6 +409,11 @@ public T setHigh(@Nonnull byte[] highBytes, @Nonnull EndpointType highEndpoint) | |
| return self(); | ||
| } | ||
|
|
||
| public T setSerializationMode(@Nonnull final SerializationMode serializationMode) { | ||
| this.serializationMode = serializationMode; | ||
| return self(); | ||
| } | ||
|
|
||
| /** | ||
| * Calculate the key prefix length for the returned values. This will be used to derive the primary key used in | ||
| * the calculated continuation. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.