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