Skip to main content

velesdb_core/collection/core/
index_management.rs

1//! Index management methods for Collection (EPIC-009 propagation).
2
3use crate::collection::types::Collection;
4use crate::error::Result;
5use crate::index::{JsonValue, SecondaryIndex};
6use parking_lot::RwLock;
7use std::collections::BTreeMap;
8
9/// Index information response for API.
10#[derive(Debug, Clone)]
11pub struct IndexInfo {
12    /// Node label.
13    pub label: String,
14    /// Property name.
15    pub property: String,
16    /// Index type (hash or range).
17    pub index_type: String,
18    /// Number of unique values indexed.
19    pub cardinality: usize,
20    /// Memory usage in bytes.
21    pub memory_bytes: usize,
22}
23
24impl Collection {
25    /// Creates a secondary metadata index for a payload field.
26    ///
27    /// # Errors
28    ///
29    /// Returns Ok(()) on success. Index creation is idempotent.
30    pub fn create_index(&self, field_name: &str) -> Result<()> {
31        let mut indexes = self.secondary_indexes.write();
32        indexes
33            .entry(field_name.to_string())
34            .or_insert_with(|| SecondaryIndex::BTree(RwLock::new(BTreeMap::new())));
35        Ok(())
36    }
37
38    /// Checks whether a secondary metadata index exists for a field.
39    #[must_use]
40    pub fn has_secondary_index(&self, field_name: &str) -> bool {
41        self.secondary_indexes.read().contains_key(field_name)
42    }
43
44    /// Looks up matching point IDs for an indexed field value.
45    #[must_use]
46    pub fn secondary_index_lookup(&self, field_name: &str, value: &JsonValue) -> Option<Vec<u64>> {
47        let indexes = self.secondary_indexes.read();
48        let index = indexes.get(field_name)?;
49        match index {
50            SecondaryIndex::BTree(tree) => tree.read().get(value).cloned(),
51        }
52    }
53
54    /// Create a property index for O(1) equality lookups.
55    ///
56    /// # Arguments
57    ///
58    /// * `label` - Node label to index (e.g., "Person")
59    /// * `property` - Property name to index (e.g., "email")
60    ///
61    /// # Errors
62    ///
63    /// Returns Ok(()) on success. Index creation is idempotent.
64    pub fn create_property_index(&self, label: &str, property: &str) -> Result<()> {
65        let mut index = self.property_index.write();
66        index.create_index(label, property);
67        Ok(())
68    }
69
70    /// Create a range index for O(log n) range queries.
71    ///
72    /// # Arguments
73    ///
74    /// * `label` - Node label to index (e.g., "Event")
75    /// * `property` - Property name to index (e.g., "timestamp")
76    ///
77    /// # Errors
78    ///
79    /// Returns Ok(()) on success. Index creation is idempotent.
80    pub fn create_range_index(&self, label: &str, property: &str) -> Result<()> {
81        let mut index = self.range_index.write();
82        index.create_index(label, property);
83        Ok(())
84    }
85
86    /// Check if a property index exists.
87    #[must_use]
88    pub fn has_property_index(&self, label: &str, property: &str) -> bool {
89        self.property_index.read().has_index(label, property)
90    }
91
92    /// Check if a range index exists.
93    #[must_use]
94    pub fn has_range_index(&self, label: &str, property: &str) -> bool {
95        self.range_index.read().has_index(label, property)
96    }
97
98    /// List all indexes on this collection.
99    #[must_use]
100    pub fn list_indexes(&self) -> Vec<IndexInfo> {
101        let mut indexes = Vec::new();
102
103        // LOCK ORDER: property_index(7) read — then range_index(7) read.
104        // Same level, reads-only; canonical order prevents deadlock.
105        let prop_index = self.property_index.read();
106        for (label, property) in prop_index.indexed_properties() {
107            let cardinality = prop_index.cardinality(&label, &property).unwrap_or(0);
108            indexes.push(IndexInfo {
109                label,
110                property,
111                index_type: "hash".to_string(),
112                cardinality,
113                memory_bytes: 0, // Approximation
114            });
115        }
116
117        // List range indexes
118        let range_idx = self.range_index.read();
119        for (label, property) in range_idx.indexed_properties() {
120            indexes.push(IndexInfo {
121                label,
122                property,
123                index_type: "range".to_string(),
124                cardinality: 0, // Range indexes don't track cardinality the same way
125                memory_bytes: 0,
126            });
127        }
128
129        indexes
130    }
131
132    /// Drop an index (either property or range).
133    ///
134    /// # Arguments
135    ///
136    /// * `label` - Node label
137    /// * `property` - Property name
138    ///
139    /// # Returns
140    ///
141    /// Ok(true) if an index was dropped, Ok(false) if no index existed.
142    ///
143    /// # Errors
144    ///
145    /// Returns an error if underlying index stores fail while dropping.
146    pub fn drop_index(&self, label: &str, property: &str) -> Result<bool> {
147        // Try property index first
148        let dropped_prop = self.property_index.write().drop_index(label, property);
149        if dropped_prop {
150            return Ok(true);
151        }
152
153        // Try range index
154        let dropped_range = self.range_index.write().drop_index(label, property);
155        Ok(dropped_range)
156    }
157
158    /// Get total memory usage of all indexes.
159    #[must_use]
160    pub fn indexes_memory_usage(&self) -> usize {
161        // LOCK ORDER: property_index(7) read — then range_index(7) read.
162        // Same level, reads-only; canonical order prevents deadlock.
163        let prop_mem = self.property_index.read().memory_usage();
164        let range_mem = self.range_index.read().memory_usage();
165        prop_mem + range_mem
166    }
167}