Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SQLite support for serde_json::Value using the Json/Jsonb #4284

Merged
merged 24 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 0 additions & 86 deletions diesel/src/pg/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,92 +310,6 @@ pub mod sql_types {
#[doc(hidden)]
pub type Bpchar = crate::sql_types::VarChar;

/// The [`jsonb`] SQL type. This type can only be used with `feature =
/// "serde_json"`
///
/// `jsonb` offers [several advantages][adv] over regular JSON:
///
/// > There are two JSON data types: `json` and `jsonb`. They accept almost
/// > identical sets of values as input. The major practical difference
/// > is one of efficiency. The `json` data type stores an exact copy of
/// > the input text, which processing functions must reparse on each
/// > execution; while `jsonb` data is stored in a decomposed binary format
/// > that makes it slightly slower to input due to added conversion
/// > overhead, but significantly faster to process, since no reparsing
/// > is needed. `jsonb` also supports indexing, which can be a significant
/// > advantage.
/// >
/// > ...In general, most applications should prefer to store JSON data as
/// > `jsonb`, unless there are quite specialized needs, such as legacy
/// > assumptions about ordering of object keys.
///
/// [adv]: https://www.postgresql.org/docs/current/static/datatype-json.html
///
/// ### [`ToSql`] impls
///
/// - [`serde_json::Value`]
///
/// ### [`FromSql`] impls
///
/// - [`serde_json::Value`]
///
/// [`ToSql`]: crate::serialize::ToSql
/// [`FromSql`]: crate::deserialize::FromSql
/// [`jsonb`]: https://www.postgresql.org/docs/current/datatype-json.html
#[cfg_attr(
feature = "serde_json",
doc = "[`serde_json::Value`]: serde_json::value::Value"
)]
#[cfg_attr(
not(feature = "serde_json"),
doc = "[`serde_json::Value`]: https://docs.rs/serde_json/1.0.64/serde_json/value/enum.Value.html"
)]
///
/// # Examples
///
/// ```rust
/// # #![allow(dead_code)]
/// # include!("../../doctest_setup.rs");
/// #
/// table! {
/// contacts {
/// id -> Integer,
/// name -> VarChar,
/// address -> Jsonb,
/// }
/// }
///
/// # #[cfg(feature = "serde_json")]
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// # use diesel::insert_into;
/// # use self::contacts::dsl::*;
/// # let connection = &mut connection_no_data();
/// # diesel::sql_query("CREATE TABLE contacts (
/// # id SERIAL PRIMARY KEY,
/// # name VARCHAR NOT NULL,
/// # address JSONB NOT NULL
/// # )").execute(connection)?;
/// let santas_address: serde_json::Value = serde_json::from_str(r#"{
/// "street": "Article Circle Expressway 1",
/// "city": "North Pole",
/// "postcode": "99705",
/// "state": "Alaska"
/// }"#)?;
/// let inserted_address = insert_into(contacts)
/// .values((name.eq("Claus"), address.eq(&santas_address)))
/// .returning(address)
/// .get_result::<serde_json::Value>(connection)?;
/// assert_eq!(santas_address, inserted_address);
/// # Ok(())
/// # }
/// # #[cfg(not(feature = "serde_json"))]
/// # fn main() {}
/// ```
#[cfg(feature = "postgres_backend")]
#[derive(Debug, Clone, Copy, Default, QueryId, SqlType)]
#[diesel(postgres_type(oid = 3802, array_oid = 3807))]
pub struct Jsonb;

/// The PostgreSQL [Money](https://www.postgresql.org/docs/current/static/datatype-money.html) type.
///
/// ### [`ToSql`] impls
Expand Down
99 changes: 99 additions & 0 deletions diesel/src/sql_types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,11 +395,110 @@ pub struct Timestamp;
/// [`ToSql`]: /serialize/trait.ToSql.html
/// [`FromSql`]: /deserialize/trait.FromSql.html
/// [`serde_json::Value`]: /../serde_json/value/enum.Value.html
#[cfg(all(
any(
feature = "postgres_backend",
feature = "sqlite",
feature = "mysql_backend"
),
feature = "serde_json"
))]
#[derive(Debug, Clone, Copy, Default, QueryId, SqlType)]
#[diesel(postgres_type(oid = 114, array_oid = 199))]
#[diesel(mysql_type(name = "String"))]
#[diesel(sqlite_type(name = "Text"))]
pub struct Json;

