mod common;
use orbit_orm::{Database, Error, Model, OrbitModel, Query};
async fn connect() -> Option<Database> {
let url = common::url()?;
Some(Database::connect(&url).await.expect("connects"))
}
fn assert_postgres_refuses(result: Result<u64, Error>) {
assert!(
matches!(
result,
Err(Error::Unsupported {
operation: "replace",
driver: "PostgreSQL"
})
),
"expected Unsupported on PostgreSQL, got {result:?}"
);
}
macro_rules! item {
($name:ident, $table:literal) => {
#[derive(Debug, PartialEq, OrbitModel)]
#[orbit(table = $table)]
struct $name {
id: i64,
#[orbit(fillable)]
code: String,
#[orbit(fillable)]
title: String,
#[orbit(fillable)]
note: Option<String>,
}
};
}
item!(Overwritten, "orbit_replace_overwrite");
item!(Inserted, "orbit_replace_insert");
item!(Checked, "orbit_replace_fillable");
async fn setup(db: &Database, table: &str) {
common::drop_tables(db, &[table]).await;
common::exec(
db,
&format!(
"CREATE TABLE `{table}` (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
code VARCHAR(32) NOT NULL,
title VARCHAR(255) NOT NULL,
note VARCHAR(255) NULL DEFAULT 'none',
UNIQUE KEY `{table}_code` (code)
)"
),
)
.await;
}
#[tokio::test]
async fn replace_overwrites_the_row_on_a_unique_key_and_changes_its_id() {
let Some(db) = connect().await else { return };
setup(&db, Overwritten::TABLE).await;
let first = Query::<Overwritten>::table()
.insert(
&db,
&[
("code", "a".into()),
("title", "first".into()),
("note", "kept?".into()),
],
)
.await
.expect("inserts");
let result = Query::<Overwritten>::table()
.replace(&db, &[("code", "a".into()), ("title", "second".into())])
.await;
if common::on_postgres(db.driver()) {
assert_postgres_refuses(result);
common::drop_tables(&db, &[Overwritten::TABLE]).await;
db.close().await;
return;
}
let affected = result.expect("replaces");
if common::on_mysql(db.driver()) {
assert_eq!(affected, 2);
} else {
assert_eq!(affected, 1);
}
let row = Overwritten::query()
.where_eq("code", "a")
.first_or_fail(&db)
.await
.expect("finds the replaced row");
assert_ne!(row.id, first as i64, "the id changes");
assert_eq!(row.title, "second");
assert_eq!(
row.note.as_deref(),
Some("none"),
"a column not supplied takes its default"
);
assert_eq!(Overwritten::query().count(&db).await.expect("counts"), 1);
common::drop_tables(&db, &[Overwritten::TABLE]).await;
db.close().await;
}
#[tokio::test]
async fn replace_inserts_when_no_row_conflicts() {
let Some(db) = connect().await else { return };
setup(&db, Inserted::TABLE).await;
Query::<Inserted>::table()
.insert(&db, &[("code", "a".into()), ("title", "first".into())])
.await
.expect("inserts");
let result = Query::<Inserted>::table()
.replace(&db, &[("code", "b".into()), ("title", "other".into())])
.await;
if common::on_postgres(db.driver()) {
assert_postgres_refuses(result);
common::drop_tables(&db, &[Inserted::TABLE]).await;
db.close().await;
return;
}
let affected = result.expect("replaces");
assert_eq!(affected, 1, "a plain insert affects one row");
assert_eq!(Inserted::query().count(&db).await.expect("counts"), 2);
let kept = Inserted::query()
.where_eq("code", "a")
.first_or_fail(&db)
.await
.expect("the other row is untouched");
assert_eq!(kept.title, "first");
common::drop_tables(&db, &[Inserted::TABLE]).await;
db.close().await;
}
#[tokio::test]
async fn replace_checks_fillable_like_insert() {
let Some(db) = connect().await else { return };
setup(&db, Checked::TABLE).await;
let result = Query::<Checked>::table()
.replace(&db, &[("id", 5.into())])
.await;
if common::on_postgres(db.driver()) {
assert_postgres_refuses(result);
} else {
assert!(matches!(result, Err(Error::NotFillable { .. })));
}
common::drop_tables(&db, &[Checked::TABLE]).await;
db.close().await;
}