hawdb_core/vector_index/
codec.rs1use super::*;
5
6const HEADER: &[u8; 8] = b"HDVIX001";
7
8impl VectorIndexDescriptor {
9 pub fn encode(&self) -> Vec<u8> {
12 let mut output = Vec::with_capacity(MAX_VECTOR_INDEX_DESCRIPTOR_BYTES);
13 output.extend_from_slice(HEADER);
14 write_name(&mut output, &self.name);
15 match &self.target {
16 VectorIndexTarget::Relational {
17 table,
18 column,
19 column_ordinal,
20 } => {
21 output.push(0);
22 write_name(&mut output, table);
23 write_name(&mut output, column);
24 output.extend_from_slice(&column_ordinal.to_le_bytes());
25 }
26 VectorIndexTarget::NodeProperty {
27 table,
28 property,
29 table_id,
30 property_id,
31 } => {
32 output.push(1);
33 write_name(&mut output, table);
34 write_name(&mut output, property);
35 output.extend_from_slice(&table_id.0.to_le_bytes());
36 output.extend_from_slice(&property_id.0.to_le_bytes());
37 }
38 }
39 output.extend_from_slice(&self.dimension.get().to_le_bytes());
40 output.push(match self.distance {
41 VectorDistance::Cosine => 0,
42 VectorDistance::Euclidean => 1,
43 });
44 match self.backend {
45 VectorIndexBackend::RaBitQ {
46 bit_width,
47 segment_rows,
48 transform_seed,
49 } => {
50 output.push(0);
51 output.push(bit_width);
52 output.extend_from_slice(&segment_rows.to_le_bytes());
53 output.extend_from_slice(&transform_seed.to_le_bytes());
54 }
55 }
56 output.extend_from_slice(&self.schema_digest);
57 output
58 }
59
60 pub fn decode(bytes: &[u8]) -> Result<Self> {
63 if bytes.len() > MAX_VECTOR_INDEX_DESCRIPTOR_BYTES {
64 return Err(invalid("descriptor exceeds its encoded size bound"));
65 }
66 let mut input = Input(bytes);
67 if &input.read::<8>()? != HEADER {
68 return Err(invalid("unsupported descriptor format"));
69 }
70 let name = input.name(128)?;
71 let kind = input.read::<1>()?[0];
72 let table = input.name(1024)?;
73 let field = input.name(1024)?;
74 let target = match kind {
75 0 => VectorIndexTarget::Relational {
76 table,
77 column: field,
78 column_ordinal: u32::from_le_bytes(input.read()?),
79 },
80 1 => VectorIndexTarget::NodeProperty {
81 table,
82 property: field,
83 table_id: TableId(u32::from_le_bytes(input.read()?)),
84 property_id: PropertyId(u32::from_le_bytes(input.read()?)),
85 },
86 _ => return Err(invalid("unsupported target kind")),
87 };
88 let dimension = VectorDimension::new(u32::from_le_bytes(input.read()?))
89 .ok_or_else(|| invalid("dimension must be positive"))?;
90 let distance = match input.read::<1>()?[0] {
91 0 => VectorDistance::Cosine,
92 _ => return Err(invalid("unsupported index metric")),
93 };
94 let backend = match input.read::<1>()?[0] {
95 0 => VectorIndexBackend::RaBitQ {
96 bit_width: input.read::<1>()?[0],
97 segment_rows: u32::from_le_bytes(input.read()?),
98 transform_seed: u64::from_le_bytes(input.read()?),
99 },
100 _ => return Err(invalid("unsupported index backend")),
101 };
102 let digest = input.read()?;
103 if !input.0.is_empty() {
104 return Err(invalid("trailing descriptor bytes"));
105 }
106 Self::new(name, target, dimension, distance, backend, digest)
107 }
108}
109
110fn write_name(output: &mut Vec<u8>, name: &str) {
111 let length = u16::try_from(name.len()).expect("validated descriptor name length");
112 output.extend_from_slice(&length.to_le_bytes());
113 output.extend_from_slice(name.as_bytes());
114}
115
116struct Input<'a>(&'a [u8]);
117
118impl Input<'_> {
119 fn read<const N: usize>(&mut self) -> Result<[u8; N]> {
120 let (value, rest) = self
121 .0
122 .split_at_checked(N)
123 .ok_or_else(|| invalid("truncated descriptor"))?;
124 self.0 = rest;
125 Ok(value.try_into().expect("fixed-size descriptor field"))
126 }
127
128 fn name(&mut self, maximum: usize) -> Result<String> {
129 let length = usize::from(u16::from_le_bytes(self.read()?));
130 if length == 0 || length > maximum {
131 return Err(invalid("descriptor name exceeds its size bound"));
132 }
133 let (value, rest) = self
134 .0
135 .split_at_checked(length)
136 .ok_or_else(|| invalid("truncated descriptor name"))?;
137 let value =
138 std::str::from_utf8(value).map_err(|_| invalid("descriptor name is not UTF-8"))?;
139 self.0 = rest;
140 Ok(value.to_owned())
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 fn descriptor(target: VectorIndexTarget) -> VectorIndexDescriptor {
149 VectorIndexDescriptor::new(
150 "memory.embedding".into(),
151 target,
152 VectorDimension::new(3).unwrap(),
153 VectorDistance::Cosine,
154 VectorIndexBackend::RaBitQ {
155 bit_width: 4,
156 segment_rows: 1024,
157 transform_seed: 42,
158 },
159 [0x5a; 32],
160 )
161 .unwrap()
162 }
163
164 #[test]
165 fn vector_index_binding_round_trip_and_truncation() {
166 for target in [
167 VectorIndexTarget::Relational {
168 table: "memories".into(),
169 column: "embedding".into(),
170 column_ordinal: 2,
171 },
172 VectorIndexTarget::NodeProperty {
173 table: "Memory".into(),
174 property: "embedding".into(),
175 table_id: TableId(7),
176 property_id: PropertyId(19),
177 },
178 ] {
179 let binding = descriptor(target);
180 let encoded = binding.encode();
181 assert!(encoded.len() <= MAX_VECTOR_INDEX_DESCRIPTOR_BYTES);
182 assert_eq!(VectorIndexDescriptor::decode(&encoded).unwrap(), binding);
183 for length in 0..encoded.len() {
184 assert!(VectorIndexDescriptor::decode(&encoded[..length]).is_err());
185 }
186 let mut trailing = encoded.clone();
187 trailing.push(0);
188 assert!(VectorIndexDescriptor::decode(&trailing).is_err());
189 let mut unknown = encoded;
190 unknown[7] = b'2';
191 assert!(VectorIndexDescriptor::decode(&unknown).is_err());
192 }
193 }
194
195 #[test]
196 fn vector_index_binding_rejects_unsupported_metric_and_build_parameters() {
197 let target = VectorIndexTarget::Relational {
198 table: "memories".into(),
199 column: "embedding".into(),
200 column_ordinal: 1,
201 };
202 let valid = descriptor(target.clone());
203 for (metric, backend) in [
204 (VectorDistance::Euclidean, valid.backend()),
205 (
206 VectorDistance::Cosine,
207 VectorIndexBackend::RaBitQ {
208 bit_width: 2,
209 segment_rows: 1024,
210 transform_seed: 0,
211 },
212 ),
213 (
214 VectorDistance::Cosine,
215 VectorIndexBackend::RaBitQ {
216 bit_width: 4,
217 segment_rows: 0,
218 transform_seed: 0,
219 },
220 ),
221 ] {
222 assert!(VectorIndexDescriptor::new(
223 "index".into(),
224 target.clone(),
225 valid.dimension(),
226 metric,
227 backend,
228 [0; 32]
229 )
230 .is_err());
231 }
232 let mut oversized_name = valid.encode();
233 oversized_name[8..10].copy_from_slice(&u16::MAX.to_le_bytes());
234 assert!(VectorIndexDescriptor::decode(&oversized_name).is_err());
235 }
236}