1use crate::meta::{ColumnId, PredicateValue};
4
5use super::{DocId, DocumentVersion, IngestDocument, Revision};
6
7pub const UPSERT_V1: u16 = 1;
29pub const DELETE_V1: u16 = 2;
31pub const METADATA_EDIT_V1: u16 = 3;
33pub const UPSERT_WITH_TIMESTAMP_V1: u16 = 4;
35pub const UPSERT_WITH_METADATA_V1: u16 = 5;
37pub const UPSERT_WITH_TIMESTAMP_AND_METADATA_V1: u16 = 6;
39pub const UPSERT_V2: u16 = 7;
41pub const UPSERT_V2_BATCH_MEMBER: u16 = 8;
45
46pub const UPSERT_V2_VECTOR: u32 = 1 << 0;
48pub const UPSERT_V2_TEXT: u32 = 1 << 1;
50pub const UPSERT_V2_TIMESTAMP: u32 = 1 << 2;
52pub const UPSERT_V2_METADATA: u32 = 1 << 3;
54pub const UPSERT_V2_TYPED_COLUMNS: u32 = 1 << 4;
56pub const UPSERT_V2_EPOCH_TAG_RESERVED: u32 = 1 << 5;
58
59const UPSERT_V2_KNOWN_FIELDS: u32 = UPSERT_V2_VECTOR
60 | UPSERT_V2_TEXT
61 | UPSERT_V2_TIMESTAMP
62 | UPSERT_V2_METADATA
63 | UPSERT_V2_TYPED_COLUMNS;
64
65const PAYLOAD_VERSION: u16 = 1;
66const COMMON_HEADER_LEN: usize = 4;
67const DOCUMENT_VERSION_LEN: usize = 24;
68const UPSERT_PREFIX_LEN: usize = COMMON_HEADER_LEN + DOCUMENT_VERSION_LEN + 4;
69const TIMESTAMPED_UPSERT_PREFIX_LEN: usize = COMMON_HEADER_LEN + DOCUMENT_VERSION_LEN + 8 + 4;
70const METADATA_UPSERT_PREFIX_LEN: usize = COMMON_HEADER_LEN + DOCUMENT_VERSION_LEN + 4 + 4;
71const TIMESTAMPED_METADATA_UPSERT_PREFIX_LEN: usize =
72 COMMON_HEADER_LEN + DOCUMENT_VERSION_LEN + 8 + 4 + 4;
73const DELETE_PREFIX_LEN: usize = COMMON_HEADER_LEN + 4;
74const METADATA_PREFIX_LEN: usize = COMMON_HEADER_LEN + DOCUMENT_VERSION_LEN + 4 + 1 + 3 + 4;
75
76const VALUE_NULL: u8 = 0;
77const VALUE_ID128: u8 = 6;
78const VALUE_U64: u8 = 1;
79const VALUE_I64: u8 = 2;
80const VALUE_F64: u8 = 3;
81const VALUE_BOOL: u8 = 4;
82const VALUE_STRING: u8 = 5;
83
84#[derive(Clone, Debug, Eq, PartialEq)]
86pub enum PayloadError {
87 UnknownOperation(u16),
89 TransactionBinding,
91 LengthOverflow,
93 Truncated,
95 Version(u16),
97 Flags(u16),
99 Reserved(u32),
101 EmptyVector,
103 EmptyDelete,
105 NonFiniteVector {
107 index: usize,
109 },
110 ValueKind(u8),
112 ValueLength {
114 kind: u8,
116 expected: usize,
118 actual: usize,
120 },
121 Boolean(u8),
123 Utf8,
125 TrailingBytes(usize),
127 FieldBitmap(u32),
129 Lexical(String),
131 Columns(String),
133 BatchPosition {
135 index: u32,
137 count: u32,
139 },
140 OrphanBatchMember {
142 index: u32,
144 count: u32,
146 },
147}
148
149impl std::fmt::Display for PayloadError {
150 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151 match self {
152 Self::TransactionBinding => write!(formatter, "invalid transaction binding"),
153 Self::UnknownOperation(op) => {
154 write!(formatter, "WAL mutation operation {op} is unknown")
155 }
156 Self::LengthOverflow => formatter.write_str("WAL mutation payload length overflow"),
157 Self::Truncated => formatter.write_str("WAL mutation payload is truncated"),
158 Self::Version(version) => write!(
159 formatter,
160 "WAL mutation payload version {version} is unsupported"
161 ),
162 Self::Flags(flags) => write!(
163 formatter,
164 "WAL mutation payload flags {flags:#06x} are non-zero"
165 ),
166 Self::Reserved(value) => write!(
167 formatter,
168 "WAL metadata payload reserved bytes are {value:#08x}, expected zero"
169 ),
170 Self::EmptyVector => formatter.write_str("WAL upsert vector is empty"),
171 Self::EmptyDelete => formatter.write_str("WAL delete document list is empty"),
172 Self::NonFiniteVector { index } => write!(
173 formatter,
174 "WAL upsert vector coordinate {index} is non-finite"
175 ),
176 Self::ValueKind(kind) => {
177 write!(formatter, "WAL metadata value kind {kind} is unknown")
178 }
179 Self::ValueLength {
180 kind,
181 expected,
182 actual,
183 } => write!(
184 formatter,
185 "WAL metadata value kind {kind} needs {expected} bytes, got {actual}"
186 ),
187 Self::Boolean(value) => write!(
188 formatter,
189 "WAL metadata Boolean byte {value} is not canonical zero or one"
190 ),
191 Self::Utf8 => formatter.write_str("WAL metadata string is not valid UTF-8"),
192 Self::TrailingBytes(bytes) => {
193 write!(formatter, "WAL mutation payload has {bytes} trailing bytes")
194 }
195 Self::FieldBitmap(bitmap) => write!(
196 formatter,
197 "WAL upsert-v2 field bitmap {bitmap:#010x} names reserved fields"
198 ),
199 Self::Lexical(detail) => write!(formatter, "WAL lexical replay failed: {detail}"),
200 Self::Columns(detail) => write!(formatter, "WAL column replay failed: {detail}"),
201 Self::BatchPosition { index, count } => write!(
202 formatter,
203 "WAL batch member {index} of {count} is not a valid position"
204 ),
205 Self::OrphanBatchMember { index, count } => write!(
206 formatter,
207 "WAL batch member {index} of {count} continues no earlier member"
208 ),
209 }
210 }
211}
212
213impl std::error::Error for PayloadError {}
214
215#[derive(Clone, Debug, PartialEq)]
217pub enum MetadataValue {
218 Null,
220 U64(u64),
222 Id128(DocId),
224 I64(i64),
226 F64(f64),
228 Bool(bool),
230 String(String),
232}
233
234#[derive(Clone, Debug, PartialEq)]
236pub struct MetadataEdit {
237 version: DocumentVersion,
238 column: ColumnId,
239 value: MetadataValue,
240}
241
242impl MetadataEdit {
243 #[must_use]
245 pub const fn new(version: DocumentVersion, column: ColumnId, value: MetadataValue) -> Self {
246 Self {
247 version,
248 column,
249 value,
250 }
251 }
252
253 #[must_use]
255 pub const fn version(&self) -> DocumentVersion {
256 self.version
257 }
258
259 #[must_use]
261 pub const fn column(&self) -> ColumnId {
262 self.column
263 }
264
265 #[must_use]
267 pub const fn value(&self) -> &MetadataValue {
268 &self.value
269 }
270}
271
272#[derive(Clone, Debug, PartialEq)]
274pub enum MutationPayload {
275 Upsert(IngestDocument),
277 Delete(Vec<DocId>),
279 MetadataEdit(MetadataEdit),
281 BatchMember {
283 index: u32,
285 count: u32,
287 document: IngestDocument,
289 },
290}
291
292pub fn encode_upsert(document: &IngestDocument) -> Result<Vec<u8>, PayloadError> {
295 encode_upsert_body(document, None, false)
296}
297
298pub fn encode_upsert_with_timestamp(document: &IngestDocument) -> Result<Vec<u8>, PayloadError> {
300 encode_upsert_body(document, Some(document.timestamp()), false)
301}
302
303pub fn encode_upsert_with_metadata(document: &IngestDocument) -> Result<Vec<u8>, PayloadError> {
305 encode_upsert_body(document, None, true)
306}
307
308pub fn encode_upsert_with_timestamp_and_metadata(
310 document: &IngestDocument,
311) -> Result<Vec<u8>, PayloadError> {
312 encode_upsert_body(document, Some(document.timestamp()), true)
313}
314
315pub fn encode_upsert_v2(document: &IngestDocument) -> Result<Vec<u8>, PayloadError> {
318 let mut bitmap = UPSERT_V2_VECTOR;
319 if document.text().is_some() {
320 bitmap |= UPSERT_V2_TEXT;
321 }
322 if document.has_timestamp() {
323 bitmap |= UPSERT_V2_TIMESTAMP;
324 }
325 if document.has_metadata() {
326 bitmap |= UPSERT_V2_METADATA;
327 }
328 if document.has_columns() {
329 bitmap |= UPSERT_V2_TYPED_COLUMNS;
330 }
331 validate_vector(document.vector())?;
332 let dims = u32::try_from(document.vector().len()).map_err(|_| PayloadError::LengthOverflow)?;
333 let mut payload = Vec::new();
334 payload.extend_from_slice(&bitmap.to_le_bytes());
335 append_version(&mut payload, document.version());
336 payload.extend_from_slice(&dims.to_le_bytes());
337 for value in document.vector() {
338 payload.extend_from_slice(&value.to_bits().to_le_bytes());
339 }
340 if let Some(text) = document.text() {
341 append_length_prefixed(text.as_bytes(), &mut payload)?;
342 }
343 if document.has_timestamp() {
344 payload.extend_from_slice(&document.timestamp().to_le_bytes());
345 }
346 if document.has_metadata() {
347 append_length_prefixed(document.metadata(), &mut payload)?;
348 }
349 if document.has_columns() {
350 encode_typed_columns(document.columns(), &mut payload)?;
351 }
352 Ok(payload)
353}
354
355fn validate_vector(vector: &[f32]) -> Result<(), PayloadError> {
356 if vector.is_empty() {
357 return Err(PayloadError::EmptyVector);
358 }
359 if let Some(index) = vector.iter().position(|value| !value.is_finite()) {
360 return Err(PayloadError::NonFiniteVector { index });
361 }
362 Ok(())
363}
364
365fn append_length_prefixed(bytes: &[u8], output: &mut Vec<u8>) -> Result<(), PayloadError> {
366 let length = u32::try_from(bytes.len()).map_err(|_| PayloadError::LengthOverflow)?;
367 output.extend_from_slice(&length.to_le_bytes());
368 output.extend_from_slice(bytes);
369 Ok(())
370}
371
372fn encode_typed_columns(
373 columns: &[(ColumnId, PredicateValue)],
374 output: &mut Vec<u8>,
375) -> Result<(), PayloadError> {
376 let count = u32::try_from(columns.len()).map_err(|_| PayloadError::LengthOverflow)?;
377 output.extend_from_slice(&count.to_le_bytes());
378 for (column, value) in columns {
379 let metadata = predicate_to_metadata(value);
380 let (kind, body) = encode_metadata_value(&metadata)?;
381 output.extend_from_slice(&column.get().to_le_bytes());
382 output.push(kind);
383 output.extend_from_slice(&[0_u8; 3]);
384 append_length_prefixed(&body, output)?;
385 }
386 Ok(())
387}
388
389pub(crate) fn encode_column_values(
390 columns: &[(ColumnId, PredicateValue)],
391) -> Result<Vec<u8>, PayloadError> {
392 let mut encoded = Vec::new();
393 encode_typed_columns(columns, &mut encoded)?;
394 Ok(encoded)
395}
396
397fn predicate_to_metadata(value: &PredicateValue) -> MetadataValue {
398 match value {
399 PredicateValue::Id128(value) => MetadataValue::Id128(*value),
400 PredicateValue::U64(value) => MetadataValue::U64(*value),
401 PredicateValue::I64(value) => MetadataValue::I64(*value),
402 PredicateValue::F64(value) => MetadataValue::F64(*value),
403 PredicateValue::Bool(value) => MetadataValue::Bool(*value),
404 PredicateValue::String(value) => MetadataValue::String(value.clone()),
405 }
406}
407
408fn encode_upsert_body(
409 document: &IngestDocument,
410 timestamp: Option<i64>,
411 include_metadata: bool,
412) -> Result<Vec<u8>, PayloadError> {
413 if document.vector().is_empty() {
414 return Err(PayloadError::EmptyVector);
415 }
416 let dims = u32::try_from(document.vector().len()).map_err(|_| PayloadError::LengthOverflow)?;
417 if let Some(index) = document
418 .vector()
419 .iter()
420 .position(|value| !value.is_finite())
421 {
422 return Err(PayloadError::NonFiniteVector { index });
423 }
424 let vector_bytes = document
425 .vector()
426 .len()
427 .checked_mul(std::mem::size_of::<f32>())
428 .ok_or(PayloadError::LengthOverflow)?;
429 let prefix = match (timestamp.is_some(), include_metadata) {
430 (false, false) => UPSERT_PREFIX_LEN,
431 (true, false) => TIMESTAMPED_UPSERT_PREFIX_LEN,
432 (false, true) => METADATA_UPSERT_PREFIX_LEN,
433 (true, true) => TIMESTAMPED_METADATA_UPSERT_PREFIX_LEN,
434 };
435 let capacity = prefix
436 .checked_add(if include_metadata {
437 document.metadata().len()
438 } else {
439 0
440 })
441 .and_then(|value| value.checked_add(vector_bytes))
442 .ok_or(PayloadError::LengthOverflow)?;
443 let mut payload = Vec::with_capacity(capacity);
444 append_header(&mut payload);
445 append_version(&mut payload, document.version());
446 if let Some(timestamp) = timestamp {
447 payload.extend_from_slice(×tamp.to_le_bytes());
448 }
449 if include_metadata {
450 let metadata_len =
451 u32::try_from(document.metadata().len()).map_err(|_| PayloadError::LengthOverflow)?;
452 payload.extend_from_slice(&metadata_len.to_le_bytes());
453 }
454 payload.extend_from_slice(&dims.to_le_bytes());
455 if include_metadata {
456 payload.extend_from_slice(document.metadata());
457 }
458 for value in document.vector() {
459 payload.extend_from_slice(&value.to_bits().to_le_bytes());
460 }
461 Ok(payload)
462}
463
464pub fn encode_delete(doc_ids: &[DocId]) -> Result<Vec<u8>, PayloadError> {
467 if doc_ids.is_empty() {
468 return Err(PayloadError::EmptyDelete);
469 }
470 let count = u32::try_from(doc_ids.len()).map_err(|_| PayloadError::LengthOverflow)?;
471 let body_bytes = doc_ids
472 .len()
473 .checked_mul(std::mem::size_of::<u128>())
474 .ok_or(PayloadError::LengthOverflow)?;
475 let capacity = DELETE_PREFIX_LEN
476 .checked_add(body_bytes)
477 .ok_or(PayloadError::LengthOverflow)?;
478 let mut payload = Vec::with_capacity(capacity);
479 append_header(&mut payload);
480 payload.extend_from_slice(&count.to_le_bytes());
481 for doc_id in doc_ids {
482 payload.extend_from_slice(&doc_id.get().to_le_bytes());
483 }
484 Ok(payload)
485}
486
487pub fn encode_metadata_edit(edit: &MetadataEdit) -> Result<Vec<u8>, PayloadError> {
490 let (kind, body) = encode_metadata_value(edit.value())?;
491 let value_len = u32::try_from(body.len()).map_err(|_| PayloadError::LengthOverflow)?;
492 let capacity = METADATA_PREFIX_LEN
493 .checked_add(body.len())
494 .ok_or(PayloadError::LengthOverflow)?;
495 let mut payload = Vec::with_capacity(capacity);
496 append_header(&mut payload);
497 append_version(&mut payload, edit.version());
498 payload.extend_from_slice(&edit.column().get().to_le_bytes());
499 payload.push(kind);
500 payload.extend_from_slice(&[0_u8; 3]);
501 payload.extend_from_slice(&value_len.to_le_bytes());
502 payload.extend_from_slice(&body);
503 Ok(payload)
504}
505
506pub fn decode_mutation(op: u16, payload: &[u8]) -> Result<MutationPayload, PayloadError> {
508 match op {
509 UPSERT_V1 => decode_upsert(payload).map(MutationPayload::Upsert),
510 DELETE_V1 => decode_delete(payload).map(MutationPayload::Delete),
511 METADATA_EDIT_V1 => decode_metadata_edit(payload).map(MutationPayload::MetadataEdit),
512 UPSERT_WITH_TIMESTAMP_V1 => {
513 decode_upsert_with_timestamp(payload).map(MutationPayload::Upsert)
514 }
515 UPSERT_WITH_METADATA_V1 => {
516 decode_upsert_with_metadata(payload).map(MutationPayload::Upsert)
517 }
518 UPSERT_WITH_TIMESTAMP_AND_METADATA_V1 => {
519 decode_upsert_with_timestamp_and_metadata(payload).map(MutationPayload::Upsert)
520 }
521 UPSERT_V2 => decode_upsert_v2(payload).map(MutationPayload::Upsert),
522 UPSERT_V2_BATCH_MEMBER => {
523 decode_upsert_v2_batch_member(payload).map(|(index, count, document)| {
524 MutationPayload::BatchMember {
525 index,
526 count,
527 document,
528 }
529 })
530 }
531 unknown => Err(PayloadError::UnknownOperation(unknown)),
532 }
533}
534
535pub fn encode_upsert_v2_batch_member(
537 index: u32,
538 count: u32,
539 upsert_v2: &[u8],
540) -> Result<Vec<u8>, PayloadError> {
541 validate_batch_position(index, count)?;
542 let mut payload = Vec::new();
543 payload
544 .try_reserve_exact(upsert_v2.len().saturating_add(8))
545 .map_err(|_| PayloadError::LengthOverflow)?;
546 payload.extend_from_slice(&index.to_le_bytes());
547 payload.extend_from_slice(&count.to_le_bytes());
548 payload.extend_from_slice(upsert_v2);
549 Ok(payload)
550}
551
552pub fn decode_upsert_v2_batch_member(
554 payload: &[u8],
555) -> Result<(u32, u32, IngestDocument), PayloadError> {
556 let mut cursor = Cursor::new(payload);
557 let index = cursor.read_u32()?;
558 let count = cursor.read_u32()?;
559 validate_batch_position(index, count)?;
560 let body = payload.get(8..).ok_or(PayloadError::Truncated)?;
561 decode_upsert_v2(body).map(|document| (index, count, document))
562}
563
564fn validate_batch_position(index: u32, count: u32) -> Result<(), PayloadError> {
565 if count < 2 || index >= count {
566 return Err(PayloadError::BatchPosition { index, count });
567 }
568 Ok(())
569}
570
571pub fn decode_upsert_v2(payload: &[u8]) -> Result<IngestDocument, PayloadError> {
573 let mut cursor = Cursor::new(payload);
574 let bitmap = cursor.read_u32()?;
575 let reserved = bitmap & !UPSERT_V2_KNOWN_FIELDS;
576 if reserved != 0 {
577 return Err(PayloadError::FieldBitmap(reserved));
578 }
579 let version = cursor.read_version()?;
580 let vector = if bitmap & UPSERT_V2_VECTOR != 0 {
581 let dims = usize::try_from(cursor.read_u32()?).map_err(|_| PayloadError::LengthOverflow)?;
582 if dims == 0 {
583 return Err(PayloadError::EmptyVector);
584 }
585 let mut vector = Vec::with_capacity(dims);
586 for index in 0..dims {
587 let value = f32::from_bits(cursor.read_u32()?);
588 if !value.is_finite() {
589 return Err(PayloadError::NonFiniteVector { index });
590 }
591 vector.push(value);
592 }
593 vector
594 } else {
595 Vec::new()
596 };
597 let text = if bitmap & UPSERT_V2_TEXT != 0 {
598 Some(cursor.read_string()?)
599 } else {
600 None
601 };
602 let timestamp = if bitmap & UPSERT_V2_TIMESTAMP != 0 {
603 Some(i64::from_le_bytes(cursor.take()?))
604 } else {
605 None
606 };
607 let metadata = if bitmap & UPSERT_V2_METADATA != 0 {
608 Some(cursor.read_length_prefixed()?.to_vec())
609 } else {
610 None
611 };
612 let columns = if bitmap & UPSERT_V2_TYPED_COLUMNS != 0 {
613 Some(decode_typed_columns(&mut cursor)?)
614 } else {
615 None
616 };
617 cursor.finish()?;
618 let mut document = IngestDocument::new(version, vector);
619 if let Some(text) = text {
620 document = document.with_text(text);
621 }
622 if let Some(timestamp) = timestamp {
623 document = document.with_timestamp(timestamp);
624 }
625 if let Some(metadata) = metadata {
626 document = document.with_metadata(metadata);
627 }
628 if let Some(columns) = columns {
629 document = document.with_columns(columns);
630 }
631 Ok(document)
632}
633
634fn decode_typed_columns(
635 cursor: &mut Cursor<'_>,
636) -> Result<Vec<(ColumnId, PredicateValue)>, PayloadError> {
637 let count = usize::try_from(cursor.read_u32()?).map_err(|_| PayloadError::LengthOverflow)?;
638 let mut columns = Vec::with_capacity(count);
639 for _ in 0..count {
640 let column = ColumnId::new(cursor.read_u32()?);
641 let kind = cursor.read_u8()?;
642 let reserved = cursor.read_u24()?;
643 if reserved != 0 {
644 return Err(PayloadError::Reserved(reserved));
645 }
646 let body = cursor.read_length_prefixed()?;
647 let value = metadata_to_predicate(decode_metadata_value(kind, body)?)?;
648 columns.push((column, value));
649 }
650 Ok(columns)
651}
652
653pub(crate) fn decode_column_values(
654 encoded: &[u8],
655) -> Result<Vec<(ColumnId, PredicateValue)>, PayloadError> {
656 let mut cursor = Cursor::new(encoded);
657 let columns = decode_typed_columns(&mut cursor)?;
658 cursor.finish()?;
659 Ok(columns)
660}
661
662fn metadata_to_predicate(value: MetadataValue) -> Result<PredicateValue, PayloadError> {
663 match value {
664 MetadataValue::Null => Err(PayloadError::ValueKind(VALUE_NULL)),
665 MetadataValue::Id128(value) => Ok(PredicateValue::Id128(value)),
666 MetadataValue::U64(value) => Ok(PredicateValue::U64(value)),
667 MetadataValue::I64(value) => Ok(PredicateValue::I64(value)),
668 MetadataValue::F64(value) => Ok(PredicateValue::F64(value)),
669 MetadataValue::Bool(value) => Ok(PredicateValue::Bool(value)),
670 MetadataValue::String(value) => Ok(PredicateValue::String(value)),
671 }
672}
673
674pub fn decode_upsert(payload: &[u8]) -> Result<IngestDocument, PayloadError> {
676 decode_upsert_body(payload, false, false)
677}
678
679pub fn decode_upsert_with_timestamp(payload: &[u8]) -> Result<IngestDocument, PayloadError> {
681 decode_upsert_body(payload, true, false)
682}
683
684pub fn decode_upsert_with_metadata(payload: &[u8]) -> Result<IngestDocument, PayloadError> {
686 decode_upsert_body(payload, false, true)
687}
688
689pub fn decode_upsert_with_timestamp_and_metadata(
691 payload: &[u8],
692) -> Result<IngestDocument, PayloadError> {
693 decode_upsert_body(payload, true, true)
694}
695
696fn decode_upsert_body(
697 payload: &[u8],
698 has_timestamp: bool,
699 has_metadata: bool,
700) -> Result<IngestDocument, PayloadError> {
701 let mut cursor = Cursor::new(payload);
702 cursor.require_header()?;
703 let version = cursor.read_version()?;
704 let timestamp = if has_timestamp {
705 i64::from_le_bytes(cursor.take()?)
706 } else {
707 0
708 };
709 let metadata_len = if has_metadata {
710 usize::try_from(cursor.read_u32()?).map_err(|_| PayloadError::LengthOverflow)?
711 } else {
712 0
713 };
714 let dims = usize::try_from(cursor.read_u32()?).map_err(|_| PayloadError::LengthOverflow)?;
715 if dims == 0 {
716 return Err(PayloadError::EmptyVector);
717 }
718 let body_bytes = dims
719 .checked_mul(std::mem::size_of::<f32>())
720 .ok_or(PayloadError::LengthOverflow)?;
721 let remaining = metadata_len
722 .checked_add(body_bytes)
723 .ok_or(PayloadError::LengthOverflow)?;
724 cursor.require_remaining(remaining)?;
725 let metadata = cursor.read_bytes(metadata_len)?.to_vec();
726 let mut vector = Vec::with_capacity(dims);
727 for index in 0..dims {
728 let value = f32::from_bits(cursor.read_u32()?);
729 if !value.is_finite() {
730 return Err(PayloadError::NonFiniteVector { index });
731 }
732 vector.push(value);
733 }
734 cursor.finish()?;
735 let mut document = IngestDocument::new(version, vector);
736 if has_timestamp {
737 document = document.with_timestamp(timestamp);
738 }
739 if has_metadata {
740 document = document.with_metadata(metadata);
741 }
742 Ok(document)
743}
744
745pub fn decode_delete(payload: &[u8]) -> Result<Vec<DocId>, PayloadError> {
747 let mut cursor = Cursor::new(payload);
748 cursor.require_header()?;
749 let count = usize::try_from(cursor.read_u32()?).map_err(|_| PayloadError::LengthOverflow)?;
750 if count == 0 {
751 return Err(PayloadError::EmptyDelete);
752 }
753 let body_bytes = count
754 .checked_mul(std::mem::size_of::<u128>())
755 .ok_or(PayloadError::LengthOverflow)?;
756 cursor.require_remaining(body_bytes)?;
757 let mut doc_ids = Vec::with_capacity(count);
758 for _ in 0..count {
759 doc_ids.push(DocId::new(cursor.read_u128()?));
760 }
761 cursor.finish()?;
762 Ok(doc_ids)
763}
764
765pub fn decode_metadata_edit(payload: &[u8]) -> Result<MetadataEdit, PayloadError> {
767 let mut cursor = Cursor::new(payload);
768 cursor.require_header()?;
769 let version = cursor.read_version()?;
770 let column = ColumnId::new(cursor.read_u32()?);
771 let kind = cursor.read_u8()?;
772 let reserved = cursor.read_u24()?;
773 if reserved != 0 {
774 return Err(PayloadError::Reserved(reserved));
775 }
776 let value_len =
777 usize::try_from(cursor.read_u32()?).map_err(|_| PayloadError::LengthOverflow)?;
778 let body = cursor.read_bytes(value_len)?;
779 let value = decode_metadata_value(kind, body)?;
780 cursor.finish()?;
781 Ok(MetadataEdit::new(version, column, value))
782}
783
784fn append_header(payload: &mut Vec<u8>) {
785 payload.extend_from_slice(&PAYLOAD_VERSION.to_le_bytes());
786 payload.extend_from_slice(&0_u16.to_le_bytes());
787}
788
789fn append_version(payload: &mut Vec<u8>, version: DocumentVersion) {
790 payload.extend_from_slice(&version.doc_id().get().to_le_bytes());
791 payload.extend_from_slice(&version.revision().get().to_le_bytes());
792}
793
794fn encode_metadata_value(value: &MetadataValue) -> Result<(u8, Vec<u8>), PayloadError> {
795 let encoded = match value {
796 MetadataValue::Null => (VALUE_NULL, Vec::new()),
797 MetadataValue::Id128(value) => (VALUE_ID128, value.get().to_le_bytes().to_vec()),
798 MetadataValue::U64(value) => (VALUE_U64, value.to_le_bytes().to_vec()),
799 MetadataValue::I64(value) => (VALUE_I64, value.to_le_bytes().to_vec()),
800 MetadataValue::F64(value) => (VALUE_F64, value.to_bits().to_le_bytes().to_vec()),
801 MetadataValue::Bool(value) => (VALUE_BOOL, vec![u8::from(*value)]),
802 MetadataValue::String(value) => {
803 u32::try_from(value.len()).map_err(|_| PayloadError::LengthOverflow)?;
804 (VALUE_STRING, value.as_bytes().to_vec())
805 }
806 };
807 Ok(encoded)
808}
809
810fn decode_metadata_value(kind: u8, body: &[u8]) -> Result<MetadataValue, PayloadError> {
811 match kind {
812 VALUE_NULL => {
813 require_value_len(kind, body, 0)?;
814 Ok(MetadataValue::Null)
815 }
816 VALUE_ID128 => {
817 require_value_len(kind, body, 16)?;
818 read_array(body)
819 .map(u128::from_le_bytes)
820 .map(DocId::new)
821 .map(MetadataValue::Id128)
822 }
823 VALUE_U64 => {
824 require_value_len(kind, body, 8)?;
825 read_array(body)
826 .map(u64::from_le_bytes)
827 .map(MetadataValue::U64)
828 }
829 VALUE_I64 => {
830 require_value_len(kind, body, 8)?;
831 read_array(body)
832 .map(i64::from_le_bytes)
833 .map(MetadataValue::I64)
834 }
835 VALUE_F64 => {
836 require_value_len(kind, body, 8)?;
837 read_array(body)
838 .map(u64::from_le_bytes)
839 .map(f64::from_bits)
840 .map(MetadataValue::F64)
841 }
842 VALUE_BOOL => {
843 require_value_len(kind, body, 1)?;
844 match body.first().copied().ok_or(PayloadError::Truncated)? {
845 0 => Ok(MetadataValue::Bool(false)),
846 1 => Ok(MetadataValue::Bool(true)),
847 value => Err(PayloadError::Boolean(value)),
848 }
849 }
850 VALUE_STRING => std::str::from_utf8(body)
851 .map(|value| MetadataValue::String(value.to_owned()))
852 .map_err(|_| PayloadError::Utf8),
853 unknown => Err(PayloadError::ValueKind(unknown)),
854 }
855}
856
857fn require_value_len(kind: u8, body: &[u8], expected: usize) -> Result<(), PayloadError> {
858 if body.len() == expected {
859 Ok(())
860 } else {
861 Err(PayloadError::ValueLength {
862 kind,
863 expected,
864 actual: body.len(),
865 })
866 }
867}
868
869fn read_array<const N: usize>(bytes: &[u8]) -> Result<[u8; N], PayloadError> {
870 <[u8; N]>::try_from(bytes).map_err(|_| PayloadError::Truncated)
871}
872
873struct Cursor<'a> {
874 bytes: &'a [u8],
875 offset: usize,
876}
877
878impl<'a> Cursor<'a> {
879 const fn new(bytes: &'a [u8]) -> Self {
880 Self { bytes, offset: 0 }
881 }
882
883 fn require_header(&mut self) -> Result<(), PayloadError> {
884 let version = self.read_u16()?;
885 if version != PAYLOAD_VERSION {
886 return Err(PayloadError::Version(version));
887 }
888 let flags = self.read_u16()?;
889 if flags != 0 {
890 return Err(PayloadError::Flags(flags));
891 }
892 Ok(())
893 }
894
895 fn read_version(&mut self) -> Result<DocumentVersion, PayloadError> {
896 Ok(DocumentVersion::new(
897 DocId::new(self.read_u128()?),
898 Revision::new(self.read_u64()?),
899 ))
900 }
901
902 fn take<const N: usize>(&mut self) -> Result<[u8; N], PayloadError> {
903 read_array(self.read_bytes(N)?)
904 }
905
906 fn read_bytes(&mut self, length: usize) -> Result<&'a [u8], PayloadError> {
907 let end = self
908 .offset
909 .checked_add(length)
910 .ok_or(PayloadError::LengthOverflow)?;
911 let bytes = self
912 .bytes
913 .get(self.offset..end)
914 .ok_or(PayloadError::Truncated)?;
915 self.offset = end;
916 Ok(bytes)
917 }
918
919 fn require_remaining(&self, length: usize) -> Result<(), PayloadError> {
920 if self.remaining() < length {
921 Err(PayloadError::Truncated)
922 } else {
923 Ok(())
924 }
925 }
926
927 fn read_u8(&mut self) -> Result<u8, PayloadError> {
928 self.take::<1>().map(u8::from_le_bytes)
929 }
930
931 fn read_u16(&mut self) -> Result<u16, PayloadError> {
932 self.take().map(u16::from_le_bytes)
933 }
934
935 fn read_u24(&mut self) -> Result<u32, PayloadError> {
936 let [low, middle, high] = self.take::<3>()?;
937 Ok(u32::from(low) | (u32::from(middle) << 8) | (u32::from(high) << 16))
938 }
939
940 fn read_u32(&mut self) -> Result<u32, PayloadError> {
941 self.take().map(u32::from_le_bytes)
942 }
943
944 fn read_u64(&mut self) -> Result<u64, PayloadError> {
945 self.take().map(u64::from_le_bytes)
946 }
947
948 fn read_u128(&mut self) -> Result<u128, PayloadError> {
949 self.take().map(u128::from_le_bytes)
950 }
951
952 fn read_length_prefixed(&mut self) -> Result<&'a [u8], PayloadError> {
953 let length = usize::try_from(self.read_u32()?).map_err(|_| PayloadError::LengthOverflow)?;
954 self.read_bytes(length)
955 }
956
957 fn read_string(&mut self) -> Result<String, PayloadError> {
958 std::str::from_utf8(self.read_length_prefixed()?)
959 .map(str::to_owned)
960 .map_err(|_| PayloadError::Utf8)
961 }
962
963 fn remaining(&self) -> usize {
964 self.bytes.len().saturating_sub(self.offset)
965 }
966
967 fn finish(self) -> Result<(), PayloadError> {
968 let remaining = self.remaining();
969 if remaining == 0 {
970 Ok(())
971 } else {
972 Err(PayloadError::TrailingBytes(remaining))
973 }
974 }
975}
976
977#[cfg(test)]
978#[allow(clippy::unwrap_used)]
979mod id128_tests {
980 use super::*;
981
982 #[test]
983 fn id128_wal_layout_is_sixteen_little_endian_bytes() {
984 let value = MetadataValue::Id128(DocId::new(0xffeeddccbbaa99887766554433221100));
985 let bytes = vec![
986 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
987 0xee, 0xff,
988 ];
989 assert_eq!(encode_metadata_value(&value).unwrap(), (6, bytes.clone()));
990 assert_eq!(decode_metadata_value(6, &bytes).unwrap(), value);
991 for length in [0, 8, 15, 17] {
992 assert!(decode_metadata_value(6, &vec![0; length]).is_err());
993 }
994 }
995}
996
997#[cfg(test)]
998#[allow(clippy::unwrap_used)]
999mod transaction_format_tests {
1000 use super::*;
1001 fn binding() -> TransactionBinding {
1002 TransactionBinding {
1003 transaction: 1,
1004 participant: 2,
1005 first_seq: 3,
1006 last_seq: 3,
1007 manifest_digest: 4,
1008 final_generation: 5,
1009 }
1010 }
1011 #[test]
1012 #[allow(clippy::indexing_slicing)]
1013 fn prepared_encoding_golden_and_rejections() {
1014 let body = encode_delete(&[DocId::new(6)]).unwrap();
1015 let bytes = encode_prepared(binding(), 0, 1, DELETE_V1, &body).unwrap();
1016 let hex = bytes.iter().map(|b| format!("{b:02x}")).collect::<String>();
1017 assert_eq!(
1018 hex,
1019 "010000000100000000000000000000000000000002000000000000000000000000000000030000000000000003000000000000000400000000000000050000000000000000000000010000000200010000000100000006000000000000000000000000000000"
1020 );
1021 let decoded = decode_prepared(&bytes).unwrap();
1022 assert_eq!(decoded.binding, binding());
1023 assert_eq!(
1024 decoded.mutation,
1025 MutationPayload::Delete(vec![DocId::new(6)])
1026 );
1027 for end in 0..bytes.len() {
1028 assert!(decode_prepared(&bytes[..end]).is_err(), "{end}");
1029 }
1030 for offset in [0, 2, 4, 20, 36, 60, 68, 72, 76] {
1031 let mut bad = bytes.clone();
1032 bad[offset] = 255;
1033 if matches!(offset, 4 | 20 | 36 | 60) {
1034 if let Ok(member) = decode_prepared(&bad) {
1036 assert_eq!(
1037 transaction_decision(member.binding, Some(binding())),
1038 TransactionDecision::Mismatch
1039 );
1040 }
1041 } else {
1042 assert!(decode_prepared(&bad).is_err(), "{offset}");
1043 }
1044 }
1045 assert!(encode_prepared(binding(), 0, 1, PREPARED_MUTATION_V1, &bytes).is_err());
1046 let mut trailing = bytes.clone();
1047 trailing.push(0);
1048 assert!(decode_prepared(&trailing).is_err());
1049 }
1050 #[test]
1051 fn transaction_decision_table() {
1052 assert_eq!(
1053 transaction_decision(binding(), None),
1054 TransactionDecision::Undecided
1055 );
1056 assert_eq!(
1057 transaction_decision(binding(), Some(binding())),
1058 TransactionDecision::Committed
1059 );
1060 for field in 0..6 {
1061 let mut changed = binding();
1062 match field {
1063 0 => changed.transaction += 1,
1064 1 => changed.participant += 1,
1065 2 => changed.first_seq += 1,
1066 3 => changed.last_seq += 1,
1067 4 => changed.manifest_digest += 1,
1068 _ => changed.final_generation += 1,
1069 }
1070 assert_eq!(
1071 transaction_decision(binding(), Some(changed)),
1072 TransactionDecision::Mismatch
1073 );
1074 }
1075 let mut invalid = binding();
1076 invalid.transaction = 0;
1077 assert_eq!(
1078 transaction_decision(invalid, None),
1079 TransactionDecision::Mismatch
1080 );
1081 }
1082 #[test]
1083 fn prepared_operation_is_recognized() {
1084 let mut bytes = vec![1, 0, 0, 0];
1087 bytes.extend_from_slice(&1_u128.to_le_bytes());
1088 bytes.extend_from_slice(&2_u128.to_le_bytes());
1089 for value in [3_u64, 3, 4, 5] {
1090 bytes.extend_from_slice(&value.to_le_bytes());
1091 }
1092 bytes.extend_from_slice(&0_u32.to_le_bytes());
1093 bytes.extend_from_slice(&1_u32.to_le_bytes());
1094 bytes.extend_from_slice(&2_u16.to_le_bytes());
1095 bytes.extend_from_slice(&super::encode_delete(&[super::DocId::new(6)]).unwrap());
1096 assert!(super::decode_prepared(&bytes).is_ok());
1097 }
1098}
1099
1100pub const PREPARED_MUTATION_V1: u16 = 9;
1102
1103#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1106pub struct TransactionBinding {
1107 pub transaction: u128,
1109 pub participant: u128,
1111 pub first_seq: u64,
1113 pub last_seq: u64,
1115 pub manifest_digest: u64,
1117 pub final_generation: u64,
1119}
1120impl TransactionBinding {
1121 pub fn encode(self) -> Result<Vec<u8>, PayloadError> {
1123 self.member_count()?;
1124 let mut bytes = Vec::with_capacity(64);
1125 bytes.extend_from_slice(&self.transaction.to_le_bytes());
1126 bytes.extend_from_slice(&self.participant.to_le_bytes());
1127 for value in [
1128 self.first_seq,
1129 self.last_seq,
1130 self.manifest_digest,
1131 self.final_generation,
1132 ] {
1133 bytes.extend_from_slice(&value.to_le_bytes());
1134 }
1135 Ok(bytes)
1136 }
1137 pub fn decode(bytes: &[u8]) -> Result<Self, PayloadError> {
1139 let mut cursor = Cursor::new(bytes);
1140 let binding = Self {
1141 transaction: cursor.read_u128()?,
1142 participant: cursor.read_u128()?,
1143 first_seq: cursor.read_u64()?,
1144 last_seq: cursor.read_u64()?,
1145 manifest_digest: cursor.read_u64()?,
1146 final_generation: cursor.read_u64()?,
1147 };
1148 cursor.finish()?;
1149 binding.member_count()?;
1150 Ok(binding)
1151 }
1152 fn member_count(self) -> Result<u32, PayloadError> {
1153 if self.transaction == 0
1154 || self.participant == 0
1155 || self.first_seq == 0
1156 || self.final_generation == 0
1157 {
1158 return Err(PayloadError::TransactionBinding);
1159 }
1160 self.last_seq
1161 .checked_sub(self.first_seq)
1162 .and_then(|n| n.checked_add(1))
1163 .and_then(|n| u32::try_from(n).ok())
1164 .ok_or(PayloadError::TransactionBinding)
1165 }
1166}
1167
1168#[derive(Clone, Debug, PartialEq)]
1170pub struct PreparedMutation {
1171 pub binding: TransactionBinding,
1173 pub index: u32,
1175 pub count: u32,
1177 pub op: u16,
1179 pub mutation: MutationPayload,
1181}
1182
1183pub fn encode_prepared(
1185 binding: TransactionBinding,
1186 index: u32,
1187 count: u32,
1188 op: u16,
1189 body: &[u8],
1190) -> Result<Vec<u8>, PayloadError> {
1191 if count != binding.member_count()? || index >= count {
1192 return Err(PayloadError::BatchPosition { index, count });
1193 }
1194 standalone_mutation(op, body)?;
1195 let mut bytes = Vec::new();
1196 bytes
1197 .try_reserve_exact(
1198 body.len()
1199 .checked_add(78)
1200 .ok_or(PayloadError::LengthOverflow)?,
1201 )
1202 .map_err(|_| PayloadError::LengthOverflow)?;
1203 append_header(&mut bytes);
1204 bytes.extend_from_slice(&binding.encode()?);
1205 bytes.extend_from_slice(&index.to_le_bytes());
1206 bytes.extend_from_slice(&count.to_le_bytes());
1207 bytes.extend_from_slice(&op.to_le_bytes());
1208 bytes.extend_from_slice(body);
1209 Ok(bytes)
1210}
1211
1212pub fn decode_prepared(bytes: &[u8]) -> Result<PreparedMutation, PayloadError> {
1214 let mut cursor = Cursor::new(bytes);
1215 cursor.require_header()?;
1216 let binding = TransactionBinding::decode(cursor.read_bytes(64)?)?;
1217 let index = cursor.read_u32()?;
1218 let count = cursor.read_u32()?;
1219 if count != binding.member_count()? || index >= count {
1220 return Err(PayloadError::BatchPosition { index, count });
1221 }
1222 let op = cursor.read_u16()?;
1223 let mutation = standalone_mutation(op, cursor.read_bytes(cursor.remaining())?)?;
1224 Ok(PreparedMutation {
1225 binding,
1226 index,
1227 count,
1228 op,
1229 mutation,
1230 })
1231}
1232fn standalone_mutation(op: u16, bytes: &[u8]) -> Result<MutationPayload, PayloadError> {
1233 if !(UPSERT_V1..=UPSERT_V2).contains(&op) {
1234 return Err(PayloadError::UnknownOperation(op));
1235 }
1236 decode_mutation(op, bytes)
1237}
1238
1239#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1241pub enum TransactionDecision {
1242 Committed,
1244 Undecided,
1246 Mismatch,
1248}
1249pub fn transaction_decision(
1251 prepared: TransactionBinding,
1252 selected: Option<TransactionBinding>,
1253) -> TransactionDecision {
1254 match selected {
1255 None if prepared.member_count().is_ok() => TransactionDecision::Undecided,
1256 Some(binding) if prepared.member_count().is_ok() && binding == prepared => {
1257 TransactionDecision::Committed
1258 }
1259 _ => TransactionDecision::Mismatch,
1260 }
1261}