1
0
mirror of https://github.com/MariaDB/server.git synced 2025-07-30 16:24:05 +03:00

MDEV-34392 Inplace algorithm violates the foreign key constraint

Don't allow the referencing key column from NULL TO NOT NULL
when

 1) Foreign key constraint type is ON UPDATE SET NULL
 2) Foreign key constraint type is ON DELETE SET NULL
 3) Foreign key constraint type is UPDATE CASCADE and referenced
 column declared as NULL

Don't allow the referenced key column from NOT NULL to NULL
when foreign key constraint type is UPDATE CASCADE
and referencing key columns doesn't allow NULL values

get_foreign_key_info(): InnoDB sends the information about
nullability of the foreign key fields and referenced key fields.

fk_check_column_changes(): Enforce the above rules for COPY
algorithm

innobase_check_foreign_drop_col(): Checks whether the dropped
column exists in existing foreign key relation

innobase_check_foreign_low() : Enforce the above rules for
INPLACE algorithm

dict_foreign_t::check_fk_constraint_valid(): This is used
by CREATE TABLE statement to check nullability for foreign
key relation.
This commit is contained in:
Thirunarayanan Balathandayuthapani
2024-09-30 13:37:26 +05:30
parent 45298b730b
commit cc810e64d4
18 changed files with 919 additions and 215 deletions

View File

