Skip to main content

linkrs_query/parser/ast/stmt/
management.rs

1use linkrs_core::types::expr::analysis_utils::collect_variables_from_contextual;
2use linkrs_core::types::expr::contextual::ContextualExpression;
3use linkrs_core::types::Span;
4
5use super::PatternUtils;
6
7use super::{CreateTarget, DeleteTarget, FetchTarget, UpdateTarget};
8use super::{OrderByClause, ReturnClause, ReturnItem, Stmt, YieldClause};
9use crate::parser::ast::types::{LimitClause, SkipClause};
10
11#[derive(Debug, Clone, PartialEq)]
12pub struct UseStmt {
13    pub span: Span,
14    pub space: String,
15}
16
17#[derive(Debug, Clone, PartialEq)]
18pub struct ShowStmt {
19    pub span: Span,
20    pub target: ShowTarget,
21}
22
23#[derive(Debug, Clone, PartialEq)]
24pub enum ShowTarget {
25    Spaces,
26    Tags,
27    Edges,
28    Indexes,
29    Index(String),
30    Stats,
31    Functions,
32    Graphs,
33    Macros,
34    AttachedDatabases,
35    Extensions,
36}
37
38#[derive(Debug, Clone, PartialEq, Default)]
39pub enum ExplainFormat {
40    #[default]
41    Table,
42    Dot,
43}
44
45#[derive(Debug, Clone)]
46pub struct ExplainStmt {
47    pub span: Span,
48    pub statement: Box<Stmt>,
49    pub format: ExplainFormat,
50    /// Whether the statement is executed and actual operator statistics
51    /// (rows / time) are overlaid on the plan output.
52    pub analyze: bool,
53}
54
55impl PartialEq for ExplainStmt {
56    fn eq(&self, other: &Self) -> bool {
57        self.span == other.span && self.format == other.format && self.analyze == other.analyze
58    }
59}
60
61#[derive(Debug, Clone)]
62pub struct ProfileStmt {
63    pub span: Span,
64    pub statement: Box<Stmt>,
65    pub format: ExplainFormat,
66}
67
68/// ANALYZE statement: collect statistics for a space.
69#[derive(Debug, Clone, PartialEq)]
70pub struct AnalyzeStmt {
71    pub span: Span,
72    /// None = current/default space.
73    pub space: Option<String>,
74}
75
76impl PartialEq for ProfileStmt {
77    fn eq(&self, other: &Self) -> bool {
78        self.span == other.span && self.format == other.format
79    }
80}
81
82#[derive(Debug, Clone, PartialEq)]
83pub enum GroupingType {
84    Standard,
85    Rollup(Vec<ContextualExpression>),
86    Cube(Vec<ContextualExpression>),
87    GroupingSets(Vec<Vec<ContextualExpression>>),
88}
89
90#[derive(Debug, Clone, PartialEq)]
91pub struct GroupByStmt {
92    pub span: Span,
93    pub group_items: Vec<ContextualExpression>,
94    pub grouping_type: GroupingType,
95    pub yield_clause: YieldClause,
96    pub having_clause: Option<ContextualExpression>,
97}
98
99#[derive(Debug, Clone)]
100pub struct PipeStmt {
101    pub span: Span,
102    pub left: Box<Stmt>,
103    pub right: Box<Stmt>,
104}
105
106impl PartialEq for PipeStmt {
107    fn eq(&self, other: &Self) -> bool {
108        self.span == other.span
109    }
110}
111
112#[derive(Debug, Clone, PartialEq)]
113pub struct UnwindStmt {
114    pub span: Span,
115    pub expression: ContextualExpression,
116    pub variable: String,
117    pub return_clause: Option<ReturnClause>,
118    pub order_by: Option<OrderByClause>,
119    pub limit: Option<LimitClause>,
120    pub skip: Option<SkipClause>,
121}
122
123#[derive(Debug, Clone)]
124pub struct WithStmt {
125    pub span: Span,
126    pub items: Vec<ReturnItem>,
127    pub where_clause: Option<ContextualExpression>,
128    pub distinct: bool,
129    pub order_by: Option<OrderByClause>,
130    pub skip: Option<SkipClause>,
131    pub limit: Option<LimitClause>,
132    pub recursive: bool,
133    pub ctes: Vec<CteDef>,
134}
135
136/// A common table expression defined in a WITH clause.
137///
138/// `name AS (<body>)` where the body is a single query, or — under
139/// `WITH RECURSIVE` — `anchor UNION ALL step`. V1 supports exactly one CTE
140/// per WITH and single-column anchors; the planner reports wider shapes
141/// with a precise error.
142#[derive(Debug, Clone)]
143pub struct CteDef {
144    pub span: Span,
145    pub name: String,
146    pub body: Box<Stmt>,
147}
148
149/// A standalone WHERE stage used as a pipe suffix (e.g. `GO ... | WHERE age > 25`).
150#[derive(Debug, Clone, PartialEq)]
151pub struct FilterStmt {
152    pub span: Span,
153    pub expression: ContextualExpression,
154}
155
156/// A standalone COLLECT stage used as a pipe suffix (e.g. `| COLLECT LIST(name) AS names`).
157#[derive(Debug, Clone, PartialEq)]
158pub struct CollectStmt {
159    pub span: Span,
160    pub items: Vec<super::YieldItem>,
161}
162
163#[derive(Debug, Clone, PartialEq)]
164pub struct ShowSessionsStmt {
165    pub span: Span,
166}
167
168#[derive(Debug, Clone, PartialEq)]
169pub struct ShowQueriesStmt {
170    pub span: Span,
171}
172
173#[derive(Debug, Clone, PartialEq)]
174pub struct KillQueryStmt {
175    pub span: Span,
176    pub session_id: i64,
177    pub plan_id: i64,
178}
179
180/// Target schema of a [`PurgeExpiredStmt`]: a single tag or edge type.
181/// `None` (when the whole field is `None`) means "every TTL-configured
182/// schema in the space".
183#[derive(Debug, Clone, PartialEq)]
184pub enum PurgeTarget {
185    Tag(String),
186    Edge(String),
187}
188
189/// `PURGE EXPIRED [TAG <tag> | EDGE <edge>] IN <space>` statement.
190///
191/// Physically deletes TTL-expired rows. Without a target it scans every
192/// TTL-configured tag and edge type in the space; a target restricts the
193/// scan to one schema.
194#[derive(Debug, Clone, PartialEq)]
195pub struct PurgeExpiredStmt {
196    pub span: Span,
197    pub space_name: String,
198    pub target: Option<PurgeTarget>,
199}
200
201#[derive(Debug, Clone, PartialEq)]
202pub struct ShowConfigsStmt {
203    pub span: Span,
204    pub module: Option<String>,
205}
206
207#[derive(Debug, Clone, PartialEq)]
208pub struct UpdateConfigsStmt {
209    pub span: Span,
210    pub module: Option<String>,
211    pub config_name: String,
212    pub config_value: ContextualExpression,
213}
214
215#[derive(Debug, Clone)]
216pub struct AssignmentStmt {
217    pub span: Span,
218    pub variable: String,
219    pub statement: Box<Stmt>,
220}
221
222impl PartialEq for AssignmentStmt {
223    fn eq(&self, other: &Self) -> bool {
224        self.span == other.span && self.variable == other.variable
225    }
226}
227
228#[derive(Debug, Clone, PartialEq)]
229pub enum SetOperationType {
230    Union,
231    UnionAll,
232    Intersect,
233    Minus,
234}
235
236#[derive(Debug, Clone)]
237pub struct SetOperationStmt {
238    pub span: Span,
239    pub op_type: SetOperationType,
240    pub left: Box<Stmt>,
241    pub right: Box<Stmt>,
242}
243
244impl PartialEq for SetOperationStmt {
245    fn eq(&self, other: &Self) -> bool {
246        self.span == other.span && self.op_type == other.op_type
247    }
248}
249
250/// `EXPORT DATABASE '<path>' [WITH OPTIONS (...)]` statement.
251#[derive(Debug, Clone, PartialEq)]
252pub struct ExportDatabaseStmt {
253    pub span: Span,
254    pub path: String,
255    pub options: Vec<ExportOption>,
256}
257
258#[derive(Debug, Clone, PartialEq)]
259pub struct ExportOption {
260    pub key: String,
261    pub value: String,
262}
263
264/// `IMPORT DATABASE '<path>'` statement.
265#[derive(Debug, Clone, PartialEq)]
266pub struct ImportDatabaseStmt {
267    pub span: Span,
268    pub path: String,
269}
270
271/// Extension management action.
272#[derive(Debug, Clone, Copy, PartialEq, Eq)]
273pub enum ExtensionAction {
274    /// `LOAD EXTENSION '<path>'`: load a UDF dynamic library.
275    Load,
276    /// `INSTALL EXTENSION <name> FROM '<source>'`: install from a local file source.
277    Install,
278    /// `UNINSTALL EXTENSION <name>`: unload a previously loaded UDF.
279    Uninstall,
280    /// `UPDATE EXTENSION <name>`: reload a previously loaded UDF from its
281    /// original library path.
282    Update,
283}
284
285/// `ATTACH '<path>' AS <alias> [(DBTYPE <type>)]` statement.
286///
287/// Registers an external data source in the attached-database catalog so it
288/// can be observed via `SHOW ATTACHED DATABASES`. Cross-source federation is
289/// not implemented; only the catalog entry is produced.
290#[derive(Debug, Clone, PartialEq)]
291pub struct AttachDatabaseStmt {
292    pub span: Span,
293    /// Source path/URL as written in the statement.
294    pub path: String,
295    /// Alias used to reference the source after attachment.
296    pub alias: String,
297    /// Optional declared source type (`DBTYPE <name>` or `DATABASE_TYPE = ...`).
298    pub db_type: Option<String>,
299    /// Additional key/value options from the parenthesized list.
300    pub options: Vec<(String, String)>,
301}
302
303/// `DETACH <alias>` statement.
304///
305/// Removes a previously registered attachment from the catalog. Distinct from
306/// `DETACH DELETE`, which is parsed as part of the DELETE statement.
307#[derive(Debug, Clone, PartialEq)]
308pub struct DetachDatabaseStmt {
309    pub span: Span,
310    pub alias: String,
311}
312
313/// `LOAD EXTENSION` / `INSTALL EXTENSION` / `UNINSTALL EXTENSION` /
314/// `UPDATE EXTENSION` statement.
315///
316/// Extension statements are executed directly by the session layer against
317/// the function registry; they never reach the planner.
318#[derive(Debug, Clone, PartialEq)]
319pub struct ExtensionStmt {
320    pub span: Span,
321    pub action: ExtensionAction,
322    /// Function name (install/uninstall) or library path (load).
323    pub name: String,
324    /// Install source path (`INSTALL ... FROM`). `None` for load/uninstall.
325    pub source: Option<String>,
326}
327
328pub struct StmtUtils;
329
330impl StmtUtils {
331    pub fn find_variables(stmt: &Stmt) -> Vec<String> {
332        let mut variables = Vec::new();
333        Self::find_variables_recursive(stmt, &mut variables);
334        variables
335    }
336
337    fn find_variables_recursive(stmt: &Stmt, variables: &mut Vec<String>) {
338        match stmt {
339            Stmt::Match(s) => {
340                for pattern in &s.patterns {
341                    variables.extend(PatternUtils::find_variables(pattern));
342                }
343                if let Some(ref where_clause) = s.where_clause {
344                    variables.extend(collect_variables_from_contextual(where_clause));
345                }
346            }
347            Stmt::Create(s) => match &s.target {
348                CreateTarget::Node {
349                    properties: Some(props),
350                    ..
351                } => {
352                    variables.extend(collect_variables_from_contextual(props));
353                }
354                CreateTarget::Edge {
355                    src,
356                    dst,
357                    properties: Some(props),
358                    ..
359                } => {
360                    variables.extend(collect_variables_from_contextual(src));
361                    variables.extend(collect_variables_from_contextual(dst));
362                    variables.extend(collect_variables_from_contextual(props));
363                }
364                _ => {}
365            },
366            Stmt::Delete(s) => match &s.target {
367                DeleteTarget::Vertices { vids, .. } => {
368                    for vertex in vids {
369                        variables.extend(collect_variables_from_contextual(vertex));
370                    }
371                }
372                DeleteTarget::Edges { edges, .. } => {
373                    for (src, dst, rank) in edges {
374                        variables.extend(collect_variables_from_contextual(src));
375                        variables.extend(collect_variables_from_contextual(dst));
376                        if let Some(ref rank) = rank {
377                            variables.extend(collect_variables_from_contextual(rank));
378                        }
379                    }
380                }
381                _ => {}
382            },
383            Stmt::Update(s) => {
384                match &s.target {
385                    UpdateTarget::Vertex(vertex) => {
386                        variables.extend(collect_variables_from_contextual(vertex));
387                    }
388                    UpdateTarget::Edge { src, dst, rank, .. } => {
389                        variables.extend(collect_variables_from_contextual(src));
390                        variables.extend(collect_variables_from_contextual(dst));
391                        if let Some(ref rank) = rank {
392                            variables.extend(collect_variables_from_contextual(rank));
393                        }
394                    }
395                    _ => {}
396                }
397                for assignment in &s.set_clause.assignments {
398                    variables.extend(collect_variables_from_contextual(&assignment.value));
399                }
400                if let Some(ref where_clause) = s.where_clause {
401                    variables.extend(collect_variables_from_contextual(where_clause));
402                }
403            }
404            Stmt::Go(s) => {
405                for vertex in &s.from.vertices {
406                    variables.extend(collect_variables_from_contextual(vertex));
407                }
408                if let Some(ref where_clause) = s.where_clause {
409                    variables.extend(collect_variables_from_contextual(where_clause));
410                }
411            }
412            Stmt::Fetch(s) => match &s.target {
413                FetchTarget::Vertices { ids, .. } => {
414                    for id in ids {
415                        variables.extend(collect_variables_from_contextual(id));
416                    }
417                }
418                FetchTarget::Edges { src, dst, rank, .. } => {
419                    variables.extend(collect_variables_from_contextual(src));
420                    variables.extend(collect_variables_from_contextual(dst));
421                    if let Some(ref rank) = rank {
422                        variables.extend(collect_variables_from_contextual(rank));
423                    }
424                }
425            },
426            Stmt::Lookup(s) => {
427                if let Some(ref where_clause) = s.where_clause {
428                    variables.extend(collect_variables_from_contextual(where_clause));
429                }
430            }
431            Stmt::Subgraph(s) => {
432                for vertex in &s.from.vertices {
433                    variables.extend(collect_variables_from_contextual(vertex));
434                }
435                if let Some(ref where_clause) = s.where_clause {
436                    variables.extend(collect_variables_from_contextual(where_clause));
437                }
438            }
439            Stmt::FindPath(s) => {
440                for vertex in &s.from.vertices {
441                    variables.extend(collect_variables_from_contextual(vertex));
442                }
443                variables.extend(collect_variables_from_contextual(&s.to));
444                if let Some(ref where_clause) = s.where_clause {
445                    variables.extend(collect_variables_from_contextual(where_clause));
446                }
447            }
448            _ => {}
449        }
450    }
451}