Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,228 @@
|
||||
use super::*;
|
||||
use crate::database::{Error, Options, Parameter};
|
||||
use futures_util::FutureExt;
|
||||
use std::panic::AssertUnwindSafe;
|
||||
use tokio_postgres::Client;
|
||||
|
||||
mod cases;
|
||||
mod codec;
|
||||
mod constraints;
|
||||
#[cfg(feature = "net")]
|
||||
mod registry;
|
||||
|
||||
const ID: Column = Column {
|
||||
constraints: &[("account_pkey", "p", "PRIMARY KEY (\"id\")")],
|
||||
..column("id", "bigint")
|
||||
};
|
||||
const NAME: Column = Column {
|
||||
constraints: &[("_dever_unique_account_1", "u", "UNIQUE (\"name\")")],
|
||||
..column("name", "text")
|
||||
};
|
||||
const TITLE: Column = Column {
|
||||
constraints: &[("_dever_unique_account_1", "u", "UNIQUE (\"title\")")],
|
||||
..column("title", "text")
|
||||
};
|
||||
const UID: Column = Column {
|
||||
generated: true,
|
||||
constraints: &[("_dever_unique_account_3", "u", "UNIQUE (\"uid\")")],
|
||||
..column("uid", "uuid")
|
||||
};
|
||||
|
||||
const INITIAL: &str = "CREATE TABLE \"account\" (id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name text NOT NULL CONSTRAINT _dever_unique_account_1 UNIQUE, legacy bigint NOT NULL DEFAULT 4, amount numeric(6,2) NOT NULL DEFAULT 1.25);";
|
||||
const FINAL: &str = "CREATE TABLE \"account\" (id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, title text NOT NULL CONSTRAINT _dever_unique_account_1 UNIQUE, amount numeric(8,3) NOT NULL DEFAULT 1.25, uid uuid NOT NULL CONSTRAINT _dever_unique_account_3 UNIQUE);";
|
||||
const INDEX: &str = "CREATE INDEX account_title ON account(title);";
|
||||
const HISTORICAL: Migration = Migration {
|
||||
name: "historical",
|
||||
revision: "historical-one",
|
||||
drops: &["ancient"],
|
||||
before: &[statement("UPDATE account SET ancient = 1")],
|
||||
after: &[statement("UPDATE account SET ancient = 2")],
|
||||
};
|
||||
const UPGRADE: Migration = Migration {
|
||||
name: "rename",
|
||||
revision: "rename-one",
|
||||
drops: &["legacy"],
|
||||
before: &[statement("UPDATE account SET name = name || '_before'")],
|
||||
after: &[statement("UPDATE account SET title = title || '_after'")],
|
||||
};
|
||||
const INITIAL_PLAN: Plan = Plan {
|
||||
create: Sql::same(INITIAL),
|
||||
postgres_columns: &[
|
||||
ID,
|
||||
NAME,
|
||||
Column {
|
||||
default: Some("4"),
|
||||
..column("legacy", "bigint")
|
||||
},
|
||||
Column {
|
||||
default: Some("1.25"),
|
||||
..column("amount", "numeric(6,2)")
|
||||
},
|
||||
],
|
||||
migrations: &[HISTORICAL],
|
||||
seeds: &[Statement {
|
||||
sql: Sql::same("INSERT INTO account(name) VALUES ($1) ON CONFLICT(name) DO NOTHING"),
|
||||
parameters: || Ok(vec![Parameter::Text("seed".into())]),
|
||||
}],
|
||||
seed_revision: "initial-seed",
|
||||
..EMPTY
|
||||
};
|
||||
const FINAL_PLAN: Plan = Plan {
|
||||
create: Sql::same(FINAL),
|
||||
indexes: Sql::same(INDEX),
|
||||
postgres_columns: &[
|
||||
ID,
|
||||
TITLE,
|
||||
Column {
|
||||
default: Some("1.25"),
|
||||
..column("amount", "numeric(8,3)")
|
||||
},
|
||||
UID,
|
||||
],
|
||||
migrations: &[HISTORICAL, UPGRADE],
|
||||
renames: &[("title", "name")],
|
||||
seeds: &[Statement {
|
||||
sql: Sql::same(
|
||||
"INSERT INTO account(title,uid) VALUES ($1,$2::uuid) ON CONFLICT(title) DO NOTHING",
|
||||
),
|
||||
parameters: || {
|
||||
Ok(vec![
|
||||
Parameter::Text("new".into()),
|
||||
Parameter::Uuid(*crate::database::scalar::generated_uuid()?.as_bytes()),
|
||||
])
|
||||
},
|
||||
}],
|
||||
seed_revision: "final-seed",
|
||||
};
|
||||
|
||||
const fn statement(sql: &'static str) -> Statement {
|
||||
Statement {
|
||||
sql: Sql::same(sql),
|
||||
parameters: || Ok(vec![]),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires explicit isolated config/setting.json database.postgres_test"]
|
||||
fn schema_migration_seed_uuid_and_rollback() {
|
||||
fixture(cases::migrations);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires explicit isolated config/setting.json database.postgres_test"]
|
||||
fn catalog_drift_fails_before_data_or_history_mutation() {
|
||||
fixture(cases::drift);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires explicit isolated config/setting.json database.postgres_test"]
|
||||
fn new_database_records_final_history_without_old_transforms() {
|
||||
fixture(cases::fresh);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires explicit isolated config/setting.json database.postgres_test"]
|
||||
fn native_binary_codecs_validate_precision_and_parameter_types() {
|
||||
fixture(codec::roundtrip);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires explicit isolated config/setting.json database.postgres_test"]
|
||||
fn foreign_keys_and_tightening_validate_existing_data() {
|
||||
fixture(constraints::foreign_keys);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "requires explicit isolated config/setting.json database.postgres_test"]
|
||||
fn numeric_migrations_reject_precision_loss() {
|
||||
fixture(constraints::numeric);
|
||||
}
|
||||
|
||||
fn configuration() -> serde_json::Value {
|
||||
let path =
|
||||
std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../../config/setting.json");
|
||||
let document =
|
||||
crate::adapter::Document::load(&path).expect("cannot read PostgreSQL test settings");
|
||||
let root: serde_json::Value = document.decode().expect("invalid PostgreSQL test settings");
|
||||
root.get("database")
|
||||
.and_then(|database| database.get("postgres_test"))
|
||||
.expect("database.postgres_test must explicitly select an isolated test database")
|
||||
.clone()
|
||||
}
|
||||
|
||||
fn options() -> Options {
|
||||
let options: Options = serde_json::from_value(configuration())
|
||||
.unwrap_or_else(|_| panic!("invalid PostgreSQL test options"));
|
||||
assert_eq!(options.driver, "postgres");
|
||||
options
|
||||
}
|
||||
|
||||
fn fixture(check: impl AsyncFnOnce(&mut Client)) {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
runtime.block_on(isolated(options(), check));
|
||||
}
|
||||
|
||||
#[cfg(feature = "net")]
|
||||
fn registry_fixture(check: impl AsyncFnOnce(&mut Client, &crate::task::Scope<'_>)) {
|
||||
let runtime = crate::task::Runtime::new(crate::task::Limits {
|
||||
threads: 1,
|
||||
workers: 1,
|
||||
tasks: 16,
|
||||
})
|
||||
.unwrap();
|
||||
runtime
|
||||
.run(async |scope| isolated(options(), async |client| check(client, scope).await).await)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn isolated(options: Options, check: impl AsyncFnOnce(&mut Client)) {
|
||||
let database = super::super::Database::open(options).await.unwrap();
|
||||
let mut client = database
|
||||
.pool
|
||||
.get()
|
||||
.await
|
||||
.unwrap_or_else(|_| panic!("cannot acquire PostgreSQL test connection"));
|
||||
let name = format!(
|
||||
"dever_test_{}",
|
||||
crate::database::scalar::generated_uuid()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
.replace('-', "")
|
||||
);
|
||||
let quoted = schema::quote(&name);
|
||||
client
|
||||
.batch_execute(&format!("CREATE SCHEMA {quoted}"))
|
||||
.await
|
||||
.map_err(Error::from)
|
||||
.unwrap();
|
||||
let result = AssertUnwindSafe(async {
|
||||
client
|
||||
.batch_execute(&format!("SET search_path TO {quoted}"))
|
||||
.await
|
||||
.map_err(Error::from)
|
||||
.unwrap();
|
||||
tokio::time::timeout(std::time::Duration::from_secs(30), check(&mut client))
|
||||
.await
|
||||
.expect("PostgreSQL test deadline exceeded");
|
||||
})
|
||||
.catch_unwind()
|
||||
.await;
|
||||
let cleanup = tokio::time::timeout(
|
||||
std::time::Duration::from_secs(10),
|
||||
client.batch_execute(&format!(
|
||||
"ROLLBACK; SET search_path TO pg_catalog; DROP SCHEMA {quoted} CASCADE"
|
||||
)),
|
||||
)
|
||||
.await;
|
||||
database.close();
|
||||
cleanup
|
||||
.expect("PostgreSQL test cleanup timed out")
|
||||
.map_err(Error::from)
|
||||
.expect("PostgreSQL test schema cleanup failed");
|
||||
if let Err(panic) = result {
|
||||
std::panic::resume_unwind(panic);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user