diff --git a/diesel/src/pg/expression/functions.rs b/diesel/src/pg/expression/functions.rs index cf1db87028e1..e380e648754a 100644 --- a/diesel/src/pg/expression/functions.rs +++ b/diesel/src/pg/expression/functions.rs @@ -2578,3 +2578,91 @@ define_sql_function! { Arr: TextArrayOrNullableTextArray + CombinedNullableValue >(base: E, path: Arr, new_value: E, create_if_missing: Bool) -> Arr::Out; } + +#[cfg(feature = "postgres_backend")] +define_sql_function! { + /// Returns target with the item designated by path replaced by new_value, + /// or with new_value added and the item designated by path does not exist. + /// + /// It can't set path in scalar + /// + /// All earlier steps in the path must exist, or the target is returned unchanged. + /// As with the path oriented operators, negative integers that appear in the path count from the end of JSON arrays. + /// If the last path step is an array index that is out of range, + /// the new value is added at the beginning of the array if the index is negative, + /// or at the end of the array if it is positive. + /// + /// If new_value is not NULL, behaves identically to jsonb_set. + /// Otherwise behaves according to the value of null_value_treatment + /// which must be one of 'raise_exception', 'use_json_null', 'delete_key', or 'return_target'. + /// The default is 'use_json_null'. + /// + /// # 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_set_lax; + /// # use diesel::sql_types::{Jsonb,Array,NullValueTreatment, Json, Nullable, Text}; + /// # use serde_json::{json,Value}; + /// # let connection = &mut establish_connection(); + /// + /// let null_value_treatment = NullValueTreatment::UseJsonNull; + /// let result = diesel::select(jsonb_set_lax::, _, _, _, _, _>( + /// json!([{"f1":1,"f2":null},2,null,3]), + /// vec!["0","f1"], + /// json!([2,3,4]), + /// true, + /// null_value_treatment + /// )).get_result::(connection)?; + /// let expected: Value = json!([{"f1": [2, 3, 4], "f2": null}, 2, null, 3]); + /// assert_eq!(result, expected); + /// + /// let null_value_treatment = NullValueTreatment::ReturnTarget; + /// let result = diesel::select(jsonb_set_lax::, Array>, _, _, _, _, _>( + /// json!([{"f1":99,"f2":null},2]), + /// vec!["0","f3"], + /// None::, + /// true, + /// null_value_treatment + /// )).get_result::>(connection)?; + /// assert_eq!(result, Some(json!([{"f1":99,"f2":null},2]))); + /// + /// let null_value_treatment = NullValueTreatment::UseJsonNull; + /// let empty:Vec = Vec::new(); + /// let result = diesel::select(jsonb_set_lax::>, _, _, _, _, _>( + /// // cannot be json!(null) + /// json!([]), + /// empty, + /// json!(null), + /// true, + /// null_value_treatment + /// )).get_result::(connection)?; + /// let expected = json!([]); + /// assert_eq!(result, expected); + /// + /// let null_value_treatment = NullValueTreatment::UseJsonNull; + /// let result = diesel::select(jsonb_set_lax::>>, _, _, _, _, _,>( + /// json!(null), + /// None::>, + /// json!({"foo": 42}), + /// true, + /// null_value_treatment + /// )).get_result::>(connection)?; + /// assert!(result.is_none()); + /// + /// # Ok(()) + /// # } + /// ``` + fn jsonb_set_lax< + E: JsonbOrNullableJsonb + SingleValue, + Arr: TextArrayOrNullableTextArray + CombinedNullableValue, + >(base: E, path: Arr, new_value: E, create_if_missing: Bool, null_value_treatment: NullValueTreatmentEnum) -> Arr::Out; +} diff --git a/diesel/src/pg/expression/helper_types.rs b/diesel/src/pg/expression/helper_types.rs index 736c8287d32c..da85c37d7ba6 100644 --- a/diesel/src/pg/expression/helper_types.rs +++ b/diesel/src/pg/expression/helper_types.rs @@ -603,3 +603,9 @@ pub type jsonb_set = super::functions::jsonb_set, SqlTypeO #[cfg(feature = "postgres_backend")] pub type jsonb_set_create_if_missing = super::functions::jsonb_set_create_if_missing, SqlTypeOf, B, J, R, C>; + +/// Return type of [`jsonb_set_lax(base, path, new_value, create_if_missing, null_value_treatment)`](super::functions::jsonb_set_lax()) +#[allow(non_camel_case_types)] +#[cfg(feature = "postgres_backend")] +pub type jsonb_set_lax = + super::functions::jsonb_set_lax, SqlTypeOf, B, J, R, C, E>; diff --git a/diesel/src/pg/types/json_function_enum.rs b/diesel/src/pg/types/json_function_enum.rs new file mode 100644 index 000000000000..837104de7d7a --- /dev/null +++ b/diesel/src/pg/types/json_function_enum.rs @@ -0,0 +1,21 @@ +use std::error::Error; +use std::io::Write; + +use crate::pg::Pg; +use crate::serialize::{self, IsNull, Output, ToSql}; +use crate::sql_types::*; + +#[cfg(feature = "postgres_backend")] +impl ToSql for NullValueTreatment { + fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result { + let literal = match self { + Self::RaiseException => "raise_exxception", + Self::UseJsonNull => "use_json_null", + Self::DeleteKey => "delete_key", + Self::ReturnTarget => "return_target", + }; + out.write_all(literal.as_bytes()) + .map(|_| IsNull::No) + .map_err(|e| Box::new(e) as Box) + } +} diff --git a/diesel/src/pg/types/mod.rs b/diesel/src/pg/types/mod.rs index 947626c0938c..dae0b3439407 100644 --- a/diesel/src/pg/types/mod.rs +++ b/diesel/src/pg/types/mod.rs @@ -10,6 +10,7 @@ mod integers; mod ipnet_address; #[cfg(feature = "serde_json")] mod json; +mod json_function_enum; mod mac_addr; mod mac_addr_8; #[doc(hidden)] @@ -208,6 +209,31 @@ pub mod sql_types { #[doc(hidden)] pub type Tstzmultirange = Multirange; + /// This is a wrapper for [`NullValueTreatment`] to represent null_value_treatment for jsonb_seet_lax: + /// 'raise_exception' 'use_json_null' 'delete_key' 'return_target' + /// used in functions jsonb_set_lax + #[derive(Debug, Clone, Copy, QueryId, SqlType)] + #[cfg(feature = "postgres_backend")] + #[diesel(postgres_type(name = "text"))] + pub struct NullValueTreatmentEnum; + + /// Represent null_value_treatment for jsonb_seet_lax: + /// 'raise_exception' 'use_json_null' 'delete_key' 'return_target' + /// used in functions jsonb_seet_lax. + #[derive(Debug, Clone, Copy, diesel_derives::AsExpression)] + #[diesel(sql_type = NullValueTreatmentEnum)] + #[allow(clippy::enum_variant_names)] + pub enum NullValueTreatment { + /// postgres 'raise_exception' + RaiseException, + /// postgres 'use_json_null' + UseJsonNull, + /// postgres 'delete_key' + DeleteKey, + /// postgres 'return_target' + ReturnTarget, + } + /// This is a wrapper for [`RangeBound`] to represent range bounds: '[]', '(]', '[)', '()', /// used in functions int4range, int8range, numrange, tsrange, tstzrange, daterange. #[derive(Debug, Clone, Copy, QueryId, SqlType)] diff --git a/diesel_derives/tests/auto_type.rs b/diesel_derives/tests/auto_type.rs index 107141eb1e45..9ddb87acdeb1 100644 --- a/diesel_derives/tests/auto_type.rs +++ b/diesel_derives/tests/auto_type.rs @@ -415,6 +415,8 @@ fn test_normal_functions() -> _ { fn postgres_functions() -> _ { let bound: sql_types::RangeBound = sql_types::RangeBound::LowerBoundExclusiveUpperBoundExclusive; + let null_value_treatment: sql_types::NullValueTreatment = + sql_types::NullValueTreatment::UseJsonNull; ( lower(pg_extras::range), upper(pg_extras::range), @@ -480,6 +482,13 @@ fn postgres_functions() -> _ { pg_extras::jsonb, pg_extras::boolean, ), + jsonb_set_lax( + pg_extras::jsonb, + pg_extras::text_array, + pg_extras::jsonb, + pg_extras::boolean, + null_value_treatment, + ), ) }