Class ChainedCursor<T>

  • Type Parameters:
    T - the type of elements of the cursor
    All Implemented Interfaces:
    BaseCursor<T>, RecordCursor<T>, AutoCloseable, Iterator<T>

    @API(MAINTAINED)
    public class ChainedCursor<T>
    extends Object
    implements BaseCursor<T>
    A cursor that iterates over a set of data that is dynamically generated a single value at a time. The cursor is driven by a value generation function. The function initially takes an Optional.empty() as input, and from that produces an initial value. Upon subsequent iterations the function is given the value it previously returned and the next value is computed. For example, the following generator would produce the values from 1 to 10:
         maybePreviousValue -> {
             if (maybePreviousValue.isPresent()) {
                 if (maybePreviousValue.get() < 10) {
                     return Optional.of(maybePreviousValue.get() + 1);
                 } else {
                     return Optional.empty();
                 }
             }
             return Optional.of(1);
         }
     
    Given this function, the ChainedCursor would iteratively generate the value from 1 to 10.

    In order to support continuations, the cursor must additionally be provided a functions that can encode and decode the values it produces into binary continuation form. So the fully defined cursor to perform our iteration from 1 to 10, would look like:

         new ChainedCursor(
             // Value generator
             maybePreviousValue -> {
                 if (maybePreviousValue.isPresent()) {
                     if (maybePreviousValue.get() < 10) {
                         return Optional.of(maybePreviousValue.get() + 1);
                     } else {
                         return Optional.empty();
                     }
                 }
                 return Optional.of(1);
             },
             // Continuation encoder
             previousValue -> Tuple.from(previousValue).pack(),
             // Continuation decoder
             encodedContinuation -> Tuple.fromBytes(encodedContinuation).getLong(0)
             executor
         )
     

    Resource Governing

    When the ChainedCursor is created with a set of ScanProperties it will attempt to count each iteration of the cursor against the limits that are indicated by the properties. This can lead to an interesting behavior if the same set of ScanProperties are also used by cursors that are present in the nextGenerator that is provided and can lead to double-counting. For example, if the nextGenerator is driven by the read of a row key via the KeyValueCursor, the cursor could end up counting that read, and the ChainedCursor would then, again, count that read. As such, the caller should take care when specifying the ScanProperties to the cursor and within the nextGenerator.

    In addition, while the ChainedCursor can track and limit on scanned records and scan time, it has no visibility into any bytes that may have been read by cursor managed by the nextGenerator and, thus, cannot enforce any byte scan limits.

    • Constructor Detail

      • ChainedCursor

        public ChainedCursor​(@Nonnull
                             Function<Optional<T>,​CompletableFuture<Optional<T>>> nextGenerator,
                             @Nonnull
                             Function<T,​byte[]> continuationEncoder,
                             @Nonnull
                             Function<byte[],​T> continuationDecoder,
                             @Nullable
                             byte[] continuation,
                             @Nonnull
                             Executor executor)
        Creates a new ChainedCursor. When created in this fashion any resource limits that may be specified in cursors that are used within the nextGenerator will be honored.
        Parameters:
        nextGenerator - a function that that is called to perform iteration
        continuationEncoder - a function that takes a value that was returned by nextGenerator and encodes it as a continuation value
        continuationDecoder - a function that takes a continuation that was produced by continuationEncoder and decodes it back to its original value as returned by nextGenerator
        continuation - the initial continuation for the iteration
        executor - executor that will be returned by getExecutor()
      • ChainedCursor

        public ChainedCursor​(@Nonnull
                             FDBRecordContext context,
                             @Nonnull
                             Function<Optional<T>,​CompletableFuture<Optional<T>>> nextGenerator,
                             @Nonnull
                             Function<T,​byte[]> continuationEncoder,
                             @Nonnull
                             Function<byte[],​T> continuationDecoder,
                             @Nullable
                             byte[] continuation,
                             @Nonnull
                             ScanProperties scanProperties)
        Creates a ChainedCursor.

        The ScanProperties.isReverse()} cannot be honored by the ChainedCursor as the direction of iteration is strictly controlled by the nextGenerator.

        Parameters:
        context - a record context
        nextGenerator - a function that that is called to perform iteration
        continuationEncoder - a function that takes a value that was returned by nextGenerator and encodes it as a continuation value
        continuationDecoder - a function that takes a continuation that was produced by continuationEncoder and decodes it back to its original value as returned by nextGenerator
        continuation - the initial continuation for the iteration
        scanProperties - properties used to control the scanning behavior
    • Method Detail

      • onNext

        @Nonnull
        public CompletableFuture<RecordCursorResult<T>> onNext()
        Description copied from interface: RecordCursor
        Asynchronously return the next result from this cursor. When complete, the future will contain a RecordCursorResult, which represents exactly one of the following:
        1. The next object of type T produced by the cursor. In addition to the next record, this result includes a RecordCursorContinuation that can be used to continue the cursor after the last record returned. The returned continuation is guaranteed not to be an "end continuation" representing the end of the cursor: specifically, RecordCursorContinuation.isEnd() is always false on the returned continuation.
        2. The fact that the cursor is stopped and cannot produce another record and a RecordCursor.NoNextReason that explains why no record could be produced. The result include a continuation that can be used to continue the cursor after the last record returned. If the result's NoNextReason is anything other than RecordCursor.NoNextReason.SOURCE_EXHAUSTED, the returned continuation must not be an end continuation. Conversely, if the result's NoNextReason is SOURCE_EXHAUSTED, then the returned continuation must be an an "end continuation".
        In either case, the returned RecordCursorContinuation can be serialized to an opaque byte array using RecordCursorContinuation.toBytes(). This can be passed back into a new cursor of the same type, with all other parameters remaining the same.
        Specified by:
        onNext in interface RecordCursor<T>
        Returns:
        a future for the next result from this cursor representing either the next record or an indication of why the cursor stopped
        See Also:
        RecordCursorResult, RecordCursorContinuation
      • getContinuation

        @Nullable
        @Deprecated
        public byte[] getContinuation()
        Deprecated.
        Description copied from interface: RecordCursor
        Get a byte string that can be used to continue a query after the last record returned.
        Specified by:
        getContinuation in interface RecordCursor<T>
        Returns:
        opaque byte array denoting where the cursor should pick up. This can be passed back into a new cursor of the same type, with all other parameters remaining the same. Returns null if the underlying source is completely exhausted, independent of any limit passed to the cursor creator. Since such creators generally accept null to mean no continuation, that is, start from the beginning, one must check for null from getContinuation to keep from starting over. Result is not always defined if called before onHasNext or before next after onHasNext has returned true. That is, a continuation is only guaranteed when called "between" records from a while (hasNext) next loop or after its end.
      • accept

        public boolean accept​(@Nonnull
                              RecordCursorVisitor visitor)
        Description copied from interface: RecordCursor
        Accept a visit from hierarchical visitor, which implements RecordCursorVisitor. By contract, implementations of this method must return the value of visitor.visitLeave(this), which determines whether or not subsequent siblings of this cursor should be visited.
        Specified by:
        accept in interface RecordCursor<T>
        Parameters:
        visitor - a hierarchical visitor
        Returns:
        true if the subsequent siblings of the cursor should be visited, and false otherwise