use crate::commands::overrides::{self, ConnectionOverrides, FilterArgs};
use crate::config::{Config, Dialect, IntrospectCasing};
use crate::error::CliError;
use crate::output;
#[derive(clap::Args, Debug, Clone)]
pub struct IntrospectOptions {
#[arg(long = "init")]
pub init_metadata: bool,
#[arg(long)]
pub casing: Option<IntrospectCasing>,
#[arg(long)]
pub out: Option<std::path::PathBuf>,
#[arg(long)]
pub breakpoints: Option<bool>,
#[arg(long)]
pub dialect: Option<Dialect>,
#[command(flatten)]
pub filters: FilterArgs,
#[command(flatten)]
pub connection: ConnectionOverrides,
}
pub fn run(
config: &Config,
db_name: Option<&str>,
opts: &IntrospectOptions,
) -> Result<(), CliError> {
let db = config.database(db_name)?;
let effective_casing = opts
.casing
.unwrap_or_else(|| db.effective_introspect_casing());
let effective_dialect = overrides::resolve_dialect(db, opts.dialect);
let effective_out = opts.out.as_deref().unwrap_or_else(|| db.migrations_dir());
let effective_breakpoints = opts.breakpoints.unwrap_or(db.breakpoints);
if effective_dialect != Dialect::Postgresql {
if opts
.filters
.schema_filters
.as_ref()
.is_some_and(|v| !v.is_empty())
{
println!(
"{}",
output::warning("Ignoring --schemaFilters: only supported for postgresql")
);
}
if opts
.filters
.extensions_filters
.as_ref()
.is_some_and(|v| !v.is_empty())
{
println!(
"{}",
output::warning("Ignoring --extensionsFilters: only supported for postgresql")
);
}
}
let filters = crate::db::SnapshotFilters {
tables: overrides::resolve_filter_list(
opts.filters.tables_filter.as_deref(),
db.tables_filter.as_ref(),
),
schemas: overrides::resolve_schema_filters(
effective_dialect,
opts.filters.schema_filters.as_deref(),
db.schema_filter.as_ref(),
),
extensions: overrides::resolve_extensions_filter(
opts.filters.extensions_filters.as_deref(),
db.extensions_filters.as_deref(),
),
};
println!("{}", output::heading("Introspecting database..."));
println!();
crate::commands::harness::print_db_header(config, db_name);
println!(
" {}: {}",
output::label("Dialect"),
effective_dialect.as_str()
);
if let Some(ref driver) = db.driver {
println!(" {}: {:?}", output::label("Driver"), driver);
}
println!(" {}: {}", output::label("Output"), effective_out.display());
if opts.init_metadata {
println!(" {}: enabled", output::label("Init metadata"));
}
println!();
let credentials = overrides::resolve_credentials(db, effective_dialect, &opts.connection)?;
let Some(credentials) = credentials else {
print_missing_credentials_help(effective_dialect);
return Ok(());
};
let result = crate::db::run_introspection(
&credentials,
effective_dialect,
effective_out,
opts.init_metadata,
effective_breakpoints,
Some(effective_casing),
&filters,
db.migrations_table(),
db.migrations_schema(),
)?;
print_introspection_summary(&result, opts.init_metadata);
Ok(())
}
fn print_missing_credentials_help(effective_dialect: Dialect) {
println!("{}", output::warning("No database credentials configured."));
println!();
println!("Add credentials to your drizzle.config.toml:");
println!();
println!(" {}", output::muted("[dbCredentials]"));
match effective_dialect.to_base() {
drizzle_types::Dialect::SQLite => {
println!(" {}", output::muted("url = \"./dev.db\""));
}
drizzle_types::Dialect::PostgreSQL => {
println!(
" {}",
output::muted("url = \"postgres://user:pass@localhost:5432/db\"")
);
}
drizzle_types::Dialect::MySQL => {
println!(
" {}",
output::muted("url = \"mysql://user:pass@localhost:3306/db\"")
);
}
}
println!();
println!("Or use an environment variable:");
println!();
println!(" {}", output::muted("[dbCredentials]"));
println!(" {}", output::muted("url = { env = \"DATABASE_URL\" }"));
}
fn print_introspection_summary(result: &crate::db::IntrospectResult, init_metadata: bool) {
println!();
println!(
" {} {} table(s), {} index(es)",
output::success("Found"),
result.table_count,
result.index_count
);
if result.view_count > 0 {
println!(
" {} {} view(s)",
output::success("Found"),
result.view_count
);
}
println!();
println!(
"{} Snapshot saved to {}",
output::success("Done!"),
result.snapshot_path.display()
);
if init_metadata {
println!();
println!(
" {} Migration metadata initialized in database.",
output::label("Note:")
);
println!(" The current database state is now the baseline for future migrations.");
}
}