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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
#[derive(Clone, Debug, Eq, PartialEq, elephantry_derive::Entity, elephantry_derive::Composite)]
#[elephantry(internal)]
pub struct Column {
    #[elephantry(default)]
    pub is_primary: bool,
    pub name: String,
    pub oid: crate::pq::Oid,
    pub len: Option<i32>,
    pub default: Option<String>,
    pub is_notnull: bool,
    pub comment: Option<String>,
    ty: String,
}

impl Column {
    #[must_use]
    pub fn ty(&self) -> String {
        self.ty_recur(self.oid, self.len)
    }

    fn ty_recur(&self, oid: crate::pq::Oid, len: Option<i32>) -> String {
        if let Ok(ty) = crate::pq::types::Type::try_from(oid) {
            if let crate::pq::types::Kind::Array(oid) = ty.kind {
                format!("{}[]", self.ty_recur(oid, len))
            } else if let Some(len) = len {
                format!("{}({len})", ty.name)
            } else {
                ty.name.to_string()
            }
        } else {
            self.ty.clone()
        }
    }
}

/**
 * Retreive columns of the `schema.relation` relation.
 */
pub fn relation(
    connection: &crate::Connection,
    schema: &str,
    relation: &str,
) -> crate::Result<Vec<crate::inspect::Column>> {
    let oid = connection
        .query_one::<i32>(
            "
select c.oid as oid
    from
        pg_catalog.pg_class c
            left join pg_catalog.pg_namespace n on n.oid = c.relnamespace
    where n.nspname = $1
        and c.relname = $2
    ",
            &[&schema, &relation],
        )
        .map_err(|_| crate::Error::Inspect(format!("Unknow relation {schema}.{relation}")))?;

    connection
        .query(
            r#"
select
    att.attnum = any(ind.indkey) as "is_primary",
    att.attname as "name",
    typ.oid as "oid",
    case
      when att.attlen < 0 and att.atttypmod > 0 then format('%s(%s)', typ.typname, catt.len)
      when name.nspname != 'pg_catalog' then format('%s.%s', name.nspname, typ.typname)
      else typ.typname
    end as "ty",
    catt.len as "len",
    pg_catalog.pg_get_expr(def.adbin, def.adrelid) as "default",
    att.attnotnull as "is_notnull",
    dsc.description as "comment"
from
  pg_catalog.pg_attribute att
    join pg_catalog.pg_type  typ  on att.atttypid = typ.oid
    join pg_catalog.pg_class cla  on att.attrelid = cla.oid
    join pg_catalog.pg_namespace clns on cla.relnamespace = clns.oid
    left join pg_catalog.pg_description dsc on cla.oid = dsc.objoid and att.attnum = dsc.objsubid
    left join pg_catalog.pg_attrdef def     on att.attrelid = def.adrelid and att.attnum = def.adnum
    left join pg_catalog.pg_index ind       on cla.oid = ind.indrelid and ind.indisprimary
    left join pg_catalog.pg_namespace name  on typ.typnamespace = name.oid,
    lateral (
        select
            case
                when atttypmod = -1 then null
                when atttypid in ($*, $*) then atttypmod - 4
                else atttypmod
            end
        ) as catt("len")
where
    att.attnum > 0
    and not att.attisdropped
    and att.attrelid = $*
order by
    att.attnum
"#,
            &[
                &crate::pq::types::BPCHAR.oid,
                &crate::pq::types::VARCHAR.oid,
                &oid,
            ],
        )
        .map(Iterator::collect)
}