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