summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Midenkov <midenok@gmail.com>2020-12-22 03:33:53 +0300
committerAleksey Midenkov <midenok@gmail.com>2020-12-22 03:33:53 +0300
commit7410ff436e95de09c2f3f0028e7af8b3a043028b (patch)
treef9a64e0059b0f16413cfd4ac11b8263072a0d705
parentd4258f3a8fba0f8972714325c0dc0ca925879b86 (diff)
downloadmariadb-git-7410ff436e95de09c2f3f0028e7af8b3a043028b.tar.gz
MDEV-21138 Assertion `col->ord_part' or `f.col->ord_part' failed in row_build_index_entry_low
First part (row0mysql.cc) fixes ins_node_set_new_row() usage workflow as it is designed to operate on empty row (see row_get_prebuilt_insert_row() for example). Second part (row0ins.cc) fixes duplicate key error in FTS_DOC_ID_INDEX since history rows must not generate entries in that index. We detect FTS_DOC_ID_INDEX by a number of attributes and skip it if the row is historical. Misc fixes: row_build_index_entry_low() does not accept non-NULL tuple for FTS index (subject assertion fails), assertion (index->type != DICT_FTS) adds code understanding. Now as historical_row is copied in row_update_vers_insert() there is no need to copy the row twice: ROW_COPY_POINTERS is used to build historical_row initially. dbug_print_rec() debug functions.
-rw-r--r--mysql-test/suite/versioning/r/delete.result19
-rw-r--r--mysql-test/suite/versioning/r/update.result20
-rw-r--r--mysql-test/suite/versioning/t/delete.test15
-rw-r--r--mysql-test/suite/versioning/t/update.test15
-rw-r--r--storage/innobase/include/row0ins.h1
-rw-r--r--storage/innobase/row/row0ins.cc31
-rw-r--r--storage/innobase/row/row0mysql.cc45
-rw-r--r--storage/innobase/row/row0row.cc2
-rw-r--r--storage/innobase/ut/ut0ut.cc39
9 files changed, 179 insertions, 8 deletions
diff --git a/mysql-test/suite/versioning/r/delete.result b/mysql-test/suite/versioning/r/delete.result
index 5aa239b9cb8..4a0fec639b0 100644
--- a/mysql-test/suite/versioning/r/delete.result
+++ b/mysql-test/suite/versioning/r/delete.result
@@ -130,3 +130,22 @@ ERROR 42S02: Table 'test.xx' doesn't exist
drop procedure pr;
drop trigger tr;
drop table t1;
+#
+# MDEV-21138 Assertion `col->ord_part' or `f.col->ord_part' failed in row_build_index_entry_low
+#
+create table t1 (
+f1 int, f2 text, f3 int, fulltext (f2), key(f1), key(f3),
+foreign key r (f3) references t1 (f1) on delete set null)
+with system versioning engine innodb;
+insert into t1 values (1, repeat('a', 8193), 1), (1, repeat('b', 8193), 1);
+select f1, f3, check_row(row_start, row_end) from t1;
+f1 f3 check_row(row_start, row_end)
+1 1 CURRENT ROW
+1 1 CURRENT ROW
+delete from t1;
+select f1, f3, check_row(row_start, row_end) from t1 for system_time all;
+f1 f3 check_row(row_start, row_end)
+1 1 HISTORICAL ROW
+1 NULL ERROR: row_end == row_start
+1 1 HISTORICAL ROW
+drop table t1;
diff --git a/mysql-test/suite/versioning/r/update.result b/mysql-test/suite/versioning/r/update.result
index cd26c341113..0b239423834 100644
--- a/mysql-test/suite/versioning/r/update.result
+++ b/mysql-test/suite/versioning/r/update.result
@@ -241,6 +241,26 @@ B2 salary
1 2500
drop table t1;
drop table t2;
+# Ensure FTS retains correct history
+create table t1 (
+x int, y text, fulltext (y),
+row_start SYS_DATATYPE as row start invisible,
+row_end SYS_DATATYPE as row end invisible,
+period for system_time (row_start, row_end))
+with system versioning engine innodb;
+insert into t1 values (1, repeat('LONG', 2048));
+update t1 set x= x + 1;
+select x, left(y, 4), length(y), check_row(row_start, row_end) from t1 for system_time all order by x, y;
+x left(y, 4) length(y) check_row(row_start, row_end)
+1 LONG 8192 HISTORICAL ROW
+2 LONG 8192 CURRENT ROW
+update t1 set y= 'SHORT';
+select x, left(y, 4), length(y), check_row(row_start, row_end) from t1 for system_time all order by x, y;
+x left(y, 4) length(y) check_row(row_start, row_end)
+1 LONG 8192 HISTORICAL ROW
+2 LONG 8192 HISTORICAL ROW
+2 SHOR 5 CURRENT ROW
+drop tables t1;
### Issue tempesta-tech/mariadb#365, bug 7 (duplicate of historical row)
create or replace table t1 (a int primary key, b int)
with system versioning engine myisam;
diff --git a/mysql-test/suite/versioning/t/delete.test b/mysql-test/suite/versioning/t/delete.test
index 492463f9395..e2f7240303d 100644
--- a/mysql-test/suite/versioning/t/delete.test
+++ b/mysql-test/suite/versioning/t/delete.test
@@ -94,4 +94,19 @@ drop procedure pr;
drop trigger tr;
drop table t1;
+--echo #
+--echo # MDEV-21138 Assertion `col->ord_part' or `f.col->ord_part' failed in row_build_index_entry_low
+--echo #
+create table t1 (
+ f1 int, f2 text, f3 int, fulltext (f2), key(f1), key(f3),
+ foreign key r (f3) references t1 (f1) on delete set null)
+with system versioning engine innodb;
+insert into t1 values (1, repeat('a', 8193), 1), (1, repeat('b', 8193), 1);
+select f1, f3, check_row(row_start, row_end) from t1;
+delete from t1;
+select f1, f3, check_row(row_start, row_end) from t1 for system_time all;
+
+# cleanup
+drop table t1;
+
--source suite/versioning/common_finish.inc
diff --git a/mysql-test/suite/versioning/t/update.test b/mysql-test/suite/versioning/t/update.test
index 06f81ea9064..f64dcfd0e5c 100644
--- a/mysql-test/suite/versioning/t/update.test
+++ b/mysql-test/suite/versioning/t/update.test
@@ -147,6 +147,21 @@ select @tmp2 = sys_trx_start as B2, salary from t2;
drop table t1;
drop table t2;
+--echo # Ensure FTS retains correct history
+replace_result $sys_datatype_expl SYS_DATATYPE;
+eval create table t1 (
+ x int, y text, fulltext (y),
+ row_start $sys_datatype_expl as row start invisible,
+ row_end $sys_datatype_expl as row end invisible,
+ period for system_time (row_start, row_end))
+with system versioning engine innodb;
+insert into t1 values (1, repeat('LONG', 2048));
+update t1 set x= x + 1;
+select x, left(y, 4), length(y), check_row(row_start, row_end) from t1 for system_time all order by x, y;
+update t1 set y= 'SHORT';
+select x, left(y, 4), length(y), check_row(row_start, row_end) from t1 for system_time all order by x, y;
+drop tables t1;
+
--echo ### Issue tempesta-tech/mariadb#365, bug 7 (duplicate of historical row)
create or replace table t1 (a int primary key, b int)
with system versioning engine myisam;
diff --git a/storage/innobase/include/row0ins.h b/storage/innobase/include/row0ins.h
index 34427dc6dc7..9a16394a052 100644
--- a/storage/innobase/include/row0ins.h
+++ b/storage/innobase/include/row0ins.h
@@ -206,6 +206,7 @@ struct ins_node_t
if this is NULL, entry list should be created
and buffers for sys fields in row allocated */
void vers_update_end(row_prebuilt_t *prebuilt, bool history_row);
+ bool vers_history_row() const; /* true if 'row' is historical */
};
/** Create an insert object.
diff --git a/storage/innobase/row/row0ins.cc b/storage/innobase/row/row0ins.cc
index d581fd21d99..88152841293 100644
--- a/storage/innobase/row/row0ins.cc
+++ b/storage/innobase/row/row0ins.cc
@@ -3566,6 +3566,16 @@ row_ins_get_row_from_select(
}
}
+inline
+bool ins_node_t::vers_history_row() const
+{
+ if (!table->versioned())
+ return false;
+ dfield_t* row_end = dtuple_get_nth_field(row, table->vers_end);
+ return row_end->vers_history_row();
+}
+
+
/***********************************************************//**
Inserts a row to a table.
@return DB_SUCCESS if operation successfully completed, else error
@@ -3604,12 +3614,31 @@ row_ins(
ut_ad(node->state == INS_NODE_INSERT_ENTRIES);
while (node->index != NULL) {
- if (node->index->type != DICT_FTS) {
+ dict_index_t *index = node->index;
+ /*
+ We do not insert history rows into FTS_DOC_ID_INDEX because
+ it is unique by FTS_DOC_ID only and we do not want to add
+ row_end to unique key. Fulltext field works the way new
+ FTS_DOC_ID is created on every fulltext UPDATE, so holding only
+ FTS_DOC_ID for history is enough.
+ */
+ const unsigned type = index->type;
+ if (index->type & DICT_FTS) {
+ } else if (!(type & DICT_UNIQUE) || index->n_uniq > 1
+ || !node->vers_history_row()) {
+
dberr_t err = row_ins_index_entry_step(node, thr);
if (err != DB_SUCCESS) {
DBUG_RETURN(err);
}
+ } else {
+ /* Unique indexes with system versioning must contain
+ the version end column. The only exception is a hidden
+ FTS_DOC_ID_INDEX that InnoDB may create on a hidden or
+ user-created FTS_DOC_ID column. */
+ ut_ad(!strcmp(index->name, FTS_DOC_ID_INDEX_NAME));
+ ut_ad(!strcmp(index->fields[0].name, FTS_DOC_ID_COL_NAME));
}
node->index = dict_table_get_next_index(node->index);
diff --git a/storage/innobase/row/row0mysql.cc b/storage/innobase/row/row0mysql.cc
index dc88b41c6b4..848f60c47d0 100644
--- a/storage/innobase/row/row0mysql.cc
+++ b/storage/innobase/row/row0mysql.cc
@@ -2104,10 +2104,18 @@ row_mysql_unfreeze_data_dictionary(
@param buf Buffer to hold start time data */
void thd_get_query_start_data(THD *thd, char *buf);
-/** Function restores btr_pcur_t, creates dtuple_t from rec_t,
-sets row_end = CURRENT_TIMESTAMP/trx->id, inserts it to a table and updates
-table statistics.
-This is used in UPDATE CASCADE/SET NULL of a system versioning table.
+/** Insert history row when evaluating foreign key referential action.
+
+1. Create new dtuple_t 'row' from node->historical_row;
+2. Update its row_end to current timestamp;
+3. Insert it to a table;
+4. Update table statistics.
+
+This is used in UPDATE CASCADE/SET NULL of a system versioned referenced table.
+
+node->historical_row: dtuple_t containing pointers of row changed by refertial
+action.
+
@param[in] thr current query thread
@param[in] node a node which just updated a row in a foreign table
@return DB_SUCCESS or some error */
@@ -2119,9 +2127,16 @@ static dberr_t row_update_vers_insert(que_thr_t* thr, upd_node_t* node)
dict_table_t* table = node->table;
ut_ad(table->versioned());
- dtuple_t* row = node->historical_row;
- ut_ad(row);
- node->historical_row = NULL;
+ dtuple_t* row;
+ const ulint n_cols = dict_table_get_n_cols(table);
+ const ulint n_v_cols = dict_table_get_n_v_cols(table);
+
+ ut_ad(n_cols == dtuple_get_n_fields(node->historical_row));
+ ut_ad(n_v_cols == dtuple_get_n_v_fields(node->historical_row));
+
+ row = dtuple_create_with_vcol(node->historical_heap, n_cols, n_v_cols);
+
+ dict_table_copy_types(row, table);
ins_node_t* insert_node =
ins_node_create(INS_DIRECT, table, node->historical_heap);
@@ -2134,6 +2149,22 @@ static dberr_t row_update_vers_insert(que_thr_t* thr, upd_node_t* node)
insert_node->common.parent = thr;
ins_node_set_new_row(insert_node, row);
+ ut_ad(n_cols > DATA_N_SYS_COLS);
+ // Exclude DB_ROW_ID, DB_TRX_ID, DB_ROLL_PTR
+ for (ulint i = 0; i < n_cols - DATA_N_SYS_COLS; i++) {
+ dfield_t *dst= dtuple_get_nth_field(row, i);
+ dfield_t *src= dtuple_get_nth_field(node->historical_row, i);
+ dfield_copy(dst, src);
+ }
+
+ for (ulint i = 0; i < n_v_cols; i++) {
+ dfield_t *dst= dtuple_get_nth_v_field(row, i);
+ dfield_t *src= dtuple_get_nth_v_field(node->historical_row, i);
+ dfield_copy(dst, src);
+ }
+
+ node->historical_row = NULL;
+
row_end = dtuple_get_nth_field(row, table->vers_end);
if (dict_table_get_nth_col(table, table->vers_end)->vers_native()) {
mach_write_to_8(row_end_data, trx->id);
diff --git a/storage/innobase/row/row0row.cc b/storage/innobase/row/row0row.cc
index bcb128f2870..f6b7a9f49b6 100644
--- a/storage/innobase/row/row0row.cc
+++ b/storage/innobase/row/row0row.cc
@@ -294,6 +294,8 @@ row_build_index_entry_low(
continue;
}
+ ut_ad(!(index->type & DICT_FTS));
+
if ((!ind_field || ind_field->prefix_len == 0)
&& (!dfield_is_ext(dfield)
|| dict_index_is_clust(index))) {
diff --git a/storage/innobase/ut/ut0ut.cc b/storage/innobase/ut/ut0ut.cc
index 6d2b84625f7..65a2c9356e2 100644
--- a/storage/innobase/ut/ut0ut.cc
+++ b/storage/innobase/ut/ut0ut.cc
@@ -38,6 +38,7 @@ Created 5/11/1994 Heikki Tuuri
#include <string>
#include "log.h"
#include "my_cpu.h"
+#include "rem0rec.h"
/**********************************************************//**
Returns the number of milliseconds since some epoch. The
@@ -627,4 +628,42 @@ fatal_or_error::~fatal_or_error()
} // namespace ib
+#ifndef DBUG_OFF
+const char * dbug_print_rec(const rec_t* rec, const rec_offs* offsets)
+{
+ rec_printer r(rec, offsets);
+ return r.str().c_str();
+}
+
+const char * dbug_print_rec(const rec_t* rec, ulint info, const rec_offs* offsets)
+{
+ rec_printer r(rec, info, offsets);
+ return r.str().c_str();
+}
+
+const char * dbug_print_rec(const dtuple_t* tuple)
+{
+ rec_printer r(tuple);
+ return r.str().c_str();
+}
+
+const char * dbug_print_rec(const dfield_t* field, ulint n)
+{
+ rec_printer r(field, n);
+ return r.str().c_str();
+}
+
+const char * dbug_print_rec(const rec_t* rec, dict_index_t* index)
+{
+ rec_offs offsets_[REC_OFFS_NORMAL_SIZE];
+ rec_offs* offsets = offsets_;
+ rec_offs_init(offsets_);
+ mem_heap_t* tmp_heap = NULL;
+ offsets = rec_get_offsets(rec, index, offsets, true,
+ ULINT_UNDEFINED, &tmp_heap);
+ rec_printer r(rec, offsets);
+ return r.str().c_str();
+}
+#endif /* !DBUG_OFF */
+
#endif /* !UNIV_INNOCHECKSUM */