use of com.apple.foundationdb.record.RecordCursorContinuation in project fdb-record-layer by FoundationDB.
the class MapPipelinedCursor method tryToFillPipeline.
/**
* Take items from inner cursor and put in pipeline until no more or a mapping result is available.
* @return a future that will complete with {@code false} if an item is available or none will ever be, or with {@code true} if this method should be called to try again
*/
protected CompletableFuture<Boolean> tryToFillPipeline() {
while (!innerExhausted && pipeline.size() < pipelineSize) {
// try to add a future to the pipeline
if (waitInnerFuture == null) {
waitInnerFuture = inner.onNext();
}
if (!waitInnerFuture.isDone()) {
// still waiting for inner future, check back once something has finished
CompletableFuture<RecordCursorResult<V>> nextEntry = pipeline.peek();
if (nextEntry == null) {
// loop back to process inner result
return waitInnerFuture.thenApply(vignore -> true);
} else {
// keep looping unless the next entry is done
return CompletableFuture.anyOf(waitInnerFuture, nextEntry).thenApply(vignore -> !nextEntry.isDone());
}
}
// future is ready, doesn't block
final RecordCursorResult<T> innerResult = waitInnerFuture.join();
pipeline.add(innerResult.mapAsync(func));
if (innerResult.hasNext()) {
// just added something to the pipeline, so pipeline will contain an entry
// done with this future, should advanced cursor next time
waitInnerFuture = null;
if (pipeline.peek().isDone()) {
// next entry ready, don't loop
return AsyncUtil.READY_FALSE;
}
// otherwise, keep looping
} else {
// don't have next, and won't ever with this cursor
innerExhausted = true;
if (innerResult.getNoNextReason() == NoNextReason.TIME_LIMIT_REACHED && nextResult != null) {
// Under time pressure, do not want to wait for any futures to complete.
// For other out-of-band reasons, still return results from the futures that were
// already started.
// Cannot do this for the very first entry, because do not have a continuation before that.
RecordCursorContinuation lastFinishedContinuation = cancelPendingFutures();
pipeline.add(CompletableFuture.completedFuture(RecordCursorResult.withoutNextValue(lastFinishedContinuation, NoNextReason.TIME_LIMIT_REACHED)));
}
// Wait for next entry, as if pipeline were full
break;
}
}
// the next result is ready
return pipeline.peek().thenApply(vignore -> false);
}
use of com.apple.foundationdb.record.RecordCursorContinuation in project fdb-record-layer by FoundationDB.
the class MapPipelinedCursor method cancelPendingFutures.
@Nonnull
private RecordCursorContinuation cancelPendingFutures() {
Iterator<CompletableFuture<RecordCursorResult<V>>> iter = pipeline.iterator();
// The earliest continuation we could need to start with is the one from the last returned result.
// We may, however, return more results if they are already completed.
RecordCursorContinuation continuation = nextResult.getContinuation();
while (iter.hasNext()) {
CompletableFuture<RecordCursorResult<V>> pendingEntry = iter.next();
if (!pendingEntry.isDone()) {
// futures, remove them from the pipeline, and do *not* update the continuation.
while (true) {
iter.remove();
pendingEntry.cancel(false);
if (!iter.hasNext()) {
return continuation;
}
pendingEntry = iter.next();
}
} else {
// Entry is done, so this cursor will return this result. Keep the entry
// in the pipeline, and update the continuation.
continuation = pendingEntry.join().getContinuation();
}
}
return continuation;
}
use of com.apple.foundationdb.record.RecordCursorContinuation in project fdb-record-layer by FoundationDB.
the class AggregateCursor method onNext.
@Nonnull
@Override
public CompletableFuture<RecordCursorResult<QueryResult>> onNext() {
if (previousResult != null && !previousResult.hasNext()) {
// post-done termination condition: Keep returning terminal element after inner is exhausted.
return CompletableFuture.completedFuture(previousResult);
}
return AsyncUtil.whileTrue(() -> inner.onNext().thenApply(innerResult -> {
previousResult = innerResult;
if (!innerResult.hasNext()) {
groupAggregator.finalizeGroup();
return false;
} else {
previousValidResult = innerResult;
FDBQueriedRecord<M> record = innerResult.get().getQueriedRecord(0);
boolean groupBreak = groupAggregator.apply(record);
return (!groupBreak);
}
}), getExecutor()).thenApply(vignore -> {
if ((previousValidResult == null) && (!previousResult.hasNext())) {
// Edge case where there are no records at all
return previousResult;
}
List<Object> groupResult = groupAggregator.getCompletedGroupResult();
QueryResult queryResult = QueryResult.of(groupResult);
// Use the last valid result for the continuation as we need non-terminal one here.
RecordCursorContinuation continuation = previousValidResult.getContinuation();
return RecordCursorResult.withNextValue(queryResult, continuation);
});
}
use of com.apple.foundationdb.record.RecordCursorContinuation in project fdb-record-layer by FoundationDB.
the class FlatMapPipelinedCursor method tryToFillPipeline.
/**
* Take items from inner cursor and put in pipeline until no more or a mapped cursor item is available.
* @return a future that will complete with {@code false} if an item is available or none will ever be, or with {@code true} if this method should be called to try again
*/
protected CompletableFuture<Boolean> tryToFillPipeline() {
// Clear pipeline entries left behind by exhausted inner cursors.
while (!pipeline.isEmpty() && pipeline.peek().doesNotHaveReturnableResult()) {
pipeline.remove().close();
}
while (!outerExhausted && pipeline.size() < pipelineSize) {
if (outerNextFuture == null) {
outerNextFuture = outerCursor.onNext();
}
if (!outerNextFuture.isDone()) {
// Still waiting for outer future. Check back when it has finished.
final PipelineQueueEntry nextEntry = pipeline.peek();
if (nextEntry == null) {
// loop back to process outer result
return outerNextFuture.thenApply(vignore -> true);
} else {
// keep looping unless we get something from the next entry's inner cursor or the next cursor is ready
final CompletableFuture<PipelineQueueEntry> innerPipelineFuture = nextEntry.getNextInnerPipelineFuture();
return CompletableFuture.anyOf(outerNextFuture, innerPipelineFuture).thenApply(vignore -> !innerPipelineFuture.isDone() || innerPipelineFuture.join().doesNotHaveReturnableResult());
}
}
final RecordCursorResult<T> outerResult = outerNextFuture.join();
if (outerResult.hasNext()) {
final RecordCursorContinuation priorOuterContinuation = outerContinuation;
final T outerValue = outerResult.get();
final byte[] outerCheckValue = checkValueFunction == null ? null : checkValueFunction.apply(outerValue);
byte[] innerContinuation = null;
if (initialInnerContinuation != null) {
// so we should start the inner cursor from the beginning.
if (initialCheckValue == null || outerCheckValue == null || Arrays.equals(initialCheckValue, outerCheckValue)) {
innerContinuation = initialInnerContinuation;
initialInnerContinuation = null;
}
}
final RecordCursor<V> innerCursor = innerCursorFunction.apply(outerValue, innerContinuation);
outerContinuation = outerResult.getContinuation();
pipeline.add(new PipelineQueueEntry(innerCursor, priorOuterContinuation, outerResult, outerCheckValue));
// done with this future, advance outer cursor next time
outerNextFuture = null;
// keep looping to fill pipeline
} else {
// don't have next, and won't ever with this cursor
// Add sentinel to end of pipeline
pipeline.add(new PipelineQueueEntry(null, outerContinuation, outerResult, null));
outerExhausted = true;
// Wait for next entry, as if pipeline were full
break;
}
}
// In any case, it contains an entry so pipeline.peek() will be non-null.
return pipeline.peek().getNextInnerPipelineFuture().thenApply(PipelineQueueEntry::doesNotHaveReturnableResult);
}
use of com.apple.foundationdb.record.RecordCursorContinuation in project fdb-record-layer by FoundationDB.
the class MapWhileCursor method onNext.
@Nonnull
@Override
public CompletableFuture<RecordCursorResult<V>> onNext() {
if (!nextResult.hasNext()) {
// that the cursor return more results if values later on in the child cursor match the predicate.
return CompletableFuture.completedFuture(nextResult);
} else {
return inner.onNext().thenApply(innerResult -> {
if (!innerResult.hasNext()) {
nextResult = RecordCursorResult.withoutNextValue(innerResult);
return nextResult;
}
final Optional<V> maybeRecord = func.apply(innerResult.get());
if (maybeRecord.isPresent()) {
nextResult = RecordCursorResult.withNextValue(maybeRecord.get(), innerResult.getContinuation());
return nextResult;
}
// return no record, handle special cases for continuation
switch(stopContinuation) {
case NONE:
nextResult = RecordCursorResult.exhausted();
break;
case BEFORE:
// previous saved result
final RecordCursorContinuation continuation = nextResult.getContinuation();
nextResult = RecordCursorResult.withoutNextValue(continuation, NoNextReason.SCAN_LIMIT_REACHED);
break;
case AFTER:
default:
nextResult = RecordCursorResult.withoutNextValue(innerResult.getContinuation(), NoNextReason.SCAN_LIMIT_REACHED);
break;
}
return nextResult;
});
}
}
Aggregations