summaryrefslogtreecommitdiff
path: root/innobase/row/row0ins.c
diff options
context:
space:
mode:
Diffstat (limited to 'innobase/row/row0ins.c')
-rw-r--r--innobase/row/row0ins.c311
1 files changed, 300 insertions, 11 deletions
diff --git a/innobase/row/row0ins.c b/innobase/row/row0ins.c
index c3f912d5f61..ed4df08fcf3 100644
--- a/innobase/row/row0ins.c
+++ b/innobase/row/row0ins.c
@@ -356,6 +356,227 @@ row_ins_dupl_error_with_rec(
}
/*************************************************************************
+Either deletes or sets the referencing columns SQL NULL in a child row.
+Used in ON DELETE ... clause for foreign keys when a parent row is
+deleted. */
+static
+ulint
+row_ins_foreign_delete_or_set_null(
+/*===============================*/
+ /* out: DB_SUCCESS, DB_LOCK_WAIT,
+ or error code */
+ que_thr_t* thr, /* in: query thread whose run_node
+ is an update node */
+ dict_foreign_t* foreign, /* in: foreign key constraint whose
+ type is != 0 */
+ btr_pcur_t* pcur, /* in: cursor placed on a matching
+ index record in the child table */
+ mtr_t* mtr) /* in: mtr holding the latch of pcur
+ page */
+{
+ upd_node_t* node;
+ upd_node_t* cascade;
+ dict_table_t* table = foreign->foreign_table;
+ dict_index_t* index;
+ dict_index_t* clust_index;
+ dtuple_t* ref;
+ mem_heap_t* tmp_heap;
+ rec_t* rec;
+ rec_t* clust_rec;
+ upd_t* update;
+ ulint err;
+ ulint i;
+ char err_buf[1000];
+
+ ut_a(thr && foreign && pcur && mtr);
+
+ node = thr->run_node;
+
+ if (node->cascade_node == NULL) {
+ /* Extend our query graph by creating a child to current
+ update node. The child is used in the cascade or set null
+ operation. */
+
+ node->cascade_heap = mem_heap_create(128);
+ node->cascade_node = row_create_update_node_for_mysql(
+ table, node->cascade_heap);
+ que_node_set_parent(node->cascade_node, node);
+ }
+
+ /* Initialize cascade_node to do the operation we want. Note that we
+ use the SAME cascade node to do all foreign key operations of the
+ SQL DELETE: the table of the cascade node may change if there are
+ several child tables to the table where the delete is done! */
+
+ cascade = node->cascade_node;
+
+ cascade->table = table;
+
+ if (foreign->type == DICT_FOREIGN_ON_DELETE_CASCADE ) {
+ cascade->is_delete = TRUE;
+ } else {
+ cascade->is_delete = FALSE;
+
+ if (foreign->n_fields > cascade->update_n_fields) {
+ /* We have to make the update vector longer */
+
+ cascade->update = upd_create(foreign->n_fields,
+ node->cascade_heap);
+ cascade->update_n_fields = foreign->n_fields;
+ }
+ }
+
+ index = btr_pcur_get_btr_cur(pcur)->index;
+
+ rec = btr_pcur_get_rec(pcur);
+
+ if (index->type & DICT_CLUSTERED) {
+ /* pcur is already positioned in the clustered index of
+ the child table */
+
+ clust_index = index;
+ clust_rec = rec;
+ } else {
+ /* We have to look for the record in the clustered index
+ in the child table */
+
+ clust_index = dict_table_get_first_index(table);
+
+ tmp_heap = mem_heap_create(256);
+
+ ref = row_build_row_ref(ROW_COPY_POINTERS, index, rec,
+ tmp_heap);
+ btr_pcur_open_with_no_init(clust_index, ref,
+ PAGE_CUR_LE, BTR_SEARCH_LEAF,
+ cascade->pcur, 0, mtr);
+
+ mem_heap_free(tmp_heap);
+
+ clust_rec = btr_pcur_get_rec(cascade->pcur);
+ }
+
+ if (!page_rec_is_user_rec(clust_rec)) {
+ fprintf(stderr,
+ "InnoDB: error in cascade of a foreign key op\n"
+ "InnoDB: index %s table %s\n", index->name,
+ index->table->name);
+
+ rec_sprintf(err_buf, 900, rec);
+ fprintf(stderr, "InnoDB: record %s\n", err_buf);
+
+ rec_sprintf(err_buf, 900, clust_rec);
+ fprintf(stderr, "InnoDB: clustered record %s\n", err_buf);
+
+ fprintf(stderr,
+ "InnoDB: Make a detailed bug report and send it\n");
+ fprintf(stderr, "InnoDB: to mysql@lists.mysql.com\n");
+
+ err = DB_SUCCESS;
+
+ goto nonstandard_exit_func;
+ }
+
+ /* Set an X-lock on the row to delete or update in the child table */
+
+ err = lock_table(0, table, LOCK_IX, thr);
+
+ if (err == DB_SUCCESS) {
+ err = lock_clust_rec_read_check_and_lock(0, clust_rec,
+ clust_index, LOCK_X, thr);
+ }
+
+ if (err != DB_SUCCESS) {
+
+ goto nonstandard_exit_func;
+ }
+
+ if (rec_get_deleted_flag(clust_rec)) {
+ /* This can happen if there is a circular reference of
+ rows such that cascading delete comes to delete a row
+ already in the process of being delete marked */
+/*
+ fprintf(stderr,
+ "InnoDB: error 2 in cascade of a foreign key op\n"
+ "InnoDB: index %s table %s\n", index->name,
+ index->table->name);
+
+ rec_sprintf(err_buf, 900, rec);
+ fprintf(stderr, "InnoDB: record %s\n", err_buf);
+
+ rec_sprintf(err_buf, 900, clust_rec);
+ fprintf(stderr, "InnoDB: clustered record %s\n", err_buf);
+
+ fprintf(stderr,
+ "InnoDB: Make a detailed bug report and send it\n");
+ fprintf(stderr, "InnoDB: to mysql@lists.mysql.com\n");
+
+ ut_a(0);
+*/
+ err = DB_SUCCESS;
+
+ goto nonstandard_exit_func;
+ }
+
+ if (foreign->type == DICT_FOREIGN_ON_DELETE_SET_NULL) {
+ /* Build the appropriate update vector which sets
+ foreign->n_fields first fields in rec to SQL NULL */
+
+ update = cascade->update;
+
+ update->info_bits = 0;
+ update->n_fields = foreign->n_fields;
+
+ for (i = 0; i < foreign->n_fields; i++) {
+ (update->fields + i)->field_no
+ = dict_table_get_nth_col_pos(table,
+ dict_index_get_nth_col_no(index, i));
+ (update->fields + i)->exp = NULL;
+ (update->fields + i)->new_val.len = UNIV_SQL_NULL;
+ (update->fields + i)->new_val.data = NULL;
+ (update->fields + i)->extern_storage = FALSE;
+ }
+ }
+
+ /* Store pcur position and initialize or store the cascade node
+ pcur stored position */
+
+ btr_pcur_store_position(pcur, mtr);
+
+ if (index == clust_index) {
+ btr_pcur_copy_stored_position(cascade->pcur, pcur);
+ } else {
+ btr_pcur_store_position(cascade->pcur, mtr);
+ }
+
+ mtr_commit(mtr);
+
+ ut_a(cascade->pcur->rel_pos == BTR_PCUR_ON);
+
+ cascade->state = UPD_NODE_UPDATE_CLUSTERED;
+
+ err = row_update_cascade_for_mysql(thr, cascade,
+ foreign->foreign_table);
+ mtr_start(mtr);
+
+ /* Restore pcur position */
+
+ btr_pcur_restore_position(BTR_SEARCH_LEAF, pcur, mtr);
+
+ return(err);
+
+nonstandard_exit_func:
+
+ btr_pcur_store_position(pcur, mtr);
+
+ mtr_commit(mtr);
+ mtr_start(mtr);
+
+ btr_pcur_restore_position(BTR_SEARCH_LEAF, pcur, mtr);
+
+ return(err);
+}
+
+/*************************************************************************
Sets a shared lock on a record. Used in locking possible duplicate key
records. */
static
@@ -391,7 +612,7 @@ row_ins_check_foreign_constraint(
/* out: DB_SUCCESS, DB_LOCK_WAIT,
DB_NO_REFERENCED_ROW,
or DB_ROW_IS_REFERENCED */
- ibool check_ref,/* in: TRUE If we want to check that
+ ibool check_ref,/* in: TRUE if we want to check that
the referenced table is ok, FALSE if we
want to to check the foreign key table */
dict_foreign_t* foreign,/* in: foreign constraint; NOTE that the
@@ -411,10 +632,30 @@ row_ins_check_foreign_constraint(
ibool moved;
int cmp;
ulint err;
+ ulint i;
mtr_t mtr;
ut_ad(rw_lock_own(&dict_foreign_key_check_lock, RW_LOCK_SHARED));
+ if (thr_get_trx(thr)->check_foreigns == FALSE) {
+ /* The user has suppressed foreign key checks currently for
+ this session */
+
+ return(DB_SUCCESS);
+ }
+
+ /* If any of the foreign key fields in entry is SQL NULL, we
+ suppress the foreign key check: this is compatible with Oracle,
+ for example */
+
+ for (i = 0; i < foreign->n_fields; i++) {
+ if (UNIV_SQL_NULL == dfield_get_len(
+ dtuple_get_nth_field(entry, i))) {
+
+ return(DB_SUCCESS);
+ }
+ }
+
if (check_ref) {
check_table = foreign->referenced_table;
check_index = foreign->referenced_index;
@@ -465,8 +706,8 @@ row_ins_check_foreign_constraint(
goto next_rec;
}
-
- /* Try to place a lock on the index record */
+
+ /* Try to place a lock on the index record */
err = row_ins_set_shared_rec_lock(rec, check_index, thr);
@@ -488,11 +729,21 @@ row_ins_check_foreign_constraint(
if (check_ref) {
err = DB_SUCCESS;
+
+ break;
+ } else if (foreign->type != 0) {
+ err =
+ row_ins_foreign_delete_or_set_null(
+ thr, foreign, &pcur, &mtr);
+
+ if (err != DB_SUCCESS) {
+
+ break;
+ }
} else {
err = DB_ROW_IS_REFERENCED;
+ break;
}
-
- break;
}
}
@@ -521,6 +772,8 @@ next_rec:
}
}
+ btr_pcur_close(&pcur);
+
mtr_commit(&mtr);
/* Restore old value */
@@ -548,6 +801,10 @@ row_ins_check_foreign_constraints(
{
dict_foreign_t* foreign;
ulint err;
+ trx_t* trx;
+ ibool got_s_lock = FALSE;
+
+ trx = thr_get_trx(thr);
foreign = UT_LIST_GET_FIRST(table->foreign_list);
@@ -556,16 +813,26 @@ row_ins_check_foreign_constraints(
if (foreign->referenced_table == NULL) {
dict_table_get(foreign->referenced_table_name,
- thr_get_trx(thr));
+ trx);
}
- rw_lock_s_lock(&dict_foreign_key_check_lock);
+ if (!trx->has_dict_foreign_key_check_lock) {
+ got_s_lock = TRUE;
+
+ rw_lock_s_lock(&dict_foreign_key_check_lock);
+
+ trx->has_dict_foreign_key_check_lock = TRUE;
+ }
err = row_ins_check_foreign_constraint(TRUE, foreign,
table, index, entry, thr);
+ if (got_s_lock) {
- rw_lock_s_unlock(&dict_foreign_key_check_lock);
+ rw_lock_s_unlock(&dict_foreign_key_check_lock);
+ trx->has_dict_foreign_key_check_lock = FALSE;
+ }
+
if (err != DB_SUCCESS) {
return(err);
}
@@ -591,6 +858,8 @@ row_ins_scan_sec_index_for_duplicate(
dtuple_t* entry, /* in: index entry */
que_thr_t* thr) /* in: query thread */
{
+ ulint n_unique;
+ ulint i;
int cmp;
ulint n_fields_cmp;
rec_t* rec;
@@ -599,6 +868,20 @@ row_ins_scan_sec_index_for_duplicate(
ibool moved;
mtr_t mtr;
+ n_unique = dict_index_get_n_unique(index);
+
+ /* If the secondary index is unique, but one of the fields in the
+ n_unique first fields is NULL, a unique key violation cannot occur,
+ since we define NULL != NULL in this case */
+
+ for (i = 0; i < n_unique; i++) {
+ if (UNIV_SQL_NULL == dfield_get_len(
+ dtuple_get_nth_field(entry, i))) {
+
+ return(DB_SUCCESS);
+ }
+ }
+
mtr_start(&mtr);
/* Store old value on n_fields_cmp */
@@ -839,13 +1122,14 @@ row_ins_index_entry_low(
ulint n_ext_vec,/* in: number of fields in ext_vec */
que_thr_t* thr) /* in: query thread */
{
- btr_cur_t cursor;
+ btr_cur_t cursor;
+ ulint ignore_sec_unique = 0;
ulint modify = 0; /* remove warning */
rec_t* insert_rec;
rec_t* rec;
ulint err;
ulint n_unique;
- big_rec_t* big_rec = NULL;
+ big_rec_t* big_rec = NULL;
mtr_t mtr;
log_free_check();
@@ -858,8 +1142,13 @@ row_ins_index_entry_low(
the function will return in both low_match and up_match of the
cursor sensible values */
+ if (!(thr_get_trx(thr)->check_unique_secondary)) {
+ ignore_sec_unique = BTR_IGNORE_SEC_UNIQUE;
+ }
+
btr_cur_search_to_nth_level(index, 0, entry, PAGE_CUR_LE,
- mode | BTR_INSERT, &cursor, 0, &mtr);
+ mode | BTR_INSERT | ignore_sec_unique,
+ &cursor, 0, &mtr);
if (cursor.flag == BTR_CUR_INSERT_TO_IBUF) {
/* The insertion was made to the insert buffer already during