Skip to main content

nodedb_sql/parser/database_stmt/
parse.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Entry point for the database-statement parser.
4//!
5//! Delegates to `crate::ddl_ast::parse::database::try_parse`, which contains
6//! the full recursive-descent implementation. This module exists to mirror the
7//! `parser/array_stmt/` structure and expose a consistent public API surface
8//! from `parser/`.
9
10use crate::ddl_ast::statement::NodedbStatement;
11use crate::error::SqlError;
12
13/// Try to parse a database-level DDL statement from raw SQL.
14///
15/// Returns `Ok(None)` for SQL that does not match any database-DDL prefix.
16/// Returns `Ok(Some(stmt))` on success. Returns `Err(SqlError::Parse { .. })`
17/// when the SQL matches a database-DDL prefix but contains a parse error (e.g.
18/// missing required name token).
19pub fn try_parse_database_statement(sql: &str) -> Result<Option<NodedbStatement>, SqlError> {
20    let trimmed = sql.trim();
21    if trimmed.is_empty() {
22        return Ok(None);
23    }
24    let upper = trimmed.to_uppercase();
25    let parts: Vec<&str> = trimmed.split_whitespace().collect();
26    if parts.is_empty() {
27        return Ok(None);
28    }
29    crate::ddl_ast::parse::database::try_parse(&upper, &parts, trimmed).transpose()
30}
31
32#[cfg(test)]
33mod tests {
34    use super::*;
35    use crate::ddl_ast::statement::{AlterDatabaseOperation, NodedbStatement};
36
37    fn ok(sql: &str) -> NodedbStatement {
38        try_parse_database_statement(sql)
39            .expect("expected Ok")
40            .expect("expected Some")
41    }
42
43    #[test]
44    fn parse_create_database() {
45        match ok("CREATE DATABASE mydb") {
46            NodedbStatement::CreateDatabase {
47                name,
48                if_not_exists,
49                ..
50            } => {
51                assert_eq!(name, "mydb");
52                assert!(!if_not_exists);
53            }
54            other => panic!("unexpected: {other:?}"),
55        }
56    }
57
58    #[test]
59    fn parse_create_database_if_not_exists() {
60        match ok("CREATE DATABASE IF NOT EXISTS mydb") {
61            NodedbStatement::CreateDatabase {
62                name,
63                if_not_exists,
64                ..
65            } => {
66                assert_eq!(name, "mydb");
67                assert!(if_not_exists);
68            }
69            other => panic!("unexpected: {other:?}"),
70        }
71    }
72
73    #[test]
74    fn parse_drop_database() {
75        match ok("DROP DATABASE mydb CASCADE") {
76            NodedbStatement::DropDatabase { name, cascade, .. } => {
77                assert_eq!(name, "mydb");
78                assert!(cascade);
79            }
80            other => panic!("unexpected: {other:?}"),
81        }
82    }
83
84    #[test]
85    fn parse_drop_database_if_exists() {
86        match ok("DROP DATABASE IF EXISTS mydb") {
87            NodedbStatement::DropDatabase {
88                name, if_exists, ..
89            } => {
90                assert_eq!(name, "mydb");
91                assert!(if_exists);
92            }
93            other => panic!("unexpected: {other:?}"),
94        }
95    }
96
97    #[test]
98    fn parse_alter_database_rename() {
99        match ok("ALTER DATABASE mydb RENAME TO newdb") {
100            NodedbStatement::AlterDatabase { name, operation } => {
101                assert_eq!(name, "mydb");
102                assert_eq!(
103                    operation,
104                    AlterDatabaseOperation::Rename {
105                        new_name: "newdb".into()
106                    }
107                );
108            }
109            other => panic!("unexpected: {other:?}"),
110        }
111    }
112
113    #[test]
114    fn parse_alter_database_set_quota() {
115        match ok("ALTER DATABASE mydb SET QUOTA (max_qps = 500)") {
116            NodedbStatement::AlterDatabase { name, operation } => {
117                assert_eq!(name, "mydb");
118                match operation {
119                    AlterDatabaseOperation::SetQuota(spec) => {
120                        assert_eq!(spec.max_qps, Some(500));
121                    }
122                    other => panic!("expected SetQuota, got {other:?}"),
123                }
124            }
125            other => panic!("unexpected: {other:?}"),
126        }
127    }
128
129    #[test]
130    fn parse_alter_database_materialize() {
131        match ok("ALTER DATABASE mydb MATERIALIZE") {
132            NodedbStatement::AlterDatabase { operation, .. } => {
133                assert_eq!(operation, AlterDatabaseOperation::Materialize);
134            }
135            other => panic!("unexpected: {other:?}"),
136        }
137    }
138
139    #[test]
140    fn parse_alter_database_promote() {
141        match ok("ALTER DATABASE mydb PROMOTE") {
142            NodedbStatement::AlterDatabase { operation, .. } => {
143                assert_eq!(operation, AlterDatabaseOperation::Promote);
144            }
145            other => panic!("unexpected: {other:?}"),
146        }
147    }
148
149    #[test]
150    fn parse_show_databases() {
151        assert_eq!(
152            try_parse_database_statement("SHOW DATABASES")
153                .unwrap()
154                .unwrap(),
155            NodedbStatement::ShowDatabases
156        );
157    }
158
159    #[test]
160    fn parse_use_database() {
161        match ok("USE DATABASE mydb") {
162            NodedbStatement::UseDatabase { name } => assert_eq!(name, "mydb"),
163            other => panic!("unexpected: {other:?}"),
164        }
165    }
166
167    #[test]
168    fn passthrough_non_database_sql() {
169        assert!(
170            try_parse_database_statement("SELECT * FROM t")
171                .unwrap()
172                .is_none()
173        );
174        assert!(
175            try_parse_database_statement("CREATE COLLECTION users")
176                .unwrap()
177                .is_none()
178        );
179        assert!(
180            try_parse_database_statement("INSERT INTO foo VALUES (1)")
181                .unwrap()
182                .is_none()
183        );
184    }
185
186    #[test]
187    fn parse_clone_database() {
188        use crate::ddl_ast::CloneAsOf;
189        match ok("CLONE DATABASE new_db FROM source_db") {
190            NodedbStatement::CloneDatabase {
191                new_name,
192                source_name,
193                as_of,
194            } => {
195                assert_eq!(new_name, "new_db");
196                assert_eq!(source_name, "source_db");
197                assert_eq!(as_of, CloneAsOf::Latest);
198            }
199            other => panic!("unexpected: {other:?}"),
200        }
201    }
202
203    #[test]
204    fn parse_mirror_database() {
205        use crate::ddl_ast::MirrorMode;
206        match ok("MIRROR DATABASE replica FROM prod-us.source") {
207            NodedbStatement::MirrorDatabase {
208                local_name,
209                source_cluster,
210                source_database,
211                mode,
212            } => {
213                assert_eq!(local_name, "replica");
214                assert_eq!(source_cluster, "prod-us");
215                assert_eq!(source_database, "source");
216                assert_eq!(mode, MirrorMode::Async);
217            }
218            other => panic!("unexpected: {other:?}"),
219        }
220    }
221
222    #[test]
223    fn parse_backup_database() {
224        match ok("BACKUP DATABASE mydb TO 's3://bucket/path'") {
225            NodedbStatement::BackupDatabase { name, uri } => {
226                assert_eq!(name, "mydb");
227                assert!(!uri.is_empty());
228            }
229            other => panic!("unexpected: {other:?}"),
230        }
231    }
232
233    #[test]
234    fn parse_restore_database() {
235        match ok("RESTORE DATABASE mydb FROM 's3://bucket/path'") {
236            NodedbStatement::RestoreDatabase { name, uri } => {
237                assert_eq!(name, "mydb");
238                assert!(!uri.is_empty());
239            }
240            other => panic!("unexpected: {other:?}"),
241        }
242    }
243
244    #[test]
245    fn parse_move_tenant() {
246        match ok("MOVE TENANT t1 FROM db_a TO db_b") {
247            NodedbStatement::MoveTenant {
248                tenant_name,
249                from_db,
250                to_db,
251            } => {
252                assert_eq!(tenant_name, "t1");
253                assert_eq!(from_db, "db_a");
254                assert_eq!(to_db, "db_b");
255            }
256            other => panic!("unexpected: {other:?}"),
257        }
258    }
259
260    #[test]
261    fn drop_missing_name_returns_error() {
262        let result = try_parse_database_statement("DROP DATABASE");
263        assert!(result.is_err() || result.unwrap().is_some());
264    }
265}