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 crate::snapshot::parse_result_to_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 = parse_result_to_snapshot(&parse_result, dialect, effective_casing);
104
105 let filters = crate::db::SnapshotFilters {
106 tables: overrides::resolve_filter_list(
107 opts.filters.tables_filter.as_deref(),
108 db.tables_filter.as_ref(),
109 ),
110 schemas: overrides::resolve_schema_filters(
111 effective_dialect,
112 opts.filters.schema_filters.as_deref(),
113 db.schema_filter.as_ref(),
114 ),
115 extensions: overrides::resolve_extensions_filter(
116 opts.filters.extensions_filters.as_deref(),
117 db.extensions_filters.as_deref(),
118 ),
119 };
120 crate::db::apply_snapshot_filters(&mut desired_snapshot, effective_dialect, &filters)?;
121
122 let plan = crate::db::plan_push(
124 &credentials,
125 effective_dialect,
126 &desired_snapshot,
127 db.breakpoints,
128 &filters,
129 )?;
130
131 if !plan.warnings.is_empty() {
132 println!("{}", output::warning("Warnings:"));
133 for w in &plan.warnings {
134 println!(" {} {}", output::warning("-"), w);
135 }
136 println!();
137 }
138
139 if explain || verbose {
141 if plan.sql_statements.is_empty() {
142 println!("{}", output::success("No schema changes detected."));
143 return Ok(());
144 }
145
146 println!("{}", output::muted("--- Planned SQL ---"));
147 println!();
148 for stmt in &plan.sql_statements {
149 println!("{stmt}\n");
150 }
151 println!("{}", output::muted("--- End SQL ---"));
152 println!();
153 }
154
155 if explain {
157 return Ok(());
158 }
159
160 if plan.sql_statements.is_empty() {
161 println!("{}", output::success("No schema changes detected."));
162 return Ok(());
163 }
164
165 crate::db::apply_push(&credentials, effective_dialect, &plan, opts.force)?;
167
168 println!("{}", output::success("Push complete!"));
169
170 Ok(())
171}
172
173fn warn_unsupported_pg_filters(effective_dialect: Dialect, opts: &PushOptions) {
176 if effective_dialect == Dialect::Postgresql {
177 return;
178 }
179 if opts
180 .filters
181 .schema_filters
182 .as_ref()
183 .is_some_and(|v| !v.is_empty())
184 {
185 println!(
186 "{}",
187 output::warning("Ignoring --schemaFilters: only supported for postgresql")
188 );
189 }
190 if opts
191 .filters
192 .extensions_filters
193 .as_ref()
194 .is_some_and(|v| !v.is_empty())
195 {
196 println!(
197 "{}",
198 output::warning("Ignoring --extensionsFilters: only supported for postgresql")
199 );
200 }
201}
202
203fn print_missing_credentials_help(effective_dialect: Dialect) {
205 println!("{}", output::warning("No database credentials configured."));
206 println!();
207 println!("Add credentials to your drizzle.config.toml:");
208 println!();
209 println!(" {}", output::muted("[dbCredentials]"));
210 match effective_dialect.to_base() {
211 drizzle_types::Dialect::SQLite => {
212 println!(" {}", output::muted("url = \"./dev.db\""));
213 }
214 drizzle_types::Dialect::PostgreSQL => {
215 println!(
216 " {}",
217 output::muted("url = \"postgres://user:pass@localhost:5432/db\"")
218 );
219 }
220 drizzle_types::Dialect::MySQL => {
221 println!(
224 " {}",
225 output::muted("url = \"mysql://user:pass@localhost:3306/db\"")
226 );
227 }
228 }
229 println!();
230 println!("Or use an environment variable:");
231 println!();
232 println!(" {}", output::muted("[dbCredentials]"));
233 println!(" {}", output::muted("url = { env = \"DATABASE_URL\" }"));
234}
235
236fn parse_schema_files(
238 db: &crate::config::DatabaseConfig,
239 schema_override: Option<&[String]>,
240) -> Result<drizzle_migrations::parser::ParseResult, CliError> {
241 use drizzle_migrations::parser::SchemaParser;
242
243 let schema_files = overrides::resolve_schema_files(db, schema_override)?;
244 if schema_files.is_empty() {
245 return Err(CliError::NoSchemaFiles(overrides::resolve_schema_display(
246 db,
247 schema_override,
248 )));
249 }
250
251 println!(
252 " {} {} schema file(s)",
253 output::label("Parsing"),
254 schema_files.len()
255 );
256
257 let mut combined_code = String::new();
258 for path in &schema_files {
259 let code = std::fs::read_to_string(path)
260 .map_err(|e| CliError::IoError(format!("Failed to read {}: {}", path.display(), e)))?;
261 combined_code.push_str(&code);
262 combined_code.push('\n');
263 }
264
265 Ok(SchemaParser::parse(&combined_code))
266}