Skip to content

Commit 453701c

Browse files
Added create-migration command line instruction (#943)
* Added create-migration command line instruction This update adds a command line interface to create migrations directly with sqlpage, and have it manage the timestamps and uniqueness of migration names. * Update sqlpage/migrations/README.md Co-authored-by: Ophir LOJKINE <[email protected]> * Update sqlpage/migrations/README.md Co-authored-by: Ophir LOJKINE <[email protected]> * Updated with changes for config directory, migration output Now respects the configuration_directory environment variable. It also outputs the path of the new migration created when it is created, and takes care to display that path relative to the current working directory. Lastly, the execution of create-migration command was moved above the rest of the initialization, so that creating a new migration does not run existing migrations. This allows you to create multiple migrations before you run sqlpage normally again to execute them. --------- Co-authored-by: Ophir LOJKINE <[email protected]>
1 parent 7ccf735 commit 453701c

File tree

3 files changed

+91
-1
lines changed

3 files changed

+91
-1
lines changed

sqlpage/migrations/README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,13 @@ that is greater than the previous one.
2121
Use commands like `ALTER TABLE` to update the schema declaratively instead of modifying the existing `CREATE TABLE`
2222
statements.
2323

24-
If you try to edit an existing migration, SQLPage will not run it again, will detect
24+
If you try to edit an existing migration, SQLPage will not run it again, it will detect that the migration has already executed. Also, if the migration is different than the one that was executed, SQLPage will throw an error as the database structure must match.
25+
26+
## Creating migrations on the command line
27+
28+
You can create a migration directly with sqlpage by running the command `sqlpage create-migration [migration_name]`
29+
30+
For example if you run `sqlpage create-migration "Example Migration 1"` on the command line, you will find a new file under the `sqlpage/migrations` folder called `[timestamp]_example_migration_1.sql` where timestamp is the current time when you ran the command.
2531

2632
## Running migrations
2733

src/app_config.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,20 @@ pub struct Cli {
2222
/// The path to the configuration file.
2323
#[clap(short = 'c', long)]
2424
pub config_file: Option<PathBuf>,
25+
26+
/// Subcommands for additional functionality.
27+
#[clap(subcommand)]
28+
pub command: Option<Commands>,
29+
}
30+
31+
/// Enum for subcommands.
32+
#[derive(Parser)]
33+
pub enum Commands {
34+
/// Create a new migration file.
35+
CreateMigration {
36+
/// Name of the migration.
37+
migration_name: String,
38+
},
2539
}
2640

2741
#[cfg(not(feature = "lambda-web"))]
@@ -686,6 +700,7 @@ mod test {
686700
web_root: Some(PathBuf::from(".")),
687701
config_dir: None,
688702
config_file: None,
703+
command: None,
689704
};
690705

691706
let config = AppConfig::from_cli(&cli).unwrap();
@@ -726,6 +741,7 @@ mod test {
726741
web_root: None,
727742
config_dir: None,
728743
config_file: Some(config_file_path.clone()),
744+
command: None,
729745
};
730746

731747
let config = AppConfig::from_cli(&cli).unwrap();
@@ -744,6 +760,7 @@ mod test {
744760
web_root: Some(cli_web_dir.clone()),
745761
config_dir: None,
746762
config_file: Some(config_file_path),
763+
command: None,
747764
};
748765

749766
let config = AppConfig::from_cli(&cli_with_web_root).unwrap();
@@ -773,6 +790,7 @@ mod test {
773790
web_root: None,
774791
config_dir: None,
775792
config_file: None,
793+
command: None,
776794
};
777795

778796
let config = AppConfig::from_cli(&cli).unwrap();

src/main.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use clap::Parser;
12
use sqlpage::{
23
app_config,
34
webserver::{self, Database},
@@ -15,9 +16,25 @@ async fn main() {
1516

1617
async fn start() -> anyhow::Result<()> {
1718
let app_config = app_config::load_from_cli()?;
19+
let cli = app_config::Cli::parse();
20+
21+
if let Some(command) = cli.command {
22+
match command {
23+
app_config::Commands::CreateMigration { migration_name } => {
24+
// Pass configuration_directory from app_config
25+
create_migration_file(
26+
&migration_name,
27+
app_config.configuration_directory.to_str().unwrap(),
28+
)?;
29+
return Ok(());
30+
}
31+
}
32+
}
33+
1834
let db = Database::init(&app_config).await?;
1935
webserver::database::migrations::apply(&app_config, &db).await?;
2036
let state = AppState::init_with_db(&app_config, db).await?;
37+
2138
log::debug!("Starting server...");
2239
webserver::http::run_server(&app_config, state).await?;
2340
log::info!("Server stopped gracefully. Goodbye!");
@@ -41,3 +58,52 @@ fn init_logging() {
4158
Err(e) => log::error!("Error loading .env file: {e}"),
4259
}
4360
}
61+
62+
fn create_migration_file(
63+
migration_name: &str,
64+
configuration_directory: &str,
65+
) -> anyhow::Result<()> {
66+
use chrono::Utc;
67+
use std::fs;
68+
use std::path::Path;
69+
70+
let timestamp = Utc::now().format("%Y%m%d%H%M%S").to_string();
71+
let snake_case_name = migration_name
72+
.replace(|c: char| !c.is_alphanumeric(), "_")
73+
.to_lowercase();
74+
let file_name = format!("{}_{}.sql", timestamp, snake_case_name);
75+
let migrations_dir = Path::new(configuration_directory).join("migrations");
76+
77+
if !migrations_dir.exists() {
78+
fs::create_dir_all(&migrations_dir)?;
79+
}
80+
81+
let mut unique_file_name = file_name.clone();
82+
let mut counter = 1;
83+
84+
while migrations_dir.join(&unique_file_name).exists() {
85+
unique_file_name = format!("{}_{}_{}.sql", timestamp, snake_case_name, counter);
86+
counter += 1;
87+
}
88+
89+
let file_path = migrations_dir.join(unique_file_name);
90+
fs::write(&file_path, "-- Write your migration here\n")?;
91+
92+
// the following code cleans up the display path to show where the migration was created
93+
// relative to the current working directory, and then outputs the path to the migration
94+
let file_path_canon = file_path.canonicalize().unwrap_or(file_path.clone());
95+
let cwd_canon = std::env::current_dir()?
96+
.canonicalize()
97+
.unwrap_or(std::env::current_dir()?);
98+
let rel_path = match file_path_canon.strip_prefix(&cwd_canon) {
99+
Ok(p) => p,
100+
Err(_) => file_path_canon.as_path(),
101+
};
102+
let mut display_path_str = rel_path.display().to_string();
103+
if display_path_str.starts_with("\\\\?\\") {
104+
display_path_str = display_path_str.trim_start_matches("\\\\?\\").to_string();
105+
}
106+
display_path_str = display_path_str.replace('\\', "/");
107+
println!("Migration file created: {}", display_path_str);
108+
Ok(())
109+
}

0 commit comments

Comments
 (0)