1use std::collections::BTreeSet;
4
5use crate::QueryError;
6use crate::edn::Edn;
7
8pub type Var = String;
10
11pub const DEFAULT_SRC: &str = "$";
13
14#[derive(Clone, Debug, Eq, PartialEq)]
16pub struct Query {
17 pub find: FindSpec,
19 pub with: Vec<Var>,
21 pub inputs: Vec<InSpec>,
23 pub wheres: Vec<Clause>,
25}
26
27#[derive(Clone, Debug, Eq, PartialEq)]
29pub enum FindSpec {
30 Rel(Vec<FindElem>),
32 Coll(FindElem),
34 Tuple(Vec<FindElem>),
36 Scalar(FindElem),
38}
39
40impl FindSpec {
41 #[must_use]
43 pub fn elems(&self) -> Vec<&FindElem> {
44 match self {
45 Self::Rel(elems) | Self::Tuple(elems) => elems.iter().collect(),
46 Self::Coll(elem) | Self::Scalar(elem) => vec![elem],
47 }
48 }
49}
50
51#[derive(Clone, Debug, Eq, PartialEq)]
53pub enum FindElem {
54 Var(Var),
56 Aggregate(Aggregate),
58 Pull(Var, Edn),
60}
61
62impl FindElem {
63 #[must_use]
65 pub fn var(&self) -> &Var {
66 match self {
67 Self::Var(v) | Self::Pull(v, _) => v,
68 Self::Aggregate(agg) => &agg.var,
69 }
70 }
71}
72
73#[derive(Clone, Debug, Eq, PartialEq)]
75pub struct Aggregate {
76 pub op: String,
78 pub n: Option<i64>,
80 pub var: Var,
82}
83
84#[derive(Clone, Debug, Eq, PartialEq)]
86pub enum InSpec {
87 Db(String),
89 Rules,
91 Scalar(Var),
93 Tuple(Vec<Var>),
95 Coll(Var),
97 Rel(Vec<Var>),
99}
100
101#[derive(Clone, Debug, Eq, PartialEq)]
103pub enum Term {
104 Var(Var),
106 Blank,
108 Const(Edn),
110}
111
112impl Term {
113 fn parse(form: &Edn) -> Self {
114 match form.as_symbol() {
115 Some("_") => Self::Blank,
116 Some(sym) if sym.starts_with('?') => Self::Var(sym.to_owned()),
117 _ => Self::Const(form.clone()),
118 }
119 }
120}
121
122#[derive(Clone, Debug, Eq, PartialEq)]
124pub struct Pattern {
125 pub src: String,
127 pub e: Term,
129 pub a: Term,
131 pub v: Term,
133 pub tx: Term,
135 pub added: Term,
137}
138
139#[derive(Clone, Debug, Eq, PartialEq)]
141pub enum Clause {
142 Pattern(Pattern),
144 Pred {
146 name: String,
148 args: Vec<Term>,
150 },
151 Fn {
153 name: String,
155 args: Vec<Term>,
157 binding: Binding,
159 },
160 Not {
162 src: String,
164 vars: Option<Vec<Var>>,
166 clauses: Vec<Clause>,
168 },
169 Or {
171 src: String,
173 vars: Option<Vec<Var>>,
175 branches: Vec<Vec<Clause>>,
177 },
178 RuleCall {
180 name: String,
182 args: Vec<Term>,
184 },
185}
186
187#[derive(Clone, Debug, Eq, PartialEq)]
189pub struct RuleDef {
190 pub name: String,
192 pub required: Vec<Var>,
194 pub free: Vec<Var>,
196 pub clauses: Vec<Clause>,
198}
199
200impl RuleDef {
201 #[must_use]
203 pub fn head_vars(&self) -> Vec<&Var> {
204 self.required.iter().chain(self.free.iter()).collect()
205 }
206}
207
208fn parse_error(message: impl Into<String>) -> QueryError {
209 QueryError::Parse(message.into())
210}
211
212fn expect_var(form: &Edn) -> Result<Var, QueryError> {
213 match form.as_symbol() {
214 Some(sym) if sym.starts_with('?') => Ok(sym.to_owned()),
215 _ => Err(parse_error(format!("expected variable, got {form}"))),
216 }
217}
218
219fn is_src_symbol(form: &Edn) -> bool {
220 form.as_symbol().is_some_and(|s| s.starts_with('$'))
221}
222
223pub fn parse_query(form: &Edn) -> Result<Query, QueryError> {
230 let map = normalize_to_map(form)?;
231 let find_form = map
232 .get(&Edn::keyword("find"))
233 .ok_or_else(|| parse_error("query requires :find"))?;
234 let find_items = find_form
235 .as_seq()
236 .ok_or_else(|| parse_error(":find must be a vector"))?;
237 let find = parse_find(find_items)?;
238
239 let with = match map.get(&Edn::keyword("with")) {
240 None => Vec::new(),
241 Some(form) => form
242 .as_seq()
243 .ok_or_else(|| parse_error(":with must be a vector"))?
244 .iter()
245 .map(expect_var)
246 .collect::<Result<_, _>>()?,
247 };
248
249 let inputs = match map.get(&Edn::keyword("in")) {
250 None => vec![InSpec::Db(DEFAULT_SRC.to_owned())],
251 Some(form) => form
252 .as_seq()
253 .ok_or_else(|| parse_error(":in must be a vector"))?
254 .iter()
255 .map(parse_in_spec)
256 .collect::<Result<_, _>>()?,
257 };
258
259 let wheres = match map.get(&Edn::keyword("where")) {
260 None => Vec::new(),
261 Some(form) => parse_clauses(
262 form.as_seq()
263 .ok_or_else(|| parse_error(":where must be a vector"))?,
264 )?,
265 };
266
267 let query = Query {
268 find,
269 with,
270 inputs,
271 wheres,
272 };
273 validate(&query)?;
274 Ok(query)
275}
276
277fn normalize_to_map(form: &Edn) -> Result<Edn, QueryError> {
279 match form {
280 Edn::Map(_) => Ok(form.clone()),
281 Edn::Vector(items) => {
282 let mut pairs: Vec<(Edn, Edn)> = Vec::new();
283 let mut current: Option<(Edn, Vec<Edn>)> = None;
284 for item in items {
285 if let Edn::Keyword(k) = item {
286 if k.namespace.is_none() {
287 if let Some((key, values)) = current.take() {
288 pairs.push((key, Edn::Vector(values)));
289 }
290 current = Some((Edn::Keyword(k.clone()), Vec::new()));
291 continue;
292 }
293 }
294 match &mut current {
295 Some((_, values)) => values.push(item.clone()),
296 None => return Err(parse_error("vector query must start with a keyword")),
297 }
298 }
299 if let Some((key, values)) = current.take() {
300 pairs.push((key, Edn::Vector(values)));
301 }
302 pairs.sort_by(|left, right| left.0.cmp(&right.0));
303 Ok(Edn::Map(pairs))
304 }
305 _ => Err(parse_error("query must be a map or vector")),
306 }
307}
308
309fn parse_find(items: &[Edn]) -> Result<FindSpec, QueryError> {
310 if items.is_empty() {
311 return Err(parse_error(":find requires at least one element"));
312 }
313 if items.len() == 2 && items[1].as_symbol() == Some(".") {
315 return Ok(FindSpec::Scalar(parse_find_elem(&items[0])?));
316 }
317 if items.len() == 1 {
319 if let Edn::Vector(inner) = &items[0] {
320 if inner.last().and_then(Edn::as_symbol) == Some("...") {
321 if inner.len() != 2 {
322 return Err(parse_error("collection find takes one element"));
323 }
324 return Ok(FindSpec::Coll(parse_find_elem(&inner[0])?));
325 }
326 return Ok(FindSpec::Tuple(
327 inner
328 .iter()
329 .map(parse_find_elem)
330 .collect::<Result<_, _>>()?,
331 ));
332 }
333 }
334 Ok(FindSpec::Rel(
335 items
336 .iter()
337 .map(parse_find_elem)
338 .collect::<Result<_, _>>()?,
339 ))
340}
341
342fn parse_find_elem(form: &Edn) -> Result<FindElem, QueryError> {
343 match form {
344 Edn::Symbol(sym) if sym.starts_with('?') => Ok(FindElem::Var(sym.clone())),
345 Edn::List(items) => {
346 let (op, rest) = items
347 .split_first()
348 .ok_or_else(|| parse_error("empty find call"))?;
349 let op = op
350 .as_symbol()
351 .ok_or_else(|| parse_error("find call must start with a symbol"))?;
352 if op == "pull" {
353 let [entity, pattern] = rest else {
354 return Err(parse_error("pull takes an entity variable and a pattern"));
355 };
356 return Ok(FindElem::Pull(expect_var(entity)?, pattern.clone()));
357 }
358 match rest {
359 [var] => Ok(FindElem::Aggregate(Aggregate {
360 op: op.to_owned(),
361 n: None,
362 var: expect_var(var)?,
363 })),
364 [Edn::Long(n), var] => Ok(FindElem::Aggregate(Aggregate {
365 op: op.to_owned(),
366 n: Some(*n),
367 var: expect_var(var)?,
368 })),
369 _ => Err(parse_error(format!("malformed aggregate ({op} …)"))),
370 }
371 }
372 _ => Err(parse_error(format!("bad find element {form}"))),
373 }
374}
375
376fn parse_in_spec(form: &Edn) -> Result<InSpec, QueryError> {
377 match form {
378 Edn::Symbol(sym) if sym.starts_with('$') => Ok(InSpec::Db(sym.clone())),
379 Edn::Symbol(sym) if sym == "%" => Ok(InSpec::Rules),
380 Edn::Symbol(sym) if sym.starts_with('?') => Ok(InSpec::Scalar(sym.clone())),
381 Edn::Vector(items) => {
382 if items.last().and_then(Edn::as_symbol) == Some("...") {
383 if items.len() != 2 {
384 return Err(parse_error("collection binding takes one variable"));
385 }
386 return Ok(InSpec::Coll(expect_var(&items[0])?));
387 }
388 if items.len() == 1 {
389 if let Edn::Vector(inner) = &items[0] {
390 return Ok(InSpec::Rel(
391 inner.iter().map(expect_var).collect::<Result<_, _>>()?,
392 ));
393 }
394 }
395 Ok(InSpec::Tuple(
396 items.iter().map(expect_var).collect::<Result<_, _>>()?,
397 ))
398 }
399 _ => Err(parse_error(format!("bad :in binding {form}"))),
400 }
401}
402
403fn parse_clauses(forms: &[Edn]) -> Result<Vec<Clause>, QueryError> {
404 forms.iter().map(parse_clause).collect()
405}
406
407pub fn parse_clause(form: &Edn) -> Result<Clause, QueryError> {
412 match form {
413 Edn::Vector(items) => parse_vector_clause(items),
414 Edn::List(items) => parse_list_clause(items),
415 _ => Err(parse_error(format!("bad clause {form}"))),
416 }
417}
418
419fn parse_vector_clause(items: &[Edn]) -> Result<Clause, QueryError> {
420 if let Some(Edn::List(call)) = items.first() {
422 let (name, args) = call
423 .split_first()
424 .ok_or_else(|| parse_error("empty call clause"))?;
425 let name = name
426 .as_symbol()
427 .ok_or_else(|| parse_error("call must start with a symbol"))?
428 .to_owned();
429 let args = args.iter().map(Term::parse).collect();
430 return match &items[1..] {
431 [] => Ok(Clause::Pred { name, args }),
432 [binding] => Ok(Clause::Fn {
433 name,
434 args,
435 binding: parse_binding(binding)?,
436 }),
437 _ => Err(parse_error("function clause takes one binding form")),
438 };
439 }
440 let (src, rest) = match items.split_first() {
442 Some((first, rest)) if is_src_symbol(first) => {
443 (first.as_symbol().unwrap_or(DEFAULT_SRC).to_owned(), rest)
444 }
445 _ => (DEFAULT_SRC.to_owned(), items),
446 };
447 if rest.is_empty() || rest.len() > 5 {
448 return Err(parse_error("data pattern takes one to five positions"));
449 }
450 let term = |i: usize| rest.get(i).map_or(Term::Blank, Term::parse);
451 Ok(Clause::Pattern(Pattern {
452 src,
453 e: term(0),
454 a: term(1),
455 v: term(2),
456 tx: term(3),
457 added: term(4),
458 }))
459}
460
461fn parse_list_clause(items: &[Edn]) -> Result<Clause, QueryError> {
462 let (src, items) = match items.split_first() {
463 Some((first, rest)) if is_src_symbol(first) => {
464 (first.as_symbol().unwrap_or(DEFAULT_SRC).to_owned(), rest)
465 }
466 _ => (DEFAULT_SRC.to_owned(), items),
467 };
468 let (head, rest) = items
469 .split_first()
470 .ok_or_else(|| parse_error("empty list clause"))?;
471 let head = head
472 .as_symbol()
473 .ok_or_else(|| parse_error("list clause must start with a symbol"))?;
474 match head {
475 "not" => Ok(Clause::Not {
476 src,
477 vars: None,
478 clauses: parse_clauses(rest)?,
479 }),
480 "not-join" => {
481 let (vars, clauses) = parse_join_head(rest, "not-join")?;
482 Ok(Clause::Not {
483 src,
484 vars: Some(vars),
485 clauses,
486 })
487 }
488 "or" => Ok(Clause::Or {
489 src,
490 vars: None,
491 branches: parse_branches(rest)?,
492 }),
493 "or-join" => {
494 let (vars, branch_forms) = rest
495 .split_first()
496 .ok_or_else(|| parse_error("or-join requires a variable vector"))?;
497 let vars = vars
498 .as_seq()
499 .ok_or_else(|| parse_error("or-join requires a variable vector"))?
500 .iter()
501 .map(expect_var)
502 .collect::<Result<_, _>>()?;
503 Ok(Clause::Or {
504 src,
505 vars: Some(vars),
506 branches: parse_branches(branch_forms)?,
507 })
508 }
509 "and" => Err(parse_error("(and …) is only valid inside or")),
510 _ => Ok(Clause::RuleCall {
511 name: head.to_owned(),
512 args: rest.iter().map(Term::parse).collect(),
513 }),
514 }
515}
516
517fn parse_join_head(forms: &[Edn], what: &str) -> Result<(Vec<Var>, Vec<Clause>), QueryError> {
518 let (vars, rest) = forms
519 .split_first()
520 .ok_or_else(|| parse_error(format!("{what} requires a variable vector")))?;
521 let vars = vars
522 .as_seq()
523 .ok_or_else(|| parse_error(format!("{what} requires a variable vector")))?
524 .iter()
525 .map(expect_var)
526 .collect::<Result<_, _>>()?;
527 Ok((vars, parse_clauses(rest)?))
528}
529
530fn parse_branches(forms: &[Edn]) -> Result<Vec<Vec<Clause>>, QueryError> {
531 forms
532 .iter()
533 .map(|form| {
534 if let Edn::List(items) = form {
535 if items.first().and_then(Edn::as_symbol) == Some("and") {
536 return parse_clauses(&items[1..]);
537 }
538 }
539 Ok(vec![parse_clause(form)?])
540 })
541 .collect()
542}
543
544fn parse_binding(form: &Edn) -> Result<Binding, QueryError> {
545 let target = |form: &Edn| -> Result<BindTarget, QueryError> {
546 match form.as_symbol() {
547 Some("_") => Ok(BindTarget::Blank),
548 Some(sym) if sym.starts_with('?') => Ok(BindTarget::Var(sym.to_owned())),
549 _ => Err(parse_error(format!("bad binding target {form}"))),
550 }
551 };
552 match form {
553 Edn::Symbol(sym) if sym.starts_with('?') => Ok(Binding::Scalar(sym.clone())),
554 Edn::Vector(items) => {
555 if items.last().and_then(Edn::as_symbol) == Some("...") {
556 if items.len() != 2 {
557 return Err(parse_error("collection binding takes one target"));
558 }
559 return Ok(Binding::Coll(target(&items[0])?));
560 }
561 if items.len() == 1 {
562 if let Edn::Vector(inner) = &items[0] {
563 return Ok(Binding::Rel(
564 inner.iter().map(target).collect::<Result<_, _>>()?,
565 ));
566 }
567 }
568 Ok(Binding::Tuple(
569 items.iter().map(target).collect::<Result<_, _>>()?,
570 ))
571 }
572 _ => Err(parse_error(format!("bad binding form {form}"))),
573 }
574}
575
576#[derive(Clone, Debug, Eq, PartialEq)]
578pub enum Binding {
579 Scalar(Var),
581 Tuple(Vec<BindTarget>),
583 Coll(BindTarget),
585 Rel(Vec<BindTarget>),
587}
588
589#[derive(Clone, Debug, Eq, PartialEq)]
591pub enum BindTarget {
592 Var(Var),
594 Blank,
596}
597
598pub fn parse_rules(form: &Edn) -> Result<Vec<RuleDef>, QueryError> {
603 let defs = form
604 .as_seq()
605 .ok_or_else(|| parse_error("rule set must be a vector"))?;
606 defs.iter()
607 .map(|def| {
608 let items = def
609 .as_seq()
610 .ok_or_else(|| parse_error("rule definition must be a vector"))?;
611 let (head, body) = items
612 .split_first()
613 .ok_or_else(|| parse_error("rule definition requires a head"))?;
614 let Edn::List(head_items) = head else {
615 return Err(parse_error("rule head must be a list"));
616 };
617 let (name, head_args) = head_items
618 .split_first()
619 .ok_or_else(|| parse_error("rule head requires a name"))?;
620 let name = name
621 .as_symbol()
622 .ok_or_else(|| parse_error("rule name must be a symbol"))?
623 .to_owned();
624 let (required, free) = match head_args.split_first() {
625 Some((Edn::Vector(required), rest)) => (
626 required.iter().map(expect_var).collect::<Result<_, _>>()?,
627 rest.iter().map(expect_var).collect::<Result<Vec<_>, _>>()?,
628 ),
629 _ => (
630 Vec::new(),
631 head_args
632 .iter()
633 .map(expect_var)
634 .collect::<Result<Vec<_>, _>>()?,
635 ),
636 };
637 Ok(RuleDef {
638 name,
639 required,
640 free,
641 clauses: parse_clauses(body)?,
642 })
643 })
644 .collect()
645}
646
647#[must_use]
649pub fn clause_vars(clause: &Clause) -> BTreeSet<Var> {
650 fn term_var(term: &Term, out: &mut BTreeSet<Var>) {
651 if let Term::Var(v) = term {
652 out.insert(v.clone());
653 }
654 }
655 let mut out = BTreeSet::new();
656 match clause {
657 Clause::Pattern(p) => {
658 for term in [&p.e, &p.a, &p.v, &p.tx, &p.added] {
659 term_var(term, &mut out);
660 }
661 }
662 Clause::Pred { args, .. } | Clause::RuleCall { args, .. } => {
663 for term in args {
664 term_var(term, &mut out);
665 }
666 }
667 Clause::Fn { args, binding, .. } => {
668 for term in args {
669 term_var(term, &mut out);
670 }
671 let mut push = |target: &BindTarget| {
672 if let BindTarget::Var(v) = target {
673 out.insert(v.clone());
674 }
675 };
676 match binding {
677 Binding::Scalar(v) => {
678 out.insert(v.clone());
679 }
680 Binding::Coll(t) => push(t),
681 Binding::Tuple(ts) | Binding::Rel(ts) => ts.iter().for_each(&mut push),
682 }
683 }
684 Clause::Not { vars, clauses, .. } => match vars {
685 Some(vars) => out.extend(vars.iter().cloned()),
686 None => {
687 for clause in clauses {
688 out.extend(clause_vars(clause));
689 }
690 }
691 },
692 Clause::Or { vars, branches, .. } => match vars {
693 Some(vars) => out.extend(vars.iter().cloned()),
694 None => {
695 for branch in branches {
696 for clause in branch {
697 out.extend(clause_vars(clause));
698 }
699 }
700 }
701 },
702 }
703 out
704}
705
706fn validate(query: &Query) -> Result<(), QueryError> {
707 let mut available: BTreeSet<Var> = BTreeSet::new();
708 for spec in &query.inputs {
709 match spec {
710 InSpec::Scalar(v) | InSpec::Coll(v) => {
711 available.insert(v.clone());
712 }
713 InSpec::Tuple(vs) | InSpec::Rel(vs) => available.extend(vs.iter().cloned()),
714 InSpec::Db(_) | InSpec::Rules => {}
715 }
716 }
717 for clause in &query.wheres {
718 available.extend(clause_vars(clause));
719 }
720 for elem in query.find.elems() {
721 if !available.contains(elem.var()) {
722 return Err(QueryError::Unbound(elem.var().clone()));
723 }
724 }
725 for var in &query.with {
726 if !available.contains(var) {
727 return Err(QueryError::Unbound(var.clone()));
728 }
729 }
730 for clause in &query.wheres {
732 validate_or(clause)?;
733 }
734 Ok(())
735}
736
737fn validate_or(clause: &Clause) -> Result<(), QueryError> {
738 match clause {
739 Clause::Or { vars, branches, .. } => {
740 if vars.is_none() {
741 let mut expected: Option<BTreeSet<Var>> = None;
742 for branch in branches {
743 let mut bound = BTreeSet::new();
744 for clause in branch {
745 bound.extend(clause_vars(clause));
746 }
747 match &expected {
748 None => expected = Some(bound),
749 Some(prev) if *prev == bound => {}
750 Some(_) => {
751 return Err(QueryError::Parse(
752 "or branches must bind the same variables".into(),
753 ));
754 }
755 }
756 }
757 }
758 for branch in branches {
759 for clause in branch {
760 validate_or(clause)?;
761 }
762 }
763 Ok(())
764 }
765 Clause::Not { clauses, .. } => {
766 for clause in clauses {
767 validate_or(clause)?;
768 }
769 Ok(())
770 }
771 _ => Ok(()),
772 }
773}