From 4f9977d8d3292caf509227154dafc416a178d17d Mon Sep 17 00:00:00 2001 From: Thirunarayanan Balathandayuthapani Date: Tue, 24 Apr 2018 13:15:35 +0530 Subject: [PATCH] MDEV-14168 Unconditionally allow ALGORITHM=INPLACE for setting a column NOT NULL - Allow NOT NULL constraint to replace the NULL value in the row with explicit or implicit default value. - If the default value is non-const value then inplace alter won't support it. - ALTER IGNORE will ignore the error if the concurrent DML contains NULL value. --- .../innodb/r/innodb-alter-timestamp.result | 12 +- .../suite/innodb/r/innodb-table-online.result | 4 +- .../innodb/t/innodb-alter-timestamp.test | 6 +- .../suite/innodb/t/innodb-table-online.test | 5 +- sql/share/errmsg-utf8.txt | 2 +- storage/innobase/handler/handler0alter.cc | 165 ++++++++++++------ storage/innobase/include/row0log.h | 7 +- storage/innobase/include/row0merge.h | 4 +- storage/innobase/include/row0row.h | 8 +- storage/innobase/row/row0log.cc | 56 ++++-- storage/innobase/row/row0merge.cc | 31 ++-- storage/innobase/row/row0row.cc | 22 +-- 12 files changed, 200 insertions(+), 122 deletions(-) diff --git a/mysql-test/suite/innodb/r/innodb-alter-timestamp.result b/mysql-test/suite/innodb/r/innodb-alter-timestamp.result index 9659b03d6b282..b8686d6812e78 100644 --- a/mysql-test/suite/innodb/r/innodb-alter-timestamp.result +++ b/mysql-test/suite/innodb/r/innodb-alter-timestamp.result @@ -2,7 +2,7 @@ CREATE TABLE t1 (i1 INT UNSIGNED NULL DEFAULT 42) ENGINE=innodb; INSERT INTO t1 VALUES(NULL); ALTER TABLE t1 CHANGE i1 i1 INT UNSIGNED NOT NULL DEFAULT rand(), ALGORITHM=INPLACE; -ERROR 22004: Invalid use of NULL value +ERROR 0A000: ALGORITHM=INPLACE is not supported. Reason: cannot convert NULL to non-constant DEFAULT. Try ALGORITHM=COPY ALTER TABLE t1 CHANGE i1 i1 INT UNSIGNED NOT NULL DEFAULT rand(), ALGORITHM=COPY; ERROR 01000: Data truncated for column 'i1' at row 1 @@ -10,20 +10,20 @@ ALTER TABLE t1 CHANGE i1 id INT UNSIGNED NOT NULL AUTO_INCREMENT, ADD PRIMARY KEY(id), ALGORITHM=INPLACE; ERROR 0A000: ALGORITHM=INPLACE is not supported. Reason: Cannot change column type INPLACE. Try ALGORITHM=COPY ALTER TABLE t1 ADD PRIMARY KEY(i1), ALGORITHM=INPLACE; -ERROR 22004: Invalid use of NULL value -ALTER TABLE t1 CHANGE i1 id INT UNSIGNED NOT NULL AUTO_INCREMENT, -ADD PRIMARY KEY(id); +affected rows: 0 +info: Records: 0 Duplicates: 0 Warnings: 0 +ALTER TABLE t1 CHANGE i1 id INT UNSIGNED NOT NULL AUTO_INCREMENT; affected rows: 1 info: Records: 1 Duplicates: 0 Warnings: 0 SELECT * FROM t1; id -1 +42 SHOW CREATE TABLE t1; Table Create Table t1 CREATE TABLE `t1` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1 +) ENGINE=InnoDB AUTO_INCREMENT=43 DEFAULT CHARSET=latin1 DROP TABLE t1; CREATE TABLE t1 (i1 INT UNSIGNED NOT NULL, d1 TIMESTAMP NULL) ENGINE=InnoDB; SHOW CREATE TABLE t1; diff --git a/mysql-test/suite/innodb/r/innodb-table-online.result b/mysql-test/suite/innodb/r/innodb-table-online.result index 3ac00436e07c3..d8482ebb23a5d 100644 --- a/mysql-test/suite/innodb/r/innodb-table-online.result +++ b/mysql-test/suite/innodb/r/innodb-table-online.result @@ -411,9 +411,7 @@ SET @old_sql_mode = @@sql_mode; SET @@sql_mode = 'STRICT_TRANS_TABLES'; ALTER TABLE t1 DROP COLUMN c22f, DROP PRIMARY KEY, ADD PRIMARY KEY c3p5(c3(5)), ALGORITHM = INPLACE; -ERROR 22004: Invalid use of NULL value -ALTER TABLE t1 MODIFY c3 CHAR(255) NOT NULL; -ERROR 22004: Invalid use of NULL value +ERROR 23000: Duplicate entry '' for key 'PRIMARY' SET @@sql_mode = @old_sql_mode; UPDATE t1 SET c3=LEFT(CONCAT(c1,REPEAT('foo',c1)),255) WHERE c3 IS NULL; SET DEBUG_SYNC = 'row_log_table_apply1_before SIGNAL c3p5_created0 WAIT_FOR ins_done0'; diff --git a/mysql-test/suite/innodb/t/innodb-alter-timestamp.test b/mysql-test/suite/innodb/t/innodb-alter-timestamp.test index d8acc02cbdb78..32a54354016e7 100644 --- a/mysql-test/suite/innodb/t/innodb-alter-timestamp.test +++ b/mysql-test/suite/innodb/t/innodb-alter-timestamp.test @@ -3,7 +3,7 @@ CREATE TABLE t1 (i1 INT UNSIGNED NULL DEFAULT 42) ENGINE=innodb; INSERT INTO t1 VALUES(NULL); --enable_info ---error ER_INVALID_USE_OF_NULL +--error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON ALTER TABLE t1 CHANGE i1 i1 INT UNSIGNED NOT NULL DEFAULT rand(), ALGORITHM=INPLACE; --error WARN_DATA_TRUNCATED @@ -12,10 +12,8 @@ ALGORITHM=COPY; --error ER_ALTER_OPERATION_NOT_SUPPORTED_REASON ALTER TABLE t1 CHANGE i1 id INT UNSIGNED NOT NULL AUTO_INCREMENT, ADD PRIMARY KEY(id), ALGORITHM=INPLACE; ---error ER_INVALID_USE_OF_NULL ALTER TABLE t1 ADD PRIMARY KEY(i1), ALGORITHM=INPLACE; -ALTER TABLE t1 CHANGE i1 id INT UNSIGNED NOT NULL AUTO_INCREMENT, -ADD PRIMARY KEY(id); +ALTER TABLE t1 CHANGE i1 id INT UNSIGNED NOT NULL AUTO_INCREMENT; --disable_info SELECT * FROM t1; SHOW CREATE TABLE t1; diff --git a/mysql-test/suite/innodb/t/innodb-table-online.test b/mysql-test/suite/innodb/t/innodb-table-online.test index 1bb4b686b5631..538e4b6876212 100644 --- a/mysql-test/suite/innodb/t/innodb-table-online.test +++ b/mysql-test/suite/innodb/t/innodb-table-online.test @@ -358,12 +358,10 @@ SET @old_sql_mode = @@sql_mode; # NULL -> NOT NULL only allowed INPLACE if strict sql_mode is on. # And adding a PRIMARY KEY will also add NOT NULL implicitly! SET @@sql_mode = 'STRICT_TRANS_TABLES'; ---error ER_INVALID_USE_OF_NULL +--error ER_DUP_ENTRY ALTER TABLE t1 DROP COLUMN c22f, DROP PRIMARY KEY, ADD PRIMARY KEY c3p5(c3(5)), ALGORITHM = INPLACE; ---error ER_INVALID_USE_OF_NULL -ALTER TABLE t1 MODIFY c3 CHAR(255) NOT NULL; SET @@sql_mode = @old_sql_mode; UPDATE t1 SET c3=LEFT(CONCAT(c1,REPEAT('foo',c1)),255) WHERE c3 IS NULL; @@ -397,6 +395,7 @@ ROLLBACK; --echo # session con1 connection con1; ALTER TABLE t1 MODIFY c3 CHAR(255) NOT NULL; + SET DEBUG_SYNC = 'row_log_table_apply1_before SIGNAL c3p5_created WAIT_FOR ins_done'; --send ALTER TABLE t1 DROP PRIMARY KEY, DROP COLUMN c22f, diff --git a/sql/share/errmsg-utf8.txt b/sql/share/errmsg-utf8.txt index 1719266338195..20e42214e933b 100644 --- a/sql/share/errmsg-utf8.txt +++ b/sql/share/errmsg-utf8.txt @@ -7089,7 +7089,7 @@ ER_IDENT_CAUSES_TOO_LONG_PATH eng "Long database name and identifier for object resulted in path length exceeding %d characters. Path: '%s'" ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOT_NULL - eng "cannot silently convert NULL values, as required in this SQL_MODE" + eng "cannot convert NULL to non-constant DEFAULT" ER_MUST_CHANGE_PASSWORD_LOGIN eng "Your password has expired. To log in you must change it using a client that supports expired passwords" diff --git a/storage/innobase/handler/handler0alter.cc b/storage/innobase/handler/handler0alter.cc index 64a41e25632b9..85b1febf478b3 100644 --- a/storage/innobase/handler/handler0alter.cc +++ b/storage/innobase/handler/handler0alter.cc @@ -127,6 +127,10 @@ static const alter_table_operations INNOBASE_ALTER_NOREBUILD | ALTER_DROP_VIRTUAL_COLUMN | ALTER_VIRTUAL_COLUMN_ORDER; +static const alter_table_operations INNOBASE_DEFAULTS + = ALTER_COLUMN_NOT_NULLABLE + | ALTER_ADD_COLUMN; + struct ha_innobase_inplace_ctx : public inplace_alter_handler_ctx { /** Dummy query graph */ @@ -173,8 +177,8 @@ struct ha_innobase_inplace_ctx : public inplace_alter_handler_ctx const char** col_names; /** added AUTO_INCREMENT column position, or ULINT_UNDEFINED */ const ulint add_autoinc; - /** default values of ADD COLUMN, or NULL */ - const dtuple_t* add_cols; + /** default values of ADD and CHANGE COLUMN, or NULL */ + const dtuple_t* defaults; /** autoinc sequence to use */ ib_sequence_t sequence; /** temporary table name to use for old table when renaming tables */ @@ -200,6 +204,9 @@ struct ha_innobase_inplace_ctx : public inplace_alter_handler_ctx /** original column names of the table */ const char* const old_col_names; + /** Whether alter ignore issued. */ + const bool ignore; + ha_innobase_inplace_ctx(row_prebuilt_t*& prebuilt_arg, dict_index_t** drop_arg, ulint num_to_drop_arg, @@ -216,7 +223,8 @@ struct ha_innobase_inplace_ctx : public inplace_alter_handler_ctx ulint add_autoinc_arg, ulonglong autoinc_col_min_value_arg, ulonglong autoinc_col_max_value_arg, - ulint num_to_drop_vcol_arg) : + ulint num_to_drop_vcol_arg, + bool ignore_flag) : inplace_alter_handler_ctx(), prebuilt (prebuilt_arg), add_index (0), add_key_numbers (0), num_to_add_index (0), @@ -229,7 +237,7 @@ struct ha_innobase_inplace_ctx : public inplace_alter_handler_ctx new_table (new_table_arg), instant_table (0), col_map (0), col_names (col_names_arg), add_autoinc (add_autoinc_arg), - add_cols (0), + defaults (0), sequence(prebuilt->trx->mysql_thd, autoinc_col_min_value_arg, autoinc_col_max_value_arg), tmp_name (0), @@ -243,7 +251,8 @@ struct ha_innobase_inplace_ctx : public inplace_alter_handler_ctx m_stage(NULL), old_n_cols(prebuilt_arg->table->n_cols), old_cols(prebuilt_arg->table->cols), - old_col_names(prebuilt_arg->table->col_names) + old_col_names(prebuilt_arg->table->col_names), + ignore(ignore_flag) { ut_ad(old_n_cols >= DATA_N_SYS_COLS); #ifdef UNIV_DEBUG @@ -674,6 +683,46 @@ instant_alter_column_possible( || !create_option_need_rebuild(ha_alter_info, table); } +/** Check whether the non-const default value for the field +@param[in] field field which could be added or changed +@return true if the non-const default is present. */ +static bool is_non_const_value(Field* field) +{ + return field->default_value + && field->default_value->flags & ~(VCOL_SESSION_FUNC + | VCOL_TIME_FUNC); +} + +/** Set default value for the field. +@param[in] field field which could be added or changed +@return true if the default value is set. */ +static bool set_default_value(Field* field) +{ + /* The added/changed NOT NULL column lacks a DEFAULT value, + or the DEFAULT is the same for all rows. + (Time functions, such as CURRENT_TIMESTAMP(), + are evaluated from a timestamp that is assigned + at the start of the statement. Session + functions, such as USER(), always evaluate the + same within a statement.) */ + + ut_ad(!is_non_const_value(field)); + + /* Compute the DEFAULT values of non-constant columns + (VCOL_SESSION_FUNC | VCOL_TIME_FUNC). */ + switch (field->set_default()) { + case 0: /* OK */ + case 3: /* DATETIME to TIME or DATE conversion */ + return true; + case -1: /* OOM, or GEOMETRY type mismatch */ + case 1: /* A number adjusted to the min/max value */ + case 2: /* String truncation, or conversion problem */ + break; + } + + return false; +} + /** Check if InnoDB supports a particular alter table in-place @param altered_table TABLE object for new version of table. @param ha_alter_info Structure describing changes to be done @@ -1080,20 +1129,26 @@ ha_innobase::check_if_supported_inplace_alter( /* No DEFAULT value is specified. We can report errors for any NULL values for - the TIMESTAMP. - - FIXME: Allow any DEFAULT - expression whose value does - not change during ALTER TABLE. - This would require a fix in - row_merge_read_clustered_index() - to try to replace the DEFAULT - value before reporting - DB_INVALID_NULL. */ + the TIMESTAMP. */ + goto next_column; } break; default: + /* Changing from NULL to NOT NULL and + set the default constant values. */ + if (f->real_maybe_null() + && !(*af)->real_maybe_null()) { + + if (is_non_const_value(*af)) { + break; + } + + if (!set_default_value(*af)) { + break; + } + } + /* For any other data type, NULL values are not converted. (An AUTO_INCREMENT attribute cannot @@ -1109,32 +1164,16 @@ ha_innobase::check_if_supported_inplace_alter( ha_alter_info->unsupported_reason = innobase_get_err_msg( ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_NOT_NULL); - } else if (!(*af)->default_value - || !((*af)->default_value->flags - & ~(VCOL_SESSION_FUNC | VCOL_TIME_FUNC))) { + } else if (!is_non_const_value(*af)) { + n_add_cols++; if (af < &altered_table->field[table_share->fields]) { add_column_not_last = true; } - /* The added NOT NULL column lacks a DEFAULT value, - or the DEFAULT is the same for all rows. - (Time functions, such as CURRENT_TIMESTAMP(), - are evaluated from a timestamp that is assigned - at the start of the statement. Session - functions, such as USER(), always evaluate the - same within a statement.) */ - - /* Compute the DEFAULT values of non-constant columns - (VCOL_SESSION_FUNC | VCOL_TIME_FUNC). */ - switch ((*af)->set_default()) { - case 0: /* OK */ - case 3: /* DATETIME to TIME or DATE conversion */ + + if (set_default_value(*af)) { goto next_column; - case -1: /* OOM, or GEOMETRY type mismatch */ - case 1: /* A number adjusted to the min/max value */ - case 2: /* String truncation, or conversion problem */ - break; } } @@ -3181,7 +3220,7 @@ adding columns. @param table MySQL table as it is before the ALTER operation @param new_table InnoDB table corresponding to MySQL altered_table @param old_table InnoDB table corresponding to MYSQL table -@param add_cols Default values for ADD COLUMN, or NULL if no ADD COLUMN +@param defaults Default values for ADD COLUMN, or NULL if no ADD COLUMN @param heap Memory heap where allocated @return array of integers, mapping column numbers in the table to column numbers in altered_table */ @@ -3194,7 +3233,7 @@ innobase_build_col_map( const TABLE* table, const dict_table_t* new_table, const dict_table_t* old_table, - dtuple_t* add_cols, + dtuple_t* defaults, mem_heap_t* heap) { DBUG_ENTER("innobase_build_col_map"); @@ -3206,9 +3245,10 @@ innobase_build_col_map( DBUG_ASSERT(dict_table_get_n_cols(old_table) + dict_table_get_n_v_cols(old_table) >= table->s->fields + DATA_N_SYS_COLS); - DBUG_ASSERT(!!add_cols == !!(ha_alter_info->handler_flags - & ALTER_ADD_COLUMN)); - DBUG_ASSERT(!add_cols || dtuple_get_n_fields(add_cols) + DBUG_ASSERT(!!defaults == !!(ha_alter_info->handler_flags + & (ALTER_ADD_COLUMN + | ALTER_COLUMN_NOT_NULLABLE))); + DBUG_ASSERT(!defaults || dtuple_get_n_fields(defaults) == dict_table_get_n_cols(new_table)); ulint* col_map = static_cast( @@ -3250,6 +3290,19 @@ innobase_build_col_map( } if (new_field->field == field) { + + const Field* altered_field = + altered_table->field[i]; + + if (field->real_maybe_null() + && !altered_field->real_maybe_null()) { + innobase_build_col_map_add( + heap, dtuple_get_nth_field( + defaults, i), + altered_field, + dict_table_is_comp(new_table)); + } + col_map[old_i - num_old_v] = i; goto found_col; } @@ -3257,7 +3310,7 @@ innobase_build_col_map( ut_ad(!is_v); innobase_build_col_map_add( - heap, dtuple_get_nth_field(add_cols, i), + heap, dtuple_get_nth_field(defaults, i), altered_table->field[i + num_v], dict_table_is_comp(new_table)); found_col: @@ -4765,7 +4818,7 @@ prepare_inplace_alter_table_dict( DBUG_ASSERT(!add_fts_doc_id || add_fts_doc_id_idx); DBUG_ASSERT(!add_fts_doc_id_idx || innobase_fulltext_exist(altered_table)); - DBUG_ASSERT(!ctx->add_cols); + DBUG_ASSERT(!ctx->defaults); DBUG_ASSERT(!ctx->add_index); DBUG_ASSERT(!ctx->add_key_numbers); DBUG_ASSERT(!ctx->num_to_add_index); @@ -4933,7 +4986,7 @@ prepare_inplace_alter_table_dict( part ? part : "", partlen + 1); ulint n_cols = 0; ulint n_v_cols = 0; - dtuple_t* add_cols; + dtuple_t* defaults; ulint z = 0; for (uint i = 0; i < altered_table->s->fields; i++) { @@ -5101,23 +5154,21 @@ prepare_inplace_alter_table_dict( dict_table_add_system_columns(ctx->new_table, ctx->heap); - if (ha_alter_info->handler_flags - & ALTER_ADD_COLUMN) { - add_cols = dtuple_create_with_vcol( + if (ha_alter_info->handler_flags & INNOBASE_DEFAULTS) { + defaults = dtuple_create_with_vcol( ctx->heap, dict_table_get_n_cols(ctx->new_table), dict_table_get_n_v_cols(ctx->new_table)); - dict_table_copy_types(add_cols, ctx->new_table); + dict_table_copy_types(defaults, ctx->new_table); } else { - add_cols = NULL; + defaults = NULL; } ctx->col_map = innobase_build_col_map( ha_alter_info, altered_table, old_table, - ctx->new_table, user_table, - add_cols, ctx->heap); - ctx->add_cols = add_cols; + ctx->new_table, user_table, defaults, ctx->heap); + ctx->defaults = defaults; } else { DBUG_ASSERT(!innobase_need_rebuild(ha_alter_info, old_table)); DBUG_ASSERT(old_table->s->primary_key @@ -5490,7 +5541,8 @@ prepare_inplace_alter_table_dict( clust_index, ctx->new_table, !(ha_alter_info->handler_flags & ALTER_ADD_PK_INDEX), - ctx->add_cols, ctx->col_map, path); + ctx->defaults, ctx->col_map, path, + ctx->ignore); rw_lock_x_unlock(&clust_index->lock); if (!ok) { @@ -5548,7 +5600,7 @@ prepare_inplace_alter_table_dict( ctx->prebuilt->trx, index, NULL, true, NULL, NULL, - path); + path, ctx->ignore); rw_lock_x_unlock(&index->lock); if (!ok) { @@ -6720,7 +6772,8 @@ ha_innobase::prepare_inplace_alter_table( add_fk, n_add_fk, ha_alter_info->online, heap, indexed_table, - col_names, ULINT_UNDEFINED, 0, 0, 0); + col_names, ULINT_UNDEFINED, 0, 0, 0, + ha_alter_info->ignore); } DBUG_ASSERT(m_prebuilt->trx->dict_operation_lock_mode == 0); @@ -6855,7 +6908,7 @@ ha_innobase::prepare_inplace_alter_table( heap, m_prebuilt->table, col_names, add_autoinc_col_no, ha_alter_info->create_info->auto_increment_value, - autoinc_col_max_value, 0); + autoinc_col_max_value, 0, ha_alter_info->ignore); DBUG_RETURN(prepare_inplace_alter_table_dict( ha_alter_info, altered_table, table, @@ -7086,7 +7139,7 @@ ha_innobase::inplace_alter_table( m_prebuilt->table, ctx->new_table, ctx->online, ctx->add_index, ctx->add_key_numbers, ctx->num_to_add_index, - altered_table, ctx->add_cols, ctx->col_map, + altered_table, ctx->defaults, ctx->col_map, ctx->add_autoinc, ctx->sequence, ctx->skip_pk_sort, ctx->m_stage, add_v, eval_table, ha_alter_info->handler_flags & ALTER_DROP_HISTORICAL); diff --git a/storage/innobase/include/row0log.h b/storage/innobase/include/row0log.h index df9920d9bcc0e..6974ce1b56bbe 100644 --- a/storage/innobase/include/row0log.h +++ b/storage/innobase/include/row0log.h @@ -55,12 +55,13 @@ row_log_allocate( or NULL when creating a secondary index */ bool same_pk,/*!< in: whether the definition of the PRIMARY KEY has remained the same */ - const dtuple_t* add_cols, + const dtuple_t* defaults, /*!< in: default values of - added columns, or NULL */ + added, changed columns, or NULL */ const ulint* col_map,/*!< in: mapping of old column numbers to new ones, or NULL if !table */ - const char* path) /*!< in: where to create temporary file */ + const char* path, /*!< in: where to create temporary file */ + bool ignore) /*!< in: Whether alter ignore issued */ MY_ATTRIBUTE((nonnull(1), warn_unused_result)); /******************************************************//** diff --git a/storage/innobase/include/row0merge.h b/storage/innobase/include/row0merge.h index 2fbc443dc21c4..207fb5b656431 100644 --- a/storage/innobase/include/row0merge.h +++ b/storage/innobase/include/row0merge.h @@ -308,7 +308,7 @@ old_table unless creating a PRIMARY KEY @param[in] n_indexes size of indexes[] @param[in,out] table MySQL table, for reporting erroneous key value if applicable -@param[in] add_cols default values of added columns, or NULL +@param[in] defaults default values of added, changed columns, or NULL @param[in] col_map mapping of old column numbers to new ones, or NULL if old_table == new_table @param[in] add_autoinc number of added AUTO_INCREMENT columns, or @@ -334,7 +334,7 @@ row_merge_build_indexes( const ulint* key_numbers, ulint n_indexes, struct TABLE* table, - const dtuple_t* add_cols, + const dtuple_t* defaults, const ulint* col_map, ulint add_autoinc, ib_sequence_t& sequence, diff --git a/storage/innobase/include/row0row.h b/storage/innobase/include/row0row.h index 4f329f0d675a1..90e35195365b3 100644 --- a/storage/innobase/include/row0row.h +++ b/storage/innobase/include/row0row.h @@ -153,9 +153,9 @@ row_build( consulted instead; the user columns in this table should be the same columns as in index->table */ - const dtuple_t* add_cols, + const dtuple_t* defaults, /*!< in: default values of - added columns, or NULL */ + added, changed columns, or NULL */ const ulint* col_map,/*!< in: mapping of old column numbers to new ones, or NULL */ row_ext_t** ext, /*!< out, own: cache of @@ -177,7 +177,7 @@ addition of new virtual columns. of an index, or NULL if index->table should be consulted instead -@param[in] add_cols default values of added columns, or NULL +@param[in] defaults default values of added, changed columns, or NULL @param[in] add_v new virtual columns added along with new indexes @param[in] col_map mapping of old column @@ -194,7 +194,7 @@ row_build_w_add_vcol( const rec_t* rec, const ulint* offsets, const dict_table_t* col_table, - const dtuple_t* add_cols, + const dtuple_t* defaults, const dict_add_v_col_t* add_v, const ulint* col_map, row_ext_t** ext, diff --git a/storage/innobase/row/row0log.cc b/storage/innobase/row/row0log.cc index 670367d42d45d..9364f37890a64 100644 --- a/storage/innobase/row/row0log.cc +++ b/storage/innobase/row/row0log.cc @@ -183,8 +183,9 @@ struct row_log_t { index that is being created online */ bool same_pk;/*!< whether the definition of the PRIMARY KEY has remained the same */ - const dtuple_t* add_cols; - /*!< default values of added columns, or NULL */ + const dtuple_t* defaults; + /*!< default values of added, changed columns, + or NULL */ const ulint* col_map;/*!< mapping of old column numbers to new ones, or NULL if !table */ dberr_t error; /*!< error that occurred during online @@ -220,6 +221,9 @@ struct row_log_t { decryption or NULL */ const char* path; /*!< where to create temporary file during log operation */ + bool ignore; /*!< Whether the alter ignore is being used; + if not, NULL values will not be converted to + defaults */ }; /** Create the file or online log if it does not exist. @@ -1151,7 +1155,9 @@ row_log_table_get_pk_col( const ulint* offsets, ulint i, const page_size_t& page_size, - ulint max_len) + ulint max_len, + bool ignore, + const dtuple_t* defaults) { const byte* field; ulint len; @@ -1159,7 +1165,12 @@ row_log_table_get_pk_col( field = rec_get_nth_field(rec, offsets, i, &len); if (len == UNIV_SQL_NULL) { - return(DB_INVALID_NULL); + if (!ignore || !defaults->fields[i].data) { + return(DB_INVALID_NULL); + } + + field = static_cast(defaults->fields[i].data); + len = defaults->fields[i].len; } if (rec_offs_nth_extern(offsets, i)) { @@ -1323,7 +1334,8 @@ row_log_table_get_pk( log->error = row_log_table_get_pk_col( col, ifield, dfield, *heap, - rec, offsets, i, page_size, max_len); + rec, offsets, i, page_size, max_len, + log->ignore, log->defaults); if (log->error != DB_SUCCESS) { err_exit: @@ -1338,10 +1350,10 @@ row_log_table_get_pk( /* No matching column was found in the old table, so this must be an added column. Copy the default value. */ - ut_ad(log->add_cols); + ut_ad(log->defaults); dfield_copy(dfield, dtuple_get_nth_field( - log->add_cols, col_no)); + log->defaults, col_no)); mbminlen = dfield->type.mbminlen; mbmaxlen = dfield->type.mbmaxlen; prtype = dfield->type.prtype; @@ -1512,8 +1524,8 @@ row_log_table_apply_convert_mrec( *error = DB_SUCCESS; /* This is based on row_build(). */ - if (log->add_cols) { - row = dtuple_copy(log->add_cols, heap); + if (log->defaults) { + row = dtuple_copy(log->defaults, heap); /* dict_table_copy_types() would set the fields to NULL */ for (ulint i = 0; i < dict_table_get_n_cols(log->table); i++) { dict_col_copy_type( @@ -1634,9 +1646,17 @@ row_log_table_apply_convert_mrec( if ((new_col->prtype & DATA_NOT_NULL) && dfield_is_null(dfield)) { - /* We got a NULL value for a NOT NULL column. */ - *error = DB_INVALID_NULL; - return(NULL); + + const dfield_t& default_field + = log->defaults->fields[col_no]; + + if (!log->ignore || !default_field.data) { + /* We got a NULL value for a NOT NULL column. */ + *error = DB_INVALID_NULL; + return NULL; + } + + *dfield = default_field; } /* Adjust the DATA_NOT_NULL flag in the parsed row. */ @@ -3147,12 +3167,13 @@ row_log_allocate( or NULL when creating a secondary index */ bool same_pk,/*!< in: whether the definition of the PRIMARY KEY has remained the same */ - const dtuple_t* add_cols, + const dtuple_t* defaults, /*!< in: default values of - added columns, or NULL */ + added, changed columns, or NULL */ const ulint* col_map,/*!< in: mapping of old column numbers to new ones, or NULL if !table */ - const char* path) /*!< in: where to create temporary file */ + const char* path, /*!< in: where to create temporary file */ + const bool ignore) /*!< in: alter ignore issued */ { row_log_t* log; DBUG_ENTER("row_log_allocate"); @@ -3162,7 +3183,7 @@ row_log_allocate( ut_ad(!table || index->table != table); ut_ad(same_pk || table); ut_ad(!table || col_map); - ut_ad(!add_cols || col_map); + ut_ad(!defaults || col_map); ut_ad(rw_lock_own(dict_index_get_lock(index), RW_LOCK_X)); ut_ad(trx_state_eq(trx, TRX_STATE_ACTIVE)); ut_ad(trx->id); @@ -3179,7 +3200,7 @@ row_log_allocate( log->blobs = NULL; log->table = table; log->same_pk = same_pk; - log->add_cols = add_cols; + log->defaults = defaults; log->col_map = col_map; log->error = DB_SUCCESS; log->min_trx = trx->id; @@ -3191,6 +3212,7 @@ row_log_allocate( log->head.blocks = log->head.bytes = 0; log->head.total = 0; log->path = path; + log->ignore=ignore; dict_index_set_online_status(index, ONLINE_INDEX_CREATION); index->online_log = log; diff --git a/storage/innobase/row/row0merge.cc b/storage/innobase/row/row0merge.cc index 264651bfaa318..669bbd59a0e2f 100644 --- a/storage/innobase/row/row0merge.cc +++ b/storage/innobase/row/row0merge.cc @@ -1656,7 +1656,7 @@ containing the index entries for the indexes to be built. @param[in] files temporary files @param[in] key_numbers MySQL key numbers to create @param[in] n_index number of indexes to create -@param[in] add_cols default values of added columns, or NULL +@param[in] defaults default values of added, changed columns, or NULL @param[in] add_v newly added virtual columns along with indexes @param[in] col_map mapping of old column numbers to new ones, or NULL if old_table == new_table @@ -1689,7 +1689,7 @@ row_merge_read_clustered_index( merge_file_t* files, const ulint* key_numbers, ulint n_index, - const dtuple_t* add_cols, + const dtuple_t* defaults, const dict_add_v_col_t* add_v, const ulint* col_map, ulint add_autoinc, @@ -1743,7 +1743,7 @@ row_merge_read_clustered_index( DBUG_ENTER("row_merge_read_clustered_index"); ut_ad((old_table == new_table) == !col_map); - ut_ad(!add_cols || col_map); + ut_ad(!defaults || col_map); ut_ad(trx_state_eq(trx, TRX_STATE_ACTIVE)); ut_ad(trx->id); @@ -2187,19 +2187,26 @@ row_merge_read_clustered_index( row = row_build_w_add_vcol(ROW_COPY_POINTERS, clust_index, rec, offsets, new_table, - add_cols, add_v, col_map, &ext, + defaults, add_v, col_map, &ext, row_heap); ut_ad(row); for (ulint i = 0; i < n_nonnull; i++) { - const dfield_t* field = &row->fields[nonnull[i]]; + dfield_t* field = &row->fields[nonnull[i]]; ut_ad(dfield_get_type(field)->prtype & DATA_NOT_NULL); if (dfield_is_null(field)) { - err = DB_INVALID_NULL; - trx->error_key_num = 0; - goto func_exit; + const dfield_t& default_field + = defaults->fields[nonnull[i]]; + + if (default_field.data == NULL) { + err = DB_INVALID_NULL; + trx->error_key_num = 0; + goto func_exit; + } + + *field = default_field; } } @@ -4548,7 +4555,7 @@ old_table unless creating a PRIMARY KEY @param[in] n_indexes size of indexes[] @param[in,out] table MySQL table, for reporting erroneous key value if applicable -@param[in] add_cols default values of added columns, or NULL +@param[in] defaults default values of added, changed columns, or NULL @param[in] col_map mapping of old column numbers to new ones, or NULL if old_table == new_table @param[in] add_autoinc number of added AUTO_INCREMENT columns, or @@ -4574,7 +4581,7 @@ row_merge_build_indexes( const ulint* key_numbers, ulint n_indexes, struct TABLE* table, - const dtuple_t* add_cols, + const dtuple_t* defaults, const ulint* col_map, ulint add_autoinc, ib_sequence_t& sequence, @@ -4610,7 +4617,7 @@ row_merge_build_indexes( ut_ad(!srv_read_only_mode); ut_ad((old_table == new_table) == !col_map); - ut_ad(!add_cols || col_map); + ut_ad(!defaults || col_map); stage->begin_phase_read_pk(skip_pk_sort && new_table != old_table ? n_indexes - 1 @@ -4744,7 +4751,7 @@ row_merge_build_indexes( error = row_merge_read_clustered_index( trx, table, old_table, new_table, online, indexes, fts_sort_idx, psort_info, merge_files, key_numbers, - n_indexes, add_cols, add_v, col_map, add_autoinc, + n_indexes, defaults, add_v, col_map, add_autoinc, sequence, block, skip_pk_sort, &tmpfd, stage, pct_cost, crypt_block, eval_table, drop_historical); diff --git a/storage/innobase/row/row0row.cc b/storage/innobase/row/row0row.cc index f9aa4fadacc98..2664f8a3d9e24 100644 --- a/storage/innobase/row/row0row.cc +++ b/storage/innobase/row/row0row.cc @@ -357,7 +357,7 @@ addition of new virtual columns. of an index, or NULL if index->table should be consulted instead -@param[in] add_cols default values of added columns, or NULL +@param[in] defaults default values of added/changed columns, or NULL @param[in] add_v new virtual columns added along with new indexes @param[in] col_map mapping of old column @@ -375,7 +375,7 @@ row_build_low( const rec_t* rec, const ulint* offsets, const dict_table_t* col_table, - const dtuple_t* add_cols, + const dtuple_t* defaults, const dict_add_v_col_t* add_v, const ulint* col_map, row_ext_t** ext, @@ -441,13 +441,13 @@ row_build_low( if (!col_table) { ut_ad(!col_map); - ut_ad(!add_cols); + ut_ad(!defaults); col_table = index->table; } - if (add_cols) { + if (defaults) { ut_ad(col_map); - row = dtuple_copy(add_cols, heap); + row = dtuple_copy(defaults, heap); /* dict_table_copy_types() would set the fields to NULL */ for (ulint i = 0; i < dict_table_get_n_cols(col_table); i++) { dict_col_copy_type( @@ -594,9 +594,9 @@ row_build( of an index, or NULL if index->table should be consulted instead */ - const dtuple_t* add_cols, + const dtuple_t* defaults, /*!< in: default values of - added columns, or NULL */ + added and changed columns, or NULL */ const ulint* col_map,/*!< in: mapping of old column numbers to new ones, or NULL */ row_ext_t** ext, /*!< out, own: cache of @@ -606,7 +606,7 @@ row_build( the memory needed is allocated */ { return(row_build_low(type, index, rec, offsets, col_table, - add_cols, NULL, col_map, ext, heap)); + defaults, NULL, col_map, ext, heap)); } /** An inverse function to row_build_index_entry. Builds a row from a @@ -622,7 +622,7 @@ addition of new virtual columns. of an index, or NULL if index->table should be consulted instead -@param[in] add_cols default values of added columns, or NULL +@param[in] defaults default values of added, changed columns, or NULL @param[in] add_v new virtual columns added along with new indexes @param[in] col_map mapping of old column @@ -639,14 +639,14 @@ row_build_w_add_vcol( const rec_t* rec, const ulint* offsets, const dict_table_t* col_table, - const dtuple_t* add_cols, + const dtuple_t* defaults, const dict_add_v_col_t* add_v, const ulint* col_map, row_ext_t** ext, mem_heap_t* heap) { return(row_build_low(type, index, rec, offsets, col_table, - add_cols, add_v, col_map, ext, heap)); + defaults, add_v, col_map, ext, heap)); } /** Convert an index record to a data tuple.