//! Checked Model/transaction source through LLVM and the real database ABI. #[cfg(all(target_os = "linux", target_arch = "x86_64"))] #[path = "dever-tests/tests/support/llvm_module.rs"] mod llvm_module; #[cfg(all( target_os = "linux", target_arch = "x86_64", feature = "runtime-postgres" ))] #[path = "dever-tests/tests/support/postgres.rs"] pub mod postgres; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] #[path = "dever-tests/tests/support/process.rs"] mod process; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] #[path = "dever-tests/tests/support/temp.rs"] mod temp; #[cfg(all(target_os = "linux", target_arch = "x86_64"))] mod native { use super::llvm_module; use dever_backend_bridge::{Target, emit_object}; use dever_core::{check, hir::Program, source::SourceMap}; use std::time::Duration; const SETTING: &str = r#"{"database":{"default":{"type":"sqlite","path":"data/db/owned.db","max_connections":1,"max_page_size":10}}}"#; const ITEM: &str = r#"type Item { name: Text(1, 64) unique amount: Int default 1 index note: Text? } "#; struct Fixture<'a> { model: &'a str, app: &'a str, body: &'a str, } const CRUD: Fixture<'static> = Fixture { model: ITEM, app: r#"some_text(value: Text) (answer: Text?) { answer = value } amount(value: null) (answer: Int) { answer = -1 } amount(value: model.Item) (answer: Int) { answer = value.amount } exercise() (okay: Bool) { saved = model.create({ name = "你好 🌍" }) changed = model.update(saved.id, { amount = 7 note = "owned" }) loaded = model.get(saved.id) first = model.first({ where = name == "你好 🌍" }) absent = model.first({ where = name == "missing" }) page = model.list({ order = id.asc size = 2 }) present = model.exists({ where = name == "你好 🌍" }) removed = model.delete(saved.id) remaining = model.count() okay = loaded.id == saved.id and loaded.name == "你好 🌍" and loaded.amount == 7 and loaded.note == some_text("owned") and amount(first) == 7 and amount(absent) == -1 and changed == 1 and page.total == 1 and length(page.items) == 1 and present and removed == 1 and remaining == 0 } "#, body: "{}", }; const BULK: Fixture<'static> = Fixture { model: ITEM, app: r#"exercise() (okay: Bool) { inserted = model.create_many([{ name = "one" amount = 2 }, { name = "two" amount = 3 }]) updated = model.upsert({ name = "one" }, { amount = 99 }, { amount = 4 }) created = model.upsert({ name = "three" }, { amount = 5 }, { amount = 99 }) changed = model.update({ where = amount >= 3 }, { note = "bulk" }) count = model.count({ where = not contains(name, "missing") and in(amount, [3, 4, 5]) and between(amount, 3, 5) and note != null }) removed = model.delete({ where = amount >= 3 }) okay = inserted == 2 and updated.amount == 4 and created.amount == 5 and changed == 3 and count == 3 and removed == 3 and model.count() == 0 } "#, body: "{}", }; const NULL_EQUALITY: Fixture<'static> = Fixture { model: ITEM, app: r#"some_text(value: Text) (answer: Text?) { answer = value } missing_text() (answer: Text?) { answer = null } exercise() (okay: Bool) { absent = model.create({ name = "absent" }) present = model.create({ name = "present" note = "value" }) missing = missing_text() value = some_text("value") nulls = model.count({ where = note == missing }) values = model.count({ where = note == value }) not_nulls = model.count({ where = note != missing }) removed = model.delete({ where = name == "absent" or name == "present" }) okay = nulls == 1 and values == 1 and not_nulls == 1 and absent.note == null and present.note == value and removed == 2 } "#, body: "{}", }; const CURSOR: Fixture<'static> = Fixture { model: ITEM, app: r#"names(value: model.Item) (name: Text) { name = value.name } cursor_after(items: List) (value: model.Item?) { value = first(items) } exercise() (okay: Bool) { inserted = model.create_many([{ name = "one" amount = 1 }, { name = "two" amount = 1 }, { name = "three" amount = 2 }]) first = model.cursor({ order = [amount.asc, id.asc] size = 2 }) second = model.cursor({ after = first.next order = [amount.asc, id.asc] size = 2 }) end = model.cursor({ after = cursor_after(second.items) order = [amount.asc, id.asc] size = 2 }) page = model.list({ order = [amount.asc, id.asc] page = 2 size = 2 }) removed = model.delete({ where = amount >= 1 }) okay = inserted == 3 and each(names, first.items) == ["one", "two"] and first.has_more and each(names, second.items) == ["three"] and second.next == null and not second.has_more and length(end.items) == 0 and end.next == null and each(names, page.items) == ["three"] and page.total == 3 and removed == 3 } "#, body: "{}", }; const SCALARS: Fixture<'static> = Fixture { model: r#"type State { Active Disabled } type Item { name: Text(1, 64) unique enabled: Bool amount: Int fraction: Float price: Decimal(10, 2) payload: Bytes(8) external_id: Uuid generated_id: Uuid generated unique instant: DateTime day: Date clock: Time elapsed: Duration document: Json state: State default State.Active private memo: Text? } "#, app: r#"exercise(uuid: Uuid, instant: DateTime, day: Date, clock: Time, elapsed: Duration, document: Json) (okay: Bool) { payload = dever.bytes.from_ints([0, 128, 255]) saved = model.create({ name = "scalar" enabled = true amount = 9223372036854775807 fraction = 1.5 price = 123.45 payload = payload external_id = uuid instant = instant day = day clock = clock elapsed = elapsed document = document memo = "private" }) loaded = model.get(saved.id) removed = model.delete(saved.id) okay = loaded.enabled and loaded.amount == 9223372036854775807 and loaded.fraction == 1.5 and loaded.price == 123.45 and loaded.payload == payload and loaded.external_id == uuid and loaded.generated_id == saved.generated_id and loaded.instant == instant and loaded.day == day and loaded.clock == clock and loaded.elapsed == elapsed and loaded.document == document and loaded.state == model.State.Active and loaded.memo != null and removed == 1 } "#, body: r#"{"uuid":"01890f3e-7c00-7000-8000-000000000001","instant":"2026-10-01T12:34:56.789Z","day":"2026-10-01","clock":"12:34:56.789","elapsed":-42,"document":{"large":9999999999999999999999999999,"nested":[true,null]}}"#, }; const TRANSACTION: Fixture<'static> = Fixture { model: ITEM, app: r#"transaction insert() (saved: model.Item) { saved = model.create({ name = "nested" }) } forward() (saved: model.Item) { saved = insert() } transaction edit() (saved: model.Item) { inserted = forward() changed = model.update(inserted.id, { amount = 7 }) saved = model.get(inserted.id) } exercise() (okay: Bool) { saved = edit() loaded = model.get(saved.id) removed = model.delete(saved.id) okay = saved.amount == 7 and loaded.amount == 7 and removed == 1 and model.count() == 0 } "#, body: "{}", }; const STREAM: Fixture<'static> = Fixture { model: r#"type Item { name: Text(1, 64) unique amount: Int default 1 } seed { { name = "one" amount = 2 } { name = "two" amount = 3 } } "#, app: r#"add(value: model.Item, previous: Int) (next: Int) { next = previous + value.amount } one(value: model.Item, previous: Int) (next: Int, stop: Bool) { next = previous + value.amount stop = true } exercise() (okay: Bool) { full = model.stream({ order = id.asc size = 1 }) total = reduce(add, full, 0) partial = model.stream({ order = id.asc size = 1 }) alias = partial first = reduce_until(one, partial, 0) remaining = reduce(add, alias, 0) unopened = model.stream({ size = 1 }) close(unopened) okay = total == 5 and first == 2 and remaining == 0 and model.count() == 2 } "#, body: "{}", }; const SQL: Fixture<'static> = Fixture { model: r#"type Summary { amount: Int } type Item { name: Text(1, 64) unique amount: Int default 1 } sql by_name(name: Text) (item: Item?) { sqlite = "SELECT id, created_at, name, amount FROM item WHERE name = ?1" postgres = "SELECT id, created_at, name, amount FROM item WHERE name = $1" } sql all_items(amount: Int) (items: List) { sqlite = "SELECT id, created_at, name, amount FROM item WHERE amount >= ?1 ORDER BY id" postgres = "SELECT id, created_at, name, amount FROM item WHERE amount >= $1::bigint ORDER BY id" } sql summary() (summary: Summary) { sqlite = "SELECT SUM(amount) FROM item" postgres = "SELECT SUM(amount)::bigint FROM item" } "#, app: r#"amount(value: null) (answer: Int) { answer = -1 } amount(value: model.Item) (answer: Int) { answer = value.amount } transaction populate() (okay: Bool) { inserted = model.create_many([{ name = "one" amount = 2 }, { name = "two" amount = 3 }]) found = model.by_name("one") absent = model.by_name("missing") rows = model.all_items(2) summary = model.summary() okay = inserted == 2 and amount(found) == 2 and amount(absent) == -1 and length(rows) == 2 and summary.amount == 5 } exercise() (okay: Bool) { valid = populate() removed = model.delete({ where = amount >= 2 }) okay = valid and removed == 2 and model.count() == 0 } "#, body: "{}", }; const ERRORS: &str = r#"type ReadResult { Done(value: model.Item) error Failed(error: dever.database.Error) } category(error: dever.database.Error.Pool(message)) (kind: Int) recover("assert database failure identity") { kind = 1 } category(error: dever.database.Error.PoolExhausted(message)) (kind: Int) recover("assert database failure identity") { kind = 2 } category(error: dever.database.Error.Connection(message)) (kind: Int) recover("assert database failure identity") { kind = 3 } category(error: dever.database.Error.Timeout(message)) (kind: Int) recover("assert database failure identity") { kind = 4 } category(error: dever.database.Error.Cancelled(message)) (kind: Int) recover("assert database failure identity") { kind = 5 } category(error: dever.database.Error.Database(message)) (kind: Int) recover("assert database failure identity") { kind = 6 } category(error: dever.database.Error.Constraint(message)) (kind: Int) recover("assert database failure identity") { kind = 7 } category(error: dever.database.Error.NotFound(message)) (kind: Int) recover("assert database failure identity") { kind = 8 } category(error: dever.database.Error.InvalidData(message)) (kind: Int) recover("assert database failure identity") { kind = 9 } category(error: dever.database.Error.Migration(message)) (kind: Int) recover("assert database failure identity") { kind = 10 } captured(value: ReadResult.Done(item)) (kind: Int) recover("assert database failure identity") { kind = 0 } captured(value: ReadResult.Failed(error)) (kind: Int) recover("assert database failure identity") { kind = category(error) } "#; fn captured_errors() -> Fixture<'static> { Fixture { model: ITEM, app: r#"transaction missing() (value: model.Item) { saved = model.create({ name = "temporary" }) removed = model.delete(saved.id) value = model.get(saved.id) } transaction duplicate() (value: model.Item) { saved = model.create({ name = "duplicate" }) value = model.create({ name = "duplicate" }) } exercise() (okay: Bool) { missing = captured(result(missing())) duplicate = captured(result(duplicate())) retained = model.count() removed = model.delete({ where = name == "duplicate" }) okay = missing == 8 and duplicate == 7 and retained == 1 and removed == 1 and model.count() == 0 } "#, body: "{}", } } const NUMERIC_ROLLBACK: Fixture<'static> = Fixture { model: ITEM, app: r#"transaction fail_numeric(divisor: Int) (okay: Bool) { saved = model.create({ name = "numeric rollback" }) okay = 42 // divisor == 42 } exercise(divisor: Int) (okay: Bool) { okay = fail_numeric(divisor) } "#, body: r#"{"divisor":0}"#, }; const CAPTURED_NOT_FOUND: Fixture<'static> = Fixture { model: ITEM, app: r#"transaction missing() (value: model.Item) { saved = model.create({ name = "temporary" }) removed = model.delete(saved.id) value = model.get(saved.id) } exercise() (okay: Bool) { missing = captured(result(missing())) okay = missing == 8 and model.count() == 0 } "#, body: "{}", }; const CONSTRAINT_ROLLBACK: Fixture<'static> = Fixture { model: ITEM, app: r#"exercise() (okay: Bool) { first = model.create({ name = "duplicate" }) second = model.create({ name = "duplicate" }) okay = true } inspect() (okay: Bool) { okay = model.count() == 0 } "#, body: "{}", }; const ENCODING_COMMIT: Fixture<'static> = Fixture { model: ITEM, app: r#"transaction exercise(denominator: Float) (value: Float) { removed = model.delete({ where = name == "encoding commit" }) saved = model.create({ name = "encoding commit" }) value = 1.0 / denominator } inspect() (okay: Bool) { okay = model.count() == 1 } "#, body: r#"{"denominator":0}"#, }; const ENCODING_ROLLBACK: Fixture<'static> = Fixture { model: ITEM, app: r#"exercise(denominator: Float) (value: Float) { saved = model.create({ name = "encoding rollback" }) value = 1.0 / denominator } "#, body: r#"{"denominator":0}"#, }; const TASK_ERROR: Fixture<'static> = Fixture { model: ITEM, app: r#"missing(id: model.id) (value: model.Item) { value = model.get(id) } task_missing(id: model.id) (value: model.Item) { pending = run(missing(id)) value = wait(pending) } exercise(id: model.id) (okay: Bool) { missing = captured(result(task_missing(id))) okay = missing == 8 and model.count() == 0 } "#, body: r#"{"id":9223372036854775807}"#, }; const HANDLER_TRANSACTION: Fixture<'static> = Fixture { model: ITEM, app: r#"update(item: model.Item) (count: Int) { count = model.update(item.id, { amount = 7 }) } transaction change_all() (count: Int) { inserted = model.create_many([{ name = "one" }, { name = "two" }]) page = model.list({ order = id.asc size = 10 }) count = sum(update, page.items) } exercise() (okay: Bool) { changed = change_all() matching = model.count({ where = amount == 7 }) removed = model.delete({ where = amount == 7 }) okay = changed == 2 and matching == 2 and removed == 2 and model.count() == 0 } "#, body: "{}", }; const SEED: Fixture<'static> = Fixture { model: r#"type Item { name: Text(1, 64) unique amount: Int default 1 } seed { { name = "seed" amount = 2 } } "#, app: r#"exercise() (okay: Bool) { seeded = model.count({ where = name == "seed" and amount == 2 }) created = model.create({ name = "temporary" }) removed = model.delete(created.id) okay = seeded == 1 and created.amount == 1 and removed == 1 and model.count() == 1 } "#, body: "{}", }; const EMPTY_BULK: Fixture<'static> = Fixture { model: "type Item {}", app: r#"exercise() (okay: Bool) { created = model.create_many([{}, {}]) count = model.count() removed = model.delete({}) okay = created == 2 and count == 2 and removed == 2 } "#, body: "{}", }; const MIGRATION_INITIAL: Fixture<'static> = Fixture { model: r#"type Item { name: Text(1, 64) unique amount: Int default 1 legacy: Text? } seed { { name = "too-long-name" legacy = "old" } } "#, app: r#"exercise() (okay: Bool) { okay = model.count({ where = name == "too-long-name" and amount == 1 }) == 1 } "#, body: "{}", }; const MIGRATION_UPGRADED: Fixture<'static> = Fixture { model: r#"type Item { display_name: Text(1, 8) from name unique amount: Int default 2 } migrate normalize_names { before { sqlite = "UPDATE item SET name = ?1 WHERE name = ?2" postgres = "UPDATE item SET name = $1 WHERE name = $2" parameters = ["short", "too-long-name"] } drop legacy after { sqlite = "UPDATE item SET display_name = ?1 WHERE display_name = ?2" postgres = "UPDATE item SET display_name = $1 WHERE display_name = $2" parameters = ["final", "short"] } } seed { { display_name = "second" } } "#, app: r#"exercise() (okay: Bool) { original = model.count({ where = display_name == "final" and amount == 1 }) seeded = model.count({ where = display_name == "second" and amount == 2 }) created = model.create({ display_name = "temp" }) removed = model.delete(created.id) okay = original == 1 and seeded == 1 and created.amount == 2 and removed == 1 and model.count() == 2 } "#, body: "{}", }; const CANCEL_STREAM: Fixture<'static> = Fixture { model: r#"type Item { amount: Int unique } seed { { amount = 1 } { amount = 2 } { amount = 3 } { amount = 4 } } "#, app: r#"pause(item: model.Item, ready: Channel) (next: Channel) { send(ready, item.amount) dever.task.sleep(1000) next = ready } scan(ready: Channel) (okay: Bool) { rows = model.stream({ order = id.asc size = 1 }) signal = reduce(pause, rows, ready) okay = false } fallback() (okay: Bool) { okay = model.count() == 4 } exercise() (okay: Bool) { ready = channel(Int, 1) pending = run(scan(ready)) started = receive(ready) released = timeout(pending, 1, fallback) close(ready) okay = started != null and released and model.count() == 4 } "#, body: "{}", }; fn relations() -> SourceMap { let mut sources = SourceMap::default(); sources.add( "store/account/model/organization.dever", "type Organization { name: Text(1, 64) }\nrelation users = store.account.model.user.organization_id", ); sources.add( "store/account/model/user.dever", "type User { name: Text(1, 64)\n organization_id: store.account.model.organization.id?\n manager_id: store.account.model.user.id? }\nrelation posts = store.account.model.post.author_id\nrelation other_posts = store.account.model.post.author_id\nrelation unloaded_posts = store.account.model.post.author_id", ); sources.add( "store/account/model/post.dever", "type Post { author_id: store.account.model.user.id\n title: Text(1, 64) }", ); sources.add( "store/account/app.dever", r#"organization_name(value: Related.Unloaded()) (name: Text) { name = "unloaded" } organization_name(value: Related.Loaded(organization)) (name: Text) { name = nullable_organization(organization) } nullable_organization(value: null) (name: Text) { name = "none" } nullable_organization(value: model.organization.Organization) (name: Text) { name = value.name } manager_name(value: Related.Unloaded()) (name: Text) { name = "unloaded" } manager_name(value: Related.Loaded(manager)) (name: Text) { name = nullable_user(manager) } nullable_user(value: null) (name: Text) { name = "none" } nullable_user(value: model.user.User) (name: Text) { name = value.name } post_count(value: Related.Unloaded>()) (count: Int) { count = -1 } post_count(value: Related.Loaded>(posts)) (count: Int) { count = length(posts) } is_primary(value: model.user.User) (yes: Bool) { yes = value.name == "First" } is_secondary(value: model.user.User) (yes: Bool) { yes = value.name == "Second" } transaction loaded() (page: model.user.Page) { page = model.user.list({ order = id.asc size = 10 with = [organization, manager, posts, other_posts] }) } exercise() (okay: Bool) { organization = model.organization.create({ name = "Acme" }) primary = model.user.create({ name = "First" organization_id = organization.id }) secondary = model.user.create({ name = "Second" manager_id = primary.id }) inserted = model.post.create_many([{ author_id = primary.id title = "One" }, { author_id = primary.id title = "Two" }]) unloaded = model.user.get(primary.id) page = loaded() first_loaded = find(is_primary, page.items) second_loaded = find(is_secondary, page.items) valid = validate(first_loaded, second_loaded) deleted_posts = model.post.delete({ where = author_id == primary.id }) deleted_second = model.user.delete(secondary.id) deleted_first = model.user.delete(primary.id) deleted_org = model.organization.delete(organization.id) okay = inserted == 2 and organization_name(unloaded.organization) == "unloaded" and page.total == 2 and valid and deleted_posts == 2 and deleted_second == 1 and deleted_first == 1 and deleted_org == 1 } validate(first: null, second: model.user.User?) (okay: Bool) { okay = false } validate(first: model.user.User, second: null) (okay: Bool) { okay = false } validate(first: model.user.User, second: model.user.User) (okay: Bool) { okay = organization_name(first.organization) == "Acme" and organization_name(second.organization) == "none" and manager_name(second.manager) == "First" and post_count(first.posts) == 2 and post_count(first.other_posts) == 2 and post_count(second.posts) == 0 and post_count(first.unloaded_posts) == -1 } "#, ); sources.add("store/account/api.dever", "cmd exercise = app.exercise"); sources } fn fixture_sources(fixture: &Fixture<'_>) -> SourceMap { fixture_sources_with_api(fixture, "cmd exercise = app.exercise") } fn fixture_sources_with_api(fixture: &Fixture<'_>, api: &str) -> SourceMap { let mut sources = SourceMap::default(); sources.add("store/item/model.dever", fixture.model); sources.add("store/item/app.dever", fixture.app); sources.add("store/item/api.dever", api); sources } fn checked(sources: &SourceMap) -> Program { check(sources).unwrap_or_else(|errors| { panic!( "{}", errors .iter() .map(|error| error.render(sources)) .collect::() ) }) } fn entry_ir(sources: &SourceMap, fault: Option) -> String { let program = checked(sources); let mut ir = dever_core::llvm::emit_application(&program, sources).unwrap(); let checks = fault.map_or_else( || " %passed = icmp eq i32 %status, 0".to_owned(), |code| llvm_module::fault_check(code, None), ); ir.push_str(&format!( "\ndefine i32 @dever_test_run() {{\nentry:\n %out = alloca i8\n %fault = alloca %dever.fault\n %status = call i32 @dever_application_entry(ptr %out, ptr %fault)\n{checks}\n call void @dever_outputs_release(ptr %out)\n call void @dever_fault_release(ptr %fault)\n %exit = select i1 %passed, i32 0, i32 1\n ret i32 %exit\n}}\n" )); ir } fn assert_output(output: std::process::Output, fault: bool) { if fault { assert!(output.stdout.is_empty()); } else { assert_eq!( String::from_utf8(output.stdout).unwrap(), "{\"code\":0,\"message\":\"ok\",\"data\":true}\n".repeat(65) ); } assert!(output.stderr.is_empty()); } fn execute( sources: &SourceMap, command: &str, body: &str, setting: &str, fault: Option, timeout: Duration, ) { let ir = entry_ir(sources, fault); let output = llvm_module::execute( &ir, "checked database application", 64, timeout, &[command, body], Some(setting), ); assert_output(output, fault.is_some()); } fn sqlite(fixture: &Fixture<'_>) { execute( &fixture_sources(fixture), "store.item.exercise", fixture.body, SETTING, None, Duration::from_secs(20), ); } #[test] fn database_fixtures_pass_the_existing_checker() { for fixture in [ &CRUD, &BULK, &NULL_EQUALITY, &CURSOR, &SCALARS, &TRANSACTION, &STREAM, &SQL, &NUMERIC_ROLLBACK, &ENCODING_ROLLBACK, &HANDLER_TRANSACTION, &SEED, &EMPTY_BULK, &MIGRATION_INITIAL, &MIGRATION_UPGRADED, &CANCEL_STREAM, ] { checked(&fixture_sources(fixture)); } let fixture = captured_errors(); let app = format!("{ERRORS}{}", fixture.app); checked(&fixture_sources(&Fixture { app: &app, ..fixture })); let app = format!("{ERRORS}{}", TASK_ERROR.app); checked(&fixture_sources(&Fixture { app: &app, ..TASK_ERROR })); let app = format!("{ERRORS}{}", CAPTURED_NOT_FOUND.app); checked(&fixture_sources(&Fixture { app: &app, ..CAPTURED_NOT_FOUND })); checked(&fixture_sources_with_api( &CONSTRAINT_ROLLBACK, "cmd exercise = app.exercise\ncmd inspect = app.inspect", )); checked(&relations()); } #[test] fn model_application_emits_objects_for_all_six_targets() { let sources = fixture_sources(&CRUD); let ir = dever_core::llvm::emit_application(&checked(&sources), &sources).unwrap(); for target in Target::ALL { assert!(!emit_object(&ir, target).unwrap().is_empty()); } } #[test] #[ignore = "requires explicitly prepared database runtime archive; owned SQLite only"] fn sqlite_crud_defaults_owned_rows_and_repeat_entry() { sqlite(&CRUD); } #[test] #[ignore = "requires explicitly prepared database runtime archive; owned SQLite only"] fn sqlite_bulk_upsert_and_static_predicates() { sqlite(&BULK); } #[test] #[ignore = "requires explicitly prepared database runtime archive; owned SQLite only"] fn sqlite_nullable_equality_keeps_present_and_absent_binds() { sqlite(&NULL_EQUALITY); } #[test] #[ignore = "requires explicitly prepared database runtime archive; owned SQLite only"] fn sqlite_cursor_ties_nullable_next_and_offset_page() { sqlite(&CURSOR); } #[test] #[ignore = "requires explicitly prepared database runtime archive; owned SQLite only"] fn sqlite_persistent_scalars_choices_and_private_fields_round_trip() { sqlite(&SCALARS); } #[test] #[ignore = "requires explicitly prepared database runtime archive; owned SQLite only"] fn sqlite_nested_transactions_share_the_one_connection() { sqlite(&TRANSACTION); } #[test] #[ignore = "requires explicitly prepared database runtime archive; owned SQLite only"] fn sqlite_row_stream_full_early_stop_and_close_release_connections() { sqlite(&STREAM); } #[test] #[ignore = "requires explicitly prepared database runtime archive; owned SQLite only"] fn sqlite_static_sql_typed_optional_list_and_record_results() { sqlite(&SQL); } #[test] #[ignore = "requires explicitly prepared database runtime archive; owned SQLite only"] fn sqlite_captured_nested_database_faults_keep_exact_categories() { let fixture = captured_errors(); let app = format!("{ERRORS}{}", fixture.app); sqlite(&Fixture { app: &app, ..fixture }); } #[test] #[ignore = "requires explicitly prepared database runtime archive; owned SQLite only"] fn sqlite_task_wait_keeps_database_failure_identity() { let app = format!("{ERRORS}{}", TASK_ERROR.app); sqlite(&Fixture { app: &app, ..TASK_ERROR }); } #[test] #[ignore = "requires explicitly prepared database runtime archive; owned SQLite only"] fn sqlite_numeric_fault_rolls_back_before_the_next_entry() { execute( &fixture_sources(&NUMERIC_ROLLBACK), "store.item.exercise", NUMERIC_ROLLBACK.body, SETTING, Some(2), Duration::from_secs(20), ); } #[test] #[ignore = "requires explicitly prepared database runtime archive; owned SQLite only"] fn sqlite_command_encoding_failure_rolls_back_before_retry() { execute( &fixture_sources(&ENCODING_ROLLBACK), "store.item.exercise", ENCODING_ROLLBACK.body, SETTING, Some(3), Duration::from_secs(20), ); } #[test] #[ignore = "requires explicitly prepared database runtime archive; owned SQLite only"] fn sqlite_uncaught_constraint_rolls_back_before_the_next_command() { constraint_sequence(SETTING, Duration::from_secs(20)); } #[test] #[ignore = "requires explicitly prepared database runtime archive; owned SQLite only"] fn sqlite_sequential_database_handler_inherits_transaction_context() { sqlite(&HANDLER_TRANSACTION); } #[test] #[ignore = "requires explicitly prepared database runtime archive; owned SQLite only"] fn sqlite_seed_history_is_reused_without_overwriting_business_rows() { sqlite(&SEED); } #[test] #[ignore = "requires explicitly prepared database runtime archive; owned SQLite only"] fn sqlite_empty_create_many_retains_valid_generated_defaults() { sqlite(&EMPTY_BULK); } #[test] #[ignore = "requires explicitly prepared database runtime archive; owned SQLite only"] fn sqlite_related_cycles_join_batch_and_unloaded_values() { execute( &relations(), "store.account.exercise", "{}", SETTING, None, Duration::from_secs(20), ); } #[test] #[ignore = "requires explicitly prepared database runtime archive; owned SQLite only"] fn sqlite_schema_upgrade_preserves_rows_and_replays_migration_and_seed_once() { migration_sequence(SETTING, Duration::from_secs(20)); } fn migration_sequence(setting: &str, timeout: Duration) { let directory = configured_directory(setting); for fixture in [&MIGRATION_INITIAL, &MIGRATION_UPGRADED] { let ir = entry_ir(&fixture_sources(fixture), None); assert_output( llvm_module::execute_in( &directory, &ir, "successive checked Model revisions", 64, timeout, &["store.item.exercise", "{}"], ), false, ); } } fn configured_directory(setting: &str) -> super::temp::TemporaryDirectory { let directory = super::temp::TemporaryDirectory::new(); std::fs::create_dir(directory.path().join("config")).unwrap(); std::fs::write(directory.path().join("config/setting.json"), setting).unwrap(); directory } fn encoding_commit_sequence(setting: &str, timeout: Duration) { let directory = configured_directory(setting); let sources = fixture_sources_with_api( &ENCODING_COMMIT, "long cmd exercise = app.exercise\ncmd inspect = app.inspect", ); for (command, body, fault) in [ ("store.item.exercise", ENCODING_COMMIT.body, Some(3)), ("store.item.inspect", "{}", None), ] { let ir = entry_ir(&sources, fault); assert_output( llvm_module::execute_in( &directory, &ir, "encoding failure preserves committed row", 64, timeout, &[command, body], ), fault.is_some(), ); } } #[test] #[ignore = "requires explicit runtime archive; committed long-entry SQLite stages"] fn sqlite_long_command_preserves_commit_on_encoding_failure() { encoding_commit_sequence(SETTING, Duration::from_secs(20)); } fn constraint_sequence(setting: &str, timeout: Duration) { let directory = configured_directory(setting); let sources = fixture_sources_with_api( &CONSTRAINT_ROLLBACK, "cmd exercise = app.exercise\ncmd inspect = app.inspect", ); for (command, fault) in [ ("store.item.exercise", Some(4)), ("store.item.inspect", None), ] { let ir = entry_ir(&sources, fault); assert_output( llvm_module::execute_in( &directory, &ir, "constraint failure then persisted rollback inspection", 64, timeout, &[command, "{}"], ), fault.is_some(), ); } } #[test] #[ignore = "requires explicitly prepared database runtime archive; owned SQLite only"] fn sqlite_cancelled_row_stream_drains_before_single_connection_fallback() { sqlite(&CANCEL_STREAM); } #[cfg(feature = "runtime-postgres")] fn postgres_case(sources: &SourceMap, command: &str, body: &str, fault: Option) { super::postgres::run_in_isolated_schema("llvm_database", |database| async move { execute( sources, command, body, &database.settings_json(), fault, Duration::from_secs(60), ); Ok(()) }); } #[cfg(feature = "runtime-postgres")] #[test] #[ignore = "requires configured isolated database.postgres_test and an explicit database runtime archive"] fn postgres_checked_models_transactions_streams_and_schema_upgrade() { for fixture in [ &CRUD, &BULK, &NULL_EQUALITY, &CURSOR, &SCALARS, &TRANSACTION, &STREAM, &SQL, &HANDLER_TRANSACTION, &SEED, &EMPTY_BULK, &CANCEL_STREAM, ] { postgres_case( &fixture_sources(fixture), "store.item.exercise", fixture.body, None, ); } for fixture in [CAPTURED_NOT_FOUND, TASK_ERROR] { let app = format!("{ERRORS}{}", fixture.app); postgres_case( &fixture_sources(&Fixture { app: &app, ..fixture }), "store.item.exercise", fixture.body, None, ); } postgres_case( &fixture_sources(&NUMERIC_ROLLBACK), "store.item.exercise", NUMERIC_ROLLBACK.body, Some(2), ); postgres_case(&relations(), "store.account.exercise", "{}", None); super::postgres::run_in_isolated_schema("llvm_database", |database| async move { migration_sequence(&database.settings_json(), Duration::from_secs(60)); Ok(()) }); } #[cfg(feature = "runtime-postgres")] #[test] #[ignore = "requires configured isolated database.postgres_test and an explicit database runtime archive"] fn postgres_command_encoding_failure_rolls_back_before_retry() { postgres_case( &fixture_sources(&ENCODING_ROLLBACK), "store.item.exercise", ENCODING_ROLLBACK.body, Some(3), ); } #[cfg(feature = "runtime-postgres")] #[test] #[ignore = "requires configured isolated database.postgres_test and an explicit database runtime archive"] fn postgres_uncaught_constraint_rolls_back_before_the_next_command() { super::postgres::run_in_isolated_schema("llvm_database", |database| async move { constraint_sequence(&database.settings_json(), Duration::from_secs(60)); Ok(()) }); } }