prax_postgres/
types.rs

1//! Type conversions for PostgreSQL.
2
3use prax_query::filter::FilterValue;
4use tokio_postgres::types::{ToSql, Type};
5
6use crate::error::{PgError, PgResult};
7
8/// Convert a FilterValue to a type that can be used as a PostgreSQL parameter.
9pub fn filter_value_to_sql(value: &FilterValue) -> PgResult<Box<dyn ToSql + Sync + Send>> {
10    match value {
11        FilterValue::Null => Ok(Box::new(Option::<String>::None)),
12        FilterValue::Bool(b) => Ok(Box::new(*b)),
13        FilterValue::Int(i) => Ok(Box::new(*i)),
14        FilterValue::Float(f) => Ok(Box::new(*f)),
15        FilterValue::String(s) => Ok(Box::new(s.clone())),
16        FilterValue::Json(j) => Ok(Box::new(j.clone())),
17        FilterValue::List(_) => {
18            // Lists need special handling - they should be converted to arrays
19            // For now, return an error and handle lists specially in the engine
20            Err(PgError::type_conversion(
21                "list values should be handled specially",
22            ))
23        }
24    }
25}
26
27/// Convert filter values to PostgreSQL parameters.
28pub fn filter_values_to_params(
29    values: &[FilterValue],
30) -> PgResult<Vec<Box<dyn ToSql + Sync + Send>>> {
31    values.iter().map(filter_value_to_sql).collect()
32}
33
34/// PostgreSQL type mapping utilities.
35pub mod pg_types {
36    use super::*;
37
38    /// Get the PostgreSQL type for a Rust type name.
39    pub fn rust_type_to_pg(rust_type: &str) -> Option<Type> {
40        match rust_type {
41            "i16" => Some(Type::INT2),
42            "i32" => Some(Type::INT4),
43            "i64" => Some(Type::INT8),
44            "f32" => Some(Type::FLOAT4),
45            "f64" => Some(Type::FLOAT8),
46            "bool" => Some(Type::BOOL),
47            "String" | "&str" => Some(Type::TEXT),
48            "Vec<u8>" | "&[u8]" => Some(Type::BYTEA),
49            "chrono::NaiveDate" => Some(Type::DATE),
50            "chrono::NaiveTime" => Some(Type::TIME),
51            "chrono::NaiveDateTime" => Some(Type::TIMESTAMP),
52            "chrono::DateTime<chrono::Utc>" => Some(Type::TIMESTAMPTZ),
53            "uuid::Uuid" => Some(Type::UUID),
54            "serde_json::Value" => Some(Type::JSONB),
55            _ => None,
56        }
57    }
58
59    /// Get the Rust type for a PostgreSQL type.
60    pub fn pg_type_to_rust(pg_type: &Type) -> &'static str {
61        match *pg_type {
62            Type::BOOL => "bool",
63            Type::INT2 => "i16",
64            Type::INT4 => "i32",
65            Type::INT8 => "i64",
66            Type::FLOAT4 => "f32",
67            Type::FLOAT8 => "f64",
68            Type::TEXT | Type::VARCHAR | Type::CHAR | Type::NAME => "String",
69            Type::BYTEA => "Vec<u8>",
70            Type::DATE => "chrono::NaiveDate",
71            Type::TIME => "chrono::NaiveTime",
72            Type::TIMESTAMP => "chrono::NaiveDateTime",
73            Type::TIMESTAMPTZ => "chrono::DateTime<chrono::Utc>",
74            Type::UUID => "uuid::Uuid",
75            Type::JSON | Type::JSONB => "serde_json::Value",
76            _ => "unknown",
77        }
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn test_filter_value_to_sql() {
87        let result = filter_value_to_sql(&FilterValue::Int(42));
88        assert!(result.is_ok());
89
90        let result = filter_value_to_sql(&FilterValue::String("test".to_string()));
91        assert!(result.is_ok());
92
93        let result = filter_value_to_sql(&FilterValue::Bool(true));
94        assert!(result.is_ok());
95    }
96
97    #[test]
98    fn test_pg_type_mapping() {
99        use pg_types::*;
100
101        assert_eq!(rust_type_to_pg("i32"), Some(Type::INT4));
102        assert_eq!(rust_type_to_pg("String"), Some(Type::TEXT));
103        assert_eq!(rust_type_to_pg("bool"), Some(Type::BOOL));
104
105        assert_eq!(pg_type_to_rust(&Type::INT4), "i32");
106        assert_eq!(pg_type_to_rust(&Type::TEXT), "String");
107    }
108}