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::ShowSecrets { ref prefix } => {
1240 let auth_store = self.inner.auth_store.read().clone().ok_or_else(|| {
1241 RedDBError::Query("SHOW SECRET requires an enabled, unsealed vault".to_string())
1242 })?;
1243 if !auth_store.is_vault_backed() {
1244 return Err(RedDBError::Query(
1245 "SHOW SECRET requires an enabled, unsealed vault".to_string(),
1246 ));
1247 }
1248 let mut keys = auth_store.vault_kv_keys();
1249 keys.sort();
1250 let mut result = UnifiedResult::with_columns(vec![
1251 "key".into(),
1252 "value".into(),
1253 "status".into(),
1254 ]);
1255 for key in keys {
1256 if !show_secrets_allows_key(&key) {
1257 continue;
1258 }
1259 if let Some(ref pfx) = prefix {
1260 if !key.starts_with(pfx) {
1261 continue;
1262 }
1263 }
1264 let mut record = UnifiedRecord::new();
1265 record.set("key", Value::text(key));
1266 record.set("value", Value::text("***"));
1267 record.set("status", Value::text("active"));
1268 result.push(record);
1269 }
1270 Ok(RuntimeQueryResult {
1271 query: query.to_string(),
1272 mode,
1273 statement: "show_secrets",
1274 engine: "runtime-secret",
1275 result,
1276 affected_rows: 0,
1277 statement_type: "select",
1278 bookmark: None,
1279 notice: None,
1280 })
1281 }
1282 QueryExpr::ShowConfig {
1284 ref prefix,
1285 as_json,
1286 } => {
1287 let store = self.inner.db.store();
1288 let all_collections = store.list_collections();
1289 if !all_collections.contains(&"red_config".to_string()) {
1290 if as_json {
1291 return Ok(show_config_json_result(
1292 query,
1293 mode,
1294 prefix,
1295 crate::serde_json::Value::Object(crate::serde_json::Map::new()),
1296 ));
1297 }
1298 let result = UnifiedResult::with_columns(vec!["key".into(), "value".into()]);
1299 return Ok(RuntimeQueryResult {
1300 query: query.to_string(),
1301 mode,
1302 statement: "show_config",
1303 engine: "runtime-config",
1304 result,
1305 affected_rows: 0,
1306 statement_type: "select",
1307 bookmark: None,
1308 notice: None,
1309 });
1310 }
1311 let manager = store
1312 .get_collection("red_config")
1313 .ok_or_else(|| RedDBError::NotFound("red_config".to_string()))?;
1314 let entities = manager.query_all(|_| true);
1315 let mut latest = std::collections::BTreeMap::<String, (u64, Value, Value)>::new();
1316 for entity in entities {
1317 if let EntityData::Row(ref row) = entity.data {
1318 if let Some(ref named) = row.named {
1319 let key_val = named.get("key").cloned().unwrap_or(Value::Null);
1320 let val = named.get("value").cloned().unwrap_or(Value::Null);
1321 let key_str = match &key_val {
1322 Value::Text(s) => s.as_ref(),
1323 _ => continue,
1324 };
1325 if let Some(ref pfx) = prefix {
1326 if !key_str.starts_with(pfx.as_str()) {
1327 continue;
1328 }
1329 }
1330 let entity_id = entity.id.raw();
1331 match latest.get(key_str) {
1332 Some((prev_id, _, _)) if *prev_id > entity_id => {}
1333 _ => {
1334 latest.insert(key_str.to_string(), (entity_id, key_val, val));
1335 }
1336 }
1337 }
1338 }
1339 }
1340 if as_json {
1341 let mut tree = crate::serde_json::Value::Object(crate::serde_json::Map::new());
1342 for (key, (_, _, val)) in latest {
1343 let relative = match prefix {
1344 Some(pfx) if key == *pfx => "",
1345 Some(pfx) => key
1346 .strip_prefix(pfx.as_str())
1347 .and_then(|tail| tail.strip_prefix('.'))
1348 .unwrap_or(key.as_str()),
1349 None => key.as_str(),
1350 };
1351 insert_config_json_path(
1352 &mut tree,
1353 relative,
1354 crate::presentation::entity_json::storage_value_to_json(&val),
1355 );
1356 }
1357 return Ok(show_config_json_result(query, mode, prefix, tree));
1358 }
1359 let mut result = UnifiedResult::with_columns(vec!["key".into(), "value".into()]);
1360 for (_, key_val, val) in latest.into_values() {
1361 let mut record = UnifiedRecord::new();
1362 record.set("key", key_val);
1363 record.set("value", val);
1364 result.push(record);
1365 }
1366 Ok(RuntimeQueryResult {
1367 query: query.to_string(),
1368 mode,
1369 statement: "show_config",
1370 engine: "runtime-config",
1371 result,
1372 affected_rows: 0,
1373 statement_type: "select",
1374 bookmark: None,
1375 notice: None,
1376 })
1377 }
1378 QueryExpr::SetTenant(ref value) => {
1384 match value {
1385 Some(id) => set_current_tenant(id.clone()),
1386 None => clear_current_tenant(),
1387 }
1388 Ok(RuntimeQueryResult::ok_message(
1389 query.to_string(),
1390 &match value {
1391 Some(id) => format!("tenant set: {id}"),
1392 None => "tenant cleared".to_string(),
1393 },
1394 "set_tenant",
1395 ))
1396 }
1397 QueryExpr::ShowTenant => {
1398 let mut result = UnifiedResult::with_columns(vec!["tenant".into()]);
1399 let mut record = UnifiedRecord::new();
1400 record.set(
1401 "tenant",
1402 current_tenant().map(Value::text).unwrap_or(Value::Null),
1403 );
1404 result.push(record);
1405 Ok(RuntimeQueryResult {
1406 query: query.to_string(),
1407 mode,
1408 statement: "show_tenant",
1409 engine: "runtime-tenant",
1410 result,
1411 affected_rows: 0,
1412 statement_type: "select",
1413 bookmark: None,
1414 notice: None,
1415 })
1416 }
1417 QueryExpr::TransactionControl(ref ctl) => {
1429 use crate::storage::query::ast::TxnControl;
1430 use crate::storage::transaction::snapshot::{TxnContext, Xid};
1431 use crate::storage::transaction::IsolationLevel;
1432
1433 let conn_id = current_connection_id();
1438
1439 let (kind, msg) = match ctl {
1440 TxnControl::Begin(requested_isolation) => {
1441 let isolation = requested_isolation
1442 .map(IsolationLevel::from)
1443 .unwrap_or(IsolationLevel::SnapshotIsolation);
1444 let mgr = Arc::clone(&self.inner.snapshot_manager);
1445 let xid = mgr.begin();
1446 if isolation == IsolationLevel::Serializable {
1447 mgr.begin_serializable(xid);
1448 }
1449 let snapshot = mgr.snapshot(xid);
1450 let ctx = TxnContext {
1451 xid,
1452 isolation,
1453 snapshot,
1454 savepoints: Vec::new(),
1455 released_sub_xids: Vec::new(),
1456 };
1457 self.inner.tx_contexts.write().insert(conn_id, ctx);
1458 ("begin", format!("BEGIN — xid={xid} (snapshot isolation)"))
1459 }
1460 TxnControl::Commit => {
1461 self.inner.tx_local_tenants.write().remove(&conn_id);
1463 let ctx = self.inner.tx_contexts.write().remove(&conn_id);
1464 match ctx {
1465 Some(ctx) => {
1466 let mut own_xids = std::collections::HashSet::new();
1467 own_xids.insert(ctx.xid);
1468 for (_, sub) in &ctx.savepoints {
1469 own_xids.insert(*sub);
1470 }
1471 for sub in &ctx.released_sub_xids {
1472 own_xids.insert(*sub);
1473 }
1474 if let Err(err) = self.check_table_row_write_conflicts(
1475 conn_id,
1476 &ctx.snapshot,
1477 &own_xids,
1478 ctx.isolation,
1479 ) {
1480 for (_, sub) in &ctx.savepoints {
1481 self.inner.snapshot_manager.rollback(*sub);
1482 }
1483 for sub in &ctx.released_sub_xids {
1484 self.inner.snapshot_manager.rollback(*sub);
1485 }
1486 self.inner.snapshot_manager.rollback(ctx.xid);
1487 self.revive_pending_versioned_updates(conn_id);
1488 self.revive_pending_tombstones(conn_id);
1489 self.discard_pending_kv_watch_events(conn_id);
1490 self.discard_pending_queue_wakes(conn_id);
1491 self.discard_pending_store_wal_actions(conn_id);
1492 self.release_pending_claim_locks(conn_id);
1493 return Err(err);
1494 }
1495 if let Err(err) = self.check_queue_dedup_write_conflicts(
1496 conn_id,
1497 &ctx.snapshot,
1498 &own_xids,
1499 ) {
1500 for (_, sub) in &ctx.savepoints {
1501 self.inner.snapshot_manager.rollback(*sub);
1502 }
1503 for sub in &ctx.released_sub_xids {
1504 self.inner.snapshot_manager.rollback(*sub);
1505 }
1506 self.inner.snapshot_manager.rollback(ctx.xid);
1507 self.revive_pending_versioned_updates(conn_id);
1508 self.revive_pending_tombstones(conn_id);
1509 self.discard_pending_queue_dedup(conn_id);
1510 self.discard_pending_kv_watch_events(conn_id);
1511 self.discard_pending_queue_wakes(conn_id);
1512 self.discard_pending_store_wal_actions(conn_id);
1513 self.release_pending_claim_locks(conn_id);
1514 return Err(err);
1515 }
1516 self.restore_pending_write_stamps(conn_id);
1517 if let Err(err) = self.flush_pending_store_wal_actions(conn_id) {
1518 for (_, sub) in &ctx.savepoints {
1519 self.inner.snapshot_manager.rollback(*sub);
1520 }
1521 for sub in &ctx.released_sub_xids {
1522 self.inner.snapshot_manager.rollback(*sub);
1523 }
1524 self.inner.snapshot_manager.rollback(ctx.xid);
1525 self.revive_pending_versioned_updates(conn_id);
1526 self.revive_pending_tombstones(conn_id);
1527 self.discard_pending_queue_dedup(conn_id);
1528 self.discard_pending_kv_watch_events(conn_id);
1529 self.discard_pending_queue_wakes(conn_id);
1530 self.release_pending_claim_locks(conn_id);
1531 return Err(err);
1532 }
1533 for (_, sub) in &ctx.savepoints {
1539 self.inner.snapshot_manager.commit(*sub);
1540 }
1541 for sub in &ctx.released_sub_xids {
1542 self.inner.snapshot_manager.commit(*sub);
1543 }
1544 self.inner.snapshot_manager.commit(ctx.xid);
1545 self.finalize_pending_versioned_updates(conn_id);
1546 self.finalize_pending_tombstones(conn_id);
1547 self.finalize_pending_queue_dedup(conn_id);
1548 self.finalize_pending_kv_watch_events(conn_id);
1549 self.finalize_pending_queue_wakes(conn_id);
1550 self.release_pending_claim_locks(conn_id);
1551 ("commit", format!("COMMIT — xid={} committed", ctx.xid))
1552 }
1553 None => (
1554 "commit",
1555 "COMMIT outside transaction — no-op (autocommit)".to_string(),
1556 ),
1557 }
1558 }
1559 TxnControl::Rollback => {
1560 self.inner.tx_local_tenants.write().remove(&conn_id);
1561 let ctx = self.inner.tx_contexts.write().remove(&conn_id);
1562 match ctx {
1563 Some(ctx) => {
1564 for (_, sub) in &ctx.savepoints {
1567 self.inner.snapshot_manager.rollback(*sub);
1568 }
1569 for sub in &ctx.released_sub_xids {
1570 self.inner.snapshot_manager.rollback(*sub);
1571 }
1572 self.inner.snapshot_manager.rollback(ctx.xid);
1573 self.revive_pending_versioned_updates(conn_id);
1577 self.revive_pending_tombstones(conn_id);
1578 self.discard_pending_queue_dedup(conn_id);
1579 self.discard_pending_kv_watch_events(conn_id);
1580 self.discard_pending_queue_wakes(conn_id);
1581 self.discard_pending_store_wal_actions(conn_id);
1582 self.release_pending_claim_locks(conn_id);
1583 ("rollback", format!("ROLLBACK — xid={} aborted", ctx.xid))
1584 }
1585 None => (
1586 "rollback",
1587 "ROLLBACK outside transaction — no-op (autocommit)".to_string(),
1588 ),
1589 }
1590 }
1591 TxnControl::Savepoint(name) => {
1598 let mgr = Arc::clone(&self.inner.snapshot_manager);
1599 let mut guard = self.inner.tx_contexts.write();
1600 match guard.get_mut(&conn_id) {
1601 Some(ctx) => {
1602 let sub = mgr.begin();
1603 ctx.savepoints.push((name.clone(), sub));
1604 ("savepoint", format!("SAVEPOINT {name} — sub_xid={sub}"))
1605 }
1606 None => (
1607 "savepoint",
1608 "SAVEPOINT outside transaction — no-op".to_string(),
1609 ),
1610 }
1611 }
1612 TxnControl::ReleaseSavepoint(name) => {
1613 let mut guard = self.inner.tx_contexts.write();
1614 match guard.get_mut(&conn_id) {
1615 Some(ctx) => {
1616 let pos = ctx
1617 .savepoints
1618 .iter()
1619 .position(|(n, _)| n == name)
1620 .ok_or_else(|| {
1621 RedDBError::Internal(format!(
1622 "savepoint {name} does not exist"
1623 ))
1624 })?;
1625 let released = ctx.savepoints.len() - pos;
1633 let popped: Vec<Xid> = ctx
1634 .savepoints
1635 .split_off(pos)
1636 .into_iter()
1637 .map(|(_, x)| x)
1638 .collect();
1639 ctx.released_sub_xids.extend(popped);
1640 (
1641 "release_savepoint",
1642 format!("RELEASE SAVEPOINT {name} — {released} level(s)"),
1643 )
1644 }
1645 None => (
1646 "release_savepoint",
1647 "RELEASE outside transaction — no-op".to_string(),
1648 ),
1649 }
1650 }
1651 TxnControl::RollbackToSavepoint(name) => {
1652 let mgr = Arc::clone(&self.inner.snapshot_manager);
1653 let drop_result: Option<(Xid, Vec<Xid>)> = {
1658 let mut guard = self.inner.tx_contexts.write();
1659 if let Some(ctx) = guard.get_mut(&conn_id) {
1660 let pos = ctx
1661 .savepoints
1662 .iter()
1663 .position(|(n, _)| n == name)
1664 .ok_or_else(|| {
1665 RedDBError::Internal(format!(
1666 "savepoint {name} does not exist"
1667 ))
1668 })?;
1669 let savepoint_xid = ctx.savepoints[pos].1;
1670 let aborted: Vec<Xid> = ctx
1671 .savepoints
1672 .split_off(pos)
1673 .into_iter()
1674 .map(|(_, x)| x)
1675 .collect();
1676 Some((savepoint_xid, aborted))
1677 } else {
1678 None
1679 }
1680 };
1681
1682 match drop_result {
1683 Some((savepoint_xid, aborted)) => {
1684 for x in &aborted {
1685 mgr.rollback(*x);
1686 }
1687 let reverted_updates =
1688 self.revive_versioned_updates_since(conn_id, savepoint_xid);
1689 let revived = self.revive_tombstones_since(conn_id, savepoint_xid);
1690 (
1691 "rollback_to_savepoint",
1692 format!(
1693 "ROLLBACK TO SAVEPOINT {name} — aborted {} sub_xid(s), reverted {reverted_updates} update(s), revived {revived} tombstone(s)",
1694 aborted.len(),
1695 ),
1696 )
1697 }
1698 None => (
1699 "rollback_to_savepoint",
1700 "ROLLBACK TO outside transaction — no-op".to_string(),
1701 ),
1702 }
1703 }
1704 };
1705 Ok(RuntimeQueryResult::ok_message(
1706 query.to_string(),
1707 &msg,
1708 kind,
1709 ))
1710 }
1711 QueryExpr::CreateSchema(ref q) => {
1724 let store = self.inner.db.store();
1725 let key = format!("schema.{}", q.name);
1726 if store.get_config(&key).is_some() {
1727 if q.if_not_exists {
1728 return Ok(RuntimeQueryResult::ok_message(
1729 query.to_string(),
1730 &format!("schema {} already exists — skipped", q.name),
1731 "create_schema",
1732 ));
1733 }
1734 return Err(RedDBError::Internal(format!(
1735 "schema {} already exists",
1736 q.name
1737 )));
1738 }
1739 store.set_config_tree(&key, &crate::serde_json::Value::Bool(true));
1740 Ok(RuntimeQueryResult::ok_message(
1741 query.to_string(),
1742 &format!("schema {} created", q.name),
1743 "create_schema",
1744 ))
1745 }
1746 QueryExpr::DropSchema(ref q) => {
1747 let store = self.inner.db.store();
1748 let key = format!("schema.{}", q.name);
1749 let existed = store.get_config(&key).is_some();
1750 if !existed && !q.if_exists {
1751 return Err(RedDBError::Internal(format!(
1752 "schema {} does not exist",
1753 q.name
1754 )));
1755 }
1756 store.set_config_tree(&key, &crate::serde_json::Value::Null);
1758 let suffix = if q.cascade {
1759 " (CASCADE accepted — tables untouched)"
1760 } else {
1761 ""
1762 };
1763 Ok(RuntimeQueryResult::ok_message(
1764 query.to_string(),
1765 &format!("schema {} dropped{}", q.name, suffix),
1766 "drop_schema",
1767 ))
1768 }
1769 QueryExpr::CreateSequence(ref q) => {
1770 let store = self.inner.db.store();
1771 let base = format!("sequence.{}", q.name);
1772 let start_key = format!("{base}.start");
1773 let incr_key = format!("{base}.increment");
1774 let curr_key = format!("{base}.current");
1775 if store.get_config(&start_key).is_some() {
1776 if q.if_not_exists {
1777 return Ok(RuntimeQueryResult::ok_message(
1778 query.to_string(),
1779 &format!("sequence {} already exists — skipped", q.name),
1780 "create_sequence",
1781 ));
1782 }
1783 return Err(RedDBError::Internal(format!(
1784 "sequence {} already exists",
1785 q.name
1786 )));
1787 }
1788 let initial_current = q.start - q.increment;
1791 store.set_config_tree(
1792 &start_key,
1793 &crate::serde_json::Value::Number(q.start as f64),
1794 );
1795 store.set_config_tree(
1796 &incr_key,
1797 &crate::serde_json::Value::Number(q.increment as f64),
1798 );
1799 store.set_config_tree(
1800 &curr_key,
1801 &crate::serde_json::Value::Number(initial_current as f64),
1802 );
1803 Ok(RuntimeQueryResult::ok_message(
1804 query.to_string(),
1805 &format!(
1806 "sequence {} created (start={}, increment={})",
1807 q.name, q.start, q.increment
1808 ),
1809 "create_sequence",
1810 ))
1811 }
1812 QueryExpr::DropSequence(ref q) => {
1813 let store = self.inner.db.store();
1814 let base = format!("sequence.{}", q.name);
1815 let existed = store.get_config(&format!("{base}.start")).is_some();
1816 if !existed && !q.if_exists {
1817 return Err(RedDBError::Internal(format!(
1818 "sequence {} does not exist",
1819 q.name
1820 )));
1821 }
1822 for k in ["start", "increment", "current"] {
1823 store.set_config_tree(&format!("{base}.{k}"), &crate::serde_json::Value::Null);
1824 }
1825 Ok(RuntimeQueryResult::ok_message(
1826 query.to_string(),
1827 &format!("sequence {} dropped", q.name),
1828 "drop_sequence",
1829 ))
1830 }
1831 QueryExpr::CreateView(ref q) => {
1841 let mut views = self.inner.views.write();
1842 if views.contains_key(&q.name) && !q.or_replace {
1843 if q.if_not_exists {
1844 return Ok(RuntimeQueryResult::ok_message(
1845 query.to_string(),
1846 &format!("view {} already exists — skipped", q.name),
1847 "create_view",
1848 ));
1849 }
1850 return Err(RedDBError::Internal(format!(
1851 "view {} already exists",
1852 q.name
1853 )));
1854 }
1855 views.insert(q.name.clone(), Arc::new(q.clone()));
1856 drop(views);
1857
1858 if q.materialized {
1860 use crate::storage::cache::result::{MaterializedViewDef, RefreshPolicy};
1861 let refresh = match q.refresh_every_ms {
1862 Some(ms) => RefreshPolicy::Periodic(std::time::Duration::from_millis(ms)),
1863 None => RefreshPolicy::Manual,
1864 };
1865 let dependencies = collect_table_refs(&q.query);
1866 let def = MaterializedViewDef {
1867 name: q.name.clone(),
1868 query: format!("<parsed view {}>", q.name),
1869 dependencies: dependencies.clone(),
1870 refresh,
1871 retention_duration_ms: q.retention_duration_ms,
1872 };
1873 self.inner.materialized_views.write().register(def);
1874
1875 let descriptor =
1881 crate::runtime::continuous_materialized_view::MaterializedViewDescriptor {
1882 name: q.name.clone(),
1883 source_sql: query.to_string(),
1884 source_collections: dependencies,
1885 refresh_every_ms: q.refresh_every_ms,
1886 retention_duration_ms: q.retention_duration_ms,
1887 };
1888 let store = self.inner.db.store();
1889 crate::runtime::continuous_materialized_view::persist_descriptor(
1890 store.as_ref(),
1891 &descriptor,
1892 )?;
1893
1894 self.ensure_materialized_view_backing(&q.name)?;
1901 }
1902 self.invalidate_plan_cache();
1907 self.invalidate_result_cache();
1908
1909 Ok(RuntimeQueryResult::ok_message(
1910 query.to_string(),
1911 &format!(
1912 "{}view {} created",
1913 if q.materialized { "materialized " } else { "" },
1914 q.name
1915 ),
1916 "create_view",
1917 ))
1918 }
1919 QueryExpr::DropView(ref q) => {
1920 let mut views = self.inner.views.write();
1921 let removed = views.remove(&q.name);
1922 let existed = removed.is_some();
1923 let removed_materialized =
1924 removed.as_ref().map(|v| v.materialized).unwrap_or(false);
1925 drop(views);
1926 if q.materialized || existed {
1927 self.inner.materialized_views.write().remove(&q.name);
1929 let store = self.inner.db.store();
1933 crate::runtime::continuous_materialized_view::remove_by_name(
1934 store.as_ref(),
1935 &q.name,
1936 )?;
1937 }
1938 if removed_materialized || q.materialized {
1942 self.drop_materialized_view_backing(&q.name)?;
1943 }
1944 self.invalidate_plan_cache();
1947 self.invalidate_result_cache();
1948 if !existed && !q.if_exists {
1949 return Err(RedDBError::Internal(format!(
1950 "view {} does not exist",
1951 q.name
1952 )));
1953 }
1954 self.invalidate_plan_cache();
1955 Ok(RuntimeQueryResult::ok_message(
1956 query.to_string(),
1957 &format!("view {} dropped", q.name),
1958 "drop_view",
1959 ))
1960 }
1961 QueryExpr::RefreshMaterializedView(ref q) => {
1962 let view = {
1965 let views = self.inner.views.read();
1966 views.get(&q.name).cloned()
1967 };
1968 let view = match view {
1969 Some(v) => v,
1970 None => {
1971 return Err(RedDBError::Internal(format!(
1972 "view {} does not exist",
1973 q.name
1974 )))
1975 }
1976 };
1977 if !view.materialized {
1978 return Err(RedDBError::Internal(format!(
1979 "view {} is not materialized — REFRESH requires \
1980 CREATE MATERIALIZED VIEW",
1981 q.name
1982 )));
1983 }
1984 let started = std::time::Instant::now();
1986 let now_ms = std::time::SystemTime::now()
1987 .duration_since(std::time::UNIX_EPOCH)
1988 .map(|d| d.as_millis() as u64)
1989 .unwrap_or(0);
1990 match self.execute_query_expr((*view.query).clone()) {
1991 Ok(inner_result) => {
1992 let entities =
1999 view_records_to_entities(&q.name, &inner_result.result.records);
2000 let row_count = entities.len() as u64;
2001 let store = self.inner.db.store();
2002 let serialized_records = match store.refresh_collection(&q.name, entities) {
2003 Ok(records) => records,
2004 Err(err) => {
2005 let duration_ms = started.elapsed().as_millis() as u64;
2006 let msg = err.to_string();
2007 self.inner
2008 .materialized_views
2009 .write()
2010 .record_refresh_failure(
2011 &q.name,
2012 msg.clone(),
2013 duration_ms,
2014 now_ms,
2015 );
2016 return Err(RedDBError::Internal(format!(
2017 "REFRESH MATERIALIZED VIEW {}: {msg}",
2018 q.name
2019 )));
2020 }
2021 };
2022
2023 if let Some(ref primary) = self.inner.db.replication {
2029 let lsn = self.inner.cdc.emit(
2030 crate::replication::cdc::ChangeOperation::Refresh,
2031 &q.name,
2032 0,
2033 "refresh",
2034 );
2035 self.invalidate_result_cache_for_table(&q.name);
2036 let timestamp = std::time::SystemTime::now()
2037 .duration_since(std::time::UNIX_EPOCH)
2038 .unwrap_or_default()
2039 .as_millis() as u64;
2040 let record = ChangeRecord::for_refresh(
2041 lsn,
2042 timestamp,
2043 q.name.clone(),
2044 serialized_records,
2045 )
2046 .with_term(self.current_replication_term());
2047 let encoded = record.encode();
2048 primary.append_logical_record(record.lsn, encoded);
2049 }
2050
2051 let duration_ms = started.elapsed().as_millis() as u64;
2052 let serialized = format!("{:?}", inner_result.result);
2053 self.inner
2054 .materialized_views
2055 .write()
2056 .record_refresh_success(
2057 &q.name,
2058 serialized.into_bytes(),
2059 row_count,
2060 duration_ms,
2061 now_ms,
2062 );
2063 self.invalidate_result_cache();
2068 Ok(RuntimeQueryResult::ok_message(
2069 query.to_string(),
2070 &format!("materialized view {} refreshed", q.name),
2071 "refresh_materialized_view",
2072 ))
2073 }
2074 Err(err) => {
2075 let duration_ms = started.elapsed().as_millis() as u64;
2076 let msg = err.to_string();
2077 self.inner
2078 .materialized_views
2079 .write()
2080 .record_refresh_failure(&q.name, msg.clone(), duration_ms, now_ms);
2081 Err(err)
2082 }
2083 }
2084 }
2085 QueryExpr::CreatePolicy(ref q) => {
2092 let key = (q.table.clone(), q.name.clone());
2093 self.inner
2094 .rls_policies
2095 .write()
2096 .insert(key, Arc::new(q.clone()));
2097 self.invalidate_plan_cache();
2098 self.schema_vocabulary_apply(
2102 crate::runtime::schema_vocabulary::DdlEvent::CreatePolicy {
2103 collection: q.table.clone(),
2104 policy: q.name.clone(),
2105 },
2106 );
2107 Ok(RuntimeQueryResult::ok_message(
2108 query.to_string(),
2109 &format!("policy {} on {} created", q.name, q.table),
2110 "create_policy",
2111 ))
2112 }
2113 QueryExpr::DropPolicy(ref q) => {
2114 let removed = self
2115 .inner
2116 .rls_policies
2117 .write()
2118 .remove(&(q.table.clone(), q.name.clone()))
2119 .is_some();
2120 if !removed && !q.if_exists {
2121 return Err(RedDBError::Internal(format!(
2122 "policy {} on {} does not exist",
2123 q.name, q.table
2124 )));
2125 }
2126 self.invalidate_plan_cache();
2127 self.schema_vocabulary_apply(
2130 crate::runtime::schema_vocabulary::DdlEvent::DropPolicy {
2131 collection: q.table.clone(),
2132 policy: q.name.clone(),
2133 },
2134 );
2135 Ok(RuntimeQueryResult::ok_message(
2136 query.to_string(),
2137 &format!("policy {} on {} dropped", q.name, q.table),
2138 "drop_policy",
2139 ))
2140 }
2141 QueryExpr::CreateServer(ref q) => {
2152 use crate::storage::fdw::FdwOptions;
2153 let registry = Arc::clone(&self.inner.foreign_tables);
2154 if registry.server(&q.name).is_some() {
2155 if q.if_not_exists {
2156 return Ok(RuntimeQueryResult::ok_message(
2157 query.to_string(),
2158 &format!("server {} already exists — skipped", q.name),
2159 "create_server",
2160 ));
2161 }
2162 return Err(RedDBError::Internal(format!(
2163 "server {} already exists",
2164 q.name
2165 )));
2166 }
2167 let mut opts = FdwOptions::new();
2168 for (k, v) in &q.options {
2169 opts.values.insert(k.clone(), v.clone());
2170 }
2171 registry
2172 .create_server(&q.name, &q.wrapper, opts)
2173 .map_err(|e| RedDBError::Internal(e.to_string()))?;
2174 Ok(RuntimeQueryResult::ok_message(
2175 query.to_string(),
2176 &format!("server {} created (wrapper {})", q.name, q.wrapper),
2177 "create_server",
2178 ))
2179 }
2180 QueryExpr::DropServer(ref q) => {
2181 let existed = self.inner.foreign_tables.drop_server(&q.name);
2182 if !existed && !q.if_exists {
2183 return Err(RedDBError::Internal(format!(
2184 "server {} does not exist",
2185 q.name
2186 )));
2187 }
2188 Ok(RuntimeQueryResult::ok_message(
2189 query.to_string(),
2190 &format!(
2191 "server {} dropped{}",
2192 q.name,
2193 if q.cascade { " (cascade)" } else { "" }
2194 ),
2195 "drop_server",
2196 ))
2197 }
2198 QueryExpr::CreateForeignTable(ref q) => {
2199 use crate::storage::fdw::{FdwOptions, ForeignColumn, ForeignTable};
2200 let registry = Arc::clone(&self.inner.foreign_tables);
2201 if registry.foreign_table(&q.name).is_some() {
2202 if q.if_not_exists {
2203 return Ok(RuntimeQueryResult::ok_message(
2204 query.to_string(),
2205 &format!("foreign table {} already exists — skipped", q.name),
2206 "create_foreign_table",
2207 ));
2208 }
2209 return Err(RedDBError::Internal(format!(
2210 "foreign table {} already exists",
2211 q.name
2212 )));
2213 }
2214 let mut opts = FdwOptions::new();
2215 for (k, v) in &q.options {
2216 opts.values.insert(k.clone(), v.clone());
2217 }
2218 let columns: Vec<ForeignColumn> = q
2219 .columns
2220 .iter()
2221 .map(|c| ForeignColumn {
2222 name: c.name.clone(),
2223 data_type: c.data_type.clone(),
2224 not_null: c.not_null,
2225 })
2226 .collect();
2227 registry
2228 .create_foreign_table(ForeignTable {
2229 name: q.name.clone(),
2230 server_name: q.server.clone(),
2231 columns,
2232 options: opts,
2233 })
2234 .map_err(|e| RedDBError::Internal(e.to_string()))?;
2235 self.invalidate_plan_cache();
2236 Ok(RuntimeQueryResult::ok_message(
2237 query.to_string(),
2238 &format!("foreign table {} created (server {})", q.name, q.server),
2239 "create_foreign_table",
2240 ))
2241 }
2242 QueryExpr::DropForeignTable(ref q) => {
2243 let existed = self.inner.foreign_tables.drop_foreign_table(&q.name);
2244 if !existed && !q.if_exists {
2245 return Err(RedDBError::Internal(format!(
2246 "foreign table {} does not exist",
2247 q.name
2248 )));
2249 }
2250 self.invalidate_plan_cache();
2251 Ok(RuntimeQueryResult::ok_message(
2252 query.to_string(),
2253 &format!("foreign table {} dropped", q.name),
2254 "drop_foreign_table",
2255 ))
2256 }
2257 QueryExpr::CopyFrom(ref q) => {
2263 use crate::storage::import::{CsvConfig, CsvImporter};
2264 let store = self.inner.db.store();
2265 let cfg = CsvConfig {
2266 collection: q.table.clone(),
2267 has_header: q.has_header,
2268 delimiter: q.delimiter.map(|c| c as u8).unwrap_or(b','),
2269 ..CsvConfig::default()
2270 };
2271 let importer = CsvImporter::new(cfg);
2272 let stats = importer
2273 .import_file(&q.path, store.as_ref())
2274 .map_err(|e| RedDBError::Internal(format!("COPY failed: {e}")))?;
2275 self.note_table_write(&q.table);
2277 Ok(RuntimeQueryResult::ok_message(
2278 query.to_string(),
2279 &format!(
2280 "COPY imported {} rows into {} ({} errors skipped, {}ms)",
2281 stats.records_imported, q.table, stats.errors_skipped, stats.duration_ms
2282 ),
2283 "copy_from",
2284 ))
2285 }
2286 QueryExpr::MaintenanceCommand(ref cmd) => {
2302 use crate::storage::query::ast::MaintenanceCommand as Mc;
2303 let store = self.inner.db.store();
2304 let (kind, msg) = match cmd {
2305 Mc::Analyze { target } => {
2306 let targets: Vec<String> = match target {
2307 Some(t) => vec![t.clone()],
2308 None => store.list_collections(),
2309 };
2310 for t in &targets {
2311 self.refresh_table_planner_stats(t);
2312 }
2313 (
2314 "analyze",
2315 format!("ANALYZE refreshed stats for {} table(s)", targets.len()),
2316 )
2317 }
2318 Mc::Vacuum { target, full } => {
2319 let targets: Vec<String> = match target {
2320 Some(t) => vec![t.clone()],
2321 None => store.list_collections(),
2322 };
2323 let cutoff_xid = self.mvcc_vacuum_cutoff_xid();
2324 let mut vacuum_stats =
2325 crate::storage::unified::store::MvccVacuumStats::default();
2326 for t in &targets {
2327 let stats = store.vacuum_mvcc_history(t, cutoff_xid).map_err(|e| {
2328 RedDBError::Internal(format!(
2329 "VACUUM MVCC history failed for {t}: {e}"
2330 ))
2331 })?;
2332 if stats.reclaimed_versions > 0 {
2333 self.rebuild_runtime_indexes_for_table(t)?;
2334 }
2335 vacuum_stats.add(&stats);
2336 }
2337 self.inner.snapshot_manager.prune_aborted(cutoff_xid);
2338 for t in &targets {
2340 self.refresh_table_planner_stats(t);
2341 }
2342 let persisted = if *full {
2346 match store.persist() {
2347 Ok(()) => true,
2348 Err(e) => {
2349 return Err(RedDBError::Internal(format!(
2350 "VACUUM FULL persist failed: {e:?}"
2351 )));
2352 }
2353 }
2354 } else {
2355 false
2356 };
2357 self.invalidate_result_cache();
2359 (
2360 "vacuum",
2361 format!(
2362 "VACUUM{} processed {} table(s): scanned_versions={}, retained_versions={}, reclaimed_versions={}, retained_history_versions={}, reclaimed_history_versions={}, retained_tombstones={}, reclaimed_tombstones={}{}",
2363 if *full { " FULL" } else { "" },
2364 targets.len(),
2365 vacuum_stats.scanned_versions,
2366 vacuum_stats.retained_versions,
2367 vacuum_stats.reclaimed_versions,
2368 vacuum_stats.retained_history_versions,
2369 vacuum_stats.reclaimed_history_versions,
2370 vacuum_stats.retained_tombstones,
2371 vacuum_stats.reclaimed_tombstones,
2372 if persisted {
2373 " (pages flushed to disk)"
2374 } else {
2375 ""
2376 }
2377 ),
2378 )
2379 }
2380 };
2381 Ok(RuntimeQueryResult::ok_message(
2382 query.to_string(),
2383 &msg,
2384 kind,
2385 ))
2386 }
2387 QueryExpr::Grant(ref g) => self.execute_grant_statement(query, g),
2394 QueryExpr::Revoke(ref r) => self.execute_revoke_statement(query, r),
2395 QueryExpr::AlterUser(ref a) => self.execute_alter_user_statement(query, a),
2396 QueryExpr::CreateUser(ref u) => self.execute_create_user_statement(query, u),
2397 QueryExpr::CreateIamPolicy { ref id, ref json } => {
2398 self.execute_create_iam_policy(query, id, json)
2399 }
2400 QueryExpr::DropIamPolicy { ref id } => self.execute_drop_iam_policy(query, id),
2401 QueryExpr::AttachPolicy {
2402 ref policy_id,
2403 ref principal,
2404 } => self.execute_attach_policy(query, policy_id, principal),
2405 QueryExpr::DetachPolicy {
2406 ref policy_id,
2407 ref principal,
2408 } => self.execute_detach_policy(query, policy_id, principal),
2409 QueryExpr::ShowPolicies { ref filter } => {
2410 self.execute_show_policies(query, filter.as_ref())
2411 }
2412 QueryExpr::ShowEffectivePermissions {
2413 ref user,
2414 ref resource,
2415 } => self.execute_show_effective_permissions(query, user, resource.as_ref()),
2416 QueryExpr::SimulatePolicy {
2417 ref user,
2418 ref action,
2419 ref resource,
2420 } => self.execute_simulate_policy(query, user, action, resource),
2421 QueryExpr::LintPolicy { ref source } => self.execute_lint_policy(query, source),
2422 QueryExpr::MigratePolicyMode {
2423 ref target,
2424 dry_run,
2425 } => self.execute_migrate_policy_mode(query, target, dry_run),
2426 QueryExpr::CreateMigration(ref q) => self.execute_create_migration(query, q),
2427 QueryExpr::ApplyMigration(ref q) => self.execute_apply_migration(query, q),
2428 QueryExpr::RollbackMigration(ref q) => self.execute_rollback_migration(query, q),
2429 QueryExpr::ExplainMigration(ref q) => self.execute_explain_migration(query, q),
2430 _ => Err(RedDBError::Query(
2431 "unsupported command in runtime dispatcher".to_string(),
2432 )),
2433 };
2434
2435 if !control_event_specs.is_empty() {
2436 let (outcome, reason) = match &query_result {
2437 Ok(_) => (crate::runtime::control_events::Outcome::Allowed, None),
2438 Err(err) => (control_event_outcome_for_error(err), Some(err.to_string())),
2439 };
2440 for spec in &control_event_specs {
2441 self.emit_control_event(
2442 spec.kind,
2443 outcome,
2444 spec.action,
2445 spec.resource.clone(),
2446 reason.clone(),
2447 spec.fields.clone(),
2448 )?;
2449 }
2450 }
2451
2452 if let (Some(plan), Ok(result)) = (&query_audit_plan, &query_result) {
2453 self.emit_query_audit(
2454 query,
2455 plan,
2456 query_audit_started.elapsed().as_millis() as u64,
2457 result,
2458 );
2459 }
2460
2461 let mut query_result = query_result;
2465 if let Ok(ref mut result) = query_result {
2466 if result.statement_type == "select" {
2467 self.apply_secret_decryption(result);
2468 }
2469 }
2470
2471 if let Ok(ref result) = query_result {
2478 frame.write_result_cache(self, result, result_cache_scopes);
2479 }
2480
2481 query_result
2482 }
2483
2484 pub fn execute_query_expr(&self, expr: QueryExpr) -> RedDBResult<RuntimeQueryResult> {
2490 let _config_snapshot_guard = ConfigSnapshotGuard::install(
2491 Arc::clone(&self.inner.db),
2492 self.inner.auth_store.read().clone(),
2493 );
2494 let _secret_store_guard = SecretStoreGuard::install(self.inner.auth_store.read().clone());
2495 let expr = self.rewrite_view_refs(expr);
2499
2500 self.validate_model_operations_before_auth(&expr)?;
2501 if let Err(err) = self.check_query_privilege(&expr) {
2505 return Err(RedDBError::Query(format!("permission denied: {err}")));
2506 }
2507
2508 let statement = query_expr_name(&expr);
2509 let mode = detect_mode(statement);
2510 let query_str = statement;
2511
2512 let result = self.dispatch_expr(expr, query_str, mode)?;
2513 let mut r = result;
2514 if r.statement_type == "select" {
2515 self.apply_secret_decryption(&mut r);
2516 }
2517 Ok(r)
2518 }
2519
2520 pub(super) fn validate_model_operations_before_auth(
2521 &self,
2522 expr: &QueryExpr,
2523 ) -> RedDBResult<()> {
2524 use crate::catalog::CollectionModel;
2525 use crate::runtime::ddl::polymorphic_resolver;
2526 use crate::storage::query::ast::KvCommand;
2527
2528 let system_schema_target = match expr {
2529 QueryExpr::DropTable(q) => Some(q.name.as_str()),
2530 QueryExpr::DropGraph(q) => Some(q.name.as_str()),
2531 QueryExpr::DropVector(q) => Some(q.name.as_str()),
2532 QueryExpr::DropDocument(q) => Some(q.name.as_str()),
2533 QueryExpr::DropKv(q) => Some(q.name.as_str()),
2534 QueryExpr::DropCollection(q) => Some(q.name.as_str()),
2535 QueryExpr::Truncate(q) => Some(q.name.as_str()),
2536 _ => None,
2537 };
2538 if system_schema_target.is_some_and(crate::runtime::impl_ddl::is_system_schema_name) {
2539 return Err(RedDBError::Query("system schema is read-only".to_string()));
2540 }
2541
2542 let expected = match expr {
2543 QueryExpr::DropTable(q) => Some((q.name.as_str(), CollectionModel::Table)),
2544 QueryExpr::DropGraph(q) => Some((q.name.as_str(), CollectionModel::Graph)),
2545 QueryExpr::DropVector(q) => Some((q.name.as_str(), CollectionModel::Vector)),
2546 QueryExpr::DropDocument(q) => Some((q.name.as_str(), CollectionModel::Document)),
2547 QueryExpr::DropKv(q) => Some((q.name.as_str(), q.model)),
2548 QueryExpr::DropCollection(q) => q.model.map(|model| (q.name.as_str(), model)),
2549 QueryExpr::Truncate(q) => q.model.map(|model| (q.name.as_str(), model)),
2550 QueryExpr::KvCommand(cmd) => {
2551 let (collection, model) = match cmd {
2552 KvCommand::Put {
2553 collection, model, ..
2554 }
2555 | KvCommand::Get {
2556 collection, model, ..
2557 }
2558 | KvCommand::Incr {
2559 collection, model, ..
2560 }
2561 | KvCommand::Cas {
2562 collection, model, ..
2563 }
2564 | KvCommand::List {
2565 collection, model, ..
2566 }
2567 | KvCommand::Delete {
2568 collection, model, ..
2569 } => (collection.as_str(), *model),
2570 KvCommand::Rotate { collection, .. }
2571 | KvCommand::History { collection, .. }
2572 | KvCommand::Purge { collection, .. } => {
2573 (collection.as_str(), CollectionModel::Vault)
2574 }
2575 KvCommand::InvalidateTags { collection, .. } => {
2576 (collection.as_str(), CollectionModel::Kv)
2577 }
2578 KvCommand::Watch {
2579 collection, model, ..
2580 } => (collection.as_str(), *model),
2581 KvCommand::Unseal { collection, .. } => {
2582 (collection.as_str(), CollectionModel::Vault)
2583 }
2584 };
2585 Some((collection, model))
2586 }
2587 QueryExpr::ConfigCommand(cmd) => {
2588 self.validate_config_command_before_auth(cmd)?;
2589 None
2590 }
2591 _ => None,
2592 };
2593
2594 let Some((name, expected_model)) = expected else {
2595 return Ok(());
2596 };
2597 let snapshot = self.inner.db.catalog_model_snapshot();
2598 let Some(actual_model) = snapshot
2599 .collections
2600 .iter()
2601 .find(|collection| collection.name == name)
2602 .map(|collection| collection.declared_model.unwrap_or(collection.model))
2603 else {
2604 return Ok(());
2605 };
2606 polymorphic_resolver::ensure_model_match(expected_model, actual_model)
2607 }
2608
2609 pub(super) fn rewrite_view_refs(&self, expr: QueryExpr) -> QueryExpr {
2614 if self.inner.views.read().is_empty() {
2616 return expr;
2617 }
2618 self.rewrite_view_refs_inner(expr)
2619 }
2620
2621 fn rewrite_view_refs_inner(&self, expr: QueryExpr) -> QueryExpr {
2622 use crate::storage::query::ast::{Filter, TableSource};
2623 match expr {
2624 QueryExpr::Table(mut tq) => {
2625 if let Some(TableSource::Subquery(body)) = tq.source.take() {
2631 tq.source = Some(TableSource::Subquery(Box::new(
2632 self.rewrite_view_refs_inner(*body),
2633 )));
2634 return QueryExpr::Table(tq);
2635 }
2636
2637 let maybe_view = {
2641 let views = self.inner.views.read();
2642 views.get(&tq.table).cloned()
2643 };
2644 let Some(view) = maybe_view else {
2645 return QueryExpr::Table(tq);
2646 };
2647
2648 if view.materialized {
2654 return QueryExpr::Table(tq);
2655 }
2656
2657 let inner_expr = self.rewrite_view_refs_inner((*view.query).clone());
2661
2662 match inner_expr {
2670 QueryExpr::Table(mut inner_tq) => {
2671 if let Some(outer_filter) = tq.filter.take() {
2672 inner_tq.filter = Some(match inner_tq.filter.take() {
2673 Some(existing) => {
2674 Filter::And(Box::new(existing), Box::new(outer_filter))
2675 }
2676 None => outer_filter,
2677 });
2678 inner_tq.where_expr = inner_tq
2686 .filter
2687 .as_ref()
2688 .map(crate::storage::query::sql_lowering::filter_to_expr);
2689 }
2690 if let Some(outer_limit) = tq.limit {
2691 inner_tq.limit = Some(match inner_tq.limit {
2692 Some(existing) => existing.min(outer_limit),
2693 None => outer_limit,
2694 });
2695 }
2696 if let Some(outer_offset) = tq.offset {
2697 inner_tq.offset = Some(match inner_tq.offset {
2698 Some(existing) => existing + outer_offset,
2699 None => outer_offset,
2700 });
2701 }
2702 QueryExpr::Table(inner_tq)
2703 }
2704 other => other,
2705 }
2706 }
2707 QueryExpr::Join(mut jq) => {
2708 jq.left = Box::new(self.rewrite_view_refs_inner(*jq.left));
2709 jq.right = Box::new(self.rewrite_view_refs_inner(*jq.right));
2710 QueryExpr::Join(jq)
2711 }
2712 other => other,
2715 }
2716 }
2717
2718 fn resolve_table_expr_subqueries(
2719 &self,
2720 mut table: TableQuery,
2721 frame: &dyn super::statement_frame::ReadFrame,
2722 ) -> RedDBResult<TableQuery> {
2723 match table.source.take() {
2730 Some(TableSource::Subquery(inner)) => {
2731 let inner = self.resolve_select_expr_subqueries(*inner, frame)?;
2732 table.source = Some(TableSource::Subquery(Box::new(inner)));
2733 }
2734 other => table.source = other,
2735 }
2736
2737 let outer_scopes = relation_scopes_for_query(&QueryExpr::Table(table.clone()));
2738 for item in &mut table.select_items {
2739 if let crate::storage::query::ast::SelectItem::Expr { expr, .. } = item {
2740 *expr = self.resolve_expr_subqueries(expr.clone(), &outer_scopes, frame)?;
2741 }
2742 }
2743 if let Some(where_expr) = table.where_expr.take() {
2744 table.where_expr =
2745 Some(self.resolve_expr_subqueries(where_expr, &outer_scopes, frame)?);
2746 table.filter = None;
2747 }
2748 if let Some(having_expr) = table.having_expr.take() {
2749 table.having_expr =
2750 Some(self.resolve_expr_subqueries(having_expr, &outer_scopes, frame)?);
2751 table.having = None;
2752 }
2753 for expr in &mut table.group_by_exprs {
2754 *expr = self.resolve_expr_subqueries(expr.clone(), &outer_scopes, frame)?;
2755 }
2756 for clause in &mut table.order_by {
2757 if let Some(expr) = clause.expr.take() {
2758 clause.expr = Some(self.resolve_expr_subqueries(expr, &outer_scopes, frame)?);
2759 }
2760 }
2761 Ok(table)
2762 }
2763
2764 fn resolve_select_expr_subqueries(
2765 &self,
2766 expr: QueryExpr,
2767 frame: &dyn super::statement_frame::ReadFrame,
2768 ) -> RedDBResult<QueryExpr> {
2769 match expr {
2770 QueryExpr::Table(table) => self
2771 .resolve_table_expr_subqueries(table, frame)
2772 .map(QueryExpr::Table),
2773 QueryExpr::Join(mut join) => {
2774 join.left = Box::new(self.resolve_select_expr_subqueries(*join.left, frame)?);
2775 join.right = Box::new(self.resolve_select_expr_subqueries(*join.right, frame)?);
2776 Ok(QueryExpr::Join(join))
2777 }
2778 other => Ok(other),
2779 }
2780 }
2781
2782 fn resolve_expr_subqueries(
2783 &self,
2784 expr: crate::storage::query::ast::Expr,
2785 outer_scopes: &[String],
2786 frame: &dyn super::statement_frame::ReadFrame,
2787 ) -> RedDBResult<crate::storage::query::ast::Expr> {
2788 use crate::storage::query::ast::Expr;
2789
2790 match expr {
2791 Expr::Subquery { query, span } => {
2792 let values = self.execute_expr_subquery_values(query, outer_scopes, frame)?;
2793 if values.len() > 1 {
2794 return Err(RedDBError::Query(
2795 "scalar subquery returned more than one row".to_string(),
2796 ));
2797 }
2798 Ok(Expr::Literal {
2799 value: values.into_iter().next().unwrap_or(Value::Null),
2800 span,
2801 })
2802 }
2803 Expr::BinaryOp { op, lhs, rhs, span } => Ok(Expr::BinaryOp {
2804 op,
2805 lhs: Box::new(self.resolve_expr_subqueries(*lhs, outer_scopes, frame)?),
2806 rhs: Box::new(self.resolve_expr_subqueries(*rhs, outer_scopes, frame)?),
2807 span,
2808 }),
2809 Expr::UnaryOp { op, operand, span } => Ok(Expr::UnaryOp {
2810 op,
2811 operand: Box::new(self.resolve_expr_subqueries(*operand, outer_scopes, frame)?),
2812 span,
2813 }),
2814 Expr::Cast {
2815 inner,
2816 target,
2817 span,
2818 } => Ok(Expr::Cast {
2819 inner: Box::new(self.resolve_expr_subqueries(*inner, outer_scopes, frame)?),
2820 target,
2821 span,
2822 }),
2823 Expr::FunctionCall { name, args, span } => {
2824 let args = args
2825 .into_iter()
2826 .map(|arg| self.resolve_expr_subqueries(arg, outer_scopes, frame))
2827 .collect::<RedDBResult<Vec<_>>>()?;
2828 Ok(Expr::FunctionCall { name, args, span })
2829 }
2830 Expr::Case {
2831 branches,
2832 else_,
2833 span,
2834 } => {
2835 let branches = branches
2836 .into_iter()
2837 .map(|(cond, value)| {
2838 Ok((
2839 self.resolve_expr_subqueries(cond, outer_scopes, frame)?,
2840 self.resolve_expr_subqueries(value, outer_scopes, frame)?,
2841 ))
2842 })
2843 .collect::<RedDBResult<Vec<_>>>()?;
2844 let else_ = else_
2845 .map(|expr| self.resolve_expr_subqueries(*expr, outer_scopes, frame))
2846 .transpose()?
2847 .map(Box::new);
2848 Ok(Expr::Case {
2849 branches,
2850 else_,
2851 span,
2852 })
2853 }
2854 Expr::IsNull {
2855 operand,
2856 negated,
2857 span,
2858 } => Ok(Expr::IsNull {
2859 operand: Box::new(self.resolve_expr_subqueries(*operand, outer_scopes, frame)?),
2860 negated,
2861 span,
2862 }),
2863 Expr::InList {
2864 target,
2865 values,
2866 negated,
2867 span,
2868 } => {
2869 let target =
2870 Box::new(self.resolve_expr_subqueries(*target, outer_scopes, frame)?);
2871 let mut resolved = Vec::new();
2872 for value in values {
2873 if let Expr::Subquery { query, .. } = value {
2874 resolved.extend(
2875 self.execute_expr_subquery_values(query, outer_scopes, frame)?
2876 .into_iter()
2877 .map(Expr::lit),
2878 );
2879 } else {
2880 resolved.push(self.resolve_expr_subqueries(value, outer_scopes, frame)?);
2881 }
2882 }
2883 Ok(Expr::InList {
2884 target,
2885 values: resolved,
2886 negated,
2887 span,
2888 })
2889 }
2890 Expr::Between {
2891 target,
2892 low,
2893 high,
2894 negated,
2895 span,
2896 } => Ok(Expr::Between {
2897 target: Box::new(self.resolve_expr_subqueries(*target, outer_scopes, frame)?),
2898 low: Box::new(self.resolve_expr_subqueries(*low, outer_scopes, frame)?),
2899 high: Box::new(self.resolve_expr_subqueries(*high, outer_scopes, frame)?),
2900 negated,
2901 span,
2902 }),
2903 other => Ok(other),
2904 }
2905 }
2906
2907 fn execute_expr_subquery_values(
2908 &self,
2909 subquery: crate::storage::query::ast::ExprSubquery,
2910 outer_scopes: &[String],
2911 frame: &dyn super::statement_frame::ReadFrame,
2912 ) -> RedDBResult<Vec<Value>> {
2913 let query = *subquery.query;
2914 if query_references_outer_scope(&query, outer_scopes) {
2915 return Err(RedDBError::Query(
2916 "NOT_YET_SUPPORTED: correlated subqueries are not supported yet; track follow-up issue #470-correlated-subqueries".to_string(),
2917 ));
2918 }
2919 let query = self.rewrite_view_refs(query);
2920 let query = self.resolve_select_expr_subqueries(query, frame)?;
2921 let query = self.authorize_relational_select_expr(query, frame)?;
2922 let result = match query {
2923 QueryExpr::Table(table) => {
2924 execute_runtime_table_query(&self.inner.db, &table, Some(&self.inner.index_store))?
2925 }
2926 QueryExpr::Join(join) => execute_runtime_join_query(&self.inner.db, &join)?,
2927 other => {
2928 return Err(RedDBError::Query(format!(
2929 "expression subquery must be a SELECT query, got {}",
2930 query_expr_name(&other)
2931 )))
2932 }
2933 };
2934 first_column_values(result)
2935 }
2936
2937 fn dispatch_expr(
2938 &self,
2939 expr: QueryExpr,
2940 query_str: &str,
2941 mode: QueryMode,
2942 ) -> RedDBResult<RuntimeQueryResult> {
2943 let statement = query_expr_name(&expr);
2944 match expr {
2945 QueryExpr::Graph(_) | QueryExpr::Path(_) => {
2946 Err(RedDBError::Query(
2948 "graph queries cannot be used as prepared statements".to_string(),
2949 ))
2950 }
2951 QueryExpr::Table(table) => {
2952 let scope = self.ai_scope();
2953 let table = self.resolve_table_expr_subqueries(
2954 table,
2955 &scope as &dyn super::statement_frame::ReadFrame,
2956 )?;
2957 if let Some(TableSource::Function {
2961 name,
2962 args,
2963 named_args,
2964 }) = table.source.clone()
2965 {
2966 return Ok(RuntimeQueryResult {
2967 query: query_str.to_string(),
2968 mode,
2969 statement,
2970 engine: "runtime-graph-tvf",
2971 result: self.execute_table_function(&name, &args, &named_args)?,
2972 affected_rows: 0,
2973 statement_type: "select",
2974 bookmark: None,
2975 notice: None,
2976 });
2977 }
2978 if let Some(TableSource::InlineGraphFunction {
2982 name,
2983 nodes,
2984 edges,
2985 named_args,
2986 }) = table.source.clone()
2987 {
2988 return Ok(RuntimeQueryResult {
2989 query: query_str.to_string(),
2990 mode,
2991 statement,
2992 engine: "runtime-graph-tvf-inline",
2993 result: self.execute_inline_graph_function(
2994 &name,
2995 &nodes,
2996 &edges,
2997 &named_args,
2998 )?,
2999 affected_rows: 0,
3000 statement_type: "select",
3001 bookmark: None,
3002 notice: None,
3003 });
3004 }
3005 if super::red_schema::is_virtual_table(&table.table) {
3006 return Ok(RuntimeQueryResult {
3007 query: query_str.to_string(),
3008 mode,
3009 statement,
3010 engine: "runtime-red-schema",
3011 result: super::red_schema::red_query(
3012 self,
3013 &table.table,
3014 &table,
3015 &scope as &dyn super::statement_frame::ReadFrame,
3016 )?,
3017 affected_rows: 0,
3018 statement_type: "select",
3019 bookmark: None,
3020 notice: None,
3021 });
3022 }
3023 if let Some(view_result) = self.try_resolve_analytics_view(
3025 &table,
3026 &scope as &dyn super::statement_frame::ReadFrame,
3027 )? {
3028 return Ok(RuntimeQueryResult {
3029 query: query_str.to_string(),
3030 mode,
3031 statement,
3032 engine: "runtime-graph-analytics-view",
3033 result: view_result,
3034 affected_rows: 0,
3035 statement_type: "select",
3036 bookmark: None,
3037 notice: None,
3038 });
3039 }
3040 let Some(table_with_rls) = self.authorize_relational_table_select(
3041 table,
3042 &scope as &dyn super::statement_frame::ReadFrame,
3043 )?
3044 else {
3045 return Ok(RuntimeQueryResult {
3046 query: query_str.to_string(),
3047 mode,
3048 statement,
3049 engine: "runtime-table-rls",
3050 result: crate::storage::query::unified::UnifiedResult::empty(),
3051 affected_rows: 0,
3052 statement_type: "select",
3053 bookmark: None,
3054 notice: None,
3055 });
3056 };
3057 Ok(RuntimeQueryResult {
3058 query: query_str.to_string(),
3059 mode,
3060 statement,
3061 engine: "runtime-table",
3062 result: execute_runtime_table_query(
3063 &self.inner.db,
3064 &table_with_rls,
3065 Some(&self.inner.index_store),
3066 )?,
3067 affected_rows: 0,
3068 statement_type: "select",
3069 bookmark: None,
3070 notice: None,
3071 })
3072 }
3073 QueryExpr::Join(join) => {
3074 let scope = self.ai_scope();
3075 let Some(join_with_rls) = self.authorize_relational_join_select(
3076 join,
3077 &scope as &dyn super::statement_frame::ReadFrame,
3078 )?
3079 else {
3080 return Ok(RuntimeQueryResult {
3081 query: query_str.to_string(),
3082 mode,
3083 statement,
3084 engine: "runtime-join-rls",
3085 result: crate::storage::query::unified::UnifiedResult::empty(),
3086 affected_rows: 0,
3087 statement_type: "select",
3088 bookmark: None,
3089 notice: None,
3090 });
3091 };
3092 Ok(RuntimeQueryResult {
3093 query: query_str.to_string(),
3094 mode,
3095 statement,
3096 engine: "runtime-join",
3097 result: execute_runtime_join_query(&self.inner.db, &join_with_rls)?,
3098 affected_rows: 0,
3099 statement_type: "select",
3100 bookmark: None,
3101 notice: None,
3102 })
3103 }
3104 QueryExpr::Vector(vector) => Ok(RuntimeQueryResult {
3105 query: query_str.to_string(),
3106 mode,
3107 statement,
3108 engine: "runtime-vector",
3109 result: execute_runtime_vector_query(&self.inner.db, &vector)?,
3110 affected_rows: 0,
3111 statement_type: "select",
3112 bookmark: None,
3113 notice: None,
3114 }),
3115 QueryExpr::Hybrid(hybrid) => Ok(RuntimeQueryResult {
3116 query: query_str.to_string(),
3117 mode,
3118 statement,
3119 engine: "runtime-hybrid",
3120 result: execute_runtime_hybrid_query(&self.inner.db, &hybrid)?,
3121 affected_rows: 0,
3122 statement_type: "select",
3123 bookmark: None,
3124 notice: None,
3125 }),
3126 QueryExpr::Insert(ref insert) if super::red_schema::is_virtual_table(&insert.table) => {
3127 Err(RedDBError::Query(
3128 super::red_schema::READ_ONLY_ERROR.to_string(),
3129 ))
3130 }
3131 QueryExpr::Update(ref update) if super::red_schema::is_virtual_table(&update.table) => {
3132 Err(RedDBError::Query(
3133 super::red_schema::READ_ONLY_ERROR.to_string(),
3134 ))
3135 }
3136 QueryExpr::Delete(ref delete) if super::red_schema::is_virtual_table(&delete.table) => {
3137 Err(RedDBError::Query(
3138 super::red_schema::READ_ONLY_ERROR.to_string(),
3139 ))
3140 }
3141 QueryExpr::Insert(ref insert) => self
3142 .with_deferred_store_wal_for_dml(self.insert_may_emit_events(insert), || {
3143 self.execute_insert(query_str, insert)
3144 }),
3145 QueryExpr::Update(ref update) => self
3146 .with_deferred_store_wal_for_dml(self.update_may_emit_events(update), || {
3147 self.execute_update(query_str, update)
3148 }),
3149 QueryExpr::Delete(ref delete) => self
3150 .with_deferred_store_wal_for_dml(self.delete_may_emit_events(delete), || {
3151 self.execute_delete(query_str, delete)
3152 }),
3153 QueryExpr::SearchCommand(ref cmd) => self.execute_search_command(query_str, cmd),
3154 QueryExpr::Ask(ref ask) => self.execute_ask(query_str, ask),
3155 _ => Err(RedDBError::Query(format!(
3156 "prepared-statement execution does not support {statement} statements"
3157 ))),
3158 }
3159 }
3160
3161 fn execute_table_function(
3168 &self,
3169 name: &str,
3170 args: &[String],
3171 named_args: &[(String, f64)],
3172 ) -> RedDBResult<crate::storage::query::unified::UnifiedResult> {
3173 if name.eq_ignore_ascii_case("red.diff") {
3174 return self.execute_vcs_diff_tvf(args, named_args);
3175 }
3176 if !is_graph_tvf_name(name) {
3177 return Err(RedDBError::Query(format!("unknown table function: {name}")));
3178 }
3179 if args.len() != 1 {
3181 return Err(RedDBError::Query(format!(
3182 "table function '{name}' takes exactly 1 graph argument, got {}",
3183 args.len()
3184 )));
3185 }
3186
3187 let (nodes, edges) = self.materialize_whole_graph_abstract()?;
3192 self.dispatch_graph_algorithm(name, nodes, edges, named_args)
3193 }
3194
3195 pub(crate) fn revive_versioned_updates_since(&self, conn_id: u64, stamper_xid: u64) -> usize {
3196 let mut guard = self.inner.pending_versioned_updates.write();
3197 let Some(pending) = guard.get_mut(&conn_id) else {
3198 return 0;
3199 };
3200
3201 let store = self.inner.db.store();
3202 let mut reverted = 0usize;
3203 pending.retain(|(collection, old_id, new_id, xid, previous_xmax)| {
3204 if *xid < stamper_xid {
3205 return true;
3206 }
3207 if let Some(manager) = store.get_collection(collection) {
3208 if let Some(mut old) = manager.get(*old_id) {
3209 if old.xmax == *xid {
3210 old.set_xmax(*previous_xmax);
3211 let _ = manager.update(old);
3212 }
3213 }
3214 }
3215 let _ = store.delete_batch(collection, &[*new_id]);
3216 reverted += 1;
3217 false
3218 });
3219 if pending.is_empty() {
3220 guard.remove(&conn_id);
3221 }
3222 reverted
3223 }
3224
3225 fn explain_as_rows(&self, raw_query: &str, inner_sql: &str) -> RedDBResult<RuntimeQueryResult> {
3233 let explain = self.explain_query(inner_sql)?;
3234
3235 let columns = vec![
3236 "op".to_string(),
3237 "source".to_string(),
3238 "estimated_rows".to_string(),
3239 "estimated_cost".to_string(),
3240 "depth".to_string(),
3241 ];
3242
3243 let mut records: Vec<crate::storage::query::unified::UnifiedRecord> = Vec::new();
3244
3245 for name in &explain.cte_materializations {
3251 use std::sync::Arc;
3252 let mut rec = crate::storage::query::unified::UnifiedRecord::default();
3253 rec.set_arc(Arc::from("op"), Value::text("CteScan".to_string()));
3254 rec.set_arc(Arc::from("source"), Value::text(name.clone()));
3255 rec.set_arc(Arc::from("estimated_rows"), Value::Float(0.0));
3256 rec.set_arc(Arc::from("estimated_cost"), Value::Float(0.0));
3257 rec.set_arc(Arc::from("depth"), Value::Integer(0));
3258 records.push(rec);
3259 }
3260
3261 walk_plan_node(&explain.logical_plan.root, 0, &mut records);
3262
3263 let result = crate::storage::query::unified::UnifiedResult {
3264 columns,
3265 records,
3266 stats: Default::default(),
3267 pre_serialized_json: None,
3268 };
3269
3270 Ok(RuntimeQueryResult {
3271 query: raw_query.to_string(),
3272 mode: explain.mode,
3273 statement: "explain",
3274 engine: "runtime-explain",
3275 result,
3276 affected_rows: 0,
3277 statement_type: "select",
3278 bookmark: None,
3279 notice: None,
3280 })
3281 }
3282
3283 fn explain_analyze_as_rows(
3284 &self,
3285 raw_query: &str,
3286 inner_sql: &str,
3287 ) -> RedDBResult<RuntimeQueryResult> {
3288 if !starts_with_dml_keyword(inner_sql) {
3289 return Err(RedDBError::Query(
3290 "EXPLAIN ANALYZE currently supports INSERT, UPDATE, and DELETE".to_string(),
3291 ));
3292 }
3293
3294 let explain = self.explain_query(inner_sql)?;
3295 let conn_id = current_connection_id();
3296 if self.inner.tx_contexts.read().contains_key(&conn_id) {
3297 return Err(RedDBError::Query(
3298 "EXPLAIN ANALYZE requires no active transaction".to_string(),
3299 ));
3300 }
3301
3302 self.execute_query_inner("BEGIN")?;
3303 let started = std::time::Instant::now();
3304 let execution = self.execute_query_inner(inner_sql);
3305 let elapsed_ms = started.elapsed().as_secs_f64() * 1000.0;
3306 let rollback = self.execute_query_inner("ROLLBACK");
3307
3308 let affected_rows = match execution {
3309 Ok(result) => result.affected_rows,
3310 Err(err) => {
3311 rollback?;
3312 return Err(err);
3313 }
3314 };
3315 rollback?;
3316
3317 let columns = vec![
3318 "op".to_string(),
3319 "source".to_string(),
3320 "estimated_rows".to_string(),
3321 "estimated_cost".to_string(),
3322 "actual_rows".to_string(),
3323 "actual_ms".to_string(),
3324 "depth".to_string(),
3325 ];
3326 let mut records = Vec::new();
3327 walk_analyze_plan_node(
3328 &explain.logical_plan.root,
3329 0,
3330 affected_rows,
3331 elapsed_ms,
3332 &mut records,
3333 );
3334
3335 let result = crate::storage::query::unified::UnifiedResult {
3336 columns,
3337 records,
3338 stats: Default::default(),
3339 pre_serialized_json: None,
3340 };
3341
3342 Ok(RuntimeQueryResult {
3343 query: raw_query.to_string(),
3344 mode: explain.mode,
3345 statement: "explain_analyze",
3346 engine: "runtime-explain-analyze",
3347 result,
3348 affected_rows: 0,
3349 statement_type: "select",
3350 bookmark: None,
3351 notice: None,
3352 })
3353 }
3354}
3355
3356fn strip_explain_analyze_prefix(sql: &str) -> Option<&str> {
3357 let rest = strip_keyword_ci(sql.trim_start(), "EXPLAIN")?.trim_start();
3358 Some(strip_keyword_ci(rest, "ANALYZE")?.trim_start()).filter(|inner| !inner.is_empty())
3359}
3360
3361fn strip_keyword_ci<'a>(sql: &'a str, keyword: &str) -> Option<&'a str> {
3362 if sql.len() < keyword.len() {
3363 return None;
3364 }
3365 let (head, rest) = sql.split_at(keyword.len());
3366 if !head.eq_ignore_ascii_case(keyword) {
3367 return None;
3368 }
3369 if rest
3370 .chars()
3371 .next()
3372 .is_some_and(|ch| !ch.is_ascii_whitespace())
3373 {
3374 return None;
3375 }
3376 Some(rest)
3377}
3378
3379fn starts_with_dml_keyword(sql: &str) -> bool {
3380 let trimmed = sql.trim_start();
3381 let head_end = trimmed
3382 .find(|ch: char| ch.is_ascii_whitespace())
3383 .unwrap_or(trimmed.len());
3384 let head = &trimmed[..head_end];
3385 head.eq_ignore_ascii_case("INSERT")
3386 || head.eq_ignore_ascii_case("UPDATE")
3387 || head.eq_ignore_ascii_case("DELETE")
3388}
3389
3390fn walk_plan_node(
3391 node: &crate::storage::query::planner::CanonicalLogicalNode,
3392 depth: usize,
3393 out: &mut Vec<crate::storage::query::unified::UnifiedRecord>,
3394) {
3395 use std::sync::Arc;
3396 let mut rec = crate::storage::query::unified::UnifiedRecord::default();
3397 rec.set_arc(Arc::from("op"), Value::text(node.operator.clone()));
3398 rec.set_arc(
3399 Arc::from("source"),
3400 node.source.clone().map(Value::text).unwrap_or(Value::Null),
3401 );
3402 rec.set_arc(
3403 Arc::from("estimated_rows"),
3404 Value::Float(node.estimated_rows),
3405 );
3406 rec.set_arc(
3407 Arc::from("estimated_cost"),
3408 Value::Float(node.operator_cost),
3409 );
3410 rec.set_arc(Arc::from("depth"), Value::Integer(depth as i64));
3411 out.push(rec);
3412 for child in &node.children {
3413 walk_plan_node(child, depth + 1, out);
3414 }
3415}
3416
3417fn walk_analyze_plan_node(
3418 node: &crate::storage::query::planner::CanonicalLogicalNode,
3419 depth: usize,
3420 affected_rows: u64,
3421 elapsed_ms: f64,
3422 out: &mut Vec<crate::storage::query::unified::UnifiedRecord>,
3423) {
3424 use std::sync::Arc;
3425 let mut rec = crate::storage::query::unified::UnifiedRecord::default();
3426 rec.set_arc(Arc::from("op"), Value::text(node.operator.clone()));
3427 rec.set_arc(
3428 Arc::from("source"),
3429 node.source.clone().map(Value::text).unwrap_or(Value::Null),
3430 );
3431 rec.set_arc(
3432 Arc::from("estimated_rows"),
3433 Value::Float(node.estimated_rows),
3434 );
3435 rec.set_arc(
3436 Arc::from("estimated_cost"),
3437 Value::Float(node.operator_cost),
3438 );
3439 rec.set_arc(
3440 Arc::from("actual_rows"),
3441 Value::UnsignedInteger(if depth == 0 { affected_rows } else { 0 }),
3442 );
3443 rec.set_arc(
3444 Arc::from("actual_ms"),
3445 Value::Float(if depth == 0 { elapsed_ms } else { 0.0 }),
3446 );
3447 rec.set_arc(Arc::from("depth"), Value::Integer(depth as i64));
3448 out.push(rec);
3449
3450 for child in &node.children {
3451 walk_analyze_plan_node(child, depth + 1, 0, 0.0, out);
3452 }
3453}
3454
3455#[cfg(test)]
3456mod inline_graph_tvf_tests {
3457 use super::*;
3458
3459 fn scopes_for(sql: &str) -> HashSet<String> {
3460 let expr = crate::storage::query::parser::parse(sql)
3461 .expect("parse")
3462 .query;
3463 query_expr_result_cache_scopes(&expr)
3464 }
3465
3466 #[test]
3467 fn inline_tvf_cache_scopes_include_source_collections() {
3468 let scopes = scopes_for(
3472 "SELECT * FROM components(nodes => (SELECT id FROM hosts), edges => (SELECT src, dst FROM links))",
3473 );
3474 assert!(scopes.contains("hosts"), "nodes source scoped: {scopes:?}");
3475 assert!(scopes.contains("links"), "edges source scoped: {scopes:?}");
3476 }
3477
3478 #[test]
3479 fn graph_collection_tvf_cache_scope_is_graph_argument() {
3480 let scopes = scopes_for("SELECT * FROM components(g)");
3484 assert!(scopes.contains("g"), "collection form scoped: {scopes:?}");
3485 }
3486
3487 #[test]
3488 fn abstract_degree_centrality_counts_undirected_endpoints() {
3489 let nodes = vec!["a".to_string(), "b".to_string(), "c".to_string()];
3490 let edges = vec![
3491 ("a".to_string(), "b".to_string(), 1.0_f32),
3492 ("b".to_string(), "c".to_string(), 1.0_f32),
3493 ];
3494 let degrees = abstract_degree_centrality(&nodes, &edges);
3495 assert_eq!(
3496 degrees,
3497 vec![
3498 ("a".to_string(), 1),
3499 ("b".to_string(), 2),
3500 ("c".to_string(), 1),
3501 ]
3502 );
3503 }
3504}