Skip to main content

corium_query/
ast.rs

1//! Query AST and parsing from EDN forms (map and vector shapes).
2
3use std::collections::BTreeSet;
4
5use crate::QueryError;
6use crate::edn::Edn;
7
8/// A logic variable (`?name`).
9pub type Var = String;
10
11/// Default database source name.
12pub const DEFAULT_SRC: &str = "$";
13
14/// A parsed query.
15#[derive(Clone, Debug, Eq, PartialEq)]
16pub struct Query {
17    /// The `:find` specification.
18    pub find: FindSpec,
19    /// Extra grouping variables (`:with`).
20    pub with: Vec<Var>,
21    /// Input bindings (`:in`), defaulting to `[$]`.
22    pub inputs: Vec<InSpec>,
23    /// The `:where` clauses.
24    pub wheres: Vec<Clause>,
25}
26
27/// The shape of `:find`.
28#[derive(Clone, Debug, Eq, PartialEq)]
29pub enum FindSpec {
30    /// Relation of tuples.
31    Rel(Vec<FindElem>),
32    /// Collection `[?x …]`.
33    Coll(FindElem),
34    /// Single tuple `[?x ?y]`.
35    Tuple(Vec<FindElem>),
36    /// Scalar `?x .`.
37    Scalar(FindElem),
38}
39
40impl FindSpec {
41    /// All find elements in order.
42    #[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/// One element of a find specification.
52#[derive(Clone, Debug, Eq, PartialEq)]
53pub enum FindElem {
54    /// A plain variable.
55    Var(Var),
56    /// An aggregate call `(op args… ?var)`.
57    Aggregate(Aggregate),
58    /// A pull expression `(pull ?e pattern)`.
59    Pull(Var, Edn),
60}
61
62impl FindElem {
63    /// The variable this element projects.
64    #[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/// An aggregate find element.
74#[derive(Clone, Debug, Eq, PartialEq)]
75pub struct Aggregate {
76    /// Operation name (`count`, `sum`, …).
77    pub op: String,
78    /// Optional leading constant argument (`(min 3 ?x)`).
79    pub n: Option<i64>,
80    /// Aggregated variable.
81    pub var: Var,
82}
83
84/// One `:in` binding.
85#[derive(Clone, Debug, Eq, PartialEq)]
86pub enum InSpec {
87    /// A database source (`$`, `$hist`, …).
88    Db(String),
89    /// A rule set (`%`).
90    Rules,
91    /// Scalar binding `?x`.
92    Scalar(Var),
93    /// Tuple binding `[?x ?y]`.
94    Tuple(Vec<Var>),
95    /// Collection binding `[?x …]`.
96    Coll(Var),
97    /// Relation binding `[[?x ?y]]`.
98    Rel(Vec<Var>),
99}
100
101/// A term in a pattern or call position.
102#[derive(Clone, Debug, Eq, PartialEq)]
103pub enum Term {
104    /// A variable.
105    Var(Var),
106    /// The blank `_`.
107    Blank,
108    /// A constant EDN form, resolved against a database at execution.
109    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/// A data pattern with up to five positions (`added` binds on history views).
123#[derive(Clone, Debug, Eq, PartialEq)]
124pub struct Pattern {
125    /// Database source name.
126    pub src: String,
127    /// Entity position.
128    pub e: Term,
129    /// Attribute position.
130    pub a: Term,
131    /// Value position.
132    pub v: Term,
133    /// Transaction position.
134    pub tx: Term,
135    /// Assertion flag position.
136    pub added: Term,
137}
138
139/// A `:where` clause.
140#[derive(Clone, Debug, Eq, PartialEq)]
141pub enum Clause {
142    /// A data pattern.
143    Pattern(Pattern),
144    /// A predicate `[(pred args…)]`.
145    Pred {
146        /// Predicate name.
147        name: String,
148        /// Argument terms.
149        args: Vec<Term>,
150    },
151    /// A function `[(f args…) binding]`.
152    Fn {
153        /// Function name.
154        name: String,
155        /// Argument terms.
156        args: Vec<Term>,
157        /// Output binding.
158        binding: Binding,
159    },
160    /// `not` / `not-join`.
161    Not {
162        /// Database source name.
163        src: String,
164        /// Join variables for `not-join`; `None` for plain `not`.
165        vars: Option<Vec<Var>>,
166        /// Negated clauses.
167        clauses: Vec<Clause>,
168    },
169    /// `or` / `or-join`.
170    Or {
171        /// Database source name.
172        src: String,
173        /// Join variables for `or-join`; `None` for plain `or`.
174        vars: Option<Vec<Var>>,
175        /// Alternative clause groups.
176        branches: Vec<Vec<Clause>>,
177    },
178    /// A rule invocation `(rule-name args…)`.
179    RuleCall {
180        /// Rule name.
181        name: String,
182        /// Argument terms.
183        args: Vec<Term>,
184    },
185}
186
187/// One rule definition.
188#[derive(Clone, Debug, Eq, PartialEq)]
189pub struct RuleDef {
190    /// Rule name.
191    pub name: String,
192    /// Head variables that must be bound at invocation.
193    pub required: Vec<Var>,
194    /// Remaining head variables.
195    pub free: Vec<Var>,
196    /// Body clauses.
197    pub clauses: Vec<Clause>,
198}
199
200impl RuleDef {
201    /// All head variables in argument order.
202    #[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
223/// Parses a query from its EDN map form or vector form.
224///
225/// # Errors
226/// Returns [`QueryError::Parse`] for malformed queries and
227/// [`QueryError::Unbound`] when find/with variables never appear in `:where`
228/// or `:in`.
229pub 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
277/// Rewrites the vector form `[:find … :in … :where …]` into map shape.
278fn 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    // Scalar: `?x .`
314    if items.len() == 2 && items[1].as_symbol() == Some(".") {
315        return Ok(FindSpec::Scalar(parse_find_elem(&items[0])?));
316    }
317    // Collection `[?x …]` or single tuple `[?x ?y]`.
318    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
407/// Parses one `:where` clause.
408///
409/// # Errors
410/// Returns [`QueryError::Parse`] for malformed clause forms.
411pub 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    // `[(f …)]` predicate, `[(f …) binding]` function.
421    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    // Data pattern with optional leading src.
441    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/// A function clause output binding.
577#[derive(Clone, Debug, Eq, PartialEq)]
578pub enum Binding {
579    /// `?x`.
580    Scalar(Var),
581    /// `[?x ?y]`.
582    Tuple(Vec<BindTarget>),
583    /// `[?x …]`.
584    Coll(BindTarget),
585    /// `[[?x ?y]]`.
586    Rel(Vec<BindTarget>),
587}
588
589/// A binding target: a variable or the blank.
590#[derive(Clone, Debug, Eq, PartialEq)]
591pub enum BindTarget {
592    /// A variable.
593    Var(Var),
594    /// `_`.
595    Blank,
596}
597
598/// Parses a rule set from its EDN form: `[[(name head…) clause…]…]`.
599///
600/// # Errors
601/// Returns [`QueryError::Parse`] for malformed rule definitions.
602pub 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/// Variables bound by a clause (produced, not merely consumed).
648#[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    // `or` branches must agree on the variables they bind.
731    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}