1use std::collections::BTreeMap;
7use std::time::{SystemTime, UNIX_EPOCH};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
10pub enum IndexKind {
11 BTree,
12 Hash,
13 Bitmap,
14 Spatial,
15 VectorHnsw,
16 VectorInverted,
17 GraphAdjacency,
18 FullText,
19 DocumentPathValue,
20 HybridSearch,
21}
22
23impl IndexKind {
24 pub const fn as_str(self) -> &'static str {
25 match self {
26 Self::BTree => "btree",
27 Self::Hash => "hash",
28 Self::Bitmap => "bitmap",
29 Self::Spatial => "spatial.rtree",
30 Self::VectorHnsw => "vector.hnsw",
31 Self::VectorInverted => "vector.inverted",
32 Self::GraphAdjacency => "graph.adjacency",
33 Self::FullText => "text.fulltext",
34 Self::DocumentPathValue => "document.pathvalue",
35 Self::HybridSearch => "search.hybrid",
36 }
37 }
38}
39
40#[derive(Debug, Clone)]
41pub struct IndexConfig {
42 pub name: String,
43 pub kind: IndexKind,
44 pub enabled: bool,
45 pub warmup: bool,
46 pub updated_at_ms: u128,
47}
48
49impl IndexConfig {
50 pub fn new(name: impl Into<String>, kind: IndexKind) -> Self {
51 Self {
52 name: name.into(),
53 kind,
54 enabled: true,
55 warmup: false,
56 updated_at_ms: SystemTime::now()
57 .duration_since(UNIX_EPOCH)
58 .unwrap_or_default()
59 .as_millis(),
60 }
61 }
62
63 pub fn disabled(mut self) -> Self {
64 self.enabled = false;
65 self
66 }
67
68 pub fn with_warmup(mut self, warmup: bool) -> Self {
69 self.warmup = warmup;
70 self
71 }
72}
73
74#[derive(Debug, Clone, Default)]
75pub struct IndexStats {
76 pub memory_bytes: u64,
77 pub entries: usize,
78 pub queries: u64,
79 pub builds: u64,
80 pub errors: u64,
81}
82
83#[derive(Debug, Clone)]
84pub struct IndexMetric {
85 pub name: String,
86 pub kind: IndexKind,
87 pub enabled: bool,
88 pub last_refresh_ms: Option<u128>,
89 pub stats: IndexStats,
90}
91
92#[derive(Debug, Default)]
93pub struct IndexCatalog {
94 metrics: BTreeMap<String, IndexMetric>,
95}
96
97impl IndexCatalog {
98 pub fn register(&mut self, cfg: IndexConfig) {
99 let metric = IndexMetric {
100 name: cfg.name.clone(),
101 kind: cfg.kind,
102 enabled: cfg.enabled,
103 last_refresh_ms: Some(cfg.updated_at_ms),
104 stats: IndexStats::default(),
105 };
106 self.metrics.insert(cfg.name, metric);
107 }
108
109 pub fn snapshot(&self) -> Vec<IndexMetric> {
110 self.metrics.values().cloned().collect()
111 }
112
113 pub fn enabled(&self) -> Vec<String> {
114 self.metrics
115 .iter()
116 .filter_map(|(name, metric)| metric.enabled.then_some(name.clone()))
117 .collect()
118 }
119
120 pub fn disable(&mut self, name: &str) -> bool {
121 if let Some(metric) = self.metrics.get_mut(name) {
122 metric.enabled = false;
123 }
124
125 self.metrics.contains_key(name)
126 }
127
128 pub fn touch(&mut self, name: &str) {
129 if let Some(metric) = self.metrics.get_mut(name) {
130 metric.last_refresh_ms = Some(
131 SystemTime::now()
132 .duration_since(UNIX_EPOCH)
133 .unwrap_or_default()
134 .as_millis(),
135 );
136 }
137 }
138
139 pub fn register_default_vector_graph(table: bool, graph: bool) -> Self {
140 let mut catalog = Self::default();
141 if table {
142 catalog.register(IndexConfig::new("metadata-btree", IndexKind::BTree));
143 }
144 if graph {
145 catalog.register(IndexConfig::new(
146 "graph-adjacency",
147 IndexKind::GraphAdjacency,
148 ));
149 }
150 catalog.register(IndexConfig::new("vector-hnsw", IndexKind::VectorHnsw).with_warmup(true));
151 catalog.register(IndexConfig::new(
152 "vector-inverted",
153 IndexKind::VectorInverted,
154 ));
155 catalog
156 }
157}
158
159pub trait IndexRuntime {
160 fn describe(&self) -> Vec<IndexMetric>;
161 fn apply_metric(&mut self, metric: IndexMetric);
162}
163
164impl IndexRuntime for IndexCatalog {
165 fn describe(&self) -> Vec<IndexMetric> {
166 self.snapshot()
167 }
168
169 fn apply_metric(&mut self, metric: IndexMetric) {
170 self.metrics.insert(metric.name.clone(), metric);
171 }
172}
173
174pub type IndexCatalogSnapshot = Vec<IndexMetric>;