Add migrate! macro for embedded migrations

This commit is contained in:
Raphaël Thériault 2020-07-23 14:22:50 -04:00
parent 60c3ece671
commit e5e9665bd9
7 changed files with 127 additions and 5 deletions

View File

@ -39,7 +39,7 @@ rustdoc-args = ["--cfg", "docsrs"]
[features]
default = [ "macros", "runtime-async-std", "migrate" ]
macros = [ "sqlx-macros" ]
migrate = [ "sqlx-core/migrate" ]
migrate = [ "sqlx-macros/migrate", "sqlx-core/migrate" ]
# [deprecated] TLS is not possible to disable due to it being conditional on multiple features
# Hopefully Cargo can handle this in the future

View File

@ -50,7 +50,7 @@ pub async fn info(uri: &str) -> anyhow::Result<()> {
} else {
style("pending").yellow()
},
migration.description(),
migration.description,
);
}
@ -77,7 +77,7 @@ pub async fn run(uri: &str) -> anyhow::Result<()> {
"{}/{} {} {}",
style(migration.version()).cyan(),
style("migrate").green(),
migration.description(),
migration.description,
style(format!("({:?})", elapsed)).dim()
);
} else {

View File

@ -71,7 +71,7 @@ impl Migrator {
}
for migration in self.iter() {
if migration.version() > version {
if migration.version > version {
conn.apply(migration).await?;
} else {
conn.validate(migration).await?;

View File

@ -16,7 +16,8 @@ authors = [
proc-macro = true
[features]
default = [ "runtime-async-std" ]
default = [ "runtime-async-std", "migrate" ]
migrate = [ "sha2" ]
# runtimes
runtime-async-std = [ "sqlx-core/runtime-async-std", "sqlx-rt/runtime-async-std" ]

View File

@ -16,6 +16,9 @@ mod database;
mod derives;
mod query;
#[cfg(feature = "migrate")]
mod migrate;
fn macro_result(tokens: proc_macro2::TokenStream) -> TokenStream {
quote!(
macro_rules! macro_result {
@ -79,6 +82,29 @@ pub fn derive_from_row(input: TokenStream) -> TokenStream {
}
}
#[cfg(feature = "migrate")]
#[proc_macro]
pub fn migrate(input: TokenStream) -> TokenStream {
use syn::LitStr;
let input = syn::parse_macro_input!(input as Option<LitStr>);
let dir = input
.as_ref()
.map_or("migrations".to_owned(), LitStr::value);
match migrate::expand_migrator_from_dir(dir) {
Ok(ts) => ts.into(),
Err(e) => {
if let Some(parse_err) = e.downcast_ref::<syn::Error>() {
macro_result(parse_err.to_compile_error())
} else {
let msg = e.to_string();
macro_result(quote!(compile_error!(#msg)))
}
}
}
}
#[doc(hidden)]
#[proc_macro_attribute]
pub fn test(_attr: TokenStream, input: TokenStream) -> TokenStream {

View File

@ -0,0 +1,91 @@
use proc_macro2::TokenStream;
use quote::{quote, ToTokens, TokenStreamExt};
use sha2::{Digest, Sha384};
use std::fs;
use std::fs::DirEntry;
use std::path::Path;
struct QuotedMigration {
version: i64,
description: String,
sql: String,
checksum: Vec<u8>,
}
impl ToTokens for QuotedMigration {
fn to_tokens(&self, tokens: &mut TokenStream) {
let QuotedMigration {
version,
description,
sql,
checksum,
} = &self;
let ts = quote! {
sqlx::migrate::Migration {
version: #version,
description: std::borrow::Cow::Borrowed(#description),
sql: std::borrow::Cow::Borrowed(#sql),
checksum: std::borow::Cow::Borrowed(&[
#(#checksum),*
]),
}
};
tokens.append_all(ts.into_iter());
}
}
// mostly copied from sqlx-core/src/migrate/source.rs
pub(crate) fn expand_migrator_from_dir<P: AsRef<Path>>(
dir: P,
) -> crate::Result<proc_macro2::TokenStream> {
let mut s = fs::read_dir(dir.as_ref().canonicalize()?)?;
let mut migrations = Vec::new();
while let Some(entry) = s.next() {
let entry = entry?;
if !entry.metadata()?.is_file() {
// not a file; ignore
continue;
}
let file_name = entry.file_name();
let file_name = file_name.to_string_lossy();
let parts = file_name.splitn(2, '_').collect::<Vec<_>>();
if parts.len() != 2 || !parts[1].ends_with(".sql") {
// not of the format: <VERSION>_<DESCRIPTION>.sql; ignore
continue;
}
let version: i64 = parts[0].parse()?;
// remove the `.sql` and replace `_` with ` `
let description = parts[1]
.trim_end_matches(".sql")
.replace('_', " ")
.to_owned();
let sql = fs::read_to_string(&entry.path())?;
let checksum = Vec::from(Sha384::digest(sql.as_bytes()).as_slice());
migrations.push(QuotedMigration {
version,
description,
sql,
checksum,
})
}
// ensure that we are sorted by `VERSION ASC`
migrations.sort_by_key(|m| m.version);
Ok(quote! {
sqlx::migrate::Migrator::from_static(&[
#(#migrations),*
])
})
}

View File

@ -61,6 +61,10 @@ pub extern crate sqlx_macros;
#[doc(hidden)]
pub use sqlx_macros::{FromRow, Type};
// embedded migrations
#[cfg(all(feature = "migrate", features = "macros"))]
pub use sqlx_macros::migrate;
#[cfg(feature = "macros")]
mod macros;