generated from alecodes/base-template
feat: serialize database schema
This commit is contained in:
parent
fc0a9b4206
commit
de506a73df
5 changed files with 460 additions and 8 deletions
12
src/sql.rs
12
src/sql.rs
|
|
@ -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
157
src/sql/schema.rs
Normal 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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue