drizzle_core/
prepared.rs

1pub mod owned;
2use crate::{
3    Param, ParamBind, SQL, SQLChunk, ToSQL, prepared::owned::OwnedPreparedStatement,
4    traits::SQLParam,
5};
6use compact_str::CompactString;
7use smallvec::SmallVec;
8use std::{borrow::Cow, fmt};
9
10/// A pre-rendered SQL statement with parameter placeholders
11/// Structure: [text, param, text, param, text] where text segments
12/// are pre-rendered and params are placeholders to be bound later
13#[derive(Debug, Clone)]
14pub struct PreparedStatement<'a, V: SQLParam> {
15    /// Pre-rendered text segments
16    pub text_segments: Box<[CompactString]>,
17    /// Parameter placeholders (in order)  
18    pub params: Box<[Param<'a, V>]>,
19}
20
21impl<'a, V: SQLParam> From<OwnedPreparedStatement<V>> for PreparedStatement<'a, V> {
22    fn from(value: OwnedPreparedStatement<V>) -> Self {
23        Self {
24            text_segments: value.text_segments,
25            params: value.params.iter().map(|v| v.clone().into()).collect(),
26        }
27    }
28}
29
30impl<'a, V: SQLParam + std::fmt::Display> std::fmt::Display for PreparedStatement<'a, V> {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        write!(f, "{}", self.to_sql())
33    }
34}
35
36impl<'a, V: SQLParam> PreparedStatement<'a, V> {
37    /// Bind parameters and render final SQL string
38    pub fn bind(self, param_binds: impl IntoIterator<Item = ParamBind<'a, V>>) -> (String, Vec<V>) {
39        use std::collections::HashMap;
40
41        let param_map: HashMap<&str, V> =
42            param_binds.into_iter().map(|p| (p.name, p.value)).collect();
43
44        let mut sql = String::new();
45        let mut bound_params = Vec::new();
46
47        // Zip text segments with params: text[0], param[0], text[1], param[1], text[2]
48        for (i, text_segment) in self.text_segments.iter().enumerate() {
49            sql.push_str(text_segment);
50
51            // Add param if there's one at this position
52            if let Some(param) = self.params.get(i) {
53                if let Some(name) = param.placeholder.name {
54                    if let Some(value) = param_map.get(name) {
55                        bound_params.push(value.clone());
56                        sql.push_str(&param.placeholder.to_string());
57                    } else {
58                        // Parameter not found, keep placeholder
59                        sql.push_str(&param.placeholder.to_string());
60                    }
61                } else {
62                    // Positional parameter - use existing value if any
63                    if let Some(value) = &param.value {
64                        bound_params.push(value.as_ref().clone());
65                        sql.push_str(&param.placeholder.to_string());
66                    }
67                }
68            }
69        }
70
71        (sql, bound_params)
72    }
73}
74
75impl<'a, V: SQLParam> ToSQL<'a, V> for PreparedStatement<'a, V> {
76    fn to_sql(&self) -> SQL<'a, V> {
77        // Calculate exact capacity needed: text_segments.len() + params.len()
78        let capacity = self.text_segments.len() + self.params.len();
79        let mut chunks = SmallVec::with_capacity(capacity);
80
81        // Interleave text segments and params: text[0], param[0], text[1], param[1], ..., text[n]
82        // Use iterators to avoid bounds checking and minimize allocations
83        let mut param_iter = self.params.iter();
84
85        for text_segment in &self.text_segments {
86            chunks.push(SQLChunk::Text(Cow::Owned(text_segment.clone())));
87
88            // Add corresponding param if available
89            if let Some(param) = param_iter.next() {
90                chunks.push(SQLChunk::Param(param.clone()));
91            }
92        }
93
94        SQL { chunks }
95    }
96}
97/// Pre-render SQL by processing chunks and separating text from parameters
98/// This preserves the original SQL pattern detection logic while creating a PreparedSQL
99/// that can be efficiently bound and executed multiple times
100pub fn prepare_render<'a, V: SQLParam>(sql: SQL<'a, V>) -> PreparedStatement<'a, V> {
101    let mut text_segments = Vec::new();
102    let mut params = Vec::new();
103    let mut current_text = CompactString::default();
104
105    // Process chunks with original pattern detection logic preserved
106    for (i, chunk) in sql.chunks.iter().enumerate() {
107        match chunk {
108            SQLChunk::Param(param) => {
109                // End current text segment and start a new one
110                text_segments.push(current_text);
111                current_text = CompactString::default();
112                params.push(param.clone());
113            }
114            SQLChunk::Text(text) if text.is_empty() => {
115                // Handle empty text - check for SELECT-FROM-TABLE pattern
116                if let Some(table) = sql.detect_pattern_at(i) {
117                    sql.write_qualified_columns(&mut current_text, table);
118                }
119            }
120            SQLChunk::Text(text) if text.trim().eq_ignore_ascii_case("SELECT") => {
121                // Check if this is a SELECT-FROM-TABLE pattern (SELECT with no columns)
122                if let Some(table) = sql.detect_select_from_table_pattern(i) {
123                    current_text.push_str("SELECT ");
124                    sql.write_qualified_columns(&mut current_text, table);
125                } else {
126                    current_text.push_str(text);
127                }
128            }
129            SQLChunk::Text(text) => {
130                current_text.push_str(text);
131            }
132            SQLChunk::Table(table) => {
133                current_text.push('"');
134                current_text.push_str(table.name());
135                current_text.push('"');
136            }
137            SQLChunk::Column(column) => {
138                current_text.push('"');
139                current_text.push_str(column.table().name());
140                current_text.push_str(r#"".""#);
141                current_text.push_str(column.name());
142                current_text.push('"');
143            }
144            SQLChunk::Alias { chunk, alias } => {
145                // Process the nested chunk first
146                sql.write_chunk(&mut current_text, chunk, i);
147                current_text.push_str(" AS ");
148                current_text.push_str(alias);
149            }
150            SQLChunk::Subquery(sql) => {
151                // Process subquery like nested SQL but with parentheses
152                current_text.push('(');
153                let nested_prepared = prepare_render(sql.as_ref().clone());
154
155                // Merge the nested prepared SQL into current one
156                for (j, text_segment) in nested_prepared.text_segments.iter().enumerate() {
157                    if j == 0 {
158                        // First segment goes into current text
159                        current_text.push_str(text_segment);
160                    } else {
161                        // Subsequent segments create new text segments with params between
162                        if let Some(param) = nested_prepared.params.get(j - 1) {
163                            text_segments.push(current_text);
164                            current_text = CompactString::default();
165                            params.push(param.clone());
166                        }
167                        current_text.push_str(text_segment);
168                    }
169                }
170
171                // Add any remaining parameters from the nested prepared
172                for remaining_param in nested_prepared
173                    .params
174                    .iter()
175                    .skip(nested_prepared.text_segments.len() - 1)
176                {
177                    text_segments.push(current_text);
178                    current_text = CompactString::default();
179                    params.push(remaining_param.clone());
180                }
181
182                current_text.push(')');
183            }
184            SQLChunk::SQL(nested_sql) => {
185                // Recursively process nested SQL
186                let nested_prepared = prepare_render(nested_sql.as_ref().clone());
187
188                // Merge the nested prepared SQL into current one
189                for (j, text_segment) in nested_prepared.text_segments.iter().enumerate() {
190                    if j == 0 {
191                        // First segment goes into current text
192                        current_text.push_str(text_segment);
193                    } else {
194                        // Subsequent segments create new text segments with params between
195                        if let Some(param) = nested_prepared.params.get(j - 1) {
196                            text_segments.push(current_text);
197                            current_text = CompactString::default();
198                            params.push(param.clone());
199                        }
200                        current_text.push_str(text_segment);
201                    }
202                }
203            }
204        }
205
206        // Add spacing between chunks if needed
207        if sql.needs_space(i) {
208            current_text.push(' ');
209        }
210    }
211
212    // Don't forget the final text segment
213    text_segments.push(current_text);
214
215    PreparedStatement {
216        text_segments: text_segments.into_boxed_slice(),
217        params: params.into_boxed_slice(),
218    }
219}