Skip to main content

drizzle_postgres/builder/
prepared.rs

1//! Prepared `PostgreSQL` statements: SQL split around its parameter slots.
2
3use crate::prelude::*;
4
5use drizzle_core::{
6    OwnedParam, Param,
7    prepared::{
8        OwnedPreparedStatement as CoreOwnedPreparedStatement,
9        PreparedStatement as CorePreparedStatement,
10    },
11};
12
13use crate::values::{OwnedPostgresValue, PostgresValue};
14
15/// A `PostgreSQL` statement rendered once, with its parameter slots, for reuse.
16///
17/// The SQL text uses `$1`, `$2`, ... placeholders. Each slot holds either a
18/// bound value or a named [`Placeholder`](drizzle_core::Placeholder) to fill
19/// at execution time. Values are borrowed for `'a`; convert with
20/// [`into_owned`](Self::into_owned) to keep the statement longer.
21///
22/// `Display` prints the SQL text.
23#[derive(Debug, Clone)]
24pub struct PreparedStatement<'a> {
25    pub(crate) inner: CorePreparedStatement<'a, PostgresValue<'a>>,
26}
27
28impl PreparedStatement<'_> {
29    /// Copies this statement into an [`OwnedPreparedStatement`] with no
30    /// borrowed data.
31    ///
32    /// # Examples
33    ///
34    /// ```rust
35    /// # fn example(prepared: drizzle_postgres::builder::prepared::PreparedStatement<'_>) {
36    /// let owned = prepared.into_owned();
37    /// // `owned` has no lifetime, so it can be cached or sent to another thread.
38    /// # }
39    /// ```
40    #[must_use]
41    pub fn into_owned(&self) -> OwnedPreparedStatement {
42        let owned_params = self.inner.params.iter().map(|p| OwnedParam {
43            placeholder: p.placeholder,
44            value: p
45                .value
46                .clone()
47                .map(|v| OwnedPostgresValue::from(v.into_owned())),
48        });
49
50        let inner = CoreOwnedPreparedStatement {
51            text_segments: self.inner.text_segments.clone(),
52            params: owned_params.collect::<Box<[_]>>(),
53            sql: self.inner.sql.clone(),
54        };
55
56        OwnedPreparedStatement { inner }
57    }
58}
59
60/// A [`PreparedStatement`] that owns all its values, so it has no lifetime.
61///
62/// Use it to cache a statement or move it across threads. Convert from a
63/// [`PreparedStatement`] with `into_owned()` or `From`, and back with `From`.
64/// `Display` prints the SQL text.
65#[derive(Debug, Clone)]
66pub struct OwnedPreparedStatement {
67    pub(crate) inner: CoreOwnedPreparedStatement<crate::values::OwnedPostgresValue>,
68}
69
70impl<'a> From<PreparedStatement<'a>> for OwnedPreparedStatement {
71    fn from(value: PreparedStatement<'a>) -> Self {
72        let owned_params = value.inner.params.iter().map(|p| OwnedParam {
73            placeholder: p.placeholder,
74            value: p
75                .value
76                .clone()
77                .map(|v| OwnedPostgresValue::from(v.into_owned())),
78        });
79        let inner = CoreOwnedPreparedStatement {
80            text_segments: value.inner.text_segments,
81            params: owned_params.collect::<Box<[_]>>(),
82            sql: value.inner.sql,
83        };
84        Self { inner }
85    }
86}
87
88impl From<OwnedPreparedStatement> for PreparedStatement<'_> {
89    fn from(value: OwnedPreparedStatement) -> Self {
90        let postgresvalue = value.inner.params.iter().map(|v| {
91            Param::new(
92                v.placeholder,
93                v.value.clone().map(|v| Cow::Owned(PostgresValue::from(v))),
94            )
95        });
96        let inner = CorePreparedStatement {
97            text_segments: value.inner.text_segments,
98            params: postgresvalue.collect::<Box<[_]>>(),
99            sql: value.inner.sql,
100        };
101        PreparedStatement { inner }
102    }
103}
104
105impl OwnedPreparedStatement {}
106
107impl core::fmt::Display for PreparedStatement<'_> {
108    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
109        write!(f, "{}", self.inner)
110    }
111}
112
113impl core::fmt::Display for OwnedPreparedStatement {
114    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
115        write!(f, "{}", self.inner)
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122    use drizzle_core::{SQL, ToSQL, prepared::prepare_render};
123
124    #[test]
125    fn test_prepare_render_basic() {
126        // Test the basic prepare_render functionality for PostgreSQL
127        let sql: SQL<'_, PostgresValue<'_>> = SQL::raw("SELECT * FROM users WHERE id = ")
128            .append(drizzle_core::Placeholder::named("user_id").to_sql())
129            .append(SQL::raw(" AND name = "))
130            .append(drizzle_core::Placeholder::named("user_name").to_sql());
131
132        let prepared = prepare_render(&sql);
133
134        // Should have 3 text segments: before first param, between params, after last param
135        assert_eq!(prepared.text_segments.len(), 3);
136        assert_eq!(prepared.params.len(), 2);
137
138        // Verify text segments contain expected content
139        assert!(prepared.text_segments[0].contains("SELECT * FROM users WHERE id"));
140        assert!(prepared.text_segments[1].contains("AND name"));
141    }
142
143    #[test]
144    fn test_prepare_with_no_parameters() {
145        // Test preparing SQL with no parameters
146        let sql: SQL<'_, PostgresValue<'_>> = SQL::raw("SELECT COUNT(*) FROM users");
147        let prepared = prepare_render(&sql);
148
149        assert_eq!(prepared.text_segments.len(), 1);
150        assert_eq!(prepared.params.len(), 0);
151        assert_eq!(prepared.text_segments[0], "SELECT COUNT(*) FROM users");
152    }
153
154    #[test]
155    fn test_prepared_statement_display() {
156        let sql: SQL<'_, PostgresValue<'_>> = SQL::raw("SELECT * FROM users")
157            .append(SQL::raw(" WHERE id = "))
158            .append(drizzle_core::Placeholder::named("id").to_sql());
159
160        let prepared = prepare_render(&sql);
161        let display = format!("{}", prepared);
162
163        assert!(display.contains("SELECT * FROM users"));
164        assert!(display.contains("WHERE id"));
165    }
166
167    #[test]
168    fn test_owned_conversion_roundtrip() {
169        let sql: SQL<'_, PostgresValue<'_>> = SQL::raw("SELECT name FROM users WHERE id = ")
170            .append(drizzle_core::Placeholder::named("id").to_sql());
171
172        let prepared = prepare_render(&sql);
173        let core_prepared = PreparedStatement { inner: prepared };
174
175        // Convert to owned
176        let owned = core_prepared.into_owned();
177
178        // Convert back to borrowed
179        let borrowed: PreparedStatement<'_> = owned.into();
180
181        // Verify structure is preserved
182        assert_eq!(borrowed.inner.text_segments.len(), 2);
183        assert_eq!(borrowed.inner.params.len(), 1);
184    }
185}