Skip to main content

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}