1use crate::{
2 ast::Value,
3 connector::{queryable::TakeRow, TypeIdentifier},
4 error::{Error, ErrorKind},
5};
6#[cfg(feature = "chrono")]
7use chrono::{DateTime, Datelike, NaiveDate, NaiveDateTime, NaiveTime, Timelike, Utc};
8use mysql_async::{
9 self as my,
10 consts::{ColumnFlags, ColumnType},
11};
12use std::convert::TryFrom;
13
14pub fn conv_params(params: &[Value<'_>]) -> crate::Result<my::Params> {
15 if params.is_empty() {
16 Ok(my::Params::Empty)
19 } else {
20 let mut values = Vec::with_capacity(params.len());
21
22 for pv in params {
23 let res = match pv {
24 Value::Int32(i) => i.map(|i| my::Value::Int(i as i64)),
25 Value::Int64(i) => i.map(my::Value::Int),
26 Value::Float(f) => f.map(my::Value::Float),
27 Value::Double(f) => f.map(my::Value::Double),
28 Value::Text(s) => s.clone().map(|s| my::Value::Bytes((*s).as_bytes().to_vec())),
29 Value::Bytes(bytes) => bytes.clone().map(|bytes| my::Value::Bytes(bytes.into_owned())),
30 Value::Enum(s) => s.clone().map(|s| my::Value::Bytes((*s).as_bytes().to_vec())),
31 Value::Boolean(b) => b.map(|b| my::Value::Int(b as i64)),
32 Value::Char(c) => c.map(|c| my::Value::Bytes(vec![c as u8])),
33 Value::Xml(s) => s.as_ref().map(|s| my::Value::Bytes((s).as_bytes().to_vec())),
34 Value::Array(_) => {
35 let msg = "Arrays are not supported in MySQL.";
36 let kind = ErrorKind::conversion(msg);
37
38 let mut builder = Error::builder(kind);
39 builder.set_original_message(msg);
40
41 return Err(builder.build());
42 }
43 #[cfg(feature = "bigdecimal")]
44 Value::Numeric(f) => f.as_ref().map(|f| my::Value::Bytes(f.to_string().as_bytes().to_vec())),
45 #[cfg(feature = "json")]
46 Value::Json(s) => match s {
47 Some(ref s) => {
48 let json = serde_json::to_string(s)?;
49 let bytes = json.into_bytes();
50
51 Some(my::Value::Bytes(bytes))
52 }
53 None => None,
54 },
55 #[cfg(feature = "uuid")]
56 Value::Uuid(u) => u.map(|u| my::Value::Bytes(u.hyphenated().to_string().into_bytes())),
57 #[cfg(feature = "chrono")]
58 Value::Date(d) => {
59 d.map(|d| my::Value::Date(d.year() as u16, d.month() as u8, d.day() as u8, 0, 0, 0, 0))
60 }
61 #[cfg(feature = "chrono")]
62 Value::Time(t) => {
63 t.map(|t| my::Value::Time(false, 0, t.hour() as u8, t.minute() as u8, t.second() as u8, 0))
64 }
65 #[cfg(feature = "chrono")]
66 Value::DateTime(dt) => dt.map(|dt| {
67 my::Value::Date(
68 dt.year() as u16,
69 dt.month() as u8,
70 dt.day() as u8,
71 dt.hour() as u8,
72 dt.minute() as u8,
73 dt.second() as u8,
74 dt.timestamp_subsec_micros(),
75 )
76 }),
77 };
78
79 match res {
80 Some(val) => values.push(val),
81 None => values.push(my::Value::NULL),
82 }
83 }
84
85 Ok(my::Params::Positional(values))
86 }
87}
88
89impl TypeIdentifier for my::Column {
90 fn is_real(&self) -> bool {
91 use ColumnType::*;
92
93 matches!(self.column_type(), MYSQL_TYPE_DECIMAL | MYSQL_TYPE_NEWDECIMAL)
94 }
95
96 fn is_float(&self) -> bool {
97 use ColumnType::*;
98
99 matches!(self.column_type(), MYSQL_TYPE_FLOAT)
100 }
101
102 fn is_double(&self) -> bool {
103 use ColumnType::*;
104
105 matches!(self.column_type(), MYSQL_TYPE_DOUBLE)
106 }
107
108 fn is_int32(&self) -> bool {
109 use ColumnType::*;
110
111 let is_unsigned = self.flags().intersects(ColumnFlags::UNSIGNED_FLAG);
112
113 matches!(
120 (self.column_type(), is_unsigned),
121 (MYSQL_TYPE_TINY, _)
122 | (MYSQL_TYPE_SHORT, _)
123 | (MYSQL_TYPE_YEAR, _)
124 | (MYSQL_TYPE_LONG, false)
125 | (MYSQL_TYPE_INT24, false)
126 )
127 }
128
129 fn is_int64(&self) -> bool {
130 use ColumnType::*;
131
132 let is_unsigned = self.flags().intersects(ColumnFlags::UNSIGNED_FLAG);
133
134 matches!(
139 (self.column_type(), is_unsigned),
140 (MYSQL_TYPE_LONGLONG, _) | (MYSQL_TYPE_LONG, true) | (MYSQL_TYPE_INT24, true)
141 )
142 }
143
144 fn is_datetime(&self) -> bool {
145 use ColumnType::*;
146
147 matches!(
148 self.column_type(),
149 MYSQL_TYPE_TIMESTAMP | MYSQL_TYPE_DATETIME | MYSQL_TYPE_TIMESTAMP2 | MYSQL_TYPE_DATETIME2
150 )
151 }
152
153 fn is_time(&self) -> bool {
154 use ColumnType::*;
155
156 matches!(self.column_type(), MYSQL_TYPE_TIME | MYSQL_TYPE_TIME2)
157 }
158
159 fn is_date(&self) -> bool {
160 use ColumnType::*;
161
162 matches!(self.column_type(), MYSQL_TYPE_DATE | MYSQL_TYPE_NEWDATE)
163 }
164
165 fn is_text(&self) -> bool {
166 use ColumnType::*;
167
168 let is_defined_text =
169 matches!(self.column_type(), MYSQL_TYPE_VARCHAR | MYSQL_TYPE_VAR_STRING | MYSQL_TYPE_STRING);
170
171 let is_bytes_but_text = matches!(
172 self.column_type(),
173 MYSQL_TYPE_TINY_BLOB | MYSQL_TYPE_MEDIUM_BLOB | MYSQL_TYPE_LONG_BLOB | MYSQL_TYPE_BLOB
174 ) && self.character_set() != 63;
175
176 is_defined_text || is_bytes_but_text
177 }
178
179 fn is_bytes(&self) -> bool {
180 use ColumnType::*;
181
182 let is_a_blob = matches!(
183 self.column_type(),
184 MYSQL_TYPE_TINY_BLOB | MYSQL_TYPE_MEDIUM_BLOB | MYSQL_TYPE_LONG_BLOB | MYSQL_TYPE_BLOB
185 ) && self.character_set() == 63;
186
187 let is_bits = self.column_type() == MYSQL_TYPE_BIT && self.column_length() > 1;
188
189 is_a_blob || is_bits
190 }
191
192 fn is_bool(&self) -> bool {
193 self.column_type() == ColumnType::MYSQL_TYPE_BIT && self.column_length() == 1
194 }
195
196 fn is_json(&self) -> bool {
197 self.column_type() == ColumnType::MYSQL_TYPE_JSON
198 }
199
200 fn is_enum(&self) -> bool {
201 self.flags() == ColumnFlags::ENUM_FLAG || self.column_type() == ColumnType::MYSQL_TYPE_ENUM
202 }
203
204 fn is_null(&self) -> bool {
205 self.column_type() == ColumnType::MYSQL_TYPE_NULL
206 }
207}
208
209impl TakeRow for my::Row {
210 fn take_result_row(&mut self) -> crate::Result<Vec<Value<'static>>> {
211 fn convert(row: &mut my::Row, i: usize) -> crate::Result<Value<'static>> {
212 let value = row.take(i).ok_or_else(|| {
213 let msg = "Index out of bounds";
214 let kind = ErrorKind::conversion(msg);
215
216 Error::builder(kind).build()
217 })?;
218
219 let column = row.columns_ref().get(i).ok_or_else(|| {
220 let msg = "Index out of bounds";
221 let kind = ErrorKind::conversion(msg);
222
223 Error::builder(kind).build()
224 })?;
225
226 let res = match value {
227 #[cfg(feature = "json")]
229 my::Value::Bytes(b) if column.is_json() => {
230 serde_json::from_slice(&b).map(Value::json).map_err(|_| {
231 let msg = "Unable to convert bytes to JSON";
232 let kind = ErrorKind::conversion(msg);
233
234 Error::builder(kind).build()
235 })?
236 }
237 my::Value::Bytes(b) if column.is_enum() => {
238 let s = String::from_utf8(b)?;
239 Value::enum_variant(s)
240 }
241 #[cfg(feature = "bigdecimal")]
243 my::Value::Bytes(b) if column.is_real() => {
244 let s = String::from_utf8(b).map_err(|_| {
245 let msg = "Could not convert NEWDECIMAL from bytes to String.";
246 let kind = ErrorKind::conversion(msg);
247
248 Error::builder(kind).build()
249 })?;
250
251 let dec = s.parse().map_err(|_| {
252 let msg = "Could not convert NEWDECIMAL string to a BigDecimal.";
253 let kind = ErrorKind::conversion(msg);
254
255 Error::builder(kind).build()
256 })?;
257
258 Value::numeric(dec)
259 }
260 my::Value::Bytes(b) if column.is_bool() => match b.as_slice() {
261 [0] => Value::boolean(false),
262 _ => Value::boolean(true),
263 },
264 my::Value::Bytes(b) if column.character_set() == 63 => Value::bytes(b),
266 my::Value::Bytes(s) => Value::text(String::from_utf8(s)?),
267 my::Value::Int(i) if column.is_int64() => Value::int64(i),
268 my::Value::Int(i) => Value::int32(i as i32),
269 my::Value::UInt(i) => Value::int64(i64::try_from(i).map_err(|_| {
270 let msg = "Unsigned integers larger than 9_223_372_036_854_775_807 are currently not handled.";
271 let kind = ErrorKind::value_out_of_range(msg);
272
273 Error::builder(kind).build()
274 })?),
275 my::Value::Float(f) => Value::from(f),
276 my::Value::Double(f) => Value::from(f),
277 #[cfg(feature = "chrono")]
278 my::Value::Date(year, month, day, hour, min, sec, micro) => {
279 if day == 0 || month == 0 {
280 let msg = format!(
281 "The column `{}` contained an invalid datetime value with either day or month set to zero.",
282 column.name_str()
283 );
284 let kind = ErrorKind::value_out_of_range(msg);
285 return Err(Error::builder(kind).build());
286 }
287
288 let time = NaiveTime::from_hms_micro_opt(hour.into(), min.into(), sec.into(), micro).unwrap();
289
290 let date = NaiveDate::from_ymd_opt(year.into(), month.into(), day.into()).unwrap();
291 let dt = NaiveDateTime::new(date, time);
292
293 Value::datetime(DateTime::<Utc>::from_utc(dt, Utc))
294 }
295 #[cfg(feature = "chrono")]
296 my::Value::Time(is_neg, days, hours, minutes, seconds, micros) => {
297 if is_neg {
298 let kind = ErrorKind::conversion("Failed to convert a negative time");
299 return Err(Error::builder(kind).build());
300 }
301
302 if days != 0 {
303 let kind = ErrorKind::conversion("Failed to read a MySQL `time` as duration");
304 return Err(Error::builder(kind).build());
305 }
306
307 let time =
308 NaiveTime::from_hms_micro_opt(hours.into(), minutes.into(), seconds.into(), micros).unwrap();
309 Value::time(time)
310 }
311 my::Value::NULL => match column {
312 t if t.is_bool() => Value::Boolean(None),
313 t if t.is_enum() => Value::Enum(None),
314 t if t.is_null() => Value::Int32(None),
315 t if t.is_int64() => Value::Int64(None),
316 t if t.is_int32() => Value::Int32(None),
317 t if t.is_float() => Value::Float(None),
318 t if t.is_double() => Value::Double(None),
319 t if t.is_text() => Value::Text(None),
320 t if t.is_bytes() => Value::Bytes(None),
321 #[cfg(feature = "bigdecimal")]
322 t if t.is_real() => Value::Numeric(None),
323 #[cfg(feature = "chrono")]
324 t if t.is_datetime() => Value::DateTime(None),
325 #[cfg(feature = "chrono")]
326 t if t.is_time() => Value::Time(None),
327 #[cfg(feature = "chrono")]
328 t if t.is_date() => Value::Date(None),
329 #[cfg(feature = "json")]
330 t if t.is_json() => Value::Json(None),
331 typ => {
332 let msg = format!("Value of type {typ:?} is not supported with the current configuration");
333
334 let kind = ErrorKind::conversion(msg);
335 return Err(Error::builder(kind).build());
336 }
337 },
338 #[cfg(not(feature = "chrono"))]
339 typ => {
340 let msg = format!("Value of type {:?} is not supported with the current configuration", typ);
341
342 let kind = ErrorKind::conversion(msg);
343 Err(Error::builder(kind).build())?
344 }
345 };
346
347 Ok(res)
348 }
349
350 let mut row = Vec::with_capacity(self.len());
351
352 for i in 0..self.len() {
353 row.push(convert(self, i)?);
354 }
355
356 Ok(row)
357 }
358}