Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding Support for json_populate_record and jsonb_populate_record #4325

Merged
merged 2 commits into from
Nov 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 22 additions & 7 deletions diesel/src/pg/expression/expression_methods.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
//! PostgreSQL specific expression methods

pub(in crate::pg) use self::private::{
ArrayOrNullableArray, CombinedNullableValue, InetOrCidr, JsonIndex, JsonOrNullableJson,
JsonOrNullableJsonOrJsonbOrNullableJsonb, JsonRemoveIndex, JsonbOrNullableJsonb,
MaybeNullableValue, MultirangeOrNullableMultirange, MultirangeOrRangeMaybeNullable,
RangeOrMultirange, RangeOrNullableRange, RecordOrNullableRecord, TextArrayOrNullableTextArray,
TextOrNullableText,
ArrayOrNullableArray, CombinedAllNullableValue, CombinedNullableValue, InetOrCidr, JsonIndex,
JsonOrNullableJson, JsonOrNullableJsonOrJsonbOrNullableJsonb, JsonRemoveIndex,
JsonbOrNullableJsonb, MaybeNullableValue, MultirangeOrNullableMultirange,
MultirangeOrRangeMaybeNullable, RangeOrMultirange, RangeOrNullableRange,
RecordOrNullableRecord, TextArrayOrNullableTextArray, TextOrNullableText,
};
use super::date_and_time::{AtTimeZone, DateTimeLike};
use super::operators::*;
Expand Down Expand Up @@ -3378,8 +3378,8 @@ where

