Skip to main content

diesel/pg/types/
array.rs

1use byteorder::{NetworkEndian, ReadBytesExt, WriteBytesExt};
2use core::fmt;
3use std::io::Write;
4
5use crate::deserialize::{self, FromSql, FromSqlRow};
6use crate::pg::{Pg, PgTypeMetadata, PgValue};
7use crate::query_builder::bind_collector::ByteWrapper;
8use crate::serialize::{self, IsNull, Output, ToSql};
9use crate::sql_types::{Array, HasSqlType, Nullable};
10
11#[cfg(feature = "postgres_backend")]
12#[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::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)]
13#[diesel(sql_type = Array<T>)]
14/// Postgres allows multi-dimensional arrays of at most 6 dimensions. Internally they are stored as a flattened
15/// representation with the dimension information encoded in the header. This struct represents a
16/// multi-dimensional array with elements of type `T` as opposed to Vec<T> which can be used for 1d-arrays.
17pub struct NdArray<T> {
18    /// A list that describes how many values for each dimension are returned
19    pub dims: Vec<usize>,
20    /// The actual data flattened to a single array
21    ///
22    /// This array contains values ordered by the left most dimension
23    /// which means there will be dim[0] values for the first element of the second dimension
24    /// followed by dim[0] values for the second element of the second dimensions
25    /// and so up to dim[1] times. Afterwards that number of values is repeated for dim[2],
26    /// and so for all dimensions in the dimension field above
27    pub data: Vec<T>,
28}
29
30#[cfg(feature = "postgres_backend")]
31impl<T> HasSqlType<Array<T>> for Pg
32where
33    Pg: HasSqlType<T>,
34{
35    fn metadata(lookup: &mut Self::MetadataLookup) -> PgTypeMetadata {
36        match <Pg as HasSqlType<T>>::metadata(lookup).0 {
37            Ok(tpe) => PgTypeMetadata::new(tpe.array_oid, 0),
38            c @ Err(_) => PgTypeMetadata(c),
39        }
40    }
41}
42
43#[cfg(feature = "postgres_backend")]
44impl<T, ST> FromSql<Array<ST>, Pg> for Vec<T>
45where
46    T: FromSql<ST, Pg>,
47{
48    fn from_sql(value: PgValue<'_>) -> deserialize::Result<Self> {
49        let mut bytes = value.as_bytes();
50        let num_dimensions = bytes.read_i32::<NetworkEndian>()?;
51        let has_null = bytes.read_i32::<NetworkEndian>()? != 0;
52        let _oid = bytes.read_i32::<NetworkEndian>()?;
53
54        if num_dimensions == 0 {
55            return Ok(Vec::new());
56        }
57
58        let num_elements = bytes.read_i32::<NetworkEndian>()?;
59        let _lower_bound = bytes.read_i32::<NetworkEndian>()?;
60
61        if num_dimensions != 1 {
62            return Err("multi-dimensional arrays are not supported".into());
63        }
64
65        (0..num_elements)
66            .map(|_| -> deserialize::Result<_> {
67                let elem_size = bytes.read_i32::<NetworkEndian>()?;
68                if has_null && elem_size == -1 {
69                    T::from_nullable_sql(None)
70                } else {
71                    let (elem_bytes, new_bytes) = bytes
72                        .split_at_checked(elem_size.try_into()?)
73                        .ok_or_else(|| {
74                            ::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!(
75                                "Invalid element byte count: Expected at least {elem_size} bytes, but only {} bytes were received",
76                                bytes.len()
77                            )
78                        })?;
79                    bytes = new_bytes;
80                    T::from_sql(PgValue::new_internal(elem_bytes, &value))
81                }
82            })
83            .collect()
84    }
85}
86
87#[cfg(feature = "postgres_backend")]
88impl<T, ST> FromSql<Array<ST>, Pg> for NdArray<T>
89where
90    T: FromSql<ST, Pg>,
91{
92    fn from_sql(value: PgValue<'_>) -> deserialize::Result<Self> {
93        let mut bytes = value.as_bytes();
94        let num_dimensions = bytes.read_i32::<NetworkEndian>()?;
95        let has_null = bytes.read_i32::<NetworkEndian>()? != 0;
96        let _oid = bytes.read_i32::<NetworkEndian>()?;
97
98        if num_dimensions == 0 {
99            return Ok(NdArray {
100                dims: Vec::new(),
101                data: Vec::new(),
102            });
103        }
104
105        let num_dims: usize = num_dimensions
106            .try_into()
107            .map_err(|_| "number of dimensions must be positive")?;
108
109        let dims = (0..num_dims)
110            .map(|_| {
111                let num_elements = bytes.read_i32::<NetworkEndian>()?;
112                let _lower_bound = bytes.read_i32::<NetworkEndian>()?;
113
114                let dim: usize = num_elements
115                    .try_into()
116                    .map_err(|_| "array dimension length must be positive")?;
117                Ok(dim)
118            })
119            .collect::<deserialize::Result<Vec<_>>>()?;
120
121        let data = (0..dims.iter().product::<usize>())
122            .map(|_| -> deserialize::Result<T> {
123                let elem_size = bytes.read_i32::<NetworkEndian>()?;
124                if has_null && elem_size == -1 {
125                    T::from_nullable_sql(None)
126                } else {
127                    let (elem_bytes, new_bytes) = bytes
128                        .split_at_checked(elem_size.try_into()?)
129                        .ok_or_else(|| {
130                            ::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!(
131                                "Invalid element byte count: Expected at least {elem_size} bytes, but only {} bytes were received",
132                                bytes.len()
133                            )
134                        })?;
135                    bytes = new_bytes;
136                    T::from_sql(PgValue::new_internal(elem_bytes, &value))
137                }
138            })
139            .collect::<deserialize::Result<Vec<T>>>()?;
140        Ok(NdArray { dims, data })
141    }
142}
143
144use crate::expression::AsExpression;
145use crate::expression::bound::Bound;
146
147macro_rules! array_as_expression {
148    ($ty:ty, $sql_type:ty) => {
149        #[cfg(feature = "postgres_backend")]
150        // this simplifies the macro implementation
151        // as some macro calls use this lifetime
152        #[allow(clippy::extra_unused_lifetimes)]
153        impl<'a, 'b, ST: 'static, T> AsExpression<$sql_type> for $ty {
154            type Expression = Bound<$sql_type, Self>;
155
156            fn as_expression(self) -> Self::Expression {
157                Bound::new(self)
158            }
159        }
160    };
161}
162
163#[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>);
164#[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>>);
165#[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>);
166#[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>>);
167#[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>);
168#[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>>);
169#[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>);
170#[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>>);
171#[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>);
172#[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>>);
173
174#[cfg(feature = "postgres_backend")]
175impl<ST, T> ToSql<Array<ST>, Pg> for [T]
176where
177    Pg: HasSqlType<ST>,
178    T: ToSql<ST, Pg>,
179{
180    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
181        let num_dimensions = 1;
182        out.write_i32::<NetworkEndian>(num_dimensions)?;
183        let flags = 0;
184        out.write_i32::<NetworkEndian>(flags)?;
185        let element_oid = Pg::metadata(out.metadata_lookup()).oid()?;
186        out.write_u32::<NetworkEndian>(element_oid)?;
187        out.write_i32::<NetworkEndian>(self.len().try_into()?)?;
188        let lower_bound = 1;
189        out.write_i32::<NetworkEndian>(lower_bound)?;
190
191        // This buffer is created outside of the loop to reuse the underlying memory allocation
192        // For most cases all array elements will have the same serialized size
193        let mut buffer = Vec::new();
194
195        for elem in self.iter() {
196            let is_null = {
197                let mut temp_buffer = Output::new(ByteWrapper(&mut buffer), out.metadata_lookup());
198                elem.to_sql(&mut temp_buffer)?
199            };
200
201            if let IsNull::No = is_null {
202                out.write_i32::<NetworkEndian>(buffer.len().try_into()?)?;
203                out.write_all(&buffer)?;
204                buffer.clear();
205            } else {
206                // https://github.com/postgres/postgres/blob/82f8107b92c9104ec9d9465f3f6a4c6dab4c124a/src/backend/utils/adt/arrayfuncs.c#L1461
207                out.write_i32::<NetworkEndian>(-1)?;
208            }
209        }
210
211        Ok(IsNull::No)
212    }
213}
214
215#[cfg(feature = "postgres_backend")]
216impl<ST, T> ToSql<Nullable<Array<ST>>, Pg> for [T]
217where
218    [T]: ToSql<Array<ST>, Pg>,
219    ST: 'static,
220{
221    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
222        ToSql::<Array<ST>, Pg>::to_sql(self, out)
223    }
224}
225
226#[cfg(feature = "postgres_backend")]
227impl<ST, T> ToSql<Array<ST>, Pg> for Vec<T>
228where
229    ST: 'static,
230    [T]: ToSql<Array<ST>, Pg>,
231    T: fmt::Debug,
232{
233    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
234        (self as &[T]).to_sql(out)
235    }
236}
237
238#[cfg(feature = "postgres_backend")]
239impl<ST, T> ToSql<Nullable<Array<ST>>, Pg> for Vec<T>
240where
241    ST: 'static,
242    Vec<T>: ToSql<Array<ST>, Pg>,
243{
244    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result {
245        ToSql::<Array<ST>, Pg>::to_sql(self, out)
246    }
247}
248
249#[cfg(test)]
250mod tests {
251    use byteorder::{NetworkEndian, WriteBytesExt};
252
253    use crate::data_types::NdArray;
254    use crate::deserialize::FromSql;
255    use crate::pg::{Pg, PgValue};
256    use crate::sql_types::{Array, Integer};
257
258    #[test]
259    fn check_invalid_element_size_for_array() {
260        // check for the wrong element size
261        let mut value = Vec::<u8>::new();
262
263        // dimensions
264        value.write_i32::<NetworkEndian>(1).unwrap();
265        // has null
266        value.write_i32::<NetworkEndian>(0).unwrap();
267        // oid
268        value.write_i32::<NetworkEndian>(0).unwrap();
269        // num elements
270        value.write_i32::<NetworkEndian>(2).unwrap();
271        // lower bound
272        value.write_i32::<NetworkEndian>(0).unwrap();
273        // elem size element 1
274        value.write_i32::<NetworkEndian>(6).unwrap();
275        // the element itself
276        value.write_i32::<NetworkEndian>(42).unwrap();
277
278        let value = PgValue::for_test(&value);
279        let res = <Vec<i32> as FromSql<Array<Integer>, Pg>>::from_sql(value);
280        assert!(res.is_err());
281        assert_eq!(
282            format!("{}", res.unwrap_err()),
283            "Invalid element byte count: Expected at least 6 bytes, but only 4 bytes were received",
284        );
285
286        // check for the wrong number of elements
287        let mut value = Vec::<u8>::new();
288
289        // dimensions
290        value.write_i32::<NetworkEndian>(1).unwrap();
291        // has null
292        value.write_i32::<NetworkEndian>(0).unwrap();
293        // oid
294        value.write_i32::<NetworkEndian>(0).unwrap();
295        // num elements
296        value.write_i32::<NetworkEndian>(2).unwrap();
297        // lower bound
298        value.write_i32::<NetworkEndian>(0).unwrap();
299        // elem size element 1
300        value.write_i32::<NetworkEndian>(4).unwrap();
301        // the element itself
302        value.write_i32::<NetworkEndian>(42).unwrap();
303
304        let value = PgValue::for_test(&value);
305        let res = <Vec<i32> as FromSql<Array<Integer>, Pg>>::from_sql(value);
306        assert!(res.is_err());
307        assert_eq!(
308            format!("{}", res.unwrap_err()),
309            "failed to fill whole buffer"
310        );
311    }
312
313    #[test]
314    fn check_invalid_element_size_for_multidimensional_array() {
315        // check for the wrong element size
316        let mut value = Vec::<u8>::new();
317
318        // dimensions
319        value.write_i32::<NetworkEndian>(1).unwrap();
320        // has null
321        value.write_i32::<NetworkEndian>(0).unwrap();
322        // oid
323        value.write_i32::<NetworkEndian>(0).unwrap();
324        // num elements
325        value.write_i32::<NetworkEndian>(2).unwrap();
326        // lower bound
327        value.write_i32::<NetworkEndian>(0).unwrap();
328        // elem size element 1
329        value.write_i32::<NetworkEndian>(6).unwrap();
330        // the element itself
331        value.write_i32::<NetworkEndian>(42).unwrap();
332
333        let value = PgValue::for_test(&value);
334        let res = <NdArray<i32> as FromSql<Array<Integer>, Pg>>::from_sql(value);
335        assert!(res.is_err());
336        assert_eq!(
337            format!("{}", res.unwrap_err()),
338            "Invalid element byte count: Expected at least 6 bytes, but only 4 bytes were received",
339        );
340
341        // check for the wrong number of elements
342        let mut value = Vec::<u8>::new();
343
344        // dimensions
345        value.write_i32::<NetworkEndian>(1).unwrap();
346        // has null
347        value.write_i32::<NetworkEndian>(0).unwrap();
348        // oid
349        value.write_i32::<NetworkEndian>(0).unwrap();
350        // num elements
351        value.write_i32::<NetworkEndian>(2).unwrap();
352        // lower bound
353        value.write_i32::<NetworkEndian>(0).unwrap();
354        // elem size element 1
355        value.write_i32::<NetworkEndian>(4).unwrap();
356        // the element itself
357        value.write_i32::<NetworkEndian>(42).unwrap();
358
359        let value = PgValue::for_test(&value);
360        let res = <NdArray<i32> as FromSql<Array<Integer>, Pg>>::from_sql(value);
361        assert!(res.is_err());
362        assert_eq!(
363            format!("{}", res.unwrap_err()),
364            "failed to fill whole buffer"
365        );
366    }
367}