1use serde::{Deserialize, Serialize};
4use serde_json::Value as JsonValue;
5
6#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
8pub enum SqlParam {
9 Null,
11 Bool(bool),
13 Int(i64),
15 Float(f64),
17 Text(String),
19 Bytes(Vec<u8>),
21 Json(JsonValue),
23 Uuid(uuid::Uuid),
25 Timestamp(chrono::DateTime<chrono::Utc>),
27 Array(Vec<SqlParam>),
29}
30
31impl SqlParam {
32 pub fn text(s: impl Into<String>) -> Self {
34 Self::Text(s.into())
35 }
36
37 pub fn int(n: i64) -> Self {
39 Self::Int(n)
40 }
41
42 pub fn json(v: JsonValue) -> Self {
44 Self::Json(v)
45 }
46
47 pub fn is_null(&self) -> bool {
49 matches!(self, Self::Null)
50 }
51
52 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}