diff --git a/diesel/src/pg/expression/expression_methods.rs b/diesel/src/pg/expression/expression_methods.rs index 67e8f73cb447..43ca9d0a243d 100644 --- a/diesel/src/pg/expression/expression_methods.rs +++ b/diesel/src/pg/expression/expression_methods.rs @@ -2,8 +2,8 @@ pub(in crate::pg) use self::private::{ ArrayOrNullableArray, InetOrCidr, JsonIndex, JsonOrNullableJsonOrJsonbOrNullableJsonb, - JsonRemoveIndex, JsonbOrNullableJsonb, MultirangeOrRangeMaybeNullable, RangeHelper, - RangeOrNullableRange, TextOrNullableText, + JsonRemoveIndex, JsonbOrNullableJsonb, MultirangeOrNullableMultirange, + MultirangeOrRangeMaybeNullable, RangeHelper, RangeOrNullableRange, TextOrNullableText, }; use super::date_and_time::{AtTimeZone, DateTimeLike}; use super::operators::*; @@ -3485,10 +3485,37 @@ pub(in crate::pg) mod private { message = "`{Self}` is neither `diesel::sql_types::Range<_>` nor `diesel::sql_types::Nullable>`", note = "try to provide an expression that produces one of the expected sql types" )] - pub trait RangeOrNullableRange {} + pub trait RangeOrNullableRange { + type Inner: SingleValue; + } - impl RangeOrNullableRange for Range {} - impl RangeOrNullableRange for Nullable> {} + impl RangeOrNullableRange for Range { + type Inner = ST; + } + impl RangeOrNullableRange for Nullable> { + type Inner = ST; + } + + /// Marker trait used to implement `PgRangeExpressionMethods` on the appropriate + /// types. Once coherence takes associated types into account, we can remove + /// this trait. + #[diagnostic::on_unimplemented( + message = "`{Self}` is neither `diesel::sql_types::Range<_>` nor `diesel::sql_types::Nullable>`", + note = "try to provide an expression that produces one of the expected sql types" + )] + pub trait MultirangeOrNullableMultirange { + type Inner: SingleValue; + type Range: SingleValue; + } + + impl MultirangeOrNullableMultirange for Multirange { + type Inner = ST; + type Range = Range; + } + impl MultirangeOrNullableMultirange for Nullable> { + type Inner = ST; + type Range = Nullable>; + } /// Marker trait used to implement `PgRangeExpressionMethods` on the appropriate /// types. Once coherence takes associated types into account, we can remove diff --git a/diesel/src/pg/expression/functions.rs b/diesel/src/pg/expression/functions.rs index dbf58b6ed6e4..fd017e7223e5 100644 --- a/diesel/src/pg/expression/functions.rs +++ b/diesel/src/pg/expression/functions.rs @@ -1,10 +1,11 @@ //! PostgreSQL specific functions use super::expression_methods::InetOrCidr; -use super::expression_methods::RangeHelper; use crate::expression::functions::define_sql_function; use crate::pg::expression::expression_methods::ArrayOrNullableArray; +use crate::pg::expression::expression_methods::MultirangeOrNullableMultirange; use crate::pg::expression::expression_methods::MultirangeOrRangeMaybeNullable; +use crate::pg::expression::expression_methods::RangeOrNullableRange; use crate::sql_types::*; define_sql_function! { @@ -95,7 +96,7 @@ define_sql_function! { /// let int = diesel::select(lower::>, _>(None::>)).get_result::>(connection)?; /// assert_eq!(None, int); /// - /// let int = diesel::select(lower::, _>(vec![(Bound::Included(5), Bound::Included(7))])).get_result::>(connection)?; + /// let int = diesel::select(lower::, _>(vec![5..7])).get_result::>(connection)?; /// assert_eq!(Some(5), int); /// # Ok(()) /// # } @@ -114,39 +115,32 @@ define_sql_function! { /// ```rust /// # include!("../../doctest_setup.rs"); /// # - /// # table! { - /// # posts { - /// # id -> Integer, - /// # versions -> Range, - /// # } - /// # } - /// # /// # fn main() { /// # run_test().unwrap(); /// # } /// # /// # fn run_test() -> QueryResult<()> { - /// # use self::posts::dsl::*; + /// # use diesel::pg::sql_types::{Range, Multirange}; + /// # use diesel::dsl::upper; /// # use std::collections::Bound; - /// # let conn = &mut establish_connection(); - /// # diesel::sql_query("DROP TABLE IF EXISTS posts").execute(conn).unwrap(); - /// # diesel::sql_query("CREATE TABLE posts (id SERIAL PRIMARY KEY, versions INT4RANGE NOT NULL)").execute(conn).unwrap(); - /// # - /// use diesel::dsl::upper; - /// diesel::insert_into(posts) - /// .values(&[ - /// versions.eq((Bound::Included(5), Bound::Excluded(7))), - /// versions.eq((Bound::Included(5), Bound::Unbounded)) - /// ]).execute(conn)?; + /// # use diesel::sql_types::{Nullable, Integer, Array}; + /// # let connection = &mut establish_connection(); + /// let int = diesel::select(upper::, _>(1..2)).get_result::>(connection)?; + /// assert_eq!(Some(2), int); + /// + /// let int = diesel::select(upper::, _>(1..)).get_result::>(connection)?; + /// assert_eq!(None, int); + /// + /// let int = diesel::select(upper::>, _>(None::>)).get_result::>(connection)?; + /// assert_eq!(None, int); /// - /// let cool_posts = posts.select(upper(versions)) - /// .load::>(conn)?; - /// assert_eq!(vec![Some(7), None], cool_posts); + /// let int = diesel::select(upper::, _>(vec![5..7])).get_result::>(connection)?; + /// assert_eq!(Some(7), int); /// # Ok(()) /// # } /// ``` #[cfg(feature = "postgres_backend")] - fn upper(range: T) -> Nullable<::Inner>; + fn upper(range: R) -> Nullable; } define_sql_function! { @@ -157,39 +151,32 @@ define_sql_function! { /// ```rust /// # include!("../../doctest_setup.rs"); /// # - /// # table! { - /// # posts { - /// # id -> Integer, - /// # versions -> Range, - /// # } - /// # } - /// # /// # fn main() { /// # run_test().unwrap(); /// # } /// # /// # fn run_test() -> QueryResult<()> { - /// # use self::posts::dsl::*; + /// # use diesel::pg::sql_types::{Range, Multirange}; + /// # use diesel::dsl::isempty; /// # use std::collections::Bound; - /// # let conn = &mut establish_connection(); - /// # diesel::sql_query("DROP TABLE IF EXISTS posts").execute(conn).unwrap(); - /// # diesel::sql_query("CREATE TABLE posts (id SERIAL PRIMARY KEY, versions INT4RANGE NOT NULL)").execute(conn).unwrap(); - /// # - /// use diesel::dsl::isempty; - /// diesel::insert_into(posts) - /// .values(&[ - /// versions.eq((Bound::Included(5), Bound::Excluded(7))), - /// versions.eq((Bound::Excluded(7), Bound::Excluded(7))), - /// ]).execute(conn)?; + /// # use diesel::sql_types::{Nullable, Integer, Array}; + /// # let connection = &mut establish_connection(); + /// let int = diesel::select(isempty::, _>(1..5)).get_result::>(connection)?; + /// assert_eq!(Some(false), int); + /// + /// let int = diesel::select(isempty::, _>(1..1)).get_result::>(connection)?; + /// assert_eq!(Some(true), int); + /// + /// let int = diesel::select(isempty::>, _>(None::>)).get_result::>(connection)?; + /// assert_eq!(None, int); /// - /// let cool_posts = posts.select(isempty(versions)) - /// .load::(conn)?; - /// assert_eq!(vec![false, true], cool_posts); + /// let int = diesel::select(isempty::, _>(vec![5..7])).get_result::>(connection)?; + /// assert_eq!(Some(false), int); /// # Ok(()) /// # } /// ``` #[cfg(feature = "postgres_backend")] - fn isempty(range: T) -> Bool; + fn isempty(range: R) -> Nullable; } define_sql_function! { @@ -200,39 +187,32 @@ define_sql_function! { /// ```rust /// # include!("../../doctest_setup.rs"); /// # - /// # table! { - /// # posts { - /// # id -> Integer, - /// # versions -> Range, - /// # } - /// # } - /// # /// # fn main() { /// # run_test().unwrap(); /// # } /// # /// # fn run_test() -> QueryResult<()> { - /// # use self::posts::dsl::*; + /// # use diesel::pg::sql_types::{Range, Multirange}; + /// # use diesel::dsl::lower_inc; /// # use std::collections::Bound; - /// # let conn = &mut establish_connection(); - /// # diesel::sql_query("DROP TABLE IF EXISTS posts").execute(conn).unwrap(); - /// # diesel::sql_query("CREATE TABLE posts (id SERIAL PRIMARY KEY, versions INT4RANGE NOT NULL)").execute(conn).unwrap(); - /// # - /// use diesel::dsl::lower_inc; - /// diesel::insert_into(posts) - /// .values(&[ - /// versions.eq((Bound::Included(5), Bound::Excluded(7))), - /// versions.eq((Bound::Excluded(7), Bound::Excluded(7))), - /// ]).execute(conn)?; + /// # use diesel::sql_types::{Nullable, Integer, Array}; + /// # let connection = &mut establish_connection(); + /// let int = diesel::select(lower_inc::, _>(1..5)).get_result::>(connection)?; + /// assert_eq!(Some(true), int); + /// + /// let int = diesel::select(lower_inc::, _>(..5)).get_result::>(connection)?; + /// assert_eq!(Some(false), int); + /// + /// let int = diesel::select(lower_inc::>, _>(None::>)).get_result::>(connection)?; + /// assert_eq!(None, int); /// - /// let cool_posts = posts.select(lower_inc(versions)) - /// .load::(conn)?; - /// assert_eq!(vec![true, false], cool_posts); + /// let int = diesel::select(lower_inc::, _>(vec![5..7])).get_result::>(connection)?; + /// assert_eq!(Some(true), int); /// # Ok(()) /// # } /// ``` #[cfg(feature = "postgres_backend")] - fn lower_inc(range: T) -> Bool; + fn lower_inc(range: R) -> Nullable; } define_sql_function! { @@ -243,38 +223,29 @@ define_sql_function! { /// ```rust /// # include!("../../doctest_setup.rs"); /// # - /// # table! { - /// # posts { - /// # id -> Integer, - /// # versions -> Range, - /// # } - /// # } - /// # /// # fn main() { /// # run_test().unwrap(); /// # } /// # /// # fn run_test() -> QueryResult<()> { - /// # use self::posts::dsl::*; + /// # use diesel::pg::sql_types::{Range, Multirange}; + /// # use diesel::dsl::upper_inc; /// # use std::collections::Bound; - /// # let conn = &mut establish_connection(); - /// # diesel::sql_query("DROP TABLE IF EXISTS posts").execute(conn).unwrap(); - /// # diesel::sql_query("CREATE TABLE posts (id SERIAL PRIMARY KEY, versions INT4RANGE NOT NULL)").execute(conn).unwrap(); - /// # - /// use diesel::dsl::upper_inc; - /// diesel::insert_into(posts) - /// .values(&[ - /// versions.eq((Bound::Included(5), Bound::Excluded(7))), - /// ]).execute(conn)?; + /// # use diesel::sql_types::{Nullable, Integer, Array}; + /// # let connection = &mut establish_connection(); + /// let int = diesel::select(upper_inc::, _>(1..5)).get_result::>(connection)?; + /// assert_eq!(Some(false), int); + /// + /// let int = diesel::select(upper_inc::>, _>(None::>)).get_result::>(connection)?; + /// assert_eq!(None, int); /// - /// let cool_posts = posts.select(upper_inc(versions)) - /// .load::(conn)?; - /// assert_eq!(vec![false], cool_posts); + /// let int = diesel::select(upper_inc::, _>(vec![5..7])).get_result::>(connection)?; + /// assert_eq!(Some(false), int); /// # Ok(()) /// # } /// ``` #[cfg(feature = "postgres_backend")] - fn upper_inc(range: T) -> Bool; + fn upper_inc(range: R) -> Nullable; } define_sql_function! { @@ -285,39 +256,32 @@ define_sql_function! { /// ```rust /// # include!("../../doctest_setup.rs"); /// # - /// # table! { - /// # posts { - /// # id -> Integer, - /// # versions -> Range, - /// # } - /// # } - /// # /// # fn main() { /// # run_test().unwrap(); /// # } /// # /// # fn run_test() -> QueryResult<()> { - /// # use self::posts::dsl::*; + /// # use diesel::pg::sql_types::{Range, Multirange}; + /// # use diesel::dsl::lower_inf; /// # use std::collections::Bound; - /// # let conn = &mut establish_connection(); - /// # diesel::sql_query("DROP TABLE IF EXISTS posts").execute(conn).unwrap(); - /// # diesel::sql_query("CREATE TABLE posts (id SERIAL PRIMARY KEY, versions INT4RANGE NOT NULL)").execute(conn).unwrap(); - /// # - /// use diesel::dsl::lower_inf; - /// diesel::insert_into(posts) - /// .values(&[ - /// versions.eq((Bound::Included(5), Bound::Excluded(7))), - /// versions.eq((Bound::Unbounded, Bound::Excluded(7))), - /// ]).execute(conn)?; + /// # use diesel::sql_types::{Nullable, Integer, Array}; + /// # let connection = &mut establish_connection(); + /// let int = diesel::select(lower_inf::, _>(1..5)).get_result::>(connection)?; + /// assert_eq!(Some(false), int); + /// + /// let int = diesel::select(lower_inf::, _>(..5)).get_result::>(connection)?; + /// assert_eq!(Some(true), int); + /// + /// let int = diesel::select(lower_inf::>, _>(None::>)).get_result::>(connection)?; + /// assert_eq!(None, int); /// - /// let cool_posts = posts.select(lower_inf(versions)) - /// .load::(conn)?; - /// assert_eq!(vec![false, true], cool_posts); + /// let int = diesel::select(lower_inf::, _>(vec![5..7])).get_result::>(connection)?; + /// assert_eq!(Some(false), int); /// # Ok(()) /// # } /// ``` #[cfg(feature = "postgres_backend")] - fn lower_inf(range: T) -> Bool; + fn lower_inf(range: R) -> Nullable; } define_sql_function! { @@ -328,39 +292,32 @@ define_sql_function! { /// ```rust /// # include!("../../doctest_setup.rs"); /// # - /// # table! { - /// # posts { - /// # id -> Integer, - /// # versions -> Range, - /// # } - /// # } - /// # /// # fn main() { /// # run_test().unwrap(); /// # } /// # /// # fn run_test() -> QueryResult<()> { - /// # use self::posts::dsl::*; + /// # use diesel::pg::sql_types::{Range, Multirange}; + /// # use diesel::dsl::upper_inf; /// # use std::collections::Bound; - /// # let conn = &mut establish_connection(); - /// # diesel::sql_query("DROP TABLE IF EXISTS posts").execute(conn).unwrap(); - /// # diesel::sql_query("CREATE TABLE posts (id SERIAL PRIMARY KEY, versions INT4RANGE NOT NULL)").execute(conn).unwrap(); - /// # - /// use diesel::dsl::upper_inf; - /// diesel::insert_into(posts) - /// .values(&[ - /// versions.eq((Bound::Included(5), Bound::Excluded(7))), - /// versions.eq((Bound::Included(5),Bound::Unbounded)), - /// ]).execute(conn)?; + /// # use diesel::sql_types::{Nullable, Integer, Array}; + /// # let connection = &mut establish_connection(); + /// let int = diesel::select(upper_inf::, _>(1..5)).get_result::>(connection)?; + /// assert_eq!(Some(false), int); + /// + /// let int = diesel::select(upper_inf::, _>(1..)).get_result::>(connection)?; + /// assert_eq!(Some(true), int); + /// + /// let int = diesel::select(upper_inf::>, _>(None::>)).get_result::>(connection)?; + /// assert_eq!(None, int); /// - /// let cool_posts = posts.select(upper_inf(versions)) - /// .load::(conn)?; - /// assert_eq!(vec![false, true], cool_posts); + /// let int = diesel::select(upper_inf::, _>(vec![5..7])).get_result::>(connection)?; + /// assert_eq!(Some(false), int); /// # Ok(()) /// # } /// ``` #[cfg(feature = "postgres_backend")] - fn upper_inf(range: T) -> Bool; + fn upper_inf(range: R) -> Nullable; } define_sql_function! { @@ -371,40 +328,63 @@ define_sql_function! { /// ```rust /// # include!("../../doctest_setup.rs"); /// # - /// # table! { - /// # posts { - /// # id -> Integer, - /// # first_versions -> Range, - /// # second_versions -> Range, - /// # } + /// # fn main() { + /// # run_test().unwrap(); /// # } /// # + /// # fn run_test() -> QueryResult<()> { + /// # use diesel::pg::sql_types::{Range, Multirange}; + /// # use diesel::dsl::range_merge; + /// # use std::collections::Bound; + /// # use diesel::sql_types::{Nullable, Integer, Array}; + /// # let connection = &mut establish_connection(); + /// let int = diesel::select(range_merge::, Range<_>, _, _>(5..11, 10..)).get_result::, Bound)>>(connection)?; + /// assert_eq!(Some((Bound::Included(5), Bound::Unbounded)), int); + /// + /// let int = diesel::select(range_merge::, Range<_>, _, _>(1..3, 7..10)).get_result::, Bound)>>(connection)?; + /// assert_eq!(Some((Bound::Included(1), Bound::Excluded(10))), int); + /// + /// let int = diesel::select(range_merge::>, Nullable>, _, _>(None::>, 7..10)).get_result::, Bound)>>(connection)?; + /// assert_eq!(None, int); + /// + /// let int = diesel::select(range_merge::>, Nullable>, _, _>(1..3, None::>)).get_result::, Bound)>>(connection)?; + /// assert_eq!(None, int); + /// # Ok(()) + /// # } + /// ``` + #[cfg(feature = "postgres_backend")] + fn range_merge + SingleValue>(lhs: R1, rhs: R2) -> Nullable>; +} + +define_sql_function! { + /// Returns the smallest range which includes all ranges in the multirange + /// + /// # Example + /// + /// ```rust + /// # include!("../../doctest_setup.rs"); + /// # /// # fn main() { /// # run_test().unwrap(); /// # } /// # /// # fn run_test() -> QueryResult<()> { - /// # use self::posts::dsl::*; + /// # use diesel::pg::sql_types::{Range, Multirange}; + /// # use diesel::dsl::multirange_merge; /// # use std::collections::Bound; - /// # let conn = &mut establish_connection(); - /// # diesel::sql_query("DROP TABLE IF EXISTS posts").execute(conn).unwrap(); - /// # diesel::sql_query("CREATE TABLE posts (id SERIAL PRIMARY KEY, first_versions INT4RANGE NOT NULL, second_versions INT4RANGE NOT NULL)").execute(conn).unwrap(); - /// # - /// use diesel::dsl::range_merge; - /// diesel::insert_into(posts) - /// .values(( - /// first_versions.eq((Bound::Included(5), Bound::Excluded(7))), - /// second_versions.eq((Bound::Included(6),Bound::Unbounded)), - /// )).execute(conn)?; + /// # use diesel::sql_types::{Nullable, Integer, Array}; + /// # let connection = &mut establish_connection(); + /// let int = diesel::select(multirange_merge::, _>(vec![1..3, 7..10])).get_result::<(Bound, Bound)>(connection)?; + /// assert_eq!((Bound::Included(1), Bound::Excluded(10)), int); /// - /// let cool_posts = posts.select(range_merge(first_versions, second_versions)) - /// .load::<(Bound, Bound)>(conn)?; - /// assert_eq!(vec![(Bound::Included(5), Bound::Unbounded)], cool_posts); + /// let int = diesel::select(multirange_merge::>, _>(None::>>)).get_result::, Bound)>>(connection)?; + /// assert_eq!(None, int); /// # Ok(()) /// # } /// ``` #[cfg(feature = "postgres_backend")] - fn range_merge>(lhs: T1, rhs: T2) -> Range; + #[sql_name = "range_merge"] + fn multirange_merge(multirange: R) -> R::Range; } define_sql_function! { diff --git a/diesel/src/pg/expression/helper_types.rs b/diesel/src/pg/expression/helper_types.rs index 105c58029a70..0cdc40f3ed2c 100644 --- a/diesel/src/pg/expression/helper_types.rs +++ b/diesel/src/pg/expression/helper_types.rs @@ -348,6 +348,11 @@ pub type upper_inf = super::functions::upper_inf, R>; #[cfg(feature = "postgres_backend")] pub type range_merge = super::functions::range_merge, SqlTypeOf, R1, R2>; +/// Return type of [`multirange_merge(multirange)`](super::functions::multirange_merge()) +#[allow(non_camel_case_types)] +#[cfg(feature = "postgres_backend")] +pub type multirange_merge = super::functions::multirange_merge, R>; + /// Return type of [`array_append(array, element)`](super::functions::array_append()) #[allow(non_camel_case_types)] #[cfg(feature = "postgres_backend")] diff --git a/diesel_derives/tests/auto_type.rs b/diesel_derives/tests/auto_type.rs index fb37ca58b625..d7879cebf9e5 100644 --- a/diesel_derives/tests/auto_type.rs +++ b/diesel_derives/tests/auto_type.rs @@ -49,6 +49,7 @@ table! { blob -> Binary, timestamp -> Timestamp, range -> Range, + multirange -> Multirange, timestamptz -> Timestamptz, } } @@ -400,6 +401,7 @@ fn postgres_functions() -> _ { lower_inf(pg_extras::range), upper_inf(pg_extras::range), range_merge(pg_extras::range, pg_extras::range), + multirange_merge(pg_extras::multirange), int4range(users::id.nullable(), users::id.nullable(), bound), int8range(users::bigint.nullable(), users::bigint.nullable(), bound), numrange(users::numeric.nullable(), users::numeric.nullable(), bound),