Skip to main content

diesel/pg/types/
array.rs

1use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt};
2use core::fmt;
3use core::num::NonZeroU32;
4use std::io::Write;
5
6use crate::deserialize::{self, FromSql, FromSqlRow};
7use crate::pg::{Pg, PgTypeMetadata, PgValue};
8use crate::query_builder::bind_collector::ByteWrapper;
9use crate::serialize::{self, IsNull, Output, ToSql};
10use crate::sql_types::{Array, HasSqlType, Nullable};
11
12#[cfg(feature = "postgres_backend")]
13#[derive(#[automatically_derived]
impl<T: ::core::fmt::Debug> ::core::fmt::Debug for NdArray<T> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f, "NdArray",
            "dims", &self.dims, "data", &&self.data)
    }
}Debug, #[automatically_derived]
impl<T: ::core::clone::Clone> ::core::clone::Clone for NdArray<T> {
    #[inline]
    fn clone(&self) -> Self {
        Self {
            dims: ::core::clone::Clone::clone(&self.dims),
            data: ::core::clone::Clone::clone(&self.data),
        }
    }
}Clone, #[automatically_derived]
impl<T: ::core::cmp::PartialEq> ::core::marker::StructuralPartialEq for
    NdArray<T> {
}
#[automatically_derived]
impl<T: ::core::cmp::PartialEq> ::core::cmp::PartialEq for NdArray<T> {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        self.dims == other.dims && self.data == other.data
    }
}PartialEq, #[automatically_derived]
impl<T: ::core::cmp::Eq> ::core::cmp::Eq for NdArray<T> {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<Vec<usize>>;
        let _: ::core::cmp::AssertParamIsEq<Vec<T>>;
    }
}Eq, #[automatically_derived]
impl<T: ::core::hash::Hash> ::core::hash::Hash for NdArray<T> {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
        ::core::hash::Hash::hash(&self.dims, state);
        ::core::hash::Hash::hash(&self.data, state)
    }
}Hash, #[automatically_derived]
impl<T: ::core::cmp::PartialOrd> ::core::cmp::PartialOrd for NdArray<T> {
    #[inline]
    fn partial_cmp(&self, other: &Self)
        -> ::core::option::Option<::core::cmp::Ordering> {
        match ::core::cmp::PartialOrd::partial_cmp(&self.dims, &other.dims) {
            ::core::option::Option::Some(::core::cmp::Ordering::Equal) =>
                ::core::cmp::PartialOrd::partial_cmp(&self.data, &other.data),
            cmp => cmp,
        }
    }
}PartialOrd, #[automatically_derived]
impl<T: ::core::cmp::Ord> ::core::cmp::Ord for NdArray<T> {
    #[inline]
    fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
        match ::core::cmp::Ord::cmp(&self.dims, &other.dims) {
            ::core::cmp::Ordering::Equal =>
                ::core::cmp::Ord::cmp(&self.data, &other.data),
            cmp => cmp,
        }
    }
}Ord, const _: () =
    {
        use diesel;
        impl<'__expr, T> diesel::expression::AsExpression<Array<T>> for
            &'__expr NdArray<T> {
            type Expression =
                diesel::internal::derives::as_expression::Bound<Array<T>,
                Self>;
            fn as_expression(self)
                ->
                    <Self as
                    diesel::expression::AsExpression<Array<T>>>::Expression {
                diesel::internal::derives::as_expression::Bound::new(self)
            }
        }
        #[diagnostic::do_not_recommend]
        impl<'__expr, T>
            diesel::expression::AsExpression<diesel::sql_types::Nullable<Array<T>>>
            for &'__expr NdArray<T> {
            type Expression =
                diesel::internal::derives::as_expression::Bound<diesel::sql_types::Nullable<Array<T>>,
                Self>;
            fn as_expression(self)
                ->
                    <Self as
                    diesel::expression::AsExpression<diesel::sql_types::Nullable<Array<T>>>>::Expression {
                diesel::internal::derives::as_expression::Bound::new(self)
            }
        }
        #[diagnostic::do_not_recommend]
        impl<'__expr, '__expr2, T> diesel::expression::AsExpression<Array<T>>
            for &'__expr2 &'__expr NdArray<T> {
            type Expression =
                diesel::internal::derives::as_expression::Bound<Array<T>,
                Self>;
            fn as_expression(self)
                ->
                    <Self as
                    diesel::expression::AsExpression<Array<T>>>::Expression {
                diesel::internal::derives::as_expression::Bound::new(self)
            }
        }
        #[diagnostic::do_not_recommend]
        impl<'__expr, '__expr2, T>
            diesel::expression::AsExpression<diesel::sql_types::Nullable<Array<T>>>
            for &'__expr2 &'__expr NdArray<T> {
            type Expression =
                diesel::internal::derives::as_expression::Bound<diesel::sql_types::Nullable<Array<T>>,
                Self>;
            fn as_expression(self)
                ->
                    <Self as
                    diesel::expression::AsExpression<diesel::sql_types::Nullable<Array<T>>>>::Expression {
                diesel::internal::derives::as_expression::Bound::new(self)
            }
        }
        impl<T, __DB>
            diesel::serialize::ToSql<diesel::sql_types::Nullable<Array<T>>,
            __DB> for NdArray<T> where __DB: diesel::backend::Backend,
            Self: diesel::serialize::ToSql<Array<T>, __DB> {
            fn to_sql<'__b>(&'__b self,
                out: &mut diesel::serialize::Output<'__b, '_, __DB>)
                -> diesel::serialize::Result {
                diesel::serialize::ToSql::<Array<T>, __DB>::to_sql(self, out)
            }
        }
        impl<T> diesel::expression::AsExpression<Array<T>> for NdArray<T> {
            type Expression =
                diesel::internal::derives::as_expression::Bound<Array<T>,
                Self>;
            fn as_expression(self)
                ->
                    <Self as
                    diesel::expression::AsExpression<Array<T>>>::Expression {
                diesel::internal::derives::as_expression::Bound::new(self)
            }
        }
        impl<T>
            diesel::expression::AsExpression<diesel::sql_types::Nullable<Array<T>>>
            for NdArray<T> {
            type Expression =
                diesel::internal::derives::as_expression::Bound<diesel::sql_types::Nullable<Array<T>>,
                Self>;
            fn as_expression(self)
                ->
                    <Self as
                    diesel::expression::AsExpression<diesel::sql_types::Nullable<Array<T>>>>::Expression {
                diesel::internal::derives::as_expression::Bound::new(self)
            }
        }
    };AsExpression, const _: () =
    {
        use diesel;
        impl<T, __DB, __ST> diesel::deserialize::Queryable<__ST, __DB> for
            NdArray<T> where __DB: diesel::backend::Backend,
            __ST: diesel::sql_types::SingleValue,
            Self: diesel::deserialize::FromSql<__ST, __DB> {
            type Row = Self;
            fn build(row: Self) -> diesel::deserialize::Result<Self> {
                diesel::deserialize::Result::Ok(row)
            }
        }
    };FromSqlRow)]