/// The [`jsonb`] SQL type. This type can only be used with `feature =
/// "serde_json"`
///
/// `jsonb` offers [several advantages][adv] over regular JSON:
///
/// > There are two JSON data types: `json` and `jsonb`. They accept almost
/// > identical sets of values as input. The major practical difference
/// > is one of efficiency. The `json` data type stores an exact copy of
/// > the input text, which processing functions must reparse on each
/// > execution; while `jsonb` data is stored in a decomposed binary format
/// > that makes it slightly slower to input due to added conversion
/// > overhead, but significantly faster to process, since no reparsing
/// > is needed. `jsonb` also supports indexing, which can be a significant
/// > advantage.
/// >
/// > ...In general, most applications should prefer to store JSON data as
/// > `jsonb`, unless there are quite specialized needs, such as legacy
/// > assumptions about ordering of object keys.
///
/// [adv]: https://www.postgresql.org/docs/current/static/datatype-json.html
///
/// ### [`ToSql`] impls
///
/// - [`serde_json::Value`]
///
/// ### [`FromSql`] impls
///
/// - [`serde_json::Value`]
///
/// [`ToSql`]: crate::serialize::ToSql
/// [`FromSql`]: crate::deserialize::FromSql
/// [`jsonb`]: https://www.postgresql.org/docs/current/datatype-json.html
JMLX42 marked this conversation as resolved.
Show resolved Hide resolved
#[cfg_attr(
feature = "serde_json",
doc = "[`serde_json::Value`]: serde_json::value::Value"
)]
#[cfg_attr(
not(feature = "serde_json"),
doc = "[`serde_json::Value`]: https://docs.rs/serde_json/1.0.64/serde_json/value/enum.Value.html"
)]
///
/// # Examples
///
/// ```rust
weiznich marked this conversation as resolved.
Show resolved Hide resolved
/// # #![allow(dead_code)]
/// # include!("../../doctest_setup.rs");
/// #
/// table! {
/// contacts {
/// id -> Integer,
/// name -> VarChar,
/// address -> Jsonb,
/// }
/// }
///
/// # #[cfg(feature = "serde_json")]
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// # use diesel::insert_into;
/// # use self::contacts::dsl::*;
/// # let connection = &mut connection_no_data();
/// # diesel::sql_query("CREATE TABLE contacts (
/// # id SERIAL PRIMARY KEY,
/// # name VARCHAR NOT NULL,
/// # address JSONB NOT NULL
/// # )").execute(connection)?;
/// let santas_address: serde_json::Value = serde_json::from_str(r#"{
/// "street": "Article Circle Expressway 1",
/// "city": "North Pole",
/// "postcode": "99705",
/// "state": "Alaska"
/// }"#)?;
/// let inserted_address = insert_into(contacts)
/// .values((name.eq("Claus"), address.eq(&santas_address)))
/// .returning(address)
/// .get_result::<serde_json::Value>(connection)?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that fails on CI because support for get_result() for insert statements for sqlite is behind a feature flag.

/// assert_eq!(santas_address, inserted_address);
/// # Ok(())
/// # }
/// # #[cfg(not(feature = "serde_json"))]
/// # fn main() {}
/// ```
#[cfg(all(
any(feature = "postgres_backend", feature = "sqlite"),
feature = "serde_json"
))]
#[derive(Debug, Clone, Copy, Default, QueryId, SqlType)]
#[diesel(postgres_type(oid = 3802, array_oid = 3807))]
#[diesel(sqlite_type(name = "Binary"))]
pub struct Jsonb;

/// The nullable SQL type.
///
/// This wraps another SQL type to indicate that it can be null.
Expand Down
164 changes: 164 additions & 0 deletions diesel/src/sqlite/types/json.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
extern crate serde_json;

use crate::deserialize::{self, FromSql};
use crate::serialize::{self, IsNull, Output, ToSql};
use crate::sql_types;
use crate::sqlite::{Sqlite, SqliteValue};

#[cfg(all(feature = "sqlite", feature = "serde_json"))]
impl FromSql<sql_types::Json, Sqlite> for serde_json::Value {
fn from_sql(value: SqliteValue<'_, '_, '_>) -> deserialize::Result<Self> {
serde_json::from_str(value.read_text()).map_err(|_| "Invalid Json".into())
}
}

#[cfg(all(feature = "sqlite", feature = "serde_json"))]
impl ToSql<sql_types::Json, Sqlite> for serde_json::Value {
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
out.set_value(serde_json::to_string(self)?);
Ok(IsNull::No)
}
}

#[cfg(all(feature = "sqlite", feature = "serde_json"))]
impl FromSql<sql_types::Jsonb, Sqlite> for serde_json::Value {
fn from_sql(value: SqliteValue<'_, '_, '_>) -> deserialize::Result<Self> {
let bytes = value.read_blob();
if bytes[0] != 1 {
return Err("Unsupported JSONB encoding version".into());
}
serde_json::from_slice(&bytes[1..]).map_err(|_| "Invalid Json".into())
JMLX42 marked this conversation as resolved.
Show resolved Hide resolved
}
}

