Skip to main content

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