1use indexmap::IndexMap as IndexMapRaw;
52
53use alloc::vec;
54use alloc::vec::Vec;
55use core::fmt::Debug;
56use core::hash::Hash;
57use core::ops::{BitOr, BitOrAssign};
58
59use crate::{
60 SchemaWithPK,
61 builders::{
62 ChangeDelete, ChangesetFormat, ChangesetOp, Insert, Operation, PatchDelete, PatchsetFormat,
63 PatchsetOp, Update, format::Format,
64 },
65 encoding::{MaybeValue, Value, encode_defined_value, encode_value, markers, op_codes},
66};
67
68type IndexMap<K, V> = IndexMapRaw<K, V, hashbrown::DefaultHashBuilder>;
70
71type RowMap<F, S, B> = IndexMap<Vec<Value<S, B>>, Operation<F, S, B>>;
73
74type TableMap<F, T, S, B> = IndexMap<T, RowMap<F, S, B>>;
76
77type RowVec<F, S, B> = Vec<(Vec<Value<S, B>>, Operation<F, S, B>)>;
79
80type TableVec<F, T, S, B> = Vec<(T, RowVec<F, S, B>)>;
82
83const fn hash_append(h: u32, add: u32) -> u32 {
91 (h << 3) ^ h ^ add
92}
93
94#[allow(clippy::cast_sign_loss)]
98fn session_hash_append_i64(h: u32, i: i64) -> u32 {
99 let lo = (i as u64 & 0xFFFF_FFFF) as u32;
100 let hi = ((i as u64 >> 32) & 0xFFFF_FFFF) as u32;
101 let h = hash_append(h, lo);
102 hash_append(h, hi)
103}
104
105fn session_hash_append_blob(mut h: u32, data: &[u8]) -> u32 {
109 for &byte in data {
110 h = hash_append(h, u32::from(byte));
111 }
112 h
113}
114
115fn session_hash_pk<S: AsRef<str>, B: AsRef<[u8]>>(pk: &[Value<S, B>]) -> u32 {
120 let mut h: u32 = 0;
121 for value in pk {
122 match value {
123 Value::Integer(i) => {
124 h = hash_append(h, 1); h = session_hash_append_i64(h, *i);
126 }
127 Value::Real(f) => {
128 h = hash_append(h, 2); let i = i64::from_ne_bytes(f.to_ne_bytes());
131 h = session_hash_append_i64(h, i);
132 }
133 Value::Text(s) => {
134 h = hash_append(h, 3); h = session_hash_append_blob(h, s.as_ref().as_bytes());
136 }
137 Value::Blob(b) => {
138 h = hash_append(h, 4); h = session_hash_append_blob(h, b.as_ref());
140 }
141 Value::Null => {
142 }
145 }
146 }
147 h
148}
149
150fn session_row_order<S: AsRef<str>, B: AsRef<[u8]>, V>(
160 rows: &IndexMap<Vec<Value<S, B>>, V>,
161) -> Vec<usize> {
162 let n = rows.len();
163 if n == 0 {
164 return Vec::new();
165 }
166
167 let pks: Vec<&Vec<Value<S, B>>> = rows.keys().collect();
168
169 let mut n_change: usize = 0;
173 let mut buckets: Vec<Vec<usize>> = Vec::new();
174
175 for idx in 0..n {
176 if n_change == 0 || idx >= n_change / 2 {
180 let new_size = if n_change == 0 { 256 } else { n_change * 2 };
181 let mut new_buckets: Vec<Vec<usize>> = vec![Vec::new(); new_size];
182
183 for old_bucket in &buckets {
190 for &entry_idx in old_bucket.iter().rev() {
191 let h = session_hash_pk(pks[entry_idx]) as usize % new_size;
192 new_buckets[h].push(entry_idx);
193 }
194 }
195
196 buckets = new_buckets;
197 n_change = new_size;
198 }
199
200 let h = session_hash_pk(pks[idx]) as usize % n_change;
202 buckets[h].push(idx);
203 }
204
205 let mut order = Vec::with_capacity(n);
208 for bucket in &buckets {
209 for &idx in bucket.iter().rev() {
210 order.push(idx);
211 }
212 }
213
214 order
215}
216
217fn write_table_header<T: SchemaWithPK>(out: &mut Vec<u8>, marker: u8, table: &T) {
229 out.push(marker);
230
231 let num_cols = table.number_of_columns();
232 debug_assert!(u64::try_from(num_cols).is_ok(), "column count exceeds u64");
235 out.extend(crate::encoding::varint::encode_varint(num_cols as u64));
236
237 let pk_start = out.len();
238 out.resize(pk_start + num_cols, 0);
239 table.write_pk_flags(&mut out[pk_start..]);
240
241 out.extend(table.name().as_bytes());
242 out.push(0);
243}
244
245fn patchset_pk_mapping<T: SchemaWithPK>(table: &T) -> (Vec<u8>, Vec<Option<usize>>) {
250 let num_cols = table.number_of_columns();
251 let mut pk_flags = alloc::vec![0u8; num_cols];
252 table.write_pk_flags(&mut pk_flags);
253
254 let mut pk_col_to_pk_pos: Vec<Option<usize>> = alloc::vec![None; num_cols];
255 let mut pk_cols: Vec<(usize, u8)> = pk_flags
256 .iter()
257 .enumerate()
258 .filter_map(|(i, &ord)| if ord > 0 { Some((i, ord)) } else { None })
259 .collect();
260 pk_cols.sort_by_key(|(_, ord)| *ord);
261 for (pos, (col_idx, _)) in pk_cols.into_iter().enumerate() {
262 pk_col_to_pk_pos[col_idx] = Some(pos);
263 }
264
265 (pk_flags, pk_col_to_pk_pos)
266}
267
268fn encode_patchset_delete_values<S: AsRef<str>, B: AsRef<[u8]>>(
270 out: &mut Vec<u8>,
271 pk_flags: &[u8],
272 pk_col_to_pk_pos: &[Option<usize>],
273 pk: &[Value<S, B>],
274) {
275 for (col_idx, &pk_flag) in pk_flags.iter().enumerate() {
276 if pk_flag > 0 {
277 if let Some(pk_pos) = pk_col_to_pk_pos[col_idx] {
278 encode_value(out, Some(&pk[pk_pos]));
279 } else {
280 encode_value::<S, B>(out, None);
281 }
282 }
283 }
284}
285
286fn encode_changeset_op<S: AsRef<str> + Clone + Debug, B: AsRef<[u8]> + Clone + Debug>(
288 out: &mut Vec<u8>,
289 op: &Operation<ChangesetFormat, S, B>,
290) {
291 match op {
292 Operation::Insert { values, indirect } => {
293 out.push(op_codes::INSERT);
294 out.push(u8::from(*indirect));
295 for value in values {
296 encode_value(out, Some(value));
297 }
298 }
299 Operation::Delete {
300 data: values,
301 indirect,
302 } => {
303 out.push(op_codes::DELETE);
304 out.push(u8::from(*indirect));
305 for value in values {
306 encode_value(out, Some(value));
307 }
308 }
309 Operation::Update { values, indirect } => {
310 out.push(op_codes::UPDATE);
311 out.push(u8::from(*indirect));
312 for (old, _new) in values {
313 encode_value(out, old.as_ref());
314 }
315 for (_old, new) in values {
316 encode_value(out, new.as_ref());
317 }
318 }
319 }
320}
321
322fn encode_patchset_op<S: AsRef<str>, B: AsRef<[u8]>>(
326 out: &mut Vec<u8>,
327 op: &Operation<PatchsetFormat, S, B>,
328 pk: &[Value<S, B>],
329 pk_flags: &[u8],
330 pk_col_to_pk_pos: &[Option<usize>],
331) {
332 match op {
333 Operation::Insert { values, indirect } => {
334 out.push(op_codes::INSERT);
335 out.push(u8::from(*indirect));
336 for value in values {
337 encode_value(out, Some(value));
338 }
339 }
340 Operation::Delete { data: (), indirect } => {
341 out.push(op_codes::DELETE);
342 out.push(u8::from(*indirect));
343 encode_patchset_delete_values(out, pk_flags, pk_col_to_pk_pos, pk);
344 }
345 Operation::Update { values, indirect } => {
346 out.push(op_codes::UPDATE);
347 out.push(u8::from(*indirect));
348 for (col_idx, &pk_flag) in pk_flags.iter().enumerate() {
353 if pk_flag > 0 {
354 debug_assert!(
355 pk_col_to_pk_pos[col_idx].is_some(),
356 "a primary key column always has a primary key vector position"
357 );
358 if let Some(pk_pos) = pk_col_to_pk_pos[col_idx] {
359 encode_defined_value(out, &pk[pk_pos]);
360 }
361 } else {
362 encode_value(out, values[col_idx].1.as_ref());
363 }
364 }
365 }
366 }
367}
368
369#[derive(Debug, Clone)]
384pub struct DiffSetBuilder<F: Format<S, B>, T: SchemaWithPK, S, B> {
385 pub(crate) tables: TableMap<F, T, S, B>,
386}
387
388impl<F: Format<S, B>, T: SchemaWithPK, S, B> PartialEq for DiffSetBuilder<F, T, S, B>
399where
400 S: PartialEq + Eq + Hash + AsRef<str>,
401 B: PartialEq + Eq + Hash + AsRef<[u8]>,
402 F::Old: PartialEq,
403 F::DeleteData: PartialEq,
404{
405 fn eq(&self, other: &Self) -> bool {
406 self.tables
409 .iter()
410 .filter(|(_, ops)| !ops.is_empty())
411 .eq(other.tables.iter().filter(|(_, ops)| !ops.is_empty()))
412 }
413}
414
415impl<F: Format<S, B>, T: SchemaWithPK, S, B> Eq for DiffSetBuilder<F, T, S, B>
416where
417 S: Eq + Hash + AsRef<str>,
418 B: Eq + Hash + AsRef<[u8]>,
419 F::Old: Eq,
420 F::DeleteData: Eq,
421{
422}
423
424pub type ChangeSet<T, S, B> = DiffSetBuilder<ChangesetFormat, T, S, B>;
426pub type PatchSet<T, S, B> = DiffSetBuilder<PatchsetFormat, T, S, B>;
428
429impl<F: Format<S, B>, T: SchemaWithPK, S: AsRef<str> + Hash + Eq, B: AsRef<[u8]> + Hash + Eq>
430 Default for DiffSetBuilder<F, T, S, B>
431{
432 fn default() -> Self {
433 Self::new()
434 }
435}
436
437impl<
438 T: SchemaWithPK,
439 S: Clone + Debug + Hash + Eq + AsRef<str>,
440 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
441> From<&DiffSetBuilder<ChangesetFormat, T, S, B>> for Vec<u8>
442{
443 #[inline]
444 fn from(builder: &DiffSetBuilder<ChangesetFormat, T, S, B>) -> Self {
445 builder.build()
446 }
447}
448
449impl<
450 T: SchemaWithPK,
451 S: Clone + Debug + Hash + Eq + AsRef<str>,
452 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
453> From<DiffSetBuilder<ChangesetFormat, T, S, B>> for Vec<u8>
454{
455 #[inline]
456 fn from(builder: DiffSetBuilder<ChangesetFormat, T, S, B>) -> Self {
457 builder.build()
458 }
459}
460
461impl<T: SchemaWithPK, S: AsRef<str> + Clone + Hash + Eq, B: AsRef<[u8]> + Clone + Hash + Eq>
462 From<&DiffSetBuilder<PatchsetFormat, T, S, B>> for Vec<u8>
463{
464 #[inline]
465 fn from(builder: &DiffSetBuilder<PatchsetFormat, T, S, B>) -> Self {
466 builder.build()
467 }
468}
469
470impl<T: SchemaWithPK, S: AsRef<str> + Clone + Hash + Eq, B: AsRef<[u8]> + Clone + Hash + Eq>
471 From<DiffSetBuilder<PatchsetFormat, T, S, B>> for Vec<u8>
472{
473 #[inline]
474 fn from(builder: DiffSetBuilder<PatchsetFormat, T, S, B>) -> Self {
475 builder.build()
476 }
477}
478
479impl<F: Format<S, B>, T: SchemaWithPK, S, B> DiffSetBuilder<F, T, S, B> {
480 pub(super) fn table<'builder>(&'builder self, name: &str) -> Option<&'builder T> {
482 self.tables.keys().find(|t| t.name() == name)
483 }
484}
485
486impl<F: Format<S, B>, T: SchemaWithPK, S: AsRef<str> + Hash + Eq, B: AsRef<[u8]> + Hash + Eq>
487 DiffSetBuilder<F, T, S, B>
488{
489 #[inline]
491 #[must_use]
492 pub fn new() -> Self {
493 Self {
494 tables: IndexMap::default(),
495 }
496 }
497
498 #[inline]
503 fn ensure_table(&mut self, table: &T) -> &mut RowMap<F, S, B> {
504 self.tables.entry(table.clone()).or_default()
505 }
506
507 pub fn add_table(&mut self, table: &T) -> &mut Self {
513 self.ensure_table(table);
514 self
515 }
516
517 #[inline]
519 #[must_use]
520 pub fn is_empty(&self) -> bool {
521 self.tables.values().all(IndexMap::is_empty)
522 }
523
524 #[inline]
526 #[must_use]
527 pub fn len(&self) -> usize {
528 self.tables.values().map(IndexMap::len).sum()
529 }
530
531 pub(crate) fn add_operation(
535 &mut self,
536 table: &T,
537 pk: Vec<Value<S, B>>,
538 new_op: Operation<F, S, B>,
539 ) -> &mut Self
540 where
541 S: Clone,
542 B: Clone,
543 Operation<F, S, B>: core::ops::Add<Output = Option<Operation<F, S, B>>>,
544 {
545 let rows = self.ensure_table(table);
546
547 match rows.shift_remove_full(&pk) {
548 None => {
549 rows.insert(pk, new_op);
550 }
551 Some((original_index, _removed_key, existing)) => {
552 match (&existing, &new_op) {
554 (Operation::Insert { .. }, Operation::Update { .. }) => {
555 if let Some(combined) = existing + new_op
557 && let Operation::Insert { values, .. } = &combined
558 {
559 let new_pk = table.extract_pk(values);
560 rows.shift_remove(&new_pk);
562 let index = original_index.min(rows.len());
563 rows.shift_insert(index, new_pk, combined);
564 }
565 }
566 _ => {
567 if let Some(combined) = existing + new_op {
569 rows.shift_insert(original_index, pk, combined);
571 }
572 }
573 }
574 }
575 }
576
577 self
578 }
579}
580
581impl<F, T, S, B> DiffSetBuilder<F, T, S, B>
584where
585 F: Format<S, B>,
586 T: crate::schema::NamedColumns,
587 S: Clone + Debug + Hash + Eq + AsRef<str> + Default,
588 B: Clone + Debug + Hash + Eq + AsRef<[u8]> + Default,
589{
590 pub fn digest<E, Sch, A>(self, event: &E, schema: &Sch, adapter: &A) -> Result<Self, E::Error>
597 where
598 E: crate::wire::Digestable<F, T, S, B>,
599 Sch: crate::wire::WireSchema<Table = T>,
600 A: crate::wire::WireAdapter<E::Src, S, B>,
601 T: crate::wire::WireColumnTypes,
602 {
603 event.digest_into(self, schema, adapter)
604 }
605}
606
607pub trait DiffOps<T: SchemaWithPK, S, B>: Sized {
617 type Format: Format<S, B>;
619
620 type DeleteArg;
625
626 fn insert(self, insert: Insert<T, S, B>) -> DiffSetBuilder<Self::Format, T, S, B>;
628
629 fn delete(self, delete: Self::DeleteArg) -> DiffSetBuilder<Self::Format, T, S, B>;
631
632 fn update(self, update: Update<T, Self::Format, S, B>)
634 -> DiffSetBuilder<Self::Format, T, S, B>;
635}
636
637impl<
640 T: SchemaWithPK,
641 S: Clone + Debug + Hash + Eq + AsRef<str>,
642 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
643> DiffOps<T, S, B> for DiffSetBuilder<ChangesetFormat, T, S, B>
644{
645 type Format = ChangesetFormat;
646 type DeleteArg = ChangeDelete<T, S, B>;
647
648 fn insert(mut self, insert: Insert<T, S, B>) -> Self {
649 let pk = insert.extract_pk();
650 let table = insert.as_ref().clone();
651 let indirect = insert.indirect;
652 self.add_operation(
653 &table,
654 pk,
655 Operation::Insert {
656 values: insert.into_values(),
657 indirect,
658 },
659 );
660 self
661 }
662
663 fn delete(mut self, delete: ChangeDelete<T, S, B>) -> Self {
664 let pk = delete.as_ref().extract_pk(&delete.values);
665 let table = delete.as_ref().clone();
666 let indirect = delete.indirect;
667 self.add_operation(
668 &table,
669 pk,
670 Operation::Delete {
671 data: delete.into_values(),
672 indirect,
673 },
674 );
675 self
676 }
677
678 fn update(mut self, update: Update<T, ChangesetFormat, S, B>) -> Self {
679 let old_values: Vec<_> = update
680 .values()
681 .iter()
682 .map(|(old, _): &(_, _)| old.clone().unwrap_or(Value::Null))
683 .collect();
684 let pk = update.as_ref().extract_pk(&old_values);
685 let table = update.as_ref().clone();
686 let indirect = update.indirect;
687 let values: Vec<(MaybeValue<S, B>, MaybeValue<S, B>)> = update.into();
688 self.add_operation(&table, pk, Operation::Update { values, indirect });
689 self
690 }
691}
692
693impl<T: SchemaWithPK, S: Clone + Hash + Eq + AsRef<str>, B: Clone + Hash + Eq + AsRef<[u8]>>
696 DiffOps<T, S, B> for DiffSetBuilder<PatchsetFormat, T, S, B>
697{
698 type Format = PatchsetFormat;
699 type DeleteArg = PatchDelete<T, S, B>;
700
701 fn insert(mut self, insert: Insert<T, S, B>) -> Self {
702 let pk = insert.extract_pk();
703 let table = insert.as_ref().clone();
704 let indirect = insert.indirect;
705 self.add_operation(
706 &table,
707 pk,
708 Operation::Insert {
709 values: insert.into_values(),
710 indirect,
711 },
712 );
713 self
714 }
715
716 fn delete(mut self, delete: PatchDelete<T, S, B>) -> Self {
731 let indirect = delete.indirect;
732 self.add_operation(
733 &delete.table,
734 delete.pk,
735 Operation::Delete { data: (), indirect },
736 );
737 self
738 }
739
740 fn update(mut self, update: Update<T, PatchsetFormat, S, B>) -> Self {
759 let pk = update.extract_pk();
760 let table = update.as_ref().clone();
761 let indirect = update.indirect;
762 let values: Vec<((), MaybeValue<S, B>)> = update.into();
763 self.add_operation(&table, pk, Operation::Update { values, indirect });
764 self
765 }
766}
767
768impl<
771 T: SchemaWithPK,
772 S: Clone + Debug + Hash + Eq + AsRef<str>,
773 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
774> DiffOps<T, S, B> for DiffSet<ChangesetFormat, T, S, B>
775{
776 type Format = ChangesetFormat;
777 type DeleteArg = ChangeDelete<T, S, B>;
778
779 fn insert(self, insert: Insert<T, S, B>) -> DiffSetBuilder<ChangesetFormat, T, S, B> {
780 let builder: DiffSetBuilder<ChangesetFormat, T, S, B> = self.into();
781 builder.insert(insert)
782 }
783
784 fn delete(self, delete: ChangeDelete<T, S, B>) -> DiffSetBuilder<ChangesetFormat, T, S, B> {
785 let builder: DiffSetBuilder<ChangesetFormat, T, S, B> = self.into();
786 builder.delete(delete)
787 }
788
789 fn update(
790 self,
791 update: Update<T, ChangesetFormat, S, B>,
792 ) -> DiffSetBuilder<ChangesetFormat, T, S, B> {
793 let builder: DiffSetBuilder<ChangesetFormat, T, S, B> = self.into();
794 builder.update(update)
795 }
796}
797
798impl<T: SchemaWithPK, S: Clone + Hash + Eq + AsRef<str>, B: Clone + Hash + Eq + AsRef<[u8]>>
801 DiffOps<T, S, B> for DiffSet<PatchsetFormat, T, S, B>
802{
803 type Format = PatchsetFormat;
804 type DeleteArg = PatchDelete<T, S, B>;
805
806 fn insert(self, insert: Insert<T, S, B>) -> DiffSetBuilder<PatchsetFormat, T, S, B> {
807 let builder: DiffSetBuilder<PatchsetFormat, T, S, B> = self.into();
808 builder.insert(insert)
809 }
810
811 fn delete(self, delete: PatchDelete<T, S, B>) -> DiffSetBuilder<PatchsetFormat, T, S, B> {
812 let builder: DiffSetBuilder<PatchsetFormat, T, S, B> = self.into();
813 builder.delete(delete)
814 }
815
816 fn update(
817 self,
818 update: Update<T, PatchsetFormat, S, B>,
819 ) -> DiffSetBuilder<PatchsetFormat, T, S, B> {
820 let builder: DiffSetBuilder<PatchsetFormat, T, S, B> = self.into();
821 builder.update(update)
822 }
823}
824
825impl<T: crate::schema::NamedColumns, S: Clone + Hash + Eq + AsRef<str> + for<'a> From<&'a str>>
826 DiffSetBuilder<PatchsetFormat, T, S, Vec<u8>>
827{
828 pub fn digest_sql<'input>(
858 &mut self,
859 input: &'input str,
860 ) -> Result<&mut Self, crate::builders::sql::ParseError<'input>> {
861 let mut parser = crate::builders::sql::Parser::new(input, self);
862 parser.digest_all()?;
863 let pending = parser.into_pending();
864 for (table, pk, op) in pending {
865 self.add_operation(&table, pk, op);
866 }
867 Ok(self)
868 }
869}
870
871impl<
876 T: SchemaWithPK,
877 S: Clone + Debug + Hash + Eq + AsRef<str>,
878 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
879> DiffSetBuilder<ChangesetFormat, T, S, B>
880{
881 #[must_use]
890 pub fn build(&self) -> Vec<u8> {
891 let mut out = Vec::new();
892
893 for (table, rows) in &self.tables {
894 if rows.is_empty() {
895 continue;
896 }
897
898 write_table_header(&mut out, markers::CHANGESET, table);
899
900 for idx in session_row_order(rows) {
901 let (_pk, op) = rows.get_index(idx).unwrap();
902 encode_changeset_op(&mut out, op);
903 }
904 }
905
906 out
907 }
908}
909
910impl<T: SchemaWithPK, S: Clone + Debug + AsRef<str>, B: Clone + Debug + AsRef<[u8]>>
911 DiffSetBuilder<ChangesetFormat, T, S, B>
912{
913 pub fn iter(&self) -> impl Iterator<Item = ChangesetOp<'_, T, S, B>> {
919 self.tables.iter().flat_map(|(table, rows)| {
920 rows.iter().map(move |(_pk, op)| match op {
921 Operation::Insert { values, indirect } => ChangesetOp::Insert {
922 table,
923 values: values.as_slice(),
924 indirect: *indirect,
925 },
926 Operation::Update { values, indirect } => ChangesetOp::Update {
927 table,
928 values: values.as_slice(),
929 indirect: *indirect,
930 },
931 Operation::Delete { data, indirect } => ChangesetOp::Delete {
932 table,
933 old_values: data.as_slice(),
934 indirect: *indirect,
935 },
936 })
937 })
938 }
939}
940
941impl<T: SchemaWithPK, S: Clone + Hash + Eq + AsRef<str>, B: Clone + Hash + Eq + AsRef<[u8]>>
942 DiffSetBuilder<PatchsetFormat, T, S, B>
943{
944 #[must_use]
953 pub fn build(&self) -> Vec<u8> {
954 let mut out = Vec::new();
955
956 for (table, rows) in &self.tables {
957 if rows.is_empty() {
958 continue;
959 }
960
961 write_table_header(&mut out, markers::PATCHSET, table);
962
963 let (pk_flags, pk_col_to_pk_pos) = patchset_pk_mapping(table);
964
965 for idx in session_row_order(rows) {
966 let (pk, op) = rows.get_index(idx).unwrap();
967 encode_patchset_op(&mut out, op, pk, &pk_flags, &pk_col_to_pk_pos);
968 }
969 }
970
971 out
972 }
973}
974
975impl<T: SchemaWithPK, S: Clone + AsRef<str>, B: Clone + AsRef<[u8]>>
976 DiffSetBuilder<PatchsetFormat, T, S, B>
977{
978 pub fn iter(&self) -> impl Iterator<Item = PatchsetOp<'_, T, S, B>> {
986 self.tables.iter().flat_map(|(table, rows)| {
987 rows.iter().map(move |(pk, op)| match op {
988 Operation::Insert { values, indirect } => PatchsetOp::Insert {
989 table,
990 values: values.as_slice(),
991 indirect: *indirect,
992 },
993 Operation::Update { values, indirect } => PatchsetOp::Update {
994 table,
995 pk: pk.as_slice(),
996 entries: values.as_slice(),
997 indirect: *indirect,
998 },
999 Operation::Delete { indirect, .. } => PatchsetOp::Delete {
1000 table,
1001 pk: pk.as_slice(),
1002 indirect: *indirect,
1003 },
1004 })
1005 })
1006 }
1007}
1008
1009use crate::builders::operation::Reverse;
1014
1015impl<
1016 T: SchemaWithPK,
1017 S: Clone + Debug + Hash + Eq + AsRef<str>,
1018 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
1019> Reverse for DiffSetBuilder<ChangesetFormat, T, S, B>
1020{
1021 type Output = DiffSetBuilder<ChangesetFormat, T, S, B>;
1022
1023 fn reverse(self) -> Self::Output {
1024 let mut reversed: DiffSetBuilder<ChangesetFormat, T, S, B> = DiffSetBuilder::new();
1025
1026 for (table, rows) in self.tables {
1027 for (pk, op) in rows {
1028 let rev_op = op.reverse();
1029
1030 reversed.add_operation(&table, pk, rev_op);
1031 }
1032 }
1033
1034 reversed
1035 }
1036}
1037
1038impl<
1044 F: Format<S, B>,
1045 T: SchemaWithPK,
1046 S: Clone + Hash + Eq + AsRef<str>,
1047 B: Clone + Hash + Eq + AsRef<[u8]>,
1048> BitOrAssign for DiffSetBuilder<F, T, S, B>
1049where
1050 Operation<F, S, B>: core::ops::Add<Output = Option<Operation<F, S, B>>>,
1051{
1052 fn bitor_assign(&mut self, rhs: Self) {
1054 for (table, rows) in rhs.tables {
1055 for (pk, op) in rows {
1056 self.add_operation(&table, pk, op);
1057 }
1058 }
1059 }
1060}
1061
1062impl<
1063 F: Format<S, B>,
1064 T: SchemaWithPK,
1065 S: Clone + Hash + Eq + AsRef<str>,
1066 B: Clone + Hash + Eq + AsRef<[u8]>,
1067> BitOr for DiffSetBuilder<F, T, S, B>
1068where
1069 Operation<F, S, B>: core::ops::Add<Output = Option<Operation<F, S, B>>>,
1070{
1071 type Output = Self;
1072
1073 #[inline]
1075 fn bitor(mut self, rhs: Self) -> Self::Output {
1076 self |= rhs;
1077 self
1078 }
1079}
1080
1081#[derive(Debug, Clone)]
1099pub struct DiffSet<F: Format<S, B>, T: SchemaWithPK, S, B> {
1100 pub(crate) tables: TableVec<F, T, S, B>,
1102}
1103
1104impl<F: Format<S, B>, T: SchemaWithPK, S, B> PartialEq for DiffSet<F, T, S, B>
1107where
1108 S: PartialEq + Eq + Hash + AsRef<str>,
1109 B: PartialEq + Eq + Hash + AsRef<[u8]>,
1110 F::Old: PartialEq,
1111 F::DeleteData: PartialEq,
1112{
1113 fn eq(&self, other: &Self) -> bool {
1114 self.tables
1115 .iter()
1116 .filter(|(_, ops)| !ops.is_empty())
1117 .eq(other.tables.iter().filter(|(_, ops)| !ops.is_empty()))
1118 }
1119}
1120
1121impl<F: Format<S, B>, T: SchemaWithPK, S, B> Eq for DiffSet<F, T, S, B>
1122where
1123 S: Eq + Hash + AsRef<str>,
1124 B: Eq + Hash + AsRef<[u8]>,
1125 F::Old: Eq,
1126 F::DeleteData: Eq,
1127{
1128}
1129
1130impl<F: Format<S, B>, T: SchemaWithPK, S: AsRef<str> + Hash + Eq, B: AsRef<[u8]> + Hash + Eq>
1131 Default for DiffSet<F, T, S, B>
1132{
1133 fn default() -> Self {
1134 Self { tables: Vec::new() }
1135 }
1136}
1137
1138impl<F: Format<S, B>, T: SchemaWithPK, S: AsRef<str> + Hash + Eq, B: AsRef<[u8]> + Hash + Eq>
1139 DiffSet<F, T, S, B>
1140{
1141 #[inline]
1143 #[must_use]
1144 pub fn is_empty(&self) -> bool {
1145 self.tables.iter().all(|(_, rows)| rows.is_empty())
1146 }
1147
1148 #[inline]
1150 #[must_use]
1151 pub fn len(&self) -> usize {
1152 self.tables.iter().map(|(_, rows)| rows.len()).sum()
1153 }
1154}
1155
1156impl<F: Format<S, B>, T: SchemaWithPK, S: AsRef<str> + Hash + Eq, B: AsRef<[u8]> + Hash + Eq>
1159 DiffSet<F, T, S, B>
1160{
1161 pub fn tables(&self) -> impl Iterator<Item = &T> {
1164 self.tables
1165 .iter()
1166 .filter(|(_, rows)| !rows.is_empty())
1167 .map(|(t, _)| t)
1168 }
1169}
1170
1171impl<T: SchemaWithPK, S: Clone + Debug + AsRef<str>, B: Clone + Debug + AsRef<[u8]>>
1172 DiffSet<ChangesetFormat, T, S, B>
1173{
1174 pub fn iter(&self) -> impl Iterator<Item = ChangesetOp<'_, T, S, B>> {
1180 self.tables.iter().flat_map(|(table, rows)| {
1181 rows.iter().map(move |(_pk, op)| match op {
1182 Operation::Insert { values, indirect } => ChangesetOp::Insert {
1183 table,
1184 values: values.as_slice(),
1185 indirect: *indirect,
1186 },
1187 Operation::Update { values, indirect } => ChangesetOp::Update {
1188 table,
1189 values: values.as_slice(),
1190 indirect: *indirect,
1191 },
1192 Operation::Delete { data, indirect } => ChangesetOp::Delete {
1193 table,
1194 old_values: data.as_slice(),
1195 indirect: *indirect,
1196 },
1197 })
1198 })
1199 }
1200}
1201
1202impl<T: SchemaWithPK, S: Clone + AsRef<str>, B: Clone + AsRef<[u8]>>
1203 DiffSet<PatchsetFormat, T, S, B>
1204{
1205 pub fn iter(&self) -> impl Iterator<Item = PatchsetOp<'_, T, S, B>> {
1212 self.tables.iter().flat_map(|(table, rows)| {
1213 rows.iter().map(move |(pk, op)| match op {
1214 Operation::Insert { values, indirect } => PatchsetOp::Insert {
1215 table,
1216 values: values.as_slice(),
1217 indirect: *indirect,
1218 },
1219 Operation::Update { values, indirect } => PatchsetOp::Update {
1220 table,
1221 pk: pk.as_slice(),
1222 entries: values.as_slice(),
1223 indirect: *indirect,
1224 },
1225 Operation::Delete { indirect, .. } => PatchsetOp::Delete {
1226 table,
1227 pk: pk.as_slice(),
1228 indirect: *indirect,
1229 },
1230 })
1231 })
1232 }
1233}
1234
1235impl<
1238 T: SchemaWithPK,
1239 S: Clone + Debug + Hash + Eq + AsRef<str>,
1240 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
1241> DiffSet<ChangesetFormat, T, S, B>
1242{
1243 #[must_use]
1247 pub fn build(&self) -> Vec<u8> {
1248 let mut out = Vec::new();
1249
1250 for (table, rows) in &self.tables {
1251 if rows.is_empty() {
1252 continue;
1253 }
1254
1255 write_table_header(&mut out, markers::CHANGESET, table);
1256
1257 for (_pk, op) in rows {
1258 encode_changeset_op(&mut out, op);
1259 }
1260 }
1261
1262 out
1263 }
1264}
1265
1266impl<T: SchemaWithPK, S: Clone + Hash + Eq + AsRef<str>, B: Clone + Hash + Eq + AsRef<[u8]>>
1269 DiffSet<PatchsetFormat, T, S, B>
1270{
1271 #[must_use]
1275 pub fn build(&self) -> Vec<u8> {
1276 let mut out = Vec::new();
1277
1278 for (table, rows) in &self.tables {
1279 if rows.is_empty() {
1280 continue;
1281 }
1282
1283 write_table_header(&mut out, markers::PATCHSET, table);
1284
1285 let (pk_flags, pk_col_to_pk_pos) = patchset_pk_mapping(table);
1286
1287 for (pk, op) in rows {
1288 encode_patchset_op(&mut out, op, pk, &pk_flags, &pk_col_to_pk_pos);
1289 }
1290 }
1291
1292 out
1293 }
1294}
1295
1296impl<
1299 T: SchemaWithPK,
1300 S: Clone + Debug + Hash + Eq + AsRef<str>,
1301 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
1302> From<&DiffSet<ChangesetFormat, T, S, B>> for Vec<u8>
1303{
1304 #[inline]
1305 fn from(diffset: &DiffSet<ChangesetFormat, T, S, B>) -> Self {
1306 diffset.build()
1307 }
1308}
1309
1310impl<
1311 T: SchemaWithPK,
1312 S: Clone + Debug + Hash + Eq + AsRef<str>,
1313 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
1314> From<DiffSet<ChangesetFormat, T, S, B>> for Vec<u8>
1315{
1316 #[inline]
1317 fn from(diffset: DiffSet<ChangesetFormat, T, S, B>) -> Self {
1318 diffset.build()
1319 }
1320}
1321
1322impl<T: SchemaWithPK, S: AsRef<str> + Clone + Hash + Eq, B: AsRef<[u8]> + Clone + Hash + Eq>
1323 From<&DiffSet<PatchsetFormat, T, S, B>> for Vec<u8>
1324{
1325 #[inline]
1326 fn from(diffset: &DiffSet<PatchsetFormat, T, S, B>) -> Self {
1327 diffset.build()
1328 }
1329}
1330
1331impl<T: SchemaWithPK, S: AsRef<str> + Clone + Hash + Eq, B: AsRef<[u8]> + Clone + Hash + Eq>
1332 From<DiffSet<PatchsetFormat, T, S, B>> for Vec<u8>
1333{
1334 #[inline]
1335 fn from(diffset: DiffSet<PatchsetFormat, T, S, B>) -> Self {
1336 diffset.build()
1337 }
1338}
1339
1340impl<
1343 T: SchemaWithPK,
1344 S: Clone + Debug + Hash + Eq + AsRef<str>,
1345 B: Clone + Debug + Hash + Eq + AsRef<[u8]>,
1346> Reverse for DiffSet<ChangesetFormat, T, S, B>
1347{
1348 type Output = DiffSet<ChangesetFormat, T, S, B>;
1349
1350 fn reverse(self) -> Self::Output {
1351 DiffSet {
1352 tables: self
1353 .tables
1354 .into_iter()
1355 .map(|(table, rows)| {
1356 let rev_rows = rows
1357 .into_iter()
1358 .map(|(pk, op)| (pk, op.reverse()))
1359 .collect();
1360 (table, rev_rows)
1361 })
1362 .collect(),
1363 }
1364 }
1365}
1366
1367impl<F: Format<S, B>, T: SchemaWithPK, S: Hash + Eq + AsRef<str>, B: Hash + Eq + AsRef<[u8]>>
1370 From<DiffSetBuilder<F, T, S, B>> for DiffSet<F, T, S, B>
1371{
1372 fn from(builder: DiffSetBuilder<F, T, S, B>) -> Self {
1373 Self {
1374 tables: builder
1375 .tables
1376 .into_iter()
1377 .map(|(table, rows)| {
1378 let ordered_rows: RowVec<F, S, B> = rows.into_iter().collect();
1379 (table, ordered_rows)
1380 })
1381 .collect(),
1382 }
1383 }
1384}
1385
1386impl<F: Format<S, B>, T: SchemaWithPK, S: Hash + Eq + AsRef<str>, B: Hash + Eq + AsRef<[u8]>>
1387 From<DiffSet<F, T, S, B>> for DiffSetBuilder<F, T, S, B>
1388{
1389 fn from(diffset: DiffSet<F, T, S, B>) -> Self {
1390 let mut builder = Self::new();
1391 for (table, rows) in diffset.tables {
1392 let map: IndexMap<Vec<Value<S, B>>, Operation<F, S, B>> = rows.into_iter().collect();
1393 builder.tables.insert(table, map);
1394 }
1395 builder
1396 }
1397}
1398
1399#[cfg(test)]
1400mod tests {
1401 use super::*;
1402 use crate::builders::operation::Indirect;
1403 use crate::encoding::Value;
1404 use alloc::{string::String, vec};
1405
1406 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
1408 struct TestTable {
1409 name: String,
1410 num_columns: usize,
1411 pk_column: usize,
1412 }
1413
1414 impl TestTable {
1415 fn new(name: &str, num_columns: usize, pk_column: usize) -> Self {
1416 Self {
1417 name: name.into(),
1418 num_columns,
1419 pk_column,
1420 }
1421 }
1422 }
1423
1424 impl crate::DynTable for TestTable {
1425 fn name(&self) -> &str {
1426 &self.name
1427 }
1428
1429 fn number_of_columns(&self) -> usize {
1430 self.num_columns
1431 }
1432
1433 fn write_pk_flags(&self, buf: &mut [u8]) {
1434 assert_eq!(buf.len(), self.num_columns);
1435 buf.fill(0);
1436 buf[self.pk_column] = 1;
1437 }
1438 }
1439
1440 impl crate::SchemaWithPK for TestTable {
1441 fn number_of_primary_keys(&self) -> usize {
1442 1
1443 }
1444
1445 fn primary_key_index(&self, col_idx: usize) -> Option<usize> {
1446 if col_idx == self.pk_column {
1447 Some(0)
1448 } else {
1449 None
1450 }
1451 }
1452
1453 fn extract_pk<S: Clone, B: Clone>(
1454 &self,
1455 values: &impl crate::IndexableValues<Text = S, Binary = B>,
1456 ) -> alloc::vec::Vec<Value<S, B>> {
1457 alloc::vec![
1458 values
1459 .get(self.pk_column)
1460 .expect("primary key column index out of bounds, values shorter than schema")
1461 ]
1462 }
1463 }
1464
1465 type ChangesetBuilder = DiffSetBuilder<ChangesetFormat, TestTable, String, Vec<u8>>;
1467
1468 #[test]
1469 fn test_insert_single_row() {
1470 let table = TestTable::new("users", 2, 0);
1471 let insert = Insert::from(table.clone())
1472 .set(0, 1i64)
1473 .unwrap()
1474 .set(1, "alice")
1475 .unwrap();
1476
1477 let builder = ChangesetBuilder::new().insert(insert);
1478
1479 assert_eq!(builder.len(), 1);
1480 assert!(!builder.is_empty());
1481 }
1482
1483 #[test]
1484 fn test_insert_then_delete_cancels_out() {
1485 let table = TestTable::new("users", 2, 0);
1486
1487 let insert = Insert::from(table.clone())
1488 .set(0, 1i64)
1489 .unwrap()
1490 .set(1, "alice")
1491 .unwrap();
1492
1493 let delete = ChangeDelete::from(table.clone())
1494 .set(0, 1i64)
1495 .unwrap()
1496 .set(1, "alice")
1497 .unwrap();
1498
1499 let builder = ChangesetBuilder::new().insert(insert).delete(delete);
1500
1501 assert_eq!(builder.len(), 0);
1502 assert!(builder.is_empty());
1503 }
1504
1505 #[test]
1506 fn test_insert_then_update_becomes_insert() {
1507 let table = TestTable::new("users", 2, 0);
1508
1509 let insert = Insert::from(table.clone())
1510 .set(0, 1i64)
1511 .unwrap()
1512 .set(1, "alice")
1513 .unwrap();
1514
1515 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
1516 .set(0, 1i64, 1i64) .unwrap()
1518 .set(1, "alice", "bob")
1519 .unwrap();
1520
1521 let builder = ChangesetBuilder::new().insert(insert).update(update);
1522
1523 assert_eq!(builder.len(), 1);
1524 }
1526
1527 #[test]
1528 fn test_delete_then_insert_same_values_cancels_out() {
1529 let table = TestTable::new("users", 2, 0);
1530
1531 let delete = ChangeDelete::from(table.clone())
1532 .set(0, 1i64)
1533 .unwrap()
1534 .set(1, "alice")
1535 .unwrap();
1536
1537 let insert = Insert::from(table.clone())
1538 .set(0, 1i64)
1539 .unwrap()
1540 .set(1, "alice")
1541 .unwrap();
1542
1543 let builder = ChangesetBuilder::new().delete(delete).insert(insert);
1544
1545 assert_eq!(builder.len(), 0);
1546 assert!(builder.is_empty());
1547 }
1548
1549 #[test]
1550 fn test_delete_then_insert_different_values_becomes_update() {
1551 let table = TestTable::new("users", 2, 0);
1552
1553 let delete = ChangeDelete::from(table.clone())
1554 .set(0, 1i64)
1555 .unwrap()
1556 .set(1, "alice")
1557 .unwrap();
1558
1559 let insert = Insert::from(table.clone())
1560 .set(0, 1i64)
1561 .unwrap()
1562 .set(1, "bob")
1563 .unwrap();
1564
1565 let builder = ChangesetBuilder::new().delete(delete).insert(insert);
1566
1567 assert_eq!(builder.len(), 1);
1568 }
1570
1571 #[test]
1572 fn test_multiple_rows() {
1573 let table = TestTable::new("users", 2, 0);
1574
1575 let insert1 = Insert::from(table.clone())
1576 .set(0, 1i64)
1577 .unwrap()
1578 .set(1, "alice")
1579 .unwrap();
1580
1581 let insert2 = Insert::from(table.clone())
1582 .set(0, 2i64)
1583 .unwrap()
1584 .set(1, "bob")
1585 .unwrap();
1586
1587 let builder = ChangesetBuilder::new().insert(insert1).insert(insert2);
1588
1589 assert_eq!(builder.len(), 2);
1590 }
1591
1592 #[test]
1593 fn test_update_then_update_consolidates() {
1594 let table = TestTable::new("users", 2, 0);
1595
1596 let update1 = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
1597 .set(0, 1i64, 1i64)
1598 .unwrap()
1599 .set(1, "alice", "bob")
1600 .unwrap();
1601
1602 let update2 = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
1603 .set(0, 1i64, 1i64)
1604 .unwrap()
1605 .set(1, "bob", "charlie")
1606 .unwrap();
1607
1608 let builder = ChangesetBuilder::new().update(update1).update(update2);
1609
1610 assert_eq!(builder.len(), 1);
1611 }
1613
1614 #[test]
1619 fn test_reverse_operation_insert_becomes_delete() {
1620 let op: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Insert {
1621 values: vec![Value::Integer(1), Value::Text("alice".into())],
1622 indirect: false,
1623 };
1624 let reversed = op.reverse();
1625 let Operation::Delete { data, .. } = reversed else {
1626 panic!("Expected Delete operation");
1627 };
1628 assert_eq!(
1629 data,
1630 vec![
1631 Value::<String, Vec<u8>>::Integer(1),
1632 Value::Text("alice".into())
1633 ]
1634 );
1635 }
1636
1637 #[test]
1638 fn test_reverse_operation_delete_becomes_insert() {
1639 let op: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Delete {
1640 data: vec![Value::Integer(1), Value::Text("alice".into())],
1641 indirect: false,
1642 };
1643 let reversed = op.reverse();
1644 let Operation::Insert { values, .. } = reversed else {
1645 panic!("Expected Insert operation");
1646 };
1647 assert_eq!(
1648 values,
1649 vec![
1650 Value::<String, Vec<u8>>::Integer(1),
1651 Value::Text("alice".into())
1652 ]
1653 );
1654 }
1655
1656 #[test]
1657 fn test_reverse_operation_update_swaps_old_new() {
1658 let op: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Update {
1659 values: vec![
1660 (Some(Value::Integer(1)), Some(Value::Integer(1))),
1661 (
1662 Some(Value::Text("alice".into())),
1663 Some(Value::Text("bob".into())),
1664 ),
1665 ],
1666 indirect: false,
1667 };
1668 let reversed = op.reverse();
1669 let Operation::Update { values, .. } = reversed else {
1670 panic!("Expected Update operation");
1671 };
1672 assert_eq!(
1673 values[0],
1674 (Some(Value::Integer(1)), Some(Value::Integer(1)))
1675 );
1676 assert_eq!(
1677 values[1],
1678 (
1679 Some(Value::Text("bob".into())),
1680 Some(Value::Text("alice".into()))
1681 )
1682 );
1683 }
1684
1685 #[test]
1686 fn test_reverse_builder_insert_becomes_delete() {
1687 let table = TestTable::new("users", 2, 0);
1688 let insert = Insert::from(table.clone())
1689 .set(0, 1i64)
1690 .unwrap()
1691 .set(1, "alice")
1692 .unwrap();
1693
1694 let builder = ChangesetBuilder::new().insert(insert);
1695 let reversed = builder.reverse();
1696
1697 assert_eq!(reversed.len(), 1);
1698 let rows = reversed.tables.get(&table).unwrap();
1700 assert!(matches!(
1701 rows.values().next().unwrap(),
1702 Operation::Delete { .. }
1703 ));
1704 }
1705
1706 #[test]
1707 fn test_reverse_builder_delete_becomes_insert() {
1708 let table = TestTable::new("users", 2, 0);
1709 let delete = ChangeDelete::from(table.clone())
1710 .set(0, 1i64)
1711 .unwrap()
1712 .set(1, "alice")
1713 .unwrap();
1714
1715 let builder = ChangesetBuilder::new().delete(delete);
1716 let reversed = builder.reverse();
1717
1718 assert_eq!(reversed.len(), 1);
1719 let rows = reversed.tables.get(&table).unwrap();
1721 assert!(matches!(
1722 rows.values().next().unwrap(),
1723 Operation::Insert { .. }
1724 ));
1725 }
1726
1727 #[test]
1728 fn test_reverse_builder_update_swaps() {
1729 let table = TestTable::new("users", 2, 0);
1730 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
1731 .set(0, 1i64, 1i64)
1732 .unwrap()
1733 .set(1, "alice", "bob")
1734 .unwrap();
1735
1736 let builder = ChangesetBuilder::new().update(update);
1737 let reversed = builder.reverse();
1738
1739 assert_eq!(reversed.len(), 1);
1740 let rows = reversed.tables.get(&table).unwrap();
1742 let Operation::Update { values, .. } = rows.values().next().unwrap() else {
1743 panic!("Expected Update operation");
1744 };
1745 assert_eq!(
1746 values[1],
1747 (
1748 Some(Value::Text("bob".into())),
1749 Some(Value::Text("alice".into()))
1750 )
1751 );
1752 }
1753
1754 #[test]
1755 fn test_reverse_is_involutory() {
1756 let table = TestTable::new("users", 2, 0);
1758 let insert = Insert::from(table.clone())
1759 .set(0, 1i64)
1760 .unwrap()
1761 .set(1, "alice")
1762 .unwrap();
1763
1764 let original_values = insert.into_values();
1765 let insert2 = Insert::from(table.clone())
1766 .set(0, 1i64)
1767 .unwrap()
1768 .set(1, "alice")
1769 .unwrap();
1770 let builder = ChangesetBuilder::new().insert(insert2);
1771 let double_reversed = builder.reverse().reverse();
1772
1773 assert_eq!(double_reversed.len(), 1);
1774 let rows = double_reversed.tables.get(&table).unwrap();
1775 let Operation::Insert { values, .. } = rows.values().next().unwrap() else {
1776 panic!("Expected Insert operation");
1777 };
1778 assert_eq!(values, &original_values);
1779 }
1780
1781 #[test]
1786 fn test_build_empty_builder() {
1787 let builder = ChangesetBuilder::new();
1788 let bytes = builder.build();
1789 assert_eq!(bytes, [] as [u8; 0]);
1790 }
1791
1792 #[test]
1793 fn test_build_insert_format() {
1794 let table = TestTable::new("t", 2, 0);
1795 let insert = Insert::from(table.clone())
1796 .set(0, 1i64)
1797 .unwrap()
1798 .set(1, "a")
1799 .unwrap();
1800
1801 let builder = ChangesetBuilder::new().insert(insert);
1802 let bytes = builder.build();
1803
1804 assert_ne!(bytes, [] as [u8; 0]);
1808
1809 assert_eq!(bytes[0], b'T');
1811 assert_eq!(bytes[1], 2);
1813 assert_eq!(bytes[2], 1);
1815 assert_eq!(bytes[3], 0);
1816 assert_eq!(bytes[4], b't');
1818 assert_eq!(bytes[5], 0);
1819 assert_eq!(bytes[6], 0x12);
1821 assert_eq!(bytes[7], 0);
1823 }
1824
1825 #[test]
1826 fn test_build_delete_format() {
1827 let table = TestTable::new("t", 2, 0);
1828 let delete = ChangeDelete::from(table.clone())
1829 .set(0, 1i64)
1830 .unwrap()
1831 .set(1, "a")
1832 .unwrap();
1833
1834 let builder = ChangesetBuilder::new().delete(delete);
1835 let bytes = builder.build();
1836
1837 assert_ne!(bytes, [] as [u8; 0]);
1838 assert_eq!(bytes[0], b'T');
1839 assert_eq!(bytes[6], 0x09);
1841 }
1842
1843 #[test]
1844 fn test_build_update_format() {
1845 let table = TestTable::new("t", 2, 0);
1846 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
1847 .set(0, 1i64, 1i64)
1848 .unwrap()
1849 .set(1, "a", "b")
1850 .unwrap();
1851
1852 let builder = ChangesetBuilder::new().update(update);
1853 let bytes = builder.build();
1854
1855 assert_ne!(bytes, [] as [u8; 0]);
1856 assert_eq!(bytes[0], b'T');
1857 assert_eq!(bytes[6], 0x17);
1859 }
1860
1861 #[test]
1862 fn test_build_multiple_operations() {
1863 let table = TestTable::new("t", 2, 0);
1864
1865 let insert = Insert::from(table.clone())
1866 .set(0, 1i64)
1867 .unwrap()
1868 .set(1, "a")
1869 .unwrap();
1870
1871 let insert2 = Insert::from(table.clone())
1872 .set(0, 2i64)
1873 .unwrap()
1874 .set(1, "b")
1875 .unwrap();
1876
1877 let builder = ChangesetBuilder::new().insert(insert).insert(insert2);
1878 let bytes = builder.build();
1879
1880 assert_ne!(bytes, [] as [u8; 0]);
1881 assert_eq!(bytes[0], b'T');
1883 }
1884
1885 #[test]
1886 fn test_build_cancelled_operations_produce_empty() {
1887 let table = TestTable::new("t", 2, 0);
1888
1889 let insert = Insert::from(table.clone())
1890 .set(0, 1i64)
1891 .unwrap()
1892 .set(1, "a")
1893 .unwrap();
1894
1895 let delete = ChangeDelete::from(table.clone())
1896 .set(0, 1i64)
1897 .unwrap()
1898 .set(1, "a")
1899 .unwrap();
1900
1901 let builder = ChangesetBuilder::new().insert(insert).delete(delete);
1902 let bytes = builder.build();
1903
1904 assert_eq!(bytes, [] as [u8; 0]);
1906 }
1907
1908 #[test]
1913 fn test_bitor_changeset_disjoint_rows() {
1914 let table = TestTable::new("users", 2, 0);
1915
1916 let insert1 = Insert::from(table.clone())
1917 .set(0, 1i64)
1918 .unwrap()
1919 .set(1, "alice")
1920 .unwrap();
1921
1922 let insert2 = Insert::from(table.clone())
1923 .set(0, 2i64)
1924 .unwrap()
1925 .set(1, "bob")
1926 .unwrap();
1927
1928 let cs1 = ChangesetBuilder::new().insert(insert1);
1929 let cs2 = ChangesetBuilder::new().insert(insert2);
1930
1931 let merged = cs1 | cs2;
1932 assert_eq!(merged.len(), 2);
1933 }
1934
1935 #[test]
1936 fn test_bitor_changeset_consolidates_same_row() {
1937 let table = TestTable::new("users", 2, 0);
1938
1939 let insert = Insert::from(table.clone())
1941 .set(0, 1i64)
1942 .unwrap()
1943 .set(1, "alice")
1944 .unwrap();
1945
1946 let delete = ChangeDelete::from(table.clone())
1948 .set(0, 1i64)
1949 .unwrap()
1950 .set(1, "alice")
1951 .unwrap();
1952
1953 let cs1 = ChangesetBuilder::new().insert(insert);
1954 let cs2 = ChangesetBuilder::new().delete(delete);
1955
1956 let merged = cs1 | cs2;
1958 assert_eq!(merged.len(), 0);
1959 assert!(merged.is_empty());
1960 }
1961
1962 #[test]
1963 fn test_bitor_assign_changeset() {
1964 let table = TestTable::new("users", 2, 0);
1965
1966 let insert1 = Insert::from(table.clone())
1967 .set(0, 1i64)
1968 .unwrap()
1969 .set(1, "alice")
1970 .unwrap();
1971
1972 let insert2 = Insert::from(table.clone())
1973 .set(0, 2i64)
1974 .unwrap()
1975 .set(1, "bob")
1976 .unwrap();
1977
1978 let mut cs = ChangesetBuilder::new().insert(insert1);
1979 cs |= ChangesetBuilder::new().insert(insert2);
1980
1981 assert_eq!(cs.len(), 2);
1982 }
1983
1984 #[test]
1985 fn test_bitor_patchset_disjoint_rows() {
1986 type PatchsetBuilder = DiffSetBuilder<PatchsetFormat, TestTable, String, Vec<u8>>;
1987
1988 let table = TestTable::new("users", 2, 0);
1989
1990 let insert1 = Insert::from(table.clone())
1991 .set(0, 1i64)
1992 .unwrap()
1993 .set(1, "alice")
1994 .unwrap();
1995
1996 let insert2 = Insert::from(table.clone())
1997 .set(0, 2i64)
1998 .unwrap()
1999 .set(1, "bob")
2000 .unwrap();
2001
2002 let ps1 = PatchsetBuilder::new().insert(insert1);
2003 let ps2 = PatchsetBuilder::new().insert(insert2);
2004
2005 let merged = ps1 | ps2;
2006 assert_eq!(merged.len(), 2);
2007 }
2008
2009 #[test]
2010 fn test_bitor_patchset_consolidates_same_row() {
2011 type PatchsetBuilder = DiffSetBuilder<PatchsetFormat, TestTable, String, Vec<u8>>;
2012
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 delete = PatchDelete::new(table.clone(), vec![Value::Integer(1)]);
2024
2025 let ps1 = PatchsetBuilder::new().insert(insert);
2026 let ps2 = PatchsetBuilder::new().delete(delete);
2027
2028 let merged = ps1 | ps2;
2030 assert_eq!(merged.len(), 0);
2031 assert!(merged.is_empty());
2032 }
2033
2034 #[test]
2035 fn test_bitor_multiple_tables() {
2036 let table1 = TestTable::new("users", 2, 0);
2037 let table2 = TestTable::new("posts", 2, 0);
2038
2039 let insert1 = Insert::from(table1.clone())
2040 .set(0, 1i64)
2041 .unwrap()
2042 .set(1, "alice")
2043 .unwrap();
2044
2045 let insert2 = Insert::from(table2.clone())
2046 .set(0, 100i64)
2047 .unwrap()
2048 .set(1, "first post")
2049 .unwrap();
2050
2051 let cs1 = ChangesetBuilder::new().insert(insert1);
2052 let cs2 = ChangesetBuilder::new().insert(insert2);
2053
2054 let merged = cs1 | cs2;
2055 assert_eq!(merged.len(), 2);
2056 }
2057
2058 #[test]
2059 fn test_bitor_insert_then_update_consolidates() {
2060 let table = TestTable::new("users", 2, 0);
2061
2062 let insert = Insert::from(table.clone())
2064 .set(0, 1i64)
2065 .unwrap()
2066 .set(1, "alice")
2067 .unwrap();
2068
2069 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
2071 .set(0, 1i64, 1i64)
2072 .unwrap()
2073 .set(1, "alice", "alicia")
2074 .unwrap();
2075
2076 let cs1 = ChangesetBuilder::new().insert(insert);
2077 let cs2 = ChangesetBuilder::new().update(update);
2078
2079 let merged = cs1 | cs2;
2081 assert_eq!(merged.len(), 1);
2082 }
2083
2084 #[test]
2085 fn test_session_hash_growth_with_many_rows() {
2086 let table = TestTable::new("many", 2, 0);
2090 let mut cs = ChangesetBuilder::new();
2091 for i in 0..200i64 {
2092 let insert = Insert::from(table.clone())
2093 .set(0, i)
2094 .unwrap()
2095 .set(1, alloc::format!("row-{i}"))
2096 .unwrap();
2097 cs = cs.insert(insert);
2098 }
2099 assert_eq!(cs.len(), 200);
2100
2101 let bytes = cs.build();
2102 assert_ne!(bytes, [] as [u8; 0]);
2103
2104 let reparsed = crate::parser::ParsedDiffSet::try_from(bytes.as_slice()).unwrap();
2106 let reparsed_bytes: Vec<u8> = reparsed.into();
2107 assert_eq!(bytes, reparsed_bytes);
2108 }
2109
2110 const INDIRECT_BYTE_OFFSET: usize = 7;
2118
2119 #[test]
2120 fn test_build_insert_indirect_byte_set() {
2121 let table = TestTable::new("t", 2, 0);
2122 let insert = Insert::from(table)
2123 .set(0, 1i64)
2124 .unwrap()
2125 .set(1, "a")
2126 .unwrap()
2127 .indirect(true);
2128
2129 let bytes = ChangesetBuilder::new().insert(insert).build();
2130 assert_eq!(bytes[INDIRECT_BYTE_OFFSET], 1);
2131 }
2132
2133 #[test]
2134 fn test_build_delete_indirect_byte_set() {
2135 let table = TestTable::new("t", 2, 0);
2136 let delete = ChangeDelete::from(table)
2137 .set(0, 1i64)
2138 .unwrap()
2139 .set(1, "a")
2140 .unwrap()
2141 .indirect(true);
2142
2143 let bytes = ChangesetBuilder::new().delete(delete).build();
2144 assert_eq!(bytes[INDIRECT_BYTE_OFFSET], 1);
2145 }
2146
2147 #[test]
2148 fn test_build_update_indirect_byte_set() {
2149 let table = TestTable::new("t", 2, 0);
2150 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table)
2151 .set(0, 1i64, 1i64)
2152 .unwrap()
2153 .set(1, "a", "b")
2154 .unwrap()
2155 .indirect(true);
2156
2157 let bytes = ChangesetBuilder::new().update(update).build();
2158 assert_eq!(bytes[INDIRECT_BYTE_OFFSET], 1);
2159 }
2160
2161 #[test]
2162 fn test_build_patchset_insert_indirect_byte() {
2163 let table = TestTable::new("t", 2, 0);
2164 let insert = Insert::from(table)
2165 .set(0, 1i64)
2166 .unwrap()
2167 .set(1, "a")
2168 .unwrap()
2169 .indirect(true);
2170
2171 let patchset: PatchSet<TestTable, String, Vec<u8>> = PatchSet::new().insert(insert);
2172 let bytes = patchset.build();
2173 assert_eq!(bytes[INDIRECT_BYTE_OFFSET], 1);
2174 }
2175
2176 #[test]
2177 fn test_reverse_preserves_indirect() {
2178 let insert: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Insert {
2180 values: vec![Value::Integer(1)],
2181 indirect: true,
2182 };
2183 let reversed = insert.reverse();
2184 assert!(reversed.indirect());
2185 assert!(matches!(reversed, Operation::Delete { .. }));
2186
2187 let delete: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Delete {
2189 data: vec![Value::Integer(1)],
2190 indirect: true,
2191 };
2192 let reversed = delete.reverse();
2193 assert!(reversed.indirect());
2194 assert!(matches!(reversed, Operation::Insert { .. }));
2195
2196 let update: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Update {
2198 values: vec![(Some(Value::Integer(1)), Some(Value::Integer(2)))],
2199 indirect: true,
2200 };
2201 let reversed = update.reverse();
2202 assert!(reversed.indirect());
2203 assert!(matches!(reversed, Operation::Update { .. }));
2204 }
2205
2206 #[test]
2207 fn test_patchdelete_indirect_byte_set() {
2208 let table = TestTable::new("t", 2, 0);
2209 let delete: PatchDelete<TestTable, String, Vec<u8>> =
2210 PatchDelete::new(table, vec![Value::Integer(1)]).indirect(true);
2211 let bytes = PatchSet::new().delete(delete).build();
2212 assert_eq!(bytes[INDIRECT_BYTE_OFFSET], 1);
2213 }
2214
2215 #[test]
2216 fn test_operation_eq_indirect_differs() {
2217 let a: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Insert {
2219 values: vec![Value::Integer(1)],
2220 indirect: false,
2221 };
2222 let b: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Insert {
2223 values: vec![Value::Integer(1)],
2224 indirect: true,
2225 };
2226 assert_ne!(a, b);
2227 }
2228
2229 #[test]
2230 fn test_operation_eq_variant_mismatch() {
2231 let insert: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Insert {
2233 values: vec![Value::Integer(1)],
2234 indirect: false,
2235 };
2236 let delete: Operation<ChangesetFormat, String, Vec<u8>> = Operation::Delete {
2237 data: vec![Value::Integer(1)],
2238 indirect: false,
2239 };
2240 assert_ne!(insert, delete);
2241 }
2242
2243 #[test]
2244 fn test_bitor_indirect_rhs_wins() {
2245 let table = TestTable::new("t", 2, 0);
2248 let lhs_insert = Insert::from(table.clone())
2249 .set(0, 1i64)
2250 .unwrap()
2251 .set(1, "a")
2252 .unwrap()
2253 .indirect(true);
2254 let rhs_insert = Insert::from(table.clone())
2255 .set(0, 1i64)
2256 .unwrap()
2257 .set(1, "a")
2258 .unwrap()
2259 .indirect(false);
2260
2261 let merged =
2262 ChangesetBuilder::new().insert(lhs_insert) | ChangesetBuilder::new().insert(rhs_insert);
2263 let rows = merged.tables.get(&table).unwrap();
2264 let op = rows.values().next().unwrap();
2265 assert!(!op.indirect(), "rhs (false) should win over lhs (true)");
2266
2267 let lhs_insert = Insert::from(table.clone())
2269 .set(0, 2i64)
2270 .unwrap()
2271 .set(1, "b")
2272 .unwrap()
2273 .indirect(false);
2274 let rhs_insert = Insert::from(table.clone())
2275 .set(0, 2i64)
2276 .unwrap()
2277 .set(1, "b")
2278 .unwrap()
2279 .indirect(true);
2280
2281 let merged =
2282 ChangesetBuilder::new().insert(lhs_insert) | ChangesetBuilder::new().insert(rhs_insert);
2283 let rows = merged.tables.get(&table).unwrap();
2284 let op = rows.values().next().unwrap();
2285 assert!(op.indirect(), "rhs (true) should win over lhs (false)");
2286 }
2287
2288 #[test]
2289 fn test_roundtrip_indirect_changeset() {
2290 let table = TestTable::new("t", 2, 0);
2291 let insert = Insert::from(table)
2292 .set(0, 1i64)
2293 .unwrap()
2294 .set(1, "a")
2295 .unwrap()
2296 .indirect(true);
2297
2298 let bytes = ChangesetBuilder::new().insert(insert).build();
2299 let reparsed = crate::parser::ParsedDiffSet::try_from(bytes.as_slice()).unwrap();
2300 let reparsed_bytes: Vec<u8> = reparsed.into();
2301 assert_eq!(bytes, reparsed_bytes);
2302 assert_eq!(reparsed_bytes[INDIRECT_BYTE_OFFSET], 1);
2303 }
2304
2305 #[test]
2306 fn test_roundtrip_indirect_patchset() {
2307 let table = TestTable::new("t", 2, 0);
2308 let insert = Insert::from(table)
2309 .set(0, 1i64)
2310 .unwrap()
2311 .set(1, "a")
2312 .unwrap()
2313 .indirect(true);
2314
2315 let patchset: PatchSet<TestTable, String, Vec<u8>> = PatchSet::new().insert(insert);
2316 let bytes = patchset.build();
2317 let reparsed = crate::parser::ParsedDiffSet::try_from(bytes.as_slice()).unwrap();
2318 let reparsed_bytes: Vec<u8> = reparsed.into();
2319 assert_eq!(bytes, reparsed_bytes);
2320 assert_eq!(reparsed_bytes[INDIRECT_BYTE_OFFSET], 1);
2321 }
2322
2323 #[test]
2324 fn test_indirect_full_pipeline_roundtrip() {
2325 let table = TestTable::new("t", 2, 0);
2328 let insert = Insert::from(table)
2329 .set(0, 1i64)
2330 .unwrap()
2331 .set(1, "alice")
2332 .unwrap()
2333 .indirect(true);
2334 let original = ChangesetBuilder::new().insert(insert);
2335 let bytes_a = original.build();
2336 assert_eq!(bytes_a[INDIRECT_BYTE_OFFSET], 1);
2337
2338 let parsed = crate::parser::ParsedDiffSet::try_from(bytes_a.as_slice()).unwrap();
2340 let crate::parser::ParsedDiffSet::Changeset(parsed_set) = parsed else {
2341 panic!("expected Changeset variant");
2342 };
2343
2344 let parsed_builder: DiffSetBuilder<
2345 ChangesetFormat,
2346 crate::parser::TableSchema<String>,
2347 String,
2348 Vec<u8>,
2349 > = parsed_set.into();
2350 let empty: DiffSetBuilder<
2351 ChangesetFormat,
2352 crate::parser::TableSchema<String>,
2353 String,
2354 Vec<u8>,
2355 > = DiffSetBuilder::new();
2356 let doubled = parsed_builder.reverse().reverse();
2357 let merged = doubled | empty;
2358
2359 let bytes_b = merged.build();
2360 assert_eq!(bytes_a, bytes_b);
2361 assert_eq!(bytes_b[INDIRECT_BYTE_OFFSET], 1);
2362 }
2363
2364 fn changeset_insert(v: i64) -> Operation<ChangesetFormat, String, Vec<u8>> {
2369 Operation::Insert {
2370 values: vec![Value::Integer(v), Value::Text("a".into())],
2371 indirect: false,
2372 }
2373 }
2374
2375 fn changeset_delete(v: i64) -> Operation<ChangesetFormat, String, Vec<u8>> {
2376 Operation::Delete {
2377 data: vec![Value::Integer(v), Value::Text("a".into())],
2378 indirect: false,
2379 }
2380 }
2381
2382 fn changeset_update(old: i64, new: i64) -> Operation<ChangesetFormat, String, Vec<u8>> {
2383 Operation::Update {
2384 values: vec![
2385 (Some(Value::Integer(old)), Some(Value::Integer(new))),
2386 (Some(Value::Text("a".into())), Some(Value::Text("b".into()))),
2387 ],
2388 indirect: false,
2389 }
2390 }
2391
2392 #[test]
2393 fn test_add_changeset_insert_plus_update() {
2394 let merged = (changeset_insert(1) + changeset_update(1, 2)).unwrap();
2395 let Operation::Insert { values, .. } = merged else {
2396 panic!("expected Insert");
2397 };
2398 assert_eq!(values[0], Value::Integer(2));
2399 assert_eq!(values[1], Value::Text("b".into()));
2400 }
2401
2402 #[test]
2403 fn test_add_changeset_update_plus_insert() {
2404 let merged = (changeset_update(1, 2) + changeset_insert(99)).unwrap();
2406 assert!(matches!(merged, Operation::Update { .. }));
2407 }
2408
2409 #[test]
2410 fn test_add_changeset_update_plus_update() {
2411 let merged = (changeset_update(1, 2) + changeset_update(2, 3)).unwrap();
2413 let Operation::Update { values, .. } = merged else {
2414 panic!("expected Update");
2415 };
2416 assert_eq!(values[0].0, Some(Value::Integer(1)));
2417 assert_eq!(values[0].1, Some(Value::Integer(3)));
2418 }
2419
2420 #[test]
2421 fn test_add_changeset_update_plus_delete() {
2422 let merged = (changeset_update(1, 2) + changeset_delete(99)).unwrap();
2424 let Operation::Delete { data, .. } = merged else {
2425 panic!("expected Delete");
2426 };
2427 assert_eq!(data[0], Value::Integer(1));
2428 assert_eq!(data[1], Value::Text("a".into()));
2429 }
2430
2431 #[test]
2432 fn test_add_changeset_delete_plus_update_keeps_delete() {
2433 let merged = (changeset_delete(1) + changeset_update(1, 2)).unwrap();
2434 assert!(matches!(merged, Operation::Delete { .. }));
2435 }
2436
2437 #[test]
2438 fn test_add_changeset_delete_plus_delete_keeps_first() {
2439 let merged = (changeset_delete(1) + changeset_delete(2)).unwrap();
2440 let Operation::Delete { data, .. } = merged else {
2441 panic!("expected Delete");
2442 };
2443 assert_eq!(data[0], Value::Integer(1));
2444 }
2445
2446 fn patchset_insert(v: i64) -> Operation<PatchsetFormat, String, Vec<u8>> {
2447 Operation::Insert {
2448 values: vec![Value::Integer(v), Value::Text("a".into())],
2449 indirect: false,
2450 }
2451 }
2452
2453 fn patchset_update(new: i64) -> Operation<PatchsetFormat, String, Vec<u8>> {
2454 Operation::Update {
2455 values: vec![
2456 ((), Some(Value::Integer(new))),
2457 ((), Some(Value::Text("b".into()))),
2458 ],
2459 indirect: false,
2460 }
2461 }
2462
2463 fn patchset_delete() -> Operation<PatchsetFormat, String, Vec<u8>> {
2464 Operation::Delete {
2465 data: (),
2466 indirect: false,
2467 }
2468 }
2469
2470 #[test]
2471 fn test_add_patchset_insert_plus_update() {
2472 let merged = (patchset_insert(1) + patchset_update(2)).unwrap();
2473 let Operation::Insert { values, .. } = merged else {
2474 panic!("expected Insert");
2475 };
2476 assert_eq!(values[0], Value::Integer(2));
2477 assert_eq!(values[1], Value::Text("b".into()));
2478 }
2479
2480 #[test]
2481 fn test_add_patchset_update_plus_insert() {
2482 let merged = (patchset_update(2) + patchset_insert(99)).unwrap();
2483 assert!(matches!(merged, Operation::Update { .. }));
2484 }
2485
2486 #[test]
2487 fn test_add_patchset_update_plus_update() {
2488 let merged = (patchset_update(2) + patchset_update(3)).unwrap();
2489 let Operation::Update { values, .. } = merged else {
2490 panic!("expected Update");
2491 };
2492 assert_eq!(values[0].1, Some(Value::Integer(3)));
2493 }
2494
2495 #[test]
2496 fn test_add_patchset_update_plus_delete() {
2497 let merged = (patchset_update(2) + patchset_delete()).unwrap();
2498 assert!(matches!(merged, Operation::Delete { .. }));
2499 }
2500
2501 #[test]
2502 fn test_add_patchset_delete_plus_insert_promotes_to_update() {
2503 let merged = (patchset_delete() + patchset_insert(1)).unwrap();
2505 assert!(matches!(merged, Operation::Update { .. }));
2506 }
2507
2508 #[test]
2509 fn test_add_patchset_delete_plus_update_keeps_delete() {
2510 let merged = (patchset_delete() + patchset_update(2)).unwrap();
2511 assert!(matches!(merged, Operation::Delete { .. }));
2512 }
2513
2514 #[test]
2515 fn test_add_patchset_delete_plus_delete_keeps_first() {
2516 let merged = (patchset_delete() + patchset_delete()).unwrap();
2517 assert!(matches!(merged, Operation::Delete { .. }));
2518 }
2519
2520 #[test]
2525 fn test_session_hash_real_pk() {
2526 let table = TestTable::new("t", 2, 0);
2527 let insert = Insert::from(table)
2528 .set(0, 2.5f64)
2529 .unwrap()
2530 .set(1, "a")
2531 .unwrap();
2532 let bytes = ChangesetBuilder::new().insert(insert).build();
2533 assert_ne!(bytes, [] as [u8; 0]);
2534 }
2535
2536 #[test]
2537 fn test_session_hash_text_pk() {
2538 let table = TestTable::new("t", 2, 0);
2539 let insert = Insert::from(table)
2540 .set(0, "alice")
2541 .unwrap()
2542 .set(1, 42i64)
2543 .unwrap();
2544 let bytes = ChangesetBuilder::new().insert(insert).build();
2545 assert_ne!(bytes, [] as [u8; 0]);
2546 }
2547
2548 #[test]
2549 fn test_session_hash_blob_pk() {
2550 let table = TestTable::new("t", 2, 0);
2551 let insert = Insert::from(table)
2552 .set(0, alloc::vec![0xDE_u8, 0xAD, 0xBE, 0xEF])
2553 .unwrap()
2554 .set(1, "a")
2555 .unwrap();
2556 let bytes = ChangesetBuilder::new().insert(insert).build();
2557 assert_ne!(bytes, [] as [u8; 0]);
2558 }
2559
2560 #[test]
2565 fn test_session_row_order_empty() {
2566 let cs: ChangesetBuilder = ChangesetBuilder::new();
2568 let bytes = cs.build();
2569 assert_eq!(bytes, [] as [u8; 0]);
2570 }
2571
2572 #[test]
2573 fn test_session_row_order_empty_rows_returns_empty_vec() {
2574 let rows: RowMap<ChangesetFormat, String, Vec<u8>> = IndexMap::default();
2576 assert_eq!(session_row_order(&rows), [] as [usize; 0]);
2577 }
2578
2579 #[test]
2580 fn test_diffset_patchset_build_skips_empty_table() {
2581 let table = TestTable::new("t", 2, 0);
2583 let mut builder: PatchSet<TestTable, String, Vec<u8>> = PatchSet::new();
2584 builder.add_table(&table);
2585 let frozen: DiffSet<PatchsetFormat, TestTable, String, Vec<u8>> = builder.into();
2586 let bytes = frozen.build();
2587 assert_eq!(bytes, [] as [u8; 0]);
2588 }
2589
2590 #[test]
2595 fn test_from_changeset_builder_into_vec() {
2596 let table = TestTable::new("t", 2, 0);
2597 let insert = Insert::from(table)
2598 .set(0, 1i64)
2599 .unwrap()
2600 .set(1, "a")
2601 .unwrap();
2602 let builder = ChangesetBuilder::new().insert(insert);
2603 let bytes_owned: Vec<u8> = builder.clone().into();
2604 let bytes_ref: Vec<u8> = (&builder).into();
2605 assert_eq!(bytes_owned, bytes_ref);
2606 let frozen: DiffSet<ChangesetFormat, TestTable, String, Vec<u8>> = builder.into();
2607 let bytes_frozen_owned: Vec<u8> = frozen.clone().into();
2608 let bytes_frozen_ref: Vec<u8> = (&frozen).into();
2609 assert_eq!(bytes_frozen_owned, bytes_frozen_ref);
2610 }
2611
2612 #[test]
2613 fn test_from_patchset_builder_into_vec() {
2614 let table = TestTable::new("t", 2, 0);
2615 let insert = Insert::from(table)
2616 .set(0, 1i64)
2617 .unwrap()
2618 .set(1, "a")
2619 .unwrap();
2620 let builder: PatchSet<TestTable, String, Vec<u8>> = PatchSet::new().insert(insert);
2621 let bytes_owned: Vec<u8> = builder.clone().into();
2622 let bytes_ref: Vec<u8> = (&builder).into();
2623 assert_eq!(bytes_owned, bytes_ref);
2624 let frozen: DiffSet<PatchsetFormat, TestTable, String, Vec<u8>> = builder.into();
2625 let bytes_frozen_owned: Vec<u8> = frozen.clone().into();
2626 let bytes_frozen_ref: Vec<u8> = (&frozen).into();
2627 assert_eq!(bytes_frozen_owned, bytes_frozen_ref);
2628 }
2629
2630 #[test]
2635 fn test_add_operation_insert_then_update_changes_pk() {
2636 let table = TestTable::new("t", 2, 0);
2639 let insert = Insert::from(table.clone())
2640 .set(0, 1i64)
2641 .unwrap()
2642 .set(1, "alice")
2643 .unwrap();
2644 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
2645 .set(0, 1i64, 2i64)
2646 .unwrap()
2647 .set(1, "alice", "bob")
2648 .unwrap();
2649 let builder = ChangesetBuilder::new().insert(insert).update(update);
2650 let rows = builder.tables.get(&table).unwrap();
2651 assert_eq!(rows.len(), 1);
2652 let (pk, op) = rows.iter().next().unwrap();
2654 assert_eq!(pk[0], Value::Integer(2));
2655 let Operation::Insert { values, .. } = op else {
2656 panic!("expected merged INSERT");
2657 };
2658 assert_eq!(values[0], Value::Integer(2));
2659 assert_eq!(values[1], Value::Text("bob".into()));
2660 }
2661
2662 #[test]
2667 fn test_diffset_changeset_diffops_wrappers() {
2668 let table = TestTable::new("t", 2, 0);
2669 let initial = Insert::from(table.clone())
2670 .set(0, 1i64)
2671 .unwrap()
2672 .set(1, "a")
2673 .unwrap();
2674 let frozen: DiffSet<ChangesetFormat, TestTable, String, Vec<u8>> =
2675 ChangesetBuilder::new().insert(initial).into();
2676
2677 let insert2 = Insert::from(table.clone())
2678 .set(0, 2i64)
2679 .unwrap()
2680 .set(1, "b")
2681 .unwrap();
2682 let after_insert = <_ as DiffOps<_, _, _>>::insert(frozen.clone(), insert2);
2683 assert_eq!(after_insert.len(), 2);
2684
2685 let delete = ChangeDelete::from(table.clone())
2686 .set(0, 1i64)
2687 .unwrap()
2688 .set(1, "a")
2689 .unwrap();
2690 let after_delete = <_ as DiffOps<_, _, _>>::delete(frozen.clone(), delete);
2691 assert_eq!(after_delete.len(), 0);
2692
2693 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table)
2694 .set(0, 1i64, 1i64)
2695 .unwrap()
2696 .set(1, "a", "z")
2697 .unwrap();
2698 let after_update = <_ as DiffOps<_, _, _>>::update(frozen, update);
2699 assert_eq!(after_update.len(), 1);
2700 }
2701
2702 #[test]
2703 fn test_diffset_patchset_diffops_wrappers() {
2704 let table = TestTable::new("t", 2, 0);
2705 let initial = Insert::from(table.clone())
2706 .set(0, 1i64)
2707 .unwrap()
2708 .set(1, "a")
2709 .unwrap();
2710 let frozen: DiffSet<PatchsetFormat, TestTable, String, Vec<u8>> =
2711 PatchSet::new().insert(initial).into();
2712
2713 let insert2 = Insert::from(table.clone())
2714 .set(0, 2i64)
2715 .unwrap()
2716 .set(1, "b")
2717 .unwrap();
2718 let after_insert = <_ as DiffOps<_, _, _>>::insert(frozen.clone(), insert2);
2719 assert_eq!(after_insert.len(), 2);
2720
2721 let delete: PatchDelete<TestTable, String, Vec<u8>> =
2722 PatchDelete::new(table.clone(), vec![Value::Integer(1)]);
2723 let after_delete = <_ as DiffOps<_, _, _>>::delete(frozen.clone(), delete);
2724 assert_eq!(after_delete.len(), 0);
2726
2727 let update = Update::<TestTable, PatchsetFormat, String, Vec<u8>>::from(table)
2728 .set(0, 1i64)
2729 .unwrap()
2730 .set(1, "z")
2731 .unwrap();
2732 let after_update = <_ as DiffOps<_, _, _>>::update(frozen, update);
2733 assert_eq!(after_update.len(), 1);
2734 }
2735
2736 #[test]
2737 fn test_diffset_changeset_iter_yields_inserts_and_indirect_flag() {
2738 let table = TestTable::new("t", 2, 0);
2739 let direct = Insert::from(table.clone()).set(0, 1i64).unwrap();
2740 let indirect = Insert::from(table.clone())
2741 .set(0, 2i64)
2742 .unwrap()
2743 .indirect(true);
2744 let frozen: DiffSet<ChangesetFormat, TestTable, String, Vec<u8>> = ChangesetBuilder::new()
2745 .insert(direct)
2746 .insert(indirect)
2747 .into();
2748
2749 let ops: Vec<_> = frozen.iter().collect();
2750 assert_eq!(ops.len(), 2);
2751 for op in &ops {
2752 assert_eq!(crate::DynTable::name(op.table()), "t");
2753 }
2754 assert!(matches!(
2755 ops[0],
2756 ChangesetOp::Insert {
2757 indirect: false,
2758 ..
2759 }
2760 ));
2761 assert!(matches!(ops[1], ChangesetOp::Insert { indirect: true, .. }));
2762 }
2763
2764 #[test]
2765 fn test_diffset_changeset_iter_yields_update_and_delete() {
2766 let table = TestTable::new("t", 2, 0);
2767 let starting = Insert::from(table.clone())
2768 .set(0, 1i64)
2769 .unwrap()
2770 .set(1, "a")
2771 .unwrap();
2772 let update = Update::<TestTable, ChangesetFormat, String, Vec<u8>>::from(table.clone())
2773 .set(0, 2i64, 2i64)
2774 .unwrap()
2775 .set(1, "before", "after")
2776 .unwrap();
2777 let delete = ChangeDelete::from(table.clone())
2778 .set(0, 3i64)
2779 .unwrap()
2780 .set(1, "gone")
2781 .unwrap();
2782 let frozen: DiffSet<ChangesetFormat, TestTable, String, Vec<u8>> = ChangesetBuilder::new()
2783 .insert(starting)
2784 .update(update)
2785 .delete(delete)
2786 .into();
2787
2788 let kinds: Vec<&'static str> = frozen
2789 .iter()
2790 .map(|op| match op {
2791 ChangesetOp::Insert { .. } => "insert",
2792 ChangesetOp::Update { .. } => "update",
2793 ChangesetOp::Delete { .. } => "delete",
2794 })
2795 .collect();
2796 assert_eq!(kinds, ["insert", "update", "delete"]);
2797 }
2798
2799 #[test]
2800 fn test_diffset_patchset_iter_exposes_pk_for_delete_and_update() {
2801 let table = TestTable::new("t", 2, 0);
2802 let starting = Insert::from(table.clone())
2803 .set(0, 1i64)
2804 .unwrap()
2805 .set(1, "a")
2806 .unwrap();
2807 let update = Update::<TestTable, PatchsetFormat, String, Vec<u8>>::from(table.clone())
2808 .set(0, 5i64)
2809 .unwrap()
2810 .set(1, "z")
2811 .unwrap();
2812 let delete: PatchDelete<TestTable, String, Vec<u8>> =
2813 PatchDelete::new(table.clone(), vec![Value::Integer(7)]);
2814
2815 let frozen: DiffSet<PatchsetFormat, TestTable, String, Vec<u8>> = PatchSet::new()
2816 .insert(starting)
2817 .update(update)
2818 .delete(delete)
2819 .into();
2820
2821 let mut saw_insert = false;
2822 let mut saw_update_pk: Option<i64> = None;
2823 let mut saw_delete_pk: Option<i64> = None;
2824 for op in frozen.iter() {
2825 match op {
2826 PatchsetOp::Insert { values, .. } => {
2827 saw_insert = true;
2828 assert!(matches!(values[0], Value::Integer(1)));
2829 }
2830 PatchsetOp::Update { pk, .. } => {
2831 if let Value::Integer(id) = pk[0] {
2832 saw_update_pk = Some(id);
2833 }
2834 }
2835 PatchsetOp::Delete { pk, .. } => {
2836 if let Value::Integer(id) = pk[0] {
2837 saw_delete_pk = Some(id);
2838 }
2839 }
2840 }
2841 }
2842 assert!(saw_insert);
2843 assert_eq!(saw_update_pk, Some(5));
2844 assert_eq!(saw_delete_pk, Some(7));
2845 }
2846
2847 #[test]
2848 fn test_diffset_tables_skips_empty() {
2849 let t1 = TestTable::new("t1", 2, 0);
2850 let t2 = TestTable::new("t2", 2, 0);
2851 let insert_t1 = Insert::from(t1.clone()).set(0, 1i64).unwrap();
2852 let insert_then_delete_t2 = Insert::from(t2.clone()).set(0, 9i64).unwrap();
2853 let delete_t2 = ChangeDelete::from(t2.clone()).set(0, 9i64).unwrap();
2854 let frozen: DiffSet<ChangesetFormat, TestTable, String, Vec<u8>> = ChangesetBuilder::new()
2855 .insert(insert_t1)
2856 .insert(insert_then_delete_t2)
2857 .delete(delete_t2)
2858 .into();
2859
2860 let names: Vec<&str> = frozen.tables().map(crate::DynTable::name).collect();
2861 assert_eq!(names, ["t1"]);
2862 }
2863}