1mod owned;
2pub use owned::OwnedPreparedStatement;
3
4use crate::prelude::*;
5use crate::{
6 error::DrizzleError,
7 param::{Param, ParamBind},
8 sql::{SQL, SQLChunk, SQLiteNamedParams},
9 traits::{SQLParam, ToSQL},
10};
11use compact_str::CompactString;
12use core::fmt;
13use smallvec::SmallVec;
14
15#[derive(Debug, Clone)]
19pub struct PreparedStatement<'a, V: SQLParam> {
20 pub text_segments: Box<[CompactString]>,
22 pub params: Box<[Param<'a, V>]>,
24 pub sql: CompactString,
26}
27
28impl<V: SQLParam> From<OwnedPreparedStatement<V>> for PreparedStatement<'_, V> {
29 fn from(value: OwnedPreparedStatement<V>) -> Self {
30 Self {
31 text_segments: value.text_segments,
32 params: value.params.iter().map(|v| v.clone().into()).collect(),
33 sql: value.sql,
34 }
35 }
36}
37
38impl<V: SQLParam> core::fmt::Display for PreparedStatement<'_, V> {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 write!(f, "{}", self.sql())
41 }
42}
43
44pub(crate) fn bind_values_internal<'a, V, T, P>(
47 params: &[P],
48 param_binds: impl IntoIterator<Item = ParamBind<'a, T>>,
49 param_name_fn: impl Fn(&P) -> Option<&str>,
50 param_value_fn: impl Fn(&P) -> Option<&V>,
51) -> crate::error::Result<SmallVec<[V; 8]>>
52where
53 V: SQLParam + Clone,
54 T: SQLParam + Into<V>,
55{
56 #[cfg(feature = "profiling")]
57 crate::drizzle_profile_scope!("prepared", "bind_values_internal");
58 let param_binds = param_binds.into_iter();
59 let (binds_lower, binds_upper) = param_binds.size_hint();
60
61 let mut expected_named = HashMap::<&str, usize>::new();
62 let mut expected_positional = 0usize;
63 for param in params {
64 if param_value_fn(param).is_some() {
65 continue;
66 }
67
68 match param_name_fn(param) {
69 Some(name) if !name.is_empty() => {
70 *expected_named.entry(name).or_insert(0) += 1;
71 }
72 _ => expected_positional += 1,
73 }
74 }
75
76 let mut param_map = HashMap::<&str, V>::with_capacity(expected_named.len().max(binds_lower));
77
78 let mut positional_params: SmallVec<[V; 8]> =
79 SmallVec::with_capacity(binds_upper.unwrap_or(binds_lower));
80
81 for bind in param_binds {
82 if bind.name.is_empty() {
83 positional_params.push(bind.value.into());
84 } else if param_map.insert(bind.name, bind.value.into()).is_some() {
85 return Err(DrizzleError::ParameterError(
86 format!("Duplicate parameter binding: '{}'", bind.name).into(),
87 ));
88 }
89 }
90
91 if positional_params.len() < expected_positional {
92 return Err(DrizzleError::ParameterError(
93 format!(
94 "Missing positional parameter(s): expected {}, got {}",
95 expected_positional,
96 positional_params.len()
97 )
98 .into(),
99 ));
100 }
101 if positional_params.len() > expected_positional {
102 return Err(DrizzleError::ParameterError(
103 format!(
104 "Unexpected positional parameter(s): expected {}, got {}",
105 expected_positional,
106 positional_params.len()
107 )
108 .into(),
109 ));
110 }
111
112 let mut missing_named: SmallVec<[&str; 8]> = expected_named
113 .keys()
114 .filter(|name| !param_map.contains_key(**name))
115 .copied()
116 .collect();
117 if !missing_named.is_empty() {
118 missing_named.sort_unstable();
119 return Err(DrizzleError::ParameterError(
120 format!("Missing named parameter(s): {}", missing_named.join(", ")).into(),
121 ));
122 }
123
124 let mut extra_named: SmallVec<[&str; 8]> = param_map
125 .keys()
126 .filter(|name| !expected_named.contains_key(**name))
127 .copied()
128 .collect();
129 if !extra_named.is_empty() {
130 extra_named.sort_unstable();
131 return Err(DrizzleError::ParameterError(
132 format!("Unexpected named parameter(s): {}", extra_named.join(", ")).into(),
133 ));
134 }
135
136 let mut positional_iter = positional_params.into_iter();
137
138 let mut bound_params = SmallVec::<[V; 8]>::with_capacity(params.len());
139 let mut sqlite_names = SQLiteNamedParams::default();
140
141 for param in params {
142 if V::DIALECT == crate::dialect::Dialect::SQLite
145 && let Some(name) = param_name_fn(param)
146 && sqlite_names.is_repeat(name)
147 {
148 continue;
149 }
150
151 if let Some(value) = param_value_fn(param) {
153 bound_params.push(value.clone());
155 } else if let Some(name) = param_name_fn(param) {
156 if !name.is_empty() {
158 if let Some(value) = param_map.get(name) {
159 bound_params.push(value.clone());
160 }
161 } else if let Some(value) = positional_iter.next() {
162 bound_params.push(value);
163 }
164 } else if let Some(value) = positional_iter.next() {
165 bound_params.push(value);
166 }
167 }
168
169 Ok(bound_params)
170}
171
172impl<'a, V: SQLParam> PreparedStatement<'a, V> {
173 #[must_use]
177 pub fn external_param_count(&self) -> usize {
178 let mut named = HashSet::<&str>::new();
179 let mut positional = 0usize;
180 for param in &self.params {
181 if param.value.is_some() {
182 continue;
183 }
184 match param.placeholder.name {
185 Some(name) if !name.is_empty() => {
186 named.insert(name);
187 }
188 _ => positional += 1,
189 }
190 }
191 named.len() + positional
192 }
193
194 pub fn bind<T: SQLParam + Into<V>>(
202 &self,
203 param_binds: impl IntoIterator<Item = ParamBind<'a, T>>,
204 ) -> crate::error::Result<(&str, impl Iterator<Item = V>)> {
205 let bound_params = bind_values_internal(
206 &self.params,
207 param_binds,
208 |p| p.placeholder.name,
209 |p| p.value.as_ref().map(core::convert::AsRef::as_ref),
210 )?;
211
212 Ok((self.sql.as_str(), bound_params.into_iter()))
213 }
214
215 #[must_use]
217 pub fn sql(&self) -> &str {
218 self.sql.as_str()
219 }
220}
221
222impl<'a, V: SQLParam> ToSQL<'a, V> for PreparedStatement<'a, V> {
223 fn to_sql(&self) -> SQL<'a, V> {
224 let capacity = self.text_segments.len() + self.params.len();
226 let mut chunks = SmallVec::with_capacity(capacity);
227
228 let mut param_iter = self.params.iter();
231
232 for text_segment in &self.text_segments {
233 chunks.push(SQLChunk::Raw(Cow::Owned(text_segment.to_string())));
234
235 if let Some(param) = param_iter.next() {
237 chunks.push(SQLChunk::Param(param.clone()));
238 }
239 }
240
241 SQL { chunks }
242 }
243}
244pub fn prepare_render<'a, V: SQLParam>(sql: &SQL<'a, V>) -> PreparedStatement<'a, V> {
246 use crate::dialect::{Dialect, write_placeholder};
247 use crate::sql::chunk_needs_space;
248
249 #[cfg(feature = "profiling")]
250 crate::drizzle_profile_scope!("prepared", "prepare_render");
251
252 if !sql
253 .chunks
254 .iter()
255 .any(|chunk| matches!(chunk, SQLChunk::Param(_)))
256 {
257 #[cfg(feature = "profiling")]
258 crate::drizzle_profile_scope!("prepared", "prepare_render.no_params");
259 let rendered_sql = CompactString::new(sql.sql());
260 return PreparedStatement {
261 text_segments: vec![rendered_sql.clone()].into_boxed_slice(),
262 params: Vec::new().into_boxed_slice(),
263 sql: rendered_sql,
264 };
265 }
266
267 #[cfg(feature = "profiling")]
268 crate::drizzle_profile_scope!("prepared", "prepare_render.scan");
269 let mut text_segments = Vec::new();
270 let mut params = Vec::new();
271 let mut current_text = String::new();
272 let mut rendered_sql = String::with_capacity(sql.chunks.len().saturating_mul(8).max(64));
273 let mut param_index = 1usize;
274
275 for (i, chunk) in sql.chunks.iter().enumerate() {
276 let current_text_ends_with_space = if let SQLChunk::Param(param) = chunk {
277 text_segments.push(CompactString::new(¤t_text));
278 rendered_sql.push_str(¤t_text);
279 current_text.clear();
280 params.push(param.clone());
281
282 if let Some(name) = param.placeholder.name
283 && V::DIALECT == Dialect::SQLite
284 {
285 rendered_sql.push(':');
286 rendered_sql.push_str(name);
287 } else {
288 write_placeholder(V::DIALECT, param_index, &mut rendered_sql);
289 }
290 param_index += 1;
291 false
292 } else {
293 sql.write_chunk_to(&mut current_text, chunk, i);
294 matches!(chunk, SQLChunk::Raw(text) if text.ends_with(' '))
295 };
296
297 if let Some(next) = sql.chunks.get(i + 1)
300 && !current_text_ends_with_space
301 && chunk_needs_space(chunk, next)
302 {
303 current_text.push(' ');
304 }
305 }
306
307 text_segments.push(CompactString::new(¤t_text));
308 rendered_sql.push_str(¤t_text);
309
310 #[cfg(feature = "profiling")]
311 crate::drizzle_profile_scope!("prepared", "prepare_render.finalize");
312 let text_segments = text_segments.into_boxed_slice();
313 let params = params.into_boxed_slice();
314 let rendered_sql = CompactString::new(rendered_sql);
315
316 PreparedStatement {
317 text_segments,
318 params,
319 sql: rendered_sql,
320 }
321}