mod common;
use common::TestItem;
use rust_ef::db_context::{DbContext, DbContextOptionsBuilder};
use rust_ef::db_set::IDbSet;
use rust_ef_sqlite::DbContextOptionsBuilderExt as _;
fn make_ctx() -> DbContext {
let mut builder = DbContextOptionsBuilder::new();
builder.use_sqlite_in_memory();
let options = builder.build();
DbContext::from_options(&options).expect("ctx")
}
#[tokio::test]
async fn partial_modify_sets_only_dirty_column() {
let mut ctx = make_ctx();
ctx.set::<TestItem>();
ctx.ensure_created().await.expect("ensure_created");
ctx.set::<TestItem>().add(TestItem {
id: 0,
name: "Alpha".into(),
value: 1.0,
});
ctx.save_changes().await.expect("seed save");
let items = ctx.set::<TestItem>().query().to_list().await.expect("load");
let item = items.into_iter().next().expect("one row");
ctx.set::<TestItem>().clear_entries();
ctx.set::<TestItem>().attach(item);
{
let entry = ctx.set::<TestItem>().tracked_entries_mut().next().unwrap();
entry.name = "Alpha2".into();
}
ctx.set::<TestItem>().detect_changes();
let states: Vec<_> = ctx
.set::<TestItem>()
.entries_with_state()
.into_iter()
.map(|(_, s)| s)
.collect();
assert!(
states.contains(&rust_ef::entity::EntityState::Modified),
"entry should be Modified after detect_changes, got {states:?}"
);
ctx.save_changes().await.expect("partial modify save");
let rows = ctx
.set::<TestItem>()
.query()
.to_list()
.await
.expect("reload");
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].name, "Alpha2");
assert!(
(rows[0].value - 1.0).abs() < f64::EPSILON,
"value should be unchanged, got {}",
rows[0].value
);
}
#[tokio::test]
async fn full_modify_when_no_detection() {
let mut ctx = make_ctx();
ctx.set::<TestItem>();
ctx.ensure_created().await.expect("ensure_created");
ctx.set::<TestItem>().add(TestItem {
id: 0,
name: "Gamma".into(),
value: 5.0,
});
ctx.save_changes().await.expect("seed save");
let items = ctx.set::<TestItem>().query().to_list().await.expect("load");
let mut item = items.into_iter().next().expect("one row");
item.name = "Gamma2".into();
item.value = 99.0;
ctx.set::<TestItem>().clear_entries();
ctx.set::<TestItem>().update(item);
ctx.save_changes().await.expect("full modify save");
let rows = ctx
.set::<TestItem>()
.query()
.to_list()
.await
.expect("reload");
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].name, "Gamma2");
assert!(
(rows[0].value - 99.0).abs() < f64::EPSILON,
"value should be 99.0, got {}",
rows[0].value
);
}
#[tokio::test]
async fn batch_insert_backfills_auto_increment_pk() {
let mut ctx = make_ctx();
ctx.set::<TestItem>();
ctx.ensure_created().await.expect("ensure_created");
let names = ["Alpha", "Bravo", "Charlie", "Delta"];
for name in names {
ctx.set::<TestItem>().add(TestItem {
id: 0,
name: name.into(),
value: 1.0,
});
}
ctx.save_changes().await.expect("batch insert save");
let ids: Vec<i64> = ctx
.set::<TestItem>()
.tracked_entries()
.map(|e| e.id as i64)
.collect();
assert_eq!(
ids.len(),
names.len(),
"all added entities should be tracked"
);
assert!(
ids.iter().all(|&id| id > 0),
"all PKs should be backfilled (non-zero), got {ids:?}"
);
let mut sorted = ids.clone();
sorted.sort_unstable();
assert_eq!(
sorted,
vec![1, 2, 3, 4],
"PKs should be contiguous starting from 1, got {ids:?}"
);
}