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::{
58 MaybeValue, Value, encode_defined_value, encode_undefined, encode_value, markers, op_codes,
59 },
60};
61
62type IndexMap<K, V> = IndexMapRaw<K, V, hashbrown::DefaultHashBuilder>;
64
65type RowMap<F, S, B> = IndexMap<Vec<Value<S, B>>, Operation<F, S, B>>;
67
68type TableMap<F, T, S, B> = IndexMap<T, RowMap<F, S, B>>;
70
71type RowVec<F, S, B> = Vec<(Vec<Value<S, B>>, Operation<F, S, B>)>;
73
74type TableVec<F, T, S, B> = Vec<(T, RowVec<F, S, B>)>;
76
77const fn hash_append(h: u32, add: u32) -> u32 {
85 (h << 3) ^ h ^ add
86}
87
88#[allow(clippy::cast_sign_loss)]
92fn session_hash_append_i64(h: u32, i: i64) -> u32 {
93 let lo = (i as u64 & 0xFFFF_FFFF) as u32;
94 let hi = ((i as u64 >> 32) & 0xFFFF_FFFF) as u32;
95 let h = hash_append(h, lo);
96 hash_append(h, hi)
97}
98
99fn session_hash_append_blob(mut h: u32, data: &[u8]) -> u32 {
103 for &byte in data {
104 h = hash_append(h, u32::from(byte));
105 }
106 h
107}
108
109fn session_hash_pk<S: AsRef<str>, B: AsRef<[u8]>>(pk: &[Value<S, B>]) -> u32 {
114 let mut h: u32 = 0;
115 for value in pk {
116 match value {
117 Value::Integer(i) => {
118 h = hash_append(h, 1); h = session_hash_append_i64(h, *i);
120 }
121 Value::Real(f) => {
122 h = hash_append(h, 2); let i = i64::from_ne_bytes(f.to_ne_bytes());
125 h = session_hash_append_i64(h, i);
126 }
127 Value::Text(s) => {
128 h = hash_append(h, 3); h = session_hash_append_blob(h, s.as_ref().as_bytes());
130 }
131 Value::Blob(b) => {
132 h = hash_append(h, 4); h = session_hash_append_blob(h, b.as_ref());
134 }
135 Value::Null => {
136 }
139 }
140 }
141 h
142}
143
144fn session_row_order<S: AsRef<str>, B: AsRef<[u8]>, V>(
154 rows: &IndexMap<Vec<Value<S, B>>, V>,
155) -> Vec<usize> {
156 let n = rows.len();
157 if n == 0 {
158 return Vec::new();
159 }
160
161 let pks: Vec<&Vec<Value<S, B>>> = rows.keys().collect();
162
163 let mut n_change: usize = 0;
167 let mut buckets: Vec<Vec<usize>> = Vec::new();
168
169 for idx in 0..n {
170 if n_change == 0 || idx >= n_change / 2 {
174 let new_size = if n_change == 0 { 256 } else { n_change * 2 };
175 let mut new_buckets: Vec<Vec<usize>> = vec![Vec::new(); new_size];
176
177 for old_bucket in &buckets {
184 for &entry_idx in old_bucket.iter().rev() {
185 let h = session_hash_pk(pks[entry_idx]) as usize % new_size;
186 new_buckets[h].push(entry_idx);
187 }
188 }
189
190 buckets = new_buckets;
191 n_change = new_size;
192 }
193
194 let h = session_hash_pk(pks[idx]) as usize % n_change;
196 buckets[h].push(idx);
197 }
198
199 let mut order = Vec::with_capacity(n);
202 for bucket in &buckets {
203 for &idx in bucket.iter().rev() {
204 order.push(idx);
205 }
206 }
207
208 order
209}
210
211fn write_table_header<T: SchemaWithPK>(out: &mut Vec<u8>, marker: u8, table: &T) {
223 out.push(marker);
224
225 let num_cols = table.number_of_columns();
226 out.push(u8::try_from(num_cols).unwrap());
227
228 let pk_start = out.len();
229 out.resize(pk_start + num_cols, 0);
230 table.write_pk_flags(&mut out[pk_start..]);
231
232 out.extend(table.name().as_bytes());
233 out.push(0);
234}
235
236fn patchset_pk_mapping<T: SchemaWithPK>(table: &T) -> (Vec<u8>, Vec<Option<usize>>) {
241 let num_cols = table.number_of_columns();
242 let mut pk_flags = alloc::vec![0u8; num_cols];
243 table.write_pk_flags(&mut pk_flags);
244
245 let mut pk_col_to_pk_pos: Vec<Option<usize>> = alloc::vec![None; num_cols];
246 let mut pk_cols: Vec<(usize, u8)> = pk_flags
247 .iter()
248 .enumerate()
249 .filter_map(|(i, &ord)| if ord > 0 { Some((i, ord)) } else { None })
250 .collect();
251 pk_cols.sort_by_key(|(_, ord)| *ord);
252 for (pos, (col_idx, _)) in pk_cols.into_iter().enumerate() {
253 pk_col_to_pk_pos[col_idx] = Some(pos);
254 }
255
256 (pk_flags, pk_col_to_pk_pos)
257}
258
259fn encode_patchset_delete_values<S: AsRef<str>, B: AsRef<[u8]>>(
261 out: &mut Vec<u8>,
262 pk_flags: &[u8],
263 pk_col_to_pk_pos: &[Option<usize>],
264 pk: &[Value<S, B>],
265) {
266 for (col_idx, &pk_flag) in pk_flags.iter().enumerate() {
267 if pk_flag > 0 {
268 if let Some(pk_pos) = pk_col_to_pk_pos[col_idx] {
269 encode_value(out, Some(&pk[pk_pos]));
270 } else {
271 encode_value::<S, B>(out, None);
272 }
273 }
274 }
275}
276
277fn encode_patchset_update_old_values<S: AsRef<str>, B: AsRef<[u8]>>(
279 out: &mut Vec<u8>,
280 pk_flags: &[u8],
281 pk_col_to_pk_pos: &[Option<usize>],
282 pk: &[Value<S, B>],
283) {
284 for (col_idx, &pk_flag) in pk_flags.iter().enumerate() {
285 if pk_flag > 0 {
286 if let Some(pk_pos) = pk_col_to_pk_pos[col_idx] {
287 encode_defined_value(out, &pk[pk_pos]);
288 } else {
289 encode_undefined(out);
290 }
291 } else {
292 encode_undefined(out);
293 }
294 }
295}
296
297fn encode_changeset_op<S: AsRef<str> + Clone + Debug, B: AsRef<[u8]> + Clone + Debug>(
299 out: &mut Vec<u8>,
300 op: &Operation<ChangesetFormat, S, B>,
301) {
302 match op {
303 Operation::Insert { values, indirect } => {
304 out.push(op_codes::INSERT);
305 out.push(u8::from(*indirect));
306 for value in values {
307 encode_value(out, Some(value));
308 }
309 }
310 Operation::Delete {
311 data: values,
312 indirect,
313 } => {
314 out.push(op_codes::DELETE);
315 out.push(u8::from(*indirect));
316 for value in values {
317 encode_value(out, Some(value));
318 }
319 }
320 Operation::Update { values, indirect } => {
321 out.push(op_codes::UPDATE);
322 out.push(u8::from(*indirect));
323 for (old, _new) in values {
324 encode_value(out, old.as_ref());
325 }
326 for (_old, new) in values {
327 encode_value(out, new.as_ref());
328 }
329 }
330 }
331}
332
333fn encode_patchset_op<S: AsRef<str>, B: AsRef<[u8]>>(
337 out: &mut Vec<u8>,
338 op: &Operation<PatchsetFormat, S, B>,
339 pk: &[Value<S, B>],
340 pk_flags: &[u8],
341 pk_col_to_pk_pos: &[Option<usize>],
342) {
343 match op {
344 Operation::Insert { values, indirect } => {
345 out.push(op_codes::INSERT);
346 out.push(u8::from(*indirect));
347 for value in values {
348 encode_value(out, Some(value));
349 }
350 }
351 Operation::Delete { data: (), indirect } => {
352 out.push(op_codes::DELETE);
353 out.push(u8::from(*indirect));
354 encode_patchset_delete_values(out, pk_flags, pk_col_to_pk_pos, pk);
355 }
356 Operation::Update { values, indirect } => {
357 out.push(op_codes::UPDATE);
358 out.push(u8::from(*indirect));
359 encode_patchset_update_old_values(out, pk_flags, pk_col_to_pk_pos, pk);
360 for ((), new) in values {
361 encode_value(out, new.as_ref());
362 }
363 }
364 }
365}
366
367#[derive(Debug, Clone)]
382pub struct DiffSetBuilder<F: Format<S, B>, T: SchemaWithPK, S, B> {
383 pub(crate) tables: TableMap<F, T, S, B>,
384}
385
386impl<F: Format<S, B>, T: SchemaWithPK, S, B> PartialEq for DiffSetBuilder<F, T, S, B>
397where
398 S: PartialEq + Eq + Hash + AsRef<str>,
399 B: PartialEq + Eq + Hash + AsRef<[u8]>,
400 F::Old: PartialEq,
401 F::DeleteData: PartialEq,
402{
403 fn eq(&self, other: &Self) -> bool {
404 self.tables
407 .iter()
408 .filter(|(_, ops)| !ops.is_empty())
409 .eq(other.tables.iter().filter(|(_, ops)| !ops.is_empty()))
410 }
411}
412
413impl<F: Format<S, B>, T: SchemaWithPK, S, B> Eq for DiffSetBuilder<F, T, S, B>
414where
415 S: Eq + Hash + AsRef<str>,
416 B: Eq + Hash + AsRef<[u8]>,
417 F::Old: Eq,
418 F::DeleteData: Eq,
419{
420}
421
422pub type ChangeSet<T, S, B> = DiffSetBuilder<ChangesetFormat, T, S, B>;
424pub type PatchSet<T, S, B> = DiffSetBuilder<PatchsetFormat, T, S, B>;
426
427impl<F: Format<S, B>, T: SchemaWithPK, S: AsRef<str> + Hash + Eq, B: AsRef<[u8]> + Hash + Eq>
428 Default for DiffSetBuilder<F, T, S, B>
429{
430 fn default() -> Self {
431 Self::new()
432 }
433}
434
435impl<
436 T: SchemaWithPK,
437 S: Clone + Debug + Hash + Eq + AsRef<str>,
438 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
439> From<&DiffSetBuilder<ChangesetFormat, T, S, B>> for Vec<u8>
440{
441 #[inline]
442 fn from(builder: &DiffSetBuilder<ChangesetFormat, T, S, B>) -> Self {
443 builder.build()
444 }
445}
446
447impl<
448 T: SchemaWithPK,
449 S: Clone + Debug + Hash + Eq + AsRef<str>,
450 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
451> From<DiffSetBuilder<ChangesetFormat, T, S, B>> for Vec<u8>
452{
453 #[inline]
454 fn from(builder: DiffSetBuilder<ChangesetFormat, T, S, B>) -> Self {
455 builder.build()
456 }
457}
458
459impl<T: SchemaWithPK, S: AsRef<str> + Clone + Hash + Eq, B: AsRef<[u8]> + Clone + Hash + Eq>
460 From<&DiffSetBuilder<PatchsetFormat, T, S, B>> for Vec<u8>
461{
462 #[inline]
463 fn from(builder: &DiffSetBuilder<PatchsetFormat, T, S, B>) -> Self {
464 builder.build()
465 }
466}
467
468impl<T: SchemaWithPK, S: AsRef<str> + Clone + Hash + Eq, B: AsRef<[u8]> + Clone + Hash + Eq>
469 From<DiffSetBuilder<PatchsetFormat, T, S, B>> for Vec<u8>
470{
471 #[inline]
472 fn from(builder: DiffSetBuilder<PatchsetFormat, T, S, B>) -> Self {
473 builder.build()
474 }
475}
476
477impl<F: Format<S, B>, T: SchemaWithPK, S, B> DiffSetBuilder<F, T, S, B> {
478 pub(super) fn table<'builder>(&'builder self, name: &str) -> Option<&'builder T> {
480 self.tables.keys().find(|t| t.name() == name)
481 }
482}
483
484impl<F: Format<S, B>, T: SchemaWithPK, S: AsRef<str> + Hash + Eq, B: AsRef<[u8]> + Hash + Eq>
485 DiffSetBuilder<F, T, S, B>
486{
487 #[inline]
489 #[must_use]
490 pub fn new() -> Self {
491 Self {
492 tables: IndexMap::default(),
493 }
494 }
495
496 #[inline]
501 fn ensure_table(&mut self, table: &T) -> &mut RowMap<F, S, B> {
502 self.tables.entry(table.clone()).or_default()
503 }
504
505 pub fn add_table(&mut self, table: &T) -> &mut Self {
511 self.ensure_table(table);
512 self
513 }
514
515 #[inline]
517 #[must_use]
518 pub fn is_empty(&self) -> bool {
519 self.tables.values().all(IndexMap::is_empty)
520 }
521
522 #[inline]
524 #[must_use]
525 pub fn len(&self) -> usize {
526 self.tables.values().map(IndexMap::len).sum()
527 }
528
529 pub(crate) fn add_operation(
533 &mut self,
534 table: &T,
535 pk: Vec<Value<S, B>>,
536 new_op: Operation<F, S, B>,
537 ) -> &mut Self
538 where
539 S: Clone,
540 B: Clone,
541 Operation<F, S, B>: core::ops::Add<Output = Option<Operation<F, S, B>>>,
542 {
543 let rows = self.ensure_table(table);
544
545 match rows.shift_remove_full(&pk) {
546 None => {
547 rows.insert(pk, new_op);
548 }
549 Some((original_index, _removed_key, existing)) => {
550 match (&existing, &new_op) {
552 (Operation::Insert { .. }, Operation::Update { .. }) => {
553 if let Some(combined) = existing + new_op
555 && let Operation::Insert { values, .. } = &combined
556 {
557 let new_pk = table.extract_pk(values);
558 rows.shift_remove(&new_pk);
560 let index = original_index.min(rows.len());
561 rows.shift_insert(index, new_pk, combined);
562 }
563 }
564 _ => {
565 if let Some(combined) = existing + new_op {
567 rows.shift_insert(original_index, pk, combined);
569 }
570 }
571 }
572 }
573 }
574
575 self
576 }
577}
578
579pub trait DiffOps<T: SchemaWithPK, S, B>: Sized {
589 type Format: Format<S, B>;
591
592 type DeleteArg;
597
598 fn insert(self, insert: Insert<T, S, B>) -> DiffSetBuilder<Self::Format, T, S, B>;
600
601 fn delete(self, delete: Self::DeleteArg) -> DiffSetBuilder<Self::Format, T, S, B>;
603
604 fn update(self, update: Update<T, Self::Format, S, B>)
606 -> DiffSetBuilder<Self::Format, T, S, B>;
607}
608
609impl<
612 T: SchemaWithPK,
613 S: Clone + Debug + Hash + Eq + AsRef<str>,
614 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
615> DiffOps<T, S, B> for DiffSetBuilder<ChangesetFormat, T, S, B>
616{
617 type Format = ChangesetFormat;
618 type DeleteArg = ChangeDelete<T, S, B>;
619
620 fn insert(mut self, insert: Insert<T, S, B>) -> Self {
621 let pk = insert.extract_pk();
622 let table = insert.as_ref().clone();
623 let indirect = insert.indirect;
624 self.add_operation(
625 &table,
626 pk,
627 Operation::Insert {
628 values: insert.into_values(),
629 indirect,
630 },
631 );
632 self
633 }
634
635 fn delete(mut self, delete: ChangeDelete<T, S, B>) -> Self {
636 let pk = delete.as_ref().extract_pk(&delete.values);
637 let table = delete.as_ref().clone();
638 let indirect = delete.indirect;
639 self.add_operation(
640 &table,
641 pk,
642 Operation::Delete {
643 data: delete.into_values(),
644 indirect,
645 },
646 );
647 self
648 }
649
650 fn update(mut self, update: Update<T, ChangesetFormat, S, B>) -> Self {
651 let old_values: Vec<_> = update
652 .values()
653 .iter()
654 .map(|(old, _): &(_, _)| old.clone().unwrap_or(Value::Null))
655 .collect();
656 let pk = update.as_ref().extract_pk(&old_values);
657 let table = update.as_ref().clone();
658 let indirect = update.indirect;
659 let values: Vec<(MaybeValue<S, B>, MaybeValue<S, B>)> = update.into();
660 self.add_operation(&table, pk, Operation::Update { values, indirect });
661 self
662 }
663}
664
665impl<T: SchemaWithPK, S: Clone + Hash + Eq + AsRef<str>, B: Clone + Hash + Eq + AsRef<[u8]>>
668 DiffOps<T, S, B> for DiffSetBuilder<PatchsetFormat, T, S, B>
669{
670 type Format = PatchsetFormat;
671 type DeleteArg = PatchDelete<T, S, B>;
672
673 fn insert(mut self, insert: Insert<T, S, B>) -> Self {
674 let pk = insert.extract_pk();
675 let table = insert.as_ref().clone();
676 let indirect = insert.indirect;
677 self.add_operation(
678 &table,
679 pk,
680 Operation::Insert {
681 values: insert.into_values(),
682 indirect,
683 },
684 );
685 self
686 }
687
688 fn delete(mut self, delete: PatchDelete<T, S, B>) -> Self {
703 let indirect = delete.indirect;
704 self.add_operation(
705 &delete.table,
706 delete.pk,
707 Operation::Delete { data: (), indirect },
708 );
709 self
710 }
711
712 fn update(mut self, update: Update<T, PatchsetFormat, S, B>) -> Self {
731 let pk = update.extract_pk();
732 let table = update.as_ref().clone();
733 let indirect = update.indirect;
734 let values: Vec<((), MaybeValue<S, B>)> = update.into();
735 self.add_operation(&table, pk, Operation::Update { values, indirect });
736 self
737 }
738}
739
740impl<
743 T: SchemaWithPK,
744 S: Clone + Debug + Hash + Eq + AsRef<str>,
745 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
746> DiffOps<T, S, B> for DiffSet<ChangesetFormat, T, S, B>
747{
748 type Format = ChangesetFormat;
749 type DeleteArg = ChangeDelete<T, S, B>;
750
751 fn insert(self, insert: Insert<T, S, B>) -> DiffSetBuilder<ChangesetFormat, T, S, B> {
752 let builder: DiffSetBuilder<ChangesetFormat, T, S, B> = self.into();
753 builder.insert(insert)
754 }
755
756 fn delete(self, delete: ChangeDelete<T, S, B>) -> DiffSetBuilder<ChangesetFormat, T, S, B> {
757 let builder: DiffSetBuilder<ChangesetFormat, T, S, B> = self.into();
758 builder.delete(delete)
759 }
760
761 fn update(
762 self,
763 update: Update<T, ChangesetFormat, S, B>,
764 ) -> DiffSetBuilder<ChangesetFormat, T, S, B> {
765 let builder: DiffSetBuilder<ChangesetFormat, T, S, B> = self.into();
766 builder.update(update)
767 }
768}
769
770impl<T: SchemaWithPK, S: Clone + Hash + Eq + AsRef<str>, B: Clone + Hash + Eq + AsRef<[u8]>>
773 DiffOps<T, S, B> for DiffSet<PatchsetFormat, T, S, B>
774{
775 type Format = PatchsetFormat;
776 type DeleteArg = PatchDelete<T, S, B>;
777
778 fn insert(self, insert: Insert<T, S, B>) -> DiffSetBuilder<PatchsetFormat, T, S, B> {
779 let builder: DiffSetBuilder<PatchsetFormat, T, S, B> = self.into();
780 builder.insert(insert)
781 }
782
783 fn delete(self, delete: PatchDelete<T, S, B>) -> DiffSetBuilder<PatchsetFormat, T, S, B> {
784 let builder: DiffSetBuilder<PatchsetFormat, T, S, B> = self.into();
785 builder.delete(delete)
786 }
787
788 fn update(
789 self,
790 update: Update<T, PatchsetFormat, S, B>,
791 ) -> DiffSetBuilder<PatchsetFormat, T, S, B> {
792 let builder: DiffSetBuilder<PatchsetFormat, T, S, B> = self.into();
793 builder.update(update)
794 }
795}
796
797impl<T: crate::schema::NamedColumns, S: Clone + Hash + Eq + AsRef<str> + for<'a> From<&'a str>>
798 DiffSetBuilder<PatchsetFormat, T, S, Vec<u8>>
799{
800 pub fn digest_sql<'input>(
810 &mut self,
811 input: &'input str,
812 ) -> Result<&mut Self, crate::builders::sql::ParseError<'input>> {
813 let mut parser = crate::builders::sql::Parser::new(input, self);
814 parser.digest_all()?;
815 Ok(self)
816 }
817}
818
819impl<
824 T: SchemaWithPK,
825 S: Clone + Debug + Hash + Eq + AsRef<str>,
826 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
827> DiffSetBuilder<ChangesetFormat, T, S, B>
828{
829 #[must_use]
838 pub fn build(&self) -> Vec<u8> {
839 let mut out = Vec::new();
840
841 for (table, rows) in &self.tables {
842 if rows.is_empty() {
843 continue;
844 }
845
846 write_table_header(&mut out, markers::CHANGESET, table);
847
848 for idx in session_row_order(rows) {
849 let (_pk, op) = rows.get_index(idx).unwrap();
850 encode_changeset_op(&mut out, op);
851 }
852 }
853
854 out
855 }
856}
857
858impl<T: SchemaWithPK, S: Clone + Hash + Eq + AsRef<str>, B: Clone + Hash + Eq + AsRef<[u8]>>
859 DiffSetBuilder<PatchsetFormat, T, S, B>
860{
861 #[must_use]
870 pub fn build(&self) -> Vec<u8> {
871 let mut out = Vec::new();
872
873 for (table, rows) in &self.tables {
874 if rows.is_empty() {
875 continue;
876 }
877
878 write_table_header(&mut out, markers::PATCHSET, table);
879
880 let (pk_flags, pk_col_to_pk_pos) = patchset_pk_mapping(table);
881
882 for idx in session_row_order(rows) {
883 let (pk, op) = rows.get_index(idx).unwrap();
884 encode_patchset_op(&mut out, op, pk, &pk_flags, &pk_col_to_pk_pos);
885 }
886 }
887
888 out
889 }
890}
891
892use crate::builders::operation::Reverse;
897
898impl<
899 T: SchemaWithPK,
900 S: Clone + Debug + Hash + Eq + AsRef<str>,
901 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
902> Reverse for DiffSetBuilder<ChangesetFormat, T, S, B>
903{
904 type Output = DiffSetBuilder<ChangesetFormat, T, S, B>;
905
906 fn reverse(self) -> Self::Output {
907 let mut reversed: DiffSetBuilder<ChangesetFormat, T, S, B> = DiffSetBuilder::new();
908
909 for (table, rows) in self.tables {
910 for (pk, op) in rows {
911 let rev_op = op.reverse();
912
913 reversed.add_operation(&table, pk, rev_op);
914 }
915 }
916
917 reversed
918 }
919}
920
921impl<
927 F: Format<S, B>,
928 T: SchemaWithPK,
929 S: Clone + Hash + Eq + AsRef<str>,
930 B: Clone + Hash + Eq + AsRef<[u8]>,
931> BitOrAssign for DiffSetBuilder<F, T, S, B>
932where
933 Operation<F, S, B>: core::ops::Add<Output = Option<Operation<F, S, B>>>,
934{
935 fn bitor_assign(&mut self, rhs: Self) {
937 for (table, rows) in rhs.tables {
938 for (pk, op) in rows {
939 self.add_operation(&table, pk, op);
940 }
941 }
942 }
943}
944
945impl<
946 F: Format<S, B>,
947 T: SchemaWithPK,
948 S: Clone + Hash + Eq + AsRef<str>,
949 B: Clone + Hash + Eq + AsRef<[u8]>,
950> BitOr for DiffSetBuilder<F, T, S, B>
951where
952 Operation<F, S, B>: core::ops::Add<Output = Option<Operation<F, S, B>>>,
953{
954 type Output = Self;
955
956 #[inline]
958 fn bitor(mut self, rhs: Self) -> Self::Output {
959 self |= rhs;
960 self
961 }
962}
963
964#[derive(Debug, Clone)]
982pub struct DiffSet<F: Format<S, B>, T: SchemaWithPK, S, B> {
983 pub(crate) tables: TableVec<F, T, S, B>,
985}
986
987impl<F: Format<S, B>, T: SchemaWithPK, S, B> PartialEq for DiffSet<F, T, S, B>
990where
991 S: PartialEq + Eq + Hash + AsRef<str>,
992 B: PartialEq + Eq + Hash + AsRef<[u8]>,
993 F::Old: PartialEq,
994 F::DeleteData: PartialEq,
995{
996 fn eq(&self, other: &Self) -> bool {
997 self.tables
998 .iter()
999 .filter(|(_, ops)| !ops.is_empty())
1000 .eq(other.tables.iter().filter(|(_, ops)| !ops.is_empty()))
1001 }
1002}
1003
1004impl<F: Format<S, B>, T: SchemaWithPK, S, B> Eq for DiffSet<F, T, S, B>
1005where
1006 S: Eq + Hash + AsRef<str>,
1007 B: Eq + Hash + AsRef<[u8]>,
1008 F::Old: Eq,
1009 F::DeleteData: Eq,
1010{
1011}
1012
1013impl<F: Format<S, B>, T: SchemaWithPK, S: AsRef<str> + Hash + Eq, B: AsRef<[u8]> + Hash + Eq>
1014 Default for DiffSet<F, T, S, B>
1015{
1016 fn default() -> Self {
1017 Self { tables: Vec::new() }
1018 }
1019}
1020
1021impl<F: Format<S, B>, T: SchemaWithPK, S: AsRef<str> + Hash + Eq, B: AsRef<[u8]> + Hash + Eq>
1022 DiffSet<F, T, S, B>
1023{
1024 #[inline]
1026 #[must_use]
1027 pub fn is_empty(&self) -> bool {
1028 self.tables.iter().all(|(_, rows)| rows.is_empty())
1029 }
1030
1031 #[inline]
1033 #[must_use]
1034 pub fn len(&self) -> usize {
1035 self.tables.iter().map(|(_, rows)| rows.len()).sum()
1036 }
1037}
1038
1039impl<F: Format<S, B>, T: SchemaWithPK, S: AsRef<str> + Hash + Eq, B: AsRef<[u8]> + Hash + Eq>
1042 DiffSet<F, T, S, B>
1043{
1044 pub fn tables(&self) -> impl Iterator<Item = &T> {
1047 self.tables
1048 .iter()
1049 .filter(|(_, rows)| !rows.is_empty())
1050 .map(|(t, _)| t)
1051 }
1052}
1053
1054impl<T: SchemaWithPK, S: Clone + Debug + AsRef<str>, B: Clone + Debug + AsRef<[u8]>>
1055 DiffSet<ChangesetFormat, T, S, B>
1056{
1057 pub fn iter(&self) -> impl Iterator<Item = ChangesetOp<'_, T, S, B>> {
1063 self.tables.iter().flat_map(|(table, rows)| {
1064 rows.iter().map(move |(_pk, op)| match op {
1065 Operation::Insert { values, indirect } => ChangesetOp::Insert {
1066 table,
1067 values: values.as_slice(),
1068 indirect: *indirect,
1069 },
1070 Operation::Update { values, indirect } => ChangesetOp::Update {
1071 table,
1072 values: values.as_slice(),
1073 indirect: *indirect,
1074 },
1075 Operation::Delete { data, indirect } => ChangesetOp::Delete {
1076 table,
1077 old_values: data.as_slice(),
1078 indirect: *indirect,
1079 },
1080 })
1081 })
1082 }
1083}
1084
1085impl<T: SchemaWithPK, S: Clone + AsRef<str>, B: Clone + AsRef<[u8]>>
1086 DiffSet<PatchsetFormat, T, S, B>
1087{
1088 pub fn iter(&self) -> impl Iterator<Item = PatchsetOp<'_, T, S, B>> {
1095 self.tables.iter().flat_map(|(table, rows)| {
1096 rows.iter().map(move |(pk, op)| match op {
1097 Operation::Insert { values, indirect } => PatchsetOp::Insert {
1098 table,
1099 values: values.as_slice(),
1100 indirect: *indirect,
1101 },
1102 Operation::Update { values, indirect } => PatchsetOp::Update {
1103 table,
1104 pk: pk.as_slice(),
1105 entries: values.as_slice(),
1106 indirect: *indirect,
1107 },
1108 Operation::Delete { indirect, .. } => PatchsetOp::Delete {
1109 table,
1110 pk: pk.as_slice(),
1111 indirect: *indirect,
1112 },
1113 })
1114 })
1115 }
1116}
1117
1118impl<
1121 T: SchemaWithPK,
1122 S: Clone + Debug + Hash + Eq + AsRef<str>,
1123 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
1124> DiffSet<ChangesetFormat, T, S, B>
1125{
1126 #[must_use]
1130 pub fn build(&self) -> Vec<u8> {
1131 let mut out = Vec::new();
1132
1133 for (table, rows) in &self.tables {
1134 if rows.is_empty() {
1135 continue;
1136 }
1137
1138 write_table_header(&mut out, markers::CHANGESET, table);
1139
1140 for (_pk, op) in rows {
1141 encode_changeset_op(&mut out, op);
1142 }
1143 }
1144
1145 out
1146 }
1147}
1148
1149impl<T: SchemaWithPK, S: Clone + Hash + Eq + AsRef<str>, B: Clone + Hash + Eq + AsRef<[u8]>>
1152 DiffSet<PatchsetFormat, T, S, B>
1153{
1154 #[must_use]
1158 pub fn build(&self) -> Vec<u8> {
1159 let mut out = Vec::new();
1160
1161 for (table, rows) in &self.tables {
1162 if rows.is_empty() {
1163 continue;
1164 }
1165
1166 write_table_header(&mut out, markers::PATCHSET, table);
1167
1168 let (pk_flags, pk_col_to_pk_pos) = patchset_pk_mapping(table);
1169
1170 for (pk, op) in rows {
1171 encode_patchset_op(&mut out, op, pk, &pk_flags, &pk_col_to_pk_pos);
1172 }
1173 }
1174
1175 out
1176 }
1177}
1178
1179impl<
1182 T: SchemaWithPK,
1183 S: Clone + Debug + Hash + Eq + AsRef<str>,
1184 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
1185> From<&DiffSet<ChangesetFormat, T, S, B>> for Vec<u8>
1186{
1187 #[inline]
1188 fn from(diffset: &DiffSet<ChangesetFormat, T, S, B>) -> Self {
1189 diffset.build()
1190 }
1191}
1192
1193impl<
1194 T: SchemaWithPK,
1195 S: Clone + Debug + Hash + Eq + AsRef<str>,
1196 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
1197> From<DiffSet<ChangesetFormat, T, S, B>> for Vec<u8>
1198{
1199 #[inline]
1200 fn from(diffset: DiffSet<ChangesetFormat, T, S, B>) -> Self {
1201 diffset.build()
1202 }
1203}
1204
1205impl<T: SchemaWithPK, S: AsRef<str> + Clone + Hash + Eq, B: AsRef<[u8]> + Clone + Hash + Eq>
1206 From<&DiffSet<PatchsetFormat, T, S, B>> for Vec<u8>
1207{
1208 #[inline]
1209 fn from(diffset: &DiffSet<PatchsetFormat, T, S, B>) -> Self {
1210 diffset.build()
1211 }
1212}
1213
1214impl<T: SchemaWithPK, S: AsRef<str> + Clone + Hash + Eq, B: AsRef<[u8]> + Clone + Hash + Eq>
1215 From<DiffSet<PatchsetFormat, T, S, B>> for Vec<u8>
1216{
1217 #[inline]
1218 fn from(diffset: DiffSet<PatchsetFormat, T, S, B>) -> Self {
1219 diffset.build()
1220 }
1221}
1222
1223impl<
1226 T: SchemaWithPK,
1227 S: Clone + Debug + Hash + Eq + AsRef<str>,
1228 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
1229> Reverse for DiffSet<ChangesetFormat, T, S, B>
1230{
1231 type Output = DiffSet<ChangesetFormat, T, S, B>;
1232
1233 fn reverse(self) -> Self::Output {
1234 DiffSet {
1235 tables: self
1236 .tables
1237 .into_iter()
1238 .map(|(table, rows)| {
1239 let rev_rows = rows
1240 .into_iter()
1241 .map(|(pk, op)| (pk, op.reverse()))
1242 .collect();
1243 (table, rev_rows)
1244 })
1245 .collect(),
1246 }
1247 }
1248}
1249
1250impl<F: Format<S, B>, T: SchemaWithPK, S: Hash + Eq + AsRef<str>, B: Hash + Eq + AsRef<[u8]>>
1253 From<DiffSetBuilder<F, T, S, B>> for DiffSet<F, T, S, B>
1254{
1255 fn from(builder: DiffSetBuilder<F, T, S, B>) -> Self {
1256 Self {
1257 tables: builder
1258 .tables
1259 .into_iter()
1260 .map(|(table, rows)| {
1261 let ordered_rows: RowVec<F, S, B> = rows.into_iter().collect();
1262 (table, ordered_rows)
1263 })
1264 .collect(),
1265 }
1266 }
1267}
1268
1269impl<F: Format<S, B>, T: SchemaWithPK, S: Hash + Eq + AsRef<str>, B: Hash + Eq + AsRef<[u8]>>
1270 From<DiffSet<F, T, S, B>> for DiffSetBuilder<F, T, S, B>
1271{
1272 fn from(diffset: DiffSet<F, T, S, B>) -> Self {
1273 let mut builder = Self::new();
1274 for (table, rows) in diffset.tables {
1275 let map: IndexMap<Vec<Value<S, B>>, Operation<F, S, B>> = rows.into_iter().collect();
1276 builder.tables.insert(table, map);
1277 }
1278 builder
1279 }
1280}
1281
1282#[cfg(test)]
1283mod tests {
1284 use super::*;
1285 use crate::builders::operation::Indirect;
1286 use crate::encoding::Value;
1287 use alloc::{string::String, vec};
1288
1289 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
1291 struct TestTable {
1292 name: String,
1293 num_columns: usize,
1294 pk_column: usize,
1295 }
1296
1297 impl TestTable {
1298 fn new(name: &str, num_columns: usize, pk_column: usize) -> Self {
1299 Self {
1300 name: name.into(),
1301 num_columns,
1302 pk_column,
1303 }
1304 }
1305 }
1306
1307 impl crate::DynTable for TestTable {
1308 fn name(&self) -> &str {
1309 &self.name
1310 }
1311
1312 fn number_of_columns(&self) -> usize {
1313 self.num_columns
1314 }
1315
1316 fn write_pk_flags(&self, buf: &mut [u8]) {
1317 assert_eq!(buf.len(), self.num_columns);
1318 buf.fill(0);
1319 buf[self.pk_column] = 1;
1320 }
1321 }
1322
1323 impl crate::SchemaWithPK for TestTable {
1324 fn number_of_primary_keys(&self) -> usize {
1325 1
1326 }
1327
1328 fn primary_key_index(&self, col_idx: usize) -> Option<usize> {
1329 if col_idx == self.pk_column {
1330 Some(0)
1331 } else {
1332 None
1333 }
1334 }
1335
1336 fn extract_pk<S: Clone, B: Clone>(
1337 &self,
1338 values: &impl crate::IndexableValues<Text = S, Binary = B>,
1339 ) -> alloc::vec::Vec<Value<S, B>> {
1340 alloc::vec![
1341 values
1342 .get(self.pk_column)
1343 .expect("primary key column index out of bounds, values shorter than schema")
1344 ]
1345 }
1346 }
1347
1348 type ChangesetBuilder = DiffSetBuilder<ChangesetFormat, TestTable, String, Vec<u8>>;
1350
1351 #[test]
1352 fn test_insert_single_row() {
1353 let table = TestTable::new("users", 2, 0);
1354 let insert = Insert::from(table.clone())
1355 .set(0, 1i64)
1356 .unwrap()
1357 .set(1, "alice")
1358 .unwrap();
1359
1360 let builder = ChangesetBuilder::new().insert(insert);
1361
1362 assert_eq!(builder.len(), 1);
1363 assert!(!builder.is_empty());
1364 }
1365
1366 #[test]
1367 fn test_insert_then_delete_cancels_out() {
1368 let table = TestTable::new("users", 2, 0);
1369
1370 let insert = Insert::from(table.clone())
1371 .set(0, 1i64)
1372 .unwrap()
1373 .set(1, "alice")
1374 .unwrap();
1375
1376 let delete = ChangeDelete::from(table.clone())
1377 .set(0, 1i64)
1378 .unwrap()
1379 .set(1, "alice")
1380 .unwrap();
1381
1382 let builder = ChangesetBuilder::new().insert(insert).delete(delete);
1383
1384 assert_eq!(builder.len(), 0);
1385 assert!(builder.is_empty());
1386 }
1387
1388 #[test]
1389 fn test_insert_then_update_becomes_insert() {
1390 let table = TestTable::new("users", 2, 0);
1391
1392 let insert = Insert::from(table.clone())
1393 .set(0, 1i64)
1394 .unwrap()
1395 .set(1, "alice")
1396 .unwrap();
1397
1398 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
1399 .set(0, 1i64, 1i64) .unwrap()
1401 .set(1, "alice", "bob")
1402 .unwrap();
1403
1404 let builder = ChangesetBuilder::new().insert(insert).update(update);
1405
1406 assert_eq!(builder.len(), 1);
1407 }
1409
1410 #[test]
1411 fn test_delete_then_insert_same_values_cancels_out() {
1412 let table = TestTable::new("users", 2, 0);
1413
1414 let delete = ChangeDelete::from(table.clone())
1415 .set(0, 1i64)
1416 .unwrap()
1417 .set(1, "alice")
1418 .unwrap();
1419
1420 let insert = Insert::from(table.clone())
1421 .set(0, 1i64)
1422 .unwrap()
1423 .set(1, "alice")
1424 .unwrap();
1425
1426 let builder = ChangesetBuilder::new().delete(delete).insert(insert);
1427
1428 assert_eq!(builder.len(), 0);
1429 assert!(builder.is_empty());
1430 }
1431
1432 #[test]
1433 fn test_delete_then_insert_different_values_becomes_update() {
1434 let table = TestTable::new("users", 2, 0);
1435
1436 let delete = ChangeDelete::from(table.clone())
1437 .set(0, 1i64)
1438 .unwrap()
1439 .set(1, "alice")
1440 .unwrap();
1441
1442 let insert = Insert::from(table.clone())
1443 .set(0, 1i64)
1444 .unwrap()
1445 .set(1, "bob")
1446 .unwrap();
1447
1448 let builder = ChangesetBuilder::new().delete(delete).insert(insert);
1449
1450 assert_eq!(builder.len(), 1);
1451 }
1453
1454 #[test]
1455 fn test_multiple_rows() {
1456 let table = TestTable::new("users", 2, 0);
1457
1458 let insert1 = Insert::from(table.clone())
1459 .set(0, 1i64)
1460 .unwrap()
1461 .set(1, "alice")
1462 .unwrap();
1463
1464 let insert2 = Insert::from(table.clone())
1465 .set(0, 2i64)
1466 .unwrap()
1467 .set(1, "bob")
1468 .unwrap();
1469
1470 let builder = ChangesetBuilder::new().insert(insert1).insert(insert2);
1471
1472 assert_eq!(builder.len(), 2);
1473 }
1474
1475 #[test]
1476 fn test_update_then_update_consolidates() {
1477 let table = TestTable::new("users", 2, 0);
1478
1479 let update1 = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
1480 .set(0, 1i64, 1i64)
1481 .unwrap()
1482 .set(1, "alice", "bob")
1483 .unwrap();
1484
1485 let update2 = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
1486 .set(0, 1i64, 1i64)
1487 .unwrap()
1488 .set(1, "bob", "charlie")
1489 .unwrap();
1490
1491 let builder = ChangesetBuilder::new().update(update1).update(update2);
1492
1493 assert_eq!(builder.len(), 1);
1494 }
1496
1497 #[test]
1502 fn test_reverse_operation_insert_becomes_delete() {
1503 let op: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Insert {
1504 values: vec![Value::Integer(1), Value::Text("alice".into())],
1505 indirect: false,
1506 };
1507 let reversed = op.reverse();
1508 let Operation::Delete { data, .. } = reversed else {
1509 panic!("Expected Delete operation");
1510 };
1511 assert_eq!(
1512 data,
1513 vec![
1514 Value::<String, Vec<u8>>::Integer(1),
1515 Value::Text("alice".into())
1516 ]
1517 );
1518 }
1519
1520 #[test]
1521 fn test_reverse_operation_delete_becomes_insert() {
1522 let op: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Delete {
1523 data: vec![Value::Integer(1), Value::Text("alice".into())],
1524 indirect: false,
1525 };
1526 let reversed = op.reverse();
1527 let Operation::Insert { values, .. } = reversed else {
1528 panic!("Expected Insert operation");
1529 };
1530 assert_eq!(
1531 values,
1532 vec![
1533 Value::<String, Vec<u8>>::Integer(1),
1534 Value::Text("alice".into())
1535 ]
1536 );
1537 }
1538
1539 #[test]
1540 fn test_reverse_operation_update_swaps_old_new() {
1541 let op: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Update {
1542 values: vec![
1543 (Some(Value::Integer(1)), Some(Value::Integer(1))),
1544 (
1545 Some(Value::Text("alice".into())),
1546 Some(Value::Text("bob".into())),
1547 ),
1548 ],
1549 indirect: false,
1550 };
1551 let reversed = op.reverse();
1552 let Operation::Update { values, .. } = reversed else {
1553 panic!("Expected Update operation");
1554 };
1555 assert_eq!(
1556 values[0],
1557 (Some(Value::Integer(1)), Some(Value::Integer(1)))
1558 );
1559 assert_eq!(
1560 values[1],
1561 (
1562 Some(Value::Text("bob".into())),
1563 Some(Value::Text("alice".into()))
1564 )
1565 );
1566 }
1567
1568 #[test]
1569 fn test_reverse_builder_insert_becomes_delete() {
1570 let table = TestTable::new("users", 2, 0);
1571 let insert = Insert::from(table.clone())
1572 .set(0, 1i64)
1573 .unwrap()
1574 .set(1, "alice")
1575 .unwrap();
1576
1577 let builder = ChangesetBuilder::new().insert(insert);
1578 let reversed = builder.reverse();
1579
1580 assert_eq!(reversed.len(), 1);
1581 let rows = reversed.tables.get(&table).unwrap();
1583 assert!(matches!(
1584 rows.values().next().unwrap(),
1585 Operation::Delete { .. }
1586 ));
1587 }
1588
1589 #[test]
1590 fn test_reverse_builder_delete_becomes_insert() {
1591 let table = TestTable::new("users", 2, 0);
1592 let delete = ChangeDelete::from(table.clone())
1593 .set(0, 1i64)
1594 .unwrap()
1595 .set(1, "alice")
1596 .unwrap();
1597
1598 let builder = ChangesetBuilder::new().delete(delete);
1599 let reversed = builder.reverse();
1600
1601 assert_eq!(reversed.len(), 1);
1602 let rows = reversed.tables.get(&table).unwrap();
1604 assert!(matches!(
1605 rows.values().next().unwrap(),
1606 Operation::Insert { .. }
1607 ));
1608 }
1609
1610 #[test]
1611 fn test_reverse_builder_update_swaps() {
1612 let table = TestTable::new("users", 2, 0);
1613 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
1614 .set(0, 1i64, 1i64)
1615 .unwrap()
1616 .set(1, "alice", "bob")
1617 .unwrap();
1618
1619 let builder = ChangesetBuilder::new().update(update);
1620 let reversed = builder.reverse();
1621
1622 assert_eq!(reversed.len(), 1);
1623 let rows = reversed.tables.get(&table).unwrap();
1625 let Operation::Update { values, .. } = rows.values().next().unwrap() else {
1626 panic!("Expected Update operation");
1627 };
1628 assert_eq!(
1629 values[1],
1630 (
1631 Some(Value::Text("bob".into())),
1632 Some(Value::Text("alice".into()))
1633 )
1634 );
1635 }
1636
1637 #[test]
1638 fn test_reverse_is_involutory() {
1639 let table = TestTable::new("users", 2, 0);
1641 let insert = Insert::from(table.clone())
1642 .set(0, 1i64)
1643 .unwrap()
1644 .set(1, "alice")
1645 .unwrap();
1646
1647 let original_values = insert.into_values();
1648 let insert2 = Insert::from(table.clone())
1649 .set(0, 1i64)
1650 .unwrap()
1651 .set(1, "alice")
1652 .unwrap();
1653 let builder = ChangesetBuilder::new().insert(insert2);
1654 let double_reversed = builder.reverse().reverse();
1655
1656 assert_eq!(double_reversed.len(), 1);
1657 let rows = double_reversed.tables.get(&table).unwrap();
1658 let Operation::Insert { values, .. } = rows.values().next().unwrap() else {
1659 panic!("Expected Insert operation");
1660 };
1661 assert_eq!(values, &original_values);
1662 }
1663
1664 #[test]
1669 fn test_build_empty_builder() {
1670 let builder = ChangesetBuilder::new();
1671 let bytes = builder.build();
1672 assert!(bytes.is_empty());
1673 }
1674
1675 #[test]
1676 fn test_build_insert_format() {
1677 let table = TestTable::new("t", 2, 0);
1678 let insert = Insert::from(table.clone())
1679 .set(0, 1i64)
1680 .unwrap()
1681 .set(1, "a")
1682 .unwrap();
1683
1684 let builder = ChangesetBuilder::new().insert(insert);
1685 let bytes = builder.build();
1686
1687 assert!(!bytes.is_empty());
1691
1692 assert_eq!(bytes[0], b'T');
1694 assert_eq!(bytes[1], 2);
1696 assert_eq!(bytes[2], 1);
1698 assert_eq!(bytes[3], 0);
1699 assert_eq!(bytes[4], b't');
1701 assert_eq!(bytes[5], 0);
1702 assert_eq!(bytes[6], 0x12);
1704 assert_eq!(bytes[7], 0);
1706 }
1707
1708 #[test]
1709 fn test_build_delete_format() {
1710 let table = TestTable::new("t", 2, 0);
1711 let delete = ChangeDelete::from(table.clone())
1712 .set(0, 1i64)
1713 .unwrap()
1714 .set(1, "a")
1715 .unwrap();
1716
1717 let builder = ChangesetBuilder::new().delete(delete);
1718 let bytes = builder.build();
1719
1720 assert!(!bytes.is_empty());
1721 assert_eq!(bytes[0], b'T');
1722 assert_eq!(bytes[6], 0x09);
1724 }
1725
1726 #[test]
1727 fn test_build_update_format() {
1728 let table = TestTable::new("t", 2, 0);
1729 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
1730 .set(0, 1i64, 1i64)
1731 .unwrap()
1732 .set(1, "a", "b")
1733 .unwrap();
1734
1735 let builder = ChangesetBuilder::new().update(update);
1736 let bytes = builder.build();
1737
1738 assert!(!bytes.is_empty());
1739 assert_eq!(bytes[0], b'T');
1740 assert_eq!(bytes[6], 0x17);
1742 }
1743
1744 #[test]
1745 fn test_build_multiple_operations() {
1746 let table = TestTable::new("t", 2, 0);
1747
1748 let insert = Insert::from(table.clone())
1749 .set(0, 1i64)
1750 .unwrap()
1751 .set(1, "a")
1752 .unwrap();
1753
1754 let insert2 = Insert::from(table.clone())
1755 .set(0, 2i64)
1756 .unwrap()
1757 .set(1, "b")
1758 .unwrap();
1759
1760 let builder = ChangesetBuilder::new().insert(insert).insert(insert2);
1761 let bytes = builder.build();
1762
1763 assert!(!bytes.is_empty());
1764 assert_eq!(bytes[0], b'T');
1766 }
1767
1768 #[test]
1769 fn test_build_cancelled_operations_produce_empty() {
1770 let table = TestTable::new("t", 2, 0);
1771
1772 let insert = Insert::from(table.clone())
1773 .set(0, 1i64)
1774 .unwrap()
1775 .set(1, "a")
1776 .unwrap();
1777
1778 let delete = ChangeDelete::from(table.clone())
1779 .set(0, 1i64)
1780 .unwrap()
1781 .set(1, "a")
1782 .unwrap();
1783
1784 let builder = ChangesetBuilder::new().insert(insert).delete(delete);
1785 let bytes = builder.build();
1786
1787 assert!(bytes.is_empty());
1789 }
1790
1791 #[test]
1796 fn test_bitor_changeset_disjoint_rows() {
1797 let table = TestTable::new("users", 2, 0);
1798
1799 let insert1 = Insert::from(table.clone())
1800 .set(0, 1i64)
1801 .unwrap()
1802 .set(1, "alice")
1803 .unwrap();
1804
1805 let insert2 = Insert::from(table.clone())
1806 .set(0, 2i64)
1807 .unwrap()
1808 .set(1, "bob")
1809 .unwrap();
1810
1811 let cs1 = ChangesetBuilder::new().insert(insert1);
1812 let cs2 = ChangesetBuilder::new().insert(insert2);
1813
1814 let merged = cs1 | cs2;
1815 assert_eq!(merged.len(), 2);
1816 }
1817
1818 #[test]
1819 fn test_bitor_changeset_consolidates_same_row() {
1820 let table = TestTable::new("users", 2, 0);
1821
1822 let insert = Insert::from(table.clone())
1824 .set(0, 1i64)
1825 .unwrap()
1826 .set(1, "alice")
1827 .unwrap();
1828
1829 let delete = ChangeDelete::from(table.clone())
1831 .set(0, 1i64)
1832 .unwrap()
1833 .set(1, "alice")
1834 .unwrap();
1835
1836 let cs1 = ChangesetBuilder::new().insert(insert);
1837 let cs2 = ChangesetBuilder::new().delete(delete);
1838
1839 let merged = cs1 | cs2;
1841 assert_eq!(merged.len(), 0);
1842 assert!(merged.is_empty());
1843 }
1844
1845 #[test]
1846 fn test_bitor_assign_changeset() {
1847 let table = TestTable::new("users", 2, 0);
1848
1849 let insert1 = Insert::from(table.clone())
1850 .set(0, 1i64)
1851 .unwrap()
1852 .set(1, "alice")
1853 .unwrap();
1854
1855 let insert2 = Insert::from(table.clone())
1856 .set(0, 2i64)
1857 .unwrap()
1858 .set(1, "bob")
1859 .unwrap();
1860
1861 let mut cs = ChangesetBuilder::new().insert(insert1);
1862 cs |= ChangesetBuilder::new().insert(insert2);
1863
1864 assert_eq!(cs.len(), 2);
1865 }
1866
1867 #[test]
1868 fn test_bitor_patchset_disjoint_rows() {
1869 type PatchsetBuilder = DiffSetBuilder<PatchsetFormat, TestTable, String, Vec<u8>>;
1870
1871 let table = TestTable::new("users", 2, 0);
1872
1873 let insert1 = Insert::from(table.clone())
1874 .set(0, 1i64)
1875 .unwrap()
1876 .set(1, "alice")
1877 .unwrap();
1878
1879 let insert2 = Insert::from(table.clone())
1880 .set(0, 2i64)
1881 .unwrap()
1882 .set(1, "bob")
1883 .unwrap();
1884
1885 let ps1 = PatchsetBuilder::new().insert(insert1);
1886 let ps2 = PatchsetBuilder::new().insert(insert2);
1887
1888 let merged = ps1 | ps2;
1889 assert_eq!(merged.len(), 2);
1890 }
1891
1892 #[test]
1893 fn test_bitor_patchset_consolidates_same_row() {
1894 type PatchsetBuilder = DiffSetBuilder<PatchsetFormat, TestTable, String, Vec<u8>>;
1895
1896 let table = TestTable::new("users", 2, 0);
1897
1898 let insert = Insert::from(table.clone())
1900 .set(0, 1i64)
1901 .unwrap()
1902 .set(1, "alice")
1903 .unwrap();
1904
1905 let delete = PatchDelete::new(table.clone(), vec![Value::Integer(1)]);
1907
1908 let ps1 = PatchsetBuilder::new().insert(insert);
1909 let ps2 = PatchsetBuilder::new().delete(delete);
1910
1911 let merged = ps1 | ps2;
1913 assert_eq!(merged.len(), 0);
1914 assert!(merged.is_empty());
1915 }
1916
1917 #[test]
1918 fn test_bitor_multiple_tables() {
1919 let table1 = TestTable::new("users", 2, 0);
1920 let table2 = TestTable::new("posts", 2, 0);
1921
1922 let insert1 = Insert::from(table1.clone())
1923 .set(0, 1i64)
1924 .unwrap()
1925 .set(1, "alice")
1926 .unwrap();
1927
1928 let insert2 = Insert::from(table2.clone())
1929 .set(0, 100i64)
1930 .unwrap()
1931 .set(1, "first post")
1932 .unwrap();
1933
1934 let cs1 = ChangesetBuilder::new().insert(insert1);
1935 let cs2 = ChangesetBuilder::new().insert(insert2);
1936
1937 let merged = cs1 | cs2;
1938 assert_eq!(merged.len(), 2);
1939 }
1940
1941 #[test]
1942 fn test_bitor_insert_then_update_consolidates() {
1943 let table = TestTable::new("users", 2, 0);
1944
1945 let insert = Insert::from(table.clone())
1947 .set(0, 1i64)
1948 .unwrap()
1949 .set(1, "alice")
1950 .unwrap();
1951
1952 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
1954 .set(0, 1i64, 1i64)
1955 .unwrap()
1956 .set(1, "alice", "alicia")
1957 .unwrap();
1958
1959 let cs1 = ChangesetBuilder::new().insert(insert);
1960 let cs2 = ChangesetBuilder::new().update(update);
1961
1962 let merged = cs1 | cs2;
1964 assert_eq!(merged.len(), 1);
1965 }
1966
1967 #[test]
1968 fn test_session_hash_growth_with_many_rows() {
1969 let table = TestTable::new("many", 2, 0);
1973 let mut cs = ChangesetBuilder::new();
1974 for i in 0..200i64 {
1975 let insert = Insert::from(table.clone())
1976 .set(0, i)
1977 .unwrap()
1978 .set(1, alloc::format!("row-{i}"))
1979 .unwrap();
1980 cs = cs.insert(insert);
1981 }
1982 assert_eq!(cs.len(), 200);
1983
1984 let bytes = cs.build();
1985 assert!(!bytes.is_empty());
1986
1987 let reparsed = crate::parser::ParsedDiffSet::try_from(bytes.as_slice()).unwrap();
1989 let reparsed_bytes: Vec<u8> = reparsed.into();
1990 assert_eq!(bytes, reparsed_bytes);
1991 }
1992
1993 const INDIRECT_BYTE_OFFSET: usize = 7;
2001
2002 #[test]
2003 fn test_build_insert_indirect_byte_set() {
2004 let table = TestTable::new("t", 2, 0);
2005 let insert = Insert::from(table)
2006 .set(0, 1i64)
2007 .unwrap()
2008 .set(1, "a")
2009 .unwrap()
2010 .indirect(true);
2011
2012 let bytes = ChangesetBuilder::new().insert(insert).build();
2013 assert_eq!(bytes[INDIRECT_BYTE_OFFSET], 1);
2014 }
2015
2016 #[test]
2017 fn test_build_delete_indirect_byte_set() {
2018 let table = TestTable::new("t", 2, 0);
2019 let delete = ChangeDelete::from(table)
2020 .set(0, 1i64)
2021 .unwrap()
2022 .set(1, "a")
2023 .unwrap()
2024 .indirect(true);
2025
2026 let bytes = ChangesetBuilder::new().delete(delete).build();
2027 assert_eq!(bytes[INDIRECT_BYTE_OFFSET], 1);
2028 }
2029
2030 #[test]
2031 fn test_build_update_indirect_byte_set() {
2032 let table = TestTable::new("t", 2, 0);
2033 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table)
2034 .set(0, 1i64, 1i64)
2035 .unwrap()
2036 .set(1, "a", "b")
2037 .unwrap()
2038 .indirect(true);
2039
2040 let bytes = ChangesetBuilder::new().update(update).build();
2041 assert_eq!(bytes[INDIRECT_BYTE_OFFSET], 1);
2042 }
2043
2044 #[test]
2045 fn test_build_patchset_insert_indirect_byte() {
2046 let table = TestTable::new("t", 2, 0);
2047 let insert = Insert::from(table)
2048 .set(0, 1i64)
2049 .unwrap()
2050 .set(1, "a")
2051 .unwrap()
2052 .indirect(true);
2053
2054 let patchset: PatchSet<TestTable, String, Vec<u8>> = PatchSet::new().insert(insert);
2055 let bytes = patchset.build();
2056 assert_eq!(bytes[INDIRECT_BYTE_OFFSET], 1);
2057 }
2058
2059 #[test]
2060 fn test_reverse_preserves_indirect() {
2061 let insert: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Insert {
2063 values: vec![Value::Integer(1)],
2064 indirect: true,
2065 };
2066 let reversed = insert.reverse();
2067 assert!(reversed.indirect());
2068 assert!(matches!(reversed, Operation::Delete { .. }));
2069
2070 let delete: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Delete {
2072 data: vec![Value::Integer(1)],
2073 indirect: true,
2074 };
2075 let reversed = delete.reverse();
2076 assert!(reversed.indirect());
2077 assert!(matches!(reversed, Operation::Insert { .. }));
2078
2079 let update: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Update {
2081 values: vec![(Some(Value::Integer(1)), Some(Value::Integer(2)))],
2082 indirect: true,
2083 };
2084 let reversed = update.reverse();
2085 assert!(reversed.indirect());
2086 assert!(matches!(reversed, Operation::Update { .. }));
2087 }
2088
2089 #[test]
2090 fn test_patchdelete_indirect_byte_set() {
2091 let table = TestTable::new("t", 2, 0);
2092 let delete: PatchDelete<TestTable, String, Vec<u8>> =
2093 PatchDelete::new(table, vec![Value::Integer(1)]).indirect(true);
2094 let bytes = PatchSet::new().delete(delete).build();
2095 assert_eq!(bytes[INDIRECT_BYTE_OFFSET], 1);
2096 }
2097
2098 #[test]
2099 fn test_operation_eq_indirect_differs() {
2100 let a: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Insert {
2102 values: vec![Value::Integer(1)],
2103 indirect: false,
2104 };
2105 let b: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Insert {
2106 values: vec![Value::Integer(1)],
2107 indirect: true,
2108 };
2109 assert_ne!(a, b);
2110 }
2111
2112 #[test]
2113 fn test_operation_eq_variant_mismatch() {
2114 let insert: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Insert {
2116 values: vec![Value::Integer(1)],
2117 indirect: false,
2118 };
2119 let delete: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Delete {
2120 data: vec![Value::Integer(1)],
2121 indirect: false,
2122 };
2123 assert_ne!(insert, delete);
2124 }
2125
2126 #[test]
2127 fn test_bitor_indirect_rhs_wins() {
2128 let table = TestTable::new("t", 2, 0);
2131 let lhs_insert = Insert::from(table.clone())
2132 .set(0, 1i64)
2133 .unwrap()
2134 .set(1, "a")
2135 .unwrap()
2136 .indirect(true);
2137 let rhs_insert = Insert::from(table.clone())
2138 .set(0, 1i64)
2139 .unwrap()
2140 .set(1, "a")
2141 .unwrap()
2142 .indirect(false);
2143
2144 let merged =
2145 ChangesetBuilder::new().insert(lhs_insert) | ChangesetBuilder::new().insert(rhs_insert);
2146 let rows = merged.tables.get(&table).unwrap();
2147 let op = rows.values().next().unwrap();
2148 assert!(!op.indirect(), "rhs (false) should win over lhs (true)");
2149
2150 let lhs_insert = Insert::from(table.clone())
2152 .set(0, 2i64)
2153 .unwrap()
2154 .set(1, "b")
2155 .unwrap()
2156 .indirect(false);
2157 let rhs_insert = Insert::from(table.clone())
2158 .set(0, 2i64)
2159 .unwrap()
2160 .set(1, "b")
2161 .unwrap()
2162 .indirect(true);
2163
2164 let merged =
2165 ChangesetBuilder::new().insert(lhs_insert) | ChangesetBuilder::new().insert(rhs_insert);
2166 let rows = merged.tables.get(&table).unwrap();
2167 let op = rows.values().next().unwrap();
2168 assert!(op.indirect(), "rhs (true) should win over lhs (false)");
2169 }
2170
2171 #[test]
2172 fn test_roundtrip_indirect_changeset() {
2173 let table = TestTable::new("t", 2, 0);
2174 let insert = Insert::from(table)
2175 .set(0, 1i64)
2176 .unwrap()
2177 .set(1, "a")
2178 .unwrap()
2179 .indirect(true);
2180
2181 let bytes = ChangesetBuilder::new().insert(insert).build();
2182 let reparsed = crate::parser::ParsedDiffSet::try_from(bytes.as_slice()).unwrap();
2183 let reparsed_bytes: Vec<u8> = reparsed.into();
2184 assert_eq!(bytes, reparsed_bytes);
2185 assert_eq!(reparsed_bytes[INDIRECT_BYTE_OFFSET], 1);
2186 }
2187
2188 #[test]
2189 fn test_roundtrip_indirect_patchset() {
2190 let table = TestTable::new("t", 2, 0);
2191 let insert = Insert::from(table)
2192 .set(0, 1i64)
2193 .unwrap()
2194 .set(1, "a")
2195 .unwrap()
2196 .indirect(true);
2197
2198 let patchset: PatchSet<TestTable, String, Vec<u8>> = PatchSet::new().insert(insert);
2199 let bytes = patchset.build();
2200 let reparsed = crate::parser::ParsedDiffSet::try_from(bytes.as_slice()).unwrap();
2201 let reparsed_bytes: Vec<u8> = reparsed.into();
2202 assert_eq!(bytes, reparsed_bytes);
2203 assert_eq!(reparsed_bytes[INDIRECT_BYTE_OFFSET], 1);
2204 }
2205
2206 #[test]
2207 fn test_indirect_full_pipeline_roundtrip() {
2208 let table = TestTable::new("t", 2, 0);
2211 let insert = Insert::from(table)
2212 .set(0, 1i64)
2213 .unwrap()
2214 .set(1, "alice")
2215 .unwrap()
2216 .indirect(true);
2217 let original = ChangesetBuilder::new().insert(insert);
2218 let bytes_a = original.build();
2219 assert_eq!(bytes_a[INDIRECT_BYTE_OFFSET], 1);
2220
2221 let parsed = crate::parser::ParsedDiffSet::try_from(bytes_a.as_slice()).unwrap();
2223 let crate::parser::ParsedDiffSet::Changeset(parsed_set) = parsed else {
2224 panic!("expected Changeset variant");
2225 };
2226
2227 let parsed_builder: DiffSetBuilder<
2228 ChangesetFormat,
2229 crate::parser::TableSchema<String>,
2230 String,
2231 Vec<u8>,
2232 > = parsed_set.into();
2233 let empty: DiffSetBuilder<
2234 ChangesetFormat,
2235 crate::parser::TableSchema<String>,
2236 String,
2237 Vec<u8>,
2238 > = DiffSetBuilder::new();
2239 let doubled = parsed_builder.reverse().reverse();
2240 let merged = doubled | empty;
2241
2242 let bytes_b = merged.build();
2243 assert_eq!(bytes_a, bytes_b);
2244 assert_eq!(bytes_b[INDIRECT_BYTE_OFFSET], 1);
2245 }
2246
2247 fn changeset_insert(v: i64) -> Operation<ChangesetFormat, String, Vec<u8>> {
2252 Operation::Insert {
2253 values: vec![Value::Integer(v), Value::Text("a".into())],
2254 indirect: false,
2255 }
2256 }
2257
2258 fn changeset_delete(v: i64) -> Operation<ChangesetFormat, String, Vec<u8>> {
2259 Operation::Delete {
2260 data: vec![Value::Integer(v), Value::Text("a".into())],
2261 indirect: false,
2262 }
2263 }
2264
2265 fn changeset_update(old: i64, new: i64) -> Operation<ChangesetFormat, String, Vec<u8>> {
2266 Operation::Update {
2267 values: vec![
2268 (Some(Value::Integer(old)), Some(Value::Integer(new))),
2269 (Some(Value::Text("a".into())), Some(Value::Text("b".into()))),
2270 ],
2271 indirect: false,
2272 }
2273 }
2274
2275 #[test]
2276 fn test_add_changeset_insert_plus_update() {
2277 let merged = (changeset_insert(1) + changeset_update(1, 2)).unwrap();
2278 let Operation::Insert { values, .. } = merged else {
2279 panic!("expected Insert");
2280 };
2281 assert_eq!(values[0], Value::Integer(2));
2282 assert_eq!(values[1], Value::Text("b".into()));
2283 }
2284
2285 #[test]
2286 fn test_add_changeset_update_plus_insert() {
2287 let merged = (changeset_update(1, 2) + changeset_insert(99)).unwrap();
2289 assert!(matches!(merged, Operation::Update { .. }));
2290 }
2291
2292 #[test]
2293 fn test_add_changeset_update_plus_update() {
2294 let merged = (changeset_update(1, 2) + changeset_update(2, 3)).unwrap();
2296 let Operation::Update { values, .. } = merged else {
2297 panic!("expected Update");
2298 };
2299 assert_eq!(values[0].0, Some(Value::Integer(1)));
2300 assert_eq!(values[0].1, Some(Value::Integer(3)));
2301 }
2302
2303 #[test]
2304 fn test_add_changeset_update_plus_delete() {
2305 let merged = (changeset_update(1, 2) + changeset_delete(99)).unwrap();
2307 let Operation::Delete { data, .. } = merged else {
2308 panic!("expected Delete");
2309 };
2310 assert_eq!(data[0], Value::Integer(1));
2311 assert_eq!(data[1], Value::Text("a".into()));
2312 }
2313
2314 #[test]
2315 fn test_add_changeset_delete_plus_update_keeps_delete() {
2316 let merged = (changeset_delete(1) + changeset_update(1, 2)).unwrap();
2317 assert!(matches!(merged, Operation::Delete { .. }));
2318 }
2319
2320 #[test]
2321 fn test_add_changeset_delete_plus_delete_keeps_first() {
2322 let merged = (changeset_delete(1) + changeset_delete(2)).unwrap();
2323 let Operation::Delete { data, .. } = merged else {
2324 panic!("expected Delete");
2325 };
2326 assert_eq!(data[0], Value::Integer(1));
2327 }
2328
2329 fn patchset_insert(v: i64) -> Operation<PatchsetFormat, String, Vec<u8>> {
2330 Operation::Insert {
2331 values: vec![Value::Integer(v), Value::Text("a".into())],
2332 indirect: false,
2333 }
2334 }
2335
2336 fn patchset_update(new: i64) -> Operation<PatchsetFormat, String, Vec<u8>> {
2337 Operation::Update {
2338 values: vec![
2339 ((), Some(Value::Integer(new))),
2340 ((), Some(Value::Text("b".into()))),
2341 ],
2342 indirect: false,
2343 }
2344 }
2345
2346 fn patchset_delete() -> Operation<PatchsetFormat, String, Vec<u8>> {
2347 Operation::Delete {
2348 data: (),
2349 indirect: false,
2350 }
2351 }
2352
2353 #[test]
2354 fn test_add_patchset_insert_plus_update() {
2355 let merged = (patchset_insert(1) + patchset_update(2)).unwrap();
2356 let Operation::Insert { values, .. } = merged else {
2357 panic!("expected Insert");
2358 };
2359 assert_eq!(values[0], Value::Integer(2));
2360 assert_eq!(values[1], Value::Text("b".into()));
2361 }
2362
2363 #[test]
2364 fn test_add_patchset_update_plus_insert() {
2365 let merged = (patchset_update(2) + patchset_insert(99)).unwrap();
2366 assert!(matches!(merged, Operation::Update { .. }));
2367 }
2368
2369 #[test]
2370 fn test_add_patchset_update_plus_update() {
2371 let merged = (patchset_update(2) + patchset_update(3)).unwrap();
2372 let Operation::Update { values, .. } = merged else {
2373 panic!("expected Update");
2374 };
2375 assert_eq!(values[0].1, Some(Value::Integer(3)));
2376 }
2377
2378 #[test]
2379 fn test_add_patchset_update_plus_delete() {
2380 let merged = (patchset_update(2) + patchset_delete()).unwrap();
2381 assert!(matches!(merged, Operation::Delete { .. }));
2382 }
2383
2384 #[test]
2385 fn test_add_patchset_delete_plus_insert_promotes_to_update() {
2386 let merged = (patchset_delete() + patchset_insert(1)).unwrap();
2388 assert!(matches!(merged, Operation::Update { .. }));
2389 }
2390
2391 #[test]
2392 fn test_add_patchset_delete_plus_update_keeps_delete() {
2393 let merged = (patchset_delete() + patchset_update(2)).unwrap();
2394 assert!(matches!(merged, Operation::Delete { .. }));
2395 }
2396
2397 #[test]
2398 fn test_add_patchset_delete_plus_delete_keeps_first() {
2399 let merged = (patchset_delete() + patchset_delete()).unwrap();
2400 assert!(matches!(merged, Operation::Delete { .. }));
2401 }
2402
2403 #[test]
2408 fn test_session_hash_real_pk() {
2409 let table = TestTable::new("t", 2, 0);
2410 let insert = Insert::from(table)
2411 .set(0, 2.5f64)
2412 .unwrap()
2413 .set(1, "a")
2414 .unwrap();
2415 let bytes = ChangesetBuilder::new().insert(insert).build();
2416 assert!(!bytes.is_empty());
2417 }
2418
2419 #[test]
2420 fn test_session_hash_text_pk() {
2421 let table = TestTable::new("t", 2, 0);
2422 let insert = Insert::from(table)
2423 .set(0, "alice")
2424 .unwrap()
2425 .set(1, 42i64)
2426 .unwrap();
2427 let bytes = ChangesetBuilder::new().insert(insert).build();
2428 assert!(!bytes.is_empty());
2429 }
2430
2431 #[test]
2432 fn test_session_hash_blob_pk() {
2433 let table = TestTable::new("t", 2, 0);
2434 let insert = Insert::from(table)
2435 .set(0, alloc::vec![0xDE_u8, 0xAD, 0xBE, 0xEF])
2436 .unwrap()
2437 .set(1, "a")
2438 .unwrap();
2439 let bytes = ChangesetBuilder::new().insert(insert).build();
2440 assert!(!bytes.is_empty());
2441 }
2442
2443 #[test]
2448 fn test_session_row_order_empty() {
2449 let cs: ChangesetBuilder = ChangesetBuilder::new();
2451 let bytes = cs.build();
2452 assert!(bytes.is_empty());
2453 }
2454
2455 #[test]
2456 fn test_session_row_order_empty_rows_returns_empty_vec() {
2457 let rows: RowMap<ChangesetFormat, String, Vec<u8>> = IndexMap::default();
2459 assert!(session_row_order(&rows).is_empty());
2460 }
2461
2462 #[test]
2463 fn test_diffset_patchset_build_skips_empty_table() {
2464 let table = TestTable::new("t", 2, 0);
2466 let mut builder: PatchSet<TestTable, String, Vec<u8>> = PatchSet::new();
2467 builder.add_table(&table);
2468 let frozen: DiffSet<PatchsetFormat, TestTable, String, Vec<u8>> = builder.into();
2469 let bytes = frozen.build();
2470 assert!(bytes.is_empty());
2471 }
2472
2473 #[test]
2478 fn test_from_changeset_builder_into_vec() {
2479 let table = TestTable::new("t", 2, 0);
2480 let insert = Insert::from(table)
2481 .set(0, 1i64)
2482 .unwrap()
2483 .set(1, "a")
2484 .unwrap();
2485 let builder = ChangesetBuilder::new().insert(insert);
2486 let bytes_owned: Vec<u8> = builder.clone().into();
2487 let bytes_ref: Vec<u8> = (&builder).into();
2488 assert_eq!(bytes_owned, bytes_ref);
2489 let frozen: DiffSet<ChangesetFormat, TestTable, String, Vec<u8>> = builder.into();
2490 let bytes_frozen_owned: Vec<u8> = frozen.clone().into();
2491 let bytes_frozen_ref: Vec<u8> = (&frozen).into();
2492 assert_eq!(bytes_frozen_owned, bytes_frozen_ref);
2493 }
2494
2495 #[test]
2496 fn test_from_patchset_builder_into_vec() {
2497 let table = TestTable::new("t", 2, 0);
2498 let insert = Insert::from(table)
2499 .set(0, 1i64)
2500 .unwrap()
2501 .set(1, "a")
2502 .unwrap();
2503 let builder: PatchSet<TestTable, String, Vec<u8>> = PatchSet::new().insert(insert);
2504 let bytes_owned: Vec<u8> = builder.clone().into();
2505 let bytes_ref: Vec<u8> = (&builder).into();
2506 assert_eq!(bytes_owned, bytes_ref);
2507 let frozen: DiffSet<PatchsetFormat, TestTable, String, Vec<u8>> = builder.into();
2508 let bytes_frozen_owned: Vec<u8> = frozen.clone().into();
2509 let bytes_frozen_ref: Vec<u8> = (&frozen).into();
2510 assert_eq!(bytes_frozen_owned, bytes_frozen_ref);
2511 }
2512
2513 #[test]
2518 fn test_add_operation_insert_then_update_changes_pk() {
2519 let table = TestTable::new("t", 2, 0);
2522 let insert = Insert::from(table.clone())
2523 .set(0, 1i64)
2524 .unwrap()
2525 .set(1, "alice")
2526 .unwrap();
2527 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
2528 .set(0, 1i64, 2i64)
2529 .unwrap()
2530 .set(1, "alice", "bob")
2531 .unwrap();
2532 let builder = ChangesetBuilder::new().insert(insert).update(update);
2533 let rows = builder.tables.get(&table).unwrap();
2534 assert_eq!(rows.len(), 1);
2535 let (pk, op) = rows.iter().next().unwrap();
2537 assert_eq!(pk[0], Value::Integer(2));
2538 let Operation::Insert { values, .. } = op else {
2539 panic!("expected merged INSERT");
2540 };
2541 assert_eq!(values[0], Value::Integer(2));
2542 assert_eq!(values[1], Value::Text("bob".into()));
2543 }
2544
2545 #[test]
2550 fn test_diffset_changeset_diffops_wrappers() {
2551 let table = TestTable::new("t", 2, 0);
2552 let initial = Insert::from(table.clone())
2553 .set(0, 1i64)
2554 .unwrap()
2555 .set(1, "a")
2556 .unwrap();
2557 let frozen: DiffSet<ChangesetFormat, TestTable, String, Vec<u8>> =
2558 ChangesetBuilder::new().insert(initial).into();
2559
2560 let insert2 = Insert::from(table.clone())
2561 .set(0, 2i64)
2562 .unwrap()
2563 .set(1, "b")
2564 .unwrap();
2565 let after_insert = <_ as DiffOps<_, _, _>>::insert(frozen.clone(), insert2);
2566 assert_eq!(after_insert.len(), 2);
2567
2568 let delete = ChangeDelete::from(table.clone())
2569 .set(0, 1i64)
2570 .unwrap()
2571 .set(1, "a")
2572 .unwrap();
2573 let after_delete = <_ as DiffOps<_, _, _>>::delete(frozen.clone(), delete);
2574 assert_eq!(after_delete.len(), 0);
2575
2576 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table)
2577 .set(0, 1i64, 1i64)
2578 .unwrap()
2579 .set(1, "a", "z")
2580 .unwrap();
2581 let after_update = <_ as DiffOps<_, _, _>>::update(frozen, update);
2582 assert_eq!(after_update.len(), 1);
2583 }
2584
2585 #[test]
2586 fn test_diffset_patchset_diffops_wrappers() {
2587 let table = TestTable::new("t", 2, 0);
2588 let initial = Insert::from(table.clone())
2589 .set(0, 1i64)
2590 .unwrap()
2591 .set(1, "a")
2592 .unwrap();
2593 let frozen: DiffSet<PatchsetFormat, TestTable, String, Vec<u8>> =
2594 PatchSet::new().insert(initial).into();
2595
2596 let insert2 = Insert::from(table.clone())
2597 .set(0, 2i64)
2598 .unwrap()
2599 .set(1, "b")
2600 .unwrap();
2601 let after_insert = <_ as DiffOps<_, _, _>>::insert(frozen.clone(), insert2);
2602 assert_eq!(after_insert.len(), 2);
2603
2604 let delete: PatchDelete<TestTable, String, Vec<u8>> =
2605 PatchDelete::new(table.clone(), vec![Value::Integer(1)]);
2606 let after_delete = <_ as DiffOps<_, _, _>>::delete(frozen.clone(), delete);
2607 assert_eq!(after_delete.len(), 0);
2609
2610 let update = Update::<TestTable, PatchsetFormat, String, Vec<u8>>::from(table)
2611 .set(0, 1i64)
2612 .unwrap()
2613 .set(1, "z")
2614 .unwrap();
2615 let after_update = <_ as DiffOps<_, _, _>>::update(frozen, update);
2616 assert_eq!(after_update.len(), 1);
2617 }
2618
2619 #[test]
2620 fn test_diffset_changeset_iter_yields_inserts_and_indirect_flag() {
2621 let table = TestTable::new("t", 2, 0);
2622 let direct = Insert::from(table.clone()).set(0, 1i64).unwrap();
2623 let indirect = Insert::from(table.clone())
2624 .set(0, 2i64)
2625 .unwrap()
2626 .indirect(true);
2627 let frozen: DiffSet<ChangesetFormat, TestTable, String, Vec<u8>> = ChangesetBuilder::new()
2628 .insert(direct)
2629 .insert(indirect)
2630 .into();
2631
2632 let ops: Vec<_> = frozen.iter().collect();
2633 assert_eq!(ops.len(), 2);
2634 for op in &ops {
2635 assert_eq!(crate::DynTable::name(op.table()), "t");
2636 }
2637 assert!(matches!(
2638 ops[0],
2639 ChangesetOp::Insert {
2640 indirect: false,
2641 ..
2642 }
2643 ));
2644 assert!(matches!(ops[1], ChangesetOp::Insert { indirect: true, .. }));
2645 }
2646
2647 #[test]
2648 fn test_diffset_changeset_iter_yields_update_and_delete() {
2649 let table = TestTable::new("t", 2, 0);
2650 let starting = Insert::from(table.clone())
2651 .set(0, 1i64)
2652 .unwrap()
2653 .set(1, "a")
2654 .unwrap();
2655 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
2656 .set(0, 2i64, 2i64)
2657 .unwrap()
2658 .set(1, "before", "after")
2659 .unwrap();
2660 let delete = ChangeDelete::from(table.clone())
2661 .set(0, 3i64)
2662 .unwrap()
2663 .set(1, "gone")
2664 .unwrap();
2665 let frozen: DiffSet<ChangesetFormat, TestTable, String, Vec<u8>> = ChangesetBuilder::new()
2666 .insert(starting)
2667 .update(update)
2668 .delete(delete)
2669 .into();
2670
2671 let kinds: Vec<&'static str> = frozen
2672 .iter()
2673 .map(|op| match op {
2674 ChangesetOp::Insert { .. } => "insert",
2675 ChangesetOp::Update { .. } => "update",
2676 ChangesetOp::Delete { .. } => "delete",
2677 })
2678 .collect();
2679 assert_eq!(kinds, ["insert", "update", "delete"]);
2680 }
2681
2682 #[test]
2683 fn test_diffset_patchset_iter_exposes_pk_for_delete_and_update() {
2684 let table = TestTable::new("t", 2, 0);
2685 let starting = Insert::from(table.clone())
2686 .set(0, 1i64)
2687 .unwrap()
2688 .set(1, "a")
2689 .unwrap();
2690 let update = Update::<TestTable, PatchsetFormat, String, Vec<u8>>::from(table.clone())
2691 .set(0, 5i64)
2692 .unwrap()
2693 .set(1, "z")
2694 .unwrap();
2695 let delete: PatchDelete<TestTable, String, Vec<u8>> =
2696 PatchDelete::new(table.clone(), vec![Value::Integer(7)]);
2697
2698 let frozen: DiffSet<PatchsetFormat, TestTable, String, Vec<u8>> = PatchSet::new()
2699 .insert(starting)
2700 .update(update)
2701 .delete(delete)
2702 .into();
2703
2704 let mut saw_insert = false;
2705 let mut saw_update_pk: Option<i64> = None;
2706 let mut saw_delete_pk: Option<i64> = None;
2707 for op in frozen.iter() {
2708 match op {
2709 PatchsetOp::Insert { values, .. } => {
2710 saw_insert = true;
2711 assert!(matches!(values[0], Value::Integer(1)));
2712 }
2713 PatchsetOp::Update { pk, .. } => {
2714 if let Value::Integer(id) = pk[0] {
2715 saw_update_pk = Some(id);
2716 }
2717 }
2718 PatchsetOp::Delete { pk, .. } => {
2719 if let Value::Integer(id) = pk[0] {
2720 saw_delete_pk = Some(id);
2721 }
2722 }
2723 }
2724 }
2725 assert!(saw_insert);
2726 assert_eq!(saw_update_pk, Some(5));
2727 assert_eq!(saw_delete_pk, Some(7));
2728 }
2729
2730 #[test]
2731 fn test_diffset_tables_skips_empty() {
2732 let t1 = TestTable::new("t1", 2, 0);
2733 let t2 = TestTable::new("t2", 2, 0);
2734 let insert_t1 = Insert::from(t1.clone()).set(0, 1i64).unwrap();
2735 let insert_then_delete_t2 = Insert::from(t2.clone()).set(0, 9i64).unwrap();
2736 let delete_t2 = ChangeDelete::from(t2.clone()).set(0, 9i64).unwrap();
2737 let frozen: DiffSet<ChangesetFormat, TestTable, String, Vec<u8>> = ChangesetBuilder::new()
2738 .insert(insert_t1)
2739 .insert(insert_then_delete_t2)
2740 .delete(delete_t2)
2741 .into();
2742
2743 let names: Vec<&str> = frozen.tables().map(crate::DynTable::name).collect();
2744 assert_eq!(names, ["t1"]);
2745 }
2746}