/**************************************************//**
@file row/row0mysql.cc
Interface between Innobase row operations and MySQL.
Contains also create table and other data dictionary operations.
Created 9/17/2000 Heikki Tuuri
*******************************************************/
/** Delay an INSERT, DELETE or UPDATE operation if the purge is lagging. */ staticvoid row_mysql_delay_if_needed() noexcept
{ constauto delay= srv_dml_needed_delay; if (UNIV_UNLIKELY(delay != 0))
{ /* Adjust for purge_coordinator_state::refresh() */
log_sys.latch.rd_lock(); const lsn_t last= log_sys.last_checkpoint_lsn,
max_age= log_sys.max_checkpoint_age; const lsn_t lsn= log_sys.get_flushed_lsn();
log_sys.latch.rd_unlock(); if ((lsn - last) / 4 >= max_age / 5)
buf_flush_ahead(last + max_age / 5, false);
purge_sys.wake_if_not_active();
std::this_thread::sleep_for(std::chrono::microseconds(delay));
}
}
/*******************************************************************//**
Frees the blob heap in prebuilt when no longer needed. */ void
row_mysql_prebuilt_free_blob_heap( /*==============================*/
row_prebuilt_t* prebuilt) /*!< in: prebuilt struct of a
ha_innobase:: table handle */
{
DBUG_ENTER("row_mysql_prebuilt_free_blob_heap");
/*******************************************************************//**
Stores a >= 5.0.3 format true VARCHAR length to dest, in the MySQL row
format.
@return pointer to the data, we skip the 1or2 bytes at the start
that are used to store the len */
byte*
row_mysql_store_true_var_len( /*=========================*/
byte* dest, /*!< in: where to store */
ulint len, /*!< in: length, must fit in two bytes */
ulint lenlen) /*!< in: storage length of len: either 1 or 2 bytes */
{ if (lenlen == 2) {
ut_a(len < 256 * 256);
mach_write_to_2_little_endian(dest, len);
return(dest + 2);
}
ut_a(lenlen == 1);
ut_a(len < 256);
mach_write_to_1(dest, len);
return(dest + 1);
}
/*******************************************************************//**
Reads a >= 5.0.3 format true VARCHAR length, in the MySQL row format, and
returns a pointer to the data.
@return pointer to the data, we skip the 1or2 bytes at the start
that are used to store the len */ const byte*
row_mysql_read_true_varchar( /*========================*/
ulint* len, /*!< out: variable-length field length */ const byte* field, /*!< in: field in the MySQL format */
ulint lenlen) /*!< in: storage length of len: either 1
or 2 bytes */
{ if (lenlen == 2) {
*len = mach_read_from_2_little_endian(field);
return(field + 2);
}
ut_a(lenlen == 1);
*len = mach_read_from_1(field);
return(field + 1);
}
/*******************************************************************//**
Stores a reference to a BLOB in the MySQL format. */ void
row_mysql_store_blob_ref( /*=====================*/
byte* dest, /*!< in: where to store */
ulint col_len,/*!< in: dest buffer size: determines into howmanybytestheBLOBlengthisstored, thespaceforthelengthmayvaryfrom1
to 4 bytes */ constvoid* data, /*!< in: BLOB data; if the value to store
is SQL NULL this should be NULL pointer */
ulint len) /*!< in: BLOB length; if the value to store isSQLNULLthisshouldbe0;remember alsotosettheNULLbitintheMySQLrecord
header! */
{ /* MySQL might assume the field is set to zero except the length and
the pointer fields */
memset(dest, '\0', col_len);
/* In dest there are 1 - 4 bytes reserved for the BLOB length, andafterthat8bytesreservedforthepointertothedata. In32-bitarchitecturesweonlyusethefirst4bytesofthepointer
slot. */
/*******************************************************************//**
Reads a reference to a BLOB in the MySQL format.
@return pointer to BLOB data */ const byte*
row_mysql_read_blob_ref( /*====================*/
ulint* len, /*!< out: BLOB length */ const byte* ref, /*!< in: BLOB reference in the
MySQL format */
ulint col_len) /*!< in: BLOB reference length
(not BLOB length) */
{
byte* data;
if (!*len) { /* Field_blob::store() if (!length) would encode both thelengthandthepointerinthesamearea.Anempty stringmustbeavalid(nonnull)pointerinthe
collation functions that cmp_data() may invoke. */ return ref;
}
memcpy(&data, ref + col_len - 8, sizeof data);
return(data);
}
/*******************************************************************//**
Converting InnoDB geometry data format to MySQL data format. */ void
row_mysql_store_geometry( /*=====================*/
byte* dest, /*!< in/out: where to store */
ulint dest_len, /*!< in: dest buffer size: determines intohowmanybytestheGEOMETRYlength isstored,thespaceforthelength
may vary from 1 to 4 bytes */ const byte* src, /*!< in: GEOMETRY data; if the value to storeisSQLNULLthisshouldbeNULL
pointer */
ulint src_len) /*!< in: GEOMETRY length; if the value tostoreisSQLNULLthisshouldbe0; rememberalsotosettheNULLbitin
the MySQL record header! */
{ /* MySQL might assume the field is set to zero except the length and
the pointer fields */
MEM_CHECK_DEFINED(src, src_len);
memset(dest, '\0', dest_len);
/* In dest there are 1 - 4 bytes reserved for the BLOB length, andafterthat8bytesreservedforthepointertothedata. In32-bitarchitecturesweonlyusethefirst4bytesofthepointer
slot. */
/*******************************************************************//**
Read geometry data in the MySQL format.
@return pointer to geometry data */ static const byte*
row_mysql_read_geometry( /*====================*/
ulint* len, /*!< out: data length */ const byte* ref, /*!< in: geometry data in the
MySQL format */
ulint col_len) /*!< in: MySQL format length */
{
byte* data;
ut_ad(col_len > 8);
/**************************************************************//**
Pad a column with spaces. */ void
row_mysql_pad_col( /*==============*/
ulint mbminlen, /*!< in: minimum size of a character,
in bytes */
byte* pad, /*!< out: padded buffer */
ulint len) /*!< in: number of bytes to pad */
{ const byte* pad_end;
/**************************************************************//**
Stores a non-SQL-NULL field given in the MySQL format in the InnoDB format.
The counterpart of this function is row_sel_field_store_in_mysql_format() in
row0sel.cc.
@return up to which byte we used buf in the conversion */
byte*
row_mysql_store_col_in_innobase_format( /*===================================*/
dfield_t* dfield, /*!< in/out: dfield where dtype informationmustbealreadysetwhen
this function is called! */
byte* buf, /*!< in/out: buffer for a converted integervalue;thismustbeatleast col_lenlongthen!NOTEthatdfield mayalsogetapointerto'buf', thereforedonotdiscardthisaslong
as dfield is used! */
ibool row_format_col, /*!< TRUE if the mysql_data is from aMySQLrow,FALSEiffromaMySQL keyvalue; inMySQL,atrueVARCHARstorage formatdiffersinarowandina keyvalue:inakeyvaluethelength
is always stored in 2 bytes! */ const byte* mysql_data, /*!< in: MySQL column value, not SQLNULL;NOTEthatdfieldmayalso getapointertomysql_data, thereforedonotdiscardthisaslong
as dfield is used! */
ulint col_len, /*!< in: MySQL column length; NOTE that thisisthestoragelengthofthe columnintheMySQLformatrow,not necessarilythelengthoftheactual payloaddata;ifthecolumnisatrue
VARCHAR then this is irrelevant */
ulint comp) /*!< in: nonzero=compact format */
{ const byte* ptr = mysql_data; const dtype_t* dtype;
ulint type;
ulint lenlen;
dtype = dfield_get_type(dfield);
type = dtype->mtype;
if (type == DATA_INT) { /* Store integer data in Innobase in a big-endian format, signbitnegatedifthedataisasignedinteger.InMySQL,
integers are stored in a little-endian format. */
byte* p = buf + col_len;
for (;;) {
p--;
*p = *mysql_data; if (p == buf) { break;
}
mysql_data++;
}
if (!(dtype->prtype & DATA_UNSIGNED)) {
*buf ^= 128;
}
ptr = buf;
buf += col_len;
} elseif ((type == DATA_VARCHAR
|| type == DATA_VARMYSQL
|| type == DATA_BINARY)) {
if (dtype_get_mysql_type(dtype) == DATA_MYSQL_TRUE_VARCHAR) { /* The length of the actual data is stored to 1 or 2
bytes at the start of the field */
if (row_format_col) { if (dtype->prtype & DATA_LONG_TRUE_VARCHAR) {
lenlen = 2;
} else {
lenlen = 1;
}
} else { /* In a MySQL key value, lenlen is always 2 */
lenlen = 2;
}
ptr = row_mysql_read_true_varchar(&col_len, mysql_data,
lenlen);
} else { /* Remove trailing spaces from old style VARCHAR
columns. */
/* Strip space padding. */ while (col_len > n_chars && ptr[col_len - 1] == 0x20) {
col_len--;
}
} elseif (!row_format_col) { /* if mysql data is from a MySQL key value sincethelengthisalwaysstoredin2bytes,
we need do nothing here. */
} elseif (type == DATA_BLOB) {
/**************************************************************//**
Convert a row in the MySQL format to a row in the Innobase format. Note that
the function to convert a MySQL format key value to an InnoDB dtuple is
row_sel_convert_mysql_key_to_innobase() in row0sel.cc.
@return DB_SUCCESS or error code */ static
dberr_t
row_mysql_convert_row_to_innobase( /*==============================*/
dtuple_t* row, /*!< in/out: Innobase row where the fieldtypeinformationisalready
copied there! */
row_prebuilt_t* prebuilt, /*!< in: prebuilt struct where template
must be of type ROW_MYSQL_WHOLE_ROW */ const byte* mysql_rec, /*!< in: row in the MySQL format; NOTE:donotdiscardaslongas rowisused,asrowmaycontain
pointers to this record! */
mem_heap_t** blob_heap) /*!< in: FIX_ME, remove this after
server fixes its issue */
{ const mysql_row_templ_t*templ;
dfield_t* dfield;
ulint i;
ulint n_col = 0;
ulint n_v_col = 0;
if (templ->mysql_null_bit_mask != 0) { /* Column may be SQL NULL */
if (mysql_rec[templ->mysql_null_byte_offset]
& (byte) (templ->mysql_null_bit_mask)) {
/* It is SQL NULL */
dfield_set_null(dfield);
goto next_column;
}
}
row_mysql_store_col_in_innobase_format(
dfield,
prebuilt->ins_upd_rec_buff + templ->mysql_col_offset, TRUE, /* MySQL row format data */
mysql_rec + templ->mysql_col_offset,
templ->mysql_col_len,
dict_table_is_comp(prebuilt->table));
/* server has issue regarding handling BLOB virtual fields,
and we need to duplicate it with our own memory here */ if (templ->is_virtual
&& DATA_LARGE_MTYPE(dfield_get_type(dfield)->mtype)) { if (*blob_heap == NULL) {
*blob_heap = mem_heap_create(dfield->len);
}
dfield_dup(dfield, *blob_heap);
}
next_column:
;
}
/* If there is a FTS doc id column and it is not user supplied (
generated by server) then assign it a new doc id. */ if (!prebuilt->table->fts) { return DB_SUCCESS;
}
/****************************************************************//**
Handles user errors and lock waits detected by the database engine.
@returntrueif it was a lock wait and we should continue running the
query thread and in that case the thr is ALREADY in the running state. */ bool
row_mysql_handle_errors( /*====================*/
dberr_t* new_err,/*!< out: possible new error encountered in lockwait,orifnonewerror,thevalue oftrx->error_stateattheentryofthis
function */
trx_t* trx, /*!< in: transaction */
que_thr_t* thr, /*!< in: query thread, or NULL */ const undo_no_t*savept) /*!< in: pointer to savepoint, or nullptr */
{
dberr_t err;
switch (err) { case DB_LOCK_WAIT_TIMEOUT: extern my_bool innobase_rollback_on_timeout; if (innobase_rollback_on_timeout) { goto rollback;
} /* fall through */ case DB_DUPLICATE_KEY: case DB_FOREIGN_DUPLICATE_KEY: case DB_TOO_BIG_RECORD: case DB_UNDO_RECORD_TOO_BIG: case DB_ROW_IS_REFERENCED: case DB_NO_REFERENCED_ROW: case DB_CANNOT_ADD_CONSTRAINT: case DB_TOO_MANY_CONCURRENT_TRXS: case DB_OUT_OF_FILE_SPACE: case DB_READ_ONLY: case DB_FTS_INVALID_DOCID: case DB_INTERRUPTED: case DB_CANT_CREATE_GEOMETRY_OBJECT: case DB_TABLE_NOT_FOUND: case DB_DECRYPTION_FAILED: case DB_COMPUTE_VALUE_FAILED:
rollback_to_savept:
DBUG_EXECUTE_IF("row_mysql_crash_if_error", {
log_buffer_flush_to_disk();
DBUG_SUICIDE(); }); if (savept) { /* Roll back the latest, possibly incomplete insertion
or update */
trx->rollback(savept);
} if (!trx->bulk_insert) { /* MariaDB will roll back the latest SQL statement */ break;
} /* For DML, InnoDB does partial rollback and clear bulkbufferinrow_mysql_handle_errors(). ForALTERTABLEALGORITHM=COPY&CREATETABLE...SELECT, thebulkinserttransactionwillberolledbackinside
ha_innobase::extra(HA_EXTRA_ABORT_COPY) */
trx->clear_dml_bulk();
trx->last_stmt_start = 0; break; case DB_LOCK_WAIT:
err = lock_wait(thr); if (err != DB_SUCCESS) { goto handle_new_error;
}
*new_err = err;
DBUG_RETURN(true);
case DB_DEADLOCK: case DB_RECORD_CHANGED: case DB_LOCK_TABLE_FULL: case DB_TEMP_FILE_WRITE_FAIL:
rollback: /* Roll back the whole transaction; this resolution was added
to version 3.23.43 */
trx->rollback(); break;
case DB_IO_ERROR: case DB_TABLE_CORRUPT: case DB_CORRUPTION: case DB_PAGE_CORRUPTED:
ib::error() << "We detected index corruption in an InnoDB type" " table. You have to dump + drop + reimport the" " table or, in a case of widespread corruption," " dump all InnoDB tables and recreate the whole" " tablespace. If the mariadbd server crashes after" " the startup or when you dump the tables. "
<< FORCE_RECOVERY_MSG; goto rollback_to_savept; case DB_FOREIGN_EXCEED_MAX_CASCADE:
ib::error() << "Cannot delete/update rows with cascading" " foreign key constraints that exceed max depth of "
<< FK_MAX_CASCADE_DEL << ". Please drop excessive" " foreign constraints and try again"; goto rollback_to_savept; case DB_UNSUPPORTED:
ib::error() << "Cannot delete/update rows with cascading" " foreign key constraints in timestamp-based temporal" " table. Please drop excessive" " foreign constraints and try again"; goto rollback_to_savept; default:
ib::fatal() << "Unknown error " << err;
}
/********************************************************************//**
Create a prebuilt structfor a MySQL table handle.
@return own: a prebuilt struct */
row_prebuilt_t*
row_create_prebuilt( /*================*/
dict_table_t* table, /*!< in: Innobase table handle */
ulint mysql_row_len) /*!< in: length in bytes of a row in
the MySQL format */
{
DBUG_ENTER("row_create_prebuilt");
/* Make sure that search_tuple is long enough for clustered index */
ut_a(2 * unsigned(table->n_cols) >= unsigned(clust_index->n_fields)
- clust_index->table->n_dropped());
ref_len = dict_index_get_n_unique(clust_index);
/* Maximum size of the buffer needed for conversion of INTs from littleendianformattobigendianformatinanindex.Anindex canhavemaximum16columns(MAX_REF_PARTS)init.Therefore MaxsizeforPK:16*8bytes(BIGINT'ssize)=128bytes
Max size Secondary index: 16 * 8 bytes + PK = 256 bytes. */ #define MAX_SRCH_KEY_VAL_BUFFER 2* (8 * MAX_REF_PARTS)
#define PREBUILT_HEAP_INITIAL_SIZE \
( \ sizeof(*prebuilt) \ /* allocd in this function */ \
+ DTUPLE_EST_ALLOC(search_tuple_n_fields) \
+ DTUPLE_EST_ALLOC(ref_len) \ /* allocd in row_prebuild_sel_graph() */ \
+ sizeof(sel_node_t) \
+ sizeof(que_fork_t) \
+ sizeof(que_thr_t) \ /* allocd in row_get_prebuilt_update_vector() */ \
+ sizeof(upd_node_t) \
+ sizeof(upd_t) \
+ sizeof(upd_field_t) \
* dict_table_get_n_cols(table) \
+ sizeof(que_fork_t) \
+ sizeof(que_thr_t) \ /* allocd in row_get_prebuilt_insert_row() */ \
+ sizeof(ins_node_t) \ /* mysql_row_len could be huge and we are not \ sureifthisprebuiltinstanceisgoingtobe\
used in inserts */ \
+ (mysql_row_len < 256 ? mysql_row_len : 0) \
+ DTUPLE_EST_ALLOC(dict_table_get_n_cols(table) \
+ dict_table_get_n_v_cols(table)) \
+ sizeof(que_fork_t) \
+ sizeof(que_thr_t) \
+ sizeof(*prebuilt->pcur) \
+ sizeof(*prebuilt->clust_pcur) \
)
/* Calculate size of key buffer used to store search key in InnoDBformat.MySQLstoresINTsinlittleendianformatand InnoDBstoresINTsinbigendianformatwiththesignbit flipped.Allotherfieldtypesarestored/comparedthesame inMySQLandInnoDB,sowemustcreateabuffercontaining theINTkeypartsinInnoDBformat.Weneedtwosuchbuffers
since both start and end keys are used in records_in_range(). */
for (temp_index = dict_table_get_first_index(table); temp_index;
temp_index = dict_table_get_next_index(temp_index)) {
DBUG_EXECUTE_IF("innodb_srch_key_buffer_max_value",
ut_a(temp_index->n_user_defined_cols
== MAX_REF_PARTS);); if (temp_index->is_corrupted()) { continue;
}
uint temp_len = 0; for (uint i = 0; i < temp_index->n_uniq; i++) {
ulint type = temp_index->fields[i].col->mtype; if (type == DATA_INT) {
temp_len +=
temp_index->fields[i].fixed_len;
}
}
srch_key_len = std::max(srch_key_len,temp_len);
}
/* We allocate enough space for the objects that are likely to becreatedlaterinordertominimizethenumberofmalloc()
calls */
heap = mem_heap_create(PREBUILT_HEAP_INITIAL_SIZE + 2 * srch_key_len);
if (prebuilt->rtr_info) {
rtr_clean_rtr_info(prebuilt->rtr_info, true);
} if (prebuilt->table) {
prebuilt->table->release();
}
mem_heap_free(prebuilt->heap);
DBUG_VOID_RETURN;
}
/*********************************************************************//**
Updates the transaction pointers in query graphs stored in the prebuilt struct. */ void
row_update_prebuilt_trx( /*====================*/
row_prebuilt_t* prebuilt, /*!< in/out: prebuilt struct
in MySQL handle */
trx_t* trx) /*!< in: transaction handle */
{
ut_a(trx->magic_n == TRX_MAGIC_N);
ut_a(prebuilt->magic_n == ROW_PREBUILT_ALLOCATED);
ut_a(prebuilt->magic_n2 == ROW_PREBUILT_ALLOCATED);
prebuilt->trx = trx;
if (prebuilt->ins_graph) {
prebuilt->ins_graph->trx = trx;
}
if (prebuilt->upd_graph) {
prebuilt->upd_graph->trx = trx;
}
if (prebuilt->sel_graph) {
prebuilt->sel_graph->trx = trx;
}
}
/*********************************************************************//**
Gets pointer to a prebuilt dtuple used in insertions. If the insert graph
has not yet been built in the prebuilt struct, then this function first
builds it.
@return prebuilt dtuple; the column type information is also set in it */ static
dtuple_t*
row_get_prebuilt_insert_row( /*========================*/
row_prebuilt_t* prebuilt) /*!< in: prebuilt struct in MySQL
handle */
{
dict_table_t* table = prebuilt->table;
ut_ad(prebuilt && table && prebuilt->trx);
if (prebuilt->ins_node != 0) {
/* Check if indexes have been dropped or added and we
may need to rebuild the row insert template. */
/*********************************************************************//**
Sets an AUTO_INC type lock on the table mentioned in prebuilt. The
AUTO_INC lock gives exclusive access to the auto-inc counter of the
table. The lock is reserved only for the duration of an SQL statement.
It is not compatible with another AUTO_INC or exclusive lock on the
table.
@return error code or DB_SUCCESS */
dberr_t
row_lock_table_autoinc_for_mysql( /*=============================*/
row_prebuilt_t* prebuilt) /*!< in: prebuilt struct in the MySQL
table handle */
{
trx_t* trx = prebuilt->trx;
ins_node_t* node = prebuilt->ins_node; const dict_table_t* table = prebuilt->table;
que_thr_t* thr;
dberr_t err;
/* If we already hold an AUTOINC lock on the table then do nothing. Note:Wepeekatthevalueofthecurrentownerwithoutacquiring
lock_sys.latch. */ if (trx == table->autoinc_trx) {
if (err != DB_SUCCESS) {
error_exit: /* FIXME: What's this ? */
thr->lock_state = QUE_THR_LOCK_ROW;
/* Because we now allow multiple INSERT into the same initiallyemptytableinbulkinsertmode,onerrorwemust rollbacktothestartofthetransaction.Forcorrectness,it wouldsufficetorollbacktothestartofthefirstinsert intothisemptytable,butwewillkeepitsimpleandefficient.
fts_add_doc_from_tuple(&ftt, doc_id, node->row);
} else { /* Pass NULL for the columns affected, since an INSERT affects
all FTS indexes. */
fts_trx_add_op(trx, table, doc_id, FTS_INSERT, NULL);
}
}
/* Not protected by dict_sys.latch or table->stats_mutex_lock() forperformance reasons,wewouldrathergetgarbageinstat_n_rows(whichis justanestimateanyway)thanprotectingthefollowingcode
with a latch. */
dict_table_n_rows_inc(table);
if (prebuilt->clust_index_was_generated) { /* set row id to prebuilt */
memcpy(prebuilt->row_id, node->sys_buf, DATA_ROW_ID_LEN);
}
/*********************************************************************//**
Creates an query graph node of 'update' type to be used in the MySQL
interface.
@return own: update node */
upd_node_t*
row_create_update_node_for_mysql( /*=============================*/
dict_table_t* table, /*!< in: table to update */
mem_heap_t* heap) /*!< in: mem heap from which allocated */
{
upd_node_t* node;
/*********************************************************************//**
Gets pointer to a prebuilt update vector used in updates. If the update
graph has not yet been built in the prebuilt struct, then this function
first builds it.
@return prebuilt update vector */
upd_t*
row_get_prebuilt_update_vector( /*===========================*/
row_prebuilt_t* prebuilt) /*!< in: prebuilt struct in MySQL
handle */
{ if (prebuilt->upd_node == NULL) {
/* Not called before for this handle: create an update node
and query graph to the prebuilt struct */
/********************************************************************
Handle an update of a column that has an FTS index. */ static void
row_fts_do_update( /*==============*/
trx_t* trx, /* in: transaction */
dict_table_t* table, /* in: Table with FTS index */
doc_id_t old_doc_id, /* in: old document id */
doc_id_t new_doc_id) /* in: new document id */
{ if(trx->fts_next_doc_id) {
fts_trx_add_op(trx, table, old_doc_id, FTS_DELETE, NULL); if(new_doc_id != FTS_NULL_DOC_ID)
fts_trx_add_op(trx, table, new_doc_id, FTS_INSERT, NULL);
}
}
/************************************************************************ HandlesFTSmattersforanupdateoradelete.
NOTE: should not be called if the table does not have an FTS index. .*/ static
dberr_t
row_fts_update_or_delete( /*=====================*/
row_prebuilt_t* prebuilt) /* in: prebuilt struct in MySQL
handle */
{
trx_t* trx = prebuilt->trx;
dict_table_t* table = prebuilt->table;
upd_node_t* node = prebuilt->upd_node;
doc_id_t old_doc_id = prebuilt->fts_doc_id;
DBUG_ENTER("row_fts_update_or_delete");
ut_a(dict_table_has_fts_index(prebuilt->table));
/* Deletes are simple; get them out of the way first. */ if (node->is_delete) { /* A delete affects all FTS indexes, so we pass NULL */
fts_trx_add_op(trx, table, old_doc_id, FTS_DELETE, NULL);
} else {
doc_id_t new_doc_id;
new_doc_id = fts_read_doc_id((byte*) &trx->fts_next_doc_id);
if (new_doc_id == 0) {
ib::error() << "InnoDB FTS: Doc ID cannot be 0";
DBUG_RETURN(DB_FTS_INVALID_DOCID);
}
row_fts_do_update(trx, table, old_doc_id, new_doc_id);
}
DBUG_RETURN(DB_SUCCESS);
}
/*********************************************************************//**
Initialize the Doc ID system for FK table with FTS index */ static void
init_fts_doc_id_for_ref( /*====================*/
dict_table_t* table, /*!< in: table */
ulint* depth, /*!< in: recusive call depth */
THD* thd) /*!< in: caller's THD */
{ /* Limit on tables involved in cascading delete/update */ if (++*depth > FK_MAX_CASCADE_DEL) { return;
}
/* Loop through this table's referenced list and also
recursively traverse each table's foreign table list */ for (dict_foreign_t* foreign : table->referenced_set) {
ut_ad(foreign->foreign_table);
if (foreign->foreign_table->space
&& foreign->foreign_table->fts) {
doc_id_t ignored_doc_id; /* Best-effort init for the referenced FTS table;
ignore failure so other FKs are still walked. */
(void) fts_init_doc_id(foreign->foreign_table,
thd, &ignored_doc_id);
}
/* MySQL seems to call rnd_pos before updating each row it hascached:wecangetthecorrectcursorpositionfrom prebuilt->pcur;NOTEthatwecannotbuildtherowreference frommysql_reciftheclusteredindexwasautomatically generatedforthetable:MySQLdoesnotknowanythingabout
the row id used as the clustered index key */
if (is_delete) { /* Not protected by dict_sys.latch orprebuilt->table->stats_mutex_lock()forperformance reasons,wewouldrathergetgarbageinstat_n_rows(whichis justanestimateanyway)thanprotectingthefollowingcode
with a latch. */
dict_table_n_rows_dec(prebuilt->table);
if (update_statistics) {
dict_stats_update_if_needed(prebuilt->table, *trx);
} else { /* Always update the table modification counter. */
prebuilt->table->stat_modified_counter++;
}
error:
trx->op_info = "";
DBUG_RETURN(err);
}
/** This can only be used when the current transaction is at READCOMMITTEDorREADUNCOMMITTEDisolationlevel. Beforecallingthisfunctionrow_search_mvcc()musthave initializedprebuilt->new_rec_lockstostoretheinformationwhichnew recordlocksreallywereset.Thisfunctionremovesanewlyset clusteredindexrecordlockunderprebuilt->pcuror prebuilt->clust_pcur.Thus,thisimplementsa'mini-rollback'that releasesthelatestclusteredindexrecordlockweset. @param[in,out]prebuiltprebuiltstructinMySQLhandle @param[in]has_latches_on_recsTRUEifcalledsothatwehavethe latchesontherecordsunderpcur andclust_pcur,andwedonotneed
to reposition the cursors. */ void
row_unlock_for_mysql(
row_prebuilt_t* prebuilt,
ibool has_latches_on_recs)
{ if (prebuilt->new_rec_locks == 1 && prebuilt->index->is_clust()) {
trx_t* trx = prebuilt->trx;
ut_ad(trx->isolation_level <= TRX_ISO_READ_COMMITTED);
trx->op_info = "unlock_row";
/** Write query start time as SQL field data to a buffer. Needed by InnoDB. @paramthdThreadobject
@param buf Buffer to hold start time data */ void thd_get_query_start_data(THD *thd, char *buf);
/** Insert history row when evaluating foreign key referential action.
/**********************************************************************//**
Does a cascaded deleteor set null in a foreign key operation.
@return error code or DB_SUCCESS */
dberr_t
row_update_cascade_for_mysql( /*=========================*/
que_thr_t* thr, /*!< in: query thread */
upd_node_t* node, /*!< in: update node used in the cascade
or set null operation */
dict_table_t* table) /*!< in: table where we do the operation */
{ /* Increment fk_cascade_depth to record the recursive call depth on asingleupdate/deletethataffectsmultipletableschained
together with foreign key relations. */
if (++thr->fk_cascade_depth > FK_MAX_CASCADE_DEL) { return(DB_FOREIGN_EXCEED_MAX_CASCADE);
}
trx_t* trx = thr_get_trx(thr);
if (table->versioned()) { if (node->is_delete == PLAIN_DELETE) {
node->vers_make_delete(trx);
} elseif (node->update->affects_versioned()) {
dberr_t err = row_update_vers_insert(thr, node); if (err != DB_SUCCESS) { return err;
}
node->vers_make_update(trx);
}
}
for (;;) {
thr->run_node = node;
thr->prev_node = node;
if (stats) {
dict_stats_update_if_needed(node->table, *trx);
} else { /* Always update the table
modification counter. */
node->table->stat_modified_counter++;
}
return(DB_SUCCESS);
}
}
}
/*********************************************************************//**
Creates a table for MySQL. On failure the transaction will be rolled back and the 'table' object will be freed.
@return error code or DB_SUCCESS */
dberr_t
row_create_table_for_mysql( /*=======================*/
dict_table_t* table, /*!< in, own: table definition (willbefreed,oronDB_SUCCESS
added to the data dictionary cache) */
trx_t* trx) /*!< in/out: transaction */
{
tab_node_t* node;
mem_heap_t* heap;
que_thr_t* thr;
/*********************************************************************//**
Create an index when creating a table.
On failure, the caller must drop the table!
@return error number or DB_SUCCESS */
dberr_t
row_create_index_for_mysql( /*=======================*/
dict_index_t* index, /*!< in, own: index definition
(will be freed) */
trx_t* trx, /*!< in: transaction handle */ const ulint* field_lengths, /*!< in: if not NULL, must contain dict_index_get_n_fields(index) actualfieldlengthsforthe indexcolumns,whichare thencheckedfornotbeingtoo
large. */
fil_encryption_t mode, /*!< in: encryption mode */
uint32_t key_id) /*!< in: encryption key_id */
{
ind_node_t* node;
mem_heap_t* heap;
que_thr_t* thr;
dberr_t err;
ulint i;
ulint len;
dict_table_t* table = index->table;
ut_ad(dict_sys.locked());
for (i = 0; i < index->n_def; i++) { /* Check that prefix_len and actual length
< DICT_MAX_INDEX_COL_LEN */
len = dict_index_get_nth_field(index, i)->prefix_len;
if (field_lengths && field_lengths[i]) {
len = ut_max(len, field_lengths[i]);
}
DBUG_EXECUTE_IF( "ib_create_table_fail_at_create_index",
len = DICT_MAX_FIELD_LEN_BY_FORMAT(table) + 1;
);
/* Column or prefix length exceeds maximum column length */ if (len > (ulint) DICT_MAX_FIELD_LEN_BY_FORMAT(table)) {
dict_mem_index_free(index); return DB_TOO_BIG_INDEX_COL;
}
}
/* For temp-table we avoid insertion into SYSTEM TABLES to maintainperformanceandsowehaveseparatepaththatdirectly
just updates dictionary cache. */ if (!table->is_temporary()) {
ut_ad(trx->state == TRX_STATE_ACTIVE);
ut_ad(trx->dict_operation);
trx->op_info = "creating index";
/* Note that the space id where we store the index is inheritedfromthetableindict_build_index_def_step()
in dict0crea.cc. */
/* Note: This cannot be rolled back. Rollback would see the UPDATESYS_INDEXESastwooperations:DELETEandINSERT. Itwouldinvokebtr_free_if_exists()whenrollingbackthe
INSERT, effectively dropping all indexes of the table. */
err = que_eval_sql(
info, "PROCEDURE RENUMBER_TABLE_PROC () IS\n" "BEGIN\n" "UPDATE SYS_TABLES SET ID = :new_id\n" " WHERE ID = :old_id;\n" "UPDATE SYS_COLUMNS SET TABLE_ID = :new_id\n" " WHERE TABLE_ID = :old_id;\n" "UPDATE SYS_INDEXES SET TABLE_ID = :new_id\n" " WHERE TABLE_ID = :old_id;\n" "UPDATE SYS_VIRTUAL SET TABLE_ID = :new_id\n" " WHERE TABLE_ID = :old_id;\n" "END;\n", trx);
return(err);
}
/*********************************************************************//** Do the foreign key constraint checks.
@return DB_SUCCESS or error code. */ static
dberr_t
row_discard_tablespace_foreign_key_checks( /*======================================*/ const trx_t* trx, /*!< in: transaction handle */ const dict_table_t* table) /*!< in: table to be discarded */
{
ut_ad(!srv_read_only_mode);
ut_ad(!recv_sys.rpo);
if (!trx->check_foreigns) { return(DB_SUCCESS);
}
/* Check if the table is referenced by foreign key constraints from
some other table (not the table itself) */
dict_foreign_set::const_iterator it
= std::find_if(table->referenced_set.begin(),
table->referenced_set.end(),
dict_foreign_different_tables());
if (it == table->referenced_set.end()) { return(DB_SUCCESS);
}
const dict_foreign_t* foreign = *it;
FILE* ef = dict_foreign_err_file;
/* We only allow discarding a referenced table if
FOREIGN_KEY_CHECKS is set to 0 */
mysql_mutex_lock(&dict_foreign_err_mutex);
rewind(ef);
ut_print_timestamp(ef);
fputs(" Cannot DISCARD table ", ef);
ut_print_name(ef, trx, table->name.m_name);
fputs("\n" "because it is referenced by ", ef);
ut_print_name(ef, trx, foreign->foreign_table_name);
putc('\n', ef);
mysql_mutex_unlock(&dict_foreign_err_mutex);
return(DB_CANNOT_DROP_CONSTRAINT);
}
/*********************************************************************//** Do the DISCARD TABLESPACE operation.
@return DB_SUCCESS or error code. */ static
dberr_t
row_discard_tablespace( /*===================*/
trx_t* trx, /*!< in/out: transaction handle */
dict_table_t* table) /*!< in/out: table to be discarded */
{
ut_ad(table->magic_n == DICT_TABLE_MAGIC_N);
ut_ad(!table->is_temporary());
dberr_t err;
/* How do we prevent crashes caused by ongoing operations on thetable?Oldoperationscouldtrytoaccessnon-existent pages.TheSQLlayerwillblockallDMLonthetableusingMDLanda DISCARDwillnotstartunlessallexistingoperationsonthe tabletobediscardedarecompleted.
/* All persistent operations successful, update the
data dictionary memory cache. */
ut_ad(dict_sys.locked());
/* Remove the table from the hash table of id's */
dict_sys.table_id_hash.cell_get(ut_fold_ull(table->id))
->remove(*table, &dict_table_t::id_hash);
table->id = new_id;
dict_sys.table_id_hash.cell_get(ut_fold_ull(table->id))
->append(*table, &dict_table_t::id_hash);
dict_index_t* index = UT_LIST_GET_FIRST(table->indexes);
/* Reset the root page numbers. */ for (; index; index = UT_LIST_GET_NEXT(indexes, index)) {
index->page = FIL_NULL;
}
/* If the tablespace did not already exist or we couldn't writetoit,wetreatthatasasuccessfulDISCARD.Itis
unusable anyway. */ return DB_SUCCESS;
}
/*********************************************************************//**
Discards the tablespace of a table which stored in an .ibd file. Discarding
means that this function renames the .ibd file and assigns a new table id for
the table. Also the file_unreadable flag is set.
@return error code or DB_SUCCESS */
dberr_t row_discard_tablespace_for_mysql(dict_table_t *table, trx_t *trx)
{
ut_ad(!is_system_tablespace(table->space_id));
ut_ad(!table->is_temporary());
/* We serialize data dictionary operations with dict_sys.latch:
this is to avoid deadlocks during data dictionary operations */
err= row_discard_tablespace_foreign_key_checks(trx, table); if (err != DB_SUCCESS) goto rollback;
/* Note: The following cannot be rolled back. Rollback would see the UPDATEofSYS_INDEXES.TABLE_IDastwooperations:DELETEandINSERT. Itwouldinvokebtr_free_if_exists()whenrollingbacktheINSERT, effectivelydroppingallindexesofthetable.Furthermore,weare alreadydiscardingdatabeforethetransactioniscommitted.
/****************************************************************//** Delete a single constraint.
@return error code or DB_SUCCESS */ static MY_ATTRIBUTE((nonnull, warn_unused_result))
dberr_t
row_delete_constraint_low( /*======================*/ constchar* id, /*!< in: constraint id */
trx_t* trx) /*!< in: transaction handle */
{
pars_info_t* info = pars_info_create();
pars_info_add_str_literal(info, "id", id);
return(que_eval_sql(info, "PROCEDURE DELETE_CONSTRAINT () IS\n" "BEGIN\n" "DELETE FROM SYS_FOREIGN_COLS WHERE ID = :id;\n" "DELETE FROM SYS_FOREIGN WHERE ID = :id;\n" "END;\n", trx));
}
/****************************************************************//** Delete a single constraint.
@return error code or DB_SUCCESS */ static MY_ATTRIBUTE((nonnull, warn_unused_result))
dberr_t
row_delete_constraint( /*==================*/ constchar* id, /*!< in: constraint id */ constchar* name, /*!< in: table name */
mem_heap_t* heap, /*!< in: memory heap */
trx_t* trx) /*!< in: transaction handle */
{ const size_t s= strlen(id) + strlen(name) + 2; char *fk= static_cast<char*>(mem_heap_alloc(heap, s));
snprintf(fk, s, "%s\377%s", name, id);
dberr_t err= row_delete_constraint_low(fk, trx); if (err == DB_SUCCESS)
{ /* Also try dropping the MySQL 4.0.18 or 4.1.2 constraints,
with schemaname/constraintname. */
snprintf(fk, s, "%.*s%s", int(dict_get_db_name_len(name) + 1), name, id);
err= row_delete_constraint_low(fk, trx);
} if (err == DB_SUCCESS && !strchr(id, '/')) /* Before MySQL 4.0.18 or 4.1.2, constraints were of the form %lu_%lu(a64-bitglobalidentifiersplitintotwo32-bit
parts). Let them be dropped. */
err= row_delete_constraint_low(id, trx);
return err;
}
/*********************************************************************//**
Renames a table for MySQL.
@return error code or DB_SUCCESS */
dberr_t
row_rename_table_for_mysql( /*=======================*/ constchar* old_name, /*!< in: old table name */ constchar* new_name, /*!< in: new table name */
trx_t* trx, /*!< in/out: transaction */
rename_fk fk) /*!< in: how to handle
FOREIGN KEY constraints */
{
dict_table_t* table = NULL;
dberr_t err = DB_ERROR;
mem_heap_t* heap = NULL; constchar** constraints_to_drop = NULL;
ulint n_constraints_to_drop = 0;
ibool old_is_tmp, new_is_tmp;
pars_info_t* info = NULL;
err = que_eval_sql(
info, "PROCEDURE RENAME_CONSTRAINT_IDS () IS\n" "old CHAR; new CHAR; found INT; p INT;\n" "BEGIN\n" "found := 1;\n" "WHILE found = 1 LOOP\n" " SELECT ID INTO old FROM SYS_FOREIGN\n" " WHERE FOR_NAME = :old_name\n" " AND TO_BINARY(FOR_NAME)=TO_BINARY(:old_name)\n" " LOCK IN SHARE MODE;\n" " IF (SQL % NOTFOUND) THEN\n" " found := 0;\n" " ELSE\n" " p := INSTR(old, '\377');\n" " IF p = 0 THEN p := INSTR(old, '/'); END IF;\n" " new:=CONCAT(:new_name,'\377',\n" " SUBSTR(old,p,LENGTH(old)-1));\n" " UPDATE SYS_FOREIGN\n" " SET ID=new, FOR_NAME=:new_name WHERE ID=old;\n" " UPDATE SYS_FOREIGN_COLS\n" " SET ID=new WHERE ID=old;\n" " END IF;\n" "END LOOP;\n" "UPDATE SYS_FOREIGN SET REF_NAME = :new_name\n" "WHERE REF_NAME = :old_name\n" "AND TO_BINARY(REF_NAME)=TO_BINARY(:old_name);\n" "END;\n", trx); if (err == DB_DUPLICATE_KEY) {
err = DB_FOREIGN_DUPLICATE_KEY;
}
} elseif (n_constraints_to_drop > 0) { /* Drop some constraints of tmp tables. */ for (auto i = n_constraints_to_drop; i--; ) {
err = row_delete_constraint(constraints_to_drop[i],
old_name, heap, trx);
if (err == DB_SUCCESS) {
DEBUG_SYNC_C("innodb_rename_in_cache"); /* The following call will also rename the .ibd file */
err = dict_table_rename_in_cache(
table, span<constchar>{new_name,strlen(new_name)}, false);
}
if (err == DB_SUCCESS) { /* In case of copy alter, template db_name and table_nameshouldberenamedonlyfornewly
created table. */ if (table->vc_templ != NULL && !new_is_tmp) {
innobase_rename_vc_templ(table);
}
/* We only want to switch off some of the type checking in
an ALTER TABLE, not in a RENAME. */
dict_names_t fk_tables;
{
mtr_t mtr{trx};
err = dict_load_foreigns(mtr, new_name, nullptr,
trx->id,
!old_is_tmp
|| trx->check_foreigns,
fk == RENAME_ALTER_COPY
? DICT_ERR_IGNORE_NONE
: DICT_ERR_IGNORE_FK_NOKEY,
fk_tables);
} if (err != DB_SUCCESS) { if (old_is_tmp) { /* In case of copy alter, ignore the loadingofforeignkeyconstraint
when foreign_key_check is disabled */
ib::error_or_warn(trx->check_foreigns)
<< "In ALTER TABLE "
<< ut_get_name(trx, new_name)
<< " has or is referenced in foreign" " key constraints which are not" " compatible with the new table" " definition."; if (!trx->check_foreigns) {
err = DB_SUCCESS; goto funct_exit;
}
} else {
ib::error() << "In RENAME TABLE table "
<< ut_get_name(trx, new_name)
<< " is referenced in foreign key" " constraints which are not compatible" " with the new table definition.";
}
} elseif (dict_foreigns_has_s_base_col(table->foreign_set,
table)) {
err = DB_NO_FK_ON_S_BASE_COL;
} else { /* Fill the virtual column set in foreign when
the table undergoes copy alter operation. */
dict_mem_table_free_foreign_vcol_set(table);
dict_mem_table_fill_foreign_vcol_set(table);
if (UNIV_LIKELY_NULL(heap)) {
mem_heap_free(heap);
}
trx->op_info = "";
return(err);
}
Messung V0.5 in Prozent
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.63Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-10-08)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.