tinyquant_core/corpus/vector_entry.rs
1//! `VectorEntry` entity — one compressed vector stored in a [`Corpus`](super::Corpus).
2//!
3//! Identity is determined solely by [`VectorId`]; equality and hashing ignore
4//! the compressed payload and metadata. This matches Python's behaviour where
5//! `dict` keys are the vector identifiers.
6
7use alloc::{collections::BTreeMap, string::String};
8
9use crate::codec::CompressedVector;
10use crate::corpus::entry_meta_value::EntryMetaValue;
11use crate::types::{Timestamp, VectorId};
12
13/// A single compressed vector together with its insertion timestamp and
14/// arbitrary key-value metadata.
15///
16/// # Identity
17///
18/// `PartialEq`, `Eq`, and `Hash` are implemented **by `vector_id` only**.
19/// Two entries with the same id but different payloads compare as equal.
20/// This lets callers use `VectorEntry` as a set element or map key.
21#[derive(Clone, Debug)]
22pub struct VectorEntry {
23 vector_id: VectorId,
24 compressed: CompressedVector,
25 /// The actual f32 vector dimension (not the byte count stored in
26 /// `compressed.dimension()` for Passthrough/Fp16 policies).
27 declared_dimension: u32,
28 inserted_at: Timestamp,
29 metadata: BTreeMap<String, EntryMetaValue>,
30}
31
32impl VectorEntry {
33 /// Construct a new `VectorEntry`.
34 ///
35 /// `declared_dimension` is the number of f32 elements in the original
36 /// vector. For `Compress` policy this equals `compressed.dimension()`;
37 /// for `Passthrough` and `Fp16` the compressed dimension is a byte count
38 /// that differs from the f32 dimension.
39 ///
40 /// `inserted_at` should be a nanosecond Unix timestamp from the caller's
41 /// time source. `metadata` may be empty.
42 #[must_use]
43 pub const fn new(
44 vector_id: VectorId,
45 compressed: CompressedVector,
46 declared_dimension: u32,
47 inserted_at: Timestamp,
48 metadata: BTreeMap<String, EntryMetaValue>,
49 ) -> Self {
50 Self {
51 vector_id,
52 compressed,
53 declared_dimension,
54 inserted_at,
55 metadata,
56 }
57 }
58
59 /// The stable string identifier for this vector.
60 #[must_use]
61 pub const fn vector_id(&self) -> &VectorId {
62 &self.vector_id
63 }
64
65 /// Reference to the compressed payload.
66 #[must_use]
67 pub const fn compressed(&self) -> &CompressedVector {
68 &self.compressed
69 }
70
71 /// Nanosecond Unix timestamp recorded at insertion time.
72 #[must_use]
73 pub const fn inserted_at(&self) -> Timestamp {
74 self.inserted_at
75 }
76
77 /// Read-only view of the metadata map.
78 #[must_use]
79 pub const fn metadata(&self) -> &BTreeMap<String, EntryMetaValue> {
80 &self.metadata
81 }
82
83 /// Mutable access to the metadata map.
84 pub fn metadata_mut(&mut self) -> &mut BTreeMap<String, EntryMetaValue> {
85 &mut self.metadata
86 }
87
88 /// Convenience delegate: config hash of the underlying compressed vector.
89 #[must_use]
90 pub const fn config_hash(&self) -> &crate::types::ConfigHash {
91 self.compressed.config_hash()
92 }
93
94 /// The number of f32 dimensions in the original vector.
95 ///
96 /// For `Compress` policy this matches `compressed.dimension()`. For
97 /// `Passthrough` and `Fp16` policies `compressed.dimension()` holds a
98 /// byte count; this method always returns the true f32 vector length.
99 #[must_use]
100 pub const fn dimension(&self) -> u32 {
101 self.declared_dimension
102 }
103
104 /// Convenience delegate: whether a residual buffer is present.
105 #[must_use]
106 pub fn has_residual(&self) -> bool {
107 self.compressed.has_residual()
108 }
109}
110
111impl PartialEq for VectorEntry {
112 /// Equality by `vector_id` only — payload and metadata are ignored.
113 fn eq(&self, other: &Self) -> bool {
114 self.vector_id == other.vector_id
115 }
116}
117
118impl Eq for VectorEntry {}
119
120impl core::hash::Hash for VectorEntry {
121 /// Hash by `vector_id` only — must be consistent with `PartialEq`.
122 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
123 self.vector_id.hash(state);
124 }
125}