Skip to main content

lsm_tree/tree/columnar_scan/
projection.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026-present, Dmitry Prudnikov
3
4//! What a projected columnar scan returns, and what a field a row does not
5//! have reads as.
6//!
7//! Sources disagree about which fields a row has: an older segment predates a
8//! column, a row value a [`ValueProjector`] reads may not carry one. Each
9//! projected field therefore declares what its absence means
10//! ([`Absent`]), and the scan applies that declaration the same way to every
11//! source, so one query over the same data returns the same rows wherever
12//! they are stored. A null cell counts as absent: a row either has a value for
13//! a field or it does not.
14//!
15//! Absence never means "take the field from an older version". Filling a
16//! field from history is partial-update semantics, which a caller asks for
17//! through a merge operator.
18
19use alloc::sync::Arc;
20use alloc::vec::Vec;
21
22use crate::table::columnar::{
23    COL_SEQNO, COL_USER_KEY, COL_VALUE_TYPE, Column, ColumnBatch, Number, TypeTag,
24    bytes_column_row, frame_bytes_column,
25};
26use crate::{Error, Slice};
27
28/// What a projected field reads as in a row that does not have it: a segment
29/// written without the column, a null cell, or a row value the
30/// [`ValueProjector`] finds no such field in.
31#[derive(Clone, Debug, Eq, PartialEq)]
32pub enum Absent {
33    /// The cell is null: the column carries a validity bitmap and the row's
34    /// bit is clear.
35    Null,
36    /// The cell reads as this value, which must fit the field's type.
37    Default(Slice),
38    /// The scan fails, naming the field.
39    Error,
40}
41
42/// One field of a [`Projection`]: its column id, its physical type and what
43/// its absence means.
44#[derive(Clone, Debug, Eq, PartialEq)]
45pub struct ProjectedField {
46    column_id: u16,
47    /// The declared type, or `None` for a field projected by id alone, whose
48    /// type is whatever the segments store.
49    type_tag: Option<TypeTag>,
50    absent: Absent,
51}
52
53impl ProjectedField {
54    /// A field with a declared type and absence.
55    ///
56    /// # Errors
57    ///
58    /// Returns an error if `absent` is a default that does not fit `type_tag`
59    /// (a fixed-width type takes exactly its width), if `type_tag` is a fixed
60    /// width of zero, which no column may carry, or if `column_id` names an
61    /// intrinsic column, whose type and presence are fixed.
62    pub fn new(column_id: u16, type_tag: TypeTag, absent: Absent) -> crate::Result<Self> {
63        if intrinsic_type(column_id).is_some() {
64            return Err(Error::Projection(
65                "projection: an intrinsic column is projected by id, not declared",
66            ));
67        }
68        if type_tag.fixed_width() == Some(0) {
69            return Err(Error::Projection(
70                "projection: a fixed-width field has a width of zero",
71            ));
72        }
73        if let (Absent::Default(value), Some(width)) = (&absent, type_tag.fixed_width())
74            && value.len() != usize::from(width)
75        {
76            return Err(Error::Projection(
77                "projection: a default does not fit its field's width",
78            ));
79        }
80        Ok(Self {
81            column_id,
82            type_tag: Some(type_tag),
83            absent,
84        })
85    }
86
87    /// A field projected by id alone: an intrinsic column, or a value column
88    /// whose type is whatever the segments store. Its null cells stay null; a
89    /// segment written without the column is an error, since nothing declares
90    /// the type a stand-in column would need.
91    #[must_use]
92    pub fn by_id(column_id: u16) -> Self {
93        Self {
94            column_id,
95            type_tag: intrinsic_type(column_id),
96            absent: Absent::Null,
97        }
98    }
99
100    /// The field's column id.
101    #[must_use]
102    pub fn column_id(&self) -> u16 {
103        self.column_id
104    }
105
106    /// The field's declared type, or `None` when it was projected by id alone.
107    #[must_use]
108    pub fn type_tag(&self) -> Option<TypeTag> {
109        self.type_tag
110    }
111
112    /// What the field reads as where a row does not have it.
113    #[must_use]
114    pub fn absent(&self) -> &Absent {
115        &self.absent
116    }
117}
118
119/// The type of an intrinsic column, or `None` for a value column.
120fn intrinsic_type(column_id: u16) -> Option<TypeTag> {
121    match column_id {
122        COL_USER_KEY => Some(TypeTag::Bytes),
123        COL_SEQNO => Some(TypeTag::Number(Number::U64_LE)),
124        COL_VALUE_TYPE => Some(TypeTag::Fixed(1)),
125        _ => None,
126    }
127}
128
129/// Reads the projected fields out of a row value the engine cannot interpret:
130/// a value in a memtable or a row-oriented table, or the result of a merge.
131///
132/// The engine does not guess an encoding; the caller that wrote the values
133/// knows it. It is called only for the rows the scan returns: never for a
134/// version a newer one shadows, a deleted one or one newer than the snapshot.
135pub trait ValueProjector: Send + Sync {
136    /// Writes into `row` the cell of each field the row value `value` of `key`
137    /// has. A field left unset is absent from the row and reads as its
138    /// declaration says.
139    ///
140    /// # Errors
141    ///
142    /// Returns an error when `value` is not a value this projector reads; the
143    /// scan fails with it.
144    fn project(&self, key: &[u8], value: &[u8], row: &mut ProjectedRow<'_>) -> crate::Result<()>;
145}
146
147/// The cells a [`ValueProjector`] writes for one row.
148pub struct ProjectedRow<'a> {
149    fields: &'a [ProjectedField],
150    cells: &'a mut [Option<Vec<u8>>],
151}
152
153impl<'a> ProjectedRow<'a> {
154    /// A row of `fields`, whose cells are written into `cells`, one per field.
155    pub(crate) fn new(fields: &'a [ProjectedField], cells: &'a mut [Option<Vec<u8>>]) -> Self {
156        Self { fields, cells }
157    }
158
159    /// The fields to write, in the order `set` indexes them.
160    #[must_use]
161    pub fn fields(&self) -> &[ProjectedField] {
162        self.fields
163    }
164
165    /// Sets field `index` of the row to `cell`.
166    ///
167    /// # Errors
168    ///
169    /// Returns an error if `index` names no field, if the field was projected
170    /// by id alone (a row value has no type the engine could check it
171    /// against), or if `cell` does not fit the field's fixed width.
172    pub fn set(&mut self, index: usize, cell: &[u8]) -> crate::Result<()> {
173        let field = self.fields.get(index).ok_or(Error::Projection(
174            "projection: the projector set a field the projection does not have",
175        ))?;
176        let type_tag = field.type_tag.ok_or(Error::Projection(
177            "projection: a row value is projected into a field with no declared type",
178        ))?;
179        if let Some(width) = type_tag.fixed_width()
180            && cell.len() != usize::from(width)
181        {
182            return Err(Error::Projection(
183                "projection: the projector wrote a cell of the wrong width",
184            ));
185        }
186        let slot = self.cells.get_mut(index).ok_or(Error::Projection(
187            "projection: the projector set a field the projection does not have",
188        ))?;
189        let buf = slot.get_or_insert_with(Vec::new);
190        buf.clear();
191        buf.extend_from_slice(cell);
192        Ok(())
193    }
194}
195
196/// The columns a projected scan returns: intrinsic columns and value fields,
197/// in the order the scan yields them, and the projector that reads the fields
198/// out of row values.
199#[derive(Clone, Default)]
200pub struct Projection {
201    fields: Vec<ProjectedField>,
202    projector: Option<Arc<dyn ValueProjector>>,
203}
204
205impl core::fmt::Debug for Projection {
206    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
207        f.debug_struct("Projection")
208            .field("fields", &self.fields)
209            .field("projector", &self.projector.is_some())
210            .finish()
211    }
212}
213
214impl Projection {
215    /// An empty projection.
216    #[must_use]
217    pub fn new() -> Self {
218        Self::default()
219    }
220
221    /// Adds a column projected by id alone (see [`ProjectedField::by_id`]).
222    #[must_use]
223    pub fn column(mut self, column_id: u16) -> Self {
224        self.fields.push(ProjectedField::by_id(column_id));
225        self
226    }
227
228    /// Adds a declared field.
229    #[must_use]
230    pub fn field(mut self, field: ProjectedField) -> Self {
231        self.fields.push(field);
232        self
233    }
234
235    /// Sets the projector that reads the fields out of row values.
236    #[must_use]
237    pub fn projector(mut self, projector: Arc<dyn ValueProjector>) -> Self {
238        self.projector = Some(projector);
239        self
240    }
241
242    /// The projected fields, in output order.
243    #[must_use]
244    pub fn fields(&self) -> &[ProjectedField] {
245        &self.fields
246    }
247
248    /// The projector, when one is set.
249    #[must_use]
250    pub fn value_projector(&self) -> Option<&Arc<dyn ValueProjector>> {
251        self.projector.as_ref()
252    }
253
254    /// The projected column ids, in output order.
255    pub(crate) fn column_ids(&self) -> Vec<u16> {
256        self.fields.iter().map(|f| f.column_id).collect()
257    }
258}
259
260impl From<&[u16]> for Projection {
261    fn from(ids: &[u16]) -> Self {
262        Self {
263            fields: ids.iter().copied().map(ProjectedField::by_id).collect(),
264            projector: None,
265        }
266    }
267}
268
269impl<const N: usize> From<&[u16; N]> for Projection {
270    fn from(ids: &[u16; N]) -> Self {
271        Self::from(ids.as_slice())
272    }
273}
274
275impl From<&Vec<u16>> for Projection {
276    fn from(ids: &Vec<u16>) -> Self {
277        Self::from(ids.as_slice())
278    }
279}
280
281impl From<&Self> for Projection {
282    fn from(projection: &Self) -> Self {
283        projection.clone()
284    }
285}
286
287/// Whether `field` was declared with a type, rather than projected by id: the
288/// fields a projector writes and a whole value is read through.
289pub fn is_declared(field: &ProjectedField) -> bool {
290    field.type_tag.is_some() && intrinsic_type(field.column_id).is_none()
291}
292
293/// Reads, in a batch of rows a scan returns, the declared `fields` of each row
294/// carrying a whole value in column `whole_id` out of that value through
295/// `projector`, each value given with its key, in place of the cells the row
296/// holds for them. A row without a whole value keeps its cells as its source
297/// stored them, and a row that is not a value (a deletion) is not read. A
298/// field the projector leaves unset is null here, and reads as declared once
299/// the batch is conformed. A whole value to read with no projector set is an
300/// error: its fields lie inside it and only the caller knows how.
301pub fn project_decided(
302    batch: ColumnBatch,
303    fields: &[ProjectedField],
304    projector: Option<&dyn ValueProjector>,
305    whole_id: u16,
306) -> crate::Result<ColumnBatch> {
307    let row_count = batch.row_count;
308    let rows = row_count as usize;
309    let find = |id: u16| batch.columns.iter().find(|c| c.column_id == id);
310    let (Some(values), Some(keys)) = (find(whole_id), find(COL_USER_KEY)) else {
311        return Err(MISSING_SCAN_COLUMN);
312    };
313    let types = find(COL_VALUE_TYPE);
314    let declared: Vec<ProjectedField> = fields.iter().filter(|f| is_declared(f)).cloned().collect();
315
316    // Per declared field, the cells a projected row read; `None` in `read`
317    // marks a row the projector did not read, which keeps its own cells.
318    let mut read: Vec<Option<Vec<Option<Vec<u8>>>>> = Vec::with_capacity(rows);
319    let mut cells: Vec<Option<Vec<u8>>> = alloc::vec![None; declared.len()];
320    for row in 0..row_count {
321        let is_value = match types {
322            Some(types) => {
323                let byte = *types.data.get(row as usize).ok_or(MISSING_SCAN_COLUMN)?;
324                returns_a_value(
325                    crate::ValueType::try_from(byte)
326                        .map_err(|()| Error::InvalidTag(("ValueType", byte)))?,
327                )
328            }
329            None => true,
330        };
331        if !(is_value && values.is_valid(row)) {
332            read.push(None);
333            continue;
334        }
335        let projector = projector.ok_or(UNREADABLE)?;
336        cells.fill(None);
337        let key = bytes_column_row(&keys.data, row_count, row)?;
338        let value = bytes_column_row(&values.data, row_count, row)?;
339        projector.project(key, value, &mut ProjectedRow::new(&declared, &mut cells))?;
340        read.push(Some(core::mem::take(&mut cells)));
341        cells = alloc::vec![None; declared.len()];
342    }
343    if read.iter().all(Option::is_none) {
344        return Ok(batch);
345    }
346
347    let ColumnBatch {
348        row_count,
349        mut columns,
350    } = batch;
351    for (index, field) in declared.iter().enumerate() {
352        let Some(at) = columns.iter().position(|c| c.column_id == field.column_id) else {
353            return Err(MISSING_SCAN_COLUMN);
354        };
355        let old = columns.get(at).ok_or(MISSING_SCAN_COLUMN)?;
356        let mut column: Vec<Option<Vec<u8>>> = Vec::with_capacity(rows);
357        for (row, projected) in (0..row_count).zip(&read) {
358            column.push(match projected {
359                Some(cells) => cells.get(index).cloned().flatten(),
360                None if old.is_valid(row) => Some(cell_of(old, row_count, row)?.to_vec()),
361                None => None,
362            });
363        }
364        let built = built_column(field, &column)?;
365        if let Some(slot) = columns.get_mut(at) {
366            *slot = built;
367        }
368    }
369    Ok(ColumnBatch { row_count, columns })
370}
371
372/// Row `row` of `column`, of `rows` rows, as stored.
373fn cell_of(column: &Column, rows: u32, row: u32) -> crate::Result<&[u8]> {
374    match column.type_tag.fixed_width() {
375        Some(width) => {
376            let width = usize::from(width);
377            let start = row as usize * width;
378            column
379                .data
380                .get(start..start + width)
381                .ok_or(MISSING_SCAN_COLUMN)
382        }
383        None => bytes_column_row(&column.data, rows, row),
384    }
385}
386
387/// A whole value holds declared fields and no projector is set to read them.
388pub(super) const UNREADABLE: Error = Error::Projection(
389    "projection: a row returned stores its value whole, and declared fields are read out \
390     of it through a projector, which is not set",
391);
392
393/// Whether a row of `value_type` is returned with a value: a value, or a merge
394/// operand left unresolved, which is what a read of a tree without a merge
395/// operator returns (a tree with one has its operands resolved by then).
396fn returns_a_value(value_type: crate::ValueType) -> bool {
397    matches!(
398        value_type,
399        crate::ValueType::Value | crate::ValueType::MergeOperand
400    )
401}
402
403/// A column of `field` holding `cells`, a missing cell null.
404fn built_column(field: &ProjectedField, cells: &[Option<Vec<u8>>]) -> crate::Result<Column> {
405    let type_tag = field.type_tag.ok_or(ABSENT_FIELD)?;
406    let count = cells.len();
407    let validity = cells.iter().any(Option::is_none).then(|| {
408        let mut bits = alloc::vec![0u8; count.div_ceil(8)];
409        for (row, cell) in cells.iter().enumerate() {
410            if cell.is_some()
411                && let Some(byte) = bits.get_mut(row / 8)
412            {
413                *byte |= 1 << (row % 8);
414            }
415        }
416        bits
417    });
418    let data = match type_tag.fixed_width() {
419        Some(width) => {
420            let width = usize::from(width);
421            let mut data = alloc::vec![0u8; count * width];
422            for (slot, cell) in data.chunks_exact_mut(width).zip(cells) {
423                if let Some(cell) = cell {
424                    slot.copy_from_slice(cell);
425                }
426            }
427            Slice::from(data)
428        }
429        None => frame_bytes_column(count, || cells.iter().map(|c| c.as_deref().unwrap_or(&[])))?,
430    };
431    Ok(Column {
432        column_id: field.column_id,
433        type_tag,
434        validity,
435        data,
436    })
437}
438
439/// A batch of a whole-value table lacks a column the scan decoded to read the
440/// value through the projector.
441const MISSING_SCAN_COLUMN: Error =
442    Error::InvalidHeader("columnar_scan: a whole-value batch is missing its key or value column");
443
444/// The column that stands for `field` in a batch of `rows` rows whose source
445/// does not have it, or the error its declaration asks for when a row of it
446/// is a value (`is_value`): a deletion carries no fields and is not refused.
447pub fn absent_column(
448    field: &ProjectedField,
449    rows: u32,
450    is_value: &dyn Fn(u32) -> bool,
451) -> crate::Result<Column> {
452    let type_tag = field.type_tag.ok_or(ABSENT_FIELD)?;
453    let count = rows as usize;
454    let (validity, data) = match &field.absent {
455        Absent::Error if (0..rows).any(is_value) => return Err(ABSENT_FIELD),
456        Absent::Null | Absent::Error => {
457            let data = match type_tag.fixed_width() {
458                Some(width) => Slice::from(alloc::vec![0u8; count * usize::from(width)]),
459                None => frame_bytes_column(count, || core::iter::repeat_n(&[][..], count))?,
460            };
461            (Some(alloc::vec![0u8; count.div_ceil(8)]), data)
462        }
463        Absent::Default(value) => {
464            let data = match type_tag.fixed_width() {
465                Some(_) => Slice::from(value.repeat(count)),
466                None => frame_bytes_column(count, || core::iter::repeat_n(&**value, count))?,
467            };
468            (None, data)
469        }
470    };
471    Ok(Column {
472        column_id: field.column_id,
473        type_tag,
474        validity,
475        data,
476    })
477}
478
479/// A projected field is absent from a row and declared an error, or was
480/// projected by id alone.
481const ABSENT_FIELD: Error = Error::Projection(
482    "projection: a projected field is absent from a row and its declaration does not allow it",
483);
484
485/// Applies the absence rule of `fields` to a batch a columnar segment yielded:
486/// a declared column the segment does not have is filled in as declared, a
487/// null cell reads as its field's declaration, and a column stored under
488/// another type than the one declared is an error. The columns come back in
489/// the order of `fields`; a column the batch holds that no field names is
490/// kept after them, in its place.
491///
492/// Only a row that is a value is held to its fields: a deletion or a merge
493/// operand, which the batch's value-type column names when it carries one,
494/// has none, and is decided by its type before it could be returned.
495pub fn conform(batch: ColumnBatch, fields: &[ProjectedField]) -> crate::Result<ColumnBatch> {
496    conform_with(batch, fields, Conform::Strict).map(|(batch, _)| batch)
497}
498
499/// [`conform`] for a batch whose rows are not yet decided: a field declared
500/// an error where absent reads as null instead, and so does one with a
501/// default unless it is the predicate's column `judged`, so a predicate over
502/// the batch sees its own field as declared, and the rows a scan returns are
503/// held to the declarations by [`conform`] once they are decided. A row that
504/// is shadowed, deleted or filtered out never fails the scan, and no default
505/// is laid out for it.
506///
507/// A column stored under another type than its field declares reads as null
508/// here, and its id is returned: a row of the batch the scan returns fails it
509/// with [`MISTYPED`], one shadowed or filtered out does not. A predicate over
510/// such a column cannot judge the rows, so none of them is filtered out by it.
511pub fn conform_lenient(
512    batch: ColumnBatch,
513    fields: &[ProjectedField],
514    judged: Option<u16>,
515) -> crate::Result<(ColumnBatch, Vec<u16>)> {
516    conform_with(batch, fields, Conform::Lenient { judged })
517}
518
519/// A segment stores a projected field under another type than declared.
520pub const MISTYPED: Error =
521    Error::Projection("projection: a segment stores a projected field under another type");
522
523/// How [`conform_with`] holds a batch to its fields.
524#[derive(Clone, Copy)]
525enum Conform {
526    /// The rows are decided: each value row is held to every declaration.
527    Strict,
528    /// The rows are not decided yet; `judged` is the predicate's column.
529    Lenient { judged: Option<u16> },
530}
531
532/// [`conform`], holding the rows that are values to [`Absent::Error`] and a
533/// mistyped column to [`MISTYPED`] only when strict, and laying out a default
534/// only when strict or for the judged column; also returns the ids of the
535/// columns that were mistyped.
536fn conform_with(
537    batch: ColumnBatch,
538    fields: &[ProjectedField],
539    mode: Conform,
540) -> crate::Result<(ColumnBatch, Vec<u16>)> {
541    let strict = matches!(mode, Conform::Strict);
542    // A default left out reads as null until the rows are decided, when the
543    // strict pass lays it out for the rows returned.
544    let defers = |field: &ProjectedField| match mode {
545        Conform::Strict => false,
546        Conform::Lenient { judged } => {
547            judged != Some(field.column_id) && matches!(field.absent, Absent::Default(_))
548        }
549    };
550    let ColumnBatch {
551        row_count,
552        mut columns,
553    } = batch;
554    let types = columns
555        .iter()
556        .find(|c| c.column_id == COL_VALUE_TYPE)
557        .map(|c| c.data.clone());
558    let is_value = |row: u32| {
559        strict
560            && types.as_ref().is_none_or(|types| {
561                types
562                    .get(row as usize)
563                    .and_then(|&byte| crate::ValueType::try_from(byte).ok())
564                    .is_some_and(returns_a_value)
565            })
566    };
567    let mut mistyped = Vec::new();
568    let mut out = Vec::with_capacity(fields.len().max(columns.len()));
569    for field in fields {
570        let at = columns.iter().position(|c| c.column_id == field.column_id);
571        let column = match at {
572            Some(at) => {
573                let column = columns.remove(at);
574                if field.type_tag.is_some_and(|t| t != column.type_tag) {
575                    if strict {
576                        return Err(MISTYPED);
577                    }
578                    // Null under the declared type, so the batch agrees with
579                    // the other sources; its rows fail only if returned.
580                    mistyped.push(field.column_id);
581                    let null = ProjectedField {
582                        absent: Absent::Null,
583                        ..field.clone()
584                    };
585                    out.push(absent_column(&null, row_count, &|_| false)?);
586                    continue;
587                }
588                if defers(field) {
589                    column
590                } else {
591                    fill_nulls(column, field, row_count, &is_value)?
592                }
593            }
594            // A column of no declared type has no absent form; its rows are
595            // held to it once they are decided.
596            None if !strict && field.type_tag.is_none() => continue,
597            None if defers(field) => {
598                let null = ProjectedField {
599                    absent: Absent::Null,
600                    ..field.clone()
601                };
602                absent_column(&null, row_count, &|_| false)?
603            }
604            None => absent_column(field, row_count, &is_value)?,
605        };
606        out.push(column);
607    }
608    out.extend(columns);
609    Ok((
610        ColumnBatch {
611            row_count,
612            columns: out,
613        },
614        mistyped,
615    ))
616}
617
618/// `column` with its null cells read as `field` declares: kept for
619/// [`Absent::Null`], set to the default for [`Absent::Default`], an error for
620/// [`Absent::Error`] when a null row is a value (`is_value`).
621fn fill_nulls(
622    column: Column,
623    field: &ProjectedField,
624    rows: u32,
625    is_value: &dyn Fn(u32) -> bool,
626) -> crate::Result<Column> {
627    let Some(validity) = &column.validity else {
628        return Ok(column);
629    };
630    let is_null = |row: u32| {
631        validity
632            .get(row as usize / 8)
633            .is_none_or(|byte| byte >> (row % 8) & 1 == 0)
634    };
635    if !(0..rows).any(is_null) {
636        return Ok(column);
637    }
638    let value = match &field.absent {
639        Absent::Error if (0..rows).any(|row| is_null(row) && is_value(row)) => {
640            return Err(ABSENT_FIELD);
641        }
642        Absent::Null | Absent::Error => return Ok(column),
643        Absent::Default(value) => value,
644    };
645    let count = rows as usize;
646    let data = if let Some(width) = column.type_tag.fixed_width() {
647        let width = usize::from(width);
648        let mut data = column.data.to_vec();
649        for row in (0..rows).filter(|&row| is_null(row)) {
650            let at = row as usize * width;
651            data.get_mut(at..at + width)
652                .ok_or(Error::InvalidHeader("columnar: fixed column row truncated"))?
653                .copy_from_slice(value);
654        }
655        Slice::from(data)
656    } else {
657        let cells = (0..rows)
658            .map(|row| {
659                if is_null(row) {
660                    Ok(&**value)
661                } else {
662                    bytes_column_row(&column.data, rows, row)
663                }
664            })
665            .collect::<crate::Result<Vec<&[u8]>>>()?;
666        frame_bytes_column(count, || cells.iter().copied())?
667    };
668    Ok(Column {
669        column_id: column.column_id,
670        type_tag: column.type_tag,
671        validity: None,
672        data,
673    })
674}
675
676#[cfg(test)]
677#[expect(clippy::unwrap_used, reason = "test code")]
678mod tests;