@@ -1347,6 +1347,8 @@ impl MutTxId {
1347
1347
if let ( _, Some ( commit_ptr) ) =
1348
1348
unsafe { Table :: find_same_row ( commit_table, tx_table, tx_blob_store, tx_row_ptr, tx_row_hash) }
1349
1349
{
1350
+ // (insert_undelete)
1351
+ // -----------------------------------------------------
1350
1352
// If `row` was already present in the committed state,
1351
1353
// either this is a set-semantic duplicate,
1352
1354
// or the row is marked as deleted, so we will undelete it
@@ -1547,6 +1549,8 @@ impl MutTxId {
1547
1549
// In the former case, the index must not have been removed in the transaction.
1548
1550
// As it's unlikely that an index was added in this transaction,
1549
1551
// we begin by checking the committed state.
1552
+ let commit_blob_store = & self . committed_state_write_lock . blob_store ;
1553
+ let mut old_commit_del_ptr = None ;
1550
1554
let err = ' failed_rev_ins: {
1551
1555
let tx_row_ptr = if tx_removed_index {
1552
1556
break ' failed_rev_ins IndexError :: NotFound ( index_id) . into ( ) ;
@@ -1561,7 +1565,13 @@ impl MutTxId {
1561
1565
. committed_state_write_lock
1562
1566
. get_index_by_id_with_table ( table_id, index_id)
1563
1567
. and_then ( |index| find_old_row ( tx_row_ref, index) . 0 . map ( |ptr| ( index, ptr) ) )
1564
- . filter ( |( _, ptr) | !del_table. contains ( * ptr) )
1568
+ . filter ( |& ( _, ptr) | {
1569
+ // Was committed row previously deleted in this TX?
1570
+ let deleted = del_table. contains ( ptr) ;
1571
+ // If so, remember it in case it was identical to the new row.
1572
+ old_commit_del_ptr = deleted. then_some ( ptr) ;
1573
+ !deleted
1574
+ } )
1565
1575
{
1566
1576
// 1. Ensure the index is unique.
1567
1577
// 2. Ensure the new row doesn't violate any other committed state unique indices.
@@ -1581,7 +1591,6 @@ impl MutTxId {
1581
1591
if unsafe { Table :: eq_row_in_page ( commit_table, old_ptr, tx_table, tx_row_ptr) } {
1582
1592
// SAFETY: `self.is_row_present(tx_row_ptr)` holds, as noted in 3.
1583
1593
unsafe { tx_table. delete_internal_skip_pointer_map ( tx_blob_store, tx_row_ptr) } ;
1584
- let commit_blob_store = & self . committed_state_write_lock . blob_store ;
1585
1594
// SAFETY: `commit_table.is_row_present(old_ptr)` holds, as noted in 2.
1586
1595
let old_row_ref = unsafe { commit_table. get_row_ref_unchecked ( commit_blob_store, old_ptr) } ;
1587
1596
return Ok ( ( cols_to_gen, old_row_ref, update_flags) ) ;
@@ -1638,7 +1647,6 @@ impl MutTxId {
1638
1647
if unsafe { Table :: eq_row_in_page ( commit_table, old_ptr, tx_table, tx_row_ptr) } {
1639
1648
// SAFETY: `self.is_row_present(tx_row_ptr)` holds, as noted in 3.
1640
1649
unsafe { tx_table. delete_internal_skip_pointer_map ( tx_blob_store, tx_row_ptr) } ;
1641
- let commit_blob_store = & self . committed_state_write_lock . blob_store ;
1642
1650
// SAFETY: `commit_table.is_row_present(old_ptr)` holds, as noted in 2.
1643
1651
let old_row_ref =
1644
1652
unsafe { commit_table. get_row_ref_unchecked ( commit_blob_store, old_ptr) } ;
@@ -1665,8 +1673,42 @@ impl MutTxId {
1665
1673
// SAFETY: `self.is_row_present(tx_row_ptr)` and `self.is_row_present(old_ptr)` both hold
1666
1674
// as we've deleted neither.
1667
1675
// In particular, the `write_gen_val_to_col` call does not remove the row.
1668
- unsafe { tx_table. confirm_update ( tx_blob_store, tx_row_ptr, old_ptr, blob_bytes) }
1669
- . map_err ( IndexError :: UniqueConstraintViolation ) ?
1676
+ let tx_row_ptr =
1677
+ unsafe { tx_table. confirm_update ( tx_blob_store, tx_row_ptr, old_ptr, blob_bytes) }
1678
+ . map_err ( IndexError :: UniqueConstraintViolation ) ?;
1679
+
1680
+ if let Some ( old_commit_del_ptr) = old_commit_del_ptr {
1681
+ let commit_table =
1682
+ commit_table. expect ( "previously found a row in `commit_table`, so there should be one" ) ;
1683
+ // If we have an identical deleted row in the committed state,
1684
+ // we need to undeleted it, just like in `Self::insert`.
1685
+ // The same note (`insert_undelete`) there re. MVCC applies here as well.
1686
+ //
1687
+ // SAFETY:
1688
+ // 1. `tx_table` is derived from `commit_table` so they have the same layouts.
1689
+ // 2. `old_commit_del_ptr` was found in an index of `commit_table`.
1690
+ // 3. we just inserted `tx_row_ptr` into `tx_table`, so we know it is valid.
1691
+ if unsafe { Table :: eq_row_in_page ( commit_table, old_commit_del_ptr, tx_table, tx_row_ptr) }
1692
+ {
1693
+ // It is important that we `confirm_update` first,
1694
+ // as we must ensure that undeleting the row causes no tx state conflict.
1695
+ tx_table
1696
+ . delete ( tx_blob_store, tx_row_ptr, |_| ( ) )
1697
+ . expect ( "Failed to delete a row we just inserted" ) ;
1698
+
1699
+ // Undelete.
1700
+ del_table. remove ( old_commit_del_ptr) ;
1701
+
1702
+ // Return the undeleted committed state row.
1703
+ // SAFETY: `commit_table.is_row_present(old_commit_del_ptr)` holds.
1704
+ let old_row_ref = unsafe {
1705
+ commit_table. get_row_ref_unchecked ( commit_blob_store, old_commit_del_ptr)
1706
+ } ;
1707
+ return Ok ( ( cols_to_gen, old_row_ref, update_flags) ) ;
1708
+ }
1709
+ }
1710
+
1711
+ tx_row_ptr
1670
1712
}
1671
1713
_ => unreachable ! ( "Invalid SquashedOffset for RowPointer: {:?}" , old_ptr) ,
1672
1714
}
0 commit comments