diff --git a/diesel/src/sqlite/expression/expression_methods.rs b/diesel/src/sqlite/expression/expression_methods.rs index 6aefd317d603..c5203010917d 100644 --- a/diesel/src/sqlite/expression/expression_methods.rs +++ b/diesel/src/sqlite/expression/expression_methods.rs @@ -2,7 +2,7 @@ pub(in crate::sqlite) use self::private::{ BinaryOrNullableBinary, JsonOrNullableJson, JsonOrNullableJsonOrJsonbOrNullableJsonb, - MaybeNullableValue, TextOrNullableText, + MaybeNullableValue, TextOrNullableText, TextOrNullableTextOrBinaryOrNullableBinary, }; use super::operators::*; use crate::dsl; @@ -108,6 +108,17 @@ pub(in crate::sqlite) mod private { impl BinaryOrNullableBinary for Binary {} impl BinaryOrNullableBinary for Nullable {} + #[diagnostic::on_unimplemented( + message = "`{Self}` is neither `diesel::sql_types::Text`, `diesel::sql_types::Nullable`, `diesel::sql_types::Binary` nor `diesel::sql_types::Nullable`", + note = "try to provide an expression that produces one of the expected sql types" + )] + pub trait TextOrNullableTextOrBinaryOrNullableBinary {} + + impl TextOrNullableTextOrBinaryOrNullableBinary for Text {} + impl TextOrNullableTextOrBinaryOrNullableBinary for Nullable {} + impl TextOrNullableTextOrBinaryOrNullableBinary for Binary {} + impl TextOrNullableTextOrBinaryOrNullableBinary 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" diff --git a/diesel/src/sqlite/expression/functions.rs b/diesel/src/sqlite/expression/functions.rs index ff78163e628c..be29a070c8bb 100644 --- a/diesel/src/sqlite/expression/functions.rs +++ b/diesel/src/sqlite/expression/functions.rs @@ -6,6 +6,7 @@ use crate::sqlite::expression::expression_methods::JsonOrNullableJson; use crate::sqlite::expression::expression_methods::JsonOrNullableJsonOrJsonbOrNullableJsonb; use crate::sqlite::expression::expression_methods::MaybeNullableValue; use crate::sqlite::expression::expression_methods::TextOrNullableText; +use crate::sqlite::expression::expression_methods::TextOrNullableTextOrBinaryOrNullableBinary; #[cfg(feature = "sqlite")] #[declare_sql_function] @@ -272,6 +273,111 @@ extern "SQL" { path: Text, ) -> Nullable; + /// The json_error_position(X) function returns 0 if the input X is a well-formed JSON or JSON5 string. + /// If the input X contains one or more syntax errors, then this function returns the character position of the first syntax error. + /// The left-most character is position 1. + /// + /// If the input X is a BLOB, then this routine returns 0 if X is a well-formed JSONB blob. If the return value is positive, + /// then it represents the approximate 1-based position in the BLOB of the first detected error. + /// + /// # 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_error_position}; + /// # use diesel::sql_types::{Binary, Text, 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_error_position::(r#"{"a": "b", "c": 1}"#)) + /// .get_result::>(connection)?; + /// + /// assert_eq!(Some(0), result); + /// + /// let result = diesel::select(json_error_position::(r#"{"a": b", "c": 1}"#)) + /// .get_result::>(connection)?; + /// + /// assert_eq!(Some(7), result); + /// + /// let json5 = r#" + /// { + /// // A traditional message. + /// message: 'hello world', + /// + /// // A number for some reason. + /// n: 42, + /// } + /// "#; + /// let result = diesel::select(json_error_position::(json5)) + /// .get_result::>(connection)?; + /// + /// assert_eq!(Some(0), result); + /// + /// let json5_with_error = r#" + /// { + /// // A traditional message. + /// message: hello world', + /// + /// // A number for some reason. + /// n: 42, + /// } + /// "#; + /// let result = diesel::select(json_error_position::(json5_with_error)) + /// .get_result::>(connection)?; + /// + /// assert_eq!(Some(59), result); + /// + /// let result = diesel::select(json_error_position::, _>(None::<&str>)) + /// .get_result::>(connection)?; + /// + /// assert_eq!(None, result); + /// + /// let result = diesel::select(json_error_position::(br#"{"a": "b", "c": 1}"#)) + /// .get_result::>(connection)?; + /// + /// assert_eq!(Some(0), result); + /// + /// let result = diesel::select(json_error_position::(br#"{"a": b", "c": 1}"#)) + /// .get_result::>(connection)?; + /// + /// assert_eq!(Some(7), result); + /// + /// let result = diesel::select(json_error_position::, _>(None::>)) + /// .get_result::>(connection)?; + /// + /// assert_eq!(None, result); + /// + /// # Ok(()) + /// # } + /// ``` + #[cfg(feature = "sqlite")] + fn json_error_position( + x: X, + ) -> Nullable; + /// Converts the given json value to pretty-printed, indented text /// /// # Example diff --git a/diesel/src/sqlite/expression/helper_types.rs b/diesel/src/sqlite/expression/helper_types.rs index e13f7fbeebd1..8ba585db4c1f 100644 --- a/diesel/src/sqlite/expression/helper_types.rs +++ b/diesel/src/sqlite/expression/helper_types.rs @@ -28,6 +28,11 @@ pub type json_array_length = super::functions::json_array_length pub type json_array_length_with_path = super::functions::json_array_length_with_path, J, P>; +/// Return type of [`json_error_position(json)`](super::functions::json_error_position()) +#[allow(non_camel_case_types)] +#[cfg(feature = "sqlite")] +pub type json_error_position = super::functions::json_error_position, X>; + /// Return type of [`json_pretty(json)`](super::functions::json_pretty()) #[allow(non_camel_case_types)] #[cfg(feature = "sqlite")] diff --git a/diesel_derives/tests/auto_type.rs b/diesel_derives/tests/auto_type.rs index 679252817cd6..bfec9f64c221 100644 --- a/diesel_derives/tests/auto_type.rs +++ b/diesel_derives/tests/auto_type.rs @@ -507,6 +507,8 @@ fn sqlite_functions() -> _ { jsonb(sqlite_extras::blob), json_array_length(sqlite_extras::json), json_array_length_with_path(sqlite_extras::json, sqlite_extras::text), + json_error_position(sqlite_extras::text), + json_error_position(sqlite_extras::blob), json_pretty(sqlite_extras::json), json_pretty(sqlite_extras::jsonb), json_pretty_with_indentation(sqlite_extras::json, " "),