lsm_tree/tree/columnar_scan.rs
1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2024-present, fjall-rs
3// Copyright (c) 2026-present, Dmitry Prudnikov
4
5//! Tree-level projected columnar scan.
6//!
7//! Lifts the per-SST [`Table::columnar_scan`](crate::Table::columnar_scan) to the
8//! whole tree: a consumer holding a [`Tree`] (or an
9//! [`AnyTree`](crate::AnyTree)) can run a projected, predicate-pushed columnar
10//! scan across every columnar segment intersecting a key range and visible at an
11//! MVCC snapshot, without reimplementing segment selection, snapshot visibility,
12//! delete-masking, or cross-segment ordering.
13//!
14//! # Strategy (overlap-aware merge)
15//!
16//! A row's effective sequence number is `local_seqno + global_seqno`. Bulk
17//! ingested segments carry a *uniform per-segment* seqno (every local seqno is
18//! `0`, one `global_seqno` per table), so their visibility is segment-granular;
19//! flush-produced segments carry per-row seqnos, so a snapshot can straddle them.
20//! A projected seqno column is emitted in that EFFECTIVE (tree-global) space,
21//! which is what every other read surface speaks: the stored local value would
22//! read as `0` for an ingested row and name a commit the tree never had. The
23//! masking arithmetic still runs in local space (one subtraction per segment
24//! instead of one addition per row), so only the emitted column is translated.
25//! The visible columnar segments overlapping the range are grouped by key-range
26//! overlap:
27//!
28//! - A **singleton** group (a segment whose key range overlaps no other) whose
29//! rows are all visible AND provably one-version-per-key (the writer's
30//! distinct-key count equals its row count) streams its
31//! [`Table::columnar_scan`](crate::Table::columnar_scan) batches verbatim —
32//! zero-copy column-skip, no key decode, no row gather. A singleton the
33//! snapshot straddles gets a per-row seqno mask first, and one that can hold
34//! several versions of a key (an overwritten key in a flush / compaction
35//! product) additionally gets per-key newest-visible dedup.
36//! - An **overlapping** group is merged as it streams: the projection is
37//! augmented with the intrinsic key + seqno columns, each segment is a source
38//! read one row group at a time, and the merge takes the least row across the
39//! sources by `(key asc, effective seqno desc, source recency asc)`, keeping
40//! the newest visible row of each key. The key/seqno decode and the gather
41//! are paid only where segments overlap, and what the merge holds is one
42//! batch per source, not the group.
43//!
44//! Groups are emitted in ascending key order, so the scan yields projected
45//! [`ColumnBatch`]es in global key order. This mirrors how `InfluxDB` `IOx`
46//! inserts its deduplication operator only over overlapping files and engineers
47//! compaction to keep files non-overlapping: as multi-segment columnar compaction
48//! reduces overlap, more of the scan takes the zero-cost singleton path.
49//!
50//! Deletes reach the scan two ways and both remove the key. A segment's
51//! positional delete-bitmap is applied inside
52//! [`Table::columnar_scan`](crate::Table::columnar_scan); a value-type TOMBSTONE
53//! is consumed here, when the newest visible version of a key is one — the key
54//! then yields no row at all, matching what a point read reports, instead of
55//! surfacing a row a caller who did not project the value-type column could not
56//! tell from a live one. Only a segment that RECORDS deletions pays for it: one
57//! whose metadata counts none keeps its columns untouched (and its zero-copy
58//! verbatim path).
59//!
60//! # Row sources
61//!
62//! The memtables and the row-oriented tables in the range are sources too, so
63//! fresh writes are seen before they reach a columnar table. Each is read at
64//! the snapshot into batches of its keys, versions, value types and whole
65//! values, and always merged, since it holds every version of its keys. A
66//! merge operand row, in a tree with a merge operator, is replaced by what a
67//! point read at the snapshot returns for its key, so a chain reads as the read
68//! path resolves it.
69//!
70//! # Projected fields
71//!
72//! What a batch carries is a [`Projection`](projection::Projection): columns by
73//! id, and declared fields with a type and what their absence reads as. A table
74//! that stores each value whole (rows transposed at a flush or a compaction,
75//! and every row source) yields its declared fields through the projection's
76//! [`ValueProjector`](projection::ValueProjector); a table that stores the
77//! value split into fields yields them as its columns, and a field it lacks is
78//! absent. See [`projection`] for the absence rule every source follows.
79//!
80//! # Blob trees
81//!
82//! The index of a blob tree holds rows written as cells, whose fields sit
83//! inline or in blob files, and whole values, inline or in a blob file. Every
84//! source of it is merged and its rows decided on their keys, seqnos and value
85//! types; only the rows the scan returns have their fields read, and a blob
86//! object only when a projected field needs it. See [`cells`].
87
88use core::ops::{Bound, RangeBounds};
89
90use alloc::{boxed::Box, vec, vec::Vec};
91
92use alloc::borrow::Cow;
93
94use crate::comparator::UserComparator;
95use crate::table::SeqnoVisibility;
96use crate::table::columnar::{
97 COL_SEQNO, COL_USER_KEY, COL_VALUE_TYPE, ColumnBatch, Number, TypeTag, bytes_column_row,
98 fixed_u64_row,
99};
100use crate::table::columnar_predicate::{
101 ColumnRangePredicate, PredicateApply, PredicateSupport, filter_batch, take_column,
102 take_validity,
103};
104use crate::{Error, SeqNo, Table, Tree, UserKey};
105
106mod cells;
107mod merge;
108pub(crate) mod projection;
109mod rows;
110
111use rows::{RowCursor, SourceCursor};
112
113/// What a scan source reads: a columnar segment, or a source of rows.
114enum Source {
115 /// A columnar table, read column by column.
116 Columnar(Table),
117 /// A row-oriented table, read row by row.
118 RowTable(Table),
119 /// A memtable, active or sealed.
120 Memtable(alloc::sync::Arc<crate::memtable::Memtable>),
121}
122
123/// A visible source selected for the scan, with its cached key range,
124/// sequence base, and snapshot-visibility class.
125struct Segment {
126 source: Source,
127 min: UserKey,
128 max: UserKey,
129 /// The segment's `global_seqno` base; a row's effective seqno is
130 /// `local + global`.
131 global: SeqNo,
132 /// Whether every row is visible at the snapshot, or visibility is per-row.
133 visibility: SeqnoVisibility,
134 /// Whether this segment can physically hold several MVCC versions of one
135 /// key (a flush / compaction product with an overwritten key). Proven
136 /// unique only when the writer's distinct-key count equals the row count;
137 /// legacy tables without the count are conservatively assumed to carry
138 /// duplicates. Gates the singleton path's per-key newest-visible dedup.
139 may_dup: bool,
140 /// Source recency: the segment's position in the version's newest-first
141 /// table order (lower = newer). Two segments can hold DIFFERENT values
142 /// for one key at one caller-assigned seqno, and the read path serves
143 /// the newer run's value — the merge path breaks the tie with this rank,
144 /// because `group_by_overlap` re-sorts segments by minimum key and the
145 /// concatenation order alone says nothing about recency.
146 recency_rank: usize,
147 /// Whether the segment stores each value whole, so its declared fields
148 /// are read out of the value through the projector.
149 whole: bool,
150 /// Whether the segment is a blob tree's columnar table, which splits rows
151 /// written as cells into the columns of their fields and keeps every
152 /// other value whole beside them.
153 cells: bool,
154}
155
156impl Segment {
157 /// Whether the source is read row by row.
158 fn is_rows(&self) -> bool {
159 !matches!(self.source, Source::Columnar(_))
160 }
161
162 /// Whether the source can hold a deletion, so the value type is decoded.
163 fn records_deletions(&self) -> bool {
164 match &self.source {
165 Source::Columnar(table) | Source::RowTable(table) => {
166 table.tombstone_count() > 0 || table.weak_tombstone_count() > 0
167 }
168 // A memtable keeps no count; any row of it can be a deletion.
169 Source::Memtable(_) => true,
170 }
171 }
172
173 /// The base the source's range tombstone seqnos are local to. A row
174 /// table's range read already yields effective row seqnos, so its rows
175 /// are merged at base `0`, but its tombstones are stored local.
176 fn rt_base(&self) -> SeqNo {
177 match &self.source {
178 Source::Columnar(_) => self.global,
179 Source::RowTable(table) => table.global_seqno(),
180 Source::Memtable(_) => 0,
181 }
182 }
183
184 /// The source's range tombstones, in its local seqno space.
185 fn range_tombstones(&self) -> Vec<crate::range_tombstone::RangeTombstone> {
186 match &self.source {
187 Source::Columnar(table) | Source::RowTable(table) => table
188 .visible_range_tombstones()
189 .map(alloc::borrow::Cow::into_owned)
190 .collect(),
191 Source::Memtable(memtable) => memtable.range_tombstones_sorted(),
192 }
193 }
194}
195
196/// One key-disjoint group of segments: either a single segment (streamed
197/// verbatim) or several whose key ranges transitively overlap (row-merged).
198struct Group {
199 segments: Vec<Segment>,
200 /// Running maximum key of the group's span, used while grouping.
201 max: UserKey,
202}
203
204impl Tree {
205 /// Runs a projected columnar scan across the whole tree.
206 ///
207 /// Reads every source intersecting `range` at snapshot `seqno`: the
208 /// memtables, the row-oriented tables and the columnar ones. It applies
209 /// deletions and range tombstones from any source to the keys of every
210 /// other, resolves merge chains as a read does, applies the optional
211 /// `predicate` (zone-map block-skip + row filter, after the newest version
212 /// of each key is chosen) and yields projected [`ColumnBatch`]es in
213 /// ascending key order, one row per key: its newest visible version. A
214 /// columnar segment no other source overlaps streams without merge
215 /// overhead.
216 ///
217 /// `range` bounds the result at row granularity: a segment that only
218 /// partially overlaps `range` contributes only the rows whose keys fall
219 /// inside it (the inclusive / exclusive sense of each bound is honored). A
220 /// fully unbounded range keeps the zero-copy fast path for an all-visible
221 /// segment.
222 ///
223 /// `projection` names the columns each batch carries, in its order: column
224 /// ids (the intrinsic [`COL_USER_KEY`] / seqno / value-type columns, or a
225 /// value column typed by what the tables store), converted from a slice of
226 /// ids, or a [`Projection`](projection::Projection) of declared fields,
227 /// which says what a field a row lacks reads as and carries the projector
228 /// the fields are read out of whole values through. Every other column of a
229 /// columnar table is stepped over without decoding.
230 ///
231 /// # Errors
232 ///
233 /// Returns [`Error::Projection`] when the projection names one column id
234 /// twice or the highest id, and, lazily, while iterating, when a row
235 /// returned stores its value whole and no projector is set to read its
236 /// declared fields, or when the data does not satisfy the projection.
237 /// Returns an error on a block read
238 /// or decode failure, or on a failed point read resolving a merge chain.
239 ///
240 /// # Examples
241 ///
242 /// ```
243 /// use lsm_tree::table::columnar::COL_USER_KEY;
244 /// use lsm_tree::{AbstractTree, Config, SeqNo};
245 ///
246 /// let folder = tempfile::tempdir()?;
247 /// let tree = Config::new(folder, Default::default(), Default::default()).open()?;
248 /// // Rows in the memtable are read like any other source.
249 /// tree.insert("a", "1", 0);
250 /// tree.insert("b", "2", 1);
251 /// let rows: u32 = tree
252 /// .columnar_scan(&[COL_USER_KEY], None, SeqNo::MAX, ..)?
253 /// .map(|batch| batch.map(|b| b.row_count))
254 /// .sum::<lsm_tree::Result<u32>>()?;
255 /// assert_eq!(rows, 2);
256 /// # Ok::<(), lsm_tree::Error>(())
257 /// ```
258 pub fn columnar_scan<R: RangeBounds<UserKey>>(
259 &self,
260 projection: impl Into<projection::Projection>,
261 predicate: Option<&ColumnRangePredicate>,
262 seqno: SeqNo,
263 range: R,
264 ) -> crate::Result<ColumnarScan> {
265 let projection = projection.into();
266 // A batch names its columns by id, so one id projected twice (say a
267 // declared field and the raw value by id) would come out as two
268 // columns no reader can tell apart.
269 let fields = projection.fields();
270 if fields.iter().enumerate().any(|(at, f)| {
271 fields
272 .get(..at)
273 .is_some_and(|before| before.iter().any(|b| b.column_id() == f.column_id()))
274 }) {
275 return Err(Error::Projection(
276 "projection: a column id is projected twice",
277 ));
278 }
279 if fields
280 .iter()
281 .any(|f| f.column_id() == merge::COL_WHOLE_VALUE)
282 || predicate.is_some_and(|p| p.column_id == merge::COL_WHOLE_VALUE)
283 {
284 return Err(Error::Projection(
285 "projection: the highest column id is reserved for the scan",
286 ));
287 }
288 // A merge chain is not a version chain: its older rows are the merge's
289 // INPUTS, not data the newest row shadows, and a read hands back the
290 // merged value. An operand row is therefore replaced by what a read at
291 // the snapshot returns for its key, through the same operator, before
292 // anything is projected from it. Without an operator the read path
293 // returns the newest entry unchanged, the raw operand, which is what
294 // the scan yields, so no resolution runs and no value type is decoded.
295 let comparator = self.config.comparator.clone();
296
297 // Owned bounds keep the returned iterator free of borrows from `range`.
298 let lo = clone_bound(range.start_bound());
299 let hi = clone_bound(range.end_bound());
300 let bounds_ref = (bound_as_ref(&lo), bound_as_ref(&hi));
301
302 // The segment list below is built from the one version a read at
303 // `seqno` resolves to, held for the whole scan: a compaction installing
304 // mid-scan cannot turn a row-major tree columnar (or the reverse)
305 // underneath it, and the per-segment recency ranking is the ranking
306 // THAT version has.
307 //
308 // The active memtable in that version stays writable, and its span is
309 // taken now while its rows are read later: the snapshot is capped at
310 // what the tree holds now, so a write or deletion that lands after the
311 // scan is created is invisible to it, as it is outside the span and
312 // the groups computed here. The version is the one the requested
313 // snapshot is served by: a compaction can raise the retention floor
314 // past every seqno the tree still holds, and a snapshot at or below the
315 // floor is refused because it may need a version the compaction
316 // collected. The cap never needs one: it lies above every seqno the
317 // version holds, so it reads what the latest snapshot reads. It is
318 // taken from that version, not the live tree: a compaction installed
319 // since may have dropped the newest seqno, and a cap below a version
320 // the pinned tables still hold would surface an older one.
321 let super_version = self.get_version_for_snapshot(seqno)?;
322 // A projected scan is a scan of the read mix, as a row-major one is.
323 super_version.version.read_mix().note_scan();
324 let highest = super_version
325 .active_memtable
326 .get_highest_seqno()
327 .max(
328 super_version
329 .sealed_memtables
330 .iter()
331 .filter_map(|memtable| memtable.get_highest_seqno())
332 .max(),
333 )
334 .max(
335 super_version
336 .version
337 .iter_tables()
338 .map(crate::Table::get_highest_seqno)
339 .max(),
340 );
341 let seqno = match highest {
342 Some(highest) => highest
343 .checked_add(1)
344 .map_or(seqno, |next: SeqNo| seqno.min(next)),
345 None => 0,
346 };
347 // One blob source for the whole scan: a merge's base and a projected
348 // value are read under the one fill policy the scan began with.
349 let blob_source = self.blob_source();
350 // Operands are resolved in that same version, so a version installed
351 // mid-scan changes no row the scan returns.
352 let resolver = self.config.merge_operator.clone().map(|operator| Resolver {
353 version: super_version.clone(),
354 operator,
355 blob_source: blob_source.clone(),
356 });
357 // The index of a blob tree: its rows' fields are read by the engine,
358 // out of rows written as cells and the objects they reference, and
359 // out of whole values through the projector, a whole value kept in a
360 // blob file read first. Every value column is a field: one projected
361 // by id alone, or a predicate on one no field declares, would read a
362 // stored representation (a cell row, an indirection) rather than a
363 // value, so a field is declared with its type.
364 let cells = blob_source.map(|source| cells::CellSource {
365 version: super_version.clone(),
366 source,
367 });
368 if cells.is_some() {
369 let intrinsic = |id: u16| matches!(id, COL_USER_KEY | COL_SEQNO | COL_VALUE_TYPE);
370 let loose = |id: u16| {
371 !intrinsic(id)
372 && fields
373 .iter()
374 .all(|f| f.column_id() != id || !projection::is_declared(f))
375 };
376 if fields.iter().any(|f| loose(f.column_id()))
377 || predicate.is_some_and(|p| loose(p.column_id))
378 {
379 return Err(Error::Projection(
380 "projection: a blob tree's fields are projected as declared fields",
381 ));
382 }
383 }
384
385 // A declared field of a segment that stores each value whole lies
386 // inside the value, which only the caller's projector reads, and only
387 // for a row the scan returns.
388 let declared = projection.fields().iter().any(projection::is_declared);
389
390 let mut segments: Vec<Segment> = Vec::new();
391 // Memtables are newer than every table, the active one newest; the
392 // sealed ones are kept oldest first.
393 let memtables = core::iter::once(super_version.active_memtable.clone())
394 .chain(super_version.sealed_memtables.iter().rev().cloned());
395 let mut recency_rank = 0;
396 for memtable in memtables {
397 if let Some((min, max)) = memtable_span(&memtable, &lo, &hi, comparator.as_ref()) {
398 segments.push(Segment {
399 min,
400 max,
401 // A row source yields effective seqnos, and only the rows
402 // the snapshot sees.
403 global: 0,
404 visibility: SeqnoVisibility::All,
405 may_dup: true,
406 recency_rank,
407 whole: true,
408 cells: false,
409 source: Source::Memtable(memtable),
410 });
411 }
412 recency_rank += 1;
413 }
414 // `iter_tables` yields newest-first (the same order the sequenced
415 // scan sources rely on), so the enumeration index is the recency
416 // rank after the memtables'.
417 for (rank, table) in super_version.version.iter_tables().enumerate() {
418 let recency_rank = recency_rank + rank;
419 if !table.check_key_range_overlap_cmp(&bounds_ref, comparator.as_ref()) {
420 continue;
421 }
422 // Snapshot visibility (exclusive MVCC). `None` segments postdate the
423 // snapshot and are dropped.
424 let visibility = table.seqno_visibility(seqno);
425 if visibility == SeqnoVisibility::None {
426 continue;
427 }
428 let key_range = &table.metadata.key_range;
429 if !table.metadata.columnar {
430 segments.push(Segment {
431 min: key_range.min().clone(),
432 max: key_range.max().clone(),
433 // Its range read yields effective seqnos, filtered to the
434 // snapshot by the row cursor.
435 global: 0,
436 visibility: SeqnoVisibility::All,
437 may_dup: true,
438 recency_rank,
439 whole: true,
440 cells: false,
441 source: Source::RowTable(table.clone()),
442 });
443 continue;
444 }
445 // `key_count == item_count` proves the segment holds one version per
446 // key, so the verbatim path can return its rows untouched. The count
447 // the writer recorded and the duplicate-free claim read here rest on
448 // the SAME identity relation the read path uses to collapse versions
449 // (`comparator::same_user_key`), so a segment this calls unique is
450 // one a normal read would also return whole. `None` (a legacy
451 // segment that recorded no count) proves nothing and dedups.
452 let may_dup = table
453 .metadata
454 .key_count
455 .is_none_or(|k| k != table.metadata.item_count);
456 segments.push(Segment {
457 min: key_range.min().clone(),
458 max: key_range.max().clone(),
459 global: table.global_seqno(),
460 visibility,
461 may_dup,
462 recency_rank,
463 whole: table.metadata.value_layout == crate::table::meta::ValueLayout::Whole,
464 cells: table.metadata.value_layout == crate::table::meta::ValueLayout::Cells,
465 source: Source::Columnar(table.clone()),
466 });
467 }
468
469 let groups = group_by_overlap(segments, comparator.as_ref());
470
471 Ok(ColumnarScan {
472 groups: groups.into_iter().collect(),
473 current: None,
474 projection: projection.column_ids(),
475 fields: projection.fields().to_vec(),
476 projector: declared
477 .then(|| projection.value_projector().cloned())
478 .flatten(),
479 declared,
480 resolver,
481 cells,
482 predicate: predicate.cloned(),
483 support: PredicateSupport::Exact,
484 comparator,
485 seqno,
486 lo,
487 hi,
488 budget: self.config.columnar_scan_budget,
489 peak_payload: core::cell::Cell::new(0),
490 oversized: core::cell::Cell::new(0),
491 #[cfg(feature = "metrics")]
492 metrics: self.0.metrics.clone(),
493 })
494 }
495}
496
497/// The span of keys `memtable` holds in `lo..hi`, rows and range tombstones
498/// alike, or `None` when it holds none there.
499fn memtable_span(
500 memtable: &crate::memtable::Memtable,
501 lo: &Bound<UserKey>,
502 hi: &Bound<UserKey>,
503 cmp: &dyn UserComparator,
504) -> Option<(UserKey, UserKey)> {
505 use core::cmp::Ordering;
506
507 let (ilo, ihi) = crate::range::internal_bounds(lo, hi);
508 let mut rows = memtable.range_internal((ilo, ihi));
509 let first = rows.next().map(|row| row.key.user_key);
510 let last = rows
511 .next_back()
512 .map(|row| row.key.user_key)
513 .or_else(|| first.clone());
514 let mut span = first.zip(last);
515 let bounds = (lo.clone(), hi.clone());
516 for rt in memtable.range_tombstones_sorted() {
517 if !crate::range::range_tombstone_overlaps_bounds(&rt, &bounds, cmp) {
518 continue;
519 }
520 span = Some(match span {
521 None => (rt.start.clone(), rt.end.clone()),
522 Some((min, max)) => (
523 if cmp.compare(&rt.start, &min) == Ordering::Less {
524 rt.start.clone()
525 } else {
526 min
527 },
528 if cmp.compare(&rt.end, &max) == Ordering::Greater {
529 rt.end.clone()
530 } else {
531 max
532 },
533 ),
534 });
535 }
536 span
537}
538
539/// The tighter of two lower bounds: the greater key, an excluded one when
540/// both name the same key.
541fn tighter_lower(
542 a: Bound<UserKey>,
543 b: &Bound<UserKey>,
544 cmp: &dyn UserComparator,
545) -> Bound<UserKey> {
546 use core::cmp::Ordering;
547
548 match (&a, b) {
549 (_, Bound::Unbounded) => a,
550 (Bound::Unbounded, _) => b.clone(),
551 (Bound::Included(x) | Bound::Excluded(x), Bound::Included(y) | Bound::Excluded(y)) => {
552 match cmp.compare(x, y) {
553 Ordering::Less => b.clone(),
554 Ordering::Equal if matches!(b, Bound::Excluded(_)) => b.clone(),
555 Ordering::Greater | Ordering::Equal => a,
556 }
557 }
558 }
559}
560
561/// The tighter of two upper bounds: the lesser key, an excluded one when
562/// both name the same key.
563fn tighter_upper(
564 a: Bound<UserKey>,
565 b: &Bound<UserKey>,
566 cmp: &dyn UserComparator,
567) -> Bound<UserKey> {
568 use core::cmp::Ordering;
569
570 match (&a, b) {
571 (_, Bound::Unbounded) => a,
572 (Bound::Unbounded, _) => b.clone(),
573 (Bound::Included(x) | Bound::Excluded(x), Bound::Included(y) | Bound::Excluded(y)) => {
574 match cmp.compare(x, y) {
575 Ordering::Greater => b.clone(),
576 Ordering::Equal if matches!(b, Bound::Excluded(_)) => b.clone(),
577 Ordering::Less | Ordering::Equal => a,
578 }
579 }
580 }
581}
582
583/// Partitions `segments` into key-disjoint overlap groups, ordered by ascending
584/// minimum key. Segments are sorted by their minimum key, then greedily extended
585/// into the current group while the next segment's minimum key is `<=` the
586/// group's running maximum (an inclusive-range overlap). The result preserves
587/// global key order across groups: group `i`'s span lies entirely below group
588/// `i + 1`'s.
589fn group_by_overlap(mut segments: Vec<Segment>, cmp: &dyn UserComparator) -> Vec<Group> {
590 use core::cmp::Ordering;
591
592 segments.sort_by(|a, b| cmp.compare(a.min.as_ref(), b.min.as_ref()));
593
594 let mut groups: Vec<Group> = Vec::new();
595 for seg in segments {
596 match groups.last_mut() {
597 Some(g) if cmp.compare(seg.min.as_ref(), g.max.as_ref()) != Ordering::Greater => {
598 if cmp.compare(seg.max.as_ref(), g.max.as_ref()) == Ordering::Greater {
599 g.max = seg.max.clone();
600 }
601 g.segments.push(seg);
602 }
603 _ => groups.push(Group {
604 max: seg.max.clone(),
605 segments: vec![seg],
606 }),
607 }
608 }
609 groups
610}
611
612/// How a singleton group shapes each batch its table's cursor yields.
613enum SingletonMode {
614 /// Every row visible, the range unbounded, one version per key and no
615 /// deletions: the batch goes out as read, its seqnos globalized.
616 Verbatim,
617 /// Rows masked by seqno visibility and the key range.
618 Masked {
619 /// Whether the snapshot straddles the segment, so rows are masked by
620 /// seqno.
621 partial: bool,
622 /// The snapshot in the segment's local seqno space.
623 threshold: SeqNo,
624 /// Columns decoded only for the mask, dropped from each batch.
625 dropped: Vec<u16>,
626 },
627 /// Newest visible version per key, deletions consumed, the predicate
628 /// applied after the dedup.
629 Dedup(DedupState),
630}
631
632/// The per-scan state of a singleton group's dedup: what each batch needs, and
633/// the key run it last decided, which can span batch boundaries.
634struct DedupState {
635 /// The scan's predicate in the segment's local coordinates.
636 predicate: Option<ColumnRangePredicate>,
637 /// The segment's visible range tombstones.
638 rts: TombstoneSweep,
639 /// Whether the snapshot straddles the segment, so rows are masked by
640 /// seqno.
641 partial: bool,
642 /// Whether the segment records deletions, so the value type is decoded.
643 deletes: bool,
644 /// The snapshot in the segment's local seqno space.
645 threshold: SeqNo,
646 /// Columns decoded only for the dedup and the predicate, dropped from
647 /// each batch.
648 dropped: Vec<u16>,
649 /// The user key of the last key run whose newest visible version was
650 /// already emitted (or deliberately dropped) — owned, because a run can
651 /// span batch boundaries. One REUSED buffer: a fresh `to_vec` per run
652 /// would make unique-key data (the common case) pay an allocation and
653 /// free per row.
654 last_key: Option<Vec<u8>>,
655}
656
657/// A segment's cursor, and whether it carries each row's whole value to the
658/// rows the scan returns (see [`ColumnarScan::read_late`]).
659pub(super) struct SegmentCursor {
660 cursor: SourceCursor,
661 whole: bool,
662 /// The columns the cursor decodes.
663 ids: Vec<u16>,
664}
665
666/// A singleton group streamed from its table's cursor.
667struct SingletonStream {
668 cursor: SourceCursor,
669 /// The segment's `global_seqno` base.
670 global: SeqNo,
671 mode: SingletonMode,
672}
673
674/// The source of the group the scan is in.
675enum GroupStream {
676 /// A singleton group, streamed from its table's cursor: one allocation
677 /// per group, next to the row groups it reads.
678 Singleton(Box<SingletonStream>),
679 /// An overlapping group, merged as it streams.
680 Merge(Box<merge::MergeStream>),
681 /// A group that yields nothing: its segment is ruled out.
682 Empty,
683}
684
685impl GroupStream {
686 /// Page bytes the group's sources hold now.
687 fn held_bytes(&self) -> u64 {
688 match self {
689 Self::Singleton(singleton) => singleton.cursor.held_bytes(),
690 Self::Merge(merge) => merge.held_bytes(),
691 Self::Empty => 0,
692 }
693 }
694
695 /// Reads past a share the group's sources made since this was last
696 /// called.
697 fn take_oversized(&mut self) -> u64 {
698 match self {
699 Self::Singleton(singleton) => singleton.cursor.take_oversized(),
700 Self::Merge(merge) => merge.take_oversized(),
701 Self::Empty => 0,
702 }
703 }
704}
705
706/// The rows of a batch whose operand was resolved to a value, by key.
707#[derive(Debug, Default)]
708pub(super) struct Resolved {
709 /// The keys resolved, sorted: their declared fields are read out of the
710 /// merged value, not out of the cells the operand carried.
711 keys: Vec<crate::Slice>,
712 /// Among them, sorted, the keys of rows a field projected by id alone
713 /// cannot be read for: returned, such a row fails the scan.
714 unreadable: Vec<crate::Slice>,
715}
716
717impl Resolved {
718 /// Whether any operand was resolved to a value.
719 pub(super) fn any(&self) -> bool {
720 !self.keys.is_empty()
721 }
722
723 /// Whether `key`'s operand was resolved to a value.
724 pub(super) fn holds(&self, key: &[u8]) -> bool {
725 self.keys.binary_search_by(|k| (**k).cmp(key)).is_ok()
726 }
727
728 /// Whether `key`'s row cannot be returned: a field projected by id alone
729 /// cannot be read out of its merged value.
730 pub(super) fn unreadable(&self, key: &[u8]) -> bool {
731 self.unreadable.binary_search_by(|k| (**k).cmp(key)).is_ok()
732 }
733}
734
735/// A returned row's operand was resolved to a value read whole, which a field
736/// projected by id alone cannot be read out of.
737pub(super) const UNREADABLE_BY_ID: Error = Error::Projection(
738 "projection: a merged value is read whole, and a field projected by id alone cannot be \
739 read out of it",
740);
741
742/// A batch lacks a column the scan decoded for itself.
743const MISSING_BATCH_COLUMN: Error =
744 Error::InvalidHeader("columnar_scan: a batch is missing a column the scan decoded");
745
746/// Whether a row of `batch` is a merge operand; `false` for a batch without a
747/// value-type column.
748fn holds_operand(batch: &ColumnBatch) -> bool {
749 value_types(batch).is_some_and(|types| {
750 types
751 .iter()
752 .take(batch.row_count as usize)
753 .copied()
754 .any(is_operand)
755 })
756}
757
758/// The value-type cells of `batch`, one byte per row, or `None` for a batch
759/// without a value-type column.
760pub(super) fn value_types(batch: &ColumnBatch) -> Option<&[u8]> {
761 batch
762 .columns
763 .iter()
764 .find(|c| c.column_id == COL_VALUE_TYPE)
765 .map(|c| &*c.data)
766}
767
768/// Whether a value-type cell marks a merge operand.
769pub(super) fn is_operand(byte: u8) -> bool {
770 crate::ValueType::try_from(byte) == Ok(crate::ValueType::MergeOperand)
771}
772
773/// The visible range tombstones of a group, swept in the ascending key order
774/// its rows are decided in: each tombstone is entered once the keys reach its
775/// start and dropped once they pass its end, so a row is checked against the
776/// newest tombstone over it, not against every tombstone of the group.
777pub(super) struct TombstoneSweep {
778 /// `(start, end, effective seqno)`, by start key.
779 rts: Vec<(UserKey, UserKey, SeqNo)>,
780 /// The first tombstone the keys have not reached yet.
781 next: usize,
782 /// The tombstones entered, newest first, each with its place in `rts`;
783 /// one the keys have passed is dropped when it comes to the top.
784 open: alloc::collections::BinaryHeap<(SeqNo, usize)>,
785}
786
787impl TombstoneSweep {
788 /// The sweep over `rts`, ordered by `cmp`.
789 pub(super) fn new(mut rts: Vec<(UserKey, UserKey, SeqNo)>, cmp: &dyn UserComparator) -> Self {
790 rts.sort_by(|a, b| cmp.compare(&a.0, &b.0));
791 Self {
792 rts,
793 next: 0,
794 open: alloc::collections::BinaryHeap::new(),
795 }
796 }
797
798 /// Whether the group has no visible range tombstone.
799 pub(super) fn is_empty(&self) -> bool {
800 self.rts.is_empty()
801 }
802
803 /// Whether a row at `key`, no lower than any key asked before, at the
804 /// tree-global seqno `eff` is deleted: inside a tombstone's half-open
805 /// `[start, end)` span and older than it.
806 pub(super) fn covers(&mut self, key: &[u8], eff: SeqNo, cmp: &dyn UserComparator) -> bool {
807 use core::cmp::Ordering;
808
809 while let Some((start, _, seqno)) = self.rts.get(self.next)
810 && cmp.compare(start, key) != Ordering::Greater
811 {
812 self.open.push((*seqno, self.next));
813 self.next += 1;
814 }
815 // The newest open tombstone the keys have passed can cover no later
816 // key either; one below it is dropped once it comes to the top.
817 while let Some(&(_, at)) = self.open.peek() {
818 let passed = self
819 .rts
820 .get(at)
821 .is_none_or(|(_, end, _)| cmp.compare(key, end) != Ordering::Less);
822 if !passed {
823 break;
824 }
825 self.open.pop();
826 }
827 self.open.peek().is_some_and(|&(seqno, _)| eff < seqno)
828 }
829}
830
831/// When a scan's predicate judges the rows the dedup decided: see
832/// [`ColumnarScan::predicate_timing`].
833#[derive(Clone, Copy, PartialEq, Eq)]
834pub(super) enum PredicateTiming {
835 /// Before the values are read, every row.
836 BeforeValues,
837 /// Before the values are read for the plain rows, after for the operands
838 /// still to be resolved.
839 BeforeValuesExceptOperands,
840 /// After the values are read.
841 AfterValues,
842}
843
844/// Drops from `batch` the columns decoded only for the scan's own use.
845fn drop_columns(batch: &mut ColumnBatch, dropped: &[u16]) {
846 if !dropped.is_empty() {
847 batch.columns.retain(|c| !dropped.contains(&c.column_id));
848 }
849}
850
851/// Resolves a merge operand row as a point read at the scan's snapshot would,
852/// in the version the scan reads.
853struct Resolver {
854 version: crate::version::SuperVersion,
855 operator: alloc::sync::Arc<dyn crate::merge_operator::MergeOperator>,
856 /// Where a merge reads a base kept in the value log.
857 blob_source: Option<crate::blob_tree::BlobSource>,
858}
859
860/// Iterator over a tree-level projected columnar scan.
861///
862/// Yields projected [`ColumnBatch`]es in ascending key order. Created by
863/// [`Tree::columnar_scan`] (and surfaced through
864/// [`AnyTree::columnar_scan`](crate::AnyTree::columnar_scan)). A group of one
865/// segment streams its table as it yields, one row group at a time, so a
866/// caller that stops early reads nothing more; a group of overlapping
867/// segments is merged when the scan reaches it.
868pub struct ColumnarScan {
869 groups: alloc::collections::VecDeque<Group>,
870 current: Option<GroupStream>,
871 /// The projected column ids, in output order.
872 projection: Vec<u16>,
873 /// The projected fields, whose declarations every yielded batch is
874 /// brought to.
875 fields: Vec<projection::ProjectedField>,
876 /// The projector the declared fields of a whole-value segment are read
877 /// through; `None` when no field is declared.
878 projector: Option<alloc::sync::Arc<dyn projection::ValueProjector>>,
879 /// Whether a field is declared, so a whole-value segment is read without
880 /// its value column in the declared ids' place even with no projector,
881 /// which only a memtable holding nothing but deletions allows.
882 declared: bool,
883 /// What resolves a merge operand row, when the tree merges; `None` when
884 /// it has no merge operator.
885 resolver: Option<Resolver>,
886 /// Where the objects of a blob tree's rows are read, when the tree is a
887 /// blob tree's index: every row returned has its fields read late (see
888 /// [`cells`]).
889 cells: Option<cells::CellSource>,
890 predicate: Option<ColumnRangePredicate>,
891 /// The weakest [`PredicateSupport`] over the segments read so far.
892 support: PredicateSupport,
893 comparator: alloc::sync::Arc<dyn UserComparator>,
894 /// The query snapshot, used for per-row seqno visibility masking.
895 seqno: SeqNo,
896 /// The requested key range. Applied as a per-row filter (not just segment
897 /// selection): a segment that only partially overlaps the range must still
898 /// drop the rows that fall outside it.
899 lo: Bound<UserKey>,
900 hi: Bound<UserKey>,
901
902 /// The page bytes the scan may hold at once, shared by the segments of an
903 /// overlapping group.
904 budget: u64,
905 /// The most page bytes the scan held at once so far.
906 peak_payload: core::cell::Cell<u64>,
907 /// Reads past a segment's share, as [`Self::oversized_reads`] counts them.
908 oversized: core::cell::Cell<u64>,
909
910 /// Where this scan's gather cost is recorded. Held rather than reached
911 /// for through the tree because the scan outlives the call that built it.
912 #[cfg(feature = "metrics")]
913 metrics: alloc::sync::Arc<crate::Metrics>,
914}
915
916impl ColumnarScan {
917 /// The most page bytes the scan held at once so far: the batches its
918 /// segments have read and not yet handed to the merge or out. It stays
919 /// within [`Config::columnar_scan_budget`](crate::Config::columnar_scan_budget)
920 /// except by the reads [`Self::oversized_reads`] counts. What a read holds
921 /// only while it runs, such as the pages a filter drops, is what that
922 /// counter sees; this figure is what stays read between batches. A batch
923 /// handed out and the scan's per-segment bookkeeping are not counted: the
924 /// first is the caller's, the second grows with the segments merged, not
925 /// with the rows.
926 #[must_use]
927 pub fn peak_payload_bytes(&self) -> u64 {
928 self.peak_payload.get()
929 }
930
931 /// Reads that went past the share of the budget their segment has: a row
932 /// page larger than the share, read on its own; a run of row pages whose
933 /// rows decoded wider than the rows before them; a row group read whole
934 /// because a pushed-down predicate selects its row pages by their zones.
935 #[must_use]
936 pub fn oversized_reads(&self) -> u64 {
937 self.oversized.get()
938 }
939
940 /// Records that the scan holds `bytes` of page payload now.
941 fn observe_payload(&self, bytes: u64) {
942 if bytes > self.peak_payload.get() {
943 self.peak_payload.set(bytes);
944 }
945 }
946
947 /// Records `count` reads past their segment's share.
948 fn record_oversized(&self, count: u64) {
949 self.oversized.set(self.oversized.get() + count);
950 }
951 /// How far the scan's predicate ran over the segments read so far, or
952 /// `None` when the scan has no predicate: the weakest answer of any
953 /// segment, so it only ever falls as the scan goes, and is the answer for
954 /// the whole scan once it is exhausted.
955 ///
956 /// [`PredicateSupport::Exact`] means every row yielded matches;
957 /// [`PredicateSupport::PruneOnly`] and [`PredicateSupport::Unsupported`]
958 /// mean the rows still need the caller's own check.
959 #[must_use]
960 pub fn predicate_support(&self) -> Option<PredicateSupport> {
961 self.predicate.as_ref().map(|_| self.support)
962 }
963
964 /// Records the bytes a gather moved.
965 ///
966 /// The figure is the SIZE OF THE RESULT — what the operation wrote into a
967 /// new buffer — which is what makes repeated accumulation visible:
968 /// folding `k` batches one at a time records the whole accumulated size
969 /// `k` times, so the counter grows quadratically exactly where the work
970 /// does, while a single pass over the same data records it once.
971 #[inline]
972 #[cfg_attr(
973 not(feature = "metrics"),
974 expect(
975 clippy::unused_self,
976 reason = "the scan's metrics exist only with the feature"
977 )
978 )]
979 fn record_gather(&self, batch: &ColumnBatch) {
980 #[cfg(feature = "metrics")]
981 self.metrics.record_gather(batch.data_size());
982 #[cfg(not(feature = "metrics"))]
983 let _ = batch;
984 }
985
986 /// Records `useful` bytes of chosen rows' cells taken from payload pages
987 /// read for them, and `incidental` bytes those pages held besides.
988 #[inline]
989 #[cfg_attr(
990 not(feature = "metrics"),
991 expect(
992 clippy::unused_self,
993 reason = "the scan's metrics exist only with the feature"
994 )
995 )]
996 fn record_payload(&self, useful: u64, incidental: u64) {
997 #[cfg(feature = "metrics")]
998 {
999 self.metrics.record_payload_useful(useful);
1000 self.metrics.record_payload_incidental(incidental);
1001 }
1002 #[cfg(not(feature = "metrics"))]
1003 let _ = (useful, incidental);
1004 }
1005
1006 /// Records `bytes` a read copied out of the pages it decoded.
1007 #[inline]
1008 #[cfg_attr(
1009 not(feature = "metrics"),
1010 expect(
1011 clippy::unused_self,
1012 reason = "the scan's metrics exist only with the feature"
1013 )
1014 )]
1015 fn record_copied(&self, bytes: usize) {
1016 #[cfg(feature = "metrics")]
1017 self.metrics.record_gather(bytes);
1018 #[cfg(not(feature = "metrics"))]
1019 let _ = bytes;
1020 }
1021}
1022
1023impl ColumnarScan {
1024 /// Opens one overlap group as the stream of its projected, key-ordered
1025 /// output batches. A singleton group streams its segment's table (masking
1026 /// by seqno only when the snapshot straddles the segment); an overlapping
1027 /// group is row-merged with newest-effective-seqno-wins dedup.
1028 ///
1029 /// Lowers `support` to how far the predicate ran over what opening read.
1030 fn open_group(
1031 &self,
1032 group: &Group,
1033 support: &mut PredicateSupport,
1034 ) -> crate::Result<GroupStream> {
1035 let rts = self.visible_group_range_tombstones(&group.segments)?;
1036 // A row source yields every version of its keys, deletions among
1037 // them, and its values whole: the merge decides each key. So does a
1038 // table whose values are read only for the rows it returns.
1039 if let [seg] = group.segments.as_slice()
1040 && !seg.is_rows()
1041 && !self.reads_late(seg)
1042 {
1043 return self.open_singleton(seg, rts, support);
1044 }
1045 Ok(GroupStream::Merge(Box::new(merge::MergeStream::open(
1046 self,
1047 &group.segments,
1048 rts,
1049 )?)))
1050 }
1051
1052 /// Whether `seg` is read through the merge with its values read only for
1053 /// the rows the scan returns: every segment of a tree that merges, whose
1054 /// operands a returned row resolves, and a whole-value table whose
1055 /// declared fields a returned row reads out of its value.
1056 fn reads_late(&self, seg: &Segment) -> bool {
1057 // A blob tree reads every returned row's fields late.
1058 if self.cells.is_some() {
1059 return true;
1060 }
1061 self.resolver.is_some() || (seg.whole && self.declared)
1062 }
1063
1064 /// A cursor over `seg`'s table within the scan's key range, decoding
1065 /// `projection`, pushing `predicate` down and holding at most `share`
1066 /// page bytes at once.
1067 fn segment_cursor(
1068 &self,
1069 seg: &Segment,
1070 projection: &[u16],
1071 predicate: Option<&ColumnRangePredicate>,
1072 share: u64,
1073 ) -> crate::Result<SegmentCursor> {
1074 // A whole value is carried to the rows the scan returns, for its
1075 // declared fields and for the merge operands it may hold; a scan of
1076 // intrinsic columns alone reads no value.
1077 let needs_values = self.declared || self.resolver.is_some();
1078 let whole = seg.whole && needs_values;
1079 // The declared fields of a whole value lie inside it: decode the value
1080 // in their place. A declared field's id may be the value column's own,
1081 // so no predicate is pushed down; the merge filters after the fields
1082 // are read.
1083 let mut ids: Vec<u16> = projection.to_vec();
1084 if seg.cells && needs_values {
1085 // A blob tree's columnar table keeps each field of a split row in
1086 // its own column, the row's references beside them, and every
1087 // other value whole under the id the merge carries whole values
1088 // in: no column moves.
1089 use crate::blob_tree::field_row::CELL_REFS_COLUMN;
1090 for id in [CELL_REFS_COLUMN, merge::COL_WHOLE_VALUE] {
1091 if !ids.contains(&id) {
1092 ids.push(id);
1093 }
1094 }
1095 } else if whole {
1096 let declared: Vec<u16> = self
1097 .fields
1098 .iter()
1099 .filter(|f| projection::is_declared(f))
1100 .map(projection::ProjectedField::column_id)
1101 .collect();
1102 ids.retain(|id| !declared.contains(id));
1103 if !ids.contains(&crate::table::columnar::COL_VALUE) {
1104 ids.push(crate::table::columnar::COL_VALUE);
1105 }
1106 }
1107 let predicate = predicate.filter(|_| !whole);
1108 // Every segment of the scan is opened here once: its table counts the
1109 // scan as reaching it, as a row scan's tables do.
1110 if let Source::Columnar(table) | Source::RowTable(table) = &seg.source {
1111 table.note_scan();
1112 }
1113 let cursor = match &seg.source {
1114 Source::Columnar(table) => SourceCursor::Columnar(Box::new(table.columnar_cursor(
1115 &ids,
1116 predicate,
1117 self.lo.clone(),
1118 self.hi.clone(),
1119 Some(share),
1120 )?)),
1121 Source::RowTable(table) => SourceCursor::Rows(Box::new(RowCursor::table(
1122 table,
1123 self.lo.clone(),
1124 self.hi.clone(),
1125 self.seqno,
1126 ids.clone(),
1127 share,
1128 ))),
1129 // Within the span its group was formed on: the active memtable
1130 // stays writable, and a row landing outside that span after the
1131 // scan was created would be read out of order, or beside a
1132 // version of its key another group returns. A range tombstone
1133 // widens that span past the scan's range, so the cursor reads
1134 // only where the two meet: every row beyond is dropped by the
1135 // range check anyway.
1136 Source::Memtable(memtable) => {
1137 let cmp = self.comparator.as_ref();
1138 let lo = tighter_lower(Bound::Included(seg.min.clone()), &self.lo, cmp);
1139 let hi = tighter_upper(Bound::Included(seg.max.clone()), &self.hi, cmp);
1140 SourceCursor::Rows(Box::new(RowCursor::memtable(
1141 memtable.clone(),
1142 &lo,
1143 &hi,
1144 self.seqno,
1145 ids.clone(),
1146 share,
1147 )))
1148 }
1149 };
1150 Ok(SegmentCursor { cursor, whole, ids })
1151 }
1152
1153 /// The columns of `seg` a merge reads for its chosen rows only, as the
1154 /// pairs of the id a table stores each under and the id the merge carries
1155 /// it under: the fields the projection declares, but the predicate's,
1156 /// and the columns a whole value or a blob tree's references are carried
1157 /// in. Empty for a source read row by row, and while operands are
1158 /// resolved or the raw value is read as well, whose values are needed
1159 /// whole.
1160 fn late_payload(&self, seg: &Segment) -> Vec<(u16, u16)> {
1161 use crate::blob_tree::field_row::CELL_REFS_COLUMN;
1162 use crate::table::columnar::COL_VALUE;
1163
1164 if seg.is_rows() || self.resolver.is_some() || self.raw_value_read() {
1165 return Vec::new();
1166 }
1167 let predicate = self.predicate.as_ref().map(|p| p.column_id);
1168 let fields = self
1169 .fields
1170 .iter()
1171 .filter(|f| projection::is_declared(f) && Some(f.column_id()) != predicate)
1172 .map(|f| (f.column_id(), f.column_id()));
1173 // Without a declared field no value is read at all, a blob tree's
1174 // included (a returned row's value type reads as a value regardless).
1175 if seg.cells && self.declared {
1176 fields
1177 .chain([
1178 (CELL_REFS_COLUMN, CELL_REFS_COLUMN),
1179 (merge::COL_WHOLE_VALUE, merge::COL_WHOLE_VALUE),
1180 ])
1181 .collect()
1182 } else if seg.whole {
1183 // The declared fields lie inside the whole value.
1184 if self.declared {
1185 alloc::vec![(COL_VALUE, merge::COL_WHOLE_VALUE)]
1186 } else {
1187 Vec::new()
1188 }
1189 } else {
1190 fields.collect()
1191 }
1192 }
1193
1194 /// The rows a merge returned, `batch`, with their values read: each merge
1195 /// operand replaced by what a read at the scan's snapshot returns for its
1196 /// key (a key the read finds absent is dropped), then the declared fields
1197 /// of each row carrying a whole value read out of it, and the carried
1198 /// value column dropped. Only returned rows get here, so a shadowed,
1199 /// deleted or invisible version is never resolved or projected. Also
1200 /// returns which keys were resolved to a value: such a row holds no cell
1201 /// of its own for a column no field declares. `judged` says the merge
1202 /// already gave every row its final verdict on the predicate.
1203 pub(super) fn read_late(
1204 &self,
1205 batch: ColumnBatch,
1206 judged: bool,
1207 ) -> crate::Result<(ColumnBatch, Resolved)> {
1208 let (batch, resolved) = self.resolve_operands(batch)?;
1209 if let Some(cells) = &self.cells {
1210 return Ok((cells::materialize(self, cells, batch, judged)?, resolved));
1211 }
1212 let mut batch = if self.declared {
1213 projection::project_decided(
1214 batch,
1215 &self.fields,
1216 self.projector.as_deref(),
1217 merge::COL_WHOLE_VALUE,
1218 )?
1219 } else {
1220 batch
1221 };
1222 drop_columns(&mut batch, &[merge::COL_WHOLE_VALUE]);
1223 Ok((batch, resolved))
1224 }
1225
1226 /// `batch` with each merge operand row replaced by what a read at the
1227 /// scan's snapshot returns for its key, through the tree's own operator
1228 /// exactly as the read path resolves it: that value, carried as the row's
1229 /// whole value (and in the value column when it is projected by id), or
1230 /// the row dropped when the read finds the key absent. Also returns which
1231 /// keys were resolved to a value (see [`Resolved`]).
1232 ///
1233 /// The value an operand resolves to is read whole, like any row value: a
1234 /// value field projected by id alone cannot be read out of it, so such a
1235 /// row is named unreadable, and the scan fails as it does for a whole
1236 /// value if the row is returned, instead of returning the cell the
1237 /// operand itself carried.
1238 fn resolve_operands(&self, batch: ColumnBatch) -> crate::Result<(ColumnBatch, Resolved)> {
1239 use crate::table::columnar::{COL_VALUE, Column, frame_bytes_column};
1240
1241 let Some(resolver) = &self.resolver else {
1242 return Ok((batch, Resolved::default()));
1243 };
1244 if !holds_operand(&batch) {
1245 return Ok((batch, Resolved::default()));
1246 }
1247 let row_count = batch.row_count;
1248 let rows = row_count as usize;
1249 let find = |id: u16| batch.columns.iter().position(|c| c.column_id == id);
1250 let (Some(key_at), Some(type_at), Some(whole_at)) = (
1251 find(COL_USER_KEY),
1252 find(COL_VALUE_TYPE),
1253 find(merge::COL_WHOLE_VALUE),
1254 ) else {
1255 return Err(MISSING_BATCH_COLUMN);
1256 };
1257 // The raw value, read by id or by the predicate, reads the resolved
1258 // value too.
1259 let raw_at = find(COL_VALUE).filter(|&at| {
1260 batch
1261 .columns
1262 .get(at)
1263 .is_some_and(|c| c.type_tag == TypeTag::Bytes)
1264 && self.raw_value_read()
1265 });
1266 let column = |at: usize| batch.columns.get(at).ok_or(MISSING_BATCH_COLUMN);
1267 let (keys, kinds) = (column(key_at)?, column(type_at)?);
1268 // Per row: `None` keeps it as read, `Some(None)` drops it, and
1269 // `Some(Some(value))` makes it that value.
1270 let mut resolved: Vec<Option<Option<crate::Slice>>> = Vec::with_capacity(rows);
1271 for row in 0..row_count {
1272 let byte = *kinds.data.get(row as usize).ok_or(MISSING_BATCH_COLUMN)?;
1273 if crate::ValueType::try_from(byte) != Ok(crate::ValueType::MergeOperand) {
1274 resolved.push(None);
1275 continue;
1276 }
1277 // Each key is returned once, so each is read once.
1278 let key = bytes_column_row(&keys.data, row_count, row)?;
1279 resolved.push(Some(Tree::resolve_or_passthrough(
1280 &resolver.version,
1281 key,
1282 self.seqno,
1283 Some(&resolver.operator),
1284 self.comparator.as_ref(),
1285 || resolver.blob_source.clone(),
1286 )?));
1287 }
1288 // Only the raw value is rewritten below, and only a row that carried
1289 // its value whole holds the raw value under the value column's id: a
1290 // row of a table that splits its values carries no whole value, and
1291 // its cell under that id is a field like any other.
1292 let whole = column(whole_at)?;
1293 let by_id: Vec<u16> = self
1294 .fields
1295 .iter()
1296 .filter(|f| {
1297 f.type_tag().is_none() && batch.columns.iter().any(|c| c.column_id == f.column_id())
1298 })
1299 .map(projection::ProjectedField::column_id)
1300 .collect();
1301 let mut outcome = Resolved::default();
1302 for (row, fix) in (0..row_count).zip(&resolved) {
1303 if !matches!(fix, Some(Some(_))) {
1304 continue;
1305 }
1306 let key = crate::Slice::from(bytes_column_row(&keys.data, row_count, row)?);
1307 let raw_rewritten = raw_at.is_some() && whole.is_valid(row);
1308 if by_id.iter().any(|&id| !(id == COL_VALUE && raw_rewritten)) {
1309 outcome.unreadable.push(key.clone());
1310 }
1311 outcome.keys.push(key);
1312 }
1313 outcome.keys.sort_unstable();
1314 outcome.unreadable.sort_unstable();
1315
1316 // A key the read finds absent is not returned: the rows returned are
1317 // the others, and only they are written below, once each.
1318 let returned = |row: &u32| !matches!(resolved.get(*row as usize), Some(Some(None)));
1319 let kept: Option<Vec<u32>> = resolved
1320 .iter()
1321 .any(|fix| matches!(fix, Some(None)))
1322 .then(|| (0..row_count).filter(returned).collect());
1323 let out_rows = kept.as_ref().map_or(rows, Vec::len);
1324 // The cells of bytes column `at` with the resolved rows' values in
1325 // place: a resolved row's cell is its value, the others as read.
1326 let rewrite = |at: usize| -> crate::Result<Column> {
1327 let old = column(at)?;
1328 let mut cells: Vec<Option<&[u8]>> = Vec::with_capacity(out_rows);
1329 for row in (0..row_count).filter(returned) {
1330 cells.push(match resolved.get(row as usize) {
1331 Some(Some(Some(value))) => Some(&**value),
1332 _ if old.is_valid(row) => Some(bytes_column_row(&old.data, row_count, row)?),
1333 _ => None,
1334 });
1335 }
1336 let validity = cells.iter().any(Option::is_none).then(|| {
1337 let mut bits = alloc::vec![0u8; out_rows.div_ceil(8)];
1338 for (row, cell) in cells.iter().enumerate() {
1339 if cell.is_some()
1340 && let Some(byte) = bits.get_mut(row / 8)
1341 {
1342 *byte |= 1 << (row % 8);
1343 }
1344 }
1345 bits
1346 });
1347 Ok(Column {
1348 column_id: old.column_id,
1349 type_tag: TypeTag::Bytes,
1350 validity,
1351 data: frame_bytes_column(out_rows, || cells.iter().map(|c| c.unwrap_or(&[])))?,
1352 })
1353 };
1354 let whole = rewrite(whole_at)?;
1355 let raw = raw_at.map(rewrite).transpose()?;
1356 let mut types: Vec<u8> = Vec::with_capacity(out_rows);
1357 for row in (0..row_count).filter(returned) {
1358 types.push(match resolved.get(row as usize) {
1359 Some(Some(_)) => u8::from(crate::ValueType::Value),
1360 _ => *kinds.data.get(row as usize).ok_or(MISSING_BATCH_COLUMN)?,
1361 });
1362 }
1363
1364 let mut batch = batch;
1365 if let Some(kept) = &kept {
1366 for (at, slot) in batch.columns.iter_mut().enumerate() {
1367 if at == whole_at || raw_at == Some(at) {
1368 continue;
1369 }
1370 if at == type_at {
1371 // Its cells are written above; only its nulls are gathered.
1372 slot.validity = slot
1373 .validity
1374 .as_deref()
1375 .map(|bits| take_validity(bits, kept));
1376 continue;
1377 }
1378 // Every other column keeps the rows returned, gathered once.
1379 *slot = take_column(slot, rows, kept)?;
1380 }
1381 }
1382 if let Some(slot) = batch.columns.get_mut(whole_at) {
1383 *slot = whole;
1384 }
1385 if let (Some(at), Some(raw)) = (raw_at, raw)
1386 && let Some(slot) = batch.columns.get_mut(at)
1387 {
1388 *slot = raw;
1389 }
1390 if let Some(slot) = batch.columns.get_mut(type_at) {
1391 slot.data = crate::Slice::from(types);
1392 }
1393 // A subset of the batch's rows, whose count is a u32.
1394 batch.row_count = u32::try_from(out_rows).unwrap_or(row_count);
1395 self.record_gather(&batch);
1396 Ok((batch, outcome))
1397 }
1398
1399 /// The next output batch of `stream`, or `None` once it is exhausted.
1400 /// Lowers `support` to how far the predicate ran over what was read.
1401 fn next_from(
1402 &self,
1403 stream: &mut GroupStream,
1404 support: &mut PredicateSupport,
1405 ) -> Option<crate::Result<ColumnBatch>> {
1406 match stream {
1407 GroupStream::Empty => None,
1408 GroupStream::Merge(merge) => merge.next_batch(self, support).transpose(),
1409 GroupStream::Singleton(singleton) => loop {
1410 let batch = match singleton.cursor.next()? {
1411 Ok(batch) => batch,
1412 Err(e) => return Some(Err(e)),
1413 };
1414 // Observed before the batch is shaped: a mask or dedup that
1415 // keeps none of its rows drops it here, and what the cursor
1416 // held with it would otherwise never be seen.
1417 self.observe_payload(singleton.cursor.held_bytes() + batch.data_size() as u64);
1418 *support = (*support).min(singleton.cursor.predicate_support());
1419 // A segment written without a projected column, or with null
1420 // cells in one, reads as the field declares before its rows
1421 // are decided, so a predicate after the dedup sees the
1422 // declared defaults, as it does on the merge path.
1423 let judged = self.predicate.as_ref().map(|p| p.column_id);
1424 let (batch, mistyped) =
1425 match projection::conform_lenient(batch, &self.fields, judged) {
1426 Ok(conformed) => conformed,
1427 Err(e) => return Some(Err(e)),
1428 };
1429 let SingletonStream { global, mode, .. } = &mut **singleton;
1430 // A predicate over a column the batch stores under another
1431 // type cannot judge its rows: it is set aside for this batch,
1432 // so a row the dedup keeps fails the scan below.
1433 let unjudged = match mode {
1434 SingletonMode::Dedup(state)
1435 if state
1436 .predicate
1437 .as_ref()
1438 .is_some_and(|p| mistyped.contains(&p.column_id)) =>
1439 {
1440 state.predicate.take()
1441 }
1442 _ => None,
1443 };
1444 let shaped = self.shape_singleton_batch(batch, *global, mode, support);
1445 if let (Some(predicate), SingletonMode::Dedup(state)) = (unjudged, &mut *mode) {
1446 state.predicate = Some(predicate);
1447 }
1448 match shaped {
1449 // A column's type is the batch's, so any row it returns
1450 // stores the field under the other type.
1451 Ok(Some(_)) if !mistyped.is_empty() => {
1452 return Some(Err(projection::MISTYPED));
1453 }
1454 // The rows returned are decided: each is held to the
1455 // declarations.
1456 Ok(Some(batch)) => return Some(projection::conform(batch, &self.fields)),
1457 Ok(None) => {}
1458 Err(e) => return Some(Err(e)),
1459 }
1460 },
1461 }
1462 }
1463
1464 /// Shapes one batch a singleton group's cursor read as `mode` says, or
1465 /// `None` when no row of it survives.
1466 fn shape_singleton_batch(
1467 &self,
1468 batch: ColumnBatch,
1469 global: SeqNo,
1470 mode: &mut SingletonMode,
1471 support: &mut PredicateSupport,
1472 ) -> crate::Result<Option<ColumnBatch>> {
1473 // A cursor yields a batch only for a row page that keeps a row.
1474 debug_assert!(batch.row_count > 0, "a cursor yielded an empty batch");
1475 match mode {
1476 SingletonMode::Verbatim => {
1477 let mut batch = batch;
1478 self.globalize_seqnos(&mut batch, global)?;
1479 Ok(Some(batch))
1480 }
1481 SingletonMode::Masked {
1482 partial,
1483 threshold,
1484 dropped,
1485 } => self.mask_singleton_batch(&batch, global, *partial, *threshold, dropped),
1486 SingletonMode::Dedup(state) => {
1487 self.dedup_singleton_batch(&batch, global, state, support)
1488 }
1489 }
1490 }
1491
1492 /// Whether the raw value is read by id, beside any declared field read
1493 /// out of it: a field projects it by id, or the predicate runs on it and
1494 /// no field declares its id.
1495 pub(super) fn raw_value_read(&self) -> bool {
1496 use crate::table::columnar::COL_VALUE;
1497
1498 let by_id = self
1499 .fields
1500 .iter()
1501 .any(|f| f.column_id() == COL_VALUE && !projection::is_declared(f));
1502 let declared = self
1503 .fields
1504 .iter()
1505 .any(|f| f.column_id() == COL_VALUE && projection::is_declared(f));
1506 by_id
1507 || (!declared
1508 && self
1509 .predicate
1510 .as_ref()
1511 .is_some_and(|p| p.column_id == COL_VALUE))
1512 }
1513
1514 /// When the scan's predicate judges the decided rows: before their values
1515 /// are read where its column is one reading the values does not change.
1516 /// The key and the seqno never change; a column no field declares is the
1517 /// row's own physical cell, except on an operand still to be resolved
1518 /// into a value read whole, which is judged after. A declared field is
1519 /// read out of the value, so a predicate over one runs after.
1520 /// `holds_operand` says whether a decided row is a merge operand, asked
1521 /// only when the answer matters.
1522 pub(super) fn predicate_timing(&self, holds_operand: impl FnOnce() -> bool) -> PredicateTiming {
1523 use crate::table::columnar::COL_SEQNO;
1524
1525 let Some(pred) = &self.predicate else {
1526 return PredicateTiming::AfterValues;
1527 };
1528 // The raw value and the value type are the row's own cells too: a
1529 // whole-value source keeps its raw value in place when the predicate
1530 // reads it, and only an operand still to be resolved rewrites them.
1531 match pred.column_id {
1532 COL_USER_KEY | COL_SEQNO => PredicateTiming::BeforeValues,
1533 // The value type is an opaque fixed-width column, which a range
1534 // predicate does not judge (its support is reported unsupported
1535 // and every row kept), so a blob tree's stored types (an
1536 // indirection, a cell row) never decide a row before it is
1537 // returned as a value.
1538 id if self
1539 .fields
1540 .iter()
1541 .any(|f| f.column_id() == id && projection::is_declared(f)) =>
1542 {
1543 PredicateTiming::AfterValues
1544 }
1545 _ if self.resolver.is_some() && holds_operand() => {
1546 PredicateTiming::BeforeValuesExceptOperands
1547 }
1548 _ => PredicateTiming::BeforeValues,
1549 }
1550 }
1551
1552 /// Applies the scan's predicate to `batch` after the dedup, when it filters,
1553 /// and lowers `support` to how far it ran there.
1554 fn filter_after_dedup(
1555 &self,
1556 batch: ColumnBatch,
1557 pred: Option<&ColumnRangePredicate>,
1558 support: &mut PredicateSupport,
1559 ) -> crate::Result<ColumnBatch> {
1560 let Some(pred) = pred else {
1561 return Ok(batch);
1562 };
1563 *support = (*support).min(pred.support_in(&batch));
1564 if pred.apply != PredicateApply::Filter {
1565 return Ok(batch);
1566 }
1567 let mask = pred.matching_rows(&batch);
1568 // Rows judged on their stored column before the gather all pass
1569 // again: the batch goes on as gathered, not copied once more.
1570 if mask.iter().all(|&keep| keep) {
1571 return Ok(batch);
1572 }
1573 let filtered = filter_batch(&batch, &mask)?;
1574 self.record_gather(&filtered);
1575 Ok(filtered)
1576 }
1577
1578 /// The range tombstones of `segments` visible to the scan snapshot, with
1579 /// tree-global effective seqnos: an UNMATERIALIZED range deletion (a
1580 /// flushed `remove_range` no relocation has folded into a positional
1581 /// delete bitmap yet) lives only in the segments' RT sections, and the
1582 /// scan must suppress the rows it covers exactly as the point and
1583 /// ordinary range reads do. A group is key-disjoint from its neighbours
1584 /// and a tombstone's span is inside its own segment's key range, so
1585 /// per-group collection sees every tombstone that can cover a group row.
1586 fn visible_group_range_tombstones(
1587 &self,
1588 segments: &[Segment],
1589 ) -> crate::Result<Vec<(UserKey, UserKey, SeqNo)>> {
1590 let mut rts = Vec::new();
1591 for seg in segments {
1592 for rt in seg.range_tombstones() {
1593 let eff = rt
1594 .seqno
1595 .checked_add(seg.rt_base())
1596 .ok_or(Error::InvalidHeader(
1597 "columnar_scan: effective range-tombstone seqno overflows",
1598 ))?;
1599 // Same exclusive-MVCC visibility as rows: the deletion exists
1600 // for this snapshot only below it.
1601 if eff < self.seqno {
1602 rts.push((rt.start.clone(), rt.end.clone(), eff));
1603 }
1604 }
1605 }
1606 Ok(rts)
1607 }
1608
1609 /// Whether the requested key range is fully unbounded, so no per-row range
1610 /// filtering is needed (the segment's every row is in range).
1611 fn range_is_full(&self) -> bool {
1612 matches!(self.lo, Bound::Unbounded) && matches!(self.hi, Bound::Unbounded)
1613 }
1614
1615 /// Rewrites a batch's seqno column from its segment's LOCAL space into the
1616 /// tree's global one (`local + global`).
1617 ///
1618 /// A bulk-ingested segment stores every row at local seqno `0` and carries
1619 /// its ordering in a per-segment `global_seqno`, so the stored column is not
1620 /// a commit sequence number any other read surface would recognize. The
1621 /// masking arithmetic elsewhere translates the THRESHOLD into local space
1622 /// instead (cheaper, one subtraction per segment), which is why the column
1623 /// itself still needs this before it reaches a caller. A zero offset leaves
1624 /// the batch untouched. The rewritten column is a gather and is charged.
1625 #[cfg_attr(
1626 not(feature = "metrics"),
1627 expect(
1628 clippy::unused_self,
1629 reason = "the scan's metrics exist only with the feature"
1630 )
1631 )]
1632 fn globalize_seqnos(&self, batch: &mut ColumnBatch, global: SeqNo) -> crate::Result<()> {
1633 if global == 0 {
1634 return Ok(());
1635 }
1636 let Some(col) = batch.columns.iter_mut().find(|c| c.column_id == COL_SEQNO) else {
1637 return Ok(());
1638 };
1639 let len = batch.row_count as usize * 8;
1640 if col.data.len() != len {
1641 return Err(Error::InvalidHeader("columnar_scan: short seqno column"));
1642 }
1643 // Column bytes are an immutable (possibly shared) view, so the
1644 // globalized column is rebuilt into a new buffer, written in place:
1645 // one allocation per batch, and only for bulk-ingested segments
1646 // (`global != 0`).
1647 // SAFETY: the loop writes one 8-byte seqno per row, and `len` is
1648 // exactly `row_count * 8`, which the source column matches, so every
1649 // byte is initialized before the buffer is frozen and read. An early
1650 // return on overflow drops the builder unread.
1651 #[expect(unsafe_code, reason = "see safety")]
1652 let mut out = unsafe { crate::Slice::builder_unzeroed(len) };
1653 for (row, (dst, src)) in out
1654 .as_chunks_mut::<8>()
1655 .0
1656 .iter_mut()
1657 .zip(col.data.as_chunks::<8>().0)
1658 .enumerate()
1659 {
1660 let Some(effective) = u64::from_le_bytes(*src).checked_add(global) else {
1661 // The rows before this one were already rewritten.
1662 #[cfg(feature = "metrics")]
1663 self.metrics.record_gather(row * 8);
1664 #[cfg(not(feature = "metrics"))]
1665 let _ = row;
1666 return Err(Error::InvalidHeader(
1667 "columnar_scan: effective seqno overflows",
1668 ));
1669 };
1670 *dst = effective.to_le_bytes();
1671 }
1672 col.data = crate::Slice::from(out.freeze());
1673 #[cfg(feature = "metrics")]
1674 self.metrics.record_gather(len);
1675 Ok(())
1676 }
1677
1678 /// The rows of `batch` that `mask` keeps, as the batch a singleton group
1679 /// yields, or `None` when it keeps none: every column but the `dropped`
1680 /// ones the scan decoded for itself, gathered once (shared as decoded when
1681 /// it keeps every row), with the seqno column
1682 /// written in the tree's global space as it is gathered (see
1683 /// [`Self::globalize_seqnos`]). The gather is charged.
1684 fn gather_kept(
1685 &self,
1686 batch: &ColumnBatch,
1687 mask: &[bool],
1688 dropped: &[u16],
1689 global: SeqNo,
1690 ) -> crate::Result<Option<ColumnBatch>> {
1691 let mut kept = Vec::with_capacity(mask.len());
1692 for (row, &keep) in (0..batch.row_count).zip(mask) {
1693 if keep {
1694 kept.push(row);
1695 }
1696 }
1697 if kept.is_empty() {
1698 return Ok(None);
1699 }
1700 let rows = batch.row_count as usize;
1701 // Every row kept: a column goes on as decoded, shared rather than
1702 // gathered, and only the seqno column is written anew.
1703 let all = kept.len() == rows;
1704 let mut copied = 0usize;
1705 let mut columns = Vec::with_capacity(batch.columns.len());
1706 for col in batch
1707 .columns
1708 .iter()
1709 .filter(|c| !dropped.contains(&c.column_id))
1710 {
1711 let written = if col.column_id == COL_SEQNO && global != 0 {
1712 let mut data = Vec::with_capacity(kept.len() * 8);
1713 for &row in &kept {
1714 let effective = fixed_u64_row(&col.data, row)?.checked_add(global).ok_or(
1715 Error::InvalidHeader("columnar_scan: effective seqno overflows"),
1716 )?;
1717 data.extend_from_slice(&effective.to_le_bytes());
1718 }
1719 crate::table::columnar::Column {
1720 column_id: col.column_id,
1721 type_tag: col.type_tag,
1722 validity: col
1723 .validity
1724 .as_deref()
1725 .map(|bits| take_validity(bits, &kept)),
1726 data: crate::Slice::from(data),
1727 }
1728 } else if all {
1729 columns.push(col.clone());
1730 continue;
1731 } else {
1732 take_column(col, rows, &kept)?
1733 };
1734 copied += written.data.len() + written.validity.as_ref().map_or(0, Vec::len);
1735 columns.push(written);
1736 }
1737 // A subset of the batch's rows, whose count is a u32.
1738 let row_count = u32::try_from(kept.len()).unwrap_or(batch.row_count);
1739 let out = ColumnBatch { row_count, columns };
1740 #[cfg(feature = "metrics")]
1741 self.metrics.record_gather(copied);
1742 #[cfg(not(feature = "metrics"))]
1743 let _ = copied;
1744 Ok(Some(out))
1745 }
1746
1747 /// Singleton group: no cross-segment merge. When every row is visible and the
1748 /// range is unbounded, the per-SST projected scan streams verbatim (zero-copy
1749 /// column-skip). Otherwise a per-row mask drops rows that are seqno-invisible
1750 /// (when the snapshot straddles the segment) or outside the requested range
1751 /// (when the segment only partially overlaps it).
1752 fn open_singleton(
1753 &self,
1754 seg: &Segment,
1755 rts: Vec<(UserKey, UserKey, SeqNo)>,
1756 support: &mut PredicateSupport,
1757 ) -> crate::Result<GroupStream> {
1758 // Every path below hands the predicate to the segment's table, or
1759 // evaluates it before the seqno column is globalized, so it runs in
1760 // the table's LOCAL seqno coordinates: a seqno bound is translated by
1761 // the segment's base first. A predicate no row of the segment can
1762 // satisfy (a seqno range wholly below its base) rules the segment out.
1763 let predicate = if let Some(pred) = self.predicate.as_ref() {
1764 let Some(local) = localize(pred, seg.global) else {
1765 *support = (*support).min(pred.support(Some(TypeTag::Number(Number::U64_LE))));
1766 return Ok(GroupStream::Empty);
1767 };
1768 Some(local.into_owned())
1769 } else {
1770 None
1771 };
1772 // A segment that RECORDS deletions takes the dedup path even when its
1773 // keys are provably unique: a key whose single row is a tombstone would
1774 // otherwise stream through verbatim and surface a key the point read
1775 // calls absent. Deciding a run is where tombstones are consumed, and
1776 // that lives there. A visible RANGE tombstone routes there for the
1777 // same reason: covered rows must be suppressed, and that needs each
1778 // row's seqno, which the verbatim path never decodes.
1779 // So does a predicate over a declared field: a segment may lack its
1780 // column, which reads as its declaration only once the batch is read,
1781 // and a table's own predicate would find nothing to run on. A segment
1782 // whose values are read late never gets here (see `reads_late`).
1783 let predicate_on_declared = self.predicate.as_ref().is_some_and(|p| {
1784 self.fields
1785 .iter()
1786 .any(|f| f.column_id() == p.column_id && projection::is_declared(f))
1787 });
1788 if seg.may_dup || seg.records_deletions() || predicate_on_declared || !rts.is_empty() {
1789 return self.open_singleton_dedup(seg, rts, predicate);
1790 }
1791 let range_filter = !self.range_is_full();
1792 if seg.visibility == SeqnoVisibility::All && !range_filter {
1793 // Pushed down in local coordinates (translated above); the seqno
1794 // column is globalized only on the way out.
1795 let SegmentCursor { cursor, .. } =
1796 self.segment_cursor(seg, &self.projection, predicate.as_ref(), self.budget)?;
1797 return Ok(GroupStream::Singleton(Box::new(SingletonStream {
1798 cursor,
1799 global: seg.global,
1800 mode: SingletonMode::Verbatim,
1801 })));
1802 }
1803
1804 // Decode the columns the mask needs even when the caller did not project
1805 // them (dropped again at the end): the seqno column for partial-visibility
1806 // masking, the key column for range filtering.
1807 let partial = seg.visibility == SeqnoVisibility::Partial;
1808 let mut needed = Vec::new();
1809 if partial {
1810 needed.push(COL_SEQNO);
1811 }
1812 if range_filter {
1813 needed.push(COL_USER_KEY);
1814 }
1815 let (augmented, dropped) = self.augment(&needed);
1816 // Same local-coordinate pushdown as the verbatim path above.
1817 let SegmentCursor { cursor, .. } =
1818 self.segment_cursor(seg, &augmented, predicate.as_ref(), self.budget)?;
1819 Ok(GroupStream::Singleton(Box::new(SingletonStream {
1820 cursor,
1821 global: seg.global,
1822 mode: SingletonMode::Masked {
1823 partial,
1824 // Visible iff `local < threshold` (the snapshot in this
1825 // segment's local seqno space); `Partial` guarantees the
1826 // subtraction is in range.
1827 threshold: self.seqno.saturating_sub(seg.global),
1828 dropped,
1829 },
1830 })))
1831 }
1832
1833 /// The projection extended by the `needed` columns the caller did not
1834 /// project, in order, and the list of those added columns, which are the
1835 /// scan's own and dropped from what it yields.
1836 fn augment(&self, needed: &[u16]) -> (Vec<u16>, Vec<u16>) {
1837 let mut augmented = self.projection.clone();
1838 let mut dropped = Vec::new();
1839 for &column in needed {
1840 if !augmented.contains(&column) {
1841 augmented.push(column);
1842 dropped.push(column);
1843 }
1844 }
1845 (augmented, dropped)
1846 }
1847
1848 /// One batch of a singleton group masked by seqno visibility and the key
1849 /// range, or `None` when no row of it survives.
1850 fn mask_singleton_batch(
1851 &self,
1852 batch: &ColumnBatch,
1853 global: SeqNo,
1854 partial: bool,
1855 threshold: SeqNo,
1856 dropped: &[u16],
1857 ) -> crate::Result<Option<ColumnBatch>> {
1858 let cmp = self.comparator.as_ref();
1859 let range_filter = !self.range_is_full();
1860 {
1861 let seqno_col = if partial {
1862 Some(
1863 batch
1864 .columns
1865 .iter()
1866 .find(|c| c.column_id == COL_SEQNO)
1867 .ok_or(Error::InvalidHeader(
1868 "columnar_scan: partial-visibility batch missing the seqno column",
1869 ))?,
1870 )
1871 } else {
1872 None
1873 };
1874 let key_col = if range_filter {
1875 Some(
1876 batch
1877 .columns
1878 .iter()
1879 .find(|c| c.column_id == COL_USER_KEY)
1880 .ok_or(Error::InvalidHeader(
1881 "columnar_scan: range-filtered batch missing the key column",
1882 ))?,
1883 )
1884 } else {
1885 None
1886 };
1887
1888 let mut mask = Vec::with_capacity(batch.row_count as usize);
1889 for row in 0..batch.row_count {
1890 let seqno_ok = match seqno_col {
1891 Some(seqno_col) => fixed_u64_row(&seqno_col.data, row)? < threshold,
1892 None => true,
1893 };
1894 // Evaluate the range bound only when the row survived the seqno
1895 // gate (short-circuit), so a row's key is decoded only if needed.
1896 let keep = if !seqno_ok {
1897 false
1898 } else if let Some(key_col) = key_col {
1899 let key = bytes_column_row(&key_col.data, batch.row_count, row)?;
1900 key_in_bounds(key, &self.lo, &self.hi, cmp)
1901 } else {
1902 true
1903 };
1904 mask.push(keep);
1905 }
1906 self.gather_kept(batch, &mask, dropped, global)
1907 }
1908 }
1909
1910 /// Singleton whose segment can physically hold several MVCC versions of one
1911 /// key (`Segment::may_dup`): every version is a physical row, so the scan
1912 /// must keep only the newest VISIBLE version per key instead of streaming
1913 /// the segment verbatim. Rows are stored in internal-key order (key
1914 /// ascending, seqno descending within a key), so within each key run the
1915 /// invisible too-new versions come first and the first visible row is the
1916 /// newest visible version; a run can span batch boundaries, so the last
1917 /// kept key carries across batches. The predicate runs AFTER dedup
1918 /// (mirroring the merge of an overlapping group, [`merge::MergeStream`]): a
1919 /// key whose newest version fails the
1920 /// predicate is dropped, never served from an older matching version —
1921 /// which also rules out predicate-driven zone-map block-skip here.
1922 ///
1923 /// `predicate` is the scan's, in this segment's local coordinates: it runs
1924 /// before the seqno column is globalized.
1925 fn open_singleton_dedup(
1926 &self,
1927 seg: &Segment,
1928 rts: Vec<(UserKey, UserKey, SeqNo)>,
1929 predicate: Option<ColumnRangePredicate>,
1930 ) -> crate::Result<GroupStream> {
1931 // Decode the columns the dedup needs even when the caller did not
1932 // project them (dropped again at the end): the key column always, the
1933 // seqno column when the snapshot straddles the segment OR a range
1934 // tombstone needs each row's age, the predicate column for the
1935 // after-dedup filter.
1936 let partial = seg.visibility == SeqnoVisibility::Partial;
1937 let mut needed = vec![COL_USER_KEY];
1938 if partial || !rts.is_empty() {
1939 needed.push(COL_SEQNO);
1940 }
1941 if let Some(pred) = &predicate {
1942 needed.push(pred.column_id);
1943 }
1944 // A deletion is what a key's newest row can BE, so deciding a run needs
1945 // the value type — otherwise a tombstone decides the run and is emitted
1946 // as a row while a point read calls the key absent, and a caller that did
1947 // not project the type column cannot tell that row from a live one with
1948 // an empty value. Decoded only for a segment that RECORDS deletions; one
1949 // without them keeps its columns untouched.
1950 let deletes = seg.records_deletions();
1951 if deletes {
1952 needed.push(COL_VALUE_TYPE);
1953 }
1954 let (augmented, dropped) = self.augment(&needed);
1955 // No predicate pushed down: it runs after the dedup (see above).
1956 let SegmentCursor { cursor, .. } =
1957 self.segment_cursor(seg, &augmented, None, self.budget)?;
1958 Ok(GroupStream::Singleton(Box::new(SingletonStream {
1959 cursor,
1960 global: seg.global,
1961 mode: SingletonMode::Dedup(DedupState {
1962 predicate,
1963 rts: TombstoneSweep::new(rts, self.comparator.as_ref()),
1964 partial,
1965 deletes,
1966 // Visible iff `local < threshold` (the snapshot in this
1967 // segment's local seqno space); `Partial` guarantees the
1968 // subtraction is in range.
1969 threshold: self.seqno.saturating_sub(seg.global),
1970 dropped,
1971 last_key: None,
1972 }),
1973 })))
1974 }
1975
1976 /// One batch of a singleton group deduped to the newest visible version of
1977 /// each key, deletions consumed and the predicate applied after, or `None`
1978 /// when no row of it survives. The key run it last decided is carried in
1979 /// `state` across batches.
1980 fn dedup_singleton_batch(
1981 &self,
1982 batch: &ColumnBatch,
1983 global: SeqNo,
1984 state: &mut DedupState,
1985 support: &mut PredicateSupport,
1986 ) -> crate::Result<Option<ColumnBatch>> {
1987 let (partial, deletes, threshold) = (state.partial, state.deletes, state.threshold);
1988 let seqno_needed = partial || !state.rts.is_empty();
1989 let range_filter = !self.range_is_full();
1990 let cmp = self.comparator.as_ref();
1991 {
1992 let key_col = batch
1993 .columns
1994 .iter()
1995 .find(|c| c.column_id == COL_USER_KEY)
1996 .ok_or(Error::InvalidHeader(
1997 "columnar_scan: dedup batch missing the key column",
1998 ))?;
1999 let vt_col = if deletes {
2000 Some(
2001 batch
2002 .columns
2003 .iter()
2004 .find(|c| c.column_id == COL_VALUE_TYPE)
2005 .ok_or(Error::InvalidHeader(
2006 "columnar_scan: dedup batch missing the value-type column",
2007 ))?,
2008 )
2009 } else {
2010 None
2011 };
2012 let seqno_col = if seqno_needed {
2013 Some(
2014 batch
2015 .columns
2016 .iter()
2017 .find(|c| c.column_id == COL_SEQNO)
2018 .ok_or(Error::InvalidHeader(
2019 "columnar_scan: dedup batch missing the seqno column",
2020 ))?,
2021 )
2022 } else {
2023 None
2024 };
2025
2026 // The predicate judges the deduped survivors only (see doc): a row
2027 // is judged where it survives the dedup, into the same mask, so the
2028 // rows are gathered once.
2029 if let Some(pred) = &state.predicate {
2030 *support = (*support).min(pred.support_in(batch));
2031 }
2032 let matcher = state
2033 .predicate
2034 .as_ref()
2035 .filter(|p| p.apply == PredicateApply::Filter)
2036 .map(|p| p.matcher(batch));
2037 let mut mask = Vec::with_capacity(batch.row_count as usize);
2038 for row in 0..batch.row_count {
2039 let local = match seqno_col {
2040 Some(seqno_col) => Some(fixed_u64_row(&seqno_col.data, row)?),
2041 None => None,
2042 };
2043 let visible = !partial || local.is_some_and(|l| l < threshold);
2044 if !visible {
2045 mask.push(false);
2046 continue;
2047 }
2048 let key = bytes_column_row(&key_col.data, batch.row_count, row)?;
2049 if state
2050 .last_key
2051 .as_deref()
2052 .is_some_and(|last| cmp.compare(last, key) == core::cmp::Ordering::Equal)
2053 {
2054 // A later visible version of an already-decided key run —
2055 // shadowed by the newest visible version above it.
2056 mask.push(false);
2057 continue;
2058 }
2059 // First visible row of a new key run = the newest visible
2060 // version. Deciding the run here (even when the range filter or
2061 // a deletion drops the row) also drops its older versions above.
2062 let last = state.last_key.get_or_insert_with(Vec::new);
2063 last.clear();
2064 last.extend_from_slice(key);
2065 // A visible range tombstone deletes the run when it covers the
2066 // NEWEST visible version (older versions are older still); an
2067 // uncovered newest version shadows the covered older ones, so
2068 // deciding on it alone is exact.
2069 if !state.rts.is_empty() {
2070 let eff =
2071 local
2072 .unwrap_or(0)
2073 .checked_add(global)
2074 .ok_or(Error::InvalidHeader(
2075 "columnar_scan: effective seqno overflows",
2076 ))?;
2077 if state.rts.covers(key, eff, cmp) {
2078 mask.push(false);
2079 continue;
2080 }
2081 }
2082 if let Some(vt_col) = vt_col {
2083 let byte = *vt_col.data.get(row as usize).ok_or(Error::InvalidHeader(
2084 "columnar_scan: value-type column shorter than the row count",
2085 ))?;
2086 let value_type = crate::ValueType::try_from(byte)
2087 .map_err(|()| Error::InvalidTag(("ValueType", byte)))?;
2088 if value_type.is_tombstone() {
2089 // The key is GONE as of this row, so the run yields
2090 // nothing: emitting the tombstone would surface a key the
2091 // point read reports absent.
2092 mask.push(false);
2093 continue;
2094 }
2095 }
2096 mask.push(
2097 (!range_filter || key_in_bounds(key, &self.lo, &self.hi, cmp))
2098 && matcher.as_ref().is_none_or(|m| m.matches(row)),
2099 );
2100 }
2101
2102 // Match the singleton contract: yield exactly the projected columns.
2103 self.gather_kept(batch, &mask, &state.dropped, global)
2104 }
2105 }
2106}
2107
2108/// `pred` in the LOCAL coordinates of a segment based at `global`, or `None`
2109/// when no row of that segment can match it.
2110///
2111/// A segment's table stores each row's seqno locally (a bulk-ingested one at
2112/// `0`, with one `global_seqno` for all of them) and the scan speaks effective
2113/// ones (`local + global`), so a bound on the seqno column is moved down by
2114/// `global` before the table's statistics or its row filter see it. A predicate
2115/// on any other column is passed as it is.
2116fn localize(pred: &ColumnRangePredicate, global: SeqNo) -> Option<Cow<'_, ColumnRangePredicate>> {
2117 if pred.column_id != COL_SEQNO || global == 0 {
2118 return Some(Cow::Borrowed(pred));
2119 }
2120 // An unsigned column's ordinal is its value.
2121 let (lo, hi) = pred.ordinal_span(Number::U64_LE)?;
2122 let global = u128::from(global);
2123 // Every row of the segment is at `global` or above: an upper bound below
2124 // it admits none, and a lower bound below it admits them from the first,
2125 // which is exactly the clamp to local `0`.
2126 let hi = hi.checked_sub(global)?;
2127 let lo = lo.saturating_sub(global);
2128 // Both are below `2^64`: the span is of 8-byte values.
2129 let bound = |v: u128| u64::try_from(v).map(|v| v.to_be_bytes().to_vec());
2130 Some(Cow::Owned(ColumnRangePredicate {
2131 column_id: COL_SEQNO,
2132 lower: Some(bound(lo).ok()?),
2133 upper: Some(bound(hi).ok()?),
2134 apply: pred.apply,
2135 }))
2136}
2137
2138/// Whether `key` lies within the requested `[lo, hi]` key bounds, per the tree
2139/// comparator. An unbounded side never excludes; the inclusive / exclusive sense
2140/// of each bound matches the `RangeBounds` the caller passed.
2141fn key_in_bounds(
2142 key: &[u8],
2143 lo: &Bound<UserKey>,
2144 hi: &Bound<UserKey>,
2145 cmp: &dyn UserComparator,
2146) -> bool {
2147 use core::cmp::Ordering;
2148 let above_lo = match lo {
2149 Bound::Unbounded => true,
2150 Bound::Included(k) => cmp.compare(key, k.as_ref()) != Ordering::Less,
2151 Bound::Excluded(k) => cmp.compare(key, k.as_ref()) == Ordering::Greater,
2152 };
2153 let below_hi = match hi {
2154 Bound::Unbounded => true,
2155 Bound::Included(k) => cmp.compare(key, k.as_ref()) != Ordering::Greater,
2156 Bound::Excluded(k) => cmp.compare(key, k.as_ref()) == Ordering::Less,
2157 };
2158 above_lo && below_hi
2159}
2160
2161impl Iterator for ColumnarScan {
2162 type Item = crate::Result<ColumnBatch>;
2163
2164 fn next(&mut self) -> Option<Self::Item> {
2165 loop {
2166 let mut support = self.support;
2167 if let Some(mut stream) = self.current.take() {
2168 let next = self.next_from(&mut stream, &mut support);
2169 self.support = support;
2170 self.observe_payload(stream.held_bytes());
2171 self.record_oversized(stream.take_oversized());
2172 match next {
2173 Some(Ok(batch)) => {
2174 self.current = Some(stream);
2175 #[cfg(feature = "metrics")]
2176 self.metrics.record_materialized(batch.data_size());
2177 return Some(Ok(batch));
2178 }
2179 // A failed group yields its error and is dropped; the next
2180 // call moves on to the next group.
2181 Some(Err(e)) => return Some(Err(e)),
2182 None => continue,
2183 }
2184 }
2185 let group = self.groups.pop_front()?;
2186 let opened = self.open_group(&group, &mut support);
2187 self.support = support;
2188 match opened {
2189 Ok(stream) => self.current = Some(stream),
2190 Err(e) => return Some(Err(e)),
2191 }
2192 }
2193 }
2194}
2195
2196/// Clones a borrowed key bound into an owned one.
2197fn clone_bound(bound: Bound<&UserKey>) -> Bound<UserKey> {
2198 match bound {
2199 Bound::Included(k) => Bound::Included(k.clone()),
2200 Bound::Excluded(k) => Bound::Excluded(k.clone()),
2201 Bound::Unbounded => Bound::Unbounded,
2202 }
2203}
2204
2205/// Borrows an owned key bound as a byte-slice bound for key-range overlap checks.
2206fn bound_as_ref(bound: &Bound<UserKey>) -> Bound<&[u8]> {
2207 match bound {
2208 Bound::Included(k) => Bound::Included(k.as_ref()),
2209 Bound::Excluded(k) => Bound::Excluded(k.as_ref()),
2210 Bound::Unbounded => Bound::Unbounded,
2211 }
2212}
2213
2214#[cfg(all(test, feature = "metrics"))]
2215mod tests;