Skip to main content

khive_query/
ast.rs

1//! GQL abstract syntax tree.
2
3use std::collections::HashMap;
4
5/// A SQL parameter value emitted by the query compiler.
6#[derive(Clone, Debug)]
7pub enum QueryValue {
8    Null,
9    Integer(i64),
10    Float(f64),
11    Text(String),
12    Blob(Vec<u8>),
13}
14
15/// Top-level GQL query node produced by the parser.
16#[derive(Debug, Clone)]
17pub struct GqlQuery {
18    pub pattern: MatchPattern,
19    pub where_clause: WhereExpr,
20    pub return_items: Vec<ReturnItem>,
21    pub limit: Option<usize>,
22}
23
24/// A WHERE expression tree supporting AND, OR, and leaf conditions.
25#[derive(Debug, Clone)]
26pub enum WhereExpr {
27    /// AND of two sub-expressions.
28    And(Box<WhereExpr>, Box<WhereExpr>),
29    /// OR of two sub-expressions.
30    Or(Box<WhereExpr>, Box<WhereExpr>),
31    /// A single scalar condition.
32    Condition(Condition),
33    /// Always-true — used when there is no WHERE clause.
34    True,
35}
36
37impl WhereExpr {
38    /// Iterate all leaf conditions in the expression tree (depth-first).
39    pub fn conditions(&self) -> impl Iterator<Item = &Condition> {
40        let mut stack = vec![self];
41        let mut out: Vec<&Condition> = Vec::new();
42        while let Some(expr) = stack.pop() {
43            match expr {
44                WhereExpr::Condition(c) => out.push(c),
45                WhereExpr::And(l, r) | WhereExpr::Or(l, r) => {
46                    stack.push(r);
47                    stack.push(l);
48                }
49                WhereExpr::True => {}
50            }
51        }
52        out.into_iter()
53    }
54
55    /// Mutable walk — applies `f` to every leaf condition.
56    pub fn for_each_condition_mut(&mut self, f: &mut impl FnMut(&mut Condition)) {
57        match self {
58            WhereExpr::Condition(c) => f(c),
59            WhereExpr::And(l, r) | WhereExpr::Or(l, r) => {
60                l.for_each_condition_mut(f);
61                r.for_each_condition_mut(f);
62            }
63            WhereExpr::True => {}
64        }
65    }
66
67    /// Return `true` when the expression has no conditions (is always-true).
68    pub fn is_true(&self) -> bool {
69        matches!(self, WhereExpr::True)
70    }
71}
72
73/// A single item in the RETURN clause — either a bound variable or a property projection.
74#[derive(Debug, Clone, PartialEq, Eq)]
75pub enum ReturnItem {
76    Variable(String),
77    Property(String, String),
78}
79
80impl ReturnItem {
81    /// Returns the variable name bound to this return item.
82    pub fn variable(&self) -> &str {
83        match self {
84            Self::Variable(v) | Self::Property(v, _) => v,
85        }
86    }
87}
88
89/// The MATCH pattern of a GQL query, as an alternating sequence of node and edge elements.
90#[derive(Debug, Clone)]
91pub struct MatchPattern {
92    pub elements: Vec<PatternElement>,
93}
94
95impl MatchPattern {
96    /// Iterate over the `NodePattern` elements in this MATCH pattern.
97    pub fn nodes(&self) -> impl Iterator<Item = &NodePattern> {
98        self.elements.iter().filter_map(|e| match e {
99            PatternElement::Node(n) => Some(n),
100            _ => None,
101        })
102    }
103
104    /// Iterate over the `EdgePattern` elements in this MATCH pattern.
105    pub fn edges(&self) -> impl Iterator<Item = &EdgePattern> {
106        self.elements.iter().filter_map(|e| match e {
107            PatternElement::Edge(e) => Some(e),
108            _ => None,
109        })
110    }
111
112    pub fn has_variable_length(&self) -> bool {
113        self.edges().any(|e| e.max_hops > 1)
114    }
115}
116
117/// A single element in the MATCH pattern -- either a node or an edge.
118#[derive(Debug, Clone)]
119pub enum PatternElement {
120    Node(NodePattern),
121    Edge(EdgePattern),
122}
123
124/// A node binding in the MATCH pattern with optional kind, entity_type, and property filters.
125#[derive(Debug, Clone)]
126pub struct NodePattern {
127    pub variable: Option<String>,
128    pub kind: Option<String>,
129    /// Governed subtype within the kind (e.g. "researcher" within "person").
130    /// Compiled to `entity_type = ?` — a direct column, not a property extraction.
131    pub entity_type: Option<String>,
132    pub properties: HashMap<String, String>,
133}
134
135/// An edge binding in the MATCH pattern with optional relation filters, direction, and hop bounds.
136#[derive(Debug, Clone)]
137pub struct EdgePattern {
138    pub variable: Option<String>,
139    pub relations: Vec<String>,
140    pub direction: EdgeDirection,
141    pub min_hops: usize,
142    pub max_hops: usize,
143}
144
145/// Traversal direction for an edge in the MATCH pattern.
146#[derive(Debug, Clone, Copy, PartialEq, Eq)]
147pub enum EdgeDirection {
148    /// Outgoing only — `(a)-->(b)`.
149    Out,
150    /// Incoming only — `(a)<--(b)`.
151    In,
152    /// Either direction — `(a)--(b)`.
153    Both,
154}
155
156/// A scalar comparison in the WHERE clause: `variable.property op value`.
157#[derive(Debug, Clone)]
158pub struct Condition {
159    pub variable: String,
160    pub property: String,
161    pub op: CompareOp,
162    pub value: ConditionValue,
163}
164
165/// Comparison operator used in WHERE clause conditions.
166#[derive(Debug, Clone, Copy, PartialEq, Eq)]
167pub enum CompareOp {
168    Eq,
169    Neq,
170    Gt,
171    Lt,
172    Gte,
173    Lte,
174    Like,
175}
176
177/// Right-hand side value in a WHERE condition.
178#[derive(Debug, Clone)]
179pub enum ConditionValue {
180    String(String),
181    Number(f64),
182    Bool(bool),
183}