1#![allow(clippy::expect_used)]
5
6use crate::error::TypeError;
7use crate::value::SqlValue;
8
9pub trait ToSql {
14 fn to_sql(&self) -> Result<SqlValue, TypeError>;
16
17 fn sql_type(&self) -> &'static str;
19}
20
21impl ToSql for bool {
22 fn to_sql(&self) -> Result<SqlValue, TypeError> {
23 Ok(SqlValue::Bool(*self))
24 }
25
26 fn sql_type(&self) -> &'static str {
27 "BIT"
28 }
29}
30
31impl ToSql for u8 {
32 fn to_sql(&self) -> Result<SqlValue, TypeError> {
33 Ok(SqlValue::TinyInt(*self))
34 }
35
36 fn sql_type(&self) -> &'static str {
37 "TINYINT"
38 }
39}
40
41impl ToSql for i16 {
42 fn to_sql(&self) -> Result<SqlValue, TypeError> {
43 Ok(SqlValue::SmallInt(*self))
44 }
45
46 fn sql_type(&self) -> &'static str {
47 "SMALLINT"
48 }
49}
50
51impl ToSql for i32 {
52 fn to_sql(&self) -> Result<SqlValue, TypeError> {
53 Ok(SqlValue::Int(*self))
54 }
55
56 fn sql_type(&self) -> &'static str {
57 "INT"
58 }
59}
60
61impl ToSql for i64 {
62 fn to_sql(&self) -> Result<SqlValue, TypeError> {
63 Ok(SqlValue::BigInt(*self))
64 }
65
66 fn sql_type(&self) -> &'static str {
67 "BIGINT"
68 }
69}
70
71impl ToSql for f32 {
72 fn to_sql(&self) -> Result<SqlValue, TypeError> {
73 Ok(SqlValue::Float(*self))
74 }
75
76 fn sql_type(&self) -> &'static str {
77 "REAL"
78 }
79}
80
81impl ToSql for f64 {
82 fn to_sql(&self) -> Result<SqlValue, TypeError> {
83 Ok(SqlValue::Double(*self))
84 }
85
86 fn sql_type(&self) -> &'static str {
87 "FLOAT"
88 }
89}
90
91impl ToSql for str {
92 fn to_sql(&self) -> Result<SqlValue, TypeError> {
93 Ok(SqlValue::String(self.to_owned()))
94 }
95
96 fn sql_type(&self) -> &'static str {
97 "NVARCHAR"
98 }
99}
100
101impl ToSql for String {
102 fn to_sql(&self) -> Result<SqlValue, TypeError> {
103 Ok(SqlValue::String(self.clone()))
104 }
105
106 fn sql_type(&self) -> &'static str {
107 "NVARCHAR"
108 }
109}
110
111impl ToSql for [u8] {
112 fn to_sql(&self) -> Result<SqlValue, TypeError> {
113 Ok(SqlValue::Binary(bytes::Bytes::copy_from_slice(self)))
114 }
115
116 fn sql_type(&self) -> &'static str {
117 "VARBINARY"
118 }
119}
120
121impl ToSql for Vec<u8> {
122 fn to_sql(&self) -> Result<SqlValue, TypeError> {
123 Ok(SqlValue::Binary(bytes::Bytes::copy_from_slice(self)))
124 }
125
126 fn sql_type(&self) -> &'static str {
127 "VARBINARY"
128 }
129}
130
131impl<T: ToSql> ToSql for Option<T> {
132 fn to_sql(&self) -> Result<SqlValue, TypeError> {
133 match self {
134 Some(v) => v.to_sql(),
135 None => Ok(SqlValue::Null),
136 }
137 }
138
139 fn sql_type(&self) -> &'static str {
140 match self {
141 Some(v) => v.sql_type(),
142 None => "NULL",
143 }
144 }
145}
146
147impl<T: ToSql + ?Sized> ToSql for &T {
148 fn to_sql(&self) -> Result<SqlValue, TypeError> {
149 (*self).to_sql()
150 }
151
152 fn sql_type(&self) -> &'static str {
153 (*self).sql_type()
154 }
155}
156
157#[cfg(feature = "uuid")]
158impl ToSql for uuid::Uuid {
159 fn to_sql(&self) -> Result<SqlValue, TypeError> {
160 Ok(SqlValue::Uuid(*self))
161 }
162
163 fn sql_type(&self) -> &'static str {
164 "UNIQUEIDENTIFIER"
165 }
166}
167
168#[cfg(feature = "decimal")]
169impl ToSql for rust_decimal::Decimal {
170 fn to_sql(&self) -> Result<SqlValue, TypeError> {
171 Ok(SqlValue::Decimal(*self))
172 }
173
174 fn sql_type(&self) -> &'static str {
175 "DECIMAL"
176 }
177}
178
179#[cfg(feature = "chrono")]
180impl ToSql for chrono::NaiveDate {
181 fn to_sql(&self) -> Result<SqlValue, TypeError> {
182 Ok(SqlValue::Date(*self))
183 }
184
185 fn sql_type(&self) -> &'static str {
186 "DATE"
187 }
188}
189
190#[cfg(feature = "chrono")]
191impl ToSql for chrono::NaiveTime {
192 fn to_sql(&self) -> Result<SqlValue, TypeError> {
193 Ok(SqlValue::Time(*self))
194 }
195
196 fn sql_type(&self) -> &'static str {
197 "TIME"
198 }
199}
200
201#[cfg(feature = "chrono")]
202impl ToSql for chrono::NaiveDateTime {
203 fn to_sql(&self) -> Result<SqlValue, TypeError> {
204 Ok(SqlValue::DateTime(*self))
205 }
206
207 fn sql_type(&self) -> &'static str {
208 "DATETIME2"
209 }
210}
211
212#[cfg(feature = "chrono")]
213impl ToSql for chrono::DateTime<chrono::FixedOffset> {
214 fn to_sql(&self) -> Result<SqlValue, TypeError> {
215 Ok(SqlValue::DateTimeOffset(*self))
216 }
217
218 fn sql_type(&self) -> &'static str {
219 "DATETIMEOFFSET"
220 }
221}
222
223#[cfg(feature = "chrono")]
224impl ToSql for chrono::DateTime<chrono::Utc> {
225 fn to_sql(&self) -> Result<SqlValue, TypeError> {
226 let fixed = self.with_timezone(&chrono::FixedOffset::east_opt(0).expect("valid offset"));
228 Ok(SqlValue::DateTimeOffset(fixed))
229 }
230
231 fn sql_type(&self) -> &'static str {
232 "DATETIMEOFFSET"
233 }
234}
235
236#[cfg(feature = "json")]
237impl ToSql for serde_json::Value {
238 fn to_sql(&self) -> Result<SqlValue, TypeError> {
239 Ok(SqlValue::Json(self.clone()))
240 }
241
242 fn sql_type(&self) -> &'static str {
243 "NVARCHAR(MAX)"
244 }
245}
246
247#[cfg(test)]
248#[allow(clippy::unwrap_used)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn test_to_sql_i32() {
254 let value: i32 = 42;
255 assert_eq!(value.to_sql().unwrap(), SqlValue::Int(42));
256 assert_eq!(value.sql_type(), "INT");
257 }
258
259 #[test]
260 fn test_to_sql_string() {
261 let value = "hello".to_string();
262 assert_eq!(
263 value.to_sql().unwrap(),
264 SqlValue::String("hello".to_string())
265 );
266 assert_eq!(value.sql_type(), "NVARCHAR");
267 }
268
269 #[test]
270 fn test_to_sql_option() {
271 let some: Option<i32> = Some(42);
272 assert_eq!(some.to_sql().unwrap(), SqlValue::Int(42));
273
274 let none: Option<i32> = None;
275 assert_eq!(none.to_sql().unwrap(), SqlValue::Null);
276 }
277}