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#[derive(Debug, Clone)]
14pub struct PreparedStatement<'a, V: SQLParam> {
15 pub text_segments: Box<[CompactString]>,
17 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 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 for (i, text_segment) in self.text_segments.iter().enumerate() {
49 sql.push_str(text_segment);
50
51 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(¶m.placeholder.to_string());
57 } else {
58 sql.push_str(¶m.placeholder.to_string());
60 }
61 } else {
62 if let Some(value) = ¶m.value {
64 bound_params.push(value.as_ref().clone());
65 sql.push_str(¶m.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 let capacity = self.text_segments.len() + self.params.len();
79 let mut chunks = SmallVec::with_capacity(capacity);
80
81 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 if let Some(param) = param_iter.next() {
90 chunks.push(SQLChunk::Param(param.clone()));
91 }
92 }
93
94 SQL { chunks }
95 }
96}
97pub 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 for (i, chunk) in sql.chunks.iter().enumerate() {
107 match chunk {
108 SQLChunk::Param(param) => {
109 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 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 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 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 current_text.push('(');
153 let nested_prepared = prepare_render(sql.as_ref().clone());
154
155 for (j, text_segment) in nested_prepared.text_segments.iter().enumerate() {
157 if j == 0 {
158 current_text.push_str(text_segment);
160 } else {
161 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 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 let nested_prepared = prepare_render(nested_sql.as_ref().clone());
187
188 for (j, text_segment) in nested_prepared.text_segments.iter().enumerate() {
190 if j == 0 {
191 current_text.push_str(text_segment);
193 } else {
194 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 if sql.needs_space(i) {
208 current_text.push(' ');
209 }
210 }
211
212 text_segments.push(current_text);
214
215 PreparedStatement {
216 text_segments: text_segments.into_boxed_slice(),
217 params: params.into_boxed_slice(),
218 }
219}