Skip to main content

drizzle_postgres/values/
update.rs

1//! Update value types for `PostgreSQL`.
2//!
3//! Each field in an UPDATE operation can be skipped (left unchanged),
4//! set to NULL, or set to a value or expression.
5
6use super::PostgresValue;
7use super::insert::ValueWrapper;
8use crate::prelude::*;
9use crate::types::Any;
10use drizzle_core::expr::{
11    AcceptsNullability, ColumnBinOp, ColumnNeg, Excluded, Expr, Null, Nullability, SQLExpr, Scalar,
12};
13use drizzle_core::{
14    PostgresDialect, SQLColumnInfo, ToSQL, TypedPlaceholder,
15    param::Param,
16    placeholder::Placeholder,
17    sql::SQL,
18    sql::SQLChunk,
19    traits::SQLParam,
20    types::{Assignable, DataType},
21};
22
23#[cfg(feature = "uuid")]
24use uuid::Uuid;
25
26/// One column in an `UPDATE ... SET`: skipped, `NULL`, or a value or SQL expression.
27///
28/// Generated `Update*` models store their fields as this type. `T` is the
29/// column's Rust type; `Target` and `TargetNull` are the column's SQL type
30/// and nullability, used to check what can be assigned.
31#[derive(Debug, Clone, Default)]
32#[allow(clippy::large_enum_variant)]
33pub enum PostgresUpdateValue<
34    'a,
35    V: SQLParam,
36    T,
37    Target: DataType = Any,
38    TargetNull: Nullability = Null,
39> {
40    /// Leave the column out of `SET`, so it keeps its current value.
41    #[default]
42    Skip,
43    /// Set the column to `NULL`.
44    Null,
45    /// Set the column to a bound value, placeholder or SQL expression.
46    Value(ValueWrapper<'a, V, (T, Target, TargetNull)>),
47}
48
49impl<V: SQLParam, T, Target: DataType, TargetNull: Nullability>
50    PostgresUpdateValue<'_, V, T, Target, TargetNull>
51{
52    /// Returns `true` if the column is left out of `SET`.
53    pub const fn is_skip(&self) -> bool {
54        matches!(self, Self::Skip)
55    }
56}
57
58/// Converts any value that converts to a [`PostgresValue`].
59///
60/// # Panics
61///
62/// Panics when the value fails to convert (a JSON payload that fails to
63/// serialize, for example), rather than storing NULL in its place.
64impl<'a, T, Target, TargetNull> From<T>
65    for PostgresUpdateValue<'a, PostgresValue<'a>, T, Target, TargetNull>
66where
67    T: TryInto<PostgresValue<'a>>,
68    Target: DataType,
69    TargetNull: Nullability,
70{
71    fn from(value: T) -> Self {
72        // A failed conversion is a caller bug; storing NULL in its place would
73        // lose the value silently.
74        let sql = SQL::from(
75            TryInto::<PostgresValue<'a>>::try_into(value).unwrap_or_else(|_| {
76                panic!(
77                    "could not convert a `{}` to a PostgreSQL value",
78                    core::any::type_name::<T>()
79                )
80            }),
81        );
82        PostgresUpdateValue::Value(ValueWrapper::<PostgresValue<'a>, T>::new(sql))
83    }
84}
85
86// Specific conversion for &str to String UpdateValue
87impl<'a, Target, TargetNull> From<&str>
88    for PostgresUpdateValue<'a, PostgresValue<'a>, String, Target, TargetNull>
89where
90    Target: DataType,
91    TargetNull: Nullability,
92{
93    fn from(value: &str) -> Self {
94        let postgres_value = SQL::param(Cow::Owned(PostgresValue::from(value.to_string())));
95        PostgresUpdateValue::Value(ValueWrapper::<PostgresValue<'a>, String>::new(
96            postgres_value,
97        ))
98    }
99}
100
101// Placeholder conversion
102impl<'a, T, Target, TargetNull> From<Placeholder>
103    for PostgresUpdateValue<'a, PostgresValue<'a>, T, Target, TargetNull>
104where
105    Target: DataType,
106    TargetNull: Nullability,
107{
108    fn from(placeholder: Placeholder) -> Self {
109        let chunk = SQLChunk::Param(Param {
110            placeholder,
111            value: None,
112        });
113        PostgresUpdateValue::Value(ValueWrapper::<PostgresValue<'a>, T>::new(
114            core::iter::once(chunk).collect(),
115        ))
116    }
117}
118
119impl<'a, M, N, T, Target, TargetNull> From<TypedPlaceholder<M, N>>
120    for PostgresUpdateValue<'a, PostgresValue<'a>, T, Target, TargetNull>
121where
122    M: DataType,
123    N: Nullability,
124    Target: DataType + Assignable<M>,
125    TargetNull: Nullability + AcceptsNullability<N>,
126{
127    fn from(typed: TypedPlaceholder<M, N>) -> Self {
128        Placeholder::from(typed).into()
129    }
130}
131
132// Excluded column reference conversion (for ON CONFLICT DO UPDATE SET)
133impl<'a, C, T, Target, TargetNull, Actual, ActualNull> From<Excluded<C>>
134    for PostgresUpdateValue<'a, PostgresValue<'a>, T, Target, TargetNull>
135where
136    C: SQLColumnInfo + Expr<'a, PostgresValue<'a>, SQLType = Actual, Nullable = ActualNull>,
137    Target: DataType + Assignable<Actual>,
138    TargetNull: Nullability + AcceptsNullability<ActualNull>,
139    Actual: DataType,
140    ActualNull: Nullability,
141{
142    fn from(excluded: Excluded<C>) -> Self {
143        use drizzle_core::ToSQL;
144        let sql = excluded.to_sql();
145        PostgresUpdateValue::Value(ValueWrapper::<PostgresValue<'a>, T>::new(sql))
146    }
147}
148
149// UUID conversion for String UpdateValue (for text columns)
150#[cfg(feature = "uuid")]
151impl<'a, Target, TargetNull> From<Uuid>
152    for PostgresUpdateValue<'a, PostgresValue<'a>, String, Target, TargetNull>
153where
154    Target: DataType,
155    TargetNull: Nullability,
156{
157    fn from(value: Uuid) -> Self {
158        let postgres_value = PostgresValue::Uuid(value);
159        let sql = SQL::param(postgres_value);
160        PostgresUpdateValue::Value(ValueWrapper::<PostgresValue<'a>, String>::new(sql))
161    }
162}
163
164#[cfg(feature = "uuid")]
165impl<'a, Target, TargetNull> From<&'a Uuid>
166    for PostgresUpdateValue<'a, PostgresValue<'a>, String, Target, TargetNull>
167where
168    Target: DataType,
169    TargetNull: Nullability,
170{
171    fn from(value: &'a Uuid) -> Self {
172        let postgres_value = PostgresValue::Uuid(*value);
173        let sql = SQL::param(postgres_value);
174        PostgresUpdateValue::Value(ValueWrapper::<PostgresValue<'a>, String>::new(sql))
175    }
176}
177
178impl<'a, T, Target, TargetNull, Actual, ActualNull, Sources>
179    From<SQLExpr<'a, PostgresValue<'a>, Actual, ActualNull, Scalar, Sources>>
180    for PostgresUpdateValue<'a, PostgresValue<'a>, T, Target, TargetNull>
181where
182    Target: DataType + Assignable<Actual>,
183    TargetNull: Nullability + AcceptsNullability<ActualNull>,
184    Actual: DataType,
185    ActualNull: Nullability,
186{
187    fn from(value: SQLExpr<'a, PostgresValue<'a>, Actual, ActualNull, Scalar, Sources>) -> Self {
188        Self::Value(ValueWrapper::<PostgresValue<'a>, T>::new(
189            value.into_expr_sql(),
190        ))
191    }
192}
193
194impl<'a, T, Target, TargetNull, L, R, Op, Actual, ActualNull>
195    From<ColumnBinOp<L, R, Op, PostgresDialect, Actual, ActualNull>>
196    for PostgresUpdateValue<'a, PostgresValue<'a>, T, Target, TargetNull>
197where
198    Target: DataType + Assignable<Actual>,
199    TargetNull: Nullability + AcceptsNullability<ActualNull>,
200    Actual: DataType,
201    ActualNull: Nullability,
202    ColumnBinOp<L, R, Op, PostgresDialect, Actual, ActualNull>: ToSQL<'a, PostgresValue<'a>>,
203{
204    fn from(value: ColumnBinOp<L, R, Op, PostgresDialect, Actual, ActualNull>) -> Self {
205        Self::Value(ValueWrapper::<PostgresValue<'a>, T>::new(value.into_sql()))
206    }
207}
208
209impl<'a, T, Target, TargetNull, E, Actual, ActualNull>
210    From<ColumnNeg<E, PostgresDialect, Actual, ActualNull>>
211    for PostgresUpdateValue<'a, PostgresValue<'a>, T, Target, TargetNull>
212where
213    Target: DataType + Assignable<Actual>,
214    TargetNull: Nullability + AcceptsNullability<ActualNull>,
215    Actual: DataType,
216    ActualNull: Nullability,
217    ColumnNeg<E, PostgresDialect, Actual, ActualNull>: ToSQL<'a, PostgresValue<'a>>,
218{
219    fn from(value: ColumnNeg<E, PostgresDialect, Actual, ActualNull>) -> Self {
220        Self::Value(ValueWrapper::<PostgresValue<'a>, T>::new(value.into_sql()))
221    }
222}