Compare commits

..

1 commit

Author SHA1 Message Date
0980c9b4d2 feat: serialize database schema 2025-01-24 15:52:44 -03:00
3 changed files with 31 additions and 77 deletions

View file

@ -30,9 +30,6 @@ enum Commands {
Discover { Discover {
/// Schema to discover, it defaults to `public` in postures. /// Schema to discover, it defaults to `public` in postures.
schema: Option<String>, schema: Option<String>,
#[arg(short = 'o', long)]
output: Option<String>,
}, },
} }
@ -51,9 +48,7 @@ async fn main() -> Result<()> {
// matches just as you would the top level cmd // matches just as you would the top level cmd
let result = match &cli.command { let result = match &cli.command {
Commands::Query { sql } => sql::handle_query(url, sql).await, Commands::Query { sql } => sql::handle_query(url, sql).await,
Commands::Discover { schema, output } => { Commands::Discover { schema } => sql::discover_scheme(url, schema.to_owned()).await,
sql::discover_scheme(url, schema.to_owned(), output.to_owned()).await
}
}; };
if let Err(err) = result { if let Err(err) = result {

View file

@ -1,7 +1,3 @@
use std::fs::File;
use std::io;
use std::path::Path;
use clap::ValueEnum; use clap::ValueEnum;
use sea_schema::postgres::discovery::SchemaDiscovery; use sea_schema::postgres::discovery::SchemaDiscovery;
use sqlx::PgPool; use sqlx::PgPool;
@ -9,7 +5,7 @@ use url::Url;
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use self::schema::{DataModel, Schema}; use self::schema::SchemaDefinition;
mod postgres; mod postgres;
mod schema; mod schema;
@ -47,11 +43,7 @@ pub async fn handle_query(url: String, query: &String) -> Result<()> {
return Ok(()); return Ok(());
} }
pub async fn discover_scheme( pub async fn discover_scheme(url: String, schema: Option<String>) -> Result<()> {
url: String,
schema: Option<String>,
output: Option<String>,
) -> Result<()> {
let (_, db_name) = get_connector(&url).await?; let (_, db_name) = get_connector(&url).await?;
let schema_discovery = match db_name.as_str() { let schema_discovery = match db_name.as_str() {
@ -70,17 +62,9 @@ pub async fn discover_scheme(
let schema = schema_discovery.discover().await?; let schema = schema_discovery.discover().await?;
let mut data_model = DataModel::new(); let schema_definition = SchemaDefinition::from(schema);
data_model
.schemas
.insert(schema.schema.to_owned(), Schema::from(schema));
let mut buffer: Box<dyn io::Write> = match output { println!("{}", schema_definition);
Some(path) => Box::new(File::create(Path::new(&path))?),
None => Box::new(io::stdout()),
};
data_model.write(&mut buffer)?;
Ok(()) Ok(())
} }

View file

@ -2,62 +2,40 @@ use heck::ToTitleCase;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt; use std::fmt;
use std::io::Write; use toml_edit::visit_mut::{visit_table_like_kv_mut, VisitMut};
use toml_edit::visit_mut::VisitMut; use toml_edit::{DocumentMut, Item, KeyMut, Table as TomlTable, Value};
use sea_schema::postgres::def as sea_schema_def; use sea_schema::postgres::def::{ColumnInfo, ColumnType, References, Schema, TableDef};
use crate::error;
struct InlineTableFix; struct InlineTableFix;
// the toml serializer doesn't generate inline tables by default
impl VisitMut for InlineTableFix { impl VisitMut for InlineTableFix {
fn visit_table_like_kv_mut( fn visit_table_like_kv_mut(&mut self, mut key: KeyMut<'_>, node: &mut Item) {
&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 ["reference", "type"].contains(&key.get()) {
if let toml_edit::Item::Table(table) = node {
// Turn the table into an inline table. // Turn the table into an inline table.
let table = std::mem::replace(table, toml_edit::Table::new()); if let Item::Table(table) = node {
// Turn the table into an inline table.
let table = std::mem::replace(table, TomlTable::new());
let inline_table = table.into_inline_table(); let inline_table = table.into_inline_table();
key.fmt(); key.fmt();
*node = toml_edit::Item::Value(toml_edit::Value::InlineTable(inline_table)); *node = Item::Value(Value::InlineTable(inline_table));
} }
} }
toml_edit::visit_mut::visit_table_like_kv_mut(self, key, node); visit_table_like_kv_mut(self, key, node);
} }
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct DataModel { pub struct SchemaDefinition {
#[serde(flatten)] /// Name of the schema, and table associated
pub schemas: HashMap<String, Schema>, pub tables: HashMap<String, Table>,
} }
impl DataModel { impl fmt::Display for SchemaDefinition {
pub fn new() -> Self {
Self {
schemas: HashMap::new(),
}
}
}
impl DataModel {
pub fn write<T: Write>(&self, buffer: &mut T) -> error::Result<()> {
buffer.write_all(self.to_string().as_bytes())?;
Ok(())
}
}
impl fmt::Display for DataModel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let toml = toml::to_string_pretty(self).map_err(|_| fmt::Error)?; 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 document: DocumentMut = toml.parse().map_err(|_| fmt::Error)?;
let mut visitor = InlineTableFix; let mut visitor = InlineTableFix;
visitor.visit_document_mut(&mut document); visitor.visit_document_mut(&mut document);
@ -66,14 +44,8 @@ impl fmt::Display for DataModel {
} }
} }
#[derive(Serialize, Deserialize, Debug)] impl From<Schema> for SchemaDefinition {
pub struct Schema { fn from(schema: Schema) -> Self {
#[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(); let mut tables: HashMap<String, Table> = HashMap::new();
for table in schema.tables { for table in schema.tables {
@ -86,14 +58,17 @@ impl From<sea_schema_def::Schema> for Schema {
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct Table { pub struct Table {
/// real name of the table
name: String,
/// Default is capitalized name /// Default is capitalized name
display_name: Option<String>, display_name: Option<String>,
columns: Vec<Column>, columns: Vec<Column>,
} }
impl From<sea_schema_def::TableDef> for Table { impl From<TableDef> for Table {
fn from(table: sea_schema_def::TableDef) -> Self { fn from(table: TableDef) -> Self {
Self { Self {
name: table.info.name.to_owned(),
display_name: Some(table.info.name.to_owned().to_title_case()), display_name: Some(table.info.name.to_owned().to_title_case()),
columns: table columns: table
.columns .columns
@ -121,14 +96,14 @@ impl From<sea_schema_def::TableDef> for Table {
pub struct Column { pub struct Column {
name: String, name: String,
#[serde(rename = "type")] #[serde(rename = "type")]
col_type: sea_schema_def::ColumnType, col_type: ColumnType,
default: Option<String>, default: Option<String>,
not_null: bool, not_null: bool,
reference: Option<ColumnReference>, reference: Option<ColumnReference>,
} }
impl From<sea_schema_def::ColumnInfo> for Column { impl From<ColumnInfo> for Column {
fn from(col: sea_schema_def::ColumnInfo) -> Self { fn from(col: ColumnInfo) -> Self {
Self { Self {
name: col.name, name: col.name,
col_type: col.col_type, col_type: col.col_type,
@ -146,8 +121,8 @@ pub struct ColumnReference {
label: Option<String>, label: Option<String>,
} }
impl From<sea_schema_def::References> for ColumnReference { impl From<References> for ColumnReference {
fn from(reference: sea_schema_def::References) -> Self { fn from(reference: References) -> Self {
Self { Self {
table: reference.table, table: reference.table,
identity: reference.foreign_columns[0].to_owned(), identity: reference.foreign_columns[0].to_owned(),