diff --git a/diesel/src/pg/expression/expression_methods.rs b/diesel/src/pg/expression/expression_methods.rs index 939e1403e7ca..e6ceed27fa4d 100644 --- a/diesel/src/pg/expression/expression_methods.rs +++ b/diesel/src/pg/expression/expression_methods.rs @@ -10,7 +10,8 @@ use crate::dsl; use crate::expression::grouped::Grouped; use crate::expression::operators::{Asc, Desc}; use crate::expression::{AsExpression, Expression, IntoSql, TypedExpressionType}; -use crate::sql_types::{Array, Inet, Integer, Jsonb, SqlType, Text, VarChar}; +use crate::pg::expression::expression_methods::private::BinaryOrNullableBinary; +use crate::sql_types::{Array, Binary, Inet, Integer, Jsonb, SqlType, Text, VarChar}; use crate::EscapeExpressionMethods; /// PostgreSQL specific methods which are present on all expressions. @@ -2177,9 +2178,179 @@ where { } +/// PostgreSQL specific methods present on Binary expressions. +#[cfg(feature = "postgres_backend")] +pub trait PgBinaryExpressionMethods: Expression + Sized { + /// Concatenates two PostgreSQL byte arrays using the `||` operator. + /// + /// # Example + /// + /// ```rust + /// # include!("../../doctest_setup.rs"); + /// # + /// # table! { + /// # users { + /// # id -> Integer, + /// # name -> Binary, + /// # hair_color -> Nullable, + /// # } + /// # } + /// # + /// # fn main() { + /// # use self::users::dsl::*; + /// # use diesel::insert_into; + /// # + /// # let connection = &mut connection_no_data(); + /// # diesel::sql_query("CREATE TABLE users ( + /// # id INTEGER PRIMARY KEY, + /// # name BYTEA NOT NULL, + /// # hair_color BYTEA + /// # )").execute(connection).unwrap(); + /// # + /// # insert_into(users) + /// # .values(&vec![ + /// # (id.eq(1), name.eq("Sean".as_bytes()), Some(hair_color.eq(Some("Green".as_bytes())))), + /// # (id.eq(2), name.eq("Tess".as_bytes()), None), + /// # ]) + /// # .execute(connection) + /// # .unwrap(); + /// # + /// let names = users.select(name.concat(" the Greatest".as_bytes())).load(connection); + /// let expected_names = vec![ + /// b"Sean the Greatest".to_vec(), + /// b"Tess the Greatest".to_vec() + /// ]; + /// assert_eq!(Ok(expected_names), names); + /// + /// // If the value is nullable, the output will be nullable + /// let names = users.select(hair_color.concat("ish".as_bytes())).load(connection); + /// let expected_names = vec![ + /// Some(b"Greenish".to_vec()), + /// None, + /// ]; + /// assert_eq!(Ok(expected_names), names); + /// # } + /// ``` + fn concat(self, other: T) -> dsl::ConcatBinary + where + Self::SqlType: SqlType, + T: AsExpression, + { + Grouped(ConcatBinary::new(self, other.as_expression())) + } + + /// Creates a PostgreSQL binary `LIKE` expression. + /// + /// This method is case sensitive. There is no case-insensitive + /// equivalent as of PostgreSQL 14. + /// + /// # Examples + /// + /// ```rust + /// # include!("../../doctest_setup.rs"); + /// # + /// # table! { + /// # users { + /// # id -> Integer, + /// # name -> Binary, + /// # } + /// # } + /// # + /// # fn main() { + /// # use self::users::dsl::*; + /// # use diesel::insert_into; + /// # + /// # let connection = &mut connection_no_data(); + /// # diesel::sql_query("CREATE TABLE users ( + /// # id INTEGER PRIMARY KEY, + /// # name BYTEA NOT NULL + /// # )").execute(connection).unwrap(); + /// # + /// # insert_into(users) + /// # .values(&vec![ + /// # (id.eq(1), name.eq("Sean".as_bytes())), + /// # (id.eq(2), name.eq("Tess".as_bytes())) + /// # ]) + /// # .execute(connection) + /// # .unwrap(); + /// # + /// let starts_with_s = users + /// .select(name) + /// .filter(name.like(b"S%".to_vec())) + /// .load(connection); + /// assert_eq!(Ok(vec![b"Sean".to_vec()]), starts_with_s); + /// # } + /// ``` + fn like(self, other: T) -> dsl::LikeBinary + where + Self::SqlType: SqlType, + T: AsExpression, + { + Grouped(LikeBinary::new(self, other.as_expression())) + } + + /// Creates a PostgreSQL binary `LIKE` expression. + /// + /// This method is case sensitive. There is no case-insensitive + /// equivalent as of PostgreSQL 14. + /// + /// # Examples + /// + /// ```rust + /// # include!("../../doctest_setup.rs"); + /// # + /// # table! { + /// # users { + /// # id -> Integer, + /// # name -> Binary, + /// # } + /// # } + /// # + /// # fn main() { + /// # use self::users::dsl::*; + /// # use diesel::insert_into; + /// # + /// # let connection = &mut connection_no_data(); + /// # diesel::sql_query("CREATE TABLE users ( + /// # id INTEGER PRIMARY KEY, + /// # name BYTEA NOT NULL + /// # )").execute(connection).unwrap(); + /// # + /// # insert_into(users) + /// # .values(&vec![ + /// # (id.eq(1), name.eq("Sean".as_bytes())), + /// # (id.eq(2), name.eq("Tess".as_bytes())) + /// # ]) + /// # .execute(connection) + /// # .unwrap(); + /// # + /// let starts_with_s = users + /// .select(name) + /// .filter(name.not_like(b"S%".to_vec())) + /// .load(connection); + /// assert_eq!(Ok(vec![b"Tess".to_vec()]), starts_with_s); + /// # } + /// ``` + fn not_like(self, other: T) -> dsl::NotLikeBinary + where + Self::SqlType: SqlType, + T: AsExpression, + { + Grouped(NotLikeBinary::new(self, other.as_expression())) + } +} + +#[doc(hidden)] +impl PgBinaryExpressionMethods for T +where + T: Expression, + T::SqlType: BinaryOrNullableBinary, +{ +} + mod private { use crate::sql_types::{ - Array, Cidr, Inet, Integer, Json, Jsonb, Nullable, Range, SqlType, Text, + Array, Binary, Cidr, Inet, Integer, Json, Jsonb, Nullable, Range, SqlType, Text, }; use crate::{Expression, IntoSql}; @@ -2361,4 +2532,9 @@ mod private { pub trait TextOrInteger {} impl TextOrInteger for Text {} impl TextOrInteger for Integer {} + + pub trait BinaryOrNullableBinary {} + + impl BinaryOrNullableBinary for Binary {} + impl BinaryOrNullableBinary for Nullable {} } diff --git a/diesel/src/pg/expression/helper_types.rs b/diesel/src/pg/expression/helper_types.rs index 374058ad5db9..be3a4f3f74c2 100644 --- a/diesel/src/pg/expression/helper_types.rs +++ b/diesel/src/pg/expression/helper_types.rs @@ -1,7 +1,7 @@ use crate::dsl::{AsExpr, AsExprOf, SqlTypeOf}; use crate::expression::grouped::Grouped; use crate::pg::types::sql_types::Array; -use crate::sql_types::{Inet, Integer, Jsonb, VarChar}; +use crate::sql_types::{Binary, Inet, Integer, Jsonb, VarChar}; /// The return type of [`lhs.ilike(rhs)`](super::expression_methods::PgTextExpressionMethods::ilike) #[cfg(feature = "postgres_backend")] @@ -170,3 +170,17 @@ pub type RetrieveByPathAsTextJson = #[cfg(feature = "postgres_backend")] pub type RemoveByPathFromJsonb = Grouped>>>; + +/// The return type of [`lhs.remove_by_path(rhs)`](super::expression_methods::PgBinaryExpressionMethods::concat) +#[cfg(feature = "postgres_backend")] +pub type ConcatBinary = + Grouped>>; + +/// The return type of [`lhs.remove_by_path(rhs)`](super::expression_methods::PgBinaryExpressionMethods::like) +#[cfg(feature = "postgres_backend")] +pub type LikeBinary = Grouped>>; + +/// The return type of [`lhs.remove_by_path(rhs)`](super::expression_methods::PgBinaryExpressionMethods::not_like) +#[cfg(feature = "postgres_backend")] +pub type NotLikeBinary = + Grouped>>; diff --git a/diesel/src/pg/expression/operators.rs b/diesel/src/pg/expression/operators.rs index 83db89d46eb1..ea99bf376ecf 100644 --- a/diesel/src/pg/expression/operators.rs +++ b/diesel/src/pg/expression/operators.rs @@ -5,7 +5,7 @@ use crate::pg::Pg; use crate::query_builder::update_statement::changeset::AssignmentTarget; use crate::query_builder::{AstPass, QueryFragment, QueryId}; use crate::sql_types::{ - Array, Bigint, Bool, DieselNumericOps, Inet, Integer, Jsonb, SqlType, Text, + Array, Bigint, Binary, Bool, DieselNumericOps, Inet, Integer, Jsonb, SqlType, Text, }; use crate::{Column, QueryResult}; @@ -49,6 +49,9 @@ __diesel_infix_operator!( ); infix_operator!(RetrieveByPathAsTextJson, " #>> ", Text, backend: Pg); infix_operator!(RemoveByPathFromJsonb, " #-", Jsonb, backend: Pg); +infix_operator!(ConcatBinary, " || ", Binary, backend: Pg); +infix_operator!(LikeBinary, " LIKE ", backend: Pg); +infix_operator!(NotLikeBinary, " NOT LIKE ", backend: Pg); #[derive(Debug, Clone, Copy, QueryId, DieselNumericOps, ValidGrouping)] #[doc(hidden)] diff --git a/diesel_compile_tests/Cargo.lock b/diesel_compile_tests/Cargo.lock index 010398bff0b8..502c4cfe96a6 100644 --- a/diesel_compile_tests/Cargo.lock +++ b/diesel_compile_tests/Cargo.lock @@ -51,7 +51,7 @@ dependencies = [ [[package]] name = "diesel" -version = "2.0.0" +version = "2.0.0-rc.0" dependencies = [ "bigdecimal", "bitflags", @@ -84,7 +84,7 @@ dependencies = [ [[package]] name = "diesel_derives" -version = "2.0.0" +version = "2.0.0-rc.0" dependencies = [ "proc-macro-error", "proc-macro2", diff --git a/diesel_compile_tests/tests/fail/array_expressions_must_be_same_type.stderr b/diesel_compile_tests/tests/fail/array_expressions_must_be_same_type.stderr index 19531cbf9017..a8e03f7dabb2 100644 --- a/diesel_compile_tests/tests/fail/array_expressions_must_be_same_type.stderr +++ b/diesel_compile_tests/tests/fail/array_expressions_must_be_same_type.stderr @@ -110,7 +110,7 @@ error[E0277]: the trait bound `{integer}: SelectableExpression` is <(T0, T1) as SelectableExpression> <(T0, T1, T2) as SelectableExpression> <(T0, T1, T2, T3) as SelectableExpression> - and 155 others + and 158 others = note: required because of the requirements on the impl of `SelectableExpression` for `({integer}, diesel::internal::derives::as_expression::Bound)` = note: 1 redundant requirements hidden = note: required because of the requirements on the impl of `SelectableExpression` for `diesel::pg::expression::array::ArrayLiteral<({integer}, diesel::internal::derives::as_expression::Bound), diesel::sql_types::Double>` @@ -134,7 +134,7 @@ error[E0277]: the trait bound `{integer}: ValidGrouping<()>` is not satisfied <(T0, T1) as ValidGrouping<__GroupByClause>> <(T0, T1, T2) as ValidGrouping<__GroupByClause>> <(T0, T1, T2, T3) as ValidGrouping<__GroupByClause>> - and 140 others + and 143 others = note: required because of the requirements on the impl of `ValidGrouping<()>` for `({integer}, diesel::internal::derives::as_expression::Bound)` = note: 1 redundant requirements hidden = note: required because of the requirements on the impl of `ValidGrouping<()>` for `diesel::pg::expression::array::ArrayLiteral<({integer}, diesel::internal::derives::as_expression::Bound), diesel::sql_types::Double>` @@ -152,7 +152,7 @@ error[E0277]: the trait bound `{integer}: SelectableExpression` is <(T0, T1) as SelectableExpression> <(T0, T1, T2) as SelectableExpression> <(T0, T1, T2, T3) as SelectableExpression> - and 155 others + and 158 others = note: required because of the requirements on the impl of `SelectableExpression` for `({integer}, diesel::internal::derives::as_expression::Bound)` = note: 1 redundant requirements hidden = note: required because of the requirements on the impl of `SelectableExpression` for `diesel::pg::expression::array::ArrayLiteral<({integer}, diesel::internal::derives::as_expression::Bound), diesel::sql_types::Double>` @@ -171,7 +171,7 @@ error[E0277]: the trait bound `{integer}: ValidGrouping<()>` is not satisfied <(T0, T1) as ValidGrouping<__GroupByClause>> <(T0, T1, T2) as ValidGrouping<__GroupByClause>> <(T0, T1, T2, T3) as ValidGrouping<__GroupByClause>> - and 140 others + and 143 others = note: required because of the requirements on the impl of `ValidGrouping<()>` for `({integer}, diesel::internal::derives::as_expression::Bound)` = note: 1 redundant requirements hidden = note: required because of the requirements on the impl of `ValidGrouping<()>` for `diesel::pg::expression::array::ArrayLiteral<({integer}, diesel::internal::derives::as_expression::Bound), diesel::sql_types::Double>` @@ -189,7 +189,7 @@ error[E0277]: the trait bound `{integer}: QueryFragment` is not satisfied <() as QueryFragment> <(T0, T1) as QueryFragment<__DB>> <(T0, T1, T2) as QueryFragment<__DB>> - and 269 others + and 272 others = note: required because of the requirements on the impl of `QueryFragment` for `({integer}, diesel::internal::derives::as_expression::Bound)` = note: 3 redundant requirements hidden = note: required because of the requirements on the impl of `QueryFragment` for `SelectStatement), diesel::sql_types::Double>>>` @@ -206,7 +206,7 @@ error[E0277]: the trait bound `{integer}: QueryId` is not satisfied <() as QueryId> <(T0, T1) as QueryId> <(T0, T1, T2) as QueryId> - and 231 others + and 234 others = note: required because of the requirements on the impl of `QueryId` for `({integer}, diesel::internal::derives::as_expression::Bound)` = note: 3 redundant requirements hidden = note: required because of the requirements on the impl of `QueryId` for `SelectStatement), diesel::sql_types::Double>>>` @@ -228,6 +228,6 @@ error[E0277]: the trait bound `{integer}: diesel::Expression` is not satisfied <(T0, T1) as diesel::Expression> <(T0, T1, T2) as diesel::Expression> <(T0, T1, T2, T3) as diesel::Expression> - and 120 others + and 123 others = note: required because of the requirements on the impl of `AsExpression` for `{integer}` = note: required because of the requirements on the impl of `AsExpressionList` for `({integer}, f64)` diff --git a/diesel_compile_tests/tests/fail/find_requires_correct_type.stderr b/diesel_compile_tests/tests/fail/find_requires_correct_type.stderr index 474029af0af7..2a109d996122 100644 --- a/diesel_compile_tests/tests/fail/find_requires_correct_type.stderr +++ b/diesel_compile_tests/tests/fail/find_requires_correct_type.stderr @@ -32,7 +32,7 @@ error[E0277]: the trait bound `{integer}: diesel::Expression` is not satisfied <(T0, T1) as diesel::Expression> <(T0, T1, T2) as diesel::Expression> <(T0, T1, T2, T3) as diesel::Expression> - and 124 others + and 127 others = note: required because of the requirements on the impl of `diesel::Expression` for `diesel::expression::operators::Eq` = note: required because of the requirements on the impl of `FilterDsl>>` for `SelectStatement>` @@ -47,7 +47,7 @@ error[E0277]: the trait bound `{integer}: ValidGrouping<()>` is not satisfied <(T0, T1) as ValidGrouping<__GroupByClause>> <(T0, T1, T2) as ValidGrouping<__GroupByClause>> <(T0, T1, T2, T3) as ValidGrouping<__GroupByClause>> - and 146 others + and 149 others = note: required because of the requirements on the impl of `ValidGrouping<()>` for `diesel::expression::operators::Eq` = note: required because of the requirements on the impl of `NonAggregate` for `diesel::expression::grouped::Grouped>` = note: required because of the requirements on the impl of `FilterDsl>>` for `SelectStatement>` diff --git a/diesel_compile_tests/tests/fail/insert_requires_value_of_same_type_as_column.stderr b/diesel_compile_tests/tests/fail/insert_requires_value_of_same_type_as_column.stderr index e5677bc9515c..cab95e1a46c7 100644 --- a/diesel_compile_tests/tests/fail/insert_requires_value_of_same_type_as_column.stderr +++ b/diesel_compile_tests/tests/fail/insert_requires_value_of_same_type_as_column.stderr @@ -9,5 +9,5 @@ error[E0277]: the trait bound `{integer}: diesel::Expression` is not satisfied <(T0, T1) as diesel::Expression> <(T0, T1, T2) as diesel::Expression> <(T0, T1, T2, T3) as diesel::Expression> - and 124 others + and 127 others = note: required because of the requirements on the impl of `AsExpression` for `{integer}` diff --git a/diesel_compile_tests/tests/fail/pg_specific_binary_expressions_only_usable_with_pg.rs b/diesel_compile_tests/tests/fail/pg_specific_binary_expressions_only_usable_with_pg.rs new file mode 100644 index 000000000000..9154c1de34fa --- /dev/null +++ b/diesel_compile_tests/tests/fail/pg_specific_binary_expressions_only_usable_with_pg.rs @@ -0,0 +1,30 @@ +extern crate diesel; + +use diesel::prelude::*; + +table! { + users { + id -> Integer, + name -> Binary, + } +} + +fn main() { + use self::users::dsl::*; + + let mut connection = SqliteConnection::establish("").unwrap(); + + users + .select(name.concat(b"foo".to_vec())) + .filter(name.like(b"bar".to_vec())) + .filter(name.not_like(b"baz".to_vec())) + .get_result::>(&mut connection).unwrap(); + + let mut connection = MysqlConnection::establish("").unwrap(); + + users + .select(name.concat(b"foo".to_vec())) + .filter(name.like(b"bar".to_vec())) + .filter(name.not_like(b"baz".to_vec())) + .get_result::>(&mut connection).unwrap(); +} diff --git a/diesel_compile_tests/tests/fail/pg_specific_binary_expressions_only_usable_with_pg.stderr b/diesel_compile_tests/tests/fail/pg_specific_binary_expressions_only_usable_with_pg.stderr new file mode 100644 index 000000000000..329e659f6af0 --- /dev/null +++ b/diesel_compile_tests/tests/fail/pg_specific_binary_expressions_only_usable_with_pg.stderr @@ -0,0 +1,15 @@ +error[E0271]: type mismatch resolving `::Backend == Pg` + --> tests/fail/pg_specific_binary_expressions_only_usable_with_pg.rs:21:10 + | +21 | .get_result::>(&mut connection).unwrap(); + | ^^^^^^^^^^ expected struct `Sqlite`, found struct `Pg` + | + = note: required because of the requirements on the impl of `LoadQuery<'_, diesel::SqliteConnection, Vec>` for `SelectStatement, diesel::query_builder::select_clause::SelectClause>>>>, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause>>>, diesel::expression::grouped::Grouped>>>>>>>` + +error[E0271]: type mismatch resolving `::Backend == Pg` + --> tests/fail/pg_specific_binary_expressions_only_usable_with_pg.rs:29:10 + | +29 | .get_result::>(&mut connection).unwrap(); + | ^^^^^^^^^^ expected struct `Mysql`, found struct `Pg` + | + = note: required because of the requirements on the impl of `LoadQuery<'_, diesel::MysqlConnection, Vec>` for `SelectStatement, diesel::query_builder::select_clause::SelectClause>>>>, diesel::query_builder::distinct_clause::NoDistinctClause, diesel::query_builder::where_clause::WhereClause>>>, diesel::expression::grouped::Grouped>>>>>>>`