pub(in crate::pg) mod private {
use crate::sql_types::{
Array, Binary, Cidr, Inet, Integer, Json, Jsonb, MaybeNullableType, Multirange, Nullable,
OneIsNullable, Range, Record, SingleValue, SqlType, Text,
AllAreNullable, Array, Binary, Cidr, Inet, Integer, Json, Jsonb, MaybeNullableType,
Multirange, Nullable, OneIsNullable, Range, Record, SingleValue, SqlType, Text,
};
use crate::{Expression, IntoSql};

Expand Down Expand Up @@ -3726,4 +3726,19 @@ pub(in crate::pg) mod private {

impl<T> RecordOrNullableRecord for Record<T> {}
impl<T> RecordOrNullableRecord for Nullable<Record<T>> {}

pub trait CombinedAllNullableValue<O, Out>: SingleValue {
type Out: SingleValue;
}

impl<T, O, Out> CombinedAllNullableValue<O, Out> for T
where
T: SingleValue,
O: SingleValue,
T::IsNull: AllAreNullable<O::IsNull>,
<T::IsNull as AllAreNullable<O::IsNull>>::Out: MaybeNullableType<Out>,
<<T::IsNull as AllAreNullable<O::IsNull>>::Out as MaybeNullableType<Out>>::Out: SingleValue,
{
type Out = <<T::IsNull as AllAreNullable<O::IsNull>>::Out as MaybeNullableType<Out>>::Out;
}
}
119 changes: 119 additions & 0 deletions diesel/src/pg/expression/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use super::expression_methods::InetOrCidr;
use crate::expression::functions::define_sql_function;
use crate::pg::expression::expression_methods::ArrayOrNullableArray;
use crate::pg::expression::expression_methods::CombinedAllNullableValue;
use crate::pg::expression::expression_methods::CombinedNullableValue;
use crate::pg::expression::expression_methods::JsonOrNullableJson;
use crate::pg::expression::expression_methods::JsonbOrNullableJsonb;
Expand Down Expand Up @@ -2279,3 +2280,121 @@ define_sql_function! {
#[sql_name = "row_to_json"]
fn row_to_json<R: RecordOrNullableRecord + MaybeNullableValue<Json>>(record: R) -> R::Out;
}

#[cfg(feature = "postgres_backend")]
define_sql_function! {
/// This function `json_populate_record` takes a Record base and Json as an input and converts it to top-level
/// JSON object to a row having the composite type of the base argument.
///
/// # 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::json_populate_record;
/// # use diesel::dsl::sql;
/// # use diesel::sql_types::{Record, Text, Integer, Json};
/// # use serde_json::Value;
/// # let connection = &mut establish_connection();
///
/// let expected: Value = serde_json::json!({
/// "f1": "Alice",
/// "f2": 16
/// });
/// let record: (String, i32) = diesel::select(json_populate_record::<Record<(Text, Integer)>, Json, _, _>(
/// sql::<Record<(Text, Integer)>>("ROW('John', 30)"),
/// expected
/// )).get_result(connection)?;
/// assert_eq!(record, ("Alice".to_string(), 16));
///
/// let expected: Value = serde_json::json!({});
/// let record: (String, i32) = diesel::select(json_populate_record::<Record<(Text, Integer)>, Json, _, _>(
/// sql::<Record<(Text, Integer)>>("ROW('John', 30)"),
/// expected
/// )).get_result(connection)?;
/// assert_eq!(record, ("John".to_string(), 30));
///
/// let expected: Value = serde_json::json!({
/// "f1": "Alice"
/// });
/// let record: (String, i32) = diesel::select(json_populate_record::<Record<(Text, Integer)>, Json, _, _>(
/// sql::<Record<(Text, Integer)>>("ROW('John', 30)"),
/// expected
/// )).get_result(connection)?;
/// assert_eq!(record, ("Alice".to_string(), 30));
///
/// # Ok(())
/// # }
/// ```
#[sql_name = "json_populate_record"]
fn json_populate_record<
B: RecordOrNullableRecord + SingleValue,
J: JsonOrNullableJson + CombinedAllNullableValue<Json, B>
>(base: B, from_json: J) -> J::Out;
}

#[cfg(feature = "postgres_backend")]
define_sql_function! {
/// This function `jsonb_populate_record` takes a Record base and Jsonb as an input and converts it to top-level
/// JSON object to a row having the composite type of the base argument.
///
/// # 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_populate_record;
/// # use diesel::dsl::sql;
/// # use diesel::sql_types::{Record, Text, Integer, Jsonb};
/// # use serde_json::Value;
/// # let connection = &mut establish_connection();
///
/// let expected: Value = serde_json::json!({
/// "f1": "Alice",
/// "f2": 16
/// });
/// let record: (String, i32) = diesel::select(jsonb_populate_record::<Record<(Text, Integer)>, Jsonb, _, _>(
/// sql::<Record<(Text, Integer)>>("ROW('John', 30)"),
/// expected
/// )).get_result(connection)?;
/// assert_eq!(record, ("Alice".to_string(), 16));
///
/// let expected: Value = serde_json::json!({});
/// let record: (String, i32) = diesel::select(jsonb_populate_record::<Record<(Text, Integer)>, Jsonb, _, _>(
/// sql::<Record<(Text, Integer)>>("ROW('John', 30)"),
/// expected
/// )).get_result(connection)?;
/// assert_eq!(record, ("John".to_string(), 30));
///
/// let expected: Value = serde_json::json!({
/// "f2": 42,
/// });
/// let record: (String, i32) = diesel::select(jsonb_populate_record::<Record<(Text, Integer)>, Jsonb, _, _>(
/// sql::<Record<(Text, Integer)>>("ROW('John', 30)"),
/// expected
/// )).get_result(connection)?;
/// assert_eq!(record, ("John".to_string(), 42));
///
/// # Ok(())
/// # }
/// ```
#[sql_name = "jsonb_populate_record"]
fn jsonb_populate_record<
B: RecordOrNullableRecord + SingleValue,
J: JsonbOrNullableJsonb + CombinedAllNullableValue<Jsonb, B>
>(base: B, from_json: J) -> J::Out;
}
12 changes: 12 additions & 0 deletions diesel/src/pg/expression/helper_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -556,3 +556,15 @@ pub type jsonb_object_with_keys_and_values<K, V> =
#[allow(non_camel_case_types)]
#[cfg(feature = "postgres_backend")]
pub type row_to_json<R> = super::functions::row_to_json<SqlTypeOf<R>, R>;

/// Return type of [`json_populate_record(base, json)`](super::functions::json_populate_record())
#[allow(non_camel_case_types)]
#[cfg(feature = "postgres_backend")]
pub type json_populate_record<B, J> =
super::functions::json_populate_record<SqlTypeOf<B>, SqlTypeOf<J>, B, J>;

/// Return type of [`jsonb_populate_record(base, json)`](super::functions::jsonb_populate_record())
#[allow(non_camel_case_types)]
#[cfg(feature = "postgres_backend")]
pub type jsonb_populate_record<B, J> =
super::functions::jsonb_populate_record<SqlTypeOf<B>, SqlTypeOf<J>, B, J>;
2 changes: 2 additions & 0 deletions diesel_derives/tests/auto_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,8 @@ fn postgres_functions() -> _ {
jsonb_object(pg_extras::text_array),
jsonb_object_with_keys_and_values(pg_extras::text_array, pg_extras::text_array),
row_to_json(pg_extras::record),
json_populate_record(pg_extras::record, pg_extras::json),
jsonb_populate_record(pg_extras::record, pg_extras::jsonb),
)
}

Expand Down
Loading