#[cfg(all(feature = "sqlite", feature = "serde_json"))]
impl ToSql<sql_types::Jsonb, Sqlite> for serde_json::Value {
fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> serialize::Result {
out.set_value(serde_json::to_string(self)?.into_bytes());
Ok(IsNull::No)
}
}

#[cfg(test)]
mod tests {
use crate::deserialize::FromSql;
use crate::dsl::sql;
use crate::prelude::*;
use crate::select;
use crate::serialize::{Output, ToSql};
use crate::sql_types;
use crate::sql_types::{Json, Text};
use crate::sqlite::connection::SqliteBindCollector;
use crate::sqlite::Sqlite;
use crate::sqlite::SqliteBindValue;
use crate::sqlite::SqliteValue;
use crate::test_helpers::connection;
use serde_json::json;

// #[test]
// fn json_to_sql() {
// let buffer = SqliteBindValue::from(0i32);
// let mut out = Output::<'_, 'static, Sqlite>::test(buffer);
// let test_json = serde_json::Value::Bool(true);
// ToSql::<sql_types::Json, Sqlite>::to_sql(&test_json, &mut out).unwrap();
// assert_eq!(buffer.inner, SqliteBindValue::from(1i32).inner);
// }

// #[test]
// fn json_to_sql() {
// crate::table! {
// #[allow(unused_parens)]
// test_insert_json_into_table_as_text(id) {
// id -> Integer,
// json -> Text,
// }
// }
// let conn = &mut connection();
// crate::sql_query(
// "CREATE TABLE test_insert_json_into_table_as_text(id INTEGER PRIMARY KEY, json TEXT);",
// )
// .execute(conn)
// .unwrap();

// let value = json!(true);

// crate::insert_into(test_insert_json_into_table_as_text::table)
// .values((
// test_insert_json_into_table_as_text::id.eq(1),
// test_insert_json_into_table_as_text::json.eq(value),
// ))
// .execute(conn)
// .unwrap();
// }

#[test]
fn some_json_from_sql() {
JMLX42 marked this conversation as resolved.
Show resolved Hide resolved
let input_json = b"true";
let output_json: serde_json::Value =
FromSql::<sql_types::Json, Sqlite>::from_sql(SqliteValue::for_test(input_json))
.unwrap();
assert_eq!(output_json, serde_json::Value::Bool(true));
}

// #[test]
// fn bad_json_from_sql() {
// let uuid: Result<serde_json::Value, _> =
// FromSql::<sql_types::Json, Sqlite>::from_sql(SqliteValue::for_test(b"boom"));
// assert_eq!(uuid.unwrap_err().to_string(), "Invalid Json");
// }

// #[test]
// fn no_json_from_sql() {
// let uuid: Result<serde_json::Value, _> =
// FromSql::<sql_types::Json, Sqlite>::from_nullable_sql(None);
// assert_eq!(
// uuid.unwrap_err().to_string(),
// "Unexpected null for non-null column"
// );
// }

// #[test]
// fn jsonb_to_sql() {
// let mut buffer = Vec::new();
// let mut bytes = Output::test(ByteWrapper(&mut buffer));
// let test_json = serde_json::Value::Bool(true);
// ToSql::<sql_types::Jsonb, Sqlite>::to_sql(&test_json, &mut bytes).unwrap();
// assert_eq!(buffer, b"\x01true");
// }

// #[test]
// fn some_jsonb_from_sql() {
// let input_json = b"\x01true";
// let output_json: serde_json::Value =
// FromSql::<sql_types::Jsonb, Sqlite>::from_sql(SqliteValue::for_test(input_json))
// .unwrap();
// assert_eq!(output_json, serde_json::Value::Bool(true));
// }

// #[test]
// fn bad_jsonb_from_sql() {
// let uuid: Result<serde_json::Value, _> =
// FromSql::<sql_types::Jsonb, Sqlite>::from_sql(SqliteValue::for_test(b"\x01boom"));
// assert_eq!(uuid.unwrap_err().to_string(), "Invalid Json");
// }

// #[test]
// fn bad_jsonb_version_from_sql() {
// let uuid: Result<serde_json::Value, _> =
// FromSql::<sql_types::Jsonb, Sqlite>::from_sql(SqliteValue::for_test(b"\x02true"));
// assert_eq!(
// uuid.unwrap_err().to_string(),
// "Unsupported JSONB encoding version"
// );
// }

// #[test]
// fn no_jsonb_from_sql() {
// let uuid: Result<serde_json::Value, _> =
// FromSql::<sql_types::Jsonb, Sqlite>::from_nullable_sql(None);
// assert_eq!(
// uuid.unwrap_err().to_string(),
// "Unexpected null for non-null column"
// );
// }
}
1 change: 1 addition & 0 deletions diesel/src/sqlite/types/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod date_and_time;
mod json;
mod numeric;

use super::connection::SqliteValue;
Expand Down
Loading