1use indexmap::IndexMap as IndexMapRaw;
44
45use alloc::vec;
46use alloc::vec::Vec;
47use core::fmt::Debug;
48use core::hash::Hash;
49use core::ops::{BitOr, BitOrAssign};
50
51use crate::{
52 SchemaWithPK,
53 builders::{
54 ChangeDelete, ChangesetFormat, ChangesetOp, Insert, Operation, PatchDelete, PatchsetFormat,
55 PatchsetOp, Update, format::Format,
56 },
57 encoding::{MaybeValue, Value, encode_defined_value, encode_value, markers, op_codes},
58};
59
60type IndexMap<K, V> = IndexMapRaw<K, V, hashbrown::DefaultHashBuilder>;
62
63type RowMap<F, S, B> = IndexMap<Vec<Value<S, B>>, Operation<F, S, B>>;
65
66type TableMap<F, T, S, B> = IndexMap<T, RowMap<F, S, B>>;
68
69type RowVec<F, S, B> = Vec<(Vec<Value<S, B>>, Operation<F, S, B>)>;
71
72type TableVec<F, T, S, B> = Vec<(T, RowVec<F, S, B>)>;
74
75const fn hash_append(h: u32, add: u32) -> u32 {
83 (h << 3) ^ h ^ add
84}
85
86#[allow(clippy::cast_sign_loss)]
90fn session_hash_append_i64(h: u32, i: i64) -> u32 {
91 let lo = (i as u64 & 0xFFFF_FFFF) as u32;
92 let hi = ((i as u64 >> 32) & 0xFFFF_FFFF) as u32;
93 let h = hash_append(h, lo);
94 hash_append(h, hi)
95}
96
97fn session_hash_append_blob(mut h: u32, data: &[u8]) -> u32 {
101 for &byte in data {
102 h = hash_append(h, u32::from(byte));
103 }
104 h
105}
106
107fn session_hash_pk<S: AsRef<str>, B: AsRef<[u8]>>(pk: &[Value<S, B>]) -> u32 {
112 let mut h: u32 = 0;
113 for value in pk {
114 match value {
115 Value::Integer(i) => {
116 h = hash_append(h, 1); h = session_hash_append_i64(h, *i);
118 }
119 Value::Real(f) => {
120 h = hash_append(h, 2); let i = i64::from_ne_bytes(f.to_ne_bytes());
123 h = session_hash_append_i64(h, i);
124 }
125 Value::Text(s) => {
126 h = hash_append(h, 3); h = session_hash_append_blob(h, s.as_ref().as_bytes());
128 }
129 Value::Blob(b) => {
130 h = hash_append(h, 4); h = session_hash_append_blob(h, b.as_ref());
132 }
133 Value::Null => {
134 }
137 }
138 }
139 h
140}
141
142fn session_row_order<S: AsRef<str>, B: AsRef<[u8]>, V>(
152 rows: &IndexMap<Vec<Value<S, B>>, V>,
153) -> Vec<usize> {
154 let n = rows.len();
155 if n == 0 {
156 return Vec::new();
157 }
158
159 let pks: Vec<&Vec<Value<S, B>>> = rows.keys().collect();
160
161 let mut n_change: usize = 0;
165 let mut buckets: Vec<Vec<usize>> = Vec::new();
166
167 for idx in 0..n {
168 if n_change == 0 || idx >= n_change / 2 {
172 let new_size = if n_change == 0 { 256 } else { n_change * 2 };
173 let mut new_buckets: Vec<Vec<usize>> = vec![Vec::new(); new_size];
174
175 for old_bucket in &buckets {
182 for &entry_idx in old_bucket.iter().rev() {
183 let h = session_hash_pk(pks[entry_idx]) as usize % new_size;
184 new_buckets[h].push(entry_idx);
185 }
186 }
187
188 buckets = new_buckets;
189 n_change = new_size;
190 }
191
192 let h = session_hash_pk(pks[idx]) as usize % n_change;
194 buckets[h].push(idx);
195 }
196
197 let mut order = Vec::with_capacity(n);
200 for bucket in &buckets {
201 for &idx in bucket.iter().rev() {
202 order.push(idx);
203 }
204 }
205
206 order
207}
208
209fn write_table_header<T: SchemaWithPK>(out: &mut Vec<u8>, marker: u8, table: &T) {
221 out.push(marker);
222
223 let num_cols = table.number_of_columns();
224 out.push(u8::try_from(num_cols).unwrap());
225
226 let pk_start = out.len();
227 out.resize(pk_start + num_cols, 0);
228 table.write_pk_flags(&mut out[pk_start..]);
229
230 out.extend(table.name().as_bytes());
231 out.push(0);
232}
233
234fn patchset_pk_mapping<T: SchemaWithPK>(table: &T) -> (Vec<u8>, Vec<Option<usize>>) {
239 let num_cols = table.number_of_columns();
240 let mut pk_flags = alloc::vec![0u8; num_cols];
241 table.write_pk_flags(&mut pk_flags);
242
243 let mut pk_col_to_pk_pos: Vec<Option<usize>> = alloc::vec![None; num_cols];
244 let mut pk_cols: Vec<(usize, u8)> = pk_flags
245 .iter()
246 .enumerate()
247 .filter_map(|(i, &ord)| if ord > 0 { Some((i, ord)) } else { None })
248 .collect();
249 pk_cols.sort_by_key(|(_, ord)| *ord);
250 for (pos, (col_idx, _)) in pk_cols.into_iter().enumerate() {
251 pk_col_to_pk_pos[col_idx] = Some(pos);
252 }
253
254 (pk_flags, pk_col_to_pk_pos)
255}
256
257fn encode_patchset_delete_values<S: AsRef<str>, B: AsRef<[u8]>>(
259 out: &mut Vec<u8>,
260 pk_flags: &[u8],
261 pk_col_to_pk_pos: &[Option<usize>],
262 pk: &[Value<S, B>],
263) {
264 for (col_idx, &pk_flag) in pk_flags.iter().enumerate() {
265 if pk_flag > 0 {
266 if let Some(pk_pos) = pk_col_to_pk_pos[col_idx] {
267 encode_value(out, Some(&pk[pk_pos]));
268 } else {
269 encode_value::<S, B>(out, None);
270 }
271 }
272 }
273}
274
275fn encode_patchset_update_old_values<S: AsRef<str>, B: AsRef<[u8]>>(
282 out: &mut Vec<u8>,
283 pk_flags: &[u8],
284 pk_col_to_pk_pos: &[Option<usize>],
285 pk: &[Value<S, B>],
286) {
287 for (col_idx, &pk_flag) in pk_flags.iter().enumerate() {
288 if pk_flag > 0
289 && let Some(pk_pos) = pk_col_to_pk_pos[col_idx]
290 {
291 encode_defined_value(out, &pk[pk_pos]);
292 }
293 }
294}
295
296fn encode_changeset_op<S: AsRef<str> + Clone + Debug, B: AsRef<[u8]> + Clone + Debug>(
298 out: &mut Vec<u8>,
299 op: &Operation<ChangesetFormat, S, B>,
300) {
301 match op {
302 Operation::Insert { values, indirect } => {
303 out.push(op_codes::INSERT);
304 out.push(u8::from(*indirect));
305 for value in values {
306 encode_value(out, Some(value));
307 }
308 }
309 Operation::Delete {
310 data: values,
311 indirect,
312 } => {
313 out.push(op_codes::DELETE);
314 out.push(u8::from(*indirect));
315 for value in values {
316 encode_value(out, Some(value));
317 }
318 }
319 Operation::Update { values, indirect } => {
320 out.push(op_codes::UPDATE);
321 out.push(u8::from(*indirect));
322 for (old, _new) in values {
323 encode_value(out, old.as_ref());
324 }
325 for (_old, new) in values {
326 encode_value(out, new.as_ref());
327 }
328 }
329 }
330}
331
332fn encode_patchset_op<S: AsRef<str>, B: AsRef<[u8]>>(
336 out: &mut Vec<u8>,
337 op: &Operation<PatchsetFormat, S, B>,
338 pk: &[Value<S, B>],
339 pk_flags: &[u8],
340 pk_col_to_pk_pos: &[Option<usize>],
341) {
342 match op {
343 Operation::Insert { values, indirect } => {
344 out.push(op_codes::INSERT);
345 out.push(u8::from(*indirect));
346 for value in values {
347 encode_value(out, Some(value));
348 }
349 }
350 Operation::Delete { data: (), indirect } => {
351 out.push(op_codes::DELETE);
352 out.push(u8::from(*indirect));
353 encode_patchset_delete_values(out, pk_flags, pk_col_to_pk_pos, pk);
354 }
355 Operation::Update { values, indirect } => {
356 out.push(op_codes::UPDATE);
357 out.push(u8::from(*indirect));
358 encode_patchset_update_old_values(out, pk_flags, pk_col_to_pk_pos, pk);
359 for (col_idx, &pk_flag) in pk_flags.iter().enumerate() {
364 if pk_flag == 0 {
365 encode_value(out, values[col_idx].1.as_ref());
366 }
367 }
368 }
369 }
370}
371
372#[derive(Debug, Clone)]
387pub struct DiffSetBuilder<F: Format<S, B>, T: SchemaWithPK, S, B> {
388 pub(crate) tables: TableMap<F, T, S, B>,
389}
390
391impl<F: Format<S, B>, T: SchemaWithPK, S, B> PartialEq for DiffSetBuilder<F, T, S, B>
402where
403 S: PartialEq + Eq + Hash + AsRef<str>,
404 B: PartialEq + Eq + Hash + AsRef<[u8]>,
405 F::Old: PartialEq,
406 F::DeleteData: PartialEq,
407{
408 fn eq(&self, other: &Self) -> bool {
409 self.tables
412 .iter()
413 .filter(|(_, ops)| !ops.is_empty())
414 .eq(other.tables.iter().filter(|(_, ops)| !ops.is_empty()))
415 }
416}
417
418impl<F: Format<S, B>, T: SchemaWithPK, S, B> Eq for DiffSetBuilder<F, T, S, B>
419where
420 S: Eq + Hash + AsRef<str>,
421 B: Eq + Hash + AsRef<[u8]>,
422 F::Old: Eq,
423 F::DeleteData: Eq,
424{
425}
426
427pub type ChangeSet<T, S, B> = DiffSetBuilder<ChangesetFormat, T, S, B>;
429pub type PatchSet<T, S, B> = DiffSetBuilder<PatchsetFormat, T, S, B>;
431
432impl<F: Format<S, B>, T: SchemaWithPK, S: AsRef<str> + Hash + Eq, B: AsRef<[u8]> + Hash + Eq>
433 Default for DiffSetBuilder<F, T, S, B>
434{
435 fn default() -> Self {
436 Self::new()
437 }
438}
439
440impl<
441 T: SchemaWithPK,
442 S: Clone + Debug + Hash + Eq + AsRef<str>,
443 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
444> From<&DiffSetBuilder<ChangesetFormat, T, S, B>> for Vec<u8>
445{
446 #[inline]
447 fn from(builder: &DiffSetBuilder<ChangesetFormat, T, S, B>) -> Self {
448 builder.build()
449 }
450}
451
452impl<
453 T: SchemaWithPK,
454 S: Clone + Debug + Hash + Eq + AsRef<str>,
455 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
456> From<DiffSetBuilder<ChangesetFormat, T, S, B>> for Vec<u8>
457{
458 #[inline]
459 fn from(builder: DiffSetBuilder<ChangesetFormat, T, S, B>) -> Self {
460 builder.build()
461 }
462}
463
464impl<T: SchemaWithPK, S: AsRef<str> + Clone + Hash + Eq, B: AsRef<[u8]> + Clone + Hash + Eq>
465 From<&DiffSetBuilder<PatchsetFormat, T, S, B>> for Vec<u8>
466{
467 #[inline]
468 fn from(builder: &DiffSetBuilder<PatchsetFormat, T, S, B>) -> Self {
469 builder.build()
470 }
471}
472
473impl<T: SchemaWithPK, S: AsRef<str> + Clone + Hash + Eq, B: AsRef<[u8]> + Clone + Hash + Eq>
474 From<DiffSetBuilder<PatchsetFormat, T, S, B>> for Vec<u8>
475{
476 #[inline]
477 fn from(builder: DiffSetBuilder<PatchsetFormat, T, S, B>) -> Self {
478 builder.build()
479 }
480}
481
482impl<F: Format<S, B>, T: SchemaWithPK, S, B> DiffSetBuilder<F, T, S, B> {
483 pub(super) fn table<'builder>(&'builder self, name: &str) -> Option<&'builder T> {
485 self.tables.keys().find(|t| t.name() == name)
486 }
487}
488
489impl<F: Format<S, B>, T: SchemaWithPK, S: AsRef<str> + Hash + Eq, B: AsRef<[u8]> + Hash + Eq>
490 DiffSetBuilder<F, T, S, B>
491{
492 #[inline]
494 #[must_use]
495 pub fn new() -> Self {
496 Self {
497 tables: IndexMap::default(),
498 }
499 }
500
501 #[inline]
506 fn ensure_table(&mut self, table: &T) -> &mut RowMap<F, S, B> {
507 self.tables.entry(table.clone()).or_default()
508 }
509
510 pub fn add_table(&mut self, table: &T) -> &mut Self {
516 self.ensure_table(table);
517 self
518 }
519
520 #[inline]
522 #[must_use]
523 pub fn is_empty(&self) -> bool {
524 self.tables.values().all(IndexMap::is_empty)
525 }
526
527 #[inline]
529 #[must_use]
530 pub fn len(&self) -> usize {
531 self.tables.values().map(IndexMap::len).sum()
532 }
533
534 pub(crate) fn add_operation(
538 &mut self,
539 table: &T,
540 pk: Vec<Value<S, B>>,
541 new_op: Operation<F, S, B>,
542 ) -> &mut Self
543 where
544 S: Clone,
545 B: Clone,
546 Operation<F, S, B>: core::ops::Add<Output = Option<Operation<F, S, B>>>,
547 {
548 let rows = self.ensure_table(table);
549
550 match rows.shift_remove_full(&pk) {
551 None => {
552 rows.insert(pk, new_op);
553 }
554 Some((original_index, _removed_key, existing)) => {
555 match (&existing, &new_op) {
557 (Operation::Insert { .. }, Operation::Update { .. }) => {
558 if let Some(combined) = existing + new_op
560 && let Operation::Insert { values, .. } = &combined
561 {
562 let new_pk = table.extract_pk(values);
563 rows.shift_remove(&new_pk);
565 let index = original_index.min(rows.len());
566 rows.shift_insert(index, new_pk, combined);
567 }
568 }
569 _ => {
570 if let Some(combined) = existing + new_op {
572 rows.shift_insert(original_index, pk, combined);
574 }
575 }
576 }
577 }
578 }
579
580 self
581 }
582}
583
584impl<F, T, S, B> DiffSetBuilder<F, T, S, B>
587where
588 F: Format<S, B>,
589 T: crate::schema::NamedColumns,
590 S: Clone + Debug + Hash + Eq + AsRef<str> + Default,
591 B: Clone + Debug + Hash + Eq + AsRef<[u8]> + Default,
592{
593 pub fn digest<E, Sch, A>(self, event: &E, schema: &Sch, adapter: &A) -> Result<Self, E::Error>
600 where
601 E: crate::wire::Digestable<F, T, S, B>,
602 Sch: crate::wire::WireSchema<E::Src, Table = T>,
603 A: crate::wire::WireAdapter<E::Src, S, B>,
604 T: crate::wire::WireColumnTypes<E::Src>,
605 {
606 event.digest_into(self, schema, adapter)
607 }
608}
609
610pub trait DiffOps<T: SchemaWithPK, S, B>: Sized {
620 type Format: Format<S, B>;
622
623 type DeleteArg;
628
629 fn insert(self, insert: Insert<T, S, B>) -> DiffSetBuilder<Self::Format, T, S, B>;
631
632 fn delete(self, delete: Self::DeleteArg) -> DiffSetBuilder<Self::Format, T, S, B>;
634
635 fn update(self, update: Update<T, Self::Format, S, B>)
637 -> DiffSetBuilder<Self::Format, T, S, B>;
638}
639
640impl<
643 T: SchemaWithPK,
644 S: Clone + Debug + Hash + Eq + AsRef<str>,
645 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
646> DiffOps<T, S, B> for DiffSetBuilder<ChangesetFormat, T, S, B>
647{
648 type Format = ChangesetFormat;
649 type DeleteArg = ChangeDelete<T, S, B>;
650
651 fn insert(mut self, insert: Insert<T, S, B>) -> Self {
652 let pk = insert.extract_pk();
653 let table = insert.as_ref().clone();
654 let indirect = insert.indirect;
655 self.add_operation(
656 &table,
657 pk,
658 Operation::Insert {
659 values: insert.into_values(),
660 indirect,
661 },
662 );
663 self
664 }
665
666 fn delete(mut self, delete: ChangeDelete<T, S, B>) -> Self {
667 let pk = delete.as_ref().extract_pk(&delete.values);
668 let table = delete.as_ref().clone();
669 let indirect = delete.indirect;
670 self.add_operation(
671 &table,
672 pk,
673 Operation::Delete {
674 data: delete.into_values(),
675 indirect,
676 },
677 );
678 self
679 }
680
681 fn update(mut self, update: Update<T, ChangesetFormat, S, B>) -> Self {
682 let old_values: Vec<_> = update
683 .values()
684 .iter()
685 .map(|(old, _): &(_, _)| old.clone().unwrap_or(Value::Null))
686 .collect();
687 let pk = update.as_ref().extract_pk(&old_values);
688 let table = update.as_ref().clone();
689 let indirect = update.indirect;
690 let values: Vec<(MaybeValue<S, B>, MaybeValue<S, B>)> = update.into();
691 self.add_operation(&table, pk, Operation::Update { values, indirect });
692 self
693 }
694}
695
696impl<T: SchemaWithPK, S: Clone + Hash + Eq + AsRef<str>, B: Clone + Hash + Eq + AsRef<[u8]>>
699 DiffOps<T, S, B> for DiffSetBuilder<PatchsetFormat, T, S, B>
700{
701 type Format = PatchsetFormat;
702 type DeleteArg = PatchDelete<T, S, B>;
703
704 fn insert(mut self, insert: Insert<T, S, B>) -> Self {
705 let pk = insert.extract_pk();
706 let table = insert.as_ref().clone();
707 let indirect = insert.indirect;
708 self.add_operation(
709 &table,
710 pk,
711 Operation::Insert {
712 values: insert.into_values(),
713 indirect,
714 },
715 );
716 self
717 }
718
719 fn delete(mut self, delete: PatchDelete<T, S, B>) -> Self {
734 let indirect = delete.indirect;
735 self.add_operation(
736 &delete.table,
737 delete.pk,
738 Operation::Delete { data: (), indirect },
739 );
740 self
741 }
742
743 fn update(mut self, update: Update<T, PatchsetFormat, S, B>) -> Self {
762 let pk = update.extract_pk();
763 let table = update.as_ref().clone();
764 let indirect = update.indirect;
765 let values: Vec<((), MaybeValue<S, B>)> = update.into();
766 self.add_operation(&table, pk, Operation::Update { values, indirect });
767 self
768 }
769}
770
771impl<
774 T: SchemaWithPK,
775 S: Clone + Debug + Hash + Eq + AsRef<str>,
776 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
777> DiffOps<T, S, B> for DiffSet<ChangesetFormat, T, S, B>
778{
779 type Format = ChangesetFormat;
780 type DeleteArg = ChangeDelete<T, S, B>;
781
782 fn insert(self, insert: Insert<T, S, B>) -> DiffSetBuilder<ChangesetFormat, T, S, B> {
783 let builder: DiffSetBuilder<ChangesetFormat, T, S, B> = self.into();
784 builder.insert(insert)
785 }
786
787 fn delete(self, delete: ChangeDelete<T, S, B>) -> DiffSetBuilder<ChangesetFormat, T, S, B> {
788 let builder: DiffSetBuilder<ChangesetFormat, T, S, B> = self.into();
789 builder.delete(delete)
790 }
791
792 fn update(
793 self,
794 update: Update<T, ChangesetFormat, S, B>,
795 ) -> DiffSetBuilder<ChangesetFormat, T, S, B> {
796 let builder: DiffSetBuilder<ChangesetFormat, T, S, B> = self.into();
797 builder.update(update)
798 }
799}
800
801impl<T: SchemaWithPK, S: Clone + Hash + Eq + AsRef<str>, B: Clone + Hash + Eq + AsRef<[u8]>>
804 DiffOps<T, S, B> for DiffSet<PatchsetFormat, T, S, B>
805{
806 type Format = PatchsetFormat;
807 type DeleteArg = PatchDelete<T, S, B>;
808
809 fn insert(self, insert: Insert<T, S, B>) -> DiffSetBuilder<PatchsetFormat, T, S, B> {
810 let builder: DiffSetBuilder<PatchsetFormat, T, S, B> = self.into();
811 builder.insert(insert)
812 }
813
814 fn delete(self, delete: PatchDelete<T, S, B>) -> DiffSetBuilder<PatchsetFormat, T, S, B> {
815 let builder: DiffSetBuilder<PatchsetFormat, T, S, B> = self.into();
816 builder.delete(delete)
817 }
818
819 fn update(
820 self,
821 update: Update<T, PatchsetFormat, S, B>,
822 ) -> DiffSetBuilder<PatchsetFormat, T, S, B> {
823 let builder: DiffSetBuilder<PatchsetFormat, T, S, B> = self.into();
824 builder.update(update)
825 }
826}
827
828impl<T: crate::schema::NamedColumns, S: Clone + Hash + Eq + AsRef<str> + for<'a> From<&'a str>>
829 DiffSetBuilder<PatchsetFormat, T, S, Vec<u8>>
830{
831 pub fn digest_sql<'input>(
841 &mut self,
842 input: &'input str,
843 ) -> Result<&mut Self, crate::builders::sql::ParseError<'input>> {
844 let mut parser = crate::builders::sql::Parser::new(input, self);
845 parser.digest_all()?;
846 Ok(self)
847 }
848}
849
850impl<
855 T: SchemaWithPK,
856 S: Clone + Debug + Hash + Eq + AsRef<str>,
857 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
858> DiffSetBuilder<ChangesetFormat, T, S, B>
859{
860 #[must_use]
869 pub fn build(&self) -> Vec<u8> {
870 let mut out = Vec::new();
871
872 for (table, rows) in &self.tables {
873 if rows.is_empty() {
874 continue;
875 }
876
877 write_table_header(&mut out, markers::CHANGESET, table);
878
879 for idx in session_row_order(rows) {
880 let (_pk, op) = rows.get_index(idx).unwrap();
881 encode_changeset_op(&mut out, op);
882 }
883 }
884
885 out
886 }
887}
888
889impl<T: SchemaWithPK, S: Clone + Debug + AsRef<str>, B: Clone + Debug + AsRef<[u8]>>
890 DiffSetBuilder<ChangesetFormat, T, S, B>
891{
892 pub fn iter(&self) -> impl Iterator<Item = ChangesetOp<'_, T, S, B>> {
898 self.tables.iter().flat_map(|(table, rows)| {
899 rows.iter().map(move |(_pk, op)| match op {
900 Operation::Insert { values, indirect } => ChangesetOp::Insert {
901 table,
902 values: values.as_slice(),
903 indirect: *indirect,
904 },
905 Operation::Update { values, indirect } => ChangesetOp::Update {
906 table,
907 values: values.as_slice(),
908 indirect: *indirect,
909 },
910 Operation::Delete { data, indirect } => ChangesetOp::Delete {
911 table,
912 old_values: data.as_slice(),
913 indirect: *indirect,
914 },
915 })
916 })
917 }
918}
919
920impl<T: SchemaWithPK, S: Clone + Hash + Eq + AsRef<str>, B: Clone + Hash + Eq + AsRef<[u8]>>
921 DiffSetBuilder<PatchsetFormat, T, S, B>
922{
923 #[must_use]
932 pub fn build(&self) -> Vec<u8> {
933 let mut out = Vec::new();
934
935 for (table, rows) in &self.tables {
936 if rows.is_empty() {
937 continue;
938 }
939
940 write_table_header(&mut out, markers::PATCHSET, table);
941
942 let (pk_flags, pk_col_to_pk_pos) = patchset_pk_mapping(table);
943
944 for idx in session_row_order(rows) {
945 let (pk, op) = rows.get_index(idx).unwrap();
946 encode_patchset_op(&mut out, op, pk, &pk_flags, &pk_col_to_pk_pos);
947 }
948 }
949
950 out
951 }
952}
953
954impl<T: SchemaWithPK, S: Clone + AsRef<str>, B: Clone + AsRef<[u8]>>
955 DiffSetBuilder<PatchsetFormat, T, S, B>
956{
957 pub fn iter(&self) -> impl Iterator<Item = PatchsetOp<'_, T, S, B>> {
965 self.tables.iter().flat_map(|(table, rows)| {
966 rows.iter().map(move |(pk, op)| match op {
967 Operation::Insert { values, indirect } => PatchsetOp::Insert {
968 table,
969 values: values.as_slice(),
970 indirect: *indirect,
971 },
972 Operation::Update { values, indirect } => PatchsetOp::Update {
973 table,
974 pk: pk.as_slice(),
975 entries: values.as_slice(),
976 indirect: *indirect,
977 },
978 Operation::Delete { indirect, .. } => PatchsetOp::Delete {
979 table,
980 pk: pk.as_slice(),
981 indirect: *indirect,
982 },
983 })
984 })
985 }
986}
987
988use crate::builders::operation::Reverse;
993
994impl<
995 T: SchemaWithPK,
996 S: Clone + Debug + Hash + Eq + AsRef<str>,
997 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
998> Reverse for DiffSetBuilder<ChangesetFormat, T, S, B>
999{
1000 type Output = DiffSetBuilder<ChangesetFormat, T, S, B>;
1001
1002 fn reverse(self) -> Self::Output {
1003 let mut reversed: DiffSetBuilder<ChangesetFormat, T, S, B> = DiffSetBuilder::new();
1004
1005 for (table, rows) in self.tables {
1006 for (pk, op) in rows {
1007 let rev_op = op.reverse();
1008
1009 reversed.add_operation(&table, pk, rev_op);
1010 }
1011 }
1012
1013 reversed
1014 }
1015}
1016
1017impl<
1023 F: Format<S, B>,
1024 T: SchemaWithPK,
1025 S: Clone + Hash + Eq + AsRef<str>,
1026 B: Clone + Hash + Eq + AsRef<[u8]>,
1027> BitOrAssign for DiffSetBuilder<F, T, S, B>
1028where
1029 Operation<F, S, B>: core::ops::Add<Output = Option<Operation<F, S, B>>>,
1030{
1031 fn bitor_assign(&mut self, rhs: Self) {
1033 for (table, rows) in rhs.tables {
1034 for (pk, op) in rows {
1035 self.add_operation(&table, pk, op);
1036 }
1037 }
1038 }
1039}
1040
1041impl<
1042 F: Format<S, B>,
1043 T: SchemaWithPK,
1044 S: Clone + Hash + Eq + AsRef<str>,
1045 B: Clone + Hash + Eq + AsRef<[u8]>,
1046> BitOr for DiffSetBuilder<F, T, S, B>
1047where
1048 Operation<F, S, B>: core::ops::Add<Output = Option<Operation<F, S, B>>>,
1049{
1050 type Output = Self;
1051
1052 #[inline]
1054 fn bitor(mut self, rhs: Self) -> Self::Output {
1055 self |= rhs;
1056 self
1057 }
1058}
1059
1060#[derive(Debug, Clone)]
1078pub struct DiffSet<F: Format<S, B>, T: SchemaWithPK, S, B> {
1079 pub(crate) tables: TableVec<F, T, S, B>,
1081}
1082
1083impl<F: Format<S, B>, T: SchemaWithPK, S, B> PartialEq for DiffSet<F, T, S, B>
1086where
1087 S: PartialEq + Eq + Hash + AsRef<str>,
1088 B: PartialEq + Eq + Hash + AsRef<[u8]>,
1089 F::Old: PartialEq,
1090 F::DeleteData: PartialEq,
1091{
1092 fn eq(&self, other: &Self) -> bool {
1093 self.tables
1094 .iter()
1095 .filter(|(_, ops)| !ops.is_empty())
1096 .eq(other.tables.iter().filter(|(_, ops)| !ops.is_empty()))
1097 }
1098}
1099
1100impl<F: Format<S, B>, T: SchemaWithPK, S, B> Eq for DiffSet<F, T, S, B>
1101where
1102 S: Eq + Hash + AsRef<str>,
1103 B: Eq + Hash + AsRef<[u8]>,
1104 F::Old: Eq,
1105 F::DeleteData: Eq,
1106{
1107}
1108
1109impl<F: Format<S, B>, T: SchemaWithPK, S: AsRef<str> + Hash + Eq, B: AsRef<[u8]> + Hash + Eq>
1110 Default for DiffSet<F, T, S, B>
1111{
1112 fn default() -> Self {
1113 Self { tables: Vec::new() }
1114 }
1115}
1116
1117impl<F: Format<S, B>, T: SchemaWithPK, S: AsRef<str> + Hash + Eq, B: AsRef<[u8]> + Hash + Eq>
1118 DiffSet<F, T, S, B>
1119{
1120 #[inline]
1122 #[must_use]
1123 pub fn is_empty(&self) -> bool {
1124 self.tables.iter().all(|(_, rows)| rows.is_empty())
1125 }
1126
1127 #[inline]
1129 #[must_use]
1130 pub fn len(&self) -> usize {
1131 self.tables.iter().map(|(_, rows)| rows.len()).sum()
1132 }
1133}
1134
1135impl<F: Format<S, B>, T: SchemaWithPK, S: AsRef<str> + Hash + Eq, B: AsRef<[u8]> + Hash + Eq>
1138 DiffSet<F, T, S, B>
1139{
1140 pub fn tables(&self) -> impl Iterator<Item = &T> {
1143 self.tables
1144 .iter()
1145 .filter(|(_, rows)| !rows.is_empty())
1146 .map(|(t, _)| t)
1147 }
1148}
1149
1150impl<T: SchemaWithPK, S: Clone + Debug + AsRef<str>, B: Clone + Debug + AsRef<[u8]>>
1151 DiffSet<ChangesetFormat, T, S, B>
1152{
1153 pub fn iter(&self) -> impl Iterator<Item = ChangesetOp<'_, T, S, B>> {
1159 self.tables.iter().flat_map(|(table, rows)| {
1160 rows.iter().map(move |(_pk, op)| match op {
1161 Operation::Insert { values, indirect } => ChangesetOp::Insert {
1162 table,
1163 values: values.as_slice(),
1164 indirect: *indirect,
1165 },
1166 Operation::Update { values, indirect } => ChangesetOp::Update {
1167 table,
1168 values: values.as_slice(),
1169 indirect: *indirect,
1170 },
1171 Operation::Delete { data, indirect } => ChangesetOp::Delete {
1172 table,
1173 old_values: data.as_slice(),
1174 indirect: *indirect,
1175 },
1176 })
1177 })
1178 }
1179}
1180
1181impl<T: SchemaWithPK, S: Clone + AsRef<str>, B: Clone + AsRef<[u8]>>
1182 DiffSet<PatchsetFormat, T, S, B>
1183{
1184 pub fn iter(&self) -> impl Iterator<Item = PatchsetOp<'_, T, S, B>> {
1191 self.tables.iter().flat_map(|(table, rows)| {
1192 rows.iter().map(move |(pk, op)| match op {
1193 Operation::Insert { values, indirect } => PatchsetOp::Insert {
1194 table,
1195 values: values.as_slice(),
1196 indirect: *indirect,
1197 },
1198 Operation::Update { values, indirect } => PatchsetOp::Update {
1199 table,
1200 pk: pk.as_slice(),
1201 entries: values.as_slice(),
1202 indirect: *indirect,
1203 },
1204 Operation::Delete { indirect, .. } => PatchsetOp::Delete {
1205 table,
1206 pk: pk.as_slice(),
1207 indirect: *indirect,
1208 },
1209 })
1210 })
1211 }
1212}
1213
1214impl<
1217 T: SchemaWithPK,
1218 S: Clone + Debug + Hash + Eq + AsRef<str>,
1219 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
1220> DiffSet<ChangesetFormat, T, S, B>
1221{
1222 #[must_use]
1226 pub fn build(&self) -> Vec<u8> {
1227 let mut out = Vec::new();
1228
1229 for (table, rows) in &self.tables {
1230 if rows.is_empty() {
1231 continue;
1232 }
1233
1234 write_table_header(&mut out, markers::CHANGESET, table);
1235
1236 for (_pk, op) in rows {
1237 encode_changeset_op(&mut out, op);
1238 }
1239 }
1240
1241 out
1242 }
1243}
1244
1245impl<T: SchemaWithPK, S: Clone + Hash + Eq + AsRef<str>, B: Clone + Hash + Eq + AsRef<[u8]>>
1248 DiffSet<PatchsetFormat, T, S, B>
1249{
1250 #[must_use]
1254 pub fn build(&self) -> Vec<u8> {
1255 let mut out = Vec::new();
1256
1257 for (table, rows) in &self.tables {
1258 if rows.is_empty() {
1259 continue;
1260 }
1261
1262 write_table_header(&mut out, markers::PATCHSET, table);
1263
1264 let (pk_flags, pk_col_to_pk_pos) = patchset_pk_mapping(table);
1265
1266 for (pk, op) in rows {
1267 encode_patchset_op(&mut out, op, pk, &pk_flags, &pk_col_to_pk_pos);
1268 }
1269 }
1270
1271 out
1272 }
1273}
1274
1275impl<
1278 T: SchemaWithPK,
1279 S: Clone + Debug + Hash + Eq + AsRef<str>,
1280 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
1281> From<&DiffSet<ChangesetFormat, T, S, B>> for Vec<u8>
1282{
1283 #[inline]
1284 fn from(diffset: &DiffSet<ChangesetFormat, T, S, B>) -> Self {
1285 diffset.build()
1286 }
1287}
1288
1289impl<
1290 T: SchemaWithPK,
1291 S: Clone + Debug + Hash + Eq + AsRef<str>,
1292 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
1293> From<DiffSet<ChangesetFormat, T, S, B>> for Vec<u8>
1294{
1295 #[inline]
1296 fn from(diffset: DiffSet<ChangesetFormat, T, S, B>) -> Self {
1297 diffset.build()
1298 }
1299}
1300
1301impl<T: SchemaWithPK, S: AsRef<str> + Clone + Hash + Eq, B: AsRef<[u8]> + Clone + Hash + Eq>
1302 From<&DiffSet<PatchsetFormat, T, S, B>> for Vec<u8>
1303{
1304 #[inline]
1305 fn from(diffset: &DiffSet<PatchsetFormat, T, S, B>) -> Self {
1306 diffset.build()
1307 }
1308}
1309
1310impl<T: SchemaWithPK, S: AsRef<str> + Clone + Hash + Eq, B: AsRef<[u8]> + Clone + Hash + Eq>
1311 From<DiffSet<PatchsetFormat, T, S, B>> for Vec<u8>
1312{
1313 #[inline]
1314 fn from(diffset: DiffSet<PatchsetFormat, T, S, B>) -> Self {
1315 diffset.build()
1316 }
1317}
1318
1319impl<
1322 T: SchemaWithPK,
1323 S: Clone + Debug + Hash + Eq + AsRef<str>,
1324 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
1325> Reverse for DiffSet<ChangesetFormat, T, S, B>
1326{
1327 type Output = DiffSet<ChangesetFormat, T, S, B>;
1328
1329 fn reverse(self) -> Self::Output {
1330 DiffSet {
1331 tables: self
1332 .tables
1333 .into_iter()
1334 .map(|(table, rows)| {
1335 let rev_rows = rows
1336 .into_iter()
1337 .map(|(pk, op)| (pk, op.reverse()))
1338 .collect();
1339 (table, rev_rows)
1340 })
1341 .collect(),
1342 }
1343 }
1344}
1345
1346impl<F: Format<S, B>, T: SchemaWithPK, S: Hash + Eq + AsRef<str>, B: Hash + Eq + AsRef<[u8]>>
1349 From<DiffSetBuilder<F, T, S, B>> for DiffSet<F, T, S, B>
1350{
1351 fn from(builder: DiffSetBuilder<F, T, S, B>) -> Self {
1352 Self {
1353 tables: builder
1354 .tables
1355 .into_iter()
1356 .map(|(table, rows)| {
1357 let ordered_rows: RowVec<F, S, B> = rows.into_iter().collect();
1358 (table, ordered_rows)
1359 })
1360 .collect(),
1361 }
1362 }
1363}
1364
1365impl<F: Format<S, B>, T: SchemaWithPK, S: Hash + Eq + AsRef<str>, B: Hash + Eq + AsRef<[u8]>>
1366 From<DiffSet<F, T, S, B>> for DiffSetBuilder<F, T, S, B>
1367{
1368 fn from(diffset: DiffSet<F, T, S, B>) -> Self {
1369 let mut builder = Self::new();
1370 for (table, rows) in diffset.tables {
1371 let map: IndexMap<Vec<Value<S, B>>, Operation<F, S, B>> = rows.into_iter().collect();
1372 builder.tables.insert(table, map);
1373 }
1374 builder
1375 }
1376}
1377
1378#[cfg(test)]
1379mod tests {
1380 use super::*;
1381 use crate::builders::operation::Indirect;
1382 use crate::encoding::Value;
1383 use alloc::{string::String, vec};
1384
1385 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
1387 struct TestTable {
1388 name: String,
1389 num_columns: usize,
1390 pk_column: usize,
1391 }
1392
1393 impl TestTable {
1394 fn new(name: &str, num_columns: usize, pk_column: usize) -> Self {
1395 Self {
1396 name: name.into(),
1397 num_columns,
1398 pk_column,
1399 }
1400 }
1401 }
1402
1403 impl crate::DynTable for TestTable {
1404 fn name(&self) -> &str {
1405 &self.name
1406 }
1407
1408 fn number_of_columns(&self) -> usize {
1409 self.num_columns
1410 }
1411
1412 fn write_pk_flags(&self, buf: &mut [u8]) {
1413 assert_eq!(buf.len(), self.num_columns);
1414 buf.fill(0);
1415 buf[self.pk_column] = 1;
1416 }
1417 }
1418
1419 impl crate::SchemaWithPK for TestTable {
1420 fn number_of_primary_keys(&self) -> usize {
1421 1
1422 }
1423
1424 fn primary_key_index(&self, col_idx: usize) -> Option<usize> {
1425 if col_idx == self.pk_column {
1426 Some(0)
1427 } else {
1428 None
1429 }
1430 }
1431
1432 fn extract_pk<S: Clone, B: Clone>(
1433 &self,
1434 values: &impl crate::IndexableValues<Text = S, Binary = B>,
1435 ) -> alloc::vec::Vec<Value<S, B>> {
1436 alloc::vec![
1437 values
1438 .get(self.pk_column)
1439 .expect("primary key column index out of bounds, values shorter than schema")
1440 ]
1441 }
1442 }
1443
1444 type ChangesetBuilder = DiffSetBuilder<ChangesetFormat, TestTable, String, Vec<u8>>;
1446
1447 #[test]
1448 fn test_insert_single_row() {
1449 let table = TestTable::new("users", 2, 0);
1450 let insert = Insert::from(table.clone())
1451 .set(0, 1i64)
1452 .unwrap()
1453 .set(1, "alice")
1454 .unwrap();
1455
1456 let builder = ChangesetBuilder::new().insert(insert);
1457
1458 assert_eq!(builder.len(), 1);
1459 assert!(!builder.is_empty());
1460 }
1461
1462 #[test]
1463 fn test_insert_then_delete_cancels_out() {
1464 let table = TestTable::new("users", 2, 0);
1465
1466 let insert = Insert::from(table.clone())
1467 .set(0, 1i64)
1468 .unwrap()
1469 .set(1, "alice")
1470 .unwrap();
1471
1472 let delete = ChangeDelete::from(table.clone())
1473 .set(0, 1i64)
1474 .unwrap()
1475 .set(1, "alice")
1476 .unwrap();
1477
1478 let builder = ChangesetBuilder::new().insert(insert).delete(delete);
1479
1480 assert_eq!(builder.len(), 0);
1481 assert!(builder.is_empty());
1482 }
1483
1484 #[test]
1485 fn test_insert_then_update_becomes_insert() {
1486 let table = TestTable::new("users", 2, 0);
1487
1488 let insert = Insert::from(table.clone())
1489 .set(0, 1i64)
1490 .unwrap()
1491 .set(1, "alice")
1492 .unwrap();
1493
1494 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
1495 .set(0, 1i64, 1i64) .unwrap()
1497 .set(1, "alice", "bob")
1498 .unwrap();
1499
1500 let builder = ChangesetBuilder::new().insert(insert).update(update);
1501
1502 assert_eq!(builder.len(), 1);
1503 }
1505
1506 #[test]
1507 fn test_delete_then_insert_same_values_cancels_out() {
1508 let table = TestTable::new("users", 2, 0);
1509
1510 let delete = ChangeDelete::from(table.clone())
1511 .set(0, 1i64)
1512 .unwrap()
1513 .set(1, "alice")
1514 .unwrap();
1515
1516 let insert = Insert::from(table.clone())
1517 .set(0, 1i64)
1518 .unwrap()
1519 .set(1, "alice")
1520 .unwrap();
1521
1522 let builder = ChangesetBuilder::new().delete(delete).insert(insert);
1523
1524 assert_eq!(builder.len(), 0);
1525 assert!(builder.is_empty());
1526 }
1527
1528 #[test]
1529 fn test_delete_then_insert_different_values_becomes_update() {
1530 let table = TestTable::new("users", 2, 0);
1531
1532 let delete = ChangeDelete::from(table.clone())
1533 .set(0, 1i64)
1534 .unwrap()
1535 .set(1, "alice")
1536 .unwrap();
1537
1538 let insert = Insert::from(table.clone())
1539 .set(0, 1i64)
1540 .unwrap()
1541 .set(1, "bob")
1542 .unwrap();
1543
1544 let builder = ChangesetBuilder::new().delete(delete).insert(insert);
1545
1546 assert_eq!(builder.len(), 1);
1547 }
1549
1550 #[test]
1551 fn test_multiple_rows() {
1552 let table = TestTable::new("users", 2, 0);
1553
1554 let insert1 = Insert::from(table.clone())
1555 .set(0, 1i64)
1556 .unwrap()
1557 .set(1, "alice")
1558 .unwrap();
1559
1560 let insert2 = Insert::from(table.clone())
1561 .set(0, 2i64)
1562 .unwrap()
1563 .set(1, "bob")
1564 .unwrap();
1565
1566 let builder = ChangesetBuilder::new().insert(insert1).insert(insert2);
1567
1568 assert_eq!(builder.len(), 2);
1569 }
1570
1571 #[test]
1572 fn test_update_then_update_consolidates() {
1573 let table = TestTable::new("users", 2, 0);
1574
1575 let update1 = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
1576 .set(0, 1i64, 1i64)
1577 .unwrap()
1578 .set(1, "alice", "bob")
1579 .unwrap();
1580
1581 let update2 = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
1582 .set(0, 1i64, 1i64)
1583 .unwrap()
1584 .set(1, "bob", "charlie")
1585 .unwrap();
1586
1587 let builder = ChangesetBuilder::new().update(update1).update(update2);
1588
1589 assert_eq!(builder.len(), 1);
1590 }
1592
1593 #[test]
1598 fn test_reverse_operation_insert_becomes_delete() {
1599 let op: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Insert {
1600 values: vec![Value::Integer(1), Value::Text("alice".into())],
1601 indirect: false,
1602 };
1603 let reversed = op.reverse();
1604 let Operation::Delete { data, .. } = reversed else {
1605 panic!("Expected Delete operation");
1606 };
1607 assert_eq!(
1608 data,
1609 vec![
1610 Value::<String, Vec<u8>>::Integer(1),
1611 Value::Text("alice".into())
1612 ]
1613 );
1614 }
1615
1616 #[test]
1617 fn test_reverse_operation_delete_becomes_insert() {
1618 let op: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Delete {
1619 data: vec![Value::Integer(1), Value::Text("alice".into())],
1620 indirect: false,
1621 };
1622 let reversed = op.reverse();
1623 let Operation::Insert { values, .. } = reversed else {
1624 panic!("Expected Insert operation");
1625 };
1626 assert_eq!(
1627 values,
1628 vec![
1629 Value::<String, Vec<u8>>::Integer(1),
1630 Value::Text("alice".into())
1631 ]
1632 );
1633 }
1634
1635 #[test]
1636 fn test_reverse_operation_update_swaps_old_new() {
1637 let op: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Update {
1638 values: vec![
1639 (Some(Value::Integer(1)), Some(Value::Integer(1))),
1640 (
1641 Some(Value::Text("alice".into())),
1642 Some(Value::Text("bob".into())),
1643 ),
1644 ],
1645 indirect: false,
1646 };
1647 let reversed = op.reverse();
1648 let Operation::Update { values, .. } = reversed else {
1649 panic!("Expected Update operation");
1650 };
1651 assert_eq!(
1652 values[0],
1653 (Some(Value::Integer(1)), Some(Value::Integer(1)))
1654 );
1655 assert_eq!(
1656 values[1],
1657 (
1658 Some(Value::Text("bob".into())),
1659 Some(Value::Text("alice".into()))
1660 )
1661 );
1662 }
1663
1664 #[test]
1665 fn test_reverse_builder_insert_becomes_delete() {
1666 let table = TestTable::new("users", 2, 0);
1667 let insert = Insert::from(table.clone())
1668 .set(0, 1i64)
1669 .unwrap()
1670 .set(1, "alice")
1671 .unwrap();
1672
1673 let builder = ChangesetBuilder::new().insert(insert);
1674 let reversed = builder.reverse();
1675
1676 assert_eq!(reversed.len(), 1);
1677 let rows = reversed.tables.get(&table).unwrap();
1679 assert!(matches!(
1680 rows.values().next().unwrap(),
1681 Operation::Delete { .. }
1682 ));
1683 }
1684
1685 #[test]
1686 fn test_reverse_builder_delete_becomes_insert() {
1687 let table = TestTable::new("users", 2, 0);
1688 let delete = ChangeDelete::from(table.clone())
1689 .set(0, 1i64)
1690 .unwrap()
1691 .set(1, "alice")
1692 .unwrap();
1693
1694 let builder = ChangesetBuilder::new().delete(delete);
1695 let reversed = builder.reverse();
1696
1697 assert_eq!(reversed.len(), 1);
1698 let rows = reversed.tables.get(&table).unwrap();
1700 assert!(matches!(
1701 rows.values().next().unwrap(),
1702 Operation::Insert { .. }
1703 ));
1704 }
1705
1706 #[test]
1707 fn test_reverse_builder_update_swaps() {
1708 let table = TestTable::new("users", 2, 0);
1709 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
1710 .set(0, 1i64, 1i64)
1711 .unwrap()
1712 .set(1, "alice", "bob")
1713 .unwrap();
1714
1715 let builder = ChangesetBuilder::new().update(update);
1716 let reversed = builder.reverse();
1717
1718 assert_eq!(reversed.len(), 1);
1719 let rows = reversed.tables.get(&table).unwrap();
1721 let Operation::Update { values, .. } = rows.values().next().unwrap() else {
1722 panic!("Expected Update operation");
1723 };
1724 assert_eq!(
1725 values[1],
1726 (
1727 Some(Value::Text("bob".into())),
1728 Some(Value::Text("alice".into()))
1729 )
1730 );
1731 }
1732
1733 #[test]
1734 fn test_reverse_is_involutory() {
1735 let table = TestTable::new("users", 2, 0);
1737 let insert = Insert::from(table.clone())
1738 .set(0, 1i64)
1739 .unwrap()
1740 .set(1, "alice")
1741 .unwrap();
1742
1743 let original_values = insert.into_values();
1744 let insert2 = Insert::from(table.clone())
1745 .set(0, 1i64)
1746 .unwrap()
1747 .set(1, "alice")
1748 .unwrap();
1749 let builder = ChangesetBuilder::new().insert(insert2);
1750 let double_reversed = builder.reverse().reverse();
1751
1752 assert_eq!(double_reversed.len(), 1);
1753 let rows = double_reversed.tables.get(&table).unwrap();
1754 let Operation::Insert { values, .. } = rows.values().next().unwrap() else {
1755 panic!("Expected Insert operation");
1756 };
1757 assert_eq!(values, &original_values);
1758 }
1759
1760 #[test]
1765 fn test_build_empty_builder() {
1766 let builder = ChangesetBuilder::new();
1767 let bytes = builder.build();
1768 assert!(bytes.is_empty());
1769 }
1770
1771 #[test]
1772 fn test_build_insert_format() {
1773 let table = TestTable::new("t", 2, 0);
1774 let insert = Insert::from(table.clone())
1775 .set(0, 1i64)
1776 .unwrap()
1777 .set(1, "a")
1778 .unwrap();
1779
1780 let builder = ChangesetBuilder::new().insert(insert);
1781 let bytes = builder.build();
1782
1783 assert!(!bytes.is_empty());
1787
1788 assert_eq!(bytes[0], b'T');
1790 assert_eq!(bytes[1], 2);
1792 assert_eq!(bytes[2], 1);
1794 assert_eq!(bytes[3], 0);
1795 assert_eq!(bytes[4], b't');
1797 assert_eq!(bytes[5], 0);
1798 assert_eq!(bytes[6], 0x12);
1800 assert_eq!(bytes[7], 0);
1802 }
1803
1804 #[test]
1805 fn test_build_delete_format() {
1806 let table = TestTable::new("t", 2, 0);
1807 let delete = ChangeDelete::from(table.clone())
1808 .set(0, 1i64)
1809 .unwrap()
1810 .set(1, "a")
1811 .unwrap();
1812
1813 let builder = ChangesetBuilder::new().delete(delete);
1814 let bytes = builder.build();
1815
1816 assert!(!bytes.is_empty());
1817 assert_eq!(bytes[0], b'T');
1818 assert_eq!(bytes[6], 0x09);
1820 }
1821
1822 #[test]
1823 fn test_build_update_format() {
1824 let table = TestTable::new("t", 2, 0);
1825 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
1826 .set(0, 1i64, 1i64)
1827 .unwrap()
1828 .set(1, "a", "b")
1829 .unwrap();
1830
1831 let builder = ChangesetBuilder::new().update(update);
1832 let bytes = builder.build();
1833
1834 assert!(!bytes.is_empty());
1835 assert_eq!(bytes[0], b'T');
1836 assert_eq!(bytes[6], 0x17);
1838 }
1839
1840 #[test]
1841 fn test_build_multiple_operations() {
1842 let table = TestTable::new("t", 2, 0);
1843
1844 let insert = Insert::from(table.clone())
1845 .set(0, 1i64)
1846 .unwrap()
1847 .set(1, "a")
1848 .unwrap();
1849
1850 let insert2 = Insert::from(table.clone())
1851 .set(0, 2i64)
1852 .unwrap()
1853 .set(1, "b")
1854 .unwrap();
1855
1856 let builder = ChangesetBuilder::new().insert(insert).insert(insert2);
1857 let bytes = builder.build();
1858
1859 assert!(!bytes.is_empty());
1860 assert_eq!(bytes[0], b'T');
1862 }
1863
1864 #[test]
1865 fn test_build_cancelled_operations_produce_empty() {
1866 let table = TestTable::new("t", 2, 0);
1867
1868 let insert = Insert::from(table.clone())
1869 .set(0, 1i64)
1870 .unwrap()
1871 .set(1, "a")
1872 .unwrap();
1873
1874 let delete = ChangeDelete::from(table.clone())
1875 .set(0, 1i64)
1876 .unwrap()
1877 .set(1, "a")
1878 .unwrap();
1879
1880 let builder = ChangesetBuilder::new().insert(insert).delete(delete);
1881 let bytes = builder.build();
1882
1883 assert!(bytes.is_empty());
1885 }
1886
1887 #[test]
1892 fn test_bitor_changeset_disjoint_rows() {
1893 let table = TestTable::new("users", 2, 0);
1894
1895 let insert1 = Insert::from(table.clone())
1896 .set(0, 1i64)
1897 .unwrap()
1898 .set(1, "alice")
1899 .unwrap();
1900
1901 let insert2 = Insert::from(table.clone())
1902 .set(0, 2i64)
1903 .unwrap()
1904 .set(1, "bob")
1905 .unwrap();
1906
1907 let cs1 = ChangesetBuilder::new().insert(insert1);
1908 let cs2 = ChangesetBuilder::new().insert(insert2);
1909
1910 let merged = cs1 | cs2;
1911 assert_eq!(merged.len(), 2);
1912 }
1913
1914 #[test]
1915 fn test_bitor_changeset_consolidates_same_row() {
1916 let table = TestTable::new("users", 2, 0);
1917
1918 let insert = Insert::from(table.clone())
1920 .set(0, 1i64)
1921 .unwrap()
1922 .set(1, "alice")
1923 .unwrap();
1924
1925 let delete = ChangeDelete::from(table.clone())
1927 .set(0, 1i64)
1928 .unwrap()
1929 .set(1, "alice")
1930 .unwrap();
1931
1932 let cs1 = ChangesetBuilder::new().insert(insert);
1933 let cs2 = ChangesetBuilder::new().delete(delete);
1934
1935 let merged = cs1 | cs2;
1937 assert_eq!(merged.len(), 0);
1938 assert!(merged.is_empty());
1939 }
1940
1941 #[test]
1942 fn test_bitor_assign_changeset() {
1943 let table = TestTable::new("users", 2, 0);
1944
1945 let insert1 = Insert::from(table.clone())
1946 .set(0, 1i64)
1947 .unwrap()
1948 .set(1, "alice")
1949 .unwrap();
1950
1951 let insert2 = Insert::from(table.clone())
1952 .set(0, 2i64)
1953 .unwrap()
1954 .set(1, "bob")
1955 .unwrap();
1956
1957 let mut cs = ChangesetBuilder::new().insert(insert1);
1958 cs |= ChangesetBuilder::new().insert(insert2);
1959
1960 assert_eq!(cs.len(), 2);
1961 }
1962
1963 #[test]
1964 fn test_bitor_patchset_disjoint_rows() {
1965 type PatchsetBuilder = DiffSetBuilder<PatchsetFormat, TestTable, String, Vec<u8>>;
1966
1967 let table = TestTable::new("users", 2, 0);
1968
1969 let insert1 = Insert::from(table.clone())
1970 .set(0, 1i64)
1971 .unwrap()
1972 .set(1, "alice")
1973 .unwrap();
1974
1975 let insert2 = Insert::from(table.clone())
1976 .set(0, 2i64)
1977 .unwrap()
1978 .set(1, "bob")
1979 .unwrap();
1980
1981 let ps1 = PatchsetBuilder::new().insert(insert1);
1982 let ps2 = PatchsetBuilder::new().insert(insert2);
1983
1984 let merged = ps1 | ps2;
1985 assert_eq!(merged.len(), 2);
1986 }
1987
1988 #[test]
1989 fn test_bitor_patchset_consolidates_same_row() {
1990 type PatchsetBuilder = DiffSetBuilder<PatchsetFormat, TestTable, String, Vec<u8>>;
1991
1992 let table = TestTable::new("users", 2, 0);
1993
1994 let insert = Insert::from(table.clone())
1996 .set(0, 1i64)
1997 .unwrap()
1998 .set(1, "alice")
1999 .unwrap();
2000
2001 let delete = PatchDelete::new(table.clone(), vec![Value::Integer(1)]);
2003
2004 let ps1 = PatchsetBuilder::new().insert(insert);
2005 let ps2 = PatchsetBuilder::new().delete(delete);
2006
2007 let merged = ps1 | ps2;
2009 assert_eq!(merged.len(), 0);
2010 assert!(merged.is_empty());
2011 }
2012
2013 #[test]
2014 fn test_bitor_multiple_tables() {
2015 let table1 = TestTable::new("users", 2, 0);
2016 let table2 = TestTable::new("posts", 2, 0);
2017
2018 let insert1 = Insert::from(table1.clone())
2019 .set(0, 1i64)
2020 .unwrap()
2021 .set(1, "alice")
2022 .unwrap();
2023
2024 let insert2 = Insert::from(table2.clone())
2025 .set(0, 100i64)
2026 .unwrap()
2027 .set(1, "first post")
2028 .unwrap();
2029
2030 let cs1 = ChangesetBuilder::new().insert(insert1);
2031 let cs2 = ChangesetBuilder::new().insert(insert2);
2032
2033 let merged = cs1 | cs2;
2034 assert_eq!(merged.len(), 2);
2035 }
2036
2037 #[test]
2038 fn test_bitor_insert_then_update_consolidates() {
2039 let table = TestTable::new("users", 2, 0);
2040
2041 let insert = Insert::from(table.clone())
2043 .set(0, 1i64)
2044 .unwrap()
2045 .set(1, "alice")
2046 .unwrap();
2047
2048 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
2050 .set(0, 1i64, 1i64)
2051 .unwrap()
2052 .set(1, "alice", "alicia")
2053 .unwrap();
2054
2055 let cs1 = ChangesetBuilder::new().insert(insert);
2056 let cs2 = ChangesetBuilder::new().update(update);
2057
2058 let merged = cs1 | cs2;
2060 assert_eq!(merged.len(), 1);
2061 }
2062
2063 #[test]
2064 fn test_session_hash_growth_with_many_rows() {
2065 let table = TestTable::new("many", 2, 0);
2069 let mut cs = ChangesetBuilder::new();
2070 for i in 0..200i64 {
2071 let insert = Insert::from(table.clone())
2072 .set(0, i)
2073 .unwrap()
2074 .set(1, alloc::format!("row-{i}"))
2075 .unwrap();
2076 cs = cs.insert(insert);
2077 }
2078 assert_eq!(cs.len(), 200);
2079
2080 let bytes = cs.build();
2081 assert!(!bytes.is_empty());
2082
2083 let reparsed = crate::parser::ParsedDiffSet::try_from(bytes.as_slice()).unwrap();
2085 let reparsed_bytes: Vec<u8> = reparsed.into();
2086 assert_eq!(bytes, reparsed_bytes);
2087 }
2088
2089 const INDIRECT_BYTE_OFFSET: usize = 7;
2097
2098 #[test]
2099 fn test_build_insert_indirect_byte_set() {
2100 let table = TestTable::new("t", 2, 0);
2101 let insert = Insert::from(table)
2102 .set(0, 1i64)
2103 .unwrap()
2104 .set(1, "a")
2105 .unwrap()
2106 .indirect(true);
2107
2108 let bytes = ChangesetBuilder::new().insert(insert).build();
2109 assert_eq!(bytes[INDIRECT_BYTE_OFFSET], 1);
2110 }
2111
2112 #[test]
2113 fn test_build_delete_indirect_byte_set() {
2114 let table = TestTable::new("t", 2, 0);
2115 let delete = ChangeDelete::from(table)
2116 .set(0, 1i64)
2117 .unwrap()
2118 .set(1, "a")
2119 .unwrap()
2120 .indirect(true);
2121
2122 let bytes = ChangesetBuilder::new().delete(delete).build();
2123 assert_eq!(bytes[INDIRECT_BYTE_OFFSET], 1);
2124 }
2125
2126 #[test]
2127 fn test_build_update_indirect_byte_set() {
2128 let table = TestTable::new("t", 2, 0);
2129 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table)
2130 .set(0, 1i64, 1i64)
2131 .unwrap()
2132 .set(1, "a", "b")
2133 .unwrap()
2134 .indirect(true);
2135
2136 let bytes = ChangesetBuilder::new().update(update).build();
2137 assert_eq!(bytes[INDIRECT_BYTE_OFFSET], 1);
2138 }
2139
2140 #[test]
2141 fn test_build_patchset_insert_indirect_byte() {
2142 let table = TestTable::new("t", 2, 0);
2143 let insert = Insert::from(table)
2144 .set(0, 1i64)
2145 .unwrap()
2146 .set(1, "a")
2147 .unwrap()
2148 .indirect(true);
2149
2150 let patchset: PatchSet<TestTable, String, Vec<u8>> = PatchSet::new().insert(insert);
2151 let bytes = patchset.build();
2152 assert_eq!(bytes[INDIRECT_BYTE_OFFSET], 1);
2153 }
2154
2155 #[test]
2156 fn test_reverse_preserves_indirect() {
2157 let insert: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Insert {
2159 values: vec![Value::Integer(1)],
2160 indirect: true,
2161 };
2162 let reversed = insert.reverse();
2163 assert!(reversed.indirect());
2164 assert!(matches!(reversed, Operation::Delete { .. }));
2165
2166 let delete: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Delete {
2168 data: vec![Value::Integer(1)],
2169 indirect: true,
2170 };
2171 let reversed = delete.reverse();
2172 assert!(reversed.indirect());
2173 assert!(matches!(reversed, Operation::Insert { .. }));
2174
2175 let update: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Update {
2177 values: vec![(Some(Value::Integer(1)), Some(Value::Integer(2)))],
2178 indirect: true,
2179 };
2180 let reversed = update.reverse();
2181 assert!(reversed.indirect());
2182 assert!(matches!(reversed, Operation::Update { .. }));
2183 }
2184
2185 #[test]
2186 fn test_patchdelete_indirect_byte_set() {
2187 let table = TestTable::new("t", 2, 0);
2188 let delete: PatchDelete<TestTable, String, Vec<u8>> =
2189 PatchDelete::new(table, vec![Value::Integer(1)]).indirect(true);
2190 let bytes = PatchSet::new().delete(delete).build();
2191 assert_eq!(bytes[INDIRECT_BYTE_OFFSET], 1);
2192 }
2193
2194 #[test]
2195 fn test_operation_eq_indirect_differs() {
2196 let a: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Insert {
2198 values: vec![Value::Integer(1)],
2199 indirect: false,
2200 };
2201 let b: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Insert {
2202 values: vec![Value::Integer(1)],
2203 indirect: true,
2204 };
2205 assert_ne!(a, b);
2206 }
2207
2208 #[test]
2209 fn test_operation_eq_variant_mismatch() {
2210 let insert: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Insert {
2212 values: vec![Value::Integer(1)],
2213 indirect: false,
2214 };
2215 let delete: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Delete {
2216 data: vec![Value::Integer(1)],
2217 indirect: false,
2218 };
2219 assert_ne!(insert, delete);
2220 }
2221
2222 #[test]
2223 fn test_bitor_indirect_rhs_wins() {
2224 let table = TestTable::new("t", 2, 0);
2227 let lhs_insert = Insert::from(table.clone())
2228 .set(0, 1i64)
2229 .unwrap()
2230 .set(1, "a")
2231 .unwrap()
2232 .indirect(true);
2233 let rhs_insert = Insert::from(table.clone())
2234 .set(0, 1i64)
2235 .unwrap()
2236 .set(1, "a")
2237 .unwrap()
2238 .indirect(false);
2239
2240 let merged =
2241 ChangesetBuilder::new().insert(lhs_insert) | ChangesetBuilder::new().insert(rhs_insert);
2242 let rows = merged.tables.get(&table).unwrap();
2243 let op = rows.values().next().unwrap();
2244 assert!(!op.indirect(), "rhs (false) should win over lhs (true)");
2245
2246 let lhs_insert = Insert::from(table.clone())
2248 .set(0, 2i64)
2249 .unwrap()
2250 .set(1, "b")
2251 .unwrap()
2252 .indirect(false);
2253 let rhs_insert = Insert::from(table.clone())
2254 .set(0, 2i64)
2255 .unwrap()
2256 .set(1, "b")
2257 .unwrap()
2258 .indirect(true);
2259
2260 let merged =
2261 ChangesetBuilder::new().insert(lhs_insert) | ChangesetBuilder::new().insert(rhs_insert);
2262 let rows = merged.tables.get(&table).unwrap();
2263 let op = rows.values().next().unwrap();
2264 assert!(op.indirect(), "rhs (true) should win over lhs (false)");
2265 }
2266
2267 #[test]
2268 fn test_roundtrip_indirect_changeset() {
2269 let table = TestTable::new("t", 2, 0);
2270 let insert = Insert::from(table)
2271 .set(0, 1i64)
2272 .unwrap()
2273 .set(1, "a")
2274 .unwrap()
2275 .indirect(true);
2276
2277 let bytes = ChangesetBuilder::new().insert(insert).build();
2278 let reparsed = crate::parser::ParsedDiffSet::try_from(bytes.as_slice()).unwrap();
2279 let reparsed_bytes: Vec<u8> = reparsed.into();
2280 assert_eq!(bytes, reparsed_bytes);
2281 assert_eq!(reparsed_bytes[INDIRECT_BYTE_OFFSET], 1);
2282 }
2283
2284 #[test]
2285 fn test_roundtrip_indirect_patchset() {
2286 let table = TestTable::new("t", 2, 0);
2287 let insert = Insert::from(table)
2288 .set(0, 1i64)
2289 .unwrap()
2290 .set(1, "a")
2291 .unwrap()
2292 .indirect(true);
2293
2294 let patchset: PatchSet<TestTable, String, Vec<u8>> = PatchSet::new().insert(insert);
2295 let bytes = patchset.build();
2296 let reparsed = crate::parser::ParsedDiffSet::try_from(bytes.as_slice()).unwrap();
2297 let reparsed_bytes: Vec<u8> = reparsed.into();
2298 assert_eq!(bytes, reparsed_bytes);
2299 assert_eq!(reparsed_bytes[INDIRECT_BYTE_OFFSET], 1);
2300 }
2301
2302 #[test]
2303 fn test_indirect_full_pipeline_roundtrip() {
2304 let table = TestTable::new("t", 2, 0);
2307 let insert = Insert::from(table)
2308 .set(0, 1i64)
2309 .unwrap()
2310 .set(1, "alice")
2311 .unwrap()
2312 .indirect(true);
2313 let original = ChangesetBuilder::new().insert(insert);
2314 let bytes_a = original.build();
2315 assert_eq!(bytes_a[INDIRECT_BYTE_OFFSET], 1);
2316
2317 let parsed = crate::parser::ParsedDiffSet::try_from(bytes_a.as_slice()).unwrap();
2319 let crate::parser::ParsedDiffSet::Changeset(parsed_set) = parsed else {
2320 panic!("expected Changeset variant");
2321 };
2322
2323 let parsed_builder: DiffSetBuilder<
2324 ChangesetFormat,
2325 crate::parser::TableSchema<String>,
2326 String,
2327 Vec<u8>,
2328 > = parsed_set.into();
2329 let empty: DiffSetBuilder<
2330 ChangesetFormat,
2331 crate::parser::TableSchema<String>,
2332 String,
2333 Vec<u8>,
2334 > = DiffSetBuilder::new();
2335 let doubled = parsed_builder.reverse().reverse();
2336 let merged = doubled | empty;
2337
2338 let bytes_b = merged.build();
2339 assert_eq!(bytes_a, bytes_b);
2340 assert_eq!(bytes_b[INDIRECT_BYTE_OFFSET], 1);
2341 }
2342
2343 fn changeset_insert(v: i64) -> Operation<ChangesetFormat, String, Vec<u8>> {
2348 Operation::Insert {
2349 values: vec![Value::Integer(v), Value::Text("a".into())],
2350 indirect: false,
2351 }
2352 }
2353
2354 fn changeset_delete(v: i64) -> Operation<ChangesetFormat, String, Vec<u8>> {
2355 Operation::Delete {
2356 data: vec![Value::Integer(v), Value::Text("a".into())],
2357 indirect: false,
2358 }
2359 }
2360
2361 fn changeset_update(old: i64, new: i64) -> Operation<ChangesetFormat, String, Vec<u8>> {
2362 Operation::Update {
2363 values: vec![
2364 (Some(Value::Integer(old)), Some(Value::Integer(new))),
2365 (Some(Value::Text("a".into())), Some(Value::Text("b".into()))),
2366 ],
2367 indirect: false,
2368 }
2369 }
2370
2371 #[test]
2372 fn test_add_changeset_insert_plus_update() {
2373 let merged = (changeset_insert(1) + changeset_update(1, 2)).unwrap();
2374 let Operation::Insert { values, .. } = merged else {
2375 panic!("expected Insert");
2376 };
2377 assert_eq!(values[0], Value::Integer(2));
2378 assert_eq!(values[1], Value::Text("b".into()));
2379 }
2380
2381 #[test]
2382 fn test_add_changeset_update_plus_insert() {
2383 let merged = (changeset_update(1, 2) + changeset_insert(99)).unwrap();
2385 assert!(matches!(merged, Operation::Update { .. }));
2386 }
2387
2388 #[test]
2389 fn test_add_changeset_update_plus_update() {
2390 let merged = (changeset_update(1, 2) + changeset_update(2, 3)).unwrap();
2392 let Operation::Update { values, .. } = merged else {
2393 panic!("expected Update");
2394 };
2395 assert_eq!(values[0].0, Some(Value::Integer(1)));
2396 assert_eq!(values[0].1, Some(Value::Integer(3)));
2397 }
2398
2399 #[test]
2400 fn test_add_changeset_update_plus_delete() {
2401 let merged = (changeset_update(1, 2) + changeset_delete(99)).unwrap();
2403 let Operation::Delete { data, .. } = merged else {
2404 panic!("expected Delete");
2405 };
2406 assert_eq!(data[0], Value::Integer(1));
2407 assert_eq!(data[1], Value::Text("a".into()));
2408 }
2409
2410 #[test]
2411 fn test_add_changeset_delete_plus_update_keeps_delete() {
2412 let merged = (changeset_delete(1) + changeset_update(1, 2)).unwrap();
2413 assert!(matches!(merged, Operation::Delete { .. }));
2414 }
2415
2416 #[test]
2417 fn test_add_changeset_delete_plus_delete_keeps_first() {
2418 let merged = (changeset_delete(1) + changeset_delete(2)).unwrap();
2419 let Operation::Delete { data, .. } = merged else {
2420 panic!("expected Delete");
2421 };
2422 assert_eq!(data[0], Value::Integer(1));
2423 }
2424
2425 fn patchset_insert(v: i64) -> Operation<PatchsetFormat, String, Vec<u8>> {
2426 Operation::Insert {
2427 values: vec![Value::Integer(v), Value::Text("a".into())],
2428 indirect: false,
2429 }
2430 }
2431
2432 fn patchset_update(new: i64) -> Operation<PatchsetFormat, String, Vec<u8>> {
2433 Operation::Update {
2434 values: vec![
2435 ((), Some(Value::Integer(new))),
2436 ((), Some(Value::Text("b".into()))),
2437 ],
2438 indirect: false,
2439 }
2440 }
2441
2442 fn patchset_delete() -> Operation<PatchsetFormat, String, Vec<u8>> {
2443 Operation::Delete {
2444 data: (),
2445 indirect: false,
2446 }
2447 }
2448
2449 #[test]
2450 fn test_add_patchset_insert_plus_update() {
2451 let merged = (patchset_insert(1) + patchset_update(2)).unwrap();
2452 let Operation::Insert { values, .. } = merged else {
2453 panic!("expected Insert");
2454 };
2455 assert_eq!(values[0], Value::Integer(2));
2456 assert_eq!(values[1], Value::Text("b".into()));
2457 }
2458
2459 #[test]
2460 fn test_add_patchset_update_plus_insert() {
2461 let merged = (patchset_update(2) + patchset_insert(99)).unwrap();
2462 assert!(matches!(merged, Operation::Update { .. }));
2463 }
2464
2465 #[test]
2466 fn test_add_patchset_update_plus_update() {
2467 let merged = (patchset_update(2) + patchset_update(3)).unwrap();
2468 let Operation::Update { values, .. } = merged else {
2469 panic!("expected Update");
2470 };
2471 assert_eq!(values[0].1, Some(Value::Integer(3)));
2472 }
2473
2474 #[test]
2475 fn test_add_patchset_update_plus_delete() {
2476 let merged = (patchset_update(2) + patchset_delete()).unwrap();
2477 assert!(matches!(merged, Operation::Delete { .. }));
2478 }
2479
2480 #[test]
2481 fn test_add_patchset_delete_plus_insert_promotes_to_update() {
2482 let merged = (patchset_delete() + patchset_insert(1)).unwrap();
2484 assert!(matches!(merged, Operation::Update { .. }));
2485 }
2486
2487 #[test]
2488 fn test_add_patchset_delete_plus_update_keeps_delete() {
2489 let merged = (patchset_delete() + patchset_update(2)).unwrap();
2490 assert!(matches!(merged, Operation::Delete { .. }));
2491 }
2492
2493 #[test]
2494 fn test_add_patchset_delete_plus_delete_keeps_first() {
2495 let merged = (patchset_delete() + patchset_delete()).unwrap();
2496 assert!(matches!(merged, Operation::Delete { .. }));
2497 }
2498
2499 #[test]
2504 fn test_session_hash_real_pk() {
2505 let table = TestTable::new("t", 2, 0);
2506 let insert = Insert::from(table)
2507 .set(0, 2.5f64)
2508 .unwrap()
2509 .set(1, "a")
2510 .unwrap();
2511 let bytes = ChangesetBuilder::new().insert(insert).build();
2512 assert!(!bytes.is_empty());
2513 }
2514
2515 #[test]
2516 fn test_session_hash_text_pk() {
2517 let table = TestTable::new("t", 2, 0);
2518 let insert = Insert::from(table)
2519 .set(0, "alice")
2520 .unwrap()
2521 .set(1, 42i64)
2522 .unwrap();
2523 let bytes = ChangesetBuilder::new().insert(insert).build();
2524 assert!(!bytes.is_empty());
2525 }
2526
2527 #[test]
2528 fn test_session_hash_blob_pk() {
2529 let table = TestTable::new("t", 2, 0);
2530 let insert = Insert::from(table)
2531 .set(0, alloc::vec![0xDE_u8, 0xAD, 0xBE, 0xEF])
2532 .unwrap()
2533 .set(1, "a")
2534 .unwrap();
2535 let bytes = ChangesetBuilder::new().insert(insert).build();
2536 assert!(!bytes.is_empty());
2537 }
2538
2539 #[test]
2544 fn test_session_row_order_empty() {
2545 let cs: ChangesetBuilder = ChangesetBuilder::new();
2547 let bytes = cs.build();
2548 assert!(bytes.is_empty());
2549 }
2550
2551 #[test]
2552 fn test_session_row_order_empty_rows_returns_empty_vec() {
2553 let rows: RowMap<ChangesetFormat, String, Vec<u8>> = IndexMap::default();
2555 assert!(session_row_order(&rows).is_empty());
2556 }
2557
2558 #[test]
2559 fn test_diffset_patchset_build_skips_empty_table() {
2560 let table = TestTable::new("t", 2, 0);
2562 let mut builder: PatchSet<TestTable, String, Vec<u8>> = PatchSet::new();
2563 builder.add_table(&table);
2564 let frozen: DiffSet<PatchsetFormat, TestTable, String, Vec<u8>> = builder.into();
2565 let bytes = frozen.build();
2566 assert!(bytes.is_empty());
2567 }
2568
2569 #[test]
2574 fn test_from_changeset_builder_into_vec() {
2575 let table = TestTable::new("t", 2, 0);
2576 let insert = Insert::from(table)
2577 .set(0, 1i64)
2578 .unwrap()
2579 .set(1, "a")
2580 .unwrap();
2581 let builder = ChangesetBuilder::new().insert(insert);
2582 let bytes_owned: Vec<u8> = builder.clone().into();
2583 let bytes_ref: Vec<u8> = (&builder).into();
2584 assert_eq!(bytes_owned, bytes_ref);
2585 let frozen: DiffSet<ChangesetFormat, TestTable, String, Vec<u8>> = builder.into();
2586 let bytes_frozen_owned: Vec<u8> = frozen.clone().into();
2587 let bytes_frozen_ref: Vec<u8> = (&frozen).into();
2588 assert_eq!(bytes_frozen_owned, bytes_frozen_ref);
2589 }
2590
2591 #[test]
2592 fn test_from_patchset_builder_into_vec() {
2593 let table = TestTable::new("t", 2, 0);
2594 let insert = Insert::from(table)
2595 .set(0, 1i64)
2596 .unwrap()
2597 .set(1, "a")
2598 .unwrap();
2599 let builder: PatchSet<TestTable, String, Vec<u8>> = PatchSet::new().insert(insert);
2600 let bytes_owned: Vec<u8> = builder.clone().into();
2601 let bytes_ref: Vec<u8> = (&builder).into();
2602 assert_eq!(bytes_owned, bytes_ref);
2603 let frozen: DiffSet<PatchsetFormat, TestTable, String, Vec<u8>> = builder.into();
2604 let bytes_frozen_owned: Vec<u8> = frozen.clone().into();
2605 let bytes_frozen_ref: Vec<u8> = (&frozen).into();
2606 assert_eq!(bytes_frozen_owned, bytes_frozen_ref);
2607 }
2608
2609 #[test]
2614 fn test_add_operation_insert_then_update_changes_pk() {
2615 let table = TestTable::new("t", 2, 0);
2618 let insert = Insert::from(table.clone())
2619 .set(0, 1i64)
2620 .unwrap()
2621 .set(1, "alice")
2622 .unwrap();
2623 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
2624 .set(0, 1i64, 2i64)
2625 .unwrap()
2626 .set(1, "alice", "bob")
2627 .unwrap();
2628 let builder = ChangesetBuilder::new().insert(insert).update(update);
2629 let rows = builder.tables.get(&table).unwrap();
2630 assert_eq!(rows.len(), 1);
2631 let (pk, op) = rows.iter().next().unwrap();
2633 assert_eq!(pk[0], Value::Integer(2));
2634 let Operation::Insert { values, .. } = op else {
2635 panic!("expected merged INSERT");
2636 };
2637 assert_eq!(values[0], Value::Integer(2));
2638 assert_eq!(values[1], Value::Text("bob".into()));
2639 }
2640
2641 #[test]
2646 fn test_diffset_changeset_diffops_wrappers() {
2647 let table = TestTable::new("t", 2, 0);
2648 let initial = Insert::from(table.clone())
2649 .set(0, 1i64)
2650 .unwrap()
2651 .set(1, "a")
2652 .unwrap();
2653 let frozen: DiffSet<ChangesetFormat, TestTable, String, Vec<u8>> =
2654 ChangesetBuilder::new().insert(initial).into();
2655
2656 let insert2 = Insert::from(table.clone())
2657 .set(0, 2i64)
2658 .unwrap()
2659 .set(1, "b")
2660 .unwrap();
2661 let after_insert = <_ as DiffOps<_, _, _>>::insert(frozen.clone(), insert2);
2662 assert_eq!(after_insert.len(), 2);
2663
2664 let delete = ChangeDelete::from(table.clone())
2665 .set(0, 1i64)
2666 .unwrap()
2667 .set(1, "a")
2668 .unwrap();
2669 let after_delete = <_ as DiffOps<_, _, _>>::delete(frozen.clone(), delete);
2670 assert_eq!(after_delete.len(), 0);
2671
2672 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table)
2673 .set(0, 1i64, 1i64)
2674 .unwrap()
2675 .set(1, "a", "z")
2676 .unwrap();
2677 let after_update = <_ as DiffOps<_, _, _>>::update(frozen, update);
2678 assert_eq!(after_update.len(), 1);
2679 }
2680
2681 #[test]
2682 fn test_diffset_patchset_diffops_wrappers() {
2683 let table = TestTable::new("t", 2, 0);
2684 let initial = Insert::from(table.clone())
2685 .set(0, 1i64)
2686 .unwrap()
2687 .set(1, "a")
2688 .unwrap();
2689 let frozen: DiffSet<PatchsetFormat, TestTable, String, Vec<u8>> =
2690 PatchSet::new().insert(initial).into();
2691
2692 let insert2 = Insert::from(table.clone())
2693 .set(0, 2i64)
2694 .unwrap()
2695 .set(1, "b")
2696 .unwrap();
2697 let after_insert = <_ as DiffOps<_, _, _>>::insert(frozen.clone(), insert2);
2698 assert_eq!(after_insert.len(), 2);
2699
2700 let delete: PatchDelete<TestTable, String, Vec<u8>> =
2701 PatchDelete::new(table.clone(), vec![Value::Integer(1)]);
2702 let after_delete = <_ as DiffOps<_, _, _>>::delete(frozen.clone(), delete);
2703 assert_eq!(after_delete.len(), 0);
2705
2706 let update = Update::<TestTable, PatchsetFormat, String, Vec<u8>>::from(table)
2707 .set(0, 1i64)
2708 .unwrap()
2709 .set(1, "z")
2710 .unwrap();
2711 let after_update = <_ as DiffOps<_, _, _>>::update(frozen, update);
2712 assert_eq!(after_update.len(), 1);
2713 }
2714
2715 #[test]
2716 fn test_diffset_changeset_iter_yields_inserts_and_indirect_flag() {
2717 let table = TestTable::new("t", 2, 0);
2718 let direct = Insert::from(table.clone()).set(0, 1i64).unwrap();
2719 let indirect = Insert::from(table.clone())
2720 .set(0, 2i64)
2721 .unwrap()
2722 .indirect(true);
2723 let frozen: DiffSet<ChangesetFormat, TestTable, String, Vec<u8>> = ChangesetBuilder::new()
2724 .insert(direct)
2725 .insert(indirect)
2726 .into();
2727
2728 let ops: Vec<_> = frozen.iter().collect();
2729 assert_eq!(ops.len(), 2);
2730 for op in &ops {
2731 assert_eq!(crate::DynTable::name(op.table()), "t");
2732 }
2733 assert!(matches!(
2734 ops[0],
2735 ChangesetOp::Insert {
2736 indirect: false,
2737 ..
2738 }
2739 ));
2740 assert!(matches!(ops[1], ChangesetOp::Insert { indirect: true, .. }));
2741 }
2742
2743 #[test]
2744 fn test_diffset_changeset_iter_yields_update_and_delete() {
2745 let table = TestTable::new("t", 2, 0);
2746 let starting = Insert::from(table.clone())
2747 .set(0, 1i64)
2748 .unwrap()
2749 .set(1, "a")
2750 .unwrap();
2751 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
2752 .set(0, 2i64, 2i64)
2753 .unwrap()
2754 .set(1, "before", "after")
2755 .unwrap();
2756 let delete = ChangeDelete::from(table.clone())
2757 .set(0, 3i64)
2758 .unwrap()
2759 .set(1, "gone")
2760 .unwrap();
2761 let frozen: DiffSet<ChangesetFormat, TestTable, String, Vec<u8>> = ChangesetBuilder::new()
2762 .insert(starting)
2763 .update(update)
2764 .delete(delete)
2765 .into();
2766
2767 let kinds: Vec<&'static str> = frozen
2768 .iter()
2769 .map(|op| match op {
2770 ChangesetOp::Insert { .. } => "insert",
2771 ChangesetOp::Update { .. } => "update",
2772 ChangesetOp::Delete { .. } => "delete",
2773 })
2774 .collect();
2775 assert_eq!(kinds, ["insert", "update", "delete"]);
2776 }
2777
2778 #[test]
2779 fn test_diffset_patchset_iter_exposes_pk_for_delete_and_update() {
2780 let table = TestTable::new("t", 2, 0);
2781 let starting = Insert::from(table.clone())
2782 .set(0, 1i64)
2783 .unwrap()
2784 .set(1, "a")
2785 .unwrap();
2786 let update = Update::<TestTable, PatchsetFormat, String, Vec<u8>>::from(table.clone())
2787 .set(0, 5i64)
2788 .unwrap()
2789 .set(1, "z")
2790 .unwrap();
2791 let delete: PatchDelete<TestTable, String, Vec<u8>> =
2792 PatchDelete::new(table.clone(), vec![Value::Integer(7)]);
2793
2794 let frozen: DiffSet<PatchsetFormat, TestTable, String, Vec<u8>> = PatchSet::new()
2795 .insert(starting)
2796 .update(update)
2797 .delete(delete)
2798 .into();
2799
2800 let mut saw_insert = false;
2801 let mut saw_update_pk: Option<i64> = None;
2802 let mut saw_delete_pk: Option<i64> = None;
2803 for op in frozen.iter() {
2804 match op {
2805 PatchsetOp::Insert { values, .. } => {
2806 saw_insert = true;
2807 assert!(matches!(values[0], Value::Integer(1)));
2808 }
2809 PatchsetOp::Update { pk, .. } => {
2810 if let Value::Integer(id) = pk[0] {
2811 saw_update_pk = Some(id);
2812 }
2813 }
2814 PatchsetOp::Delete { pk, .. } => {
2815 if let Value::Integer(id) = pk[0] {
2816 saw_delete_pk = Some(id);
2817 }
2818 }
2819 }
2820 }
2821 assert!(saw_insert);
2822 assert_eq!(saw_update_pk, Some(5));
2823 assert_eq!(saw_delete_pk, Some(7));
2824 }
2825
2826 #[test]
2827 fn test_diffset_tables_skips_empty() {
2828 let t1 = TestTable::new("t1", 2, 0);
2829 let t2 = TestTable::new("t2", 2, 0);
2830 let insert_t1 = Insert::from(t1.clone()).set(0, 1i64).unwrap();
2831 let insert_then_delete_t2 = Insert::from(t2.clone()).set(0, 9i64).unwrap();
2832 let delete_t2 = ChangeDelete::from(t2.clone()).set(0, 9i64).unwrap();
2833 let frozen: DiffSet<ChangesetFormat, TestTable, String, Vec<u8>> = ChangesetBuilder::new()
2834 .insert(insert_t1)
2835 .insert(insert_then_delete_t2)
2836 .delete(delete_t2)
2837 .into();
2838
2839 let names: Vec<&str> = frozen.tables().map(crate::DynTable::name).collect();
2840 assert_eq!(names, ["t1"]);
2841 }
2842}