Skip to main content

duckdb/
appender_params.rs

1use crate::{Appender, Result, ToSql};
2
3mod sealed {
4    /// This trait exists just to ensure that the only impls of
5    /// `trait AppenderParams` that are allowed are ones in this crate.
6    pub trait Sealed {}
7}
8use sealed::Sealed;
9
10/// Trait used for rows passed to [`Appender::append_row`] and
11/// [`Appender::append_rows`].
12///
13/// Note: Currently, this trait can only be implemented inside this crate.
14/// Additionally, its methods (which are `doc(hidden)`) should currently not be
15/// considered part of the stable API, although it's possible they will
16/// stabilize in the future.
17///
18/// `AppenderParams` normalizes the row values accepted by the appender API into
19/// the positional values DuckDB appends to one table row. It is separate from
20/// [`Params`](crate::Params), which is used for prepared statements and
21/// queries.
22///
23/// Supported forms include:
24///
25/// - tuples of mixed types, up to 16 elements;
26/// - arrays and references to arrays, up to 32 elements;
27/// - [`duckdb::params!`](crate::params!) or `&[&dyn ToSql]` for heterogeneous
28///   rows or rows assembled dynamically;
29/// - [`appender_params_from_iter`] for iterators over one concrete
30///   [`ToSql`] item type.
31///
32/// # Example
33///
34/// ```rust,no_run
35/// # use duckdb::{Connection, Result, params};
36/// fn append_rows(conn: &Connection) -> Result<()> {
37///     conn.execute_batch("CREATE TABLE people (id INTEGER, name TEXT)")?;
38///     let mut appender = conn.appender("people")?;
39///
40///     appender.append_row((1i32, "Alice"))?;
41///     appender.append_row(params![2i32, "Bob"])?;
42///
43///     appender.flush()
44/// }
45/// ```
46pub trait AppenderParams: Sealed {
47    // XXX not public api, might not need to expose.
48    //
49    // Binds the parameters to the appender. It is unlikely calling this
50    // explicitly will do what you want. Please use `Appender::append_row` or
51    // similar directly.
52    //
53    // For now, just hide the function in the docs...
54    #[doc(hidden)]
55    fn __bind_in(self, stmt: &mut Appender<'_>) -> Result<()>;
56}
57
58// Explicitly impl for empty array. Critically, for `conn.execute([])` to be
59// unambiguous, this must be the *only* implementation for an empty array. This
60// avoids `NO_PARAMS` being a necessary part of the API.
61impl Sealed for [&dyn ToSql; 0] {}
62impl AppenderParams for [&dyn ToSql; 0] {
63    #[inline]
64    fn __bind_in(self, stmt: &mut Appender<'_>) -> Result<()> {
65        // Route through the normal append path so DuckDB can reject empty rows
66        // for tables that require values.
67        stmt.append_parameter_row(&[] as &[&dyn ToSql])
68    }
69}
70
71impl Sealed for &[&dyn ToSql] {}
72impl AppenderParams for &[&dyn ToSql] {
73    #[inline]
74    fn __bind_in(self, stmt: &mut Appender<'_>) -> Result<()> {
75        stmt.append_parameter_row(self)
76    }
77}
78
79macro_rules! impl_for_array_ref {
80    ($($N:literal)+) => {$(
81        // These are already generic, and there's a shedload of them, so lets
82        // avoid the compile time hit from making them all inline for now.
83        impl<T: ToSql + ?Sized> Sealed for &[&T; $N] {}
84        impl<T: ToSql + ?Sized> AppenderParams for &[&T; $N] {
85            fn __bind_in(self, stmt: &mut Appender<'_>) -> Result<()> {
86                stmt.append_parameter_row(self)
87            }
88        }
89        impl<T: ToSql> Sealed for [T; $N] {}
90        impl<T: ToSql> AppenderParams for [T; $N] {
91            #[inline]
92            fn __bind_in(self, stmt: &mut Appender<'_>) -> Result<()> {
93                stmt.append_parameter_row(&self)
94            }
95        }
96    )+};
97}
98
99// Following libstd/libcore's (old) lead, implement this for arrays up to `[_;
100// 32]`. Note `[_; 0]` is intentionally omitted for coherence reasons, see the
101// note above the impl of `[&dyn ToSql; 0]` for more information.
102//
103// For tables with more than 32 columns, users should use:
104// - `&[&dyn ToSql]` for dynamic parameter binding (recommended)
105// - `params!` macro
106// - `appender_params_from_iter`
107impl_for_array_ref!(
108    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
109    18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
110);
111
112macro_rules! impl_appender_params_for_tuple {
113    ($($T:ident),+) => {
114        impl<$($T: ToSql),+> Sealed for ($($T,)+) {}
115        impl<$($T: ToSql),+> AppenderParams for ($($T,)+) {
116            fn __bind_in(self, stmt: &mut Appender<'_>) -> Result<()> {
117                #[allow(non_snake_case)]
118                let ($($T,)+) = &self;
119                stmt.append_parameter_row([$($T as &dyn ToSql),+])
120            }
121        }
122    };
123}
124
125// Implement AppenderParams for tuples, following the pattern from rusqlite's Params trait
126// (https://github.com/rusqlite/rusqlite/blob/master/src/params.rs).
127// This allows ergonomic row insertion without lifetime issues, e.g.:
128//   appender.append_rows(data.iter().map(|(id, name)| (id, name)))
129// Support tuples up to arity 16, matching rusqlite's Params.
130impl_appender_params_for_tuple!(T1);
131impl_appender_params_for_tuple!(T1, T2);
132impl_appender_params_for_tuple!(T1, T2, T3);
133impl_appender_params_for_tuple!(T1, T2, T3, T4);
134impl_appender_params_for_tuple!(T1, T2, T3, T4, T5);
135impl_appender_params_for_tuple!(T1, T2, T3, T4, T5, T6);
136impl_appender_params_for_tuple!(T1, T2, T3, T4, T5, T6, T7);
137impl_appender_params_for_tuple!(T1, T2, T3, T4, T5, T6, T7, T8);
138impl_appender_params_for_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9);
139impl_appender_params_for_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10);
140impl_appender_params_for_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11);
141impl_appender_params_for_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12);
142impl_appender_params_for_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13);
143impl_appender_params_for_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14);
144impl_appender_params_for_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15);
145impl_appender_params_for_tuple!(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16);
146
147/// Adapter type which allows any iterator over [`ToSql`] values to implement
148/// [`AppenderParams`].
149///
150/// This struct is created by the [`appender_params_from_iter`] function.
151///
152/// This can be useful when the values for one appended row are already stored
153/// in a collection or produced by an iterator. It avoids allocating storage for
154/// something like a `Vec<&dyn ToSql>`.
155///
156/// The iterator must yield one concrete item type that implements [`ToSql`].
157/// For heterogeneous rows, use a tuple, [`duckdb::params!`](crate::params!), or
158/// a `&[&dyn ToSql]`. As with every [`Appender::append_row`] call, the number
159/// of values must match the target table's column count.
160///
161/// # Example
162///
163/// ```rust,no_run
164/// use duckdb::{Connection, Result, appender_params_from_iter};
165///
166/// fn append_scores(conn: &Connection, scores: Vec<i32>) -> Result<()> {
167///     conn.execute_batch("CREATE TABLE scores (a INTEGER, b INTEGER, c INTEGER)")?;
168///     let mut appender = conn.appender("scores")?;
169///
170///     appender.append_row(appender_params_from_iter(scores))?;
171///     appender.flush()
172/// }
173/// ```
174#[derive(Clone, Debug)]
175pub struct AppenderParamsFromIter<I>(I);
176
177/// Constructor function for an [`AppenderParamsFromIter`]. See its
178/// documentation for more.
179#[inline]
180pub fn appender_params_from_iter<I>(iter: I) -> AppenderParamsFromIter<I>
181where
182    I: IntoIterator,
183    I::Item: ToSql,
184{
185    AppenderParamsFromIter(iter)
186}
187
188impl<I> Sealed for AppenderParamsFromIter<I>
189where
190    I: IntoIterator,
191    I::Item: ToSql,
192{
193}
194
195impl<I> AppenderParams for AppenderParamsFromIter<I>
196where
197    I: IntoIterator,
198    I::Item: ToSql,
199{
200    #[inline]
201    fn __bind_in(self, stmt: &mut Appender<'_>) -> Result<()> {
202        stmt.append_parameter_row(self.0)
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use crate::{Connection, Result};
209
210    #[test]
211    fn test_append_row_tuple() -> Result<()> {
212        let db = Connection::open_in_memory()?;
213        db.execute_batch("CREATE TABLE test (id INTEGER, name TEXT)")?;
214
215        let mut app = db.appender("test")?;
216        app.append_row((1i32, "alice"))?;
217        app.flush()?;
218
219        let mut stmt = db.prepare("SELECT id, name FROM test")?;
220        let mut rows = stmt.query([])?;
221        let row = rows.next()?.unwrap();
222        assert_eq!(row.get::<_, i32>(0)?, 1i32);
223        assert_eq!(row.get::<_, String>(1)?, "alice");
224
225        Ok(())
226    }
227
228    #[test]
229    fn test_append_rows_iterator_tuple() -> Result<()> {
230        let db = Connection::open_in_memory()?;
231        db.execute_batch("CREATE TABLE test (id INTEGER, name TEXT)")?;
232
233        #[allow(clippy::useless_vec)]
234        let data = vec![(1, "alice"), (2, "bob")];
235        let mut app = db.appender("test")?;
236        app.append_rows(data.iter().map(|(id, name)| (*id, *name)))?;
237        app.flush()?;
238
239        let count: i32 = db.query_row("SELECT COUNT(*) FROM test", [], |r| r.get(0))?;
240        assert_eq!(count, 2);
241
242        Ok(())
243    }
244}