mirror of
https://github.com/MariaDB/server.git
synced 2025-07-30 16:24:05 +03:00
IB: (0.4) foreign keys for versioned tables (#58)
This commit is contained in:
158
mysql-test/suite/versioning/t/foreign.test
Normal file
158
mysql-test/suite/versioning/t/foreign.test
Normal file
@ -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;
|
@ -7520,9 +7520,6 @@ ER_SYS_START_AND_SYS_END_SAME
|
|||||||
ER_GENERATED_FIELD_CANNOT_BE_SET_BY_USER
|
ER_GENERATED_FIELD_CANNOT_BE_SET_BY_USER
|
||||||
eng "Generated field for System Versioning 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
|
ER_UPDATE_INFO_WITH_SYSTEM_VERSIONING
|
||||||
eng "Rows matched: %ld Changed: %ld Inserted: %ld Warnings: %ld"
|
eng "Rows matched: %ld Changed: %ld Inserted: %ld Warnings: %ld"
|
||||||
|
|
||||||
|
@ -4621,20 +4621,6 @@ handler *mysql_create_frm_image(THD *thd,
|
|||||||
|
|
||||||
if (create_info->versioned())
|
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> 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,
|
if(vers_prepare_keys(thd, create_info, alter_info, key_info,
|
||||||
*key_count))
|
*key_count))
|
||||||
goto err;
|
goto err;
|
||||||
|
@ -8960,6 +8960,7 @@ calc_row_difference(
|
|||||||
buf = (byte*) upd_buff;
|
buf = (byte*) upd_buff;
|
||||||
|
|
||||||
prebuilt->upd_node->versioned = false;
|
prebuilt->upd_node->versioned = false;
|
||||||
|
prebuilt->upd_node->vers_delete = false;
|
||||||
|
|
||||||
for (i = 0; i < table->s->fields; i++) {
|
for (i = 0; i < table->s->fields; i++) {
|
||||||
field = table->field[i];
|
field = table->field[i];
|
||||||
|
@ -583,6 +583,7 @@ struct upd_node_t{
|
|||||||
UPD_NODE_NO_ORD_CHANGE and
|
UPD_NODE_NO_ORD_CHANGE and
|
||||||
UPD_NODE_NO_SIZE_CHANGE, ORed */
|
UPD_NODE_NO_SIZE_CHANGE, ORed */
|
||||||
bool versioned;/* update is versioned */
|
bool versioned;/* update is versioned */
|
||||||
|
bool vers_delete;/* versioned delete */
|
||||||
/*----------------------*/
|
/*----------------------*/
|
||||||
/* Local storage for this graph node */
|
/* Local storage for this graph node */
|
||||||
ulint state; /*!< node execution state */
|
ulint state; /*!< node execution state */
|
||||||
|
@ -1569,6 +1569,79 @@ private:
|
|||||||
ulint& counter;
|
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
|
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
|
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);
|
cmp = cmp_dtuple_rec(entry, rec, offsets);
|
||||||
|
|
||||||
if (cmp == 0) {
|
if (cmp == 0) {
|
||||||
if (rec_get_deleted_flag(rec,
|
if (DICT_TF2_FLAG_IS_SET(check_table, DICT_TF2_VERSIONED)) {
|
||||||
rec_offs_comp(offsets))) {
|
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(
|
err = row_ins_set_shared_rec_lock(
|
||||||
LOCK_ORDINARY, block,
|
LOCK_ORDINARY, block,
|
||||||
rec, check_index, offsets, thr);
|
rec, check_index, offsets, thr);
|
||||||
|
@ -1957,43 +1957,6 @@ row_update_for_mysql_using_upd_graph(
|
|||||||
prebuilt->clust_pcur);
|
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<byte*>(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);
|
ut_a(node->pcur->rel_pos == BTR_PCUR_ON);
|
||||||
|
|
||||||
/* MySQL seems to call rnd_pos before updating each row it
|
/* 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;
|
thr->fk_cascade_depth = 0;
|
||||||
|
|
||||||
run_again:
|
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<byte*>(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) {
|
if (thr->fk_cascade_depth == 1 && trx->dict_operation_lock_mode == 0) {
|
||||||
got_s_lock = true;
|
got_s_lock = true;
|
||||||
row_mysql_freeze_data_dictionary(trx);
|
row_mysql_freeze_data_dictionary(trx);
|
||||||
|
@ -269,6 +269,7 @@ row_upd_check_references_constraints(
|
|||||||
|
|
||||||
if (foreign->referenced_index == index
|
if (foreign->referenced_index == index
|
||||||
&& (node->is_delete
|
&& (node->is_delete
|
||||||
|
|| node->vers_delete
|
||||||
|| row_upd_changes_first_fields_binary(
|
|| row_upd_changes_first_fields_binary(
|
||||||
entry, index, node->update,
|
entry, index, node->update,
|
||||||
foreign->n_fields))) {
|
foreign->n_fields))) {
|
||||||
@ -381,6 +382,7 @@ wsrep_row_upd_check_foreign_constraints(
|
|||||||
|
|
||||||
if (foreign->foreign_index == index
|
if (foreign->foreign_index == index
|
||||||
&& (node->is_delete
|
&& (node->is_delete
|
||||||
|
|| node->vers_delete
|
||||||
|| row_upd_changes_first_fields_binary(
|
|| row_upd_changes_first_fields_binary(
|
||||||
entry, index, node->update,
|
entry, index, node->update,
|
||||||
foreign->n_fields))) {
|
foreign->n_fields))) {
|
||||||
|
Reference in New Issue
Block a user