diff --git a/diesel/src/sqlite/expression/expression_methods.rs b/diesel/src/sqlite/expression/expression_methods.rs index 8043cb45320e..0efc891cc522 100644 --- a/diesel/src/sqlite/expression/expression_methods.rs +++ b/diesel/src/sqlite/expression/expression_methods.rs @@ -1,7 +1,8 @@ //! Sqlite specific expression methods. pub(in crate::sqlite) use self::private::{ - BinaryOrNullableBinary, MaybeNullableValue, TextOrNullableText, + BinaryOrNullableBinary, JsonOrNullableJsonOrJsonbOrNullableJsonb, MaybeNullableValue, + TextOrNullableText, }; use super::operators::*; use crate::dsl; @@ -87,7 +88,7 @@ pub trait SqliteExpressionMethods: Expression + Sized { impl SqliteExpressionMethods for T {} pub(in crate::sqlite) mod private { - use crate::sql_types::{Binary, MaybeNullableType, Nullable, SingleValue, Text}; + use crate::sql_types::{Binary, Json, Jsonb, MaybeNullableType, Nullable, SingleValue, Text}; #[diagnostic::on_unimplemented( message = "`{Self}` is neither `diesel::sql_types::Text` nor `diesel::sql_types::Nullable`", @@ -107,6 +108,16 @@ pub(in crate::sqlite) mod private { impl BinaryOrNullableBinary for Binary {} impl BinaryOrNullableBinary for Nullable {} + #[diagnostic::on_unimplemented( + message = "`{Self}` is neither `diesel::sql_types::Json`, `diesel::sql_types::Jsonb`, `diesel::sql_types::Nullable` nor `diesel::sql_types::Nullable`", + note = "try to provide an expression that produces one of the expected sql types" + )] + 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; } diff --git a/diesel/src/sqlite/expression/functions.rs b/diesel/src/sqlite/expression/functions.rs index 7d37531d5c31..cc609d15fe88 100644 --- a/diesel/src/sqlite/expression/functions.rs +++ b/diesel/src/sqlite/expression/functions.rs @@ -2,6 +2,7 @@ use crate::expression::functions::define_sql_function; use crate::sql_types::*; use crate::sqlite::expression::expression_methods::BinaryOrNullableBinary; +use crate::sqlite::expression::expression_methods::JsonOrNullableJsonOrJsonbOrNullableJsonb; use crate::sqlite::expression::expression_methods::MaybeNullableValue; use crate::sqlite::expression::expression_methods::TextOrNullableText; @@ -105,3 +106,319 @@ define_sql_function! { /// ``` fn jsonb>(e: E) -> E::Out; } + +#[cfg(feature = "sqlite")] +define_sql_function! { + /// Converts the given json value to pretty-printed, indented text + /// + /// # 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::{sql, json_pretty}; + /// # use serde_json::{json, Value}; + /// # use diesel::sql_types::{Text, Json, Jsonb, Nullable}; + /// # let connection = &mut establish_connection(); + /// + /// let version = diesel::select(sql::("sqlite_version();")) + /// .get_result::(connection)?; + /// + /// // Querying SQLite version should not fail. + /// let version_components: Vec<&str> = version.split('.').collect(); + /// let major: u32 = version_components[0].parse().unwrap(); + /// let minor: u32 = version_components[1].parse().unwrap(); + /// let patch: u32 = version_components[2].parse().unwrap(); + /// + /// if major > 3 || (major == 3 && minor >= 46) { + /// /* Valid sqlite version, do nothing */ + /// } else { + /// println!("SQLite version is too old, skipping the test."); + /// return Ok(()); + /// } + /// + /// let result = diesel::select(json_pretty::(json!([{"f1":1,"f2":null},2,null,3]))) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"[ + /// { + /// "f1": 1, + /// "f2": null + /// }, + /// 2, + /// null, + /// 3 + /// ]"#, result); + /// + /// let result = diesel::select(json_pretty::(json!({"a": 1, "b": "cd"}))) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"{ + /// "a": 1, + /// "b": "cd" + /// }"#, result); + /// + /// let result = diesel::select(json_pretty::(json!("abc"))) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#""abc""#, result); + /// + /// let result = diesel::select(json_pretty::(json!(22))) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"22"#, result); + /// + /// let result = diesel::select(json_pretty::(json!(false))) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"false"#, result); + /// + /// let result = diesel::select(json_pretty::(json!(null))) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"null"#, result); + /// + /// let result = diesel::select(json_pretty::(json!({}))) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"{}"#, result); + /// + /// let result = diesel::select(json_pretty::, _>(None::)) + /// .get_result::>(connection)?; + /// + /// assert!(result.is_none()); + /// + /// let result = diesel::select(json_pretty::(json!([{"f1":1,"f2":null},2,null,3]))) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"[ + /// { + /// "f1": 1, + /// "f2": null + /// }, + /// 2, + /// null, + /// 3 + /// ]"#, result); + /// + /// let result = diesel::select(json_pretty::(json!({"a": 1, "b": "cd"}))) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"{ + /// "a": 1, + /// "b": "cd" + /// }"#, result); + /// + /// let result = diesel::select(json_pretty::(json!("abc"))) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#""abc""#, result); + /// + /// let result = diesel::select(json_pretty::(json!(22))) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"22"#, result); + /// + /// let result = diesel::select(json_pretty::(json!(false))) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"false"#, result); + /// + /// let result = diesel::select(json_pretty::(json!(null))) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"null"#, result); + /// + /// let result = diesel::select(json_pretty::(json!({}))) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"{}"#, result); + /// + /// let result = diesel::select(json_pretty::, _>(None::)) + /// .get_result::>(connection)?; + /// + /// assert!(result.is_none()); + /// # Ok(()) + /// # } + /// ``` + fn json_pretty>(j: J) -> J::Out; +} + +#[cfg(feature = "sqlite")] +define_sql_function! { + /// Converts the given json value to pretty-printed, indented text + /// + /// # 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::{sql, json_pretty_with_indentation}; + /// # use serde_json::{json, Value}; + /// # use diesel::sql_types::{Text, Json, Jsonb, Nullable}; + /// # let connection = &mut establish_connection(); + /// + /// let version = diesel::select(sql::("sqlite_version();")) + /// .get_result::(connection)?; + /// + /// // Querying SQLite version should not fail. + /// let version_components: Vec<&str> = version.split('.').collect(); + /// let major: u32 = version_components[0].parse().unwrap(); + /// let minor: u32 = version_components[1].parse().unwrap(); + /// let patch: u32 = version_components[2].parse().unwrap(); + /// + /// if major > 3 || (major == 3 && minor >= 46) { + /// /* Valid sqlite version, do nothing */ + /// } else { + /// println!("SQLite version is too old, skipping the test."); + /// return Ok(()); + /// } + /// + /// let result = diesel::select(json_pretty_with_indentation::(json!([{"f1":1,"f2":null},2,null,3]), " ")) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"[ + /// { + /// "f1": 1, + /// "f2": null + /// }, + /// 2, + /// null, + /// 3 + /// ]"#, result); + /// + /// let result = diesel::select(json_pretty_with_indentation::(json!([{"f1":1,"f2":null},2,null,3]), None::<&str>)) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"[ + /// { + /// "f1": 1, + /// "f2": null + /// }, + /// 2, + /// null, + /// 3 + /// ]"#, result); + /// + /// let result = diesel::select(json_pretty_with_indentation::(json!({"a": 1, "b": "cd"}), " ")) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"{ + /// "a": 1, + /// "b": "cd" + /// }"#, result); + /// + /// let result = diesel::select(json_pretty_with_indentation::(json!("abc"), " ")) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#""abc""#, result); + /// + /// let result = diesel::select(json_pretty_with_indentation::(json!(22), " ")) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"22"#, result); + /// + /// let result = diesel::select(json_pretty_with_indentation::(json!(false), None::<&str>)) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"false"#, result); + /// + /// let result = diesel::select(json_pretty_with_indentation::(json!(null), None::<&str>)) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"null"#, result); + /// + /// let result = diesel::select(json_pretty_with_indentation::(json!({}), " ")) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"{}"#, result); + /// + /// let result = diesel::select(json_pretty_with_indentation::, _, _>(None::, None::<&str>)) + /// .get_result::>(connection)?; + /// + /// assert!(result.is_none()); + /// + /// let result = diesel::select(json_pretty_with_indentation::(json!([{"f1":1,"f2":null},2,null,3]), " ")) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"[ + /// { + /// "f1": 1, + /// "f2": null + /// }, + /// 2, + /// null, + /// 3 + /// ]"#, result); + /// + /// let result = diesel::select(json_pretty_with_indentation::(json!([{"f1":1,"f2":null},2,null,3]), None::<&str>)) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"[ + /// { + /// "f1": 1, + /// "f2": null + /// }, + /// 2, + /// null, + /// 3 + /// ]"#, result); + /// + /// let result = diesel::select(json_pretty_with_indentation::(json!({"a": 1, "b": "cd"}), " ")) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"{ + /// "a": 1, + /// "b": "cd" + /// }"#, result); + /// + /// let result = diesel::select(json_pretty_with_indentation::(json!("abc"), " ")) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#""abc""#, result); + /// + /// let result = diesel::select(json_pretty_with_indentation::(json!(22), " ")) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"22"#, result); + /// + /// let result = diesel::select(json_pretty_with_indentation::(json!(false), None::<&str>)) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"false"#, result); + /// + /// let result = diesel::select(json_pretty_with_indentation::(json!(null), None::<&str>)) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"null"#, result); + /// + /// let result = diesel::select(json_pretty_with_indentation::(json!({}), " ")) + /// .get_result::(connection)?; + /// + /// assert_eq!(r#"{}"#, result); + /// + /// let result = diesel::select(json_pretty_with_indentation::, _, _>(None::, None::<&str>)) + /// .get_result::>(connection)?; + /// + /// assert!(result.is_none()); + /// + /// # Ok(()) + /// # } + /// ``` + #[sql_name = "json_pretty"] + fn json_pretty_with_indentation>(j: J, indentation: Nullable) -> J::Out; +} diff --git a/diesel/src/sqlite/expression/helper_types.rs b/diesel/src/sqlite/expression/helper_types.rs index 807aea965c68..97f0c87191c9 100644 --- a/diesel/src/sqlite/expression/helper_types.rs +++ b/diesel/src/sqlite/expression/helper_types.rs @@ -16,3 +16,14 @@ pub type json = super::functions::json, E>; #[allow(non_camel_case_types)] #[cfg(feature = "sqlite")] pub type jsonb = super::functions::jsonb, E>; + +/// Return type of [`json_pretty(json)`](super::functions::json_pretty()) +#[allow(non_camel_case_types)] +#[cfg(feature = "sqlite")] +pub type json_pretty = super::functions::json_pretty, E>; + +/// Return type of [`json_pretty(json, indent)`](super::functions::json_pretty_with_indentation()) +#[allow(non_camel_case_types)] +#[cfg(feature = "sqlite")] +pub type json_pretty_with_indentation = + super::functions::json_pretty_with_indentation, J, I>; diff --git a/diesel_derives/tests/auto_type.rs b/diesel_derives/tests/auto_type.rs index 107141eb1e45..e16bff0b83c3 100644 --- a/diesel_derives/tests/auto_type.rs +++ b/diesel_derives/tests/auto_type.rs @@ -486,7 +486,14 @@ fn postgres_functions() -> _ { #[cfg(feature = "sqlite")] #[auto_type] fn sqlite_functions() -> _ { - (json(sqlite_extras::text), jsonb(sqlite_extras::blob)) + ( + json(sqlite_extras::text), + jsonb(sqlite_extras::blob), + json_pretty(sqlite_extras::json), + json_pretty(sqlite_extras::jsonb), + json_pretty_with_indentation(sqlite_extras::json, " "), + json_pretty_with_indentation(sqlite_extras::jsonb, " "), + ) } #[auto_type]