Skip to main content

nodedb_sql/ddl_ast/parse/database/
dispatch.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Database-DDL dispatch: matches the leading verb tokens and delegates to
4//! the per-operation parser. Keep this file thin — the actual parsing logic
5//! belongs in the sibling modules.
6
7use crate::ddl_ast::parse::clause::{ClauseCursor, tokenize};
8use crate::ddl_ast::statement::{DatabaseStmt, NodedbStatement};
9use crate::error::SqlError;
10
11use super::alter::parse_alter_database;
12use super::backup_restore::{parse_backup_database, parse_restore_database};
13use super::clone::parse_clone_database;
14use super::create::parse_create_database;
15use super::drop_db::parse_drop_database;
16use super::mirror::{parse_mirror_database, parse_show_database_mirror_status};
17use super::move_tenant::parse_move_tenant;
18use super::show_extras::{parse_show_database_lineage, parse_show_database_quota_or_usage};
19use super::use_db::parse_use_database;
20
21/// Try to parse a database-level DDL statement.
22///
23/// Returns `None` for SQL that does not match any database-DDL prefix.
24/// Returns `Some(Err(...))` for structurally valid database DDL that contains a
25/// parse error (e.g. missing required name token). Each parser reads its
26/// statement left to right and refuses any token it does not consume.
27pub fn try_parse(
28    _upper: &str,
29    _parts: &[&str],
30    original: &str,
31) -> Option<Result<NodedbStatement, SqlError>> {
32    let toks = tokenize(original);
33    let mut c = ClauseCursor::new(original, &toks, "DATABASE");
34    let result = if c.eat_keywords(&["CREATE", "DATABASE"]) {
35        parse_create_database(&mut c)
36    } else if c.eat_keywords(&["DROP", "DATABASE"]) {
37        parse_drop_database(&mut c)
38    } else if c.eat_keywords(&["ALTER", "DATABASE"]) {
39        parse_alter_database(&mut c)
40    } else if c.eat_keywords(&["USE", "DATABASE"]) {
41        parse_use_database(&mut c)
42    } else if c.eat_keywords(&["CLONE", "DATABASE"]) {
43        parse_clone_database(&mut c)
44    } else if c.eat_keywords(&["MIRROR", "DATABASE"]) {
45        parse_mirror_database(&mut c)
46    } else if c.eat_keywords(&["MOVE", "TENANT"]) {
47        parse_move_tenant(&mut c)
48    } else if c.eat_keywords(&["BACKUP", "DATABASE"]) {
49        parse_backup_database(&mut c)
50    } else if c.eat_keywords(&["RESTORE", "DATABASE"]) {
51        parse_restore_database(&mut c)
52    } else if c.peek_keywords(&["SHOW", "DATABASES"]) {
53        c.eat_keywords(&["SHOW", "DATABASES"]);
54        if !c.at_end() {
55            return None;
56        }
57        Ok(NodedbStatement::Database(DatabaseStmt::ShowDatabases))
58    } else if c.eat_keywords(&["SHOW", "DATABASE", "QUOTA"]) {
59        parse_show_database_quota_or_usage(&mut c, false)
60    } else if c.eat_keywords(&["SHOW", "DATABASE", "USAGE"]) {
61        parse_show_database_quota_or_usage(&mut c, true)
62    } else if c.eat_keywords(&["SHOW", "DATABASE", "LINEAGE"]) {
63        parse_show_database_lineage(&mut c)
64    } else if c.eat_keywords(&["SHOW", "DATABASE", "MIRROR", "STATUS"]) {
65        parse_show_database_mirror_status(&mut c)
66    } else {
67        return None;
68    };
69    Some(result)
70}
71
72/// A database or tenant name: a bare word as written, or a quoted
73/// identifier's content.
74pub(super) fn db_name(c: &mut ClauseCursor<'_, '_>, what: &str) -> Result<String, SqlError> {
75    let raw = c.raw(what)?;
76    Ok(raw
77        .strip_prefix('"')
78        .and_then(|r| r.strip_suffix('"'))
79        .map_or_else(|| raw.to_string(), |inner| inner.replace("\"\"", "\"")))
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn parse_show_databases() {
88        let sql = "SHOW DATABASES";
89        let upper = sql.to_uppercase();
90        let parts: Vec<&str> = sql.split_whitespace().collect();
91        let stmt = try_parse(&upper, &parts, sql).unwrap().unwrap();
92        assert_eq!(stmt, NodedbStatement::Database(DatabaseStmt::ShowDatabases));
93    }
94}