Skip to main content

qusql_sqlx_type/
lib.rs

1//! Proc macros to perform type sql queries similarly to sqlx::query, but without the need
2//! to run `cargo sqlx prepare`
3//!
4//! A schema definition must be placed in "sqlx-type-schema.sql" in the root of a using crate:
5//!
6//! ```sql
7//! DROP TABLE IF EXISTS `t1`;
8//! CREATE TABLE `t1` (
9//!     `id` int(11) NOT NULL,
10//!     `cbool` tinyint(1) NOT NULL,
11//!     `cu8` tinyint UNSIGNED NOT NULL,
12//!     `cu16` smallint UNSIGNED NOT NULL,
13//!     `cu32` int UNSIGNED NOT NULL,
14//!     `cu64` bigint UNSIGNED NOT NULL,
15//!     `ci8` tinyint,
16//!     `ci16` smallint,
17//!     `ci32` int,
18//!     `ci64` bigint,
19//!     `ctext` varchar(100) NOT NULL,
20//!     `cbytes` blob,
21//!     `cf32` float,
22//!     `cf64` double
23//! ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
24//!
25//! ALTER TABLE `t1`
26//!     MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
27//! ```
28//! See [sql_type::schema] for a detailed description.
29//!
30//! [sql_type::schema]: https://docs.rs/sql-type/latest/sql_type/schema/index.html
31//!
32//! This schema can then be used to type queries:
33//!
34//! ``` no_run
35//! use {std::env, sqlx::MySqlPool, qusql_sqlx_type::query};
36//!
37//! async fn test() -> Result<(), sqlx::Error> {
38//!     let pool = MySqlPool::connect(&env::var("DATABASE_URL").unwrap()).await?;
39//!
40//!     let id = query!("INSERT INTO `t1` (`cbool`, `cu8`, `cu16`, `cu32`, `cu64`, `ctext`)
41//!         VALUES (?, ?, ?, ?, ?, ?)", true, 8, 1243, 42, 42, "Hello world")
42//!         .execute(&pool).await?.last_insert_id();
43//!
44//!     let row = query!("SELECT `cu16`, `ctext`, `ci32` FROM `t1` WHERE `id`=?", id)
45//!         .fetch_one(&pool).await?;
46//!
47//!     assert_eq!(row.cu16, 1234);
48//!     assert_eq!(row.ctext, "Hello would");
49//!     assert!(row.ci32.is_none());
50//!     Ok(())
51//! }
52//! ```
53#![forbid(unsafe_code)]
54#[allow(clippy::single_component_path_imports)]
55use qusql_sqlx_type_macro;
56
57pub use crate::qusql_sqlx_type_macro::{query, query_as};
58
59/// Tag type for integer input
60#[doc(hidden)]
61pub struct Integer;
62
63/// Tag type for float input
64#[doc(hidden)]
65pub struct Float;
66
67/// Tag type for timestamp input
68#[doc(hidden)]
69pub struct Timestamp;
70
71/// Tag type for datetime input
72#[doc(hidden)]
73pub struct DateTime;
74
75/// Tag type for date input
76#[doc(hidden)]
77pub struct Date;
78
79/// Tag type for time input
80#[doc(hidden)]
81pub struct Time;
82
83/// Tag type for time input
84#[doc(hidden)]
85pub struct Any;
86
87/// Tag type for UUID input/output
88#[doc(hidden)]
89pub struct Uuid;
90
91/// If ArgIn<T> is implemented for J, it means that J can be used as for arguments of type T
92#[doc(hidden)]
93pub trait ArgIn<T> {}
94
95/// If ArgOut<T> is implemented for J, it means that J can be used to read output of type T
96pub trait ArgOut<T, const IDX: usize> {}
97
98/// Implement ArgIn and ArgOut for the given types
99macro_rules! arg_io {
100    ( $dst: ty, $t: ty ) => {
101        impl ArgIn<$dst> for $t {}
102        impl ArgIn<$dst> for &$t {}
103        impl ArgIn<$dst> for Option<$t> {}
104        impl ArgIn<$dst> for Option<&$t> {}
105        impl ArgIn<$dst> for &Option<$t> {}
106        impl ArgIn<$dst> for &Option<&$t> {}
107        impl ArgIn<Option<$dst>> for $t {}
108        impl ArgIn<Option<$dst>> for &$t {}
109        impl ArgIn<Option<$dst>> for Option<$t> {}
110        impl ArgIn<Option<$dst>> for Option<&$t> {}
111        impl ArgIn<Option<$dst>> for &Option<$t> {}
112        impl ArgIn<Option<$dst>> for &Option<&$t> {}
113
114        impl<const IDX: usize> ArgOut<$dst, IDX> for $t {}
115        impl<const IDX: usize> ArgOut<Option<$dst>, IDX> for Option<$t> {}
116        impl<const IDX: usize> ArgOut<$dst, IDX> for Option<$t> {}
117        impl<const IDX: usize> ArgOut<Option<$dst>, IDX> for $t {}
118    };
119}
120
121arg_io!(Any, u64);
122arg_io!(Any, i64);
123arg_io!(Any, u32);
124arg_io!(Any, i32);
125arg_io!(Any, u16);
126arg_io!(Any, i16);
127arg_io!(Any, u8);
128arg_io!(Any, i8);
129arg_io!(Any, String);
130arg_io!(Any, f64);
131arg_io!(Any, f32);
132arg_io!(Any, &str);
133
134arg_io!(Integer, u64);
135arg_io!(Integer, i64);
136arg_io!(Integer, u32);
137arg_io!(Integer, i32);
138arg_io!(Integer, u16);
139arg_io!(Integer, i16);
140arg_io!(Integer, u8);
141arg_io!(Integer, i8);
142
143arg_io!(String, String);
144
145arg_io!(Float, f64);
146arg_io!(Float, f32);
147
148arg_io!(u64, u64);
149arg_io!(i64, i64);
150arg_io!(u32, u32);
151arg_io!(i32, i32);
152arg_io!(u16, u16);
153arg_io!(i16, i16);
154arg_io!(u8, u8);
155arg_io!(i8, i8);
156arg_io!(bool, bool);
157arg_io!(f32, f32);
158arg_io!(f64, f64);
159
160arg_io!(&str, &str);
161arg_io!(&str, String);
162arg_io!(&str, std::borrow::Cow<'_, str>);
163
164arg_io!(&[u8], &[u8]);
165arg_io!(&[u8], Vec<u8>);
166arg_io!(Vec<u8>, Vec<u8>);
167
168arg_io!(Any, Vec<u8>);
169arg_io!(Any, Vec<i32>);
170arg_io!(Any, Vec<i64>);
171arg_io!(Any, Vec<String>);
172arg_io!(Any, Vec<Vec<u8>>);
173arg_io!(Any, Vec<bool>);
174arg_io!(Any, Vec<f64>);
175
176/// Slice ArgIn impls for `Any` array arguments.
177impl ArgIn<Any> for &[u8] {}
178impl ArgIn<Option<Any>> for &[u8] {}
179impl ArgIn<Any> for &[i32] {}
180impl ArgIn<Option<Any>> for &[i32] {}
181impl ArgIn<Any> for &[i64] {}
182impl ArgIn<Option<Any>> for &[i64] {}
183impl ArgIn<Any> for &[String] {}
184impl ArgIn<Option<Any>> for &[String] {}
185impl ArgIn<Option<Any>> for Option<&[String]> {}
186impl ArgIn<Option<Any>> for Option<&[i32]> {}
187impl ArgIn<Option<Any>> for Option<&[i64]> {}
188impl ArgIn<Any> for &[f64] {}
189impl ArgIn<Option<Any>> for &[f64] {}
190impl ArgIn<Option<Any>> for Option<&[f64]> {}
191impl ArgIn<Any> for &[bool] {}
192impl ArgIn<Option<Any>> for &[bool] {}
193
194/// Blanket impls: any `Infallible` argument.
195impl<T> ArgIn<std::convert::Infallible> for T {}
196impl<T> ArgIn<Option<std::convert::Infallible>> for T {}
197
198arg_io!(Timestamp, chrono::NaiveDateTime);
199arg_io!(DateTime, chrono::NaiveDateTime);
200arg_io!(chrono::DateTime<chrono::Utc>, chrono::DateTime<chrono::Utc>);
201arg_io!(Timestamp, chrono::DateTime<chrono::Utc>);
202arg_io!(Date, chrono::NaiveDate);
203arg_io!(Uuid, String);
204arg_io!(Uuid, &str);
205
206#[cfg(feature = "uuid")]
207mod uuid_support {
208    //! UUID bindings when the `uuid` feature is enabled.
209    use super::*;
210    arg_io!(Uuid, uuid::Uuid);
211    arg_io!(uuid::Uuid, uuid::Uuid);
212    arg_io!(Any, Vec<uuid::Uuid>);
213    arg_io!(Any, Vec<Option<uuid::Uuid>>);
214    impl ArgIn<Any> for &[uuid::Uuid] {}
215    impl ArgIn<Option<Any>> for &[uuid::Uuid] {}
216    impl ArgIn<Option<Any>> for Option<&[uuid::Uuid]> {}
217}
218
219/// Re-export of [`uuid::Uuid`] when the `uuid` feature is enabled,
220/// or [`String`] otherwise.
221#[cfg(feature = "uuid")]
222#[doc(hidden)]
223pub type UuidValue = uuid::Uuid;
224
225/// Re-export of [`uuid::Uuid`] when the `uuid` feature is enabled,
226/// or [`String`] otherwise.
227#[cfg(not(feature = "uuid"))]
228#[doc(hidden)]
229pub type UuidValue = String;
230
231#[cfg(feature = "json")]
232mod json_support {
233    //! JSON bindings when the `json` feature is enabled.
234    use super::*;
235    arg_io!(String, serde_json::Value);
236    arg_io!(serde_json::Value, serde_json::Value);
237    arg_io!(&str, serde_json::Value);
238    arg_io!(Any, Vec<serde_json::Value>);
239    impl ArgIn<Any> for &[serde_json::Value] {}
240    impl ArgIn<Option<Any>> for &[serde_json::Value] {}
241}
242
243/// Re-export of [`serde_json::Value`] when the `json` feature is enabled.
244#[cfg(feature = "json")]
245#[doc(hidden)]
246pub type JsonValue = serde_json::Value;
247
248#[doc(hidden)]
249pub fn check_arg<T, T2: ArgIn<T>>(_: &T2) {}
250
251#[doc(hidden)]
252pub fn check_arg_list_hack<T, T2: ArgIn<T>>(_: &[T2]) {}
253
254#[doc(hidden)]
255pub fn arg_out<T, T2: ArgOut<T, IDX>, const IDX: usize>(v: T2) -> T2 {
256    v
257}
258
259#[doc(hidden)]
260pub fn convert_list_query(query: &str, list_sizes: &[usize]) -> String {
261    let mut query_iter = query.split("_LIST_");
262    let mut query = query_iter.next().expect("None empty query").to_string();
263    for size in list_sizes {
264        if *size == 0 {
265            query.push_str("NULL");
266        } else {
267            for i in 0..*size {
268                if i == 0 {
269                    query.push('?');
270                } else {
271                    query.push_str(", ?");
272                }
273            }
274        }
275        query.push_str(query_iter.next().expect("More _LIST_ in query"));
276    }
277    if query_iter.next().is_some() {
278        panic!("Too many _LIST_ in query");
279    }
280    query
281}
282
283#[cfg(test)]
284mod tests {
285    use super::*;
286
287    #[test]
288    fn test_convert_list_query() {
289        // This assert would fire and test will fail.
290        // Please note, that private functions can be tested too!
291        assert_eq!(
292            &convert_list_query("FOO (_LIST_) X _LIST_ O _LIST_ BAR (_LIST_)", &[0, 1, 2, 3]),
293            "FOO (NULL) X ? O ?, ? BAR (?, ?, ?)"
294        );
295    }
296}