Skip to main content

storage_kit/core/
record.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use uuid::Uuid;
6
7use super::vector::DenseVector;
8use crate::StorageError;
9
10/// Logical record identifier preserved by implementations supporting logical IDs.
11///
12/// Constructing an identifier does not enable ID persistence or lookup on every
13/// backend. Consult the backend contract and handle authoritative operation errors. The supplied LanceDB adapter does not support
14/// logical IDs; store an application key as a normal field and use a filter,
15/// as shown in the [`crate::database`] walkthrough.
16///
17/// # Examples
18///
19/// ```
20/// let id = storage_kit::database::data::RecordId::String("doc-1".into());
21/// assert_eq!(id.as_string_lossy(), "doc-1");
22/// ```
23#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
24pub enum RecordId {
25    /// UTF-8 string id.
26    String(String),
27    /// Unsigned 64-bit id.
28    U64(u64),
29    /// Signed 64-bit id.
30    I64(i64),
31    /// UUID id.
32    Uuid(Uuid),
33    /// Opaque application-defined id that should round-trip as text.
34    Opaque(String),
35}
36
37impl RecordId {
38    /// Converts the identifier into a human-readable string.
39    ///
40    /// # Examples
41    ///
42    /// ```
43    /// let id = storage_kit::database::data::RecordId::U64(42);
44    /// assert_eq!(id.as_string_lossy(), "42");
45    /// ```
46    pub fn as_string_lossy(&self) -> String {
47        match self {
48            Self::String(v) => v.clone(),
49            Self::U64(v) => v.to_string(),
50            Self::I64(v) => v.to_string(),
51            Self::Uuid(v) => v.to_string(),
52            Self::Opaque(v) => v.clone(),
53        }
54    }
55}
56
57impl From<&str> for RecordId {
58    fn from(value: &str) -> Self {
59        Self::String(value.into())
60    }
61}
62
63impl From<String> for RecordId {
64    fn from(value: String) -> Self {
65        Self::String(value)
66    }
67}
68
69impl From<u64> for RecordId {
70    fn from(value: u64) -> Self {
71        Self::U64(value)
72    }
73}
74
75impl From<i64> for RecordId {
76    fn from(value: i64) -> Self {
77        Self::I64(value)
78    }
79}
80
81impl From<Uuid> for RecordId {
82    fn from(value: Uuid) -> Self {
83        Self::Uuid(value)
84    }
85}
86
87/// Portable record payload used for inserts, updates, and query responses.
88///
89/// # Examples
90///
91/// ```
92/// let mut record = storage_kit::database::data::DataRecord {
93///     id: Some(storage_kit::database::data::RecordId::String("doc-1".into())),
94///     ..Default::default()
95/// };
96/// record
97///     .fields
98///     .insert("title".into(), serde_json::json!("Storage Kit"));
99/// assert_eq!(record.id.as_ref().unwrap().as_string_lossy(), "doc-1");
100/// ```
101#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
102pub struct DataRecord {
103    /// Logical record identifier, when the selected backend supports ID persistence.
104    /// Unsupported backends reject a populated ID rather than synthesizing one.
105    pub id: Option<RecordId>,
106    /// Named vector payloads stored with the record.
107    pub vectors: BTreeMap<String, DenseVector>,
108    /// JSON document fields stored alongside vectors and the optional logical id.
109    pub fields: serde_json::Map<String, Value>,
110}
111
112impl DataRecord {
113    /// Creates a record with a logical id.
114    ///
115    /// # Examples
116    ///
117    /// ```
118    /// let record = storage_kit::database::data::DataRecord::new("doc-1");
119    /// assert_eq!(record.id.unwrap().as_string_lossy(), "doc-1");
120    /// ```
121    #[must_use]
122    pub fn new(id: impl Into<RecordId>) -> Self {
123        Self {
124            id: Some(id.into()),
125            ..Self::default()
126        }
127    }
128
129    /// Creates a record without a logical id.
130    ///
131    /// # Examples
132    ///
133    /// ```
134    /// let record = storage_kit::database::data::DataRecord::without_id();
135    /// assert!(record.id.is_none());
136    /// ```
137    #[must_use]
138    pub fn without_id() -> Self {
139        Self::default()
140    }
141
142    /// Adds one serializable JSON field.
143    ///
144    /// # Examples
145    ///
146    /// ```
147    /// let record = storage_kit::database::data::DataRecord::new("doc-1")
148    ///     .field("title", "Storage Kit")?;
149    /// assert_eq!(record.fields["title"], serde_json::json!("Storage Kit"));
150    /// # Ok::<(), storage_kit::StorageError>(())
151    /// ```
152    ///
153    /// # Errors
154    ///
155    /// Returns an error when the value cannot be serialized to JSON.
156    pub fn field<T>(mut self, key: impl Into<String>, value: T) -> Result<Self, StorageError>
157    where
158        T: Serialize,
159    {
160        let value = serde_json::to_value(value).map_err(|error| {
161            StorageError::InvalidConfig(format!("failed to serialize record field: {error}"))
162        })?;
163        self.fields.insert(key.into(), value);
164        Ok(self)
165    }
166
167    /// Adds one already-materialized JSON field.
168    ///
169    /// # Examples
170    ///
171    /// ```
172    /// let record = storage_kit::database::data::DataRecord::new("doc-1")
173    ///     .json_field("title", serde_json::json!("Storage Kit"));
174    /// assert_eq!(record.fields["title"], serde_json::json!("Storage Kit"));
175    /// ```
176    #[must_use]
177    pub fn json_field(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
178        self.fields.insert(key.into(), value);
179        self
180    }
181
182    /// Adds one named vector field.
183    ///
184    /// # Examples
185    ///
186    /// ```
187    /// let record = storage_kit::database::data::DataRecord::new("doc-1").vector(
188    ///     "embedding",
189    ///     storage_kit::database::data::DenseVector::F32(vec![0.1, 0.2]),
190    /// );
191    /// assert_eq!(record.vectors.len(), 1);
192    /// ```
193    #[must_use]
194    pub fn vector(mut self, key: impl Into<String>, vector: DenseVector) -> Self {
195        self.vectors.insert(key.into(), vector);
196        self
197    }
198}