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