1use alloc::string::String;
25use alloc::vec;
26use alloc::vec::Vec;
27use core::hash::Hash;
28
29use crate::IndexableValues;
30
31type UpdateValues = Vec<(MaybeValue<String, Vec<u8>>, MaybeValue<String, Vec<u8>>)>;
33
34type ParsedValues = (Vec<MaybeValue<String, Vec<u8>>>, usize);
36use crate::builders::{ChangesetFormat, DiffSet, DiffSetBuilder, Operation, PatchsetFormat};
37use crate::encoding::varint::decode_varint;
38use crate::encoding::{MaybeValue, Value, decode_value, markers, op_codes};
39use crate::schema::{DynTable, SchemaWithPK};
40
41#[non_exhaustive]
43#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
44pub enum ParseError {
45 #[error("Unexpected end of input at position {0}")]
47 UnexpectedEof(usize),
48
49 #[error("Invalid table marker 0x{0:02x} at position {1}")]
51 InvalidTableMarker(u8, usize),
52
53 #[error("Invalid operation code 0x{0:02x} at position {1}")]
55 InvalidOpCode(u8, usize),
56
57 #[error("Invalid UTF-8 in table name at position {0}")]
59 InvalidTableName(usize),
60
61 #[error("Failed to decode value at position {0}")]
63 InvalidValue(usize),
64
65 #[error("Table name not null-terminated")]
67 UnterminatedTableName,
68
69 #[error("Mixed format markers: expected {expected:?}, found {found:?} at position {position}")]
71 MixedFormats {
72 expected: FormatMarker,
74 found: FormatMarker,
76 position: usize,
78 },
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub enum FormatMarker {
84 Changeset,
86 Patchset,
88}
89
90#[derive(Debug, Clone, PartialEq, Eq, Hash)]
95pub struct TableSchema<S> {
96 name: S,
98 column_count: usize,
100 pk_flags: Vec<u8>,
107}
108
109impl<S> TableSchema<S> {
110 #[inline]
112 #[must_use]
113 pub fn new(name: S, column_count: usize, pk_flags: Vec<u8>) -> Self {
114 debug_assert_eq!(pk_flags.len(), column_count);
115 Self {
116 name,
117 column_count,
118 pk_flags,
119 }
120 }
121
122 #[inline]
124 #[must_use]
125 pub fn name(&self) -> &S {
126 &self.name
127 }
128
129 #[inline]
134 #[must_use]
135 pub fn pk_flags(&self) -> &[u8] {
136 &self.pk_flags
137 }
138
139 #[must_use]
141 pub(crate) fn pk_indices(&self) -> Vec<usize> {
142 let mut pk_cols: Vec<(usize, u8)> = self
144 .pk_flags
145 .iter()
146 .enumerate()
147 .filter_map(|(i, &pk_ordinal)| {
148 if pk_ordinal > 0 {
149 Some((i, pk_ordinal))
150 } else {
151 None
152 }
153 })
154 .collect();
155 pk_cols.sort_by_key(|(_, ordinal)| *ordinal);
157 pk_cols.into_iter().map(|(idx, _)| idx).collect()
158 }
159}
160
161impl<S: AsRef<str> + Clone + Eq + core::fmt::Debug> DynTable for TableSchema<S> {
162 #[inline]
163 fn name(&self) -> &str {
164 self.name.as_ref()
165 }
166
167 #[inline]
168 fn number_of_columns(&self) -> usize {
169 self.column_count
170 }
171
172 #[inline]
173 fn write_pk_flags(&self, buf: &mut [u8]) {
174 assert_eq!(buf.len(), self.column_count);
175 buf.copy_from_slice(&self.pk_flags);
176 }
177}
178
179impl<N: AsRef<str> + Clone + core::hash::Hash + Eq + core::fmt::Debug> SchemaWithPK
180 for TableSchema<N>
181{
182 fn number_of_primary_keys(&self) -> usize {
183 self.pk_flags.iter().filter(|&&b| b > 0).count()
184 }
185
186 fn primary_key_index(&self, col_idx: usize) -> Option<usize> {
187 self.pk_flags.get(col_idx).and_then(|&pk_ordinal| {
188 if pk_ordinal > 0 {
189 Some(usize::from(pk_ordinal - 1))
190 } else {
191 None
192 }
193 })
194 }
195
196 fn extract_pk<S, B>(
197 &self,
198 values: &impl IndexableValues<Text = S, Binary = B>,
199 ) -> alloc::vec::Vec<Value<S, B>>
200 where
201 S: Clone,
202 B: Clone,
203 {
204 self.pk_indices()
205 .into_iter()
206 .map(|i| {
207 values
208 .get(i)
209 .expect("primary key column index out of bounds, values shorter than schema")
210 })
211 .collect()
212 }
213}
214
215#[derive(Debug, Clone, Eq)]
220pub enum ParsedDiffSet {
221 Changeset(DiffSet<ChangesetFormat, TableSchema<String>, String, Vec<u8>>),
223 Patchset(DiffSet<PatchsetFormat, TableSchema<String>, String, Vec<u8>>),
225}
226
227impl PartialEq for ParsedDiffSet {
228 fn eq(&self, other: &Self) -> bool {
229 let self_empty = match self {
230 ParsedDiffSet::Changeset(d) => d.is_empty(),
231 ParsedDiffSet::Patchset(d) => d.is_empty(),
232 };
233 let other_empty = match other {
234 ParsedDiffSet::Changeset(d) => d.is_empty(),
235 ParsedDiffSet::Patchset(d) => d.is_empty(),
236 };
237
238 if self_empty && other_empty {
239 return true;
240 }
241
242 match (self, other) {
244 (ParsedDiffSet::Changeset(a), ParsedDiffSet::Changeset(b)) => a == b,
245 (ParsedDiffSet::Patchset(a), ParsedDiffSet::Patchset(b)) => a == b,
246 _ => false,
247 }
248 }
249}
250
251impl TryFrom<&[u8]> for ParsedDiffSet {
252 type Error = ParseError;
253
254 fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
255 Self::parse(data)
256 }
257}
258
259impl From<ParsedDiffSet> for Vec<u8> {
260 fn from(diffset: ParsedDiffSet) -> Self {
261 match diffset {
262 ParsedDiffSet::Changeset(d) => d.into(),
263 ParsedDiffSet::Patchset(d) => d.into(),
264 }
265 }
266}
267
268impl ParsedDiffSet {
269 pub fn parse(data: &[u8]) -> Result<Self, ParseError> {
277 if data.is_empty() {
278 return Ok(ParsedDiffSet::Changeset(DiffSet::default()));
280 }
281
282 match data[0] {
284 markers::CHANGESET => {
285 let diffset = parse_as_changeset(data)?;
286 Ok(ParsedDiffSet::Changeset(diffset))
287 }
288 markers::PATCHSET => {
289 let diffset = parse_as_patchset(data)?;
290 Ok(ParsedDiffSet::Patchset(diffset))
291 }
292 b => Err(ParseError::InvalidTableMarker(b, 0)),
293 }
294 }
295
296 #[must_use]
298 pub fn is_changeset(&self) -> bool {
299 matches!(self, ParsedDiffSet::Changeset(_))
300 }
301
302 #[must_use]
304 pub fn is_patchset(&self) -> bool {
305 matches!(self, ParsedDiffSet::Patchset(_))
306 }
307
308 #[must_use]
310 pub fn table_schemas(&self) -> Vec<&TableSchema<String>> {
311 match self {
312 ParsedDiffSet::Changeset(d) => d
313 .tables
314 .iter()
315 .filter(|(_, ops)| !ops.is_empty())
316 .map(|(schema, _)| schema)
317 .collect(),
318 ParsedDiffSet::Patchset(d) => d
319 .tables
320 .iter()
321 .filter(|(_, ops)| !ops.is_empty())
322 .map(|(schema, _)| schema)
323 .collect(),
324 }
325 }
326
327 pub fn rename_tables<F>(&mut self, mut rename: F) -> usize
334 where
335 F: FnMut(&str) -> Option<String>,
336 {
337 fn rename_in<Fmt, F>(tables: &mut [(TableSchema<String>, Fmt)], rename: &mut F) -> usize
338 where
339 F: FnMut(&str) -> Option<String>,
340 {
341 let mut renamed = 0;
342 for (schema, _) in tables.iter_mut() {
343 if let Some(new_name) = rename(schema.name.as_str()) {
344 schema.name = new_name;
345 renamed += 1;
346 }
347 }
348 renamed
349 }
350
351 match self {
352 ParsedDiffSet::Changeset(d) => rename_in(&mut d.tables, &mut rename),
353 ParsedDiffSet::Patchset(d) => rename_in(&mut d.tables, &mut rename),
354 }
355 }
356}
357
358fn parse_as_changeset(
364 data: &[u8],
365) -> Result<DiffSet<ChangesetFormat, TableSchema<String>, String, Vec<u8>>, ParseError> {
366 let mut builder: DiffSetBuilder<ChangesetFormat, TableSchema<String>, String, Vec<u8>> =
367 DiffSetBuilder::new();
368 let mut pos = 0;
369
370 while pos < data.len() {
371 let (schema, format, header_len) = parse_table_header(&data[pos..], pos)?;
372 if format != FormatMarker::Changeset {
373 return Err(ParseError::MixedFormats {
374 expected: FormatMarker::Changeset,
375 found: format,
376 position: pos,
377 });
378 }
379 pos += header_len;
380
381 while pos < data.len() {
382 let byte = data[pos];
383 if byte == markers::CHANGESET || byte == markers::PATCHSET {
384 break;
385 }
386 let op_len = parse_changeset_operation(&data[pos..], pos, &schema, &mut builder)?;
387 pos += op_len;
388 }
389 }
390
391 Ok(builder.into())
392}
393
394fn parse_as_patchset(
400 data: &[u8],
401) -> Result<DiffSet<PatchsetFormat, TableSchema<String>, String, Vec<u8>>, ParseError> {
402 let mut builder: DiffSetBuilder<PatchsetFormat, TableSchema<String>, String, Vec<u8>> =
403 DiffSetBuilder::new();
404 let mut pos = 0;
405
406 while pos < data.len() {
407 let (schema, format, header_len) = parse_table_header(&data[pos..], pos)?;
408 if format != FormatMarker::Patchset {
409 return Err(ParseError::MixedFormats {
410 expected: FormatMarker::Patchset,
411 found: format,
412 position: pos,
413 });
414 }
415 pos += header_len;
416
417 while pos < data.len() {
418 let byte = data[pos];
419 if byte == markers::CHANGESET || byte == markers::PATCHSET {
420 break;
421 }
422 let op_len = parse_patchset_operation(&data[pos..], pos, &schema, &mut builder)?;
423 pos += op_len;
424 }
425 }
426
427 Ok(builder.into())
428}
429
430fn parse_table_header(
432 data: &[u8],
433 base_pos: usize,
434) -> Result<(TableSchema<String>, FormatMarker, usize), ParseError> {
435 let mut pos = 0;
436
437 if data.is_empty() {
438 return Err(ParseError::UnexpectedEof(base_pos));
439 }
440 let format = match data[pos] {
441 markers::CHANGESET => FormatMarker::Changeset,
442 markers::PATCHSET => FormatMarker::Patchset,
443 b => return Err(ParseError::InvalidTableMarker(b, base_pos + pos)),
444 };
445 pos += 1;
446
447 let (column_count, varint_len) = decode_varint(&data[pos..])
448 .ok_or(ParseError::UnexpectedEof(base_pos + pos))
449 .and_then(|(count, len)| {
450 usize::try_from(count)
451 .map(|count| (count, len))
452 .map_err(|_| ParseError::UnexpectedEof(base_pos + pos))
453 })?;
454 pos += varint_len;
455
456 if pos + column_count > data.len() {
457 return Err(ParseError::UnexpectedEof(base_pos + pos));
458 }
459 let pk_flags: Vec<u8> = data[pos..pos + column_count].to_vec();
460 pos += column_count;
461
462 let name_start = pos;
463 while pos < data.len() && data[pos] != 0 {
464 pos += 1;
465 }
466 if pos >= data.len() {
467 return Err(ParseError::UnterminatedTableName);
468 }
469 let name = String::from_utf8(data[name_start..pos].to_vec())
470 .map_err(|_| ParseError::InvalidTableName(base_pos + name_start))?;
471 pos += 1;
472
473 Ok((TableSchema::new(name, column_count, pk_flags), format, pos))
474}
475
476fn parse_operation_header(data: &[u8], base_pos: usize) -> Result<(u8, bool, usize), ParseError> {
481 if data.len() < 2 {
482 return Err(ParseError::UnexpectedEof(base_pos));
483 }
484 Ok((data[0], data[1] != 0, 2))
485}
486
487fn parse_changeset_operation(
489 data: &[u8],
490 base_pos: usize,
491 schema: &TableSchema<String>,
492 builder: &mut DiffSetBuilder<ChangesetFormat, TableSchema<String>, String, Vec<u8>>,
493) -> Result<usize, ParseError> {
494 let (op_code, indirect, mut pos) = parse_operation_header(data, base_pos)?;
495
496 match op_code {
497 op_codes::INSERT => {
498 let (values, len) = parse_values(&data[pos..], base_pos + pos, schema.column_count)?;
499 pos += len;
500 let values: Vec<Value<String, Vec<u8>>> = values
501 .into_iter()
502 .map(|v| v.unwrap_or(Value::Null))
503 .collect();
504 let pk = schema.extract_pk(&values);
505 builder.add_operation(schema, pk, Operation::Insert { values, indirect });
506 }
507 op_codes::DELETE => {
508 let (values, len) = parse_values(&data[pos..], base_pos + pos, schema.column_count)?;
509 pos += len;
510 let values: Vec<Value<String, Vec<u8>>> = values
511 .into_iter()
512 .map(|v| v.unwrap_or(Value::Null))
513 .collect();
514 let pk = schema.extract_pk(&values);
515 builder.add_operation(
516 schema,
517 pk,
518 Operation::Delete {
519 data: values,
520 indirect,
521 },
522 );
523 }
524 op_codes::UPDATE => {
525 let (old_values, old_len) =
526 parse_values(&data[pos..], base_pos + pos, schema.column_count)?;
527 pos += old_len;
528 let (new_values, new_len) =
529 parse_values(&data[pos..], base_pos + pos, schema.column_count)?;
530 pos += new_len;
531 let pk_values: Vec<Value<String, Vec<u8>>> = old_values
533 .iter()
534 .map(|v| v.clone().unwrap_or(Value::Null))
535 .collect();
536 let pk = schema.extract_pk(&pk_values);
537 let values: UpdateValues = old_values.into_iter().zip(new_values).collect();
538 builder.add_operation(schema, pk, Operation::Update { values, indirect });
539 }
540 _ => return Err(ParseError::InvalidOpCode(op_code, base_pos)),
541 }
542
543 Ok(pos)
544}
545
546fn parse_patchset_operation(
548 data: &[u8],
549 base_pos: usize,
550 schema: &TableSchema<String>,
551 builder: &mut DiffSetBuilder<PatchsetFormat, TableSchema<String>, String, Vec<u8>>,
552) -> Result<usize, ParseError> {
553 let (op_code, indirect, mut pos) = parse_operation_header(data, base_pos)?;
554
555 match op_code {
556 op_codes::INSERT => {
557 let (values, len) = parse_values(&data[pos..], base_pos + pos, schema.column_count)?;
558 pos += len;
559 let values: Vec<Value<String, Vec<u8>>> = values
560 .into_iter()
561 .map(|v| v.unwrap_or(Value::Null))
562 .collect();
563 let pk = schema.extract_pk(&values);
564 builder.add_operation(schema, pk, Operation::Insert { values, indirect });
565 }
566 op_codes::DELETE => {
567 let pk_count = schema.pk_flags.iter().filter(|&&b| b > 0).count();
569 let (pk_values, len) = parse_values(&data[pos..], base_pos + pos, pk_count)?;
570 pos += len;
571 let full_values = expand_pk_values(&schema.pk_flags, pk_values, schema.column_count);
575 let full_values_concrete: Vec<Value<String, Vec<u8>>> = full_values
577 .into_iter()
578 .map(|v| v.unwrap_or(Value::Null))
579 .collect();
580 let pk = schema.extract_pk(&full_values_concrete);
581 builder.add_operation(schema, pk, Operation::Delete { data: (), indirect });
582 }
583 op_codes::UPDATE => {
584 let (record, record_len) =
595 parse_values(&data[pos..], base_pos + pos, schema.column_count)?;
596 pos += record_len;
597
598 let mut values: Vec<((), MaybeValue<String, Vec<u8>>)> =
599 alloc::vec![((), None); schema.column_count];
600 for (col_idx, (&pk_flag, entry)) in schema.pk_flags.iter().zip(record).enumerate() {
601 if pk_flag > 0 {
602 values[col_idx] = ((), Some(entry.unwrap_or(Value::Null)));
606 } else {
607 values[col_idx] = ((), entry);
608 }
609 }
610
611 let pk = schema.extract_pk(&values);
612 builder.add_operation(schema, pk, Operation::Update { values, indirect });
613 }
614 _ => return Err(ParseError::InvalidOpCode(op_code, base_pos)),
615 }
616
617 Ok(pos)
618}
619
620fn expand_pk_values(
625 pk_flags: &[u8],
626 pk_values: Vec<MaybeValue<String, Vec<u8>>>,
627 column_count: usize,
628) -> Vec<MaybeValue<String, Vec<u8>>> {
629 let mut full: Vec<MaybeValue<String, Vec<u8>>> = vec![None; column_count];
630 let mut pk_iter = pk_values.into_iter();
631 for (i, &pk_ordinal) in pk_flags.iter().enumerate() {
632 if pk_ordinal > 0
633 && let Some(v) = pk_iter.next()
634 {
635 full[i] = v;
636 }
637 }
638 full
639}
640
641fn parse_values(data: &[u8], base_pos: usize, count: usize) -> Result<ParsedValues, ParseError> {
643 let mut values = Vec::with_capacity(count);
644 let mut pos = 0;
645
646 for _ in 0..count {
647 let (value, value_len) =
648 decode_value(&data[pos..]).ok_or(ParseError::InvalidValue(base_pos + pos))?;
649 values.push(value);
650 pos += value_len;
651 }
652
653 Ok((values, pos))
654}
655
656#[cfg(test)]
657mod tests {
658 use super::*;
659 use crate::SimpleTable;
660 use alloc::vec;
661
662 #[test]
663 fn test_parse_empty() {
664 let result = ParsedDiffSet::parse(&[]);
665 assert!(result.is_ok());
666 assert!(result.unwrap().is_changeset());
667 }
668
669 #[test]
670 fn test_parse_table_header() {
671 let data = [b'T', 2, 1, 0, b't', 0];
673 let (schema, format, len) = parse_table_header(&data, 0).unwrap();
674
675 assert_eq!(format, FormatMarker::Changeset);
676 assert_eq!(schema.column_count, 2);
677 assert_eq!(schema.pk_flags, vec![1, 0]); assert_eq!(schema.name, "t");
679 assert_eq!(len, 6);
680 }
681
682 #[test]
683 fn test_parse_insert_changeset() {
684 let mut data = vec![b'T', 2, 1, 0, b't', 0];
686 data.push(op_codes::INSERT);
688 data.push(0);
689 data.push(0x01);
691 data.extend(&1i64.to_be_bytes());
692 data.push(0x03);
694 data.push(1);
695 data.push(b'a');
696
697 let parsed = ParsedDiffSet::parse(&data).unwrap();
698 assert!(parsed.is_changeset());
699 }
700
701 #[test]
702 fn test_parse_delete_changeset() {
703 let mut data = vec![b'T', 2, 1, 0, b't', 0];
704 data.push(op_codes::DELETE);
705 data.push(0);
706 data.push(0x01);
708 data.extend(&1i64.to_be_bytes());
709 data.push(0x03);
711 data.push(1);
712 data.push(b'a');
713
714 let parsed = ParsedDiffSet::parse(&data).unwrap();
715 assert!(parsed.is_changeset());
716 }
717
718 #[test]
719 fn test_parse_delete_patchset() {
720 let mut data = vec![b'P', 2, 1, 0, b't', 0];
722 data.push(op_codes::DELETE);
723 data.push(0);
724 data.push(0x01);
726 data.extend(&1i64.to_be_bytes());
727
728 let parsed = ParsedDiffSet::parse(&data).unwrap();
729 assert!(parsed.is_patchset());
730 }
731
732 #[test]
733 fn test_parse_update_changeset() {
734 let mut data = vec![b'T', 2, 1, 0, b't', 0];
735 data.push(op_codes::UPDATE);
736 data.push(0);
737 data.push(0x01);
739 data.extend(&1i64.to_be_bytes());
740 data.push(0x03);
741 data.push(1);
742 data.push(b'a');
743 data.push(0x01);
745 data.extend(&1i64.to_be_bytes());
746 data.push(0x03);
747 data.push(1);
748 data.push(b'b');
749
750 let parsed = ParsedDiffSet::parse(&data).unwrap();
751 assert!(parsed.is_changeset());
752 }
753
754 #[test]
755 fn test_is_changeset() {
756 let data = vec![b'T', 1, 1, b't', 0];
757 let parsed = ParsedDiffSet::parse(&data).unwrap();
758 assert!(parsed.is_changeset());
759 assert!(!parsed.is_patchset());
760 }
761
762 #[test]
763 fn test_is_patchset() {
764 let data = vec![b'P', 1, 1, b't', 0];
765 let parsed = ParsedDiffSet::parse(&data).unwrap();
766 assert!(parsed.is_patchset());
767 assert!(!parsed.is_changeset());
768 }
769
770 #[test]
771 fn test_parsed_table_schema_dyn_table() {
772 let schema: TableSchema<String> = TableSchema::new("users".into(), 3, vec![1, 0, 0]);
773 assert_eq!(schema.name(), "users");
774 assert_eq!(schema.number_of_columns(), 3);
775
776 let mut buf = [0u8; 3];
777 schema.write_pk_flags(&mut buf);
778 assert_eq!(buf, [1, 0, 0]);
779 }
780
781 #[test]
782 fn test_parsed_table_schema_extract_pk() {
783 let schema: TableSchema<String> = TableSchema::new("users".into(), 3, vec![1, 0, 2]);
784 let values: Vec<Value<String, Vec<u8>>> = vec![
785 Value::Integer(1),
786 Value::Text("alice".into()),
787 Value::Integer(100),
788 ];
789 let pk = schema.extract_pk(&values);
790 let expected: Vec<Value<String, Vec<u8>>> = vec![Value::Integer(1), Value::Integer(100)];
791 assert_eq!(pk, expected);
792 }
793
794 #[test]
797 fn test_parse_invalid_table_marker() {
798 let data = [0xFFu8, 1, 1, b't', 0];
799 let err = ParsedDiffSet::parse(&data).unwrap_err();
800 assert!(
801 matches!(err, ParseError::InvalidTableMarker(0xFF, 0)),
802 "got {err:?}"
803 );
804 }
805
806 #[test]
807 fn test_parse_unexpected_eof_in_table_header() {
808 let data = *b"T";
810 let err = ParsedDiffSet::parse(&data).unwrap_err();
811 assert!(matches!(err, ParseError::UnexpectedEof(_)), "got {err:?}");
812 }
813
814 #[test]
815 fn test_parse_unexpected_eof_in_pk_flags() {
816 let data = [b'T', 3, 1];
818 let err = ParsedDiffSet::parse(&data).unwrap_err();
819 assert!(matches!(err, ParseError::UnexpectedEof(_)), "got {err:?}");
820 }
821
822 #[test]
823 fn test_parse_unterminated_table_name() {
824 let data = [b'T', 1, 1, b'a', b'b', b'c'];
826 let err = ParsedDiffSet::parse(&data).unwrap_err();
827 assert!(
828 matches!(err, ParseError::UnterminatedTableName),
829 "got {err:?}"
830 );
831 }
832
833 #[test]
834 fn test_parse_invalid_utf8_in_table_name() {
835 let data = [b'T', 1, 1, 0xFF, 0];
837 let err = ParsedDiffSet::parse(&data).unwrap_err();
838 assert!(
839 matches!(err, ParseError::InvalidTableName(_)),
840 "got {err:?}"
841 );
842 }
843
844 #[test]
845 fn test_parse_mixed_formats_changeset_then_patchset() {
846 let mut data = vec![b'T', 1, 1, b'a', 0];
848 data.extend_from_slice(&[b'P', 1, 1, b'b', 0]);
850 let err = ParsedDiffSet::parse(&data).unwrap_err();
851 assert!(
852 matches!(
853 err,
854 ParseError::MixedFormats {
855 expected: FormatMarker::Changeset,
856 found: FormatMarker::Patchset,
857 ..
858 }
859 ),
860 "got {err:?}"
861 );
862 }
863
864 #[test]
865 fn test_parse_mixed_formats_patchset_then_changeset() {
866 let mut data = vec![b'P', 1, 1, b'a', 0];
867 data.extend_from_slice(&[b'T', 1, 1, b'b', 0]);
868 let err = ParsedDiffSet::parse(&data).unwrap_err();
869 assert!(
870 matches!(
871 err,
872 ParseError::MixedFormats {
873 expected: FormatMarker::Patchset,
874 found: FormatMarker::Changeset,
875 ..
876 }
877 ),
878 "got {err:?}"
879 );
880 }
881
882 fn make_insert_with_indirect(indirect_byte: u8) -> Vec<u8> {
884 let mut data = vec![b'T', 1, 1, b't', 0];
885 data.push(op_codes::INSERT);
886 data.push(indirect_byte);
887 data.push(0x01);
889 data.extend(&1i64.to_be_bytes());
890 data
891 }
892
893 fn first_op_indirect_changeset(data: &[u8]) -> bool {
894 let parsed = ParsedDiffSet::parse(data).unwrap();
895 let ParsedDiffSet::Changeset(set) = parsed else {
896 panic!("expected Changeset");
897 };
898 set.tables
899 .iter()
900 .find_map(|(_schema, rows)| rows.first().map(|(_, op)| op.indirect()))
901 .expect("expected at least one op")
902 }
903
904 #[test]
905 fn test_parse_changeset_indirect_flag_set() {
906 let data = make_insert_with_indirect(1);
907 assert!(first_op_indirect_changeset(&data));
908 }
909
910 #[test]
911 fn test_parse_changeset_indirect_flag_clear() {
912 let data = make_insert_with_indirect(0);
913 assert!(!first_op_indirect_changeset(&data));
914 }
915
916 #[test]
917 fn test_parse_indirect_nonzero_treated_as_true() {
918 let data = make_insert_with_indirect(0x42);
920 assert!(first_op_indirect_changeset(&data));
921 }
922
923 #[test]
924 fn test_parsed_diffset_variant_mismatch_partial_eq() {
925 let changeset = ParsedDiffSet::parse(&[b'T', 1, 1, b't', 0]).unwrap();
926 let patchset = ParsedDiffSet::parse(&[b'P', 1, 1, b't', 0]).unwrap();
927 let mut full_changeset = vec![b'T', 1, 1, b't', 0];
930 full_changeset.push(op_codes::INSERT);
931 full_changeset.push(0);
932 full_changeset.push(0x01);
933 full_changeset.extend(&1i64.to_be_bytes());
934 let cs = ParsedDiffSet::parse(&full_changeset).unwrap();
935
936 let mut full_patchset = vec![b'P', 1, 1, b't', 0];
937 full_patchset.push(op_codes::INSERT);
938 full_patchset.push(0);
939 full_patchset.push(0x01);
940 full_patchset.extend(&1i64.to_be_bytes());
941 let ps = ParsedDiffSet::parse(&full_patchset).unwrap();
942
943 assert_ne!(cs, ps);
944 assert_eq!(changeset, patchset);
946 }
947
948 #[test]
949 fn test_parse_unexpected_eof_in_operation_header() {
950 let data = [b'T', 1, 1, b't', 0, op_codes::INSERT];
953 let err = ParsedDiffSet::parse(&data).unwrap_err();
954 assert!(matches!(err, ParseError::UnexpectedEof(_)), "got {err:?}");
955 }
956
957 #[test]
958 fn test_parse_patchset_indirect_flag_set() {
959 let mut data = vec![b'P', 1, 1, b't', 0];
961 data.push(op_codes::INSERT);
962 data.push(1);
963 data.push(0x01);
964 data.extend(&1i64.to_be_bytes());
965
966 let parsed = ParsedDiffSet::parse(&data).unwrap();
967 let ParsedDiffSet::Patchset(set) = parsed else {
968 panic!("expected Patchset");
969 };
970 let indirect = set
971 .tables
972 .iter()
973 .find_map(|(_schema, rows)| rows.first().map(|(_, op)| op.indirect()))
974 .expect("expected at least one op");
975 assert!(indirect);
976 }
977
978 fn assert_patchset_update_roundtrip(
988 data: &[u8],
989 check: impl FnOnce(
990 &TableSchema<String>,
991 &[Value<String, Vec<u8>>],
992 &[((), MaybeValue<String, Vec<u8>>)],
993 bool,
994 ),
995 ) {
996 let parsed = ParsedDiffSet::parse(data).expect("SQLite patchset UPDATE must parse");
997 let ParsedDiffSet::Patchset(set) = parsed else {
998 panic!("expected Patchset, got {parsed:?}");
999 };
1000 let (schema, rows) = set.tables.first().expect("expected one table");
1001 assert_eq!(rows.len(), 1, "expected exactly one row");
1002 let (pk, op) = rows.first().expect("row map non-empty");
1003 let Operation::Update { values, indirect } = op else {
1004 panic!("expected Update, got {op:?}");
1005 };
1006 check(schema, pk.as_slice(), values.as_slice(), *indirect);
1007 let serialized: Vec<u8> = set.into();
1008 assert_eq!(serialized, data, "roundtrip must match SQLite output");
1009 }
1010
1011 #[test]
1029 fn test_parse_patchset_update_sqlite_wire_layout_single_pk() {
1030 let data: [u8; 33] = [
1031 0x50, 0x03, 0x01, 0x00, 0x00, b'o', b'r', b'd', b'e', b'r', b's', 0x00, 0x17, 0x00,
1032 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x03, 0x07, b's', b'h',
1033 b'i', b'p', b'p', b'e', b'd',
1034 ];
1035 assert_patchset_update_roundtrip(&data, |schema, pk, values, indirect| {
1036 assert_eq!(schema.name, "orders");
1037 assert_eq!(schema.column_count, 3);
1038 assert_eq!(schema.pk_flags, vec![1, 0, 0]);
1039 assert_eq!(pk, &[Value::Integer(5)]);
1040 assert!(!indirect);
1041 assert_eq!(values.len(), 3);
1042 assert_eq!(values[0].1, Some(Value::Integer(5))); assert_eq!(values[1].1, None); assert_eq!(values[2].1, Some(Value::Text("shipped".into())));
1045 });
1046 }
1047
1048 #[test]
1059 fn test_parse_patchset_update_sqlite_wire_layout_composite_pk() {
1060 let data: [u8; 35] = [
1061 0x50, 0x03, 0x01, 0x02, 0x00, b'i', b't', b'e', b'm', b's', 0x00, 0x17, 0x00, 0x01,
1062 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
1063 0x00, 0x00, 0x02, 0x03, 0x02, b'v', b'2',
1064 ];
1065 assert_patchset_update_roundtrip(&data, |schema, pk, values, _indirect| {
1066 assert_eq!(schema.name, "items");
1067 assert_eq!(schema.pk_flags, vec![1, 2, 0]);
1068 assert_eq!(pk, &[Value::Integer(1), Value::Integer(2)]);
1071 assert_eq!(values.len(), 3);
1072 assert_eq!(values[0].1, Some(Value::Integer(1))); assert_eq!(values[1].1, Some(Value::Integer(2))); assert_eq!(values[2].1, Some(Value::Text("v2".into())));
1075 });
1076 }
1077
1078 #[test]
1088 fn test_parse_patchset_update_all_non_pk_changed() {
1089 let data: [u8; 41] = [
1090 0x50, 0x03, 0x01, 0x00, 0x00, b'o', b'r', b'd', b'e', b'r', b's', 0x00, 0x17, 0x00,
1091 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00,
1092 0x00, 0x00, 0x00, 0xc8, 0x03, 0x07, b's', b'h', b'i', b'p', b'p', b'e', b'd',
1093 ];
1094 assert_patchset_update_roundtrip(&data, |_schema, _pk, values, _indirect| {
1095 assert_eq!(values[0].1, Some(Value::Integer(5)));
1096 assert_eq!(values[1].1, Some(Value::Integer(200)));
1097 assert_eq!(values[2].1, Some(Value::Text("shipped".into())));
1098 });
1099 }
1100
1101 fn assert_schema_pk_parity(
1105 parsed: &TableSchema<String>,
1106 simple: &SimpleTable,
1107 row: &[Value<String, Vec<u8>>],
1108 ) {
1109 assert_eq!(
1110 parsed.number_of_primary_keys(),
1111 simple.number_of_primary_keys(),
1112 "number_of_primary_keys",
1113 );
1114 for col in 0..simple.number_of_columns() {
1115 assert_eq!(
1116 parsed.primary_key_index(col),
1117 simple.primary_key_index(col),
1118 "primary_key_index at col {col}",
1119 );
1120 }
1121 assert_eq!(
1122 parsed.primary_key_columns(),
1123 simple.primary_key_columns(),
1124 "primary_key_columns",
1125 );
1126 assert_eq!(
1127 parsed.extract_pk(&row),
1128 simple.extract_pk(&row),
1129 "extract_pk"
1130 );
1131 }
1132
1133 #[test]
1134 fn test_parsed_schema_pk_parity_single_key() {
1135 let mut data = vec![b'T', 2, 1, 0, b'k', b'v', 0];
1137 data.push(op_codes::INSERT);
1138 data.push(0);
1139 data.push(0x01);
1140 data.extend(&1i64.to_be_bytes());
1141 data.push(0x03);
1142 data.push(1);
1143 data.push(b'x');
1144
1145 let ParsedDiffSet::Changeset(set) = ParsedDiffSet::parse(&data).unwrap() else {
1146 panic!("expected changeset");
1147 };
1148 let (parsed, _rows) = set.tables.first().expect("one table");
1149 assert_eq!(parsed.pk_flags(), &[1, 0]);
1150
1151 let simple = SimpleTable::new("kv", &["id", "val"], &[0]);
1152 let row: Vec<Value<String, Vec<u8>>> = vec![Value::Integer(1), Value::Text("x".into())];
1153 assert_schema_pk_parity(parsed, &simple, &row);
1154 assert_eq!(parsed.primary_key_columns(), vec![0]);
1155 }
1156
1157 #[test]
1158 fn test_parsed_schema_pk_parity_composite_reordered_key() {
1159 let mut data = vec![b'T', 3, 2, 1, 0, b'a', b'b', b'c', 0];
1162 data.push(op_codes::INSERT);
1163 data.push(0);
1164 data.push(0x01);
1165 data.extend(&10i64.to_be_bytes());
1166 data.push(0x01);
1167 data.extend(&20i64.to_be_bytes());
1168 data.push(0x03);
1169 data.push(1);
1170 data.push(b'z');
1171
1172 let ParsedDiffSet::Changeset(set) = ParsedDiffSet::parse(&data).unwrap() else {
1173 panic!("expected changeset");
1174 };
1175 let (parsed, _rows) = set.tables.first().expect("one table");
1176 assert_eq!(parsed.pk_flags(), &[2, 1, 0]);
1177
1178 let simple = SimpleTable::new("abc", &["a", "b", "c"], &[1, 0]);
1179 let row: Vec<Value<String, Vec<u8>>> = vec![
1180 Value::Integer(10),
1181 Value::Integer(20),
1182 Value::Text("z".into()),
1183 ];
1184 assert_schema_pk_parity(parsed, &simple, &row);
1185 assert_eq!(parsed.primary_key_columns(), vec![1, 0]);
1187 assert_eq!(
1189 parsed.extract_pk(&row),
1190 vec![Value::Integer(20), Value::Integer(10)]
1191 );
1192 }
1193}