drizzle_cli/commands/
introspect.rs1use crate::commands::overrides::{self, ConnectionOverrides, FilterArgs};
6use crate::config::{Config, Dialect, IntrospectCasing};
7use crate::error::CliError;
8use crate::output;
9
10#[derive(clap::Args, Debug, Clone)]
11pub struct IntrospectOptions {
12 #[arg(long = "init")]
14 pub init_metadata: bool,
15
16 #[arg(long)]
18 pub casing: Option<IntrospectCasing>,
19
20 #[arg(long)]
22 pub out: Option<std::path::PathBuf>,
23
24 #[arg(long)]
26 pub breakpoints: Option<bool>,
27
28 #[arg(long)]
30 pub dialect: Option<Dialect>,
31
32 #[command(flatten)]
33 pub filters: FilterArgs,
34
35 #[command(flatten)]
36 pub connection: ConnectionOverrides,
37}
38
39pub fn run(
47 config: &Config,
48 db_name: Option<&str>,
49 opts: &IntrospectOptions,
50) -> Result<(), CliError> {
51 let db = config.database(db_name)?;
52
53 let effective_casing = opts
55 .casing
56 .unwrap_or_else(|| db.effective_introspect_casing());
57 let effective_dialect = overrides::resolve_dialect(db, opts.dialect);
58 let effective_out = opts.out.as_deref().unwrap_or_else(|| db.migrations_dir());
59 let effective_breakpoints = opts.breakpoints.unwrap_or(db.breakpoints);
60
61 if effective_dialect != Dialect::Postgresql {
62 if opts
63 .filters
64 .schema_filters
65 .as_ref()
66 .is_some_and(|v| !v.is_empty())
67 {
68 println!(
69 "{}",
70 output::warning("Ignoring --schemaFilters: only supported for postgresql")
71 );
72 }
73 if opts
74 .filters
75 .extensions_filters
76 .as_ref()
77 .is_some_and(|v| !v.is_empty())
78 {
79 println!(
80 "{}",
81 output::warning("Ignoring --extensionsFilters: only supported for postgresql")
82 );
83 }
84 }
85
86 let filters = crate::db::SnapshotFilters {
87 tables: overrides::resolve_filter_list(
88 opts.filters.tables_filter.as_deref(),
89 db.tables_filter.as_ref(),
90 ),
91 schemas: overrides::resolve_schema_filters(
92 effective_dialect,
93 opts.filters.schema_filters.as_deref(),
94 db.schema_filter.as_ref(),
95 ),
96 extensions: overrides::resolve_extensions_filter(
97 opts.filters.extensions_filters.as_deref(),
98 db.extensions_filters.as_deref(),
99 ),
100 };
101
102 println!("{}", output::heading("Introspecting database..."));
103 println!();
104
105 crate::commands::harness::print_db_header(config, db_name);
106
107 println!(
108 " {}: {}",
109 output::label("Dialect"),
110 effective_dialect.as_str()
111 );
112 if let Some(ref driver) = db.driver {
113 println!(" {}: {:?}", output::label("Driver"), driver);
114 }
115 println!(" {}: {}", output::label("Output"), effective_out.display());
116
117 if opts.init_metadata {
118 println!(" {}: enabled", output::label("Init metadata"));
119 }
120 println!();
121
122 let credentials = overrides::resolve_credentials(db, effective_dialect, &opts.connection)?;
124
125 let Some(credentials) = credentials else {
126 print_missing_credentials_help(effective_dialect);
127 return Ok(());
128 };
129
130 let result = crate::db::run_introspection(
132 &credentials,
133 effective_dialect,
134 effective_out,
135 opts.init_metadata,
136 effective_breakpoints,
137 Some(effective_casing),
138 &filters,
139 db.migrations_table(),
140 db.migrations_schema(),
141 )?;
142
143 print_introspection_summary(&result, opts.init_metadata);
144 Ok(())
145}
146
147fn print_missing_credentials_help(effective_dialect: Dialect) {
149 println!("{}", output::warning("No database credentials configured."));
150 println!();
151 println!("Add credentials to your drizzle.config.toml:");
152 println!();
153 println!(" {}", output::muted("[dbCredentials]"));
154 match effective_dialect.to_base() {
155 drizzle_types::Dialect::SQLite => {
156 println!(" {}", output::muted("url = \"./dev.db\""));
157 }
158 drizzle_types::Dialect::PostgreSQL => {
159 println!(
160 " {}",
161 output::muted("url = \"postgres://user:pass@localhost:5432/db\"")
162 );
163 }
164 drizzle_types::Dialect::MySQL => {
165 println!(
168 " {}",
169 output::muted("url = \"mysql://user:pass@localhost:3306/db\"")
170 );
171 }
172 }
173 println!();
174 println!("Or use an environment variable:");
175 println!();
176 println!(" {}", output::muted("[dbCredentials]"));
177 println!(" {}", output::muted("url = { env = \"DATABASE_URL\" }"));
178}
179
180fn print_introspection_summary(result: &crate::db::IntrospectResult, init_metadata: bool) {
182 println!();
183 println!(
184 " {} {} table(s), {} index(es)",
185 output::success("Found"),
186 result.table_count,
187 result.index_count
188 );
189
190 if result.view_count > 0 {
191 println!(
192 " {} {} view(s)",
193 output::success("Found"),
194 result.view_count
195 );
196 }
197
198 println!();
199 println!(
200 "{} Snapshot saved to {}",
201 output::success("Done!"),
202 result.snapshot_path.display()
203 );
204
205 if init_metadata {
206 println!();
207 println!(
208 " {} Migration metadata initialized in database.",
209 output::label("Note:")
210 );
211 println!(" The current database state is now the baseline for future migrations.");
212 }
213}