sqlite_diff_rs/builders/format.rs
1//! Format trait defining changeset vs patchset behavior.
2
3use crate::builders::Operation;
4use crate::builders::change::{encode_changeset_op, encode_patchset_op, patchset_pk_mapping};
5use crate::encoding::markers;
6use crate::encoding::{MaybeValue, Value};
7use crate::schema::SchemaWithPK;
8use alloc::vec::Vec;
9use core::fmt::Debug;
10
11/// Trait defining the differences between changeset and patchset formats.
12///
13/// A changeset DELETE stores all column values and a changeset UPDATE stores
14/// both old and new values. A patchset DELETE stores only the PK (data lives
15/// externally) and a patchset UPDATE stores only the PK plus new values.
16pub(crate) trait Format<S, B>: Default + Clone + Copy + PartialEq + Eq + 'static {
17 /// The type representing old values in this format.
18 ///
19 /// - Changeset: `MaybeValue<S, B>` (Option<Value<S, B>>, None = undefined/unchanged)
20 /// - Patchset: `()` (old values not stored)
21 type Old: Clone + Debug + Default;
22
23 /// The data stored for a DELETE operation (beyond the PK which is always
24 /// stored as the `IndexMap` key in `DiffSetBuilder`).
25 ///
26 /// - Changeset: `Vec<Value<S, B>>` (full old-row values)
27 /// - Patchset: `()` (only the PK matters, stored externally)
28 type DeleteData: Clone + Debug + Default;
29
30 /// One-byte table-section marker written before each table's rows:
31 /// `b'T'` for a changeset, `b'P'` for a patchset.
32 const TABLE_MARKER: u8;
33
34 /// Per-table state precomputed once and reused for every row during
35 /// [`build`](crate::DiffSetBuilder::build).
36 ///
37 /// Changeset needs none (`()`); patchset needs the primary-key column
38 /// mapping used to project each record.
39 type BuildState;
40
41 /// Precompute the per-table [`BuildState`](Self::BuildState).
42 fn build_state<T: SchemaWithPK>(table: &T) -> Self::BuildState;
43
44 /// Encode one operation's record into `out`, using `pk` (the row's
45 /// primary-key values) and the precomputed `state`.
46 fn encode_op(
47 out: &mut Vec<u8>,
48 op: &Operation<Self, S, B>,
49 pk: &[Value<S, B>],
50 state: &Self::BuildState,
51 ) where
52 S: AsRef<str>,
53 B: AsRef<[u8]>;
54}
55
56/// Public, nameable bound for a diff format, either changeset or patchset.
57///
58/// This is the downstream-visible counterpart to the crate-private
59/// `Format` trait. It carries no items of its own; the associated types
60/// live on the sealed supertrait and stay private. Because `Format` is
61/// crate-private, no external type can satisfy this bound, so it is sealed
62/// in the same sense as a sealed trait: only [`ChangesetFormat`] and
63/// [`PatchsetFormat`] implement it.
64///
65/// A consumer that only calls `new`, `digest`, and `build` can therefore
66/// write one function generic over `F: DiffFormat<String, Vec<u8>>` and
67/// fold a batch of wire events into either a changeset or a patchset,
68/// instead of duplicating the body once per format.
69pub trait DiffFormat<S, B>: Format<S, B> {}
70
71impl<S, B, F: Format<S, B>> DiffFormat<S, B> for F {}
72
73/// Changeset format marker.
74#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
75pub struct ChangesetFormat;
76
77impl<S: Clone + Debug + AsRef<str>, B: Clone + Debug + AsRef<[u8]>> Format<S, B>
78 for ChangesetFormat
79{
80 type Old = MaybeValue<S, B>;
81 type DeleteData = Vec<Value<S, B>>;
82 const TABLE_MARKER: u8 = markers::CHANGESET;
83 type BuildState = ();
84
85 fn build_state<T: SchemaWithPK>(_table: &T) -> Self::BuildState {}
86
87 fn encode_op(
88 out: &mut Vec<u8>,
89 op: &Operation<Self, S, B>,
90 _pk: &[Value<S, B>],
91 _state: &Self::BuildState,
92 ) where
93 S: AsRef<str>,
94 B: AsRef<[u8]>,
95 {
96 encode_changeset_op(out, op);
97 }
98}
99
100/// Patchset format marker.
101#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
102pub struct PatchsetFormat;
103
104impl<S, B> Format<S, B> for PatchsetFormat {
105 type Old = ();
106 type DeleteData = ();
107 const TABLE_MARKER: u8 = markers::PATCHSET;
108 type BuildState = (Vec<u8>, Vec<Option<usize>>);
109
110 fn build_state<T: SchemaWithPK>(table: &T) -> Self::BuildState {
111 patchset_pk_mapping(table)
112 }
113
114 fn encode_op(
115 out: &mut Vec<u8>,
116 op: &Operation<Self, S, B>,
117 pk: &[Value<S, B>],
118 state: &Self::BuildState,
119 ) where
120 S: AsRef<str>,
121 B: AsRef<[u8]>,
122 {
123 encode_patchset_op(out, op, pk, &state.0, &state.1);
124 }
125}