Skip to content

Commit 40beb1a

Browse files
committed
Implemented UniqueColumn::try_update(). Resolves #2128
1 parent 7726fe8 commit 40beb1a

2 files changed

Lines changed: 64 additions & 7 deletions

File tree

crates/bindings/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,9 +378,9 @@ ctx.db.person().ssn()
378378
- [`UniqueColumn::find`]
379379
- [`UniqueColumn::delete`]
380380
- [`UniqueColumn::update`]
381-
<!-- TODO: "current limitations" try_update -->
381+
- [`UniqueColumn::try_update`]
382382

383-
Notice that updating a row is only possible if a row has a unique column -- there is no `update` method in the base [`Table`] trait. SpacetimeDB has no notion of rows having an "identity" aside from their unique / primary keys.
383+
Notice that updating a row is only possible if a row has a unique column -- there is no `update` or `try_update` method in the base [`Table`] trait. SpacetimeDB has no notion of rows having an "identity" aside from their unique / primary keys.
384384

385385
The `#[primary_key]` annotation implies `#[unique]` annotation, but avails additional methods in the [client]-side SDKs.
386386

crates/bindings/src/table.rs

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,48 @@ impl<Tbl: Table> From<TryInsertError<Tbl>> for String {
259259
}
260260
}
261261

262+
/// The error type returned from [`UniqueColumn::try_update()`], signalling a constraint violation.
263+
pub enum TryUpdateError<Tbl: Table> {
264+
/// A [`UniqueConstraintViolation`].
265+
///
266+
/// Returned from [`Table::try_update`] if an attempted update
267+
/// has the same value in a unique column as an already-present row
268+
/// (excluding the update key itself).
269+
UniqueConstraintViolation(Tbl::UniqueConstraintViolation),
270+
}
271+
272+
impl<Tbl: Table> fmt::Debug for TryUpdateError<Tbl> {
273+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
274+
write!(f, "TryUpdateError::<{}>::", Tbl::TABLE_NAME)?;
275+
match self {
276+
Self::UniqueConstraintViolation(e) => fmt::Debug::fmt(e, f),
277+
}
278+
}
279+
}
280+
281+
impl<Tbl: Table> fmt::Display for TryUpdateError<Tbl> {
282+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283+
write!(f, "update error on table `{}`:", Tbl::TABLE_NAME)?;
284+
match self {
285+
Self::UniqueConstraintViolation(e) => fmt::Display::fmt(e, f),
286+
}
287+
}
288+
}
289+
290+
impl<Tbl: Table> std::error::Error for TryUpdateError<Tbl> {
291+
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
292+
Some(match self {
293+
Self::UniqueConstraintViolation(e) => e,
294+
})
295+
}
296+
}
297+
298+
impl<Tbl: Table> From<TryUpdateError<Tbl>> for String {
299+
fn from(err: TryUpdateError<Tbl>) -> Self {
300+
err.to_string()
301+
}
302+
}
303+
262304
#[doc(hidden)]
263305
pub trait MaybeError<E = Self>: std::error::Error + Send + Sync + Sized + 'static {
264306
fn get() -> Option<Self>;
@@ -388,8 +430,17 @@ impl<Tbl: Table, Col: Index + Column<Table = Tbl>> UniqueColumn<Tbl, Col::ColTyp
388430
/// # Panics
389431
/// Panics if no row was previously present with the matching value in the unique column,
390432
/// or if either the delete or the insertion would violate a constraint.
433+
/// Callers which intend to handle constraint violation errors should instead use [`Self::try_update`].
391434
#[track_caller]
392-
pub fn update(&self, new_row: Tbl::Row) -> Tbl::Row
435+
pub fn update(&self, new_row: Tbl::Row) -> Tbl::Row {
436+
self.try_update(new_row).unwrap_or_else(|e| panic!("{e}"))
437+
}
438+
439+
/// Counterpart to [`Self::update`] which allows handling failed updates.
440+
///
441+
/// This method returns an `Err` when the update fails rather than panicking.
442+
#[track_caller]
443+
pub fn try_update(&self, new_row: Tbl::Row) -> Result<Tbl::Row, TryUpdateError<Tbl>>
393444
where
394445
Col: PrimaryKey,
395446
{
@@ -1374,7 +1425,7 @@ fn insert<T: Table>(mut row: T::Row, mut buf: IterBuf) -> Result<T::Row, TryInse
13741425

13751426
/// Update a row of type `T` to `row` using the index identified by `index_id`.
13761427
#[track_caller]
1377-
fn update<T: Table>(index_id: IndexId, mut row: T::Row, mut buf: IterBuf) -> T::Row {
1428+
fn update<T: Table>(index_id: IndexId, mut row: T::Row, mut buf: IterBuf) -> Result<T::Row, TryUpdateError<T>> {
13781429
let table_id = T::table_id();
13791430
// Encode the row as bsatn into the buffer `buf`.
13801431
buf.clear();
@@ -1387,9 +1438,15 @@ fn update<T: Table>(index_id: IndexId, mut row: T::Row, mut buf: IterBuf) -> T::
13871438
T::integrate_generated_columns(&mut row, gen_cols);
13881439
row
13891440
});
1390-
1391-
// TODO(centril): introduce a `TryUpdateError`.
1392-
res.unwrap_or_else(|e| panic!("unexpected update error: {e}"))
1441+
res.map_err(|e| {
1442+
let err = match e {
1443+
sys::Errno::UNIQUE_ALREADY_EXISTS => {
1444+
T::UniqueConstraintViolation::get().map(TryUpdateError::UniqueConstraintViolation)
1445+
}
1446+
_ => None,
1447+
};
1448+
err.unwrap_or_else(|| panic!("unexpected update error: {e}"))
1449+
})
13931450
}
13941451

13951452
/// A table iterator which yields values of the `TableType` corresponding to the table.

0 commit comments

Comments
 (0)