Skip to main content

coven_foundation/
changeset.rs

1//! Row changes reported by synchronization operations.
2
3/// The operation type for a changeset entry.
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum ChangeOp {
6    Insert,
7    Update,
8    Delete,
9}
10
11/// One row change extracted from a changeset.
12///
13/// `columns` holds the row's column values in schema order. Inserts and updates
14/// contain the resulting values; deletes contain the removed values. Unchanged
15/// update columns are filled from the old side so primary keys and foreign keys
16/// remain available.
17///
18/// `None` means SQL NULL or a column absent from the changeset.
19#[derive(Debug, Clone)]
20pub struct RowChange {
21    pub table: String,
22    pub op: ChangeOp,
23    pub columns: Vec<Option<String>>,
24    changed_columns: Vec<bool>,
25}
26
27impl RowChange {
28    /// Build a row change from equally sized column-value and changed-column
29    /// vectors. The decoder is the sole producer; keeping the marker beside the
30    /// decoded row preserves SQLite's distinction between an unchanged value
31    /// copied from the old side and a value written by this UPDATE.
32    pub fn new(
33        table: String,
34        op: ChangeOp,
35        columns: Vec<Option<String>>,
36        changed_columns: Vec<bool>,
37    ) -> Self {
38        assert_eq!(
39            columns.len(),
40            changed_columns.len(),
41            "row change values and change markers must have equal lengths"
42        );
43        Self {
44            table,
45            op,
46            columns,
47            changed_columns,
48        }
49    }
50
51    /// The primary key (column 0).
52    pub fn pk(&self) -> Option<&str> {
53        self.col(0)
54    }
55
56    /// A column value by index.
57    pub fn col(&self, i: usize) -> Option<&str> {
58        self.columns.get(i).and_then(|c| c.as_deref())
59    }
60
61    /// Whether this column was written by the change. Inserts and deletes affect
62    /// every value in their row; updates mark only values present on SQLite's new
63    /// side, even though [`Self::col`] fills unchanged values from the old side.
64    pub fn column_changed(&self, i: usize) -> bool {
65        self.changed_columns.get(i).copied().unwrap_or(false)
66    }
67}