1use std::collections::HashMap;
7
8use rusqlite::types::Value;
9use serde_json::Value as JsonValue;
10use tracing::{debug, instrument, trace};
11
12use prax_query::dialect::{SqlDialect, Sqlite};
13use prax_query::filter::FilterValue;
14use prax_query::types::SortOrder;
15
16use crate::error::SqliteError;
17use crate::pool::SqlitePool;
18use crate::types::filter_value_to_sqlite;
19
20#[derive(Clone)]
22pub struct SqliteRawEngine {
23 pool: SqlitePool,
24}
25
26#[derive(Debug, Clone)]
28pub struct SqliteJsonRow {
29 pub data: JsonValue,
31}
32
33impl SqliteJsonRow {
34 pub fn new(data: JsonValue) -> Self {
36 Self { data }
37 }
38
39 pub fn json(&self) -> &JsonValue {
41 &self.data
42 }
43
44 pub fn into_json(self) -> JsonValue {
46 self.data
47 }
48}
49
50impl SqliteRawEngine {
51 pub fn new(pool: SqlitePool) -> Self {
53 Self { pool }
54 }
55
56 pub fn pool(&self) -> &SqlitePool {
58 &self.pool
59 }
60
61 fn build_select(
63 &self,
64 table: &str,
65 columns: &[String],
66 filters: &HashMap<String, FilterValue>,
67 sort: &[(String, SortOrder)],
68 limit: Option<u64>,
69 offset: Option<u64>,
70 ) -> (String, Vec<Value>) {
71 let mut sql = String::new();
72 let mut params: Vec<Value> = Vec::new();
73
74 let cols = if columns.is_empty() {
76 "*".to_string()
77 } else {
78 columns
79 .iter()
80 .map(|c| Sqlite.quote_ident(c))
81 .collect::<Vec<_>>()
82 .join(", ")
83 };
84 sql.push_str(&format!(
85 "SELECT {} FROM {}",
86 cols,
87 Sqlite.quote_ident(table)
88 ));
89
90 if !filters.is_empty() {
92 let mut conditions = Vec::new();
93 for (field, value) in filters {
94 match value {
95 FilterValue::Null => {
96 conditions.push(format!("{} IS NULL", Sqlite.quote_ident(field)));
97 }
98 _ => {
99 conditions.push(format!("{} = ?", Sqlite.quote_ident(field)));
100 params.push(filter_value_to_sqlite(value));
101 }
102 }
103 }
104 sql.push_str(" WHERE ");
105 sql.push_str(&conditions.join(" AND "));
106 }
107
108 if !sort.is_empty() {
110 let order_parts: Vec<String> = sort
111 .iter()
112 .map(|(col, dir)| {
113 let direction = match dir {
114 SortOrder::Asc => "ASC",
115 SortOrder::Desc => "DESC",
116 };
117 format!("{} {}", Sqlite.quote_ident(col), direction)
118 })
119 .collect();
120 sql.push_str(" ORDER BY ");
121 sql.push_str(&order_parts.join(", "));
122 }
123
124 if let Some(lim) = limit {
126 sql.push_str(&format!(" LIMIT {}", lim));
127 }
128 if let Some(off) = offset {
129 sql.push_str(&format!(" OFFSET {}", off));
130 }
131
132 (sql, params)
133 }
134
135 fn build_insert(
137 &self,
138 table: &str,
139 data: &HashMap<String, FilterValue>,
140 ) -> (String, Vec<Value>) {
141 let mut columns = Vec::new();
142 let mut placeholders = Vec::new();
143 let mut params: Vec<Value> = Vec::new();
144
145 for (col, val) in data {
146 columns.push(Sqlite.quote_ident(col));
147 placeholders.push("?".to_string());
148 params.push(filter_value_to_sqlite(val));
149 }
150
151 let sql = format!(
152 "INSERT INTO {} ({}) VALUES ({})",
153 Sqlite.quote_ident(table),
154 columns.join(", "),
155 placeholders.join(", ")
156 );
157
158 (sql, params)
159 }
160
161 fn build_update(
163 &self,
164 table: &str,
165 data: &HashMap<String, FilterValue>,
166 filters: &HashMap<String, FilterValue>,
167 ) -> (String, Vec<Value>) {
168 let mut params: Vec<Value> = Vec::new();
169
170 let set_parts: Vec<String> = data
172 .iter()
173 .map(|(col, val)| {
174 params.push(filter_value_to_sqlite(val));
175 format!("{} = ?", Sqlite.quote_ident(col))
176 })
177 .collect();
178
179 let mut sql = format!(
180 "UPDATE {} SET {}",
181 Sqlite.quote_ident(table),
182 set_parts.join(", ")
183 );
184
185 if !filters.is_empty() {
187 let mut conditions = Vec::new();
188 for (field, value) in filters {
189 match value {
190 FilterValue::Null => {
191 conditions.push(format!("{} IS NULL", Sqlite.quote_ident(field)));
192 }
193 _ => {
194 conditions.push(format!("{} = ?", Sqlite.quote_ident(field)));
195 params.push(filter_value_to_sqlite(value));
196 }
197 }
198 }
199 sql.push_str(" WHERE ");
200 sql.push_str(&conditions.join(" AND "));
201 }
202
203 (sql, params)
204 }
205
206 fn build_delete(
208 &self,
209 table: &str,
210 filters: &HashMap<String, FilterValue>,
211 ) -> (String, Vec<Value>) {
212 let mut sql = format!("DELETE FROM {}", Sqlite.quote_ident(table));
213 let mut params: Vec<Value> = Vec::new();
214
215 if !filters.is_empty() {
216 let mut conditions = Vec::new();
217 for (field, value) in filters {
218 match value {
219 FilterValue::Null => {
220 conditions.push(format!("{} IS NULL", Sqlite.quote_ident(field)));
221 }
222 _ => {
223 conditions.push(format!("{} = ?", Sqlite.quote_ident(field)));
224 params.push(filter_value_to_sqlite(value));
225 }
226 }
227 }
228 sql.push_str(" WHERE ");
229 sql.push_str(&conditions.join(" AND "));
230 }
231
232 (sql, params)
233 }
234
235 #[instrument(skip(self, columns, filters, sort), fields(table = %table))]
237 pub async fn query_many(
238 &self,
239 table: &str,
240 columns: &[String],
241 filters: &HashMap<String, FilterValue>,
242 sort: &[(String, SortOrder)],
243 limit: Option<u64>,
244 offset: Option<u64>,
245 ) -> Result<Vec<SqliteJsonRow>, SqliteError> {
246 let (sql, params) = self.build_select(table, columns, filters, sort, limit, offset);
247 trace!(sql = %sql, "Executing query_many");
248
249 let conn = self.pool.get().await?;
250
251 let results = conn.query_params(&sql, params).await?;
252
253 Ok(results.into_iter().map(SqliteJsonRow::new).collect())
254 }
255
256 #[instrument(skip(self, columns, filters), fields(table = %table))]
258 pub async fn query_one(
259 &self,
260 table: &str,
261 columns: &[String],
262 filters: &HashMap<String, FilterValue>,
263 ) -> Result<SqliteJsonRow, SqliteError> {
264 let (sql, params) = self.build_select(table, columns, filters, &[], Some(1), None);
265 trace!(sql = %sql, "Executing query_one");
266
267 let conn = self.pool.get().await?;
268
269 let results = conn.query_params(&sql, params).await?;
270
271 results
272 .into_iter()
273 .next()
274 .map(SqliteJsonRow::new)
275 .ok_or_else(|| {
276 SqliteError::query(format!(
277 "No row found in table '{}' with the given filters",
278 table
279 ))
280 })
281 }
282
283 #[instrument(skip(self, columns, filters), fields(table = %table))]
285 pub async fn query_optional(
286 &self,
287 table: &str,
288 columns: &[String],
289 filters: &HashMap<String, FilterValue>,
290 ) -> Result<Option<SqliteJsonRow>, SqliteError> {
291 let (sql, params) = self.build_select(table, columns, filters, &[], Some(1), None);
292 trace!(sql = %sql, "Executing query_optional");
293
294 let conn = self.pool.get().await?;
295
296 let results = conn.query_params(&sql, params).await?;
297
298 Ok(results.into_iter().next().map(SqliteJsonRow::new))
299 }
300
301 #[instrument(skip(self, data), fields(table = %table))]
308 pub async fn execute_insert(
309 &self,
310 table: &str,
311 data: &HashMap<String, FilterValue>,
312 ) -> Result<SqliteJsonRow, SqliteError> {
313 let (sql, params) = self.build_insert(table, data);
314 trace!(sql = %sql, "Executing insert");
315
316 let conn = self.pool.get().await?;
317
318 let last_rowid = conn.execute_insert_params(&sql, params).await?;
319
320 let mut result = data.clone();
322 if !result.contains_key("id") {
323 result.insert("id".to_string(), FilterValue::Int(last_rowid));
324 }
325
326 let json = result
327 .into_iter()
328 .map(|(k, v)| (k, filter_value_to_json(&v)))
329 .collect::<serde_json::Map<_, _>>();
330
331 Ok(SqliteJsonRow::new(JsonValue::Object(json)))
332 }
333
334 #[instrument(skip(self, data, filters), fields(table = %table))]
336 pub async fn execute_update(
337 &self,
338 table: &str,
339 data: &HashMap<String, FilterValue>,
340 filters: &HashMap<String, FilterValue>,
341 ) -> Result<u64, SqliteError> {
342 let (sql, params) = self.build_update(table, data, filters);
343 trace!(sql = %sql, "Executing update");
344
345 let conn = self.pool.get().await?;
346
347 let affected = conn.execute_params(&sql, params).await?;
348
349 Ok(affected as u64)
350 }
351
352 #[instrument(skip(self, filters), fields(table = %table))]
354 pub async fn execute_delete(
355 &self,
356 table: &str,
357 filters: &HashMap<String, FilterValue>,
358 ) -> Result<u64, SqliteError> {
359 let (sql, params) = self.build_delete(table, filters);
360 trace!(sql = %sql, "Executing delete");
361
362 let conn = self.pool.get().await?;
363
364 let affected = conn.execute_params(&sql, params).await?;
365
366 Ok(affected as u64)
367 }
368
369 #[instrument(skip(self, params), fields(sql = %sql))]
371 pub async fn execute_raw(
372 &self,
373 sql: &str,
374 params: &[FilterValue],
375 ) -> Result<Vec<SqliteJsonRow>, SqliteError> {
376 debug!("Executing raw SQL");
377
378 let sqlite_params: Vec<Value> = params.iter().map(filter_value_to_sqlite).collect();
379
380 let conn = self.pool.get().await?;
381
382 let results = conn.query_params(sql, sqlite_params).await?;
383
384 Ok(results.into_iter().map(SqliteJsonRow::new).collect())
385 }
386
387 #[instrument(skip(self, sql))]
406 pub async fn raw_sql(
407 &self,
408 sql: prax_query::raw::Sql,
409 ) -> Result<Vec<SqliteJsonRow>, SqliteError> {
410 let (query_string, params) = sql.build();
411 debug!(sql = %query_string, "Executing raw SQL from builder");
412 self.raw_sql_query(&query_string, ¶ms).await
413 }
414
415 #[instrument(skip(self, params), fields(sql = %sql))]
426 pub async fn raw_sql_query(
427 &self,
428 sql: &str,
429 params: &[FilterValue],
430 ) -> Result<Vec<SqliteJsonRow>, SqliteError> {
431 debug!("Executing raw SQL query");
432
433 let sqlite_params: Vec<Value> = params.iter().map(filter_value_to_sqlite).collect();
434
435 let conn = self.pool.get().await?;
436
437 let results = conn.query_params(sql, sqlite_params).await?;
438
439 Ok(results.into_iter().map(SqliteJsonRow::new).collect())
440 }
441
442 #[instrument(skip(self, params), fields(sql = %sql))]
456 pub async fn raw_sql_execute(
457 &self,
458 sql: &str,
459 params: &[FilterValue],
460 ) -> Result<u64, SqliteError> {
461 debug!("Executing raw SQL statement");
462
463 let sqlite_params: Vec<Value> = params.iter().map(filter_value_to_sqlite).collect();
464
465 let conn = self.pool.get().await?;
466
467 let affected = conn.execute_params(sql, sqlite_params).await?;
468
469 Ok(affected as u64)
470 }
471
472 #[instrument(skip(self, params), fields(sql = %sql))]
485 pub async fn raw_sql_first(
486 &self,
487 sql: &str,
488 params: &[FilterValue],
489 ) -> Result<SqliteJsonRow, SqliteError> {
490 debug!("Executing raw SQL first");
491
492 let sqlite_params: Vec<Value> = params.iter().map(filter_value_to_sqlite).collect();
493
494 let conn = self.pool.get().await?;
495
496 let results = conn.query_params(sql, sqlite_params).await?;
497
498 results
499 .into_iter()
500 .next()
501 .map(SqliteJsonRow::new)
502 .ok_or_else(|| SqliteError::query("raw_sql_first returned no rows"))
503 }
504
505 #[instrument(skip(self, params), fields(sql = %sql))]
516 pub async fn raw_sql_optional(
517 &self,
518 sql: &str,
519 params: &[FilterValue],
520 ) -> Result<Option<SqliteJsonRow>, SqliteError> {
521 debug!("Executing raw SQL optional");
522
523 let sqlite_params: Vec<Value> = params.iter().map(filter_value_to_sqlite).collect();
524
525 let conn = self.pool.get().await?;
526
527 let results = conn.query_params(sql, sqlite_params).await?;
528
529 Ok(results.into_iter().next().map(SqliteJsonRow::new))
530 }
531
532 #[instrument(skip(self, params), fields(sql = %sql))]
543 pub async fn raw_sql_scalar<T>(
544 &self,
545 sql: &str,
546 params: &[FilterValue],
547 ) -> Result<T, SqliteError>
548 where
549 T: for<'a> serde::Deserialize<'a>,
550 {
551 debug!("Executing raw SQL scalar");
552
553 let sqlite_params: Vec<Value> = params.iter().map(filter_value_to_sqlite).collect();
554
555 let conn = self.pool.get().await?;
556
557 let results = conn.query_params(sql, sqlite_params).await?;
558
559 let row = results
560 .into_iter()
561 .next()
562 .ok_or_else(|| SqliteError::query("raw_sql_scalar returned no rows"))?;
563
564 let value = row
566 .as_object()
567 .and_then(|obj| obj.values().next())
568 .ok_or_else(|| SqliteError::query("raw_sql_scalar returned empty row"))?;
569
570 serde_json::from_value(value.clone()).map_err(|e| {
571 SqliteError::deserialization(format!("failed to deserialize scalar: {}", e))
572 })
573 }
574
575 #[instrument(skip(self), fields(sql_len = %sql.len()))]
588 pub async fn raw_sql_batch(&self, sql: &str) -> Result<(), SqliteError> {
589 debug!("Executing raw SQL batch");
590
591 let conn = self.pool.get().await?;
592
593 conn.execute_batch(sql).await
594 }
595
596 #[instrument(skip(self, filters), fields(table = %table))]
598 pub async fn count(
599 &self,
600 table: &str,
601 filters: &HashMap<String, FilterValue>,
602 ) -> Result<u64, SqliteError> {
603 let mut sql = format!(
604 "SELECT COUNT(*) as count FROM {}",
605 Sqlite.quote_ident(table)
606 );
607 let mut params: Vec<Value> = Vec::new();
608
609 if !filters.is_empty() {
610 let mut conditions = Vec::new();
611 for (field, value) in filters {
612 match value {
613 FilterValue::Null => {
614 conditions.push(format!("{} IS NULL", Sqlite.quote_ident(field)));
615 }
616 _ => {
617 conditions.push(format!("{} = ?", Sqlite.quote_ident(field)));
618 params.push(filter_value_to_sqlite(value));
619 }
620 }
621 }
622 sql.push_str(" WHERE ");
623 sql.push_str(&conditions.join(" AND "));
624 }
625
626 trace!(sql = %sql, "Executing count");
627
628 let conn = self.pool.get().await?;
629
630 let results = conn.query_params(&sql, params).await?;
631
632 let count = results
634 .first()
635 .and_then(|row| row.get("count"))
636 .and_then(|v| v.as_i64())
637 .unwrap_or(0);
638
639 Ok(count as u64)
640 }
641}
642
643fn filter_value_to_json(value: &FilterValue) -> JsonValue {
645 match value {
646 FilterValue::Null => JsonValue::Null,
647 FilterValue::Bool(b) => JsonValue::Bool(*b),
648 FilterValue::Int(i) => JsonValue::Number((*i).into()),
649 FilterValue::Float(f) => serde_json::Number::from_f64(*f)
650 .map(JsonValue::Number)
651 .unwrap_or(JsonValue::Null),
652 FilterValue::String(s) => JsonValue::String(s.clone()),
653 FilterValue::Json(j) => j.clone(),
654 FilterValue::List(list) => {
655 JsonValue::Array(list.iter().map(filter_value_to_json).collect())
656 }
657 }
658}
659
660#[cfg(test)]
661mod tests {
662 use super::*;
663
664 #[test]
665 fn test_quote_ident_simple() {
666 assert_eq!(Sqlite.quote_ident("users"), "\"users\"");
667 assert_eq!(Sqlite.quote_ident("user_name"), "\"user_name\"");
668 assert_eq!(Sqlite.quote_ident(""), "\"\"");
669 }
670
671 #[test]
672 fn test_quote_ident_doubles_embedded_quotes() {
673 assert_eq!(Sqlite.quote_ident("we\"ird"), "\"we\"\"ird\"");
674 assert_eq!(Sqlite.quote_ident("\"quoted\""), "\"\"\"quoted\"\"\"");
675 assert_eq!(Sqlite.quote_ident("a\"b\"c"), "\"a\"\"b\"\"c\"");
676 }
677
678 #[test]
679 fn test_filter_value_to_json() {
680 assert_eq!(filter_value_to_json(&FilterValue::Null), JsonValue::Null);
681 assert_eq!(
682 filter_value_to_json(&FilterValue::Bool(true)),
683 JsonValue::Bool(true)
684 );
685 assert_eq!(
686 filter_value_to_json(&FilterValue::Int(42)),
687 JsonValue::Number(42.into())
688 );
689 assert_eq!(
690 filter_value_to_json(&FilterValue::String("test".to_string())),
691 JsonValue::String("test".to_string())
692 );
693 }
694
695 #[test]
696 fn test_build_select_simple() {
697 let sql = "SELECT * FROM \"users\"";
698 assert!(sql.contains("SELECT"));
699 assert!(sql.contains("users"));
700 }
701
702 #[test]
703 fn test_query_result() {
704 let result = SqliteJsonRow::new(JsonValue::Object(serde_json::Map::new()));
705 assert!(result.json().is_object());
706 }
707
708 #[test]
709 fn test_query_result_into_json() {
710 let json = JsonValue::Object(serde_json::Map::new());
711 let result = SqliteJsonRow::new(json.clone());
712 assert_eq!(result.into_json(), json);
713 }
714
715 #[test]
716 fn test_sql_builder_integration() {
717 use prax_query::raw::Sql;
718
719 let sql = Sql::new("SELECT * FROM users WHERE age > ")
720 .bind(18)
721 .push(" AND active = ")
722 .bind(true);
723
724 let (query, params) = sql.build();
725 assert!(query.contains("SELECT"));
726 assert!(query.contains("users"));
727 assert_eq!(params.len(), 2);
728 }
729}