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 current_auth_identity, current_config_value, current_kv_value, current_role_projected,
74 current_scope_override, current_secret_value, current_snapshot_requires_index_fallback,
75 current_user_projected, has_scope_override_active, parse_set_local_tenant,
76 update_current_config_value, update_current_kv_value, update_current_secret_value,
77 xids_visible_under_current_snapshot, ConfigSnapshotGuard, CurrentSnapshotGuard, KvStoreGuard,
78 ScopeOverrideGuard, SecretStoreGuard, TxLocalTenantGuard,
79};
80
81pub(super) fn has_with_prefix(sql: &str) -> bool {
86 let trimmed = sql.trim_start();
87 let head_end = trimmed
88 .find(|c: char| c.is_whitespace() || c == '(')
89 .unwrap_or(trimmed.len());
90 trimmed[..head_end].eq_ignore_ascii_case("WITH")
91}
92
93pub(super) fn peek_top_level_as_of_with_table(
98 sql: &str,
99) -> Option<(crate::application::vcs::AsOfSpec, Option<String>)> {
100 if !sql
101 .as_bytes()
102 .windows(5)
103 .any(|w| w.eq_ignore_ascii_case(b"as of"))
104 {
105 return None;
106 }
107 let parsed = crate::storage::query::parser::parse(sql).ok()?;
108 let crate::storage::query::ast::QueryExpr::Table(table) = parsed.query else {
109 return None;
110 };
111 let clause = table.as_of?;
112 let table_name = if table.table.is_empty() || table.table == "any" {
113 None
114 } else {
115 Some(table.table.clone())
116 };
117 let spec = match clause {
118 crate::storage::query::ast::AsOfClause::Commit(h) => {
119 crate::application::vcs::AsOfSpec::Commit(h)
120 }
121 crate::storage::query::ast::AsOfClause::Branch(b) => {
122 crate::application::vcs::AsOfSpec::Branch(b)
123 }
124 crate::storage::query::ast::AsOfClause::Tag(t) => crate::application::vcs::AsOfSpec::Tag(t),
125 crate::storage::query::ast::AsOfClause::TimestampMs(ts) => {
126 crate::application::vcs::AsOfSpec::TimestampMs(ts)
127 }
128 crate::storage::query::ast::AsOfClause::Snapshot(x) => {
129 crate::application::vcs::AsOfSpec::Snapshot(x)
130 }
131 };
132 Some((spec, table_name))
133}
134
135pub(super) fn query_has_volatile_builtin(sql: &str) -> bool {
136 const VOLATILE_TOKENS: &[&str] = &[
140 "pg_advisory_lock",
141 "pg_try_advisory_lock",
142 "pg_advisory_unlock",
143 "random()",
144 "$config",
149 "$secret",
150 "$kv",
151 ];
156 let lowered = sql.to_ascii_lowercase();
157 VOLATILE_TOKENS.iter().any(|t| lowered.contains(t))
158}
159
160pub(super) fn query_is_ask_statement(sql: &str) -> bool {
161 let trimmed = sql.trim_start();
162 let head_end = trimmed
163 .find(|c: char| c.is_whitespace() || c == '(' || c == ';')
164 .unwrap_or(trimmed.len());
165 trimmed[..head_end].eq_ignore_ascii_case("ASK")
166}
167
168pub(super) fn intent_lock_modes_for(
178 expr: &QueryExpr,
179) -> Option<(
180 crate::runtime::lock_manager::LockMode,
181 crate::runtime::lock_manager::LockMode,
182)> {
183 use crate::runtime::lock_manager::LockMode::{Exclusive, IntentExclusive, IntentShared};
184
185 match expr {
186 QueryExpr::Table(_)
188 | QueryExpr::Join(_)
189 | QueryExpr::Vector(_)
190 | QueryExpr::Hybrid(_)
191 | QueryExpr::Graph(_)
192 | QueryExpr::Path(_)
193 | QueryExpr::Ask(_)
194 | QueryExpr::SearchCommand(_)
195 | QueryExpr::GraphCommand(_)
196 | QueryExpr::RankOf(_)
197 | QueryExpr::ApproxRankOf(_)
198 | QueryExpr::RankRange(_)
199 | QueryExpr::QueueSelect(_) => Some((IntentShared, IntentShared)),
200
201 QueryExpr::Insert(_)
209 | QueryExpr::Update(_)
210 | QueryExpr::Delete(_)
211 | QueryExpr::QueueCommand(QueueCommand::Move { .. }) => {
212 Some((IntentExclusive, IntentExclusive))
213 }
214 QueryExpr::QueueCommand(_) => Some((IntentShared, IntentShared)),
215
216 QueryExpr::CreateTable(_)
220 | QueryExpr::CreateCollection(_)
221 | QueryExpr::CreateVector(_)
222 | QueryExpr::DropTable(_)
223 | QueryExpr::DropGraph(_)
224 | QueryExpr::DropVector(_)
225 | QueryExpr::DropDocument(_)
226 | QueryExpr::DropKv(_)
227 | QueryExpr::DropCollection(_)
228 | QueryExpr::Truncate(_)
229 | QueryExpr::AlterTable(_)
230 | QueryExpr::CreateIndex(_)
231 | QueryExpr::DropIndex(_)
232 | QueryExpr::CreateTimeSeries(_)
233 | QueryExpr::CreateMetric(_)
234 | QueryExpr::AlterMetric(_)
235 | QueryExpr::CreateSlo(_)
236 | QueryExpr::DropTimeSeries(_)
237 | QueryExpr::CreateQueue(_)
238 | QueryExpr::AlterQueue(_)
239 | QueryExpr::DropQueue(_)
240 | QueryExpr::CreateTree(_)
241 | QueryExpr::DropTree(_)
242 | QueryExpr::CreatePolicy(_)
243 | QueryExpr::DropPolicy(_)
244 | QueryExpr::CreateView(_)
245 | QueryExpr::DropView(_)
246 | QueryExpr::RefreshMaterializedView(_)
247 | QueryExpr::CreateSchema(_)
248 | QueryExpr::DropSchema(_)
249 | QueryExpr::CreateSequence(_)
250 | QueryExpr::DropSequence(_)
251 | QueryExpr::CreateServer(_)
252 | QueryExpr::DropServer(_)
253 | QueryExpr::CreateForeignTable(_)
254 | QueryExpr::DropForeignTable(_) => Some((IntentExclusive, Exclusive)),
255
256 _ => None,
262 }
263}
264
265pub(super) fn collections_referenced(expr: &QueryExpr) -> Vec<String> {
270 let mut out = Vec::new();
271 walk_collections(expr, &mut out);
272 out.sort();
273 out.dedup();
274 out
275}
276
277impl RedDBRuntime {
278 pub fn execute_query_with_scope(
292 &self,
293 query: &str,
294 scope: crate::runtime::within_clause::ScopeOverride,
295 ) -> RedDBResult<RuntimeQueryResult> {
296 if scope.is_empty() {
297 return self.execute_query(query);
298 }
299 let _scope_guard = ScopeOverrideGuard::install(scope);
300 self.execute_query(query)
301 }
302
303 pub fn execute_query(&self, query: &str) -> RedDBResult<RuntimeQueryResult> {
312 let started = std::time::Instant::now();
313 self.inner.node_load_telemetry.query_start();
314 let result = self.execute_query_inner(query);
315 self.finish_query_lifecycle(query, started, result)
316 }
317
318 pub fn execute_query_with_params(
326 &self,
327 query: &str,
328 params: &[Value],
329 ) -> RedDBResult<RuntimeQueryResult> {
330 if params.is_empty() {
331 return self.execute_query(query);
332 }
333 let started = std::time::Instant::now();
334 self.inner.node_load_telemetry.query_start();
335 let result = self.execute_query_with_params_inner(query, params);
336 self.finish_query_lifecycle(query, started, result)
337 }
338
339 fn finish_query_lifecycle(
340 &self,
341 query: &str,
342 started: std::time::Instant,
343 mut result: RedDBResult<RuntimeQueryResult>,
344 ) -> RedDBResult<RuntimeQueryResult> {
345 if let Ok(ref mut query_result) = result {
350 if query_result.statement_type == "select" {
351 self.filter_integrity_tombstoned(&mut query_result.result);
352 }
353 }
354 let elapsed_ms = started.elapsed().as_millis() as u64;
355
356 let scope = self.ai_scope();
361 let kind = match result
362 .as_ref()
363 .map(|r| r.statement_type)
364 .unwrap_or("select")
365 {
366 "select" => crate::telemetry::slow_query_logger::QueryKind::Select,
367 "insert" => crate::telemetry::slow_query_logger::QueryKind::Insert,
368 "update" => crate::telemetry::slow_query_logger::QueryKind::Update,
369 "delete" => crate::telemetry::slow_query_logger::QueryKind::Delete,
370 _ => crate::telemetry::slow_query_logger::QueryKind::Internal,
371 };
372 self.inner
378 .slow_query_logger
379 .record(kind, elapsed_ms, query.to_string(), &scope);
380
381 self.inner
387 .query_latency_telemetry
388 .observe(kind, started.elapsed().as_secs_f64());
389
390 self.inner.node_load_telemetry.query_finish();
394
395 if let Ok(ref mut query_result) = result {
396 if matches!(query_result.statement_type, "insert" | "update" | "delete") {
397 let bookmark = crate::replication::CausalBookmark::new(
398 self.current_replication_term(),
399 self.cdc_current_lsn(),
400 );
401 query_result.bookmark = Some(bookmark.encode());
402 }
403 }
404
405 result
406 }
407
408 fn execute_query_with_params_inner(
409 &self,
410 query: &str,
411 params: &[Value],
412 ) -> RedDBResult<RuntimeQueryResult> {
413 let parsed = parse_multi(query).map_err(|err| RedDBError::Query(err.to_string()))?;
414 let bound = crate::storage::query::user_params::bind(&parsed, params).map_err(|err| {
415 RedDBError::Validation {
416 message: err.to_string(),
417 validation: crate::json!({
418 "code": "INVALID_PARAMS",
419 "surface": "query.params",
420 }),
421 }
422 })?;
423 self.execute_bound_query_expr_in_frame(query, bound)
424 }
425
426 fn execute_bound_query_expr_in_frame(
427 &self,
428 query: &str,
429 expr: QueryExpr,
430 ) -> RedDBResult<RuntimeQueryResult> {
431 let rewritten_query = super::red_schema::rewrite_virtual_names(query);
432 let execution_query = rewritten_query.as_deref().unwrap_or(query);
433 let frame = super::statement_frame::StatementExecutionFrame::build(self, execution_query)?;
434 let _frame_guards = Box::new(frame.install(self));
438 let _log_span = crate::telemetry::span::query_span(query).entered();
439
440 let expr = self.rewrite_view_refs(expr);
441 let mode = detect_mode(execution_query);
442 let control_event_specs = query_control_event_specs(&expr);
443 let _lock_guard = match frame.prepare_dispatch(self, &expr) {
444 Ok(guard) => guard,
445 Err(err) => {
446 let outcome = control_event_outcome_for_error(&err);
447 for spec in &control_event_specs {
448 self.emit_control_event(
449 spec.kind,
450 outcome,
451 spec.action,
452 spec.resource.clone(),
453 Some(err.to_string()),
454 spec.fields.clone(),
455 )?;
456 }
457 return Err(err);
458 }
459 };
460
461 let mut result = self.dispatch_expr(expr, query, mode)?;
462 if result.statement_type == "select" {
463 self.apply_secret_decryption(&mut result);
464 }
465 Ok(result)
466 }
467
468 pub fn causal_session(&self) -> crate::runtime::CausalSession {
469 crate::runtime::CausalSession {
470 runtime: self.clone(),
471 bookmark: None,
472 wait_timeout: std::time::Duration::from_secs(5),
473 }
474 }
475
476 pub fn wait_for_bookmark(
477 &self,
478 bookmark: &crate::replication::CausalBookmark,
479 timeout: std::time::Duration,
480 ) -> RedDBResult<()> {
481 let deadline = std::time::Instant::now() + timeout;
482 loop {
483 let applied_lsn = self.local_contiguous_applied_lsn();
484 if applied_lsn >= bookmark.commit_lsn() {
485 return Ok(());
486 }
487 let now = std::time::Instant::now();
488 if now >= deadline {
489 return Err(RedDBError::InvalidOperation(format!(
490 "timed out waiting for causal bookmark lsn {}; applied={}",
491 bookmark.commit_lsn(),
492 applied_lsn
493 )));
494 }
495 let remaining = deadline.saturating_duration_since(now);
496 std::thread::sleep(remaining.min(std::time::Duration::from_millis(5)));
497 }
498 }
499
500 fn local_contiguous_applied_lsn(&self) -> u64 {
501 match self.inner.db.options().replication.role {
502 crate::replication::ReplicationRole::Replica { .. } => {
503 self.config_u64("red.replication.last_applied_lsn", 0)
504 }
505 _ => self.cdc_current_lsn(),
506 }
507 }
508
509 #[inline(never)]
510 pub(super) fn execute_query_inner(&self, query: &str) -> RedDBResult<RuntimeQueryResult> {
511 if !has_scope_override_active()
522 && !query.trim_start().starts_with("WITHIN")
523 && !query.trim_start().starts_with("within")
524 && !self.inner.query_audit.has_rules()
525 && !self
526 .inner
527 .tx_contexts
528 .read()
529 .contains_key(¤t_connection_id())
530 {
531 if let Some(result) = self.try_fast_entity_lookup(query) {
532 return result;
533 }
534 }
535
536 match crate::runtime::within_clause::try_strip_within_prefix(query) {
543 Ok(Some((scope, inner))) => {
544 let _scope_guard = ScopeOverrideGuard::install(scope);
545 return self.execute_query_inner(inner);
550 }
551 Ok(None) => {}
552 Err(msg) => return Err(RedDBError::Query(msg)),
553 }
554
555 if let Some(inner) = strip_explain_prefix(query) {
562 return self.explain_as_rows(query, inner);
563 }
564
565 if let Some(value) = parse_set_local_tenant(query)? {
570 let conn_id = current_connection_id();
571 if !self.inner.tx_contexts.read().contains_key(&conn_id) {
572 return Err(RedDBError::Query(
573 "SET LOCAL TENANT requires an active transaction".to_string(),
574 ));
575 }
576 self.inner
577 .tx_local_tenants
578 .write()
579 .insert(conn_id, value.clone());
580 return Ok(RuntimeQueryResult::ok_message(
581 query.to_string(),
582 &match &value {
583 Some(id) => format!("local tenant set: {id}"),
584 None => "local tenant cleared".to_string(),
585 },
586 "set_local_tenant",
587 ));
588 }
589
590 if super::red_schema::is_system_schema_write(query) {
591 return Err(RedDBError::Query(
592 super::red_schema::READ_ONLY_ERROR.to_string(),
593 ));
594 }
595
596 if let Some(command) = parse_runtime_vcs_command(query) {
597 return self.execute_vcs_command(query, detect_mode(query), command?);
598 }
599
600 if let Some(create_source) = super::analytics_source_catalog::parse_create_statement(query)?
601 {
602 return self.execute_create_analytics_source(query, create_source);
603 }
604
605 if let Some(path) = super::metric_descriptor_catalog::parse_read_metric_statement(query) {
611 return Err(super::metric_descriptor_catalog::read_output_unsupported(
612 &path,
613 ));
614 }
615
616 if let Some(parsed) = super::ranking_descriptor_catalog::parse_create_ranking(query) {
621 return self.execute_create_ranking(query, parsed?);
622 }
623 if super::ranking_descriptor_catalog::parse_show_rankings(query) {
624 return self.execute_show_rankings(query);
625 }
626
627 let rewritten_query = super::red_schema::rewrite_virtual_names(query);
628 let execution_query = rewritten_query.as_deref().unwrap_or(query);
629
630 let frame = super::statement_frame::StatementExecutionFrame::build(self, execution_query)?;
631 let _frame_guards = Box::new(frame.install(self));
632
633 let _log_span = crate::telemetry::span::query_span(query).entered();
640
641 if let Some(rewritten) = frame.prepare_cte(execution_query)? {
643 return self.execute_query_expr(rewritten);
644 }
645
646 if !self.inner.query_audit.has_rules() {
648 if let Some(result) = self.try_fast_entity_lookup(execution_query) {
649 return result;
650 }
651 }
652
653 if !self.inner.query_audit.has_rules() {
655 if let Some(result) = frame.read_result_cache(self) {
656 return Ok(result);
657 }
658 }
659
660 let prepared = frame.prepare_statement(self, execution_query)?;
661 let mode = prepared.mode;
662 let expr = prepared.expr;
663
664 let statement = query_expr_name(&expr);
665 let result_cache_scopes = query_expr_result_cache_scopes(&expr);
666 let control_event_specs = query_control_event_specs(&expr);
667 let query_audit_plan = query_audit_plan(&expr);
668
669 let _lock_guard = match frame.prepare_dispatch(self, &expr) {
670 Ok(guard) => guard,
671 Err(err) => {
672 let outcome = control_event_outcome_for_error(&err);
673 for spec in &control_event_specs {
674 self.emit_control_event(
675 spec.kind,
676 outcome,
677 spec.action,
678 spec.resource.clone(),
679 Some(err.to_string()),
680 spec.fields.clone(),
681 )?;
682 }
683 return Err(err);
684 }
685 };
686 let frame_iface: &dyn super::statement_frame::ReadFrame = &frame;
687 let query_audit_started = std::time::Instant::now();
688
689 let query_result = match expr {
690 QueryExpr::Graph(_) | QueryExpr::Path(_) => {
691 let (graph, node_properties, edge_properties) =
699 self.materialize_graph_with_rls()?;
700 let result =
701 crate::storage::query::unified::UnifiedExecutor::execute_on_with_graph_properties(
702 &graph,
703 &expr,
704 node_properties,
705 edge_properties,
706 )
707 .map_err(|err| RedDBError::Query(err.to_string()))?;
708
709 Ok(RuntimeQueryResult {
710 query: query.to_string(),
711 mode,
712 statement,
713 engine: "materialized-graph",
714 result,
715 affected_rows: 0,
716 statement_type: "select",
717 bookmark: None,
718 })
719 }
720 QueryExpr::Table(table) => {
721 let table = self.resolve_table_expr_subqueries(
722 table,
723 &frame as &dyn super::statement_frame::ReadFrame,
724 )?;
725 if let Some(TableSource::Function {
729 name,
730 args,
731 named_args,
732 }) = table.source.clone()
733 {
734 let tvf_result = RuntimeQueryResult {
742 query: query.to_string(),
743 mode,
744 statement,
745 engine: "runtime-graph-tvf",
746 result: self.execute_table_function(&name, &args, &named_args)?,
747 affected_rows: 0,
748 statement_type: "select",
749 bookmark: None,
750 };
751 frame.write_result_cache(self, &tvf_result, result_cache_scopes.clone());
752 return Ok(tvf_result);
753 }
754 if let Some(TableSource::InlineGraphFunction {
762 name,
763 nodes,
764 edges,
765 named_args,
766 }) = table.source.clone()
767 {
768 let inline_result = RuntimeQueryResult {
769 query: query.to_string(),
770 mode,
771 statement,
772 engine: "runtime-graph-tvf-inline",
773 result: self.execute_inline_graph_function(
774 &name,
775 &nodes,
776 &edges,
777 &named_args,
778 )?,
779 affected_rows: 0,
780 statement_type: "select",
781 bookmark: None,
782 };
783 frame.write_result_cache(self, &inline_result, result_cache_scopes);
784 return Ok(inline_result);
785 }
786 if super::red_schema::is_virtual_table(&table.table) {
787 return Ok(RuntimeQueryResult {
788 query: query.to_string(),
789 mode,
790 statement,
791 engine: "runtime-red-schema",
792 result: super::red_schema::red_query(
793 self,
794 &table.table,
795 &table,
796 &frame as &dyn super::statement_frame::ReadFrame,
797 )?,
798 affected_rows: 0,
799 statement_type: "select",
800 bookmark: None,
801 });
802 }
803
804 if let Some(view_result) = self.try_resolve_analytics_view(
808 &table,
809 &frame as &dyn super::statement_frame::ReadFrame,
810 )? {
811 return Ok(RuntimeQueryResult {
812 query: query.to_string(),
813 mode,
814 statement,
815 engine: "runtime-graph-analytics-view",
816 result: view_result,
817 affected_rows: 0,
818 statement_type: "select",
819 bookmark: None,
820 });
821 }
822
823 if let Some(result) = self.execute_probabilistic_select(&table)? {
824 return Ok(RuntimeQueryResult {
825 query: query.to_string(),
826 mode,
827 statement,
828 engine: "runtime-probabilistic",
829 result,
830 affected_rows: 0,
831 statement_type: "select",
832 bookmark: None,
833 });
834 }
835
836 if self.inner.foreign_tables.is_foreign_table(&table.table) {
844 let records = self
845 .inner
846 .foreign_tables
847 .scan(&table.table)
848 .map_err(|e| RedDBError::Internal(e.to_string()))?;
849 let result = apply_foreign_table_filters(records, &table);
850 return Ok(RuntimeQueryResult {
851 query: query.to_string(),
852 mode,
853 statement,
854 engine: "runtime-fdw",
855 result,
856 affected_rows: 0,
857 statement_type: "select",
858 bookmark: None,
859 });
860 }
861
862 let Some(table_with_rls) = self.authorize_relational_table_select(
879 table,
880 &frame as &dyn super::statement_frame::ReadFrame,
881 )?
882 else {
883 let empty = crate::storage::query::unified::UnifiedResult::empty();
884 return Ok(RuntimeQueryResult {
885 query: query.to_string(),
886 mode,
887 statement,
888 engine: "runtime-table-rls",
889 result: empty,
890 affected_rows: 0,
891 statement_type: "select",
892 bookmark: None,
893 });
894 };
895 Ok(RuntimeQueryResult {
896 query: query.to_string(),
897 mode,
898 statement,
899 engine: "runtime-table",
900 result: execute_runtime_table_query_in(
907 &self.inner.db,
908 &table_with_rls,
909 Some(&self.inner.index_store),
910 Some(frame.row_arena()),
911 )?,
912 affected_rows: 0,
913 statement_type: "select",
914 bookmark: None,
915 })
916 }
917 QueryExpr::Join(join) => {
918 let join_with_rls = match self.authorize_relational_join_select(
927 join,
928 &frame as &dyn super::statement_frame::ReadFrame,
929 )? {
930 Some(j) => j,
931 None => {
932 return Ok(RuntimeQueryResult {
933 query: query.to_string(),
934 mode,
935 statement,
936 engine: "runtime-join-rls",
937 result: crate::storage::query::unified::UnifiedResult::empty(),
938 affected_rows: 0,
939 statement_type: "select",
940 bookmark: None,
941 });
942 }
943 };
944 Ok(RuntimeQueryResult {
945 query: query.to_string(),
946 mode,
947 statement,
948 engine: "runtime-join",
949 result: execute_runtime_join_query(&self.inner.db, &join_with_rls)?,
950 affected_rows: 0,
951 statement_type: "select",
952 bookmark: None,
953 })
954 }
955 QueryExpr::Vector(vector) => Ok(RuntimeQueryResult {
956 query: query.to_string(),
957 mode,
958 statement,
959 engine: "runtime-vector",
960 result: execute_runtime_vector_query(&self.inner.db, &vector)?,
961 affected_rows: 0,
962 statement_type: "select",
963 bookmark: None,
964 }),
965 QueryExpr::Hybrid(hybrid) => Ok(RuntimeQueryResult {
966 query: query.to_string(),
967 mode,
968 statement,
969 engine: "runtime-hybrid",
970 result: execute_runtime_hybrid_query(&self.inner.db, &hybrid)?,
971 affected_rows: 0,
972 statement_type: "select",
973 bookmark: None,
974 }),
975 QueryExpr::RankOf(ref rank) => self.execute_rank_of(query, rank),
976 QueryExpr::ApproxRankOf(ref rank) => self.execute_approx_rank_of(query, rank),
977 QueryExpr::RankRange(ref range) => self.execute_rank_range(query, range),
978 QueryExpr::Insert(ref insert) if super::red_schema::is_virtual_table(&insert.table) => {
980 Err(RedDBError::Query(
981 super::red_schema::READ_ONLY_ERROR.to_string(),
982 ))
983 }
984 QueryExpr::Update(ref update) if super::red_schema::is_virtual_table(&update.table) => {
985 Err(RedDBError::Query(
986 super::red_schema::READ_ONLY_ERROR.to_string(),
987 ))
988 }
989 QueryExpr::Delete(ref delete) if super::red_schema::is_virtual_table(&delete.table) => {
990 Err(RedDBError::Query(
991 super::red_schema::READ_ONLY_ERROR.to_string(),
992 ))
993 }
994 QueryExpr::Insert(ref insert) => self
995 .with_deferred_store_wal_for_dml(self.insert_may_emit_events(insert), || {
996 self.execute_insert(query, insert)
997 }),
998 QueryExpr::Update(ref update) => self
999 .with_deferred_store_wal_for_dml(self.update_may_emit_events(update), || {
1000 self.execute_update(query, update)
1001 }),
1002 QueryExpr::Delete(ref delete) => self
1003 .with_deferred_store_wal_for_dml(self.delete_may_emit_events(delete), || {
1004 self.execute_delete(query, delete)
1005 }),
1006 QueryExpr::CreateTable(ref create) => self.execute_create_table(query, create),
1008 QueryExpr::CreateCollection(ref create) => {
1009 self.execute_create_collection(query, create)
1010 }
1011 QueryExpr::CreateVector(ref create) => self.execute_create_vector(query, create),
1012 QueryExpr::DropTable(ref drop_tbl) => self.execute_drop_table(query, drop_tbl),
1013 QueryExpr::DropGraph(ref drop_graph) => self.execute_drop_graph(query, drop_graph),
1014 QueryExpr::DropVector(ref drop_vector) => self.execute_drop_vector(query, drop_vector),
1015 QueryExpr::DropDocument(ref drop_document) => {
1016 self.execute_drop_document(query, drop_document)
1017 }
1018 QueryExpr::DropKv(ref drop_kv) => self.execute_drop_kv(query, drop_kv),
1019 QueryExpr::DropCollection(ref drop_collection) => {
1020 self.execute_drop_collection(query, drop_collection)
1021 }
1022 QueryExpr::Truncate(ref truncate) => self.execute_truncate(query, truncate),
1023 QueryExpr::AlterTable(ref alter) => self.execute_alter_table(query, alter),
1024 QueryExpr::CreateVcsRef(ref create) => self.execute_create_vcs_ref(query, create),
1025 QueryExpr::DropVcsRef(ref drop_ref) => self.execute_drop_vcs_ref(query, drop_ref),
1026 QueryExpr::ExplainAlter(ref explain) => self.execute_explain_alter(query, explain),
1027 QueryExpr::GraphCommand(ref cmd) => self.execute_graph_command(query, cmd),
1029 QueryExpr::SearchCommand(ref cmd) => self.execute_search_command(query, cmd),
1031 QueryExpr::Ask(ref ask) => self.execute_ask(query, ask),
1033 QueryExpr::CreateIndex(ref create_idx) => self.execute_create_index(query, create_idx),
1034 QueryExpr::DropIndex(ref drop_idx) => self.execute_drop_index(query, drop_idx),
1035 QueryExpr::ProbabilisticCommand(ref cmd) => {
1036 self.execute_probabilistic_command(query, cmd)
1037 }
1038 QueryExpr::CreateTimeSeries(ref ts) => self.execute_create_timeseries(query, ts),
1040 QueryExpr::CreateMetric(ref metric) => self.execute_create_metric(query, metric),
1041 QueryExpr::AlterMetric(ref alter) => self.execute_alter_metric(query, alter),
1042 QueryExpr::CreateSlo(ref slo) => self.execute_create_slo(query, slo),
1043 QueryExpr::DropTimeSeries(ref ts) => self.execute_drop_timeseries(query, ts),
1044 QueryExpr::CreateQueue(ref q) => self.execute_create_queue(query, q),
1046 QueryExpr::AlterQueue(ref q) => self.execute_alter_queue(query, q),
1047 QueryExpr::DropQueue(ref q) => self.execute_drop_queue(query, q),
1048 QueryExpr::QueueSelect(ref q) => self.execute_queue_select(query, q),
1049 QueryExpr::QueueCommand(ref cmd) => self
1050 .with_deferred_store_wal_if_transaction(|| self.execute_queue_command(query, cmd)),
1051 QueryExpr::EventsBackfill(ref backfill) => {
1052 self.execute_events_backfill(query, backfill)
1053 }
1054 QueryExpr::EventsBackfillStatus { ref collection } => Err(RedDBError::Query(format!(
1055 "EVENTS BACKFILL STATUS for '{collection}' is not implemented in this slice"
1056 ))),
1057 QueryExpr::KvCommand(ref cmd) => self.execute_kv_command(query, cmd),
1058 QueryExpr::ConfigCommand(ref cmd) => self.execute_config_command(query, cmd),
1059 QueryExpr::CreateTree(ref tree) => self.execute_create_tree(query, tree),
1060 QueryExpr::DropTree(ref tree) => self.execute_drop_tree(query, tree),
1061 QueryExpr::TreeCommand(ref cmd) => self.execute_tree_command(query, cmd),
1062 QueryExpr::SetConfig { ref key, ref value } => {
1064 if key.starts_with("red.secret.") {
1065 return Err(RedDBError::Query(
1066 "red.secret.* is reserved for vault secrets; use SET SECRET".to_string(),
1067 ));
1068 }
1069 if key.starts_with("red.secrets.") {
1070 return Err(RedDBError::Query(
1071 "red.secrets.* is reserved for vault secrets; use SET SECRET".to_string(),
1072 ));
1073 }
1074 match self.check_managed_config_write_for_set_config(key) {
1075 Err(err) => Err(err),
1076 Ok(()) => {
1077 let store = self.inner.db.store();
1078 let json_val = match value {
1079 Value::Text(s) => crate::serde_json::Value::String(s.to_string()),
1080 Value::Integer(n) => crate::serde_json::Value::Number(*n as f64),
1081 Value::Float(n) => crate::serde_json::Value::Number(*n),
1082 Value::Boolean(b) => crate::serde_json::Value::Bool(*b),
1083 _ => crate::serde_json::Value::String(value.to_string()),
1084 };
1085 store.set_config_tree(key, &json_val);
1086 update_current_config_value(key, value.clone());
1087 self.invalidate_result_cache();
1092 Ok(RuntimeQueryResult::ok_message(
1093 query.to_string(),
1094 &format!("config set: {key}"),
1095 "set",
1096 ))
1097 }
1098 }
1099 }
1100 QueryExpr::SetSecret { ref key, ref value } => {
1102 if key.starts_with("red.config.") {
1103 return Err(RedDBError::Query(
1104 "red.config.* is reserved for config; use SET CONFIG".to_string(),
1105 ));
1106 }
1107 let auth_store = self.inner.auth_store.read().clone().ok_or_else(|| {
1108 RedDBError::Query("SET SECRET requires an enabled, unsealed vault".to_string())
1109 })?;
1110 self.check_secret_write_privilege(&auth_store, key)?;
1111 if matches!(value, Value::Null) {
1112 auth_store
1113 .vault_kv_try_delete(key)
1114 .map_err(|err| RedDBError::Query(err.to_string()))?;
1115 update_current_secret_value(key, None);
1116 self.invalidate_result_cache();
1117 return Ok(RuntimeQueryResult::ok_message(
1118 query.to_string(),
1119 &format!("secret deleted: {key}"),
1120 "delete_secret",
1121 ));
1122 }
1123 let value = secret_sql_value_to_string(value)?;
1124 auth_store
1125 .vault_kv_try_set(key.clone(), value.clone())
1126 .map_err(|err| RedDBError::Query(err.to_string()))?;
1127 update_current_secret_value(key, Some(value));
1128 self.invalidate_result_cache();
1129 Ok(RuntimeQueryResult::ok_message(
1130 query.to_string(),
1131 &format!("secret set: {key}"),
1132 "set_secret",
1133 ))
1134 }
1135 QueryExpr::DeleteSecret { ref key } => {
1137 let auth_store = self.inner.auth_store.read().clone().ok_or_else(|| {
1138 RedDBError::Query(
1139 "DELETE SECRET requires an enabled, unsealed vault".to_string(),
1140 )
1141 })?;
1142 self.check_secret_write_privilege(&auth_store, key)?;
1143 let deleted = auth_store
1144 .vault_kv_try_delete(key)
1145 .map_err(|err| RedDBError::Query(err.to_string()))?;
1146 if deleted {
1147 update_current_secret_value(key, None);
1148 }
1149 self.invalidate_result_cache();
1150 Ok(RuntimeQueryResult::ok_message(
1151 query.to_string(),
1152 &format!("secret deleted: {key}"),
1153 if deleted {
1154 "delete_secret"
1155 } else {
1156 "delete_secret_not_found"
1157 },
1158 ))
1159 }
1160 QueryExpr::SetKv { ref key, ref value } => {
1162 let auth_store = self.inner.auth_store.read().clone().ok_or_else(|| {
1163 RedDBError::Query("SET KV requires an auth store".to_string())
1164 })?;
1165 self.check_kv_write_privilege(&auth_store, key)?;
1166 if matches!(value, Value::Null) {
1168 auth_store.plain_kv_delete(key);
1169 update_current_kv_value(key, None);
1170 self.invalidate_result_cache();
1171 return Ok(RuntimeQueryResult::ok_message(
1172 query.to_string(),
1173 &format!("kv deleted: {key}"),
1174 "delete_kv",
1175 ));
1176 }
1177 let value = secret_sql_value_to_string(value)?;
1178 auth_store.plain_kv_set(key.clone(), value.clone());
1179 update_current_kv_value(key, Some(value));
1180 self.invalidate_result_cache();
1181 Ok(RuntimeQueryResult::ok_message(
1182 query.to_string(),
1183 &format!("kv set: {key}"),
1184 "set_kv",
1185 ))
1186 }
1187 QueryExpr::DeleteKv { ref key } => {
1189 let auth_store = self.inner.auth_store.read().clone().ok_or_else(|| {
1190 RedDBError::Query("DELETE KV requires an auth store".to_string())
1191 })?;
1192 self.check_kv_write_privilege(&auth_store, key)?;
1193 let deleted = auth_store.plain_kv_delete(key);
1194 if deleted {
1195 update_current_kv_value(key, None);
1196 }
1197 self.invalidate_result_cache();
1198 Ok(RuntimeQueryResult::ok_message(
1199 query.to_string(),
1200 &format!("kv deleted: {key}"),
1201 if deleted {
1202 "delete_kv"
1203 } else {
1204 "delete_kv_not_found"
1205 },
1206 ))
1207 }
1208 QueryExpr::ShowSecrets { ref prefix } => {
1210 let auth_store = self.inner.auth_store.read().clone().ok_or_else(|| {
1211 RedDBError::Query("SHOW SECRET requires an enabled, unsealed vault".to_string())
1212 })?;
1213 if !auth_store.is_vault_backed() {
1214 return Err(RedDBError::Query(
1215 "SHOW SECRET requires an enabled, unsealed vault".to_string(),
1216 ));
1217 }
1218 let mut keys = auth_store.vault_kv_keys();
1219 keys.sort();
1220 let mut result = UnifiedResult::with_columns(vec![
1221 "key".into(),
1222 "value".into(),
1223 "status".into(),
1224 ]);
1225 for key in keys {
1226 if !show_secrets_allows_key(&key) {
1227 continue;
1228 }
1229 if let Some(ref pfx) = prefix {
1230 if !key.starts_with(pfx) {
1231 continue;
1232 }
1233 }
1234 let mut record = UnifiedRecord::new();
1235 record.set("key", Value::text(key));
1236 record.set("value", Value::text("***"));
1237 record.set("status", Value::text("active"));
1238 result.push(record);
1239 }
1240 Ok(RuntimeQueryResult {
1241 query: query.to_string(),
1242 mode,
1243 statement: "show_secrets",
1244 engine: "runtime-secret",
1245 result,
1246 affected_rows: 0,
1247 statement_type: "select",
1248 bookmark: None,
1249 })
1250 }
1251 QueryExpr::ShowConfig {
1253 ref prefix,
1254 as_json,
1255 } => {
1256 let store = self.inner.db.store();
1257 let all_collections = store.list_collections();
1258 if !all_collections.contains(&"red_config".to_string()) {
1259 if as_json {
1260 return Ok(show_config_json_result(
1261 query,
1262 mode,
1263 prefix,
1264 crate::serde_json::Value::Object(crate::serde_json::Map::new()),
1265 ));
1266 }
1267 let result = UnifiedResult::with_columns(vec!["key".into(), "value".into()]);
1268 return Ok(RuntimeQueryResult {
1269 query: query.to_string(),
1270 mode,
1271 statement: "show_config",
1272 engine: "runtime-config",
1273 result,
1274 affected_rows: 0,
1275 statement_type: "select",
1276 bookmark: None,
1277 });
1278 }
1279 let manager = store
1280 .get_collection("red_config")
1281 .ok_or_else(|| RedDBError::NotFound("red_config".to_string()))?;
1282 let entities = manager.query_all(|_| true);
1283 let mut latest = std::collections::BTreeMap::<String, (u64, Value, Value)>::new();
1284 for entity in entities {
1285 if let EntityData::Row(ref row) = entity.data {
1286 if let Some(ref named) = row.named {
1287 let key_val = named.get("key").cloned().unwrap_or(Value::Null);
1288 let val = named.get("value").cloned().unwrap_or(Value::Null);
1289 let key_str = match &key_val {
1290 Value::Text(s) => s.as_ref(),
1291 _ => continue,
1292 };
1293 if let Some(ref pfx) = prefix {
1294 if !key_str.starts_with(pfx.as_str()) {
1295 continue;
1296 }
1297 }
1298 let entity_id = entity.id.raw();
1299 match latest.get(key_str) {
1300 Some((prev_id, _, _)) if *prev_id > entity_id => {}
1301 _ => {
1302 latest.insert(key_str.to_string(), (entity_id, key_val, val));
1303 }
1304 }
1305 }
1306 }
1307 }
1308 if as_json {
1309 let mut tree = crate::serde_json::Value::Object(crate::serde_json::Map::new());
1310 for (key, (_, _, val)) in latest {
1311 let relative = match prefix {
1312 Some(pfx) if key == *pfx => "",
1313 Some(pfx) => key
1314 .strip_prefix(pfx.as_str())
1315 .and_then(|tail| tail.strip_prefix('.'))
1316 .unwrap_or(key.as_str()),
1317 None => key.as_str(),
1318 };
1319 insert_config_json_path(
1320 &mut tree,
1321 relative,
1322 crate::presentation::entity_json::storage_value_to_json(&val),
1323 );
1324 }
1325 return Ok(show_config_json_result(query, mode, prefix, tree));
1326 }
1327 let mut result = UnifiedResult::with_columns(vec!["key".into(), "value".into()]);
1328 for (_, key_val, val) in latest.into_values() {
1329 let mut record = UnifiedRecord::new();
1330 record.set("key", key_val);
1331 record.set("value", val);
1332 result.push(record);
1333 }
1334 Ok(RuntimeQueryResult {
1335 query: query.to_string(),
1336 mode,
1337 statement: "show_config",
1338 engine: "runtime-config",
1339 result,
1340 affected_rows: 0,
1341 statement_type: "select",
1342 bookmark: None,
1343 })
1344 }
1345 QueryExpr::SetTenant(ref value) => {
1351 match value {
1352 Some(id) => set_current_tenant(id.clone()),
1353 None => clear_current_tenant(),
1354 }
1355 Ok(RuntimeQueryResult::ok_message(
1356 query.to_string(),
1357 &match value {
1358 Some(id) => format!("tenant set: {id}"),
1359 None => "tenant cleared".to_string(),
1360 },
1361 "set_tenant",
1362 ))
1363 }
1364 QueryExpr::ShowTenant => {
1365 let mut result = UnifiedResult::with_columns(vec!["tenant".into()]);
1366 let mut record = UnifiedRecord::new();
1367 record.set(
1368 "tenant",
1369 current_tenant().map(Value::text).unwrap_or(Value::Null),
1370 );
1371 result.push(record);
1372 Ok(RuntimeQueryResult {
1373 query: query.to_string(),
1374 mode,
1375 statement: "show_tenant",
1376 engine: "runtime-tenant",
1377 result,
1378 affected_rows: 0,
1379 statement_type: "select",
1380 bookmark: None,
1381 })
1382 }
1383 QueryExpr::TransactionControl(ref ctl) => {
1395 use crate::storage::query::ast::TxnControl;
1396 use crate::storage::transaction::snapshot::{TxnContext, Xid};
1397 use crate::storage::transaction::IsolationLevel;
1398
1399 let conn_id = current_connection_id();
1404
1405 let (kind, msg) = match ctl {
1406 TxnControl::Begin(requested_isolation) => {
1407 let isolation = requested_isolation
1408 .map(IsolationLevel::from)
1409 .unwrap_or(IsolationLevel::SnapshotIsolation);
1410 let mgr = Arc::clone(&self.inner.snapshot_manager);
1411 let xid = mgr.begin();
1412 if isolation == IsolationLevel::Serializable {
1413 mgr.begin_serializable(xid);
1414 }
1415 let snapshot = mgr.snapshot(xid);
1416 let ctx = TxnContext {
1417 xid,
1418 isolation,
1419 snapshot,
1420 savepoints: Vec::new(),
1421 released_sub_xids: Vec::new(),
1422 };
1423 self.inner.tx_contexts.write().insert(conn_id, ctx);
1424 ("begin", format!("BEGIN — xid={xid} (snapshot isolation)"))
1425 }
1426 TxnControl::Commit => {
1427 self.inner.tx_local_tenants.write().remove(&conn_id);
1429 let ctx = self.inner.tx_contexts.write().remove(&conn_id);
1430 match ctx {
1431 Some(ctx) => {
1432 let mut own_xids = std::collections::HashSet::new();
1433 own_xids.insert(ctx.xid);
1434 for (_, sub) in &ctx.savepoints {
1435 own_xids.insert(*sub);
1436 }
1437 for sub in &ctx.released_sub_xids {
1438 own_xids.insert(*sub);
1439 }
1440 if let Err(err) = self.check_table_row_write_conflicts(
1441 conn_id,
1442 &ctx.snapshot,
1443 &own_xids,
1444 ctx.isolation,
1445 ) {
1446 for (_, sub) in &ctx.savepoints {
1447 self.inner.snapshot_manager.rollback(*sub);
1448 }
1449 for sub in &ctx.released_sub_xids {
1450 self.inner.snapshot_manager.rollback(*sub);
1451 }
1452 self.inner.snapshot_manager.rollback(ctx.xid);
1453 self.revive_pending_versioned_updates(conn_id);
1454 self.revive_pending_tombstones(conn_id);
1455 self.discard_pending_kv_watch_events(conn_id);
1456 self.discard_pending_queue_wakes(conn_id);
1457 self.discard_pending_store_wal_actions(conn_id);
1458 self.release_pending_claim_locks(conn_id);
1459 return Err(err);
1460 }
1461 self.restore_pending_write_stamps(conn_id);
1462 if let Err(err) = self.flush_pending_store_wal_actions(conn_id) {
1463 for (_, sub) in &ctx.savepoints {
1464 self.inner.snapshot_manager.rollback(*sub);
1465 }
1466 for sub in &ctx.released_sub_xids {
1467 self.inner.snapshot_manager.rollback(*sub);
1468 }
1469 self.inner.snapshot_manager.rollback(ctx.xid);
1470 self.revive_pending_versioned_updates(conn_id);
1471 self.revive_pending_tombstones(conn_id);
1472 self.discard_pending_kv_watch_events(conn_id);
1473 self.discard_pending_queue_wakes(conn_id);
1474 self.release_pending_claim_locks(conn_id);
1475 return Err(err);
1476 }
1477 for (_, sub) in &ctx.savepoints {
1483 self.inner.snapshot_manager.commit(*sub);
1484 }
1485 for sub in &ctx.released_sub_xids {
1486 self.inner.snapshot_manager.commit(*sub);
1487 }
1488 self.inner.snapshot_manager.commit(ctx.xid);
1489 self.finalize_pending_versioned_updates(conn_id);
1490 self.finalize_pending_tombstones(conn_id);
1491 self.finalize_pending_kv_watch_events(conn_id);
1492 self.finalize_pending_queue_wakes(conn_id);
1493 self.release_pending_claim_locks(conn_id);
1494 ("commit", format!("COMMIT — xid={} committed", ctx.xid))
1495 }
1496 None => (
1497 "commit",
1498 "COMMIT outside transaction — no-op (autocommit)".to_string(),
1499 ),
1500 }
1501 }
1502 TxnControl::Rollback => {
1503 self.inner.tx_local_tenants.write().remove(&conn_id);
1504 let ctx = self.inner.tx_contexts.write().remove(&conn_id);
1505 match ctx {
1506 Some(ctx) => {
1507 for (_, sub) in &ctx.savepoints {
1510 self.inner.snapshot_manager.rollback(*sub);
1511 }
1512 for sub in &ctx.released_sub_xids {
1513 self.inner.snapshot_manager.rollback(*sub);
1514 }
1515 self.inner.snapshot_manager.rollback(ctx.xid);
1516 self.revive_pending_versioned_updates(conn_id);
1520 self.revive_pending_tombstones(conn_id);
1521 self.discard_pending_kv_watch_events(conn_id);
1522 self.discard_pending_queue_wakes(conn_id);
1523 self.discard_pending_store_wal_actions(conn_id);
1524 self.release_pending_claim_locks(conn_id);
1525 ("rollback", format!("ROLLBACK — xid={} aborted", ctx.xid))
1526 }
1527 None => (
1528 "rollback",
1529 "ROLLBACK outside transaction — no-op (autocommit)".to_string(),
1530 ),
1531 }
1532 }
1533 TxnControl::Savepoint(name) => {
1540 let mgr = Arc::clone(&self.inner.snapshot_manager);
1541 let mut guard = self.inner.tx_contexts.write();
1542 match guard.get_mut(&conn_id) {
1543 Some(ctx) => {
1544 let sub = mgr.begin();
1545 ctx.savepoints.push((name.clone(), sub));
1546 ("savepoint", format!("SAVEPOINT {name} — sub_xid={sub}"))
1547 }
1548 None => (
1549 "savepoint",
1550 "SAVEPOINT outside transaction — no-op".to_string(),
1551 ),
1552 }
1553 }
1554 TxnControl::ReleaseSavepoint(name) => {
1555 let mut guard = self.inner.tx_contexts.write();
1556 match guard.get_mut(&conn_id) {
1557 Some(ctx) => {
1558 let pos = ctx
1559 .savepoints
1560 .iter()
1561 .position(|(n, _)| n == name)
1562 .ok_or_else(|| {
1563 RedDBError::Internal(format!(
1564 "savepoint {name} does not exist"
1565 ))
1566 })?;
1567 let released = ctx.savepoints.len() - pos;
1575 let popped: Vec<Xid> = ctx
1576 .savepoints
1577 .split_off(pos)
1578 .into_iter()
1579 .map(|(_, x)| x)
1580 .collect();
1581 ctx.released_sub_xids.extend(popped);
1582 (
1583 "release_savepoint",
1584 format!("RELEASE SAVEPOINT {name} — {released} level(s)"),
1585 )
1586 }
1587 None => (
1588 "release_savepoint",
1589 "RELEASE outside transaction — no-op".to_string(),
1590 ),
1591 }
1592 }
1593 TxnControl::RollbackToSavepoint(name) => {
1594 let mgr = Arc::clone(&self.inner.snapshot_manager);
1595 let drop_result: Option<(Xid, Vec<Xid>)> = {
1600 let mut guard = self.inner.tx_contexts.write();
1601 if let Some(ctx) = guard.get_mut(&conn_id) {
1602 let pos = ctx
1603 .savepoints
1604 .iter()
1605 .position(|(n, _)| n == name)
1606 .ok_or_else(|| {
1607 RedDBError::Internal(format!(
1608 "savepoint {name} does not exist"
1609 ))
1610 })?;
1611 let savepoint_xid = ctx.savepoints[pos].1;
1612 let aborted: Vec<Xid> = ctx
1613 .savepoints
1614 .split_off(pos)
1615 .into_iter()
1616 .map(|(_, x)| x)
1617 .collect();
1618 Some((savepoint_xid, aborted))
1619 } else {
1620 None
1621 }
1622 };
1623
1624 match drop_result {
1625 Some((savepoint_xid, aborted)) => {
1626 for x in &aborted {
1627 mgr.rollback(*x);
1628 }
1629 let reverted_updates =
1630 self.revive_versioned_updates_since(conn_id, savepoint_xid);
1631 let revived = self.revive_tombstones_since(conn_id, savepoint_xid);
1632 (
1633 "rollback_to_savepoint",
1634 format!(
1635 "ROLLBACK TO SAVEPOINT {name} — aborted {} sub_xid(s), reverted {reverted_updates} update(s), revived {revived} tombstone(s)",
1636 aborted.len(),
1637 ),
1638 )
1639 }
1640 None => (
1641 "rollback_to_savepoint",
1642 "ROLLBACK TO outside transaction — no-op".to_string(),
1643 ),
1644 }
1645 }
1646 };
1647 Ok(RuntimeQueryResult::ok_message(
1648 query.to_string(),
1649 &msg,
1650 kind,
1651 ))
1652 }
1653 QueryExpr::CreateSchema(ref q) => {
1666 let store = self.inner.db.store();
1667 let key = format!("schema.{}", q.name);
1668 if store.get_config(&key).is_some() {
1669 if q.if_not_exists {
1670 return Ok(RuntimeQueryResult::ok_message(
1671 query.to_string(),
1672 &format!("schema {} already exists — skipped", q.name),
1673 "create_schema",
1674 ));
1675 }
1676 return Err(RedDBError::Internal(format!(
1677 "schema {} already exists",
1678 q.name
1679 )));
1680 }
1681 store.set_config_tree(&key, &crate::serde_json::Value::Bool(true));
1682 Ok(RuntimeQueryResult::ok_message(
1683 query.to_string(),
1684 &format!("schema {} created", q.name),
1685 "create_schema",
1686 ))
1687 }
1688 QueryExpr::DropSchema(ref q) => {
1689 let store = self.inner.db.store();
1690 let key = format!("schema.{}", q.name);
1691 let existed = store.get_config(&key).is_some();
1692 if !existed && !q.if_exists {
1693 return Err(RedDBError::Internal(format!(
1694 "schema {} does not exist",
1695 q.name
1696 )));
1697 }
1698 store.set_config_tree(&key, &crate::serde_json::Value::Null);
1700 let suffix = if q.cascade {
1701 " (CASCADE accepted — tables untouched)"
1702 } else {
1703 ""
1704 };
1705 Ok(RuntimeQueryResult::ok_message(
1706 query.to_string(),
1707 &format!("schema {} dropped{}", q.name, suffix),
1708 "drop_schema",
1709 ))
1710 }
1711 QueryExpr::CreateSequence(ref q) => {
1712 let store = self.inner.db.store();
1713 let base = format!("sequence.{}", q.name);
1714 let start_key = format!("{base}.start");
1715 let incr_key = format!("{base}.increment");
1716 let curr_key = format!("{base}.current");
1717 if store.get_config(&start_key).is_some() {
1718 if q.if_not_exists {
1719 return Ok(RuntimeQueryResult::ok_message(
1720 query.to_string(),
1721 &format!("sequence {} already exists — skipped", q.name),
1722 "create_sequence",
1723 ));
1724 }
1725 return Err(RedDBError::Internal(format!(
1726 "sequence {} already exists",
1727 q.name
1728 )));
1729 }
1730 let initial_current = q.start - q.increment;
1733 store.set_config_tree(
1734 &start_key,
1735 &crate::serde_json::Value::Number(q.start as f64),
1736 );
1737 store.set_config_tree(
1738 &incr_key,
1739 &crate::serde_json::Value::Number(q.increment as f64),
1740 );
1741 store.set_config_tree(
1742 &curr_key,
1743 &crate::serde_json::Value::Number(initial_current as f64),
1744 );
1745 Ok(RuntimeQueryResult::ok_message(
1746 query.to_string(),
1747 &format!(
1748 "sequence {} created (start={}, increment={})",
1749 q.name, q.start, q.increment
1750 ),
1751 "create_sequence",
1752 ))
1753 }
1754 QueryExpr::DropSequence(ref q) => {
1755 let store = self.inner.db.store();
1756 let base = format!("sequence.{}", q.name);
1757 let existed = store.get_config(&format!("{base}.start")).is_some();
1758 if !existed && !q.if_exists {
1759 return Err(RedDBError::Internal(format!(
1760 "sequence {} does not exist",
1761 q.name
1762 )));
1763 }
1764 for k in ["start", "increment", "current"] {
1765 store.set_config_tree(&format!("{base}.{k}"), &crate::serde_json::Value::Null);
1766 }
1767 Ok(RuntimeQueryResult::ok_message(
1768 query.to_string(),
1769 &format!("sequence {} dropped", q.name),
1770 "drop_sequence",
1771 ))
1772 }
1773 QueryExpr::CreateView(ref q) => {
1783 let mut views = self.inner.views.write();
1784 if views.contains_key(&q.name) && !q.or_replace {
1785 if q.if_not_exists {
1786 return Ok(RuntimeQueryResult::ok_message(
1787 query.to_string(),
1788 &format!("view {} already exists — skipped", q.name),
1789 "create_view",
1790 ));
1791 }
1792 return Err(RedDBError::Internal(format!(
1793 "view {} already exists",
1794 q.name
1795 )));
1796 }
1797 views.insert(q.name.clone(), Arc::new(q.clone()));
1798 drop(views);
1799
1800 if q.materialized {
1802 use crate::storage::cache::result::{MaterializedViewDef, RefreshPolicy};
1803 let refresh = match q.refresh_every_ms {
1804 Some(ms) => RefreshPolicy::Periodic(std::time::Duration::from_millis(ms)),
1805 None => RefreshPolicy::Manual,
1806 };
1807 let dependencies = collect_table_refs(&q.query);
1808 let def = MaterializedViewDef {
1809 name: q.name.clone(),
1810 query: format!("<parsed view {}>", q.name),
1811 dependencies: dependencies.clone(),
1812 refresh,
1813 retention_duration_ms: q.retention_duration_ms,
1814 };
1815 self.inner.materialized_views.write().register(def);
1816
1817 let descriptor =
1823 crate::runtime::continuous_materialized_view::MaterializedViewDescriptor {
1824 name: q.name.clone(),
1825 source_sql: query.to_string(),
1826 source_collections: dependencies,
1827 refresh_every_ms: q.refresh_every_ms,
1828 retention_duration_ms: q.retention_duration_ms,
1829 };
1830 let store = self.inner.db.store();
1831 crate::runtime::continuous_materialized_view::persist_descriptor(
1832 store.as_ref(),
1833 &descriptor,
1834 )?;
1835
1836 self.ensure_materialized_view_backing(&q.name)?;
1843 }
1844 self.invalidate_plan_cache();
1849 self.invalidate_result_cache();
1850
1851 Ok(RuntimeQueryResult::ok_message(
1852 query.to_string(),
1853 &format!(
1854 "{}view {} created",
1855 if q.materialized { "materialized " } else { "" },
1856 q.name
1857 ),
1858 "create_view",
1859 ))
1860 }
1861 QueryExpr::DropView(ref q) => {
1862 let mut views = self.inner.views.write();
1863 let removed = views.remove(&q.name);
1864 let existed = removed.is_some();
1865 let removed_materialized =
1866 removed.as_ref().map(|v| v.materialized).unwrap_or(false);
1867 drop(views);
1868 if q.materialized || existed {
1869 self.inner.materialized_views.write().remove(&q.name);
1871 let store = self.inner.db.store();
1875 crate::runtime::continuous_materialized_view::remove_by_name(
1876 store.as_ref(),
1877 &q.name,
1878 )?;
1879 }
1880 if removed_materialized || q.materialized {
1884 self.drop_materialized_view_backing(&q.name)?;
1885 }
1886 self.invalidate_plan_cache();
1889 self.invalidate_result_cache();
1890 if !existed && !q.if_exists {
1891 return Err(RedDBError::Internal(format!(
1892 "view {} does not exist",
1893 q.name
1894 )));
1895 }
1896 self.invalidate_plan_cache();
1897 Ok(RuntimeQueryResult::ok_message(
1898 query.to_string(),
1899 &format!("view {} dropped", q.name),
1900 "drop_view",
1901 ))
1902 }
1903 QueryExpr::RefreshMaterializedView(ref q) => {
1904 let view = {
1907 let views = self.inner.views.read();
1908 views.get(&q.name).cloned()
1909 };
1910 let view = match view {
1911 Some(v) => v,
1912 None => {
1913 return Err(RedDBError::Internal(format!(
1914 "view {} does not exist",
1915 q.name
1916 )))
1917 }
1918 };
1919 if !view.materialized {
1920 return Err(RedDBError::Internal(format!(
1921 "view {} is not materialized — REFRESH requires \
1922 CREATE MATERIALIZED VIEW",
1923 q.name
1924 )));
1925 }
1926 let started = std::time::Instant::now();
1928 let now_ms = std::time::SystemTime::now()
1929 .duration_since(std::time::UNIX_EPOCH)
1930 .map(|d| d.as_millis() as u64)
1931 .unwrap_or(0);
1932 match self.execute_query_expr((*view.query).clone()) {
1933 Ok(inner_result) => {
1934 let entities =
1941 view_records_to_entities(&q.name, &inner_result.result.records);
1942 let row_count = entities.len() as u64;
1943 let store = self.inner.db.store();
1944 let serialized_records = match store.refresh_collection(&q.name, entities) {
1945 Ok(records) => records,
1946 Err(err) => {
1947 let duration_ms = started.elapsed().as_millis() as u64;
1948 let msg = err.to_string();
1949 self.inner
1950 .materialized_views
1951 .write()
1952 .record_refresh_failure(
1953 &q.name,
1954 msg.clone(),
1955 duration_ms,
1956 now_ms,
1957 );
1958 return Err(RedDBError::Internal(format!(
1959 "REFRESH MATERIALIZED VIEW {}: {msg}",
1960 q.name
1961 )));
1962 }
1963 };
1964
1965 if let Some(ref primary) = self.inner.db.replication {
1971 let lsn = self.inner.cdc.emit(
1972 crate::replication::cdc::ChangeOperation::Refresh,
1973 &q.name,
1974 0,
1975 "refresh",
1976 );
1977 self.invalidate_result_cache_for_table(&q.name);
1978 let timestamp = std::time::SystemTime::now()
1979 .duration_since(std::time::UNIX_EPOCH)
1980 .unwrap_or_default()
1981 .as_millis() as u64;
1982 let record = ChangeRecord::for_refresh(
1983 lsn,
1984 timestamp,
1985 q.name.clone(),
1986 serialized_records,
1987 )
1988 .with_term(self.current_replication_term());
1989 let encoded = record.encode();
1990 primary.append_logical_record(record.lsn, encoded);
1991 }
1992
1993 let duration_ms = started.elapsed().as_millis() as u64;
1994 let serialized = format!("{:?}", inner_result.result);
1995 self.inner
1996 .materialized_views
1997 .write()
1998 .record_refresh_success(
1999 &q.name,
2000 serialized.into_bytes(),
2001 row_count,
2002 duration_ms,
2003 now_ms,
2004 );
2005 self.invalidate_result_cache();
2010 Ok(RuntimeQueryResult::ok_message(
2011 query.to_string(),
2012 &format!("materialized view {} refreshed", q.name),
2013 "refresh_materialized_view",
2014 ))
2015 }
2016 Err(err) => {
2017 let duration_ms = started.elapsed().as_millis() as u64;
2018 let msg = err.to_string();
2019 self.inner
2020 .materialized_views
2021 .write()
2022 .record_refresh_failure(&q.name, msg.clone(), duration_ms, now_ms);
2023 Err(err)
2024 }
2025 }
2026 }
2027 QueryExpr::CreatePolicy(ref q) => {
2034 let key = (q.table.clone(), q.name.clone());
2035 self.inner
2036 .rls_policies
2037 .write()
2038 .insert(key, Arc::new(q.clone()));
2039 self.invalidate_plan_cache();
2040 self.schema_vocabulary_apply(
2044 crate::runtime::schema_vocabulary::DdlEvent::CreatePolicy {
2045 collection: q.table.clone(),
2046 policy: q.name.clone(),
2047 },
2048 );
2049 Ok(RuntimeQueryResult::ok_message(
2050 query.to_string(),
2051 &format!("policy {} on {} created", q.name, q.table),
2052 "create_policy",
2053 ))
2054 }
2055 QueryExpr::DropPolicy(ref q) => {
2056 let removed = self
2057 .inner
2058 .rls_policies
2059 .write()
2060 .remove(&(q.table.clone(), q.name.clone()))
2061 .is_some();
2062 if !removed && !q.if_exists {
2063 return Err(RedDBError::Internal(format!(
2064 "policy {} on {} does not exist",
2065 q.name, q.table
2066 )));
2067 }
2068 self.invalidate_plan_cache();
2069 self.schema_vocabulary_apply(
2072 crate::runtime::schema_vocabulary::DdlEvent::DropPolicy {
2073 collection: q.table.clone(),
2074 policy: q.name.clone(),
2075 },
2076 );
2077 Ok(RuntimeQueryResult::ok_message(
2078 query.to_string(),
2079 &format!("policy {} on {} dropped", q.name, q.table),
2080 "drop_policy",
2081 ))
2082 }
2083 QueryExpr::CreateServer(ref q) => {
2094 use crate::storage::fdw::FdwOptions;
2095 let registry = Arc::clone(&self.inner.foreign_tables);
2096 if registry.server(&q.name).is_some() {
2097 if q.if_not_exists {
2098 return Ok(RuntimeQueryResult::ok_message(
2099 query.to_string(),
2100 &format!("server {} already exists — skipped", q.name),
2101 "create_server",
2102 ));
2103 }
2104 return Err(RedDBError::Internal(format!(
2105 "server {} already exists",
2106 q.name
2107 )));
2108 }
2109 let mut opts = FdwOptions::new();
2110 for (k, v) in &q.options {
2111 opts.values.insert(k.clone(), v.clone());
2112 }
2113 registry
2114 .create_server(&q.name, &q.wrapper, opts)
2115 .map_err(|e| RedDBError::Internal(e.to_string()))?;
2116 Ok(RuntimeQueryResult::ok_message(
2117 query.to_string(),
2118 &format!("server {} created (wrapper {})", q.name, q.wrapper),
2119 "create_server",
2120 ))
2121 }
2122 QueryExpr::DropServer(ref q) => {
2123 let existed = self.inner.foreign_tables.drop_server(&q.name);
2124 if !existed && !q.if_exists {
2125 return Err(RedDBError::Internal(format!(
2126 "server {} does not exist",
2127 q.name
2128 )));
2129 }
2130 Ok(RuntimeQueryResult::ok_message(
2131 query.to_string(),
2132 &format!(
2133 "server {} dropped{}",
2134 q.name,
2135 if q.cascade { " (cascade)" } else { "" }
2136 ),
2137 "drop_server",
2138 ))
2139 }
2140 QueryExpr::CreateForeignTable(ref q) => {
2141 use crate::storage::fdw::{FdwOptions, ForeignColumn, ForeignTable};
2142 let registry = Arc::clone(&self.inner.foreign_tables);
2143 if registry.foreign_table(&q.name).is_some() {
2144 if q.if_not_exists {
2145 return Ok(RuntimeQueryResult::ok_message(
2146 query.to_string(),
2147 &format!("foreign table {} already exists — skipped", q.name),
2148 "create_foreign_table",
2149 ));
2150 }
2151 return Err(RedDBError::Internal(format!(
2152 "foreign table {} already exists",
2153 q.name
2154 )));
2155 }
2156 let mut opts = FdwOptions::new();
2157 for (k, v) in &q.options {
2158 opts.values.insert(k.clone(), v.clone());
2159 }
2160 let columns: Vec<ForeignColumn> = q
2161 .columns
2162 .iter()
2163 .map(|c| ForeignColumn {
2164 name: c.name.clone(),
2165 data_type: c.data_type.clone(),
2166 not_null: c.not_null,
2167 })
2168 .collect();
2169 registry
2170 .create_foreign_table(ForeignTable {
2171 name: q.name.clone(),
2172 server_name: q.server.clone(),
2173 columns,
2174 options: opts,
2175 })
2176 .map_err(|e| RedDBError::Internal(e.to_string()))?;
2177 self.invalidate_plan_cache();
2178 Ok(RuntimeQueryResult::ok_message(
2179 query.to_string(),
2180 &format!("foreign table {} created (server {})", q.name, q.server),
2181 "create_foreign_table",
2182 ))
2183 }
2184 QueryExpr::DropForeignTable(ref q) => {
2185 let existed = self.inner.foreign_tables.drop_foreign_table(&q.name);
2186 if !existed && !q.if_exists {
2187 return Err(RedDBError::Internal(format!(
2188 "foreign table {} does not exist",
2189 q.name
2190 )));
2191 }
2192 self.invalidate_plan_cache();
2193 Ok(RuntimeQueryResult::ok_message(
2194 query.to_string(),
2195 &format!("foreign table {} dropped", q.name),
2196 "drop_foreign_table",
2197 ))
2198 }
2199 QueryExpr::CopyFrom(ref q) => {
2205 use crate::storage::import::{CsvConfig, CsvImporter};
2206 let store = self.inner.db.store();
2207 let cfg = CsvConfig {
2208 collection: q.table.clone(),
2209 has_header: q.has_header,
2210 delimiter: q.delimiter.map(|c| c as u8).unwrap_or(b','),
2211 ..CsvConfig::default()
2212 };
2213 let importer = CsvImporter::new(cfg);
2214 let stats = importer
2215 .import_file(&q.path, store.as_ref())
2216 .map_err(|e| RedDBError::Internal(format!("COPY failed: {e}")))?;
2217 self.note_table_write(&q.table);
2219 Ok(RuntimeQueryResult::ok_message(
2220 query.to_string(),
2221 &format!(
2222 "COPY imported {} rows into {} ({} errors skipped, {}ms)",
2223 stats.records_imported, q.table, stats.errors_skipped, stats.duration_ms
2224 ),
2225 "copy_from",
2226 ))
2227 }
2228 QueryExpr::MaintenanceCommand(ref cmd) => {
2244 use crate::storage::query::ast::MaintenanceCommand as Mc;
2245 let store = self.inner.db.store();
2246 let (kind, msg) = match cmd {
2247 Mc::Analyze { target } => {
2248 let targets: Vec<String> = match target {
2249 Some(t) => vec![t.clone()],
2250 None => store.list_collections(),
2251 };
2252 for t in &targets {
2253 self.refresh_table_planner_stats(t);
2254 }
2255 (
2256 "analyze",
2257 format!("ANALYZE refreshed stats for {} table(s)", targets.len()),
2258 )
2259 }
2260 Mc::Vacuum { target, full } => {
2261 let targets: Vec<String> = match target {
2262 Some(t) => vec![t.clone()],
2263 None => store.list_collections(),
2264 };
2265 let cutoff_xid = self.mvcc_vacuum_cutoff_xid();
2266 let mut vacuum_stats =
2267 crate::storage::unified::store::MvccVacuumStats::default();
2268 for t in &targets {
2269 let stats = store.vacuum_mvcc_history(t, cutoff_xid).map_err(|e| {
2270 RedDBError::Internal(format!(
2271 "VACUUM MVCC history failed for {t}: {e}"
2272 ))
2273 })?;
2274 if stats.reclaimed_versions > 0 {
2275 self.rebuild_runtime_indexes_for_table(t)?;
2276 }
2277 vacuum_stats.add(&stats);
2278 }
2279 self.inner.snapshot_manager.prune_aborted(cutoff_xid);
2280 for t in &targets {
2282 self.refresh_table_planner_stats(t);
2283 }
2284 let persisted = if *full {
2288 match store.persist() {
2289 Ok(()) => true,
2290 Err(e) => {
2291 return Err(RedDBError::Internal(format!(
2292 "VACUUM FULL persist failed: {e:?}"
2293 )));
2294 }
2295 }
2296 } else {
2297 false
2298 };
2299 self.invalidate_result_cache();
2301 (
2302 "vacuum",
2303 format!(
2304 "VACUUM{} processed {} table(s): scanned_versions={}, retained_versions={}, reclaimed_versions={}, retained_history_versions={}, reclaimed_history_versions={}, retained_tombstones={}, reclaimed_tombstones={}{}",
2305 if *full { " FULL" } else { "" },
2306 targets.len(),
2307 vacuum_stats.scanned_versions,
2308 vacuum_stats.retained_versions,
2309 vacuum_stats.reclaimed_versions,
2310 vacuum_stats.retained_history_versions,
2311 vacuum_stats.reclaimed_history_versions,
2312 vacuum_stats.retained_tombstones,
2313 vacuum_stats.reclaimed_tombstones,
2314 if persisted {
2315 " (pages flushed to disk)"
2316 } else {
2317 ""
2318 }
2319 ),
2320 )
2321 }
2322 };
2323 Ok(RuntimeQueryResult::ok_message(
2324 query.to_string(),
2325 &msg,
2326 kind,
2327 ))
2328 }
2329 QueryExpr::Grant(ref g) => self.execute_grant_statement(query, g),
2336 QueryExpr::Revoke(ref r) => self.execute_revoke_statement(query, r),
2337 QueryExpr::AlterUser(ref a) => self.execute_alter_user_statement(query, a),
2338 QueryExpr::CreateUser(ref u) => self.execute_create_user_statement(query, u),
2339 QueryExpr::CreateIamPolicy { ref id, ref json } => {
2340 self.execute_create_iam_policy(query, id, json)
2341 }
2342 QueryExpr::DropIamPolicy { ref id } => self.execute_drop_iam_policy(query, id),
2343 QueryExpr::AttachPolicy {
2344 ref policy_id,
2345 ref principal,
2346 } => self.execute_attach_policy(query, policy_id, principal),
2347 QueryExpr::DetachPolicy {
2348 ref policy_id,
2349 ref principal,
2350 } => self.execute_detach_policy(query, policy_id, principal),
2351 QueryExpr::ShowPolicies { ref filter } => {
2352 self.execute_show_policies(query, filter.as_ref())
2353 }
2354 QueryExpr::ShowEffectivePermissions {
2355 ref user,
2356 ref resource,
2357 } => self.execute_show_effective_permissions(query, user, resource.as_ref()),
2358 QueryExpr::SimulatePolicy {
2359 ref user,
2360 ref action,
2361 ref resource,
2362 } => self.execute_simulate_policy(query, user, action, resource),
2363 QueryExpr::LintPolicy { ref source } => self.execute_lint_policy(query, source),
2364 QueryExpr::MigratePolicyMode {
2365 ref target,
2366 dry_run,
2367 } => self.execute_migrate_policy_mode(query, target, dry_run),
2368 QueryExpr::CreateMigration(ref q) => self.execute_create_migration(query, q),
2369 QueryExpr::ApplyMigration(ref q) => self.execute_apply_migration(query, q),
2370 QueryExpr::RollbackMigration(ref q) => self.execute_rollback_migration(query, q),
2371 QueryExpr::ExplainMigration(ref q) => self.execute_explain_migration(query, q),
2372 _ => Err(RedDBError::Query(
2373 "unsupported command in runtime dispatcher".to_string(),
2374 )),
2375 };
2376
2377 if !control_event_specs.is_empty() {
2378 let (outcome, reason) = match &query_result {
2379 Ok(_) => (crate::runtime::control_events::Outcome::Allowed, None),
2380 Err(err) => (control_event_outcome_for_error(err), Some(err.to_string())),
2381 };
2382 for spec in &control_event_specs {
2383 self.emit_control_event(
2384 spec.kind,
2385 outcome,
2386 spec.action,
2387 spec.resource.clone(),
2388 reason.clone(),
2389 spec.fields.clone(),
2390 )?;
2391 }
2392 }
2393
2394 if let (Some(plan), Ok(result)) = (&query_audit_plan, &query_result) {
2395 self.emit_query_audit(
2396 query,
2397 plan,
2398 query_audit_started.elapsed().as_millis() as u64,
2399 result,
2400 );
2401 }
2402
2403 let mut query_result = query_result;
2407 if let Ok(ref mut result) = query_result {
2408 if result.statement_type == "select" {
2409 self.apply_secret_decryption(result);
2410 }
2411 }
2412
2413 if let Ok(ref result) = query_result {
2420 frame.write_result_cache(self, result, result_cache_scopes);
2421 }
2422
2423 query_result
2424 }
2425
2426 pub fn execute_query_expr(&self, expr: QueryExpr) -> RedDBResult<RuntimeQueryResult> {
2432 let _config_snapshot_guard = ConfigSnapshotGuard::install(Arc::clone(&self.inner.db));
2433 let _secret_store_guard = SecretStoreGuard::install(self.inner.auth_store.read().clone());
2434 let expr = self.rewrite_view_refs(expr);
2438
2439 self.validate_model_operations_before_auth(&expr)?;
2440 if let Err(err) = self.check_query_privilege(&expr) {
2444 return Err(RedDBError::Query(format!("permission denied: {err}")));
2445 }
2446
2447 let statement = query_expr_name(&expr);
2448 let mode = detect_mode(statement);
2449 let query_str = statement;
2450
2451 let result = self.dispatch_expr(expr, query_str, mode)?;
2452 let mut r = result;
2453 if r.statement_type == "select" {
2454 self.apply_secret_decryption(&mut r);
2455 }
2456 Ok(r)
2457 }
2458
2459 pub(super) fn validate_model_operations_before_auth(
2460 &self,
2461 expr: &QueryExpr,
2462 ) -> RedDBResult<()> {
2463 use crate::catalog::CollectionModel;
2464 use crate::runtime::ddl::polymorphic_resolver;
2465 use crate::storage::query::ast::KvCommand;
2466
2467 let system_schema_target = match expr {
2468 QueryExpr::DropTable(q) => Some(q.name.as_str()),
2469 QueryExpr::DropGraph(q) => Some(q.name.as_str()),
2470 QueryExpr::DropVector(q) => Some(q.name.as_str()),
2471 QueryExpr::DropDocument(q) => Some(q.name.as_str()),
2472 QueryExpr::DropKv(q) => Some(q.name.as_str()),
2473 QueryExpr::DropCollection(q) => Some(q.name.as_str()),
2474 QueryExpr::Truncate(q) => Some(q.name.as_str()),
2475 _ => None,
2476 };
2477 if system_schema_target.is_some_and(crate::runtime::impl_ddl::is_system_schema_name) {
2478 return Err(RedDBError::Query("system schema is read-only".to_string()));
2479 }
2480
2481 let expected = match expr {
2482 QueryExpr::DropTable(q) => Some((q.name.as_str(), CollectionModel::Table)),
2483 QueryExpr::DropGraph(q) => Some((q.name.as_str(), CollectionModel::Graph)),
2484 QueryExpr::DropVector(q) => Some((q.name.as_str(), CollectionModel::Vector)),
2485 QueryExpr::DropDocument(q) => Some((q.name.as_str(), CollectionModel::Document)),
2486 QueryExpr::DropKv(q) => Some((q.name.as_str(), q.model)),
2487 QueryExpr::DropCollection(q) => q.model.map(|model| (q.name.as_str(), model)),
2488 QueryExpr::Truncate(q) => q.model.map(|model| (q.name.as_str(), model)),
2489 QueryExpr::KvCommand(cmd) => {
2490 let (collection, model) = match cmd {
2491 KvCommand::Put {
2492 collection, model, ..
2493 }
2494 | KvCommand::Get {
2495 collection, model, ..
2496 }
2497 | KvCommand::Incr {
2498 collection, model, ..
2499 }
2500 | KvCommand::Cas {
2501 collection, model, ..
2502 }
2503 | KvCommand::List {
2504 collection, model, ..
2505 }
2506 | KvCommand::Delete {
2507 collection, model, ..
2508 } => (collection.as_str(), *model),
2509 KvCommand::Rotate { collection, .. }
2510 | KvCommand::History { collection, .. }
2511 | KvCommand::Purge { collection, .. } => {
2512 (collection.as_str(), CollectionModel::Vault)
2513 }
2514 KvCommand::InvalidateTags { collection, .. } => {
2515 (collection.as_str(), CollectionModel::Kv)
2516 }
2517 KvCommand::Watch {
2518 collection, model, ..
2519 } => (collection.as_str(), *model),
2520 KvCommand::Unseal { collection, .. } => {
2521 (collection.as_str(), CollectionModel::Vault)
2522 }
2523 };
2524 Some((collection, model))
2525 }
2526 QueryExpr::ConfigCommand(cmd) => {
2527 self.validate_config_command_before_auth(cmd)?;
2528 None
2529 }
2530 _ => None,
2531 };
2532
2533 let Some((name, expected_model)) = expected else {
2534 return Ok(());
2535 };
2536 let snapshot = self.inner.db.catalog_model_snapshot();
2537 let Some(actual_model) = snapshot
2538 .collections
2539 .iter()
2540 .find(|collection| collection.name == name)
2541 .map(|collection| collection.declared_model.unwrap_or(collection.model))
2542 else {
2543 return Ok(());
2544 };
2545 polymorphic_resolver::ensure_model_match(expected_model, actual_model)
2546 }
2547
2548 pub(super) fn rewrite_view_refs(&self, expr: QueryExpr) -> QueryExpr {
2553 if self.inner.views.read().is_empty() {
2555 return expr;
2556 }
2557 self.rewrite_view_refs_inner(expr)
2558 }
2559
2560 fn rewrite_view_refs_inner(&self, expr: QueryExpr) -> QueryExpr {
2561 use crate::storage::query::ast::{Filter, TableSource};
2562 match expr {
2563 QueryExpr::Table(mut tq) => {
2564 if let Some(TableSource::Subquery(body)) = tq.source.take() {
2570 tq.source = Some(TableSource::Subquery(Box::new(
2571 self.rewrite_view_refs_inner(*body),
2572 )));
2573 return QueryExpr::Table(tq);
2574 }
2575
2576 let maybe_view = {
2580 let views = self.inner.views.read();
2581 views.get(&tq.table).cloned()
2582 };
2583 let Some(view) = maybe_view else {
2584 return QueryExpr::Table(tq);
2585 };
2586
2587 if view.materialized {
2593 return QueryExpr::Table(tq);
2594 }
2595
2596 let inner_expr = self.rewrite_view_refs_inner((*view.query).clone());
2600
2601 match inner_expr {
2609 QueryExpr::Table(mut inner_tq) => {
2610 if let Some(outer_filter) = tq.filter.take() {
2611 inner_tq.filter = Some(match inner_tq.filter.take() {
2612 Some(existing) => {
2613 Filter::And(Box::new(existing), Box::new(outer_filter))
2614 }
2615 None => outer_filter,
2616 });
2617 inner_tq.where_expr = inner_tq
2625 .filter
2626 .as_ref()
2627 .map(crate::storage::query::sql_lowering::filter_to_expr);
2628 }
2629 if let Some(outer_limit) = tq.limit {
2630 inner_tq.limit = Some(match inner_tq.limit {
2631 Some(existing) => existing.min(outer_limit),
2632 None => outer_limit,
2633 });
2634 }
2635 if let Some(outer_offset) = tq.offset {
2636 inner_tq.offset = Some(match inner_tq.offset {
2637 Some(existing) => existing + outer_offset,
2638 None => outer_offset,
2639 });
2640 }
2641 QueryExpr::Table(inner_tq)
2642 }
2643 other => other,
2644 }
2645 }
2646 QueryExpr::Join(mut jq) => {
2647 jq.left = Box::new(self.rewrite_view_refs_inner(*jq.left));
2648 jq.right = Box::new(self.rewrite_view_refs_inner(*jq.right));
2649 QueryExpr::Join(jq)
2650 }
2651 other => other,
2654 }
2655 }
2656
2657 fn resolve_table_expr_subqueries(
2658 &self,
2659 mut table: TableQuery,
2660 frame: &dyn super::statement_frame::ReadFrame,
2661 ) -> RedDBResult<TableQuery> {
2662 match table.source.take() {
2669 Some(TableSource::Subquery(inner)) => {
2670 let inner = self.resolve_select_expr_subqueries(*inner, frame)?;
2671 table.source = Some(TableSource::Subquery(Box::new(inner)));
2672 }
2673 other => table.source = other,
2674 }
2675
2676 let outer_scopes = relation_scopes_for_query(&QueryExpr::Table(table.clone()));
2677 for item in &mut table.select_items {
2678 if let crate::storage::query::ast::SelectItem::Expr { expr, .. } = item {
2679 *expr = self.resolve_expr_subqueries(expr.clone(), &outer_scopes, frame)?;
2680 }
2681 }
2682 if let Some(where_expr) = table.where_expr.take() {
2683 table.where_expr =
2684 Some(self.resolve_expr_subqueries(where_expr, &outer_scopes, frame)?);
2685 table.filter = None;
2686 }
2687 if let Some(having_expr) = table.having_expr.take() {
2688 table.having_expr =
2689 Some(self.resolve_expr_subqueries(having_expr, &outer_scopes, frame)?);
2690 table.having = None;
2691 }
2692 for expr in &mut table.group_by_exprs {
2693 *expr = self.resolve_expr_subqueries(expr.clone(), &outer_scopes, frame)?;
2694 }
2695 for clause in &mut table.order_by {
2696 if let Some(expr) = clause.expr.take() {
2697 clause.expr = Some(self.resolve_expr_subqueries(expr, &outer_scopes, frame)?);
2698 }
2699 }
2700 Ok(table)
2701 }
2702
2703 fn resolve_select_expr_subqueries(
2704 &self,
2705 expr: QueryExpr,
2706 frame: &dyn super::statement_frame::ReadFrame,
2707 ) -> RedDBResult<QueryExpr> {
2708 match expr {
2709 QueryExpr::Table(table) => self
2710 .resolve_table_expr_subqueries(table, frame)
2711 .map(QueryExpr::Table),
2712 QueryExpr::Join(mut join) => {
2713 join.left = Box::new(self.resolve_select_expr_subqueries(*join.left, frame)?);
2714 join.right = Box::new(self.resolve_select_expr_subqueries(*join.right, frame)?);
2715 Ok(QueryExpr::Join(join))
2716 }
2717 other => Ok(other),
2718 }
2719 }
2720
2721 fn resolve_expr_subqueries(
2722 &self,
2723 expr: crate::storage::query::ast::Expr,
2724 outer_scopes: &[String],
2725 frame: &dyn super::statement_frame::ReadFrame,
2726 ) -> RedDBResult<crate::storage::query::ast::Expr> {
2727 use crate::storage::query::ast::Expr;
2728
2729 match expr {
2730 Expr::Subquery { query, span } => {
2731 let values = self.execute_expr_subquery_values(query, outer_scopes, frame)?;
2732 if values.len() > 1 {
2733 return Err(RedDBError::Query(
2734 "scalar subquery returned more than one row".to_string(),
2735 ));
2736 }
2737 Ok(Expr::Literal {
2738 value: values.into_iter().next().unwrap_or(Value::Null),
2739 span,
2740 })
2741 }
2742 Expr::BinaryOp { op, lhs, rhs, span } => Ok(Expr::BinaryOp {
2743 op,
2744 lhs: Box::new(self.resolve_expr_subqueries(*lhs, outer_scopes, frame)?),
2745 rhs: Box::new(self.resolve_expr_subqueries(*rhs, outer_scopes, frame)?),
2746 span,
2747 }),
2748 Expr::UnaryOp { op, operand, span } => Ok(Expr::UnaryOp {
2749 op,
2750 operand: Box::new(self.resolve_expr_subqueries(*operand, outer_scopes, frame)?),
2751 span,
2752 }),
2753 Expr::Cast {
2754 inner,
2755 target,
2756 span,
2757 } => Ok(Expr::Cast {
2758 inner: Box::new(self.resolve_expr_subqueries(*inner, outer_scopes, frame)?),
2759 target,
2760 span,
2761 }),
2762 Expr::FunctionCall { name, args, span } => {
2763 let args = args
2764 .into_iter()
2765 .map(|arg| self.resolve_expr_subqueries(arg, outer_scopes, frame))
2766 .collect::<RedDBResult<Vec<_>>>()?;
2767 Ok(Expr::FunctionCall { name, args, span })
2768 }
2769 Expr::Case {
2770 branches,
2771 else_,
2772 span,
2773 } => {
2774 let branches = branches
2775 .into_iter()
2776 .map(|(cond, value)| {
2777 Ok((
2778 self.resolve_expr_subqueries(cond, outer_scopes, frame)?,
2779 self.resolve_expr_subqueries(value, outer_scopes, frame)?,
2780 ))
2781 })
2782 .collect::<RedDBResult<Vec<_>>>()?;
2783 let else_ = else_
2784 .map(|expr| self.resolve_expr_subqueries(*expr, outer_scopes, frame))
2785 .transpose()?
2786 .map(Box::new);
2787 Ok(Expr::Case {
2788 branches,
2789 else_,
2790 span,
2791 })
2792 }
2793 Expr::IsNull {
2794 operand,
2795 negated,
2796 span,
2797 } => Ok(Expr::IsNull {
2798 operand: Box::new(self.resolve_expr_subqueries(*operand, outer_scopes, frame)?),
2799 negated,
2800 span,
2801 }),
2802 Expr::InList {
2803 target,
2804 values,
2805 negated,
2806 span,
2807 } => {
2808 let target =
2809 Box::new(self.resolve_expr_subqueries(*target, outer_scopes, frame)?);
2810 let mut resolved = Vec::new();
2811 for value in values {
2812 if let Expr::Subquery { query, .. } = value {
2813 resolved.extend(
2814 self.execute_expr_subquery_values(query, outer_scopes, frame)?
2815 .into_iter()
2816 .map(Expr::lit),
2817 );
2818 } else {
2819 resolved.push(self.resolve_expr_subqueries(value, outer_scopes, frame)?);
2820 }
2821 }
2822 Ok(Expr::InList {
2823 target,
2824 values: resolved,
2825 negated,
2826 span,
2827 })
2828 }
2829 Expr::Between {
2830 target,
2831 low,
2832 high,
2833 negated,
2834 span,
2835 } => Ok(Expr::Between {
2836 target: Box::new(self.resolve_expr_subqueries(*target, outer_scopes, frame)?),
2837 low: Box::new(self.resolve_expr_subqueries(*low, outer_scopes, frame)?),
2838 high: Box::new(self.resolve_expr_subqueries(*high, outer_scopes, frame)?),
2839 negated,
2840 span,
2841 }),
2842 other => Ok(other),
2843 }
2844 }
2845
2846 fn execute_expr_subquery_values(
2847 &self,
2848 subquery: crate::storage::query::ast::ExprSubquery,
2849 outer_scopes: &[String],
2850 frame: &dyn super::statement_frame::ReadFrame,
2851 ) -> RedDBResult<Vec<Value>> {
2852 let query = *subquery.query;
2853 if query_references_outer_scope(&query, outer_scopes) {
2854 return Err(RedDBError::Query(
2855 "NOT_YET_SUPPORTED: correlated subqueries are not supported yet; track follow-up issue #470-correlated-subqueries".to_string(),
2856 ));
2857 }
2858 let query = self.rewrite_view_refs(query);
2859 let query = self.resolve_select_expr_subqueries(query, frame)?;
2860 let query = self.authorize_relational_select_expr(query, frame)?;
2861 let result = match query {
2862 QueryExpr::Table(table) => {
2863 execute_runtime_table_query(&self.inner.db, &table, Some(&self.inner.index_store))?
2864 }
2865 QueryExpr::Join(join) => execute_runtime_join_query(&self.inner.db, &join)?,
2866 other => {
2867 return Err(RedDBError::Query(format!(
2868 "expression subquery must be a SELECT query, got {}",
2869 query_expr_name(&other)
2870 )))
2871 }
2872 };
2873 first_column_values(result)
2874 }
2875
2876 fn dispatch_expr(
2877 &self,
2878 expr: QueryExpr,
2879 query_str: &str,
2880 mode: QueryMode,
2881 ) -> RedDBResult<RuntimeQueryResult> {
2882 let statement = query_expr_name(&expr);
2883 match expr {
2884 QueryExpr::Graph(_) | QueryExpr::Path(_) => {
2885 Err(RedDBError::Query(
2887 "graph queries cannot be used as prepared statements".to_string(),
2888 ))
2889 }
2890 QueryExpr::Table(table) => {
2891 let scope = self.ai_scope();
2892 let table = self.resolve_table_expr_subqueries(
2893 table,
2894 &scope as &dyn super::statement_frame::ReadFrame,
2895 )?;
2896 if let Some(TableSource::Function {
2900 name,
2901 args,
2902 named_args,
2903 }) = table.source.clone()
2904 {
2905 return Ok(RuntimeQueryResult {
2906 query: query_str.to_string(),
2907 mode,
2908 statement,
2909 engine: "runtime-graph-tvf",
2910 result: self.execute_table_function(&name, &args, &named_args)?,
2911 affected_rows: 0,
2912 statement_type: "select",
2913 bookmark: None,
2914 });
2915 }
2916 if let Some(TableSource::InlineGraphFunction {
2920 name,
2921 nodes,
2922 edges,
2923 named_args,
2924 }) = table.source.clone()
2925 {
2926 return Ok(RuntimeQueryResult {
2927 query: query_str.to_string(),
2928 mode,
2929 statement,
2930 engine: "runtime-graph-tvf-inline",
2931 result: self.execute_inline_graph_function(
2932 &name,
2933 &nodes,
2934 &edges,
2935 &named_args,
2936 )?,
2937 affected_rows: 0,
2938 statement_type: "select",
2939 bookmark: None,
2940 });
2941 }
2942 if super::red_schema::is_virtual_table(&table.table) {
2943 return Ok(RuntimeQueryResult {
2944 query: query_str.to_string(),
2945 mode,
2946 statement,
2947 engine: "runtime-red-schema",
2948 result: super::red_schema::red_query(
2949 self,
2950 &table.table,
2951 &table,
2952 &scope as &dyn super::statement_frame::ReadFrame,
2953 )?,
2954 affected_rows: 0,
2955 statement_type: "select",
2956 bookmark: None,
2957 });
2958 }
2959 if let Some(view_result) = self.try_resolve_analytics_view(
2961 &table,
2962 &scope as &dyn super::statement_frame::ReadFrame,
2963 )? {
2964 return Ok(RuntimeQueryResult {
2965 query: query_str.to_string(),
2966 mode,
2967 statement,
2968 engine: "runtime-graph-analytics-view",
2969 result: view_result,
2970 affected_rows: 0,
2971 statement_type: "select",
2972 bookmark: None,
2973 });
2974 }
2975 let Some(table_with_rls) = self.authorize_relational_table_select(
2976 table,
2977 &scope as &dyn super::statement_frame::ReadFrame,
2978 )?
2979 else {
2980 return Ok(RuntimeQueryResult {
2981 query: query_str.to_string(),
2982 mode,
2983 statement,
2984 engine: "runtime-table-rls",
2985 result: crate::storage::query::unified::UnifiedResult::empty(),
2986 affected_rows: 0,
2987 statement_type: "select",
2988 bookmark: None,
2989 });
2990 };
2991 Ok(RuntimeQueryResult {
2992 query: query_str.to_string(),
2993 mode,
2994 statement,
2995 engine: "runtime-table",
2996 result: execute_runtime_table_query(
2997 &self.inner.db,
2998 &table_with_rls,
2999 Some(&self.inner.index_store),
3000 )?,
3001 affected_rows: 0,
3002 statement_type: "select",
3003 bookmark: None,
3004 })
3005 }
3006 QueryExpr::Join(join) => {
3007 let scope = self.ai_scope();
3008 let Some(join_with_rls) = self.authorize_relational_join_select(
3009 join,
3010 &scope as &dyn super::statement_frame::ReadFrame,
3011 )?
3012 else {
3013 return Ok(RuntimeQueryResult {
3014 query: query_str.to_string(),
3015 mode,
3016 statement,
3017 engine: "runtime-join-rls",
3018 result: crate::storage::query::unified::UnifiedResult::empty(),
3019 affected_rows: 0,
3020 statement_type: "select",
3021 bookmark: None,
3022 });
3023 };
3024 Ok(RuntimeQueryResult {
3025 query: query_str.to_string(),
3026 mode,
3027 statement,
3028 engine: "runtime-join",
3029 result: execute_runtime_join_query(&self.inner.db, &join_with_rls)?,
3030 affected_rows: 0,
3031 statement_type: "select",
3032 bookmark: None,
3033 })
3034 }
3035 QueryExpr::Vector(vector) => Ok(RuntimeQueryResult {
3036 query: query_str.to_string(),
3037 mode,
3038 statement,
3039 engine: "runtime-vector",
3040 result: execute_runtime_vector_query(&self.inner.db, &vector)?,
3041 affected_rows: 0,
3042 statement_type: "select",
3043 bookmark: None,
3044 }),
3045 QueryExpr::Hybrid(hybrid) => Ok(RuntimeQueryResult {
3046 query: query_str.to_string(),
3047 mode,
3048 statement,
3049 engine: "runtime-hybrid",
3050 result: execute_runtime_hybrid_query(&self.inner.db, &hybrid)?,
3051 affected_rows: 0,
3052 statement_type: "select",
3053 bookmark: None,
3054 }),
3055 QueryExpr::Insert(ref insert) if super::red_schema::is_virtual_table(&insert.table) => {
3056 Err(RedDBError::Query(
3057 super::red_schema::READ_ONLY_ERROR.to_string(),
3058 ))
3059 }
3060 QueryExpr::Update(ref update) if super::red_schema::is_virtual_table(&update.table) => {
3061 Err(RedDBError::Query(
3062 super::red_schema::READ_ONLY_ERROR.to_string(),
3063 ))
3064 }
3065 QueryExpr::Delete(ref delete) if super::red_schema::is_virtual_table(&delete.table) => {
3066 Err(RedDBError::Query(
3067 super::red_schema::READ_ONLY_ERROR.to_string(),
3068 ))
3069 }
3070 QueryExpr::Insert(ref insert) => self
3071 .with_deferred_store_wal_for_dml(self.insert_may_emit_events(insert), || {
3072 self.execute_insert(query_str, insert)
3073 }),
3074 QueryExpr::Update(ref update) => self
3075 .with_deferred_store_wal_for_dml(self.update_may_emit_events(update), || {
3076 self.execute_update(query_str, update)
3077 }),
3078 QueryExpr::Delete(ref delete) => self
3079 .with_deferred_store_wal_for_dml(self.delete_may_emit_events(delete), || {
3080 self.execute_delete(query_str, delete)
3081 }),
3082 QueryExpr::SearchCommand(ref cmd) => self.execute_search_command(query_str, cmd),
3083 QueryExpr::Ask(ref ask) => self.execute_ask(query_str, ask),
3084 _ => Err(RedDBError::Query(format!(
3085 "prepared-statement execution does not support {statement} statements"
3086 ))),
3087 }
3088 }
3089
3090 fn execute_table_function(
3097 &self,
3098 name: &str,
3099 args: &[String],
3100 named_args: &[(String, f64)],
3101 ) -> RedDBResult<crate::storage::query::unified::UnifiedResult> {
3102 if name.eq_ignore_ascii_case("red.diff") {
3103 return self.execute_vcs_diff_tvf(args, named_args);
3104 }
3105 if !is_graph_tvf_name(name) {
3106 return Err(RedDBError::Query(format!("unknown table function: {name}")));
3107 }
3108 if args.len() != 1 {
3110 return Err(RedDBError::Query(format!(
3111 "table function '{name}' takes exactly 1 graph argument, got {}",
3112 args.len()
3113 )));
3114 }
3115
3116 let (nodes, edges) = self.materialize_whole_graph_abstract()?;
3121 self.dispatch_graph_algorithm(name, nodes, edges, named_args)
3122 }
3123
3124 pub(crate) fn revive_versioned_updates_since(&self, conn_id: u64, stamper_xid: u64) -> usize {
3125 let mut guard = self.inner.pending_versioned_updates.write();
3126 let Some(pending) = guard.get_mut(&conn_id) else {
3127 return 0;
3128 };
3129
3130 let store = self.inner.db.store();
3131 let mut reverted = 0usize;
3132 pending.retain(|(collection, old_id, new_id, xid, previous_xmax)| {
3133 if *xid < stamper_xid {
3134 return true;
3135 }
3136 if let Some(manager) = store.get_collection(collection) {
3137 if let Some(mut old) = manager.get(*old_id) {
3138 if old.xmax == *xid {
3139 old.set_xmax(*previous_xmax);
3140 let _ = manager.update(old);
3141 }
3142 }
3143 }
3144 let _ = store.delete_batch(collection, &[*new_id]);
3145 reverted += 1;
3146 false
3147 });
3148 if pending.is_empty() {
3149 guard.remove(&conn_id);
3150 }
3151 reverted
3152 }
3153
3154 fn explain_as_rows(&self, raw_query: &str, inner_sql: &str) -> RedDBResult<RuntimeQueryResult> {
3162 let explain = self.explain_query(inner_sql)?;
3163
3164 let columns = vec![
3165 "op".to_string(),
3166 "source".to_string(),
3167 "est_rows".to_string(),
3168 "est_cost".to_string(),
3169 "depth".to_string(),
3170 ];
3171
3172 let mut records: Vec<crate::storage::query::unified::UnifiedRecord> = Vec::new();
3173
3174 for name in &explain.cte_materializations {
3180 use std::sync::Arc;
3181 let mut rec = crate::storage::query::unified::UnifiedRecord::default();
3182 rec.set_arc(Arc::from("op"), Value::text("CteScan".to_string()));
3183 rec.set_arc(Arc::from("source"), Value::text(name.clone()));
3184 rec.set_arc(Arc::from("est_rows"), Value::Float(0.0));
3185 rec.set_arc(Arc::from("est_cost"), Value::Float(0.0));
3186 rec.set_arc(Arc::from("depth"), Value::Integer(0));
3187 records.push(rec);
3188 }
3189
3190 walk_plan_node(&explain.logical_plan.root, 0, &mut records);
3191
3192 let result = crate::storage::query::unified::UnifiedResult {
3193 columns,
3194 records,
3195 stats: Default::default(),
3196 pre_serialized_json: None,
3197 };
3198
3199 Ok(RuntimeQueryResult {
3200 query: raw_query.to_string(),
3201 mode: explain.mode,
3202 statement: "explain",
3203 engine: "runtime-explain",
3204 result,
3205 affected_rows: 0,
3206 statement_type: "select",
3207 bookmark: None,
3208 })
3209 }
3210}
3211
3212fn walk_plan_node(
3213 node: &crate::storage::query::planner::CanonicalLogicalNode,
3214 depth: usize,
3215 out: &mut Vec<crate::storage::query::unified::UnifiedRecord>,
3216) {
3217 use std::sync::Arc;
3218 let mut rec = crate::storage::query::unified::UnifiedRecord::default();
3219 rec.set_arc(Arc::from("op"), Value::text(node.operator.clone()));
3220 rec.set_arc(
3221 Arc::from("source"),
3222 node.source.clone().map(Value::text).unwrap_or(Value::Null),
3223 );
3224 rec.set_arc(Arc::from("est_rows"), Value::Float(node.estimated_rows));
3225 rec.set_arc(Arc::from("est_cost"), Value::Float(node.operator_cost));
3226 rec.set_arc(Arc::from("depth"), Value::Integer(depth as i64));
3227 out.push(rec);
3228 for child in &node.children {
3229 walk_plan_node(child, depth + 1, out);
3230 }
3231}
3232
3233#[cfg(test)]
3234mod inline_graph_tvf_tests {
3235 use super::*;
3236
3237 fn scopes_for(sql: &str) -> HashSet<String> {
3238 let expr = crate::storage::query::parser::parse(sql)
3239 .expect("parse")
3240 .query;
3241 query_expr_result_cache_scopes(&expr)
3242 }
3243
3244 #[test]
3245 fn inline_tvf_cache_scopes_include_source_collections() {
3246 let scopes = scopes_for(
3250 "SELECT * FROM components(nodes => (SELECT id FROM hosts), edges => (SELECT src, dst FROM links))",
3251 );
3252 assert!(scopes.contains("hosts"), "nodes source scoped: {scopes:?}");
3253 assert!(scopes.contains("links"), "edges source scoped: {scopes:?}");
3254 }
3255
3256 #[test]
3257 fn graph_collection_tvf_cache_scope_is_graph_argument() {
3258 let scopes = scopes_for("SELECT * FROM components(g)");
3262 assert!(scopes.contains("g"), "collection form scoped: {scopes:?}");
3263 }
3264
3265 #[test]
3266 fn abstract_degree_centrality_counts_undirected_endpoints() {
3267 let nodes = vec!["a".to_string(), "b".to_string(), "c".to_string()];
3268 let edges = vec![
3269 ("a".to_string(), "b".to_string(), 1.0_f32),
3270 ("b".to_string(), "c".to_string(), 1.0_f32),
3271 ];
3272 let degrees = abstract_degree_centrality(&nodes, &edges);
3273 assert_eq!(
3274 degrees,
3275 vec![
3276 ("a".to_string(), 1),
3277 ("b".to_string(), 2),
3278 ("c".to_string(), 1),
3279 ]
3280 );
3281 }
3282}