Skip to main content

nodedb_sql/ddl_ast/
statement.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! The [`NodedbStatement`] enum — one variant per DDL command.
4
5pub use super::alter_ops::{AlterCollectionOp, AlterRoleOp, AlterUserOp};
6pub use super::graph_types::{GraphDirection, GraphProperties};
7pub use nodedb_types::{AuditDmlMode, QuotaSpec};
8
9/// Temporal anchor for a `CLONE DATABASE` statement.
10///
11/// Every `match` on this enum must be exhaustive — no `_ =>` arms.
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum CloneAsOf {
14    /// Use the source database's current commit LSN at clone time.
15    /// Corresponds to the bare `CLONE DATABASE … FROM …` form or the
16    /// explicit `… AS OF SYSTEM TIME LATEST` form.
17    Latest,
18    /// Use the LSN corresponding to the given milliseconds-since-epoch
19    /// timestamp, resolved via the `LsnMsAnchor` mechanism.
20    ///
21    /// Corresponds to `… AS OF SYSTEM TIME <ms>`.
22    SystemTimeMs(i64),
23}
24
25/// Operations available on `ALTER DATABASE <name> <operation>`.
26///
27/// Every variant must be matched exhaustively — no `_ =>` arms anywhere.
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub enum AlterDatabaseOperation {
30    /// `ALTER DATABASE <name> RENAME TO <new_name>`
31    Rename { new_name: String },
32    /// `ALTER DATABASE <name> SET QUOTA (max_memory_bytes = ..., ...)`
33    ///
34    /// All fields in the spec are optional; absent fields leave the existing
35    /// quota value unchanged (merged at apply time with the stored record or
36    /// `QuotaRecord::DEFAULT`).
37    SetQuota(QuotaSpec),
38    /// `ALTER DATABASE <name> SET DEFAULT` — marks this database as the
39    /// per-user default for future sessions. Returns
40    /// `FEATURE_NOT_YET_IMPLEMENTED` until the per-user default-database
41    /// binding lands; the canonical path is
42    /// `ALTER USER <name> SET DEFAULT DATABASE <db>`.
43    SetDefault,
44    /// `ALTER DATABASE <name> MATERIALIZE` — triggers background materialization
45    /// of a cloned database. Returns `FEATURE_NOT_YET_IMPLEMENTED` until the
46    /// clone/mirror subsystem lands.
47    Materialize,
48    /// `ALTER DATABASE <name> PROMOTE` — promotes a mirror to writable primary.
49    /// Returns `FEATURE_NOT_YET_IMPLEMENTED` until the mirror subsystem lands.
50    Promote,
51    /// `ALTER DATABASE <name> SET AUDIT_DML = <mode>` — sets the DML audit level.
52    SetAuditDml(AuditDmlMode),
53    /// `ALTER DATABASE <name> SET IDLE_TIMEOUT = <secs>` — sets the idle session
54    /// timeout in seconds for sessions in this database. `0` disables the per-database
55    /// timeout (falls back to the global `idle_timeout_secs` setting).
56    SetIdleTimeout(u64),
57}
58
59/// Operations available on `ALTER TENANT <name> IN DATABASE <db> <operation>`.
60///
61/// Every variant must be matched exhaustively — no `_ =>` arms anywhere.
62#[derive(Debug, Clone, PartialEq, Eq)]
63pub enum AlterTenantOperation {
64    /// `ALTER TENANT <name> IN DATABASE <db> SET QUOTA (...)`
65    SetQuota(QuotaSpec),
66}
67
68/// Typed representation of every NodeDB DDL statement.
69///
70/// Handlers receive a fully-parsed variant instead of raw `&[&str]`
71/// parts, eliminating array-index panics and enabling exhaustive
72/// match coverage for new DDL commands.
73#[derive(Debug, Clone, PartialEq)]
74pub enum NodedbStatement {
75    // ── Collection lifecycle ─────────────────────────────────────
76    CreateCollection {
77        name: String,
78        if_not_exists: bool,
79        /// Canonical engine name (e.g. `"kv"`, `"vector"`, `"document_strict"`).
80        /// `None` means no `engine=` key was present.
81        engine: Option<String>,
82        /// `(col_name, col_type)` pairs from the parenthesised column list.
83        columns: Vec<(String, String)>,
84        /// Key-value pairs from the `WITH (...)` clause, excluding `engine=`.
85        options: Vec<(String, String)>,
86        /// Free-standing modifier keywords: `APPEND_ONLY`, `HASH_CHAIN`, `BITEMPORAL`.
87        flags: Vec<String>,
88        /// Raw interior of a `BALANCED ON (group_key = col, ...)` clause, or `None`.
89        balanced_raw: Option<String>,
90    },
91    /// `CREATE TABLE <name> (<col_list>)` — Postgres-style strict-default DDL.
92    /// Infers strict relational mode unless overridden via `WITH (engine='...')`.
93    /// No column list → rejected with SQLSTATE `42601`.
94    CreateTable {
95        name: String,
96        if_not_exists: bool,
97        engine: Option<String>,
98        columns: Vec<(String, String)>,
99        options: Vec<(String, String)>,
100        flags: Vec<String>,
101        balanced_raw: Option<String>,
102    },
103    DropCollection {
104        name: String,
105        if_exists: bool,
106        /// Skip the soft-delete step (requires superuser/tenant_admin).
107        purge: bool,
108        /// Recursively drop dependents (triggers, RLS, MVs, streams, schedules).
109        cascade: bool,
110        /// Like `cascade` but also drops schedules with `references_unknown = true`.
111        cascade_force: bool,
112    },
113    /// `UNDROP COLLECTION <n>` — restore a soft-deleted collection within retention window.
114    UndropCollection {
115        name: String,
116    },
117    AlterCollection {
118        name: String,
119        operation: AlterCollectionOp,
120    },
121    DescribeCollection {
122        name: String,
123    },
124    ShowCollections,
125
126    // ── Index ────────────────────────────────────────────────────
127    CreateIndex {
128        unique: bool,
129        index_name: Option<String>,
130        collection: String,
131        field: String,
132        case_insensitive: bool,
133        where_condition: Option<String>,
134    },
135    DropIndex {
136        name: String,
137        collection: Option<String>,
138        if_exists: bool,
139    },
140    ShowIndexes {
141        collection: Option<String>,
142    },
143    Reindex {
144        collection: String,
145        index_name: Option<String>,
146        concurrent: bool,
147    },
148
149    // ── Trigger ──────────────────────────────────────────────────
150    CreateTrigger {
151        or_replace: bool,
152        /// "ASYNC", "SYNC", or "DEFERRED".
153        execution_mode: String,
154        name: String,
155        /// "BEFORE", "AFTER", or "INSTEAD OF".
156        timing: String,
157        events_insert: bool,
158        events_update: bool,
159        events_delete: bool,
160        collection: String,
161        /// "ROW" or "STATEMENT".
162        granularity: String,
163        when_condition: Option<String>,
164        priority: i32,
165        /// "INVOKER" or "DEFINER".
166        security: String,
167        body_sql: String,
168    },
169    DropTrigger {
170        name: String,
171        collection: String,
172        if_exists: bool,
173    },
174    AlterTrigger {
175        name: String,
176        action: String,
177        new_owner: Option<String>,
178    },
179    ShowTriggers {
180        collection: Option<String>,
181    },
182
183    // ── Schedule ─────────────────────────────────────────────────
184    CreateSchedule {
185        name: String,
186        cron_expr: String,
187        body_sql: String,
188        scope: String,
189        missed_policy: String,
190        allow_overlap: bool,
191    },
192    DropSchedule {
193        name: String,
194        if_exists: bool,
195    },
196    AlterSchedule {
197        name: String,
198        action: String,
199        cron_expr: Option<String>,
200    },
201    ShowSchedules,
202    ShowScheduleHistory {
203        name: String,
204    },
205
206    // ── Sequence ─────────────────────────────────────────────────
207    CreateSequence {
208        name: String,
209        if_not_exists: bool,
210        start: Option<i64>,
211        increment: Option<i64>,
212        min_value: Option<i64>,
213        max_value: Option<i64>,
214        cycle: bool,
215        cache: Option<i64>,
216        /// Raw `FORMAT 'template'` string (quotes stripped), or `None`.
217        format_template_raw: Option<String>,
218        /// Raw `RESET YEARLY|MONTHLY|QUARTERLY|DAILY` token, or `None`.
219        reset_period_raw: Option<String>,
220        gap_free: bool,
221        scope: Option<String>,
222    },
223    DropSequence {
224        name: String,
225        if_exists: bool,
226    },
227    AlterSequence {
228        name: String,
229        action: String,
230        with_value: Option<String>,
231    },
232    DescribeSequence {
233        name: String,
234    },
235    ShowSequences,
236
237    // ── Alert ────────────────────────────────────────────────────
238    CreateAlert {
239        name: String,
240        collection: String,
241        where_filter: Option<String>,
242        condition_raw: String,
243        group_by: Vec<String>,
244        window_raw: String,
245        fire_after: u32,
246        recover_after: u32,
247        severity: String,
248        notify_targets_raw: String,
249    },
250    DropAlert {
251        name: String,
252        if_exists: bool,
253    },
254    AlterAlert {
255        name: String,
256        action: String,
257    },
258    ShowAlerts,
259    ShowAlertStatus {
260        name: String,
261    },
262
263    // ── Retention policy ─────────────────────────────────────────
264    CreateRetentionPolicy {
265        name: String,
266        collection: String,
267        body_raw: String,
268        eval_interval_raw: Option<String>,
269    },
270    DropRetentionPolicy {
271        name: String,
272        if_exists: bool,
273    },
274    AlterRetentionPolicy {
275        name: String,
276        action: String,
277        set_key: Option<String>,
278        set_value: Option<String>,
279    },
280    ShowRetentionPolicies,
281
282    // ── Change stream ────────────────────────────────────────────
283    CreateChangeStream {
284        name: String,
285        collection: String,
286        with_clause_raw: String,
287    },
288    DropChangeStream {
289        name: String,
290        if_exists: bool,
291    },
292    AlterChangeStream {
293        name: String,
294        action: String,
295    },
296    ShowChangeStreams,
297
298    // ── Consumer group ───────────────────────────────────────────
299    CreateConsumerGroup {
300        group_name: String,
301        stream_name: String,
302    },
303    DropConsumerGroup {
304        name: String,
305        stream: String,
306        if_exists: bool,
307    },
308    ShowConsumerGroups {
309        stream: Option<String>,
310    },
311
312    // ── RLS policy ───────────────────────────────────────────────
313    CreateRlsPolicy {
314        name: String,
315        collection: String,
316        policy_type: String,
317        predicate_raw: String,
318        is_restrictive: bool,
319        on_deny_raw: Option<String>,
320        tenant_id_override: Option<u64>,
321    },
322    DropRlsPolicy {
323        name: String,
324        collection: String,
325        if_exists: bool,
326    },
327    ShowRlsPolicies {
328        collection: Option<String>,
329    },
330
331    // ── Materialized view ────────────────────────────────────────
332    CreateMaterializedView {
333        name: String,
334        source: String,
335        query_sql: String,
336        refresh_mode: String,
337    },
338    DropMaterializedView {
339        name: String,
340        if_exists: bool,
341    },
342    ShowMaterializedViews,
343
344    // ── Continuous aggregate ─────────────────────────────────────
345    CreateContinuousAggregate {
346        name: String,
347        source: String,
348        bucket_raw: String,
349        aggregate_exprs_raw: String,
350        group_by: Vec<String>,
351        with_clause_raw: String,
352    },
353    DropContinuousAggregate {
354        name: String,
355        if_exists: bool,
356    },
357    ShowContinuousAggregates,
358
359    // ── Database lifecycle ───────────────────────────────────────
360    /// `CREATE DATABASE [IF NOT EXISTS] <name> [WITH (...)]`
361    CreateDatabase {
362        name: String,
363        if_not_exists: bool,
364        /// Key-value pairs from `WITH (...)`, if present.
365        options: Vec<(String, String)>,
366    },
367    /// `DROP DATABASE [IF EXISTS] <name> [CASCADE | FORCE]`
368    ///
369    /// `FORCE` and `CASCADE` are accepted as synonyms by the parser and both
370    /// set `cascade = true`. PostgreSQL's `WITH (FORCE)` extension also
371    /// terminates active sessions; that is a separate concern handled by the
372    /// session registry at drop time and does not require a distinct AST flag.
373    DropDatabase {
374        name: String,
375        if_exists: bool,
376        cascade: bool,
377    },
378    /// `ALTER DATABASE <name> <operation>`
379    AlterDatabase {
380        name: String,
381        operation: AlterDatabaseOperation,
382    },
383    /// `SHOW DATABASES`
384    ShowDatabases,
385    /// `SHOW DATABASE QUOTA FOR <name>` — quota limits for a named database.
386    ShowDatabaseQuota {
387        name: String,
388    },
389    /// `SHOW DATABASE USAGE FOR <name>` — runtime usage counters for a database.
390    ShowDatabaseUsage {
391        name: String,
392    },
393    /// `SHOW DATABASE LINEAGE FOR <name>` — walks the parent clone chain from
394    /// `<name>` up to the root, returning one row per ancestor with
395    /// `(database_id, name, as_of_lsn, clone_created_at_lsn)`.
396    ShowDatabaseLineage {
397        name: String,
398    },
399    /// `ALTER TENANT <name> IN DATABASE <db> <operation>`
400    ///
401    /// New SQL surface. Sets per-tenant resource budgets within a specific database.
402    AlterTenant {
403        name: String,
404        database: String,
405        operation: AlterTenantOperation,
406    },
407    /// `SHOW TENANT QUOTA FOR <name> IN DATABASE <db>`
408    ShowTenantQuotaInDatabase {
409        name: String,
410        database: String,
411    },
412    /// `SHOW TENANT USAGE FOR <name> IN DATABASE <db>`
413    ShowTenantUsageInDatabase {
414        name: String,
415        database: String,
416    },
417    /// `USE DATABASE <name>` — session reset to a different database.
418    UseDatabase {
419        name: String,
420    },
421    /// `CLONE DATABASE <new> FROM <source> [AS OF SYSTEM TIME <ms> | LATEST]`
422    CloneDatabase {
423        new_name: String,
424        source_name: String,
425        /// The temporal anchor for this clone. `Latest` means "use the
426        /// source's current commit LSN at clone time".
427        as_of: CloneAsOf,
428    },
429    /// `MIRROR DATABASE <local_name> FROM <source_cluster>.<source_database> [MODE = sync | async]`
430    ///
431    /// Creates a continuously-updated read-only replica of `source_database` in
432    /// `source_cluster`. The local database is initialized with
433    /// `MirrorStatus::Bootstrapping` and transitions to `Following` once the
434    /// initial snapshot transfer completes.
435    ///
436    /// Every match on this variant must be exhaustive — no `_ =>` arms.
437    MirrorDatabase {
438        /// Name of the new local mirror database.
439        local_name: String,
440        /// Cluster identifier of the source cluster.
441        source_cluster: String,
442        /// Name of the database in the source cluster to mirror.
443        source_database: String,
444        /// Replication mode: `Sync` means the source waits for mirror ack;
445        /// `Async` (default) means the mirror trails the source.
446        mode: nodedb_types::MirrorMode,
447    },
448    /// `SHOW DATABASE MIRROR STATUS [FOR <name>]`
449    ///
450    /// Returns one row per mirror database (or one row if `FOR <name>` is given):
451    /// `name`, `source_cluster`, `source_database`, `mode`, `status`,
452    /// `last_applied_lsn`, `last_apply_ms`.
453    ///
454    /// Every match on this variant must be exhaustive — no `_ =>` arms.
455    ShowDatabaseMirrorStatus {
456        /// Filter to a specific mirror by name, or `None` to show all mirrors.
457        name: Option<String>,
458    },
459    /// `MOVE TENANT <tenant> FROM <db_a> TO <db_b>`
460    ///
461    /// Returns `FEATURE_NOT_YET_IMPLEMENTED` until the tenant-move subsystem lands.
462    MoveTenant {
463        tenant_name: String,
464        from_db: String,
465        to_db: String,
466    },
467    /// `BACKUP DATABASE <name> TO <uri>`
468    ///
469    /// Returns `FEATURE_NOT_YET_IMPLEMENTED` until the backup subsystem lands.
470    BackupDatabase {
471        name: String,
472        uri: String,
473    },
474    /// `RESTORE DATABASE <name> FROM <uri>`
475    ///
476    /// Returns `FEATURE_NOT_YET_IMPLEMENTED` until the restore subsystem lands.
477    RestoreDatabase {
478        name: String,
479        uri: String,
480    },
481
482    // ── Backup / restore ─────────────────────────────────────────
483    BackupTenant {
484        tenant_id: String,
485    },
486    RestoreTenant {
487        dry_run: bool,
488        tenant_id: String,
489    },
490
491    // ── Cluster admin ────────────────────────────────────────────
492    ShowNodes,
493    ShowNode {
494        node_id: String,
495    },
496    RemoveNode {
497        node_id: String,
498    },
499    ShowCluster,
500    ShowMigrations,
501    ShowRanges,
502    ShowRouting,
503    ShowSchemaVersion,
504    ShowPeerHealth,
505    Rebalance,
506    ShowRaftGroups,
507    ShowRaftGroup {
508        group_id: String,
509    },
510    AlterRaftGroup {
511        group_id: String,
512        action: String,
513        node_id: String,
514    },
515
516    // ── Maintenance ──────────────────────────────────────────────
517    Analyze {
518        collection: Option<String>,
519    },
520    Compact {
521        collection: String,
522    },
523    ShowStorage {
524        collection: Option<String>,
525    },
526    ShowCompactionStatus,
527
528    // ── User / auth / grant ──────────────────────────────────────
529    CreateUser {
530        username: String,
531        password: String,
532        role: Option<String>,
533        tenant_id: Option<u64>,
534    },
535    DropUser {
536        username: String,
537    },
538    AlterUser {
539        username: String,
540        op: AlterUserOp,
541    },
542    ShowUsers,
543    /// `ALTER ROLE <name> GRANT/REVOKE/SET`.
544    AlterRole {
545        name: String,
546        sub_op: AlterRoleOp,
547    },
548    GrantRole {
549        role: String,
550        username: String,
551    },
552    RevokeRole {
553        role: String,
554        username: String,
555    },
556    GrantPermission {
557        permission: String,
558        target_type: String,
559        target_name: String,
560        grantee: String,
561    },
562    /// `GRANT <privilege> ON DATABASE <name> TO <user>`
563    GrantDatabasePermission {
564        permission: String,
565        db_name: String,
566        grantee: String,
567    },
568    RevokePermission {
569        permission: String,
570        target_type: String,
571        target_name: String,
572        grantee: String,
573    },
574    /// `REVOKE <privilege> ON DATABASE <name> FROM <user>`
575    RevokeDatabasePermission {
576        permission: String,
577        db_name: String,
578        grantee: String,
579    },
580    ShowPermissions {
581        on_collection: Option<String>,
582        for_grantee: Option<String>,
583    },
584    ShowGrants {
585        username: Option<String>,
586    },
587
588    // ── OIDC providers ───────────────────────────────────────────
589    /// `CREATE OIDC PROVIDER <name> ISSUER '<iss>' JWKS_URI '<uri>'
590    ///  [AUDIENCE '<aud>'] [CLAIM MAPPING WHEN <claim_name> = '<value>'
591    ///  SET DEFAULT_DATABASE = <id>, ADD DATABASES [<ids>], ADD ROLES ['<role>', ...]]`
592    CreateOidcProvider {
593        name: String,
594        issuer: String,
595        jwks_uri: String,
596        audience: Option<String>,
597        /// `(claim_name, claim_value, default_database, add_databases, add_roles)` tuples.
598        claim_mappings: Vec<OidcClaimMappingClause>,
599    },
600    /// `ALTER OIDC PROVIDER <name> SET CLAIM MAPPING WHEN <claim_name> = '<value>'
601    ///  SET DEFAULT_DATABASE = <id>, ADD DATABASES [<ids>], ADD ROLES ['<role>', ...]`
602    ///
603    /// Replaces the entire claim-mapping list for the named provider.
604    AlterOidcProviderClaimMapping {
605        name: String,
606        claim_mappings: Vec<OidcClaimMappingClause>,
607    },
608    /// `DROP OIDC PROVIDER [IF EXISTS] <name>`
609    DropOidcProvider {
610        name: String,
611        if_exists: bool,
612    },
613    /// `SHOW OIDC PROVIDERS`
614    ShowOidcProviders,
615
616    // ── CRDT conflict policy ─────────────────────────────────────
617    /// `SHOW CONFLICT POLICY ON <collection>`
618    ShowConflictPolicy {
619        collection: String,
620    },
621
622    // ── Miscellaneous ────────────────────────────────────────────
623    ShowTenants,
624    ShowAuditLog,
625    ShowConstraints {
626        collection: String,
627    },
628    ShowTypeGuards {
629        collection: String,
630    },
631
632    // ── Custom types ─────────────────────────────────────────────
633    /// `CREATE TYPE <name> AS ENUM ('label1', 'label2', ...)`
634    CreateEnumType {
635        name: String,
636        labels: Vec<String>,
637    },
638    /// `CREATE TYPE <name> AS (<field1> <type1>, <field2> <type2>, ...)`
639    CreateCompositeType {
640        name: String,
641        /// `(field_name, type_name)` pairs.
642        fields: Vec<(String, String)>,
643    },
644    /// `DROP TYPE [IF EXISTS] <name>`
645    DropType {
646        name: String,
647        if_exists: bool,
648    },
649    /// `ALTER TYPE <name> ADD VALUE 'label'`
650    AlterTypeAddValue {
651        type_name: String,
652        label: String,
653    },
654    /// `SHOW TYPES`
655    ShowTypes,
656
657    // ── Synonym groups ───────────────────────────────────────────
658    /// `CREATE SYNONYM GROUP <name> AS ('term1', 'term2', ...)`
659    CreateSynonymGroup {
660        name: String,
661        terms: Vec<String>,
662    },
663    /// `DROP SYNONYM GROUP [IF EXISTS] <name>`
664    DropSynonymGroup {
665        name: String,
666        if_exists: bool,
667    },
668    /// `SHOW SYNONYM GROUPS`
669    ShowSynonymGroups,
670
671    // ── Graph DSL ────────────────────────────────────────────────
672    GraphInsertEdge {
673        collection: String,
674        src: String,
675        dst: String,
676        label: String,
677        properties: GraphProperties,
678    },
679    GraphDeleteEdge {
680        collection: String,
681        src: String,
682        dst: String,
683        label: String,
684    },
685    GraphSetLabels {
686        node_id: String,
687        labels: Vec<String>,
688        remove: bool,
689    },
690    GraphTraverse {
691        start: String,
692        depth: usize,
693        edge_label: Option<String>,
694        direction: GraphDirection,
695    },
696    GraphNeighbors {
697        node: String,
698        edge_label: Option<String>,
699        direction: GraphDirection,
700    },
701    GraphPath {
702        src: String,
703        dst: String,
704        max_depth: usize,
705        edge_label: Option<String>,
706    },
707    GraphAlgo {
708        algorithm: String,
709        collection: String,
710        edge_label: Option<String>,
711        damping: Option<f64>,
712        tolerance: Option<f64>,
713        resolution: Option<f64>,
714        max_iterations: Option<usize>,
715        sample_size: Option<usize>,
716        source_node: Option<String>,
717        direction: Option<String>,
718        mode: Option<String>,
719    },
720    /// `MATCH (x)-[:l]->(y) RETURN x, y` — body forwarded verbatim to the graph pattern compiler.
721    MatchQuery {
722        body: String,
723    },
724    /// `GRAPH RAG FUSION ON <collection> QUERY ARRAY[…] [options…]`
725    GraphRagFusion {
726        collection: String,
727        params: crate::ddl_ast::graph_parse::FusionParams,
728    },
729
730    // ── Bulk import ──────────────────────────────────────────────
731    /// `COPY <collection> FROM '<path>' [WITH (FORMAT ..., DELIMITER ..., HEADER ...)]`
732    ///
733    /// Server-side file-path bulk import. Does not handle STDIN streaming
734    /// (that is a different protocol path) or COPY ... TO.
735    CopyFromFile {
736        collection: String,
737        path: String,
738        format: Option<CopyFormat>,
739        delimiter: Option<char>,
740        header: bool,
741    },
742
743    // ── Bulk export ──────────────────────────────────────────────
744    /// `COPY <collection> TO '<path>' [WITH (FORMAT ..., DELIMITER ..., HEADER ...)]`
745    /// `COPY (SELECT ...) TO '<path>' [WITH (...)]`
746    ///
747    /// Server-side file-path bulk export. Streams scan results to a file.
748    CopyToFile {
749        /// The source: either a bare collection name or a SELECT query.
750        source: CopyToSource,
751        path: String,
752        format: Option<CopyFormat>,
753        delimiter: Option<char>,
754        header: bool,
755    },
756}
757
758/// One `WHEN <claim_name> = '<value>' SET ...` clause inside a
759/// `CREATE OIDC PROVIDER` or `ALTER OIDC PROVIDER` statement.
760#[derive(Debug, Clone, PartialEq, Eq)]
761pub struct OidcClaimMappingClause {
762    pub claim_name: String,
763    pub claim_value: String,
764    /// Optional default database ID to grant for matching tokens.
765    pub default_database: Option<u64>,
766    /// Additional database IDs accessible to matching tokens.
767    pub add_databases: Vec<u64>,
768    /// Role names to grant to matching tokens.
769    pub add_roles: Vec<String>,
770}
771
772/// Source for `COPY ... TO`.
773#[derive(Debug, Clone, PartialEq, Eq)]
774pub enum CopyToSource {
775    /// `COPY <collection> TO '<path>'` — export from a named collection.
776    Collection(String),
777    /// `COPY (SELECT ...) TO '<path>'` — export from an arbitrary query.
778    Query(String),
779}
780
781/// Format for `COPY ... FROM` bulk import.
782#[derive(Debug, Clone, PartialEq, Eq)]
783pub enum CopyFormat {
784    /// One JSON object per line (`.ndjson` / `.jsonl`).
785    Ndjson,
786    /// A JSON array of objects (`.json`).
787    JsonArray,
788    /// CSV with an optional header row (`.csv`).
789    Csv,
790}