egglog_core_relations/
table_spec.rs

1//! High-level types for specifying the behavior and layout of tables.
2//!
3//! Tables are a mapping from some set of keys to another set of values. Tables
4//! can also be "sorted by" a columna dn "partitioned by" another. This can help
5//! speed up queries.
6
7use std::{
8    any::Any,
9    marker::PhantomData,
10    ops::{Deref, DerefMut},
11};
12
13use crate::numeric_id::{DenseIdMap, NumericId, define_id};
14use smallvec::SmallVec;
15
16use crate::{
17    QueryEntry, TableId, Variable,
18    action::{
19        Bindings, ExecutionState,
20        mask::{Mask, MaskIter, ValueSource},
21    },
22    common::Value,
23    hash_index::{IndexBase, TupleIndex},
24    offsets::{RowId, Subset, SubsetRef},
25    pool::{PoolSet, Pooled, with_pool_set},
26    row_buffer::{RowBuffer, RowSink, TaggedRowBuffer},
27};
28
29define_id!(pub ColumnId, u32, "a particular column in a table", pretty "Col");
30define_id!(
31    pub Generation,
32    u64,
33    "the current version of a table -- used to invalidate any existing RowIds"
34);
35define_id!(
36    pub Offset,
37    u64,
38    "an opaque offset token -- used to encode iterations over a table (within a generation). These always start at 0."
39);
40
41/// The version of a table.
42#[derive(Clone, Debug, PartialEq, Eq)]
43pub struct TableVersion {
44    /// New major generations invalidate all existing RowIds for a table.
45    pub major: Generation,
46    /// New minor generations within a major generation do not invalidate
47    /// existing RowIds, but they may indicate that `all` can return a larger
48    /// subset than before.
49    pub minor: Offset,
50    // NB: we may want to make `Offset` and `RowId` the same.
51}
52
53#[derive(Clone)]
54pub struct TableSpec {
55    /// The number of key columns for the table.
56    pub n_keys: usize,
57
58    /// The number of non-key (i.e. value) columns in the table.
59    ///
60    /// The total "arity" of the table is `n_keys + n_vals`.
61    pub n_vals: usize,
62
63    /// Columns that cannot be cached across generations.
64    ///
65    /// These columns should (e.g.) never have indexes built for them, as they
66    /// will go out of date too quickly.
67    pub uncacheable_columns: DenseIdMap<ColumnId, bool>,
68
69    /// Whether or not deletions are supported for this table.
70    ///
71    /// Tables where this value is false are allowed to panic on calls to
72    /// `stage_remove`.
73    pub allows_delete: bool,
74}
75
76impl TableSpec {
77    /// The total number of columns stored by the table.
78    pub fn arity(&self) -> usize {
79        self.n_keys + self.n_vals
80    }
81}
82
83/// A summary of the kinds of changes that a table underwent after a merge operation.
84#[derive(Eq, PartialEq, Copy, Clone)]
85pub struct TableChange {
86    /// Whether or not rows were added to the table.
87    pub added: bool,
88    /// Whether or not rows were removed from the table.
89    pub removed: bool,
90}
91
92/// A constraint on the values within a row.
93#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
94pub enum Constraint {
95    Eq { l_col: ColumnId, r_col: ColumnId },
96    EqConst { col: ColumnId, val: Value },
97    LtConst { col: ColumnId, val: Value },
98    GtConst { col: ColumnId, val: Value },
99    LeConst { col: ColumnId, val: Value },
100    GeConst { col: ColumnId, val: Value },
101}
102
103/// Remap individual values (e.g. to their union-find leaders) — the value-level
104/// half of rebuilding, enough to rebuild a single container's contents (see
105/// [`crate::ContainerValue::rebuild_contents`]).
106pub trait ValueRebuilder: Send + Sync {
107    /// Rebuild a single value.
108    fn rebuild_val(&self, val: Value) -> Value;
109    /// Rebuild a slice of values in place, returning true if any values were changed.
110    ///
111    /// Defaults to mapping each value through [`ValueRebuilder::rebuild_val`];
112    /// implementors may override for efficiency.
113    fn rebuild_slice(&self, vals: &mut [Value]) -> bool {
114        let mut changed = false;
115        for val in vals.iter_mut() {
116            let new = self.rebuild_val(*val);
117            if new != *val {
118                *val = new;
119                changed = true;
120            }
121        }
122        changed
123    }
124}
125
126/// Custom functions used for tables that encode a bulk value-level rebuild of other tables.
127///
128/// Extends [`ValueRebuilder`] with table-level (bulk) operations.
129///
130/// The initial use-case for this trait is to support optimized implementations of rebuilding,
131/// where `Rebuilder` is implemented as a Union-find.
132///
133/// Value-level rebuilds are difficult to implement efficiently using rules as they require
134/// searching for changes to any column for a table: while it is possible to do, implementing this
135/// custom is more efficient in the case of rebuilding.
136pub trait Rebuilder: ValueRebuilder {
137    /// The column that contains values that should be rebuilt. If this is set, callers can use
138    /// this functionality to perform rebuilds incrementally.
139    fn hint_col(&self) -> Option<ColumnId>;
140    /// Rebuild a contiguous slice of rows in the table.
141    fn rebuild_buf(
142        &self,
143        buf: &RowBuffer,
144        start: RowId,
145        end: RowId,
146        out: &mut TaggedRowBuffer,
147        exec_state: &mut ExecutionState,
148    );
149    /// Rebuild an arbitrary subset of the table.
150    fn rebuild_subset(
151        &self,
152        other: WrappedTableRef,
153        subset: SubsetRef,
154        out: &mut TaggedRowBuffer,
155        exec_state: &mut ExecutionState,
156    );
157}
158
159/// A row in a table.
160pub struct Row {
161    /// The id associated with the row.
162    pub id: RowId,
163    /// The Row itself.
164    pub vals: Pooled<Vec<Value>>,
165}
166
167/// An interface for a table.
168pub trait Table: Any + Send + Sync {
169    /// A variant of clone that returns a boxed trait object; this trait object
170    /// must contain all of the data associated with the current table.
171    fn dyn_clone(&self) -> Box<dyn Table>;
172
173    /// If this table can perform a table-level rebuild, construct a [`Rebuilder`] for it.
174    fn rebuilder<'a>(&'a self, _cols: &[ColumnId]) -> Option<Box<dyn Rebuilder + 'a>> {
175        None
176    }
177
178    /// Rebuild the table according to the given [`Rebuilder`] implemented by `table`, if
179    /// there is one. Applying a rebuild can cause more mutations to be buffered, which can in turn
180    /// be flushed by a call to [`Table::merge`].
181    ///
182    /// Note that value-level rebuilds are only relevant for tables that opt into it. As a result,
183    /// tables do nothing by default.
184    ///
185    /// Returns whether any rows may be removed or inserted.
186    fn apply_rebuild(
187        &mut self,
188        _table_id: TableId,
189        _table: &WrappedTable,
190        _next_ts: Value,
191        _exec_state: &mut ExecutionState,
192    ) -> bool {
193        // Default implementation does nothing.
194        false
195    }
196
197    /// Refresh rows whose rebuildable columns mention one of `dirty_ids` by re-inserting the same
198    /// logical row with a fresh timestamp.
199    ///
200    /// This is the narrow escape hatch used when some external rebuild step
201    /// changes the semantics of an id in place, so seminaive needs a new
202    /// parent-row delta even though the row's key columns do not otherwise
203    /// change.
204    ///
205    /// One source of such ids is [`crate::ContainerRebuildSummary::dirty_ids`].
206    ///
207    /// Tables that do not maintain rebuildable id columns can use the default
208    /// no-op implementation.
209    fn refresh_rows_for_values(&mut self, _dirty_ids: &[Value], _next_ts: Value) -> bool {
210        false
211    }
212
213    /// A boilerplate method to make it easier to downcast values of `Table`.
214    ///
215    /// Implementors should be able to implement this method by returning
216    /// `self`.
217    fn as_any(&self) -> &dyn Any;
218
219    /// The schema of the table.
220    ///
221    /// These are immutable properties of the table; callers can assume they
222    /// will never change.
223    fn spec(&self) -> TableSpec;
224
225    /// Clear all table contents. If the table is nonempty, this will change the
226    /// generation of the table. This method also clears any pending data.
227    fn clear(&mut self);
228
229    // Used in queries:
230
231    /// Get a subset corresponding to all rows in the table.
232    fn all(&self) -> Subset;
233
234    /// Get the length of the table.
235    ///
236    /// This is not in general equal to the length of the `all` subset: the size
237    /// of a subset is allowed to be larger than the number of table entries in
238    /// range of the subset.
239    fn len(&self) -> usize;
240
241    /// Check if the table is empty.
242    fn is_empty(&self) -> bool {
243        self.len() == 0
244    }
245
246    /// Get the current version for the table. [`RowId`]s and [`Subset`]s are
247    /// only valid for a given major generation.
248    fn version(&self) -> TableVersion;
249
250    /// Get the subset of the table that has appeared since the last offset.
251    fn updates_since(&self, offset: Offset) -> Subset;
252
253    /// Iterate over the given subset of the table, starting at an opaque
254    /// `start` token, ending after up to `n` rows, returning the next start
255    /// token if more rows remain. Only invoke `f` on rows that match the given
256    /// constraints.
257    ///
258    /// This method is _not_ object safe, but it is used to define various
259    /// "default" implementations of object-safe methods like `scan` and
260    /// `pivot`.
261    fn scan_generic_bounded(
262        &self,
263        subset: SubsetRef,
264        start: Offset,
265        n: usize,
266        cs: &[Constraint],
267        f: impl FnMut(RowId, &[Value]),
268    ) -> Option<Offset>
269    where
270        Self: Sized;
271
272    /// Iterate over the given subset of the table.
273    ///
274    /// This is a variant of [`Table::scan_generic_bounded`] that iterates over
275    /// the entire table.
276    fn scan_generic(&self, subset: SubsetRef, mut f: impl FnMut(RowId, &[Value]))
277    where
278        Self: Sized,
279    {
280        let mut cur = Offset::new(0);
281        while let Some(next) = self.scan_generic_bounded(subset, cur, usize::MAX, &[], |id, row| {
282            f(id, row);
283        }) {
284            cur = next;
285        }
286    }
287
288    /// Returns true if the table contains any stale rows (rows whose first column
289    /// has been set to [`Value::stale()`]). The default implementation returns `true`
290    /// (conservative). Tables that track stale-row counts should override this.
291    fn has_stale_rows(&self) -> bool {
292        true
293    }
294
295    /// Filter a given subset of the table for the rows that are live
296    fn refine_live(&self, subset: Subset) -> Subset {
297        // NB: This relies on Value::stale() being strictly larger than any other value in the table.
298        self.refine_one(
299            subset,
300            &Constraint::LtConst {
301                col: ColumnId::new_const(0),
302                val: Value::stale(),
303            },
304        )
305    }
306
307    /// Filter a given subset of the table for the rows matching the single constraint.
308    ///
309    /// Implementors must provide at least one of `refine_one` or `refine`.`
310    fn refine_one(&self, subset: Subset, c: &Constraint) -> Subset {
311        self.refine(subset, std::slice::from_ref(c))
312    }
313
314    /// Filter a given subset of the table for the rows matching the given constraints.
315    ///
316    /// Implementors must provide at least one of `refine_one` or `refine`.`
317    fn refine(&self, subset: Subset, cs: &[Constraint]) -> Subset {
318        cs.iter()
319            .fold(subset, |subset, c| self.refine_one(subset, c))
320    }
321
322    /// Filter a borrowed `subset` to the rows matching `cs` — and, when
323    /// `check_live` is set, to live rows — returning an owned subset.
324    ///
325    /// Equivalent to `to_owned` + [`Table::refine_live`] + [`Table::refine`];
326    /// implementors may fuse the copy and filter into a single pass.
327    fn refine_ref(&self, subset: SubsetRef, cs: &[Constraint], check_live: bool) -> Subset {
328        let mut owned = subset.to_owned(&with_pool_set(|ps| ps.get_pool()));
329        if check_live {
330            owned = self.refine_live(owned);
331        }
332        if cs.is_empty() {
333            owned
334        } else {
335            self.refine(owned, cs)
336        }
337    }
338
339    /// An optional method for quickly generating a subset from a constraint.
340    /// The standard use-case here is to apply constraints based on a column
341    /// that is known to be sorted.
342    ///
343    /// These constraints are very helpful for query planning; it is a good idea
344    /// to implement them.
345    fn fast_subset(&self, _: &Constraint) -> Option<Subset> {
346        None
347    }
348
349    /// A helper routine that leverages the existing `fast_subset` method to
350    /// preprocess a set of constraints into "fast" and "slow" ones, returning
351    /// the subet of indexes that match the fast one.
352    fn split_fast_slow(
353        &self,
354        cs: &[Constraint],
355    ) -> (
356        Subset,                  /* the subset of the table matching all fast constraints */
357        Pooled<Vec<Constraint>>, /* the fast constraints */
358        Pooled<Vec<Constraint>>, /* the slow constraints */
359    ) {
360        with_pool_set(|ps| {
361            let mut fast = ps.get::<Vec<Constraint>>();
362            let mut slow = ps.get::<Vec<Constraint>>();
363            let mut subset = self.all();
364            for c in cs {
365                if let Some(sub) = self.fast_subset(c) {
366                    subset.intersect(sub.as_ref(), &ps.get_pool());
367                    fast.push(c.clone());
368                } else {
369                    slow.push(c.clone());
370                }
371            }
372            (subset, fast, slow)
373        })
374    }
375
376    // Used in actions:
377
378    /// Look up a single row by the given key values, if it is in the table.
379    ///
380    /// The number of values specified by `keys` should match the number of
381    /// primary keys for the table.
382    fn get_row(&self, key: &[Value]) -> Option<Row>;
383
384    /// Look up the given column of single row by the given key values, if it is
385    /// in the table.
386    ///
387    /// The number of values specified by `keys` should match the number of
388    /// primary keys for the table.
389    fn get_row_column(&self, key: &[Value], col: ColumnId) -> Option<Value> {
390        self.get_row(key).map(|row| row.vals[col.index()])
391    }
392
393    /// Merge any updates to the table, and potentially update the generation for
394    /// the table.
395    fn merge(&mut self, exec_state: &mut ExecutionState) -> TableChange;
396
397    /// Create a new buffer for staging mutations on this table. Mutations staged to a
398    /// MutationBuffer that is then dropped may not take effect until the next call to
399    /// [`Table::merge`].
400    fn new_buffer(&self) -> Box<dyn MutationBuffer>;
401}
402
403/// A trait specifying a buffer of pending mutations for a [`Table`].
404///
405/// Dropping an object implementing this trait should "flush" the pending
406/// mutations to the table. Calling  [`Table::merge`] on that table would then
407/// apply those mutations, making them visible for future readers.
408pub trait MutationBuffer: Any + Send + Sync {
409    /// Stage the keyed entries for insertion. Changes may not be visible until
410    /// this buffer is dropped, and after `merge` is called on the underlying
411    /// table.
412    fn stage_insert(&mut self, row: &[Value]);
413
414    /// Stage the keyed entries for removal. Changes may not be visible until
415    /// this buffer is dropped, and after `merge` is called on the underlying
416    /// table.
417    fn stage_remove(&mut self, key: &[Value]);
418
419    /// Get a fresh handle to the same table.
420    fn fresh_handle(&self) -> Box<dyn MutationBuffer>;
421}
422
423struct WrapperImpl<T>(PhantomData<T>);
424
425pub(crate) fn wrapper<T: Table>() -> Box<dyn TableWrapper> {
426    Box::new(WrapperImpl::<T>(PhantomData))
427}
428
429impl<T: Table> TableWrapper for WrapperImpl<T> {
430    fn dyn_clone(&self) -> Box<dyn TableWrapper> {
431        Box::new(Self(PhantomData))
432    }
433    fn scan_bounded(
434        &self,
435        table: &dyn Table,
436        subset: SubsetRef,
437        start: Offset,
438        n: usize,
439        out: &mut TaggedRowBuffer,
440    ) -> Option<Offset> {
441        let table = table.as_any().downcast_ref::<T>().unwrap();
442        table.scan_generic_bounded(subset, start, n, &[], |row_id, row| {
443            out.add_row(row_id, row);
444        })
445    }
446    fn group_by_key(&self, table: &dyn Table, subset: SubsetRef, cols: &[ColumnId]) -> TupleIndex {
447        let table = table.as_any().downcast_ref::<T>().unwrap();
448        let mut res = TupleIndex::new(cols.len());
449        match cols {
450            [] => {}
451            [col] => table.scan_generic(subset, |row_id, row| {
452                res.add_row(&[row[col.index()]], row_id);
453            }),
454            [x, y] => table.scan_generic(subset, |row_id, row| {
455                res.add_row(&[row[x.index()], row[y.index()]], row_id);
456            }),
457            [x, y, z] => table.scan_generic(subset, |row_id, row| {
458                res.add_row(&[row[x.index()], row[y.index()], row[z.index()]], row_id);
459            }),
460            _ => {
461                let mut scratch = SmallVec::<[Value; 8]>::new();
462                table.scan_generic(subset, |row_id, row| {
463                    for col in cols {
464                        scratch.push(row[col.index()]);
465                    }
466                    res.add_row(&scratch, row_id);
467                    scratch.clear();
468                });
469            }
470        }
471        res
472    }
473    fn for_each_col(
474        &self,
475        table: &dyn Table,
476        subset: SubsetRef,
477        col: ColumnId,
478        f: &mut dyn FnMut(RowId, Value),
479    ) {
480        let table = table.as_any().downcast_ref::<T>().unwrap();
481        let col_idx = col.index();
482        table.scan_generic(subset, |row_id, row| {
483            f(row_id, row[col_idx]);
484        });
485    }
486
487    fn collect_col_pairs(
488        &self,
489        table: &dyn Table,
490        subset: SubsetRef,
491        col: ColumnId,
492        out: &mut Vec<(Value, RowId)>,
493    ) {
494        let table = table.as_any().downcast_ref::<T>().unwrap();
495        let col_idx = col.index();
496        out.reserve(subset.size());
497        table.scan_generic(subset, |row_id, row| {
498            out.push((row[col_idx], row_id));
499        });
500    }
501
502    fn scan_project(
503        &self,
504        table: &dyn Table,
505        subset: SubsetRef,
506        cols: &[ColumnId],
507        start: Offset,
508        n: usize,
509        cs: &[Constraint],
510        out: &mut dyn RowSink,
511    ) -> Option<Offset> {
512        let table = table.as_any().downcast_ref::<T>().unwrap();
513        match cols {
514            [] => None,
515            [col] => table.scan_generic_bounded(subset, start, n, cs, |id, row| {
516                out.add_row(id, &[row[col.index()]]);
517            }),
518            [x, y] => table.scan_generic_bounded(subset, start, n, cs, |id, row| {
519                out.add_row(id, &[row[x.index()], row[y.index()]]);
520            }),
521            [x, y, z] => table.scan_generic_bounded(subset, start, n, cs, |id, row| {
522                out.add_row(id, &[row[x.index()], row[y.index()], row[z.index()]]);
523            }),
524            _ => {
525                let mut scratch = SmallVec::<[Value; 8]>::with_capacity(cols.len());
526                table.scan_generic_bounded(subset, start, n, cs, |id, row| {
527                    for col in cols {
528                        scratch.push(row[col.index()]);
529                    }
530                    out.add_row(id, &scratch);
531                    scratch.clear();
532                })
533            }
534        }
535    }
536
537    fn lookup_row_vectorized(
538        &self,
539        table: &dyn Table,
540        mask: &mut Mask,
541        bindings: &mut Bindings,
542        args: &[QueryEntry],
543        col: ColumnId,
544        out_var: Variable,
545    ) {
546        let table = table.as_any().downcast_ref::<T>().unwrap();
547        let mut out = with_pool_set(PoolSet::get::<Vec<Value>>);
548        for_each_binding_with_mask!(mask, args, bindings, |iter| {
549            iter.fill_vec(&mut out, Value::stale, |_, args| {
550                table.get_row_column(args.as_slice(), col)
551            })
552        });
553        bindings.insert(out_var, &out);
554    }
555
556    fn lookup_with_default_vectorized(
557        &self,
558        table: &dyn Table,
559        mask: &mut Mask,
560        bindings: &mut Bindings,
561        args: &[QueryEntry],
562        col: ColumnId,
563        default: QueryEntry,
564        out_var: Variable,
565    ) {
566        let table = table.as_any().downcast_ref::<T>().unwrap();
567        let mut out = with_pool_set(|ps| ps.get::<Vec<Value>>());
568        for_each_binding_with_mask!(mask, args, bindings, |iter| {
569            match default {
570                QueryEntry::Var(default) => iter.zip(&bindings[default]).fill_vec(
571                    &mut out,
572                    Value::stale,
573                    |_, (args, default)| {
574                        Some(
575                            table
576                                .get_row_column(args.as_slice(), col)
577                                .unwrap_or(*default),
578                        )
579                    },
580                ),
581                QueryEntry::Const(default) => iter.fill_vec(&mut out, Value::stale, |_, args| {
582                    Some(
583                        table
584                            .get_row_column(args.as_slice(), col)
585                            .unwrap_or(default),
586                    )
587                }),
588            }
589        });
590        bindings.insert(out_var, &out);
591    }
592}
593
594/// A WrappedTable takes a Table and extends it with a number of helpful,
595/// object-safe methods for accessing a table.
596///
597/// It essentially acts like an extension trait: it is a separate type to allow
598/// object-safe extension methods to call methods that require `Self: Sized`.
599/// The implementations here downcast manually to the type used when
600/// constructing the WrappedTable.
601pub struct WrappedTable {
602    inner: Box<dyn Table>,
603    wrapper: Box<dyn TableWrapper>,
604}
605
606impl WrappedTable {
607    pub(crate) fn new<T: Table>(inner: T) -> Self {
608        let wrapper = wrapper::<T>();
609        let inner = Box::new(inner);
610        Self { inner, wrapper }
611    }
612
613    /// Clone the contents of the table.
614    pub fn dyn_clone(&self) -> Self {
615        WrappedTable {
616            inner: self.inner.dyn_clone(),
617            wrapper: self.wrapper.dyn_clone(),
618        }
619    }
620
621    pub(crate) fn as_ref(&self) -> WrappedTableRef<'_> {
622        WrappedTableRef {
623            inner: &*self.inner,
624            wrapper: &*self.wrapper,
625        }
626    }
627
628    /// Starting at the given [`Offset`] into `subset`, scan up to `n` rows and
629    /// write them to `out`. Return the next starting offset. If no offset is
630    /// returned then the subset has been scanned completely.
631    pub fn scan_bounded(
632        &self,
633        subset: SubsetRef,
634        start: Offset,
635        n: usize,
636        out: &mut TaggedRowBuffer,
637    ) -> Option<Offset> {
638        self.as_ref().scan_bounded(subset, start, n, out)
639    }
640
641    /// Group the contents of the given subset by the given columns.
642    pub(crate) fn group_by_key(&self, subset: SubsetRef, cols: &[ColumnId]) -> TupleIndex {
643        self.as_ref().group_by_key(subset, cols)
644    }
645
646    /// A variant fo [`WrappedTable::scan_bounded`] that projects a subset of
647    /// columns and only appends rows that match the given constraints.
648    pub fn scan_project(
649        &self,
650        subset: SubsetRef,
651        cols: &[ColumnId],
652        start: Offset,
653        n: usize,
654        cs: &[Constraint],
655        out: &mut dyn RowSink,
656    ) -> Option<Offset> {
657        self.as_ref().scan_project(subset, cols, start, n, cs, out)
658    }
659
660    /// Return the contents of the subset as a [`TaggedRowBuffer`].
661    pub fn scan(&self, subset: SubsetRef) -> TaggedRowBuffer {
662        self.as_ref().scan(subset)
663    }
664
665    /// Return the number of rows currently stored in the table.
666    pub fn len(&self) -> usize {
667        self.inner.len()
668    }
669
670    /// Check if the table is empty.
671    pub fn is_empty(&self) -> bool {
672        self.inner.is_empty()
673    }
674
675    pub(crate) fn lookup_row_vectorized(
676        &self,
677        mask: &mut Mask,
678        bindings: &mut Bindings,
679        args: &[QueryEntry],
680        col: ColumnId,
681        out_var: Variable,
682    ) {
683        self.as_ref()
684            .lookup_row_vectorized(mask, bindings, args, col, out_var)
685    }
686
687    #[allow(clippy::too_many_arguments)]
688    pub(crate) fn lookup_with_default_vectorized(
689        &self,
690        mask: &mut Mask,
691        bindings: &mut Bindings,
692        args: &[QueryEntry],
693        col: ColumnId,
694        default: QueryEntry,
695        out_var: Variable,
696    ) {
697        self.as_ref()
698            .lookup_with_default_vectorized(mask, bindings, args, col, default, out_var)
699    }
700}
701
702impl Deref for WrappedTable {
703    type Target = dyn Table;
704
705    fn deref(&self) -> &Self::Target {
706        &*self.inner
707    }
708}
709
710impl DerefMut for WrappedTable {
711    fn deref_mut(&mut self) -> &mut Self::Target {
712        &mut *self.inner
713    }
714}
715
716pub(crate) trait TableWrapper: Send + Sync {
717    fn dyn_clone(&self) -> Box<dyn TableWrapper>;
718    fn scan_bounded(
719        &self,
720        table: &dyn Table,
721        subset: SubsetRef,
722        start: Offset,
723        n: usize,
724        out: &mut TaggedRowBuffer,
725    ) -> Option<Offset>;
726    fn group_by_key(&self, table: &dyn Table, subset: SubsetRef, cols: &[ColumnId]) -> TupleIndex;
727
728    /// Scan each row in `subset`, calling `f(row_id, col_value)` for each.
729    /// Unlike `scan_project`, this writes directly to the callback with no
730    /// intermediate buffer.
731    fn for_each_col(
732        &self,
733        table: &dyn Table,
734        subset: SubsetRef,
735        col: ColumnId,
736        f: &mut dyn FnMut(RowId, Value),
737    );
738
739    /// Append `(col_value, row_id)` for each row in `subset` to `out`.
740    ///
741    /// Equivalent to [`TableWrapper::for_each_col`] pushing into `out`, but the
742    /// scan loop is monomorphized, avoiding a virtual callback per row.
743    fn collect_col_pairs(
744        &self,
745        table: &dyn Table,
746        subset: SubsetRef,
747        col: ColumnId,
748        out: &mut Vec<(Value, RowId)>,
749    );
750
751    #[allow(clippy::too_many_arguments)]
752    fn scan_project(
753        &self,
754        table: &dyn Table,
755        subset: SubsetRef,
756        cols: &[ColumnId],
757        start: Offset,
758        n: usize,
759        cs: &[Constraint],
760        out: &mut dyn RowSink,
761    ) -> Option<Offset>;
762
763    fn scan(&self, table: &dyn Table, subset: SubsetRef) -> TaggedRowBuffer {
764        let arity = table.spec().arity();
765        let mut buf = TaggedRowBuffer::new(arity);
766        assert!(
767            self.scan_bounded(table, subset, Offset::new(0), usize::MAX, &mut buf)
768                .is_none()
769        );
770        buf
771    }
772
773    #[allow(clippy::too_many_arguments)]
774    fn lookup_row_vectorized(
775        &self,
776        table: &dyn Table,
777        mask: &mut Mask,
778        bindings: &mut Bindings,
779        args: &[QueryEntry],
780        col: ColumnId,
781        out_var: Variable,
782    );
783
784    #[allow(clippy::too_many_arguments)]
785    fn lookup_with_default_vectorized(
786        &self,
787        table: &dyn Table,
788        mask: &mut Mask,
789        bindings: &mut Bindings,
790        args: &[QueryEntry],
791        col: ColumnId,
792        default: QueryEntry,
793        out_var: Variable,
794    );
795}
796
797/// An extra layer of indirection over a [`WrappedTable`] that does not require that the caller
798/// actually own the table. This is useful when a table implementation needs to construct a
799/// WrappedTable on its own.
800#[derive(Clone, Copy)]
801pub struct WrappedTableRef<'a> {
802    inner: &'a dyn Table,
803    wrapper: &'a dyn TableWrapper,
804}
805
806impl WrappedTableRef<'_> {
807    pub(crate) fn with_wrapper<T: Table, R>(
808        inner: &T,
809        f: impl for<'a> FnOnce(WrappedTableRef<'a>) -> R,
810    ) -> R {
811        let wrapper = WrapperImpl::<T>(PhantomData);
812        f(WrappedTableRef {
813            inner,
814            wrapper: &wrapper,
815        })
816    }
817
818    /// Starting at the given [`Offset`] into `subset`, scan up to `n` rows and
819    /// write them to `out`. Return the next starting offset. If no offset is
820    /// returned then the subset has been scanned completely.
821    pub fn scan_bounded(
822        &self,
823        subset: SubsetRef,
824        start: Offset,
825        n: usize,
826        out: &mut TaggedRowBuffer,
827    ) -> Option<Offset> {
828        self.wrapper.scan_bounded(self.inner, subset, start, n, out)
829    }
830
831    /// Group the contents of the given subset by the given columns.
832    pub(crate) fn group_by_key(&self, subset: SubsetRef, cols: &[ColumnId]) -> TupleIndex {
833        self.wrapper.group_by_key(self.inner, subset, cols)
834    }
835
836    /// Scan each row in `subset` and call `f(row_id, col_value)` for each.
837    /// This is a zero-copy alternative to `scan_project` for single-column
838    /// scans over small subsets where an intermediate buffer is wasteful.
839    pub(crate) fn for_each_col(
840        &self,
841        subset: SubsetRef,
842        col: ColumnId,
843        f: &mut dyn FnMut(RowId, Value),
844    ) {
845        self.wrapper.for_each_col(self.inner, subset, col, f);
846    }
847
848    /// Append `(col_value, row_id)` for each row in `subset` to `out`, using a
849    /// monomorphized scan loop (no per-row virtual call).
850    pub(crate) fn collect_col_pairs(
851        &self,
852        subset: SubsetRef,
853        col: ColumnId,
854        out: &mut Vec<(Value, RowId)>,
855    ) {
856        self.wrapper.collect_col_pairs(self.inner, subset, col, out);
857    }
858
859    /// A variant fo [`WrappedTable::scan_bounded`] that projects a subset of
860    /// columns and only appends rows that match the given constraints.
861    pub fn scan_project(
862        &self,
863        subset: SubsetRef,
864        cols: &[ColumnId],
865        start: Offset,
866        n: usize,
867        cs: &[Constraint],
868        out: &mut dyn RowSink,
869    ) -> Option<Offset> {
870        self.wrapper
871            .scan_project(self.inner, subset, cols, start, n, cs, out)
872    }
873
874    /// Return the contents of the subset as a [`TaggedRowBuffer`].
875    pub fn scan(&self, subset: SubsetRef) -> TaggedRowBuffer {
876        self.wrapper.scan(self.inner, subset)
877    }
878
879    /// Return the number of rows currently stored in the table.
880    pub fn len(&self) -> usize {
881        self.inner.len()
882    }
883
884    pub(crate) fn lookup_row_vectorized(
885        &self,
886        mask: &mut Mask,
887        bindings: &mut Bindings,
888        args: &[QueryEntry],
889        col: ColumnId,
890        out_var: Variable,
891    ) {
892        self.wrapper
893            .lookup_row_vectorized(self.inner, mask, bindings, args, col, out_var);
894    }
895
896    #[allow(clippy::too_many_arguments)]
897    pub(crate) fn lookup_with_default_vectorized(
898        &self,
899        mask: &mut Mask,
900        bindings: &mut Bindings,
901        args: &[QueryEntry],
902        col: ColumnId,
903        default: QueryEntry,
904        out_var: Variable,
905    ) {
906        self.wrapper.lookup_with_default_vectorized(
907            self.inner, mask, bindings, args, col, default, out_var,
908        );
909    }
910}
911
912impl Deref for WrappedTableRef<'_> {
913    type Target = dyn Table;
914
915    fn deref(&self) -> &Self::Target {
916        self.inner
917    }
918}