Skip to main content

graphforge_ast/
ast.rs

1//! Syntax-faithful, span-rich AST for openCypher queries.
2//!
3//! Produced by `graphforge-cypher`; consumed by `graphforge-ir` (the binder). No other crate
4//! should depend on this module directly.
5//!
6//! Every public type is `Clone + Debug + PartialEq + Serialize + Deserialize`
7//! so the differential test harness can round-trip ASTs through JSON and
8//! compare parser outputs.
9//!
10//! Struct fields carry `span: Span` to locate source positions — doc comments
11//! on these repetitive span fields are omitted intentionally.
12#![allow(missing_docs)]
13
14use graphforge_core::Span;
15use serde::{Deserialize, Serialize};
16use std::collections::HashMap;
17
18// ---------------------------------------------------------------------------
19// Top-level query
20// ---------------------------------------------------------------------------
21
22/// openCypher dialect version.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
24pub enum DialectVersion {
25    /// openCypher 9 (the baseline target for GraphForge).
26    #[default]
27    OpenCypher9,
28}
29
30/// A fully parsed Cypher query.
31///
32/// At the skeleton stage `clauses` may be empty; the real parser populates it.
33#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
34pub struct AstQuery {
35    /// Cypher dialect in use.
36    pub dialect: DialectVersion,
37    /// Top-level clauses in source order.
38    pub clauses: Vec<AstClause>,
39    /// Span covering the full query source.
40    pub span: Span,
41}
42
43impl AstQuery {
44    /// Whether this query contains an explicit graph mutation clause.
45    #[must_use]
46    pub fn has_mutation_clauses(&self) -> bool {
47        self.clauses.iter().any(|clause| {
48            matches!(
49                clause,
50                AstClause::Create(_)
51                    | AstClause::Merge(_)
52                    | AstClause::Set(_)
53                    | AstClause::Remove(_)
54                    | AstClause::Delete(_)
55            )
56        })
57    }
58}
59
60// ---------------------------------------------------------------------------
61// Clause variants
62// ---------------------------------------------------------------------------
63
64/// A top-level clause in a Cypher query.
65#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
66#[non_exhaustive]
67pub enum AstClause {
68    Match(MatchClause),
69    OptionalMatch(MatchClause),
70    Where(WhereClause),
71    With(WithClause),
72    Return(ReturnClause),
73    Create(CreateClause),
74    Merge(MergeClause),
75    Set(SetClause),
76    Remove(RemoveClause),
77    Delete(DeleteClause),
78    Unwind(UnwindClause),
79    Call(CallClause),
80    Union(UnionClause),
81}
82
83impl AstClause {
84    /// Span of the clause keyword plus its body.
85    #[must_use]
86    pub fn span(&self) -> Span {
87        match self {
88            Self::Match(c) | Self::OptionalMatch(c) => c.span,
89            Self::Where(c) => c.span,
90            Self::With(c) => c.span,
91            Self::Return(c) => c.span,
92            Self::Create(c) => c.span,
93            Self::Merge(c) => c.span,
94            Self::Set(c) => c.span,
95            Self::Remove(c) => c.span,
96            Self::Delete(c) => c.span,
97            Self::Unwind(c) => c.span,
98            Self::Call(c) => c.span,
99            Self::Union(c) => c.span,
100        }
101    }
102}
103
104// ---------------------------------------------------------------------------
105// MATCH
106// ---------------------------------------------------------------------------
107
108/// A `MATCH` or `OPTIONAL MATCH` clause.
109#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
110pub struct MatchClause {
111    /// The path patterns being matched.
112    pub patterns: Vec<PathPattern>,
113    /// Optional inline `WHERE` predicate.
114    pub where_clause: Option<WhereClause>,
115    pub span: Span,
116}
117
118// ---------------------------------------------------------------------------
119// WHERE
120// ---------------------------------------------------------------------------
121
122/// A `WHERE` clause (also used inline in MATCH/WITH).
123#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
124pub struct WhereClause {
125    pub predicate: Expr,
126    pub span: Span,
127}
128
129// ---------------------------------------------------------------------------
130// WITH
131// ---------------------------------------------------------------------------
132
133/// A `WITH` clause (pipeline stage with optional ORDER/SKIP/LIMIT/WHERE).
134#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
135pub struct WithClause {
136    pub distinct: bool,
137    pub items: Vec<ReturnItem>,
138    pub order_by: Option<OrderByClause>,
139    pub skip: Option<Expr>,
140    pub limit: Option<Expr>,
141    pub where_clause: Option<WhereClause>,
142    pub span: Span,
143}
144
145// ---------------------------------------------------------------------------
146// RETURN
147// ---------------------------------------------------------------------------
148
149/// A `RETURN` clause.
150#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
151pub struct ReturnClause {
152    pub distinct: bool,
153    pub items: Vec<ReturnItem>,
154    pub order_by: Option<OrderByClause>,
155    pub skip: Option<Expr>,
156    pub limit: Option<Expr>,
157    pub span: Span,
158}
159
160/// A single item in a `RETURN` or `WITH` projection.
161#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
162pub struct ReturnItem {
163    pub expr: Expr,
164    /// Optional `AS alias` name.
165    pub alias: Option<String>,
166    /// The verbatim source text of `expr` (no `AS` part), captured by the parser
167    /// for an un-aliased item. openCypher names an un-aliased projection column by
168    /// the expression as written (`n.prop`, `count(*)`, `a.x IS NULL`); the binder
169    /// uses this as the default `RETURN` column name when there is no `alias`.
170    /// `None` for `RETURN *` and for items built outside the parser.
171    #[serde(default)]
172    pub display: Option<String>,
173    pub span: Span,
174}
175
176// ---------------------------------------------------------------------------
177// ORDER BY
178// ---------------------------------------------------------------------------
179
180/// An `ORDER BY` sub-clause carrying one or more sort keys.
181#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
182pub struct OrderByClause {
183    pub items: Vec<SortItem>,
184    pub span: Span,
185}
186
187/// A single sort key.
188#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
189pub struct SortItem {
190    pub expr: Expr,
191    pub order: SortOrder,
192    pub span: Span,
193}
194
195/// Sort direction.
196#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
197pub enum SortOrder {
198    #[default]
199    Ascending,
200    Descending,
201}
202
203// ---------------------------------------------------------------------------
204// CREATE / MERGE
205// ---------------------------------------------------------------------------
206
207/// A `CREATE` clause.
208#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
209pub struct CreateClause {
210    pub patterns: Vec<PathPattern>,
211    pub span: Span,
212}
213
214/// A `MERGE` clause (with optional ON CREATE / ON MATCH SET actions).
215#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
216pub struct MergeClause {
217    pub pattern: PathPattern,
218    pub on_create: Vec<SetItem>,
219    pub on_match: Vec<SetItem>,
220    pub span: Span,
221}
222
223// ---------------------------------------------------------------------------
224// SET / REMOVE / DELETE
225// ---------------------------------------------------------------------------
226
227/// A `SET` clause.
228#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
229pub struct SetClause {
230    pub items: Vec<SetItem>,
231    pub span: Span,
232}
233
234/// A single item inside a SET clause.
235#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
236#[non_exhaustive]
237pub enum SetItem {
238    /// `n.prop = expr`
239    Property {
240        target: PropertyAccess,
241        value: Expr,
242        span: Span,
243    },
244    /// `n += {map}` (property merge)
245    PropertyMerge { var: String, map: Expr, span: Span },
246    /// `n = {map}` (property replace)
247    PropertyReplace { var: String, map: Expr, span: Span },
248    /// `n:Label` (add label)
249    Label {
250        var: String,
251        labels: Vec<String>,
252        span: Span,
253    },
254}
255
256/// A `REMOVE` clause.
257#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
258pub struct RemoveClause {
259    pub items: Vec<RemoveItem>,
260    pub span: Span,
261}
262
263/// A single item inside a `REMOVE` clause.
264#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
265#[non_exhaustive]
266pub enum RemoveItem {
267    /// `n.prop`
268    Property(PropertyAccess, Span),
269    /// `n:Label`
270    Label {
271        var: String,
272        labels: Vec<String>,
273        span: Span,
274    },
275}
276
277/// A `DELETE` or `DETACH DELETE` clause.
278#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
279pub struct DeleteClause {
280    pub detach: bool,
281    pub exprs: Vec<Expr>,
282    pub span: Span,
283}
284
285// ---------------------------------------------------------------------------
286// UNWIND
287// ---------------------------------------------------------------------------
288
289/// An `UNWIND` clause.
290#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
291pub struct UnwindClause {
292    pub expr: Expr,
293    pub alias: String,
294    pub span: Span,
295}
296
297// ---------------------------------------------------------------------------
298// CALL
299// ---------------------------------------------------------------------------
300
301/// A `CALL procedure YIELD ...` clause.
302#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
303pub struct CallClause {
304    pub procedure: Vec<String>,
305    pub args: Vec<Expr>,
306    /// Whether the call included an explicit parenthesized argument list.
307    #[serde(default)]
308    pub args_explicit: bool,
309    pub yield_items: Vec<ReturnItem>,
310    pub span: Span,
311}
312
313// ---------------------------------------------------------------------------
314// UNION
315// ---------------------------------------------------------------------------
316
317/// A `UNION` or `UNION ALL` clause joining two query halves.
318#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
319pub struct UnionClause {
320    pub all: bool,
321    pub span: Span,
322}
323
324// ---------------------------------------------------------------------------
325// Path patterns
326// ---------------------------------------------------------------------------
327
328/// A path pattern: one or more alternating node/relationship patterns.
329#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
330pub struct PathPattern {
331    /// Optional `variable = ...` path binding.
332    pub var: Option<String>,
333    /// Elements: must start and end with a `NodePattern`, with
334    /// `RelPattern` in between.
335    pub elements: Vec<PathElement>,
336    pub span: Span,
337}
338
339/// One element in a path pattern.
340#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
341pub enum PathElement {
342    Node(NodePattern),
343    Rel(RelPattern),
344}
345
346/// A node pattern: `(var:Label {props})`.
347#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
348pub struct NodePattern {
349    /// Optional variable binding.
350    pub var: Option<String>,
351    /// Label constraints.
352    pub labels: Vec<String>,
353    /// Property constraints (map literal).
354    pub properties: Option<Expr>,
355    pub span: Span,
356}
357
358/// A relationship pattern: `-[var:TYPE*min..max {props}]->`.
359#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
360pub struct RelPattern {
361    pub var: Option<String>,
362    /// Relationship type constraints.
363    pub types: Vec<String>,
364    pub direction: Direction,
365    /// Minimum hops for variable-length patterns (`None` = exactly 1).
366    pub min_hops: Option<u32>,
367    /// Maximum hops (`None` = unbounded or exactly 1 if `min_hops` is also None).
368    pub max_hops: Option<u32>,
369    pub properties: Option<Expr>,
370    pub span: Span,
371}
372
373/// Edge direction in a relationship pattern.
374#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
375pub enum Direction {
376    /// `-[r]->` — outgoing.
377    Out,
378    /// `<-[r]-` — incoming.
379    In,
380    /// `-[r]-` — undirected.
381    Undirected,
382}
383
384// ---------------------------------------------------------------------------
385// Expressions
386// ---------------------------------------------------------------------------
387
388/// A Cypher expression.
389///
390/// `Expr` is recursive: `Box<Expr>` is used wherever a child expression is
391/// needed to keep the enum `Sized`.
392#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
393#[non_exhaustive]
394pub enum Expr {
395    /// An integer, float, string, boolean, or null literal.
396    Literal(Literal),
397    /// A variable reference: `n`.
398    Var(VarRef),
399    /// A property access: `n.prop`.
400    Property(PropertyAccess),
401    /// A binary operation: `left op right`.
402    BinaryOp(BinaryOp),
403    /// A unary operation: `op expr` (e.g. `NOT x`, `-x`).
404    UnaryOp(UnaryOp),
405    /// A function call: `name(args)` or `name(DISTINCT arg)`.
406    FunctionCall(FunctionCall),
407    /// A list literal: `[1, 2, 3]`.
408    List(ListLiteral),
409    /// A map literal: `{key: expr}`.
410    Map(MapLiteral),
411    /// A Cypher parameter: `$name`.
412    Param(ParamRef),
413    /// A `CASE` expression.
414    Case(CaseExpr),
415    /// A list comprehension: `[x IN list WHERE pred | expr]`.
416    ListComprehension(ListComprehension),
417    /// A quantifier predicate: `all/any/none/single(x IN list WHERE pred)`.
418    Quantifier(Quantifier),
419    /// A pattern comprehension: `[(n)-[r]->(m) | r.weight]`.
420    PatternComprehension(PatternComprehension),
421    /// A pattern predicate: `(n)-[:KNOWS]->(m)`.
422    PatternPredicate(PatternPredicate),
423    /// A simple or full existential subquery: `exists { (n)-->(m) }` or
424    /// `exists { MATCH (n)-->(m) RETURN true }`.
425    ExistentialSubquery(ExistentialSubquery),
426    /// A label predicate: `n:Person`.
427    LabelPredicate(LabelPredicate),
428    /// `IS NULL` / `IS NOT NULL`.
429    IsNull {
430        expr: Box<Expr>,
431        negated: bool,
432        span: Span,
433    },
434    /// `x IN list` / `x NOT IN list`.
435    InList {
436        expr: Box<Expr>,
437        list: Box<Expr>,
438        negated: bool,
439        span: Span,
440    },
441    /// `x STARTS WITH y` / `x ENDS WITH y` / `x CONTAINS y`.
442    StringOp {
443        expr: Box<Expr>,
444        op: StringOpKind,
445        pattern: Box<Expr>,
446        span: Span,
447    },
448    /// `x =~ pattern` — regular expression match.
449    RegexMatch {
450        expr: Box<Expr>,
451        pattern: Box<Expr>,
452        span: Span,
453    },
454    /// A subexpression wrapped in parentheses (preserved for span accuracy).
455    Parenthesized { inner: Box<Expr>, span: Span },
456}
457
458impl Expr {
459    /// Return the span of this expression.
460    #[must_use]
461    pub fn span(&self) -> Span {
462        match self {
463            Self::Literal(l) => l.span(),
464            Self::Var(v) => v.span,
465            Self::Property(p) => p.span,
466            Self::BinaryOp(b) => b.span,
467            Self::UnaryOp(u) => u.span,
468            Self::FunctionCall(f) => f.span,
469            Self::List(l) => l.span,
470            Self::Map(m) => m.span,
471            Self::Param(p) => p.span,
472            Self::Case(c) => c.span,
473            Self::ListComprehension(lc) => lc.span,
474            Self::Quantifier(q) => q.span,
475            Self::PatternComprehension(pc) => pc.span,
476            Self::PatternPredicate(pp) => pp.span,
477            Self::ExistentialSubquery(es) => es.span,
478            Self::LabelPredicate(lp) => lp.span,
479            Self::IsNull { span, .. }
480            | Self::InList { span, .. }
481            | Self::StringOp { span, .. }
482            | Self::RegexMatch { span, .. }
483            | Self::Parenthesized { span, .. } => *span,
484        }
485    }
486}
487
488// ---------------------------------------------------------------------------
489// Literal
490// ---------------------------------------------------------------------------
491
492/// A scalar literal value: integer, float, string, boolean, or null.
493#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
494pub enum Literal {
495    Int(i64, Span),
496    Float(f64, Span),
497    Str(String, Span),
498    Bool(bool, Span),
499    Null(Span),
500}
501
502impl Literal {
503    #[must_use]
504    pub fn span(&self) -> Span {
505        match self {
506            Self::Int(_, s)
507            | Self::Float(_, s)
508            | Self::Str(_, s)
509            | Self::Bool(_, s)
510            | Self::Null(s) => *s,
511        }
512    }
513}
514
515// ---------------------------------------------------------------------------
516// Leaf expression types
517// ---------------------------------------------------------------------------
518
519/// A variable reference: `n`.
520#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
521pub struct VarRef {
522    pub name: String,
523    pub span: Span,
524}
525
526/// A property access: `n.prop`.
527#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
528pub struct PropertyAccess {
529    pub object: Box<Expr>,
530    pub key: String,
531    pub span: Span,
532}
533
534/// A query parameter reference: `$name`.
535#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
536pub struct ParamRef {
537    pub name: String,
538    pub span: Span,
539}
540
541/// A label predicate: `n:Person` used as a boolean expression.
542#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
543pub struct LabelPredicate {
544    pub var: String,
545    pub labels: Vec<String>,
546    pub span: Span,
547}
548
549// ---------------------------------------------------------------------------
550// Compound expression types
551// ---------------------------------------------------------------------------
552
553/// A binary infix expression: `left op right`.
554#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
555pub struct BinaryOp {
556    pub op: BinaryOpKind,
557    pub left: Box<Expr>,
558    pub right: Box<Expr>,
559    pub span: Span,
560}
561
562/// All binary operators in openCypher.
563#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
564pub enum BinaryOpKind {
565    // Comparison
566    Eq,
567    Neq,
568    Lt,
569    Lte,
570    Gt,
571    Gte,
572    // Logical
573    And,
574    Or,
575    Xor,
576    // Arithmetic
577    Add,
578    Sub,
579    Mul,
580    Div,
581    Mod,
582    Pow,
583    // String
584    Concat,
585}
586
587/// A unary prefix expression: `NOT expr` or `-expr`.
588#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
589pub struct UnaryOp {
590    pub op: UnaryOpKind,
591    pub expr: Box<Expr>,
592    pub span: Span,
593}
594
595/// The operator in a unary expression.
596#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
597pub enum UnaryOpKind {
598    Not,
599    Neg,
600}
601
602/// A function call: `name(args)`, `name(DISTINCT arg)`, or `name(*)`.
603#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
604pub struct FunctionCall {
605    /// Namespace-qualified name, e.g. `["apoc", "coll", "sum"]`.
606    pub name: Vec<String>,
607    pub distinct: bool,
608    /// `true` when called as `f(*)` — `args` is empty in this case.
609    pub star: bool,
610    pub args: Vec<Expr>,
611    pub span: Span,
612}
613
614/// A list literal: `[1, 2, 3]`.
615#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
616pub struct ListLiteral {
617    pub elements: Vec<Expr>,
618    pub span: Span,
619}
620
621/// A map literal: `{key: expr, …}`.
622#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
623pub struct MapLiteral {
624    pub entries: HashMap<String, Expr>,
625    /// Exact source span for each parsed key token. Synthetic and legacy
626    /// deserialized maps may omit entries here.
627    #[serde(default)]
628    pub key_spans: HashMap<String, Span>,
629    pub span: Span,
630}
631
632// ---------------------------------------------------------------------------
633// CASE expression
634// ---------------------------------------------------------------------------
635
636/// A `CASE` expression — simple (`CASE expr WHEN … THEN …`) or searched (`CASE WHEN … THEN …`).
637#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
638pub struct CaseExpr {
639    /// `CASE expr WHEN ...` — the subject is `Some`; simple `CASE WHEN` is `None`.
640    pub subject: Option<Box<Expr>>,
641    pub when_clauses: Vec<WhenClause>,
642    pub else_expr: Option<Box<Expr>>,
643    pub span: Span,
644}
645
646/// A single `WHEN condition THEN result` arm inside a `CASE` expression.
647#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
648pub struct WhenClause {
649    pub condition: Expr,
650    pub result: Expr,
651    pub span: Span,
652}
653
654// ---------------------------------------------------------------------------
655// List / pattern comprehensions
656// ---------------------------------------------------------------------------
657
658/// A list comprehension: `[x IN list WHERE pred | projection]`.
659#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
660pub struct ListComprehension {
661    pub var: String,
662    pub list: Box<Expr>,
663    pub filter: Option<Box<Expr>>,
664    pub projection: Option<Box<Expr>>,
665    pub span: Span,
666}
667
668/// Which quantifier a [`Quantifier`] predicate applies.
669#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
670pub enum QuantifierKind {
671    /// `all(x IN list WHERE pred)` — true iff every element satisfies `pred`.
672    All,
673    /// `any(x IN list WHERE pred)` — true iff some element satisfies `pred`.
674    Any,
675    /// `none(x IN list WHERE pred)` — true iff no element satisfies `pred`.
676    None,
677    /// `single(x IN list WHERE pred)` — true iff exactly one element satisfies `pred`.
678    Single,
679}
680
681/// A quantifier predicate: `all/any/none/single(var IN list WHERE pred)`.
682#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
683pub struct Quantifier {
684    pub kind: QuantifierKind,
685    pub var: String,
686    pub list: Box<Expr>,
687    pub predicate: Box<Expr>,
688    pub span: Span,
689}
690
691/// A pattern comprehension: `[(n)-[r]->(m) WHERE pred | r.weight]`.
692#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
693pub struct PatternComprehension {
694    pub var: Option<String>,
695    pub pattern: PathPattern,
696    pub filter: Option<Box<Expr>>,
697    pub projection: Box<Expr>,
698    pub span: Span,
699}
700
701/// A pattern predicate used as a boolean expression, e.g. `(n)-->()`.
702#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
703pub struct PatternPredicate {
704    pub pattern: PathPattern,
705    pub span: Span,
706}
707
708/// A simple or full existential subquery.
709#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
710pub struct ExistentialSubquery {
711    pub body: ExistentialSubqueryBody,
712    pub span: Span,
713}
714
715#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
716pub enum ExistentialSubqueryBody {
717    Simple {
718        pattern: PathPattern,
719        filter: Option<Box<Expr>>,
720    },
721    Full(Box<AstQuery>),
722}
723
724// ---------------------------------------------------------------------------
725// String operations
726// ---------------------------------------------------------------------------
727
728/// The operation in a `STARTS WITH` / `ENDS WITH` / `CONTAINS` string predicate.
729#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
730pub enum StringOpKind {
731    StartsWith,
732    EndsWith,
733    Contains,
734}