1use crate::ast::{
2 AlterMetricQuery, AlterQueueQuery, AlterTableQuery, AlterUserStmt, ApplyMigrationQuery,
3 AskQuery, BinOp, CompareOp, ConfigCommand, CopyFormat, CopyFromQuery, CreateCollectionQuery,
4 CreateForeignTableQuery, CreateIndexQuery, CreateMetricQuery, CreateMigrationQuery,
5 CreatePolicyQuery, CreateQueueQuery, CreateSchemaQuery, CreateSequenceQuery, CreateServerQuery,
6 CreateSloQuery, CreateTableQuery, CreateTimeSeriesQuery, CreateTreeQuery, CreateUserStmt,
7 CreateVcsRefQuery, CreateVectorQuery, CreateViewQuery, DeleteQuery, DropCollectionQuery,
8 DropDocumentQuery, DropForeignTableQuery, DropGraphQuery, DropIndexQuery, DropKvQuery,
9 DropPolicyQuery, DropQueueQuery, DropSchemaQuery, DropSequenceQuery, DropServerQuery,
10 DropTableQuery, DropTimeSeriesQuery, DropTreeQuery, DropVcsRefQuery, DropVectorQuery,
11 DropViewQuery, EventsBackfillQuery, ExplainAlterQuery, ExplainMigrationQuery, Expr, FieldRef,
12 Filter, ForeignColumnDef, GrantStmt, GraphCommand, GraphQuery, HybridQuery, InsertQuery,
13 JoinQuery, KvCommand, MaintenanceCommand, PathQuery, PolicyAction, ProbabilisticCommand,
14 QueryExpr, QueueCommand, QueueSelectQuery, RankOfQuery, RankRangeQuery,
15 RefreshMaterializedViewQuery, RevokeStmt, RollbackMigrationQuery, SearchCommand, Span,
16 TableQuery, TreeCommand, TruncateQuery, TxnControl, UpdateQuery, VcsCommand,
17 VcsConflictResolution, VcsRefKind, VcsResetMode, VectorQuery,
18};
19use crate::lexer::Token;
20use crate::parser::{ParseError, Parser, SafeTokenDisplay};
21use crate::sql_lowering::filter_to_expr;
22use reddb_types::catalog::CollectionModel;
23use reddb_types::types::Value;
24
25#[derive(Debug, Clone)]
30pub enum SqlStatement {
31 Query(SqlQuery),
32 Mutation(SqlMutation),
33 Schema(SqlSchemaCommand),
34 Admin(SqlAdminCommand),
35}
36
37#[derive(Debug, Clone)]
38#[allow(clippy::large_enum_variant)]
39pub enum FrontendStatement {
40 Sql(SqlStatement),
41 Graph(GraphQuery),
42 GraphCommand(GraphCommand),
43 Path(PathQuery),
44 Vector(VectorQuery),
45 Hybrid(HybridQuery),
46 Search(SearchCommand),
47 Ask(AskQuery),
48 QueueSelect(QueueSelectQuery),
49 QueueCommand(QueueCommand),
50 EventsBackfill(EventsBackfillQuery),
51 EventsBackfillStatus { collection: String },
52 TreeCommand(TreeCommand),
53 ProbabilisticCommand(ProbabilisticCommand),
54 KvCommand(KvCommand),
55 ConfigCommand(ConfigCommand),
56 Ranking(QueryExpr),
57}
58
59#[derive(Debug, Clone)]
60pub enum SqlCommand {
61 Select(TableQuery),
62 Join(JoinQuery),
63 Insert(InsertQuery),
64 Update(UpdateQuery),
65 Delete(DeleteQuery),
66 ExplainAlter(ExplainAlterQuery),
67 CreateTable(CreateTableQuery),
68 CreateCollection(CreateCollectionQuery),
69 CreateVector(CreateVectorQuery),
70 DropTable(DropTableQuery),
71 DropGraph(DropGraphQuery),
72 DropVector(DropVectorQuery),
73 DropDocument(DropDocumentQuery),
74 DropKv(DropKvQuery),
75 DropCollection(DropCollectionQuery),
76 Truncate(TruncateQuery),
77 AlterTable(AlterTableQuery),
78 CreateVcsRef(CreateVcsRefQuery),
79 DropVcsRef(DropVcsRefQuery),
80 CreateIndex(CreateIndexQuery),
81 DropIndex(DropIndexQuery),
82 CreateTimeSeries(CreateTimeSeriesQuery),
83 CreateMetric(CreateMetricQuery),
84 AlterMetric(AlterMetricQuery),
85 CreateSlo(CreateSloQuery),
86 DropTimeSeries(DropTimeSeriesQuery),
87 CreateQueue(CreateQueueQuery),
88 AlterQueue(AlterQueueQuery),
89 DropQueue(DropQueueQuery),
90 CreateTree(CreateTreeQuery),
91 DropTree(DropTreeQuery),
92 Probabilistic(ProbabilisticCommand),
93 SetConfig {
94 key: String,
95 value: Value,
96 },
97 ShowConfig {
98 prefix: Option<String>,
99 as_json: bool,
100 },
101 SetSecret {
102 key: String,
103 value: Value,
104 },
105 DeleteSecret {
106 key: String,
107 },
108 ShowSecrets {
109 prefix: Option<String>,
110 },
111 SetTenant(Option<String>),
112 ShowTenant,
113 TransactionControl(TxnControl),
114 Maintenance(MaintenanceCommand),
115 Vcs(VcsCommand),
116 CreateSchema(CreateSchemaQuery),
117 DropSchema(DropSchemaQuery),
118 CreateSequence(CreateSequenceQuery),
119 DropSequence(DropSequenceQuery),
120 CopyFrom(CopyFromQuery),
121 CreateView(CreateViewQuery),
122 DropView(DropViewQuery),
123 RefreshMaterializedView(RefreshMaterializedViewQuery),
124 CreatePolicy(CreatePolicyQuery),
125 DropPolicy(DropPolicyQuery),
126 CreateServer(CreateServerQuery),
127 DropServer(DropServerQuery),
128 CreateForeignTable(CreateForeignTableQuery),
129 DropForeignTable(DropForeignTableQuery),
130 Grant(GrantStmt),
132 Revoke(RevokeStmt),
134 AlterUser(AlterUserStmt),
136 CreateUser(CreateUserStmt),
138 IamPolicy(QueryExpr),
143 CreateMigration(CreateMigrationQuery),
144 ApplyMigration(ApplyMigrationQuery),
145 RollbackMigration(RollbackMigrationQuery),
146 ExplainMigration(ExplainMigrationQuery),
147}
148
149fn analytics_v0_non_goal_create(token: &Token) -> Option<String> {
158 let ident = match token {
159 Token::Ident(s) => s,
160 _ => return None,
161 };
162 let upper = ident.to_ascii_uppercase();
163 let message = match upper.as_str() {
164 "ANALYTICS" => {
165 "CREATE ANALYTICS is not supported in Analytics v0 — \
166 use CREATE METRIC <dotted.path> for the metric-centric \
167 catalog (PRD #782 non-goal)"
168 }
169 "EVENT" => {
170 "CREATE EVENT is not supported in Analytics v0 — \
171 event-shaped data lives in ordinary TABLE/DOCUMENT \
172 collections, not a new storage model (PRD #782 non-goal)"
173 }
174 "COHORT" => {
175 "CREATE COHORT is not supported in Analytics v0 — \
176 cohort surfaces are deferred (PRD #782 non-goal)"
177 }
178 "FUNNEL" => {
179 "CREATE FUNNEL is not supported in Analytics v0 — \
180 funnel surfaces are deferred (PRD #782 non-goal)"
181 }
182 "SLA" => {
183 "CREATE SLA is not supported in Analytics v0 — \
184 SLA/legal/commercial contract modeling is post-MVP \
185 (PRD #782 non-goal)"
186 }
187 "ADAPTER" => {
188 "CREATE ADAPTER is not supported in Analytics v0 — \
189 Prometheus/Grafana/Snowplow/Google Analytics adapters \
190 are deferred (PRD #782 non-goal)"
191 }
192 _ => return None,
193 };
194 Some(message.to_string())
195}
196
197fn collection_model_filter(model: &str) -> Filter {
198 Filter::Compare {
199 field: FieldRef::column("", "model"),
200 op: CompareOp::Eq,
201 value: Value::Text(model.to_string().into()),
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208 use reddb_types::catalog::CollectionModel;
209
210 fn frontend(input: &str) -> FrontendStatement {
211 parse_frontend(input)
212 .unwrap_or_else(|err| panic!("failed to parse frontend {input:?}: {err:?}"))
213 }
214
215 fn expr(input: &str) -> QueryExpr {
216 frontend(input).into_query_expr()
217 }
218
219 fn sql_command(input: &str) -> SqlCommand {
220 sql_command_result(input)
221 .unwrap_or_else(|err| panic!("failed to parse SQL command {input:?}: {err:?}"))
222 }
223
224 fn sql_command_result(input: &str) -> Result<SqlCommand, ParseError> {
225 let mut parser = Parser::new(input)?;
226 parser.parse_sql_command()
227 }
228
229 fn assert_text(value: &Value, expected: &str) {
230 match value {
231 Value::Text(text) => assert_eq!(text.as_ref(), expected),
232 other => panic!("expected text {expected:?}, got {other:?}"),
233 }
234 }
235
236 #[test]
237 fn parse_frontend_routes_core_sql_statements() {
238 let FrontendStatement::Sql(SqlStatement::Query(SqlQuery::Select(query))) =
239 frontend("SELECT * FROM users")
240 else {
241 panic!("SELECT should route to SqlStatement::Query::Select");
242 };
243 assert_eq!(query.table, "users");
244
245 let QueryExpr::Insert(query) = expr("INSERT INTO users (id, name) VALUES (1, 'ada')")
246 else {
247 panic!("INSERT should lower through the SQL frontend");
248 };
249 assert_eq!(query.table, "users");
250 assert_eq!(query.columns, vec!["id", "name"]);
251 assert_eq!(query.values.len(), 1);
252
253 let QueryExpr::Update(query) = expr("UPDATE users SET name = 'ada' WHERE id = 1") else {
254 panic!("UPDATE should lower through the SQL frontend");
255 };
256 assert_eq!(query.table, "users");
257 assert_eq!(query.assignments[0].0, "name");
258
259 let QueryExpr::Delete(query) = expr("DELETE FROM users WHERE id = 1") else {
260 panic!("DELETE should lower through the SQL frontend");
261 };
262 assert_eq!(query.table, "users");
263 assert!(query.filter.is_some());
264
265 let QueryExpr::CreateTable(query) = expr("CREATE TABLE users (id INT, name TEXT)") else {
266 panic!("CREATE TABLE should lower through the SQL frontend");
267 };
268 assert_eq!(query.collection_model, CollectionModel::Table);
269 assert_eq!(query.name, "users");
270 assert_eq!(query.columns[0].name, "id");
271
272 let QueryExpr::DropTable(query) = expr("DROP TABLE IF EXISTS users") else {
273 panic!("DROP TABLE should lower through the SQL frontend");
274 };
275 assert_eq!(query.name, "users");
276 assert!(query.if_exists);
277 }
278
279 #[test]
280 fn parse_frontend_routes_admin_and_catalog_sql() {
281 let QueryExpr::Table(query) = expr("SHOW COLLECTIONS") else {
282 panic!("SHOW COLLECTIONS should become a red.collections table query");
283 };
284 assert_eq!(query.table, "red.collections");
285 assert!(query.filter.is_some());
286
287 let QueryExpr::Table(query) = expr("SHOW TABLES LIMIT 5") else {
288 panic!("SHOW TABLES should become a filtered red.collections table query");
289 };
290 assert_eq!(query.table, "red.collections");
291 assert_eq!(query.limit, Some(5));
292 assert!(query.filter.is_some());
293
294 assert!(matches!(
295 expr("SHOW CONFIG durability.mode"),
296 QueryExpr::ShowConfig { prefix: Some(prefix), as_json: false } if prefix == "durability.mode"
297 ));
298 assert!(matches!(
299 expr("SHOW CONFIG"),
300 QueryExpr::ShowConfig {
301 prefix: None,
302 as_json: false
303 }
304 ));
305 assert!(matches!(
306 expr("SHOW CONFIG runtime.result_cache AS JSON"),
307 QueryExpr::ShowConfig { prefix: Some(prefix), as_json: true } if prefix == "runtime.result_cache"
308 ));
309 assert!(matches!(
310 expr("SHOW CONFIG FORMAT JSON"),
311 QueryExpr::ShowConfig {
312 prefix: None,
313 as_json: true
314 }
315 ));
316
317 let QueryExpr::SetConfig { key, value } = expr("SET CONFIG durability.mode = 'sync'")
318 else {
319 panic!("SET CONFIG should stay on the SQL admin surface");
320 };
321 assert_eq!(key, "durability.mode");
322 assert_text(&value, "sync");
323
324 let QueryExpr::SetSecret { key, value } = expr("SET SECRET provider.api_key = 'sk_test'")
325 else {
326 panic!("SET SECRET should stay on the SQL admin surface");
327 };
328 assert_eq!(key, "provider.api_key");
329 assert_text(&value, "sk_test");
330 assert!(matches!(
331 expr("SET SECRET red.secrets.provider.api_key = 'sk_test'"),
332 QueryExpr::SetSecret { key, .. } if key == "red.secret.provider.api_key"
333 ));
334
335 assert!(matches!(
336 expr("DELETE SECRET provider.api_key"),
337 QueryExpr::DeleteSecret { key } if key == "provider.api_key"
338 ));
339 assert!(matches!(
340 expr("DELETE SECRET red.secrets.provider.api_key"),
341 QueryExpr::DeleteSecret { key } if key == "red.secret.provider.api_key"
342 ));
343 assert!(matches!(
344 expr("SHOW SECRETS provider"),
345 QueryExpr::ShowSecrets { prefix: Some(prefix) } if prefix == "provider"
346 ));
347 assert!(matches!(
348 expr("SHOW SECRETS red.secrets.provider"),
349 QueryExpr::ShowSecrets { prefix: Some(prefix) } if prefix == "red.secret.provider"
350 ));
351 assert!(matches!(
352 expr("SET TENANT 'acme'"),
353 QueryExpr::SetTenant(Some(tenant)) if tenant == "acme"
354 ));
355 assert!(matches!(expr("RESET TENANT"), QueryExpr::SetTenant(None)));
356 assert!(matches!(expr("SHOW TENANT"), QueryExpr::ShowTenant));
357 assert!(matches!(
358 expr("BEGIN ISOLATION LEVEL SNAPSHOT"),
359 QueryExpr::TransactionControl(TxnControl::Begin)
360 ));
361 assert!(matches!(
362 expr("ROLLBACK TO SAVEPOINT sp1"),
363 QueryExpr::TransactionControl(TxnControl::RollbackToSavepoint(name)) if name == "sp1"
364 ));
365 assert!(matches!(
366 expr("VACUUM FULL users"),
367 QueryExpr::MaintenanceCommand(MaintenanceCommand::Vacuum {
368 target: Some(target),
369 full: true,
370 }) if target == "users"
371 ));
372 }
373
374 #[test]
375 fn parse_frontend_routes_extended_schema_sql() {
376 assert!(matches!(
377 expr("CREATE SCHEMA IF NOT EXISTS app"),
378 QueryExpr::CreateSchema(CreateSchemaQuery {
379 name,
380 if_not_exists: true,
381 }) if name == "app"
382 ));
383 assert!(matches!(
384 expr("DROP SCHEMA IF EXISTS app CASCADE"),
385 QueryExpr::DropSchema(DropSchemaQuery {
386 name,
387 if_exists: true,
388 cascade: true,
389 }) if name == "app"
390 ));
391 assert!(matches!(
392 expr("CREATE SEQUENCE IF NOT EXISTS seq START WITH 10 INCREMENT BY 2"),
393 QueryExpr::CreateSequence(CreateSequenceQuery {
394 name,
395 if_not_exists: true,
396 start: 10,
397 increment: 2,
398 }) if name == "seq"
399 ));
400 assert!(matches!(
401 expr("DROP SEQUENCE IF EXISTS seq"),
402 QueryExpr::DropSequence(DropSequenceQuery {
403 name,
404 if_exists: true,
405 }) if name == "seq"
406 ));
407
408 let QueryExpr::CopyFrom(copy) = expr(
409 "COPY users FROM '/tmp/u.csv' WITH (FORMAT = csv, HEADER = true, DELIMITER = ';')",
410 ) else {
411 panic!("COPY should lower through SQL frontend");
412 };
413 assert_eq!(copy.table, "users");
414 assert_eq!(copy.path, "/tmp/u.csv");
415 assert_eq!(copy.format, CopyFormat::Csv);
416 assert_eq!(copy.delimiter, Some(';'));
417 assert!(copy.has_header);
418
419 let QueryExpr::CreateView(view) = expr(
420 "CREATE MATERIALIZED VIEW IF NOT EXISTS mv WITH RETENTION 1 h \
421 AS SELECT id FROM users REFRESH EVERY 5 s",
422 ) else {
423 panic!("CREATE MATERIALIZED VIEW should lower through SQL frontend");
424 };
425 assert_eq!(view.name, "mv");
426 assert!(view.materialized);
427 assert!(view.if_not_exists);
428 assert_eq!(view.retention_duration_ms, Some(3_600_000));
429 assert_eq!(view.refresh_every_ms, Some(5_000));
430 assert!(matches!(*view.query, QueryExpr::Table(_)));
431
432 assert!(matches!(
433 expr("DROP MATERIALIZED VIEW IF EXISTS mv"),
434 QueryExpr::DropView(DropViewQuery {
435 name,
436 materialized: true,
437 if_exists: true,
438 }) if name == "mv"
439 ));
440 assert!(matches!(
441 expr("REFRESH MATERIALIZED VIEW mv"),
442 QueryExpr::RefreshMaterializedView(RefreshMaterializedViewQuery { name }) if name == "mv"
443 ));
444 }
445
446 #[test]
447 fn parse_frontend_routes_fdw_policy_auth_and_migrations() {
448 let QueryExpr::CreateServer(server) = expr(
449 "CREATE SERVER IF NOT EXISTS csvsrv FOREIGN DATA WRAPPER csv OPTIONS (path '/data')",
450 ) else {
451 panic!("CREATE SERVER should lower through SQL frontend");
452 };
453 assert_eq!(server.name, "csvsrv");
454 assert_eq!(server.wrapper, "csv");
455 assert!(server.if_not_exists);
456 assert_eq!(
457 server.options,
458 vec![("path".to_string(), "/data".to_string())]
459 );
460
461 let QueryExpr::CreateForeignTable(table) = expr(
462 "CREATE FOREIGN TABLE IF NOT EXISTS ext_users \
463 (id INT, name TEXT) SERVER csvsrv OPTIONS (file 'users.csv')",
464 ) else {
465 panic!("CREATE FOREIGN TABLE should lower through SQL frontend");
466 };
467 assert_eq!(table.name, "ext_users");
468 assert_eq!(table.server, "csvsrv");
469 assert!(table.if_not_exists);
470 assert_eq!(table.columns.len(), 2);
471 assert!(!table.columns[0].not_null);
472
473 assert!(matches!(
474 expr("DROP SERVER IF EXISTS csvsrv CASCADE"),
475 QueryExpr::DropServer(DropServerQuery {
476 name,
477 if_exists: true,
478 cascade: true,
479 }) if name == "csvsrv"
480 ));
481 assert!(matches!(
482 expr("DROP FOREIGN TABLE IF EXISTS ext_users"),
483 QueryExpr::DropForeignTable(DropForeignTableQuery {
484 name,
485 if_exists: true,
486 }) if name == "ext_users"
487 ));
488
489 let QueryExpr::CreatePolicy(policy) = expr(
490 "CREATE POLICY readonly ON NODES OF mygraph FOR SELECT TO analytics USING (public = 1)",
491 ) else {
492 panic!("CREATE POLICY should lower through SQL frontend");
493 };
494 assert_eq!(policy.name, "readonly");
495 assert_eq!(policy.table, "mygraph");
496 assert_eq!(policy.action, Some(PolicyAction::Select));
497 assert_eq!(policy.role.as_deref(), Some("analytics"));
498 assert_eq!(policy.target_kind.as_ident(), "nodes");
499
500 assert!(matches!(
501 expr("DROP POLICY IF EXISTS readonly ON mygraph"),
502 QueryExpr::DropPolicy(DropPolicyQuery {
503 name,
504 table,
505 if_exists: true,
506 }) if name == "readonly" && table == "mygraph"
507 ));
508
509 assert!(matches!(
510 expr("GRANT SELECT ON TABLE public.users TO tenant1.alice"),
511 QueryExpr::Grant(grant)
512 if grant.actions == vec!["SELECT"]
513 && grant.objects[0].schema.as_deref() == Some("public")
514 ));
515 assert!(matches!(
516 expr("REVOKE GRANT OPTION FOR USAGE ON SCHEMA analytics FROM GROUP analysts"),
517 QueryExpr::Revoke(revoke) if revoke.grant_option_for && revoke.all == false
518 ));
519 assert!(matches!(
520 expr("ALTER USER bob ENABLE SET search_path TO 'public'"),
521 QueryExpr::AlterUser(user)
522 if user.username == "bob" && user.attributes.len() == 2
523 ));
524 assert!(matches!(
525 expr("CREATE USER tenant1.alice WITH PASSWORD 'pw' ROLE write"),
526 QueryExpr::CreateUser(user)
527 if user.tenant.as_deref() == Some("tenant1")
528 && user.username == "alice"
529 && user.password == "pw"
530 && user.role == "write"
531 ));
532
533 assert!(matches!(
534 expr("CREATE POLICY 'readonly' AS '{\"Statement\":[]}'"),
535 QueryExpr::CreateIamPolicy { id, json }
536 if id == "readonly" && json == "{\"Statement\":[]}"
537 ));
538 assert!(matches!(
539 expr("DROP POLICY 'readonly'"),
540 QueryExpr::DropIamPolicy { id } if id == "readonly"
541 ));
542
543 assert!(matches!(
544 expr("CREATE MIGRATION m2 DEPENDS ON m0 BATCH 10 ROWS AS CREATE TABLE accounts (id INT)"),
545 QueryExpr::CreateMigration(migration)
546 if migration.name == "m2"
547 && migration.depends_on == vec!["m0".to_string()]
548 && migration.batch_size == Some(10)
549 ));
550 assert!(matches!(
551 expr("APPLY MIGRATION * FOR TENANT tenant1"),
552 QueryExpr::ApplyMigration(apply)
553 if apply.for_tenant.as_deref() == Some("tenant1")
554 ));
555 assert!(matches!(
556 expr("ROLLBACK MIGRATION m2"),
557 QueryExpr::RollbackMigration(RollbackMigrationQuery { name }) if name == "m2"
558 ));
559 assert!(matches!(
560 expr("EXPLAIN MIGRATION m2"),
561 QueryExpr::ExplainMigration(ExplainMigrationQuery { name }) if name == "m2"
562 ));
563 }
564
565 #[test]
566 fn parse_sql_statement_covers_statement_category_wrapping() {
567 enum Expected {
568 Select,
569 Insert,
570 CreateSchema,
571 SetTenant,
572 }
573
574 let cases = [
575 ("SELECT * FROM users", Expected::Select),
576 ("INSERT INTO users (id) VALUES (1)", Expected::Insert),
577 ("CREATE SCHEMA app", Expected::CreateSchema),
578 ("SET TENANT 'acme'", Expected::SetTenant),
579 ];
580
581 for (input, expected) in cases {
582 let mut parser = Parser::new(input).expect("lexer");
583 let statement = parser
584 .parse_sql_statement()
585 .unwrap_or_else(|err| panic!("failed to parse {input:?}: {err:?}"));
586 let matched = match expected {
587 Expected::Select => matches!(statement, SqlStatement::Query(SqlQuery::Select(_))),
588 Expected::Insert => {
589 matches!(statement, SqlStatement::Mutation(SqlMutation::Insert(_)))
590 }
591 Expected::CreateSchema => matches!(
592 statement,
593 SqlStatement::Schema(SqlSchemaCommand::CreateSchema(_))
594 ),
595 Expected::SetTenant => {
596 matches!(
597 statement,
598 SqlStatement::Admin(SqlAdminCommand::SetTenant(_))
599 )
600 }
601 };
602 assert!(matched, "{input}");
603 }
604 }
605
606 #[test]
607 fn parse_frontend_routes_non_sql_frontends() {
608 let QueryExpr::KvCommand(KvCommand::Get {
609 model,
610 collection,
611 key,
612 }) = expr("KV GET settings.feature")
613 else {
614 panic!("KV GET should route to FrontendStatement::KvCommand");
615 };
616 assert_eq!(model, CollectionModel::Kv);
617 assert_eq!(collection, "settings");
618 assert_eq!(key, "feature");
619
620 let QueryExpr::ConfigCommand(ConfigCommand::Watch {
621 collection,
622 key,
623 prefix,
624 from_lsn,
625 }) = expr("WATCH CONFIG app PREFIX feature FROM LSN 7")
626 else {
627 panic!("WATCH CONFIG should route to FrontendStatement::ConfigCommand");
628 };
629 assert_eq!(collection, "app");
630 assert_eq!(key, "feature");
631 assert!(prefix);
632 assert_eq!(from_lsn, Some(7));
633
634 let QueryExpr::ConfigCommand(ConfigCommand::List {
635 collection,
636 prefix,
637 limit,
638 offset,
639 }) = expr("LIST CONFIG app PREFIX feature LIMIT 3 OFFSET 1")
640 else {
641 panic!("LIST CONFIG should route to FrontendStatement::ConfigCommand");
642 };
643 assert_eq!(collection, "app");
644 assert_eq!(prefix.as_deref(), Some("feature"));
645 assert_eq!(limit, Some(3));
646 assert_eq!(offset, 1);
647
648 let QueryExpr::KvCommand(KvCommand::List {
649 model,
650 collection,
651 prefix,
652 limit,
653 offset,
654 as_json,
655 }) = expr("KV LIST settings PREFIX 'feature.' LIMIT 10 OFFSET 2")
656 else {
657 panic!("KV LIST should route to FrontendStatement::KvCommand");
658 };
659 assert_eq!(model, CollectionModel::Kv);
660 assert_eq!(collection, "settings");
661 assert_eq!(prefix.as_deref(), Some("feature."));
662 assert_eq!(limit, Some(10));
663 assert_eq!(offset, 2);
664 assert!(!as_json);
665
666 let QueryExpr::KvCommand(KvCommand::List {
667 model,
668 collection,
669 prefix,
670 limit,
671 offset,
672 as_json,
673 }) = expr("LIST KV settings PREFIX feature LIMIT 10 OFFSET 2")
674 else {
675 panic!("LIST KV should route to FrontendStatement::KvCommand");
676 };
677 assert_eq!(model, CollectionModel::Kv);
678 assert_eq!(collection, "settings");
679 assert_eq!(prefix.as_deref(), Some("feature"));
680 assert_eq!(limit, Some(10));
681 assert_eq!(offset, 2);
682 assert!(!as_json);
683
684 let QueryExpr::KvCommand(KvCommand::List {
685 model,
686 collection,
687 prefix,
688 as_json,
689 ..
690 }) = expr("KV LIST settings PREFIX feature AS JSON")
691 else {
692 panic!("KV LIST AS JSON should route to FrontendStatement::KvCommand");
693 };
694 assert_eq!(model, CollectionModel::Kv);
695 assert_eq!(collection, "settings");
696 assert_eq!(prefix.as_deref(), Some("feature"));
697 assert!(as_json);
698
699 let QueryExpr::KvCommand(KvCommand::Watch {
700 model,
701 collection,
702 key,
703 prefix,
704 from_lsn,
705 }) = expr("WATCH sessions.user.* FROM LSN 3")
706 else {
707 panic!("bare WATCH should route to FrontendStatement::KvCommand");
708 };
709 assert_eq!(model, CollectionModel::Kv);
710 assert_eq!(collection, "sessions");
711 assert_eq!(key, "user");
712 assert!(prefix);
713 assert_eq!(from_lsn, Some(3));
714
715 let QueryExpr::KvCommand(KvCommand::Watch {
716 model,
717 collection,
718 key,
719 prefix,
720 from_lsn,
721 }) = expr("WATCH VAULT secrets PREFIX api FROM LSN 7")
722 else {
723 panic!("WATCH VAULT should route to FrontendStatement::KvCommand");
724 };
725 assert_eq!(model, CollectionModel::Vault);
726 assert_eq!(collection, "secrets");
727 assert_eq!(key, "api");
728 assert!(prefix);
729 assert_eq!(from_lsn, Some(7));
730
731 let QueryExpr::KvCommand(KvCommand::List {
732 model,
733 collection,
734 prefix,
735 limit,
736 offset,
737 as_json,
738 }) = expr("LIST VAULT secrets PREFIX api LIMIT 10 OFFSET 2")
739 else {
740 panic!("LIST VAULT should route to FrontendStatement::KvCommand");
741 };
742 assert_eq!(model, CollectionModel::Vault);
743 assert_eq!(collection, "secrets");
744 assert_eq!(prefix.as_deref(), Some("api"));
745 assert_eq!(limit, Some(10));
746 assert_eq!(offset, 2);
747 assert!(!as_json);
748
749 assert!(matches!(
750 expr("INVALIDATE CONFIG app feature_flag"),
751 QueryExpr::ConfigCommand(ConfigCommand::InvalidVolatileOperation {
752 operation,
753 collection,
754 key: Some(key),
755 }) if operation == "INVALIDATE" && collection == "app" && key == "feature_flag"
756 ));
757 assert!(matches!(
758 expr("INVALIDATE TAGS [user:42, org:7] FROM sessions"),
759 QueryExpr::KvCommand(KvCommand::InvalidateTags { collection, tags })
760 if collection == "sessions" && tags == vec!["user:42".to_string(), "org:7".to_string()]
761 ));
762
763 let QueryExpr::EventsBackfill(query) =
764 expr("EVENTS BACKFILL users WHERE status = 'active' TO audit LIMIT 10")
765 else {
766 panic!("EVENTS BACKFILL should route to FrontendStatement::EventsBackfill");
767 };
768 assert_eq!(query.collection, "users");
769 assert_eq!(query.where_filter.as_deref(), Some("status = 'active'"));
770 assert_eq!(query.target_queue, "audit");
771 assert_eq!(query.limit, Some(10));
772
773 let QueryExpr::Table(query) = expr("EVENTS STATUS users LIMIT 2") else {
774 panic!("EVENTS STATUS should route through the SQL select surface");
775 };
776 assert_eq!(query.table, "red.subscriptions");
777 assert_eq!(query.limit, Some(2));
778 assert!(query.filter.is_some());
779
780 assert!(matches!(
781 expr("EVENTS BACKFILL STATUS users"),
782 QueryExpr::EventsBackfillStatus { collection } if collection == "users"
783 ));
784 assert!(parse_frontend("LIST UNKNOWN").is_err());
785 assert!(parse_frontend("EVENTS UNKNOWN").is_err());
786 }
787
788 #[test]
789 fn parse_frontend_routes_ranking_reads() {
790 assert!(matches!(
791 expr("RANK OF 42 IN page_rank"),
792 QueryExpr::RankOf(RankOfQuery { ranking, entity_id })
793 if ranking == "page_rank" && entity_id == 42
794 ));
795 assert!(matches!(
796 expr("APPROX RANK OF 7 IN page_rank"),
797 QueryExpr::ApproxRankOf(RankOfQuery { ranking, entity_id })
798 if ranking == "page_rank" && entity_id == 7
799 ));
800 assert!(matches!(
801 expr("RANK RANGE 1 TO 3 IN page_rank"),
802 QueryExpr::RankRange(RankRangeQuery { ranking, lo, hi })
803 if ranking == "page_rank" && lo == 1 && hi == 3
804 ));
805 assert!(matches!(
806 expr("ZRANK page_rank 0"),
807 QueryExpr::RankOf(RankOfQuery { ranking, entity_id })
808 if ranking == "page_rank" && entity_id == 0
809 ));
810 assert!(matches!(
811 expr("ZRANGE page_rank 0 3 WITHSCORES"),
812 QueryExpr::RankRange(RankRangeQuery { ranking, lo, hi })
813 if ranking == "page_rank" && lo == 1 && hi == 4
814 ));
815 assert!(
816 parse_frontend("RANK RANGE 3 TO 1 IN page_rank").is_err(),
817 "rank range must reject reversed bounds"
818 );
819 }
820
821 #[test]
822 fn parse_frontend_covers_multimodel_command_routing() {
823 assert!(matches!(
824 expr("GRAPH CENTRALITY ALGORITHM pagerank LIMIT 5"),
825 QueryExpr::GraphCommand(GraphCommand::Centrality {
826 algorithm,
827 limit: Some(5),
828 ..
829 }) if algorithm == "pagerank"
830 ));
831 assert!(matches!(
832 expr("SEARCH TEXT 'login failure' COLLECTION incidents LIMIT 20 FUZZY"),
833 QueryExpr::SearchCommand(SearchCommand::Text {
834 query,
835 collection: Some(collection),
836 limit: 20,
837 fuzzy: true,
838 ..
839 }) if query == "login failure" && collection == "incidents"
840 ));
841 assert!(matches!(
842 expr("ASK 'why did login fail?' USING openai LIMIT 3"),
843 QueryExpr::Ask(query)
844 if query.question == "why did login fail?"
845 && query.provider.as_deref() == Some("openai")
846 && query.limit == Some(3)
847 ));
848 assert!(matches!(
849 expr("QUEUE LEN tasks"),
850 QueryExpr::QueueCommand(QueueCommand::Len { queue }) if queue == "tasks"
851 ));
852 assert!(matches!(
853 expr("TREE REBALANCE forest.org DRY RUN"),
854 QueryExpr::TreeCommand(TreeCommand::Rebalance {
855 collection,
856 tree_name,
857 dry_run: true,
858 }) if collection == "forest" && tree_name == "org"
859 ));
860 assert!(matches!(
861 expr("HLL COUNT visitors"),
862 QueryExpr::ProbabilisticCommand(ProbabilisticCommand::HllCount { names })
863 if names == vec!["visitors".to_string()]
864 ));
865 }
866
867 #[test]
868 fn sql_command_round_trips_multimodel_schema_variants() {
869 macro_rules! assert_command_round_trip {
870 ($input:expr, $pattern:pat) => {{
871 let command = sql_command($input);
872 assert!(matches!(command, $pattern), "unexpected command for {}", $input);
873
874 let statement = sql_command($input).into_statement();
875 let command = statement.into_command();
876 assert!(
877 matches!(command, $pattern),
878 "statement round trip changed command for {}",
879 $input
880 );
881
882 let expr = sql_command($input).into_query_expr();
883 assert!(
884 !matches!(expr, QueryExpr::Table(TableQuery { table, .. }) if table.is_empty()),
885 "lowering produced an empty table placeholder for {}",
886 $input
887 );
888 }};
889 }
890
891 assert_command_round_trip!(
892 "EXPLAIN ALTER FOR CREATE TABLE users (id INT) FORMAT JSON",
893 SqlCommand::ExplainAlter(_)
894 );
895 assert_command_round_trip!("CREATE TABLE users (id INT)", SqlCommand::CreateTable(_));
896 assert_command_round_trip!("DROP TABLE IF EXISTS users", SqlCommand::DropTable(_));
897 assert_command_round_trip!(
898 "ALTER TABLE users ADD COLUMN status TEXT",
899 SqlCommand::AlterTable(_)
900 );
901 assert_command_round_trip!(
902 "CREATE INDEX idx_email ON users (email) USING HASH",
903 SqlCommand::CreateIndex(_)
904 );
905 assert_command_round_trip!(
906 "DROP INDEX IF EXISTS idx_email ON users",
907 SqlCommand::DropIndex(_)
908 );
909 assert_command_round_trip!("CREATE GRAPH identity", SqlCommand::CreateTable(_));
910 assert_command_round_trip!("CREATE DOCUMENT docs", SqlCommand::CreateTable(_));
911 assert_command_round_trip!(
912 "CREATE VECTOR embeddings DIM 4",
913 SqlCommand::CreateVector(_)
914 );
915 assert_command_round_trip!(
916 "CREATE COLLECTION turbo KIND vector.turbo DIM 3",
917 SqlCommand::CreateCollection(_)
918 );
919 assert_command_round_trip!("CREATE KV settings", SqlCommand::CreateTable(_));
920 assert_command_round_trip!("CREATE CONFIG app", SqlCommand::CreateTable(_));
921 assert_command_round_trip!(
922 "CREATE VAULT secrets WITH OWN MASTER KEY",
923 SqlCommand::CreateTable(_)
924 );
925 assert_command_round_trip!(
926 "CREATE TIMESERIES metrics RETENTION 90 d",
927 SqlCommand::CreateTimeSeries(_)
928 );
929 assert_command_round_trip!(
930 "CREATE METRIC svc.latency TYPE gauge ROLE sli",
931 SqlCommand::CreateMetric(_)
932 );
933 assert_command_round_trip!(
934 "ALTER METRIC svc.latency SET ROLE internal",
935 SqlCommand::AlterMetric(_)
936 );
937 assert_command_round_trip!(
938 "CREATE SLO svc.availability ON svc.latency TARGET 99.9 WINDOW 5 m",
939 SqlCommand::CreateSlo(_)
940 );
941 assert_command_round_trip!(
942 "CREATE QUEUE tasks MAX_SIZE 100",
943 SqlCommand::CreateQueue(_)
944 );
945 assert_command_round_trip!(
946 "ALTER QUEUE tasks SET MODE FANOUT",
947 SqlCommand::AlterQueue(_)
948 );
949 assert_command_round_trip!(
950 "CREATE TREE org IN forest ROOT LABEL root MAX_CHILDREN 4",
951 SqlCommand::CreateTree(_)
952 );
953 assert_command_round_trip!(
954 "CREATE HLL visitors PRECISION 14",
955 SqlCommand::Probabilistic(_)
956 );
957 assert_command_round_trip!(
958 "CREATE SKETCH freqs WIDTH 512 DEPTH 3",
959 SqlCommand::Probabilistic(_)
960 );
961 assert_command_round_trip!(
962 "CREATE FILTER seen CAPACITY 1024",
963 SqlCommand::Probabilistic(_)
964 );
965 assert_command_round_trip!("COPY users FROM '/tmp/u.csv'", SqlCommand::CopyFrom(_));
966 assert_command_round_trip!(
967 "CREATE VIEW active_users AS SELECT * FROM users",
968 SqlCommand::CreateView(_)
969 );
970 assert_command_round_trip!("DROP VIEW active_users", SqlCommand::DropView(_));
971 assert_command_round_trip!(
972 "REFRESH MATERIALIZED VIEW active_users",
973 SqlCommand::RefreshMaterializedView(_)
974 );
975 assert_command_round_trip!(
976 "CREATE SERVER mycsv FOREIGN DATA WRAPPER csv OPTIONS (base_path '/data')",
977 SqlCommand::CreateServer(_)
978 );
979 assert_command_round_trip!(
980 "DROP SERVER IF EXISTS mycsv CASCADE",
981 SqlCommand::DropServer(_)
982 );
983 assert_command_round_trip!(
984 "CREATE FOREIGN TABLE ext_users (id INT, name TEXT) SERVER mycsv OPTIONS (path 'users.csv')",
985 SqlCommand::CreateForeignTable(_)
986 );
987 assert_command_round_trip!(
988 "DROP FOREIGN TABLE IF EXISTS ext_users",
989 SqlCommand::DropForeignTable(_)
990 );
991 }
992
993 #[test]
994 fn sql_command_round_trips_drop_truncate_and_maintenance_variants() {
995 macro_rules! assert_command_round_trip {
996 ($input:expr, $pattern:pat) => {{
997 let command = sql_command($input);
998 assert!(
999 matches!(command, $pattern),
1000 "unexpected command for {}",
1001 $input
1002 );
1003 let statement = sql_command($input).into_statement();
1004 assert!(
1005 matches!(statement.into_command(), $pattern),
1006 "statement round trip changed command for {}",
1007 $input
1008 );
1009 }};
1010 }
1011
1012 assert_command_round_trip!("DROP GRAPH IF EXISTS identity", SqlCommand::DropGraph(_));
1013 assert_command_round_trip!(
1014 "DROP VECTOR IF EXISTS embeddings",
1015 SqlCommand::DropVector(_)
1016 );
1017 assert_command_round_trip!("DROP DOCUMENT IF EXISTS docs", SqlCommand::DropDocument(_));
1018 assert_command_round_trip!("DROP KV IF EXISTS settings", SqlCommand::DropKv(_));
1019 assert_command_round_trip!("DROP CONFIG IF EXISTS app", SqlCommand::DropKv(_));
1020 assert_command_round_trip!("DROP VAULT IF EXISTS secrets", SqlCommand::DropKv(_));
1021 assert_command_round_trip!(
1022 "DROP COLLECTION IF EXISTS docs",
1023 SqlCommand::DropCollection(_)
1024 );
1025 assert_command_round_trip!(
1026 "DROP TIMESERIES IF EXISTS metrics",
1027 SqlCommand::DropTimeSeries(_)
1028 );
1029 assert_command_round_trip!(
1030 "DROP HYPERTABLE IF EXISTS metrics",
1031 SqlCommand::DropTimeSeries(_)
1032 );
1033 assert_command_round_trip!("DROP QUEUE IF EXISTS tasks", SqlCommand::DropQueue(_));
1034 assert_command_round_trip!("DROP TREE IF EXISTS org IN forest", SqlCommand::DropTree(_));
1035 assert_command_round_trip!("DROP HLL IF EXISTS visitors", SqlCommand::Probabilistic(_));
1036 assert_command_round_trip!("DROP SKETCH IF EXISTS freqs", SqlCommand::Probabilistic(_));
1037 assert_command_round_trip!("DROP FILTER IF EXISTS seen", SqlCommand::Probabilistic(_));
1038 assert_command_round_trip!(
1039 "TRUNCATE VECTOR IF EXISTS embeddings",
1040 SqlCommand::Truncate(_)
1041 );
1042 assert_command_round_trip!(
1043 "COMMIT WORK",
1044 SqlCommand::TransactionControl(TxnControl::Commit)
1045 );
1046 assert_command_round_trip!(
1047 "ROLLBACK",
1048 SqlCommand::TransactionControl(TxnControl::Rollback)
1049 );
1050 assert_command_round_trip!(
1051 "SAVEPOINT before_batch",
1052 SqlCommand::TransactionControl(TxnControl::Savepoint(_))
1053 );
1054 assert_command_round_trip!(
1055 "RELEASE SAVEPOINT before_batch",
1056 SqlCommand::TransactionControl(TxnControl::ReleaseSavepoint(_))
1057 );
1058 assert_command_round_trip!(
1059 "ANALYZE users",
1060 SqlCommand::Maintenance(MaintenanceCommand::Analyze { .. })
1061 );
1062 assert_command_round_trip!(
1063 "VACUUM",
1064 SqlCommand::Maintenance(MaintenanceCommand::Vacuum { .. })
1065 );
1066 }
1067
1068 #[test]
1069 fn parse_sql_command_covers_show_and_error_branches() {
1070 assert!(matches!(
1071 sql_command("SHOW CREATE TABLE public.users"),
1072 SqlCommand::Select(TableQuery { table, .. }) if table == "red.show_create"
1073 ));
1074 assert!(matches!(
1075 sql_command("SHOW COLLECTIONS INCLUDING INTERNAL LIMIT 2"),
1076 SqlCommand::Select(TableQuery { table, limit: Some(2), .. }) if table == "red.collections"
1077 ));
1078 assert!(matches!(
1079 sql_command("SHOW QUEUES INCLUDING INTERNAL"),
1080 SqlCommand::Select(TableQuery { table, filter: None, .. }) if table == "red.queues"
1081 ));
1082 assert!(matches!(
1083 sql_command("SHOW INDICES ON users"),
1084 SqlCommand::Select(TableQuery { table, filter: Some(_), .. }) if table == "red.show_indexes"
1085 ));
1086 assert!(matches!(
1087 sql_command("SHOW POLICIES ON users WHERE action = 'SELECT'"),
1088 SqlCommand::Select(TableQuery { table, filter: Some(_), .. }) if table == "red.policies"
1089 ));
1090 assert!(matches!(
1091 sql_command("SHOW STATS 'users' WHERE rows > 0"),
1092 SqlCommand::Select(TableQuery { table, filter: Some(_), .. }) if table == "red.stats"
1093 ));
1094 assert!(matches!(
1095 sql_command("SHOW SAMPLE users"),
1096 SqlCommand::Select(TableQuery { table, limit: Some(10), .. }) if table == "users"
1097 ));
1098 assert!(matches!(
1099 sql_command("DESC public.users"),
1100 SqlCommand::Select(TableQuery { table, filter: Some(_), .. }) if table == "red.describe"
1101 ));
1102 assert!(
1103 sql_command_result("CREATE VIEW v WITH RETENTION 1 h AS SELECT * FROM users").is_err()
1104 );
1105 assert!(sql_command_result("CREATE TABLE bad WITH ANALYTICS (centrality)").is_err());
1106 assert!(sql_command_result("BEGIN ISOLATION LEVEL SERIALIZABLE").is_err());
1107 assert!(sql_command_result("EVENTS BACKFILL STATUS users").is_err());
1108 }
1109
1110 #[test]
1111 fn parse_sql_command_covers_remaining_catalog_and_copy_shapes() {
1112 for input in [
1113 "SHOW VECTORS",
1114 "SHOW DOCUMENTS",
1115 "SHOW TIMESERIES",
1116 "SHOW GRAPHS",
1117 "SHOW CONFIGS",
1118 "SHOW VAULTS",
1119 "SHOW KV",
1120 "SHOW SCHEMA public.users",
1121 ] {
1122 assert!(
1123 matches!(sql_command(input), SqlCommand::Select(_)),
1124 "{input}"
1125 );
1126 }
1127
1128 for input in [
1129 "TRUNCATE TABLE users",
1130 "TRUNCATE GRAPH identity",
1131 "TRUNCATE DOCUMENT docs",
1132 "TRUNCATE TIMESERIES metrics",
1133 "TRUNCATE METRICS metrics",
1134 "TRUNCATE KV settings",
1135 "TRUNCATE QUEUE tasks",
1136 "TRUNCATE COLLECTION docs",
1137 ] {
1138 assert!(
1139 matches!(sql_command(input), SqlCommand::Truncate(_)),
1140 "{input}"
1141 );
1142 }
1143 assert!(sql_command_result("TRUNCATE UNKNOWN users").is_err());
1144
1145 let SqlCommand::CopyFrom(copy) = sql_command("COPY users FROM '/tmp/u.csv' WITH (HEADER)")
1146 else {
1147 panic!("expected COPY");
1148 };
1149 assert!(copy.has_header);
1150 assert_eq!(copy.delimiter, None);
1151
1152 let SqlCommand::CopyFrom(copy) =
1153 sql_command("COPY users FROM '/tmp/u.csv' WITH (HEADER = false)")
1154 else {
1155 panic!("expected COPY");
1156 };
1157 assert!(!copy.has_header);
1158
1159 let SqlCommand::CopyFrom(copy) =
1160 sql_command("COPY users FROM '/tmp/u.csv' DELIMITER '|' HEADER")
1161 else {
1162 panic!("expected COPY");
1163 };
1164 assert_eq!(copy.delimiter, Some('|'));
1165 assert!(copy.has_header);
1166 }
1167
1168 #[test]
1169 fn parse_sql_command_covers_remaining_ranking_and_event_errors() {
1170 assert!(matches!(
1171 expr("APPROXIMATE RANK OF 9 IN page_rank"),
1172 QueryExpr::ApproxRankOf(RankOfQuery { ranking, entity_id })
1173 if ranking == "page_rank" && entity_id == 9
1174 ));
1175 assert!(parse_frontend("APPROX OF 7 IN page_rank").is_err());
1176 assert!(parse_frontend("RANK 1 IN page_rank").is_err());
1177 assert!(parse_frontend("RANK RANGE 0 TO 3 IN page_rank").is_err());
1178 assert!(parse_frontend("ZRANK page_rank -1").is_err());
1179 assert!(parse_frontend("ZRANGE page_rank 3 1").is_err());
1180
1181 let QueryExpr::Table(query) = expr("EVENTS STATUS 'users' WHERE active = true") else {
1182 panic!("EVENTS STATUS should accept a quoted collection");
1183 };
1184 assert_eq!(query.table, "red.subscriptions");
1185 assert!(query.filter.is_some());
1186 assert!(query.where_expr.is_some());
1187
1188 let QueryExpr::EventsBackfill(query) = expr("EVENTS BACKFILL users TO audit") else {
1189 panic!("EVENTS BACKFILL should allow omitted filter and limit");
1190 };
1191 assert_eq!(query.collection, "users");
1192 assert_eq!(query.where_filter, None);
1193 assert_eq!(query.limit, None);
1194
1195 assert!(parse_frontend("EVENTS BACKFILL users WHERE TO audit").is_err());
1196 }
1197
1198 #[test]
1199 fn parse_sql_command_covers_analytics_non_goal_rejections() {
1200 for head in ["ANALYTICS", "EVENT", "COHORT", "FUNNEL", "SLA", "ADAPTER"] {
1201 let err = sql_command_result(&format!("CREATE {head} demo"))
1202 .expect_err("analytics v0 non-goal should be rejected");
1203 assert!(err.to_string().contains(&format!("CREATE {head}")), "{err}");
1204 }
1205 }
1206
1207 #[test]
1208 fn parse_sql_command_covers_transaction_isolation_edges() {
1209 for input in [
1210 "BEGIN ISOLATION LEVEL READ UNCOMMITTED",
1211 "BEGIN ISOLATION LEVEL READ COMMITTED",
1212 "BEGIN ISOLATION LEVEL REPEATABLE READ",
1213 "START TRANSACTION ISOLATION LEVEL SNAPSHOT",
1214 ] {
1215 assert!(
1216 matches!(
1217 sql_command(input),
1218 SqlCommand::TransactionControl(TxnControl::Begin)
1219 ),
1220 "{input}"
1221 );
1222 }
1223
1224 assert!(sql_command_result("BEGIN ISOLATION LEVEL READ").is_err());
1225 assert!(sql_command_result("BEGIN ISOLATION LEVEL REPEATABLE").is_err());
1226 assert!(sql_command_result("BEGIN ISOLATION LEVEL CHAOS").is_err());
1227 }
1228
1229 #[test]
1230 fn parse_sql_command_covers_vcs_working_set_verbs() {
1231 assert!(matches!(
1232 sql_command("CHECKPOINT 'initial import' AUTHOR 'Ada <ada@reddb.io>'"),
1233 SqlCommand::Vcs(VcsCommand::Checkpoint { .. })
1234 ));
1235 assert!(matches!(
1236 sql_command("CHECKOUT main"),
1237 SqlCommand::Vcs(VcsCommand::Checkout { .. })
1238 ));
1239 assert!(matches!(
1240 sql_command("RESET HARD TO abc123"),
1241 SqlCommand::Vcs(VcsCommand::Reset {
1242 mode: VcsResetMode::Hard,
1243 ..
1244 })
1245 ));
1246 assert!(matches!(
1247 sql_command("MERGE 'feature'"),
1248 SqlCommand::Vcs(VcsCommand::Merge { .. })
1249 ));
1250 assert!(matches!(
1251 sql_command("CHERRY PICK 'abc123'"),
1252 SqlCommand::Vcs(VcsCommand::CherryPick { .. })
1253 ));
1254 assert!(matches!(
1255 sql_command("REVERT 'abc123'"),
1256 SqlCommand::Vcs(VcsCommand::Revert { .. })
1257 ));
1258 assert!(matches!(
1259 sql_command("RESOLVE CONFLICT 'orders/1' USING THEIRS"),
1260 SqlCommand::Vcs(VcsCommand::ResolveConflict {
1261 resolution: VcsConflictResolution::Theirs,
1262 ..
1263 })
1264 ));
1265 assert!(matches!(
1266 sql_command("RESET TENANT"),
1267 SqlCommand::SetTenant(None)
1268 ));
1269 }
1270
1271 #[test]
1272 fn parse_sql_command_covers_iam_and_hypertable_dispatch_edges() {
1273 assert!(matches!(
1274 expr("CREATE HYPERTABLE metrics TIME_COLUMN ts CHUNK_INTERVAL '1d'"),
1275 QueryExpr::CreateTimeSeries(query)
1276 if query.name == "metrics" && query.hypertable.is_some()
1277 ));
1278 assert!(sql_command_result("CREATE OR TABLE bad (id INT)").is_err());
1279 assert!(sql_command_result("DROP MATERIALIZED TABLE bad").is_err());
1280
1281 assert!(matches!(
1282 expr("ATTACH POLICY 'readonly' TO USER tenant1.alice"),
1283 QueryExpr::AttachPolicy { policy_id, .. } if policy_id == "readonly"
1284 ));
1285 assert!(matches!(
1286 expr("DETACH POLICY 'readonly' FROM GROUP analysts"),
1287 QueryExpr::DetachPolicy { policy_id, .. } if policy_id == "readonly"
1288 ));
1289 assert!(matches!(
1290 expr("SHOW POLICIES FOR USER alice"),
1291 QueryExpr::ShowPolicies { filter: Some(_) }
1292 ));
1293 assert!(matches!(
1294 expr("SHOW EFFECTIVE PERMISSIONS FOR alice"),
1295 QueryExpr::ShowEffectivePermissions { resource: None, .. }
1296 ));
1297 assert!(matches!(
1298 expr("SIMULATE alice ACTION 'iam:PassRole' ON TABLE:public.orders"),
1299 QueryExpr::SimulatePolicy { action, .. } if action == "iam:PassRole"
1300 ));
1301 assert!(matches!(
1302 expr("LINT POLICY JSON '{\"Statement\":[]}'"),
1303 QueryExpr::LintPolicy { .. }
1304 ));
1305 assert!(matches!(
1306 expr("MIGRATE POLICY MODE TO 'policy_only' DRY RUN"),
1307 QueryExpr::MigratePolicyMode {
1308 target,
1309 dry_run: true,
1310 } if target == "policy_only"
1311 ));
1312 assert!(parse_frontend("MIGRATE OTHER").is_err());
1313 }
1314
1315 #[test]
1316 fn parse_frontend_rejects_trailing_tokens() {
1317 let err = parse_frontend("SET TENANT 'acme' junk")
1318 .expect_err("parse_frontend should reject trailing tokens");
1319 assert!(
1320 err.to_string().contains("Unexpected token after query"),
1321 "{err}"
1322 );
1323 }
1324}
1325
1326fn add_table_filter(query: &mut TableQuery, filter: Filter) {
1327 let combined = match query.filter.take() {
1328 Some(existing) => existing.and(filter),
1329 None => filter,
1330 };
1331 query.where_expr = Some(filter_to_expr(&combined));
1332 query.filter = Some(combined);
1333}
1334
1335fn parse_show_collections_by_model(
1336 parser: &mut Parser<'_>,
1337 model: &str,
1338) -> Result<TableQuery, ParseError> {
1339 let mut query = TableQuery::new("red.collections");
1340 parser.parse_table_clauses(&mut query)?;
1341 add_table_filter(&mut query, collection_model_filter(model));
1342 Ok(query)
1343}
1344
1345#[derive(Debug, Clone)]
1346#[allow(clippy::large_enum_variant)]
1347pub enum SqlQuery {
1348 Select(TableQuery),
1349 Join(JoinQuery),
1350}
1351
1352#[derive(Debug, Clone)]
1353pub enum SqlMutation {
1354 Insert(InsertQuery),
1355 Update(UpdateQuery),
1356 Delete(DeleteQuery),
1357}
1358
1359#[derive(Debug, Clone)]
1360pub enum SqlSchemaCommand {
1361 ExplainAlter(ExplainAlterQuery),
1362 CreateTable(CreateTableQuery),
1363 CreateCollection(CreateCollectionQuery),
1364 CreateVector(CreateVectorQuery),
1365 DropTable(DropTableQuery),
1366 DropGraph(DropGraphQuery),
1367 DropVector(DropVectorQuery),
1368 DropDocument(DropDocumentQuery),
1369 DropKv(DropKvQuery),
1370 DropCollection(DropCollectionQuery),
1371 Truncate(TruncateQuery),
1372 AlterTable(AlterTableQuery),
1373 CreateVcsRef(CreateVcsRefQuery),
1374 DropVcsRef(DropVcsRefQuery),
1375 CreateIndex(CreateIndexQuery),
1376 DropIndex(DropIndexQuery),
1377 CreateTimeSeries(CreateTimeSeriesQuery),
1378 CreateMetric(CreateMetricQuery),
1379 AlterMetric(AlterMetricQuery),
1380 CreateSlo(CreateSloQuery),
1381 DropTimeSeries(DropTimeSeriesQuery),
1382 CreateQueue(CreateQueueQuery),
1383 AlterQueue(AlterQueueQuery),
1384 DropQueue(DropQueueQuery),
1385 CreateTree(CreateTreeQuery),
1386 DropTree(DropTreeQuery),
1387 Probabilistic(ProbabilisticCommand),
1388 CreateSchema(CreateSchemaQuery),
1389 DropSchema(DropSchemaQuery),
1390 CreateSequence(CreateSequenceQuery),
1391 DropSequence(DropSequenceQuery),
1392 CopyFrom(CopyFromQuery),
1393 CreateView(CreateViewQuery),
1394 DropView(DropViewQuery),
1395 RefreshMaterializedView(RefreshMaterializedViewQuery),
1396 CreatePolicy(CreatePolicyQuery),
1397 DropPolicy(DropPolicyQuery),
1398 CreateServer(CreateServerQuery),
1399 DropServer(DropServerQuery),
1400 CreateForeignTable(CreateForeignTableQuery),
1401 DropForeignTable(DropForeignTableQuery),
1402 CreateMigration(CreateMigrationQuery),
1403 ApplyMigration(ApplyMigrationQuery),
1404 RollbackMigration(RollbackMigrationQuery),
1405 ExplainMigration(ExplainMigrationQuery),
1406}
1407
1408#[derive(Debug, Clone)]
1409#[allow(clippy::large_enum_variant)]
1410pub enum SqlAdminCommand {
1411 SetConfig {
1412 key: String,
1413 value: Value,
1414 },
1415 ShowConfig {
1416 prefix: Option<String>,
1417 as_json: bool,
1418 },
1419 SetSecret {
1420 key: String,
1421 value: Value,
1422 },
1423 DeleteSecret {
1424 key: String,
1425 },
1426 ShowSecrets {
1427 prefix: Option<String>,
1428 },
1429 SetTenant(Option<String>),
1430 ShowTenant,
1431 TransactionControl(TxnControl),
1432 Maintenance(MaintenanceCommand),
1433 Vcs(VcsCommand),
1434 Grant(GrantStmt),
1435 Revoke(RevokeStmt),
1436 AlterUser(AlterUserStmt),
1437 CreateUser(CreateUserStmt),
1438 IamPolicy(QueryExpr),
1439}
1440
1441impl SqlStatement {
1442 pub fn into_command(self) -> SqlCommand {
1443 match self {
1444 SqlStatement::Query(SqlQuery::Select(query)) => SqlCommand::Select(query),
1445 SqlStatement::Query(SqlQuery::Join(query)) => SqlCommand::Join(query),
1446 SqlStatement::Mutation(SqlMutation::Insert(query)) => SqlCommand::Insert(query),
1447 SqlStatement::Mutation(SqlMutation::Update(query)) => SqlCommand::Update(query),
1448 SqlStatement::Mutation(SqlMutation::Delete(query)) => SqlCommand::Delete(query),
1449 SqlStatement::Schema(SqlSchemaCommand::ExplainAlter(query)) => {
1450 SqlCommand::ExplainAlter(query)
1451 }
1452 SqlStatement::Schema(SqlSchemaCommand::CreateTable(query)) => {
1453 SqlCommand::CreateTable(query)
1454 }
1455 SqlStatement::Schema(SqlSchemaCommand::CreateCollection(query)) => {
1456 SqlCommand::CreateCollection(query)
1457 }
1458 SqlStatement::Schema(SqlSchemaCommand::CreateVector(query)) => {
1459 SqlCommand::CreateVector(query)
1460 }
1461 SqlStatement::Schema(SqlSchemaCommand::DropTable(query)) => {
1462 SqlCommand::DropTable(query)
1463 }
1464 SqlStatement::Schema(SqlSchemaCommand::DropGraph(query)) => {
1465 SqlCommand::DropGraph(query)
1466 }
1467 SqlStatement::Schema(SqlSchemaCommand::DropVector(query)) => {
1468 SqlCommand::DropVector(query)
1469 }
1470 SqlStatement::Schema(SqlSchemaCommand::DropDocument(query)) => {
1471 SqlCommand::DropDocument(query)
1472 }
1473 SqlStatement::Schema(SqlSchemaCommand::DropKv(query)) => SqlCommand::DropKv(query),
1474 SqlStatement::Schema(SqlSchemaCommand::DropCollection(query)) => {
1475 SqlCommand::DropCollection(query)
1476 }
1477 SqlStatement::Schema(SqlSchemaCommand::Truncate(query)) => SqlCommand::Truncate(query),
1478 SqlStatement::Schema(SqlSchemaCommand::AlterTable(query)) => {
1479 SqlCommand::AlterTable(query)
1480 }
1481 SqlStatement::Schema(SqlSchemaCommand::CreateVcsRef(query)) => {
1482 SqlCommand::CreateVcsRef(query)
1483 }
1484 SqlStatement::Schema(SqlSchemaCommand::DropVcsRef(query)) => {
1485 SqlCommand::DropVcsRef(query)
1486 }
1487 SqlStatement::Schema(SqlSchemaCommand::CreateIndex(query)) => {
1488 SqlCommand::CreateIndex(query)
1489 }
1490 SqlStatement::Schema(SqlSchemaCommand::DropIndex(query)) => {
1491 SqlCommand::DropIndex(query)
1492 }
1493 SqlStatement::Schema(SqlSchemaCommand::CreateTimeSeries(query)) => {
1494 SqlCommand::CreateTimeSeries(query)
1495 }
1496 SqlStatement::Schema(SqlSchemaCommand::CreateMetric(query)) => {
1497 SqlCommand::CreateMetric(query)
1498 }
1499 SqlStatement::Schema(SqlSchemaCommand::AlterMetric(query)) => {
1500 SqlCommand::AlterMetric(query)
1501 }
1502 SqlStatement::Schema(SqlSchemaCommand::CreateSlo(query)) => {
1503 SqlCommand::CreateSlo(query)
1504 }
1505 SqlStatement::Schema(SqlSchemaCommand::DropTimeSeries(query)) => {
1506 SqlCommand::DropTimeSeries(query)
1507 }
1508 SqlStatement::Schema(SqlSchemaCommand::CreateQueue(query)) => {
1509 SqlCommand::CreateQueue(query)
1510 }
1511 SqlStatement::Schema(SqlSchemaCommand::AlterQueue(query)) => {
1512 SqlCommand::AlterQueue(query)
1513 }
1514 SqlStatement::Schema(SqlSchemaCommand::DropQueue(query)) => {
1515 SqlCommand::DropQueue(query)
1516 }
1517 SqlStatement::Schema(SqlSchemaCommand::CreateTree(query)) => {
1518 SqlCommand::CreateTree(query)
1519 }
1520 SqlStatement::Schema(SqlSchemaCommand::DropTree(query)) => SqlCommand::DropTree(query),
1521 SqlStatement::Schema(SqlSchemaCommand::Probabilistic(command)) => {
1522 SqlCommand::Probabilistic(command)
1523 }
1524 SqlStatement::Admin(SqlAdminCommand::SetConfig { key, value }) => {
1525 SqlCommand::SetConfig { key, value }
1526 }
1527 SqlStatement::Admin(SqlAdminCommand::ShowConfig { prefix, as_json }) => {
1528 SqlCommand::ShowConfig { prefix, as_json }
1529 }
1530 SqlStatement::Admin(SqlAdminCommand::SetSecret { key, value }) => {
1531 SqlCommand::SetSecret { key, value }
1532 }
1533 SqlStatement::Admin(SqlAdminCommand::DeleteSecret { key }) => {
1534 SqlCommand::DeleteSecret { key }
1535 }
1536 SqlStatement::Admin(SqlAdminCommand::ShowSecrets { prefix }) => {
1537 SqlCommand::ShowSecrets { prefix }
1538 }
1539 SqlStatement::Admin(SqlAdminCommand::SetTenant(value)) => SqlCommand::SetTenant(value),
1540 SqlStatement::Admin(SqlAdminCommand::ShowTenant) => SqlCommand::ShowTenant,
1541 SqlStatement::Admin(SqlAdminCommand::TransactionControl(ctl)) => {
1542 SqlCommand::TransactionControl(ctl)
1543 }
1544 SqlStatement::Admin(SqlAdminCommand::Maintenance(cmd)) => SqlCommand::Maintenance(cmd),
1545 SqlStatement::Admin(SqlAdminCommand::Vcs(cmd)) => SqlCommand::Vcs(cmd),
1546 SqlStatement::Schema(SqlSchemaCommand::CreateSchema(q)) => SqlCommand::CreateSchema(q),
1547 SqlStatement::Schema(SqlSchemaCommand::DropSchema(q)) => SqlCommand::DropSchema(q),
1548 SqlStatement::Schema(SqlSchemaCommand::CreateSequence(q)) => {
1549 SqlCommand::CreateSequence(q)
1550 }
1551 SqlStatement::Schema(SqlSchemaCommand::DropSequence(q)) => SqlCommand::DropSequence(q),
1552 SqlStatement::Schema(SqlSchemaCommand::CopyFrom(q)) => SqlCommand::CopyFrom(q),
1553 SqlStatement::Schema(SqlSchemaCommand::CreateView(q)) => SqlCommand::CreateView(q),
1554 SqlStatement::Schema(SqlSchemaCommand::DropView(q)) => SqlCommand::DropView(q),
1555 SqlStatement::Schema(SqlSchemaCommand::RefreshMaterializedView(q)) => {
1556 SqlCommand::RefreshMaterializedView(q)
1557 }
1558 SqlStatement::Schema(SqlSchemaCommand::CreatePolicy(q)) => SqlCommand::CreatePolicy(q),
1559 SqlStatement::Schema(SqlSchemaCommand::DropPolicy(q)) => SqlCommand::DropPolicy(q),
1560 SqlStatement::Schema(SqlSchemaCommand::CreateServer(q)) => SqlCommand::CreateServer(q),
1561 SqlStatement::Schema(SqlSchemaCommand::DropServer(q)) => SqlCommand::DropServer(q),
1562 SqlStatement::Schema(SqlSchemaCommand::CreateForeignTable(q)) => {
1563 SqlCommand::CreateForeignTable(q)
1564 }
1565 SqlStatement::Schema(SqlSchemaCommand::DropForeignTable(q)) => {
1566 SqlCommand::DropForeignTable(q)
1567 }
1568 SqlStatement::Admin(SqlAdminCommand::Grant(s)) => SqlCommand::Grant(s),
1569 SqlStatement::Admin(SqlAdminCommand::Revoke(s)) => SqlCommand::Revoke(s),
1570 SqlStatement::Admin(SqlAdminCommand::AlterUser(s)) => SqlCommand::AlterUser(s),
1571 SqlStatement::Admin(SqlAdminCommand::CreateUser(s)) => SqlCommand::CreateUser(s),
1572 SqlStatement::Admin(SqlAdminCommand::IamPolicy(e)) => SqlCommand::IamPolicy(e),
1573 SqlStatement::Schema(SqlSchemaCommand::CreateMigration(q)) => {
1574 SqlCommand::CreateMigration(q)
1575 }
1576 SqlStatement::Schema(SqlSchemaCommand::ApplyMigration(q)) => {
1577 SqlCommand::ApplyMigration(q)
1578 }
1579 SqlStatement::Schema(SqlSchemaCommand::RollbackMigration(q)) => {
1580 SqlCommand::RollbackMigration(q)
1581 }
1582 SqlStatement::Schema(SqlSchemaCommand::ExplainMigration(q)) => {
1583 SqlCommand::ExplainMigration(q)
1584 }
1585 }
1586 }
1587
1588 pub fn into_query_expr(self) -> QueryExpr {
1589 self.into_command().into_query_expr()
1590 }
1591}
1592
1593impl FrontendStatement {
1594 pub fn into_query_expr(self) -> QueryExpr {
1595 match self {
1596 FrontendStatement::Sql(statement) => statement.into_query_expr(),
1597 FrontendStatement::Graph(query) => QueryExpr::Graph(query),
1598 FrontendStatement::GraphCommand(command) => QueryExpr::GraphCommand(command),
1599 FrontendStatement::Path(query) => QueryExpr::Path(query),
1600 FrontendStatement::Vector(query) => QueryExpr::Vector(query),
1601 FrontendStatement::Hybrid(query) => QueryExpr::Hybrid(query),
1602 FrontendStatement::Search(command) => QueryExpr::SearchCommand(command),
1603 FrontendStatement::Ask(query) => QueryExpr::Ask(query),
1604 FrontendStatement::QueueSelect(query) => QueryExpr::QueueSelect(query),
1605 FrontendStatement::QueueCommand(command) => QueryExpr::QueueCommand(command),
1606 FrontendStatement::EventsBackfill(query) => QueryExpr::EventsBackfill(query),
1607 FrontendStatement::EventsBackfillStatus { collection } => {
1608 QueryExpr::EventsBackfillStatus { collection }
1609 }
1610 FrontendStatement::TreeCommand(command) => QueryExpr::TreeCommand(command),
1611 FrontendStatement::ProbabilisticCommand(command) => {
1612 QueryExpr::ProbabilisticCommand(command)
1613 }
1614 FrontendStatement::KvCommand(command) => QueryExpr::KvCommand(command),
1615 FrontendStatement::ConfigCommand(command) => QueryExpr::ConfigCommand(command),
1616 FrontendStatement::Ranking(expr) => expr,
1617 }
1618 }
1619}
1620
1621pub fn parse_frontend(input: &str) -> Result<FrontendStatement, ParseError> {
1622 let mut parser = Parser::new(input)?;
1623 let statement = parser.parse_frontend_statement()?;
1624 if !parser.check(&Token::Eof) {
1625 return Err(ParseError::new(
1626 format!("Unexpected token after query: {:?}", parser.current.token),
1631 parser.position(),
1632 ));
1633 }
1634 Ok(statement)
1635}
1636
1637fn is_vcs_command_head(name: &str) -> bool {
1638 name.eq_ignore_ascii_case("CHECKPOINT")
1639 || name.eq_ignore_ascii_case("CHECKOUT")
1640 || name.eq_ignore_ascii_case("MERGE")
1641 || name.eq_ignore_ascii_case("CHERRY")
1642 || name.eq_ignore_ascii_case("REVERT")
1643 }
1647
1648impl SqlCommand {
1649 pub fn into_query_expr(self) -> QueryExpr {
1650 match self {
1651 SqlCommand::Select(query) => QueryExpr::Table(query),
1652 SqlCommand::Join(query) => QueryExpr::Join(query),
1653 SqlCommand::Insert(query) => QueryExpr::Insert(query),
1654 SqlCommand::Update(query) => QueryExpr::Update(query),
1655 SqlCommand::Delete(query) => QueryExpr::Delete(query),
1656 SqlCommand::ExplainAlter(query) => QueryExpr::ExplainAlter(query),
1657 SqlCommand::CreateTable(query) => QueryExpr::CreateTable(query),
1658 SqlCommand::CreateCollection(query) => QueryExpr::CreateCollection(query),
1659 SqlCommand::CreateVector(query) => QueryExpr::CreateVector(query),
1660 SqlCommand::DropTable(query) => QueryExpr::DropTable(query),
1661 SqlCommand::DropGraph(query) => QueryExpr::DropGraph(query),
1662 SqlCommand::DropVector(query) => QueryExpr::DropVector(query),
1663 SqlCommand::DropDocument(query) => QueryExpr::DropDocument(query),
1664 SqlCommand::DropKv(query) => QueryExpr::DropKv(query),
1665 SqlCommand::DropCollection(query) => QueryExpr::DropCollection(query),
1666 SqlCommand::Truncate(query) => QueryExpr::Truncate(query),
1667 SqlCommand::AlterTable(query) => QueryExpr::AlterTable(query),
1668 SqlCommand::CreateVcsRef(query) => QueryExpr::CreateVcsRef(query),
1669 SqlCommand::DropVcsRef(query) => QueryExpr::DropVcsRef(query),
1670 SqlCommand::CreateIndex(query) => QueryExpr::CreateIndex(query),
1671 SqlCommand::DropIndex(query) => QueryExpr::DropIndex(query),
1672 SqlCommand::CreateTimeSeries(query) => QueryExpr::CreateTimeSeries(query),
1673 SqlCommand::CreateMetric(query) => QueryExpr::CreateMetric(query),
1674 SqlCommand::AlterMetric(query) => QueryExpr::AlterMetric(query),
1675 SqlCommand::CreateSlo(query) => QueryExpr::CreateSlo(query),
1676 SqlCommand::DropTimeSeries(query) => QueryExpr::DropTimeSeries(query),
1677 SqlCommand::CreateQueue(query) => QueryExpr::CreateQueue(query),
1678 SqlCommand::AlterQueue(query) => QueryExpr::AlterQueue(query),
1679 SqlCommand::DropQueue(query) => QueryExpr::DropQueue(query),
1680 SqlCommand::CreateTree(query) => QueryExpr::CreateTree(query),
1681 SqlCommand::DropTree(query) => QueryExpr::DropTree(query),
1682 SqlCommand::Probabilistic(command) => QueryExpr::ProbabilisticCommand(command),
1683 SqlCommand::SetConfig { key, value } => QueryExpr::SetConfig { key, value },
1684 SqlCommand::ShowConfig { prefix, as_json } => QueryExpr::ShowConfig { prefix, as_json },
1685 SqlCommand::SetSecret { key, value } => QueryExpr::SetSecret { key, value },
1686 SqlCommand::DeleteSecret { key } => QueryExpr::DeleteSecret { key },
1687 SqlCommand::ShowSecrets { prefix } => QueryExpr::ShowSecrets { prefix },
1688 SqlCommand::SetTenant(value) => QueryExpr::SetTenant(value),
1689 SqlCommand::ShowTenant => QueryExpr::ShowTenant,
1690 SqlCommand::TransactionControl(ctl) => QueryExpr::TransactionControl(ctl),
1691 SqlCommand::Maintenance(cmd) => QueryExpr::MaintenanceCommand(cmd),
1692 SqlCommand::Vcs(cmd) => QueryExpr::VcsCommand(cmd),
1693 SqlCommand::CreateSchema(q) => QueryExpr::CreateSchema(q),
1694 SqlCommand::DropSchema(q) => QueryExpr::DropSchema(q),
1695 SqlCommand::CreateSequence(q) => QueryExpr::CreateSequence(q),
1696 SqlCommand::DropSequence(q) => QueryExpr::DropSequence(q),
1697 SqlCommand::CopyFrom(q) => QueryExpr::CopyFrom(q),
1698 SqlCommand::CreateView(q) => QueryExpr::CreateView(q),
1699 SqlCommand::DropView(q) => QueryExpr::DropView(q),
1700 SqlCommand::RefreshMaterializedView(q) => QueryExpr::RefreshMaterializedView(q),
1701 SqlCommand::CreatePolicy(q) => QueryExpr::CreatePolicy(q),
1702 SqlCommand::DropPolicy(q) => QueryExpr::DropPolicy(q),
1703 SqlCommand::CreateServer(q) => QueryExpr::CreateServer(q),
1704 SqlCommand::DropServer(q) => QueryExpr::DropServer(q),
1705 SqlCommand::CreateForeignTable(q) => QueryExpr::CreateForeignTable(q),
1706 SqlCommand::DropForeignTable(q) => QueryExpr::DropForeignTable(q),
1707 SqlCommand::Grant(s) => QueryExpr::Grant(s),
1708 SqlCommand::Revoke(s) => QueryExpr::Revoke(s),
1709 SqlCommand::AlterUser(s) => QueryExpr::AlterUser(s),
1710 SqlCommand::CreateUser(s) => QueryExpr::CreateUser(s),
1711 SqlCommand::IamPolicy(e) => e,
1712 SqlCommand::CreateMigration(q) => QueryExpr::CreateMigration(q),
1713 SqlCommand::ApplyMigration(q) => QueryExpr::ApplyMigration(q),
1714 SqlCommand::RollbackMigration(q) => QueryExpr::RollbackMigration(q),
1715 SqlCommand::ExplainMigration(q) => QueryExpr::ExplainMigration(q),
1716 }
1717 }
1718
1719 pub fn into_statement(self) -> SqlStatement {
1720 match self {
1721 SqlCommand::Select(query) => SqlStatement::Query(SqlQuery::Select(query)),
1722 SqlCommand::Join(query) => SqlStatement::Query(SqlQuery::Join(query)),
1723 SqlCommand::Insert(query) => SqlStatement::Mutation(SqlMutation::Insert(query)),
1724 SqlCommand::Update(query) => SqlStatement::Mutation(SqlMutation::Update(query)),
1725 SqlCommand::Delete(query) => SqlStatement::Mutation(SqlMutation::Delete(query)),
1726 SqlCommand::ExplainAlter(query) => {
1727 SqlStatement::Schema(SqlSchemaCommand::ExplainAlter(query))
1728 }
1729 SqlCommand::CreateTable(query) => {
1730 SqlStatement::Schema(SqlSchemaCommand::CreateTable(query))
1731 }
1732 SqlCommand::CreateCollection(query) => {
1733 SqlStatement::Schema(SqlSchemaCommand::CreateCollection(query))
1734 }
1735 SqlCommand::CreateVector(query) => {
1736 SqlStatement::Schema(SqlSchemaCommand::CreateVector(query))
1737 }
1738 SqlCommand::DropTable(query) => {
1739 SqlStatement::Schema(SqlSchemaCommand::DropTable(query))
1740 }
1741 SqlCommand::DropGraph(query) => {
1742 SqlStatement::Schema(SqlSchemaCommand::DropGraph(query))
1743 }
1744 SqlCommand::DropVector(query) => {
1745 SqlStatement::Schema(SqlSchemaCommand::DropVector(query))
1746 }
1747 SqlCommand::DropDocument(query) => {
1748 SqlStatement::Schema(SqlSchemaCommand::DropDocument(query))
1749 }
1750 SqlCommand::DropKv(query) => SqlStatement::Schema(SqlSchemaCommand::DropKv(query)),
1751 SqlCommand::DropCollection(query) => {
1752 SqlStatement::Schema(SqlSchemaCommand::DropCollection(query))
1753 }
1754 SqlCommand::Truncate(query) => SqlStatement::Schema(SqlSchemaCommand::Truncate(query)),
1755 SqlCommand::AlterTable(query) => {
1756 SqlStatement::Schema(SqlSchemaCommand::AlterTable(query))
1757 }
1758 SqlCommand::CreateVcsRef(query) => {
1759 SqlStatement::Schema(SqlSchemaCommand::CreateVcsRef(query))
1760 }
1761 SqlCommand::DropVcsRef(query) => {
1762 SqlStatement::Schema(SqlSchemaCommand::DropVcsRef(query))
1763 }
1764 SqlCommand::CreateIndex(query) => {
1765 SqlStatement::Schema(SqlSchemaCommand::CreateIndex(query))
1766 }
1767 SqlCommand::DropIndex(query) => {
1768 SqlStatement::Schema(SqlSchemaCommand::DropIndex(query))
1769 }
1770 SqlCommand::CreateTimeSeries(query) => {
1771 SqlStatement::Schema(SqlSchemaCommand::CreateTimeSeries(query))
1772 }
1773 SqlCommand::CreateMetric(query) => {
1774 SqlStatement::Schema(SqlSchemaCommand::CreateMetric(query))
1775 }
1776 SqlCommand::AlterMetric(query) => {
1777 SqlStatement::Schema(SqlSchemaCommand::AlterMetric(query))
1778 }
1779 SqlCommand::CreateSlo(query) => {
1780 SqlStatement::Schema(SqlSchemaCommand::CreateSlo(query))
1781 }
1782 SqlCommand::DropTimeSeries(query) => {
1783 SqlStatement::Schema(SqlSchemaCommand::DropTimeSeries(query))
1784 }
1785 SqlCommand::CreateQueue(query) => {
1786 SqlStatement::Schema(SqlSchemaCommand::CreateQueue(query))
1787 }
1788 SqlCommand::AlterQueue(query) => {
1789 SqlStatement::Schema(SqlSchemaCommand::AlterQueue(query))
1790 }
1791 SqlCommand::DropQueue(query) => {
1792 SqlStatement::Schema(SqlSchemaCommand::DropQueue(query))
1793 }
1794 SqlCommand::CreateTree(query) => {
1795 SqlStatement::Schema(SqlSchemaCommand::CreateTree(query))
1796 }
1797 SqlCommand::DropTree(query) => SqlStatement::Schema(SqlSchemaCommand::DropTree(query)),
1798 SqlCommand::Probabilistic(command) => {
1799 SqlStatement::Schema(SqlSchemaCommand::Probabilistic(command))
1800 }
1801 SqlCommand::SetConfig { key, value } => {
1802 SqlStatement::Admin(SqlAdminCommand::SetConfig { key, value })
1803 }
1804 SqlCommand::ShowConfig { prefix, as_json } => {
1805 SqlStatement::Admin(SqlAdminCommand::ShowConfig { prefix, as_json })
1806 }
1807 SqlCommand::SetSecret { key, value } => {
1808 SqlStatement::Admin(SqlAdminCommand::SetSecret { key, value })
1809 }
1810 SqlCommand::DeleteSecret { key } => {
1811 SqlStatement::Admin(SqlAdminCommand::DeleteSecret { key })
1812 }
1813 SqlCommand::ShowSecrets { prefix } => {
1814 SqlStatement::Admin(SqlAdminCommand::ShowSecrets { prefix })
1815 }
1816 SqlCommand::SetTenant(value) => SqlStatement::Admin(SqlAdminCommand::SetTenant(value)),
1817 SqlCommand::ShowTenant => SqlStatement::Admin(SqlAdminCommand::ShowTenant),
1818 SqlCommand::TransactionControl(ctl) => {
1819 SqlStatement::Admin(SqlAdminCommand::TransactionControl(ctl))
1820 }
1821 SqlCommand::Maintenance(cmd) => SqlStatement::Admin(SqlAdminCommand::Maintenance(cmd)),
1822 SqlCommand::Vcs(cmd) => SqlStatement::Admin(SqlAdminCommand::Vcs(cmd)),
1823 SqlCommand::CreateSchema(q) => SqlStatement::Schema(SqlSchemaCommand::CreateSchema(q)),
1824 SqlCommand::DropSchema(q) => SqlStatement::Schema(SqlSchemaCommand::DropSchema(q)),
1825 SqlCommand::CreateSequence(q) => {
1826 SqlStatement::Schema(SqlSchemaCommand::CreateSequence(q))
1827 }
1828 SqlCommand::DropSequence(q) => SqlStatement::Schema(SqlSchemaCommand::DropSequence(q)),
1829 SqlCommand::CopyFrom(q) => SqlStatement::Schema(SqlSchemaCommand::CopyFrom(q)),
1830 SqlCommand::CreateView(q) => SqlStatement::Schema(SqlSchemaCommand::CreateView(q)),
1831 SqlCommand::DropView(q) => SqlStatement::Schema(SqlSchemaCommand::DropView(q)),
1832 SqlCommand::RefreshMaterializedView(q) => {
1833 SqlStatement::Schema(SqlSchemaCommand::RefreshMaterializedView(q))
1834 }
1835 SqlCommand::CreatePolicy(q) => SqlStatement::Schema(SqlSchemaCommand::CreatePolicy(q)),
1836 SqlCommand::DropPolicy(q) => SqlStatement::Schema(SqlSchemaCommand::DropPolicy(q)),
1837 SqlCommand::CreateServer(q) => SqlStatement::Schema(SqlSchemaCommand::CreateServer(q)),
1838 SqlCommand::DropServer(q) => SqlStatement::Schema(SqlSchemaCommand::DropServer(q)),
1839 SqlCommand::CreateForeignTable(q) => {
1840 SqlStatement::Schema(SqlSchemaCommand::CreateForeignTable(q))
1841 }
1842 SqlCommand::DropForeignTable(q) => {
1843 SqlStatement::Schema(SqlSchemaCommand::DropForeignTable(q))
1844 }
1845 SqlCommand::Grant(s) => SqlStatement::Admin(SqlAdminCommand::Grant(s)),
1846 SqlCommand::Revoke(s) => SqlStatement::Admin(SqlAdminCommand::Revoke(s)),
1847 SqlCommand::AlterUser(s) => SqlStatement::Admin(SqlAdminCommand::AlterUser(s)),
1848 SqlCommand::CreateUser(s) => SqlStatement::Admin(SqlAdminCommand::CreateUser(s)),
1849 SqlCommand::IamPolicy(e) => SqlStatement::Admin(SqlAdminCommand::IamPolicy(e)),
1850 SqlCommand::CreateMigration(q) => {
1851 SqlStatement::Schema(SqlSchemaCommand::CreateMigration(q))
1852 }
1853 SqlCommand::ApplyMigration(q) => {
1854 SqlStatement::Schema(SqlSchemaCommand::ApplyMigration(q))
1855 }
1856 SqlCommand::RollbackMigration(q) => {
1857 SqlStatement::Schema(SqlSchemaCommand::RollbackMigration(q))
1858 }
1859 SqlCommand::ExplainMigration(q) => {
1860 SqlStatement::Schema(SqlSchemaCommand::ExplainMigration(q))
1861 }
1862 }
1863 }
1864}
1865
1866impl<'a> Parser<'a> {
1867 fn parse_events_command(&mut self) -> Result<QueryExpr, ParseError> {
1868 self.expect_ident()?; if self.consume_ident_ci("STATUS")? {
1870 let mut query = TableQuery::new("red.subscriptions");
1871 let collection = match self.peek().clone() {
1872 Token::Ident(name) => {
1873 self.advance()?;
1874 Some(name)
1875 }
1876 Token::String(name) => {
1877 self.advance()?;
1878 Some(name)
1879 }
1880 _ => None,
1881 };
1882 self.parse_table_clauses(&mut query)?;
1883 if let Some(collection) = collection {
1884 let filter = Filter::compare(
1885 FieldRef::column("red.subscriptions", "collection"),
1886 CompareOp::Eq,
1887 Value::text(collection),
1888 );
1889 let expr = filter_to_expr(&filter);
1890 query.where_expr = Some(match query.where_expr.take() {
1891 Some(existing) => Expr::binop(BinOp::And, existing, expr),
1892 None => expr,
1893 });
1894 query.filter = Some(match query.filter.take() {
1895 Some(existing) => existing.and(filter),
1896 None => filter,
1897 });
1898 }
1899 return Ok(QueryExpr::Table(query));
1900 }
1901
1902 if !self.consume_ident_ci("BACKFILL")? {
1903 return Err(ParseError::expected(
1904 vec!["BACKFILL", "STATUS"],
1905 self.peek(),
1906 self.position(),
1907 ));
1908 }
1909
1910 if self.consume_ident_ci("STATUS")? {
1911 let collection = self.expect_ident()?;
1912 return Ok(QueryExpr::EventsBackfillStatus { collection });
1913 }
1914
1915 let collection = self.expect_ident()?;
1916 let where_filter = if self.consume(&Token::Where)? {
1917 let mut parts = Vec::new();
1918 while !self.check(&Token::Eof) && !self.check(&Token::To) {
1919 parts.push(self.peek().to_string());
1920 self.advance()?;
1921 }
1922 if parts.is_empty() {
1923 return Err(ParseError::expected(
1924 vec!["predicate"],
1925 self.peek(),
1926 self.position(),
1927 ));
1928 }
1929 Some(parts.join(" "))
1930 } else {
1931 None
1932 };
1933
1934 self.expect(Token::To)?;
1935 let target_queue = self.expect_ident()?;
1936 let limit = if self.consume(&Token::Limit)? {
1937 Some(self.parse_positive_integer("LIMIT")? as u64)
1938 } else {
1939 None
1940 };
1941
1942 Ok(QueryExpr::EventsBackfill(EventsBackfillQuery {
1943 collection,
1944 where_filter,
1945 target_queue,
1946 limit,
1947 }))
1948 }
1949
1950 pub(crate) fn parse_fdw_options_clause(&mut self) -> Result<Vec<(String, String)>, ParseError> {
1955 if !self.consume(&Token::Options)? {
1956 return Ok(Vec::new());
1957 }
1958 self.expect(Token::LParen)?;
1959 let mut out: Vec<(String, String)> = Vec::new();
1960 loop {
1961 let was_ident = matches!(self.peek(), Token::Ident(_));
1966 let raw = self.expect_ident_or_keyword()?;
1967 let key = if was_ident {
1968 raw
1969 } else {
1970 raw.to_ascii_lowercase()
1971 };
1972 let value = self.parse_string()?;
1974 out.push((key, value));
1975 if !self.consume(&Token::Comma)? {
1976 break;
1977 }
1978 }
1979 self.expect(Token::RParen)?;
1980 Ok(out)
1981 }
1982
1983 pub fn parse_frontend_statement(&mut self) -> Result<FrontendStatement, ParseError> {
1985 self.enter_depth()?;
1986 let result = self.parse_frontend_statement_inner();
1987 self.exit_depth();
1988 result
1989 }
1990
1991 fn parse_frontend_statement_inner(&mut self) -> Result<FrontendStatement, ParseError> {
1992 match self.peek() {
1993 Token::Select => match self.parse_select_query()? {
1994 QueryExpr::Table(query) => Ok(FrontendStatement::Sql(SqlStatement::Query(
1995 SqlQuery::Select(query),
1996 ))),
1997 QueryExpr::Join(query) => Ok(FrontendStatement::Sql(SqlStatement::Query(
1998 SqlQuery::Join(query),
1999 ))),
2000 QueryExpr::QueueSelect(query) => Ok(FrontendStatement::QueueSelect(query)),
2001 other => Err(ParseError::new(
2002 format!("internal: SELECT produced unexpected query kind {other:?}"),
2003 self.position(),
2004 )),
2005 },
2006 Token::From
2007 | Token::Insert
2008 | Token::Update
2009 | Token::Truncate
2010 | Token::Create
2011 | Token::Drop
2012 | Token::Alter
2013 | Token::Set
2014 | Token::Begin
2015 | Token::Commit
2016 | Token::Rollback
2017 | Token::Savepoint
2018 | Token::Release
2019 | Token::Start
2020 | Token::Vacuum
2021 | Token::Analyze
2022 | Token::Copy
2023 | Token::Refresh => self.parse_sql_statement().map(FrontendStatement::Sql),
2024 Token::Explain => {
2025 if matches!(
2026 self.peek_next()?,
2027 Token::Ident(name) if name.eq_ignore_ascii_case("ASK")
2028 ) {
2029 match self.parse_explain_ask_query()? {
2030 QueryExpr::Ask(query) => Ok(FrontendStatement::Ask(query)),
2031 other => Err(ParseError::new(
2032 format!(
2033 "internal: EXPLAIN ASK produced unexpected query kind {other:?}"
2034 ),
2035 self.position(),
2036 )),
2037 }
2038 } else {
2039 self.parse_sql_statement().map(FrontendStatement::Sql)
2040 }
2041 }
2042 Token::Ident(name) if name.eq_ignore_ascii_case("SHOW") => {
2043 self.parse_sql_statement().map(FrontendStatement::Sql)
2044 }
2045 Token::Ident(name) if name.eq_ignore_ascii_case("RESET") => {
2046 self.parse_sql_statement().map(FrontendStatement::Sql)
2047 }
2048 Token::Ident(name)
2049 if name.eq_ignore_ascii_case("RANK")
2050 || name.eq_ignore_ascii_case("APPROX")
2051 || name.eq_ignore_ascii_case("APPROXIMATE")
2052 || name.eq_ignore_ascii_case("ZRANK")
2053 || name.eq_ignore_ascii_case("ZRANGE") =>
2054 {
2055 self.parse_ranking_read().map(FrontendStatement::Ranking)
2056 }
2057 Token::Desc => self.parse_sql_statement().map(FrontendStatement::Sql),
2058 Token::Ident(name)
2059 if name.eq_ignore_ascii_case("DESCRIBE") || name.eq_ignore_ascii_case("DESC") =>
2060 {
2061 self.parse_sql_statement().map(FrontendStatement::Sql)
2062 }
2063 Token::Ident(name)
2064 if name.eq_ignore_ascii_case("GRANT")
2065 || name.eq_ignore_ascii_case("REVOKE")
2066 || name.eq_ignore_ascii_case("SIMULATE")
2067 || name.eq_ignore_ascii_case("LINT")
2068 || name.eq_ignore_ascii_case("MIGRATE")
2069 || name.eq_ignore_ascii_case("APPLY") =>
2070 {
2071 self.parse_sql_statement().map(FrontendStatement::Sql)
2072 }
2073 Token::Ident(name) if name.eq_ignore_ascii_case("WATCH") => {
2074 self.advance()?;
2075 if matches!(
2076 self.peek(),
2077 Token::Ident(name) if name.eq_ignore_ascii_case("CONFIG")
2078 ) {
2079 match self.parse_config_watch_after_watch()? {
2080 QueryExpr::ConfigCommand(command) => {
2081 Ok(FrontendStatement::ConfigCommand(command))
2082 }
2083 other => Err(ParseError::new(
2084 format!(
2085 "internal: WATCH CONFIG produced unexpected query kind {other:?}"
2086 ),
2087 self.position(),
2088 )),
2089 }
2090 } else if matches!(
2091 self.peek(),
2092 Token::Ident(name) if name.eq_ignore_ascii_case("VAULT")
2093 ) {
2094 match self.parse_vault_watch_after_watch()? {
2095 QueryExpr::KvCommand(command) => Ok(FrontendStatement::KvCommand(command)),
2096 other => Err(ParseError::new(
2097 format!(
2098 "internal: WATCH VAULT produced unexpected query kind {other:?}"
2099 ),
2100 self.position(),
2101 )),
2102 }
2103 } else {
2104 match self.parse_kv_watch(reddb_types::catalog::CollectionModel::Kv)? {
2105 QueryExpr::KvCommand(command) => Ok(FrontendStatement::KvCommand(command)),
2106 other => Err(ParseError::new(
2107 format!("internal: WATCH produced unexpected query kind {other:?}"),
2108 self.position(),
2109 )),
2110 }
2111 }
2112 }
2113 Token::List => {
2114 self.advance()?;
2115 if matches!(
2116 self.peek(),
2117 Token::Ident(name) if name.eq_ignore_ascii_case("CONFIG")
2118 ) {
2119 match self.parse_config_list_after_list()? {
2120 QueryExpr::ConfigCommand(command) => {
2121 Ok(FrontendStatement::ConfigCommand(command))
2122 }
2123 other => Err(ParseError::new(
2124 format!(
2125 "internal: LIST CONFIG produced unexpected query kind {other:?}"
2126 ),
2127 self.position(),
2128 )),
2129 }
2130 } else if matches!(self.peek(), Token::Kv) {
2131 match self.parse_kv_list_after_list()? {
2132 QueryExpr::KvCommand(command) => Ok(FrontendStatement::KvCommand(command)),
2133 other => Err(ParseError::new(
2134 format!("internal: LIST KV produced unexpected query kind {other:?}"),
2135 self.position(),
2136 )),
2137 }
2138 } else if matches!(
2139 self.peek(),
2140 Token::Ident(name) if name.eq_ignore_ascii_case("VAULT")
2141 ) {
2142 match self.parse_vault_list_after_list()? {
2143 QueryExpr::KvCommand(command) => Ok(FrontendStatement::KvCommand(command)),
2144 other => Err(ParseError::new(
2145 format!(
2146 "internal: LIST VAULT produced unexpected query kind {other:?}"
2147 ),
2148 self.position(),
2149 )),
2150 }
2151 } else {
2152 Err(ParseError::expected(
2153 vec!["CONFIG", "KV", "VAULT"],
2154 self.peek(),
2155 self.position(),
2156 ))
2157 }
2158 }
2159 Token::Ident(name) if name.eq_ignore_ascii_case("LIST") => {
2160 self.advance()?;
2161 if matches!(
2162 self.peek(),
2163 Token::Ident(name) if name.eq_ignore_ascii_case("CONFIG")
2164 ) {
2165 match self.parse_config_list_after_list()? {
2166 QueryExpr::ConfigCommand(command) => {
2167 Ok(FrontendStatement::ConfigCommand(command))
2168 }
2169 other => Err(ParseError::new(
2170 format!(
2171 "internal: LIST CONFIG produced unexpected query kind {other:?}"
2172 ),
2173 self.position(),
2174 )),
2175 }
2176 } else if matches!(self.peek(), Token::Kv) {
2177 match self.parse_kv_list_after_list()? {
2178 QueryExpr::KvCommand(command) => Ok(FrontendStatement::KvCommand(command)),
2179 other => Err(ParseError::new(
2180 format!("internal: LIST KV produced unexpected query kind {other:?}"),
2181 self.position(),
2182 )),
2183 }
2184 } else if matches!(
2185 self.peek(),
2186 Token::Ident(name) if name.eq_ignore_ascii_case("VAULT")
2187 ) {
2188 match self.parse_vault_list_after_list()? {
2189 QueryExpr::KvCommand(command) => Ok(FrontendStatement::KvCommand(command)),
2190 other => Err(ParseError::new(
2191 format!(
2192 "internal: LIST VAULT produced unexpected query kind {other:?}"
2193 ),
2194 self.position(),
2195 )),
2196 }
2197 } else {
2198 Err(ParseError::expected(
2199 vec!["CONFIG", "KV", "VAULT"],
2200 self.peek(),
2201 self.position(),
2202 ))
2203 }
2204 }
2205 Token::Ident(name) if name.eq_ignore_ascii_case("INVALIDATE") => {
2206 if matches!(
2207 self.peek_next()?,
2208 Token::Ident(next) if next.eq_ignore_ascii_case("CONFIG")
2209 ) {
2210 match self.parse_config_command()? {
2211 QueryExpr::ConfigCommand(command) => {
2212 Ok(FrontendStatement::ConfigCommand(command))
2213 }
2214 other => Err(ParseError::new(
2215 format!("internal: CONFIG produced unexpected query kind {other:?}"),
2216 self.position(),
2217 )),
2218 }
2219 } else {
2220 self.advance()?;
2221 match self.parse_kv_invalidate_tags_after_invalidate()? {
2222 QueryExpr::KvCommand(command) => Ok(FrontendStatement::KvCommand(command)),
2223 other => Err(ParseError::new(
2224 format!(
2225 "internal: INVALIDATE produced unexpected query kind {other:?}"
2226 ),
2227 self.position(),
2228 )),
2229 }
2230 }
2231 }
2232 Token::Attach | Token::Detach => self.parse_sql_statement().map(FrontendStatement::Sql),
2233 Token::Match => match self.parse_match_query()? {
2234 QueryExpr::Graph(query) => Ok(FrontendStatement::Graph(query)),
2235 other => Err(ParseError::new(
2236 format!("internal: MATCH produced unexpected query kind {other:?}"),
2237 self.position(),
2238 )),
2239 },
2240 Token::Path => match self.parse_path_query()? {
2241 QueryExpr::Path(query) => Ok(FrontendStatement::Path(query)),
2242 other => Err(ParseError::new(
2243 format!("internal: PATH produced unexpected query kind {other:?}"),
2244 self.position(),
2245 )),
2246 },
2247 Token::Vector => match self.parse_vector_query()? {
2248 QueryExpr::Vector(query) => Ok(FrontendStatement::Vector(query)),
2249 other => Err(ParseError::new(
2250 format!("internal: VECTOR produced unexpected query kind {other:?}"),
2251 self.position(),
2252 )),
2253 },
2254 Token::Hybrid => match self.parse_hybrid_query()? {
2255 QueryExpr::Hybrid(query) => Ok(FrontendStatement::Hybrid(query)),
2256 other => Err(ParseError::new(
2257 format!("internal: HYBRID produced unexpected query kind {other:?}"),
2258 self.position(),
2259 )),
2260 },
2261 Token::Graph => match self.parse_graph_command()? {
2262 QueryExpr::GraphCommand(command) => Ok(FrontendStatement::GraphCommand(command)),
2263 other => Err(ParseError::new(
2264 format!("internal: GRAPH produced unexpected query kind {other:?}"),
2265 self.position(),
2266 )),
2267 },
2268 Token::Search => match self.parse_search_command()? {
2269 QueryExpr::SearchCommand(command) => Ok(FrontendStatement::Search(command)),
2270 other => Err(ParseError::new(
2271 format!("internal: SEARCH produced unexpected query kind {other:?}"),
2272 self.position(),
2273 )),
2274 },
2275 Token::Ident(name) if name.eq_ignore_ascii_case("ASK") => {
2276 match self.parse_ask_query()? {
2277 QueryExpr::Ask(query) => Ok(FrontendStatement::Ask(query)),
2278 other => Err(ParseError::new(
2279 format!("internal: ASK produced unexpected query kind {other:?}"),
2280 self.position(),
2281 )),
2282 }
2283 }
2284 Token::Ident(name) if name.eq_ignore_ascii_case("UNSEAL") => {
2285 match self.parse_unseal_vault_command()? {
2286 QueryExpr::KvCommand(command) => Ok(FrontendStatement::KvCommand(command)),
2287 other => Err(ParseError::new(
2288 format!("internal: UNSEAL VAULT produced unexpected query kind {other:?}"),
2289 self.position(),
2290 )),
2291 }
2292 }
2293 Token::Queue => match self.parse_queue_command()? {
2294 QueryExpr::QueueCommand(command) => Ok(FrontendStatement::QueueCommand(command)),
2295 other => Err(ParseError::new(
2296 format!("internal: QUEUE produced unexpected query kind {other:?}"),
2297 self.position(),
2298 )),
2299 },
2300 Token::Ident(name) if name.eq_ignore_ascii_case("EVENTS") => {
2301 match self.parse_events_command()? {
2302 QueryExpr::Table(query) => Ok(FrontendStatement::Sql(SqlStatement::Query(
2303 SqlQuery::Select(query),
2304 ))),
2305 QueryExpr::EventsBackfill(query) => {
2306 Ok(FrontendStatement::EventsBackfill(query))
2307 }
2308 QueryExpr::EventsBackfillStatus { collection } => {
2309 Ok(FrontendStatement::EventsBackfillStatus { collection })
2310 }
2311 other => Err(ParseError::new(
2312 format!("internal: EVENTS produced unexpected query kind {other:?}"),
2313 self.position(),
2314 )),
2315 }
2316 }
2317 Token::Kv => match self.parse_kv_command()? {
2318 QueryExpr::KvCommand(command) => Ok(FrontendStatement::KvCommand(command)),
2319 other => Err(ParseError::new(
2320 format!("internal: KV produced unexpected query kind {other:?}"),
2321 self.position(),
2322 )),
2323 },
2324 Token::Delete => {
2325 if matches!(
2326 self.peek_next()?,
2327 Token::Ident(name) if name.eq_ignore_ascii_case("CONFIG")
2328 ) {
2329 match self.parse_config_command()? {
2330 QueryExpr::ConfigCommand(command) => {
2331 Ok(FrontendStatement::ConfigCommand(command))
2332 }
2333 other => Err(ParseError::new(
2334 format!("internal: CONFIG produced unexpected query kind {other:?}"),
2335 self.position(),
2336 )),
2337 }
2338 } else if matches!(
2339 self.peek_next()?,
2340 Token::Ident(name) if name.eq_ignore_ascii_case("VAULT")
2341 ) {
2342 match self.parse_vault_lifecycle_command()? {
2343 QueryExpr::KvCommand(command) => Ok(FrontendStatement::KvCommand(command)),
2344 other => Err(ParseError::new(
2345 format!("internal: VAULT produced unexpected query kind {other:?}"),
2346 self.position(),
2347 )),
2348 }
2349 } else {
2350 self.parse_sql_statement().map(FrontendStatement::Sql)
2351 }
2352 }
2353 Token::Add => match self.parse_config_command()? {
2354 QueryExpr::ConfigCommand(command) => Ok(FrontendStatement::ConfigCommand(command)),
2355 other => Err(ParseError::new(
2356 format!("internal: CONFIG produced unexpected query kind {other:?}"),
2357 self.position(),
2358 )),
2359 },
2360 Token::Purge => match self.parse_vault_lifecycle_command()? {
2361 QueryExpr::KvCommand(command) => Ok(FrontendStatement::KvCommand(command)),
2362 other => Err(ParseError::new(
2363 format!("internal: VAULT produced unexpected query kind {other:?}"),
2364 self.position(),
2365 )),
2366 },
2367 Token::Ident(name)
2368 if name.eq_ignore_ascii_case("PUT")
2369 || name.eq_ignore_ascii_case("GET")
2370 || name.eq_ignore_ascii_case("RESOLVE")
2371 || name.eq_ignore_ascii_case("ROTATE")
2372 || name.eq_ignore_ascii_case("HISTORY")
2373 || name.eq_ignore_ascii_case("PURGE")
2374 || name.eq_ignore_ascii_case("INCR")
2375 || name.eq_ignore_ascii_case("DECR")
2376 || name.eq_ignore_ascii_case("INVALIDATE") =>
2377 {
2378 if name.eq_ignore_ascii_case("RESOLVE")
2381 && matches!(self.peek_next()?, Token::Ident(next) if next.eq_ignore_ascii_case("CONFLICT"))
2382 {
2383 self.parse_sql_command()
2384 .map(SqlCommand::into_statement)
2385 .map(FrontendStatement::Sql)
2386 } else if matches!(
2387 self.peek_next()?,
2388 Token::Ident(next) if next.eq_ignore_ascii_case("VAULT")
2389 ) {
2390 match self.parse_vault_lifecycle_command()? {
2391 QueryExpr::KvCommand(command) => Ok(FrontendStatement::KvCommand(command)),
2392 other => Err(ParseError::new(
2393 format!("internal: VAULT produced unexpected query kind {other:?}"),
2394 self.position(),
2395 )),
2396 }
2397 } else {
2398 match self.parse_config_command()? {
2399 QueryExpr::ConfigCommand(command) => {
2400 Ok(FrontendStatement::ConfigCommand(command))
2401 }
2402 other => Err(ParseError::new(
2403 format!("internal: CONFIG produced unexpected query kind {other:?}"),
2404 self.position(),
2405 )),
2406 }
2407 }
2408 }
2409 Token::Ident(name) if name.eq_ignore_ascii_case("VAULT") => {
2410 match self.parse_vault_command()? {
2411 QueryExpr::KvCommand(command) => Ok(FrontendStatement::KvCommand(command)),
2412 other => Err(ParseError::new(
2413 format!("internal: VAULT produced unexpected query kind {other:?}"),
2414 self.position(),
2415 )),
2416 }
2417 }
2418 Token::Tree => match self.parse_tree_command()? {
2419 QueryExpr::TreeCommand(command) => Ok(FrontendStatement::TreeCommand(command)),
2420 other => Err(ParseError::new(
2421 format!("internal: TREE produced unexpected query kind {other:?}"),
2422 self.position(),
2423 )),
2424 },
2425 Token::Ident(name) if name.eq_ignore_ascii_case("HLL") => {
2426 match self.parse_hll_command()? {
2427 QueryExpr::ProbabilisticCommand(command) => {
2428 Ok(FrontendStatement::ProbabilisticCommand(command))
2429 }
2430 other => Err(ParseError::new(
2431 format!("internal: HLL produced unexpected query kind {other:?}"),
2432 self.position(),
2433 )),
2434 }
2435 }
2436 Token::Ident(name) if name.eq_ignore_ascii_case("SKETCH") => {
2437 match self.parse_sketch_command()? {
2438 QueryExpr::ProbabilisticCommand(command) => {
2439 Ok(FrontendStatement::ProbabilisticCommand(command))
2440 }
2441 other => Err(ParseError::new(
2442 format!("internal: SKETCH produced unexpected query kind {other:?}"),
2443 self.position(),
2444 )),
2445 }
2446 }
2447 Token::Ident(name) if name.eq_ignore_ascii_case("FILTER") => {
2448 match self.parse_filter_command()? {
2449 QueryExpr::ProbabilisticCommand(command) => {
2450 Ok(FrontendStatement::ProbabilisticCommand(command))
2451 }
2452 other => Err(ParseError::new(
2453 format!("internal: FILTER produced unexpected query kind {other:?}"),
2454 self.position(),
2455 )),
2456 }
2457 }
2458 Token::Ident(name) if name.eq_ignore_ascii_case("EVENTS") => self
2459 .parse_sql_command()
2460 .map(SqlCommand::into_statement)
2461 .map(FrontendStatement::Sql),
2462 Token::Ident(name)
2463 if is_vcs_command_head(name) || name.eq_ignore_ascii_case("RESET") =>
2464 {
2465 self.parse_sql_command()
2466 .map(SqlCommand::into_statement)
2467 .map(FrontendStatement::Sql)
2468 }
2469 other => Err(ParseError::expected(
2470 vec![
2471 "SELECT",
2472 "MATCH",
2473 "PATH",
2474 "FROM",
2475 "VECTOR",
2476 "HYBRID",
2477 "INSERT",
2478 "UPDATE",
2479 "DELETE",
2480 "TRUNCATE",
2481 "CREATE",
2482 "DROP",
2483 "ALTER",
2484 "GRAPH",
2485 "SEARCH",
2486 "ASK",
2487 "QUEUE",
2488 "EVENTS",
2489 "KV",
2490 "HLL",
2491 "TREE",
2492 "SKETCH",
2493 "FILTER",
2494 "SET",
2495 "SHOW",
2496 "RESET",
2497 "CHECKPOINT",
2498 "CHECKOUT",
2499 "MERGE",
2500 "CHERRY",
2501 "REVERT",
2502 "RESOLVE",
2503 "DESCRIBE",
2504 "DESC",
2505 "RANK",
2506 "ZRANK",
2507 "ZRANGE",
2508 ],
2509 other,
2510 self.position(),
2511 )),
2512 }
2513 }
2514
2515 fn parse_ranking_read(&mut self) -> Result<QueryExpr, ParseError> {
2516 let head = self.expect_ident()?;
2517 if head.eq_ignore_ascii_case("RANK") {
2518 return self.parse_rank_after_rank(false);
2519 }
2520 if head.eq_ignore_ascii_case("APPROX") || head.eq_ignore_ascii_case("APPROXIMATE") {
2521 if !self.consume_ident_ci("RANK")? {
2522 return Err(ParseError::expected(
2523 vec!["RANK"],
2524 self.peek(),
2525 self.position(),
2526 ));
2527 }
2528 return self.parse_rank_after_rank(true);
2529 }
2530 if head.eq_ignore_ascii_case("ZRANK") {
2531 return self.parse_zrank();
2532 }
2533 if head.eq_ignore_ascii_case("ZRANGE") {
2534 return self.parse_zrange();
2535 }
2536 Err(ParseError::expected(
2537 vec!["RANK", "APPROX RANK", "ZRANK", "ZRANGE"],
2538 self.peek(),
2539 self.position(),
2540 ))
2541 }
2542
2543 fn parse_rank_after_rank(&mut self, approximate: bool) -> Result<QueryExpr, ParseError> {
2544 if self.consume(&Token::Of)? {
2545 let entity_id = self.parse_u64_slot("rank entity id")?;
2546 self.expect(Token::In)?;
2547 let ranking = self.expect_ident()?;
2548 let query = RankOfQuery { ranking, entity_id };
2549 return Ok(if approximate {
2550 QueryExpr::ApproxRankOf(query)
2551 } else {
2552 QueryExpr::RankOf(query)
2553 });
2554 }
2555
2556 if !approximate && self.consume(&Token::Range)? {
2557 let lo = self.parse_positive_u64_slot("rank range lower bound")?;
2558 self.expect(Token::To)?;
2559 let hi = self.parse_positive_u64_slot("rank range upper bound")?;
2560 if hi < lo {
2561 return Err(ParseError::value_out_of_range(
2562 "rank range upper bound",
2563 "must be greater than or equal to the lower bound",
2564 self.position(),
2565 ));
2566 }
2567 self.expect(Token::In)?;
2568 let ranking = self.expect_ident()?;
2569 return Ok(QueryExpr::RankRange(RankRangeQuery { ranking, lo, hi }));
2570 }
2571
2572 Err(ParseError::expected(
2573 if approximate {
2574 vec!["OF"]
2575 } else {
2576 vec!["OF", "RANGE"]
2577 },
2578 self.peek(),
2579 self.position(),
2580 ))
2581 }
2582
2583 fn parse_zrank(&mut self) -> Result<QueryExpr, ParseError> {
2584 let ranking = self.expect_ident()?;
2585 let entity_id = self.parse_u64_slot("ZRANK entity id")?;
2586 Ok(QueryExpr::RankOf(RankOfQuery { ranking, entity_id }))
2587 }
2588
2589 fn parse_zrange(&mut self) -> Result<QueryExpr, ParseError> {
2590 let ranking = self.expect_ident()?;
2591 let start = self.parse_u64_slot("ZRANGE start")?;
2592 let stop = self.parse_u64_slot("ZRANGE stop")?;
2593 if stop < start {
2594 return Err(ParseError::value_out_of_range(
2595 "ZRANGE stop",
2596 "must be greater than or equal to start",
2597 self.position(),
2598 ));
2599 }
2600 let _with_scores = self.consume_ident_ci("WITHSCORES")?;
2601 Ok(QueryExpr::RankRange(RankRangeQuery {
2602 ranking,
2603 lo: start + 1,
2604 hi: stop + 1,
2605 }))
2606 }
2607
2608 fn parse_positive_u64_slot(&mut self, field: &'static str) -> Result<u64, ParseError> {
2609 let value = self.parse_u64_slot(field)?;
2610 if value == 0 {
2611 return Err(ParseError::value_out_of_range(
2612 field,
2613 "must be a positive integer",
2614 self.position(),
2615 ));
2616 }
2617 Ok(value)
2618 }
2619
2620 fn parse_u64_slot(&mut self, field: &'static str) -> Result<u64, ParseError> {
2621 let pos = self.position();
2622 if matches!(self.peek(), Token::Minus | Token::Dash) {
2623 return Err(ParseError::value_out_of_range(
2624 field,
2625 "must be an unsigned integer",
2626 pos,
2627 ));
2628 }
2629 let raw = self.parse_integer()?;
2630 u64::try_from(raw)
2631 .map_err(|_| ParseError::value_out_of_range(field, "must be an unsigned integer", pos))
2632 }
2633
2634 #[inline(never)]
2635 fn parse_vcs_atom(&mut self, field: &'static str) -> Result<String, ParseError> {
2636 match self.peek().clone() {
2637 Token::Ident(value) | Token::String(value) => {
2638 self.advance()?;
2639 Ok(value)
2640 }
2641 Token::Integer(value) => {
2642 self.advance()?;
2643 Ok(value.to_string())
2644 }
2645 other => Err(ParseError::expected(vec![field], &other, self.position())),
2646 }
2647 }
2648
2649 #[inline(never)]
2650 fn parse_vcs_command(&mut self) -> Result<VcsCommand, ParseError> {
2651 let head = self.expect_ident()?;
2652 if head.eq_ignore_ascii_case("CHECKPOINT") {
2653 let message = self.parse_string()?;
2654 let author = if self.consume_ident_ci("AUTHOR")? {
2655 Some(self.parse_string()?)
2656 } else {
2657 None
2658 };
2659 return Ok(VcsCommand::Checkpoint { message, author });
2660 }
2661 if head.eq_ignore_ascii_case("CHECKOUT") {
2662 return Ok(VcsCommand::Checkout {
2663 target: self.parse_vcs_atom("commitish")?,
2664 });
2665 }
2666 if head.eq_ignore_ascii_case("RESET") {
2667 let mode = if self.consume_ident_ci("HARD")? {
2668 VcsResetMode::Hard
2669 } else if self.consume_ident_ci("SOFT")? {
2670 VcsResetMode::Soft
2671 } else {
2672 let _ = self.consume_ident_ci("MIXED")?;
2674 VcsResetMode::Mixed
2675 };
2676 self.expect(Token::To)?;
2677 return Ok(VcsCommand::Reset {
2678 mode,
2679 target: self.parse_vcs_atom("commitish")?,
2680 });
2681 }
2682 if head.eq_ignore_ascii_case("MERGE") {
2683 return Ok(VcsCommand::Merge {
2684 branch: self.parse_vcs_atom("branch")?,
2685 });
2686 }
2687 if head.eq_ignore_ascii_case("CHERRY") {
2688 if !self.consume_ident_ci("PICK")? {
2689 return Err(ParseError::expected(
2690 vec!["PICK"],
2691 self.peek(),
2692 self.position(),
2693 ));
2694 }
2695 return Ok(VcsCommand::CherryPick {
2696 commit: self.parse_vcs_atom("commit")?,
2697 });
2698 }
2699 if head.eq_ignore_ascii_case("REVERT") {
2700 return Ok(VcsCommand::Revert {
2701 commit: self.parse_vcs_atom("commit")?,
2702 });
2703 }
2704 if head.eq_ignore_ascii_case("RESOLVE") {
2705 if !self.consume_ident_ci("CONFLICT")? {
2706 return Err(ParseError::expected(
2707 vec!["CONFLICT"],
2708 self.peek(),
2709 self.position(),
2710 ));
2711 }
2712 let key = self.parse_string()?;
2713 if !self.consume(&Token::Using)? {
2714 return Err(ParseError::expected(
2715 vec!["USING"],
2716 self.peek(),
2717 self.position(),
2718 ));
2719 }
2720 let resolution = if self.consume_ident_ci("OURS")? {
2721 VcsConflictResolution::Ours
2722 } else if self.consume_ident_ci("THEIRS")? {
2723 VcsConflictResolution::Theirs
2724 } else {
2725 return Err(ParseError::expected(
2726 vec!["OURS", "THEIRS"],
2727 self.peek(),
2728 self.position(),
2729 ));
2730 };
2731 return Ok(VcsCommand::ResolveConflict { key, resolution });
2732 }
2733 Err(ParseError::expected(
2734 vec![
2735 "CHECKPOINT",
2736 "CHECKOUT",
2737 "RESET",
2738 "MERGE",
2739 "CHERRY",
2740 "REVERT",
2741 "RESOLVE",
2742 ],
2743 self.peek(),
2744 self.position(),
2745 ))
2746 }
2747
2748 pub fn parse_sql_statement(&mut self) -> Result<SqlStatement, ParseError> {
2750 self.parse_sql_command().map(SqlCommand::into_statement)
2751 }
2752
2753 fn parse_dotted_admin_path(&mut self, lowercase: bool) -> Result<String, ParseError> {
2754 let mut path = self.expect_ident()?;
2755 while self.consume(&Token::Dot)? {
2756 let next = self.expect_ident_or_keyword()?;
2757 path = format!("{path}.{next}");
2758 }
2759 Ok(if lowercase {
2760 path.to_ascii_lowercase()
2761 } else {
2762 path
2763 })
2764 }
2765
2766 fn normalize_secret_admin_path(path: String) -> String {
2767 if let Some(rest) = path.strip_prefix("red.secrets.") {
2768 format!("red.secret.{rest}")
2769 } else if path == "red.secrets" {
2770 "red.secret".to_string()
2771 } else {
2772 path
2773 }
2774 }
2775
2776 #[inline(never)]
2785 fn parse_create_command(&mut self) -> Result<SqlCommand, ParseError> {
2786 let pos = self.position();
2787 self.advance()?;
2788
2789 let mut or_replace = false;
2793 if self.consume(&Token::Or)? || self.consume_ident_ci("OR")? {
2794 let _ = self.consume_ident_ci("REPLACE")?;
2795 or_replace = true;
2796 }
2797 let materialized = self.consume(&Token::Materialized)?;
2798 if self.check(&Token::View) {
2799 self.advance()?;
2800 let if_not_exists = self.match_if_not_exists()?;
2801 let name = self.expect_ident()?;
2802 let mut retention_duration_ms: Option<u64> = None;
2810 if self.check(&Token::With) {
2811 self.advance()?;
2812 if !self.consume(&Token::Retention)? && !self.consume_ident_ci("RETENTION")? {
2813 return Err(ParseError::expected(
2814 vec!["RETENTION"],
2815 self.peek(),
2816 self.position(),
2817 ));
2818 }
2819 if !materialized {
2820 return Err(ParseError::new(
2821 "WITH RETENTION is only valid on \
2822 CREATE MATERIALIZED VIEW"
2823 .to_string(),
2824 self.position(),
2825 ));
2826 }
2827 let value = self.parse_float()?;
2828 let unit_mult = self.parse_duration_unit()?;
2829 retention_duration_ms = Some((value * unit_mult).round() as u64);
2830 }
2831 if !self.consume(&Token::As)? && !self.consume_ident_ci("AS")? {
2834 return Err(ParseError::expected(
2835 vec!["AS"],
2836 self.peek(),
2837 self.position(),
2838 ));
2839 }
2840 let body = self.parse_sql_command()?.into_query_expr();
2843 let mut refresh_every_ms: Option<u64> = None;
2848 if self.check(&Token::Refresh) {
2849 if !materialized {
2850 return Err(ParseError::new(
2851 "REFRESH EVERY is only valid on \
2852 CREATE MATERIALIZED VIEW"
2853 .to_string(),
2854 self.position(),
2855 ));
2856 }
2857 self.advance()?;
2858 if !self.consume_ident_ci("EVERY")? {
2859 return Err(ParseError::expected(
2860 vec!["EVERY"],
2861 self.peek(),
2862 self.position(),
2863 ));
2864 }
2865 let value = self.parse_float()?;
2866 let unit_mult = self.parse_duration_unit()?;
2867 refresh_every_ms = Some((value * unit_mult).round() as u64);
2868 }
2869 return Ok(SqlCommand::CreateView(CreateViewQuery {
2870 name,
2871 query: Box::new(body),
2872 materialized,
2873 if_not_exists,
2874 or_replace,
2875 refresh_every_ms,
2876 retention_duration_ms,
2877 }));
2878 }
2879 if or_replace || materialized {
2882 return Err(ParseError::expected(
2883 vec!["VIEW"],
2884 self.peek(),
2885 self.position(),
2886 ));
2887 }
2888
2889 if matches!(self.peek(), Token::Ident(name) if name.eq_ignore_ascii_case("USER")) {
2890 let stmt = self.parse_create_user_statement()?;
2891 Ok(SqlCommand::CreateUser(stmt))
2892 } else if self.consume_ident_ci("BRANCH")? {
2893 self.parse_create_vcs_ref(VcsRefKind::Branch)
2894 } else if self.consume_ident_ci("TAG")? {
2895 self.parse_create_vcs_ref(VcsRefKind::Tag)
2896 } else if self.check(&Token::Index) || self.check(&Token::Unique) {
2897 match self.parse_create_index_query()? {
2898 QueryExpr::CreateIndex(query) => Ok(SqlCommand::CreateIndex(query)),
2899 other => Err(ParseError::new(
2900 format!("internal: CREATE INDEX produced unexpected kind {other:?}"),
2901 self.position(),
2902 )),
2903 }
2904 } else if self.check(&Token::Table) {
2905 self.expect(Token::Table)?;
2906 match self.parse_create_table_body()? {
2907 QueryExpr::CreateTable(query) => Ok(SqlCommand::CreateTable(query)),
2908 other => Err(ParseError::new(
2909 format!("internal: CREATE TABLE produced unexpected kind {other:?}"),
2910 self.position(),
2911 )),
2912 }
2913 } else if self.check(&Token::Graph) {
2914 self.advance()?;
2915 match self.parse_create_collection_model_body(CollectionModel::Graph)? {
2916 QueryExpr::CreateTable(query) => Ok(SqlCommand::CreateTable(query)),
2917 other => Err(ParseError::new(
2918 format!("internal: CREATE GRAPH produced unexpected kind {other:?}"),
2919 self.position(),
2920 )),
2921 }
2922 } else if self.check(&Token::Document) {
2923 self.advance()?;
2924 match self.parse_create_collection_model_body(CollectionModel::Document)? {
2925 QueryExpr::CreateTable(query) => Ok(SqlCommand::CreateTable(query)),
2926 other => Err(ParseError::new(
2927 format!("internal: CREATE DOCUMENT produced unexpected kind {other:?}"),
2928 self.position(),
2929 )),
2930 }
2931 } else if self.check(&Token::Vector) {
2932 self.advance()?;
2933 match self.parse_create_vector_body()? {
2934 QueryExpr::CreateVector(query) => Ok(SqlCommand::CreateVector(query)),
2935 other => Err(ParseError::new(
2936 format!("internal: CREATE VECTOR produced unexpected kind {other:?}"),
2937 self.position(),
2938 )),
2939 }
2940 } else if self.check(&Token::Collection) {
2941 self.advance()?;
2942 match self.parse_create_collection_body()? {
2943 QueryExpr::CreateCollection(query) => Ok(SqlCommand::CreateCollection(query)),
2944 other => Err(ParseError::new(
2945 format!("internal: CREATE COLLECTION produced unexpected kind {other:?}"),
2946 self.position(),
2947 )),
2948 }
2949 } else if self.check(&Token::Kv) {
2950 self.advance()?;
2951 match self.parse_create_keyed_body(CollectionModel::Kv)? {
2952 QueryExpr::CreateTable(query) => Ok(SqlCommand::CreateTable(query)),
2953 other => Err(ParseError::new(
2954 format!("internal: CREATE KV produced unexpected kind {other:?}"),
2955 self.position(),
2956 )),
2957 }
2958 } else if self.consume_ident_ci("CONFIG")? {
2959 match self.parse_create_keyed_body(CollectionModel::Config)? {
2960 QueryExpr::CreateTable(query) => Ok(SqlCommand::CreateTable(query)),
2961 other => Err(ParseError::new(
2962 format!("internal: CREATE CONFIG produced unexpected kind {other:?}"),
2963 self.position(),
2964 )),
2965 }
2966 } else if self.consume_ident_ci("VAULT")? {
2967 match self.parse_create_keyed_body(CollectionModel::Vault)? {
2968 QueryExpr::CreateTable(query) => Ok(SqlCommand::CreateTable(query)),
2969 other => Err(ParseError::new(
2970 format!("internal: CREATE VAULT produced unexpected kind {other:?}"),
2971 self.position(),
2972 )),
2973 }
2974 } else if self.check(&Token::Timeseries) {
2975 self.advance()?;
2976 match self.parse_create_timeseries_body()? {
2977 QueryExpr::CreateTimeSeries(query) => Ok(SqlCommand::CreateTimeSeries(query)),
2978 other => Err(ParseError::new(
2979 format!("internal: CREATE TIMESERIES produced unexpected kind {other:?}"),
2980 self.position(),
2981 )),
2982 }
2983 } else if self.check(&Token::Metric) {
2984 self.advance()?;
2985 match self.parse_create_metric_body()? {
2986 QueryExpr::CreateMetric(query) => Ok(SqlCommand::CreateMetric(query)),
2987 other => Err(ParseError::new(
2988 format!("internal: CREATE METRIC produced unexpected kind {other:?}"),
2989 self.position(),
2990 )),
2991 }
2992 } else if self.consume_ident_ci("METRICS")? {
2993 match self.parse_create_metrics_body()? {
2994 QueryExpr::CreateTable(query) => Ok(SqlCommand::CreateTable(query)),
2995 other => Err(ParseError::new(
2996 format!("internal: CREATE METRICS produced unexpected kind {other:?}"),
2997 self.position(),
2998 )),
2999 }
3000 } else if self.consume_ident_ci("SLO")? {
3001 match self.parse_create_slo_body()? {
3002 QueryExpr::CreateSlo(query) => Ok(SqlCommand::CreateSlo(query)),
3003 other => Err(ParseError::new(
3004 format!("internal: CREATE SLO produced unexpected kind {other:?}"),
3005 self.position(),
3006 )),
3007 }
3008 } else if matches!(self.peek(), Token::Ident(s) if s.eq_ignore_ascii_case("HYPERTABLE")) {
3009 self.advance()?;
3010 match self.parse_create_hypertable_body()? {
3011 QueryExpr::CreateTimeSeries(query) => Ok(SqlCommand::CreateTimeSeries(query)),
3012 other => Err(ParseError::new(
3013 format!("internal: CREATE HYPERTABLE produced unexpected kind {other:?}"),
3014 self.position(),
3015 )),
3016 }
3017 } else if self.check(&Token::Queue) {
3018 self.advance()?;
3019 match self.parse_create_queue_body()? {
3020 QueryExpr::CreateQueue(query) => Ok(SqlCommand::CreateQueue(query)),
3021 other => Err(ParseError::new(
3022 format!("internal: CREATE QUEUE produced unexpected kind {other:?}"),
3023 self.position(),
3024 )),
3025 }
3026 } else if self.check(&Token::Tree) {
3027 self.advance()?;
3028 match self.parse_create_tree_body()? {
3029 QueryExpr::CreateTree(query) => Ok(SqlCommand::CreateTree(query)),
3030 other => Err(ParseError::new(
3031 format!("internal: CREATE TREE produced unexpected kind {other:?}"),
3032 self.position(),
3033 )),
3034 }
3035 } else if matches!(self.peek(), Token::Ident(n) if
3036 n.eq_ignore_ascii_case("HLL") ||
3037 n.eq_ignore_ascii_case("SKETCH") ||
3038 n.eq_ignore_ascii_case("FILTER"))
3039 {
3040 match self.parse_create_probabilistic()? {
3041 QueryExpr::ProbabilisticCommand(command) => Ok(SqlCommand::Probabilistic(command)),
3042 other => Err(ParseError::new(
3043 format!("internal: CREATE probabilistic produced unexpected kind {other:?}"),
3044 self.position(),
3045 )),
3046 }
3047 } else if self.check(&Token::Schema) {
3048 self.advance()?;
3050 let if_not_exists = self.match_if_not_exists()?;
3051 let name = self.expect_ident()?;
3052 Ok(SqlCommand::CreateSchema(CreateSchemaQuery {
3053 name,
3054 if_not_exists,
3055 }))
3056 } else if self.check(&Token::Policy) {
3057 self.advance()?;
3062 if matches!(self.peek(), Token::String(_)) {
3063 let expr = self.parse_create_iam_policy_after_keywords()?;
3065 return Ok(SqlCommand::IamPolicy(expr));
3071 }
3072 let name = self.expect_ident()?;
3073 self.expect(Token::On)?;
3074
3075 let (target_kind, table) = {
3076 use crate::ast::PolicyTargetKind;
3077 let kw = match self.peek() {
3078 Token::Ident(s) => Some(s.to_ascii_uppercase()),
3079 _ => None,
3080 };
3081 let kind = kw.as_deref().and_then(|k| match k {
3082 "NODES" => Some(PolicyTargetKind::Nodes),
3083 "EDGES" => Some(PolicyTargetKind::Edges),
3084 "VECTORS" => Some(PolicyTargetKind::Vectors),
3085 "MESSAGES" => Some(PolicyTargetKind::Messages),
3086 "POINTS" => Some(PolicyTargetKind::Points),
3087 "DOCUMENTS" => Some(PolicyTargetKind::Documents),
3088 _ => None,
3089 });
3090 if let Some(k) = kind {
3091 self.advance()?;
3092 self.expect(Token::Of)?;
3093 let coll = self.expect_ident()?;
3094 (k, coll)
3095 } else {
3096 let coll = self.expect_ident()?;
3097 (PolicyTargetKind::Table, coll)
3098 }
3099 };
3100
3101 let action = if self.consume(&Token::For)? {
3102 let a = match self.peek() {
3103 Token::Select => {
3104 self.advance()?;
3105 Some(PolicyAction::Select)
3106 }
3107 Token::Insert => {
3108 self.advance()?;
3109 Some(PolicyAction::Insert)
3110 }
3111 Token::Update => {
3112 self.advance()?;
3113 Some(PolicyAction::Update)
3114 }
3115 Token::Delete => {
3116 self.advance()?;
3117 Some(PolicyAction::Delete)
3118 }
3119 Token::All => {
3120 self.advance()?;
3121 None
3122 }
3123 _ => None,
3124 };
3125 a
3126 } else {
3127 None
3128 };
3129
3130 let role = if self.consume(&Token::To)? {
3131 Some(self.expect_ident()?)
3132 } else {
3133 None
3134 };
3135
3136 self.expect(Token::Using)?;
3137 self.expect(Token::LParen)?;
3138 let filter = self.parse_filter()?;
3139 self.expect(Token::RParen)?;
3140
3141 Ok(SqlCommand::CreatePolicy(CreatePolicyQuery {
3142 name,
3143 table,
3144 action,
3145 role,
3146 using: Box::new(filter),
3147 target_kind,
3148 }))
3149 } else if self.check(&Token::Server) {
3150 self.advance()?;
3154 let if_not_exists = self.match_if_not_exists()?;
3155 let name = self.expect_ident()?;
3156 self.expect(Token::Foreign)?;
3157 self.expect(Token::Data)?;
3158 self.expect(Token::Wrapper)?;
3159 let wrapper = self.expect_ident()?;
3160 let options = self.parse_fdw_options_clause()?;
3161 Ok(SqlCommand::CreateServer(CreateServerQuery {
3162 name,
3163 wrapper,
3164 options,
3165 if_not_exists,
3166 }))
3167 } else if self.check(&Token::Foreign) {
3168 self.advance()?;
3172 self.expect(Token::Table)?;
3173 let if_not_exists = self.match_if_not_exists()?;
3174 let name = self.expect_ident()?;
3175 self.expect(Token::LParen)?;
3176 let mut columns = Vec::new();
3177 loop {
3178 let col_name = self.expect_ident()?;
3179 let data_type = self.expect_ident_or_keyword()?;
3180 let mut not_null = false;
3183 if matches!(self.peek(), Token::Ident(n) if n.eq_ignore_ascii_case("NOT")) {
3184 self.advance()?;
3185 if matches!(self.peek(), Token::Ident(n) if n.eq_ignore_ascii_case("NULL")) {
3186 self.advance()?;
3187 not_null = true;
3188 }
3189 }
3190 columns.push(ForeignColumnDef {
3191 name: col_name,
3192 data_type,
3193 not_null,
3194 });
3195 if !self.consume(&Token::Comma)? {
3196 break;
3197 }
3198 }
3199 self.expect(Token::RParen)?;
3200 self.expect(Token::Server)?;
3201 let server = self.expect_ident()?;
3202 let options = self.parse_fdw_options_clause()?;
3203 Ok(SqlCommand::CreateForeignTable(CreateForeignTableQuery {
3204 name,
3205 server,
3206 columns,
3207 options,
3208 if_not_exists,
3209 }))
3210 } else if self.check(&Token::Sequence) {
3211 self.advance()?;
3214 let if_not_exists = self.match_if_not_exists()?;
3215 let name = self.expect_ident()?;
3216 let mut start: i64 = 1;
3217 let mut increment: i64 = 1;
3218 loop {
3220 if self.consume(&Token::Start)? {
3221 let _ = self.consume(&Token::With)? || self.consume_ident_ci("WITH")?;
3223 start = self.parse_integer()?;
3224 } else if self.consume(&Token::Increment)? {
3225 let _ = self.consume(&Token::By)? || self.consume_ident_ci("BY")?;
3227 increment = self.parse_integer()?;
3228 } else {
3229 break;
3230 }
3231 }
3232 Ok(SqlCommand::CreateSequence(CreateSequenceQuery {
3233 name,
3234 if_not_exists,
3235 start,
3236 increment,
3237 }))
3238 } else if matches!(self.peek(), Token::Ident(n) if n.eq_ignore_ascii_case("MIGRATION")) {
3239 self.advance()?; match self.parse_create_migration_body()? {
3241 QueryExpr::CreateMigration(q) => Ok(SqlCommand::CreateMigration(q)),
3242 other => Err(ParseError::new(
3243 format!("internal: CREATE MIGRATION produced unexpected kind {other:?}"),
3244 self.position(),
3245 )),
3246 }
3247 } else if let Some(reason) = analytics_v0_non_goal_create(self.peek()) {
3248 Err(ParseError::new(reason, self.position()))
3256 } else if let Some(err) =
3257 ParseError::unsupported_recognized_token(self.peek(), self.position())
3258 {
3259 Err(err)
3260 } else {
3261 Err(ParseError::expected(
3262 vec![
3263 "TABLE",
3264 "GRAPH",
3265 "VECTOR",
3266 "DOCUMENT",
3267 "KV",
3268 "COLLECTION",
3269 "INDEX",
3270 "UNIQUE",
3271 "METRIC",
3272 "TIMESERIES",
3273 "QUEUE",
3274 "TREE",
3275 "HLL",
3276 "SKETCH",
3277 "FILTER",
3278 "SCHEMA",
3279 "SEQUENCE",
3280 "USER",
3281 "BRANCH",
3282 "TAG",
3283 "MIGRATION",
3284 ],
3285 self.peek(),
3286 pos,
3287 ))
3288 }
3289 }
3290
3291 pub fn parse_sql_command(&mut self) -> Result<SqlCommand, ParseError> {
3292 self.enter_depth()?;
3293 let result = self.parse_sql_command_inner();
3294 self.exit_depth();
3295 result
3296 }
3297
3298 #[inline(never)]
3304 fn parse_show_vcs_ref_table(&mut self, table: &str) -> Result<SqlCommand, ParseError> {
3305 let mut query = TableQuery::new(table);
3306 self.parse_table_clauses(&mut query)?;
3307 Ok(SqlCommand::Select(query))
3308 }
3309
3310 #[inline(never)]
3316 fn parse_create_vcs_ref(&mut self, kind: VcsRefKind) -> Result<SqlCommand, ParseError> {
3317 match self.parse_create_vcs_ref_body(kind)? {
3318 QueryExpr::CreateVcsRef(query) => Ok(SqlCommand::CreateVcsRef(query)),
3319 other => {
3320 let noun = match kind {
3321 VcsRefKind::Branch => "BRANCH",
3322 VcsRefKind::Tag => "TAG",
3323 };
3324 Err(ParseError::new(
3325 format!("internal: CREATE {noun} produced unexpected kind {other:?}"),
3326 self.position(),
3327 ))
3328 }
3329 }
3330 }
3331
3332 #[inline(never)]
3336 fn parse_drop_vcs_ref(&mut self, kind: VcsRefKind) -> Result<SqlCommand, ParseError> {
3337 match self.parse_drop_vcs_ref_body(kind)? {
3338 QueryExpr::DropVcsRef(query) => Ok(SqlCommand::DropVcsRef(query)),
3339 other => {
3340 let noun = match kind {
3341 VcsRefKind::Branch => "BRANCH",
3342 VcsRefKind::Tag => "TAG",
3343 };
3344 Err(ParseError::new(
3345 format!("internal: DROP {noun} produced unexpected kind {other:?}"),
3346 self.position(),
3347 ))
3348 }
3349 }
3350 }
3351
3352 fn parse_sql_command_inner(&mut self) -> Result<SqlCommand, ParseError> {
3353 match self.peek() {
3354 Token::Select => match self.parse_select_query()? {
3355 QueryExpr::Table(query) => Ok(SqlCommand::Select(query)),
3356 QueryExpr::Join(query) => Ok(SqlCommand::Join(query)),
3357 other => Err(ParseError::new(
3358 format!("internal: SELECT produced unexpected query kind {other:?}"),
3359 self.position(),
3360 )),
3361 },
3362 Token::From => match self.parse_from_query()? {
3363 QueryExpr::Table(query) => Ok(SqlCommand::Select(query)),
3364 QueryExpr::Join(query) => Ok(SqlCommand::Join(query)),
3365 other => Err(ParseError::new(
3366 format!("internal: FROM produced unexpected query kind {other:?}"),
3367 self.position(),
3368 )),
3369 },
3370 Token::Insert => match self.parse_insert_query()? {
3371 QueryExpr::Insert(query) => Ok(SqlCommand::Insert(query)),
3372 other => Err(ParseError::new(
3373 format!("internal: INSERT produced unexpected query kind {other:?}"),
3374 self.position(),
3375 )),
3376 },
3377 Token::Update => match self.parse_update_query()? {
3378 QueryExpr::Update(query) => Ok(SqlCommand::Update(query)),
3379 other => Err(ParseError::new(
3380 format!("internal: UPDATE produced unexpected query kind {other:?}"),
3381 self.position(),
3382 )),
3383 },
3384 Token::Delete => {
3385 if matches!(self.peek_next()?, Token::Ident(n) if n.eq_ignore_ascii_case("SECRET"))
3386 {
3387 self.advance()?; self.advance()?; let key =
3390 Self::normalize_secret_admin_path(self.parse_dotted_admin_path(true)?);
3391 Ok(SqlCommand::DeleteSecret { key })
3392 } else {
3393 match self.parse_delete_query()? {
3394 QueryExpr::Delete(query) => Ok(SqlCommand::Delete(query)),
3395 other => Err(ParseError::new(
3396 format!("internal: DELETE produced unexpected query kind {other:?}"),
3397 self.position(),
3398 )),
3399 }
3400 }
3401 }
3402 Token::Truncate => {
3403 self.advance()?;
3404 let model = if self.consume(&Token::Table)? {
3405 Some(CollectionModel::Table)
3406 } else if self.consume(&Token::Graph)? {
3407 Some(CollectionModel::Graph)
3408 } else if self.consume(&Token::Vector)? {
3409 Some(CollectionModel::Vector)
3410 } else if self.consume(&Token::Document)? {
3411 Some(CollectionModel::Document)
3412 } else if self.consume(&Token::Timeseries)? {
3413 Some(CollectionModel::TimeSeries)
3414 } else if self.consume_ident_ci("METRICS")? {
3415 Some(CollectionModel::Metrics)
3416 } else if self.consume(&Token::Kv)? {
3417 Some(CollectionModel::Kv)
3418 } else if self.consume(&Token::Queue)? {
3419 Some(CollectionModel::Queue)
3420 } else if self.consume(&Token::Collection)? {
3421 None
3422 } else {
3423 return Err(ParseError::expected(
3424 vec![
3425 "TABLE",
3426 "GRAPH",
3427 "VECTOR",
3428 "DOCUMENT",
3429 "TIMESERIES",
3430 "METRICS",
3431 "KV",
3432 "QUEUE",
3433 "COLLECTION",
3434 ],
3435 self.peek(),
3436 self.position(),
3437 ));
3438 };
3439 match self.parse_truncate_body(model)? {
3440 QueryExpr::Truncate(query) => Ok(SqlCommand::Truncate(query)),
3441 other => Err(ParseError::new(
3442 format!("internal: TRUNCATE produced unexpected kind {other:?}"),
3443 self.position(),
3444 )),
3445 }
3446 }
3447 Token::Explain => {
3448 if matches!(self.peek_next()?, Token::Ident(n) if n.eq_ignore_ascii_case("MIGRATION"))
3451 {
3452 self.advance()?; match self.parse_explain_migration_after_keyword()? {
3454 QueryExpr::ExplainMigration(q) => Ok(SqlCommand::ExplainMigration(q)),
3455 other => Err(ParseError::new(
3456 format!(
3457 "internal: EXPLAIN MIGRATION produced unexpected kind {other:?}"
3458 ),
3459 self.position(),
3460 )),
3461 }
3462 } else {
3463 match self.parse_explain_alter_query()? {
3464 QueryExpr::ExplainAlter(query) => Ok(SqlCommand::ExplainAlter(query)),
3465 other => Err(ParseError::new(
3466 format!("internal: EXPLAIN produced unexpected query kind {other:?}"),
3467 self.position(),
3468 )),
3469 }
3470 }
3471 }
3472 Token::Create => self.parse_create_command(),
3473 Token::Drop => {
3474 let pos = self.position();
3475 self.advance()?;
3476
3477 let materialized = self.consume(&Token::Materialized)?;
3479 if self.check(&Token::View) {
3480 self.advance()?;
3481 let if_exists = self.match_if_exists()?;
3482 let name = self.expect_ident()?;
3483 return Ok(SqlCommand::DropView(DropViewQuery {
3484 name,
3485 materialized,
3486 if_exists,
3487 }));
3488 }
3489 if materialized {
3490 return Err(ParseError::expected(
3491 vec!["VIEW"],
3492 self.peek(),
3493 self.position(),
3494 ));
3495 }
3496
3497 if self.consume_ident_ci("BRANCH")? {
3498 self.parse_drop_vcs_ref(VcsRefKind::Branch)
3499 } else if self.consume_ident_ci("TAG")? {
3500 self.parse_drop_vcs_ref(VcsRefKind::Tag)
3501 } else if self.check(&Token::Index) {
3502 match self.parse_drop_index_query()? {
3503 QueryExpr::DropIndex(query) => Ok(SqlCommand::DropIndex(query)),
3504 other => Err(ParseError::new(
3505 format!("internal: DROP INDEX produced unexpected kind {other:?}"),
3506 self.position(),
3507 )),
3508 }
3509 } else if self.check(&Token::Table) {
3510 self.expect(Token::Table)?;
3511 match self.parse_drop_table_body()? {
3512 QueryExpr::DropTable(query) => Ok(SqlCommand::DropTable(query)),
3513 other => Err(ParseError::new(
3514 format!("internal: DROP TABLE produced unexpected kind {other:?}"),
3515 self.position(),
3516 )),
3517 }
3518 } else if self.check(&Token::Graph) {
3519 self.advance()?;
3520 match self.parse_drop_graph_body()? {
3521 QueryExpr::DropGraph(query) => Ok(SqlCommand::DropGraph(query)),
3522 other => Err(ParseError::new(
3523 format!("internal: DROP GRAPH produced unexpected kind {other:?}"),
3524 self.position(),
3525 )),
3526 }
3527 } else if self.check(&Token::Vector) {
3528 self.advance()?;
3529 match self.parse_drop_vector_body()? {
3530 QueryExpr::DropVector(query) => Ok(SqlCommand::DropVector(query)),
3531 other => Err(ParseError::new(
3532 format!("internal: DROP VECTOR produced unexpected kind {other:?}"),
3533 self.position(),
3534 )),
3535 }
3536 } else if self.check(&Token::Document) {
3537 self.advance()?;
3538 match self.parse_drop_document_body()? {
3539 QueryExpr::DropDocument(query) => Ok(SqlCommand::DropDocument(query)),
3540 other => Err(ParseError::new(
3541 format!("internal: DROP DOCUMENT produced unexpected kind {other:?}"),
3542 self.position(),
3543 )),
3544 }
3545 } else if self.check(&Token::Kv) {
3546 self.advance()?;
3547 match self.parse_drop_kv_body()? {
3548 QueryExpr::DropKv(query) => Ok(SqlCommand::DropKv(query)),
3549 other => Err(ParseError::new(
3550 format!("internal: DROP KV produced unexpected kind {other:?}"),
3551 self.position(),
3552 )),
3553 }
3554 } else if self.consume_ident_ci("CONFIG")? {
3555 match self.parse_drop_keyed_body(CollectionModel::Config)? {
3556 QueryExpr::DropKv(query) => Ok(SqlCommand::DropKv(query)),
3557 other => Err(ParseError::new(
3558 format!("internal: DROP CONFIG produced unexpected kind {other:?}"),
3559 self.position(),
3560 )),
3561 }
3562 } else if self.consume_ident_ci("VAULT")? {
3563 match self.parse_drop_keyed_body(CollectionModel::Vault)? {
3564 QueryExpr::DropKv(query) => Ok(SqlCommand::DropKv(query)),
3565 other => Err(ParseError::new(
3566 format!("internal: DROP VAULT produced unexpected kind {other:?}"),
3567 self.position(),
3568 )),
3569 }
3570 } else if self.check(&Token::Collection) {
3571 self.advance()?;
3572 match self.parse_drop_collection_body()? {
3573 QueryExpr::DropCollection(query) => Ok(SqlCommand::DropCollection(query)),
3574 other => Err(ParseError::new(
3575 format!("internal: DROP COLLECTION produced unexpected kind {other:?}"),
3576 self.position(),
3577 )),
3578 }
3579 } else if self.check(&Token::Timeseries) {
3580 self.advance()?;
3581 match self.parse_drop_timeseries_body()? {
3582 QueryExpr::DropTimeSeries(query) => Ok(SqlCommand::DropTimeSeries(query)),
3583 other => Err(ParseError::new(
3584 format!("internal: DROP TIMESERIES produced unexpected kind {other:?}"),
3585 self.position(),
3586 )),
3587 }
3588 } else if self.consume_ident_ci("METRICS")? {
3589 match self.parse_drop_collection_model_body(Some(CollectionModel::Metrics))? {
3590 QueryExpr::DropCollection(query) => Ok(SqlCommand::DropCollection(query)),
3591 other => Err(ParseError::new(
3592 format!("internal: DROP METRICS produced unexpected kind {other:?}"),
3593 self.position(),
3594 )),
3595 }
3596 } else if matches!(self.peek(), Token::Ident(s) if s.eq_ignore_ascii_case("HYPERTABLE"))
3597 {
3598 self.advance()?;
3602 match self.parse_drop_timeseries_body()? {
3603 QueryExpr::DropTimeSeries(query) => Ok(SqlCommand::DropTimeSeries(query)),
3604 other => Err(ParseError::new(
3605 format!("internal: DROP HYPERTABLE produced unexpected kind {other:?}"),
3606 self.position(),
3607 )),
3608 }
3609 } else if self.check(&Token::Queue) {
3610 self.advance()?;
3611 match self.parse_drop_queue_body()? {
3612 QueryExpr::DropQueue(query) => Ok(SqlCommand::DropQueue(query)),
3613 other => Err(ParseError::new(
3614 format!("internal: DROP QUEUE produced unexpected kind {other:?}"),
3615 self.position(),
3616 )),
3617 }
3618 } else if self.check(&Token::Tree) {
3619 self.advance()?;
3620 match self.parse_drop_tree_body()? {
3621 QueryExpr::DropTree(query) => Ok(SqlCommand::DropTree(query)),
3622 other => Err(ParseError::new(
3623 format!("internal: DROP TREE produced unexpected kind {other:?}"),
3624 self.position(),
3625 )),
3626 }
3627 } else if matches!(self.peek(), Token::Ident(n) if
3628 n.eq_ignore_ascii_case("HLL") ||
3629 n.eq_ignore_ascii_case("SKETCH") ||
3630 n.eq_ignore_ascii_case("FILTER"))
3631 {
3632 match self.parse_drop_probabilistic()? {
3633 QueryExpr::ProbabilisticCommand(command) => {
3634 Ok(SqlCommand::Probabilistic(command))
3635 }
3636 other => Err(ParseError::new(
3637 format!(
3638 "internal: DROP probabilistic produced unexpected kind {other:?}"
3639 ),
3640 self.position(),
3641 )),
3642 }
3643 } else if self.check(&Token::Schema) {
3644 self.advance()?;
3646 let if_exists = self.match_if_exists()?;
3647 let name = self.expect_ident()?;
3648 let cascade = self.consume(&Token::Cascade)?;
3649 Ok(SqlCommand::DropSchema(DropSchemaQuery {
3650 name,
3651 if_exists,
3652 cascade,
3653 }))
3654 } else if self.check(&Token::Policy) {
3655 self.advance()?;
3659 if matches!(self.peek(), Token::String(_)) {
3660 let expr = self.parse_drop_iam_policy_after_keywords()?;
3661 return Ok(SqlCommand::IamPolicy(expr));
3662 }
3663 let if_exists = self.match_if_exists()?;
3664 let name = self.expect_ident()?;
3665 self.expect(Token::On)?;
3666 let table = self.expect_ident()?;
3667 Ok(SqlCommand::DropPolicy(DropPolicyQuery {
3668 name,
3669 table,
3670 if_exists,
3671 }))
3672 } else if self.check(&Token::Server) {
3673 self.advance()?;
3675 let if_exists = self.match_if_exists()?;
3676 let name = self.expect_ident()?;
3677 let cascade = self.consume(&Token::Cascade)?;
3678 Ok(SqlCommand::DropServer(DropServerQuery {
3679 name,
3680 if_exists,
3681 cascade,
3682 }))
3683 } else if self.check(&Token::Foreign) {
3684 self.advance()?;
3686 self.expect(Token::Table)?;
3687 let if_exists = self.match_if_exists()?;
3688 let name = self.expect_ident()?;
3689 Ok(SqlCommand::DropForeignTable(DropForeignTableQuery {
3690 name,
3691 if_exists,
3692 }))
3693 } else if self.check(&Token::Sequence) {
3694 self.advance()?;
3696 let if_exists = self.match_if_exists()?;
3697 let name = self.expect_ident()?;
3698 Ok(SqlCommand::DropSequence(DropSequenceQuery {
3699 name,
3700 if_exists,
3701 }))
3702 } else if let Some(err) =
3703 ParseError::unsupported_recognized_token(self.peek(), self.position())
3704 {
3705 Err(err)
3706 } else {
3707 Err(ParseError::expected(
3708 vec![
3709 "TABLE",
3710 "INDEX",
3711 "TIMESERIES",
3712 "QUEUE",
3713 "TREE",
3714 "HLL",
3715 "SKETCH",
3716 "FILTER",
3717 "SCHEMA",
3718 "SEQUENCE",
3719 "BRANCH",
3720 "TAG",
3721 ],
3722 self.peek(),
3723 pos,
3724 ))
3725 }
3726 }
3727 Token::Alter => {
3728 let next = self.peek_next()?.clone();
3733 if matches!(next, Token::Ident(ref s) if s.eq_ignore_ascii_case("USER")) {
3734 self.advance()?; let stmt = self.parse_alter_user_statement()?;
3736 Ok(SqlCommand::AlterUser(stmt))
3737 } else if matches!(next, Token::Queue) {
3738 self.advance()?; self.advance()?; match self.parse_alter_queue_body()? {
3741 QueryExpr::AlterQueue(query) => Ok(SqlCommand::AlterQueue(query)),
3742 other => Err(ParseError::new(
3743 format!("internal: ALTER QUEUE produced unexpected kind {other:?}"),
3744 self.position(),
3745 )),
3746 }
3747 } else if matches!(next, Token::Metric) {
3748 self.advance()?; self.advance()?; match self.parse_alter_metric_body()? {
3751 QueryExpr::AlterMetric(query) => Ok(SqlCommand::AlterMetric(query)),
3752 other => Err(ParseError::new(
3753 format!("internal: ALTER METRIC produced unexpected kind {other:?}"),
3754 self.position(),
3755 )),
3756 }
3757 } else if matches!(next, Token::Graph) {
3758 match self.parse_alter_graph_query()? {
3762 QueryExpr::AlterTable(query) => Ok(SqlCommand::AlterTable(query)),
3763 other => Err(ParseError::new(
3764 format!(
3765 "internal: ALTER GRAPH produced unexpected query kind {other:?}"
3766 ),
3767 self.position(),
3768 )),
3769 }
3770 } else if matches!(next, Token::Table)
3771 || matches!(next, Token::Collection)
3772 || matches!(next, Token::Ident(ref s) if s.eq_ignore_ascii_case("COLLECTION"))
3773 {
3774 match self.parse_alter_table_query()? {
3780 QueryExpr::AlterTable(query) => Ok(SqlCommand::AlterTable(query)),
3781 other => Err(ParseError::new(
3782 format!(
3783 "internal: ALTER TABLE produced unexpected query kind {other:?}"
3784 ),
3785 self.position(),
3786 )),
3787 }
3788 } else if let Some(err) =
3789 ParseError::unsupported_recognized_token(&next, self.position())
3790 {
3791 Err(err)
3792 } else {
3793 match self.parse_alter_table_query()? {
3794 QueryExpr::AlterTable(query) => Ok(SqlCommand::AlterTable(query)),
3795 other => Err(ParseError::new(
3796 format!("internal: ALTER produced unexpected query kind {other:?}"),
3797 self.position(),
3798 )),
3799 }
3800 }
3801 }
3802 Token::Ident(name) if name.eq_ignore_ascii_case("GRANT") => {
3803 let stmt = self.parse_grant_statement()?;
3804 Ok(SqlCommand::Grant(stmt))
3805 }
3806 Token::Ident(name) if name.eq_ignore_ascii_case("REVOKE") => {
3807 let stmt = self.parse_revoke_statement()?;
3808 Ok(SqlCommand::Revoke(stmt))
3809 }
3810 Token::Ident(name) if name.eq_ignore_ascii_case("EVENTS") => {
3811 self.advance()?;
3812 if self.consume_ident_ci("BACKFILL")? {
3813 return Err(ParseError::new(
3814 "EVENTS BACKFILL STATUS is not implemented; EVENTS BACKFILL runtime is available but durable progress tracking is not"
3815 .to_string(),
3816 self.position(),
3817 ));
3818 }
3819 if !self.consume_ident_ci("STATUS")? {
3820 return Err(ParseError::expected(
3821 vec!["STATUS"],
3822 self.peek(),
3823 self.position(),
3824 ));
3825 }
3826
3827 let mut query = TableQuery::new("red.subscriptions");
3828 let collection = match self.peek().clone() {
3829 Token::Ident(name) => {
3830 self.advance()?;
3831 Some(name)
3832 }
3833 Token::String(name) => {
3834 self.advance()?;
3835 Some(name)
3836 }
3837 _ => None,
3838 };
3839 self.parse_table_clauses(&mut query)?;
3840 if let Some(collection) = collection {
3841 let filter = Filter::compare(
3842 FieldRef::column("red.subscriptions", "collection"),
3843 CompareOp::Eq,
3844 Value::text(collection),
3845 );
3846 let expr = filter_to_expr(&filter);
3847 query.where_expr = Some(match query.where_expr.take() {
3848 Some(existing) => Expr::binop(BinOp::And, existing, expr),
3849 None => expr,
3850 });
3851 query.filter = Some(match query.filter.take() {
3852 Some(existing) => existing.and(filter),
3853 None => filter,
3854 });
3855 }
3856 Ok(SqlCommand::Select(query))
3857 }
3858 Token::Attach => {
3859 let expr = self.parse_attach_policy()?;
3860 Ok(SqlCommand::IamPolicy(expr))
3861 }
3862 Token::Detach => {
3863 let expr = self.parse_detach_policy()?;
3864 Ok(SqlCommand::IamPolicy(expr))
3865 }
3866 Token::Ident(name) if name.eq_ignore_ascii_case("SIMULATE") => {
3867 let expr = self.parse_simulate_policy()?;
3868 Ok(SqlCommand::IamPolicy(expr))
3869 }
3870 Token::Ident(name) if name.eq_ignore_ascii_case("LINT") => {
3871 let expr = self.parse_lint_policy()?;
3872 Ok(SqlCommand::IamPolicy(expr))
3873 }
3874 Token::Ident(name) if name.eq_ignore_ascii_case("MIGRATE") => {
3875 let next = self.peek_next()?.clone();
3879 let is_policy_mode = matches!(&next, Token::Policy)
3880 || matches!(&next, Token::Ident(name)
3881 if name.eq_ignore_ascii_case("POLICY"));
3882 if is_policy_mode {
3883 let expr = self.parse_migrate_policy_mode()?;
3884 return Ok(SqlCommand::IamPolicy(expr));
3885 }
3886 Err(ParseError::expected(
3887 vec!["POLICY"],
3888 self.peek(),
3889 self.position(),
3890 ))
3891 }
3892 Token::Ident(name) if is_vcs_command_head(name) => {
3893 self.parse_vcs_command().map(SqlCommand::Vcs)
3894 }
3895 Token::Ident(name) if name.eq_ignore_ascii_case("RESOLVE") => {
3899 self.parse_vcs_command().map(SqlCommand::Vcs)
3900 }
3901 Token::Set => {
3902 self.advance()?;
3903 if self.consume_ident_ci("CONFIG")? {
3904 let full_key = self.parse_dotted_admin_path(true)?;
3905 self.expect(Token::Eq)?;
3906 let value = self.parse_literal_value()?;
3907 Ok(SqlCommand::SetConfig {
3908 key: full_key,
3909 value,
3910 })
3911 } else if self.consume_ident_ci("SECRET")? {
3912 let key =
3913 Self::normalize_secret_admin_path(self.parse_dotted_admin_path(true)?);
3914 self.expect(Token::Eq)?;
3915 let value = self.parse_literal_value()?;
3916 Ok(SqlCommand::SetSecret { key, value })
3917 } else if self.consume_ident_ci("TENANT")? {
3918 let _ = self.consume(&Token::Eq)?;
3921 if self.consume_ident_ci("NULL")? {
3922 Ok(SqlCommand::SetTenant(None))
3923 } else {
3924 let value = self.parse_literal_value()?;
3925 match value {
3926 Value::Text(s) => Ok(SqlCommand::SetTenant(Some(s.to_string()))),
3927 Value::Null => Ok(SqlCommand::SetTenant(None)),
3928 other => Err(ParseError::new(
3929 format!("SET TENANT expects a text literal or NULL, got {other:?}"),
3930 self.position(),
3931 )),
3932 }
3933 }
3934 } else {
3935 Err(ParseError::expected(
3936 vec!["CONFIG", "SECRET", "TENANT"],
3937 self.peek(),
3938 self.position(),
3939 ))
3940 }
3941 }
3942 Token::Ident(name) if name.eq_ignore_ascii_case("APPLY") => {
3943 self.advance()?;
3944 match self.parse_apply_migration()? {
3945 QueryExpr::ApplyMigration(q) => Ok(SqlCommand::ApplyMigration(q)),
3946 other => Err(ParseError::new(
3947 format!("internal: APPLY MIGRATION produced unexpected kind {other:?}"),
3948 self.position(),
3949 )),
3950 }
3951 }
3952 Token::Ident(name) if name.eq_ignore_ascii_case("RESET") => {
3953 if matches!(self.peek_next()?, Token::Ident(next) if next.eq_ignore_ascii_case("TENANT"))
3957 {
3958 self.advance()?;
3959 self.advance()?;
3960 Ok(SqlCommand::SetTenant(None))
3961 } else {
3962 self.parse_vcs_command().map(SqlCommand::Vcs)
3963 }
3964 }
3965 Token::Ident(name)
3966 if name.eq_ignore_ascii_case("DESCRIBE") || name.eq_ignore_ascii_case("DESC") =>
3967 {
3968 self.advance()?;
3969 let collection = self.parse_dotted_admin_path(false)?;
3970 let mut query = TableQuery::new("red.describe");
3971 query.filter = Some(Filter::compare(
3972 FieldRef::column("", "collection"),
3973 CompareOp::Eq,
3974 Value::text(collection),
3975 ));
3976 Ok(SqlCommand::Select(query))
3977 }
3978 Token::Desc => {
3979 self.advance()?;
3980 let collection = self.parse_dotted_admin_path(false)?;
3981 let mut query = TableQuery::new("red.describe");
3982 query.filter = Some(Filter::compare(
3983 FieldRef::column("", "collection"),
3984 CompareOp::Eq,
3985 Value::text(collection),
3986 ));
3987 Ok(SqlCommand::Select(query))
3988 }
3989 Token::Ident(name) if name.eq_ignore_ascii_case("SHOW") => {
3990 self.advance()?;
3991 if self.consume(&Token::Create)? || self.consume_ident_ci("CREATE")? {
3992 if !(self.consume(&Token::Table)? || self.consume_ident_ci("TABLE")?) {
3993 return Err(ParseError::expected(
3994 vec!["TABLE"],
3995 self.peek(),
3996 self.position(),
3997 ));
3998 }
3999 let collection = self.parse_dotted_admin_path(false)?;
4000 let mut query = TableQuery::new("red.show_create");
4001 query.filter = Some(Filter::compare(
4002 FieldRef::column("", "collection"),
4003 CompareOp::Eq,
4004 Value::text(collection),
4005 ));
4006 Ok(SqlCommand::Select(query))
4007 } else if self.consume_ident_ci("CONFIG")? {
4008 let prefix = if !(self.check(&Token::Eof)
4012 || self.check(&Token::As)
4013 || self.check(&Token::Format))
4014 {
4015 let first = self.expect_ident()?;
4016 let mut full = first;
4017 while self.consume(&Token::Dot)? {
4018 let next = self.expect_ident_or_keyword()?;
4019 full = format!("{full}.{next}");
4020 }
4021 Some(full.to_ascii_lowercase())
4024 } else {
4025 None
4026 };
4027 let as_json = if self.consume(&Token::As)? || self.consume(&Token::Format)? {
4028 if !self.consume(&Token::Json)? {
4029 return Err(ParseError::expected(
4030 vec!["JSON"],
4031 self.peek(),
4032 self.position(),
4033 ));
4034 }
4035 true
4036 } else {
4037 false
4038 };
4039 Ok(SqlCommand::ShowConfig { prefix, as_json })
4040 } else if self.consume_ident_ci("COLLECTIONS")? {
4041 let mut query = TableQuery::new("red.collections");
4042 let include_internal = if self.consume_ident_ci("INCLUDING")? {
4043 if !self.consume_ident_ci("INTERNAL")? {
4044 return Err(ParseError::expected(
4045 vec!["INTERNAL"],
4046 self.peek(),
4047 self.position(),
4048 ));
4049 }
4050 true
4051 } else {
4052 false
4053 };
4054 self.parse_table_clauses(&mut query)?;
4055 if !include_internal {
4056 let user_filter = query.filter.take();
4057 let hide_internal = crate::ast::Filter::Compare {
4058 field: FieldRef::column("", "internal"),
4059 op: CompareOp::Eq,
4060 value: Value::Boolean(false),
4061 };
4062 query.filter = Some(match user_filter {
4063 Some(filter) => filter.and(hide_internal),
4064 None => hide_internal,
4065 });
4066 }
4067 Ok(SqlCommand::Select(query))
4068 } else if self.consume_ident_ci("TABLES")? {
4069 Ok(SqlCommand::Select(parse_show_collections_by_model(
4070 self, "table",
4071 )?))
4072 } else if self.consume_ident_ci("QUEUES")? {
4073 let mut query = TableQuery::new("red.queues");
4081 let include_internal = if self.consume_ident_ci("INCLUDING")? {
4082 if !self.consume_ident_ci("INTERNAL")? {
4083 return Err(ParseError::expected(
4084 vec!["INTERNAL"],
4085 self.peek(),
4086 self.position(),
4087 ));
4088 }
4089 true
4090 } else {
4091 false
4092 };
4093 self.parse_table_clauses(&mut query)?;
4094 if !include_internal {
4095 let hide_internal = Filter::Compare {
4096 field: FieldRef::column("", "internal"),
4097 op: CompareOp::Eq,
4098 value: Value::Boolean(false),
4099 };
4100 add_table_filter(&mut query, hide_internal);
4101 }
4102 Ok(SqlCommand::Select(query))
4103 } else if self.consume_ident_ci("BRANCHES")? {
4104 self.parse_show_vcs_ref_table("red.branches")
4105 } else if self.consume_ident_ci("TAGS")? {
4106 self.parse_show_vcs_ref_table("red.tags")
4107 } else if self.consume(&Token::Vectors)? || self.consume_ident_ci("VECTORS")? {
4108 Ok(SqlCommand::Select(parse_show_collections_by_model(
4109 self, "vector",
4110 )?))
4111 } else if self.consume_ident_ci("DOCUMENTS")? {
4112 Ok(SqlCommand::Select(parse_show_collections_by_model(
4113 self, "document",
4114 )?))
4115 } else if self.consume(&Token::Timeseries)?
4116 || self.consume_ident_ci("TIMESERIES")?
4117 {
4118 Ok(SqlCommand::Select(parse_show_collections_by_model(
4119 self,
4120 "timeseries",
4121 )?))
4122 } else if self.consume_ident_ci("GRAPHS")? {
4123 Ok(SqlCommand::Select(parse_show_collections_by_model(
4124 self, "graph",
4125 )?))
4126 } else if self.consume_ident_ci("CONFIGS")? {
4127 Ok(SqlCommand::Select(parse_show_collections_by_model(
4128 self, "config",
4129 )?))
4130 } else if self.consume_ident_ci("VAULTS")? {
4131 Ok(SqlCommand::Select(parse_show_collections_by_model(
4132 self, "vault",
4133 )?))
4134 } else if self.consume(&Token::Kv)?
4135 || self.consume_ident_ci("KV")?
4136 || self.consume_ident_ci("KVS")?
4137 {
4138 Ok(SqlCommand::Select(parse_show_collections_by_model(
4139 self, "kv",
4140 )?))
4141 } else if self.consume(&Token::Schema)? || self.consume_ident_ci("SCHEMA")? {
4142 let collection = self.parse_dotted_admin_path(false)?;
4143 let mut query = TableQuery::new("red.columns");
4144 query.filter = Some(Filter::compare(
4145 FieldRef::column("", "collection"),
4146 CompareOp::Eq,
4147 Value::text(collection),
4148 ));
4149 Ok(SqlCommand::Select(query))
4150 } else if self.consume_ident_ci("INDICES")? || self.consume_ident_ci("INDEXES")? {
4151 let mut query = TableQuery::new("red.show_indexes");
4152 if self.consume(&Token::On)? {
4153 let collection = self.expect_ident_or_keyword()?;
4154 let filter = Filter::Compare {
4155 field: FieldRef::column("", "table"),
4156 op: CompareOp::Eq,
4157 value: Value::text(collection),
4158 };
4159 query.where_expr = Some(filter_to_expr(&filter));
4160 query.filter = Some(filter);
4161 }
4162 self.parse_table_clauses(&mut query)?;
4163 Ok(SqlCommand::Select(query))
4164 } else if self.consume_ident_ci("POLICIES")? {
4165 if self.consume(&Token::For)? || self.consume_ident_ci("FOR")? {
4166 let principal = self.parse_iam_principal_kind()?;
4167 return Ok(SqlCommand::IamPolicy(QueryExpr::ShowPolicies {
4168 filter: Some(principal),
4169 }));
4170 }
4171 let mut query = TableQuery::new("red.policies");
4172 let collection_filter =
4173 if self.consume(&Token::On)? || self.consume_ident_ci("ON")? {
4174 let collection = self.parse_dotted_admin_path(false)?;
4175 Some(Filter::Compare {
4176 field: FieldRef::TableColumn {
4177 table: String::new(),
4178 column: "collection".to_string(),
4179 },
4180 op: CompareOp::Eq,
4181 value: Value::text(collection),
4182 })
4183 } else {
4184 None
4185 };
4186 self.parse_table_clauses(&mut query)?;
4187 if let Some(collection_filter) = collection_filter {
4188 let combined = match query.filter.take() {
4189 Some(existing) => {
4190 Filter::And(Box::new(collection_filter), Box::new(existing))
4191 }
4192 None => collection_filter,
4193 };
4194 query.where_expr = Some(filter_to_expr(&combined));
4195 query.filter = Some(combined);
4196 }
4197 Ok(SqlCommand::Select(query))
4198 } else if self.consume_ident_ci("STATS")? {
4199 let mut query = TableQuery::new("red.stats");
4200 let collection = match self.peek().clone() {
4201 Token::Ident(name) => {
4202 self.advance()?;
4203 Some(name)
4204 }
4205 Token::String(name) => {
4206 self.advance()?;
4207 Some(name)
4208 }
4209 _ => None,
4210 };
4211 self.parse_table_clauses(&mut query)?;
4212 if let Some(collection) = collection {
4213 let filter = Filter::compare(
4214 FieldRef::column("red.stats", "collection"),
4215 CompareOp::Eq,
4216 Value::text(collection),
4217 );
4218 let expr = filter_to_expr(&filter);
4219 query.where_expr = Some(match query.where_expr.take() {
4220 Some(existing) => Expr::binop(BinOp::And, existing, expr),
4221 None => expr,
4222 });
4223 query.filter = Some(match query.filter.take() {
4224 Some(existing) => existing.and(filter),
4225 None => filter,
4226 });
4227 }
4228 Ok(SqlCommand::Select(query))
4229 } else if self.consume_ident_ci("SAMPLE")? {
4230 let mut query = TableQuery::new(&self.expect_ident()?);
4231 query.limit = if self.consume(&Token::Limit)? {
4232 Some(self.parse_integer()? as u64)
4233 } else {
4234 Some(10)
4235 };
4236 Ok(SqlCommand::Select(query))
4237 } else if self.consume_ident_ci("SECRET")? || self.consume_ident_ci("SECRETS")? {
4238 let prefix = if !self.check(&Token::Eof) {
4239 Some(Self::normalize_secret_admin_path(
4240 self.parse_dotted_admin_path(true)?,
4241 ))
4242 } else {
4243 None
4244 };
4245 Ok(SqlCommand::ShowSecrets { prefix })
4246 } else if self.consume_ident_ci("TENANT")? {
4247 Ok(SqlCommand::ShowTenant)
4248 } else if let Some(expr) = self.parse_show_iam_after_show()? {
4249 Ok(SqlCommand::IamPolicy(expr))
4250 } else {
4251 Err(ParseError::expected(
4252 vec![
4253 "CONFIG",
4254 "SECRET",
4255 "SECRETS",
4256 "COLLECTIONS",
4257 "TABLES",
4258 "QUEUES",
4259 "VECTORS",
4260 "DOCUMENTS",
4261 "TIMESERIES",
4262 "GRAPHS",
4263 "KV",
4264 "SCHEMA",
4265 "INDICES",
4266 "INDEXES",
4267 "SAMPLE",
4268 "POLICIES",
4269 "STATS",
4270 "TENANT",
4271 "EFFECTIVE",
4272 ],
4273 self.peek(),
4274 self.position(),
4275 ))
4276 }
4277 }
4278 Token::Begin | Token::Start => {
4290 self.advance()?;
4291 let _ = self.consume(&Token::Work)? || self.consume(&Token::Transaction)?;
4292 if self.consume_ident_ci("ISOLATION")? {
4294 self.expect(Token::Level)?;
4295 let mut parts: Vec<String> = Vec::new();
4299 if self.consume_ident_ci("READ")? {
4300 parts.push("READ".to_string());
4301 if self.consume_ident_ci("UNCOMMITTED")? {
4302 parts.push("UNCOMMITTED".to_string());
4303 } else if self.consume_ident_ci("COMMITTED")? {
4304 parts.push("COMMITTED".to_string());
4305 } else {
4306 return Err(ParseError::expected(
4307 vec!["UNCOMMITTED", "COMMITTED"],
4308 self.peek(),
4309 self.position(),
4310 ));
4311 }
4312 } else if self.consume_ident_ci("REPEATABLE")? {
4313 parts.push("REPEATABLE".to_string());
4314 if !self.consume_ident_ci("READ")? {
4315 return Err(ParseError::expected(
4316 vec!["READ"],
4317 self.peek(),
4318 self.position(),
4319 ));
4320 }
4321 parts.push("READ".to_string());
4322 } else if self.consume_ident_ci("SNAPSHOT")? {
4323 parts.push("SNAPSHOT".to_string());
4324 } else if self.consume_ident_ci("SERIALIZABLE")? {
4325 return Err(ParseError::new(
4326 "ISOLATION LEVEL SERIALIZABLE is not yet supported — reddb \
4327 currently provides SNAPSHOT ISOLATION (which PG calls \
4328 REPEATABLE READ). Use REPEATABLE READ / SNAPSHOT / \
4329 READ COMMITTED, or omit ISOLATION LEVEL for the default."
4330 .to_string(),
4331 self.position(),
4332 ));
4333 } else {
4334 return Err(ParseError::expected(
4335 vec!["READ", "REPEATABLE", "SNAPSHOT", "SERIALIZABLE"],
4336 self.peek(),
4337 self.position(),
4338 ));
4339 }
4340 let _ = parts;
4342 }
4343 Ok(SqlCommand::TransactionControl(TxnControl::Begin))
4344 }
4345 Token::Commit => {
4347 self.advance()?;
4348 let _ = self.consume(&Token::Work)? || self.consume(&Token::Transaction)?;
4349 Ok(SqlCommand::TransactionControl(TxnControl::Commit))
4350 }
4351 Token::Rollback => {
4354 self.advance()?;
4355 if matches!(self.peek(), Token::Ident(n) if n.eq_ignore_ascii_case("MIGRATION")) {
4356 match self.parse_rollback_migration_after_keyword()? {
4357 QueryExpr::RollbackMigration(q) => Ok(SqlCommand::RollbackMigration(q)),
4358 other => Err(ParseError::new(
4359 format!(
4360 "internal: ROLLBACK MIGRATION produced unexpected kind {other:?}"
4361 ),
4362 self.position(),
4363 )),
4364 }
4365 } else {
4366 let _ = self.consume(&Token::Work)? || self.consume(&Token::Transaction)?;
4367 if self.consume(&Token::To)? {
4368 let _ = self.consume(&Token::Savepoint)?;
4369 let name = self.expect_ident()?;
4370 Ok(SqlCommand::TransactionControl(
4371 TxnControl::RollbackToSavepoint(name),
4372 ))
4373 } else {
4374 Ok(SqlCommand::TransactionControl(TxnControl::Rollback))
4375 }
4376 }
4377 }
4378 Token::Savepoint => {
4380 self.advance()?;
4381 let name = self.expect_ident()?;
4382 Ok(SqlCommand::TransactionControl(TxnControl::Savepoint(name)))
4383 }
4384 Token::Release => {
4386 self.advance()?;
4387 let _ = self.consume(&Token::Savepoint)?;
4388 let name = self.expect_ident()?;
4389 Ok(SqlCommand::TransactionControl(
4390 TxnControl::ReleaseSavepoint(name),
4391 ))
4392 }
4393 Token::Vacuum => {
4395 self.advance()?;
4396 let full = self.consume(&Token::Full)?;
4397 let target = if self.check(&Token::Eof) {
4398 None
4399 } else {
4400 Some(self.expect_ident()?)
4401 };
4402 Ok(SqlCommand::Maintenance(MaintenanceCommand::Vacuum {
4403 target,
4404 full,
4405 }))
4406 }
4407 Token::Refresh => {
4409 self.advance()?;
4410 self.expect(Token::Materialized)?;
4411 self.expect(Token::View)?;
4412 let name = self.expect_ident()?;
4413 Ok(SqlCommand::RefreshMaterializedView(
4414 RefreshMaterializedViewQuery { name },
4415 ))
4416 }
4417 Token::Analyze => {
4419 self.advance()?;
4420 let target = if self.check(&Token::Eof) {
4421 None
4422 } else {
4423 Some(self.expect_ident()?)
4424 };
4425 Ok(SqlCommand::Maintenance(MaintenanceCommand::Analyze {
4426 target,
4427 }))
4428 }
4429 Token::Copy => {
4435 self.advance()?;
4436 let table = self.expect_ident()?;
4437 self.expect(Token::From)?;
4438 let path = self.parse_string()?;
4439
4440 let mut delimiter: Option<char> = None;
4441 let mut has_header = false;
4442 let format = CopyFormat::Csv;
4443
4444 if self.consume(&Token::With)? || self.consume_ident_ci("WITH")? {
4448 self.expect(Token::LParen)?;
4449 loop {
4450 if self.consume(&Token::Format)? || self.consume_ident_ci("FORMAT")? {
4451 let _ = self.consume(&Token::Eq)?;
4452 let _ = self.expect_ident()?;
4454 } else if self.consume(&Token::Header)? {
4455 let _ = self.consume(&Token::Eq)?;
4456 has_header = match self.peek().clone() {
4459 Token::True => {
4460 self.advance()?;
4461 true
4462 }
4463 Token::False => {
4464 self.advance()?;
4465 false
4466 }
4467 Token::Ident(ref n) if n.eq_ignore_ascii_case("true") => {
4468 self.advance()?;
4469 true
4470 }
4471 Token::Ident(ref n) if n.eq_ignore_ascii_case("false") => {
4472 self.advance()?;
4473 false
4474 }
4475 _ => true,
4476 };
4477 } else if self.consume(&Token::Delimiter)? {
4478 let _ = self.consume(&Token::Eq)?;
4479 let s = self.parse_string()?;
4480 delimiter = s.chars().next();
4481 } else {
4482 break;
4483 }
4484 if !self.consume(&Token::Comma)? {
4485 break;
4486 }
4487 }
4488 self.expect(Token::RParen)?;
4489 }
4490
4491 loop {
4493 if self.consume(&Token::Delimiter)? {
4494 let s = self.parse_string()?;
4495 delimiter = s.chars().next();
4496 } else if self.consume(&Token::Header)? {
4497 has_header = true;
4498 } else {
4499 break;
4500 }
4501 }
4502
4503 Ok(SqlCommand::CopyFrom(CopyFromQuery {
4504 table,
4505 path,
4506 format,
4507 delimiter,
4508 has_header,
4509 }))
4510 }
4511 other => Err(ParseError::expected(
4512 vec![
4513 "SELECT",
4514 "FROM",
4515 "INSERT",
4516 "UPDATE",
4517 "DELETE",
4518 "EXPLAIN",
4519 "CREATE",
4520 "DROP",
4521 "ALTER",
4522 "SET",
4523 "SHOW",
4524 "BEGIN",
4525 "COMMIT",
4526 "ROLLBACK",
4527 "SAVEPOINT",
4528 "RELEASE",
4529 "START",
4530 "VACUUM",
4531 "ANALYZE",
4532 "COPY",
4533 "REFRESH",
4534 "DESCRIBE",
4535 "DESC",
4536 ],
4537 other,
4538 self.position(),
4539 )),
4540 }
4541 }
4542}