feat: serialize database schema

This commit is contained in:
Alexander Navarro 2025-01-24 15:52:44 -03:00
parent fc0a9b4206
commit de506a73df
5 changed files with 460 additions and 8 deletions

View file

@ -1,3 +1,5 @@
use std::io;
use clap::ValueEnum;
use sea_schema::postgres::discovery::SchemaDiscovery;
use sqlx::PgPool;
@ -5,7 +7,10 @@ use url::Url;
use crate::error::{Error, Result};
use self::schema::{DataModel, Schema};
mod postgres;
mod schema;
#[derive(ValueEnum, Clone)]
pub enum Database {
@ -59,7 +64,12 @@ pub async fn discover_scheme(url: String, schema: Option<String>) -> Result<()>
let schema = schema_discovery.discover().await?;
println!("{:#?}", schema);
let mut data_model = DataModel::new();
data_model
.schemas
.insert(schema.schema.to_owned(), Schema::from(schema));
data_model.write(&mut io::stdout())?;
Ok(())
}

157
src/sql/schema.rs Normal file
View file

@ -0,0 +1,157 @@
use heck::ToTitleCase;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
use std::io::Write;
use toml_edit::visit_mut::VisitMut;
use sea_schema::postgres::def as sea_schema_def;
use crate::error;
struct InlineTableFix;
// the toml serializer doesn't generate inline tables by default
impl VisitMut for InlineTableFix {
fn visit_table_like_kv_mut(
&mut self,
mut key: toml_edit::KeyMut<'_>,
node: &mut toml_edit::Item,
) {
// add the keys of the tables that needs to be inline here
if ["reference", "type"].contains(&key.get()) {
if let toml_edit::Item::Table(table) = node {
// Turn the table into an inline table.
let table = std::mem::replace(table, toml_edit::Table::new());
let inline_table = table.into_inline_table();
key.fmt();
*node = toml_edit::Item::Value(toml_edit::Value::InlineTable(inline_table));
}
}
toml_edit::visit_mut::visit_table_like_kv_mut(self, key, node);
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct DataModel {
#[serde(flatten)]
pub schemas: HashMap<String, Schema>,
}
impl DataModel {
pub fn new() -> Self {
Self {
schemas: HashMap::new(),
}
}
}
impl DataModel {
pub fn write<T: Write>(&self, writter: &mut T) -> error::Result<()> {
writter.write_all(self.to_string().as_bytes())?;
Ok(())
}
}
impl fmt::Display for DataModel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let toml = toml::to_string_pretty(self).map_err(|_| fmt::Error)?;
let mut document: toml_edit::DocumentMut = toml.parse().map_err(|_| fmt::Error)?;
let mut visitor = InlineTableFix;
visitor.visit_document_mut(&mut document);
write!(f, "{}", document)
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Schema {
#[serde(flatten)]
pub tables: HashMap<String, Table>,
}
impl From<sea_schema_def::Schema> for Schema {
fn from(schema: sea_schema_def::Schema) -> Self {
let mut tables: HashMap<String, Table> = HashMap::new();
for table in schema.tables {
tables.insert(table.info.to_owned().name, table.into());
}
Self { tables }
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Table {
/// Default is capitalized name
display_name: Option<String>,
columns: Vec<Column>,
}
impl From<sea_schema_def::TableDef> for Table {
fn from(table: sea_schema_def::TableDef) -> Self {
Self {
display_name: Some(table.info.name.to_owned().to_title_case()),
columns: table
.columns
.iter()
.map(|column_info| {
let mut col: Column = column_info.to_owned().into();
let reference = table
.reference_constraints
.iter()
.find(|reference| reference.columns.contains(&col.name));
if let Some(reference) = reference {
col.reference = Some(reference.to_owned().into());
}
return col;
})
.collect(),
}
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Column {
name: String,
#[serde(rename = "type")]
col_type: sea_schema_def::ColumnType,
default: Option<String>,
not_null: bool,
reference: Option<ColumnReference>,
}
impl From<sea_schema_def::ColumnInfo> for Column {
fn from(col: sea_schema_def::ColumnInfo) -> Self {
Self {
name: col.name,
col_type: col.col_type,
default: col.default.map(|expression| expression.0),
not_null: col.not_null.is_some(),
reference: None,
}
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct ColumnReference {
table: String,
identity: String,
label: Option<String>,
}
impl From<sea_schema_def::References> for ColumnReference {
fn from(reference: sea_schema_def::References) -> Self {
Self {
table: reference.table,
identity: reference.foreign_columns[0].to_owned(),
label: Some(reference.columns[0].to_title_case()),
}
}
}