Skip to main content

uqa_storage/vector_index/
canonical.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Immutable raw vectors are independent of a physical index's mutation provenance.
8
9use crate::{read_control::StorageReadControl, StorageBackendResult};
10use std::sync::Arc;
11use uqa_core::{
12    memory::{Budgeted, BudgetedVec},
13    DocId,
14};
15
16mod fingerprint;
17mod ordered;
18mod selection;
19pub(crate) use fingerprint::selected_fingerprint;
20pub(crate) use ordered::{copy_vector, ordinal_count};
21pub use selection::SelectedVectorRead;
22
23#[cfg(test)]
24mod tests;
25
26/// A fixed canonical field view. Owners retain their original visibility, allowance and cancellation. Metadata enumeration never decodes coordinates; a point read owns at most one vector and releases provider locks before returning. This interface makes no claim about physical index lineage or mutation origins.
27pub trait VectorRead: Send + Sync {
28    fn check_control(&self, control: &StorageReadControl) -> StorageBackendResult<()>;
29    fn dimensions(&self) -> u32;
30    /// Trustworthy identity of this complete fixed field, obtained from retained metadata. None means fixed-model reuse cannot be verified; callers must not replace it with a user-supplied label.
31    fn corpus_fingerprint(
32        &self,
33        control: &StorageReadControl,
34    ) -> StorageBackendResult<Option<[u8; 32]>> {
35        self.check_control(control)?;
36        Ok(None)
37    }
38    fn next_document_after(
39        &self,
40        after: Option<DocId>,
41        control: &StorageReadControl,
42    ) -> StorageBackendResult<Option<DocId>>;
43    /// Validate contiguous ordinal keys and return their count. Missing and explicitly empty tensors both have zero vectors.
44    fn document_vector_count(
45        &self,
46        document: DocId,
47        control: &StorageReadControl,
48    ) -> StorageBackendResult<u64>;
49    /// Read one ordinal without invoking a caller while a provider read lock is held. Absent ordinals return None; malformed values fail.
50    fn read_vector(
51        &self,
52        document: DocId,
53        ordinal: u32,
54        control: &StorageReadControl,
55    ) -> StorageBackendResult<Option<BudgetedVec<f32>>>;
56}
57
58pub type VectorReadSnapshot = Arc<dyn VectorRead>;
59
60/// Compare a complete replacement with the selected canonical document, keeping at most one existing vector in memory. Float bits, ordinal order and tensor length all matter; normalized equality is insufficient for a canonical replacement.
61pub(crate) fn canonical_vectors_equal(
62    source: &dyn VectorRead,
63    document: DocId,
64    vectors: &[Vec<f32>],
65    control: &StorageReadControl,
66) -> StorageBackendResult<bool> {
67    source.check_control(control)?;
68    for vector in vectors {
69        super::validate_vector_values_controlled(source.dimensions(), vector, Some(control))?;
70    }
71    if source.document_vector_count(document, control)? != vectors.len() as u64 {
72        return Ok(false);
73    }
74    for (ordinal, expected) in vectors.iter().enumerate() {
75        let ordinal =
76            u32::try_from(ordinal).map_err(|_| uqa_core::memory::MemoryError::SizeOverflow)?;
77        let Some(actual) = source.read_vector(document, ordinal, control)? else {
78            return Ok(false);
79        };
80        if actual.len() != expected.len() {
81            return Ok(false);
82        }
83        for (actual, expected) in actual.chunks(1024).zip(expected.chunks(1024)) {
84            source.check_control(control)?;
85            if actual
86                .iter()
87                .zip(expected)
88                .any(|(a, b)| a.to_bits() != b.to_bits())
89            {
90                return Ok(false);
91            }
92        }
93    }
94    source.check_control(control)?;
95    Ok(true)
96}
97
98/// Decode one little-endian canonical float vector under the invoking read allowance.
99pub fn decode_vector_bytes(
100    bytes: &[u8],
101    dimensions: u32,
102    control: &StorageReadControl,
103) -> StorageBackendResult<BudgetedVec<f32>> {
104    control.check()?;
105    let expected = usize::try_from(dimensions)
106        .map_err(|_| uqa_core::memory::MemoryError::SizeOverflow)?
107        .checked_mul(4)
108        .ok_or(uqa_core::memory::MemoryError::SizeOverflow)?;
109    if bytes.len() != expected {
110        return Err(
111            crate::mvcc::VersionError::InvalidEncoding("canonical vector width mismatch")
112                .into_storage_error(),
113        );
114    }
115    let mut vector = BudgetedVec::new(control.memory());
116    vector.reserve(bytes.len() / 4)?;
117    for (position, bytes) in bytes.as_chunks::<4>().0.iter().enumerate() {
118        if position.is_multiple_of(1024) {
119            control.check()?;
120        }
121        vector.push(f32::from_le_bytes(*bytes))?;
122    }
123    super::validate_vector_values_controlled(dimensions, &vector, Some(control))?;
124    Ok(vector)
125}
126
127impl<T: VectorRead> VectorRead for Budgeted<T> {
128    fn check_control(&self, control: &StorageReadControl) -> StorageBackendResult<()> {
129        (**self).check_control(control)
130    }
131    fn dimensions(&self) -> u32 {
132        (**self).dimensions()
133    }
134    fn corpus_fingerprint(
135        &self,
136        control: &StorageReadControl,
137    ) -> StorageBackendResult<Option<[u8; 32]>> {
138        (**self).corpus_fingerprint(control)
139    }
140    fn next_document_after(
141        &self,
142        after: Option<DocId>,
143        control: &StorageReadControl,
144    ) -> StorageBackendResult<Option<DocId>> {
145        (**self).next_document_after(after, control)
146    }
147    fn document_vector_count(
148        &self,
149        document: DocId,
150        control: &StorageReadControl,
151    ) -> StorageBackendResult<u64> {
152        (**self).document_vector_count(document, control)
153    }
154    fn read_vector(
155        &self,
156        document: DocId,
157        ordinal: u32,
158        control: &StorageReadControl,
159    ) -> StorageBackendResult<Option<BudgetedVec<f32>>> {
160        (**self).read_vector(document, ordinal, control)
161    }
162}