Skip to content

Commit 0f70e63

Browse files
Simple implementation of insert_or_update and try_insert_or_update (#2678)
Co-authored-by: Zeke Foppa <[email protected]>
1 parent 14ba750 commit 0f70e63

File tree

3 files changed

+40
-2
lines changed

3 files changed

+40
-2
lines changed

crates/bindings/src/table.rs

+32
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,38 @@ impl<Tbl: Table, Col: Index + Column<Table = Tbl>> UniqueColumn<Tbl, Col::ColTyp
388388
let buf = IterBuf::take();
389389
update::<Tbl>(Col::index_id(), new_row, buf)
390390
}
391+
392+
/// Inserts `new_row` into the table, first checking for an existing
393+
/// row with a matching value in the unique column and deleting it if present.
394+
///
395+
/// Be careful: in case of a constraint violation, this method will return Err,
396+
/// but the previous row will be deleted. If you propagate the error, SpacetimeDB will
397+
/// rollback the transaction and the old row will be restored. If you ignore the error,
398+
/// the old row will be lost.
399+
#[track_caller]
400+
#[doc(alias = "try_upsert")]
401+
#[cfg(feature = "unstable")]
402+
pub fn try_insert_or_update(&self, new_row: Tbl::Row) -> Result<Tbl::Row, TryInsertError<Tbl>> {
403+
let col_val = Col::get_field(&new_row);
404+
// If the row doesn't exist, delete will return false, which we ignore.
405+
let _ = self.delete(col_val);
406+
407+
// Then, insert the new row.
408+
let buf = IterBuf::take();
409+
insert::<Tbl>(new_row, buf)
410+
}
411+
412+
/// Inserts `new_row` into the table, first checking for an existing
413+
/// row with a matching value in the unique column and deleting it if present.
414+
///
415+
/// # Panics
416+
/// Panics if either the delete or the insertion would violate a constraint.
417+
#[track_caller]
418+
#[doc(alias = "upsert")]
419+
#[cfg(feature = "unstable")]
420+
pub fn insert_or_update(&self, new_row: Tbl::Row) -> Tbl::Row {
421+
self.try_insert_or_update(new_row).unwrap_or_else(|e| panic!("{e}"))
422+
}
391423
}
392424

393425
pub trait Index {

modules/module-test/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ crate-type = ["cdylib"]
1212
bench = false
1313

1414
[dependencies]
15-
spacetimedb = { path = "../../crates/bindings" }
15+
spacetimedb = { path = "../../crates/bindings", features = ["unstable"] }
1616

1717
anyhow.workspace = true
1818
log.workspace = true

modules/module-test/src/lib.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,13 @@ pub fn test(ctx: &ReducerContext, arg: TestAlias, arg2: TestB, arg3: TestC, arg4
305305

306306
#[spacetimedb::reducer]
307307
pub fn add_player(ctx: &ReducerContext, name: String) -> Result<(), String> {
308-
ctx.db.test_e().try_insert(TestE { id: 0, name })?;
308+
// This always creates a new one because id is auto-incremented.
309+
let inserted = ctx.db.test_e().id().try_insert_or_update(TestE { id: 0, name })?;
310+
311+
// Since the previous one is always inserted, at this point it's always updated by this function.
312+
// This is a no-op, but we can still call it.
313+
ctx.db.test_e().id().try_insert_or_update(inserted)?;
314+
309315
Ok(())
310316
}
311317

0 commit comments

Comments
 (0)