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