Skip to content

Commit d7b1228

Browse files
mysql: Add JSON support
1 parent c6e9b27 commit d7b1228

File tree

5 files changed

+103
-2
lines changed

5 files changed

+103
-2
lines changed

Cargo.lock

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sqlx-core/src/mysql/types/json.rs

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use crate::decode::Decode;
2+
use crate::encode::Encode;
3+
use crate::mysql::database::MySql;
4+
use crate::mysql::protocol::TypeId;
5+
use crate::mysql::types::*;
6+
use crate::mysql::{MySqlTypeInfo, MySqlValue};
7+
use crate::types::{Json, Type};
8+
use serde::{Deserialize, Serialize};
9+
use serde_json::Value as JsonValue;
10+
11+
impl Type<MySql> for JsonValue {
12+
fn type_info() -> MySqlTypeInfo {
13+
<Json<Self> as Type<MySql>>::type_info()
14+
}
15+
}
16+
17+
impl Encode<MySql> for JsonValue {
18+
fn encode(&self, buf: &mut Vec<u8>) {
19+
(Box::new(Json(self)) as Box<dyn Encode<MySql>>).encode(buf)
20+
}
21+
}
22+
23+
impl<'de> Decode<'de, MySql> for JsonValue {
24+
fn decode(value: MySqlValue<'de>) -> crate::Result<Self> {
25+
<Json<Self> as Decode<MySql>>::decode(value).map(|item| item.0)
26+
}
27+
}
28+
29+
impl<T> Type<MySql> for Json<T> {
30+
fn type_info() -> MySqlTypeInfo {
31+
// MySql uses the CHAR type to pass JSON data from and to the client
32+
MySqlTypeInfo::new(TypeId::CHAR)
33+
}
34+
}
35+
36+
impl<T> Encode<MySql> for Json<T>
37+
where
38+
T: Serialize,
39+
{
40+
fn encode(&self, buf: &mut Vec<u8>) {
41+
let json_string_value =
42+
serde_json::to_string(&self.0).expect("serde_json failed to convert to string");
43+
<str as Encode<MySql>>::encode(json_string_value.as_str(), buf);
44+
}
45+
}
46+
47+
impl<'de, T> Decode<'de, MySql> for Json<T>
48+
where
49+
T: 'de,
50+
T: for<'de1> Deserialize<'de1>,
51+
{
52+
fn decode(value: MySqlValue<'de>) -> crate::Result<Self> {
53+
let string_value = <&'de str as Decode<MySql>>::decode(value).unwrap();
54+
serde_json::from_str(&string_value)
55+
.map(Json)
56+
.map_err(crate::Error::decode)
57+
}
58+
}

sqlx-core/src/mysql/types/mod.rs

+10
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@
4747
//! | Rust type | MySQL type(s) |
4848
//! |---------------------------------------|------------------------------------------------------|
4949
//! | `bigdecimal::BigDecimal` | DECIMAL |
50+
//! ### [`json`](https://crates.io/crates/json)
51+
//!
52+
//! Requires the `json` Cargo feature flag.
53+
//!
54+
//! | Rust type | MySQL type(s) |
55+
//! |---------------------------------------|------------------------------------------------------|
56+
//! | `json::JsonValue` | JSON
5057
//!
5158
//! # Nullable
5259
//!
@@ -70,6 +77,9 @@ mod chrono;
7077
#[cfg(feature = "time")]
7178
mod time;
7279

80+
#[cfg(feature = "json")]
81+
mod json;
82+
7383
use crate::decode::Decode;
7484
use crate::mysql::{MySql, MySqlValue};
7585

sqlx-core/src/postgres/types/json.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ impl Type<Postgres> for JsonValue {
2323

2424
impl Encode<Postgres> for JsonValue {
2525
fn encode(&self, buf: &mut PgRawBuffer) {
26-
Json(self).encode(buf)
26+
(Box::new(Json(self)) as Box<dyn Encode<Postgres>>).encode(buf)
2727
}
2828
}
2929

@@ -41,7 +41,7 @@ impl Type<Postgres> for &'_ JsonRawValue {
4141

4242
impl Encode<Postgres> for &'_ JsonRawValue {
4343
fn encode(&self, buf: &mut PgRawBuffer) {
44-
Json(self).encode(buf)
44+
(Box::new(Json(self)) as Box<dyn Encode<Postgres>>).encode(buf)
4545
}
4646
}
4747

tests/mysql-types.rs

+31
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,34 @@ test_type!(decimal(
135135
"CAST(12.34 AS DECIMAL(4, 2))" == "12.34".parse::<sqlx::types::BigDecimal>().unwrap(),
136136
"CAST(12345.6789 AS DECIMAL(9, 4))" == "12345.6789".parse::<sqlx::types::BigDecimal>().unwrap(),
137137
));
138+
139+
#[cfg(feature = "json")]
140+
mod json_tests {
141+
use super::*;
142+
use serde_json::{json, Value as JsonValue};
143+
use sqlx::types::Json;
144+
use sqlx_test::test_type;
145+
146+
test_type!(json(
147+
MySql,
148+
JsonValue,
149+
"SELECT CAST({0} AS JSON) <=> CAST(? AS JSON), '<UNKNOWN>' as _1, ? as _2, ? as _3",
150+
"'\"Hello, World\"'" == json!("Hello, World"),
151+
"'\"😎\"'" == json!("😎"),
152+
"'\"🙋‍♀️\"'" == json!("🙋‍♀️"),
153+
"'[\"Hello\", \"World!\"]'" == json!(["Hello", "World!"])
154+
));
155+
156+
#[derive(serde::Deserialize, serde::Serialize, Debug, PartialEq)]
157+
struct Friend {
158+
name: String,
159+
age: u32,
160+
}
161+
162+
test_type!(json_struct(
163+
MySql,
164+
Json<Friend>,
165+
"SELECT CAST({0} AS JSON) <=> CAST(? AS JSON), '<UNKNOWN>' as _1, ? as _2, ? as _3",
166+
"\'{\"name\": \"Joe\", \"age\":33}\'" == Json(Friend { name: "Joe".to_string(), age: 33 })
167+
));
168+
}

0 commit comments

Comments
 (0)