Skip to main content

sqlite_diff_rs/builders/
insert_operation.rs

1//! Submodule defining a builder for an insert operation.
2
3use alloc::vec;
4use alloc::vec::Vec;
5use core::fmt::Debug;
6
7use crate::{DynTable, SchemaWithPK, builders::operation::Indirect, encoding::Value};
8
9#[derive(Debug)]
10/// Builder for an insert operation.
11pub struct Insert<T: DynTable, S, B> {
12    /// The table being inserted into.
13    table: T,
14    /// Values for the inserted row.
15    pub(super) values: Vec<Value<S, B>>,
16    /// SQLite session-extension indirect flag. See [`Indirect`].
17    pub(crate) indirect: bool,
18}
19
20impl<T: DynTable, S: Clone, B: Clone> Clone for Insert<T, S, B> {
21    fn clone(&self) -> Self {
22        Self {
23            table: self.table.clone(),
24            values: self.values.clone(),
25            indirect: self.indirect,
26        }
27    }
28}
29
30impl<T: SchemaWithPK, S: Clone, B: Clone> Insert<T, S, B> {
31    /// Extract the primary key values from this insert's values.
32    #[inline]
33    pub fn extract_pk(&self) -> Vec<Value<S, B>> {
34        self.table.extract_pk(&self.values)
35    }
36}
37
38impl<T: DynTable + PartialEq, S: PartialEq + AsRef<str>, B: PartialEq + AsRef<[u8]>> PartialEq
39    for Insert<T, S, B>
40{
41    fn eq(&self, other: &Self) -> bool {
42        self.table == other.table && self.values == other.values && self.indirect == other.indirect
43    }
44}
45
46impl<T: DynTable + Eq, S: Eq + AsRef<str>, B: Eq + AsRef<[u8]>> Eq for Insert<T, S, B> {}
47
48impl<T: DynTable, S: Clone, B: Clone> From<T> for Insert<T, S, B> {
49    #[inline]
50    fn from(table: T) -> Self {
51        let num_cols = table.number_of_columns();
52        Self {
53            table,
54            values: vec![Value::Null; num_cols],
55            indirect: false,
56        }
57    }
58}
59
60impl<T: DynTable, S: AsRef<str>, B: AsRef<[u8]>> AsRef<T> for Insert<T, S, B> {
61    #[inline]
62    fn as_ref(&self) -> &T {
63        &self.table
64    }
65}
66
67impl<T: DynTable, S: AsRef<str>, B: AsRef<[u8]>> Insert<T, S, B> {
68    /// Consumes self and returns the values.
69    #[inline]
70    pub(crate) fn into_values(self) -> Vec<Value<S, B>> {
71        self.values
72    }
73
74    /// Sets the value for a specific column by index.
75    ///
76    /// # Arguments
77    ///
78    /// * `col_idx` - The index of the column to set.
79    /// * `value` - The value to set for the column.
80    ///
81    /// # Errors
82    ///
83    /// * `ColumnIndexOutOfBounds` - If the provided column index is out of bounds for the table schema.
84    ///
85    pub fn set(
86        mut self,
87        col_idx: usize,
88        value: impl Into<Value<S, B>>,
89    ) -> Result<Self, crate::errors::Error> {
90        if col_idx >= self.values.len() {
91            return Err(crate::errors::Error::ColumnIndexOutOfBounds(
92                col_idx,
93                self.values.len(),
94            ));
95        }
96
97        self.values[col_idx] = value.into();
98        Ok(self)
99    }
100
101    /// Sets a column to NULL.
102    ///
103    /// This is a convenience method equivalent to `.set(col_idx, Value::Null)`.
104    ///
105    /// # Errors
106    ///
107    /// * `ColumnIndexOutOfBounds` - If the provided column index is out of bounds for the table schema.
108    ///
109    /// # Example
110    ///
111    /// ```
112    /// use sqlite_diff_rs::{Insert, TableSchema};
113    ///
114    /// // CREATE TABLE items (id INTEGER PRIMARY KEY, description TEXT, price REAL)
115    /// let schema: TableSchema<String> = TableSchema::new("items".into(), 3, vec![1, 0, 0]);
116    ///
117    /// // INSERT INTO items (id, description, price) VALUES (1, NULL, 9.99)
118    /// let insert = Insert::<_, String, Vec<u8>>::from(schema)
119    ///     .set(0, 1i64).unwrap()
120    ///     .set_null(1).unwrap()    // description = NULL
121    ///     .set(2, 9.99f64).unwrap();
122    /// ```
123    pub fn set_null(self, col_idx: usize) -> Result<Self, crate::errors::Error>
124    where
125        S: Default,
126        B: Default,
127    {
128        self.set(col_idx, Value::Null)
129    }
130}
131
132impl<T: DynTable, S, B> Indirect for Insert<T, S, B> {
133    #[inline]
134    fn indirect(mut self, indirect: bool) -> Self {
135        self.indirect = indirect;
136        self
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::Insert;
143    use crate::errors::Error;
144    use crate::schema::SimpleTable;
145    use alloc::string::String;
146    use alloc::vec::Vec;
147
148    fn users() -> SimpleTable {
149        SimpleTable::new("users", &["id", "name"], &[0])
150    }
151
152    #[test]
153    fn test_insert_set_out_of_bounds() {
154        let err = Insert::<_, String, Vec<u8>>::from(users())
155            .set(99, 1i64)
156            .unwrap_err();
157        assert!(
158            matches!(err, Error::ColumnIndexOutOfBounds(99, 2)),
159            "got {err:?}"
160        );
161    }
162
163    #[test]
164    fn test_insert_set_null_out_of_bounds() {
165        let err = Insert::<_, String, Vec<u8>>::from(users())
166            .set_null(2)
167            .unwrap_err();
168        assert!(
169            matches!(err, Error::ColumnIndexOutOfBounds(2, 2)),
170            "got {err:?}"
171        );
172    }
173
174    #[test]
175    fn test_insert_clone_and_eq() {
176        let a = Insert::<_, String, Vec<u8>>::from(users())
177            .set(0, 1i64)
178            .unwrap();
179        let b = a.clone();
180        assert_eq!(a, b);
181
182        let c = Insert::<_, String, Vec<u8>>::from(users())
183            .set(0, 2i64)
184            .unwrap();
185        assert_ne!(a, c);
186    }
187}