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