Skip to main content

sql_composer_mysql/
lib.rs

1//! MySQL driver for sql-composer (sync and async).
2//!
3//! Provides both sync and async wrappers for composing SQL templates
4//! with bind values against MySQL databases.
5//!
6//! - **Async**: [`MysqlConn`] wraps [`mysql_async::Conn`] (feature `async`, enabled by default)
7//! - **Sync**: [`MysqlConnection`] wraps [`mysql::Conn`] (feature `sync`, enabled by default)
8//!
9//! # Async Example
10//!
11//! ```ignore
12//! use sql_composer::composer::Composer;
13//! use sql_composer::driver::ComposerConnectionAsync;
14//! use sql_composer::types::{Dialect, TemplateSource};
15//! use sql_composer::bind_values;
16//! use sql_composer_mysql::MysqlConn;
17//!
18//! let pool = mysql_async::Pool::new("mysql://root@localhost/test");
19//! let conn = pool.get_conn().await?;
20//! let conn = MysqlConn::from_conn(conn);
21//!
22//! let template = sql_composer::parser::parse_template(
23//!     "SELECT * FROM users WHERE id = :bind(user_id)",
24//!     TemplateSource::Literal("example".into()),
25//! )?;
26//! let composer = Composer::new(Dialect::Mysql);
27//! let values = bind_values!("user_id" => [mysql_async::Value::from(1i32)]);
28//! let (sql, params) = conn.compose(&composer, &template, values).await?;
29//! ```
30//!
31//! # Sync Example
32//!
33//! ```ignore
34//! use sql_composer::composer::Composer;
35//! use sql_composer::driver::ComposerConnection;
36//! use sql_composer::types::{Dialect, TemplateSource};
37//! use sql_composer::bind_values;
38//! use sql_composer_mysql::MysqlConnection;
39//!
40//! let conn = mysql::Conn::new("mysql://root@localhost/test")?;
41//! let conn = MysqlConnection::from_conn(conn);
42//!
43//! let template = sql_composer::parser::parse_template(
44//!     "SELECT * FROM users WHERE id = :bind(user_id)",
45//!     TemplateSource::Literal("example".into()),
46//! )?;
47//! let composer = Composer::new(Dialect::Mysql);
48//! let values = bind_values!("user_id" => [mysql::Value::from(1i32)]);
49//! let (sql, params) = conn.compose(&composer, &template, values)?;
50//! ```
51
52#[cfg(feature = "async")]
53pub use mysql_async;
54
55#[cfg(feature = "sync")]
56pub use mysql;
57
58use std::collections::BTreeMap;
59use std::ops::{Deref, DerefMut};
60
61use sql_composer::composer::Composer;
62use sql_composer::driver;
63use sql_composer::types::Template;
64
65// ---------------------------------------------------------------------------
66// Async: MysqlConn (mysql_async)
67// ---------------------------------------------------------------------------
68
69/// Error type for async sql-composer-mysql operations.
70#[cfg(feature = "async")]
71#[derive(Debug, thiserror::Error)]
72pub enum AsyncError {
73    /// An error from the sql-composer core.
74    #[error(transparent)]
75    Composer(#[from] sql_composer::Error),
76
77    /// An error from mysql_async.
78    #[error(transparent)]
79    Mysql(#[from] mysql_async::Error),
80}
81
82/// A wrapper around [`mysql_async::Conn`] that implements [`sql_composer::driver::ComposerConnectionAsync`].
83///
84/// Dereferences to the inner `mysql_async::Conn`, so all native async
85/// methods are available directly.
86#[cfg(feature = "async")]
87pub struct MysqlConn(pub mysql_async::Conn);
88
89#[cfg(feature = "async")]
90impl MysqlConn {
91    /// Wrap an existing `mysql_async::Conn`.
92    pub fn from_conn(conn: mysql_async::Conn) -> Self {
93        Self(conn)
94    }
95}
96
97#[cfg(feature = "async")]
98impl Deref for MysqlConn {
99    type Target = mysql_async::Conn;
100
101    fn deref(&self) -> &Self::Target {
102        &self.0
103    }
104}
105
106#[cfg(feature = "async")]
107impl DerefMut for MysqlConn {
108    fn deref_mut(&mut self) -> &mut Self::Target {
109        &mut self.0
110    }
111}
112
113#[cfg(feature = "async")]
114impl driver::ComposerConnectionAsync for MysqlConn {
115    type Value = mysql_async::Value;
116    type Statement = String;
117    type Error = AsyncError;
118
119    async fn compose(
120        &self,
121        composer: &Composer,
122        template: &Template,
123        mut values: BTreeMap<String, Vec<Self::Value>>,
124    ) -> Result<(String, Vec<Self::Value>), AsyncError> {
125        let composed = composer.compose_with_values(template, &values)?;
126        let ordered = driver::resolve_values(&composed, &mut values)?;
127        Ok((composed.sql, ordered))
128    }
129}
130
131// ---------------------------------------------------------------------------
132// Sync: MysqlConnection (mysql)
133// ---------------------------------------------------------------------------
134
135/// Error type for sync sql-composer-mysql operations.
136#[cfg(feature = "sync")]
137#[derive(Debug, thiserror::Error)]
138pub enum SyncError {
139    /// An error from the sql-composer core.
140    #[error(transparent)]
141    Composer(#[from] sql_composer::Error),
142
143    /// An error from mysql.
144    #[error(transparent)]
145    Mysql(#[from] mysql::Error),
146}
147
148/// A wrapper around [`mysql::Conn`] that implements [`sql_composer::driver::ComposerConnection`].
149///
150/// Dereferences to the inner `mysql::Conn`, so all native sync methods
151/// are available directly.
152#[cfg(feature = "sync")]
153pub struct MysqlConnection(pub mysql::Conn);
154
155#[cfg(feature = "sync")]
156impl MysqlConnection {
157    /// Wrap an existing `mysql::Conn`.
158    pub fn from_conn(conn: mysql::Conn) -> Self {
159        Self(conn)
160    }
161}
162
163#[cfg(feature = "sync")]
164impl Deref for MysqlConnection {
165    type Target = mysql::Conn;
166
167    fn deref(&self) -> &Self::Target {
168        &self.0
169    }
170}
171
172#[cfg(feature = "sync")]
173impl DerefMut for MysqlConnection {
174    fn deref_mut(&mut self) -> &mut Self::Target {
175        &mut self.0
176    }
177}
178
179#[cfg(feature = "sync")]
180impl driver::ComposerConnection for MysqlConnection {
181    type Value = mysql::Value;
182    type Statement = String;
183    type Error = SyncError;
184
185    fn compose(
186        &self,
187        composer: &Composer,
188        template: &Template,
189        mut values: BTreeMap<String, Vec<Self::Value>>,
190    ) -> Result<(String, Vec<Self::Value>), SyncError> {
191        let composed = composer.compose_with_values(template, &values)?;
192        let ordered = driver::resolve_values(&composed, &mut values)?;
193        Ok((composed.sql, ordered))
194    }
195}
196
197// Backward-compatible type alias when both features are enabled
198/// Error type alias — resolves to [`AsyncError`] for backward compatibility.
199#[cfg(feature = "async")]
200pub type Error = AsyncError;
201
202#[cfg(test)]
203mod tests {
204    use sql_composer::composer::Composer;
205    use sql_composer::parser::parse_template;
206    use sql_composer::types::{Dialect, TemplateSource};
207
208    #[test]
209    fn test_compose_single_bind_mysql() {
210        let input = "SELECT * FROM users WHERE id = :bind(user_id)";
211        let template = parse_template(input, TemplateSource::Literal("test".into())).unwrap();
212        let composer = Composer::new(Dialect::Mysql);
213        let result = composer.compose(&template).unwrap();
214        assert_eq!(result.sql, "SELECT * FROM users WHERE id = ?");
215        assert_eq!(result.bind_params, vec!["user_id"]);
216    }
217
218    #[test]
219    fn test_compose_multiple_binds_mysql() {
220        let input = "SELECT * FROM users WHERE name = :bind(name) AND active = :bind(active)";
221        let template = parse_template(input, TemplateSource::Literal("test".into())).unwrap();
222        let composer = Composer::new(Dialect::Mysql);
223        let result = composer.compose(&template).unwrap();
224        // MySQL: document order, bare ?
225        assert_eq!(
226            result.sql,
227            "SELECT * FROM users WHERE name = ? AND active = ?"
228        );
229        assert_eq!(result.bind_params, vec!["name", "active"]);
230    }
231
232    #[test]
233    fn test_compose_with_values_multi_bind_mysql() {
234        let input = "SELECT * FROM users WHERE id IN (:bind(ids))";
235        let template = parse_template(input, TemplateSource::Literal("test".into())).unwrap();
236        let composer = Composer::new(Dialect::Mysql);
237        let values = sql_composer::bind_values!("ids" => [10, 20, 30]);
238        let result = composer.compose_with_values(&template, &values).unwrap();
239        assert_eq!(result.sql, "SELECT * FROM users WHERE id IN (?, ?, ?)");
240        assert_eq!(result.bind_params, vec!["ids", "ids", "ids"]);
241    }
242}