1use super::*;
2use crate::auth::column_policy_gate::ColumnAccessRequest;
3use crate::auth::UserId;
4use crate::replication::cdc::ChangeRecord;
5use crate::storage::query::ast::TableSource;
6pub(crate) use super::authz::policy_columns::*;
11
12pub(crate) use super::graph_tvf::{abstract_degree_centrality, is_graph_tvf_name};
17pub(crate) use super::rls_injection::{
18 apply_foreign_table_filters, inject_rls_filters, inject_rls_into_join, rls_is_enabled,
19 rls_policy_filter, rls_policy_filter_for_kind,
20};
21pub(crate) use super::vcs_command::{
27 parse_runtime_vcs_command, strip_explain_prefix, walk_collections,
28};
29
30pub(crate) use super::impl_audit_control::{
42 control_event_outcome_for_error, query_audit_plan, query_control_event_specs,
43};
44pub(crate) use super::impl_config_secret::{
46 insert_config_json_path, secret_sql_value_to_string, seed_storage_deploy_config,
47 show_config_json_result, show_secrets_allows_key,
48};
49pub(super) use super::impl_core_outer_scope::{
50 first_column_values, query_references_outer_scope, relation_scopes_for_query,
51};
52pub(crate) use super::impl_core_result_cache_scopes::collect_table_refs;
53pub(super) use super::impl_core_result_cache_scopes::query_expr_result_cache_scopes;
54
55pub(crate) use super::impl_lifecycle::{table_row_index_fields, view_records_to_entities};
63
64pub use super::execution_context::{
65 capture_current_snapshot, clear_current_auth_identity, clear_current_connection_id,
66 clear_current_snapshot, clear_current_tenant, current_auth_identity_for_audit,
67 current_connection_id, current_tenant, entity_visible_under_current_snapshot,
68 entity_visible_with_context, set_current_auth_identity, set_current_connection_id,
69 set_current_snapshot, set_current_tenant, snapshot_bundle, with_snapshot_bundle,
70 SnapshotBundle, SnapshotContext,
71};
72pub(crate) use super::execution_context::{
73 config_read_permitted, current_auth_identity, current_config_value, current_kv_value,
74 current_role_projected, current_scope_override, current_secret_value,
75 current_snapshot_requires_index_fallback, current_user_projected, has_scope_override_active,
76 kv_read_permitted, parse_set_local_tenant, update_current_config_value,
77 update_current_kv_value, update_current_secret_value, xids_visible_under_current_snapshot,
78 ConfigSnapshotGuard, CurrentSnapshotGuard, KvStoreGuard, ScopeOverrideGuard, SecretStoreGuard,
79 TxLocalTenantGuard,
80};
81
82pub(super) fn has_with_prefix(sql: &str) -> bool {
87 let trimmed = sql.trim_start();
88 let head_end = trimmed
89 .find(|c: char| c.is_whitespace() || c == '(')
90 .unwrap_or(trimmed.len());
91 trimmed[..head_end].eq_ignore_ascii_case("WITH")
92}
93
94pub(super) fn peek_top_level_as_of_with_table(
99 sql: &str,
100) -> Option<(crate::application::vcs::AsOfSpec, Option<String>)> {
101 if !sql
102 .as_bytes()
103 .windows(5)
104 .any(|w| w.eq_ignore_ascii_case(b"as of"))
105 {
106 return None;
107 }
108 let parsed = crate::storage::query::parser::parse(sql).ok()?;
109 let crate::storage::query::ast::QueryExpr::Table(table) = parsed.query else {
110 return None;
111 };
112 let clause = table.as_of?;
113 let table_name = if table.table.is_empty() || table.table == "any" {
114 None
115 } else {
116 Some(table.table.clone())
117 };
118 let spec = match clause {
119 crate::storage::query::ast::AsOfClause::Commit(h) => {
120 crate::application::vcs::AsOfSpec::Commit(h)
121 }
122 crate::storage::query::ast::AsOfClause::Branch(b) => {
123 crate::application::vcs::AsOfSpec::Branch(b)
124 }
125 crate::storage::query::ast::AsOfClause::Tag(t) => crate::application::vcs::AsOfSpec::Tag(t),
126 crate::storage::query::ast::AsOfClause::TimestampMs(ts) => {
127 crate::application::vcs::AsOfSpec::TimestampMs(ts)
128 }
129 crate::storage::query::ast::AsOfClause::Snapshot(x) => {
130 crate::application::vcs::AsOfSpec::Snapshot(x)
131 }
132 };
133 Some((spec, table_name))
134}
135
136pub(super) fn query_has_volatile_builtin(sql: &str) -> bool {
137 const VOLATILE_TOKENS: &[&str] = &[
141 "pg_advisory_lock",
142 "pg_try_advisory_lock",
143 "pg_advisory_unlock",
144 "random()",
145 "$config",
150 "$secret",
151 "$kv",
152 ];
157 let lowered = sql.to_ascii_lowercase();
158 VOLATILE_TOKENS.iter().any(|t| lowered.contains(t))
159}
160
161pub(super) fn query_is_ask_statement(sql: &str) -> bool {
162 let trimmed = sql.trim_start();
163 let head_end = trimmed
164 .find(|c: char| c.is_whitespace() || c == '(' || c == ';')
165 .unwrap_or(trimmed.len());
166 trimmed[..head_end].eq_ignore_ascii_case("ASK")
167}
168
169pub(super) fn intent_lock_modes_for(
179 expr: &QueryExpr,
180) -> Option<(
181 crate::runtime::lock_manager::LockMode,
182 crate::runtime::lock_manager::LockMode,
183)> {
184 use crate::runtime::lock_manager::LockMode::{Exclusive, IntentExclusive, IntentShared};
185
186 match expr {
187 QueryExpr::Table(_)
189 | QueryExpr::Join(_)
190 | QueryExpr::Vector(_)
191 | QueryExpr::Hybrid(_)
192 | QueryExpr::Graph(_)
193 | QueryExpr::Path(_)
194 | QueryExpr::Ask(_)
195 | QueryExpr::SearchCommand(_)
196 | QueryExpr::GraphCommand(_)
197 | QueryExpr::RankOf(_)
198 | QueryExpr::ApproxRankOf(_)
199 | QueryExpr::RankRange(_)
200 | QueryExpr::QueueSelect(_) => Some((IntentShared, IntentShared)),
201
202 QueryExpr::Insert(_)
210 | QueryExpr::Update(_)
211 | QueryExpr::Delete(_)
212 | QueryExpr::QueueCommand(QueueCommand::Move { .. }) => {
213 Some((IntentExclusive, IntentExclusive))
214 }
215 QueryExpr::QueueCommand(_) => Some((IntentShared, IntentShared)),
216
217 QueryExpr::CreateTable(_)
221 | QueryExpr::CreateCollection(_)
222 | QueryExpr::CreateVector(_)
223 | QueryExpr::DropTable(_)
224 | QueryExpr::DropGraph(_)
225 | QueryExpr::DropVector(_)
226 | QueryExpr::DropDocument(_)
227 | QueryExpr::DropKv(_)
228 | QueryExpr::DropCollection(_)
229 | QueryExpr::Truncate(_)
230 | QueryExpr::AlterTable(_)
231 | QueryExpr::CreateIndex(_)
232 | QueryExpr::DropIndex(_)
233 | QueryExpr::CreateTimeSeries(_)
234 | QueryExpr::CreateMetric(_)
235 | QueryExpr::AlterMetric(_)
236 | QueryExpr::CreateSlo(_)
237 | QueryExpr::DropTimeSeries(_)
238 | QueryExpr::CreateQueue(_)
239 | QueryExpr::AlterQueue(_)
240 | QueryExpr::DropQueue(_)
241 | QueryExpr::CreateTree(_)
242 | QueryExpr::DropTree(_)
243 | QueryExpr::CreatePolicy(_)
244 | QueryExpr::DropPolicy(_)
245 | QueryExpr::CreateView(_)
246 | QueryExpr::DropView(_)
247 | QueryExpr::RefreshMaterializedView(_)
248 | QueryExpr::CreateSchema(_)
249 | QueryExpr::DropSchema(_)
250 | QueryExpr::CreateSequence(_)
251 | QueryExpr::DropSequence(_)
252 | QueryExpr::CreateServer(_)
253 | QueryExpr::DropServer(_)
254 | QueryExpr::CreateForeignTable(_)
255 | QueryExpr::DropForeignTable(_) => Some((IntentExclusive, Exclusive)),
256
257 _ => None,
263 }
264}
265
266pub(super) fn collections_referenced(expr: &QueryExpr) -> Vec<String> {
271 let mut out = Vec::new();
272 walk_collections(expr, &mut out);
273 out.sort();
274 out.dedup();
275 out
276}
277
278impl RedDBRuntime {
279 pub fn execute_query_with_scope(
293 &self,
294 query: &str,
295 scope: crate::runtime::within_clause::ScopeOverride,
296 ) -> RedDBResult<RuntimeQueryResult> {
297 if scope.is_empty() {
298 return self.execute_query(query);
299 }
300 let _scope_guard = ScopeOverrideGuard::install(scope);
301 self.execute_query(query)
302 }
303
304 pub fn execute_query(&self, query: &str) -> RedDBResult<RuntimeQueryResult> {
313 let started = std::time::Instant::now();
314 self.inner.node_load_telemetry.query_start();
315 let result = self.execute_query_inner(query);
316 self.finish_query_lifecycle(query, started, result)
317 }
318
319 pub fn execute_query_with_params(
327 &self,
328 query: &str,
329 params: &[Value],
330 ) -> RedDBResult<RuntimeQueryResult> {
331 if params.is_empty() {
332 return self.execute_query(query);
333 }
334 let started = std::time::Instant::now();
335 self.inner.node_load_telemetry.query_start();
336 let result = self.execute_query_with_params_inner(query, params);
337 self.finish_query_lifecycle(query, started, result)
338 }
339
340 fn finish_query_lifecycle(
341 &self,
342 query: &str,
343 started: std::time::Instant,
344 mut result: RedDBResult<RuntimeQueryResult>,
345 ) -> RedDBResult<RuntimeQueryResult> {
346 if let Ok(ref mut query_result) = result {
351 if query_result.statement_type == "select" {
352 self.filter_integrity_tombstoned(&mut query_result.result);
353 }
354 }
355 let elapsed_ms = started.elapsed().as_millis() as u64;
356
357 let scope = self.ai_scope();
362 let kind = match result
363 .as_ref()
364 .map(|r| r.statement_type)
365 .unwrap_or("select")
366 {
367 "select" => crate::telemetry::slow_query_logger::QueryKind::Select,
368 "insert" => crate::telemetry::slow_query_logger::QueryKind::Insert,
369 "update" => crate::telemetry::slow_query_logger::QueryKind::Update,
370 "delete" => crate::telemetry::slow_query_logger::QueryKind::Delete,
371 _ => crate::telemetry::slow_query_logger::QueryKind::Internal,
372 };
373 self.inner
379 .slow_query_logger
380 .record(kind, elapsed_ms, query.to_string(), &scope);
381
382 self.inner
388 .query_latency_telemetry
389 .observe(kind, started.elapsed().as_secs_f64());
390
391 self.inner.node_load_telemetry.query_finish();
395
396 if let Ok(ref mut query_result) = result {
397 if matches!(query_result.statement_type, "insert" | "update" | "delete") {
398 let bookmark = crate::replication::CausalBookmark::new(
399 self.current_replication_term(),
400 self.cdc_current_lsn(),
401 );
402 query_result.bookmark = Some(bookmark.encode());
403 }
404 }
405
406 result
407 }
408
409 fn execute_query_with_params_inner(
410 &self,
411 query: &str,
412 params: &[Value],
413 ) -> RedDBResult<RuntimeQueryResult> {
414 let parsed = parse_multi(query).map_err(|err| RedDBError::Query(err.to_string()))?;
415 let bound = crate::storage::query::user_params::bind(&parsed, params).map_err(|err| {
416 RedDBError::Validation {
417 message: err.to_string(),
418 validation: crate::json!({
419 "code": "INVALID_PARAMS",
420 "surface": "query.params",
421 }),
422 }
423 })?;
424 self.execute_bound_query_expr_in_frame(query, bound)
425 }
426
427 fn execute_bound_query_expr_in_frame(
428 &self,
429 query: &str,
430 expr: QueryExpr,
431 ) -> RedDBResult<RuntimeQueryResult> {
432 let rewritten_query = super::red_schema::rewrite_virtual_names(query);
433 let execution_query = rewritten_query.as_deref().unwrap_or(query);
434 let frame = super::statement_frame::StatementExecutionFrame::build(self, execution_query)?;
435 let _frame_guards = Box::new(frame.install(self));
439 let _log_span = crate::telemetry::span::query_span(query).entered();
440
441 let expr = self.rewrite_view_refs(expr);
442 let mode = detect_mode(execution_query);
443 let control_event_specs = query_control_event_specs(&expr);
444 let _lock_guard = match frame.prepare_dispatch(self, &expr) {
445 Ok(guard) => guard,
446 Err(err) => {
447 let outcome = control_event_outcome_for_error(&err);
448 for spec in &control_event_specs {
449 self.emit_control_event(
450 spec.kind,
451 outcome,
452 spec.action,
453 spec.resource.clone(),
454 Some(err.to_string()),
455 spec.fields.clone(),
456 )?;
457 }
458 return Err(err);
459 }
460 };
461
462 let mut result = self.dispatch_expr(expr, query, mode)?;
463 if result.statement_type == "select" {
464 self.apply_secret_decryption(&mut result);
465 }
466 Ok(result)
467 }
468
469 pub fn causal_session(&self) -> crate::runtime::CausalSession {
470 crate::runtime::CausalSession {
471 runtime: self.clone(),
472 bookmark: None,
473 wait_timeout: std::time::Duration::from_secs(5),
474 }
475 }
476
477 pub fn wait_for_bookmark(
478 &self,
479 bookmark: &crate::replication::CausalBookmark,
480 timeout: std::time::Duration,
481 ) -> RedDBResult<()> {
482 let deadline = std::time::Instant::now() + timeout;
483 loop {
484 let applied_lsn = self.local_contiguous_applied_lsn();
485 if applied_lsn >= bookmark.commit_lsn() {
486 return Ok(());
487 }
488 let now = std::time::Instant::now();
489 if now >= deadline {
490 return Err(RedDBError::InvalidOperation(format!(
491 "timed out waiting for causal bookmark lsn {}; applied={}",
492 bookmark.commit_lsn(),
493 applied_lsn
494 )));
495 }
496 let remaining = deadline.saturating_duration_since(now);
497 std::thread::sleep(remaining.min(std::time::Duration::from_millis(5)));
498 }
499 }
500
501 fn local_contiguous_applied_lsn(&self) -> u64 {
502 match self.inner.db.options().replication.role {
503 crate::replication::ReplicationRole::Replica { .. } => {
504 self.config_u64("red.replication.last_applied_lsn", 0)
505 }
506 _ => self.cdc_current_lsn(),
507 }
508 }
509
510 #[inline(never)]
511 pub(super) fn execute_query_inner(&self, query: &str) -> RedDBResult<RuntimeQueryResult> {
512 if !has_scope_override_active()
523 && !query.trim_start().starts_with("WITHIN")
524 && !query.trim_start().starts_with("within")
525 && !self.inner.query_audit.has_rules()
526 && !self
527 .inner
528 .tx_contexts
529 .read()
530 .contains_key(¤t_connection_id())
531 {
532 if let Some(result) = self.try_fast_entity_lookup(query) {
533 return result;
534 }
535 }
536
537 match crate::runtime::within_clause::try_strip_within_prefix(query) {
544 Ok(Some((scope, inner))) => {
545 let _scope_guard = ScopeOverrideGuard::install(scope);
546 return self.execute_query_inner(inner);
551 }
552 Ok(None) => {}
553 Err(msg) => return Err(RedDBError::Query(msg)),
554 }
555
556 if let Some(inner) = strip_explain_analyze_prefix(query) {
560 return self.explain_analyze_as_rows(query, inner);
561 }
562
563 if let Some(inner) = strip_explain_prefix(query) {
570 return self.explain_as_rows(query, inner);
571 }
572
573 if let Some(value) = parse_set_local_tenant(query)? {
578 let conn_id = current_connection_id();
579 if !self.inner.tx_contexts.read().contains_key(&conn_id) {
580 return Err(RedDBError::Query(
581 "SET LOCAL TENANT requires an active transaction".to_string(),
582 ));
583 }
584 self.inner
585 .tx_local_tenants
586 .write()
587 .insert(conn_id, value.clone());
588 return Ok(RuntimeQueryResult::ok_message(
589 query.to_string(),
590 &match &value {
591 Some(id) => format!("local tenant set: {id}"),
592 None => "local tenant cleared".to_string(),
593 },
594 "set_local_tenant",
595 ));
596 }
597
598 if super::red_schema::is_system_schema_write(query) {
599 return Err(RedDBError::Query(
600 super::red_schema::READ_ONLY_ERROR.to_string(),
601 ));
602 }
603
604 if let Some(command) = parse_runtime_vcs_command(query) {
605 return self.execute_vcs_command(query, detect_mode(query), command?);
606 }
607
608 if let Some(create_source) = super::analytics_source_catalog::parse_create_statement(query)?
609 {
610 return self.execute_create_analytics_source(query, create_source);
611 }
612
613 if let Some(path) = super::metric_descriptor_catalog::parse_read_metric_statement(query) {
619 return Err(super::metric_descriptor_catalog::read_output_unsupported(
620 &path,
621 ));
622 }
623
624 if let Some(parsed) = super::ranking_descriptor_catalog::parse_create_ranking(query) {
629 return self.execute_create_ranking(query, parsed?);
630 }
631 if super::ranking_descriptor_catalog::parse_show_rankings(query) {
632 return self.execute_show_rankings(query);
633 }
634
635 let rewritten_query = super::red_schema::rewrite_virtual_names(query);
636 let execution_query = rewritten_query.as_deref().unwrap_or(query);
637
638 let frame = super::statement_frame::StatementExecutionFrame::build(self, execution_query)?;
639 let _frame_guards = Box::new(frame.install(self));
640
641 let _log_span = crate::telemetry::span::query_span(query).entered();
648
649 if let Some(rewritten) = frame.prepare_cte(execution_query)? {
651 return self.execute_query_expr(rewritten);
652 }
653
654 if !self.inner.query_audit.has_rules() {
656 if let Some(result) = self.try_fast_entity_lookup(execution_query) {
657 return result;
658 }
659 }
660
661 if !self.inner.query_audit.has_rules() {
663 if let Some(result) = frame.read_result_cache(self) {
664 return Ok(result);
665 }
666 }
667
668 let prepared = frame.prepare_statement(self, execution_query)?;
669 let mode = prepared.mode;
670 let expr = prepared.expr;
671
672 let statement = query_expr_name(&expr);
673 let result_cache_scopes = query_expr_result_cache_scopes(&expr);
674 let control_event_specs = query_control_event_specs(&expr);
675 let query_audit_plan = query_audit_plan(&expr);
676
677 let _lock_guard = match frame.prepare_dispatch(self, &expr) {
678 Ok(guard) => guard,
679 Err(err) => {
680 let outcome = control_event_outcome_for_error(&err);
681 for spec in &control_event_specs {
682 self.emit_control_event(
683 spec.kind,
684 outcome,
685 spec.action,
686 spec.resource.clone(),
687 Some(err.to_string()),
688 spec.fields.clone(),
689 )?;
690 }
691 return Err(err);
692 }
693 };
694 let frame_iface: &dyn super::statement_frame::ReadFrame = &frame;
695 let query_audit_started = std::time::Instant::now();
696
697 let query_result = match expr {
698 QueryExpr::Graph(_) | QueryExpr::Path(_) => {
699 let (graph, node_properties, edge_properties) =
707 self.materialize_graph_with_rls()?;
708 let result =
709 crate::storage::query::unified::UnifiedExecutor::execute_on_with_graph_properties(
710 &graph,
711 &expr,
712 node_properties,
713 edge_properties,
714 )
715 .map_err(|err| RedDBError::Query(err.to_string()))?;
716
717 Ok(RuntimeQueryResult {
718 query: query.to_string(),
719 mode,
720 statement,
721 engine: "materialized-graph",
722 result,
723 affected_rows: 0,
724 statement_type: "select",
725 bookmark: None,
726 notice: None,
727 })
728 }
729 QueryExpr::Table(table) => {
730 let table = self.resolve_table_expr_subqueries(
731 table,
732 &frame as &dyn super::statement_frame::ReadFrame,
733 )?;
734 if let Some(TableSource::Function {
738 name,
739 args,
740 named_args,
741 }) = table.source.clone()
742 {
743 let tvf_result = RuntimeQueryResult {
751 query: query.to_string(),
752 mode,
753 statement,
754 engine: "runtime-graph-tvf",
755 result: self.execute_table_function(&name, &args, &named_args)?,
756 affected_rows: 0,
757 statement_type: "select",
758 bookmark: None,
759 notice: None,
760 };
761 frame.write_result_cache(self, &tvf_result, result_cache_scopes.clone());
762 return Ok(tvf_result);
763 }
764 if let Some(TableSource::InlineGraphFunction {
772 name,
773 nodes,
774 edges,
775 named_args,
776 }) = table.source.clone()
777 {
778 let inline_result = RuntimeQueryResult {
779 query: query.to_string(),
780 mode,
781 statement,
782 engine: "runtime-graph-tvf-inline",
783 result: self.execute_inline_graph_function(
784 &name,
785 &nodes,
786 &edges,
787 &named_args,
788 )?,
789 affected_rows: 0,
790 statement_type: "select",
791 bookmark: None,
792 notice: None,
793 };
794 frame.write_result_cache(self, &inline_result, result_cache_scopes);
795 return Ok(inline_result);
796 }
797 if super::red_schema::is_virtual_table(&table.table) {
798 return Ok(RuntimeQueryResult {
799 query: query.to_string(),
800 mode,
801 statement,
802 engine: "runtime-red-schema",
803 result: super::red_schema::red_query(
804 self,
805 &table.table,
806 &table,
807 &frame as &dyn super::statement_frame::ReadFrame,
808 )?,
809 affected_rows: 0,
810 statement_type: "select",
811 bookmark: None,
812 notice: None,
813 });
814 }
815
816 if let Some(view_result) = self.try_resolve_analytics_view(
820 &table,
821 &frame as &dyn super::statement_frame::ReadFrame,
822 )? {
823 return Ok(RuntimeQueryResult {
824 query: query.to_string(),
825 mode,
826 statement,
827 engine: "runtime-graph-analytics-view",
828 result: view_result,
829 affected_rows: 0,
830 statement_type: "select",
831 bookmark: None,
832 notice: None,
833 });
834 }
835
836 if let Some(result) = self.execute_probabilistic_select(&table)? {
837 return Ok(RuntimeQueryResult {
838 query: query.to_string(),
839 mode,
840 statement,
841 engine: "runtime-probabilistic",
842 result,
843 affected_rows: 0,
844 statement_type: "select",
845 bookmark: None,
846 notice: None,
847 });
848 }
849
850 if self.inner.foreign_tables.is_foreign_table(&table.table) {
858 let records = self
859 .inner
860 .foreign_tables
861 .scan(&table.table)
862 .map_err(|e| RedDBError::Internal(e.to_string()))?;
863 let result = apply_foreign_table_filters(records, &table);
864 return Ok(RuntimeQueryResult {
865 query: query.to_string(),
866 mode,
867 statement,
868 engine: "runtime-fdw",
869 result,
870 affected_rows: 0,
871 statement_type: "select",
872 bookmark: None,
873 notice: None,
874 });
875 }
876
877 let Some(table_with_rls) = self.authorize_relational_table_select(
894 table,
895 &frame as &dyn super::statement_frame::ReadFrame,
896 )?
897 else {
898 let empty = crate::storage::query::unified::UnifiedResult::empty();
899 return Ok(RuntimeQueryResult {
900 query: query.to_string(),
901 mode,
902 statement,
903 engine: "runtime-table-rls",
904 result: empty,
905 affected_rows: 0,
906 statement_type: "select",
907 bookmark: None,
908 notice: None,
909 });
910 };
911 Ok(RuntimeQueryResult {
912 query: query.to_string(),
913 mode,
914 statement,
915 engine: "runtime-table",
916 result: execute_runtime_table_query_in(
923 &self.inner.db,
924 &table_with_rls,
925 Some(&self.inner.index_store),
926 Some(frame.row_arena()),
927 )?,
928 affected_rows: 0,
929 statement_type: "select",
930 bookmark: None,
931 notice: None,
932 })
933 }
934 QueryExpr::Join(join) => {
935 let join_with_rls = match self.authorize_relational_join_select(
944 join,
945 &frame as &dyn super::statement_frame::ReadFrame,
946 )? {
947 Some(j) => j,
948 None => {
949 return Ok(RuntimeQueryResult {
950 query: query.to_string(),
951 mode,
952 statement,
953 engine: "runtime-join-rls",
954 result: crate::storage::query::unified::UnifiedResult::empty(),
955 affected_rows: 0,
956 statement_type: "select",
957 bookmark: None,
958 notice: None,
959 });
960 }
961 };
962 Ok(RuntimeQueryResult {
963 query: query.to_string(),
964 mode,
965 statement,
966 engine: "runtime-join",
967 result: execute_runtime_join_query(&self.inner.db, &join_with_rls)?,
968 affected_rows: 0,
969 statement_type: "select",
970 bookmark: None,
971 notice: None,
972 })
973 }
974 QueryExpr::Vector(vector) => Ok(RuntimeQueryResult {
975 query: query.to_string(),
976 mode,
977 statement,
978 engine: "runtime-vector",
979 result: execute_runtime_vector_query(&self.inner.db, &vector)?,
980 affected_rows: 0,
981 statement_type: "select",
982 bookmark: None,
983 notice: None,
984 }),
985 QueryExpr::Hybrid(hybrid) => Ok(RuntimeQueryResult {
986 query: query.to_string(),
987 mode,
988 statement,
989 engine: "runtime-hybrid",
990 result: execute_runtime_hybrid_query(&self.inner.db, &hybrid)?,
991 affected_rows: 0,
992 statement_type: "select",
993 bookmark: None,
994 notice: None,
995 }),
996 QueryExpr::RankOf(ref rank) => self.execute_rank_of(query, rank),
997 QueryExpr::ApproxRankOf(ref rank) => self.execute_approx_rank_of(query, rank),
998 QueryExpr::RankRange(ref range) => self.execute_rank_range(query, range),
999 QueryExpr::Insert(ref insert) if super::red_schema::is_virtual_table(&insert.table) => {
1001 Err(RedDBError::Query(
1002 super::red_schema::READ_ONLY_ERROR.to_string(),
1003 ))
1004 }
1005 QueryExpr::Update(ref update) if super::red_schema::is_virtual_table(&update.table) => {
1006 Err(RedDBError::Query(
1007 super::red_schema::READ_ONLY_ERROR.to_string(),
1008 ))
1009 }
1010 QueryExpr::Delete(ref delete) if super::red_schema::is_virtual_table(&delete.table) => {
1011 Err(RedDBError::Query(
1012 super::red_schema::READ_ONLY_ERROR.to_string(),
1013 ))
1014 }
1015 QueryExpr::Insert(ref insert) => self
1016 .with_deferred_store_wal_for_dml(self.insert_may_emit_events(insert), || {
1017 self.execute_insert(query, insert)
1018 }),
1019 QueryExpr::Update(ref update) => self
1020 .with_deferred_store_wal_for_dml(self.update_may_emit_events(update), || {
1021 self.execute_update(query, update)
1022 }),
1023 QueryExpr::Delete(ref delete) => self
1024 .with_deferred_store_wal_for_dml(self.delete_may_emit_events(delete), || {
1025 self.execute_delete(query, delete)
1026 }),
1027 QueryExpr::CreateTable(ref create) => self.execute_create_table(query, create),
1029 QueryExpr::CreateCollection(ref create) => {
1030 self.execute_create_collection(query, create)
1031 }
1032 QueryExpr::CreateVector(ref create) => self.execute_create_vector(query, create),
1033 QueryExpr::DropTable(ref drop_tbl) => self.execute_drop_table(query, drop_tbl),
1034 QueryExpr::DropGraph(ref drop_graph) => self.execute_drop_graph(query, drop_graph),
1035 QueryExpr::DropVector(ref drop_vector) => self.execute_drop_vector(query, drop_vector),
1036 QueryExpr::DropDocument(ref drop_document) => {
1037 self.execute_drop_document(query, drop_document)
1038 }
1039 QueryExpr::DropKv(ref drop_kv) => self.execute_drop_kv(query, drop_kv),
1040 QueryExpr::DropCollection(ref drop_collection) => {
1041 self.execute_drop_collection(query, drop_collection)
1042 }
1043 QueryExpr::Truncate(ref truncate) => self.execute_truncate(query, truncate),
1044 QueryExpr::AlterTable(ref alter) => self.execute_alter_table(query, alter),
1045 QueryExpr::CreateVcsRef(ref create) => self.execute_create_vcs_ref(query, create),
1046 QueryExpr::DropVcsRef(ref drop_ref) => self.execute_drop_vcs_ref(query, drop_ref),
1047 QueryExpr::ForkStore(ref fork) => self.execute_fork_store(query, fork),
1048 QueryExpr::PromoteFork(ref promote_fork) => {
1049 self.execute_promote_fork(query, promote_fork)
1050 }
1051 QueryExpr::DropFork(ref drop_fork) => self.execute_drop_fork(query, drop_fork),
1052 QueryExpr::ExplainAlter(ref explain) => self.execute_explain_alter(query, explain),
1053 QueryExpr::GraphCommand(ref cmd) => self.execute_graph_command(query, cmd),
1055 QueryExpr::SearchCommand(ref cmd) => self.execute_search_command(query, cmd),
1057 QueryExpr::Ask(ref ask) => self.execute_ask(query, ask),
1059 QueryExpr::CreateIndex(ref create_idx) => self.execute_create_index(query, create_idx),
1060 QueryExpr::DropIndex(ref drop_idx) => self.execute_drop_index(query, drop_idx),
1061 QueryExpr::ProbabilisticCommand(ref cmd) => {
1062 self.execute_probabilistic_command(query, cmd)
1063 }
1064 QueryExpr::CreateTimeSeries(ref ts) => self.execute_create_timeseries(query, ts),
1066 QueryExpr::CreateMetric(ref metric) => self.execute_create_metric(query, metric),
1067 QueryExpr::AlterMetric(ref alter) => self.execute_alter_metric(query, alter),
1068 QueryExpr::CreateSlo(ref slo) => self.execute_create_slo(query, slo),
1069 QueryExpr::DropTimeSeries(ref ts) => self.execute_drop_timeseries(query, ts),
1070 QueryExpr::CreateQueue(ref q) => self.execute_create_queue(query, q),
1072 QueryExpr::AlterQueue(ref q) => self.execute_alter_queue(query, q),
1073 QueryExpr::DropQueue(ref q) => self.execute_drop_queue(query, q),
1074 QueryExpr::QueueSelect(ref q) => self.execute_queue_select(query, q),
1075 QueryExpr::QueueCommand(ref cmd) => self
1076 .with_deferred_store_wal_if_transaction(|| self.execute_queue_command(query, cmd)),
1077 QueryExpr::EventsBackfill(ref backfill) => {
1078 self.execute_events_backfill(query, backfill)
1079 }
1080 QueryExpr::EventsBackfillStatus { ref collection } => Err(RedDBError::Query(format!(
1081 "EVENTS BACKFILL STATUS for '{collection}' is not implemented in this slice"
1082 ))),
1083 QueryExpr::KvCommand(ref cmd) => self.execute_kv_command(query, cmd),
1084 QueryExpr::ConfigCommand(ref cmd) => self.execute_config_command(query, cmd),
1085 QueryExpr::CreateTree(ref tree) => self.execute_create_tree(query, tree),
1086 QueryExpr::DropTree(ref tree) => self.execute_drop_tree(query, tree),
1087 QueryExpr::TreeCommand(ref cmd) => self.execute_tree_command(query, cmd),
1088 QueryExpr::SetConfig { ref key, ref value } => {
1090 if key.starts_with("red.secret.") {
1091 return Err(RedDBError::Query(
1092 "red.secret.* is reserved for vault secrets; use SET SECRET".to_string(),
1093 ));
1094 }
1095 if key.starts_with("red.secrets.") {
1096 return Err(RedDBError::Query(
1097 "red.secrets.* is reserved for vault secrets; use SET SECRET".to_string(),
1098 ));
1099 }
1100 crate::ai::validate_ai_config_key_on_write(key)?;
1104 match self.check_managed_config_write_for_set_config(key) {
1105 Err(err) => Err(err),
1106 Ok(()) => {
1107 let store = self.inner.db.store();
1108 let json_val = match value {
1109 Value::Text(s) => crate::serde_json::Value::String(s.to_string()),
1110 Value::Integer(n) => crate::serde_json::Value::Number(*n as f64),
1111 Value::Float(n) => crate::serde_json::Value::Number(*n),
1112 Value::Boolean(b) => crate::serde_json::Value::Bool(*b),
1113 _ => crate::serde_json::Value::String(value.to_string()),
1114 };
1115 store.set_config_tree(key, &json_val);
1116 update_current_config_value(key, value.clone());
1117 self.invalidate_result_cache();
1122 Ok(RuntimeQueryResult::ok_message(
1123 query.to_string(),
1124 &format!("config set: {key}"),
1125 "set",
1126 ))
1127 }
1128 }
1129 }
1130 QueryExpr::SetSecret { ref key, ref value } => {
1132 if key.starts_with("red.config.") {
1133 return Err(RedDBError::Query(
1134 "red.config.* is reserved for config; use SET CONFIG".to_string(),
1135 ));
1136 }
1137 let auth_store = self.inner.auth_store.read().clone().ok_or_else(|| {
1138 RedDBError::Query("SET SECRET requires an enabled, unsealed vault".to_string())
1139 })?;
1140 self.check_secret_write_privilege(&auth_store, key)?;
1141 if matches!(value, Value::Null) {
1142 auth_store
1143 .vault_kv_try_delete(key)
1144 .map_err(|err| RedDBError::Query(err.to_string()))?;
1145 update_current_secret_value(key, None);
1146 self.invalidate_result_cache();
1147 return Ok(RuntimeQueryResult::ok_message(
1148 query.to_string(),
1149 &format!("secret deleted: {key}"),
1150 "delete_secret",
1151 ));
1152 }
1153 let value = secret_sql_value_to_string(value)?;
1154 auth_store
1155 .vault_kv_try_set(key.clone(), value.clone())
1156 .map_err(|err| RedDBError::Query(err.to_string()))?;
1157 update_current_secret_value(key, Some(value));
1158 self.invalidate_result_cache();
1159 Ok(RuntimeQueryResult::ok_message(
1160 query.to_string(),
1161 &format!("secret set: {key}"),
1162 "set_secret",
1163 ))
1164 }
1165 QueryExpr::DeleteSecret { ref key } => {
1167 let auth_store = self.inner.auth_store.read().clone().ok_or_else(|| {
1168 RedDBError::Query(
1169 "DELETE SECRET requires an enabled, unsealed vault".to_string(),
1170 )
1171 })?;
1172 self.check_secret_write_privilege(&auth_store, key)?;
1173 let deleted = auth_store
1174 .vault_kv_try_delete(key)
1175 .map_err(|err| RedDBError::Query(err.to_string()))?;
1176 if deleted {
1177 update_current_secret_value(key, None);
1178 }
1179 self.invalidate_result_cache();
1180 Ok(RuntimeQueryResult::ok_message(
1181 query.to_string(),
1182 &format!("secret deleted: {key}"),
1183 if deleted {
1184 "delete_secret"
1185 } else {
1186 "delete_secret_not_found"
1187 },
1188 ))
1189 }
1190 QueryExpr::SetKv { ref key, ref value } => {
1192 let auth_store = self.inner.auth_store.read().clone().ok_or_else(|| {
1193 RedDBError::Query("SET KV requires an auth store".to_string())
1194 })?;
1195 self.check_kv_write_privilege(&auth_store, key)?;
1196 if matches!(value, Value::Null) {
1198 auth_store.plain_kv_delete(key);
1199 update_current_kv_value(key, None);
1200 self.invalidate_result_cache();
1201 return Ok(RuntimeQueryResult::ok_message(
1202 query.to_string(),
1203 &format!("kv deleted: {key}"),
1204 "delete_kv",
1205 ));
1206 }
1207 let value = secret_sql_value_to_string(value)?;
1208 auth_store.plain_kv_set(key.clone(), value.clone());
1209 update_current_kv_value(key, Some(value));
1210 self.invalidate_result_cache();
1211 Ok(RuntimeQueryResult::ok_message(
1212 query.to_string(),
1213 &format!("kv set: {key}"),
1214 "set_kv",
1215 ))
1216 }
1217 QueryExpr::DeleteKv { ref key } => {
1219 let auth_store = self.inner.auth_store.read().clone().ok_or_else(|| {
1220 RedDBError::Query("DELETE KV requires an auth store".to_string())
1221 })?;
1222 self.check_kv_write_privilege(&auth_store, key)?;
1223 let deleted = auth_store.plain_kv_delete(key);
1224 if deleted {
1225 update_current_kv_value(key, None);
1226 }
1227 self.invalidate_result_cache();
1228 Ok(RuntimeQueryResult::ok_message(
1229 query.to_string(),
1230 &format!("kv deleted: {key}"),
1231 if deleted {
1232 "delete_kv"
1233 } else {
1234 "delete_kv_not_found"
1235 },
1236 ))
1237 }
1238 QueryExpr::Scrub { background, budget } => {
1239 self.execute_scrub_query(query, mode, background, budget)
1240 }
1241 QueryExpr::ShowSecrets { ref prefix } => {
1243 let auth_store = self.inner.auth_store.read().clone().ok_or_else(|| {
1244 RedDBError::Query("SHOW SECRET requires an enabled, unsealed vault".to_string())
1245 })?;
1246 if !auth_store.is_vault_backed() {
1247 return Err(RedDBError::Query(
1248 "SHOW SECRET requires an enabled, unsealed vault".to_string(),
1249 ));
1250 }
1251 let mut keys = auth_store.vault_kv_keys();
1252 keys.sort();
1253 let mut result = UnifiedResult::with_columns(vec![
1254 "key".into(),
1255 "value".into(),
1256 "status".into(),
1257 ]);
1258 for key in keys {
1259 if !show_secrets_allows_key(&key) {
1260 continue;
1261 }
1262 if let Some(ref pfx) = prefix {
1263 if !key.starts_with(pfx) {
1264 continue;
1265 }
1266 }
1267 let mut record = UnifiedRecord::new();
1268 record.set("key", Value::text(key));
1269 record.set("value", Value::text("***"));
1270 record.set("status", Value::text("active"));
1271 result.push(record);
1272 }
1273 Ok(RuntimeQueryResult {
1274 query: query.to_string(),
1275 mode,
1276 statement: "show_secrets",
1277 engine: "runtime-secret",
1278 result,
1279 affected_rows: 0,
1280 statement_type: "select",
1281 bookmark: None,
1282 notice: None,
1283 })
1284 }
1285 QueryExpr::ShowConfig {
1287 ref prefix,
1288 as_json,
1289 } => {
1290 let store = self.inner.db.store();
1291 let all_collections = store.list_collections();
1292 if !all_collections.contains(&"red_config".to_string()) {
1293 if as_json {
1294 return Ok(show_config_json_result(
1295 query,
1296 mode,
1297 prefix,
1298 crate::serde_json::Value::Object(crate::serde_json::Map::new()),
1299 ));
1300 }
1301 let result = UnifiedResult::with_columns(vec!["key".into(), "value".into()]);
1302 return Ok(RuntimeQueryResult {
1303 query: query.to_string(),
1304 mode,
1305 statement: "show_config",
1306 engine: "runtime-config",
1307 result,
1308 affected_rows: 0,
1309 statement_type: "select",
1310 bookmark: None,
1311 notice: None,
1312 });
1313 }
1314 let manager = store
1315 .get_collection("red_config")
1316 .ok_or_else(|| RedDBError::NotFound("red_config".to_string()))?;
1317 let entities = manager.query_all(|_| true);
1318 let mut latest = std::collections::BTreeMap::<String, (u64, Value, Value)>::new();
1319 for entity in entities {
1320 if let EntityData::Row(ref row) = entity.data {
1321 if let Some(ref named) = row.named {
1322 let key_val = named.get("key").cloned().unwrap_or(Value::Null);
1323 let val = named.get("value").cloned().unwrap_or(Value::Null);
1324 let key_str = match &key_val {
1325 Value::Text(s) => s.as_ref(),
1326 _ => continue,
1327 };
1328 if let Some(ref pfx) = prefix {
1329 if !key_str.starts_with(pfx.as_str()) {
1330 continue;
1331 }
1332 }
1333 let entity_id = entity.id.raw();
1334 match latest.get(key_str) {
1335 Some((prev_id, _, _)) if *prev_id > entity_id => {}
1336 _ => {
1337 latest.insert(key_str.to_string(), (entity_id, key_val, val));
1338 }
1339 }
1340 }
1341 }
1342 }
1343 if as_json {
1344 let mut tree = crate::serde_json::Value::Object(crate::serde_json::Map::new());
1345 for (key, (_, _, val)) in latest {
1346 let relative = match prefix {
1347 Some(pfx) if key == *pfx => "",
1348 Some(pfx) => key
1349 .strip_prefix(pfx.as_str())
1350 .and_then(|tail| tail.strip_prefix('.'))
1351 .unwrap_or(key.as_str()),
1352 None => key.as_str(),
1353 };
1354 insert_config_json_path(
1355 &mut tree,
1356 relative,
1357 crate::presentation::entity_json::storage_value_to_json(&val),
1358 );
1359 }
1360 return Ok(show_config_json_result(query, mode, prefix, tree));
1361 }
1362 let mut result = UnifiedResult::with_columns(vec!["key".into(), "value".into()]);
1363 for (_, key_val, val) in latest.into_values() {
1364 let mut record = UnifiedRecord::new();
1365 record.set("key", key_val);
1366 record.set("value", val);
1367 result.push(record);
1368 }
1369 Ok(RuntimeQueryResult {
1370 query: query.to_string(),
1371 mode,
1372 statement: "show_config",
1373 engine: "runtime-config",
1374 result,
1375 affected_rows: 0,
1376 statement_type: "select",
1377 bookmark: None,
1378 notice: None,
1379 })
1380 }
1381 QueryExpr::SetTenant(ref value) => {
1387 match value {
1388 Some(id) => set_current_tenant(id.clone()),
1389 None => clear_current_tenant(),
1390 }
1391 Ok(RuntimeQueryResult::ok_message(
1392 query.to_string(),
1393 &match value {
1394 Some(id) => format!("tenant set: {id}"),
1395 None => "tenant cleared".to_string(),
1396 },
1397 "set_tenant",
1398 ))
1399 }
1400 QueryExpr::ShowTenant => {
1401 let mut result = UnifiedResult::with_columns(vec!["tenant".into()]);
1402 let mut record = UnifiedRecord::new();
1403 record.set(
1404 "tenant",
1405 current_tenant().map(Value::text).unwrap_or(Value::Null),
1406 );
1407 result.push(record);
1408 Ok(RuntimeQueryResult {
1409 query: query.to_string(),
1410 mode,
1411 statement: "show_tenant",
1412 engine: "runtime-tenant",
1413 result,
1414 affected_rows: 0,
1415 statement_type: "select",
1416 bookmark: None,
1417 notice: None,
1418 })
1419 }
1420 QueryExpr::TransactionControl(ref ctl) => {
1432 use crate::storage::query::ast::TxnControl;
1433 use crate::storage::transaction::snapshot::{TxnContext, Xid};
1434 use crate::storage::transaction::IsolationLevel;
1435
1436 let conn_id = current_connection_id();
1441
1442 let (kind, msg) = match ctl {
1443 TxnControl::Begin(requested_isolation) => {
1444 let isolation = requested_isolation
1445 .map(IsolationLevel::from)
1446 .unwrap_or(IsolationLevel::SnapshotIsolation);
1447 let mgr = Arc::clone(&self.inner.snapshot_manager);
1448 let xid = mgr.begin();
1449 if isolation == IsolationLevel::Serializable {
1450 mgr.begin_serializable(xid);
1451 }
1452 let snapshot = mgr.snapshot(xid);
1453 let ctx = TxnContext {
1454 xid,
1455 isolation,
1456 snapshot,
1457 savepoints: Vec::new(),
1458 released_sub_xids: Vec::new(),
1459 };
1460 self.inner.tx_contexts.write().insert(conn_id, ctx);
1461 ("begin", format!("BEGIN — xid={xid} (snapshot isolation)"))
1462 }
1463 TxnControl::Commit => {
1464 self.inner.tx_local_tenants.write().remove(&conn_id);
1466 let ctx = self.inner.tx_contexts.write().remove(&conn_id);
1467 match ctx {
1468 Some(ctx) => {
1469 let mut own_xids = std::collections::HashSet::new();
1470 own_xids.insert(ctx.xid);
1471 for (_, sub) in &ctx.savepoints {
1472 own_xids.insert(*sub);
1473 }
1474 for sub in &ctx.released_sub_xids {
1475 own_xids.insert(*sub);
1476 }
1477 if let Err(err) = self.check_table_row_write_conflicts(
1478 conn_id,
1479 &ctx.snapshot,
1480 &own_xids,
1481 ctx.isolation,
1482 ) {
1483 for (_, sub) in &ctx.savepoints {
1484 self.inner.snapshot_manager.rollback(*sub);
1485 }
1486 for sub in &ctx.released_sub_xids {
1487 self.inner.snapshot_manager.rollback(*sub);
1488 }
1489 self.inner.snapshot_manager.rollback(ctx.xid);
1490 self.revive_pending_versioned_updates(conn_id);
1491 self.revive_pending_tombstones(conn_id);
1492 self.discard_pending_kv_watch_events(conn_id);
1493 self.discard_pending_queue_wakes(conn_id);
1494 self.discard_pending_store_wal_actions(conn_id);
1495 self.release_pending_claim_locks(conn_id);
1496 return Err(err);
1497 }
1498 if let Err(err) = self.check_queue_dedup_write_conflicts(
1499 conn_id,
1500 &ctx.snapshot,
1501 &own_xids,
1502 ) {
1503 for (_, sub) in &ctx.savepoints {
1504 self.inner.snapshot_manager.rollback(*sub);
1505 }
1506 for sub in &ctx.released_sub_xids {
1507 self.inner.snapshot_manager.rollback(*sub);
1508 }
1509 self.inner.snapshot_manager.rollback(ctx.xid);
1510 self.revive_pending_versioned_updates(conn_id);
1511 self.revive_pending_tombstones(conn_id);
1512 self.discard_pending_queue_dedup(conn_id);
1513 self.discard_pending_kv_watch_events(conn_id);
1514 self.discard_pending_queue_wakes(conn_id);
1515 self.discard_pending_store_wal_actions(conn_id);
1516 self.release_pending_claim_locks(conn_id);
1517 return Err(err);
1518 }
1519 self.restore_pending_write_stamps(conn_id);
1520 if let Err(err) = self.flush_pending_store_wal_actions(conn_id) {
1521 for (_, sub) in &ctx.savepoints {
1522 self.inner.snapshot_manager.rollback(*sub);
1523 }
1524 for sub in &ctx.released_sub_xids {
1525 self.inner.snapshot_manager.rollback(*sub);
1526 }
1527 self.inner.snapshot_manager.rollback(ctx.xid);
1528 self.revive_pending_versioned_updates(conn_id);
1529 self.revive_pending_tombstones(conn_id);
1530 self.discard_pending_queue_dedup(conn_id);
1531 self.discard_pending_kv_watch_events(conn_id);
1532 self.discard_pending_queue_wakes(conn_id);
1533 self.release_pending_claim_locks(conn_id);
1534 return Err(err);
1535 }
1536 for (_, sub) in &ctx.savepoints {
1542 self.inner.snapshot_manager.commit(*sub);
1543 }
1544 for sub in &ctx.released_sub_xids {
1545 self.inner.snapshot_manager.commit(*sub);
1546 }
1547 self.inner.snapshot_manager.commit(ctx.xid);
1548 self.finalize_pending_versioned_updates(conn_id);
1549 self.finalize_pending_tombstones(conn_id);
1550 self.finalize_pending_queue_dedup(conn_id);
1551 self.finalize_pending_kv_watch_events(conn_id);
1552 self.finalize_pending_queue_wakes(conn_id);
1553 self.release_pending_claim_locks(conn_id);
1554 ("commit", format!("COMMIT — xid={} committed", ctx.xid))
1555 }
1556 None => (
1557 "commit",
1558 "COMMIT outside transaction — no-op (autocommit)".to_string(),
1559 ),
1560 }
1561 }
1562 TxnControl::Rollback => {
1563 self.inner.tx_local_tenants.write().remove(&conn_id);
1564 let ctx = self.inner.tx_contexts.write().remove(&conn_id);
1565 match ctx {
1566 Some(ctx) => {
1567 for (_, sub) in &ctx.savepoints {
1570 self.inner.snapshot_manager.rollback(*sub);
1571 }
1572 for sub in &ctx.released_sub_xids {
1573 self.inner.snapshot_manager.rollback(*sub);
1574 }
1575 self.inner.snapshot_manager.rollback(ctx.xid);
1576 self.revive_pending_versioned_updates(conn_id);
1580 self.revive_pending_tombstones(conn_id);
1581 self.discard_pending_queue_dedup(conn_id);
1582 self.discard_pending_kv_watch_events(conn_id);
1583 self.discard_pending_queue_wakes(conn_id);
1584 self.discard_pending_store_wal_actions(conn_id);
1585 self.release_pending_claim_locks(conn_id);
1586 ("rollback", format!("ROLLBACK — xid={} aborted", ctx.xid))
1587 }
1588 None => (
1589 "rollback",
1590 "ROLLBACK outside transaction — no-op (autocommit)".to_string(),
1591 ),
1592 }
1593 }
1594 TxnControl::Savepoint(name) => {
1601 let mgr = Arc::clone(&self.inner.snapshot_manager);
1602 let mut guard = self.inner.tx_contexts.write();
1603 match guard.get_mut(&conn_id) {
1604 Some(ctx) => {
1605 let sub = mgr.begin();
1606 ctx.savepoints.push((name.clone(), sub));
1607 ("savepoint", format!("SAVEPOINT {name} — sub_xid={sub}"))
1608 }
1609 None => (
1610 "savepoint",
1611 "SAVEPOINT outside transaction — no-op".to_string(),
1612 ),
1613 }
1614 }
1615 TxnControl::ReleaseSavepoint(name) => {
1616 let mut guard = self.inner.tx_contexts.write();
1617 match guard.get_mut(&conn_id) {
1618 Some(ctx) => {
1619 let pos = ctx
1620 .savepoints
1621 .iter()
1622 .position(|(n, _)| n == name)
1623 .ok_or_else(|| {
1624 RedDBError::Internal(format!(
1625 "savepoint {name} does not exist"
1626 ))
1627 })?;
1628 let released = ctx.savepoints.len() - pos;
1636 let popped: Vec<Xid> = ctx
1637 .savepoints
1638 .split_off(pos)
1639 .into_iter()
1640 .map(|(_, x)| x)
1641 .collect();
1642 ctx.released_sub_xids.extend(popped);
1643 (
1644 "release_savepoint",
1645 format!("RELEASE SAVEPOINT {name} — {released} level(s)"),
1646 )
1647 }
1648 None => (
1649 "release_savepoint",
1650 "RELEASE outside transaction — no-op".to_string(),
1651 ),
1652 }
1653 }
1654 TxnControl::RollbackToSavepoint(name) => {
1655 let mgr = Arc::clone(&self.inner.snapshot_manager);
1656 let drop_result: Option<(Xid, Vec<Xid>)> = {
1661 let mut guard = self.inner.tx_contexts.write();
1662 if let Some(ctx) = guard.get_mut(&conn_id) {
1663 let pos = ctx
1664 .savepoints
1665 .iter()
1666 .position(|(n, _)| n == name)
1667 .ok_or_else(|| {
1668 RedDBError::Internal(format!(
1669 "savepoint {name} does not exist"
1670 ))
1671 })?;
1672 let savepoint_xid = ctx.savepoints[pos].1;
1673 let aborted: Vec<Xid> = ctx
1674 .savepoints
1675 .split_off(pos)
1676 .into_iter()
1677 .map(|(_, x)| x)
1678 .collect();
1679 Some((savepoint_xid, aborted))
1680 } else {
1681 None
1682 }
1683 };
1684
1685 match drop_result {
1686 Some((savepoint_xid, aborted)) => {
1687 for x in &aborted {
1688 mgr.rollback(*x);
1689 }
1690 let reverted_updates =
1691 self.revive_versioned_updates_since(conn_id, savepoint_xid);
1692 let revived = self.revive_tombstones_since(conn_id, savepoint_xid);
1693 (
1694 "rollback_to_savepoint",
1695 format!(
1696 "ROLLBACK TO SAVEPOINT {name} — aborted {} sub_xid(s), reverted {reverted_updates} update(s), revived {revived} tombstone(s)",
1697 aborted.len(),
1698 ),
1699 )
1700 }
1701 None => (
1702 "rollback_to_savepoint",
1703 "ROLLBACK TO outside transaction — no-op".to_string(),
1704 ),
1705 }
1706 }
1707 };
1708 Ok(RuntimeQueryResult::ok_message(
1709 query.to_string(),
1710 &msg,
1711 kind,
1712 ))
1713 }
1714 QueryExpr::CreateSchema(ref q) => {
1727 let store = self.inner.db.store();
1728 let key = format!("schema.{}", q.name);
1729 if store.get_config(&key).is_some() {
1730 if q.if_not_exists {
1731 return Ok(RuntimeQueryResult::ok_message(
1732 query.to_string(),
1733 &format!("schema {} already exists — skipped", q.name),
1734 "create_schema",
1735 ));
1736 }
1737 return Err(RedDBError::Internal(format!(
1738 "schema {} already exists",
1739 q.name
1740 )));
1741 }
1742 store.set_config_tree(&key, &crate::serde_json::Value::Bool(true));
1743 Ok(RuntimeQueryResult::ok_message(
1744 query.to_string(),
1745 &format!("schema {} created", q.name),
1746 "create_schema",
1747 ))
1748 }
1749 QueryExpr::DropSchema(ref q) => {
1750 let store = self.inner.db.store();
1751 let key = format!("schema.{}", q.name);
1752 let existed = store.get_config(&key).is_some();
1753 if !existed && !q.if_exists {
1754 return Err(RedDBError::Internal(format!(
1755 "schema {} does not exist",
1756 q.name
1757 )));
1758 }
1759 store.set_config_tree(&key, &crate::serde_json::Value::Null);
1761 let suffix = if q.cascade {
1762 " (CASCADE accepted — tables untouched)"
1763 } else {
1764 ""
1765 };
1766 Ok(RuntimeQueryResult::ok_message(
1767 query.to_string(),
1768 &format!("schema {} dropped{}", q.name, suffix),
1769 "drop_schema",
1770 ))
1771 }
1772 QueryExpr::CreateSequence(ref q) => {
1773 let store = self.inner.db.store();
1774 let base = format!("sequence.{}", q.name);
1775 let start_key = format!("{base}.start");
1776 let incr_key = format!("{base}.increment");
1777 let curr_key = format!("{base}.current");
1778 if store.get_config(&start_key).is_some() {
1779 if q.if_not_exists {
1780 return Ok(RuntimeQueryResult::ok_message(
1781 query.to_string(),
1782 &format!("sequence {} already exists — skipped", q.name),
1783 "create_sequence",
1784 ));
1785 }
1786 return Err(RedDBError::Internal(format!(
1787 "sequence {} already exists",
1788 q.name
1789 )));
1790 }
1791 let initial_current = q.start - q.increment;
1794 store.set_config_tree(
1795 &start_key,
1796 &crate::serde_json::Value::Number(q.start as f64),
1797 );
1798 store.set_config_tree(
1799 &incr_key,
1800 &crate::serde_json::Value::Number(q.increment as f64),
1801 );
1802 store.set_config_tree(
1803 &curr_key,
1804 &crate::serde_json::Value::Number(initial_current as f64),
1805 );
1806 Ok(RuntimeQueryResult::ok_message(
1807 query.to_string(),
1808 &format!(
1809 "sequence {} created (start={}, increment={})",
1810 q.name, q.start, q.increment
1811 ),
1812 "create_sequence",
1813 ))
1814 }
1815 QueryExpr::DropSequence(ref q) => {
1816 let store = self.inner.db.store();
1817 let base = format!("sequence.{}", q.name);
1818 let existed = store.get_config(&format!("{base}.start")).is_some();
1819 if !existed && !q.if_exists {
1820 return Err(RedDBError::Internal(format!(
1821 "sequence {} does not exist",
1822 q.name
1823 )));
1824 }
1825 for k in ["start", "increment", "current"] {
1826 store.set_config_tree(&format!("{base}.{k}"), &crate::serde_json::Value::Null);
1827 }
1828 Ok(RuntimeQueryResult::ok_message(
1829 query.to_string(),
1830 &format!("sequence {} dropped", q.name),
1831 "drop_sequence",
1832 ))
1833 }
1834 QueryExpr::CreateView(ref q) => {
1844 let mut views = self.inner.views.write();
1845 if views.contains_key(&q.name) && !q.or_replace {
1846 if q.if_not_exists {
1847 return Ok(RuntimeQueryResult::ok_message(
1848 query.to_string(),
1849 &format!("view {} already exists — skipped", q.name),
1850 "create_view",
1851 ));
1852 }
1853 return Err(RedDBError::Internal(format!(
1854 "view {} already exists",
1855 q.name
1856 )));
1857 }
1858 views.insert(q.name.clone(), Arc::new(q.clone()));
1859 drop(views);
1860
1861 if q.materialized {
1863 use crate::storage::cache::result::{MaterializedViewDef, RefreshPolicy};
1864 let refresh = match q.refresh_every_ms {
1865 Some(ms) => RefreshPolicy::Periodic(std::time::Duration::from_millis(ms)),
1866 None => RefreshPolicy::Manual,
1867 };
1868 let dependencies = collect_table_refs(&q.query);
1869 let def = MaterializedViewDef {
1870 name: q.name.clone(),
1871 query: format!("<parsed view {}>", q.name),
1872 dependencies: dependencies.clone(),
1873 refresh,
1874 retention_duration_ms: q.retention_duration_ms,
1875 };
1876 self.inner.materialized_views.write().register(def);
1877
1878 let descriptor =
1884 crate::runtime::continuous_materialized_view::MaterializedViewDescriptor {
1885 name: q.name.clone(),
1886 source_sql: query.to_string(),
1887 source_collections: dependencies,
1888 refresh_every_ms: q.refresh_every_ms,
1889 retention_duration_ms: q.retention_duration_ms,
1890 };
1891 let store = self.inner.db.store();
1892 crate::runtime::continuous_materialized_view::persist_descriptor(
1893 store.as_ref(),
1894 &descriptor,
1895 )?;
1896
1897 self.ensure_materialized_view_backing(&q.name)?;
1904 }
1905 self.invalidate_plan_cache();
1910 self.invalidate_result_cache();
1911
1912 Ok(RuntimeQueryResult::ok_message(
1913 query.to_string(),
1914 &format!(
1915 "{}view {} created",
1916 if q.materialized { "materialized " } else { "" },
1917 q.name
1918 ),
1919 "create_view",
1920 ))
1921 }
1922 QueryExpr::DropView(ref q) => {
1923 let mut views = self.inner.views.write();
1924 let removed = views.remove(&q.name);
1925 let existed = removed.is_some();
1926 let removed_materialized =
1927 removed.as_ref().map(|v| v.materialized).unwrap_or(false);
1928 drop(views);
1929 if q.materialized || existed {
1930 self.inner.materialized_views.write().remove(&q.name);
1932 let store = self.inner.db.store();
1936 crate::runtime::continuous_materialized_view::remove_by_name(
1937 store.as_ref(),
1938 &q.name,
1939 )?;
1940 }
1941 if removed_materialized || q.materialized {
1945 self.drop_materialized_view_backing(&q.name)?;
1946 }
1947 self.invalidate_plan_cache();
1950 self.invalidate_result_cache();
1951 if !existed && !q.if_exists {
1952 return Err(RedDBError::Internal(format!(
1953 "view {} does not exist",
1954 q.name
1955 )));
1956 }
1957 self.invalidate_plan_cache();
1958 Ok(RuntimeQueryResult::ok_message(
1959 query.to_string(),
1960 &format!("view {} dropped", q.name),
1961 "drop_view",
1962 ))
1963 }
1964 QueryExpr::RefreshMaterializedView(ref q) => {
1965 let view = {
1968 let views = self.inner.views.read();
1969 views.get(&q.name).cloned()
1970 };
1971 let view = match view {
1972 Some(v) => v,
1973 None => {
1974 return Err(RedDBError::Internal(format!(
1975 "view {} does not exist",
1976 q.name
1977 )))
1978 }
1979 };
1980 if !view.materialized {
1981 return Err(RedDBError::Internal(format!(
1982 "view {} is not materialized — REFRESH requires \
1983 CREATE MATERIALIZED VIEW",
1984 q.name
1985 )));
1986 }
1987 let started = std::time::Instant::now();
1989 let now_ms = std::time::SystemTime::now()
1990 .duration_since(std::time::UNIX_EPOCH)
1991 .map(|d| d.as_millis() as u64)
1992 .unwrap_or(0);
1993 match self.execute_query_expr((*view.query).clone()) {
1994 Ok(inner_result) => {
1995 let entities =
2002 view_records_to_entities(&q.name, &inner_result.result.records);
2003 let row_count = entities.len() as u64;
2004 let store = self.inner.db.store();
2005 let serialized_records = match store.refresh_collection(&q.name, entities) {
2006 Ok(records) => records,
2007 Err(err) => {
2008 let duration_ms = started.elapsed().as_millis() as u64;
2009 let msg = err.to_string();
2010 self.inner
2011 .materialized_views
2012 .write()
2013 .record_refresh_failure(
2014 &q.name,
2015 msg.clone(),
2016 duration_ms,
2017 now_ms,
2018 );
2019 return Err(RedDBError::Internal(format!(
2020 "REFRESH MATERIALIZED VIEW {}: {msg}",
2021 q.name
2022 )));
2023 }
2024 };
2025
2026 if let Some(ref primary) = self.inner.db.replication {
2032 let lsn = self.inner.cdc.emit(
2033 crate::replication::cdc::ChangeOperation::Refresh,
2034 &q.name,
2035 0,
2036 "refresh",
2037 );
2038 self.invalidate_result_cache_for_table(&q.name);
2039 let timestamp = std::time::SystemTime::now()
2040 .duration_since(std::time::UNIX_EPOCH)
2041 .unwrap_or_default()
2042 .as_millis() as u64;
2043 let record = ChangeRecord::for_refresh(
2044 lsn,
2045 timestamp,
2046 q.name.clone(),
2047 serialized_records,
2048 )
2049 .with_term(self.current_replication_term());
2050 let encoded = record.encode();
2051 primary.append_logical_record(record.lsn, encoded);
2052 }
2053
2054 let duration_ms = started.elapsed().as_millis() as u64;
2055 let serialized = format!("{:?}", inner_result.result);
2056 self.inner
2057 .materialized_views
2058 .write()
2059 .record_refresh_success(
2060 &q.name,
2061 serialized.into_bytes(),
2062 row_count,
2063 duration_ms,
2064 now_ms,
2065 );
2066 self.invalidate_result_cache();
2071 Ok(RuntimeQueryResult::ok_message(
2072 query.to_string(),
2073 &format!("materialized view {} refreshed", q.name),
2074 "refresh_materialized_view",
2075 ))
2076 }
2077 Err(err) => {
2078 let duration_ms = started.elapsed().as_millis() as u64;
2079 let msg = err.to_string();
2080 self.inner
2081 .materialized_views
2082 .write()
2083 .record_refresh_failure(&q.name, msg.clone(), duration_ms, now_ms);
2084 Err(err)
2085 }
2086 }
2087 }
2088 QueryExpr::CreatePolicy(ref q) => {
2095 let key = (q.table.clone(), q.name.clone());
2096 self.inner
2097 .rls_policies
2098 .write()
2099 .insert(key, Arc::new(q.clone()));
2100 self.invalidate_plan_cache();
2101 self.schema_vocabulary_apply(
2105 crate::runtime::schema_vocabulary::DdlEvent::CreatePolicy {
2106 collection: q.table.clone(),
2107 policy: q.name.clone(),
2108 },
2109 );
2110 Ok(RuntimeQueryResult::ok_message(
2111 query.to_string(),
2112 &format!("policy {} on {} created", q.name, q.table),
2113 "create_policy",
2114 ))
2115 }
2116 QueryExpr::DropPolicy(ref q) => {
2117 let removed = self
2118 .inner
2119 .rls_policies
2120 .write()
2121 .remove(&(q.table.clone(), q.name.clone()))
2122 .is_some();
2123 if !removed && !q.if_exists {
2124 return Err(RedDBError::Internal(format!(
2125 "policy {} on {} does not exist",
2126 q.name, q.table
2127 )));
2128 }
2129 self.invalidate_plan_cache();
2130 self.schema_vocabulary_apply(
2133 crate::runtime::schema_vocabulary::DdlEvent::DropPolicy {
2134 collection: q.table.clone(),
2135 policy: q.name.clone(),
2136 },
2137 );
2138 Ok(RuntimeQueryResult::ok_message(
2139 query.to_string(),
2140 &format!("policy {} on {} dropped", q.name, q.table),
2141 "drop_policy",
2142 ))
2143 }
2144 QueryExpr::CreateServer(ref q) => {
2155 use crate::storage::fdw::FdwOptions;
2156 let registry = Arc::clone(&self.inner.foreign_tables);
2157 if registry.server(&q.name).is_some() {
2158 if q.if_not_exists {
2159 return Ok(RuntimeQueryResult::ok_message(
2160 query.to_string(),
2161 &format!("server {} already exists — skipped", q.name),
2162 "create_server",
2163 ));
2164 }
2165 return Err(RedDBError::Internal(format!(
2166 "server {} already exists",
2167 q.name
2168 )));
2169 }
2170 let mut opts = FdwOptions::new();
2171 for (k, v) in &q.options {
2172 opts.values.insert(k.clone(), v.clone());
2173 }
2174 registry
2175 .create_server(&q.name, &q.wrapper, opts)
2176 .map_err(|e| RedDBError::Internal(e.to_string()))?;
2177 Ok(RuntimeQueryResult::ok_message(
2178 query.to_string(),
2179 &format!("server {} created (wrapper {})", q.name, q.wrapper),
2180 "create_server",
2181 ))
2182 }
2183 QueryExpr::DropServer(ref q) => {
2184 let existed = self.inner.foreign_tables.drop_server(&q.name);
2185 if !existed && !q.if_exists {
2186 return Err(RedDBError::Internal(format!(
2187 "server {} does not exist",
2188 q.name
2189 )));
2190 }
2191 Ok(RuntimeQueryResult::ok_message(
2192 query.to_string(),
2193 &format!(
2194 "server {} dropped{}",
2195 q.name,
2196 if q.cascade { " (cascade)" } else { "" }
2197 ),
2198 "drop_server",
2199 ))
2200 }
2201 QueryExpr::CreateForeignTable(ref q) => {
2202 use crate::storage::fdw::{FdwOptions, ForeignColumn, ForeignTable};
2203 let registry = Arc::clone(&self.inner.foreign_tables);
2204 if registry.foreign_table(&q.name).is_some() {
2205 if q.if_not_exists {
2206 return Ok(RuntimeQueryResult::ok_message(
2207 query.to_string(),
2208 &format!("foreign table {} already exists — skipped", q.name),
2209 "create_foreign_table",
2210 ));
2211 }
2212 return Err(RedDBError::Internal(format!(
2213 "foreign table {} already exists",
2214 q.name
2215 )));
2216 }
2217 let mut opts = FdwOptions::new();
2218 for (k, v) in &q.options {
2219 opts.values.insert(k.clone(), v.clone());
2220 }
2221 let columns: Vec<ForeignColumn> = q
2222 .columns
2223 .iter()
2224 .map(|c| ForeignColumn {
2225 name: c.name.clone(),
2226 data_type: c.data_type.clone(),
2227 not_null: c.not_null,
2228 })
2229 .collect();
2230 registry
2231 .create_foreign_table(ForeignTable {
2232 name: q.name.clone(),
2233 server_name: q.server.clone(),
2234 columns,
2235 options: opts,
2236 })
2237 .map_err(|e| RedDBError::Internal(e.to_string()))?;
2238 self.invalidate_plan_cache();
2239 Ok(RuntimeQueryResult::ok_message(
2240 query.to_string(),
2241 &format!("foreign table {} created (server {})", q.name, q.server),
2242 "create_foreign_table",
2243 ))
2244 }
2245 QueryExpr::DropForeignTable(ref q) => {
2246 let existed = self.inner.foreign_tables.drop_foreign_table(&q.name);
2247 if !existed && !q.if_exists {
2248 return Err(RedDBError::Internal(format!(
2249 "foreign table {} does not exist",
2250 q.name
2251 )));
2252 }
2253 self.invalidate_plan_cache();
2254 Ok(RuntimeQueryResult::ok_message(
2255 query.to_string(),
2256 &format!("foreign table {} dropped", q.name),
2257 "drop_foreign_table",
2258 ))
2259 }
2260 QueryExpr::CopyFrom(ref q) => {
2266 use crate::storage::import::{CsvConfig, CsvImporter};
2267 let store = self.inner.db.store();
2268 let cfg = CsvConfig {
2269 collection: q.table.clone(),
2270 has_header: q.has_header,
2271 delimiter: q.delimiter.map(|c| c as u8).unwrap_or(b','),
2272 ..CsvConfig::default()
2273 };
2274 let importer = CsvImporter::new(cfg);
2275 let stats = importer
2276 .import_file(&q.path, store.as_ref())
2277 .map_err(|e| RedDBError::Internal(format!("COPY failed: {e}")))?;
2278 self.note_table_write(&q.table);
2280 Ok(RuntimeQueryResult::ok_message(
2281 query.to_string(),
2282 &format!(
2283 "COPY imported {} rows into {} ({} errors skipped, {}ms)",
2284 stats.records_imported, q.table, stats.errors_skipped, stats.duration_ms
2285 ),
2286 "copy_from",
2287 ))
2288 }
2289 QueryExpr::MaintenanceCommand(ref cmd) => {
2305 use crate::storage::query::ast::MaintenanceCommand as Mc;
2306 let store = self.inner.db.store();
2307 let (kind, msg) = match cmd {
2308 Mc::Analyze { target } => {
2309 let targets: Vec<String> = match target {
2310 Some(t) => vec![t.clone()],
2311 None => store.list_collections(),
2312 };
2313 for t in &targets {
2314 self.refresh_table_planner_stats(t);
2315 }
2316 (
2317 "analyze",
2318 format!("ANALYZE refreshed stats for {} table(s)", targets.len()),
2319 )
2320 }
2321 Mc::Vacuum { target, full } => {
2322 let targets: Vec<String> = match target {
2323 Some(t) => vec![t.clone()],
2324 None => store.list_collections(),
2325 };
2326 let cutoff_xid = self.mvcc_vacuum_cutoff_xid();
2327 let mut vacuum_stats =
2328 crate::storage::unified::store::MvccVacuumStats::default();
2329 for t in &targets {
2330 let stats = store.vacuum_mvcc_history(t, cutoff_xid).map_err(|e| {
2331 RedDBError::Internal(format!(
2332 "VACUUM MVCC history failed for {t}: {e}"
2333 ))
2334 })?;
2335 if stats.reclaimed_versions > 0 {
2336 self.rebuild_runtime_indexes_for_table(t)?;
2337 }
2338 vacuum_stats.add(&stats);
2339 }
2340 self.inner.snapshot_manager.prune_aborted(cutoff_xid);
2341 for t in &targets {
2343 self.refresh_table_planner_stats(t);
2344 }
2345 let persisted = if *full {
2349 match store.persist() {
2350 Ok(()) => true,
2351 Err(e) => {
2352 return Err(RedDBError::Internal(format!(
2353 "VACUUM FULL persist failed: {e:?}"
2354 )));
2355 }
2356 }
2357 } else {
2358 false
2359 };
2360 self.invalidate_result_cache();
2362 (
2363 "vacuum",
2364 format!(
2365 "VACUUM{} processed {} table(s): scanned_versions={}, retained_versions={}, reclaimed_versions={}, retained_history_versions={}, reclaimed_history_versions={}, retained_tombstones={}, reclaimed_tombstones={}{}",
2366 if *full { " FULL" } else { "" },
2367 targets.len(),
2368 vacuum_stats.scanned_versions,
2369 vacuum_stats.retained_versions,
2370 vacuum_stats.reclaimed_versions,
2371 vacuum_stats.retained_history_versions,
2372 vacuum_stats.reclaimed_history_versions,
2373 vacuum_stats.retained_tombstones,
2374 vacuum_stats.reclaimed_tombstones,
2375 if persisted {
2376 " (pages flushed to disk)"
2377 } else {
2378 ""
2379 }
2380 ),
2381 )
2382 }
2383 };
2384 Ok(RuntimeQueryResult::ok_message(
2385 query.to_string(),
2386 &msg,
2387 kind,
2388 ))
2389 }
2390 QueryExpr::Grant(ref g) => self.execute_grant_statement(query, g),
2397 QueryExpr::Revoke(ref r) => self.execute_revoke_statement(query, r),
2398 QueryExpr::AlterUser(ref a) => self.execute_alter_user_statement(query, a),
2399 QueryExpr::CreateUser(ref u) => self.execute_create_user_statement(query, u),
2400 QueryExpr::CreateIamPolicy { ref id, ref json } => {
2401 self.execute_create_iam_policy(query, id, json)
2402 }
2403 QueryExpr::DropIamPolicy { ref id } => self.execute_drop_iam_policy(query, id),
2404 QueryExpr::AttachPolicy {
2405 ref policy_id,
2406 ref principal,
2407 } => self.execute_attach_policy(query, policy_id, principal),
2408 QueryExpr::DetachPolicy {
2409 ref policy_id,
2410 ref principal,
2411 } => self.execute_detach_policy(query, policy_id, principal),
2412 QueryExpr::ShowPolicies { ref filter } => {
2413 self.execute_show_policies(query, filter.as_ref())
2414 }
2415 QueryExpr::ShowEffectivePermissions {
2416 ref user,
2417 ref resource,
2418 } => self.execute_show_effective_permissions(query, user, resource.as_ref()),
2419 QueryExpr::SimulatePolicy {
2420 ref user,
2421 ref action,
2422 ref resource,
2423 } => self.execute_simulate_policy(query, user, action, resource),
2424 QueryExpr::LintPolicy { ref source } => self.execute_lint_policy(query, source),
2425 QueryExpr::MigratePolicyMode {
2426 ref target,
2427 dry_run,
2428 } => self.execute_migrate_policy_mode(query, target, dry_run),
2429 QueryExpr::CreateMigration(ref q) => self.execute_create_migration(query, q),
2430 QueryExpr::ApplyMigration(ref q) => self.execute_apply_migration(query, q),
2431 QueryExpr::RollbackMigration(ref q) => self.execute_rollback_migration(query, q),
2432 QueryExpr::ExplainMigration(ref q) => self.execute_explain_migration(query, q),
2433 _ => Err(RedDBError::Query(
2434 "unsupported command in runtime dispatcher".to_string(),
2435 )),
2436 };
2437
2438 if !control_event_specs.is_empty() {
2439 let (outcome, reason) = match &query_result {
2440 Ok(_) => (crate::runtime::control_events::Outcome::Allowed, None),
2441 Err(err) => (control_event_outcome_for_error(err), Some(err.to_string())),
2442 };
2443 for spec in &control_event_specs {
2444 self.emit_control_event(
2445 spec.kind,
2446 outcome,
2447 spec.action,
2448 spec.resource.clone(),
2449 reason.clone(),
2450 spec.fields.clone(),
2451 )?;
2452 }
2453 }
2454
2455 if let (Some(plan), Ok(result)) = (&query_audit_plan, &query_result) {
2456 self.emit_query_audit(
2457 query,
2458 plan,
2459 query_audit_started.elapsed().as_millis() as u64,
2460 result,
2461 );
2462 }
2463
2464 let mut query_result = query_result;
2468 if let Ok(ref mut result) = query_result {
2469 if result.statement_type == "select" {
2470 self.apply_secret_decryption(result);
2471 }
2472 }
2473
2474 if let Ok(ref result) = query_result {
2481 frame.write_result_cache(self, result, result_cache_scopes);
2482 }
2483
2484 query_result
2485 }
2486
2487 pub fn execute_query_expr(&self, expr: QueryExpr) -> RedDBResult<RuntimeQueryResult> {
2493 let _config_snapshot_guard = ConfigSnapshotGuard::install(
2494 Arc::clone(&self.inner.db),
2495 self.inner.auth_store.read().clone(),
2496 );
2497 let _secret_store_guard = SecretStoreGuard::install(self.inner.auth_store.read().clone());
2498 let expr = self.rewrite_view_refs(expr);
2502
2503 self.validate_model_operations_before_auth(&expr)?;
2504 if let Err(err) = self.check_query_privilege(&expr) {
2508 return Err(RedDBError::Query(format!("permission denied: {err}")));
2509 }
2510
2511 let statement = query_expr_name(&expr);
2512 let mode = detect_mode(statement);
2513 let query_str = statement;
2514
2515 let result = self.dispatch_expr(expr, query_str, mode)?;
2516 let mut r = result;
2517 if r.statement_type == "select" {
2518 self.apply_secret_decryption(&mut r);
2519 }
2520 Ok(r)
2521 }
2522
2523 pub(super) fn validate_model_operations_before_auth(
2524 &self,
2525 expr: &QueryExpr,
2526 ) -> RedDBResult<()> {
2527 use crate::catalog::CollectionModel;
2528 use crate::runtime::ddl::polymorphic_resolver;
2529 use crate::storage::query::ast::KvCommand;
2530
2531 let system_schema_target = match expr {
2532 QueryExpr::DropTable(q) => Some(q.name.as_str()),
2533 QueryExpr::DropGraph(q) => Some(q.name.as_str()),
2534 QueryExpr::DropVector(q) => Some(q.name.as_str()),
2535 QueryExpr::DropDocument(q) => Some(q.name.as_str()),
2536 QueryExpr::DropKv(q) => Some(q.name.as_str()),
2537 QueryExpr::DropCollection(q) => Some(q.name.as_str()),
2538 QueryExpr::Truncate(q) => Some(q.name.as_str()),
2539 _ => None,
2540 };
2541 if system_schema_target.is_some_and(crate::runtime::impl_ddl::is_system_schema_name) {
2542 return Err(RedDBError::Query("system schema is read-only".to_string()));
2543 }
2544
2545 let expected = match expr {
2546 QueryExpr::DropTable(q) => Some((q.name.as_str(), CollectionModel::Table)),
2547 QueryExpr::DropGraph(q) => Some((q.name.as_str(), CollectionModel::Graph)),
2548 QueryExpr::DropVector(q) => Some((q.name.as_str(), CollectionModel::Vector)),
2549 QueryExpr::DropDocument(q) => Some((q.name.as_str(), CollectionModel::Document)),
2550 QueryExpr::DropKv(q) => Some((q.name.as_str(), q.model)),
2551 QueryExpr::DropCollection(q) => q.model.map(|model| (q.name.as_str(), model)),
2552 QueryExpr::Truncate(q) => q.model.map(|model| (q.name.as_str(), model)),
2553 QueryExpr::KvCommand(cmd) => {
2554 let (collection, model) = match cmd {
2555 KvCommand::Put {
2556 collection, model, ..
2557 }
2558 | KvCommand::Get {
2559 collection, model, ..
2560 }
2561 | KvCommand::Incr {
2562 collection, model, ..
2563 }
2564 | KvCommand::Cas {
2565 collection, model, ..
2566 }
2567 | KvCommand::List {
2568 collection, model, ..
2569 }
2570 | KvCommand::Delete {
2571 collection, model, ..
2572 } => (collection.as_str(), *model),
2573 KvCommand::Rotate { collection, .. }
2574 | KvCommand::History { collection, .. }
2575 | KvCommand::Purge { collection, .. } => {
2576 (collection.as_str(), CollectionModel::Vault)
2577 }
2578 KvCommand::InvalidateTags { collection, .. } => {
2579 (collection.as_str(), CollectionModel::Kv)
2580 }
2581 KvCommand::Watch {
2582 collection, model, ..
2583 } => (collection.as_str(), *model),
2584 KvCommand::Unseal { collection, .. } => {
2585 (collection.as_str(), CollectionModel::Vault)
2586 }
2587 };
2588 Some((collection, model))
2589 }
2590 QueryExpr::ConfigCommand(cmd) => {
2591 self.validate_config_command_before_auth(cmd)?;
2592 None
2593 }
2594 _ => None,
2595 };
2596
2597 let Some((name, expected_model)) = expected else {
2598 return Ok(());
2599 };
2600 let snapshot = self.inner.db.catalog_model_snapshot();
2601 let Some(actual_model) = snapshot
2602 .collections
2603 .iter()
2604 .find(|collection| collection.name == name)
2605 .map(|collection| collection.declared_model.unwrap_or(collection.model))
2606 else {
2607 return Ok(());
2608 };
2609 polymorphic_resolver::ensure_model_match(expected_model, actual_model)
2610 }
2611
2612 pub(super) fn rewrite_view_refs(&self, expr: QueryExpr) -> QueryExpr {
2617 if self.inner.views.read().is_empty() {
2619 return expr;
2620 }
2621 self.rewrite_view_refs_inner(expr)
2622 }
2623
2624 fn rewrite_view_refs_inner(&self, expr: QueryExpr) -> QueryExpr {
2625 use crate::storage::query::ast::{Filter, TableSource};
2626 match expr {
2627 QueryExpr::Table(mut tq) => {
2628 if let Some(TableSource::Subquery(body)) = tq.source.take() {
2634 tq.source = Some(TableSource::Subquery(Box::new(
2635 self.rewrite_view_refs_inner(*body),
2636 )));
2637 return QueryExpr::Table(tq);
2638 }
2639
2640 let maybe_view = {
2644 let views = self.inner.views.read();
2645 views.get(&tq.table).cloned()
2646 };
2647 let Some(view) = maybe_view else {
2648 return QueryExpr::Table(tq);
2649 };
2650
2651 if view.materialized {
2657 return QueryExpr::Table(tq);
2658 }
2659
2660 let inner_expr = self.rewrite_view_refs_inner((*view.query).clone());
2664
2665 match inner_expr {
2673 QueryExpr::Table(mut inner_tq) => {
2674 if let Some(outer_filter) = tq.filter.take() {
2675 inner_tq.filter = Some(match inner_tq.filter.take() {
2676 Some(existing) => {
2677 Filter::And(Box::new(existing), Box::new(outer_filter))
2678 }
2679 None => outer_filter,
2680 });
2681 inner_tq.where_expr = inner_tq
2689 .filter
2690 .as_ref()
2691 .map(crate::storage::query::sql_lowering::filter_to_expr);
2692 }
2693 if let Some(outer_limit) = tq.limit {
2694 inner_tq.limit = Some(match inner_tq.limit {
2695 Some(existing) => existing.min(outer_limit),
2696 None => outer_limit,
2697 });
2698 }
2699 if let Some(outer_offset) = tq.offset {
2700 inner_tq.offset = Some(match inner_tq.offset {
2701 Some(existing) => existing + outer_offset,
2702 None => outer_offset,
2703 });
2704 }
2705 QueryExpr::Table(inner_tq)
2706 }
2707 other => other,
2708 }
2709 }
2710 QueryExpr::Join(mut jq) => {
2711 jq.left = Box::new(self.rewrite_view_refs_inner(*jq.left));
2712 jq.right = Box::new(self.rewrite_view_refs_inner(*jq.right));
2713 QueryExpr::Join(jq)
2714 }
2715 other => other,
2718 }
2719 }
2720
2721 fn resolve_table_expr_subqueries(
2722 &self,
2723 mut table: TableQuery,
2724 frame: &dyn super::statement_frame::ReadFrame,
2725 ) -> RedDBResult<TableQuery> {
2726 match table.source.take() {
2733 Some(TableSource::Subquery(inner)) => {
2734 let inner = self.resolve_select_expr_subqueries(*inner, frame)?;
2735 table.source = Some(TableSource::Subquery(Box::new(inner)));
2736 }
2737 other => table.source = other,
2738 }
2739
2740 let outer_scopes = relation_scopes_for_query(&QueryExpr::Table(table.clone()));
2741 for item in &mut table.select_items {
2742 if let crate::storage::query::ast::SelectItem::Expr { expr, .. } = item {
2743 *expr = self.resolve_expr_subqueries(expr.clone(), &outer_scopes, frame)?;
2744 }
2745 }
2746 if let Some(where_expr) = table.where_expr.take() {
2747 table.where_expr =
2748 Some(self.resolve_expr_subqueries(where_expr, &outer_scopes, frame)?);
2749 table.filter = None;
2750 }
2751 if let Some(having_expr) = table.having_expr.take() {
2752 table.having_expr =
2753 Some(self.resolve_expr_subqueries(having_expr, &outer_scopes, frame)?);
2754 table.having = None;
2755 }
2756 for expr in &mut table.group_by_exprs {
2757 *expr = self.resolve_expr_subqueries(expr.clone(), &outer_scopes, frame)?;
2758 }
2759 for clause in &mut table.order_by {
2760 if let Some(expr) = clause.expr.take() {
2761 clause.expr = Some(self.resolve_expr_subqueries(expr, &outer_scopes, frame)?);
2762 }
2763 }
2764 Ok(table)
2765 }
2766
2767 fn resolve_select_expr_subqueries(
2768 &self,
2769 expr: QueryExpr,
2770 frame: &dyn super::statement_frame::ReadFrame,
2771 ) -> RedDBResult<QueryExpr> {
2772 match expr {
2773 QueryExpr::Table(table) => self
2774 .resolve_table_expr_subqueries(table, frame)
2775 .map(QueryExpr::Table),
2776 QueryExpr::Join(mut join) => {
2777 join.left = Box::new(self.resolve_select_expr_subqueries(*join.left, frame)?);
2778 join.right = Box::new(self.resolve_select_expr_subqueries(*join.right, frame)?);
2779 Ok(QueryExpr::Join(join))
2780 }
2781 other => Ok(other),
2782 }
2783 }
2784
2785 fn resolve_expr_subqueries(
2786 &self,
2787 expr: crate::storage::query::ast::Expr,
2788 outer_scopes: &[String],
2789 frame: &dyn super::statement_frame::ReadFrame,
2790 ) -> RedDBResult<crate::storage::query::ast::Expr> {
2791 use crate::storage::query::ast::Expr;
2792
2793 match expr {
2794 Expr::Subquery { query, span } => {
2795 let values = self.execute_expr_subquery_values(query, outer_scopes, frame)?;
2796 if values.len() > 1 {
2797 return Err(RedDBError::Query(
2798 "scalar subquery returned more than one row".to_string(),
2799 ));
2800 }
2801 Ok(Expr::Literal {
2802 value: values.into_iter().next().unwrap_or(Value::Null),
2803 span,
2804 })
2805 }
2806 Expr::BinaryOp { op, lhs, rhs, span } => Ok(Expr::BinaryOp {
2807 op,
2808 lhs: Box::new(self.resolve_expr_subqueries(*lhs, outer_scopes, frame)?),
2809 rhs: Box::new(self.resolve_expr_subqueries(*rhs, outer_scopes, frame)?),
2810 span,
2811 }),
2812 Expr::UnaryOp { op, operand, span } => Ok(Expr::UnaryOp {
2813 op,
2814 operand: Box::new(self.resolve_expr_subqueries(*operand, outer_scopes, frame)?),
2815 span,
2816 }),
2817 Expr::Cast {
2818 inner,
2819 target,
2820 span,
2821 } => Ok(Expr::Cast {
2822 inner: Box::new(self.resolve_expr_subqueries(*inner, outer_scopes, frame)?),
2823 target,
2824 span,
2825 }),
2826 Expr::FunctionCall { name, args, span } => {
2827 let args = args
2828 .into_iter()
2829 .map(|arg| self.resolve_expr_subqueries(arg, outer_scopes, frame))
2830 .collect::<RedDBResult<Vec<_>>>()?;
2831 Ok(Expr::FunctionCall { name, args, span })
2832 }
2833 Expr::Case {
2834 branches,
2835 else_,
2836 span,
2837 } => {
2838 let branches = branches
2839 .into_iter()
2840 .map(|(cond, value)| {
2841 Ok((
2842 self.resolve_expr_subqueries(cond, outer_scopes, frame)?,
2843 self.resolve_expr_subqueries(value, outer_scopes, frame)?,
2844 ))
2845 })
2846 .collect::<RedDBResult<Vec<_>>>()?;
2847 let else_ = else_
2848 .map(|expr| self.resolve_expr_subqueries(*expr, outer_scopes, frame))
2849 .transpose()?
2850 .map(Box::new);
2851 Ok(Expr::Case {
2852 branches,
2853 else_,
2854 span,
2855 })
2856 }
2857 Expr::IsNull {
2858 operand,
2859 negated,
2860 span,
2861 } => Ok(Expr::IsNull {
2862 operand: Box::new(self.resolve_expr_subqueries(*operand, outer_scopes, frame)?),
2863 negated,
2864 span,
2865 }),
2866 Expr::InList {
2867 target,
2868 values,
2869 negated,
2870 span,
2871 } => {
2872 let target =
2873 Box::new(self.resolve_expr_subqueries(*target, outer_scopes, frame)?);
2874 let mut resolved = Vec::new();
2875 for value in values {
2876 if let Expr::Subquery { query, .. } = value {
2877 resolved.extend(
2878 self.execute_expr_subquery_values(query, outer_scopes, frame)?
2879 .into_iter()
2880 .map(Expr::lit),
2881 );
2882 } else {
2883 resolved.push(self.resolve_expr_subqueries(value, outer_scopes, frame)?);
2884 }
2885 }
2886 Ok(Expr::InList {
2887 target,
2888 values: resolved,
2889 negated,
2890 span,
2891 })
2892 }
2893 Expr::Between {
2894 target,
2895 low,
2896 high,
2897 negated,
2898 span,
2899 } => Ok(Expr::Between {
2900 target: Box::new(self.resolve_expr_subqueries(*target, outer_scopes, frame)?),
2901 low: Box::new(self.resolve_expr_subqueries(*low, outer_scopes, frame)?),
2902 high: Box::new(self.resolve_expr_subqueries(*high, outer_scopes, frame)?),
2903 negated,
2904 span,
2905 }),
2906 other => Ok(other),
2907 }
2908 }
2909
2910 fn execute_expr_subquery_values(
2911 &self,
2912 subquery: crate::storage::query::ast::ExprSubquery,
2913 outer_scopes: &[String],
2914 frame: &dyn super::statement_frame::ReadFrame,
2915 ) -> RedDBResult<Vec<Value>> {
2916 let query = *subquery.query;
2917 if query_references_outer_scope(&query, outer_scopes) {
2918 return Err(RedDBError::Query(
2919 "NOT_YET_SUPPORTED: correlated subqueries are not supported yet; track follow-up issue #470-correlated-subqueries".to_string(),
2920 ));
2921 }
2922 let query = self.rewrite_view_refs(query);
2923 let query = self.resolve_select_expr_subqueries(query, frame)?;
2924 let query = self.authorize_relational_select_expr(query, frame)?;
2925 let result = match query {
2926 QueryExpr::Table(table) => {
2927 execute_runtime_table_query(&self.inner.db, &table, Some(&self.inner.index_store))?
2928 }
2929 QueryExpr::Join(join) => execute_runtime_join_query(&self.inner.db, &join)?,
2930 other => {
2931 return Err(RedDBError::Query(format!(
2932 "expression subquery must be a SELECT query, got {}",
2933 query_expr_name(&other)
2934 )))
2935 }
2936 };
2937 first_column_values(result)
2938 }
2939
2940 fn dispatch_expr(
2941 &self,
2942 expr: QueryExpr,
2943 query_str: &str,
2944 mode: QueryMode,
2945 ) -> RedDBResult<RuntimeQueryResult> {
2946 let statement = query_expr_name(&expr);
2947 match expr {
2948 QueryExpr::Graph(_) | QueryExpr::Path(_) => {
2949 Err(RedDBError::Query(
2951 "graph queries cannot be used as prepared statements".to_string(),
2952 ))
2953 }
2954 QueryExpr::Table(table) => {
2955 let scope = self.ai_scope();
2956 let table = self.resolve_table_expr_subqueries(
2957 table,
2958 &scope as &dyn super::statement_frame::ReadFrame,
2959 )?;
2960 if let Some(TableSource::Function {
2964 name,
2965 args,
2966 named_args,
2967 }) = table.source.clone()
2968 {
2969 return Ok(RuntimeQueryResult {
2970 query: query_str.to_string(),
2971 mode,
2972 statement,
2973 engine: "runtime-graph-tvf",
2974 result: self.execute_table_function(&name, &args, &named_args)?,
2975 affected_rows: 0,
2976 statement_type: "select",
2977 bookmark: None,
2978 notice: None,
2979 });
2980 }
2981 if let Some(TableSource::InlineGraphFunction {
2985 name,
2986 nodes,
2987 edges,
2988 named_args,
2989 }) = table.source.clone()
2990 {
2991 return Ok(RuntimeQueryResult {
2992 query: query_str.to_string(),
2993 mode,
2994 statement,
2995 engine: "runtime-graph-tvf-inline",
2996 result: self.execute_inline_graph_function(
2997 &name,
2998 &nodes,
2999 &edges,
3000 &named_args,
3001 )?,
3002 affected_rows: 0,
3003 statement_type: "select",
3004 bookmark: None,
3005 notice: None,
3006 });
3007 }
3008 if super::red_schema::is_virtual_table(&table.table) {
3009 return Ok(RuntimeQueryResult {
3010 query: query_str.to_string(),
3011 mode,
3012 statement,
3013 engine: "runtime-red-schema",
3014 result: super::red_schema::red_query(
3015 self,
3016 &table.table,
3017 &table,
3018 &scope as &dyn super::statement_frame::ReadFrame,
3019 )?,
3020 affected_rows: 0,
3021 statement_type: "select",
3022 bookmark: None,
3023 notice: None,
3024 });
3025 }
3026 if let Some(view_result) = self.try_resolve_analytics_view(
3028 &table,
3029 &scope as &dyn super::statement_frame::ReadFrame,
3030 )? {
3031 return Ok(RuntimeQueryResult {
3032 query: query_str.to_string(),
3033 mode,
3034 statement,
3035 engine: "runtime-graph-analytics-view",
3036 result: view_result,
3037 affected_rows: 0,
3038 statement_type: "select",
3039 bookmark: None,
3040 notice: None,
3041 });
3042 }
3043 let Some(table_with_rls) = self.authorize_relational_table_select(
3044 table,
3045 &scope as &dyn super::statement_frame::ReadFrame,
3046 )?
3047 else {
3048 return Ok(RuntimeQueryResult {
3049 query: query_str.to_string(),
3050 mode,
3051 statement,
3052 engine: "runtime-table-rls",
3053 result: crate::storage::query::unified::UnifiedResult::empty(),
3054 affected_rows: 0,
3055 statement_type: "select",
3056 bookmark: None,
3057 notice: None,
3058 });
3059 };
3060 Ok(RuntimeQueryResult {
3061 query: query_str.to_string(),
3062 mode,
3063 statement,
3064 engine: "runtime-table",
3065 result: execute_runtime_table_query(
3066 &self.inner.db,
3067 &table_with_rls,
3068 Some(&self.inner.index_store),
3069 )?,
3070 affected_rows: 0,
3071 statement_type: "select",
3072 bookmark: None,
3073 notice: None,
3074 })
3075 }
3076 QueryExpr::Join(join) => {
3077 let scope = self.ai_scope();
3078 let Some(join_with_rls) = self.authorize_relational_join_select(
3079 join,
3080 &scope as &dyn super::statement_frame::ReadFrame,
3081 )?
3082 else {
3083 return Ok(RuntimeQueryResult {
3084 query: query_str.to_string(),
3085 mode,
3086 statement,
3087 engine: "runtime-join-rls",
3088 result: crate::storage::query::unified::UnifiedResult::empty(),
3089 affected_rows: 0,
3090 statement_type: "select",
3091 bookmark: None,
3092 notice: None,
3093 });
3094 };
3095 Ok(RuntimeQueryResult {
3096 query: query_str.to_string(),
3097 mode,
3098 statement,
3099 engine: "runtime-join",
3100 result: execute_runtime_join_query(&self.inner.db, &join_with_rls)?,
3101 affected_rows: 0,
3102 statement_type: "select",
3103 bookmark: None,
3104 notice: None,
3105 })
3106 }
3107 QueryExpr::Vector(vector) => Ok(RuntimeQueryResult {
3108 query: query_str.to_string(),
3109 mode,
3110 statement,
3111 engine: "runtime-vector",
3112 result: execute_runtime_vector_query(&self.inner.db, &vector)?,
3113 affected_rows: 0,
3114 statement_type: "select",
3115 bookmark: None,
3116 notice: None,
3117 }),
3118 QueryExpr::Hybrid(hybrid) => Ok(RuntimeQueryResult {
3119 query: query_str.to_string(),
3120 mode,
3121 statement,
3122 engine: "runtime-hybrid",
3123 result: execute_runtime_hybrid_query(&self.inner.db, &hybrid)?,
3124 affected_rows: 0,
3125 statement_type: "select",
3126 bookmark: None,
3127 notice: None,
3128 }),
3129 QueryExpr::Insert(ref insert) if super::red_schema::is_virtual_table(&insert.table) => {
3130 Err(RedDBError::Query(
3131 super::red_schema::READ_ONLY_ERROR.to_string(),
3132 ))
3133 }
3134 QueryExpr::Update(ref update) if super::red_schema::is_virtual_table(&update.table) => {
3135 Err(RedDBError::Query(
3136 super::red_schema::READ_ONLY_ERROR.to_string(),
3137 ))
3138 }
3139 QueryExpr::Delete(ref delete) if super::red_schema::is_virtual_table(&delete.table) => {
3140 Err(RedDBError::Query(
3141 super::red_schema::READ_ONLY_ERROR.to_string(),
3142 ))
3143 }
3144 QueryExpr::Insert(ref insert) => self
3145 .with_deferred_store_wal_for_dml(self.insert_may_emit_events(insert), || {
3146 self.execute_insert(query_str, insert)
3147 }),
3148 QueryExpr::Update(ref update) => self
3149 .with_deferred_store_wal_for_dml(self.update_may_emit_events(update), || {
3150 self.execute_update(query_str, update)
3151 }),
3152 QueryExpr::Delete(ref delete) => self
3153 .with_deferred_store_wal_for_dml(self.delete_may_emit_events(delete), || {
3154 self.execute_delete(query_str, delete)
3155 }),
3156 QueryExpr::SearchCommand(ref cmd) => self.execute_search_command(query_str, cmd),
3157 QueryExpr::Ask(ref ask) => self.execute_ask(query_str, ask),
3158 _ => Err(RedDBError::Query(format!(
3159 "prepared-statement execution does not support {statement} statements"
3160 ))),
3161 }
3162 }
3163
3164 fn execute_table_function(
3171 &self,
3172 name: &str,
3173 args: &[String],
3174 named_args: &[(String, f64)],
3175 ) -> RedDBResult<crate::storage::query::unified::UnifiedResult> {
3176 if name.eq_ignore_ascii_case("red.diff") {
3177 return self.execute_vcs_diff_tvf(args, named_args);
3178 }
3179 if !is_graph_tvf_name(name) {
3180 return Err(RedDBError::Query(format!("unknown table function: {name}")));
3181 }
3182 if args.len() != 1 {
3184 return Err(RedDBError::Query(format!(
3185 "table function '{name}' takes exactly 1 graph argument, got {}",
3186 args.len()
3187 )));
3188 }
3189
3190 let (nodes, edges) = self.materialize_whole_graph_abstract()?;
3195 self.dispatch_graph_algorithm(name, nodes, edges, named_args)
3196 }
3197
3198 pub(crate) fn revive_versioned_updates_since(&self, conn_id: u64, stamper_xid: u64) -> usize {
3199 let mut guard = self.inner.pending_versioned_updates.write();
3200 let Some(pending) = guard.get_mut(&conn_id) else {
3201 return 0;
3202 };
3203
3204 let store = self.inner.db.store();
3205 let mut reverted = 0usize;
3206 pending.retain(|(collection, old_id, new_id, xid, previous_xmax)| {
3207 if *xid < stamper_xid {
3208 return true;
3209 }
3210 if let Some(manager) = store.get_collection(collection) {
3211 if let Some(mut old) = manager.get(*old_id) {
3212 if old.xmax == *xid {
3213 old.set_xmax(*previous_xmax);
3214 let _ = manager.update(old);
3215 }
3216 }
3217 }
3218 let _ = store.delete_batch(collection, &[*new_id]);
3219 reverted += 1;
3220 false
3221 });
3222 if pending.is_empty() {
3223 guard.remove(&conn_id);
3224 }
3225 reverted
3226 }
3227
3228 fn explain_as_rows(&self, raw_query: &str, inner_sql: &str) -> RedDBResult<RuntimeQueryResult> {
3236 let explain = self.explain_query(inner_sql)?;
3237
3238 let columns = vec![
3239 "op".to_string(),
3240 "source".to_string(),
3241 "estimated_rows".to_string(),
3242 "estimated_cost".to_string(),
3243 "depth".to_string(),
3244 ];
3245
3246 let mut records: Vec<crate::storage::query::unified::UnifiedRecord> = Vec::new();
3247
3248 for name in &explain.cte_materializations {
3254 use std::sync::Arc;
3255 let mut rec = crate::storage::query::unified::UnifiedRecord::default();
3256 rec.set_arc(Arc::from("op"), Value::text("CteScan".to_string()));
3257 rec.set_arc(Arc::from("source"), Value::text(name.clone()));
3258 rec.set_arc(Arc::from("estimated_rows"), Value::Float(0.0));
3259 rec.set_arc(Arc::from("estimated_cost"), Value::Float(0.0));
3260 rec.set_arc(Arc::from("depth"), Value::Integer(0));
3261 records.push(rec);
3262 }
3263
3264 walk_plan_node(&explain.logical_plan.root, 0, &mut records);
3265
3266 let result = crate::storage::query::unified::UnifiedResult {
3267 columns,
3268 records,
3269 stats: Default::default(),
3270 pre_serialized_json: None,
3271 };
3272
3273 Ok(RuntimeQueryResult {
3274 query: raw_query.to_string(),
3275 mode: explain.mode,
3276 statement: "explain",
3277 engine: "runtime-explain",
3278 result,
3279 affected_rows: 0,
3280 statement_type: "select",
3281 bookmark: None,
3282 notice: None,
3283 })
3284 }
3285
3286 fn explain_analyze_as_rows(
3287 &self,
3288 raw_query: &str,
3289 inner_sql: &str,
3290 ) -> RedDBResult<RuntimeQueryResult> {
3291 if !starts_with_dml_keyword(inner_sql) {
3292 return Err(RedDBError::Query(
3293 "EXPLAIN ANALYZE currently supports INSERT, UPDATE, and DELETE".to_string(),
3294 ));
3295 }
3296
3297 let explain = self.explain_query(inner_sql)?;
3298 let conn_id = current_connection_id();
3299 if self.inner.tx_contexts.read().contains_key(&conn_id) {
3300 return Err(RedDBError::Query(
3301 "EXPLAIN ANALYZE requires no active transaction".to_string(),
3302 ));
3303 }
3304
3305 self.execute_query_inner("BEGIN")?;
3306 let started = std::time::Instant::now();
3307 let execution = self.execute_query_inner(inner_sql);
3308 let elapsed_ms = started.elapsed().as_secs_f64() * 1000.0;
3309 let rollback = self.execute_query_inner("ROLLBACK");
3310
3311 let affected_rows = match execution {
3312 Ok(result) => result.affected_rows,
3313 Err(err) => {
3314 rollback?;
3315 return Err(err);
3316 }
3317 };
3318 rollback?;
3319
3320 let columns = vec![
3321 "op".to_string(),
3322 "source".to_string(),
3323 "estimated_rows".to_string(),
3324 "estimated_cost".to_string(),
3325 "actual_rows".to_string(),
3326 "actual_ms".to_string(),
3327 "depth".to_string(),
3328 ];
3329 let mut records = Vec::new();
3330 walk_analyze_plan_node(
3331 &explain.logical_plan.root,
3332 0,
3333 affected_rows,
3334 elapsed_ms,
3335 &mut records,
3336 );
3337
3338 let result = crate::storage::query::unified::UnifiedResult {
3339 columns,
3340 records,
3341 stats: Default::default(),
3342 pre_serialized_json: None,
3343 };
3344
3345 Ok(RuntimeQueryResult {
3346 query: raw_query.to_string(),
3347 mode: explain.mode,
3348 statement: "explain_analyze",
3349 engine: "runtime-explain-analyze",
3350 result,
3351 affected_rows: 0,
3352 statement_type: "select",
3353 bookmark: None,
3354 notice: None,
3355 })
3356 }
3357}
3358
3359fn strip_explain_analyze_prefix(sql: &str) -> Option<&str> {
3360 let rest = strip_keyword_ci(sql.trim_start(), "EXPLAIN")?.trim_start();
3361 Some(strip_keyword_ci(rest, "ANALYZE")?.trim_start()).filter(|inner| !inner.is_empty())
3362}
3363
3364fn strip_keyword_ci<'a>(sql: &'a str, keyword: &str) -> Option<&'a str> {
3365 if sql.len() < keyword.len() {
3366 return None;
3367 }
3368 let (head, rest) = sql.split_at(keyword.len());
3369 if !head.eq_ignore_ascii_case(keyword) {
3370 return None;
3371 }
3372 if rest
3373 .chars()
3374 .next()
3375 .is_some_and(|ch| !ch.is_ascii_whitespace())
3376 {
3377 return None;
3378 }
3379 Some(rest)
3380}
3381
3382fn starts_with_dml_keyword(sql: &str) -> bool {
3383 let trimmed = sql.trim_start();
3384 let head_end = trimmed
3385 .find(|ch: char| ch.is_ascii_whitespace())
3386 .unwrap_or(trimmed.len());
3387 let head = &trimmed[..head_end];
3388 head.eq_ignore_ascii_case("INSERT")
3389 || head.eq_ignore_ascii_case("UPDATE")
3390 || head.eq_ignore_ascii_case("DELETE")
3391}
3392
3393fn walk_plan_node(
3394 node: &crate::storage::query::planner::CanonicalLogicalNode,
3395 depth: usize,
3396 out: &mut Vec<crate::storage::query::unified::UnifiedRecord>,
3397) {
3398 use std::sync::Arc;
3399 let mut rec = crate::storage::query::unified::UnifiedRecord::default();
3400 rec.set_arc(Arc::from("op"), Value::text(node.operator.clone()));
3401 rec.set_arc(
3402 Arc::from("source"),
3403 node.source.clone().map(Value::text).unwrap_or(Value::Null),
3404 );
3405 rec.set_arc(
3406 Arc::from("estimated_rows"),
3407 Value::Float(node.estimated_rows),
3408 );
3409 rec.set_arc(
3410 Arc::from("estimated_cost"),
3411 Value::Float(node.operator_cost),
3412 );
3413 rec.set_arc(Arc::from("depth"), Value::Integer(depth as i64));
3414 out.push(rec);
3415 for child in &node.children {
3416 walk_plan_node(child, depth + 1, out);
3417 }
3418}
3419
3420fn walk_analyze_plan_node(
3421 node: &crate::storage::query::planner::CanonicalLogicalNode,
3422 depth: usize,
3423 affected_rows: u64,
3424 elapsed_ms: f64,
3425 out: &mut Vec<crate::storage::query::unified::UnifiedRecord>,
3426) {
3427 use std::sync::Arc;
3428 let mut rec = crate::storage::query::unified::UnifiedRecord::default();
3429 rec.set_arc(Arc::from("op"), Value::text(node.operator.clone()));
3430 rec.set_arc(
3431 Arc::from("source"),
3432 node.source.clone().map(Value::text).unwrap_or(Value::Null),
3433 );
3434 rec.set_arc(
3435 Arc::from("estimated_rows"),
3436 Value::Float(node.estimated_rows),
3437 );
3438 rec.set_arc(
3439 Arc::from("estimated_cost"),
3440 Value::Float(node.operator_cost),
3441 );
3442 rec.set_arc(
3443 Arc::from("actual_rows"),
3444 Value::UnsignedInteger(if depth == 0 { affected_rows } else { 0 }),
3445 );
3446 rec.set_arc(
3447 Arc::from("actual_ms"),
3448 Value::Float(if depth == 0 { elapsed_ms } else { 0.0 }),
3449 );
3450 rec.set_arc(Arc::from("depth"), Value::Integer(depth as i64));
3451 out.push(rec);
3452
3453 for child in &node.children {
3454 walk_analyze_plan_node(child, depth + 1, 0, 0.0, out);
3455 }
3456}
3457
3458#[cfg(test)]
3459mod inline_graph_tvf_tests {
3460 use super::*;
3461
3462 fn scopes_for(sql: &str) -> HashSet<String> {
3463 let expr = crate::storage::query::parser::parse(sql)
3464 .expect("parse")
3465 .query;
3466 query_expr_result_cache_scopes(&expr)
3467 }
3468
3469 #[test]
3470 fn inline_tvf_cache_scopes_include_source_collections() {
3471 let scopes = scopes_for(
3475 "SELECT * FROM components(nodes => (SELECT id FROM hosts), edges => (SELECT src, dst FROM links))",
3476 );
3477 assert!(scopes.contains("hosts"), "nodes source scoped: {scopes:?}");
3478 assert!(scopes.contains("links"), "edges source scoped: {scopes:?}");
3479 }
3480
3481 #[test]
3482 fn graph_collection_tvf_cache_scope_is_graph_argument() {
3483 let scopes = scopes_for("SELECT * FROM components(g)");
3487 assert!(scopes.contains("g"), "collection form scoped: {scopes:?}");
3488 }
3489
3490 #[test]
3491 fn abstract_degree_centrality_counts_undirected_endpoints() {
3492 let nodes = vec!["a".to_string(), "b".to_string(), "c".to_string()];
3493 let edges = vec![
3494 ("a".to_string(), "b".to_string(), 1.0_f32),
3495 ("b".to_string(), "c".to_string(), 1.0_f32),
3496 ];
3497 let degrees = abstract_degree_centrality(&nodes, &edges);
3498 assert_eq!(
3499 degrees,
3500 vec![
3501 ("a".to_string(), 1),
3502 ("b".to_string(), 2),
3503 ("c".to_string(), 1),
3504 ]
3505 );
3506 }
3507}