generated from alecodes/base-template
Compare commits
2 commits
0980c9b4d2
...
b93f037017
| Author | SHA1 | Date | |
|---|---|---|---|
| b93f037017 | |||
| de506a73df |
3 changed files with 77 additions and 31 deletions
|
|
@ -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 {
|
||||||
|
|
|
||||||
24
src/sql.rs
24
src/sql.rs
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue