Skip to content

Commit 45276ae

Browse files
authored
Merge pull request diesel-rs#3093 from Justice4Joffrey/oc-pg-updating-array
Enable expressions on the left-hand side of update.set()
2 parents f344a1f + 9dbb278 commit 45276ae

16 files changed

Lines changed: 145 additions & 34 deletions

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ Increasing the minimal supported Rust version will always be coupled at least wi
8484

8585
* Added support for the usage of slices of references with `belonging_to` from `BelongingToDsl`
8686

87+
* Added support for updating individual array elements `UPDATE table SET array_column[1] = true`
88+
8789
### Removed
8890

8991
* All previously deprecated items have been removed.

diesel/src/pg/expression/operators.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
use crate::backend::{Backend, DieselReserveSpecialization};
12
use crate::expression::expression_types::NotSelectable;
23
use crate::expression::{TypedExpressionType, ValidGrouping};
34
use crate::pg::Pg;
4-
use crate::query_builder::{QueryFragment, QueryId};
5+
use crate::query_builder::update_statement::changeset::AssignmentTarget;
6+
use crate::query_builder::{AstPass, QueryFragment, QueryId};
57
use crate::sql_types::{
68
Array, Bigint, Bool, DieselNumericOps, Inet, Integer, Jsonb, SqlType, Text,
79
};
10+
use crate::{Column, QueryResult};
811

912
__diesel_infix_operator!(IsDistinctFrom, " IS DISTINCT FROM ", ConstantNullability Bool, backend: Pg);
1013
__diesel_infix_operator!(IsNotDistinctFrom, " IS NOT DISTINCT FROM ", ConstantNullability Bool, backend: Pg);
@@ -90,3 +93,31 @@ where
9093
Ok(())
9194
}
9295
}
96+
97+
impl<L, R> AssignmentTarget for ArrayIndex<L, R>
98+
where
99+
L: Column,
100+
{
101+
type Table = <L as Column>::Table;
102+
type QueryAstNode = ArrayIndex<UncorrelatedColumn<L>, R>;
103+
104+
fn into_target(self) -> Self::QueryAstNode {
105+
ArrayIndex::new(UncorrelatedColumn(self.array_expr), self.index_expr)
106+
}
107+
}
108+
109+
/// Signifies that this column should be rendered without its 'correlation'
110+
/// (i.e. table name prefix). For update statements, fully qualified column
111+
/// names aren't allowed.
112+
#[derive(Debug)]
113+
pub struct UncorrelatedColumn<C>(C);
114+
115+
impl<C, DB> QueryFragment<DB> for UncorrelatedColumn<C>
116+
where
117+
C: Column,
118+
DB: Backend + DieselReserveSpecialization,
119+
{
120+
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, DB>) -> QueryResult<()> {
121+
out.push_identifier(C::NAME)
122+
}
123+
}

diesel/src/query_builder/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ mod returning_clause;
3030
pub(crate) mod select_clause;
3131
pub(crate) mod select_statement;
3232
mod sql_query;
33-
mod update_statement;
33+
pub(crate) mod update_statement;
3434
pub(crate) mod upsert;
3535
mod where_clause;
3636

diesel/src/query_builder/update_statement/changeset.rs

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
use crate::backend::Backend;
1+
use crate::backend::{Backend, DieselReserveSpecialization};
22
use crate::expression::grouped::Grouped;
33
use crate::expression::operators::Eq;
44
use crate::expression::AppearsOnTable;
55
use crate::query_builder::*;
66
use crate::query_source::{Column, QuerySource};
77
use crate::result::QueryResult;
8+
use crate::Table;
89

