Skip to main content

elefant_client/types/
nullable.rs

1use crate::protocol::FieldDescription;
2use crate::types::{EnumTypeRegistry, FromSqlBase, FromSqlBinary, FromSqlText, ToSql};
3use crate::ElefantClientError;
4use std::error::Error;
5
6impl<'a, T> FromSqlBase<'a> for Option<T>
7where
8    T: FromSqlBase<'a>,
9{
10    fn accepts_postgres_type(oid: i32) -> bool {
11        T::accepts_postgres_type(oid)
12    }
13
14    fn accepts_with_registry(field: &FieldDescription, registry: &EnumTypeRegistry) -> bool {
15        T::accepts_with_registry(field, registry)
16    }
17
18    fn from_null(_field: &FieldDescription) -> Result<Self, ElefantClientError> {
19        Ok(None)
20    }
21}
22
23impl<'a, T> FromSqlBinary<'a> for Option<T>
24where
25    T: FromSqlBinary<'a>,
26{
27    fn from_sql_binary(
28        raw: &'a [u8],
29        field: &FieldDescription,
30    ) -> Result<Self, Box<dyn Error + Sync + Send>> {
31        T::from_sql_binary(raw, field).map(Some)
32    }
33}
34
35impl<'a, T> FromSqlText<'a> for Option<T>
36where
37    T: FromSqlText<'a>,
38{
39    fn from_sql_text(
40        raw: &'a str,
41        field: &FieldDescription,
42    ) -> Result<Self, Box<dyn Error + Sync + Send>> {
43        T::from_sql_text(raw, field).map(Some)
44    }
45}
46
47impl<T> ToSql for Option<T>
48where
49    T: ToSql,
50{
51    fn to_sql_binary(
52        &self,
53        target_buffer: &mut Vec<u8>,
54    ) -> Result<(), Box<dyn Error + Sync + Send>> {
55        match self {
56            Some(value) => value.to_sql_binary(target_buffer),
57            None => Err("Cannot convert None to binary representation. This case should never happens and should be considered a bug in the ElefantClient library. Please create an issue on GitHub.".into())
58        }
59    }
60
61    fn is_null(&self) -> bool {
62        self.is_none()
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    #[cfg(feature = "tokio")]
69    mod tokio_connection {
70        use crate::test_helpers::get_settings;
71        use crate::tokio_connection::new_client;
72        use crate::ElefantClientError;
73        use tokio::test;
74
75        #[test]
76        async fn test_nullable_types() {
77            let mut client = new_client(get_settings()).await.unwrap();
78
79            client
80                .execute_non_query_simple(
81                    r#"
82                drop table if exists test_table;
83                create table test_table(value int2);
84                insert into test_table values (42);
85                "#,
86                )
87                .await
88                .unwrap();
89
90            let value: Option<i16> = client
91                .read_single_value("select value from test_table;", &[])
92                .await;
93            assert_eq!(value, Some(42));
94
95            client
96                .execute_non_query_simple(
97                    "delete from test_table; insert into test_table values (null);",
98                )
99                .await
100                .unwrap();
101
102            let value: Option<i16> = client
103                .read_single_value("select value from test_table;", &[])
104                .await;
105            assert_eq!(value, None);
106
107            let result = client
108                .try_read_single_value::<i16>("select value from test_table;", &[])
109                .await;
110
111            if let Err(ElefantClientError::UnexpectedNullValue { postgres_field }) = result {
112                assert_eq!(postgres_field.column_attribute_number, 1);
113            } else {
114                panic!("Expected UnexpectedNullValue error, got {result:?}");
115            }
116
117            client
118                .execute_non_query("delete from test_table;", &[])
119                .await
120                .unwrap();
121
122            client
123                .execute_non_query("insert into test_table values ($1);", &[&None::<i16>])
124                .await
125                .unwrap();
126            let value: Option<i16> = client
127                .read_single_value("select value from test_table;", &[])
128                .await;
129            assert_eq!(value, None);
130
131            client
132                .execute_non_query("delete from test_table;", &[])
133                .await
134                .unwrap();
135
136            client
137                .execute_non_query("insert into test_table values ($1);", &[&Some(42i16)])
138                .await
139                .unwrap();
140            let value: Option<i16> = client
141                .read_single_value("select value from test_table;", &[])
142                .await;
143            assert_eq!(value, Some(42));
144        }
145    }
146}