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        // List property (hash) indexes
104        let prop_index = self.property_index.read();
105        for (label, property) in prop_index.indexed_properties() {
106            let cardinality = prop_index.cardinality(&label, &property).unwrap_or(0);
107            indexes.push(IndexInfo {
108                label,
109                property,
110                index_type: "hash".to_string(),
111                cardinality,
112                memory_bytes: 0, // Approximation
113            });
114        }
115
116        // List range indexes
117        let range_idx = self.range_index.read();
118        for (label, property) in range_idx.indexed_properties() {
119            indexes.push(IndexInfo {
120                label,
121                property,
122                index_type: "range".to_string(),
123                cardinality: 0, // Range indexes don't track cardinality the same way
124                memory_bytes: 0,
125            });
126        }
127
128        indexes
129    }
130
131    /// Drop an index (either property or range).
132    ///
133    /// # Arguments
134    ///
135    /// * `label` - Node label
136    /// * `property` - Property name
137    ///
138    /// # Returns
139    ///
140    /// Ok(true) if an index was dropped, Ok(false) if no index existed.
141    ///
142    /// # Errors
143    ///
144    /// Returns an error if underlying index stores fail while dropping.
145    pub fn drop_index(&self, label: &str, property: &str) -> Result<bool> {
146        // Try property index first
147        let dropped_prop = self.property_index.write().drop_index(label, property);
148        if dropped_prop {
149            return Ok(true);
150        }
151
152        // Try range index
153        let dropped_range = self.range_index.write().drop_index(label, property);
154        Ok(dropped_range)
155    }
156
157    /// Get total memory usage of all indexes.
158    #[must_use]
159    pub fn indexes_memory_usage(&self) -> usize {
160        let prop_mem = self.property_index.read().memory_usage();
161        let range_mem = self.range_index.read().memory_usage();
162        prop_mem + range_mem
163    }
164}