nodedb_sql/ddl_ast/statement.rs
1//! The [`NodedbStatement`] enum — one variant per DDL command.
2
3/// Typed representation of every NodeDB DDL statement.
4///
5/// Handlers receive a fully-parsed variant instead of raw `&[&str]`
6/// parts, eliminating array-index panics and enabling exhaustive
7/// match coverage for new DDL commands.
8#[derive(Debug, Clone, PartialEq)]
9pub enum NodedbStatement {
10 // ── Collection lifecycle ─────────────────────────────────────
11 CreateCollection {
12 name: String,
13 if_not_exists: bool,
14 raw_sql: String,
15 },
16 DropCollection {
17 name: String,
18 if_exists: bool,
19 /// `DROP COLLECTION <n> PURGE` — skip the soft-delete step and
20 /// go straight to `CatalogEntry::PurgeCollection`. Requires
21 /// superuser or tenant_admin. Fails if dependents exist unless
22 /// `cascade` is also set.
23 purge: bool,
24 /// `DROP COLLECTION <n> CASCADE` — recursively drop dependents
25 /// (triggers, RLS, MVs sourcing the collection, change streams,
26 /// schedules). Rejects on cascade cycles (32-deep limit).
27 cascade: bool,
28 /// `DROP COLLECTION <n> CASCADE FORCE` — only difference from
29 /// `cascade`: also drops schedules whose SQL carries
30 /// `references_unknown = true`. Requires superuser.
31 cascade_force: bool,
32 },
33 /// `UNDROP COLLECTION <n>` — restore a soft-deleted collection
34 /// whose retention window has not yet elapsed. Flips
35 /// `StoredCollection.is_active` back to true via a new
36 /// `PutCollection` catalog entry. Fails if the retention window
37 /// has already expired (storage is already purged) or if an
38 /// active collection of the same name exists.
39 UndropCollection {
40 name: String,
41 },
42 AlterCollection {
43 name: String,
44 raw_sql: String,
45 },
46 DescribeCollection {
47 name: String,
48 },
49 ShowCollections,
50
51 // ── Index ────────────────────────────────────────────────────
52 CreateIndex {
53 unique: bool,
54 raw_sql: String,
55 },
56 DropIndex {
57 name: String,
58 collection: Option<String>,
59 if_exists: bool,
60 },
61 ShowIndexes {
62 collection: Option<String>,
63 },
64 Reindex {
65 collection: String,
66 },
67
68 // ── Trigger ──────────────────────────────────────────────────
69 CreateTrigger {
70 or_replace: bool,
71 deferred: bool,
72 sync: bool,
73 raw_sql: String,
74 },
75 DropTrigger {
76 name: String,
77 collection: String,
78 if_exists: bool,
79 },
80 AlterTrigger {
81 raw_sql: String,
82 },
83 ShowTriggers {
84 collection: Option<String>,
85 },
86
87 // ── Schedule ─────────────────────────────────────────────────
88 CreateSchedule {
89 raw_sql: String,
90 },
91 DropSchedule {
92 name: String,
93 if_exists: bool,
94 },
95 AlterSchedule {
96 raw_sql: String,
97 },
98 ShowSchedules,
99 ShowScheduleHistory {
100 name: String,
101 },
102
103 // ── Sequence ─────────────────────────────────────────────────
104 CreateSequence {
105 name: String,
106 if_not_exists: bool,
107 raw_sql: String,
108 },
109 DropSequence {
110 name: String,
111 if_exists: bool,
112 },
113 AlterSequence {
114 raw_sql: String,
115 },
116 DescribeSequence {
117 name: String,
118 },
119 ShowSequences,
120
121 // ── Alert ────────────────────────────────────────────────────
122 CreateAlert {
123 raw_sql: String,
124 },
125 DropAlert {
126 name: String,
127 if_exists: bool,
128 },
129 AlterAlert {
130 raw_sql: String,
131 },
132 ShowAlerts,
133 ShowAlertStatus {
134 name: String,
135 },
136
137 // ── Retention policy ─────────────────────────────────────────
138 CreateRetentionPolicy {
139 raw_sql: String,
140 },
141 DropRetentionPolicy {
142 name: String,
143 if_exists: bool,
144 },
145 AlterRetentionPolicy {
146 raw_sql: String,
147 },
148 ShowRetentionPolicies,
149
150 // ── Change stream ────────────────────────────────────────────
151 CreateChangeStream {
152 raw_sql: String,
153 },
154 DropChangeStream {
155 name: String,
156 if_exists: bool,
157 },
158 AlterChangeStream {
159 raw_sql: String,
160 },
161 ShowChangeStreams,
162
163 // ── Consumer group ───────────────────────────────────────────
164 CreateConsumerGroup {
165 raw_sql: String,
166 },
167 DropConsumerGroup {
168 name: String,
169 stream: String,
170 if_exists: bool,
171 },
172 ShowConsumerGroups {
173 stream: Option<String>,
174 },
175
176 // ── RLS policy ───────────────────────────────────────────────
177 CreateRlsPolicy {
178 raw_sql: String,
179 },
180 DropRlsPolicy {
181 name: String,
182 collection: String,
183 if_exists: bool,
184 },
185 ShowRlsPolicies {
186 collection: Option<String>,
187 },
188
189 // ── Materialized view ────────────────────────────────────────
190 CreateMaterializedView {
191 raw_sql: String,
192 },
193 DropMaterializedView {
194 name: String,
195 if_exists: bool,
196 },
197 ShowMaterializedViews,
198
199 // ── Continuous aggregate ─────────────────────────────────────
200 CreateContinuousAggregate {
201 raw_sql: String,
202 },
203 DropContinuousAggregate {
204 name: String,
205 if_exists: bool,
206 },
207 ShowContinuousAggregates,
208
209 // ── Backup / restore ─────────────────────────────────────────
210 BackupTenant {
211 raw_sql: String,
212 },
213 RestoreTenant {
214 dry_run: bool,
215 raw_sql: String,
216 },
217
218 // ── Cluster admin ────────────────────────────────────────────
219 ShowNodes,
220 ShowNode {
221 node_id: String,
222 },
223 RemoveNode {
224 node_id: String,
225 },
226 ShowCluster,
227 ShowMigrations,
228 ShowRanges,
229 ShowRouting,
230 ShowSchemaVersion,
231 ShowPeerHealth,
232 Rebalance,
233 ShowRaftGroups,
234 ShowRaftGroup {
235 group_id: String,
236 },
237 AlterRaftGroup {
238 raw_sql: String,
239 },
240
241 // ── Maintenance ──────────────────────────────────────────────
242 Analyze {
243 collection: Option<String>,
244 },
245 Compact {
246 collection: String,
247 },
248 ShowStorage {
249 collection: Option<String>,
250 },
251 ShowCompactionStatus,
252
253 // ── User / auth / grant ──────────────────────────────────────
254 CreateUser {
255 raw_sql: String,
256 },
257 DropUser {
258 username: String,
259 },
260 AlterUser {
261 raw_sql: String,
262 },
263 ShowUsers,
264 GrantRole {
265 raw_sql: String,
266 },
267 RevokeRole {
268 raw_sql: String,
269 },
270 GrantPermission {
271 raw_sql: String,
272 },
273 RevokePermission {
274 raw_sql: String,
275 },
276 ShowPermissions {
277 collection: Option<String>,
278 },
279 ShowGrants {
280 username: Option<String>,
281 },
282
283 // ── Miscellaneous ────────────────────────────────────────────
284 ShowTenants,
285 ShowAuditLog,
286 ShowConstraints {
287 collection: String,
288 },
289 ShowTypeGuards {
290 collection: String,
291 },
292
293 // ── Graph DSL ─────────────────────────────────────────────────
294 //
295 // Typed variants replace substring-matched parsing in the
296 // pgwire handlers. The `ddl_ast::graph_parse` module is the
297 // single source of truth for graph-DSL syntax — quote- and
298 // brace-aware, so node ids / labels / property values that
299 // shadow DSL keywords cannot short-circuit extraction.
300 GraphInsertEdge {
301 collection: String,
302 src: String,
303 dst: String,
304 label: String,
305 properties: GraphProperties,
306 },
307 GraphDeleteEdge {
308 collection: String,
309 src: String,
310 dst: String,
311 label: String,
312 },
313 /// `GRAPH LABEL` / `GRAPH UNLABEL`.
314 GraphSetLabels {
315 node_id: String,
316 labels: Vec<String>,
317 /// `true` for `UNLABEL`, `false` for `LABEL`.
318 remove: bool,
319 },
320 GraphTraverse {
321 start: String,
322 depth: usize,
323 edge_label: Option<String>,
324 direction: GraphDirection,
325 },
326 GraphNeighbors {
327 node: String,
328 edge_label: Option<String>,
329 direction: GraphDirection,
330 },
331 GraphPath {
332 src: String,
333 dst: String,
334 max_depth: usize,
335 edge_label: Option<String>,
336 },
337 GraphAlgo {
338 algorithm: String,
339 collection: String,
340 damping: Option<f64>,
341 tolerance: Option<f64>,
342 resolution: Option<f64>,
343 max_iterations: Option<usize>,
344 sample_size: Option<usize>,
345 source_node: Option<String>,
346 direction: Option<String>,
347 mode: Option<String>,
348 },
349 /// `MATCH (x)-[:l]->(y) RETURN x, y` — the query body is
350 /// compiled deep inside the Data Plane via
351 /// `engine::graph::pattern::compiler::parse`, so the AST
352 /// variant just captures the raw SQL for that consumer.
353 MatchQuery {
354 raw_sql: String,
355 },
356
357 /// `GRAPH RAG FUSION ON <collection> QUERY ARRAY[…] [options…]`
358 ///
359 /// All numeric and label options are optional at parse time; the pgwire
360 /// handler applies defaults and caps so validation errors surface with
361 /// proper SQLSTATE codes rather than parser panics.
362 ///
363 /// `query_vector` is `None` when no `ARRAY[…]` clause was found — the
364 /// handler rejects such requests with SQLSTATE 42601.
365 GraphRagFusion {
366 collection: String,
367 params: crate::ddl_ast::graph_parse::FusionParams,
368 },
369
370 /// Catch-all for DDL-like commands not yet promoted to their
371 /// own variant. Preserves the raw SQL for the legacy dispatch
372 /// path so new variants can be added incrementally without
373 /// breaking existing handlers.
374 Other {
375 raw_sql: String,
376 },
377}
378
379/// Traversal direction for graph DSL variants. Mirrors the engine's
380/// own `Direction` enum so `nodedb-sql` has no dependency cycle with
381/// `nodedb`.
382#[derive(Debug, Clone, Copy, PartialEq, Eq)]
383pub enum GraphDirection {
384 In,
385 Out,
386 Both,
387}
388
389/// The `PROPERTIES` clause of `GRAPH INSERT EDGE`. Captured in its
390/// source form so the pgwire handler — which already depends on a
391/// JSON serializer (sonic_rs) — can do the conversion to storage
392/// bytes without dragging JSON deps into `nodedb-sql`.
393#[derive(Debug, Clone, PartialEq, Eq)]
394pub enum GraphProperties {
395 None,
396 /// Raw `{ ... }` object-literal span, including the outer braces
397 /// and brace-balanced inner content. Parsed by
398 /// `crate::parser::object_literal` at the handler boundary.
399 Object(String),
400 /// Content of `'...'` (outer quotes stripped, `''` un-escaped);
401 /// expected to already be a JSON document.
402 Quoted(String),
403}