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 (old_values, old_len) =
552 parse_values(&data[pos..], base_pos + pos, schema.column_count)?;
553 pos += old_len;
554 let (new_values, new_len) =
555 parse_values(&data[pos..], base_pos + pos, schema.column_count)?;
556 pos += new_len;
557 let pk_values: Vec<Value<String, Vec<u8>>> = old_values
559 .iter()
560 .map(|v| v.clone().unwrap_or(Value::Null))
561 .collect();
562 let pk = schema.extract_pk(&pk_values);
563 let values: Vec<((), MaybeValue<String, Vec<u8>>)> =
564 new_values.into_iter().map(|v| ((), v)).collect();
565 builder.add_operation(schema, pk, Operation::Update { values, indirect });
566 }
567 _ => return Err(ParseError::InvalidOpCode(op_code, base_pos)),
568 }
569
570 Ok(pos)
571}
572
573fn expand_pk_values(
578 pk_flags: &[u8],
579 pk_values: Vec<MaybeValue<String, Vec<u8>>>,
580 column_count: usize,
581) -> Vec<MaybeValue<String, Vec<u8>>> {
582 let mut full: Vec<MaybeValue<String, Vec<u8>>> = vec![None; column_count];
583 let mut pk_iter = pk_values.into_iter();
584 for (i, &pk_ordinal) in pk_flags.iter().enumerate() {
585 if pk_ordinal > 0
586 && let Some(v) = pk_iter.next()
587 {
588 full[i] = v;
589 }
590 }
591 full
592}
593
594fn parse_values(data: &[u8], base_pos: usize, count: usize) -> Result<ParsedValues, ParseError> {
596 let mut values = Vec::with_capacity(count);
597 let mut pos = 0;
598
599 for _ in 0..count {
600 let (value, value_len) =
601 decode_value(&data[pos..]).ok_or(ParseError::InvalidValue(base_pos + pos))?;
602 values.push(value);
603 pos += value_len;
604 }
605
606 Ok((values, pos))
607}
608
609#[cfg(test)]
610mod tests {
611 use super::*;
612 use alloc::vec;
613
614 #[test]
615 fn test_parse_empty() {
616 let result = ParsedDiffSet::parse(&[]);
617 assert!(result.is_ok());
618 assert!(result.unwrap().is_changeset());
619 }
620
621 #[test]
622 fn test_parse_table_header() {
623 let data = [b'T', 2, 1, 0, b't', 0];
625 let (schema, format, len) = parse_table_header(&data, 0).unwrap();
626
627 assert_eq!(format, FormatMarker::Changeset);
628 assert_eq!(schema.column_count, 2);
629 assert_eq!(schema.pk_flags, vec![1, 0]); assert_eq!(schema.name, "t");
631 assert_eq!(len, 6);
632 }
633
634 #[test]
635 fn test_parse_insert_changeset() {
636 let mut data = vec![b'T', 2, 1, 0, b't', 0];
638 data.push(op_codes::INSERT);
640 data.push(0);
641 data.push(0x01);
643 data.extend(&1i64.to_be_bytes());
644 data.push(0x03);
646 data.push(1);
647 data.push(b'a');
648
649 let parsed = ParsedDiffSet::parse(&data).unwrap();
650 assert!(parsed.is_changeset());
651 }
652
653 #[test]
654 fn test_parse_delete_changeset() {
655 let mut data = vec![b'T', 2, 1, 0, b't', 0];
656 data.push(op_codes::DELETE);
657 data.push(0);
658 data.push(0x01);
660 data.extend(&1i64.to_be_bytes());
661 data.push(0x03);
663 data.push(1);
664 data.push(b'a');
665
666 let parsed = ParsedDiffSet::parse(&data).unwrap();
667 assert!(parsed.is_changeset());
668 }
669
670 #[test]
671 fn test_parse_delete_patchset() {
672 let mut data = vec![b'P', 2, 1, 0, b't', 0];
674 data.push(op_codes::DELETE);
675 data.push(0);
676 data.push(0x01);
678 data.extend(&1i64.to_be_bytes());
679
680 let parsed = ParsedDiffSet::parse(&data).unwrap();
681 assert!(parsed.is_patchset());
682 }
683
684 #[test]
685 fn test_parse_update_changeset() {
686 let mut data = vec![b'T', 2, 1, 0, b't', 0];
687 data.push(op_codes::UPDATE);
688 data.push(0);
689 data.push(0x01);
691 data.extend(&1i64.to_be_bytes());
692 data.push(0x03);
693 data.push(1);
694 data.push(b'a');
695 data.push(0x01);
697 data.extend(&1i64.to_be_bytes());
698 data.push(0x03);
699 data.push(1);
700 data.push(b'b');
701
702 let parsed = ParsedDiffSet::parse(&data).unwrap();
703 assert!(parsed.is_changeset());
704 }
705
706 #[test]
707 fn test_is_changeset() {
708 let data = vec![b'T', 1, 1, b't', 0];
709 let parsed = ParsedDiffSet::parse(&data).unwrap();
710 assert!(parsed.is_changeset());
711 assert!(!parsed.is_patchset());
712 }
713
714 #[test]
715 fn test_is_patchset() {
716 let data = vec![b'P', 1, 1, b't', 0];
717 let parsed = ParsedDiffSet::parse(&data).unwrap();
718 assert!(parsed.is_patchset());
719 assert!(!parsed.is_changeset());
720 }
721
722 #[test]
723 fn test_parsed_table_schema_dyn_table() {
724 let schema: TableSchema<String> = TableSchema::new("users".into(), 3, vec![1, 0, 0]);
725 assert_eq!(schema.name(), "users");
726 assert_eq!(schema.number_of_columns(), 3);
727
728 let mut buf = [0u8; 3];
729 schema.write_pk_flags(&mut buf);
730 assert_eq!(buf, [1, 0, 0]);
731 }
732
733 #[test]
734 fn test_parsed_table_schema_extract_pk() {
735 let schema: TableSchema<String> = TableSchema::new("users".into(), 3, vec![1, 0, 2]);
736 let values: Vec<Value<String, Vec<u8>>> = vec![
737 Value::Integer(1),
738 Value::Text("alice".into()),
739 Value::Integer(100),
740 ];
741 let pk = schema.extract_pk(&values);
742 let expected: Vec<Value<String, Vec<u8>>> = vec![Value::Integer(1), Value::Integer(100)];
743 assert_eq!(pk, expected);
744 }
745
746 #[test]
749 fn test_parse_invalid_table_marker() {
750 let data = [0xFFu8, 1, 1, b't', 0];
751 let err = ParsedDiffSet::parse(&data).unwrap_err();
752 assert!(
753 matches!(err, ParseError::InvalidTableMarker(0xFF, 0)),
754 "got {err:?}"
755 );
756 }
757
758 #[test]
759 fn test_parse_unexpected_eof_in_table_header() {
760 let data = *b"T";
762 let err = ParsedDiffSet::parse(&data).unwrap_err();
763 assert!(matches!(err, ParseError::UnexpectedEof(_)), "got {err:?}");
764 }
765
766 #[test]
767 fn test_parse_unexpected_eof_in_pk_flags() {
768 let data = [b'T', 3, 1];
770 let err = ParsedDiffSet::parse(&data).unwrap_err();
771 assert!(matches!(err, ParseError::UnexpectedEof(_)), "got {err:?}");
772 }
773
774 #[test]
775 fn test_parse_unterminated_table_name() {
776 let data = [b'T', 1, 1, b'a', b'b', b'c'];
778 let err = ParsedDiffSet::parse(&data).unwrap_err();
779 assert!(
780 matches!(err, ParseError::UnterminatedTableName),
781 "got {err:?}"
782 );
783 }
784
785 #[test]
786 fn test_parse_invalid_utf8_in_table_name() {
787 let data = [b'T', 1, 1, 0xFF, 0];
789 let err = ParsedDiffSet::parse(&data).unwrap_err();
790 assert!(
791 matches!(err, ParseError::InvalidTableName(_)),
792 "got {err:?}"
793 );
794 }
795
796 #[test]
797 fn test_parse_mixed_formats_changeset_then_patchset() {
798 let mut data = vec![b'T', 1, 1, b'a', 0];
800 data.extend_from_slice(&[b'P', 1, 1, b'b', 0]);
802 let err = ParsedDiffSet::parse(&data).unwrap_err();
803 assert!(
804 matches!(
805 err,
806 ParseError::MixedFormats {
807 expected: FormatMarker::Changeset,
808 found: FormatMarker::Patchset,
809 ..
810 }
811 ),
812 "got {err:?}"
813 );
814 }
815
816 #[test]
817 fn test_parse_mixed_formats_patchset_then_changeset() {
818 let mut data = vec![b'P', 1, 1, b'a', 0];
819 data.extend_from_slice(&[b'T', 1, 1, b'b', 0]);
820 let err = ParsedDiffSet::parse(&data).unwrap_err();
821 assert!(
822 matches!(
823 err,
824 ParseError::MixedFormats {
825 expected: FormatMarker::Patchset,
826 found: FormatMarker::Changeset,
827 ..
828 }
829 ),
830 "got {err:?}"
831 );
832 }
833
834 fn make_insert_with_indirect(indirect_byte: u8) -> Vec<u8> {
836 let mut data = vec![b'T', 1, 1, b't', 0];
837 data.push(op_codes::INSERT);
838 data.push(indirect_byte);
839 data.push(0x01);
841 data.extend(&1i64.to_be_bytes());
842 data
843 }
844
845 fn first_op_indirect_changeset(data: &[u8]) -> bool {
846 let parsed = ParsedDiffSet::parse(data).unwrap();
847 let ParsedDiffSet::Changeset(set) = parsed else {
848 panic!("expected Changeset");
849 };
850 set.tables
851 .iter()
852 .find_map(|(_schema, rows)| rows.first().map(|(_, op)| op.indirect()))
853 .expect("expected at least one op")
854 }
855
856 #[test]
857 fn test_parse_changeset_indirect_flag_set() {
858 let data = make_insert_with_indirect(1);
859 assert!(first_op_indirect_changeset(&data));
860 }
861
862 #[test]
863 fn test_parse_changeset_indirect_flag_clear() {
864 let data = make_insert_with_indirect(0);
865 assert!(!first_op_indirect_changeset(&data));
866 }
867
868 #[test]
869 fn test_parse_indirect_nonzero_treated_as_true() {
870 let data = make_insert_with_indirect(0x42);
872 assert!(first_op_indirect_changeset(&data));
873 }
874
875 #[test]
876 fn test_parsed_diffset_variant_mismatch_partial_eq() {
877 let changeset = ParsedDiffSet::parse(&[b'T', 1, 1, b't', 0]).unwrap();
878 let patchset = ParsedDiffSet::parse(&[b'P', 1, 1, b't', 0]).unwrap();
879 let mut full_changeset = vec![b'T', 1, 1, b't', 0];
882 full_changeset.push(op_codes::INSERT);
883 full_changeset.push(0);
884 full_changeset.push(0x01);
885 full_changeset.extend(&1i64.to_be_bytes());
886 let cs = ParsedDiffSet::parse(&full_changeset).unwrap();
887
888 let mut full_patchset = vec![b'P', 1, 1, b't', 0];
889 full_patchset.push(op_codes::INSERT);
890 full_patchset.push(0);
891 full_patchset.push(0x01);
892 full_patchset.extend(&1i64.to_be_bytes());
893 let ps = ParsedDiffSet::parse(&full_patchset).unwrap();
894
895 assert_ne!(cs, ps);
896 assert_eq!(changeset, patchset);
898 }
899
900 #[test]
901 fn test_parse_unexpected_eof_in_operation_header() {
902 let data = [b'T', 1, 1, b't', 0, op_codes::INSERT];
905 let err = ParsedDiffSet::parse(&data).unwrap_err();
906 assert!(matches!(err, ParseError::UnexpectedEof(_)), "got {err:?}");
907 }
908
909 #[test]
910 fn test_parse_patchset_indirect_flag_set() {
911 let mut data = vec![b'P', 1, 1, b't', 0];
913 data.push(op_codes::INSERT);
914 data.push(1);
915 data.push(0x01);
916 data.extend(&1i64.to_be_bytes());
917
918 let parsed = ParsedDiffSet::parse(&data).unwrap();
919 let ParsedDiffSet::Patchset(set) = parsed else {
920 panic!("expected Patchset");
921 };
922 let indirect = set
923 .tables
924 .iter()
925 .find_map(|(_schema, rows)| rows.first().map(|(_, op)| op.indirect()))
926 .expect("expected at least one op");
927 assert!(indirect);
928 }
929}