Skip to main content

selene_graph/mutator/
vector_index.rs

1//! Vector-index mutation methods for the transaction mutator.
2
3use selene_core::{Change, DbString, HnswIndexConfig, SchemaChange, SchemaVectorIndexKind};
4
5use crate::graph::VectorIndexEntry;
6use crate::{GraphError, GraphResult, Mutator, VectorIndexConfig, VectorIndexKind};
7
8impl<'tx, 'g> Mutator<'tx, 'g> {
9    /// Register a durable node vector index in the active write transaction.
10    ///
11    /// # Errors
12    ///
13    /// Returns [`GraphError::VectorIndexAlreadyExists`] if the pair already
14    /// exists, [`GraphError::VectorIndexInvalidDimension`] when `dimension` is
15    /// zero, or [`GraphError::VectorIndexValueRejected`] if an existing non-null
16    /// value for `(label, property)` is not a vector with `dimension`.
17    pub fn create_vector_index(
18        &mut self,
19        label: DbString,
20        property: DbString,
21        kind: VectorIndexKind,
22        dimension: u32,
23    ) -> GraphResult<()> {
24        self.create_vector_index_named(label, property, kind, dimension, None)
25    }
26
27    /// Register a durable node vector index with optional catalog name.
28    pub fn create_vector_index_named(
29        &mut self,
30        label: DbString,
31        property: DbString,
32        kind: VectorIndexKind,
33        dimension: u32,
34        name: Option<DbString>,
35    ) -> GraphResult<()> {
36        self.create_vector_index_named_with_config(label, property, kind, dimension, name, None)
37    }
38
39    /// Register a durable node vector index with optional HNSW construction config.
40    pub fn create_vector_index_named_with_config(
41        &mut self,
42        label: DbString,
43        property: DbString,
44        kind: VectorIndexKind,
45        dimension: u32,
46        name: Option<DbString>,
47        hnsw_config: Option<HnswIndexConfig>,
48    ) -> GraphResult<()> {
49        self.create_vector_index_named_with_configs(
50            label,
51            property,
52            kind,
53            dimension,
54            name,
55            VectorIndexConfig::new(hnsw_config, None),
56        )
57    }
58
59    /// Register a durable node vector index with optional ANN construction config.
60    pub fn create_vector_index_named_with_configs(
61        &mut self,
62        label: DbString,
63        property: DbString,
64        kind: VectorIndexKind,
65        dimension: u32,
66        name: Option<DbString>,
67        config: VectorIndexConfig,
68    ) -> GraphResult<()> {
69        if self
70            .txn
71            .read()
72            .vector_index
73            .contains_key(&(label.clone(), property.clone()))
74        {
75            return Err(GraphError::VectorIndexAlreadyExists { label, property });
76        }
77        let index = crate::vector_index::build_vector_index_with_configs(
78            self.txn.read(),
79            label.clone(),
80            property.clone(),
81            kind,
82            dimension,
83            config,
84        )?;
85        let hnsw_config = index.hnsw_config();
86        let ivf_config = index.ivf_config();
87        let graph_id = self.txn.read().graph_id();
88        self.txn.guard_mut().vector_index.insert(
89            (label.clone(), property.clone()),
90            VectorIndexEntry::new(index, name.clone()),
91        );
92        self.txn.changes.push(Change::SchemaChanged {
93            graph: graph_id,
94            change: SchemaChange::VectorIndexCreated {
95                label,
96                property,
97                kind: schema_kind_from(kind),
98                dimension,
99                name,
100                hnsw_config,
101                ivf_config,
102            },
103        });
104        Ok(())
105    }
106
107    /// Drop a durable node vector index from the active write transaction.
108    ///
109    /// The operation is idempotent. Dropping an absent index succeeds and emits
110    /// no WAL change.
111    pub fn drop_vector_index(&mut self, label: DbString, property: DbString) -> GraphResult<()> {
112        if !self
113            .txn
114            .read()
115            .vector_index
116            .contains_key(&(label.clone(), property.clone()))
117        {
118            return Ok(());
119        }
120        let graph_id = self.txn.read().graph_id();
121        self.txn
122            .guard_mut()
123            .vector_index
124            .remove(&(label.clone(), property.clone()));
125        self.txn.changes.push(Change::SchemaChanged {
126            graph: graph_id,
127            change: SchemaChange::VectorIndexDropped { label, property },
128        });
129        Ok(())
130    }
131}
132
133const fn schema_kind_from(kind: VectorIndexKind) -> SchemaVectorIndexKind {
134    match kind {
135        VectorIndexKind::Flat => SchemaVectorIndexKind::Flat,
136        VectorIndexKind::HnswSquaredEuclidean => SchemaVectorIndexKind::HnswSquaredEuclidean,
137        VectorIndexKind::HnswCosine => SchemaVectorIndexKind::HnswCosine,
138        VectorIndexKind::HnswNegativeInnerProduct => {
139            SchemaVectorIndexKind::HnswNegativeInnerProduct
140        }
141        VectorIndexKind::IvfSquaredEuclidean => SchemaVectorIndexKind::IvfSquaredEuclidean,
142        VectorIndexKind::IvfCosine => SchemaVectorIndexKind::IvfCosine,
143        VectorIndexKind::IvfNegativeInnerProduct => SchemaVectorIndexKind::IvfNegativeInnerProduct,
144        VectorIndexKind::TurboQuantCosine => SchemaVectorIndexKind::TurboQuantCosine,
145    }
146}
147
148#[cfg(test)]
149#[path = "vector_index/tests.rs"]
150mod tests;