Skip to main content

sochdb_query/sql/
ast.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2// SochDB - LLM-Optimized Embedded Database
3// Copyright (C) 2026 Sushanth Reddy Vanagala (https://github.com/sushanthpy)
4//
5// This program is free software: you can redistribute it and/or modify
6// it under the terms of the GNU Affero General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU Affero General Public License for more details.
14//
15// You should have received a copy of the GNU Affero General Public License
16// along with this program. If not, see <https://www.gnu.org/licenses/>.
17
18//! SQL Abstract Syntax Tree
19//!
20//! Represents parsed SQL statements as a tree structure.
21
22use super::token::Span;
23
24/// Top-level SQL statement
25#[derive(Debug, Clone, PartialEq)]
26#[allow(clippy::large_enum_variant)]
27pub enum Statement {
28    Select(SelectStmt),
29    Insert(InsertStmt),
30    Update(UpdateStmt),
31    Delete(DeleteStmt),
32    CreateTable(CreateTableStmt),
33    DropTable(DropTableStmt),
34    AlterTable(AlterTableStmt),
35    CreateIndex(CreateIndexStmt),
36    DropIndex(DropIndexStmt),
37    Begin(BeginStmt),
38    Commit,
39    Rollback(Option<String>), // Optional savepoint name
40    Savepoint(String),
41    Release(String),
42    Explain(Box<Statement>),
43
44    // ====================================================================
45    // Security DDL (P2 — Scope-Based Auth)
46    // ====================================================================
47    /// DEFINE SCOPE <name> SESSION <duration>
48    ///   SIGNIN (<expr>)
49    ///   SIGNUP (<expr>)
50    DefineScope(DefineScopeStmt),
51
52    /// DEFINE TABLE <name> PERMISSIONS
53    ///   FOR select WHERE <expr>
54    ///   FOR create WHERE <expr>
55    ///   FOR update WHERE <expr>
56    ///   FOR delete WHERE <expr>
57    DefineTablePermissions(DefineTablePermissionsStmt),
58
59    /// REMOVE SCOPE <name>
60    RemoveScope(String),
61
62    // ====================================================================
63    // Graph & Real-Time (P1 — Multi-Model)
64    // ====================================================================
65    /// RELATE <from> -> <edge> -> <to> [SET ... | CONTENT ...]
66    Relate(RelateStmt),
67
68    /// LIVE SELECT [DIFF] <columns> FROM <table> [WHERE ...]
69    LiveSelect(LiveSelectStmt),
70
71    /// DEFINE EVENT <name> ON TABLE <table> WHEN <condition> THEN <action>
72    DefineEvent(DefineEventStmt),
73}
74
75/// SELECT statement
76#[derive(Debug, Clone, PartialEq)]
77pub struct SelectStmt {
78    pub span: Span,
79    pub distinct: bool,
80    pub columns: Vec<SelectItem>,
81    pub from: Option<FromClause>,
82    pub where_clause: Option<Expr>,
83    pub group_by: Vec<Expr>,
84    pub having: Option<Expr>,
85    pub order_by: Vec<OrderByItem>,
86    pub limit: Option<Expr>,
87    pub offset: Option<Expr>,
88    pub unions: Vec<(SetOp, Box<SelectStmt>)>,
89}
90
91/// Items in SELECT clause
92#[derive(Debug, Clone, PartialEq)]
93pub enum SelectItem {
94    /// SELECT *
95    Wildcard,
96    /// SELECT table.*
97    QualifiedWildcard(String),
98    /// SELECT expr [AS alias]
99    Expr { expr: Expr, alias: Option<String> },
100}
101
102/// Set operations (UNION, INTERSECT, EXCEPT)
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub enum SetOp {
105    Union,
106    UnionAll,
107    Intersect,
108    IntersectAll,
109    Except,
110    ExceptAll,
111}
112
113/// FROM clause
114#[derive(Debug, Clone, PartialEq)]
115pub struct FromClause {
116    pub tables: Vec<TableRef>,
117}
118
119/// Table reference in FROM clause
120#[derive(Debug, Clone, PartialEq)]
121pub enum TableRef {
122    /// Simple table: table_name [AS alias]
123    Table {
124        name: ObjectName,
125        alias: Option<String>,
126    },
127    /// Subquery: (SELECT ...) AS alias
128    Subquery {
129        query: Box<SelectStmt>,
130        alias: String,
131    },
132    /// Join: left JOIN right ON condition
133    Join {
134        left: Box<TableRef>,
135        join_type: JoinType,
136        right: Box<TableRef>,
137        condition: Option<JoinCondition>,
138    },
139    /// Table-valued function: func(...) AS alias
140    Function {
141        name: String,
142        args: Vec<Expr>,
143        alias: Option<String>,
144    },
145}
146
147/// Join types
148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub enum JoinType {
150    Inner,
151    Left,
152    Right,
153    Full,
154    Cross,
155}
156
157/// Join condition
158#[derive(Debug, Clone, PartialEq)]
159pub enum JoinCondition {
160    On(Expr),
161    Using(Vec<String>),
162    Natural,
163}
164
165/// ORDER BY item
166#[derive(Debug, Clone, PartialEq)]
167pub struct OrderByItem {
168    pub expr: Expr,
169    pub asc: bool,
170    pub nulls_first: Option<bool>,
171}
172
173/// INSERT statement
174#[derive(Debug, Clone, PartialEq)]
175pub struct InsertStmt {
176    pub span: Span,
177    pub table: ObjectName,
178    pub columns: Option<Vec<String>>,
179    pub source: InsertSource,
180    pub on_conflict: Option<OnConflict>,
181    pub returning: Option<Vec<SelectItem>>,
182}
183
184/// Source of INSERT data
185#[derive(Debug, Clone, PartialEq)]
186pub enum InsertSource {
187    /// VALUES (a, b), (c, d), ...
188    Values(Vec<Vec<Expr>>),
189    /// SELECT ...
190    Query(Box<SelectStmt>),
191    /// DEFAULT VALUES
192    Default,
193}
194
195/// ON CONFLICT clause
196///
197/// Represents conflict handling for INSERT statements across SQL dialects:
198/// - PostgreSQL: `ON CONFLICT DO NOTHING/UPDATE`
199/// - MySQL: `INSERT IGNORE`, `ON DUPLICATE KEY UPDATE`
200/// - SQLite: `INSERT OR IGNORE/REPLACE/ABORT`
201///
202/// All dialects normalize to this single representation.
203#[derive(Debug, Clone, PartialEq)]
204pub struct OnConflict {
205    pub target: Option<ConflictTarget>,
206    pub action: ConflictAction,
207}
208
209#[derive(Debug, Clone, PartialEq)]
210pub enum ConflictTarget {
211    Columns(Vec<String>),
212    Constraint(String),
213}
214
215#[derive(Debug, Clone, PartialEq)]
216pub enum ConflictAction {
217    /// ON CONFLICT DO NOTHING / INSERT IGNORE / INSERT OR IGNORE
218    DoNothing,
219    /// ON CONFLICT DO UPDATE SET ... / ON DUPLICATE KEY UPDATE ...
220    DoUpdate(Vec<Assignment>),
221    /// INSERT OR REPLACE (SQLite) - replaces the entire row
222    DoReplace,
223    /// INSERT OR ABORT (SQLite) - abort on conflict (default behavior)
224    DoAbort,
225    /// INSERT OR FAIL (SQLite) - fail but continue with other rows
226    DoFail,
227}
228
229/// UPDATE statement
230#[derive(Debug, Clone, PartialEq)]
231pub struct UpdateStmt {
232    pub span: Span,
233    pub table: ObjectName,
234    pub alias: Option<String>,
235    pub assignments: Vec<Assignment>,
236    pub from: Option<FromClause>,
237    pub where_clause: Option<Expr>,
238    pub returning: Option<Vec<SelectItem>>,
239}
240
241/// Assignment: column = expr
242#[derive(Debug, Clone, PartialEq)]
243pub struct Assignment {
244    pub column: String,
245    pub value: Expr,
246}
247
248/// DELETE statement
249#[derive(Debug, Clone, PartialEq)]
250pub struct DeleteStmt {
251    pub span: Span,
252    pub table: ObjectName,
253    pub alias: Option<String>,
254    pub using: Option<FromClause>,
255    pub where_clause: Option<Expr>,
256    pub returning: Option<Vec<SelectItem>>,
257}
258
259/// CREATE TABLE statement
260#[derive(Debug, Clone, PartialEq)]
261pub struct CreateTableStmt {
262    pub span: Span,
263    pub if_not_exists: bool,
264    pub name: ObjectName,
265    pub columns: Vec<ColumnDef>,
266    pub constraints: Vec<TableConstraint>,
267    pub options: Vec<TableOption>,
268}
269
270/// Column definition
271#[derive(Debug, Clone, PartialEq)]
272pub struct ColumnDef {
273    pub name: String,
274    pub data_type: DataType,
275    pub constraints: Vec<ColumnConstraint>,
276}
277
278/// SQL Data types
279#[derive(Debug, Clone, PartialEq)]
280pub enum DataType {
281    // Numeric
282    TinyInt,
283    SmallInt,
284    Int,
285    BigInt,
286    Float,
287    Double,
288    Decimal {
289        precision: Option<u32>,
290        scale: Option<u32>,
291    },
292
293    // String
294    Char(Option<u32>),
295    Varchar(Option<u32>),
296    Text,
297
298    // Binary
299    Binary(Option<u32>),
300    Varbinary(Option<u32>),
301    Blob,
302
303    // Date/Time
304    Date,
305    Time,
306    Timestamp,
307    DateTime,
308    Interval,
309
310    // Boolean
311    Boolean,
312
313    // JSON
314    Json,
315    Jsonb,
316
317    // SochDB Extensions
318    Vector(u32),    // VECTOR(dimensions)
319    Embedding(u32), // EMBEDDING(dimensions)
320
321    // Custom/Unknown
322    Custom(String),
323}
324
325/// Column constraints
326#[derive(Debug, Clone, PartialEq)]
327pub enum ColumnConstraint {
328    NotNull,
329    Null,
330    Unique,
331    PrimaryKey,
332    Default(Expr),
333    Check(Expr),
334    References {
335        table: ObjectName,
336        columns: Vec<String>,
337        on_delete: Option<ReferentialAction>,
338        on_update: Option<ReferentialAction>,
339    },
340    AutoIncrement,
341    Generated {
342        expr: Expr,
343        stored: bool,
344    },
345}
346
347/// Table-level constraints
348#[derive(Debug, Clone, PartialEq)]
349pub enum TableConstraint {
350    PrimaryKey {
351        name: Option<String>,
352        columns: Vec<String>,
353    },
354    Unique {
355        name: Option<String>,
356        columns: Vec<String>,
357    },
358    ForeignKey {
359        name: Option<String>,
360        columns: Vec<String>,
361        ref_table: ObjectName,
362        ref_columns: Vec<String>,
363        on_delete: Option<ReferentialAction>,
364        on_update: Option<ReferentialAction>,
365    },
366    Check {
367        name: Option<String>,
368        expr: Expr,
369    },
370}
371
372#[derive(Debug, Clone, Copy, PartialEq, Eq)]
373pub enum ReferentialAction {
374    NoAction,
375    Restrict,
376    Cascade,
377    SetNull,
378    SetDefault,
379}
380
381/// Table options (ENGINE, CHARSET, etc.)
382#[derive(Debug, Clone, PartialEq)]
383pub struct TableOption {
384    pub name: String,
385    pub value: String,
386}
387
388/// DROP TABLE statement
389#[derive(Debug, Clone, PartialEq)]
390pub struct DropTableStmt {
391    pub span: Span,
392    pub if_exists: bool,
393    pub names: Vec<ObjectName>,
394    pub cascade: bool,
395}
396
397/// ALTER TABLE statement
398#[derive(Debug, Clone, PartialEq)]
399pub struct AlterTableStmt {
400    pub span: Span,
401    pub name: ObjectName,
402    pub operations: Vec<AlterTableOp>,
403}
404
405#[derive(Debug, Clone, PartialEq)]
406pub enum AlterTableOp {
407    AddColumn(ColumnDef),
408    DropColumn {
409        name: String,
410        cascade: bool,
411    },
412    AlterColumn {
413        name: String,
414        operation: AlterColumnOp,
415    },
416    AddConstraint(TableConstraint),
417    DropConstraint {
418        name: String,
419        cascade: bool,
420    },
421    RenameTable(ObjectName),
422    RenameColumn {
423        old_name: String,
424        new_name: String,
425    },
426}
427
428#[derive(Debug, Clone, PartialEq)]
429pub enum AlterColumnOp {
430    SetType(DataType),
431    SetNotNull,
432    DropNotNull,
433    SetDefault(Expr),
434    DropDefault,
435}
436
437/// CREATE INDEX statement
438#[derive(Debug, Clone, PartialEq)]
439pub struct CreateIndexStmt {
440    pub span: Span,
441    pub unique: bool,
442    pub if_not_exists: bool,
443    pub name: String,
444    pub table: ObjectName,
445    pub columns: Vec<IndexColumn>,
446    pub where_clause: Option<Expr>,
447    pub index_type: Option<IndexType>,
448}
449
450#[derive(Debug, Clone, PartialEq)]
451pub struct IndexColumn {
452    pub name: String,
453    pub asc: bool,
454    pub nulls_first: Option<bool>,
455}
456
457#[derive(Debug, Clone, Copy, PartialEq, Eq)]
458pub enum IndexType {
459    BTree,
460    Hash,
461    Gin,
462    Gist,
463    // SochDB extensions
464    Hnsw,   // For vector search
465    Vamana, // For vector search
466}
467
468/// DROP INDEX statement
469#[derive(Debug, Clone, PartialEq)]
470pub struct DropIndexStmt {
471    pub span: Span,
472    pub if_exists: bool,
473    pub name: String,
474    pub table: Option<ObjectName>,
475    pub cascade: bool,
476}
477
478/// BEGIN statement
479#[derive(Debug, Clone, PartialEq)]
480pub struct BeginStmt {
481    pub read_only: bool,
482    pub isolation_level: Option<IsolationLevel>,
483}
484
485#[derive(Debug, Clone, Copy, PartialEq, Eq)]
486pub enum IsolationLevel {
487    ReadUncommitted,
488    ReadCommitted,
489    RepeatableRead,
490    Serializable,
491    Snapshot,
492}
493
494/// Object name (potentially qualified: schema.table)
495#[derive(Debug, Clone, PartialEq, Eq, Hash)]
496pub struct ObjectName {
497    pub parts: Vec<String>,
498}
499
500impl ObjectName {
501    pub fn new(name: impl Into<String>) -> Self {
502        Self {
503            parts: vec![name.into()],
504        }
505    }
506
507    pub fn qualified(schema: impl Into<String>, name: impl Into<String>) -> Self {
508        Self {
509            parts: vec![schema.into(), name.into()],
510        }
511    }
512
513    pub fn name(&self) -> &str {
514        self.parts.last().map(|s| s.as_str()).unwrap_or("")
515    }
516
517    pub fn schema(&self) -> Option<&str> {
518        if self.parts.len() > 1 {
519            Some(&self.parts[self.parts.len() - 2])
520        } else {
521            None
522        }
523    }
524}
525
526impl std::fmt::Display for ObjectName {
527    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
528        write!(f, "{}", self.parts.join("."))
529    }
530}
531
532/// Expression
533#[derive(Debug, Clone, PartialEq)]
534pub enum Expr {
535    /// Literal value
536    Literal(Literal),
537
538    /// Column reference: [table.]column
539    Column(ColumnRef),
540
541    /// Binary operation: expr op expr
542    BinaryOp {
543        left: Box<Expr>,
544        op: BinaryOperator,
545        right: Box<Expr>,
546    },
547
548    /// Unary operation: op expr
549    UnaryOp { op: UnaryOperator, expr: Box<Expr> },
550
551    /// Function call: func(args)
552    Function(FunctionCall),
553
554    /// CASE expression
555    Case {
556        operand: Option<Box<Expr>>,
557        conditions: Vec<(Expr, Expr)>, // (WHEN, THEN)
558        else_result: Option<Box<Expr>>,
559    },
560
561    /// Subquery: (SELECT ...)
562    Subquery(Box<SelectStmt>),
563
564    /// EXISTS (SELECT ...)
565    Exists(Box<SelectStmt>),
566
567    /// expr IN (values)
568    InList {
569        expr: Box<Expr>,
570        list: Vec<Expr>,
571        negated: bool,
572    },
573
574    /// expr IN (SELECT ...)
575    InSubquery {
576        expr: Box<Expr>,
577        subquery: Box<SelectStmt>,
578        negated: bool,
579    },
580
581    /// expr BETWEEN low AND high
582    Between {
583        expr: Box<Expr>,
584        low: Box<Expr>,
585        high: Box<Expr>,
586        negated: bool,
587    },
588
589    /// expr LIKE pattern [ESCAPE escape]
590    Like {
591        expr: Box<Expr>,
592        pattern: Box<Expr>,
593        escape: Option<Box<Expr>>,
594        negated: bool,
595    },
596
597    /// expr IS [NOT] NULL
598    IsNull { expr: Box<Expr>, negated: bool },
599
600    /// CAST(expr AS type)
601    Cast {
602        expr: Box<Expr>,
603        data_type: DataType,
604    },
605
606    /// Placeholder: $1, $2, ?
607    Placeholder(u32),
608
609    /// Array: [a, b, c] or ARRAY[a, b, c]
610    Array(Vec<Expr>),
611
612    /// Tuple/Row: (a, b, c)
613    Tuple(Vec<Expr>),
614
615    /// Array subscript: arr[index]
616    Subscript { expr: Box<Expr>, index: Box<Expr> },
617
618    // ========== SochDB Extensions ==========
619    /// Vector literal: [1.0, 2.0, 3.0]::VECTOR
620    Vector(Vec<f32>),
621
622    /// Vector search: VECTOR_SEARCH(column, query_vector, k, metric)
623    VectorSearch {
624        column: Box<Expr>,
625        query: Box<Expr>,
626        k: u32,
627        metric: VectorMetric,
628    },
629
630    /// JSON path: json_col -> 'path'
631    JsonAccess {
632        expr: Box<Expr>,
633        path: Box<Expr>,
634        return_text: bool, // -> vs ->>
635    },
636
637    /// Context window for LLM: CONTEXT_WINDOW(tokens, priority_expr)
638    ContextWindow {
639        source: Box<Expr>,
640        max_tokens: u32,
641        priority: Option<Box<Expr>>,
642    },
643
644    /// Record ID literal: table:id (e.g. person:1, post:abc)
645    RecordId { table: String, id: Box<Expr> },
646}
647
648/// Literal values
649#[derive(Debug, Clone, PartialEq)]
650pub enum Literal {
651    Null,
652    Boolean(bool),
653    Integer(i64),
654    Float(f64),
655    String(String),
656    Blob(Vec<u8>),
657}
658
659/// Column reference
660#[derive(Debug, Clone, PartialEq)]
661pub struct ColumnRef {
662    pub table: Option<String>,
663    pub column: String,
664}
665
666impl ColumnRef {
667    pub fn new(column: impl Into<String>) -> Self {
668        Self {
669            table: None,
670            column: column.into(),
671        }
672    }
673
674    pub fn qualified(table: impl Into<String>, column: impl Into<String>) -> Self {
675        Self {
676            table: Some(table.into()),
677            column: column.into(),
678        }
679    }
680}
681
682/// Binary operators
683#[derive(Debug, Clone, Copy, PartialEq, Eq)]
684pub enum BinaryOperator {
685    // Arithmetic
686    Plus,
687    Minus,
688    Multiply,
689    Divide,
690    Modulo,
691
692    // Comparison
693    Eq,
694    Ne,
695    Lt,
696    Le,
697    Gt,
698    Ge,
699
700    // Logical
701    And,
702    Or,
703
704    // String
705    Concat,
706    Like,
707
708    // Bitwise
709    BitAnd,
710    BitOr,
711    BitXor,
712    LeftShift,
713    RightShift,
714
715    // Graph traversal
716    GraphRight, // ->  (outgoing edge)
717    GraphLeft,  // <-  (incoming edge)
718    GraphBi,    // <-> (bidirectional edge)
719}
720
721/// Unary operators
722#[derive(Debug, Clone, Copy, PartialEq, Eq)]
723pub enum UnaryOperator {
724    Plus,
725    Minus,
726    Not,
727    BitNot,
728}
729
730/// Function call
731#[derive(Debug, Clone, PartialEq)]
732pub struct FunctionCall {
733    pub name: ObjectName,
734    pub args: Vec<Expr>,
735    pub distinct: bool,
736    pub filter: Option<Box<Expr>>,
737    pub over: Option<WindowSpec>,
738}
739
740/// Window specification for window functions
741#[derive(Debug, Clone, PartialEq)]
742pub struct WindowSpec {
743    pub partition_by: Vec<Expr>,
744    pub order_by: Vec<OrderByItem>,
745    pub frame: Option<WindowFrame>,
746}
747
748#[derive(Debug, Clone, PartialEq)]
749pub struct WindowFrame {
750    pub kind: WindowFrameKind,
751    pub start: WindowFrameBound,
752    pub end: Option<WindowFrameBound>,
753}
754
755#[derive(Debug, Clone, Copy, PartialEq, Eq)]
756pub enum WindowFrameKind {
757    Rows,
758    Range,
759    Groups,
760}
761
762#[derive(Debug, Clone, PartialEq)]
763pub enum WindowFrameBound {
764    CurrentRow,
765    Preceding(Option<Box<Expr>>), // None = UNBOUNDED
766    Following(Option<Box<Expr>>), // None = UNBOUNDED
767}
768
769/// Vector distance metrics (SochDB extension)
770#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
771pub enum VectorMetric {
772    #[default]
773    Cosine,
774    Euclidean,
775    DotProduct,
776    Manhattan,
777}
778
779// ============================================================================
780// Security DDL AST nodes (P2 — Scope-Based Auth)
781// ============================================================================
782
783/// DEFINE SCOPE statement — creates an authentication scope.
784///
785/// Mirrors SurrealDB's `DEFINE SCOPE`:
786/// ```sql
787/// DEFINE SCOPE user_scope SESSION 24h
788///   SIGNIN (SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(password, $password))
789///   SIGNUP (CREATE user SET email = $email, password = crypto::argon2::generate($password))
790/// ```
791#[derive(Debug, Clone, PartialEq)]
792pub struct DefineScopeStmt {
793    /// Scope name (e.g. "user_scope")
794    pub name: String,
795    /// Session duration in seconds (e.g. 86400 for 24h)
796    pub session_duration_secs: Option<u64>,
797    /// SIGNIN expression — evaluated to authenticate
798    pub signin: Option<Box<Expr>>,
799    /// SIGNUP expression — evaluated to register
800    pub signup: Option<Box<Expr>>,
801}
802
803/// DEFINE TABLE PERMISSIONS — row-level security policies per operation.
804///
805/// Mirrors SurrealDB's per-table permissions:
806/// ```sql
807/// DEFINE TABLE post PERMISSIONS
808///   FOR select WHERE published = true OR user = $auth.id
809///   FOR create WHERE $auth.role = 'editor'
810///   FOR update WHERE user = $auth.id
811///   FOR delete WHERE $auth.role = 'admin'
812/// ```
813#[derive(Debug, Clone, PartialEq)]
814pub struct DefineTablePermissionsStmt {
815    /// Table name
816    pub table: ObjectName,
817    /// Permission rules per operation
818    pub permissions: Vec<TablePermission>,
819}
820
821/// A single permission rule for a table operation.
822#[derive(Debug, Clone, PartialEq)]
823pub struct TablePermission {
824    /// Operation this permission applies to
825    pub operation: PermissionOp,
826    /// WHERE clause — row is accessible if this evaluates to true
827    pub condition: Expr,
828}
829
830/// Operations that can have per-table permissions.
831#[derive(Debug, Clone, Copy, PartialEq, Eq)]
832pub enum PermissionOp {
833    Select,
834    Create,
835    Update,
836    Delete,
837}
838
839// ============================================================================
840// Graph & Real-Time AST nodes (P1 — Multi-Model)
841// ============================================================================
842
843/// RELATE statement — creates a graph edge between two records.
844///
845/// ```sql
846/// RELATE person:1 -> knows -> person:2 SET since = '2024-01-01';
847/// RELATE person:1 -> knows -> person:2 CONTENT { "since": "2024-01-01" };
848/// ```
849#[derive(Debug, Clone, PartialEq)]
850pub struct RelateStmt {
851    pub span: Span,
852    /// Source record (e.g. person:1)
853    pub from: Expr,
854    /// Edge table name (e.g. knows)
855    pub edge: ObjectName,
856    /// Target record (e.g. person:2)
857    pub to: Expr,
858    /// SET field = value, ...
859    pub set: Vec<Assignment>,
860    /// CONTENT { ... } — alternative to SET
861    pub content: Option<Expr>,
862    /// RETURNING clause
863    pub returning: Option<Vec<SelectItem>>,
864}
865
866/// LIVE SELECT statement — subscribes to real-time changes on a table.
867///
868/// ```sql
869/// LIVE SELECT * FROM person WHERE age > 18;
870/// LIVE SELECT DIFF FROM person;
871/// ```
872#[derive(Debug, Clone, PartialEq)]
873pub struct LiveSelectStmt {
874    pub span: Span,
875    /// The underlying SELECT statement
876    pub select: SelectStmt,
877    /// Whether to return diffs instead of full records
878    pub diff: bool,
879}
880
881/// DEFINE EVENT statement — registers a trigger on table mutations.
882///
883/// ```sql
884/// DEFINE EVENT email_notification ON TABLE user
885///   WHEN $event = "CREATE"
886///   THEN (CREATE notification SET body = "Welcome " + $after.name);
887/// ```
888#[derive(Debug, Clone, PartialEq)]
889pub struct DefineEventStmt {
890    pub span: Span,
891    /// Event name
892    pub name: String,
893    /// Table this event is attached to
894    pub table: ObjectName,
895    /// WHEN condition
896    pub condition: Expr,
897    /// THEN action (typically a statement wrapped in parens)
898    pub action: Expr,
899}