summaryrefslogtreecommitdiff
path: root/innobase/row
diff options
context:
space:
mode:
Diffstat (limited to 'innobase/row')
-rw-r--r--innobase/row/row0ins.c245
-rw-r--r--innobase/row/row0mysql.c68
-rw-r--r--innobase/row/row0sel.c17
-rw-r--r--innobase/row/row0upd.c88
4 files changed, 381 insertions, 37 deletions
diff --git a/innobase/row/row0ins.c b/innobase/row/row0ins.c
index d0a5cfec604..990ef99b2a4 100644
--- a/innobase/row/row0ins.c
+++ b/innobase/row/row0ins.c
@@ -322,13 +322,129 @@ row_ins_clust_index_entry_by_modify(
}
/*************************************************************************
-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. */
+Returns TRUE if in a cascaded update/delete an ancestor node of node
+updates table. */
+static
+ibool
+row_ins_cascade_ancestor_updates_table(
+/*===================================*/
+ /* out: TRUE if an ancestor updates table */
+ que_node_t* node, /* in: node in a query graph */
+ dict_table_t* table) /* in: table */
+{
+ que_node_t* parent;
+ upd_node_t* upd_node;
+
+ parent = que_node_get_parent(node);
+
+ while (que_node_get_type(parent) == QUE_NODE_UPDATE) {
+
+ upd_node = parent;
+
+ if (upd_node->table == table) {
+
+ return(TRUE);
+ }
+
+ parent = que_node_get_parent(parent);
+
+ ut_a(parent);
+ }
+
+ return(FALSE);
+}
+
+/**********************************************************************
+Calculates the update vector node->cascade->update for a child table in
+a cascaded update. */
+static
+ulint
+row_ins_cascade_calc_update_vec(
+/*============================*/
+ /* out: number of fields in the
+ calculated update vector; the value
+ can also be 0 if no foreign key
+ fields changed */
+ upd_node_t* node, /* in: update node of the parent
+ table */
+ dict_foreign_t* foreign) /* in: foreign key constraint whose
+ type is != 0 */
+{
+ upd_node_t* cascade = node->cascade_node;
+ dict_table_t* table = foreign->foreign_table;
+ dict_index_t* index = foreign->foreign_index;
+ upd_t* update;
+ upd_field_t* ufield;
+ dict_table_t* parent_table;
+ dict_index_t* parent_index;
+ upd_t* parent_update;
+ upd_field_t* parent_ufield;
+ ulint n_fields_updated;
+ ulint parent_field_no;
+ ulint i;
+ ulint j;
+
+ ut_a(node && foreign && cascade && table && index);
+
+ /* Calculate the appropriate update vector which will set the fields
+ in the child index record to the same value as the referenced index
+ record will get in the update. */
+
+ parent_table = node->table;
+ ut_a(parent_table == foreign->referenced_table);
+ parent_index = foreign->referenced_index;
+ parent_update = node->update;
+
+ update = cascade->update;
+
+ update->info_bits = 0;
+ update->n_fields = foreign->n_fields;
+
+ n_fields_updated = 0;
+
+ for (i = 0; i < foreign->n_fields; i++) {
+
+ parent_field_no = dict_table_get_nth_col_pos(
+ parent_table,
+ dict_index_get_nth_col_no(
+ parent_index, i));
+
+ for (j = 0; j < parent_update->n_fields; j++) {
+ parent_ufield = parent_update->fields + j;
+
+ if (parent_ufield->field_no == parent_field_no) {
+
+ /* A field in the parent index record is
+ updated. Let us make the update vector
+ field for the child table. */
+
+ ufield = update->fields + n_fields_updated;
+
+ ufield->field_no =
+ dict_table_get_nth_col_pos(table,
+ dict_index_get_nth_col_no(index, i));
+ ufield->exp = NULL;
+ ufield->new_val = parent_ufield->new_val;
+ ufield->extern_storage = FALSE;
+
+ n_fields_updated++;
+ }
+ }
+ }
+
+ update->n_fields = n_fields_updated;
+
+ return(n_fields_updated);
+}
+
+/*************************************************************************
+Perform referential actions or checks when a parent row is deleted or updated
+and the constraint had an ON DELETE or ON UPDATE condition which was not
+RESTRICT. */
static
ulint
-row_ins_foreign_delete_or_set_null(
-/*===============================*/
+row_ins_foreign_check_on_constraint(
+/*================================*/
/* out: DB_SUCCESS, DB_LOCK_WAIT,
or error code */
que_thr_t* thr, /* in: query thread whose run_node
@@ -378,15 +494,34 @@ row_ins_foreign_delete_or_set_null(
ut_strlen(table->name) + 1);
node = thr->run_node;
- ut_a(que_node_get_type(node) == QUE_NODE_UPDATE);
+ if (node->is_delete && 0 == (foreign->type &
+ (DICT_FOREIGN_ON_DELETE_CASCADE
+ | DICT_FOREIGN_ON_DELETE_SET_NULL))) {
- if (!node->is_delete) {
- /* According to SQL-92 an UPDATE with respect to FOREIGN
- KEY constraints is not semantically equivalent to a
- DELETE + INSERT. Therefore we do not perform any action
- here and consequently the child rows would be left
- orphaned if we would let the UPDATE happen. Thus we return
- an error. */
+ /* No action is defined: return a foreign key error if
+ NO ACTION is not specified */
+
+ if (foreign->type & DICT_FOREIGN_ON_DELETE_NO_ACTION) {
+
+ return(DB_SUCCESS);
+ }
+
+ return(DB_ROW_IS_REFERENCED);
+ }
+
+ if (!node->is_delete && 0 == (foreign->type &
+ (DICT_FOREIGN_ON_UPDATE_CASCADE
+ | DICT_FOREIGN_ON_UPDATE_SET_NULL))) {
+
+ /* This is an UPDATE */
+
+ /* No action is defined: return a foreign key error if
+ NO ACTION is not specified */
+
+ if (foreign->type & DICT_FOREIGN_ON_UPDATE_NO_ACTION) {
+
+ return(DB_SUCCESS);
+ }
return(DB_ROW_IS_REFERENCED);
}
@@ -411,7 +546,10 @@ row_ins_foreign_delete_or_set_null(
cascade->table = table;
- if (foreign->type == DICT_FOREIGN_ON_DELETE_CASCADE ) {
+ cascade->foreign = foreign;
+
+ if (node->is_delete
+ && (foreign->type & DICT_FOREIGN_ON_DELETE_CASCADE)) {
cascade->is_delete = TRUE;
} else {
cascade->is_delete = FALSE;
@@ -425,8 +563,30 @@ row_ins_foreign_delete_or_set_null(
}
}
+ /* We do not allow cyclic cascaded updating of the same
+ table. Check that we are not updating the same table which
+ is already being modified in this cascade chain. We have to
+ check this because the modification of the indexes of a
+ 'parent' table may still be incomplete, and we must avoid
+ seeing the indexes of the parent table in an inconsistent
+ state! In this way we also prevent possible infinite
+ update loops caused by cyclic cascaded updates. */
+
+ if (!cascade->is_delete
+ && row_ins_cascade_ancestor_updates_table(cascade, table)) {
+
+ /* We do not know if this would break foreign key
+ constraints, but play safe and return an error */
+
+ err = DB_ROW_IS_REFERENCED;
+
+ goto nonstandard_exit_func;
+ }
+
index = btr_pcur_get_btr_cur(pcur)->index;
+ ut_a(index == foreign->foreign_index);
+
rec = btr_pcur_get_rec(pcur);
if (index->type & DICT_CLUSTERED) {
@@ -520,7 +680,11 @@ row_ins_foreign_delete_or_set_null(
goto nonstandard_exit_func;
}
- if (foreign->type == DICT_FOREIGN_ON_DELETE_SET_NULL) {
+ if ((node->is_delete
+ && (foreign->type & DICT_FOREIGN_ON_DELETE_SET_NULL))
+ || (!node->is_delete
+ && (foreign->type & DICT_FOREIGN_ON_UPDATE_SET_NULL))) {
+
/* Build the appropriate update vector which sets
foreign->n_fields first fields in rec to SQL NULL */
@@ -540,6 +704,26 @@ row_ins_foreign_delete_or_set_null(
}
}
+ if (!node->is_delete
+ && (foreign->type & DICT_FOREIGN_ON_UPDATE_CASCADE)) {
+
+ /* Build the appropriate update vector which sets changing
+ foreign->n_fields first fields in rec to new values */
+
+ row_ins_cascade_calc_update_vec(node, foreign);
+
+ if (cascade->update->n_fields == 0) {
+
+ /* The update does not change any columns referred
+ to in this foreign key constraint: no need to do
+ anything */
+
+ err = DB_SUCCESS;
+
+ goto nonstandard_exit_func;
+ }
+ }
+
/* Store pcur position and initialize or store the cascade node
pcur stored position */
@@ -629,6 +813,7 @@ row_ins_check_foreign_constraint(
dtuple_t* entry, /* in: index entry for index */
que_thr_t* thr) /* in: query thread */
{
+ upd_node_t* upd_node;
dict_table_t* check_table;
dict_index_t* check_index;
ulint n_fields_cmp;
@@ -665,6 +850,30 @@ run_again:
}
}
+ if (que_node_get_type(thr->run_node) == QUE_NODE_UPDATE) {
+ upd_node = thr->run_node;
+
+ if (!(upd_node->is_delete) && upd_node->foreign == foreign) {
+ /* If a cascaded update is done as defined by a
+ foreign key constraint, do not check that
+ constraint for the child row. In ON UPDATE CASCADE
+ the update of the parent row is only half done when
+ we come here: if we would check the constraint here
+ for the child row it would fail.
+
+ A QUESTION remains: if in the child table there are
+ several constraints which refer to the same parent
+ table, we should merge all updates to the child as
+ one update? And the updates can be contradictory!
+ Currently we just perform the update associated
+ with each foreign key constraint, one after
+ another, and the user has problems predicting in
+ which order they are performed. */
+
+ return(DB_SUCCESS);
+ }
+ }
+
if (check_ref) {
check_table = foreign->referenced_table;
check_index = foreign->referenced_index;
@@ -774,8 +983,12 @@ run_again:
break;
} else if (foreign->type != 0) {
+ /* There is an ON UPDATE or ON DELETE
+ condition: check them in a separate
+ function */
+
err =
- row_ins_foreign_delete_or_set_null(
+ row_ins_foreign_check_on_constraint(
thr, foreign, &pcur, &mtr);
if (err != DB_SUCCESS) {
diff --git a/innobase/row/row0mysql.c b/innobase/row/row0mysql.c
index 9ce86b5d487..ba56b3071cd 100644
--- a/innobase/row/row0mysql.c
+++ b/innobase/row/row0mysql.c
@@ -313,6 +313,7 @@ row_create_prebuilt(
prebuilt = mem_heap_alloc(heap, sizeof(row_prebuilt_t));
prebuilt->magic_n = ROW_PREBUILT_ALLOCATED;
+ prebuilt->magic_n2 = ROW_PREBUILT_ALLOCATED;
prebuilt->table = table;
@@ -378,11 +379,12 @@ row_prebuilt_free(
{
ulint i;
- if (prebuilt->magic_n != ROW_PREBUILT_ALLOCATED) {
+ if (prebuilt->magic_n != ROW_PREBUILT_ALLOCATED
+ || prebuilt->magic_n2 != ROW_PREBUILT_ALLOCATED) {
fprintf(stderr,
- "InnoDB: Error: trying to free a corrupt\n"
- "InnoDB: table handle. Magic n %lu, table name %s\n",
- prebuilt->magic_n, prebuilt->table->name);
+"InnoDB: Error: trying to free a corrupt\n"
+"InnoDB: table handle. Magic n %lu, magic n2 %lu, table name %s\n",
+ prebuilt->magic_n, prebuilt->magic_n2, prebuilt->table->name);
mem_analyze_corruption((byte*)prebuilt);
@@ -390,6 +392,7 @@ row_prebuilt_free(
}
prebuilt->magic_n = ROW_PREBUILT_FREED;
+ prebuilt->magic_n2 = ROW_PREBUILT_FREED;
btr_pcur_free_for_mysql(prebuilt->pcur);
btr_pcur_free_for_mysql(prebuilt->clust_pcur);
@@ -420,7 +423,23 @@ row_prebuilt_free(
for (i = 0; i < MYSQL_FETCH_CACHE_SIZE; i++) {
if (prebuilt->fetch_cache[i] != NULL) {
- mem_free(prebuilt->fetch_cache[i]);
+
+ if ((ROW_PREBUILT_FETCH_MAGIC_N !=
+ mach_read_from_4((prebuilt->fetch_cache[i]) - 4))
+ || (ROW_PREBUILT_FETCH_MAGIC_N !=
+ mach_read_from_4((prebuilt->fetch_cache[i])
+ + prebuilt->mysql_row_len))) {
+ fprintf(stderr,
+ "InnoDB: Error: trying to free a corrupt\n"
+ "InnoDB: fetch buffer.\n");
+
+ mem_analyze_corruption(
+ prebuilt->fetch_cache[i]);
+
+ ut_a(0);
+ }
+
+ mem_free((prebuilt->fetch_cache[i]) - 4);
}
}
@@ -1435,7 +1454,7 @@ int
row_create_index_for_mysql(
/*=======================*/
/* out: error number or DB_SUCCESS */
- dict_index_t* index, /* in: index defintion */
+ dict_index_t* index, /* in: index definition */
trx_t* trx) /* in: transaction handle */
{
ind_node_t* node;
@@ -1444,7 +1463,9 @@ row_create_index_for_mysql(
ulint namelen;
ulint keywordlen;
ulint err;
-
+ ulint i;
+ ulint j;
+
ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX));
ut_ad(mutex_own(&(dict_sys->mutex)));
ut_ad(trx->mysql_thread_id == os_thread_get_curr_id());
@@ -1465,6 +1486,31 @@ row_create_index_for_mysql(
return(DB_SUCCESS);
}
+ /* Check that the same column does not appear twice in the index.
+ InnoDB assumes this in its algorithms, e.g., update of an index
+ entry */
+
+ for (i = 0; i < dict_index_get_n_fields(index); i++) {
+ for (j = 0; j < i; j++) {
+ if (0 == ut_strcmp(
+ dict_index_get_nth_field(index, j)->name,
+ dict_index_get_nth_field(index, i)->name)) {
+
+ ut_print_timestamp(stderr);
+
+ fprintf(stderr,
+" InnoDB: Error: column %s appears twice in index %s of table %s\n"
+"InnoDB: This is not allowed in InnoDB.\n",
+ dict_index_get_nth_field(index, i)->name,
+ index->name, index->table_name);
+
+ err = DB_COL_APPEARS_TWICE_IN_INDEX;
+
+ goto error_handling;
+ }
+ }
+ }
+
heap = mem_heap_create(512);
trx->dict_operation = TRUE;
@@ -1477,11 +1523,13 @@ row_create_index_for_mysql(
SESS_COMM_EXECUTE, 0));
que_run_threads(thr);
- err = trx->error_state;
+ err = trx->error_state;
+
+ que_graph_free((que_t*) que_node_get_parent(thr));
+error_handling:
if (err != DB_SUCCESS) {
/* We have special error handling here */
- ut_a(err == DB_OUT_OF_FILE_SPACE);
trx->error_state = DB_SUCCESS;
@@ -1491,8 +1539,6 @@ row_create_index_for_mysql(
trx->error_state = DB_SUCCESS;
}
-
- que_graph_free((que_t*) que_node_get_parent(thr));
trx->op_info = (char *) "";
diff --git a/innobase/row/row0sel.c b/innobase/row/row0sel.c
index a3744089258..ea5b3020c08 100644
--- a/innobase/row/row0sel.c
+++ b/innobase/row/row0sel.c
@@ -2415,6 +2415,7 @@ row_sel_push_cache_row_for_mysql(
row_prebuilt_t* prebuilt, /* in: prebuilt struct */
rec_t* rec) /* in: record to push */
{
+ byte* buf;
ulint i;
ut_ad(prebuilt->n_fetch_cached < MYSQL_FETCH_CACHE_SIZE);
@@ -2424,8 +2425,18 @@ row_sel_push_cache_row_for_mysql(
/* Allocate memory for the fetch cache */
for (i = 0; i < MYSQL_FETCH_CACHE_SIZE; i++) {
- prebuilt->fetch_cache[i] = mem_alloc(
- prebuilt->mysql_row_len);
+
+ /* A user has reported memory corruption in these
+ buffers in Linux. Put magic numbers there to help
+ to track a possible bug. */
+
+ buf = mem_alloc(prebuilt->mysql_row_len + 8);
+
+ prebuilt->fetch_cache[i] = buf + 4;
+
+ mach_write_to_4(buf, ROW_PREBUILT_FETCH_MAGIC_N);
+ mach_write_to_4(buf + 4 + prebuilt->mysql_row_len,
+ ROW_PREBUILT_FETCH_MAGIC_N);
}
}
@@ -2437,7 +2448,7 @@ row_sel_push_cache_row_for_mysql(
prebuilt->n_fetch_cached++;
}
-
+
/*************************************************************************
Tries to do a shortcut to fetch a clustered index record with a unique key,
using the hash index if possible (not always). We assume that the search
diff --git a/innobase/row/row0upd.c b/innobase/row/row0upd.c
index 1231c94da63..64569bf3f96 100644
--- a/innobase/row/row0upd.c
+++ b/innobase/row/row0upd.c
@@ -71,6 +71,20 @@ the x-latch freed? The most efficient way for performing a
searched delete is obviously to keep the x-latch for several
steps of query graph execution. */
+/***************************************************************
+Checks if an update vector changes some of the first fields of an index
+record. */
+static
+ibool
+row_upd_changes_first_fields(
+/*=========================*/
+ /* out: TRUE if changes */
+ dtuple_t* entry, /* in: old value of index entry */
+ dict_index_t* index, /* in: index of entry */
+ upd_t* update, /* in: update vector for the row */
+ ulint n); /* in: how many first fields to check */
+
+
/*************************************************************************
Checks if index currently is mentioned as a referenced index in a foreign
key constraint. */
@@ -132,6 +146,7 @@ ulint
row_upd_check_references_constraints(
/*=================================*/
/* out: DB_SUCCESS or an error code */
+ upd_node_t* node, /* in: row update node */
btr_pcur_t* pcur, /* in: cursor positioned on a record; NOTE: the
cursor position is lost in this function! */
dict_table_t* table, /* in: table in question */
@@ -173,7 +188,16 @@ row_upd_check_references_constraints(
foreign = UT_LIST_GET_FIRST(table->referenced_list);
while (foreign) {
- if (foreign->referenced_index == index) {
+ /* Note that we may have an update which updates the index
+ record, but does NOT update the first fields which are
+ referenced in a foreign key constraint. Then the update does
+ NOT break the constraint. */
+
+ if (foreign->referenced_index == index
+ && (node->is_delete
+ || row_upd_changes_first_fields(entry, index,
+ node->update, foreign->n_fields))) {
+
if (foreign->foreign_table == NULL) {
dict_table_get(foreign->foreign_table_name,
trx);
@@ -189,10 +213,9 @@ row_upd_check_references_constraints(
}
/* NOTE that if the thread ends up waiting for a lock
- we will release dict_operation_lock
- temporarily! But the counter on the table
- protects 'foreign' from being dropped while the check
- is running. */
+ we will release dict_operation_lock temporarily!
+ But the counter on the table protects 'foreign' from
+ being dropped while the check is running. */
err = row_ins_check_foreign_constraint(FALSE, foreign,
table, index, entry, thr);
@@ -255,6 +278,7 @@ upd_node_create(
node->index = NULL;
node->update = NULL;
+ node->foreign = NULL;
node->cascade_heap = NULL;
node->cascade_node = NULL;
@@ -953,6 +977,53 @@ row_upd_changes_some_index_ord_field_binary(
return(FALSE);
}
+/***************************************************************
+Checks if an update vector changes some of the first fields of an index
+record. */
+static
+ibool
+row_upd_changes_first_fields(
+/*=========================*/
+ /* out: TRUE if changes */
+ dtuple_t* entry, /* in: index entry */
+ dict_index_t* index, /* in: index of entry */
+ upd_t* update, /* in: update vector for the row */
+ ulint n) /* in: how many first fields to check */
+{
+ upd_field_t* upd_field;
+ dict_field_t* ind_field;
+ dict_col_t* col;
+ ulint n_upd_fields;
+ ulint col_pos;
+ ulint i, j;
+
+ ut_a(update && index);
+ ut_a(n <= dict_index_get_n_fields(index));
+
+ n_upd_fields = upd_get_n_fields(update);
+
+ for (i = 0; i < n; i++) {
+
+ ind_field = dict_index_get_nth_field(index, i);
+ col = dict_field_get_col(ind_field);
+ col_pos = dict_col_get_clust_pos(col);
+
+ for (j = 0; j < n_upd_fields; j++) {
+
+ upd_field = upd_get_nth_field(update, j);
+
+ if (col_pos == upd_field->field_no
+ && cmp_dfield_dfield(
+ dtuple_get_nth_field(entry, i),
+ &(upd_field->new_val))) {
+ return(TRUE);
+ }
+ }
+ }
+
+ return(FALSE);
+}
+
/*************************************************************************
Copies the column values from a record. */
UNIV_INLINE
@@ -1106,9 +1177,11 @@ row_upd_sec_index_entry(
err = btr_cur_del_mark_set_sec_rec(0, btr_cur, TRUE,
thr, &mtr);
if (err == DB_SUCCESS && check_ref) {
+
/* NOTE that the following call loses
the position of pcur ! */
err = row_upd_check_references_constraints(
+ node,
&pcur, index->table,
index, thr, &mtr);
if (err != DB_SUCCESS) {
@@ -1224,7 +1297,7 @@ row_upd_clust_rec_by_insert(
if (check_ref) {
/* NOTE that the following call loses
the position of pcur ! */
- err = row_upd_check_references_constraints(
+ err = row_upd_check_references_constraints(node,
pcur, table,
index, thr, mtr);
if (err != DB_SUCCESS) {
@@ -1392,7 +1465,8 @@ row_upd_del_mark_clust_rec(
if (err == DB_SUCCESS && check_ref) {
/* NOTE that the following call loses the position of pcur ! */
- err = row_upd_check_references_constraints(pcur, index->table,
+ err = row_upd_check_references_constraints(node,
+ pcur, index->table,
index, thr, mtr);
if (err != DB_SUCCESS) {
mtr_commit(mtr);