diff --git a/storage/innobase/CMakeLists.txt b/storage/innobase/CMakeLists.txt index 9f68ceb539bbf..867cf72af8671 100644 --- a/storage/innobase/CMakeLists.txt +++ b/storage/innobase/CMakeLists.txt @@ -175,7 +175,6 @@ SET(INNOBASE_SOURCES fts/fts0opt.cc fts/fts0pars.cc fts/fts0que.cc - fts/fts0sql.cc fts/fts0tlex.cc gis/gis0geo.cc gis/gis0rtree.cc diff --git a/storage/innobase/fts/fts0config.cc b/storage/innobase/fts/fts0config.cc index 524f648676eb7..e3b8d023bd4c6 100644 --- a/storage/innobase/fts/fts0config.cc +++ b/storage/innobase/fts/fts0config.cc @@ -29,98 +29,46 @@ Created 2007/5/9 Sunny Bains #include "fts0priv.h" -/******************************************************************//** -Callback function for fetching the config value. -@return always returns TRUE */ -static -ibool -fts_config_fetch_value( -/*===================*/ - void* row, /*!< in: sel_node_t* */ - void* user_arg) /*!< in: pointer to - ib_vector_t */ -{ - sel_node_t* node = static_cast(row); - fts_string_t* value = static_cast(user_arg); - - dfield_t* dfield = que_node_get_val(node->select_list); - dtype_t* type = dfield_get_type(dfield); - ulint len = dfield_get_len(dfield); - void* data = dfield_get_data(dfield); - - ut_a(dtype_get_mtype(type) == DATA_VARCHAR); - - if (len != UNIV_SQL_NULL) { - ulint max_len = ut_min(value->f_len - 1, len); - - memcpy(value->f_str, data, max_len); - value->f_len = max_len; - value->f_str[value->f_len] = '\0'; - } - - return(TRUE); -} - -/******************************************************************//** -Get value from the config table. The caller must ensure that enough +/** Get value from the config table. The caller must ensure that enough space is allocated for value to hold the column contents. +@param trx transaction +@param fts_table indexed FTS table +@param name get config value for this parameter name +@param value Value read from config table @return DB_SUCCESS or error code */ -dberr_t -fts_config_get_value( -/*=================*/ - trx_t* trx, /*!< transaction */ - fts_table_t* fts_table, /*!< in: the indexed - FTS table */ - const char* name, /*!< in: get config value for - this parameter name */ - fts_string_t* value) /*!< out: value read from - config table */ +dberr_t fts_config_get_value(trx_t *trx, fts_table_t *fts_table, + const char *name, fts_string_t *value) { - pars_info_t* info; - que_t* graph; - dberr_t error; - ulint name_len = strlen(name); - char table_name[MAX_FULL_NAME_LEN]; - - info = pars_info_create(); - - *value->f_str = '\0'; - ut_a(value->f_len > 0); - - pars_info_bind_function(info, "my_func", fts_config_fetch_value, - value); - - /* The len field of value must be set to the max bytes that - it can hold. On a successful read, the len field will be set - to the actual number of bytes copied to value. */ - pars_info_bind_varchar_literal(info, "name", (byte*) name, name_len); - - fts_table->suffix = "CONFIG"; - fts_get_table_name(fts_table, table_name); - pars_info_bind_id(info, "table_name", table_name); - - graph = fts_parse_sql( - fts_table, - info, - "DECLARE FUNCTION my_func;\n" - "DECLARE CURSOR c IS SELECT value FROM $table_name" - " WHERE key = :name;\n" - "BEGIN\n" - "" - "OPEN c;\n" - "WHILE 1 = 1 LOOP\n" - " FETCH c INTO my_func();\n" - " IF c % NOTFOUND THEN\n" - " EXIT;\n" - " END IF;\n" - "END LOOP;\n" - "CLOSE c;"); - - trx->op_info = "getting FTS config value"; - - error = fts_eval_sql(trx, graph); - que_graph_free(graph); - return(error); + FTSQueryRunner sqlRunner(trx); + fts_table->suffix = "CONFIG"; + dberr_t err= DB_SUCCESS; + dict_table_t *table= sqlRunner.open_table(fts_table, &err); + if (table) + { + ut_ad(UT_LIST_GET_LEN(table->indexes) == 1); + err= sqlRunner.prepare_for_read(table); + if (err == DB_SUCCESS) + { + dict_index_t *clust_index= dict_table_get_first_index(table); + sqlRunner.build_tuple(clust_index, 1, 1); + sqlRunner.assign_config_fields(name); + + *value->f_str = '\0'; + ut_a(value->f_len > 0); + + /* Following record executor does the following command + SELECT value FROM $FTS_PREFIX_CONFIG WHERE key = :name;" + and stores the value field in fts_string_t value */ + err= sqlRunner.record_executor(clust_index, READ, MATCH_UNIQUE, + PAGE_CUR_GE, &read_fts_config, value); + /* In case of empty table, error can be DB_END_OF_INDEX + and key doesn't exist, error can be DB_RECORD_NOT_FOUND */ + if (err == DB_END_OF_INDEX || err == DB_RECORD_NOT_FOUND) + err= DB_SUCCESS; + } + table->release(); + } + return err; } /*********************************************************************//** @@ -181,81 +129,61 @@ fts_config_get_index_value( return(error); } -/******************************************************************//** -Set the value in the config table for name. -@return DB_SUCCESS or error code */ -dberr_t -fts_config_set_value( -/*=================*/ - trx_t* trx, /*!< transaction */ - fts_table_t* fts_table, /*!< in: the indexed - FTS table */ - const char* name, /*!< in: get config value for - this parameter name */ - const fts_string_t* - value) /*!< in: value to update */ +dberr_t fts_config_set_value(trx_t *trx, fts_table_t *fts_table, + const char *name, const fts_string_t *value) { - pars_info_t* info; - que_t* graph; - dberr_t error; - undo_no_t undo_no; - undo_no_t n_rows_updated; - ulint name_len = strlen(name); - char table_name[MAX_FULL_NAME_LEN]; - - info = pars_info_create(); - - pars_info_bind_varchar_literal(info, "name", (byte*) name, name_len); - pars_info_bind_varchar_literal(info, "value", - value->f_str, value->f_len); - - const bool dict_locked = fts_table->table->fts->dict_locked; - - fts_table->suffix = "CONFIG"; - fts_get_table_name(fts_table, table_name, dict_locked); - pars_info_bind_id(info, "table_name", table_name); - - graph = fts_parse_sql( - fts_table, info, - "BEGIN UPDATE $table_name SET value = :value" - " WHERE key = :name;"); - - trx->op_info = "setting FTS config value"; - - undo_no = trx->undo_no; - - error = fts_eval_sql(trx, graph); - - que_graph_free(graph); - - n_rows_updated = trx->undo_no - undo_no; - - /* Check if we need to do an insert. */ - if (error == DB_SUCCESS && n_rows_updated == 0) { - info = pars_info_create(); - - pars_info_bind_varchar_literal( - info, "name", (byte*) name, name_len); - - pars_info_bind_varchar_literal( - info, "value", value->f_str, value->f_len); - - fts_get_table_name(fts_table, table_name, dict_locked); - pars_info_bind_id(info, "table_name", table_name); - - graph = fts_parse_sql( - fts_table, info, - "BEGIN\n" - "INSERT INTO $table_name VALUES(:name, :value);"); - - trx->op_info = "inserting FTS config value"; - - error = fts_eval_sql(trx, graph); - - que_graph_free(graph); - } - - return(error); + dberr_t err= DB_SUCCESS; + const bool dict_locked = fts_table->table->fts->dict_locked; + fts_table->suffix = "CONFIG"; + + FTSQueryRunner sqlRunner(trx); + dict_table_t *fts_config= sqlRunner.open_table(fts_table, &err, dict_locked); + if (fts_config) + { + err= sqlRunner.prepare_for_write(fts_config); + if (err == DB_SUCCESS) + { + ut_ad(UT_LIST_GET_LEN(fts_config->indexes) == 1); + dict_index_t *clust_index= dict_table_get_first_index(fts_config); + sqlRunner.build_tuple(clust_index, 1, 1); + /* We set the length of value to the max bytes it can hold. This + information is used by the callback that reads the value.*/ + fts_string_t old_value; + old_value.f_len= FTS_MAX_CONFIG_VALUE_LEN; + old_value.f_str= + static_cast(ut_malloc_nokey(old_value.f_len + 1)); + + /* REPLACE INTO $FTS_PREFIX_CONFIG VALUES (KEY, VALUE) */ + sqlRunner.assign_config_fields(name); + err= sqlRunner.record_executor(clust_index, SELECT_UPDATE, + MATCH_UNIQUE, PAGE_CUR_GE, + &read_fts_config, &old_value); + if (err == DB_SUCCESS) + { + /* Record already exist. So Update the value field with new value */ + if (old_value.f_len != value->f_len || + memcmp(old_value.f_str, value->f_str, value->f_len) != 0) + { + /* Build update vector with new value for FTS_CONFIG table */ + sqlRunner.build_update_config(fts_config, 3, value); + err= sqlRunner.update_record( + fts_config, static_cast(old_value.f_len), + static_cast(value->f_len)); + } + } + else if (err == DB_END_OF_INDEX || err == DB_RECORD_NOT_FOUND) + { + /* Record doesn't exist. So do insert the key, value in config table */ + sqlRunner.build_tuple(clust_index); + sqlRunner.assign_config_fields(name, value->f_str, value->f_len); + err= sqlRunner.write_record(fts_config); + } + ut_free(old_value.f_str); + } + fts_config->release(); + } + + return err; } /******************************************************************//** diff --git a/storage/innobase/fts/fts0fts.cc b/storage/innobase/fts/fts0fts.cc index 5bf9548f95cf2..46ee360d32312 100644 --- a/storage/innobase/fts/fts0fts.cc +++ b/storage/innobase/fts/fts0fts.cc @@ -38,6 +38,9 @@ Full Text Search interface #include "dict0stats.h" #include "btr0pcur.h" #include "log.h" +#include "row0sel.h" +#include "row0ins.h" +#include "row0vers.h" static const ulint FTS_MAX_ID_LEN = 32; @@ -156,27 +159,6 @@ const fts_index_selector_t fts_index_selector[] = { { 0 , NULL } }; -/** Default config values for FTS indexes on a table. */ -static const char* fts_config_table_insert_values_sql = - "PROCEDURE P() IS\n" - "BEGIN\n" - "\n" - "INSERT INTO $config_table VALUES('" - FTS_MAX_CACHE_SIZE_IN_MB "', '256');\n" - "" - "INSERT INTO $config_table VALUES('" - FTS_OPTIMIZE_LIMIT_IN_SECS "', '180');\n" - "" - "INSERT INTO $config_table VALUES ('" - FTS_SYNCED_DOC_ID "', '0');\n" - "" - "INSERT INTO $config_table VALUES ('" - FTS_TOTAL_DELETED_COUNT "', '0');\n" - "" /* Note: 0 == FTS_TABLE_STATE_RUNNING */ - "INSERT INTO $config_table VALUES ('" - FTS_TABLE_STATE "', '0');\n" - "END;\n"; - /** FTS tokenize parmameter for plugin parser */ struct fts_tokenize_param_t { fts_doc_t* result_doc; /*!< Result doc for tokens */ @@ -280,6 +262,54 @@ fts_cache_destroy(fts_cache_t* cache) mem_heap_free(cache->cache_heap); } +/** Get the table id. +@param fts_table FTS Auxiliary table +@param table_id table id must be FTS_AUX_MIN_TABLE_ID_LENGTH bytes + long +@return number of bytes written */ +int fts_get_table_id(const fts_table_t *fts_table, char *table_id) +{ + int len; + ut_a(fts_table->table != NULL); + switch (fts_table->type) { + case FTS_COMMON_TABLE: + len = fts_write_object_id(fts_table->table_id, table_id); + break; + case FTS_INDEX_TABLE: + len = fts_write_object_id(fts_table->table_id, table_id); + table_id[len] = '_'; + ++len; + table_id += len; + len += fts_write_object_id(fts_table->index_id, table_id); + break; + default: ut_error; + } + ut_a(len >= 16); + ut_a(len < FTS_AUX_MIN_TABLE_ID_LENGTH); + return len; +} + +/** Construct the name of an internal FTS table for the given table. +@param[in] fts_table metadata on fulltext-indexed table +@param[out] table_name a name up to MAX_FULL_NAME_LEN +@param[in] dict_locked whether dict_sys.latch is being held */ +void fts_get_table_name(const fts_table_t* fts_table, char* table_name, + bool dict_locked) +{ + if (!dict_locked) dict_sys.freeze(SRW_LOCK_CALL); + ut_ad(dict_sys.frozen()); + /* Include the separator as well. */ + const size_t dbname_len = fts_table->table->name.dblen() + 1; + ut_ad(dbname_len > 1); + memcpy(table_name, fts_table->table->name.m_name, dbname_len); + if (!dict_locked) dict_sys.unfreeze(); + memcpy(table_name += dbname_len, "FTS_", 4); + table_name += 4; + table_name += fts_get_table_id(fts_table, table_name); + *table_name++ = '_'; + strcpy(table_name, fts_table->suffix); +} + /** Get a character set based on precise type. @param prtype precise type @return the corresponding character set */ @@ -360,87 +390,67 @@ fts_load_default_stopword( stopword_info->status = STOPWORD_FROM_DEFAULT; } -/****************************************************************//** -Callback function to read a single stopword value. -@return Always return TRUE */ +/** Callback function to read a single stopword value. */ static -ibool -fts_read_stopword( -/*==============*/ - void* row, /*!< in: sel_node_t* */ - void* user_arg) /*!< in: pointer to ib_vector_t */ -{ - ib_alloc_t* allocator; - fts_stopword_t* stopword_info; - sel_node_t* sel_node; - que_node_t* exp; - ib_rbt_t* stop_words; - dfield_t* dfield; - fts_string_t str; - mem_heap_t* heap; - ib_rbt_bound_t parent; - dict_table_t* table; - - sel_node = static_cast(row); - table = sel_node->table_list->table; - stopword_info = static_cast(user_arg); - - stop_words = stopword_info->cached_stopword; - allocator = static_cast(stopword_info->heap); - heap = static_cast(allocator->arg); - - exp = sel_node->select_list; - - /* We only need to read the first column */ - dfield = que_node_get_val(exp); - - str.f_n_char = 0; - str.f_str = static_cast(dfield_get_data(dfield)); - str.f_len = dfield_get_len(dfield); - exp = que_node_get_next(exp); - ut_ad(exp); - - if (table->versioned()) { - dfield = que_node_get_val(exp); - ut_ad(dfield_get_type(dfield)->vers_sys_end()); - void* data = dfield_get_data(dfield); - ulint len = dfield_get_len(dfield); - if (table->versioned_by_id()) { - ut_ad(len == sizeof trx_id_max_bytes); - if (0 != memcmp(data, trx_id_max_bytes, len)) { - return true; - } - } else { - ut_ad(len == sizeof timestamp_max_bytes); - if (!IS_MAX_TIMESTAMP(data)) { - return true; - } - } - } - ut_ad(!que_node_get_next(exp)); - - /* Only create new node if it is a value not already existed */ - if (str.f_len != UNIV_SQL_NULL - && rbt_search(stop_words, &parent, &str) != 0) { - - fts_tokenizer_word_t new_word; - - new_word.nodes = ib_vector_create( - allocator, sizeof(fts_node_t), 4); +bool fts_read_stopword(dict_index_t *index, const rec_t *rec, + const rec_offs *offsets, void *user_arg) +{ + ut_ad(dict_index_is_clust(index)); + ib_rbt_bound_t parent; + fts_stopword_t *stopword_info= static_cast(user_arg); + ib_rbt_t *stop_words= stopword_info->cached_stopword; + ib_alloc_t *allocator= static_cast(stopword_info->heap); + mem_heap_t *heap= static_cast(allocator->arg); + const byte *data= nullptr; + ulint len; + fts_string_t str; + bool versioning= index->table->versioned(); + + for (uint32_t i= 0; i < index->n_fields; i++) + { + dict_field_t *f= &index->fields[i]; + if (!strcmp(f->name(), "value")) + { + data= rec_get_nth_field(rec, offsets, i, &len); + str.f_n_char= 0; + str.f_str= const_cast(data); + str.f_len= len; + } - new_word.text.f_str = static_cast( - mem_heap_alloc(heap, str.f_len + 1)); + if (versioning && !strcmp(f->name(), "row_end")) + { + data= rec_get_nth_field(rec, offsets, i, &len); + if (index->table->versioned_by_id()) + { + ut_ad(len == sizeof trx_id_max_bytes); + if (0 != memcmp(data, trx_id_max_bytes, len)) return true; + } + else + { + ut_ad(len == sizeof timestamp_max_bytes); + if (!IS_MAX_TIMESTAMP(data)) return true; + } + } + } - memcpy(new_word.text.f_str, str.f_str, str.f_len); + /* Only create new node if it is a value not already existed */ + if (str.f_len != UNIV_SQL_NULL && + rbt_search(stop_words, &parent, &str) != 0) + { + fts_tokenizer_word_t new_word; + new_word.nodes = ib_vector_create(allocator, sizeof(fts_node_t), 4); + new_word.text.f_str = static_cast( + mem_heap_alloc(heap, str.f_len + 1)); - new_word.text.f_n_char = 0; - new_word.text.f_len = str.f_len; - new_word.text.f_str[str.f_len] = 0; + memcpy(new_word.text.f_str, str.f_str, str.f_len); - rbt_insert(stop_words, &new_word, &new_word); - } + new_word.text.f_n_char = 0; + new_word.text.f_len = str.f_len; + new_word.text.f_str[str.f_len] = 0; + rbt_insert(stop_words, &new_word, &new_word); + } - return(TRUE); + return true; } /******************************************************************//** @@ -485,61 +495,36 @@ fts_load_user_stopword( } - pars_info_t* info = pars_info_create(); - - pars_info_bind_id(info, "table_stopword", stopword_table_name); - pars_info_bind_id(info, "row_end", row_end); - - pars_info_bind_function(info, "my_func", fts_read_stopword, - stopword_info); - - que_t* graph = pars_sql( - info, - "PROCEDURE P() IS\n" - "DECLARE FUNCTION my_func;\n" - "DECLARE CURSOR c IS" - " SELECT value, $row_end" - " FROM $table_stopword;\n" - "BEGIN\n" - "\n" - "OPEN c;\n" - "WHILE 1 = 1 LOOP\n" - " FETCH c INTO my_func();\n" - " IF c % NOTFOUND THEN\n" - " EXIT;\n" - " END IF;\n" - "END LOOP;\n" - "CLOSE c;" - "END;\n"); + FTSQueryRunner sqlRunner(trx); + dberr_t err= DB_SUCCESS; + dict_table_t *table= dict_table_open_on_name(stopword_table_name, true, + DICT_ERR_IGNORE_NONE); + if (table) { + err = sqlRunner.prepare_for_read(table); - for (;;) { - dberr_t error = fts_eval_sql(trx, graph); + if (err == DB_SUCCESS) { + dict_index_t *index = dict_table_get_first_index(table); + ut_d(sqlRunner.is_stopword_table= true;); + err= sqlRunner.record_executor( + index, READ, MATCH_ALL, PAGE_CUR_GE, + &fts_read_stopword, stopword_info); + if (err == DB_END_OF_INDEX) err= DB_SUCCESS; + } - if (UNIV_LIKELY(error == DB_SUCCESS)) { + if (err == DB_SUCCESS) { fts_sql_commit(trx); - stopword_info->status = STOPWORD_USER_TABLE; - break; - } else { - fts_sql_rollback(trx); - - if (error == DB_LOCK_WAIT_TIMEOUT) { - ib::warn() << "Lock wait timeout reading user" - " stopword table. Retrying!"; - - trx->error_state = DB_SUCCESS; - } else { - ib::error() << "Error '" << error - << "' while reading user stopword" - " table."; - ret = FALSE; - break; - } + ret = true; } } - que_graph_free(graph); + if (err) { + ib::error() << "Error '" << err << "' while reading user " + << "stopword table."; + trx->rollback(); + } + + if (table) table->release(); trx->free(); - ret = true; goto cleanup; } @@ -552,8 +537,6 @@ fts_index_cache_init( ib_alloc_t* allocator, /*!< in: the allocator to use */ fts_index_cache_t* index_cache) /*!< in: index cache */ { - ulint i; - ut_a(index_cache->words == NULL); index_cache->words = rbt_create_arg_cmp( @@ -564,11 +547,6 @@ fts_index_cache_init( index_cache->doc_stats = ib_vector_create( allocator, sizeof(fts_doc_stats_t), 4); - - for (i = 0; i < FTS_NUM_AUX_INDEX; ++i) { - ut_a(index_cache->ins_graph[i] == NULL); - ut_a(index_cache->sel_graph[i] == NULL); - } } /*********************************************************************//** @@ -937,7 +915,6 @@ fts_cache_index_cache_create( dict_table_t* table, /*!< in: table with FTS index */ dict_index_t* index) /*!< in: FTS index */ { - ulint n_bytes; fts_index_cache_t* index_cache; fts_cache_t* cache = table->fts->cache; @@ -957,16 +934,6 @@ fts_cache_index_cache_create( index_cache->charset = fts_index_get_charset(index); - n_bytes = sizeof(que_t*) * FTS_NUM_AUX_INDEX; - - index_cache->ins_graph = static_cast( - mem_heap_zalloc(static_cast( - cache->self_heap->arg), n_bytes)); - - index_cache->sel_graph = static_cast( - mem_heap_zalloc(static_cast( - cache->self_heap->arg), n_bytes)); - fts_index_cache_init(cache->sync_heap, index_cache); if (cache->get_docs) { @@ -1020,7 +987,6 @@ fts_cache_clear( ulint i; for (i = 0; i < ib_vector_size(cache->indexes); ++i) { - ulint j; fts_index_cache_t* index_cache; index_cache = static_cast( @@ -1032,23 +998,6 @@ fts_cache_clear( index_cache->words = NULL; - for (j = 0; j < FTS_NUM_AUX_INDEX; ++j) { - - if (index_cache->ins_graph[j] != NULL) { - - que_graph_free(index_cache->ins_graph[j]); - - index_cache->ins_graph[j] = NULL; - } - - if (index_cache->sel_graph[j] != NULL) { - - que_graph_free(index_cache->sel_graph[j]); - - index_cache->sel_graph[j] = NULL; - } - } - index_cache->doc_stats = NULL; } @@ -1901,26 +1850,25 @@ fts_create_common_tables( dict_table_t* table, bool skip_doc_id_index) { - dberr_t error; - que_t* graph; + dberr_t err; fts_table_t fts_table; - mem_heap_t* heap = mem_heap_create(1024); - pars_info_t* info; - char fts_name[MAX_FULL_NAME_LEN]; char full_name[sizeof(fts_common_tables) / sizeof(char*)] [MAX_FULL_NAME_LEN]; - dict_index_t* index = NULL; + dict_index_t* index = NULL; + dict_table_t* fts_config= nullptr; FTS_INIT_FTS_TABLE(&fts_table, NULL, FTS_COMMON_TABLE, table); - error = fts_drop_common_tables(trx, &fts_table, true); - - if (error != DB_SUCCESS) { + err = fts_drop_common_tables(trx, &fts_table, true); - goto func_exit; + if (err != DB_SUCCESS) { + return err; } + FTSQueryRunner sqlRunner(trx); + mem_heap_t* heap = sqlRunner.heap(); + /* Create the FTS tables that are common to an FTS index. */ for (ulint i = 0; fts_common_tables[i] != NULL; ++i) { @@ -1931,38 +1879,65 @@ fts_create_common_tables( if (!common_table) { trx->error_state = DB_SUCCESS; - error = DB_ERROR; + err = DB_ERROR; goto func_exit; } mem_heap_empty(heap); } - /* Write the default settings to the config table. */ - info = pars_info_create(); - fts_table.suffix = "CONFIG"; - fts_get_table_name(&fts_table, fts_name, true); - pars_info_bind_id(info, "config_table", fts_name); - graph = pars_sql( - info, fts_config_table_insert_values_sql); + fts_config= sqlRunner.open_table(&fts_table, &err, true); + if (fts_config == nullptr) + goto func_exit; - error = fts_eval_sql(trx, graph); - que_graph_free(graph); + err = sqlRunner.prepare_for_write(fts_config); + if (err) goto func_exit; - if (error != DB_SUCCESS || skip_doc_id_index) { + ut_ad(UT_LIST_GET_LEN(fts_config->indexes) == 1); + sqlRunner.build_tuple(dict_table_get_first_index(fts_config)); - goto func_exit; - } + /* Following commands are executed: + INSERT INTO $config_table VALUES("FTS_MAX_CACHE_SIZE_IN_MB", "256"); + INSERT INTO $config_table VALUES("FTS_OPTIMIZE_LIMIT_IN_SECS","180"); + INSERT INTO $config_table VALUES ("FTS_SYNCED_DOC_ID", "0"); + INSERT INTO $config_table VALUES ("FTS_TOTAL_DELETED_COUNT","0"); + Note: 0 == FTS_TABLE_STATE_RUNNING + INSERT INTO $config_table VALUES ("FTS_TABLE_STATE","0"); */ + sqlRunner.assign_config_fields(FTS_MAX_CACHE_SIZE_IN_MB, "256", 3); + err = sqlRunner.write_record(fts_config); + + if (err) goto func_exit; + + sqlRunner.assign_config_fields(FTS_OPTIMIZE_LIMIT_IN_SECS, "180", 3); + err = sqlRunner.write_record(fts_config); + + if (err) goto func_exit; + + sqlRunner.assign_config_fields(FTS_SYNCED_DOC_ID, "0", 1); + err = sqlRunner.write_record(fts_config); + + if (err) goto func_exit; + + sqlRunner.assign_config_fields(FTS_TOTAL_DELETED_COUNT, "0", 1); + err = sqlRunner.write_record(fts_config); + + if (err) goto func_exit; + + sqlRunner.assign_config_fields(FTS_TABLE_STATE, "0", 1); + err = sqlRunner.write_record(fts_config); + + if (err || skip_doc_id_index) goto func_exit; if (table->versioned()) { index = dict_mem_index_create(table, FTS_DOC_ID_INDEX.str, DICT_UNIQUE, 2); dict_mem_index_add_field(index, FTS_DOC_ID.str, 0); - dict_mem_index_add_field(index, table->cols[table->vers_end].name(*table).str, 0); + dict_mem_index_add_field( + index, table->cols[table->vers_end].name(*table).str, 0); } else { index = dict_mem_index_create(table, FTS_DOC_ID_INDEX.str, @@ -1970,14 +1945,12 @@ fts_create_common_tables( dict_mem_index_add_field(index, FTS_DOC_ID.str, 0); } - error = row_create_index_for_mysql(index, trx, NULL, - FIL_ENCRYPTION_DEFAULT, - FIL_DEFAULT_ENCRYPTION_KEY); - + err = row_create_index_for_mysql(index, trx, NULL, + FIL_ENCRYPTION_DEFAULT, + FIL_DEFAULT_ENCRYPTION_KEY); func_exit: - mem_heap_free(heap); - - return(error); + if (fts_config) fts_config->release(); + return err; } /** Create one FTS auxiliary index table for an FTS index. @@ -2442,37 +2415,30 @@ fts_trx_add_op( fts_trx_table_add_op(stmt_ftt, doc_id, state, fts_indexes); } -/******************************************************************//** -Fetch callback that converts a textual document id to a binary value and +/** Fetch callback that converts a textual document id to a binary value and stores it in the given place. @return always returns NULL */ -static -ibool -fts_fetch_store_doc_id( -/*===================*/ - void* row, /*!< in: sel_node_t* */ - void* user_arg) /*!< in: doc_id_t* to store - doc_id in */ +static bool fts_fetch_store_doc_id(dict_index_t *index, const rec_t *rec, + const rec_offs *offsets, void *user_arg) { - int n_parsed; - sel_node_t* node = static_cast(row); - doc_id_t* doc_id = static_cast(user_arg); - dfield_t* dfield = que_node_get_val(node->select_list); - dtype_t* type = dfield_get_type(dfield); - ulint len = dfield_get_len(dfield); - - char buf[32]; + ut_ad(dict_index_is_clust(index)); + doc_id_t *doc_id= static_cast(user_arg); - ut_a(dtype_get_mtype(type) == DATA_VARCHAR); - ut_a(len > 0 && len < sizeof(buf)); - - memcpy(buf, dfield_get_data(dfield), len); - buf[len] = '\0'; + for (uint32_t i= 0; i < index->n_fields; i++) + { + if (strcmp(index->fields[i].name, "value") != 0) + continue; - n_parsed = sscanf(buf, FTS_DOC_ID_FORMAT, doc_id); - ut_a(n_parsed == 1); + ulint len; + const byte *data= rec_get_nth_field(rec, offsets, i, &len); + ut_ad(len != 8); + char buf[32]; + memcpy(buf, data, len); + buf[len]= '\0'; - return(FALSE); + return sscanf(buf, FTS_DOC_ID_FORMAT, doc_id) == 1; + } + return false; } #ifdef FTS_CACHE_SIZE_DEBUG @@ -2586,9 +2552,7 @@ dberr_t fts_read_synced_doc_id(const dict_table_t *table, doc_id_t *doc_id, trx_t *trx) { - dberr_t error; - char table_name[MAX_FULL_NAME_LEN]; - + dberr_t err= DB_SUCCESS; fts_table_t fts_table; fts_table.suffix= "CONFIG"; fts_table.table_id= table->id; @@ -2597,33 +2561,28 @@ dberr_t fts_read_synced_doc_id(const dict_table_t *table, ut_a(table->fts->doc_col != ULINT_UNDEFINED); trx->op_info = "update the next FTS document id"; - pars_info_t *info= pars_info_create(); - pars_info_bind_function(info, "my_func", fts_fetch_store_doc_id, - doc_id); - - fts_get_table_name(&fts_table, table_name); - pars_info_bind_id(info, "config_table", table_name); - - que_t *graph= fts_parse_sql( - &fts_table, info, - "DECLARE FUNCTION my_func;\n" - "DECLARE CURSOR c IS SELECT value FROM $config_table" - " WHERE key = 'synced_doc_id' FOR UPDATE;\n" - "BEGIN\n" - "" - "OPEN c;\n" - "WHILE 1 = 1 LOOP\n" - " FETCH c INTO my_func();\n" - " IF c % NOTFOUND THEN\n" - " EXIT;\n" - " END IF;\n" - "END LOOP;\n" - "CLOSE c;"); - - *doc_id= 0; - error = fts_eval_sql(trx, graph); - que_graph_free(graph); - return error; + FTSQueryRunner sqlRunner(trx); + + dict_table_t *fts_config= sqlRunner.open_table(&fts_table, &err); + if (fts_config) + { + err= sqlRunner.prepare_for_write(fts_config); + if (err == DB_SUCCESS) + { + ut_ad(UT_LIST_GET_LEN(fts_config->indexes) == 1); + dict_index_t *clust_index= dict_table_get_first_index(fts_config); + sqlRunner.build_tuple(clust_index, 1, 1); + sqlRunner.assign_config_fields("synced_doc_id"); + err= sqlRunner.record_executor(clust_index, SELECT_UPDATE, + MATCH_UNIQUE, PAGE_CUR_GE, + &fts_fetch_store_doc_id, + doc_id); + if (err == DB_END_OF_INDEX || err == DB_RECORD_NOT_FOUND) + err= DB_SUCCESS; + } + fts_config->release(); + } + return err; } /** This function fetch the Doc ID from CONFIG table, and compare with @@ -2714,75 +2673,102 @@ transaction to update the last document id. @param doc_id last document id @param trx update trx or null @retval DB_SUCCESS if OK */ -dberr_t -fts_update_sync_doc_id( - const dict_table_t* table, - doc_id_t doc_id, - trx_t* trx) +dberr_t fts_update_sync_doc_id(const dict_table_t *table, doc_id_t doc_id, + trx_t *trx) { - byte id[FTS_MAX_ID_LEN]; - pars_info_t* info; - fts_table_t fts_table; - ulint id_len; - que_t* graph = NULL; - dberr_t error; - ibool local_trx = FALSE; - fts_cache_t* cache = table->fts->cache; - char fts_name[MAX_FULL_NAME_LEN]; - - if (srv_read_only_mode) { - return DB_READ_ONLY; - } + fts_table_t fts_table; + dberr_t err= DB_SUCCESS; + bool local_trx= false; + fts_cache_t *cache = table->fts->cache; - fts_table.suffix = "CONFIG"; - fts_table.table_id = table->id; - fts_table.type = FTS_COMMON_TABLE; - fts_table.table = table; + if (srv_read_only_mode) return DB_READ_ONLY; - if (!trx) { - trx = trx_create(); - trx_start_internal(trx); + fts_table.suffix = "CONFIG"; + fts_table.table_id = table->id; + fts_table.type = FTS_COMMON_TABLE; + fts_table.table = table; - trx->op_info = "setting last FTS document id"; - local_trx = TRUE; - } + if (!trx) + { + trx= trx_create(); + trx_start_internal(trx); + trx->op_info= "setting last FTS document id"; + local_trx= true; + } - info = pars_info_create(); + FTSQueryRunner sqlRunner(trx); + dict_table_t *fts_config= sqlRunner.open_table(&fts_table, &err); + if (fts_config) + { + err= sqlRunner.prepare_for_write(fts_config); + if (err == DB_SUCCESS) + { + ut_ad(UT_LIST_GET_LEN(fts_config->indexes) == 1); + dict_index_t *clust_index= dict_table_get_first_index(fts_config); + sqlRunner.build_tuple(clust_index, 1, 1); + sqlRunner.assign_config_fields("synced_doc_id"); + fts_string_t old_value; + fts_string_t new_value; - id_len = (ulint) snprintf( - (char*) id, sizeof(id), FTS_DOC_ID_FORMAT, doc_id + 1); + old_value.f_len= FTS_MAX_ID_LEN; + old_value.f_str= + static_cast(ut_malloc_nokey(old_value.f_len + 1)); - pars_info_bind_varchar_literal(info, "doc_id", id, id_len); + err= sqlRunner.record_executor(clust_index, SELECT_UPDATE, MATCH_UNIQUE, + PAGE_CUR_GE, &read_fts_config, &old_value); - fts_get_table_name(&fts_table, fts_name, - table->fts->dict_locked); - pars_info_bind_id(info, "table_name", fts_name); + ut_a(err != DB_END_OF_INDEX || err != DB_RECORD_NOT_FOUND); - graph = fts_parse_sql( - &fts_table, info, - "BEGIN" - " UPDATE $table_name SET value = :doc_id" - " WHERE key = 'synced_doc_id';"); + if (err != DB_SUCCESS) + { + ut_free(old_value.f_str); + goto func_exit; + } - error = fts_eval_sql(trx, graph); + /* Construct new value for the record to be updated */ + byte id[FTS_MAX_ID_LEN]; + ulint new_len= (ulint) snprintf((char*) id, sizeof(id), + FTS_DOC_ID_FORMAT, doc_id + 1); - que_graph_free(graph); + new_value.f_len= new_len; + new_value.f_str= + static_cast(ut_malloc_nokey(new_value.f_len + 1)); + memcpy(new_value.f_str, id, new_len); + new_value.f_str[new_len]='\0'; - if (local_trx) { - if (UNIV_LIKELY(error == DB_SUCCESS)) { - fts_sql_commit(trx); - cache->synced_doc_id = doc_id; - } else { - ib::error() << "(" << error << ") while" - " updating last doc id for table" - << table->name; + /* Update only if there is a change in new value */ + if (old_value.f_len != new_value.f_len || + memcmp(old_value.f_str, new_value.f_str, new_value.f_len) != 0) + { + sqlRunner.build_update_config(fts_config, 3, &new_value); + err= sqlRunner.update_record(fts_config, + static_cast(old_value.f_len), + static_cast(new_len)); + } + ut_free(new_value.f_str); + ut_free(old_value.f_str); + } + } - fts_sql_rollback(trx); - } - trx->free(); - } +func_exit: + if (local_trx) + { + if (UNIV_LIKELY(err == DB_SUCCESS)) + { + fts_sql_commit(trx); + cache->synced_doc_id = doc_id; + } + else + { + ib::error() << "(" << err << ") while updating last doc id for table" + << table->name; + fts_sql_rollback(trx); + } + trx->free(); + } - return(error); + if (fts_config) fts_config->release(); + return err; } /*********************************************************************//** @@ -2841,13 +2827,11 @@ fts_delete( fts_trx_table_t*ftt, /*!< in: FTS trx table */ fts_trx_row_t* row) /*!< in: row */ { - que_t* graph; fts_table_t fts_table; doc_id_t write_doc_id; dict_table_t* table = ftt->table; doc_id_t doc_id = row->doc_id; trx_t* trx = ftt->fts_trx->trx; - pars_info_t* info = pars_info_create(); fts_cache_t* cache = table->fts->cache; /* we do not index Documents whose Doc ID value is 0 */ @@ -2862,7 +2846,6 @@ fts_delete( /* Convert to "storage" byte order. */ fts_write_doc_id((byte*) &write_doc_id, doc_id); - fts_bind_doc_id(info, "doc_id", &write_doc_id); /* It is possible we update a record that has not yet been sync-ed into cache from last crash (delete Doc will not initialize the @@ -2887,25 +2870,28 @@ fts_delete( ut_a(row->state == FTS_DELETE || row->state == FTS_MODIFY); } - /* Note the deleted document for OPTIMIZE to purge. */ - char table_name[MAX_FULL_NAME_LEN]; - trx->op_info = "adding doc id to FTS DELETED"; - fts_table.suffix = "DELETED"; - fts_get_table_name(&fts_table, table_name); - pars_info_bind_id(info, "deleted", table_name); + dberr_t err= DB_SUCCESS; + FTSQueryRunner sqlRunner(trx); + dict_table_t *fts_deleted= sqlRunner.open_table(&fts_table, &err); - graph = fts_parse_sql(&fts_table, info, - "BEGIN INSERT INTO $deleted VALUES (:doc_id);"); + if (fts_deleted == nullptr) goto func_exit; - dberr_t error = fts_eval_sql(trx, graph); - que_graph_free(graph); + err = sqlRunner.prepare_for_write(fts_deleted); + if (err) { + goto func_exit; + } - /* Increment the total deleted count, this is used to calculate the - number of documents indexed. */ - if (error == DB_SUCCESS) { + ut_ad(UT_LIST_GET_LEN(fts_deleted->indexes) == 1); + sqlRunner.build_tuple(dict_table_get_first_index(fts_deleted)); + sqlRunner.assign_common_table_fields(&write_doc_id); + /* INSERT INTO FTS_DELETED VALUES(:DOC_ID) */ + err = sqlRunner.write_record(fts_deleted); + if (err == DB_SUCCESS) { + /* Increment the total deleted count, this is used to + calculate the number of documents indexed. */ mysql_mutex_lock(&table->fts->cache->deleted_lock); ++table->fts->cache->deleted; @@ -2913,7 +2899,11 @@ fts_delete( mysql_mutex_unlock(&table->fts->cache->deleted_lock); } - return(error); +func_exit: + if (fts_deleted) { + fts_deleted->release(); + } + return err; } /*********************************************************************//** @@ -3068,91 +3058,56 @@ fts_doc_free( mem_heap_free(heap); } -/*********************************************************************//** -Callback function for fetch that stores the text of an FTS document, +/** Callback function for fetch that stores the text of an FTS document, converting each column to UTF-16. -@return always FALSE */ -ibool -fts_query_expansion_fetch_doc( -/*==========================*/ - void* row, /*!< in: sel_node_t* */ - void* user_arg) /*!< in: fts_doc_t* */ -{ - que_node_t* exp; - sel_node_t* node = static_cast(row); - fts_doc_t* result_doc = static_cast(user_arg); - dfield_t* dfield; - ulint len; - ulint doc_len; - fts_doc_t doc; - CHARSET_INFO* doc_charset = NULL; - ulint field_no = 0; - - len = 0; - - fts_doc_init(&doc); - doc.found = TRUE; - - exp = node->select_list; - doc_len = 0; - - doc_charset = result_doc->charset; - - /* Copy each indexed column content into doc->text.f_str */ - while (exp) { - dfield = que_node_get_val(exp); - len = dfield_get_len(dfield); - - /* NULL column */ - if (len == UNIV_SQL_NULL) { - exp = que_node_get_next(exp); - continue; - } - - if (!doc_charset) { - doc_charset = fts_get_charset(dfield->type.prtype); - } - - doc.charset = doc_charset; - - if (dfield_is_ext(dfield)) { - /* We ignore columns that are stored externally, this - could result in too many words to search */ - exp = que_node_get_next(exp); - continue; - } else { - doc.text.f_n_char = 0; - - doc.text.f_str = static_cast( - dfield_get_data(dfield)); - - doc.text.f_len = len; - } - - if (field_no == 0) { - fts_tokenize_document(&doc, result_doc, - result_doc->parser); - } else { - fts_tokenize_document_next(&doc, doc_len, result_doc, - result_doc->parser); - } - - exp = que_node_get_next(exp); - - doc_len += (exp) ? len + 1 : len; - - field_no++; - } - - ut_ad(doc_charset); - - if (!result_doc->charset) { - result_doc->charset = doc_charset; - } - - fts_doc_free(&doc); +@param index clustered index +@param rec clustered index record +@param offsets offsets for the record +@param user_arg points to fts_fetch_doc_id since it has field information + to fetch and user_arg which points to fts_doc_t +@return always true */ +bool fts_query_expansion_fetch_doc(dict_index_t *index, const rec_t *rec, + const rec_offs *offsets, void *user_arg) +{ + ut_ad(dict_index_is_clust(index)); + fts_fetch_doc_id *fetch= static_cast(user_arg); + fts_doc_t *result_doc= static_cast(fetch->user_arg); + ulint doc_len= 0; + fts_doc_t doc; + CHARSET_INFO *doc_charset = NULL; + + fts_doc_init(&doc); + doc.found= TRUE; + doc_charset= result_doc->charset; + + bool start_doc= true; + for (uint32_t i= 0; i < fetch->field_to_fetch.size(); i++) + { + ulint field_no= fetch->field_to_fetch[i]; + if (!doc_charset) + doc_charset = fts_get_charset(index->fields[field_no].col->prtype); + doc.charset = doc_charset; + + /* We ignore columns that are stored externally, this + could result in too many words to search */ + if (rec_offs_nth_extern(offsets, field_no)) continue; + else + doc.text.f_str = (byte*) rec_get_nth_field(rec, offsets, field_no, + &doc.text.f_len); + if (start_doc) + { + fts_tokenize_document(&doc, result_doc, result_doc->parser); + start_doc= false; + } + else fts_tokenize_document_next(&doc, ++doc_len, result_doc, + result_doc->parser); + doc_len+= doc.text.f_len; + } - return(FALSE); + ut_ad(doc_charset); + if (!result_doc->charset) result_doc->charset = doc_charset; + fts_doc_free(&doc); + return true; } /*********************************************************************//** @@ -3591,28 +3546,6 @@ fts_add_doc_by_id( mem_heap_free(heap); } - -/*********************************************************************//** -Callback function to read a single ulint column. -return always returns TRUE */ -static -ibool -fts_read_ulint( -/*===========*/ - void* row, /*!< in: sel_node_t* */ - void* user_arg) /*!< in: pointer to ulint */ -{ - sel_node_t* sel_node = static_cast(row); - ulint* value = static_cast(user_arg); - que_node_t* exp = sel_node->select_list; - dfield_t* dfield = que_node_get_val(exp); - void* data = dfield_get_data(dfield); - - *value = mach_read_from_4(static_cast(data)); - - return(TRUE); -} - /*********************************************************************//** Get maximum Doc ID in a table if index "FTS_DOC_ID_INDEX" exists @return max Doc ID or 0 if index "FTS_DOC_ID_INDEX" does not exist */ @@ -3689,200 +3622,122 @@ fts_get_max_doc_id( return(doc_id); } -/*********************************************************************//** -Fetch document with the given document id. +/** Fetch document with the given document id. +@param get_doc state +@param doc_id document id to fetch +@param index_to_use index to be used for fetching fts information +@param option search option, if it is greater than doc_id or equal +@param callback callback function after fetching the fulltext indexed + column +@param arg callback argument @return DB_SUCCESS if OK else error */ -dberr_t -fts_doc_fetch_by_doc_id( -/*====================*/ - fts_get_doc_t* get_doc, /*!< in: state */ - doc_id_t doc_id, /*!< in: id of document to - fetch */ - dict_index_t* index_to_use, /*!< in: caller supplied FTS index, - or NULL */ - ulint option, /*!< in: search option, if it is - greater than doc_id or equal */ - fts_sql_callback - callback, /*!< in: callback to read */ - void* arg) /*!< in: callback arg */ -{ - pars_info_t* info; - dberr_t error; - const char* select_str; - doc_id_t write_doc_id; - dict_index_t* index; - trx_t* trx = trx_create(); - que_t* graph; - - trx->op_info = "fetching indexed FTS document"; - - /* The FTS index can be supplied by caller directly with - "index_to_use", otherwise, get it from "get_doc" */ - index = (index_to_use) ? index_to_use : get_doc->index_cache->index; - - if (get_doc && get_doc->get_document_graph) { - info = get_doc->get_document_graph->info; - } else { - info = pars_info_create(); - } - - /* Convert to "storage" byte order. */ - fts_write_doc_id((byte*) &write_doc_id, doc_id); - fts_bind_doc_id(info, "doc_id", &write_doc_id); - pars_info_bind_function(info, "my_func", callback, arg); - - select_str = fts_get_select_columns_str(index, info, info->heap); - pars_info_bind_id(info, "table_name", index->table->name.m_name); - - if (!get_doc || !get_doc->get_document_graph) { - if (option == FTS_FETCH_DOC_BY_ID_EQUAL) { - graph = fts_parse_sql( - NULL, - info, - mem_heap_printf(info->heap, - "DECLARE FUNCTION my_func;\n" - "DECLARE CURSOR c IS" - " SELECT %s FROM $table_name" - " WHERE %s = :doc_id;\n" - "BEGIN\n" - "" - "OPEN c;\n" - "WHILE 1 = 1 LOOP\n" - " FETCH c INTO my_func();\n" - " IF c %% NOTFOUND THEN\n" - " EXIT;\n" - " END IF;\n" - "END LOOP;\n" - "CLOSE c;", - select_str, - FTS_DOC_ID.str)); - } else { - ut_ad(option == FTS_FETCH_DOC_BY_ID_LARGE); - - /* This is used for crash recovery of table with - hidden DOC ID or FTS indexes. We will scan the table - to re-processing user table rows whose DOC ID or - FTS indexed documents have not been sync-ed to disc - during recent crash. - In the case that all fulltext indexes are dropped - for a table, we will keep the "hidden" FTS_DOC_ID - column, and this scan is to retreive the largest - DOC ID being used in the table to determine the - appropriate next DOC ID. - In the case of there exists fulltext index(es), this - operation will re-tokenize any docs that have not - been sync-ed to the disk, and re-prime the FTS - cached */ - graph = fts_parse_sql( - NULL, - info, - mem_heap_printf(info->heap, - "DECLARE FUNCTION my_func;\n" - "DECLARE CURSOR c IS" - " SELECT %s, %s FROM $table_name" - " WHERE %s > :doc_id;\n" - "BEGIN\n" - "" - "OPEN c;\n" - "WHILE 1 = 1 LOOP\n" - " FETCH c INTO my_func();\n" - " IF c %% NOTFOUND THEN\n" - " EXIT;\n" - " END IF;\n" - "END LOOP;\n" - "CLOSE c;", - FTS_DOC_ID.str, - select_str, - FTS_DOC_ID.str)); - } - if (get_doc) { - get_doc->get_document_graph = graph; - } - } else { - graph = get_doc->get_document_graph; - } +dberr_t fts_doc_fetch_by_doc_id(fts_get_doc_t *get_doc, doc_id_t doc_id, + dict_index_t *index_to_use, ulint option, + fts_sql_callback *callback, void *arg) +{ + dberr_t err; + doc_id_t write_doc_id; + dict_index_t *index= nullptr; + trx_t *trx = trx_create(); + trx->op_info = "fetching indexed FTS document"; + + /* The FTS index can be supplied by caller directly with + "index_to_use", otherwise, get it from "get_doc" */ + index = (index_to_use) ? index_to_use : get_doc->index_cache->index; + + fts_fetch_doc_id fetch; + fetch.user_arg= arg; + + fts_match_key match_op= MATCH_UNIQUE; + page_cur_mode_t mode= PAGE_CUR_GE; + dict_table_t *table= index->table; + dict_index_t *clust_index= dict_table_get_first_index(table); + dict_index_t *fts_idx = dict_table_get_index_on_name(table, + FTS_DOC_ID_INDEX.str); + + if (option == FTS_FETCH_DOC_BY_ID_LARGE) + { + /* Get the FTS_DOC_ID field number in clustered index record */ + dict_col_t *col= fts_idx->fields[0].col; + for (uint32_t i= 0; i < clust_index->n_fields; i++) + { + if (clust_index->fields[i].col == col) + { + fetch.field_to_fetch.push_back(i); + break; + } + } - error = fts_eval_sql(trx, graph); - fts_sql_commit(trx); - trx->free(); + /* SELECT %s, %s FROM $table_name WHERE %s > :doc_id */ + match_op= MATCH_ALL; + mode= PAGE_CUR_G; + } + else if (table->versioned()) + /* In case of versioned table, FTS_DOC_ID_IDX can have + {FTS_DOC_ID, ROW_END}. So there can be multiple entries + for the same DOC_ID */ + match_op= MATCH_PREFIX; + + if (index == fts_idx); + else + { + /* Get the field number of INDEX FIELDS in clustered index record */ + for (uint32_t i= 0; i < index->n_user_defined_cols; i++) + { + dict_col_t *col= index->fields[i].col; + for (uint32_t j= 0; j < clust_index->n_fields; j++) + { + if (clust_index->fields[j].col == col) + { + fetch.field_to_fetch.push_back(j); + break; + } + } + } + } - if (!get_doc) { - que_graph_free(graph); - } + /* Convert to "storage" byte order. */ + fts_write_doc_id((byte*) &write_doc_id, doc_id); - return(error); + FTSQueryRunner sqlRunner(trx); + sqlRunner.build_tuple(fts_idx, 1, 1); + err= sqlRunner.prepare_for_read(table); + if (err == DB_SUCCESS) + { + dtuple_t *tuple= sqlRunner.tuple(); + dfield_set_data(&tuple->fields[0], &write_doc_id, sizeof(doc_id_t)); + err= sqlRunner.record_executor(fts_idx, READ, match_op, + mode, callback, &fetch); + } + if (err == DB_RECORD_NOT_FOUND || err == DB_END_OF_INDEX) + err= DB_SUCCESS; + fts_sql_commit(trx); + trx->free(); + return err; } -/*********************************************************************//** -Write out a single word's data as new entry/entries in the INDEX table. +/** Write out a single word's data as new entry/entries in the INDEX table. +@param word word in UTF-8 +@param node node columns +@param sqlRunner Runs the query in FTS SYSTEM TABLES @return DB_SUCCESS if all OK. */ dberr_t -fts_write_node( -/*===========*/ - trx_t* trx, /*!< in: transaction */ - que_t** graph, /*!< in: query graph */ - fts_table_t* fts_table, /*!< in: aux table */ - fts_string_t* word, /*!< in: word in UTF-8 */ - fts_node_t* node) /*!< in: node columns */ +fts_write_node(dict_table_t *table, + fts_string_t *word, fts_node_t* node, + FTSQueryRunner *sqlRunner) { - pars_info_t* info; - dberr_t error; - ib_uint32_t doc_count; - time_t start_time; - doc_id_t last_doc_id; - doc_id_t first_doc_id; - char table_name[MAX_FULL_NAME_LEN]; - - ut_a(node->ilist != NULL); - - if (*graph) { - info = (*graph)->info; - } else { - info = pars_info_create(); - - fts_get_table_name(fts_table, table_name); - pars_info_bind_id(info, "index_table_name", table_name); - } - - pars_info_bind_varchar_literal(info, "token", word->f_str, word->f_len); - - /* Convert to "storage" byte order. */ - fts_write_doc_id((byte*) &first_doc_id, node->first_doc_id); - fts_bind_doc_id(info, "first_doc_id", &first_doc_id); - - /* Convert to "storage" byte order. */ - fts_write_doc_id((byte*) &last_doc_id, node->last_doc_id); - fts_bind_doc_id(info, "last_doc_id", &last_doc_id); - - ut_a(node->last_doc_id >= node->first_doc_id); - - /* Convert to "storage" byte order. */ - mach_write_to_4((byte*) &doc_count, node->doc_count); - pars_info_bind_int4_literal( - info, "doc_count", (const ib_uint32_t*) &doc_count); - - /* Set copy_name to FALSE since it's a static. */ - pars_info_bind_literal( - info, "ilist", node->ilist, node->ilist_size, - DATA_BLOB, DATA_BINARY_TYPE); - - if (!*graph) { - - *graph = fts_parse_sql( - fts_table, - info, - "BEGIN\n" - "INSERT INTO $index_table_name VALUES" - " (:token, :first_doc_id," - " :last_doc_id, :doc_count, :ilist);"); - } - - start_time = time(NULL); - error = fts_eval_sql(trx, *graph); - elapsed_time += time(NULL) - start_time; - ++n_nodes; - - return(error); + dberr_t err= sqlRunner->prepare_for_write(table); + if (err == DB_SUCCESS) + { + dict_index_t *index= dict_table_get_first_index(table); + sqlRunner->build_tuple(index); + sqlRunner->assign_aux_table_fields(word->f_str, word->f_len, node); + time_t start_time = time(NULL); + err= sqlRunner->write_record(table); + elapsed_time += time(NULL) - start_time; + ++n_nodes; + } + return err; } /** Sort an array of doc_id */ @@ -3892,60 +3747,42 @@ void fts_doc_ids_sort(ib_vector_t *doc_ids) std::sort(data, data + doc_ids->used); } -/*********************************************************************//** -Add rows to the DELETED_CACHE table. +/** Add rows to the DELETED_CACHE table. @return DB_SUCCESS if all went well else error code*/ static MY_ATTRIBUTE((nonnull, warn_unused_result)) -dberr_t -fts_sync_add_deleted_cache( -/*=======================*/ - fts_sync_t* sync, /*!< in: sync state */ - ib_vector_t* doc_ids) /*!< in: doc ids to add */ +dberr_t fts_sync_add_deleted_cache(fts_sync_t *sync, ib_vector_t *doc_ids) { - ulint i; - pars_info_t* info; - que_t* graph; - fts_table_t fts_table; - char table_name[MAX_FULL_NAME_LEN]; - doc_id_t dummy = 0; - dberr_t error = DB_SUCCESS; - ulint n_elems = ib_vector_size(doc_ids); - - ut_a(ib_vector_size(doc_ids) > 0); - - fts_doc_ids_sort(doc_ids); - - info = pars_info_create(); - - fts_bind_doc_id(info, "doc_id", &dummy); - - FTS_INIT_FTS_TABLE( - &fts_table, "DELETED_CACHE", FTS_COMMON_TABLE, sync->table); - - fts_get_table_name(&fts_table, table_name); - pars_info_bind_id(info, "table_name", table_name); - - graph = fts_parse_sql( - &fts_table, - info, - "BEGIN INSERT INTO $table_name VALUES (:doc_id);"); + ulint n_elems = ib_vector_size(doc_ids); + ut_a(ib_vector_size(doc_ids) > 0); + fts_doc_ids_sort(doc_ids); - for (i = 0; i < n_elems && error == DB_SUCCESS; ++i) { - doc_id_t* update; - doc_id_t write_doc_id; - - update = static_cast(ib_vector_get(doc_ids, i)); - - /* Convert to "storage" byte order. */ - fts_write_doc_id((byte*) &write_doc_id, *update); - fts_bind_doc_id(info, "doc_id", &write_doc_id); - - error = fts_eval_sql(sync->trx, graph); - } - - que_graph_free(graph); + fts_table_t fts_table; + FTS_INIT_FTS_TABLE(&fts_table, "DELETED_CACHE", FTS_COMMON_TABLE, + sync->table); + FTSQueryRunner sqlRunner(sync->trx); + dberr_t err= DB_SUCCESS; + dict_index_t *clust_index= nullptr; + dict_table_t *deleted_cache= sqlRunner.open_table(&fts_table, &err); + + if (deleted_cache) + { + err= sqlRunner.prepare_for_write(deleted_cache); + if (err) goto func_exit; + ut_ad(UT_LIST_GET_LEN(deleted_cache->indexes) == 1); + clust_index= dict_table_get_first_index(deleted_cache); + sqlRunner.build_tuple(clust_index); + for (ulint i = 0; i < n_elems && err == DB_SUCCESS; ++i) + { + doc_id_t* update= static_cast(ib_vector_get(doc_ids, i)); + sqlRunner.assign_common_table_fields(update); + /* INSERT INTO DELETED_CACHE VALUES(doc_id) */ + err= sqlRunner.write_record(deleted_cache); + } + } - return(error); +func_exit: + if (deleted_cache) deleted_cache->release(); + return err; } /** Write the words and ilist to disk. @@ -3967,6 +3804,9 @@ fts_sync_write_words( dberr_t error = DB_SUCCESS; ibool print_error = FALSE; dict_table_t* table = index_cache->index->table; + dict_table_t* aux_table = nullptr; + std::map aux_tables; + FTSQueryRunner sqlRunner(trx); FTS_INIT_INDEX_TABLE( &fts_table, NULL, FTS_INDEX_TABLE, index_cache->index); @@ -3996,6 +3836,17 @@ fts_sync_write_words( fts_table.suffix = fts_get_suffix(selected); + auto find_table = aux_tables.find(selected); + if (find_table != aux_tables.end()) { + aux_table = find_table->second; + } else { + fts_table.suffix = fts_get_suffix(selected); + aux_table= sqlRunner.open_table(&fts_table, &error); + if (error == DB_SUCCESS) { + aux_tables[selected]= aux_table; + } + } + /* We iterate over all the nodes even if there was an error */ for (i = 0; i < ib_vector_size(word->nodes); ++i) { @@ -4014,12 +3865,9 @@ fts_sync_write_words( mysql_mutex_unlock( &table->fts->cache->lock); } - error = fts_write_node( - trx, - &index_cache->ins_graph[selected], - &fts_table, &word->text, fts_node); - + aux_table, &word->text, fts_node, + &sqlRunner); DEBUG_SYNC_C("fts_write_node"); DBUG_EXECUTE_IF("fts_write_node_crash", DBUG_SUICIDE();); @@ -4046,6 +3894,10 @@ fts_sync_write_words( } } + for (auto it : aux_tables) { + it.second->release(); + } + if (UNIV_UNLIKELY(fts_enable_diag_print)) { printf("Avg number of nodes: %lf\n", (double) n_nodes / (double) (n_words > 1 ? n_words : 1)); @@ -4229,7 +4081,6 @@ fts_sync_rollback( fts_cache_t* cache = sync->table->fts->cache; for (ulint i = 0; i < ib_vector_size(cache->indexes); ++i) { - ulint j; fts_index_cache_t* index_cache; index_cache = static_cast( @@ -4238,23 +4089,6 @@ fts_sync_rollback( /* Reset synced flag so nodes will not be skipped in the next sync, see fts_sync_write_words(). */ fts_sync_index_reset(index_cache); - - for (j = 0; fts_index_selector[j].value; ++j) { - - if (index_cache->ins_graph[j] != NULL) { - - que_graph_free(index_cache->ins_graph[j]); - - index_cache->ins_graph[j] = NULL; - } - - if (index_cache->sel_graph[j] != NULL) { - - que_graph_free(index_cache->sel_graph[j]); - - index_cache->sel_graph[j] = NULL; - } - } } mysql_mutex_unlock(&cache->lock); @@ -4801,32 +4635,6 @@ fts_get_docs_create( return(get_docs); } -/******************************************************************** -Release any resources held by the fts_get_doc_t instances. */ -static -void -fts_get_docs_clear( -/*===============*/ - ib_vector_t* get_docs) /*!< in: Doc retrieval vector */ -{ - ulint i; - - /* Release the get doc graphs if any. */ - for (i = 0; i < ib_vector_size(get_docs); ++i) { - - fts_get_doc_t* get_doc = static_cast( - ib_vector_get(get_docs, i)); - - if (get_doc->get_document_graph != NULL) { - - ut_a(get_doc->index_cache); - - que_graph_free(get_doc->get_document_graph); - get_doc->get_document_graph = NULL; - } - } -} - /*********************************************************************//** Get the initial Doc ID by consulting the CONFIG table @return initial Doc ID */ @@ -4901,79 +4709,51 @@ fts_is_index_updated( } #endif -/*********************************************************************//** -Fetch COUNT(*) from specified table. -@return the number of rows in the table */ -ulint -fts_get_rows_count( -/*===============*/ - fts_table_t* fts_table) /*!< in: fts table to read */ +bool fts_read_count(dict_index_t *index, const rec_t *rec, + const rec_offs *offset, void *user_arg) { - trx_t* trx; - pars_info_t* info; - que_t* graph; - dberr_t error; - ulint count = 0; - char table_name[MAX_FULL_NAME_LEN]; - - trx = trx_create(); - trx->op_info = "fetching FT table rows count"; - - info = pars_info_create(); - - pars_info_bind_function(info, "my_func", fts_read_ulint, &count); - - fts_get_table_name(fts_table, table_name); - pars_info_bind_id(info, "table_name", table_name); - - graph = fts_parse_sql( - fts_table, - info, - "DECLARE FUNCTION my_func;\n" - "DECLARE CURSOR c IS" - " SELECT COUNT(*)" - " FROM $table_name;\n" - "BEGIN\n" - "\n" - "OPEN c;\n" - "WHILE 1 = 1 LOOP\n" - " FETCH c INTO my_func();\n" - " IF c % NOTFOUND THEN\n" - " EXIT;\n" - " END IF;\n" - "END LOOP;\n" - "CLOSE c;"); - - for (;;) { - error = fts_eval_sql(trx, graph); - - if (UNIV_LIKELY(error == DB_SUCCESS)) { - fts_sql_commit(trx); - - break; /* Exit the loop. */ - } else { - fts_sql_rollback(trx); - - if (error == DB_LOCK_WAIT_TIMEOUT) { - ib::warn() << "lock wait timeout reading" - " FTS table. Retrying!"; - - trx->error_state = DB_SUCCESS; - } else { - ib::error() << "(" << error - << ") while reading FTS table " - << table_name; - - break; /* Exit the loop. */ - } - } - } + ut_ad(dict_index_is_clust(index)); + ulint* n_count= static_cast(user_arg); + (*n_count)++; + return true; +} - que_graph_free(graph); +/** Fetch COUNT(*) from specified table. +@param fts_table Fulltext table to be fetched +@return the number of rows in the table */ +ulint fts_get_rows_count(fts_table_t *fts_table) +{ + trx_t *trx= trx_create(); + trx->op_info= "fetching FT table rows count"; + FTSQueryRunner sqlRunner(trx); + ulint n_count= 0; + dberr_t err= DB_SUCCESS; + dict_table_t *table= sqlRunner.open_table(fts_table, &err); + if (table) + { + err= sqlRunner.prepare_for_read(table); + if (err == DB_SUCCESS) + { + ut_ad(UT_LIST_GET_LEN(table->indexes) == 1); + dict_index_t *clust_index= dict_table_get_first_index(table); + err= sqlRunner.record_executor(clust_index, READ, MATCH_UNIQUE, + PAGE_CUR_GE, &fts_read_count, &n_count); + if (err == DB_END_OF_INDEX) err= DB_SUCCESS; + } + } - trx->free(); + if (err == DB_SUCCESS) + fts_sql_commit(trx); + else + { + fts_sql_rollback(trx); + ib::error() << "(" << err << ") while reading FTS table " + << table->name.m_name; + } - return(count); + if (table) table->release(); + trx->free(); + return n_count; } #ifdef FTS_CACHE_SIZE_DEBUG @@ -5945,168 +5725,110 @@ fts_load_stopword( return error == DB_SUCCESS; } -/**********************************************************************//** -Callback function when we initialize the FTS at the start up +/** Callback function when we initialize the FTS at the start up time. It recovers the maximum Doc IDs presented in the current table. Tested by innodb_fts.crash_recovery +@param index clustered index +@param rec clustered index record +@param offsets offsets to the clustered index record +@param user_arg points to fts_fetch_doc_id. fetch->user_arg points + to dict_table_t @return: always returns TRUE */ static -ibool -fts_init_get_doc_id( -/*================*/ - void* row, /*!< in: sel_node_t* */ - void* user_arg) /*!< in: table with fts */ +bool fts_init_get_doc_id(dict_index_t *index, const rec_t *rec, + const rec_offs *offsets, void *user_arg) { - doc_id_t doc_id = FTS_NULL_DOC_ID; - sel_node_t* node = static_cast(row); - que_node_t* exp = node->select_list; - dict_table_t* table = static_cast(user_arg); - fts_cache_t* cache = table->fts->cache; - - ut_ad(ib_vector_is_empty(cache->get_docs)); - - /* Copy each indexed column content into doc->text.f_str */ - if (exp) { - dfield_t* dfield = que_node_get_val(exp); - dtype_t* type = dfield_get_type(dfield); - void* data = dfield_get_data(dfield); - - ut_a(dtype_get_mtype(type) == DATA_INT); - - doc_id = static_cast(mach_read_from_8( - static_cast(data))); - - exp = que_node_get_next(que_node_get_next(exp)); - if (exp) { - ut_ad(table->versioned()); - dfield = que_node_get_val(exp); - type = dfield_get_type(dfield); - ut_ad(type->vers_sys_end()); - data = dfield_get_data(dfield); - ulint len = dfield_get_len(dfield); - if (table->versioned_by_id()) { - ut_ad(len == sizeof trx_id_max_bytes); - if (0 != memcmp(data, trx_id_max_bytes, len)) { - return true; - } - } else { - ut_ad(len == sizeof timestamp_max_bytes); - if (!IS_MAX_TIMESTAMP(data)) { - return true; - } - } - ut_ad(!(exp = que_node_get_next(exp))); - } - ut_ad(!exp); - - if (doc_id >= cache->next_doc_id) { - cache->next_doc_id = doc_id + 1; - } - } + fts_fetch_doc_id *fetch= static_cast(user_arg); + dict_table_t *table= static_cast(fetch->user_arg); + fts_cache_t *cache= table->fts->cache; + ut_ad(fetch->field_to_fetch.size() == 1); + ut_ad(ib_vector_is_empty(cache->get_docs)); + ulint len; + const byte *data= rec_get_nth_field(rec, offsets, + fetch->field_to_fetch.front(), &len); + doc_id_t doc_id = static_cast(mach_read_from_8(data)); - return(TRUE); + if (doc_id >= cache->next_doc_id) + cache->next_doc_id = doc_id + 1; + return true; } -/**********************************************************************//** -Callback function when we initialize the FTS at the start up +/** Callback function when we initialize the FTS at the start up time. It recovers Doc IDs that have not sync-ed to the auxiliary table, and require to bring them back into FTS index. +@param index clustered index +@param rec clustered index record +@param offsets offset to clustered index record +@param user_arg points to fts_fetch_doc_id & + fts_fetch_doc_id->user_arg points to fts_get_doc_t @return: always returns TRUE */ static -ibool -fts_init_recover_doc( -/*=================*/ - void* row, /*!< in: sel_node_t* */ - void* user_arg) /*!< in: fts cache */ -{ - - fts_doc_t doc; - ulint doc_len = 0; - ulint field_no = 0; - fts_get_doc_t* get_doc = static_cast(user_arg); - doc_id_t doc_id = FTS_NULL_DOC_ID; - sel_node_t* node = static_cast(row); - que_node_t* exp = node->select_list; - fts_cache_t* cache = get_doc->cache; - st_mysql_ftparser* parser = get_doc->index_cache->index->parser; - - fts_doc_init(&doc); - doc.found = TRUE; - - ut_ad(cache); - - /* Copy each indexed column content into doc->text.f_str */ - while (exp) { - dfield_t* dfield = que_node_get_val(exp); - ulint len = dfield_get_len(dfield); - - if (field_no == 0) { - dtype_t* type = dfield_get_type(dfield); - void* data = dfield_get_data(dfield); - - ut_a(dtype_get_mtype(type) == DATA_INT); - - doc_id = static_cast(mach_read_from_8( - static_cast(data))); - - field_no++; - exp = que_node_get_next(exp); - continue; - } - - if (len == UNIV_SQL_NULL) { - exp = que_node_get_next(exp); - continue; - } - - ut_ad(get_doc); - - if (!get_doc->index_cache->charset) { - get_doc->index_cache->charset = fts_get_charset( - dfield->type.prtype); - } - - doc.charset = get_doc->index_cache->charset; - - if (dfield_is_ext(dfield)) { - dict_table_t* table = cache->sync->table; - - doc.text.f_str = btr_copy_externally_stored_field( - &doc.text.f_len, - static_cast(dfield_get_data(dfield)), - table->space->zip_size(), len, - static_cast(doc.self_heap->arg)); - } else { - doc.text.f_str = static_cast( - dfield_get_data(dfield)); - - doc.text.f_len = len; - } - - if (field_no == 1) { - fts_tokenize_document(&doc, NULL, parser); - } else { - fts_tokenize_document_next(&doc, doc_len, NULL, parser); - } - - exp = que_node_get_next(exp); - - doc_len += (exp) ? len + 1 : len; - - field_no++; - } - - fts_cache_add_doc(cache, get_doc->index_cache, doc_id, doc.tokens); - - fts_doc_free(&doc); - - cache->added++; +bool fts_init_recover_doc(dict_index_t *index, const rec_t *rec, + const rec_offs *offsets, void *user_arg) +{ + ut_ad(dict_index_is_clust(index)); + fts_fetch_doc_id *fetch= static_cast(user_arg); + fts_get_doc_t *get_doc= static_cast(fetch->user_arg); + ut_ad(get_doc->cache); + fts_cache_t *cache= get_doc->cache; + st_mysql_ftparser *parser= get_doc->index_cache->index->parser; + doc_id_t doc_id= FTS_NULL_DOC_ID; + ulint doc_len= 0; + + fts_doc_t doc; + fts_doc_init(&doc); + doc.found = TRUE; + + bool first_doc= true; + for (uint32_t i= 0; i < fetch->field_to_fetch.size(); i++) + { + ulint field_no= fetch->field_to_fetch[i]; + if (i == 0) + { + ulint len; + const byte *data= rec_get_nth_field(rec, offsets, field_no, &len); + ut_ad(len == 8); + doc_id= static_cast(mach_read_from_8( + static_cast(data))); + continue; + } - if (doc_id >= cache->next_doc_id) { - cache->next_doc_id = doc_id + 1; - } + if (rec_offs_nth_sql_null(offsets, field_no)) + continue; + + if (!get_doc->index_cache->charset) + get_doc->index_cache->charset= + fts_get_charset(index->fields[field_no].col->prtype); + + doc.charset = get_doc->index_cache->charset; + if (rec_offs_nth_extern(offsets, field_no)) + doc.text.f_str= + static_cast( + btr_rec_copy_externally_stored_field( + rec, offsets, 0, field_no, &doc.text.f_len, + static_cast(doc.self_heap->arg))); + else + { + ulint len; + const byte *data= rec_get_nth_field(rec, offsets, field_no, &len); + doc.text.f_str= const_cast(data); + doc.text.f_len= len; + } + if (first_doc) + { + fts_tokenize_document(&doc, NULL, parser); + first_doc= false; + } + else fts_tokenize_document_next(&doc, ++doc_len, NULL, parser); + doc_len+= doc.text.f_len; + } - return(TRUE); + fts_cache_add_doc(cache, get_doc->index_cache, doc_id, doc.tokens); + fts_doc_free(&doc); + cache->added++; + if (doc_id >= cache->next_doc_id) + cache->next_doc_id = doc_id + 1; + return true; } /**********************************************************************//** @@ -6192,8 +5914,6 @@ fts_init_index( table->fts->added_synced = true; - fts_get_docs_clear(cache->get_docs); - func_exit: if (!has_cache_lock) { mysql_mutex_unlock(&cache->lock); @@ -6206,3 +5926,572 @@ fts_init_index( dict_sys.unlock(); } } + +/** Callback function for fetching the config value. */ +bool read_fts_config(dict_index_t *index, const rec_t *rec, + const rec_offs *offsets, void *user_arg) +{ + ut_ad(dict_index_is_clust(index)); + ulint len; + fts_string_t *value= static_cast(user_arg); + + for (uint32_t i= 0; i < index->n_fields; i++) + { + if (strcmp(index->fields[i].name, "value") == 0) + { + const byte *data= rec_get_nth_field(rec, offsets, i, &len); + if (len != UNIV_SQL_NULL) + { + ulint max_len= ut_min(value->f_len - 1, len); + memcpy(value->f_str, data, max_len); + value->f_len= max_len; + value->f_str[value->f_len]= '\0'; + return true; + } + } + } + + return false; +} + +void FTSQueryRunner::create_query_thread() noexcept +{ + ut_ad(m_heap); + m_thr= que_thr_create(que_fork_create(m_heap), m_heap, nullptr); +} + +void FTSQueryRunner::build_tuple(dict_index_t *index, uint32_t n_fields, + uint32_t n_uniq) noexcept +{ + if (m_tuple && m_tuple->n_fields == index->n_fields) return; + if (!n_fields) n_fields= index->n_fields; + if (!n_uniq) n_uniq= index->n_uniq; + m_tuple= dtuple_create(m_heap, n_fields); + dtuple_set_n_fields_cmp(m_tuple, n_uniq); + dict_index_copy_types(m_tuple, index, n_fields); +} + +void FTSQueryRunner::build_clust_ref(dict_index_t *index) noexcept +{ + if (m_clust_ref && m_clust_ref->n_fields == index->n_fields) return; + m_clust_ref= dtuple_create(m_heap, index->n_uniq); + dtuple_set_n_fields_cmp(m_clust_ref, index->n_uniq); + dict_index_copy_types(m_clust_ref, index, m_clust_ref->n_fields); +} + +void FTSQueryRunner::assign_config_fields(const char *name, const void* value, + ulint value_len) noexcept +{ + dfield_set_data(dtuple_get_nth_field(m_tuple, 0), name, strlen(name)); + + if (m_tuple->n_fields == 1) return; + + if (m_sys_buf == nullptr) + { + m_sys_buf= static_cast( + mem_heap_zalloc(m_heap, DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN)); + dfield_set_data(dtuple_get_nth_field(m_tuple, 1), + m_sys_buf, DATA_TRX_ID_LEN); + dfield_set_data(dtuple_get_nth_field(m_tuple, 2), + &m_sys_buf[DATA_TRX_ID_LEN], DATA_ROLL_PTR_LEN); + } + else + { + memset(dtuple_get_nth_field(m_tuple, 1)->data, 0x00, DATA_TRX_ID_LEN); + memset(dtuple_get_nth_field(m_tuple, 2)->data, 0x00, DATA_ROLL_PTR_LEN); + } + + if (value) + dfield_set_data(dtuple_get_nth_field(m_tuple, 3), value, value_len); +} + +void FTSQueryRunner::assign_common_table_fields(doc_id_t *doc_id) noexcept +{ + dfield_set_data(dtuple_get_nth_field(m_tuple, 0), doc_id, + sizeof(doc_id_t)); + if (m_tuple->n_fields == 1) return; + if (!m_sys_buf) + { + m_sys_buf= static_cast( + mem_heap_zalloc(m_heap, DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN)); + dfield_set_data(dtuple_get_nth_field(m_tuple, 1), m_sys_buf, + DATA_TRX_ID_LEN); + dfield_set_data(dtuple_get_nth_field(m_tuple, 2), + &m_sys_buf[DATA_TRX_ID_LEN], DATA_ROLL_PTR_LEN); + } + else + { + memset(dtuple_get_nth_field(m_tuple, 1)->data, 0x00, DATA_TRX_ID_LEN); + memset(dtuple_get_nth_field(m_tuple, 2)->data, 0x00, DATA_ROLL_PTR_LEN); + } +} + +void FTSQueryRunner::assign_aux_table_fields(const byte *word, + ulint word_len, + const fts_node_t *node) noexcept +{ + dfield_set_data(dtuple_get_nth_field(m_tuple, 0), word, word_len); + if (m_tuple->n_fields == 1) return; + if (!m_sys_buf) + { + byte *first_doc_id= static_cast(mem_heap_zalloc(m_heap, 8)); + mach_write_to_8(first_doc_id, node->first_doc_id); + dfield_set_data(dtuple_get_nth_field(m_tuple, 1), first_doc_id, 8); + + m_sys_buf= static_cast( + mem_heap_zalloc(m_heap, DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN)); + dfield_set_data(dtuple_get_nth_field(m_tuple, 2), + m_sys_buf, DATA_TRX_ID_LEN); + dfield_set_data(dtuple_get_nth_field(m_tuple, 3), + &m_sys_buf[DATA_TRX_ID_LEN], DATA_ROLL_PTR_LEN); + byte *last_doc_id= static_cast(mem_heap_zalloc(m_heap, 8)); + mach_write_to_8(last_doc_id, node->last_doc_id); + dfield_set_data(dtuple_get_nth_field(m_tuple, 4), last_doc_id, 8); + + byte *doc_count= static_cast(mem_heap_zalloc(m_heap, 4)); + mach_write_to_4(doc_count, node->doc_count); + dfield_set_data(dtuple_get_nth_field(m_tuple, 5), doc_count, 4); + } + else + { + mach_write_to_8(static_cast(dtuple_get_nth_field(m_tuple, 1)->data), + node->first_doc_id); + memset(dtuple_get_nth_field(m_tuple, 2)->data, 0x00, DATA_TRX_ID_LEN); + memset(dtuple_get_nth_field(m_tuple, 3)->data, 0x00, DATA_ROLL_PTR_LEN); + mach_write_to_8(static_cast(dtuple_get_nth_field(m_tuple, 4)->data), + node->last_doc_id); + mach_write_to_4(static_cast(dtuple_get_nth_field(m_tuple, 5)->data), + node->doc_count); + } + dfield_set_data(dtuple_get_nth_field(m_tuple, 6), node->ilist, + node->ilist_size); +} + +dberr_t FTSQueryRunner::handle_wait(dberr_t err, bool table_lock) noexcept +{ + m_trx->error_state= err; + m_thr->lock_state= QUE_THR_LOCK_ROW; + if (table_lock) + m_thr->lock_state= QUE_THR_LOCK_TABLE; + if (m_trx->lock.wait_thr) + { + if (lock_wait(m_thr) == DB_SUCCESS) + { + m_thr->lock_state= QUE_THR_LOCK_NOLOCK; + return DB_SUCCESS; + } + } + return m_trx->error_state; +} + +dict_table_t *FTSQueryRunner::open_table(fts_table_t *fts_table, dberr_t *err, + bool dict_locked) noexcept +{ + char table_name[MAX_FULL_NAME_LEN]; + fts_get_table_name(fts_table, table_name, dict_locked); + dict_table_t *table= dict_table_open_on_name(table_name, dict_locked, + DICT_ERR_IGNORE_NONE); + if (table == nullptr) *err= DB_TABLE_NOT_FOUND; + return table; +} + +dberr_t FTSQueryRunner::prepare_for_write(dict_table_t *table) noexcept +{ + if (!m_thr) create_query_thread(); + trx_start_if_not_started(m_trx, true); + m_thr->graph->trx = m_trx; + if (!m_trx->read_view.is_open()) + m_trx->read_view.open(m_trx); + dberr_t err= DB_SUCCESS; +lock_again: + err= lock_table(table, nullptr, LOCK_IX, m_thr); + if (err) + { + err= handle_wait(err, true); + if (err == DB_SUCCESS) + goto lock_again; + } + return err; +} + +dberr_t FTSQueryRunner::prepare_for_read(dict_table_t *table) noexcept +{ + if (!m_thr) create_query_thread(); + trx_start_if_not_started(m_trx, false); + m_thr->graph->trx = m_trx; + if (!m_trx->read_view.is_open()) + m_trx->read_view.open(m_trx); + dberr_t err= DB_SUCCESS; +lock_again: + err= lock_table(table, nullptr, LOCK_IS, m_thr); + if (err) + { + err= handle_wait(err, true); + if (err == DB_SUCCESS) + goto lock_again; + } + return err; +} + +dberr_t FTSQueryRunner::write_record(dict_table_t *table) noexcept +{ + dberr_t err= DB_SUCCESS; +run_again: + err= row_ins_clust_index_entry(dict_table_get_first_index(table), + m_tuple, m_thr, 0); + if (err) + { + err= handle_wait(err); + if (err == DB_SUCCESS) + goto run_again; + } + return err; +} + +dberr_t FTSQueryRunner::lock_or_sees_rec(dict_index_t *index, const rec_t *rec, + const rec_t **out_rec, + rec_offs **offsets, mtr_t *mtr, + fts_operation op) noexcept +{ + uint32_t lock_mode= LOCK_REC_NOT_GAP; + buf_block_t *block= btr_pcur_get_block(m_pcur); + dberr_t err= DB_SUCCESS; + bool supremum= page_rec_is_supremum(rec); + *out_rec= rec; + switch (op) { + case SELECT_UPDATE: + case REMOVE: + ut_ad(index->is_clust()); + if (supremum) lock_mode= LOCK_ORDINARY; + err= lock_clust_rec_read_check_and_lock(0, block, rec, index, *offsets, + m_lock_type, lock_mode, m_thr); + if (err == DB_SUCCESS_LOCKED_REC) err= DB_SUCCESS; + break; + case READ: + if (supremum) return err; + + if (index->is_clust()) + { + /* Construct the record based on transaction read view */ + const trx_id_t id= row_get_rec_trx_id(rec, index, *offsets); + ReadView *view= &m_trx->read_view; + if (view->changes_visible(id)); + else if (id < view->low_limit_id() || id < trx_sys.get_max_trx_id()) + { + rec_t *old_vers; + err= row_vers_build_for_consistent_read(rec, mtr, index, offsets, + view, &m_heap, m_heap, + &old_vers, nullptr); + if (err) return err; + *out_rec= old_vers; + } + } + else + { + /* Do lookup and construct the clustered index record */ + dict_index_t *clust_index= dict_table_get_first_index(index->table); + if (!m_clust_pcur) + m_clust_pcur= static_cast( + mem_heap_zalloc(m_heap, sizeof(btr_pcur_t))); + build_clust_ref(clust_index); + + Row_sel_get_clust_rec_for_mysql row_sel_get_clust_rec_for_mysql; + + err= row_sel_get_clust_rec_for_mysql(m_clust_pcur, m_pcur, + m_clust_ref, m_lock_type, + m_trx, index, rec, m_thr, + out_rec, offsets, &m_heap, + &m_old_vers_heap, nullptr, mtr); + } + break; + default: err= DB_CORRUPTION; + } + return err; +} + +static +int fts_cmp_rec_dtuple_pattern(const dtuple_t *dtuple, const rec_t *rec, + const dict_index_t *index, + const rec_offs *offsets) noexcept +{ + for (uint32_t i= 0; i < dtuple->n_fields; i++) + { + const byte* rec_b_ptr; + const dfield_t* dtuple_field= dtuple_get_nth_field(dtuple, i); + const byte* dtuple_b_ptr= + static_cast(dfield_get_data(dtuple_field)); + const dtype_t* type= dfield_get_type(dtuple_field); + ulint dtuple_f_len= dfield_get_len(dtuple_field); + ulint rec_f_len; + + ut_ad(!rec_offs_nth_extern(offsets, i)); + ut_ad(!rec_offs_nth_default(offsets, i)); + + rec_b_ptr= rec_get_nth_field(rec, offsets, i, &rec_f_len); + ut_ad(!dfield_is_ext(dtuple_field)); + rec_f_len= dtuple_f_len; + int ret= cmp_data(type->mtype, type->prtype, false, dtuple_b_ptr, + dtuple_f_len, rec_b_ptr, rec_f_len); + if (ret) return ret; + } + return 0; +} + +dberr_t FTSQueryRunner::record_executor(dict_index_t *index, fts_operation op, + fts_match_key match_op, + page_cur_mode_t mode, + fts_sql_callback *func, + void *user_arg) noexcept +{ + if (m_pcur == nullptr) + m_pcur= static_cast(mem_heap_zalloc(m_heap, + sizeof(btr_pcur_t))); + + btr_latch_mode latch_mode; + /* Determine the latch mode and lock type based on statement */ + switch (op) { + case REMOVE: + case SELECT_UPDATE: + latch_mode= BTR_MODIFY_LEAF; + m_lock_type= LOCK_X; + break; + case READ: + latch_mode= BTR_SEARCH_LEAF; + m_lock_type= LOCK_NONE; + break; + default: return DB_CORRUPTION; + } + + ut_ad(is_stopword_table || + strstr(index->name(), FTS_DOC_ID_INDEX.str) || + strstr(index->table->name.m_name, "/FTS_")); + mtr_t mtr; + rec_t *rec= nullptr; + dberr_t err= DB_SUCCESS; + bool restore_cursor= false; + bool move_next= false; + /* If the function executor can't handle any more records then + abort the operation and retry again */ + bool interrupt= false; + const rec_t *clust_rec= nullptr; + dict_table_t *table= index->table; + dict_index_t *clust_index= dict_table_get_first_index(table); + bool need_to_access_clust= index != clust_index; + + mtr.start(); + mtr.set_named_space(index->table->space); + m_pcur->btr_cur.page_cur.index= index; + rec_offs offsets_[REC_OFFS_NORMAL_SIZE]; + rec_offs *offsets= offsets_; + rec_offs_init(offsets_); + +reopen_index: + if (restore_cursor) + { + /* Restore the cursor */ + auto status= m_pcur->restore_position(latch_mode, &mtr); + if (status == btr_pcur_t::SAME_ALL) + { + restore_cursor= false; + if (move_next) + { + move_next= false; + goto next_rec; + } + } + else err= DB_CORRUPTION; + } + else if (m_tuple) + err= btr_pcur_open_with_no_init(m_tuple, mode, latch_mode, + m_pcur, &mtr); + else err= m_pcur->open_leaf(true, index, latch_mode, &mtr); + + if (err) + { +func_exit: + mtr.commit(); + return err; + } + +rec_loop: + rec= btr_pcur_get_rec(m_pcur); + if (page_rec_is_infimum(rec)) + goto next_rec; + + offsets= rec_get_offsets(rec, index, offsets, index->n_core_fields, + ULINT_UNDEFINED, &m_heap); + + if (page_rec_is_supremum(rec)) + { + /* lock the supremum record based on the operation */ + err= lock_or_sees_rec(index, rec, &clust_rec, &offsets, &mtr, op); + if (err) goto lock_wait_or_error; + goto next_rec; + } + + if (m_tuple) + { + /* In case of unique match, InnoDB should compare the tuple with record */ + if (match_op == MATCH_UNIQUE && + cmp_dtuple_rec(m_tuple, rec, index, offsets) != 0) + { + err= DB_RECORD_NOT_FOUND; + goto func_exit; + } + + if (match_op == MATCH_PREFIX && + cmp_dtuple_is_prefix_of_rec(m_tuple, rec, index, offsets) == 0) + { + err= DB_RECORD_NOT_FOUND; + goto func_exit; + } + + /* Searching tuple field can be pattern of the record key. So compare + the tuple field data with record only till tuple field length */ + if (match_op == MATCH_PATTERN && + fts_cmp_rec_dtuple_pattern(m_tuple, rec, index, offsets)) + { + err= DB_RECORD_NOT_FOUND; + goto func_exit; + } + } + + err= lock_or_sees_rec(index, rec, &clust_rec, &offsets, &mtr, op); + + if (err) goto lock_wait_or_error; + + if (!need_to_access_clust && + (clust_rec == nullptr || + rec_get_deleted_flag(clust_rec, dict_table_is_comp(table)))) + goto next_rec; + + /* Found the suitable record and do the operation based on the statement */ + switch (op) { + case REMOVE: + if (dict_index_is_clust(index) && clust_rec) + { + ut_ad(clust_rec == rec); + err= btr_cur_del_mark_set_clust_rec(btr_pcur_get_block(m_pcur), + rec, index, offsets, m_thr, + m_tuple, &mtr); + } + else err= DB_CORRUPTION; + break; + case READ: + case SELECT_UPDATE: + if (func && clust_rec) + interrupt= !((*func)(clust_index, clust_rec, offsets, user_arg)); + break; + default: err= DB_CORRUPTION; + } + + if (err == DB_SUCCESS) + { + /* Avoid storing and restoring of cursor if we are doing READ_ALL + operation directly on clustered index */ + if (!interrupt && !need_to_access_clust && op == READ + && match_op != MATCH_UNIQUE) + goto next_rec; + } + +lock_wait_or_error: + if (m_clust_pcur) + btr_pcur_store_position(m_clust_pcur, &mtr); + btr_pcur_store_position(m_pcur, &mtr); + mtr.commit(); + + if (err == DB_SUCCESS) + { + /* In case, InnoDB got interruption from function handler or + we already found the unique key value */ + if ((op != REMOVE && match_op == MATCH_UNIQUE) || interrupt) + return err; + move_next= true; + } + else + { + err= handle_wait(err); + if (err != DB_SUCCESS) return err; + } + mtr.start(); + restore_cursor= true; + goto reopen_index; + +next_rec: + if (btr_pcur_is_after_last_on_page(m_pcur)) + { + if (btr_pcur_is_after_last_in_tree(m_pcur)) + { + err= DB_END_OF_INDEX; + goto func_exit; + } + err= btr_pcur_move_to_next_page(m_pcur, &mtr); + if (err) goto lock_wait_or_error; + } + else if (!btr_pcur_move_to_next_on_page(m_pcur)) + { + err= DB_CORRUPTION; + goto func_exit; + } + goto rec_loop; +} + +void FTSQueryRunner::build_update_config(dict_table_t *table, uint16_t field_no, + const fts_string_t *new_value) noexcept +{ + m_update= upd_create(1, m_heap); + upd_field_t *uf= upd_get_nth_field(m_update, 0); + + dict_index_t *index= dict_table_get_first_index(table); + dict_field_t *ifield= dict_index_get_nth_field(index, field_no); + dict_col_copy_type(dict_field_get_col(ifield), + dfield_get_type(&uf->new_val)); + dfield_set_data(&uf->new_val, new_value->f_str, new_value->f_len); + uf->field_no= field_no; + uf->orig_len= 0; +} + +dberr_t FTSQueryRunner::update_record(dict_table_t *table, uint16_t old_len, + uint16_t new_len) noexcept +{ + dberr_t err= DB_SUCCESS; + dict_index_t *index= dict_table_get_first_index(table); + ulint cmpl_info= (old_len == new_len) + ? UPD_NODE_NO_SIZE_CHANGE : 0; + + mtr_t mtr; + mtr.start(); + mtr.set_named_space(table->space); + if (m_pcur->restore_position(BTR_MODIFY_LEAF, &mtr) + != btr_pcur_t::SAME_ALL) + { + err= DB_RECORD_NOT_FOUND; + mtr.commit(); + return err; + } + + rec_t *rec= btr_pcur_get_rec(m_pcur); + rec_offs offsets_[REC_OFFS_NORMAL_SIZE]; + rec_offs *offsets; + rec_offs_init(offsets_); + + offsets= rec_get_offsets(rec, index, offsets_, index->n_core_fields, + ULINT_UNDEFINED, &m_heap); + + if (old_len == new_len) + cmpl_info= UPD_NODE_NO_SIZE_CHANGE; + + err= row_upd_clust_rec_low(0, m_update, m_pcur, cmpl_info, + index, offsets, &m_heap, m_thr, + m_trx, &mtr); + mtr.commit(); + return err; +} + +FTSQueryRunner::~FTSQueryRunner() +{ + if (m_pcur) btr_pcur_close(m_pcur); + if (m_clust_pcur) btr_pcur_close(m_clust_pcur); + if (m_old_vers_heap) mem_heap_free(m_old_vers_heap); + mem_heap_free(m_heap); +} diff --git a/storage/innobase/fts/fts0opt.cc b/storage/innobase/fts/fts0opt.cc index 38b45a94142fb..830d65fbf8b28 100644 --- a/storage/innobase/fts/fts0opt.cc +++ b/storage/innobase/fts/fts0opt.cc @@ -238,28 +238,6 @@ static ulint fts_optimize_time_limit; /** It's defined in fts0fts.cc */ extern const char* fts_common_tables[]; -/** SQL Statement for changing state of rows to be deleted from FTS Index. */ -static const char* fts_init_delete_sql = - "BEGIN\n" - "\n" - "INSERT INTO $BEING_DELETED\n" - "SELECT doc_id FROM $DELETED;\n" - "\n" - "INSERT INTO $BEING_DELETED_CACHE\n" - "SELECT doc_id FROM $DELETED_CACHE;\n"; - -static const char* fts_delete_doc_ids_sql = - "BEGIN\n" - "\n" - "DELETE FROM $DELETED WHERE doc_id = :doc_id1;\n" - "DELETE FROM $DELETED_CACHE WHERE doc_id = :doc_id2;\n"; - -static const char* fts_end_delete_sql = - "BEGIN\n" - "\n" - "DELETE FROM $BEING_DELETED;\n" - "DELETE FROM $BEING_DELETED_CACHE;\n"; - /**********************************************************************//** Initialize fts_zip_t. */ static @@ -360,208 +338,162 @@ fts_word_init( return(word); } -/**********************************************************************//** -Read the FTS INDEX row. -@return fts_node_t instance */ -static -fts_node_t* -fts_optimize_read_node( -/*===================*/ - fts_word_t* word, /*!< in: */ - que_node_t* exp) /*!< in: */ +/** Read the node from auxiliary table record +@param index auxiliary table index +@param word word +@param rec auxiliary table record +@param offsets offsets to table record +@param heap memory heap to store external data */ +static fts_node_t *fts_optimize_read_node(dict_index_t *index, fts_word_t *word, + const rec_t *rec, + const rec_offs *offsets, + mem_heap_t *heap) { - int i; - fts_node_t* node = static_cast( - ib_vector_push(word->nodes, NULL)); - - /* Start from 1 since the first node has been read by the caller */ - for (i = 1; exp; exp = que_node_get_next(exp), ++i) { - - dfield_t* dfield = que_node_get_val(exp); - byte* data = static_cast( - dfield_get_data(dfield)); - ulint len = dfield_get_len(dfield); - - ut_a(len != UNIV_SQL_NULL); - - /* Note: The column numbers below must match the SELECT */ - switch (i) { - case 1: /* DOC_COUNT */ - node->doc_count = mach_read_from_4(data); - break; - - case 2: /* FIRST_DOC_ID */ - node->first_doc_id = fts_read_doc_id(data); - break; - - case 3: /* LAST_DOC_ID */ - node->last_doc_id = fts_read_doc_id(data); - break; - - case 4: /* ILIST */ - node->ilist_size_alloc = node->ilist_size = len; - node->ilist = static_cast(ut_malloc_nokey(len)); - memcpy(node->ilist, data, len); - break; - - default: - ut_error; - } - } - - /* Make sure all columns were read. */ - ut_a(i == 5); - - return(node); + ut_ad(dict_index_is_clust(index)); + /* Start from 1 since the first node has been read by the caller */ + fts_node_t *node= static_cast(ib_vector_push(word->nodes, NULL)); + ulint len; + const byte *data= nullptr; + for (uint32_t i= 1; i < index->n_fields; i++) + { + switch(i) { + case 1: + /* FIRST DOC ID */ + data= rec_get_nth_field(rec, offsets, i, &len); + ut_ad(len == 8); + node->first_doc_id= fts_read_doc_id(data); + break; + case 4: + /* Last doc id */ + data= rec_get_nth_field(rec, offsets, i, &len); + ut_ad(len == 8); + node->last_doc_id= fts_read_doc_id(data); + break; + case 5: + /* Doc count */ + data= rec_get_nth_field(rec, offsets, i, &len); + ut_ad(len == 4); + node->doc_count= mach_read_from_4(data); + break; + case 6: + /* Ilist */ + if (rec_offs_nth_extern(offsets, 6)) + data= btr_rec_copy_externally_stored_field(rec, offsets, 0, 6, &len, + heap); + else data = rec_get_nth_field(rec, offsets, 6, &len); + node->ilist_size_alloc= node->ilist_size= len; + node->ilist = static_cast(ut_malloc_nokey(len)); + memcpy(node->ilist, data, len); + } + } + return node; } -/**********************************************************************//** -Callback function to fetch the rows in an FTS INDEX record. -@return always returns non-NULL */ -ibool -fts_optimize_index_fetch_node( -/*==========================*/ - void* row, /*!< in: sel_node_t* */ - void* user_arg) /*!< in: pointer to ib_vector_t */ +bool fts_optimize_index_fetch_node(dict_index_t *index, const rec_t *rec, + const rec_offs *offsets, void *user_arg) { - fts_word_t* word; - sel_node_t* sel_node = static_cast(row); - fts_fetch_t* fetch = static_cast(user_arg); - ib_vector_t* words = static_cast(fetch->read_arg); - que_node_t* exp = sel_node->select_list; - dfield_t* dfield = que_node_get_val(exp); - void* data = dfield_get_data(dfield); - ulint dfield_len = dfield_get_len(dfield); - fts_node_t* node; - bool is_word_init = false; - - ut_a(dfield_len <= FTS_MAX_WORD_LEN); - - if (ib_vector_size(words) == 0) { - - word = static_cast(ib_vector_push(words, NULL)); - fts_word_init(word, (byte*) data, dfield_len); - is_word_init = true; - } + ut_ad(dict_index_is_clust(index)); + fts_fetch_t *fetch = static_cast(user_arg); + ib_vector_t *words = static_cast(fetch->read_arg); + bool is_word_init = false; + fts_word_t *word; - word = static_cast(ib_vector_last(words)); + ulint len= 0; + /* Word */ + const byte *data= rec_get_nth_field(rec, offsets, 0, &len); - if (dfield_len != word->text.f_len - || memcmp(word->text.f_str, data, dfield_len)) { - - word = static_cast(ib_vector_push(words, NULL)); - fts_word_init(word, (byte*) data, dfield_len); - is_word_init = true; - } - - node = fts_optimize_read_node(word, que_node_get_next(exp)); - - fetch->total_memory += node->ilist_size; - if (is_word_init) { - fetch->total_memory += sizeof(fts_word_t) - + sizeof(ib_alloc_t) + sizeof(ib_vector_t) + dfield_len - + sizeof(fts_node_t) * FTS_WORD_NODES_INIT_SIZE; - } else if (ib_vector_size(words) > FTS_WORD_NODES_INIT_SIZE) { - fetch->total_memory += sizeof(fts_node_t); - } + if (ib_vector_size(words) == 0) + { + word = static_cast(ib_vector_push(words, NULL)); + fts_word_init(word, (byte*) data, len); + is_word_init = true; + } - if (fetch->total_memory >= fts_result_cache_limit) { - return(FALSE); - } + word = static_cast(ib_vector_last(words)); + if (len != word->text.f_len || + memcmp(word->text.f_str, data, len)) + { + word = static_cast(ib_vector_push(words, NULL)); + fts_word_init(word, (byte*) data, len); + is_word_init = true; + } - return(TRUE); + fts_node_t *node= fts_optimize_read_node(index, word, rec, offsets, + fetch->heap); + fetch->total_memory += node->ilist_size; + if (is_word_init) + fetch->total_memory += sizeof(fts_word_t) + + sizeof(ib_alloc_t) + sizeof(ib_vector_t) + len + + sizeof(fts_node_t) * FTS_WORD_NODES_INIT_SIZE; + else if (ib_vector_size(words) > FTS_WORD_NODES_INIT_SIZE) + fetch->total_memory += sizeof(fts_node_t); + + return fetch->total_memory < fts_result_cache_limit; } -/**********************************************************************//** -Read the rows from the FTS inde. -@return DB_SUCCESS or error code */ -dberr_t -fts_index_fetch_nodes( -/*==================*/ - trx_t* trx, /*!< in: transaction */ - que_t** graph, /*!< in: prepared statement */ - fts_table_t* fts_table, /*!< in: table of the FTS INDEX */ - const fts_string_t* - word, /*!< in: the word to fetch */ - fts_fetch_t* fetch) /*!< in: fetch callback.*/ +dberr_t fts_index_fetch_nodes_low( + dict_table_t *aux_table, const fts_string_t *word, fts_fetch_t *fetch, + FTSQueryRunner *sqlRunner) { - pars_info_t* info; - dberr_t error; - char table_name[MAX_FULL_NAME_LEN]; - - trx->op_info = "fetching FTS index nodes"; - - if (*graph) { - info = (*graph)->info; - } else { - ulint selected; - - info = pars_info_create(); - - ut_a(fts_table->type == FTS_INDEX_TABLE); - - selected = fts_select_index(fts_table->charset, - word->f_str, word->f_len); - - fts_table->suffix = fts_get_suffix(selected); - - fts_get_table_name(fts_table, table_name); - - pars_info_bind_id(info, "table_name", table_name); - } - - pars_info_bind_function(info, "my_func", fetch->read_record, fetch); - pars_info_bind_varchar_literal(info, "word", word->f_str, word->f_len); - - if (!*graph) { - - *graph = fts_parse_sql( - fts_table, - info, - "DECLARE FUNCTION my_func;\n" - "DECLARE CURSOR c IS" - " SELECT word, doc_count, first_doc_id, last_doc_id," - " ilist\n" - " FROM $table_name\n" - " WHERE word LIKE :word\n" - " ORDER BY first_doc_id;\n" - "BEGIN\n" - "\n" - "OPEN c;\n" - "WHILE 1 = 1 LOOP\n" - " FETCH c INTO my_func();\n" - " IF c % NOTFOUND THEN\n" - " EXIT;\n" - " END IF;\n" - "END LOOP;\n" - "CLOSE c;"); - } - - for (;;) { - error = fts_eval_sql(trx, *graph); - - if (UNIV_LIKELY(error == DB_SUCCESS)) { - fts_sql_commit(trx); - - break; /* Exit the loop. */ - } else { - fts_sql_rollback(trx); - - if (error == DB_LOCK_WAIT_TIMEOUT) { - ib::warn() << "lock wait timeout reading" - " FTS index. Retrying!"; + dberr_t err= sqlRunner->prepare_for_read(aux_table); + if (err == DB_SUCCESS) + { + ut_ad(UT_LIST_GET_LEN(aux_table->indexes) == 1); + dict_index_t *index= dict_table_get_first_index(aux_table); + sqlRunner->build_tuple(index, 1, 1); - trx->error_state = DB_SUCCESS; - } else { - ib::error() << "(" << error - << ") while reading FTS index."; + ulint len= word->f_len; + fts_match_key match_op= MATCH_PREFIX; + if (word->f_str[word->f_len - 1] == '%') + { + len-= 1; + match_op= MATCH_PATTERN; + } - break; /* Exit the loop. */ - } - } - } + sqlRunner->assign_aux_table_fields(word->f_str, len); + /* SELECT WORD, FIRST_DOC_ID, LAST_DOC_ID, DOC_COUNT, ILIST + FROM FTS_AUX_TABLE WHERE WORD >= word */ + err= sqlRunner->record_executor(index, READ, match_op, PAGE_CUR_GE, + fetch->callback, fetch); + if (err == DB_RECORD_NOT_FOUND || err == DB_END_OF_INDEX) + err= DB_SUCCESS; + } + return err; +} - return(error); +dberr_t fts_index_fetch_nodes(trx_t *trx, fts_table_t *fts_table, + const fts_string_t *word, fts_fetch_t *fetch) +{ + ulint selected= fts_select_index(fts_table->charset, + word->f_str, word->f_len); + fts_table->suffix = fts_get_suffix(selected); + + dberr_t err= DB_SUCCESS; + FTSQueryRunner sqlRunner(trx); + fetch->heap= sqlRunner.heap(); + dict_table_t *aux_table= sqlRunner.open_table(fts_table, &err); + if (aux_table) + { +retry: + err= fts_index_fetch_nodes_low(aux_table, word, fetch, &sqlRunner); + if (UNIV_LIKELY(err == DB_SUCCESS)) + fts_sql_commit(trx); + else + { + fts_sql_rollback(trx); + if (err == DB_LOCK_WAIT_TIMEOUT) + { + ib::warn() << "lock wait timeout reading FTS index. Retrying!"; + trx->error_state = DB_SUCCESS; + goto retry; + } + else + ib::error() << "(" << err << ") while reading FTS index."; + } + aux_table->release(); + } + fetch->heap= nullptr; + return err; } /**********************************************************************//** @@ -665,86 +597,70 @@ fts_zip_read_word( return(zip->status == Z_OK || zip->status == Z_STREAM_END ? ptr : NULL); } -/**********************************************************************//** -Callback function to fetch and compress the word in an FTS -INDEX record. -@return FALSE on EOF */ -static -ibool -fts_fetch_index_words( -/*==================*/ - void* row, /*!< in: sel_node_t* */ - void* user_arg) /*!< in: pointer to ib_vector_t */ +/** Callback function to fetch and compress the word in an FTS AUXILIARY table +@param index auxiliary index +@param rec auxiliary index record +@param offsets offsets to the record +@param user_arg compressed list which will have words from auxiliary table +@retval true if number of words is lesser than maximum words to be compressed +or else false */ +static bool fts_fetch_index_words(dict_index_t *index, const rec_t *rec, + const rec_offs *offsets, void *user_arg) { - sel_node_t* sel_node = static_cast(row); - fts_zip_t* zip = static_cast(user_arg); - que_node_t* exp = sel_node->select_list; - dfield_t* dfield = que_node_get_val(exp); - - ut_a(dfield_get_len(dfield) <= FTS_MAX_WORD_LEN); - - uint16 len = uint16(dfield_get_len(dfield)); - void* data = dfield_get_data(dfield); - - /* Skip the duplicate words. */ - if (zip->word.f_len == len && !memcmp(zip->word.f_str, data, len)) { - return(TRUE); - } - - memcpy(zip->word.f_str, data, len); - zip->word.f_len = len; - - ut_a(zip->zp->avail_in == 0); - ut_a(zip->zp->next_in == NULL); - - /* The string is prefixed by len. */ - /* FIXME: This is not byte order agnostic (InnoDB data files - with FULLTEXT INDEX are not portable between little-endian and - big-endian systems!) */ - zip->zp->next_in = reinterpret_cast(&len); - zip->zp->avail_in = sizeof(len); - - /* Compress the word, create output blocks as necessary. */ - while (zip->zp->avail_in > 0) { - - /* No space left in output buffer, create a new one. */ - if (zip->zp->avail_out == 0) { - byte* block; - - block = static_cast( - ut_malloc_nokey(zip->block_sz)); - - ib_vector_push(zip->blocks, &block); - - zip->zp->next_out = block; - zip->zp->avail_out = static_cast(zip->block_sz); - } - - switch (zip->status = deflate(zip->zp, Z_NO_FLUSH)) { - case Z_OK: - if (zip->zp->avail_in == 0) { - zip->zp->next_in = static_cast(data); - zip->zp->avail_in = uInt(len); - ut_a(len <= FTS_MAX_WORD_LEN); - len = 0; - } - continue; - - case Z_STREAM_END: - case Z_BUF_ERROR: - case Z_STREAM_ERROR: - default: - ut_error; - } - } - - /* All data should have been compressed. */ - ut_a(zip->zp->avail_in == 0); - zip->zp->next_in = NULL; - - ++zip->n_words; + ut_ad(dict_index_is_clust(index)); + fts_zip_t *zip = static_cast(user_arg); + ulint rec_len; + const byte *data= rec_get_nth_field(rec, offsets, 0, &rec_len); + ut_a(rec_len <= FTS_MAX_WORD_LEN); + uint16_t len= (uint16_t) rec_len; + /* Skip the duplicate words. */ + if (zip->word.f_len == len && !memcmp(zip->word.f_str, data, len)) + return true; + memcpy(zip->word.f_str, data, len); + zip->word.f_len = len; + ut_a(zip->zp->avail_in == 0); + ut_a(zip->zp->next_in == NULL); + + /* The string is prefixed by len. */ + /* FIXME: This is not byte order agnostic (InnoDB data files + with FULLTEXT INDEX are not portable between little-endian and + big-endian systems!) */ + zip->zp->next_in = reinterpret_cast(&len); + zip->zp->avail_in = sizeof(len); + + /* Compress the word, create output blocks as necessary. */ + while (zip->zp->avail_in > 0) + { + /* No space left in output buffer, create a new one. */ + if (zip->zp->avail_out == 0) + { + byte *block= static_cast(ut_malloc_nokey(zip->block_sz)); + ib_vector_push(zip->blocks, &block); + zip->zp->next_out = block; + zip->zp->avail_out = static_cast(zip->block_sz); + } - return(zip->n_words >= zip->max_words ? FALSE : TRUE); + switch (zip->status = deflate(zip->zp, Z_NO_FLUSH)) { + case Z_OK: + if (zip->zp->avail_in == 0) + { + zip->zp->next_in = const_cast(data); + zip->zp->avail_in = uInt(len); + ut_a(len <= FTS_MAX_WORD_LEN); + len = 0; + } + continue; + case Z_STREAM_END: + case Z_BUF_ERROR: + case Z_STREAM_ERROR: + default: ut_error; + } + } + /* All data should have been compressed. */ + ut_a(zip->zp->avail_in == 0); + zip->zp->next_in = NULL; + ++zip->n_words; + return zip->n_words < zip->max_words; } /**********************************************************************//** @@ -789,241 +705,148 @@ fts_zip_deflate_end( memset(zip->zp, 0, sizeof(*zip->zp)); } -/**********************************************************************//** -Read the words from the FTS INDEX. +/** Read the words from the FTS INDEX. +@param optim optimize scratch pad +@param word query word +@param n_words max words to be read @return DB_SUCCESS if all OK, DB_TABLE_NOT_FOUND if no more indexes - to search else error code */ +to search else error code */ static MY_ATTRIBUTE((nonnull, warn_unused_result)) -dberr_t -fts_index_fetch_words( -/*==================*/ - fts_optimize_t* optim, /*!< in: optimize scratch pad */ - const fts_string_t* word, /*!< in: get words greater than this - word */ - ulint n_words)/*!< in: max words to read */ +dberr_t fts_index_fetch_words(fts_optimize_t *optim, const fts_string_t *word, + ulint n_words) { - pars_info_t* info; - que_t* graph; - ulint selected; - fts_zip_t* zip = NULL; - dberr_t error = DB_SUCCESS; - mem_heap_t* heap = static_cast(optim->self_heap->arg); - ibool inited = FALSE; - - optim->trx->op_info = "fetching FTS index words"; - - if (optim->zip == NULL) { - optim->zip = fts_zip_create(heap, FTS_ZIP_BLOCK_SIZE, n_words); - } else { - fts_zip_initialize(optim->zip); - } - - for (selected = fts_select_index( - optim->fts_index_table.charset, word->f_str, word->f_len); - selected < FTS_NUM_AUX_INDEX; - selected++) { - - char table_name[MAX_FULL_NAME_LEN]; - - optim->fts_index_table.suffix = fts_get_suffix(selected); - - info = pars_info_create(); - - pars_info_bind_function( - info, "my_func", fts_fetch_index_words, optim->zip); - - pars_info_bind_varchar_literal( - info, "word", word->f_str, word->f_len); - - fts_get_table_name(&optim->fts_index_table, table_name); - pars_info_bind_id(info, "table_name", table_name); - - graph = fts_parse_sql( - &optim->fts_index_table, - info, - "DECLARE FUNCTION my_func;\n" - "DECLARE CURSOR c IS" - " SELECT word\n" - " FROM $table_name\n" - " WHERE word > :word\n" - " ORDER BY word;\n" - "BEGIN\n" - "\n" - "OPEN c;\n" - "WHILE 1 = 1 LOOP\n" - " FETCH c INTO my_func();\n" - " IF c % NOTFOUND THEN\n" - " EXIT;\n" - " END IF;\n" - "END LOOP;\n" - "CLOSE c;"); - - zip = optim->zip; - - for (;;) { - int err; - - if (!inited && ((err = deflateInit(zip->zp, 9)) - != Z_OK)) { - ib::error() << "ZLib deflateInit() failed: " - << err; - - error = DB_ERROR; - break; - } else { - inited = TRUE; - error = fts_eval_sql(optim->trx, graph); - } - - if (UNIV_LIKELY(error == DB_SUCCESS)) { - //FIXME fts_sql_commit(optim->trx); - break; - } else { - //FIXME fts_sql_rollback(optim->trx); - - if (error == DB_LOCK_WAIT_TIMEOUT) { - ib::warn() << "Lock wait timeout" - " reading document. Retrying!"; - - /* We need to reset the ZLib state. */ - inited = FALSE; - deflateEnd(zip->zp); - fts_zip_init(zip); - - optim->trx->error_state = DB_SUCCESS; - } else { - ib::error() << "(" << error - << ") while reading document."; - - break; /* Exit the loop. */ - } - } - } - - que_graph_free(graph); - - /* Check if max word to fetch is exceeded */ - if (optim->zip->n_words >= n_words) { - break; - } - } - - if (error == DB_SUCCESS && zip->status == Z_OK && zip->n_words > 0) { - - /* All data should have been read. */ - ut_a(zip->zp->avail_in == 0); - - fts_zip_deflate_end(zip); - } else { - deflateEnd(zip->zp); - } + dberr_t error= DB_SUCCESS; + mem_heap_t *heap= static_cast(optim->self_heap->arg); + bool inited= true; + optim->trx->op_info = "fetching FTS index words"; + + if (optim->zip == NULL) + optim->zip = fts_zip_create(heap, FTS_ZIP_BLOCK_SIZE, n_words); + else fts_zip_initialize(optim->zip); + + fts_zip_t *zip= optim->zip; + dberr_t err= DB_SUCCESS; + FTSQueryRunner sqlRunner(optim->trx); + for (ulint selected= + fts_select_index(optim->fts_index_table.charset, word->f_str, + word->f_len); + selected < FTS_NUM_AUX_INDEX; selected++) + { + optim->fts_index_table.suffix= fts_get_suffix(selected); + dict_table_t *aux_table= sqlRunner.open_table(&optim->fts_index_table, + &err); + if (aux_table) + { +retry: + err= sqlRunner.prepare_for_read(aux_table); + if (err == DB_SUCCESS) + { + ut_ad(UT_LIST_GET_LEN(aux_table->indexes) == 1); + dict_index_t *clust_index= dict_table_get_first_index(aux_table); + sqlRunner.build_tuple(clust_index, 1, 1); + sqlRunner.assign_aux_table_fields(word->f_str, word->f_len); + int ret; + if (!inited && ((ret= deflateInit(zip->zp, 9)) != Z_OK)) + { + ib::error() << "ZLib deflateInit() failed: " << ret; + err= DB_ERROR; + break; + } + else + { + inited= true; + /* Executes the following query + SELECT word FROM FTS_AUX_TABLE WHERE word > :word */ + err= sqlRunner.record_executor(clust_index, READ, MATCH_ALL, + PAGE_CUR_G, &fts_fetch_index_words, + &zip); + } + } + + if (UNIV_LIKELY(err == DB_SUCCESS)); + else if (error == DB_LOCK_WAIT_TIMEOUT) + { + ib::warn() << "Lock wait timeout reading document. Retrying!"; + /* We need to reset the ZLib state. */ + inited= false; + deflateEnd(zip->zp); + fts_zip_init(zip); + optim->trx->error_state = DB_SUCCESS; + /* Retry the same auxiliary table again */ + goto retry; + } + else ib::error() << "(" << err << ") while reading document."; + } + aux_table->release(); - return(error); + /* Check if max word to fetch is exceeded */ + if (zip->n_words >= n_words) goto func_exit; + } +func_exit: + if (err == DB_SUCCESS && zip->status == Z_OK && zip->n_words > 0) + { + /* All data should have been read. */ + ut_a(zip->zp->avail_in == 0); + fts_zip_deflate_end(zip); + } + else deflateEnd(zip->zp); + return err; } -/**********************************************************************//** -Callback function to fetch the doc id from the record. +/** Callback function to fetch the doc id from the record. @return always returns TRUE */ -static -ibool -fts_fetch_doc_ids( -/*==============*/ - void* row, /*!< in: sel_node_t* */ - void* user_arg) /*!< in: pointer to ib_vector_t */ +static bool fts_fetch_doc_ids(dict_index_t *index, const rec_t *rec, + const rec_offs *offsets, void *user_arg) { - que_node_t* exp; - int i = 0; - sel_node_t* sel_node = static_cast(row); - fts_doc_ids_t* fts_doc_ids = static_cast(user_arg); - doc_id_t* update = static_cast( - ib_vector_push(fts_doc_ids->doc_ids, NULL)); - - for (exp = sel_node->select_list; - exp; - exp = que_node_get_next(exp), ++i) { - - dfield_t* dfield = que_node_get_val(exp); - void* data = dfield_get_data(dfield); - ulint len = dfield_get_len(dfield); - - ut_a(len != UNIV_SQL_NULL); - - /* Note: The column numbers below must match the SELECT. */ - switch (i) { - case 0: /* DOC_ID */ - *update = fts_read_doc_id( - static_cast(data)); - break; - - default: - ut_error; - } - } - - return(TRUE); + ut_ad(dict_index_is_clust(index)); + fts_doc_ids_t *fts_doc_ids= static_cast(user_arg); + doc_id_t *update= + static_cast(ib_vector_push(fts_doc_ids->doc_ids, NULL)); + ulint len; + const byte *data= rec_get_nth_field(rec, offsets, 0, &len); + *update= mach_read_from_8(data); + return true; } -/**********************************************************************//** -Read the rows from a FTS common auxiliary table. +/** Read the rows from a FTS common auxiliary table. +@param trx transaction +@param fts_table Fulltext table information +@param doc_ids For collecting doc_ids @return DB_SUCCESS or error code */ -dberr_t -fts_table_fetch_doc_ids( -/*====================*/ - trx_t* trx, /*!< in: transaction */ - fts_table_t* fts_table, /*!< in: table */ - fts_doc_ids_t* doc_ids) /*!< in: For collecting doc ids */ +dberr_t fts_table_fetch_doc_ids(trx_t *trx, fts_table_t *fts_table, + fts_doc_ids_t *doc_ids) { - dberr_t error; - que_t* graph; - pars_info_t* info = pars_info_create(); - ibool alloc_bk_trx = FALSE; - char table_name[MAX_FULL_NAME_LEN]; - - ut_a(fts_table->suffix != NULL); - ut_a(fts_table->type == FTS_COMMON_TABLE); - - if (!trx) { - trx = trx_create(); - alloc_bk_trx = TRUE; - } - - trx->op_info = "fetching FTS doc ids"; - - pars_info_bind_function(info, "my_func", fts_fetch_doc_ids, doc_ids); - - fts_get_table_name(fts_table, table_name); - pars_info_bind_id(info, "table_name", table_name); - - graph = fts_parse_sql( - fts_table, - info, - "DECLARE FUNCTION my_func;\n" - "DECLARE CURSOR c IS" - " SELECT doc_id FROM $table_name;\n" - "BEGIN\n" - "\n" - "OPEN c;\n" - "WHILE 1 = 1 LOOP\n" - " FETCH c INTO my_func();\n" - " IF c % NOTFOUND THEN\n" - " EXIT;\n" - " END IF;\n" - "END LOOP;\n" - "CLOSE c;"); - - error = fts_eval_sql(trx, graph); - fts_sql_commit(trx); - que_graph_free(graph); - - if (error == DB_SUCCESS) { - fts_doc_ids_sort(doc_ids->doc_ids); - } + dberr_t err= DB_SUCCESS; + bool alloc_trx= false; + if (!trx) + { + trx= trx_create(); + alloc_trx= true; + } - if (alloc_bk_trx) { - trx->free(); - } + FTSQueryRunner sqlRunner(trx); + dict_table_t *fts_aux= sqlRunner.open_table(fts_table, &err); + if (!fts_aux) goto func_exit; - return(error); + err= sqlRunner.prepare_for_read(fts_aux); + if (err == DB_SUCCESS) + { + ut_ad(UT_LIST_GET_LEN(fts_aux->indexes) == 1); + dict_index_t *clust_index= dict_table_get_first_index(fts_aux); + err= sqlRunner.record_executor(clust_index, READ, MATCH_ALL, + PAGE_CUR_GE, &fts_fetch_doc_ids, doc_ids); + } + if (err == DB_END_OF_INDEX) err= DB_SUCCESS; + if (alloc_trx) + { + if (err == DB_SUCCESS) fts_sql_commit(trx); + else fts_sql_rollback(trx); + } + if (err == DB_SUCCESS) fts_doc_ids_sort(doc_ids->doc_ids); +func_exit: + if (alloc_trx) { trx->free(); } + if (fts_aux) fts_aux->release(); + return err; } /**********************************************************************//** @@ -1423,87 +1246,70 @@ fts_optimize_word( return(nodes); } -/**********************************************************************//** -Update the FTS index table. This is a delete followed by an insert. +/** Update the FTS index table. This is a delete followed by an insert. +@param trx transaction to update the aux tables +@param fts_table table of FTS index +@param word word data to write +@param node node to write into aux table @return DB_SUCCESS or error code */ static MY_ATTRIBUTE((nonnull, warn_unused_result)) -dberr_t -fts_optimize_write_word( -/*====================*/ - trx_t* trx, /*!< in: transaction */ - fts_table_t* fts_table, /*!< in: table of FTS index */ - fts_string_t* word, /*!< in: word data to write */ - ib_vector_t* nodes) /*!< in: the nodes to write */ +dberr_t fts_optimize_write_word(trx_t *trx, fts_table_t *fts_table, + fts_string_t *word, ib_vector_t *nodes) { - ulint i; - pars_info_t* info; - que_t* graph; - ulint selected; - dberr_t error = DB_SUCCESS; - char table_name[MAX_FULL_NAME_LEN]; - - info = pars_info_create(); - - ut_ad(fts_table->charset); - - pars_info_bind_varchar_literal( - info, "word", word->f_str, word->f_len); - - selected = fts_select_index(fts_table->charset, - word->f_str, word->f_len); - - fts_table->suffix = fts_get_suffix(selected); - fts_get_table_name(fts_table, table_name); - pars_info_bind_id(info, "table_name", table_name); - - graph = fts_parse_sql( - fts_table, - info, - "BEGIN DELETE FROM $table_name WHERE word = :word;"); - - error = fts_eval_sql(trx, graph); - - if (UNIV_UNLIKELY(error != DB_SUCCESS)) { - ib::error() << "(" << error << ") during optimize," - " when deleting a word from the FTS index."; - } - - que_graph_free(graph); - graph = NULL; - - /* Even if the operation needs to be rolled back and redone, - we iterate over the nodes in order to free the ilist. */ - for (i = 0; i < ib_vector_size(nodes); ++i) { - - fts_node_t* node = (fts_node_t*) ib_vector_get(nodes, i); - - if (error == DB_SUCCESS) { - /* Skip empty node. */ - if (node->ilist == NULL) { - ut_ad(node->ilist_size == 0); - continue; - } - - error = fts_write_node( - trx, &graph, fts_table, word, node); + FTSQueryRunner sqlRunner(trx); + ulint selected = fts_select_index(fts_table->charset, word->f_str, + word->f_len); + fts_table->suffix = fts_get_suffix(selected); + dberr_t err= DB_SUCCESS; + dict_table_t *aux_table= sqlRunner.open_table(fts_table, &err); + if (!aux_table) goto free_node; + + err= sqlRunner.prepare_for_write(aux_table); + if (err == DB_SUCCESS) + { + ut_ad(UT_LIST_GET_LEN(aux_table->indexes) == 1); + dict_index_t *clust_index= dict_table_get_first_index(aux_table); + sqlRunner.build_tuple(clust_index, 1, 1); + sqlRunner.assign_aux_table_fields(word->f_str, word->f_len, nullptr); + /* DELETE FROM FTS_AUX WHERE WORD= :word */ + err= sqlRunner.record_executor(clust_index, REMOVE, MATCH_UNIQUE); + } - if (UNIV_UNLIKELY(error != DB_SUCCESS)) { - ib::error() << "(" << error << ")" - " during optimize, while adding a" - " word to the FTS index."; - } - } + if (err == DB_RECORD_NOT_FOUND || err == DB_END_OF_INDEX) + err= DB_SUCCESS; - ut_free(node->ilist); - node->ilist = NULL; - node->ilist_size = node->ilist_size_alloc = 0; - } + if (UNIV_UNLIKELY(err != DB_SUCCESS)) + ib::error() << "(" << err << ") during optimize," + " when deleting a word from the FTS index."; +free_node: + /* Even if the operation needs to be rolled back and redone, + we iterate over the nodes in order to free the ilist. */ + for (uint32_t i = 0; i < ib_vector_size(nodes); ++i) + { + fts_node_t* node = (fts_node_t*) ib_vector_get(nodes, i); + /* Skip empty node. */ + if (node->ilist == NULL) + { + ut_ad(node->ilist_size == 0); + continue; + } - if (graph != NULL) { - que_graph_free(graph); - } + if (err == DB_SUCCESS) + { + /* INSERT INTO FTS_AUX TABLE (word, first_doc_id, last_doc_id, + doc_count, ilist) */ + err = fts_write_node(aux_table, word, node, &sqlRunner); - return(error); + if (UNIV_UNLIKELY(err != DB_SUCCESS)) + ib::error() << "(" << err << ") during optimize, while adding a" + << " word to the FTS index."; + } + ut_free(node->ilist); + node->ilist = NULL; + node->ilist_size = node->ilist_size_alloc = 0; + } + if (aux_table) aux_table->release(); + return err; } /**********************************************************************//** @@ -1781,7 +1587,6 @@ fts_optimize_words( fts_string_t* word) /*!< in: the starting word to optimize */ { fts_fetch_t fetch; - que_t* graph = NULL; CHARSET_INFO* charset = optim->fts_index_table.charset; ut_a(!optim->done); @@ -1791,26 +1596,41 @@ fts_optimize_words( optim->trx, &optim->fts_common_table); const time_t start_time = time(NULL); - + std::map aux_tables; + trx_t *trx = optim->trx; + dict_table_t *aux_table= nullptr; + FTSQueryRunner sqlRunner(trx); /* Setup the callback to use for fetching the word ilist etc. */ fetch.read_arg = optim->words; - fetch.read_record = fts_optimize_index_fetch_node; + fetch.callback = &fts_optimize_index_fetch_node; + fetch.heap= sqlRunner.heap(); while (!optim->done) { - dberr_t error; - trx_t* trx = optim->trx; + dberr_t error = DB_SUCCESS; ulint selected; - ut_a(ib_vector_size(optim->words) == 0); - selected = fts_select_index(charset, word->f_str, word->f_len); - /* Read the index records to optimize. */ - fetch.total_memory = 0; - error = fts_index_fetch_nodes( - trx, &graph, &optim->fts_index_table, word, - &fetch); - ut_ad(fetch.total_memory < fts_result_cache_limit); + auto it = aux_tables.find(selected); + if (it == aux_tables.end()) { + optim->fts_index_table.suffix = + fts_get_suffix(selected); + aux_table = + sqlRunner.open_table(&optim->fts_index_table, + &error); + if (aux_table) { + aux_tables[selected] = aux_table; + } + } + else aux_table = it->second; + + if (error == DB_SUCCESS) { + /* Read the index records to optimize. */ + fetch.total_memory = 0; + error = fts_index_fetch_nodes_low(aux_table, word, + &fetch, &sqlRunner); + ut_ad(fetch.total_memory < fts_result_cache_limit); + } if (error == DB_SUCCESS) { /* There must be some nodes to read. */ @@ -1833,13 +1653,6 @@ fts_optimize_words( if (!optim->done) { if (!fts_zip_read_word(optim->zip, word)) { optim->done = TRUE; - } else if (selected - != fts_select_index( - charset, word->f_str, - word->f_len) - && graph) { - que_graph_free(graph); - graph = NULL; } } } else if (error == DB_LOCK_WAIT_TIMEOUT) { @@ -1855,9 +1668,9 @@ fts_optimize_words( optim->done = TRUE; /* Exit the loop. */ } } - - if (graph != NULL) { - que_graph_free(graph); + /* Release all aux tables */ + for (auto it : aux_tables) { + it.second->release(); } } @@ -2022,114 +1835,117 @@ fts_optimize_index( return(error); } -/**********************************************************************//** -Delete the document ids in the delete, and delete cache tables. +/** Delete the document ids in the delete, and delete cache tables. +@param optim Optimize instance for fulltext index @return DB_SUCCESS if all OK */ static MY_ATTRIBUTE((nonnull, warn_unused_result)) -dberr_t -fts_optimize_purge_deleted_doc_ids( -/*===============================*/ - fts_optimize_t* optim) /*!< in: optimize instance */ +dberr_t fts_optimize_purge_deleted_doc_ids(fts_optimize_t *optim) { - ulint i; - pars_info_t* info; - que_t* graph; - doc_id_t* update; - doc_id_t write_doc_id; - dberr_t error = DB_SUCCESS; - char deleted[MAX_FULL_NAME_LEN]; - char deleted_cache[MAX_FULL_NAME_LEN]; - - info = pars_info_create(); - - ut_a(ib_vector_size(optim->to_delete->doc_ids) > 0); - - update = static_cast( - ib_vector_get(optim->to_delete->doc_ids, 0)); - - /* Convert to "storage" byte order. */ - fts_write_doc_id((byte*) &write_doc_id, *update); - - /* This is required for the SQL parser to work. It must be able - to find the following variables. So we do it twice. */ - fts_bind_doc_id(info, "doc_id1", &write_doc_id); - fts_bind_doc_id(info, "doc_id2", &write_doc_id); - - /* Make sure the following two names are consistent with the name - used in the fts_delete_doc_ids_sql */ - optim->fts_common_table.suffix = fts_common_tables[3]; - fts_get_table_name(&optim->fts_common_table, deleted); - pars_info_bind_id(info, fts_common_tables[3], deleted); - - optim->fts_common_table.suffix = fts_common_tables[4]; - fts_get_table_name(&optim->fts_common_table, deleted_cache); - pars_info_bind_id(info, fts_common_tables[4], deleted_cache); - - graph = fts_parse_sql(NULL, info, fts_delete_doc_ids_sql); - - /* Delete the doc ids that were copied at the start. */ - for (i = 0; i < ib_vector_size(optim->to_delete->doc_ids); ++i) { - - update = static_cast(ib_vector_get( - optim->to_delete->doc_ids, i)); - - /* Convert to "storage" byte order. */ - fts_write_doc_id((byte*) &write_doc_id, *update); - - fts_bind_doc_id(info, "doc_id1", &write_doc_id); - - fts_bind_doc_id(info, "doc_id2", &write_doc_id); - - error = fts_eval_sql(optim->trx, graph); - - // FIXME: Check whether delete actually succeeded! - if (error != DB_SUCCESS) { - - fts_sql_rollback(optim->trx); - break; - } - } - - que_graph_free(graph); - - return(error); + dberr_t err= DB_SUCCESS; + doc_id_t *update= static_cast( + ib_vector_get(optim->to_delete->doc_ids, 0)); + doc_id_t write_doc_id; + /* Convert to "storage" byte order. */ + fts_write_doc_id((byte*) &write_doc_id, *update); + + FTSQueryRunner sqlRunner(optim->trx); + optim->fts_common_table.suffix= fts_common_tables[3]; + dict_table_t *fts_deleted_cache= nullptr; + dict_table_t *fts_deleted= + sqlRunner.open_table(&optim->fts_common_table, &err); + ut_ad(UT_LIST_GET_LEN(fts_deleted->indexes) == 1); + dict_index_t *deleted_cache_idx= nullptr; + + if (fts_deleted == nullptr) goto func_exit; + + err= sqlRunner.prepare_for_write(fts_deleted); + if (err) goto func_exit; + + optim->fts_common_table.suffix= fts_common_tables[4]; + fts_deleted_cache= sqlRunner.open_table(&optim->fts_common_table, &err); + if (!fts_deleted_cache) goto func_exit; + + err= sqlRunner.prepare_for_write(fts_deleted_cache); + if (err) goto func_exit; + + ut_ad(UT_LIST_GET_LEN(fts_deleted_cache->indexes) == 1); + deleted_cache_idx= dict_table_get_first_index(fts_deleted_cache); + sqlRunner.build_tuple(deleted_cache_idx, 1, 1); + + for (ulint i = 0; i < ib_vector_size(optim->to_delete->doc_ids); + ++i) + { + update= static_cast( + ib_vector_get(optim->to_delete->doc_ids, i)); + /* Convert to "storage" byte order. */ + fts_write_doc_id((byte*) &write_doc_id, *update); + sqlRunner.assign_common_table_fields(&write_doc_id); + + /* DELETE FROM FTS_DELETED_CACHE WHERE DOC_ID= :doc_id */ + err= sqlRunner.record_executor(deleted_cache_idx, REMOVE); + if (err == DB_RECORD_NOT_FOUND || err == DB_END_OF_INDEX) + err= DB_SUCCESS; + + if (err == DB_SUCCESS) + { + /* DELETE FROM FTS_DELETED WHERE DOC_ID= :doc_id */ + err= sqlRunner.record_executor(dict_table_get_first_index(fts_deleted), + REMOVE); + if (err == DB_RECORD_NOT_FOUND || err == DB_END_OF_INDEX) + err= DB_SUCCESS; + } + if (err) break; + } +func_exit: + if (fts_deleted_cache) fts_deleted_cache->release(); + if (fts_deleted) fts_deleted->release(); + return err; } -/**********************************************************************//** -Delete the document ids in the pending delete, and delete tables. +/** Delete the document ids in the pending delete, and delete tables. +@param optim optimize instance of fulltext index @return DB_SUCCESS if all OK */ static MY_ATTRIBUTE((nonnull, warn_unused_result)) -dberr_t -fts_optimize_purge_deleted_doc_id_snapshot( -/*=======================================*/ - fts_optimize_t* optim) /*!< in: optimize instance */ +dberr_t fts_optimize_purge_deleted_doc_id_snapshot(fts_optimize_t *optim) { - dberr_t error; - que_t* graph; - pars_info_t* info; - char being_deleted[MAX_FULL_NAME_LEN]; - char being_deleted_cache[MAX_FULL_NAME_LEN]; + FTSQueryRunner sqlRunner(optim->trx); + dberr_t err= DB_SUCCESS; + dict_table_t *fts_deleted_cache= nullptr; - info = pars_info_create(); + optim->fts_common_table.suffix = fts_common_tables[0]; + dict_table_t *fts_deleted= sqlRunner.open_table(&optim->fts_common_table, + &err); + if (!fts_deleted) goto func_exit; - /* Make sure the following two names are consistent with the name - used in the fts_end_delete_sql */ - optim->fts_common_table.suffix = fts_common_tables[0]; - fts_get_table_name(&optim->fts_common_table, being_deleted); - pars_info_bind_id(info, fts_common_tables[0], being_deleted); + err= sqlRunner.prepare_for_write(fts_deleted); + if (err) goto func_exit; - optim->fts_common_table.suffix = fts_common_tables[1]; - fts_get_table_name(&optim->fts_common_table, being_deleted_cache); - pars_info_bind_id(info, fts_common_tables[1], being_deleted_cache); + ut_ad(UT_LIST_GET_LEN(fts_deleted->indexes) == 1); + /* DELETE FROM FTS_DELETED */ + err= sqlRunner.record_executor(dict_table_get_first_index(fts_deleted), + REMOVE, MATCH_ALL); - /* Delete the doc ids that were copied to delete pending state at - the start of optimize. */ - graph = fts_parse_sql(NULL, info, fts_end_delete_sql); + if (err == DB_END_OF_INDEX || err == DB_SUCCESS) + { + optim->fts_common_table.suffix= fts_common_tables[4]; + fts_deleted_cache= sqlRunner.open_table(&optim->fts_common_table, &err); + if (!fts_deleted_cache) goto func_exit; - error = fts_eval_sql(optim->trx, graph); - que_graph_free(graph); + err= sqlRunner.prepare_for_write(fts_deleted_cache); + if (err) goto func_exit; - return(error); + ut_ad(UT_LIST_GET_LEN(fts_deleted_cache->indexes) == 1); + /* DELETE FROM FTS_DELETED_CACHE */ + err= sqlRunner.record_executor( + dict_table_get_first_index(fts_deleted_cache), REMOVE, MATCH_ALL); + + if (err == DB_END_OF_INDEX || err == DB_SUCCESS) + err= DB_SUCCESS; + } +func_exit: + if (fts_deleted_cache) fts_deleted_cache->release(); + if (fts_deleted) fts_deleted->release(); + return err; } /**********************************************************************//** @@ -2151,61 +1967,116 @@ fts_optimize_being_deleted_count( return(fts_get_rows_count(&fts_table)); } -/*********************************************************************//** -Copy the deleted doc ids that will be purged during this optimize run -to the being deleted FTS auxiliary tables. The transaction is committed -upon successfull copy and rolled back on DB_DUPLICATE_KEY error. +/* Read doc_id from the given record. +@param index DELETED or DELETED CACHE index +@param rec record belongs to DELETED or DELETED cache +@param offsets record offsets +@param user_arg vector of document ids +@return true always */ +static +bool fts_read_ulint(dict_index_t *index, const rec_t *rec, + const rec_offs* offsets, void *user_arg) +{ + ut_ad(dict_index_is_clust(index)); + std::vector *doc_ids= + static_cast*>(user_arg); + ulint len; + const byte *data= rec_get_nth_field(rec, offsets, 0, &len); + doc_id_t doc_id= mach_read_from_8(data); + doc_ids->push_back(doc_id); + return true; +} + +/** Copy the record from FROM to TO common table +@param from DELETED or DELETED_CACHE table +@param to BEING_DELETED or BEING_DELETED_CACHE table +@param sqlRunner executor for FTS internal query +@return error code or DB_SUCCESS */ +static +dberr_t fts_copy_doc_ids(dict_table_t *from, dict_table_t *to, + FTSQueryRunner *sqlRunner) +{ + std::vector doc_ids; + dberr_t error= sqlRunner->prepare_for_read(from); + if (error == DB_SUCCESS) + { + ut_ad(UT_LIST_GET_LEN(from->indexes) == 1); + dict_index_t *from_index= dict_table_get_first_index(from); + error= sqlRunner->record_executor(from_index, READ, MATCH_ALL, + PAGE_CUR_GE, &fts_read_ulint, &doc_ids); + } + if (error == DB_SUCCESS) + error= sqlRunner->prepare_for_write(to); + if (error == DB_SUCCESS) + { + dict_index_t *to_index= dict_table_get_first_index(to); + sqlRunner->build_tuple(to_index); + for (doc_id_t it : doc_ids) + { + sqlRunner->assign_common_table_fields(&it); + error= sqlRunner->write_record(to); + if (error) return error; + } + } + return error; +} + +/** Copy the deleted doc ids that will be purged during this +optimize run to the being deleted FTS auxiliary tables. The transaction +is committed upon successfull copy and rolled back on DB_DUPLICATE_KEY error. +@param optim optimize table instance @return DB_SUCCESS if all OK */ static MY_ATTRIBUTE((nonnull, warn_unused_result)) dberr_t -fts_optimize_create_deleted_doc_id_snapshot( -/*========================================*/ - fts_optimize_t* optim) /*!< in: optimize instance */ +fts_optimize_create_deleted_doc_id_snapshot(fts_optimize_t *optim) { - dberr_t error; - que_t* graph; - pars_info_t* info; - char being_deleted[MAX_FULL_NAME_LEN]; - char deleted[MAX_FULL_NAME_LEN]; - char being_deleted_cache[MAX_FULL_NAME_LEN]; - char deleted_cache[MAX_FULL_NAME_LEN]; - - info = pars_info_create(); - - /* Make sure the following four names are consistent with the name - used in the fts_init_delete_sql */ - optim->fts_common_table.suffix = fts_common_tables[0]; - fts_get_table_name(&optim->fts_common_table, being_deleted); - pars_info_bind_id(info, fts_common_tables[0], being_deleted); - - optim->fts_common_table.suffix = fts_common_tables[3]; - fts_get_table_name(&optim->fts_common_table, deleted); - pars_info_bind_id(info, fts_common_tables[3], deleted); - - optim->fts_common_table.suffix = fts_common_tables[1]; - fts_get_table_name(&optim->fts_common_table, being_deleted_cache); - pars_info_bind_id(info, fts_common_tables[1], being_deleted_cache); - - optim->fts_common_table.suffix = fts_common_tables[4]; - fts_get_table_name(&optim->fts_common_table, deleted_cache); - pars_info_bind_id(info, fts_common_tables[4], deleted_cache); - - /* Move doc_ids that are to be deleted to state being deleted. */ - graph = fts_parse_sql(NULL, info, fts_init_delete_sql); - - error = fts_eval_sql(optim->trx, graph); - - que_graph_free(graph); - - if (error != DB_SUCCESS) { - fts_sql_rollback(optim->trx); - } else { - fts_sql_commit(optim->trx); - } + dberr_t error= DB_SUCCESS; + FTSQueryRunner sqlRunner(optim->trx); + dict_table_t *deleted= nullptr; + dict_table_t *deleted_cache= nullptr; + dict_table_t *being_deleted= nullptr; + dict_table_t *being_deleted_cache= nullptr; + std::vector doc_ids; + dberr_t err= DB_SUCCESS; + /* Read all rows from DELETED table */ + optim->fts_common_table.suffix = fts_common_tables[3]; + deleted= sqlRunner.open_table(&optim->fts_common_table, &err); + if (deleted) + { + /* Write into BEING_DELETED table */ + optim->fts_common_table.suffix = fts_common_tables[0]; + being_deleted= sqlRunner.open_table(&optim->fts_common_table, &err); + if (being_deleted == nullptr) goto err_exit; + } + if (error == DB_SUCCESS) + error= fts_copy_doc_ids(deleted, being_deleted, &sqlRunner); - optim->del_list_regenerated = TRUE; + if (error) goto err_exit; - return(error); + /* Read all rows from DELETED_CACHE table */ + optim->fts_common_table.suffix = fts_common_tables[4]; + deleted_cache= sqlRunner.open_table(&optim->fts_common_table, &err); + if (error == DB_SUCCESS) + { + optim->fts_common_table.suffix = fts_common_tables[1]; + being_deleted_cache= sqlRunner.open_table(&optim->fts_common_table, &err); + } + /* Write these rows into BEING_DELETED_CACHE table */ + if (error == DB_SUCCESS) + error= fts_copy_doc_ids(deleted_cache, being_deleted_cache, + &sqlRunner); +err_exit: + if (error != DB_SUCCESS) + fts_sql_rollback(optim->trx); + else fts_sql_commit(optim->trx); + optim->del_list_regenerated = TRUE; + + if (deleted) deleted->release(); + if (being_deleted) being_deleted->release(); + if (deleted_cache) deleted_cache->release(); + if (being_deleted_cache) being_deleted_cache->release(); + + return error; } /*********************************************************************//** diff --git a/storage/innobase/fts/fts0que.cc b/storage/innobase/fts/fts0que.cc index 385685e44f8e7..a321ca96eac10 100644 --- a/storage/innobase/fts/fts0que.cc +++ b/storage/innobase/fts/fts0que.cc @@ -270,15 +270,15 @@ struct fts_word_freq_t { double idf; /*!< Inverse document frequency */ }; -/******************************************************************** -Callback function to fetch the rows in an FTS INDEX record. -@return always TRUE */ +/** Callback function to fetch the rows in FTS AUXILIARY table +@param index index where record belongs to +@param rec auxiliary table record +@param offsets record offsets +@param user_arg pointer to ib_vector_t +@return true */ static -ibool -fts_query_index_fetch_nodes( -/*========================*/ - void* row, /*!< in: sel_node_t* */ - void* user_arg); /*!< in: pointer to ib_vector_t */ +bool fts_query_index_fetch_nodes(dict_index_t *index, const rec_t *rec, + const rec_offs *offsets, void *user_arg); /******************************************************************** Read and filter nodes. @@ -291,7 +291,7 @@ fts_query_filter_doc_ids( const fts_string_t* word, /*!< in: the current word */ fts_word_freq_t* word_freq, /*!< in/out: word frequency */ const fts_node_t* node, /*!< in: current FTS node */ - void* data, /*!< in: doc id ilist */ + const void* data, /*!< in: doc id ilist */ ulint len, /*!< in: doc id ilist size */ ibool calc_doc_count);/*!< in: whether to remember doc count */ @@ -1124,7 +1124,6 @@ fts_query_difference( fts_fetch_t fetch; const ib_vector_t* nodes; const fts_index_cache_t*index_cache; - que_t* graph = NULL; fts_cache_t* cache = table->fts->cache; dberr_t error; @@ -1165,18 +1164,16 @@ fts_query_difference( /* Setup the callback args for filtering and consolidating the ilist. */ fetch.read_arg = query; - fetch.read_record = fts_query_index_fetch_nodes; + fetch.callback = &fts_query_index_fetch_nodes; error = fts_index_fetch_nodes( - trx, &graph, &query->fts_index_table, token, &fetch); + trx, &query->fts_index_table, token, &fetch); /* DB_FTS_EXCEED_RESULT_CACHE_LIMIT passed by 'query->error' */ ut_ad(!(query->error != DB_SUCCESS && error != DB_SUCCESS)); if (error != DB_SUCCESS) { query->error = error; } - - que_graph_free(graph); } /* The size can't increase. */ @@ -1225,7 +1222,6 @@ fts_query_intersect( fts_fetch_t fetch; const ib_vector_t* nodes; const fts_index_cache_t*index_cache; - que_t* graph = NULL; fts_cache_t* cache = table->fts->cache; dberr_t error; @@ -1299,10 +1295,10 @@ fts_query_intersect( /* Setup the callback args for filtering and consolidating the ilist. */ fetch.read_arg = query; - fetch.read_record = fts_query_index_fetch_nodes; + fetch.callback = &fts_query_index_fetch_nodes; error = fts_index_fetch_nodes( - trx, &graph, &query->fts_index_table, token, &fetch); + trx, &query->fts_index_table, token, &fetch); /* DB_FTS_EXCEED_RESULT_CACHE_LIMIT passed by 'query->error' */ ut_ad(!(query->error != DB_SUCCESS && error != DB_SUCCESS)); @@ -1310,8 +1306,6 @@ fts_query_intersect( query->error = error; } - que_graph_free(graph); - if (query->error == DB_SUCCESS) { /* Make the intesection (rb tree) the current doc id set and free the old set. */ @@ -1392,7 +1386,6 @@ fts_query_union( fts_fetch_t fetch; ulint n_doc_ids = 0; trx_t* trx = query->trx; - que_t* graph = NULL; dberr_t error; ut_a(query->oper == FTS_NONE || query->oper == FTS_DECR_RATING || @@ -1420,11 +1413,11 @@ fts_query_union( /* Setup the callback args for filtering and consolidating the ilist. */ fetch.read_arg = query; - fetch.read_record = fts_query_index_fetch_nodes; + fetch.callback = &fts_query_index_fetch_nodes; /* Read the nodes from disk. */ error = fts_index_fetch_nodes( - trx, &graph, &query->fts_index_table, token, &fetch); + trx, &query->fts_index_table, token, &fetch); /* DB_FTS_EXCEED_RESULT_CACHE_LIMIT passed by 'query->error' */ ut_ad(!(query->error != DB_SUCCESS && error != DB_SUCCESS)); @@ -1432,8 +1425,6 @@ fts_query_union( query->error = error; } - que_graph_free(graph); - if (query->error == DB_SUCCESS) { /* The size can't decrease. */ @@ -1950,476 +1941,83 @@ fts_query_match_phrase( return(phrase->found); } -/*****************************************************************//** -Callback function to fetch and search the document. +/** Callback function to fetch and search the document. +@param index clustered index +@param rec clustered index record +@param offsets offset to clustered index record +@param user_arg points to fts_fetch_doc_id and fetch->user_arg + points to fts_phrase_t @return whether the phrase is found */ static -ibool -fts_query_fetch_document( -/*=====================*/ - void* row, /*!< in: sel_node_t* */ - void* user_arg) /*!< in: fts_doc_t* */ -{ - - que_node_t* exp; - sel_node_t* node = static_cast(row); - fts_phrase_t* phrase = static_cast(user_arg); - ulint prev_len = 0; - ulint total_len = 0; - byte* document_text = NULL; - - exp = node->select_list; - - phrase->found = FALSE; - - /* For proximity search, we will need to get the whole document - from all fields, so first count the total length of the document - from all the fields */ - if (phrase->proximity_pos) { - while (exp) { - ulint field_len; - dfield_t* dfield = que_node_get_val(exp); - byte* data = static_cast( - dfield_get_data(dfield)); - - if (dfield_is_ext(dfield)) { - ulint local_len = dfield_get_len(dfield); - - local_len -= BTR_EXTERN_FIELD_REF_SIZE; - - field_len = mach_read_from_4( - data + local_len + BTR_EXTERN_LEN + 4); - } else { - field_len = dfield_get_len(dfield); - } - - if (field_len != UNIV_SQL_NULL) { - total_len += field_len + 1; - } - - exp = que_node_get_next(exp); - } - - document_text = static_cast(mem_heap_zalloc( - phrase->heap, total_len)); - - if (!document_text) { - return(FALSE); - } - } - - exp = node->select_list; - - while (exp) { - dfield_t* dfield = que_node_get_val(exp); - byte* data = static_cast( - dfield_get_data(dfield)); - ulint cur_len; - - if (dfield_is_ext(dfield)) { - data = btr_copy_externally_stored_field( - &cur_len, data, phrase->zip_size, - dfield_get_len(dfield), phrase->heap); - } else { - cur_len = dfield_get_len(dfield); - } - - if (cur_len != UNIV_SQL_NULL && cur_len != 0) { - if (phrase->proximity_pos) { - ut_ad(prev_len + cur_len <= total_len); - memcpy(document_text + prev_len, data, cur_len); - } else { - /* For phrase search */ - phrase->found = - fts_query_match_phrase( - phrase, - static_cast(data), - cur_len, prev_len, - phrase->heap); - } - - /* Document positions are calculated from the beginning - of the first field, need to save the length for each - searched field to adjust the doc position when search - phrases. */ - prev_len += cur_len + 1; - } - - if (phrase->found) { - break; - } - - exp = que_node_get_next(exp); - } - - if (phrase->proximity_pos) { - ut_ad(prev_len <= total_len); - - phrase->found = fts_proximity_is_word_in_range( - phrase, document_text, total_len); - } - - return(phrase->found); -} - -#if 0 -/******************************************************************** -Callback function to check whether a record was found or not. */ -static -ibool -fts_query_select( -/*=============*/ - void* row, /*!< in: sel_node_t* */ - void* user_arg) /*!< in: fts_doc_t* */ -{ - int i; - que_node_t* exp; - sel_node_t* node = row; - fts_select_t* select = user_arg; - - ut_a(select->word_freq); - ut_a(select->word_freq->doc_freqs); - - exp = node->select_list; - - for (i = 0; exp && !select->found; ++i) { - dfield_t* dfield = que_node_get_val(exp); - void* data = dfield_get_data(dfield); - ulint len = dfield_get_len(dfield); - - switch (i) { - case 0: /* DOC_COUNT */ - if (len != UNIV_SQL_NULL && len != 0) { - - select->word_freq->doc_count += - mach_read_from_4(data); - } - break; - - case 1: /* ILIST */ - if (len != UNIV_SQL_NULL && len != 0) { - - fts_query_find_doc_id(select, data, len); - } - break; - - default: - ut_error; - } - - exp = que_node_get_next(exp); - } - - return(FALSE); -} - -/******************************************************************** -Read the rows from the FTS index, that match word and where the -doc id is between first and last doc id. -@return DB_SUCCESS if all go well else error code */ -static MY_ATTRIBUTE((nonnull, warn_unused_result)) -dberr_t -fts_query_find_term( -/*================*/ - fts_query_t* query, /*!< in: FTS query state */ - que_t** graph, /*!< in: prepared statement */ - const fts_string_t* word, /*!< in: the word to fetch */ - doc_id_t doc_id, /*!< in: doc id to match */ - ulint* min_pos,/*!< in/out: pos found must be - greater than this minimum value. */ - ibool* found) /*!< out: TRUE if found else FALSE */ -{ - pars_info_t* info; - dberr_t error; - fts_select_t select; - doc_id_t match_doc_id; - trx_t* trx = query->trx; - char table_name[MAX_FULL_NAME_LEN]; - - trx->op_info = "fetching FTS index matching nodes"; - - if (*graph) { - info = (*graph)->info; - } else { - ulint selected; - - info = pars_info_create(); - - selected = fts_select_index(*word->f_str); - query->fts_index_table.suffix = fts_get_suffix(selected); - - fts_get_table_name(&query->fts_index_table, table_name); - pars_info_bind_id(info, "index_table_name", table_name); - } - - select.found = FALSE; - select.doc_id = doc_id; - select.min_pos = *min_pos; - select.word_freq = fts_query_add_word_freq(query, word->f_str); - - pars_info_bind_function(info, "my_func", fts_query_select, &select); - pars_info_bind_varchar_literal(info, "word", word->f_str, word->f_len); - - /* Convert to "storage" byte order. */ - fts_write_doc_id((byte*) &match_doc_id, doc_id); - - fts_bind_doc_id(info, "min_doc_id", &match_doc_id); - - fts_bind_doc_id(info, "max_doc_id", &match_doc_id); - - if (!*graph) { - - *graph = fts_parse_sql( - &query->fts_index_table, - info, - "DECLARE FUNCTION my_func;\n" - "DECLARE CURSOR c IS" - " SELECT doc_count, ilist\n" - " FROM $index_table_name\n" - " WHERE word LIKE :word AND" - " first_doc_id <= :min_doc_id AND" - " last_doc_id >= :max_doc_id\n" - " ORDER BY first_doc_id;\n" - "BEGIN\n" - "\n" - "OPEN c;\n" - "WHILE 1 = 1 LOOP\n" - " FETCH c INTO my_func();\n" - " IF c % NOTFOUND THEN\n" - " EXIT;\n" - " END IF;\n" - "END LOOP;\n" - "CLOSE c;"); - } - - for (;;) { - error = fts_eval_sql(trx, *graph); - - if (error == DB_SUCCESS) { - - break; /* Exit the loop. */ - } else { - - if (error == DB_LOCK_WAIT_TIMEOUT) { - ib::warn() << "lock wait timeout reading FTS" - " index. Retrying!"; - - trx->error_state = DB_SUCCESS; - } else { - ib::error() << error - << " while reading FTS index."; - - break; /* Exit the loop. */ - } - } - } - - /* Value to return */ - *found = select.found; - - if (*found) { - *min_pos = select.min_pos; - } - - return(error); -} - -/******************************************************************** -Callback aggregator for int columns. */ -static -ibool -fts_query_sum( -/*==========*/ - /*!< out: always returns TRUE */ - void* row, /*!< in: sel_node_t* */ - void* user_arg) /*!< in: ulint* */ -{ - - que_node_t* exp; - sel_node_t* node = row; - ulint* total = user_arg; - - exp = node->select_list; - - while (exp) { - dfield_t* dfield = que_node_get_val(exp); - void* data = dfield_get_data(dfield); - ulint len = dfield_get_len(dfield); - - if (len != UNIV_SQL_NULL && len != 0) { - *total += mach_read_from_4(data); - } - - exp = que_node_get_next(exp); - } - - return(TRUE); -} - -/******************************************************************** -Calculate the total documents that contain a particular word (term). -@return DB_SUCCESS if all go well else error code */ -static MY_ATTRIBUTE((nonnull, warn_unused_result)) -dberr_t -fts_query_total_docs_containing_term( -/*=================================*/ - fts_query_t* query, /*!< in: FTS query state */ - const fts_string_t* word, /*!< in: the word to check */ - ulint* total) /*!< out: documents containing word */ -{ - pars_info_t* info; - dberr_t error; - que_t* graph; - ulint selected; - trx_t* trx = query->trx; - char table_name[MAX_FULL_NAME_LEN] - - trx->op_info = "fetching FTS index document count"; - - *total = 0; - - info = pars_info_create(); - - pars_info_bind_function(info, "my_func", fts_query_sum, total); - pars_info_bind_varchar_literal(info, "word", word->f_str, word->f_len); - - selected = fts_select_index(*word->f_str); - - query->fts_index_table.suffix = fts_get_suffix(selected); - - fts_get_table_name(&query->fts_index_table, table_name); - - pars_info_bind_id(info, "index_table_name", table_name); - - graph = fts_parse_sql( - &query->fts_index_table, - info, - "DECLARE FUNCTION my_func;\n" - "DECLARE CURSOR c IS" - " SELECT doc_count\n" - " FROM $index_table_name\n" - " WHERE word = :word" - " ORDER BY first_doc_id;\n" - "BEGIN\n" - "\n" - "OPEN c;\n" - "WHILE 1 = 1 LOOP\n" - " FETCH c INTO my_func();\n" - " IF c % NOTFOUND THEN\n" - " EXIT;\n" - " END IF;\n" - "END LOOP;\n" - "CLOSE c;"); - - for (;;) { - error = fts_eval_sql(trx, graph); - - if (error == DB_SUCCESS) { - - break; /* Exit the loop. */ - } else { - - if (error == DB_LOCK_WAIT_TIMEOUT) { - ib::warn() << "lock wait timeout reading FTS" - " index. Retrying!"; - - trx->error_state = DB_SUCCESS; - } else { - ib::error() << error - << " while reading FTS index."; - - break; /* Exit the loop. */ - } - } - } - - que_graph_free(graph); - - return(error); -} - -/******************************************************************** -Get the total number of words in a documents. -@return DB_SUCCESS if all go well else error code */ -static MY_ATTRIBUTE((nonnull, warn_unused_result)) -dberr_t -fts_query_terms_in_document( -/*========================*/ - fts_query_t* query, /*!< in: FTS query state */ - doc_id_t doc_id, /*!< in: the word to check */ - ulint* total) /*!< out: total words in document */ +bool fts_query_fetch_document(dict_index_t *index, const rec_t *rec, + const rec_offs *offsets, void *user_arg) { - pars_info_t* info; - dberr_t error; - que_t* graph; - doc_id_t read_doc_id; - trx_t* trx = query->trx; - char table_name[MAX_FULL_NAME_LEN]; - - trx->op_info = "fetching FTS document term count"; - - *total = 0; - - info = pars_info_create(); - - pars_info_bind_function(info, "my_func", fts_query_sum, total); - - /* Convert to "storage" byte order. */ - fts_write_doc_id((byte*) &read_doc_id, doc_id); - fts_bind_doc_id(info, "doc_id", &read_doc_id); - - query->fts_index_table.suffix = "DOC_ID"; - - fts_get_table_name(&query->fts_index_table, table_name); - - pars_info_bind_id(info, "index_table_name", table_name); - - graph = fts_parse_sql( - &query->fts_index_table, - info, - "DECLARE FUNCTION my_func;\n" - "DECLARE CURSOR c IS" - " SELECT count\n" - " FROM $index_table_name\n" - " WHERE doc_id = :doc_id" - " BEGIN\n" - "\n" - "OPEN c;\n" - "WHILE 1 = 1 LOOP\n" - " FETCH c INTO my_func();\n" - " IF c % NOTFOUND THEN\n" - " EXIT;\n" - " END IF;\n" - "END LOOP;\n" - "CLOSE c;"); - - for (;;) { - error = fts_eval_sql(trx, graph); - - if (error == DB_SUCCESS) { - - break; /* Exit the loop. */ - } else { - - if (error == DB_LOCK_WAIT_TIMEOUT) { - ib::warn() << "lock wait timeout reading FTS" - " doc id table. Retrying!"; - - trx->error_state = DB_SUCCESS; - } else { - ib::error() << error << " while reading FTS" - " doc id table."; - - break; /* Exit the loop. */ - } - } - } - - que_graph_free(graph); - - return(error); + ut_ad(dict_index_is_clust(index)); + fts_fetch_doc_id *fetch= static_cast(user_arg); + fts_phrase_t *phrase= static_cast(fetch->user_arg); + ulint prev_len= 0; + ulint total_len= 0; + byte *document_text= NULL; + ulint len= 0; + phrase->found = FALSE; + const byte *data= nullptr; + /* For proximity search, we will need to get the whole document + from all fields, so first count the total length of the document + from all the fields */ + if (phrase->proximity_pos) + { + for (uint32_t i= 0; i < fetch->field_to_fetch.size(); i++) + { + ulint field_no= fetch->field_to_fetch[i]; + if (rec_offs_nth_extern(offsets, field_no)) + { + ulint local_len; + const byte *field= rec_get_nth_field(rec, offsets, field_no, &local_len); + local_len-= BTR_EXTERN_FIELD_REF_SIZE; + len= mach_read_from_4(field + local_len + BTR_EXTERN_LEN + 4); + } + else rec_get_nth_field_offs(offsets, field_no, &len); + if (len != UNIV_SQL_NULL) total_len+= len + 1; + } + + document_text= static_cast(mem_heap_zalloc(phrase->heap, total_len)); + if (!document_text) return false; + } + + for (uint32_t i= 0; i < fetch->field_to_fetch.size(); i++) + { + ulint field_no= fetch->field_to_fetch[i]; + if (rec_offs_nth_extern(offsets, field_no)) + data= btr_rec_copy_externally_stored_field(rec, offsets, 0, field_no, + &len, phrase->heap); + else data = rec_get_nth_field(rec, offsets, field_no, &len); + if (len != UNIV_SQL_NULL && len != 0) + { + if (phrase->proximity_pos) + { + ut_ad(prev_len + len <= total_len); + memcpy(document_text + prev_len, data, len); + } + else + /* For phrase search */ + phrase->found = fts_query_match_phrase(phrase, + const_cast(data), + len, prev_len, phrase->heap); + /* Document positions are calculated from the beginning + of the first field, need to save the length for each + searched field to adjust the doc position when search phrases. */ + prev_len+= len + 1; + } + if (phrase->found) break; + } + if (phrase->proximity_pos) + { + ut_ad(prev_len <= total_len); + phrase->found= fts_proximity_is_word_in_range(phrase, document_text, + total_len); + } + return phrase->found; } -#endif /*****************************************************************//** Retrieve the document and match the phrase tokens. @@ -2504,12 +2102,6 @@ fts_query_is_in_proximity_range( " phase of proximity search"; } - /* Free the prepared statement. */ - if (get_doc.get_document_graph) { - que_graph_free(get_doc.get_document_graph); - get_doc.get_document_graph = NULL; - } - mem_heap_free(phrase.heap); return(err == DB_SUCCESS && phrase.found); @@ -2594,12 +2186,6 @@ fts_query_search_phrase( } func_exit: - /* Free the prepared statement. */ - if (get_doc.get_document_graph) { - que_graph_free(get_doc.get_document_graph); - get_doc.get_document_graph = NULL; - } - return(query->error); } @@ -2751,7 +2337,6 @@ fts_query_phrase_search( fts_fetch_t fetch; trx_t* trx = query->trx; fts_ast_oper_t oper = query->oper; - que_t* graph = NULL; ulint i; dberr_t error; @@ -2789,7 +2374,7 @@ fts_query_phrase_search( /* Setup the callback args for filtering and consolidating the ilist. */ fetch.read_arg = query; - fetch.read_record = fts_query_index_fetch_nodes; + fetch.callback = fts_query_index_fetch_nodes; for (i = 0; i < num_token; i++) { /* Search for the first word from the phrase. */ @@ -2802,7 +2387,7 @@ fts_query_phrase_search( } error = fts_index_fetch_nodes( - trx, &graph, &query->fts_index_table, + trx, &query->fts_index_table, token, &fetch); /* DB_FTS_EXCEED_RESULT_CACHE_LIMIT passed by 'query->error' */ @@ -2811,9 +2396,6 @@ fts_query_phrase_search( query->error = error; } - que_graph_free(graph); - graph = NULL; - fts_query_cache(query, token); if (!(query->flags & FTS_PHRASE) @@ -3224,11 +2806,11 @@ fts_query_filter_doc_ids( const fts_string_t* word, /*!< in: the current word */ fts_word_freq_t* word_freq, /*!< in/out: word frequency */ const fts_node_t* node, /*!< in: current FTS node */ - void* data, /*!< in: doc id ilist */ + const void* data, /*!< in: doc id ilist */ ulint len, /*!< in: doc id ilist size */ ibool calc_doc_count) /*!< in: whether to remember doc count */ { - const byte* ptr = static_cast(data); + const byte* ptr = static_cast(data); doc_id_t doc_id = 0; ulint decoded = 0; ib_rbt_t* doc_freqs = word_freq->doc_freqs; @@ -3332,154 +2914,125 @@ fts_query_filter_doc_ids( } } -/*****************************************************************//** -Read the FTS INDEX row. -@return DB_SUCCESS if all go well. */ -static -dberr_t -fts_query_read_node( -/*================*/ - fts_query_t* query, /*!< in: query instance */ - const fts_string_t* word, /*!< in: current word */ - que_node_t* exp) /*!< in: query graph node */ +/** Read the content of the fts auxiliary record +@param index auxiliary table index +@param rec auxiliary table record +@param offsets record offsets +@param query fulltext query instance +@param word current word +@param heap Heap to fetch external field data +@return error code or DB_SUCCESS */ +static dberr_t fts_query_read_node(dict_index_t *index, const rec_t *rec, + const rec_offs *offsets, fts_query_t *query, + const fts_string_t *word, mem_heap_t *heap) { - int i; - int ret; - fts_node_t node; - ib_rbt_bound_t parent; - fts_word_freq_t* word_freq; - ibool skip = FALSE; - fts_string_t term; - byte buf[FTS_MAX_WORD_LEN + 1]; - dberr_t error = DB_SUCCESS; - - ut_a(query->cur_node->type == FTS_AST_TERM - || query->cur_node->type == FTS_AST_TEXT - || query->cur_node->type == FTS_AST_PARSER_PHRASE_LIST); - - memset(&node, 0, sizeof(node)); - term.f_str = buf; - - /* Need to consider the wildcard search case, the word frequency - is created on the search string not the actual word. So we need - to assign the frequency on search string behalf. */ - if (query->cur_node->type == FTS_AST_TERM - && query->cur_node->term.wildcard) { - - term.f_len = query->cur_node->term.ptr->len; - ut_ad(FTS_MAX_WORD_LEN >= term.f_len); - memcpy(term.f_str, query->cur_node->term.ptr->str, term.f_len); - } else { - term.f_len = word->f_len; - ut_ad(FTS_MAX_WORD_LEN >= word->f_len); - memcpy(term.f_str, word->f_str, word->f_len); - } - - /* Lookup the word in our rb tree, it must exist. */ - ret = rbt_search(query->word_freqs, &parent, &term); - - ut_a(ret == 0); - - word_freq = rbt_value(fts_word_freq_t, parent.last); - - /* Start from 1 since the first column has been read by the caller. - Also, we rely on the order of the columns projected, to filter - out ilists that are out of range and we always want to read - the doc_count irrespective of the suitablility of the row. */ - - for (i = 1; exp && !skip; exp = que_node_get_next(exp), ++i) { - - dfield_t* dfield = que_node_get_val(exp); - byte* data = static_cast( - dfield_get_data(dfield)); - ulint len = dfield_get_len(dfield); - - ut_a(len != UNIV_SQL_NULL); - - /* Note: The column numbers below must match the SELECT. */ - - switch (i) { - case 1: /* DOC_COUNT */ - word_freq->doc_count += mach_read_from_4(data); - break; - - case 2: /* FIRST_DOC_ID */ - node.first_doc_id = fts_read_doc_id(data); - - /* Skip nodes whose doc ids are out range. */ - if (query->oper == FTS_EXIST - && query->upper_doc_id > 0 - && node.first_doc_id > query->upper_doc_id) { - skip = TRUE; - } - break; - - case 3: /* LAST_DOC_ID */ - node.last_doc_id = fts_read_doc_id(data); - - /* Skip nodes whose doc ids are out range. */ - if (query->oper == FTS_EXIST - && query->lower_doc_id > 0 - && node.last_doc_id < query->lower_doc_id) { - skip = TRUE; - } - break; - - case 4: /* ILIST */ - - error = fts_query_filter_doc_ids( - query, &word_freq->word, word_freq, - &node, data, len, FALSE); - - break; - - default: - ut_error; - } - } - - if (!skip) { - /* Make sure all columns were read. */ - - ut_a(i == 5); - } - - return error; + fts_node_t node; + ib_rbt_bound_t parent; + fts_word_freq_t *word_freq; + bool skip = FALSE; + fts_string_t term; + byte buf[FTS_MAX_WORD_LEN + 1]; + dberr_t error = DB_SUCCESS; + + ut_a(query->cur_node->type == FTS_AST_TERM || + query->cur_node->type == FTS_AST_TEXT || + query->cur_node->type == FTS_AST_PARSER_PHRASE_LIST); + + memset(&node, 0, sizeof(node)); + term.f_str = buf; + + /* Need to consider the wildcard search case, the word frequency + is created on the search string not the actual word. So we need + to assign the frequency on search string behalf. */ + if (query->cur_node->type == FTS_AST_TERM && query->cur_node->term.wildcard) + { + term.f_len = query->cur_node->term.ptr->len; + ut_ad(FTS_MAX_WORD_LEN >= term.f_len); + memcpy(term.f_str, query->cur_node->term.ptr->str, term.f_len); + } + else + { + term.f_len = word->f_len; + ut_ad(FTS_MAX_WORD_LEN >= word->f_len); + memcpy(term.f_str, word->f_str, word->f_len); + } + + /* Lookup the word in our rb tree, it must exist. */ + int ret = rbt_search(query->word_freqs, &parent, &term); + ut_a(ret == 0); + + word_freq = rbt_value(fts_word_freq_t, parent.last); + const byte *data= nullptr; + ulint len; + /* Start from 1 since the first column has been read by the caller. + Also, we rely on the order of the columns projected, to filter + out ilists that are out of range and we always want to read + the doc_count irrespective of the suitablility of the row. */ + for (uint32_t i = 1; i < index->n_fields && !skip; ++i) + { + switch (i) { + case 1: + /* FIRST_DOC_ID */ + data= rec_get_nth_field(rec, offsets, 1, &len); + node.first_doc_id= fts_read_doc_id(data); + /* Skip nodes whose doc ids are out range. */ + if (query->oper == FTS_EXIST && query->upper_doc_id > 0 && + node.first_doc_id > query->upper_doc_id) + skip = true; + break; + case 4 : + /* LAST DOC ID */ + data= rec_get_nth_field(rec, offsets, i, &len); + node.last_doc_id= fts_read_doc_id(data); + /* Skip nodes whose doc ids are out range. */ + if (query->oper == FTS_EXIST && query->lower_doc_id > 0 && + node.last_doc_id < query->lower_doc_id) + skip= true; + break; + case 5: + /* DOC COUNT */ + data= rec_get_nth_field(rec, offsets, i, &len); + word_freq->doc_count+= mach_read_from_4(data); + break; + case 6: + /* ILIST */ + if (rec_offs_nth_extern(offsets, 6)) + data= btr_rec_copy_externally_stored_field(rec, offsets, 0, 6, &len, + heap); + else data = rec_get_nth_field(rec, offsets, 6, &len); + error= fts_query_filter_doc_ids(query, &word_freq->word, word_freq, + &node, data, len, FALSE); + break; + } + } + return error; } -/*****************************************************************//** -Callback function to fetch the rows in an FTS INDEX record. -@return always returns TRUE */ -static -ibool -fts_query_index_fetch_nodes( -/*========================*/ - void* row, /*!< in: sel_node_t* */ - void* user_arg) /*!< in: pointer to fts_fetch_t */ +static bool fts_query_index_fetch_nodes(dict_index_t *index, const rec_t *rec, + const rec_offs *offsets, void *user_arg) { - fts_string_t key; - sel_node_t* sel_node = static_cast(row); - fts_fetch_t* fetch = static_cast(user_arg); - fts_query_t* query = static_cast(fetch->read_arg); - que_node_t* exp = sel_node->select_list; - dfield_t* dfield = que_node_get_val(exp); - void* data = dfield_get_data(dfield); - ulint dfield_len = dfield_get_len(dfield); - - key.f_str = static_cast(data); - key.f_len = dfield_len; - - ut_a(dfield_len <= FTS_MAX_WORD_LEN); - - /* Note: we pass error out by 'query->error' */ - query->error = fts_query_read_node(query, &key, que_node_get_next(exp)); - - if (query->error != DB_SUCCESS) { - ut_ad(query->error == DB_FTS_EXCEED_RESULT_CACHE_LIMIT); - return(FALSE); - } else { - return(TRUE); - } + ut_ad(dict_index_is_clust(index)); + fts_fetch_t *fetch = static_cast(user_arg); + fts_query_t *query = static_cast(fetch->read_arg); + + fts_string_t key; + ulint len; + /* Fetched word */ + const byte *data= rec_get_nth_field(rec, offsets, 0, &len); + key.f_str= const_cast(data); + key.f_len= len; + + ut_a(len <= FTS_MAX_WORD_LEN); + + /* Note: we pass error out by 'query->error' */ + query->error = fts_query_read_node(index, rec, offsets, query, &key, + fetch->heap); + if (query->error != DB_SUCCESS) + { + ut_ad(query->error == DB_FTS_EXCEED_RESULT_CACHE_LIMIT); + return false; + } + return true; } /*****************************************************************//** diff --git a/storage/innobase/fts/fts0sql.cc b/storage/innobase/fts/fts0sql.cc deleted file mode 100644 index 1970f6f584feb..0000000000000 --- a/storage/innobase/fts/fts0sql.cc +++ /dev/null @@ -1,208 +0,0 @@ -/***************************************************************************** - -Copyright (c) 2007, 2016, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2019, 2021, MariaDB Corporation. - -This program is free software; you can redistribute it and/or modify it under -the terms of the GNU General Public License as published by the Free Software -Foundation; version 2 of the License. - -This program is distributed in the hope that it will be useful, but WITHOUT -ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along with -this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA - -*****************************************************************************/ - -/**************************************************//** -@file fts/fts0sql.cc -Full Text Search functionality. - -Created 2007-03-27 Sunny Bains -*******************************************************/ - -#include "que0que.h" -#include "trx0roll.h" -#include "pars0pars.h" -#include "dict0dict.h" -#include "fts0types.h" -#include "fts0priv.h" - -/** SQL statements for creating the ancillary FTS tables. */ - -/** Preamble to all SQL statements. */ -static const char* fts_sql_begin= - "PROCEDURE P() IS\n"; - -/** Postamble to non-committing SQL statements. */ -static const char* fts_sql_end= - "\n" - "END;\n"; - -/******************************************************************//** -Get the table id. -@return number of bytes written */ -int -fts_get_table_id( -/*=============*/ - const fts_table_t* - fts_table, /*!< in: FTS Auxiliary table */ - char* table_id) /*!< out: table id, must be at least - FTS_AUX_MIN_TABLE_ID_LENGTH bytes - long */ -{ - int len; - - ut_a(fts_table->table != NULL); - - switch (fts_table->type) { - case FTS_COMMON_TABLE: - len = fts_write_object_id(fts_table->table_id, table_id); - break; - - case FTS_INDEX_TABLE: - - len = fts_write_object_id(fts_table->table_id, table_id); - - table_id[len] = '_'; - ++len; - table_id += len; - - len += fts_write_object_id(fts_table->index_id, table_id); - break; - - default: - ut_error; - } - - ut_a(len >= 16); - ut_a(len < FTS_AUX_MIN_TABLE_ID_LENGTH); - - return(len); -} - -/** Construct the name of an internal FTS table for the given table. -@param[in] fts_table metadata on fulltext-indexed table -@param[out] table_name a name up to MAX_FULL_NAME_LEN -@param[in] dict_locked whether dict_sys.latch is being held */ -void fts_get_table_name(const fts_table_t* fts_table, char* table_name, - bool dict_locked) -{ - if (!dict_locked) { - dict_sys.freeze(SRW_LOCK_CALL); - } - ut_ad(dict_sys.frozen()); - /* Include the separator as well. */ - const size_t dbname_len = fts_table->table->name.dblen() + 1; - ut_ad(dbname_len > 1); - memcpy(table_name, fts_table->table->name.m_name, dbname_len); - if (!dict_locked) { - dict_sys.unfreeze(); - } - memcpy(table_name += dbname_len, "FTS_", 4); - table_name += 4; - table_name += fts_get_table_id(fts_table, table_name); - *table_name++ = '_'; - strcpy(table_name, fts_table->suffix); -} - -/******************************************************************//** -Parse an SQL string. -@return query graph */ -que_t* -fts_parse_sql( -/*==========*/ - fts_table_t* fts_table, /*!< in: FTS auxiliarry table info */ - pars_info_t* info, /*!< in: info struct, or NULL */ - const char* sql) /*!< in: SQL string to evaluate */ -{ - char* str; - que_t* graph; - ibool dict_locked; - - str = ut_str3cat(fts_sql_begin, sql, fts_sql_end); - - dict_locked = (fts_table && fts_table->table->fts - && fts_table->table->fts->dict_locked); - - if (!dict_locked) { - /* The InnoDB SQL parser is not re-entrant. */ - dict_sys.lock(SRW_LOCK_CALL); - } - - graph = pars_sql(info, str); - ut_a(graph); - - if (!dict_locked) { - dict_sys.unlock(); - } - - ut_free(str); - - return(graph); -} - -/******************************************************************//** -Evaluate an SQL query graph. -@return DB_SUCCESS or error code */ -dberr_t -fts_eval_sql( -/*=========*/ - trx_t* trx, /*!< in: transaction */ - que_t* graph) /*!< in: Query graph to evaluate */ -{ - que_thr_t* thr; - - graph->trx = trx; - - ut_a(thr = que_fork_start_command(graph)); - - que_run_threads(thr); - - return(trx->error_state); -} - -/******************************************************************//** -Construct the column specification part of the SQL string for selecting the -indexed FTS columns for the given table. Adds the necessary bound -ids to the given 'info' and returns the SQL string. Examples: - -One indexed column named "text": - - "$sel0", - info/ids: sel0 -> "text" - -Two indexed columns named "subject" and "content": - - "$sel0, $sel1", - info/ids: sel0 -> "subject", sel1 -> "content", -@return heap-allocated WHERE string */ -const char* -fts_get_select_columns_str( -/*=======================*/ - dict_index_t* index, /*!< in: index */ - pars_info_t* info, /*!< in/out: parser info */ - mem_heap_t* heap) /*!< in: memory heap */ -{ - ulint i; - const char* str = ""; - - for (i = 0; i < index->n_user_defined_cols; i++) { - char* sel_str; - - dict_field_t* field = dict_index_get_nth_field(index, i); - - sel_str = mem_heap_printf(heap, "sel%lu", (ulong) i); - - /* Set copy_name to TRUE since it's dynamic. */ - pars_info_bind_id(info, sel_str, field->name); - - str = mem_heap_printf( - heap, "%s%s$%s", str, (*str) ? ", " : "", sel_str); - } - - return(str); -} diff --git a/storage/innobase/handler/i_s.cc b/storage/innobase/handler/i_s.cc index 828e0ef9fd5d8..4c377c94ab504 100644 --- a/storage/innobase/handler/i_s.cc +++ b/storage/innobase/handler/i_s.cc @@ -2236,7 +2236,14 @@ i_s_fts_deleted_generic_fill( (being_deleted) ? "BEING_DELETED" : "DELETED", FTS_COMMON_TABLE, user_table); - fts_table_fetch_doc_ids(trx, &fts_table, deleted); + dberr_t err= fts_table_fetch_doc_ids(trx, &fts_table, deleted); + + if (err == DB_SUCCESS) { + trx_commit_for_mysql(trx); + } + else { + trx->rollback(); + } dict_table_close(user_table, thd, mdl_ticket); @@ -2657,100 +2664,86 @@ struct st_maria_plugin i_s_innodb_ft_index_cache = MariaDB_PLUGIN_MATURITY_STABLE }; -/*******************************************************************//** -Go through a FTS index auxiliary table, fetch its rows and fill +/** Go through a FTS index auxiliary table, fetch its rows and fill FTS word cache structure. +@param index fulltext index +@param words vector to hold fetched words +@param selected selected index +@param word words to fetch @return DB_SUCCESS on success, otherwise error code */ static dberr_t -i_s_fts_index_table_fill_selected( -/*==============================*/ - dict_index_t* index, /*!< in: FTS index */ - ib_vector_t* words, /*!< in/out: vector to hold - fetched words */ - ulint selected, /*!< in: selected FTS index */ - fts_string_t* word) /*!< in: word to select */ -{ - pars_info_t* info; - fts_table_t fts_table; - trx_t* trx; - que_t* graph; - dberr_t error; - fts_fetch_t fetch; - char table_name[MAX_FULL_NAME_LEN]; - - info = pars_info_create(); - - fetch.read_arg = words; - fetch.read_record = fts_optimize_index_fetch_node; - fetch.total_memory = 0; - - DBUG_EXECUTE_IF("fts_instrument_result_cache_limit", - fts_result_cache_limit = 8192; - ); - - trx = trx_create(); - - trx->op_info = "fetching FTS index nodes"; - - pars_info_bind_function(info, "my_func", fetch.read_record, &fetch); - pars_info_bind_varchar_literal(info, "word", word->f_str, word->f_len); - - FTS_INIT_INDEX_TABLE(&fts_table, fts_get_suffix(selected), - FTS_INDEX_TABLE, index); - fts_get_table_name(&fts_table, table_name); - pars_info_bind_id(info, "table_name", table_name); - - graph = fts_parse_sql( - &fts_table, info, - "DECLARE FUNCTION my_func;\n" - "DECLARE CURSOR c IS" - " SELECT word, doc_count, first_doc_id, last_doc_id," - " ilist\n" - " FROM $table_name WHERE word >= :word;\n" - "BEGIN\n" - "\n" - "OPEN c;\n" - "WHILE 1 = 1 LOOP\n" - " FETCH c INTO my_func();\n" - " IF c % NOTFOUND THEN\n" - " EXIT;\n" - " END IF;\n" - "END LOOP;\n" - "CLOSE c;"); - - for (;;) { - error = fts_eval_sql(trx, graph); - - if (UNIV_LIKELY(error == DB_SUCCESS)) { - fts_sql_commit(trx); - - break; - } else { - fts_sql_rollback(trx); - - if (error == DB_LOCK_WAIT_TIMEOUT) { - ib::warn() << "Lock wait timeout reading" - " FTS index. Retrying!"; - - trx->error_state = DB_SUCCESS; - } else { - ib::error() << "Error occurred while reading" - " FTS index: " << error; - break; - } - } - } - - que_graph_free(graph); +i_s_fts_index_table_fill_selected(dict_index_t *index, ib_vector_t *words, + ulint selected, fts_string_t *word) +{ + dberr_t err= DB_SUCCESS; + + DBUG_EXECUTE_IF("fts_instrument_result_cache_limit", + fts_result_cache_limit = 8192;); + + bool table_locked= false; + fts_fetch_t fetch; + fetch.read_arg= words; + fetch.callback= &fts_optimize_index_fetch_node; + fetch.total_memory = 0; + + trx_t *trx = trx_create(); + trx->op_info = "fetching FTS index nodes"; + fts_table_t fts_table; + FTS_INIT_INDEX_TABLE(&fts_table, fts_get_suffix(selected), + FTS_INDEX_TABLE, index); + FTSQueryRunner sqlRunner(trx); + dict_table_t *fts_aux= sqlRunner.open_table(&fts_table, &err); + fetch.heap= sqlRunner.heap(); + if (fts_aux == nullptr) goto func_exit; + + for (;;) + { + if (!table_locked) err= sqlRunner.prepare_for_read(fts_aux); - trx->free(); + ut_ad(UT_LIST_GET_LEN(fts_aux->indexes) == 1); + dict_index_t *aux_index= dict_table_get_first_index(fts_aux); + sqlRunner.build_tuple(aux_index, 1, 1); + sqlRunner.assign_aux_table_fields(word->f_str, word->f_len); + if (err == DB_SUCCESS) + { + table_locked= true; + /* Executes the following statement + SELECT word, doc_count, first_doc_id, last_doc_id, ilist + FROM $FTS_PREFIX_INDEX_[1-6] WHERE word >= :query_word + and collects the result set in fetch variable */ + sqlRunner.record_executor(aux_index, READ, MATCH_ALL, + PAGE_CUR_GE, + &fts_optimize_index_fetch_node, &fetch); + } - if (fetch.total_memory >= fts_result_cache_limit) { - error = DB_FTS_EXCEED_RESULT_CACHE_LIMIT; - } + if (UNIV_LIKELY(err == DB_SUCCESS)) + { + fts_sql_commit(trx); + break; + } + else + { + fts_sql_rollback(trx); + if (err == DB_LOCK_WAIT_TIMEOUT) + { + ib::warn() << "Lock wait timeout reading FTS index. Retrying!"; + trx->error_state = DB_SUCCESS; + } + else + { + ib::error() << "Error occurred while reading FTS index: " << err; + break; + } + } + } - return(error); + if (fts_aux) fts_aux->release(); +func_exit: + trx->free(); + if (fetch.total_memory >= fts_result_cache_limit) + err= DB_FTS_EXCEED_RESULT_CACHE_LIMIT; + return err; } /*******************************************************************//** diff --git a/storage/innobase/include/fts0opt.h b/storage/innobase/include/fts0opt.h index c527ad8e5281e..3867624f3f088 100644 --- a/storage/innobase/include/fts0opt.h +++ b/storage/innobase/include/fts0opt.h @@ -28,12 +28,12 @@ Created 2011-02-15 Jimmy Yang /** The FTS optimize thread's work queue. */ extern ib_wqueue_t* fts_optimize_wq; -/******************************************************************** -Callback function to fetch the rows in an FTS INDEX record. */ -ibool -fts_optimize_index_fetch_node( -/*==========================*/ - /* out: always returns non-NULL */ - void* row, /* in: sel_node_t* */ - void* user_arg); /* in: pointer to ib_vector_t */ +/** Callback function to fetch the rows in an FTS INDEX record. +@param index auxiliary table clustered index +@param rec auxiliary table record +@param offsets offset to the record +@param user_arg points to ib_vector_t +@retval false if result memory exceeds FTS result cache limit */ +bool fts_optimize_index_fetch_node(dict_index_t *index, const rec_t *rec, + const rec_offs *offsets, void *user_arg); #endif diff --git a/storage/innobase/include/fts0priv.h b/storage/innobase/include/fts0priv.h index 04faceb995e09..580028df1fbaf 100644 --- a/storage/innobase/include/fts0priv.h +++ b/storage/innobase/include/fts0priv.h @@ -112,26 +112,6 @@ component. /** Maximum length of an integer stored in the config table value column. */ #define FTS_MAX_INT_LEN 32 -/******************************************************************//** -Parse an SQL string. %s is replaced with the table's id. -@return query graph */ -que_t* -fts_parse_sql( -/*==========*/ - fts_table_t* fts_table, /*!< in: FTS aux table */ - pars_info_t* info, /*!< in: info struct, or NULL */ - const char* sql) /*!< in: SQL string to evaluate */ - MY_ATTRIBUTE((nonnull(3), malloc, warn_unused_result)); -/******************************************************************//** -Evaluate a parsed SQL statement -@return DB_SUCCESS or error code */ -dberr_t -fts_eval_sql( -/*=========*/ - trx_t* trx, /*!< in: transaction */ - que_t* graph) /*!< in: Parsed statement */ - MY_ATTRIBUTE((nonnull, warn_unused_result)); - /** Construct the name of an internal FTS table for the given table. @param[in] fts_table metadata on fulltext-indexed table @param[out] table_name a name up to MAX_FULL_NAME_LEN @@ -139,28 +119,6 @@ fts_eval_sql( void fts_get_table_name(const fts_table_t* fts_table, char* table_name, bool dict_locked = false) MY_ATTRIBUTE((nonnull)); -/******************************************************************//** -Construct the column specification part of the SQL string for selecting the -indexed FTS columns for the given table. Adds the necessary bound -ids to the given 'info' and returns the SQL string. Examples: - -One indexed column named "text": - - "$sel0", - info/ids: sel0 -> "text" - -Two indexed columns named "subject" and "content": - - "$sel0, $sel1", - info/ids: sel0 -> "subject", sel1 -> "content", -@return heap-allocated WHERE string */ -const char* -fts_get_select_columns_str( -/*=======================*/ - dict_index_t* index, /*!< in: FTS index */ - pars_info_t* info, /*!< in/out: parser info */ - mem_heap_t* heap) /*!< in: memory heap */ - MY_ATTRIBUTE((nonnull, warn_unused_result)); /** define for fts_doc_fetch_by_doc_id() "option" value, defines whether we want to get Doc whose ID is equal to or greater or smaller than supplied @@ -169,47 +127,52 @@ ID */ #define FTS_FETCH_DOC_BY_ID_LARGE 2 #define FTS_FETCH_DOC_BY_ID_SMALL 3 -/*************************************************************//** -Fetch document (= a single row's indexed text) with the given -document id. -@return: DB_SUCCESS if fetch is successful, else error */ -dberr_t -fts_doc_fetch_by_doc_id( -/*====================*/ - fts_get_doc_t* get_doc, /*!< in: state */ - doc_id_t doc_id, /*!< in: id of document to fetch */ - dict_index_t* index_to_use, /*!< in: caller supplied FTS index, - or NULL */ - ulint option, /*!< in: search option, if it is - greater than doc_id or equal */ - fts_sql_callback - callback, /*!< in: callback to read - records */ - void* arg) /*!< in: callback arg */ - MY_ATTRIBUTE((nonnull(6))); - -/*******************************************************************//** -Callback function for fetch that stores the text of an FTS document, +/** Structure to use it in fts_doc_fetch_by_doc_id() funtion. +This structure contains fields to fetch from clustered index record +and points to user_arg */ +struct fts_fetch_doc_id +{ + std::vector field_to_fetch; + void *user_arg; +}; + +/** Fetch document with the given document id. +@param get_doc state +@param doc_id document id to fetch +@param index_to_use index to be used for fetching fts information +@param option search option, if it is greater than doc_id or equal +@param callback callback function after fetching the fulltext indexed + column +@param arg callback argument +@return DB_SUCCESS if OK else error */ +dberr_t fts_doc_fetch_by_doc_id(fts_get_doc_t *get_doc, doc_id_t doc_id, + dict_index_t *index_to_use, ulint option, + fts_sql_callback *callback, void *arg) + MY_ATTRIBUTE((nonnull(6))); + +/** Callback function for fetch that stores the text of an FTS document, converting each column to UTF-16. -@return always FALSE */ -ibool -fts_query_expansion_fetch_doc( -/*==========================*/ - void* row, /*!< in: sel_node_t* */ - void* user_arg) /*!< in: fts_doc_t* */ - MY_ATTRIBUTE((nonnull)); -/******************************************************************** -Write out a single word's data as new entry/entries in the INDEX table. +@param index clustered index +@param rec clustered index record +@param offsets offsets for the record +@param user_arg points to fts_fetch_doc_id since it has field information + to fetch and user_arg which points to fts_doc_t +@return always true */ +bool fts_query_expansion_fetch_doc(dict_index_t *index, const rec_t *rec, + const rec_offs *offsets, void *user_arg); + +class FTSQueryRunner; + +/** Write out a single word's data as new entry/entries in the INDEX table. +@param fts_table FTS auxiliary table information +@param word word in UTF-8 +@param node node columns +@param runner Runs the query in FTS internal SYSTEM TABLES @return DB_SUCCESS if all OK. */ dberr_t -fts_write_node( -/*===========*/ - trx_t* trx, /*!< in: transaction */ - que_t** graph, /*!< in: query graph */ - fts_table_t* fts_table, /*!< in: the FTS aux index */ - fts_string_t* word, /*!< in: word in UTF-8 */ - fts_node_t* node) /*!< in: node columns */ - MY_ATTRIBUTE((nonnull, warn_unused_result)); +fts_write_node(dict_table_t *table, fts_string_t *word, + fts_node_t* node, FTSQueryRunner *sqlRunner) + MY_ATTRIBUTE((nonnull, warn_unused_result)); /** Check if a fts token is a stopword or less than fts_min_token_size or greater than fts_max_token_size. @@ -258,19 +221,26 @@ fts_word_free( /*==========*/ fts_word_t* word) /*!< in: instance to free.*/ MY_ATTRIBUTE((nonnull)); -/******************************************************************//** -Read the rows from the FTS inde + +/** Reads the rows from the given aux table. +@param aux_table auxiliary table +@param word word to fetch +@param fetch store the rows value +@param sqlRunner Query executor @return DB_SUCCESS or error code */ -dberr_t -fts_index_fetch_nodes( -/*==================*/ - trx_t* trx, /*!< in: transaction */ - que_t** graph, /*!< in: prepared statement */ - fts_table_t* fts_table, /*!< in: FTS aux table */ - const fts_string_t* - word, /*!< in: the word to fetch */ - fts_fetch_t* fetch) /*!< in: fetch callback.*/ - MY_ATTRIBUTE((nonnull)); +dberr_t fts_index_fetch_nodes_low( + dict_table_t *aux_table, const fts_string_t *word, fts_fetch_t *fetch, + FTSQueryRunner *sqlRunner); + +/** Reads the rows from fts auxiliary table +@param trx transaction +@param fts_table fulltext auxiliary table +@param word word to fetch +@param fetch stores the rows value and calculates memory needed +@return error code or DB_SUCCESS */ +dberr_t fts_index_fetch_nodes(trx_t *trx, fts_table_t *fts_table, + const fts_string_t *word, fts_fetch_t *fetch); + #define fts_sql_commit(trx) trx_commit_for_mysql(trx) #define fts_sql_rollback(trx) (trx)->rollback() /******************************************************************//** @@ -459,6 +429,178 @@ fts_config_create_index_param_name( const dict_index_t* index) /*!< in: index for config */ MY_ATTRIBUTE((nonnull, malloc, warn_unused_result)); +/** Callback function for fetching the config value. */ +bool read_fts_config(dict_index_t *index, const rec_t *rec, + const rec_offs *offsets, void *user_arg); + +/** Operation on FTS internal tables */ +enum fts_operation +{ + INSERT, + READ, + SELECT_UPDATE, + REMOVE +}; + +/** Match mode for fulltext tables */ +enum fts_match_key +{ + /** Searching single unique records */ + MATCH_UNIQUE, + /** Searching single key record in multiple key index */ + MATCH_PREFIX, + /** Searching for pattern like records */ + MATCH_PATTERN, + /** Traverse all records on the table */ + MATCH_ALL +}; + +/** This class does insert, delete, search in all FTS internal tables.*/ +class FTSQueryRunner +{ +private: + /** Query thread */ + que_thr_t *m_thr= nullptr; + /** Transaction to do the operation on FTS interal table */ + trx_t *m_trx= nullptr; + /** Tuple to do DML operation on FTS internal table */ + dtuple_t *m_tuple= nullptr; + /** Update build vector */ + upd_t *m_update= nullptr; + /** system buffer to create DB_ROLL_PTR and DB_TRX_ID */ + byte *m_sys_buf= nullptr; + /** Persistent cursor for search operation */ + btr_pcur_t *m_pcur= nullptr; + /** Persistent cursor for clustered index */ + btr_pcur_t *m_clust_pcur= nullptr; + /** Clustered index reference in case of lookup */ + dtuple_t *m_clust_ref= nullptr; + /** lock type for the operation */ + lock_mode m_lock_type; + /** Heap for old version record */ + mem_heap_t *m_old_vers_heap= nullptr; + /** Heap to allocate query thread */ + mem_heap_t *m_heap= nullptr; + +private: + /** Create a query thread for executing the query */ + void create_query_thread() noexcept; + + /** Handle the lock wait while doing any operation on FTS internal table + @param err error to be handled + @param table_lock Encountered the error during table lock + @return DB_SUCCESS in case of lock wait or error code */ + dberr_t handle_wait(dberr_t err, bool table_lock= false) noexcept; + + /** Lock the given record based on operation, reconstruct the + previous of the record based on transaction read view. + @param index record where index belongs to + @param rec record to be locked + @param out_rec record which is viewable by transaction + @param offsets offsets of the record + @param mtr mini-transaction + @param op Operation to be performed + @return error code or DB_SUCCESS */ + dberr_t lock_or_sees_rec(dict_index_t *index, const rec_t *rec, + const rec_t **out_rec, rec_offs **offsets, + mtr_t *mtr, fts_operation op) noexcept; + + /** Build clustered reference in case of clustered index lookup */ + void build_clust_ref(dict_index_t *index) noexcept; + +public: + /** Build a tuple based on the given index of the table */ + void build_tuple(dict_index_t *index, uint32_t n_fields= 0, + uint32_t n_uniq= 0) noexcept; + + /** Assign the FTS config fields for the tuple + @param name key for the field + @param value value for the field + @param value_len length of value string */ + void assign_config_fields(const char* name, const void *value= nullptr, + ulint value_len= 0) noexcept; + + /** Assign the FTS common table fields + @param doc_id doc_id to be assigned */ + void assign_common_table_fields(doc_id_t *doc_id) noexcept; + + /** Assign the auxiliary table fields + @param word word to be initialized + @param word_len length of the word + @param node information about word where it stored */ + void assign_aux_table_fields(const byte *word, ulint word_len, + const fts_node_t *node= nullptr) noexcept; + + /** Build update vector for the value field of fts internal config table + @param table fulltext config table + @param field_no value field number + @param new_value value to be updated */ + void build_update_config(dict_table_t *table, uint16_t field_no, + const fts_string_t *new_value) noexcept; + + /** Open the table based on fts_auxiliary table name + @param fts_table fts table internal name + @param err set error code in case of table is not open + @param dict_locked dictionary locked + @retval DB_TABLE_NOT_FOUND in case if table has been dropped + @retval DB_SUCCESS in case of success */ + dict_table_t *open_table(fts_table_t *fts_table, dberr_t *err, + bool dict_locked= false) noexcept; + + /** Prepare the table for write operation + @param table table to be prepared + @retval DB_SUCCESS In case of success + @retrun error code for failure */ + dberr_t prepare_for_write(dict_table_t *table) noexcept; + + /** Prepare the table for read operation + @param table table to be prepared + @retval DB_SUCCESS In case of success + @retrun error code for failure */ + dberr_t prepare_for_read(dict_table_t *table) noexcept; + + /** Insert the record in the given table. + @param table table where insert is going to happen + @return error code or DB_SUCCESS */ + dberr_t write_record(dict_table_t *table) noexcept; + + /** Iterate the record and execute the functionality depends + on the operation. Lock the record when operation is + a delete (or) replace statement + @param index Doing the operation on the given index + @param op Read (or) Write (or) Delete (or) select..for update + @param match_op Match mode for the given tuple + @param mode Page cursor mode + @param func function pointer for read operation + @param user_arg user argument for function pointer + @return DB_SUCCESS or error code */ + dberr_t record_executor(dict_index_t *index, fts_operation op, + fts_match_key match_op= MATCH_UNIQUE, + page_cur_mode_t mode= PAGE_CUR_GE, + fts_sql_callback *func= nullptr, + void *user_arg= nullptr) noexcept; + + /** Update the clustered index of the table based on m_update and m_pcur + @param table table to be updated + @param old_len old length of the old data + @param new_len new length of the new data + @return error code or DB_SUCCESS */ + dberr_t update_record(dict_table_t *table, uint16_t old_len, + uint16_t new_len) noexcept; + + /** Get the heap */ + mem_heap_t *heap() noexcept { return m_heap; } + + dtuple_t *tuple() noexcept { return m_tuple; } + + FTSQueryRunner(trx_t *trx): m_trx(trx) { m_heap= mem_heap_create(100); } + + ~FTSQueryRunner(); + +#ifdef UNIV_DEBUG + bool is_stopword_table= false; +#endif +}; #include "fts0priv.inl" #endif /* INNOBASE_FTS0PRIV_H */ diff --git a/storage/innobase/include/fts0types.h b/storage/innobase/include/fts0types.h index 694ef75b01148..bcf58a347e8de 100644 --- a/storage/innobase/include/fts0types.h +++ b/storage/innobase/include/fts0types.h @@ -38,7 +38,8 @@ struct fts_que_t; struct fts_node_t; /** Callbacks used within FTS. */ -typedef pars_user_func_cb_t fts_sql_callback; +typedef bool fts_sql_callback(dict_index_t *index, const rec_t *rec, + const rec_offs *offsets, void *user_arg); typedef void (*fts_filter)(void*, fts_node_t*, void*, ulint len); /** Statistics relevant to a particular document, used during retrieval. */ @@ -53,30 +54,23 @@ struct fts_get_doc_t { fts_index_cache_t* index_cache; /*!< The index cache instance */ - /*!< Parsed sql statement */ - que_t* get_document_graph; fts_cache_t* cache; /*!< The parent cache */ }; /** Since we can have multiple FTS indexes on a table, we keep a per index cache of words etc. */ -struct fts_index_cache_t { - dict_index_t* index; /*!< The FTS index instance */ - - ib_rbt_t* words; /*!< Nodes; indexed by fts_string_t*, - cells are fts_tokenizer_word_t*.*/ - - ib_vector_t* doc_stats; /*!< Array of the fts_doc_stats_t - contained in the memory buffer. - Must be in sorted order (ascending). - The ideal choice is an rb tree but - the rb tree imposes a space overhead - that we can do without */ - - que_t** ins_graph; /*!< Insert query graphs */ - - que_t** sel_graph; /*!< Select query graphs */ - CHARSET_INFO* charset; /*!< charset */ +struct fts_index_cache_t +{ + /** FTS index instance */ + dict_index_t *index; + /** Nodes; indexed by fts_string_t*, cells are fts_tokenizer_word_t*. */ + ib_rbt_t *words; + /** Array of fts_doc_stats_t contained in memory buffer. Must be in + ascending sorted order. Ideal choice is an rb tree but rb tree imposes + a space overhead that we can do without */ + ib_vector_t *doc_stats; + /** charset */ + CHARSET_INFO* charset; }; /** Stop word control infotmation. */ @@ -225,12 +219,12 @@ struct fts_word_t { /** Callback for reading and filtering nodes that are read from FTS index */ struct fts_fetch_t { - void* read_arg; /*!< Arg for the sql_callback */ - - fts_sql_callback - read_record; /*!< Callback for reading index - record */ - size_t total_memory; /*!< Total memory used */ + void* read_arg; /*!< Arg for the sql_callback */ + fts_sql_callback* callback; /*!< Callback function for + filtering nodes */ + size_t total_memory; /*!< Total memory used */ + mem_heap_t* heap; /*!< Heap to read data + for externally stored field */ }; /** For horizontally splitting an FTS auxiliary index */ diff --git a/storage/innobase/include/row0sel.h b/storage/innobase/include/row0sel.h index 35e3cbe66315c..861fd1ed858c9 100644 --- a/storage/innobase/include/row0sel.h +++ b/storage/innobase/include/row0sel.h @@ -454,3 +454,48 @@ row_sel_field_store_in_mysql_format_func( #endif /* UNIV_DEBUG */ const byte* data, /*!< in: data to store */ ulint len); /*!< in: length of the data */ + +/** Helper class to cache clust_rec and old_vers */ +class Row_sel_get_clust_rec_for_mysql +{ + const rec_t *cached_clust_rec; + rec_t *cached_old_vers; + lsn_t cached_lsn; + page_id_t cached_page_id; + +#ifdef UNIV_DEBUG + void check_eq(const dict_index_t *index, const rec_offs *offsets) const + { + rec_offs vers_offs[REC_OFFS_HEADER_SIZE + MAX_REF_PARTS]; + rec_offs_init(vers_offs); + mem_heap_t *heap= nullptr; + + ut_ad(rec_offs_validate(cached_clust_rec, index, offsets)); + ut_ad(index->first_user_field() <= rec_offs_n_fields(offsets)); + ut_ad(vers_offs == rec_get_offsets(cached_old_vers, index, vers_offs, + index->n_core_fields, + index->db_trx_id(), &heap)); + ut_ad(!heap); + for (auto n= index->db_trx_id(); n--; ) + { + const dict_col_t *col= dict_index_get_nth_col(index, n); + ulint len1, len2; + const byte *b1= rec_get_nth_field(cached_clust_rec, offsets, n, &len1); + const byte *b2= rec_get_nth_field(cached_old_vers, vers_offs, n, &len2); + ut_ad(!cmp_data(col->mtype, col->prtype, false, b1, len1, b2, len2)); + } + } +#endif + +public: + Row_sel_get_clust_rec_for_mysql() : + cached_clust_rec(NULL), cached_old_vers(NULL), cached_lsn(0), + cached_page_id(page_id_t(0,0)) {} + + dberr_t operator()(btr_pcur_t *clust_pcur, btr_pcur_t *pcur, + dtuple_t *clust_ref, lock_mode select_lock_type, + trx_t *trx, dict_index_t *sec_index, + const rec_t *rec, que_thr_t *thr, const rec_t **out_rec, + rec_offs **offsets, mem_heap_t **offset_heap, + mem_heap_t **old_vers_heap, dtuple_t **vrow, mtr_t *mtr); +}; diff --git a/storage/innobase/include/row0upd.h b/storage/innobase/include/row0upd.h index f60fc3595dcb6..c8feccfbfdbfd 100644 --- a/storage/innobase/include/row0upd.h +++ b/storage/innobase/include/row0upd.h @@ -553,6 +553,25 @@ struct upd_node_t{ #define UPD_NODE_NO_SIZE_CHANGE 2 /* no record field size will be changed in the update */ +/** Updates a clustered index record of a row when the ordering fields do +not change. +@param flags undo logging and locking +@param update update vector for the row +@param pcur persistent cursor placed on the clustered index + record which should be updated +@param cmpl_info UPD_NODE_NO_ORD_CHANGE or UPD_NODE_NO_SIZE_CHANGE +@param index clustered index +@param offsets rec_get_offsets() on pcur +@param offsets_heap memory heap for offsets +@param thr query thread +@param trx transaction +@param mtr mini-transaction +@return DB_SUCCESS if operation successfully completed, else error +code or DB_LOCK_WAIT */ +dberr_t row_upd_clust_rec_low(ulint flags, upd_t *update, btr_pcur_t *pcur, + ulint cmpl_info, dict_index_t *index, + rec_offs *offsets, mem_heap_t **offsets_heap, + que_thr_t *thr, trx_t *trx, mtr_t *mtr); #include "row0upd.inl" diff --git a/storage/innobase/row/row0sel.cc b/storage/innobase/row/row0sel.cc index 10df64b2abcd5..3b00ff4678ed0 100644 --- a/storage/innobase/row/row0sel.cc +++ b/storage/innobase/row/row0sel.cc @@ -3296,49 +3296,6 @@ row_sel_build_prev_vers_for_mysql( prebuilt->old_vers_heap, old_vers, vrow); } -/** Helper class to cache clust_rec and old_vers */ -class Row_sel_get_clust_rec_for_mysql -{ - const rec_t *cached_clust_rec; - rec_t *cached_old_vers; - lsn_t cached_lsn; - page_id_t cached_page_id; - -#ifdef UNIV_DEBUG - void check_eq(const dict_index_t *index, const rec_offs *offsets) const - { - rec_offs vers_offs[REC_OFFS_HEADER_SIZE + MAX_REF_PARTS]; - rec_offs_init(vers_offs); - mem_heap_t *heap= nullptr; - - ut_ad(rec_offs_validate(cached_clust_rec, index, offsets)); - ut_ad(index->first_user_field() <= rec_offs_n_fields(offsets)); - ut_ad(vers_offs == rec_get_offsets(cached_old_vers, index, vers_offs, - index->n_core_fields, - index->db_trx_id(), &heap)); - ut_ad(!heap); - for (auto n= index->db_trx_id(); n--; ) - { - const dict_col_t *col= dict_index_get_nth_col(index, n); - ulint len1, len2; - const byte *b1= rec_get_nth_field(cached_clust_rec, offsets, n, &len1); - const byte *b2= rec_get_nth_field(cached_old_vers, vers_offs, n, &len2); - ut_ad(!cmp_data(col->mtype, col->prtype, false, b1, len1, b2, len2)); - } - } -#endif - -public: - Row_sel_get_clust_rec_for_mysql() : - cached_clust_rec(NULL), cached_old_vers(NULL), cached_lsn(0), - cached_page_id(page_id_t(0,0)) {} - - dberr_t operator()(row_prebuilt_t *prebuilt, dict_index_t *sec_index, - const rec_t *rec, que_thr_t *thr, const rec_t **out_rec, - rec_offs **offsets, mem_heap_t **offset_heap, - dtuple_t **vrow, mtr_t *mtr); -}; - /*********************************************************************//** Retrieves the clustered index record corresponding to a record in a non-clustered index. Does the necessary locking. Used in the MySQL @@ -3347,7 +3304,11 @@ interface. dberr_t Row_sel_get_clust_rec_for_mysql::operator()( /*============================*/ - row_prebuilt_t* prebuilt,/*!< in: prebuilt struct in the handle */ + btr_pcur_t* clust_pcur,/*!< in: clustered index persistent cursor */ + btr_pcur_t* pcur, /*!< in: Secondary index persistent cursor */ + dtuple_t* clust_ref, /*!< in: clustered index tuple */ + lock_mode select_lock_type, /*!< in: lock type */ + trx_t* trx, /*!< in: transaction */ dict_index_t* sec_index,/*!< in: secondary index where rec resides */ const rec_t* rec, /*!< in: record in a non-clustered index; if this is a locking read, then rec is not @@ -3364,6 +3325,7 @@ Row_sel_get_clust_rec_for_mysql::operator()( rec_get_offsets(out_rec, clust_index) */ mem_heap_t** offset_heap,/*!< in/out: memory heap from which the offsets are allocated */ + mem_heap_t** old_vers_heap,/*!< old version heap */ dtuple_t** vrow, /*!< out: virtual column to fill */ mtr_t* mtr) /*!< in: mtr used to get access to the non-clustered record; the same mtr is used to @@ -3371,36 +3333,33 @@ Row_sel_get_clust_rec_for_mysql::operator()( { dict_index_t* clust_index; rec_t* old_vers; - trx_t* trx; - prebuilt->clust_pcur->old_rec = nullptr; + clust_pcur->old_rec = nullptr; *out_rec = NULL; - trx = thr_get_trx(thr); - row_build_row_ref_in_tuple(prebuilt->clust_ref, rec, - sec_index, *offsets); + row_build_row_ref_in_tuple(clust_ref, rec, sec_index, *offsets); clust_index = dict_table_get_first_index(sec_index->table); - prebuilt->clust_pcur->btr_cur.page_cur.index = clust_index; + clust_pcur->btr_cur.page_cur.index = clust_index; - dberr_t err = btr_pcur_open_with_no_init(prebuilt->clust_ref, - PAGE_CUR_LE, BTR_SEARCH_LEAF, - prebuilt->clust_pcur, mtr); + dberr_t err = btr_pcur_open_with_no_init(clust_ref, PAGE_CUR_LE, + BTR_SEARCH_LEAF, + clust_pcur, mtr); if (UNIV_UNLIKELY(err != DB_SUCCESS)) { return err; } - const rec_t* clust_rec = btr_pcur_get_rec(prebuilt->clust_pcur); + const rec_t* clust_rec = btr_pcur_get_rec(clust_pcur); - prebuilt->clust_pcur->trx_if_known = trx; + clust_pcur->trx_if_known = trx; /* Note: only if the search ends up on a non-infimum record is the low_match value the real match to the search tuple */ if (!page_rec_is_user_rec(clust_rec) - || btr_pcur_get_low_match(prebuilt->clust_pcur) + || btr_pcur_get_low_match(clust_pcur) < dict_index_get_n_unique(clust_index)) { - btr_cur_t* btr_cur = btr_pcur_get_btr_cur(prebuilt->pcur); + btr_cur_t* btr_cur = btr_pcur_get_btr_cur(pcur); /* If this is a spatial index scan, and we are reading from a shadow buffer, the record could be already @@ -3411,7 +3370,7 @@ Row_sel_get_clust_rec_for_mysql::operator()( && (!(ulint(rec - btr_cur->rtr_info->matches->block->page.frame) >> srv_page_size_shift) - || rec != btr_pcur_get_rec(prebuilt->pcur))) { + || rec != btr_pcur_get_rec(pcur))) { #ifdef UNIV_DEBUG rtr_info_t* rtr_info = btr_cur->rtr_info; mysql_mutex_lock(&rtr_info->matches->rtr_match_mutex); @@ -3425,13 +3384,13 @@ Row_sel_get_clust_rec_for_mysql::operator()( if (rec_get_deleted_flag(rec, dict_table_is_comp(sec_index->table)) - && prebuilt->select_lock_type == LOCK_NONE) { + && select_lock_type == LOCK_NONE) { clust_rec = NULL; goto func_exit; } - if (rec != btr_pcur_get_rec(prebuilt->pcur)) { + if (rec != btr_pcur_get_rec(pcur)) { clust_rec = NULL; goto func_exit; } @@ -3440,8 +3399,8 @@ Row_sel_get_clust_rec_for_mysql::operator()( same as btr_pcur_get_block(prebuilt->pcur), and is it not unsafe to use RW_NO_LATCH here? */ buf_block_t* block = buf_page_get_gen( - btr_pcur_get_block(prebuilt->pcur)->page.id(), - btr_pcur_get_block(prebuilt->pcur)->zip_size(), + btr_pcur_get_block(pcur)->page.id(), + btr_pcur_get_block(pcur)->zip_size(), RW_NO_LATCH, NULL, BUF_GET, mtr, &err); ut_ad(block); // FIXME: avoid crash mem_heap_t* heap = mem_heap_create(256); @@ -3463,7 +3422,7 @@ Row_sel_get_clust_rec_for_mysql::operator()( #endif /* UNIV_DEBUG */ } else if (!rec_get_deleted_flag(rec, dict_table_is_comp(sec_index->table)) - || prebuilt->select_lock_type != LOCK_NONE) { + || select_lock_type != LOCK_NONE) { /* In a rare case it is possible that no clust rec is found for a delete-marked secondary index record: if row_undo_mod_clust() has already removed @@ -3492,15 +3451,15 @@ Row_sel_get_clust_rec_for_mysql::operator()( clust_index->n_core_fields, ULINT_UNDEFINED, offset_heap); - if (prebuilt->select_lock_type != LOCK_NONE) { + if (select_lock_type != LOCK_NONE) { /* Try to place a lock on the index record; we are searching the clust rec with a unique condition, hence we set a LOCK_REC_NOT_GAP type lock */ err = lock_clust_rec_read_check_and_lock( - 0, btr_pcur_get_block(prebuilt->clust_pcur), + 0, btr_pcur_get_block(clust_pcur), clust_rec, clust_index, *offsets, - prebuilt->select_lock_type, + select_lock_type, LOCK_REC_NOT_GAP, thr); @@ -3534,7 +3493,7 @@ Row_sel_get_clust_rec_for_mysql::operator()( break; case DB_SUCCESS_LOCKED_REC: const buf_page_t& bpage = btr_pcur_get_block( - prebuilt->clust_pcur)->page; + clust_pcur)->page; const lsn_t lsn = mach_read_from_8( bpage.frame + FIL_PAGE_LSN); @@ -3542,12 +3501,16 @@ Row_sel_get_clust_rec_for_mysql::operator()( if (lsn != cached_lsn || bpage.id() != cached_page_id || clust_rec != cached_clust_rec) { - /* The following call returns 'offsets' associated with - 'old_vers' */ - err = row_sel_build_prev_vers_for_mysql( - prebuilt, clust_index, - clust_rec, offsets, offset_heap, &old_vers, - vrow, mtr); + /* The following call returns 'offsets' + associated with 'old_vers' */ + if (*old_vers_heap) + mem_heap_empty(*old_vers_heap); + else *old_vers_heap = mem_heap_create(200); + + err= row_vers_build_for_consistent_read( + clust_rec, mtr, clust_index, offsets, + &trx->read_view, offset_heap, + *old_vers_heap, &old_vers, vrow); if (UNIV_UNLIKELY(err != DB_SUCCESS)) { return err; @@ -3620,11 +3583,11 @@ Row_sel_get_clust_rec_for_mysql::operator()( func_exit: *out_rec = clust_rec; - if (prebuilt->select_lock_type != LOCK_NONE) { + if (select_lock_type != LOCK_NONE) { /* We may use the cursor in update or in unlock_row(): store its position */ - btr_pcur_store_position(prebuilt->clust_pcur, mtr); + btr_pcur_store_position(clust_pcur, mtr); } return err; @@ -5528,11 +5491,13 @@ row_search_mvcc( 'clust_rec'. Note that 'clust_rec' can be an old version built for a consistent read. */ - err = row_sel_get_clust_rec_for_mysql(prebuilt, index, rec, - thr, &clust_rec, - &offsets, &heap, - need_vrow ? &vrow : NULL, - &mtr); + err = row_sel_get_clust_rec_for_mysql( + prebuilt->clust_pcur, prebuilt->pcur, + prebuilt->clust_ref, prebuilt->select_lock_type, + prebuilt->trx, index, rec, thr, &clust_rec, &offsets, + &heap, &prebuilt->old_vers_heap, + need_vrow ? &vrow : NULL, &mtr); + if (err == DB_LOCK_WAIT && prebuilt->skip_locked) { err = lock_trx_handle_wait(trx); } diff --git a/storage/innobase/row/row0upd.cc b/storage/innobase/row/row0upd.cc index 43621d910a559..bbc666fb4fbaa 100644 --- a/storage/innobase/row/row0upd.cc +++ b/storage/innobase/row/row0upd.cc @@ -2324,6 +2324,92 @@ row_upd_clust_rec_by_insert( return(err); } +dberr_t row_upd_clust_rec_low(ulint flags, upd_t *update, btr_pcur_t *pcur, + ulint cmpl_info, dict_index_t *index, + rec_offs *offsets, mem_heap_t **offsets_heap, + que_thr_t *thr, trx_t *trx, mtr_t *mtr) +{ + ut_ad(dict_index_is_clust(index)); + ut_ad(!trx->in_rollback); + ut_ad(!index->table->skip_alter_undo); + + mem_heap_t *heap= nullptr; + big_rec_t *big_rec= nullptr; + dberr_t err; + btr_cur_t *btr_cur = btr_pcur_get_btr_cur(pcur); + ut_ad(btr_cur_get_index(btr_cur) == index); + ut_ad(!rec_get_deleted_flag(btr_cur_get_rec(btr_cur), + dict_table_is_comp(index->table))); + ut_ad(rec_offs_validate(btr_cur_get_rec(btr_cur), index, offsets)); + + /* Try optimistic updating of the record, keeping changes within + the page; we do not check locks because we assume the x-lock on the + record to update */ + if (cmpl_info & UPD_NODE_NO_SIZE_CHANGE) + err = btr_cur_update_in_place( + flags | BTR_NO_LOCKING_FLAG, btr_cur, offsets, update, + cmpl_info, thr, trx->id, mtr); + else + err = btr_cur_optimistic_update(flags | BTR_NO_LOCKING_FLAG, btr_cur, + &offsets, offsets_heap, update, + cmpl_info, thr, trx->id, mtr); + + if (err == DB_SUCCESS) goto func_exit; + + if (buf_pool.running_out()) + { + err = DB_LOCK_TABLE_FULL; + goto func_exit; + } + + /* We may have to modify the tree structure: do a pessimistic descent + down the index tree */ + mtr->commit(); + mtr->start(); + + if (index->table->is_temporary()) + { + /* Disable locking, because temporary tables are never + shared between transactions or connections. */ + flags |= BTR_NO_LOCKING_FLAG; + mtr->set_log_mode(MTR_LOG_NO_REDO); + } + else index->set_modified(*mtr); + + /* NOTE: this transaction has an s-lock or x-lock on the record and + therefore other transactions cannot modify the record when we have no + latch on the page. In addition, we assume that other query threads of + the same transaction do not modify the record in the meantime. + Therefore we can assert that the restoration of the cursor succeeds. */ + + ut_a(pcur->restore_position(BTR_MODIFY_TREE, mtr) == + btr_pcur_t::SAME_ALL); + + ut_ad(!rec_get_deleted_flag(btr_pcur_get_rec(pcur), + dict_table_is_comp(index->table))); + + if (!heap) heap = mem_heap_create(1024); + err= btr_cur_pessimistic_update( + flags | BTR_NO_LOCKING_FLAG | BTR_KEEP_POS_FLAG, btr_cur, &offsets, + offsets_heap, heap, &big_rec, update, cmpl_info, thr, trx->id, mtr); + + if (big_rec) + { + ut_a(err == DB_SUCCESS); + DEBUG_SYNC_C("before_row_upd_extern"); + err = btr_store_big_rec_extern_fields(pcur, offsets, big_rec, mtr, + BTR_STORE_UPDATE); + DEBUG_SYNC_C("after_row_upd_extern"); + } + +func_exit: + if (heap) mem_heap_free(heap); + + if (big_rec) dtuple_big_rec_free(big_rec); + + return err; +} + /***********************************************************//** Updates a clustered index record of a row when the ordering fields do not change. @@ -2343,104 +2429,9 @@ row_upd_clust_rec( mtr_t* mtr) /*!< in,out: mini-transaction; may be committed and restarted here */ { - mem_heap_t* heap = NULL; - big_rec_t* big_rec = NULL; - btr_pcur_t* pcur; - btr_cur_t* btr_cur; - dberr_t err; - - ut_ad(dict_index_is_clust(index)); - ut_ad(!thr_get_trx(thr)->in_rollback); - ut_ad(!node->table->skip_alter_undo); - - pcur = node->pcur; - btr_cur = btr_pcur_get_btr_cur(pcur); - - ut_ad(btr_cur_get_index(btr_cur) == index); - ut_ad(!rec_get_deleted_flag(btr_cur_get_rec(btr_cur), - dict_table_is_comp(index->table))); - ut_ad(rec_offs_validate(btr_cur_get_rec(btr_cur), index, offsets)); - - /* Try optimistic updating of the record, keeping changes within - the page; we do not check locks because we assume the x-lock on the - record to update */ - - if (node->cmpl_info & UPD_NODE_NO_SIZE_CHANGE) { - err = btr_cur_update_in_place( - flags | BTR_NO_LOCKING_FLAG, btr_cur, - offsets, node->update, - node->cmpl_info, thr, thr_get_trx(thr)->id, mtr); - } else { - err = btr_cur_optimistic_update( - flags | BTR_NO_LOCKING_FLAG, btr_cur, - &offsets, offsets_heap, node->update, - node->cmpl_info, thr, thr_get_trx(thr)->id, mtr); - } - - if (err == DB_SUCCESS) { - goto func_exit; - } - - if (buf_pool.running_out()) { - err = DB_LOCK_TABLE_FULL; - goto func_exit; - } - - /* We may have to modify the tree structure: do a pessimistic descent - down the index tree */ - - mtr->commit(); - mtr->start(); - - if (index->table->is_temporary()) { - /* Disable locking, because temporary tables are never - shared between transactions or connections. */ - flags |= BTR_NO_LOCKING_FLAG; - mtr->set_log_mode(MTR_LOG_NO_REDO); - } else { - index->set_modified(*mtr); - } - - /* NOTE: this transaction has an s-lock or x-lock on the record and - therefore other transactions cannot modify the record when we have no - latch on the page. In addition, we assume that other query threads of - the same transaction do not modify the record in the meantime. - Therefore we can assert that the restoration of the cursor succeeds. */ - - ut_a(pcur->restore_position(BTR_MODIFY_TREE, mtr) == - btr_pcur_t::SAME_ALL); - - ut_ad(!rec_get_deleted_flag(btr_pcur_get_rec(pcur), - dict_table_is_comp(index->table))); - - if (!heap) { - heap = mem_heap_create(1024); - } - - err = btr_cur_pessimistic_update( - flags | BTR_NO_LOCKING_FLAG | BTR_KEEP_POS_FLAG, btr_cur, - &offsets, offsets_heap, heap, &big_rec, - node->update, node->cmpl_info, - thr, thr_get_trx(thr)->id, mtr); - if (big_rec) { - ut_a(err == DB_SUCCESS); - - DEBUG_SYNC_C("before_row_upd_extern"); - err = btr_store_big_rec_extern_fields( - pcur, offsets, big_rec, mtr, BTR_STORE_UPDATE); - DEBUG_SYNC_C("after_row_upd_extern"); - } - -func_exit: - if (heap) { - mem_heap_free(heap); - } - - if (big_rec) { - dtuple_big_rec_free(big_rec); - } - - return(err); + return row_upd_clust_rec_low(flags, node->update, node->pcur, + node->cmpl_info, index, offsets, offsets_heap, + thr, thr_get_trx(thr), mtr); } /***********************************************************//**