Skip to content

Commit

Permalink
feat(sqlite): add json_error_position fn
Browse files Browse the repository at this point in the history
  • Loading branch information
IamTossan committed Feb 18, 2025
1 parent 7d88445 commit 29fcdcc
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 1 deletion.
13 changes: 12 additions & 1 deletion diesel/src/sqlite/expression/expression_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -108,6 +108,17 @@ pub(in crate::sqlite) mod private {
impl BinaryOrNullableBinary for Binary {}
impl BinaryOrNullableBinary for Nullable<Binary> {}

#[diagnostic::on_unimplemented(
message = "`{Self}` is neither `diesel::sql_types::Text`, `diesel::sql_types::Nullable<Text>`, `diesel::sql_types::Binary` nor `diesel::sql_types::Nullable<Binary>`",
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<Text> {}
impl TextOrNullableTextOrBinaryOrNullableBinary for Binary {}
impl TextOrNullableTextOrBinaryOrNullableBinary for Nullable<Binary> {}

#[diagnostic::on_unimplemented(
message = "`{Self}` is neither `diesel::sql_types::Json`, `diesel::sql_types::Jsonb`, `diesel::sql_types::Nullable<Json>` nor `diesel::sql_types::Nullable<Jsonb>`",
note = "try to provide an expression that produces one of the expected sql types"
Expand Down
106 changes: 106 additions & 0 deletions diesel/src/sqlite/expression/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -272,6 +273,111 @@ extern "SQL" {
path: Text,
) -> Nullable<Integer>;

/// 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::<Text>("sqlite_version();"))
/// .get_result::<String>(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::<Text, _>(r#"{"a": "b", "c": 1}"#))
/// .get_result::<Option<i32>>(connection)?;
///
/// assert_eq!(Some(0), result);
///
/// let result = diesel::select(json_error_position::<Text, _>(r#"{"a": b", "c": 1}"#))
/// .get_result::<Option<i32>>(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::<Text, _>(json5))
/// .get_result::<Option<i32>>(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::<Text, _>(json5_with_error))
/// .get_result::<Option<i32>>(connection)?;
///
/// assert_eq!(Some(59), result);
///
/// let result = diesel::select(json_error_position::<Nullable<Text>, _>(None::<&str>))
/// .get_result::<Option<i32>>(connection)?;
///
/// assert_eq!(None, result);
///
/// let result = diesel::select(json_error_position::<Binary, _>(br#"{"a": "b", "c": 1}"#))
/// .get_result::<Option<i32>>(connection)?;
///
/// assert_eq!(Some(0), result);
///
/// let result = diesel::select(json_error_position::<Binary, _>(br#"{"a": b", "c": 1}"#))
/// .get_result::<Option<i32>>(connection)?;
///
/// assert_eq!(Some(7), result);
///
/// let result = diesel::select(json_error_position::<Nullable<Binary>, _>(None::<Vec<u8>>))
/// .get_result::<Option<i32>>(connection)?;
///
/// assert_eq!(None, result);
///
/// # Ok(())
/// # }
/// ```
#[cfg(feature = "sqlite")]
fn json_error_position<X: TextOrNullableTextOrBinaryOrNullableBinary + SingleValue>(
x: X,
) -> Nullable<Integer>;

/// Converts the given json value to pretty-printed, indented text
///
/// # Example
Expand Down
5 changes: 5 additions & 0 deletions diesel/src/sqlite/expression/helper_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ pub type json_array_length<J> = super::functions::json_array_length<SqlTypeOf<J>
pub type json_array_length_with_path<J, P> =
super::functions::json_array_length_with_path<SqlTypeOf<J>, 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<X> = super::functions::json_error_position<SqlTypeOf<X>, X>;

/// Return type of [`json_pretty(json)`](super::functions::json_pretty())
#[allow(non_camel_case_types)]
#[cfg(feature = "sqlite")]
Expand Down
2 changes: 2 additions & 0 deletions diesel_derives/tests/auto_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, " "),
Expand Down

0 comments on commit 29fcdcc

Please sign in to comment.