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 + Debug + AsRef<str>, B: Clone + Debug + AsRef<[u8]>>
864 DiffSetBuilder<ChangesetFormat, T, S, B>
865{
866 pub fn iter(&self) -> impl Iterator<Item = ChangesetOp<'_, T, S, B>> {
872 self.tables.iter().flat_map(|(table, rows)| {
873 rows.iter().map(move |(_pk, op)| match op {
874 Operation::Insert { values, indirect } => ChangesetOp::Insert {
875 table,
876 values: values.as_slice(),
877 indirect: *indirect,
878 },
879 Operation::Update { values, indirect } => ChangesetOp::Update {
880 table,
881 values: values.as_slice(),
882 indirect: *indirect,
883 },
884 Operation::Delete { data, indirect } => ChangesetOp::Delete {
885 table,
886 old_values: data.as_slice(),
887 indirect: *indirect,
888 },
889 })
890 })
891 }
892}
893
894impl<T: SchemaWithPK, S: Clone + Hash + Eq + AsRef<str>, B: Clone + Hash + Eq + AsRef<[u8]>>
895 DiffSetBuilder<PatchsetFormat, T, S, B>
896{
897 #[must_use]
906 pub fn build(&self) -> Vec<u8> {
907 let mut out = Vec::new();
908
909 for (table, rows) in &self.tables {
910 if rows.is_empty() {
911 continue;
912 }
913
914 write_table_header(&mut out, markers::PATCHSET, table);
915
916 let (pk_flags, pk_col_to_pk_pos) = patchset_pk_mapping(table);
917
918 for idx in session_row_order(rows) {
919 let (pk, op) = rows.get_index(idx).unwrap();
920 encode_patchset_op(&mut out, op, pk, &pk_flags, &pk_col_to_pk_pos);
921 }
922 }
923
924 out
925 }
926}
927
928impl<T: SchemaWithPK, S: Clone + AsRef<str>, B: Clone + AsRef<[u8]>>
929 DiffSetBuilder<PatchsetFormat, T, S, B>
930{
931 pub fn iter(&self) -> impl Iterator<Item = PatchsetOp<'_, T, S, B>> {
939 self.tables.iter().flat_map(|(table, rows)| {
940 rows.iter().map(move |(pk, op)| match op {
941 Operation::Insert { values, indirect } => PatchsetOp::Insert {
942 table,
943 values: values.as_slice(),
944 indirect: *indirect,
945 },
946 Operation::Update { values, indirect } => PatchsetOp::Update {
947 table,
948 pk: pk.as_slice(),
949 entries: values.as_slice(),
950 indirect: *indirect,
951 },
952 Operation::Delete { indirect, .. } => PatchsetOp::Delete {
953 table,
954 pk: pk.as_slice(),
955 indirect: *indirect,
956 },
957 })
958 })
959 }
960}
961
962use crate::builders::operation::Reverse;
967
968impl<
969 T: SchemaWithPK,
970 S: Clone + Debug + Hash + Eq + AsRef<str>,
971 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
972> Reverse for DiffSetBuilder<ChangesetFormat, T, S, B>
973{
974 type Output = DiffSetBuilder<ChangesetFormat, T, S, B>;
975
976 fn reverse(self) -> Self::Output {
977 let mut reversed: DiffSetBuilder<ChangesetFormat, T, S, B> = DiffSetBuilder::new();
978
979 for (table, rows) in self.tables {
980 for (pk, op) in rows {
981 let rev_op = op.reverse();
982
983 reversed.add_operation(&table, pk, rev_op);
984 }
985 }
986
987 reversed
988 }
989}
990
991impl<
997 F: Format<S, B>,
998 T: SchemaWithPK,
999 S: Clone + Hash + Eq + AsRef<str>,
1000 B: Clone + Hash + Eq + AsRef<[u8]>,
1001> BitOrAssign for DiffSetBuilder<F, T, S, B>
1002where
1003 Operation<F, S, B>: core::ops::Add<Output = Option<Operation<F, S, B>>>,
1004{
1005 fn bitor_assign(&mut self, rhs: Self) {
1007 for (table, rows) in rhs.tables {
1008 for (pk, op) in rows {
1009 self.add_operation(&table, pk, op);
1010 }
1011 }
1012 }
1013}
1014
1015impl<
1016 F: Format<S, B>,
1017 T: SchemaWithPK,
1018 S: Clone + Hash + Eq + AsRef<str>,
1019 B: Clone + Hash + Eq + AsRef<[u8]>,
1020> BitOr for DiffSetBuilder<F, T, S, B>
1021where
1022 Operation<F, S, B>: core::ops::Add<Output = Option<Operation<F, S, B>>>,
1023{
1024 type Output = Self;
1025
1026 #[inline]
1028 fn bitor(mut self, rhs: Self) -> Self::Output {
1029 self |= rhs;
1030 self
1031 }
1032}
1033
1034#[derive(Debug, Clone)]
1052pub struct DiffSet<F: Format<S, B>, T: SchemaWithPK, S, B> {
1053 pub(crate) tables: TableVec<F, T, S, B>,
1055}
1056
1057impl<F: Format<S, B>, T: SchemaWithPK, S, B> PartialEq for DiffSet<F, T, S, B>
1060where
1061 S: PartialEq + Eq + Hash + AsRef<str>,
1062 B: PartialEq + Eq + Hash + AsRef<[u8]>,
1063 F::Old: PartialEq,
1064 F::DeleteData: PartialEq,
1065{
1066 fn eq(&self, other: &Self) -> bool {
1067 self.tables
1068 .iter()
1069 .filter(|(_, ops)| !ops.is_empty())
1070 .eq(other.tables.iter().filter(|(_, ops)| !ops.is_empty()))
1071 }
1072}
1073
1074impl<F: Format<S, B>, T: SchemaWithPK, S, B> Eq for DiffSet<F, T, S, B>
1075where
1076 S: Eq + Hash + AsRef<str>,
1077 B: Eq + Hash + AsRef<[u8]>,
1078 F::Old: Eq,
1079 F::DeleteData: Eq,
1080{
1081}
1082
1083impl<F: Format<S, B>, T: SchemaWithPK, S: AsRef<str> + Hash + Eq, B: AsRef<[u8]> + Hash + Eq>
1084 Default for DiffSet<F, T, S, B>
1085{
1086 fn default() -> Self {
1087 Self { tables: Vec::new() }
1088 }
1089}
1090
1091impl<F: Format<S, B>, T: SchemaWithPK, S: AsRef<str> + Hash + Eq, B: AsRef<[u8]> + Hash + Eq>
1092 DiffSet<F, T, S, B>
1093{
1094 #[inline]
1096 #[must_use]
1097 pub fn is_empty(&self) -> bool {
1098 self.tables.iter().all(|(_, rows)| rows.is_empty())
1099 }
1100
1101 #[inline]
1103 #[must_use]
1104 pub fn len(&self) -> usize {
1105 self.tables.iter().map(|(_, rows)| rows.len()).sum()
1106 }
1107}
1108
1109impl<F: Format<S, B>, T: SchemaWithPK, S: AsRef<str> + Hash + Eq, B: AsRef<[u8]> + Hash + Eq>
1112 DiffSet<F, T, S, B>
1113{
1114 pub fn tables(&self) -> impl Iterator<Item = &T> {
1117 self.tables
1118 .iter()
1119 .filter(|(_, rows)| !rows.is_empty())
1120 .map(|(t, _)| t)
1121 }
1122}
1123
1124impl<T: SchemaWithPK, S: Clone + Debug + AsRef<str>, B: Clone + Debug + AsRef<[u8]>>
1125 DiffSet<ChangesetFormat, T, S, B>
1126{
1127 pub fn iter(&self) -> impl Iterator<Item = ChangesetOp<'_, T, S, B>> {
1133 self.tables.iter().flat_map(|(table, rows)| {
1134 rows.iter().map(move |(_pk, op)| match op {
1135 Operation::Insert { values, indirect } => ChangesetOp::Insert {
1136 table,
1137 values: values.as_slice(),
1138 indirect: *indirect,
1139 },
1140 Operation::Update { values, indirect } => ChangesetOp::Update {
1141 table,
1142 values: values.as_slice(),
1143 indirect: *indirect,
1144 },
1145 Operation::Delete { data, indirect } => ChangesetOp::Delete {
1146 table,
1147 old_values: data.as_slice(),
1148 indirect: *indirect,
1149 },
1150 })
1151 })
1152 }
1153}
1154
1155impl<T: SchemaWithPK, S: Clone + AsRef<str>, B: Clone + AsRef<[u8]>>
1156 DiffSet<PatchsetFormat, T, S, B>
1157{
1158 pub fn iter(&self) -> impl Iterator<Item = PatchsetOp<'_, T, S, B>> {
1165 self.tables.iter().flat_map(|(table, rows)| {
1166 rows.iter().map(move |(pk, op)| match op {
1167 Operation::Insert { values, indirect } => PatchsetOp::Insert {
1168 table,
1169 values: values.as_slice(),
1170 indirect: *indirect,
1171 },
1172 Operation::Update { values, indirect } => PatchsetOp::Update {
1173 table,
1174 pk: pk.as_slice(),
1175 entries: values.as_slice(),
1176 indirect: *indirect,
1177 },
1178 Operation::Delete { indirect, .. } => PatchsetOp::Delete {
1179 table,
1180 pk: pk.as_slice(),
1181 indirect: *indirect,
1182 },
1183 })
1184 })
1185 }
1186}
1187
1188impl<
1191 T: SchemaWithPK,
1192 S: Clone + Debug + Hash + Eq + AsRef<str>,
1193 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
1194> DiffSet<ChangesetFormat, T, S, B>
1195{
1196 #[must_use]
1200 pub fn build(&self) -> Vec<u8> {
1201 let mut out = Vec::new();
1202
1203 for (table, rows) in &self.tables {
1204 if rows.is_empty() {
1205 continue;
1206 }
1207
1208 write_table_header(&mut out, markers::CHANGESET, table);
1209
1210 for (_pk, op) in rows {
1211 encode_changeset_op(&mut out, op);
1212 }
1213 }
1214
1215 out
1216 }
1217}
1218
1219impl<T: SchemaWithPK, S: Clone + Hash + Eq + AsRef<str>, B: Clone + Hash + Eq + AsRef<[u8]>>
1222 DiffSet<PatchsetFormat, T, S, B>
1223{
1224 #[must_use]
1228 pub fn build(&self) -> Vec<u8> {
1229 let mut out = Vec::new();
1230
1231 for (table, rows) in &self.tables {
1232 if rows.is_empty() {
1233 continue;
1234 }
1235
1236 write_table_header(&mut out, markers::PATCHSET, table);
1237
1238 let (pk_flags, pk_col_to_pk_pos) = patchset_pk_mapping(table);
1239
1240 for (pk, op) in rows {
1241 encode_patchset_op(&mut out, op, pk, &pk_flags, &pk_col_to_pk_pos);
1242 }
1243 }
1244
1245 out
1246 }
1247}
1248
1249impl<
1252 T: SchemaWithPK,
1253 S: Clone + Debug + Hash + Eq + AsRef<str>,
1254 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
1255> From<&DiffSet<ChangesetFormat, T, S, B>> for Vec<u8>
1256{
1257 #[inline]
1258 fn from(diffset: &DiffSet<ChangesetFormat, T, S, B>) -> Self {
1259 diffset.build()
1260 }
1261}
1262
1263impl<
1264 T: SchemaWithPK,
1265 S: Clone + Debug + Hash + Eq + AsRef<str>,
1266 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
1267> From<DiffSet<ChangesetFormat, T, S, B>> for Vec<u8>
1268{
1269 #[inline]
1270 fn from(diffset: DiffSet<ChangesetFormat, T, S, B>) -> Self {
1271 diffset.build()
1272 }
1273}
1274
1275impl<T: SchemaWithPK, S: AsRef<str> + Clone + Hash + Eq, B: AsRef<[u8]> + Clone + Hash + Eq>
1276 From<&DiffSet<PatchsetFormat, T, S, B>> for Vec<u8>
1277{
1278 #[inline]
1279 fn from(diffset: &DiffSet<PatchsetFormat, T, S, B>) -> Self {
1280 diffset.build()
1281 }
1282}
1283
1284impl<T: SchemaWithPK, S: AsRef<str> + Clone + Hash + Eq, B: AsRef<[u8]> + Clone + Hash + Eq>
1285 From<DiffSet<PatchsetFormat, T, S, B>> for Vec<u8>
1286{
1287 #[inline]
1288 fn from(diffset: DiffSet<PatchsetFormat, T, S, B>) -> Self {
1289 diffset.build()
1290 }
1291}
1292
1293impl<
1296 T: SchemaWithPK,
1297 S: Clone + Debug + Hash + Eq + AsRef<str>,
1298 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
1299> Reverse for DiffSet<ChangesetFormat, T, S, B>
1300{
1301 type Output = DiffSet<ChangesetFormat, T, S, B>;
1302
1303 fn reverse(self) -> Self::Output {
1304 DiffSet {
1305 tables: self
1306 .tables
1307 .into_iter()
1308 .map(|(table, rows)| {
1309 let rev_rows = rows
1310 .into_iter()
1311 .map(|(pk, op)| (pk, op.reverse()))
1312 .collect();
1313 (table, rev_rows)
1314 })
1315 .collect(),
1316 }
1317 }
1318}
1319
1320impl<F: Format<S, B>, T: SchemaWithPK, S: Hash + Eq + AsRef<str>, B: Hash + Eq + AsRef<[u8]>>
1323 From<DiffSetBuilder<F, T, S, B>> for DiffSet<F, T, S, B>
1324{
1325 fn from(builder: DiffSetBuilder<F, T, S, B>) -> Self {
1326 Self {
1327 tables: builder
1328 .tables
1329 .into_iter()
1330 .map(|(table, rows)| {
1331 let ordered_rows: RowVec<F, S, B> = rows.into_iter().collect();
1332 (table, ordered_rows)
1333 })
1334 .collect(),
1335 }
1336 }
1337}
1338
1339impl<F: Format<S, B>, T: SchemaWithPK, S: Hash + Eq + AsRef<str>, B: Hash + Eq + AsRef<[u8]>>
1340 From<DiffSet<F, T, S, B>> for DiffSetBuilder<F, T, S, B>
1341{
1342 fn from(diffset: DiffSet<F, T, S, B>) -> Self {
1343 let mut builder = Self::new();
1344 for (table, rows) in diffset.tables {
1345 let map: IndexMap<Vec<Value<S, B>>, Operation<F, S, B>> = rows.into_iter().collect();
1346 builder.tables.insert(table, map);
1347 }
1348 builder
1349 }
1350}
1351
1352#[cfg(test)]
1353mod tests {
1354 use super::*;
1355 use crate::builders::operation::Indirect;
1356 use crate::encoding::Value;
1357 use alloc::{string::String, vec};
1358
1359 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
1361 struct TestTable {
1362 name: String,
1363 num_columns: usize,
1364 pk_column: usize,
1365 }
1366
1367 impl TestTable {
1368 fn new(name: &str, num_columns: usize, pk_column: usize) -> Self {
1369 Self {
1370 name: name.into(),
1371 num_columns,
1372 pk_column,
1373 }
1374 }
1375 }
1376
1377 impl crate::DynTable for TestTable {
1378 fn name(&self) -> &str {
1379 &self.name
1380 }
1381
1382 fn number_of_columns(&self) -> usize {
1383 self.num_columns
1384 }
1385
1386 fn write_pk_flags(&self, buf: &mut [u8]) {
1387 assert_eq!(buf.len(), self.num_columns);
1388 buf.fill(0);
1389 buf[self.pk_column] = 1;
1390 }
1391 }
1392
1393 impl crate::SchemaWithPK for TestTable {
1394 fn number_of_primary_keys(&self) -> usize {
1395 1
1396 }
1397
1398 fn primary_key_index(&self, col_idx: usize) -> Option<usize> {
1399 if col_idx == self.pk_column {
1400 Some(0)
1401 } else {
1402 None
1403 }
1404 }
1405
1406 fn extract_pk<S: Clone, B: Clone>(
1407 &self,
1408 values: &impl crate::IndexableValues<Text = S, Binary = B>,
1409 ) -> alloc::vec::Vec<Value<S, B>> {
1410 alloc::vec![
1411 values
1412 .get(self.pk_column)
1413 .expect("primary key column index out of bounds, values shorter than schema")
1414 ]
1415 }
1416 }
1417
1418 type ChangesetBuilder = DiffSetBuilder<ChangesetFormat, TestTable, String, Vec<u8>>;
1420
1421 #[test]
1422 fn test_insert_single_row() {
1423 let table = TestTable::new("users", 2, 0);
1424 let insert = Insert::from(table.clone())
1425 .set(0, 1i64)
1426 .unwrap()
1427 .set(1, "alice")
1428 .unwrap();
1429
1430 let builder = ChangesetBuilder::new().insert(insert);
1431
1432 assert_eq!(builder.len(), 1);
1433 assert!(!builder.is_empty());
1434 }
1435
1436 #[test]
1437 fn test_insert_then_delete_cancels_out() {
1438 let table = TestTable::new("users", 2, 0);
1439
1440 let insert = Insert::from(table.clone())
1441 .set(0, 1i64)
1442 .unwrap()
1443 .set(1, "alice")
1444 .unwrap();
1445
1446 let delete = ChangeDelete::from(table.clone())
1447 .set(0, 1i64)
1448 .unwrap()
1449 .set(1, "alice")
1450 .unwrap();
1451
1452 let builder = ChangesetBuilder::new().insert(insert).delete(delete);
1453
1454 assert_eq!(builder.len(), 0);
1455 assert!(builder.is_empty());
1456 }
1457
1458 #[test]
1459 fn test_insert_then_update_becomes_insert() {
1460 let table = TestTable::new("users", 2, 0);
1461
1462 let insert = Insert::from(table.clone())
1463 .set(0, 1i64)
1464 .unwrap()
1465 .set(1, "alice")
1466 .unwrap();
1467
1468 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
1469 .set(0, 1i64, 1i64) .unwrap()
1471 .set(1, "alice", "bob")
1472 .unwrap();
1473
1474 let builder = ChangesetBuilder::new().insert(insert).update(update);
1475
1476 assert_eq!(builder.len(), 1);
1477 }
1479
1480 #[test]
1481 fn test_delete_then_insert_same_values_cancels_out() {
1482 let table = TestTable::new("users", 2, 0);
1483
1484 let delete = ChangeDelete::from(table.clone())
1485 .set(0, 1i64)
1486 .unwrap()
1487 .set(1, "alice")
1488 .unwrap();
1489
1490 let insert = Insert::from(table.clone())
1491 .set(0, 1i64)
1492 .unwrap()
1493 .set(1, "alice")
1494 .unwrap();
1495
1496 let builder = ChangesetBuilder::new().delete(delete).insert(insert);
1497
1498 assert_eq!(builder.len(), 0);
1499 assert!(builder.is_empty());
1500 }
1501
1502 #[test]
1503 fn test_delete_then_insert_different_values_becomes_update() {
1504 let table = TestTable::new("users", 2, 0);
1505
1506 let delete = ChangeDelete::from(table.clone())
1507 .set(0, 1i64)
1508 .unwrap()
1509 .set(1, "alice")
1510 .unwrap();
1511
1512 let insert = Insert::from(table.clone())
1513 .set(0, 1i64)
1514 .unwrap()
1515 .set(1, "bob")
1516 .unwrap();
1517
1518 let builder = ChangesetBuilder::new().delete(delete).insert(insert);
1519
1520 assert_eq!(builder.len(), 1);
1521 }
1523
1524 #[test]
1525 fn test_multiple_rows() {
1526 let table = TestTable::new("users", 2, 0);
1527
1528 let insert1 = Insert::from(table.clone())
1529 .set(0, 1i64)
1530 .unwrap()
1531 .set(1, "alice")
1532 .unwrap();
1533
1534 let insert2 = Insert::from(table.clone())
1535 .set(0, 2i64)
1536 .unwrap()
1537 .set(1, "bob")
1538 .unwrap();
1539
1540 let builder = ChangesetBuilder::new().insert(insert1).insert(insert2);
1541
1542 assert_eq!(builder.len(), 2);
1543 }
1544
1545 #[test]
1546 fn test_update_then_update_consolidates() {
1547 let table = TestTable::new("users", 2, 0);
1548
1549 let update1 = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
1550 .set(0, 1i64, 1i64)
1551 .unwrap()
1552 .set(1, "alice", "bob")
1553 .unwrap();
1554
1555 let update2 = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
1556 .set(0, 1i64, 1i64)
1557 .unwrap()
1558 .set(1, "bob", "charlie")
1559 .unwrap();
1560
1561 let builder = ChangesetBuilder::new().update(update1).update(update2);
1562
1563 assert_eq!(builder.len(), 1);
1564 }
1566
1567 #[test]
1572 fn test_reverse_operation_insert_becomes_delete() {
1573 let op: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Insert {
1574 values: vec![Value::Integer(1), Value::Text("alice".into())],
1575 indirect: false,
1576 };
1577 let reversed = op.reverse();
1578 let Operation::Delete { data, .. } = reversed else {
1579 panic!("Expected Delete operation");
1580 };
1581 assert_eq!(
1582 data,
1583 vec![
1584 Value::<String, Vec<u8>>::Integer(1),
1585 Value::Text("alice".into())
1586 ]
1587 );
1588 }
1589
1590 #[test]
1591 fn test_reverse_operation_delete_becomes_insert() {
1592 let op: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Delete {
1593 data: vec![Value::Integer(1), Value::Text("alice".into())],
1594 indirect: false,
1595 };
1596 let reversed = op.reverse();
1597 let Operation::Insert { values, .. } = reversed else {
1598 panic!("Expected Insert operation");
1599 };
1600 assert_eq!(
1601 values,
1602 vec![
1603 Value::<String, Vec<u8>>::Integer(1),
1604 Value::Text("alice".into())
1605 ]
1606 );
1607 }
1608
1609 #[test]
1610 fn test_reverse_operation_update_swaps_old_new() {
1611 let op: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Update {
1612 values: vec![
1613 (Some(Value::Integer(1)), Some(Value::Integer(1))),
1614 (
1615 Some(Value::Text("alice".into())),
1616 Some(Value::Text("bob".into())),
1617 ),
1618 ],
1619 indirect: false,
1620 };
1621 let reversed = op.reverse();
1622 let Operation::Update { values, .. } = reversed else {
1623 panic!("Expected Update operation");
1624 };
1625 assert_eq!(
1626 values[0],
1627 (Some(Value::Integer(1)), Some(Value::Integer(1)))
1628 );
1629 assert_eq!(
1630 values[1],
1631 (
1632 Some(Value::Text("bob".into())),
1633 Some(Value::Text("alice".into()))
1634 )
1635 );
1636 }
1637
1638 #[test]
1639 fn test_reverse_builder_insert_becomes_delete() {
1640 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 builder = ChangesetBuilder::new().insert(insert);
1648 let reversed = builder.reverse();
1649
1650 assert_eq!(reversed.len(), 1);
1651 let rows = reversed.tables.get(&table).unwrap();
1653 assert!(matches!(
1654 rows.values().next().unwrap(),
1655 Operation::Delete { .. }
1656 ));
1657 }
1658
1659 #[test]
1660 fn test_reverse_builder_delete_becomes_insert() {
1661 let table = TestTable::new("users", 2, 0);
1662 let delete = ChangeDelete::from(table.clone())
1663 .set(0, 1i64)
1664 .unwrap()
1665 .set(1, "alice")
1666 .unwrap();
1667
1668 let builder = ChangesetBuilder::new().delete(delete);
1669 let reversed = builder.reverse();
1670
1671 assert_eq!(reversed.len(), 1);
1672 let rows = reversed.tables.get(&table).unwrap();
1674 assert!(matches!(
1675 rows.values().next().unwrap(),
1676 Operation::Insert { .. }
1677 ));
1678 }
1679
1680 #[test]
1681 fn test_reverse_builder_update_swaps() {
1682 let table = TestTable::new("users", 2, 0);
1683 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
1684 .set(0, 1i64, 1i64)
1685 .unwrap()
1686 .set(1, "alice", "bob")
1687 .unwrap();
1688
1689 let builder = ChangesetBuilder::new().update(update);
1690 let reversed = builder.reverse();
1691
1692 assert_eq!(reversed.len(), 1);
1693 let rows = reversed.tables.get(&table).unwrap();
1695 let Operation::Update { values, .. } = rows.values().next().unwrap() else {
1696 panic!("Expected Update operation");
1697 };
1698 assert_eq!(
1699 values[1],
1700 (
1701 Some(Value::Text("bob".into())),
1702 Some(Value::Text("alice".into()))
1703 )
1704 );
1705 }
1706
1707 #[test]
1708 fn test_reverse_is_involutory() {
1709 let table = TestTable::new("users", 2, 0);
1711 let insert = Insert::from(table.clone())
1712 .set(0, 1i64)
1713 .unwrap()
1714 .set(1, "alice")
1715 .unwrap();
1716
1717 let original_values = insert.into_values();
1718 let insert2 = Insert::from(table.clone())
1719 .set(0, 1i64)
1720 .unwrap()
1721 .set(1, "alice")
1722 .unwrap();
1723 let builder = ChangesetBuilder::new().insert(insert2);
1724 let double_reversed = builder.reverse().reverse();
1725
1726 assert_eq!(double_reversed.len(), 1);
1727 let rows = double_reversed.tables.get(&table).unwrap();
1728 let Operation::Insert { values, .. } = rows.values().next().unwrap() else {
1729 panic!("Expected Insert operation");
1730 };
1731 assert_eq!(values, &original_values);
1732 }
1733
1734 #[test]
1739 fn test_build_empty_builder() {
1740 let builder = ChangesetBuilder::new();
1741 let bytes = builder.build();
1742 assert!(bytes.is_empty());
1743 }
1744
1745 #[test]
1746 fn test_build_insert_format() {
1747 let table = TestTable::new("t", 2, 0);
1748 let insert = Insert::from(table.clone())
1749 .set(0, 1i64)
1750 .unwrap()
1751 .set(1, "a")
1752 .unwrap();
1753
1754 let builder = ChangesetBuilder::new().insert(insert);
1755 let bytes = builder.build();
1756
1757 assert!(!bytes.is_empty());
1761
1762 assert_eq!(bytes[0], b'T');
1764 assert_eq!(bytes[1], 2);
1766 assert_eq!(bytes[2], 1);
1768 assert_eq!(bytes[3], 0);
1769 assert_eq!(bytes[4], b't');
1771 assert_eq!(bytes[5], 0);
1772 assert_eq!(bytes[6], 0x12);
1774 assert_eq!(bytes[7], 0);
1776 }
1777
1778 #[test]
1779 fn test_build_delete_format() {
1780 let table = TestTable::new("t", 2, 0);
1781 let delete = ChangeDelete::from(table.clone())
1782 .set(0, 1i64)
1783 .unwrap()
1784 .set(1, "a")
1785 .unwrap();
1786
1787 let builder = ChangesetBuilder::new().delete(delete);
1788 let bytes = builder.build();
1789
1790 assert!(!bytes.is_empty());
1791 assert_eq!(bytes[0], b'T');
1792 assert_eq!(bytes[6], 0x09);
1794 }
1795
1796 #[test]
1797 fn test_build_update_format() {
1798 let table = TestTable::new("t", 2, 0);
1799 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
1800 .set(0, 1i64, 1i64)
1801 .unwrap()
1802 .set(1, "a", "b")
1803 .unwrap();
1804
1805 let builder = ChangesetBuilder::new().update(update);
1806 let bytes = builder.build();
1807
1808 assert!(!bytes.is_empty());
1809 assert_eq!(bytes[0], b'T');
1810 assert_eq!(bytes[6], 0x17);
1812 }
1813
1814 #[test]
1815 fn test_build_multiple_operations() {
1816 let table = TestTable::new("t", 2, 0);
1817
1818 let insert = Insert::from(table.clone())
1819 .set(0, 1i64)
1820 .unwrap()
1821 .set(1, "a")
1822 .unwrap();
1823
1824 let insert2 = Insert::from(table.clone())
1825 .set(0, 2i64)
1826 .unwrap()
1827 .set(1, "b")
1828 .unwrap();
1829
1830 let builder = ChangesetBuilder::new().insert(insert).insert(insert2);
1831 let bytes = builder.build();
1832
1833 assert!(!bytes.is_empty());
1834 assert_eq!(bytes[0], b'T');
1836 }
1837
1838 #[test]
1839 fn test_build_cancelled_operations_produce_empty() {
1840 let table = TestTable::new("t", 2, 0);
1841
1842 let insert = Insert::from(table.clone())
1843 .set(0, 1i64)
1844 .unwrap()
1845 .set(1, "a")
1846 .unwrap();
1847
1848 let delete = ChangeDelete::from(table.clone())
1849 .set(0, 1i64)
1850 .unwrap()
1851 .set(1, "a")
1852 .unwrap();
1853
1854 let builder = ChangesetBuilder::new().insert(insert).delete(delete);
1855 let bytes = builder.build();
1856
1857 assert!(bytes.is_empty());
1859 }
1860
1861 #[test]
1866 fn test_bitor_changeset_disjoint_rows() {
1867 let table = TestTable::new("users", 2, 0);
1868
1869 let insert1 = Insert::from(table.clone())
1870 .set(0, 1i64)
1871 .unwrap()
1872 .set(1, "alice")
1873 .unwrap();
1874
1875 let insert2 = Insert::from(table.clone())
1876 .set(0, 2i64)
1877 .unwrap()
1878 .set(1, "bob")
1879 .unwrap();
1880
1881 let cs1 = ChangesetBuilder::new().insert(insert1);
1882 let cs2 = ChangesetBuilder::new().insert(insert2);
1883
1884 let merged = cs1 | cs2;
1885 assert_eq!(merged.len(), 2);
1886 }
1887
1888 #[test]
1889 fn test_bitor_changeset_consolidates_same_row() {
1890 let table = TestTable::new("users", 2, 0);
1891
1892 let insert = Insert::from(table.clone())
1894 .set(0, 1i64)
1895 .unwrap()
1896 .set(1, "alice")
1897 .unwrap();
1898
1899 let delete = ChangeDelete::from(table.clone())
1901 .set(0, 1i64)
1902 .unwrap()
1903 .set(1, "alice")
1904 .unwrap();
1905
1906 let cs1 = ChangesetBuilder::new().insert(insert);
1907 let cs2 = ChangesetBuilder::new().delete(delete);
1908
1909 let merged = cs1 | cs2;
1911 assert_eq!(merged.len(), 0);
1912 assert!(merged.is_empty());
1913 }
1914
1915 #[test]
1916 fn test_bitor_assign_changeset() {
1917 let table = TestTable::new("users", 2, 0);
1918
1919 let insert1 = Insert::from(table.clone())
1920 .set(0, 1i64)
1921 .unwrap()
1922 .set(1, "alice")
1923 .unwrap();
1924
1925 let insert2 = Insert::from(table.clone())
1926 .set(0, 2i64)
1927 .unwrap()
1928 .set(1, "bob")
1929 .unwrap();
1930
1931 let mut cs = ChangesetBuilder::new().insert(insert1);
1932 cs |= ChangesetBuilder::new().insert(insert2);
1933
1934 assert_eq!(cs.len(), 2);
1935 }
1936
1937 #[test]
1938 fn test_bitor_patchset_disjoint_rows() {
1939 type PatchsetBuilder = DiffSetBuilder<PatchsetFormat, TestTable, String, Vec<u8>>;
1940
1941 let table = TestTable::new("users", 2, 0);
1942
1943 let insert1 = Insert::from(table.clone())
1944 .set(0, 1i64)
1945 .unwrap()
1946 .set(1, "alice")
1947 .unwrap();
1948
1949 let insert2 = Insert::from(table.clone())
1950 .set(0, 2i64)
1951 .unwrap()
1952 .set(1, "bob")
1953 .unwrap();
1954
1955 let ps1 = PatchsetBuilder::new().insert(insert1);
1956 let ps2 = PatchsetBuilder::new().insert(insert2);
1957
1958 let merged = ps1 | ps2;
1959 assert_eq!(merged.len(), 2);
1960 }
1961
1962 #[test]
1963 fn test_bitor_patchset_consolidates_same_row() {
1964 type PatchsetBuilder = DiffSetBuilder<PatchsetFormat, TestTable, String, Vec<u8>>;
1965
1966 let table = TestTable::new("users", 2, 0);
1967
1968 let insert = Insert::from(table.clone())
1970 .set(0, 1i64)
1971 .unwrap()
1972 .set(1, "alice")
1973 .unwrap();
1974
1975 let delete = PatchDelete::new(table.clone(), vec![Value::Integer(1)]);
1977
1978 let ps1 = PatchsetBuilder::new().insert(insert);
1979 let ps2 = PatchsetBuilder::new().delete(delete);
1980
1981 let merged = ps1 | ps2;
1983 assert_eq!(merged.len(), 0);
1984 assert!(merged.is_empty());
1985 }
1986
1987 #[test]
1988 fn test_bitor_multiple_tables() {
1989 let table1 = TestTable::new("users", 2, 0);
1990 let table2 = TestTable::new("posts", 2, 0);
1991
1992 let insert1 = Insert::from(table1.clone())
1993 .set(0, 1i64)
1994 .unwrap()
1995 .set(1, "alice")
1996 .unwrap();
1997
1998 let insert2 = Insert::from(table2.clone())
1999 .set(0, 100i64)
2000 .unwrap()
2001 .set(1, "first post")
2002 .unwrap();
2003
2004 let cs1 = ChangesetBuilder::new().insert(insert1);
2005 let cs2 = ChangesetBuilder::new().insert(insert2);
2006
2007 let merged = cs1 | cs2;
2008 assert_eq!(merged.len(), 2);
2009 }
2010
2011 #[test]
2012 fn test_bitor_insert_then_update_consolidates() {
2013 let table = TestTable::new("users", 2, 0);
2014
2015 let insert = Insert::from(table.clone())
2017 .set(0, 1i64)
2018 .unwrap()
2019 .set(1, "alice")
2020 .unwrap();
2021
2022 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
2024 .set(0, 1i64, 1i64)
2025 .unwrap()
2026 .set(1, "alice", "alicia")
2027 .unwrap();
2028
2029 let cs1 = ChangesetBuilder::new().insert(insert);
2030 let cs2 = ChangesetBuilder::new().update(update);
2031
2032 let merged = cs1 | cs2;
2034 assert_eq!(merged.len(), 1);
2035 }
2036
2037 #[test]
2038 fn test_session_hash_growth_with_many_rows() {
2039 let table = TestTable::new("many", 2, 0);
2043 let mut cs = ChangesetBuilder::new();
2044 for i in 0..200i64 {
2045 let insert = Insert::from(table.clone())
2046 .set(0, i)
2047 .unwrap()
2048 .set(1, alloc::format!("row-{i}"))
2049 .unwrap();
2050 cs = cs.insert(insert);
2051 }
2052 assert_eq!(cs.len(), 200);
2053
2054 let bytes = cs.build();
2055 assert!(!bytes.is_empty());
2056
2057 let reparsed = crate::parser::ParsedDiffSet::try_from(bytes.as_slice()).unwrap();
2059 let reparsed_bytes: Vec<u8> = reparsed.into();
2060 assert_eq!(bytes, reparsed_bytes);
2061 }
2062
2063 const INDIRECT_BYTE_OFFSET: usize = 7;
2071
2072 #[test]
2073 fn test_build_insert_indirect_byte_set() {
2074 let table = TestTable::new("t", 2, 0);
2075 let insert = Insert::from(table)
2076 .set(0, 1i64)
2077 .unwrap()
2078 .set(1, "a")
2079 .unwrap()
2080 .indirect(true);
2081
2082 let bytes = ChangesetBuilder::new().insert(insert).build();
2083 assert_eq!(bytes[INDIRECT_BYTE_OFFSET], 1);
2084 }
2085
2086 #[test]
2087 fn test_build_delete_indirect_byte_set() {
2088 let table = TestTable::new("t", 2, 0);
2089 let delete = ChangeDelete::from(table)
2090 .set(0, 1i64)
2091 .unwrap()
2092 .set(1, "a")
2093 .unwrap()
2094 .indirect(true);
2095
2096 let bytes = ChangesetBuilder::new().delete(delete).build();
2097 assert_eq!(bytes[INDIRECT_BYTE_OFFSET], 1);
2098 }
2099
2100 #[test]
2101 fn test_build_update_indirect_byte_set() {
2102 let table = TestTable::new("t", 2, 0);
2103 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table)
2104 .set(0, 1i64, 1i64)
2105 .unwrap()
2106 .set(1, "a", "b")
2107 .unwrap()
2108 .indirect(true);
2109
2110 let bytes = ChangesetBuilder::new().update(update).build();
2111 assert_eq!(bytes[INDIRECT_BYTE_OFFSET], 1);
2112 }
2113
2114 #[test]
2115 fn test_build_patchset_insert_indirect_byte() {
2116 let table = TestTable::new("t", 2, 0);
2117 let insert = Insert::from(table)
2118 .set(0, 1i64)
2119 .unwrap()
2120 .set(1, "a")
2121 .unwrap()
2122 .indirect(true);
2123
2124 let patchset: PatchSet<TestTable, String, Vec<u8>> = PatchSet::new().insert(insert);
2125 let bytes = patchset.build();
2126 assert_eq!(bytes[INDIRECT_BYTE_OFFSET], 1);
2127 }
2128
2129 #[test]
2130 fn test_reverse_preserves_indirect() {
2131 let insert: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Insert {
2133 values: vec![Value::Integer(1)],
2134 indirect: true,
2135 };
2136 let reversed = insert.reverse();
2137 assert!(reversed.indirect());
2138 assert!(matches!(reversed, Operation::Delete { .. }));
2139
2140 let delete: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Delete {
2142 data: vec![Value::Integer(1)],
2143 indirect: true,
2144 };
2145 let reversed = delete.reverse();
2146 assert!(reversed.indirect());
2147 assert!(matches!(reversed, Operation::Insert { .. }));
2148
2149 let update: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Update {
2151 values: vec![(Some(Value::Integer(1)), Some(Value::Integer(2)))],
2152 indirect: true,
2153 };
2154 let reversed = update.reverse();
2155 assert!(reversed.indirect());
2156 assert!(matches!(reversed, Operation::Update { .. }));
2157 }
2158
2159 #[test]
2160 fn test_patchdelete_indirect_byte_set() {
2161 let table = TestTable::new("t", 2, 0);
2162 let delete: PatchDelete<TestTable, String, Vec<u8>> =
2163 PatchDelete::new(table, vec![Value::Integer(1)]).indirect(true);
2164 let bytes = PatchSet::new().delete(delete).build();
2165 assert_eq!(bytes[INDIRECT_BYTE_OFFSET], 1);
2166 }
2167
2168 #[test]
2169 fn test_operation_eq_indirect_differs() {
2170 let a: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Insert {
2172 values: vec![Value::Integer(1)],
2173 indirect: false,
2174 };
2175 let b: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Insert {
2176 values: vec![Value::Integer(1)],
2177 indirect: true,
2178 };
2179 assert_ne!(a, b);
2180 }
2181
2182 #[test]
2183 fn test_operation_eq_variant_mismatch() {
2184 let insert: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Insert {
2186 values: vec![Value::Integer(1)],
2187 indirect: false,
2188 };
2189 let delete: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Delete {
2190 data: vec![Value::Integer(1)],
2191 indirect: false,
2192 };
2193 assert_ne!(insert, delete);
2194 }
2195
2196 #[test]
2197 fn test_bitor_indirect_rhs_wins() {
2198 let table = TestTable::new("t", 2, 0);
2201 let lhs_insert = Insert::from(table.clone())
2202 .set(0, 1i64)
2203 .unwrap()
2204 .set(1, "a")
2205 .unwrap()
2206 .indirect(true);
2207 let rhs_insert = Insert::from(table.clone())
2208 .set(0, 1i64)
2209 .unwrap()
2210 .set(1, "a")
2211 .unwrap()
2212 .indirect(false);
2213
2214 let merged =
2215 ChangesetBuilder::new().insert(lhs_insert) | ChangesetBuilder::new().insert(rhs_insert);
2216 let rows = merged.tables.get(&table).unwrap();
2217 let op = rows.values().next().unwrap();
2218 assert!(!op.indirect(), "rhs (false) should win over lhs (true)");
2219
2220 let lhs_insert = Insert::from(table.clone())
2222 .set(0, 2i64)
2223 .unwrap()
2224 .set(1, "b")
2225 .unwrap()
2226 .indirect(false);
2227 let rhs_insert = Insert::from(table.clone())
2228 .set(0, 2i64)
2229 .unwrap()
2230 .set(1, "b")
2231 .unwrap()
2232 .indirect(true);
2233
2234 let merged =
2235 ChangesetBuilder::new().insert(lhs_insert) | ChangesetBuilder::new().insert(rhs_insert);
2236 let rows = merged.tables.get(&table).unwrap();
2237 let op = rows.values().next().unwrap();
2238 assert!(op.indirect(), "rhs (true) should win over lhs (false)");
2239 }
2240
2241 #[test]
2242 fn test_roundtrip_indirect_changeset() {
2243 let table = TestTable::new("t", 2, 0);
2244 let insert = Insert::from(table)
2245 .set(0, 1i64)
2246 .unwrap()
2247 .set(1, "a")
2248 .unwrap()
2249 .indirect(true);
2250
2251 let bytes = ChangesetBuilder::new().insert(insert).build();
2252 let reparsed = crate::parser::ParsedDiffSet::try_from(bytes.as_slice()).unwrap();
2253 let reparsed_bytes: Vec<u8> = reparsed.into();
2254 assert_eq!(bytes, reparsed_bytes);
2255 assert_eq!(reparsed_bytes[INDIRECT_BYTE_OFFSET], 1);
2256 }
2257
2258 #[test]
2259 fn test_roundtrip_indirect_patchset() {
2260 let table = TestTable::new("t", 2, 0);
2261 let insert = Insert::from(table)
2262 .set(0, 1i64)
2263 .unwrap()
2264 .set(1, "a")
2265 .unwrap()
2266 .indirect(true);
2267
2268 let patchset: PatchSet<TestTable, String, Vec<u8>> = PatchSet::new().insert(insert);
2269 let bytes = patchset.build();
2270 let reparsed = crate::parser::ParsedDiffSet::try_from(bytes.as_slice()).unwrap();
2271 let reparsed_bytes: Vec<u8> = reparsed.into();
2272 assert_eq!(bytes, reparsed_bytes);
2273 assert_eq!(reparsed_bytes[INDIRECT_BYTE_OFFSET], 1);
2274 }
2275
2276 #[test]
2277 fn test_indirect_full_pipeline_roundtrip() {
2278 let table = TestTable::new("t", 2, 0);
2281 let insert = Insert::from(table)
2282 .set(0, 1i64)
2283 .unwrap()
2284 .set(1, "alice")
2285 .unwrap()
2286 .indirect(true);
2287 let original = ChangesetBuilder::new().insert(insert);
2288 let bytes_a = original.build();
2289 assert_eq!(bytes_a[INDIRECT_BYTE_OFFSET], 1);
2290
2291 let parsed = crate::parser::ParsedDiffSet::try_from(bytes_a.as_slice()).unwrap();
2293 let crate::parser::ParsedDiffSet::Changeset(parsed_set) = parsed else {
2294 panic!("expected Changeset variant");
2295 };
2296
2297 let parsed_builder: DiffSetBuilder<
2298 ChangesetFormat,
2299 crate::parser::TableSchema<String>,
2300 String,
2301 Vec<u8>,
2302 > = parsed_set.into();
2303 let empty: DiffSetBuilder<
2304 ChangesetFormat,
2305 crate::parser::TableSchema<String>,
2306 String,
2307 Vec<u8>,
2308 > = DiffSetBuilder::new();
2309 let doubled = parsed_builder.reverse().reverse();
2310 let merged = doubled | empty;
2311
2312 let bytes_b = merged.build();
2313 assert_eq!(bytes_a, bytes_b);
2314 assert_eq!(bytes_b[INDIRECT_BYTE_OFFSET], 1);
2315 }
2316
2317 fn changeset_insert(v: i64) -> Operation<ChangesetFormat, String, Vec<u8>> {
2322 Operation::Insert {
2323 values: vec![Value::Integer(v), Value::Text("a".into())],
2324 indirect: false,
2325 }
2326 }
2327
2328 fn changeset_delete(v: i64) -> Operation<ChangesetFormat, String, Vec<u8>> {
2329 Operation::Delete {
2330 data: vec![Value::Integer(v), Value::Text("a".into())],
2331 indirect: false,
2332 }
2333 }
2334
2335 fn changeset_update(old: i64, new: i64) -> Operation<ChangesetFormat, String, Vec<u8>> {
2336 Operation::Update {
2337 values: vec![
2338 (Some(Value::Integer(old)), Some(Value::Integer(new))),
2339 (Some(Value::Text("a".into())), Some(Value::Text("b".into()))),
2340 ],
2341 indirect: false,
2342 }
2343 }
2344
2345 #[test]
2346 fn test_add_changeset_insert_plus_update() {
2347 let merged = (changeset_insert(1) + changeset_update(1, 2)).unwrap();
2348 let Operation::Insert { values, .. } = merged else {
2349 panic!("expected Insert");
2350 };
2351 assert_eq!(values[0], Value::Integer(2));
2352 assert_eq!(values[1], Value::Text("b".into()));
2353 }
2354
2355 #[test]
2356 fn test_add_changeset_update_plus_insert() {
2357 let merged = (changeset_update(1, 2) + changeset_insert(99)).unwrap();
2359 assert!(matches!(merged, Operation::Update { .. }));
2360 }
2361
2362 #[test]
2363 fn test_add_changeset_update_plus_update() {
2364 let merged = (changeset_update(1, 2) + changeset_update(2, 3)).unwrap();
2366 let Operation::Update { values, .. } = merged else {
2367 panic!("expected Update");
2368 };
2369 assert_eq!(values[0].0, Some(Value::Integer(1)));
2370 assert_eq!(values[0].1, Some(Value::Integer(3)));
2371 }
2372
2373 #[test]
2374 fn test_add_changeset_update_plus_delete() {
2375 let merged = (changeset_update(1, 2) + changeset_delete(99)).unwrap();
2377 let Operation::Delete { data, .. } = merged else {
2378 panic!("expected Delete");
2379 };
2380 assert_eq!(data[0], Value::Integer(1));
2381 assert_eq!(data[1], Value::Text("a".into()));
2382 }
2383
2384 #[test]
2385 fn test_add_changeset_delete_plus_update_keeps_delete() {
2386 let merged = (changeset_delete(1) + changeset_update(1, 2)).unwrap();
2387 assert!(matches!(merged, Operation::Delete { .. }));
2388 }
2389
2390 #[test]
2391 fn test_add_changeset_delete_plus_delete_keeps_first() {
2392 let merged = (changeset_delete(1) + changeset_delete(2)).unwrap();
2393 let Operation::Delete { data, .. } = merged else {
2394 panic!("expected Delete");
2395 };
2396 assert_eq!(data[0], Value::Integer(1));
2397 }
2398
2399 fn patchset_insert(v: i64) -> Operation<PatchsetFormat, String, Vec<u8>> {
2400 Operation::Insert {
2401 values: vec![Value::Integer(v), Value::Text("a".into())],
2402 indirect: false,
2403 }
2404 }
2405
2406 fn patchset_update(new: i64) -> Operation<PatchsetFormat, String, Vec<u8>> {
2407 Operation::Update {
2408 values: vec![
2409 ((), Some(Value::Integer(new))),
2410 ((), Some(Value::Text("b".into()))),
2411 ],
2412 indirect: false,
2413 }
2414 }
2415
2416 fn patchset_delete() -> Operation<PatchsetFormat, String, Vec<u8>> {
2417 Operation::Delete {
2418 data: (),
2419 indirect: false,
2420 }
2421 }
2422
2423 #[test]
2424 fn test_add_patchset_insert_plus_update() {
2425 let merged = (patchset_insert(1) + patchset_update(2)).unwrap();
2426 let Operation::Insert { values, .. } = merged else {
2427 panic!("expected Insert");
2428 };
2429 assert_eq!(values[0], Value::Integer(2));
2430 assert_eq!(values[1], Value::Text("b".into()));
2431 }
2432
2433 #[test]
2434 fn test_add_patchset_update_plus_insert() {
2435 let merged = (patchset_update(2) + patchset_insert(99)).unwrap();
2436 assert!(matches!(merged, Operation::Update { .. }));
2437 }
2438
2439 #[test]
2440 fn test_add_patchset_update_plus_update() {
2441 let merged = (patchset_update(2) + patchset_update(3)).unwrap();
2442 let Operation::Update { values, .. } = merged else {
2443 panic!("expected Update");
2444 };
2445 assert_eq!(values[0].1, Some(Value::Integer(3)));
2446 }
2447
2448 #[test]
2449 fn test_add_patchset_update_plus_delete() {
2450 let merged = (patchset_update(2) + patchset_delete()).unwrap();
2451 assert!(matches!(merged, Operation::Delete { .. }));
2452 }
2453
2454 #[test]
2455 fn test_add_patchset_delete_plus_insert_promotes_to_update() {
2456 let merged = (patchset_delete() + patchset_insert(1)).unwrap();
2458 assert!(matches!(merged, Operation::Update { .. }));
2459 }
2460
2461 #[test]
2462 fn test_add_patchset_delete_plus_update_keeps_delete() {
2463 let merged = (patchset_delete() + patchset_update(2)).unwrap();
2464 assert!(matches!(merged, Operation::Delete { .. }));
2465 }
2466
2467 #[test]
2468 fn test_add_patchset_delete_plus_delete_keeps_first() {
2469 let merged = (patchset_delete() + patchset_delete()).unwrap();
2470 assert!(matches!(merged, Operation::Delete { .. }));
2471 }
2472
2473 #[test]
2478 fn test_session_hash_real_pk() {
2479 let table = TestTable::new("t", 2, 0);
2480 let insert = Insert::from(table)
2481 .set(0, 2.5f64)
2482 .unwrap()
2483 .set(1, "a")
2484 .unwrap();
2485 let bytes = ChangesetBuilder::new().insert(insert).build();
2486 assert!(!bytes.is_empty());
2487 }
2488
2489 #[test]
2490 fn test_session_hash_text_pk() {
2491 let table = TestTable::new("t", 2, 0);
2492 let insert = Insert::from(table)
2493 .set(0, "alice")
2494 .unwrap()
2495 .set(1, 42i64)
2496 .unwrap();
2497 let bytes = ChangesetBuilder::new().insert(insert).build();
2498 assert!(!bytes.is_empty());
2499 }
2500
2501 #[test]
2502 fn test_session_hash_blob_pk() {
2503 let table = TestTable::new("t", 2, 0);
2504 let insert = Insert::from(table)
2505 .set(0, alloc::vec![0xDE_u8, 0xAD, 0xBE, 0xEF])
2506 .unwrap()
2507 .set(1, "a")
2508 .unwrap();
2509 let bytes = ChangesetBuilder::new().insert(insert).build();
2510 assert!(!bytes.is_empty());
2511 }
2512
2513 #[test]
2518 fn test_session_row_order_empty() {
2519 let cs: ChangesetBuilder = ChangesetBuilder::new();
2521 let bytes = cs.build();
2522 assert!(bytes.is_empty());
2523 }
2524
2525 #[test]
2526 fn test_session_row_order_empty_rows_returns_empty_vec() {
2527 let rows: RowMap<ChangesetFormat, String, Vec<u8>> = IndexMap::default();
2529 assert!(session_row_order(&rows).is_empty());
2530 }
2531
2532 #[test]
2533 fn test_diffset_patchset_build_skips_empty_table() {
2534 let table = TestTable::new("t", 2, 0);
2536 let mut builder: PatchSet<TestTable, String, Vec<u8>> = PatchSet::new();
2537 builder.add_table(&table);
2538 let frozen: DiffSet<PatchsetFormat, TestTable, String, Vec<u8>> = builder.into();
2539 let bytes = frozen.build();
2540 assert!(bytes.is_empty());
2541 }
2542
2543 #[test]
2548 fn test_from_changeset_builder_into_vec() {
2549 let table = TestTable::new("t", 2, 0);
2550 let insert = Insert::from(table)
2551 .set(0, 1i64)
2552 .unwrap()
2553 .set(1, "a")
2554 .unwrap();
2555 let builder = ChangesetBuilder::new().insert(insert);
2556 let bytes_owned: Vec<u8> = builder.clone().into();
2557 let bytes_ref: Vec<u8> = (&builder).into();
2558 assert_eq!(bytes_owned, bytes_ref);
2559 let frozen: DiffSet<ChangesetFormat, TestTable, String, Vec<u8>> = builder.into();
2560 let bytes_frozen_owned: Vec<u8> = frozen.clone().into();
2561 let bytes_frozen_ref: Vec<u8> = (&frozen).into();
2562 assert_eq!(bytes_frozen_owned, bytes_frozen_ref);
2563 }
2564
2565 #[test]
2566 fn test_from_patchset_builder_into_vec() {
2567 let table = TestTable::new("t", 2, 0);
2568 let insert = Insert::from(table)
2569 .set(0, 1i64)
2570 .unwrap()
2571 .set(1, "a")
2572 .unwrap();
2573 let builder: PatchSet<TestTable, String, Vec<u8>> = PatchSet::new().insert(insert);
2574 let bytes_owned: Vec<u8> = builder.clone().into();
2575 let bytes_ref: Vec<u8> = (&builder).into();
2576 assert_eq!(bytes_owned, bytes_ref);
2577 let frozen: DiffSet<PatchsetFormat, TestTable, String, Vec<u8>> = builder.into();
2578 let bytes_frozen_owned: Vec<u8> = frozen.clone().into();
2579 let bytes_frozen_ref: Vec<u8> = (&frozen).into();
2580 assert_eq!(bytes_frozen_owned, bytes_frozen_ref);
2581 }
2582
2583 #[test]
2588 fn test_add_operation_insert_then_update_changes_pk() {
2589 let table = TestTable::new("t", 2, 0);
2592 let insert = Insert::from(table.clone())
2593 .set(0, 1i64)
2594 .unwrap()
2595 .set(1, "alice")
2596 .unwrap();
2597 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
2598 .set(0, 1i64, 2i64)
2599 .unwrap()
2600 .set(1, "alice", "bob")
2601 .unwrap();
2602 let builder = ChangesetBuilder::new().insert(insert).update(update);
2603 let rows = builder.tables.get(&table).unwrap();
2604 assert_eq!(rows.len(), 1);
2605 let (pk, op) = rows.iter().next().unwrap();
2607 assert_eq!(pk[0], Value::Integer(2));
2608 let Operation::Insert { values, .. } = op else {
2609 panic!("expected merged INSERT");
2610 };
2611 assert_eq!(values[0], Value::Integer(2));
2612 assert_eq!(values[1], Value::Text("bob".into()));
2613 }
2614
2615 #[test]
2620 fn test_diffset_changeset_diffops_wrappers() {
2621 let table = TestTable::new("t", 2, 0);
2622 let initial = Insert::from(table.clone())
2623 .set(0, 1i64)
2624 .unwrap()
2625 .set(1, "a")
2626 .unwrap();
2627 let frozen: DiffSet<ChangesetFormat, TestTable, String, Vec<u8>> =
2628 ChangesetBuilder::new().insert(initial).into();
2629
2630 let insert2 = Insert::from(table.clone())
2631 .set(0, 2i64)
2632 .unwrap()
2633 .set(1, "b")
2634 .unwrap();
2635 let after_insert = <_ as DiffOps<_, _, _>>::insert(frozen.clone(), insert2);
2636 assert_eq!(after_insert.len(), 2);
2637
2638 let delete = ChangeDelete::from(table.clone())
2639 .set(0, 1i64)
2640 .unwrap()
2641 .set(1, "a")
2642 .unwrap();
2643 let after_delete = <_ as DiffOps<_, _, _>>::delete(frozen.clone(), delete);
2644 assert_eq!(after_delete.len(), 0);
2645
2646 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table)
2647 .set(0, 1i64, 1i64)
2648 .unwrap()
2649 .set(1, "a", "z")
2650 .unwrap();
2651 let after_update = <_ as DiffOps<_, _, _>>::update(frozen, update);
2652 assert_eq!(after_update.len(), 1);
2653 }
2654
2655 #[test]
2656 fn test_diffset_patchset_diffops_wrappers() {
2657 let table = TestTable::new("t", 2, 0);
2658 let initial = Insert::from(table.clone())
2659 .set(0, 1i64)
2660 .unwrap()
2661 .set(1, "a")
2662 .unwrap();
2663 let frozen: DiffSet<PatchsetFormat, TestTable, String, Vec<u8>> =
2664 PatchSet::new().insert(initial).into();
2665
2666 let insert2 = Insert::from(table.clone())
2667 .set(0, 2i64)
2668 .unwrap()
2669 .set(1, "b")
2670 .unwrap();
2671 let after_insert = <_ as DiffOps<_, _, _>>::insert(frozen.clone(), insert2);
2672 assert_eq!(after_insert.len(), 2);
2673
2674 let delete: PatchDelete<TestTable, String, Vec<u8>> =
2675 PatchDelete::new(table.clone(), vec![Value::Integer(1)]);
2676 let after_delete = <_ as DiffOps<_, _, _>>::delete(frozen.clone(), delete);
2677 assert_eq!(after_delete.len(), 0);
2679
2680 let update = Update::<TestTable, PatchsetFormat, String, Vec<u8>>::from(table)
2681 .set(0, 1i64)
2682 .unwrap()
2683 .set(1, "z")
2684 .unwrap();
2685 let after_update = <_ as DiffOps<_, _, _>>::update(frozen, update);
2686 assert_eq!(after_update.len(), 1);
2687 }
2688
2689 #[test]
2690 fn test_diffset_changeset_iter_yields_inserts_and_indirect_flag() {
2691 let table = TestTable::new("t", 2, 0);
2692 let direct = Insert::from(table.clone()).set(0, 1i64).unwrap();
2693 let indirect = Insert::from(table.clone())
2694 .set(0, 2i64)
2695 .unwrap()
2696 .indirect(true);
2697 let frozen: DiffSet<ChangesetFormat, TestTable, String, Vec<u8>> = ChangesetBuilder::new()
2698 .insert(direct)
2699 .insert(indirect)
2700 .into();
2701
2702 let ops: Vec<_> = frozen.iter().collect();
2703 assert_eq!(ops.len(), 2);
2704 for op in &ops {
2705 assert_eq!(crate::DynTable::name(op.table()), "t");
2706 }
2707 assert!(matches!(
2708 ops[0],
2709 ChangesetOp::Insert {
2710 indirect: false,
2711 ..
2712 }
2713 ));
2714 assert!(matches!(ops[1], ChangesetOp::Insert { indirect: true, .. }));
2715 }
2716
2717 #[test]
2718 fn test_diffset_changeset_iter_yields_update_and_delete() {
2719 let table = TestTable::new("t", 2, 0);
2720 let starting = Insert::from(table.clone())
2721 .set(0, 1i64)
2722 .unwrap()
2723 .set(1, "a")
2724 .unwrap();
2725 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
2726 .set(0, 2i64, 2i64)
2727 .unwrap()
2728 .set(1, "before", "after")
2729 .unwrap();
2730 let delete = ChangeDelete::from(table.clone())
2731 .set(0, 3i64)
2732 .unwrap()
2733 .set(1, "gone")
2734 .unwrap();
2735 let frozen: DiffSet<ChangesetFormat, TestTable, String, Vec<u8>> = ChangesetBuilder::new()
2736 .insert(starting)
2737 .update(update)
2738 .delete(delete)
2739 .into();
2740
2741 let kinds: Vec<&'static str> = frozen
2742 .iter()
2743 .map(|op| match op {
2744 ChangesetOp::Insert { .. } => "insert",
2745 ChangesetOp::Update { .. } => "update",
2746 ChangesetOp::Delete { .. } => "delete",
2747 })
2748 .collect();
2749 assert_eq!(kinds, ["insert", "update", "delete"]);
2750 }
2751
2752 #[test]
2753 fn test_diffset_patchset_iter_exposes_pk_for_delete_and_update() {
2754 let table = TestTable::new("t", 2, 0);
2755 let starting = Insert::from(table.clone())
2756 .set(0, 1i64)
2757 .unwrap()
2758 .set(1, "a")
2759 .unwrap();
2760 let update = Update::<TestTable, PatchsetFormat, String, Vec<u8>>::from(table.clone())
2761 .set(0, 5i64)
2762 .unwrap()
2763 .set(1, "z")
2764 .unwrap();
2765 let delete: PatchDelete<TestTable, String, Vec<u8>> =
2766 PatchDelete::new(table.clone(), vec![Value::Integer(7)]);
2767
2768 let frozen: DiffSet<PatchsetFormat, TestTable, String, Vec<u8>> = PatchSet::new()
2769 .insert(starting)
2770 .update(update)
2771 .delete(delete)
2772 .into();
2773
2774 let mut saw_insert = false;
2775 let mut saw_update_pk: Option<i64> = None;
2776 let mut saw_delete_pk: Option<i64> = None;
2777 for op in frozen.iter() {
2778 match op {
2779 PatchsetOp::Insert { values, .. } => {
2780 saw_insert = true;
2781 assert!(matches!(values[0], Value::Integer(1)));
2782 }
2783 PatchsetOp::Update { pk, .. } => {
2784 if let Value::Integer(id) = pk[0] {
2785 saw_update_pk = Some(id);
2786 }
2787 }
2788 PatchsetOp::Delete { pk, .. } => {
2789 if let Value::Integer(id) = pk[0] {
2790 saw_delete_pk = Some(id);
2791 }
2792 }
2793 }
2794 }
2795 assert!(saw_insert);
2796 assert_eq!(saw_update_pk, Some(5));
2797 assert_eq!(saw_delete_pk, Some(7));
2798 }
2799
2800 #[test]
2801 fn test_diffset_tables_skips_empty() {
2802 let t1 = TestTable::new("t1", 2, 0);
2803 let t2 = TestTable::new("t2", 2, 0);
2804 let insert_t1 = Insert::from(t1.clone()).set(0, 1i64).unwrap();
2805 let insert_then_delete_t2 = Insert::from(t2.clone()).set(0, 9i64).unwrap();
2806 let delete_t2 = ChangeDelete::from(t2.clone()).set(0, 9i64).unwrap();
2807 let frozen: DiffSet<ChangesetFormat, TestTable, String, Vec<u8>> = ChangesetBuilder::new()
2808 .insert(insert_t1)
2809 .insert(insert_then_delete_t2)
2810 .delete(delete_t2)
2811 .into();
2812
2813 let names: Vec<&str> = frozen.tables().map(crate::DynTable::name).collect();
2814 assert_eq!(names, ["t1"]);
2815 }
2816}