nodedb_sql/ddl_ast/parse/
dispatch.rs1use super::{
4 alert, backup, change_stream, cluster_admin, collection, index, maintenance, materialized_view,
5 retention, rls, schedule, sequence, trigger, user_auth,
6};
7use crate::ddl_ast::graph_parse;
8use crate::ddl_ast::statement::NodedbStatement;
9
10pub fn parse(sql: &str) -> Option<NodedbStatement> {
14 let trimmed = sql.trim();
15 if trimmed.is_empty() {
16 return None;
17 }
18 let upper = trimmed.to_uppercase();
19 let parts: Vec<&str> = trimmed.split_whitespace().collect();
20 if parts.is_empty() {
21 return None;
22 }
23
24 if upper.starts_with("GRAPH ")
29 || upper.starts_with("MATCH ")
30 || upper.starts_with("OPTIONAL MATCH ")
31 {
32 return graph_parse::try_parse(trimmed);
33 }
34
35 collection::try_parse(&upper, &parts, trimmed)
39 .or_else(|| index::try_parse(&upper, &parts, trimmed))
40 .or_else(|| trigger::try_parse(&upper, &parts, trimmed))
41 .or_else(|| schedule::try_parse(&upper, &parts, trimmed))
42 .or_else(|| sequence::try_parse(&upper, &parts, trimmed))
43 .or_else(|| alert::try_parse(&upper, &parts, trimmed))
44 .or_else(|| retention::try_parse(&upper, &parts, trimmed))
45 .or_else(|| cluster_admin::try_parse(&upper, &parts, trimmed))
46 .or_else(|| maintenance::try_parse(&upper, &parts, trimmed))
47 .or_else(|| backup::try_parse(&upper, &parts, trimmed))
48 .or_else(|| user_auth::try_parse(&upper, &parts, trimmed))
49 .or_else(|| change_stream::try_parse(&upper, &parts, trimmed))
50 .or_else(|| rls::try_parse(&upper, &parts, trimmed))
51 .or_else(|| materialized_view::try_parse(&upper, &parts, trimmed))
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 #[test]
59 fn parse_create_collection() {
60 let stmt = parse("CREATE COLLECTION users (id INT, name TEXT)").unwrap();
61 match stmt {
62 NodedbStatement::CreateCollection {
63 name,
64 if_not_exists,
65 ..
66 } => {
67 assert_eq!(name, "users");
68 assert!(!if_not_exists);
69 }
70 other => panic!("expected CreateCollection, got {other:?}"),
71 }
72 }
73
74 #[test]
75 fn parse_create_collection_if_not_exists() {
76 let stmt = parse("CREATE COLLECTION IF NOT EXISTS users").unwrap();
77 match stmt {
78 NodedbStatement::CreateCollection {
79 name,
80 if_not_exists,
81 ..
82 } => {
83 assert_eq!(name, "users");
84 assert!(if_not_exists);
85 }
86 other => panic!("expected CreateCollection, got {other:?}"),
87 }
88 }
89
90 #[test]
91 fn parse_drop_collection() {
92 let stmt = parse("DROP COLLECTION users").unwrap();
93 assert_eq!(
94 stmt,
95 NodedbStatement::DropCollection {
96 name: "users".into(),
97 if_exists: false,
98 purge: false,
99 cascade: false,
100 cascade_force: false,
101 }
102 );
103 }
104
105 #[test]
106 fn parse_drop_collection_if_exists() {
107 let stmt = parse("DROP COLLECTION IF EXISTS users").unwrap();
108 assert_eq!(
109 stmt,
110 NodedbStatement::DropCollection {
111 name: "users".into(),
112 if_exists: true,
113 purge: false,
114 cascade: false,
115 cascade_force: false,
116 }
117 );
118 }
119
120 #[test]
121 fn parse_drop_collection_purge() {
122 let stmt = parse("DROP COLLECTION users PURGE").unwrap();
123 assert_eq!(
124 stmt,
125 NodedbStatement::DropCollection {
126 name: "users".into(),
127 if_exists: false,
128 purge: true,
129 cascade: false,
130 cascade_force: false,
131 }
132 );
133 }
134
135 #[test]
136 fn parse_drop_collection_cascade() {
137 let stmt = parse("DROP COLLECTION users CASCADE").unwrap();
138 assert_eq!(
139 stmt,
140 NodedbStatement::DropCollection {
141 name: "users".into(),
142 if_exists: false,
143 purge: false,
144 cascade: true,
145 cascade_force: false,
146 }
147 );
148 }
149
150 #[test]
151 fn parse_drop_collection_purge_cascade() {
152 let stmt = parse("DROP COLLECTION users PURGE CASCADE").unwrap();
153 assert_eq!(
154 stmt,
155 NodedbStatement::DropCollection {
156 name: "users".into(),
157 if_exists: false,
158 purge: true,
159 cascade: true,
160 cascade_force: false,
161 }
162 );
163 }
164
165 #[test]
166 fn parse_drop_collection_cascade_force() {
167 let stmt = parse("DROP COLLECTION users CASCADE FORCE").unwrap();
168 assert_eq!(
169 stmt,
170 NodedbStatement::DropCollection {
171 name: "users".into(),
172 if_exists: false,
173 purge: false,
174 cascade: true,
175 cascade_force: true,
176 }
177 );
178 }
179
180 #[test]
181 fn parse_undrop_collection() {
182 let stmt = parse("UNDROP COLLECTION users").unwrap();
183 assert_eq!(
184 stmt,
185 NodedbStatement::UndropCollection {
186 name: "users".into()
187 }
188 );
189 }
190
191 #[test]
192 fn parse_undrop_table_alias() {
193 let stmt = parse("UNDROP TABLE users").unwrap();
194 assert_eq!(
195 stmt,
196 NodedbStatement::UndropCollection {
197 name: "users".into()
198 }
199 );
200 }
201
202 #[test]
203 fn parse_show_nodes() {
204 assert_eq!(parse("SHOW NODES"), Some(NodedbStatement::ShowNodes));
205 }
206
207 #[test]
208 fn parse_show_cluster() {
209 assert_eq!(parse("SHOW CLUSTER"), Some(NodedbStatement::ShowCluster));
210 }
211
212 #[test]
213 fn parse_create_trigger() {
214 let stmt = parse("CREATE OR REPLACE SYNC TRIGGER on_insert ...").unwrap();
215 match stmt {
216 NodedbStatement::CreateTrigger {
217 or_replace,
218 sync,
219 deferred,
220 ..
221 } => {
222 assert!(or_replace);
223 assert!(sync);
224 assert!(!deferred);
225 }
226 other => panic!("expected CreateTrigger, got {other:?}"),
227 }
228 }
229
230 #[test]
231 fn parse_drop_index_if_exists() {
232 let stmt = parse("DROP INDEX IF EXISTS idx_name").unwrap();
233 match stmt {
234 NodedbStatement::DropIndex {
235 name, if_exists, ..
236 } => {
237 assert_eq!(name, "idx_name");
238 assert!(if_exists);
239 }
240 other => panic!("expected DropIndex, got {other:?}"),
241 }
242 }
243
244 #[test]
245 fn parse_analyze() {
246 assert_eq!(
247 parse("ANALYZE users"),
248 Some(NodedbStatement::Analyze {
249 collection: Some("users".into()),
250 })
251 );
252 assert_eq!(
253 parse("ANALYZE"),
254 Some(NodedbStatement::Analyze { collection: None })
255 );
256 }
257
258 #[test]
259 fn non_ddl_returns_none() {
260 assert!(parse("SELECT * FROM users").is_none());
261 assert!(parse("INSERT INTO users VALUES (1)").is_none());
262 }
263
264 #[test]
265 fn parse_grant_role() {
266 let stmt = parse("GRANT ROLE admin TO alice").unwrap();
267 match stmt {
268 NodedbStatement::GrantRole { raw_sql } => {
269 assert!(raw_sql.contains("admin"));
270 }
271 other => panic!("expected GrantRole, got {other:?}"),
272 }
273 }
274
275 #[test]
276 fn parse_create_sequence_if_not_exists() {
277 let stmt = parse("CREATE SEQUENCE IF NOT EXISTS my_seq START 1").unwrap();
278 match stmt {
279 NodedbStatement::CreateSequence {
280 name,
281 if_not_exists,
282 ..
283 } => {
284 assert_eq!(name, "my_seq");
285 assert!(if_not_exists);
286 }
287 other => panic!("expected CreateSequence, got {other:?}"),
288 }
289 }
290
291 #[test]
292 fn parse_restore_dry_run() {
293 let stmt = parse("RESTORE TENANT 1 FROM '/tmp/backup' DRY RUN").unwrap();
294 match stmt {
295 NodedbStatement::RestoreTenant { dry_run, .. } => {
296 assert!(dry_run);
297 }
298 other => panic!("expected RestoreTenant, got {other:?}"),
299 }
300 }
301}