Skip to content
This repository was archived by the owner on Aug 18, 2025. It is now read-only.

Commit 95cb7b6

Browse files
committed
Include column type definitions in schema
1 parent e1afc3a commit 95cb7b6

File tree

2 files changed

+87
-14
lines changed

2 files changed

+87
-14
lines changed

src/lib.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,43 @@ pub use table::Table;
77
mod column;
88
pub use column::Column;
99
mod types;
10-
pub use types::Type;
10+
pub use types::{Enum, Type, TypeDefinition, Composite};
1111

1212
pub struct Schema {
1313
pub tables: Vec<Table>,
14+
pub types: Vec<TypeDefinition>,
1415
}
1516

1617
impl Schema {
1718
#[cfg(feature = "postgres")]
18-
pub async fn from_postgres(client: &Client) -> Result<Self, anyhow::Error> {
19+
pub async fn from_postgres(client: &Client) -> anyhow::Result<Self> {
1920
let sql = r#"
2021
SELECT table_name
2122
FROM information_schema.tables
2223
WHERE table_schema = 'public'
2324
"#;
2425

25-
let mut tables = Vec::new();
26+
let (mut tables, mut types) = (Vec::new(), Vec::new());
2627
for row in client.query(sql, &[]).await? {
2728
let name = row.get::<_, &str>(0);
2829
let table = Table::from_postgres(name, client).await?;
30+
31+
for column in table.columns.values() {
32+
match &column.r#type {
33+
Type::Composite(c) => types.push(TypeDefinition::Composite(
34+
Composite::from_postgres(&c.name, client).await?,
35+
)),
36+
Type::Enum(e) => types.push(TypeDefinition::Enum(
37+
Enum::from_postgres(&e.name, client).await?,
38+
)),
39+
_ => {}
40+
}
41+
}
42+
2943
tables.push(table);
3044
}
3145

32-
Ok(Self { tables })
46+
Ok(Self { tables, types })
3347
}
3448
}
3549

@@ -40,6 +54,11 @@ impl fmt::Display for Schema {
4054
f.write_str("\n")?;
4155
}
4256

57+
for type_def in &self.types {
58+
f.write_fmt(format_args!("{type_def}"))?;
59+
f.write_str("\n")?;
60+
}
61+
4362
Ok(())
4463
}
4564
}

src/types.rs

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::fmt;
22
use std::str::FromStr;
33

44
use async_recursion::async_recursion;
5-
use heck::AsUpperCamelCase;
5+
use heck::{AsUpperCamelCase, AsSnakeCase};
66
use tokio_postgres::types::{ToSql, Type as PgType};
77
use tokio_postgres::Client;
88

@@ -156,15 +156,69 @@ impl fmt::Display for Type {
156156
}
157157
}
158158

159+
#[derive(Debug)]
160+
pub enum TypeDefinition {
161+
Composite(Composite),
162+
Enum(Enum),
163+
}
164+
165+
impl fmt::Display for TypeDefinition {
166+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167+
match self {
168+
Self::Composite(inner) => f.write_fmt(format_args!("{inner}")),
169+
Self::Enum(inner) => f.write_fmt(format_args!("{inner}")),
170+
}
171+
}
172+
}
173+
174+
#[derive(Debug)]
175+
pub struct Composite {
176+
pub name: String,
177+
pub fields: Vec<(String, Type)>,
178+
}
179+
180+
impl Composite {
181+
pub(crate) async fn from_postgres(
182+
name: &str,
183+
client: &Client,
184+
) -> Result<Self, tokio_postgres::Error> {
185+
let sql = "SELECT attname, atttypid
186+
FROM pg_type, pg_attribute
187+
WHERE typname = $1 AND pg_type.typrelid = pg_attribute.attrelid";
188+
189+
let mut fields = Vec::new();
190+
for row in client.query(sql, &[&name]).await? {
191+
let name = row.get::<_, &str>(0);
192+
let r#type = Type::from_postgres_by_id(row.get(1), client).await?;
193+
fields.push((name.to_owned(), r#type));
194+
}
195+
196+
Ok(Self {
197+
name: name.to_owned(),
198+
fields,
199+
})
200+
}
201+
}
202+
203+
impl fmt::Display for Composite {
204+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205+
f.write_fmt(format_args!("struct {} {{\n", AsUpperCamelCase(&self.name)))?;
206+
for (name, ty) in &self.fields {
207+
f.write_fmt(format_args!(" {}: {ty},\n", AsSnakeCase(&name)))?;
208+
}
209+
210+
f.write_str("}\n")
211+
}
212+
}
213+
159214
#[derive(Debug, PartialEq)]
160-
pub struct PgEnum {
215+
pub struct Enum {
161216
name: String,
162217
variants: Vec<String>,
163218
}
164219

165-
impl PgEnum {
166-
async fn from_postgres(
167-
id: u32,
220+
impl Enum {
221+
pub(crate) async fn from_postgres(
168222
name: &str,
169223
client: &Client,
170224
) -> Result<Self, tokio_postgres::Error> {
@@ -175,11 +229,11 @@ impl PgEnum {
175229

176230
let sql = r#"
177231
SELECT enumlabel
178-
FROM pg_catalog.pg_enum
179-
WHERE enumtypid = $1
232+
FROM pg_catalog.pg_enum, pg_catalog.pg_type
233+
WHERE pg_type.typname = $1 AND pg_type.oid = enumtypid
180234
ORDER BY enumsortorder ASC
181235
"#;
182-
for row in client.query(sql, &[&id]).await? {
236+
for row in client.query(sql, &[&name]).await? {
183237
let label = row.get::<_, &str>(0);
184238
new.variants.push(label.to_owned());
185239
}
@@ -188,11 +242,11 @@ impl PgEnum {
188242
}
189243
}
190244

191-
impl fmt::Display for PgEnum {
245+
impl fmt::Display for Enum {
192246
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193247
f.write_fmt(format_args!("enum {} {{\n", AsUpperCamelCase(&self.name)))?;
194248
for variant in &self.variants {
195-
f.write_fmt(format_args!(" {},", AsUpperCamelCase(&variant)))?;
249+
f.write_fmt(format_args!(" {},\n", AsUpperCamelCase(&variant)))?;
196250
}
197251

198252
f.write_str("}\n")

0 commit comments

Comments
 (0)