1use std::{
4 cmp, iter, mem,
5 ops::Range,
6 sync::{
7 Arc, OnceLock, RwLock,
8 atomic::{AtomicUsize, Ordering},
9 },
10};
11
12use crate::{
13 common::{HashMap, HashSet, IndexMap},
14 free_join::plan::{JoinStages, MatId, MatScanMode, MatSpec},
15 numeric_id::{DenseIdMap, IdVec, NumericId},
16 query::Atom,
17 row_buffer::{RowBuffer, SmallValueVec},
18};
19use crossbeam::utils::CachePadded;
20use dashmap::mapref::entry::Entry;
21use dashmap::mapref::one::RefMut;
22use egglog_concurrency::Scope;
23use egglog_reports::{ReportLevel, RuleReport, RuleSetReport};
24use smallvec::SmallVec;
25use web_time::Instant;
26
27use crate::{
28 Constraint, OffsetRange, Pool, SubsetRef,
29 action::{Bindings, ExecutionState},
30 common::{DashMap, Value},
31 free_join::{
32 frame_update::{FrameUpdates, UpdateInstr},
33 get_index_from_tableinfo,
34 },
35 hash_index::{IndexBase, TupleIndex},
36 offsets::{Offsets, RowId, SortedOffsetSlice, SortedOffsetVector, Subset},
37 parallel_heuristics::{action_batch_size, free_join_fork_depth, parallelize_db_level_op},
38 pool::Pooled,
39 query::RuleSet,
40 row_buffer::TaggedRowBuffer,
41 table_spec::{ColumnId, Offset, WrappedTableRef},
42};
43
44use super::{
45 ActionId, AtomId, Database, HashColumnIndex, HashIndex, TableId, TableInfo, Variable,
46 get_column_index_from_tableinfo,
47 plan::{JoinHeader, JoinStage, Plan},
48 with_pool_set,
49};
50
51const SMALL_RESIDUAL: usize = 8;
52
53struct SparseColumnIndex {
54 n_keys: usize,
55 n_subsets: usize,
56 keys: [Value; SMALL_RESIDUAL],
57 offsets: [usize; SMALL_RESIDUAL],
58 subset_ids: [RowId; SMALL_RESIDUAL],
59}
60
61#[inline]
69unsafe fn sparse_subset_ref(ids: &[RowId], range: Range<usize>) -> SubsetRef<'_> {
70 if range.len() == 1 {
71 let row = ids[range.start];
72 SubsetRef::Dense(OffsetRange::new(row, row.inc()))
73 } else {
74 SubsetRef::Sparse(unsafe { SortedOffsetSlice::new_unchecked(&ids[range]) })
76 }
77}
78
79impl SparseColumnIndex {
80 fn keys(&self) -> &[Value] {
81 &self.keys[..self.n_keys]
82 }
83
84 fn get_offset_for(&self, i: usize) -> Range<usize> {
85 let lo = self.offsets[i];
86 let hi = if i + 1 < self.n_keys {
87 self.offsets[i + 1]
88 } else {
89 self.n_subsets
90 };
91 lo..hi
92 }
93
94 fn new(table: WrappedTableRef<'_>, subset: SubsetRef<'_>, col: ColumnId) -> Self {
95 let mut rows = [(Value::new_const(0), RowId::new_const(0)); SMALL_RESIDUAL];
96 let mut pos = 0;
97 table.for_each_col(subset, col, &mut |row_id, val| {
98 rows[pos] = (val, row_id);
99 pos += 1;
100 });
101 let n_subsets = pos;
102
103 rows[..pos].sort_unstable();
104
105 let mut n_keys = 0;
106 let mut keys = [Value::new_const(0); SMALL_RESIDUAL];
107 let mut offsets = [0; SMALL_RESIDUAL];
108 let mut subset_ids = [RowId::new_const(0); SMALL_RESIDUAL];
109 offsets[0] = 0;
110
111 for (i, &(key, row_id)) in rows[..n_subsets].iter().enumerate() {
112 let is_new_key = n_keys == 0 || keys[n_keys - 1] != key;
113 if is_new_key {
114 offsets[n_keys] = i;
115 keys[n_keys] = key;
116 n_keys += 1;
117 }
118 subset_ids[i] = row_id;
119 }
120
121 SparseColumnIndex {
122 n_keys,
123 n_subsets,
124 keys,
125 offsets,
126 subset_ids,
127 }
128 }
129
130 fn get_subset(&self, key: Value) -> Option<SubsetRef<'_>> {
131 if self.n_keys == 0 {
132 return None;
133 }
134 let found = self.keys().binary_search(&key).ok()?;
135 let range = self.get_offset_for(found);
136 Some(unsafe { sparse_subset_ref(&self.subset_ids, range) })
140 }
141
142 fn for_each(&self, mut f: impl FnMut(&[Value], SubsetRef)) {
143 if self.n_keys == 0 {
144 return;
145 }
146 for i in 0..self.n_keys {
147 let range = self.get_offset_for(i);
148 let subset = unsafe { sparse_subset_ref(&self.subset_ids, range) };
150 f(&self.keys[i..i + 1], subset);
151 }
152 }
153
154 fn len(&self) -> usize {
155 self.n_keys
156 }
157}
158
159#[inline]
166unsafe fn dense_or_sparse_ref(ids: &[RowId], range: Range<usize>) -> SubsetRef<'_> {
167 let slice = &ids[range];
168 let first = slice[0];
169 let last = slice[slice.len() - 1];
170 if last.index() - first.index() == slice.len() - 1 {
171 SubsetRef::Dense(OffsetRange::new(first, last.inc()))
172 } else {
173 SubsetRef::Sparse(unsafe { SortedOffsetSlice::new_unchecked(slice) })
175 }
176}
177
178pub(crate) struct SortedColumnIndex {
187 keys: Vec<(Value, u32)>,
191 row_ids: Vec<RowId>,
193}
194
195impl SortedColumnIndex {
196 fn build_for_subset(table: WrappedTableRef, subset: SubsetRef, col: ColumnId) -> Self {
197 let mut pairs: Vec<(Value, RowId)> = Vec::new();
198 table.collect_col_pairs(subset, col, &mut pairs);
201 let mut scratch = vec![(Value::new_const(0), RowId::new_const(0)); pairs.len()];
202 crate::hash_index::radix_sort_slice_by_value(&mut pairs, &mut scratch);
203 drop(scratch);
204
205 let mut keys: Vec<(Value, u32)> = Vec::new();
206 let mut row_ids: Vec<RowId> = Vec::with_capacity(pairs.len());
207 for (val, row) in pairs {
208 if keys.last().map(|&(v, _)| v) != Some(val) {
209 keys.push((val, row_ids.len() as u32));
210 }
211 row_ids.push(row);
212 }
213 keys.push((Value::new_const(0), row_ids.len() as u32));
214 SortedColumnIndex { keys, row_ids }
215 }
216
217 fn get_subset(&self, key: Value) -> Option<SubsetRef<'_>> {
218 let n = self.len();
221 let i = self.keys[..n]
222 .binary_search_by_key(&key, |&(v, _)| v)
223 .ok()?;
224 let lo = self.keys[i].1 as usize;
225 let hi = self.keys[i + 1].1 as usize;
226 Some(unsafe { dense_or_sparse_ref(&self.row_ids, lo..hi) })
228 }
229
230 fn for_each(&self, mut f: impl FnMut(Value, SubsetRef)) {
231 let n = self.len();
232 for i in 0..n {
233 let (val, lo) = self.keys[i];
234 let hi = self.keys[i + 1].1 as usize;
235 let subset = unsafe { dense_or_sparse_ref(&self.row_ids, lo as usize..hi) };
237 f(val, subset);
238 }
239 }
240
241 fn len(&self) -> usize {
242 self.keys.len().saturating_sub(1)
244 }
245}
246
247enum DynamicIndex {
248 Cached {
249 intersect_outer: Option<OffsetRange>,
252 table: HashIndex,
253 },
254 CachedColumn {
255 intersect_outer: Option<OffsetRange>,
258 table: HashColumnIndex,
259 },
260 Dynamic(TupleIndex),
261 DynamicColumn(Arc<SortedColumnIndex>),
262 SparseColumn(SparseColumnIndex),
263}
264
265struct PotentiallyStale<T> {
270 inner: T,
271 can_be_stale: bool,
272}
273
274impl<T> PotentiallyStale<T> {
275 fn maybe_stale(inner: T) -> Self {
276 Self {
277 inner,
278 can_be_stale: true,
279 }
280 }
281
282 fn not_stale(inner: T) -> Self {
283 Self {
284 inner,
285 can_be_stale: false,
286 }
287 }
288}
289
290impl PotentiallyStale<SubsetRef<'_>> {
291 fn size(&self) -> usize {
292 self.inner.size()
293 }
294}
295
296#[inline]
303fn intersect_with_dense_ref<'a>(v: SubsetRef<'a>, range: OffsetRange) -> Option<SubsetRef<'a>> {
304 match v {
305 SubsetRef::Dense(r) => {
306 let resl = cmp::max(r.start, range.start);
307 let resr = cmp::min(r.end, range.end);
308 if resl >= resr {
309 None
310 } else {
311 Some(SubsetRef::Dense(OffsetRange::new(resl, resr)))
312 }
313 }
314 SubsetRef::Sparse(s) => {
315 let l = s.binary_search_by_id(range.start);
316 let r = s.binary_search_by_id(range.end);
317 if l >= r {
318 None
319 } else {
320 Some(SubsetRef::Sparse(s.subslice(l, r)))
321 }
322 }
323 }
324}
325
326struct Prober {
327 node: Arc<TrieNode>,
328 ix: DynamicIndex,
329}
330
331impl Prober {
332 fn get_subset<'a>(&'a self, key: &'a [Value]) -> Option<PotentiallyStale<SubsetRef<'a>>> {
333 match &self.ix {
334 DynamicIndex::Cached {
335 intersect_outer,
336 table,
337 } => {
338 let subset_ref = table.get().unwrap().get_subset(key)?;
339 let subset = if let Some(range) = intersect_outer {
340 intersect_with_dense_ref(subset_ref, *range)?
341 } else {
342 subset_ref
343 };
344 Some(PotentiallyStale::maybe_stale(subset))
345 }
346 DynamicIndex::CachedColumn {
347 intersect_outer,
348 table,
349 } => {
350 debug_assert_eq!(key.len(), 1);
351 let subset_ref = table.get().unwrap().get_subset(&key[0])?;
352 let subset = if let Some(range) = intersect_outer {
353 intersect_with_dense_ref(subset_ref, *range)?
354 } else {
355 subset_ref
356 };
357 Some(PotentiallyStale::maybe_stale(subset))
358 }
359 DynamicIndex::Dynamic(tab) => tab.get_subset(key).map(PotentiallyStale::not_stale),
360 DynamicIndex::DynamicColumn(tab) => {
361 tab.get_subset(key[0]).map(PotentiallyStale::not_stale)
362 }
363 DynamicIndex::SparseColumn(tab) => {
364 debug_assert_eq!(key.len(), 1);
365 tab.get_subset(key[0]).map(PotentiallyStale::not_stale)
366 }
367 }
368 }
369 fn for_each(&self, mut f: impl FnMut(&[Value], PotentiallyStale<SubsetRef>)) {
370 match &self.ix {
371 DynamicIndex::Cached {
372 intersect_outer: Some(range),
373 table,
374 } => {
375 let range = *range;
376 table.get().unwrap().for_each(|k, v| {
377 if let Some(res) = intersect_with_dense_ref(v, range) {
378 f(k, PotentiallyStale::maybe_stale(res))
379 }
380 });
381 }
382 DynamicIndex::Cached {
383 intersect_outer: None,
384 table,
385 } => table
386 .get()
387 .unwrap()
388 .for_each(|k, v| f(k, PotentiallyStale::maybe_stale(v))),
389 DynamicIndex::CachedColumn {
390 intersect_outer: Some(range),
391 table,
392 } => {
393 let range = *range;
394 table.get().unwrap().for_each(|k, v| {
395 if let Some(res) = intersect_with_dense_ref(v, range) {
396 f(&[*k], PotentiallyStale::maybe_stale(res))
397 }
398 });
399 }
400 DynamicIndex::CachedColumn {
401 intersect_outer: None,
402 table,
403 } => {
404 table
405 .get()
406 .unwrap()
407 .for_each(|k, v| f(&[*k], PotentiallyStale::maybe_stale(v)));
408 }
409 DynamicIndex::Dynamic(tab) => {
410 tab.for_each(|k, v| f(k, PotentiallyStale::not_stale(v)));
411 }
412 DynamicIndex::DynamicColumn(tab) => tab.for_each(|k, v| {
413 f(&[k], PotentiallyStale::not_stale(v));
414 }),
415 DynamicIndex::SparseColumn(tab) => {
416 tab.for_each(|k, v| f(k, PotentiallyStale::not_stale(v)));
417 }
418 }
419 }
420
421 fn len(&self) -> usize {
422 match &self.ix {
423 DynamicIndex::Cached { table, .. } => table.get().unwrap().len(),
424 DynamicIndex::CachedColumn { table, .. } => table.get().unwrap().len(),
425 DynamicIndex::Dynamic(tab) => tab.len(),
426 DynamicIndex::DynamicColumn(tab) => tab.len(),
427 DynamicIndex::SparseColumn(tab) => tab.len(),
428 }
429 }
430}
431
432impl Database {
433 pub fn run_rule_set(&mut self, rule_set: &RuleSet, report_level: ReportLevel) -> RuleSetReport {
434 if rule_set.plans.is_empty() {
435 return RuleSetReport::default();
436 }
437 let match_counter = Arc::new(MatchCounter::new(rule_set.actions.n_ids()));
438 let trie_cache: Option<Arc<TrieCache>> = {
456 let shared =
457 TrieCache::compute_shared(rule_set.plans.values().map(|(plan, _, _)| plan));
458 (!shared.is_empty()).then(|| Arc::new(TrieCache::with_shared(shared)))
459 };
460
461 let search_and_apply_timer = Instant::now();
462 let mut rule_reports: HashMap<Arc<str>, Vec<RuleReport>>;
464 let exec_state = ExecutionState::new(self.read_only_view(), Default::default());
465 if parallelize_db_level_op(self.total_size_estimate) {
466 let dash_rule_reports: Arc<DashMap<Arc<str>, Vec<RuleReport>>> =
467 Arc::new(DashMap::default());
468 let db: &Database = self;
469 egglog_concurrency::scope(|scope| {
470 for (plan, desc, symbol_map) in rule_set.plans.values() {
471 let report_plan = match report_level {
473 ReportLevel::TimeOnly => None,
474 ReportLevel::WithPlan | ReportLevel::StageInfo => {
475 Some(plan.to_report(symbol_map))
476 }
477 };
478
479 let dash_rule_reports = dash_rule_reports.clone();
480 let desc = desc.clone();
481 let exec_state = exec_state.clone();
482 let match_counter = match_counter.clone();
483 let trie_cache = trie_cache.clone();
484 scope.spawn(move |rule_scope| {
485 let join_state = JoinState::new(db, exec_state.clone(), trie_cache);
486 let mut binding_info = BindingInfo::default();
487 let mut action_buf =
488 ScopedActionBuffer::new(rule_scope, rule_set, match_counter.clone());
489 let search_and_apply_timer = Instant::now();
490
491 'eval: {
492 for (id, info) in plan.atoms().iter() {
493 let headers: SmallVec<[&JoinHeader; 2]> =
494 plan.header().iter().filter(|h| h.atom == id).collect();
495 match join_state.root_node(info.table, &headers) {
496 Some(node) => binding_info.insert_node(id, node),
497 None => break 'eval,
498 }
499 }
500
501 match plan {
502 Plan::SinglePlan(plan) => {
503 join_state.run_join_stages(
504 &plan.stages,
505 &plan.atoms,
506 plan.actions,
507 &mut binding_info,
508 &mut action_buf,
509 );
510 }
511 Plan::DecomposedPlan(plan) => {
512 let mut materializations: DenseIdMap<
513 MatId,
514 Arc<DashMap<Vec<Value>, RowBuffer>>,
515 > = DenseIdMap::with_capacity(plan.stages.blocks.len());
516 for i in 0..plan.stages.blocks.len() {
517 materializations.insert(
518 MatId::from_usize(i),
519 Arc::new(Default::default()),
520 );
521 }
522 let specs: Arc<DenseIdMap<MatId, MatSpec>> = Arc::new(
523 plan.stages
524 .blocks
525 .iter()
526 .enumerate()
527 .map(|(i, block)| {
528 (MatId::from_usize(i), block.1.clone())
529 })
530 .collect(),
531 );
532 let mut materializations = Arc::new(materializations);
533
534 for (mat_id, stage_block) in
535 plan.stages.blocks.iter().enumerate()
536 {
537 let mat_id = MatId::from_usize(mat_id);
538 egglog_concurrency::scope(|stage_scope| {
539 let mut materializer = ScopedMaterializer {
540 scope: stage_scope,
541 specs: specs.clone(),
542 materializations: materializations.clone(),
543 scratch_key: Default::default(),
544 scratch_val: Default::default(),
545 };
546 join_state.run_join_stages(
547 &stage_block.0,
548 &plan.atoms,
549 mat_id,
550 &mut binding_info,
551 &mut materializer,
552 );
553 });
554 if materializations[mat_id].is_empty() {
555 break 'eval;
556 }
557 assert_eq!(Arc::strong_count(&materializations), 1);
558 let mut materializations_dearc =
559 Arc::unwrap_or_clone(materializations);
560 let materialization = mem::take(
561 Arc::get_mut(&mut materializations_dearc[mat_id])
562 .unwrap(),
563 )
564 .into_iter()
565 .collect::<IndexMap<_, _>>();
566 binding_info
567 .materializations
568 .insert(mat_id, Arc::new(materialization));
569 materializations = Arc::new(materializations_dearc);
570 }
571 join_state.run_join_stages(
572 &plan.result_block,
573 &plan.atoms,
574 plan.actions,
575 &mut binding_info,
576 &mut action_buf,
577 );
578 }
579 }
580 }
581 let search_and_apply_time = search_and_apply_timer.elapsed();
582 if action_buf.needs_flush {
583 action_buf.flush(&mut exec_state.clone());
584 }
585 let mut rule_report: RefMut<'_, Arc<str>, Vec<RuleReport>> =
586 dash_rule_reports.entry(desc).or_default();
587 rule_report.value_mut().push(RuleReport {
588 plan: report_plan,
589 search_and_apply_time,
590 num_matches: usize::MAX,
591 });
592 });
593 }
594 });
595 rule_reports = dash_rule_reports
596 .iter()
597 .map(|entry| (entry.key().clone(), entry.value().clone()))
598 .collect();
599 } else {
600 rule_reports = HashMap::default();
601 let join_state = JoinState::new(self, exec_state.clone(), trie_cache.clone());
602 let mut action_buf = InPlaceActionBuffer {
605 rule_set,
606 match_counter: match_counter.as_ref(),
607 batches: Default::default(),
608 };
609 for (plan, desc, symbol_map) in rule_set.plans.values() {
610 let report_plan = match report_level {
611 ReportLevel::TimeOnly => None,
612 ReportLevel::WithPlan | ReportLevel::StageInfo => {
613 Some(plan.to_report(symbol_map))
614 }
615 };
616 let mut binding_info = BindingInfo::default();
617
618 let search_and_apply_timer = Instant::now();
619 'eval: {
620 for (id, info) in plan.atoms().iter() {
621 let headers: SmallVec<[&JoinHeader; 2]> =
622 plan.header().iter().filter(|h| h.atom == id).collect();
623 match join_state.root_node(info.table, &headers) {
624 Some(node) => binding_info.insert_node(id, node),
625 None => break 'eval,
626 }
627 }
628 match plan {
629 Plan::SinglePlan(plan) => {
630 join_state.run_join_stages(
631 &plan.stages,
632 &plan.atoms,
633 plan.actions,
634 &mut binding_info,
635 &mut action_buf,
636 );
637 }
638 Plan::DecomposedPlan(plan) => {
639 let mut materializations =
640 DenseIdMap::with_capacity(plan.stages.blocks.len());
641 for i in 0..plan.stages.blocks.len() {
642 materializations.insert(MatId::from_usize(i), Default::default());
643 }
644 let mut materializer = InPlaceMaterializer {
645 specs: &plan
646 .stages
647 .blocks
648 .iter()
649 .enumerate()
650 .map(|(i, block)| (MatId::from_usize(i), block.1.clone()))
651 .collect(),
652 materializations,
653 scratch_key: Default::default(),
654 scratch_val: Default::default(),
655 };
656
657 for (mat_id, stage_block) in plan.stages.blocks.iter().enumerate() {
658 let mat_id = MatId::from_usize(mat_id);
659 join_state.run_join_stages(
660 &stage_block.0,
661 &plan.atoms,
662 mat_id,
663 &mut binding_info,
664 &mut materializer,
665 );
666 if materializer.materializations[mat_id].is_empty() {
667 break 'eval;
668 }
669 binding_info.materializations.insert(
670 mat_id,
671 Arc::new(materializer.materializations.take(mat_id).unwrap()),
672 );
673 }
674 join_state.run_join_stages(
675 &plan.result_block,
676 &plan.atoms,
677 plan.actions,
678 &mut binding_info,
679 &mut action_buf,
680 );
681 }
682 }
683 }
684 let search_and_apply_time = search_and_apply_timer.elapsed();
685
686 let rule_report = rule_reports.entry(desc.clone()).or_default();
688 rule_report.push(RuleReport {
689 plan: report_plan,
690 search_and_apply_time,
691 num_matches: usize::MAX,
692 });
693 }
694 action_buf.flush(&mut exec_state.clone());
695 }
696
697 for (plan, desc, _symbol_map) in rule_set.plans.values() {
698 let reports = rule_reports.get_mut(desc).unwrap();
699 let i = reports
700 .iter()
701 .position(|r| r.num_matches == usize::MAX)
705 .unwrap();
706 reports[i].num_matches = match_counter.read_matches(plan.actions());
710 }
711 let search_and_apply_time = search_and_apply_timer.elapsed();
712
713 let merge_timer = Instant::now();
714 let changed = self.merge_all();
715 let merge_time = merge_timer.elapsed();
716
717 RuleSetReport {
718 changed,
719 rule_reports,
720 search_and_apply_time,
721 merge_time,
722 }
723 }
724}
725
726struct ActionState {
727 n_runs: usize,
728 len: usize,
729 bindings: Bindings,
730}
731
732impl ActionState {
733 fn new(batch_size: usize) -> Self {
734 Self {
735 n_runs: 0,
736 len: 0,
737 bindings: Bindings::new(batch_size),
738 }
739 }
740}
741
742struct JoinState<'a> {
743 db: &'a Database,
744 exec_state: ExecutionState<'a>,
745 pool: Pool<SortedOffsetVector>,
748 trie_cache: Option<Arc<TrieCache>>,
751}
752
753type ColumnIndexes = IdVec<ColumnId, OnceLock<Arc<SortedColumnIndex>>>;
755type ChildrenMaps = IdVec<ColumnId, RwLock<HashMap<Value, (Arc<TrieNode>, Box<[Constraint]>)>>>;
764
765type BaseSig = (TableId, SmallVec<[Constraint; 2]>);
768
769type RootKey = (TableId, u32);
772
773#[derive(Default)]
788struct TrieCache {
789 roots: DashMap<RootKey, Arc<TrieNode>>,
790 bases: DashMap<BaseSig, u32>,
792 next_base: AtomicUsize,
793 shared: HashSet<BaseSig>,
795}
796
797impl TrieCache {
798 fn base_id(&self, table: TableId, fast: &[Constraint]) -> u32 {
806 if fast.is_empty() {
807 return 0;
808 }
809 let mut sig: SmallVec<[Constraint; 2]> = SmallVec::from_iter(fast.iter().cloned());
810 sig.sort_unstable();
811 match self.bases.entry((table, sig)) {
812 Entry::Occupied(o) => *o.get(),
813 Entry::Vacant(v) => {
814 let id = self.next_base.fetch_add(1, Ordering::Relaxed) as u32 + 1;
815 v.insert(id);
816 id
817 }
818 }
819 }
820
821 fn root_sig(plan: &Plan, atom: AtomId, table: TableId) -> BaseSig {
824 let mut fast: SmallVec<[Constraint; 2]> = SmallVec::new();
825 for h in plan.header().iter().filter(|h| h.atom == atom) {
826 fast.extend(h.constraints.iter().cloned());
827 }
828 fast.sort_unstable();
829 (table, fast)
830 }
831
832 fn compute_shared<'a>(plans: impl Iterator<Item = &'a Plan>) -> HashSet<BaseSig> {
835 let mut counts: HashMap<BaseSig, u32> = HashMap::default();
836 for plan in plans {
837 for (atom, info) in plan.atoms().iter() {
838 *counts
839 .entry(Self::root_sig(plan, atom, info.table))
840 .or_default() += 1;
841 }
842 }
843 counts
844 .into_iter()
845 .filter_map(|(sig, n)| (n > 1).then_some(sig))
846 .collect()
847 }
848
849 fn with_shared(shared: HashSet<BaseSig>) -> TrieCache {
857 let shards = crate::parallel::current_num_threads()
860 .next_power_of_two()
861 .max(2);
862 TrieCache {
863 roots: DashMap::with_hasher_and_shard_amount(Default::default(), shards),
864 bases: DashMap::with_hasher_and_shard_amount(Default::default(), shards),
865 next_base: AtomicUsize::new(0),
866 shared,
867 }
868 }
869}
870
871pub(crate) struct TrieNode {
879 subset: Subset,
881 cached_subsets: OnceLock<Pooled<ColumnIndexes>>,
883 cached_children: OnceLock<Pooled<ChildrenMaps>>,
889}
890
891impl std::fmt::Debug for TrieNode {
892 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
893 f.debug_struct("TrieNode")
894 .field("subset", &self.subset)
895 .finish()
896 }
897}
898
899impl TrieNode {
900 fn new(subset: Subset) -> Self {
901 Self {
902 subset,
903 cached_subsets: Default::default(),
904 cached_children: Default::default(),
905 }
906 }
907
908 fn size(&self) -> usize {
909 self.subset.size()
910 }
911 fn get_cached_index(&self, col: ColumnId, info: &TableInfo) -> Arc<SortedColumnIndex> {
912 self.cached_subsets.get_or_init(|| {
913 let mut vec: Pooled<ColumnIndexes> = with_pool_set(|ps| ps.get());
915 vec.resize_with(info.spec.arity(), OnceLock::new);
916 vec
917 })[col]
918 .get_or_init(|| {
919 Arc::new(SortedColumnIndex::build_for_subset(
920 info.table.as_ref(),
921 self.subset.as_ref(),
922 col,
923 ))
924 })
925 .clone()
926 }
927
928 fn get_cached_trie_node(
939 &self,
940 col: ColumnId,
941 value: Value,
942 edge_cs: &[Constraint],
943 info: &TableInfo,
944 sub: impl FnOnce() -> Subset,
945 ) -> Arc<TrieNode> {
946 let map = &self.cached_children.get_or_init(|| {
947 let mut vec: Pooled<ChildrenMaps> = with_pool_set(|ps| ps.get());
948 vec.resize_with(info.spec.arity(), || RwLock::new(HashMap::default()));
949 vec
950 })[col];
951 {
954 let guard = map.read().unwrap();
955 if let Some((node, stored_cs)) = guard.get(&value)
956 && &**stored_cs == edge_cs
957 {
958 return node.clone();
959 }
960 }
961 let mut guard = map.write().unwrap();
963 if let Some((node, stored_cs)) = guard.get(&value)
964 && &**stored_cs == edge_cs
965 {
966 return node.clone();
967 }
968 let new_node = Arc::new(TrieNode::new(sub()));
969 guard.insert(value, (new_node.clone(), Box::from(edge_cs)));
970 new_node
971 }
972}
973
974impl FrameUpdates {
975 fn refine_atom_subset(&mut self, atom: AtomId, subset: Subset) {
978 match subset {
979 Subset::Dense(range) => self.refine_atom_dense(atom, range),
980 sub => self.refine_atom(atom, Arc::new(TrieNode::new(sub))),
981 }
982 }
983}
984
985type BindingSet = Vec<(SmallVec<[Variable; 4]>, Arc<TaggedRowBuffer<SmallValueVec>>)>;
986
987#[derive(Default, Clone)]
988struct BindingInfo {
989 bindings: DenseIdMap<Variable, Value>,
990 binding_sets: BindingSet,
991 subsets: DenseIdMap<AtomId, Arc<TrieNode>>,
992 materializations: DenseIdMap<MatId, Arc<IndexMap<Vec<Value>, RowBuffer>>>,
993}
994
995impl BindingInfo {
996 fn insert_subset(&mut self, atom: AtomId, subset: Subset) {
998 if let Some(slot) = self.subsets.get_mut(atom)
999 && let Some(node) = Arc::get_mut(slot)
1000 {
1001 node.cached_subsets.take();
1002 node.cached_children.take();
1003 node.subset = subset;
1004 return;
1005 }
1006 self.subsets.insert(atom, Arc::new(TrieNode::new(subset)));
1007 }
1008
1009 fn insert_node(&mut self, atom: AtomId, node: Arc<TrieNode>) {
1010 self.subsets.insert(atom, node);
1011 }
1012
1013 fn move_back(&mut self, atom: AtomId, prober: Prober) {
1016 self.subsets.insert(atom, prober.node);
1017 }
1018
1019 fn move_back_node(&mut self, atom: AtomId, node: Arc<TrieNode>) {
1020 self.subsets.insert(atom, node);
1021 }
1022
1023 fn has_empty_subset(&self, atom: AtomId) -> bool {
1024 self.subsets[atom].subset.is_empty()
1025 }
1026
1027 fn unwrap_val(&mut self, atom: AtomId) -> Arc<TrieNode> {
1028 self.subsets.unwrap_val(atom)
1029 }
1030}
1031
1032impl<'a> JoinState<'a> {
1033 fn new(
1034 db: &'a Database,
1035 exec_state: ExecutionState<'a>,
1036 trie_cache: Option<Arc<TrieCache>>,
1037 ) -> Self {
1038 Self {
1039 db,
1040 exec_state,
1041 pool: with_pool_set(|ps| ps.get_pool()),
1042 trie_cache,
1043 }
1044 }
1045
1046 fn root_node(&self, table_id: TableId, headers: &[&JoinHeader]) -> Option<Arc<TrieNode>> {
1058 let Some(trie_cache) = self.trie_cache.as_ref() else {
1063 return Some(Arc::new(TrieNode::new(
1064 self.build_root_subset(table_id, headers)?,
1065 )));
1066 };
1067 let mut fast: SmallVec<[Constraint; 2]> = SmallVec::new();
1069 for h in headers {
1070 fast.extend(h.constraints.iter().cloned());
1071 }
1072 fast.sort_unstable();
1073 let sig: BaseSig = (table_id, fast);
1074
1075 if !trie_cache.shared.contains(&sig) {
1076 return Some(Arc::new(TrieNode::new(
1078 self.build_root_subset(table_id, headers)?,
1079 )));
1080 }
1081
1082 let base = trie_cache.base_id(table_id, &sig.1);
1083 let key: RootKey = (table_id, base);
1084 if let Some(node) = trie_cache.roots.get(&key) {
1085 return (!node.subset.is_empty()).then(|| node.clone());
1086 }
1087 let subset = self.build_root_subset(table_id, headers)?;
1088 let node = match trie_cache.roots.entry(key) {
1089 Entry::Occupied(o) => o.get().clone(),
1090 Entry::Vacant(v) => {
1091 let node = Arc::new(TrieNode::new(subset));
1092 v.insert(node.clone());
1093 node
1094 }
1095 };
1096 (!node.subset.is_empty()).then_some(node)
1097 }
1098
1099 fn build_root_subset(&self, table_id: TableId, headers: &[&JoinHeader]) -> Option<Subset> {
1102 let mut subset = self.db.get_table(table_id).all();
1103 for h in headers {
1104 if h.subset.is_empty() {
1105 return None;
1106 }
1107 subset.intersect(h.subset.as_ref(), &self.pool);
1108 if subset.is_empty() {
1109 return None;
1110 }
1111 }
1112 Some(subset)
1113 }
1114
1115 fn get_index(
1116 &self,
1117 atoms: &Arc<DenseIdMap<AtomId, Atom>>,
1118 atom: AtomId,
1119 binding_info: &mut BindingInfo,
1120 cols: impl Iterator<Item = ColumnId>,
1121 ) -> Prober {
1122 let cols = SmallVec::<[ColumnId; 4]>::from_iter(cols);
1123 let trie_node = binding_info.subsets.unwrap_val(atom);
1124 let subset = &trie_node.subset;
1125
1126 let table_id = atoms[atom].table;
1127 let info = &self.db.tables[table_id];
1128 let dyn_index = if subset.size() <= SMALL_RESIDUAL && cols.len() == 1 {
1129 DynamicIndex::SparseColumn(SparseColumnIndex::new(
1130 info.table.as_ref(),
1131 subset.as_ref(),
1132 cols[0],
1133 ))
1134 } else {
1135 let all_cacheable = cols.iter().all(|col| {
1136 !info
1137 .spec
1138 .uncacheable_columns
1139 .get(*col)
1140 .copied()
1141 .unwrap_or(false)
1142 });
1143 let whole_table = info.table.all();
1144 if let Subset::Dense(range) = subset
1145 && all_cacheable
1146 && whole_table.size() / 2 < subset.size()
1147 {
1148 let needs_intersect =
1151 !(whole_table.is_dense() && subset.bounds() == whole_table.bounds());
1152 let intersect_outer = if needs_intersect { Some(*range) } else { None };
1155 if cols.len() != 1 {
1159 DynamicIndex::Cached {
1160 intersect_outer,
1161 table: get_index_from_tableinfo(info, &cols),
1162 }
1163 } else {
1164 DynamicIndex::CachedColumn {
1165 intersect_outer,
1166 table: get_column_index_from_tableinfo(info, cols[0]).clone(),
1167 }
1168 }
1169 } else if cols.len() != 1 {
1170 DynamicIndex::Dynamic(info.table.group_by_key(subset.as_ref(), &cols))
1172 } else {
1173 DynamicIndex::DynamicColumn(trie_node.get_cached_index(cols[0], info))
1174 }
1175 };
1176 Prober {
1177 node: trie_node,
1178 ix: dyn_index,
1179 }
1180 }
1181 fn get_column_index(
1182 &self,
1183 atoms: &Arc<DenseIdMap<AtomId, Atom>>,
1184 binding_info: &mut BindingInfo,
1185 atom: AtomId,
1186 col: ColumnId,
1187 ) -> Prober {
1188 self.get_index(atoms, atom, binding_info, iter::once(col))
1189 }
1190
1191 fn run_join_stages<'buf, A: NumericId + 'buf, BUF: ActionBuffer<'buf, A>>(
1201 &self,
1202 stages: &'buf JoinStages,
1203 atoms: &'buf Arc<DenseIdMap<AtomId, Atom>>,
1204 action: A,
1205 binding_info: &mut BindingInfo,
1206 action_buf: &mut BUF,
1207 ) where
1208 'a: 'buf,
1209 {
1210 if log::log_enabled!(log::Level::Trace) {
1211 log::trace!("Starting running query stages:\n{stages:#?}");
1212 }
1213 for (_, node) in binding_info.subsets.iter() {
1214 if node.subset.is_empty() {
1215 return;
1216 }
1217 }
1218 let mut order = InstrOrder::from_iter(0..stages.instrs.len());
1219 let mut leaf_scans: LeafScans = smallvec::smallvec![false; stages.instrs.len()];
1220 sort_plan_by_size(&mut order, &mut leaf_scans, 0, &stages.instrs, binding_info);
1221 self.run_plan(
1222 stages,
1223 atoms,
1224 action,
1225 &mut order,
1226 &mut leaf_scans,
1227 0,
1228 binding_info,
1229 action_buf,
1230 );
1231 }
1232
1233 #[allow(clippy::too_many_arguments)]
1242 fn run_plan<'buf, A: NumericId + 'buf, BUF: ActionBuffer<'buf, A>>(
1243 &self,
1244 stages: &'buf JoinStages,
1245 atoms: &'buf Arc<DenseIdMap<AtomId, Atom>>,
1246 action: A,
1247 instr_order: &mut InstrOrder,
1248 leaf_scans: &mut LeafScans,
1249 cur: usize,
1250 binding_info: &mut BindingInfo,
1251 action_buf: &mut BUF,
1252 ) where
1253 'a: 'buf,
1254 {
1255 if self.exec_state.should_stop() {
1256 return;
1257 }
1258
1259 if cur >= instr_order.len() {
1260 action_buf.push_bindings_factorized(
1261 action,
1262 &mut binding_info.bindings,
1263 &binding_info.binding_sets,
1264 &self.exec_state,
1265 );
1266 return;
1267 }
1268 let chunk_size = action_buf.morsel_size(cur, instr_order.len());
1269 let mut cur_size = estimate_size(&stages.instrs[instr_order.get(cur)], binding_info);
1270 if cur_size > 32 && cur % 3 == 1 && cur < instr_order.len() - 1 {
1271 sort_plan_by_size(instr_order, leaf_scans, cur, &stages.instrs, binding_info);
1274 cur_size = estimate_size(&stages.instrs[instr_order.get(cur)], binding_info);
1275 }
1276
1277 macro_rules! drain_updates {
1279 ($updates:expr) => {
1280 if self.exec_state.should_stop() {
1281 return;
1282 }
1283 if cur < free_join_fork_depth() && action_buf.supports_parallel_drain() {
1286 drain_updates_parallel!($updates)
1287 } else {
1288 $updates.drain(|update| match update {
1289 UpdateInstr::PushBinding(var, val) => {
1290 binding_info.bindings.insert(var, val);
1291 }
1292 UpdateInstr::RefineAtom(atom, subset) => {
1293 binding_info.insert_node(atom, subset);
1294 }
1295 UpdateInstr::RefineAtomDense(atom, range) => {
1296 binding_info.insert_subset(atom, Subset::Dense(range));
1297 }
1298 UpdateInstr::EndFrame => {
1299 if cur + 1 >= instr_order.len() {
1304 action_buf.push_bindings_factorized(
1305 action,
1306 &mut binding_info.bindings,
1307 &binding_info.binding_sets,
1308 &self.exec_state,
1309 );
1310 } else {
1311 self.run_plan(
1312 stages,
1313 atoms,
1314 action,
1315 instr_order,
1316 leaf_scans,
1317 cur + 1,
1318 binding_info,
1319 action_buf,
1320 );
1321 }
1322 }
1323 })
1324 }
1325 };
1326 }
1327 macro_rules! drain_updates_parallel {
1328 ($updates:expr) => {{
1329 if self.exec_state.should_stop() {
1330 return;
1331 }
1332 let db = self.db;
1333 let exec_state_for_factory = self.exec_state.clone();
1334 let exec_state_for_work = self.exec_state.clone();
1335 let trie_cache = self.trie_cache.clone();
1336 action_buf.recur(
1337 BorrowedLocalState {
1338 binding_info,
1339 instr_order,
1340 leaf_scans,
1341 updates: &mut $updates,
1342 },
1343 move || exec_state_for_factory.clone(),
1344 move |BorrowedLocalState {
1345 binding_info,
1346 instr_order,
1347 leaf_scans,
1348 updates,
1349 },
1350 buf| {
1351 updates.drain(|update| match update {
1352 UpdateInstr::PushBinding(var, val) => {
1353 binding_info.bindings.insert(var, val);
1354 }
1355 UpdateInstr::RefineAtom(atom, subset) => {
1356 binding_info.insert_node(atom, subset);
1357 }
1358 UpdateInstr::RefineAtomDense(atom, range) => {
1359 binding_info.insert_subset(atom, Subset::Dense(range));
1360 }
1361 UpdateInstr::EndFrame => {
1362 JoinState {
1363 db,
1364 exec_state: exec_state_for_work.clone(),
1365 pool: with_pool_set(|ps| ps.get_pool()),
1369 trie_cache: trie_cache.clone(),
1370 }
1371 .run_plan(
1372 stages,
1373 atoms,
1374 action,
1375 instr_order,
1376 leaf_scans,
1377 cur + 1,
1378 binding_info,
1379 buf,
1380 );
1381 }
1382 })
1383 },
1384 );
1385 $updates.clear();
1386 }};
1387 }
1388
1389 fn refine_subset(
1390 sub: PotentiallyStale<SubsetRef<'_>>,
1391 constraints: &[Constraint],
1392 table: &WrappedTableRef,
1393 has_stale: bool,
1394 pool: &Pool<SortedOffsetVector>,
1395 ) -> Subset {
1396 let need_live = sub.can_be_stale && has_stale;
1397 if constraints.is_empty() && !need_live {
1398 sub.inner.to_owned(pool)
1399 } else {
1400 table.refine_ref(sub.inner, constraints, need_live)
1403 }
1404 }
1405
1406 let pool = &self.pool;
1407 match &stages.instrs[instr_order.get(cur)] {
1408 JoinStage::Intersect { var, scans } => match scans.as_slice() {
1409 [] => {}
1410 [a] => {
1411 if binding_info.has_empty_subset(a.atom) {
1412 return;
1413 }
1414 let prober = self.get_column_index(atoms, binding_info, a.atom, a.column);
1415 let info = &self.db.tables[atoms[a.atom].table];
1416 let table = info.table.as_ref();
1417 let has_stale = table.has_stale_rows();
1418 let mut updates = FrameUpdates::with_capacity(cmp::min(chunk_size, cur_size));
1419 prober.for_each(|val, x| {
1420 updates.push_binding(*var, val[0]);
1421 if x.size() <= 16 {
1422 let sub = refine_subset(x, &a.cs, &table, has_stale, pool);
1423 if sub.is_empty() {
1424 updates.rollback();
1425 return;
1426 }
1427 updates.refine_atom_subset(a.atom, sub);
1428 } else {
1429 let node = prober.node.get_cached_trie_node(
1430 a.column,
1431 val[0],
1432 &a.cs,
1433 info,
1434 || refine_subset(x, &a.cs, &table, has_stale, pool),
1435 );
1436 if node.subset.is_empty() {
1437 updates.rollback();
1438 return;
1439 }
1440 updates.refine_atom(a.atom, node);
1441 }
1442 updates.finish_frame();
1443 if updates.frames() >= chunk_size {
1444 drain_updates!(updates);
1445 }
1446 });
1447 drain_updates!(updates);
1448 binding_info.move_back(a.atom, prober);
1449 }
1450 [a, b] => {
1451 let a_prober = self.get_column_index(atoms, binding_info, a.atom, a.column);
1452 let b_prober = self.get_column_index(atoms, binding_info, b.atom, b.column);
1453
1454 let ((smaller, smaller_scan), (larger, larger_scan)) =
1455 if a_prober.len() < b_prober.len() {
1456 ((&a_prober, a), (&b_prober, b))
1457 } else {
1458 ((&b_prober, b), (&a_prober, a))
1459 };
1460
1461 let smaller_atom = smaller_scan.atom;
1462 let larger_atom = larger_scan.atom;
1463 let large_info = &self.db.tables[atoms[larger_atom].table];
1464 let large_table = large_info.table.as_ref();
1465 let large_has_stale = large_table.has_stale_rows();
1466 let small_info = &self.db.tables[atoms[smaller_atom].table];
1467 let small_table = small_info.table.as_ref();
1468 let small_has_stale = small_table.has_stale_rows();
1469 let mut updates = FrameUpdates::with_capacity(cmp::min(chunk_size, cur_size));
1470 smaller.for_each(|val, small_sub| {
1471 if let Some(large_sub) = larger.get_subset(val) {
1472 updates.push_binding(*var, val[0]);
1473 if small_sub.size() <= 16 {
1474 let small_sub = refine_subset(
1475 small_sub,
1476 &smaller_scan.cs,
1477 &small_table,
1478 small_has_stale,
1479 pool,
1480 );
1481 if small_sub.is_empty() {
1482 updates.rollback();
1483 return;
1484 }
1485 updates.refine_atom_subset(smaller_atom, small_sub);
1486 } else {
1487 let smaller_node = smaller.node.get_cached_trie_node(
1488 smaller_scan.column,
1489 val[0],
1490 &smaller_scan.cs,
1491 small_info,
1492 || {
1493 refine_subset(
1494 small_sub,
1495 &smaller_scan.cs,
1496 &small_table,
1497 small_has_stale,
1498 pool,
1499 )
1500 },
1501 );
1502 if smaller_node.subset.is_empty() {
1503 updates.rollback();
1504 return;
1505 }
1506 updates.refine_atom(smaller_atom, smaller_node);
1507 }
1508 if large_sub.size() <= 16 {
1509 let large_sub = refine_subset(
1510 large_sub,
1511 &larger_scan.cs,
1512 &large_table,
1513 large_has_stale,
1514 pool,
1515 );
1516 if large_sub.is_empty() {
1517 updates.rollback();
1518 return;
1519 }
1520 updates.refine_atom_subset(larger_atom, large_sub);
1521 } else {
1522 let larger_node = larger.node.get_cached_trie_node(
1523 larger_scan.column,
1524 val[0],
1525 &larger_scan.cs,
1526 large_info,
1527 || {
1528 refine_subset(
1529 large_sub,
1530 &larger_scan.cs,
1531 &large_table,
1532 large_has_stale,
1533 pool,
1534 )
1535 },
1536 );
1537 if larger_node.subset.is_empty() {
1538 updates.rollback();
1539 return;
1540 }
1541 updates.refine_atom(larger_atom, larger_node);
1542 }
1543 updates.finish_frame();
1544 if updates.frames() >= chunk_size {
1545 drain_updates!(updates);
1546 }
1547 }
1548 });
1549 drain_updates!(updates);
1550
1551 binding_info.move_back(a.atom, a_prober);
1552 binding_info.move_back(b.atom, b_prober);
1553 }
1554 rest => {
1555 let mut smallest = 0;
1556 let mut smallest_size = usize::MAX;
1557 let mut probers = Vec::with_capacity(rest.len());
1558 for (i, scan) in rest.iter().enumerate() {
1559 let prober =
1560 self.get_column_index(atoms, binding_info, scan.atom, scan.column);
1561 let size = prober.len();
1562 if size < smallest_size {
1563 smallest = i;
1564 smallest_size = size;
1565 }
1566 probers.push(prober);
1567 }
1568
1569 let main_spec = &rest[smallest];
1570 let main_spec_info = &self.db.tables[atoms[main_spec.atom].table];
1571 let main_spec_table = main_spec_info.table.as_ref();
1572 let main_spec_has_stale = main_spec_table.has_stale_rows();
1573 let rest_has_stale: SmallVec<[bool; 3]> = rest
1575 .iter()
1576 .map(|scan| {
1577 self.db.tables[atoms[scan.atom].table]
1578 .table
1579 .as_ref()
1580 .has_stale_rows()
1581 })
1582 .collect();
1583
1584 if smallest_size != 0 {
1585 let mut updates =
1587 FrameUpdates::with_capacity(cmp::min(chunk_size, cur_size));
1588 probers[smallest].for_each(|key, sub| {
1589 updates.push_binding(*var, key[0]);
1590 for (i, scan) in rest.iter().enumerate() {
1591 if i == smallest {
1592 continue;
1593 }
1594 if let Some(sub) = probers[i].get_subset(key) {
1595 let table =
1596 self.db.tables[atoms[rest[i].atom].table].table.as_ref();
1597 if sub.size() <= 16 {
1598 let sub = refine_subset(
1599 sub,
1600 &rest[i].cs,
1601 &table,
1602 rest_has_stale[i],
1603 pool,
1604 );
1605 if sub.is_empty() {
1606 updates.rollback();
1607 return;
1608 }
1609 updates.refine_atom_subset(scan.atom, sub);
1610 } else {
1611 let node = probers[i].node.get_cached_trie_node(
1612 scan.column,
1613 key[0],
1614 &rest[i].cs,
1615 &self.db.tables[atoms[scan.atom].table],
1616 || {
1617 refine_subset(
1618 sub,
1619 &rest[i].cs,
1620 &table,
1621 rest_has_stale[i],
1622 pool,
1623 )
1624 },
1625 );
1626 if node.subset.is_empty() {
1627 updates.rollback();
1628 return;
1629 }
1630 updates.refine_atom(scan.atom, node);
1631 }
1632 } else {
1633 updates.rollback();
1634 return;
1636 }
1637 }
1638 if sub.size() <= 16 {
1639 let main_sub = refine_subset(
1640 sub,
1641 &main_spec.cs,
1642 &main_spec_table,
1643 main_spec_has_stale,
1644 pool,
1645 );
1646 if main_sub.is_empty() {
1647 updates.rollback();
1648 return;
1649 }
1650 updates.refine_atom_subset(main_spec.atom, main_sub);
1651 } else {
1652 let main_node = probers[smallest].node.get_cached_trie_node(
1653 main_spec.column,
1654 key[0],
1655 &main_spec.cs,
1656 main_spec_info,
1657 || {
1658 refine_subset(
1659 sub,
1660 &main_spec.cs,
1661 &main_spec_table,
1662 main_spec_has_stale,
1663 pool,
1664 )
1665 },
1666 );
1667 if main_node.subset.is_empty() {
1668 updates.rollback();
1669 return;
1670 }
1671 updates.refine_atom(main_spec.atom, main_node);
1672 }
1673 updates.finish_frame();
1674 if updates.frames() >= chunk_size {
1675 drain_updates!(updates);
1676 }
1677 });
1678 drain_updates!(updates);
1679 }
1680 for (spec, prober) in rest.iter().zip(probers.into_iter()) {
1681 binding_info.move_back(spec.atom, prober);
1682 }
1683 }
1684 },
1685 JoinStage::FusedIntersect {
1686 cover,
1687 bind,
1688 to_intersect,
1689 } if to_intersect.is_empty() => {
1690 let is_leaf_scan = leaf_scans[cur];
1691 let cover_atom = cover.to_index.atom;
1692 if binding_info.has_empty_subset(cover_atom) {
1693 return;
1694 }
1695 if is_leaf_scan {
1696 let table = self.db.tables[atoms[cover_atom].table].table.as_ref();
1697 let cover_node = binding_info.unwrap_val(cover_atom);
1698 let cover_subset = cover_node.subset.as_ref();
1699
1700 let proj =
1701 SmallVec::<[ColumnId; 4]>::from_iter(bind.iter().map(|(col, _)| *col));
1702 let vars = bind.iter().map(|(_, var)| *var).collect();
1703 let mut buf = TaggedRowBuffer::new_inline(bind.len());
1704 table.scan_project(
1705 cover_subset,
1706 &proj,
1707 Offset::new(0),
1708 usize::MAX,
1709 &cover.constraints,
1710 &mut buf,
1711 );
1712
1713 if buf.is_empty() {
1714 binding_info.move_back_node(cover_atom, cover_node);
1715 return;
1716 }
1717
1718 binding_info.binding_sets.push((vars, Arc::new(buf)));
1719 let mut updates = FrameUpdates::with_capacity(1);
1720 updates.finish_frame();
1721 drain_updates!(updates);
1722 binding_info.binding_sets.pop();
1723 binding_info.move_back_node(cover_atom, cover_node);
1724 } else {
1725 let proj =
1726 SmallVec::<[ColumnId; 4]>::from_iter(bind.iter().map(|(col, _)| *col));
1727 let cover_node = binding_info.unwrap_val(cover_atom);
1728 let cover_subset = cover_node.subset.as_ref();
1729 let mut offset = Offset::new(0);
1730 let mut buffer = TaggedRowBuffer::new(bind.len());
1731 let mut updates = FrameUpdates::with_capacity(cmp::min(chunk_size, cur_size));
1732 loop {
1733 buffer.clear();
1734 let table = &self.db.tables[atoms[cover_atom].table].table;
1735 let next = table.scan_project(
1736 cover_subset,
1737 &proj,
1738 offset,
1739 chunk_size,
1740 &cover.constraints,
1741 &mut buffer,
1742 );
1743 for (row, key) in buffer.iter() {
1744 updates.refine_atom_dense(cover_atom, OffsetRange::new(row, row.inc()));
1745 for (i, (_, var)) in bind.iter().enumerate() {
1747 updates.push_binding(*var, key[i]);
1748 }
1749 updates.finish_frame();
1750 if updates.frames() >= chunk_size {
1751 drain_updates!(updates);
1752 }
1753 }
1754 if let Some(next) = next {
1755 offset = next;
1756 continue;
1757 }
1758 break;
1759 }
1760 drain_updates!(updates);
1761 binding_info.move_back_node(cover_atom, cover_node);
1763 }
1764 }
1765 JoinStage::FusedIntersect {
1766 cover,
1767 bind,
1768 to_intersect,
1769 } => {
1770 let cover_atom = cover.to_index.atom;
1771 if binding_info.has_empty_subset(cover_atom) {
1772 return;
1773 }
1774 let index_probers = to_intersect
1775 .iter()
1776 .enumerate()
1777 .map(|(i, (spec, _))| {
1778 (
1779 i,
1780 spec.to_index.atom,
1781 self.get_index(
1782 atoms,
1783 spec.to_index.atom,
1784 binding_info,
1785 spec.to_index.vars.iter().copied(),
1786 ),
1787 )
1788 })
1789 .collect::<SmallVec<[(usize, AtomId, Prober); 4]>>();
1790 let index_has_stale: SmallVec<[bool; 4]> = index_probers
1792 .iter()
1793 .map(|(_, atom, _)| {
1794 self.db.tables[atoms[*atom].table]
1795 .table
1796 .as_ref()
1797 .has_stale_rows()
1798 })
1799 .collect();
1800 let proj = SmallVec::<[ColumnId; 4]>::from_iter(bind.iter().map(|(col, _)| *col));
1801 let cover_node = binding_info.unwrap_val(cover_atom);
1802 let cover_subset = cover_node.subset.as_ref();
1803 let mut cur = Offset::new(0);
1804 let mut buffer = TaggedRowBuffer::new(bind.len());
1805 let mut updates = FrameUpdates::with_capacity(cmp::min(chunk_size, cur_size));
1806 loop {
1807 buffer.clear();
1808 let table = &self.db.tables[atoms[cover_atom].table].table;
1809 let next = table.scan_project(
1810 cover_subset,
1811 &proj,
1812 cur,
1813 chunk_size,
1814 &cover.constraints,
1815 &mut buffer,
1816 );
1817 'mid: for (row, key) in buffer.iter() {
1818 updates.refine_atom_dense(cover_atom, OffsetRange::new(row, row.inc()));
1819 for (i, (_, var)) in bind.iter().enumerate() {
1821 updates.push_binding(*var, key[i]);
1822 }
1823 for (prober_idx, (i, atom, prober)) in index_probers.iter().enumerate() {
1825 let index_cols = &to_intersect[*i].1;
1827 let index_key_buf: SmallVec<[Value; 4]>;
1829 let index_key: &[Value] = if let [col] = index_cols.as_slice() {
1830 std::slice::from_ref(&key[col.index()])
1831 } else {
1832 index_key_buf =
1833 index_cols.iter().map(|col| key[col.index()]).collect();
1834 &index_key_buf
1835 };
1836 let Some(subset) = prober.get_subset(index_key) else {
1837 updates.rollback();
1838 continue 'mid;
1840 };
1841 let table_info = &self.db.tables[atoms[*atom].table];
1843 let cs = &to_intersect[*i].0.constraints;
1844 let subset = refine_subset(
1845 subset,
1846 cs,
1847 &table_info.table.as_ref(),
1848 index_has_stale[prober_idx],
1849 pool,
1850 );
1851 if subset.is_empty() {
1852 updates.rollback();
1853 continue 'mid;
1855 }
1856 updates.refine_atom_subset(*atom, subset);
1857 }
1858 updates.finish_frame();
1859 if updates.frames() >= chunk_size {
1860 drain_updates!(updates);
1861 }
1862 }
1863 if let Some(next) = next {
1864 cur = next;
1865 continue;
1866 }
1867 break;
1868 }
1869 drain_updates!(updates);
1874 binding_info.move_back_node(cover_atom, cover_node);
1876 for (_, atom, prober) in index_probers {
1877 binding_info.move_back(atom, prober);
1878 }
1879 }
1880 JoinStage::FusedIntersectMat {
1881 cover,
1882 mode,
1883 bind,
1884 to_intersect,
1885 } if leaf_scans[cur]
1886 && to_intersect.is_empty()
1887 && matches!(
1888 mode,
1889 MatScanMode::Full | MatScanMode::KeyOnly | MatScanMode::Value(_)
1890 ) =>
1891 {
1892 let cover_mat = binding_info.materializations[*cover].clone();
1895 let vars: SmallVec<[Variable; 4]> = bind.iter().map(|(_, v)| *v).collect();
1896 let mut buf = TaggedRowBuffer::new_inline(bind.len());
1897 let mut row_scratch: SmallVec<[Value; 8]> = SmallVec::new();
1898 match mode {
1899 MatScanMode::Full => {
1900 for group in cover_mat.iter() {
1901 let group_key = group.0;
1902 let group_key_len = group_key.len();
1903 for non_keys in group.1.iter() {
1904 row_scratch.clear();
1905 for (col, _) in bind.iter() {
1906 let val = if col.index() < group_key_len {
1907 group_key[col.index()]
1908 } else {
1909 non_keys[col.index() - group_key_len]
1910 };
1911 row_scratch.push(val);
1912 }
1913 buf.add_row(RowId::new(0), &row_scratch);
1914 }
1915 }
1916 }
1917 MatScanMode::KeyOnly => {
1918 for group in cover_mat.iter() {
1919 let group_key = group.0;
1920 row_scratch.clear();
1921 for (col, _) in bind.iter() {
1922 debug_assert!(col.index() < group_key.len());
1923 row_scratch.push(group_key[col.index()]);
1924 }
1925 buf.add_row(RowId::new(0), &row_scratch);
1926 }
1927 }
1928 MatScanMode::Value(index_vars) => {
1929 let keys: Vec<Value> = index_vars
1930 .iter()
1931 .map(|var| binding_info.bindings[*var])
1932 .collect();
1933 if let Some(group) = cover_mat.get(&keys) {
1934 for vals in group.iter() {
1935 debug_assert!(vals.len() == bind.len());
1936 row_scratch.clear();
1937 for (col, _) in bind.iter() {
1938 row_scratch.push(vals[col.index()]);
1939 }
1940 buf.add_row(RowId::new(0), &row_scratch);
1941 }
1942 }
1943 }
1944 MatScanMode::Lookup(_) => unreachable!("guarded above"),
1945 }
1946 if buf.is_empty() {
1947 return;
1948 }
1949 binding_info.binding_sets.push((vars, Arc::new(buf)));
1950 let mut updates = FrameUpdates::with_capacity(1);
1951 updates.finish_frame();
1952 drain_updates!(updates);
1953 binding_info.binding_sets.pop();
1954 }
1955 JoinStage::FusedIntersectMat {
1956 cover,
1957 mode,
1958 bind,
1959 to_intersect,
1960 } => {
1961 let cover_mat = binding_info.materializations[*cover].clone();
1962 let mut updates = FrameUpdates::with_capacity(cmp::min(chunk_size, cur_size));
1963 let probers = to_intersect
1964 .iter()
1965 .map(|(spec, _)| {
1966 self.get_index(
1967 atoms,
1968 spec.to_index.atom,
1969 binding_info,
1970 spec.to_index.vars.iter().copied(),
1971 )
1972 })
1973 .collect::<SmallVec<[Prober; 4]>>();
1974 let probers_has_stale: SmallVec<[bool; 4]> = to_intersect
1976 .iter()
1977 .map(|(spec, _)| {
1978 self.db.tables[atoms[spec.to_index.atom].table]
1979 .table
1980 .as_ref()
1981 .has_stale_rows()
1982 })
1983 .collect();
1984
1985 let mut key = Vec::with_capacity(4);
1986 let mut prune_probers = |updates: &mut FrameUpdates,
1987 mat_key: Option<&[Value]>,
1988 mat_non_key: Option<&[Value]>|
1989 -> bool {
1990 for (j, ((spec, cols), prober)) in
1991 to_intersect.iter().zip(probers.iter()).enumerate()
1992 {
1993 key.clear();
1994 for col in cols.iter() {
1995 let val = match mat_key {
1996 Some(mat_key) => {
1997 if col.index() < mat_key.len() {
1998 mat_key[col.index()]
1999 } else {
2000 mat_non_key.unwrap()[col.index() - mat_key.len()]
2001 }
2002 }
2003 None => mat_non_key.unwrap()[col.index()],
2004 };
2005 key.push(val);
2006 }
2007 if let Some(subset) = prober.get_subset(&key) {
2008 let subset = refine_subset(
2009 subset,
2010 &spec.constraints,
2011 &self.db.tables[atoms[spec.to_index.atom].table]
2012 .table
2013 .as_ref(),
2014 probers_has_stale[j],
2015 pool,
2016 );
2017 if subset.is_empty() {
2018 return false;
2019 }
2020 updates.refine_atom_subset(spec.to_index.atom, subset);
2021 } else {
2022 return false;
2023 }
2024 }
2025 true
2026 };
2027
2028 match mode {
2029 MatScanMode::Full | MatScanMode::KeyOnly => {
2030 for group in cover_mat.iter() {
2032 let group_key = group.0;
2033 let group_val = group.1;
2034 let group_key_len = group_key.len();
2035 if mode == &MatScanMode::Full {
2036 for non_keys in group_val.iter() {
2038 for (col, var) in bind.iter() {
2039 if col.index() < group_key_len {
2040 updates.push_binding(*var, group_key[col.index()]);
2041 }
2042 }
2043
2044 for (col, var) in bind.iter() {
2046 if col.index() >= group_key_len {
2047 updates.push_binding(
2048 *var,
2049 non_keys[col.index() - group_key_len],
2050 );
2051 }
2052 }
2053 if prune_probers(&mut updates, Some(group_key), Some(non_keys))
2054 {
2055 updates.finish_frame();
2056 } else {
2057 updates.rollback();
2058 }
2059 }
2060 } else if mode == &MatScanMode::KeyOnly {
2061 for (col, var) in bind.iter() {
2062 debug_assert!(col.index() < group_key_len);
2063 updates.push_binding(*var, group_key[col.index()]);
2064 }
2065 if prune_probers(&mut updates, Some(group_key), None) {
2066 updates.finish_frame();
2067 } else {
2068 updates.rollback();
2069 }
2070 }
2071 }
2072 }
2073 MatScanMode::Value(index_vars) | MatScanMode::Lookup(index_vars) => {
2074 let keys = index_vars
2075 .iter()
2076 .map(|var| binding_info.bindings[*var])
2077 .collect::<Vec<Value>>();
2078 if let Some(group) = cover_mat.get(&keys) {
2080 if matches!(mode, MatScanMode::Lookup(_)) {
2081 debug_assert_eq!(to_intersect.len(), 0);
2082 debug_assert_eq!(bind.len(), 0);
2083 if group.len() > 0 {
2084 updates.finish_frame();
2085 }
2086 drain_updates!(updates);
2087 } else {
2088 for vals in group.iter() {
2091 debug_assert!(vals.len() == bind.len()); for (col, var) in bind.iter() {
2093 updates.push_binding(*var, vals[col.index()]);
2094 }
2095 if prune_probers(&mut updates, None, Some(vals)) {
2096 updates.finish_frame();
2097 } else {
2098 updates.rollback();
2099 }
2100 if updates.frames() >= chunk_size {
2101 drain_updates!(updates);
2102 }
2103 }
2104 }
2105 }
2106 }
2107 }
2108
2109 drain_updates!(updates);
2110 for (spec, prober) in to_intersect.iter().zip(probers) {
2111 binding_info.move_back(spec.0.to_index.atom, prober);
2112 }
2113 }
2114 }
2115 }
2116}
2117
2118const LOCAL_ACTION_BATCH_SIZE: usize = 128;
2119
2120trait ActionBuffer<'state, A: NumericId>: Send {
2127 type AsLocal<'a>: ActionBuffer<'state, A>
2128 where
2129 'state: 'a;
2130
2131 fn push_bindings_factorized(
2134 &mut self,
2135 action: A,
2136 bindings: &mut DenseIdMap<Variable, Value>,
2137 binding_sets: &BindingSet,
2138 exec_state: &ExecutionState<'state>,
2139 ) {
2140 expand_binding_sets(self, action, bindings, binding_sets, 0, exec_state);
2141 }
2142
2143 fn push_bindings(
2151 &mut self,
2152 action: A,
2153 bindings: &DenseIdMap<Variable, Value>,
2154 to_exec_state: impl FnMut() -> ExecutionState<'state>,
2155 );
2156
2157 fn flush(&mut self, exec_state: &mut ExecutionState);
2159
2160 fn recur<'local>(
2169 &mut self,
2170 local: BorrowedLocalState<'local>,
2171 to_exec_state: impl FnMut() -> ExecutionState<'state> + Send + 'state,
2172 work: impl for<'a> FnOnce(BorrowedLocalState<'a>, &mut Self::AsLocal<'a>) + Send + 'state,
2173 );
2174
2175 fn morsel_size(&mut self, _level: usize, _total: usize) -> usize {
2181 256
2182 }
2183
2184 fn supports_parallel_drain(&self) -> bool {
2189 true
2190 }
2191}
2192
2193struct InPlaceActionBuffer<'a> {
2196 rule_set: &'a RuleSet,
2197 match_counter: &'a MatchCounter,
2198 batches: DenseIdMap<ActionId, ActionState>,
2199}
2200
2201impl<'a, 'outer: 'a> ActionBuffer<'a, ActionId> for InPlaceActionBuffer<'outer> {
2202 type AsLocal<'b>
2203 = Self
2204 where
2205 'a: 'b;
2206
2207 fn push_bindings(
2208 &mut self,
2209 action: ActionId,
2210 bindings: &DenseIdMap<Variable, Value>,
2211 mut to_exec_state: impl FnMut() -> ExecutionState<'a>,
2212 ) {
2213 let action_state = self
2214 .batches
2215 .get_or_insert(action, || ActionState::new(LOCAL_ACTION_BATCH_SIZE));
2216 action_state.n_runs += 1;
2217 action_state.len += 1;
2218 let action_info = &self.rule_set.actions[action];
2219 unsafe {
2222 action_state.bindings.push(bindings, &action_info.used_vars);
2223 }
2224 if action_state.len >= LOCAL_ACTION_BATCH_SIZE {
2225 let mut state = to_exec_state();
2226 let succeeded = state.run_instrs(&action_info.instrs, &mut action_state.bindings);
2227 action_state.bindings.clear();
2228 self.match_counter.inc_matches(action, succeeded);
2229 action_state.len = 0;
2230 }
2231 }
2232
2233 fn flush(&mut self, exec_state: &mut ExecutionState) {
2234 flush_action_states(
2235 exec_state,
2236 &mut self.batches,
2237 self.rule_set,
2238 self.match_counter,
2239 );
2240 }
2241
2242 fn recur<'local>(
2243 &mut self,
2244 local: BorrowedLocalState<'local>,
2245 _to_exec_state: impl FnMut() -> ExecutionState<'a> + Send + 'a,
2246 work: impl for<'b> FnOnce(BorrowedLocalState<'b>, &mut Self) + Send + 'a,
2247 ) {
2248 work(local, self)
2249 }
2250
2251 fn supports_parallel_drain(&self) -> bool {
2252 false
2253 }
2254}
2255
2256struct ScopedActionBuffer<'inner, 'scope> {
2258 scope: &'inner Scope<'scope>,
2259 rule_set: &'scope RuleSet,
2260 match_counter: Arc<MatchCounter>,
2261 batches: DenseIdMap<ActionId, ActionState>,
2262 needs_flush: bool,
2263}
2264
2265impl<'inner, 'scope> ScopedActionBuffer<'inner, 'scope> {
2266 fn new(
2267 scope: &'inner Scope<'scope>,
2268 rule_set: &'scope RuleSet,
2269 match_counter: Arc<MatchCounter>,
2270 ) -> Self {
2271 Self {
2272 scope,
2273 rule_set,
2274 batches: Default::default(),
2275 match_counter,
2276 needs_flush: false,
2277 }
2278 }
2279}
2280
2281impl<'scope> ActionBuffer<'scope, ActionId> for ScopedActionBuffer<'_, 'scope> {
2282 type AsLocal<'a>
2283 = ScopedActionBuffer<'a, 'scope>
2284 where
2285 'scope: 'a;
2286 fn push_bindings(
2287 &mut self,
2288 action: ActionId,
2289 bindings: &DenseIdMap<Variable, Value>,
2290 mut to_exec_state: impl FnMut() -> ExecutionState<'scope>,
2291 ) {
2292 self.needs_flush = true;
2293 let batch_size = action_batch_size();
2294 let action_state = self
2295 .batches
2296 .get_or_insert(action, || ActionState::new(batch_size));
2297 action_state.n_runs += 1;
2298 action_state.len += 1;
2299 let action_info = &self.rule_set.actions[action];
2300 unsafe {
2303 action_state.bindings.push(bindings, &action_info.used_vars);
2304 }
2305 if action_state.len >= batch_size {
2306 let mut state = to_exec_state();
2307 let mut bindings = mem::replace(&mut action_state.bindings, Bindings::new(batch_size));
2308 action_state.len = 0;
2309 let match_counter = self.match_counter.clone();
2310 self.scope.spawn(move |_| {
2311 let succeeded = state.run_instrs(&action_info.instrs, &mut bindings);
2312 match_counter.inc_matches(action, succeeded);
2313 });
2314 }
2315 }
2316
2317 fn flush(&mut self, exec_state: &mut ExecutionState) {
2318 flush_action_states(
2319 exec_state,
2320 &mut self.batches,
2321 self.rule_set,
2322 self.match_counter.as_ref(),
2323 );
2324 self.needs_flush = false;
2325 }
2326 fn recur<'local>(
2327 &mut self,
2328 mut local: BorrowedLocalState<'local>,
2329 mut to_exec_state: impl FnMut() -> ExecutionState<'scope> + Send + 'scope,
2330 work: impl for<'a> FnOnce(BorrowedLocalState<'a>, &mut ScopedActionBuffer<'a, 'scope>)
2331 + Send
2332 + 'scope,
2333 ) {
2334 let rule_set = self.rule_set;
2335 let match_counter = self.match_counter.clone();
2336 let mut inner = local.clone_state();
2337 self.scope.spawn(move |scope| {
2338 let mut buf: ScopedActionBuffer<'_, 'scope> = ScopedActionBuffer {
2339 scope,
2340 rule_set,
2341 match_counter,
2342 needs_flush: false,
2343 batches: Default::default(),
2344 };
2345 work(inner.borrow_mut(), &mut buf);
2346 if buf.needs_flush {
2347 flush_action_states(
2348 &mut to_exec_state(),
2349 &mut buf.batches,
2350 buf.rule_set,
2351 buf.match_counter.as_ref(),
2352 );
2353 }
2354 });
2355 }
2356
2357 fn morsel_size(&mut self, _level: usize, _total: usize) -> usize {
2358 match _level {
2360 0 if _total > 2 => 32,
2361 _ => 256,
2362 }
2363 }
2364}
2365
2366fn expand_binding_sets<'state, A: NumericId, BUF: ActionBuffer<'state, A> + ?Sized>(
2367 action_buf: &mut BUF,
2368 action: A,
2369 bindings: &mut DenseIdMap<Variable, Value>,
2370 binding_sets: &BindingSet,
2371 idx: usize,
2372 exec_state: &ExecutionState<'state>,
2373) {
2374 if exec_state.should_stop() {
2375 return;
2376 }
2377 if idx >= binding_sets.len() {
2378 action_buf.push_bindings(action, bindings, || exec_state.clone());
2379 return;
2380 }
2381 if idx + 1 == binding_sets.len() {
2382 let (vars, buf) = &binding_sets[idx];
2383 for (_, row) in buf.iter() {
2384 if exec_state.should_stop() {
2385 return;
2386 }
2387 for (var, val) in vars.iter().zip(row.iter()) {
2388 bindings.insert(*var, *val);
2389 }
2390 action_buf.push_bindings(action, bindings, || exec_state.clone());
2391 }
2392 return;
2393 }
2394 let (vars, buf) = &binding_sets[idx];
2395 for (_, row) in buf.iter() {
2396 for (var, val) in vars.iter().zip(row.iter()) {
2397 bindings.insert(*var, *val);
2398 }
2399 expand_binding_sets(
2400 action_buf,
2401 action,
2402 bindings,
2403 binding_sets,
2404 idx + 1,
2405 exec_state,
2406 );
2407 }
2408}
2409
2410fn flush_action_states(
2411 exec_state: &mut ExecutionState,
2412 actions: &mut DenseIdMap<ActionId, ActionState>,
2413 rule_set: &RuleSet,
2414 match_counter: &MatchCounter,
2415) {
2416 for (action, ActionState { bindings, len, .. }) in actions.iter_mut() {
2417 if *len > 0 {
2418 let succeeded = exec_state.run_instrs(&rule_set.actions[action].instrs, bindings);
2419 bindings.clear();
2420 match_counter.inc_matches(action, succeeded);
2421 *len = 0;
2422 }
2423 }
2424}
2425
2426struct InPlaceMaterializer<'a> {
2427 specs: &'a DenseIdMap<MatId, MatSpec>,
2428 materializations: DenseIdMap<MatId, IndexMap<Vec<Value>, RowBuffer>>,
2429 scratch_key: Vec<Value>,
2430 scratch_val: Vec<Value>,
2431}
2432
2433impl<'a> ActionBuffer<'a, MatId> for InPlaceMaterializer<'a> {
2434 type AsLocal<'b>
2435 = Self
2436 where
2437 'a: 'b;
2438
2439 fn push_bindings(
2440 &mut self,
2441 mat_id: MatId,
2442 bindings: &DenseIdMap<Variable, Value>,
2443 _to_exec_state: impl FnMut() -> ExecutionState<'a>,
2444 ) {
2445 let mat = self
2446 .materializations
2447 .get_mut(mat_id)
2448 .expect("invalid mat id");
2449 let spec = self.specs.get(mat_id).expect("invalid mat id");
2450 self.scratch_key.clear();
2451 for key in spec.msg_vars.iter().map(|var| bindings[*var]) {
2452 self.scratch_key.push(key);
2453 }
2454 self.scratch_val.clear();
2455 for val in spec.val_vars.iter().map(|var| bindings[*var]) {
2456 self.scratch_val.push(val);
2457 }
2458 if self.scratch_val.is_empty() {
2459 self.scratch_val.push(Value::stale());
2460 }
2461 if let Some(buffer) = mat.get_mut(&self.scratch_key) {
2462 buffer.add_row(&self.scratch_val);
2463 } else {
2464 let mut buffer = RowBuffer::new(usize::max(spec.val_vars.len(), 1));
2465 buffer.add_row(&self.scratch_val);
2466 mat.insert(self.scratch_key.clone(), buffer);
2467 }
2468 }
2469
2470 fn flush(&mut self, _exec_state: &mut ExecutionState) {
2471 }
2473
2474 fn recur<'local>(
2475 &mut self,
2476 local: BorrowedLocalState<'local>,
2477 _to_exec_state: impl FnMut() -> ExecutionState<'a> + Send + 'a,
2478 work: impl for<'b> FnOnce(BorrowedLocalState<'b>, &mut Self) + Send + 'a,
2479 ) {
2480 work(local, self)
2481 }
2482
2483 fn supports_parallel_drain(&self) -> bool {
2484 false
2485 }
2486}
2487
2488struct ScopedMaterializer<'inner, 'scope> {
2489 scope: &'inner Scope<'scope>,
2490 specs: Arc<DenseIdMap<MatId, MatSpec>>,
2491 materializations: Arc<DenseIdMap<MatId, Arc<DashMap<Vec<Value>, RowBuffer>>>>,
2492 scratch_key: Vec<Value>,
2493 scratch_val: Vec<Value>,
2494}
2495impl<'scope> ActionBuffer<'scope, MatId> for ScopedMaterializer<'_, 'scope> {
2496 type AsLocal<'a>
2497 = ScopedMaterializer<'a, 'scope>
2498 where
2499 'scope: 'a;
2500
2501 fn push_bindings(
2502 &mut self,
2503 mat_id: MatId,
2504 bindings: &DenseIdMap<Variable, Value>,
2505 _to_exec_state: impl FnMut() -> ExecutionState<'scope>,
2506 ) {
2507 let mat = self.materializations.get(mat_id).expect("invalid mat id");
2508 let spec = self.specs.get(mat_id).expect("invalid mat id");
2509 self.scratch_key.clear();
2510 for key in spec.msg_vars.iter().map(|var| bindings[*var]) {
2511 self.scratch_key.push(key);
2512 }
2513 self.scratch_val.clear();
2514 for val in spec.val_vars.iter().map(|var| bindings[*var]) {
2515 self.scratch_val.push(val);
2516 }
2517 if self.scratch_val.is_empty() {
2518 self.scratch_val.push(Value::stale());
2519 }
2520 let key = self.scratch_key.clone();
2521 match mat.entry(key) {
2522 Entry::Occupied(mut occ) => {
2523 occ.get_mut().add_row(&self.scratch_val);
2524 }
2525 Entry::Vacant(vac) => {
2526 let mut buffer = RowBuffer::new(usize::max(spec.val_vars.len(), 1));
2527 buffer.add_row(&self.scratch_val);
2528 vac.insert(buffer);
2529 }
2530 }
2531 }
2532
2533 fn flush(&mut self, _exec_state: &mut ExecutionState) {
2534 }
2536
2537 fn recur<'local>(
2538 &mut self,
2539 mut local: BorrowedLocalState<'local>,
2540 _to_exec_state: impl FnMut() -> ExecutionState<'scope> + Send + 'scope,
2541 work: impl for<'a> FnOnce(BorrowedLocalState<'a>, &mut ScopedMaterializer<'a, 'scope>)
2542 + Send
2543 + 'scope,
2544 ) {
2545 let scope = self.scope;
2546 let specs = self.specs.clone();
2547 let materializations = self.materializations.clone();
2548 let mut inner = local.clone_state();
2549 scope.spawn(move |scope| {
2550 let mut buf: ScopedMaterializer<'_, 'scope> = ScopedMaterializer {
2551 scope,
2552 specs,
2553 materializations: materializations.clone(),
2554 scratch_key: Vec::new(),
2555 scratch_val: Vec::new(),
2556 };
2557 work(inner.borrow_mut(), &mut buf);
2558 });
2559 }
2560}
2561
2562struct MatchCounter {
2563 matches: IdVec<ActionId, CachePadded<AtomicUsize>>,
2564}
2565
2566impl MatchCounter {
2567 fn new(n_ids: usize) -> Self {
2568 let mut matches = IdVec::with_capacity(n_ids);
2569 matches.resize_with(n_ids, || CachePadded::new(AtomicUsize::new(0)));
2570 Self { matches }
2571 }
2572
2573 fn inc_matches(&self, action: ActionId, by: usize) {
2574 self.matches[action].fetch_add(by, std::sync::atomic::Ordering::Relaxed);
2575 }
2576 fn read_matches(&self, action: ActionId) -> usize {
2577 self.matches[action].load(std::sync::atomic::Ordering::Acquire)
2578 }
2579}
2580
2581fn estimate_size(join_stage: &JoinStage, binding_info: &BindingInfo) -> usize {
2582 match join_stage {
2583 JoinStage::Intersect { scans, .. } => scans
2584 .iter()
2585 .map(|scan| binding_info.subsets[scan.atom].size())
2586 .min()
2587 .unwrap_or(0),
2588 JoinStage::FusedIntersect { cover, .. } => binding_info.subsets[cover.to_index.atom].size(),
2589 JoinStage::FusedIntersectMat { cover, .. } => binding_info.materializations[*cover].len(), }
2591}
2592
2593fn num_intersected_rels(join_stage: &JoinStage) -> i32 {
2594 match join_stage {
2595 JoinStage::Intersect { scans, .. } => scans.len() as i32,
2596 JoinStage::FusedIntersect { to_intersect, .. } => to_intersect.len() as i32 + 1,
2597 JoinStage::FusedIntersectMat { to_intersect, .. } => to_intersect.len() as i32,
2598 }
2599}
2600
2601fn sort_plan_by_size(
2602 order: &mut InstrOrder,
2603 leaf_scans: &mut LeafScans,
2604 start: usize,
2605 instrs: &[JoinStage],
2606 binding_info: &mut BindingInfo,
2607) {
2608 let mut last_pos = start;
2609 for i in start..instrs.len() {
2610 if matches!(
2611 &instrs[i],
2612 JoinStage::FusedIntersectMat {
2614 mode: MatScanMode::Lookup(_) | MatScanMode::Value(_) | MatScanMode::Full,
2615 ..
2616 }
2617 ) {
2618 sort_plan_by_size_inner(order, last_pos..i, instrs, binding_info);
2619 last_pos = i + 1;
2620 }
2621 }
2622 sort_plan_by_size_inner(order, last_pos..instrs.len(), instrs, binding_info);
2623 recompute_leaf_scans(order, leaf_scans, instrs, start);
2624}
2625
2626fn recompute_leaf_scans(
2633 order: &InstrOrder,
2634 leaf_scans: &mut LeafScans,
2635 instrs: &[JoinStage],
2636 start: usize,
2637) {
2638 for i in start..order.len() {
2639 let stage_idx = order.get(i);
2640 let (cover_atom, bind_vars) = match &instrs[stage_idx] {
2641 JoinStage::FusedIntersect {
2642 cover,
2643 bind,
2644 to_intersect,
2645 } if to_intersect.is_empty() => {
2646 let vars: SmallVec<[Variable; 4]> = bind.iter().map(|(_, v)| *v).collect();
2647 (Some(cover.to_index.atom), vars)
2648 }
2649 JoinStage::FusedIntersectMat {
2650 mode,
2651 bind,
2652 to_intersect,
2653 ..
2654 } if to_intersect.is_empty()
2655 && matches!(
2656 mode,
2657 MatScanMode::Full | MatScanMode::KeyOnly | MatScanMode::Value(_)
2658 ) =>
2659 {
2660 let vars: SmallVec<[Variable; 4]> = bind.iter().map(|(_, v)| *v).collect();
2661 (None, vars)
2662 }
2663 _ => {
2664 leaf_scans[i] = false;
2665 continue;
2666 }
2667 };
2668 let mut blocked = false;
2669 for j in (i + 1)..order.len() {
2670 match &instrs[order.get(j)] {
2671 JoinStage::Intersect { scans, .. } => {
2672 if let Some(ca) = cover_atom
2673 && scans.iter().any(|scan| scan.atom == ca)
2674 {
2675 blocked = true;
2676 break;
2677 }
2678 }
2679 JoinStage::FusedIntersect {
2680 cover,
2681 to_intersect,
2682 ..
2683 } => {
2684 if let Some(ca) = cover_atom
2685 && (cover.to_index.atom == ca
2686 || to_intersect.iter().any(|(s, _)| s.to_index.atom == ca))
2687 {
2688 blocked = true;
2689 break;
2690 }
2691 }
2692 JoinStage::FusedIntersectMat {
2693 mode, to_intersect, ..
2694 } => {
2695 if let Some(ca) = cover_atom
2696 && to_intersect.iter().any(|(s, _)| s.to_index.atom == ca)
2697 {
2698 blocked = true;
2699 break;
2700 }
2701 if let MatScanMode::Value(vars) | MatScanMode::Lookup(vars) = mode
2702 && vars.iter().any(|v| bind_vars.contains(v))
2703 {
2704 blocked = true;
2705 break;
2706 }
2707 }
2708 }
2709 }
2710 leaf_scans[i] = !blocked;
2711 }
2712}
2713
2714fn sort_plan_by_size_inner(
2715 order: &mut InstrOrder,
2716 range: Range<usize>,
2717 instrs: &[JoinStage],
2718 binding_info: &mut BindingInfo,
2719) {
2720 if range.len() <= 1 {
2722 return;
2723 }
2724 let mut times_refined = with_pool_set(|ps| ps.get::<DenseIdMap<AtomId, i64>>());
2726
2727 for ins in instrs[..range.start].iter() {
2729 match ins {
2730 JoinStage::Intersect { scans, .. } => scans.iter().for_each(|scan| {
2731 *times_refined.get_or_default(scan.atom) += 1;
2732 }),
2733 JoinStage::FusedIntersect {
2734 cover,
2735 to_intersect,
2736 ..
2737 } => {
2738 *times_refined.get_or_default(cover.to_index.atom) +=
2739 cover.to_index.vars.len() as i64;
2740 to_intersect.iter().for_each(|(spec, _)| {
2741 *times_refined.get_or_default(spec.to_index.atom) +=
2742 spec.to_index.vars.len() as i64;
2743 });
2744 }
2745 JoinStage::FusedIntersectMat { to_intersect, .. } => {
2746 to_intersect.iter().for_each(|(spec, _)| {
2747 *times_refined.get_or_default(spec.to_index.atom) +=
2748 spec.to_index.vars.len() as i64;
2749 });
2750 }
2751 }
2752 }
2753
2754 let key_fn = |join_stage: &JoinStage,
2764 binding_info: &BindingInfo,
2765 times_refined: &DenseIdMap<AtomId, i64>| {
2766 let refine = match join_stage {
2767 JoinStage::Intersect { scans, .. } => scans
2768 .iter()
2769 .map(|scan| times_refined.get(scan.atom).copied().unwrap_or_default())
2770 .max()
2771 .unwrap(),
2772 JoinStage::FusedIntersect { cover, .. } => times_refined
2773 .get(cover.to_index.atom)
2774 .copied()
2775 .unwrap_or_default(),
2776 JoinStage::FusedIntersectMat { bind, .. } => bind.len() as _,
2777 };
2778 (
2779 -refine,
2780 estimate_size(join_stage, binding_info),
2781 -num_intersected_rels(join_stage),
2782 )
2783 };
2784
2785 for i in range.clone() {
2786 let mut key_i = key_fn(&instrs[order.get(i)], binding_info, ×_refined);
2787 for j in (i + 1)..range.end {
2788 let key_j = key_fn(&instrs[order.get(j)], binding_info, ×_refined);
2789 if key_j < key_i {
2790 order.data.swap(i, j);
2791 key_i = key_j;
2792 }
2793 }
2794 match &instrs[order.get(i)] {
2796 JoinStage::Intersect { scans, .. } => scans.iter().for_each(|scan| {
2797 *times_refined.get_or_default(scan.atom) += 1;
2798 }),
2799 JoinStage::FusedIntersect {
2800 cover,
2801 to_intersect,
2802 ..
2803 } => {
2804 *times_refined.get_or_default(cover.to_index.atom) +=
2805 cover.to_index.vars.len() as i64;
2806
2807 to_intersect.iter().for_each(|(spec, _)| {
2808 *times_refined.get_or_default(spec.to_index.atom) +=
2809 spec.to_index.vars.len() as i64;
2810 });
2811 }
2812 JoinStage::FusedIntersectMat { to_intersect, .. } => {
2813 to_intersect.iter().for_each(|(spec, _)| {
2814 *times_refined.get_or_default(spec.to_index.atom) +=
2815 spec.to_index.vars.len() as i64;
2816 });
2817 }
2818 }
2819 }
2820}
2821
2822#[derive(Debug, Clone, PartialEq, Eq)]
2823struct InstrOrder {
2824 data: SmallVec<[u16; 8]>,
2825}
2826
2827impl InstrOrder {
2828 fn new() -> Self {
2829 InstrOrder {
2830 data: SmallVec::new(),
2831 }
2832 }
2833
2834 fn from_iter(range: impl Iterator<Item = usize>) -> InstrOrder {
2835 let mut res = InstrOrder::new();
2836 res.data
2837 .extend(range.map(|x| u16::try_from(x).expect("too many instructions")));
2838 res
2839 }
2840
2841 fn get(&self, idx: usize) -> usize {
2842 self.data[idx] as usize
2843 }
2844 fn len(&self) -> usize {
2845 self.data.len()
2846 }
2847}
2848
2849type LeafScans = SmallVec<[bool; 8]>;
2853
2854struct BorrowedLocalState<'a> {
2855 instr_order: &'a mut InstrOrder,
2856 leaf_scans: &'a mut LeafScans,
2857 binding_info: &'a mut BindingInfo,
2858 updates: &'a mut FrameUpdates,
2859}
2860
2861impl BorrowedLocalState<'_> {
2862 fn clone_state(&mut self) -> LocalState {
2863 LocalState {
2864 instr_order: self.instr_order.clone(),
2865 leaf_scans: self.leaf_scans.clone(),
2866 binding_info: self.binding_info.clone(),
2867 updates: std::mem::take(self.updates),
2868 }
2869 }
2870}
2871
2872struct LocalState {
2873 instr_order: InstrOrder,
2874 leaf_scans: LeafScans,
2875 binding_info: BindingInfo,
2876 updates: FrameUpdates,
2877}
2878
2879impl LocalState {
2880 fn borrow_mut<'a>(&'a mut self) -> BorrowedLocalState<'a> {
2881 BorrowedLocalState {
2882 instr_order: &mut self.instr_order,
2883 leaf_scans: &mut self.leaf_scans,
2884 binding_info: &mut self.binding_info,
2885 updates: &mut self.updates,
2886 }
2887 }
2888}