1use corium_query::edn::Edn;
26
27use crate::pull::Pull;
28use crate::value::IntoEdn;
29
30#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
33pub struct Var(String);
34
35impl Var {
36 #[must_use]
38 pub fn new(name: impl AsRef<str>) -> Self {
39 let name = name.as_ref();
40 if name.starts_with('?') {
41 Self(name.to_owned())
42 } else {
43 Self(format!("?{name}"))
44 }
45 }
46
47 #[must_use]
49 pub fn as_str(&self) -> &str {
50 &self.0
51 }
52
53 fn to_edn(&self) -> Edn {
54 Edn::Symbol(self.0.clone())
55 }
56}
57
58#[must_use]
60pub fn var(name: impl AsRef<str>) -> Var {
61 Var::new(name)
62}
63
64#[must_use]
67pub fn attr(name: &str) -> Edn {
68 Edn::keyword(name)
69}
70
71#[must_use]
73pub fn kw(name: &str) -> Edn {
74 Edn::keyword(name)
75}
76
77#[must_use]
80pub fn lit(value: impl IntoEdn) -> Term {
81 Term::Const(value.into_edn())
82}
83
84#[must_use]
86pub fn blank() -> Term {
87 Term::Blank
88}
89
90#[derive(Clone, Debug, Eq, PartialEq)]
92pub enum Term {
93 Var(Var),
95 Blank,
97 Const(Edn),
99}
100
101impl Term {
102 fn to_edn(&self) -> Edn {
103 match self {
104 Self::Var(variable) => variable.to_edn(),
105 Self::Blank => Edn::symbol("_"),
106 Self::Const(form) => form.clone(),
107 }
108 }
109}
110
111impl From<Var> for Term {
112 fn from(value: Var) -> Self {
113 Self::Var(value)
114 }
115}
116
117impl From<Edn> for Term {
118 fn from(value: Edn) -> Self {
119 Self::Const(value)
120 }
121}
122
123#[derive(Clone, Debug, Eq, PartialEq)]
125pub enum FindElem {
126 Var(Var),
128 Pull(Var, Pull),
130 Aggregate {
132 op: String,
134 n: Option<i64>,
136 var: Var,
138 },
139}
140
141impl FindElem {
142 fn to_edn(&self) -> Edn {
143 match self {
144 Self::Var(variable) => variable.to_edn(),
145 Self::Pull(entity, pattern) => {
146 Edn::List(vec![Edn::symbol("pull"), entity.to_edn(), pattern.to_edn()])
147 }
148 Self::Aggregate { op, n, var } => {
149 let mut items = vec![Edn::symbol(op)];
150 if let Some(n) = n {
151 items.push(Edn::Long(*n));
152 }
153 items.push(var.to_edn());
154 Edn::List(items)
155 }
156 }
157 }
158}
159
160impl From<Var> for FindElem {
161 fn from(value: Var) -> Self {
162 Self::Var(value)
163 }
164}
165
166#[must_use]
168pub fn pull_expr(entity: Var, pattern: Pull) -> FindElem {
169 FindElem::Pull(entity, pattern)
170}
171
172macro_rules! aggregate_fn {
173 ($(#[$meta:meta])* $name:ident => $op:literal) => {
174 $(#[$meta])*
175 #[must_use]
176 pub fn $name(var: Var) -> FindElem {
177 FindElem::Aggregate { op: $op.to_owned(), n: None, var }
178 }
179 };
180}
181
182aggregate_fn!(count => "count");
184aggregate_fn!(count_distinct => "count-distinct");
186aggregate_fn!(sum => "sum");
188aggregate_fn!(avg => "avg");
190aggregate_fn!(min => "min");
192aggregate_fn!(max => "max");
194
195#[must_use]
197pub fn agg_n(op: &str, n: i64, var: Var) -> FindElem {
198 FindElem::Aggregate {
199 op: op.to_owned(),
200 n: Some(n),
201 var,
202 }
203}
204
205#[derive(Clone, Debug, Eq, PartialEq)]
207enum Find {
208 Rel(Vec<FindElem>),
210 Coll(FindElem),
212 Tuple(Vec<FindElem>),
214 Scalar(FindElem),
216}
217
218impl Find {
219 fn to_find_items(&self) -> Vec<Edn> {
220 match self {
221 Self::Rel(elems) => elems.iter().map(FindElem::to_edn).collect(),
222 Self::Coll(elem) => {
223 vec![Edn::Vector(vec![elem.to_edn(), Edn::symbol("...")])]
224 }
225 Self::Tuple(elems) => {
226 vec![Edn::Vector(elems.iter().map(FindElem::to_edn).collect())]
227 }
228 Self::Scalar(elem) => vec![elem.to_edn(), Edn::symbol(".")],
229 }
230 }
231}
232
233#[derive(Clone, Debug, Eq, PartialEq)]
235pub enum Binding {
236 Scalar(Var),
238 Tuple(Vec<Var>),
240 Coll(Var),
242 Rel(Vec<Var>),
244}
245
246impl Binding {
247 fn to_edn(&self) -> Edn {
248 match self {
249 Self::Scalar(var) => var.to_edn(),
250 Self::Tuple(vars) => Edn::Vector(vars.iter().map(Var::to_edn).collect()),
251 Self::Coll(var) => Edn::Vector(vec![var.to_edn(), Edn::symbol("...")]),
252 Self::Rel(vars) => {
253 Edn::Vector(vec![Edn::Vector(vars.iter().map(Var::to_edn).collect())])
254 }
255 }
256 }
257}
258
259#[derive(Clone, Debug, Eq, PartialEq)]
261pub enum Clause {
262 Pattern(Pattern),
264 Predicate {
266 name: String,
268 args: Vec<Term>,
270 },
271 Function {
273 name: String,
275 args: Vec<Term>,
277 binding: Binding,
279 },
280 Not {
282 vars: Option<Vec<Var>>,
284 clauses: Vec<Clause>,
286 },
287 Or {
289 vars: Option<Vec<Var>>,
291 branches: Vec<Vec<Clause>>,
293 },
294 Rule {
296 name: String,
298 args: Vec<Term>,
300 },
301}
302
303impl Clause {
304 fn to_edn(&self) -> Edn {
305 match self {
306 Self::Pattern(pattern) => pattern.to_edn(),
307 Self::Predicate { name, args } => Edn::Vector(vec![call_form(name, args)]),
308 Self::Function {
309 name,
310 args,
311 binding,
312 } => Edn::Vector(vec![call_form(name, args), binding.to_edn()]),
313 Self::Not { vars, clauses } => {
314 let mut items = Vec::new();
315 match vars {
316 Some(vars) => {
317 items.push(Edn::symbol("not-join"));
318 items.push(Edn::Vector(vars.iter().map(Var::to_edn).collect()));
319 }
320 None => items.push(Edn::symbol("not")),
321 }
322 items.extend(clauses.iter().map(Clause::to_edn));
323 Edn::List(items)
324 }
325 Self::Or { vars, branches } => {
326 let mut items = Vec::new();
327 match vars {
328 Some(vars) => {
329 items.push(Edn::symbol("or-join"));
330 items.push(Edn::Vector(vars.iter().map(Var::to_edn).collect()));
331 }
332 None => items.push(Edn::symbol("or")),
333 }
334 for branch in branches {
335 if branch.len() == 1 {
336 items.push(branch[0].to_edn());
337 } else {
338 let mut and = vec![Edn::symbol("and")];
339 and.extend(branch.iter().map(Clause::to_edn));
340 items.push(Edn::List(and));
341 }
342 }
343 Edn::List(items)
344 }
345 Self::Rule { name, args } => {
346 let mut items = vec![Edn::symbol(name)];
347 items.extend(args.iter().map(Term::to_edn));
348 Edn::List(items)
349 }
350 }
351 }
352}
353
354fn call_form(name: &str, args: &[Term]) -> Edn {
355 let mut items = vec![Edn::symbol(name)];
356 items.extend(args.iter().map(Term::to_edn));
357 Edn::List(items)
358}
359
360#[derive(Clone, Debug, Eq, PartialEq)]
363pub struct Pattern {
364 src: Option<String>,
365 e: Term,
366 a: Term,
367 v: Term,
368 tx: Option<Term>,
369 added: Option<Term>,
370}
371
372impl Pattern {
373 #[must_use]
375 pub fn src(mut self, src: impl Into<String>) -> Self {
376 self.src = Some(src.into());
377 self
378 }
379
380 #[must_use]
382 pub fn tx(mut self, tx: impl Into<Term>) -> Self {
383 self.tx = Some(tx.into());
384 self
385 }
386
387 #[must_use]
389 pub fn added(mut self, added: impl Into<Term>) -> Self {
390 self.added = Some(added.into());
391 self
392 }
393
394 fn to_edn(&self) -> Edn {
395 let mut items = Vec::with_capacity(6);
396 if let Some(src) = &self.src {
397 items.push(Edn::symbol(src));
398 }
399 items.push(self.e.to_edn());
400 items.push(self.a.to_edn());
401 items.push(self.v.to_edn());
402 if self.tx.is_some() || self.added.is_some() {
405 items.push(self.tx.as_ref().map_or(Edn::symbol("_"), Term::to_edn));
406 }
407 if let Some(added) = &self.added {
408 items.push(added.to_edn());
409 }
410 Edn::Vector(items)
411 }
412}
413
414impl From<Pattern> for Clause {
415 fn from(value: Pattern) -> Self {
416 Self::Pattern(value)
417 }
418}
419
420#[must_use]
423pub fn data(e: impl Into<Term>, a: impl Into<Term>, v: impl Into<Term>) -> Pattern {
424 Pattern {
425 src: None,
426 e: e.into(),
427 a: a.into(),
428 v: v.into(),
429 tx: None,
430 added: None,
431 }
432}
433
434#[must_use]
436pub fn pred(name: impl Into<String>, args: Vec<Term>) -> Clause {
437 Clause::Predicate {
438 name: name.into(),
439 args,
440 }
441}
442
443macro_rules! comparison_fn {
444 ($(#[$meta:meta])* $name:ident => $op:literal) => {
445 $(#[$meta])*
446 #[must_use]
447 pub fn $name(left: impl Into<Term>, right: impl Into<Term>) -> Clause {
448 Clause::Predicate { name: $op.to_owned(), args: vec![left.into(), right.into()] }
449 }
450 };
451}
452
453comparison_fn!(gt => ">");
455comparison_fn!(lt => "<");
457comparison_fn!(gte => ">=");
459comparison_fn!(lte => "<=");
461comparison_fn!(eq => "=");
463comparison_fn!(neq => "not=");
465
466#[derive(Clone, Debug, Eq, PartialEq)]
468pub struct Call {
469 name: String,
470 args: Vec<Term>,
471}
472
473impl Call {
474 #[must_use]
477 pub fn bind(self, binding: Binding) -> Clause {
478 Clause::Function {
479 name: self.name,
480 args: self.args,
481 binding,
482 }
483 }
484
485 #[must_use]
487 pub fn bind_scalar(self, var: Var) -> Clause {
488 self.bind(Binding::Scalar(var))
489 }
490}
491
492#[must_use]
494pub fn call(name: impl Into<String>, args: Vec<Term>) -> Call {
495 Call {
496 name: name.into(),
497 args,
498 }
499}
500
501fn collect_clauses<C: Into<Clause>>(clauses: impl IntoIterator<Item = C>) -> Vec<Clause> {
502 clauses.into_iter().map(Into::into).collect()
503}
504
505fn collect_branches<B, C>(branches: impl IntoIterator<Item = B>) -> Vec<Vec<Clause>>
506where
507 B: IntoIterator<Item = C>,
508 C: Into<Clause>,
509{
510 branches.into_iter().map(collect_clauses).collect()
511}
512
513#[must_use]
515pub fn not<C: Into<Clause>>(clauses: impl IntoIterator<Item = C>) -> Clause {
516 Clause::Not {
517 vars: None,
518 clauses: collect_clauses(clauses),
519 }
520}
521
522#[must_use]
524pub fn not_join<C: Into<Clause>>(vars: Vec<Var>, clauses: impl IntoIterator<Item = C>) -> Clause {
525 Clause::Not {
526 vars: Some(vars),
527 clauses: collect_clauses(clauses),
528 }
529}
530
531#[must_use]
534pub fn or<B, C>(branches: impl IntoIterator<Item = B>) -> Clause
535where
536 B: IntoIterator<Item = C>,
537 C: Into<Clause>,
538{
539 Clause::Or {
540 vars: None,
541 branches: collect_branches(branches),
542 }
543}
544
545#[must_use]
547pub fn or_join<B, C>(vars: Vec<Var>, branches: impl IntoIterator<Item = B>) -> Clause
548where
549 B: IntoIterator<Item = C>,
550 C: Into<Clause>,
551{
552 Clause::Or {
553 vars: Some(vars),
554 branches: collect_branches(branches),
555 }
556}
557
558#[must_use]
560pub fn rule(name: impl Into<String>, args: Vec<Term>) -> Clause {
561 Clause::Rule {
562 name: name.into(),
563 args,
564 }
565}
566
567#[derive(Clone, Debug, Eq, PartialEq)]
569enum InSpec {
570 Db(String),
571 Rules,
572 Scalar(Var),
573 Tuple(Vec<Var>),
574 Coll(Var),
575 Rel(Vec<Var>),
576}
577
578impl InSpec {
579 fn to_edn(&self) -> Edn {
580 match self {
581 Self::Db(name) => Edn::symbol(name),
582 Self::Rules => Edn::symbol("%"),
583 Self::Scalar(var) => var.to_edn(),
584 Self::Tuple(vars) => Edn::Vector(vars.iter().map(Var::to_edn).collect()),
585 Self::Coll(var) => Edn::Vector(vec![var.to_edn(), Edn::symbol("...")]),
586 Self::Rel(vars) => {
587 Edn::Vector(vec![Edn::Vector(vars.iter().map(Var::to_edn).collect())])
588 }
589 }
590 }
591}
592
593#[derive(Clone, Debug, Eq, PartialEq)]
600pub struct Query {
601 find: Find,
602 with: Vec<Var>,
603 inputs: Vec<InSpec>,
604 wheres: Vec<Clause>,
605}
606
607impl Query {
608 fn from_find(find: Find) -> Self {
609 Self {
610 find,
611 with: Vec::new(),
612 inputs: Vec::new(),
613 wheres: Vec::new(),
614 }
615 }
616
617 #[must_use]
619 pub fn find<E: Into<FindElem>>(elems: impl IntoIterator<Item = E>) -> Self {
620 Self::from_find(Find::Rel(elems.into_iter().map(Into::into).collect()))
621 }
622
623 #[must_use]
626 pub fn find_rel(elems: Vec<FindElem>) -> Self {
627 Self::from_find(Find::Rel(elems))
628 }
629
630 #[must_use]
632 pub fn find_coll(elem: impl Into<FindElem>) -> Self {
633 Self::from_find(Find::Coll(elem.into()))
634 }
635
636 #[must_use]
638 pub fn find_tuple<E: Into<FindElem>>(elems: impl IntoIterator<Item = E>) -> Self {
639 Self::from_find(Find::Tuple(elems.into_iter().map(Into::into).collect()))
640 }
641
642 #[must_use]
644 pub fn find_scalar(elem: impl Into<FindElem>) -> Self {
645 Self::from_find(Find::Scalar(elem.into()))
646 }
647
648 #[must_use]
651 pub fn with(mut self, vars: impl IntoIterator<Item = Var>) -> Self {
652 self.with.extend(vars);
653 self
654 }
655
656 #[must_use]
659 pub fn in_db(mut self, name: impl Into<String>) -> Self {
660 self.inputs.push(InSpec::Db(name.into()));
661 self
662 }
663
664 #[must_use]
666 pub fn in_rules(mut self) -> Self {
667 self.inputs.push(InSpec::Rules);
668 self
669 }
670
671 #[must_use]
673 pub fn in_scalar(mut self, var: Var) -> Self {
674 self.inputs.push(InSpec::Scalar(var));
675 self
676 }
677
678 #[must_use]
680 pub fn in_tuple(mut self, vars: Vec<Var>) -> Self {
681 self.inputs.push(InSpec::Tuple(vars));
682 self
683 }
684
685 #[must_use]
687 pub fn in_coll(mut self, var: Var) -> Self {
688 self.inputs.push(InSpec::Coll(var));
689 self
690 }
691
692 #[must_use]
694 pub fn in_rel(mut self, vars: Vec<Var>) -> Self {
695 self.inputs.push(InSpec::Rel(vars));
696 self
697 }
698
699 #[must_use]
702 pub fn where_(mut self, clause: impl Into<Clause>) -> Self {
703 self.wheres.push(clause.into());
704 self
705 }
706
707 #[must_use]
709 pub fn and(mut self, clause: impl Into<Clause>) -> Self {
710 self.wheres.push(clause.into());
711 self
712 }
713
714 #[must_use]
718 pub fn has_inputs(&self) -> bool {
719 self.inputs
720 .iter()
721 .any(|spec| !matches!(spec, InSpec::Db(_)))
722 }
723
724 #[must_use]
726 pub fn to_edn(&self) -> Edn {
727 let mut pairs = Vec::with_capacity(4);
728 pairs.push((Edn::keyword("find"), Edn::Vector(self.find.to_find_items())));
729 if !self.with.is_empty() {
730 pairs.push((
731 Edn::keyword("with"),
732 Edn::Vector(self.with.iter().map(Var::to_edn).collect()),
733 ));
734 }
735 if !self.inputs.is_empty() {
736 let mut ins = vec![Edn::symbol("$")];
737 ins.extend(self.inputs.iter().map(InSpec::to_edn));
738 pairs.push((Edn::keyword("in"), Edn::Vector(ins)));
739 }
740 pairs.push((
741 Edn::keyword("where"),
742 Edn::Vector(self.wheres.iter().map(Clause::to_edn).collect()),
743 ));
744 Edn::Map(pairs)
745 }
746}
747
748#[cfg(test)]
749mod tests {
750 use super::*;
751 use corium_query::ast::parse_query;
752
753 fn assert_parses(query: &Query) {
755 let edn = query.to_edn();
756 parse_query(&edn).unwrap_or_else(|error| panic!("query did not parse: {error}\n{edn}"));
757 }
758
759 #[test]
760 fn relation_with_predicate_and_input() {
761 let query = Query::find([var("name"), var("age")])
762 .in_scalar(var("min"))
763 .where_(data(var("e"), attr("person/name"), var("name")))
764 .and(data(var("e"), attr("person/age"), var("age")))
765 .and(gte(var("age"), var("min")));
766 assert_parses(&query);
767 assert_eq!(
768 query.to_edn().to_string(),
769 "{:find [?name ?age], :in [$ ?min], :where [[?e :person/name ?name] \
770 [?e :person/age ?age] [(>= ?age ?min)]]}"
771 );
772 }
773
774 #[test]
775 fn scalar_coll_tuple_shapes_parse() {
776 assert_parses(&Query::find_scalar(var("e")).where_(data(
777 var("e"),
778 attr("db/ident"),
779 blank(),
780 )));
781 assert_parses(&Query::find_coll(var("e")).where_(data(
782 var("e"),
783 attr("db/ident"),
784 blank(),
785 )));
786 assert_parses(&Query::find_tuple([var("e"), var("a")]).where_(data(
787 var("e"),
788 attr("db/ident"),
789 var("a"),
790 )));
791 }
792
793 #[test]
794 fn aggregate_and_function_clauses_parse() {
795 assert_parses(
796 &Query::find_rel(vec![min(var("age")), max(var("age"))]).where_(data(
797 var("e"),
798 attr("person/age"),
799 var("age"),
800 )),
801 );
802 let with_count = Query::find_rel(vec![FindElem::Var(var("name")), count(var("e"))])
803 .where_(data(var("e"), attr("person/name"), var("name")));
804 assert_parses(&with_count);
805 assert_parses(
806 &Query::find([var("total")])
807 .where_(data(var("e"), attr("order/subtotal"), var("sub")))
808 .and(data(var("e"), attr("order/tax"), var("tax")))
809 .and(
810 call("+", vec![var("sub").into(), var("tax").into()]).bind_scalar(var("total")),
811 ),
812 );
813 }
814
815 #[test]
816 fn not_or_and_rules_parse() {
817 assert_parses(
818 &Query::find([var("e")])
819 .where_(data(var("e"), attr("person/name"), blank()))
820 .and(not(vec![data(
821 var("e"),
822 attr("person/deceased"),
823 lit(true),
824 )])),
825 );
826 assert_parses(&Query::find([var("e")]).where_(or(vec![
827 vec![data(var("e"), attr("person/name"), lit("Alice"))],
828 vec![data(var("e"), attr("person/name"), lit("Bob"))],
829 ])));
830 assert_parses(
831 &Query::find([var("e")])
832 .in_rules()
833 .where_(rule("ancestor", vec![var("e").into(), var("x").into()])),
834 );
835 }
836}