diff --git a/diesel/src/pg/expression/expression_methods.rs b/diesel/src/pg/expression/expression_methods.rs index 10a179617696..28a40a2e1f1b 100644 --- a/diesel/src/pg/expression/expression_methods.rs +++ b/diesel/src/pg/expression/expression_methods.rs @@ -158,6 +158,82 @@ pub trait PgExpressionMethods: Expression + Sized { { Grouped(IsContainedBy::new(self, other.as_expression())) } + + /// Creates a PostgreSQL `IS JSON` expression. + /// Requires PostgreSQL>=16 + /// + /// This operator returns true whether an object is a valid JSON + /// + /// # Example + /// + /// ```rust,no_run + /// # include!("../../doctest_setup.rs"); + /// # + /// # fn main() { + /// # run_test().unwrap(); + /// # } + /// # + /// # fn run_test() -> QueryResult<()> { + /// # use std::collections::Bound; + /// # use diesel::sql_types::Text; + /// # + /// # let conn = &mut establish_connection(); + /// # + /// + /// let res = diesel::select(("1".into_sql::().is_json())).get_result::(conn)?; + /// assert_eq!(res, true); + /// let res = diesel::select(("[1,2,3]".into_sql::().is_json())).get_result::(conn)?; + /// assert_eq!(res, true); + /// let res = diesel::select(("{\"products\": [1,2,3]}".into_sql::().is_json())).get_result::(conn)?; + /// assert_eq!(res, true); + /// let res = diesel::select(("(1,2,3)".into_sql::().is_json())).get_result::(conn)?; + /// assert_eq!(res, false); + /// # + /// # Ok(()) + /// # } + /// ``` + #[allow(clippy::wrong_self_convention)] // This is named after the sql operator + fn is_json(self) -> dsl::IsJson { + IsJson::new(self) + } + + /// Creates a PostgreSQL `IS NOT JSON` expression. + /// Requires PostgreSQL>=16 + /// + /// This operator returns true whether an object is not a valid JSON + /// + /// # Example + /// + /// ```rust,no_run + /// # include!("../../doctest_setup.rs"); + /// # + /// # fn main() { + /// # run_test().unwrap(); + /// # } + /// # + /// # fn run_test() -> QueryResult<()> { + /// # use std::collections::Bound; + /// # use diesel::sql_types::Text; + /// # + /// # let conn = &mut establish_connection(); + /// # + /// + /// let res = diesel::select(("1".into_sql::().is_not_json())).get_result::(conn)?; + /// assert_eq!(res, false); + /// let res = diesel::select(("[1,2,3]".into_sql::().is_not_json())).get_result::(conn)?; + /// assert_eq!(res, false); + /// let res = diesel::select(("{\"products\": [1,2,3]}".into_sql::().is_not_json())).get_result::(conn)?; + /// assert_eq!(res, false); + /// let res = diesel::select(("(1,2,3)".into_sql::().is_not_json())).get_result::(conn)?; + /// assert_eq!(res, true); + /// # + /// # Ok(()) + /// # } + /// ``` + #[allow(clippy::wrong_self_convention)] // This is named after the sql operator + fn is_not_json(self) -> dsl::IsNotJson { + IsNotJson::new(self) + } } impl PgExpressionMethods for T {} diff --git a/diesel/src/pg/expression/helper_types.rs b/diesel/src/pg/expression/helper_types.rs index 0fa6401ce569..06f0a8f8a472 100644 --- a/diesel/src/pg/expression/helper_types.rs +++ b/diesel/src/pg/expression/helper_types.rs @@ -130,6 +130,14 @@ pub type IntersectionRange = Intersection; #[cfg(feature = "postgres_backend")] pub type NullsFirst = super::operators::NullsFirst; +/// The return type of [`expr.is_json()`](super::expression_methods::PgExpressionMethods::is_json) +#[cfg(feature = "postgres_backend")] +pub type IsJson = super::operators::IsJson; + +/// The return type of [`expr.is_not_json()`](super::expression_methods::PgExpressionMethods::is_not_json) +#[cfg(feature = "postgres_backend")] +pub type IsNotJson = super::operators::IsNotJson; + /// The return type of [`expr.nulls_last()`](super::expression_methods::PgSortExpressionMethods::nulls_last) #[cfg(feature = "postgres_backend")] pub type NullsLast = super::operators::NullsLast; diff --git a/diesel/src/pg/expression/operators.rs b/diesel/src/pg/expression/operators.rs index 4bc660ecb09c..261b8fc945fa 100644 --- a/diesel/src/pg/expression/operators.rs +++ b/diesel/src/pg/expression/operators.rs @@ -23,6 +23,8 @@ infix_operator!(SimilarTo, " SIMILAR TO ", backend: Pg); infix_operator!(NotSimilarTo, " NOT SIMILAR TO ", backend: Pg); postfix_operator!(NullsFirst, " NULLS FIRST", NotSelectable, backend: Pg); postfix_operator!(NullsLast, " NULLS LAST", NotSelectable, backend: Pg); +postfix_operator!(IsJson, " IS JSON", backend: Pg); +postfix_operator!(IsNotJson, " IS NOT JSON", backend: Pg); infix_operator!(ContainsNet, " >> ", backend: Pg); infix_operator!(ContainsNetLoose, " >>= ", backend: Pg); infix_operator!(IsContainedByNet, " << ", backend: Pg); diff --git a/diesel_derives/tests/auto_type.rs b/diesel_derives/tests/auto_type.rs index 673db7841741..2deabeaccb96 100644 --- a/diesel_derives/tests/auto_type.rs +++ b/diesel_derives/tests/auto_type.rs @@ -282,6 +282,8 @@ fn test_pg_jsonb_expression_methods() -> _ { .and(pg_extras::jsonb.remove(1_i32).eq(pg_extras::jsonb)) .and(pg_extras::jsonb.remove_by_path(v).eq(pg_extras::jsonb)) .and(pg_extras::jsonb.is_contained_by(pg_extras::jsonb)) + .and(pg_extras::id.is_json()) + .and(pg_extras::id.is_not_json()) } #[cfg(feature = "postgres")]