pub struct Tail<'reader> { /* private fields */ }Expand description
A live tail over the frames appended after a reader’s snapshot.
A tail is the mutable counterpart of a snapshot Scan: it
begins after the blocks the reader already holds, and
poll_next refreshes that reader and decodes one newly
committed matching block per call. Polling is synchronous and never blocks:
it sleeps on nothing, spawns no thread, and owns no timer, so a call that
has nothing new returns Ok(None) immediately and the
caller decides how long to wait before polling again. None means “pending
now”, never a permanent end of stream, so a tail does not implement
Iterator or FusedIterator.
None means only that, too. A block that prunes on its bounds or filters
down to no rows is consumed inside the poll that reaches it, and the poll
carries on to the next committed block, so a caller that waits on None
is never waiting on data that has already arrived. Neither is a batch of
zero rows ever reported in place of one.
A physically incomplete frame is never exposed: the tail leaves it
undispatched and reports it through incomplete_tail, and a later poll
sees it once a writer completes it.
Projection, reordered projection, empty projection, primary ranges, file
order, and the treatment of a per-block decode error all match
Scan, and the tail’s cumulative scan limits apply across
its whole lifetime rather than per poll. Cancellation is dropping the tail
or stopping the polls; dropping performs no I/O and leaks no thread or
handle.
The tail borrows its reader, so the reader cannot be refreshed through
Reader::refresh while one of its tails exists. That is the same
snapshot safety boundary Scan already draws, kept on
purpose rather than bypassed with interior mutation.
Implementations§
Source§impl<'reader> Tail<'reader>
impl<'reader> Tail<'reader>
Sourcepub fn project<I, S>(self, columns: I) -> Result<Self>
pub fn project<I, S>(self, columns: I) -> Result<Self>
Select columns by exact schema name, preserving the requested order.
This has exactly Scan::project’s semantics:
the requested order becomes the batch column order, an empty list
yields zero-column batches that still carry their row counts, an
unknown or repeated name fails here rather than during polling, and
calling this again replaces the whole projection.
Sourcepub fn primary_range(self, range: PrimaryRange) -> Result<Self>
pub fn primary_range(self, range: PrimaryRange) -> Result<Self>
Configure a typed half-open primary range, [start, end).
This has exactly Scan::primary_range’s
semantics: the range type must match the schema’s primary column and an
empty range is legal. Blocks whose stored bounds cannot intersect the
range are polled through without a batch, and no global primary
ordering is ever inferred: a future block may overlap this range even
when none of the current blocks do.
Sourcepub fn file_order(self) -> Self
pub fn file_order(self) -> Self
Keep committed block order and row order within each block explicit.
This is currently the tail’s only ordering mode, matching
Scan::file_order.
Sourcepub fn poll_next(&mut self) -> Result<Option<RecordBatch>>
pub fn poll_next(&mut self) -> Result<Option<RecordBatch>>
Synchronously discover and decode the next newly committed matching block, without sleeping or blocking.
A newly committed block is returned as Ok(Some(batch)). Ok(None)
means the file currently holds no committed block this tail has not
already dealt with — “pending now”, not the end of the stream, since
polling again later may yield data. Blocks that pruning or range
filtering removes are consumed on the way, so None is never returned
with matching work still queued and a caller may safely wait on it.
A refresh or decode failure is returned as Err. A per-block decode
failure consumes that block and the tail continues with the next
committed block on a later poll, exactly as a snapshot
Scan yields the damaged block as one error and keeps
going. A refresh failure leaves the tail’s position untouched and is
retried by the next poll.
Until a poll returns a decoded block it keeps refreshing the reader, which keeps the file extent and commit boundary current. Once undispatched blocks exist, polling decodes them first and only refreshes again when they are exhausted, so no block is skipped and none is decoded twice.
Sourcepub fn metrics(&self) -> ScanMetrics
pub fn metrics(&self) -> ScanMetrics
Return aggregate planning, stream, byte, and row counters collected across this tail’s lifetime.
ScanMetrics::bytes_read covers both halves of a tail’s work: the
frames each refresh streamed to verify their commit trailers, and the
bytes the decodes then read. A tail therefore reports more bytes than a
Scan over the same blocks, because a scan never has to
discover them. The row and decoded-byte allowances from
Limits still bound decoding only; discovery is
bounded by the file, not by the scan.