Commit 7410ff43 authored by Aleksey Midenkov's avatar Aleksey Midenkov

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.
parent d4258f3a
......@@ -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;
......@@ -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;
......
......@@ -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
......@@ -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;
......
......@@ -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.
......
......@@ -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);
......
......@@ -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);
......
......@@ -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))) {
......
......@@ -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 */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment