From a22cbc453f6140ef0588328f3b4b72553286ad8e Mon Sep 17 00:00:00 2001 From: kevgs Date: Tue, 1 Nov 2016 19:51:44 +0300 Subject: [PATCH] IB: (0.4) foreign keys for versioned tables (#58) --- mysql-test/suite/versioning/t/foreign.test | 158 +++++++++++++++++++++ sql/share/errmsg-utf8.txt | 3 - sql/sql_table.cc | 14 -- storage/innobase/handler/ha_innodb.cc | 1 + storage/innobase/include/row0upd.h | 1 + storage/innobase/row/row0ins.cc | 93 +++++++++++- storage/innobase/row/row0mysql.cc | 81 ++++++----- storage/innobase/row/row0upd.cc | 2 + 8 files changed, 297 insertions(+), 56 deletions(-) create mode 100644 mysql-test/suite/versioning/t/foreign.test diff --git a/mysql-test/suite/versioning/t/foreign.test b/mysql-test/suite/versioning/t/foreign.test new file mode 100644 index 00000000000..3f1a14e58aa --- /dev/null +++ b/mysql-test/suite/versioning/t/foreign.test @@ -0,0 +1,158 @@ +-- source include/have_innodb.inc + +################# +# Test RESTRICT # +################# + +create table parent( + id int unique key +) engine innodb; + +create table child( + parent_id int, + foreign key(parent_id) references parent(id) + on delete restrict + on update restrict +) engine innodb with system versioning; + +insert into parent values(1); +insert into child values(1); + +-- error ER_ROW_IS_REFERENCED_2 +delete from parent where id = 1; +delete from child where parent_id = 1; +delete from parent where id = 1; + +insert into parent values(1); +insert into child values(1); +-- error ER_ROW_IS_REFERENCED_2 +update parent set id=id+1; +delete from child; +update parent set id=id+1; +select * from child for system_time from timestamp '1-1-1' to timestamp now(6); + +drop table child; +drop table parent; + +############################################## +# Test when clustered index is a foreign key # +############################################## + +create table parent( + id int(10) unsigned unique key +) engine innodb; + +create table child( + parent_id int(10) unsigned primary key, + foreign key(parent_id) references parent(id) +) engine innodb with system versioning; + +insert into parent values(1); +insert into child values(1); + +-- error ER_ROW_IS_REFERENCED_2 +delete from parent where id = 1; + +drop table child; +drop table parent; + +################ +# Test CASCADE # +################ + +create table parent( + id int unique key +) engine innodb; + +create table child( + parent_id int, + foreign key(parent_id) references parent(id) + on delete cascade + on update cascade +) engine innodb with system versioning; + +insert into parent values(1); +insert into child values(1); + +delete from parent where id = 1; +select * from child; +select * from child for system_time from timestamp '1-1-1' to timestamp now(6); + +insert into parent values(1); +insert into child values(1); +update parent set id=id+1; +select * from child; +select * from child for system_time from timestamp '1-1-1' to timestamp now(6); + +drop table child; +drop table parent; + +################# +# Test SET NULL # +################# + +create table parent( + id int unique key +) engine innodb; + +create table child( + parent_id int, + foreign key(parent_id) references parent(id) + on delete set null + on update set null +) engine innodb with system versioning; + +insert into parent values(1); +insert into child values(1); +delete from child; +insert into child values(1); + +delete from parent where id = 1; +select * from child; +select * from child for system_time from timestamp '1-1-1' to timestamp now(6); +delete from child; + +insert into parent values(1); +insert into child values(1); +update parent set id=id+1; +select * from child; +select * from child for system_time from timestamp '1-1-1' to timestamp now(6); + +drop table child; +drop table parent; + +########################### +# Parent table is foreign # +########################### + +create or replace table parent( + id int unique key +) engine innodb with system versioning; + +create or replace table child( + parent_id int, + foreign key(parent_id) references parent(id) +) engine innodb; + +insert into parent values(1); +insert into child values(1); +-- error ER_ROW_IS_REFERENCED_2 +delete from parent; +-- error ER_ROW_IS_REFERENCED_2 +update parent set id=2; + +delete from child; +delete from parent; + +-- error ER_NO_REFERENCED_ROW_2 +insert into child values(1); + +insert into parent values(1); +insert into child values(1); +-- error ER_ROW_IS_REFERENCED_2 +delete from parent; +-- error ER_ROW_IS_REFERENCED_2 +update parent set id=2; + +drop table child; +drop table parent; diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 167be4d10fc..e215395c3c2 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -7520,9 +7520,6 @@ ER_SYS_START_AND_SYS_END_SAME ER_GENERATED_FIELD_CANNOT_BE_SET_BY_USER eng "Generated field for System Versioning cannot be set by user" -ER_FOREIGN_KEY_ON_SYSTEM_VERSIONED - eng "Foreign key clause is not yet supported in conjunction with system versioning" - ER_UPDATE_INFO_WITH_SYSTEM_VERSIONING eng "Rows matched: %ld Changed: %ld Inserted: %ld Warnings: %ld" diff --git a/sql/sql_table.cc b/sql/sql_table.cc index b92b5f5c6c4..ae697b61eb1 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -4621,20 +4621,6 @@ handler *mysql_create_frm_image(THD *thd, if (create_info->versioned()) { - // FIXME: This test doesn't detect foreign key relationship on the side of - // parent table and System Time support will not work correctly for such - // table either. But this cannot be implemented without changes to innodb - // that are postponed for later time. - List_iterator_fast key_iterator(alter_info->key_list); - Key *key; - while ((key= key_iterator++)) - { - if (key->type == Key::FOREIGN_KEY) - { - my_error(ER_FOREIGN_KEY_ON_SYSTEM_VERSIONED, MYF(0)); - goto err; - } - } if(vers_prepare_keys(thd, create_info, alter_info, key_info, *key_count)) goto err; diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 8f97169adef..18a8a9018ec 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -8960,6 +8960,7 @@ calc_row_difference( buf = (byte*) upd_buff; prebuilt->upd_node->versioned = false; + prebuilt->upd_node->vers_delete = false; for (i = 0; i < table->s->fields; i++) { field = table->field[i]; diff --git a/storage/innobase/include/row0upd.h b/storage/innobase/include/row0upd.h index d035e346c1e..1775eb2e179 100644 --- a/storage/innobase/include/row0upd.h +++ b/storage/innobase/include/row0upd.h @@ -583,6 +583,7 @@ struct upd_node_t{ UPD_NODE_NO_ORD_CHANGE and UPD_NODE_NO_SIZE_CHANGE, ORed */ bool versioned;/* update is versioned */ + bool vers_delete;/* versioned delete */ /*----------------------*/ /* Local storage for this graph node */ ulint state; /*!< node execution state */ diff --git a/storage/innobase/row/row0ins.cc b/storage/innobase/row/row0ins.cc index 604656c8944..0a13be95d98 100644 --- a/storage/innobase/row/row0ins.cc +++ b/storage/innobase/row/row0ins.cc @@ -1569,6 +1569,79 @@ private: ulint& counter; }; +/*********************************************************************//** +Reads sys_trx_end field from clustered index row. +@return trx_id_t */ +static +trx_id_t +row_ins_get_sys_trx_end( +/*===================================*/ + const rec_t *rec, /*!< in: clustered row */ + ulint *offsets, /*!< in: offsets */ + dict_index_t *index) /*!< in: clustered index */ +{ + ut_a(dict_index_is_clust(index)); + + ulint len; + ulint nfield = dict_col_get_clust_pos( + &index->table->cols[index->table->vers_row_end], index); + const byte *field = rec_get_nth_field(rec, offsets, nfield, &len); + ut_a(len == 8); + return(mach_read_from_8(field)); +} + +/*********************************************************************//** +Performs search at clustered index and returns sys_trx_end if row was found. +@return DB_SUCCESS, DB_NO_REFERENCED_ROW */ +static +dberr_t +row_ins_search_sys_trx_end( +/*=======================*/ + dict_index_t *index, /*!< in: index of record */ + const rec_t *rec, /*!< in: record */ + trx_id_t *end_trx_id) /*!< out: end_trx_id */ +{ + rec_t *clust_rec; + bool found = false; + mem_heap_t *clust_heap = mem_heap_create(256); + ulint clust_offsets_[REC_OFFS_NORMAL_SIZE]; + ulint *clust_offsets = clust_offsets_; + rec_offs_init(clust_offsets_); + btr_pcur_t clust_pcur; + + dict_index_t *clust_index = dict_table_get_first_index(index->table); + + dtuple_t *ref = + row_build_row_ref(ROW_COPY_POINTERS, index, rec, clust_heap); + + mtr_t clust_mtr; + mtr_start(&clust_mtr); + btr_pcur_open_on_user_rec(clust_index, ref, PAGE_CUR_GE, + BTR_SEARCH_LEAF, &clust_pcur, &clust_mtr); + + if (!btr_pcur_is_on_user_rec(&clust_pcur)) + goto not_found; + + clust_rec = btr_pcur_get_rec(&clust_pcur); + clust_offsets = rec_get_offsets(clust_rec, clust_index, clust_offsets, + ULINT_UNDEFINED, &clust_heap); + if (0 != cmp_dtuple_rec(ref, clust_rec, clust_offsets)) + goto not_found; + + *end_trx_id = row_ins_get_sys_trx_end( + clust_rec, clust_offsets, clust_index); + found = true; +not_found: + mtr_commit(&clust_mtr); + btr_pcur_close(&clust_pcur); + mem_heap_free(clust_heap); + if (!found) { + fprintf(stderr, "InnoDB: foreign constraints: secondary index is out of sync\n"); + return(DB_NO_REFERENCED_ROW); + } + return(DB_SUCCESS); +} + /***************************************************************//** Checks if foreign key constraint fails for an index entry. Sets shared locks which lock either the success or the failure of the constraint. NOTE that @@ -1745,8 +1818,24 @@ row_ins_check_foreign_constraint( cmp = cmp_dtuple_rec(entry, rec, offsets); if (cmp == 0) { - if (rec_get_deleted_flag(rec, - rec_offs_comp(offsets))) { + if (DICT_TF2_FLAG_IS_SET(check_table, DICT_TF2_VERSIONED)) { + trx_id_t end_trx_id = 0; + + if (dict_index_is_clust(check_index)) { + end_trx_id = + row_ins_get_sys_trx_end( + rec, offsets, check_index); + } else if (row_ins_search_sys_trx_end( + check_index, rec, &end_trx_id) != + DB_SUCCESS) { + break; + } + + if (end_trx_id != TRX_ID_MAX) + continue; + } + + if (rec_get_deleted_flag(rec, rec_offs_comp(offsets))) { err = row_ins_set_shared_rec_lock( LOCK_ORDINARY, block, rec, check_index, offsets, thr); diff --git a/storage/innobase/row/row0mysql.cc b/storage/innobase/row/row0mysql.cc index 7a4f2719ba5..60b221a4e4a 100644 --- a/storage/innobase/row/row0mysql.cc +++ b/storage/innobase/row/row0mysql.cc @@ -1957,43 +1957,6 @@ row_update_for_mysql_using_upd_graph( prebuilt->clust_pcur); } - if (DICT_TF2_FLAG_IS_SET(node->table, DICT_TF2_VERSIONED) && - (node->is_delete || node->versioned)) - { - /* System Versioning: modify update vector to set - sys_trx_start (or sys_trx_end in case of DELETE) - to current trx_id. */ - upd_t* uvect = node->update; - upd_field_t* ufield; - dict_col_t* col; - unsigned col_idx; - if (node->is_delete) { - ufield = &uvect->fields[0]; - uvect->n_fields = 0; - node->is_delete = false; - col_idx = table->vers_row_end; - } else { - ut_ad(uvect->n_fields < node->table->n_cols); - ufield = &uvect->fields[uvect->n_fields]; - col_idx = table->vers_row_start; - } - col = &table->cols[col_idx]; - UNIV_MEM_INVALID(ufield, sizeof *ufield); - ufield->field_no = dict_col_get_clust_pos(col, clust_index); - ufield->orig_len = 0; - ufield->exp = NULL; - - static const ulint fsize = sizeof(trx_id_t); - byte* buf = static_cast(mem_heap_alloc(node->heap, fsize)); - mach_write_to_8(buf, trx->id); - dfield_t* dfield = &ufield->new_val; - dfield_set_data(dfield, buf, fsize); - dict_col_copy_type(col, &dfield->type); - - uvect->n_fields++; - ut_ad(node->in_mysql_interface); // otherwise needs to recalculate node->cmpl_info - } - ut_a(node->pcur->rel_pos == BTR_PCUR_ON); /* MySQL seems to call rnd_pos before updating each row it @@ -2029,6 +1992,50 @@ row_update_for_mysql_using_upd_graph( thr->fk_cascade_depth = 0; run_again: + if (DICT_TF2_FLAG_IS_SET(node->table, DICT_TF2_VERSIONED) && + (node->is_delete || node->versioned)) + { + /* System Versioning: modify update vector to set + sys_trx_start (or sys_trx_end in case of DELETE) + to current trx_id. */ + dict_table_t* table = node->table; + dict_index_t* clust_index = dict_table_get_first_index(table); + upd_t* uvect = node->update; + upd_field_t* ufield; + dict_col_t* col; + unsigned col_idx; + if (node->is_delete) { + ufield = &uvect->fields[0]; + uvect->n_fields = 0; + node->is_delete = false; + node->vers_delete = true; + col_idx = table->vers_row_end; + } else { + ut_ad(uvect->n_fields < table->n_cols); + ufield = &uvect->fields[uvect->n_fields]; + col_idx = table->vers_row_start; + } + col = &table->cols[col_idx]; + UNIV_MEM_INVALID(ufield, sizeof *ufield); + { + ulint field_no = dict_col_get_clust_pos(col, clust_index); + ut_ad(field_no != ULINT_UNDEFINED); + ufield->field_no = field_no; + } + ufield->orig_len = 0; + ufield->exp = NULL; + + static const ulint fsize = sizeof(trx_id_t); + byte* buf = static_cast(mem_heap_alloc(node->update->heap, fsize)); + mach_write_to_8(buf, trx->id); + dfield_t* dfield = &ufield->new_val; + dfield_set_data(dfield, buf, fsize); + dict_col_copy_type(col, &dfield->type); + + uvect->n_fields++; + ut_ad(node->in_mysql_interface); // otherwise needs to recalculate node->cmpl_info + } + if (thr->fk_cascade_depth == 1 && trx->dict_operation_lock_mode == 0) { got_s_lock = true; row_mysql_freeze_data_dictionary(trx); diff --git a/storage/innobase/row/row0upd.cc b/storage/innobase/row/row0upd.cc index cc86a8c419b..513a2a15d6c 100644 --- a/storage/innobase/row/row0upd.cc +++ b/storage/innobase/row/row0upd.cc @@ -269,6 +269,7 @@ row_upd_check_references_constraints( if (foreign->referenced_index == index && (node->is_delete + || node->vers_delete || row_upd_changes_first_fields_binary( entry, index, node->update, foreign->n_fields))) { @@ -381,6 +382,7 @@ wsrep_row_upd_check_foreign_constraints( if (foreign->foreign_index == index && (node->is_delete + || node->vers_delete || row_upd_changes_first_fields_binary( entry, index, node->update, foreign->n_fields))) {