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::{MaybeValue, Value, decode_value, markers, op_codes};
38use crate::schema::{DynTable, SchemaWithPK};
39
40#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
42pub enum ParseError {
43 #[error("Unexpected end of input at position {0}")]
45 UnexpectedEof(usize),
46
47 #[error("Invalid table marker 0x{0:02x} at position {1}")]
49 InvalidTableMarker(u8, usize),
50
51 #[error("Invalid operation code 0x{0:02x} at position {1}")]
53 InvalidOpCode(u8, usize),
54
55 #[error("Invalid UTF-8 in table name at position {0}")]
57 InvalidTableName(usize),
58
59 #[error("Failed to decode value at position {0}")]
61 InvalidValue(usize),
62
63 #[error("Table name not null-terminated")]
65 UnterminatedTableName,
66
67 #[error("Mixed format markers: expected {expected:?}, found {found:?} at position {position}")]
69 MixedFormats {
70 expected: FormatMarker,
72 found: FormatMarker,
74 position: usize,
76 },
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81pub enum FormatMarker {
82 Changeset,
84 Patchset,
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, Hash)]
93pub struct TableSchema<S> {
94 name: S,
96 column_count: usize,
98 pk_flags: Vec<u8>,
105}
106
107impl<S> TableSchema<S> {
108 #[inline]
110 #[must_use]
111 pub fn new(name: S, column_count: usize, pk_flags: Vec<u8>) -> Self {
112 debug_assert_eq!(pk_flags.len(), column_count);
113 Self {
114 name,
115 column_count,
116 pk_flags,
117 }
118 }
119
120 #[inline]
122 #[must_use]
123 pub fn name(&self) -> &S {
124 &self.name
125 }
126
127 #[inline]
132 #[must_use]
133 pub fn pk_flags(&self) -> &[u8] {
134 &self.pk_flags
135 }
136
137 #[must_use]
139 pub(crate) fn pk_indices(&self) -> Vec<usize> {
140 let mut pk_cols: Vec<(usize, u8)> = self
142 .pk_flags
143 .iter()
144 .enumerate()
145 .filter_map(|(i, &pk_ordinal)| {
146 if pk_ordinal > 0 {
147 Some((i, pk_ordinal))
148 } else {
149 None
150 }
151 })
152 .collect();
153 pk_cols.sort_by_key(|(_, ordinal)| *ordinal);
155 pk_cols.into_iter().map(|(idx, _)| idx).collect()
156 }
157}
158
159impl<S: AsRef<str> + Clone + Eq + core::fmt::Debug> DynTable for TableSchema<S> {
160 #[inline]
161 fn name(&self) -> &str {
162 self.name.as_ref()
163 }
164
165 #[inline]
166 fn number_of_columns(&self) -> usize {
167 self.column_count
168 }
169
170 #[inline]
171 fn write_pk_flags(&self, buf: &mut [u8]) {
172 assert_eq!(buf.len(), self.column_count);
173 buf.copy_from_slice(&self.pk_flags);
174 }
175}
176
177impl<N: AsRef<str> + Clone + core::hash::Hash + Eq + core::fmt::Debug> SchemaWithPK
178 for TableSchema<N>
179{
180 fn number_of_primary_keys(&self) -> usize {
181 self.pk_flags.iter().filter(|&&b| b > 0).count()
182 }
183
184 fn primary_key_index(&self, col_idx: usize) -> Option<usize> {
185 self.pk_flags.get(col_idx).and_then(|&pk_ordinal| {
186 if pk_ordinal > 0 {
187 Some(usize::from(pk_ordinal - 1))
188 } else {
189 None
190 }
191 })
192 }
193
194 fn extract_pk<S, B>(
195 &self,
196 values: &impl IndexableValues<Text = S, Binary = B>,
197 ) -> alloc::vec::Vec<Value<S, B>>
198 where
199 S: Clone,
200 B: Clone,
201 {
202 self.pk_indices()
203 .into_iter()
204 .map(|i| {
205 values
206 .get(i)
207 .expect("primary key column index out of bounds, values shorter than schema")
208 })
209 .collect()
210 }
211}
212
213#[derive(Debug, Clone, Eq)]
218pub enum ParsedDiffSet {
219 Changeset(DiffSet<ChangesetFormat, TableSchema<String>, String, Vec<u8>>),
221 Patchset(DiffSet<PatchsetFormat, TableSchema<String>, String, Vec<u8>>),
223}
224
225impl PartialEq for ParsedDiffSet {
226 fn eq(&self, other: &Self) -> bool {
227 let self_empty = match self {
228 ParsedDiffSet::Changeset(d) => d.is_empty(),
229 ParsedDiffSet::Patchset(d) => d.is_empty(),
230 };
231 let other_empty = match other {
232 ParsedDiffSet::Changeset(d) => d.is_empty(),
233 ParsedDiffSet::Patchset(d) => d.is_empty(),
234 };
235
236 if self_empty && other_empty {
237 return true;
238 }
239
240 match (self, other) {
242 (ParsedDiffSet::Changeset(a), ParsedDiffSet::Changeset(b)) => a == b,
243 (ParsedDiffSet::Patchset(a), ParsedDiffSet::Patchset(b)) => a == b,
244 _ => false,
245 }
246 }
247}
248
249impl TryFrom<&[u8]> for ParsedDiffSet {
250 type Error = ParseError;
251
252 fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
253 Self::parse(data)
254 }
255}
256
257impl From<ParsedDiffSet> for Vec<u8> {
258 fn from(diffset: ParsedDiffSet) -> Self {
259 match diffset {
260 ParsedDiffSet::Changeset(d) => d.into(),
261 ParsedDiffSet::Patchset(d) => d.into(),
262 }
263 }
264}
265
266impl ParsedDiffSet {
267 pub fn parse(data: &[u8]) -> Result<Self, ParseError> {
275 if data.is_empty() {
276 return Ok(ParsedDiffSet::Changeset(DiffSet::default()));
278 }
279
280 match data[0] {
282 markers::CHANGESET => {
283 let diffset = parse_as_changeset(data)?;
284 Ok(ParsedDiffSet::Changeset(diffset))
285 }
286 markers::PATCHSET => {
287 let diffset = parse_as_patchset(data)?;
288 Ok(ParsedDiffSet::Patchset(diffset))
289 }
290 b => Err(ParseError::InvalidTableMarker(b, 0)),
291 }
292 }
293
294 #[must_use]
296 pub fn is_changeset(&self) -> bool {
297 matches!(self, ParsedDiffSet::Changeset(_))
298 }
299
300 #[must_use]
302 pub fn is_patchset(&self) -> bool {
303 matches!(self, ParsedDiffSet::Patchset(_))
304 }
305
306 #[must_use]
308 pub fn table_schemas(&self) -> Vec<&TableSchema<String>> {
309 match self {
310 ParsedDiffSet::Changeset(d) => d
311 .tables
312 .iter()
313 .filter(|(_, ops)| !ops.is_empty())
314 .map(|(schema, _)| schema)
315 .collect(),
316 ParsedDiffSet::Patchset(d) => d
317 .tables
318 .iter()
319 .filter(|(_, ops)| !ops.is_empty())
320 .map(|(schema, _)| schema)
321 .collect(),
322 }
323 }
324}
325
326fn parse_as_changeset(
332 data: &[u8],
333) -> Result<DiffSet<ChangesetFormat, TableSchema<String>, String, Vec<u8>>, ParseError> {
334 let mut builder: DiffSetBuilder<ChangesetFormat, TableSchema<String>, String, Vec<u8>> =
335 DiffSetBuilder::new();
336 let mut pos = 0;
337
338 while pos < data.len() {
339 let (schema, format, header_len) = parse_table_header(&data[pos..], pos)?;
340 if format != FormatMarker::Changeset {
341 return Err(ParseError::MixedFormats {
342 expected: FormatMarker::Changeset,
343 found: format,
344 position: pos,
345 });
346 }
347 pos += header_len;
348
349 while pos < data.len() {
350 let byte = data[pos];
351 if byte == markers::CHANGESET || byte == markers::PATCHSET {
352 break;
353 }
354 let op_len = parse_changeset_operation(&data[pos..], pos, &schema, &mut builder)?;
355 pos += op_len;
356 }
357 }
358
359 Ok(builder.into())
360}
361
362fn parse_as_patchset(
368 data: &[u8],
369) -> Result<DiffSet<PatchsetFormat, TableSchema<String>, String, Vec<u8>>, ParseError> {
370 let mut builder: DiffSetBuilder<PatchsetFormat, TableSchema<String>, String, Vec<u8>> =
371 DiffSetBuilder::new();
372 let mut pos = 0;
373
374 while pos < data.len() {
375 let (schema, format, header_len) = parse_table_header(&data[pos..], pos)?;
376 if format != FormatMarker::Patchset {
377 return Err(ParseError::MixedFormats {
378 expected: FormatMarker::Patchset,
379 found: format,
380 position: pos,
381 });
382 }
383 pos += header_len;
384
385 while pos < data.len() {
386 let byte = data[pos];
387 if byte == markers::CHANGESET || byte == markers::PATCHSET {
388 break;
389 }
390 let op_len = parse_patchset_operation(&data[pos..], pos, &schema, &mut builder)?;
391 pos += op_len;
392 }
393 }
394
395 Ok(builder.into())
396}
397
398fn parse_table_header(
400 data: &[u8],
401 base_pos: usize,
402) -> Result<(TableSchema<String>, FormatMarker, usize), ParseError> {
403 let mut pos = 0;
404
405 if data.is_empty() {
406 return Err(ParseError::UnexpectedEof(base_pos));
407 }
408 let format = match data[pos] {
409 markers::CHANGESET => FormatMarker::Changeset,
410 markers::PATCHSET => FormatMarker::Patchset,
411 b => return Err(ParseError::InvalidTableMarker(b, base_pos + pos)),
412 };
413 pos += 1;
414
415 if pos >= data.len() {
416 return Err(ParseError::UnexpectedEof(base_pos + pos));
417 }
418 let column_count = data[pos] as usize;
419 pos += 1;
420
421 if pos + column_count > data.len() {
422 return Err(ParseError::UnexpectedEof(base_pos + pos));
423 }
424 let pk_flags: Vec<u8> = data[pos..pos + column_count].to_vec();
425 pos += column_count;
426
427 let name_start = pos;
428 while pos < data.len() && data[pos] != 0 {
429 pos += 1;
430 }
431 if pos >= data.len() {
432 return Err(ParseError::UnterminatedTableName);
433 }
434 let name = String::from_utf8(data[name_start..pos].to_vec())
435 .map_err(|_| ParseError::InvalidTableName(base_pos + name_start))?;
436 pos += 1;
437
438 Ok((TableSchema::new(name, column_count, pk_flags), format, pos))
439}
440
441fn parse_operation_header(data: &[u8], base_pos: usize) -> Result<(u8, bool, usize), ParseError> {
446 if data.len() < 2 {
447 return Err(ParseError::UnexpectedEof(base_pos));
448 }
449 Ok((data[0], data[1] != 0, 2))
450}
451
452fn parse_changeset_operation(
454 data: &[u8],
455 base_pos: usize,
456 schema: &TableSchema<String>,
457 builder: &mut DiffSetBuilder<ChangesetFormat, TableSchema<String>, String, Vec<u8>>,
458) -> Result<usize, ParseError> {
459 let (op_code, indirect, mut pos) = parse_operation_header(data, base_pos)?;
460
461 match op_code {
462 op_codes::INSERT => {
463 let (values, len) = parse_values(&data[pos..], base_pos + pos, schema.column_count)?;
464 pos += len;
465 let values: Vec<Value<String, Vec<u8>>> = values
466 .into_iter()
467 .map(|v| v.unwrap_or(Value::Null))
468 .collect();
469 let pk = schema.extract_pk(&values);
470 builder.add_operation(schema, pk, Operation::Insert { values, indirect });
471 }
472 op_codes::DELETE => {
473 let (values, len) = parse_values(&data[pos..], base_pos + pos, schema.column_count)?;
474 pos += len;
475 let values: Vec<Value<String, Vec<u8>>> = values
476 .into_iter()
477 .map(|v| v.unwrap_or(Value::Null))
478 .collect();
479 let pk = schema.extract_pk(&values);
480 builder.add_operation(
481 schema,
482 pk,
483 Operation::Delete {
484 data: values,
485 indirect,
486 },
487 );
488 }
489 op_codes::UPDATE => {
490 let (old_values, old_len) =
491 parse_values(&data[pos..], base_pos + pos, schema.column_count)?;
492 pos += old_len;
493 let (new_values, new_len) =
494 parse_values(&data[pos..], base_pos + pos, schema.column_count)?;
495 pos += new_len;
496 let pk_values: Vec<Value<String, Vec<u8>>> = old_values
498 .iter()
499 .map(|v| v.clone().unwrap_or(Value::Null))
500 .collect();
501 let pk = schema.extract_pk(&pk_values);
502 let values: UpdateValues = old_values.into_iter().zip(new_values).collect();
503 builder.add_operation(schema, pk, Operation::Update { values, indirect });
504 }
505 _ => return Err(ParseError::InvalidOpCode(op_code, base_pos)),
506 }
507
508 Ok(pos)
509}
510
511fn parse_patchset_operation(
513 data: &[u8],
514 base_pos: usize,
515 schema: &TableSchema<String>,
516 builder: &mut DiffSetBuilder<PatchsetFormat, TableSchema<String>, String, Vec<u8>>,
517) -> Result<usize, ParseError> {
518 let (op_code, indirect, mut pos) = parse_operation_header(data, base_pos)?;
519
520 match op_code {
521 op_codes::INSERT => {
522 let (values, len) = parse_values(&data[pos..], base_pos + pos, schema.column_count)?;
523 pos += len;
524 let values: Vec<Value<String, Vec<u8>>> = values
525 .into_iter()
526 .map(|v| v.unwrap_or(Value::Null))
527 .collect();
528 let pk = schema.extract_pk(&values);
529 builder.add_operation(schema, pk, Operation::Insert { values, indirect });
530 }
531 op_codes::DELETE => {
532 let pk_count = schema.pk_flags.iter().filter(|&&b| b > 0).count();
534 let (pk_values, len) = parse_values(&data[pos..], base_pos + pos, pk_count)?;
535 pos += len;
536 let full_values = expand_pk_values(&schema.pk_flags, pk_values, schema.column_count);
540 let full_values_concrete: Vec<Value<String, Vec<u8>>> = full_values
542 .into_iter()
543 .map(|v| v.unwrap_or(Value::Null))
544 .collect();
545 let pk = schema.extract_pk(&full_values_concrete);
546 builder.add_operation(schema, pk, Operation::Delete { data: (), indirect });
547 }
548 op_codes::UPDATE => {
549 let pk_count = schema.pk_flags.iter().filter(|&&b| b > 0).count();
563 let non_pk_count = schema.column_count.saturating_sub(pk_count);
564
565 let (old_pk_values, old_len) = parse_values(&data[pos..], base_pos + pos, pk_count)?;
566 pos += old_len;
567 let (new_non_pk_values, new_len) =
568 parse_values(&data[pos..], base_pos + pos, non_pk_count)?;
569 pos += new_len;
570
571 let mut values: Vec<((), MaybeValue<String, Vec<u8>>)> =
572 alloc::vec![((), None); schema.column_count];
573 let mut old_iter = old_pk_values.into_iter();
574 let mut new_iter = new_non_pk_values.into_iter();
575 for (col_idx, &pk_flag) in schema.pk_flags.iter().enumerate() {
576 if pk_flag > 0 {
577 let old = old_iter.next().flatten().unwrap_or(Value::Null);
582 values[col_idx] = ((), Some(old));
583 } else {
584 values[col_idx] = ((), new_iter.next().flatten());
585 }
586 }
587
588 let pk = schema.extract_pk(&values);
589 builder.add_operation(schema, pk, Operation::Update { values, indirect });
590 }
591 _ => return Err(ParseError::InvalidOpCode(op_code, base_pos)),
592 }
593
594 Ok(pos)
595}
596
597fn expand_pk_values(
602 pk_flags: &[u8],
603 pk_values: Vec<MaybeValue<String, Vec<u8>>>,
604 column_count: usize,
605) -> Vec<MaybeValue<String, Vec<u8>>> {
606 let mut full: Vec<MaybeValue<String, Vec<u8>>> = vec![None; column_count];
607 let mut pk_iter = pk_values.into_iter();
608 for (i, &pk_ordinal) in pk_flags.iter().enumerate() {
609 if pk_ordinal > 0
610 && let Some(v) = pk_iter.next()
611 {
612 full[i] = v;
613 }
614 }
615 full
616}
617
618fn parse_values(data: &[u8], base_pos: usize, count: usize) -> Result<ParsedValues, ParseError> {
620 let mut values = Vec::with_capacity(count);
621 let mut pos = 0;
622
623 for _ in 0..count {
624 let (value, value_len) =
625 decode_value(&data[pos..]).ok_or(ParseError::InvalidValue(base_pos + pos))?;
626 values.push(value);
627 pos += value_len;
628 }
629
630 Ok((values, pos))
631}
632
633#[cfg(test)]
634mod tests {
635 use super::*;
636 use crate::SimpleTable;
637 use alloc::vec;
638
639 #[test]
640 fn test_parse_empty() {
641 let result = ParsedDiffSet::parse(&[]);
642 assert!(result.is_ok());
643 assert!(result.unwrap().is_changeset());
644 }
645
646 #[test]
647 fn test_parse_table_header() {
648 let data = [b'T', 2, 1, 0, b't', 0];
650 let (schema, format, len) = parse_table_header(&data, 0).unwrap();
651
652 assert_eq!(format, FormatMarker::Changeset);
653 assert_eq!(schema.column_count, 2);
654 assert_eq!(schema.pk_flags, vec![1, 0]); assert_eq!(schema.name, "t");
656 assert_eq!(len, 6);
657 }
658
659 #[test]
660 fn test_parse_insert_changeset() {
661 let mut data = vec![b'T', 2, 1, 0, b't', 0];
663 data.push(op_codes::INSERT);
665 data.push(0);
666 data.push(0x01);
668 data.extend(&1i64.to_be_bytes());
669 data.push(0x03);
671 data.push(1);
672 data.push(b'a');
673
674 let parsed = ParsedDiffSet::parse(&data).unwrap();
675 assert!(parsed.is_changeset());
676 }
677
678 #[test]
679 fn test_parse_delete_changeset() {
680 let mut data = vec![b'T', 2, 1, 0, b't', 0];
681 data.push(op_codes::DELETE);
682 data.push(0);
683 data.push(0x01);
685 data.extend(&1i64.to_be_bytes());
686 data.push(0x03);
688 data.push(1);
689 data.push(b'a');
690
691 let parsed = ParsedDiffSet::parse(&data).unwrap();
692 assert!(parsed.is_changeset());
693 }
694
695 #[test]
696 fn test_parse_delete_patchset() {
697 let mut data = vec![b'P', 2, 1, 0, b't', 0];
699 data.push(op_codes::DELETE);
700 data.push(0);
701 data.push(0x01);
703 data.extend(&1i64.to_be_bytes());
704
705 let parsed = ParsedDiffSet::parse(&data).unwrap();
706 assert!(parsed.is_patchset());
707 }
708
709 #[test]
710 fn test_parse_update_changeset() {
711 let mut data = vec![b'T', 2, 1, 0, b't', 0];
712 data.push(op_codes::UPDATE);
713 data.push(0);
714 data.push(0x01);
716 data.extend(&1i64.to_be_bytes());
717 data.push(0x03);
718 data.push(1);
719 data.push(b'a');
720 data.push(0x01);
722 data.extend(&1i64.to_be_bytes());
723 data.push(0x03);
724 data.push(1);
725 data.push(b'b');
726
727 let parsed = ParsedDiffSet::parse(&data).unwrap();
728 assert!(parsed.is_changeset());
729 }
730
731 #[test]
732 fn test_is_changeset() {
733 let data = vec![b'T', 1, 1, b't', 0];
734 let parsed = ParsedDiffSet::parse(&data).unwrap();
735 assert!(parsed.is_changeset());
736 assert!(!parsed.is_patchset());
737 }
738
739 #[test]
740 fn test_is_patchset() {
741 let data = vec![b'P', 1, 1, b't', 0];
742 let parsed = ParsedDiffSet::parse(&data).unwrap();
743 assert!(parsed.is_patchset());
744 assert!(!parsed.is_changeset());
745 }
746
747 #[test]
748 fn test_parsed_table_schema_dyn_table() {
749 let schema: TableSchema<String> = TableSchema::new("users".into(), 3, vec![1, 0, 0]);
750 assert_eq!(schema.name(), "users");
751 assert_eq!(schema.number_of_columns(), 3);
752
753 let mut buf = [0u8; 3];
754 schema.write_pk_flags(&mut buf);
755 assert_eq!(buf, [1, 0, 0]);
756 }
757
758 #[test]
759 fn test_parsed_table_schema_extract_pk() {
760 let schema: TableSchema<String> = TableSchema::new("users".into(), 3, vec![1, 0, 2]);
761 let values: Vec<Value<String, Vec<u8>>> = vec![
762 Value::Integer(1),
763 Value::Text("alice".into()),
764 Value::Integer(100),
765 ];
766 let pk = schema.extract_pk(&values);
767 let expected: Vec<Value<String, Vec<u8>>> = vec![Value::Integer(1), Value::Integer(100)];
768 assert_eq!(pk, expected);
769 }
770
771 #[test]
774 fn test_parse_invalid_table_marker() {
775 let data = [0xFFu8, 1, 1, b't', 0];
776 let err = ParsedDiffSet::parse(&data).unwrap_err();
777 assert!(
778 matches!(err, ParseError::InvalidTableMarker(0xFF, 0)),
779 "got {err:?}"
780 );
781 }
782
783 #[test]
784 fn test_parse_unexpected_eof_in_table_header() {
785 let data = *b"T";
787 let err = ParsedDiffSet::parse(&data).unwrap_err();
788 assert!(matches!(err, ParseError::UnexpectedEof(_)), "got {err:?}");
789 }
790
791 #[test]
792 fn test_parse_unexpected_eof_in_pk_flags() {
793 let data = [b'T', 3, 1];
795 let err = ParsedDiffSet::parse(&data).unwrap_err();
796 assert!(matches!(err, ParseError::UnexpectedEof(_)), "got {err:?}");
797 }
798
799 #[test]
800 fn test_parse_unterminated_table_name() {
801 let data = [b'T', 1, 1, b'a', b'b', b'c'];
803 let err = ParsedDiffSet::parse(&data).unwrap_err();
804 assert!(
805 matches!(err, ParseError::UnterminatedTableName),
806 "got {err:?}"
807 );
808 }
809
810 #[test]
811 fn test_parse_invalid_utf8_in_table_name() {
812 let data = [b'T', 1, 1, 0xFF, 0];
814 let err = ParsedDiffSet::parse(&data).unwrap_err();
815 assert!(
816 matches!(err, ParseError::InvalidTableName(_)),
817 "got {err:?}"
818 );
819 }
820
821 #[test]
822 fn test_parse_mixed_formats_changeset_then_patchset() {
823 let mut data = vec![b'T', 1, 1, b'a', 0];
825 data.extend_from_slice(&[b'P', 1, 1, b'b', 0]);
827 let err = ParsedDiffSet::parse(&data).unwrap_err();
828 assert!(
829 matches!(
830 err,
831 ParseError::MixedFormats {
832 expected: FormatMarker::Changeset,
833 found: FormatMarker::Patchset,
834 ..
835 }
836 ),
837 "got {err:?}"
838 );
839 }
840
841 #[test]
842 fn test_parse_mixed_formats_patchset_then_changeset() {
843 let mut data = vec![b'P', 1, 1, b'a', 0];
844 data.extend_from_slice(&[b'T', 1, 1, b'b', 0]);
845 let err = ParsedDiffSet::parse(&data).unwrap_err();
846 assert!(
847 matches!(
848 err,
849 ParseError::MixedFormats {
850 expected: FormatMarker::Patchset,
851 found: FormatMarker::Changeset,
852 ..
853 }
854 ),
855 "got {err:?}"
856 );
857 }
858
859 fn make_insert_with_indirect(indirect_byte: u8) -> Vec<u8> {
861 let mut data = vec![b'T', 1, 1, b't', 0];
862 data.push(op_codes::INSERT);
863 data.push(indirect_byte);
864 data.push(0x01);
866 data.extend(&1i64.to_be_bytes());
867 data
868 }
869
870 fn first_op_indirect_changeset(data: &[u8]) -> bool {
871 let parsed = ParsedDiffSet::parse(data).unwrap();
872 let ParsedDiffSet::Changeset(set) = parsed else {
873 panic!("expected Changeset");
874 };
875 set.tables
876 .iter()
877 .find_map(|(_schema, rows)| rows.first().map(|(_, op)| op.indirect()))
878 .expect("expected at least one op")
879 }
880
881 #[test]
882 fn test_parse_changeset_indirect_flag_set() {
883 let data = make_insert_with_indirect(1);
884 assert!(first_op_indirect_changeset(&data));
885 }
886
887 #[test]
888 fn test_parse_changeset_indirect_flag_clear() {
889 let data = make_insert_with_indirect(0);
890 assert!(!first_op_indirect_changeset(&data));
891 }
892
893 #[test]
894 fn test_parse_indirect_nonzero_treated_as_true() {
895 let data = make_insert_with_indirect(0x42);
897 assert!(first_op_indirect_changeset(&data));
898 }
899
900 #[test]
901 fn test_parsed_diffset_variant_mismatch_partial_eq() {
902 let changeset = ParsedDiffSet::parse(&[b'T', 1, 1, b't', 0]).unwrap();
903 let patchset = ParsedDiffSet::parse(&[b'P', 1, 1, b't', 0]).unwrap();
904 let mut full_changeset = vec![b'T', 1, 1, b't', 0];
907 full_changeset.push(op_codes::INSERT);
908 full_changeset.push(0);
909 full_changeset.push(0x01);
910 full_changeset.extend(&1i64.to_be_bytes());
911 let cs = ParsedDiffSet::parse(&full_changeset).unwrap();
912
913 let mut full_patchset = vec![b'P', 1, 1, b't', 0];
914 full_patchset.push(op_codes::INSERT);
915 full_patchset.push(0);
916 full_patchset.push(0x01);
917 full_patchset.extend(&1i64.to_be_bytes());
918 let ps = ParsedDiffSet::parse(&full_patchset).unwrap();
919
920 assert_ne!(cs, ps);
921 assert_eq!(changeset, patchset);
923 }
924
925 #[test]
926 fn test_parse_unexpected_eof_in_operation_header() {
927 let data = [b'T', 1, 1, b't', 0, op_codes::INSERT];
930 let err = ParsedDiffSet::parse(&data).unwrap_err();
931 assert!(matches!(err, ParseError::UnexpectedEof(_)), "got {err:?}");
932 }
933
934 #[test]
935 fn test_parse_patchset_indirect_flag_set() {
936 let mut data = vec![b'P', 1, 1, b't', 0];
938 data.push(op_codes::INSERT);
939 data.push(1);
940 data.push(0x01);
941 data.extend(&1i64.to_be_bytes());
942
943 let parsed = ParsedDiffSet::parse(&data).unwrap();
944 let ParsedDiffSet::Patchset(set) = parsed else {
945 panic!("expected Patchset");
946 };
947 let indirect = set
948 .tables
949 .iter()
950 .find_map(|(_schema, rows)| rows.first().map(|(_, op)| op.indirect()))
951 .expect("expected at least one op");
952 assert!(indirect);
953 }
954
955 fn assert_patchset_update_roundtrip(
965 data: &[u8],
966 check: impl FnOnce(
967 &TableSchema<String>,
968 &[Value<String, Vec<u8>>],
969 &[((), MaybeValue<String, Vec<u8>>)],
970 bool,
971 ),
972 ) {
973 let parsed = ParsedDiffSet::parse(data).expect("SQLite patchset UPDATE must parse");
974 let ParsedDiffSet::Patchset(set) = parsed else {
975 panic!("expected Patchset, got {parsed:?}");
976 };
977 let (schema, rows) = set.tables.first().expect("expected one table");
978 assert_eq!(rows.len(), 1, "expected exactly one row");
979 let (pk, op) = rows.first().expect("row map non-empty");
980 let Operation::Update { values, indirect } = op else {
981 panic!("expected Update, got {op:?}");
982 };
983 check(schema, pk.as_slice(), values.as_slice(), *indirect);
984 let serialized: Vec<u8> = set.into();
985 assert_eq!(serialized, data, "roundtrip must match SQLite output");
986 }
987
988 #[test]
1006 fn test_parse_patchset_update_sqlite_wire_layout_single_pk() {
1007 let data: [u8; 33] = [
1008 0x50, 0x03, 0x01, 0x00, 0x00, b'o', b'r', b'd', b'e', b'r', b's', 0x00, 0x17, 0x00,
1009 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x03, 0x07, b's', b'h',
1010 b'i', b'p', b'p', b'e', b'd',
1011 ];
1012 assert_patchset_update_roundtrip(&data, |schema, pk, values, indirect| {
1013 assert_eq!(schema.name, "orders");
1014 assert_eq!(schema.column_count, 3);
1015 assert_eq!(schema.pk_flags, vec![1, 0, 0]);
1016 assert_eq!(pk, &[Value::Integer(5)]);
1017 assert!(!indirect);
1018 assert_eq!(values.len(), 3);
1019 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())));
1022 });
1023 }
1024
1025 #[test]
1036 fn test_parse_patchset_update_sqlite_wire_layout_composite_pk() {
1037 let data: [u8; 35] = [
1038 0x50, 0x03, 0x01, 0x02, 0x00, b'i', b't', b'e', b'm', b's', 0x00, 0x17, 0x00, 0x01,
1039 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
1040 0x00, 0x00, 0x02, 0x03, 0x02, b'v', b'2',
1041 ];
1042 assert_patchset_update_roundtrip(&data, |schema, pk, values, _indirect| {
1043 assert_eq!(schema.name, "items");
1044 assert_eq!(schema.pk_flags, vec![1, 2, 0]);
1045 assert_eq!(pk, &[Value::Integer(1), Value::Integer(2)]);
1048 assert_eq!(values.len(), 3);
1049 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())));
1052 });
1053 }
1054
1055 #[test]
1065 fn test_parse_patchset_update_all_non_pk_changed() {
1066 let data: [u8; 41] = [
1067 0x50, 0x03, 0x01, 0x00, 0x00, b'o', b'r', b'd', b'e', b'r', b's', 0x00, 0x17, 0x00,
1068 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00,
1069 0x00, 0x00, 0x00, 0xc8, 0x03, 0x07, b's', b'h', b'i', b'p', b'p', b'e', b'd',
1070 ];
1071 assert_patchset_update_roundtrip(&data, |_schema, _pk, values, _indirect| {
1072 assert_eq!(values[0].1, Some(Value::Integer(5)));
1073 assert_eq!(values[1].1, Some(Value::Integer(200)));
1074 assert_eq!(values[2].1, Some(Value::Text("shipped".into())));
1075 });
1076 }
1077
1078 fn assert_schema_pk_parity(
1082 parsed: &TableSchema<String>,
1083 simple: &SimpleTable,
1084 row: &[Value<String, Vec<u8>>],
1085 ) {
1086 assert_eq!(
1087 parsed.number_of_primary_keys(),
1088 simple.number_of_primary_keys(),
1089 "number_of_primary_keys",
1090 );
1091 for col in 0..simple.number_of_columns() {
1092 assert_eq!(
1093 parsed.primary_key_index(col),
1094 simple.primary_key_index(col),
1095 "primary_key_index at col {col}",
1096 );
1097 }
1098 assert_eq!(
1099 parsed.primary_key_columns(),
1100 simple.primary_key_columns(),
1101 "primary_key_columns",
1102 );
1103 assert_eq!(
1104 parsed.extract_pk(&row),
1105 simple.extract_pk(&row),
1106 "extract_pk"
1107 );
1108 }
1109
1110 #[test]
1111 fn test_parsed_schema_pk_parity_single_key() {
1112 let mut data = vec![b'T', 2, 1, 0, b'k', b'v', 0];
1114 data.push(op_codes::INSERT);
1115 data.push(0);
1116 data.push(0x01);
1117 data.extend(&1i64.to_be_bytes());
1118 data.push(0x03);
1119 data.push(1);
1120 data.push(b'x');
1121
1122 let ParsedDiffSet::Changeset(set) = ParsedDiffSet::parse(&data).unwrap() else {
1123 panic!("expected changeset");
1124 };
1125 let (parsed, _rows) = set.tables.first().expect("one table");
1126 assert_eq!(parsed.pk_flags(), &[1, 0]);
1127
1128 let simple = SimpleTable::new("kv", &["id", "val"], &[0]);
1129 let row: Vec<Value<String, Vec<u8>>> = vec![Value::Integer(1), Value::Text("x".into())];
1130 assert_schema_pk_parity(parsed, &simple, &row);
1131 assert_eq!(parsed.primary_key_columns(), vec![0]);
1132 }
1133
1134 #[test]
1135 fn test_parsed_schema_pk_parity_composite_reordered_key() {
1136 let mut data = vec![b'T', 3, 2, 1, 0, b'a', b'b', b'c', 0];
1139 data.push(op_codes::INSERT);
1140 data.push(0);
1141 data.push(0x01);
1142 data.extend(&10i64.to_be_bytes());
1143 data.push(0x01);
1144 data.extend(&20i64.to_be_bytes());
1145 data.push(0x03);
1146 data.push(1);
1147 data.push(b'z');
1148
1149 let ParsedDiffSet::Changeset(set) = ParsedDiffSet::parse(&data).unwrap() else {
1150 panic!("expected changeset");
1151 };
1152 let (parsed, _rows) = set.tables.first().expect("one table");
1153 assert_eq!(parsed.pk_flags(), &[2, 1, 0]);
1154
1155 let simple = SimpleTable::new("abc", &["a", "b", "c"], &[1, 0]);
1156 let row: Vec<Value<String, Vec<u8>>> = vec![
1157 Value::Integer(10),
1158 Value::Integer(20),
1159 Value::Text("z".into()),
1160 ];
1161 assert_schema_pk_parity(parsed, &simple, &row);
1162 assert_eq!(parsed.primary_key_columns(), vec![1, 0]);
1164 assert_eq!(
1166 parsed.extract_pk(&row),
1167 vec![Value::Integer(20), Value::Integer(10)]
1168 );
1169 }
1170}