import migrations and don't require diesel_cli for admins (#555)
* import migrations via macro * panic on database not to the latest migration * add subcommand to plm * create migration that run tantivy index creation * remove diesel_cli from places it was * use our migration system for tests * create table __diesel_schema_migrations if needed
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "plume-macro"
|
||||
version = "0.1.0"
|
||||
authors = ["Trinity Pointard <trinity.pointard@insa-rennes.fr>"]
|
||||
edition = "2018"
|
||||
description = "Plume procedural macros"
|
||||
license = "AGPLv3"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "0.4"
|
||||
quote = "0.6.12"
|
||||
syn = "0.11.4"
|
||||
|
||||
|
||||
[features]
|
||||
default = []
|
||||
postgres = []
|
||||
sqlite = []
|
||||
@@ -0,0 +1,139 @@
|
||||
#![recursion_limit = "128"]
|
||||
extern crate proc_macro;
|
||||
#[macro_use]
|
||||
extern crate quote;
|
||||
extern crate syn;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use std::fs::{read_dir, File};
|
||||
use std::io::Read;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[proc_macro]
|
||||
pub fn import_migrations(input: TokenStream) -> TokenStream {
|
||||
assert!(input.is_empty());
|
||||
let migration_dir = if cfg!(feature = "postgres") {
|
||||
"migrations/postgres"
|
||||
} else if cfg!(feature = "sqlite") {
|
||||
"migrations/sqlite"
|
||||
} else {
|
||||
"migrations"
|
||||
};
|
||||
let mut files = read_dir(migration_dir)
|
||||
.unwrap()
|
||||
.map(|dir| dir.unwrap())
|
||||
.filter(|dir| dir.file_type().unwrap().is_dir())
|
||||
.map(|dir| dir.path())
|
||||
.collect::<Vec<_>>();
|
||||
files.sort_unstable();
|
||||
let migrations = files
|
||||
.into_iter()
|
||||
.map(|path| {
|
||||
let mut up = path.clone();
|
||||
let mut down = path.clone();
|
||||
up.push("up.sql");
|
||||
down.push("down.sql");
|
||||
let mut up_sql = String::new();
|
||||
let mut down_sql = String::new();
|
||||
File::open(up).unwrap().read_to_string(&mut up_sql).unwrap();
|
||||
File::open(down)
|
||||
.unwrap()
|
||||
.read_to_string(&mut down_sql)
|
||||
.unwrap();
|
||||
let name = path
|
||||
.file_name()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.chars()
|
||||
.filter(char::is_ascii_digit)
|
||||
.take(14)
|
||||
.collect::<String>();
|
||||
(name, up_sql, down_sql)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let migrations_name = migrations.iter().map(|m| &m.0).collect::<Vec<_>>();
|
||||
let migrations_up = migrations
|
||||
.iter()
|
||||
.map(|m| m.1.as_str())
|
||||
.map(file_to_migration)
|
||||
.collect::<Vec<_>>();
|
||||
let migrations_down = migrations
|
||||
.iter()
|
||||
.map(|m| m.2.as_str())
|
||||
.map(file_to_migration)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
/*
|
||||
enum Action {
|
||||
Sql(&'static str),
|
||||
Function(&'static Fn(&Connection, &Path) -> Result<()>)
|
||||
}*/
|
||||
|
||||
quote!(
|
||||
ImportedMigrations(
|
||||
&[#(ComplexMigration{name: #migrations_name, up: #migrations_up, down: #migrations_down}),*]
|
||||
)
|
||||
).into()
|
||||
}
|
||||
|
||||
fn file_to_migration(file: &str) -> TokenStream2 {
|
||||
let mut sql = true;
|
||||
let mut acc = String::new();
|
||||
let mut actions = vec![];
|
||||
for line in file.lines() {
|
||||
if sql {
|
||||
if line.starts_with("--#!") {
|
||||
if !acc.trim().is_empty() {
|
||||
actions.push(quote!(Action::Sql(#acc)));
|
||||
}
|
||||
sql = false;
|
||||
acc = line[4..].to_string();
|
||||
acc.push('\n');
|
||||
} else if line.starts_with("--") {
|
||||
continue;
|
||||
} else {
|
||||
acc.push_str(line);
|
||||
acc.push('\n');
|
||||
}
|
||||
} else {
|
||||
if line.starts_with("--#!") {
|
||||
acc.push_str(&line[4..]);
|
||||
acc.push('\n');
|
||||
} else if line.starts_with("--") {
|
||||
continue;
|
||||
} else {
|
||||
let func: TokenStream2 = trampoline(TokenStream::from_str(&acc).unwrap().into());
|
||||
actions.push(quote!(Action::Function(&#func)));
|
||||
sql = true;
|
||||
acc = line.to_string();
|
||||
acc.push('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
if !acc.trim().is_empty() {
|
||||
if sql {
|
||||
actions.push(quote!(Action::Sql(#acc)));
|
||||
} else {
|
||||
let func: TokenStream2 = trampoline(TokenStream::from_str(&acc).unwrap().into());
|
||||
actions.push(quote!(Action::Function(&#func)));
|
||||
}
|
||||
}
|
||||
|
||||
quote!(
|
||||
&[#(#actions),*]
|
||||
)
|
||||
}
|
||||
|
||||
/// Build a trampoline to allow reference to closure from const context
|
||||
fn trampoline(closure: TokenStream2) -> TokenStream2 {
|
||||
quote! {
|
||||
{
|
||||
fn trampoline<'a, 'b>(conn: &'a Connection, path: &'b Path) -> Result<()> {
|
||||
(#closure)(conn, path)
|
||||
}
|
||||
trampoline
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user