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

+366
View File
@@ -0,0 +1,366 @@
use dever_runtime::{
adapter::Document,
database::{Database, Error, Kind, Options, Parameter, Settings},
task::{Limits, Runtime},
};
use std::{
path::PathBuf,
sync::atomic::{AtomicU64, Ordering},
};
static NEXT: AtomicU64 = AtomicU64::new(0);
mod performance;
mod scalar;
mod schema;
struct Directory(PathBuf);
impl Directory {
fn new() -> Self {
let path = std::env::temp_dir().join(format!(
"dever-database-{}-{}",
std::process::id(),
NEXT.fetch_add(1, Ordering::Relaxed)
));
std::fs::create_dir(&path).unwrap();
Self(path)
}
}
impl Drop for Directory {
fn drop(&mut self) {
std::fs::remove_dir_all(&self.0).unwrap();
}
}
fn options() -> Options {
Options {
driver: "sqlite".into(),
path: "data/case.db".into(),
url: None,
tls: None,
tenant_directory: None,
tenant_database_prefix: None,
min_connections: 1,
max_connections: 1,
max_page_size: 20,
wait_timeout_ms: 100,
io_timeout_ms: 1000,
}
}
fn runtime() -> Runtime {
Runtime::new(Limits {
threads: 2,
workers: 2,
tasks: 16,
})
.unwrap()
}
const CREATE: &str = "CREATE TABLE account (id INTEGER PRIMARY KEY, name TEXT NOT NULL UNIQUE);";
#[test]
fn pool_and_checked_out_transaction_drop_outside_runtime_in_any_order() {
for order in 0..3 {
let directory = Directory::new();
let runtime = runtime();
let (database, executor) = runtime
.run(async |scope| {
let database = Database::open(&directory.0, options(), scope)
.await
.unwrap();
let mut executor = database.executor().await.unwrap();
executor.initialize(scope, CREATE).await.unwrap();
executor.begin(scope).await.unwrap();
executor
.execute(
scope,
"INSERT INTO account(name) VALUES ('uncommitted')",
vec![],
)
.await
.unwrap();
(database, executor)
})
.unwrap();
match order {
0 => {
drop(executor);
database.close();
drop(database);
drop(runtime);
}
1 => {
drop(database);
drop(executor);
drop(runtime);
}
_ => {
drop(runtime);
drop(database);
drop(executor);
}
}
self::runtime()
.run(async |scope| {
let database = Database::open(&directory.0, options(), scope)
.await
.unwrap();
let count = database
.executor()
.await
.unwrap()
.query(scope, "SELECT COUNT(*) FROM account", vec![], 1, |row| {
row.get::<_, i64>(0)
})
.await
.unwrap();
assert_eq!(count, [0]);
})
.unwrap();
}
}
#[test]
fn transaction_commit_rollback_and_typed_rows() {
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.initialize(scope, CREATE).await.unwrap();
let owner = executor.begin(scope).await.unwrap();
assert!(owner);
executor
.execute(
scope,
"INSERT INTO account(name) VALUES (?1)",
vec![Parameter::Text("Dever 中文".into())],
)
.await
.unwrap();
let nested = executor.begin(scope).await.unwrap();
assert!(!nested);
executor.finish(scope, nested, true).await.unwrap();
executor.finish(scope, owner, true).await.unwrap();
let owner = executor.begin(scope).await.unwrap();
executor
.execute(
scope,
"INSERT INTO account(name) VALUES (?1)",
vec![Parameter::Text("rollback".into())],
)
.await
.unwrap();
executor.finish(scope, owner, false).await.unwrap();
let rows = executor
.query(
scope,
"SELECT id, name FROM account ORDER BY id",
vec![],
20,
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, String>(1)?)),
)
.await
.unwrap();
assert_eq!(rows, vec![(1, "Dever 中文".into())]);
drop(executor);
drop(database.executor().await.unwrap());
database.close();
})
.unwrap();
}
#[test]
fn nested_failure_prevents_outer_commit_and_preserves_pool() {
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.initialize(scope, CREATE).await.unwrap();
let outer = executor.begin(scope).await.unwrap();
executor
.execute(
scope,
"INSERT INTO account(name) VALUES ('temporary')",
vec![],
)
.await
.unwrap();
let inner = executor.begin(scope).await.unwrap();
executor.finish(scope, inner, false).await.unwrap();
assert_eq!(
executor.finish(scope, outer, true).await.unwrap_err().kind,
Kind::Database
);
drop(executor);
let mut executor = database.executor().await.unwrap();
let count = executor
.query(scope, "SELECT COUNT(*) FROM account", vec![], 1, |row| {
row.get::<_, i64>(0)
})
.await
.unwrap();
assert_eq!(count, vec![0]);
database.close();
})
.unwrap();
}
#[test]
fn binds_errors_and_result_bounds_are_real_database_checks() {
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.initialize(scope, CREATE).await.unwrap();
let input = "'); DROP TABLE account; --";
executor
.execute(
scope,
"INSERT INTO account(name) VALUES (?1)",
vec![Parameter::Text(input.into())],
)
.await
.unwrap();
let error = executor
.execute(
scope,
"INSERT INTO account(name) VALUES (?1)",
vec![Parameter::Text(input.into())],
)
.await
.unwrap_err();
assert_eq!(error.kind, Kind::Constraint);
assert!(!error.to_string().contains(input));
let value = executor
.query(scope, "SELECT name FROM account", vec![], 1, |row| {
row.get::<_, String>(0)
})
.await
.unwrap();
assert_eq!(value, vec![input]);
let invalid = executor
.query(scope, "SELECT name FROM account", vec![], 1, |row| {
row.get::<_, i64>(0)
})
.await
.unwrap_err();
assert_eq!(invalid.kind, Kind::InvalidData);
let overflow = executor
.query(scope, "SELECT 1 UNION ALL SELECT 2", vec![], 1, |row| {
row.get::<_, i64>(0)
})
.await
.unwrap_err();
assert_eq!(overflow.kind, Kind::InvalidData);
let exhausted = database.executor().await.err().unwrap();
assert_eq!(exhausted.kind, Kind::PoolExhausted);
database.close();
})
.unwrap();
}
#[test]
fn timeout_discards_transaction_before_next_borrow() {
let directory = Directory::new();
runtime().run(async |scope| {
let mut config = options();
config.io_timeout_ms = 50;
let database = Database::open(&directory.0, config, scope).await.unwrap();
let mut executor = database.executor().await.unwrap();
executor.initialize(scope, CREATE).await.unwrap();
executor.begin(scope).await.unwrap();
executor.execute(scope, "INSERT INTO account(name) VALUES ('must rollback')", vec![]).await.unwrap();
let result = executor.query(scope, "WITH RECURSIVE numbers(value) AS (VALUES(1) UNION ALL SELECT value + 1 FROM numbers WHERE value < 1000000000) SELECT SUM(value) FROM numbers", vec![], 1, |row| row.get::<_, i64>(0)).await;
assert_eq!(result.unwrap_err().kind, Kind::Timeout);
drop(executor);
let mut replacement = database.executor().await.unwrap();
let count = replacement.query(scope, "SELECT COUNT(*) FROM account", vec![], 1, |row| row.get::<_, i64>(0)).await.unwrap();
assert_eq!(count, vec![0]);
database.close();
}).unwrap();
}
#[test]
fn configuration_and_rollback_cause_do_not_expose_secrets() {
for tenant in ["{}", "null"] {
let document = Document::parse(format!(r#"{{"tenant":{tenant},"database":{{"default":{{"type":"sqlite","path":"data/app.db"}}}}}}"#)).unwrap();
assert!(Settings::load(&document).is_err());
}
let settings = Settings::load(
&Document::parse(
r#"{"database":{"default":{"type":"sqlite","path":"data/app.db"}}}"#.into(),
)
.unwrap(),
)
.unwrap();
assert_eq!(settings.select("demo", None).unwrap().max_connections, 1);
assert!(settings.select("demo", Some("missing")).is_err());
for path in ["/tmp/escape", "../escape", "data/../escape", ""] {
let mut value = options();
value.path = path.into();
assert!(value.validate().is_err());
}
let error = Error::new(Kind::Constraint, "business failure")
.with_cause(Error::new(Kind::Connection, "rollback failed"))
.with_cause(Error::new(Kind::Pool, "pool close failed"));
assert_eq!(error.kind, Kind::Constraint);
assert!(error.to_string().contains("rollback failed"));
assert!(error.to_string().contains("pool close failed"));
let rollback = error.cause.as_ref().unwrap();
assert_eq!(rollback.kind, Kind::Connection);
assert_eq!(rollback.cause.as_ref().unwrap().kind, Kind::Pool);
}
#[test]
fn schema_drift_fails_before_touching_data_or_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();
const SCHEMA: &str = "CREATE TABLE account (id INTEGER PRIMARY KEY, name TEXT NOT NULL); CREATE INDEX account_name ON account(name);";
executor.schema(scope, "account", SCHEMA).await.unwrap();
executor.schema(scope, "account", SCHEMA).await.unwrap();
executor.execute(scope, "INSERT INTO account(name) VALUES ('preserved')", vec![]).await.unwrap();
executor.initialize(scope, "DROP INDEX account_name;").await.unwrap();
assert_eq!(executor.schema(scope, "account", SCHEMA).await.unwrap_err().kind, Kind::Migration);
let values = executor.query(scope, "SELECT name FROM account", vec![], 1, |row| row.get::<_, String>(0)).await.unwrap();
assert_eq!(values, vec!["preserved"]);
database.close();
}).unwrap();
}
#[test]
fn stopping_a_running_query_rolls_back_and_releases_its_worker() {
let directory = Directory::new();
runtime().run(async |scope| {
let database = Database::open(&directory.0, options(), scope).await.unwrap();
let mut setup = database.executor().await.unwrap();
setup.initialize(scope, CREATE).await.unwrap();
drop(setup);
let copy = database.clone();
let (started, ready) = tokio::sync::oneshot::channel();
let task = scope.spawn(move |child| async move {
let mut executor = copy.executor().await.unwrap();
executor.begin(&child).await.unwrap();
executor.execute(&child, "INSERT INTO account(name) VALUES ('cancelled')", vec![]).await.unwrap();
started.send(()).unwrap();
executor.query(&child, "WITH RECURSIVE numbers(value) AS (VALUES(1) UNION ALL SELECT value + 1 FROM numbers WHERE value < 1000000000) SELECT SUM(value) FROM numbers", vec![], 1, |row| row.get::<_, i64>(0)).await
}).unwrap();
ready.await.unwrap();
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
assert!(task.stop().await.unwrap().is_none());
let mut executor = database.executor().await.unwrap();
let count = executor.query(scope, "SELECT COUNT(*) FROM account", vec![], 1, |row| row.get::<_, i64>(0)).await.unwrap();
assert_eq!(count, vec![0]);
database.close();
}).unwrap();
}