Skip to main content

fraiseql_db/dialect/
sqlite.rs

1//! SQLite SQL dialect implementation.
2
3use std::borrow::Cow;
4
5use super::trait_def::{RowViewColumnType, SqlDialect, UnsupportedOperator};
6
7/// SQLite dialect for [`GenericWhereGenerator`].
8///
9/// [`GenericWhereGenerator`]: crate::where_generator::GenericWhereGenerator
10pub struct SqliteDialect;
11
12impl SqlDialect for SqliteDialect {
13    fn name(&self) -> &'static str {
14        "SQLite"
15    }
16
17    fn quote_identifier(&self, name: &str) -> String {
18        format!("\"{}\"", name.replace('"', "\"\""))
19    }
20
21    fn json_extract_scalar(&self, column: &str, path: &[String]) -> String {
22        let json_path = crate::path_escape::escape_sqlite_json_path(path);
23        format!("json_extract({column}, '{json_path}')")
24    }
25
26    fn placeholder(&self, _n: usize) -> String {
27        "?".to_string()
28    }
29
30    fn cast_to_numeric<'a>(&self, expr: &'a str) -> Cow<'a, str> {
31        Cow::Owned(format!("CAST({expr} AS REAL)"))
32    }
33
34    fn always_false(&self) -> &'static str {
35        "1=0"
36    }
37
38    fn always_true(&self) -> &'static str {
39        "1=1"
40    }
41
42    fn json_array_length(&self, expr: &str) -> String {
43        format!("json_array_length({expr})")
44    }
45
46    fn row_view_column_expr(
47        &self,
48        json_column: &str,
49        field_name: &str,
50        col_type: &RowViewColumnType,
51    ) -> String {
52        // SQLite has limited CAST targets; use TEXT for most types.
53        let sqlite_type = match col_type {
54            RowViewColumnType::Text
55            | RowViewColumnType::Uuid
56            | RowViewColumnType::Timestamptz
57            | RowViewColumnType::Date => "TEXT",
58            RowViewColumnType::Int32 | RowViewColumnType::Int64 | RowViewColumnType::Boolean => {
59                "INTEGER"
60            },
61            RowViewColumnType::Float64 => "REAL",
62            RowViewColumnType::Json => "TEXT",
63        };
64        format!("CAST(json_extract({json_column}, '$.{field_name}') AS {sqlite_type})")
65    }
66
67    fn create_row_view_ddl(
68        &self,
69        view_name: &str,
70        source_table: &str,
71        columns: &[(String, String)],
72    ) -> String {
73        let quoted_view = self.quote_identifier(view_name);
74        let quoted_table = self.quote_identifier(source_table);
75        let col_list: Vec<String> = columns
76            .iter()
77            .map(|(alias, expr)| format!("{expr} AS {}", self.quote_identifier(alias)))
78            .collect();
79        // SQLite does not support CREATE OR REPLACE VIEW.
80        format!(
81            "DROP VIEW IF EXISTS {quoted_view};\nCREATE VIEW {quoted_view} AS\nSELECT\n  {}\nFROM {quoted_table};",
82            col_list.join(",\n  ")
83        )
84    }
85
86    fn array_contains_sql(&self, lhs: &str, rhs: &str) -> Result<String, UnsupportedOperator> {
87        // SQLite has no native @>; use EXISTS + json_each()
88        Ok(format!("EXISTS (SELECT 1 FROM json_each({lhs}) WHERE value = json({rhs}))"))
89    }
90}