14#[diesel(sql_type = Array<T>)]
15/// Postgres allows multi-dimensional arrays of at most 6 dimensions. Internally they are stored as a flattened
16/// representation with the dimension information encoded in the header. This struct represents a
17/// multi-dimensional array with elements of type `T` as opposed to `Vec<T>` which can be used for 1d-arrays.
18pub struct NdArray<T> {
19    /// A list that describes how many values for each dimension are returned
20    pub dims: Vec<usize>,
21    /// The actual data flattened to a single array
22    ///
23    /// This array contains values ordered by the left most dimension
24    /// which means there will be dim\[0\] values for the first element of the second dimension
25    /// followed by dim\[0\] values for the second element of the second dimensions
26    /// and so up to dim\[1\] times. Afterwards that number of values is repeated for dim\[2\],
27    /// and so for all dimensions in the dimension field above
28    pub data: Vec<T>,
29}
30
31#[cfg(feature = "postgres_backend")]
32impl<T> HasSqlType<Array<T>> for Pg
33where
34    Pg: HasSqlType<T>,
35{
36    fn metadata(lookup: &mut Self::MetadataLookup) -> PgTypeMetadata {
37        match <Pg as HasSqlType<T>>::metadata(lookup).0 {
38            Ok(tpe) => PgTypeMetadata::new(tpe.array_oid, 0),
39            c @ Err(_) => PgTypeMetadata(c),
40        }
41    }
42}
43
44#[cfg(feature = "postgres_backend")]
45impl<T, ST> FromSql<Array<ST>, Pg> for Vec<T>
46where
47    T: FromSql<ST, Pg>,
48{
49    fn from_sql(value: PgValue<'_>) -> deserialize::Result<Self> {
50        let mut bytes = value.as_bytes();
51        let num_dimensions = bytes.read_i32::<NetworkEndian>()?;
52        let has_null = bytes.read_i32::<NetworkEndian>()? != 0;
53        let element_oid =
54            NonZeroU32::new(bytes.read_u32::<NetworkEndian>()?).ok_or("Oid's aren't zero")?;
55
56        if num_dimensions == 0 {
57            return Ok(Vec::new());
58        }
59
60        let num_elements = bytes.read_i32::<NetworkEndian>()?;
61        let _lower_bound = bytes.read_i32::<NetworkEndian>()?;
62
63        if num_dimensions != 1 {
64            return Err("multi-dimensional arrays are not supported".into());
65        }
66
67        (0..num_elements)
68            .map(|_| -> deserialize::Result<_> {
69                let elem_size = bytes.read_i32::<NetworkEndian>()?;
70                if has_null && elem_size == -1 {
71                    T::from_nullable_sql(None)
72                } else {
73                    let (elem_bytes, new_bytes) = bytes
74                        .split_at_checked(elem_size.try_into()?)
75                        .ok_or_else(|| {
76                            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("Invalid element byte count: Expected at least {1} bytes, but only {0} bytes were received",
                bytes.len(), elem_size))
    })format!(
77                                "Invalid element byte count: Expected at least {elem_size} bytes, but only {} bytes were received",
78                                bytes.len()
79                            )
80                        })?;
81                    bytes = new_bytes;
82                    T::from_sql(PgValue::new_internal(elem_bytes, &element_oid))
83                }
84            })
85            .collect()
86    }
87}
88
89#[cfg(feature = "postgres_backend")]
90impl<T, ST> FromSql<Array<ST>, Pg> for NdArray<T>
91where
92    T: FromSql<ST, Pg>,
93{
94    fn from_sql(value: PgValue<'_>) -> deserialize::Result<Self> {
95        let mut bytes = value.as_bytes();
96        let num_dimensions = bytes.read_i32::<NetworkEndian>()?;
97        let has_null = bytes.read_i32::<NetworkEndian>()? != 0;
98        let element_oid =
99            NonZeroU32::new(bytes.read_u32::<NetworkEndian>()?).ok_or("Oid's aren't zero")?;
100
101        if num_dimensions == 0 {
102            return Ok(NdArray {
103                dims: Vec::new(),
104                data: Vec::new(),
105            });
106        }
107
108        let num_dims: usize = num_dimensions
109            .try_into()
110            .map_err(|_| "number of dimensions must be positive")?;
111
112        let dims = (0..num_dims)
113            .map(|_| {
114                let num_elements = bytes.read_i32::<NetworkEndian>()?;
115                let _lower_bound = bytes.read_i32::<NetworkEndian>()?;
116
117                let dim: usize = num_elements
118                    .try_into()
119                    .map_err(|_| "array dimension length must be positive")?;
120                Ok(dim)
121            })
122            .collect::<deserialize::Result<Vec<_>>>()?;
123
124        let max_dim = dims
125            .iter()
126            .try_fold(1_usize, |a, b| a.checked_mul(*b))
127            .ok_or("Overflow while deserializing package size")?;
128
129        let data = (0..max_dim)
130            .map(|_| -> deserialize::Result<T> {
131                let elem_size = bytes.read_i32::<NetworkEndian>()?;
132                if has_null && elem_size == -1 {
133                    T::from_nullable_sql(None)
134                } else {
135                    let (elem_bytes, new_bytes) = bytes
136                        .split_at_checked(elem_size.try_into()?)
137                        .ok_or_else(|| {
138                            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("Invalid element byte count: Expected at least {1} bytes, but only {0} bytes were received",
                bytes.len(), elem_size))
    })format!(
139                                "Invalid element byte count: Expected at least {elem_size} bytes, but only {} bytes were received",
140                                bytes.len()
141                            )
142                        })?;
143                    bytes = new_bytes;
144                    T::from_sql(PgValue::new_internal(elem_bytes, &element_oid))
145                }
146            })
147            .collect::<deserialize::Result<Vec<T>>>()?;
148        Ok(NdArray { dims, data })
149    }
150}
151
152use crate::expression::AsExpression;
153use crate::expression::bound::Bound;
154
155macro_rules! array_as_expression {
156    ($ty:ty, $sql_type:ty) => {
157        #[cfg(feature = "postgres_backend")]
158        // this simplifies the macro implementation
159        // as some macro calls use this lifetime
160        #[allow(clippy::extra_unused_lifetimes)]
161        impl<'a, 'b, ST: 'static, T> AsExpression<$sql_type> for $ty {
162            type Expression = Bound<$sql_type, Self>;
163
164            fn as_expression(self) -> Self::Expression {
165                Bound::new(self)
166            }
167        }
168    };
169}
170
171#[allow(clippy :: extra_unused_lifetimes)]
impl<'a, 'b, ST: 'static, T> AsExpression<Array<ST>> for &'a [T] {
    type Expression = Bound<Array<ST>, Self>;
    fn as_expression(self) -> Self::Expression { Bound::new(self) }
}array_as_expression!(&'a [T], Array<ST>);
172#[allow(clippy :: extra_unused_lifetimes)]
impl<'a, 'b, ST: 'static, T> AsExpression<Nullable<Array<ST>>> for &'a [T] {
    type Expression = Bound<Nullable<Array<ST>>, Self>;
    fn as_expression(self) -> Self::Expression { Bound::new(self) }
}array_as_expression!(&'a [T], Nullable<Array<ST>>);
173#[allow(clippy :: extra_unused_lifetimes)]
impl<'a, 'b, ST: 'static, T> AsExpression<Array<ST>> for &'a &'b [T] {
    type Expression = Bound<Array<ST>, Self>;
    fn as_expression(self) -> Self::Expression { Bound::new(self) }
}array_as_expression!(&'a &'b [T], Array<ST>);
174#[allow(clippy :: extra_unused_lifetimes)]
impl<'a, 'b, ST: 'static, T> AsExpression<Nullable<Array<ST>>> for &'a &'b [T]
    {
    type Expression = Bound<Nullable<Array<ST>>, Self>;
    fn as_expression(self) -> Self::Expression { Bound::new(self) }
}array_as_expression!(&'a &'b [T], Nullable<Array<ST>>);
175#[allow(clippy :: extra_unused_lifetimes)]
impl<'a, 'b, ST: 'static, T> AsExpression<Array<ST>> for Vec<T> {
    type Expression = Bound<Array<ST>, Self>;
    fn as_expression(self) -> Self::Expression { Bound::new(self) }
}array_as_expression!(Vec<T>, Array<ST>);
176#[allow(clippy :: extra_unused_lifetimes)]
impl<'a, 'b, ST: 'static, T> AsExpression<Nullable<Array<ST>>> for Vec<T> {
    type Expression = Bound<Nullable<Array<ST>>, Self>;
    fn as_expression(self) -> Self::Expression { Bound::new(self) }
}array_as_expression!(Vec<T>, Nullable<Array<ST>>);
177#[allow(clippy :: extra_unused_lifetimes)]
impl<'a, 'b, ST: 'static, T> AsExpression<Array<ST>> for &'a Vec<T> {
    type Expression = Bound<Array<ST>, Self>;
    fn as_expression(self) -> Self::Expression { Bound::new(self) }
}array_as_expression!(&'a Vec<T>, Array<ST>);
178#[allow(clippy :: extra_unused_lifetimes)]
impl<'a, 'b, ST: 'static, T> AsExpression<Nullable<Array<ST>>> for &'a Vec<T>
    {
    type Expression = Bound<Nullable<Array<ST>>, Self>;
    fn as_expression(self) -> Self::Expression { Bound::new(self) }
}array_as_expression!(&'a Vec<T>, Nullable<Array<ST>>);
179#[allow(clippy :: extra_unused_lifetimes)]
impl<'a, 'b, ST: 'static, T> AsExpression<Array<ST>> for &'a &'b Vec<T> {
    type Expression = Bound<Array<ST>, Self>;
    fn as_expression(self) -> Self::Expression { Bound::new(self) }
}array_as_expression!(&'a &'b Vec<T>, Array<ST>);
180#[allow(clippy :: extra_unused_lifetimes)]
impl<'a, 'b, ST: 'static, T> AsExpression<Nullable<Array<ST>>> for
    &'a &'b Vec<T> {
    type Expression = Bound<Nullable<Array<ST>>, Self>;
    fn as_expression(self) -> Self::Expression { Bound::new(self) }
}array_as_expression!(&'a &'b Vec<T>, Nullable<Array<ST>>);
181
182#[cfg(feature = "postgres_backend")]
183impl<ST, T> ToSql<Array<ST>, Pg> for [T]
184where
185    Pg: HasSqlType<ST>,
186    T: ToSql<ST, Pg>,
187{
188    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
189        let num_dimensions = 1;
190        out.write_i32::<NetworkEndian>(num_dimensions)?;
191        // The null flag depends on the elements, so it is patched once they are written.
192        let flags_offset = out.reborrow().into_inner().0.len();
193        let flags = 0;
194        out.write_i32::<NetworkEndian>(flags)?;
195        let element_oid = Pg::metadata(out.metadata_lookup()).oid()?;
196        out.write_u32::<NetworkEndian>(element_oid)?;
197        out.write_i32::<NetworkEndian>(self.len().try_into()?)?;
198        let lower_bound = 1;
199        out.write_i32::<NetworkEndian>(lower_bound)?;
200
201        // This buffer is created outside of the loop to reuse the underlying memory allocation
202        // For most cases all array elements will have the same serialized size
203        let mut buffer = Vec::new();
204        let mut has_nulls = false;
205
206        for elem in self.iter() {
207            let is_null = {
208                let mut temp_buffer = Output::new(ByteWrapper(&mut buffer), out.metadata_lookup());
209                elem.to_sql(&mut temp_buffer)?
210            };
211
212            if let IsNull::No = is_null {
213                out.write_i32::<NetworkEndian>(buffer.len().try_into()?)?;
214                out.write_all(&buffer)?;
215                buffer.clear();
216            } else {
217                has_nulls = true;
218                // https://github.com/postgres/postgres/blob/82f8107b92c9104ec9d9465f3f6a4c6dab4c124a/src/backend/utils/adt/arrayfuncs.c#L1461
219                out.write_i32::<NetworkEndian>(-1)?;
220            }
221        }
222
223        if has_nulls {
224            out.reborrow().into_inner().0[flags_offset..flags_offset + 4]
225                .copy_from_slice(&1_i32.to_be_bytes());
226        }
227
228        Ok(IsNull::No)
229    }
230}
231
232#[cfg(feature = "postgres_backend")]
233impl<ST, T> ToSql<Nullable<Array<ST>>, Pg> for [T]
234where
235    [T]: ToSql<Array<ST>, Pg>,
236    ST: 'static,
237{
238    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
239        ToSql::<Array<ST>, Pg>::to_sql(self, out)
240    }
241}
242
243#[cfg(feature = "postgres_backend")]
244impl<ST, T> ToSql<Array<ST>, Pg> for Vec<T>
245where
246    ST: 'static,
247    [T]: ToSql<Array<ST>, Pg>,
248    T: fmt::Debug,
249{
250    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
251        (self as &[T]).to_sql(out)
252    }
253}
254
255#[cfg(feature = "postgres_backend")]
256impl<ST, T> ToSql<Nullable<Array<ST>>, Pg> for Vec<T>
257where
258    ST: 'static,
259    Vec<T>: ToSql<Array<ST>, Pg>,
260{
261    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
262        ToSql::<Array<ST>, Pg>::to_sql(self, out)
263    }
264}
265
266#[cfg(test)]
267mod tests {
268    use byteorder::{NetworkEndian, WriteBytesExt};
269
270    use super::ByteWrapper;
271    use crate::data_types::NdArray;
272    use crate::deserialize::{self, FromSql};
273    use crate::pg::{Pg, PgMetadataLookup, PgTypeMetadata, PgValue};
274    use crate::serialize::{Output, ToSql};
275    use crate::sql_types::{Array, Integer, Nullable};
276
277    #[derive(Debug, PartialEq)]
278    struct ElementOid(u32);
279
280    impl FromSql<Integer, Pg> for ElementOid {
281        fn from_sql(value: PgValue<'_>) -> deserialize::Result<Self> {
282            Ok(Self(value.get_oid().get()))
283        }
284    }
285
286    fn one_element_array_value(element_oid: u32) -> Vec<u8> {
287        let mut value = Vec::<u8>::new();
288        value.write_i32::<NetworkEndian>(1).unwrap();
289        value.write_i32::<NetworkEndian>(0).unwrap();
290        value.write_u32::<NetworkEndian>(element_oid).unwrap();
291        value.write_i32::<NetworkEndian>(1).unwrap();
292        value.write_i32::<NetworkEndian>(0).unwrap();
293        value.write_i32::<NetworkEndian>(4).unwrap();
294        value.write_i32::<NetworkEndian>(42).unwrap();
295        value
296    }
297
298    #[test]
299    fn zero_element_oid_is_rejected() {
300        let value = one_element_array_value(0);
301        let value = PgValue::for_test(&value);
302        let res = <Vec<ElementOid> as FromSql<Array<Integer>, Pg>>::from_sql(value);
303        assert!(res.is_err());
304        assert_eq!(format!("{}", res.unwrap_err()), "Oid's aren't zero");
305
306        let value = one_element_array_value(0);
307        let value = PgValue::for_test(&value);
308        let res = <NdArray<ElementOid> as FromSql<Array<Integer>, Pg>>::from_sql(value);
309        assert!(res.is_err());
310        assert_eq!(format!("{}", res.unwrap_err()), "Oid's aren't zero");
311    }
312
313    #[test]
314    fn nonzero_header_oid_is_used_for_element() {
315        let value = one_element_array_value(23);
316        let value = PgValue::for_test(&value);
317        let res = <Vec<ElementOid> as FromSql<Array<Integer>, Pg>>::from_sql(value);
318        assert_eq!(vec![ElementOid(23)], res.unwrap());
319
320        let value = one_element_array_value(23);
321        let value = PgValue::for_test(&value);
322        let res = <NdArray<ElementOid> as FromSql<Array<Integer>, Pg>>::from_sql(value).unwrap();
323        assert_eq!(vec![1], res.dims);
324        assert_eq!(vec![ElementOid(23)], res.data);
325    }
326
327    /// Built-in element types have static OIDs, so serializing them never looks one up.
328    struct StaticOidsOnly;
329
330    impl PgMetadataLookup for StaticOidsOnly {
331        fn lookup_type(&mut self, type_name: &str, _schema: Option<&str>) -> PgTypeMetadata {
332            panic!("unexpected type lookup for `{type_name}`")
333        }
334    }
335
336    #[test]
337    fn serialized_null_flag_matches_elements() {
338        // The decoder treats a `-1` element length as NULL only when this flag is set,
339        // so arrays containing NULL previously failed to read back.
340        for (array, flags) in [
341            (vec![Some(7), None, Some(-9)], 1),
342            (vec![Some(7), Some(-9)], 0),
343        ] {
344            // The flag is patched after the elements, so serialize after unrelated bytes.
345            let prefix = [0xAA; 3];
346            let mut buffer = prefix.to_vec();
347            ToSql::<Array<Nullable<Integer>>, Pg>::to_sql(
348                &array,
349                &mut Output::new(
350                    ByteWrapper(&mut buffer),
351                    &mut StaticOidsOnly as &mut dyn PgMetadataLookup,
352                ),
353            )
354            .unwrap();
355
356            let (written_prefix, value) = buffer.split_at(prefix.len());
357            assert_eq!(written_prefix, prefix);
358            assert_eq!(value[4..8], i32::to_be_bytes(flags));
359            let decoded = <Vec<Option<i32>> as FromSql<Array<Nullable<Integer>>, Pg>>::from_sql(
360                PgValue::for_test(value),
361            );
362            assert_eq!(decoded.unwrap(), array);
363        }
364    }
365
366    #[test]
367    fn check_invalid_element_size_for_array() {
368        // check for the wrong element size
369        let mut value = Vec::<u8>::new();
370
371        // dimensions
372        value.write_i32::<NetworkEndian>(1).unwrap();
373        // has null
374        value.write_i32::<NetworkEndian>(0).unwrap();
375        // oid
376        value.write_u32::<NetworkEndian>(23).unwrap();
377        // num elements
378        value.write_i32::<NetworkEndian>(2).unwrap();
379        // lower bound
380        value.write_i32::<NetworkEndian>(0).unwrap();
381        // elem size element 1
382        value.write_i32::<NetworkEndian>(6).unwrap();
383        // the element itself
384        value.write_i32::<NetworkEndian>(42).unwrap();
385
386        let value = PgValue::for_test(&value);
387        let res = <Vec<i32> as FromSql<Array<Integer>, Pg>>::from_sql(value);
388        assert!(res.is_err());
389        assert_eq!(
390            format!("{}", res.unwrap_err()),
391            "Invalid element byte count: Expected at least 6 bytes, but only 4 bytes were received",
392        );
393
394        // check for the wrong number of elements
395        let mut value = Vec::<u8>::new();
396
397        // dimensions
398        value.write_i32::<NetworkEndian>(1).unwrap();
399        // has null
400        value.write_i32::<NetworkEndian>(0).unwrap();
401        // oid
402        value.write_u32::<NetworkEndian>(23).unwrap();
403        // num elements
404        value.write_i32::<NetworkEndian>(2).unwrap();
405        // lower bound
406        value.write_i32::<NetworkEndian>(0).unwrap();
407        // elem size element 1
408        value.write_i32::<NetworkEndian>(4).unwrap();
409        // the element itself
410        value.write_i32::<NetworkEndian>(42).unwrap();
411
412        let value = PgValue::for_test(&value);
413        let res = <Vec<i32> as FromSql<Array<Integer>, Pg>>::from_sql(value);
414        assert!(res.is_err());
415        assert_eq!(
416            format!("{}", res.unwrap_err()),
417            "failed to fill whole buffer"
418        );
419    }
420
421    #[test]
422    fn check_invalid_element_size_for_multidimensional_array() {
423        // check for the wrong element size
424        let mut value = Vec::<u8>::new();
425
426        // dimensions
427        value.write_i32::<NetworkEndian>(1).unwrap();
428        // has null
429        value.write_i32::<NetworkEndian>(0).unwrap();
430        // oid
431        value.write_u32::<NetworkEndian>(23).unwrap();
432        // num elements
433        value.write_i32::<NetworkEndian>(2).unwrap();
434        // lower bound
435        value.write_i32::<NetworkEndian>(0).unwrap();
436        // elem size element 1
437        value.write_i32::<NetworkEndian>(6).unwrap();
438        // the element itself
439        value.write_i32::<NetworkEndian>(42).unwrap();
440
441        let value = PgValue::for_test(&value);
442        let res = <NdArray<i32> as FromSql<Array<Integer>, Pg>>::from_sql(value);
443        assert!(res.is_err());
444        assert_eq!(
445            format!("{}", res.unwrap_err()),
446            "Invalid element byte count: Expected at least 6 bytes, but only 4 bytes were received",
447        );
448
449        // check for the wrong number of elements
450        let mut value = Vec::<u8>::new();
451
452        // dimensions
453        value.write_i32::<NetworkEndian>(1).unwrap();
454        // has null
455        value.write_i32::<NetworkEndian>(0).unwrap();
456        // oid
457        value.write_u32::<NetworkEndian>(23).unwrap();
458        // num elements
459        value.write_i32::<NetworkEndian>(2).unwrap();
460        // lower bound
461        value.write_i32::<NetworkEndian>(0).unwrap();
462        // elem size element 1
463        value.write_i32::<NetworkEndian>(4).unwrap();
464        // the element itself
465        value.write_i32::<NetworkEndian>(42).unwrap();
466
467        let value = PgValue::for_test(&value);
468        let res = <NdArray<i32> as FromSql<Array<Integer>, Pg>>::from_sql(value);
469        assert!(res.is_err());
470        assert_eq!(
471            format!("{}", res.unwrap_err()),
472            "failed to fill whole buffer"
473        );
474    }
475}