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