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) -> NdArray<T> {
        NdArray {
            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: &NdArray<T>) -> 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: &NdArray<T>)
        -> ::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: &NdArray<T>) -> ::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        let flags = 0;
192        out.write_i32::<NetworkEndian>(flags)?;
193        let element_oid = Pg::metadata(out.metadata_lookup()).oid()?;
194        out.write_u32::<NetworkEndian>(element_oid)?;
195        out.write_i32::<NetworkEndian>(self.len().try_into()?)?;
196        let lower_bound = 1;
197        out.write_i32::<NetworkEndian>(lower_bound)?;
198
199        // This buffer is created outside of the loop to reuse the underlying memory allocation
200        // For most cases all array elements will have the same serialized size
201        let mut buffer = Vec::new();
202
203        for elem in self.iter() {
204            let is_null = {
205                let mut temp_buffer = Output::new(ByteWrapper(&mut buffer), out.metadata_lookup());
206                elem.to_sql(&mut temp_buffer)?
207            };
208
209            if let IsNull::No = is_null {
210                out.write_i32::<NetworkEndian>(buffer.len().try_into()?)?;
211                out.write_all(&buffer)?;
212                buffer.clear();
213            } else {
214                // https://github.com/postgres/postgres/blob/82f8107b92c9104ec9d9465f3f6a4c6dab4c124a/src/backend/utils/adt/arrayfuncs.c#L1461
215                out.write_i32::<NetworkEndian>(-1)?;
216            }
217        }
218
219        Ok(IsNull::No)
220    }
221}
222
223#[cfg(feature = "postgres_backend")]
224impl<ST, T> ToSql<Nullable<Array<ST>>, Pg> for [T]
225where
226    [T]: ToSql<Array<ST>, Pg>,
227    ST: 'static,
228{
229    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
230        ToSql::<Array<ST>, Pg>::to_sql(self, out)
231    }
232}
233
234#[cfg(feature = "postgres_backend")]
235impl<ST, T> ToSql<Array<ST>, Pg> for Vec<T>
236where
237    ST: 'static,
238    [T]: ToSql<Array<ST>, Pg>,
239    T: fmt::Debug,
240{
241    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
242        (self as &[T]).to_sql(out)
243    }
244}
245
246#[cfg(feature = "postgres_backend")]
247impl<ST, T> ToSql<Nullable<Array<ST>>, Pg> for Vec<T>
248where
249    ST: 'static,
250    Vec<T>: ToSql<Array<ST>, Pg>,
251{
252    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
253        ToSql::<Array<ST>, Pg>::to_sql(self, out)
254    }
255}
256
257#[cfg(test)]
258mod tests {
259    use byteorder::{NetworkEndian, WriteBytesExt};
260
261    use crate::data_types::NdArray;
262    use crate::deserialize::{self, FromSql};
263    use crate::pg::{Pg, PgValue};
264    use crate::sql_types::{Array, Integer};
265
266    #[derive(Debug, PartialEq)]
267    struct ElementOid(u32);
268
269    impl FromSql<Integer, Pg> for ElementOid {
270        fn from_sql(value: PgValue<'_>) -> deserialize::Result<Self> {
271            Ok(Self(value.get_oid().get()))
272        }
273    }
274
275    fn one_element_array_value(element_oid: u32) -> Vec<u8> {
276        let mut value = Vec::<u8>::new();
277        value.write_i32::<NetworkEndian>(1).unwrap();
278        value.write_i32::<NetworkEndian>(0).unwrap();
279        value.write_u32::<NetworkEndian>(element_oid).unwrap();
280        value.write_i32::<NetworkEndian>(1).unwrap();
281        value.write_i32::<NetworkEndian>(0).unwrap();
282        value.write_i32::<NetworkEndian>(4).unwrap();
283        value.write_i32::<NetworkEndian>(42).unwrap();
284        value
285    }
286
287    #[test]
288    fn zero_element_oid_is_rejected() {
289        let value = one_element_array_value(0);
290        let value = PgValue::for_test(&value);
291        let res = <Vec<ElementOid> as FromSql<Array<Integer>, Pg>>::from_sql(value);
292        assert!(res.is_err());
293        assert_eq!(format!("{}", res.unwrap_err()), "Oid's aren't zero");
294
295        let value = one_element_array_value(0);
296        let value = PgValue::for_test(&value);
297        let res = <NdArray<ElementOid> as FromSql<Array<Integer>, Pg>>::from_sql(value);
298        assert!(res.is_err());
299        assert_eq!(format!("{}", res.unwrap_err()), "Oid's aren't zero");
300    }
301
302    #[test]
303    fn nonzero_header_oid_is_used_for_element() {
304        let value = one_element_array_value(23);
305        let value = PgValue::for_test(&value);
306        let res = <Vec<ElementOid> as FromSql<Array<Integer>, Pg>>::from_sql(value);
307        assert_eq!(vec![ElementOid(23)], res.unwrap());
308
309        let value = one_element_array_value(23);
310        let value = PgValue::for_test(&value);
311        let res = <NdArray<ElementOid> as FromSql<Array<Integer>, Pg>>::from_sql(value).unwrap();
312        assert_eq!(vec![1], res.dims);
313        assert_eq!(vec![ElementOid(23)], res.data);
314    }
315
316    #[test]
317    fn check_invalid_element_size_for_array() {
318        // check for the wrong element size
319        let mut value = Vec::<u8>::new();
320
321        // dimensions
322        value.write_i32::<NetworkEndian>(1).unwrap();
323        // has null
324        value.write_i32::<NetworkEndian>(0).unwrap();
325        // oid
326        value.write_u32::<NetworkEndian>(23).unwrap();
327        // num elements
328        value.write_i32::<NetworkEndian>(2).unwrap();
329        // lower bound
330        value.write_i32::<NetworkEndian>(0).unwrap();
331        // elem size element 1
332        value.write_i32::<NetworkEndian>(6).unwrap();
333        // the element itself
334        value.write_i32::<NetworkEndian>(42).unwrap();
335
336        let value = PgValue::for_test(&value);
337        let res = <Vec<i32> as FromSql<Array<Integer>, Pg>>::from_sql(value);
338        assert!(res.is_err());
339        assert_eq!(
340            format!("{}", res.unwrap_err()),
341            "Invalid element byte count: Expected at least 6 bytes, but only 4 bytes were received",
342        );
343
344        // check for the wrong number of elements
345        let mut value = Vec::<u8>::new();
346
347        // dimensions
348        value.write_i32::<NetworkEndian>(1).unwrap();
349        // has null
350        value.write_i32::<NetworkEndian>(0).unwrap();
351        // oid
352        value.write_u32::<NetworkEndian>(23).unwrap();
353        // num elements
354        value.write_i32::<NetworkEndian>(2).unwrap();
355        // lower bound
356        value.write_i32::<NetworkEndian>(0).unwrap();
357        // elem size element 1
358        value.write_i32::<NetworkEndian>(4).unwrap();
359        // the element itself
360        value.write_i32::<NetworkEndian>(42).unwrap();
361
362        let value = PgValue::for_test(&value);
363        let res = <Vec<i32> as FromSql<Array<Integer>, Pg>>::from_sql(value);
364        assert!(res.is_err());
365        assert_eq!(
366            format!("{}", res.unwrap_err()),
367            "failed to fill whole buffer"
368        );
369    }
370
371    #[test]
372    fn check_invalid_element_size_for_multidimensional_array() {
373        // check for the wrong element size
374        let mut value = Vec::<u8>::new();
375
376        // dimensions
377        value.write_i32::<NetworkEndian>(1).unwrap();
378        // has null
379        value.write_i32::<NetworkEndian>(0).unwrap();
380        // oid
381        value.write_u32::<NetworkEndian>(23).unwrap();
382        // num elements
383        value.write_i32::<NetworkEndian>(2).unwrap();
384        // lower bound
385        value.write_i32::<NetworkEndian>(0).unwrap();
386        // elem size element 1
387        value.write_i32::<NetworkEndian>(6).unwrap();
388        // the element itself
389        value.write_i32::<NetworkEndian>(42).unwrap();
390
391        let value = PgValue::for_test(&value);
392        let res = <NdArray<i32> as FromSql<Array<Integer>, Pg>>::from_sql(value);
393        assert!(res.is_err());
394        assert_eq!(
395            format!("{}", res.unwrap_err()),
396            "Invalid element byte count: Expected at least 6 bytes, but only 4 bytes were received",
397        );
398
399        // check for the wrong number of elements
400        let mut value = Vec::<u8>::new();
401
402        // dimensions
403        value.write_i32::<NetworkEndian>(1).unwrap();
404        // has null
405        value.write_i32::<NetworkEndian>(0).unwrap();
406        // oid
407        value.write_u32::<NetworkEndian>(23).unwrap();
408        // num elements
409        value.write_i32::<NetworkEndian>(2).unwrap();
410        // lower bound
411        value.write_i32::<NetworkEndian>(0).unwrap();
412        // elem size element 1
413        value.write_i32::<NetworkEndian>(4).unwrap();
414        // the element itself
415        value.write_i32::<NetworkEndian>(42).unwrap();
416
417        let value = PgValue::for_test(&value);
418        let res = <NdArray<i32> as FromSql<Array<Integer>, Pg>>::from_sql(value);
419        assert!(res.is_err());
420        assert_eq!(
421            format!("{}", res.unwrap_err()),
422            "failed to fill whole buffer"
423        );
424    }
425}