Repository: diesel-rs/diesel
File: diesel/src/pg/types/array.rs (Rust)

Code before:
    T: FromSql<ST, Pg>,
{
    fn from_sql(value: PgValue<'_>) -> deserialize::Result<Self> {
        let mut bytes = value.as_bytes();
        let num_dimensions = bytes.read_i32::<NetworkEndian>()?;
        let has_null = bytes.read_i32::<NetworkEndian>()? != 0;
        let _oid = bytes.read_i32::<NetworkEndian>()?;

        if num_dimensions == 0 {
            return Ok(NdArray {
                dims: Vec::new(),
                data: Vec::new(),
            });
        }

        if num_dimensions == 1 {
            return Err("trying to deserialize one-dimensional postgres array into NdArray<T>, use Vec<T> instead".into());
        }

        if num_dimensions != 2 {
            return Err(
                "currently only two-dimensional arrays are supported for NdArray<T>".into(),
            );
        }

        // This is very hardcoded for two-dimensional arrays for now but can easily be adapted to be dynamic later
        let num_elements_dim1 = bytes.read_i32::<NetworkEndian>()?;
        let _lower_bound_dim1 = bytes.read_i32::<NetworkEndian>()?;

        let num_elements_dim2 = bytes.read_i32::<NetworkEndian>()?;
        let _lower_bound_dim2 = bytes.read_i32::<NetworkEndian>()?;

        // This error should never occur as Postgres arrays won't have negative dimensions
        let dim1: usize = num_elements_dim1
            .try_into()
            .map_err(|_| "array dimension length must be positive")?;

        let dim2: usize = num_elements_dim2
            .try_into()
            .map_err(|_| "array dimension length must be positive")?;

        let ndims = vec![dim1, dim2];

        let data = (0..dim1 * dim2)
            .map(|_| -> deserialize::Result<T> {
                let elem_size = bytes.read_i32::<NetworkEndian>()?;
                if has_null && elem_size == -1 {
                    T::from_nullable_sql(None)
                } else {
                    let (elem_bytes, new_bytes) = bytes.split_at(elem_size.try_into()?);
                    bytes = new_bytes;

Review comment (suggestion):
I would welcome generalizing this to more dimensions before landing this PR. From what I can see here that shouldn't be too hard as we only need to:

* read the elements_per_dimension + bound tuple for each dimension in a loop
* convert the elements_per_dimension in the same loop
* Produce the ndims vector out of that.
* Use the [https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.product] iterator extension to get the actual number of elements to read
* Read the elements as already done in a flatten manner

Code after:
            })
            .collect()
    }
}

#[cfg(feature = "postgres_backend")]
impl<T, ST> FromSql<Array<ST>, Pg> for NdArray<T>
where
    T: FromSql<ST, Pg>,
{
    fn from_sql(value: PgValue<'_>) -> deserialize::Result<Self> {
        let mut bytes = value.as_bytes();
        let num_dimensions = bytes.read_i32::<NetworkEndian>()?;
        let has_null = bytes.read_i32::<NetworkEndian>()? != 0;
        let _oid = bytes.read_i32::<NetworkEndian>()?;

        if num_dimensions == 0 {
            return Ok(NdArray {
                dims: Vec::new(),
                data: Vec::new(),
            });
        }

        let num_dims: usize = num_dimensions
            .try_into()
            .map_err(|_| "number of dimensions must be positive")?;

        let dims = (0..num_dims)
            .map(|_| {
                let num_elements = bytes.read_i32::<NetworkEndian>()?;
                let _lower_bound = bytes.read_i32::<NetworkEndian>()?;

                let dim: usize = num_elements
                    .try_into()
                    .map_err(|_| "array dimension length must be positive")?;
                Ok(dim)
            })
            .collect::<deserialize::Result<Vec<_>>>()?;

        let data = (0..dims.iter().product::<usize>())
            .map(|_| -> deserialize::Result<T> {
                let elem_size = bytes.read_i32::<NetworkEndian>()?;
                if has_null && elem_size == -1 {
                    T::from_nullable_sql(None)
                } else {
                    let (elem_bytes, new_bytes) = bytes.split_at(elem_size.try_into()?);
                    bytes = new_bytes;
                    T::from_sql(PgValue::new_internal(elem_bytes, &value))
                }
            })
            .collect::<deserialize::Result<Vec<T>>>()?;