1use std::cell::{Cell, RefCell};
10use std::collections::{BTreeMap, BTreeSet};
11
12use corium_core::{AttrId, EntityId, IndexOrder, Value, ValueType};
13use corium_db::{Db, avet_covered, key_prefix};
14
15use crate::QueryError;
16use crate::ast::{BindTarget, Binding, Clause, Pattern, RuleDef, Term, Var};
17use crate::builtins::{self, CallResult};
18use crate::edn::Edn;
19use crate::plan::{ScanChoice, choose_index, order_clauses};
20
21pub type Frame = BTreeMap<Var, Value>;
23
24pub const UNKNOWN_KEYWORD: u64 = u64::MAX;
27
28type RuleKey = (String, Vec<Option<Value>>);
29
30#[derive(Default)]
31struct RuleState {
32 partial: BTreeMap<RuleKey, BTreeSet<Vec<Value>>>,
33 changed: bool,
34}
35
36pub struct ExecCtx<'a> {
38 dbs: BTreeMap<String, &'a Db>,
39 rules: BTreeMap<String, Vec<RuleDef>>,
40 scanned: Cell<usize>,
41 fuel: Cell<u64>,
42 rule_state: RefCell<RuleState>,
43 extern_call: Option<crate::ExternCall>,
44}
45
46impl<'a> ExecCtx<'a> {
47 #[must_use]
49 pub fn new(dbs: BTreeMap<String, &'a Db>, rule_defs: Vec<RuleDef>) -> Self {
50 let mut rules: BTreeMap<String, Vec<RuleDef>> = BTreeMap::new();
51 for def in rule_defs {
52 rules.entry(def.name.clone()).or_default().push(def);
53 }
54 Self {
55 dbs,
56 rules,
57 scanned: Cell::new(0),
58 fuel: Cell::new(u64::MAX),
59 rule_state: RefCell::new(RuleState::default()),
60 extern_call: None,
61 }
62 }
63
64 pub fn set_fuel(&self, fuel: u64) {
66 self.fuel.set(fuel);
67 }
68
69 pub fn set_extern_call(&mut self, extern_call: crate::ExternCall) {
71 self.extern_call = Some(extern_call);
72 }
73
74 fn call(&self, name: &str, values: &[Value]) -> Result<CallResult, QueryError> {
77 if builtins::is_native(name) {
78 return builtins::call(name, values);
79 }
80 if let Some(extern_call) = &self.extern_call
81 && let Some(result) = extern_call(name, values)
82 {
83 return result;
84 }
85 builtins::call(name, values)
86 }
87
88 #[must_use]
90 pub fn scanned(&self) -> usize {
91 self.scanned.get()
92 }
93
94 #[must_use]
96 pub fn db(&self, src: &str) -> Option<&'a Db> {
97 self.dbs.get(src).copied()
98 }
99
100 #[must_use]
102 pub fn default_db(&self) -> Option<&'a Db> {
103 self.db(crate::ast::DEFAULT_SRC)
104 .or_else(|| self.dbs.values().next().copied())
105 }
106
107 #[must_use]
109 pub fn attr_of(&self, db: &Db, form: &Edn) -> Option<AttrId> {
110 match form {
111 Edn::Keyword(k) => db.idents().entid(k),
112 Edn::Long(n) => u64::try_from(*n).ok().map(EntityId::from_raw),
113 Edn::Tagged(tag, value) if tag == "eid" => match value.as_ref() {
114 Edn::Long(n) => u64::try_from(*n).ok().map(EntityId::from_raw),
115 _ => None,
116 },
117 _ => None,
118 }
119 }
120
121 fn spend(&self, amount: u64) -> Result<(), QueryError> {
122 let fuel = self.fuel.get();
123 if fuel < amount {
124 return Err(QueryError::FuelExhausted);
125 }
126 self.fuel.set(fuel - amount);
127 Ok(())
128 }
129}
130
131#[must_use]
135pub fn const_value(db: &Db, form: &Edn) -> Option<Value> {
136 crate::boundary::edn_to_value(Some(db), form)
137}
138
139#[must_use]
142pub fn coerce_for_type(value: Value, value_type: ValueType) -> Value {
143 match (&value, value_type) {
144 (Value::Long(n), ValueType::Ref) => u64::try_from(*n)
145 .map(|n| Value::Ref(EntityId::from_raw(n)))
146 .unwrap_or(value),
147 (Value::Long(n), ValueType::Instant) => Value::Instant(*n),
148 #[allow(clippy::cast_precision_loss)]
149 (Value::Long(n), ValueType::Double) => Value::Double(corium_core::TotalF64(*n as f64)),
150 _ => value,
151 }
152}
153
154#[must_use]
156pub fn to_entity(value: &Value) -> Option<EntityId> {
157 match value {
158 Value::Ref(e) => Some(*e),
159 Value::Long(n) => u64::try_from(*n).ok().map(EntityId::from_raw),
160 _ => None,
161 }
162}
163
164#[must_use]
167pub fn value_match(actual: &Value, expected: &Value) -> bool {
168 if actual == expected {
169 return true;
170 }
171 match (actual, expected) {
172 (Value::Ref(e), Value::Long(n)) | (Value::Long(n), Value::Ref(e)) => {
173 u64::try_from(*n).is_ok_and(|n| e.raw() == n)
174 }
175 (Value::Instant(i), Value::Long(n)) | (Value::Long(n), Value::Instant(i)) => i == n,
176 _ => false,
177 }
178}
179
180pub fn eval_clauses(
186 ctx: &ExecCtx<'_>,
187 clauses: &[Clause],
188 frames: Vec<Frame>,
189 stack: &mut Vec<RuleKey>,
190) -> Result<Vec<Frame>, QueryError> {
191 let mut frame_iter = frames.iter();
194 let mut initially_bound: BTreeSet<Var> = frame_iter
195 .next()
196 .map(|frame| frame.keys().cloned().collect())
197 .unwrap_or_default();
198 for frame in frame_iter {
199 initially_bound.retain(|var| frame.contains_key(var));
200 }
201 let order = order_clauses(clauses, &initially_bound, ctx);
202 let mut frames = frames;
203 for index in order {
204 frames = eval_clause(ctx, &clauses[index], frames, stack)?;
205 frames.sort_unstable();
208 frames.dedup();
209 if frames.is_empty() {
210 return Ok(frames);
211 }
212 }
213 Ok(frames)
214}
215
216fn eval_clause(
217 ctx: &ExecCtx<'_>,
218 clause: &Clause,
219 frames: Vec<Frame>,
220 stack: &mut Vec<RuleKey>,
221) -> Result<Vec<Frame>, QueryError> {
222 match clause {
223 Clause::Pattern(pattern) => eval_pattern(ctx, pattern, frames),
224 Clause::Pred { name, args } => eval_pred(ctx, name, args, frames),
225 Clause::Fn {
226 name,
227 args,
228 binding,
229 } => eval_fn(ctx, name, args, binding, frames),
230 Clause::Not {
231 src: _,
232 vars,
233 clauses,
234 } => {
235 let mut kept = Vec::with_capacity(frames.len());
236 for frame in frames {
237 let seed = match vars {
238 Some(vars) => restrict(&frame, vars),
239 None => frame.clone(),
240 };
241 let sub = eval_clauses(ctx, clauses, vec![seed], stack)?;
242 if sub.is_empty() {
243 kept.push(frame);
244 }
245 }
246 Ok(kept)
247 }
248 Clause::Or {
249 src: _,
250 vars,
251 branches,
252 } => {
253 let mut out = Vec::with_capacity(frames.len().saturating_mul(branches.len().max(1)));
254 for frame in frames {
255 for branch in branches {
256 let seed = match vars {
257 Some(vars) => restrict(&frame, vars),
258 None => frame.clone(),
259 };
260 for result in eval_clauses(ctx, branch, vec![seed], stack)? {
261 let mut merged = frame.clone();
262 let mut consistent = true;
263 let exported: Vec<&Var> = match vars {
264 Some(vars) => vars.iter().collect(),
265 None => result.keys().collect(),
266 };
267 for var in exported {
268 if let Some(value) = result.get(var) {
269 match merged.get(var) {
270 Some(existing) if !value_match(existing, value) => {
271 consistent = false;
272 break;
273 }
274 Some(_) => {}
275 None => {
276 merged.insert(var.clone(), value.clone());
277 }
278 }
279 }
280 }
281 if consistent {
282 out.push(merged);
283 }
284 }
285 }
286 }
287 Ok(out)
288 }
289 Clause::RuleCall { name, args } => eval_rule_call(ctx, name, args, frames, stack),
290 }
291}
292
293fn restrict(frame: &Frame, vars: &[Var]) -> Frame {
294 vars.iter()
295 .filter_map(|var| frame.get(var).map(|value| (var.clone(), value.clone())))
296 .collect()
297}
298
299enum Spec {
302 Free(Option<Var>),
303 Bound(Value),
304 NoMatch,
305}
306
307fn resolve_term(
308 ctx: &ExecCtx<'_>,
309 db: &Db,
310 frame: &Frame,
311 term: &Term,
312 position: Position,
313 attr: Option<AttrId>,
314) -> Result<Spec, QueryError> {
315 let coerce = |value: Value| -> Value {
316 match (position, attr) {
317 (Position::V, Some(a)) => db.schema().get(a).map_or_else(
318 || value.clone(),
319 |meta| coerce_for_type(value.clone(), meta.value_type),
320 ),
321 _ => value,
322 }
323 };
324 match term {
325 Term::Blank => Ok(Spec::Free(None)),
326 Term::Var(var) => Ok(frame
327 .get(var)
328 .map_or(Spec::Free(Some(var.clone())), |value| {
329 Spec::Bound(coerce(value.clone()))
330 })),
331 Term::Const(form) => match position {
332 Position::E | Position::Tx => match entity_const(db, form)? {
333 Some(e) => Ok(Spec::Bound(Value::Ref(e))),
334 None => Ok(Spec::NoMatch),
335 },
336 Position::A => match ctx.attr_of(db, form) {
337 Some(a) => Ok(Spec::Bound(Value::Ref(a))),
338 None => match form {
339 Edn::Keyword(k) => Err(QueryError::UnknownIdent(k.clone())),
340 _ => Ok(Spec::NoMatch),
341 },
342 },
343 Position::Added => match form {
344 Edn::Bool(b) => Ok(Spec::Bound(Value::Bool(*b))),
345 _ => Ok(Spec::NoMatch),
346 },
347 Position::V => match const_value(db, form) {
348 Some(value) => Ok(Spec::Bound(coerce(value))),
349 None => match entity_const(db, form)? {
350 Some(e) => Ok(Spec::Bound(Value::Ref(e))),
351 None => Ok(Spec::NoMatch),
352 },
353 },
354 },
355 }
356}
357
358fn entity_const(db: &Db, form: &Edn) -> Result<Option<EntityId>, QueryError> {
361 match form {
362 Edn::Long(_) | Edn::Tagged(_, _) => match const_value(db, form) {
363 Some(Value::Ref(e)) => Ok(Some(e)),
364 Some(Value::Long(n)) => Ok(u64::try_from(n).ok().map(EntityId::from_raw)),
365 _ => Ok(None),
366 },
367 Edn::Keyword(k) => Ok(db.idents().entid(k)),
368 Edn::Vector(items) => {
369 let [attr_form, value_form] = items.as_slice() else {
370 return Ok(None);
371 };
372 let Some(attr_kw) = attr_form.as_keyword() else {
373 return Ok(None);
374 };
375 let attr = db
376 .idents()
377 .entid(attr_kw)
378 .ok_or_else(|| QueryError::UnknownIdent(attr_kw.clone()))?;
379 let Some(value) = const_value(db, value_form) else {
380 return Ok(None);
381 };
382 let value = db.schema().get(attr).map_or(value.clone(), |meta| {
383 coerce_for_type(value, meta.value_type)
384 });
385 Ok(db.lookup(attr, &value))
386 }
387 _ => Ok(None),
388 }
389}
390
391#[derive(Clone, Copy, Eq, PartialEq)]
392enum Position {
393 E,
394 A,
395 V,
396 Tx,
397 Added,
398}
399
400#[allow(clippy::too_many_lines)]
401fn eval_pattern(
402 ctx: &ExecCtx<'_>,
403 pattern: &Pattern,
404 frames: Vec<Frame>,
405) -> Result<Vec<Frame>, QueryError> {
406 let db = ctx
407 .db(&pattern.src)
408 .ok_or_else(|| QueryError::UnknownSource(pattern.src.clone()))?;
409 let mut out = Vec::with_capacity(frames.len());
410 for frame in frames {
411 let a_spec = resolve_term(ctx, db, &frame, &pattern.a, Position::A, None)?;
413 let attr = match &a_spec {
414 Spec::Bound(Value::Ref(a)) => Some(*a),
415 Spec::Bound(other) => to_entity(other),
416 _ => None,
417 };
418 let e_spec = resolve_term(ctx, db, &frame, &pattern.e, Position::E, None)?;
419 let v_spec = resolve_term(ctx, db, &frame, &pattern.v, Position::V, attr)?;
420 let tx_spec = resolve_term(ctx, db, &frame, &pattern.tx, Position::Tx, None)?;
421 let added_spec = resolve_term(ctx, db, &frame, &pattern.added, Position::Added, None)?;
422 if [&a_spec, &e_spec, &v_spec, &tx_spec, &added_spec]
423 .iter()
424 .any(|spec| matches!(spec, Spec::NoMatch))
425 {
426 continue;
427 }
428 let e = match &e_spec {
429 Spec::Bound(value) => to_entity(value),
430 _ => None,
431 };
432 let v = match &v_spec {
433 Spec::Bound(value) => Some(value.clone()),
434 _ => None,
435 };
436 let choice: ScanChoice = choose_index(
437 e.is_some(),
438 attr.is_some(),
439 v.is_some(),
440 attr.is_some_and(|a| avet_covered(db.schema(), a)),
441 matches!(v, Some(Value::Ref(_))),
442 );
443 let prefix = match choice.order {
444 IndexOrder::Eavt => key_prefix(
445 IndexOrder::Eavt,
446 e,
447 if choice.prefix_len >= 2 { attr } else { None },
448 if choice.prefix_len >= 3 {
449 v.as_ref()
450 } else {
451 None
452 },
453 ),
454 IndexOrder::Aevt => key_prefix(IndexOrder::Aevt, None, attr, None),
455 IndexOrder::Avet => key_prefix(IndexOrder::Avet, None, attr, v.as_ref()),
456 IndexOrder::Vaet => key_prefix(
457 IndexOrder::Vaet,
458 None,
459 if choice.prefix_len >= 2 { attr } else { None },
460 v.as_ref(),
461 ),
462 };
463 for datom in db.datoms_prefix(choice.order, &prefix) {
464 ctx.spend(1)?;
465 ctx.scanned.set(ctx.scanned.get() + 1);
466 let fields = [
467 (Position::E, Value::Ref(datom.e)),
468 (Position::A, Value::Ref(datom.a)),
469 (Position::V, datom.v.clone()),
470 (Position::Tx, Value::Ref(datom.tx)),
471 (Position::Added, Value::Bool(datom.added)),
472 ];
473 let specs = [&e_spec, &a_spec, &v_spec, &tx_spec, &added_spec];
474 let mut extended = frame.clone();
475 let mut matched = true;
476 for ((_, actual), spec) in fields.into_iter().zip(specs) {
477 match spec {
478 Spec::Bound(expected) => {
479 if !value_match(&actual, expected) {
480 matched = false;
481 break;
482 }
483 }
484 Spec::Free(Some(var)) => match extended.get(var) {
485 Some(existing) => {
486 if !value_match(&actual, existing) {
487 matched = false;
488 break;
489 }
490 }
491 None => {
492 extended.insert(var.clone(), actual);
493 }
494 },
495 Spec::Free(None) | Spec::NoMatch => {}
496 }
497 }
498 if matched {
499 out.push(extended);
500 }
501 }
502 }
503 Ok(out)
504}
505
506fn call_args(ctx: &ExecCtx<'_>, frame: &Frame, args: &[Term]) -> Result<Vec<Value>, QueryError> {
509 let db = ctx.default_db();
510 args.iter()
511 .map(|term| match term {
512 Term::Var(var) => frame
513 .get(var)
514 .cloned()
515 .ok_or_else(|| QueryError::Unbound(var.clone())),
516 Term::Blank => Err(QueryError::Parse("call arguments cannot be _".into())),
517 Term::Const(form) => db
518 .and_then(|db| const_value(db, form))
519 .ok_or_else(|| QueryError::Type(format!("bad call argument {form}"))),
520 })
521 .collect()
522}
523
524fn eval_pred(
525 ctx: &ExecCtx<'_>,
526 name: &str,
527 args: &[Term],
528 frames: Vec<Frame>,
529) -> Result<Vec<Frame>, QueryError> {
530 if name == "missing?" {
531 return eval_missing(ctx, args, frames);
532 }
533 let mut out = Vec::with_capacity(frames.len());
534 for frame in frames {
535 let values = call_args(ctx, &frame, args)?;
536 match ctx.call(name, &values)? {
537 CallResult::Test(true) | CallResult::Scalar(Value::Bool(true)) => {
538 out.push(frame);
539 }
540 CallResult::Test(false) | CallResult::Scalar(Value::Bool(false)) => {}
541 _ => {
542 return Err(QueryError::Type(format!("{name} is not a predicate")));
543 }
544 }
545 }
546 Ok(out)
547}
548
549fn eval_fn(
550 ctx: &ExecCtx<'_>,
551 name: &str,
552 args: &[Term],
553 binding: &Binding,
554 frames: Vec<Frame>,
555) -> Result<Vec<Frame>, QueryError> {
556 if name == "ground" {
557 return eval_ground(ctx, args, binding, frames);
558 }
559 if name == "get-else" {
560 return eval_get_else(ctx, args, binding, frames);
561 }
562 let mut out = Vec::with_capacity(frames.len());
563 for frame in frames {
564 let values = call_args(ctx, &frame, args)?;
565 let result = match ctx.call(name, &values)? {
566 CallResult::Test(truth) => CallResult::Scalar(Value::Bool(truth)),
567 other => other,
568 };
569 bind_result(&frame, binding, result, &mut out)?;
570 }
571 Ok(out)
572}
573
574fn bind_result(
575 frame: &Frame,
576 binding: &Binding,
577 result: CallResult,
578 out: &mut Vec<Frame>,
579) -> Result<(), QueryError> {
580 let scalar = |value: &Value, var: &Var, frame: &Frame| -> Option<Frame> {
581 let mut next = frame.clone();
582 match next.get(var) {
583 Some(existing) if !value_match(existing, value) => None,
584 Some(_) => Some(next),
585 None => {
586 next.insert(var.clone(), value.clone());
587 Some(next)
588 }
589 }
590 };
591 let bind_tuple = |values: &[Value], targets: &[BindTarget], frame: &Frame| -> Option<Frame> {
592 if values.len() != targets.len() {
593 return None;
594 }
595 let mut next = frame.clone();
596 for (value, target) in values.iter().zip(targets) {
597 match target {
598 BindTarget::Blank => {}
599 BindTarget::Var(var) => match next.get(var) {
600 Some(existing) if !value_match(existing, value) => return None,
601 Some(_) => {}
602 None => {
603 next.insert(var.clone(), value.clone());
604 }
605 },
606 }
607 }
608 Some(next)
609 };
610 match (binding, result) {
611 (Binding::Scalar(var), CallResult::Scalar(value)) => {
612 out.extend(scalar(&value, var, frame));
613 }
614 (Binding::Scalar(var), CallResult::Test(flag)) => {
615 out.extend(scalar(&Value::Bool(flag), var, frame));
616 }
617 (Binding::Coll(target), CallResult::Coll(values)) => match target {
618 BindTarget::Blank => {
619 if !values.is_empty() {
620 out.push(frame.clone());
621 }
622 }
623 BindTarget::Var(var) => {
624 for value in values {
625 out.extend(scalar(&value, var, frame));
626 }
627 }
628 },
629 (Binding::Tuple(targets), CallResult::Coll(values)) => {
630 out.extend(bind_tuple(&values, targets, frame));
631 }
632 (Binding::Rel(targets), CallResult::Rel(rows)) => {
633 for row in rows {
634 out.extend(bind_tuple(&row, targets, frame));
635 }
636 }
637 (_, result) => {
638 return Err(QueryError::Type(format!(
639 "binding form does not fit result {result:?}"
640 )));
641 }
642 }
643 Ok(())
644}
645
646fn eval_ground(
648 ctx: &ExecCtx<'_>,
649 args: &[Term],
650 binding: &Binding,
651 frames: Vec<Frame>,
652) -> Result<Vec<Frame>, QueryError> {
653 let db = ctx
654 .default_db()
655 .ok_or_else(|| QueryError::UnknownSource("$".into()))?;
656 let [Term::Const(form)] = args else {
657 return Err(QueryError::Arity("ground takes one constant".into()));
658 };
659 let value_of = |form: &Edn| -> Result<Value, QueryError> {
660 const_value(db, form).ok_or_else(|| QueryError::Type(format!("cannot ground {form}")))
661 };
662 let result = match (binding, form) {
663 (Binding::Coll(_) | Binding::Tuple(_), Edn::Vector(items) | Edn::List(items)) => {
664 CallResult::Coll(items.iter().map(value_of).collect::<Result<_, _>>()?)
665 }
666 (Binding::Rel(_), Edn::Vector(rows) | Edn::List(rows)) => CallResult::Rel(
667 rows.iter()
668 .map(|row| match row {
669 Edn::Vector(items) | Edn::List(items) => {
670 items.iter().map(value_of).collect::<Result<Vec<_>, _>>()
671 }
672 _ => Err(QueryError::Type("ground relation requires tuples".into())),
673 })
674 .collect::<Result<_, _>>()?,
675 ),
676 (Binding::Scalar(_), form) => CallResult::Scalar(value_of(form)?),
677 _ => return Err(QueryError::Type(format!("cannot ground {form}"))),
678 };
679 let mut out = Vec::with_capacity(frames.len());
680 for frame in frames {
681 bind_result(&frame, binding, result.clone(), &mut out)?;
682 }
683 Ok(out)
684}
685
686fn eval_get_else(
688 ctx: &ExecCtx<'_>,
689 args: &[Term],
690 binding: &Binding,
691 frames: Vec<Frame>,
692) -> Result<Vec<Frame>, QueryError> {
693 let (db, rest) = db_context_args(ctx, args)?;
694 let [entity_term, attr_term, default_term] = rest else {
695 return Err(QueryError::Arity(
696 "get-else takes a source, entity, attribute, and default".into(),
697 ));
698 };
699 let attr = attr_const(ctx, db, attr_term)?;
700 let value_type = db.schema().get(attr).map(|meta| meta.value_type);
701 let mut out = Vec::with_capacity(frames.len());
702 for frame in frames {
703 let entity = entity_arg(db, &frame, entity_term)?;
704 let value = entity
705 .and_then(|e| db.values(e, attr).into_iter().next())
706 .map_or_else(
707 || match default_term {
708 Term::Const(form) => {
709 let value = const_value(db, form).ok_or_else(|| {
710 QueryError::Type(format!("bad get-else default {form}"))
711 })?;
712 Ok(match value_type {
713 Some(t) => coerce_for_type(value, t),
714 None => value,
715 })
716 }
717 Term::Var(var) => frame
718 .get(var)
719 .cloned()
720 .ok_or_else(|| QueryError::Unbound(var.clone())),
721 Term::Blank => Err(QueryError::Parse("get-else default cannot be _".into())),
722 },
723 Ok,
724 )?;
725 bind_result(&frame, binding, CallResult::Scalar(value), &mut out)?;
726 }
727 Ok(out)
728}
729
730fn eval_missing(
732 ctx: &ExecCtx<'_>,
733 args: &[Term],
734 frames: Vec<Frame>,
735) -> Result<Vec<Frame>, QueryError> {
736 let (db, rest) = db_context_args(ctx, args)?;
737 let [entity_term, attr_term] = rest else {
738 return Err(QueryError::Arity(
739 "missing? takes a source, entity, and attribute".into(),
740 ));
741 };
742 let attr = attr_const(ctx, db, attr_term)?;
743 let mut out = Vec::with_capacity(frames.len());
744 for frame in frames {
745 let entity = entity_arg(db, &frame, entity_term)?;
746 let missing = entity.is_none_or(|e| db.values(e, attr).is_empty());
747 if missing {
748 out.push(frame);
749 }
750 }
751 Ok(out)
752}
753
754fn db_context_args<'a, 'b>(
755 ctx: &'b ExecCtx<'a>,
756 args: &'b [Term],
757) -> Result<(&'a Db, &'b [Term]), QueryError> {
758 match args.split_first() {
759 Some((Term::Const(Edn::Symbol(src)), rest)) if src.starts_with('$') => {
760 let db = ctx
761 .db(src)
762 .ok_or_else(|| QueryError::UnknownSource(src.clone()))?;
763 Ok((db, rest))
764 }
765 _ => {
766 let db = ctx
767 .default_db()
768 .ok_or_else(|| QueryError::UnknownSource("$".into()))?;
769 Ok((db, args))
770 }
771 }
772}
773
774fn attr_const(ctx: &ExecCtx<'_>, db: &Db, term: &Term) -> Result<AttrId, QueryError> {
775 match term {
776 Term::Const(form) => ctx.attr_of(db, form).ok_or_else(|| match form {
777 Edn::Keyword(k) => QueryError::UnknownIdent(k.clone()),
778 _ => QueryError::Type(format!("bad attribute argument {form}")),
779 }),
780 _ => Err(QueryError::Type(
781 "attribute argument must be a constant".into(),
782 )),
783 }
784}
785
786fn entity_arg(db: &Db, frame: &Frame, term: &Term) -> Result<Option<EntityId>, QueryError> {
787 match term {
788 Term::Var(var) => {
789 let value = frame
790 .get(var)
791 .ok_or_else(|| QueryError::Unbound(var.clone()))?;
792 Ok(to_entity(value))
793 }
794 Term::Const(form) => entity_const(db, form),
795 Term::Blank => Err(QueryError::Parse("entity argument cannot be _".into())),
796 }
797}
798
799fn eval_rule_call(
800 ctx: &ExecCtx<'_>,
801 name: &str,
802 args: &[Term],
803 frames: Vec<Frame>,
804 stack: &mut Vec<RuleKey>,
805) -> Result<Vec<Frame>, QueryError> {
806 let db = ctx.default_db();
807 let mut out = Vec::with_capacity(frames.len());
808 for frame in frames {
809 let mut key_args: Vec<Option<Value>> = Vec::with_capacity(args.len());
810 for term in args {
811 key_args.push(match term {
812 Term::Var(var) => frame.get(var).cloned(),
813 Term::Blank => None,
814 Term::Const(form) => Some(
815 db.and_then(|db| const_value(db, form))
816 .ok_or_else(|| QueryError::Type(format!("bad rule argument {form}")))?,
817 ),
818 });
819 }
820 let tuples = rule_tuples(ctx, name, key_args, stack)?;
821 for tuple in tuples {
822 let mut extended = frame.clone();
823 let mut consistent = true;
824 for (term, value) in args.iter().zip(&tuple) {
825 match term {
826 Term::Blank | Term::Const(_) => {}
828 Term::Var(var) => match extended.get(var) {
829 Some(existing) if !value_match(existing, value) => {
830 consistent = false;
831 break;
832 }
833 Some(_) => {}
834 None => {
835 extended.insert(var.clone(), value.clone());
836 }
837 },
838 }
839 }
840 if consistent {
841 out.push(extended);
842 }
843 }
844 }
845 Ok(out)
846}
847
848fn rule_tuples(
855 ctx: &ExecCtx<'_>,
856 name: &str,
857 key_args: Vec<Option<Value>>,
858 stack: &mut Vec<RuleKey>,
859) -> Result<BTreeSet<Vec<Value>>, QueryError> {
860 let key: RuleKey = (name.to_owned(), key_args);
861 if stack.is_empty() {
862 loop {
863 ctx.rule_state.borrow_mut().changed = false;
864 eval_rule_once(ctx, &key, stack)?;
865 if !ctx.rule_state.borrow().changed {
866 break;
867 }
868 ctx.spend(1)?;
869 }
870 } else {
871 eval_rule_once(ctx, &key, stack)?;
872 }
873 Ok(ctx
874 .rule_state
875 .borrow()
876 .partial
877 .get(&key)
878 .cloned()
879 .unwrap_or_default())
880}
881
882fn eval_rule_once(
883 ctx: &ExecCtx<'_>,
884 key: &RuleKey,
885 stack: &mut Vec<RuleKey>,
886) -> Result<(), QueryError> {
887 if stack.contains(key) {
888 return Ok(());
889 }
890 let defs = ctx
891 .rules
892 .get(&key.0)
893 .ok_or_else(|| QueryError::Unsupported(format!("unknown rule {}", key.0)))?
894 .clone();
895 stack.push(key.clone());
896 let result = (|| -> Result<(), QueryError> {
897 for def in &defs {
898 let head: Vec<&Var> = def.head_vars();
899 if head.len() != key.1.len() {
900 return Err(QueryError::Arity(format!(
901 "rule {} expects {} arguments",
902 key.0,
903 head.len()
904 )));
905 }
906 for (index, _) in def.required.iter().enumerate() {
907 if key.1[index].is_none() {
908 return Err(QueryError::Unbound(format!(
909 "rule {} requires its first {} arguments bound",
910 key.0,
911 def.required.len()
912 )));
913 }
914 }
915 let mut seed = Frame::new();
916 for (var, value) in head.iter().zip(&key.1) {
917 if let Some(value) = value {
918 seed.insert((*var).clone(), value.clone());
919 }
920 }
921 let frames = eval_clauses(ctx, &def.clauses, vec![seed], stack)?;
922 for frame in frames {
923 let tuple = head
924 .iter()
925 .map(|var| {
926 frame
927 .get(*var)
928 .cloned()
929 .ok_or_else(|| QueryError::Unbound((*var).clone()))
930 })
931 .collect::<Result<Vec<_>, _>>()?;
932 let mut state = ctx.rule_state.borrow_mut();
933 if state.partial.entry(key.clone()).or_default().insert(tuple) {
934 state.changed = true;
935 }
936 }
937 }
938 Ok(())
939 })();
940 stack.pop();
941 result
942}