Skip to main content

hawdb_core/
vector.rs

1// Copyright 2026 Nowledge
2// Licensed under the Apache License, Version 2.0.
3
4//! Shared finite Float32 values and exact distance operators for SQL and Cypher.
5//! The canonical payload is a little-endian u32 dimension followed by f32 bits.
6//! Byte-backed storage permits unaligned, allocation-free views of row pages.
7
8use crate::{HawDBError, Result, ValueRef, VectorDimension};
9
10#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
11pub struct Vector {
12    encoded: Vec<u8>,
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
16pub struct VectorRef<'a> {
17    encoded: &'a [u8],
18    dimension: VectorDimension,
19}
20
21impl Vector {
22    pub fn new(coordinates: &[f32]) -> Result<Self> {
23        let dimension = u32::try_from(coordinates.len())
24            .ok()
25            .and_then(VectorDimension::new)
26            .ok_or_else(|| invalid("dimension must be a positive u32"))?;
27        if coordinates.iter().any(|value| !value.is_finite()) {
28            return Err(invalid("coordinates must be finite"));
29        }
30        let bytes = coordinates
31            .len()
32            .checked_mul(4)
33            .and_then(|n| n.checked_add(4))
34            .ok_or_else(|| invalid("payload length overflow"))?;
35        let mut encoded = Vec::with_capacity(bytes);
36        encoded.extend_from_slice(&dimension.get().to_le_bytes());
37        for value in coordinates {
38            encoded.extend_from_slice(&value.to_le_bytes());
39        }
40        Ok(Self { encoded })
41    }
42
43    /// Takes ownership of an already admitted payload without an additional copy.
44    pub fn from_encoded(encoded: Vec<u8>) -> Result<Self> {
45        Self::from_encoded_with_work(encoded, || Ok(()), |_| Ok(()), std::convert::identity)
46    }
47
48    /// Validates in bounded units before taking ownership, without rescanning.
49    pub fn from_encoded_with_work<T, E>(
50        encoded: Vec<u8>,
51        start: impl FnMut() -> std::result::Result<T, E>,
52        finish: impl FnMut(T) -> std::result::Result<(), E>,
53        invalid: impl Fn(HawDBError) -> E,
54    ) -> std::result::Result<Self, E> {
55        VectorRef::from_encoded_with_work(&encoded, start, finish, invalid)?;
56        Ok(Self { encoded })
57    }
58
59    pub fn dimension(&self) -> VectorDimension {
60        VectorDimension::new(u32::from_le_bytes(
61            self.encoded[..4].try_into().expect("validated dimension"),
62        ))
63        .expect("nonzero dimension")
64    }
65    pub fn retained_capacity(&self) -> usize {
66        self.encoded.capacity()
67    }
68
69    pub fn as_ref(&self) -> VectorRef<'_> {
70        VectorRef {
71            dimension: self.dimension(),
72            encoded: &self.encoded,
73        }
74    }
75
76    pub fn encoded(&self) -> &[u8] {
77        &self.encoded
78    }
79}
80
81impl<'a> VectorRef<'a> {
82    /// Validates length and finite components before any caller-owned allocation.
83    pub fn from_encoded(encoded: &'a [u8]) -> Result<Self> {
84        Self::from_encoded_with_work(encoded, || Ok(()), |_| Ok(()), std::convert::identity)
85    }
86
87    /// Each work unit scans at most 64 KiB; callbacks own admission and cancellation.
88    pub fn from_encoded_with_work<T, E>(
89        encoded: &'a [u8],
90        mut start: impl FnMut() -> std::result::Result<T, E>,
91        mut finish: impl FnMut(T) -> std::result::Result<(), E>,
92        map_invalid: impl Fn(HawDBError) -> E,
93    ) -> std::result::Result<Self, E> {
94        let shape = || -> Result<VectorDimension> {
95            let prefix: [u8; 4] = encoded
96                .get(..4)
97                .and_then(|bytes| bytes.try_into().ok())
98                .ok_or_else(|| invalid("truncated dimension"))?;
99            let dimension = VectorDimension::new(u32::from_le_bytes(prefix))
100                .ok_or_else(|| invalid("dimension must be positive"))?;
101            let expected = usize::try_from(dimension.get())
102                .ok()
103                .and_then(|n| n.checked_mul(4))
104                .and_then(|n| n.checked_add(4))
105                .ok_or_else(|| invalid("payload length overflow"))?;
106            if encoded.len() != expected {
107                return Err(invalid("payload length does not match dimension"));
108            }
109            Ok(dimension)
110        };
111        let dimension = shape().map_err(&map_invalid)?;
112        for block in encoded[4..].chunks(64 * 1024) {
113            let unit = start()?;
114            let finite = block.chunks_exact(4).all(|bytes| {
115                f32::from_le_bytes(bytes.try_into().expect("fixed-width coordinate")).is_finite()
116            });
117            finish(unit)?;
118            if !finite {
119                return Err(map_invalid(invalid("coordinates must be finite")));
120            }
121        }
122        Ok(Self { dimension, encoded })
123    }
124
125    pub const fn dimension(self) -> VectorDimension {
126        self.dimension
127    }
128    pub const fn encoded(self) -> &'a [u8] {
129        self.encoded
130    }
131
132    pub fn coordinates(self) -> impl ExactSizeIterator<Item = f32> + 'a {
133        self.encoded[4..]
134            .chunks_exact(4)
135            .map(|bytes| f32::from_le_bytes(bytes.try_into().expect("fixed-width coordinate")))
136    }
137
138    /// The caller must admit the payload before materializing a borrowed value.
139    pub fn to_owned(self) -> Vector {
140        Vector {
141            encoded: self.encoded.to_vec(),
142        }
143    }
144}
145
146#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
147pub enum VectorDistance {
148    Cosine,
149    Euclidean,
150}
151
152impl VectorDistance {
153    pub fn from_function_name(name: &str) -> Option<Self> {
154        if name.eq_ignore_ascii_case("cosine_distance") {
155            Some(Self::Cosine)
156        } else if name.eq_ignore_ascii_case("l2_distance") {
157            Some(Self::Euclidean)
158        } else {
159            None
160        }
161    }
162
163    pub const fn function_name(self) -> &'static str {
164        match self {
165            Self::Cosine => "cosine_distance",
166            Self::Euclidean => "l2_distance",
167        }
168    }
169
170    pub fn distance(self, left: VectorRef<'_>, right: VectorRef<'_>) -> Result<f64> {
171        self.distance_with_work(left, right, || Ok(()))
172    }
173
174    /// Cooperatively scores canonical values without copying their payloads.
175    pub fn distance_with_work(
176        self,
177        left: VectorRef<'_>,
178        right: VectorRef<'_>,
179        checkpoint: impl FnMut() -> Result<()>,
180    ) -> Result<f64> {
181        if left.dimension != right.dimension {
182            return Err(invalid("distance dimensions must match"));
183        }
184        self.distance_coordinates(left.coordinates(), right.coordinates(), checkpoint)
185    }
186
187    /// Scores admitted raw projection vectors with the same mathematical
188    /// contract as SQL/Cypher, without creating canonical payload copies.
189    pub fn distance_slices_with_work(
190        self,
191        left: &[f32],
192        right: &[f32],
193        checkpoint: impl FnMut() -> Result<()>,
194    ) -> Result<f64> {
195        if left.len() != right.len() {
196            return Err(invalid("distance dimensions must match"));
197        }
198        if left.is_empty() || u32::try_from(left.len()).is_err() {
199            return Err(invalid("dimension must be a positive u32"));
200        }
201        self.distance_coordinates(left.iter().copied(), right.iter().copied(), checkpoint)
202    }
203
204    fn distance_coordinates(
205        self,
206        left: impl Iterator<Item = f32>,
207        right: impl Iterator<Item = f32>,
208        mut checkpoint: impl FnMut() -> Result<()>,
209    ) -> Result<f64> {
210        let (mut dot, mut left_norm, mut right_norm, mut squared) =
211            (0.0f64, 0.0f64, 0.0f64, 0.0f64);
212        for (index, (left, right)) in left.zip(right).enumerate() {
213            // Read at most 64 KiB across both operands between checkpoints.
214            if index % (64 * 1024 / (2 * std::mem::size_of::<f32>())) == 0 {
215                checkpoint()?;
216            }
217            if !left.is_finite() || !right.is_finite() {
218                return Err(invalid("coordinates must be finite"));
219            }
220            let (left, right) = (f64::from(left), f64::from(right));
221            match self {
222                Self::Euclidean => squared += (left - right).powi(2),
223                Self::Cosine => {
224                    dot += left * right;
225                    left_norm += left * left;
226                    right_norm += right * right;
227                }
228            }
229        }
230        checkpoint()?;
231        match self {
232            Self::Euclidean => Ok(squared.sqrt()),
233            Self::Cosine if left_norm == 0.0 || right_norm == 0.0 => {
234                Err(invalid("cosine distance requires nonzero vectors"))
235            }
236            Self::Cosine => {
237                Ok(1.0 - (dot / (left_norm.sqrt() * right_norm.sqrt())).clamp(-1.0, 1.0))
238            }
239        }
240    }
241
242    pub fn evaluate(self, left: ValueRef<'_>, right: ValueRef<'_>) -> Result<Option<f64>> {
243        match (left, right) {
244            (ValueRef::Null, _) | (_, ValueRef::Null) => Ok(None),
245            (ValueRef::Vector(left), ValueRef::Vector(right)) => {
246                self.distance(left, right).map(Some)
247            }
248            _ => Err(invalid("distance arguments must be vectors")),
249        }
250    }
251}
252
253fn invalid(reason: &str) -> HawDBError {
254    HawDBError::Semantic(format!("invalid vector: {reason}"))
255}
256
257#[cfg(test)]
258mod tests {
259    use super::*;
260
261    #[test]
262    fn projection_distance_preserves_signed_cosine_and_canonical_parity() {
263        let query = [1.0, 0.0];
264        let values = [[1.0, 0.0], [0.0, 1.0], [-0.6, 0.8], [-1.0, 0.0]];
265        for metric in [VectorDistance::Cosine, VectorDistance::Euclidean] {
266            for coordinates in values {
267                let canonical = metric
268                    .distance(
269                        Vector::new(&query).unwrap().as_ref(),
270                        Vector::new(&coordinates).unwrap().as_ref(),
271                    )
272                    .unwrap();
273                let projection = metric
274                    .distance_slices_with_work(&query, &coordinates, || Ok(()))
275                    .unwrap();
276                assert_eq!(projection.to_bits(), canonical.to_bits());
277            }
278        }
279        let distance = |coordinates: &[f32]| {
280            VectorDistance::Cosine
281                .distance_slices_with_work(&query, coordinates, || Ok(()))
282                .unwrap()
283        };
284        assert!(distance(&values[2]) > 1.0);
285        assert!(distance(&values[2]) < distance(&values[3]));
286        assert_eq!(distance(&values[3]), 2.0);
287        for coordinates in [&[][..], &[0.0][..], &[f32::NAN, 0.0][..], &[0.0, 0.0][..]] {
288            assert!(VectorDistance::Cosine
289                .distance_slices_with_work(&query, coordinates, || Ok(()))
290                .is_err());
291        }
292    }
293
294    #[test]
295    fn projection_distance_preserves_cooperative_failure() {
296        let coordinates = vec![1.0; 20_000];
297        let mut checkpoints = 0;
298        let error = VectorDistance::Cosine
299            .distance_slices_with_work(&coordinates, &coordinates, || {
300                checkpoints += 1;
301                if checkpoints == 2 {
302                    Err(HawDBError::ProjectionBusy)
303                } else {
304                    Ok(())
305                }
306            })
307            .unwrap_err();
308        assert!(matches!(error, HawDBError::ProjectionBusy));
309        assert_eq!(checkpoints, 2);
310    }
311
312    #[test]
313    fn vector_encoding_rejects_malformed_and_nonfinite_payloads() {
314        let value = Vector::new(&[1.0, -2.5]).unwrap();
315        assert_eq!(value.encoded(), &[2, 0, 0, 0, 0, 0, 128, 63, 0, 0, 32, 192]);
316        assert_eq!(
317            Vector::from_encoded(value.encoded().to_vec()).unwrap(),
318            value
319        );
320        let mut unaligned = vec![0];
321        unaligned.extend_from_slice(value.encoded());
322        assert_eq!(
323            VectorRef::from_encoded(&unaligned[1..]).unwrap(),
324            value.as_ref()
325        );
326        for size in 0..value.encoded().len() {
327            assert!(VectorRef::from_encoded(&value.encoded()[..size]).is_err());
328        }
329        assert!(VectorRef::from_encoded(&[0, 0, 0, 0]).is_err());
330        assert!(VectorRef::from_encoded(&u32::MAX.to_le_bytes()).is_err());
331        for component in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY] {
332            assert!(Vector::new(&[component]).is_err());
333            let mut encoded = 1u32.to_le_bytes().to_vec();
334            encoded.extend_from_slice(&component.to_le_bytes());
335            assert!(VectorRef::from_encoded(&encoded).is_err());
336        }
337        assert!(Vector::new(&[]).is_err());
338    }
339
340    #[test]
341    fn vector_distance_is_mathematical_and_shared() {
342        let left = Vector::new(&[1.0, 0.0]).unwrap();
343        let opposite = Vector::new(&[-1.0, 0.0]).unwrap();
344        let zero = Vector::new(&[0.0, 0.0]).unwrap();
345        assert_eq!(
346            VectorDistance::Cosine
347                .distance(left.as_ref(), opposite.as_ref())
348                .unwrap(),
349            2.0
350        );
351        assert_eq!(
352            VectorDistance::Euclidean
353                .distance(left.as_ref(), zero.as_ref())
354                .unwrap(),
355            1.0
356        );
357        assert!(VectorDistance::Cosine
358            .distance(left.as_ref(), zero.as_ref())
359            .is_err());
360        assert!(VectorDistance::Euclidean
361            .distance(left.as_ref(), Vector::new(&[1.0]).unwrap().as_ref())
362            .is_err());
363        assert_eq!(
364            VectorDistance::Cosine
365                .evaluate(ValueRef::Null, ValueRef::Vector(zero.as_ref()))
366                .unwrap(),
367            None
368        );
369        let large = Vector::new(&[f32::MAX, f32::MAX]).unwrap();
370        assert!(VectorDistance::Euclidean
371            .distance(large.as_ref(), zero.as_ref())
372            .unwrap()
373            .is_finite());
374        assert!(
375            VectorDistance::Cosine
376                .distance(large.as_ref(), large.as_ref())
377                .unwrap()
378                .abs()
379                < 1e-12
380        );
381    }
382}
383
384impl std::hash::Hash for VectorRef<'_> {
385    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
386        self.encoded.hash(state);
387    }
388}
389
390#[cfg(test)]
391mod work_tests {
392    use super::*;
393
394    #[test]
395    fn shared_vector_validation_stops_between_bounded_coordinate_blocks() {
396        let vector = Vector::new(&vec![1.0; 32769]).unwrap();
397        let calls = std::cell::Cell::new(0);
398        let error = Vector::from_encoded_with_work(
399            vector.encoded().to_vec(),
400            || {
401                calls.set(calls.get() + 1);
402                if calls.get() == 2 {
403                    Err("cancelled".to_owned())
404                } else {
405                    Ok(())
406                }
407            },
408            |_| Ok(()),
409            |error| error.to_string(),
410        )
411        .unwrap_err();
412        assert_eq!(error, "cancelled");
413        assert_eq!(calls.get(), 2);
414        let mut encoded = vector.encoded().to_vec();
415        let last = encoded.len() - 4;
416        encoded[last..].copy_from_slice(&f32::NAN.to_le_bytes());
417        assert!(Vector::from_encoded(encoded).is_err());
418    }
419}