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
584pub trait DiffOps<T: SchemaWithPK, S, B>: Sized {
594 type Format: Format<S, B>;
596
597 type DeleteArg;
602
603 fn insert(self, insert: Insert<T, S, B>) -> DiffSetBuilder<Self::Format, T, S, B>;
605
606 fn delete(self, delete: Self::DeleteArg) -> DiffSetBuilder<Self::Format, T, S, B>;
608
609 fn update(self, update: Update<T, Self::Format, S, B>)
611 -> DiffSetBuilder<Self::Format, T, S, B>;
612}
613
614impl<
617 T: SchemaWithPK,
618 S: Clone + Debug + Hash + Eq + AsRef<str>,
619 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
620> DiffOps<T, S, B> for DiffSetBuilder<ChangesetFormat, T, S, B>
621{
622 type Format = ChangesetFormat;
623 type DeleteArg = ChangeDelete<T, S, B>;
624
625 fn insert(mut self, insert: Insert<T, S, B>) -> Self {
626 let pk = insert.extract_pk();
627 let table = insert.as_ref().clone();
628 let indirect = insert.indirect;
629 self.add_operation(
630 &table,
631 pk,
632 Operation::Insert {
633 values: insert.into_values(),
634 indirect,
635 },
636 );
637 self
638 }
639
640 fn delete(mut self, delete: ChangeDelete<T, S, B>) -> Self {
641 let pk = delete.as_ref().extract_pk(&delete.values);
642 let table = delete.as_ref().clone();
643 let indirect = delete.indirect;
644 self.add_operation(
645 &table,
646 pk,
647 Operation::Delete {
648 data: delete.into_values(),
649 indirect,
650 },
651 );
652 self
653 }
654
655 fn update(mut self, update: Update<T, ChangesetFormat, S, B>) -> Self {
656 let old_values: Vec<_> = update
657 .values()
658 .iter()
659 .map(|(old, _): &(_, _)| old.clone().unwrap_or(Value::Null))
660 .collect();
661 let pk = update.as_ref().extract_pk(&old_values);
662 let table = update.as_ref().clone();
663 let indirect = update.indirect;
664 let values: Vec<(MaybeValue<S, B>, MaybeValue<S, B>)> = update.into();
665 self.add_operation(&table, pk, Operation::Update { values, indirect });
666 self
667 }
668}
669
670impl<T: SchemaWithPK, S: Clone + Hash + Eq + AsRef<str>, B: Clone + Hash + Eq + AsRef<[u8]>>
673 DiffOps<T, S, B> for DiffSetBuilder<PatchsetFormat, T, S, B>
674{
675 type Format = PatchsetFormat;
676 type DeleteArg = PatchDelete<T, S, B>;
677
678 fn insert(mut self, insert: Insert<T, S, B>) -> Self {
679 let pk = insert.extract_pk();
680 let table = insert.as_ref().clone();
681 let indirect = insert.indirect;
682 self.add_operation(
683 &table,
684 pk,
685 Operation::Insert {
686 values: insert.into_values(),
687 indirect,
688 },
689 );
690 self
691 }
692
693 fn delete(mut self, delete: PatchDelete<T, S, B>) -> Self {
708 let indirect = delete.indirect;
709 self.add_operation(
710 &delete.table,
711 delete.pk,
712 Operation::Delete { data: (), indirect },
713 );
714 self
715 }
716
717 fn update(mut self, update: Update<T, PatchsetFormat, S, B>) -> Self {
736 let pk = update.extract_pk();
737 let table = update.as_ref().clone();
738 let indirect = update.indirect;
739 let values: Vec<((), MaybeValue<S, B>)> = update.into();
740 self.add_operation(&table, pk, Operation::Update { values, indirect });
741 self
742 }
743}
744
745impl<
748 T: SchemaWithPK,
749 S: Clone + Debug + Hash + Eq + AsRef<str>,
750 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
751> DiffOps<T, S, B> for DiffSet<ChangesetFormat, T, S, B>
752{
753 type Format = ChangesetFormat;
754 type DeleteArg = ChangeDelete<T, S, B>;
755
756 fn insert(self, insert: Insert<T, S, B>) -> DiffSetBuilder<ChangesetFormat, T, S, B> {
757 let builder: DiffSetBuilder<ChangesetFormat, T, S, B> = self.into();
758 builder.insert(insert)
759 }
760
761 fn delete(self, delete: ChangeDelete<T, S, B>) -> DiffSetBuilder<ChangesetFormat, T, S, B> {
762 let builder: DiffSetBuilder<ChangesetFormat, T, S, B> = self.into();
763 builder.delete(delete)
764 }
765
766 fn update(
767 self,
768 update: Update<T, ChangesetFormat, S, B>,
769 ) -> DiffSetBuilder<ChangesetFormat, T, S, B> {
770 let builder: DiffSetBuilder<ChangesetFormat, T, S, B> = self.into();
771 builder.update(update)
772 }
773}
774
775impl<T: SchemaWithPK, S: Clone + Hash + Eq + AsRef<str>, B: Clone + Hash + Eq + AsRef<[u8]>>
778 DiffOps<T, S, B> for DiffSet<PatchsetFormat, T, S, B>
779{
780 type Format = PatchsetFormat;
781 type DeleteArg = PatchDelete<T, S, B>;
782
783 fn insert(self, insert: Insert<T, S, B>) -> DiffSetBuilder<PatchsetFormat, T, S, B> {
784 let builder: DiffSetBuilder<PatchsetFormat, T, S, B> = self.into();
785 builder.insert(insert)
786 }
787
788 fn delete(self, delete: PatchDelete<T, S, B>) -> DiffSetBuilder<PatchsetFormat, T, S, B> {
789 let builder: DiffSetBuilder<PatchsetFormat, T, S, B> = self.into();
790 builder.delete(delete)
791 }
792
793 fn update(
794 self,
795 update: Update<T, PatchsetFormat, S, B>,
796 ) -> DiffSetBuilder<PatchsetFormat, T, S, B> {
797 let builder: DiffSetBuilder<PatchsetFormat, T, S, B> = self.into();
798 builder.update(update)
799 }
800}
801
802impl<T: crate::schema::NamedColumns, S: Clone + Hash + Eq + AsRef<str> + for<'a> From<&'a str>>
803 DiffSetBuilder<PatchsetFormat, T, S, Vec<u8>>
804{
805 pub fn digest_sql<'input>(
815 &mut self,
816 input: &'input str,
817 ) -> Result<&mut Self, crate::builders::sql::ParseError<'input>> {
818 let mut parser = crate::builders::sql::Parser::new(input, self);
819 parser.digest_all()?;
820 Ok(self)
821 }
822}
823
824impl<
829 T: SchemaWithPK,
830 S: Clone + Debug + Hash + Eq + AsRef<str>,
831 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
832> DiffSetBuilder<ChangesetFormat, T, S, B>
833{
834 #[must_use]
843 pub fn build(&self) -> Vec<u8> {
844 let mut out = Vec::new();
845
846 for (table, rows) in &self.tables {
847 if rows.is_empty() {
848 continue;
849 }
850
851 write_table_header(&mut out, markers::CHANGESET, table);
852
853 for idx in session_row_order(rows) {
854 let (_pk, op) = rows.get_index(idx).unwrap();
855 encode_changeset_op(&mut out, op);
856 }
857 }
858
859 out
860 }
861}
862
863impl<T: SchemaWithPK, S: Clone + Hash + Eq + AsRef<str>, B: Clone + Hash + Eq + AsRef<[u8]>>
864 DiffSetBuilder<PatchsetFormat, T, S, B>
865{
866 #[must_use]
875 pub fn build(&self) -> Vec<u8> {
876 let mut out = Vec::new();
877
878 for (table, rows) in &self.tables {
879 if rows.is_empty() {
880 continue;
881 }
882
883 write_table_header(&mut out, markers::PATCHSET, table);
884
885 let (pk_flags, pk_col_to_pk_pos) = patchset_pk_mapping(table);
886
887 for idx in session_row_order(rows) {
888 let (pk, op) = rows.get_index(idx).unwrap();
889 encode_patchset_op(&mut out, op, pk, &pk_flags, &pk_col_to_pk_pos);
890 }
891 }
892
893 out
894 }
895}
896
897use crate::builders::operation::Reverse;
902
903impl<
904 T: SchemaWithPK,
905 S: Clone + Debug + Hash + Eq + AsRef<str>,
906 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
907> Reverse for DiffSetBuilder<ChangesetFormat, T, S, B>
908{
909 type Output = DiffSetBuilder<ChangesetFormat, T, S, B>;
910
911 fn reverse(self) -> Self::Output {
912 let mut reversed: DiffSetBuilder<ChangesetFormat, T, S, B> = DiffSetBuilder::new();
913
914 for (table, rows) in self.tables {
915 for (pk, op) in rows {
916 let rev_op = op.reverse();
917
918 reversed.add_operation(&table, pk, rev_op);
919 }
920 }
921
922 reversed
923 }
924}
925
926impl<
932 F: Format<S, B>,
933 T: SchemaWithPK,
934 S: Clone + Hash + Eq + AsRef<str>,
935 B: Clone + Hash + Eq + AsRef<[u8]>,
936> BitOrAssign for DiffSetBuilder<F, T, S, B>
937where
938 Operation<F, S, B>: core::ops::Add<Output = Option<Operation<F, S, B>>>,
939{
940 fn bitor_assign(&mut self, rhs: Self) {
942 for (table, rows) in rhs.tables {
943 for (pk, op) in rows {
944 self.add_operation(&table, pk, op);
945 }
946 }
947 }
948}
949
950impl<
951 F: Format<S, B>,
952 T: SchemaWithPK,
953 S: Clone + Hash + Eq + AsRef<str>,
954 B: Clone + Hash + Eq + AsRef<[u8]>,
955> BitOr for DiffSetBuilder<F, T, S, B>
956where
957 Operation<F, S, B>: core::ops::Add<Output = Option<Operation<F, S, B>>>,
958{
959 type Output = Self;
960
961 #[inline]
963 fn bitor(mut self, rhs: Self) -> Self::Output {
964 self |= rhs;
965 self
966 }
967}
968
969#[derive(Debug, Clone)]
987pub struct DiffSet<F: Format<S, B>, T: SchemaWithPK, S, B> {
988 pub(crate) tables: TableVec<F, T, S, B>,
990}
991
992impl<F: Format<S, B>, T: SchemaWithPK, S, B> PartialEq for DiffSet<F, T, S, B>
995where
996 S: PartialEq + Eq + Hash + AsRef<str>,
997 B: PartialEq + Eq + Hash + AsRef<[u8]>,
998 F::Old: PartialEq,
999 F::DeleteData: PartialEq,
1000{
1001 fn eq(&self, other: &Self) -> bool {
1002 self.tables
1003 .iter()
1004 .filter(|(_, ops)| !ops.is_empty())
1005 .eq(other.tables.iter().filter(|(_, ops)| !ops.is_empty()))
1006 }
1007}
1008
1009impl<F: Format<S, B>, T: SchemaWithPK, S, B> Eq for DiffSet<F, T, S, B>
1010where
1011 S: Eq + Hash + AsRef<str>,
1012 B: Eq + Hash + AsRef<[u8]>,
1013 F::Old: Eq,
1014 F::DeleteData: Eq,
1015{
1016}
1017
1018impl<F: Format<S, B>, T: SchemaWithPK, S: AsRef<str> + Hash + Eq, B: AsRef<[u8]> + Hash + Eq>
1019 Default for DiffSet<F, T, S, B>
1020{
1021 fn default() -> Self {
1022 Self { tables: Vec::new() }
1023 }
1024}
1025
1026impl<F: Format<S, B>, T: SchemaWithPK, S: AsRef<str> + Hash + Eq, B: AsRef<[u8]> + Hash + Eq>
1027 DiffSet<F, T, S, B>
1028{
1029 #[inline]
1031 #[must_use]
1032 pub fn is_empty(&self) -> bool {
1033 self.tables.iter().all(|(_, rows)| rows.is_empty())
1034 }
1035
1036 #[inline]
1038 #[must_use]
1039 pub fn len(&self) -> usize {
1040 self.tables.iter().map(|(_, rows)| rows.len()).sum()
1041 }
1042}
1043
1044impl<F: Format<S, B>, T: SchemaWithPK, S: AsRef<str> + Hash + Eq, B: AsRef<[u8]> + Hash + Eq>
1047 DiffSet<F, T, S, B>
1048{
1049 pub fn tables(&self) -> impl Iterator<Item = &T> {
1052 self.tables
1053 .iter()
1054 .filter(|(_, rows)| !rows.is_empty())
1055 .map(|(t, _)| t)
1056 }
1057}
1058
1059impl<T: SchemaWithPK, S: Clone + Debug + AsRef<str>, B: Clone + Debug + AsRef<[u8]>>
1060 DiffSet<ChangesetFormat, T, S, B>
1061{
1062 pub fn iter(&self) -> impl Iterator<Item = ChangesetOp<'_, T, S, B>> {
1068 self.tables.iter().flat_map(|(table, rows)| {
1069 rows.iter().map(move |(_pk, op)| match op {
1070 Operation::Insert { values, indirect } => ChangesetOp::Insert {
1071 table,
1072 values: values.as_slice(),
1073 indirect: *indirect,
1074 },
1075 Operation::Update { values, indirect } => ChangesetOp::Update {
1076 table,
1077 values: values.as_slice(),
1078 indirect: *indirect,
1079 },
1080 Operation::Delete { data, indirect } => ChangesetOp::Delete {
1081 table,
1082 old_values: data.as_slice(),
1083 indirect: *indirect,
1084 },
1085 })
1086 })
1087 }
1088}
1089
1090impl<T: SchemaWithPK, S: Clone + AsRef<str>, B: Clone + AsRef<[u8]>>
1091 DiffSet<PatchsetFormat, T, S, B>
1092{
1093 pub fn iter(&self) -> impl Iterator<Item = PatchsetOp<'_, T, S, B>> {
1100 self.tables.iter().flat_map(|(table, rows)| {
1101 rows.iter().map(move |(pk, op)| match op {
1102 Operation::Insert { values, indirect } => PatchsetOp::Insert {
1103 table,
1104 values: values.as_slice(),
1105 indirect: *indirect,
1106 },
1107 Operation::Update { values, indirect } => PatchsetOp::Update {
1108 table,
1109 pk: pk.as_slice(),
1110 entries: values.as_slice(),
1111 indirect: *indirect,
1112 },
1113 Operation::Delete { indirect, .. } => PatchsetOp::Delete {
1114 table,
1115 pk: pk.as_slice(),
1116 indirect: *indirect,
1117 },
1118 })
1119 })
1120 }
1121}
1122
1123impl<
1126 T: SchemaWithPK,
1127 S: Clone + Debug + Hash + Eq + AsRef<str>,
1128 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
1129> DiffSet<ChangesetFormat, T, S, B>
1130{
1131 #[must_use]
1135 pub fn build(&self) -> Vec<u8> {
1136 let mut out = Vec::new();
1137
1138 for (table, rows) in &self.tables {
1139 if rows.is_empty() {
1140 continue;
1141 }
1142
1143 write_table_header(&mut out, markers::CHANGESET, table);
1144
1145 for (_pk, op) in rows {
1146 encode_changeset_op(&mut out, op);
1147 }
1148 }
1149
1150 out
1151 }
1152}
1153
1154impl<T: SchemaWithPK, S: Clone + Hash + Eq + AsRef<str>, B: Clone + Hash + Eq + AsRef<[u8]>>
1157 DiffSet<PatchsetFormat, T, S, B>
1158{
1159 #[must_use]
1163 pub fn build(&self) -> Vec<u8> {
1164 let mut out = Vec::new();
1165
1166 for (table, rows) in &self.tables {
1167 if rows.is_empty() {
1168 continue;
1169 }
1170
1171 write_table_header(&mut out, markers::PATCHSET, table);
1172
1173 let (pk_flags, pk_col_to_pk_pos) = patchset_pk_mapping(table);
1174
1175 for (pk, op) in rows {
1176 encode_patchset_op(&mut out, op, pk, &pk_flags, &pk_col_to_pk_pos);
1177 }
1178 }
1179
1180 out
1181 }
1182}
1183
1184impl<
1187 T: SchemaWithPK,
1188 S: Clone + Debug + Hash + Eq + AsRef<str>,
1189 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
1190> From<&DiffSet<ChangesetFormat, T, S, B>> for Vec<u8>
1191{
1192 #[inline]
1193 fn from(diffset: &DiffSet<ChangesetFormat, T, S, B>) -> Self {
1194 diffset.build()
1195 }
1196}
1197
1198impl<
1199 T: SchemaWithPK,
1200 S: Clone + Debug + Hash + Eq + AsRef<str>,
1201 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
1202> From<DiffSet<ChangesetFormat, T, S, B>> for Vec<u8>
1203{
1204 #[inline]
1205 fn from(diffset: DiffSet<ChangesetFormat, T, S, B>) -> Self {
1206 diffset.build()
1207 }
1208}
1209
1210impl<T: SchemaWithPK, S: AsRef<str> + Clone + Hash + Eq, B: AsRef<[u8]> + Clone + Hash + Eq>
1211 From<&DiffSet<PatchsetFormat, T, S, B>> for Vec<u8>
1212{
1213 #[inline]
1214 fn from(diffset: &DiffSet<PatchsetFormat, T, S, B>) -> Self {
1215 diffset.build()
1216 }
1217}
1218
1219impl<T: SchemaWithPK, S: AsRef<str> + Clone + Hash + Eq, B: AsRef<[u8]> + Clone + Hash + Eq>
1220 From<DiffSet<PatchsetFormat, T, S, B>> for Vec<u8>
1221{
1222 #[inline]
1223 fn from(diffset: DiffSet<PatchsetFormat, T, S, B>) -> Self {
1224 diffset.build()
1225 }
1226}
1227
1228impl<
1231 T: SchemaWithPK,
1232 S: Clone + Debug + Hash + Eq + AsRef<str>,
1233 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
1234> Reverse for DiffSet<ChangesetFormat, T, S, B>
1235{
1236 type Output = DiffSet<ChangesetFormat, T, S, B>;
1237
1238 fn reverse(self) -> Self::Output {
1239 DiffSet {
1240 tables: self
1241 .tables
1242 .into_iter()
1243 .map(|(table, rows)| {
1244 let rev_rows = rows
1245 .into_iter()
1246 .map(|(pk, op)| (pk, op.reverse()))
1247 .collect();
1248 (table, rev_rows)
1249 })
1250 .collect(),
1251 }
1252 }
1253}
1254
1255impl<F: Format<S, B>, T: SchemaWithPK, S: Hash + Eq + AsRef<str>, B: Hash + Eq + AsRef<[u8]>>
1258 From<DiffSetBuilder<F, T, S, B>> for DiffSet<F, T, S, B>
1259{
1260 fn from(builder: DiffSetBuilder<F, T, S, B>) -> Self {
1261 Self {
1262 tables: builder
1263 .tables
1264 .into_iter()
1265 .map(|(table, rows)| {
1266 let ordered_rows: RowVec<F, S, B> = rows.into_iter().collect();
1267 (table, ordered_rows)
1268 })
1269 .collect(),
1270 }
1271 }
1272}
1273
1274impl<F: Format<S, B>, T: SchemaWithPK, S: Hash + Eq + AsRef<str>, B: Hash + Eq + AsRef<[u8]>>
1275 From<DiffSet<F, T, S, B>> for DiffSetBuilder<F, T, S, B>
1276{
1277 fn from(diffset: DiffSet<F, T, S, B>) -> Self {
1278 let mut builder = Self::new();
1279 for (table, rows) in diffset.tables {
1280 let map: IndexMap<Vec<Value<S, B>>, Operation<F, S, B>> = rows.into_iter().collect();
1281 builder.tables.insert(table, map);
1282 }
1283 builder
1284 }
1285}
1286
1287#[cfg(test)]
1288mod tests {
1289 use super::*;
1290 use crate::builders::operation::Indirect;
1291 use crate::encoding::Value;
1292 use alloc::{string::String, vec};
1293
1294 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
1296 struct TestTable {
1297 name: String,
1298 num_columns: usize,
1299 pk_column: usize,
1300 }
1301
1302 impl TestTable {
1303 fn new(name: &str, num_columns: usize, pk_column: usize) -> Self {
1304 Self {
1305 name: name.into(),
1306 num_columns,
1307 pk_column,
1308 }
1309 }
1310 }
1311
1312 impl crate::DynTable for TestTable {
1313 fn name(&self) -> &str {
1314 &self.name
1315 }
1316
1317 fn number_of_columns(&self) -> usize {
1318 self.num_columns
1319 }
1320
1321 fn write_pk_flags(&self, buf: &mut [u8]) {
1322 assert_eq!(buf.len(), self.num_columns);
1323 buf.fill(0);
1324 buf[self.pk_column] = 1;
1325 }
1326 }
1327
1328 impl crate::SchemaWithPK for TestTable {
1329 fn number_of_primary_keys(&self) -> usize {
1330 1
1331 }
1332
1333 fn primary_key_index(&self, col_idx: usize) -> Option<usize> {
1334 if col_idx == self.pk_column {
1335 Some(0)
1336 } else {
1337 None
1338 }
1339 }
1340
1341 fn extract_pk<S: Clone, B: Clone>(
1342 &self,
1343 values: &impl crate::IndexableValues<Text = S, Binary = B>,
1344 ) -> alloc::vec::Vec<Value<S, B>> {
1345 alloc::vec![
1346 values
1347 .get(self.pk_column)
1348 .expect("primary key column index out of bounds, values shorter than schema")
1349 ]
1350 }
1351 }
1352
1353 type ChangesetBuilder = DiffSetBuilder<ChangesetFormat, TestTable, String, Vec<u8>>;
1355
1356 #[test]
1357 fn test_insert_single_row() {
1358 let table = TestTable::new("users", 2, 0);
1359 let insert = Insert::from(table.clone())
1360 .set(0, 1i64)
1361 .unwrap()
1362 .set(1, "alice")
1363 .unwrap();
1364
1365 let builder = ChangesetBuilder::new().insert(insert);
1366
1367 assert_eq!(builder.len(), 1);
1368 assert!(!builder.is_empty());
1369 }
1370
1371 #[test]
1372 fn test_insert_then_delete_cancels_out() {
1373 let table = TestTable::new("users", 2, 0);
1374
1375 let insert = Insert::from(table.clone())
1376 .set(0, 1i64)
1377 .unwrap()
1378 .set(1, "alice")
1379 .unwrap();
1380
1381 let delete = ChangeDelete::from(table.clone())
1382 .set(0, 1i64)
1383 .unwrap()
1384 .set(1, "alice")
1385 .unwrap();
1386
1387 let builder = ChangesetBuilder::new().insert(insert).delete(delete);
1388
1389 assert_eq!(builder.len(), 0);
1390 assert!(builder.is_empty());
1391 }
1392
1393 #[test]
1394 fn test_insert_then_update_becomes_insert() {
1395 let table = TestTable::new("users", 2, 0);
1396
1397 let insert = Insert::from(table.clone())
1398 .set(0, 1i64)
1399 .unwrap()
1400 .set(1, "alice")
1401 .unwrap();
1402
1403 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
1404 .set(0, 1i64, 1i64) .unwrap()
1406 .set(1, "alice", "bob")
1407 .unwrap();
1408
1409 let builder = ChangesetBuilder::new().insert(insert).update(update);
1410
1411 assert_eq!(builder.len(), 1);
1412 }
1414
1415 #[test]
1416 fn test_delete_then_insert_same_values_cancels_out() {
1417 let table = TestTable::new("users", 2, 0);
1418
1419 let delete = ChangeDelete::from(table.clone())
1420 .set(0, 1i64)
1421 .unwrap()
1422 .set(1, "alice")
1423 .unwrap();
1424
1425 let insert = Insert::from(table.clone())
1426 .set(0, 1i64)
1427 .unwrap()
1428 .set(1, "alice")
1429 .unwrap();
1430
1431 let builder = ChangesetBuilder::new().delete(delete).insert(insert);
1432
1433 assert_eq!(builder.len(), 0);
1434 assert!(builder.is_empty());
1435 }
1436
1437 #[test]
1438 fn test_delete_then_insert_different_values_becomes_update() {
1439 let table = TestTable::new("users", 2, 0);
1440
1441 let delete = ChangeDelete::from(table.clone())
1442 .set(0, 1i64)
1443 .unwrap()
1444 .set(1, "alice")
1445 .unwrap();
1446
1447 let insert = Insert::from(table.clone())
1448 .set(0, 1i64)
1449 .unwrap()
1450 .set(1, "bob")
1451 .unwrap();
1452
1453 let builder = ChangesetBuilder::new().delete(delete).insert(insert);
1454
1455 assert_eq!(builder.len(), 1);
1456 }
1458
1459 #[test]
1460 fn test_multiple_rows() {
1461 let table = TestTable::new("users", 2, 0);
1462
1463 let insert1 = Insert::from(table.clone())
1464 .set(0, 1i64)
1465 .unwrap()
1466 .set(1, "alice")
1467 .unwrap();
1468
1469 let insert2 = Insert::from(table.clone())
1470 .set(0, 2i64)
1471 .unwrap()
1472 .set(1, "bob")
1473 .unwrap();
1474
1475 let builder = ChangesetBuilder::new().insert(insert1).insert(insert2);
1476
1477 assert_eq!(builder.len(), 2);
1478 }
1479
1480 #[test]
1481 fn test_update_then_update_consolidates() {
1482 let table = TestTable::new("users", 2, 0);
1483
1484 let update1 = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
1485 .set(0, 1i64, 1i64)
1486 .unwrap()
1487 .set(1, "alice", "bob")
1488 .unwrap();
1489
1490 let update2 = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
1491 .set(0, 1i64, 1i64)
1492 .unwrap()
1493 .set(1, "bob", "charlie")
1494 .unwrap();
1495
1496 let builder = ChangesetBuilder::new().update(update1).update(update2);
1497
1498 assert_eq!(builder.len(), 1);
1499 }
1501
1502 #[test]
1507 fn test_reverse_operation_insert_becomes_delete() {
1508 let op: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Insert {
1509 values: vec![Value::Integer(1), Value::Text("alice".into())],
1510 indirect: false,
1511 };
1512 let reversed = op.reverse();
1513 let Operation::Delete { data, .. } = reversed else {
1514 panic!("Expected Delete operation");
1515 };
1516 assert_eq!(
1517 data,
1518 vec![
1519 Value::<String, Vec<u8>>::Integer(1),
1520 Value::Text("alice".into())
1521 ]
1522 );
1523 }
1524
1525 #[test]
1526 fn test_reverse_operation_delete_becomes_insert() {
1527 let op: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Delete {
1528 data: vec![Value::Integer(1), Value::Text("alice".into())],
1529 indirect: false,
1530 };
1531 let reversed = op.reverse();
1532 let Operation::Insert { values, .. } = reversed else {
1533 panic!("Expected Insert operation");
1534 };
1535 assert_eq!(
1536 values,
1537 vec![
1538 Value::<String, Vec<u8>>::Integer(1),
1539 Value::Text("alice".into())
1540 ]
1541 );
1542 }
1543
1544 #[test]
1545 fn test_reverse_operation_update_swaps_old_new() {
1546 let op: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Update {
1547 values: vec![
1548 (Some(Value::Integer(1)), Some(Value::Integer(1))),
1549 (
1550 Some(Value::Text("alice".into())),
1551 Some(Value::Text("bob".into())),
1552 ),
1553 ],
1554 indirect: false,
1555 };
1556 let reversed = op.reverse();
1557 let Operation::Update { values, .. } = reversed else {
1558 panic!("Expected Update operation");
1559 };
1560 assert_eq!(
1561 values[0],
1562 (Some(Value::Integer(1)), Some(Value::Integer(1)))
1563 );
1564 assert_eq!(
1565 values[1],
1566 (
1567 Some(Value::Text("bob".into())),
1568 Some(Value::Text("alice".into()))
1569 )
1570 );
1571 }
1572
1573 #[test]
1574 fn test_reverse_builder_insert_becomes_delete() {
1575 let table = TestTable::new("users", 2, 0);
1576 let insert = Insert::from(table.clone())
1577 .set(0, 1i64)
1578 .unwrap()
1579 .set(1, "alice")
1580 .unwrap();
1581
1582 let builder = ChangesetBuilder::new().insert(insert);
1583 let reversed = builder.reverse();
1584
1585 assert_eq!(reversed.len(), 1);
1586 let rows = reversed.tables.get(&table).unwrap();
1588 assert!(matches!(
1589 rows.values().next().unwrap(),
1590 Operation::Delete { .. }
1591 ));
1592 }
1593
1594 #[test]
1595 fn test_reverse_builder_delete_becomes_insert() {
1596 let table = TestTable::new("users", 2, 0);
1597 let delete = ChangeDelete::from(table.clone())
1598 .set(0, 1i64)
1599 .unwrap()
1600 .set(1, "alice")
1601 .unwrap();
1602
1603 let builder = ChangesetBuilder::new().delete(delete);
1604 let reversed = builder.reverse();
1605
1606 assert_eq!(reversed.len(), 1);
1607 let rows = reversed.tables.get(&table).unwrap();
1609 assert!(matches!(
1610 rows.values().next().unwrap(),
1611 Operation::Insert { .. }
1612 ));
1613 }
1614
1615 #[test]
1616 fn test_reverse_builder_update_swaps() {
1617 let table = TestTable::new("users", 2, 0);
1618 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
1619 .set(0, 1i64, 1i64)
1620 .unwrap()
1621 .set(1, "alice", "bob")
1622 .unwrap();
1623
1624 let builder = ChangesetBuilder::new().update(update);
1625 let reversed = builder.reverse();
1626
1627 assert_eq!(reversed.len(), 1);
1628 let rows = reversed.tables.get(&table).unwrap();
1630 let Operation::Update { values, .. } = rows.values().next().unwrap() else {
1631 panic!("Expected Update operation");
1632 };
1633 assert_eq!(
1634 values[1],
1635 (
1636 Some(Value::Text("bob".into())),
1637 Some(Value::Text("alice".into()))
1638 )
1639 );
1640 }
1641
1642 #[test]
1643 fn test_reverse_is_involutory() {
1644 let table = TestTable::new("users", 2, 0);
1646 let insert = Insert::from(table.clone())
1647 .set(0, 1i64)
1648 .unwrap()
1649 .set(1, "alice")
1650 .unwrap();
1651
1652 let original_values = insert.into_values();
1653 let insert2 = Insert::from(table.clone())
1654 .set(0, 1i64)
1655 .unwrap()
1656 .set(1, "alice")
1657 .unwrap();
1658 let builder = ChangesetBuilder::new().insert(insert2);
1659 let double_reversed = builder.reverse().reverse();
1660
1661 assert_eq!(double_reversed.len(), 1);
1662 let rows = double_reversed.tables.get(&table).unwrap();
1663 let Operation::Insert { values, .. } = rows.values().next().unwrap() else {
1664 panic!("Expected Insert operation");
1665 };
1666 assert_eq!(values, &original_values);
1667 }
1668
1669 #[test]
1674 fn test_build_empty_builder() {
1675 let builder = ChangesetBuilder::new();
1676 let bytes = builder.build();
1677 assert!(bytes.is_empty());
1678 }
1679
1680 #[test]
1681 fn test_build_insert_format() {
1682 let table = TestTable::new("t", 2, 0);
1683 let insert = Insert::from(table.clone())
1684 .set(0, 1i64)
1685 .unwrap()
1686 .set(1, "a")
1687 .unwrap();
1688
1689 let builder = ChangesetBuilder::new().insert(insert);
1690 let bytes = builder.build();
1691
1692 assert!(!bytes.is_empty());
1696
1697 assert_eq!(bytes[0], b'T');
1699 assert_eq!(bytes[1], 2);
1701 assert_eq!(bytes[2], 1);
1703 assert_eq!(bytes[3], 0);
1704 assert_eq!(bytes[4], b't');
1706 assert_eq!(bytes[5], 0);
1707 assert_eq!(bytes[6], 0x12);
1709 assert_eq!(bytes[7], 0);
1711 }
1712
1713 #[test]
1714 fn test_build_delete_format() {
1715 let table = TestTable::new("t", 2, 0);
1716 let delete = ChangeDelete::from(table.clone())
1717 .set(0, 1i64)
1718 .unwrap()
1719 .set(1, "a")
1720 .unwrap();
1721
1722 let builder = ChangesetBuilder::new().delete(delete);
1723 let bytes = builder.build();
1724
1725 assert!(!bytes.is_empty());
1726 assert_eq!(bytes[0], b'T');
1727 assert_eq!(bytes[6], 0x09);
1729 }
1730
1731 #[test]
1732 fn test_build_update_format() {
1733 let table = TestTable::new("t", 2, 0);
1734 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
1735 .set(0, 1i64, 1i64)
1736 .unwrap()
1737 .set(1, "a", "b")
1738 .unwrap();
1739
1740 let builder = ChangesetBuilder::new().update(update);
1741 let bytes = builder.build();
1742
1743 assert!(!bytes.is_empty());
1744 assert_eq!(bytes[0], b'T');
1745 assert_eq!(bytes[6], 0x17);
1747 }
1748
1749 #[test]
1750 fn test_build_multiple_operations() {
1751 let table = TestTable::new("t", 2, 0);
1752
1753 let insert = Insert::from(table.clone())
1754 .set(0, 1i64)
1755 .unwrap()
1756 .set(1, "a")
1757 .unwrap();
1758
1759 let insert2 = Insert::from(table.clone())
1760 .set(0, 2i64)
1761 .unwrap()
1762 .set(1, "b")
1763 .unwrap();
1764
1765 let builder = ChangesetBuilder::new().insert(insert).insert(insert2);
1766 let bytes = builder.build();
1767
1768 assert!(!bytes.is_empty());
1769 assert_eq!(bytes[0], b'T');
1771 }
1772
1773 #[test]
1774 fn test_build_cancelled_operations_produce_empty() {
1775 let table = TestTable::new("t", 2, 0);
1776
1777 let insert = Insert::from(table.clone())
1778 .set(0, 1i64)
1779 .unwrap()
1780 .set(1, "a")
1781 .unwrap();
1782
1783 let delete = ChangeDelete::from(table.clone())
1784 .set(0, 1i64)
1785 .unwrap()
1786 .set(1, "a")
1787 .unwrap();
1788
1789 let builder = ChangesetBuilder::new().insert(insert).delete(delete);
1790 let bytes = builder.build();
1791
1792 assert!(bytes.is_empty());
1794 }
1795
1796 #[test]
1801 fn test_bitor_changeset_disjoint_rows() {
1802 let table = TestTable::new("users", 2, 0);
1803
1804 let insert1 = Insert::from(table.clone())
1805 .set(0, 1i64)
1806 .unwrap()
1807 .set(1, "alice")
1808 .unwrap();
1809
1810 let insert2 = Insert::from(table.clone())
1811 .set(0, 2i64)
1812 .unwrap()
1813 .set(1, "bob")
1814 .unwrap();
1815
1816 let cs1 = ChangesetBuilder::new().insert(insert1);
1817 let cs2 = ChangesetBuilder::new().insert(insert2);
1818
1819 let merged = cs1 | cs2;
1820 assert_eq!(merged.len(), 2);
1821 }
1822
1823 #[test]
1824 fn test_bitor_changeset_consolidates_same_row() {
1825 let table = TestTable::new("users", 2, 0);
1826
1827 let insert = Insert::from(table.clone())
1829 .set(0, 1i64)
1830 .unwrap()
1831 .set(1, "alice")
1832 .unwrap();
1833
1834 let delete = ChangeDelete::from(table.clone())
1836 .set(0, 1i64)
1837 .unwrap()
1838 .set(1, "alice")
1839 .unwrap();
1840
1841 let cs1 = ChangesetBuilder::new().insert(insert);
1842 let cs2 = ChangesetBuilder::new().delete(delete);
1843
1844 let merged = cs1 | cs2;
1846 assert_eq!(merged.len(), 0);
1847 assert!(merged.is_empty());
1848 }
1849
1850 #[test]
1851 fn test_bitor_assign_changeset() {
1852 let table = TestTable::new("users", 2, 0);
1853
1854 let insert1 = Insert::from(table.clone())
1855 .set(0, 1i64)
1856 .unwrap()
1857 .set(1, "alice")
1858 .unwrap();
1859
1860 let insert2 = Insert::from(table.clone())
1861 .set(0, 2i64)
1862 .unwrap()
1863 .set(1, "bob")
1864 .unwrap();
1865
1866 let mut cs = ChangesetBuilder::new().insert(insert1);
1867 cs |= ChangesetBuilder::new().insert(insert2);
1868
1869 assert_eq!(cs.len(), 2);
1870 }
1871
1872 #[test]
1873 fn test_bitor_patchset_disjoint_rows() {
1874 type PatchsetBuilder = DiffSetBuilder<PatchsetFormat, TestTable, String, Vec<u8>>;
1875
1876 let table = TestTable::new("users", 2, 0);
1877
1878 let insert1 = Insert::from(table.clone())
1879 .set(0, 1i64)
1880 .unwrap()
1881 .set(1, "alice")
1882 .unwrap();
1883
1884 let insert2 = Insert::from(table.clone())
1885 .set(0, 2i64)
1886 .unwrap()
1887 .set(1, "bob")
1888 .unwrap();
1889
1890 let ps1 = PatchsetBuilder::new().insert(insert1);
1891 let ps2 = PatchsetBuilder::new().insert(insert2);
1892
1893 let merged = ps1 | ps2;
1894 assert_eq!(merged.len(), 2);
1895 }
1896
1897 #[test]
1898 fn test_bitor_patchset_consolidates_same_row() {
1899 type PatchsetBuilder = DiffSetBuilder<PatchsetFormat, TestTable, String, Vec<u8>>;
1900
1901 let table = TestTable::new("users", 2, 0);
1902
1903 let insert = Insert::from(table.clone())
1905 .set(0, 1i64)
1906 .unwrap()
1907 .set(1, "alice")
1908 .unwrap();
1909
1910 let delete = PatchDelete::new(table.clone(), vec![Value::Integer(1)]);
1912
1913 let ps1 = PatchsetBuilder::new().insert(insert);
1914 let ps2 = PatchsetBuilder::new().delete(delete);
1915
1916 let merged = ps1 | ps2;
1918 assert_eq!(merged.len(), 0);
1919 assert!(merged.is_empty());
1920 }
1921
1922 #[test]
1923 fn test_bitor_multiple_tables() {
1924 let table1 = TestTable::new("users", 2, 0);
1925 let table2 = TestTable::new("posts", 2, 0);
1926
1927 let insert1 = Insert::from(table1.clone())
1928 .set(0, 1i64)
1929 .unwrap()
1930 .set(1, "alice")
1931 .unwrap();
1932
1933 let insert2 = Insert::from(table2.clone())
1934 .set(0, 100i64)
1935 .unwrap()
1936 .set(1, "first post")
1937 .unwrap();
1938
1939 let cs1 = ChangesetBuilder::new().insert(insert1);
1940 let cs2 = ChangesetBuilder::new().insert(insert2);
1941
1942 let merged = cs1 | cs2;
1943 assert_eq!(merged.len(), 2);
1944 }
1945
1946 #[test]
1947 fn test_bitor_insert_then_update_consolidates() {
1948 let table = TestTable::new("users", 2, 0);
1949
1950 let insert = Insert::from(table.clone())
1952 .set(0, 1i64)
1953 .unwrap()
1954 .set(1, "alice")
1955 .unwrap();
1956
1957 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
1959 .set(0, 1i64, 1i64)
1960 .unwrap()
1961 .set(1, "alice", "alicia")
1962 .unwrap();
1963
1964 let cs1 = ChangesetBuilder::new().insert(insert);
1965 let cs2 = ChangesetBuilder::new().update(update);
1966
1967 let merged = cs1 | cs2;
1969 assert_eq!(merged.len(), 1);
1970 }
1971
1972 #[test]
1973 fn test_session_hash_growth_with_many_rows() {
1974 let table = TestTable::new("many", 2, 0);
1978 let mut cs = ChangesetBuilder::new();
1979 for i in 0..200i64 {
1980 let insert = Insert::from(table.clone())
1981 .set(0, i)
1982 .unwrap()
1983 .set(1, alloc::format!("row-{i}"))
1984 .unwrap();
1985 cs = cs.insert(insert);
1986 }
1987 assert_eq!(cs.len(), 200);
1988
1989 let bytes = cs.build();
1990 assert!(!bytes.is_empty());
1991
1992 let reparsed = crate::parser::ParsedDiffSet::try_from(bytes.as_slice()).unwrap();
1994 let reparsed_bytes: Vec<u8> = reparsed.into();
1995 assert_eq!(bytes, reparsed_bytes);
1996 }
1997
1998 const INDIRECT_BYTE_OFFSET: usize = 7;
2006
2007 #[test]
2008 fn test_build_insert_indirect_byte_set() {
2009 let table = TestTable::new("t", 2, 0);
2010 let insert = Insert::from(table)
2011 .set(0, 1i64)
2012 .unwrap()
2013 .set(1, "a")
2014 .unwrap()
2015 .indirect(true);
2016
2017 let bytes = ChangesetBuilder::new().insert(insert).build();
2018 assert_eq!(bytes[INDIRECT_BYTE_OFFSET], 1);
2019 }
2020
2021 #[test]
2022 fn test_build_delete_indirect_byte_set() {
2023 let table = TestTable::new("t", 2, 0);
2024 let delete = ChangeDelete::from(table)
2025 .set(0, 1i64)
2026 .unwrap()
2027 .set(1, "a")
2028 .unwrap()
2029 .indirect(true);
2030
2031 let bytes = ChangesetBuilder::new().delete(delete).build();
2032 assert_eq!(bytes[INDIRECT_BYTE_OFFSET], 1);
2033 }
2034
2035 #[test]
2036 fn test_build_update_indirect_byte_set() {
2037 let table = TestTable::new("t", 2, 0);
2038 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table)
2039 .set(0, 1i64, 1i64)
2040 .unwrap()
2041 .set(1, "a", "b")
2042 .unwrap()
2043 .indirect(true);
2044
2045 let bytes = ChangesetBuilder::new().update(update).build();
2046 assert_eq!(bytes[INDIRECT_BYTE_OFFSET], 1);
2047 }
2048
2049 #[test]
2050 fn test_build_patchset_insert_indirect_byte() {
2051 let table = TestTable::new("t", 2, 0);
2052 let insert = Insert::from(table)
2053 .set(0, 1i64)
2054 .unwrap()
2055 .set(1, "a")
2056 .unwrap()
2057 .indirect(true);
2058
2059 let patchset: PatchSet<TestTable, String, Vec<u8>> = PatchSet::new().insert(insert);
2060 let bytes = patchset.build();
2061 assert_eq!(bytes[INDIRECT_BYTE_OFFSET], 1);
2062 }
2063
2064 #[test]
2065 fn test_reverse_preserves_indirect() {
2066 let insert: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Insert {
2068 values: vec![Value::Integer(1)],
2069 indirect: true,
2070 };
2071 let reversed = insert.reverse();
2072 assert!(reversed.indirect());
2073 assert!(matches!(reversed, Operation::Delete { .. }));
2074
2075 let delete: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Delete {
2077 data: vec![Value::Integer(1)],
2078 indirect: true,
2079 };
2080 let reversed = delete.reverse();
2081 assert!(reversed.indirect());
2082 assert!(matches!(reversed, Operation::Insert { .. }));
2083
2084 let update: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Update {
2086 values: vec![(Some(Value::Integer(1)), Some(Value::Integer(2)))],
2087 indirect: true,
2088 };
2089 let reversed = update.reverse();
2090 assert!(reversed.indirect());
2091 assert!(matches!(reversed, Operation::Update { .. }));
2092 }
2093
2094 #[test]
2095 fn test_patchdelete_indirect_byte_set() {
2096 let table = TestTable::new("t", 2, 0);
2097 let delete: PatchDelete<TestTable, String, Vec<u8>> =
2098 PatchDelete::new(table, vec![Value::Integer(1)]).indirect(true);
2099 let bytes = PatchSet::new().delete(delete).build();
2100 assert_eq!(bytes[INDIRECT_BYTE_OFFSET], 1);
2101 }
2102
2103 #[test]
2104 fn test_operation_eq_indirect_differs() {
2105 let a: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Insert {
2107 values: vec![Value::Integer(1)],
2108 indirect: false,
2109 };
2110 let b: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Insert {
2111 values: vec![Value::Integer(1)],
2112 indirect: true,
2113 };
2114 assert_ne!(a, b);
2115 }
2116
2117 #[test]
2118 fn test_operation_eq_variant_mismatch() {
2119 let insert: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Insert {
2121 values: vec![Value::Integer(1)],
2122 indirect: false,
2123 };
2124 let delete: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Delete {
2125 data: vec![Value::Integer(1)],
2126 indirect: false,
2127 };
2128 assert_ne!(insert, delete);
2129 }
2130
2131 #[test]
2132 fn test_bitor_indirect_rhs_wins() {
2133 let table = TestTable::new("t", 2, 0);
2136 let lhs_insert = Insert::from(table.clone())
2137 .set(0, 1i64)
2138 .unwrap()
2139 .set(1, "a")
2140 .unwrap()
2141 .indirect(true);
2142 let rhs_insert = Insert::from(table.clone())
2143 .set(0, 1i64)
2144 .unwrap()
2145 .set(1, "a")
2146 .unwrap()
2147 .indirect(false);
2148
2149 let merged =
2150 ChangesetBuilder::new().insert(lhs_insert) | ChangesetBuilder::new().insert(rhs_insert);
2151 let rows = merged.tables.get(&table).unwrap();
2152 let op = rows.values().next().unwrap();
2153 assert!(!op.indirect(), "rhs (false) should win over lhs (true)");
2154
2155 let lhs_insert = Insert::from(table.clone())
2157 .set(0, 2i64)
2158 .unwrap()
2159 .set(1, "b")
2160 .unwrap()
2161 .indirect(false);
2162 let rhs_insert = Insert::from(table.clone())
2163 .set(0, 2i64)
2164 .unwrap()
2165 .set(1, "b")
2166 .unwrap()
2167 .indirect(true);
2168
2169 let merged =
2170 ChangesetBuilder::new().insert(lhs_insert) | ChangesetBuilder::new().insert(rhs_insert);
2171 let rows = merged.tables.get(&table).unwrap();
2172 let op = rows.values().next().unwrap();
2173 assert!(op.indirect(), "rhs (true) should win over lhs (false)");
2174 }
2175
2176 #[test]
2177 fn test_roundtrip_indirect_changeset() {
2178 let table = TestTable::new("t", 2, 0);
2179 let insert = Insert::from(table)
2180 .set(0, 1i64)
2181 .unwrap()
2182 .set(1, "a")
2183 .unwrap()
2184 .indirect(true);
2185
2186 let bytes = ChangesetBuilder::new().insert(insert).build();
2187 let reparsed = crate::parser::ParsedDiffSet::try_from(bytes.as_slice()).unwrap();
2188 let reparsed_bytes: Vec<u8> = reparsed.into();
2189 assert_eq!(bytes, reparsed_bytes);
2190 assert_eq!(reparsed_bytes[INDIRECT_BYTE_OFFSET], 1);
2191 }
2192
2193 #[test]
2194 fn test_roundtrip_indirect_patchset() {
2195 let table = TestTable::new("t", 2, 0);
2196 let insert = Insert::from(table)
2197 .set(0, 1i64)
2198 .unwrap()
2199 .set(1, "a")
2200 .unwrap()
2201 .indirect(true);
2202
2203 let patchset: PatchSet<TestTable, String, Vec<u8>> = PatchSet::new().insert(insert);
2204 let bytes = patchset.build();
2205 let reparsed = crate::parser::ParsedDiffSet::try_from(bytes.as_slice()).unwrap();
2206 let reparsed_bytes: Vec<u8> = reparsed.into();
2207 assert_eq!(bytes, reparsed_bytes);
2208 assert_eq!(reparsed_bytes[INDIRECT_BYTE_OFFSET], 1);
2209 }
2210
2211 #[test]
2212 fn test_indirect_full_pipeline_roundtrip() {
2213 let table = TestTable::new("t", 2, 0);
2216 let insert = Insert::from(table)
2217 .set(0, 1i64)
2218 .unwrap()
2219 .set(1, "alice")
2220 .unwrap()
2221 .indirect(true);
2222 let original = ChangesetBuilder::new().insert(insert);
2223 let bytes_a = original.build();
2224 assert_eq!(bytes_a[INDIRECT_BYTE_OFFSET], 1);
2225
2226 let parsed = crate::parser::ParsedDiffSet::try_from(bytes_a.as_slice()).unwrap();
2228 let crate::parser::ParsedDiffSet::Changeset(parsed_set) = parsed else {
2229 panic!("expected Changeset variant");
2230 };
2231
2232 let parsed_builder: DiffSetBuilder<
2233 ChangesetFormat,
2234 crate::parser::TableSchema<String>,
2235 String,
2236 Vec<u8>,
2237 > = parsed_set.into();
2238 let empty: DiffSetBuilder<
2239 ChangesetFormat,
2240 crate::parser::TableSchema<String>,
2241 String,
2242 Vec<u8>,
2243 > = DiffSetBuilder::new();
2244 let doubled = parsed_builder.reverse().reverse();
2245 let merged = doubled | empty;
2246
2247 let bytes_b = merged.build();
2248 assert_eq!(bytes_a, bytes_b);
2249 assert_eq!(bytes_b[INDIRECT_BYTE_OFFSET], 1);
2250 }
2251
2252 fn changeset_insert(v: i64) -> Operation<ChangesetFormat, String, Vec<u8>> {
2257 Operation::Insert {
2258 values: vec![Value::Integer(v), Value::Text("a".into())],
2259 indirect: false,
2260 }
2261 }
2262
2263 fn changeset_delete(v: i64) -> Operation<ChangesetFormat, String, Vec<u8>> {
2264 Operation::Delete {
2265 data: vec![Value::Integer(v), Value::Text("a".into())],
2266 indirect: false,
2267 }
2268 }
2269
2270 fn changeset_update(old: i64, new: i64) -> Operation<ChangesetFormat, String, Vec<u8>> {
2271 Operation::Update {
2272 values: vec![
2273 (Some(Value::Integer(old)), Some(Value::Integer(new))),
2274 (Some(Value::Text("a".into())), Some(Value::Text("b".into()))),
2275 ],
2276 indirect: false,
2277 }
2278 }
2279
2280 #[test]
2281 fn test_add_changeset_insert_plus_update() {
2282 let merged = (changeset_insert(1) + changeset_update(1, 2)).unwrap();
2283 let Operation::Insert { values, .. } = merged else {
2284 panic!("expected Insert");
2285 };
2286 assert_eq!(values[0], Value::Integer(2));
2287 assert_eq!(values[1], Value::Text("b".into()));
2288 }
2289
2290 #[test]
2291 fn test_add_changeset_update_plus_insert() {
2292 let merged = (changeset_update(1, 2) + changeset_insert(99)).unwrap();
2294 assert!(matches!(merged, Operation::Update { .. }));
2295 }
2296
2297 #[test]
2298 fn test_add_changeset_update_plus_update() {
2299 let merged = (changeset_update(1, 2) + changeset_update(2, 3)).unwrap();
2301 let Operation::Update { values, .. } = merged else {
2302 panic!("expected Update");
2303 };
2304 assert_eq!(values[0].0, Some(Value::Integer(1)));
2305 assert_eq!(values[0].1, Some(Value::Integer(3)));
2306 }
2307
2308 #[test]
2309 fn test_add_changeset_update_plus_delete() {
2310 let merged = (changeset_update(1, 2) + changeset_delete(99)).unwrap();
2312 let Operation::Delete { data, .. } = merged else {
2313 panic!("expected Delete");
2314 };
2315 assert_eq!(data[0], Value::Integer(1));
2316 assert_eq!(data[1], Value::Text("a".into()));
2317 }
2318
2319 #[test]
2320 fn test_add_changeset_delete_plus_update_keeps_delete() {
2321 let merged = (changeset_delete(1) + changeset_update(1, 2)).unwrap();
2322 assert!(matches!(merged, Operation::Delete { .. }));
2323 }
2324
2325 #[test]
2326 fn test_add_changeset_delete_plus_delete_keeps_first() {
2327 let merged = (changeset_delete(1) + changeset_delete(2)).unwrap();
2328 let Operation::Delete { data, .. } = merged else {
2329 panic!("expected Delete");
2330 };
2331 assert_eq!(data[0], Value::Integer(1));
2332 }
2333
2334 fn patchset_insert(v: i64) -> Operation<PatchsetFormat, String, Vec<u8>> {
2335 Operation::Insert {
2336 values: vec![Value::Integer(v), Value::Text("a".into())],
2337 indirect: false,
2338 }
2339 }
2340
2341 fn patchset_update(new: i64) -> Operation<PatchsetFormat, String, Vec<u8>> {
2342 Operation::Update {
2343 values: vec![
2344 ((), Some(Value::Integer(new))),
2345 ((), Some(Value::Text("b".into()))),
2346 ],
2347 indirect: false,
2348 }
2349 }
2350
2351 fn patchset_delete() -> Operation<PatchsetFormat, String, Vec<u8>> {
2352 Operation::Delete {
2353 data: (),
2354 indirect: false,
2355 }
2356 }
2357
2358 #[test]
2359 fn test_add_patchset_insert_plus_update() {
2360 let merged = (patchset_insert(1) + patchset_update(2)).unwrap();
2361 let Operation::Insert { values, .. } = merged else {
2362 panic!("expected Insert");
2363 };
2364 assert_eq!(values[0], Value::Integer(2));
2365 assert_eq!(values[1], Value::Text("b".into()));
2366 }
2367
2368 #[test]
2369 fn test_add_patchset_update_plus_insert() {
2370 let merged = (patchset_update(2) + patchset_insert(99)).unwrap();
2371 assert!(matches!(merged, Operation::Update { .. }));
2372 }
2373
2374 #[test]
2375 fn test_add_patchset_update_plus_update() {
2376 let merged = (patchset_update(2) + patchset_update(3)).unwrap();
2377 let Operation::Update { values, .. } = merged else {
2378 panic!("expected Update");
2379 };
2380 assert_eq!(values[0].1, Some(Value::Integer(3)));
2381 }
2382
2383 #[test]
2384 fn test_add_patchset_update_plus_delete() {
2385 let merged = (patchset_update(2) + patchset_delete()).unwrap();
2386 assert!(matches!(merged, Operation::Delete { .. }));
2387 }
2388
2389 #[test]
2390 fn test_add_patchset_delete_plus_insert_promotes_to_update() {
2391 let merged = (patchset_delete() + patchset_insert(1)).unwrap();
2393 assert!(matches!(merged, Operation::Update { .. }));
2394 }
2395
2396 #[test]
2397 fn test_add_patchset_delete_plus_update_keeps_delete() {
2398 let merged = (patchset_delete() + patchset_update(2)).unwrap();
2399 assert!(matches!(merged, Operation::Delete { .. }));
2400 }
2401
2402 #[test]
2403 fn test_add_patchset_delete_plus_delete_keeps_first() {
2404 let merged = (patchset_delete() + patchset_delete()).unwrap();
2405 assert!(matches!(merged, Operation::Delete { .. }));
2406 }
2407
2408 #[test]
2413 fn test_session_hash_real_pk() {
2414 let table = TestTable::new("t", 2, 0);
2415 let insert = Insert::from(table)
2416 .set(0, 2.5f64)
2417 .unwrap()
2418 .set(1, "a")
2419 .unwrap();
2420 let bytes = ChangesetBuilder::new().insert(insert).build();
2421 assert!(!bytes.is_empty());
2422 }
2423
2424 #[test]
2425 fn test_session_hash_text_pk() {
2426 let table = TestTable::new("t", 2, 0);
2427 let insert = Insert::from(table)
2428 .set(0, "alice")
2429 .unwrap()
2430 .set(1, 42i64)
2431 .unwrap();
2432 let bytes = ChangesetBuilder::new().insert(insert).build();
2433 assert!(!bytes.is_empty());
2434 }
2435
2436 #[test]
2437 fn test_session_hash_blob_pk() {
2438 let table = TestTable::new("t", 2, 0);
2439 let insert = Insert::from(table)
2440 .set(0, alloc::vec![0xDE_u8, 0xAD, 0xBE, 0xEF])
2441 .unwrap()
2442 .set(1, "a")
2443 .unwrap();
2444 let bytes = ChangesetBuilder::new().insert(insert).build();
2445 assert!(!bytes.is_empty());
2446 }
2447
2448 #[test]
2453 fn test_session_row_order_empty() {
2454 let cs: ChangesetBuilder = ChangesetBuilder::new();
2456 let bytes = cs.build();
2457 assert!(bytes.is_empty());
2458 }
2459
2460 #[test]
2461 fn test_session_row_order_empty_rows_returns_empty_vec() {
2462 let rows: RowMap<ChangesetFormat, String, Vec<u8>> = IndexMap::default();
2464 assert!(session_row_order(&rows).is_empty());
2465 }
2466
2467 #[test]
2468 fn test_diffset_patchset_build_skips_empty_table() {
2469 let table = TestTable::new("t", 2, 0);
2471 let mut builder: PatchSet<TestTable, String, Vec<u8>> = PatchSet::new();
2472 builder.add_table(&table);
2473 let frozen: DiffSet<PatchsetFormat, TestTable, String, Vec<u8>> = builder.into();
2474 let bytes = frozen.build();
2475 assert!(bytes.is_empty());
2476 }
2477
2478 #[test]
2483 fn test_from_changeset_builder_into_vec() {
2484 let table = TestTable::new("t", 2, 0);
2485 let insert = Insert::from(table)
2486 .set(0, 1i64)
2487 .unwrap()
2488 .set(1, "a")
2489 .unwrap();
2490 let builder = ChangesetBuilder::new().insert(insert);
2491 let bytes_owned: Vec<u8> = builder.clone().into();
2492 let bytes_ref: Vec<u8> = (&builder).into();
2493 assert_eq!(bytes_owned, bytes_ref);
2494 let frozen: DiffSet<ChangesetFormat, TestTable, String, Vec<u8>> = builder.into();
2495 let bytes_frozen_owned: Vec<u8> = frozen.clone().into();
2496 let bytes_frozen_ref: Vec<u8> = (&frozen).into();
2497 assert_eq!(bytes_frozen_owned, bytes_frozen_ref);
2498 }
2499
2500 #[test]
2501 fn test_from_patchset_builder_into_vec() {
2502 let table = TestTable::new("t", 2, 0);
2503 let insert = Insert::from(table)
2504 .set(0, 1i64)
2505 .unwrap()
2506 .set(1, "a")
2507 .unwrap();
2508 let builder: PatchSet<TestTable, String, Vec<u8>> = PatchSet::new().insert(insert);
2509 let bytes_owned: Vec<u8> = builder.clone().into();
2510 let bytes_ref: Vec<u8> = (&builder).into();
2511 assert_eq!(bytes_owned, bytes_ref);
2512 let frozen: DiffSet<PatchsetFormat, TestTable, String, Vec<u8>> = builder.into();
2513 let bytes_frozen_owned: Vec<u8> = frozen.clone().into();
2514 let bytes_frozen_ref: Vec<u8> = (&frozen).into();
2515 assert_eq!(bytes_frozen_owned, bytes_frozen_ref);
2516 }
2517
2518 #[test]
2523 fn test_add_operation_insert_then_update_changes_pk() {
2524 let table = TestTable::new("t", 2, 0);
2527 let insert = Insert::from(table.clone())
2528 .set(0, 1i64)
2529 .unwrap()
2530 .set(1, "alice")
2531 .unwrap();
2532 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
2533 .set(0, 1i64, 2i64)
2534 .unwrap()
2535 .set(1, "alice", "bob")
2536 .unwrap();
2537 let builder = ChangesetBuilder::new().insert(insert).update(update);
2538 let rows = builder.tables.get(&table).unwrap();
2539 assert_eq!(rows.len(), 1);
2540 let (pk, op) = rows.iter().next().unwrap();
2542 assert_eq!(pk[0], Value::Integer(2));
2543 let Operation::Insert { values, .. } = op else {
2544 panic!("expected merged INSERT");
2545 };
2546 assert_eq!(values[0], Value::Integer(2));
2547 assert_eq!(values[1], Value::Text("bob".into()));
2548 }
2549
2550 #[test]
2555 fn test_diffset_changeset_diffops_wrappers() {
2556 let table = TestTable::new("t", 2, 0);
2557 let initial = Insert::from(table.clone())
2558 .set(0, 1i64)
2559 .unwrap()
2560 .set(1, "a")
2561 .unwrap();
2562 let frozen: DiffSet<ChangesetFormat, TestTable, String, Vec<u8>> =
2563 ChangesetBuilder::new().insert(initial).into();
2564
2565 let insert2 = Insert::from(table.clone())
2566 .set(0, 2i64)
2567 .unwrap()
2568 .set(1, "b")
2569 .unwrap();
2570 let after_insert = <_ as DiffOps<_, _, _>>::insert(frozen.clone(), insert2);
2571 assert_eq!(after_insert.len(), 2);
2572
2573 let delete = ChangeDelete::from(table.clone())
2574 .set(0, 1i64)
2575 .unwrap()
2576 .set(1, "a")
2577 .unwrap();
2578 let after_delete = <_ as DiffOps<_, _, _>>::delete(frozen.clone(), delete);
2579 assert_eq!(after_delete.len(), 0);
2580
2581 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table)
2582 .set(0, 1i64, 1i64)
2583 .unwrap()
2584 .set(1, "a", "z")
2585 .unwrap();
2586 let after_update = <_ as DiffOps<_, _, _>>::update(frozen, update);
2587 assert_eq!(after_update.len(), 1);
2588 }
2589
2590 #[test]
2591 fn test_diffset_patchset_diffops_wrappers() {
2592 let table = TestTable::new("t", 2, 0);
2593 let initial = Insert::from(table.clone())
2594 .set(0, 1i64)
2595 .unwrap()
2596 .set(1, "a")
2597 .unwrap();
2598 let frozen: DiffSet<PatchsetFormat, TestTable, String, Vec<u8>> =
2599 PatchSet::new().insert(initial).into();
2600
2601 let insert2 = Insert::from(table.clone())
2602 .set(0, 2i64)
2603 .unwrap()
2604 .set(1, "b")
2605 .unwrap();
2606 let after_insert = <_ as DiffOps<_, _, _>>::insert(frozen.clone(), insert2);
2607 assert_eq!(after_insert.len(), 2);
2608
2609 let delete: PatchDelete<TestTable, String, Vec<u8>> =
2610 PatchDelete::new(table.clone(), vec![Value::Integer(1)]);
2611 let after_delete = <_ as DiffOps<_, _, _>>::delete(frozen.clone(), delete);
2612 assert_eq!(after_delete.len(), 0);
2614
2615 let update = Update::<TestTable, PatchsetFormat, String, Vec<u8>>::from(table)
2616 .set(0, 1i64)
2617 .unwrap()
2618 .set(1, "z")
2619 .unwrap();
2620 let after_update = <_ as DiffOps<_, _, _>>::update(frozen, update);
2621 assert_eq!(after_update.len(), 1);
2622 }
2623
2624 #[test]
2625 fn test_diffset_changeset_iter_yields_inserts_and_indirect_flag() {
2626 let table = TestTable::new("t", 2, 0);
2627 let direct = Insert::from(table.clone()).set(0, 1i64).unwrap();
2628 let indirect = Insert::from(table.clone())
2629 .set(0, 2i64)
2630 .unwrap()
2631 .indirect(true);
2632 let frozen: DiffSet<ChangesetFormat, TestTable, String, Vec<u8>> = ChangesetBuilder::new()
2633 .insert(direct)
2634 .insert(indirect)
2635 .into();
2636
2637 let ops: Vec<_> = frozen.iter().collect();
2638 assert_eq!(ops.len(), 2);
2639 for op in &ops {
2640 assert_eq!(crate::DynTable::name(op.table()), "t");
2641 }
2642 assert!(matches!(
2643 ops[0],
2644 ChangesetOp::Insert {
2645 indirect: false,
2646 ..
2647 }
2648 ));
2649 assert!(matches!(ops[1], ChangesetOp::Insert { indirect: true, .. }));
2650 }
2651
2652 #[test]
2653 fn test_diffset_changeset_iter_yields_update_and_delete() {
2654 let table = TestTable::new("t", 2, 0);
2655 let starting = Insert::from(table.clone())
2656 .set(0, 1i64)
2657 .unwrap()
2658 .set(1, "a")
2659 .unwrap();
2660 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
2661 .set(0, 2i64, 2i64)
2662 .unwrap()
2663 .set(1, "before", "after")
2664 .unwrap();
2665 let delete = ChangeDelete::from(table.clone())
2666 .set(0, 3i64)
2667 .unwrap()
2668 .set(1, "gone")
2669 .unwrap();
2670 let frozen: DiffSet<ChangesetFormat, TestTable, String, Vec<u8>> = ChangesetBuilder::new()
2671 .insert(starting)
2672 .update(update)
2673 .delete(delete)
2674 .into();
2675
2676 let kinds: Vec<&'static str> = frozen
2677 .iter()
2678 .map(|op| match op {
2679 ChangesetOp::Insert { .. } => "insert",
2680 ChangesetOp::Update { .. } => "update",
2681 ChangesetOp::Delete { .. } => "delete",
2682 })
2683 .collect();
2684 assert_eq!(kinds, ["insert", "update", "delete"]);
2685 }
2686
2687 #[test]
2688 fn test_diffset_patchset_iter_exposes_pk_for_delete_and_update() {
2689 let table = TestTable::new("t", 2, 0);
2690 let starting = Insert::from(table.clone())
2691 .set(0, 1i64)
2692 .unwrap()
2693 .set(1, "a")
2694 .unwrap();
2695 let update = Update::<TestTable, PatchsetFormat, String, Vec<u8>>::from(table.clone())
2696 .set(0, 5i64)
2697 .unwrap()
2698 .set(1, "z")
2699 .unwrap();
2700 let delete: PatchDelete<TestTable, String, Vec<u8>> =
2701 PatchDelete::new(table.clone(), vec![Value::Integer(7)]);
2702
2703 let frozen: DiffSet<PatchsetFormat, TestTable, String, Vec<u8>> = PatchSet::new()
2704 .insert(starting)
2705 .update(update)
2706 .delete(delete)
2707 .into();
2708
2709 let mut saw_insert = false;
2710 let mut saw_update_pk: Option<i64> = None;
2711 let mut saw_delete_pk: Option<i64> = None;
2712 for op in frozen.iter() {
2713 match op {
2714 PatchsetOp::Insert { values, .. } => {
2715 saw_insert = true;
2716 assert!(matches!(values[0], Value::Integer(1)));
2717 }
2718 PatchsetOp::Update { pk, .. } => {
2719 if let Value::Integer(id) = pk[0] {
2720 saw_update_pk = Some(id);
2721 }
2722 }
2723 PatchsetOp::Delete { pk, .. } => {
2724 if let Value::Integer(id) = pk[0] {
2725 saw_delete_pk = Some(id);
2726 }
2727 }
2728 }
2729 }
2730 assert!(saw_insert);
2731 assert_eq!(saw_update_pk, Some(5));
2732 assert_eq!(saw_delete_pk, Some(7));
2733 }
2734
2735 #[test]
2736 fn test_diffset_tables_skips_empty() {
2737 let t1 = TestTable::new("t1", 2, 0);
2738 let t2 = TestTable::new("t2", 2, 0);
2739 let insert_t1 = Insert::from(t1.clone()).set(0, 1i64).unwrap();
2740 let insert_then_delete_t2 = Insert::from(t2.clone()).set(0, 9i64).unwrap();
2741 let delete_t2 = ChangeDelete::from(t2.clone()).set(0, 9i64).unwrap();
2742 let frozen: DiffSet<ChangesetFormat, TestTable, String, Vec<u8>> = ChangesetBuilder::new()
2743 .insert(insert_t1)
2744 .insert(insert_then_delete_t2)
2745 .delete(delete_t2)
2746 .into();
2747
2748 let names: Vec<&str> = frozen.tables().map(crate::DynTable::name).collect();
2749 assert_eq!(names, ["t1"]);
2750 }
2751}