Compare commits

..

2 commits

3 changed files with 77 additions and 31 deletions

View file

@ -30,6 +30,9 @@ 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>,
}, },
} }
@ -48,7 +51,9 @@ 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 } => sql::discover_scheme(url, schema.to_owned()).await, Commands::Discover { schema, output } => {
sql::discover_scheme(url, schema.to_owned(), output.to_owned()).await
}
}; };
if let Err(err) = result { if let Err(err) = result {

View file

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

View file

@ -2,40 +2,62 @@ 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 toml_edit::visit_mut::{visit_table_like_kv_mut, VisitMut}; use std::io::Write;
use toml_edit::{DocumentMut, Item, KeyMut, Table as TomlTable, Value}; use toml_edit::visit_mut::VisitMut;
use sea_schema::postgres::def::{ColumnInfo, ColumnType, References, Schema, TableDef}; use sea_schema::postgres::def as sea_schema_def;
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(&mut self, mut key: KeyMut<'_>, node: &mut Item) { 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 ["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.
if let Item::Table(table) = node { let table = std::mem::replace(table, toml_edit::Table::new());
// 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 = Item::Value(Value::InlineTable(inline_table)); *node = toml_edit::Item::Value(toml_edit::Value::InlineTable(inline_table));
} }
} }
visit_table_like_kv_mut(self, key, node); toml_edit::visit_mut::visit_table_like_kv_mut(self, key, node);
} }
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct SchemaDefinition { pub struct DataModel {
/// Name of the schema, and table associated #[serde(flatten)]
pub tables: HashMap<String, Table>, pub schemas: HashMap<String, Schema>,
} }
impl fmt::Display for SchemaDefinition { impl DataModel {
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: DocumentMut = toml.parse().map_err(|_| fmt::Error)?; let mut document: toml_edit::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);
@ -44,8 +66,14 @@ impl fmt::Display for SchemaDefinition {
} }
} }
impl From<Schema> for SchemaDefinition { #[derive(Serialize, Deserialize, Debug)]
fn from(schema: Schema) -> Self { 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(); let mut tables: HashMap<String, Table> = HashMap::new();
for table in schema.tables { for table in schema.tables {
@ -58,17 +86,14 @@ impl From<Schema> for SchemaDefinition {
#[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<TableDef> for Table { impl From<sea_schema_def::TableDef> for Table {
fn from(table: TableDef) -> Self { fn from(table: sea_schema_def::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
@ -96,14 +121,14 @@ impl From<TableDef> for Table {
pub struct Column { pub struct Column {
name: String, name: String,
#[serde(rename = "type")] #[serde(rename = "type")]
col_type: ColumnType, col_type: sea_schema_def::ColumnType,
default: Option<String>, default: Option<String>,
not_null: bool, not_null: bool,
reference: Option<ColumnReference>, reference: Option<ColumnReference>,
} }
impl From<ColumnInfo> for Column { impl From<sea_schema_def::ColumnInfo> for Column {
fn from(col: ColumnInfo) -> Self { fn from(col: sea_schema_def::ColumnInfo) -> Self {
Self { Self {
name: col.name, name: col.name,
col_type: col.col_type, col_type: col.col_type,
@ -121,8 +146,8 @@ pub struct ColumnReference {
label: Option<String>, label: Option<String>,
} }
impl From<References> for ColumnReference { impl From<sea_schema_def::References> for ColumnReference {
fn from(reference: References) -> Self { fn from(reference: sea_schema_def::References) -> Self {
Self { Self {
table: reference.table, table: reference.table,
identity: reference.foreign_columns[0].to_owned(), identity: reference.foreign_columns[0].to_owned(),