Skip to main content

egglog_core_relations/free_join/
mod.rs

1//! Execute queries against a database using a variant of Free Join.
2use std::{
3    mem,
4    sync::{
5        Arc,
6        atomic::{AtomicUsize, Ordering},
7    },
8};
9
10use crate::{
11    common::IndexSet,
12    hash_index::IndexCatalog,
13    numeric_id::{DenseIdMap, DenseIdMapWithReuse, NumericId, define_id},
14};
15use egglog_concurrency::{NotificationList, ResettableOnceLock};
16use smallvec::SmallVec;
17
18use crate::{
19    BaseValues, ContainerRebuildSummary, ContainerValues, PoolSet, QueryEntry, TupleIndex, Value,
20    action::{
21        Bindings, DbView,
22        mask::{Mask, MaskIter, ValueSource},
23    },
24    dependency_graph::DependencyGraph,
25    hash_index::{ColumnIndex, Index, IndexBase},
26    offsets::Subset,
27    parallel,
28    parallel_heuristics::parallelize_db_level_op,
29    pool::{Pool, Pooled, with_pool_set},
30    query::{Query, RuleSetBuilder},
31    table_spec::{
32        ColumnId, Constraint, MutationBuffer, Table, TableSpec, WrappedTable, WrappedTableRef,
33    },
34};
35
36use self::plan::Plan;
37use crate::action::{ExecutionState, ExternalContext};
38
39pub(crate) mod execute;
40pub(crate) mod frame_update;
41pub(crate) mod plan;
42
43define_id!(
44    pub AtomId,
45    u32,
46    "A component of a query consisting of a function and a list of variables or constants"
47);
48define_id!(pub Variable, u32, "a variable in a query", pretty "Var");
49
50impl Variable {
51    pub fn placeholder() -> Variable {
52        Variable::new(!0)
53    }
54}
55
56define_id!(pub TableId, u32, "a table in the database");
57
58impl TableId {
59    pub fn dummy() -> TableId {
60        TableId::new(u32::MAX)
61    }
62
63    pub fn is_dummy(&self) -> bool {
64        self.rep == u32::MAX
65    }
66}
67
68define_id!(pub(crate) ActionId, u32, "an identifier picking out the RHS of a rule");
69
70#[derive(Debug)]
71pub(crate) struct ProcessedConstraints {
72    /// The subset of the table matching the fast constraints. If there are no
73    /// fast constraints then this is the full table.
74    pub(crate) subset: Subset,
75    /// The constraints that can be evaluated quickly (O(log(n)) or O(1)).
76    pub(crate) fast: Pooled<Vec<Constraint>>,
77    /// The constraints that require an O(n) scan to evaluate.
78    pub(crate) slow: Pooled<Vec<Constraint>>,
79}
80
81impl Clone for ProcessedConstraints {
82    fn clone(&self) -> Self {
83        ProcessedConstraints {
84            subset: self.subset.clone(),
85            fast: Pooled::cloned(&self.fast),
86            slow: Pooled::cloned(&self.slow),
87        }
88    }
89}
90
91impl ProcessedConstraints {
92    /// The size of the subset of the table matching the fast constraints.
93    fn approx_size(&self) -> usize {
94        self.subset.size()
95    }
96
97    pub(crate) fn dummy() -> ProcessedConstraints {
98        ProcessedConstraints {
99            subset: Subset::empty(),
100            fast: Pooled::new(Vec::new()),
101            slow: Pooled::new(Vec::new()),
102        }
103    }
104}
105
106#[derive(Clone, Debug, PartialEq, Eq)]
107pub(crate) struct SubAtom {
108    pub(crate) atom: AtomId,
109    pub(crate) vars: SmallVec<[ColumnId; 2]>,
110}
111
112impl SubAtom {
113    pub(crate) fn new(atom: AtomId) -> SubAtom {
114        SubAtom {
115            atom,
116            vars: Default::default(),
117        }
118    }
119}
120
121#[derive(Debug, Clone)]
122pub(crate) struct VarInfo {
123    pub(crate) occurrences: Vec<SubAtom>,
124    /// Whether or not this variable shows up in the "actions" portion of a
125    /// rule.
126    pub(crate) used_in_rhs: bool,
127    pub(crate) defined_in_rhs: bool,
128    pub(crate) name: Option<Arc<str>>,
129}
130
131pub(crate) type HashIndex = Arc<ResettableOnceLock<Index<TupleIndex>>>;
132pub(crate) type HashColumnIndex = Arc<ResettableOnceLock<Index<ColumnIndex>>>;
133
134pub struct TableInfo {
135    pub(crate) name: Option<Arc<str>>,
136    pub(crate) spec: TableSpec,
137    pub(crate) table: WrappedTable,
138    pub(crate) indexes: IndexCatalog<SmallVec<[ColumnId; 4]>, HashIndex>,
139    pub(crate) column_indexes: IndexCatalog<ColumnId, HashColumnIndex>,
140}
141
142impl TableInfo {
143    pub fn table(&self) -> &WrappedTable {
144        &self.table
145    }
146
147    pub fn name(&self) -> Option<&str> {
148        self.name.as_deref()
149    }
150
151    pub fn spec(&self) -> &TableSpec {
152        &self.spec
153    }
154}
155
156impl Clone for TableInfo {
157    fn clone(&self) -> Self {
158        fn deep_clone_map<K: Clone + std::hash::Hash + Eq, TI: IndexBase + Clone>(
159            map: &IndexCatalog<K, Arc<ResettableOnceLock<Index<TI>>>>,
160            table: WrappedTableRef,
161        ) -> IndexCatalog<K, Arc<ResettableOnceLock<Index<TI>>>> {
162            map.map(|table_ref| {
163                let (k, v) = table_ref;
164                let v: Index<TI> = v
165                    .get_or_update(|index| {
166                        index.refresh(table);
167                    })
168                    .clone();
169                (k.clone(), Arc::new(ResettableOnceLock::new(v)))
170            })
171        }
172        TableInfo {
173            name: self.name.clone(),
174            spec: self.spec.clone(),
175            table: self.table.dyn_clone(),
176            indexes: deep_clone_map(&self.indexes, self.table.as_ref()),
177            column_indexes: deep_clone_map(&self.column_indexes, self.table.as_ref()),
178        }
179    }
180}
181
182define_id!(pub CounterId, u32, "A counter accessible to actions, useful for generating unique Ids.");
183define_id!(pub ExternalFunctionId, u32, "A user-defined operation that can be invoked from a query");
184
185/// External functions allow external callers to manipulate database state in
186/// near-arbitrary ways.
187///
188/// This is a useful, if low-level, interface for extending this database with
189/// functionality and state not built into the core model.
190pub trait ExternalFunction: dyn_clone::DynClone + Send + Sync {
191    /// Invoke the function with mutable access to the database. If a value is
192    /// not returned, halt the execution of the current rule.
193    fn invoke(&self, state: &mut ExecutionState, args: &[Value]) -> Option<Value>;
194}
195
196/// Automatically generate an `ExternalFunction` implementation from a function.
197pub fn make_external_func<
198    F: Fn(&mut ExecutionState, &[Value]) -> Option<Value> + Clone + Send + Sync,
199>(
200    f: F,
201) -> impl ExternalFunction {
202    #[derive(Clone)]
203    struct Wrapped<F>(F);
204    impl<F> ExternalFunction for Wrapped<F>
205    where
206        F: Fn(&mut ExecutionState, &[Value]) -> Option<Value> + Clone + Send + Sync,
207    {
208        fn invoke(&self, state: &mut ExecutionState, args: &[Value]) -> Option<Value> {
209            (self.0)(state, args)
210        }
211    }
212    Wrapped(f)
213}
214
215/// A vectorized variant of [`ExternalFunction::invoke`] to avoid repeated dynamic dispatch.
216pub(crate) fn invoke_batch(
217    this: &dyn ExternalFunction,
218    state: &mut ExecutionState,
219    mask: &mut Mask,
220    bindings: &mut Bindings,
221    args: &[QueryEntry],
222    out_var: Variable,
223) {
224    let pool: Pool<Vec<Value>> = with_pool_set(|ps| ps.get_pool());
225    let mut out = pool.get();
226    out.reserve(mask.len());
227    for_each_binding_with_mask!(mask, args, bindings, |iter| {
228        iter.fill_vec(&mut out, Value::stale, |_, args| {
229            this.invoke(state, args.as_slice())
230        });
231    });
232    bindings.insert(out_var, &out);
233}
234
235/// A variant of [`invoke_batch`] that overwrites the output variable,
236/// rather than assigning all new values.
237///
238/// *Panics* This method will panic if `out_var` doesn't already have an appropriately-sized
239/// vector bound in `bindings`.
240pub(crate) fn invoke_batch_assign(
241    this: &dyn ExternalFunction,
242    state: &mut ExecutionState,
243    mask: &mut Mask,
244    bindings: &mut Bindings,
245    args: &[QueryEntry],
246    out_var: Variable,
247) {
248    let mut out = bindings.take(out_var).expect("out_var must be bound");
249    for_each_binding_with_mask!(mask, args, bindings, |iter| {
250        iter.assign_vec_and_retain(&mut out.vals, |_, args| this.invoke(state, &args))
251    });
252    bindings.replace(out);
253}
254
255// Implements `Clone` for `Box<dyn ExternalFunction>`.
256dyn_clone::clone_trait_object!(ExternalFunction);
257
258pub(crate) type ExternalFunctions =
259    DenseIdMapWithReuse<ExternalFunctionId, Box<dyn ExternalFunction>>;
260
261#[derive(Default)]
262pub(crate) struct Counters(DenseIdMap<CounterId, AtomicUsize>);
263
264impl Clone for Counters {
265    fn clone(&self) -> Counters {
266        let mut map = DenseIdMap::new();
267        for (k, v) in self.0.iter() {
268            // NB: we may want to experiment with Ordering::Relaxed here.
269            map.insert(k, AtomicUsize::new(v.load(Ordering::SeqCst)));
270        }
271        Counters(map)
272    }
273}
274
275impl Counters {
276    pub(crate) fn read(&self, ctr: CounterId) -> usize {
277        self.0[ctr].load(Ordering::Acquire)
278    }
279    pub(crate) fn inc(&self, ctr: CounterId) -> usize {
280        // We synchronize with `read_counter` but not with other increments.
281        // NB: we may want to experiment with Ordering::Relaxed here.
282        self.0[ctr].fetch_add(1, Ordering::Release)
283    }
284}
285
286/// A collection of tables and indexes over them.
287///
288/// A database also owns the memory pools used by its tables.
289#[derive(Clone, Default)]
290pub struct Database {
291    // NB: some fields are pub(crate) to allow some internal modules to avoid
292    // borrowing the whole table.
293    pub(crate) tables: DenseIdMap<TableId, TableInfo>,
294    // TODO: having a single AtomicUsize per counter can lead to contention. We
295    // should look into prefetching counters when creating a new ExecutionState
296    // and incrementing locally. Note that the batch size shouldn't be too big
297    // because we keep an array per id in the UF.
298    pub(crate) counters: Counters,
299    pub(crate) external_functions: ExternalFunctions,
300    container_values: ContainerValues,
301    /// `notification_list` contains the list of tables that have been modified since the last call
302    /// to [`Database::merge_all`].
303    notification_list: NotificationList<TableId>,
304    // Tracks the relative dependencies between tables during merge operations.
305    deps: DependencyGraph,
306    base_values: BaseValues,
307    /// A rough estimate of the total size of the database.
308    ///
309    /// This is primarily used to determine whether or not to attempt to do some operations in
310    /// parallel.
311    total_size_estimate: usize,
312}
313
314impl Database {
315    /// Create an empty Database.
316    ///
317    /// Queries use the currently installed egglog thread pool. If no pool is
318    /// installed, queries run single-threaded.
319    pub fn new() -> Database {
320        Database::default()
321    }
322
323    /// Initialize a new rulse set to run against this database.
324    pub fn new_rule_set(&mut self) -> RuleSetBuilder<'_> {
325        RuleSetBuilder::new(self)
326    }
327
328    /// Add a new external function to the database.
329    pub fn add_external_function(
330        &mut self,
331        f: Box<dyn ExternalFunction + 'static>,
332    ) -> ExternalFunctionId {
333        self.external_functions.push(f)
334    }
335
336    /// Free an existing external function. Make sure not to use `id` afterwards.
337    pub fn free_external_function(&mut self, id: ExternalFunctionId) {
338        self.external_functions.take(id);
339    }
340
341    pub fn base_values(&self) -> &BaseValues {
342        &self.base_values
343    }
344
345    pub fn base_values_mut(&mut self) -> &mut BaseValues {
346        &mut self.base_values
347    }
348
349    pub fn container_values(&self) -> &ContainerValues {
350        &self.container_values
351    }
352
353    pub fn container_values_mut(&mut self) -> &mut ContainerValues {
354        &mut self.container_values
355    }
356
357    pub fn rebuild_containers(&mut self, table_id: TableId) -> ContainerRebuildSummary {
358        let mut containers = mem::take(&mut self.container_values);
359        let table = &self.tables[table_id].table;
360        let res =
361            self.with_execution_state(None, |state| containers.rebuild_all(table_id, table, state));
362        self.container_values = containers;
363        res
364    }
365
366    /// Apply the value-level rebuild encoded by `func_id` to all the tables in `to_rebuild`.
367    ///
368    /// The native [`Table::apply_rebuild`] method takes a `next_ts` argument for filling in new
369    /// values in a table like [`crate::SortedWritesTable`] where values in a certain column need
370    /// to be inserted in sorted order; the `next_ts` argument to this method is passed to
371    /// `apply_rebuild` for this purpose.
372    pub fn apply_rebuild(
373        &mut self,
374        func_id: TableId,
375        to_rebuild: &[TableId],
376        next_ts: Value,
377    ) -> bool {
378        let func = self.tables.take(func_id).unwrap();
379        self.run_on_tables(to_rebuild, |_, info, view| {
380            info.table.apply_rebuild(
381                func_id,
382                &func.table,
383                next_ts,
384                &mut ExecutionState::new(*view, Default::default()),
385            )
386        });
387        self.tables.insert(func_id, func);
388        self.merge_all()
389    }
390
391    pub fn refresh_rows_for_values(
392        &mut self,
393        to_refresh: &[TableId],
394        dirty_ids: &[Value],
395        next_ts: Value,
396    ) -> bool {
397        if dirty_ids.is_empty() {
398            return false;
399        }
400        // This is the follow-up for `ContainerRebuildSummary::dirty_ids()`.
401        // These ids changed semantics without changing identity, so parent
402        // rows can become newly matchable without getting an ordinary table
403        // delta.
404        //
405        // It must run after ordinary table rebuild, which already handles
406        // changed-id cases by rewriting parent rows to the new id.
407        self.run_on_tables(to_refresh, |_, info, _| {
408            info.table.refresh_rows_for_values(dirty_ids, next_ts)
409        });
410        self.merge_all()
411    }
412
413    fn run_on_tables(
414        &mut self,
415        table_ids: &[TableId],
416        run: impl for<'a> Fn(TableId, &mut TableInfo, &DbView<'a>) -> bool + Sync,
417    ) {
418        if parallelize_db_level_op(self.total_size_estimate) {
419            let mut tables = Vec::with_capacity(table_ids.len());
420            for id in table_ids {
421                tables.push((*id, self.tables.take(*id).unwrap()));
422            }
423            let view = self.read_only_view();
424            parallel::for_each_mut(&mut tables, |_, (id, info)| {
425                if run(*id, info, &view) {
426                    self.notification_list.notify(*id);
427                }
428            });
429            for (id, info) in tables {
430                self.tables.insert(id, info);
431            }
432        } else {
433            for id in table_ids {
434                let mut info = self.tables.take(*id).unwrap();
435                let changed = {
436                    let view = self.read_only_view();
437                    run(*id, &mut info, &view)
438                };
439                if changed {
440                    self.notification_list.notify(*id);
441                }
442                self.tables.insert(*id, info);
443            }
444        }
445    }
446
447    /// Run `f` with access to an `ExecutionState` mapped to this database.
448    ///
449    /// `context` is visible to any external function the closure reaches; pass
450    /// `None` if there is nothing to share.
451    pub fn with_execution_state<R>(
452        &self,
453        context: ExternalContext<'_>,
454        f: impl FnOnce(&mut ExecutionState) -> R,
455    ) -> R {
456        let mut state = ExecutionState::new(self.read_only_view_with(context), Default::default());
457        f(&mut state)
458    }
459
460    /// Like [`Database::with_execution_state`], including its `context`, but
461    /// also reports whether `f` staged any mutation through the execution
462    /// state. Callers can use the flag to skip a subsequent `merge_all` when
463    /// the closure was read-only.
464    pub fn with_execution_state_tracked<R>(
465        &self,
466        context: ExternalContext<'_>,
467        f: impl FnOnce(&mut ExecutionState) -> R,
468    ) -> (R, bool) {
469        let mut state = ExecutionState::new(self.read_only_view_with(context), Default::default());
470        let result = f(&mut state);
471        (result, state.changed)
472    }
473
474    pub(crate) fn read_only_view(&self) -> DbView<'_> {
475        self.read_only_view_with(None)
476    }
477
478    /// Like [`Database::read_only_view`], but with an [`ExternalContext`] that
479    /// every [`ExecutionState`] built from the view will expose.
480    pub(crate) fn read_only_view_with<'a>(&'a self, context: ExternalContext<'a>) -> DbView<'a> {
481        DbView {
482            external_context: context,
483            table_info: &self.tables,
484            counters: &self.counters,
485            external_funcs: &self.external_functions,
486            bases: &self.base_values,
487            containers: &self.container_values,
488            notification_list: &self.notification_list,
489        }
490    }
491
492    /// Estimate the size of the table. If a constraint is provided, return an
493    /// estimate of the size of the subset of the table matching the constraint.
494    pub fn estimate_size(&self, table: TableId, c: Option<Constraint>) -> usize {
495        let table_info = self
496            .tables
497            .get(table)
498            .expect("table must be declared in the current database");
499        let table = &table_info.table;
500        if let Some(c) = c {
501            if let Some(sub) = table.fast_subset(&c) {
502                // In the case where a the constraint can be computed quickly,
503                // we do not filter for staleness, which may over-approximate.
504                sub.size()
505            } else {
506                table.refine_one(table.refine_live(table.all()), &c).size()
507            }
508        } else {
509            table.len()
510        }
511    }
512
513    /// Create a new counter for this database.
514    ///
515    /// These counters can be used to generate unique ids as part of an action.
516    pub fn add_counter(&mut self) -> CounterId {
517        self.counters.0.push(AtomicUsize::new(0))
518    }
519
520    /// Increment the given counter and return its previous value.
521    pub fn inc_counter(&self, counter: CounterId) -> usize {
522        self.counters.inc(counter)
523    }
524
525    /// Get the current value of the given counter.
526    pub fn read_counter(&self, counter: CounterId) -> usize {
527        self.counters.read(counter)
528    }
529
530    /// A helper for merging all pending updates. Used to write to the database after updates have
531    /// been staged. Returns true if any tuples were added.
532    ///
533    /// Exposed for testing purposes.
534    ///
535    /// Useful for out-of-band insertions into the database.
536    pub fn merge_all(&mut self) -> bool {
537        let mut ever_changed = false;
538        let do_parallel = parallelize_db_level_op(self.total_size_estimate);
539        let mut to_merge = IndexSet::default();
540        // Tables modified during this `merge_all` call. Only these need their cached indexes reset
541        // at the end so future reads refresh them.
542        let mut touched: IndexSet<TableId> = IndexSet::default();
543        loop {
544            to_merge.clear();
545            let to_merge_vec = self.notification_list.reset();
546            touched.extend(to_merge_vec.iter().copied());
547            if to_merge_vec.len() < 4 {
548                ever_changed |= self.merge_simple(to_merge_vec, &mut touched);
549                break;
550            }
551            for table in to_merge_vec {
552                to_merge.insert(table);
553            }
554
555            let mut changed = false;
556            let mut tables_merging = DenseIdMap::<
557                TableId,
558                (
559                    // The info needed to merge this table.
560                    Option<TableInfo>,
561                    // Pre-allocated write buffers, according to the tables declared write
562                    // dependencies.
563                    DenseIdMap<TableId, Box<dyn MutationBuffer>>,
564                ),
565            >::with_capacity(self.tables.n_ids());
566            for stratum in self.deps.strata() {
567                // Initialize the write dependencies first.
568                for table in stratum.intersection(&to_merge).copied() {
569                    let mut bufs = DenseIdMap::default();
570                    for dep in self.deps.write_deps(table) {
571                        if let Some(info) = self.tables.get(dep) {
572                            bufs.insert(dep, info.table.new_buffer());
573                        }
574                    }
575                    tables_merging.insert(table, (None, bufs));
576                }
577                // Then initialize read dependencies (this two-phase structure is why we have an
578                // Option in the tables_merging map).
579                for table in stratum.intersection(&to_merge).copied() {
580                    let val = self.tables.unwrap_val(table);
581                    // Maintain `total_size_estimate` incrementally (subtract now, add
582                    // the post-merge length on drain below) so the reset loop no
583                    // longer re-sums every table.
584                    self.total_size_estimate =
585                        self.total_size_estimate.wrapping_sub(val.table.len());
586                    tables_merging[table].0 = Some(val);
587                }
588                let db = self.read_only_view();
589                changed |= if do_parallel {
590                    parallel::map_dense_id_map_mut(&mut tables_merging, |_, (info, buffers)| {
591                        let mut es = ExecutionState::new(db, mem::take(buffers));
592                        info.as_mut().unwrap().table.merge(&mut es).added || es.changed
593                    })
594                    .into_iter()
595                    .any(|changed| changed)
596                } else {
597                    tables_merging
598                        .iter_mut()
599                        .map(|(_, (info, buffers))| {
600                            let mut es = ExecutionState::new(db, mem::take(buffers));
601                            info.as_mut().unwrap().table.merge(&mut es).added || es.changed
602                        })
603                        .max()
604                        .unwrap_or(false)
605                };
606                for (id, (table, _)) in tables_merging.drain() {
607                    let val = table.unwrap();
608                    self.total_size_estimate =
609                        self.total_size_estimate.wrapping_add(val.table.len());
610                    self.tables.insert(id, val);
611                }
612            }
613            ever_changed |= changed;
614        }
615        // Reset the cached indexes of only the tables modified during this call so
616        // they refresh on next access; unmodified tables keep their still-valid
617        // cached indexes. `touched` must contain *every* table whose version bumped
618        // this call: `ResettableOnceLock::get_or_update` runs the index `refresh`
619        // only after a `reset()`, so a modified-but-unreset table would keep serving
620        // a stale cached index. It does — every merged table comes from
621        // `notification_list.reset()`, which is exactly what `touched` accumulates.
622        // `total_size_estimate` was maintained incrementally at each merge (above and
623        // in `merge_simple`), so we no longer re-sum every table here.
624        for table in touched.iter().copied() {
625            if let Some(info) = self.tables.get_mut(table) {
626                info.column_indexes.update(|_, ti| {
627                    Arc::get_mut(ti).unwrap().reset();
628                });
629                info.indexes.update(|_, ti| {
630                    Arc::get_mut(ti).unwrap().reset();
631                });
632            }
633        }
634        ever_changed
635    }
636
637    /// A "fast path" merge method that is not optimized for parallelism and does not respect read
638    /// and write dependencies. This ends up being faster than the full "strata-aware" option in
639    /// the body of `merge_all`.
640    fn merge_simple(
641        &mut self,
642        mut to_merge: SmallVec<[TableId; 4]>,
643        touched: &mut IndexSet<TableId>,
644    ) -> bool {
645        let mut changed = false;
646        while !to_merge.is_empty() {
647            for table_id in to_merge.iter().copied() {
648                let mut info = self.tables.unwrap_val(table_id);
649                // Maintain `total_size_estimate` incrementally (see `merge_all`'s
650                // reset loop, which no longer re-sums every table).
651                self.total_size_estimate = self.total_size_estimate.wrapping_sub(info.table.len());
652                let mut es = ExecutionState::new(self.read_only_view(), Default::default());
653                changed |= info.table.merge(&mut es).added || es.changed;
654                self.total_size_estimate = self.total_size_estimate.wrapping_add(info.table.len());
655                self.tables.insert(table_id, info);
656            }
657            to_merge = self.notification_list.reset();
658            touched.extend(to_merge.iter().copied());
659        }
660        changed
661    }
662
663    /// A low-level helper for merging pending updates to a particular function.
664    ///
665    /// Callers should prefer `merge_all`, as the process of merging the data
666    /// for a particular table may cause other updates to be buffered
667    /// elesewhere. The `merge_all` method runs merges to a fixed point to avoid
668    /// surprises here.
669    pub fn merge_table(&mut self, table: TableId) -> bool {
670        let mut info = self.tables.unwrap_val(table);
671        self.total_size_estimate = self.total_size_estimate.wrapping_sub(info.table.len());
672        let table_changed = info.table.merge(&mut ExecutionState::new(
673            self.read_only_view(),
674            Default::default(),
675        ));
676        self.total_size_estimate = self.total_size_estimate.wrapping_add(info.table.len());
677        self.tables.insert(table, info);
678        table_changed.added
679    }
680
681    /// Get id of the next table to be added to the database.
682    ///
683    /// This can be useful for "knot tying", when tables need to reference their
684    /// own id.
685    pub fn next_table_id(&self) -> TableId {
686        self.tables.next_id()
687    }
688
689    /// Add a table with the given schema to the database.
690    ///
691    /// The table must have a compatible spec with `types` (e.g. same number of
692    /// columns).
693    pub fn add_table<T: Table + Sized + 'static>(
694        &mut self,
695        table: T,
696        read_deps: impl IntoIterator<Item = TableId>,
697        write_deps: impl IntoIterator<Item = TableId>,
698    ) -> TableId {
699        self.add_table_impl(table, None, read_deps, write_deps)
700    }
701
702    pub fn add_table_named<T: Table + Sized + 'static>(
703        &mut self,
704        table: T,
705        name: Arc<str>,
706        read_deps: impl IntoIterator<Item = TableId>,
707        write_deps: impl IntoIterator<Item = TableId>,
708    ) -> TableId {
709        self.add_table_impl(table, Some(name), read_deps, write_deps)
710    }
711
712    fn add_table_impl<T: Table + Sized + 'static>(
713        &mut self,
714        table: T,
715        name: Option<Arc<str>>,
716        read_deps: impl IntoIterator<Item = TableId>,
717        write_deps: impl IntoIterator<Item = TableId>,
718    ) -> TableId {
719        let spec = table.spec();
720        let table = WrappedTable::new(table);
721        let res = self.tables.push(TableInfo {
722            name,
723            spec,
724            table,
725            indexes: IndexCatalog::new(),
726            column_indexes: IndexCatalog::new(),
727        });
728        self.deps.add_table(res, read_deps, write_deps);
729        res
730    }
731
732    /// Get direct mutable access to the table.
733    ///
734    /// This method is useful for out-of-band access to databse state.
735    ///
736    /// **NOTE:** It is legal to call [`Table::new_buffer`] on the returned table handle, and use
737    /// that to stage updates to the given table via [`MutationBuffer::stage_insert`] or
738    /// [`MutationBuffer::stage_remove`], however this is *likely to be a source of bugs*.
739    ///
740    /// Updates staged in this way will not cause `table` to be marked as having pending changes in
741    /// the next call to [`Database::merge_all`]. Instead, such users should use
742    /// [`Database::new_buffer`], which plumbs this signal through correctly, or better yet,
743    /// perform all updates through an [`ExecutionState`] or a [`crate::RuleBuilder`]. If these
744    /// options do not work, then calling [`Database::merge_table`] directly will force a merge
745    /// call on the table.
746    pub fn get_table(&self, table: TableId) -> &WrappedTable {
747        &self
748            .tables
749            .get(table)
750            .expect("must access a table that has been declared in this database")
751            .table
752    }
753
754    /// Get a handle on the given table along with metadata about it.
755    ///
756    ///
757    /// **NOTE:** See the note on [`Database::get_table`] around manually staging updates.
758    pub fn get_table_info(&self, table: TableId) -> &TableInfo {
759        self.tables
760            .get(table)
761            .expect("must access a table that has been declared in this database")
762    }
763
764    /// Create a new mutation buffer for the table with id `id`.
765    ///
766    /// This will marked the given table as potentially changed for the next round of merging.
767    /// Unlike calling [`Table::new_buffer`] on a table returned from a getter, this method also
768    /// triggers change notification metadata that is read by [`Database::merge_all`].
769    pub fn new_buffer(&self, id: TableId) -> Box<dyn MutationBuffer> {
770        self.notification_list.notify(id);
771        self.get_table(id).new_buffer()
772    }
773
774    pub(crate) fn process_constraints(
775        &self,
776        table: TableId,
777        cs: &[Constraint],
778    ) -> ProcessedConstraints {
779        let table_info = &self.tables[table];
780        let (mut subset, mut fast, mut slow) = table_info.table.split_fast_slow(cs);
781        slow.retain(|c| {
782            let (col, val) = match c {
783                Constraint::EqConst { col, val } => (*col, *val),
784                Constraint::Eq { .. }
785                | Constraint::LtConst { .. }
786                | Constraint::GtConst { .. }
787                | Constraint::LeConst { .. }
788                | Constraint::GeConst { .. } => return true,
789            };
790            // We are looking up by a constant: this is something we can build
791            // an index for as long as the column is cacheable.
792            if *table_info
793                .spec
794                .uncacheable_columns
795                .get(col)
796                .unwrap_or(&false)
797            {
798                return true;
799            }
800            // We have or will build an index: upgrade this constraint to
801            // 'fast'.
802            fast.push(c.clone());
803            let index = get_column_index_from_tableinfo(table_info, col);
804            match index.get().unwrap().get_subset(&val) {
805                Some(s) => {
806                    with_pool_set(|ps| subset.intersect(s, &ps.get_pool()));
807                }
808                None => {
809                    // There are no rows matching this key! We can constrain this to nothing.
810                    subset = Subset::empty();
811                }
812            }
813            // Remove this constraint from the slow list.
814            false
815        });
816        ProcessedConstraints { subset, fast, slow }
817    }
818
819    /// Get direct mutable access to the table.
820    ///
821    /// This method is useful for out-of-band access to databse state.
822    ///
823    /// **NOTE:** See the warning around staging updates to handles returned through this method in
824    /// the documentation for [`Database::get_table`].
825    pub fn get_table_mut(&mut self, id: TableId) -> &mut dyn Table {
826        &mut *self
827            .tables
828            .get_mut(id)
829            .expect("must access a table that has been declared in this database")
830            .table
831    }
832
833    /// Remove every row from the given table.
834    ///
835    /// This is intended as a faster alternative to staging a per-row
836    /// `stage_remove` for every key in the table. The underlying [`Table::clear`]
837    /// implementation drops the row storage in bulk and bumps the table's major
838    /// generation, so any cached indexes/subsets observed by future readers will
839    /// be lazily rebuilt against the now-empty table. Any pending staged
840    /// inserts or removes for this table are dropped (they pre-dated the clear,
841    /// so they no longer make sense once the table is empty).
842    ///
843    /// This method also resets the cached column- and key-indexes for the
844    /// table so subsequent merges can take the `Arc::get_mut`-based reset path,
845    /// matching the invariant maintained by [`Database::merge_all`].
846    ///
847    /// This does **not** flush pending changes for *other* tables; it is the
848    /// caller's responsibility to call [`Database::merge_all`] beforehand if
849    /// they need staged updates from a previous step to land before the clear.
850    pub fn clear_table(&mut self, table: TableId) {
851        let info = self
852            .tables
853            .get_mut(table)
854            .expect("must access a table that has been declared in this database");
855        let prev_len = info.table.len();
856        info.table.clear();
857        // The version bump from `clear` is enough on its own to make the
858        // indexes self-refresh on next access (see `Index::refresh`). We still
859        // reset them eagerly here so that the next `merge_all` sees the same
860        // "indexes are resettable" state it expects after a successful merge.
861        info.column_indexes.update(|_, ti| {
862            if let Some(arc) = Arc::get_mut(ti) {
863                arc.reset();
864            }
865        });
866        info.indexes.update(|_, ti| {
867            if let Some(arc) = Arc::get_mut(ti) {
868                arc.reset();
869            }
870        });
871        self.total_size_estimate = self.total_size_estimate.wrapping_sub(prev_len);
872    }
873
874    pub(crate) fn plan_query(&mut self, query: Query) -> Plan {
875        plan::plan_query(query, ColumnCardEst::new(self))
876    }
877}
878
879impl Drop for Database {
880    fn drop(&mut self) {
881        // Clean up this thread's ambient memory pool.
882        with_pool_set(PoolSet::clear);
883    }
884}
885
886/// The core logic behind getting and updating a hash index.
887///
888/// This is in a separate function to allow us to reuse it while already
889/// borrowing a `TableInfo`.
890fn get_index_from_tableinfo(table_info: &TableInfo, cols: &[ColumnId]) -> HashIndex {
891    let index: Arc<_> = table_info.indexes.get_or_insert(cols.into(), || {
892        Arc::new(ResettableOnceLock::new(Index::new(
893            cols.to_vec(),
894            TupleIndex::new(cols.len()),
895        )))
896    });
897    index.get_or_update(|index| {
898        index.refresh(table_info.table.as_ref());
899    });
900    debug_assert!(
901        !index
902            .get()
903            .unwrap()
904            .needs_refresh(table_info.table.as_ref())
905    );
906    index
907}
908
909/// The core logic behind getting and updating a column index.
910///
911/// This is the single-column analog to [`get_index_from_tableinfo`].
912pub(crate) fn get_column_index_from_tableinfo(
913    table_info: &TableInfo,
914    col: ColumnId,
915) -> HashColumnIndex {
916    let index: Arc<_> = table_info.column_indexes.get_or_insert(col, || {
917        Arc::new(ResettableOnceLock::new(Index::new(
918            vec![col],
919            ColumnIndex::new(),
920        )))
921    });
922    index.get_or_update(|index| {
923        index.refresh(table_info.table.as_ref());
924    });
925    debug_assert!(
926        !index
927            .get()
928            .unwrap()
929            .needs_refresh(table_info.table.as_ref())
930    );
931    index
932}
933
934#[derive(Clone)]
935pub struct ColumnCardEst<'a> {
936    db: &'a Database,
937}
938
939impl ColumnCardEst<'_> {
940    pub fn new(db: &Database) -> ColumnCardEst<'_> {
941        ColumnCardEst { db }
942    }
943
944    pub fn col_uniqueness(&self, table: TableId, col: ColumnId) -> ColUniqueness {
945        let col_idx = get_column_index_from_tableinfo(&self.db.tables[table], col);
946        let table = &self.db.tables[table].table;
947        ColUniqueness {
948            col_size: col_idx.get().unwrap().len(),
949            table_size: table.len(),
950        }
951    }
952}
953
954impl std::fmt::Debug for ColumnCardEst<'_> {
955    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
956        f.debug_struct("ColumnCardEst").finish_non_exhaustive()
957    }
958}
959
960/// A coarse cardinality estimate for a column of a table, used by the query
961/// planner to decide which variable to eliminate next during tree
962/// decomposition.
963///
964/// `table_size` is the number of rows in the (sub)table and `col_size` is the
965/// number of distinct values in the column. Their ratio
966/// (`table_size / col_size`) approximates the average number of rows that share
967/// a given value of the column: a smaller ratio means the column is closer to
968/// being unique and therefore cheaper to join on. [`ColUniqueness`] is ordered
969/// by this ratio (see the [`Ord`] impl), so the planner prefers variables with
970/// the most selective (most unique) columns.
971#[derive(Copy, Clone, Debug)]
972pub struct ColUniqueness {
973    table_size: usize,
974    col_size: usize,
975}
976
977impl Default for ColUniqueness {
978    fn default() -> ColUniqueness {
979        ColUniqueness {
980            table_size: 1,
981            col_size: 1,
982        }
983    }
984}
985
986impl ColUniqueness {
987    #[allow(dead_code)] // not yet wired up into the planner
988    fn scale(&self, subset_size: usize) -> ColUniqueness {
989        if self.table_size == 0 || subset_size == 0 {
990            return ColUniqueness {
991                table_size: 0,
992                col_size: 0,
993            };
994        }
995        ColUniqueness {
996            table_size: subset_size,
997            col_size: self.col_size.saturating_mul(subset_size) / self.table_size,
998        }
999    }
1000    fn join(&self, other: &ColUniqueness) -> ColUniqueness {
1001        ColUniqueness {
1002            table_size: self.table_size.saturating_mul(other.table_size),
1003            col_size: self.col_size.max(other.col_size),
1004        }
1005    }
1006
1007    #[allow(dead_code)] // not yet wired up into the planner
1008    fn col_size(&self) -> usize {
1009        self.col_size
1010    }
1011}
1012
1013impl PartialEq for ColUniqueness {
1014    fn eq(&self, other: &Self) -> bool {
1015        self.cmp(other) == std::cmp::Ordering::Equal
1016    }
1017}
1018
1019impl Eq for ColUniqueness {}
1020
1021impl PartialOrd for ColUniqueness {
1022    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1023        Some(self.cmp(other))
1024    }
1025}
1026
1027impl Ord for ColUniqueness {
1028    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1029        (self.table_size.saturating_mul(other.col_size))
1030            .cmp(&(other.table_size.saturating_mul(self.col_size)))
1031    }
1032}