164 lines
10 KiB
Rust
164 lines
10 KiB
Rust
use super::*;
|
|
use dever_runtime::database::{Migration, Plan, Sql, Statement};
|
|
|
|
const BASE: &str = "CREATE TABLE \"account\" (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE CHECK(_dever_length(name) <= 24), legacy TEXT, amount TEXT(5,2) NOT NULL);";
|
|
const NEXT: &str = "CREATE TABLE \"account\" (id INTEGER PRIMARY KEY AUTOINCREMENT, label TEXT NOT NULL UNIQUE CHECK(_dever_length(label) <= 8), amount TEXT(7,3) NOT NULL, enabled INTEGER NOT NULL DEFAULT 1 CHECK(enabled IN (0,1)));";
|
|
const BEFORE: Statement = Statement {
|
|
sql: Sql::same("UPDATE account SET name = ?1 WHERE name = ?2"),
|
|
parameters: || {
|
|
Ok(vec![
|
|
Parameter::Text("Editor".into()),
|
|
Parameter::Text("Legacy".into()),
|
|
])
|
|
},
|
|
};
|
|
const AFTER: Statement = Statement {
|
|
sql: Sql::same("UPDATE account SET label = ?1 WHERE label = ?2"),
|
|
parameters: || {
|
|
Ok(vec![
|
|
Parameter::Text("Final".into()),
|
|
Parameter::Text("Editor".into()),
|
|
])
|
|
},
|
|
};
|
|
const SEED: Statement = Statement {
|
|
sql: Sql::same(
|
|
"INSERT INTO account(label,amount) SELECT ?1,?2 WHERE NOT EXISTS(SELECT 1 FROM account WHERE label = ?1)",
|
|
),
|
|
parameters: || {
|
|
Ok(vec![
|
|
Parameter::Text("Seed".into()),
|
|
Parameter::Text("10001000".into()),
|
|
])
|
|
},
|
|
};
|
|
const STEP: Migration = Migration {
|
|
name: "normalize",
|
|
revision: "normalize-v1",
|
|
drops: &["legacy"],
|
|
before: &[BEFORE],
|
|
after: &[AFTER],
|
|
};
|
|
const INITIAL: Plan = Plan {
|
|
create: Sql::same(BASE),
|
|
indexes: Sql::same(""),
|
|
postgres_columns: &[],
|
|
renames: &[],
|
|
migrations: &[],
|
|
seeds: &[],
|
|
seed_revision: "empty",
|
|
};
|
|
const UPGRADE: Plan = Plan {
|
|
create: Sql::same(NEXT),
|
|
indexes: Sql::same(""),
|
|
postgres_columns: &[],
|
|
renames: &[("label", "name")],
|
|
migrations: &[STEP],
|
|
seeds: &[SEED],
|
|
seed_revision: "seed-v1",
|
|
};
|
|
|
|
#[test]
|
|
fn schema_upgrade_preserves_rows_decimal_foreign_keys_and_id_sequence() {
|
|
let directory = Directory::new();
|
|
runtime().run(async |scope| {
|
|
let database = Database::open(&directory.0,options(),scope).await.unwrap();
|
|
let mut executor = database.executor().await.unwrap();
|
|
executor.migrate(scope,"account",BASE,Some(&INITIAL)).await.unwrap();
|
|
executor.initialize(scope,"INSERT INTO account(id,name,legacy,amount) VALUES (1,'Legacy','old','101230'),(20,'Deleted','old','100000'); DELETE FROM account WHERE id=20; CREATE TABLE dependent(account_id INTEGER REFERENCES account(id) ON DELETE CASCADE); INSERT INTO dependent VALUES (1);").await.unwrap();
|
|
executor.migrate(scope,"account",NEXT,Some(&UPGRADE)).await.unwrap();
|
|
let rows = executor.query(scope,"SELECT id,label,amount,enabled FROM account ORDER BY id",vec![],20,|row|Ok((row.get::<_,i64>(0)?,row.get::<_,String>(1)?,dever_runtime::database::scalar::decimal(row,2,7,3)?.to_string(),row.get::<_,bool>(3)?))).await.unwrap();
|
|
assert_eq!(rows,[(1,"Final".into(),"12.3".into(),true),(21,"Seed".into(),"1".into(),true)]);
|
|
let child = executor.query(scope,"SELECT account_id FROM dependent",vec![],20,|row|row.get::<_,i64>(0)).await.unwrap();
|
|
assert_eq!(child,[1]);
|
|
assert!(executor.execute(scope,"INSERT INTO dependent VALUES (999)",vec![]).await.is_err());
|
|
executor.migrate(scope,"account",NEXT,Some(&UPGRADE)).await.unwrap();
|
|
assert_eq!(executor.query(scope,"SELECT COUNT(*) FROM account",vec![],1,|row|row.get::<_,i64>(0)).await.unwrap(),[2]);
|
|
let old = executor.migrate(scope,"account",BASE,Some(&INITIAL)).await.unwrap_err();
|
|
assert_eq!(old.kind,Kind::Migration);
|
|
const ALTERED: Plan = Plan { migrations:&[Migration { revision:"changed", ..STEP }], ..UPGRADE };
|
|
assert_eq!(executor.migrate(scope,"account",NEXT,Some(&ALTERED)).await.unwrap_err().kind,Kind::Migration);
|
|
}).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn failed_after_step_or_narrowing_rolls_back_data_ddl_seed_and_history() {
|
|
let directory = Directory::new();
|
|
runtime().run(async |scope| {
|
|
let database = Database::open(&directory.0,options(),scope).await.unwrap();
|
|
let mut executor = database.executor().await.unwrap();
|
|
executor.migrate(scope,"account",BASE,Some(&INITIAL)).await.unwrap();
|
|
executor.initialize(scope,"INSERT INTO account(name,legacy,amount) VALUES ('Legacy','old','101230');").await.unwrap();
|
|
const FAIL: Plan = Plan { migrations:&[Migration { after:&[Statement { sql:Sql::same("UPDATE absent_table SET value = ?1"),parameters:||Ok(vec![Parameter::Integer(1)]) }], ..STEP }],..UPGRADE };
|
|
assert!(executor.migrate(scope,"account",NEXT,Some(&FAIL)).await.is_err());
|
|
let rows = executor.query(scope,"SELECT name,legacy,amount FROM account",vec![],20,|row|Ok((row.get::<_,String>(0)?,row.get::<_,String>(1)?,row.get::<_,String>(2)?))).await.unwrap();
|
|
assert_eq!(rows,[("Legacy".into(),"old".into(),"101230".into())]);
|
|
assert_eq!(executor.query(scope,"SELECT COUNT(*) FROM _dever_migration",vec![],1,|row|row.get::<_,i64>(0)).await.unwrap(),[0]);
|
|
assert_eq!(executor.query(scope,"SELECT definition FROM _dever_history WHERE name='account'",vec![],1,|row|row.get::<_,String>(0)).await.unwrap(),[BASE]);
|
|
executor.migrate(scope,"account",NEXT,Some(&UPGRADE)).await.unwrap();
|
|
const TIGHT: &str = "CREATE TABLE \"account\" (id INTEGER PRIMARY KEY AUTOINCREMENT, label TEXT NOT NULL UNIQUE CHECK(_dever_length(label) <= 4), amount TEXT(7,3) NOT NULL, enabled INTEGER NOT NULL DEFAULT 1 CHECK(enabled IN (0,1)));";
|
|
const TIGHT_PLAN: Plan = Plan {create:Sql::same(TIGHT),..UPGRADE};
|
|
assert_eq!(executor.migrate(scope,"account",TIGHT,Some(&TIGHT_PLAN)).await.unwrap_err().kind,Kind::Constraint);
|
|
executor.migrate(scope,"account",NEXT,Some(&UPGRADE)).await.unwrap();
|
|
assert_eq!(executor.query(scope,"SELECT label FROM account ORDER BY id",vec![],20,|row|row.get::<_,String>(0)).await.unwrap(),["Final","Seed"]);
|
|
}).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn seed_conflict_rolls_back_prior_seed_rows_and_revision() {
|
|
let directory = Directory::new();
|
|
runtime().run(async |scope| {
|
|
let database = Database::open(&directory.0,options(),scope).await.unwrap();
|
|
let mut executor = database.executor().await.unwrap();
|
|
const SQL: &str = "CREATE TABLE \"account\" (id INTEGER PRIMARY KEY, name TEXT NOT NULL UNIQUE, email TEXT NOT NULL UNIQUE);";
|
|
const FIRST: Statement = Statement { sql:Sql::same("INSERT INTO account(name,email) SELECT ?1,?2 WHERE NOT EXISTS(SELECT 1 FROM account WHERE name=?1)"), parameters:||Ok(vec![Parameter::Text("first".into()),Parameter::Text("one".into())]) };
|
|
const START: Plan = Plan {create:Sql::same(SQL),indexes:Sql::same(""),postgres_columns:&[],renames:&[],migrations:&[],seeds:&[FIRST],seed_revision:"one"};
|
|
executor.migrate(scope,"account",SQL,Some(&START)).await.unwrap();
|
|
const MORE: Plan = Plan {seeds:&[FIRST,
|
|
Statement {sql:FIRST.sql,parameters:||Ok(vec![Parameter::Text("second".into()),Parameter::Text("two".into())])},
|
|
Statement {sql:FIRST.sql,parameters:||Ok(vec![Parameter::Text("third".into()),Parameter::Text("one".into())])}],seed_revision:"more",..START};
|
|
assert_eq!(executor.migrate(scope,"account",SQL,Some(&MORE)).await.unwrap_err().kind,Kind::Constraint);
|
|
assert_eq!(executor.query(scope,"SELECT name FROM account",vec![],20,|row|row.get::<_,String>(0)).await.unwrap(),["first"]);
|
|
assert_eq!(executor.query(scope,"SELECT revision FROM _dever_seed WHERE model='account'",vec![],1,|row|row.get::<_,String>(0)).await.unwrap(),["one"]);
|
|
}).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn empty_rebuild_keeps_the_last_allocated_id() {
|
|
let directory = Directory::new();
|
|
runtime().run(async |scope| {
|
|
let database = Database::open(&directory.0,options(),scope).await.unwrap();
|
|
let mut executor = database.executor().await.unwrap();
|
|
executor.migrate(scope,"account",BASE,Some(&INITIAL)).await.unwrap();
|
|
executor.initialize(scope,"INSERT INTO account(id,name,amount) VALUES (20,'gone','100000'); DELETE FROM account;").await.unwrap();
|
|
const EMPTY: Plan = Plan { seeds:&[],seed_revision:"empty",..UPGRADE };
|
|
executor.migrate(scope,"account",NEXT,Some(&EMPTY)).await.unwrap();
|
|
executor.execute(scope,"INSERT INTO account(label,amount) VALUES ('new','10000000')",vec![]).await.unwrap();
|
|
assert_eq!(executor.query(scope,"SELECT id FROM account",vec![],1,|row|row.get::<_,i64>(0)).await.unwrap(),[21]);
|
|
}).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn foreign_seed_failure_preserves_earlier_models_and_rolls_back_its_model() {
|
|
let directory = Directory::new();
|
|
runtime().run(async |scope| {
|
|
let database = Database::open(&directory.0,options(),scope).await.unwrap();
|
|
let mut executor = database.executor().await.unwrap();
|
|
const CHILD: &str = "CREATE TABLE \"child\" (id INTEGER PRIMARY KEY, parent_id INTEGER NOT NULL REFERENCES parent(id));";
|
|
const EMPTY: Plan = Plan {create:Sql::same(CHILD),indexes:Sql::same(""),postgres_columns:&[],renames:&[],migrations:&[],seeds:&[],seed_revision:"empty"};
|
|
// 没有记录时可以先建外键表;真正的 Seed 必须引用已存在的目标记录。
|
|
executor.migrate(scope,"child",CHILD,Some(&EMPTY)).await.unwrap();
|
|
const PARENT: &str = "CREATE TABLE \"parent\" (id INTEGER PRIMARY KEY, name TEXT NOT NULL);";
|
|
executor.schema(scope,"parent",PARENT).await.unwrap();
|
|
executor.execute(scope,"INSERT INTO parent VALUES (1,'preserved')",vec![]).await.unwrap();
|
|
const INVALID: Plan = Plan {seeds:&[Statement {sql:Sql::same("INSERT INTO child VALUES (1,999)"),parameters:||Ok(vec![])}],seed_revision:"invalid",..EMPTY};
|
|
assert!(executor.migrate(scope,"child",CHILD,Some(&INVALID)).await.is_err());
|
|
assert_eq!(executor.query(scope,"SELECT name FROM parent",vec![],1,|row|row.get::<_,String>(0)).await.unwrap(),["preserved"]);
|
|
assert_eq!(executor.query(scope,"SELECT COUNT(*) FROM child",vec![],1,|row|row.get::<_,i64>(0)).await.unwrap(),[0]);
|
|
assert_eq!(executor.query(scope,"SELECT revision FROM _dever_seed WHERE model='child'",vec![],1,|row|row.get::<_,String>(0)).await.unwrap(),["empty"]);
|
|
assert!(executor.execute(scope,"INSERT INTO child VALUES (1,999)",vec![]).await.is_err());
|
|
const VALID: Plan = Plan {seeds:&[Statement {sql:Sql::same("INSERT INTO child VALUES (1,1)"),parameters:||Ok(vec![])}],seed_revision:"valid",..EMPTY};
|
|
executor.migrate(scope,"child",CHILD,Some(&VALID)).await.unwrap();
|
|
}).unwrap();
|
|
}
|