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

MDEV-7563 Support CHECK constraint as in (or close to) SQL Standard

MDEV-10134 Add full support for DEFAULT

- Added support for using tables with MySQL 5.7 virtual fields,
  including MySQL 5.7 syntax
- Better error messages also for old cases
- CREATE ... SELECT now also updates timestamp columns
- Blob can now have default values
- Added new system variable "check_constraint_checks", to turn of
  CHECK constraint checking if needed.
- Removed some engine independent tests in suite vcol to only test myisam
- Moved some tests from 'include' to 't'. Should some day be done for all tests.
- FRM version increased to 11 if one uses virtual fields or constraints
- Changed to use a bitmap to check if a field has got a value, instead of
  setting HAS_EXPLICIT_VALUE bit in field flags
- Expressions can now be up to 65K in total
- Ensure we are not refering to uninitialized fields when handling virtual fields or defaults
- Changed check_vcol_func_processor() to return a bitmap of used types
- Had to change some functions that calculated cached value in fix_fields to do
  this in val() or getdate() instead.
- store_now_in_TIME() now takes a THD argument
- fill_record() now updates default values
- Add a lookahead for NOT NULL, to be able to handle DEFAULT 1+1 NOT NULL
- Automatically generate a name for constraints that doesn't have a name
- Added support for ALTER TABLE DROP CONSTRAINT
- Ensure that partition functions register virtual fields used. This fixes
  some bugs when using virtual fields in a partitioning function
This commit is contained in:
Michael Widenius
2016-06-29 09:14:22 +02:00
committed by Sergei Golubchik
parent 23d03a1b1e
commit db7edfed17
165 changed files with 5793 additions and 6325 deletions

View File

