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:
@ -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);
|
||||
}
|
||||
|
Reference in New Issue
Block a user