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