Skip to main content

elefant_client/types/
binary.rs

1use crate::protocol::FieldDescription;
2use crate::types::{FromSqlBase, FromSqlBinary, FromSqlText, PostgresNamedType, ToSql};
3use crate::PostgresType;
4use std::error::Error;
5
6impl<'a> FromSqlBase<'a> for Vec<u8> {
7    fn accepts_postgres_type(oid: i32) -> bool {
8        oid == PostgresType::BYTEA.oid
9    }
10}
11
12impl<'a> FromSqlBinary<'a> for Vec<u8> {
13    fn from_sql_binary(
14        raw: &'a [u8],
15        _field: &FieldDescription,
16    ) -> Result<Self, Box<dyn Error + Sync + Send>> {
17        Ok(raw.to_vec())
18    }
19}
20
21impl<'a> FromSqlText<'a> for Vec<u8> {
22    fn from_sql_text(
23        raw: &'a str,
24        _field: &FieldDescription,
25    ) -> Result<Self, Box<dyn Error + Sync + Send>> {
26        // PostgreSQL BYTEA text format uses \x prefix for hex encoding
27        // Handle both direct \x format and escaped \\x format
28        let hex_str = if let Some(stripped) = raw.strip_prefix("\\x") {
29            stripped
30        } else if raw.starts_with("\"\\\\x") && raw.ends_with("\"") {
31            // Handle escaped format in quotes: "\\x48656C6C6F"
32            &raw[4..raw.len() - 1]
33        } else if let Some(stripped) = raw.strip_prefix("\\\\x") {
34            // Handle escaped format: \\x48656C6C6F
35            stripped
36        } else {
37            // For array elements, PostgreSQL might return the raw hex without escapes
38            // Let's try direct hex parsing
39            let mut result = Vec::with_capacity(raw.len() / 2);
40            for chunk in raw.as_bytes().chunks(2) {
41                if chunk.len() == 2 {
42                    let hex_byte = std::str::from_utf8(chunk)?;
43                    if let Ok(byte) = u8::from_str_radix(hex_byte, 16) {
44                        result.push(byte);
45                    } else {
46                        // If it's not valid hex, treat it as raw bytes
47                        return Ok(raw.as_bytes().to_vec());
48                    }
49                } else {
50                    // Odd length, treat as raw bytes
51                    return Ok(raw.as_bytes().to_vec());
52                }
53            }
54            return Ok(result);
55        };
56
57        let mut result = Vec::with_capacity(hex_str.len() / 2);
58
59        for chunk in hex_str.as_bytes().chunks(2) {
60            if chunk.len() == 2 {
61                let hex_byte = std::str::from_utf8(chunk)?;
62                let byte = u8::from_str_radix(hex_byte, 16)
63                    .map_err(|e| format!("Invalid hex byte '{hex_byte}': {e}"))?;
64                result.push(byte);
65            }
66        }
67        Ok(result)
68    }
69}
70
71impl ToSql for Vec<u8> {
72    fn to_sql_binary(
73        &self,
74        target_buffer: &mut Vec<u8>,
75    ) -> Result<(), Box<dyn Error + Sync + Send>> {
76        target_buffer.extend_from_slice(self);
77        Ok(())
78    }
79}
80
81impl PostgresNamedType for Vec<u8> {
82    const PG_NAME: &'static str = PostgresType::BYTEA.name;
83}
84
85impl<'a> FromSqlBase<'a> for &'a [u8] {
86    fn accepts_postgres_type(oid: i32) -> bool {
87        oid == PostgresType::BYTEA.oid
88    }
89}
90
91impl<'a> FromSqlBinary<'a> for &'a [u8] {
92    fn from_sql_binary(
93        raw: &'a [u8],
94        _field: &FieldDescription,
95    ) -> Result<Self, Box<dyn Error + Sync + Send>> {
96        Ok(raw)
97    }
98}
99
100// Note: &[u8] does NOT implement FromSqlText because we can't return a borrowed slice
101// from parsed hex data. This demonstrates compile-time safety - &[u8] can only be used
102// with binary format queries. For text format, use Vec<u8> instead.
103
104impl ToSql for &[u8] {
105    fn to_sql_binary(
106        &self,
107        target_buffer: &mut Vec<u8>,
108    ) -> Result<(), Box<dyn Error + Sync + Send>> {
109        target_buffer.extend_from_slice(self);
110        Ok(())
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    #[cfg(feature = "tokio")]
117    mod tokio_connection {
118        use crate::test_helpers::get_settings;
119        use crate::tokio_connection::new_client;
120        use tokio::test;
121
122        #[test]
123        async fn test_bytea_vec_u8() {
124            let mut client = new_client(get_settings()).await.unwrap();
125
126            // Test BYTEA (Vec<u8>)
127            let empty_bytes: Vec<u8> = client.read_single_value_dual_mode("select ''::bytea").await;
128            assert_eq!(empty_bytes, Vec::<u8>::new());
129
130            let test_bytes: Vec<u8> = client
131                .read_single_value_dual_mode("select '\\x48656C6C6F'::bytea")
132                .await;
133            assert_eq!(test_bytes, b"Hello".to_vec());
134
135            let binary_data: Vec<u8> = client
136                .read_single_value_dual_mode("select '\\x00010203FF'::bytea")
137                .await;
138            assert_eq!(binary_data, vec![0, 1, 2, 3, 255]);
139
140            // Test round-trip for BYTEA (manual test since Vec<u8> doesn't implement Display)
141            let test_data = vec![0u8, 255u8, 42u8];
142            let round_trip_result: Vec<u8> = client
143                .read_single_value("select $1::bytea;", &[&test_data])
144                .await;
145            assert_eq!(round_trip_result, test_data);
146
147            let empty_data = Vec::<u8>::new();
148            let round_trip_empty: Vec<u8> = client
149                .read_single_value("select $1::bytea;", &[&empty_data])
150                .await;
151            assert_eq!(round_trip_empty, empty_data);
152
153            let large_data = vec![1u8; 1000];
154            let round_trip_large: Vec<u8> = client
155                .read_single_value("select $1::bytea;", &[&large_data])
156                .await;
157            assert_eq!(round_trip_large, large_data);
158
159            // Test with parameter
160            let param_bytes: Vec<u8> = client
161                .read_single_value(
162                    "select $1::bytea;",
163                    &[&vec![72u8, 101u8, 108u8, 108u8, 111u8]],
164                )
165                .await;
166            assert_eq!(param_bytes, b"Hello".to_vec());
167        }
168
169        #[test]
170        async fn test_bytea_slice() {
171            let mut client = new_client(get_settings()).await.unwrap();
172
173            // Test ToSql for &[u8] with parameter binding (this uses binary format internally)
174            let test_slice: &[u8] = b"World";
175            let received_from_slice: Vec<u8> = client
176                .read_single_value("select $1::bytea;", &[&test_slice])
177                .await;
178            assert_eq!(received_from_slice, b"World".to_vec());
179
180            // Note: &[u8] FromSql only works with binary format since we can't create borrowed slices
181            // from parsed hex text. For text format queries, use Vec<u8> instead.
182        }
183
184        #[test]
185        async fn test_bytea_nullable() {
186            let mut client = new_client(get_settings()).await.unwrap();
187
188            client
189                .execute_non_query_simple(
190                    r#"
191                drop table if exists test_bytea_table;
192                create table test_bytea_table(data bytea);
193                insert into test_bytea_table values ('\x48656C6C6F');
194                "#,
195                )
196                .await
197                .unwrap();
198
199            let bytea_value: Option<Vec<u8>> = client
200                .read_single_value_dual_mode("select data from test_bytea_table")
201                .await;
202            assert_eq!(bytea_value, Some(b"Hello".to_vec()));
203
204            client
205                .execute_non_query("update test_bytea_table set data = null;", &[])
206                .await
207                .unwrap();
208            let null_bytea: Option<Vec<u8>> = client
209                .read_single_value_dual_mode("select data from test_bytea_table")
210                .await;
211            assert_eq!(null_bytea, None);
212
213            // Test inserting NULL BYTEA via parameter
214            client
215                .execute_non_query("delete from test_bytea_table;", &[])
216                .await
217                .unwrap();
218            client
219                .execute_non_query(
220                    "insert into test_bytea_table values ($1);",
221                    &[&None::<Vec<u8>>],
222                )
223                .await
224                .unwrap();
225            let value: Option<Vec<u8>> = client
226                .read_single_value_dual_mode("select data from test_bytea_table")
227                .await;
228            assert_eq!(value, None);
229
230            // Test inserting Some(Vec<u8>) via parameter
231            client
232                .execute_non_query("delete from test_bytea_table;", &[])
233                .await
234                .unwrap();
235            client
236                .execute_non_query(
237                    "insert into test_bytea_table values ($1);",
238                    &[&Some(vec![1u8, 2u8, 3u8])],
239                )
240                .await
241                .unwrap();
242            let value: Option<Vec<u8>> = client
243                .read_single_value_dual_mode("select data from test_bytea_table")
244                .await;
245            assert_eq!(value, Some(vec![1, 2, 3]));
246
247            // Test &[u8] parameter binding (works fine since it uses binary format)
248            client
249                .execute_non_query("delete from test_bytea_table;", &[])
250                .await
251                .unwrap();
252            let slice_data: &[u8] = b"SliceTest";
253            client
254                .execute_non_query("insert into test_bytea_table values ($1);", &[&slice_data])
255                .await
256                .unwrap();
257            let value: Option<Vec<u8>> = client
258                .read_single_value_dual_mode("select data from test_bytea_table")
259                .await;
260            assert_eq!(value, Some(b"SliceTest".to_vec()));
261
262            // Test Option<&[u8]> parameter binding
263            client
264                .execute_non_query("delete from test_bytea_table;", &[])
265                .await
266                .unwrap();
267            let some_slice: Option<&[u8]> = Some(b"OptionSlice");
268            client
269                .execute_non_query("insert into test_bytea_table values ($1);", &[&some_slice])
270                .await
271                .unwrap();
272            let value: Option<Vec<u8>> = client
273                .read_single_value_dual_mode("select data from test_bytea_table")
274                .await;
275            assert_eq!(value, Some(b"OptionSlice".to_vec()));
276
277            // Test None for Option<&[u8]>
278            client
279                .execute_non_query("delete from test_bytea_table;", &[])
280                .await
281                .unwrap();
282            let none_slice: Option<&[u8]> = None;
283            client
284                .execute_non_query("insert into test_bytea_table values ($1);", &[&none_slice])
285                .await
286                .unwrap();
287            let value: Option<Vec<u8>> = client
288                .read_single_value_dual_mode("select data from test_bytea_table")
289                .await;
290            assert_eq!(value, None);
291
292            // Note: &[u8] FromSql only works with binary format since we can't create borrowed slices
293            // from text format hex parsing. For reading from text queries, use Vec<u8> instead.
294            // Parameter binding works fine since it uses binary format via ToSql.
295        }
296
297        #[test]
298        async fn test_bytea_arrays() {
299            let mut client = new_client(get_settings()).await.unwrap();
300
301            // Test BYTEA arrays
302            client
303                .execute_non_query_simple(
304                    r#"
305                drop table if exists test_bytea_array_table;
306                create table test_bytea_array_table(data bytea[]);
307                "#,
308                )
309                .await
310                .unwrap();
311
312            client.execute_non_query("insert into test_bytea_array_table values (array['\\x48656C6C6F'::bytea, '\\x576F726C64'::bytea]);", &[]).await.unwrap();
313            let bytea_array: Vec<Vec<u8>> = client
314                .read_single_value_dual_mode("select data from test_bytea_array_table")
315                .await;
316            assert_eq!(bytea_array, vec![b"Hello".to_vec(), b"World".to_vec()]);
317
318            client
319                .execute_non_query(
320                    "update test_bytea_array_table set data = array[]::bytea[]",
321                    &[],
322                )
323                .await
324                .unwrap();
325            let empty_bytea_array: Vec<Vec<u8>> = client
326                .read_single_value_dual_mode("select data from test_bytea_array_table")
327                .await;
328            assert_eq!(empty_bytea_array, Vec::<Vec<u8>>::new());
329        }
330    }
331}