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 if let Some(result) = extern_call(name, values) {
82 return result;
83 }
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 initially_bound: BTreeSet<Var> = frames
192 .first()
193 .map(|frame| frame.keys().cloned().collect())
194 .unwrap_or_default();
195 let order = order_clauses(clauses, &initially_bound, ctx);
196 let mut frames = frames;
197 for index in order {
198 frames = eval_clause(ctx, &clauses[index], frames, stack)?;
199 let set: BTreeSet<Frame> = frames.into_iter().collect();
201 frames = set.into_iter().collect();
202 if frames.is_empty() {
203 return Ok(frames);
204 }
205 }
206 Ok(frames)
207}
208
209fn eval_clause(
210 ctx: &ExecCtx<'_>,
211 clause: &Clause,
212 frames: Vec<Frame>,
213 stack: &mut Vec<RuleKey>,
214) -> Result<Vec<Frame>, QueryError> {
215 match clause {
216 Clause::Pattern(pattern) => eval_pattern(ctx, pattern, frames),
217 Clause::Pred { name, args } => eval_pred(ctx, name, args, frames),
218 Clause::Fn {
219 name,
220 args,
221 binding,
222 } => eval_fn(ctx, name, args, binding, frames),
223 Clause::Not {
224 src: _,
225 vars,
226 clauses,
227 } => {
228 let mut kept = Vec::new();
229 for frame in frames {
230 let seed = match vars {
231 Some(vars) => restrict(&frame, vars),
232 None => frame.clone(),
233 };
234 let sub = eval_clauses(ctx, clauses, vec![seed], stack)?;
235 if sub.is_empty() {
236 kept.push(frame);
237 }
238 }
239 Ok(kept)
240 }
241 Clause::Or {
242 src: _,
243 vars,
244 branches,
245 } => {
246 let mut out = Vec::new();
247 for frame in frames {
248 for branch in branches {
249 let seed = match vars {
250 Some(vars) => restrict(&frame, vars),
251 None => frame.clone(),
252 };
253 for result in eval_clauses(ctx, branch, vec![seed], stack)? {
254 let mut merged = frame.clone();
255 let mut consistent = true;
256 let exported: Vec<&Var> = match vars {
257 Some(vars) => vars.iter().collect(),
258 None => result.keys().collect(),
259 };
260 for var in exported {
261 if let Some(value) = result.get(var) {
262 match merged.get(var) {
263 Some(existing) if !value_match(existing, value) => {
264 consistent = false;
265 break;
266 }
267 Some(_) => {}
268 None => {
269 merged.insert(var.clone(), value.clone());
270 }
271 }
272 }
273 }
274 if consistent {
275 out.push(merged);
276 }
277 }
278 }
279 }
280 Ok(out)
281 }
282 Clause::RuleCall { name, args } => eval_rule_call(ctx, name, args, frames, stack),
283 }
284}
285
286fn restrict(frame: &Frame, vars: &[Var]) -> Frame {
287 vars.iter()
288 .filter_map(|var| frame.get(var).map(|value| (var.clone(), value.clone())))
289 .collect()
290}
291
292enum Spec {
295 Free(Option<Var>),
296 Bound(Value),
297 NoMatch,
298}
299
300fn resolve_term(
301 ctx: &ExecCtx<'_>,
302 db: &Db,
303 frame: &Frame,
304 term: &Term,
305 position: Position,
306 attr: Option<AttrId>,
307) -> Result<Spec, QueryError> {
308 let coerce = |value: Value| -> Value {
309 match (position, attr) {
310 (Position::V, Some(a)) => db.schema().get(a).map_or_else(
311 || value.clone(),
312 |meta| coerce_for_type(value.clone(), meta.value_type),
313 ),
314 _ => value,
315 }
316 };
317 match term {
318 Term::Blank => Ok(Spec::Free(None)),
319 Term::Var(var) => Ok(frame
320 .get(var)
321 .map_or(Spec::Free(Some(var.clone())), |value| {
322 Spec::Bound(coerce(value.clone()))
323 })),
324 Term::Const(form) => match position {
325 Position::E | Position::Tx => match entity_const(db, form)? {
326 Some(e) => Ok(Spec::Bound(Value::Ref(e))),
327 None => Ok(Spec::NoMatch),
328 },
329 Position::A => match ctx.attr_of(db, form) {
330 Some(a) => Ok(Spec::Bound(Value::Ref(a))),
331 None => match form {
332 Edn::Keyword(k) => Err(QueryError::UnknownIdent(k.clone())),
333 _ => Ok(Spec::NoMatch),
334 },
335 },
336 Position::Added => match form {
337 Edn::Bool(b) => Ok(Spec::Bound(Value::Bool(*b))),
338 _ => Ok(Spec::NoMatch),
339 },
340 Position::V => match const_value(db, form) {
341 Some(value) => Ok(Spec::Bound(coerce(value))),
342 None => match entity_const(db, form)? {
343 Some(e) => Ok(Spec::Bound(Value::Ref(e))),
344 None => Ok(Spec::NoMatch),
345 },
346 },
347 },
348 }
349}
350
351fn entity_const(db: &Db, form: &Edn) -> Result<Option<EntityId>, QueryError> {
354 match form {
355 Edn::Long(_) | Edn::Tagged(_, _) => match const_value(db, form) {
356 Some(Value::Ref(e)) => Ok(Some(e)),
357 Some(Value::Long(n)) => Ok(u64::try_from(n).ok().map(EntityId::from_raw)),
358 _ => Ok(None),
359 },
360 Edn::Keyword(k) => Ok(db.idents().entid(k)),
361 Edn::Vector(items) => {
362 let [attr_form, value_form] = items.as_slice() else {
363 return Ok(None);
364 };
365 let Some(attr_kw) = attr_form.as_keyword() else {
366 return Ok(None);
367 };
368 let attr = db
369 .idents()
370 .entid(attr_kw)
371 .ok_or_else(|| QueryError::UnknownIdent(attr_kw.clone()))?;
372 let Some(value) = const_value(db, value_form) else {
373 return Ok(None);
374 };
375 let value = db.schema().get(attr).map_or(value.clone(), |meta| {
376 coerce_for_type(value, meta.value_type)
377 });
378 Ok(db.lookup(attr, &value))
379 }
380 _ => Ok(None),
381 }
382}
383
384#[derive(Clone, Copy, Eq, PartialEq)]
385enum Position {
386 E,
387 A,
388 V,
389 Tx,
390 Added,
391}
392
393#[allow(clippy::too_many_lines)]
394fn eval_pattern(
395 ctx: &ExecCtx<'_>,
396 pattern: &Pattern,
397 frames: Vec<Frame>,
398) -> Result<Vec<Frame>, QueryError> {
399 let db = ctx
400 .db(&pattern.src)
401 .ok_or_else(|| QueryError::UnknownSource(pattern.src.clone()))?;
402 let mut out = Vec::new();
403 for frame in frames {
404 let a_spec = resolve_term(ctx, db, &frame, &pattern.a, Position::A, None)?;
406 let attr = match &a_spec {
407 Spec::Bound(Value::Ref(a)) => Some(*a),
408 Spec::Bound(other) => to_entity(other),
409 _ => None,
410 };
411 let e_spec = resolve_term(ctx, db, &frame, &pattern.e, Position::E, None)?;
412 let v_spec = resolve_term(ctx, db, &frame, &pattern.v, Position::V, attr)?;
413 let tx_spec = resolve_term(ctx, db, &frame, &pattern.tx, Position::Tx, None)?;
414 let added_spec = resolve_term(ctx, db, &frame, &pattern.added, Position::Added, None)?;
415 if [&a_spec, &e_spec, &v_spec, &tx_spec, &added_spec]
416 .iter()
417 .any(|spec| matches!(spec, Spec::NoMatch))
418 {
419 continue;
420 }
421 let e = match &e_spec {
422 Spec::Bound(value) => to_entity(value),
423 _ => None,
424 };
425 let v = match &v_spec {
426 Spec::Bound(value) => Some(value.clone()),
427 _ => None,
428 };
429 let choice: ScanChoice = choose_index(
430 e.is_some(),
431 attr.is_some(),
432 v.is_some(),
433 attr.is_some_and(|a| avet_covered(db.schema(), a)),
434 matches!(v, Some(Value::Ref(_))),
435 );
436 let prefix = match choice.order {
437 IndexOrder::Eavt => key_prefix(
438 IndexOrder::Eavt,
439 e,
440 if choice.prefix_len >= 2 { attr } else { None },
441 if choice.prefix_len >= 3 {
442 v.as_ref()
443 } else {
444 None
445 },
446 ),
447 IndexOrder::Aevt => key_prefix(IndexOrder::Aevt, None, attr, None),
448 IndexOrder::Avet => key_prefix(IndexOrder::Avet, None, attr, v.as_ref()),
449 IndexOrder::Vaet => key_prefix(
450 IndexOrder::Vaet,
451 None,
452 if choice.prefix_len >= 2 { attr } else { None },
453 v.as_ref(),
454 ),
455 };
456 for datom in db.datoms_prefix(choice.order, &prefix) {
457 ctx.spend(1)?;
458 ctx.scanned.set(ctx.scanned.get() + 1);
459 let fields = [
460 (Position::E, Value::Ref(datom.e)),
461 (Position::A, Value::Ref(datom.a)),
462 (Position::V, datom.v.clone()),
463 (Position::Tx, Value::Ref(datom.tx)),
464 (Position::Added, Value::Bool(datom.added)),
465 ];
466 let specs = [&e_spec, &a_spec, &v_spec, &tx_spec, &added_spec];
467 let mut extended = frame.clone();
468 let mut matched = true;
469 for ((_, actual), spec) in fields.into_iter().zip(specs) {
470 match spec {
471 Spec::Bound(expected) => {
472 if !value_match(&actual, expected) {
473 matched = false;
474 break;
475 }
476 }
477 Spec::Free(Some(var)) => match extended.get(var) {
478 Some(existing) => {
479 if !value_match(&actual, existing) {
480 matched = false;
481 break;
482 }
483 }
484 None => {
485 extended.insert(var.clone(), actual);
486 }
487 },
488 Spec::Free(None) | Spec::NoMatch => {}
489 }
490 }
491 if matched {
492 out.push(extended);
493 }
494 }
495 }
496 Ok(out)
497}
498
499fn call_args(ctx: &ExecCtx<'_>, frame: &Frame, args: &[Term]) -> Result<Vec<Value>, QueryError> {
502 let db = ctx.default_db();
503 args.iter()
504 .map(|term| match term {
505 Term::Var(var) => frame
506 .get(var)
507 .cloned()
508 .ok_or_else(|| QueryError::Unbound(var.clone())),
509 Term::Blank => Err(QueryError::Parse("call arguments cannot be _".into())),
510 Term::Const(form) => db
511 .and_then(|db| const_value(db, form))
512 .ok_or_else(|| QueryError::Type(format!("bad call argument {form}"))),
513 })
514 .collect()
515}
516
517fn eval_pred(
518 ctx: &ExecCtx<'_>,
519 name: &str,
520 args: &[Term],
521 frames: Vec<Frame>,
522) -> Result<Vec<Frame>, QueryError> {
523 if name == "missing?" {
524 return eval_missing(ctx, args, frames);
525 }
526 let mut out = Vec::new();
527 for frame in frames {
528 let values = call_args(ctx, &frame, args)?;
529 match ctx.call(name, &values)? {
530 CallResult::Test(true) | CallResult::Scalar(Value::Bool(true)) => out.push(frame),
531 CallResult::Test(false) | CallResult::Scalar(Value::Bool(false)) => {}
532 _ => {
533 return Err(QueryError::Type(format!("{name} is not a predicate")));
534 }
535 }
536 }
537 Ok(out)
538}
539
540fn eval_fn(
541 ctx: &ExecCtx<'_>,
542 name: &str,
543 args: &[Term],
544 binding: &Binding,
545 frames: Vec<Frame>,
546) -> Result<Vec<Frame>, QueryError> {
547 if name == "ground" {
548 return eval_ground(ctx, args, binding, frames);
549 }
550 if name == "get-else" {
551 return eval_get_else(ctx, args, binding, frames);
552 }
553 let mut out = Vec::new();
554 for frame in frames {
555 let values = call_args(ctx, &frame, args)?;
556 let result = match ctx.call(name, &values)? {
557 CallResult::Test(truth) => CallResult::Scalar(Value::Bool(truth)),
558 other => other,
559 };
560 bind_result(&frame, binding, result, &mut out)?;
561 }
562 Ok(out)
563}
564
565fn bind_result(
566 frame: &Frame,
567 binding: &Binding,
568 result: CallResult,
569 out: &mut Vec<Frame>,
570) -> Result<(), QueryError> {
571 let scalar = |value: &Value, var: &Var, frame: &Frame| -> Option<Frame> {
572 let mut next = frame.clone();
573 match next.get(var) {
574 Some(existing) if !value_match(existing, value) => None,
575 Some(_) => Some(next),
576 None => {
577 next.insert(var.clone(), value.clone());
578 Some(next)
579 }
580 }
581 };
582 let bind_tuple = |values: &[Value], targets: &[BindTarget], frame: &Frame| -> Option<Frame> {
583 if values.len() != targets.len() {
584 return None;
585 }
586 let mut next = frame.clone();
587 for (value, target) in values.iter().zip(targets) {
588 match target {
589 BindTarget::Blank => {}
590 BindTarget::Var(var) => match next.get(var) {
591 Some(existing) if !value_match(existing, value) => return None,
592 Some(_) => {}
593 None => {
594 next.insert(var.clone(), value.clone());
595 }
596 },
597 }
598 }
599 Some(next)
600 };
601 match (binding, result) {
602 (Binding::Scalar(var), CallResult::Scalar(value)) => {
603 out.extend(scalar(&value, var, frame));
604 }
605 (Binding::Scalar(var), CallResult::Test(flag)) => {
606 out.extend(scalar(&Value::Bool(flag), var, frame));
607 }
608 (Binding::Coll(target), CallResult::Coll(values)) => match target {
609 BindTarget::Blank => {
610 if !values.is_empty() {
611 out.push(frame.clone());
612 }
613 }
614 BindTarget::Var(var) => {
615 for value in values {
616 out.extend(scalar(&value, var, frame));
617 }
618 }
619 },
620 (Binding::Tuple(targets), CallResult::Coll(values)) => {
621 out.extend(bind_tuple(&values, targets, frame));
622 }
623 (Binding::Rel(targets), CallResult::Rel(rows)) => {
624 for row in rows {
625 out.extend(bind_tuple(&row, targets, frame));
626 }
627 }
628 (_, result) => {
629 return Err(QueryError::Type(format!(
630 "binding form does not fit result {result:?}"
631 )));
632 }
633 }
634 Ok(())
635}
636
637fn eval_ground(
639 ctx: &ExecCtx<'_>,
640 args: &[Term],
641 binding: &Binding,
642 frames: Vec<Frame>,
643) -> Result<Vec<Frame>, QueryError> {
644 let db = ctx
645 .default_db()
646 .ok_or_else(|| QueryError::UnknownSource("$".into()))?;
647 let [Term::Const(form)] = args else {
648 return Err(QueryError::Arity("ground takes one constant".into()));
649 };
650 let value_of = |form: &Edn| -> Result<Value, QueryError> {
651 const_value(db, form).ok_or_else(|| QueryError::Type(format!("cannot ground {form}")))
652 };
653 let result = match (binding, form) {
654 (Binding::Coll(_) | Binding::Tuple(_), Edn::Vector(items) | Edn::List(items)) => {
655 CallResult::Coll(items.iter().map(value_of).collect::<Result<_, _>>()?)
656 }
657 (Binding::Rel(_), Edn::Vector(rows) | Edn::List(rows)) => CallResult::Rel(
658 rows.iter()
659 .map(|row| match row {
660 Edn::Vector(items) | Edn::List(items) => {
661 items.iter().map(value_of).collect::<Result<Vec<_>, _>>()
662 }
663 _ => Err(QueryError::Type("ground relation requires tuples".into())),
664 })
665 .collect::<Result<_, _>>()?,
666 ),
667 (Binding::Scalar(_), form) => CallResult::Scalar(value_of(form)?),
668 _ => return Err(QueryError::Type(format!("cannot ground {form}"))),
669 };
670 let mut out = Vec::new();
671 for frame in frames {
672 bind_result(&frame, binding, result.clone(), &mut out)?;
673 }
674 Ok(out)
675}
676
677fn eval_get_else(
679 ctx: &ExecCtx<'_>,
680 args: &[Term],
681 binding: &Binding,
682 frames: Vec<Frame>,
683) -> Result<Vec<Frame>, QueryError> {
684 let (db, rest) = db_context_args(ctx, args)?;
685 let [entity_term, attr_term, default_term] = rest else {
686 return Err(QueryError::Arity(
687 "get-else takes a source, entity, attribute, and default".into(),
688 ));
689 };
690 let attr = attr_const(ctx, db, attr_term)?;
691 let value_type = db.schema().get(attr).map(|meta| meta.value_type);
692 let mut out = Vec::new();
693 for frame in frames {
694 let entity = entity_arg(db, &frame, entity_term)?;
695 let value = entity
696 .and_then(|e| db.values(e, attr).into_iter().next())
697 .map_or_else(
698 || match default_term {
699 Term::Const(form) => {
700 let value = const_value(db, form).ok_or_else(|| {
701 QueryError::Type(format!("bad get-else default {form}"))
702 })?;
703 Ok(match value_type {
704 Some(t) => coerce_for_type(value, t),
705 None => value,
706 })
707 }
708 Term::Var(var) => frame
709 .get(var)
710 .cloned()
711 .ok_or_else(|| QueryError::Unbound(var.clone())),
712 Term::Blank => Err(QueryError::Parse("get-else default cannot be _".into())),
713 },
714 Ok,
715 )?;
716 bind_result(&frame, binding, CallResult::Scalar(value), &mut out)?;
717 }
718 Ok(out)
719}
720
721fn eval_missing(
723 ctx: &ExecCtx<'_>,
724 args: &[Term],
725 frames: Vec<Frame>,
726) -> Result<Vec<Frame>, QueryError> {
727 let (db, rest) = db_context_args(ctx, args)?;
728 let [entity_term, attr_term] = rest else {
729 return Err(QueryError::Arity(
730 "missing? takes a source, entity, and attribute".into(),
731 ));
732 };
733 let attr = attr_const(ctx, db, attr_term)?;
734 let mut out = Vec::new();
735 for frame in frames {
736 let entity = entity_arg(db, &frame, entity_term)?;
737 let missing = entity.is_none_or(|e| db.values(e, attr).is_empty());
738 if missing {
739 out.push(frame);
740 }
741 }
742 Ok(out)
743}
744
745fn db_context_args<'a, 'b>(
746 ctx: &'b ExecCtx<'a>,
747 args: &'b [Term],
748) -> Result<(&'a Db, &'b [Term]), QueryError> {
749 match args.split_first() {
750 Some((Term::Const(Edn::Symbol(src)), rest)) if src.starts_with('$') => {
751 let db = ctx
752 .db(src)
753 .ok_or_else(|| QueryError::UnknownSource(src.clone()))?;
754 Ok((db, rest))
755 }
756 _ => {
757 let db = ctx
758 .default_db()
759 .ok_or_else(|| QueryError::UnknownSource("$".into()))?;
760 Ok((db, args))
761 }
762 }
763}
764
765fn attr_const(ctx: &ExecCtx<'_>, db: &Db, term: &Term) -> Result<AttrId, QueryError> {
766 match term {
767 Term::Const(form) => ctx.attr_of(db, form).ok_or_else(|| match form {
768 Edn::Keyword(k) => QueryError::UnknownIdent(k.clone()),
769 _ => QueryError::Type(format!("bad attribute argument {form}")),
770 }),
771 _ => Err(QueryError::Type(
772 "attribute argument must be a constant".into(),
773 )),
774 }
775}
776
777fn entity_arg(db: &Db, frame: &Frame, term: &Term) -> Result<Option<EntityId>, QueryError> {
778 match term {
779 Term::Var(var) => {
780 let value = frame
781 .get(var)
782 .ok_or_else(|| QueryError::Unbound(var.clone()))?;
783 Ok(to_entity(value))
784 }
785 Term::Const(form) => entity_const(db, form),
786 Term::Blank => Err(QueryError::Parse("entity argument cannot be _".into())),
787 }
788}
789
790fn eval_rule_call(
791 ctx: &ExecCtx<'_>,
792 name: &str,
793 args: &[Term],
794 frames: Vec<Frame>,
795 stack: &mut Vec<RuleKey>,
796) -> Result<Vec<Frame>, QueryError> {
797 let db = ctx.default_db();
798 let mut out = Vec::new();
799 for frame in frames {
800 let mut key_args: Vec<Option<Value>> = Vec::with_capacity(args.len());
801 for term in args {
802 key_args.push(match term {
803 Term::Var(var) => frame.get(var).cloned(),
804 Term::Blank => None,
805 Term::Const(form) => Some(
806 db.and_then(|db| const_value(db, form))
807 .ok_or_else(|| QueryError::Type(format!("bad rule argument {form}")))?,
808 ),
809 });
810 }
811 let tuples = rule_tuples(ctx, name, key_args, stack)?;
812 for tuple in tuples {
813 let mut extended = frame.clone();
814 let mut consistent = true;
815 for (term, value) in args.iter().zip(&tuple) {
816 match term {
817 Term::Blank | Term::Const(_) => {}
819 Term::Var(var) => match extended.get(var) {
820 Some(existing) if !value_match(existing, value) => {
821 consistent = false;
822 break;
823 }
824 Some(_) => {}
825 None => {
826 extended.insert(var.clone(), value.clone());
827 }
828 },
829 }
830 }
831 if consistent {
832 out.push(extended);
833 }
834 }
835 }
836 Ok(out)
837}
838
839fn rule_tuples(
846 ctx: &ExecCtx<'_>,
847 name: &str,
848 key_args: Vec<Option<Value>>,
849 stack: &mut Vec<RuleKey>,
850) -> Result<BTreeSet<Vec<Value>>, QueryError> {
851 let key: RuleKey = (name.to_owned(), key_args);
852 if stack.is_empty() {
853 loop {
854 ctx.rule_state.borrow_mut().changed = false;
855 eval_rule_once(ctx, &key, stack)?;
856 if !ctx.rule_state.borrow().changed {
857 break;
858 }
859 ctx.spend(1)?;
860 }
861 } else {
862 eval_rule_once(ctx, &key, stack)?;
863 }
864 Ok(ctx
865 .rule_state
866 .borrow()
867 .partial
868 .get(&key)
869 .cloned()
870 .unwrap_or_default())
871}
872
873fn eval_rule_once(
874 ctx: &ExecCtx<'_>,
875 key: &RuleKey,
876 stack: &mut Vec<RuleKey>,
877) -> Result<(), QueryError> {
878 if stack.contains(key) {
879 return Ok(());
880 }
881 let defs = ctx
882 .rules
883 .get(&key.0)
884 .ok_or_else(|| QueryError::Unsupported(format!("unknown rule {}", key.0)))?
885 .clone();
886 stack.push(key.clone());
887 let result = (|| -> Result<(), QueryError> {
888 for def in &defs {
889 let head: Vec<&Var> = def.head_vars();
890 if head.len() != key.1.len() {
891 return Err(QueryError::Arity(format!(
892 "rule {} expects {} arguments",
893 key.0,
894 head.len()
895 )));
896 }
897 for (index, _) in def.required.iter().enumerate() {
898 if key.1[index].is_none() {
899 return Err(QueryError::Unbound(format!(
900 "rule {} requires its first {} arguments bound",
901 key.0,
902 def.required.len()
903 )));
904 }
905 }
906 let mut seed = Frame::new();
907 for (var, value) in head.iter().zip(&key.1) {
908 if let Some(value) = value {
909 seed.insert((*var).clone(), value.clone());
910 }
911 }
912 let frames = eval_clauses(ctx, &def.clauses, vec![seed], stack)?;
913 for frame in frames {
914 let tuple = head
915 .iter()
916 .map(|var| {
917 frame
918 .get(*var)
919 .cloned()
920 .ok_or_else(|| QueryError::Unbound((*var).clone()))
921 })
922 .collect::<Result<Vec<_>, _>>()?;
923 let mut state = ctx.rule_state.borrow_mut();
924 if state.partial.entry(key.clone()).or_default().insert(tuple) {
925 state.changed = true;
926 }
927 }
928 }
929 Ok(())
930 })();
931 stack.pop();
932 result
933}