From fc0a9b42067005f4253e0e25e48d5c65e71645b3 Mon Sep 17 00:00:00 2001 From: aleidk Date: Thu, 23 Jan 2025 11:39:55 -0300 Subject: [PATCH] feat: add discover command --- .justfile | 3 +- Cargo.lock | 212 ++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 10 +++ src/bin/cli/main.rs | 8 +- src/error.rs | 3 + src/sql.rs | 37 +++++++- 6 files changed, 261 insertions(+), 12 deletions(-) diff --git a/.justfile b/.justfile index 232e670..691a92a 100644 --- a/.justfile +++ b/.justfile @@ -1,4 +1,5 @@ set dotenv-load := true dev: - cargo run --bin cli -- query "SELECT * FROM sources;" + # cargo run --bin cli -- query "SELECT * FROM sources;" + cargo run --bin cli -- discover diff --git a/Cargo.lock b/Cargo.lock index 3871454..84a5b98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -223,7 +223,7 @@ version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn", @@ -321,6 +321,40 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "der" version = "0.7.9" @@ -425,6 +459,12 @@ dependencies = [ "spin", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.1.4" @@ -602,6 +642,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -782,6 +828,12 @@ dependencies = [ "syn", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.0.3" @@ -813,6 +865,17 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inherent" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0122b7114117e64a63ac49f752a5ca4624d534c7b1c7de796ac196381cd2d947" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -1269,6 +1332,66 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sea-query" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "085e94f7d7271c0393ac2d164a39994b1dff1b06bc40cd9a0da04f3d672b0fee" +dependencies = [ + "inherent", + "sea-query-derive", +] + +[[package]] +name = "sea-query-binder" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0019f47430f7995af63deda77e238c17323359af241233ec768aba1faea7608" +dependencies = [ + "sea-query", + "sqlx", +] + +[[package]] +name = "sea-query-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9834af2c4bd8c5162f00c89f1701fb6886119a88062cf76fe842ea9e232b9839" +dependencies = [ + "darling", + "heck 0.4.1", + "proc-macro2", + "quote", + "syn", + "thiserror 1.0.69", +] + +[[package]] +name = "sea-schema" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef5dd7848c993f3789d09a2616484c72c9330cae2b048df59d8c9b8c0343e95" +dependencies = [ + "futures", + "sea-query", + "sea-query-binder", + "sea-schema-derive", + "serde", + "sqlx", +] + +[[package]] +name = "sea-schema-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "debdc8729c37fdbf88472f97fd470393089f997a909e535ff67c544d18cfccf0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -1324,6 +1447,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1390,9 +1522,12 @@ dependencies = [ "chrono", "clap", "futures", + "sea-schema", + "serde", "sqlx", - "thiserror", + "thiserror 2.0.11", "tokio", + "toml", "url", ] @@ -1484,7 +1619,7 @@ dependencies = [ "serde_json", "sha2", "smallvec", - "thiserror", + "thiserror 2.0.11", "tokio", "tokio-stream", "tracing", @@ -1512,7 +1647,7 @@ checksum = "4e9f90acc5ab146a99bf5061a7eb4976b573f560bc898ef3bf8435448dd5e7ad" dependencies = [ "dotenvy", "either", - "heck", + "heck 0.5.0", "hex", "once_cell", "proc-macro2", @@ -1568,7 +1703,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.11", "tracing", "whoami", ] @@ -1606,7 +1741,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.11", "tracing", "whoami", ] @@ -1700,13 +1835,33 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.11", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1785,6 +1940,40 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tracing" version = "0.1.41" @@ -2128,6 +2317,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.6.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" +dependencies = [ + "memchr", +] + [[package]] name = "write16" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 58f7a68..be2007a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,15 @@ edition = "2021" chrono = "0.4.39" clap = { version = "4.5.26", features = ["derive", "env"] } futures = "0.3.31" +sea-schema = { version = "0.16.1", features = [ + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "discovery", + "probe", + "with-serde", +] } +serde = "1.0.217" sqlx = { version = "0.8", features = [ "runtime-tokio", "tls-native-tls", @@ -16,4 +25,5 @@ sqlx = { version = "0.8", features = [ ] } thiserror = "2.0.11" tokio = { version = "1.43.0", features = ["full"] } +toml = "0.8.19" url = "2.5.4" diff --git a/src/bin/cli/main.rs b/src/bin/cli/main.rs index 106a077..e5c4456 100644 --- a/src/bin/cli/main.rs +++ b/src/bin/cli/main.rs @@ -23,9 +23,14 @@ struct Cli { enum Commands { /// Executes a query against the database, it pass it directly to the underlying driver Query { - /// Query to execute + /// Query to execute. sql: String, }, + + Discover { + /// Schema to discover, it defaults to `public` in postures. + schema: Option, + }, } #[tokio::main] @@ -43,6 +48,7 @@ async fn main() -> Result<()> { // matches just as you would the top level cmd let result = match &cli.command { Commands::Query { sql } => sql::handle_query(url, sql).await, + Commands::Discover { schema } => sql::discover_scheme(url, schema.to_owned()).await, }; if let Err(err) = result { diff --git a/src/error.rs b/src/error.rs index 7dccfd7..c468be5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -14,6 +14,9 @@ pub enum Error { #[error("Error in runtime execution: {0}")] Runtime(&'static str), + #[error(transparent)] + TomlEncoding(#[from] toml::ser::Error), + #[error(transparent)] ParseError(#[from] url::ParseError), diff --git a/src/sql.rs b/src/sql.rs index 3d5fc90..8ea1f30 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -1,4 +1,6 @@ use clap::ValueEnum; +use sea_schema::postgres::discovery::SchemaDiscovery; +use sqlx::PgPool; use url::Url; use crate::error::{Error, Result}; @@ -10,12 +12,12 @@ pub enum Database { Postgres, } -pub async fn handle_query(url: String, query: &String) -> Result<()> { - let parse = Url::parse(&url)?; +async fn get_connector(url: &String) -> Result<(postgres::PgConnector, String)> { + let parse = Url::parse(url)?; let scheme = parse.scheme(); let connector = match scheme { - "postgresql" => postgres::PgConnector::new(url).await?, + "postgresql" => postgres::PgConnector::new(url.to_owned()).await?, &_ => { return Err(Error::Generic(format!( "Database `{}` is not supported", @@ -23,6 +25,11 @@ pub async fn handle_query(url: String, query: &String) -> Result<()> { ))) } }; + Ok((connector, scheme.to_owned())) +} + +pub async fn handle_query(url: String, query: &String) -> Result<()> { + let (connector, _) = get_connector(&url).await?; let rows = connector.query(query).await?; @@ -32,3 +39,27 @@ pub async fn handle_query(url: String, query: &String) -> Result<()> { return Ok(()); } + +pub async fn discover_scheme(url: String, schema: Option) -> Result<()> { + let (_, db_name) = get_connector(&url).await?; + + let schema_discovery = match db_name.as_str() { + "postgresql" => { + let schema = schema.unwrap_or("public".to_owned()); + let pool = PgPool::connect(url.as_str()).await?; + SchemaDiscovery::new(pool, schema.as_str()) + } + &_ => { + return Err(Error::Generic(format!( + "Database `{}` is not supported", + db_name + ))) + } + }; + + let schema = schema_discovery.discover().await?; + + println!("{:#?}", schema); + + Ok(()) +}