910
/// Types which can be passed to
1011
/// [`update.set`](UpdateStatement::set()).
@@ -40,15 +41,15 @@ impl<T: AsChangeset> AsChangeset for Option<T> {
4041

4142
impl<Left, Right> AsChangeset for Eq<Left, Right>
4243
where
43-
Left: Column,
44+
Left: AssignmentTarget,
4445
Right: AppearsOnTable<Left::Table>,
4546
{
4647
type Target = Left::Table;
47-
type Changeset = Assign<Left, Right>;
48+
type Changeset = Assign<<Left as AssignmentTarget>::QueryAstNode, Right>;
4849

4950
fn as_changeset(self) -> Self::Changeset {
5051
Assign {
51-
_column: self.left,
52+
target: self.left.into_target(),
5253
expr: self.right,
5354
}
5455
}
@@ -68,20 +69,63 @@ where
6869
}
6970

7071
#[derive(Debug, Clone, Copy, QueryId)]
71-
pub struct Assign<Col, Expr> {
72-
_column: Col,
72+
pub struct Assign<Target, Expr> {
73+
target: Target,
7374
expr: Expr,
7475
}
7576

7677
impl<T, U, DB> QueryFragment<DB> for Assign<T, U>
7778
where
7879
DB: Backend,
79-
T: Column,
80+
T: QueryFragment<DB>,
8081
U: QueryFragment<DB>,
8182
{
8283
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, DB>) -> QueryResult<()> {
83-
out.push_identifier(T::NAME)?;
84+
QueryFragment::walk_ast(&self.target, out.reborrow())?;
8485
out.push_sql(" = ");
85-
QueryFragment::walk_ast(&self.expr, out)
86+
QueryFragment::walk_ast(&self.expr, out.reborrow())
87+
}
88+
}
89+
90+
/// Represents the left hand side of an assignment expression for an
91+
/// assignment in [AsChangeset]. The vast majority of the time, this will
92+
/// be a [Column]. However, in certain database backends, it's possible to
93+
/// assign to an expression. For example, in Postgres, it's possible to
94+
/// "UPDATE TABLE SET array_column\[1\] = 'foo'".
95+
pub trait AssignmentTarget {
96+
/// Table the assignment is to
97+
type Table: Table;
98+
/// A wrapper around a type to assign to (this wrapper should implement
99+
/// [QueryFragment]).
100+
type QueryAstNode;
101+
102+
/// Move this in to the AST node which should implement [QueryFragment].
103+
fn into_target(self) -> Self::QueryAstNode;
104+
}
105+
106+
/// Represents a `Column` as an `AssignmentTarget`. The vast majority of
107+
/// targets in an update statement will be `Column`s.
108+
#[derive(Debug, Clone, Copy)]
109+
pub struct ColumnWrapperForUpdate<C>(pub C);
110+
111+
impl<DB, C> QueryFragment<DB> for ColumnWrapperForUpdate<C>
112+
where
113+
DB: Backend + DieselReserveSpecialization,
114+
C: Column,
115+
{
116+
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, DB>) -> QueryResult<()> {
117+
out.push_identifier(C::NAME)
118+
}
119+
}
120+
121+
impl<C> AssignmentTarget for C
122+
where
123+
C: Column,
124+
{
125+
type Table = C::Table;
126+
type QueryAstNode = ColumnWrapperForUpdate<C>;
127+
128+
fn into_target(self) -> Self::QueryAstNode {
129+
ColumnWrapperForUpdate(self)
86130
}
87131
}

diesel/src/query_builder/update_statement/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
pub(super) mod changeset;
1+
pub(crate) mod changeset;
22
pub(super) mod target;
33

44
use self::target::UpdateTarget;

diesel_compile_tests/Cargo.lock

Lines changed: 8 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

diesel_compile_tests/tests/fail/array_expressions_must_be_same_type.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ error[E0277]: the trait bound `{integer}: QueryFragment<Pg>` is not satisfied
189189
<() as QueryFragment<DB>>
190190
<(T0, T1) as QueryFragment<__DB>>
191191
<(T0, T1, T2) as QueryFragment<__DB>>
192-
and 267 others
192+
and 269 others
193193
= note: required because of the requirements on the impl of `QueryFragment<Pg>` for `({integer}, diesel::internal::derives::as_expression::Bound<diesel::sql_types::Double, f64>)`
194194
= note: 3 redundant requirements hidden
195195
= note: required because of the requirements on the impl of `QueryFragment<Pg>` for `SelectStatement<NoFromClause, diesel::query_builder::select_clause::SelectClause<diesel::pg::expression::array::ArrayLiteral<({integer}, diesel::internal::derives::as_expression::Bound<diesel::sql_types::Double, f64>), diesel::sql_types::Double>>>`

