1use fsqlite_types::serial_type::{read_varint, varint_len, write_varint};
2use fsqlite_types::value::SqliteValue;
3
4const TABLE_HEADER_BYTE: u8 = 0x54;
10
11const OP_INSERT: u8 = 0x12; const OP_DELETE: u8 = 0x09; const OP_UPDATE: u8 = 0x17; const VAL_UNDEFINED: u8 = 0x00;
18const VAL_INTEGER: u8 = 0x01;
19const VAL_REAL: u8 = 0x02;
20const VAL_TEXT: u8 = 0x03;
21const VAL_BLOB: u8 = 0x04;
22const VAL_NULL: u8 = 0x05;
23
24#[must_use]
29pub const fn extension_name() -> &'static str {
30 "session"
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum ChangeOp {
40 Insert,
41 Delete,
42 Update,
43}
44
45impl ChangeOp {
46 #[must_use]
47 pub const fn as_byte(self) -> u8 {
48 match self {
49 Self::Insert => OP_INSERT,
50 Self::Delete => OP_DELETE,
51 Self::Update => OP_UPDATE,
52 }
53 }
54
55 #[must_use]
59 pub const fn from_byte(b: u8) -> Option<Self> {
60 match b {
61 OP_INSERT => Some(Self::Insert),
62 OP_DELETE => Some(Self::Delete),
63 OP_UPDATE => Some(Self::Update),
64 _ => None,
65 }
66 }
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
75pub enum ConflictType {
76 Data,
78 NotFound,
80 Conflict,
82 Constraint,
84 ForeignKey,
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90pub enum ConflictAction {
91 OmitChange,
93 Replace,
95 Abort,
97}
98
99#[derive(Debug, Clone, PartialEq)]
107pub enum ChangesetValue {
108 Undefined,
109 Null,
110 Integer(i64),
111 Real(f64),
112 Text(String),
113 Blob(Vec<u8>),
114}
115
116impl ChangesetValue {
117 #[must_use]
119 pub fn from_sqlite(val: &SqliteValue) -> Self {
120 match val {
121 SqliteValue::Null => Self::Null,
122 SqliteValue::Integer(i) => Self::Integer(*i),
123 SqliteValue::Float(f) => Self::Real(*f),
124 SqliteValue::Text(s) => Self::Text(s.clone()),
125 SqliteValue::Blob(b) => Self::Blob(b.clone()),
126 }
127 }
128
129 #[must_use]
131 pub fn to_sqlite(&self) -> SqliteValue {
132 match self {
133 Self::Undefined | Self::Null => SqliteValue::Null,
134 Self::Integer(i) => SqliteValue::Integer(*i),
135 Self::Real(f) => SqliteValue::Float(*f),
136 Self::Text(s) => SqliteValue::Text(s.clone()),
137 Self::Blob(b) => SqliteValue::Blob(b.clone()),
138 }
139 }
140
141 pub fn encode(&self, out: &mut Vec<u8>) {
143 match self {
144 Self::Undefined => {
145 out.push(VAL_UNDEFINED);
146 }
147 Self::Null => {
148 out.push(VAL_NULL);
149 }
150 Self::Integer(i) => {
151 out.push(VAL_INTEGER);
152 out.extend_from_slice(&i.to_be_bytes());
153 }
154 Self::Real(f) => {
155 out.push(VAL_REAL);
156 out.extend_from_slice(&f.to_be_bytes());
157 }
158 Self::Text(s) => {
159 out.push(VAL_TEXT);
160 let bytes = s.as_bytes();
161 let mut vbuf = [0u8; 9];
162 let vlen = write_varint(&mut vbuf, bytes.len() as u64);
163 out.extend_from_slice(&vbuf[..vlen]);
164 out.extend_from_slice(bytes);
165 }
166 Self::Blob(b) => {
167 out.push(VAL_BLOB);
168 let mut vbuf = [0u8; 9];
169 let vlen = write_varint(&mut vbuf, b.len() as u64);
170 out.extend_from_slice(&vbuf[..vlen]);
171 out.extend_from_slice(b);
172 }
173 }
174 }
175
176 pub fn decode(data: &[u8], pos: usize) -> Option<(Self, usize)> {
180 let type_byte = *data.get(pos)?;
181 let mut offset = pos + 1;
182 match type_byte {
183 VAL_UNDEFINED => Some((Self::Undefined, offset - pos)),
184 VAL_NULL => Some((Self::Null, offset - pos)),
185 VAL_INTEGER => {
186 let end = offset + 8;
187 if data.len() < end {
188 return None;
189 }
190 let arr: [u8; 8] = data[offset..end].try_into().ok()?;
191 Some((Self::Integer(i64::from_be_bytes(arr)), end - pos))
192 }
193 VAL_REAL => {
194 let end = offset + 8;
195 if data.len() < end {
196 return None;
197 }
198 let arr: [u8; 8] = data[offset..end].try_into().ok()?;
199 Some((Self::Real(f64::from_be_bytes(arr)), end - pos))
200 }
201 VAL_TEXT => {
202 let (len, vlen) = read_varint(&data[offset..])?;
203 offset += vlen;
204 let len = usize::try_from(len).ok()?;
205 let end = offset + len;
206 if data.len() < end {
207 return None;
208 }
209 let s = std::str::from_utf8(&data[offset..end]).ok()?;
210 Some((Self::Text(s.to_owned()), end - pos))
211 }
212 VAL_BLOB => {
213 let (len, vlen) = read_varint(&data[offset..])?;
214 offset += vlen;
215 let len = usize::try_from(len).ok()?;
216 let end = offset + len;
217 if data.len() < end {
218 return None;
219 }
220 Some((Self::Blob(data[offset..end].to_vec()), end - pos))
221 }
222 _ => None,
223 }
224 }
225}
226
227#[derive(Debug, Clone, PartialEq, Eq)]
233pub struct TableInfo {
234 pub name: String,
236 pub column_count: usize,
238 pub pk_flags: Vec<bool>,
240}
241
242impl TableInfo {
243 pub fn encode(&self, out: &mut Vec<u8>) {
245 out.push(TABLE_HEADER_BYTE);
246 let mut vbuf = [0u8; 9];
247 let vlen = write_varint(&mut vbuf, self.column_count as u64);
248 out.extend_from_slice(&vbuf[..vlen]);
249 for &pk in &self.pk_flags {
250 out.push(u8::from(pk));
251 }
252 out.extend_from_slice(self.name.as_bytes());
253 out.push(0x00); }
255
256 pub fn decode(data: &[u8], pos: usize) -> Option<(Self, usize)> {
260 if *data.get(pos)? != TABLE_HEADER_BYTE {
261 return None;
262 }
263 let mut offset = pos + 1;
264 let (col_count, vlen) = read_varint(&data[offset..])?;
265 offset += vlen;
266 let col_count = usize::try_from(col_count).ok()?;
267 if data.len() < offset + col_count {
268 return None;
269 }
270 let pk_flags: Vec<bool> = data[offset..offset + col_count]
271 .iter()
272 .map(|&b| b != 0)
273 .collect();
274 offset += col_count;
275 let name_start = offset;
277 let nul_pos = data[name_start..].iter().position(|&b| b == 0)?;
278 let name = std::str::from_utf8(&data[name_start..name_start + nul_pos])
279 .ok()?
280 .to_owned();
281 offset = name_start + nul_pos + 1;
282 Some((
283 Self {
284 name,
285 column_count: col_count,
286 pk_flags,
287 },
288 offset - pos,
289 ))
290 }
291}
292
293#[derive(Debug, Clone, PartialEq)]
299pub struct ChangesetRow {
300 pub op: ChangeOp,
301 pub old_values: Vec<ChangesetValue>,
303 pub new_values: Vec<ChangesetValue>,
305}
306
307impl ChangesetRow {
308 pub fn encode_changeset(&self, out: &mut Vec<u8>) {
310 out.push(self.op.as_byte());
311 match self.op {
312 ChangeOp::Insert => {
313 for v in &self.new_values {
314 v.encode(out);
315 }
316 }
317 ChangeOp::Delete => {
318 for v in &self.old_values {
319 v.encode(out);
320 }
321 }
322 ChangeOp::Update => {
323 for v in &self.old_values {
324 v.encode(out);
325 }
326 for v in &self.new_values {
327 v.encode(out);
328 }
329 }
330 }
331 }
332
333 pub fn encode_patchset(&self, out: &mut Vec<u8>, pk_flags: &[bool]) {
339 out.push(self.op.as_byte());
340 match self.op {
341 ChangeOp::Insert => {
342 for v in &self.new_values {
343 v.encode(out);
344 }
345 }
346 ChangeOp::Delete => {
347 for v in &self.old_values {
348 v.encode(out);
349 }
350 }
351 ChangeOp::Update => {
352 for (i, v) in self.old_values.iter().enumerate() {
354 if pk_flags.get(i).copied().unwrap_or(false) {
355 v.encode(out);
356 }
357 }
358 for v in &self.new_values {
359 v.encode(out);
360 }
361 }
362 }
363 }
364
365 pub fn decode_changeset(data: &[u8], pos: usize, col_count: usize) -> Option<(Self, usize)> {
367 let op = ChangeOp::from_byte(*data.get(pos)?)?;
368 let mut offset = pos + 1;
369
370 let decode_n = |data: &[u8], offset: &mut usize, n: usize| -> Option<Vec<ChangesetValue>> {
371 let mut vals = Vec::with_capacity(n);
372 for _ in 0..n {
373 let (v, consumed) = ChangesetValue::decode(data, *offset)?;
374 *offset += consumed;
375 vals.push(v);
376 }
377 Some(vals)
378 };
379
380 let (old_values, new_values) = match op {
381 ChangeOp::Insert => {
382 let new_values = decode_n(data, &mut offset, col_count)?;
383 (Vec::new(), new_values)
384 }
385 ChangeOp::Delete => {
386 let old_values = decode_n(data, &mut offset, col_count)?;
387 (old_values, Vec::new())
388 }
389 ChangeOp::Update => {
390 let old_values = decode_n(data, &mut offset, col_count)?;
391 let new_values = decode_n(data, &mut offset, col_count)?;
392 (old_values, new_values)
393 }
394 };
395
396 Some((
397 Self {
398 op,
399 old_values,
400 new_values,
401 },
402 offset - pos,
403 ))
404 }
405
406 #[must_use]
409 pub fn invert(&self) -> Self {
410 match self.op {
411 ChangeOp::Insert => Self {
412 op: ChangeOp::Delete,
413 old_values: self.new_values.clone(),
414 new_values: Vec::new(),
415 },
416 ChangeOp::Delete => Self {
417 op: ChangeOp::Insert,
418 old_values: Vec::new(),
419 new_values: self.old_values.clone(),
420 },
421 ChangeOp::Update => Self {
422 op: ChangeOp::Update,
423 old_values: self.new_values.clone(),
424 new_values: self.old_values.clone(),
425 },
426 }
427 }
428}
429
430#[derive(Debug, Clone, PartialEq)]
436pub struct TableChangeset {
437 pub info: TableInfo,
438 pub rows: Vec<ChangesetRow>,
439}
440
441impl TableChangeset {
442 pub fn encode_changeset(&self, out: &mut Vec<u8>) {
444 self.info.encode(out);
445 for row in &self.rows {
446 row.encode_changeset(out);
447 }
448 }
449
450 pub fn encode_patchset(&self, out: &mut Vec<u8>) {
452 self.info.encode(out);
453 for row in &self.rows {
454 row.encode_patchset(out, &self.info.pk_flags);
455 }
456 }
457}
458
459#[derive(Debug, Clone, PartialEq)]
465pub struct Changeset {
466 pub tables: Vec<TableChangeset>,
467}
468
469impl Changeset {
470 #[must_use]
472 pub fn new() -> Self {
473 Self { tables: Vec::new() }
474 }
475
476 #[must_use]
478 pub fn encode(&self) -> Vec<u8> {
479 let mut out = Vec::new();
480 for tc in &self.tables {
481 tc.encode_changeset(&mut out);
482 }
483 out
484 }
485
486 #[must_use]
488 pub fn encode_patchset(&self) -> Vec<u8> {
489 let mut out = Vec::new();
490 for tc in &self.tables {
491 tc.encode_patchset(&mut out);
492 }
493 out
494 }
495
496 pub fn decode(data: &[u8]) -> Option<Self> {
498 let mut tables = Vec::new();
499 let mut pos = 0;
500 while pos < data.len() {
501 let (info, consumed) = TableInfo::decode(data, pos)?;
502 pos += consumed;
503 let mut rows = Vec::new();
504 while pos < data.len() && data[pos] != TABLE_HEADER_BYTE {
506 let (row, consumed) = ChangesetRow::decode_changeset(data, pos, info.column_count)?;
507 pos += consumed;
508 rows.push(row);
509 }
510 tables.push(TableChangeset { info, rows });
511 }
512 Some(Self { tables })
513 }
514
515 #[must_use]
518 pub fn invert(&self) -> Self {
519 Self {
520 tables: self
521 .tables
522 .iter()
523 .map(|tc| TableChangeset {
524 info: tc.info.clone(),
525 rows: tc.rows.iter().map(ChangesetRow::invert).collect(),
526 })
527 .collect(),
528 }
529 }
530
531 pub fn concat(&mut self, other: &Self) {
533 for tc in &other.tables {
534 self.tables.push(tc.clone());
535 }
536 }
537}
538
539impl Default for Changeset {
540 fn default() -> Self {
541 Self::new()
542 }
543}
544
545#[derive(Debug, Clone)]
551struct TrackedChange {
552 table_name: String,
553 op: ChangeOp,
554 old_values: Vec<ChangesetValue>,
555 new_values: Vec<ChangesetValue>,
556}
557
558#[derive(Debug, Clone)]
560struct TrackedTable {
561 name: String,
562 column_count: usize,
563 pk_flags: Vec<bool>,
564}
565
566#[derive(Debug)]
573pub struct Session {
574 tables: Vec<TrackedTable>,
575 changes: Vec<TrackedChange>,
576}
577
578impl Session {
579 #[must_use]
581 pub fn new() -> Self {
582 Self {
583 tables: Vec::new(),
584 changes: Vec::new(),
585 }
586 }
587
588 pub fn attach_table(&mut self, name: &str, column_count: usize, pk_flags: Vec<bool>) {
592 assert_eq!(
593 pk_flags.len(),
594 column_count,
595 "pk_flags length must match column_count"
596 );
597 self.tables.push(TrackedTable {
598 name: name.to_owned(),
599 column_count,
600 pk_flags,
601 });
602 }
603
604 pub fn record_insert(&mut self, table: &str, new_values: Vec<ChangesetValue>) {
606 self.changes.push(TrackedChange {
607 table_name: table.to_owned(),
608 op: ChangeOp::Insert,
609 old_values: Vec::new(),
610 new_values,
611 });
612 }
613
614 pub fn record_delete(&mut self, table: &str, old_values: Vec<ChangesetValue>) {
616 self.changes.push(TrackedChange {
617 table_name: table.to_owned(),
618 op: ChangeOp::Delete,
619 old_values,
620 new_values: Vec::new(),
621 });
622 }
623
624 pub fn record_update(
629 &mut self,
630 table: &str,
631 old_values: Vec<ChangesetValue>,
632 new_values: Vec<ChangesetValue>,
633 ) {
634 self.changes.push(TrackedChange {
635 table_name: table.to_owned(),
636 op: ChangeOp::Update,
637 old_values,
638 new_values,
639 });
640 }
641
642 #[must_use]
644 pub fn changeset(&self) -> Changeset {
645 self.build_changeset_impl()
646 }
647
648 #[must_use]
650 pub fn patchset(&self) -> Vec<u8> {
651 let cs = self.build_changeset_impl();
652 cs.encode_patchset()
653 }
654
655 fn build_changeset_impl(&self) -> Changeset {
657 let mut table_map: std::collections::HashMap<String, Vec<ChangesetRow>> =
658 std::collections::HashMap::new();
659
660 for change in &self.changes {
661 table_map
662 .entry(change.table_name.clone())
663 .or_default()
664 .push(ChangesetRow {
665 op: change.op,
666 old_values: change.old_values.clone(),
667 new_values: change.new_values.clone(),
668 });
669 }
670
671 let mut tables = Vec::new();
672 for tracked in &self.tables {
674 if let Some(rows) = table_map.remove(&tracked.name) {
675 tables.push(TableChangeset {
676 info: TableInfo {
677 name: tracked.name.clone(),
678 column_count: tracked.column_count,
679 pk_flags: tracked.pk_flags.clone(),
680 },
681 rows,
682 });
683 }
684 }
685 for (name, rows) in table_map {
688 let col_count = rows.first().map_or(0, |r| {
689 if r.new_values.is_empty() {
690 r.old_values.len()
691 } else {
692 r.new_values.len()
693 }
694 });
695 tables.push(TableChangeset {
696 info: TableInfo {
697 name,
698 column_count: col_count,
699 pk_flags: vec![false; col_count],
700 },
701 rows,
702 });
703 }
704 Changeset { tables }
705 }
706}
707
708impl Default for Session {
709 fn default() -> Self {
710 Self::new()
711 }
712}
713
714#[derive(Debug, Clone, PartialEq, Eq)]
720pub enum ApplyOutcome {
721 Success { applied: usize, skipped: usize },
723 Aborted { applied: usize },
725}
726
727#[derive(Debug, Clone, Default)]
733pub struct SimpleTarget {
734 pub tables: std::collections::HashMap<String, Vec<Vec<SqliteValue>>>,
735}
736
737type RowApplyResult = Result<bool, usize>;
740
741impl SimpleTarget {
742 pub fn apply<F>(&mut self, changeset: &Changeset, mut handler: F) -> ApplyOutcome
745 where
746 F: FnMut(ConflictType, &ChangesetRow) -> ConflictAction,
747 {
748 let mut applied = 0usize;
749 let mut skipped = 0usize;
750
751 for tc in &changeset.tables {
752 let rows = self.tables.entry(tc.info.name.clone()).or_default();
753 for change in &tc.rows {
754 let result = match change.op {
755 ChangeOp::Insert => {
756 Self::apply_insert(rows, &tc.info.pk_flags, change, &mut handler, applied)
757 }
758 ChangeOp::Delete => {
759 Self::apply_delete(rows, &tc.info.pk_flags, change, &mut handler, applied)
760 }
761 ChangeOp::Update => {
762 Self::apply_update(rows, &tc.info.pk_flags, change, &mut handler, applied)
763 }
764 };
765 match result {
766 Ok(true) => applied += 1,
767 Ok(false) => skipped += 1,
768 Err(n) => return ApplyOutcome::Aborted { applied: n },
769 }
770 }
771 }
772 ApplyOutcome::Success { applied, skipped }
773 }
774
775 fn apply_insert<F>(
776 rows: &mut Vec<Vec<SqliteValue>>,
777 pk_flags: &[bool],
778 change: &ChangesetRow,
779 handler: &mut F,
780 applied: usize,
781 ) -> RowApplyResult
782 where
783 F: FnMut(ConflictType, &ChangesetRow) -> ConflictAction,
784 {
785 let new_row: Vec<SqliteValue> = change
786 .new_values
787 .iter()
788 .map(ChangesetValue::to_sqlite)
789 .collect();
790 if Self::find_row_by_pk(rows, pk_flags, &new_row).is_some() {
791 match handler(ConflictType::Conflict, change) {
792 ConflictAction::OmitChange => return Ok(false),
793 ConflictAction::Replace => {
794 let idx =
795 Self::find_row_by_pk(rows, pk_flags, &new_row).expect("row just found");
796 rows[idx] = new_row;
797 return Ok(true);
798 }
799 ConflictAction::Abort => return Err(applied),
800 }
801 }
802 rows.push(new_row);
803 Ok(true)
804 }
805
806 fn apply_delete<F>(
807 rows: &mut Vec<Vec<SqliteValue>>,
808 pk_flags: &[bool],
809 change: &ChangesetRow,
810 handler: &mut F,
811 applied: usize,
812 ) -> RowApplyResult
813 where
814 F: FnMut(ConflictType, &ChangesetRow) -> ConflictAction,
815 {
816 let old_row: Vec<SqliteValue> = change
817 .old_values
818 .iter()
819 .map(ChangesetValue::to_sqlite)
820 .collect();
821 if let Some(idx) = Self::find_row_by_pk(rows, pk_flags, &old_row) {
822 if rows[idx] != old_row {
823 match handler(ConflictType::Data, change) {
824 ConflictAction::OmitChange => return Ok(false),
825 ConflictAction::Replace => {
826 rows.remove(idx);
827 return Ok(true);
828 }
829 ConflictAction::Abort => return Err(applied),
830 }
831 }
832 rows.remove(idx);
833 Ok(true)
834 } else {
835 match handler(ConflictType::NotFound, change) {
836 ConflictAction::OmitChange | ConflictAction::Replace => Ok(false),
837 ConflictAction::Abort => Err(applied),
838 }
839 }
840 }
841
842 fn apply_update<F>(
843 rows: &mut [Vec<SqliteValue>],
844 pk_flags: &[bool],
845 change: &ChangesetRow,
846 handler: &mut F,
847 applied: usize,
848 ) -> RowApplyResult
849 where
850 F: FnMut(ConflictType, &ChangesetRow) -> ConflictAction,
851 {
852 let old_row: Vec<SqliteValue> = change
853 .old_values
854 .iter()
855 .map(ChangesetValue::to_sqlite)
856 .collect();
857 if let Some(idx) = Self::find_row_by_pk(rows, pk_flags, &old_row) {
858 let old_match =
859 change
860 .old_values
861 .iter()
862 .zip(rows[idx].iter())
863 .all(|(cv, sv)| match cv {
864 ChangesetValue::Undefined => true,
865 _ => cv.to_sqlite() == *sv,
866 });
867 if !old_match {
868 match handler(ConflictType::Data, change) {
869 ConflictAction::OmitChange => return Ok(false),
870 ConflictAction::Replace => {}
871 ConflictAction::Abort => return Err(applied),
872 }
873 }
874 let row = &mut rows[idx];
875 for (i, nv) in change.new_values.iter().enumerate() {
876 if *nv != ChangesetValue::Undefined {
877 if let Some(cell) = row.get_mut(i) {
878 *cell = nv.to_sqlite();
879 }
880 }
881 }
882 Ok(true)
883 } else {
884 match handler(ConflictType::NotFound, change) {
885 ConflictAction::OmitChange | ConflictAction::Replace => Ok(false),
886 ConflictAction::Abort => Err(applied),
887 }
888 }
889 }
890
891 fn find_row_by_pk(
892 rows: &[Vec<SqliteValue>],
893 pk_flags: &[bool],
894 target: &[SqliteValue],
895 ) -> Option<usize> {
896 rows.iter().position(|row| {
897 pk_flags
898 .iter()
899 .enumerate()
900 .filter(|&(_, &is_pk)| is_pk)
901 .all(|(i, _)| row.get(i).zip(target.get(i)).is_some_and(|(a, b)| a == b))
902 })
903 }
904}
905
906#[must_use]
912pub const fn changeset_varint_len(value: u64) -> usize {
913 varint_len(value)
914}
915
916#[cfg(test)]
921mod tests {
922 use super::*;
923
924 #[test]
925 fn test_extension_name_matches_crate_suffix() {
926 let expected = env!("CARGO_PKG_NAME")
927 .strip_prefix("fsqlite-ext-")
928 .expect("extension crates should use fsqlite-ext-* naming");
929 assert_eq!(extension_name(), expected);
930 }
931
932 #[test]
937 fn test_change_op_byte_roundtrip() {
938 for op in [ChangeOp::Insert, ChangeOp::Delete, ChangeOp::Update] {
939 assert_eq!(ChangeOp::from_byte(op.as_byte()), Some(op));
940 }
941 assert_eq!(ChangeOp::from_byte(0xFF), None);
942 }
943
944 #[test]
945 fn test_change_op_byte_values() {
946 assert_eq!(ChangeOp::Insert.as_byte(), 18);
947 assert_eq!(ChangeOp::Delete.as_byte(), 9);
948 assert_eq!(ChangeOp::Update.as_byte(), 23);
949 }
950
951 #[test]
956 fn test_changeset_value_undefined() {
957 let mut buf = Vec::new();
958 ChangesetValue::Undefined.encode(&mut buf);
959 assert_eq!(buf, [VAL_UNDEFINED]);
960 let (val, consumed) = ChangesetValue::decode(&buf, 0).unwrap();
961 assert_eq!(val, ChangesetValue::Undefined);
962 assert_eq!(consumed, 1);
963 }
964
965 #[test]
966 fn test_changeset_value_null() {
967 let mut buf = Vec::new();
968 ChangesetValue::Null.encode(&mut buf);
969 assert_eq!(buf, [VAL_NULL]);
970 let (val, consumed) = ChangesetValue::decode(&buf, 0).unwrap();
971 assert_eq!(val, ChangesetValue::Null);
972 assert_eq!(consumed, 1);
973 }
974
975 #[test]
976 fn test_changeset_value_integer() {
977 let mut buf = Vec::new();
978 ChangesetValue::Integer(42).encode(&mut buf);
979 assert_eq!(buf[0], VAL_INTEGER);
980 assert_eq!(&buf[1..], 42_i64.to_be_bytes());
981 let (val, consumed) = ChangesetValue::decode(&buf, 0).unwrap();
982 assert_eq!(val, ChangesetValue::Integer(42));
983 assert_eq!(consumed, 9);
984 }
985
986 #[test]
987 fn test_changeset_value_integer_negative() {
988 let mut buf = Vec::new();
989 ChangesetValue::Integer(-12_345).encode(&mut buf);
990 let (val, _) = ChangesetValue::decode(&buf, 0).unwrap();
991 assert_eq!(val, ChangesetValue::Integer(-12_345));
992 }
993
994 #[test]
995 fn test_changeset_value_real() {
996 let mut buf = Vec::new();
997 ChangesetValue::Real(1.23).encode(&mut buf);
998 assert_eq!(buf[0], VAL_REAL);
999 assert_eq!(&buf[1..], 1.23_f64.to_be_bytes());
1000 let (val, consumed) = ChangesetValue::decode(&buf, 0).unwrap();
1001 assert_eq!(val, ChangesetValue::Real(1.23));
1002 assert_eq!(consumed, 9);
1003 }
1004
1005 #[test]
1006 fn test_changeset_value_text() {
1007 let mut buf = Vec::new();
1008 ChangesetValue::Text("hello".to_owned()).encode(&mut buf);
1009 assert_eq!(buf[0], VAL_TEXT);
1010 assert_eq!(buf[1], 5);
1012 assert_eq!(&buf[2..], b"hello");
1013 let (val, consumed) = ChangesetValue::decode(&buf, 0).unwrap();
1014 assert_eq!(val, ChangesetValue::Text("hello".to_owned()));
1015 assert_eq!(consumed, 7); }
1017
1018 #[test]
1019 fn test_changeset_value_text_empty() {
1020 let mut buf = Vec::new();
1021 ChangesetValue::Text(String::new()).encode(&mut buf);
1022 let (val, consumed) = ChangesetValue::decode(&buf, 0).unwrap();
1023 assert_eq!(val, ChangesetValue::Text(String::new()));
1024 assert_eq!(consumed, 2); }
1026
1027 #[test]
1028 fn test_changeset_value_blob() {
1029 let data = vec![0xDE, 0xAD, 0xBE, 0xEF];
1030 let mut buf = Vec::new();
1031 ChangesetValue::Blob(data.clone()).encode(&mut buf);
1032 assert_eq!(buf[0], VAL_BLOB);
1033 assert_eq!(buf[1], 4); assert_eq!(&buf[2..], &data);
1035 let (val, consumed) = ChangesetValue::decode(&buf, 0).unwrap();
1036 assert_eq!(val, ChangesetValue::Blob(data));
1037 assert_eq!(consumed, 6);
1038 }
1039
1040 #[test]
1041 fn test_changeset_value_decode_bad_type() {
1042 assert!(ChangesetValue::decode(&[0xFF], 0).is_none());
1043 }
1044
1045 #[test]
1046 fn test_changeset_value_decode_truncated() {
1047 assert!(ChangesetValue::decode(&[VAL_INTEGER, 0, 0, 0, 0], 0).is_none());
1049 }
1050
1051 #[test]
1056 fn test_table_info_roundtrip() {
1057 let info = TableInfo {
1058 name: "users".to_owned(),
1059 column_count: 3,
1060 pk_flags: vec![true, false, false],
1061 };
1062 let mut buf = Vec::new();
1063 info.encode(&mut buf);
1064
1065 assert_eq!(buf[0], TABLE_HEADER_BYTE);
1066 let (decoded, consumed) = TableInfo::decode(&buf, 0).unwrap();
1067 assert_eq!(decoded, info);
1068 assert_eq!(consumed, buf.len());
1069 }
1070
1071 #[test]
1072 fn test_table_info_header_byte() {
1073 let info = TableInfo {
1074 name: "t".to_owned(),
1075 column_count: 1,
1076 pk_flags: vec![true],
1077 };
1078 let mut buf = Vec::new();
1079 info.encode(&mut buf);
1080 assert_eq!(buf[0], 0x54); }
1082
1083 #[test]
1084 fn test_table_info_nul_terminated_name() {
1085 let info = TableInfo {
1086 name: "orders".to_owned(),
1087 column_count: 2,
1088 pk_flags: vec![true, false],
1089 };
1090 let mut buf = Vec::new();
1091 info.encode(&mut buf);
1092 assert_eq!(*buf.last().unwrap(), 0x00);
1094 }
1095
1096 #[test]
1101 fn test_session_create() {
1102 let session = Session::new();
1103 assert!(session.tables.is_empty());
1104 assert!(session.changes.is_empty());
1105 }
1106
1107 #[test]
1108 fn test_session_attach_table() {
1109 let mut session = Session::new();
1110 session.attach_table("users", 3, vec![true, false, false]);
1111 assert_eq!(session.tables.len(), 1);
1112 assert_eq!(session.tables[0].name, "users");
1113 }
1114
1115 #[test]
1116 fn test_session_record_insert() {
1117 let mut session = Session::new();
1118 session.attach_table("t", 2, vec![true, false]);
1119 session.record_insert(
1120 "t",
1121 vec![
1122 ChangesetValue::Integer(1),
1123 ChangesetValue::Text("a".to_owned()),
1124 ],
1125 );
1126 let cs = session.changeset();
1127 assert_eq!(cs.tables.len(), 1);
1128 assert_eq!(cs.tables[0].rows.len(), 1);
1129 assert_eq!(cs.tables[0].rows[0].op, ChangeOp::Insert);
1130 }
1131
1132 #[test]
1133 fn test_session_record_delete() {
1134 let mut session = Session::new();
1135 session.attach_table("t", 2, vec![true, false]);
1136 session.record_delete(
1137 "t",
1138 vec![
1139 ChangesetValue::Integer(1),
1140 ChangesetValue::Text("a".to_owned()),
1141 ],
1142 );
1143 let cs = session.changeset();
1144 assert_eq!(cs.tables[0].rows[0].op, ChangeOp::Delete);
1145 }
1146
1147 #[test]
1148 fn test_session_record_update() {
1149 let mut session = Session::new();
1150 session.attach_table("t", 2, vec![true, false]);
1151 session.record_update(
1152 "t",
1153 vec![
1154 ChangesetValue::Integer(1),
1155 ChangesetValue::Text("a".to_owned()),
1156 ],
1157 vec![
1158 ChangesetValue::Undefined,
1159 ChangesetValue::Text("b".to_owned()),
1160 ],
1161 );
1162 let cs = session.changeset();
1163 let row = &cs.tables[0].rows[0];
1164 assert_eq!(row.op, ChangeOp::Update);
1165 assert_eq!(row.old_values[1], ChangesetValue::Text("a".to_owned()));
1166 assert_eq!(row.new_values[0], ChangesetValue::Undefined);
1167 assert_eq!(row.new_values[1], ChangesetValue::Text("b".to_owned()));
1168 }
1169
1170 #[test]
1171 fn test_session_multiple_tables() {
1172 let mut session = Session::new();
1173 session.attach_table("a", 1, vec![true]);
1174 session.attach_table("b", 1, vec![true]);
1175 session.record_insert("a", vec![ChangesetValue::Integer(1)]);
1176 session.record_insert("b", vec![ChangesetValue::Integer(2)]);
1177 let cs = session.changeset();
1178 assert_eq!(cs.tables.len(), 2);
1179 assert_eq!(cs.tables[0].info.name, "a");
1180 assert_eq!(cs.tables[1].info.name, "b");
1181 }
1182
1183 #[test]
1184 fn test_session_pk_columns() {
1185 let mut session = Session::new();
1186 session.attach_table("t", 3, vec![true, false, true]);
1187 let cs = session.changeset();
1188 assert!(cs.tables.is_empty());
1190 session.record_insert(
1192 "t",
1193 vec![
1194 ChangesetValue::Integer(1),
1195 ChangesetValue::Text("x".to_owned()),
1196 ChangesetValue::Integer(2),
1197 ],
1198 );
1199 let cs = session.changeset();
1200 assert_eq!(cs.tables[0].info.pk_flags, vec![true, false, true]);
1201 }
1202
1203 #[test]
1208 fn test_changeset_binary_format() {
1209 let mut session = Session::new();
1210 session.attach_table("t", 2, vec![true, false]);
1211 session.record_insert(
1212 "t",
1213 vec![
1214 ChangesetValue::Integer(1),
1215 ChangesetValue::Text("hi".to_owned()),
1216 ],
1217 );
1218 let encoded = session.changeset().encode();
1219 assert_eq!(encoded[0], 0x54);
1221 let decoded = Changeset::decode(&encoded).unwrap();
1223 assert_eq!(decoded.tables.len(), 1);
1224 assert_eq!(decoded.tables[0].info.name, "t");
1225 assert_eq!(decoded.tables[0].rows[0].op, ChangeOp::Insert);
1226 }
1227
1228 #[test]
1229 fn test_changeset_roundtrip() {
1230 let mut session = Session::new();
1231 session.attach_table("users", 3, vec![true, false, false]);
1232 session.record_insert(
1233 "users",
1234 vec![
1235 ChangesetValue::Integer(1),
1236 ChangesetValue::Text("Alice".to_owned()),
1237 ChangesetValue::Integer(30),
1238 ],
1239 );
1240 session.record_insert(
1241 "users",
1242 vec![
1243 ChangesetValue::Integer(2),
1244 ChangesetValue::Text("Bob".to_owned()),
1245 ChangesetValue::Integer(25),
1246 ],
1247 );
1248 session.record_delete(
1249 "users",
1250 vec![
1251 ChangesetValue::Integer(1),
1252 ChangesetValue::Text("Alice".to_owned()),
1253 ChangesetValue::Integer(30),
1254 ],
1255 );
1256 session.record_update(
1257 "users",
1258 vec![
1259 ChangesetValue::Integer(2),
1260 ChangesetValue::Text("Bob".to_owned()),
1261 ChangesetValue::Integer(25),
1262 ],
1263 vec![
1264 ChangesetValue::Undefined,
1265 ChangesetValue::Text("Robert".to_owned()),
1266 ChangesetValue::Undefined,
1267 ],
1268 );
1269
1270 let cs = session.changeset();
1271 let encoded = cs.encode();
1272 let decoded = Changeset::decode(&encoded).unwrap();
1273 assert_eq!(decoded, cs);
1274 }
1275
1276 #[test]
1281 fn test_changeset_invert_insert() {
1282 let row = ChangesetRow {
1283 op: ChangeOp::Insert,
1284 old_values: Vec::new(),
1285 new_values: vec![ChangesetValue::Integer(1)],
1286 };
1287 let inv = row.invert();
1288 assert_eq!(inv.op, ChangeOp::Delete);
1289 assert_eq!(inv.old_values, vec![ChangesetValue::Integer(1)]);
1290 assert!(inv.new_values.is_empty());
1291 }
1292
1293 #[test]
1294 fn test_changeset_invert_delete() {
1295 let row = ChangesetRow {
1296 op: ChangeOp::Delete,
1297 old_values: vec![ChangesetValue::Integer(1)],
1298 new_values: Vec::new(),
1299 };
1300 let inv = row.invert();
1301 assert_eq!(inv.op, ChangeOp::Insert);
1302 assert!(inv.old_values.is_empty());
1303 assert_eq!(inv.new_values, vec![ChangesetValue::Integer(1)]);
1304 }
1305
1306 #[test]
1307 fn test_changeset_invert_update() {
1308 let row = ChangesetRow {
1309 op: ChangeOp::Update,
1310 old_values: vec![
1311 ChangesetValue::Integer(1),
1312 ChangesetValue::Text("old".to_owned()),
1313 ],
1314 new_values: vec![
1315 ChangesetValue::Undefined,
1316 ChangesetValue::Text("new".to_owned()),
1317 ],
1318 };
1319 let inv = row.invert();
1320 assert_eq!(inv.op, ChangeOp::Update);
1321 assert_eq!(inv.old_values[0], ChangesetValue::Undefined);
1322 assert_eq!(inv.old_values[1], ChangesetValue::Text("new".to_owned()));
1323 assert_eq!(inv.new_values[0], ChangesetValue::Integer(1));
1324 assert_eq!(inv.new_values[1], ChangesetValue::Text("old".to_owned()));
1325 }
1326
1327 #[test]
1332 fn test_changeset_concat() {
1333 let mut cs1 = Changeset::new();
1334 cs1.tables.push(TableChangeset {
1335 info: TableInfo {
1336 name: "a".to_owned(),
1337 column_count: 1,
1338 pk_flags: vec![true],
1339 },
1340 rows: vec![ChangesetRow {
1341 op: ChangeOp::Insert,
1342 old_values: Vec::new(),
1343 new_values: vec![ChangesetValue::Integer(1)],
1344 }],
1345 });
1346 let cs2 = Changeset {
1347 tables: vec![TableChangeset {
1348 info: TableInfo {
1349 name: "b".to_owned(),
1350 column_count: 1,
1351 pk_flags: vec![true],
1352 },
1353 rows: vec![ChangesetRow {
1354 op: ChangeOp::Insert,
1355 old_values: Vec::new(),
1356 new_values: vec![ChangesetValue::Integer(2)],
1357 }],
1358 }],
1359 };
1360 cs1.concat(&cs2);
1361 assert_eq!(cs1.tables.len(), 2);
1362 }
1363
1364 #[test]
1369 fn test_patchset_format_omits_old_values() {
1370 let mut session = Session::new();
1371 session.attach_table("t", 3, vec![true, false, false]);
1372 session.record_update(
1373 "t",
1374 vec![
1375 ChangesetValue::Integer(1),
1376 ChangesetValue::Text("old_name".to_owned()),
1377 ChangesetValue::Integer(100),
1378 ],
1379 vec![
1380 ChangesetValue::Undefined,
1381 ChangesetValue::Text("new_name".to_owned()),
1382 ChangesetValue::Undefined,
1383 ],
1384 );
1385 let changeset_bytes = session.changeset().encode();
1386 let patchset_bytes = session.patchset();
1387 assert!(
1389 patchset_bytes.len() < changeset_bytes.len(),
1390 "patchset ({}) should be smaller than changeset ({})",
1391 patchset_bytes.len(),
1392 changeset_bytes.len(),
1393 );
1394 }
1395
1396 #[test]
1397 fn test_patchset_insert_same_as_changeset() {
1398 let mut session = Session::new();
1399 session.attach_table("t", 2, vec![true, false]);
1400 session.record_insert(
1401 "t",
1402 vec![
1403 ChangesetValue::Integer(1),
1404 ChangesetValue::Text("a".to_owned()),
1405 ],
1406 );
1407 let changeset_bytes = session.changeset().encode();
1408 let patchset_bytes = session.patchset();
1409 assert_eq!(changeset_bytes, patchset_bytes);
1411 }
1412
1413 #[test]
1418 fn test_apply_insert() {
1419 let cs = Changeset {
1420 tables: vec![TableChangeset {
1421 info: TableInfo {
1422 name: "t".to_owned(),
1423 column_count: 2,
1424 pk_flags: vec![true, false],
1425 },
1426 rows: vec![ChangesetRow {
1427 op: ChangeOp::Insert,
1428 old_values: Vec::new(),
1429 new_values: vec![
1430 ChangesetValue::Integer(1),
1431 ChangesetValue::Text("hello".to_owned()),
1432 ],
1433 }],
1434 }],
1435 };
1436
1437 let mut target = SimpleTarget::default();
1438 let outcome = target.apply(&cs, |_, _| ConflictAction::Abort);
1439 assert_eq!(
1440 outcome,
1441 ApplyOutcome::Success {
1442 applied: 1,
1443 skipped: 0
1444 }
1445 );
1446 assert_eq!(
1447 target.tables["t"],
1448 vec![vec![
1449 SqliteValue::Integer(1),
1450 SqliteValue::Text("hello".to_owned())
1451 ]]
1452 );
1453 }
1454
1455 #[test]
1456 fn test_apply_delete() {
1457 let mut target = SimpleTarget::default();
1458 target.tables.insert(
1459 "t".to_owned(),
1460 vec![vec![
1461 SqliteValue::Integer(1),
1462 SqliteValue::Text("hello".to_owned()),
1463 ]],
1464 );
1465
1466 let cs = Changeset {
1467 tables: vec![TableChangeset {
1468 info: TableInfo {
1469 name: "t".to_owned(),
1470 column_count: 2,
1471 pk_flags: vec![true, false],
1472 },
1473 rows: vec![ChangesetRow {
1474 op: ChangeOp::Delete,
1475 old_values: vec![
1476 ChangesetValue::Integer(1),
1477 ChangesetValue::Text("hello".to_owned()),
1478 ],
1479 new_values: Vec::new(),
1480 }],
1481 }],
1482 };
1483
1484 let outcome = target.apply(&cs, |_, _| ConflictAction::Abort);
1485 assert_eq!(
1486 outcome,
1487 ApplyOutcome::Success {
1488 applied: 1,
1489 skipped: 0
1490 }
1491 );
1492 assert!(target.tables["t"].is_empty());
1493 }
1494
1495 #[test]
1496 fn test_apply_update() {
1497 let mut target = SimpleTarget::default();
1498 target.tables.insert(
1499 "t".to_owned(),
1500 vec![vec![
1501 SqliteValue::Integer(1),
1502 SqliteValue::Text("old".to_owned()),
1503 ]],
1504 );
1505
1506 let cs = Changeset {
1507 tables: vec![TableChangeset {
1508 info: TableInfo {
1509 name: "t".to_owned(),
1510 column_count: 2,
1511 pk_flags: vec![true, false],
1512 },
1513 rows: vec![ChangesetRow {
1514 op: ChangeOp::Update,
1515 old_values: vec![
1516 ChangesetValue::Integer(1),
1517 ChangesetValue::Text("old".to_owned()),
1518 ],
1519 new_values: vec![
1520 ChangesetValue::Undefined,
1521 ChangesetValue::Text("new".to_owned()),
1522 ],
1523 }],
1524 }],
1525 };
1526
1527 let outcome = target.apply(&cs, |_, _| ConflictAction::Abort);
1528 assert_eq!(
1529 outcome,
1530 ApplyOutcome::Success {
1531 applied: 1,
1532 skipped: 0
1533 }
1534 );
1535 assert_eq!(
1536 target.tables["t"][0],
1537 vec![SqliteValue::Integer(1), SqliteValue::Text("new".to_owned())]
1538 );
1539 }
1540
1541 #[test]
1546 fn test_conflict_not_found() {
1547 let cs = Changeset {
1548 tables: vec![TableChangeset {
1549 info: TableInfo {
1550 name: "t".to_owned(),
1551 column_count: 1,
1552 pk_flags: vec![true],
1553 },
1554 rows: vec![ChangesetRow {
1555 op: ChangeOp::Delete,
1556 old_values: vec![ChangesetValue::Integer(999)],
1557 new_values: Vec::new(),
1558 }],
1559 }],
1560 };
1561 let mut target = SimpleTarget::default();
1562 let mut conflict_seen = None;
1563 let outcome = target.apply(&cs, |ct, _| {
1564 conflict_seen = Some(ct);
1565 ConflictAction::OmitChange
1566 });
1567 assert_eq!(conflict_seen, Some(ConflictType::NotFound));
1568 assert_eq!(
1569 outcome,
1570 ApplyOutcome::Success {
1571 applied: 0,
1572 skipped: 1
1573 }
1574 );
1575 }
1576
1577 #[test]
1578 fn test_conflict_data() {
1579 let mut target = SimpleTarget::default();
1580 target.tables.insert(
1581 "t".to_owned(),
1582 vec![vec![
1583 SqliteValue::Integer(1),
1584 SqliteValue::Text("actual".to_owned()),
1585 ]],
1586 );
1587
1588 let cs = Changeset {
1589 tables: vec![TableChangeset {
1590 info: TableInfo {
1591 name: "t".to_owned(),
1592 column_count: 2,
1593 pk_flags: vec![true, false],
1594 },
1595 rows: vec![ChangesetRow {
1596 op: ChangeOp::Delete,
1597 old_values: vec![
1598 ChangesetValue::Integer(1),
1599 ChangesetValue::Text("expected".to_owned()),
1600 ],
1601 new_values: Vec::new(),
1602 }],
1603 }],
1604 };
1605
1606 let mut conflict_seen = None;
1607 let outcome = target.apply(&cs, |ct, _| {
1608 conflict_seen = Some(ct);
1609 ConflictAction::OmitChange
1610 });
1611 assert_eq!(conflict_seen, Some(ConflictType::Data));
1612 assert_eq!(
1613 outcome,
1614 ApplyOutcome::Success {
1615 applied: 0,
1616 skipped: 1
1617 }
1618 );
1619 }
1620
1621 #[test]
1622 fn test_conflict_unique_insert() {
1623 let mut target = SimpleTarget::default();
1624 target
1625 .tables
1626 .insert("t".to_owned(), vec![vec![SqliteValue::Integer(1)]]);
1627
1628 let cs = Changeset {
1629 tables: vec![TableChangeset {
1630 info: TableInfo {
1631 name: "t".to_owned(),
1632 column_count: 1,
1633 pk_flags: vec![true],
1634 },
1635 rows: vec![ChangesetRow {
1636 op: ChangeOp::Insert,
1637 old_values: Vec::new(),
1638 new_values: vec![ChangesetValue::Integer(1)], }],
1640 }],
1641 };
1642
1643 let mut conflict_seen = None;
1644 let outcome = target.apply(&cs, |ct, _| {
1645 conflict_seen = Some(ct);
1646 ConflictAction::OmitChange
1647 });
1648 assert_eq!(conflict_seen, Some(ConflictType::Conflict));
1649 assert_eq!(
1650 outcome,
1651 ApplyOutcome::Success {
1652 applied: 0,
1653 skipped: 1
1654 }
1655 );
1656 }
1657
1658 #[test]
1659 fn test_conflict_omit_skips() {
1660 let mut target = SimpleTarget::default();
1661 let cs = Changeset {
1662 tables: vec![TableChangeset {
1663 info: TableInfo {
1664 name: "t".to_owned(),
1665 column_count: 1,
1666 pk_flags: vec![true],
1667 },
1668 rows: vec![ChangesetRow {
1669 op: ChangeOp::Delete,
1670 old_values: vec![ChangesetValue::Integer(1)],
1671 new_values: Vec::new(),
1672 }],
1673 }],
1674 };
1675 let outcome = target.apply(&cs, |_, _| ConflictAction::OmitChange);
1676 assert_eq!(
1677 outcome,
1678 ApplyOutcome::Success {
1679 applied: 0,
1680 skipped: 1
1681 }
1682 );
1683 }
1684
1685 #[test]
1686 fn test_conflict_replace_insert() {
1687 let mut target = SimpleTarget::default();
1688 target.tables.insert(
1689 "t".to_owned(),
1690 vec![vec![
1691 SqliteValue::Integer(1),
1692 SqliteValue::Text("old".to_owned()),
1693 ]],
1694 );
1695
1696 let cs = Changeset {
1697 tables: vec![TableChangeset {
1698 info: TableInfo {
1699 name: "t".to_owned(),
1700 column_count: 2,
1701 pk_flags: vec![true, false],
1702 },
1703 rows: vec![ChangesetRow {
1704 op: ChangeOp::Insert,
1705 old_values: Vec::new(),
1706 new_values: vec![
1707 ChangesetValue::Integer(1),
1708 ChangesetValue::Text("replaced".to_owned()),
1709 ],
1710 }],
1711 }],
1712 };
1713
1714 let outcome = target.apply(&cs, |_, _| ConflictAction::Replace);
1715 assert_eq!(
1716 outcome,
1717 ApplyOutcome::Success {
1718 applied: 1,
1719 skipped: 0
1720 }
1721 );
1722 assert_eq!(
1723 target.tables["t"][0],
1724 vec![
1725 SqliteValue::Integer(1),
1726 SqliteValue::Text("replaced".to_owned())
1727 ]
1728 );
1729 }
1730
1731 #[test]
1732 fn test_conflict_abort_stops_apply() {
1733 let mut target = SimpleTarget::default();
1734 let cs = Changeset {
1735 tables: vec![TableChangeset {
1736 info: TableInfo {
1737 name: "t".to_owned(),
1738 column_count: 1,
1739 pk_flags: vec![true],
1740 },
1741 rows: vec![
1742 ChangesetRow {
1743 op: ChangeOp::Delete,
1744 old_values: vec![ChangesetValue::Integer(1)],
1745 new_values: Vec::new(),
1746 },
1747 ChangesetRow {
1748 op: ChangeOp::Insert,
1749 old_values: Vec::new(),
1750 new_values: vec![ChangesetValue::Integer(2)],
1751 },
1752 ],
1753 }],
1754 };
1755 let outcome = target.apply(&cs, |_, _| ConflictAction::Abort);
1756 assert_eq!(outcome, ApplyOutcome::Aborted { applied: 0 });
1757 assert!(!target.tables.contains_key("t") || target.tables["t"].is_empty());
1759 }
1760
1761 #[test]
1766 fn test_changeset_full_roundtrip() {
1767 let mut session = Session::new();
1769 session.attach_table("users", 3, vec![true, false, false]);
1770 session.record_insert(
1771 "users",
1772 vec![
1773 ChangesetValue::Integer(1),
1774 ChangesetValue::Text("Alice".to_owned()),
1775 ChangesetValue::Integer(30),
1776 ],
1777 );
1778 session.record_insert(
1779 "users",
1780 vec![
1781 ChangesetValue::Integer(2),
1782 ChangesetValue::Text("Bob".to_owned()),
1783 ChangesetValue::Integer(25),
1784 ],
1785 );
1786
1787 let cs = session.changeset();
1788
1789 let mut target = SimpleTarget::default();
1791 let outcome = target.apply(&cs, |_, _| ConflictAction::Abort);
1792 assert_eq!(
1793 outcome,
1794 ApplyOutcome::Success {
1795 applied: 2,
1796 skipped: 0
1797 }
1798 );
1799 assert_eq!(target.tables["users"].len(), 2);
1800 assert_eq!(
1801 target.tables["users"][0][1],
1802 SqliteValue::Text("Alice".to_owned())
1803 );
1804 assert_eq!(
1805 target.tables["users"][1][1],
1806 SqliteValue::Text("Bob".to_owned())
1807 );
1808 }
1809
1810 #[test]
1811 fn test_changeset_invert_undoes_changes() {
1812 let mut session = Session::new();
1813 session.attach_table("t", 2, vec![true, false]);
1814 session.record_insert(
1815 "t",
1816 vec![
1817 ChangesetValue::Integer(1),
1818 ChangesetValue::Text("a".to_owned()),
1819 ],
1820 );
1821
1822 let cs = session.changeset();
1823 let inv = cs.invert();
1824
1825 let mut target = SimpleTarget::default();
1827 target.apply(&cs, |_, _| ConflictAction::Abort);
1828 assert_eq!(target.tables["t"].len(), 1);
1829
1830 target.apply(&inv, |_, _| ConflictAction::Abort);
1832 assert!(target.tables["t"].is_empty());
1833 }
1834
1835 #[test]
1840 fn test_changeset_value_from_sqlite() {
1841 assert_eq!(
1842 ChangesetValue::from_sqlite(&SqliteValue::Null),
1843 ChangesetValue::Null
1844 );
1845 assert_eq!(
1846 ChangesetValue::from_sqlite(&SqliteValue::Integer(42)),
1847 ChangesetValue::Integer(42)
1848 );
1849 assert_eq!(
1850 ChangesetValue::from_sqlite(&SqliteValue::Float(1.5)),
1851 ChangesetValue::Real(1.5)
1852 );
1853 assert_eq!(
1854 ChangesetValue::from_sqlite(&SqliteValue::Text("x".to_owned())),
1855 ChangesetValue::Text("x".to_owned())
1856 );
1857 assert_eq!(
1858 ChangesetValue::from_sqlite(&SqliteValue::Blob(vec![1, 2])),
1859 ChangesetValue::Blob(vec![1, 2])
1860 );
1861 }
1862
1863 #[test]
1864 fn test_changeset_value_to_sqlite() {
1865 assert_eq!(ChangesetValue::Undefined.to_sqlite(), SqliteValue::Null);
1866 assert_eq!(ChangesetValue::Null.to_sqlite(), SqliteValue::Null);
1867 assert_eq!(
1868 ChangesetValue::Integer(7).to_sqlite(),
1869 SqliteValue::Integer(7)
1870 );
1871 assert_eq!(
1872 ChangesetValue::Real(2.5).to_sqlite(),
1873 SqliteValue::Float(2.5)
1874 );
1875 assert_eq!(
1876 ChangesetValue::Text("hi".to_owned()).to_sqlite(),
1877 SqliteValue::Text("hi".to_owned())
1878 );
1879 assert_eq!(
1880 ChangesetValue::Blob(vec![0xAB]).to_sqlite(),
1881 SqliteValue::Blob(vec![0xAB])
1882 );
1883 }
1884
1885 #[test]
1890 fn test_change_op_from_byte_exhaustive_invalid() {
1891 for b in 0..=255u8 {
1892 if matches!(b, 0x12 | 0x09 | 0x17) {
1893 assert!(ChangeOp::from_byte(b).is_some());
1894 } else {
1895 assert!(
1896 ChangeOp::from_byte(b).is_none(),
1897 "byte {b:#x} should be None"
1898 );
1899 }
1900 }
1901 }
1902
1903 #[test]
1904 fn test_change_op_copy_clone_eq() {
1905 let a = ChangeOp::Insert;
1906 let b = a;
1907 assert_eq!(a, b);
1908 assert_ne!(ChangeOp::Insert, ChangeOp::Delete);
1909 assert_ne!(ChangeOp::Delete, ChangeOp::Update);
1910 }
1911
1912 #[test]
1913 fn test_change_op_debug() {
1914 let s = format!("{:?}", ChangeOp::Insert);
1915 assert_eq!(s, "Insert");
1916 }
1917
1918 #[test]
1923 fn test_changeset_value_integer_boundaries() {
1924 for &val in &[i64::MIN, i64::MAX, 0, -1, 1] {
1925 let mut buf = Vec::new();
1926 ChangesetValue::Integer(val).encode(&mut buf);
1927 let (decoded, _) = ChangesetValue::decode(&buf, 0).unwrap();
1928 assert_eq!(decoded, ChangesetValue::Integer(val));
1929 }
1930 }
1931
1932 #[test]
1933 fn test_changeset_value_real_special() {
1934 for &val in &[
1935 0.0,
1936 -0.0,
1937 f64::MAX,
1938 f64::MIN,
1939 f64::MIN_POSITIVE,
1940 f64::EPSILON,
1941 ] {
1942 let mut buf = Vec::new();
1943 ChangesetValue::Real(val).encode(&mut buf);
1944 let (decoded, _) = ChangesetValue::decode(&buf, 0).unwrap();
1945 assert_eq!(decoded, ChangesetValue::Real(val));
1946 }
1947 }
1948
1949 #[test]
1950 fn test_changeset_value_real_nan_roundtrip() {
1951 let mut buf = Vec::new();
1952 ChangesetValue::Real(f64::NAN).encode(&mut buf);
1953 let (decoded, _) = ChangesetValue::decode(&buf, 0).unwrap();
1954 if let ChangesetValue::Real(f) = decoded {
1955 assert!(f.is_nan());
1956 } else {
1957 panic!("expected Real");
1958 }
1959 }
1960
1961 #[test]
1962 fn test_changeset_value_blob_empty() {
1963 let mut buf = Vec::new();
1964 ChangesetValue::Blob(Vec::new()).encode(&mut buf);
1965 let (decoded, consumed) = ChangesetValue::decode(&buf, 0).unwrap();
1966 assert_eq!(decoded, ChangesetValue::Blob(Vec::new()));
1967 assert_eq!(consumed, 2); }
1969
1970 #[test]
1971 fn test_changeset_value_text_unicode() {
1972 let text = "\u{1F600}\u{1F4A9}\u{2603}"; let mut buf = Vec::new();
1974 ChangesetValue::Text(text.to_owned()).encode(&mut buf);
1975 let (decoded, _) = ChangesetValue::decode(&buf, 0).unwrap();
1976 assert_eq!(decoded, ChangesetValue::Text(text.to_owned()));
1977 }
1978
1979 #[test]
1980 fn test_changeset_value_decode_at_offset() {
1981 let mut buf = Vec::new();
1982 ChangesetValue::Null.encode(&mut buf); ChangesetValue::Integer(42).encode(&mut buf); let (val, consumed) = ChangesetValue::decode(&buf, 1).unwrap();
1985 assert_eq!(val, ChangesetValue::Integer(42));
1986 assert_eq!(consumed, 9);
1987 }
1988
1989 #[test]
1990 fn test_changeset_value_decode_empty_slice() {
1991 assert!(ChangesetValue::decode(&[], 0).is_none());
1992 }
1993
1994 #[test]
1995 fn test_changeset_value_decode_offset_beyond_len() {
1996 assert!(ChangesetValue::decode(&[VAL_NULL], 5).is_none());
1997 }
1998
1999 #[test]
2000 fn test_changeset_value_decode_truncated_real() {
2001 assert!(ChangesetValue::decode(&[VAL_REAL, 0, 0, 0], 0).is_none());
2002 }
2003
2004 #[test]
2005 fn test_changeset_value_decode_truncated_text() {
2006 let mut buf = vec![VAL_TEXT, 10, b'a', b'b', b'c'];
2008 assert!(ChangesetValue::decode(&buf, 0).is_none());
2009 buf.extend_from_slice(&[0; 7]);
2011 buf[5] = 0xFF;
2013 assert!(ChangesetValue::decode(&buf, 0).is_none());
2014 }
2015
2016 #[test]
2017 fn test_changeset_value_decode_truncated_blob() {
2018 let buf = vec![VAL_BLOB, 5, 1, 2]; assert!(ChangesetValue::decode(&buf, 0).is_none());
2020 }
2021
2022 #[test]
2027 #[allow(clippy::approx_constant)]
2028 fn test_changeset_value_sqlite_roundtrip_all_types() {
2029 let values = vec![
2030 SqliteValue::Null,
2031 SqliteValue::Integer(0),
2032 SqliteValue::Integer(i64::MAX),
2033 SqliteValue::Float(3.14),
2034 SqliteValue::Text(String::new()),
2035 SqliteValue::Text("test".to_owned()),
2036 SqliteValue::Blob(vec![]),
2037 SqliteValue::Blob(vec![1, 2, 3]),
2038 ];
2039 for sv in &values {
2040 let cv = ChangesetValue::from_sqlite(sv);
2041 let back = cv.to_sqlite();
2042 assert_eq!(&back, sv);
2043 }
2044 }
2045
2046 #[test]
2051 fn test_table_info_single_column() {
2052 let info = TableInfo {
2053 name: "x".to_owned(),
2054 column_count: 1,
2055 pk_flags: vec![true],
2056 };
2057 let mut buf = Vec::new();
2058 info.encode(&mut buf);
2059 let (decoded, consumed) = TableInfo::decode(&buf, 0).unwrap();
2060 assert_eq!(decoded, info);
2061 assert_eq!(consumed, buf.len());
2062 }
2063
2064 #[test]
2065 fn test_table_info_no_pk_columns() {
2066 let info = TableInfo {
2067 name: "t".to_owned(),
2068 column_count: 3,
2069 pk_flags: vec![false, false, false],
2070 };
2071 let mut buf = Vec::new();
2072 info.encode(&mut buf);
2073 let (decoded, _) = TableInfo::decode(&buf, 0).unwrap();
2074 assert_eq!(decoded.pk_flags, vec![false, false, false]);
2075 }
2076
2077 #[test]
2078 fn test_table_info_unicode_name() {
2079 let info = TableInfo {
2080 name: "\u{00FC}berschrift".to_owned(),
2081 column_count: 1,
2082 pk_flags: vec![true],
2083 };
2084 let mut buf = Vec::new();
2085 info.encode(&mut buf);
2086 let (decoded, _) = TableInfo::decode(&buf, 0).unwrap();
2087 assert_eq!(decoded.name, "\u{00FC}berschrift");
2088 }
2089
2090 #[test]
2091 fn test_table_info_decode_wrong_header() {
2092 assert!(TableInfo::decode(&[0x00, 0x01, 0x01, b't', 0x00], 0).is_none());
2093 }
2094
2095 #[test]
2096 fn test_table_info_decode_truncated() {
2097 assert!(TableInfo::decode(&[TABLE_HEADER_BYTE], 0).is_none());
2098 assert!(TableInfo::decode(&[TABLE_HEADER_BYTE, 3, 1], 0).is_none());
2099 }
2100
2101 #[test]
2102 fn test_table_info_decode_at_offset() {
2103 let mut buf = vec![0xFF, 0xFF]; let info = TableInfo {
2105 name: "t".to_owned(),
2106 column_count: 1,
2107 pk_flags: vec![true],
2108 };
2109 info.encode(&mut buf);
2110 let (decoded, _) = TableInfo::decode(&buf, 2).unwrap();
2111 assert_eq!(decoded, info);
2112 }
2113
2114 #[test]
2119 fn test_changeset_row_invert_double_is_identity() {
2120 let row = ChangesetRow {
2121 op: ChangeOp::Update,
2122 old_values: vec![
2123 ChangesetValue::Integer(1),
2124 ChangesetValue::Text("old".to_owned()),
2125 ],
2126 new_values: vec![
2127 ChangesetValue::Undefined,
2128 ChangesetValue::Text("new".to_owned()),
2129 ],
2130 };
2131 let double_inverted = row.invert().invert();
2132 assert_eq!(double_inverted, row);
2133 }
2134
2135 #[test]
2136 fn test_changeset_row_encode_decode_all_ops() {
2137 let col_count = 2;
2138 for op in [ChangeOp::Insert, ChangeOp::Delete, ChangeOp::Update] {
2139 let row = match op {
2140 ChangeOp::Insert => ChangesetRow {
2141 op,
2142 old_values: Vec::new(),
2143 new_values: vec![ChangesetValue::Integer(1), ChangesetValue::Null],
2144 },
2145 ChangeOp::Delete => ChangesetRow {
2146 op,
2147 old_values: vec![ChangesetValue::Integer(1), ChangesetValue::Null],
2148 new_values: Vec::new(),
2149 },
2150 ChangeOp::Update => ChangesetRow {
2151 op,
2152 old_values: vec![
2153 ChangesetValue::Integer(1),
2154 ChangesetValue::Text("a".to_owned()),
2155 ],
2156 new_values: vec![
2157 ChangesetValue::Undefined,
2158 ChangesetValue::Text("b".to_owned()),
2159 ],
2160 },
2161 };
2162 let mut buf = Vec::new();
2163 row.encode_changeset(&mut buf);
2164 let (decoded, consumed) = ChangesetRow::decode_changeset(&buf, 0, col_count).unwrap();
2165 assert_eq!(decoded, row);
2166 assert_eq!(consumed, buf.len());
2167 }
2168 }
2169
2170 #[test]
2171 fn test_changeset_row_decode_bad_op() {
2172 assert!(ChangesetRow::decode_changeset(&[0xFF, VAL_NULL], 0, 1).is_none());
2173 }
2174
2175 #[test]
2180 fn test_patchset_update_only_pk_old() {
2181 let pk_flags = vec![true, false, false];
2182 let row = ChangesetRow {
2183 op: ChangeOp::Update,
2184 old_values: vec![
2185 ChangesetValue::Integer(1),
2186 ChangesetValue::Text("old_name".to_owned()),
2187 ChangesetValue::Integer(100),
2188 ],
2189 new_values: vec![
2190 ChangesetValue::Undefined,
2191 ChangesetValue::Text("new_name".to_owned()),
2192 ChangesetValue::Undefined,
2193 ],
2194 };
2195 let mut cs_buf = Vec::new();
2196 row.encode_changeset(&mut cs_buf);
2197 let mut ps_buf = Vec::new();
2198 row.encode_patchset(&mut ps_buf, &pk_flags);
2199 assert!(ps_buf.len() < cs_buf.len());
2200 }
2201
2202 #[test]
2203 fn test_patchset_delete_same_as_changeset() {
2204 let pk_flags = vec![true, false];
2205 let row = ChangesetRow {
2206 op: ChangeOp::Delete,
2207 old_values: vec![
2208 ChangesetValue::Integer(1),
2209 ChangesetValue::Text("a".to_owned()),
2210 ],
2211 new_values: Vec::new(),
2212 };
2213 let mut cs_buf = Vec::new();
2214 row.encode_changeset(&mut cs_buf);
2215 let mut ps_buf = Vec::new();
2216 row.encode_patchset(&mut ps_buf, &pk_flags);
2217 assert_eq!(cs_buf, ps_buf);
2218 }
2219
2220 #[test]
2225 fn test_session_unattached_table_inferred() {
2226 let mut session = Session::new();
2227 session.record_insert("auto", vec![ChangesetValue::Integer(1)]);
2229 let cs = session.changeset();
2230 assert_eq!(cs.tables.len(), 1);
2231 assert_eq!(cs.tables[0].info.name, "auto");
2232 assert_eq!(cs.tables[0].info.column_count, 1);
2233 assert_eq!(cs.tables[0].info.pk_flags, vec![false]); }
2235
2236 #[test]
2237 fn test_session_empty_changeset() {
2238 let session = Session::new();
2239 let cs = session.changeset();
2240 assert!(cs.tables.is_empty());
2241 assert!(cs.encode().is_empty());
2242 }
2243
2244 #[test]
2245 fn test_session_empty_patchset() {
2246 let session = Session::new();
2247 assert!(session.patchset().is_empty());
2248 }
2249
2250 #[test]
2251 fn test_session_default_trait() {
2252 let session = Session::default();
2253 assert!(session.tables.is_empty());
2254 }
2255
2256 #[test]
2261 fn test_changeset_default_trait() {
2262 let cs = Changeset::default();
2263 assert!(cs.tables.is_empty());
2264 }
2265
2266 #[test]
2267 fn test_changeset_empty_encode_decode() {
2268 let cs = Changeset::new();
2269 let encoded = cs.encode();
2270 assert!(encoded.is_empty());
2271 let decoded = Changeset::decode(&encoded).unwrap();
2272 assert!(decoded.tables.is_empty());
2273 }
2274
2275 #[test]
2276 fn test_changeset_invert_is_self_inverse() {
2277 let mut session = Session::new();
2278 session.attach_table("t", 2, vec![true, false]);
2279 session.record_insert(
2280 "t",
2281 vec![
2282 ChangesetValue::Integer(1),
2283 ChangesetValue::Text("a".to_owned()),
2284 ],
2285 );
2286 session.record_delete(
2287 "t",
2288 vec![
2289 ChangesetValue::Integer(2),
2290 ChangesetValue::Text("b".to_owned()),
2291 ],
2292 );
2293 session.record_update(
2294 "t",
2295 vec![
2296 ChangesetValue::Integer(3),
2297 ChangesetValue::Text("c".to_owned()),
2298 ],
2299 vec![
2300 ChangesetValue::Undefined,
2301 ChangesetValue::Text("d".to_owned()),
2302 ],
2303 );
2304
2305 let cs = session.changeset();
2306 let double_inv = cs.invert().invert();
2307 assert_eq!(double_inv, cs);
2308 }
2309
2310 #[test]
2311 fn test_changeset_multi_table_encode_decode() {
2312 let mut session = Session::new();
2313 session.attach_table("a", 1, vec![true]);
2314 session.attach_table("b", 2, vec![true, false]);
2315 session.record_insert("a", vec![ChangesetValue::Integer(1)]);
2316 session.record_insert(
2317 "b",
2318 vec![
2319 ChangesetValue::Integer(2),
2320 ChangesetValue::Text("x".to_owned()),
2321 ],
2322 );
2323 session.record_delete("a", vec![ChangesetValue::Integer(3)]);
2324
2325 let cs = session.changeset();
2326 let encoded = cs.encode();
2327 let decoded = Changeset::decode(&encoded).unwrap();
2328 assert_eq!(decoded, cs);
2329 }
2330
2331 #[test]
2336 fn test_apply_update_data_conflict_replace() {
2337 let mut target = SimpleTarget::default();
2338 target.tables.insert(
2339 "t".to_owned(),
2340 vec![vec![
2341 SqliteValue::Integer(1),
2342 SqliteValue::Text("actual".to_owned()),
2343 ]],
2344 );
2345
2346 let cs = Changeset {
2347 tables: vec![TableChangeset {
2348 info: TableInfo {
2349 name: "t".to_owned(),
2350 column_count: 2,
2351 pk_flags: vec![true, false],
2352 },
2353 rows: vec![ChangesetRow {
2354 op: ChangeOp::Update,
2355 old_values: vec![
2356 ChangesetValue::Integer(1),
2357 ChangesetValue::Text("expected".to_owned()),
2358 ],
2359 new_values: vec![
2360 ChangesetValue::Undefined,
2361 ChangesetValue::Text("new".to_owned()),
2362 ],
2363 }],
2364 }],
2365 };
2366
2367 let outcome = target.apply(&cs, |_, _| ConflictAction::Replace);
2368 assert_eq!(
2369 outcome,
2370 ApplyOutcome::Success {
2371 applied: 1,
2372 skipped: 0
2373 }
2374 );
2375 assert_eq!(
2376 target.tables["t"][0][1],
2377 SqliteValue::Text("new".to_owned())
2378 );
2379 }
2380
2381 #[test]
2382 fn test_apply_delete_data_conflict_replace_removes() {
2383 let mut target = SimpleTarget::default();
2384 target.tables.insert(
2385 "t".to_owned(),
2386 vec![vec![
2387 SqliteValue::Integer(1),
2388 SqliteValue::Text("actual".to_owned()),
2389 ]],
2390 );
2391
2392 let cs = Changeset {
2393 tables: vec![TableChangeset {
2394 info: TableInfo {
2395 name: "t".to_owned(),
2396 column_count: 2,
2397 pk_flags: vec![true, false],
2398 },
2399 rows: vec![ChangesetRow {
2400 op: ChangeOp::Delete,
2401 old_values: vec![
2402 ChangesetValue::Integer(1),
2403 ChangesetValue::Text("expected".to_owned()),
2404 ],
2405 new_values: Vec::new(),
2406 }],
2407 }],
2408 };
2409
2410 let outcome = target.apply(&cs, |_, _| ConflictAction::Replace);
2411 assert_eq!(
2412 outcome,
2413 ApplyOutcome::Success {
2414 applied: 1,
2415 skipped: 0
2416 }
2417 );
2418 assert!(target.tables["t"].is_empty());
2419 }
2420
2421 #[test]
2422 fn test_apply_update_not_found_abort() {
2423 let mut target = SimpleTarget::default();
2424 let cs = Changeset {
2425 tables: vec![TableChangeset {
2426 info: TableInfo {
2427 name: "t".to_owned(),
2428 column_count: 1,
2429 pk_flags: vec![true],
2430 },
2431 rows: vec![ChangesetRow {
2432 op: ChangeOp::Update,
2433 old_values: vec![ChangesetValue::Integer(1)],
2434 new_values: vec![ChangesetValue::Integer(2)],
2435 }],
2436 }],
2437 };
2438 let outcome = target.apply(&cs, |_, _| ConflictAction::Abort);
2439 assert_eq!(outcome, ApplyOutcome::Aborted { applied: 0 });
2440 }
2441
2442 #[test]
2443 fn test_apply_multiple_rows_mixed() {
2444 let mut target = SimpleTarget::default();
2445 let cs = Changeset {
2446 tables: vec![TableChangeset {
2447 info: TableInfo {
2448 name: "t".to_owned(),
2449 column_count: 2,
2450 pk_flags: vec![true, false],
2451 },
2452 rows: vec![
2453 ChangesetRow {
2454 op: ChangeOp::Insert,
2455 old_values: Vec::new(),
2456 new_values: vec![
2457 ChangesetValue::Integer(1),
2458 ChangesetValue::Text("a".to_owned()),
2459 ],
2460 },
2461 ChangesetRow {
2462 op: ChangeOp::Insert,
2463 old_values: Vec::new(),
2464 new_values: vec![
2465 ChangesetValue::Integer(2),
2466 ChangesetValue::Text("b".to_owned()),
2467 ],
2468 },
2469 ],
2470 }],
2471 };
2472 let outcome = target.apply(&cs, |_, _| ConflictAction::Abort);
2473 assert_eq!(
2474 outcome,
2475 ApplyOutcome::Success {
2476 applied: 2,
2477 skipped: 0
2478 }
2479 );
2480 assert_eq!(target.tables["t"].len(), 2);
2481 }
2482
2483 #[test]
2484 fn test_apply_empty_changeset() {
2485 let mut target = SimpleTarget::default();
2486 let cs = Changeset::new();
2487 let outcome = target.apply(&cs, |_, _| ConflictAction::Abort);
2488 assert_eq!(
2489 outcome,
2490 ApplyOutcome::Success {
2491 applied: 0,
2492 skipped: 0
2493 }
2494 );
2495 }
2496
2497 #[test]
2502 fn test_table_changeset_encode_patchset() {
2503 let tc = TableChangeset {
2504 info: TableInfo {
2505 name: "t".to_owned(),
2506 column_count: 2,
2507 pk_flags: vec![true, false],
2508 },
2509 rows: vec![ChangesetRow {
2510 op: ChangeOp::Insert,
2511 old_values: Vec::new(),
2512 new_values: vec![ChangesetValue::Integer(1), ChangesetValue::Null],
2513 }],
2514 };
2515 let mut cs_buf = Vec::new();
2516 tc.encode_changeset(&mut cs_buf);
2517 let mut ps_buf = Vec::new();
2518 tc.encode_patchset(&mut ps_buf);
2519 assert_eq!(cs_buf, ps_buf);
2521 }
2522
2523 #[test]
2528 fn test_changeset_varint_len_values() {
2529 assert_eq!(changeset_varint_len(0), 1);
2530 assert_eq!(changeset_varint_len(127), 1);
2531 assert_eq!(changeset_varint_len(128), 2);
2532 assert!(changeset_varint_len(u64::MAX) > 0);
2533 }
2534
2535 #[test]
2540 fn test_conflict_type_eq() {
2541 assert_eq!(ConflictType::Data, ConflictType::Data);
2542 assert_ne!(ConflictType::Data, ConflictType::NotFound);
2543 assert_ne!(ConflictType::Conflict, ConflictType::Constraint);
2544 assert_ne!(ConflictType::Constraint, ConflictType::ForeignKey);
2545 }
2546
2547 #[test]
2548 fn test_conflict_action_eq() {
2549 assert_eq!(ConflictAction::OmitChange, ConflictAction::OmitChange);
2550 assert_ne!(ConflictAction::OmitChange, ConflictAction::Replace);
2551 assert_ne!(ConflictAction::Replace, ConflictAction::Abort);
2552 }
2553
2554 #[test]
2555 fn test_conflict_type_debug() {
2556 assert_eq!(format!("{:?}", ConflictType::ForeignKey), "ForeignKey");
2557 }
2558
2559 #[test]
2564 fn test_extension_name_value() {
2565 assert_eq!(extension_name(), "session");
2566 }
2567
2568 #[test]
2573 fn test_apply_outcome_debug() {
2574 let outcome = ApplyOutcome::Success {
2575 applied: 5,
2576 skipped: 2,
2577 };
2578 let s = format!("{:?}", outcome);
2579 assert!(s.contains('5'));
2580 assert!(s.contains('2'));
2581 }
2582
2583 #[test]
2584 fn test_apply_outcome_aborted_eq() {
2585 assert_eq!(
2586 ApplyOutcome::Aborted { applied: 3 },
2587 ApplyOutcome::Aborted { applied: 3 }
2588 );
2589 assert_ne!(
2590 ApplyOutcome::Aborted { applied: 3 },
2591 ApplyOutcome::Aborted { applied: 4 }
2592 );
2593 }
2594}