Skip to main content

chain_builder/
sqlx_bind.rs

1//! sqlx handoff for v2 (`feature = "sqlx_*"`).
2//!
3//! [`SqlxDialect`] is a sealed sub-trait of [`Dialect`] that carries the sqlx
4//! `Database` for a dialect and knows how to turn the builder's dialect-agnostic
5//! [`Value`] binds into that database's owned `Arguments`. With it, any
6//! [`QueryBuilder<D>`] whose `D: SqlxDialect` can produce a ready-to-execute
7//! `sqlx::query::Query` / `QueryAs` via [`to_sqlx_query`](QueryBuilder::to_sqlx_query)
8//! and [`to_sqlx_query_as`](QueryBuilder::to_sqlx_query_as).
9//!
10//! These mirror the 1.x `value_to_arguments` / `to_sqlx_query` integration in
11//! `src/sqlx_mysql.rs` and `src/sqlx_sqlite.rs`.
12
13use crate::builder::QueryBuilder;
14use crate::dialect::Dialect;
15use crate::value::Value;
16
17#[cfg(feature = "sqlx_mysql")]
18use crate::dialect::MySql;
19#[cfg(feature = "sqlx_postgres")]
20use crate::dialect::Postgres;
21#[cfg(feature = "sqlx_sqlite")]
22use crate::dialect::Sqlite;
23
24/// Sealed sub-trait carrying the sqlx binding for a dialect.
25///
26/// Sealed via the private [`private::Sealed`] supertrait so only the dialect
27/// markers in this crate can implement it.
28pub trait SqlxDialect: Dialect + private::Sealed {
29    /// The sqlx `Database` this dialect binds against.
30    ///
31    /// The database's owned `Arguments` must be `IntoArguments<Self::Database>`
32    /// (it always is for sqlx's built-in databases) so the produced query type
33    /// is executable.
34    type Database: sqlx::Database<Arguments: sqlx::IntoArguments<Self::Database>>;
35
36    /// Build owned `Arguments` for `binds`.
37    ///
38    /// Mirrors 1.x `value_to_arguments`: each [`Value`] is appended with
39    /// `arguments.add(..)`. The sqlx encoders for this M1 [`Value`] set are
40    /// infallible, so the `add` result is discarded with `let _ =`.
41    fn bind_arguments(binds: &[Value]) -> <Self::Database as sqlx::Database>::Arguments;
42}
43
44mod private {
45    /// Sealing marker — implemented only for this crate's dialect markers.
46    pub trait Sealed {}
47
48    #[cfg(feature = "sqlx_postgres")]
49    impl Sealed for crate::dialect::Postgres {}
50    #[cfg(feature = "sqlx_mysql")]
51    impl Sealed for crate::dialect::MySql {}
52    #[cfg(feature = "sqlx_sqlite")]
53    impl Sealed for crate::dialect::Sqlite {}
54}
55
56#[cfg(feature = "sqlx_postgres")]
57impl SqlxDialect for Postgres {
58    type Database = sqlx::Postgres;
59
60    fn bind_arguments(binds: &[Value]) -> sqlx::postgres::PgArguments {
61        use sqlx::Arguments;
62        let mut arguments = sqlx::postgres::PgArguments::default();
63        for bind in binds {
64            // These encoders are infallible for the M1 `Value` set.
65            match bind {
66                Value::Null => {
67                    let _ = arguments.add(Option::<String>::None);
68                }
69                Value::Bool(b) => {
70                    let _ = arguments.add(*b);
71                }
72                Value::I64(i) => {
73                    let _ = arguments.add(*i);
74                }
75                Value::F64(f) => {
76                    let _ = arguments.add(*f);
77                }
78                Value::Text(s) => {
79                    let _ = arguments.add(s.clone());
80                }
81                Value::Bytes(b) => {
82                    let _ = arguments.add(b.clone());
83                }
84                #[cfg(feature = "json")]
85                Value::Json(j) => {
86                    // Match 1.x: store JSON as text.
87                    let _ = arguments.add(serde_json::to_string(j).unwrap_or_default());
88                }
89                #[cfg(feature = "uuid")]
90                Value::Uuid(u) => {
91                    let _ = arguments.add(*u);
92                }
93                #[cfg(feature = "chrono")]
94                Value::DateTimeUtc(dt) => {
95                    let _ = arguments.add(*dt);
96                }
97                #[cfg(feature = "chrono")]
98                Value::NaiveDateTime(dt) => {
99                    let _ = arguments.add(*dt);
100                }
101                #[cfg(feature = "chrono")]
102                Value::NaiveDate(d) => {
103                    let _ = arguments.add(*d);
104                }
105                #[cfg(feature = "chrono")]
106                Value::NaiveTime(t) => {
107                    let _ = arguments.add(*t);
108                }
109                #[cfg(feature = "decimal")]
110                Value::Decimal(d) => {
111                    // Native NUMERIC on Postgres.
112                    let _ = arguments.add(*d);
113                }
114            }
115        }
116        arguments
117    }
118}
119
120#[cfg(feature = "sqlx_mysql")]
121impl SqlxDialect for MySql {
122    type Database = sqlx::MySql;
123
124    fn bind_arguments(binds: &[Value]) -> sqlx::mysql::MySqlArguments {
125        use sqlx::Arguments;
126        let mut arguments = sqlx::mysql::MySqlArguments::default();
127        for bind in binds {
128            // These encoders are infallible for the M1 `Value` set.
129            match bind {
130                Value::Null => {
131                    let _ = arguments.add(Option::<String>::None);
132                }
133                Value::Bool(b) => {
134                    let _ = arguments.add(*b);
135                }
136                Value::I64(i) => {
137                    let _ = arguments.add(*i);
138                }
139                Value::F64(f) => {
140                    let _ = arguments.add(*f);
141                }
142                Value::Text(s) => {
143                    let _ = arguments.add(s.clone());
144                }
145                Value::Bytes(b) => {
146                    let _ = arguments.add(b.clone());
147                }
148                #[cfg(feature = "json")]
149                Value::Json(j) => {
150                    // Match 1.x: store JSON as text.
151                    let _ = arguments.add(serde_json::to_string(j).unwrap_or_default());
152                }
153                #[cfg(feature = "uuid")]
154                Value::Uuid(u) => {
155                    let _ = arguments.add(*u);
156                }
157                #[cfg(feature = "chrono")]
158                Value::DateTimeUtc(dt) => {
159                    let _ = arguments.add(*dt);
160                }
161                #[cfg(feature = "chrono")]
162                Value::NaiveDateTime(dt) => {
163                    let _ = arguments.add(*dt);
164                }
165                #[cfg(feature = "chrono")]
166                Value::NaiveDate(d) => {
167                    let _ = arguments.add(*d);
168                }
169                #[cfg(feature = "chrono")]
170                Value::NaiveTime(t) => {
171                    let _ = arguments.add(*t);
172                }
173                #[cfg(feature = "decimal")]
174                Value::Decimal(d) => {
175                    // Native DECIMAL on MySQL.
176                    let _ = arguments.add(*d);
177                }
178            }
179        }
180        arguments
181    }
182}
183
184#[cfg(feature = "sqlx_sqlite")]
185impl SqlxDialect for Sqlite {
186    type Database = sqlx::Sqlite;
187
188    fn bind_arguments(binds: &[Value]) -> sqlx::sqlite::SqliteArguments {
189        use sqlx::Arguments;
190        // sqlx 0.9 dropped the lifetime param from `SqliteArguments`; this is an
191        // owned set, matching 1.x `src/sqlx_sqlite.rs`.
192        let mut arguments = sqlx::sqlite::SqliteArguments::default();
193        for bind in binds {
194            // These encoders are infallible for the M1 `Value` set.
195            match bind {
196                Value::Null => {
197                    let _ = arguments.add(Option::<String>::None);
198                }
199                Value::Bool(b) => {
200                    let _ = arguments.add(*b);
201                }
202                Value::I64(i) => {
203                    let _ = arguments.add(*i);
204                }
205                Value::F64(f) => {
206                    let _ = arguments.add(*f);
207                }
208                Value::Text(s) => {
209                    let _ = arguments.add(s.clone());
210                }
211                Value::Bytes(b) => {
212                    let _ = arguments.add(b.clone());
213                }
214                #[cfg(feature = "json")]
215                Value::Json(j) => {
216                    // Match 1.x: store JSON as text.
217                    let _ = arguments.add(serde_json::to_string(j).unwrap_or_default());
218                }
219                #[cfg(feature = "uuid")]
220                Value::Uuid(u) => {
221                    let _ = arguments.add(*u);
222                }
223                #[cfg(feature = "chrono")]
224                Value::DateTimeUtc(dt) => {
225                    let _ = arguments.add(*dt);
226                }
227                #[cfg(feature = "chrono")]
228                Value::NaiveDateTime(dt) => {
229                    let _ = arguments.add(*dt);
230                }
231                #[cfg(feature = "chrono")]
232                Value::NaiveDate(d) => {
233                    let _ = arguments.add(*d);
234                }
235                #[cfg(feature = "chrono")]
236                Value::NaiveTime(t) => {
237                    let _ = arguments.add(*t);
238                }
239                #[cfg(feature = "decimal")]
240                Value::Decimal(d) => {
241                    // SQLite has no native decimal type and sqlx provides no
242                    // `Decimal` encoder for it, so bind the exact value as TEXT.
243                    let _ = arguments.add(d.to_string());
244                }
245            }
246        }
247        arguments
248    }
249}
250
251/// The `sqlx::query::Query` type a [`QueryBuilder<D>`] hands off to.
252pub type SqlxQuery<'q, D> = sqlx::query::Query<
253    'q,
254    <D as SqlxDialect>::Database,
255    <<D as SqlxDialect>::Database as sqlx::Database>::Arguments,
256>;
257
258/// The `sqlx::query::QueryAs` type a [`QueryBuilder<D>`] hands off to,
259/// decoding rows into `T`.
260pub type SqlxQueryAs<'q, D, T> = sqlx::query::QueryAs<
261    'q,
262    <D as SqlxDialect>::Database,
263    T,
264    <<D as SqlxDialect>::Database as sqlx::Database>::Arguments,
265>;
266
267#[cfg(any(
268    feature = "sqlx_postgres",
269    feature = "sqlx_mysql",
270    feature = "sqlx_sqlite"
271))]
272impl<D: SqlxDialect> QueryBuilder<D> {
273    /// Build an executable `sqlx::query::Query` from this builder, panicking on
274    /// an invalid builder.
275    ///
276    /// The SQL is builder-generated with bound placeholders, so it is asserted
277    /// safe via `sqlx::AssertSqlSafe` (which also satisfies sqlx 0.9's `'static`
278    /// SQL bound). Mirrors 1.x `ChainBuilder::to_sqlx_query`. Panicking twin of
279    /// [`Self::try_to_sqlx_query`].
280    pub fn to_sqlx_query(
281        &self,
282    ) -> sqlx::query::Query<'_, D::Database, <D::Database as sqlx::Database>::Arguments> {
283        let (sql, binds) = self.to_sql();
284        sqlx::query_with(sqlx::AssertSqlSafe(sql), D::bind_arguments(&binds))
285    }
286
287    /// Build an executable `sqlx::query::Query`, or return the
288    /// [`BuildError`](crate::BuildError) when the builder is invalid (see
289    /// [`try_to_sql`](QueryBuilder::try_to_sql)).
290    pub fn try_to_sqlx_query(&self) -> Result<SqlxQuery<'_, D>, crate::BuildError> {
291        let (sql, binds) = self.try_to_sql()?;
292        Ok(sqlx::query_with(
293            sqlx::AssertSqlSafe(sql),
294            D::bind_arguments(&binds),
295        ))
296    }
297
298    /// Build an executable `sqlx::query::QueryAs` decoding rows into `T`,
299    /// panicking on an invalid builder. Panicking twin of
300    /// [`Self::try_to_sqlx_query_as`].
301    pub fn to_sqlx_query_as<T>(
302        &self,
303    ) -> sqlx::query::QueryAs<'_, D::Database, T, <D::Database as sqlx::Database>::Arguments>
304    where
305        T: for<'r> sqlx::FromRow<'r, <D::Database as sqlx::Database>::Row>,
306    {
307        let (sql, binds) = self.to_sql();
308        sqlx::query_as_with(sqlx::AssertSqlSafe(sql), D::bind_arguments(&binds))
309    }
310
311    /// Build an executable `sqlx::query::QueryAs` decoding rows into `T`, or
312    /// return the [`BuildError`](crate::BuildError) when the builder is invalid
313    /// (see [`try_to_sql`](QueryBuilder::try_to_sql)).
314    pub fn try_to_sqlx_query_as<T>(&self) -> Result<SqlxQueryAs<'_, D, T>, crate::BuildError>
315    where
316        T: for<'r> sqlx::FromRow<'r, <D::Database as sqlx::Database>::Row>,
317    {
318        let (sql, binds) = self.try_to_sql()?;
319        Ok(sqlx::query_as_with(
320            sqlx::AssertSqlSafe(sql),
321            D::bind_arguments(&binds),
322        ))
323    }
324}