drizzle_cli/commands/
push.rs1use crate::commands::overrides::{self, ConnectionOverrides, FilterArgs};
8use crate::config::{Casing, Config, Dialect};
9use crate::error::CliError;
10use crate::output;
11use drizzle_migrations::schema::Snapshot;
12
13#[derive(clap::Args, Debug, Clone)]
14pub struct PushOptions {
15 #[arg(long)]
17 pub verbose: bool,
18
19 #[arg(long)]
21 pub force: bool,
22
23 #[arg(long)]
25 pub explain: bool,
26
27 #[arg(long)]
29 pub casing: Option<Casing>,
30
31 #[arg(long)]
33 pub dialect: Option<Dialect>,
34
35 #[arg(long, value_delimiter = ',')]
37 pub schema: Option<Vec<String>>,
38
39 #[command(flatten)]
40 pub filters: FilterArgs,
41
42 #[command(flatten)]
43 pub connection: ConnectionOverrides,
44}
45
46pub fn run(config: &Config, db_name: Option<&str>, opts: &PushOptions) -> Result<(), CliError> {
55 let db = config.database(db_name)?;
56
57 let verbose = opts.verbose || db.verbose;
59 let explain = opts.explain;
60 let effective_casing = opts.casing.or(db.casing);
61 let effective_dialect = overrides::resolve_dialect(db, opts.dialect);
62
63 warn_unsupported_pg_filters(effective_dialect, opts);
64
65 crate::commands::harness::print_db_header(config, db_name);
66
67 println!("{}", output::heading("Pushing schema to database..."));
68 println!();
69
70 println!(
71 " {}: {}",
72 output::label("Dialect"),
73 effective_dialect.as_str()
74 );
75
76 let credentials = overrides::resolve_credentials(db, effective_dialect, &opts.connection)?;
78 let Some(credentials) = credentials else {
79 print_missing_credentials_help(effective_dialect);
80 return Ok(());
81 };
82
83 let parse_result = parse_schema_files(db, opts.schema.as_deref())?;
85
86 if parse_result.tables.is_empty() && parse_result.indexes.is_empty() {
87 println!(
88 "{}",
89 output::warning("No tables or indexes found in schema files.")
90 );
91 return Ok(());
92 }
93
94 println!(
95 " {} {} table(s), {} index(es)",
96 output::label("Found"),
97 parse_result.tables.len(),
98 parse_result.indexes.len()
99 );
100
101 let dialect = effective_dialect.to_base();
103 let mut desired_snapshot =
104 Snapshot::from_parse_result(&parse_result, dialect, effective_casing);
105
106 let filters = crate::db::SnapshotFilters {
107 tables: overrides::resolve_filter_list(
108 opts.filters.tables_filter.as_deref(),
109 db.tables_filter.as_ref(),
110 ),
111 schemas: overrides::resolve_schema_filters(
112 effective_dialect,
113 opts.filters.schema_filters.as_deref(),
114 db.schema_filter.as_ref(),
115 ),
116 extensions: overrides::resolve_extensions_filter(
117 opts.filters.extensions_filters.as_deref(),
118 db.extensions_filters.as_deref(),
119 ),
120 };
121 crate::db::apply_snapshot_filters(&mut desired_snapshot, effective_dialect, &filters)?;
122
123 let plan = crate::db::plan_push(
125 &credentials,
126 effective_dialect,
127 &desired_snapshot,
128 db.breakpoints,
129 &filters,
130 )?;
131
132 if !plan.warnings.is_empty() {
133 println!("{}", output::warning("Warnings:"));
134 for w in &plan.warnings {
135 println!(" {} {}", output::warning("-"), w);
136 }
137 println!();
138 }
139
140 if explain || verbose {
142 if plan.sql_statements.is_empty() {
143 println!("{}", output::success("No schema changes detected."));
144 return Ok(());
145 }
146
147 println!("{}", output::muted("--- Planned SQL ---"));
148 println!();
149 for stmt in &plan.sql_statements {
150 println!("{stmt}\n");
151 }
152 println!("{}", output::muted("--- End SQL ---"));
153 println!();
154 }
155
156 if explain {
158 return Ok(());
159 }
160
161 if plan.sql_statements.is_empty() {
162 println!("{}", output::success("No schema changes detected."));
163 return Ok(());
164 }
165
166 crate::db::apply_push(&credentials, effective_dialect, &plan, opts.force)?;
168
169 println!("{}", output::success("Push complete!"));
170
171 Ok(())
172}
173
174fn warn_unsupported_pg_filters(effective_dialect: Dialect, opts: &PushOptions) {
177 if effective_dialect == Dialect::Postgresql {
178 return;
179 }
180 if opts
181 .filters
182 .schema_filters
183 .as_ref()
184 .is_some_and(|v| !v.is_empty())
185 {
186 println!(
187 "{}",
188 output::warning("Ignoring --schemaFilters: only supported for postgresql")
189 );
190 }
191 if opts
192 .filters
193 .extensions_filters
194 .as_ref()
195 .is_some_and(|v| !v.is_empty())
196 {
197 println!(
198 "{}",
199 output::warning("Ignoring --extensionsFilters: only supported for postgresql")
200 );
201 }
202}
203
204fn print_missing_credentials_help(effective_dialect: Dialect) {
206 println!("{}", output::warning("No database credentials configured."));
207 println!();
208 println!("Add credentials to your drizzle.config.toml:");
209 println!();
210 println!(" {}", output::muted("[dbCredentials]"));
211 match effective_dialect.to_base() {
212 drizzle_types::Dialect::SQLite => {
213 println!(" {}", output::muted("url = \"./dev.db\""));
214 }
215 drizzle_types::Dialect::PostgreSQL => {
216 println!(
217 " {}",
218 output::muted("url = \"postgres://user:pass@localhost:5432/db\"")
219 );
220 }
221 drizzle_types::Dialect::MySQL => {
222 println!(
225 " {}",
226 output::muted("url = \"mysql://user:pass@localhost:3306/db\"")
227 );
228 }
229 }
230 println!();
231 println!("Or use an environment variable:");
232 println!();
233 println!(" {}", output::muted("[dbCredentials]"));
234 println!(" {}", output::muted("url = { env = \"DATABASE_URL\" }"));
235}
236
237fn parse_schema_files(
239 db: &crate::config::DatabaseConfig,
240 schema_override: Option<&[String]>,
241) -> Result<drizzle_migrations::parser::ParseResult, CliError> {
242 use drizzle_migrations::parser::SchemaParser;
243
244 let schema_files = overrides::resolve_schema_files(db, schema_override)?;
245 if schema_files.is_empty() {
246 return Err(CliError::NoSchemaFiles(overrides::resolve_schema_display(
247 db,
248 schema_override,
249 )));
250 }
251
252 println!(
253 " {} {} schema file(s)",
254 output::label("Parsing"),
255 schema_files.len()
256 );
257
258 let mut combined_code = String::new();
259 for path in &schema_files {
260 let code = std::fs::read_to_string(path)
261 .map_err(|e| CliError::IoError(format!("Failed to read {}: {}", path.display(), e)))?;
262 combined_code.push_str(&code);
263 combined_code.push('\n');
264 }
265
266 let parse_result = SchemaParser::parse(&combined_code);
267 crate::snapshot::surface_parse_diagnostics(&parse_result)?;
268 Ok(parse_result)
269}