feat: add discover command

This commit is contained in:
Alexander Navarro 2025-01-23 11:39:55 -03:00
parent 48a582eb7e
commit fc0a9b4206
6 changed files with 261 additions and 12 deletions

View file

@ -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

212
Cargo.lock generated
View file

@ -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"

View file

@ -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"

View file

@ -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<String>,
},
}
#[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 {

View file

@ -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),

View file

@ -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<String>) -> 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(())
}