Skip to main content

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    /// An implementation must not invoke `f` on a stale row, constrained or
259    /// not, so callers need not filter stale rows back out.
260    /// `tests/repro-stale-rows.egg` guards the constrained path.
261    ///
262    /// This method is _not_ object safe, but it is used to define various
263    /// "default" implementations of object-safe methods like `scan` and
264    /// `pivot`.
265    fn scan_generic_bounded(
266        &self,
267        subset: SubsetRef,
268        start: Offset,
269        n: usize,
270        cs: &[Constraint],
271        f: impl FnMut(RowId, &[Value]),
272    ) -> Option<Offset>
273    where
274        Self: Sized;
275
276    /// Iterate over the given subset of the table.
277    ///
278    /// This is a variant of [`Table::scan_generic_bounded`] that iterates over
279    /// the entire table.
280    fn scan_generic(&self, subset: SubsetRef, mut f: impl FnMut(RowId, &[Value]))
281    where
282        Self: Sized,
283    {
284        let mut cur = Offset::new(0);
285        while let Some(next) = self.scan_generic_bounded(subset, cur, usize::MAX, &[], |id, row| {
286            f(id, row);
287        }) {
288            cur = next;
289        }
290    }
291
292    /// Returns true if the table contains any stale rows (rows whose first column
293    /// has been set to [`Value::stale()`]). The default implementation returns `true`
294    /// (conservative). Tables that track stale-row counts should override this.
295    fn has_stale_rows(&self) -> bool {
296        true
297    }
298
299    /// Filter a given subset of the table for the rows that are live
300    fn refine_live(&self, subset: Subset) -> Subset {
301        // NB: This relies on Value::stale() being strictly larger than any other value in the table.
302        self.refine_one(
303            subset,
304            &Constraint::LtConst {
305                col: ColumnId::new_const(0),
306                val: Value::stale(),
307            },
308        )
309    }
310
311    /// Filter a given subset of the table for the rows matching the single constraint.
312    ///
313    /// Implementors must provide at least one of `refine_one` or `refine`.`
314    fn refine_one(&self, subset: Subset, c: &Constraint) -> Subset {
315        self.refine(subset, std::slice::from_ref(c))
316    }
317
318    /// Filter a given subset of the table for the rows matching the given constraints.
319    ///
320    /// Implementors must provide at least one of `refine_one` or `refine`.`
321    fn refine(&self, subset: Subset, cs: &[Constraint]) -> Subset {
322        cs.iter()
323            .fold(subset, |subset, c| self.refine_one(subset, c))
324    }
325
326    /// Filter a borrowed `subset` to the rows matching `cs` — and, when
327    /// `check_live` is set, to live rows — returning an owned subset.
328    ///
329    /// Equivalent to `to_owned` + [`Table::refine_live`] + [`Table::refine`];
330    /// implementors may fuse the copy and filter into a single pass.
331    fn refine_ref(&self, subset: SubsetRef, cs: &[Constraint], check_live: bool) -> Subset {
332        let mut owned = subset.to_owned(&with_pool_set(|ps| ps.get_pool()));
333        if check_live {
334            owned = self.refine_live(owned);
335        }
336        if cs.is_empty() {
337            owned
338        } else {
339            self.refine(owned, cs)
340        }
341    }
342
343    /// An optional method for quickly generating a subset from a constraint.
344    /// The standard use-case here is to apply constraints based on a column
345    /// that is known to be sorted.
346    ///
347    /// These constraints are very helpful for query planning; it is a good idea
348    /// to implement them.
349    fn fast_subset(&self, _: &Constraint) -> Option<Subset> {
350        None
351    }
352
353    /// A helper routine that leverages the existing `fast_subset` method to
354    /// preprocess a set of constraints into "fast" and "slow" ones, returning
355    /// the subet of indexes that match the fast one.
356    fn split_fast_slow(
357        &self,
358        cs: &[Constraint],
359    ) -> (
360        Subset,                  /* the subset of the table matching all fast constraints */
361        Pooled<Vec<Constraint>>, /* the fast constraints */
362        Pooled<Vec<Constraint>>, /* the slow constraints */
363    ) {
364        with_pool_set(|ps| {
365            let mut fast = ps.get::<Vec<Constraint>>();
366            let mut slow = ps.get::<Vec<Constraint>>();
367            let mut subset = self.all();
368            for c in cs {
369                if let Some(sub) = self.fast_subset(c) {
370                    subset.intersect(sub.as_ref(), &ps.get_pool());
371                    fast.push(c.clone());
372                } else {
373                    slow.push(c.clone());
374                }
375            }
376            (subset, fast, slow)
377        })
378    }
379
380    // Used in actions:
381
382    /// Look up a single row by the given key values, if it is in the table.
383    ///
384    /// The number of values specified by `keys` should match the number of
385    /// primary keys for the table.
386    fn get_row(&self, key: &[Value]) -> Option<Row>;
387
388    /// Look up the given column of single row by the given key values, if it is
389    /// in the table.
390    ///
391    /// The number of values specified by `keys` should match the number of
392    /// primary keys for the table.
393    fn get_row_column(&self, key: &[Value], col: ColumnId) -> Option<Value> {
394        self.get_row(key).map(|row| row.vals[col.index()])
395    }
396
397    /// Merge any updates to the table, and potentially update the generation for
398    /// the table.
399    fn merge(&mut self, exec_state: &mut ExecutionState) -> TableChange;
400
401    /// Create a new buffer for staging mutations on this table. Mutations staged to a
402    /// MutationBuffer that is then dropped may not take effect until the next call to
403    /// [`Table::merge`].
404    fn new_buffer(&self) -> Box<dyn MutationBuffer>;
405}
406
407/// A trait specifying a buffer of pending mutations for a [`Table`].
408///
409/// Dropping an object implementing this trait should "flush" the pending
410/// mutations to the table. Calling  [`Table::merge`] on that table would then
411/// apply those mutations, making them visible for future readers.
412pub trait MutationBuffer: Any + Send + Sync {
413    /// Stage the keyed entries for insertion. Changes may not be visible until
414    /// this buffer is dropped, and after `merge` is called on the underlying
415    /// table.
416    fn stage_insert(&mut self, row: &[Value]);
417
418    /// Stage the keyed entries for removal. Changes may not be visible until
419    /// this buffer is dropped, and after `merge` is called on the underlying
420    /// table.
421    fn stage_remove(&mut self, key: &[Value]);
422
423    /// Get a fresh handle to the same table.
424    fn fresh_handle(&self) -> Box<dyn MutationBuffer>;
425}
426
427struct WrapperImpl<T>(PhantomData<T>);
428
429pub(crate) fn wrapper<T: Table>() -> Box<dyn TableWrapper> {
430    Box::new(WrapperImpl::<T>(PhantomData))
431}
432
433impl<T: Table> TableWrapper for WrapperImpl<T> {
434    fn dyn_clone(&self) -> Box<dyn TableWrapper> {
435        Box::new(Self(PhantomData))
436    }
437    fn scan_bounded(
438        &self,
439        table: &dyn Table,
440        subset: SubsetRef,
441        start: Offset,
442        n: usize,
443        out: &mut TaggedRowBuffer,
444    ) -> Option<Offset> {
445        let table = table.as_any().downcast_ref::<T>().unwrap();
446        table.scan_generic_bounded(subset, start, n, &[], |row_id, row| {
447            out.add_row(row_id, row);
448        })
449    }
450    fn group_by_key(&self, table: &dyn Table, subset: SubsetRef, cols: &[ColumnId]) -> TupleIndex {
451        let table = table.as_any().downcast_ref::<T>().unwrap();
452        let mut res = TupleIndex::new(cols.len());
453        match cols {
454            [] => {}
455            [col] => table.scan_generic(subset, |row_id, row| {
456                res.add_row(&[row[col.index()]], row_id);
457            }),
458            [x, y] => table.scan_generic(subset, |row_id, row| {
459                res.add_row(&[row[x.index()], row[y.index()]], row_id);
460            }),
461            [x, y, z] => table.scan_generic(subset, |row_id, row| {
462                res.add_row(&[row[x.index()], row[y.index()], row[z.index()]], row_id);
463            }),
464            _ => {
465                let mut scratch = SmallVec::<[Value; 8]>::new();
466                table.scan_generic(subset, |row_id, row| {
467                    for col in cols {
468                        scratch.push(row[col.index()]);
469                    }
470                    res.add_row(&scratch, row_id);
471                    scratch.clear();
472                });
473            }
474        }
475        res
476    }
477    fn for_each_col(
478        &self,
479        table: &dyn Table,
480        subset: SubsetRef,
481        col: ColumnId,
482        f: &mut dyn FnMut(RowId, Value),
483    ) {
484        let table = table.as_any().downcast_ref::<T>().unwrap();
485        let col_idx = col.index();
486        table.scan_generic(subset, |row_id, row| {
487            f(row_id, row[col_idx]);
488        });
489    }
490
491    fn collect_col_pairs(
492        &self,
493        table: &dyn Table,
494        subset: SubsetRef,
495        col: ColumnId,
496        out: &mut Vec<(Value, RowId)>,
497    ) {
498        let table = table.as_any().downcast_ref::<T>().unwrap();
499        let col_idx = col.index();
500        out.reserve(subset.size());
501        table.scan_generic(subset, |row_id, row| {
502            out.push((row[col_idx], row_id));
503        });
504    }
505
506    fn scan_project(
507        &self,
508        table: &dyn Table,
509        subset: SubsetRef,
510        cols: &[ColumnId],
511        start: Offset,
512        n: usize,
513        cs: &[Constraint],
514        out: &mut dyn RowSink,
515    ) -> Option<Offset> {
516        let table = table.as_any().downcast_ref::<T>().unwrap();
517        match cols {
518            [] => None,
519            [col] => table.scan_generic_bounded(subset, start, n, cs, |id, row| {
520                out.add_row(id, &[row[col.index()]]);
521            }),
522            [x, y] => table.scan_generic_bounded(subset, start, n, cs, |id, row| {
523                out.add_row(id, &[row[x.index()], row[y.index()]]);
524            }),
525            [x, y, z] => table.scan_generic_bounded(subset, start, n, cs, |id, row| {
526                out.add_row(id, &[row[x.index()], row[y.index()], row[z.index()]]);
527            }),
528            _ => {
529                let mut scratch = SmallVec::<[Value; 8]>::with_capacity(cols.len());
530                table.scan_generic_bounded(subset, start, n, cs, |id, row| {
531                    for col in cols {
532                        scratch.push(row[col.index()]);
533                    }
534                    out.add_row(id, &scratch);
535                    scratch.clear();
536                })
537            }
538        }
539    }
540
541    fn lookup_row_vectorized(
542        &self,
543        table: &dyn Table,
544        mask: &mut Mask,
545        bindings: &mut Bindings,
546        args: &[QueryEntry],
547        col: ColumnId,
548        out_var: Variable,
549    ) {
550        let table = table.as_any().downcast_ref::<T>().unwrap();
551        let mut out = with_pool_set(PoolSet::get::<Vec<Value>>);
552        for_each_binding_with_mask!(mask, args, bindings, |iter| {
553            iter.fill_vec(&mut out, Value::stale, |_, args| {
554                table.get_row_column(args.as_slice(), col)
555            })
556        });
557        bindings.insert(out_var, &out);
558    }
559
560    fn lookup_with_default_vectorized(
561        &self,
562        table: &dyn Table,
563        mask: &mut Mask,
564        bindings: &mut Bindings,
565        args: &[QueryEntry],
566        col: ColumnId,
567        default: QueryEntry,
568        out_var: Variable,
569    ) {
570        let table = table.as_any().downcast_ref::<T>().unwrap();
571        let mut out = with_pool_set(|ps| ps.get::<Vec<Value>>());
572        for_each_binding_with_mask!(mask, args, bindings, |iter| {
573            match default {
574                QueryEntry::Var(default) => iter.zip(&bindings[default]).fill_vec(
575                    &mut out,
576                    Value::stale,
577                    |_, (args, default)| {
578                        Some(
579                            table
580                                .get_row_column(args.as_slice(), col)
581                                .unwrap_or(*default),
582                        )
583                    },
584                ),
585                QueryEntry::Const(default) => iter.fill_vec(&mut out, Value::stale, |_, args| {
586                    Some(
587                        table
588                            .get_row_column(args.as_slice(), col)
589                            .unwrap_or(default),
590                    )
591                }),
592            }
593        });
594        bindings.insert(out_var, &out);
595    }
596}
597
598/// A WrappedTable takes a Table and extends it with a number of helpful,
599/// object-safe methods for accessing a table.
600///
601/// It essentially acts like an extension trait: it is a separate type to allow
602/// object-safe extension methods to call methods that require `Self: Sized`.
603/// The implementations here downcast manually to the type used when
604/// constructing the WrappedTable.
605pub struct WrappedTable {
606    inner: Box<dyn Table>,
607    wrapper: Box<dyn TableWrapper>,
608}
609
610impl WrappedTable {
611    pub(crate) fn new<T: Table>(inner: T) -> Self {
612        let wrapper = wrapper::<T>();
613        let inner = Box::new(inner);
614        Self { inner, wrapper }
615    }
616
617    /// Clone the contents of the table.
618    pub fn dyn_clone(&self) -> Self {
619        WrappedTable {
620            inner: self.inner.dyn_clone(),
621            wrapper: self.wrapper.dyn_clone(),
622        }
623    }
624
625    pub(crate) fn as_ref(&self) -> WrappedTableRef<'_> {
626        WrappedTableRef {
627            inner: &*self.inner,
628            wrapper: &*self.wrapper,
629        }
630    }
631
632    /// Starting at the given [`Offset`] into `subset`, scan up to `n` rows and
633    /// write them to `out`. Return the next starting offset. If no offset is
634    /// returned then the subset has been scanned completely.
635    /// Rows written to `out` are never stale, so callers can take the buffer's
636    /// full contents.
637    pub fn scan_bounded(
638        &self,
639        subset: SubsetRef,
640        start: Offset,
641        n: usize,
642        out: &mut TaggedRowBuffer,
643    ) -> Option<Offset> {
644        self.as_ref().scan_bounded(subset, start, n, out)
645    }
646
647    /// Group the contents of the given subset by the given columns.
648    pub(crate) fn group_by_key(&self, subset: SubsetRef, cols: &[ColumnId]) -> TupleIndex {
649        self.as_ref().group_by_key(subset, cols)
650    }
651
652    /// A variant fo [`WrappedTable::scan_bounded`] that projects a subset of
653    /// columns and only appends rows that match the given constraints.
654    /// Rows written to `out` are never stale, so callers can take the buffer's
655    /// full contents.
656    pub fn scan_project(
657        &self,
658        subset: SubsetRef,
659        cols: &[ColumnId],
660        start: Offset,
661        n: usize,
662        cs: &[Constraint],
663        out: &mut dyn RowSink,
664    ) -> Option<Offset> {
665        self.as_ref().scan_project(subset, cols, start, n, cs, out)
666    }
667
668    /// Return the contents of the subset as a [`TaggedRowBuffer`].
669    pub fn scan(&self, subset: SubsetRef) -> TaggedRowBuffer {
670        self.as_ref().scan(subset)
671    }
672
673    /// Return the number of rows currently stored in the table.
674    pub fn len(&self) -> usize {
675        self.inner.len()
676    }
677
678    /// Check if the table is empty.
679    pub fn is_empty(&self) -> bool {
680        self.inner.is_empty()
681    }
682
683    pub(crate) fn lookup_row_vectorized(
684        &self,
685        mask: &mut Mask,
686        bindings: &mut Bindings,
687        args: &[QueryEntry],
688        col: ColumnId,
689        out_var: Variable,
690    ) {
691        self.as_ref()
692            .lookup_row_vectorized(mask, bindings, args, col, out_var)
693    }
694
695    #[allow(clippy::too_many_arguments)]
696    pub(crate) fn lookup_with_default_vectorized(
697        &self,
698        mask: &mut Mask,
699        bindings: &mut Bindings,
700        args: &[QueryEntry],
701        col: ColumnId,
702        default: QueryEntry,
703        out_var: Variable,
704    ) {
705        self.as_ref()
706            .lookup_with_default_vectorized(mask, bindings, args, col, default, out_var)
707    }
708}
709
710impl Deref for WrappedTable {
711    type Target = dyn Table;
712
713    fn deref(&self) -> &Self::Target {
714        &*self.inner
715    }
716}
717
718impl DerefMut for WrappedTable {
719    fn deref_mut(&mut self) -> &mut Self::Target {
720        &mut *self.inner
721    }
722}
723
724pub(crate) trait TableWrapper: Send + Sync {
725    fn dyn_clone(&self) -> Box<dyn TableWrapper>;
726    fn scan_bounded(
727        &self,
728        table: &dyn Table,
729        subset: SubsetRef,
730        start: Offset,
731        n: usize,
732        out: &mut TaggedRowBuffer,
733    ) -> Option<Offset>;
734    fn group_by_key(&self, table: &dyn Table, subset: SubsetRef, cols: &[ColumnId]) -> TupleIndex;
735
736    /// Scan each row in `subset`, calling `f(row_id, col_value)` for each.
737    /// Unlike `scan_project`, this writes directly to the callback with no
738    /// intermediate buffer.
739    fn for_each_col(
740        &self,
741        table: &dyn Table,
742        subset: SubsetRef,
743        col: ColumnId,
744        f: &mut dyn FnMut(RowId, Value),
745    );
746
747    /// Append `(col_value, row_id)` for each row in `subset` to `out`.
748    ///
749    /// Equivalent to [`TableWrapper::for_each_col`] pushing into `out`, but the
750    /// scan loop is monomorphized, avoiding a virtual callback per row.
751    fn collect_col_pairs(
752        &self,
753        table: &dyn Table,
754        subset: SubsetRef,
755        col: ColumnId,
756        out: &mut Vec<(Value, RowId)>,
757    );
758
759    #[allow(clippy::too_many_arguments)]
760    fn scan_project(
761        &self,
762        table: &dyn Table,
763        subset: SubsetRef,
764        cols: &[ColumnId],
765        start: Offset,
766        n: usize,
767        cs: &[Constraint],
768        out: &mut dyn RowSink,
769    ) -> Option<Offset>;
770
771    fn scan(&self, table: &dyn Table, subset: SubsetRef) -> TaggedRowBuffer {
772        let arity = table.spec().arity();
773        let mut buf = TaggedRowBuffer::new(arity);
774        assert!(
775            self.scan_bounded(table, subset, Offset::new(0), usize::MAX, &mut buf)
776                .is_none()
777        );
778        buf
779    }
780
781    #[allow(clippy::too_many_arguments)]
782    fn lookup_row_vectorized(
783        &self,
784        table: &dyn Table,
785        mask: &mut Mask,
786        bindings: &mut Bindings,
787        args: &[QueryEntry],
788        col: ColumnId,
789        out_var: Variable,
790    );
791
792    #[allow(clippy::too_many_arguments)]
793    fn lookup_with_default_vectorized(
794        &self,
795        table: &dyn Table,
796        mask: &mut Mask,
797        bindings: &mut Bindings,
798        args: &[QueryEntry],
799        col: ColumnId,
800        default: QueryEntry,
801        out_var: Variable,
802    );
803}
804
805/// An extra layer of indirection over a [`WrappedTable`] that does not require that the caller
806/// actually own the table. This is useful when a table implementation needs to construct a
807/// WrappedTable on its own.
808#[derive(Clone, Copy)]
809pub struct WrappedTableRef<'a> {
810    inner: &'a dyn Table,
811    wrapper: &'a dyn TableWrapper,
812}
813
814impl WrappedTableRef<'_> {
815    pub(crate) fn with_wrapper<T: Table, R>(
816        inner: &T,
817        f: impl for<'a> FnOnce(WrappedTableRef<'a>) -> R,
818    ) -> R {
819        let wrapper = WrapperImpl::<T>(PhantomData);
820        f(WrappedTableRef {
821            inner,
822            wrapper: &wrapper,
823        })
824    }
825
826    /// Starting at the given [`Offset`] into `subset`, scan up to `n` rows and
827    /// write them to `out`. Return the next starting offset. If no offset is
828    /// returned then the subset has been scanned completely.
829    pub fn scan_bounded(
830        &self,
831        subset: SubsetRef,
832        start: Offset,
833        n: usize,
834        out: &mut TaggedRowBuffer,
835    ) -> Option<Offset> {
836        self.wrapper.scan_bounded(self.inner, subset, start, n, out)
837    }
838
839    /// Group the contents of the given subset by the given columns.
840    pub(crate) fn group_by_key(&self, subset: SubsetRef, cols: &[ColumnId]) -> TupleIndex {
841        self.wrapper.group_by_key(self.inner, subset, cols)
842    }
843
844    /// Scan each row in `subset` and call `f(row_id, col_value)` for each.
845    /// This is a zero-copy alternative to `scan_project` for single-column
846    /// scans over small subsets where an intermediate buffer is wasteful.
847    pub(crate) fn for_each_col(
848        &self,
849        subset: SubsetRef,
850        col: ColumnId,
851        f: &mut dyn FnMut(RowId, Value),
852    ) {
853        self.wrapper.for_each_col(self.inner, subset, col, f);
854    }
855
856    /// Append `(col_value, row_id)` for each row in `subset` to `out`, using a
857    /// monomorphized scan loop (no per-row virtual call).
858    pub(crate) fn collect_col_pairs(
859        &self,
860        subset: SubsetRef,
861        col: ColumnId,
862        out: &mut Vec<(Value, RowId)>,
863    ) {
864        self.wrapper.collect_col_pairs(self.inner, subset, col, out);
865    }
866
867    /// A variant fo [`WrappedTable::scan_bounded`] that projects a subset of
868    /// columns and only appends rows that match the given constraints.
869    pub fn scan_project(
870        &self,
871        subset: SubsetRef,
872        cols: &[ColumnId],
873        start: Offset,
874        n: usize,
875        cs: &[Constraint],
876        out: &mut dyn RowSink,
877    ) -> Option<Offset> {
878        self.wrapper
879            .scan_project(self.inner, subset, cols, start, n, cs, out)
880    }
881
882    /// Return the contents of the subset as a [`TaggedRowBuffer`].
883    pub fn scan(&self, subset: SubsetRef) -> TaggedRowBuffer {
884        self.wrapper.scan(self.inner, subset)
885    }
886
887    /// Return the number of rows currently stored in the table.
888    pub fn len(&self) -> usize {
889        self.inner.len()
890    }
891
892    pub(crate) fn lookup_row_vectorized(
893        &self,
894        mask: &mut Mask,
895        bindings: &mut Bindings,
896        args: &[QueryEntry],
897        col: ColumnId,
898        out_var: Variable,
899    ) {
900        self.wrapper
901            .lookup_row_vectorized(self.inner, mask, bindings, args, col, out_var);
902    }
903
904    #[allow(clippy::too_many_arguments)]
905    pub(crate) fn lookup_with_default_vectorized(
906        &self,
907        mask: &mut Mask,
908        bindings: &mut Bindings,
909        args: &[QueryEntry],
910        col: ColumnId,
911        default: QueryEntry,
912        out_var: Variable,
913    ) {
914        self.wrapper.lookup_with_default_vectorized(
915            self.inner, mask, bindings, args, col, default, out_var,
916        );
917    }
918}
919
920impl Deref for WrappedTableRef<'_> {
921    type Target = dyn Table;
922
923    fn deref(&self) -> &Self::Target {
924        self.inner
925    }
926}