use core::ops::{Bound, RangeBounds};
use alloc::{vec, vec::Vec};
use alloc::borrow::Cow;
use crate::comparator::UserComparator;
use crate::table::SeqnoVisibility;
use crate::table::columnar::{
COL_SEQNO, COL_USER_KEY, COL_VALUE_TYPE, ColumnBatch, Number, TypeTag, bytes_column_row,
fixed_u64_row,
};
use crate::table::columnar_predicate::{
ColumnRangePredicate, PredicateApply, PredicateSupport, filter_batch, take_column,
take_validity,
};
use crate::{Error, SeqNo, Table, Tree, UserKey};
mod cells;
mod merge;
pub mod projection;
mod rows;
use rows::{RowCursor, SourceCursor};
enum Source {
Columnar(Table),
RowTable(Table),
Memtable(alloc::sync::Arc<crate::memtable::Memtable>),
}
struct Segment {
source: Source,
min: UserKey,
max: UserKey,
global: SeqNo,
visibility: SeqnoVisibility,
may_dup: bool,
recency_rank: usize,
whole: bool,
cells: bool,
}
impl Segment {
fn is_rows(&self) -> bool {
!matches!(self.source, Source::Columnar(_))
}
fn records_deletions(&self) -> bool {
match &self.source {
Source::Columnar(table) | Source::RowTable(table) => {
table.tombstone_count() > 0 || table.weak_tombstone_count() > 0
}
Source::Memtable(_) => true,
}
}
fn rt_base(&self) -> SeqNo {
match &self.source {
Source::Columnar(_) => self.global,
Source::RowTable(table) => table.global_seqno(),
Source::Memtable(_) => 0,
}
}
fn range_tombstones(&self) -> Vec<crate::range_tombstone::RangeTombstone> {
match &self.source {
Source::Columnar(table) | Source::RowTable(table) => table
.visible_range_tombstones()
.map(alloc::borrow::Cow::into_owned)
.collect(),
Source::Memtable(memtable) => memtable.range_tombstones_sorted(),
}
}
}
struct Group {
segments: Vec<Segment>,
max: UserKey,
}
impl Tree {
pub fn columnar_scan<R: RangeBounds<UserKey>>(
&self,
projection: impl Into<projection::Projection>,
predicate: Option<&ColumnRangePredicate>,
seqno: SeqNo,
range: R,
) -> crate::Result<ColumnarScan> {
let projection = projection.into();
let fields = projection.fields();
if fields.iter().enumerate().any(|(at, f)| {
fields
.get(..at)
.is_some_and(|before| before.iter().any(|b| b.column_id() == f.column_id()))
}) {
return Err(Error::Projection(
"projection: a column id is projected twice",
));
}
if fields
.iter()
.any(|f| f.column_id() == merge::COL_WHOLE_VALUE)
|| predicate.is_some_and(|p| p.column_id == merge::COL_WHOLE_VALUE)
{
return Err(Error::Projection(
"projection: the highest column id is reserved for the scan",
));
}
let comparator = self.config.comparator.clone();
let lo = clone_bound(range.start_bound());
let hi = clone_bound(range.end_bound());
let bounds_ref = (bound_as_ref(&lo), bound_as_ref(&hi));
let super_version = self.get_version_for_snapshot(seqno)?;
super_version.version.read_mix().note_scan();
let highest = super_version
.active_memtable
.get_highest_seqno()
.max(
super_version
.sealed_memtables
.iter()
.filter_map(|memtable| memtable.get_highest_seqno())
.max(),
)
.max(
super_version
.version
.iter_tables()
.map(crate::Table::get_highest_seqno)
.max(),
);
let seqno = match highest {
Some(highest) => highest
.checked_add(1)
.map_or(seqno, |next: SeqNo| seqno.min(next)),
None => 0,
};
let blob_source = self.blob_source();
let resolver = self.config.merge_operator.clone().map(|operator| Resolver {
version: super_version.clone(),
operator,
blob_source: blob_source.clone(),
});
let cells = blob_source.map(|source| cells::CellSource {
version: super_version.clone(),
source,
});
if cells.is_some() {
let intrinsic = |id: u16| matches!(id, COL_USER_KEY | COL_SEQNO | COL_VALUE_TYPE);
let loose = |id: u16| {
!intrinsic(id)
&& fields
.iter()
.all(|f| f.column_id() != id || !projection::is_declared(f))
};
if fields.iter().any(|f| loose(f.column_id()))
|| predicate.is_some_and(|p| loose(p.column_id))
{
return Err(Error::Projection(
"projection: a blob tree's fields are projected as declared fields",
));
}
}
let declared = projection.fields().iter().any(projection::is_declared);
let mut segments: Vec<Segment> = Vec::new();
let memtables = core::iter::once(super_version.active_memtable.clone())
.chain(super_version.sealed_memtables.iter().rev().cloned());
let mut recency_rank = 0;
for memtable in memtables {
if let Some((min, max)) = memtable_span(&memtable, &lo, &hi, comparator.as_ref()) {
segments.push(Segment {
min,
max,
global: 0,
visibility: SeqnoVisibility::All,
may_dup: true,
recency_rank,
whole: true,
cells: false,
source: Source::Memtable(memtable),
});
}
recency_rank += 1;
}
for (rank, table) in super_version.version.iter_tables().enumerate() {
let recency_rank = recency_rank + rank;
if !table.check_key_range_overlap_cmp(&bounds_ref, comparator.as_ref()) {
continue;
}
let visibility = table.seqno_visibility(seqno);
if visibility == SeqnoVisibility::None {
continue;
}
let key_range = &table.metadata.key_range;
if !table.metadata.columnar {
segments.push(Segment {
min: key_range.min().clone(),
max: key_range.max().clone(),
global: 0,
visibility: SeqnoVisibility::All,
may_dup: true,
recency_rank,
whole: true,
cells: false,
source: Source::RowTable(table.clone()),
});
continue;
}
let may_dup = table
.metadata
.key_count
.is_none_or(|k| k != table.metadata.item_count);
segments.push(Segment {
min: key_range.min().clone(),
max: key_range.max().clone(),
global: table.global_seqno(),
visibility,
may_dup,
recency_rank,
whole: table.metadata.value_layout == crate::table::meta::ValueLayout::Whole,
cells: table.metadata.value_layout == crate::table::meta::ValueLayout::Cells,
source: Source::Columnar(table.clone()),
});
}
let groups = group_by_overlap(segments, comparator.as_ref());
Ok(ColumnarScan {
groups: groups.into_iter().collect(),
current: None,
projection: projection.column_ids(),
fields: projection.fields().to_vec(),
projector: declared
.then(|| projection.value_projector().cloned())
.flatten(),
declared,
resolver,
cells,
predicate: predicate.cloned(),
support: PredicateSupport::Exact,
comparator,
seqno,
lo,
hi,
budget: self.config.columnar_scan_budget,
peak_payload: core::cell::Cell::new(0),
oversized: core::cell::Cell::new(0),
#[cfg(feature = "metrics")]
metrics: self.0.metrics.clone(),
})
}
}
fn memtable_span(
memtable: &crate::memtable::Memtable,
lo: &Bound<UserKey>,
hi: &Bound<UserKey>,
cmp: &dyn UserComparator,
) -> Option<(UserKey, UserKey)> {
use core::cmp::Ordering;
let (ilo, ihi) = crate::range::internal_bounds(lo, hi);
let mut rows = memtable.range_internal((ilo, ihi));
let first = rows.next().map(|row| row.key.user_key);
let last = rows
.next_back()
.map(|row| row.key.user_key)
.or_else(|| first.clone());
let mut span = first.zip(last);
let bounds = (lo.clone(), hi.clone());
for rt in memtable.range_tombstones_sorted() {
if !crate::range::range_tombstone_overlaps_bounds(&rt, &bounds, cmp) {
continue;
}
span = Some(match span {
None => (rt.start.clone(), rt.end.clone()),
Some((min, max)) => (
if cmp.compare(&rt.start, &min) == Ordering::Less {
rt.start.clone()
} else {
min
},
if cmp.compare(&rt.end, &max) == Ordering::Greater {
rt.end.clone()
} else {
max
},
),
});
}
span
}
fn tighter_lower(
a: Bound<UserKey>,
b: &Bound<UserKey>,
cmp: &dyn UserComparator,
) -> Bound<UserKey> {
use core::cmp::Ordering;
match (&a, b) {
(_, Bound::Unbounded) => a,
(Bound::Unbounded, _) => b.clone(),
(Bound::Included(x) | Bound::Excluded(x), Bound::Included(y) | Bound::Excluded(y)) => {
match cmp.compare(x, y) {
Ordering::Less => b.clone(),
Ordering::Equal if matches!(b, Bound::Excluded(_)) => b.clone(),
Ordering::Greater | Ordering::Equal => a,
}
}
}
}
fn tighter_upper(
a: Bound<UserKey>,
b: &Bound<UserKey>,
cmp: &dyn UserComparator,
) -> Bound<UserKey> {
use core::cmp::Ordering;
match (&a, b) {
(_, Bound::Unbounded) => a,
(Bound::Unbounded, _) => b.clone(),
(Bound::Included(x) | Bound::Excluded(x), Bound::Included(y) | Bound::Excluded(y)) => {
match cmp.compare(x, y) {
Ordering::Greater => b.clone(),
Ordering::Equal if matches!(b, Bound::Excluded(_)) => b.clone(),
Ordering::Less | Ordering::Equal => a,
}
}
}
}
fn group_by_overlap(mut segments: Vec<Segment>, cmp: &dyn UserComparator) -> Vec<Group> {
use core::cmp::Ordering;
segments.sort_by(|a, b| cmp.compare(a.min.as_ref(), b.min.as_ref()));
let mut groups: Vec<Group> = Vec::new();
for seg in segments {
match groups.last_mut() {
Some(g) if cmp.compare(seg.min.as_ref(), g.max.as_ref()) != Ordering::Greater => {
if cmp.compare(seg.max.as_ref(), g.max.as_ref()) == Ordering::Greater {
g.max = seg.max.clone();
}
g.segments.push(seg);
}
_ => groups.push(Group {
max: seg.max.clone(),
segments: vec![seg],
}),
}
}
groups
}
enum SingletonMode {
Verbatim,
Masked {
partial: bool,
threshold: SeqNo,
dropped: Vec<u16>,
},
Dedup(DedupState),
}
struct DedupState {
predicate: Option<ColumnRangePredicate>,
rts: TombstoneSweep,
partial: bool,
deletes: bool,
threshold: SeqNo,
dropped: Vec<u16>,
last_key: Option<Vec<u8>>,
}
pub(super) struct SegmentCursor {
cursor: SourceCursor,
whole: bool,
ids: Vec<u16>,
}
struct SingletonStream {
cursor: SourceCursor,
global: SeqNo,
mode: SingletonMode,
}
enum GroupStream {
Singleton(Box<SingletonStream>),
Merge(Box<merge::MergeStream>),
Empty,
}
impl GroupStream {
fn held_bytes(&self) -> u64 {
match self {
Self::Singleton(singleton) => singleton.cursor.held_bytes(),
Self::Merge(merge) => merge.held_bytes(),
Self::Empty => 0,
}
}
fn take_oversized(&mut self) -> u64 {
match self {
Self::Singleton(singleton) => singleton.cursor.take_oversized(),
Self::Merge(merge) => merge.take_oversized(),
Self::Empty => 0,
}
}
}
#[derive(Debug, Default)]
pub(super) struct Resolved {
keys: Vec<crate::Slice>,
unreadable: Vec<crate::Slice>,
}
impl Resolved {
pub(super) fn any(&self) -> bool {
!self.keys.is_empty()
}
pub(super) fn holds(&self, key: &[u8]) -> bool {
self.keys.binary_search_by(|k| (**k).cmp(key)).is_ok()
}
pub(super) fn unreadable(&self, key: &[u8]) -> bool {
self.unreadable.binary_search_by(|k| (**k).cmp(key)).is_ok()
}
}
pub(super) const UNREADABLE_BY_ID: Error = Error::Projection(
"projection: a merged value is read whole, and a field projected by id alone cannot be \
read out of it",
);
const MISSING_BATCH_COLUMN: Error =
Error::InvalidHeader("columnar_scan: a batch is missing a column the scan decoded");
fn holds_operand(batch: &ColumnBatch) -> bool {
value_types(batch).is_some_and(|types| {
types
.iter()
.take(batch.row_count as usize)
.copied()
.any(is_operand)
})
}
pub(super) fn value_types(batch: &ColumnBatch) -> Option<&[u8]> {
batch
.columns
.iter()
.find(|c| c.column_id == COL_VALUE_TYPE)
.map(|c| &*c.data)
}
pub(super) fn is_operand(byte: u8) -> bool {
crate::ValueType::try_from(byte) == Ok(crate::ValueType::MergeOperand)
}
pub(super) struct TombstoneSweep {
rts: Vec<(UserKey, UserKey, SeqNo)>,
next: usize,
open: alloc::collections::BinaryHeap<(SeqNo, usize)>,
}
impl TombstoneSweep {
pub(super) fn new(mut rts: Vec<(UserKey, UserKey, SeqNo)>, cmp: &dyn UserComparator) -> Self {
rts.sort_by(|a, b| cmp.compare(&a.0, &b.0));
Self {
rts,
next: 0,
open: alloc::collections::BinaryHeap::new(),
}
}
pub(super) fn is_empty(&self) -> bool {
self.rts.is_empty()
}
pub(super) fn covers(&mut self, key: &[u8], eff: SeqNo, cmp: &dyn UserComparator) -> bool {
use core::cmp::Ordering;
while let Some((start, _, seqno)) = self.rts.get(self.next)
&& cmp.compare(start, key) != Ordering::Greater
{
self.open.push((*seqno, self.next));
self.next += 1;
}
while let Some(&(_, at)) = self.open.peek() {
let passed = self
.rts
.get(at)
.is_none_or(|(_, end, _)| cmp.compare(key, end) != Ordering::Less);
if !passed {
break;
}
self.open.pop();
}
self.open.peek().is_some_and(|&(seqno, _)| eff < seqno)
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(super) enum PredicateTiming {
BeforeValues,
BeforeValuesExceptOperands,
AfterValues,
}
fn drop_columns(batch: &mut ColumnBatch, dropped: &[u16]) {
if !dropped.is_empty() {
batch.columns.retain(|c| !dropped.contains(&c.column_id));
}
}
struct Resolver {
version: crate::version::SuperVersion,
operator: alloc::sync::Arc<dyn crate::merge_operator::MergeOperator>,
blob_source: Option<crate::blob_tree::BlobSource>,
}
pub struct ColumnarScan {
groups: alloc::collections::VecDeque<Group>,
current: Option<GroupStream>,
projection: Vec<u16>,
fields: Vec<projection::ProjectedField>,
projector: Option<alloc::sync::Arc<dyn projection::ValueProjector>>,
declared: bool,
resolver: Option<Resolver>,
cells: Option<cells::CellSource>,
predicate: Option<ColumnRangePredicate>,
support: PredicateSupport,
comparator: alloc::sync::Arc<dyn UserComparator>,
seqno: SeqNo,
lo: Bound<UserKey>,
hi: Bound<UserKey>,
budget: u64,
peak_payload: core::cell::Cell<u64>,
oversized: core::cell::Cell<u64>,
#[cfg(feature = "metrics")]
metrics: alloc::sync::Arc<crate::Metrics>,
}
impl ColumnarScan {
#[must_use]
pub fn peak_payload_bytes(&self) -> u64 {
self.peak_payload.get()
}
#[must_use]
pub fn oversized_reads(&self) -> u64 {
self.oversized.get()
}
fn observe_payload(&self, bytes: u64) {
if bytes > self.peak_payload.get() {
self.peak_payload.set(bytes);
}
}
fn record_oversized(&self, count: u64) {
self.oversized.set(self.oversized.get() + count);
}
#[must_use]
pub fn predicate_support(&self) -> Option<PredicateSupport> {
self.predicate.as_ref().map(|_| self.support)
}
#[inline]
#[cfg_attr(
not(feature = "metrics"),
expect(
clippy::unused_self,
reason = "the scan's metrics exist only with the feature"
)
)]
fn record_gather(&self, batch: &ColumnBatch) {
#[cfg(feature = "metrics")]
self.metrics.record_gather(batch.data_size());
#[cfg(not(feature = "metrics"))]
let _ = batch;
}
#[inline]
#[cfg_attr(
not(feature = "metrics"),
expect(
clippy::unused_self,
reason = "the scan's metrics exist only with the feature"
)
)]
fn record_payload(&self, useful: u64, incidental: u64) {
#[cfg(feature = "metrics")]
{
self.metrics.record_payload_useful(useful);
self.metrics.record_payload_incidental(incidental);
}
#[cfg(not(feature = "metrics"))]
let _ = (useful, incidental);
}
#[inline]
#[cfg_attr(
not(feature = "metrics"),
expect(
clippy::unused_self,
reason = "the scan's metrics exist only with the feature"
)
)]
fn record_copied(&self, bytes: usize) {
#[cfg(feature = "metrics")]
self.metrics.record_gather(bytes);
#[cfg(not(feature = "metrics"))]
let _ = bytes;
}
}
impl ColumnarScan {
fn open_group(
&self,
group: &Group,
support: &mut PredicateSupport,
) -> crate::Result<GroupStream> {
let rts = self.visible_group_range_tombstones(&group.segments)?;
if let [seg] = group.segments.as_slice()
&& !seg.is_rows()
&& !self.reads_late(seg)
{
return self.open_singleton(seg, rts, support);
}
Ok(GroupStream::Merge(Box::new(merge::MergeStream::open(
self,
&group.segments,
rts,
)?)))
}
fn reads_late(&self, seg: &Segment) -> bool {
if self.cells.is_some() {
return true;
}
self.resolver.is_some() || (seg.whole && self.declared)
}
fn segment_cursor(
&self,
seg: &Segment,
projection: &[u16],
predicate: Option<&ColumnRangePredicate>,
share: u64,
) -> crate::Result<SegmentCursor> {
let needs_values = self.declared || self.resolver.is_some();
let whole = seg.whole && needs_values;
let mut ids: Vec<u16> = projection.to_vec();
if seg.cells && needs_values {
use crate::blob_tree::field_row::CELL_REFS_COLUMN;
for id in [CELL_REFS_COLUMN, merge::COL_WHOLE_VALUE] {
if !ids.contains(&id) {
ids.push(id);
}
}
} else if whole {
let declared: Vec<u16> = self
.fields
.iter()
.filter(|f| projection::is_declared(f))
.map(projection::ProjectedField::column_id)
.collect();
ids.retain(|id| !declared.contains(id));
if !ids.contains(&crate::table::columnar::COL_VALUE) {
ids.push(crate::table::columnar::COL_VALUE);
}
}
let predicate = predicate.filter(|_| !whole);
if let Source::Columnar(table) | Source::RowTable(table) = &seg.source {
table.note_scan();
}
let cursor = match &seg.source {
Source::Columnar(table) => SourceCursor::Columnar(Box::new(table.columnar_cursor(
&ids,
predicate,
self.lo.clone(),
self.hi.clone(),
Some(share),
)?)),
Source::RowTable(table) => SourceCursor::Rows(Box::new(RowCursor::table(
table,
self.lo.clone(),
self.hi.clone(),
self.seqno,
ids.clone(),
share,
))),
Source::Memtable(memtable) => {
let cmp = self.comparator.as_ref();
let lo = tighter_lower(Bound::Included(seg.min.clone()), &self.lo, cmp);
let hi = tighter_upper(Bound::Included(seg.max.clone()), &self.hi, cmp);
SourceCursor::Rows(Box::new(RowCursor::memtable(
memtable.clone(),
&lo,
&hi,
self.seqno,
ids.clone(),
share,
)))
}
};
Ok(SegmentCursor { cursor, whole, ids })
}
fn late_payload(&self, seg: &Segment) -> Vec<(u16, u16)> {
use crate::blob_tree::field_row::CELL_REFS_COLUMN;
use crate::table::columnar::COL_VALUE;
if seg.is_rows() || self.resolver.is_some() || self.raw_value_read() {
return Vec::new();
}
let predicate = self.predicate.as_ref().map(|p| p.column_id);
let fields = self
.fields
.iter()
.filter(|f| projection::is_declared(f) && Some(f.column_id()) != predicate)
.map(|f| (f.column_id(), f.column_id()));
if seg.cells && self.declared {
fields
.chain([
(CELL_REFS_COLUMN, CELL_REFS_COLUMN),
(merge::COL_WHOLE_VALUE, merge::COL_WHOLE_VALUE),
])
.collect()
} else if seg.whole {
if self.declared {
alloc::vec![(COL_VALUE, merge::COL_WHOLE_VALUE)]
} else {
Vec::new()
}
} else {
fields.collect()
}
}
pub(super) fn read_late(
&self,
batch: ColumnBatch,
judged: bool,
) -> crate::Result<(ColumnBatch, Resolved)> {
let (batch, resolved) = self.resolve_operands(batch)?;
if let Some(cells) = &self.cells {
return Ok((cells::materialize(self, cells, batch, judged)?, resolved));
}
let mut batch = if self.declared {
projection::project_decided(
batch,
&self.fields,
self.projector.as_deref(),
merge::COL_WHOLE_VALUE,
)?
} else {
batch
};
drop_columns(&mut batch, &[merge::COL_WHOLE_VALUE]);
Ok((batch, resolved))
}
fn resolve_operands(&self, batch: ColumnBatch) -> crate::Result<(ColumnBatch, Resolved)> {
use crate::table::columnar::{COL_VALUE, Column, frame_bytes_column};
let Some(resolver) = &self.resolver else {
return Ok((batch, Resolved::default()));
};
if !holds_operand(&batch) {
return Ok((batch, Resolved::default()));
}
let row_count = batch.row_count;
let rows = row_count as usize;
let find = |id: u16| batch.columns.iter().position(|c| c.column_id == id);
let (Some(key_at), Some(type_at), Some(whole_at)) = (
find(COL_USER_KEY),
find(COL_VALUE_TYPE),
find(merge::COL_WHOLE_VALUE),
) else {
return Err(MISSING_BATCH_COLUMN);
};
let raw_at = find(COL_VALUE).filter(|&at| {
batch
.columns
.get(at)
.is_some_and(|c| c.type_tag == TypeTag::Bytes)
&& self.raw_value_read()
});
let column = |at: usize| batch.columns.get(at).ok_or(MISSING_BATCH_COLUMN);
let (keys, kinds) = (column(key_at)?, column(type_at)?);
let mut resolved: Vec<Option<Option<crate::Slice>>> = Vec::with_capacity(rows);
for row in 0..row_count {
let byte = *kinds.data.get(row as usize).ok_or(MISSING_BATCH_COLUMN)?;
if crate::ValueType::try_from(byte) != Ok(crate::ValueType::MergeOperand) {
resolved.push(None);
continue;
}
let key = bytes_column_row(&keys.data, row_count, row)?;
resolved.push(Some(Tree::resolve_or_passthrough(
&resolver.version,
key,
self.seqno,
Some(&resolver.operator),
self.comparator.as_ref(),
|| resolver.blob_source.clone(),
)?));
}
let whole = column(whole_at)?;
let by_id: Vec<u16> = self
.fields
.iter()
.filter(|f| {
f.type_tag().is_none() && batch.columns.iter().any(|c| c.column_id == f.column_id())
})
.map(projection::ProjectedField::column_id)
.collect();
let mut outcome = Resolved::default();
for (row, fix) in (0..row_count).zip(&resolved) {
if !matches!(fix, Some(Some(_))) {
continue;
}
let key = crate::Slice::from(bytes_column_row(&keys.data, row_count, row)?);
let raw_rewritten = raw_at.is_some() && whole.is_valid(row);
if by_id.iter().any(|&id| !(id == COL_VALUE && raw_rewritten)) {
outcome.unreadable.push(key.clone());
}
outcome.keys.push(key);
}
outcome.keys.sort_unstable();
outcome.unreadable.sort_unstable();
let returned = |row: &u32| !matches!(resolved.get(*row as usize), Some(Some(None)));
let kept: Option<Vec<u32>> = resolved
.iter()
.any(|fix| matches!(fix, Some(None)))
.then(|| (0..row_count).filter(returned).collect());
let out_rows = kept.as_ref().map_or(rows, Vec::len);
let rewrite = |at: usize| -> crate::Result<Column> {
let old = column(at)?;
let mut cells: Vec<Option<&[u8]>> = Vec::with_capacity(out_rows);
for row in (0..row_count).filter(returned) {
cells.push(match resolved.get(row as usize) {
Some(Some(Some(value))) => Some(&**value),
_ if old.is_valid(row) => Some(bytes_column_row(&old.data, row_count, row)?),
_ => None,
});
}
let validity = cells.iter().any(Option::is_none).then(|| {
let mut bits = alloc::vec![0u8; out_rows.div_ceil(8)];
for (row, cell) in cells.iter().enumerate() {
if cell.is_some()
&& let Some(byte) = bits.get_mut(row / 8)
{
*byte |= 1 << (row % 8);
}
}
bits
});
Ok(Column {
column_id: old.column_id,
type_tag: TypeTag::Bytes,
validity,
data: frame_bytes_column(out_rows, || cells.iter().map(|c| c.unwrap_or(&[])))?,
})
};
let whole = rewrite(whole_at)?;
let raw = raw_at.map(rewrite).transpose()?;
let mut types: Vec<u8> = Vec::with_capacity(out_rows);
for row in (0..row_count).filter(returned) {
types.push(match resolved.get(row as usize) {
Some(Some(_)) => u8::from(crate::ValueType::Value),
_ => *kinds.data.get(row as usize).ok_or(MISSING_BATCH_COLUMN)?,
});
}
let mut batch = batch;
if let Some(kept) = &kept {
for (at, slot) in batch.columns.iter_mut().enumerate() {
if at == whole_at || raw_at == Some(at) {
continue;
}
if at == type_at {
slot.validity = slot
.validity
.as_deref()
.map(|bits| take_validity(bits, kept));
continue;
}
*slot = take_column(slot, rows, kept)?;
}
}
if let Some(slot) = batch.columns.get_mut(whole_at) {
*slot = whole;
}
if let (Some(at), Some(raw)) = (raw_at, raw)
&& let Some(slot) = batch.columns.get_mut(at)
{
*slot = raw;
}
if let Some(slot) = batch.columns.get_mut(type_at) {
slot.data = crate::Slice::from(types);
}
batch.row_count = u32::try_from(out_rows).unwrap_or(row_count);
self.record_gather(&batch);
Ok((batch, outcome))
}
fn next_from(
&self,
stream: &mut GroupStream,
support: &mut PredicateSupport,
) -> Option<crate::Result<ColumnBatch>> {
match stream {
GroupStream::Empty => None,
GroupStream::Merge(merge) => merge.next_batch(self, support).transpose(),
GroupStream::Singleton(singleton) => loop {
let batch = match singleton.cursor.next()? {
Ok(batch) => batch,
Err(e) => return Some(Err(e)),
};
self.observe_payload(singleton.cursor.held_bytes() + batch.data_size() as u64);
*support = (*support).min(singleton.cursor.predicate_support());
let judged = self.predicate.as_ref().map(|p| p.column_id);
let (batch, mistyped) =
match projection::conform_lenient(batch, &self.fields, judged) {
Ok(conformed) => conformed,
Err(e) => return Some(Err(e)),
};
let SingletonStream { global, mode, .. } = &mut **singleton;
let unjudged = match mode {
SingletonMode::Dedup(state)
if state
.predicate
.as_ref()
.is_some_and(|p| mistyped.contains(&p.column_id)) =>
{
state.predicate.take()
}
_ => None,
};
let shaped = self.shape_singleton_batch(batch, *global, mode, support);
if let (Some(predicate), SingletonMode::Dedup(state)) = (unjudged, &mut *mode) {
state.predicate = Some(predicate);
}
match shaped {
Ok(Some(_)) if !mistyped.is_empty() => {
return Some(Err(projection::MISTYPED));
}
Ok(Some(batch)) => return Some(projection::conform(batch, &self.fields)),
Ok(None) => {}
Err(e) => return Some(Err(e)),
}
},
}
}
fn shape_singleton_batch(
&self,
batch: ColumnBatch,
global: SeqNo,
mode: &mut SingletonMode,
support: &mut PredicateSupport,
) -> crate::Result<Option<ColumnBatch>> {
debug_assert!(batch.row_count > 0, "a cursor yielded an empty batch");
match mode {
SingletonMode::Verbatim => {
let mut batch = batch;
self.globalize_seqnos(&mut batch, global)?;
Ok(Some(batch))
}
SingletonMode::Masked {
partial,
threshold,
dropped,
} => self.mask_singleton_batch(&batch, global, *partial, *threshold, dropped),
SingletonMode::Dedup(state) => {
self.dedup_singleton_batch(&batch, global, state, support)
}
}
}
pub(super) fn raw_value_read(&self) -> bool {
use crate::table::columnar::COL_VALUE;
let by_id = self
.fields
.iter()
.any(|f| f.column_id() == COL_VALUE && !projection::is_declared(f));
let declared = self
.fields
.iter()
.any(|f| f.column_id() == COL_VALUE && projection::is_declared(f));
by_id
|| (!declared
&& self
.predicate
.as_ref()
.is_some_and(|p| p.column_id == COL_VALUE))
}
pub(super) fn predicate_timing(&self, holds_operand: impl FnOnce() -> bool) -> PredicateTiming {
use crate::table::columnar::COL_SEQNO;
let Some(pred) = &self.predicate else {
return PredicateTiming::AfterValues;
};
match pred.column_id {
COL_USER_KEY | COL_SEQNO => PredicateTiming::BeforeValues,
id if self
.fields
.iter()
.any(|f| f.column_id() == id && projection::is_declared(f)) =>
{
PredicateTiming::AfterValues
}
_ if self.resolver.is_some() && holds_operand() => {
PredicateTiming::BeforeValuesExceptOperands
}
_ => PredicateTiming::BeforeValues,
}
}
fn filter_after_dedup(
&self,
batch: ColumnBatch,
pred: Option<&ColumnRangePredicate>,
support: &mut PredicateSupport,
) -> crate::Result<ColumnBatch> {
let Some(pred) = pred else {
return Ok(batch);
};
*support = (*support).min(pred.support_in(&batch));
if pred.apply != PredicateApply::Filter {
return Ok(batch);
}
let mask = pred.matching_rows(&batch);
if mask.iter().all(|&keep| keep) {
return Ok(batch);
}
let filtered = filter_batch(&batch, &mask)?;
self.record_gather(&filtered);
Ok(filtered)
}
fn visible_group_range_tombstones(
&self,
segments: &[Segment],
) -> crate::Result<Vec<(UserKey, UserKey, SeqNo)>> {
let mut rts = Vec::new();
for seg in segments {
for rt in seg.range_tombstones() {
let eff = rt
.seqno
.checked_add(seg.rt_base())
.ok_or(Error::InvalidHeader(
"columnar_scan: effective range-tombstone seqno overflows",
))?;
if eff < self.seqno {
rts.push((rt.start.clone(), rt.end.clone(), eff));
}
}
}
Ok(rts)
}
fn range_is_full(&self) -> bool {
matches!(self.lo, Bound::Unbounded) && matches!(self.hi, Bound::Unbounded)
}
#[cfg_attr(
not(feature = "metrics"),
expect(
clippy::unused_self,
reason = "the scan's metrics exist only with the feature"
)
)]
fn globalize_seqnos(&self, batch: &mut ColumnBatch, global: SeqNo) -> crate::Result<()> {
if global == 0 {
return Ok(());
}
let Some(col) = batch.columns.iter_mut().find(|c| c.column_id == COL_SEQNO) else {
return Ok(());
};
let len = batch.row_count as usize * 8;
if col.data.len() != len {
return Err(Error::InvalidHeader("columnar_scan: short seqno column"));
}
#[expect(unsafe_code, reason = "see safety")]
let mut out = unsafe { crate::Slice::builder_unzeroed(len) };
for (row, (dst, src)) in out
.chunks_exact_mut(8)
.zip(col.data.chunks_exact(8))
.enumerate()
{
let mut local = [0u8; 8];
local.copy_from_slice(src);
let Some(effective) = u64::from_le_bytes(local).checked_add(global) else {
#[cfg(feature = "metrics")]
self.metrics.record_gather(row * 8);
#[cfg(not(feature = "metrics"))]
let _ = row;
return Err(Error::InvalidHeader(
"columnar_scan: effective seqno overflows",
));
};
dst.copy_from_slice(&effective.to_le_bytes());
}
col.data = crate::Slice::from(out.freeze());
#[cfg(feature = "metrics")]
self.metrics.record_gather(len);
Ok(())
}
fn gather_kept(
&self,
batch: &ColumnBatch,
mask: &[bool],
dropped: &[u16],
global: SeqNo,
) -> crate::Result<Option<ColumnBatch>> {
let mut kept = Vec::with_capacity(mask.len());
for (row, &keep) in (0..batch.row_count).zip(mask) {
if keep {
kept.push(row);
}
}
if kept.is_empty() {
return Ok(None);
}
let rows = batch.row_count as usize;
let all = kept.len() == rows;
let mut copied = 0usize;
let mut columns = Vec::with_capacity(batch.columns.len());
for col in batch
.columns
.iter()
.filter(|c| !dropped.contains(&c.column_id))
{
let written = if col.column_id == COL_SEQNO && global != 0 {
let mut data = Vec::with_capacity(kept.len() * 8);
for &row in &kept {
let effective = fixed_u64_row(&col.data, row)?.checked_add(global).ok_or(
Error::InvalidHeader("columnar_scan: effective seqno overflows"),
)?;
data.extend_from_slice(&effective.to_le_bytes());
}
crate::table::columnar::Column {
column_id: col.column_id,
type_tag: col.type_tag,
validity: col
.validity
.as_deref()
.map(|bits| take_validity(bits, &kept)),
data: crate::Slice::from(data),
}
} else if all {
columns.push(col.clone());
continue;
} else {
take_column(col, rows, &kept)?
};
copied += written.data.len() + written.validity.as_ref().map_or(0, Vec::len);
columns.push(written);
}
let row_count = u32::try_from(kept.len()).unwrap_or(batch.row_count);
let out = ColumnBatch { row_count, columns };
#[cfg(feature = "metrics")]
self.metrics.record_gather(copied);
#[cfg(not(feature = "metrics"))]
let _ = copied;
Ok(Some(out))
}
fn open_singleton(
&self,
seg: &Segment,
rts: Vec<(UserKey, UserKey, SeqNo)>,
support: &mut PredicateSupport,
) -> crate::Result<GroupStream> {
let predicate = if let Some(pred) = self.predicate.as_ref() {
let Some(local) = localize(pred, seg.global) else {
*support = (*support).min(pred.support(Some(TypeTag::Number(Number::U64_LE))));
return Ok(GroupStream::Empty);
};
Some(local.into_owned())
} else {
None
};
let predicate_on_declared = self.predicate.as_ref().is_some_and(|p| {
self.fields
.iter()
.any(|f| f.column_id() == p.column_id && projection::is_declared(f))
});
if seg.may_dup || seg.records_deletions() || predicate_on_declared || !rts.is_empty() {
return self.open_singleton_dedup(seg, rts, predicate);
}
let range_filter = !self.range_is_full();
if seg.visibility == SeqnoVisibility::All && !range_filter {
let SegmentCursor { cursor, .. } =
self.segment_cursor(seg, &self.projection, predicate.as_ref(), self.budget)?;
return Ok(GroupStream::Singleton(Box::new(SingletonStream {
cursor,
global: seg.global,
mode: SingletonMode::Verbatim,
})));
}
let partial = seg.visibility == SeqnoVisibility::Partial;
let mut needed = Vec::new();
if partial {
needed.push(COL_SEQNO);
}
if range_filter {
needed.push(COL_USER_KEY);
}
let (augmented, dropped) = self.augment(&needed);
let SegmentCursor { cursor, .. } =
self.segment_cursor(seg, &augmented, predicate.as_ref(), self.budget)?;
Ok(GroupStream::Singleton(Box::new(SingletonStream {
cursor,
global: seg.global,
mode: SingletonMode::Masked {
partial,
threshold: self.seqno.saturating_sub(seg.global),
dropped,
},
})))
}
fn augment(&self, needed: &[u16]) -> (Vec<u16>, Vec<u16>) {
let mut augmented = self.projection.clone();
let mut dropped = Vec::new();
for &column in needed {
if !augmented.contains(&column) {
augmented.push(column);
dropped.push(column);
}
}
(augmented, dropped)
}
fn mask_singleton_batch(
&self,
batch: &ColumnBatch,
global: SeqNo,
partial: bool,
threshold: SeqNo,
dropped: &[u16],
) -> crate::Result<Option<ColumnBatch>> {
let cmp = self.comparator.as_ref();
let range_filter = !self.range_is_full();
{
let seqno_col = if partial {
Some(
batch
.columns
.iter()
.find(|c| c.column_id == COL_SEQNO)
.ok_or(Error::InvalidHeader(
"columnar_scan: partial-visibility batch missing the seqno column",
))?,
)
} else {
None
};
let key_col = if range_filter {
Some(
batch
.columns
.iter()
.find(|c| c.column_id == COL_USER_KEY)
.ok_or(Error::InvalidHeader(
"columnar_scan: range-filtered batch missing the key column",
))?,
)
} else {
None
};
let mut mask = Vec::with_capacity(batch.row_count as usize);
for row in 0..batch.row_count {
let seqno_ok = match seqno_col {
Some(seqno_col) => fixed_u64_row(&seqno_col.data, row)? < threshold,
None => true,
};
let keep = if !seqno_ok {
false
} else if let Some(key_col) = key_col {
let key = bytes_column_row(&key_col.data, batch.row_count, row)?;
key_in_bounds(key, &self.lo, &self.hi, cmp)
} else {
true
};
mask.push(keep);
}
self.gather_kept(batch, &mask, dropped, global)
}
}
fn open_singleton_dedup(
&self,
seg: &Segment,
rts: Vec<(UserKey, UserKey, SeqNo)>,
predicate: Option<ColumnRangePredicate>,
) -> crate::Result<GroupStream> {
let partial = seg.visibility == SeqnoVisibility::Partial;
let mut needed = vec![COL_USER_KEY];
if partial || !rts.is_empty() {
needed.push(COL_SEQNO);
}
if let Some(pred) = &predicate {
needed.push(pred.column_id);
}
let deletes = seg.records_deletions();
if deletes {
needed.push(COL_VALUE_TYPE);
}
let (augmented, dropped) = self.augment(&needed);
let SegmentCursor { cursor, .. } =
self.segment_cursor(seg, &augmented, None, self.budget)?;
Ok(GroupStream::Singleton(Box::new(SingletonStream {
cursor,
global: seg.global,
mode: SingletonMode::Dedup(DedupState {
predicate,
rts: TombstoneSweep::new(rts, self.comparator.as_ref()),
partial,
deletes,
threshold: self.seqno.saturating_sub(seg.global),
dropped,
last_key: None,
}),
})))
}
fn dedup_singleton_batch(
&self,
batch: &ColumnBatch,
global: SeqNo,
state: &mut DedupState,
support: &mut PredicateSupport,
) -> crate::Result<Option<ColumnBatch>> {
let (partial, deletes, threshold) = (state.partial, state.deletes, state.threshold);
let seqno_needed = partial || !state.rts.is_empty();
let range_filter = !self.range_is_full();
let cmp = self.comparator.as_ref();
{
let key_col = batch
.columns
.iter()
.find(|c| c.column_id == COL_USER_KEY)
.ok_or(Error::InvalidHeader(
"columnar_scan: dedup batch missing the key column",
))?;
let vt_col = if deletes {
Some(
batch
.columns
.iter()
.find(|c| c.column_id == COL_VALUE_TYPE)
.ok_or(Error::InvalidHeader(
"columnar_scan: dedup batch missing the value-type column",
))?,
)
} else {
None
};
let seqno_col = if seqno_needed {
Some(
batch
.columns
.iter()
.find(|c| c.column_id == COL_SEQNO)
.ok_or(Error::InvalidHeader(
"columnar_scan: dedup batch missing the seqno column",
))?,
)
} else {
None
};
if let Some(pred) = &state.predicate {
*support = (*support).min(pred.support_in(batch));
}
let matcher = state
.predicate
.as_ref()
.filter(|p| p.apply == PredicateApply::Filter)
.map(|p| p.matcher(batch));
let mut mask = Vec::with_capacity(batch.row_count as usize);
for row in 0..batch.row_count {
let local = match seqno_col {
Some(seqno_col) => Some(fixed_u64_row(&seqno_col.data, row)?),
None => None,
};
let visible = !partial || local.is_some_and(|l| l < threshold);
if !visible {
mask.push(false);
continue;
}
let key = bytes_column_row(&key_col.data, batch.row_count, row)?;
if state
.last_key
.as_deref()
.is_some_and(|last| cmp.compare(last, key) == core::cmp::Ordering::Equal)
{
mask.push(false);
continue;
}
let last = state.last_key.get_or_insert_with(Vec::new);
last.clear();
last.extend_from_slice(key);
if !state.rts.is_empty() {
let eff =
local
.unwrap_or(0)
.checked_add(global)
.ok_or(Error::InvalidHeader(
"columnar_scan: effective seqno overflows",
))?;
if state.rts.covers(key, eff, cmp) {
mask.push(false);
continue;
}
}
if let Some(vt_col) = vt_col {
let byte = *vt_col.data.get(row as usize).ok_or(Error::InvalidHeader(
"columnar_scan: value-type column shorter than the row count",
))?;
let value_type = crate::ValueType::try_from(byte)
.map_err(|()| Error::InvalidTag(("ValueType", byte)))?;
if value_type.is_tombstone() {
mask.push(false);
continue;
}
}
mask.push(
(!range_filter || key_in_bounds(key, &self.lo, &self.hi, cmp))
&& matcher.as_ref().is_none_or(|m| m.matches(row)),
);
}
self.gather_kept(batch, &mask, &state.dropped, global)
}
}
}
fn localize(pred: &ColumnRangePredicate, global: SeqNo) -> Option<Cow<'_, ColumnRangePredicate>> {
if pred.column_id != COL_SEQNO || global == 0 {
return Some(Cow::Borrowed(pred));
}
let (lo, hi) = pred.ordinal_span(Number::U64_LE)?;
let global = u128::from(global);
let hi = hi.checked_sub(global)?;
let lo = lo.saturating_sub(global);
let bound = |v: u128| u64::try_from(v).map(|v| v.to_be_bytes().to_vec());
Some(Cow::Owned(ColumnRangePredicate {
column_id: COL_SEQNO,
lower: Some(bound(lo).ok()?),
upper: Some(bound(hi).ok()?),
apply: pred.apply,
}))
}
fn key_in_bounds(
key: &[u8],
lo: &Bound<UserKey>,
hi: &Bound<UserKey>,
cmp: &dyn UserComparator,
) -> bool {
use core::cmp::Ordering;
let above_lo = match lo {
Bound::Unbounded => true,
Bound::Included(k) => cmp.compare(key, k.as_ref()) != Ordering::Less,
Bound::Excluded(k) => cmp.compare(key, k.as_ref()) == Ordering::Greater,
};
let below_hi = match hi {
Bound::Unbounded => true,
Bound::Included(k) => cmp.compare(key, k.as_ref()) != Ordering::Greater,
Bound::Excluded(k) => cmp.compare(key, k.as_ref()) == Ordering::Less,
};
above_lo && below_hi
}
impl Iterator for ColumnarScan {
type Item = crate::Result<ColumnBatch>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let mut support = self.support;
if let Some(mut stream) = self.current.take() {
let next = self.next_from(&mut stream, &mut support);
self.support = support;
self.observe_payload(stream.held_bytes());
self.record_oversized(stream.take_oversized());
match next {
Some(Ok(batch)) => {
self.current = Some(stream);
#[cfg(feature = "metrics")]
self.metrics.record_materialized(batch.data_size());
return Some(Ok(batch));
}
Some(Err(e)) => return Some(Err(e)),
None => continue,
}
}
let group = self.groups.pop_front()?;
let opened = self.open_group(&group, &mut support);
self.support = support;
match opened {
Ok(stream) => self.current = Some(stream),
Err(e) => return Some(Err(e)),
}
}
}
}
fn clone_bound(bound: Bound<&UserKey>) -> Bound<UserKey> {
match bound {
Bound::Included(k) => Bound::Included(k.clone()),
Bound::Excluded(k) => Bound::Excluded(k.clone()),
Bound::Unbounded => Bound::Unbounded,
}
}
fn bound_as_ref(bound: &Bound<UserKey>) -> Bound<&[u8]> {
match bound {
Bound::Included(k) => Bound::Included(k.as_ref()),
Bound::Excluded(k) => Bound::Excluded(k.as_ref()),
Bound::Unbounded => Bound::Unbounded,
}
}
#[cfg(all(test, feature = "metrics"))]
mod tests;