diesel/pg/returning/old_impl.rs
1//! `RETURNING old.col` support for PostgreSQL 18 and later.
2
3use crate::backend::{Backend, sql_dialect};
4use crate::expression::nullable::Nullable;
5use crate::expression::subselect::SubselectGroupBy;
6use crate::expression::{
7 AppearsOnTable, Expression, SelectableExpression, ValidGrouping, is_aggregate,
8};
9use crate::query_builder::returning::{
10 InsertStmtWithOnConflictDoUpdate, OldIdent, ReturningQuerySource, UpdateStmt,
11};
12use crate::query_builder::{AstPass, QueryFragment, QueryId};
13use crate::query_source::{AppearsInFromClause, Column};
14use crate::result::QueryResult;
15
16/// Wraps a column to refer to its pre-modification value in the `RETURNING`
17/// clause of a PostgreSQL `UPDATE` or `INSERT ... ON CONFLICT ... DO UPDATE`
18/// statement.
19///
20/// This is the type returned by [`old()`](old()).
21#[derive(Debug, Clone, Copy)]
22pub struct Old<C> {
23 _column: C,
24}
25
26impl<C> Old<C> {
27 pub(crate) fn new(c: C) -> Self {
28 Old { _column: c }
29 }
30}
31
32/// Refer to the pre-modification value of `col` in a PostgreSQL `RETURNING`
33/// clause.
34///
35/// This corresponds to the SQL `RETURNING old.col` syntax introduced in
36/// PostgreSQL 18.
37///
38/// # Requires PostgreSQL 18 or newer
39///
40/// Diesel emits `old.col` in the SQL it sends to the database. Earlier
41/// versions of PostgreSQL will reject the query at execution time.
42///
43/// # Statement compatibility
44///
45/// `old(col)` is valid inside the `RETURNING` clause of:
46///
47/// * an `UPDATE` statement, where it has the same Rust SQL type as `col`
48/// (since every returned row necessarily came from a pre-existing row).
49/// * an `INSERT ... ON CONFLICT ... DO UPDATE` statement, **but only when
50/// wrapped in [`.nullable()`]**: rows that were freshly inserted (rather
51/// than updated) have no `old` row, and `old.col` is `NULL` for them, so
52/// for type-safe deserialization you must opt into a nullable Rust SQL
53/// type. Writing `old(col)` directly (without `.nullable()`) in this
54/// context is rejected at compile time.
55///
56/// Use of `old(col)` in plain `INSERT` (without `ON CONFLICT ... DO UPDATE`)
57/// or `DELETE` `RETURNING` is rejected at compile time, because it is not useful
58/// there. (Note that `ON CONFLICT DO NOTHING` never returns untouched rows.)
59///
60/// [`.nullable()`]: crate::NullableExpressionMethods::nullable
61///
62/// # Example
63///
64/// ```rust
65/// # include!("../../doctest_setup.rs");
66/// #
67/// # #[cfg(feature = "postgres")]
68/// # fn main() {
69/// # use schema::users::dsl::*;
70/// # use diesel::pg::returning::old;
71/// # let connection = &mut establish_connection();
72/// # // `RETURNING old.col` requires PostgreSQL 18+
73/// # let pg_version: i32 = diesel::dsl::sql::<diesel::sql_types::Integer>(
74/// # "SELECT current_setting('server_version_num')::int",
75/// # ).get_result(connection).unwrap();
76/// # if pg_version < 180000 { return; }
77/// let was_and_now = diesel::update(users.find(1))
78/// .set(name.eq("Updated"))
79/// .returning((old(name), name))
80/// .get_result::<(String, String)>(connection);
81/// assert_eq!(Ok(("Sean".to_string(), "Updated".to_string())), was_and_now);
82/// # }
83/// # #[cfg(not(feature = "postgres"))]
84/// # fn main() {}
85/// ```
86pub fn old<C: Column>(col: C) -> Old<C> {
87 Old::new(col)
88}
89
90impl<C> QueryId for Old<C> {
91 type QueryId = ();
92
93 const HAS_STATIC_QUERY_ID: bool = false;
94}
95
96impl<C> Expression for Old<C>
97where
98 C: Column + Expression,
99{
100 type SqlType = <C as Expression>::SqlType;
101}
102
103impl<C> ValidGrouping<()> for Old<C>
104where
105 C: Column,
106{
107 type IsAggregate = is_aggregate::No;
108}
109
110// `old` always names the row of the outer statement
111impl<C, GB, From> ValidGrouping<SubselectGroupBy<GB, From>> for Old<C>
112where
113 C: Column,
114 Self: ValidGrouping<GB>,
115{
116 type IsAggregate = <Self as ValidGrouping<GB>>::IsAggregate;
117}
118
119// `Old<C>` is selectable on a `RETURNING` clause whose statement-kind marker
120// is `UpdateStmt`. It is deliberately *not* selectable on
121// `ReturningQuerySource<InsertStmtWithOnConflictDoUpdate, _>` directly — only
122// `Nullable<Old<C>>` is, via the existing `Nullable` machinery and the
123// `ToInnerJoin` mapping in `returning_query_source` (which makes
124// `InsertStmtWithOnConflictDoUpdate`'s inner-join "fall back" to `UpdateStmt`).
125// That's how we force users to write `old(col).nullable()` in an
126// `INSERT ... ON CONFLICT ... DO UPDATE RETURNING` and reject `old(col)`
127// alone at compile time.
128impl<C, QS> AppearsOnTable<QS> for Old<C>
129where
130 C: Column,
131 Self: Expression,
132 // Check that we have exactly one `old` identifier in the `RETURNING` clause.
133 QS: AppearsInFromClause<OldIdent, Count = crate::query_source::Once>,
134 // Check that the `old` identifier relates the table of that column.
135 QS: AppearsInFromClause<
136 ReturningQuerySource<OldIdent, C::Table>,
137 Count = crate::query_source::Once,
138 >,
139{
140}
141
142// We intentionally did not add implementations for use of `old(col)` in plain `INSERT`
143// (without `ON CONFLICT ... DO UPDATE`) or `DELETE` `RETURNING`, because it is not useful
144// there as one can just use `RETURNING column_name`. (`ON CONFLICT DO NOTHING` never returns
145// untouched rows, so allowing `ON CONFLICT DO NOTHING ... RETURNING` might be misleading to
146// users on that regard - I, the author, have seen bugs caused by not being aware of this
147// PG behavior.)
148
149impl<C> SelectableExpression<ReturningQuerySource<UpdateStmt, C::Table>> for Old<C>
150where
151 C: Column,
152 Self: AppearsOnTable<ReturningQuerySource<UpdateStmt, C::Table>>,
153{
154}
155
156impl<C> SelectableExpression<ReturningQuerySource<InsertStmtWithOnConflictDoUpdate, C::Table>>
157 for Nullable<Old<C>>
158where
159 C: Column,
160 Self: AppearsOnTable<ReturningQuerySource<InsertStmtWithOnConflictDoUpdate, C::Table>>,
161{
162}
163
164impl<C, DB> QueryFragment<DB> for Old<C>
165where
166 DB: Backend,
167 Self: QueryFragment<DB, DB::ReturningClause>,
168{
169 fn walk_ast<'b>(&'b self, pass: AstPass<'_, 'b, DB>) -> QueryResult<()> {
170 <Self as QueryFragment<DB, DB::ReturningClause>>::walk_ast(self, pass)
171 }
172}
173
174impl<C, DB> QueryFragment<DB, sql_dialect::returning_clause::PgLikeReturningClause> for Old<C>
175where
176 DB: Backend<ReturningClause = sql_dialect::returning_clause::PgLikeReturningClause>,
177 C: Column,
178{
179 fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, DB>) -> QueryResult<()> {
180 out.push_sql("old.");
181 out.push_identifier(C::NAME)?;
182 Ok(())
183 }
184}
185
186pub use return_type_helpers_reexported::*;
187
188pub(crate) mod return_type_helpers_reexported {
189 use super::Old;
190
191 /// The return type of [`old(col)`](super::old()).
192 #[allow(non_camel_case_types)]
193 pub type old<C> = Old<C>;
194}