diesel_compile_tests/tests/fail/custom_returning_requires_nonaggregate.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ error[E0277]: the trait bound `diesel::expression::is_aggregate::Yes: MixedAggre
77
= help: the following implementations were found:
88
<diesel::expression::is_aggregate::Yes as MixedAggregates<diesel::expression::is_aggregate::Never>>
99
<diesel::expression::is_aggregate::Yes as MixedAggregates<diesel::expression::is_aggregate::Yes>>
10-
= note: required because of the requirements on the impl of `Query` for `UpdateStatement<users::table, diesel::query_builder::where_clause::WhereClause<diesel::expression::grouped::Grouped<diesel::expression::operators::Eq<columns::id, diesel::internal::derives::as_expression::Bound<diesel::sql_types::Integer, i32>>>>, diesel::query_builder::update_statement::changeset::Assign<columns::name, diesel::internal::derives::as_expression::Bound<diesel::sql_types::Text, &str>>, diesel::query_builder::returning_clause::ReturningClause<diesel::expression::count::count::count<diesel::sql_types::Integer, columns::id>>>`
10+
= note: required because of the requirements on the impl of `Query` for `UpdateStatement<users::table, diesel::query_builder::where_clause::WhereClause<diesel::expression::grouped::Grouped<diesel::expression::operators::Eq<columns::id, diesel::internal::derives::as_expression::Bound<diesel::sql_types::Integer, i32>>>>, diesel::query_builder::update_statement::changeset::Assign<diesel::query_builder::update_statement::changeset::ColumnWrapperForUpdate<columns::name>, diesel::internal::derives::as_expression::Bound<diesel::sql_types::Text, &str>>, diesel::query_builder::returning_clause::ReturningClause<diesel::expression::count::count::count<diesel::sql_types::Integer, columns::id>>>`
1111

1212
error[E0277]: the trait bound `diesel::expression::is_aggregate::No: MixedAggregates<diesel::expression::is_aggregate::Yes>` is not satisfied
1313
--> tests/fail/custom_returning_requires_nonaggregate.rs:27:53

diesel_compile_tests/tests/fail/custom_returning_requires_selectable_expression.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ error[E0277]: the trait bound `bad::columns::age: SelectableExpression<users::ta
1010
<bad::columns::age as SelectableExpression<SelectStatement<FromClause<From>>>>
1111
<bad::columns::age as SelectableExpression<bad::table>>
1212
and 2 others
13-
= note: required because of the requirements on the impl of `Query` for `UpdateStatement<users::table, diesel::query_builder::where_clause::WhereClause<diesel::expression::grouped::Grouped<diesel::expression::operators::Eq<users::columns::id, diesel::internal::derives::as_expression::Bound<diesel::sql_types::Integer, i32>>>>, diesel::query_builder::update_statement::changeset::Assign<users::columns::name, diesel::internal::derives::as_expression::Bound<diesel::sql_types::Text, &str>>, diesel::query_builder::returning_clause::ReturningClause<bad::columns::age>>`
13+
= note: required because of the requirements on the impl of `Query` for `UpdateStatement<users::table, diesel::query_builder::where_clause::WhereClause<diesel::expression::grouped::Grouped<diesel::expression::operators::Eq<users::columns::id, diesel::internal::derives::as_expression::Bound<diesel::sql_types::Integer, i32>>>>, diesel::query_builder::update_statement::changeset::Assign<diesel::query_builder::update_statement::changeset::ColumnWrapperForUpdate<users::columns::name>, diesel::internal::derives::as_expression::Bound<diesel::sql_types::Text, &str>>, diesel::query_builder::returning_clause::ReturningClause<bad::columns::age>>`
1414

1515
error[E0277]: the trait bound `bad::columns::age: SelectableExpression<users::table>` is not satisfied
1616
--> tests/fail/custom_returning_requires_selectable_expression.rs:33:63

diesel_compile_tests/tests/fail/returning_cannot_be_called_twice.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ error[E0599]: no method named `returning` found for struct `InsertStatement<user
1010
27 | query.returning(name);
1111
| ^^^^^^^^^ private field, not a method
1212

13-
error[E0599]: no method named `returning` found for struct `UpdateStatement<users::table, diesel::query_builder::where_clause::NoWhereClause, diesel::query_builder::update_statement::changeset::Assign<columns::name, diesel::internal::derives::as_expression::Bound<diesel::sql_types::Text, &str>>, diesel::query_builder::returning_clause::ReturningClause<columns::id>>` in the current scope
13+
error[E0599]: no method named `returning` found for struct `UpdateStatement<users::table, diesel::query_builder::where_clause::NoWhereClause, diesel::query_builder::update_statement::changeset::Assign<diesel::query_builder::update_statement::changeset::ColumnWrapperForUpdate<columns::name>, diesel::internal::derives::as_expression::Bound<diesel::sql_types::Text, &str>>, diesel::query_builder::returning_clause::ReturningClause<columns::id>>` in the current scope
1414
--> tests/fail/returning_cannot_be_called_twice.rs:30:11
1515
|
1616
30 | query.returning(name);

0 commit comments

Comments
 (0)