gluesql_core/data/value/
to_sql.rs

1use crate::{ast::ToSql, data::Value};
2
3impl ToSql for Value {
4    fn to_sql(&self) -> String {
5        match self {
6            Value::Bool(b) => b.to_string().to_uppercase(),
7            Value::I8(n) => n.to_string(),
8            Value::I16(n) => n.to_string(),
9            Value::I32(n) => n.to_string(),
10            Value::I64(n) => n.to_string(),
11            Value::I128(n) => n.to_string(),
12            Value::U8(n) => n.to_string(),
13            Value::U16(n) => n.to_string(),
14            Value::U32(n) => n.to_string(),
15            Value::U64(n) => n.to_string(),
16            Value::U128(n) => n.to_string(),
17            Value::F32(n) => n.to_string(),
18            Value::F64(n) => n.to_string(),
19            Value::Decimal(n) => n.to_string(),
20            Value::Str(s) => {
21                let escaped = s.replace('\'', "''");
22                format!("'{escaped}'")
23            }
24            Value::Bytea(bytes) => format!("X'{}'", hex::encode(bytes)),
25            Value::Inet(addr) => format!("'{addr}'"),
26            Value::Date(d) => format!("DATE '{d}'"),
27            Value::Timestamp(ts) => format!("TIMESTAMP '{ts}'"),
28            Value::Time(t) => format!("TIME '{t}'"),
29            Value::Interval(i) => format!("INTERVAL {}", i.to_sql_str()),
30            Value::Uuid(u) => format!("'{}'", uuid::Uuid::from_u128(*u).hyphenated()),
31            Value::Map(_) | Value::List(_) => {
32                let json: serde_json::Value =
33                    self.clone().try_into().unwrap_or(serde_json::Value::Null);
34                let escaped = json.to_string().replace('\'', "''");
35                format!("'{escaped}'")
36            }
37            Value::Point(p) => format!("POINT({} {})", p.x, p.y),
38            Value::Null => "NULL".to_owned(),
39        }
40    }
41}
42
43#[cfg(test)]
44mod tests {
45    use {
46        crate::{
47            ast::ToSql,
48            data::{Interval, Point, Value},
49        },
50        chrono::{NaiveDate, NaiveTime},
51        rust_decimal::Decimal,
52        std::{collections::BTreeMap, net::IpAddr, str::FromStr},
53    };
54
55    #[test]
56    fn to_sql() {
57        // Bool - true, false
58        assert_eq!(Value::Bool(true).to_sql(), "TRUE");
59        assert_eq!(Value::Bool(false).to_sql(), "FALSE");
60
61        // I8, I16, I32, I64, I128
62        assert_eq!(Value::I8(127).to_sql(), "127");
63        assert_eq!(Value::I16(32767).to_sql(), "32767");
64        assert_eq!(Value::I32(2_147_483_647).to_sql(), "2147483647");
65        assert_eq!(Value::I64(64).to_sql(), "64");
66        assert_eq!(Value::I128(128).to_sql(), "128");
67
68        // U8, U16, U32, U64, U128
69        assert_eq!(Value::U8(255).to_sql(), "255");
70        assert_eq!(Value::U16(65535).to_sql(), "65535");
71        assert_eq!(Value::U32(32).to_sql(), "32");
72        assert_eq!(Value::U64(64).to_sql(), "64");
73        assert_eq!(Value::U128(128).to_sql(), "128");
74
75        // F32, F64
76        assert_eq!(Value::F32(1.5).to_sql(), "1.5");
77        assert_eq!(Value::F64(2.5).to_sql(), "2.5");
78
79        // Decimal
80        assert_eq!(Value::Decimal(Decimal::new(314, 2)).to_sql(), "3.14");
81
82        // Str - plain, with quotes
83        assert_eq!(Value::Str("hello".to_owned()).to_sql(), "'hello'");
84        assert_eq!(Value::Str("it's".to_owned()).to_sql(), "'it''s'");
85
86        // Bytea
87        assert_eq!(
88            Value::Bytea(vec![0x48, 0x65, 0x6c, 0x6c, 0x6f]).to_sql(),
89            "X'48656c6c6f'"
90        );
91
92        // Inet - IPv4, IPv6
93        assert_eq!(
94            Value::Inet(IpAddr::from_str("192.168.1.1").unwrap()).to_sql(),
95            "'192.168.1.1'"
96        );
97        assert_eq!(
98            Value::Inet(IpAddr::from_str("::1").unwrap()).to_sql(),
99            "'::1'"
100        );
101
102        // Date
103        assert_eq!(
104            Value::Date(NaiveDate::from_ymd_opt(2024, 1, 15).unwrap()).to_sql(),
105            "DATE '2024-01-15'"
106        );
107
108        // Timestamp
109        assert_eq!(
110            Value::Timestamp(
111                NaiveDate::from_ymd_opt(2024, 1, 15)
112                    .unwrap()
113                    .and_hms_opt(13, 30, 45)
114                    .unwrap()
115            )
116            .to_sql(),
117            "TIMESTAMP '2024-01-15 13:30:45'"
118        );
119
120        // Time
121        assert_eq!(
122            Value::Time(NaiveTime::from_hms_opt(13, 30, 45).unwrap()).to_sql(),
123            "TIME '13:30:45'"
124        );
125
126        // Interval
127        assert_eq!(
128            Value::Interval(Interval::Month(14)).to_sql(),
129            "INTERVAL '1-2' YEAR TO MONTH"
130        );
131
132        // Uuid
133        assert_eq!(
134            Value::Uuid(0x936d_a01f_9abd_4d9d_80c7_02af_85c8_22a8_u128).to_sql(),
135            "'936da01f-9abd-4d9d-80c7-02af85c822a8'"
136        );
137
138        // Map
139        let map = BTreeMap::from([
140            ("a".to_owned(), Value::I64(1)),
141            ("b".to_owned(), Value::Bool(true)),
142        ]);
143        assert_eq!(Value::Map(map).to_sql(), "'{\"a\":1,\"b\":true}'");
144
145        // List
146        let list = vec![Value::I64(1), Value::I64(2), Value::I64(3)];
147        assert_eq!(Value::List(list).to_sql(), "'[1,2,3]'");
148
149        // Point
150        assert_eq!(
151            Value::Point(Point::new(1.5, 2.5)).to_sql(),
152            "POINT(1.5 2.5)"
153        );
154
155        // Null
156        assert_eq!(Value::Null.to_sql(), "NULL");
157    }
158}