Re: [Maria-developers] df203d1daa1: MDEV-15990: REPLACE on a precise-versioned table returns duplicate key error (ER_DUP_ENTRY)
Hi, Nikita! Sorry, I don't understand what are you fixing and why :( Could you please elaborate? See few minor comments below and the main question at the very end. On Dec 26, Nikita Malyavin wrote:
revision-id: df203d1daa1 (versioning-1.0.5-42-gdf203d1daa1) parent(s): 5e89a0dc25a author: Nikita Malyavin
committer: Nikita Malyavin timestamp: 2018-07-21 00:14:57 +1000 message: MDEV-15990: REPLACE on a precise-versioned table returns duplicate key error (ER_DUP_ENTRY)
* handle zero value in `row_start` `trx_id` * refuse to do more than one versioned insert on the same transaction and same row * generalize versioned cases with and without triggers
[closes tempesta-tech/mariadb#517] [closes tempesta-tech/mariadb#520]
diff --git a/mysql-test/suite/versioning/r/replace.result b/mysql-test/suite/versioning/r/replace.result --- a/mysql-test/suite/versioning/r/replace.result +++ b/mysql-test/suite/versioning/r/replace.result @@ -7,10 +7,31 @@ period for system_time (row_start, row_end) ) with system versioning; insert t values (1, 2); replace t values (1, 3); -select *, current_row(row_end) as current from t for system_time all order by x; -id x current -1 2 0 -1 3 1 +replace t values (1, 3); +# MDEV-15990: REPLACE on a precise-versioned table +# returns duplicate key error (ER_DUP_ENTRY) +replace t values (1, 3), (1, 30), (1, 40); +create table log(s varchar(3)); +create trigger tr1del before delete on t +for each row insert into log values('DEL'); +replace t values (1, 4), (1, 40), (1, 400); +select *, check_row(row_start, row_end), row_start != 0 as start_nonzero +from t for system_time all order by x, row_end; +id x check_row(row_start, row_end) start_nonzero +1 2 HISTORICAL ROW 1 +1 3 HISTORICAL ROW 1 +1 3 HISTORICAL ROW 1 +1 40 HISTORICAL ROW 1 +1 400 CURRENT ROW 1 +select * from log; +s +DEL +DEL +DEL
better to insert OLD.x too, to be able to see where the DEL comes from
+# ensure that row_start is not duplicated +select count(row_start) as empty from t for system_time all +group by row_start having count(row_start) > 1; +empty drop table t; create table t ( id int unique, diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -1969,23 +1952,22 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info) TRG_ACTION_BEFORE, TRUE)) goto before_trg_err;
- if (!table->versioned(VERS_TIMESTAMP)) + if (table->versioned(VERS_TRX_ID) && table->vers_write) + {
can table->vers_write be false here? I suspect it can never be false for REPLACE, so I'd write it as if (table->versioned(VERS_TRX_ID)) { DBUG_ASSERT(table->vers_write);
+ bitmap_set_bit(table->write_set, table->vers_start_field()->field_index); + table->vers_start_field()->store(0, false); + } + + if (!table->versioned()) error= table->file->ha_delete_row(table->record[1]); else - { - store_record(table, record[2]); - restore_record(table, record[1]); - table->vers_update_end(); error= table->file->ha_update_row(table->record[1], table->record[0]); - restore_record(table, record[2]); - } - if (unlikely(error)) + if (unlikely(error && error != HA_ERR_RECORD_IS_THE_SAME)) goto err; - if (!table->versioned(VERS_TIMESTAMP)) + if (likely(!error)) info->deleted++; - else - info->updated++; + error= 0; if (!table->file->has_transactions()) thd->transaction.stmt.modified_non_trans_table= TRUE; if (table->triggers && @@ -1995,6 +1977,17 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info) trg_error= 1; goto ok_or_after_trg_err; } + if (table->versioned(VERS_TIMESTAMP) && table->vers_write)
same
+ { + store_record(table, record[2]); + error = vers_insert_history_row(table);
why are you doing that (insert + goto after_trg_n_copied_inc) instead of going the normal REPLACE code path?
+ restore_record(table, record[2]); + if (unlikely(error)) + goto err; + } + if (table->versioned()) + goto after_trg_n_copied_inc; + /* Let us attempt do write_row() once more */ } } diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index db755077e0b..139c43ced38 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -8916,6 +8916,19 @@ ha_innobase::update_row( upd_t* uvect = row_get_prebuilt_update_vector(m_prebuilt); ib_uint64_t autoinc;
+ bool force_insert= table->versioned_write(VERS_TRX_ID) + && table->vers_start_id() == 0;
hmm, so you're using vers_start_id() == 0 as a special hack to signal REPLACE to the storage engine...
+ + if (force_insert) { + table->vers_start_field()->store(trx->id); + + Field *start= table->vers_start_field(); + trx_id_t old_trx_id= uint8korr(start->ptr_in_record(old_row)); + + /* avoid double versioned insert on the same transaction and same row */ + force_insert= (old_trx_id != trx->id);
okay, I'm confused. What was wrong in the old code? Normally REPLACE is DELETE + INSERT, why was it not enough for InnoDB and you needed some special logic here to handle the REPLACE case?
+ } + /* Build an update vector from the modified fields in the rows (uses m_upd_buf of the handle) */
Regards, Sergei Chief Architect MariaDB and security@mariadb.org
Hi, Sergei!
On Thu, Dec 27, 2018 at 10:19 PM Sergei Golubchik
Hi, Nikita!
Sorry, I don't understand what are you fixing and why :( Could you please elaborate?
Have you already had a look at MDEV-16546 "System versioning setting to allow history modification", which I've also attached to this pull request as a dependency?
If not, You should see it first to get familiar. Here's Alexey's PR: https://github.com/MariaDB/server/pull/804
participants (2)
-
Nikita Malyavin
-
Sergei Golubchik