MDEV-15990 REPLACE on a precise-versioned table returns ER_DUP_ENTRY

We had a protection against it, by allowing versioned delete if:
trx->id != table->vers_start_id()

For replace this check fails: replace calls ha_delete_row(record[2]), but
table->vers_start_id() returns the value from record[0], which is irrelevant.

The same problem hits Field::is_max, which may have checked the wrong record.

Fix:
* Refactor Field::is_max to optionally accept a pointer as an argument.
* Refactor vers_start_id and vers_end_id to always accept a pointer to the
record. there is a difference with is_max is that is_max accepts the pointer to
the
field data, rather than to the record.

Method val_int() would be too effortful to refactor to accept the argument, so
instead the value in record is fetched directly, like it is done in
Field_longlong.
This commit is contained in:
Nikita Malyavin 2025-01-07 19:37:58 +01:00
commit 6353a80ef5
12 changed files with 80 additions and 40 deletions

View file

@ -8623,7 +8623,7 @@ ha_innobase::update_row(
if (error == DB_SUCCESS && m_prebuilt->versioned_write
/* Multiple UPDATE of same rows in single transaction create
historical rows only once. */
&& trx->id != table->vers_start_id()) {
&& trx->id != table->vers_start_id(new_row)) {
/* UPDATE is not used by ALTER TABLE. Just precaution
as we don't need history generation for ALTER TABLE. */
ut_ad(thd_sql_command(m_user_thd) != SQLCOM_ALTER_TABLE);
@ -8738,8 +8738,9 @@ ha_innobase::delete_row(
/* This is a delete */
m_prebuilt->upd_node->is_delete = table->versioned_write(VERS_TRX_ID)
&& table->vers_end_field()->is_max()
&& trx->id != table->vers_start_id()
&& table->vers_end_field()->is_max(
table->vers_end_field()->ptr_in_record(record))
&& trx->id != table->vers_start_id(record)
? VERSIONED_DELETE
: PLAIN_DELETE;
trx->fts_next_doc_id = 0;