1use super::schema_events::{SchemaChangeCallback, SchemaChangeEvent};
2use crate::event_dispatch::{EventFilter, EventSubscriptions, SubscriptionId};
3use crate::types::Index;
4use crate::StorageError;
5use parking_lot::RwLock;
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::path::Path;
9use std::sync::atomic::{AtomicU64, Ordering};
10use std::sync::Arc;
11
12const INDEX_FORMAT_VERSION: u32 = 1;
13
14#[derive(Serialize, Deserialize)]
15struct IndexSnapshot {
16 version: u32,
17 tag_indexes: Vec<(u64, String, Index)>,
18 edge_indexes: Vec<(u64, String, Index)>,
19}
20
21use crate::types::IndexStatus;
22
23pub trait IndexMetadataManager: Send + Sync + std::fmt::Debug {
24 fn create_tag_index(&self, space_id: u64, index: &Index) -> Result<bool, StorageError>;
25 fn drop_tag_index(&self, space_id: u64, index_name: &str) -> Result<bool, StorageError>;
26 fn get_tag_index(&self, space_id: u64, index_name: &str)
27 -> Result<Option<Index>, StorageError>;
28 fn list_tag_indexes(&self, space_id: u64) -> Result<Vec<Index>, StorageError>;
29 fn drop_tag_indexes_by_tag(
30 &self,
31 space_id: u64,
32 tag_name: &str,
33 ) -> Result<Vec<String>, StorageError>;
34
35 fn create_edge_index(&self, space_id: u64, index: &Index) -> Result<bool, StorageError>;
36 fn drop_edge_index(&self, space_id: u64, index_name: &str) -> Result<bool, StorageError>;
37 fn get_edge_index(
38 &self,
39 space_id: u64,
40 index_name: &str,
41 ) -> Result<Option<Index>, StorageError>;
42 fn list_edge_indexes(&self, space_id: u64) -> Result<Vec<Index>, StorageError>;
43 fn drop_edge_indexes_by_type(
44 &self,
45 space_id: u64,
46 edge_type: &str,
47 ) -> Result<Vec<String>, StorageError>;
48
49 fn set_tag_index_status(
51 &self,
52 space_id: u64,
53 index_name: &str,
54 status: IndexStatus,
55 ) -> Result<bool, StorageError>;
56
57 fn set_edge_index_status(
59 &self,
60 space_id: u64,
61 index_name: &str,
62 status: IndexStatus,
63 ) -> Result<bool, StorageError>;
64}
65
66pub struct IndexManager {
67 tag_indexes: Arc<RwLock<HashMap<(u64, String), Index>>>,
68 edge_indexes: Arc<RwLock<HashMap<(u64, String), Index>>>,
69 next_index_id: AtomicU64,
70 schema_callbacks: Arc<EventSubscriptions<SchemaChangeEvent>>,
71}
72
73impl std::fmt::Debug for IndexManager {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 f.debug_struct("IndexManager")
76 .field("tag_indexes_count", &self.tag_indexes.read().len())
77 .field("edge_indexes_count", &self.edge_indexes.read().len())
78 .finish()
79 }
80}
81
82impl IndexManager {
83 pub fn new() -> Self {
84 Self {
85 tag_indexes: Arc::new(RwLock::new(HashMap::new())),
86 edge_indexes: Arc::new(RwLock::new(HashMap::new())),
87 next_index_id: AtomicU64::new(1),
88 schema_callbacks: Arc::new(EventSubscriptions::new()),
89 }
90 }
91
92 pub fn with_shared_schema_callbacks(
98 shared: Arc<EventSubscriptions<SchemaChangeEvent>>,
99 ) -> Self {
100 Self {
101 tag_indexes: Arc::new(RwLock::new(HashMap::new())),
102 edge_indexes: Arc::new(RwLock::new(HashMap::new())),
103 next_index_id: AtomicU64::new(1),
104 schema_callbacks: shared,
105 }
106 }
107
108 pub fn shared_schema_callbacks(&self) -> Arc<EventSubscriptions<SchemaChangeEvent>> {
110 Arc::clone(&self.schema_callbacks)
111 }
112
113 pub fn register_schema_callback(&self, callback: SchemaChangeCallback) -> SubscriptionId {
118 self.schema_callbacks.add(callback)
119 }
120
121 pub fn register_schema_callback_filtered(
123 &self,
124 callback: SchemaChangeCallback,
125 filter: EventFilter<SchemaChangeEvent>,
126 ) -> SubscriptionId {
127 self.schema_callbacks.add_filtered(callback, Some(filter))
128 }
129
130 pub fn unregister_schema_callback(&self, id: SubscriptionId) -> bool {
132 self.schema_callbacks.remove(id)
133 }
134
135 pub fn schema_callback_count(&self) -> usize {
137 self.schema_callbacks.len()
138 }
139
140 fn emit_schema_event(&self, event: SchemaChangeEvent) {
141 if self.schema_callbacks.is_empty() {
142 return;
143 }
144 self.schema_callbacks.dispatch("schema", &event);
145 }
146
147 pub fn save_indexes(&self, path: &Path) -> Result<(), StorageError> {
148 use std::fs::{self, File};
149 use std::io::Write;
150
151 if let Some(parent) = path.parent() {
152 fs::create_dir_all(parent).map_err(|e| StorageError::io_error(e.to_string()))?;
153 }
154
155 let tag_indexes: Vec<(u64, String, Index)> = self
156 .tag_indexes
157 .read()
158 .iter()
159 .map(|((space_id, name), index)| (*space_id, name.clone(), index.clone()))
160 .collect();
161
162 let edge_indexes: Vec<(u64, String, Index)> = self
163 .edge_indexes
164 .read()
165 .iter()
166 .map(|((space_id, name), index)| (*space_id, name.clone(), index.clone()))
167 .collect();
168
169 let snapshot = IndexSnapshot {
170 version: INDEX_FORMAT_VERSION,
171 tag_indexes,
172 edge_indexes,
173 };
174
175 let json = serde_json::to_string_pretty(&snapshot)
176 .map_err(|e| StorageError::serialize_error(e.to_string()))?;
177
178 let mut file = File::create(path).map_err(|e| StorageError::io_error(e.to_string()))?;
179 file.write_all(json.as_bytes())
180 .map_err(|e| StorageError::io_error(e.to_string()))?;
181
182 Ok(())
183 }
184
185 pub fn load_indexes(&self, path: &Path) -> Result<(), StorageError> {
186 use std::fs::File;
187 use std::io::Read;
188
189 if !path.exists() {
190 return Ok(());
191 }
192
193 let mut file = File::open(path).map_err(|e| StorageError::io_error(e.to_string()))?;
194 let mut json = String::new();
195 file.read_to_string(&mut json)
196 .map_err(|e| StorageError::io_error(e.to_string()))?;
197
198 let snapshot: IndexSnapshot = serde_json::from_str(&json)
199 .map_err(|e| StorageError::deserialize_error(e.to_string()))?;
200
201 if snapshot.version > INDEX_FORMAT_VERSION {
202 return Err(StorageError::deserialize_error(format!(
203 "Index snapshot version {} is newer than supported version {}",
204 snapshot.version, INDEX_FORMAT_VERSION
205 )));
206 }
207
208 self.tag_indexes.write().clear();
209 self.edge_indexes.write().clear();
210
211 let mut max_id = 0;
212 for (space_id, name, index) in snapshot.tag_indexes {
213 if index.id > max_id {
214 max_id = index.id;
215 }
216 self.tag_indexes.write().insert((space_id, name), index);
217 }
218
219 for (space_id, name, index) in snapshot.edge_indexes {
220 if index.id > max_id {
221 max_id = index.id;
222 }
223 self.edge_indexes.write().insert((space_id, name), index);
224 }
225
226 self.next_index_id.store(max_id + 1, Ordering::SeqCst);
227
228 Ok(())
229 }
230}
231
232impl Default for IndexManager {
233 fn default() -> Self {
234 Self::new()
235 }
236}
237
238impl IndexMetadataManager for IndexManager {
239 fn create_tag_index(&self, space_id: u64, index: &Index) -> Result<bool, StorageError> {
240 let created = {
241 let mut indexes = self.tag_indexes.write();
242 let key = (space_id, index.name.clone());
243 if indexes.contains_key(&key) {
244 return Ok(false);
245 }
246 let mut index_with_space_id = index.clone();
247 index_with_space_id.space_id = space_id;
248 if index_with_space_id.id == 0 {
249 index_with_space_id.id = self.next_index_id.fetch_add(1, Ordering::SeqCst);
250 }
251 indexes.insert(key, index_with_space_id);
252 true
253 };
254 if created {
255 self.emit_schema_event(SchemaChangeEvent::TagIndexCreated {
256 space_id,
257 index_name: index.name.clone(),
258 });
259 }
260 Ok(created)
261 }
262
263 fn drop_tag_index(&self, space_id: u64, index_name: &str) -> Result<bool, StorageError> {
264 let removed = {
265 let mut indexes = self.tag_indexes.write();
266 let key = (space_id, index_name.to_string());
267 indexes.remove(&key).is_some()
268 };
269 if removed {
270 self.emit_schema_event(SchemaChangeEvent::TagIndexDropped {
271 space_id,
272 index_name: index_name.to_string(),
273 });
274 }
275 Ok(removed)
276 }
277
278 fn get_tag_index(
279 &self,
280 space_id: u64,
281 index_name: &str,
282 ) -> Result<Option<Index>, StorageError> {
283 let indexes = self.tag_indexes.read();
284 Ok(indexes.get(&(space_id, index_name.to_string())).cloned())
285 }
286
287 fn list_tag_indexes(&self, space_id: u64) -> Result<Vec<Index>, StorageError> {
288 let indexes = self.tag_indexes.read();
289 Ok(indexes
290 .iter()
291 .filter(|((sid, _), _)| *sid == space_id)
292 .map(|(_, index)| index.clone())
293 .collect())
294 }
295
296 fn drop_tag_indexes_by_tag(
297 &self,
298 space_id: u64,
299 tag_name: &str,
300 ) -> Result<Vec<String>, StorageError> {
301 let removed: Vec<String> = {
302 let mut indexes = self.tag_indexes.write();
303 let mut removed = Vec::new();
304 indexes.retain(|_, index| {
305 let drop_it = index.space_id == space_id && index.schema_name == tag_name;
306 if drop_it {
307 removed.push(index.name.clone());
308 }
309 !drop_it
310 });
311 removed
312 };
313 for index_name in &removed {
314 self.emit_schema_event(SchemaChangeEvent::TagIndexDropped {
315 space_id,
316 index_name: index_name.clone(),
317 });
318 }
319 Ok(removed)
320 }
321
322 fn create_edge_index(&self, space_id: u64, index: &Index) -> Result<bool, StorageError> {
323 let created = {
324 let mut indexes = self.edge_indexes.write();
325 let key = (space_id, index.name.clone());
326 if indexes.contains_key(&key) {
327 return Ok(false);
328 }
329 let mut index_with_space_id = index.clone();
330 index_with_space_id.space_id = space_id;
331 if index_with_space_id.id == 0 {
332 index_with_space_id.id = self.next_index_id.fetch_add(1, Ordering::SeqCst);
333 }
334 indexes.insert(key, index_with_space_id);
335 true
336 };
337 if created {
338 self.emit_schema_event(SchemaChangeEvent::EdgeIndexCreated {
339 space_id,
340 index_name: index.name.clone(),
341 });
342 }
343 Ok(created)
344 }
345
346 fn drop_edge_index(&self, space_id: u64, index_name: &str) -> Result<bool, StorageError> {
347 let removed = {
348 let mut indexes = self.edge_indexes.write();
349 let key = (space_id, index_name.to_string());
350 indexes.remove(&key).is_some()
351 };
352 if removed {
353 self.emit_schema_event(SchemaChangeEvent::EdgeIndexDropped {
354 space_id,
355 index_name: index_name.to_string(),
356 });
357 }
358 Ok(removed)
359 }
360
361 fn get_edge_index(
362 &self,
363 space_id: u64,
364 index_name: &str,
365 ) -> Result<Option<Index>, StorageError> {
366 let indexes = self.edge_indexes.read();
367 Ok(indexes.get(&(space_id, index_name.to_string())).cloned())
368 }
369
370 fn list_edge_indexes(&self, space_id: u64) -> Result<Vec<Index>, StorageError> {
371 let indexes = self.edge_indexes.read();
372 Ok(indexes
373 .iter()
374 .filter(|((sid, _), _)| *sid == space_id)
375 .map(|(_, index)| index.clone())
376 .collect())
377 }
378
379 fn drop_edge_indexes_by_type(
380 &self,
381 space_id: u64,
382 edge_type: &str,
383 ) -> Result<Vec<String>, StorageError> {
384 let removed: Vec<String> = {
385 let mut indexes = self.edge_indexes.write();
386 let mut removed = Vec::new();
387 indexes.retain(|_, index| {
388 let drop_it = index.space_id == space_id && index.schema_name == edge_type;
389 if drop_it {
390 removed.push(index.name.clone());
391 }
392 !drop_it
393 });
394 removed
395 };
396 for index_name in &removed {
397 self.emit_schema_event(SchemaChangeEvent::EdgeIndexDropped {
398 space_id,
399 index_name: index_name.clone(),
400 });
401 }
402 Ok(removed)
403 }
404
405 fn set_tag_index_status(
406 &self,
407 space_id: u64,
408 index_name: &str,
409 status: IndexStatus,
410 ) -> Result<bool, StorageError> {
411 let mut indexes = self.tag_indexes.write();
412 let key = (space_id, index_name.to_string());
413 if let Some(index) = indexes.get_mut(&key) {
414 index.status = status;
415 Ok(true)
416 } else {
417 Ok(false)
418 }
419 }
420
421 fn set_edge_index_status(
422 &self,
423 space_id: u64,
424 index_name: &str,
425 status: IndexStatus,
426 ) -> Result<bool, StorageError> {
427 let mut indexes = self.edge_indexes.write();
428 let key = (space_id, index_name.to_string());
429 if let Some(index) = indexes.get_mut(&key) {
430 index.status = status;
431 Ok(true)
432 } else {
433 Ok(false)
434 }
435 }
436}
437
438#[cfg(test)]
439mod tests {
440 use super::*;
441 use crate::types::{IndexConfig, IndexType};
442 use std::sync::atomic::{AtomicUsize, Ordering};
443
444 fn tag_index(name: &str, schema: &str) -> Index {
445 Index::new(IndexConfig {
446 id: 0,
447 name: name.to_string(),
448 space_id: 1,
449 schema_name: schema.to_string(),
450 fields: Vec::new(),
451 properties: Vec::new(),
452 index_type: IndexType::TagIndex,
453 is_unique: false,
454 covering: false,
455 partial_condition: None,
456 })
457 }
458
459 #[test]
460 fn batch_drop_emits_one_event_per_index() {
461 let manager = IndexManager::new();
462 manager
463 .create_tag_index(1, &tag_index("idx_a", "Person"))
464 .unwrap();
465 manager
466 .create_tag_index(1, &tag_index("idx_b", "Person"))
467 .unwrap();
468
469 let hits = Arc::new(AtomicUsize::new(0));
470 let probe = Arc::clone(&hits);
471 manager.register_schema_callback(Arc::new(move |event| {
472 if matches!(event, SchemaChangeEvent::TagIndexDropped { .. }) {
473 probe.fetch_add(1, Ordering::SeqCst);
474 }
475 }));
476
477 manager.drop_tag_indexes_by_tag(1, "Person").unwrap();
478 assert_eq!(hits.load(Ordering::SeqCst), 2);
479 assert!(manager.list_tag_indexes(1).unwrap().is_empty());
480 let removed = manager.drop_tag_indexes_by_tag(1, "Person").unwrap();
481 assert!(removed.is_empty());
482 }
483
484 #[test]
485 fn unregister_stops_delivery() {
486 let manager = IndexManager::new();
487 let hits = Arc::new(AtomicUsize::new(0));
488 let probe = Arc::clone(&hits);
489 let id = manager.register_schema_callback(Arc::new(move |_| {
490 probe.fetch_add(1, Ordering::SeqCst);
491 }));
492 assert!(manager.unregister_schema_callback(id));
493 manager
494 .create_tag_index(1, &tag_index("idx_c", "Person"))
495 .unwrap();
496 assert_eq!(hits.load(Ordering::SeqCst), 0);
497 }
498
499 #[test]
500 fn shared_registry_receives_schema_and_index_halves_once() {
501 use super::super::schema_events::SchemaChangeEvent as SharedSchemaEvent;
502 use crate::event_dispatch::EventSubscriptions;
503 use crate::metadata::SchemaManager;
504 use crate::types::SpaceInfo;
505
506 let shared = Arc::new(EventSubscriptions::<SharedSchemaEvent>::new());
507 let schema = SchemaManager::with_shared_schema_callbacks(Arc::clone(&shared));
508 let indexes = IndexManager::with_shared_schema_callbacks(shared);
509 assert!(Arc::ptr_eq(
510 &schema.shared_schema_callbacks(),
511 &indexes.shared_schema_callbacks()
512 ));
513
514 let spaces = Arc::new(AtomicUsize::new(0));
515 let idx_events = Arc::new(AtomicUsize::new(0));
516 let space_probe = Arc::clone(&spaces);
517 let idx_probe = Arc::clone(&idx_events);
518 schema.register_schema_callback(Arc::new(move |event| match event {
520 SharedSchemaEvent::SpaceCreated { .. } => {
521 space_probe.fetch_add(1, Ordering::SeqCst);
522 }
523 SharedSchemaEvent::TagIndexCreated { .. } => {
524 idx_probe.fetch_add(1, Ordering::SeqCst);
525 }
526 _ => {}
527 }));
528
529 let mut space = SpaceInfo::new("shared_space".to_string());
530 assert!(schema.create_space(&mut space).unwrap());
531 indexes
532 .create_tag_index(space.space_id, &tag_index("idx_shared", "Person"))
533 .unwrap();
534
535 assert_eq!(spaces.load(Ordering::SeqCst), 1);
536 assert_eq!(idx_events.load(Ordering::SeqCst), 1);
537 assert_eq!(schema.schema_callback_count(), 1);
539 assert_eq!(indexes.schema_callback_count(), 1);
540 }
541}