drizzle_cli/commands/
export.rs1use std::path::PathBuf;
4
5use crate::commands::overrides;
6use crate::config::Config;
7use crate::config::Dialect;
8use crate::error::CliError;
9use crate::output;
10use drizzle_migrations::schema::Snapshot;
11
12#[derive(clap::Args, Debug, Clone)]
13pub struct ExportOptions {
14 #[arg(long = "sql")]
16 pub output_path: Option<PathBuf>,
17
18 #[arg(long)]
20 pub dialect: Option<Dialect>,
21
22 #[arg(long, value_delimiter = ',')]
24 pub schema: Option<Vec<String>>,
25}
26
27pub fn run(config: &Config, db_name: Option<&str>, opts: ExportOptions) -> Result<(), CliError> {
40 use drizzle_migrations::parser::SchemaParser;
41
42 let db = config.database(db_name)?;
43 let effective_dialect = overrides::resolve_dialect(db, opts.dialect);
44
45 crate::commands::harness::print_db_header(config, db_name);
46
47 println!("{}", output::heading("Exporting schema as SQL..."));
48 println!();
49
50 println!(
51 " {}: {}",
52 output::label("Dialect"),
53 effective_dialect.as_str()
54 );
55
56 let schema_files = overrides::resolve_schema_files(db, opts.schema.as_deref())?;
58 if schema_files.is_empty() {
59 return Err(CliError::NoSchemaFiles(overrides::resolve_schema_display(
60 db,
61 opts.schema.as_deref(),
62 )));
63 }
64
65 println!(
66 " {} {} schema file(s)",
67 output::label("Parsing"),
68 schema_files.len()
69 );
70
71 let mut combined_code = String::new();
72 for path in &schema_files {
73 let code = std::fs::read_to_string(path)
74 .map_err(|e| CliError::IoError(format!("Failed to read {}: {}", path.display(), e)))?;
75 combined_code.push_str(&code);
76 combined_code.push('\n');
77 }
78
79 let parse_result = SchemaParser::parse(&combined_code);
80 crate::snapshot::surface_parse_diagnostics(&parse_result)?;
81
82 println!(
83 " {} {} table(s), {} index(es), {} enum(s), {} view(s)",
84 output::label("Found"),
85 parse_result.tables.len(),
86 parse_result.indexes.len(),
87 parse_result.enums.len(),
88 parse_result.views.len(),
89 );
90
91 let dialect = effective_dialect.to_base();
93 let snapshot = Snapshot::from_parse_result(&parse_result, dialect, db.casing);
94 if snapshot.is_empty() {
95 println!("{}", output::warning("No schema entities found."));
96 return Ok(());
97 }
98
99 let plan = generate_create_plan(&snapshot)?;
102 let sql_statements = &plan.statements;
103
104 if sql_statements.is_empty() {
105 println!("{}", output::warning("No SQL statements generated."));
106 return Ok(());
107 }
108
109 let sql_content = if db.breakpoints {
110 plan.to_sql()
111 } else {
112 sql_statements.join("\n\n")
113 };
114
115 if let Some(path) = opts.output_path {
117 std::fs::write(&path, &sql_content)
118 .map_err(|e| CliError::IoError(format!("Failed to write {}: {}", path.display(), e)))?;
119 println!();
120 println!(
121 "{}",
122 output::success(&format!(
123 "Exported {} SQL statement(s) to {}",
124 sql_statements.len(),
125 path.display()
126 ))
127 );
128 } else {
129 println!();
130 println!("{}", output::muted("-- Generated SQL --"));
131 println!();
132 println!("{sql_content}");
133 println!();
134 println!("{}", output::muted("-- End of SQL --"));
135 }
136
137 Ok(())
138}
139
140fn generate_create_plan(
142 snapshot: &drizzle_migrations::schema::Snapshot,
143) -> Result<drizzle_migrations::Plan, CliError> {
144 let empty = Snapshot::empty(snapshot.dialect());
145 drizzle_migrations::diff(&empty, snapshot)
146 .map_err(|error| CliError::MigrationError(error.to_string()))
147}