Skip to main content

selene_gql/ast/
statement.rs

1//! Statement AST nodes.
2
3use selene_core::DbString;
4
5use crate::ast::{
6    call::{InlineProcedureCall, ProcedureCall},
7    ddl::DdlStatement,
8    expr::{CharacterStringLiteralKind, ValueExpr},
9    mutation::MutationPipeline,
10    pattern::MatchClause,
11    span::SourceSpan,
12    types::GqlType,
13    util::NonEmpty,
14};
15
16/// Top-level GQL statement.
17#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
18#[non_exhaustive]
19pub enum Statement {
20    /// Read query pipeline.
21    Query(QueryPipeline),
22    /// Set-composed read pipelines.
23    Composite {
24        /// First pipeline.
25        first: QueryPipeline,
26        /// Remaining pipelines paired with their set operator; type-enforced
27        /// non-empty because a composite statement has at least one set op.
28        rest: NonEmpty<(SetOp, QueryPipeline)>,
29        /// Source span.
30        span: SourceSpan,
31    },
32    /// `NEXT`-chained read pipelines.
33    Chained {
34        /// Chained pipeline blocks.
35        blocks: Vec<QueryPipeline>,
36        /// Source span.
37        span: SourceSpan,
38    },
39    /// Write-side mutation pipeline.
40    Mutate(MutationPipeline),
41    /// Data-definition statement.
42    Ddl(DdlStatement),
43    /// Top-level procedure call.
44    Call(ProcedureCall),
45    /// `EXPLAIN <statement>`.
46    ///
47    /// Plans the inner statement and returns a textual plan without executing
48    /// the inner statement.
49    Explain {
50        /// Inner statement to plan.
51        inner: Box<Statement>,
52        /// Source span.
53        span: SourceSpan,
54    },
55    /// `START TRANSACTION`.
56    StartTransaction {
57        /// Source span.
58        span: SourceSpan,
59    },
60    /// `COMMIT`.
61    Commit {
62        /// Source span.
63        span: SourceSpan,
64    },
65    /// `ROLLBACK`.
66    Rollback {
67        /// Source span.
68        span: SourceSpan,
69    },
70    /// `SESSION SET VALUE <param> [<type>] = <value expression>` (ISO feature GS03).
71    ///
72    /// Binds a session-local value parameter. The `if_not_exists` flag carries
73    /// the `IF NOT EXISTS` qualifier from `<session set parameter name>`
74    /// (ISO/IEC 39075:2024 section 7.4): when set, an existing binding is left
75    /// untouched.
76    SessionSetValue {
77        /// Database-string parameter name without the leading `$`.
78        param: DbString,
79        /// Optional declared type for the target session parameter.
80        declared_type: Option<GqlType>,
81        /// Value expression bound to the parameter.
82        value: Box<ValueExpr>,
83        /// `IF NOT EXISTS` was present on the parameter specification.
84        if_not_exists: bool,
85        /// Source span.
86        span: SourceSpan,
87    },
88    /// `SESSION SET TIME ZONE <time zone string>` (ISO feature GS15).
89    SessionSetTimeZone {
90        /// Decoded IANA region name or fixed-offset string.
91        zone: String,
92        /// Source spelling class for the time-zone character string literal.
93        zone_source_kind: CharacterStringLiteralKind,
94        /// Source span.
95        span: SourceSpan,
96    },
97    /// `SESSION SET [PROPERTY] GRAPH <current graph>` (ISO/IEC 39075:2024 section 7.1).
98    SessionSetGraph {
99        /// Current-graph expression selected by the command.
100        target: SessionSetGraphTarget,
101        /// Source span.
102        span: SourceSpan,
103    },
104    /// `SESSION RESET [ <session reset arguments> ]` (ISO features GS04/GS07/GS08/GS16).
105    SessionReset {
106        /// Reset target selected by the arguments (bare = all characteristics).
107        target: SessionResetTarget,
108        /// Source span.
109        span: SourceSpan,
110    },
111    /// `SESSION CLOSE` (ISO/IEC 39075:2024 section 7.3).
112    ///
113    /// Sets the session termination flag; no ISO feature code (Conformance
114    /// Rules: None).
115    SessionClose {
116        /// Source span.
117        span: SourceSpan,
118    },
119}
120
121/// Current graph expression selected by `SESSION SET [PROPERTY] GRAPH`.
122#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
123pub enum SessionSetGraphTarget {
124    /// `CURRENT_GRAPH`.
125    CurrentGraph,
126    /// `CURRENT_PROPERTY_GRAPH`.
127    CurrentPropertyGraph,
128}
129
130/// Target selected by `<session reset arguments>` (ISO/IEC 39075:2024 section 7.2).
131#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
132#[non_exhaustive]
133pub enum SessionResetTarget {
134    /// `SESSION RESET` (bare) or `RESET [ALL] CHARACTERISTICS`: reset every
135    /// session characteristic (parameters and time zone). ISO feature GS04.
136    AllCharacteristics,
137    /// `SESSION RESET [ALL] PARAMETERS`: clear all session parameters only.
138    /// ISO feature GS08.
139    Parameters,
140    /// `SESSION RESET TIME ZONE`: reset the session time zone to the ID048
141    /// default. ISO feature GS07.
142    TimeZone,
143    /// `SESSION RESET [PARAMETER] <param>`: clear a single named session
144    /// parameter. ISO feature GS16.
145    Parameter(DbString),
146}
147
148impl Statement {
149    /// Return the source span for this statement.
150    #[must_use]
151    pub const fn span(&self) -> SourceSpan {
152        match self {
153            Self::Query(pipeline) => pipeline.span,
154            Self::Composite { span, .. } | Self::Chained { span, .. } => *span,
155            Self::Mutate(pipeline) => pipeline.span,
156            Self::Ddl(statement) => statement.span(),
157            Self::Call(call) => call.span,
158            Self::Explain { span, .. } => *span,
159            Self::StartTransaction { span } | Self::Commit { span } | Self::Rollback { span } => {
160                *span
161            }
162            Self::SessionSetValue { span, .. }
163            | Self::SessionSetTimeZone { span, .. }
164            | Self::SessionSetGraph { span, .. }
165            | Self::SessionReset { span, .. }
166            | Self::SessionClose { span } => *span,
167        }
168    }
169}
170
171/// Set operator.
172#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
173pub enum SetOp {
174    /// `UNION` (distinct).
175    Union,
176    /// `UNION ALL` (multiset).
177    UnionAll,
178    /// `INTERSECT` (distinct).
179    Intersect,
180    /// `INTERSECT ALL` (multiset).
181    IntersectAll,
182    /// `EXCEPT` (distinct).
183    Except,
184    /// `EXCEPT ALL` (multiset).
185    ExceptAll,
186    /// `OTHERWISE`.
187    Otherwise,
188}
189
190/// Read query pipeline.
191#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
192pub struct QueryPipeline {
193    /// Ordered pipeline statements.
194    pub statements: Vec<PipelineStatement>,
195    /// Source span.
196    pub span: SourceSpan,
197}
198
199/// One read-side pipeline statement.
200#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
201#[non_exhaustive]
202pub enum PipelineStatement {
203    /// `MATCH`.
204    Match(MatchClause),
205    /// `FILTER`.
206    Filter(ValueExpr),
207    /// `LET`.
208    Let(Vec<LetBinding>),
209    /// Row expansion (`FOR`).
210    For(ForStatement),
211    /// `ORDER BY`.
212    Sorting(Vec<OrderTerm>),
213    /// `LIMIT`.
214    Limit(LimitValue),
215    /// `OFFSET` / `SKIP`.
216    Offset(LimitValue),
217    /// `RETURN`.
218    Return(ReturnClause),
219    /// `WITH`.
220    With(WithClause),
221    /// `CALL`.
222    Call(ProcedureCall),
223    /// Inline `CALL { ... }`.
224    CallSubquery(InlineProcedureCall),
225}
226
227impl PipelineStatement {
228    /// Return this statement's source span.
229    #[must_use]
230    pub fn span(&self) -> SourceSpan {
231        match self {
232            Self::Match(value) => value.span,
233            Self::Filter(value) => value.span(),
234            Self::Let(values) => span_from_iter(values.iter().map(|value| value.span)),
235            Self::For(value) => value.span,
236            Self::Sorting(values) => span_from_iter(values.iter().map(|value| value.span)),
237            Self::Limit(value) | Self::Offset(value) => value.span(),
238            Self::Return(value) => value.span,
239            Self::With(value) => value.span,
240            Self::Call(value) => value.span,
241            Self::CallSubquery(value) => value.span,
242        }
243    }
244}
245
246/// Variable binding in `LET`.
247#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
248pub struct LetBinding {
249    /// Database-string alias.
250    pub alias: DbString,
251    /// Optional declared type for the value variable.
252    pub declared_type: Option<GqlType>,
253    /// Bound value expression.
254    pub value: ValueExpr,
255    /// Source span.
256    pub span: SourceSpan,
257}
258
259/// Row-expansion statement.
260#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
261pub struct ForStatement {
262    /// Source expression.
263    pub source: ValueExpr,
264    /// Database-string alias.
265    pub alias: DbString,
266    /// Optional ISO position output (`WITH ORDINALITY` / `WITH OFFSET`).
267    pub position: Option<RowExpansionPosition>,
268    /// Source span.
269    pub span: SourceSpan,
270}
271
272/// Optional position output for ISO `FOR`.
273#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
274pub struct RowExpansionPosition {
275    /// Position value form.
276    pub kind: RowExpansionPositionKind,
277    /// Database-string alias.
278    pub alias: DbString,
279}
280
281/// ISO `FOR` position output kind.
282#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
283pub enum RowExpansionPositionKind {
284    /// `WITH ORDINALITY`, producing one-based positions.
285    Ordinality,
286    /// `WITH OFFSET`, producing zero-based offsets.
287    Offset,
288}
289
290/// `ORDER BY` term.
291#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
292pub struct OrderTerm {
293    /// Sorted expression.
294    pub expr: ValueExpr,
295    /// Sort direction.
296    pub direction: OrderDirection,
297    /// Optional null-order policy.
298    pub nulls: Option<NullsPolicy>,
299    /// Source span.
300    pub span: SourceSpan,
301}
302
303/// Sort direction.
304#[derive(
305    Clone, Copy, Debug, Default, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize,
306)]
307pub enum OrderDirection {
308    /// Ascending.
309    #[default]
310    Asc,
311    /// Descending.
312    Desc,
313}
314
315/// Null ordering policy.
316#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
317pub enum NullsPolicy {
318    /// `NULLS FIRST`.
319    NullsFirst,
320    /// `NULLS LAST`.
321    NullsLast,
322}
323
324/// `LIMIT` / `OFFSET` value.
325#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
326#[non_exhaustive]
327pub enum LimitValue {
328    /// Literal count.
329    Count(u64, SourceSpan),
330    /// Parameter reference.
331    Parameter {
332        /// Database-string parameter name without the leading `$`.
333        name: DbString,
334        /// Optional inline declared parameter type.
335        declared_type: Option<GqlType>,
336        /// Source span of the parameter reference.
337        span: SourceSpan,
338    },
339}
340
341impl LimitValue {
342    /// Return source span.
343    #[must_use]
344    pub const fn span(&self) -> SourceSpan {
345        match self {
346            Self::Count(_, span) | Self::Parameter { span, .. } => *span,
347        }
348    }
349}
350
351/// `RETURN` clause.
352#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
353pub struct ReturnClause {
354    /// `DISTINCT`.
355    pub distinct: bool,
356    /// `RETURN *`.
357    pub star: bool,
358    /// Return-list items. Empty when `star` is true.
359    pub items: Vec<ReturnItem>,
360    /// Optional `GROUP BY` list.
361    pub group_by: Option<Vec<ValueExpr>>,
362    /// Optional `HAVING` condition.
363    pub having: Option<ValueExpr>,
364    /// Source span.
365    pub span: SourceSpan,
366}
367
368/// One `RETURN` projection item.
369#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
370pub struct ReturnItem {
371    /// Returned expression.
372    pub expr: ValueExpr,
373    /// Optional `AS` alias.
374    pub alias: Option<DbString>,
375    /// Source span of the projection item.
376    pub span: SourceSpan,
377}
378
379/// `WITH` clause.
380#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
381pub struct WithClause {
382    /// `DISTINCT`.
383    pub distinct: bool,
384    /// Projected items.
385    pub items: Vec<ReturnItem>,
386    /// Optional `GROUP BY` list.
387    pub group_by: Option<Vec<ValueExpr>>,
388    /// Optional `HAVING` condition.
389    pub having: Option<ValueExpr>,
390    /// Optional post-projection `WHERE`.
391    pub where_clause: Option<ValueExpr>,
392    /// Source span.
393    pub span: SourceSpan,
394}
395
396/// Helper for type-bearing DDL statements.
397#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
398pub struct TypedBinding {
399    /// Binding name.
400    pub name: DbString,
401    /// Parsed type.
402    pub ty: GqlType,
403    /// Source span.
404    pub span: SourceSpan,
405}
406
407fn span_from_iter(mut spans: impl Iterator<Item = SourceSpan>) -> SourceSpan {
408    let Some(first) = spans.next() else {
409        return SourceSpan::default();
410    };
411    spans.fold(first, SourceSpan::merge)
412}