sqlx_core_oldapi/postgres/types/
array.rs

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