From 61c8c3881f52f2875d65ee319e3ad7c95e34d11c Mon Sep 17 00:00:00 2001 From: Xue Haonan Date: Thu, 12 Dec 2024 00:51:33 +0800 Subject: [PATCH] add SQLite functions `json` and `jsonb` --- diesel/src/expression/mod.rs | 3 + .../sqlite/expression/expression_methods.rs | 26 ++++ diesel/src/sqlite/expression/functions.rs | 114 ++++++++++++++++++ diesel/src/sqlite/expression/helper_types.rs | 12 +- diesel/src/sqlite/expression/mod.rs | 11 ++ diesel/src/sqlite/mod.rs | 2 +- diesel_derives/tests/auto_type.rs | 22 ++++ 7 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 diesel/src/sqlite/expression/functions.rs diff --git a/diesel/src/expression/mod.rs b/diesel/src/expression/mod.rs index 9d0d127eb4b7..4c4a2c126729 100644 --- a/diesel/src/expression/mod.rs +++ b/diesel/src/expression/mod.rs @@ -74,6 +74,9 @@ pub(crate) mod dsl { #[cfg(feature = "postgres_backend")] pub use crate::pg::expression::dsl::*; + #[cfg(feature = "sqlite")] + pub use crate::sqlite::expression::dsl::*; + /// The return type of [`count(expr)`](crate::dsl::count()) pub type count = super::count::count, Expr>; diff --git a/diesel/src/sqlite/expression/expression_methods.rs b/diesel/src/sqlite/expression/expression_methods.rs index 1708c4975b43..f4f80c724a07 100644 --- a/diesel/src/sqlite/expression/expression_methods.rs +++ b/diesel/src/sqlite/expression/expression_methods.rs @@ -1,5 +1,8 @@ //! Sqlite specific expression methods. +pub(in crate::sqlite) use self::private::{ + JsonOrNullableJsonOrJsonbOrNullableJsonb, MaybeNullableValue, +}; use super::operators::*; use crate::dsl; use crate::expression::grouped::Grouped; @@ -82,3 +85,26 @@ pub trait SqliteExpressionMethods: Expression + Sized { } impl SqliteExpressionMethods for T {} + +pub(in crate::sqlite) mod private { + use crate::sql_types::{Json, Jsonb, MaybeNullableType, Nullable, SingleValue}; + + pub trait JsonOrNullableJsonOrJsonbOrNullableJsonb {} + impl JsonOrNullableJsonOrJsonbOrNullableJsonb for Json {} + impl JsonOrNullableJsonOrJsonbOrNullableJsonb for Nullable {} + impl JsonOrNullableJsonOrJsonbOrNullableJsonb for Jsonb {} + impl JsonOrNullableJsonOrJsonbOrNullableJsonb for Nullable {} + + pub trait MaybeNullableValue: SingleValue { + type Out: SingleValue; + } + + impl MaybeNullableValue for T + where + T: SingleValue, + T::IsNull: MaybeNullableType, + >::Out: SingleValue, + { + type Out = >::Out; + } +} diff --git a/diesel/src/sqlite/expression/functions.rs b/diesel/src/sqlite/expression/functions.rs new file mode 100644 index 000000000000..790cd4199072 --- /dev/null +++ b/diesel/src/sqlite/expression/functions.rs @@ -0,0 +1,114 @@ +//! SQLite specific functions +use crate::expression::functions::define_sql_function; +use crate::sql_types::*; +use crate::sqlite::expression::expression_methods::JsonOrNullableJsonOrJsonbOrNullableJsonb; +use crate::sqlite::expression::expression_methods::MaybeNullableValue; + +#[cfg(feature = "sqlite")] +define_sql_function! { + /// Verifies that its argument is a valid JSON string or JSONB blob and returns a minified + /// version of that JSON string with all unnecessary whitespace removed. + /// + /// # Example + /// + /// ```rust + /// # include!("../../doctest_setup.rs"); + /// # + /// # fn main() { + /// # #[cfg(feature = "serde_json")] + /// # run_test().unwrap(); + /// # } + /// # + /// # #[cfg(feature = "serde_json")] + /// # fn run_test() -> QueryResult<()> { + /// # use diesel::dsl::json; + /// # use serde_json::{json, Value}; + /// # use diesel::sql_types::{Text, Json, Jsonb, Nullable}; + /// # let connection = &mut establish_connection(); + /// + /// let result = diesel::select(json::(json!({"a": "b", "c": 1}))) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"{"a":"b","c":1}"#, result); + /// + /// let result = diesel::select(json::(json!({ "this" : "is", "a": [ "test" ] }))) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"{"a":["test"],"this":"is"}"#, result); + /// + /// let result = diesel::select(json::, _>(None::)) + /// .get_result::>(connection)?; + /// + /// assert!(result.is_none()); + /// + /// let result = diesel::select(json::(json!({"a": "b", "c": 1}))) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"{"a":"b","c":1}"#, result); + /// + /// let result = diesel::select(json::(json!({ "this" : "is", "a": [ "test" ] }))) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"{"a":["test"],"this":"is"}"#, result); + /// + /// let result = diesel::select(json::, _>(None::)) + /// .get_result::>(connection)?; + /// + /// assert!(result.is_none()); + /// + /// + /// # Ok(()) + /// # } + /// ``` + fn json>(e: E) -> E::Out; +} + +#[cfg(feature = "sqlite")] +define_sql_function! { + /// + /// # Example + /// + /// ```rust + /// # include!("../../doctest_setup.rs"); + /// # + /// # fn main() { + /// # #[cfg(feature = "serde_json")] + /// # run_test().unwrap(); + /// # } + /// # + /// # #[cfg(feature = "serde_json")] + /// # fn run_test() -> QueryResult<()> { + /// # use diesel::dsl::jsonb; + /// # use serde_json::{json, Value}; + /// # use diesel::sql_types::{Text, Json, Jsonb, Nullable}; + /// # let connection = &mut establish_connection(); + /// + /// let result = diesel::select(jsonb::(json!({"a": "b", "c": 1}))) + /// .get_result::(connection)?; + /// + /// assert_eq!(json!({"a": "b", "c": 1}), result); + /// println!("json abc1"); + /// + /// let result = diesel::select(jsonb::(json!({"a": "b", "c": 1}))) + /// .get_result::(connection)?; + /// + /// assert_eq!(json!({"a": "b", "c": 1}), result); + /// println!("jsonb abc1"); + /// + /// let result = diesel::select(jsonb::, _>(None::)) + /// .get_result::>(connection)?; + /// + /// assert!(result.is_none()); + /// println!("json null"); + /// + /// let result = diesel::select(jsonb::, _>(None::)) + /// .get_result::>(connection)?; + /// + /// assert!(result.is_none()); + /// println!("jsonb null"); + /// + /// # Ok(()) + /// # } + /// ``` + fn jsonb>(e: E) -> E::Out; +} diff --git a/diesel/src/sqlite/expression/helper_types.rs b/diesel/src/sqlite/expression/helper_types.rs index 77f54ebe0d6a..807aea965c68 100644 --- a/diesel/src/sqlite/expression/helper_types.rs +++ b/diesel/src/sqlite/expression/helper_types.rs @@ -1,4 +1,4 @@ -use crate::dsl::AsExpr; +use crate::dsl::{AsExpr, SqlTypeOf}; use crate::expression::grouped::Grouped; /// The return type of `lhs.is(rhs)`. @@ -6,3 +6,13 @@ pub type Is = Grouped>>; /// The return type of `lhs.is_not(rhs)`. pub type IsNot = Grouped>>; + +/// Return type of [`json(json)`](super::functions::json()) +#[allow(non_camel_case_types)] +#[cfg(feature = "sqlite")] +pub type json = super::functions::json, E>; + +/// Return type of [`jsonb(json)`](super::functions::jsonb()) +#[allow(non_camel_case_types)] +#[cfg(feature = "sqlite")] +pub type jsonb = super::functions::jsonb, E>; diff --git a/diesel/src/sqlite/expression/mod.rs b/diesel/src/sqlite/expression/mod.rs index 00a224ca9f6a..3ac455134170 100644 --- a/diesel/src/sqlite/expression/mod.rs +++ b/diesel/src/sqlite/expression/mod.rs @@ -5,5 +5,16 @@ //! kept separate purely for documentation purposes. pub(crate) mod expression_methods; +pub mod functions; pub(crate) mod helper_types; mod operators; + +/// SQLite specific expression DSL methods. +/// +/// This module will be glob imported by +/// [`diesel::dsl`](crate::dsl) when compiled with the `feature = +/// "postgres"` flag. +pub mod dsl { + #[doc(inline)] + pub use super::functions::*; +} \ No newline at end of file diff --git a/diesel/src/sqlite/mod.rs b/diesel/src/sqlite/mod.rs index 45cabe6f86c4..ee625acb0cb1 100644 --- a/diesel/src/sqlite/mod.rs +++ b/diesel/src/sqlite/mod.rs @@ -6,7 +6,7 @@ pub(crate) mod backend; mod connection; -pub(crate) mod expression; +pub mod expression; pub mod query_builder; diff --git a/diesel_derives/tests/auto_type.rs b/diesel_derives/tests/auto_type.rs index 2cc13b35c277..16eed102f5be 100644 --- a/diesel_derives/tests/auto_type.rs +++ b/diesel_derives/tests/auto_type.rs @@ -57,6 +57,16 @@ table! { } } +#[cfg(feature = "sqlite")] +table! { + sqlite_extras { + id -> Integer, + text -> Text, + json -> Json, + jsonb -> Jsonb, + } +} + joinable!(posts -> users(user_id)); joinable!(posts2 -> users(user_id)); joinable!(posts3 -> users(user_id)); @@ -465,6 +475,18 @@ fn postgres_functions() -> _ { ) } +#[cfg(feature = "sqlite")] +#[auto_type] +fn sqlite_functions() -> _ { + use diesel::sqlite::expression::functions::{json, jsonb}; + ( + json(sqlite_extras::json), + json(sqlite_extras::jsonb), + jsonb(sqlite_extras::json), + jsonb(sqlite_extras::jsonb), + ) +} + #[auto_type] fn with_lifetime<'a>(name: &'a str) -> _ { users::table.filter(users::name.eq(name))