cdbc_pg/types/
array.rs

1use bytes::Buf;
2use std::borrow::Cow;
3use cdbc::decode::Decode;
4use cdbc::encode::{Encode, IsNull};
5use cdbc::error::BoxDynError;
6use crate::type_info::PgType;
7use crate::{PgArgumentBuffer, PgTypeInfo, PgValueFormat, PgValueRef, Postgres};
8use cdbc::types::Type;
9
10pub trait PgHasArrayType {
11    fn array_type_info() -> PgTypeInfo;
12    fn array_compatible(ty: &PgTypeInfo) -> bool {
13        *ty == Self::array_type_info()
14    }
15}
16
17impl<T> PgHasArrayType for Option<T>
18where
19    T: PgHasArrayType,
20{
21    fn array_type_info() -> PgTypeInfo {
22        T::array_type_info()
23    }
24
25    fn array_compatible(ty: &PgTypeInfo) -> bool {
26        T::array_compatible(ty)
27    }
28}
29
30impl<T> Type<Postgres> for [T]
31where
32    T: PgHasArrayType,
33{
34    fn type_info() -> PgTypeInfo {
35        T::array_type_info()
36    }
37
38    fn compatible(ty: &PgTypeInfo) -> bool {
39        T::array_compatible(ty)
40    }
41}
42
43impl<T> Type<Postgres> for Vec<T>
44where
45    T: PgHasArrayType,
46{
47    fn type_info() -> PgTypeInfo {
48        T::array_type_info()
49    }
50
51    fn compatible(ty: &PgTypeInfo) -> bool {
52        T::array_compatible(ty)
53    }
54}
55
56impl<'q, T> Encode<'q, Postgres> for Vec<T>
57where
58    for<'a> &'a [T]: Encode<'q, Postgres>,
59    T: Encode<'q, Postgres>,
60{
61    #[inline]
62    fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
63        self.as_slice().encode_by_ref(buf)
64    }
65}
66
67impl<'q, T> Encode<'q, Postgres> for &'_ [T]
68where
69    T: Encode<'q, Postgres> + Type<Postgres>,
70{
71    fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> IsNull {
72        let type_info = if self.len() < 1 {
73            T::type_info()
74        } else {
75            self[0].produces().unwrap_or_else(T::type_info)
76        };
77
78        buf.extend(&1_i32.to_be_bytes()); // number of dimensions
79        buf.extend(&0_i32.to_be_bytes()); // flags
80
81        // element type
82        match type_info.0 {
83            PgType::DeclareWithName(name) => buf.patch_type_by_name(&name),
84
85            ty => {
86                buf.extend(&ty.oid().to_be_bytes());
87            }
88        }
89
90        buf.extend(&(self.len() as i32).to_be_bytes()); // len
91        buf.extend(&1_i32.to_be_bytes()); // lower bound
92
93        for element in self.iter() {
94            buf.encode(element);
95        }
96
97        IsNull::No
98    }
99}
100
101impl<'r, T> Decode<'r, Postgres> for Vec<T>
102where
103    T: for<'a> Decode<'a, Postgres> + Type<Postgres>,
104{
105    fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
106        let format = value.format();
107
108        match format {
109            PgValueFormat::Binary => {
110                // https://github.com/postgres/postgres/blob/a995b371ae29de2d38c4b7881cf414b1560e9746/src/backend/utils/adt/arrayfuncs.c#L1548
111
112                let mut buf = value.as_bytes()?;
113
114                // number of dimensions in the array
115                let ndim = buf.get_i32();
116
117                if ndim == 0 {
118                    // zero dimensions is an empty array
119                    return Ok(Vec::new());
120                }
121
122                if ndim != 1 {
123                    return Err(format!("encountered an array of {} dimensions; only one-dimensional arrays are supported", ndim).into());
124                }
125
126                // appears to have been used in the past to communicate potential NULLS
127                // but reading source code back through our supported postgres versions (9.5+)
128                // this is never used for anything
129                let _flags = buf.get_i32();
130
131                // the OID of the element
132                let element_type_oid = buf.get_u32();
133                let element_type_info: PgTypeInfo = PgTypeInfo::try_from_oid(element_type_oid)
134                    .or_else(|| value.type_info.try_array_element().map(Cow::into_owned))
135                    .unwrap_or_else(|| PgTypeInfo(PgType::DeclareWithOid(element_type_oid)));
136
137                // length of the array axis
138                let len = buf.get_i32();
139
140                // the lower bound, we only support arrays starting from "1"
141                let lower = buf.get_i32();
142
143                if lower != 1 {
144                    return Err(format!("encountered an array with a lower bound of {} in the first dimension; only arrays starting at one are supported", lower).into());
145                }
146
147                let mut elements = Vec::with_capacity(len as usize);
148
149                for _ in 0..len {
150                    elements.push(T::decode(PgValueRef::get(
151                        &mut buf,
152                        format,
153                        element_type_info.clone(),
154                    ))?)
155                }
156
157                Ok(elements)
158            }
159
160            PgValueFormat::Text => {
161                // no type is provided from the database for the element
162                let element_type_info = T::type_info();
163
164                let s = value.as_str()?;
165
166                // https://github.com/postgres/postgres/blob/a995b371ae29de2d38c4b7881cf414b1560e9746/src/backend/utils/adt/arrayfuncs.c#L718
167
168                // trim the wrapping braces
169                let s = &s[1..(s.len() - 1)];
170
171                if s.is_empty() {
172                    // short-circuit empty arrays up here
173                    return Ok(Vec::new());
174                }
175
176                // NOTE: Nearly *all* types use ',' as the sequence delimiter. Yes, there is one
177                //       that does not. The BOX (not PostGIS) type uses ';' as a delimiter.
178
179                // TODO: When we add support for BOX we need to figure out some way to make the
180                //       delimiter selection
181
182                let delimiter = ',';
183                let mut done = false;
184                let mut in_quotes = false;
185                let mut in_escape = false;
186                let mut value = String::with_capacity(10);
187                let mut chars = s.chars();
188                let mut elements = Vec::with_capacity(4);
189
190                while !done {
191                    loop {
192                        match chars.next() {
193                            Some(ch) => match ch {
194                                _ if in_escape => {
195                                    value.push(ch);
196                                    in_escape = false;
197                                }
198
199                                '"' => {
200                                    in_quotes = !in_quotes;
201                                }
202
203                                '\\' => {
204                                    in_escape = true;
205                                }
206
207                                _ if ch == delimiter && !in_quotes => {
208                                    break;
209                                }
210
211                                _ => {
212                                    value.push(ch);
213                                }
214                            },
215
216                            None => {
217                                done = true;
218                                break;
219                            }
220                        }
221                    }
222
223                    let value_opt = if value == "NULL" {
224                        None
225                    } else {
226                        Some(value.as_bytes())
227                    };
228
229                    elements.push(T::decode(PgValueRef {
230                        value: value_opt,
231                        row: None,
232                        type_info: element_type_info.clone(),
233                        format,
234                    })?);
235
236                    value.clear();
237                }
238
239                Ok(elements)
240            }
241        }
242    }
243}