1use crate::error::{DbxError, DbxResult};
6use std::collections::HashMap;
7use std::sync::RwLock;
8
9#[derive(Debug, Clone)]
11pub struct IndexMeta {
12 pub name: String,
14 pub table: String,
16 pub columns: Vec<String>,
18 pub index_type: IndexType,
20 pub version: u64,
22 pub status: IndexStatus,
24}
25
26#[derive(Debug, Clone, PartialEq)]
28pub enum IndexType {
29 Hash,
30 BTree,
31 Bitmap,
32}
33
34#[derive(Debug, Clone, PartialEq)]
36pub enum IndexStatus {
37 Building,
39 Ready,
41 Disabled,
43}
44
45pub struct IndexVersionManager {
52 versions: RwLock<HashMap<String, Vec<IndexMeta>>>,
54 active: RwLock<HashMap<String, u64>>,
56}
57
58impl IndexVersionManager {
59 pub fn new() -> Self {
60 Self {
61 versions: RwLock::new(HashMap::new()),
62 active: RwLock::new(HashMap::new()),
63 }
64 }
65
66 pub fn create_index(
68 &self,
69 name: &str,
70 table: &str,
71 columns: Vec<String>,
72 index_type: IndexType,
73 ) -> DbxResult<u64> {
74 let meta = IndexMeta {
75 name: name.to_string(),
76 table: table.to_string(),
77 columns,
78 index_type,
79 version: 1,
80 status: IndexStatus::Ready,
81 };
82
83 let mut versions = self
84 .versions
85 .write()
86 .map_err(|_| DbxError::Serialization("Lock poisoned".into()))?;
87 let mut active = self
88 .active
89 .write()
90 .map_err(|_| DbxError::Serialization("Lock poisoned".into()))?;
91
92 versions.insert(name.to_string(), vec![meta]);
93 active.insert(name.to_string(), 1);
94
95 Ok(1)
96 }
97
98 pub fn start_reindex(
100 &self,
101 name: &str,
102 columns: Vec<String>,
103 index_type: IndexType,
104 ) -> DbxResult<u64> {
105 let mut versions = self
106 .versions
107 .write()
108 .map_err(|_| DbxError::Serialization("Lock poisoned".into()))?;
109 let history = versions
110 .get_mut(name)
111 .ok_or_else(|| DbxError::Serialization(format!("Index {name} not found")))?;
112
113 let last = history
114 .last()
115 .ok_or_else(|| DbxError::Serialization("Empty history".into()))?;
116 let new_version = last.version + 1;
117
118 history.push(IndexMeta {
119 name: name.to_string(),
120 table: last.table.clone(),
121 columns,
122 index_type,
123 version: new_version,
124 status: IndexStatus::Building,
125 });
126
127 Ok(new_version)
128 }
129
130 pub fn complete_reindex(&self, name: &str, version: u64) -> DbxResult<()> {
132 let mut versions = self
133 .versions
134 .write()
135 .map_err(|_| DbxError::Serialization("Lock poisoned".into()))?;
136 let mut active = self
137 .active
138 .write()
139 .map_err(|_| DbxError::Serialization("Lock poisoned".into()))?;
140
141 let history = versions
142 .get_mut(name)
143 .ok_or_else(|| DbxError::Serialization(format!("Index {name} not found")))?;
144
145 for meta in history.iter_mut() {
146 if meta.version == version {
147 meta.status = IndexStatus::Ready;
148 } else if meta.status == IndexStatus::Ready {
149 meta.status = IndexStatus::Disabled;
150 }
151 }
152
153 active.insert(name.to_string(), version);
154 Ok(())
155 }
156
157 pub fn get_active(&self, name: &str) -> DbxResult<IndexMeta> {
159 let versions = self
160 .versions
161 .read()
162 .map_err(|_| DbxError::Serialization("Lock poisoned".into()))?;
163 let active = self
164 .active
165 .read()
166 .map_err(|_| DbxError::Serialization("Lock poisoned".into()))?;
167
168 let active_ver = active
169 .get(name)
170 .ok_or_else(|| DbxError::Serialization(format!("Index {name} not found")))?;
171 let history = versions
172 .get(name)
173 .ok_or_else(|| DbxError::Serialization(format!("Index {name} not found")))?;
174
175 history
176 .iter()
177 .find(|m| m.version == *active_ver)
178 .cloned()
179 .ok_or_else(|| DbxError::Serialization(format!("Version {active_ver} not found")))
180 }
181
182 pub fn drop_index(&self, name: &str) -> DbxResult<()> {
184 let mut versions = self
185 .versions
186 .write()
187 .map_err(|_| DbxError::Serialization("Lock poisoned".into()))?;
188 let mut active = self
189 .active
190 .write()
191 .map_err(|_| DbxError::Serialization("Lock poisoned".into()))?;
192
193 versions.remove(name);
194 active.remove(name);
195 Ok(())
196 }
197
198 pub fn list_indexes(&self, table: &str) -> DbxResult<Vec<IndexMeta>> {
200 let versions = self
201 .versions
202 .read()
203 .map_err(|_| DbxError::Serialization("Lock poisoned".into()))?;
204 let active = self
205 .active
206 .read()
207 .map_err(|_| DbxError::Serialization("Lock poisoned".into()))?;
208
209 let mut result = Vec::new();
210 for (name, history) in versions.iter() {
211 if let Some(&active_ver) = active.get(name)
212 && let Some(meta) = history
213 .iter()
214 .find(|m| m.version == active_ver && m.table == table)
215 {
216 result.push(meta.clone());
217 }
218 }
219 Ok(result)
220 }
221}
222
223impl Default for IndexVersionManager {
224 fn default() -> Self {
225 Self::new()
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232
233 #[test]
234 fn test_create_index() {
235 let mgr = IndexVersionManager::new();
236 let ver = mgr
237 .create_index(
238 "idx_users_email",
239 "users",
240 vec!["email".into()],
241 IndexType::Hash,
242 )
243 .unwrap();
244 assert_eq!(ver, 1);
245
246 let meta = mgr.get_active("idx_users_email").unwrap();
247 assert_eq!(meta.table, "users");
248 assert_eq!(meta.status, IndexStatus::Ready);
249 }
250
251 #[test]
252 fn test_reindex_zero_downtime() {
253 let mgr = IndexVersionManager::new();
254 mgr.create_index("idx1", "users", vec!["name".into()], IndexType::Hash)
255 .unwrap();
256
257 let v2 = mgr
259 .start_reindex(
260 "idx1",
261 vec!["name".into(), "email".into()],
262 IndexType::BTree,
263 )
264 .unwrap();
265 assert_eq!(v2, 2);
266
267 let active = mgr.get_active("idx1").unwrap();
269 assert_eq!(active.version, 1);
270
271 mgr.complete_reindex("idx1", 2).unwrap();
273 let active = mgr.get_active("idx1").unwrap();
274 assert_eq!(active.version, 2);
275 assert_eq!(active.columns.len(), 2);
276 assert_eq!(active.index_type, IndexType::BTree);
277 }
278
279 #[test]
280 fn test_drop_index() {
281 let mgr = IndexVersionManager::new();
282 mgr.create_index("idx1", "users", vec!["name".into()], IndexType::Hash)
283 .unwrap();
284 mgr.drop_index("idx1").unwrap();
285
286 assert!(mgr.get_active("idx1").is_err());
287 }
288
289 #[test]
290 fn test_list_indexes() {
291 let mgr = IndexVersionManager::new();
292 mgr.create_index("idx1", "users", vec!["name".into()], IndexType::Hash)
293 .unwrap();
294 mgr.create_index("idx2", "users", vec!["email".into()], IndexType::BTree)
295 .unwrap();
296 mgr.create_index("idx3", "orders", vec!["id".into()], IndexType::Hash)
297 .unwrap();
298
299 let user_indexes = mgr.list_indexes("users").unwrap();
300 assert_eq!(user_indexes.len(), 2);
301
302 let order_indexes = mgr.list_indexes("orders").unwrap();
303 assert_eq!(order_indexes.len(), 1);
304 }
305}