sqlx_postgres/types/
array.rs

1use sqlx_core::bytes::Buf;
2use sqlx_core::types::Text;
3use std::borrow::Cow;
4
5use crate::decode::Decode;
6use crate::encode::{Encode, IsNull};
7use crate::error::BoxDynError;
8use crate::types::Oid;
9use crate::types::Type;
10use crate::{PgArgumentBuffer, PgTypeInfo, PgValueFormat, PgValueRef, Postgres};
11
12/// Provides information necessary to encode and decode Postgres arrays as compatible Rust types.
13///
14/// Implementing this trait for some type `T` enables relevant `Type`,`Encode` and `Decode` impls
15/// for `Vec<T>`, `&[T]` (slices), `[T; N]` (arrays), etc.
16///
17/// ### Note: `#[derive(sqlx::Type)]`
18/// If you have the `postgres` feature enabled, `#[derive(sqlx::Type)]` will also generate
19/// an impl of this trait for your type if your wrapper is marked `#[sqlx(transparent)]`:
20///
21/// ```rust,ignore
22/// #[derive(sqlx::Type)]
23/// #[sqlx(transparent)]
24/// struct UserId(i64);
25///
26/// let user_ids: Vec<UserId> = sqlx::query_scalar("select '{ 123, 456 }'::int8[]")
27///    .fetch(&mut pg_connection)
28///    .await?;
29/// ```
30///
31/// However, this may cause an error if the type being wrapped does not implement `PgHasArrayType`,
32/// e.g. `Vec` itself, because we don't currently support multidimensional arrays:
33///
34/// ```rust,ignore
35/// #[derive(sqlx::Type)] // ERROR: `Vec<i64>` does not implement `PgHasArrayType`
36/// #[sqlx(transparent)]
37/// struct UserIds(Vec<i64>);
38/// ```
39///
40/// To remedy this, add `#[sqlx(no_pg_array)]`, which disables the generation
41/// of the `PgHasArrayType` impl:
42///
43/// ```rust,ignore
44/// #[derive(sqlx::Type)]
45/// #[sqlx(transparent, no_pg_array)]
46/// struct UserIds(Vec<i64>);
47/// ```
48///
49/// See [the documentation of `Type`][Type] for more details.
50pub trait PgHasArrayType {
51    fn array_type_info() -> PgTypeInfo;
52    fn array_compatible(ty: &PgTypeInfo) -> bool {
53        *ty == Self::array_type_info()
54    }
55}
56
57impl<T> PgHasArrayType for &T
58where
59    T: PgHasArrayType,
60{
61    fn array_type_info() -> PgTypeInfo {
62        T::array_type_info()
63    }
64
65    fn array_compatible(ty: &PgTypeInfo) -> bool {
66        T::array_compatible(ty)
67    }
68}
69
70impl<T> PgHasArrayType for Option<T>
71where
72    T: PgHasArrayType,
73{
74    fn array_type_info() -> PgTypeInfo {
75        T::array_type_info()
76    }
77
78    fn array_compatible(ty: &PgTypeInfo) -> bool {
79        T::array_compatible(ty)
80    }
81}
82
83impl<T> PgHasArrayType for Text<T> {
84    fn array_type_info() -> PgTypeInfo {
85        String::array_type_info()
86    }
87
88    fn array_compatible(ty: &PgTypeInfo) -> bool {
89        String::array_compatible(ty)
90    }
91}
92
93impl<T> Type<Postgres> for [T]
94where
95    T: PgHasArrayType,
96{
97    fn type_info() -> PgTypeInfo {
98        T::array_type_info()
99    }
100
101    fn compatible(ty: &PgTypeInfo) -> bool {
102        T::array_compatible(ty)
103    }
104}
105
106impl<T> Type<Postgres> for Vec<T>
107where
108    T: PgHasArrayType,
109{
110    fn type_info() -> PgTypeInfo {
111        T::array_type_info()
112    }
113
114    fn compatible(ty: &PgTypeInfo) -> bool {
115        T::array_compatible(ty)
116    }
117}
118
119impl<T, const N: usize> Type<Postgres> for [T; N]
120where
121    T: PgHasArrayType,
122{
123    fn type_info() -> PgTypeInfo {
124        T::array_type_info()
125    }
126
127    fn compatible(ty: &PgTypeInfo) -> bool {
128        T::array_compatible(ty)
129    }
130}
131
132impl<'q, T> Encode<'q, Postgres> for Vec<T>
133where
134    for<'a> &'a [T]: Encode<'q, Postgres>,
135    T: Encode<'q, Postgres>,
136{
137    #[inline]
138    fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
139        self.as_slice().encode_by_ref(buf)
140    }
141}
142
143impl<'q, T, const N: usize> Encode<'q, Postgres> for [T; N]
144where
145    for<'a> &'a [T]: Encode<'q, Postgres>,
146    T: Encode<'q, Postgres>,
147{
148    fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
149        self.as_slice().encode_by_ref(buf)
150    }
151}
152
153impl<'q, T> Encode<'q, Postgres> for &'_ [T]
154where
155    T: Encode<'q, Postgres> + Type<Postgres>,
156{
157    fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
158        // do the length check early to avoid doing unnecessary work
159        i32::try_from(self.len()).map_err(|_| {
160            format!(
161                "encoded array length is too large for Postgres: {}",
162                self.len()
163            )
164        })?;
165        crate::PgBindIterExt::bind_iter(self.iter()).encode(buf)
166    }
167}
168
169impl<'r, T, const N: usize> Decode<'r, Postgres> for [T; N]
170where
171    T: for<'a> Decode<'a, Postgres> + Type<Postgres>,
172{
173    fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
174        // This could be done more efficiently by refactoring the Vec decoding below so that it can
175        // be used for arrays and Vec.
176        let vec: Vec<T> = Decode::decode(value)?;
177        let array: [T; N] = vec.try_into().map_err(|_| "wrong number of elements")?;
178        Ok(array)
179    }
180}
181
182impl<'r, T> Decode<'r, Postgres> for Vec<T>
183where
184    T: for<'a> Decode<'a, Postgres> + Type<Postgres>,
185{
186    fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
187        let format = value.format();
188
189        match format {
190            PgValueFormat::Binary => {
191                // https://github.com/postgres/postgres/blob/a995b371ae29de2d38c4b7881cf414b1560e9746/src/backend/utils/adt/arrayfuncs.c#L1548
192
193                let mut buf = value.as_bytes()?;
194
195                // number of dimensions in the array
196                let ndim = buf.get_i32();
197
198                if ndim == 0 {
199                    // zero dimensions is an empty array
200                    return Ok(Vec::new());
201                }
202
203                if ndim != 1 {
204                    return Err(format!("encountered an array of {ndim} dimensions; only one-dimensional arrays are supported").into());
205                }
206
207                // appears to have been used in the past to communicate potential NULLS
208                // but reading source code back through our historically supported
209                // postgres versions (9.5+) this is never used for anything
210                let _flags = buf.get_i32();
211
212                // the OID of the element
213                let element_type_oid = Oid(buf.get_u32());
214                let element_type_info: PgTypeInfo = PgTypeInfo::try_from_oid(element_type_oid)
215                    .or_else(|| value.type_info.try_array_element().map(Cow::into_owned))
216                    .ok_or_else(|| {
217                        BoxDynError::from(format!(
218                            "failed to resolve array element type for oid {}",
219                            element_type_oid.0
220                        ))
221                    })?;
222
223                // length of the array axis
224                let len = buf.get_i32();
225
226                let len = usize::try_from(len)
227                    .map_err(|_| format!("overflow converting array len ({len}) to usize"))?;
228
229                // the lower bound, we only support arrays starting from "1"
230                let lower = buf.get_i32();
231
232                if lower != 1 {
233                    return Err(format!("encountered an array with a lower bound of {lower} in the first dimension; only arrays starting at one are supported").into());
234                }
235
236                let mut elements = Vec::with_capacity(len);
237
238                for _ in 0..len {
239                    let value_ref = PgValueRef::get(&mut buf, format, element_type_info.clone())?;
240
241                    elements.push(T::decode(value_ref)?);
242                }
243
244                Ok(elements)
245            }
246
247            PgValueFormat::Text => {
248                // no type is provided from the database for the element
249                let element_type_info = T::type_info();
250
251                let s = value.as_str()?;
252
253                // https://github.com/postgres/postgres/blob/a995b371ae29de2d38c4b7881cf414b1560e9746/src/backend/utils/adt/arrayfuncs.c#L718
254
255                // trim the wrapping braces
256                let s = &s[1..(s.len() - 1)];
257
258                if s.is_empty() {
259                    // short-circuit empty arrays up here
260                    return Ok(Vec::new());
261                }
262
263                // NOTE: Nearly *all* types use ',' as the sequence delimiter. Yes, there is one
264                //       that does not. The BOX (not PostGIS) type uses ';' as a delimiter.
265
266                // TODO: When we add support for BOX we need to figure out some way to make the
267                //       delimiter selection
268
269                let delimiter = ',';
270                let mut done = false;
271                let mut in_quotes = false;
272                let mut in_escape = false;
273                let mut value = String::with_capacity(10);
274                let mut chars = s.chars();
275                let mut elements = Vec::with_capacity(4);
276
277                while !done {
278                    loop {
279                        match chars.next() {
280                            Some(ch) => match ch {
281                                _ if in_escape => {
282                                    value.push(ch);
283                                    in_escape = false;
284                                }
285
286                                '"' => {
287                                    in_quotes = !in_quotes;
288                                }
289
290                                '\\' => {
291                                    in_escape = true;
292                                }
293
294                                _ if ch == delimiter && !in_quotes => {
295                                    break;
296                                }
297
298                                _ => {
299                                    value.push(ch);
300                                }
301                            },
302
303                            None => {
304                                done = true;
305                                break;
306                            }
307                        }
308                    }
309
310                    let value_opt = if value == "NULL" {
311                        None
312                    } else {
313                        Some(value.as_bytes())
314                    };
315
316                    elements.push(T::decode(PgValueRef {
317                        value: value_opt,
318                        row: None,
319                        type_info: element_type_info.clone(),
320                        format,
321                    })?);
322
323                    value.clear();
324                }
325
326                Ok(elements)
327            }
328        }
329    }
330}