Skip to main content

reddb_server/
runtime.rs

1//! Embedded runtime with connection pooling, scans and health.
2
3use std::cmp::Ordering;
4use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, VecDeque};
5use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering};
6use std::sync::{Arc, Mutex};
7use std::time::{SystemTime, UNIX_EPOCH};
8
9use crate::api::{RedDBError, RedDBOptions, RedDBResult};
10use crate::catalog::{
11    CatalogAnalyticsJobStatus, CatalogAttentionSummary, CatalogGraphProjectionStatus,
12    CatalogIndexStatus, CatalogModelSnapshot, CollectionDescriptor,
13};
14use crate::health::{HealthProvider, HealthReport};
15use crate::index::IndexCatalog;
16use crate::physical::{
17    ExportDescriptor, ManifestEvent, PhysicalAnalyticsJob, PhysicalGraphProjection, PhysicalLayout,
18    SnapshotDescriptor,
19};
20use crate::serde_json::Value as JsonValue;
21use crate::storage::engine::pathfinding::{AStar, BellmanFord, Dijkstra, BFS, DFS};
22use crate::storage::engine::{
23    BetweennessCentrality, ClosenessCentrality, ClusteringCoefficient, ConnectedComponents,
24    CycleDetector, DegreeCentrality, EigenvectorCentrality, GraphStore, IvfConfig, IvfIndex,
25    IvfStats, LabelPropagation, Louvain, MetadataEntry, MetadataFilter as VectorMetadataFilter,
26    MetadataValue as VectorMetadataValue, PageRank, PersonalizedPageRank, PhysicalFileHeader,
27    StoredNode, StronglyConnectedComponents, WeaklyConnectedComponents, HITS,
28};
29use crate::storage::query::is_universal_entity_source as is_universal_query_source;
30use crate::storage::query::planner::{
31    CanonicalLogicalPlan, CanonicalPlanner, CostEstimator, QueryPlanner,
32};
33use crate::storage::query::unified::{UnifiedRecord, UnifiedResult};
34use crate::storage::unified::dsl::{
35    apply_filters, cosine_similarity, Filter as DslFilter, FilterOp as DslFilterOp,
36    FilterValue as DslFilterValue, GraphPatternDsl, HybridQueryBuilder, MatchComponents,
37    QueryResult as DslQueryResult, ScoredMatch, TextSearchBuilder,
38};
39use crate::storage::unified::store::{
40    NativeCatalogSummary, NativeManifestSummary, NativePhysicalState, NativeRecoverySummary,
41    NativeRegistrySummary,
42};
43use crate::storage::unified::{
44    Metadata, MetadataValue as UnifiedMetadataValue, RefTarget, UnifiedMetadataFilter,
45};
46use crate::storage::{
47    EntityData, EntityId, EntityKind, RedDB, RefType, SimilarResult, StoreStats, UnifiedEntity,
48    UnifiedStore,
49};
50use reddb_rql::ast::{
51    AlterOperation, AlterQueueQuery, AlterTableQuery, CompareOp, CreateCollectionQuery,
52    CreateIndexQuery, CreateQueueQuery, CreateTableQuery, CreateTimeSeriesQuery, CreateTreeQuery,
53    CreateVectorQuery, DeleteQuery, DropCollectionQuery, DropDocumentQuery, DropForkQuery,
54    DropGraphQuery, DropIndexQuery, DropKvQuery, DropQueueQuery, DropTableQuery,
55    DropTimeSeriesQuery, DropTreeQuery, DropVectorQuery, EventsBackfillQuery, ExplainAlterQuery,
56    ExplainFormat, FieldRef, Filter, ForkStoreQuery, FusionStrategy, GraphCommand, HybridQuery,
57    IndexMethod, InsertEntityType, InsertQuery, JoinQuery, JoinType, OrderByClause,
58    ProbabilisticCommand, Projection, PromoteForkQuery, QueryExpr, QueueCommand, QueueSelectQuery,
59    QueueSide, SearchCommand, TableQuery, TreeCommand, TruncateQuery, UpdateQuery, VectorQuery,
60    VectorSource,
61};
62use reddb_rql::modes::{detect_mode, parse_multi, QueryMode};
63use reddb_types::Value;
64
65static TIMESERIES_TAG_INDEX_OBSERVED_POINTS: AtomicU64 = AtomicU64::new(0);
66
67pub fn reset_timeseries_tag_index_observability() {
68    TIMESERIES_TAG_INDEX_OBSERVED_POINTS.store(0, AtomicOrdering::Relaxed);
69}
70
71pub fn timeseries_tag_index_observed_points() -> u64 {
72    TIMESERIES_TAG_INDEX_OBSERVED_POINTS.load(AtomicOrdering::Relaxed)
73}
74
75pub(crate) fn observe_timeseries_tag_index_point() {
76    TIMESERIES_TAG_INDEX_OBSERVED_POINTS.fetch_add(1, AtomicOrdering::Relaxed);
77}
78
79#[derive(Debug, Clone)]
80pub struct ConnectionPoolConfig {
81    pub max_connections: usize,
82    pub max_idle: usize,
83}
84
85impl Default for ConnectionPoolConfig {
86    fn default() -> Self {
87        Self {
88            max_connections: 64,
89            max_idle: 16,
90        }
91    }
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub struct ScanCursor {
96    pub offset: usize,
97}
98
99#[derive(Debug, Clone)]
100pub struct ScanPage {
101    pub collection: String,
102    pub items: Vec<UnifiedEntity>,
103    pub next: Option<ScanCursor>,
104    pub total: usize,
105}
106
107#[derive(Debug, Clone)]
108pub struct SystemInfo {
109    pub pid: u32,
110    pub cpu_cores: usize,
111    pub total_memory_bytes: u64,
112    pub available_memory_bytes: u64,
113    pub os: String,
114    pub arch: String,
115    pub hostname: String,
116}
117
118impl SystemInfo {
119    /// Whether the system has enough cores to benefit from parallelism.
120    /// Returns false on single-core machines where thread overhead > gains.
121    pub fn should_parallelize() -> bool {
122        reddb_engine::execution::should_parallelize()
123    }
124
125    pub fn collect() -> Self {
126        Self {
127            pid: std::process::id(),
128            cpu_cores: std::thread::available_parallelism()
129                .map(|p| p.get())
130                .unwrap_or(1),
131            total_memory_bytes: Self::read_total_memory(),
132            available_memory_bytes: Self::read_available_memory(),
133            os: std::env::consts::OS.to_string(),
134            arch: std::env::consts::ARCH.to_string(),
135            hostname: std::env::var("HOSTNAME")
136                .or_else(|_| std::env::var("COMPUTERNAME"))
137                .unwrap_or_else(|_| "unknown".to_string()),
138        }
139    }
140
141    #[cfg(target_os = "linux")]
142    fn read_total_memory() -> u64 {
143        std::fs::read_to_string("/proc/meminfo")
144            .ok()
145            .and_then(|s| {
146                s.lines()
147                    .find(|l| l.starts_with("MemTotal:"))
148                    .and_then(|l| {
149                        l.split_whitespace()
150                            .nth(1)
151                            .and_then(|v| v.parse::<u64>().ok())
152                    })
153                    .map(|kb| kb * 1024)
154            })
155            .unwrap_or(0)
156    }
157
158    #[cfg(target_os = "linux")]
159    fn read_available_memory() -> u64 {
160        std::fs::read_to_string("/proc/meminfo")
161            .ok()
162            .and_then(|s| {
163                s.lines()
164                    .find(|l| l.starts_with("MemAvailable:"))
165                    .and_then(|l| {
166                        l.split_whitespace()
167                            .nth(1)
168                            .and_then(|v| v.parse::<u64>().ok())
169                    })
170                    .map(|kb| kb * 1024)
171            })
172            .unwrap_or(0)
173    }
174
175    #[cfg(not(target_os = "linux"))]
176    fn read_total_memory() -> u64 {
177        0
178    }
179
180    #[cfg(not(target_os = "linux"))]
181    fn read_available_memory() -> u64 {
182        0
183    }
184}
185
186#[derive(Debug, Clone)]
187pub struct RuntimeStats {
188    pub active_connections: usize,
189    pub idle_connections: usize,
190    pub total_checkouts: u64,
191    pub paged_mode: bool,
192    pub started_at_unix_ms: u128,
193    pub store: StoreStats,
194    pub system: SystemInfo,
195    pub result_blob_cache: crate::storage::cache::BlobCacheStats,
196    pub kv: KvStats,
197    pub metrics_ingest: MetricsIngestStats,
198}
199
200#[derive(Debug, Clone, PartialEq, Eq)]
201pub struct MetricsTenantActivityStats {
202    pub tenant: String,
203    pub namespace: String,
204    pub operation: String,
205    pub count: u64,
206}
207
208#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
209pub struct MetricsIngestStats {
210    pub samples_accepted: u64,
211    pub series_accepted: u64,
212    pub samples_rejected: u64,
213    pub series_rejected: u64,
214    pub series_rejected_cardinality_budget: u64,
215}
216
217#[derive(Debug, Default)]
218pub(crate) struct MetricsIngestCounters {
219    samples_accepted: AtomicU64,
220    series_accepted: AtomicU64,
221    samples_rejected: AtomicU64,
222    series_rejected: AtomicU64,
223    series_rejected_cardinality_budget: AtomicU64,
224}
225
226impl MetricsIngestCounters {
227    pub(crate) fn record(
228        &self,
229        accepted_samples: u64,
230        accepted_series: u64,
231        rejected_samples: u64,
232        rejected_series: u64,
233    ) {
234        self.samples_accepted
235            .fetch_add(accepted_samples, AtomicOrdering::Relaxed);
236        self.series_accepted
237            .fetch_add(accepted_series, AtomicOrdering::Relaxed);
238        self.samples_rejected
239            .fetch_add(rejected_samples, AtomicOrdering::Relaxed);
240        self.series_rejected
241            .fetch_add(rejected_series, AtomicOrdering::Relaxed);
242    }
243
244    pub(crate) fn record_cardinality_budget_rejections(&self, rejected_series: u64) {
245        self.series_rejected_cardinality_budget
246            .fetch_add(rejected_series, AtomicOrdering::Relaxed);
247    }
248
249    pub(crate) fn snapshot(&self) -> MetricsIngestStats {
250        MetricsIngestStats {
251            samples_accepted: self.samples_accepted.load(AtomicOrdering::Relaxed),
252            series_accepted: self.series_accepted.load(AtomicOrdering::Relaxed),
253            samples_rejected: self.samples_rejected.load(AtomicOrdering::Relaxed),
254            series_rejected: self.series_rejected.load(AtomicOrdering::Relaxed),
255            series_rejected_cardinality_budget: self
256                .series_rejected_cardinality_budget
257                .load(AtomicOrdering::Relaxed),
258        }
259    }
260}
261
262#[derive(Debug, Default)]
263pub(crate) struct MetricsTenantActivityCounters {
264    inner: Mutex<BTreeMap<(String, String, String), u64>>,
265}
266
267impl MetricsTenantActivityCounters {
268    pub(crate) fn record(&self, tenant: &str, namespace: &str, operation: &str) {
269        let mut inner = self
270            .inner
271            .lock()
272            .unwrap_or_else(|poison| poison.into_inner());
273        let key = (
274            tenant.to_string(),
275            namespace.to_string(),
276            operation.to_string(),
277        );
278        *inner.entry(key).or_insert(0) += 1;
279    }
280
281    pub(crate) fn snapshot(&self) -> Vec<MetricsTenantActivityStats> {
282        let inner = self
283            .inner
284            .lock()
285            .unwrap_or_else(|poison| poison.into_inner());
286        inner
287            .iter()
288            .map(
289                |((tenant, namespace, operation), count)| MetricsTenantActivityStats {
290                    tenant: tenant.clone(),
291                    namespace: namespace.clone(),
292                    operation: operation.clone(),
293                    count: *count,
294                },
295            )
296            .collect()
297    }
298}
299
300#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
301pub struct KvStats {
302    pub puts: u64,
303    pub gets: u64,
304    pub deletes: u64,
305    pub incrs: u64,
306    pub cas_success: u64,
307    pub cas_conflict: u64,
308    pub watch_streams_active: u64,
309    pub watch_events_emitted: u64,
310    pub watch_drops: u64,
311}
312
313#[derive(Debug, Default)]
314pub(crate) struct KvStatsCounters {
315    puts: AtomicU64,
316    gets: AtomicU64,
317    deletes: AtomicU64,
318    incrs: AtomicU64,
319    cas_success: AtomicU64,
320    cas_conflict: AtomicU64,
321    watch_streams_active: AtomicU64,
322    watch_events_emitted: AtomicU64,
323    watch_drops: AtomicU64,
324}
325
326#[derive(Debug, Default)]
327pub(crate) struct KvTagIndex {
328    tag_to_entries: parking_lot::RwLock<HashMap<(String, String), HashMap<String, EntityId>>>,
329    key_to_tags: parking_lot::RwLock<HashMap<(String, String), BTreeSet<String>>>,
330}
331
332impl KvTagIndex {
333    pub(crate) fn replace(&self, collection: &str, key: &str, id: EntityId, tags: &[String]) {
334        let entry_key = (collection.to_string(), key.to_string());
335        let new_tags: BTreeSet<String> = tags
336            .iter()
337            .map(|tag| tag.trim())
338            .filter(|tag| !tag.is_empty())
339            .map(ToOwned::to_owned)
340            .collect();
341
342        let old_tags = {
343            let mut key_to_tags = self.key_to_tags.write();
344            if new_tags.is_empty() {
345                key_to_tags.remove(&entry_key)
346            } else {
347                key_to_tags.insert(entry_key.clone(), new_tags.clone())
348            }
349        };
350
351        let mut tag_to_entries = self.tag_to_entries.write();
352        if let Some(old_tags) = old_tags {
353            for tag in old_tags {
354                let scoped = (collection.to_string(), tag);
355                let remove_scoped = if let Some(entries) = tag_to_entries.get_mut(&scoped) {
356                    entries.remove(key);
357                    entries.is_empty()
358                } else {
359                    false
360                };
361                if remove_scoped {
362                    tag_to_entries.remove(&scoped);
363                }
364            }
365        }
366
367        for tag in new_tags {
368            tag_to_entries
369                .entry((collection.to_string(), tag))
370                .or_default()
371                .insert(key.to_string(), id);
372        }
373    }
374
375    pub(crate) fn remove(&self, collection: &str, key: &str) {
376        let entry_key = (collection.to_string(), key.to_string());
377        let old_tags = self.key_to_tags.write().remove(&entry_key);
378        let Some(old_tags) = old_tags else {
379            return;
380        };
381
382        let mut tag_to_entries = self.tag_to_entries.write();
383        for tag in old_tags {
384            let scoped = (collection.to_string(), tag);
385            let remove_scoped = if let Some(entries) = tag_to_entries.get_mut(&scoped) {
386                entries.remove(key);
387                entries.is_empty()
388            } else {
389                false
390            };
391            if remove_scoped {
392                tag_to_entries.remove(&scoped);
393            }
394        }
395    }
396
397    pub(crate) fn entries_for_tags(
398        &self,
399        collection: &str,
400        tags: &[String],
401    ) -> Vec<(String, EntityId)> {
402        if tags.is_empty() {
403            return Vec::new();
404        }
405
406        let tag_to_entries = self.tag_to_entries.read();
407        let mut out: HashMap<String, EntityId> = HashMap::new();
408        for tag in tags {
409            let scoped = (collection.to_string(), tag.trim().to_string());
410            if let Some(entries) = tag_to_entries.get(&scoped) {
411                for (key, id) in entries {
412                    out.entry(key.clone()).or_insert(*id);
413                }
414            }
415        }
416        out.into_iter().collect()
417    }
418
419    pub(crate) fn tags_for_key(&self, collection: &str, key: &str) -> Vec<String> {
420        self.key_to_tags
421            .read()
422            .get(&(collection.to_string(), key.to_string()))
423            .map(|tags| tags.iter().cloned().collect())
424            .unwrap_or_default()
425    }
426}
427
428impl KvStatsCounters {
429    pub(crate) fn snapshot(&self) -> KvStats {
430        KvStats {
431            puts: self.puts.load(AtomicOrdering::Relaxed),
432            gets: self.gets.load(AtomicOrdering::Relaxed),
433            deletes: self.deletes.load(AtomicOrdering::Relaxed),
434            incrs: self.incrs.load(AtomicOrdering::Relaxed),
435            cas_success: self.cas_success.load(AtomicOrdering::Relaxed),
436            cas_conflict: self.cas_conflict.load(AtomicOrdering::Relaxed),
437            watch_streams_active: self.watch_streams_active.load(AtomicOrdering::Relaxed),
438            watch_events_emitted: self.watch_events_emitted.load(AtomicOrdering::Relaxed),
439            watch_drops: self.watch_drops.load(AtomicOrdering::Relaxed),
440        }
441    }
442
443    pub(crate) fn incr_puts(&self) {
444        self.puts.fetch_add(1, AtomicOrdering::Relaxed);
445    }
446
447    pub(crate) fn incr_gets(&self) {
448        self.gets.fetch_add(1, AtomicOrdering::Relaxed);
449    }
450
451    pub(crate) fn incr_deletes(&self) {
452        self.deletes.fetch_add(1, AtomicOrdering::Relaxed);
453    }
454
455    pub(crate) fn incr_incrs(&self) {
456        self.incrs.fetch_add(1, AtomicOrdering::Relaxed);
457    }
458
459    pub(crate) fn incr_cas_success(&self) {
460        self.cas_success.fetch_add(1, AtomicOrdering::Relaxed);
461    }
462
463    pub(crate) fn incr_cas_conflict(&self) {
464        self.cas_conflict.fetch_add(1, AtomicOrdering::Relaxed);
465    }
466
467    pub(crate) fn incr_watch_streams_active(&self) {
468        self.watch_streams_active
469            .fetch_add(1, AtomicOrdering::Relaxed);
470    }
471
472    pub(crate) fn decr_watch_streams_active(&self) {
473        self.watch_streams_active
474            .fetch_sub(1, AtomicOrdering::Relaxed);
475    }
476
477    pub(crate) fn incr_watch_events_emitted(&self) {
478        self.watch_events_emitted
479            .fetch_add(1, AtomicOrdering::Relaxed);
480    }
481
482    pub(crate) fn add_watch_drops(&self, count: u64) {
483        self.watch_drops.fetch_add(count, AtomicOrdering::Relaxed);
484    }
485}
486
487#[derive(Debug, Clone)]
488pub struct RuntimeQueryResult {
489    pub query: String,
490    pub mode: QueryMode,
491    pub statement: &'static str,
492    pub engine: &'static str,
493    pub result: UnifiedResult,
494    pub affected_rows: u64,
495    /// High-level statement type: "select", "insert", "update", "delete", "create", "drop", "alter"
496    pub statement_type: &'static str,
497    pub bookmark: Option<String>,
498    pub notice: Option<String>,
499}
500
501impl RuntimeQueryResult {
502    /// Pure materialized results can receive late read-budget admission.
503    /// Command acknowledgements must preserve already committed side effects.
504    pub(crate) fn is_materialized_read(&self) -> bool {
505        self.statement_type == "select"
506            && matches!(
507                self.statement,
508                "table" | "graph" | "join" | "path" | "vector" | "hybrid" | "select"
509            )
510    }
511
512    /// Construct a result representing a DML operation (insert/update/delete).
513    pub fn dml_result(
514        query: String,
515        affected: u64,
516        statement_type: &'static str,
517        engine: &'static str,
518    ) -> Self {
519        Self {
520            query,
521            mode: QueryMode::Sql,
522            statement: statement_type,
523            engine,
524            result: UnifiedResult::empty(),
525            affected_rows: affected,
526            statement_type,
527            bookmark: None,
528            notice: None,
529        }
530    }
531
532    /// Construct a result representing a DDL message (create/drop/alter).
533    pub fn ok_message(query: String, message: &str, statement_type: &'static str) -> Self {
534        let mut result = UnifiedResult::empty();
535        let mut record = UnifiedRecord::new();
536        record.set("message", Value::text(message.to_string()));
537        result.push(record);
538        result.columns = vec!["message".to_string()];
539
540        Self {
541            query,
542            mode: QueryMode::Sql,
543            statement: statement_type,
544            engine: "runtime-ddl",
545            result,
546            affected_rows: 0,
547            statement_type,
548            bookmark: None,
549            notice: None,
550        }
551    }
552
553    /// Construct a multi-column record result for read-only meta commands
554    /// (EXPLAIN ALTER, schema introspection, etc.). Each row is a Vec of
555    /// (column_name, value) pairs in column order.
556    pub fn ok_records(
557        query: String,
558        columns: Vec<String>,
559        rows: Vec<Vec<(String, Value)>>,
560        statement_type: &'static str,
561    ) -> Self {
562        let mut result = UnifiedResult::empty();
563        for row in rows {
564            let mut record = UnifiedRecord::new();
565            for (k, v) in row {
566                record.set(&k, v);
567            }
568            result.push(record);
569        }
570        result.columns = columns;
571
572        Self {
573            query,
574            mode: QueryMode::Sql,
575            statement: statement_type,
576            engine: "runtime-meta",
577            result,
578            affected_rows: 0,
579            statement_type,
580            bookmark: None,
581            notice: None,
582        }
583    }
584
585    pub fn bookmark_token(&self) -> Option<&str> {
586        self.bookmark.as_deref()
587    }
588}
589
590#[derive(Debug, Clone)]
591pub struct RuntimeQueryExplain {
592    pub query: String,
593    pub mode: QueryMode,
594    pub statement: &'static str,
595    pub is_universal: bool,
596    pub plan_cost: crate::storage::query::planner::PlanCost,
597    pub estimated_rows: f64,
598    pub estimated_selectivity: f64,
599    pub estimated_confidence: f64,
600    pub passes_applied: Vec<String>,
601    pub logical_plan: CanonicalLogicalPlan,
602    /// Names of any CTEs declared by a leading `WITH` clause. Empty
603    /// for non-CTE queries. The plan tree is built against the
604    /// post-inlining body, so each CTE's body is reachable inside
605    /// `logical_plan` as a regular `Subquery` (or, for bare refs, the
606    /// inlined Table node verbatim). This list lets renderers prepend
607    /// `CteScan` markers so operators see which CTEs were resolved.
608    pub cte_materializations: Vec<String>,
609}
610
611#[derive(Debug, Clone)]
612pub struct RuntimeIvfMatch {
613    pub entity_id: u64,
614    pub distance: f32,
615    pub entity: Option<UnifiedEntity>,
616}
617
618#[derive(Debug, Clone)]
619pub struct RuntimeIvfSearchResult {
620    pub collection: String,
621    pub k: usize,
622    pub n_lists: usize,
623    pub n_probes: usize,
624    pub stats: IvfStats,
625    pub matches: Vec<RuntimeIvfMatch>,
626}
627
628#[derive(Debug, Clone, Copy, PartialEq, Eq)]
629pub enum RuntimeGraphDirection {
630    Outgoing,
631    Incoming,
632    Both,
633}
634
635#[derive(Debug, Clone, Copy, PartialEq, Eq)]
636pub enum RuntimeGraphTraversalStrategy {
637    Bfs,
638    Dfs,
639}
640
641#[derive(Debug, Clone, Copy, PartialEq, Eq)]
642pub enum RuntimeGraphPathAlgorithm {
643    Bfs,
644    Dijkstra,
645    AStar,
646    BellmanFord,
647}
648
649#[derive(Debug, Clone)]
650pub struct RuntimeGraphNode {
651    pub id: String,
652    pub label: String,
653    pub node_type: String,
654    pub out_edge_count: u32,
655    pub in_edge_count: u32,
656}
657
658#[derive(Debug, Clone)]
659pub struct RuntimeGraphEdge {
660    pub source: String,
661    pub target: String,
662    pub edge_type: String,
663    pub weight: f32,
664}
665
666#[derive(Debug, Clone)]
667pub struct RuntimeGraphVisit {
668    pub depth: usize,
669    pub node: RuntimeGraphNode,
670}
671
672#[derive(Debug, Clone)]
673pub struct RuntimeGraphNeighborhoodResult {
674    pub source: String,
675    pub direction: RuntimeGraphDirection,
676    pub max_depth: usize,
677    pub nodes: Vec<RuntimeGraphVisit>,
678    pub edges: Vec<RuntimeGraphEdge>,
679}
680
681#[derive(Debug, Clone)]
682pub struct RuntimeGraphTraversalResult {
683    pub source: String,
684    pub direction: RuntimeGraphDirection,
685    pub strategy: RuntimeGraphTraversalStrategy,
686    pub max_depth: usize,
687    pub visits: Vec<RuntimeGraphVisit>,
688    pub edges: Vec<RuntimeGraphEdge>,
689}
690
691#[derive(Debug, Clone)]
692pub struct RuntimeGraphPath {
693    pub hop_count: usize,
694    pub total_weight: f64,
695    pub nodes: Vec<RuntimeGraphNode>,
696    pub edges: Vec<RuntimeGraphEdge>,
697}
698
699#[derive(Debug, Clone)]
700pub struct RuntimeGraphPathResult {
701    pub source: String,
702    pub target: String,
703    pub direction: RuntimeGraphDirection,
704    pub algorithm: RuntimeGraphPathAlgorithm,
705    pub nodes_visited: usize,
706    pub negative_cycle_detected: Option<bool>,
707    pub path: Option<RuntimeGraphPath>,
708}
709
710#[derive(Debug, Clone, Copy, PartialEq, Eq)]
711pub enum RuntimeGraphComponentsMode {
712    Connected,
713    Weak,
714    Strong,
715}
716
717#[derive(Debug, Clone, Copy, PartialEq, Eq)]
718pub enum RuntimeGraphCentralityAlgorithm {
719    Degree,
720    Closeness,
721    Betweenness,
722    Eigenvector,
723    PageRank,
724}
725
726#[derive(Debug, Clone, Copy, PartialEq, Eq)]
727pub enum RuntimeGraphCommunityAlgorithm {
728    LabelPropagation,
729    Louvain,
730}
731
732#[derive(Debug, Clone)]
733pub struct RuntimeGraphComponent {
734    pub id: String,
735    pub size: usize,
736    pub nodes: Vec<String>,
737}
738
739#[derive(Debug, Clone)]
740pub struct RuntimeGraphComponentsResult {
741    pub mode: RuntimeGraphComponentsMode,
742    pub count: usize,
743    pub components: Vec<RuntimeGraphComponent>,
744}
745
746#[derive(Debug, Clone)]
747pub struct RuntimeGraphCentralityScore {
748    pub node: RuntimeGraphNode,
749    pub score: f64,
750}
751
752#[derive(Debug, Clone)]
753pub struct RuntimeGraphDegreeScore {
754    pub node: RuntimeGraphNode,
755    pub in_degree: usize,
756    pub out_degree: usize,
757    pub total_degree: usize,
758}
759
760#[derive(Debug, Clone)]
761pub struct RuntimeGraphCentralityResult {
762    pub algorithm: RuntimeGraphCentralityAlgorithm,
763    pub normalized: Option<bool>,
764    pub iterations: Option<usize>,
765    pub converged: Option<bool>,
766    pub scores: Vec<RuntimeGraphCentralityScore>,
767    pub degree_scores: Vec<RuntimeGraphDegreeScore>,
768}
769
770#[derive(Debug, Clone)]
771pub struct RuntimeGraphCommunity {
772    pub id: String,
773    pub size: usize,
774    pub nodes: Vec<String>,
775}
776
777#[derive(Debug, Clone)]
778pub struct RuntimeGraphCommunityResult {
779    pub algorithm: RuntimeGraphCommunityAlgorithm,
780    pub count: usize,
781    pub iterations: Option<usize>,
782    pub converged: Option<bool>,
783    pub modularity: Option<f64>,
784    pub passes: Option<usize>,
785    pub communities: Vec<RuntimeGraphCommunity>,
786}
787
788#[derive(Debug, Clone)]
789pub struct RuntimeGraphClusteringResult {
790    pub global: f64,
791    pub local: Vec<RuntimeGraphCentralityScore>,
792    pub triangle_count: Option<usize>,
793}
794
795#[derive(Debug, Clone)]
796pub struct RuntimeGraphHitsResult {
797    pub iterations: usize,
798    pub converged: bool,
799    pub hubs: Vec<RuntimeGraphCentralityScore>,
800    pub authorities: Vec<RuntimeGraphCentralityScore>,
801}
802
803#[derive(Debug, Clone)]
804pub struct RuntimeGraphCyclesResult {
805    pub limit_reached: bool,
806    pub cycles: Vec<RuntimeGraphPath>,
807}
808
809#[derive(Debug, Clone)]
810pub struct RuntimeGraphTopologicalSortResult {
811    pub acyclic: bool,
812    pub ordered_nodes: Vec<RuntimeGraphNode>,
813}
814
815#[derive(Debug, Clone)]
816pub struct RuntimeGraphPropertiesResult {
817    pub node_count: usize,
818    pub edge_count: usize,
819    pub self_loop_count: usize,
820    pub negative_edge_count: usize,
821    pub connected_component_count: usize,
822    pub weak_component_count: usize,
823    pub strong_component_count: usize,
824    pub is_empty: bool,
825    pub is_connected: bool,
826    pub is_weakly_connected: bool,
827    pub is_strongly_connected: bool,
828    pub is_complete: bool,
829    pub is_complete_directed: bool,
830    pub is_cyclic: bool,
831    pub is_circular: bool,
832    pub is_acyclic: bool,
833    pub is_tree: bool,
834    pub density: f64,
835    pub density_directed: f64,
836}
837
838// ============================================================================
839// Context Search types
840// ============================================================================
841
842#[derive(Debug, Clone)]
843pub struct ContextSearchResult {
844    pub query: String,
845    pub tables: Vec<ContextEntity>,
846    pub graph: ContextGraphResult,
847    pub vectors: Vec<ContextEntity>,
848    pub documents: Vec<ContextEntity>,
849    pub key_values: Vec<ContextEntity>,
850    pub connections: Vec<ContextConnection>,
851    pub summary: ContextSummary,
852}
853
854#[derive(Debug, Clone)]
855pub struct ContextEntity {
856    pub entity: UnifiedEntity,
857    pub score: f32,
858    pub discovery: DiscoveryMethod,
859    pub collection: String,
860}
861
862#[derive(Debug, Clone)]
863pub enum DiscoveryMethod {
864    Indexed {
865        field: String,
866    },
867    GlobalScan,
868    CrossReference {
869        source_id: u64,
870        ref_type: String,
871    },
872    GraphTraversal {
873        source_id: u64,
874        edge_type: String,
875        depth: usize,
876    },
877    VectorQuery {
878        similarity: f32,
879    },
880}
881
882#[derive(Debug, Clone)]
883pub struct ContextGraphResult {
884    pub nodes: Vec<ContextEntity>,
885    pub edges: Vec<ContextEntity>,
886}
887
888#[derive(Debug, Clone)]
889pub struct ContextConnection {
890    pub from_id: u64,
891    pub to_id: u64,
892    pub connection_type: ContextConnectionType,
893    pub weight: f32,
894}
895
896#[derive(Debug, Clone)]
897pub enum ContextConnectionType {
898    CrossRef(String),
899    GraphEdge(String),
900    VectorSimilarity(f32),
901}
902
903#[derive(Debug, Clone)]
904pub struct ContextSummary {
905    pub total_entities: usize,
906    pub direct_matches: usize,
907    pub expanded_via_graph: usize,
908    pub expanded_via_cross_refs: usize,
909    pub expanded_via_vector_query: usize,
910    pub collections_searched: usize,
911    pub execution_time_us: u64,
912    pub tiers_used: Vec<String>,
913    pub entities_reindexed: usize,
914}
915
916struct PoolState {
917    next_id: u64,
918    active: usize,
919    pooled_active: usize,
920    idle: Vec<u64>,
921    total_checkouts: u64,
922}
923
924impl Default for PoolState {
925    fn default() -> Self {
926        Self {
927            next_id: 1,
928            active: 0,
929            pooled_active: 0,
930            idle: Vec::new(),
931            total_checkouts: 0,
932        }
933    }
934}
935
936#[derive(Debug, Clone)]
937struct RuntimeResultCacheEntry {
938    result: RuntimeQueryResult,
939    cached_at: std::time::Instant,
940    scopes: HashSet<String>,
941}
942
943pub const METRIC_CACHE_SHADOW_DIVERGENCE_TOTAL: &str = "cache_shadow_divergence_total";
944/// Stable metric names for the query/graph-analytics result cache
945/// (issue #802). Exposed under the `red.metrics` surface as
946/// `reddb_result_cache_{hit,miss,evict}_total` and readable in-process
947/// via `RedDBRuntime::result_cache_metrics`.
948pub const METRIC_RESULT_CACHE_HIT_TOTAL: &str = "result_cache_hit_total";
949pub const METRIC_RESULT_CACHE_MISS_TOTAL: &str = "result_cache_miss_total";
950pub const METRIC_RESULT_CACHE_EVICT_TOTAL: &str = "result_cache_evict_total";
951pub(crate) const ASK_ANSWER_CACHE_NAMESPACE: &str = "runtime.ask_answer_cache";
952const RMW_LOCK_SHARDS: usize = 64;
953
954type RmwLockShard = parking_lot::Mutex<HashMap<String, std::sync::Weak<RmwRowLock>>>;
955
956struct RmwLockTable {
957    shards: Vec<Arc<RmwLockShard>>,
958}
959
960struct RmwRowLock {
961    mutex: Arc<parking_lot::Mutex<()>>,
962    shard: std::sync::Weak<RmwLockShard>,
963    key: String,
964}
965
966struct OwnedRmwRowLockGuard {
967    // Unlock before dropping the identity owner and removing its weak entry.
968    _guard: parking_lot::ArcMutexGuard<parking_lot::RawMutex, ()>,
969    _owner: Arc<RmwRowLock>,
970}
971
972impl RmwRowLock {
973    fn lock(&self) -> parking_lot::MutexGuard<'_, ()> {
974        self.mutex.lock()
975    }
976
977    fn try_lock(&self) -> Option<parking_lot::MutexGuard<'_, ()>> {
978        self.mutex.try_lock()
979    }
980
981    fn lock_owned(self: Arc<Self>) -> OwnedRmwRowLockGuard {
982        let guard = self.mutex.lock_arc();
983        OwnedRmwRowLockGuard {
984            _guard: guard,
985            _owner: self,
986        }
987    }
988}
989
990impl Drop for RmwRowLock {
991    fn drop(&mut self) {
992        let Some(shard) = self.shard.upgrade() else {
993            return;
994        };
995        let mut shard = shard.lock();
996        // A new owner may already have replaced an expired weak entry while
997        // this destructor was waiting for the shard. Preserve that new lock.
998        if shard
999            .get(&self.key)
1000            .is_some_and(|entry| std::ptr::eq(entry.as_ptr(), self))
1001        {
1002            shard.remove(&self.key);
1003        }
1004    }
1005}
1006
1007impl RmwLockTable {
1008    fn row_key(logical_id: crate::storage::EntityId) -> String {
1009        format!("row:{}", logical_id.raw())
1010    }
1011
1012    fn new() -> Self {
1013        let shards = (0..RMW_LOCK_SHARDS)
1014            .map(|_| Arc::new(parking_lot::Mutex::new(HashMap::new())))
1015            .collect();
1016        Self { shards }
1017    }
1018
1019    fn lock_for(&self, collection: &str, key: &str) -> Arc<RmwRowLock> {
1020        use std::hash::{Hash, Hasher};
1021        let mut hasher = std::collections::hash_map::DefaultHasher::new();
1022        collection.hash(&mut hasher);
1023        key.hash(&mut hasher);
1024        let shard_idx = (hasher.finish() as usize) % self.shards.len();
1025        let map_key = format!("{collection}\u{1f}{key}");
1026        let mut shard = self.shards[shard_idx].lock();
1027        if let Some(lock) = shard.get(&map_key).and_then(std::sync::Weak::upgrade) {
1028            return lock;
1029        }
1030        let lock = Arc::new(RmwRowLock {
1031            mutex: Arc::new(parking_lot::Mutex::new(())),
1032            shard: Arc::downgrade(&self.shards[shard_idx]),
1033            key: map_key.clone(),
1034        });
1035        shard.insert(map_key, Arc::downgrade(&lock));
1036        lock
1037    }
1038}
1039
1040#[derive(Debug, Default)]
1041pub(crate) struct CheckpointProjectionStats {
1042    last_materialized_lsn: std::sync::atomic::AtomicU64,
1043    checkpoints_completed: std::sync::atomic::AtomicU64,
1044    last_checkpoint_duration_ms: std::sync::atomic::AtomicU64,
1045}
1046
1047impl CheckpointProjectionStats {
1048    pub(crate) fn record_checkpoint(&self, lsn: u64, duration_ms: u64) {
1049        use std::sync::atomic::Ordering;
1050
1051        self.last_materialized_lsn.store(lsn, Ordering::Relaxed);
1052        self.last_checkpoint_duration_ms
1053            .store(duration_ms, Ordering::Relaxed);
1054        self.checkpoints_completed.fetch_add(1, Ordering::Relaxed);
1055    }
1056
1057    pub(crate) fn snapshot(&self, current_lsn: u64) -> CheckpointProjectionStatsSnapshot {
1058        use std::sync::atomic::Ordering;
1059
1060        let last_materialized_lsn = self.last_materialized_lsn.load(Ordering::Relaxed);
1061        CheckpointProjectionStatsSnapshot {
1062            current_lsn,
1063            last_materialized_lsn,
1064            projection_lag: current_lsn.saturating_sub(last_materialized_lsn),
1065            checkpoints_completed: self.checkpoints_completed.load(Ordering::Relaxed),
1066            last_checkpoint_duration_ms: self.last_checkpoint_duration_ms.load(Ordering::Relaxed),
1067        }
1068    }
1069}
1070
1071#[derive(Debug, Clone, Copy)]
1072pub(crate) struct CheckpointProjectionStatsSnapshot {
1073    pub(crate) current_lsn: u64,
1074    pub(crate) last_materialized_lsn: u64,
1075    pub(crate) projection_lag: u64,
1076    pub(crate) checkpoints_completed: u64,
1077    pub(crate) last_checkpoint_duration_ms: u64,
1078}
1079
1080#[derive(Debug, Clone)]
1081pub(crate) struct ScrubStatsSnapshot {
1082    pub(crate) last_run_unix_ms: u64,
1083    pub(crate) last_findings_count: u64,
1084    pub(crate) background_status: String,
1085    pub(crate) background_verified_objects: u64,
1086    pub(crate) background_total_objects: u64,
1087    pub(crate) verified: reddb_file::StorageScrubVerifiedCounters,
1088}
1089
1090#[derive(Debug, Clone)]
1091pub(crate) struct ScrubRuntimeState {
1092    pub(crate) last_run_unix_ms: u64,
1093    pub(crate) last_findings_count: u64,
1094    pub(crate) background_cursor: usize,
1095    pub(crate) background_status: String,
1096    pub(crate) background_verified_objects: u64,
1097    pub(crate) background_total_objects: u64,
1098    pub(crate) verified: reddb_file::StorageScrubVerifiedCounters,
1099}
1100
1101impl Default for ScrubRuntimeState {
1102    fn default() -> Self {
1103        Self {
1104            last_run_unix_ms: 0,
1105            last_findings_count: 0,
1106            background_cursor: 0,
1107            background_status: "idle".to_string(),
1108            background_verified_objects: 0,
1109            background_total_objects: 0,
1110            verified: reddb_file::StorageScrubVerifiedCounters::default(),
1111        }
1112    }
1113}
1114
1115impl ScrubRuntimeState {
1116    pub(crate) fn snapshot(&self) -> ScrubStatsSnapshot {
1117        ScrubStatsSnapshot {
1118            last_run_unix_ms: self.last_run_unix_ms,
1119            last_findings_count: self.last_findings_count,
1120            background_status: self.background_status.clone(),
1121            background_verified_objects: self.background_verified_objects,
1122            background_total_objects: self.background_total_objects,
1123            verified: self.verified.clone(),
1124        }
1125    }
1126}
1127
1128struct RuntimeInner {
1129    functions: parking_lot::RwLock<function_catalog::FunctionCatalog>,
1130    db: Arc<RedDB>,
1131    layout: PhysicalLayout,
1132    embedded_single_file: bool,
1133    /// The one memory budget this process runs under (ADR 0073 §1),
1134    /// resolved once at boot and immutable for the process lifetime.
1135    pub(crate) memory_budget: crate::storage::memory_budget::MemoryBudget,
1136    /// The one shared accounting pool (ADR 0073 §2): each big consumer's share
1137    /// of the budget above, and the live usage they report into it.
1138    pub(crate) memory_accounting: Arc<crate::storage::memory_pools::MemoryAccounting>,
1139    memory_reservations: parking_lot::Mutex<memory_admission::MemoryReservations>,
1140    mvcc_maintenance: parking_lot::Mutex<mvcc_maintenance::MvccMaintenance>,
1141    indices: IndexCatalog,
1142    pool_config: ConnectionPoolConfig,
1143    pool: Mutex<PoolState>,
1144    started_at_unix_ms: u128,
1145    probabilistic: probabilistic_store::ProbabilisticStore,
1146    index_store: index_store::IndexStore,
1147    cdc: crate::replication::cdc::CdcBuffer,
1148    pub(crate) checkpoint_projection_stats: CheckpointProjectionStats,
1149    pub(crate) scrub_state: parking_lot::Mutex<ScrubRuntimeState>,
1150    pub(crate) checkpoint_columnar_emission_budget_chunks: usize,
1151    pub(crate) columnar_projection_size_floor_rows: usize,
1152    backup_scheduler: crate::replication::scheduler::BackupScheduler,
1153    query_cache: parking_lot::RwLock<crate::storage::query::planner::cache::PlanCache>,
1154    result_cache: parking_lot::RwLock<(
1155        HashMap<String, RuntimeResultCacheEntry>,
1156        std::collections::VecDeque<String>,
1157    )>,
1158    result_blob_cache: crate::storage::cache::BlobCache,
1159    result_blob_entries: parking_lot::RwLock<(
1160        HashMap<String, RuntimeResultCacheEntry>,
1161        std::collections::VecDeque<String>,
1162    )>,
1163    ask_answer_cache_entries:
1164        parking_lot::RwLock<(HashSet<String>, std::collections::VecDeque<String>)>,
1165    result_cache_shadow_divergences: std::sync::atomic::AtomicU64,
1166    /// Result-cache observability counters (issue #802). Process-cumulative
1167    /// hit/miss/eviction tallies surfaced under `red.metrics`.
1168    result_cache_hits: std::sync::atomic::AtomicU64,
1169    result_cache_misses: std::sync::atomic::AtomicU64,
1170    result_cache_evictions: std::sync::atomic::AtomicU64,
1171    ask_daily_spend:
1172        parking_lot::RwLock<HashMap<String, crate::runtime::ai::cost_guard::DailyState>>,
1173    /// Process-local queue message locks used to emulate `SKIP LOCKED`-style
1174    /// claim semantics for concurrent queue consumers inside this runtime.
1175    queue_message_locks: parking_lot::RwLock<HashMap<String, Arc<parking_lot::Mutex<()>>>>,
1176    /// Process-local read-modify-write locks. The table is sharded by
1177    /// `(collection, key)` and each entry has its own mutex, so unrelated keys
1178    /// in the same collection do not serialize behind one global lock.
1179    rmw_locks: RmwLockTable,
1180    planner_dirty_tables: parking_lot::RwLock<HashSet<String>>,
1181    ec_registry: Arc<crate::ec::config::EcRegistry>,
1182    config_registry: Arc<crate::auth::registry::ConfigRegistry>,
1183    ec_worker: crate::ec::worker::EcWorker,
1184    /// Optional AuthStore — injected by server boot when auth is
1185    /// enabled. Required for `Value::Secret` auto-encrypt/decrypt
1186    /// because the AES key lives in the vault KV under the
1187    /// `red.secret.aes_key` entry.
1188    auth_store: parking_lot::RwLock<Option<Arc<crate::auth::store::AuthStore>>>,
1189    /// Optional OAuth/OIDC JWT validator. Wired by server boot when
1190    /// the operator configures issuer + JWKS via env / CLI. HTTP and
1191    /// wire transports read this on every bearer-token request and,
1192    /// when the token decodes as a JWT, validate it against the
1193    /// configured issuer + audience + signature before falling back to
1194    /// the local AuthStore lookup.
1195    oauth_validator: parking_lot::RwLock<Option<Arc<crate::auth::oauth::OAuthValidator>>>,
1196    /// Browser credential layer — the hybrid-token authority (issue
1197    /// #936, PRD #930). When wired (an operator opts the browser
1198    /// endpoint in), the RedWire WS handshake accepts the short-lived
1199    /// access JWT it mints, and the `/auth/browser/*` HTTP endpoints
1200    /// issue/rotate the access+refresh pair. `None` leaves the browser
1201    /// flow inert — default-deny, matching the WS endpoint itself.
1202    browser_token_authority:
1203        parking_lot::RwLock<Option<Arc<crate::auth::browser_token::BrowserTokenAuthority>>>,
1204    /// View registry (Phase 2.1 PG parity).
1205    ///
1206    /// Holds the parsed `SELECT` body for every view created via
1207    /// `CREATE [MATERIALIZED] VIEW`. Queries that reference a view name
1208    /// substitute the stored `QueryExpr` at execution time. Materialized
1209    /// views additionally back onto the shared `MaterializedViewCache`
1210    /// (see `RuntimeInner::materialized_views`).
1211    ///
1212    /// This is in-memory only in Phase 2.1 — view definitions do not
1213    /// survive a restart. Persistence is a Phase 3 follow-up.
1214    views: parking_lot::RwLock<HashMap<String, Arc<reddb_rql::ast::CreateViewQuery>>>,
1215    materialized_views: parking_lot::RwLock<crate::storage::cache::result::MaterializedViewCache>,
1216    /// Per-collection retention sweeper state (issue #584 slice 12).
1217    /// Tracks `last_sweep_at_ms`, row/segment retirement totals, and
1218    /// the latest pending-rows estimate that feeds `red.retention`.
1219    /// In-memory only; resets across restart.
1220    pub(crate) retention_sweeper:
1221        parking_lot::RwLock<crate::runtime::retention_sweeper::RetentionSweeperState>,
1222    /// Per-connection transaction contexts, local tenant overrides, and the
1223    /// shared MVCC snapshot manager behind the Transaction State interface.
1224    transaction_state: crate::runtime::transaction_state::TransactionState,
1225    /// Intent-lock hierarchy (IS/IX/S/X) used to break the implicit
1226    /// global-write serialisation in write paths. Populated at boot
1227    /// with `concurrency.locking.deadlock_timeout_ms` from the matrix
1228    /// and wired through DML/DDL dispatch in later P1 tasks.
1229    /// Dormant until P1.T3 flips the read path to `(Global,IS) →
1230    /// (Collection,IS)` and P1.T4/T5 pick up writes/DDL.
1231    lock_manager: Arc<crate::runtime::lock_manager::LockManager>,
1232    /// Perf-parity env-var overrides (`REDDB_<UP_DOTTED_KEY>`).
1233    /// Populated once at boot, read by every config getter; takes
1234    /// precedence over the persisted red_config value so operators
1235    /// can hot-fix a bad config by restarting with a different env
1236    /// var set. Keys are restricted to those declared in the matrix.
1237    env_config_overrides: HashMap<String, String>,
1238    /// Row-level security policies (Phase 2.5 PG parity).
1239    ///
1240    /// Keyed by `(table_name, policy_name)`; the set of tables with RLS
1241    /// enforcement toggled on lives in `rls_enabled_tables`. Filter
1242    /// enforcement hooks into the read path via `collect_rls_filters()`
1243    /// — see `runtime::impl_core`.
1244    rls_policies:
1245        parking_lot::RwLock<HashMap<(String, String), Arc<reddb_rql::ast::CreatePolicyQuery>>>,
1246    rls_enabled_tables: parking_lot::RwLock<HashSet<String>>,
1247    /// Foreign Data Wrapper registry (Phase 3.2 PG parity).
1248    ///
1249    /// Maps server names → wrapper instances and foreign-table names →
1250    /// definitions. Queries referencing a registered foreign table are
1251    /// re-routed to `ForeignTableRegistry::scan` by the read-path
1252    /// rewriter; reads against unknown names fall through to the native
1253    /// collection lookup.
1254    foreign_tables: Arc<crate::storage::fdw::ForeignTableRegistry>,
1255    /// Per-connection list of tuples marked for deletion by the current
1256    /// transaction (Phase 2.3.2b MVCC tombstones + 2.3.2e savepoints).
1257    /// Each entry is `(collection, entity_id, stamper_xid, previous_xmax)`
1258    /// — the xid that stamped xmax on the tuple plus the value it
1259    /// replaced. For a plain transaction the
1260    /// stamper equals `ctx.xid`; with savepoints the stamper equals
1261    /// the innermost open sub-xid so ROLLBACK TO SAVEPOINT can revive
1262    /// only the matching subset. COMMIT drains the whole conn list
1263    /// and keeps the committed tombstones; ROLLBACK (whole-tx) revives them all;
1264    /// ROLLBACK TO SAVEPOINT revives those with `stamper_xid >=
1265    /// savepoint_xid`. Autocommit DELETE bypasses this map.
1266    pending_tombstones: parking_lot::RwLock<
1267        HashMap<
1268            u64,
1269            Vec<(
1270                String,
1271                crate::storage::unified::entity::EntityId,
1272                crate::storage::transaction::snapshot::Xid,
1273                crate::storage::transaction::snapshot::Xid,
1274            )>,
1275        >,
1276    >,
1277    /// Per-connection table-row UPDATE versions created by an open
1278    /// transaction. Each entry is `(collection, old_entity_id,
1279    /// new_entity_id, stamper_xid, previous_xmax)`. COMMIT keeps both physical
1280    /// versions and drops the pending marker; ROLLBACK revives the old
1281    /// version and removes the new uncommitted version.
1282    pending_versioned_updates: parking_lot::RwLock<
1283        HashMap<
1284            u64,
1285            Vec<(
1286                String,
1287                crate::storage::unified::entity::EntityId,
1288                crate::storage::unified::entity::EntityId,
1289                crate::storage::transaction::snapshot::Xid,
1290                crate::storage::transaction::snapshot::Xid,
1291            )>,
1292        >,
1293    >,
1294    /// Per-connection push idempotency keys written by an open transaction.
1295    /// Each entry is `(queue, dedup_key, metadata_entity_id)`. COMMIT checks
1296    /// the queue/key subject for first-committer-wins conflicts; ROLLBACK
1297    /// removes the uncommitted metadata row so later producer retries enqueue
1298    /// normally.
1299    pending_queue_dedup: parking_lot::RwLock<HashMap<u64, Vec<(String, String, EntityId)>>>,
1300    pending_kv_watch_events:
1301        parking_lot::RwLock<HashMap<u64, Vec<(u64, crate::replication::cdc::KvWatchEvent)>>>,
1302    pending_vault_writes: parking_lot::RwLock<HashMap<u64, Vec<impl_kv::PendingVaultWrite>>>,
1303    pending_store_wal_actions:
1304        parking_lot::RwLock<HashMap<u64, crate::storage::unified::DeferredStoreWalActions>>,
1305    /// Transaction-scoped table UPDATE CLAIM ownership.
1306    ///
1307    /// Keyed by `(collection, logical_row_id)` and valued by connection id.
1308    /// Only claim selection consults this map: ordinary DML still follows
1309    /// MVCC first-committer-wins instead of waiting on claim locks.
1310    pending_claim_locks: parking_lot::RwLock<HashMap<(String, EntityId), u64>>,
1311    /// Table-scoped tenancy registry (Phase 2.5.4).
1312    ///
1313    /// Maps `table_name → tenant_column`. DML auto-fill looks here to
1314    /// inject `CURRENT_TENANT()` on INSERTs that omit the column, and
1315    /// DDL keeps the in-memory registry in sync with the
1316    /// `tenant_tables.*` keys in red_config. Read-side enforcement
1317    /// piggy-backs on the existing RLS infrastructure: every entry
1318    /// installs an implicit `col = CURRENT_TENANT()` policy.
1319    tenant_tables: parking_lot::RwLock<HashMap<String, String>>,
1320    /// Monotonic epoch bumped on every DDL / schema-mutating operation
1321    /// that calls `invalidate_plan_cache`. Prepared statements capture
1322    /// this at PREPARE and re-check at EXECUTE — a mismatch means the
1323    /// cached shape may reference dropped or renamed columns and the
1324    /// client must re-PREPARE.
1325    ddl_epoch: std::sync::atomic::AtomicU64,
1326    /// Public-mutation gate (PLAN.md W1).
1327    ///
1328    /// Built once at construction from the immutable subset of
1329    /// `RedDBOptions` (read_only flag + replication role). Every public
1330    /// mutation surface — SQL DML/DDL, gRPC mutating RPCs, HTTP/native
1331    /// wire mutations, admin maintenance endpoints, serverless
1332    /// lifecycle — consults `write_gate.check(WriteKind::*)` before
1333    /// dispatching to storage. The replica internal apply path
1334    /// (`LogicalChangeApplier`) reaches into the store directly and
1335    /// bypasses the gate by construction.
1336    write_gate: Arc<crate::runtime::write_gate::WriteGate>,
1337    /// Process lifecycle state machine (PLAN.md Phase 1 — Lifecycle
1338    /// Contract). Drives `/health/live`, `/health/ready`,
1339    /// `/health/startup`, and `POST /admin/shutdown` so any
1340    /// orchestrator (K8s preStop, Fly autostop, ECS task drain,
1341    /// systemd) can coordinate without losing data.
1342    lifecycle: crate::runtime::lifecycle::Lifecycle,
1343    /// Operator-imposed resource limits (PLAN.md Phase 4.1).
1344    /// Read once at boot from `RED_MAX_*` env vars; consulted by
1345    /// observability and (in follow-up commits) the per-write
1346    /// enforcement points.
1347    resource_limits: crate::runtime::resource_limits::ResourceLimits,
1348    query_budget_slots: AtomicU64,
1349    /// Append-only audit log for admin mutations (PLAN.md Phase
1350    /// 6.5). Lives next to the primary `.rdb` file so backup +
1351    /// restore flows ship it alongside the data.
1352    audit_log: Arc<crate::runtime::audit_log::AuditLogger>,
1353    /// Durable control-plane evidence ledger. Producer slices emit
1354    /// through this sink and decide fail-open/fail-closed using
1355    /// `control_event_config`.
1356    control_event_ledger:
1357        parking_lot::RwLock<Arc<dyn crate::runtime::control_events::ControlEventLedger>>,
1358    control_event_config: crate::runtime::control_events::ControlEventConfig,
1359    /// Data-plane query audit stream. Kept separate from the Control
1360    /// Event Ledger so scoped query metadata cannot be confused with
1361    /// governance evidence.
1362    query_audit: Arc<crate::runtime::query_audit::QueryAuditStream>,
1363    /// Serverless writer-lease state machine. `None` when the operator
1364    /// did not opt into lease fencing (`RED_LEASE_REQUIRED` unset/false).
1365    /// When set, owns the {acquire/refresh/release/lost} transitions and
1366    /// is the single place that mutates `write_gate.set_lease_state` and
1367    /// records lease/* audit entries — keeping those two side-effects
1368    /// from drifting.
1369    lease_lifecycle: std::sync::OnceLock<Arc<crate::runtime::lease_lifecycle::LeaseLifecycle>>,
1370    /// PLAN.md Phase 11.5 — counters bumped by the replica apply
1371    /// loop on `Gap` / `Divergence` / `Apply` errors so /metrics
1372    /// surfaces them as `reddb_replica_apply_errors_total{kind}`.
1373    replica_apply_metrics: Arc<crate::replication::logical::ReplicaApplyMetrics>,
1374    /// Issue #1243 (PRD #1237 Phase B) — primary↔replica reconnect counter
1375    /// bumped by the replica loop's health-persist chokepoint when the link
1376    /// drops and restores. Surfaced as `reddb_replication_reconnects_total`
1377    /// and in the red-ui replica status read model.
1378    replica_link_metrics: Arc<crate::replication::reconnect::ReplicaLinkMetrics>,
1379    /// PLAN.md Phase 4.4 — per-caller QPS quotas. Disabled (no-op)
1380    /// when `RED_MAX_QPS_PER_CALLER` is unset.
1381    quota_bucket: crate::runtime::quota_bucket::QuotaBucket,
1382    /// Issue #120 — token → schema entity reverse index, kept current
1383    /// incrementally on DDL events. Consumed by AskPipeline (issue
1384    /// #121) Stage 2 to narrow vector-search candidates before any
1385    /// embedding compute. Mutated only from DDL execution paths.
1386    schema_vocabulary: parking_lot::RwLock<crate::runtime::schema_vocabulary::SchemaVocabulary>,
1387    /// Issue #205 — dedicated slow-query sink (`red-slow.log`).
1388    /// Built once at runtime startup; below-threshold calls pay only a
1389    /// single relaxed atomic load. Threshold + sample-pct come from
1390    /// `runtime.slow_query.threshold_ms` / `.sample_pct` (config matrix)
1391    /// at construction; live tuning via the config tree is a follow-up.
1392    slow_query_logger: Arc<crate::telemetry::slow_query_logger::SlowQueryLogger>,
1393    /// Issue #1238 — operational telemetry substrate for slow-query events
1394    /// (ADR 0060, §2). Bounded ring buffer that the slow-query logger dual-
1395    /// writes into alongside `red-slow.log`. Exposed to read-model consumers
1396    /// via `slow_query_store()`.
1397    slow_query_store: Arc<crate::telemetry::slow_query_store::SlowQueryStore>,
1398    /// Process-local normal-KV operation counters. These are intentionally
1399    /// runtime-local; persistent accounting belongs in catalog stats.
1400    kv_stats: KvStatsCounters,
1401    metrics_ingest_stats: MetricsIngestCounters,
1402    metrics_tenant_activity_stats: MetricsTenantActivityCounters,
1403    /// Issue #1457 — Concurrent claim counters rendered onto `/metrics`.
1404    /// Process-local; counters reset on restart by design.
1405    claim_telemetry: std::sync::Arc<claim_telemetry::ClaimTelemetryCounters>,
1406    /// Slice 10 of issue #527 — Prometheus counters for the queue
1407    /// lifecycle (delivered/acked/nacked) rendered onto `/metrics`.
1408    /// Process-local; counters reset on restart by design.
1409    queue_telemetry: std::sync::Arc<queue_telemetry::QueueTelemetryCounters>,
1410    /// Issue #1241 — query latency histogram substrate (per-`kind`
1411    /// `reddb_query_duration_seconds`). Process-local measurement + read
1412    /// model that `/metrics`, `/cluster/status`, and the red-ui percentile
1413    /// panels all consume; counters reset on restart by design.
1414    query_latency_telemetry: std::sync::Arc<query_latency_telemetry::QueryLatencyTelemetry>,
1415    /// Issue #1244 — node CPU/RAM occupancy sampler (ADR 0060 §2 "node
1416    /// samples"). Process-local measurement + current-snapshot read model
1417    /// `/cluster/status` consumes; sampled on-demand from the status
1418    /// handler. See `occupancy_sampler` for the overhead/interval contract.
1419    occupancy_sampler: std::sync::Arc<occupancy_sampler::OccupancySampler>,
1420    /// Issue #1245 — per-node load telemetry (active query gauge +
1421    /// connect/disconnect churn counters). Three atomics; no per-client
1422    /// labels; feeds `/metrics`, `/cluster/status`, and the red-ui load
1423    /// panels (ADR 0060 §2 "node samples" data class).
1424    node_load_telemetry: std::sync::Arc<node_load_telemetry::NodeLoadTelemetry>,
1425    /// Issue #742 — consumer presence (heartbeat / lease / lifecycle
1426    /// state per (queue, group, consumer)). Process-local in this
1427    /// slice; durability across restart lands in the follow-up that
1428    /// mirrors the registry into `red_queue_meta` rows. Independent
1429    /// of pending-delivery state by design — see
1430    /// `storage::queue::presence` for the contract.
1431    queue_presence: std::sync::Arc<crate::storage::queue::presence::ConsumerPresenceRegistry>,
1432    /// Issue #743 — vector + TurboQuant introspection registry. Red UI
1433    /// vector toolbars (and `red.*` vector virtual tables) read
1434    /// per-collection metadata + artifact state from here so they
1435    /// never have to reach into `engine::vector_store` or
1436    /// `engine::turboquant::*` internals. Engine publish points (build
1437    /// start / finish, fallback toggle, drop) call into this in
1438    /// follow-up slices of PRD #735; the public Rust surface lives on
1439    /// the runtime and does not change when those land.
1440    vector_introspection:
1441        std::sync::Arc<crate::storage::vector::introspection::VectorIntrospectionRegistry>,
1442    /// Slice C of PRD #718 — local wait registry for `QUEUE READ … WAIT`.
1443    /// Producer commits notify; readers park here until wake-or-timeout
1444    /// or until `cancel_all` is invoked at shutdown.
1445    queue_wait_registry: std::sync::Arc<queue_wait_registry::QueueWaitRegistry>,
1446    /// Per-connection list of `(scope, queue)` pairs that should be
1447    /// notified through `queue_wait_registry` on COMMIT (slice C of
1448    /// PRD #718). Push paths buffer here while inside a txn; the
1449    /// COMMIT path drains and notifies. ROLLBACK discards — by design
1450    /// rolled-back enqueues do not deliver and do not wake waiters.
1451    pending_queue_wakes: parking_lot::RwLock<HashMap<u64, Vec<(String, String)>>>,
1452    /// Process-local normal-KV tag index used by `INVALIDATE TAGS`.
1453    kv_tag_index: KvTagIndex,
1454    /// Issue #524 — in-memory chain-tip cache per collection. Populated lazily
1455    /// by the first INSERT or `GET /chain-tip` call after restart and updated
1456    /// atomically with each chain INSERT. Backed by a single mutex so a chain
1457    /// INSERT serialises against concurrent submitters — the loser observes
1458    /// the advanced tip and surfaces `BlockchainConflict` to its caller.
1459    chain_tip_cache:
1460        parking_lot::Mutex<HashMap<String, crate::runtime::blockchain_kind::ChainTipFull>>,
1461    /// Issue #525 — in-memory mirror of the persisted `integrity` flag per
1462    /// chain collection.  `true` means INSERTs must be rejected with
1463    /// `ChainIntegrityBroken`.  Loaded lazily from `red_config` on first
1464    /// access so the flag survives restart.
1465    chain_integrity_broken: parking_lot::Mutex<HashMap<String, bool>>,
1466    /// Issue #765 / S6 — in-memory cache of input-stream integrity tombstone
1467    /// RID ranges. Loaded lazily from `red_config` on first read so the set
1468    /// survives restart (the durable list lives under
1469    /// `stream.integrity.tombstones`). `integrity_tombstones_state` is the
1470    /// hot-path gate: `0` = unloaded, `1` = loaded-empty (reads skip
1471    /// filtering after a single relaxed load), `2` = loaded-with-tombstones.
1472    integrity_tombstones:
1473        parking_lot::Mutex<Vec<crate::runtime::integrity_tombstone::TombstoneRange>>,
1474    integrity_tombstones_state: std::sync::atomic::AtomicU8,
1475}
1476
1477#[derive(Clone)]
1478pub struct RedDBRuntime {
1479    // Field order matters: drain workers before dropping the foreground inner.
1480    // Temporary handles used by workers do not own their own shutdown guard.
1481    background_tasks: Option<Arc<background_tasks::RuntimeBackgroundTasks>>,
1482    inner: Arc<RuntimeInner>,
1483}
1484
1485pub struct RuntimeConnection {
1486    id: u64,
1487    uses_pool_slot: bool,
1488    background_tasks: Option<Arc<background_tasks::RuntimeBackgroundTasks>>,
1489    inner: Arc<RuntimeInner>,
1490}
1491
1492pub struct CausalSession {
1493    runtime: RedDBRuntime,
1494    bookmark: Option<crate::replication::CausalBookmark>,
1495    wait_timeout: std::time::Duration,
1496}
1497
1498impl CausalSession {
1499    pub fn bookmark_token(&self) -> Option<String> {
1500        self.bookmark.map(|bookmark| bookmark.encode())
1501    }
1502
1503    pub fn inject_bookmark(&mut self, token: &str) -> RedDBResult<()> {
1504        let bookmark = crate::replication::CausalBookmark::decode(token)
1505            .map_err(|err| RedDBError::InvalidOperation(err.to_string()))?;
1506        self.bookmark = Some(bookmark);
1507        Ok(())
1508    }
1509
1510    pub fn execute_query(&mut self, query: &str) -> RedDBResult<RuntimeQueryResult> {
1511        if is_select_query_text(query) {
1512            if let Some(bookmark) = self.bookmark {
1513                self.runtime
1514                    .wait_for_bookmark(&bookmark, self.wait_timeout)?;
1515            }
1516        }
1517        let result = self.runtime.execute_query(query)?;
1518        if let Some(token) = result.bookmark.as_deref() {
1519            self.inject_bookmark(token)?;
1520        }
1521        Ok(result)
1522    }
1523}
1524
1525fn is_select_query_text(query: &str) -> bool {
1526    query
1527        .trim_start()
1528        .get(..6)
1529        .is_some_and(|prefix| prefix.eq_ignore_ascii_case("select"))
1530}
1531
1532pub mod ai;
1533mod allocator_pressure;
1534pub mod analytics_schema_registry;
1535pub(crate) mod analytics_source_catalog;
1536pub mod ask_pipeline;
1537pub mod audit_log;
1538pub mod audit_query;
1539pub mod authorized_search;
1540pub(crate) mod authz;
1541mod background_tasks;
1542pub mod batch_insert;
1543pub mod blockchain_kind;
1544mod collection_contract;
1545pub mod config_matrix;
1546pub mod config_overlay;
1547pub mod config_watcher;
1548pub mod continuous_materialized_view;
1549mod control_event_index;
1550pub mod control_events;
1551pub(crate) mod ddl;
1552mod delete_publication;
1553pub mod disk_space_monitor;
1554mod dml_target_scan;
1555pub mod evidence_export;
1556pub(crate) mod execution_context;
1557mod expr_eval;
1558mod graph_dsl;
1559mod graph_tvf;
1560mod health_connection;
1561mod impl_audit_control;
1562mod impl_backup;
1563mod impl_catalog_accessors;
1564mod impl_cdc;
1565mod impl_config;
1566mod impl_config_secret;
1567pub(crate) mod impl_core;
1568mod impl_core_outer_scope;
1569mod impl_core_result_cache_scopes;
1570mod impl_ddl;
1571mod impl_dml;
1572mod impl_dml_batch;
1573mod impl_dml_chain;
1574mod impl_dml_claim;
1575mod impl_dml_crypto;
1576mod impl_dml_returning;
1577mod impl_dml_support;
1578mod impl_dml_tenant;
1579mod impl_dml_ttl;
1580mod impl_dml_update_analysis;
1581mod impl_dml_update_target;
1582mod impl_ec;
1583pub mod impl_ephemeral;
1584mod impl_events;
1585mod impl_fast_path;
1586mod impl_graph;
1587mod impl_graph_commands;
1588pub mod impl_kv;
1589mod impl_lifecycle;
1590mod impl_materialized_view;
1591mod impl_migrations;
1592mod impl_native;
1593mod impl_physical;
1594mod impl_primary_replica_file;
1595mod impl_probabilistic;
1596pub mod impl_queue;
1597mod impl_ranking;
1598mod impl_replica_loop;
1599mod impl_replication_commit;
1600mod impl_runtime_index_registry;
1601mod impl_telemetry_accessors;
1602mod impl_tenant_registry;
1603pub(crate) use impl_queue::RedwireWaitOutcome;
1604pub(crate) mod claim_telemetry;
1605#[cfg(test)]
1606mod evaluator_differential;
1607mod function_budget;
1608pub(crate) mod function_catalog;
1609mod function_execution;
1610mod function_validation;
1611mod impl_scrub;
1612mod impl_search;
1613mod impl_serverless;
1614mod impl_timeseries;
1615mod impl_tree;
1616mod impl_vcs;
1617mod index_store;
1618mod paged_btree_index;
1619mod paged_count;
1620mod paged_hash_index;
1621#[cfg(test)]
1622pub(crate) use index_store::{IndexMethodKind as QualificationIndexMethodKind, MutationTestPhase};
1623#[cfg(test)]
1624mod cold_keyed_models;
1625mod hash_candidate_memory;
1626#[cfg(test)]
1627mod index_intersection_memory;
1628pub mod integrity_tombstone;
1629mod join_filter;
1630mod keyed_spine;
1631pub(crate) mod kv_read;
1632pub mod kv_watch;
1633pub(crate) mod kv_write;
1634pub mod lease_lifecycle;
1635pub mod lease_loop;
1636pub mod lease_timer_wheel;
1637pub mod lifecycle;
1638pub mod lock_manager;
1639pub mod locking;
1640pub(crate) mod materialization_limit;
1641/// Samples every governed pool's live usage into the shared accounting
1642/// (ADR 0073 §2).
1643pub mod memory_accounting;
1644pub(crate) mod memory_admission;
1645pub(crate) mod memory_buffers;
1646pub(crate) mod metric_descriptor_catalog;
1647pub(crate) mod mutation;
1648#[cfg(test)]
1649mod mutation_index_topology_tests;
1650#[cfg(test)]
1651mod mutation_memory_tests;
1652pub(crate) mod mvcc_lifecycle;
1653pub(crate) mod mvcc_maintenance;
1654pub(crate) mod node_load_telemetry;
1655pub(crate) mod occupancy_sampler;
1656pub(crate) mod owned_query_result;
1657pub use owned_query_result::OwnedQueryResult;
1658pub(crate) mod primary_queue_store;
1659mod probabilistic_store;
1660pub mod query_audit;
1661pub(crate) mod query_budget;
1662pub(crate) mod query_exec;
1663pub(crate) mod query_latency_telemetry;
1664pub mod query_request;
1665pub(crate) mod queue_lifecycle;
1666pub(crate) mod queue_telemetry;
1667pub(crate) mod queue_wait_registry;
1668pub mod quota_bucket;
1669pub(crate) mod ranking_descriptor_catalog;
1670mod record_search;
1671mod red_schema;
1672pub(crate) mod replica_queue_store;
1673pub mod resource_limits;
1674mod result_cache_runtime;
1675pub(crate) mod retention_filter;
1676pub(crate) mod retention_sweeper;
1677mod rls_injection;
1678pub mod schema_diff;
1679pub mod schema_vocabulary;
1680pub(crate) mod score_sketch;
1681mod search_graph;
1682pub(crate) mod sessionize;
1683pub mod signed_chain;
1684pub mod signed_writes_kind;
1685pub(crate) mod slo_descriptor_catalog;
1686pub mod snapshot_reuse;
1687mod statement_frame;
1688mod table_row_mvcc_resolver;
1689pub(crate) mod transaction_state;
1690pub mod turbo_crash_inject;
1691mod unique_index_admission;
1692mod vcs_command;
1693mod vector_index;
1694pub mod vector_turbo_kind;
1695pub(crate) mod window_phase;
1696pub mod within_clause;
1697pub mod write_gate;
1698
1699pub use self::claim_telemetry::ClaimTelemetrySnapshot;
1700pub use self::graph_dsl::*;
1701use self::join_filter::*;
1702use self::query_exec::*;
1703use self::record_search::*;
1704pub use self::statement_frame::EffectiveScope;
1705
1706/// Re-exports for transports + tests that need per-connection
1707/// isolation, tenant / auth thread-locals, and MVCC snapshot
1708/// utilities. Mirrors what PG-wire / gRPC / HTTP middleware already
1709/// call, and is enough to emulate independent connections in
1710/// integration tests.
1711pub mod mvcc {
1712    pub use super::impl_core::{
1713        capture_current_snapshot, clear_current_auth_identity, clear_current_connection_id,
1714        clear_current_snapshot, clear_current_tenant, current_connection_id, current_tenant,
1715        entity_visible_under_current_snapshot, entity_visible_with_context,
1716        set_current_auth_identity, set_current_connection_id, set_current_snapshot,
1717        set_current_tenant, snapshot_bundle, with_snapshot_bundle, SnapshotBundle, SnapshotContext,
1718    };
1719}
1720
1721/// Public helpers re-exported for use by the presentation layer.
1722pub mod record_search_helpers {
1723    use crate::storage::query::UnifiedRecord;
1724    use crate::storage::UnifiedEntity;
1725    use std::collections::BTreeSet;
1726
1727    pub fn entity_type_and_capabilities(
1728        entity: &UnifiedEntity,
1729    ) -> (&'static str, BTreeSet<String>) {
1730        super::record_search::runtime_entity_type_and_capabilities(entity)
1731    }
1732
1733    /// Materialise any entity kind (TableRow, Node, Edge, Vector,
1734    /// TimeSeriesPoint, QueueMessage) into a `UnifiedRecord` whose
1735    /// `values` carry the native fields. Used by the RLS evaluator
1736    /// when a non-table collection matches a `CompareExpr` policy.
1737    pub fn any_record_from_entity(entity: UnifiedEntity) -> Option<UnifiedRecord> {
1738        super::record_search::runtime_any_record_from_entity(entity)
1739    }
1740}