drizzle_core/prepared/
owned.rs

1use std::{borrow::Cow, fmt};
2
3use compact_str::CompactString;
4use smallvec::SmallVec;
5
6use crate::{
7    OwnedParam, ParamBind, SQL, SQLChunk, ToSQL, prepared::PreparedStatement, traits::SQLParam,
8};
9
10/// An owned version of PreparedStatement with no lifetime dependencies
11#[derive(Debug, Clone)]
12pub struct OwnedPreparedStatement<V: SQLParam> {
13    /// Pre-rendered text segments
14    pub text_segments: Box<[CompactString]>,
15    /// Parameter placeholders (in order) - only placeholders, no values
16    pub params: Box<[OwnedParam<V>]>,
17}
18impl<V: SQLParam + std::fmt::Display + 'static> std::fmt::Display for OwnedPreparedStatement<V> {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        write!(f, "{}", self.to_sql())
21    }
22}
23
24impl<'a, V: SQLParam> From<PreparedStatement<'a, V>> for OwnedPreparedStatement<V> {
25    fn from(prepared: PreparedStatement<'a, V>) -> Self {
26        OwnedPreparedStatement {
27            text_segments: prepared.text_segments,
28            params: prepared.params.into_iter().map(|p| p.into()).collect(),
29        }
30    }
31}
32
33impl<V: SQLParam> OwnedPreparedStatement<V> {
34    /// Bind parameters and render final SQL string
35    pub fn bind<'a>(
36        self,
37        param_binds: impl IntoIterator<Item = ParamBind<'a, V>>,
38    ) -> (String, Vec<V>) {
39        let param_binds: Vec<_> = param_binds.into_iter().collect();
40        let mut bound_params = Vec::new();
41        let mut sql = String::new();
42
43        // Create a map for quick lookup of parameter bindings by name
44        let param_map: std::collections::HashMap<_, _> = param_binds
45            .iter()
46            .map(|bind| (bind.name, &bind.value))
47            .collect();
48
49        let mut param_iter = self.params.iter();
50
51        for text_segment in self.text_segments.iter() {
52            sql.push_str(text_segment);
53
54            // Add parameter placeholder if we have one
55            if let Some(owned_param) = param_iter.next() {
56                if let Some(name) = &owned_param.placeholder.name {
57                    // Try to find a matching parameter binding
58                    if let Some(value) = param_map.get(name) {
59                        bound_params.push((*value).clone());
60                        sql.push_str(&format!(":{}", name));
61                    } else {
62                        // No binding found, use placeholder name
63                        sql.push_str(&format!(":{}", name));
64                    }
65                } else {
66                    // Positional parameter
67                    sql.push('?');
68                    if let Some(bind) = param_binds.get(bound_params.len()) {
69                        bound_params.push(bind.value.clone());
70                    }
71                }
72            }
73        }
74
75        (sql, bound_params)
76    }
77}
78
79impl<'a, V: SQLParam> ToSQL<'a, V> for OwnedPreparedStatement<V> {
80    fn to_sql(&self) -> SQL<'a, V> {
81        // Calculate exact capacity needed: text_segments.len() + params.len()
82        let capacity = self.text_segments.len() + self.params.len();
83        let mut chunks = SmallVec::with_capacity(capacity);
84
85        // Interleave text segments and params: text[0], param[0], text[1], param[1], ..., text[n]
86        // Use iterators to avoid bounds checking and minimize allocations
87        let mut param_iter = self.params.iter();
88
89        for text_segment in &self.text_segments {
90            chunks.push(SQLChunk::Text(Cow::Owned(text_segment.clone())));
91
92            // Add corresponding param if available
93            if let Some(param) = param_iter.next() {
94                chunks.push(SQLChunk::Param(param.clone().into()));
95            }
96        }
97
98        SQL { chunks }
99    }
100}