1use hyphae_core::{
4 Q15Vector, VectorMetric, VectorSpaceDefinition, VectorSpaceName, VectorValueError,
5};
6use hyphae_query::FieldPath;
7use hyphae_retrieval::{LexicalField, LexicalIndexDefinition};
8use thiserror::Error;
9
10use crate::log::MAX_OPERATION_BYTES;
11
12const PUT: u8 = 1;
13const DELETE: u8 = 2;
14const DEFINE_VECTOR_SPACE: u8 = 3;
15const UPSERT_VECTOR: u8 = 4;
16const DELETE_VECTOR: u8 = 5;
17const DEFINE_LEXICAL_INDEX: u8 = 6;
18const PUT_HEADER_LENGTH: usize = 13;
19const DELETE_HEADER_LENGTH: usize = 5;
20
21pub const MAX_KEY_BYTES: usize = 1024 * 1024;
23
24#[derive(Clone, Debug, Eq, PartialEq)]
26pub enum Mutation {
27 Put {
29 key: Vec<u8>,
31 value: Vec<u8>,
33 },
34 Delete {
36 key: Vec<u8>,
38 },
39 DefineVectorSpace {
41 definition: VectorSpaceDefinition,
43 },
44 UpsertVector {
46 space: VectorSpaceName,
48 key: Vec<u8>,
50 vector: Q15Vector,
52 },
53 DeleteVector {
55 space: VectorSpaceName,
57 key: Vec<u8>,
59 },
60 DefineLexicalIndex {
62 definition: LexicalIndexDefinition,
64 },
65}
66
67impl Mutation {
68 pub fn put(key: impl Into<Vec<u8>>, value: impl Into<Vec<u8>>) -> Self {
70 Self::Put {
71 key: key.into(),
72 value: value.into(),
73 }
74 }
75
76 pub fn delete(key: impl Into<Vec<u8>>) -> Self {
78 Self::Delete { key: key.into() }
79 }
80
81 pub fn define_vector_space(definition: VectorSpaceDefinition) -> Self {
83 Self::DefineVectorSpace { definition }
84 }
85
86 pub fn upsert_vector(
88 space: VectorSpaceName,
89 key: impl Into<Vec<u8>>,
90 vector: Q15Vector,
91 ) -> Self {
92 Self::UpsertVector {
93 space,
94 key: key.into(),
95 vector,
96 }
97 }
98
99 pub fn delete_vector(space: VectorSpaceName, key: impl Into<Vec<u8>>) -> Self {
101 Self::DeleteVector {
102 space,
103 key: key.into(),
104 }
105 }
106
107 pub fn define_lexical_index(definition: LexicalIndexDefinition) -> Self {
109 Self::DefineLexicalIndex { definition }
110 }
111
112 pub(crate) fn encode(&self) -> Result<Vec<u8>, MutationError> {
113 match self {
114 Self::Put { key, value } => encode_put(key, value),
115 Self::Delete { key } => encode_delete(key),
116 Self::DefineVectorSpace { definition } => encode_vector_space(definition),
117 Self::UpsertVector { space, key, vector } => encode_vector(space, key, vector),
118 Self::DeleteVector { space, key } => encode_vector_delete(space, key),
119 Self::DefineLexicalIndex { definition } => encode_lexical_index(definition),
120 }
121 }
122
123 pub(crate) fn decode(encoded: &[u8]) -> Result<Self, MutationError> {
124 let Some(kind) = encoded.first().copied() else {
125 return Err(MutationError::Malformed);
126 };
127 match kind {
128 PUT => decode_put(encoded),
129 DELETE => decode_delete(encoded),
130 DEFINE_VECTOR_SPACE => decode_vector_space(encoded),
131 UPSERT_VECTOR => decode_vector(encoded),
132 DELETE_VECTOR => decode_vector_delete(encoded),
133 DEFINE_LEXICAL_INDEX => decode_lexical_index(encoded),
134 kind => Err(MutationError::UnknownKind { kind }),
135 }
136 }
137}
138
139#[derive(Clone, Debug, Error, Eq, PartialEq)]
141pub enum MutationError {
142 #[error("mutation key must not be empty")]
144 EmptyKey,
145
146 #[error("mutation key is {length} bytes; maximum is {maximum}")]
148 KeyTooLarge {
149 length: usize,
151 maximum: usize,
153 },
154
155 #[error("encoded mutation is {length} bytes; maximum is {maximum}")]
157 OperationTooLarge {
158 length: usize,
160 maximum: usize,
162 },
163
164 #[error("malformed persisted mutation")]
166 Malformed,
167
168 #[error("unknown persisted mutation kind {kind}")]
170 UnknownKind {
171 kind: u8,
173 },
174
175 #[error(transparent)]
177 Vector(#[from] VectorValueError),
178
179 #[error("invalid lexical definition")]
181 Lexical,
182}
183
184fn encode_lexical_index(definition: &LexicalIndexDefinition) -> Result<Vec<u8>, MutationError> {
185 let mut encoded = Vec::new();
186 encoded.push(DEFINE_LEXICAL_INDEX);
187 encode_space(&definition.name, &mut encoded)?;
188 let field_count = u8::try_from(definition.fields.len()).map_err(|_| MutationError::Lexical)?;
189 encoded.push(field_count);
190 for field in &definition.fields {
191 let segment_count =
192 u8::try_from(field.path.segments().len()).map_err(|_| MutationError::Lexical)?;
193 encoded.push(segment_count);
194 for segment in field.path.segments() {
195 let bytes = segment.as_bytes();
196 let length = u16::try_from(bytes.len()).map_err(|_| MutationError::Lexical)?;
197 encoded.extend_from_slice(&length.to_le_bytes());
198 encoded.extend_from_slice(bytes);
199 }
200 encoded.extend_from_slice(&field.weight_micros.to_le_bytes());
201 }
202 validate_operation_length(encoded.len())?;
203 Ok(encoded)
204}
205
206fn decode_lexical_index(encoded: &[u8]) -> Result<Mutation, MutationError> {
207 let mut cursor = 1;
208 let name = decode_space(encoded, &mut cursor)?;
209 let field_count = usize::from(*encoded.get(cursor).ok_or(MutationError::Malformed)?);
210 cursor = cursor.checked_add(1).ok_or(MutationError::Malformed)?;
211 let mut fields = Vec::with_capacity(field_count);
212 for _ in 0..field_count {
213 let segment_count = usize::from(*encoded.get(cursor).ok_or(MutationError::Malformed)?);
214 cursor = cursor.checked_add(1).ok_or(MutationError::Malformed)?;
215 let mut segments = Vec::with_capacity(segment_count);
216 for _ in 0..segment_count {
217 let end = cursor.checked_add(2).ok_or(MutationError::Malformed)?;
218 let length = usize::from(u16::from_le_bytes(copy_array(
219 encoded.get(cursor..end).ok_or(MutationError::Malformed)?,
220 )));
221 cursor = end;
222 let end = cursor.checked_add(length).ok_or(MutationError::Malformed)?;
223 let segment =
224 std::str::from_utf8(encoded.get(cursor..end).ok_or(MutationError::Malformed)?)
225 .map_err(|_| MutationError::Malformed)?
226 .to_owned();
227 cursor = end;
228 segments.push(segment);
229 }
230 let end = cursor.checked_add(4).ok_or(MutationError::Malformed)?;
231 let weight_micros = u32::from_le_bytes(copy_array(
232 encoded.get(cursor..end).ok_or(MutationError::Malformed)?,
233 ));
234 cursor = end;
235 fields.push(LexicalField {
236 path: FieldPath::new(segments),
237 weight_micros,
238 });
239 }
240 if cursor != encoded.len() {
241 return Err(MutationError::Malformed);
242 }
243 let definition =
244 LexicalIndexDefinition::new(name, fields).map_err(|_| MutationError::Lexical)?;
245 Ok(Mutation::DefineLexicalIndex { definition })
246}
247
248fn encode_space(space: &VectorSpaceName, encoded: &mut Vec<u8>) -> Result<(), MutationError> {
249 let length = u8::try_from(space.as_str().len()).map_err(|_| MutationError::Malformed)?;
250 encoded.push(length);
251 encoded.extend_from_slice(space.as_str().as_bytes());
252 Ok(())
253}
254
255fn decode_space(encoded: &[u8], cursor: &mut usize) -> Result<VectorSpaceName, MutationError> {
256 let length = usize::from(*encoded.get(*cursor).ok_or(MutationError::Malformed)?);
257 *cursor = cursor.checked_add(1).ok_or(MutationError::Malformed)?;
258 let end = cursor.checked_add(length).ok_or(MutationError::Malformed)?;
259 let raw = encoded.get(*cursor..end).ok_or(MutationError::Malformed)?;
260 *cursor = end;
261 let value = std::str::from_utf8(raw).map_err(|_| MutationError::Malformed)?;
262 Ok(VectorSpaceName::new(value.to_owned())?)
263}
264
265fn encode_vector_space(definition: &VectorSpaceDefinition) -> Result<Vec<u8>, MutationError> {
266 let mut encoded = Vec::with_capacity(1 + 1 + definition.name.as_str().len() + 4);
267 encoded.push(DEFINE_VECTOR_SPACE);
268 encode_space(&definition.name, &mut encoded)?;
269 encoded.extend_from_slice(&definition.dimension.to_le_bytes());
270 encoded.push(definition.metric as u8);
271 encoded.push(1);
272 validate_operation_length(encoded.len())?;
273 Ok(encoded)
274}
275
276fn decode_vector_space(encoded: &[u8]) -> Result<Mutation, MutationError> {
277 let mut cursor = 1;
278 let name = decode_space(encoded, &mut cursor)?;
279 let dimension_end = cursor.checked_add(2).ok_or(MutationError::Malformed)?;
280 let dimension = u16::from_le_bytes(copy_array(
281 encoded
282 .get(cursor..dimension_end)
283 .ok_or(MutationError::Malformed)?,
284 ));
285 cursor = dimension_end;
286 if encoded.get(cursor) != Some(&(VectorMetric::Cosine as u8))
287 || encoded.get(cursor + 1) != Some(&1)
288 || cursor + 2 != encoded.len()
289 {
290 return Err(MutationError::Malformed);
291 }
292 let definition = VectorSpaceDefinition::cosine(name, dimension)?;
293 Ok(Mutation::DefineVectorSpace { definition })
294}
295
296fn encode_vector(
297 space: &VectorSpaceName,
298 key: &[u8],
299 vector: &Q15Vector,
300) -> Result<Vec<u8>, MutationError> {
301 validate_key(key)?;
302 let key_length = u32::try_from(key.len()).map_err(|_| MutationError::KeyTooLarge {
303 length: key.len(),
304 maximum: MAX_KEY_BYTES,
305 })?;
306 let mut encoded = Vec::with_capacity(
307 1 + 1 + space.as_str().len() + 4 + key.len() + 2 + 2 * vector.as_slice().len(),
308 );
309 encoded.push(UPSERT_VECTOR);
310 encode_space(space, &mut encoded)?;
311 encoded.extend_from_slice(&key_length.to_le_bytes());
312 encoded.extend_from_slice(key);
313 encoded.extend_from_slice(&vector.dimension().to_le_bytes());
314 for value in vector.as_slice() {
315 encoded.extend_from_slice(&value.to_le_bytes());
316 }
317 validate_operation_length(encoded.len())?;
318 Ok(encoded)
319}
320
321fn decode_vector(encoded: &[u8]) -> Result<Mutation, MutationError> {
322 let mut cursor = 1;
323 let space = decode_space(encoded, &mut cursor)?;
324 let key_length_end = cursor.checked_add(4).ok_or(MutationError::Malformed)?;
325 let key_length = usize::try_from(u32::from_le_bytes(copy_array(
326 encoded
327 .get(cursor..key_length_end)
328 .ok_or(MutationError::Malformed)?,
329 )))
330 .map_err(|_| MutationError::Malformed)?;
331 cursor = key_length_end;
332 let key_end = cursor
333 .checked_add(key_length)
334 .ok_or(MutationError::Malformed)?;
335 let key = encoded
336 .get(cursor..key_end)
337 .ok_or(MutationError::Malformed)?
338 .to_vec();
339 validate_key(&key)?;
340 cursor = key_end;
341 let dimension_end = cursor.checked_add(2).ok_or(MutationError::Malformed)?;
342 let dimension = usize::from(u16::from_le_bytes(copy_array(
343 encoded
344 .get(cursor..dimension_end)
345 .ok_or(MutationError::Malformed)?,
346 )));
347 cursor = dimension_end;
348 let vector_bytes = dimension.checked_mul(2).ok_or(MutationError::Malformed)?;
349 let vector_end = cursor
350 .checked_add(vector_bytes)
351 .ok_or(MutationError::Malformed)?;
352 if vector_end != encoded.len() {
353 return Err(MutationError::Malformed);
354 }
355 let mut values = Vec::with_capacity(dimension);
356 for chunk in encoded[cursor..vector_end].chunks_exact(2) {
357 values.push(i16::from_le_bytes(copy_array(chunk)));
358 }
359 Ok(Mutation::UpsertVector {
360 space,
361 key,
362 vector: Q15Vector::new(values)?,
363 })
364}
365
366fn encode_vector_delete(space: &VectorSpaceName, key: &[u8]) -> Result<Vec<u8>, MutationError> {
367 validate_key(key)?;
368 let key_length = u32::try_from(key.len()).map_err(|_| MutationError::KeyTooLarge {
369 length: key.len(),
370 maximum: MAX_KEY_BYTES,
371 })?;
372 let mut encoded = Vec::with_capacity(1 + 1 + space.as_str().len() + 4 + key.len());
373 encoded.push(DELETE_VECTOR);
374 encode_space(space, &mut encoded)?;
375 encoded.extend_from_slice(&key_length.to_le_bytes());
376 encoded.extend_from_slice(key);
377 validate_operation_length(encoded.len())?;
378 Ok(encoded)
379}
380
381fn decode_vector_delete(encoded: &[u8]) -> Result<Mutation, MutationError> {
382 let mut cursor = 1;
383 let space = decode_space(encoded, &mut cursor)?;
384 let key_length_end = cursor.checked_add(4).ok_or(MutationError::Malformed)?;
385 let key_length = usize::try_from(u32::from_le_bytes(copy_array(
386 encoded
387 .get(cursor..key_length_end)
388 .ok_or(MutationError::Malformed)?,
389 )))
390 .map_err(|_| MutationError::Malformed)?;
391 cursor = key_length_end;
392 let key_end = cursor
393 .checked_add(key_length)
394 .ok_or(MutationError::Malformed)?;
395 if key_end != encoded.len() {
396 return Err(MutationError::Malformed);
397 }
398 let key = encoded[cursor..key_end].to_vec();
399 validate_key(&key)?;
400 Ok(Mutation::DeleteVector { space, key })
401}
402
403fn encode_put(key: &[u8], value: &[u8]) -> Result<Vec<u8>, MutationError> {
404 validate_key(key)?;
405 let key_length = u32::try_from(key.len()).map_err(|_| MutationError::KeyTooLarge {
406 length: key.len(),
407 maximum: MAX_KEY_BYTES,
408 })?;
409 let value_length =
410 u64::try_from(value.len()).map_err(|_| MutationError::OperationTooLarge {
411 length: usize::MAX,
412 maximum: MAX_OPERATION_BYTES,
413 })?;
414 let encoded_length = PUT_HEADER_LENGTH
415 .checked_add(key.len())
416 .and_then(|length| length.checked_add(value.len()))
417 .ok_or(MutationError::OperationTooLarge {
418 length: usize::MAX,
419 maximum: MAX_OPERATION_BYTES,
420 })?;
421 validate_operation_length(encoded_length)?;
422
423 let mut encoded = Vec::with_capacity(encoded_length);
424 encoded.push(PUT);
425 encoded.extend_from_slice(&key_length.to_le_bytes());
426 encoded.extend_from_slice(&value_length.to_le_bytes());
427 encoded.extend_from_slice(key);
428 encoded.extend_from_slice(value);
429 Ok(encoded)
430}
431
432fn encode_delete(key: &[u8]) -> Result<Vec<u8>, MutationError> {
433 validate_key(key)?;
434 let key_length = u32::try_from(key.len()).map_err(|_| MutationError::KeyTooLarge {
435 length: key.len(),
436 maximum: MAX_KEY_BYTES,
437 })?;
438 let encoded_length =
439 DELETE_HEADER_LENGTH
440 .checked_add(key.len())
441 .ok_or(MutationError::OperationTooLarge {
442 length: usize::MAX,
443 maximum: MAX_OPERATION_BYTES,
444 })?;
445 validate_operation_length(encoded_length)?;
446
447 let mut encoded = Vec::with_capacity(encoded_length);
448 encoded.push(DELETE);
449 encoded.extend_from_slice(&key_length.to_le_bytes());
450 encoded.extend_from_slice(key);
451 Ok(encoded)
452}
453
454fn decode_put(encoded: &[u8]) -> Result<Mutation, MutationError> {
455 if encoded.len() < PUT_HEADER_LENGTH {
456 return Err(MutationError::Malformed);
457 }
458 let key_length = usize::try_from(u32::from_le_bytes(copy_array(&encoded[1..5])))
459 .map_err(|_| MutationError::Malformed)?;
460 let value_length = usize::try_from(u64::from_le_bytes(copy_array(&encoded[5..13])))
461 .map_err(|_| MutationError::Malformed)?;
462 let key_end = PUT_HEADER_LENGTH
463 .checked_add(key_length)
464 .ok_or(MutationError::Malformed)?;
465 let value_end = key_end
466 .checked_add(value_length)
467 .ok_or(MutationError::Malformed)?;
468 if value_end != encoded.len() {
469 return Err(MutationError::Malformed);
470 }
471 let key = encoded[PUT_HEADER_LENGTH..key_end].to_vec();
472 validate_key(&key)?;
473 validate_operation_length(encoded.len())?;
474 Ok(Mutation::Put {
475 key,
476 value: encoded[key_end..value_end].to_vec(),
477 })
478}
479
480fn decode_delete(encoded: &[u8]) -> Result<Mutation, MutationError> {
481 if encoded.len() < DELETE_HEADER_LENGTH {
482 return Err(MutationError::Malformed);
483 }
484 let key_length = usize::try_from(u32::from_le_bytes(copy_array(&encoded[1..5])))
485 .map_err(|_| MutationError::Malformed)?;
486 let key_end = DELETE_HEADER_LENGTH
487 .checked_add(key_length)
488 .ok_or(MutationError::Malformed)?;
489 if key_end != encoded.len() {
490 return Err(MutationError::Malformed);
491 }
492 let key = encoded[DELETE_HEADER_LENGTH..key_end].to_vec();
493 validate_key(&key)?;
494 validate_operation_length(encoded.len())?;
495 Ok(Mutation::Delete { key })
496}
497
498pub(crate) fn validate_key(key: &[u8]) -> Result<(), MutationError> {
499 if key.is_empty() {
500 return Err(MutationError::EmptyKey);
501 }
502 if key.len() > MAX_KEY_BYTES {
503 return Err(MutationError::KeyTooLarge {
504 length: key.len(),
505 maximum: MAX_KEY_BYTES,
506 });
507 }
508 Ok(())
509}
510
511fn validate_operation_length(length: usize) -> Result<(), MutationError> {
512 if length > MAX_OPERATION_BYTES {
513 return Err(MutationError::OperationTooLarge {
514 length,
515 maximum: MAX_OPERATION_BYTES,
516 });
517 }
518 Ok(())
519}
520
521fn copy_array<const N: usize>(source: &[u8]) -> [u8; N] {
522 let mut output = [0_u8; N];
523 output.copy_from_slice(source);
524 output
525}
526
527#[cfg(test)]
528mod tests {
529 use std::error::Error;
530
531 use hyphae_core::{Q15Vector, VectorSpaceDefinition, VectorSpaceName};
532 use hyphae_query::FieldPath;
533 use hyphae_retrieval::{LexicalField, LexicalIndexDefinition};
534
535 use super::{MAX_KEY_BYTES, Mutation, MutationError};
536
537 #[test]
538 fn mutation_codec_round_trips_binary_values() -> Result<(), Box<dyn Error>> {
539 let mutations = [
540 Mutation::put([0, 1, 2], [255, 0, 3]),
541 Mutation::delete([7, 8, 9]),
542 ];
543 for mutation in mutations {
544 assert_eq!(Mutation::decode(&mutation.encode()?)?, mutation);
545 }
546 Ok(())
547 }
548
549 #[test]
550 fn vector_mutations_round_trip_canonically() -> Result<(), Box<dyn Error>> {
551 let space = VectorSpaceName::new("semantic.v1")?;
552 let mutations = [
553 Mutation::define_vector_space(VectorSpaceDefinition::cosine(space.clone(), 2)?),
554 Mutation::upsert_vector(space.clone(), b"object", Q15Vector::new(vec![32_767, -12])?),
555 Mutation::delete_vector(space, b"object"),
556 ];
557 for mutation in mutations {
558 assert_eq!(Mutation::decode(&mutation.encode()?)?, mutation);
559 }
560 Ok(())
561 }
562
563 #[test]
564 fn lexical_definition_mutation_round_trips_canonically() -> Result<(), Box<dyn Error>> {
565 let definition = LexicalIndexDefinition::new(
566 VectorSpaceName::new("documents")?,
567 vec![
568 LexicalField {
569 path: FieldPath::new(["body", "text"]),
570 weight_micros: 1_000_000,
571 },
572 LexicalField {
573 path: FieldPath::field("title"),
574 weight_micros: 2_000_000,
575 },
576 ],
577 )?;
578 let mutation = Mutation::define_lexical_index(definition);
579 assert_eq!(Mutation::decode(&mutation.encode()?)?, mutation);
580 Ok(())
581 }
582
583 #[test]
584 fn vector_mutation_decoder_rejects_invalid_q15_and_trailing_bytes() -> Result<(), Box<dyn Error>>
585 {
586 let space = VectorSpaceName::new("semantic")?;
587 let vector = Mutation::upsert_vector(space, b"object", Q15Vector::new(vec![1, 2])?);
588 let mut encoded = vector.encode()?;
589 encoded.push(0);
590 assert_eq!(Mutation::decode(&encoded), Err(MutationError::Malformed));
591 Ok(())
592 }
593
594 #[test]
595 fn mutation_codec_rejects_noncanonical_lengths() -> Result<(), Box<dyn Error>> {
596 let mut encoded = Mutation::put(b"key", b"value").encode()?;
597 encoded.push(0);
598 assert_eq!(Mutation::decode(&encoded), Err(MutationError::Malformed));
599 Ok(())
600 }
601
602 #[test]
603 fn keys_are_bounded_before_encoding() {
604 let result = Mutation::delete(vec![0; MAX_KEY_BYTES + 1]).encode();
605 assert!(matches!(result, Err(MutationError::KeyTooLarge { .. })));
606 }
607}