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