1use super::*;
2use crate::auth::column_policy_gate::ColumnAccessRequest;
3use crate::auth::UserId;
4use crate::catalog::CollectionModel;
5use crate::replication::cdc::ChangeRecord;
6use reddb_rql::ast::TableSource;
7pub(crate) use super::authz::policy_columns::*;
12
13pub(crate) use super::graph_tvf::{abstract_degree_centrality, is_graph_tvf_name};
18pub(crate) use super::rls_injection::{
19 apply_foreign_table_filters, inject_rls_filters, inject_rls_into_join, rls_is_enabled,
20 rls_policy_filter, rls_policy_filter_for_kind,
21};
22pub(crate) use super::vcs_command::{
28 parse_runtime_vcs_command, strip_explain_prefix, walk_collections,
29};
30
31pub(crate) use super::impl_audit_control::{
43 control_event_outcome_for_error, query_audit_plan, query_control_event_specs,
44};
45pub(crate) use super::impl_config_secret::{
47 insert_config_json_path, secret_sql_value_to_string, seed_storage_deploy_config,
48 show_config_json_result,
49};
50pub(super) use super::impl_core_outer_scope::{
51 first_column_values, query_references_outer_scope, relation_scopes_for_query,
52};
53pub(crate) use super::impl_core_result_cache_scopes::collect_table_refs;
54pub(super) use super::impl_core_result_cache_scopes::query_expr_result_cache_scopes;
55
56pub(crate) use super::impl_lifecycle::{table_row_index_fields, view_records_to_entities};
64
65#[cfg(test)]
66thread_local! {
67 static RESULT_CACHE_MISS_EXECUTIONS: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
68}
69
70#[cfg(test)]
71fn reset_result_cache_miss_executions() {
72 RESULT_CACHE_MISS_EXECUTIONS.with(|count| count.set(0));
73}
74
75#[cfg(test)]
76fn result_cache_miss_executions() -> u64 {
77 RESULT_CACHE_MISS_EXECUTIONS.with(std::cell::Cell::get)
78}
79
80pub use super::execution_context::{
81 capture_current_snapshot, clear_current_auth_identity, clear_current_connection_id,
82 clear_current_snapshot, clear_current_tenant, current_auth_identity_for_audit,
83 current_connection_id, current_tenant, entity_visible_under_current_snapshot,
84 entity_visible_with_context, set_current_auth_identity, set_current_auth_principal,
85 set_current_connection_id, set_current_snapshot, set_current_tenant, snapshot_bundle,
86 with_snapshot_bundle, SnapshotBundle, SnapshotContext,
87};
88pub(crate) use super::execution_context::{
89 config_read_permitted, current_auth_identity, current_config_value, current_kv_value,
90 current_role_projected, current_scope_override, current_secret_value,
91 current_snapshot_requires_index_fallback, current_user_projected, has_scope_override_active,
92 kv_read_permitted, parse_set_local_tenant, update_current_config_value,
93 update_current_kv_value, update_current_secret_value, xids_visible_under_current_snapshot,
94 ConfigSnapshotGuard, CurrentSnapshotGuard, KvStoreGuard, ScopeOverrideGuard, SecretStoreGuard,
95 TxLocalTenantGuard,
96};
97
98pub(super) fn has_with_prefix(sql: &str) -> bool {
103 let trimmed = sql.trim_start();
104 let head_end = trimmed
105 .find(|c: char| c.is_whitespace() || c == '(')
106 .unwrap_or(trimmed.len());
107 trimmed[..head_end].eq_ignore_ascii_case("WITH")
108}
109
110pub(super) fn peek_top_level_as_of_with_table(
115 sql: &str,
116) -> Option<(crate::application::vcs::AsOfSpec, Option<String>)> {
117 if !sql
118 .as_bytes()
119 .windows(5)
120 .any(|w| w.eq_ignore_ascii_case(b"as of"))
121 {
122 return None;
123 }
124 let parsed = reddb_rql::parser::parse(sql).ok()?;
125 let reddb_rql::ast::QueryExpr::Table(table) = parsed.query else {
126 return None;
127 };
128 let clause = table.as_of?;
129 let table_name = if table.table.is_empty() || table.table == "any" {
130 None
131 } else {
132 Some(table.table.clone())
133 };
134 let spec = match clause {
135 reddb_rql::ast::AsOfClause::Commit(h) => crate::application::vcs::AsOfSpec::Commit(h),
136 reddb_rql::ast::AsOfClause::Branch(b) => crate::application::vcs::AsOfSpec::Branch(b),
137 reddb_rql::ast::AsOfClause::Tag(t) => crate::application::vcs::AsOfSpec::Tag(t),
138 reddb_rql::ast::AsOfClause::TimestampMs(ts) => {
139 crate::application::vcs::AsOfSpec::TimestampMs(ts)
140 }
141 reddb_rql::ast::AsOfClause::Snapshot(x) => crate::application::vcs::AsOfSpec::Snapshot(x),
142 };
143 Some((spec, table_name))
144}
145
146pub(super) fn query_has_volatile_builtin(sql: &str) -> bool {
147 const VOLATILE_TOKENS: &[&str] = &[
151 "pg_advisory_lock",
152 "pg_try_advisory_lock",
153 "pg_advisory_unlock",
154 "random()",
155 "$config",
160 "$secret",
161 "$red.secret",
162 "$kv",
163 ];
168 let lowered = sql.to_ascii_lowercase();
169 VOLATILE_TOKENS.iter().any(|t| lowered.contains(t))
170}
171
172pub(super) fn query_is_ask_statement(sql: &str) -> bool {
173 let trimmed = sql.trim_start();
174 let head_end = trimmed
175 .find(|c: char| c.is_whitespace() || c == '(' || c == ';')
176 .unwrap_or(trimmed.len());
177 trimmed[..head_end].eq_ignore_ascii_case("ASK")
178}
179
180pub(super) fn intent_lock_modes_for(
190 expr: &QueryExpr,
191) -> Option<(
192 crate::runtime::lock_manager::LockMode,
193 crate::runtime::lock_manager::LockMode,
194)> {
195 use crate::runtime::lock_manager::LockMode::{Exclusive, IntentExclusive, IntentShared};
196
197 match expr {
198 QueryExpr::Table(_)
200 | QueryExpr::Join(_)
201 | QueryExpr::Vector(_)
202 | QueryExpr::Hybrid(_)
203 | QueryExpr::Graph(_)
204 | QueryExpr::Path(_)
205 | QueryExpr::Ask(_)
206 | QueryExpr::SearchCommand(_)
207 | QueryExpr::GraphCommand(_)
208 | QueryExpr::RankOf(_)
209 | QueryExpr::ApproxRankOf(_)
210 | QueryExpr::RankRange(_)
211 | QueryExpr::QueueSelect(_) => Some((IntentShared, IntentShared)),
212
213 QueryExpr::Insert(_)
221 | QueryExpr::Update(_)
222 | QueryExpr::Delete(_)
223 | QueryExpr::QueueCommand(QueueCommand::Move { .. }) => {
224 Some((IntentExclusive, IntentExclusive))
225 }
226 QueryExpr::QueueCommand(_) => Some((IntentShared, IntentShared)),
227
228 QueryExpr::CreateTable(_)
232 | QueryExpr::CreateCollection(_)
233 | QueryExpr::CreateVector(_)
234 | QueryExpr::DropTable(_)
235 | QueryExpr::DropGraph(_)
236 | QueryExpr::DropVector(_)
237 | QueryExpr::DropDocument(_)
238 | QueryExpr::DropKv(_)
239 | QueryExpr::DropCollection(_)
240 | QueryExpr::Truncate(_)
241 | QueryExpr::AlterTable(_)
242 | QueryExpr::CreateIndex(_)
243 | QueryExpr::DropIndex(_)
244 | QueryExpr::CreateTimeSeries(_)
245 | QueryExpr::CreateMetric(_)
246 | QueryExpr::AlterMetric(_)
247 | QueryExpr::CreateSlo(_)
248 | QueryExpr::DropTimeSeries(_)
249 | QueryExpr::CreateQueue(_)
250 | QueryExpr::AlterQueue(_)
251 | QueryExpr::DropQueue(_)
252 | QueryExpr::CreateTree(_)
253 | QueryExpr::DropTree(_)
254 | QueryExpr::CreatePolicy(_)
255 | QueryExpr::DropPolicy(_)
256 | QueryExpr::CreateView(_)
257 | QueryExpr::DropView(_)
258 | QueryExpr::RefreshMaterializedView(_)
259 | QueryExpr::CreateSchema(_)
260 | QueryExpr::DropSchema(_)
261 | QueryExpr::CreateSequence(_)
262 | QueryExpr::DropSequence(_)
263 | QueryExpr::CreateServer(_)
264 | QueryExpr::DropServer(_)
265 | QueryExpr::CreateForeignTable(_)
266 | QueryExpr::DropForeignTable(_) => Some((IntentExclusive, Exclusive)),
267
268 _ => None,
274 }
275}
276
277pub(super) fn collections_referenced(expr: &QueryExpr) -> Vec<String> {
282 let mut out = Vec::new();
283 walk_collections(expr, &mut out);
284 out.sort();
285 out.dedup();
286 out
287}
288
289impl RedDBRuntime {
290 pub fn execute_query_with_scope(
304 &self,
305 query: &str,
306 scope: crate::runtime::within_clause::ScopeOverride,
307 ) -> RedDBResult<RuntimeQueryResult> {
308 if scope.is_empty() {
309 return self.execute_query(query);
310 }
311 self.check_scope_override_privilege(&scope)?;
312 let _scope_guard = ScopeOverrideGuard::install(scope);
313 self.execute_query(query)
314 }
315
316 pub fn execute_query(&self, query: &str) -> RedDBResult<RuntimeQueryResult> {
325 let started = std::time::Instant::now();
326 self.inner.node_load_telemetry.query_start();
327 let receipt = super::transactional_replication::CommitReceiptScope::begin(self);
328 let result = self.execute_query_inner(query);
329 self.finish_query_lifecycle(query, started, result, receipt.lsn())
330 }
331
332 pub fn execute_query_with_params(
340 &self,
341 query: &str,
342 params: &[Value],
343 ) -> RedDBResult<RuntimeQueryResult> {
344 if params.is_empty() {
345 return self.execute_query(query);
346 }
347 let started = std::time::Instant::now();
348 self.inner.node_load_telemetry.query_start();
349 let receipt = super::transactional_replication::CommitReceiptScope::begin(self);
350 let result = self.execute_query_with_params_inner(query, params);
351 self.finish_query_lifecycle(query, started, result, receipt.lsn())
352 }
353
354 pub fn execute_prepared_query(
365 &self,
366 query: &str,
367 expr: QueryExpr,
368 ) -> RedDBResult<RuntimeQueryResult> {
369 let started = std::time::Instant::now();
370 self.inner.node_load_telemetry.query_start();
371 let receipt = super::transactional_replication::CommitReceiptScope::begin(self);
372 let result = self.execute_bound_query_expr_in_frame(query, expr);
373 self.finish_query_lifecycle(query, started, result, receipt.lsn())
374 }
375
376 fn finish_query_lifecycle(
377 &self,
378 query: &str,
379 started: std::time::Instant,
380 mut result: RedDBResult<RuntimeQueryResult>,
381 commit_lsn: Option<u64>,
382 ) -> RedDBResult<RuntimeQueryResult> {
383 result = result.and_then(|mut value| {
388 if value.statement_type == "select" {
389 self.filter_integrity_tombstoned(&mut value.result)?;
390 }
391 super::query_budget::check_result(self.resource_limits(), &value)?;
392 Ok(value)
393 });
394 self.finish_collection_description_transaction(&result);
395 let elapsed_ms = started.elapsed().as_millis() as u64;
396
397 let scope = self.ai_scope();
402 let kind = match result
403 .as_ref()
404 .map(|r| r.statement_type)
405 .unwrap_or("select")
406 {
407 "select" => crate::telemetry::slow_query_logger::QueryKind::Select,
408 "insert" => crate::telemetry::slow_query_logger::QueryKind::Insert,
409 "update" => crate::telemetry::slow_query_logger::QueryKind::Update,
410 "delete" => crate::telemetry::slow_query_logger::QueryKind::Delete,
411 _ => crate::telemetry::slow_query_logger::QueryKind::Internal,
412 };
413 let logged_query = redact_sql_for_slow_log(query);
414 self.inner
415 .slow_query_logger
416 .record(kind, elapsed_ms, logged_query, &scope);
417
418 self.inner
424 .query_latency_telemetry
425 .observe(kind, started.elapsed().as_secs_f64());
426
427 self.inner.node_load_telemetry.query_finish();
431
432 if let Ok(ref mut query_result) = result {
433 if matches!(
434 query_result.statement_type,
435 "insert" | "update" | "delete" | "commit"
436 ) {
437 let lsn = if self.inner.db.replication.is_some() {
438 commit_lsn
439 } else if self.current_xid().is_none() {
440 Some(self.cdc_current_lsn())
441 } else {
442 None
443 };
444 query_result.bookmark = lsn.map(|lsn| {
445 crate::replication::CausalBookmark::new(self.current_replication_term(), lsn)
446 .encode()
447 });
448 }
449 }
450
451 result
452 }
453
454 fn execute_query_with_params_inner(
455 &self,
456 query: &str,
457 params: &[Value],
458 ) -> RedDBResult<RuntimeQueryResult> {
459 let parsed = self.parameterized_query_ast(query)?;
460 let bound = crate::storage::query::user_params::bind(&parsed, params).map_err(|err| {
461 RedDBError::Validation {
462 message: err.to_string(),
463 validation: crate::json!({
464 "code": "INVALID_PARAMS",
465 "surface": "query.params",
466 }),
467 }
468 })?;
469 self.execute_bound_query_expr_in_frame(query, bound)
470 }
471
472 fn parameterized_query_ast(&self, query: &str) -> RedDBResult<QueryExpr> {
475 use crate::storage::query::planner::{CachedPlan, QueryPlan};
476
477 let key = format!("\0parameters\0{query}");
479 if let Some(parsed) = self
480 .inner
481 .query_cache
482 .read()
483 .peek(&key)
484 .filter(|entry| entry.matches_exact_query(query))
485 .map(|entry| entry.plan.original.clone())
486 {
487 return Ok(parsed);
488 }
489 let parsed = parse_multi(query).map_err(|err| RedDBError::Query(err.to_string()))?;
490 let plan = QueryPlan::new(parsed.clone(), parsed.clone(), Default::default());
491 self.inner
492 .query_cache
493 .write()
494 .insert(key, CachedPlan::new(plan).with_exact_query(query));
495 Ok(parsed)
496 }
497
498 fn execute_bound_query_expr_in_frame(
499 &self,
500 query: &str,
501 expr: QueryExpr,
502 ) -> RedDBResult<RuntimeQueryResult> {
503 let rewritten_query = super::red_schema::rewrite_virtual_names(query);
504 let execution_query = rewritten_query.as_deref().unwrap_or(query);
505 let frame = super::statement_frame::StatementExecutionFrame::build(
506 self,
507 super::statement_frame::StatementIdentity::Text(execution_query),
508 )?;
509 let _frame_guards = Box::new(frame.install(self));
513 let _log_span = crate::telemetry::span::query_span(query).entered();
514
515 let expr = self.rewrite_view_refs(expr);
516 let mode = detect_mode(execution_query);
517 let control_event_specs = query_control_event_specs(&expr);
518 let _lock_guard = match frame.prepare_dispatch(self, &expr) {
519 Ok(guard) => guard,
520 Err(err) => {
521 let outcome = control_event_outcome_for_error(&err);
522 for spec in &control_event_specs {
523 self.emit_control_event(
524 spec.kind,
525 outcome,
526 spec.action,
527 spec.resource.clone(),
528 Some(err.to_string()),
529 spec.fields.clone(),
530 )?;
531 }
532 return Err(err);
533 }
534 };
535
536 self.dispatch_expr(expr, query, mode)
537 }
538
539 pub fn causal_session(&self) -> crate::runtime::CausalSession {
540 crate::runtime::CausalSession {
541 runtime: self.clone(),
542 bookmark: None,
543 wait_timeout: std::time::Duration::from_secs(5),
544 }
545 }
546
547 pub fn wait_for_bookmark(
548 &self,
549 bookmark: &crate::replication::CausalBookmark,
550 timeout: std::time::Duration,
551 ) -> RedDBResult<()> {
552 let deadline = std::time::Instant::now() + timeout;
553 loop {
554 let applied_lsn = self.local_contiguous_applied_lsn();
555 if applied_lsn >= bookmark.commit_lsn() {
556 return Ok(());
557 }
558 let now = std::time::Instant::now();
559 if now >= deadline {
560 return Err(RedDBError::InvalidOperation(format!(
561 "timed out waiting for causal bookmark lsn {}; applied={}",
562 bookmark.commit_lsn(),
563 applied_lsn
564 )));
565 }
566 let remaining = deadline.saturating_duration_since(now);
567 std::thread::sleep(remaining.min(std::time::Duration::from_millis(5)));
568 }
569 }
570
571 fn local_contiguous_applied_lsn(&self) -> u64 {
572 match self.inner.db.options().replication.role {
573 crate::replication::ReplicationRole::Replica { .. } => {
574 self.config_u64("replication.last_applied_lsn", 0)
575 }
576 _ => self.replication_commit_lsn(),
577 }
578 }
579
580 #[inline(never)]
581 pub(super) fn execute_query_inner(&self, query: &str) -> RedDBResult<RuntimeQueryResult> {
582 if !has_scope_override_active()
593 && !query.trim_start().starts_with("WITHIN")
594 && !query.trim_start().starts_with("within")
595 && !self.inner.query_audit.has_rules()
596 && !self
597 .inner
598 .transaction_state
599 .in_transaction(current_connection_id())
600 {
601 if let Some(result) = self.try_fast_entity_lookup(query) {
602 return result;
603 }
604 }
605
606 match crate::runtime::within_clause::try_strip_within_prefix(query) {
613 Ok(Some((scope, inner))) => {
614 self.check_scope_override_privilege(&scope)?;
615 let _scope_guard = ScopeOverrideGuard::install(scope);
616 return self.execute_query_inner(inner);
621 }
622 Ok(None) => {}
623 Err(msg) => return Err(RedDBError::Query(msg)),
624 }
625
626 if let Some(inner) = strip_explain_analyze_prefix(query) {
630 return self.explain_analyze_as_rows(query, inner);
631 }
632
633 if let Some(inner) = strip_explain_prefix(query) {
640 return self.explain_as_rows(query, inner);
641 }
642
643 if let Some(value) = parse_set_local_tenant(query)? {
648 self.check_scope_tenant_privilege(value.as_deref())?;
649 let conn_id = current_connection_id();
650 if !self.inner.transaction_state.in_transaction(conn_id) {
651 return Err(RedDBError::Query(
652 "SET LOCAL TENANT requires an active transaction".to_string(),
653 ));
654 }
655 self.inner
656 .transaction_state
657 .set_local_tenant(conn_id, value.clone());
658 return Ok(RuntimeQueryResult::ok_message(
659 query.to_string(),
660 &match &value {
661 Some(id) => format!("local tenant set: {id}"),
662 None => "local tenant cleared".to_string(),
663 },
664 "set_local_tenant",
665 ));
666 }
667
668 if super::red_schema::is_system_schema_write(query) {
669 return Err(RedDBError::Query(
670 super::red_schema::READ_ONLY_ERROR.to_string(),
671 ));
672 }
673
674 if let Some(command) = parse_runtime_vcs_command(query) {
675 let command = command?;
676 self.check_vcs_command_privilege(&command)?;
677 return self.execute_vcs_command(query, detect_mode(query), command);
678 }
679
680 if let Some(create_source) = super::analytics_source_catalog::parse_create_statement(query)?
681 {
682 return self.execute_create_analytics_source(query, create_source);
683 }
684
685 if let Some(path) = super::metric_descriptor_catalog::parse_read_metric_statement(query) {
691 return Err(super::metric_descriptor_catalog::read_output_unsupported(
692 &path,
693 ));
694 }
695
696 if let Some(parsed) = super::ranking_descriptor_catalog::parse_create_ranking(query) {
701 return self.execute_create_ranking(query, parsed?);
702 }
703 if super::ranking_descriptor_catalog::parse_show_rankings(query) {
704 return self.execute_show_rankings(query);
705 }
706
707 let rewritten_query = super::red_schema::rewrite_virtual_names(query);
708 let execution_query = rewritten_query.as_deref().unwrap_or(query);
709
710 let frame = super::statement_frame::StatementExecutionFrame::build(
711 self,
712 super::statement_frame::StatementIdentity::Text(execution_query),
713 )?;
714 let _frame_guards = Box::new(frame.install(self));
715
716 let _log_span = crate::telemetry::span::query_span(query).entered();
723
724 if let Some(rewritten) = frame.prepare_cte(execution_query)? {
726 return self.execute_query_expr(rewritten);
727 }
728
729 if !self.inner.query_audit.has_rules() {
731 if let Some(result) = self.try_fast_entity_lookup(execution_query) {
732 return result;
733 }
734 }
735
736 if !self.inner.query_audit.has_rules() {
738 if let Some(result) = frame.read_result_cache(self) {
739 return Ok(result);
740 }
741 }
742
743 #[cfg(test)]
744 RESULT_CACHE_MISS_EXECUTIONS.with(|count| count.set(count.get() + 1));
745
746 let prepared = frame.prepare_statement(self, execution_query)?;
747 let mode = prepared.mode;
748 let expr = prepared.expr;
749
750 let _query_budget = if super::query_budget::is_materialized_read(&expr) {
751 Some(self.query_budget_scope()?)
752 } else {
753 None
754 };
755
756 let statement = query_expr_name(&expr);
757 let result_cache_scopes = query_expr_result_cache_scopes(&expr);
758 let control_event_specs = query_control_event_specs(&expr);
759 let query_audit_plan = query_audit_plan(&expr);
760
761 let _lock_guard = match frame.prepare_dispatch(self, &expr) {
762 Ok(guard) => guard,
763 Err(err) => {
764 let outcome = control_event_outcome_for_error(&err);
765 for spec in &control_event_specs {
766 self.emit_control_event(
767 spec.kind,
768 outcome,
769 spec.action,
770 spec.resource.clone(),
771 Some(err.to_string()),
772 spec.fields.clone(),
773 )?;
774 }
775 return Err(err);
776 }
777 };
778 let query_audit_started = std::time::Instant::now();
779
780 let query_result = match expr {
781 QueryExpr::Function(ref command) => self.execute_function(query, command),
782 QueryExpr::Graph(_) | QueryExpr::Path(_) => {
783 self.execute_materialized_graph(query, mode, statement, &expr)
784 }
785 QueryExpr::Table(table) => {
786 let table = self.resolve_table_expr_subqueries(
787 table,
788 &frame as &dyn super::statement_frame::ReadFrame,
789 )?;
790 if let Some(TableSource::Function {
794 name,
795 args,
796 named_args,
797 }) = table.source.clone()
798 {
799 let tvf_result = RuntimeQueryResult {
807 query: query.to_string(),
808 mode,
809 statement,
810 engine: "runtime-graph-tvf",
811 result: self.execute_table_function(&name, &args, &named_args)?,
812 affected_rows: 0,
813 statement_type: "select",
814 bookmark: None,
815 notice: None,
816 };
817 frame.write_result_cache(self, &tvf_result, result_cache_scopes.clone());
818 return Ok(tvf_result);
819 }
820 if let Some(TableSource::InlineGraphFunction {
828 name,
829 nodes,
830 edges,
831 named_args,
832 }) = table.source.clone()
833 {
834 let inline_result = RuntimeQueryResult {
835 query: query.to_string(),
836 mode,
837 statement,
838 engine: "runtime-graph-tvf-inline",
839 result: self.execute_inline_graph_function(
840 &name,
841 &nodes,
842 &edges,
843 &named_args,
844 )?,
845 affected_rows: 0,
846 statement_type: "select",
847 bookmark: None,
848 notice: None,
849 };
850 frame.write_result_cache(self, &inline_result, result_cache_scopes);
851 return Ok(inline_result);
852 }
853 if super::red_schema::is_virtual_table(&table.table) {
854 return Ok(RuntimeQueryResult {
855 query: query.to_string(),
856 mode,
857 statement,
858 engine: "runtime-red-schema",
859 result: super::red_schema::red_query(
860 self,
861 &table.table,
862 &table,
863 &frame as &dyn super::statement_frame::ReadFrame,
864 )?,
865 affected_rows: 0,
866 statement_type: "select",
867 bookmark: None,
868 notice: None,
869 });
870 }
871
872 if let Some(view_result) = self.try_resolve_analytics_view(
876 &table,
877 &frame as &dyn super::statement_frame::ReadFrame,
878 )? {
879 return Ok(RuntimeQueryResult {
880 query: query.to_string(),
881 mode,
882 statement,
883 engine: "runtime-graph-analytics-view",
884 result: view_result,
885 affected_rows: 0,
886 statement_type: "select",
887 bookmark: None,
888 notice: None,
889 });
890 }
891
892 if let Some(result) = self.execute_probabilistic_select(&table)? {
893 return Ok(RuntimeQueryResult {
894 query: query.to_string(),
895 mode,
896 statement,
897 engine: "runtime-probabilistic",
898 result,
899 affected_rows: 0,
900 statement_type: "select",
901 bookmark: None,
902 notice: None,
903 });
904 }
905
906 if self.inner.foreign_tables.is_foreign_table(&table.table) {
914 let records = self
915 .inner
916 .foreign_tables
917 .scan(&table.table)
918 .map_err(|e| RedDBError::Internal(e.to_string()))?;
919 let result = apply_foreign_table_filters(records, &table);
920 return Ok(RuntimeQueryResult {
921 query: query.to_string(),
922 mode,
923 statement,
924 engine: "runtime-fdw",
925 result,
926 affected_rows: 0,
927 statement_type: "select",
928 bookmark: None,
929 notice: None,
930 });
931 }
932
933 let Some(table_with_rls) = self.authorize_relational_table_select(
950 table,
951 &frame as &dyn super::statement_frame::ReadFrame,
952 )?
953 else {
954 let empty = crate::storage::query::unified::UnifiedResult::empty();
955 return Ok(RuntimeQueryResult {
956 query: query.to_string(),
957 mode,
958 statement,
959 engine: "runtime-table-rls",
960 result: empty,
961 affected_rows: 0,
962 statement_type: "select",
963 bookmark: None,
964 notice: None,
965 });
966 };
967 Ok(RuntimeQueryResult {
968 query: query.to_string(),
969 mode,
970 statement,
971 engine: "runtime-table",
972 result: execute_runtime_table_query_in(
979 &self.inner.db,
980 &table_with_rls,
981 Some(&self.inner.index_store),
982 Some(frame.row_arena()),
983 )?,
984 affected_rows: 0,
985 statement_type: "select",
986 bookmark: None,
987 notice: None,
988 })
989 }
990 QueryExpr::Join(join) => {
991 let join_with_rls = match self.authorize_relational_join_select(
1000 join,
1001 &frame as &dyn super::statement_frame::ReadFrame,
1002 )? {
1003 Some(j) => j,
1004 None => {
1005 return Ok(RuntimeQueryResult {
1006 query: query.to_string(),
1007 mode,
1008 statement,
1009 engine: "runtime-join-rls",
1010 result: crate::storage::query::unified::UnifiedResult::empty(),
1011 affected_rows: 0,
1012 statement_type: "select",
1013 bookmark: None,
1014 notice: None,
1015 });
1016 }
1017 };
1018 Ok(RuntimeQueryResult {
1019 query: query.to_string(),
1020 mode,
1021 statement,
1022 engine: "runtime-join",
1023 result: execute_runtime_join_query(self, &join_with_rls)?,
1024 affected_rows: 0,
1025 statement_type: "select",
1026 bookmark: None,
1027 notice: None,
1028 })
1029 }
1030 QueryExpr::Vector(vector) => Ok(RuntimeQueryResult {
1031 query: query.to_string(),
1032 mode,
1033 statement,
1034 engine: "runtime-vector",
1035 result: execute_runtime_vector_query(self, &vector)?,
1036 affected_rows: 0,
1037 statement_type: "select",
1038 bookmark: None,
1039 notice: None,
1040 }),
1041 QueryExpr::Hybrid(hybrid) => Ok(RuntimeQueryResult {
1042 query: query.to_string(),
1043 mode,
1044 statement,
1045 engine: "runtime-hybrid",
1046 result: execute_runtime_hybrid_query(self, &hybrid)?,
1047 affected_rows: 0,
1048 statement_type: "select",
1049 bookmark: None,
1050 notice: None,
1051 }),
1052 QueryExpr::RankOf(ref rank) => self.execute_rank_of(query, rank),
1053 QueryExpr::ApproxRankOf(ref rank) => self.execute_approx_rank_of(query, rank),
1054 QueryExpr::RankRange(ref range) => self.execute_rank_range(query, range),
1055 QueryExpr::Insert(ref insert) if super::red_schema::is_virtual_table(&insert.table) => {
1057 Err(RedDBError::Query(
1058 super::red_schema::READ_ONLY_ERROR.to_string(),
1059 ))
1060 }
1061 QueryExpr::Update(ref update) if super::red_schema::is_virtual_table(&update.table) => {
1062 Err(RedDBError::Query(
1063 super::red_schema::READ_ONLY_ERROR.to_string(),
1064 ))
1065 }
1066 QueryExpr::Delete(ref delete) if super::red_schema::is_virtual_table(&delete.table) => {
1067 Err(RedDBError::Query(
1068 super::red_schema::READ_ONLY_ERROR.to_string(),
1069 ))
1070 }
1071 QueryExpr::Insert(ref insert) => self
1072 .with_deferred_store_wal_for_dml(self.insert_may_emit_events(insert), || {
1073 self.execute_insert(query, insert)
1074 }),
1075 QueryExpr::Update(ref update) => self
1076 .with_deferred_store_wal_for_dml(self.update_may_emit_events(update), || {
1077 self.execute_update(query, update)
1078 }),
1079 QueryExpr::Delete(ref delete) => self
1080 .with_deferred_store_wal_for_dml(self.delete_may_emit_events(delete), || {
1081 self.execute_delete(query, delete)
1082 }),
1083 QueryExpr::CreateTable(ref create) => self.execute_create_table(query, create),
1085 QueryExpr::CreateCollection(ref create) => {
1086 self.execute_create_collection(query, create)
1087 }
1088 QueryExpr::CreateVector(ref create) => self.execute_create_vector(query, create),
1089 QueryExpr::DropTable(ref drop_tbl) => self.execute_drop_table(query, drop_tbl),
1090 QueryExpr::DropGraph(ref drop_graph) => self.execute_drop_graph(query, drop_graph),
1091 QueryExpr::DropVector(ref drop_vector) => self.execute_drop_vector(query, drop_vector),
1092 QueryExpr::DropDocument(ref drop_document) => {
1093 self.execute_drop_document(query, drop_document)
1094 }
1095 QueryExpr::DropKv(ref drop_kv) => self.execute_drop_kv(query, drop_kv),
1096 QueryExpr::DropCollection(ref drop_collection) => {
1097 self.execute_drop_collection(query, drop_collection)
1098 }
1099 QueryExpr::Truncate(ref truncate) => self.execute_truncate(query, truncate),
1100 QueryExpr::AlterTable(ref alter) => self.execute_alter_table(query, alter),
1101 QueryExpr::CreateVcsRef(ref create) => self.execute_create_vcs_ref(query, create),
1102 QueryExpr::DropVcsRef(ref drop_ref) => self.execute_drop_vcs_ref(query, drop_ref),
1103 QueryExpr::ForkStore(ref fork) => self.execute_fork_store(query, fork),
1104 QueryExpr::PromoteFork(ref promote_fork) => {
1105 self.execute_promote_fork(query, promote_fork)
1106 }
1107 QueryExpr::DropFork(ref drop_fork) => self.execute_drop_fork(query, drop_fork),
1108 QueryExpr::ExplainAlter(ref explain) => self.execute_explain_alter(query, explain),
1109 QueryExpr::GraphCommand(ref cmd) => self.execute_graph_command(query, cmd),
1111 QueryExpr::SearchCommand(ref cmd) => self.execute_search_command(query, cmd),
1113 QueryExpr::Ask(ref ask) => self.execute_ask(query, ask),
1115 QueryExpr::CreateIndex(ref create_idx) => self.execute_create_index(query, create_idx),
1116 QueryExpr::DropIndex(ref drop_idx) => self.execute_drop_index(query, drop_idx),
1117 QueryExpr::ProbabilisticCommand(ref cmd) => {
1118 self.execute_probabilistic_command(query, cmd)
1119 }
1120 QueryExpr::CreateTimeSeries(ref ts) => self.execute_create_timeseries(query, ts),
1122 QueryExpr::CreateMetric(ref metric) => self.execute_create_metric(query, metric),
1123 QueryExpr::AlterMetric(ref alter) => self.execute_alter_metric(query, alter),
1124 QueryExpr::CreateSlo(ref slo) => self.execute_create_slo(query, slo),
1125 QueryExpr::DropTimeSeries(ref ts) => self.execute_drop_timeseries(query, ts),
1126 QueryExpr::CreateQueue(ref q) => self.execute_create_queue(query, q),
1128 QueryExpr::AlterQueue(ref q) => self.execute_alter_queue(query, q),
1129 QueryExpr::DropQueue(ref q) => self.execute_drop_queue(query, q),
1130 QueryExpr::QueueSelect(ref q) => self.execute_queue_select(query, q),
1131 QueryExpr::QueueCommand(ref cmd) => self
1132 .with_deferred_store_wal_if_transaction(|| self.execute_queue_command(query, cmd)),
1133 QueryExpr::EventsBackfill(ref backfill) => {
1134 self.execute_events_backfill(query, backfill)
1135 }
1136 QueryExpr::EventsBackfillStatus { ref collection } => Err(RedDBError::Query(format!(
1137 "EVENTS BACKFILL STATUS for '{collection}' is not implemented in this slice"
1138 ))),
1139 QueryExpr::KvCommand(ref cmd) => self.execute_kv_command(query, cmd),
1140 QueryExpr::ConfigCommand(ref cmd) => self.execute_config_command(query, cmd),
1141 QueryExpr::CreateTree(ref tree) => self.execute_create_tree(query, tree),
1142 QueryExpr::DropTree(ref tree) => self.execute_drop_tree(query, tree),
1143 QueryExpr::TreeCommand(ref cmd) => self.execute_tree_command(query, cmd),
1144 QueryExpr::SetConfig { ref key, ref value } => {
1146 if key.starts_with("red.secret.") {
1147 return Err(RedDBError::Query(
1148 "red.secret.* is reserved for vault secrets; use SET SECRET".to_string(),
1149 ));
1150 }
1151 if key.starts_with("red.secrets.") {
1152 return Err(RedDBError::Query(
1153 "red.secrets.* is reserved for vault secrets; use SET SECRET".to_string(),
1154 ));
1155 }
1156 if key.starts_with("red.") {
1157 return Err(RedDBError::Query(format!(
1158 "config key `{key}` is invalid inside red.config: omit the redundant `red.` prefix"
1159 )));
1160 }
1161 crate::ai::validate_ai_config_key_on_write(key)?;
1165 match self.check_managed_config_write_for_set_config(key) {
1170 Err(err) => Err(err),
1171 Ok(()) => {
1172 self.check_config_write_privilege(key)?;
1173 let store = self.inner.db.store();
1174 let json_val = match value {
1175 Value::Text(s) => crate::serde_json::Value::String(s.to_string()),
1176 Value::Integer(n) => crate::serde_json::Value::Number(*n as f64),
1177 Value::Float(n) => crate::serde_json::Value::Number(*n),
1178 Value::Boolean(b) => crate::serde_json::Value::Bool(*b),
1179 _ => crate::serde_json::Value::String(value.to_string()),
1180 };
1181 store
1182 .try_set_config_tree(key, &json_val)
1183 .map_err(RedDBError::from)?;
1184 update_current_config_value(key, value.clone());
1185 self.invalidate_result_cache();
1190 Ok(RuntimeQueryResult::ok_message(
1191 query.to_string(),
1192 &format!("config set: {key}"),
1193 "set",
1194 ))
1195 }
1196 }
1197 }
1198 QueryExpr::SetSecret { ref key, ref value } => {
1200 if key.starts_with("red.config.") {
1201 return Err(RedDBError::Query(
1202 "red.config.* is reserved for config; use SET CONFIG".to_string(),
1203 ));
1204 }
1205 reject_engine_internal_vault_key(key)?;
1206 if key.starts_with("red.secret.") {
1207 self.set_internal_provider_secret(
1208 key,
1209 if value.is_null() {
1210 None
1211 } else {
1212 Some(secret_sql_value_to_string(value)?)
1213 },
1214 )?;
1215 return Ok(RuntimeQueryResult::ok_message(
1216 query.to_string(),
1217 &format!("secret set: {key}"),
1218 "set_secret",
1219 ));
1220 }
1221 let value = if value.is_null() {
1222 None
1223 } else {
1224 Some(secret_sql_value_to_string(value)?)
1225 };
1226 self.set_user_secret(key, value.clone())?;
1227 update_current_secret_value(key, value);
1228 Ok(RuntimeQueryResult::ok_message(
1229 query.to_string(),
1230 &format!("secret set: {key}"),
1231 "set_secret",
1232 ))
1233 }
1234 QueryExpr::DeleteSecret { ref key } => {
1236 reject_engine_internal_vault_key(key)?;
1237 if key.starts_with("red.secret.") {
1238 self.set_internal_provider_secret(key, None)?;
1239 return Ok(RuntimeQueryResult::ok_message(
1240 query.to_string(),
1241 &format!("secret deleted: {key}"),
1242 "delete_secret",
1243 ));
1244 }
1245 self.set_user_secret(key, None)?;
1246 update_current_secret_value(key, None);
1247 Ok(RuntimeQueryResult::ok_message(
1248 query.to_string(),
1249 &format!("secret deleted: {key}"),
1250 "delete_secret",
1251 ))
1252 }
1253 QueryExpr::SetKv { ref key, ref value } => {
1255 let auth_store = self.inner.auth_store.read().clone().ok_or_else(|| {
1256 RedDBError::Query("SET KV requires an auth store".to_string())
1257 })?;
1258 self.check_kv_write_privilege(&auth_store, key)?;
1259 if matches!(value, Value::Null) {
1261 auth_store.plain_kv_delete(key);
1262 update_current_kv_value(key, None);
1263 self.invalidate_result_cache();
1264 return Ok(RuntimeQueryResult::ok_message(
1265 query.to_string(),
1266 &format!("kv deleted: {key}"),
1267 "delete_kv",
1268 ));
1269 }
1270 let value = secret_sql_value_to_string(value)?;
1271 auth_store.plain_kv_set(key.clone(), value.clone());
1272 update_current_kv_value(key, Some(value));
1273 self.invalidate_result_cache();
1274 Ok(RuntimeQueryResult::ok_message(
1275 query.to_string(),
1276 &format!("kv set: {key}"),
1277 "set_kv",
1278 ))
1279 }
1280 QueryExpr::DeleteKv { ref key } => {
1282 let auth_store = self.inner.auth_store.read().clone().ok_or_else(|| {
1283 RedDBError::Query("DELETE KV requires an auth store".to_string())
1284 })?;
1285 self.check_kv_write_privilege(&auth_store, key)?;
1286 let deleted = auth_store.plain_kv_delete(key);
1287 if deleted {
1288 update_current_kv_value(key, None);
1289 }
1290 self.invalidate_result_cache();
1291 Ok(RuntimeQueryResult::ok_message(
1292 query.to_string(),
1293 &format!("kv deleted: {key}"),
1294 if deleted {
1295 "delete_kv"
1296 } else {
1297 "delete_kv_not_found"
1298 },
1299 ))
1300 }
1301 QueryExpr::Scrub { background, budget } => {
1302 self.execute_scrub_query(query, mode, background, budget)
1303 }
1304 QueryExpr::ShowSecrets { ref prefix } => {
1306 let result = self.show_user_secrets(prefix.as_deref())?;
1307 Ok(RuntimeQueryResult {
1308 query: query.to_string(),
1309 mode,
1310 statement: "show_secrets",
1311 engine: "runtime-secret",
1312 result,
1313 affected_rows: 0,
1314 statement_type: "select",
1315 bookmark: None,
1316 notice: None,
1317 })
1318 }
1319 QueryExpr::ShowConfig {
1321 ref prefix,
1322 as_json,
1323 } => {
1324 if let Some(prefix) = prefix {
1325 super::impl_config::validate_red_config_content_key("red.config", prefix)?;
1326 }
1327 self.check_config_read_privilege()?;
1328 let store = self.inner.db.store();
1329 let all_collections = store.list_collections();
1330 if !all_collections.contains(&"red.config".to_string()) {
1331 if as_json {
1332 return Ok(show_config_json_result(
1333 query,
1334 mode,
1335 prefix,
1336 crate::serde_json::Value::Object(crate::serde_json::Map::new()),
1337 ));
1338 }
1339 let result = UnifiedResult::with_columns(vec!["key".into(), "value".into()]);
1340 return Ok(RuntimeQueryResult {
1341 query: query.to_string(),
1342 mode,
1343 statement: "show_config",
1344 engine: "runtime-config",
1345 result,
1346 affected_rows: 0,
1347 statement_type: "select",
1348 bookmark: None,
1349 notice: None,
1350 });
1351 }
1352 let manager = store
1353 .get_collection("red.config")
1354 .ok_or_else(|| RedDBError::NotFound("red.config".to_string()))?;
1355 let entities = manager.query_all(|_| true);
1356 let mut latest = std::collections::BTreeMap::<String, (u64, Value, Value)>::new();
1357 for entity in entities {
1358 if let EntityData::Row(ref row) = entity.data {
1359 if let Some(ref named) = row.named {
1360 let key_val = named.get("key").cloned().unwrap_or(Value::Null);
1361 let val = named.get("value").cloned().unwrap_or(Value::Null);
1362 let key_str = match &key_val {
1363 Value::Text(s) => s.as_ref(),
1364 _ => continue,
1365 };
1366 if key_str == super::function_catalog::REGISTRY_KEY {
1367 continue;
1368 }
1369 if let Some(ref pfx) = prefix {
1370 if !key_str.starts_with(pfx.as_str()) {
1371 continue;
1372 }
1373 }
1374 let entity_id = entity.id.raw();
1375 match latest.get(key_str) {
1376 Some((prev_id, _, _)) if *prev_id > entity_id => {}
1377 _ => {
1378 latest.insert(key_str.to_string(), (entity_id, key_val, val));
1379 }
1380 }
1381 }
1382 }
1383 }
1384 if as_json {
1385 let mut tree = crate::serde_json::Value::Object(crate::serde_json::Map::new());
1386 for (key, (_, _, val)) in latest {
1387 let relative = match prefix {
1388 Some(pfx) if key == *pfx => "",
1389 Some(pfx) => key
1390 .strip_prefix(pfx.as_str())
1391 .and_then(|tail| tail.strip_prefix('.'))
1392 .unwrap_or(key.as_str()),
1393 None => key.as_str(),
1394 };
1395 insert_config_json_path(&mut tree, relative, val.to_json());
1396 }
1397 return Ok(show_config_json_result(query, mode, prefix, tree));
1398 }
1399 let mut result = UnifiedResult::with_columns(vec!["key".into(), "value".into()]);
1400 for (_, key_val, val) in latest.into_values() {
1401 let mut record = UnifiedRecord::new();
1402 record.set("key", key_val);
1403 record.set("value", val);
1404 result.push(record);
1405 }
1406 Ok(RuntimeQueryResult {
1407 query: query.to_string(),
1408 mode,
1409 statement: "show_config",
1410 engine: "runtime-config",
1411 result,
1412 affected_rows: 0,
1413 statement_type: "select",
1414 bookmark: None,
1415 notice: None,
1416 })
1417 }
1418 QueryExpr::SetTenant(ref value) => {
1424 self.check_set_tenant_privilege(value.as_deref())?;
1425 match value {
1426 Some(id) => set_current_tenant(id.clone()),
1427 None => clear_current_tenant(),
1428 }
1429 Ok(RuntimeQueryResult::ok_message(
1430 query.to_string(),
1431 &match value {
1432 Some(id) => format!("tenant set: {id}"),
1433 None => "tenant cleared".to_string(),
1434 },
1435 "set_tenant",
1436 ))
1437 }
1438 QueryExpr::ShowTenant => {
1439 let mut result = UnifiedResult::with_columns(vec!["tenant".into()]);
1440 let mut record = UnifiedRecord::new();
1441 record.set(
1442 "tenant",
1443 current_tenant().map(Value::text).unwrap_or(Value::Null),
1444 );
1445 result.push(record);
1446 Ok(RuntimeQueryResult {
1447 query: query.to_string(),
1448 mode,
1449 statement: "show_tenant",
1450 engine: "runtime-tenant",
1451 result,
1452 affected_rows: 0,
1453 statement_type: "select",
1454 bookmark: None,
1455 notice: None,
1456 })
1457 }
1458 QueryExpr::TransactionControl(ref ctl) => {
1470 use crate::storage::transaction::IsolationLevel;
1471 use reddb_rql::ast::TxnControl;
1472
1473 let conn_id = current_connection_id();
1478
1479 let (kind, msg) = match ctl {
1480 TxnControl::Begin(requested_isolation) => {
1481 if self.inner.transaction_state.in_transaction(conn_id) {
1482 return Err(RedDBError::Query("transaction already active".into()));
1483 }
1484 let isolation = requested_isolation
1485 .map(IsolationLevel::from)
1486 .unwrap_or(IsolationLevel::SnapshotIsolation);
1487 let xid = self.inner.transaction_state.begin(conn_id, isolation);
1488 ("begin", format!("BEGIN — xid={xid} (snapshot isolation)"))
1489 }
1490 TxnControl::Commit => {
1491 let has_vault_writes = self
1492 .inner
1493 .pending_vault_writes
1494 .read()
1495 .get(&conn_id)
1496 .is_some_and(|writes| !writes.is_empty());
1497 let store = self.inner.db.store();
1498 let _vault_guard =
1499 has_vault_writes.then(|| store.vault_publication_lock.lock());
1500 let context = self.inner.transaction_state.commit(conn_id, |ctx| {
1501 let mut own_xids = std::collections::HashSet::new();
1502 own_xids.insert(ctx.xid);
1503 for (_, sub) in &ctx.savepoints {
1504 own_xids.insert(*sub);
1505 }
1506 for sub in &ctx.released_sub_xids {
1507 own_xids.insert(*sub);
1508 }
1509 if let Err(err) = self.check_table_row_write_conflicts(
1510 conn_id,
1511 &ctx.snapshot,
1512 &own_xids,
1513 ctx.isolation,
1514 ) {
1515 let undo = self.revive_pending_versioned_updates(conn_id);
1516 self.revive_pending_tombstones(conn_id);
1517 self.discard_pending_kv_watch_events(conn_id);
1518 self.discard_pending_queue_wakes(conn_id);
1519 self.discard_pending_store_wal_actions(conn_id);
1520 self.release_pending_claim_locks(conn_id);
1521 undo?;
1522 return Err(err);
1523 }
1524 if let Err(err) = self.check_queue_dedup_write_conflicts(
1525 conn_id,
1526 &ctx.snapshot,
1527 &own_xids,
1528 ) {
1529 let undo = self.revive_pending_versioned_updates(conn_id);
1530 self.revive_pending_tombstones(conn_id);
1531 self.discard_pending_queue_dedup(conn_id);
1532 self.discard_pending_kv_watch_events(conn_id);
1533 self.discard_pending_queue_wakes(conn_id);
1534 self.discard_pending_store_wal_actions(conn_id);
1535 self.release_pending_claim_locks(conn_id);
1536 undo?;
1537 return Err(err);
1538 }
1539 if let Err(err) = self.prepare_pending_vault_writes(conn_id, ctx) {
1540 let undo = self.revive_pending_versioned_updates(conn_id);
1541 self.revive_pending_tombstones(conn_id);
1542 self.discard_pending_queue_dedup(conn_id);
1543 self.discard_pending_kv_watch_events(conn_id);
1544 self.discard_pending_queue_wakes(conn_id);
1545 self.discard_pending_store_wal_actions(conn_id);
1546 self.release_pending_claim_locks(conn_id);
1547 undo?;
1548 return Err(err);
1549 }
1550 let publication = store.vault_publication_lock.lock();
1555 self.restore_pending_write_stamps(conn_id);
1556 if let Err(err) = self.flush_pending_store_wal_actions(conn_id) {
1557 drop(publication);
1558 let undo = self.revive_pending_versioned_updates(conn_id);
1559 self.revive_pending_tombstones(conn_id);
1560 self.discard_pending_queue_dedup(conn_id);
1561 self.discard_pending_kv_watch_events(conn_id);
1562 self.discard_pending_queue_wakes(conn_id);
1563 self.release_pending_claim_locks(conn_id);
1564 undo?;
1565 return Err(err);
1566 }
1567 #[cfg(test)]
1568 self.index_store_ref().mutation_test_hook(
1569 "",
1570 super::index_store::MutationTestPhase::TransactionWalCommitted,
1571 );
1572 Ok(publication)
1573 });
1574 if context.is_err() {
1575 self.discard_pending_vault_writes(conn_id)?;
1576 }
1577 let context = context?;
1578 match context {
1579 Some(ctx) => {
1580 self.finalize_pending_vault_writes(conn_id);
1581 self.finalize_pending_versioned_updates(conn_id);
1582 self.finalize_pending_tombstones(conn_id);
1583 self.finalize_pending_queue_dedup(conn_id);
1584 self.finalize_pending_kv_watch_events(conn_id);
1585 self.finalize_pending_queue_wakes(conn_id);
1586 self.release_pending_claim_locks(conn_id);
1587 self.recover_transactional_replication().map_err(|error| {
1588 RedDBError::Internal(format!(
1589 "commit is locally durable; replication publication is pending: {error}"
1590 ))
1591 })?;
1592 ("commit", format!("COMMIT — xid={} committed", ctx.xid))
1593 }
1594 None => (
1595 "commit",
1596 "COMMIT outside transaction — no-op (autocommit)".to_string(),
1597 ),
1598 }
1599 }
1600 TxnControl::Rollback => match self.rollback_connection(conn_id)? {
1601 Some(ctx) => ("rollback", format!("ROLLBACK — xid={} aborted", ctx.xid)),
1602 None => (
1603 "rollback",
1604 "ROLLBACK outside transaction — no-op (autocommit)".to_string(),
1605 ),
1606 },
1607 TxnControl::Savepoint(name) => {
1614 match self.inner.transaction_state.savepoint(conn_id, name) {
1615 Some(sub) => ("savepoint", format!("SAVEPOINT {name} — sub_xid={sub}")),
1616 None => (
1617 "savepoint",
1618 "SAVEPOINT outside transaction — no-op".to_string(),
1619 ),
1620 }
1621 }
1622 TxnControl::ReleaseSavepoint(name) => {
1623 match self
1624 .inner
1625 .transaction_state
1626 .release_savepoint(conn_id, name)?
1627 {
1628 Some(released) => (
1629 "release_savepoint",
1630 format!("RELEASE SAVEPOINT {name} — {released} level(s)"),
1631 ),
1632 None => (
1633 "release_savepoint",
1634 "RELEASE outside transaction — no-op".to_string(),
1635 ),
1636 }
1637 }
1638 TxnControl::RollbackToSavepoint(name) => {
1639 match self
1640 .inner
1641 .transaction_state
1642 .rollback_to_savepoint_with(conn_id, name, |rollback| {
1643 self.rollback_vault_savepoint(conn_id, rollback.savepoint_xid);
1644 let reverted_updates = self.revive_versioned_updates_since(
1645 conn_id,
1646 rollback.savepoint_xid,
1647 );
1648 let revived =
1649 self.revive_tombstones_since(conn_id, rollback.savepoint_xid);
1650 reverted_updates.map(|updates| (updates, revived))
1651 })?
1652 {
1653 Some((rollback, (reverted_updates, revived))) => {
1654 (
1655 "rollback_to_savepoint",
1656 format!(
1657 "ROLLBACK TO SAVEPOINT {name} — aborted {} sub_xid(s), reverted {reverted_updates} update(s), revived {revived} tombstone(s)",
1658 rollback.aborted_xids.len(),
1659 ),
1660 )
1661 }
1662 None => (
1663 "rollback_to_savepoint",
1664 "ROLLBACK TO outside transaction — no-op".to_string(),
1665 ),
1666 }
1667 }
1668 };
1669 Ok(RuntimeQueryResult::ok_message(
1670 query.to_string(),
1671 &msg,
1672 kind,
1673 ))
1674 }
1675 QueryExpr::CreateSchema(ref q) => {
1688 let store = self.inner.db.store();
1689 let key = format!("schema.{}", q.name);
1690 if store.get_config(&key).is_some() {
1691 if q.if_not_exists {
1692 return Ok(RuntimeQueryResult::ok_message(
1693 query.to_string(),
1694 &format!("schema {} already exists — skipped", q.name),
1695 "create_schema",
1696 ));
1697 }
1698 return Err(RedDBError::Internal(format!(
1699 "schema {} already exists",
1700 q.name
1701 )));
1702 }
1703 store.set_config_tree(&key, &crate::serde_json::Value::Bool(true));
1704 Ok(RuntimeQueryResult::ok_message(
1705 query.to_string(),
1706 &format!("schema {} created", q.name),
1707 "create_schema",
1708 ))
1709 }
1710 QueryExpr::DropSchema(ref q) => {
1711 let store = self.inner.db.store();
1712 let key = format!("schema.{}", q.name);
1713 let existed = store.get_config(&key).is_some();
1714 if !existed && !q.if_exists {
1715 return Err(RedDBError::Internal(format!(
1716 "schema {} does not exist",
1717 q.name
1718 )));
1719 }
1720 store.set_config_tree(&key, &crate::serde_json::Value::Null);
1722 let suffix = if q.cascade {
1723 " (CASCADE accepted — tables untouched)"
1724 } else {
1725 ""
1726 };
1727 Ok(RuntimeQueryResult::ok_message(
1728 query.to_string(),
1729 &format!("schema {} dropped{}", q.name, suffix),
1730 "drop_schema",
1731 ))
1732 }
1733 QueryExpr::CreateSequence(ref q) => {
1734 let store = self.inner.db.store();
1735 let base = format!("sequence.{}", q.name);
1736 let start_key = format!("{base}.start");
1737 let incr_key = format!("{base}.increment");
1738 let curr_key = format!("{base}.current");
1739 if store.get_config(&start_key).is_some() {
1740 if q.if_not_exists {
1741 return Ok(RuntimeQueryResult::ok_message(
1742 query.to_string(),
1743 &format!("sequence {} already exists — skipped", q.name),
1744 "create_sequence",
1745 ));
1746 }
1747 return Err(RedDBError::Internal(format!(
1748 "sequence {} already exists",
1749 q.name
1750 )));
1751 }
1752 let initial_current = q.start - q.increment;
1755 store.set_config_tree(
1756 &start_key,
1757 &crate::serde_json::Value::Number(q.start as f64),
1758 );
1759 store.set_config_tree(
1760 &incr_key,
1761 &crate::serde_json::Value::Number(q.increment as f64),
1762 );
1763 store.set_config_tree(
1764 &curr_key,
1765 &crate::serde_json::Value::Number(initial_current as f64),
1766 );
1767 Ok(RuntimeQueryResult::ok_message(
1768 query.to_string(),
1769 &format!(
1770 "sequence {} created (start={}, increment={})",
1771 q.name, q.start, q.increment
1772 ),
1773 "create_sequence",
1774 ))
1775 }
1776 QueryExpr::DropSequence(ref q) => {
1777 let store = self.inner.db.store();
1778 let base = format!("sequence.{}", q.name);
1779 let existed = store.get_config(&format!("{base}.start")).is_some();
1780 if !existed && !q.if_exists {
1781 return Err(RedDBError::Internal(format!(
1782 "sequence {} does not exist",
1783 q.name
1784 )));
1785 }
1786 for k in ["start", "increment", "current"] {
1787 store.set_config_tree(&format!("{base}.{k}"), &crate::serde_json::Value::Null);
1788 }
1789 Ok(RuntimeQueryResult::ok_message(
1790 query.to_string(),
1791 &format!("sequence {} dropped", q.name),
1792 "drop_sequence",
1793 ))
1794 }
1795 QueryExpr::CreateView(ref q) => {
1805 let mut views = self.inner.views.write();
1806 if views.contains_key(&q.name) && !q.or_replace {
1807 if q.if_not_exists {
1808 return Ok(RuntimeQueryResult::ok_message(
1809 query.to_string(),
1810 &format!("view {} already exists — skipped", q.name),
1811 "create_view",
1812 ));
1813 }
1814 return Err(RedDBError::Internal(format!(
1815 "view {} already exists",
1816 q.name
1817 )));
1818 }
1819 views.insert(q.name.clone(), Arc::new(q.clone()));
1820 drop(views);
1821
1822 if q.materialized {
1824 use crate::storage::cache::result::{MaterializedViewDef, RefreshPolicy};
1825 let refresh = match q.refresh_every_ms {
1826 Some(ms) => RefreshPolicy::Periodic(std::time::Duration::from_millis(ms)),
1827 None => RefreshPolicy::Manual,
1828 };
1829 let dependencies = collect_table_refs(&q.query);
1830 let def = MaterializedViewDef {
1831 name: q.name.clone(),
1832 query: format!("<parsed view {}>", q.name),
1833 dependencies: dependencies.clone(),
1834 refresh,
1835 retention_duration_ms: q.retention_duration_ms,
1836 };
1837 self.inner.materialized_views.write().register(def);
1838
1839 let descriptor =
1845 crate::runtime::continuous_materialized_view::MaterializedViewDescriptor {
1846 name: q.name.clone(),
1847 source_sql: query.to_string(),
1848 source_collections: dependencies,
1849 refresh_every_ms: q.refresh_every_ms,
1850 retention_duration_ms: q.retention_duration_ms,
1851 };
1852 let store = self.inner.db.store();
1853 crate::runtime::continuous_materialized_view::persist_descriptor(
1854 store.as_ref(),
1855 &descriptor,
1856 )?;
1857
1858 self.ensure_materialized_view_backing(&q.name)?;
1865 }
1866 self.invalidate_plan_cache();
1871 self.invalidate_result_cache();
1872
1873 Ok(RuntimeQueryResult::ok_message(
1874 query.to_string(),
1875 &format!(
1876 "{}view {} created",
1877 if q.materialized { "materialized " } else { "" },
1878 q.name
1879 ),
1880 "create_view",
1881 ))
1882 }
1883 QueryExpr::DropView(ref q) => {
1884 let mut views = self.inner.views.write();
1885 let removed = views.remove(&q.name);
1886 let existed = removed.is_some();
1887 let removed_materialized =
1888 removed.as_ref().map(|v| v.materialized).unwrap_or(false);
1889 drop(views);
1890 if q.materialized || existed {
1891 self.inner.materialized_views.write().remove(&q.name);
1893 let store = self.inner.db.store();
1897 crate::runtime::continuous_materialized_view::remove_by_name(
1898 store.as_ref(),
1899 &q.name,
1900 )?;
1901 }
1902 if removed_materialized || q.materialized {
1906 self.drop_materialized_view_backing(&q.name)?;
1907 }
1908 self.invalidate_plan_cache();
1911 self.invalidate_result_cache();
1912 if !existed && !q.if_exists {
1913 return Err(RedDBError::Internal(format!(
1914 "view {} does not exist",
1915 q.name
1916 )));
1917 }
1918 self.invalidate_plan_cache();
1919 Ok(RuntimeQueryResult::ok_message(
1920 query.to_string(),
1921 &format!("view {} dropped", q.name),
1922 "drop_view",
1923 ))
1924 }
1925 QueryExpr::RefreshMaterializedView(ref q) => {
1926 let view = {
1929 let views = self.inner.views.read();
1930 views.get(&q.name).cloned()
1931 };
1932 let view = match view {
1933 Some(v) => v,
1934 None => {
1935 return Err(RedDBError::Internal(format!(
1936 "view {} does not exist",
1937 q.name
1938 )))
1939 }
1940 };
1941 if !view.materialized {
1942 return Err(RedDBError::Internal(format!(
1943 "view {} is not materialized — REFRESH requires \
1944 CREATE MATERIALIZED VIEW",
1945 q.name
1946 )));
1947 }
1948 let started = std::time::Instant::now();
1950 let now_ms = std::time::SystemTime::now()
1951 .duration_since(std::time::UNIX_EPOCH)
1952 .map(|d| d.as_millis() as u64)
1953 .unwrap_or(0);
1954 match self.execute_query_expr((*view.query).clone()) {
1955 Ok(inner_result) => {
1956 let entities =
1963 view_records_to_entities(&q.name, &inner_result.result.records);
1964 let row_count = entities.len() as u64;
1965 let store = self.inner.db.store();
1966 let serialized_records = match store.refresh_collection(&q.name, entities) {
1967 Ok(records) => records,
1968 Err(err) => {
1969 let duration_ms = started.elapsed().as_millis() as u64;
1970 let msg = err.to_string();
1971 self.inner
1972 .materialized_views
1973 .write()
1974 .record_refresh_failure(
1975 &q.name,
1976 msg.clone(),
1977 duration_ms,
1978 now_ms,
1979 );
1980 return Err(RedDBError::Internal(format!(
1981 "REFRESH MATERIALIZED VIEW {}: {msg}",
1982 q.name
1983 )));
1984 }
1985 };
1986
1987 if self.inner.db.replication.is_some() {
1993 let lsn = self.inner.cdc.emit(
1994 crate::replication::cdc::ChangeOperation::Refresh,
1995 &q.name,
1996 0,
1997 "refresh",
1998 );
1999 self.invalidate_result_cache_for_table(&q.name);
2000 let timestamp = std::time::SystemTime::now()
2001 .duration_since(std::time::UNIX_EPOCH)
2002 .unwrap_or_default()
2003 .as_millis() as u64;
2004 let record = ChangeRecord::for_refresh(
2005 lsn,
2006 timestamp,
2007 q.name.clone(),
2008 serialized_records,
2009 )
2010 .with_term(self.current_replication_term());
2011 self.stage_or_publish_logical_record(record)?;
2012 }
2013
2014 let duration_ms = started.elapsed().as_millis() as u64;
2015 let serialized = format!("{:?}", inner_result.result);
2016 self.inner
2017 .materialized_views
2018 .write()
2019 .record_refresh_success(
2020 &q.name,
2021 serialized.into_bytes(),
2022 row_count,
2023 duration_ms,
2024 now_ms,
2025 );
2026 self.invalidate_result_cache();
2031 Ok(RuntimeQueryResult::ok_message(
2032 query.to_string(),
2033 &format!("materialized view {} refreshed", q.name),
2034 "refresh_materialized_view",
2035 ))
2036 }
2037 Err(err) => {
2038 let duration_ms = started.elapsed().as_millis() as u64;
2039 let msg = err.to_string();
2040 self.inner
2041 .materialized_views
2042 .write()
2043 .record_refresh_failure(&q.name, msg.clone(), duration_ms, now_ms);
2044 Err(err)
2045 }
2046 }
2047 }
2048 QueryExpr::CreatePolicy(ref q) => {
2055 let key = (q.table.clone(), q.name.clone());
2056 self.inner
2057 .rls_policies
2058 .write()
2059 .insert(key, Arc::new(q.clone()));
2060 self.invalidate_plan_cache();
2061 self.invalidate_result_cache();
2062 self.schema_vocabulary_apply(
2066 crate::runtime::schema_vocabulary::DdlEvent::CreatePolicy {
2067 collection: q.table.clone(),
2068 policy: q.name.clone(),
2069 },
2070 );
2071 Ok(RuntimeQueryResult::ok_message(
2072 query.to_string(),
2073 &format!("policy {} on {} created", q.name, q.table),
2074 "create_policy",
2075 ))
2076 }
2077 QueryExpr::DropPolicy(ref q) => {
2078 let removed = self
2079 .inner
2080 .rls_policies
2081 .write()
2082 .remove(&(q.table.clone(), q.name.clone()))
2083 .is_some();
2084 if !removed && !q.if_exists {
2085 return Err(RedDBError::Internal(format!(
2086 "policy {} on {} does not exist",
2087 q.name, q.table
2088 )));
2089 }
2090 self.invalidate_plan_cache();
2091 self.invalidate_result_cache();
2092 self.schema_vocabulary_apply(
2095 crate::runtime::schema_vocabulary::DdlEvent::DropPolicy {
2096 collection: q.table.clone(),
2097 policy: q.name.clone(),
2098 },
2099 );
2100 Ok(RuntimeQueryResult::ok_message(
2101 query.to_string(),
2102 &format!("policy {} on {} dropped", q.name, q.table),
2103 "drop_policy",
2104 ))
2105 }
2106 QueryExpr::CreateServer(ref q) => {
2117 use crate::storage::fdw::FdwOptions;
2118 let registry = Arc::clone(&self.inner.foreign_tables);
2119 if registry.server(&q.name).is_some() {
2120 if q.if_not_exists {
2121 return Ok(RuntimeQueryResult::ok_message(
2122 query.to_string(),
2123 &format!("server {} already exists — skipped", q.name),
2124 "create_server",
2125 ));
2126 }
2127 return Err(RedDBError::Internal(format!(
2128 "server {} already exists",
2129 q.name
2130 )));
2131 }
2132 let mut opts = FdwOptions::new();
2133 for (k, v) in &q.options {
2134 opts.values.insert(k.clone(), v.clone());
2135 }
2136 registry
2137 .create_server(&q.name, &q.wrapper, opts)
2138 .map_err(|e| RedDBError::Internal(e.to_string()))?;
2139 Ok(RuntimeQueryResult::ok_message(
2140 query.to_string(),
2141 &format!("server {} created (wrapper {})", q.name, q.wrapper),
2142 "create_server",
2143 ))
2144 }
2145 QueryExpr::DropServer(ref q) => {
2146 let existed = self.inner.foreign_tables.drop_server(&q.name);
2147 if !existed && !q.if_exists {
2148 return Err(RedDBError::Internal(format!(
2149 "server {} does not exist",
2150 q.name
2151 )));
2152 }
2153 Ok(RuntimeQueryResult::ok_message(
2154 query.to_string(),
2155 &format!(
2156 "server {} dropped{}",
2157 q.name,
2158 if q.cascade { " (cascade)" } else { "" }
2159 ),
2160 "drop_server",
2161 ))
2162 }
2163 QueryExpr::CreateForeignTable(ref q) => {
2164 use crate::storage::fdw::{FdwOptions, ForeignColumn, ForeignTable};
2165 let registry = Arc::clone(&self.inner.foreign_tables);
2166 if registry.foreign_table(&q.name).is_some() {
2167 if q.if_not_exists {
2168 return Ok(RuntimeQueryResult::ok_message(
2169 query.to_string(),
2170 &format!("foreign table {} already exists — skipped", q.name),
2171 "create_foreign_table",
2172 ));
2173 }
2174 return Err(RedDBError::Internal(format!(
2175 "foreign table {} already exists",
2176 q.name
2177 )));
2178 }
2179 let mut opts = FdwOptions::new();
2180 for (k, v) in &q.options {
2181 opts.values.insert(k.clone(), v.clone());
2182 }
2183 let columns: Vec<ForeignColumn> = q
2184 .columns
2185 .iter()
2186 .map(|c| ForeignColumn {
2187 name: c.name.clone(),
2188 data_type: c.data_type.clone(),
2189 not_null: c.not_null,
2190 })
2191 .collect();
2192 registry
2193 .create_foreign_table(ForeignTable {
2194 name: q.name.clone(),
2195 server_name: q.server.clone(),
2196 columns,
2197 options: opts,
2198 })
2199 .map_err(|e| RedDBError::Internal(e.to_string()))?;
2200 self.invalidate_plan_cache();
2201 Ok(RuntimeQueryResult::ok_message(
2202 query.to_string(),
2203 &format!("foreign table {} created (server {})", q.name, q.server),
2204 "create_foreign_table",
2205 ))
2206 }
2207 QueryExpr::DropForeignTable(ref q) => {
2208 let existed = self.inner.foreign_tables.drop_foreign_table(&q.name);
2209 if !existed && !q.if_exists {
2210 return Err(RedDBError::Internal(format!(
2211 "foreign table {} does not exist",
2212 q.name
2213 )));
2214 }
2215 self.invalidate_plan_cache();
2216 Ok(RuntimeQueryResult::ok_message(
2217 query.to_string(),
2218 &format!("foreign table {} dropped", q.name),
2219 "drop_foreign_table",
2220 ))
2221 }
2222 QueryExpr::CopyFrom(ref q) => {
2228 use crate::storage::import::{CsvConfig, CsvImporter};
2229 self.check_copy_from_privilege(&q.table)?;
2230 confine_copy_from_path(&q.path)?;
2231 let store = self.inner.db.store();
2232 let cfg = CsvConfig {
2233 collection: q.table.clone(),
2234 has_header: q.has_header,
2235 delimiter: q.delimiter.map(|c| c as u8).unwrap_or(b','),
2236 ..CsvConfig::default()
2237 };
2238 let importer = CsvImporter::new(cfg);
2239 let stats = importer
2240 .import_file(&q.path, store.as_ref())
2241 .map_err(|e| RedDBError::Internal(format!("COPY failed: {e}")))?;
2242 self.note_table_write(&q.table);
2244 Ok(RuntimeQueryResult::ok_message(
2245 query.to_string(),
2246 &format!(
2247 "COPY imported {} rows into {} ({} errors skipped, {}ms)",
2248 stats.records_imported, q.table, stats.errors_skipped, stats.duration_ms
2249 ),
2250 "copy_from",
2251 ))
2252 }
2253 QueryExpr::MaintenanceCommand(ref cmd) => {
2269 use reddb_rql::ast::MaintenanceCommand as Mc;
2270 let store = self.inner.db.store();
2271 let (kind, msg) = match cmd {
2272 Mc::Analyze { target } => {
2273 let targets: Vec<String> = match target {
2274 Some(t) => vec![t.clone()],
2275 None => store.list_collections(),
2276 };
2277 for t in &targets {
2278 self.refresh_table_planner_stats(t);
2279 }
2280 (
2281 "analyze",
2282 format!("ANALYZE refreshed stats for {} table(s)", targets.len()),
2283 )
2284 }
2285 Mc::Vacuum { target, full } => {
2286 let targets: Vec<String> = match target {
2287 Some(t) => vec![t.clone()],
2288 None => store.list_collections(),
2289 };
2290 let cutoff_xid = self.mvcc_vacuum_cutoff_xid();
2291 let mut vacuum_stats =
2292 crate::storage::unified::store::MvccVacuumStats::default();
2293 for t in &targets {
2294 let stats = store.vacuum_mvcc_history(t, cutoff_xid).map_err(|e| {
2295 RedDBError::Internal(format!(
2296 "VACUUM MVCC history failed for {t}: {e}"
2297 ))
2298 })?;
2299 if stats.reclaimed_versions > 0 {
2300 self.rebuild_runtime_indexes_for_table(t)?;
2301 }
2302 vacuum_stats.add(&stats);
2303 }
2304 for t in &targets {
2309 self.refresh_table_planner_stats(t);
2310 }
2311 let persisted = if *full {
2315 match store.persist() {
2316 Ok(()) => true,
2317 Err(e) => {
2318 return Err(RedDBError::Internal(format!(
2319 "VACUUM FULL persist failed: {e:?}"
2320 )));
2321 }
2322 }
2323 } else {
2324 false
2325 };
2326 self.invalidate_result_cache();
2328 (
2329 "vacuum",
2330 format!(
2331 "VACUUM{} processed {} table(s): scanned_versions={}, retained_versions={}, reclaimed_versions={}, retained_history_versions={}, reclaimed_history_versions={}, retained_tombstones={}, reclaimed_tombstones={}{}",
2332 if *full { " FULL" } else { "" },
2333 targets.len(),
2334 vacuum_stats.scanned_versions,
2335 vacuum_stats.retained_versions,
2336 vacuum_stats.reclaimed_versions,
2337 vacuum_stats.retained_history_versions,
2338 vacuum_stats.reclaimed_history_versions,
2339 vacuum_stats.retained_tombstones,
2340 vacuum_stats.reclaimed_tombstones,
2341 if persisted {
2342 " (pages flushed to disk)"
2343 } else {
2344 ""
2345 }
2346 ),
2347 )
2348 }
2349 };
2350 Ok(RuntimeQueryResult::ok_message(
2351 query.to_string(),
2352 &msg,
2353 kind,
2354 ))
2355 }
2356 QueryExpr::Grant(ref g) => self.execute_grant_statement(query, g),
2363 QueryExpr::Revoke(ref r) => self.execute_revoke_statement(query, r),
2364 QueryExpr::AlterUser(ref a) => self.execute_alter_user_statement(query, a),
2365 QueryExpr::CreateUser(ref u) => self.execute_create_user_statement(query, u),
2366 QueryExpr::CreateIamPolicy { ref id, ref json } => {
2367 self.execute_create_iam_policy(query, id, json)
2368 }
2369 QueryExpr::DropIamPolicy { ref id } => self.execute_drop_iam_policy(query, id),
2370 QueryExpr::AttachPolicy {
2371 ref policy_id,
2372 ref principal,
2373 } => self.execute_attach_policy(query, policy_id, principal),
2374 QueryExpr::DetachPolicy {
2375 ref policy_id,
2376 ref principal,
2377 } => self.execute_detach_policy(query, policy_id, principal),
2378 QueryExpr::ShowPolicies { ref filter } => {
2379 self.execute_show_policies(query, filter.as_ref())
2380 }
2381 QueryExpr::ShowEffectivePermissions {
2382 ref user,
2383 ref resource,
2384 } => self.execute_show_effective_permissions(query, user, resource.as_ref()),
2385 QueryExpr::SimulatePolicy {
2386 ref user,
2387 ref action,
2388 ref resource,
2389 } => self.execute_simulate_policy(query, user, action, resource),
2390 QueryExpr::LintPolicy { ref source } => self.execute_lint_policy(query, source),
2391 QueryExpr::MigratePolicyMode {
2392 ref target,
2393 dry_run,
2394 } => self.execute_migrate_policy_mode(query, target, dry_run),
2395 QueryExpr::CreateMigration(ref q) => self.execute_create_migration(query, q),
2396 QueryExpr::ApplyMigration(ref q) => self.execute_apply_migration(query, q),
2397 QueryExpr::RollbackMigration(ref q) => self.execute_rollback_migration(query, q),
2398 QueryExpr::ExplainMigration(ref q) => self.execute_explain_migration(query, q),
2399 _ => Err(RedDBError::Query(
2400 "unsupported command in runtime dispatcher".to_string(),
2401 )),
2402 };
2403
2404 if _query_budget.is_some() {
2405 super::query_budget::charge(0)?;
2406 }
2407 if !control_event_specs.is_empty() {
2408 let (outcome, reason) = match &query_result {
2409 Ok(_) => (crate::runtime::control_events::Outcome::Allowed, None),
2410 Err(err) => (control_event_outcome_for_error(err), Some(err.to_string())),
2411 };
2412 for spec in &control_event_specs {
2413 self.emit_control_event(
2414 spec.kind,
2415 outcome,
2416 spec.action,
2417 spec.resource.clone(),
2418 reason.clone(),
2419 spec.fields.clone(),
2420 )?;
2421 }
2422 }
2423
2424 if let (Some(plan), Ok(result)) = (&query_audit_plan, &query_result) {
2425 self.emit_query_audit(
2426 query,
2427 plan,
2428 query_audit_started.elapsed().as_millis() as u64,
2429 result,
2430 );
2431 }
2432
2433 if let Ok(ref result) = query_result {
2440 frame.write_result_cache(self, result, result_cache_scopes);
2441 }
2442
2443 query_result
2444 }
2445
2446 pub fn execute_query_expr(&self, expr: QueryExpr) -> RedDBResult<RuntimeQueryResult> {
2466 let frame = super::statement_frame::StatementExecutionFrame::build(
2467 self,
2468 super::statement_frame::StatementIdentity::Expr(&expr),
2469 )?;
2470 let _frame_guards = Box::new(frame.install(self));
2471 let expr = self.rewrite_view_refs(expr);
2475
2476 self.validate_model_operations_before_auth(&expr)?;
2477 if let Err(err) = self.check_query_privilege(&expr) {
2481 return Err(RedDBError::Query(format!("permission denied: {err}")));
2482 }
2483
2484 frame.release_snapshot_for_maintenance(&expr);
2485 let statement = query_expr_name(&expr);
2486 let mode = detect_mode(statement);
2487 let query_str = statement;
2488
2489 self.dispatch_expr(expr, query_str, mode)
2490 }
2491
2492 pub(super) fn validate_model_operations_before_auth(
2493 &self,
2494 expr: &QueryExpr,
2495 ) -> RedDBResult<()> {
2496 self.validate_vault_table_access(expr)?;
2497 use crate::catalog::CollectionModel;
2498 use crate::runtime::ddl::polymorphic_resolver;
2499 use reddb_rql::ast::KvCommand;
2500
2501 let system_schema_target = match expr {
2502 QueryExpr::DropTable(q) => Some(q.name.as_str()),
2503 QueryExpr::DropGraph(q) => Some(q.name.as_str()),
2504 QueryExpr::DropVector(q) => Some(q.name.as_str()),
2505 QueryExpr::DropDocument(q) => Some(q.name.as_str()),
2506 QueryExpr::DropKv(q) => Some(q.name.as_str()),
2507 QueryExpr::DropCollection(q) => Some(q.name.as_str()),
2508 QueryExpr::Truncate(q) => Some(q.name.as_str()),
2509 _ => None,
2510 };
2511 if system_schema_target.is_some_and(crate::runtime::impl_ddl::is_system_schema_name) {
2512 return Err(RedDBError::Query("system schema is read-only".to_string()));
2513 }
2514
2515 let expected = match expr {
2516 QueryExpr::DropTable(q) => Some((q.name.as_str(), CollectionModel::Table)),
2517 QueryExpr::DropGraph(q) => Some((q.name.as_str(), CollectionModel::Graph)),
2518 QueryExpr::DropVector(q) => Some((q.name.as_str(), CollectionModel::Vector)),
2519 QueryExpr::DropDocument(q) => Some((q.name.as_str(), CollectionModel::Document)),
2520 QueryExpr::DropKv(q) => Some((q.name.as_str(), q.model)),
2521 QueryExpr::DropCollection(q) => q.model.map(|model| (q.name.as_str(), model)),
2522 QueryExpr::Truncate(q) => q.model.map(|model| (q.name.as_str(), model)),
2523 QueryExpr::KvCommand(cmd) => {
2524 let (collection, model) = match cmd {
2525 KvCommand::Put {
2526 collection, model, ..
2527 }
2528 | KvCommand::Get {
2529 collection, model, ..
2530 }
2531 | KvCommand::Incr {
2532 collection, model, ..
2533 }
2534 | KvCommand::Cas {
2535 collection, model, ..
2536 }
2537 | KvCommand::List {
2538 collection, model, ..
2539 }
2540 | KvCommand::Delete {
2541 collection, model, ..
2542 } => (collection.as_str(), *model),
2543 KvCommand::Rotate { collection, .. }
2544 | KvCommand::History { collection, .. }
2545 | KvCommand::Purge { collection, .. } => {
2546 (collection.as_str(), CollectionModel::Vault)
2547 }
2548 KvCommand::InvalidateTags { collection, .. } => {
2549 (collection.as_str(), CollectionModel::Kv)
2550 }
2551 KvCommand::Watch {
2552 collection, model, ..
2553 } => (collection.as_str(), *model),
2554 KvCommand::Reveal { collection, .. } => {
2555 (collection.as_str(), CollectionModel::Vault)
2556 }
2557 };
2558 Some((collection, model))
2559 }
2560 QueryExpr::ConfigCommand(cmd) => {
2561 self.validate_config_command_before_auth(cmd)?;
2562 None
2563 }
2564 _ => None,
2565 };
2566
2567 let Some((name, expected_model)) = expected else {
2568 return Ok(());
2569 };
2570 if let Some(contract) = self.inner.db.collection_contract_arc(name) {
2575 if self.inner.db.store().get_collection(name).is_some() {
2576 return polymorphic_resolver::ensure_model_match(
2577 expected_model,
2578 contract.declared_model,
2579 );
2580 }
2581 }
2582 let snapshot = self.inner.db.catalog_model_snapshot();
2583 let Some(actual_model) = snapshot
2584 .collections
2585 .iter()
2586 .find(|collection| collection.name == name)
2587 .map(|collection| collection.declared_model.unwrap_or(collection.model))
2588 else {
2589 return Ok(());
2590 };
2591 polymorphic_resolver::ensure_model_match(expected_model, actual_model)
2592 }
2593
2594 pub(super) fn rewrite_view_refs(&self, expr: QueryExpr) -> QueryExpr {
2599 if self.inner.views.read().is_empty() {
2601 return expr;
2602 }
2603 self.rewrite_view_refs_inner(expr)
2604 }
2605
2606 fn rewrite_view_refs_inner(&self, expr: QueryExpr) -> QueryExpr {
2607 use reddb_rql::ast::{Filter, TableSource};
2608 match expr {
2609 QueryExpr::Table(mut tq) => {
2610 if let Some(TableSource::Subquery(body)) = tq.source.take() {
2616 tq.source = Some(TableSource::Subquery(Box::new(
2617 self.rewrite_view_refs_inner(*body),
2618 )));
2619 return QueryExpr::Table(tq);
2620 }
2621
2622 let maybe_view = {
2626 let views = self.inner.views.read();
2627 views.get(&tq.table).cloned()
2628 };
2629 let Some(view) = maybe_view else {
2630 return QueryExpr::Table(tq);
2631 };
2632
2633 if view.materialized {
2639 return QueryExpr::Table(tq);
2640 }
2641
2642 let inner_expr = self.rewrite_view_refs_inner((*view.query).clone());
2646
2647 match inner_expr {
2655 QueryExpr::Table(mut inner_tq) => {
2656 if let Some(outer_filter) = tq.filter.take() {
2657 inner_tq.filter = Some(match inner_tq.filter.take() {
2658 Some(existing) => {
2659 Filter::And(Box::new(existing), Box::new(outer_filter))
2660 }
2661 None => outer_filter,
2662 });
2663 inner_tq.where_expr = inner_tq
2671 .filter
2672 .as_ref()
2673 .map(reddb_rql::sql_lowering::filter_to_expr);
2674 }
2675 if let Some(outer_limit) = tq.limit {
2676 inner_tq.limit = Some(match inner_tq.limit {
2677 Some(existing) => existing.min(outer_limit),
2678 None => outer_limit,
2679 });
2680 }
2681 if let Some(outer_offset) = tq.offset {
2682 inner_tq.offset = Some(match inner_tq.offset {
2683 Some(existing) => existing + outer_offset,
2684 None => outer_offset,
2685 });
2686 }
2687 QueryExpr::Table(inner_tq)
2688 }
2689 other => other,
2690 }
2691 }
2692 QueryExpr::Join(mut jq) => {
2693 jq.left = Box::new(self.rewrite_view_refs_inner(*jq.left));
2694 jq.right = Box::new(self.rewrite_view_refs_inner(*jq.right));
2695 QueryExpr::Join(jq)
2696 }
2697 other => other,
2700 }
2701 }
2702
2703 fn resolve_table_expr_subqueries(
2704 &self,
2705 mut table: TableQuery,
2706 frame: &dyn super::statement_frame::ReadFrame,
2707 ) -> RedDBResult<TableQuery> {
2708 match table.source.take() {
2715 Some(TableSource::Subquery(inner)) => {
2716 let inner = self.resolve_select_expr_subqueries(*inner, frame)?;
2717 table.source = Some(TableSource::Subquery(Box::new(inner)));
2718 }
2719 other => table.source = other,
2720 }
2721
2722 let outer_scopes = relation_scopes_for_query(&QueryExpr::Table(table.clone()));
2723 for item in &mut table.select_items {
2724 if let reddb_rql::ast::SelectItem::Expr { expr, .. } = item {
2725 *expr = self.resolve_expr_subqueries(expr.clone(), &outer_scopes, frame)?;
2726 }
2727 }
2728 if let Some(where_expr) = table.where_expr.take() {
2729 table.where_expr =
2730 Some(self.resolve_expr_subqueries(where_expr, &outer_scopes, frame)?);
2731 table.filter = None;
2732 }
2733 if let Some(having_expr) = table.having_expr.take() {
2734 table.having_expr =
2735 Some(self.resolve_expr_subqueries(having_expr, &outer_scopes, frame)?);
2736 table.having = None;
2737 }
2738 for expr in &mut table.group_by_exprs {
2739 *expr = self.resolve_expr_subqueries(expr.clone(), &outer_scopes, frame)?;
2740 }
2741 for clause in &mut table.order_by {
2742 if let Some(expr) = clause.expr.take() {
2743 clause.expr = Some(self.resolve_expr_subqueries(expr, &outer_scopes, frame)?);
2744 }
2745 }
2746 Ok(table)
2747 }
2748
2749 fn resolve_select_expr_subqueries(
2750 &self,
2751 expr: QueryExpr,
2752 frame: &dyn super::statement_frame::ReadFrame,
2753 ) -> RedDBResult<QueryExpr> {
2754 match expr {
2755 QueryExpr::Table(table) => self
2756 .resolve_table_expr_subqueries(table, frame)
2757 .map(QueryExpr::Table),
2758 QueryExpr::Join(mut join) => {
2759 join.left = Box::new(self.resolve_select_expr_subqueries(*join.left, frame)?);
2760 join.right = Box::new(self.resolve_select_expr_subqueries(*join.right, frame)?);
2761 Ok(QueryExpr::Join(join))
2762 }
2763 other => Ok(other),
2764 }
2765 }
2766
2767 fn resolve_expr_subqueries(
2768 &self,
2769 expr: reddb_rql::ast::Expr,
2770 outer_scopes: &[String],
2771 frame: &dyn super::statement_frame::ReadFrame,
2772 ) -> RedDBResult<reddb_rql::ast::Expr> {
2773 use reddb_rql::ast::Expr;
2774
2775 match expr {
2776 Expr::Subquery { query, span } => {
2777 let values = self.execute_expr_subquery_values(query, outer_scopes, frame)?;
2778 if values.len() > 1 {
2779 return Err(RedDBError::Query(
2780 "scalar subquery returned more than one row".to_string(),
2781 ));
2782 }
2783 Ok(Expr::Literal {
2784 value: values.into_iter().next().unwrap_or(Value::Null),
2785 span,
2786 })
2787 }
2788 Expr::BinaryOp { op, lhs, rhs, span } => Ok(Expr::BinaryOp {
2789 op,
2790 lhs: Box::new(self.resolve_expr_subqueries(*lhs, outer_scopes, frame)?),
2791 rhs: Box::new(self.resolve_expr_subqueries(*rhs, outer_scopes, frame)?),
2792 span,
2793 }),
2794 Expr::UnaryOp { op, operand, span } => Ok(Expr::UnaryOp {
2795 op,
2796 operand: Box::new(self.resolve_expr_subqueries(*operand, outer_scopes, frame)?),
2797 span,
2798 }),
2799 Expr::Cast {
2800 inner,
2801 target,
2802 span,
2803 } => Ok(Expr::Cast {
2804 inner: Box::new(self.resolve_expr_subqueries(*inner, outer_scopes, frame)?),
2805 target,
2806 span,
2807 }),
2808 Expr::FunctionCall { name, args, span } => {
2809 let args = args
2810 .into_iter()
2811 .map(|arg| self.resolve_expr_subqueries(arg, outer_scopes, frame))
2812 .collect::<RedDBResult<Vec<_>>>()?;
2813 Ok(Expr::FunctionCall { name, args, span })
2814 }
2815 Expr::WindowFunctionCall {
2816 name,
2817 args,
2818 mut window,
2819 span,
2820 } => {
2821 let args = args
2822 .into_iter()
2823 .map(|arg| self.resolve_expr_subqueries(arg, outer_scopes, frame))
2824 .collect::<RedDBResult<Vec<_>>>()?;
2825 for expr in &mut window.partition_by {
2826 *expr = self.resolve_expr_subqueries(expr.clone(), outer_scopes, frame)?;
2827 }
2828 for clause in &mut window.order_by {
2829 clause.expr =
2830 self.resolve_expr_subqueries(clause.expr.clone(), outer_scopes, frame)?;
2831 }
2832 Ok(Expr::WindowFunctionCall {
2833 name,
2834 args,
2835 window,
2836 span,
2837 })
2838 }
2839 Expr::Case {
2840 branches,
2841 else_,
2842 span,
2843 } => {
2844 let branches = branches
2845 .into_iter()
2846 .map(|(cond, value)| {
2847 Ok((
2848 self.resolve_expr_subqueries(cond, outer_scopes, frame)?,
2849 self.resolve_expr_subqueries(value, outer_scopes, frame)?,
2850 ))
2851 })
2852 .collect::<RedDBResult<Vec<_>>>()?;
2853 let else_ = else_
2854 .map(|expr| self.resolve_expr_subqueries(*expr, outer_scopes, frame))
2855 .transpose()?
2856 .map(Box::new);
2857 Ok(Expr::Case {
2858 branches,
2859 else_,
2860 span,
2861 })
2862 }
2863 Expr::IsNull {
2864 operand,
2865 negated,
2866 span,
2867 } => Ok(Expr::IsNull {
2868 operand: Box::new(self.resolve_expr_subqueries(*operand, outer_scopes, frame)?),
2869 negated,
2870 span,
2871 }),
2872 Expr::InList {
2873 target,
2874 values,
2875 negated,
2876 span,
2877 } => {
2878 let target =
2879 Box::new(self.resolve_expr_subqueries(*target, outer_scopes, frame)?);
2880 let mut resolved = Vec::new();
2881 for value in values {
2882 if let Expr::Subquery { query, .. } = value {
2883 resolved.extend(
2884 self.execute_expr_subquery_values(query, outer_scopes, frame)?
2885 .into_iter()
2886 .map(Expr::lit),
2887 );
2888 } else {
2889 resolved.push(self.resolve_expr_subqueries(value, outer_scopes, frame)?);
2890 }
2891 }
2892 Ok(Expr::InList {
2893 target,
2894 values: resolved,
2895 negated,
2896 span,
2897 })
2898 }
2899 Expr::Between {
2900 target,
2901 low,
2902 high,
2903 negated,
2904 span,
2905 } => Ok(Expr::Between {
2906 target: Box::new(self.resolve_expr_subqueries(*target, outer_scopes, frame)?),
2907 low: Box::new(self.resolve_expr_subqueries(*low, outer_scopes, frame)?),
2908 high: Box::new(self.resolve_expr_subqueries(*high, outer_scopes, frame)?),
2909 negated,
2910 span,
2911 }),
2912 other => Ok(other),
2913 }
2914 }
2915
2916 fn execute_expr_subquery_values(
2917 &self,
2918 subquery: reddb_rql::ast::ExprSubquery,
2919 outer_scopes: &[String],
2920 frame: &dyn super::statement_frame::ReadFrame,
2921 ) -> RedDBResult<Vec<Value>> {
2922 let query = *subquery.query;
2923 if query_references_outer_scope(&query, outer_scopes) {
2924 return Err(RedDBError::Query(
2925 "NOT_YET_SUPPORTED: correlated subqueries are not supported yet; track follow-up issue #470-correlated-subqueries".to_string(),
2926 ));
2927 }
2928 let query = self.rewrite_view_refs(query);
2929 let query = self.resolve_select_expr_subqueries(query, frame)?;
2930 let query = self.authorize_relational_select_expr(query, frame)?;
2931 let result = match query {
2932 QueryExpr::Table(table) => {
2933 execute_runtime_table_query(&self.inner.db, &table, Some(&self.inner.index_store))?
2934 }
2935 QueryExpr::Join(join) => execute_runtime_join_query(self, &join)?,
2936 other => {
2937 return Err(RedDBError::Query(format!(
2938 "expression subquery must be a SELECT query, got {}",
2939 query_expr_name(&other)
2940 )))
2941 }
2942 };
2943 first_column_values(result)
2944 }
2945
2946 fn execute_materialized_graph(
2947 &self,
2948 query: &str,
2949 mode: QueryMode,
2950 statement: &'static str,
2951 expr: &QueryExpr,
2952 ) -> RedDBResult<RuntimeQueryResult> {
2953 let (graph, node_properties, edge_properties) = self.materialize_graph_with_rls()?;
2961 let result =
2962 crate::storage::query::unified::UnifiedExecutor::execute_on_with_graph_properties_checked(
2963 &graph,
2964 expr,
2965 node_properties,
2966 edge_properties,
2967 super::function_budget::graph_work_check(),
2968 )
2969 .map_err(|err| RedDBError::Query(err.to_string()))?;
2970
2971 Ok(RuntimeQueryResult {
2972 query: query.to_string(),
2973 mode,
2974 statement,
2975 engine: "materialized-graph",
2976 result,
2977 affected_rows: 0,
2978 statement_type: "select",
2979 bookmark: None,
2980 notice: None,
2981 })
2982 }
2983
2984 fn dispatch_expr(
2985 &self,
2986 expr: QueryExpr,
2987 query_str: &str,
2988 mode: QueryMode,
2989 ) -> RedDBResult<RuntimeQueryResult> {
2990 let is_read = super::query_budget::is_materialized_read(&expr);
2993 let _budget = if is_read {
2994 Some(super::query_budget::Scope::enter(
2995 self.resource_limits(),
2996 &self.inner.query_budget_slots,
2997 )?)
2998 } else {
2999 None
3000 };
3001 let result = self.dispatch_expr_inner(expr, query_str, mode);
3002 if is_read {
3003 super::query_budget::charge(0)?;
3004 }
3005 let result = result?;
3006 super::query_budget::check_result(self.resource_limits(), &result)?;
3007 if is_read {
3008 super::query_budget::charge(0)?;
3009 }
3010 Ok(result)
3011 }
3012
3013 fn dispatch_expr_inner(
3014 &self,
3015 expr: QueryExpr,
3016 query_str: &str,
3017 mode: QueryMode,
3018 ) -> RedDBResult<RuntimeQueryResult> {
3019 let statement = query_expr_name(&expr);
3020 match expr {
3021 QueryExpr::Function(command) => self.execute_function(query_str, &command),
3022 QueryExpr::Graph(_) | QueryExpr::Path(_) => {
3023 self.execute_materialized_graph(query_str, mode, statement, &expr)
3024 }
3025 QueryExpr::Table(table) => {
3026 let scope = self.ai_scope();
3027 let table = self.resolve_table_expr_subqueries(
3028 table,
3029 &scope as &dyn super::statement_frame::ReadFrame,
3030 )?;
3031 if let Some(TableSource::Function {
3035 name,
3036 args,
3037 named_args,
3038 }) = table.source.clone()
3039 {
3040 return Ok(RuntimeQueryResult {
3041 query: query_str.to_string(),
3042 mode,
3043 statement,
3044 engine: "runtime-graph-tvf",
3045 result: self.execute_table_function(&name, &args, &named_args)?,
3046 affected_rows: 0,
3047 statement_type: "select",
3048 bookmark: None,
3049 notice: None,
3050 });
3051 }
3052 if let Some(TableSource::InlineGraphFunction {
3056 name,
3057 nodes,
3058 edges,
3059 named_args,
3060 }) = table.source.clone()
3061 {
3062 return Ok(RuntimeQueryResult {
3063 query: query_str.to_string(),
3064 mode,
3065 statement,
3066 engine: "runtime-graph-tvf-inline",
3067 result: self.execute_inline_graph_function(
3068 &name,
3069 &nodes,
3070 &edges,
3071 &named_args,
3072 )?,
3073 affected_rows: 0,
3074 statement_type: "select",
3075 bookmark: None,
3076 notice: None,
3077 });
3078 }
3079 if super::red_schema::is_virtual_table(&table.table) {
3080 return Ok(RuntimeQueryResult {
3081 query: query_str.to_string(),
3082 mode,
3083 statement,
3084 engine: "runtime-red-schema",
3085 result: super::red_schema::red_query(
3086 self,
3087 &table.table,
3088 &table,
3089 &scope as &dyn super::statement_frame::ReadFrame,
3090 )?,
3091 affected_rows: 0,
3092 statement_type: "select",
3093 bookmark: None,
3094 notice: None,
3095 });
3096 }
3097 if let Some(view_result) = self.try_resolve_analytics_view(
3099 &table,
3100 &scope as &dyn super::statement_frame::ReadFrame,
3101 )? {
3102 return Ok(RuntimeQueryResult {
3103 query: query_str.to_string(),
3104 mode,
3105 statement,
3106 engine: "runtime-graph-analytics-view",
3107 result: view_result,
3108 affected_rows: 0,
3109 statement_type: "select",
3110 bookmark: None,
3111 notice: None,
3112 });
3113 }
3114 let Some(table_with_rls) = self.authorize_relational_table_select(
3115 table,
3116 &scope as &dyn super::statement_frame::ReadFrame,
3117 )?
3118 else {
3119 return Ok(RuntimeQueryResult {
3120 query: query_str.to_string(),
3121 mode,
3122 statement,
3123 engine: "runtime-table-rls",
3124 result: crate::storage::query::unified::UnifiedResult::empty(),
3125 affected_rows: 0,
3126 statement_type: "select",
3127 bookmark: None,
3128 notice: None,
3129 });
3130 };
3131 Ok(RuntimeQueryResult {
3132 query: query_str.to_string(),
3133 mode,
3134 statement,
3135 engine: "runtime-table",
3136 result: execute_runtime_table_query(
3137 &self.inner.db,
3138 &table_with_rls,
3139 Some(&self.inner.index_store),
3140 )?,
3141 affected_rows: 0,
3142 statement_type: "select",
3143 bookmark: None,
3144 notice: None,
3145 })
3146 }
3147 QueryExpr::Join(join) => {
3148 let scope = self.ai_scope();
3149 let Some(join_with_rls) = self.authorize_relational_join_select(
3150 join,
3151 &scope as &dyn super::statement_frame::ReadFrame,
3152 )?
3153 else {
3154 return Ok(RuntimeQueryResult {
3155 query: query_str.to_string(),
3156 mode,
3157 statement,
3158 engine: "runtime-join-rls",
3159 result: crate::storage::query::unified::UnifiedResult::empty(),
3160 affected_rows: 0,
3161 statement_type: "select",
3162 bookmark: None,
3163 notice: None,
3164 });
3165 };
3166 Ok(RuntimeQueryResult {
3167 query: query_str.to_string(),
3168 mode,
3169 statement,
3170 engine: "runtime-join",
3171 result: execute_runtime_join_query(self, &join_with_rls)?,
3172 affected_rows: 0,
3173 statement_type: "select",
3174 bookmark: None,
3175 notice: None,
3176 })
3177 }
3178 QueryExpr::Vector(vector) => Ok(RuntimeQueryResult {
3179 query: query_str.to_string(),
3180 mode,
3181 statement,
3182 engine: "runtime-vector",
3183 result: execute_runtime_vector_query(self, &vector)?,
3184 affected_rows: 0,
3185 statement_type: "select",
3186 bookmark: None,
3187 notice: None,
3188 }),
3189 QueryExpr::Hybrid(hybrid) => Ok(RuntimeQueryResult {
3190 query: query_str.to_string(),
3191 mode,
3192 statement,
3193 engine: "runtime-hybrid",
3194 result: execute_runtime_hybrid_query(self, &hybrid)?,
3195 affected_rows: 0,
3196 statement_type: "select",
3197 bookmark: None,
3198 notice: None,
3199 }),
3200 QueryExpr::Insert(ref insert) if super::red_schema::is_virtual_table(&insert.table) => {
3201 Err(RedDBError::Query(
3202 super::red_schema::READ_ONLY_ERROR.to_string(),
3203 ))
3204 }
3205 QueryExpr::Update(ref update) if super::red_schema::is_virtual_table(&update.table) => {
3206 Err(RedDBError::Query(
3207 super::red_schema::READ_ONLY_ERROR.to_string(),
3208 ))
3209 }
3210 QueryExpr::Delete(ref delete) if super::red_schema::is_virtual_table(&delete.table) => {
3211 Err(RedDBError::Query(
3212 super::red_schema::READ_ONLY_ERROR.to_string(),
3213 ))
3214 }
3215 QueryExpr::Insert(ref insert) => self
3216 .with_deferred_store_wal_for_dml(self.insert_may_emit_events(insert), || {
3217 self.execute_insert(query_str, insert)
3218 }),
3219 QueryExpr::Update(ref update) => self
3220 .with_deferred_store_wal_for_dml(self.update_may_emit_events(update), || {
3221 self.execute_update(query_str, update)
3222 }),
3223 QueryExpr::Delete(ref delete) => self
3224 .with_deferred_store_wal_for_dml(self.delete_may_emit_events(delete), || {
3225 self.execute_delete(query_str, delete)
3226 }),
3227 QueryExpr::QueueCommand(ref cmd) => self.with_deferred_store_wal_if_transaction(|| {
3228 self.execute_queue_command(query_str, cmd)
3229 }),
3230 QueryExpr::KvCommand(ref cmd) => self.execute_kv_command(query_str, cmd),
3231 QueryExpr::SearchCommand(ref cmd) => self.execute_search_command(query_str, cmd),
3232 QueryExpr::Ask(ref ask) => self.execute_ask(query_str, ask),
3233 _ => Err(RedDBError::Query(format!(
3234 "prepared-statement execution does not support {statement} statements"
3235 ))),
3236 }
3237 }
3238
3239 fn execute_table_function(
3246 &self,
3247 name: &str,
3248 args: &[String],
3249 named_args: &[(String, f64)],
3250 ) -> RedDBResult<crate::storage::query::unified::UnifiedResult> {
3251 if name.eq_ignore_ascii_case("red.diff") {
3252 return self.execute_vcs_diff_tvf(args, named_args);
3253 }
3254 if !is_graph_tvf_name(name) {
3255 return Err(RedDBError::Query(format!("unknown table function: {name}")));
3256 }
3257 if args.len() != 1 {
3259 return Err(RedDBError::Query(format!(
3260 "table function '{name}' takes exactly 1 graph argument, got {}",
3261 args.len()
3262 )));
3263 }
3264
3265 let (nodes, edges) = self.materialize_whole_graph_abstract()?;
3270 self.dispatch_graph_algorithm(name, nodes, edges, named_args)
3271 }
3272
3273 pub(crate) fn revive_versioned_updates_since(
3274 &self,
3275 conn_id: u64,
3276 stamper_xid: u64,
3277 ) -> RedDBResult<usize> {
3278 let candidates = {
3281 let guard = self.inner.pending_versioned_updates.read();
3282 let Some(pending) = guard.get(&conn_id) else {
3283 return Ok(0);
3284 };
3285 pending
3286 .iter()
3287 .rev()
3288 .filter(|entry| entry.3 >= stamper_xid)
3289 .cloned()
3290 .collect::<Vec<_>>()
3291 };
3292 let mut reverted = 0usize;
3293 for candidate in candidates {
3294 let (collection, old_id, new_id, xid, previous_xmax) = &candidate;
3295 let topology_lock = self.index_store_ref().collection_topology_lock(collection);
3296 #[cfg(test)]
3297 self.index_store_ref().mutation_test_hook(
3298 collection,
3299 super::index_store::MutationTestPhase::BeforeUndoTopology,
3300 );
3301 let topology_guard = topology_lock.read();
3302 let mut guard = self.inner.pending_versioned_updates.write();
3303 let Some(pending) = guard.get_mut(&conn_id) else {
3304 continue;
3305 };
3306 let Some(position) = pending.iter().rposition(|entry| entry == &candidate) else {
3309 continue;
3310 };
3311 self.revive_versioned_update(
3312 collection,
3313 *old_id,
3314 *new_id,
3315 *xid,
3316 *previous_xmax,
3317 topology_guard,
3318 )?;
3319 pending.remove(position);
3320 reverted += 1;
3321 if pending.is_empty() {
3322 guard.remove(&conn_id);
3323 }
3324 }
3325 Ok(reverted)
3326 }
3327
3328 fn check_explain_target_privilege(&self, inner_sql: &str) -> RedDBResult<()> {
3334 let parsed =
3335 reddb_rql::parser::parse(inner_sql).map_err(|e| RedDBError::Query(e.to_string()))?;
3336 self.check_query_privilege(&parsed.query)
3337 .map_err(|err| RedDBError::Query(format!("permission denied: {err}")))
3338 }
3339
3340 fn explain_as_rows(&self, raw_query: &str, inner_sql: &str) -> RedDBResult<RuntimeQueryResult> {
3348 self.check_explain_target_privilege(inner_sql)?;
3349 let explain = self.explain_query(inner_sql)?;
3350
3351 let mut columns = vec![
3352 "op".to_string(),
3353 "source".to_string(),
3354 "estimated_rows".to_string(),
3355 "estimated_cost".to_string(),
3356 "depth".to_string(),
3357 ];
3358
3359 let mut records: Vec<crate::storage::query::unified::UnifiedRecord> = Vec::new();
3360
3361 for name in &explain.cte_materializations {
3367 use std::sync::Arc;
3368 let mut rec = crate::storage::query::unified::UnifiedRecord::default();
3369 rec.set_arc(Arc::from("op"), Value::text("CteScan".to_string()));
3370 rec.set_arc(Arc::from("source"), Value::text(name.clone()));
3371 rec.set_arc(Arc::from("estimated_rows"), Value::Float(0.0));
3372 rec.set_arc(Arc::from("estimated_cost"), Value::Float(0.0));
3373 rec.set_arc(Arc::from("depth"), Value::Integer(0));
3374 records.push(rec);
3375 }
3376
3377 let vector_details = explain.statement == "vector";
3378 if vector_details {
3379 columns.extend(["access_path_reason".to_string(), "index_hint".to_string()]);
3380 }
3381 walk_plan_node(&explain.logical_plan.root, 0, &mut records, vector_details);
3382
3383 let result = crate::storage::query::unified::UnifiedResult {
3384 columns,
3385 records,
3386 stats: Default::default(),
3387 pre_serialized_json: None,
3388 };
3389
3390 Ok(RuntimeQueryResult {
3391 query: raw_query.to_string(),
3392 mode: explain.mode,
3393 statement: "explain",
3394 engine: "runtime-explain",
3395 result,
3396 affected_rows: 0,
3397 statement_type: "select",
3398 bookmark: None,
3399 notice: None,
3400 })
3401 }
3402
3403 fn explain_analyze_vector_as_rows(
3404 &self,
3405 raw_query: &str,
3406 expr: QueryExpr,
3407 ) -> RedDBResult<RuntimeQueryResult> {
3408 let QueryExpr::Vector(query) = &expr else {
3409 return Err(RedDBError::Internal(
3410 "expected vector analysis target".to_string(),
3411 ));
3412 };
3413 let source = query.collection.clone();
3414 let execution = self.execute_query_expr(expr)?;
3417 let stats = execution.result.stats;
3418 let vector = stats.vector.as_ref().ok_or_else(|| {
3419 RedDBError::Internal("vector executor did not provide measurements".to_string())
3420 })?;
3421 let fields = vec![
3422 ("op", Value::text(vector.access_path.clone())),
3423 ("source", Value::text(source)),
3424 ("index_used", Value::Boolean(vector.index_used)),
3425 ("mode_requested", Value::text(vector.mode_requested.clone())),
3426 ("mode_executed", Value::text(vector.mode_executed.clone())),
3427 (
3428 "fallback_reason",
3429 vector
3430 .fallback_reason
3431 .as_ref()
3432 .map_or(Value::Null, |reason| Value::text(reason.clone())),
3433 ),
3434 (
3435 "approximate_distance_evaluations",
3436 Value::UnsignedInteger(vector.approximate_distance_evaluations),
3437 ),
3438 (
3439 "peak_topk_entries",
3440 Value::UnsignedInteger(vector.peak_topk_entries),
3441 ),
3442 (
3443 "candidates_examined",
3444 Value::UnsignedInteger(vector.candidates_examined),
3445 ),
3446 (
3447 "metadata_rejected",
3448 Value::UnsignedInteger(vector.metadata_rejected),
3449 ),
3450 ("rls_rejected", Value::UnsignedInteger(vector.rls_rejected)),
3451 (
3452 "visibility_rejected",
3453 Value::UnsignedInteger(vector.visibility_rejected),
3454 ),
3455 (
3456 "exact_distance_evaluations",
3457 Value::UnsignedInteger(vector.exact_distance_evaluations),
3458 ),
3459 ("actual_rows", Value::UnsignedInteger(vector.rows_returned)),
3460 (
3461 "actual_ms",
3462 Value::Float(stats.exec_time_us as f64 / 1000.0),
3463 ),
3464 ("metrics_scope", Value::text("vector_pipeline")),
3465 (
3466 "operators",
3467 Value::Json(vector.operators_json().to_string().into_bytes()),
3468 ),
3469 ];
3470 let columns = fields.iter().map(|(name, _)| name.to_string()).collect();
3471 let mut record = crate::storage::query::unified::UnifiedRecord::default();
3472 for (name, value) in fields {
3473 record.set_owned(name.to_string(), value);
3474 }
3475 Ok(RuntimeQueryResult {
3476 query: raw_query.to_string(),
3477 mode: execution.mode,
3478 statement: "explain_analyze",
3479 engine: "runtime-explain-analyze",
3480 result: crate::storage::query::unified::UnifiedResult {
3481 columns,
3482 records: vec![record],
3483 stats,
3484 pre_serialized_json: None,
3485 },
3486 affected_rows: 0,
3487 statement_type: "select",
3488 bookmark: None,
3489 notice: None,
3490 })
3491 }
3492
3493 fn explain_analyze_as_rows(
3494 &self,
3495 raw_query: &str,
3496 inner_sql: &str,
3497 ) -> RedDBResult<RuntimeQueryResult> {
3498 if strip_keyword_ci(inner_sql.trim_start(), "VECTOR").is_some() {
3499 let expr = parse_multi(inner_sql).map_err(|err| RedDBError::Query(err.to_string()))?;
3500 if matches!(&expr, QueryExpr::Vector(_)) {
3501 return self.explain_analyze_vector_as_rows(raw_query, expr);
3502 }
3503 }
3504 if !starts_with_dml_keyword(inner_sql) {
3505 return Err(RedDBError::Query(
3506 "EXPLAIN ANALYZE currently supports INSERT, UPDATE, DELETE, and VECTOR SEARCH"
3507 .to_string(),
3508 ));
3509 }
3510
3511 self.check_explain_target_privilege(inner_sql)?;
3512 let explain = self.explain_query(inner_sql)?;
3513 let conn_id = current_connection_id();
3514 if self.inner.transaction_state.in_transaction(conn_id) {
3515 return Err(RedDBError::Query(
3516 "EXPLAIN ANALYZE requires no active transaction".to_string(),
3517 ));
3518 }
3519
3520 self.execute_query_inner("BEGIN")?;
3521 let started = std::time::Instant::now();
3522 let execution = self.execute_query_inner(inner_sql);
3523 let elapsed_ms = started.elapsed().as_secs_f64() * 1000.0;
3524 let rollback = self.execute_query_inner("ROLLBACK");
3525
3526 let affected_rows = match execution {
3527 Ok(result) => result.affected_rows,
3528 Err(err) => {
3529 rollback?;
3530 return Err(err);
3531 }
3532 };
3533 rollback?;
3534
3535 let columns = vec![
3536 "op".to_string(),
3537 "source".to_string(),
3538 "estimated_rows".to_string(),
3539 "estimated_cost".to_string(),
3540 "actual_rows".to_string(),
3541 "actual_ms".to_string(),
3542 "depth".to_string(),
3543 ];
3544 let mut records = Vec::new();
3545 walk_analyze_plan_node(
3546 &explain.logical_plan.root,
3547 0,
3548 affected_rows,
3549 elapsed_ms,
3550 &mut records,
3551 );
3552
3553 let result = crate::storage::query::unified::UnifiedResult {
3554 columns,
3555 records,
3556 stats: Default::default(),
3557 pre_serialized_json: None,
3558 };
3559
3560 Ok(RuntimeQueryResult {
3561 query: raw_query.to_string(),
3562 mode: explain.mode,
3563 statement: "explain_analyze",
3564 engine: "runtime-explain-analyze",
3565 result,
3566 affected_rows: 0,
3567 statement_type: "select",
3568 bookmark: None,
3569 notice: None,
3570 })
3571 }
3572}
3573
3574fn redact_sql_for_slow_log(query: &str) -> String {
3575 let lowered = query.to_ascii_lowercase();
3576 if lowered.contains("secret") || lowered.contains("vault") {
3577 return "secret statement (SQL redacted)".to_string();
3578 }
3579 let credential_candidate = lowered.contains("password")
3580 && ((lowered.contains("create") && lowered.contains("user"))
3581 || (lowered.contains("alter") && lowered.contains("user")));
3582 if !credential_candidate {
3583 return query.to_string();
3584 }
3585 match parse_multi(query) {
3586 Ok(QueryExpr::CreateUser(stmt)) => {
3587 let target = UserId::from_parts(stmt.tenant.as_deref(), &stmt.username);
3588 format!("CREATE USER {target} PASSWORD '***' ROLE {}", stmt.role)
3589 }
3590 Ok(QueryExpr::AlterUser(stmt))
3591 if stmt
3592 .attributes
3593 .iter()
3594 .any(|attr| matches!(attr, reddb_rql::ast::AlterUserAttribute::Password(_))) =>
3595 {
3596 let target = UserId::from_parts(stmt.tenant.as_deref(), &stmt.username);
3597 format!("ALTER USER {target} PASSWORD '***'")
3598 }
3599 Ok(_) => "credential statement (SQL redacted)".to_string(),
3600 Err(_) => "credential statement (SQL redacted)".to_string(),
3601 }
3602}
3603
3604fn strip_explain_analyze_prefix(sql: &str) -> Option<&str> {
3605 let rest = strip_keyword_ci(sql.trim_start(), "EXPLAIN")?.trim_start();
3606 Some(strip_keyword_ci(rest, "ANALYZE")?.trim_start()).filter(|inner| !inner.is_empty())
3607}
3608
3609fn strip_keyword_ci<'a>(sql: &'a str, keyword: &str) -> Option<&'a str> {
3610 let head = sql.get(..keyword.len())?;
3613 let rest = &sql[keyword.len()..];
3614 if !head.eq_ignore_ascii_case(keyword) {
3615 return None;
3616 }
3617 if rest
3618 .chars()
3619 .next()
3620 .is_some_and(|ch| !ch.is_ascii_whitespace())
3621 {
3622 return None;
3623 }
3624 Some(rest)
3625}
3626
3627fn starts_with_dml_keyword(sql: &str) -> bool {
3628 let trimmed = sql.trim_start();
3629 let head_end = trimmed
3630 .find(|ch: char| ch.is_ascii_whitespace())
3631 .unwrap_or(trimmed.len());
3632 let head = &trimmed[..head_end];
3633 head.eq_ignore_ascii_case("INSERT")
3634 || head.eq_ignore_ascii_case("UPDATE")
3635 || head.eq_ignore_ascii_case("DELETE")
3636}
3637
3638fn walk_plan_node(
3639 node: &crate::storage::query::planner::CanonicalLogicalNode,
3640 depth: usize,
3641 out: &mut Vec<crate::storage::query::unified::UnifiedRecord>,
3642 vector_details: bool,
3643) {
3644 use std::sync::Arc;
3645 let mut rec = crate::storage::query::unified::UnifiedRecord::default();
3646 rec.set_arc(Arc::from("op"), Value::text(node.operator.clone()));
3647 rec.set_arc(
3648 Arc::from("source"),
3649 node.source.clone().map(Value::text).unwrap_or(Value::Null),
3650 );
3651 rec.set_arc(
3652 Arc::from("estimated_rows"),
3653 Value::Float(node.estimated_rows),
3654 );
3655 rec.set_arc(
3656 Arc::from("estimated_cost"),
3657 Value::Float(node.operator_cost),
3658 );
3659 rec.set_arc(Arc::from("depth"), Value::Integer(depth as i64));
3660 if vector_details {
3661 for name in ["access_path_reason", "index_hint"] {
3662 rec.set_owned(
3663 name.to_string(),
3664 node.details
3665 .get(name)
3666 .cloned()
3667 .map(Value::text)
3668 .unwrap_or(Value::Null),
3669 );
3670 }
3671 }
3672 out.push(rec);
3673 for child in &node.children {
3674 walk_plan_node(child, depth + 1, out, vector_details);
3675 }
3676}
3677
3678fn walk_analyze_plan_node(
3679 node: &crate::storage::query::planner::CanonicalLogicalNode,
3680 depth: usize,
3681 affected_rows: u64,
3682 elapsed_ms: f64,
3683 out: &mut Vec<crate::storage::query::unified::UnifiedRecord>,
3684) {
3685 use std::sync::Arc;
3686 let mut rec = crate::storage::query::unified::UnifiedRecord::default();
3687 rec.set_arc(Arc::from("op"), Value::text(node.operator.clone()));
3688 rec.set_arc(
3689 Arc::from("source"),
3690 node.source.clone().map(Value::text).unwrap_or(Value::Null),
3691 );
3692 rec.set_arc(
3693 Arc::from("estimated_rows"),
3694 Value::Float(node.estimated_rows),
3695 );
3696 rec.set_arc(
3697 Arc::from("estimated_cost"),
3698 Value::Float(node.operator_cost),
3699 );
3700 rec.set_arc(
3701 Arc::from("actual_rows"),
3702 Value::UnsignedInteger(if depth == 0 { affected_rows } else { 0 }),
3703 );
3704 rec.set_arc(
3705 Arc::from("actual_ms"),
3706 Value::Float(if depth == 0 { elapsed_ms } else { 0.0 }),
3707 );
3708 rec.set_arc(Arc::from("depth"), Value::Integer(depth as i64));
3709 out.push(rec);
3710
3711 for child in &node.children {
3712 walk_analyze_plan_node(child, depth + 1, 0, 0.0, out);
3713 }
3714}
3715
3716#[cfg(test)]
3717mod declared_model_validation_tests {
3718 use super::*;
3719 use crate::catalog::CollectionModel;
3720 use crate::storage::no_malloc_hot_paths::measure_allocations;
3721 use reddb_rql::ast::KvCommand;
3722
3723 fn get(collection: &str) -> QueryExpr {
3724 QueryExpr::KvCommand(KvCommand::Get {
3725 model: CollectionModel::Kv,
3726 collection: collection.into(),
3727 key: "item".into(),
3728 })
3729 }
3730
3731 #[test]
3732 fn declared_model_validation_keeps_mismatches_and_observes_contract_changes() {
3733 let runtime = RedDBRuntime::in_memory().expect("runtime");
3734 runtime
3735 .execute_query("KV PUT declared.item = 1")
3736 .expect("seed");
3737 runtime
3738 .validate_model_operations_before_auth(&get("declared"))
3739 .expect("KV contract");
3740 let db = runtime.db();
3741 for (model, name) in [
3742 (CollectionModel::Table, "table"),
3743 (CollectionModel::Mixed, "mixed"),
3744 ] {
3745 let mut contract = db.collection_contract("declared").expect("contract");
3746 contract.declared_model = model;
3747 db.save_collection_contract(contract)
3748 .expect("replace contract");
3749 let error = runtime
3750 .validate_model_operations_before_auth(&get("declared"))
3751 .expect_err("mismatch");
3752 assert_eq!(
3753 error.to_string(),
3754 format!("INVALID_OPERATION: model mismatch: expected kv, got {name}")
3755 );
3756 }
3757 }
3758
3759 #[test]
3760 fn declared_model_validation_preserves_legacy_inference_and_missing_collections() {
3761 let runtime = RedDBRuntime::in_memory().expect("runtime");
3762 let db = runtime.db();
3763 let store = db.store();
3764 store
3765 .create_collection("legacy")
3766 .expect("legacy collection");
3767 store
3768 .get_collection("legacy")
3769 .expect("manager")
3770 .insert(crate::storage::UnifiedEntity::table_row(
3771 crate::storage::EntityId::new(1),
3772 "legacy",
3773 1,
3774 Vec::new(),
3775 ))
3776 .expect("uncontracted table row");
3777 assert!(db.collection_contract("legacy").is_none());
3778 let error = runtime
3779 .validate_model_operations_before_auth(&get("legacy"))
3780 .expect_err("inferred table");
3781 assert!(error
3782 .to_string()
3783 .contains("model mismatch: expected kv, got table"));
3784 runtime
3785 .validate_model_operations_before_auth(&get("missing"))
3786 .expect("missing stays deferred to execution");
3787 }
3788
3789 #[test]
3790 fn declared_model_validation_does_not_treat_orphan_contracts_as_collections() {
3791 let runtime = RedDBRuntime::in_memory().expect("runtime");
3792 runtime
3793 .execute_query("KV PUT declared.item = 1")
3794 .expect("seed");
3795 let db = runtime.db();
3796 let mut contract = db.collection_contract("declared").expect("contract");
3797 contract.name = "orphan".into();
3798 contract.declared_model = CollectionModel::Table;
3799 db.save_collection_contract(contract)
3800 .expect("orphan contract");
3801 assert!(db.store().get_collection("orphan").is_none());
3802 runtime
3803 .validate_model_operations_before_auth(&get("orphan"))
3804 .expect("no physical collection to validate");
3805 }
3806
3807 #[test]
3808 fn declared_model_validation_allocates_nothing_with_unrelated_collections() {
3809 let runtime = RedDBRuntime::in_memory().expect("runtime");
3810 runtime
3811 .execute_query("KV PUT declared.item = 1")
3812 .expect("seed");
3813 let expr = get("declared");
3814 for unrelated in [0, 64] {
3815 let db = runtime.db();
3816 let store = db.store();
3817 for index in 0..unrelated {
3818 let name = format!("unrelated_{index}");
3819 store
3820 .create_collection(&name)
3821 .expect("unrelated collection");
3822 }
3823 runtime
3824 .validate_model_operations_before_auth(&expr)
3825 .expect("warm contract cache");
3826 let (result, count) =
3827 measure_allocations(|| runtime.validate_model_operations_before_auth(&expr));
3828 result.expect("declared KV");
3829 assert_eq!(
3830 count.allocs, 0,
3831 "validation must only borrow the target contract"
3832 );
3833 }
3834 }
3835}
3836
3837#[cfg(test)]
3838mod multibyte_sql_tests {
3839 use super::*;
3840
3841 #[test]
3842 fn explain_analyze_prefix_ignores_multibyte_text_at_the_keyword_boundary() {
3843 for pad in 0..16 {
3846 for wide in ["é", "日", "😀"] {
3847 let sql = format!("{}{wide}llo", "a".repeat(pad));
3848 assert_eq!(strip_explain_analyze_prefix(&sql), None, "{sql:?}");
3849 }
3850 }
3851 assert_eq!(
3852 strip_explain_analyze_prefix("explain analyze DELETE FROM t"),
3853 Some("DELETE FROM t")
3854 );
3855 }
3856
3857 #[test]
3862 fn multibyte_sql_never_panics_execute_query() {
3863 let rt = RedDBRuntime::in_memory().expect("in-memory runtime");
3864 let seeds = [
3865 "",
3866 "SELECT ",
3867 "SELECT '",
3868 "EXPLAIN ",
3869 "EXPLAIN ANALYZE ",
3870 "CHECKPOINT ",
3871 "CHECKOUT ",
3872 "RESET ",
3873 "MERGE ",
3874 "CHERRY PICK ",
3875 "REVERT ",
3876 "RESOLVE CONFLICT ",
3877 "INSERT INTO t VALUES ('",
3878 ];
3879 let mut panicked = Vec::new();
3880 for seed in seeds {
3881 for pad in 0..16 {
3882 for wide in ["é", "日", "😀"] {
3883 let sql = format!("{seed}{}{wide}llo", "a".repeat(pad));
3884 let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
3885 let _ = rt.execute_query(&sql);
3886 }));
3887 if outcome.is_err() {
3888 panicked.push(sql);
3889 }
3890 }
3891 }
3892 }
3893 assert!(
3894 panicked.is_empty(),
3895 "execute_query panicked on {} statement(s), first: {:?}",
3896 panicked.len(),
3897 panicked.first()
3898 );
3899 }
3900}
3901
3902#[cfg(test)]
3903mod inline_graph_tvf_tests {
3904 use super::*;
3905
3906 fn scopes_for(sql: &str) -> HashSet<String> {
3907 let expr = reddb_rql::parser::parse(sql).expect("parse").query;
3908 query_expr_result_cache_scopes(&expr)
3909 }
3910
3911 #[test]
3912 fn inline_tvf_cache_scopes_include_source_collections() {
3913 let scopes = scopes_for(
3917 "SELECT * FROM components(nodes => (SELECT id FROM hosts), edges => (SELECT src, dst FROM links))",
3918 );
3919 assert!(scopes.contains("hosts"), "nodes source scoped: {scopes:?}");
3920 assert!(scopes.contains("links"), "edges source scoped: {scopes:?}");
3921 }
3922
3923 #[test]
3924 fn graph_collection_tvf_cache_scope_is_graph_argument() {
3925 let scopes = scopes_for("SELECT * FROM components(g)");
3929 assert!(scopes.contains("g"), "collection form scoped: {scopes:?}");
3930 }
3931
3932 #[test]
3933 fn abstract_degree_centrality_counts_undirected_endpoints() {
3934 let nodes = vec!["a".to_string(), "b".to_string(), "c".to_string()];
3935 let edges = vec![
3936 ("a".to_string(), "b".to_string(), 1.0_f32),
3937 ("b".to_string(), "c".to_string(), 1.0_f32),
3938 ];
3939 let degrees = abstract_degree_centrality(&nodes, &edges);
3940 assert_eq!(
3941 degrees,
3942 vec![
3943 ("a".to_string(), 1),
3944 ("b".to_string(), 2),
3945 ("c".to_string(), 1),
3946 ]
3947 );
3948 }
3949}
3950
3951fn reject_engine_internal_vault_key(key: &str) -> RedDBResult<()> {
3958 let lowered = key.to_ascii_lowercase();
3959 let internal = lowered == "red.secret.aes_key"
3960 || lowered.starts_with("red.iam.")
3961 || lowered.starts_with("red.acl.")
3962 || lowered.starts_with("red.vault.");
3963 if internal {
3964 return Err(RedDBError::Query(format!(
3965 "`{key}` is an engine-internal vault key and cannot be set or deleted through SET/DELETE SECRET"
3966 )));
3967 }
3968 Ok(())
3969}
3970
3971fn confine_copy_from_path(path: &str) -> RedDBResult<()> {
3977 let Ok(root) = std::env::var("REDDB_COPY_IMPORT_ROOT") else {
3978 return Ok(());
3979 };
3980 let root = std::fs::canonicalize(&root).map_err(|e| {
3981 RedDBError::Query(format!(
3982 "REDDB_COPY_IMPORT_ROOT `{root}` is not usable: {e}"
3983 ))
3984 })?;
3985 let resolved = std::fs::canonicalize(path)
3986 .map_err(|e| RedDBError::Query(format!("COPY FROM `{path}`: {e}")))?;
3987 if !resolved.starts_with(&root) || !resolved.is_file() {
3988 return Err(RedDBError::Query(format!(
3989 "COPY FROM `{path}` is outside the configured import root"
3990 )));
3991 }
3992 Ok(())
3993}
3994
3995#[cfg(test)]
3996mod result_cache_execution_tests {
3997 use super::*;
3998
3999 const QUERY: &str = "SELECT SUM(n) AS n FROM result_cache_probe";
4000
4001 fn runtime_with_rows(cache_enabled: bool) -> RedDBRuntime {
4002 let runtime = RedDBRuntime::in_memory().expect("runtime");
4003 runtime
4004 .execute_query("CREATE TABLE result_cache_probe (id INT, n INT)")
4005 .expect("create table");
4006 runtime
4007 .execute_query(
4008 "INSERT INTO result_cache_probe (id, n) VALUES (1, 10), (2, 20), (3, 30)",
4009 )
4010 .expect("seed rows");
4011 runtime
4012 .execute_query(&format!(
4013 "SET CONFIG runtime.result_cache.enabled = {cache_enabled}"
4014 ))
4015 .expect("configure result cache");
4016 runtime
4017 }
4018
4019 fn aggregate_value(runtime: &RedDBRuntime) -> i64 {
4020 let result = runtime.execute_query(QUERY).expect("aggregate query");
4021 match result.result.records[0].get("n") {
4022 Some(reddb_types::Value::Integer(value)) => *value,
4023 other => panic!("expected integer aggregate, got {other:?}"),
4024 }
4025 }
4026
4027 #[test]
4028 fn hot_hit_bypasses_query_execution_pipeline() {
4029 let runtime = runtime_with_rows(true);
4030 reset_result_cache_miss_executions();
4031 let before = runtime.result_cache_metrics();
4032
4033 assert_eq!(aggregate_value(&runtime), 60);
4034 assert_eq!(result_cache_miss_executions(), 1);
4035 assert_eq!(aggregate_value(&runtime), 60);
4036
4037 let after = runtime.result_cache_metrics();
4038 assert_eq!(
4039 result_cache_miss_executions(),
4040 1,
4041 "hit re-entered the executor"
4042 );
4043 assert_eq!(after.0 - before.0, 1, "second read must be a cache hit");
4044 assert_eq!(after.1 - before.1, 1, "first read must populate the cache");
4045 }
4046
4047 #[test]
4048 fn disabled_cache_executes_every_read() {
4049 let runtime = runtime_with_rows(false);
4050 reset_result_cache_miss_executions();
4051 let before = runtime.result_cache_metrics();
4052
4053 assert_eq!(aggregate_value(&runtime), 60);
4054 assert_eq!(aggregate_value(&runtime), 60);
4055
4056 assert_eq!(result_cache_miss_executions(), 2);
4057 assert_eq!(
4058 runtime.result_cache_metrics(),
4059 before,
4060 "disabled cache must not report lookups"
4061 );
4062 }
4063
4064 #[test]
4065 fn table_write_forces_one_reexecution_then_hits_again() {
4066 let runtime = runtime_with_rows(true);
4067 assert_eq!(aggregate_value(&runtime), 60);
4068 assert_eq!(aggregate_value(&runtime), 60);
4069 runtime
4070 .execute_query("INSERT INTO result_cache_probe (id, n) VALUES (4, 40)")
4071 .expect("mutate cached table");
4072 reset_result_cache_miss_executions();
4073 let before = runtime.result_cache_metrics();
4074
4075 assert_eq!(aggregate_value(&runtime), 100);
4076 assert_eq!(result_cache_miss_executions(), 1);
4077 assert_eq!(aggregate_value(&runtime), 100);
4078
4079 let after = runtime.result_cache_metrics();
4080 assert_eq!(result_cache_miss_executions(), 1);
4081 assert_eq!(after.0 - before.0, 1, "post-write result must become hot");
4082 assert_eq!(
4083 after.1 - before.1,
4084 1,
4085 "write must invalidate the old result"
4086 );
4087 }
4088}
4089
4090#[cfg(test)]
4091mod security_gate_tests {
4092 use super::*;
4093 use crate::auth::store::AuthStore;
4094 use crate::auth::{AuthConfig, Role};
4095
4096 struct IdentityGuard;
4099
4100 impl IdentityGuard {
4101 fn install(username: &str, role: Role) -> Self {
4102 set_current_auth_identity(username.to_string(), role);
4103 Self
4104 }
4105 }
4106
4107 impl Drop for IdentityGuard {
4108 fn drop(&mut self) {
4109 clear_current_auth_identity();
4110 }
4111 }
4112
4113 struct ScopedIdentityGuard;
4114
4115 impl ScopedIdentityGuard {
4116 fn install(tenant: &str, username: &str, role: Role) -> Self {
4117 crate::runtime::execution_context::set_current_auth_principal(
4118 crate::auth::UserId::scoped(tenant, username),
4119 role,
4120 );
4121 set_current_tenant(tenant.to_string());
4122 Self
4123 }
4124 }
4125
4126 impl Drop for ScopedIdentityGuard {
4127 fn drop(&mut self) {
4128 clear_current_auth_identity();
4129 clear_current_tenant();
4130 }
4131 }
4132
4133 fn runtime_with_user(username: &str, role: Role) -> RedDBRuntime {
4134 let store = std::sync::Arc::new(AuthStore::new(AuthConfig {
4135 enabled: true,
4136 session_ttl_secs: 60,
4137 require_auth: true,
4138 auto_encrypt_storage: false,
4139 vault_enabled: false,
4140 cert: Default::default(),
4141 oauth: Default::default(),
4142 }));
4143 store.create_user(username, "pw", role).expect("user");
4144 let runtime =
4145 RedDBRuntime::with_options(crate::api::RedDBOptions::in_memory()).expect("runtime");
4146 runtime.set_auth_store(store);
4147 runtime
4148 }
4149
4150 fn runtime_with_store(username: &str, role: Role) -> (RedDBRuntime, std::sync::Arc<AuthStore>) {
4151 let store = std::sync::Arc::new(AuthStore::new(AuthConfig {
4152 enabled: true,
4153 session_ttl_secs: 60,
4154 require_auth: true,
4155 auto_encrypt_storage: false,
4156 vault_enabled: false,
4157 cert: Default::default(),
4158 oauth: Default::default(),
4159 }));
4160 store.create_user(username, "pw", role).expect("user");
4161 let runtime =
4162 RedDBRuntime::with_options(crate::api::RedDBOptions::in_memory()).expect("runtime");
4163 runtime.set_auth_store(std::sync::Arc::clone(&store));
4164 (runtime, store)
4165 }
4166
4167 #[test]
4168 fn tenant_credential_cannot_override_credential_scope() {
4169 use crate::runtime::within_clause::{FieldOverride, ScopeOverride};
4170
4171 let store = std::sync::Arc::new(AuthStore::new(AuthConfig {
4172 enabled: true,
4173 require_auth: true,
4174 ..AuthConfig::default()
4175 }));
4176 store
4177 .create_user_in_tenant(Some("acme"), "admin", "pw", Role::Admin)
4178 .expect("tenant admin");
4179 let runtime =
4180 RedDBRuntime::with_options(crate::api::RedDBOptions::in_memory()).expect("runtime");
4181 runtime.set_auth_store(store);
4182 let _identity = ScopedIdentityGuard::install("acme", "admin", Role::Admin);
4183
4184 runtime
4185 .execute_query("WITHIN TENANT 'acme' SELECT 1")
4186 .expect("own tenant remains valid");
4187 for sql in [
4188 "WITHIN TENANT 'globex' SELECT 1",
4189 "WITHIN TENANT NULL SELECT 1",
4190 "WITHIN TENANT 'acme' USER 'other' SELECT 1",
4191 "WITHIN TENANT 'acme' AS ROLE 'read' SELECT 1",
4192 ] {
4193 assert!(runtime.execute_query(sql).is_err(), "must reject {sql}");
4194 }
4195 assert!(runtime
4196 .execute_query_with_scope(
4197 "SELECT 1",
4198 ScopeOverride {
4199 tenant: FieldOverride::Set("globex".to_string()),
4200 ..ScopeOverride::default()
4201 },
4202 )
4203 .is_err());
4204
4205 set_current_connection_id(44_201);
4206 runtime.execute_query("BEGIN").expect("begin");
4207 assert!(runtime.execute_query("SET LOCAL TENANT 'globex'").is_err());
4208 runtime.execute_query("ROLLBACK").expect("rollback");
4209 clear_current_connection_id();
4210 }
4211
4212 #[test]
4213 fn tenant_admin_sql_user_lifecycle_is_tenant_bounded() {
4214 let store = std::sync::Arc::new(AuthStore::new(AuthConfig {
4215 enabled: true,
4216 require_auth: true,
4217 ..AuthConfig::default()
4218 }));
4219 store
4220 .create_user_in_tenant(Some("acme"), "admin", "pw", Role::Admin)
4221 .expect("tenant admin");
4222 let runtime =
4223 RedDBRuntime::with_options(crate::api::RedDBOptions::in_memory()).expect("runtime");
4224 runtime.set_auth_store(std::sync::Arc::clone(&store));
4225 let _identity = ScopedIdentityGuard::install("acme", "admin", Role::Admin);
4226
4227 runtime
4228 .execute_query("CREATE USER acme.alice PASSWORD 'pw' ROLE read")
4229 .expect("same-tenant create");
4230 assert!(runtime
4231 .execute_query("CREATE USER globex.alice PASSWORD 'pw' ROLE read")
4232 .is_err());
4233 assert!(runtime
4234 .execute_query("ALTER USER globex.alice PASSWORD 'owned'")
4235 .is_err());
4236 let cross_tenant_allow = crate::auth::policies::Policy::from_json_str(
4237 r#"{
4238 "id":"tenant-cross-user",
4239 "version":1,
4240 "statements":[{
4241 "effect":"allow",
4242 "actions":["user:create","user:password:change"],
4243 "resources":["user:tenant/globex/*"]
4244 }]
4245 }"#,
4246 )
4247 .expect("cross-tenant policy");
4248 store.put_policy(cross_tenant_allow).expect("put policy");
4249 store
4250 .attach_policy(
4251 crate::auth::store::PrincipalRef::User(crate::auth::UserId::scoped(
4252 "acme", "admin",
4253 )),
4254 "tenant-cross-user",
4255 )
4256 .expect("attach policy");
4257 assert!(runtime
4258 .execute_query("CREATE USER globex.bob PASSWORD 'pw' ROLE read")
4259 .is_err());
4260 assert!(store.get_user(Some("globex"), "alice").is_none());
4261 assert!(store.get_user(Some("globex"), "bob").is_none());
4262 }
4263
4264 #[test]
4265 fn slow_log_redacts_user_password_statements() {
4266 for sql in [
4267 "CREATE USER alice PASSWORD 'super-secret' ROLE admin",
4268 "CREATE USER acme.alice PASSWORD 'super-secret' ROLE read",
4269 "ALTER USER alice PASSWORD 'super-secret'",
4270 "AlTeR UsEr acme.alice PaSsWoRd 'super-secret'",
4271 "EXPLAIN CREATE USER alice PASSWORD 'super-secret' ROLE read",
4272 "EXPLAIN ALTER USER alice PASSWORD 'super-secret'",
4273 "ALTER USER alice PASSWORD 'unterminated",
4274 ] {
4275 let redacted = redact_sql_for_slow_log(sql);
4276 assert!(!redacted.contains("super-secret"), "leaked from {sql}");
4277 assert!(!redacted.contains("unterminated"), "leaked from {sql}");
4278 }
4279 assert_eq!(
4280 redact_sql_for_slow_log("SELECT password FROM accounts"),
4281 "SELECT password FROM accounts"
4282 );
4283 }
4284
4285 #[test]
4286 fn nested_reads_need_select_on_the_inner_table() {
4287 use crate::auth::privileges::{Action, GrantPrincipal, Resource};
4288 use crate::auth::UserId;
4289 let (runtime, store) = runtime_with_store("ro", Role::Read);
4290 runtime
4291 .execute_query("CREATE TABLE gate_open (a INTEGER)")
4292 .expect("create");
4293 runtime
4294 .execute_query("CREATE TABLE gate_closed (b INTEGER)")
4295 .expect("create");
4296 store
4299 .grant(
4300 &UserId::from_parts(None, "admin"),
4301 Role::Admin,
4302 GrantPrincipal::User(UserId::from_parts(None, "ro")),
4303 Resource::table_from_name("gate_open"),
4304 vec![Action::Select],
4305 false,
4306 None,
4307 )
4308 .expect("grant");
4309 let _identity = IdentityGuard::install("ro", Role::Read);
4310 runtime
4311 .execute_query("SELECT * FROM (SELECT a FROM gate_open) s")
4312 .expect("derived table over a granted table is fine");
4313 for sql in [
4316 "SELECT * FROM (SELECT b FROM gate_closed) s",
4317 "SELECT a FROM gate_open WHERE a IN (SELECT b FROM gate_closed)",
4318 ] {
4319 let err = runtime
4320 .execute_query(sql)
4321 .expect_err("nested read of an ungranted table must be refused");
4322 assert!(
4323 err.to_string().contains("permission denied"),
4324 "{sql}: unexpected error: {err}"
4325 );
4326 }
4327 }
4328
4329 #[test]
4330 fn policy_listings_are_self_or_policy_management() {
4331 let (runtime, store) = runtime_with_store("ro", Role::Read);
4332 store.create_user("other", "pw", Role::Read).expect("user");
4333 let _identity = IdentityGuard::install("ro", Role::Read);
4334 runtime
4335 .execute_query("SHOW EFFECTIVE PERMISSIONS FOR ro")
4336 .expect("a principal may inspect its own permissions");
4337 let err = runtime
4338 .execute_query("SHOW EFFECTIVE PERMISSIONS FOR other")
4339 .expect_err("inspecting another principal is policy management");
4340 assert!(
4341 err.to_string().contains("permission denied"),
4342 "unexpected error: {err}"
4343 );
4344 }
4345
4346 #[test]
4347 fn secret_columns_stay_masked_for_readers_without_secret_read() {
4348 use reddb_types::Value;
4349 let (runtime, store) = runtime_with_store("ro", Role::Read);
4350 store.create_user("root", "pw", Role::Admin).expect("admin");
4351 store.ensure_vault_secret_key();
4352 runtime
4353 .execute_query(
4354 "INSERT INTO creds_gate (name, token) VALUES ('stripe', SECRET('sk_live_abc'))",
4355 )
4356 .expect("insert");
4357
4358 let token_for = |username: &str, role: Role| {
4359 let _identity = IdentityGuard::install(username, role);
4360 let result = runtime
4361 .execute_query("SELECT name, token FROM creds_gate")
4362 .expect("select");
4363 result.result.records[0]
4364 .get("token")
4365 .cloned()
4366 .expect("token column")
4367 };
4368 assert!(
4371 matches!(token_for("ro", Role::Read), Value::Secret(_)),
4372 "a read-role principal must see the column masked"
4373 );
4374 assert!(matches!(token_for("root", Role::Admin), Value::Secret(_)));
4375 }
4376
4377 #[test]
4378 fn store_wide_operations_need_the_admin_tier() {
4379 let runtime = runtime_with_user("bob", Role::Write);
4380 let _identity = IdentityGuard::install("bob", Role::Write);
4381 for sql in ["VACUUM", "SCRUB"] {
4384 let err = runtime
4385 .execute_query(sql)
4386 .expect_err("store-wide operation must be refused for a non-admin");
4387 assert!(
4388 err.to_string().contains("permission denied"),
4389 "{sql}: unexpected error: {err}"
4390 );
4391 }
4392 }
4393
4394 #[test]
4395 fn explain_needs_the_privilege_of_the_statement_it_plans() {
4396 let runtime = runtime_with_user("ro", Role::Read);
4397 let _identity = IdentityGuard::install("ro", Role::Read);
4398 runtime
4399 .execute_query("EXPLAIN SELECT 1 AS one")
4400 .expect("planning a read is a read");
4401 let err = runtime
4404 .execute_query("EXPLAIN INSERT INTO gate_t (a) VALUES (1)")
4405 .expect_err("planning a write needs the write privilege");
4406 assert!(
4407 err.to_string().contains("permission denied"),
4408 "unexpected EXPLAIN error: {err}"
4409 );
4410 }
4411
4412 #[test]
4413 fn copy_from_a_server_path_is_admin_only() {
4414 let runtime = runtime_with_user("bob", Role::Write);
4415 let _identity = IdentityGuard::install("bob", Role::Write);
4416 let err = runtime
4420 .execute_query("COPY leak FROM '/etc/hostname'")
4421 .expect_err("COPY FROM must be refused for a non-admin");
4422 assert!(
4423 err.to_string().contains("permission denied"),
4424 "unexpected COPY error: {err}"
4425 );
4426 }
4427
4428 #[test]
4429 fn vcs_history_rewrites_are_admin_only() {
4430 let runtime = runtime_with_user("bob", Role::Write);
4431 let _identity = IdentityGuard::install("bob", Role::Write);
4432 let err = runtime
4435 .execute_query("RESET HARD TO 'deadbeef'")
4436 .expect_err("RESET must be refused for a non-admin");
4437 assert!(
4438 err.to_string().contains("permission denied"),
4439 "unexpected RESET error: {err}"
4440 );
4441 }
4442
4443 #[test]
4444 fn ai_provider_config_writes_are_admin_only() {
4445 let runtime = runtime_with_user("bob", Role::Write);
4446 let _identity = IdentityGuard::install("bob", Role::Write);
4447 let err = runtime
4450 .execute_query(
4451 "SET CONFIG ai.providers.openai.tokens.default.secret_ref = 'red.secret.aes_key'",
4452 )
4453 .expect_err("AI provider config must be refused for a non-admin");
4454 assert!(
4455 err.to_string().contains("permission denied"),
4456 "unexpected SET CONFIG error: {err}"
4457 );
4458 }
4459
4460 #[test]
4461 fn engine_internal_vault_keys_are_not_writable_through_set_secret() {
4462 let runtime = runtime_with_user("root", Role::Admin);
4463 let _identity = IdentityGuard::install("root", Role::Admin);
4464 for key in [
4468 "red.iam.policies",
4469 "red.iam.attachments.users",
4470 "red.secret.aes_key",
4471 ] {
4472 let err = runtime
4473 .execute_query(&format!("SET SECRET {key} = 'x'"))
4474 .expect_err("engine-internal vault key must be refused");
4475 assert!(
4476 err.to_string().contains("engine-internal"),
4477 "unexpected error for {key}: {err}"
4478 );
4479 }
4480 }
4481}