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            }
110        }
111        arguments
112    }
113}
114
115#[cfg(feature = "sqlx_mysql")]
116impl SqlxDialect for MySql {
117    type Database = sqlx::MySql;
118
119    fn bind_arguments(binds: &[Value]) -> sqlx::mysql::MySqlArguments {
120        use sqlx::Arguments;
121        let mut arguments = sqlx::mysql::MySqlArguments::default();
122        for bind in binds {
123            // These encoders are infallible for the M1 `Value` set.
124            match bind {
125                Value::Null => {
126                    let _ = arguments.add(Option::<String>::None);
127                }
128                Value::Bool(b) => {
129                    let _ = arguments.add(*b);
130                }
131                Value::I64(i) => {
132                    let _ = arguments.add(*i);
133                }
134                Value::F64(f) => {
135                    let _ = arguments.add(*f);
136                }
137                Value::Text(s) => {
138                    let _ = arguments.add(s.clone());
139                }
140                Value::Bytes(b) => {
141                    let _ = arguments.add(b.clone());
142                }
143                #[cfg(feature = "json")]
144                Value::Json(j) => {
145                    // Match 1.x: store JSON as text.
146                    let _ = arguments.add(serde_json::to_string(j).unwrap_or_default());
147                }
148                #[cfg(feature = "uuid")]
149                Value::Uuid(u) => {
150                    let _ = arguments.add(*u);
151                }
152                #[cfg(feature = "chrono")]
153                Value::DateTimeUtc(dt) => {
154                    let _ = arguments.add(*dt);
155                }
156                #[cfg(feature = "chrono")]
157                Value::NaiveDateTime(dt) => {
158                    let _ = arguments.add(*dt);
159                }
160                #[cfg(feature = "chrono")]
161                Value::NaiveDate(d) => {
162                    let _ = arguments.add(*d);
163                }
164                #[cfg(feature = "chrono")]
165                Value::NaiveTime(t) => {
166                    let _ = arguments.add(*t);
167                }
168            }
169        }
170        arguments
171    }
172}
173
174#[cfg(feature = "sqlx_sqlite")]
175impl SqlxDialect for Sqlite {
176    type Database = sqlx::Sqlite;
177
178    fn bind_arguments(binds: &[Value]) -> sqlx::sqlite::SqliteArguments {
179        use sqlx::Arguments;
180        // sqlx 0.9 dropped the lifetime param from `SqliteArguments`; this is an
181        // owned set, matching 1.x `src/sqlx_sqlite.rs`.
182        let mut arguments = sqlx::sqlite::SqliteArguments::default();
183        for bind in binds {
184            // These encoders are infallible for the M1 `Value` set.
185            match bind {
186                Value::Null => {
187                    let _ = arguments.add(Option::<String>::None);
188                }
189                Value::Bool(b) => {
190                    let _ = arguments.add(*b);
191                }
192                Value::I64(i) => {
193                    let _ = arguments.add(*i);
194                }
195                Value::F64(f) => {
196                    let _ = arguments.add(*f);
197                }
198                Value::Text(s) => {
199                    let _ = arguments.add(s.clone());
200                }
201                Value::Bytes(b) => {
202                    let _ = arguments.add(b.clone());
203                }
204                #[cfg(feature = "json")]
205                Value::Json(j) => {
206                    // Match 1.x: store JSON as text.
207                    let _ = arguments.add(serde_json::to_string(j).unwrap_or_default());
208                }
209                #[cfg(feature = "uuid")]
210                Value::Uuid(u) => {
211                    let _ = arguments.add(*u);
212                }
213                #[cfg(feature = "chrono")]
214                Value::DateTimeUtc(dt) => {
215                    let _ = arguments.add(*dt);
216                }
217                #[cfg(feature = "chrono")]
218                Value::NaiveDateTime(dt) => {
219                    let _ = arguments.add(*dt);
220                }
221                #[cfg(feature = "chrono")]
222                Value::NaiveDate(d) => {
223                    let _ = arguments.add(*d);
224                }
225                #[cfg(feature = "chrono")]
226                Value::NaiveTime(t) => {
227                    let _ = arguments.add(*t);
228                }
229            }
230        }
231        arguments
232    }
233}
234
235#[cfg(any(
236    feature = "sqlx_postgres",
237    feature = "sqlx_mysql",
238    feature = "sqlx_sqlite"
239))]
240impl<D: SqlxDialect> QueryBuilder<D> {
241    /// Build an executable `sqlx::query::Query` from this builder.
242    ///
243    /// The SQL is builder-generated with bound placeholders, so it is asserted
244    /// safe via `sqlx::AssertSqlSafe` (which also satisfies sqlx 0.9's `'static`
245    /// SQL bound). Mirrors 1.x `ChainBuilder::to_sqlx_query`.
246    pub fn to_sqlx_query(
247        &self,
248    ) -> sqlx::query::Query<'_, D::Database, <D::Database as sqlx::Database>::Arguments> {
249        let (sql, binds) = self.to_sql();
250        sqlx::query_with(sqlx::AssertSqlSafe(sql), D::bind_arguments(&binds))
251    }
252
253    /// Build an executable `sqlx::query::QueryAs` decoding rows into `T`.
254    pub fn to_sqlx_query_as<T>(
255        &self,
256    ) -> sqlx::query::QueryAs<'_, D::Database, T, <D::Database as sqlx::Database>::Arguments>
257    where
258        T: for<'r> sqlx::FromRow<'r, <D::Database as sqlx::Database>::Row>,
259    {
260        let (sql, binds) = self.to_sql();
261        sqlx::query_as_with(sqlx::AssertSqlSafe(sql), D::bind_arguments(&binds))
262    }
263}