postrust_sql/
param.rs

1//! SQL parameter types.
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value as JsonValue;
5
6/// A SQL parameter value.
7#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
8pub enum SqlParam {
9    /// NULL value
10    Null,
11    /// Boolean
12    Bool(bool),
13    /// 64-bit integer
14    Int(i64),
15    /// 64-bit float
16    Float(f64),
17    /// Text string
18    Text(String),
19    /// Binary data
20    Bytes(Vec<u8>),
21    /// JSON value
22    Json(JsonValue),
23    /// UUID
24    Uuid(uuid::Uuid),
25    /// Timestamp
26    Timestamp(chrono::DateTime<chrono::Utc>),
27    /// Array of parameters
28    Array(Vec<SqlParam>),
29}
30
31impl SqlParam {
32    /// Create a text parameter.
33    pub fn text(s: impl Into<String>) -> Self {
34        Self::Text(s.into())
35    }
36
37    /// Create an integer parameter.
38    pub fn int(n: i64) -> Self {
39        Self::Int(n)
40    }
41
42    /// Create a JSON parameter.
43    pub fn json(v: JsonValue) -> Self {
44        Self::Json(v)
45    }
46
47    /// Check if this is a NULL value.
48    pub fn is_null(&self) -> bool {
49        matches!(self, Self::Null)
50    }
51
52    /// Get the PostgreSQL type name for this parameter.
53    pub fn pg_type(&self) -> &'static str {
54        match self {
55            Self::Null => "unknown",
56            Self::Bool(_) => "boolean",
57            Self::Int(_) => "bigint",
58            Self::Float(_) => "double precision",
59            Self::Text(_) => "text",
60            Self::Bytes(_) => "bytea",
61            Self::Json(_) => "jsonb",
62            Self::Uuid(_) => "uuid",
63            Self::Timestamp(_) => "timestamptz",
64            Self::Array(arr) => {
65                if let Some(first) = arr.first() {
66                    match first {
67                        Self::Text(_) => "text[]",
68                        Self::Int(_) => "bigint[]",
69                        Self::Bool(_) => "boolean[]",
70                        _ => "unknown[]",
71                    }
72                } else {
73                    "text[]"
74                }
75            }
76        }
77    }
78}
79
80impl From<String> for SqlParam {
81    fn from(s: String) -> Self {
82        Self::Text(s)
83    }
84}
85
86impl From<&str> for SqlParam {
87    fn from(s: &str) -> Self {
88        Self::Text(s.to_string())
89    }
90}
91
92impl From<i32> for SqlParam {
93    fn from(n: i32) -> Self {
94        Self::Int(n as i64)
95    }
96}
97
98impl From<i64> for SqlParam {
99    fn from(n: i64) -> Self {
100        Self::Int(n)
101    }
102}
103
104impl From<f64> for SqlParam {
105    fn from(n: f64) -> Self {
106        Self::Float(n)
107    }
108}
109
110impl From<bool> for SqlParam {
111    fn from(b: bool) -> Self {
112        Self::Bool(b)
113    }
114}
115
116impl From<JsonValue> for SqlParam {
117    fn from(v: JsonValue) -> Self {
118        Self::Json(v)
119    }
120}
121
122impl From<Vec<String>> for SqlParam {
123    fn from(v: Vec<String>) -> Self {
124        Self::Array(v.into_iter().map(SqlParam::Text).collect())
125    }
126}
127
128impl<T: Into<SqlParam>> From<Option<T>> for SqlParam {
129    fn from(opt: Option<T>) -> Self {
130        match opt {
131            Some(v) => v.into(),
132            None => Self::Null,
133        }
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::*;
140
141    #[test]
142    fn test_sql_param_types() {
143        assert_eq!(SqlParam::text("hello").pg_type(), "text");
144        assert_eq!(SqlParam::int(42).pg_type(), "bigint");
145        assert_eq!(SqlParam::Bool(true).pg_type(), "boolean");
146        assert_eq!(SqlParam::Null.pg_type(), "unknown");
147    }
148
149    #[test]
150    fn test_sql_param_from() {
151        let p: SqlParam = "hello".into();
152        assert!(matches!(p, SqlParam::Text(s) if s == "hello"));
153
154        let p: SqlParam = 42i64.into();
155        assert!(matches!(p, SqlParam::Int(42)));
156
157        let p: SqlParam = None::<String>.into();
158        assert!(p.is_null());
159    }
160}