Skip to main content

drizzle_core/
placeholder.rs

1use crate::bind::{BindValue, NullableBindValue};
2use crate::expr::{Expr, NonNull, Null, Nullability, Scalar};
3use crate::param::ParamBind;
4use crate::traits::{SQLParam, ToSQL};
5use crate::types::DataType;
6use crate::{Param, SQL};
7use core::fmt;
8use core::marker::PhantomData;
9
10/// A SQL parameter placeholder.
11///
12/// Placeholders store a semantic name for parameter binding. The actual SQL syntax
13/// (`$1`, `?`, `:name`) is determined by the `Dialect` at render time.
14#[derive(Default, Debug, Clone, Hash, Copy, PartialEq, Eq)]
15pub struct Placeholder {
16    /// The semantic name of the parameter (used for binding by name).
17    pub name: Option<&'static str>,
18}
19
20/// A placeholder that carries the expected SQL type at compile time.
21#[derive(Default, Debug, Clone, Hash, Copy, PartialEq, Eq)]
22pub struct TypedPlaceholder<T: DataType, N: Nullability = NonNull> {
23    inner: Placeholder,
24    _marker: PhantomData<fn() -> (T, N)>,
25}
26
27impl Placeholder {
28    /// Creates a named placeholder.
29    ///
30    /// The name is used for binding; rendering is dialect-specific:
31    /// - `PostgreSQL`: `$1`, `$2`, ... (positional, name ignored in SQL)
32    /// - `SQLite`: `:name` for named placeholders
33    /// - `MySQL`: `?` (positional, name ignored in SQL)
34    #[must_use]
35    pub const fn named(name: &'static str) -> Self {
36        Self { name: Some(name) }
37    }
38
39    /// Creates an anonymous placeholder (no name).
40    #[must_use]
41    pub const fn anonymous() -> Self {
42        Self { name: None }
43    }
44
45    /// Creates a typed named placeholder.
46    #[must_use]
47    pub const fn typed<T: DataType>(name: &'static str) -> TypedPlaceholder<T, NonNull> {
48        TypedPlaceholder {
49            inner: Self::named(name),
50            _marker: PhantomData,
51        }
52    }
53
54    /// Creates a typed nullable named placeholder.
55    #[must_use]
56    pub const fn typed_nullable<T: DataType>(name: &'static str) -> TypedPlaceholder<T, Null> {
57        TypedPlaceholder {
58            inner: Self::named(name),
59            _marker: PhantomData,
60        }
61    }
62}
63
64impl<T: DataType, N: Nullability> TypedPlaceholder<T, N> {
65    /// Creates a typed named placeholder.
66    #[must_use]
67    pub const fn named(name: &'static str) -> Self {
68        Self {
69            inner: Placeholder::named(name),
70            _marker: PhantomData,
71        }
72    }
73
74    /// Binds a value to this placeholder with compile-time SQL type checking.
75    pub fn bind<'a, V, R>(self, value: R) -> ParamBind<'a, V>
76    where
77        V: SQLParam,
78        R: BindValue<'a, V, T>,
79    {
80        ParamBind {
81            name: self.inner.name.unwrap_or(""),
82            value: value.into_bind_value(),
83        }
84    }
85
86    /// Returns the placeholder name if present.
87    #[must_use]
88    pub const fn name(self) -> Option<&'static str> {
89        self.inner.name
90    }
91
92    /// Returns this typed placeholder as an untyped placeholder.
93    #[must_use]
94    pub const fn into_placeholder(self) -> Placeholder {
95        self.inner
96    }
97}
98
99impl<T: DataType> TypedPlaceholder<T, Null> {
100    /// Binds an optional value to a nullable placeholder.
101    pub fn bind_opt<'a, V, R>(self, value: Option<R>) -> ParamBind<'a, V>
102    where
103        V: SQLParam,
104        Option<R>: NullableBindValue<'a, V, T>,
105    {
106        ParamBind {
107            name: self.inner.name.unwrap_or(""),
108            value: value.into_nullable_bind_value(),
109        }
110    }
111}
112
113impl<T: DataType, N: Nullability> From<TypedPlaceholder<T, N>> for Placeholder {
114    fn from(value: TypedPlaceholder<T, N>) -> Self {
115        value.inner
116    }
117}
118
119impl<'a, V: SQLParam + 'a> ToSQL<'a, V> for Placeholder {
120    fn to_sql(&self) -> SQL<'a, V> {
121        SQL {
122            chunks: smallvec::smallvec![crate::SQLChunk::Param(Param {
123                value: None,
124                placeholder: *self,
125            })],
126        }
127    }
128}
129
130impl<'a, V: SQLParam + 'a> Expr<'a, V> for Placeholder {
131    type SQLType = crate::types::Placeholder;
132    type Nullable = NonNull;
133    type Aggregate = Scalar;
134}
135
136impl<'a, V: SQLParam + 'a, T: DataType, N: Nullability> ToSQL<'a, V> for TypedPlaceholder<T, N> {
137    fn to_sql(&self) -> SQL<'a, V> {
138        self.inner.to_sql()
139    }
140}
141
142impl<'a, V: SQLParam + 'a, T: DataType, N: Nullability> Expr<'a, V> for TypedPlaceholder<T, N> {
143    type SQLType = T;
144    type Nullable = N;
145    type Aggregate = Scalar;
146}
147
148impl fmt::Display for Placeholder {
149    /// Debug display: `?` for anonymous or `:name` for named.
150    /// Note: actual SQL rendering uses dialect-specific placeholders via `SQL::write_to`.
151    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152        match self.name {
153            Some(name) => write!(f, ":{name}"),
154            None => write!(f, "?"),
155        }
156    }
157}