1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
//! Proc macros to perform type sql queries similarly to sqlx::query, but without the need
//! to run `cargo sqlx prepare`
//!
//! A schema definition must be placed in "sqlx-type-schema.sql" in the root of a using crate:
//!
//! ```sql
//! DROP TABLE IF EXISTS `t1`;
//! CREATE TABLE `t1` (
//!     `id` int(11) NOT NULL,
//!     `cbool` tinyint(1) NOT NULL,
//!     `cu8` tinyint UNSIGNED NOT NULL,
//!     `cu16` smallint UNSIGNED NOT NULL,
//!     `cu32` int UNSIGNED NOT NULL,
//!     `cu64` bigint UNSIGNED NOT NULL,
//!     `ci8` tinyint,
//!     `ci16` smallint,
//!     `ci32` int,
//!     `ci64` bigint,
//!     `ctext` varchar(100) NOT NULL,
//!     `cbytes` blob,
//!     `cf32` float,
//!     `cf64` double
//! ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
//!
//! ALTER TABLE `t1`
//!     MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
//! ```
//! See [sql_type::schema] for a detailed description.
//!
//! [sql_type::schema]: https://docs.rs/sql-type/latest/sql_type/schema/index.html
//!
//! This schema can then be used to type queries:
//!
//! ``` no_run
//! use {std::env, sqlx::MySqlPool, sqlx_type::query};
//!
//! async fn test() -> Result<(), sqlx::Error> {
//!     let pool = MySqlPool::connect(&env::var("DATABASE_URL").unwrap()).await?;
//!
//!     let id = query!("INSERT INTO `t1` (`cbool`, `cu8`, `cu16`, `cu32`, `cu64`, `ctext`)
//!         VALUES (?, ?, ?, ?, ?, ?)", true, 8, 1243, 42, 42, "Hello world")
//!         .execute(&pool).await?.last_insert_id();
//!
//!     let row = query!("SELECT `cu16`, `ctext`, `ci32` FROM `t1` WHERE `id`=?", id)
//!         .fetch_one(&pool).await?;
//!
//!     assert_eq!(row.cu16, 1234);
//!     assert_eq!(row.ctext, "Hello would");
//!     assert!(row.ci32.is_none());
//!     Ok(())
//! }
//! ```
#![forbid(unsafe_code)]
use sqlx_type_macro;

pub use crate::sqlx_type_macro::{query, query_as};

/// Tag type for integer input
#[doc(hidden)]
pub struct Integer;

/// Tag type for float input
#[doc(hidden)]
pub struct Float;

/// Tag type for timestamp input
#[doc(hidden)]
pub struct Timestamp;

/// Tag type for datetime input
#[doc(hidden)]
pub struct DateTime;

/// Tag type for date input
#[doc(hidden)]
pub struct Date;

/// Tag type for time input
#[doc(hidden)]
pub struct Time;

/// If ArgIn<T> is implemented for J, it means that J can be used as for arguments of type T
#[doc(hidden)]
pub trait ArgIn<T> {}
pub trait ArgOut<T, const IDX: usize> {}

macro_rules! arg_io {
    ( $dst: ty, $t: ty ) => {
        impl ArgIn<$dst> for $t {}
        impl ArgIn<$dst> for &$t {}
        impl ArgIn<Option<$dst>> for $t {}
        impl ArgIn<Option<$dst>> for &$t {}
        impl ArgIn<Option<$dst>> for Option<$t> {}
        impl ArgIn<Option<$dst>> for Option<&$t> {}
        impl ArgIn<Option<$dst>> for &Option<$t> {}
        impl ArgIn<Option<$dst>> for &Option<&$t> {}

        impl<const IDX: usize> ArgOut<$dst, IDX> for $t {}
        impl<const IDX: usize> ArgOut<Option<$dst>, IDX> for Option<$t> {}
        impl<const IDX: usize> ArgOut<$dst, IDX> for Option<$t> {}
    };
}

arg_io!(Integer, u64);
arg_io!(Integer, i64);
arg_io!(Integer, u32);
arg_io!(Integer, i32);
arg_io!(Integer, u16);
arg_io!(Integer, i16);
arg_io!(Integer, u8);
arg_io!(Integer, i8);

arg_io!(String, String);

arg_io!(Float, f64);
arg_io!(Float, f32);

arg_io!(u64, u64);
arg_io!(i64, i64);
arg_io!(u32, u32);
arg_io!(i32, i32);
arg_io!(u16, u16);
arg_io!(i16, i16);
arg_io!(u8, u8);
arg_io!(i8, i8);
arg_io!(bool, bool);
arg_io!(f32, f32);
arg_io!(f64, f64);

arg_io!(&str, &str);
arg_io!(&str, String);
arg_io!(&str, std::borrow::Cow<'_, str>);

arg_io!(&[u8], &[u8]);
arg_io!(&[u8], Vec<u8>);
arg_io!(Vec<u8>, Vec<u8>);

arg_io!(Timestamp, chrono::NaiveDateTime);
arg_io!(DateTime, chrono::NaiveDateTime);
arg_io!(chrono::DateTime<chrono::Utc>, chrono::DateTime<chrono::Utc>);

#[doc(hidden)]
pub fn check_arg<T, T2: ArgIn<T>>(_: &T2) {}

#[doc(hidden)]
pub fn check_arg_list_hack<T, T2: ArgIn<T>>(_: &[T2]) {}

#[doc(hidden)]
pub fn arg_out<T, T2: ArgOut<T, IDX>, const IDX: usize>(v: T2) -> T2 {
    v
}

#[doc(hidden)]
pub fn convert_list_query(query: &str, list_sizes: &[usize]) -> String {
    let mut query_iter = query.split("_LIST_");
    let mut query = query_iter.next().expect("None empty query").to_string();
    for size in list_sizes {
        if *size == 0 {
            query.push_str("NULL");
        } else {
            for i in 0..*size {
                if i == 0 {
                    query.push('?');
                } else {
                    query.push_str(", ?");
                }
            }
        }
        query.push_str(query_iter.next().expect("More _LIST_ in query"));
    }
    if query_iter.next().is_some() {
        panic!("Too many _LIST_ in query");
    }
    query
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_convert_list_query() {
        // This assert would fire and test will fail.
        // Please note, that private functions can be tested too!
        assert_eq!(
            &convert_list_query("FOO (_LIST_) X _LIST_ O _LIST_ BAR (_LIST_)", &[0, 1, 2, 3]),
            "FOO (NULL) X ? O ?, ? BAR (?, ?, ?)"
        );
    }
}