mysql_common/
lib.rs

1// Copyright (c) 2017 Anatoly Ikorsky
2//
3// Licensed under the Apache License, Version 2.0
4// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
5// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. All files in the project carrying such notice may not be copied,
7// modified, or distributed except according to those terms.
8
9//! This crate is an implementation of basic MySql protocol primitives.
10//!
11//! This crate:
12//! * defines basic MySql constants;
13//! * implements necessary functionality for MySql `cached_sha2_password`,
14//!   `mysql_native_password` and legacy authentication plugins;
15//! * implements helper traits for MySql protocol IO;
16//! * implements support of named parameters for prepared statements;
17//! * implements parsers for a subset of MySql protocol packets (including binlog packets);
18//! * defines rust representation of MySql protocol values and rows;
19//! * implements conversion between MySql values and rust types, between MySql rows and tuples
20//!   of rust types.
21//! * implements [FromRow and FromValue derive macros][2]
22//!
23//! ## Supported rust types
24//!
25//! Crate offers conversion from/to MySql values for following types (please see MySql documentation
26//! on supported ranges for numeric types). Following table refers to MySql protocol types
27//! (see `Value` struct) and not to MySql column types. Please see [MySql documentation][1] for
28//! column and protocol type correspondence:
29//!
30//! | Type                                 | Notes                                                     |
31//! | ------------------------------------ | -------------------------------------------------------   |
32//! | `{i,u}8..{i,u}128`, `{i,u}size`      | MySql int/uint will be converted, bytes will be parsed.<br>⚠️ Note that range of `{i,u}128` is greater than supported by MySql integer types but it'll be serialized anyway (as decimal bytes string). |
33//! | `f32`                                | MySql float will be converted to `f32`, bytes will be parsed as `f32`.<br>⚠️ MySql double won't be converted to `f32` to avoid precision loss (see #17) |
34//! | `f64`                                | MySql float and double will be converted to `f64`, bytes will be parsed as `f64`. |
35//! | `bool`                               | MySql int {`0`, `1`} or bytes {`"0x30"`, `"0x31"`}        |
36//! | `Vec<u8>`                            | MySql bytes                                               |
37//! | `String`                             | MySql bytes parsed as utf8                                |
38//! | `Duration` (`std` and `time`)        | MySql time or bytes parsed as MySql time string           |
39//! | [`time::PrimitiveDateTime`] (v0.2.x) | MySql date time or bytes parsed as MySql date time string (⚠️ lossy! microseconds are ignored)           |
40//! | [`time::Date`] (v0.2.x)              | MySql date or bytes parsed as MySql date string (⚠️ lossy! microseconds are ignored)           |
41//! | [`time::Time`] (v0.2.x)              | MySql time or bytes parsed as MySql time string (⚠️ lossy! microseconds are ignored)           |
42//! | [`time::Duration`] (v0.2.x)          | MySql time or bytes parsed as MySql time string           |
43//! | [`time::PrimitiveDateTime`] (v0.3.x) | MySql date time or bytes parsed as MySql date time string (⚠️ lossy! microseconds are ignored)           |
44//! | [`time::Date`] (v0.3.x)              | MySql date or bytes parsed as MySql date string (⚠️ lossy! microseconds are ignored)           |
45//! | [`time::Time`] (v0.3.x)              | MySql time or bytes parsed as MySql time string (⚠️ lossy! microseconds are ignored)           |
46//! | [`time::Duration`] (v0.3.x)          | MySql time or bytes parsed as MySql time string           |
47//! | [`chrono::NaiveTime`]                | MySql date or bytes parsed as MySql date string           |
48//! | [`chrono::NaiveDate`]                | MySql date or bytes parsed as MySql date string           |
49//! | [`chrono::NaiveDateTime`]            | MySql date or bytes parsed as MySql date string           |
50//! | [`uuid::Uuid`]                       | MySql bytes parsed using `Uuid::from_slice`               |
51//! | [`serde_json::Value`]                | MySql bytes parsed using `serde_json::from_str`           |
52//! | `mysql_common::Deserialized<T : DeserializeOwned>` | MySql bytes parsed using `serde_json::from_str` |
53//! | `Option<T: FromValue>`               | Must be used for nullable columns to avoid errors         |
54//! | [`decimal::Decimal`]                 | MySql int, uint or bytes parsed using `Decimal::from_str`.<br>⚠️ Note that this type doesn't support full range of MySql `DECIMAL` type. |
55//! | [`bigdecimal::BigDecimal`]           | MySql int, uint, floats or bytes parsed using `BigDecimal::parse_bytes`.<br>⚠️ Note that range of this type is greater than supported by MySql `DECIMAL` type but it'll be serialized anyway. |
56//! | `num_bigint::{BigInt, BigUint}`      | MySql int, uint or bytes parsed using `_::parse_bytes`.<br>⚠️ Note that range of this type is greater than supported by MySql integer types but it'll be serialized anyway (as decimal bytes string). |
57//!
58//! Also crate provides from-row convertion for the following list of types (see `FromRow` trait):
59//!
60//! | Type                                            | Notes                                             |
61//! | ----------------------------------------------- | ------------------------------------------------- |
62//! | `Row`                                           | Trivial conversion for `Row` itself.              |
63//! | `T: FromValue`                                  | For rows with a single column.                    |
64//! | `(T1: FromValue [, ..., T12: FromValue])`       | Row to a tuple of arity 1-12.                     |
65//! | [`frunk::hlist::HList`] types                   | Usefull to overcome tuple arity limitation        |
66//!
67//! ## Crate features
68//!
69//! | Feature          | Description                                          | Default |
70//! | ---------------- | ---------------------------------------------------- | ------- |
71//! | `bigdecimal`     | Enables `bigdecimal` >=0.3.x, <0.5.x types support   | 🔴      |
72//! | `chrono`         | Enables `chrono` types support                       | 🔴      |
73//! | `rust_decimal`   | Enables `rust_decimal` types support                 | 🔴      |
74//! | `time`           | Enables `time` v0.3.x types support                  | 🔴      |
75//! | `frunk`          | Enables `FromRow` for `frunk::Hlist!` types          | 🔴      |
76//! | `derive`         | Enables [`FromValue` and `FromRow` derive macros][2] | 🟢      |
77//! | `binlog`         | Binlog-related functionality                         | 🔴      |
78//! | `client_ed25519` | Enables [ed25519] authentication plugin support      | 🔴      |   
79//!
80//! [ed25519]: https://mariadb.com/kb/en/authentication-plugin-ed25519/
81//!
82//! # Derive Macros
83//!
84//! ## `FromValue` Derive
85//!
86//! Supported derivations:
87//!
88//! *   for enum – you should carefully read the [corresponding section of MySql documentation][4].
89//! *   for newtypes (see [New Type Idiom][3]) – given that the wrapped type itself satisfies
90//!     `FromValue`.
91//!
92//! ### Enums
93//!
94//! #### Container attributes:
95//!
96//! *  `#[mysql(crate_name = "some_name")]` – overrides an attempt to guess a crate that provides
97//!    required traits
98//! *  `#[mysql(rename_all = ...)]` – rename all the variants according to the given case
99//!    convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase",
100//!    "snake_case", "SCREAMING_SNAKE_CASE", "kebab-case", "SCREAMING-KEBAB-CASE"
101//! *  `#[mysql(is_integer)]` – tells derive macro that the value is an integer rather than MySql
102//!    ENUM. Macro won't warn if variants are sparse or greater than u16 and will not try to parse
103//!    textual representation.
104//! *  `#[mysql(is_string)]` – tells derive macro that the value is a string rather than MySql
105//!    ENUM. Macro won't warn if variants are sparse or greater than u16 and will not try to parse
106//!    integer representation.
107//!
108//! #### Example
109//!
110//! Given `ENUM('x-small', 'small', 'medium', 'large', 'x-large')` on MySql side:
111//!
112//! ```no_run
113//! # use mysql_common_derive::FromValue;
114//! # use mysql_common::{row::Row, row::convert::from_row};
115//!
116//! fn main() {
117//!
118//! /// Note: the `crate_name` attribute should not be necessary.
119//! #[derive(FromValue)]
120//! #[mysql(rename_all = "kebab-case", crate_name = "mysql_common")]
121//! #[repr(u8)]
122//! enum Size {
123//!     XSmall = 1,
124//!     Small,
125//!     Medium,
126//!     Large,
127//!     XLarge,
128//! }
129//!
130//! fn assert_from_row_works(x: Row) -> Size {
131//!     from_row(x)
132//! }
133//!
134//! }
135//! ```
136//!
137//! ### Newtypes
138//!
139//! It is expected, that wrapper value satisfies `FromValue` or `deserialize_with` is given.
140//! Also note, that to support `FromRow` the wrapped value must satisfy `Into<Value>` or
141//! `serialize_with` must be given.
142//!
143//! #### Container attributes:
144//!
145//! *  `#[mysql(crate_name = "some_name")]` – overrides an attempt to guess a crate to import types from
146//! *  `#[mysql(bound = "Foo: Bar, Baz: Quux")]` – use the following additional bounds
147//! *  `#[mysql(deserialize_with = "some::path")]` – use the following function to deserialize
148//!    the wrapped value. Expected signature is `fn (Value) -> Result<Wrapped, FromValueError>`.
149//! *  `#[mysql(serialize_with = "some::path")]` – use the following function to serialize
150//!    the wrapped value. Expected signature is `fn (Wrapped) -> Value`.
151//!
152//! #### Example
153//!
154//! ```no_run
155//! # use mysql_common::{row::Row, row::convert::from_row, prelude::FromValue, value::Value, value::convert::{from_value, FromValueError}};
156//! # use std::convert::TryFrom;
157//!
158//! /// Trivial example
159//! #[derive(FromValue)]
160//! # #[mysql(crate_name = "mysql_common")]
161//! struct Inch(i32);
162//!
163//! /// Example of a {serialize|deserialize}_with.
164//! #[derive(FromValue)]
165//! # #[mysql(crate_name = "mysql_common")]
166//! #[mysql(deserialize_with = "neg_de", serialize_with = "neg_ser")]
167//! struct Neg(i64);
168//!
169//! /// Wrapped generic. Bounds are inferred.
170//! #[derive(FromValue)]
171//! # #[mysql(crate_name = "mysql_common")]
172//! struct Foo<T>(Option<T>);
173//!
174//! /// Example of additional bounds.
175//! #[derive(FromValue)]
176//! # #[mysql(crate_name = "mysql_common")]
177//! #[mysql(bound = "'b: 'a, T: 'a, U: From<String>, V: From<u64>")]
178//! struct Bar<'a, 'b, const N: usize, T, U, V>(ComplexTypeToWrap<'a, 'b, N, T, U, V>);
179//!
180//! fn assert_from_row_works<'a, 'b, const N: usize, T, U, V>(x: Row) -> (Inch, Neg, Foo<u8>, Bar<'a, 'b, N, T, U, V>)
181//! where 'b: 'a, T: 'a, U: From<String>, V: From<u64>,
182//! {
183//!     from_row(x)
184//! }
185//!
186//!
187//! // test boilerplate..
188//!
189//!
190//! /// Dummy complex type with additional bounds on FromValue impl.
191//! struct ComplexTypeToWrap<'a, 'b, const N: usize, T, U, V>([(&'a T, &'b U, V); N]);
192//!
193//! struct FakeIr;
194//!
195//! impl TryFrom<Value> for FakeIr {
196//!     // ...
197//! #    type Error = FromValueError;
198//! #    fn try_from(v: Value) -> Result<Self, Self::Error> {
199//! #        unimplemented!();
200//! #    }
201//! }
202//!
203//! impl<'a, 'b: 'a, const N: usize, T: 'a, U: From<String>, V: From<u64>> From<FakeIr> for ComplexTypeToWrap<'a, 'b, N, T, U, V> {
204//!     // ...
205//! #    fn from(x: FakeIr) -> Self {
206//! #        unimplemented!();
207//! #    }
208//! }
209//!
210//! impl From<FakeIr> for Value {
211//!     // ...
212//! #    fn from(x: FakeIr) -> Self {
213//! #        unimplemented!();
214//! #    }
215//! }
216//!
217//! impl<'a, 'b: 'a, const N: usize, T: 'a, U: From<String>, V: From<u64>> FromValue for ComplexTypeToWrap<'a, 'b, N, T, U, V> {
218//!     type Intermediate = FakeIr;
219//! }
220//!
221//! fn neg_de(v: Value) -> Result<i64, FromValueError> {
222//!     match v {
223//!         Value::Int(x) => Ok(-x),
224//!         Value::UInt(x) => Ok(-(x as i64)),
225//!         x => Err(FromValueError(x)),
226//!     }
227//! }
228//!
229//! fn neg_ser(x: i64) -> Value {
230//!     Value::Int(-x)
231//! }
232//!
233//! # fn main() {}
234//! ```
235//!
236//! ## `FromRow` Derive
237//!
238//! Also defines some constants on the struct:
239//!
240//! *  `const TABLE_NAME: &str` – if `table_name` is given
241//! *  `const {}_FIELD: &str` – for each struct field (`{}` is a SCREAMING_SNAKE_CASE representation
242//!    of a struct field name (not a column name))
243//!
244//! Supported derivations:
245//!
246//! * for a struct with named fields – field name will be used as a column name to search for a value
247//!
248//! ### Container attributes:
249//!
250//! *  `#[mysql(crate_name = "some_name")]` – overrides an attempt to guess a crate that provides
251//!    required traits
252//! *  `#[mysql(rename_all = ...)]` – rename all column names according to the given case
253//!    convention. The possible values are "lowercase", "UPPERCASE", "PascalCase", "camelCase",
254//!    "snake_case", "SCREAMING_SNAKE_CASE", "kebab-case", "SCREAMING-KEBAB-CASE"
255//! *  `#[mysql(table_name = "some_name")]` – defines `pub const TABLE_NAME: &str` on the struct
256//!
257//! ### Field attributes:
258//!
259//! *  `#[mysql(rename = "some_name")]` – overrides column name of a field
260//! *  `#[mysql(json)]` - column will be interpreted as a JSON string containing
261//!    a value of a field type
262//! *  `#[mysql(deserialize_with = "some::path")]` – the following function
263//!    will be used to deserialize the field (instead of `FromValue`). Expected signature is
264//!    `fn (Value) -> Result<T, FromValueError>`.
265//! *  `#[mysql(serialize_with = "some::path")]` – the following function
266//!    will be used to serialize the field (instead of `Into<Value>`). Expected signature is
267//!    `fn (T) -> Value`.
268//!
269//! ### Example
270//!
271//! ```
272//! # use mysql_common_derive::FromRow;
273//! # use mysql_common::{
274//! #     constants::ColumnType,
275//! #     packets::Column,
276//! #     prelude::FromValue,
277//! #     row::{Row, new_row},
278//! #     row::convert::from_row,
279//! #     value::Value,
280//! #     value::convert::from_value,
281//! #     FromValueError,
282//! # };
283//! use time::{
284//!     macros::{datetime, offset}, OffsetDateTime, PrimitiveDateTime, UtcOffset,
285//! };
286//!
287//! /// Note: the `crate_name` attribute should not be necessary.
288//! #[derive(Debug, PartialEq, Eq, FromRow)]
289//! #[mysql(table_name = "Foos", crate_name = "mysql_common")]
290//! struct Foo {
291//!     id: u64,
292//!     #[mysql(
293//!         serialize_with = "datetime_to_value",
294//!         deserialize_with = "value_to_datetime",
295//!     )]
296//!     ctime: OffsetDateTime,
297//!     #[mysql(json, rename = "def")]
298//!     definition: Bar,
299//!     child: Option<u64>,
300//! }
301//!
302//! fn value_to_datetime(value: Value) -> Result<OffsetDateTime, FromValueError> {
303//!     // assume mysql session timezone has been properly set up
304//!     const OFFSET: UtcOffset = offset!(+3);
305//!
306//!     let primitive = PrimitiveDateTime::from_value_opt(value)?;
307//!     Ok(primitive.assume_offset(OFFSET))
308//! }
309//!
310//! fn datetime_to_value(datetime: OffsetDateTime) -> Value {
311//!     // assume mysql session timezone has been properly set up
312//!     PrimitiveDateTime::new(datetime.date(), datetime.time()).into()
313//! }
314//!
315//! #[derive(Debug, serde::Deserialize, PartialEq, Eq)]
316//! enum Bar {
317//!     Left,
318//!     Right,
319//! }
320//!
321//! /// Returns the following row:
322//! ///
323//! /// ```
324//! /// +----+-----------+-------+------------------------+
325//! /// | id | def       | child | ctime                  |
326//! /// +----+-----------+-------+------------------------+
327//! /// | 42 | '"Right"' | NULL  | '2015-05-15 12:00:00'  |
328//! /// +----+-----------+-------+------------------------+
329//! /// ```
330//! fn get_row() -> Row {
331//!     // ...
332//! #   let values = vec![
333//! #       Value::Int(42),
334//! #       Value::Bytes(b"\"Right\"".as_slice().into()),
335//! #       Value::NULL,
336//! #       Value::Date(2015, 5, 15, 12, 0, 0, 0),
337//! #   ];
338//! #   let columns = vec![
339//! #       Column::new(ColumnType::MYSQL_TYPE_LONG).with_name(b"id"),
340//! #       Column::new(ColumnType::MYSQL_TYPE_BLOB).with_name(b"def"),
341//! #       Column::new(ColumnType::MYSQL_TYPE_NULL).with_name(b"child"),
342//! #       Column::new(ColumnType::MYSQL_TYPE_STRING).with_name(b"ctime"),
343//! #   ];
344//! #   new_row(values, columns.into_boxed_slice().into())
345//! }
346//!
347//! # fn main() {
348//! assert_eq!(Foo::TABLE_NAME, "Foos");
349//! assert_eq!(Foo::ID_FIELD, "id");
350//! assert_eq!(Foo::DEFINITION_FIELD, "def");
351//! assert_eq!(Foo::CHILD_FIELD, "child");
352//!
353//! let foo = from_row::<Foo>(get_row());
354//! assert_eq!(
355//!     foo,
356//!     Foo {
357//!         id: 42,
358//!         definition: Bar::Right,
359//!         child: None,
360//!         ctime: datetime!(2015-05-15 12:00 +3),
361//!     }
362//! );
363//! # }
364//! ```
365//!
366//! [1]: https://dev.mysql.com/doc/internals/en/binary-protocol-value.html
367//! [2]: #derive-macros
368//! [3]: https://doc.rust-lang.org/rust-by-example/generics/new_types.html
369//! [4]: https://dev.mysql.com/doc/refman/8.0/en/enum.html
370#![cfg_attr(feature = "nightly", feature(test))]
371#![cfg_attr(docsrs, feature(doc_cfg))]
372
373// The `test` feature is required to compile tests.
374// It'll bind test binaries to an official C++ impl of MySql decimals (see build.rs)
375// The idea is to test our rust impl against C++ impl.
376#[cfg(all(not(feature = "test"), test))]
377compile_error!("Please invoke `cargo test` with `--features test` flags");
378
379#[cfg(feature = "nightly")]
380extern crate test;
381
382#[macro_use]
383pub mod bitflags_ext;
384
385#[cfg(feature = "bigdecimal")]
386pub use bigdecimal;
387
388#[cfg(feature = "chrono")]
389pub use chrono;
390
391#[cfg(feature = "frunk")]
392pub use frunk;
393
394#[cfg(feature = "rust_decimal")]
395pub use rust_decimal;
396
397#[cfg(feature = "time")]
398pub use time;
399
400pub use uuid;
401
402#[cfg(feature = "derive")]
403#[allow(unused_imports)]
404#[macro_use]
405extern crate mysql_common_derive;
406
407pub use num_bigint;
408pub use serde;
409pub use serde_json;
410
411pub use value::Value;
412pub use value::convert::FromValueError;
413
414pub use row::Row;
415pub use row::convert::FromRowError;
416
417pub use value::json::{Deserialized, Serialized};
418
419pub mod prelude {
420    #[cfg(feature = "derive")]
421    #[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
422    #[doc(inline)]
423    pub use mysql_common_derive::FromValue;
424
425    #[cfg(feature = "derive")]
426    #[cfg_attr(docsrs, doc(cfg(feature = "derive")))]
427    #[doc(inline)]
428    pub use mysql_common_derive::FromRow;
429
430    pub use crate::row::{ColumnIndex, convert::FromRow};
431    pub use crate::value::convert::{FromValue, ToValue};
432}
433
434/// This macro is a convenient way to pass named parameters to a statement.
435///
436/// ```ignore
437/// let foo = 42;
438/// conn.prep_exec("SELECT :foo, :foo2x", params! {
439///     foo,
440///     "foo2x" => foo * 2,
441/// });
442/// ```
443#[macro_export]
444macro_rules! params {
445    () => {};
446    (@to_pair $map:expr_2021, $name:expr_2021 => $value:expr_2021) => (
447        let entry = $map.entry(std::vec::Vec::<u8>::from($name));
448        if let std::collections::hash_map::Entry::Occupied(_) = entry {
449            panic!("Redefinition of named parameter `{}'", std::string::String::from_utf8_lossy(entry.key()));
450        } else {
451            entry.or_insert($crate::value::Value::from($value));
452        }
453    );
454    (@to_pair $map:expr_2021, $name:ident) => (
455        let entry = $map.entry(stringify!($name).as_bytes().to_vec());
456        if let std::collections::hash_map::Entry::Occupied(_) = entry {
457            panic!("Redefinition of named parameter `{}'", std::string::String::from_utf8_lossy(entry.key()));
458        } else {
459            entry.or_insert($crate::value::Value::from($name));
460        }
461    );
462    (@expand $map:expr_2021;) => {};
463    (@expand $map:expr_2021; $name:expr_2021 => $value:expr_2021, $($tail:tt)*) => {
464        params!(@to_pair $map, $name => $value);
465        params!(@expand $map; $($tail)*);
466    };
467    (@expand $map:expr_2021; $name:expr_2021 => $value:expr_2021 $(, $tail:tt)*) => {
468        params!(@to_pair $map, $name => $value);
469        params!(@expand $map; $($tail)*);
470    };
471    (@expand $map:expr_2021; $name:ident, $($tail:tt)*) => {
472        params!(@to_pair $map, $name);
473        params!(@expand $map; $($tail)*);
474    };
475    (@expand $map:expr_2021; $name:ident $(, $tail:tt)*) => {
476        params!(@to_pair $map, $name);
477        params!(@expand $map; $($tail)*);
478    };
479    ($i:ident, $($tail:tt)*) => {
480        {
481            let mut map: std::collections::HashMap<std::vec::Vec<u8>, $crate::value::Value, _> = std::default::Default::default();
482            params!(@expand (&mut map); $i, $($tail)*);
483            $crate::params::Params::Named(map)
484        }
485    };
486    ($i:expr_2021 => $($tail:tt)*) => {
487        {
488            let mut map: std::collections::HashMap<std::vec::Vec<u8>, $crate::value::Value, _> = std::default::Default::default();
489            params!(@expand (&mut map); $i => $($tail)*);
490            $crate::params::Params::Named(map)
491        }
492    };
493    ($i:ident) => {
494        {
495            let mut map: std::collections::HashMap<std::vec::Vec<u8>, $crate::value::Value, _> = std::default::Default::default();
496            params!(@expand (&mut map); $i);
497            $crate::params::Params::Named(map)
498        }
499    }
500}
501
502pub mod collations;
503pub mod constants;
504pub mod crypto;
505pub mod io;
506pub mod misc;
507pub mod named_params;
508#[macro_use]
509pub mod packets;
510pub mod params;
511pub mod proto;
512pub mod row;
513pub mod scramble;
514pub mod value;
515
516#[cfg(feature = "binlog")]
517#[cfg_attr(docsrs, doc(cfg(feature = "binlog")))]
518pub mod binlog;
519
520#[cfg(test)]
521#[test]
522fn params_macro_test() {
523    use crate::{params::Params, value::Value};
524
525    let foo = 42;
526    let bar = "bar";
527
528    assert_eq!(
529        Params::from(vec![(String::from("foo"), Value::Int(42))]),
530        params! { foo }
531    );
532    assert_eq!(
533        Params::from(vec![(String::from("foo"), Value::Int(42))]),
534        params! { foo, }
535    );
536    assert_eq!(
537        Params::from(vec![
538            (String::from("foo"), Value::Int(42)),
539            (String::from("bar"), Value::Bytes((&b"bar"[..]).into())),
540        ]),
541        params! { foo, bar }
542    );
543    assert_eq!(
544        Params::from(vec![
545            (String::from("foo"), Value::Int(42)),
546            (String::from("bar"), Value::Bytes((&b"bar"[..]).into())),
547        ]),
548        params! { foo, bar, }
549    );
550    assert_eq!(
551        Params::from(vec![
552            (String::from("foo"), Value::Int(42)),
553            (String::from("bar"), Value::Bytes((&b"bar"[..]).into())),
554        ]),
555        params! { "foo" => foo, "bar" => bar }
556    );
557    assert_eq!(
558        Params::from(vec![
559            (String::from("foo"), Value::Int(42)),
560            (String::from("bar"), Value::Bytes((&b"bar"[..]).into())),
561        ]),
562        params! { "foo" => foo, "bar" => bar, }
563    );
564    assert_eq!(
565        Params::from(vec![
566            (String::from("foo"), Value::Int(42)),
567            (String::from("bar"), Value::Bytes((&b"bar"[..]).into())),
568        ]),
569        params! { foo, "bar" => bar }
570    );
571    assert_eq!(
572        Params::from(vec![
573            (String::from("foo"), Value::Int(42)),
574            (String::from("bar"), Value::Bytes((&b"bar"[..]).into())),
575        ]),
576        params! { "foo" => foo, bar }
577    );
578    assert_eq!(
579        Params::from(vec![
580            (String::from("foo"), Value::Int(42)),
581            (String::from("bar"), Value::Bytes((&b"bar"[..]).into())),
582        ]),
583        params! { foo, "bar" => bar, }
584    );
585    assert_eq!(
586        Params::from(vec![
587            (String::from("foo"), Value::Int(42)),
588            (String::from("bar"), Value::Bytes((&b"bar"[..]).into())),
589        ]),
590        params! { "foo" => foo, bar, }
591    );
592}
593
594#[test]
595#[should_panic(expected = "Redefinition of named parameter `a'")]
596fn params_macro_should_panic_on_named_param_redefinition() {
597    params! {"a" => 1, "b" => 2, "a" => 3};
598}
599
600#[test]
601fn issue_88() {
602    use crate::{Value, prelude::FromValue};
603    #[derive(FromValue, Debug, Eq, PartialEq)]
604    #[mysql(is_integer)]
605    #[repr(u8)]
606    enum SomeType {
607        A,
608        B = 42,
609        C,
610    }
611
612    let value = Value::Int(42);
613    assert_eq!(SomeType::B, SomeType::from_value(value));
614
615    let value = Value::Int(0);
616    assert_eq!(SomeType::A, SomeType::from_value(value));
617}
618
619#[test]
620fn from_value_is_string() {
621    use crate::{Value, prelude::FromValue};
622    #[derive(FromValue, Debug, Eq, PartialEq)]
623    #[mysql(is_string, rename_all = "snake_case")]
624    enum SomeTypeIsString {
625        FirstVariant = 0,
626        SecondVariant = 2,
627        ThirdVariant = 3,
628    }
629
630    let value = Value::Bytes(b"first_variant".to_vec());
631    assert_eq!(
632        SomeTypeIsString::FirstVariant,
633        SomeTypeIsString::from_value(value)
634    );
635
636    let value = Value::Bytes(b"third_variant".to_vec());
637    assert_eq!(
638        SomeTypeIsString::ThirdVariant,
639        SomeTypeIsString::from_value(value)
640    );
641
642    assert_eq!(
643        Value::from(SomeTypeIsString::FirstVariant),
644        Value::Bytes(b"first_variant".to_vec())
645    );
646    assert_eq!(
647        Value::from(SomeTypeIsString::SecondVariant),
648        Value::Bytes(b"second_variant".to_vec())
649    );
650    assert_eq!(
651        Value::from(SomeTypeIsString::ThirdVariant),
652        Value::Bytes(b"third_variant".to_vec())
653    );
654}
655
656#[test]
657fn from_value_is_integer() {
658    use crate::{Value, prelude::FromValue};
659    #[derive(FromValue, Debug, Eq, PartialEq)]
660    #[mysql(is_integer, rename_all = "snake_case")]
661    #[repr(i8)]
662    enum SomeTypeIsInteger {
663        FirstVariant = -1_i8,
664        SecondVariant = 2,
665        ThirdVariant = 3,
666    }
667
668    let value = Value::Int(-1);
669    assert_eq!(
670        SomeTypeIsInteger::FirstVariant,
671        SomeTypeIsInteger::from_value(value)
672    );
673
674    let value = Value::Int(3);
675    assert_eq!(
676        SomeTypeIsInteger::ThirdVariant,
677        SomeTypeIsInteger::from_value(value)
678    );
679
680    assert_eq!(Value::from(SomeTypeIsInteger::FirstVariant), Value::Int(-1));
681    assert_eq!(Value::from(SomeTypeIsInteger::SecondVariant), Value::Int(2));
682    assert_eq!(Value::from(SomeTypeIsInteger::ThirdVariant), Value::Int(3));
683}
684
685#[cfg(test)]
686mod tests {
687    use crate::{
688        FromValueError,
689        constants::ColumnType,
690        packets::Column,
691        row::{convert::FromRow, new_row},
692        value::{Value, convert::from_value},
693    };
694    use unic_langid::LanguageIdentifier;
695
696    #[derive(FromValue)]
697    #[mysql(serialize_with = "from_langid", deserialize_with = "to_langid")]
698    struct LangId(LanguageIdentifier);
699
700    impl std::ops::Deref for LangId {
701        type Target = LanguageIdentifier;
702
703        fn deref(&self) -> &Self::Target {
704            &self.0
705        }
706    }
707
708    fn to_langid(v: Value) -> Result<LanguageIdentifier, FromValueError> {
709        match v {
710            Value::Bytes(ref b) => match LanguageIdentifier::from_bytes(b) {
711                Ok(ident) => Ok(ident),
712                Err(_) => Err(FromValueError(v)),
713            },
714            _ => Err(FromValueError(v)),
715        }
716    }
717
718    fn from_langid(land_id: LanguageIdentifier) -> Value {
719        Value::Bytes(land_id.to_string().into())
720    }
721
722    #[test]
723    fn newtype_with() {
724        let mut value = Value::Bytes(b"en-US".into());
725
726        let ident = from_value::<LangId>(value);
727
728        assert_eq!(ident.language.to_string().as_str(), "en");
729        assert_eq!(ident.to_string().as_str(), "en-US");
730
731        value = ident.into();
732
733        assert_eq!(value, Value::Bytes(b"en-US".into()));
734    }
735
736    #[test]
737    fn from_row_derive() {
738        #[derive(FromRow)]
739        #[mysql(table_name = "Foos", rename_all = "camelCase")]
740        struct Foo {
741            id: u64,
742            text_data: String,
743            #[mysql(json)]
744            json_data: serde_json::Value,
745            #[mysql(deserialize_with = "from_literal", rename = "custom")]
746            custom_bool: bool,
747        }
748
749        fn from_literal(value: crate::Value) -> Result<bool, crate::FromValueError> {
750            match value {
751                crate::Value::Bytes(x) if x == b"true" => Ok(true),
752                crate::Value::Bytes(x) if x == b"false" => Ok(false),
753                x => Err(crate::FromValueError(x)),
754            }
755        }
756
757        assert_eq!(Foo::TABLE_NAME, "Foos");
758        assert_eq!(Foo::ID_FIELD, "id");
759        assert_eq!(Foo::TEXT_DATA_FIELD, "textData");
760        assert_eq!(Foo::JSON_DATA_FIELD, "jsonData");
761        assert_eq!(Foo::CUSTOM_BOOL_FIELD, "custom");
762
763        let columns = vec![
764            Column::new(ColumnType::MYSQL_TYPE_LONGLONG)
765                .with_name(b"id")
766                .with_org_name(b"id")
767                .with_table(b"Foos")
768                .with_org_table(b"Foos"),
769            Column::new(ColumnType::MYSQL_TYPE_VARCHAR)
770                .with_name(b"textData")
771                .with_org_name(b"textData")
772                .with_table(b"Foos")
773                .with_org_table(b"Foos"),
774            Column::new(ColumnType::MYSQL_TYPE_JSON)
775                .with_name(b"jsonData")
776                .with_org_name(b"jsonData")
777                .with_table(b"Foos")
778                .with_org_table(b"Foos"),
779            Column::new(ColumnType::MYSQL_TYPE_VARCHAR)
780                .with_name(b"custom")
781                .with_org_name(b"custom")
782                .with_table(b"Foos")
783                .with_org_table(b"Foos"),
784        ];
785
786        let row = new_row(
787            vec![
788                crate::Value::Int(10),
789                crate::Value::Bytes(b"bytes".into()),
790                crate::Value::Bytes(b"[true,false,\"not found\"]".into()),
791                crate::Value::Bytes(b"true".into()),
792            ],
793            columns.into(),
794        );
795
796        let deserialized = Foo::from_row(row);
797
798        assert_eq!(deserialized.id, 10);
799        assert_eq!(deserialized.text_data, "bytes");
800        assert_eq!(
801            deserialized.json_data.to_string(),
802            "[true,false,\"not found\"]"
803        );
804        assert!(deserialized.custom_bool);
805    }
806}