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