Skip to main content

nodedb_types/
vector_model.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Per-column vector model metadata for embedding tracking and migration.
4//!
5//! Stored in the system catalog keyed by
6//! `(database_id, tenant_id, collection, column)`. Informational by default;
7//! optional strict dimension enforcement.
8
9use serde::{Deserialize, Serialize};
10
11/// Metadata about the embedding model that produced vectors in a column.
12///
13/// Stored in the system catalog via `ALTER COLLECTION x SET VECTOR METADATA ON column (...)`.
14/// Retrieved via `SELECT VECTOR_METADATA('collection', 'column')` or `SHOW VECTOR MODELS`.
15#[derive(
16    Debug, Clone, Serialize, Deserialize, zerompk::ToMessagePack, zerompk::FromMessagePack,
17)]
18pub struct VectorModelMetadata {
19    /// Embedding model identifier (e.g. "text-embedding-3-large", "all-MiniLM-L6-v2").
20    pub model: String,
21    /// Expected vector dimensionality for this model.
22    pub dimensions: usize,
23    /// ISO-8601 date when the metadata was set (informational).
24    pub created_at: String,
25    /// If true, INSERT rejects vectors whose dimension doesn't match `dimensions`.
26    /// Default: false (no enforcement — metadata only).
27    #[serde(default)]
28    pub strict_dimensions: bool,
29}
30
31/// Catalog entry for vector model metadata.
32///
33/// Wraps `VectorModelMetadata` with the key fields needed for catalog storage
34/// and the `_system.vector_models` view.
35#[derive(
36    Debug, Clone, Serialize, Deserialize, zerompk::ToMessagePack, zerompk::FromMessagePack,
37)]
38pub struct VectorModelEntry {
39    /// Database that owns the collection.
40    pub database_id: u64,
41    /// Tenant that owns this collection.
42    pub tenant_id: u64,
43    /// Collection name.
44    pub collection: String,
45    /// Vector column name.
46    pub column: String,
47    /// The model metadata.
48    pub metadata: VectorModelMetadata,
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn serde_roundtrip() {
57        let entry = VectorModelEntry {
58            database_id: 5,
59            tenant_id: 1,
60            collection: "chunks".into(),
61            column: "embedding".into(),
62            metadata: VectorModelMetadata {
63                model: "text-embedding-3-large".into(),
64                dimensions: 1536,
65                created_at: "2026-03-01".into(),
66                strict_dimensions: false,
67            },
68        };
69        let bytes = zerompk::to_msgpack_vec(&entry).unwrap();
70        let restored: VectorModelEntry = zerompk::from_msgpack(&bytes).unwrap();
71        assert_eq!(restored.database_id, 5);
72        assert_eq!(restored.metadata.model, "text-embedding-3-large");
73        assert_eq!(restored.metadata.dimensions, 1536);
74        assert!(!restored.metadata.strict_dimensions);
75    }
76
77    #[test]
78    fn strict_dimensions_default_false() {
79        // Deserialize without strict_dimensions field → defaults to false.
80        let meta = VectorModelMetadata {
81            model: "test".into(),
82            dimensions: 128,
83            created_at: "2026-01-01".into(),
84            strict_dimensions: false,
85        };
86        let bytes = zerompk::to_msgpack_vec(&meta).unwrap();
87        let restored: VectorModelMetadata = zerompk::from_msgpack(&bytes).unwrap();
88        assert!(!restored.strict_dimensions);
89    }
90}