1mod owned;
2pub use owned::OwnedPreparedStatement;
3
4use crate::prelude::*;
5use crate::{
6 error::DrizzleError,
7 param::{Param, ParamBind},
8 sql::{SQL, SQLChunk},
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
140 for param in params {
141 if let Some(value) = param_value_fn(param) {
143 bound_params.push(value.clone());
145 } else if let Some(name) = param_name_fn(param) {
146 if !name.is_empty() {
148 if let Some(value) = param_map.get(name) {
149 bound_params.push(value.clone());
150 }
151 } else if let Some(value) = positional_iter.next() {
152 bound_params.push(value);
153 }
154 } else if let Some(value) = positional_iter.next() {
155 bound_params.push(value);
156 }
157 }
158
159 Ok(bound_params)
160}
161
162impl<'a, V: SQLParam> PreparedStatement<'a, V> {
163 #[must_use]
167 pub fn external_param_count(&self) -> usize {
168 let mut named = HashSet::<&str>::new();
169 let mut positional = 0usize;
170 for param in &self.params {
171 if param.value.is_some() {
172 continue;
173 }
174 match param.placeholder.name {
175 Some(name) if !name.is_empty() => {
176 named.insert(name);
177 }
178 _ => positional += 1,
179 }
180 }
181 named.len() + positional
182 }
183
184 pub fn bind<T: SQLParam + Into<V>>(
192 &self,
193 param_binds: impl IntoIterator<Item = ParamBind<'a, T>>,
194 ) -> crate::error::Result<(&str, impl Iterator<Item = V>)> {
195 let bound_params = bind_values_internal(
196 &self.params,
197 param_binds,
198 |p| p.placeholder.name,
199 |p| p.value.as_ref().map(core::convert::AsRef::as_ref),
200 )?;
201
202 Ok((self.sql.as_str(), bound_params.into_iter()))
203 }
204
205 #[must_use]
207 pub fn sql(&self) -> &str {
208 self.sql.as_str()
209 }
210}
211
212impl<'a, V: SQLParam> ToSQL<'a, V> for PreparedStatement<'a, V> {
213 fn to_sql(&self) -> SQL<'a, V> {
214 let capacity = self.text_segments.len() + self.params.len();
216 let mut chunks = SmallVec::with_capacity(capacity);
217
218 let mut param_iter = self.params.iter();
221
222 for text_segment in &self.text_segments {
223 chunks.push(SQLChunk::Raw(Cow::Owned(text_segment.to_string())));
224
225 if let Some(param) = param_iter.next() {
227 chunks.push(SQLChunk::Param(param.clone()));
228 }
229 }
230
231 SQL { chunks }
232 }
233}
234pub fn prepare_render<'a, V: SQLParam>(sql: &SQL<'a, V>) -> PreparedStatement<'a, V> {
236 use crate::dialect::{Dialect, write_placeholder};
237 use crate::sql::chunk_needs_space;
238
239 #[cfg(feature = "profiling")]
240 crate::drizzle_profile_scope!("prepared", "prepare_render");
241
242 if !sql
243 .chunks
244 .iter()
245 .any(|chunk| matches!(chunk, SQLChunk::Param(_)))
246 {
247 #[cfg(feature = "profiling")]
248 crate::drizzle_profile_scope!("prepared", "prepare_render.no_params");
249 let rendered_sql = CompactString::new(sql.sql());
250 return PreparedStatement {
251 text_segments: vec![rendered_sql.clone()].into_boxed_slice(),
252 params: Vec::new().into_boxed_slice(),
253 sql: rendered_sql,
254 };
255 }
256
257 #[cfg(feature = "profiling")]
258 crate::drizzle_profile_scope!("prepared", "prepare_render.scan");
259 let mut text_segments = Vec::new();
260 let mut params = Vec::new();
261 let mut current_text = String::new();
262 let mut rendered_sql = String::with_capacity(sql.chunks.len().saturating_mul(8).max(64));
263 let mut param_index = 1usize;
264
265 for (i, chunk) in sql.chunks.iter().enumerate() {
266 let current_text_ends_with_space = if let SQLChunk::Param(param) = chunk {
267 text_segments.push(CompactString::new(¤t_text));
268 rendered_sql.push_str(¤t_text);
269 current_text.clear();
270 params.push(param.clone());
271
272 if let Some(name) = param.placeholder.name
273 && V::DIALECT == Dialect::SQLite
274 {
275 rendered_sql.push(':');
276 rendered_sql.push_str(name);
277 } else {
278 write_placeholder(V::DIALECT, param_index, &mut rendered_sql);
279 }
280 param_index += 1;
281 false
282 } else {
283 sql.write_chunk_to(&mut current_text, chunk, i);
284 matches!(chunk, SQLChunk::Raw(text) if text.ends_with(' '))
285 };
286
287 if let Some(next) = sql.chunks.get(i + 1)
290 && !current_text_ends_with_space
291 && chunk_needs_space(chunk, next)
292 {
293 current_text.push(' ');
294 }
295 }
296
297 text_segments.push(CompactString::new(¤t_text));
298 rendered_sql.push_str(¤t_text);
299
300 #[cfg(feature = "profiling")]
301 crate::drizzle_profile_scope!("prepared", "prepare_render.finalize");
302 let text_segments = text_segments.into_boxed_slice();
303 let params = params.into_boxed_slice();
304 let rendered_sql = CompactString::new(rendered_sql);
305
306 PreparedStatement {
307 text_segments,
308 params,
309 sql: rendered_sql,
310 }
311}