clickhouse_postgres_client/
type_info.rs

1use core::num::ParseIntError;
2use std::net::{AddrParseError, Ipv4Addr};
3
4#[cfg(feature = "chrono")]
5use clickhouse_data_value::datetime::{
6    NaiveDateTime as DatetimeNaiveDateTime, ParseError as DatetimeParseError,
7};
8#[cfg(feature = "num-bigint")]
9use num_bigint::{BigInt, BigUint, ParseBigIntError};
10use sqlx_clickhouse_ext::sqlx_core::error::Error;
11#[cfg(feature = "chrono")]
12use sqlx_clickhouse_ext::sqlx_core::types::chrono::{NaiveDate, NaiveDateTime};
13#[cfg(feature = "bigdecimal")]
14use sqlx_clickhouse_ext::sqlx_core::types::BigDecimal;
15#[cfg(feature = "uuid")]
16use sqlx_clickhouse_ext::sqlx_core::types::Uuid;
17
18// https://github.com/ClickHouse/ClickHouse/blob/master/src/Core/PostgreSQLProtocol.cpp
19pub(crate) enum ClickhousePgType {
20    Char,
21    Int2,
22    Int4,
23    Int8,
24    Float4,
25    Float8,
26    Varchar,
27    #[cfg(feature = "chrono")]
28    Date,
29    #[cfg(feature = "bigdecimal")]
30    Numeric,
31    #[cfg(feature = "uuid")]
32    Uuid,
33}
34
35impl TryFrom<(&str, usize)> for ClickhousePgType {
36    type Error = Error;
37
38    fn try_from(t: (&str, usize)) -> Result<Self, Self::Error> {
39        let (s, index) = t;
40
41        // https://github.com/launchbadge/sqlx/blob/v0.5.1/sqlx-core/src/postgres/type_info.rs#L447-L541
42        match s {
43            "\"CHAR\"" => Ok(Self::Char),
44            "INT2" => Ok(Self::Int2),
45            "INT4" => Ok(Self::Int4),
46            "INT8" => Ok(Self::Int8),
47            "FLOAT4" => Ok(Self::Float4),
48            "FLOAT8" => Ok(Self::Float8),
49            "VARCHAR" => Ok(Self::Varchar),
50            #[cfg(feature = "chrono")]
51            "DATE" => Ok(Self::Date),
52            #[cfg(not(feature = "chrono"))]
53            "DATE" => Err(Error::ColumnDecode {
54                index: format!("{index:?}"),
55                source: format!("unknown SQL type `{}`, should enable chrono feature", s).into(),
56            }),
57            #[cfg(feature = "bigdecimal")]
58            "NUMERIC" => Ok(Self::Numeric),
59            #[cfg(not(feature = "bigdecimal"))]
60            "NUMERIC" => Err(Error::ColumnDecode {
61                index: format!("{index:?}"),
62                source: format!("unknown SQL type `{}`, should enable bigdecimal feature", s)
63                    .into(),
64            }),
65            #[cfg(feature = "uuid")]
66            "UUID" => Ok(Self::Uuid),
67            #[cfg(not(feature = "uuid"))]
68            "UUID" => Err(Error::ColumnDecode {
69                index: format!("{index:?}"),
70                source: format!("unknown SQL type `{}`, should enable uuid feature", s).into(),
71            }),
72            _ => Err(Error::ColumnDecode {
73                index: format!("{index:?}"),
74                source: format!("unknown SQL type `{s}`").into(),
75            }),
76        }
77    }
78}
79
80#[derive(PartialEq, Debug, Clone)]
81pub enum ClickhousePgValue {
82    Bool(bool),
83    Char(i8),
84    I16(i16),
85    I32(i32),
86    I64(i64),
87    F32(f32),
88    F64(f64),
89    String(String),
90    #[cfg(feature = "chrono")]
91    NaiveDate(NaiveDate),
92    #[cfg(feature = "bigdecimal")]
93    BigDecimal(BigDecimal),
94    #[cfg(feature = "uuid")]
95    Uuid(Uuid),
96}
97impl From<bool> for ClickhousePgValue {
98    fn from(val: bool) -> Self {
99        Self::Bool(val)
100    }
101}
102impl From<i8> for ClickhousePgValue {
103    fn from(val: i8) -> Self {
104        Self::Char(val)
105    }
106}
107impl From<i16> for ClickhousePgValue {
108    fn from(val: i16) -> Self {
109        Self::I16(val)
110    }
111}
112impl From<u8> for ClickhousePgValue {
113    fn from(val: u8) -> Self {
114        Self::I16(val.into())
115    }
116}
117impl From<i32> for ClickhousePgValue {
118    fn from(val: i32) -> Self {
119        Self::I32(val)
120    }
121}
122impl From<u16> for ClickhousePgValue {
123    fn from(val: u16) -> Self {
124        Self::I32(val.into())
125    }
126}
127impl From<i64> for ClickhousePgValue {
128    fn from(val: i64) -> Self {
129        Self::I64(val)
130    }
131}
132impl From<u32> for ClickhousePgValue {
133    fn from(val: u32) -> Self {
134        Self::I64(val.into())
135    }
136}
137impl From<f32> for ClickhousePgValue {
138    fn from(val: f32) -> Self {
139        Self::F32(val)
140    }
141}
142impl From<f64> for ClickhousePgValue {
143    fn from(val: f64) -> Self {
144        Self::F64(val)
145    }
146}
147impl From<String> for ClickhousePgValue {
148    fn from(val: String) -> Self {
149        Self::String(val)
150    }
151}
152impl From<&str> for ClickhousePgValue {
153    fn from(val: &str) -> Self {
154        Self::String(val.into())
155    }
156}
157#[cfg(feature = "chrono")]
158impl From<NaiveDate> for ClickhousePgValue {
159    fn from(val: NaiveDate) -> Self {
160        Self::NaiveDate(val)
161    }
162}
163#[cfg(feature = "bigdecimal")]
164impl From<BigDecimal> for ClickhousePgValue {
165    fn from(val: BigDecimal) -> Self {
166        Self::BigDecimal(val)
167    }
168}
169#[cfg(feature = "uuid")]
170impl From<Uuid> for ClickhousePgValue {
171    fn from(val: Uuid) -> Self {
172        Self::Uuid(val)
173    }
174}
175
176impl ClickhousePgValue {
177    pub fn as_bool(&self) -> Option<bool> {
178        match *self {
179            Self::Bool(v) => Some(v),
180            Self::Char(v) if v == '1' as i8 => Some(true),
181            Self::Char(v) if v == '0' as i8 => Some(false),
182            _ => self.as_u8().and_then(|v| match v {
183                1 => Some(true),
184                0 => Some(false),
185                _ => None,
186            }),
187        }
188    }
189    pub fn as_char(&self) -> Option<i8> {
190        match *self {
191            Self::Char(v) => Some(v),
192            _ => None,
193        }
194    }
195    pub fn as_u8(&self) -> Option<u8> {
196        match *self {
197            Self::I16(v) if (u8::MIN as i16..=u8::MAX as i16).contains(&v) => Some(v as u8),
198            _ => None,
199        }
200    }
201    pub fn as_i16(&self) -> Option<i16> {
202        match *self {
203            Self::I16(v) => Some(v),
204            _ => None,
205        }
206    }
207    pub fn as_u16(&self) -> Option<u16> {
208        match *self {
209            Self::I32(v) if (u16::MIN as i32..=u16::MAX as i32).contains(&v) => Some(v as u16),
210            _ => None,
211        }
212    }
213    pub fn as_i32(&self) -> Option<i32> {
214        match *self {
215            Self::I32(v) => Some(v),
216            _ => None,
217        }
218    }
219    pub fn as_u32(&self) -> Option<u32> {
220        match *self {
221            Self::I64(v) if (u32::MIN as i64..=u32::MAX as i64).contains(&v) => Some(v as u32),
222            _ => None,
223        }
224    }
225    pub fn as_i64(&self) -> Option<i64> {
226        match *self {
227            Self::I64(v) => Some(v),
228            _ => None,
229        }
230    }
231    pub fn as_u64(&self) -> Option<Result<u64, ParseIntError>> {
232        match *self {
233            Self::String(ref v) => Some(v.parse()),
234            _ => None,
235        }
236    }
237    pub fn as_i128(&self) -> Option<Result<i128, ParseIntError>> {
238        match *self {
239            Self::String(ref v) => Some(v.parse()),
240            _ => None,
241        }
242    }
243    pub fn as_u128(&self) -> Option<Result<u128, ParseIntError>> {
244        match *self {
245            Self::String(ref v) => Some(v.parse()),
246            _ => None,
247        }
248    }
249    #[cfg(feature = "num-bigint")]
250    pub fn as_big_int(&self) -> Option<Result<BigInt, ParseBigIntError>> {
251        match *self {
252            Self::String(ref v) => Some(v.parse()),
253            _ => None,
254        }
255    }
256    #[cfg(feature = "num-bigint")]
257    pub fn as_big_uint(&self) -> Option<Result<BigUint, ParseBigIntError>> {
258        match *self {
259            Self::String(ref v) => Some(v.parse()),
260            _ => None,
261        }
262    }
263    pub fn as_f32(&self) -> Option<f32> {
264        match *self {
265            Self::F32(v) => Some(v),
266            _ => None,
267        }
268    }
269    pub fn as_f64(&self) -> Option<f64> {
270        match *self {
271            Self::F64(v) => Some(v),
272            _ => None,
273        }
274    }
275    pub fn as_str(&self) -> Option<&str> {
276        match *self {
277            Self::String(ref v) => Some(v),
278            _ => None,
279        }
280    }
281    #[cfg(feature = "chrono")]
282    pub fn as_naive_date(&self) -> Option<&NaiveDate> {
283        match *self {
284            Self::NaiveDate(ref v) => Some(v),
285            _ => None,
286        }
287    }
288    #[cfg(feature = "bigdecimal")]
289    pub fn as_big_decimal(&self) -> Option<&BigDecimal> {
290        match *self {
291            Self::BigDecimal(ref v) => Some(v),
292            _ => None,
293        }
294    }
295    #[cfg(feature = "uuid")]
296    pub fn as_uuid(&self) -> Option<&Uuid> {
297        match *self {
298            Self::Uuid(ref v) => Some(v),
299            _ => None,
300        }
301    }
302
303    #[cfg(feature = "chrono")]
304    pub fn as_naive_date_time(&self) -> Option<Result<NaiveDateTime, DatetimeParseError>> {
305        match *self {
306            Self::String(ref v) => Some(v.parse::<DatetimeNaiveDateTime>().map(|x| x.0)),
307            _ => None,
308        }
309    }
310
311    pub fn as_ipv4_addr(&self) -> Option<Result<Ipv4Addr, AddrParseError>> {
312        match *self {
313            Self::String(ref v) => Some(v.parse()),
314            _ => self.as_u32().map(|v| Ok(v.into())),
315        }
316    }
317}
318
319#[cfg(test)]
320mod tests {
321    use super::*;
322
323    #[test]
324    fn test_as_bool() {
325        assert_eq!(ClickhousePgValue::from(false).as_bool(), Some(false));
326        assert_eq!(ClickhousePgValue::from(true).as_bool(), Some(true));
327
328        assert_eq!(ClickhousePgValue::from('0' as i8).as_bool(), Some(false));
329        assert_eq!(ClickhousePgValue::from('1' as i8).as_bool(), Some(true));
330        assert_eq!(ClickhousePgValue::from('2' as i8).as_bool(), None);
331
332        assert_eq!(ClickhousePgValue::from(0_u8).as_bool(), Some(false));
333        assert_eq!(ClickhousePgValue::from(1_u8).as_bool(), Some(true));
334        assert_eq!(ClickhousePgValue::from(2_u8).as_bool(), None);
335    }
336    #[test]
337    fn test_as_char() {
338        assert_eq!(
339            ClickhousePgValue::from('3' as i8).as_char(),
340            Some('3' as i8)
341        );
342    }
343    #[test]
344    fn test_as_u8() {
345        assert_eq!(ClickhousePgValue::from(u8::MIN).as_u8(), Some(u8::MIN));
346        assert_eq!(ClickhousePgValue::from(u8::MAX).as_u8(), Some(u8::MAX));
347    }
348    #[test]
349    fn test_as_i16() {
350        assert_eq!(ClickhousePgValue::from(i16::MIN).as_i16(), Some(i16::MIN));
351        assert_eq!(ClickhousePgValue::from(i16::MAX).as_i16(), Some(i16::MAX));
352    }
353    #[test]
354    fn test_as_u16() {
355        assert_eq!(ClickhousePgValue::from(u16::MIN).as_u16(), Some(u16::MIN));
356        assert_eq!(ClickhousePgValue::from(u16::MAX).as_u16(), Some(u16::MAX));
357    }
358    #[test]
359    fn test_as_i32() {
360        assert_eq!(ClickhousePgValue::from(i32::MIN).as_i32(), Some(i32::MIN));
361        assert_eq!(ClickhousePgValue::from(i32::MAX).as_i32(), Some(i32::MAX));
362    }
363    #[test]
364    fn test_as_u32() {
365        assert_eq!(ClickhousePgValue::from(u32::MIN).as_u32(), Some(u32::MIN));
366        assert_eq!(ClickhousePgValue::from(u32::MAX).as_u32(), Some(u32::MAX));
367    }
368    #[test]
369    fn test_as_i64() {
370        assert_eq!(ClickhousePgValue::from(i64::MIN).as_i64(), Some(i64::MIN));
371        assert_eq!(ClickhousePgValue::from(i64::MAX).as_i64(), Some(i64::MAX));
372    }
373    #[test]
374    fn test_as_u64() {
375        assert_eq!(
376            ClickhousePgValue::from(format!("{}", u64::MIN)).as_u64(),
377            Some(Ok(u64::MIN))
378        );
379        assert_eq!(
380            ClickhousePgValue::from(format!("{}", u64::MAX)).as_u64(),
381            Some(Ok(u64::MAX))
382        );
383    }
384    #[test]
385    fn test_as_i128() {
386        assert_eq!(
387            ClickhousePgValue::from(format!("{}", i128::MIN)).as_i128(),
388            Some(Ok(i128::MIN))
389        );
390        assert_eq!(
391            ClickhousePgValue::from(format!("{}", i128::MAX)).as_i128(),
392            Some(Ok(i128::MAX))
393        );
394    }
395    #[test]
396    fn test_as_u128() {
397        assert_eq!(
398            ClickhousePgValue::from(format!("{}", u128::MIN)).as_u128(),
399            Some(Ok(u128::MIN))
400        );
401        assert_eq!(
402            ClickhousePgValue::from(format!("{}", u128::MAX)).as_u128(),
403            Some(Ok(u128::MAX))
404        );
405    }
406    #[cfg(feature = "num-bigint")]
407    #[test]
408    fn test_as_big_int() {
409        assert_eq!(
410            ClickhousePgValue::from(
411                "-57896044618658097711785492504343953926634992332820282019728792003956564819968"
412            )
413            .as_big_int(),
414            Some(Ok(BigInt::parse_bytes(
415                b"-57896044618658097711785492504343953926634992332820282019728792003956564819968",
416                10
417            )
418            .unwrap()))
419        );
420        assert_eq!(
421            ClickhousePgValue::from(
422                "57896044618658097711785492504343953926634992332820282019728792003956564819967"
423            )
424            .as_big_int(),
425            Some(Ok(BigInt::parse_bytes(
426                b"57896044618658097711785492504343953926634992332820282019728792003956564819967",
427                10
428            )
429            .unwrap()))
430        );
431    }
432    #[cfg(feature = "num-bigint")]
433    #[test]
434    fn test_as_big_uint() {
435        assert_eq!(
436            ClickhousePgValue::from("0").as_big_uint(),
437            Some(Ok(BigUint::parse_bytes(b"0", 10).unwrap()))
438        );
439        assert_eq!(
440            ClickhousePgValue::from(
441                "115792089237316195423570985008687907853269984665640564039457584007913129639935"
442            )
443            .as_big_uint(),
444            Some(Ok(BigUint::parse_bytes(
445                b"115792089237316195423570985008687907853269984665640564039457584007913129639935",
446                10
447            )
448            .unwrap()))
449        );
450    }
451    #[test]
452    fn test_as_f32() {
453        assert_eq!(ClickhousePgValue::from(f32::MIN).as_f32(), Some(f32::MIN));
454        assert_eq!(ClickhousePgValue::from(f32::MAX).as_f32(), Some(f32::MAX));
455    }
456    #[test]
457    fn test_as_f64() {
458        assert_eq!(ClickhousePgValue::from(f64::MIN).as_f64(), Some(f64::MIN));
459        assert_eq!(ClickhousePgValue::from(f64::MAX).as_f64(), Some(f64::MAX));
460    }
461    #[test]
462    fn test_as_str() {
463        assert_eq!(ClickhousePgValue::from("foo").as_str(), Some("foo"));
464    }
465
466    #[cfg(feature = "chrono")]
467    #[test]
468    fn test_as_naive_date() {
469        let naive_date = NaiveDate::from_ymd_opt(2021, 1, 1).expect("");
470        assert_eq!(
471            ClickhousePgValue::from(naive_date).as_naive_date(),
472            Some(&NaiveDate::from_ymd_opt(2021, 1, 1).expect(""))
473        );
474    }
475    #[cfg(feature = "bigdecimal")]
476    #[test]
477    fn test_as_big_decimal() {
478        let big_decimal = BigDecimal::parse_bytes(b"1.1", 10).unwrap();
479        assert_eq!(
480            ClickhousePgValue::from(big_decimal.clone()).as_big_decimal(),
481            Some(&big_decimal)
482        );
483    }
484    #[cfg(feature = "uuid")]
485    #[test]
486    fn test_as_uuid() {
487        let uuid = Uuid::parse_str("936DA01F9ABD4d9d80C702AF85C822A8").unwrap();
488        assert_eq!(ClickhousePgValue::from(uuid).as_uuid(), Some(&uuid));
489    }
490
491    #[cfg(feature = "chrono")]
492    #[test]
493    fn test_as_naive_date_time() {
494        let dt = NaiveDate::from_ymd_opt(2021, 1, 1)
495            .expect("")
496            .and_hms_nano_opt(0, 0, 0, 123456789)
497            .expect("");
498        match ClickhousePgValue::from(dt.format("%Y-%m-%d %H:%M:%S").to_string())
499            .as_naive_date_time()
500        {
501            Some(Ok(dt)) => assert_eq!(
502                dt,
503                NaiveDate::from_ymd_opt(2021, 1, 1)
504                    .expect("")
505                    .and_hms_opt(0, 0, 0)
506                    .expect("")
507            ),
508            Some(Err(err)) => panic!("{err:?}"),
509            None => panic!(),
510        }
511    }
512
513    #[test]
514    fn test_as_ipv4_addr() {
515        assert_eq!(
516            ClickhousePgValue::from("127.0.0.1").as_ipv4_addr(),
517            Some(Ok(Ipv4Addr::new(127, 0, 0, 1)))
518        );
519
520        assert_eq!(
521            ClickhousePgValue::from(u32::from(Ipv4Addr::new(127, 0, 0, 1))).as_ipv4_addr(),
522            Some(Ok(Ipv4Addr::new(127, 0, 0, 1)))
523        );
524    }
525}