drizzle_core/prepared/
mod.rs1mod owned;
2pub use owned::OwnedPreparedStatement;
3
4use crate::prelude::*;
5use crate::{
6 dialect::DialectExt,
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<'a, V: SQLParam> From<OwnedPreparedStatement<V>> for PreparedStatement<'a, 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<'a, V: SQLParam> core::fmt::Display for PreparedStatement<'a, 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) -> impl Iterator<Item = V>
52where
53 V: SQLParam + Clone,
54 T: SQLParam + Into<V>,
55{
56 let mut param_map: HashMap<&str, V> = HashMap::new();
58 let mut positional_params: SmallVec<[V; 8]> = SmallVec::new_const();
59 for bind in param_binds {
60 if bind.name.is_empty() {
61 positional_params.push(bind.value.into());
62 } else {
63 param_map.insert(bind.name, bind.value.into());
64 }
65 }
66 let mut positional_iter = positional_params.into_iter();
67
68 let mut bound_params = SmallVec::<[V; 8]>::new_const();
69
70 for param in params {
71 if let Some(value) = param_value_fn(param) {
73 bound_params.push(value.clone());
75 } else if let Some(name) = param_name_fn(param) {
76 if !name.is_empty() {
78 if let Some(value) = param_map.get(name) {
79 bound_params.push(value.clone());
80 }
81 } else if let Some(value) = positional_iter.next() {
82 bound_params.push(value);
83 }
84 } else if let Some(value) = positional_iter.next() {
85 bound_params.push(value);
86 }
87 }
88
89 bound_params.into_iter()
93}
94
95fn render_sql_with_placeholders<P>(
96 text_segments: &[CompactString],
97 params: &[P],
98 placeholder_fn: impl Fn(&P, usize) -> Cow<'static, str>,
99) -> CompactString {
100 let estimated_capacity: usize =
102 text_segments.iter().map(|s| s.len()).sum::<usize>() + params.len() * 8;
103 let mut sql = String::with_capacity(estimated_capacity);
104
105 let mut param_iter = params.iter().enumerate();
107
108 for text_segment in text_segments {
109 sql.push_str(text_segment);
110
111 if let Some((idx, param)) = param_iter.next() {
112 sql.push_str(&placeholder_fn(param, idx + 1));
114 }
115 }
116
117 CompactString::new(sql)
118}
119
120impl<'a, V: SQLParam> PreparedStatement<'a, V> {
121 pub fn bind<T: SQLParam + Into<V>>(
124 &self,
125 param_binds: impl IntoIterator<Item = ParamBind<'a, T>>,
126 ) -> (&str, impl Iterator<Item = V>) {
127 let bound_params = bind_values_internal(
128 &self.params,
129 param_binds,
130 |p| p.placeholder.name,
131 |p| p.value.as_ref().map(|v| v.as_ref()),
132 );
133
134 (self.sql.as_str(), bound_params)
135 }
136
137 pub fn sql(&self) -> &str {
139 self.sql.as_str()
140 }
141}
142
143impl<'a, V: SQLParam> ToSQL<'a, V> for PreparedStatement<'a, V> {
144 fn to_sql(&self) -> SQL<'a, V> {
145 let capacity = self.text_segments.len() + self.params.len();
147 let mut chunks = SmallVec::with_capacity(capacity);
148
149 let mut param_iter = self.params.iter();
152
153 for text_segment in &self.text_segments {
154 chunks.push(SQLChunk::Raw(Cow::Owned(text_segment.to_string())));
155
156 if let Some(param) = param_iter.next() {
158 chunks.push(SQLChunk::Param(param.clone()));
159 }
160 }
161
162 SQL { chunks }
163 }
164}
165pub fn prepare_render<'a, V: SQLParam>(sql: SQL<'a, V>) -> PreparedStatement<'a, V> {
167 use crate::dialect::Dialect;
168
169 let mut text_segments = Vec::new();
170 let mut params = Vec::new();
171 let mut current_text = String::new();
172
173 for (i, chunk) in sql.chunks.iter().enumerate() {
174 match chunk {
175 SQLChunk::Param(param) => {
176 text_segments.push(CompactString::new(¤t_text));
177 current_text.clear();
178 params.push(param.clone());
179 }
180 _ => {
181 sql.write_chunk_to(&mut current_text, chunk, i);
182 }
183 }
184
185 if let Some(next) = sql.chunks.get(i + 1) {
187 let needs_space = chunk_needs_space_for_prepare(chunk, next, ¤t_text);
189 if needs_space {
190 current_text.push(' ');
191 }
192 }
193 }
194
195 text_segments.push(CompactString::new(¤t_text));
196
197 let text_segments = text_segments.into_boxed_slice();
198 let params = params.into_boxed_slice();
199 let rendered_sql = render_sql_with_placeholders(&text_segments, ¶ms, |p, idx| {
200 if let Some(name) = p.placeholder.name
203 && V::DIALECT == Dialect::SQLite
204 {
205 Cow::Owned(format!(":{}", name))
206 } else {
207 V::DIALECT.render_placeholder(idx)
208 }
209 });
210
211 PreparedStatement {
212 text_segments,
213 params,
214 sql: rendered_sql,
215 }
216}
217
218fn chunk_needs_space_for_prepare<V: SQLParam>(
220 current: &SQLChunk<'_, V>,
221 next: &SQLChunk<'_, V>,
222 current_text: &str,
223) -> bool {
224 if current_text.ends_with(' ') {
226 return false;
227 }
228
229 if let SQLChunk::Raw(text) = next
231 && text.starts_with(' ')
232 {
233 return false;
234 }
235
236 current.is_word_like() && next.is_word_like()
238}