Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,366 @@
|
||||
use crate::{
|
||||
adapter::Document,
|
||||
database::{Binding, Context, Parameter, Plan, Registry, Settings, Sql, Statement, Storage},
|
||||
task::{Limits, Runtime},
|
||||
};
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::atomic::{AtomicU64, Ordering},
|
||||
};
|
||||
|
||||
static NEXT: AtomicU64 = AtomicU64::new(0);
|
||||
struct Directory(PathBuf);
|
||||
impl Directory {
|
||||
fn new() -> Self {
|
||||
let directory = Self(std::env::temp_dir().join(format!(
|
||||
"dever-registry-{}-{}",
|
||||
std::process::id(),
|
||||
NEXT.fetch_add(1, Ordering::Relaxed)
|
||||
)));
|
||||
std::fs::create_dir(&directory.0).unwrap();
|
||||
directory
|
||||
}
|
||||
}
|
||||
impl Drop for Directory {
|
||||
fn drop(&mut self) {
|
||||
std::fs::remove_dir_all(&self.0).unwrap();
|
||||
}
|
||||
}
|
||||
fn runtime() -> Runtime {
|
||||
Runtime::new(Limits {
|
||||
threads: 2,
|
||||
workers: 2,
|
||||
tasks: 16,
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
const PARENT: &str = "CREATE TABLE z_parent(id INTEGER PRIMARY KEY);";
|
||||
const CHILD: &str = "CREATE TABLE a_child(id INTEGER PRIMARY KEY, parent_id INTEGER NOT NULL REFERENCES z_parent(id));";
|
||||
const PARENT_PLAN: Plan = Plan {
|
||||
create: Sql::same(PARENT),
|
||||
indexes: Sql::same(""),
|
||||
postgres_columns: &[],
|
||||
renames: &[],
|
||||
migrations: &[],
|
||||
seed_revision: "parent-v1",
|
||||
seeds: &[Statement {
|
||||
sql: Sql::same("INSERT INTO z_parent VALUES (?1)"),
|
||||
parameters: || Ok(vec![Parameter::Integer(7)]),
|
||||
}],
|
||||
};
|
||||
const CHILD_PLAN: Plan = Plan {
|
||||
create: Sql::same(CHILD),
|
||||
seed_revision: "child-v1",
|
||||
seeds: &[Statement {
|
||||
sql: Sql::same("INSERT INTO a_child VALUES (?1,?2)"),
|
||||
parameters: || Ok(vec![Parameter::Integer(1), Parameter::Integer(7)]),
|
||||
}],
|
||||
..PARENT_PLAN
|
||||
};
|
||||
|
||||
fn binding(
|
||||
table: &'static str,
|
||||
schema: &'static str,
|
||||
plan: Option<&'static Plan>,
|
||||
references: &'static [usize],
|
||||
) -> Binding {
|
||||
Binding {
|
||||
storage: Storage::Global,
|
||||
component: "demo",
|
||||
database: None,
|
||||
table,
|
||||
schema: Sql::same(schema),
|
||||
plan,
|
||||
references,
|
||||
}
|
||||
}
|
||||
|
||||
fn settings() -> Settings {
|
||||
Settings::load(&Document::parse(r#"{"database":{"default":{"type":"sqlite","path":"registry.db","max_connections":1}}}"#.into()).unwrap()).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shared_internal_bindings_initialize_once_and_reject_conflicting_schema() {
|
||||
let directory = Directory::new();
|
||||
let shared = binding(
|
||||
"queue",
|
||||
"CREATE TABLE queue(id INTEGER PRIMARY KEY);",
|
||||
None,
|
||||
&[],
|
||||
);
|
||||
let mut bindings = [shared.clone(), shared];
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
for expected in [1, 2] {
|
||||
let registry = Registry::prepare(&directory.0, settings(), &bindings, scope)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(registry.same_pool(0, 1));
|
||||
let mut context = Context::new(registry.clone());
|
||||
context
|
||||
.on(0, scope)
|
||||
.await
|
||||
.unwrap()
|
||||
.execute(
|
||||
scope,
|
||||
"INSERT INTO queue VALUES (?1)",
|
||||
vec![Parameter::Integer(expected)],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let count = context
|
||||
.on(1, scope)
|
||||
.await
|
||||
.unwrap()
|
||||
.query(scope, "SELECT COUNT(*) FROM queue", vec![], 1, |row| {
|
||||
row.get::<_, i64>(0)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(count, [expected]);
|
||||
drop(context);
|
||||
registry.close().await;
|
||||
}
|
||||
bindings[1].schema =
|
||||
Sql::same("CREATE TABLE queue(id INTEGER PRIMARY KEY, extra TEXT);");
|
||||
let error = Registry::prepare(&directory.0, settings(), &bindings, scope)
|
||||
.await
|
||||
.err()
|
||||
.expect("conflicting shared schema accepted");
|
||||
assert_eq!(error.kind, crate::database::Kind::Migration);
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sqlite_registry_seeds_forward_references_after_all_tables_exist() {
|
||||
let directory = Directory::new();
|
||||
let bindings = [
|
||||
binding("a_child", CHILD, Some(&CHILD_PLAN), &[1]),
|
||||
binding("z_parent", PARENT, Some(&PARENT_PLAN), &[]),
|
||||
];
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
for _ in 0..2 {
|
||||
let registry = Registry::prepare(&directory.0, settings(), &bindings, scope)
|
||||
.await
|
||||
.unwrap();
|
||||
let mut context = Context::new(registry.clone());
|
||||
let executor = context.on(0, scope).await.unwrap();
|
||||
assert_eq!(
|
||||
executor
|
||||
.query(scope, "SELECT parent_id FROM a_child", vec![], 1, |row| row
|
||||
.get::<_, i64>(0))
|
||||
.await
|
||||
.unwrap(),
|
||||
[7]
|
||||
);
|
||||
assert!(
|
||||
executor
|
||||
.execute(scope, "INSERT INTO a_child VALUES (2,999)", vec![])
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
drop(context);
|
||||
registry.close().await;
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sqlite_registry_preserves_cyclic_and_self_references_with_and_without_seeds() {
|
||||
const LEFT: &str = "CREATE TABLE cycle_left(id INTEGER PRIMARY KEY, right_id INTEGER REFERENCES cycle_right(id));";
|
||||
const RIGHT: &str = "CREATE TABLE cycle_right(id INTEGER PRIMARY KEY, left_id INTEGER REFERENCES cycle_left(id));";
|
||||
const SELF: &str = "CREATE TABLE self_node(id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES self_node(id));";
|
||||
const LEFT_PLAN: Plan = Plan {
|
||||
create: Sql::same(LEFT),
|
||||
seeds: &[Statement {
|
||||
sql: Sql::same("INSERT INTO cycle_left VALUES (1,2)"),
|
||||
parameters: || Ok(vec![]),
|
||||
}],
|
||||
..PARENT_PLAN
|
||||
};
|
||||
const RIGHT_PLAN: Plan = Plan {
|
||||
create: Sql::same(RIGHT),
|
||||
seeds: &[Statement {
|
||||
sql: Sql::same("INSERT INTO cycle_right VALUES (2,1)"),
|
||||
parameters: || Ok(vec![]),
|
||||
}],
|
||||
..PARENT_PLAN
|
||||
};
|
||||
const SELF_PLAN: Plan = Plan {
|
||||
create: Sql::same(SELF),
|
||||
seeds: &[Statement {
|
||||
sql: Sql::same("INSERT INTO self_node VALUES (1,1)"),
|
||||
parameters: || Ok(vec![]),
|
||||
}],
|
||||
..PARENT_PLAN
|
||||
};
|
||||
const EMPTY_LEFT: Plan = Plan {
|
||||
seeds: &[],
|
||||
..LEFT_PLAN
|
||||
};
|
||||
const EMPTY_RIGHT: Plan = Plan {
|
||||
seeds: &[],
|
||||
..RIGHT_PLAN
|
||||
};
|
||||
const EMPTY_SELF: Plan = Plan {
|
||||
seeds: &[],
|
||||
..SELF_PLAN
|
||||
};
|
||||
for seeded in [false, true] {
|
||||
let directory = Directory::new();
|
||||
let bindings = [
|
||||
binding(
|
||||
"cycle_left",
|
||||
LEFT,
|
||||
Some(if seeded { &LEFT_PLAN } else { &EMPTY_LEFT }),
|
||||
&[1],
|
||||
),
|
||||
binding(
|
||||
"cycle_right",
|
||||
RIGHT,
|
||||
Some(if seeded { &RIGHT_PLAN } else { &EMPTY_RIGHT }),
|
||||
&[0],
|
||||
),
|
||||
binding(
|
||||
"self_node",
|
||||
SELF,
|
||||
Some(if seeded { &SELF_PLAN } else { &EMPTY_SELF }),
|
||||
&[2],
|
||||
),
|
||||
];
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
for _ in 0..2 {
|
||||
let registry = Registry::prepare(&directory.0, settings(), &bindings, scope)
|
||||
.await
|
||||
.unwrap();
|
||||
let mut context = Context::new(registry.clone());
|
||||
let executor = context.on(0, scope).await.unwrap();
|
||||
assert_eq!(
|
||||
executor
|
||||
.query(scope, "SELECT count(*) FROM cycle_left", vec![], 1, |row| {
|
||||
row.get::<_, i64>(0)
|
||||
})
|
||||
.await
|
||||
.unwrap(),
|
||||
[i64::from(seeded)]
|
||||
);
|
||||
assert!(
|
||||
executor
|
||||
.execute(scope, "INSERT INTO cycle_left VALUES (3,999)", vec![])
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
assert!(
|
||||
executor
|
||||
.execute(scope, "INSERT INTO self_node VALUES (3,999)", vec![])
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
drop(context);
|
||||
registry.close().await;
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sqlite_registry_invalid_seed_rolls_back_the_physical_group() {
|
||||
const INVALID: Plan = Plan {
|
||||
seeds: &[Statement {
|
||||
sql: Sql::same("INSERT INTO a_child VALUES (1,999)"),
|
||||
parameters: || Ok(vec![]),
|
||||
}],
|
||||
..CHILD_PLAN
|
||||
};
|
||||
let directory = Directory::new();
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let bindings = [
|
||||
binding("a_child", CHILD, Some(&INVALID), &[1]),
|
||||
binding("z_parent", PARENT, Some(&PARENT_PLAN), &[]),
|
||||
];
|
||||
let error = Registry::prepare(&directory.0, settings(), &bindings, scope)
|
||||
.await
|
||||
.err()
|
||||
.unwrap();
|
||||
assert_eq!(error.kind, crate::database::Kind::Migration);
|
||||
let options = settings().select("", None).unwrap().clone();
|
||||
let database = crate::database::Database::open(&directory.0, options, scope)
|
||||
.await
|
||||
.unwrap();
|
||||
let mut executor = database.executor().await.unwrap();
|
||||
assert_eq!(
|
||||
executor
|
||||
.query(
|
||||
scope,
|
||||
"SELECT count(*) FROM sqlite_schema WHERE type='table'",
|
||||
vec![],
|
||||
1,
|
||||
|row| row.get::<_, i64>(0)
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
[0]
|
||||
);
|
||||
drop(executor);
|
||||
database.close();
|
||||
let bindings = [
|
||||
binding("a_child", CHILD, Some(&CHILD_PLAN), &[1]),
|
||||
bindings[1].clone(),
|
||||
];
|
||||
let registry = Registry::prepare(&directory.0, settings(), &bindings, scope)
|
||||
.await
|
||||
.unwrap();
|
||||
registry.close().await;
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sqlite_tenant_migration_batches_seed_dependencies_in_each_physical_database() {
|
||||
let directory = Directory::new();
|
||||
let mut config = settings();
|
||||
config.0.get_mut("default").unwrap().tenant_directory = Some("tenants".into());
|
||||
config.1 = Some(crate::database::TenantOptions {
|
||||
database: "default".into(),
|
||||
max_pools: 2,
|
||||
idle_timeout_ms: 100,
|
||||
});
|
||||
let mut bindings = [
|
||||
binding("a_child", CHILD, Some(&CHILD_PLAN), &[1]),
|
||||
binding("z_parent", PARENT, Some(&PARENT_PLAN), &[]),
|
||||
];
|
||||
for binding in &mut bindings {
|
||||
binding.storage = Storage::Tenant;
|
||||
}
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let registry = Registry::prepare(&directory.0, config, &bindings, scope)
|
||||
.await
|
||||
.unwrap();
|
||||
for tenant in [1, 2, 1] {
|
||||
registry.migrate_tenant(scope, tenant).await.unwrap();
|
||||
let mut context = Context::new(registry.clone());
|
||||
context.select_tenant(scope, tenant).await.unwrap();
|
||||
assert_eq!(
|
||||
context
|
||||
.on(0, scope)
|
||||
.await
|
||||
.unwrap()
|
||||
.query(scope, "SELECT parent_id FROM a_child", vec![], 1, |row| row
|
||||
.get::<_, i64>(0))
|
||||
.await
|
||||
.unwrap(),
|
||||
[7]
|
||||
);
|
||||
}
|
||||
registry.close().await;
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
Reference in new issue
Block a user