prax_sqlx/
types.rs

1//! Type conversions for SQLx.
2
3use crate::config::DatabaseBackend;
4use prax_query::filter::FilterValue;
5
6/// Convert a FilterValue to the appropriate SQL placeholder string.
7pub fn placeholder(backend: DatabaseBackend, index: usize) -> String {
8    match backend {
9        DatabaseBackend::Postgres => format!("${}", index),
10        DatabaseBackend::MySql | DatabaseBackend::Sqlite => "?".to_string(),
11    }
12}
13
14/// Convert a FilterValue to a string representation for debugging.
15pub fn filter_value_to_string(value: &FilterValue) -> String {
16    match value {
17        FilterValue::String(s) => s.clone(),
18        FilterValue::Int(i) => i.to_string(),
19        FilterValue::Float(f) => f.to_string(),
20        FilterValue::Bool(b) => b.to_string(),
21        FilterValue::Null => "NULL".to_string(),
22        FilterValue::Json(j) => j.to_string(),
23        FilterValue::List(arr) => {
24            let items: Vec<String> = arr.iter().map(filter_value_to_string).collect();
25            format!("[{}]", items.join(", "))
26        }
27    }
28}
29
30/// Generate SQL for a list of placeholders.
31pub fn placeholders(backend: DatabaseBackend, count: usize, start: usize) -> String {
32    (start..start + count)
33        .map(|i| placeholder(backend, i))
34        .collect::<Vec<_>>()
35        .join(", ")
36}
37
38/// Quote an identifier for the given database backend.
39pub fn quote_identifier(backend: DatabaseBackend, name: &str) -> String {
40    match backend {
41        DatabaseBackend::Postgres => format!("\"{}\"", name.replace('"', "\"\"")),
42        DatabaseBackend::MySql => format!("`{}`", name.replace('`', "``")),
43        DatabaseBackend::Sqlite => format!("\"{}\"", name.replace('"', "\"\"")),
44    }
45}
46
47/// Convert Rust type to SQL type string.
48pub fn rust_to_sql_type(backend: DatabaseBackend, rust_type: &str) -> &'static str {
49    match backend {
50        DatabaseBackend::Postgres => match rust_type {
51            "i32" => "INTEGER",
52            "i64" => "BIGINT",
53            "f32" => "REAL",
54            "f64" => "DOUBLE PRECISION",
55            "bool" => "BOOLEAN",
56            "String" | "&str" => "TEXT",
57            "Vec<u8>" | "&[u8]" => "BYTEA",
58            "Uuid" => "UUID",
59            "DateTime" | "chrono::DateTime" => "TIMESTAMPTZ",
60            "NaiveDate" => "DATE",
61            "NaiveTime" => "TIME",
62            "Decimal" => "DECIMAL",
63            "Json" | "serde_json::Value" => "JSONB",
64            _ => "TEXT",
65        },
66        DatabaseBackend::MySql => match rust_type {
67            "i32" => "INT",
68            "i64" => "BIGINT",
69            "f32" => "FLOAT",
70            "f64" => "DOUBLE",
71            "bool" => "BOOLEAN",
72            "String" | "&str" => "TEXT",
73            "Vec<u8>" | "&[u8]" => "BLOB",
74            "Uuid" => "CHAR(36)",
75            "DateTime" | "chrono::DateTime" => "DATETIME",
76            "NaiveDate" => "DATE",
77            "NaiveTime" => "TIME",
78            "Decimal" => "DECIMAL",
79            "Json" | "serde_json::Value" => "JSON",
80            _ => "TEXT",
81        },
82        DatabaseBackend::Sqlite => match rust_type {
83            "i32" | "i64" => "INTEGER",
84            "f32" | "f64" => "REAL",
85            "bool" => "INTEGER", // SQLite uses 0/1 for booleans
86            "String" | "&str" => "TEXT",
87            "Vec<u8>" | "&[u8]" => "BLOB",
88            "Uuid" => "TEXT",
89            "DateTime" | "chrono::DateTime" => "TEXT", // ISO8601 string
90            "NaiveDate" => "TEXT",
91            "NaiveTime" => "TEXT",
92            "Decimal" => "TEXT",
93            "Json" | "serde_json::Value" => "TEXT", // JSON as text
94            _ => "TEXT",
95        },
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn test_placeholder() {
105        assert_eq!(placeholder(DatabaseBackend::Postgres, 1), "$1");
106        assert_eq!(placeholder(DatabaseBackend::Postgres, 5), "$5");
107        assert_eq!(placeholder(DatabaseBackend::MySql, 1), "?");
108        assert_eq!(placeholder(DatabaseBackend::Sqlite, 1), "?");
109    }
110
111    #[test]
112    fn test_placeholders() {
113        assert_eq!(placeholders(DatabaseBackend::Postgres, 3, 1), "$1, $2, $3");
114        assert_eq!(placeholders(DatabaseBackend::MySql, 3, 1), "?, ?, ?");
115    }
116
117    #[test]
118    fn test_quote_identifier() {
119        assert_eq!(
120            quote_identifier(DatabaseBackend::Postgres, "users"),
121            "\"users\""
122        );
123        assert_eq!(quote_identifier(DatabaseBackend::MySql, "users"), "`users`");
124        assert_eq!(
125            quote_identifier(DatabaseBackend::Sqlite, "users"),
126            "\"users\""
127        );
128
129        // Test escaping
130        assert_eq!(
131            quote_identifier(DatabaseBackend::Postgres, "user\"name"),
132            "\"user\"\"name\""
133        );
134    }
135
136    #[test]
137    fn test_rust_to_sql_type() {
138        assert_eq!(
139            rust_to_sql_type(DatabaseBackend::Postgres, "i32"),
140            "INTEGER"
141        );
142        assert_eq!(rust_to_sql_type(DatabaseBackend::MySql, "i32"), "INT");
143        assert_eq!(rust_to_sql_type(DatabaseBackend::Sqlite, "i32"), "INTEGER");
144
145        assert_eq!(
146            rust_to_sql_type(DatabaseBackend::Postgres, "bool"),
147            "BOOLEAN"
148        );
149        assert_eq!(rust_to_sql_type(DatabaseBackend::Sqlite, "bool"), "INTEGER");
150    }
151}