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 #[error(
85 "Invalid primary-key flags for table {table_name:?} at position {position}: \
86 nonzero bytes must be a unique dense 1-based sequence"
87 )]
88 InvalidPrimaryKeyFlags {
89 table_name: String,
91 position: usize,
93 },
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub enum FormatMarker {
99 Changeset,
101 Patchset,
103}
104
105#[derive(Debug, Clone, PartialEq, Eq, Hash)]
110pub struct TableSchema<S> {
111 name: S,
113 column_count: usize,
115 pk_flags: Vec<u8>,
122}
123
124impl<S> TableSchema<S> {
125 #[inline]
134 #[must_use]
135 pub fn new(name: S, column_count: usize, pk_flags: Vec<u8>) -> Self {
136 assert_eq!(pk_flags.len(), column_count);
137 assert!(
138 pk_flags_are_dense_ordinals(&pk_flags),
139 "pk_flags must hold the dense key ordinals 1..=n"
140 );
141 Self {
142 name,
143 column_count,
144 pk_flags,
145 }
146 }
147
148 #[inline]
150 #[must_use]
151 pub fn name(&self) -> &S {
152 &self.name
153 }
154
155 #[inline]
160 #[must_use]
161 pub fn pk_flags(&self) -> &[u8] {
162 &self.pk_flags
163 }
164}
165
166impl<S: AsRef<str> + Clone + Eq + core::fmt::Debug> DynTable for TableSchema<S> {
167 #[inline]
168 fn name(&self) -> &str {
169 self.name.as_ref()
170 }
171
172 #[inline]
173 fn number_of_columns(&self) -> usize {
174 self.column_count
175 }
176
177 #[inline]
178 fn write_pk_flags(&self, buf: &mut [u8]) {
179 assert_eq!(buf.len(), self.column_count);
180 buf.copy_from_slice(&self.pk_flags);
181 }
182}
183
184impl<N: AsRef<str> + Clone + core::hash::Hash + Eq + core::fmt::Debug> SchemaWithPK
185 for TableSchema<N>
186{
187 fn number_of_primary_keys(&self) -> usize {
188 self.pk_flags.iter().filter(|&&b| b > 0).count()
189 }
190
191 fn primary_key_index(&self, col_idx: usize) -> Option<usize> {
192 self.pk_flags.get(col_idx).and_then(|&pk_ordinal| {
193 if pk_ordinal > 0 {
194 Some(usize::from(pk_ordinal - 1))
195 } else {
196 None
197 }
198 })
199 }
200
201 fn extract_pk<S, B>(
202 &self,
203 values: &impl IndexableValues<Text = S, Binary = B>,
204 ) -> alloc::vec::Vec<Value<S, B>>
205 where
206 S: Clone,
207 B: Clone,
208 {
209 self.primary_key_columns()
210 .map(|i| {
211 values
212 .get(i)
213 .expect("primary key column index out of bounds, values shorter than schema")
214 })
215 .collect()
216 }
217}
218
219#[derive(Debug, Clone, Eq)]
224pub enum ParsedDiffSet {
225 Changeset(DiffSet<ChangesetFormat, TableSchema<String>, String, Vec<u8>>),
227 Patchset(DiffSet<PatchsetFormat, TableSchema<String>, String, Vec<u8>>),
229}
230
231impl PartialEq for ParsedDiffSet {
232 fn eq(&self, other: &Self) -> bool {
233 let self_empty = match self {
234 ParsedDiffSet::Changeset(d) => d.is_empty(),
235 ParsedDiffSet::Patchset(d) => d.is_empty(),
236 };
237 let other_empty = match other {
238 ParsedDiffSet::Changeset(d) => d.is_empty(),
239 ParsedDiffSet::Patchset(d) => d.is_empty(),
240 };
241
242 if self_empty && other_empty {
243 return true;
244 }
245
246 match (self, other) {
248 (ParsedDiffSet::Changeset(a), ParsedDiffSet::Changeset(b)) => a == b,
249 (ParsedDiffSet::Patchset(a), ParsedDiffSet::Patchset(b)) => a == b,
250 _ => false,
251 }
252 }
253}
254
255impl TryFrom<&[u8]> for ParsedDiffSet {
256 type Error = ParseError;
257
258 fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
259 Self::parse(data)
260 }
261}
262
263impl From<ParsedDiffSet> for Vec<u8> {
264 fn from(diffset: ParsedDiffSet) -> Self {
265 match diffset {
266 ParsedDiffSet::Changeset(d) => d.into(),
267 ParsedDiffSet::Patchset(d) => d.into(),
268 }
269 }
270}
271
272impl ParsedDiffSet {
273 pub fn parse(data: &[u8]) -> Result<Self, ParseError> {
281 if data.is_empty() {
282 return Ok(ParsedDiffSet::Changeset(DiffSet::default()));
284 }
285
286 match data[0] {
288 markers::CHANGESET => {
289 let diffset = parse_as_changeset(data)?;
290 Ok(ParsedDiffSet::Changeset(diffset))
291 }
292 markers::PATCHSET => {
293 let diffset = parse_as_patchset(data)?;
294 Ok(ParsedDiffSet::Patchset(diffset))
295 }
296 b => Err(ParseError::InvalidTableMarker(b, 0)),
297 }
298 }
299
300 #[must_use]
302 pub fn is_changeset(&self) -> bool {
303 matches!(self, ParsedDiffSet::Changeset(_))
304 }
305
306 #[must_use]
308 pub fn is_patchset(&self) -> bool {
309 matches!(self, ParsedDiffSet::Patchset(_))
310 }
311
312 #[must_use]
314 pub fn table_schemas(&self) -> Vec<&TableSchema<String>> {
315 match self {
316 ParsedDiffSet::Changeset(d) => d
317 .tables
318 .iter()
319 .filter(|(_, ops)| !ops.is_empty())
320 .map(|(schema, _)| schema)
321 .collect(),
322 ParsedDiffSet::Patchset(d) => d
323 .tables
324 .iter()
325 .filter(|(_, ops)| !ops.is_empty())
326 .map(|(schema, _)| schema)
327 .collect(),
328 }
329 }
330
331 pub fn rename_tables<F>(&mut self, mut rename: F) -> usize
338 where
339 F: FnMut(&str) -> Option<String>,
340 {
341 fn rename_in<Fmt, F>(tables: &mut [(TableSchema<String>, Fmt)], rename: &mut F) -> usize
342 where
343 F: FnMut(&str) -> Option<String>,
344 {
345 let mut renamed = 0;
346 for (schema, _) in tables.iter_mut() {
347 if let Some(new_name) = rename(schema.name.as_str()) {
348 schema.name = new_name;
349 renamed += 1;
350 }
351 }
352 renamed
353 }
354
355 match self {
356 ParsedDiffSet::Changeset(d) => rename_in(&mut d.tables, &mut rename),
357 ParsedDiffSet::Patchset(d) => rename_in(&mut d.tables, &mut rename),
358 }
359 }
360}
361
362fn parse_as_changeset(
368 data: &[u8],
369) -> Result<DiffSet<ChangesetFormat, TableSchema<String>, String, Vec<u8>>, ParseError> {
370 let mut builder: DiffSetBuilder<ChangesetFormat, 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::Changeset {
377 return Err(ParseError::MixedFormats {
378 expected: FormatMarker::Changeset,
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_changeset_operation(&data[pos..], pos, &schema, &mut builder)?;
391 pos += op_len;
392 }
393 }
394
395 Ok(builder.into())
396}
397
398fn parse_as_patchset(
404 data: &[u8],
405) -> Result<DiffSet<PatchsetFormat, TableSchema<String>, String, Vec<u8>>, ParseError> {
406 let mut builder: DiffSetBuilder<PatchsetFormat, TableSchema<String>, String, Vec<u8>> =
407 DiffSetBuilder::new();
408 let mut pos = 0;
409
410 while pos < data.len() {
411 let (schema, format, header_len) = parse_table_header(&data[pos..], pos)?;
412 if format != FormatMarker::Patchset {
413 return Err(ParseError::MixedFormats {
414 expected: FormatMarker::Patchset,
415 found: format,
416 position: pos,
417 });
418 }
419 pos += header_len;
420
421 while pos < data.len() {
422 let byte = data[pos];
423 if byte == markers::CHANGESET || byte == markers::PATCHSET {
424 break;
425 }
426 let op_len = parse_patchset_operation(&data[pos..], pos, &schema, &mut builder)?;
427 pos += op_len;
428 }
429 }
430
431 Ok(builder.into())
432}
433
434fn parse_table_header(
436 data: &[u8],
437 base_pos: usize,
438) -> Result<(TableSchema<String>, FormatMarker, usize), ParseError> {
439 let mut pos = 0;
440
441 if data.is_empty() {
442 return Err(ParseError::UnexpectedEof(base_pos));
443 }
444 let format = match data[pos] {
445 markers::CHANGESET => FormatMarker::Changeset,
446 markers::PATCHSET => FormatMarker::Patchset,
447 b => return Err(ParseError::InvalidTableMarker(b, base_pos + pos)),
448 };
449 pos += 1;
450
451 let (column_count, varint_len) = decode_varint(&data[pos..])
452 .ok_or(ParseError::UnexpectedEof(base_pos + pos))
453 .and_then(|(count, len)| {
454 usize::try_from(count)
455 .map(|count| (count, len))
456 .map_err(|_| ParseError::UnexpectedEof(base_pos + pos))
457 })?;
458 pos += varint_len;
459
460 if pos + column_count > data.len() {
461 return Err(ParseError::UnexpectedEof(base_pos + pos));
462 }
463 let pk_flags_pos = base_pos + pos;
464 let pk_flags: Vec<u8> = data[pos..pos + column_count].to_vec();
465 pos += column_count;
466
467 let name_start = pos;
468 while pos < data.len() && data[pos] != 0 {
469 pos += 1;
470 }
471 if pos >= data.len() {
472 return Err(ParseError::UnterminatedTableName);
473 }
474 let name = String::from_utf8(data[name_start..pos].to_vec())
475 .map_err(|_| ParseError::InvalidTableName(base_pos + name_start))?;
476 pos += 1;
477
478 if !pk_flags_are_dense_ordinals(&pk_flags) {
479 return Err(ParseError::InvalidPrimaryKeyFlags {
480 table_name: name,
481 position: pk_flags_pos,
482 });
483 }
484
485 Ok((TableSchema::new(name, column_count, pk_flags), format, pos))
486}
487
488fn pk_flags_are_dense_ordinals(flags: &[u8]) -> bool {
492 let key_count = flags.iter().filter(|&&flag| flag != 0).count();
493 if key_count > usize::from(u8::MAX) {
494 return false;
495 }
496 let mut seen: [u64; 4] = [0; 4];
497 for &flag in flags {
498 if flag == 0 {
499 continue;
500 }
501 let ordinal = usize::from(flag);
502 if ordinal > key_count {
503 return false;
504 }
505 let mask = 1u64 << (ordinal % 64);
506 if seen[ordinal / 64] & mask != 0 {
507 return false;
508 }
509 seen[ordinal / 64] |= mask;
510 }
511 true
512}
513
514fn parse_operation_header(data: &[u8], base_pos: usize) -> Result<(u8, bool, usize), ParseError> {
519 if data.len() < 2 {
520 return Err(ParseError::UnexpectedEof(base_pos));
521 }
522 Ok((data[0], data[1] != 0, 2))
523}
524
525fn parse_changeset_operation(
527 data: &[u8],
528 base_pos: usize,
529 schema: &TableSchema<String>,
530 builder: &mut DiffSetBuilder<ChangesetFormat, TableSchema<String>, String, Vec<u8>>,
531) -> Result<usize, ParseError> {
532 let (op_code, indirect, mut pos) = parse_operation_header(data, base_pos)?;
533
534 match op_code {
535 op_codes::INSERT => {
536 let (values, len) = parse_values(&data[pos..], base_pos + pos, schema.column_count)?;
537 pos += len;
538 let values: Vec<Value<String, Vec<u8>>> = values
539 .into_iter()
540 .map(|v| v.unwrap_or(Value::Null))
541 .collect();
542 let pk = schema.extract_pk(&values);
543 builder.add_operation(schema, pk, Operation::Insert { values, indirect });
544 }
545 op_codes::DELETE => {
546 let (values, len) = parse_values(&data[pos..], base_pos + pos, schema.column_count)?;
547 pos += len;
548 let values: Vec<Value<String, Vec<u8>>> = values
549 .into_iter()
550 .map(|v| v.unwrap_or(Value::Null))
551 .collect();
552 let pk = schema.extract_pk(&values);
553 builder.add_operation(
554 schema,
555 pk,
556 Operation::Delete {
557 data: values,
558 indirect,
559 },
560 );
561 }
562 op_codes::UPDATE => {
563 let (old_values, old_len) =
564 parse_values(&data[pos..], base_pos + pos, schema.column_count)?;
565 pos += old_len;
566 let (new_values, new_len) =
567 parse_values(&data[pos..], base_pos + pos, schema.column_count)?;
568 pos += new_len;
569 let pk_values: Vec<Value<String, Vec<u8>>> = old_values
571 .iter()
572 .map(|v| v.clone().unwrap_or(Value::Null))
573 .collect();
574 let pk = schema.extract_pk(&pk_values);
575 let values: UpdateValues = old_values.into_iter().zip(new_values).collect();
576 builder.add_operation(schema, pk, Operation::Update { values, indirect });
577 }
578 _ => return Err(ParseError::InvalidOpCode(op_code, base_pos)),
579 }
580
581 Ok(pos)
582}
583
584fn parse_patchset_operation(
586 data: &[u8],
587 base_pos: usize,
588 schema: &TableSchema<String>,
589 builder: &mut DiffSetBuilder<PatchsetFormat, TableSchema<String>, String, Vec<u8>>,
590) -> Result<usize, ParseError> {
591 let (op_code, indirect, mut pos) = parse_operation_header(data, base_pos)?;
592
593 match op_code {
594 op_codes::INSERT => {
595 let (values, len) = parse_values(&data[pos..], base_pos + pos, schema.column_count)?;
596 pos += len;
597 let values: Vec<Value<String, Vec<u8>>> = values
598 .into_iter()
599 .map(|v| v.unwrap_or(Value::Null))
600 .collect();
601 let pk = schema.extract_pk(&values);
602 builder.add_operation(schema, pk, Operation::Insert { values, indirect });
603 }
604 op_codes::DELETE => {
605 let pk_count = schema.pk_flags.iter().filter(|&&b| b > 0).count();
607 let (pk_values, len) = parse_values(&data[pos..], base_pos + pos, pk_count)?;
608 pos += len;
609 let full_values = expand_pk_values(&schema.pk_flags, pk_values, schema.column_count);
613 let full_values_concrete: Vec<Value<String, Vec<u8>>> = full_values
615 .into_iter()
616 .map(|v| v.unwrap_or(Value::Null))
617 .collect();
618 let pk = schema.extract_pk(&full_values_concrete);
619 builder.add_operation(schema, pk, Operation::Delete { data: (), indirect });
620 }
621 op_codes::UPDATE => {
622 let (record, record_len) =
633 parse_values(&data[pos..], base_pos + pos, schema.column_count)?;
634 pos += record_len;
635
636 let mut values: Vec<((), MaybeValue<String, Vec<u8>>)> =
637 alloc::vec![((), None); schema.column_count];
638 for (col_idx, (&pk_flag, entry)) in schema.pk_flags.iter().zip(record).enumerate() {
639 if pk_flag > 0 {
640 values[col_idx] = ((), Some(entry.unwrap_or(Value::Null)));
644 } else {
645 values[col_idx] = ((), entry);
646 }
647 }
648
649 let pk = schema.extract_pk(&values);
650 builder.add_operation(schema, pk, Operation::Update { values, indirect });
651 }
652 _ => return Err(ParseError::InvalidOpCode(op_code, base_pos)),
653 }
654
655 Ok(pos)
656}
657
658fn expand_pk_values(
663 pk_flags: &[u8],
664 pk_values: Vec<MaybeValue<String, Vec<u8>>>,
665 column_count: usize,
666) -> Vec<MaybeValue<String, Vec<u8>>> {
667 let mut full: Vec<MaybeValue<String, Vec<u8>>> = vec![None; column_count];
668 let mut pk_iter = pk_values.into_iter();
669 for (i, &pk_ordinal) in pk_flags.iter().enumerate() {
670 if pk_ordinal > 0
671 && let Some(v) = pk_iter.next()
672 {
673 full[i] = v;
674 }
675 }
676 full
677}
678
679fn parse_values(data: &[u8], base_pos: usize, count: usize) -> Result<ParsedValues, ParseError> {
681 let mut values = Vec::with_capacity(count);
682 let mut pos = 0;
683
684 for _ in 0..count {
685 let (value, value_len) =
686 decode_value(&data[pos..]).ok_or(ParseError::InvalidValue(base_pos + pos))?;
687 values.push(value);
688 pos += value_len;
689 }
690
691 Ok((values, pos))
692}
693
694#[cfg(test)]
695mod tests {
696 use super::*;
697 use crate::SimpleTable;
698 use alloc::vec;
699
700 #[test]
701 fn test_parse_empty() {
702 let result = ParsedDiffSet::parse(&[]);
703 assert!(result.is_ok());
704 assert!(result.unwrap().is_changeset());
705 }
706
707 #[test]
708 fn test_parse_table_header() {
709 let data = [b'T', 2, 1, 0, b't', 0];
711 let (schema, format, len) = parse_table_header(&data, 0).unwrap();
712
713 assert_eq!(format, FormatMarker::Changeset);
714 assert_eq!(schema.column_count, 2);
715 assert_eq!(schema.pk_flags, vec![1, 0]); assert_eq!(schema.name, "t");
717 assert_eq!(len, 6);
718 }
719
720 #[test]
721 fn test_parse_insert_changeset() {
722 let mut data = vec![b'T', 2, 1, 0, b't', 0];
724 data.push(op_codes::INSERT);
726 data.push(0);
727 data.push(0x01);
729 data.extend(&1i64.to_be_bytes());
730 data.push(0x03);
732 data.push(1);
733 data.push(b'a');
734
735 let parsed = ParsedDiffSet::parse(&data).unwrap();
736 assert!(parsed.is_changeset());
737 }
738
739 #[test]
740 fn test_parse_delete_changeset() {
741 let mut data = vec![b'T', 2, 1, 0, b't', 0];
742 data.push(op_codes::DELETE);
743 data.push(0);
744 data.push(0x01);
746 data.extend(&1i64.to_be_bytes());
747 data.push(0x03);
749 data.push(1);
750 data.push(b'a');
751
752 let parsed = ParsedDiffSet::parse(&data).unwrap();
753 assert!(parsed.is_changeset());
754 }
755
756 #[test]
757 fn test_parse_delete_patchset() {
758 let mut data = vec![b'P', 2, 1, 0, b't', 0];
760 data.push(op_codes::DELETE);
761 data.push(0);
762 data.push(0x01);
764 data.extend(&1i64.to_be_bytes());
765
766 let parsed = ParsedDiffSet::parse(&data).unwrap();
767 assert!(parsed.is_patchset());
768 }
769
770 #[test]
771 fn test_parse_update_changeset() {
772 let mut data = vec![b'T', 2, 1, 0, b't', 0];
773 data.push(op_codes::UPDATE);
774 data.push(0);
775 data.push(0x01);
777 data.extend(&1i64.to_be_bytes());
778 data.push(0x03);
779 data.push(1);
780 data.push(b'a');
781 data.push(0x01);
783 data.extend(&1i64.to_be_bytes());
784 data.push(0x03);
785 data.push(1);
786 data.push(b'b');
787
788 let parsed = ParsedDiffSet::parse(&data).unwrap();
789 assert!(parsed.is_changeset());
790 }
791
792 #[test]
793 fn test_is_changeset() {
794 let data = vec![b'T', 1, 1, b't', 0];
795 let parsed = ParsedDiffSet::parse(&data).unwrap();
796 assert!(parsed.is_changeset());
797 assert!(!parsed.is_patchset());
798 }
799
800 #[test]
801 fn test_is_patchset() {
802 let data = vec![b'P', 1, 1, b't', 0];
803 let parsed = ParsedDiffSet::parse(&data).unwrap();
804 assert!(parsed.is_patchset());
805 assert!(!parsed.is_changeset());
806 }
807
808 #[test]
809 fn test_parsed_table_schema_dyn_table() {
810 let schema: TableSchema<String> = TableSchema::new("users".into(), 3, vec![1, 0, 0]);
811 assert_eq!(schema.name(), "users");
812 assert_eq!(schema.number_of_columns(), 3);
813
814 let mut buf = [0u8; 3];
815 schema.write_pk_flags(&mut buf);
816 assert_eq!(buf, [1, 0, 0]);
817 }
818
819 #[test]
820 fn test_parsed_table_schema_extract_pk() {
821 let schema: TableSchema<String> = TableSchema::new("users".into(), 3, vec![1, 0, 2]);
822 let values: Vec<Value<String, Vec<u8>>> = vec![
823 Value::Integer(1),
824 Value::Text("alice".into()),
825 Value::Integer(100),
826 ];
827 let pk = schema.extract_pk(&values);
828 let expected: Vec<Value<String, Vec<u8>>> = vec![Value::Integer(1), Value::Integer(100)];
829 assert_eq!(pk, expected);
830 }
831
832 #[test]
835 fn test_parse_invalid_table_marker() {
836 let data = [0xFFu8, 1, 1, b't', 0];
837 let err = ParsedDiffSet::parse(&data).unwrap_err();
838 assert!(
839 matches!(err, ParseError::InvalidTableMarker(0xFF, 0)),
840 "got {err:?}"
841 );
842 }
843
844 #[test]
845 fn test_parse_unexpected_eof_in_table_header() {
846 let data = *b"T";
848 let err = ParsedDiffSet::parse(&data).unwrap_err();
849 assert!(matches!(err, ParseError::UnexpectedEof(_)), "got {err:?}");
850 }
851
852 #[test]
853 fn test_parse_unexpected_eof_in_pk_flags() {
854 let data = [b'T', 3, 1];
856 let err = ParsedDiffSet::parse(&data).unwrap_err();
857 assert!(matches!(err, ParseError::UnexpectedEof(_)), "got {err:?}");
858 }
859
860 #[test]
861 fn test_parse_unterminated_table_name() {
862 let data = [b'T', 1, 1, b'a', b'b', b'c'];
864 let err = ParsedDiffSet::parse(&data).unwrap_err();
865 assert!(
866 matches!(err, ParseError::UnterminatedTableName),
867 "got {err:?}"
868 );
869 }
870
871 #[test]
872 fn test_parse_invalid_utf8_in_table_name() {
873 let data = [b'T', 1, 1, 0xFF, 0];
875 let err = ParsedDiffSet::parse(&data).unwrap_err();
876 assert!(
877 matches!(err, ParseError::InvalidTableName(_)),
878 "got {err:?}"
879 );
880 }
881
882 #[test]
883 fn test_parse_mixed_formats_changeset_then_patchset() {
884 let mut data = vec![b'T', 1, 1, b'a', 0];
886 data.extend_from_slice(&[b'P', 1, 1, b'b', 0]);
888 let err = ParsedDiffSet::parse(&data).unwrap_err();
889 assert!(
890 matches!(
891 err,
892 ParseError::MixedFormats {
893 expected: FormatMarker::Changeset,
894 found: FormatMarker::Patchset,
895 ..
896 }
897 ),
898 "got {err:?}"
899 );
900 }
901
902 #[test]
903 fn test_parse_mixed_formats_patchset_then_changeset() {
904 let mut data = vec![b'P', 1, 1, b'a', 0];
905 data.extend_from_slice(&[b'T', 1, 1, b'b', 0]);
906 let err = ParsedDiffSet::parse(&data).unwrap_err();
907 assert!(
908 matches!(
909 err,
910 ParseError::MixedFormats {
911 expected: FormatMarker::Patchset,
912 found: FormatMarker::Changeset,
913 ..
914 }
915 ),
916 "got {err:?}"
917 );
918 }
919
920 fn make_insert_with_indirect(indirect_byte: u8) -> Vec<u8> {
922 let mut data = vec![b'T', 1, 1, b't', 0];
923 data.push(op_codes::INSERT);
924 data.push(indirect_byte);
925 data.push(0x01);
927 data.extend(&1i64.to_be_bytes());
928 data
929 }
930
931 fn first_op_indirect_changeset(data: &[u8]) -> bool {
932 let parsed = ParsedDiffSet::parse(data).unwrap();
933 let ParsedDiffSet::Changeset(set) = parsed else {
934 panic!("expected Changeset");
935 };
936 set.tables
937 .iter()
938 .find_map(|(_schema, rows)| rows.first().map(|(_, op)| op.indirect()))
939 .expect("expected at least one op")
940 }
941
942 #[test]
943 fn test_parse_changeset_indirect_flag_set() {
944 let data = make_insert_with_indirect(1);
945 assert!(first_op_indirect_changeset(&data));
946 }
947
948 #[test]
949 fn test_parse_changeset_indirect_flag_clear() {
950 let data = make_insert_with_indirect(0);
951 assert!(!first_op_indirect_changeset(&data));
952 }
953
954 #[test]
955 fn test_parse_indirect_nonzero_treated_as_true() {
956 let data = make_insert_with_indirect(0x42);
958 assert!(first_op_indirect_changeset(&data));
959 }
960
961 #[test]
962 fn test_parsed_diffset_variant_mismatch_partial_eq() {
963 let changeset = ParsedDiffSet::parse(&[b'T', 1, 1, b't', 0]).unwrap();
964 let patchset = ParsedDiffSet::parse(&[b'P', 1, 1, b't', 0]).unwrap();
965 let mut full_changeset = vec![b'T', 1, 1, b't', 0];
968 full_changeset.push(op_codes::INSERT);
969 full_changeset.push(0);
970 full_changeset.push(0x01);
971 full_changeset.extend(&1i64.to_be_bytes());
972 let cs = ParsedDiffSet::parse(&full_changeset).unwrap();
973
974 let mut full_patchset = vec![b'P', 1, 1, b't', 0];
975 full_patchset.push(op_codes::INSERT);
976 full_patchset.push(0);
977 full_patchset.push(0x01);
978 full_patchset.extend(&1i64.to_be_bytes());
979 let ps = ParsedDiffSet::parse(&full_patchset).unwrap();
980
981 assert_ne!(cs, ps);
982 assert_eq!(changeset, patchset);
984 }
985
986 #[test]
987 fn test_parse_unexpected_eof_in_operation_header() {
988 let data = [b'T', 1, 1, b't', 0, op_codes::INSERT];
991 let err = ParsedDiffSet::parse(&data).unwrap_err();
992 assert!(matches!(err, ParseError::UnexpectedEof(_)), "got {err:?}");
993 }
994
995 #[test]
996 fn test_parse_patchset_indirect_flag_set() {
997 let mut data = vec![b'P', 1, 1, b't', 0];
999 data.push(op_codes::INSERT);
1000 data.push(1);
1001 data.push(0x01);
1002 data.extend(&1i64.to_be_bytes());
1003
1004 let parsed = ParsedDiffSet::parse(&data).unwrap();
1005 let ParsedDiffSet::Patchset(set) = parsed else {
1006 panic!("expected Patchset");
1007 };
1008 let indirect = set
1009 .tables
1010 .iter()
1011 .find_map(|(_schema, rows)| rows.first().map(|(_, op)| op.indirect()))
1012 .expect("expected at least one op");
1013 assert!(indirect);
1014 }
1015
1016 fn assert_patchset_update_roundtrip(
1026 data: &[u8],
1027 check: impl FnOnce(
1028 &TableSchema<String>,
1029 &[Value<String, Vec<u8>>],
1030 &[((), MaybeValue<String, Vec<u8>>)],
1031 bool,
1032 ),
1033 ) {
1034 let parsed = ParsedDiffSet::parse(data).expect("SQLite patchset UPDATE must parse");
1035 let ParsedDiffSet::Patchset(set) = parsed else {
1036 panic!("expected Patchset, got {parsed:?}");
1037 };
1038 let (schema, rows) = set.tables.first().expect("expected one table");
1039 assert_eq!(rows.len(), 1, "expected exactly one row");
1040 let (pk, op) = rows.first().expect("row map non-empty");
1041 let Operation::Update { values, indirect } = op else {
1042 panic!("expected Update, got {op:?}");
1043 };
1044 check(schema, pk.as_slice(), values.as_slice(), *indirect);
1045 let serialized: Vec<u8> = set.into();
1046 assert_eq!(serialized, data, "roundtrip must match SQLite output");
1047 }
1048
1049 #[test]
1067 fn test_parse_patchset_update_sqlite_wire_layout_single_pk() {
1068 let data: [u8; 33] = [
1069 0x50, 0x03, 0x01, 0x00, 0x00, b'o', b'r', b'd', b'e', b'r', b's', 0x00, 0x17, 0x00,
1070 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x03, 0x07, b's', b'h',
1071 b'i', b'p', b'p', b'e', b'd',
1072 ];
1073 assert_patchset_update_roundtrip(&data, |schema, pk, values, indirect| {
1074 assert_eq!(schema.name, "orders");
1075 assert_eq!(schema.column_count, 3);
1076 assert_eq!(schema.pk_flags, vec![1, 0, 0]);
1077 assert_eq!(pk, &[Value::Integer(5)]);
1078 assert!(!indirect);
1079 assert_eq!(values.len(), 3);
1080 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())));
1083 });
1084 }
1085
1086 #[test]
1097 fn test_parse_patchset_update_sqlite_wire_layout_composite_pk() {
1098 let data: [u8; 35] = [
1099 0x50, 0x03, 0x01, 0x02, 0x00, b'i', b't', b'e', b'm', b's', 0x00, 0x17, 0x00, 0x01,
1100 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
1101 0x00, 0x00, 0x02, 0x03, 0x02, b'v', b'2',
1102 ];
1103 assert_patchset_update_roundtrip(&data, |schema, pk, values, _indirect| {
1104 assert_eq!(schema.name, "items");
1105 assert_eq!(schema.pk_flags, vec![1, 2, 0]);
1106 assert_eq!(pk, &[Value::Integer(1), Value::Integer(2)]);
1109 assert_eq!(values.len(), 3);
1110 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())));
1113 });
1114 }
1115
1116 #[test]
1126 fn test_parse_patchset_update_all_non_pk_changed() {
1127 let data: [u8; 41] = [
1128 0x50, 0x03, 0x01, 0x00, 0x00, b'o', b'r', b'd', b'e', b'r', b's', 0x00, 0x17, 0x00,
1129 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00,
1130 0x00, 0x00, 0x00, 0xc8, 0x03, 0x07, b's', b'h', b'i', b'p', b'p', b'e', b'd',
1131 ];
1132 assert_patchset_update_roundtrip(&data, |_schema, _pk, values, _indirect| {
1133 assert_eq!(values[0].1, Some(Value::Integer(5)));
1134 assert_eq!(values[1].1, Some(Value::Integer(200)));
1135 assert_eq!(values[2].1, Some(Value::Text("shipped".into())));
1136 });
1137 }
1138
1139 fn assert_schema_pk_parity(
1143 parsed: &TableSchema<String>,
1144 simple: &SimpleTable,
1145 row: &[Value<String, Vec<u8>>],
1146 ) {
1147 assert_eq!(
1148 parsed.number_of_primary_keys(),
1149 simple.number_of_primary_keys(),
1150 "number_of_primary_keys",
1151 );
1152 for col in 0..simple.number_of_columns() {
1153 assert_eq!(
1154 parsed.primary_key_index(col),
1155 simple.primary_key_index(col),
1156 "primary_key_index at col {col}",
1157 );
1158 }
1159 assert_eq!(
1160 parsed.primary_key_columns().collect::<Vec<usize>>(),
1161 simple.primary_key_columns().collect::<Vec<usize>>(),
1162 "primary_key_columns",
1163 );
1164 assert_eq!(
1165 parsed.extract_pk(&row),
1166 simple.extract_pk(&row),
1167 "extract_pk"
1168 );
1169 }
1170
1171 #[test]
1172 fn test_parsed_schema_pk_parity_single_key() {
1173 let mut data = vec![b'T', 2, 1, 0, b'k', b'v', 0];
1175 data.push(op_codes::INSERT);
1176 data.push(0);
1177 data.push(0x01);
1178 data.extend(&1i64.to_be_bytes());
1179 data.push(0x03);
1180 data.push(1);
1181 data.push(b'x');
1182
1183 let ParsedDiffSet::Changeset(set) = ParsedDiffSet::parse(&data).unwrap() else {
1184 panic!("expected changeset");
1185 };
1186 let (parsed, _rows) = set.tables.first().expect("one table");
1187 assert_eq!(parsed.pk_flags(), &[1, 0]);
1188
1189 let simple = SimpleTable::new("kv", &["id", "val"], &[0]);
1190 let row: Vec<Value<String, Vec<u8>>> = vec![Value::Integer(1), Value::Text("x".into())];
1191 assert_schema_pk_parity(parsed, &simple, &row);
1192 assert_eq!(parsed.primary_key_columns().collect::<Vec<usize>>(), [0]);
1193 }
1194
1195 #[test]
1196 fn test_parsed_schema_pk_parity_composite_reordered_key() {
1197 let mut data = vec![b'T', 3, 2, 1, 0, b'a', b'b', b'c', 0];
1200 data.push(op_codes::INSERT);
1201 data.push(0);
1202 data.push(0x01);
1203 data.extend(&10i64.to_be_bytes());
1204 data.push(0x01);
1205 data.extend(&20i64.to_be_bytes());
1206 data.push(0x03);
1207 data.push(1);
1208 data.push(b'z');
1209
1210 let ParsedDiffSet::Changeset(set) = ParsedDiffSet::parse(&data).unwrap() else {
1211 panic!("expected changeset");
1212 };
1213 let (parsed, _rows) = set.tables.first().expect("one table");
1214 assert_eq!(parsed.pk_flags(), &[2, 1, 0]);
1215
1216 let simple = SimpleTable::new("abc", &["a", "b", "c"], &[1, 0]);
1217 let row: Vec<Value<String, Vec<u8>>> = vec![
1218 Value::Integer(10),
1219 Value::Integer(20),
1220 Value::Text("z".into()),
1221 ];
1222 assert_schema_pk_parity(parsed, &simple, &row);
1223 assert_eq!(parsed.primary_key_columns().collect::<Vec<usize>>(), [1, 0]);
1225 assert_eq!(
1227 parsed.extract_pk(&row),
1228 vec![Value::Integer(20), Value::Integer(10)]
1229 );
1230 }
1231
1232 #[test]
1233 fn dense_ordinals_are_accepted_and_every_other_shape_refused() {
1234 for flags in [
1235 [0, 0, 0].as_slice(),
1236 [1, 0].as_slice(),
1237 [1, 2, 0].as_slice(),
1238 [2, 1, 0].as_slice(),
1239 [3, 2, 0, 1].as_slice(),
1240 ] {
1241 assert!(pk_flags_are_dense_ordinals(flags), "{flags:?} must parse");
1242 }
1243 for flags in [
1244 [2, 0].as_slice(),
1245 [3, 0].as_slice(),
1246 [1, 1, 0].as_slice(),
1247 [255, 64, 0].as_slice(),
1248 [15, 0, 63, 215, 61, 58, 56, 56, 50].as_slice(),
1249 ] {
1250 assert!(!pk_flags_are_dense_ordinals(flags), "{flags:?} must refuse");
1251 }
1252 }
1253
1254 #[test]
1255 fn header_with_dense_flags_parses() {
1256 assert!(ParsedDiffSet::parse(&[b'T', 3, 2, 1, 0, b't', 0]).is_ok());
1257 }
1258
1259 #[test]
1260 fn pk_flags_error_carries_table_name_and_position() {
1261 let data = [b'T', 2, 2, 0, b'm', b'y', b't', b'b', b'l', 0];
1262 let err = ParsedDiffSet::parse(&data).unwrap_err();
1263 let ParseError::InvalidPrimaryKeyFlags {
1264 table_name,
1265 position,
1266 } = err
1267 else {
1268 panic!("expected InvalidPrimaryKeyFlags, got {err:?}");
1269 };
1270 assert_eq!(table_name, "mytbl");
1271 assert_eq!(position, 2);
1272 }
1273
1274 #[test]
1275 #[should_panic(expected = "pk_flags must hold the dense key ordinals")]
1276 fn table_schema_new_refuses_flags_the_parser_would_refuse() {
1277 let _ = TableSchema::new("t", 3, vec![255, 64, 0]);
1278 }
1279}