Skip to main content

khive_query/
ast.rs

1//! Shared query AST for GQL/SPARQL parsing and SQL compilation.
2
3use std::collections::HashMap;
4
5/// A backend-independent SQL parameter emitted by the compiler.
6///
7/// See `crates/khive-query/docs/api/ast.md` for value and precision semantics.
8#[derive(Clone, Debug)]
9pub enum QueryValue {
10    Null,
11    Integer(i64),
12    Float(f64),
13    Text(String),
14    Blob(Vec<u8>),
15}
16
17/// A parsed query: pattern, predicates, projections, row offset, and optional row limit.
18#[derive(Debug, Clone)]
19pub struct GqlQuery {
20    pub pattern: MatchPattern,
21    pub where_clause: WhereExpr,
22    pub return_items: Vec<ReturnItem>,
23    /// Number of deterministically ordered matches to skip before returning rows.
24    ///
25    /// GQL supplies this through `SKIP`; SPARQL currently always sets it to zero.
26    pub offset: usize,
27    pub limit: Option<usize>,
28}
29
30/// A WHERE expression tree that preserves AND/OR grouping.
31#[derive(Debug, Clone)]
32pub enum WhereExpr {
33    /// AND of two sub-expressions.
34    And(Box<WhereExpr>, Box<WhereExpr>),
35    /// OR of two sub-expressions.
36    Or(Box<WhereExpr>, Box<WhereExpr>),
37    /// A single scalar condition.
38    Condition(Condition),
39    /// Always-true — used when there is no WHERE clause.
40    True,
41}
42
43impl WhereExpr {
44    /// Iterate all leaf conditions in the expression tree (depth-first).
45    pub fn conditions(&self) -> impl Iterator<Item = &Condition> {
46        let mut stack = vec![self];
47        let mut out: Vec<&Condition> = Vec::new();
48        while let Some(expr) = stack.pop() {
49            match expr {
50                WhereExpr::Condition(c) => out.push(c),
51                WhereExpr::And(l, r) | WhereExpr::Or(l, r) => {
52                    stack.push(r);
53                    stack.push(l);
54                }
55                WhereExpr::True => {}
56            }
57        }
58        out.into_iter()
59    }
60
61    /// Mutable walk — applies `f` to every leaf condition.
62    pub fn for_each_condition_mut(&mut self, f: &mut impl FnMut(&mut Condition)) {
63        match self {
64            WhereExpr::Condition(c) => f(c),
65            WhereExpr::And(l, r) | WhereExpr::Or(l, r) => {
66                l.for_each_condition_mut(f);
67                r.for_each_condition_mut(f);
68            }
69            WhereExpr::True => {}
70        }
71    }
72
73    /// Return `true` when the expression has no conditions (is always-true).
74    pub fn is_true(&self) -> bool {
75        matches!(self, WhereExpr::True)
76    }
77}
78
79/// A single item in the RETURN clause — either a bound variable or a property projection.
80#[derive(Debug, Clone, PartialEq, Eq)]
81pub enum ReturnItem {
82    Variable(String),
83    Property(String, String),
84}
85
86impl ReturnItem {
87    /// Returns the variable name bound to this return item.
88    pub fn variable(&self) -> &str {
89        match self {
90            Self::Variable(v) | Self::Property(v, _) => v,
91        }
92    }
93}
94
95/// The MATCH pattern of a GQL query, as an alternating sequence of node and edge elements.
96#[derive(Debug, Clone)]
97pub struct MatchPattern {
98    pub elements: Vec<PatternElement>,
99}
100
101impl MatchPattern {
102    /// Iterate over the `NodePattern` elements in this MATCH pattern.
103    pub fn nodes(&self) -> impl Iterator<Item = &NodePattern> {
104        self.elements.iter().filter_map(|e| match e {
105            PatternElement::Node(n) => Some(n),
106            _ => None,
107        })
108    }
109
110    /// Iterate over the `EdgePattern` elements in this MATCH pattern.
111    pub fn edges(&self) -> impl Iterator<Item = &EdgePattern> {
112        self.elements.iter().filter_map(|e| match e {
113            PatternElement::Edge(e) => Some(e),
114            _ => None,
115        })
116    }
117
118    /// Returns whether any edge permits more than one hop.
119    pub fn has_variable_length(&self) -> bool {
120        self.edges().any(|e| e.max_hops > 1)
121    }
122}
123
124/// A single element in the MATCH pattern -- either a node or an edge.
125#[derive(Debug, Clone)]
126pub enum PatternElement {
127    Node(NodePattern),
128    Edge(EdgePattern),
129}
130
131/// A node binding with optional kind, governed subtype, and property filters.
132#[derive(Debug, Clone)]
133pub struct NodePattern {
134    pub variable: Option<String>,
135    pub kind: Option<String>,
136    /// Governed subtype compiled against the dedicated `entity_type` column.
137    pub entity_type: Option<String>,
138    pub properties: HashMap<String, ConditionValue>,
139}
140
141/// An edge binding with relation alternatives, direction, and inclusive hop bounds.
142#[derive(Debug, Clone)]
143pub struct EdgePattern {
144    pub variable: Option<String>,
145    pub relations: Vec<String>,
146    pub direction: EdgeDirection,
147    pub min_hops: usize,
148    pub max_hops: usize,
149}
150
151/// Traversal direction for an edge in the MATCH pattern.
152#[derive(Debug, Clone, Copy, PartialEq, Eq)]
153pub enum EdgeDirection {
154    /// Outgoing only — `(a)-->(b)`.
155    Out,
156    /// Incoming only — `(a)<--(b)`.
157    In,
158    /// Either direction — `(a)--(b)`.
159    Both,
160}
161
162/// A field or JSON-property path referenced by a WHERE condition.
163#[derive(Debug, Clone, PartialEq, Eq)]
164pub enum PropertyRef {
165    /// A dedicated field on the bound substrate, such as `name` or `created_at`.
166    Field(String),
167    /// A path below the bound record's JSON `properties` object.
168    JsonPath(Vec<String>),
169}
170
171/// A predicate in the WHERE clause: `variable.field op value` or
172/// `variable.properties.path op value`.
173#[derive(Debug, Clone)]
174pub struct Condition {
175    pub variable: String,
176    pub property: PropertyRef,
177    pub op: CompareOp,
178    pub value: ConditionValue,
179}
180
181/// Comparison operator used in WHERE clause conditions.
182#[derive(Debug, Clone, Copy, PartialEq, Eq)]
183pub enum CompareOp {
184    Eq,
185    Neq,
186    Gt,
187    Lt,
188    Gte,
189    Lte,
190    Like,
191    Contains,
192    StartsWith,
193    In,
194    IsNotNull,
195    IsNull,
196}
197
198/// A typed condition value; integer and decimal forms remain distinct.
199///
200/// See `crates/khive-query/docs/api/ast.md` for binding and precision details.
201#[derive(Debug, Clone, PartialEq)]
202pub enum ConditionValue {
203    String(String),
204    /// Exact `i64` literal whose source lexeme had no decimal point.
205    Integer(i64),
206    /// A float literal (decimal point present in the source lexeme).
207    Number(f64),
208    Bool(bool),
209    /// A list literal used by the `IN` operator.
210    List(Vec<ConditionValue>),
211    /// The operand marker for the operand-free `IS NOT NULL` operator.
212    Null,
213}