mirror of
https://github.com/launchbadge/sqlx.git
synced 2025-11-05 16:02:48 +00:00
Added migration tool to sqlx
This commit is contained in:
parent
0c7ab87924
commit
e577358686
15
Cargo.lock
generated
15
Cargo.lock
generated
@ -287,6 +287,18 @@ version = "0.5.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1"
|
checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cargo-sqlx"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"dotenv",
|
||||||
|
"futures 0.3.4",
|
||||||
|
"sqlx 0.2.6",
|
||||||
|
"structopt",
|
||||||
|
"tokio 0.2.13",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.50"
|
version = "1.0.50"
|
||||||
@ -1721,6 +1733,7 @@ dependencies = [
|
|||||||
"rand",
|
"rand",
|
||||||
"sha-1",
|
"sha-1",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
"tokio 0.2.13",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1800,10 +1813,12 @@ dependencies = [
|
|||||||
"async-std",
|
"async-std",
|
||||||
"dotenv",
|
"dotenv",
|
||||||
"futures 0.3.4",
|
"futures 0.3.4",
|
||||||
|
"lazy_static",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"sqlx-core 0.2.6",
|
"sqlx-core 0.2.6",
|
||||||
"syn",
|
"syn",
|
||||||
|
"tokio 0.2.13",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ members = [
|
|||||||
"examples/listen-postgres",
|
"examples/listen-postgres",
|
||||||
"examples/realworld-postgres",
|
"examples/realworld-postgres",
|
||||||
"examples/todos-postgres",
|
"examples/todos-postgres",
|
||||||
|
"cargo-sqlx",
|
||||||
]
|
]
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
|
|||||||
4
cargo-sqlx/.gitignore
vendored
Normal file
4
cargo-sqlx/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/target
|
||||||
|
/migrations
|
||||||
|
Cargo.lock
|
||||||
|
.env
|
||||||
27
cargo-sqlx/Cargo.toml
Normal file
27
cargo-sqlx/Cargo.toml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
[package]
|
||||||
|
name = "cargo-sqlx"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Simple postgres migrator without support for down migration"
|
||||||
|
authors = ["Jesper Axelsson <jesperaxe@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
readme = "README.md"
|
||||||
|
homepage = "https://github.com/JesperAxelsson/blue-grouse-migrator"
|
||||||
|
repository = "https://github.com/JesperAxelsson/blue-grouse-migrator"
|
||||||
|
keywords = ["database", "postgres", "database-management", "migration"]
|
||||||
|
categories = ["database", "command-line-utilities"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "sqlx"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
dotenv = "0.15"
|
||||||
|
|
||||||
|
tokio = { version = "0.2", features = ["macros"] }
|
||||||
|
# sqlx = { path = "..", default-features = false, features = [ "runtime-tokio", "macros", "postgres" ] }
|
||||||
|
sqlx = { version = "0.2", default-features = false, features = [ "runtime-tokio", "macros", "postgres" ] }
|
||||||
|
futures="0.3"
|
||||||
|
|
||||||
|
structopt = "0.3"
|
||||||
|
chrono = "0.4"
|
||||||
21
cargo-sqlx/LICENSE
Normal file
21
cargo-sqlx/LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 Jesper Axelsson
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
14
cargo-sqlx/README.md
Normal file
14
cargo-sqlx/README.md
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# cargo-sqlx
|
||||||
|
|
||||||
|
Sqlx migrator runs all `*.sql` files under `migrations` folder and remembers which ones has been run.
|
||||||
|
|
||||||
|
Database url is supplied through either env variable or `.env` file containing `DATABASE_URL="postgres://postgres:postgres@localhost/realworld"`.
|
||||||
|
|
||||||
|
##### Commands
|
||||||
|
- `add <name>` - add new migration to your migrations folder named `<timestamp>_<name>.sql`
|
||||||
|
- `run` - Runs all migrations in your migrations folder
|
||||||
|
|
||||||
|
|
||||||
|
##### Limitations
|
||||||
|
- No down migrations! If you need down migrations, there are other more feature complete migrators to use.
|
||||||
|
- Only support postgres. Could be convinced to add other databases if there is need and easy to use database connection libs.
|
||||||
206
cargo-sqlx/src/main.rs
Normal file
206
cargo-sqlx/src/main.rs
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::prelude::*;
|
||||||
|
|
||||||
|
use dotenv::dotenv;
|
||||||
|
|
||||||
|
use sqlx::PgConnection;
|
||||||
|
use sqlx::PgPool;
|
||||||
|
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
const MIGRATION_FOLDER: &'static str = "migrations";
|
||||||
|
|
||||||
|
/// Sqlx commandline tool
|
||||||
|
#[derive(StructOpt, Debug)]
|
||||||
|
#[structopt(name = "Sqlx")]
|
||||||
|
enum Opt {
|
||||||
|
// #[structopt(subcommand)]
|
||||||
|
Migrate(MigrationCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simple postgres migrator
|
||||||
|
#[derive(StructOpt, Debug)]
|
||||||
|
#[structopt(name = "Sqlx migrator")]
|
||||||
|
enum MigrationCommand {
|
||||||
|
/// Initalizes new migration directory with db create script
|
||||||
|
// Init {
|
||||||
|
// // #[structopt(long)]
|
||||||
|
// database_name: String,
|
||||||
|
// },
|
||||||
|
|
||||||
|
/// Add new migration with name <timestamp>_<migration_name>.sql
|
||||||
|
Add {
|
||||||
|
// #[structopt(long)]
|
||||||
|
name: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Run all migrations
|
||||||
|
Run,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let opt = Opt::from_args();
|
||||||
|
|
||||||
|
match opt {
|
||||||
|
Opt::Migrate(command) => match command {
|
||||||
|
// Opt::Init { database_name } => init_migrations(&database_name),
|
||||||
|
MigrationCommand::Add { name } => add_migration_file(&name),
|
||||||
|
MigrationCommand::Run => run_migrations().await,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("All done!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// fn init_migrations(db_name: &str) {
|
||||||
|
// println!("Initing the migrations so hard! db: {:#?}", db_name);
|
||||||
|
// }
|
||||||
|
|
||||||
|
fn add_migration_file(name: &str) {
|
||||||
|
use chrono::prelude::*;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
if !Path::new(MIGRATION_FOLDER).exists() {
|
||||||
|
fs::create_dir(MIGRATION_FOLDER).expect("Failed to create 'migrations' dir")
|
||||||
|
}
|
||||||
|
|
||||||
|
let dt = Utc::now();
|
||||||
|
let mut file_name = dt.format("%Y-%m-%d_%H-%M-%S").to_string();
|
||||||
|
file_name.push_str("_");
|
||||||
|
file_name.push_str(name);
|
||||||
|
file_name.push_str(".sql");
|
||||||
|
|
||||||
|
let mut path = PathBuf::new();
|
||||||
|
path.push(MIGRATION_FOLDER);
|
||||||
|
path.push(&file_name);
|
||||||
|
|
||||||
|
if path.exists() {
|
||||||
|
eprintln!("Migration already exists!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut file = File::create(path).expect("Failed to create file");
|
||||||
|
file.write_all(b"-- Add migration script here")
|
||||||
|
.expect("Could not write to file");
|
||||||
|
|
||||||
|
println!("Created migration: '{}'", file_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Migration {
|
||||||
|
pub name: String,
|
||||||
|
pub sql: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_migrations() -> Vec<Migration> {
|
||||||
|
let entries = fs::read_dir(&MIGRATION_FOLDER).expect("Could not find 'migrations' dir");
|
||||||
|
|
||||||
|
let mut migrations = Vec::new();
|
||||||
|
|
||||||
|
for e in entries {
|
||||||
|
if let Ok(e) = e {
|
||||||
|
if let Ok(meta) = e.metadata() {
|
||||||
|
if !meta.is_file() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ext) = e.path().extension() {
|
||||||
|
if ext != "sql" {
|
||||||
|
println!("Wrong ext: {:?}", ext);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut file =
|
||||||
|
File::open(e.path()).expect(&format!("Failed to open: '{:?}'", e.file_name()));
|
||||||
|
let mut contents = String::new();
|
||||||
|
file.read_to_string(&mut contents)
|
||||||
|
.expect(&format!("Failed to read: '{:?}'", e.file_name()));
|
||||||
|
|
||||||
|
migrations.push(Migration {
|
||||||
|
name: e.file_name().to_str().unwrap().to_string(),
|
||||||
|
sql: contents,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
migrations.sort_by(|a, b| a.name.partial_cmp(&b.name).unwrap());
|
||||||
|
|
||||||
|
migrations
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_migrations() {
|
||||||
|
dotenv().ok();
|
||||||
|
let db_url = env::var("DATABASE_URL").expect("Failed to find 'DATABASE_URL'");
|
||||||
|
|
||||||
|
let mut pool = PgPool::new(&db_url)
|
||||||
|
.await
|
||||||
|
.expect("Failed to connect to pool");
|
||||||
|
|
||||||
|
create_migration_table(&mut pool).await;
|
||||||
|
|
||||||
|
let migrations = load_migrations();
|
||||||
|
|
||||||
|
for mig in migrations.iter() {
|
||||||
|
let mut tx = pool.begin().await.unwrap();
|
||||||
|
|
||||||
|
if check_if_applied(&mut tx, &mig.name).await {
|
||||||
|
println!("Already applied migration: '{}'", mig.name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
println!("Applying migration: '{}'", mig.name);
|
||||||
|
|
||||||
|
sqlx::query(&mig.sql)
|
||||||
|
.execute(&mut tx)
|
||||||
|
.await
|
||||||
|
.expect(&format!("Failed to run migration {:?}", &mig.name));
|
||||||
|
|
||||||
|
save_applied_migration(&mut tx, &mig.name).await;
|
||||||
|
|
||||||
|
tx.commit().await.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_migration_table(mut pool: &PgPool) {
|
||||||
|
sqlx::query(
|
||||||
|
r#"
|
||||||
|
CREATE TABLE IF NOT EXISTS __migrations (
|
||||||
|
migration VARCHAR (255) PRIMARY KEY,
|
||||||
|
created TIMESTAMP NOT NULL DEFAULT current_timestamp
|
||||||
|
);
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.execute(&mut pool)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create migration table");
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn check_if_applied(pool: &mut PgConnection, migration: &str) -> bool {
|
||||||
|
use sqlx::row::Row;
|
||||||
|
|
||||||
|
let row = sqlx::query(
|
||||||
|
"select exists(select migration from __migrations where migration = $1) as exists",
|
||||||
|
)
|
||||||
|
.bind(migration.to_string())
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await
|
||||||
|
.expect("Failed to check migration table");
|
||||||
|
|
||||||
|
let exists: bool = row.get("exists");
|
||||||
|
|
||||||
|
exists
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn save_applied_migration(pool: &mut PgConnection, migration: &str) {
|
||||||
|
sqlx::query("insert into __migrations (migration) values ($1)")
|
||||||
|
.bind(migration.to_string())
|
||||||
|
.execute(pool)
|
||||||
|
.await
|
||||||
|
.expect("Failed to insert migration ");
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user