Skip to main content

marsdb_query/
ast.rs

1#[derive(Debug, Clone, PartialEq)]
2pub enum Literal {
3    Int(i64),
4    Float(f64),
5    String(String),
6    Bool(bool),
7    Null,
8    /// `$name` placeholder — resolved to a concrete `Literal` by
9    /// `params::substitute_params` before execution, never seen by the
10    /// executor.
11    Param(String),
12}
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct PropAccess {
16    pub var: String,
17    pub prop: String,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum CompareOp {
22    Eq,
23    Ne,
24    Lt,
25    Le,
26    Gt,
27    Ge,
28}
29
30#[derive(Debug, Clone)]
31pub enum Expr {
32    And(Box<Expr>, Box<Expr>),
33    Or(Box<Expr>, Box<Expr>),
34    Not(Box<Expr>),
35    Compare(PropAccess, CompareOp, Literal),
36    /// Does the node bound to `var` have label `label` among its (possibly
37    /// multiple) labels? Synthesized by the planner for the 2nd+ label in a
38    /// multi-label pattern like `(n:Post:Message)` — never user-typed.
39    HasLabel(String, String),
40    /// Do these two row bindings refer to the same node/edge? Synthesized
41    /// by the planner when a pattern's hop variable is a "bound-node
42    /// repetition" — the same variable already bound earlier reappearing
43    /// mid-pattern (e.g. IS7's `p`, bound by an earlier MATCH, reappearing
44    /// as the endpoint of an OPTIONAL MATCH pattern: `(a)-[r:KNOWS]-(p)`
45    /// must mean "KNOWS *this* `p`", not "KNOWS anyone"). Never user-typed.
46    VarEq(String, String),
47}
48
49#[derive(Debug, Clone)]
50pub enum ReturnExpr {
51    Var(String),
52    Prop(PropAccess),
53    Lit(Literal),
54    Call(String, Vec<ReturnExpr>),
55    /// Simple/value `CASE`: `CASE <test> WHEN <value> THEN <result> ... [ELSE
56    /// <else>] END`. `test` is `Some` for every form the parser currently
57    /// produces; kept `Option` so a future searched-`CASE` (`CASE WHEN
58    /// <bool_expr> THEN ...`) can reuse this variant without another type
59    /// change.
60    Case {
61        test: Option<Box<ReturnExpr>>,
62        whens: Vec<(ReturnExpr, ReturnExpr)>,
63        else_: Option<Box<ReturnExpr>>,
64    },
65}
66
67#[derive(Debug, Clone)]
68pub struct ReturnItem {
69    pub expr: ReturnExpr,
70    pub alias: Option<String>,
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum SortDir {
75    Asc,
76    Desc,
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub enum RelDirection {
81    /// (a)-[..]->(b)
82    Right,
83    /// (a)<-[..]-(b)
84    Left,
85    /// (a)-[..]-(b) — matches either direction.
86    Either,
87}
88
89#[derive(Debug, Clone)]
90pub struct NodePattern {
91    pub var: Option<String>,
92    pub labels: Vec<String>,
93    pub props: Vec<(String, Literal)>,
94}
95
96#[derive(Debug, Clone)]
97pub struct RelPattern {
98    pub var: Option<String>,
99    pub rel_type: Option<String>,
100    #[allow(dead_code)] // property-map on relationships parsed but not filtered on in v1
101    pub props: Vec<(String, Literal)>,
102    pub direction: RelDirection,
103    /// `[:TYPE*min..max]` — `None` means a fixed single hop (existing
104    /// behavior). `max: None` means unbounded, capped at a safety depth by
105    /// the executor.
106    pub hop_range: Option<(u32, Option<u32>)>,
107}
108
109/// A linear chain: node, (rel, node)*.
110#[derive(Debug, Clone)]
111pub struct Pattern {
112    pub start: NodePattern,
113    pub hops: Vec<(RelPattern, NodePattern)>,
114}
115
116#[derive(Debug, Clone)]
117pub enum Tail {
118    Return(Vec<ReturnItem>),
119    Delete(Vec<String>),
120    DetachDelete(Vec<String>),
121    Set(Vec<(PropAccess, Literal)>),
122}
123
124/// A `WITH` clause: projects/renames the current bindings, optionally
125/// filtered/sorted/limited at that boundary, and becomes the binding scope
126/// for whatever follows (the next `QueryPart`, or the final `Tail`).
127#[derive(Debug, Clone)]
128pub struct WithClause {
129    pub items: Vec<ReturnItem>,
130    pub order_by: Option<Vec<(ReturnExpr, SortDir)>>,
131    pub limit: Option<i64>,
132}
133
134/// One `MATCH <pattern>[, <pattern>...] [WHERE ...] [WITH ...]` segment.
135/// Comma-separated patterns within one part are spliced into a single
136/// linear `Pattern` at parse time (see `parser::splice_patterns`) — this
137/// only ever holds one already-combined `Pattern`, not several.
138#[derive(Debug, Clone)]
139pub struct QueryPart {
140    pub optional: bool,
141    pub pattern: Pattern,
142    pub where_clause: Option<Expr>,
143    pub with: Option<WithClause>,
144}
145
146#[derive(Debug, Clone)]
147pub enum Statement {
148    Create(Vec<Pattern>),
149    Match {
150        /// One or more `MATCH ... [WITH ...]` segments. The parser enforces
151        /// every part except the last has a `with` (matching real Cypher's
152        /// rule that multiple reading clauses must be separated by WITH)
153        /// and that at most one part has a `with` at all (v1 doesn't
154        /// support chaining past one WITH boundary — nothing in the target
155        /// query set needs it, and it keeps a hand-rolled parser's
156        /// untested-path surface smaller).
157        parts: Vec<QueryPart>,
158        tail: Tail,
159        /// Only meaningful for `Tail::Return`; evaluated against the
160        /// projected/aliased output row, not the raw pattern bindings —
161        /// every ORDER BY key in practice is a RETURN alias, not a bare
162        /// pattern variable.
163        order_by: Option<Vec<(ReturnExpr, SortDir)>>,
164        limit: Option<i64>,
165    },
166}