1use std::collections::BTreeSet;
9
10use corium_core::IndexOrder;
11
12use crate::ast::{Clause, Term, Var, clause_vars};
13use crate::exec::ExecCtx;
14
15#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub struct ScanChoice {
18 pub order: IndexOrder,
20 pub prefix_len: usize,
22}
23
24#[must_use]
32#[allow(clippy::fn_params_excessive_bools)]
33pub fn choose_index(
34 e_bound: bool,
35 a_bound: bool,
36 v_bound: bool,
37 avet_ok: bool,
38 v_is_ref: bool,
39) -> ScanChoice {
40 if e_bound {
41 let prefix_len = 1 + usize::from(a_bound) + usize::from(a_bound && v_bound);
42 return ScanChoice {
43 order: IndexOrder::Eavt,
44 prefix_len,
45 };
46 }
47 if a_bound {
48 if v_bound && avet_ok {
49 return ScanChoice {
50 order: IndexOrder::Avet,
51 prefix_len: 2,
52 };
53 }
54 if v_bound && v_is_ref {
57 return ScanChoice {
58 order: IndexOrder::Vaet,
59 prefix_len: 2,
60 };
61 }
62 return ScanChoice {
63 order: IndexOrder::Aevt,
64 prefix_len: 1,
65 };
66 }
67 if v_bound && v_is_ref {
68 return ScanChoice {
69 order: IndexOrder::Vaet,
70 prefix_len: 1,
71 };
72 }
73 ScanChoice {
74 order: IndexOrder::Eavt,
75 prefix_len: 0,
76 }
77}
78
79type Cost = (u8, usize);
85
86const CLASS_FILTER: u8 = 0;
87const CLASS_PATTERN: u8 = 1;
88const CLASS_RULE: u8 = 2;
89const CLASS_SUBPLAN: u8 = 3;
90
91#[must_use]
97pub fn order_clauses(
98 clauses: &[Clause],
99 initially_bound: &BTreeSet<Var>,
100 ctx: &ExecCtx<'_>,
101) -> Vec<usize> {
102 let mut bound = initially_bound.clone();
103 let mut remaining: Vec<usize> = (0..clauses.len()).collect();
104 let mut ordered = Vec::with_capacity(clauses.len());
105 while !remaining.is_empty() {
106 let best = remaining
107 .iter()
108 .enumerate()
109 .min_by_key(|(position, index)| (cost(&clauses[**index], &bound, ctx), *position))
110 .map_or(0, |(position, _)| position);
111 let index = remaining.remove(best);
112 bound.extend(clause_vars(&clauses[index]));
113 ordered.push(index);
114 }
115 ordered
116}
117
118fn term_bound(term: &Term, bound: &BTreeSet<Var>) -> bool {
119 match term {
120 Term::Const(_) => true,
121 Term::Var(v) => bound.contains(v),
122 Term::Blank => false,
123 }
124}
125
126fn cost(clause: &Clause, bound: &BTreeSet<Var>, ctx: &ExecCtx<'_>) -> Cost {
127 match clause {
128 Clause::Pattern(pattern) => {
129 let Some(db) = ctx.db(&pattern.src) else {
130 return (CLASS_PATTERN, usize::MAX);
131 };
132 let stats = db.planner_stats();
133 let a = match &pattern.a {
134 Term::Const(form) => ctx.attr_of(db, form),
135 _ => None,
136 };
137 let a_known = matches!(&pattern.a, Term::Const(_))
138 || matches!(&pattern.a, Term::Var(v) if bound.contains(v));
139 let e_known = term_bound(&pattern.e, bound);
140 let v_known = term_bound(&pattern.v, bound);
141 if a_known && a.is_none() && matches!(&pattern.a, Term::Var(_)) {
142 let estimate = (stats.total_datoms / stats.per_attr.len().max(1)).max(1);
144 return (CLASS_PATTERN, estimate);
145 }
146 (CLASS_PATTERN, stats.estimate(e_known, a, v_known))
147 }
148 Clause::Pred { args, .. } | Clause::Fn { args, .. } => {
149 let runnable = args
150 .iter()
151 .all(|t| term_bound(t, bound) || matches!(t, Term::Blank));
152 if runnable {
153 (CLASS_FILTER, 0)
154 } else {
155 (CLASS_SUBPLAN, 0)
156 }
157 }
158 Clause::RuleCall { .. } => (CLASS_RULE, 0),
159 Clause::Not { .. } | Clause::Or { .. } => (CLASS_SUBPLAN, 0),
160 }
161}