@ -9589,18 +9589,20 @@ static Create_field *get_field_by_old_name(Alter_info *alter_info,
enum fk_column_change_type
{
FK_COLUMN_NO_CHANGE, FK_COLUMN_DATA_CHANGE,
FK_COLUMN_RENAMED, FK_COLUMN_DROPPED
FK_COLUMN_RENAMED, FK_COLUMN_DROPPED, FK_COLUMN_NOT_NULL
};
/**
Check that ALTER TABLE's changes on columns of a foreign key are allowed.
@param[in] thd Thread context.
@param[in] table table to be altered
@param[in] alter_info Alter_info describing changes to be done
by ALTER TABLE.
@param[in] fk_columns List of columns of the foreign key to check.
@param[in] fk Foreign key information.
@param[out] bad_column_name Name of field on which ALTER TABLE tries to
do prohibited operation.
@param[in] referenced Check the referenced fields
@note This function takes into account value of @@foreign_key_checks
setting.
@ -9611,17 +9613,27 @@ enum fk_column_change_type
change in foreign key column.
@retval FK_COLUMN_RENAMED Foreign key column is renamed.
@retval FK_COLUMN_DROPPED Foreign key column is dropped.
@retval FK_COLUMN_NOT_NULL Foreign key column cannot be null
if ON...SET NULL or ON UPDATE
CASCADE conflicts with NOT NULL
*/
static enum fk_column_change_type
fk_check_column_changes(THD *thd, Alter_info *alter_info,
List<LEX_CSTRING> &fk_columns,
const char **bad_column_name)
fk_check_column_changes(THD *thd, const TABLE *table,
Alter_info *alter_info,
FOREIGN_KEY_INFO *fk,
const char **bad_column_name,
bool referenced=false)
{
List<LEX_CSTRING> &fk_columns= referenced
? fk->referenced_fields
: fk->foreign_fields;
List_iterator_fast<LEX_CSTRING> column_it(fk_columns);
LEX_CSTRING *column;
int n_col= 0;
*bad_column_name= NULL;
enum fk_column_change_type result= FK_COLUMN_NO_CHANGE;
while ((column= column_it++))
{
@ -9640,8 +9652,8 @@ fk_check_column_changes(THD *thd, Alter_info *alter_info,
SE that foreign keys should be updated to use new name of column
like it happens in case of in-place algorithm.
*/
*bad_column_name= column->str;
return FK_COLUMN_RENAMED;
result= FK_COLUMN_RENAMED;
goto func_exit;
}
/*
@ -9654,17 +9666,55 @@ fk_check_column_changes(THD *thd, Alter_info *alter_info,
new_field->flags&= ~AUTO_INCREMENT_FLAG;
const bool equal_result= old_field->is_equal(*new_field);
new_field->flags= flags;
const bool old_field_not_null= old_field->flags & NOT_NULL_FLAG;
const bool new_field_not_null= new_field->flags & NOT_NULL_FLAG;
if ((equal_result == IS_EQUAL_NO) ||
((new_field->flags & NOT_NULL_FLAG) &&
!(old_field->flags & NOT_NULL_FLAG)))
if ((equal_result == IS_EQUAL_NO))
{
/*
Column in a FK has changed significantly and it
may break referential intergrity.
*/
*bad_column_name= column->str;
return FK_COLUMN_DATA_CHANGE;
result= FK_COLUMN_DATA_CHANGE;
goto func_exit;
}
if (old_field_not_null != new_field_not_null)
{
if (referenced && !new_field_not_null)
{
/*
Don't allow referenced column to change from
NOT NULL to NULL when foreign key relation is
ON UPDATE CASCADE and the referencing column
is declared as NOT NULL
*/
if (fk->update_method == FK_OPTION_CASCADE &&
!fk->is_nullable(false, n_col))
{
result= FK_COLUMN_DATA_CHANGE;
goto func_exit;
}
}
else if (!referenced && new_field_not_null)
{
/*
Don't allow the foreign column to change
from NULL to NOT NULL when foreign key type is
1) UPDATE SET NULL
2) DELETE SET NULL
3) UPDATE CASCADE and referenced column is declared as NULL
*/
if (fk->update_method == FK_OPTION_SET_NULL ||
fk->delete_method == FK_OPTION_SET_NULL ||
(fk->update_method == FK_OPTION_CASCADE &&
fk->referenced_key_name &&
fk->is_nullable(true, n_col)))
{
result= FK_COLUMN_NOT_NULL;
goto func_exit;
}
}
}
}
else
@ -9678,12 +9728,15 @@ fk_check_column_changes(THD *thd, Alter_info *alter_info,
field being dropped since it is easy to break referential
integrity in this case.
*/
*bad_column_name= column->str;
return FK_COLUMN_DROPPED;
result= FK_COLUMN_DROPPED;
goto func_exit;
}
n_col++;
}
return FK_COLUMN_NO_CHANGE;
func_exit:
*bad_column_name= column->str;
return result;
}
@ -9775,9 +9828,8 @@ static bool fk_prepare_copy_alter_table(THD *thd, TABLE *table,
enum fk_column_change_type changes;
const char *bad_column_name;
changes= fk_check_column_changes(thd, alter_info,
f_key->referenced_fields,
&bad_column_name);
changes= fk_check_column_changes(thd, table, alter_info, f_key,
&bad_column_name, true);
switch(changes)
{
@ -9811,6 +9863,9 @@ static bool fk_prepare_copy_alter_table(THD *thd, TABLE *table,
f_key->foreign_id->str, buff.c_ptr());
DBUG_RETURN(true);
}
/* FK_COLUMN_NOT_NULL error happens only when changing
the foreign key column from NULL to NOT NULL */
case FK_COLUMN_NOT_NULL:
default:
DBUG_ASSERT(0);
}
@ -9849,8 +9904,7 @@ static bool fk_prepare_copy_alter_table(THD *thd, TABLE *table,
enum fk_column_change_type changes;
const char *bad_column_name;
changes= fk_check_column_changes(thd, alter_info,
f_key->foreign_fields,
changes= fk_check_column_changes(thd, table, alter_info, f_key,
&bad_column_name);
switch(changes)
@ -9872,6 +9926,10 @@ static bool fk_prepare_copy_alter_table(THD *thd, TABLE *table,
my_error(ER_FK_COLUMN_CANNOT_DROP, MYF(0), bad_column_name,
f_key->foreign_id->str);
DBUG_RETURN(true);
case FK_COLUMN_NOT_NULL:
my_error(ER_FK_COLUMN_NOT_NULL, MYF(0), bad_column_name,
f_key->foreign_id->str);
DBUG_RETURN(true);
default:
DBUG_ASSERT(0);
}