1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// Copyright Materialize, Inc. and contributors. All rights reserved.
//
// Use of this software is governed by the Business Source License
// included in the LICENSE file.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0.

// TODO(benesch): remove this module if upstream adds implementations of FromSql
// to tuples directly: https://github.com/sfackler/rust-postgres/pull/626.

use std::error::Error;

use postgres_types::{private, FromSql, Kind, Type, WrongType};

/// A wrapper for tuples that implements [`FromSql`] for PostgreSQL composite
/// types.
#[derive(Debug, PartialEq, Eq)]
pub struct Record<T>(pub T);

macro_rules! impl_tuple {
    ($n:expr; $($ty_ident:ident),*; $($var_ident:ident),*) => {
        impl<'a, $($ty_ident),*> FromSql<'a> for Record<($($ty_ident,)*)>
        where
            $($ty_ident: FromSql<'a>),*
        {
            fn from_sql(
                _: &Type,
                mut raw: &'a [u8],
            ) -> Result<Record<($($ty_ident,)*)>, Box<dyn Error + Sync + Send>> {
                let num_fields = private::read_be_i32(&mut raw)?;
                let num_fields = u32::try_from(num_fields).map_err(|_| format!("number of fields cannot be negative: {}", num_fields))?;
                if num_fields != $n {
                    return Err(format!(
                        "Postgres record field count does not match Rust tuple length: {} vs {}",
                        num_fields,
                        $n,
                    ).into());
                }

                $(
                    let oid = private::read_be_i32(&mut raw)?;
                    let oid = u32::try_from(oid).map_err(|_| format!("OIDs cannot be negative: {}", oid))?;
                    let ty = match Type::from_oid(oid) {
                        None => {
                            return Err(format!(
                                "cannot decode OID {} inside of anonymous record",
                                oid,
                            ).into());
                        }
                        Some(ty) if !$ty_ident::accepts(&ty) => {
                            return Err(Box::new(WrongType::new::<$ty_ident>(ty.clone())));
                        }
                        Some(ty) => ty,
                    };
                    let $var_ident = private::read_value(&ty, &mut raw)?;
                )*

                Ok(Record(($($var_ident,)*)))
            }

            fn accepts(ty: &Type) -> bool {
                match ty.kind() {
                    Kind::Pseudo => *ty == Type::RECORD,
                    Kind::Composite(fields) => fields.len() == $n,
                    _ => false,
                }
            }
        }
    };
}

impl_tuple!(0; ; );
impl_tuple!(1; T0; v0);
impl_tuple!(2; T0, T1; v0, v1);
impl_tuple!(3; T0, T1, T2; v0, v1, v2);
impl_tuple!(4; T0, T1, T2, T3; v0, v1, v2, v3);