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}