Initial Dever 0.0.1

This commit is contained in:
Codex committed 2026-11-03 03:32:07 +00:00
commit 81233d098c
1479 files changed
+163782

No files matched your search

+228
View File
@@ -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);
}
}