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 pub fn rename_tables<F>(&mut self, mut rename: F) -> usize
332 where
333 F: FnMut(&str) -> Option<String>,
334 {
335 fn rename_in<Fmt, F>(tables: &mut [(TableSchema<String>, Fmt)], rename: &mut F) -> usize
336 where
337 F: FnMut(&str) -> Option<String>,
338 {
339 let mut renamed = 0;
340 for (schema, _) in tables.iter_mut() {
341 if let Some(new_name) = rename(schema.name.as_str()) {
342 schema.name = new_name;
343 renamed += 1;
344 }
345 }
346 renamed
347 }
348
349 match self {
350 ParsedDiffSet::Changeset(d) => rename_in(&mut d.tables, &mut rename),
351 ParsedDiffSet::Patchset(d) => rename_in(&mut d.tables, &mut rename),
352 }
353 }
354}
355
356fn parse_as_changeset(
362 data: &[u8],
363) -> Result<DiffSet<ChangesetFormat, TableSchema<String>, String, Vec<u8>>, ParseError> {
364 let mut builder: DiffSetBuilder<ChangesetFormat, TableSchema<String>, String, Vec<u8>> =
365 DiffSetBuilder::new();
366 let mut pos = 0;
367
368 while pos < data.len() {
369 let (schema, format, header_len) = parse_table_header(&data[pos..], pos)?;
370 if format != FormatMarker::Changeset {
371 return Err(ParseError::MixedFormats {
372 expected: FormatMarker::Changeset,
373 found: format,
374 position: pos,
375 });
376 }
377 pos += header_len;
378
379 while pos < data.len() {
380 let byte = data[pos];
381 if byte == markers::CHANGESET || byte == markers::PATCHSET {
382 break;
383 }
384 let op_len = parse_changeset_operation(&data[pos..], pos, &schema, &mut builder)?;
385 pos += op_len;
386 }
387 }
388
389 Ok(builder.into())
390}
391
392fn parse_as_patchset(
398 data: &[u8],
399) -> Result<DiffSet<PatchsetFormat, TableSchema<String>, String, Vec<u8>>, ParseError> {
400 let mut builder: DiffSetBuilder<PatchsetFormat, TableSchema<String>, String, Vec<u8>> =
401 DiffSetBuilder::new();
402 let mut pos = 0;
403
404 while pos < data.len() {
405 let (schema, format, header_len) = parse_table_header(&data[pos..], pos)?;
406 if format != FormatMarker::Patchset {
407 return Err(ParseError::MixedFormats {
408 expected: FormatMarker::Patchset,
409 found: format,
410 position: pos,
411 });
412 }
413 pos += header_len;
414
415 while pos < data.len() {
416 let byte = data[pos];
417 if byte == markers::CHANGESET || byte == markers::PATCHSET {
418 break;
419 }
420 let op_len = parse_patchset_operation(&data[pos..], pos, &schema, &mut builder)?;
421 pos += op_len;
422 }
423 }
424
425 Ok(builder.into())
426}
427
428fn parse_table_header(
430 data: &[u8],
431 base_pos: usize,
432) -> Result<(TableSchema<String>, FormatMarker, usize), ParseError> {
433 let mut pos = 0;
434
435 if data.is_empty() {
436 return Err(ParseError::UnexpectedEof(base_pos));
437 }
438 let format = match data[pos] {
439 markers::CHANGESET => FormatMarker::Changeset,
440 markers::PATCHSET => FormatMarker::Patchset,
441 b => return Err(ParseError::InvalidTableMarker(b, base_pos + pos)),
442 };
443 pos += 1;
444
445 if pos >= data.len() {
446 return Err(ParseError::UnexpectedEof(base_pos + pos));
447 }
448 let column_count = data[pos] as usize;
449 pos += 1;
450
451 if pos + column_count > data.len() {
452 return Err(ParseError::UnexpectedEof(base_pos + pos));
453 }
454 let pk_flags: Vec<u8> = data[pos..pos + column_count].to_vec();
455 pos += column_count;
456
457 let name_start = pos;
458 while pos < data.len() && data[pos] != 0 {
459 pos += 1;
460 }
461 if pos >= data.len() {
462 return Err(ParseError::UnterminatedTableName);
463 }
464 let name = String::from_utf8(data[name_start..pos].to_vec())
465 .map_err(|_| ParseError::InvalidTableName(base_pos + name_start))?;
466 pos += 1;
467
468 Ok((TableSchema::new(name, column_count, pk_flags), format, pos))
469}
470
471fn parse_operation_header(data: &[u8], base_pos: usize) -> Result<(u8, bool, usize), ParseError> {
476 if data.len() < 2 {
477 return Err(ParseError::UnexpectedEof(base_pos));
478 }
479 Ok((data[0], data[1] != 0, 2))
480}
481
482fn parse_changeset_operation(
484 data: &[u8],
485 base_pos: usize,
486 schema: &TableSchema<String>,
487 builder: &mut DiffSetBuilder<ChangesetFormat, TableSchema<String>, String, Vec<u8>>,
488) -> Result<usize, ParseError> {
489 let (op_code, indirect, mut pos) = parse_operation_header(data, base_pos)?;
490
491 match op_code {
492 op_codes::INSERT => {
493 let (values, len) = parse_values(&data[pos..], base_pos + pos, schema.column_count)?;
494 pos += len;
495 let values: Vec<Value<String, Vec<u8>>> = values
496 .into_iter()
497 .map(|v| v.unwrap_or(Value::Null))
498 .collect();
499 let pk = schema.extract_pk(&values);
500 builder.add_operation(schema, pk, Operation::Insert { values, indirect });
501 }
502 op_codes::DELETE => {
503 let (values, len) = parse_values(&data[pos..], base_pos + pos, schema.column_count)?;
504 pos += len;
505 let values: Vec<Value<String, Vec<u8>>> = values
506 .into_iter()
507 .map(|v| v.unwrap_or(Value::Null))
508 .collect();
509 let pk = schema.extract_pk(&values);
510 builder.add_operation(
511 schema,
512 pk,
513 Operation::Delete {
514 data: values,
515 indirect,
516 },
517 );
518 }
519 op_codes::UPDATE => {
520 let (old_values, old_len) =
521 parse_values(&data[pos..], base_pos + pos, schema.column_count)?;
522 pos += old_len;
523 let (new_values, new_len) =
524 parse_values(&data[pos..], base_pos + pos, schema.column_count)?;
525 pos += new_len;
526 let pk_values: Vec<Value<String, Vec<u8>>> = old_values
528 .iter()
529 .map(|v| v.clone().unwrap_or(Value::Null))
530 .collect();
531 let pk = schema.extract_pk(&pk_values);
532 let values: UpdateValues = old_values.into_iter().zip(new_values).collect();
533 builder.add_operation(schema, pk, Operation::Update { values, indirect });
534 }
535 _ => return Err(ParseError::InvalidOpCode(op_code, base_pos)),
536 }
537
538 Ok(pos)
539}
540
541fn parse_patchset_operation(
543 data: &[u8],
544 base_pos: usize,
545 schema: &TableSchema<String>,
546 builder: &mut DiffSetBuilder<PatchsetFormat, TableSchema<String>, String, Vec<u8>>,
547) -> Result<usize, ParseError> {
548 let (op_code, indirect, mut pos) = parse_operation_header(data, base_pos)?;
549
550 match op_code {
551 op_codes::INSERT => {
552 let (values, len) = parse_values(&data[pos..], base_pos + pos, schema.column_count)?;
553 pos += len;
554 let values: Vec<Value<String, Vec<u8>>> = values
555 .into_iter()
556 .map(|v| v.unwrap_or(Value::Null))
557 .collect();
558 let pk = schema.extract_pk(&values);
559 builder.add_operation(schema, pk, Operation::Insert { values, indirect });
560 }
561 op_codes::DELETE => {
562 let pk_count = schema.pk_flags.iter().filter(|&&b| b > 0).count();
564 let (pk_values, len) = parse_values(&data[pos..], base_pos + pos, pk_count)?;
565 pos += len;
566 let full_values = expand_pk_values(&schema.pk_flags, pk_values, schema.column_count);
570 let full_values_concrete: Vec<Value<String, Vec<u8>>> = full_values
572 .into_iter()
573 .map(|v| v.unwrap_or(Value::Null))
574 .collect();
575 let pk = schema.extract_pk(&full_values_concrete);
576 builder.add_operation(schema, pk, Operation::Delete { data: (), indirect });
577 }
578 op_codes::UPDATE => {
579 let pk_count = schema.pk_flags.iter().filter(|&&b| b > 0).count();
593 let non_pk_count = schema.column_count.saturating_sub(pk_count);
594
595 let (old_pk_values, old_len) = parse_values(&data[pos..], base_pos + pos, pk_count)?;
596 pos += old_len;
597 let (new_non_pk_values, new_len) =
598 parse_values(&data[pos..], base_pos + pos, non_pk_count)?;
599 pos += new_len;
600
601 let mut values: Vec<((), MaybeValue<String, Vec<u8>>)> =
602 alloc::vec![((), None); schema.column_count];
603 let mut old_iter = old_pk_values.into_iter();
604 let mut new_iter = new_non_pk_values.into_iter();
605 for (col_idx, &pk_flag) in schema.pk_flags.iter().enumerate() {
606 if pk_flag > 0 {
607 let old = old_iter.next().flatten().unwrap_or(Value::Null);
612 values[col_idx] = ((), Some(old));
613 } else {
614 values[col_idx] = ((), new_iter.next().flatten());
615 }
616 }
617
618 let pk = schema.extract_pk(&values);
619 builder.add_operation(schema, pk, Operation::Update { values, indirect });
620 }
621 _ => return Err(ParseError::InvalidOpCode(op_code, base_pos)),
622 }
623
624 Ok(pos)
625}
626
627fn expand_pk_values(
632 pk_flags: &[u8],
633 pk_values: Vec<MaybeValue<String, Vec<u8>>>,
634 column_count: usize,
635) -> Vec<MaybeValue<String, Vec<u8>>> {
636 let mut full: Vec<MaybeValue<String, Vec<u8>>> = vec![None; column_count];
637 let mut pk_iter = pk_values.into_iter();
638 for (i, &pk_ordinal) in pk_flags.iter().enumerate() {
639 if pk_ordinal > 0
640 && let Some(v) = pk_iter.next()
641 {
642 full[i] = v;
643 }
644 }
645 full
646}
647
648fn parse_values(data: &[u8], base_pos: usize, count: usize) -> Result<ParsedValues, ParseError> {
650 let mut values = Vec::with_capacity(count);
651 let mut pos = 0;
652
653 for _ in 0..count {
654 let (value, value_len) =
655 decode_value(&data[pos..]).ok_or(ParseError::InvalidValue(base_pos + pos))?;
656 values.push(value);
657 pos += value_len;
658 }
659
660 Ok((values, pos))
661}
662
663#[cfg(test)]
664mod tests {
665 use super::*;
666 use crate::SimpleTable;
667 use alloc::vec;
668
669 #[test]
670 fn test_parse_empty() {
671 let result = ParsedDiffSet::parse(&[]);
672 assert!(result.is_ok());
673 assert!(result.unwrap().is_changeset());
674 }
675
676 #[test]
677 fn test_parse_table_header() {
678 let data = [b'T', 2, 1, 0, b't', 0];
680 let (schema, format, len) = parse_table_header(&data, 0).unwrap();
681
682 assert_eq!(format, FormatMarker::Changeset);
683 assert_eq!(schema.column_count, 2);
684 assert_eq!(schema.pk_flags, vec![1, 0]); assert_eq!(schema.name, "t");
686 assert_eq!(len, 6);
687 }
688
689 #[test]
690 fn test_parse_insert_changeset() {
691 let mut data = vec![b'T', 2, 1, 0, b't', 0];
693 data.push(op_codes::INSERT);
695 data.push(0);
696 data.push(0x01);
698 data.extend(&1i64.to_be_bytes());
699 data.push(0x03);
701 data.push(1);
702 data.push(b'a');
703
704 let parsed = ParsedDiffSet::parse(&data).unwrap();
705 assert!(parsed.is_changeset());
706 }
707
708 #[test]
709 fn test_parse_delete_changeset() {
710 let mut data = vec![b'T', 2, 1, 0, b't', 0];
711 data.push(op_codes::DELETE);
712 data.push(0);
713 data.push(0x01);
715 data.extend(&1i64.to_be_bytes());
716 data.push(0x03);
718 data.push(1);
719 data.push(b'a');
720
721 let parsed = ParsedDiffSet::parse(&data).unwrap();
722 assert!(parsed.is_changeset());
723 }
724
725 #[test]
726 fn test_parse_delete_patchset() {
727 let mut data = vec![b'P', 2, 1, 0, b't', 0];
729 data.push(op_codes::DELETE);
730 data.push(0);
731 data.push(0x01);
733 data.extend(&1i64.to_be_bytes());
734
735 let parsed = ParsedDiffSet::parse(&data).unwrap();
736 assert!(parsed.is_patchset());
737 }
738
739 #[test]
740 fn test_parse_update_changeset() {
741 let mut data = vec![b'T', 2, 1, 0, b't', 0];
742 data.push(op_codes::UPDATE);
743 data.push(0);
744 data.push(0x01);
746 data.extend(&1i64.to_be_bytes());
747 data.push(0x03);
748 data.push(1);
749 data.push(b'a');
750 data.push(0x01);
752 data.extend(&1i64.to_be_bytes());
753 data.push(0x03);
754 data.push(1);
755 data.push(b'b');
756
757 let parsed = ParsedDiffSet::parse(&data).unwrap();
758 assert!(parsed.is_changeset());
759 }
760
761 #[test]
762 fn test_is_changeset() {
763 let data = vec![b'T', 1, 1, b't', 0];
764 let parsed = ParsedDiffSet::parse(&data).unwrap();
765 assert!(parsed.is_changeset());
766 assert!(!parsed.is_patchset());
767 }
768
769 #[test]
770 fn test_is_patchset() {
771 let data = vec![b'P', 1, 1, b't', 0];
772 let parsed = ParsedDiffSet::parse(&data).unwrap();
773 assert!(parsed.is_patchset());
774 assert!(!parsed.is_changeset());
775 }
776
777 #[test]
778 fn test_parsed_table_schema_dyn_table() {
779 let schema: TableSchema<String> = TableSchema::new("users".into(), 3, vec![1, 0, 0]);
780 assert_eq!(schema.name(), "users");
781 assert_eq!(schema.number_of_columns(), 3);
782
783 let mut buf = [0u8; 3];
784 schema.write_pk_flags(&mut buf);
785 assert_eq!(buf, [1, 0, 0]);
786 }
787
788 #[test]
789 fn test_parsed_table_schema_extract_pk() {
790 let schema: TableSchema<String> = TableSchema::new("users".into(), 3, vec![1, 0, 2]);
791 let values: Vec<Value<String, Vec<u8>>> = vec![
792 Value::Integer(1),
793 Value::Text("alice".into()),
794 Value::Integer(100),
795 ];
796 let pk = schema.extract_pk(&values);
797 let expected: Vec<Value<String, Vec<u8>>> = vec![Value::Integer(1), Value::Integer(100)];
798 assert_eq!(pk, expected);
799 }
800
801 #[test]
804 fn test_parse_invalid_table_marker() {
805 let data = [0xFFu8, 1, 1, b't', 0];
806 let err = ParsedDiffSet::parse(&data).unwrap_err();
807 assert!(
808 matches!(err, ParseError::InvalidTableMarker(0xFF, 0)),
809 "got {err:?}"
810 );
811 }
812
813 #[test]
814 fn test_parse_unexpected_eof_in_table_header() {
815 let data = *b"T";
817 let err = ParsedDiffSet::parse(&data).unwrap_err();
818 assert!(matches!(err, ParseError::UnexpectedEof(_)), "got {err:?}");
819 }
820
821 #[test]
822 fn test_parse_unexpected_eof_in_pk_flags() {
823 let data = [b'T', 3, 1];
825 let err = ParsedDiffSet::parse(&data).unwrap_err();
826 assert!(matches!(err, ParseError::UnexpectedEof(_)), "got {err:?}");
827 }
828
829 #[test]
830 fn test_parse_unterminated_table_name() {
831 let data = [b'T', 1, 1, b'a', b'b', b'c'];
833 let err = ParsedDiffSet::parse(&data).unwrap_err();
834 assert!(
835 matches!(err, ParseError::UnterminatedTableName),
836 "got {err:?}"
837 );
838 }
839
840 #[test]
841 fn test_parse_invalid_utf8_in_table_name() {
842 let data = [b'T', 1, 1, 0xFF, 0];
844 let err = ParsedDiffSet::parse(&data).unwrap_err();
845 assert!(
846 matches!(err, ParseError::InvalidTableName(_)),
847 "got {err:?}"
848 );
849 }
850
851 #[test]
852 fn test_parse_mixed_formats_changeset_then_patchset() {
853 let mut data = vec![b'T', 1, 1, b'a', 0];
855 data.extend_from_slice(&[b'P', 1, 1, b'b', 0]);
857 let err = ParsedDiffSet::parse(&data).unwrap_err();
858 assert!(
859 matches!(
860 err,
861 ParseError::MixedFormats {
862 expected: FormatMarker::Changeset,
863 found: FormatMarker::Patchset,
864 ..
865 }
866 ),
867 "got {err:?}"
868 );
869 }
870
871 #[test]
872 fn test_parse_mixed_formats_patchset_then_changeset() {
873 let mut data = vec![b'P', 1, 1, b'a', 0];
874 data.extend_from_slice(&[b'T', 1, 1, b'b', 0]);
875 let err = ParsedDiffSet::parse(&data).unwrap_err();
876 assert!(
877 matches!(
878 err,
879 ParseError::MixedFormats {
880 expected: FormatMarker::Patchset,
881 found: FormatMarker::Changeset,
882 ..
883 }
884 ),
885 "got {err:?}"
886 );
887 }
888
889 fn make_insert_with_indirect(indirect_byte: u8) -> Vec<u8> {
891 let mut data = vec![b'T', 1, 1, b't', 0];
892 data.push(op_codes::INSERT);
893 data.push(indirect_byte);
894 data.push(0x01);
896 data.extend(&1i64.to_be_bytes());
897 data
898 }
899
900 fn first_op_indirect_changeset(data: &[u8]) -> bool {
901 let parsed = ParsedDiffSet::parse(data).unwrap();
902 let ParsedDiffSet::Changeset(set) = parsed else {
903 panic!("expected Changeset");
904 };
905 set.tables
906 .iter()
907 .find_map(|(_schema, rows)| rows.first().map(|(_, op)| op.indirect()))
908 .expect("expected at least one op")
909 }
910
911 #[test]
912 fn test_parse_changeset_indirect_flag_set() {
913 let data = make_insert_with_indirect(1);
914 assert!(first_op_indirect_changeset(&data));
915 }
916
917 #[test]
918 fn test_parse_changeset_indirect_flag_clear() {
919 let data = make_insert_with_indirect(0);
920 assert!(!first_op_indirect_changeset(&data));
921 }
922
923 #[test]
924 fn test_parse_indirect_nonzero_treated_as_true() {
925 let data = make_insert_with_indirect(0x42);
927 assert!(first_op_indirect_changeset(&data));
928 }
929
930 #[test]
931 fn test_parsed_diffset_variant_mismatch_partial_eq() {
932 let changeset = ParsedDiffSet::parse(&[b'T', 1, 1, b't', 0]).unwrap();
933 let patchset = ParsedDiffSet::parse(&[b'P', 1, 1, b't', 0]).unwrap();
934 let mut full_changeset = vec![b'T', 1, 1, b't', 0];
937 full_changeset.push(op_codes::INSERT);
938 full_changeset.push(0);
939 full_changeset.push(0x01);
940 full_changeset.extend(&1i64.to_be_bytes());
941 let cs = ParsedDiffSet::parse(&full_changeset).unwrap();
942
943 let mut full_patchset = vec![b'P', 1, 1, b't', 0];
944 full_patchset.push(op_codes::INSERT);
945 full_patchset.push(0);
946 full_patchset.push(0x01);
947 full_patchset.extend(&1i64.to_be_bytes());
948 let ps = ParsedDiffSet::parse(&full_patchset).unwrap();
949
950 assert_ne!(cs, ps);
951 assert_eq!(changeset, patchset);
953 }
954
955 #[test]
956 fn test_parse_unexpected_eof_in_operation_header() {
957 let data = [b'T', 1, 1, b't', 0, op_codes::INSERT];
960 let err = ParsedDiffSet::parse(&data).unwrap_err();
961 assert!(matches!(err, ParseError::UnexpectedEof(_)), "got {err:?}");
962 }
963
964 #[test]
965 fn test_parse_patchset_indirect_flag_set() {
966 let mut data = vec![b'P', 1, 1, b't', 0];
968 data.push(op_codes::INSERT);
969 data.push(1);
970 data.push(0x01);
971 data.extend(&1i64.to_be_bytes());
972
973 let parsed = ParsedDiffSet::parse(&data).unwrap();
974 let ParsedDiffSet::Patchset(set) = parsed else {
975 panic!("expected Patchset");
976 };
977 let indirect = set
978 .tables
979 .iter()
980 .find_map(|(_schema, rows)| rows.first().map(|(_, op)| op.indirect()))
981 .expect("expected at least one op");
982 assert!(indirect);
983 }
984
985 fn assert_patchset_update_roundtrip(
995 data: &[u8],
996 check: impl FnOnce(
997 &TableSchema<String>,
998 &[Value<String, Vec<u8>>],
999 &[((), MaybeValue<String, Vec<u8>>)],
1000 bool,
1001 ),
1002 ) {
1003 let parsed = ParsedDiffSet::parse(data).expect("SQLite patchset UPDATE must parse");
1004 let ParsedDiffSet::Patchset(set) = parsed else {
1005 panic!("expected Patchset, got {parsed:?}");
1006 };
1007 let (schema, rows) = set.tables.first().expect("expected one table");
1008 assert_eq!(rows.len(), 1, "expected exactly one row");
1009 let (pk, op) = rows.first().expect("row map non-empty");
1010 let Operation::Update { values, indirect } = op else {
1011 panic!("expected Update, got {op:?}");
1012 };
1013 check(schema, pk.as_slice(), values.as_slice(), *indirect);
1014 let serialized: Vec<u8> = set.into();
1015 assert_eq!(serialized, data, "roundtrip must match SQLite output");
1016 }
1017
1018 #[test]
1036 fn test_parse_patchset_update_sqlite_wire_layout_single_pk() {
1037 let data: [u8; 33] = [
1038 0x50, 0x03, 0x01, 0x00, 0x00, b'o', b'r', b'd', b'e', b'r', b's', 0x00, 0x17, 0x00,
1039 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x03, 0x07, b's', b'h',
1040 b'i', b'p', b'p', b'e', b'd',
1041 ];
1042 assert_patchset_update_roundtrip(&data, |schema, pk, values, indirect| {
1043 assert_eq!(schema.name, "orders");
1044 assert_eq!(schema.column_count, 3);
1045 assert_eq!(schema.pk_flags, vec![1, 0, 0]);
1046 assert_eq!(pk, &[Value::Integer(5)]);
1047 assert!(!indirect);
1048 assert_eq!(values.len(), 3);
1049 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())));
1052 });
1053 }
1054
1055 #[test]
1066 fn test_parse_patchset_update_sqlite_wire_layout_composite_pk() {
1067 let data: [u8; 35] = [
1068 0x50, 0x03, 0x01, 0x02, 0x00, b'i', b't', b'e', b'm', b's', 0x00, 0x17, 0x00, 0x01,
1069 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
1070 0x00, 0x00, 0x02, 0x03, 0x02, b'v', b'2',
1071 ];
1072 assert_patchset_update_roundtrip(&data, |schema, pk, values, _indirect| {
1073 assert_eq!(schema.name, "items");
1074 assert_eq!(schema.pk_flags, vec![1, 2, 0]);
1075 assert_eq!(pk, &[Value::Integer(1), Value::Integer(2)]);
1078 assert_eq!(values.len(), 3);
1079 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())));
1082 });
1083 }
1084
1085 #[test]
1095 fn test_parse_patchset_update_all_non_pk_changed() {
1096 let data: [u8; 41] = [
1097 0x50, 0x03, 0x01, 0x00, 0x00, b'o', b'r', b'd', b'e', b'r', b's', 0x00, 0x17, 0x00,
1098 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00,
1099 0x00, 0x00, 0x00, 0xc8, 0x03, 0x07, b's', b'h', b'i', b'p', b'p', b'e', b'd',
1100 ];
1101 assert_patchset_update_roundtrip(&data, |_schema, _pk, values, _indirect| {
1102 assert_eq!(values[0].1, Some(Value::Integer(5)));
1103 assert_eq!(values[1].1, Some(Value::Integer(200)));
1104 assert_eq!(values[2].1, Some(Value::Text("shipped".into())));
1105 });
1106 }
1107
1108 fn assert_schema_pk_parity(
1112 parsed: &TableSchema<String>,
1113 simple: &SimpleTable,
1114 row: &[Value<String, Vec<u8>>],
1115 ) {
1116 assert_eq!(
1117 parsed.number_of_primary_keys(),
1118 simple.number_of_primary_keys(),
1119 "number_of_primary_keys",
1120 );
1121 for col in 0..simple.number_of_columns() {
1122 assert_eq!(
1123 parsed.primary_key_index(col),
1124 simple.primary_key_index(col),
1125 "primary_key_index at col {col}",
1126 );
1127 }
1128 assert_eq!(
1129 parsed.primary_key_columns(),
1130 simple.primary_key_columns(),
1131 "primary_key_columns",
1132 );
1133 assert_eq!(
1134 parsed.extract_pk(&row),
1135 simple.extract_pk(&row),
1136 "extract_pk"
1137 );
1138 }
1139
1140 #[test]
1141 fn test_parsed_schema_pk_parity_single_key() {
1142 let mut data = vec![b'T', 2, 1, 0, b'k', b'v', 0];
1144 data.push(op_codes::INSERT);
1145 data.push(0);
1146 data.push(0x01);
1147 data.extend(&1i64.to_be_bytes());
1148 data.push(0x03);
1149 data.push(1);
1150 data.push(b'x');
1151
1152 let ParsedDiffSet::Changeset(set) = ParsedDiffSet::parse(&data).unwrap() else {
1153 panic!("expected changeset");
1154 };
1155 let (parsed, _rows) = set.tables.first().expect("one table");
1156 assert_eq!(parsed.pk_flags(), &[1, 0]);
1157
1158 let simple = SimpleTable::new("kv", &["id", "val"], &[0]);
1159 let row: Vec<Value<String, Vec<u8>>> = vec![Value::Integer(1), Value::Text("x".into())];
1160 assert_schema_pk_parity(parsed, &simple, &row);
1161 assert_eq!(parsed.primary_key_columns(), vec![0]);
1162 }
1163
1164 #[test]
1165 fn test_parsed_schema_pk_parity_composite_reordered_key() {
1166 let mut data = vec![b'T', 3, 2, 1, 0, b'a', b'b', b'c', 0];
1169 data.push(op_codes::INSERT);
1170 data.push(0);
1171 data.push(0x01);
1172 data.extend(&10i64.to_be_bytes());
1173 data.push(0x01);
1174 data.extend(&20i64.to_be_bytes());
1175 data.push(0x03);
1176 data.push(1);
1177 data.push(b'z');
1178
1179 let ParsedDiffSet::Changeset(set) = ParsedDiffSet::parse(&data).unwrap() else {
1180 panic!("expected changeset");
1181 };
1182 let (parsed, _rows) = set.tables.first().expect("one table");
1183 assert_eq!(parsed.pk_flags(), &[2, 1, 0]);
1184
1185 let simple = SimpleTable::new("abc", &["a", "b", "c"], &[1, 0]);
1186 let row: Vec<Value<String, Vec<u8>>> = vec![
1187 Value::Integer(10),
1188 Value::Integer(20),
1189 Value::Text("z".into()),
1190 ];
1191 assert_schema_pk_parity(parsed, &simple, &row);
1192 assert_eq!(parsed.primary_key_columns(), vec![1, 0]);
1194 assert_eq!(
1196 parsed.extract_pk(&row),
1197 vec![Value::Integer(20), Value::Integer(10)]
1198 );
1199 }
1200}