From d7b122890c0b9a6c836142497d3c1a5556bf84ce Mon Sep 17 00:00:00 2001 From: Peter Maatman Date: Wed, 1 Apr 2020 11:33:31 +0200 Subject: [PATCH] mysql: Add JSON support --- Cargo.lock | 2 + sqlx-core/src/mysql/types/json.rs | 58 ++++++++++++++++++++++++++++ sqlx-core/src/mysql/types/mod.rs | 10 +++++ sqlx-core/src/postgres/types/json.rs | 4 +- tests/mysql-types.rs | 31 +++++++++++++++ 5 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 sqlx-core/src/mysql/types/json.rs diff --git a/Cargo.lock b/Cargo.lock index ebceab782b..d1fdda04f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -291,12 +291,14 @@ checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" name = "cargo-sqlx" version = "0.1.0" dependencies = [ + "anyhow", "chrono", "dotenv", "futures 0.3.4", "sqlx", "structopt", "tokio 0.2.13", + "url", ] [[package]] diff --git a/sqlx-core/src/mysql/types/json.rs b/sqlx-core/src/mysql/types/json.rs new file mode 100644 index 0000000000..d62c314834 --- /dev/null +++ b/sqlx-core/src/mysql/types/json.rs @@ -0,0 +1,58 @@ +use crate::decode::Decode; +use crate::encode::Encode; +use crate::mysql::database::MySql; +use crate::mysql::protocol::TypeId; +use crate::mysql::types::*; +use crate::mysql::{MySqlTypeInfo, MySqlValue}; +use crate::types::{Json, Type}; +use serde::{Deserialize, Serialize}; +use serde_json::Value as JsonValue; + +impl Type for JsonValue { + fn type_info() -> MySqlTypeInfo { + as Type>::type_info() + } +} + +impl Encode for JsonValue { + fn encode(&self, buf: &mut Vec) { + (Box::new(Json(self)) as Box>).encode(buf) + } +} + +impl<'de> Decode<'de, MySql> for JsonValue { + fn decode(value: MySqlValue<'de>) -> crate::Result { + as Decode>::decode(value).map(|item| item.0) + } +} + +impl Type for Json { + fn type_info() -> MySqlTypeInfo { + // MySql uses the CHAR type to pass JSON data from and to the client + MySqlTypeInfo::new(TypeId::CHAR) + } +} + +impl Encode for Json +where + T: Serialize, +{ + fn encode(&self, buf: &mut Vec) { + let json_string_value = + serde_json::to_string(&self.0).expect("serde_json failed to convert to string"); + >::encode(json_string_value.as_str(), buf); + } +} + +impl<'de, T> Decode<'de, MySql> for Json +where + T: 'de, + T: for<'de1> Deserialize<'de1>, +{ + fn decode(value: MySqlValue<'de>) -> crate::Result { + let string_value = <&'de str as Decode>::decode(value).unwrap(); + serde_json::from_str(&string_value) + .map(Json) + .map_err(crate::Error::decode) + } +} diff --git a/sqlx-core/src/mysql/types/mod.rs b/sqlx-core/src/mysql/types/mod.rs index 11d31ab7eb..6a37abc0e1 100644 --- a/sqlx-core/src/mysql/types/mod.rs +++ b/sqlx-core/src/mysql/types/mod.rs @@ -47,6 +47,13 @@ //! | Rust type | MySQL type(s) | //! |---------------------------------------|------------------------------------------------------| //! | `bigdecimal::BigDecimal` | DECIMAL | +//! ### [`json`](https://crates.io/crates/json) +//! +//! Requires the `json` Cargo feature flag. +//! +//! | Rust type | MySQL type(s) | +//! |---------------------------------------|------------------------------------------------------| +//! | `json::JsonValue` | JSON //! //! # Nullable //! @@ -70,6 +77,9 @@ mod chrono; #[cfg(feature = "time")] mod time; +#[cfg(feature = "json")] +mod json; + use crate::decode::Decode; use crate::mysql::{MySql, MySqlValue}; diff --git a/sqlx-core/src/postgres/types/json.rs b/sqlx-core/src/postgres/types/json.rs index 6b4b85ea3e..ef2b82b05c 100644 --- a/sqlx-core/src/postgres/types/json.rs +++ b/sqlx-core/src/postgres/types/json.rs @@ -23,7 +23,7 @@ impl Type for JsonValue { impl Encode for JsonValue { fn encode(&self, buf: &mut PgRawBuffer) { - Json(self).encode(buf) + (Box::new(Json(self)) as Box>).encode(buf) } } @@ -41,7 +41,7 @@ impl Type for &'_ JsonRawValue { impl Encode for &'_ JsonRawValue { fn encode(&self, buf: &mut PgRawBuffer) { - Json(self).encode(buf) + (Box::new(Json(self)) as Box>).encode(buf) } } diff --git a/tests/mysql-types.rs b/tests/mysql-types.rs index 45af828da4..e4b25e0e23 100644 --- a/tests/mysql-types.rs +++ b/tests/mysql-types.rs @@ -135,3 +135,34 @@ test_type!(decimal( "CAST(12.34 AS DECIMAL(4, 2))" == "12.34".parse::().unwrap(), "CAST(12345.6789 AS DECIMAL(9, 4))" == "12345.6789".parse::().unwrap(), )); + +#[cfg(feature = "json")] +mod json_tests { + use super::*; + use serde_json::{json, Value as JsonValue}; + use sqlx::types::Json; + use sqlx_test::test_type; + + test_type!(json( + MySql, + JsonValue, + "SELECT CAST({0} AS JSON) <=> CAST(? AS JSON), '' as _1, ? as _2, ? as _3", + "'\"Hello, World\"'" == json!("Hello, World"), + "'\"😎\"'" == json!("😎"), + "'\"🙋‍♀️\"'" == json!("🙋‍♀️"), + "'[\"Hello\", \"World!\"]'" == json!(["Hello", "World!"]) + )); + + #[derive(serde::Deserialize, serde::Serialize, Debug, PartialEq)] + struct Friend { + name: String, + age: u32, + } + + test_type!(json_struct( + MySql, + Json, + "SELECT CAST({0} AS JSON) <=> CAST(? AS JSON), '' as _1, ? as _2, ? as _3", + "\'{\"name\": \"Joe\", \"age\":33}\'" == Json(Friend { name: "Joe".to_string(), age: 33 }) + )); +}