@ -65,6 +65,9 @@ const char *primary_key_name="PRIMARY";
static bool check_if_keyname_exists(const char *name,KEY *start, KEY *end);
static char *make_unique_key_name(THD *thd, const char *field_name, KEY *start,
KEY *end);
static void make_unique_constraint_name(THD *thd, LEX_STRING *name,
List<Virtual_column_info> *vcol,
uint *nr);
static int copy_data_between_tables(THD *thd, TABLE *from,TABLE *to,
List<Create_field> &create, bool ignore,
uint order_num, ORDER *order,
@ -3082,7 +3085,7 @@ void promote_first_timestamp_column(List<Create_field> *column_definitions)
column_definition->unireg_check == Field::TIMESTAMP_OLD_FIELD) // Legacy
{
if ((column_definition->flags & NOT_NULL_FLAG) != 0 && // NOT NULL,
column_definition->def == NULL && // no constant default,
column_definition->default_value == NULL && // no constant default,
column_definition->unireg_check == Field::NONE && // no function default
column_definition->vcol_info == NULL)
{
@ -3255,32 +3258,30 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info,
/*
Convert the default value from client character
set into the column character set if necessary.
We can only do this for constants as we have not yet run fix_fields.
*/
if (sql_field->def &&
save_cs != sql_field->def->collation.collation &&
if (sql_field->default_value &&
sql_field->default_value->expr_item->basic_const_item() &&
save_cs != sql_field->default_value->expr_item->collation.collation &&
(sql_field->sql_type == MYSQL_TYPE_VAR_STRING ||
sql_field->sql_type == MYSQL_TYPE_STRING ||
sql_field->sql_type == MYSQL_TYPE_SET ||
sql_field->sql_type == MYSQL_TYPE_TINY_BLOB ||
sql_field->sql_type == MYSQL_TYPE_MEDIUM_BLOB ||
sql_field->sql_type == MYSQL_TYPE_LONG_BLOB ||
sql_field->sql_type == MYSQL_TYPE_BLOB ||
sql_field->sql_type == MYSQL_TYPE_ENUM))
{
/*
Starting from 5.1 we work here with a copy of Create_field
created by the caller, not with the instance that was
originally created during parsing. It's OK to create
a temporary item and initialize with it a member of the
copy -- this item will be thrown away along with the copy
at the end of execution, and thus not introduce a dangling
pointer in the parsed tree of a prepared statement or a
stored procedure statement.
*/
sql_field->def= sql_field->def->safe_charset_converter(thd, save_cs);
if (sql_field->def == NULL)
Item *item;
if (!(item= sql_field->default_value->expr_item->
safe_charset_converter(thd, save_cs)))
{
/* Could not convert */
my_error(ER_INVALID_DEFAULT, MYF(0), sql_field->field_name);
DBUG_RETURN(TRUE);
}
/* Fix for prepare statement */
thd->change_item_tree(&sql_field->default_value->expr_item, item);
}
if (sql_field->sql_type == MYSQL_TYPE_SET ||
@ -3348,12 +3349,13 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info,
if (sql_field->sql_type == MYSQL_TYPE_SET)
{
uint32 field_length;
if (sql_field->def != NULL)
if (sql_field->default_value &&
sql_field->default_value->expr_item->basic_const_item())
{
char *not_used;
uint not_used2;
bool not_found= 0;
String str, *def= sql_field->def->val_str(&str);
String str, *def= sql_field->default_value->expr_item->val_str(&str);
if (def == NULL) /* SQL "NULL" maps to NULL */
{
if ((sql_field->flags & NOT_NULL_FLAG) != 0)
@ -3385,9 +3387,10 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info,
{
uint32 field_length;
DBUG_ASSERT(sql_field->sql_type == MYSQL_TYPE_ENUM);
if (sql_field->def != NULL)
if (sql_field->default_value &&
sql_field->default_value->expr_item->basic_const_item())
{
String str, *def= sql_field->def->val_str(&str);
String str, *def= sql_field->default_value->expr_item->val_str(&str);
if (def == NULL) /* SQL "NULL" maps to NULL */
{
if ((sql_field->flags & NOT_NULL_FLAG) != 0)
@ -3464,7 +3467,7 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info,
file->ha_table_flags() & HA_CAN_BIT_FIELD)
total_uneven_bit_length-= sql_field->length & 7;
sql_field->def= dup_field->def;
sql_field->default_value= dup_field->default_value;
sql_field->sql_type= dup_field->sql_type;
/*
@ -4129,7 +4132,7 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info,
it is NOT NULL, not an AUTO_INCREMENT field, not a TIMESTAMP and not
updated trough a NOW() function.
*/
if (!sql_field->def &&
if (!sql_field->default_value &&
!sql_field->has_default_function() &&
(sql_field->flags & NOT_NULL_FLAG) &&
!is_timestamp_type(sql_field->sql_type))
@ -4139,7 +4142,7 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info,
}
if (thd->variables.sql_mode & MODE_NO_ZERO_DATE &&
!sql_field->def &&
!sql_field->default_value &&
is_timestamp_type(sql_field->sql_type) &&
(sql_field->flags & NOT_NULL_FLAG) &&
(type == Field::NONE || type == Field::TIMESTAMP_UN_FIELD))
@ -4163,6 +4166,31 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info,
}
}
/* Check table level constraints */
create_info->constraint_list= &alter_info->constraint_list;
{
uint nr= 1;
List_iterator_fast<Virtual_column_info> c_it(alter_info->constraint_list);
Virtual_column_info *check;
while ((check= c_it++))
{
if (!check->name.length)
make_unique_constraint_name(thd, &check->name,
&alter_info->constraint_list,
&nr);
if (check_string_char_length(&check->name, 0, NAME_CHAR_LEN,
system_charset_info, 1))
{
my_error(ER_TOO_LONG_IDENT, MYF(0), key->name.str);
DBUG_RETURN(TRUE);
}
if (check_expression(check, "CONSTRAINT CHECK",
check->name.str ? check->name.str : "", 0))
DBUG_RETURN(TRUE);
}
}
/* Give warnings for not supported table options */
#if defined(WITH_ARIA_STORAGE_ENGINE)
extern handlerton *maria_hton;
@ -4278,7 +4306,7 @@ static bool prepare_blob_field(THD *thd, Column_definition *sql_field)
/* Convert long VARCHAR columns to TEXT or BLOB */
char warn_buff[MYSQL_ERRMSG_SIZE];
if (sql_field->def || thd->is_strict_mode())
if (thd->is_strict_mode())
{
my_error(ER_TOO_BIG_FIELDLENGTH, MYF(0), sql_field->field_name,
static_cast<ulong>(MAX_FIELD_VARCHARLENGTH /
@ -4357,7 +4385,7 @@ void sp_prepare_create_field(THD *thd, Column_definition *sql_field)
FIELDFLAG_TREAT_BIT_AS_CHAR;
}
sql_field->create_length_to_internal_length();
DBUG_ASSERT(sql_field->def == 0);
DBUG_ASSERT(sql_field->default_value == 0);
/* Can't go wrong as sql_field->def is not defined */
(void) prepare_blob_field(thd, sql_field);
}
@ -5122,6 +5150,38 @@ make_unique_key_name(THD *thd, const char *field_name,KEY *start,KEY *end)
return (char*) "not_specified"; // Should never happen
}
/**
Make an unique name for constraints without a name
*/
static void make_unique_constraint_name(THD *thd, LEX_STRING *name,
List<Virtual_column_info> *vcol,
uint *nr)
{
char buff[MAX_FIELD_NAME], *end;
List_iterator_fast<Virtual_column_info> it(*vcol);
end=strmov(buff, "CONSTRAINT_");
for (;;)
{
Virtual_column_info *check;
char *real_end= int10_to_str((*nr)++, end, 10);
it.rewind();
while ((check= it++))
{
if (check->name.str &&
!my_strcasecmp(system_charset_info, buff, check->name.str))
break;
}
if (!check) // Found unique name
{
name->length= (size_t) (real_end - buff);
name->str= thd->strmake(buff, name->length);
return;
}
}
}
/****************************************************************************
** Alter a table definition
@ -6176,6 +6236,10 @@ static bool fill_alter_inplace_info(THD *thd,
/* Check for: ALTER TABLE FORCE, ALTER TABLE ENGINE and OPTIMIZE TABLE. */
if (alter_info->flags & Alter_info::ALTER_RECREATE)
ha_alter_info->handler_flags|= Alter_inplace_info::RECREATE_TABLE;
if (alter_info->flags & Alter_info::ALTER_ADD_CONSTRAINT)
ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_ADD_CONSTRAINT;
if (alter_info->flags & Alter_info::ALTER_DROP_CONSTRAINT)
ha_alter_info->handler_flags|= Alter_inplace_info::ALTER_DROP_CONSTRAINT;
/*
If we altering table with old VARCHAR fields we will be automatically
@ -6923,6 +6987,14 @@ static bool is_inplace_alter_impossible(TABLE *table,
if (!table->s->mysql_version)
DBUG_RETURN(true);
/*
If we are using a MySQL 5.7 table with virtual fields, ALTER TABLE must
recreate the table as we need to rewrite generated fields
*/
if (table->s->mysql_version > 50700 && table->s->mysql_version < 100000 &&
table->s->virtual_fields)
DBUG_RETURN(TRUE);
DBUG_RETURN(false);
}
@ -7333,6 +7405,7 @@ mysql_prepare_alter_table(THD *thd, TABLE *table,
List_iterator<Create_field> find_it(new_create_list);
List_iterator<Create_field> field_it(new_create_list);
List<Key_part_spec> key_parts;
List<Virtual_column_info> new_constraint_list;
uint db_create_options= (table->s->db_create_options
& ~(HA_OPTION_PACK_RECORD));
uint used_fields;
@ -7477,12 +7550,7 @@ mysql_prepare_alter_table(THD *thd, TABLE *table,
}
if (alter)
{
if (def->sql_type == MYSQL_TYPE_BLOB)
{
my_error(ER_BLOB_CANT_HAVE_DEFAULT, MYF(0), def->change);
goto err;
}
if ((def->def=alter->def)) // Use new default
if ((def->default_value= alter->default_value))
def->flags&= ~NO_DEFAULT_VALUE_FLAG;
else
def->flags|= NO_DEFAULT_VALUE_FLAG;
@ -7756,6 +7824,33 @@ mysql_prepare_alter_table(THD *thd, TABLE *table,
}
}
/* Add all table level constraints which are not in the drop list */
if (table->s->table_check_constraints)
{
TABLE_SHARE *share= table->s;
for (uint i= share->field_check_constraints;
i < share->table_check_constraints ; i++)
{
Virtual_column_info *check= table->check_constraints[i];
Alter_drop *drop;
drop_it.rewind();
while ((drop=drop_it++))
{
if (drop->type == Alter_drop::CONSTRAINT_CHECK &&
!my_strcasecmp(system_charset_info, check->name.str, drop->name))
{
drop_it.remove();
break;
}
}
if (!drop)
new_constraint_list.push_back(check, thd->mem_root);
}
}
/* Add new constraints */
new_constraint_list.append(&alter_info->constraint_list);
if (alter_info->drop_list.elements)
{
Alter_drop *drop;
@ -7764,8 +7859,15 @@ mysql_prepare_alter_table(THD *thd, TABLE *table,
switch (drop->type) {
case Alter_drop::KEY:
case Alter_drop::COLUMN:
my_error(ER_CANT_DROP_FIELD_OR_KEY, MYF(0),
alter_info->drop_list.head()->name);
case Alter_drop::CONSTRAINT_CHECK:
if (drop->drop_if_exists)
push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
ER_CANT_DROP_FIELD_OR_KEY,
ER_THD(thd, ER_CANT_DROP_FIELD_OR_KEY),
drop->name);
else
my_error(ER_CANT_DROP_FIELD_OR_KEY, MYF(0),
alter_info->drop_list.head()->name);
goto err;
case Alter_drop::FOREIGN_KEY:
// Leave the DROP FOREIGN KEY names in the alter_info->drop_list.
@ -7811,6 +7913,7 @@ mysql_prepare_alter_table(THD *thd, TABLE *table,
rc= FALSE;
alter_info->create_list.swap(new_create_list);
alter_info->key_list.swap(new_key_list);
alter_info->constraint_list.swap(new_constraint_list);
err:
DBUG_RETURN(rc);
}
@ -8848,13 +8951,21 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name,
altered_table->column_bitmaps_set_no_signal(&altered_table->s->all_set,
&altered_table->s->all_set);
restore_record(altered_table, s->default_values); // Create empty record
if (altered_table->default_field && altered_table->update_default_fields())
/* Check that we can call default functions with default field values */
altered_table->reset_default_fields();
if (altered_table->default_field &&
altered_table->update_default_fields(0, 1))
goto err_new_table_cleanup;
// Ask storage engine whether to use copy or in-place
enum_alter_inplace_result inplace_supported=
table->file->check_if_supported_inplace_alter(altered_table,
&ha_alter_info);
HA_ALTER_INPLACE_NOT_SUPPORTED;
if (!(ha_alter_info.handler_flags &
Alter_inplace_info::ALTER_ADD_CONSTRAINT) ||
(thd->variables.option_bits & OPTION_NO_CHECK_CONSTRAINT_CHECKS))
inplace_supported=
table->file->check_if_supported_inplace_alter(altered_table,
&ha_alter_info);
switch (inplace_supported) {
case HA_ALTER_INPLACE_EXCLUSIVE_LOCK:
@ -9435,7 +9546,8 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to,
Old fields keep their current values, and therefore should not be
present in the set of autoupdate fields.
*/
if ((*ptr)->has_insert_default_function())
if ((*ptr)->default_value ||
((*ptr)->has_insert_default_function()))
{
*(dfield_ptr++)= *ptr;
++to->s->default_fields;
@ -9482,7 +9594,9 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to,
THD_STAGE_INFO(thd, stage_copy_to_tmp_table);
/* Tell handler that we have values for all columns in the to table */
to->use_all_columns();
to->mark_virtual_columns_for_write(TRUE);
/* Add virtual columns to vcol_set to ensure they are updated */
if (to->vfield)
to->mark_virtual_columns_for_write(TRUE);
if (init_read_record(&info, thd, from, (SQL_SELECT *) 0, file_sort, 1, 1,
FALSE))
goto err;
@ -9492,8 +9606,7 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to,
to->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
thd->get_stmt_da()->reset_current_row_for_warning();
restore_record(to, s->default_values); // Create empty record
if (to->default_field && to->update_default_fields())
goto err;
to->reset_default_fields();
thd->progress.max_counter= from->file->records();
time_to_report_progress= MY_HOW_OFTEN_TO_WRITE/10;
@ -9534,8 +9647,14 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to,
copy_ptr->do_copy(copy_ptr);
}
prev_insert_id= to->file->next_insert_id;
if (to->default_field)
to->update_default_fields(0, ignore);
if (to->vfield)
update_virtual_fields(thd, to, VCOL_UPDATE_FOR_WRITE);
/* This will set thd->is_error() if fatal failure */
if (to->verify_constraints(ignore) == VIEW_CHECK_SKIP)
continue;
if (thd->is_error())
{
error= 1;