1use std::collections::HashMap;
3use std::sync::{Arc, LazyLock, RwLock};
4
5use cdk_common::database::Error;
6
7use crate::database::DatabaseExecutor;
8use crate::value::Value;
9
10pub type Column = Value;
12
13#[derive(Debug, Clone, Copy, Default)]
15pub enum ExpectedSqlResponse {
16 SingleRow,
18 #[default]
20 ManyRows,
21 AffectedRows,
23 Pluck,
25 Batch,
27}
28
29#[derive(Debug, Clone)]
31pub enum PlaceholderValue {
32 Value(Value),
34 Set(Vec<Value>),
36}
37
38impl From<Value> for PlaceholderValue {
39 fn from(value: Value) -> Self {
40 PlaceholderValue::Value(value)
41 }
42}
43
44impl From<Vec<Value>> for PlaceholderValue {
45 fn from(value: Vec<Value>) -> Self {
46 PlaceholderValue::Set(value)
47 }
48}
49
50#[derive(Debug, Clone)]
52pub enum SqlPart {
53 Raw(Arc<str>),
55 Placeholder(Arc<str>, Option<PlaceholderValue>),
57}
58
59#[derive(Debug, PartialEq, thiserror::Error)]
61pub enum SqlParseError {
62 #[error("Unterminated String literal")]
64 UnterminatedStringLiteral,
65 #[error("Invalid placeholder name")]
67 InvalidPlaceholder,
68}
69
70pub fn split_sql_parts(input: &str) -> Result<Vec<SqlPart>, SqlParseError> {
75 let mut parts = Vec::new();
76 let mut current = String::new();
77 let mut chars = input.chars().peekable();
78
79 while let Some(&c) = chars.peek() {
80 match c {
81 '\'' | '"' => {
82 let quote = c;
84 current.push(
85 chars
86 .next()
87 .ok_or(SqlParseError::UnterminatedStringLiteral)?,
88 );
89
90 let mut closed = false;
91 while let Some(&next) = chars.peek() {
92 current.push(
93 chars
94 .next()
95 .ok_or(SqlParseError::UnterminatedStringLiteral)?,
96 );
97
98 if next == quote {
99 if chars.peek() == Some("e) {
100 current.push(
102 chars
103 .next()
104 .ok_or(SqlParseError::UnterminatedStringLiteral)?,
105 );
106 } else {
107 closed = true;
108 break;
109 }
110 }
111 }
112
113 if !closed {
114 return Err(SqlParseError::UnterminatedStringLiteral);
115 }
116 }
117
118 '-' => {
119 current.push(
120 chars
121 .next()
122 .ok_or(SqlParseError::UnterminatedStringLiteral)?,
123 );
124
125 if chars.peek() == Some(&'-') {
126 while let Some(&next) = chars.peek() {
127 current.push(
128 chars
129 .next()
130 .ok_or(SqlParseError::UnterminatedStringLiteral)?,
131 );
132 if next == '\n' {
133 break;
134 }
135 }
136 }
137 }
138
139 ':' => {
140 chars.next(); if chars.peek() == Some(&':') {
143 current.push(':');
144 current.push(
145 chars
146 .next()
147 .ok_or(SqlParseError::UnterminatedStringLiteral)?,
148 );
149 continue;
150 }
151
152 if !current.is_empty() {
154 parts.push(SqlPart::Raw(current.clone().into()));
155 current.clear();
156 }
157
158 let mut name = String::new();
159
160 while let Some(&next) = chars.peek() {
161 if next.is_alphanumeric() || next == '_' {
162 name.push(
163 chars
164 .next()
165 .ok_or(SqlParseError::UnterminatedStringLiteral)?,
166 );
167 } else {
168 break;
169 }
170 }
171
172 if name.is_empty() {
173 return Err(SqlParseError::InvalidPlaceholder);
174 }
175
176 parts.push(SqlPart::Placeholder(name.into(), None));
177 }
178
179 _ => {
180 current.push(
181 chars
182 .next()
183 .ok_or(SqlParseError::UnterminatedStringLiteral)?,
184 );
185 }
186 }
187 }
188
189 if !current.is_empty() {
190 parts.push(SqlPart::Raw(current.into()));
191 }
192
193 Ok(parts)
194}
195
196type Cache = HashMap<String, (Vec<SqlPart>, Option<Arc<str>>)>;
197
198#[derive(Debug, Default)]
200pub struct Statement {
201 cache: Arc<RwLock<Cache>>,
202 cached_sql: Option<Arc<str>>,
203 sql: Option<String>,
204 pub parts: Vec<SqlPart>,
206 pub expected_response: ExpectedSqlResponse,
208}
209
210impl Statement {
211 fn new(sql: &str, cache: Arc<RwLock<Cache>>) -> Result<Self, SqlParseError> {
213 let parsed = cache
214 .read()
215 .map(|cache| cache.get(sql).cloned())
216 .ok()
217 .flatten();
218
219 if let Some((parts, cached_sql)) = parsed {
220 Ok(Self {
221 parts,
222 cached_sql,
223 sql: None,
224 cache,
225 ..Default::default()
226 })
227 } else {
228 let parts = split_sql_parts(sql)?;
229
230 if let Ok(mut cache) = cache.write() {
231 cache.insert(sql.to_owned(), (parts.clone(), None));
232 } else {
233 tracing::warn!("Failed to acquire write lock for SQL statement cache");
234 }
235
236 Ok(Self {
237 parts,
238 sql: Some(sql.to_owned()),
239 cache,
240 ..Default::default()
241 })
242 }
243 }
244
245 pub fn to_sql(self) -> Result<(String, Vec<Value>), Error> {
251 let has_set_placeholder = self.parts.iter().any(|part| {
252 matches!(
253 part,
254 SqlPart::Placeholder(_, Some(PlaceholderValue::Set(_)))
255 )
256 });
257
258 if let (false, Some(cached_sql)) = (has_set_placeholder, self.cached_sql) {
259 let sql = cached_sql.to_string();
260 let values = self
261 .parts
262 .into_iter()
263 .map(|x| match x {
264 SqlPart::Placeholder(name, value) => {
265 match value.ok_or(Error::MissingPlaceholder(name.to_string()))? {
266 PlaceholderValue::Value(value) => Ok(vec![value]),
267 PlaceholderValue::Set(values) => Ok(values),
268 }
269 }
270 SqlPart::Raw(_) => Ok(vec![]),
271 })
272 .collect::<Result<Vec<_>, Error>>()?
273 .into_iter()
274 .flatten()
275 .collect::<Vec<_>>();
276 return Ok((sql, values));
277 }
278
279 let mut placeholder_values = Vec::new();
280 let mut can_be_cached = true;
281 let sql = self
282 .parts
283 .into_iter()
284 .map(|x| match x {
285 SqlPart::Placeholder(name, value) => {
286 match value.ok_or(Error::MissingPlaceholder(name.to_string()))? {
287 PlaceholderValue::Value(value) => {
288 placeholder_values.push(value);
289 Ok::<_, Error>(format!("${}", placeholder_values.len()))
290 }
291 PlaceholderValue::Set(mut values) => {
292 can_be_cached = false;
293 let start_size = placeholder_values.len();
294 placeholder_values.append(&mut values);
295 let placeholders = (start_size + 1..=placeholder_values.len())
296 .map(|i| format!("${i}"))
297 .collect::<Vec<_>>()
298 .join(", ");
299 Ok(placeholders)
300 }
301 }
302 }
303 SqlPart::Raw(raw) => Ok(raw.trim().to_string()),
304 })
305 .collect::<Result<Vec<String>, _>>()?
306 .join(" ");
307
308 if can_be_cached {
309 if let Some(original_sql) = self.sql {
310 let _ = self.cache.write().map(|mut cache| {
311 if let Some((_, cached_sql)) = cache.get_mut(&original_sql) {
312 *cached_sql = Some(sql.clone().into());
313 }
314 });
315 }
316 }
317
318 Ok((sql, placeholder_values))
319 }
320
321 #[inline]
323 pub fn bind<C, V>(mut self, name: C, value: V) -> Self
324 where
325 C: ToString,
326 V: Into<Value>,
327 {
328 let name = name.to_string();
329 let value = value.into();
330 let value: PlaceholderValue = value.into();
331
332 for part in self.parts.iter_mut() {
333 if let SqlPart::Placeholder(part_name, part_value) = part {
334 if **part_name == *name.as_str() {
335 *part_value = Some(value.clone());
336 }
337 }
338 }
339
340 self
341 }
342
343 #[inline]
350 pub fn bind_vec<C, V>(mut self, name: C, value: Vec<V>) -> Result<Self, Error>
351 where
352 C: ToString,
353 V: Into<Value>,
354 {
355 let name = name.to_string();
356
357 if value.is_empty() {
358 return Err(Error::EmptyInClause(name));
359 }
360
361 let value: PlaceholderValue = value
362 .into_iter()
363 .map(|x| x.into())
364 .collect::<Vec<Value>>()
365 .into();
366
367 for part in self.parts.iter_mut() {
368 if let SqlPart::Placeholder(part_name, part_value) = part {
369 if **part_name == *name.as_str() {
370 *part_value = Some(value.clone());
371 }
372 }
373 }
374
375 Ok(self)
376 }
377
378 pub async fn pluck<C>(self, conn: &C) -> Result<Option<Value>, Error>
380 where
381 C: DatabaseExecutor,
382 {
383 conn.pluck(self).await
384 }
385
386 pub async fn batch<C>(self, conn: &C) -> Result<(), Error>
388 where
389 C: DatabaseExecutor,
390 {
391 conn.batch(self).await
392 }
393
394 pub async fn execute<C>(self, conn: &C) -> Result<usize, Error>
396 where
397 C: DatabaseExecutor,
398 {
399 conn.execute(self).await
400 }
401
402 pub async fn fetch_one<C>(self, conn: &C) -> Result<Option<Vec<Column>>, Error>
404 where
405 C: DatabaseExecutor,
406 {
407 conn.fetch_one(self).await
408 }
409
410 pub async fn fetch_all<C>(self, conn: &C) -> Result<Vec<Vec<Column>>, Error>
412 where
413 C: DatabaseExecutor,
414 {
415 conn.fetch_all(self).await
416 }
417}
418
419#[inline(always)]
421pub fn query(sql: &str) -> Result<Statement, Error> {
422 static CACHE: LazyLock<Arc<RwLock<Cache>>> =
423 LazyLock::new(|| Arc::new(RwLock::new(HashMap::new())));
424 Statement::new(sql, CACHE.clone()).map_err(|e| Error::Database(Box::new(e)))
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430
431 #[test]
432 fn bind_vec_errors_on_empty_vec() {
433 let stmt = query("SELECT * FROM foo WHERE id IN (:ids)").unwrap();
434 let result = stmt.bind_vec("ids", Vec::<Vec<u8>>::new());
435 assert!(result.is_err());
436 assert!(matches!(result.unwrap_err(), Error::EmptyInClause(name) if name == "ids"));
437 }
438
439 #[test]
440 fn parser_preserves_postgres_cast_operator() {
441 let stmt = query("SELECT (ord - 1)::int AS matched WHERE id = :id")
442 .unwrap()
443 .bind("id", "quote-id");
444
445 let (sql, values) = stmt.to_sql().unwrap();
446
447 assert_eq!(sql, "SELECT (ord - 1)::int AS matched WHERE id = $1");
448 assert_eq!(values.len(), 1);
449 }
450
451 #[test]
452 fn bind_vec_ignores_cached_sql_for_same_query_string() {
453 let raw_sql = "SELECT * FROM cached_sql_vec_bug WHERE id IN (:ids)";
454
455 let (cached_sql, cached_values) =
456 query(raw_sql).unwrap().bind("ids", 1_i64).to_sql().unwrap();
457 assert!(cached_sql.contains("$1"));
458 assert_eq!(cached_values.len(), 1);
459
460 let (sql, values) = query(raw_sql)
461 .unwrap()
462 .bind_vec("ids", vec![1_i64, 2, 3])
463 .unwrap()
464 .to_sql()
465 .unwrap();
466
467 assert!(sql.contains("$1, $2, $3"));
468 assert_eq!(values.len(), 3);
469 }
470}