Skip to main content

drizzle_postgres/values/
insert.rs

1//! Insert value types for `PostgreSQL`
2
3use super::{OwnedPostgresValue, PostgresValue};
4use crate::prelude::*;
5use core::marker::PhantomData;
6use drizzle_core::{
7    ToSQL, TypedPlaceholder, param::Param, placeholder::Placeholder, sql::SQL, sql::SQLChunk,
8    traits::SQLParam,
9};
10
11#[cfg(feature = "uuid")]
12use uuid::Uuid;
13
14//------------------------------------------------------------------------------
15// InsertValue Definition - SQL-based value for inserts
16//------------------------------------------------------------------------------
17
18#[doc(hidden)]
19#[derive(Debug, Clone)]
20pub struct ValueWrapper<'a, V: SQLParam, T> {
21    pub value: SQL<'a, V>,
22    pub _phantom: PhantomData<T>,
23}
24
25impl<'a, V: SQLParam, T> ValueWrapper<'a, V, T> {
26    pub const fn new<U>(value: SQL<'a, V>) -> ValueWrapper<'a, V, U> {
27        ValueWrapper {
28            value,
29            _phantom: PhantomData,
30        }
31    }
32}
33
34/// Represents a value for INSERT operations that can be omitted, null, or a SQL expression
35#[derive(Debug, Clone, Default)]
36#[allow(clippy::large_enum_variant)]
37pub enum PostgresInsertValue<'a, V: SQLParam, T> {
38    /// Omit this column from the INSERT (use database default)
39    #[default]
40    Omit,
41    /// Explicitly insert NULL
42    Null,
43    /// Insert a SQL expression (value, placeholder, etc.)
44    Value(ValueWrapper<'a, V, T>),
45}
46
47impl<'a, T> PostgresInsertValue<'a, PostgresValue<'a>, T> {
48    /// Converts this `InsertValue` to an owned version with 'static lifetime.
49    ///
50    /// The whole SQL fragment is kept: placeholders stay unbound and
51    /// expressions keep their shape, with every bound value detached from its
52    /// borrow.
53    #[must_use]
54    pub fn into_owned(self) -> PostgresInsertValue<'static, PostgresValue<'static>, T> {
55        match self {
56            PostgresInsertValue::Omit => PostgresInsertValue::Omit,
57            PostgresInsertValue::Null => PostgresInsertValue::Null,
58            PostgresInsertValue::Value(wrapper) => {
59                let static_sql = wrapper
60                    .value
61                    .into_owned_with(|value| PostgresValue::from(OwnedPostgresValue::from(value)));
62                PostgresInsertValue::Value(ValueWrapper::<PostgresValue<'static>, T>::new(
63                    static_sql,
64                ))
65            }
66        }
67    }
68}
69
70// Conversion implementations for PostgresValue-based InsertValue
71
72/// Converts any value that converts to a [`PostgresValue`] (enums,
73/// `ArrayString`, `ArrayVec`, ...).
74///
75/// # Panics
76///
77/// Panics when the value fails to convert (a JSON payload that fails to
78/// serialize, for example), rather than storing NULL in its place.
79impl<'a, T> From<T> for PostgresInsertValue<'a, PostgresValue<'a>, T>
80where
81    T: TryInto<PostgresValue<'a>>,
82{
83    fn from(value: T) -> Self {
84        // A failed conversion is a caller bug; storing NULL in its place would
85        // lose the value silently.
86        let sql = SQL::from(
87            TryInto::<PostgresValue<'a>>::try_into(value).unwrap_or_else(|_| {
88                panic!(
89                    "could not convert a `{}` to a PostgreSQL value",
90                    core::any::type_name::<T>()
91                )
92            }),
93        );
94        PostgresInsertValue::Value(ValueWrapper::<PostgresValue<'a>, T>::new(sql))
95    }
96}
97
98// Specific conversion for &str to String InsertValue
99impl<'a> From<&str> for PostgresInsertValue<'a, PostgresValue<'a>, String> {
100    fn from(value: &str) -> Self {
101        let postgres_value = SQL::param(Cow::Owned(PostgresValue::from(value.to_string())));
102        PostgresInsertValue::Value(ValueWrapper::<PostgresValue<'a>, String>::new(
103            postgres_value,
104        ))
105    }
106}
107
108// Placeholder conversion
109impl<'a, T> From<Placeholder> for PostgresInsertValue<'a, PostgresValue<'a>, T> {
110    fn from(placeholder: Placeholder) -> Self {
111        let chunk = SQLChunk::Param(Param {
112            placeholder,
113            value: None,
114        });
115        PostgresInsertValue::Value(ValueWrapper::<PostgresValue<'a>, T>::new(
116            core::iter::once(chunk).collect(),
117        ))
118    }
119}
120
121impl<'a, M: drizzle_core::types::DataType, N: drizzle_core::expr::Nullability, T>
122    From<TypedPlaceholder<M, N>> for PostgresInsertValue<'a, PostgresValue<'a>, T>
123{
124    fn from(typed: TypedPlaceholder<M, N>) -> Self {
125        Placeholder::from(typed).into()
126    }
127}
128
129// Option conversion
130impl<'a, T> From<Option<T>> for PostgresInsertValue<'a, PostgresValue<'a>, T>
131where
132    T: ToSQL<'a, PostgresValue<'a>>,
133{
134    fn from(value: Option<T>) -> Self {
135        value.map_or(PostgresInsertValue::Omit, |v| {
136            PostgresInsertValue::Value(ValueWrapper::<PostgresValue<'a>, T>::new(v.to_sql()))
137        })
138    }
139}
140
141// UUID conversion for String InsertValue (for text columns)
142#[cfg(feature = "uuid")]
143impl<'a> From<Uuid> for PostgresInsertValue<'a, PostgresValue<'a>, String> {
144    fn from(value: Uuid) -> Self {
145        let postgres_value = PostgresValue::Uuid(value);
146        let sql = SQL::param(postgres_value);
147        PostgresInsertValue::Value(ValueWrapper::<PostgresValue<'a>, String>::new(sql))
148    }
149}
150
151#[cfg(feature = "uuid")]
152impl<'a> From<&'a Uuid> for PostgresInsertValue<'a, PostgresValue<'a>, String> {
153    fn from(value: &'a Uuid) -> Self {
154        let postgres_value = PostgresValue::Uuid(*value);
155        let sql = SQL::param(postgres_value);
156        PostgresInsertValue::Value(ValueWrapper::<PostgresValue<'a>, String>::new(sql))
157    }
158}