summaryrefslogtreecommitdiff
path: root/storage/xtradb/fts/fts0fts.cc
diff options
context:
space:
mode:
Diffstat (limited to 'storage/xtradb/fts/fts0fts.cc')
-rw-r--r--storage/xtradb/fts/fts0fts.cc7711
1 files changed, 0 insertions, 7711 deletions
diff --git a/storage/xtradb/fts/fts0fts.cc b/storage/xtradb/fts/fts0fts.cc
deleted file mode 100644
index e1a95bcd427..00000000000
--- a/storage/xtradb/fts/fts0fts.cc
+++ /dev/null
@@ -1,7711 +0,0 @@
-/*****************************************************************************
-
-Copyright (c) 2011, 2016, Oracle and/or its affiliates. All Rights Reserved.
-Copyright (c) 2016, MariaDB Corporation. All Rights reserved.
-
-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, Suite 500, Boston, MA 02110-1335 USA
-
-*****************************************************************************/
-
-/**************************************************//**
-@file fts/fts0fts.cc
-Full Text Search interface
-***********************************************************************/
-
-#include "trx0roll.h"
-#include "row0mysql.h"
-#include "row0upd.h"
-#include "dict0types.h"
-#include "row0sel.h"
-
-#include "fts0fts.h"
-#include "fts0priv.h"
-#include "fts0types.h"
-
-#include "fts0types.ic"
-#include "fts0vlc.ic"
-#include "dict0priv.h"
-#include "dict0stats.h"
-#include "btr0pcur.h"
-#include <vector>
-
-#include "ha_prototypes.h"
-
-#define FTS_MAX_ID_LEN 32
-
-/** Column name from the FTS config table */
-#define FTS_MAX_CACHE_SIZE_IN_MB "cache_size_in_mb"
-
-/** Verify if a aux table name is a obsolete table
-by looking up the key word in the obsolete table names */
-#define FTS_IS_OBSOLETE_AUX_TABLE(table_name) \
- (strstr((table_name), "DOC_ID") != NULL \
- || strstr((table_name), "ADDED") != NULL \
- || strstr((table_name), "STOPWORDS") != NULL)
-
-/** This is maximum FTS cache for each table and would be
-a configurable variable */
-UNIV_INTERN ulong fts_max_cache_size;
-
-/** Whether the total memory used for FTS cache is exhausted, and we will
-need a sync to free some memory */
-UNIV_INTERN bool fts_need_sync = false;
-
-/** Variable specifying the total memory allocated for FTS cache */
-UNIV_INTERN ulong fts_max_total_cache_size;
-
-/** This is FTS result cache limit for each query and would be
-a configurable variable */
-UNIV_INTERN ulong fts_result_cache_limit;
-
-/** Variable specifying the maximum FTS max token size */
-UNIV_INTERN ulong fts_max_token_size;
-
-/** Variable specifying the minimum FTS max token size */
-UNIV_INTERN ulong fts_min_token_size;
-
-
-// FIXME: testing
-ib_time_t elapsed_time = 0;
-ulint n_nodes = 0;
-
-/** Error condition reported by fts_utf8_decode() */
-const ulint UTF8_ERROR = 0xFFFFFFFF;
-
-#ifdef FTS_CACHE_SIZE_DEBUG
-/** The cache size permissible lower limit (1K) */
-static const ulint FTS_CACHE_SIZE_LOWER_LIMIT_IN_MB = 1;
-
-/** The cache size permissible upper limit (1G) */
-static const ulint FTS_CACHE_SIZE_UPPER_LIMIT_IN_MB = 1024;
-#endif /* FTS_CACHE_SIZE_DEBUG */
-
-/** Time to sleep after DEADLOCK error before retrying operation. */
-static const ulint FTS_DEADLOCK_RETRY_WAIT = 100000;
-
-#ifdef UNIV_PFS_RWLOCK
-UNIV_INTERN mysql_pfs_key_t fts_cache_rw_lock_key;
-UNIV_INTERN mysql_pfs_key_t fts_cache_init_rw_lock_key;
-#endif /* UNIV_PFS_RWLOCK */
-
-#ifdef UNIV_PFS_MUTEX
-UNIV_INTERN mysql_pfs_key_t fts_delete_mutex_key;
-UNIV_INTERN mysql_pfs_key_t fts_optimize_mutex_key;
-UNIV_INTERN mysql_pfs_key_t fts_bg_threads_mutex_key;
-UNIV_INTERN mysql_pfs_key_t fts_doc_id_mutex_key;
-UNIV_INTERN mysql_pfs_key_t fts_pll_tokenize_mutex_key;
-#endif /* UNIV_PFS_MUTEX */
-
-/** variable to record innodb_fts_internal_tbl_name for information
-schema table INNODB_FTS_INSERTED etc. */
-UNIV_INTERN char* fts_internal_tbl_name = NULL;
-UNIV_INTERN char* fts_internal_tbl_name2 = NULL;
-
-/** InnoDB default stopword list:
-There are different versions of stopwords, the stop words listed
-below comes from "Google Stopword" list. Reference:
-http://meta.wikimedia.org/wiki/Stop_word_list/google_stop_word_list.
-The final version of InnoDB default stopword list is still pending
-for decision */
-const char *fts_default_stopword[] =
-{
- "a",
- "about",
- "an",
- "are",
- "as",
- "at",
- "be",
- "by",
- "com",
- "de",
- "en",
- "for",
- "from",
- "how",
- "i",
- "in",
- "is",
- "it",
- "la",
- "of",
- "on",
- "or",
- "that",
- "the",
- "this",
- "to",
- "was",
- "what",
- "when",
- "where",
- "who",
- "will",
- "with",
- "und",
- "the",
- "www",
- NULL
-};
-
-/** For storing table info when checking for orphaned tables. */
-struct fts_aux_table_t {
- table_id_t id; /*!< Table id */
- table_id_t parent_id; /*!< Parent table id */
- table_id_t index_id; /*!< Table FT index id */
- char* name; /*!< Name of the table */
-};
-
-/** SQL statements for creating the ancillary common FTS tables. */
-static const char* fts_create_common_tables_sql = {
- "BEGIN\n"
- ""
- "CREATE TABLE \"%s_DELETED\" (\n"
- " doc_id BIGINT UNSIGNED\n"
- ") COMPACT;\n"
- "CREATE UNIQUE CLUSTERED INDEX IND ON \"%s_DELETED\"(doc_id);\n"
- ""
- "CREATE TABLE \"%s_DELETED_CACHE\" (\n"
- " doc_id BIGINT UNSIGNED\n"
- ") COMPACT;\n"
- "CREATE UNIQUE CLUSTERED INDEX IND "
- "ON \"%s_DELETED_CACHE\"(doc_id);\n"
- ""
- "CREATE TABLE \"%s_BEING_DELETED\" (\n"
- " doc_id BIGINT UNSIGNED\n"
- ") COMPACT;\n"
- "CREATE UNIQUE CLUSTERED INDEX IND "
- "ON \"%s_BEING_DELETED\"(doc_id);\n"
- ""
- "CREATE TABLE \"%s_BEING_DELETED_CACHE\" (\n"
- " doc_id BIGINT UNSIGNED\n"
- ") COMPACT;\n"
- "CREATE UNIQUE CLUSTERED INDEX IND "
- "ON \"%s_BEING_DELETED_CACHE\"(doc_id);\n"
- ""
- "CREATE TABLE \"%s_CONFIG\" (\n"
- " key CHAR(50),\n"
- " value CHAR(200) NOT NULL\n"
- ") COMPACT;\n"
- "CREATE UNIQUE CLUSTERED INDEX IND ON \"%s_CONFIG\"(key);\n"
-};
-
-#ifdef FTS_DOC_STATS_DEBUG
-/** Template for creating the FTS auxiliary index specific tables. This is
-mainly designed for the statistics work in the future */
-static const char* fts_create_index_tables_sql = {
- "BEGIN\n"
- ""
- "CREATE TABLE \"%s_DOC_ID\" (\n"
- " doc_id BIGINT UNSIGNED,\n"
- " word_count INTEGER UNSIGNED NOT NULL\n"
- ") COMPACT;\n"
- "CREATE UNIQUE CLUSTERED INDEX IND ON \"%s_DOC_ID\"(doc_id);\n"
-};
-#endif
-
-/** Template for creating the ancillary FTS tables word index tables. */
-static const char* fts_create_index_sql = {
- "BEGIN\n"
- ""
- "CREATE UNIQUE CLUSTERED INDEX FTS_INDEX_TABLE_IND "
- "ON \"%s\"(word, first_doc_id);\n"
-};
-
-/** FTS auxiliary table suffixes that are common to all FT indexes. */
-static const char* fts_common_tables[] = {
- "BEING_DELETED",
- "BEING_DELETED_CACHE",
- "CONFIG",
- "DELETED",
- "DELETED_CACHE",
- NULL
-};
-
-/** FTS auxiliary INDEX split intervals. */
-const fts_index_selector_t fts_index_selector[] = {
- { 9, "INDEX_1" },
- { 65, "INDEX_2" },
- { 70, "INDEX_3" },
- { 75, "INDEX_4" },
- { 80, "INDEX_5" },
- { 85, "INDEX_6" },
- { 0 , NULL }
-};
-
-/** Default config values for FTS indexes on a table. */
-static const char* fts_config_table_insert_values_sql =
- "BEGIN\n"
- "\n"
- "INSERT INTO \"%s\" VALUES('"
- FTS_MAX_CACHE_SIZE_IN_MB "', '256');\n"
- ""
- "INSERT INTO \"%s\" VALUES('"
- FTS_OPTIMIZE_LIMIT_IN_SECS "', '180');\n"
- ""
- "INSERT INTO \"%s\" VALUES ('"
- FTS_SYNCED_DOC_ID "', '0');\n"
- ""
- "INSERT INTO \"%s\" VALUES ('"
- FTS_TOTAL_DELETED_COUNT "', '0');\n"
- "" /* Note: 0 == FTS_TABLE_STATE_RUNNING */
- "INSERT INTO \"%s\" VALUES ('"
- FTS_TABLE_STATE "', '0');\n";
-
-/** Run SYNC on the table, i.e., write out data from the cache to the
-FTS auxiliary INDEX table and clear the cache at the end.
-@param[in,out] sync sync state
-@param[in] unlock_cache whether unlock cache lock when write node
-@param[in] wait whether wait when a sync is in progress
-@param[in] has_dict whether has dict operation lock
-@return DB_SUCCESS if all OK */
-static
-dberr_t
-fts_sync(
- fts_sync_t* sync,
- bool unlock_cache,
- bool wait,
- bool has_dict);
-
-/****************************************************************//**
-Release all resources help by the words rb tree e.g., the node ilist. */
-static
-void
-fts_words_free(
-/*===========*/
- ib_rbt_t* words) /*!< in: rb tree of words */
- MY_ATTRIBUTE((nonnull));
-#ifdef FTS_CACHE_SIZE_DEBUG
-/****************************************************************//**
-Read the max cache size parameter from the config table. */
-static
-void
-fts_update_max_cache_size(
-/*======================*/
- fts_sync_t* sync); /*!< in: sync state */
-#endif
-
-/*********************************************************************//**
-This function fetches the document just inserted right before
-we commit the transaction, and tokenize the inserted text data
-and insert into FTS auxiliary table and its cache.
-@return TRUE if successful */
-static
-ulint
-fts_add_doc_by_id(
-/*==============*/
- fts_trx_table_t*ftt, /*!< in: FTS trx table */
- doc_id_t doc_id, /*!< in: doc id */
- ib_vector_t* fts_indexes MY_ATTRIBUTE((unused)));
- /*!< in: affected fts indexes */
-#ifdef FTS_DOC_STATS_DEBUG
-/****************************************************************//**
-Check whether a particular word (term) exists in the FTS index.
-@return DB_SUCCESS if all went fine */
-static
-dberr_t
-fts_is_word_in_index(
-/*=================*/
- trx_t* trx, /*!< in: FTS query state */
- que_t** graph, /*!< out: Query graph */
- fts_table_t* fts_table, /*!< in: table instance */
- const fts_string_t* word, /*!< in: the word to check */
- ibool* found) /*!< out: TRUE if exists */
- MY_ATTRIBUTE((nonnull, warn_unused_result));
-#endif /* FTS_DOC_STATS_DEBUG */
-
-/******************************************************************//**
-Update the last document id. This function could create a new
-transaction to update the last document id.
-@return DB_SUCCESS if OK */
-static
-dberr_t
-fts_update_sync_doc_id(
-/*===================*/
- const dict_table_t* table, /*!< in: table */
- const char* table_name, /*!< in: table name, or NULL */
- doc_id_t doc_id, /*!< in: last document id */
- trx_t* trx) /*!< in: update trx, or NULL */
- MY_ATTRIBUTE((nonnull(1)));
-
-/****************************************************************//**
-This function loads the default InnoDB stopword list */
-static
-void
-fts_load_default_stopword(
-/*======================*/
- fts_stopword_t* stopword_info) /*!< in: stopword info */
-{
- fts_string_t str;
- mem_heap_t* heap;
- ib_alloc_t* allocator;
- ib_rbt_t* stop_words;
-
- allocator = stopword_info->heap;
- heap = static_cast<mem_heap_t*>(allocator->arg);
-
- if (!stopword_info->cached_stopword) {
- /* For default stopword, we always use fts_utf8_string_cmp() */
- stopword_info->cached_stopword = rbt_create(
- sizeof(fts_tokenizer_word_t), fts_utf8_string_cmp);
- }
-
- stop_words = stopword_info->cached_stopword;
-
- str.f_n_char = 0;
-
- for (ulint i = 0; fts_default_stopword[i]; ++i) {
- char* word;
- fts_tokenizer_word_t new_word;
-
- /* We are going to duplicate the value below. */
- word = const_cast<char*>(fts_default_stopword[i]);
-
- new_word.nodes = ib_vector_create(
- allocator, sizeof(fts_node_t), 4);
-
- str.f_len = ut_strlen(word);
- str.f_str = reinterpret_cast<byte*>(word);
-
- fts_utf8_string_dup(&new_word.text, &str, heap);
-
- rbt_insert(stop_words, &new_word, &new_word);
- }
-
- stopword_info->status = STOPWORD_FROM_DEFAULT;
-}
-
-/****************************************************************//**
-Callback function to read a single stopword value.
-@return Always return TRUE */
-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;
-
- sel_node = static_cast<sel_node_t*>(row);
- stopword_info = static_cast<fts_stopword_t*>(user_arg);
-
- stop_words = stopword_info->cached_stopword;
- allocator = static_cast<ib_alloc_t*>(stopword_info->heap);
- heap = static_cast<mem_heap_t*>(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<byte*>(dfield_get_data(dfield));
- str.f_len = dfield_get_len(dfield);
-
- /* 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<byte*>(
- mem_heap_alloc(heap, str.f_len + 1));
-
- memcpy(new_word.text.f_str, str.f_str, str.f_len);
-
- 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);
-}
-
-/******************************************************************//**
-Load user defined stopword from designated user table
-@return TRUE if load operation is successful */
-static
-ibool
-fts_load_user_stopword(
-/*===================*/
- fts_t* fts, /*!< in: FTS struct */
- const char* stopword_table_name, /*!< in: Stopword table
- name */
- fts_stopword_t* stopword_info) /*!< in: Stopword info */
-{
- pars_info_t* info;
- que_t* graph;
- dberr_t error = DB_SUCCESS;
- ibool ret = TRUE;
- trx_t* trx;
- ibool has_lock = fts->fts_status & TABLE_DICT_LOCKED;
-
- trx = trx_allocate_for_background();
- trx->op_info = "Load user stopword table into FTS cache";
-
- if (!has_lock) {
- mutex_enter(&dict_sys->mutex);
- }
-
- /* Validate the user table existence and in the right
- format */
- stopword_info->charset = fts_valid_stopword_table(stopword_table_name);
- if (!stopword_info->charset) {
- ret = FALSE;
- goto cleanup;
- } else if (!stopword_info->cached_stopword) {
- /* Create the stopword RB tree with the stopword column
- charset. All comparison will use this charset */
- stopword_info->cached_stopword = rbt_create_arg_cmp(
- sizeof(fts_tokenizer_word_t), innobase_fts_text_cmp,
- (void*)stopword_info->charset);
-
- }
-
- info = pars_info_create();
-
- pars_info_bind_id(info, TRUE, "table_stopword", stopword_table_name);
-
- pars_info_bind_function(info, "my_func", fts_read_stopword,
- stopword_info);
-
- graph = fts_parse_sql_no_dict_lock(
- NULL,
- info,
- "DECLARE FUNCTION my_func;\n"
- "DECLARE CURSOR c IS"
- " SELECT value "
- " 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;");
-
- for (;;) {
- error = fts_eval_sql(trx, graph);
-
- if (error == DB_SUCCESS) {
- fts_sql_commit(trx);
- stopword_info->status = STOPWORD_USER_TABLE;
- break;
- } else {
-
- fts_sql_rollback(trx);
-
- ut_print_timestamp(stderr);
-
- if (error == DB_LOCK_WAIT_TIMEOUT) {
- fprintf(stderr, " InnoDB: Warning: lock wait "
- "timeout reading user stopword table. "
- "Retrying!\n");
-
- trx->error_state = DB_SUCCESS;
- } else {
- fprintf(stderr, " InnoDB: Error '%s' "
- "while reading user stopword table.\n",
- ut_strerr(error));
- ret = FALSE;
- break;
- }
- }
- }
-
- que_graph_free(graph);
-
-cleanup:
- if (!has_lock) {
- mutex_exit(&dict_sys->mutex);
- }
-
- trx_free_for_background(trx);
- return(ret);
-}
-
-/******************************************************************//**
-Initialize the index cache. */
-static
-void
-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(
- sizeof(fts_tokenizer_word_t), innobase_fts_text_cmp,
- (void*)index_cache->charset);
-
- ut_a(index_cache->doc_stats == NULL);
-
- index_cache->doc_stats = ib_vector_create(
- allocator, sizeof(fts_doc_stats_t), 4);
-
- for (i = 0; fts_index_selector[i].value; ++i) {
- ut_a(index_cache->ins_graph[i] == NULL);
- ut_a(index_cache->sel_graph[i] == NULL);
- }
-}
-
-/*********************************************************************//**
-Initialize FTS cache. */
-UNIV_INTERN
-void
-fts_cache_init(
-/*===========*/
- fts_cache_t* cache) /*!< in: cache to initialize */
-{
- ulint i;
-
- /* Just to make sure */
- ut_a(cache->sync_heap->arg == NULL);
-
- cache->sync_heap->arg = mem_heap_create(1024);
-
- cache->total_size = 0;
-
- mutex_enter((ib_mutex_t*) &cache->deleted_lock);
- cache->deleted_doc_ids = ib_vector_create(
- cache->sync_heap, sizeof(fts_update_t), 4);
- mutex_exit((ib_mutex_t*) &cache->deleted_lock);
-
- /* Reset the cache data for all the FTS indexes. */
- for (i = 0; i < ib_vector_size(cache->indexes); ++i) {
- fts_index_cache_t* index_cache;
-
- index_cache = static_cast<fts_index_cache_t*>(
- ib_vector_get(cache->indexes, i));
-
- fts_index_cache_init(cache->sync_heap, index_cache);
- }
-}
-
-/****************************************************************//**
-Create a FTS cache. */
-UNIV_INTERN
-fts_cache_t*
-fts_cache_create(
-/*=============*/
- dict_table_t* table) /*!< in: table owns the FTS cache */
-{
- mem_heap_t* heap;
- fts_cache_t* cache;
-
- heap = static_cast<mem_heap_t*>(mem_heap_create(512));
-
- cache = static_cast<fts_cache_t*>(
- mem_heap_zalloc(heap, sizeof(*cache)));
-
- cache->cache_heap = heap;
-
- rw_lock_create(fts_cache_rw_lock_key, &cache->lock, SYNC_FTS_CACHE);
-
- rw_lock_create(
- fts_cache_init_rw_lock_key, &cache->init_lock,
- SYNC_FTS_CACHE_INIT);
-
- mutex_create(
- fts_delete_mutex_key, &cache->deleted_lock, SYNC_FTS_OPTIMIZE);
-
- mutex_create(
- fts_optimize_mutex_key, &cache->optimize_lock,
- SYNC_FTS_OPTIMIZE);
-
- mutex_create(
- fts_doc_id_mutex_key, &cache->doc_id_lock, SYNC_FTS_OPTIMIZE);
-
- /* This is the heap used to create the cache itself. */
- cache->self_heap = ib_heap_allocator_create(heap);
-
- /* This is a transient heap, used for storing sync data. */
- cache->sync_heap = ib_heap_allocator_create(heap);
- cache->sync_heap->arg = NULL;
-
- fts_need_sync = false;
-
- cache->sync = static_cast<fts_sync_t*>(
- mem_heap_zalloc(heap, sizeof(fts_sync_t)));
-
- cache->sync->table = table;
- cache->sync->event = os_event_create();
-
- /* Create the index cache vector that will hold the inverted indexes. */
- cache->indexes = ib_vector_create(
- cache->self_heap, sizeof(fts_index_cache_t), 2);
-
- fts_cache_init(cache);
-
- cache->stopword_info.cached_stopword = NULL;
- cache->stopword_info.charset = NULL;
-
- cache->stopword_info.heap = cache->self_heap;
-
- cache->stopword_info.status = STOPWORD_NOT_INIT;
-
- return(cache);
-}
-
-/*******************************************************************//**
-Add a newly create index into FTS cache */
-UNIV_INTERN
-void
-fts_add_index(
-/*==========*/
- dict_index_t* index, /*!< FTS index to be added */
- dict_table_t* table) /*!< table */
-{
- fts_t* fts = table->fts;
- fts_cache_t* cache;
- fts_index_cache_t* index_cache;
-
- ut_ad(fts);
- cache = table->fts->cache;
-
- rw_lock_x_lock(&cache->init_lock);
-
- ib_vector_push(fts->indexes, &index);
-
- index_cache = fts_find_index_cache(cache, index);
-
- if (!index_cache) {
- /* Add new index cache structure */
- index_cache = fts_cache_index_cache_create(table, index);
- }
-
- rw_lock_x_unlock(&cache->init_lock);
-}
-
-/*******************************************************************//**
-recalibrate get_doc structure after index_cache in cache->indexes changed */
-static
-void
-fts_reset_get_doc(
-/*==============*/
- fts_cache_t* cache) /*!< in: FTS index cache */
-{
- fts_get_doc_t* get_doc;
- ulint i;
-
-#ifdef UNIV_SYNC_DEBUG
- ut_ad(rw_lock_own(&cache->init_lock, RW_LOCK_EX));
-#endif
- ib_vector_reset(cache->get_docs);
-
- for (i = 0; i < ib_vector_size(cache->indexes); i++) {
- fts_index_cache_t* ind_cache;
-
- ind_cache = static_cast<fts_index_cache_t*>(
- ib_vector_get(cache->indexes, i));
-
- get_doc = static_cast<fts_get_doc_t*>(
- ib_vector_push(cache->get_docs, NULL));
-
- memset(get_doc, 0x0, sizeof(*get_doc));
-
- get_doc->index_cache = ind_cache;
- }
-
- ut_ad(ib_vector_size(cache->get_docs)
- == ib_vector_size(cache->indexes));
-}
-
-/*******************************************************************//**
-Check an index is in the table->indexes list
-@return TRUE if it exists */
-static
-ibool
-fts_in_dict_index(
-/*==============*/
- dict_table_t* table, /*!< in: Table */
- dict_index_t* index_check) /*!< in: index to be checked */
-{
- dict_index_t* index;
-
- for (index = dict_table_get_first_index(table);
- index != NULL;
- index = dict_table_get_next_index(index)) {
-
- if (index == index_check) {
- return(TRUE);
- }
- }
-
- return(FALSE);
-}
-
-/*******************************************************************//**
-Check an index is in the fts->cache->indexes list
-@return TRUE if it exists */
-static
-ibool
-fts_in_index_cache(
-/*===============*/
- dict_table_t* table, /*!< in: Table */
- dict_index_t* index) /*!< in: index to be checked */
-{
- ulint i;
-
- for (i = 0; i < ib_vector_size(table->fts->cache->indexes); i++) {
- fts_index_cache_t* index_cache;
-
- index_cache = static_cast<fts_index_cache_t*>(
- ib_vector_get(table->fts->cache->indexes, i));
-
- if (index_cache->index == index) {
- return(TRUE);
- }
- }
-
- return(FALSE);
-}
-
-/*******************************************************************//**
-Check indexes in the fts->indexes is also present in index cache and
-table->indexes list
-@return TRUE if all indexes match */
-UNIV_INTERN
-ibool
-fts_check_cached_index(
-/*===================*/
- dict_table_t* table) /*!< in: Table where indexes are dropped */
-{
- ulint i;
-
- if (!table->fts || !table->fts->cache) {
- return(TRUE);
- }
-
- ut_a(ib_vector_size(table->fts->indexes)
- == ib_vector_size(table->fts->cache->indexes));
-
- for (i = 0; i < ib_vector_size(table->fts->indexes); i++) {
- dict_index_t* index;
-
- index = static_cast<dict_index_t*>(
- ib_vector_getp(table->fts->indexes, i));
-
- if (!fts_in_index_cache(table, index)) {
- return(FALSE);
- }
-
- if (!fts_in_dict_index(table, index)) {
- return(FALSE);
- }
- }
-
- return(TRUE);
-}
-
-/*******************************************************************//**
-Drop auxiliary tables related to an FTS index
-@return DB_SUCCESS or error number */
-UNIV_INTERN
-dberr_t
-fts_drop_index(
-/*===========*/
- dict_table_t* table, /*!< in: Table where indexes are dropped */
- dict_index_t* index, /*!< in: Index to be dropped */
- trx_t* trx) /*!< in: Transaction for the drop */
-{
- ib_vector_t* indexes = table->fts->indexes;
- dberr_t err = DB_SUCCESS;
-
- ut_a(indexes);
-
- if ((ib_vector_size(indexes) == 1
- && (index == static_cast<dict_index_t*>(
- ib_vector_getp(table->fts->indexes, 0))))
- || ib_vector_is_empty(indexes)) {
- doc_id_t current_doc_id;
- doc_id_t first_doc_id;
-
- /* If we are dropping the only FTS index of the table,
- remove it from optimize thread */
- fts_optimize_remove_table(table);
-
- DICT_TF2_FLAG_UNSET(table, DICT_TF2_FTS);
-
- /* If Doc ID column is not added internally by FTS index,
- we can drop all FTS auxiliary tables. Otherwise, we will
- need to keep some common table such as CONFIG table, so
- as to keep track of incrementing Doc IDs */
- if (!DICT_TF2_FLAG_IS_SET(
- table, DICT_TF2_FTS_HAS_DOC_ID)) {
-
- err = fts_drop_tables(trx, table);
-
- err = fts_drop_index_tables(trx, index);
-
- fts_free(table);
-
- return(err);
- }
-
- current_doc_id = table->fts->cache->next_doc_id;
- first_doc_id = table->fts->cache->first_doc_id;
- fts_cache_clear(table->fts->cache);
- fts_cache_destroy(table->fts->cache);
- table->fts->cache = fts_cache_create(table);
- table->fts->cache->next_doc_id = current_doc_id;
- table->fts->cache->first_doc_id = first_doc_id;
- } else {
- fts_cache_t* cache = table->fts->cache;
- fts_index_cache_t* index_cache;
-
- rw_lock_x_lock(&cache->init_lock);
-
- index_cache = fts_find_index_cache(cache, index);
-
- if (index_cache != NULL) {
- if (index_cache->words) {
- fts_words_free(index_cache->words);
- rbt_free(index_cache->words);
- }
-
- ib_vector_remove(cache->indexes, *(void**) index_cache);
- }
-
- if (cache->get_docs) {
- fts_reset_get_doc(cache);
- }
-
- rw_lock_x_unlock(&cache->init_lock);
- }
-
- err = fts_drop_index_tables(trx, index);
-
- ib_vector_remove(indexes, (const void*) index);
-
- return(err);
-}
-
-/****************************************************************//**
-Free the query graph but check whether dict_sys->mutex is already
-held */
-UNIV_INTERN
-void
-fts_que_graph_free_check_lock(
-/*==========================*/
- fts_table_t* fts_table, /*!< in: FTS table */
- const fts_index_cache_t*index_cache, /*!< in: FTS index cache */
- que_t* graph) /*!< in: query graph */
-{
- ibool has_dict = FALSE;
-
- if (fts_table && fts_table->table) {
- ut_ad(fts_table->table->fts);
-
- has_dict = fts_table->table->fts->fts_status
- & TABLE_DICT_LOCKED;
- } else if (index_cache) {
- ut_ad(index_cache->index->table->fts);
-
- has_dict = index_cache->index->table->fts->fts_status
- & TABLE_DICT_LOCKED;
- }
-
- if (!has_dict) {
- mutex_enter(&dict_sys->mutex);
- }
-
- ut_ad(mutex_own(&dict_sys->mutex));
-
- que_graph_free(graph);
-
- if (!has_dict) {
- mutex_exit(&dict_sys->mutex);
- }
-}
-
-/****************************************************************//**
-Create an FTS index cache. */
-UNIV_INTERN
-CHARSET_INFO*
-fts_index_get_charset(
-/*==================*/
- dict_index_t* index) /*!< in: FTS index */
-{
- CHARSET_INFO* charset = NULL;
- dict_field_t* field;
- ulint prtype;
-
- field = dict_index_get_nth_field(index, 0);
- prtype = field->col->prtype;
-
- charset = innobase_get_fts_charset(
- (int) (prtype & DATA_MYSQL_TYPE_MASK),
- (uint) dtype_get_charset_coll(prtype));
-
-#ifdef FTS_DEBUG
- /* Set up charset info for this index. Please note all
- field of the FTS index should have the same charset */
- for (i = 1; i < index->n_fields; i++) {
- CHARSET_INFO* fld_charset;
-
- field = dict_index_get_nth_field(index, i);
- prtype = field->col->prtype;
-
- fld_charset = innobase_get_fts_charset(
- (int)(prtype & DATA_MYSQL_TYPE_MASK),
- (uint) dtype_get_charset_coll(prtype));
-
- /* All FTS columns should have the same charset */
- if (charset) {
- ut_a(charset == fld_charset);
- } else {
- charset = fld_charset;
- }
- }
-#endif
-
- return(charset);
-
-}
-/****************************************************************//**
-Create an FTS index cache.
-@return Index Cache */
-UNIV_INTERN
-fts_index_cache_t*
-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;
-
- ut_a(cache != NULL);
-
-#ifdef UNIV_SYNC_DEBUG
- ut_ad(rw_lock_own(&cache->init_lock, RW_LOCK_EX));
-#endif
-
- /* Must not already exist in the cache vector. */
- ut_a(fts_find_index_cache(cache, index) == NULL);
-
- index_cache = static_cast<fts_index_cache_t*>(
- ib_vector_push(cache->indexes, NULL));
-
- memset(index_cache, 0x0, sizeof(*index_cache));
-
- index_cache->index = index;
-
- index_cache->charset = fts_index_get_charset(index);
-
- n_bytes = sizeof(que_t*) * sizeof(fts_index_selector);
-
- index_cache->ins_graph = static_cast<que_t**>(
- mem_heap_zalloc(static_cast<mem_heap_t*>(
- cache->self_heap->arg), n_bytes));
-
- index_cache->sel_graph = static_cast<que_t**>(
- mem_heap_zalloc(static_cast<mem_heap_t*>(
- cache->self_heap->arg), n_bytes));
-
- fts_index_cache_init(cache->sync_heap, index_cache);
-
- if (cache->get_docs) {
- fts_reset_get_doc(cache);
- }
-
- return(index_cache);
-}
-
-/****************************************************************//**
-Release all resources help by the words rb tree e.g., the node ilist. */
-static
-void
-fts_words_free(
-/*===========*/
- ib_rbt_t* words) /*!< in: rb tree of words */
-{
- const ib_rbt_node_t* rbt_node;
-
- /* Free the resources held by a word. */
- for (rbt_node = rbt_first(words);
- rbt_node != NULL;
- rbt_node = rbt_first(words)) {
-
- ulint i;
- fts_tokenizer_word_t* word;
-
- word = rbt_value(fts_tokenizer_word_t, rbt_node);
-
- /* Free the ilists of this word. */
- for (i = 0; i < ib_vector_size(word->nodes); ++i) {
-
- fts_node_t* fts_node = static_cast<fts_node_t*>(
- ib_vector_get(word->nodes, i));
-
- ut_free(fts_node->ilist);
- fts_node->ilist = NULL;
- }
-
- /* NOTE: We are responsible for free'ing the node */
- ut_free(rbt_remove_node(words, rbt_node));
- }
-}
-
-/** Clear cache.
-@param[in,out] cache fts cache */
-UNIV_INTERN
-void
-fts_cache_clear(
- fts_cache_t* cache)
-{
- ulint i;
-
- for (i = 0; i < ib_vector_size(cache->indexes); ++i) {
- ulint j;
- fts_index_cache_t* index_cache;
-
- index_cache = static_cast<fts_index_cache_t*>(
- ib_vector_get(cache->indexes, i));
-
- fts_words_free(index_cache->words);
-
- rbt_free(index_cache->words);
-
- index_cache->words = NULL;
-
- for (j = 0; fts_index_selector[j].value; ++j) {
-
- if (index_cache->ins_graph[j] != NULL) {
-
- fts_que_graph_free_check_lock(
- NULL, index_cache,
- index_cache->ins_graph[j]);
-
- index_cache->ins_graph[j] = NULL;
- }
-
- if (index_cache->sel_graph[j] != NULL) {
-
- fts_que_graph_free_check_lock(
- NULL, index_cache,
- index_cache->sel_graph[j]);
-
- index_cache->sel_graph[j] = NULL;
- }
- }
-
- index_cache->doc_stats = NULL;
- }
-
- mem_heap_free(static_cast<mem_heap_t*>(cache->sync_heap->arg));
- cache->sync_heap->arg = NULL;
-
- cache->total_size = 0;
-
- mutex_enter((ib_mutex_t*) &cache->deleted_lock);
- cache->deleted_doc_ids = NULL;
- mutex_exit((ib_mutex_t*) &cache->deleted_lock);
-}
-
-/*********************************************************************//**
-Search the index specific cache for a particular FTS index.
-@return the index cache else NULL */
-UNIV_INLINE
-fts_index_cache_t*
-fts_get_index_cache(
-/*================*/
- fts_cache_t* cache, /*!< in: cache to search */
- const dict_index_t* index) /*!< in: index to search for */
-{
- ulint i;
-
-#ifdef UNIV_SYNC_DEBUG
- ut_ad(rw_lock_own((rw_lock_t*) &cache->lock, RW_LOCK_EX)
- || rw_lock_own((rw_lock_t*) &cache->init_lock, RW_LOCK_EX));
-#endif
-
- for (i = 0; i < ib_vector_size(cache->indexes); ++i) {
- fts_index_cache_t* index_cache;
-
- index_cache = static_cast<fts_index_cache_t*>(
- ib_vector_get(cache->indexes, i));
-
- if (index_cache->index == index) {
-
- return(index_cache);
- }
- }
-
- return(NULL);
-}
-
-#ifdef FTS_DEBUG
-/*********************************************************************//**
-Search the index cache for a get_doc structure.
-@return the fts_get_doc_t item else NULL */
-static
-fts_get_doc_t*
-fts_get_index_get_doc(
-/*==================*/
- fts_cache_t* cache, /*!< in: cache to search */
- const dict_index_t* index) /*!< in: index to search for */
-{
- ulint i;
-
-#ifdef UNIV_SYNC_DEBUG
- ut_ad(rw_lock_own((rw_lock_t*) &cache->init_lock, RW_LOCK_EX));
-#endif
-
- for (i = 0; i < ib_vector_size(cache->get_docs); ++i) {
- fts_get_doc_t* get_doc;
-
- get_doc = static_cast<fts_get_doc_t*>(
- ib_vector_get(cache->get_docs, i));
-
- if (get_doc->index_cache->index == index) {
-
- return(get_doc);
- }
- }
-
- return(NULL);
-}
-#endif
-
-/**********************************************************************//**
-Free the FTS cache. */
-UNIV_INTERN
-void
-fts_cache_destroy(
-/*==============*/
- fts_cache_t* cache) /*!< in: cache*/
-{
- rw_lock_free(&cache->lock);
- rw_lock_free(&cache->init_lock);
- mutex_free(&cache->optimize_lock);
- mutex_free(&cache->deleted_lock);
- mutex_free(&cache->doc_id_lock);
- os_event_free(cache->sync->event);
-
- if (cache->stopword_info.cached_stopword) {
- rbt_free(cache->stopword_info.cached_stopword);
- }
-
- if (cache->sync_heap->arg) {
- mem_heap_free(static_cast<mem_heap_t*>(cache->sync_heap->arg));
- }
-
- mem_heap_free(cache->cache_heap);
-}
-
-/**********************************************************************//**
-Find an existing word, or if not found, create one and return it.
-@return specified word token */
-static
-fts_tokenizer_word_t*
-fts_tokenizer_word_get(
-/*===================*/
- fts_cache_t* cache, /*!< in: cache */
- fts_index_cache_t*
- index_cache, /*!< in: index cache */
- fts_string_t* text) /*!< in: node text */
-{
- fts_tokenizer_word_t* word;
- ib_rbt_bound_t parent;
-
-#ifdef UNIV_SYNC_DEBUG
- ut_ad(rw_lock_own(&cache->lock, RW_LOCK_EX));
-#endif
-
- /* If it is a stopword, do not index it */
- if (cache->stopword_info.cached_stopword != NULL
- && rbt_search(cache->stopword_info.cached_stopword,
- &parent, text) == 0) {
-
- return(NULL);
- }
-
- /* Check if we found a match, if not then add word to tree. */
- if (rbt_search(index_cache->words, &parent, text) != 0) {
- mem_heap_t* heap;
- fts_tokenizer_word_t new_word;
-
- heap = static_cast<mem_heap_t*>(cache->sync_heap->arg);
-
- new_word.nodes = ib_vector_create(
- cache->sync_heap, sizeof(fts_node_t), 4);
-
- fts_utf8_string_dup(&new_word.text, text, heap);
-
- parent.last = rbt_add_node(
- index_cache->words, &parent, &new_word);
-
- /* Take into account the RB tree memory use and the vector. */
- cache->total_size += sizeof(new_word)
- + sizeof(ib_rbt_node_t)
- + text->f_len
- + (sizeof(fts_node_t) * 4)
- + sizeof(*new_word.nodes);
-
- ut_ad(rbt_validate(index_cache->words));
- }
-
- word = rbt_value(fts_tokenizer_word_t, parent.last);
-
- return(word);
-}
-
-/**********************************************************************//**
-Add the given doc_id/word positions to the given node's ilist. */
-UNIV_INTERN
-void
-fts_cache_node_add_positions(
-/*=========================*/
- fts_cache_t* cache, /*!< in: cache */
- fts_node_t* node, /*!< in: word node */
- doc_id_t doc_id, /*!< in: doc id */
- ib_vector_t* positions) /*!< in: fts_token_t::positions */
-{
- ulint i;
- byte* ptr;
- byte* ilist;
- ulint enc_len;
- ulint last_pos;
- byte* ptr_start;
- ulint doc_id_delta;
-
-#ifdef UNIV_SYNC_DEBUG
- if (cache) {
- ut_ad(rw_lock_own(&cache->lock, RW_LOCK_EX));
- }
-#endif
- ut_ad(doc_id >= node->last_doc_id);
-
- /* Calculate the space required to store the ilist. */
- doc_id_delta = (ulint)(doc_id - node->last_doc_id);
- enc_len = fts_get_encoded_len(doc_id_delta);
-
- last_pos = 0;
- for (i = 0; i < ib_vector_size(positions); i++) {
- ulint pos = *(static_cast<ulint*>(
- ib_vector_get(positions, i)));
-
- ut_ad(last_pos == 0 || pos > last_pos);
-
- enc_len += fts_get_encoded_len(pos - last_pos);
- last_pos = pos;
- }
-
- /* The 0x00 byte at the end of the token positions list. */
- enc_len++;
-
- if ((node->ilist_size_alloc - node->ilist_size) >= enc_len) {
- /* No need to allocate more space, we can fit in the new
- data at the end of the old one. */
- ilist = NULL;
- ptr = node->ilist + node->ilist_size;
- } else {
- ulint new_size = node->ilist_size + enc_len;
-
- /* Over-reserve space by a fixed size for small lengths and
- by 20% for lengths >= 48 bytes. */
- if (new_size < 16) {
- new_size = 16;
- } else if (new_size < 32) {
- new_size = 32;
- } else if (new_size < 48) {
- new_size = 48;
- } else {
- new_size = (ulint)(1.2 * new_size);
- }
-
- ilist = static_cast<byte*>(ut_malloc(new_size));
- ptr = ilist + node->ilist_size;
-
- node->ilist_size_alloc = new_size;
- }
-
- ptr_start = ptr;
-
- /* Encode the new fragment. */
- ptr += fts_encode_int(doc_id_delta, ptr);
-
- last_pos = 0;
- for (i = 0; i < ib_vector_size(positions); i++) {
- ulint pos = *(static_cast<ulint*>(
- ib_vector_get(positions, i)));
-
- ptr += fts_encode_int(pos - last_pos, ptr);
- last_pos = pos;
- }
-
- *ptr++ = 0;
-
- ut_a(enc_len == (ulint)(ptr - ptr_start));
-
- if (ilist) {
- /* Copy old ilist to the start of the new one and switch the
- new one into place in the node. */
- if (node->ilist_size > 0) {
- memcpy(ilist, node->ilist, node->ilist_size);
- ut_free(node->ilist);
- }
-
- node->ilist = ilist;
- }
-
- node->ilist_size += enc_len;
-
- if (cache) {
- cache->total_size += enc_len;
- }
-
- if (node->first_doc_id == FTS_NULL_DOC_ID) {
- node->first_doc_id = doc_id;
- }
-
- node->last_doc_id = doc_id;
- ++node->doc_count;
-}
-
-/**********************************************************************//**
-Add document to the cache. */
-static
-void
-fts_cache_add_doc(
-/*==============*/
- fts_cache_t* cache, /*!< in: cache */
- fts_index_cache_t*
- index_cache, /*!< in: index cache */
- doc_id_t doc_id, /*!< in: doc id to add */
- ib_rbt_t* tokens) /*!< in: document tokens */
-{
- const ib_rbt_node_t* node;
- ulint n_words;
- fts_doc_stats_t* doc_stats;
-
- if (!tokens) {
- return;
- }
-
-#ifdef UNIV_SYNC_DEBUG
- ut_ad(rw_lock_own(&cache->lock, RW_LOCK_EX));
-#endif
-
- n_words = rbt_size(tokens);
-
- for (node = rbt_first(tokens); node; node = rbt_first(tokens)) {
-
- fts_tokenizer_word_t* word;
- fts_node_t* fts_node = NULL;
- fts_token_t* token = rbt_value(fts_token_t, node);
-
- /* Find and/or add token to the cache. */
- word = fts_tokenizer_word_get(
- cache, index_cache, &token->text);
-
- if (!word) {
- ut_free(rbt_remove_node(tokens, node));
- continue;
- }
-
- if (ib_vector_size(word->nodes) > 0) {
- fts_node = static_cast<fts_node_t*>(
- ib_vector_last(word->nodes));
- }
-
- if (fts_node == NULL || fts_node->synced
- || fts_node->ilist_size > FTS_ILIST_MAX_SIZE
- || doc_id < fts_node->last_doc_id) {
-
- fts_node = static_cast<fts_node_t*>(
- ib_vector_push(word->nodes, NULL));
-
- memset(fts_node, 0x0, sizeof(*fts_node));
-
- cache->total_size += sizeof(*fts_node);
- }
-
- fts_cache_node_add_positions(
- cache, fts_node, doc_id, token->positions);
-
- ut_free(rbt_remove_node(tokens, node));
- }
-
- ut_a(rbt_empty(tokens));
-
- /* Add to doc ids processed so far. */
- doc_stats = static_cast<fts_doc_stats_t*>(
- ib_vector_push(index_cache->doc_stats, NULL));
-
- doc_stats->doc_id = doc_id;
- doc_stats->word_count = n_words;
-
- /* Add the doc stats memory usage too. */
- cache->total_size += sizeof(*doc_stats);
-
- if (doc_id > cache->sync->max_doc_id) {
- cache->sync->max_doc_id = doc_id;
- }
-}
-
-/****************************************************************//**
-Drops a table. If the table can't be found we return a SUCCESS code.
-@return DB_SUCCESS or error code */
-static MY_ATTRIBUTE((nonnull, warn_unused_result))
-dberr_t
-fts_drop_table(
-/*===========*/
- trx_t* trx, /*!< in: transaction */
- const char* table_name) /*!< in: table to drop */
-{
- dict_table_t* table;
- dberr_t error = DB_SUCCESS;
-
- /* Check that the table exists in our data dictionary.
- Similar to regular drop table case, we will open table with
- DICT_ERR_IGNORE_INDEX_ROOT and DICT_ERR_IGNORE_CORRUPT option */
- table = dict_table_open_on_name(
- table_name, TRUE, FALSE,
- static_cast<dict_err_ignore_t>(
- DICT_ERR_IGNORE_INDEX_ROOT | DICT_ERR_IGNORE_CORRUPT));
-
- if (table != 0) {
-
- dict_table_close(table, TRUE, FALSE);
-
- /* Pass nonatomic=false (dont allow data dict unlock),
- because the transaction may hold locks on SYS_* tables from
- previous calls to fts_drop_table(). */
- error = row_drop_table_for_mysql(table_name, trx, true, false);
-
- if (error != DB_SUCCESS) {
- ib_logf(IB_LOG_LEVEL_ERROR,
- "Unable to drop FTS index aux table %s: %s",
- table_name, ut_strerr(error));
- }
- } else {
- error = DB_FAIL;
- }
-
- return(error);
-}
-
-/****************************************************************//**
-Rename a single auxiliary table due to database name change.
-@return DB_SUCCESS or error code */
-static MY_ATTRIBUTE((nonnull, warn_unused_result))
-dberr_t
-fts_rename_one_aux_table(
-/*=====================*/
- const char* new_name, /*!< in: new parent tbl name */
- const char* fts_table_old_name, /*!< in: old aux tbl name */
- trx_t* trx) /*!< in: transaction */
-{
- char fts_table_new_name[MAX_TABLE_NAME_LEN];
- ulint new_db_name_len = dict_get_db_name_len(new_name);
- ulint old_db_name_len = dict_get_db_name_len(fts_table_old_name);
- ulint table_new_name_len = strlen(fts_table_old_name)
- + new_db_name_len - old_db_name_len;
-
- /* Check if the new and old database names are the same, if so,
- nothing to do */
- ut_ad((new_db_name_len != old_db_name_len)
- || strncmp(new_name, fts_table_old_name, old_db_name_len) != 0);
-
- /* Get the database name from "new_name", and table name
- from the fts_table_old_name */
- strncpy(fts_table_new_name, new_name, new_db_name_len);
- strncpy(fts_table_new_name + new_db_name_len,
- strchr(fts_table_old_name, '/'),
- table_new_name_len - new_db_name_len);
- fts_table_new_name[table_new_name_len] = 0;
-
- return(row_rename_table_for_mysql(
- fts_table_old_name, fts_table_new_name, trx, false));
-}
-
-/****************************************************************//**
-Rename auxiliary tables for all fts index for a table. This(rename)
-is due to database name change
-@return DB_SUCCESS or error code */
-
-dberr_t
-fts_rename_aux_tables(
-/*==================*/
- dict_table_t* table, /*!< in: user Table */
- const char* new_name, /*!< in: new table name */
- trx_t* trx) /*!< in: transaction */
-{
- ulint i;
- fts_table_t fts_table;
-
- FTS_INIT_FTS_TABLE(&fts_table, NULL, FTS_COMMON_TABLE, table);
-
- /* Rename common auxiliary tables */
- for (i = 0; fts_common_tables[i] != NULL; ++i) {
- char* old_table_name;
- dberr_t err = DB_SUCCESS;
-
- fts_table.suffix = fts_common_tables[i];
-
- old_table_name = fts_get_table_name(&fts_table);
-
- err = fts_rename_one_aux_table(new_name, old_table_name, trx);
-
- mem_free(old_table_name);
-
- if (err != DB_SUCCESS) {
- return(err);
- }
- }
-
- fts_t* fts = table->fts;
-
- /* Rename index specific auxiliary tables */
- for (i = 0; fts->indexes != 0 && i < ib_vector_size(fts->indexes);
- ++i) {
- dict_index_t* index;
-
- index = static_cast<dict_index_t*>(
- ib_vector_getp(fts->indexes, i));
-
- FTS_INIT_INDEX_TABLE(&fts_table, NULL, FTS_INDEX_TABLE, index);
-
- for (ulint j = 0; fts_index_selector[j].value; ++j) {
- dberr_t err;
- char* old_table_name;
-
- fts_table.suffix = fts_get_suffix(j);
-
- old_table_name = fts_get_table_name(&fts_table);
-
- err = fts_rename_one_aux_table(
- new_name, old_table_name, trx);
-
- DBUG_EXECUTE_IF("fts_rename_failure",
- err = DB_DEADLOCK;
- fts_sql_rollback(trx););
-
- mem_free(old_table_name);
-
- if (err != DB_SUCCESS) {
- return(err);
- }
- }
- }
-
- return(DB_SUCCESS);
-}
-
-/****************************************************************//**
-Drops the common ancillary tables needed for supporting an FTS index
-on the given table. row_mysql_lock_data_dictionary must have been called
-before this.
-@return DB_SUCCESS or error code */
-static MY_ATTRIBUTE((nonnull, warn_unused_result))
-dberr_t
-fts_drop_common_tables(
-/*===================*/
- trx_t* trx, /*!< in: transaction */
- fts_table_t* fts_table) /*!< in: table with an FTS
- index */
-{
- ulint i;
- dberr_t error = DB_SUCCESS;
-
- for (i = 0; fts_common_tables[i] != NULL; ++i) {
- dberr_t err;
- char* table_name;
-
- fts_table->suffix = fts_common_tables[i];
-
- table_name = fts_get_table_name(fts_table);
-
- err = fts_drop_table(trx, table_name);
-
- /* We only return the status of the last error. */
- if (err != DB_SUCCESS && err != DB_FAIL) {
- error = err;
- }
-
- mem_free(table_name);
- }
-
- return(error);
-}
-
-/****************************************************************//**
-Since we do a horizontal split on the index table, we need to drop
-all the split tables.
-@return DB_SUCCESS or error code */
-UNIV_INTERN
-dberr_t
-fts_drop_index_split_tables(
-/*========================*/
- trx_t* trx, /*!< in: transaction */
- dict_index_t* index) /*!< in: fts instance */
-
-{
- ulint i;
- fts_table_t fts_table;
- dberr_t error = DB_SUCCESS;
-
- FTS_INIT_INDEX_TABLE(&fts_table, NULL, FTS_INDEX_TABLE, index);
-
- for (i = 0; fts_index_selector[i].value; ++i) {
- dberr_t err;
- char* table_name;
-
- fts_table.suffix = fts_get_suffix(i);
-
- table_name = fts_get_table_name(&fts_table);
-
- err = fts_drop_table(trx, table_name);
-
- /* We only return the status of the last error. */
- if (err != DB_SUCCESS && err != DB_FAIL) {
- error = err;
- }
-
- mem_free(table_name);
- }
-
- return(error);
-}
-
-/****************************************************************//**
-Drops FTS auxiliary tables for an FTS index
-@return DB_SUCCESS or error code */
-UNIV_INTERN
-dberr_t
-fts_drop_index_tables(
-/*==================*/
- trx_t* trx, /*!< in: transaction */
- dict_index_t* index) /*!< in: Index to drop */
-{
- dberr_t error = DB_SUCCESS;
-
-#ifdef FTS_DOC_STATS_DEBUG
- fts_table_t fts_table;
- static const char* index_tables[] = {
- "DOC_ID",
- NULL
- };
-#endif /* FTS_DOC_STATS_DEBUG */
-
- dberr_t err = fts_drop_index_split_tables(trx, index);
-
- /* We only return the status of the last error. */
- if (err != DB_SUCCESS) {
- error = err;
- }
-
-#ifdef FTS_DOC_STATS_DEBUG
- FTS_INIT_INDEX_TABLE(&fts_table, NULL, FTS_INDEX_TABLE, index);
-
- for (ulint i = 0; index_tables[i] != NULL; ++i) {
- char* table_name;
-
- fts_table.suffix = index_tables[i];
-
- table_name = fts_get_table_name(&fts_table);
-
- err = fts_drop_table(trx, table_name);
-
- /* We only return the status of the last error. */
- if (err != DB_SUCCESS && err != DB_FAIL) {
- error = err;
- }
-
- mem_free(table_name);
- }
-#endif /* FTS_DOC_STATS_DEBUG */
-
- return(error);
-}
-
-/****************************************************************//**
-Drops FTS ancillary tables needed for supporting an FTS index
-on the given table. row_mysql_lock_data_dictionary must have been called
-before this.
-@return DB_SUCCESS or error code */
-static MY_ATTRIBUTE((nonnull, warn_unused_result))
-dberr_t
-fts_drop_all_index_tables(
-/*======================*/
- trx_t* trx, /*!< in: transaction */
- fts_t* fts) /*!< in: fts instance */
-{
- dberr_t error = DB_SUCCESS;
-
- for (ulint i = 0;
- fts->indexes != 0 && i < ib_vector_size(fts->indexes);
- ++i) {
-
- dberr_t err;
- dict_index_t* index;
-
- index = static_cast<dict_index_t*>(
- ib_vector_getp(fts->indexes, i));
-
- err = fts_drop_index_tables(trx, index);
-
- if (err != DB_SUCCESS) {
- error = err;
- }
- }
-
- return(error);
-}
-
-/*********************************************************************//**
-Drops the ancillary tables needed for supporting an FTS index on a
-given table. row_mysql_lock_data_dictionary must have been called before
-this.
-@return DB_SUCCESS or error code */
-UNIV_INTERN
-dberr_t
-fts_drop_tables(
-/*============*/
- trx_t* trx, /*!< in: transaction */
- dict_table_t* table) /*!< in: table has the FTS index */
-{
- dberr_t error;
- fts_table_t fts_table;
-
- FTS_INIT_FTS_TABLE(&fts_table, NULL, FTS_COMMON_TABLE, table);
-
- /* TODO: This is not atomic and can cause problems during recovery. */
-
- error = fts_drop_common_tables(trx, &fts_table);
-
- if (error == DB_SUCCESS) {
- error = fts_drop_all_index_tables(trx, table->fts);
- }
-
- return(error);
-}
-
-/*********************************************************************//**
-Prepare the SQL, so that all '%s' are replaced by the common prefix.
-@return sql string, use mem_free() to free the memory */
-static
-char*
-fts_prepare_sql(
-/*============*/
- fts_table_t* fts_table, /*!< in: table name info */
- const char* my_template) /*!< in: sql template */
-{
- char* sql;
- char* name_prefix;
-
- name_prefix = fts_get_table_name_prefix(fts_table);
- sql = ut_strreplace(my_template, "%s", name_prefix);
- mem_free(name_prefix);
-
- return(sql);
-}
-
-/*********************************************************************//**
-Creates the common ancillary tables needed for supporting an FTS index
-on the given table. row_mysql_lock_data_dictionary must have been called
-before this.
-@return DB_SUCCESS if succeed */
-UNIV_INTERN
-dberr_t
-fts_create_common_tables(
-/*=====================*/
- trx_t* trx, /*!< in: transaction */
- const dict_table_t* table, /*!< in: table with FTS index */
- const char* name, /*!< in: table name normalized.*/
- bool skip_doc_id_index)/*!< in: Skip index on doc id */
-{
- char* sql;
- dberr_t error;
- que_t* graph;
- fts_table_t fts_table;
- mem_heap_t* heap = mem_heap_create(1024);
- pars_info_t* info;
-
- FTS_INIT_FTS_TABLE(&fts_table, NULL, FTS_COMMON_TABLE, table);
-
- error = fts_drop_common_tables(trx, &fts_table);
-
- if (error != DB_SUCCESS) {
-
- goto func_exit;
- }
-
- /* Create the FTS tables that are common to an FTS index. */
- sql = fts_prepare_sql(&fts_table, fts_create_common_tables_sql);
- graph = fts_parse_sql_no_dict_lock(NULL, NULL, sql);
- mem_free(sql);
-
- error = fts_eval_sql(trx, graph);
-
- que_graph_free(graph);
-
- if (error != DB_SUCCESS) {
-
- goto func_exit;
- }
-
- /* Write the default settings to the config table. */
- fts_table.suffix = "CONFIG";
- graph = fts_parse_sql_no_dict_lock(
- &fts_table, NULL, fts_config_table_insert_values_sql);
-
- error = fts_eval_sql(trx, graph);
-
- que_graph_free(graph);
-
- if (error != DB_SUCCESS || skip_doc_id_index) {
-
- goto func_exit;
- }
-
- info = pars_info_create();
-
- pars_info_bind_id(info, TRUE, "table_name", name);
- pars_info_bind_id(info, TRUE, "index_name", FTS_DOC_ID_INDEX_NAME);
- pars_info_bind_id(info, TRUE, "doc_id_col_name", FTS_DOC_ID_COL_NAME);
-
- /* Create the FTS DOC_ID index on the hidden column. Currently this
- is common for any FT index created on the table. */
- graph = fts_parse_sql_no_dict_lock(
- NULL,
- info,
- mem_heap_printf(
- heap,
- "BEGIN\n"
- ""
- "CREATE UNIQUE INDEX $index_name ON $table_name("
- "$doc_id_col_name);\n"));
-
- error = fts_eval_sql(trx, graph);
- que_graph_free(graph);
-
-func_exit:
- if (error != DB_SUCCESS) {
- /* We have special error handling here */
-
- trx->error_state = DB_SUCCESS;
-
- trx_rollback_to_savepoint(trx, NULL);
-
- row_drop_table_for_mysql(table->name, trx, FALSE, TRUE);
-
- trx->error_state = DB_SUCCESS;
- }
-
- mem_heap_free(heap);
-
- return(error);
-}
-
-/*************************************************************//**
-Wrapper function of fts_create_index_tables_low(), create auxiliary
-tables for an FTS index
-
-@see row_merge_create_fts_sort_index()
-@return: DB_SUCCESS or error code */
-static
-dict_table_t*
-fts_create_one_index_table(
-/*=======================*/
- trx_t* trx, /*!< in: transaction */
- const dict_index_t*
- index, /*!< in: the index instance */
- fts_table_t* fts_table, /*!< in: fts_table structure */
- mem_heap_t* heap) /*!< in: heap */
-{
- dict_field_t* field;
- dict_table_t* new_table = NULL;
- char* table_name = fts_get_table_name(fts_table);
- dberr_t error;
- CHARSET_INFO* charset;
- ulint flags2 = 0;
-
- ut_ad(index->type & DICT_FTS);
-
- if (srv_file_per_table) {
- flags2 = DICT_TF2_USE_TABLESPACE;
- }
-
- new_table = dict_mem_table_create(table_name, 0, 5, 1, flags2);
-
- field = dict_index_get_nth_field(index, 0);
- charset = innobase_get_fts_charset(
- (int)(field->col->prtype & DATA_MYSQL_TYPE_MASK),
- (uint) dtype_get_charset_coll(field->col->prtype));
-
- dict_mem_table_add_col(new_table, heap, "word",
- charset == &my_charset_latin1
- ? DATA_VARCHAR : DATA_VARMYSQL,
- field->col->prtype,
- FTS_MAX_WORD_LEN_IN_CHAR
- * DATA_MBMAXLEN(field->col->mbminmaxlen));
-
- dict_mem_table_add_col(new_table, heap, "first_doc_id", DATA_INT,
- DATA_NOT_NULL | DATA_UNSIGNED,
- sizeof(doc_id_t));
-
- dict_mem_table_add_col(new_table, heap, "last_doc_id", DATA_INT,
- DATA_NOT_NULL | DATA_UNSIGNED,
- sizeof(doc_id_t));
-
- dict_mem_table_add_col(new_table, heap, "doc_count", DATA_INT,
- DATA_NOT_NULL | DATA_UNSIGNED, 4);
-
- dict_mem_table_add_col(new_table, heap, "ilist", DATA_BLOB,
- 4130048, 0);
-
- error = row_create_table_for_mysql(new_table, trx, false, FIL_ENCRYPTION_DEFAULT, FIL_DEFAULT_ENCRYPTION_KEY);
-
- if (error != DB_SUCCESS) {
- trx->error_state = error;
- dict_mem_table_free(new_table);
- new_table = NULL;
- ib_logf(IB_LOG_LEVEL_WARN,
- "Fail to create FTS index table %s", table_name);
- }
-
- mem_free(table_name);
-
- return(new_table);
-}
-
-/*************************************************************//**
-Wrapper function of fts_create_index_tables_low(), create auxiliary
-tables for an FTS index
-@return: DB_SUCCESS or error code */
-UNIV_INTERN
-dberr_t
-fts_create_index_tables_low(
-/*========================*/
- trx_t* trx, /*!< in: transaction */
- const dict_index_t*
- index, /*!< in: the index instance */
- const char* table_name, /*!< in: the table name */
- table_id_t table_id) /*!< in: the table id */
-
-{
- ulint i;
- que_t* graph;
- fts_table_t fts_table;
- dberr_t error = DB_SUCCESS;
- mem_heap_t* heap = mem_heap_create(1024);
-
- fts_table.type = FTS_INDEX_TABLE;
- fts_table.index_id = index->id;
- fts_table.table_id = table_id;
- fts_table.parent = table_name;
- fts_table.table = index->table;
-
-#ifdef FTS_DOC_STATS_DEBUG
- char* sql;
-
- /* Create the FTS auxiliary tables that are specific
- to an FTS index. */
- sql = fts_prepare_sql(&fts_table, fts_create_index_tables_sql);
-
- graph = fts_parse_sql_no_dict_lock(NULL, NULL, sql);
- mem_free(sql);
-
- error = fts_eval_sql(trx, graph);
- que_graph_free(graph);
-#endif /* FTS_DOC_STATS_DEBUG */
-
- for (i = 0; fts_index_selector[i].value && error == DB_SUCCESS; ++i) {
- dict_table_t* new_table;
-
- /* Create the FTS auxiliary tables that are specific
- to an FTS index. We need to preserve the table_id %s
- which fts_parse_sql_no_dict_lock() will fill in for us. */
- fts_table.suffix = fts_get_suffix(i);
-
- new_table = fts_create_one_index_table(
- trx, index, &fts_table, heap);
-
- if (!new_table) {
- error = DB_FAIL;
- break;
- }
-
- graph = fts_parse_sql_no_dict_lock(
- &fts_table, NULL, fts_create_index_sql);
-
- error = fts_eval_sql(trx, graph);
- que_graph_free(graph);
- }
-
- if (error != DB_SUCCESS) {
- /* We have special error handling here */
-
- trx->error_state = DB_SUCCESS;
-
- trx_rollback_to_savepoint(trx, NULL);
-
- row_drop_table_for_mysql(table_name, trx, FALSE, TRUE);
-
- trx->error_state = DB_SUCCESS;
- }
-
- mem_heap_free(heap);
-
- return(error);
-}
-
-/******************************************************************//**
-Creates the column specific ancillary tables needed for supporting an
-FTS index on the given table. row_mysql_lock_data_dictionary must have
-been called before this.
-@return DB_SUCCESS or error code */
-UNIV_INTERN
-dberr_t
-fts_create_index_tables(
-/*====================*/
- trx_t* trx, /*!< in: transaction */
- const dict_index_t* index) /*!< in: the index instance */
-{
- dberr_t err;
- dict_table_t* table;
-
- table = dict_table_get_low(index->table_name);
- ut_a(table != NULL);
-
- err = fts_create_index_tables_low(trx, index, table->name, table->id);
-
- if (err == DB_SUCCESS) {
- trx_commit(trx);
- }
-
- return(err);
-}
-#if 0
-/******************************************************************//**
-Return string representation of state. */
-static
-const char*
-fts_get_state_str(
-/*==============*/
- /* out: string representation of state */
- fts_row_state state) /*!< in: state */
-{
- switch (state) {
- case FTS_INSERT:
- return("INSERT");
-
- case FTS_MODIFY:
- return("MODIFY");
-
- case FTS_DELETE:
- return("DELETE");
-
- case FTS_NOTHING:
- return("NOTHING");
-
- case FTS_INVALID:
- return("INVALID");
-
- default:
- return("UNKNOWN");
- }
-}
-#endif
-
-/******************************************************************//**
-Calculate the new state of a row given the existing state and a new event.
-@return new state of row */
-static
-fts_row_state
-fts_trx_row_get_new_state(
-/*======================*/
- fts_row_state old_state, /*!< in: existing state of row */
- fts_row_state event) /*!< in: new event */
-{
- /* The rules for transforming states:
-
- I = inserted
- M = modified
- D = deleted
- N = nothing
-
- M+D -> D:
-
- If the row existed before the transaction started and it is modified
- during the transaction, followed by a deletion of the row, only the
- deletion will be signaled.
-
- M+ -> M:
-
- If the row existed before the transaction started and it is modified
- more than once during the transaction, only the last modification
- will be signaled.
-
- IM*D -> N:
-
- If a new row is added during the transaction (and possibly modified
- after its initial insertion) but it is deleted before the end of the
- transaction, nothing will be signaled.
-
- IM* -> I:
-
- If a new row is added during the transaction and modified after its
- initial insertion, only the addition will be signaled.
-
- M*DI -> M:
-
- If the row existed before the transaction started and it is deleted,
- then re-inserted, only a modification will be signaled. Note that
- this case is only possible if the table is using the row's primary
- key for FTS row ids, since those can be re-inserted by the user,
- which is not true for InnoDB generated row ids.
-
- It is easily seen that the above rules decompose such that we do not
- need to store the row's entire history of events. Instead, we can
- store just one state for the row and update that when new events
- arrive. Then we can implement the above rules as a two-dimensional
- look-up table, and get checking of invalid combinations "for free"
- in the process. */
-
- /* The lookup table for transforming states. old_state is the
- Y-axis, event is the X-axis. */
- static const fts_row_state table[4][4] = {
- /* I M D N */
- /* I */ { FTS_INVALID, FTS_INSERT, FTS_NOTHING, FTS_INVALID },
- /* M */ { FTS_INVALID, FTS_MODIFY, FTS_DELETE, FTS_INVALID },
- /* D */ { FTS_MODIFY, FTS_INVALID, FTS_INVALID, FTS_INVALID },
- /* N */ { FTS_INVALID, FTS_INVALID, FTS_INVALID, FTS_INVALID }
- };
-
- fts_row_state result;
-
- ut_a(old_state < FTS_INVALID);
- ut_a(event < FTS_INVALID);
-
- result = table[(int) old_state][(int) event];
- ut_a(result != FTS_INVALID);
-
- return(result);
-}
-
-/******************************************************************//**
-Create a savepoint instance.
-@return savepoint instance */
-static
-fts_savepoint_t*
-fts_savepoint_create(
-/*=================*/
- ib_vector_t* savepoints, /*!< out: InnoDB transaction */
- const char* name, /*!< in: savepoint name */
- mem_heap_t* heap) /*!< in: heap */
-{
- fts_savepoint_t* savepoint;
-
- savepoint = static_cast<fts_savepoint_t*>(
- ib_vector_push(savepoints, NULL));
-
- memset(savepoint, 0x0, sizeof(*savepoint));
-
- if (name) {
- savepoint->name = mem_heap_strdup(heap, name);
- }
-
- savepoint->tables = rbt_create(
- sizeof(fts_trx_table_t*), fts_trx_table_cmp);
-
- return(savepoint);
-}
-
-/******************************************************************//**
-Create an FTS trx.
-@return FTS trx */
-static
-fts_trx_t*
-fts_trx_create(
-/*===========*/
- trx_t* trx) /*!< in/out: InnoDB
- transaction */
-{
- fts_trx_t* ftt;
- ib_alloc_t* heap_alloc;
- mem_heap_t* heap = mem_heap_create(1024);
- trx_named_savept_t* savep;
-
- ut_a(trx->fts_trx == NULL);
-
- ftt = static_cast<fts_trx_t*>(mem_heap_alloc(heap, sizeof(fts_trx_t)));
- ftt->trx = trx;
- ftt->heap = heap;
-
- heap_alloc = ib_heap_allocator_create(heap);
-
- ftt->savepoints = static_cast<ib_vector_t*>(ib_vector_create(
- heap_alloc, sizeof(fts_savepoint_t), 4));
-
- ftt->last_stmt = static_cast<ib_vector_t*>(ib_vector_create(
- heap_alloc, sizeof(fts_savepoint_t), 4));
-
- /* Default instance has no name and no heap. */
- fts_savepoint_create(ftt->savepoints, NULL, NULL);
- fts_savepoint_create(ftt->last_stmt, NULL, NULL);
-
- /* Copy savepoints that already set before. */
- for (savep = UT_LIST_GET_FIRST(trx->trx_savepoints);
- savep != NULL;
- savep = UT_LIST_GET_NEXT(trx_savepoints, savep)) {
-
- fts_savepoint_take(trx, ftt, savep->name);
- }
-
- return(ftt);
-}
-
-/******************************************************************//**
-Create an FTS trx table.
-@return FTS trx table */
-static
-fts_trx_table_t*
-fts_trx_table_create(
-/*=================*/
- fts_trx_t* fts_trx, /*!< in: FTS trx */
- dict_table_t* table) /*!< in: table */
-{
- fts_trx_table_t* ftt;
-
- ftt = static_cast<fts_trx_table_t*>(
- mem_heap_alloc(fts_trx->heap, sizeof(*ftt)));
-
- memset(ftt, 0x0, sizeof(*ftt));
-
- ftt->table = table;
- ftt->fts_trx = fts_trx;
-
- ftt->rows = rbt_create(sizeof(fts_trx_row_t), fts_trx_row_doc_id_cmp);
-
- return(ftt);
-}
-
-/******************************************************************//**
-Clone an FTS trx table.
-@return FTS trx table */
-static
-fts_trx_table_t*
-fts_trx_table_clone(
-/*=================*/
- const fts_trx_table_t* ftt_src) /*!< in: FTS trx */
-{
- fts_trx_table_t* ftt;
-
- ftt = static_cast<fts_trx_table_t*>(
- mem_heap_alloc(ftt_src->fts_trx->heap, sizeof(*ftt)));
-
- memset(ftt, 0x0, sizeof(*ftt));
-
- ftt->table = ftt_src->table;
- ftt->fts_trx = ftt_src->fts_trx;
-
- ftt->rows = rbt_create(sizeof(fts_trx_row_t), fts_trx_row_doc_id_cmp);
-
- /* Copy the rb tree values to the new savepoint. */
- rbt_merge_uniq(ftt->rows, ftt_src->rows);
-
- /* These are only added on commit. At this stage we only have
- the updated row state. */
- ut_a(ftt_src->added_doc_ids == NULL);
-
- return(ftt);
-}
-
-/******************************************************************//**
-Initialize the FTS trx instance.
-@return FTS trx instance */
-static
-fts_trx_table_t*
-fts_trx_init(
-/*=========*/
- trx_t* trx, /*!< in: transaction */
- dict_table_t* table, /*!< in: FTS table instance */
- ib_vector_t* savepoints) /*!< in: Savepoints */
-{
- fts_trx_table_t* ftt;
- ib_rbt_bound_t parent;
- ib_rbt_t* tables;
- fts_savepoint_t* savepoint;
-
- savepoint = static_cast<fts_savepoint_t*>(ib_vector_last(savepoints));
-
- tables = savepoint->tables;
- rbt_search_cmp(tables, &parent, &table->id, fts_trx_table_id_cmp, NULL);
-
- if (parent.result == 0) {
- fts_trx_table_t** fttp;
-
- fttp = rbt_value(fts_trx_table_t*, parent.last);
- ftt = *fttp;
- } else {
- ftt = fts_trx_table_create(trx->fts_trx, table);
- rbt_add_node(tables, &parent, &ftt);
- }
-
- ut_a(ftt->table == table);
-
- return(ftt);
-}
-
-/******************************************************************//**
-Notify the FTS system about an operation on an FTS-indexed table. */
-static
-void
-fts_trx_table_add_op(
-/*=================*/
- fts_trx_table_t*ftt, /*!< in: FTS trx table */
- doc_id_t doc_id, /*!< in: doc id */
- fts_row_state state, /*!< in: state of the row */
- ib_vector_t* fts_indexes) /*!< in: FTS indexes affected */
-{
- ib_rbt_t* rows;
- ib_rbt_bound_t parent;
-
- rows = ftt->rows;
- rbt_search(rows, &parent, &doc_id);
-
- /* Row id found, update state, and if new state is FTS_NOTHING,
- we delete the row from our tree. */
- if (parent.result == 0) {
- fts_trx_row_t* row = rbt_value(fts_trx_row_t, parent.last);
-
- row->state = fts_trx_row_get_new_state(row->state, state);
-
- if (row->state == FTS_NOTHING) {
- if (row->fts_indexes) {
- ib_vector_free(row->fts_indexes);
- }
-
- ut_free(rbt_remove_node(rows, parent.last));
- row = NULL;
- } else if (row->fts_indexes != NULL) {
- ib_vector_free(row->fts_indexes);
- row->fts_indexes = fts_indexes;
- }
-
- } else { /* Row-id not found, create a new one. */
- fts_trx_row_t row;
-
- row.doc_id = doc_id;
- row.state = state;
- row.fts_indexes = fts_indexes;
-
- rbt_add_node(rows, &parent, &row);
- }
-}
-
-/******************************************************************//**
-Notify the FTS system about an operation on an FTS-indexed table. */
-UNIV_INTERN
-void
-fts_trx_add_op(
-/*===========*/
- trx_t* trx, /*!< in: InnoDB transaction */
- dict_table_t* table, /*!< in: table */
- doc_id_t doc_id, /*!< in: new doc id */
- fts_row_state state, /*!< in: state of the row */
- ib_vector_t* fts_indexes) /*!< in: FTS indexes affected
- (NULL=all) */
-{
- fts_trx_table_t* tran_ftt;
- fts_trx_table_t* stmt_ftt;
-
- if (!trx->fts_trx) {
- trx->fts_trx = fts_trx_create(trx);
- }
-
- tran_ftt = fts_trx_init(trx, table, trx->fts_trx->savepoints);
- stmt_ftt = fts_trx_init(trx, table, trx->fts_trx->last_stmt);
-
- fts_trx_table_add_op(tran_ftt, doc_id, state, fts_indexes);
- 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
-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 */
-{
- int n_parsed;
- sel_node_t* node = static_cast<sel_node_t*>(row);
- doc_id_t* doc_id = static_cast<doc_id_t*>(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_a(dtype_get_mtype(type) == DATA_VARCHAR);
- ut_a(len > 0 && len < sizeof(buf));
-
- memcpy(buf, dfield_get_data(dfield), len);
- buf[len] = '\0';
-
- n_parsed = sscanf(buf, FTS_DOC_ID_FORMAT, doc_id);
- ut_a(n_parsed == 1);
-
- return(FALSE);
-}
-
-#ifdef FTS_CACHE_SIZE_DEBUG
-/******************************************************************//**
-Get the max cache size in bytes. If there is an error reading the
-value we simply print an error message here and return the default
-value to the caller.
-@return max cache size in bytes */
-static
-ulint
-fts_get_max_cache_size(
-/*===================*/
- trx_t* trx, /*!< in: transaction */
- fts_table_t* fts_table) /*!< in: table instance */
-{
- dberr_t error;
- fts_string_t value;
- ulint cache_size_in_mb;
-
- /* Set to the default value. */
- cache_size_in_mb = FTS_CACHE_SIZE_LOWER_LIMIT_IN_MB;
-
- /* We set the length of value to the max bytes it can hold. This
- information is used by the callback that reads the value. */
- value.f_n_char = 0;
- value.f_len = FTS_MAX_CONFIG_VALUE_LEN;
- value.f_str = ut_malloc(value.f_len + 1);
-
- error = fts_config_get_value(
- trx, fts_table, FTS_MAX_CACHE_SIZE_IN_MB, &value);
-
- if (error == DB_SUCCESS) {
-
- value.f_str[value.f_len] = 0;
- cache_size_in_mb = strtoul((char*) value.f_str, NULL, 10);
-
- if (cache_size_in_mb > FTS_CACHE_SIZE_UPPER_LIMIT_IN_MB) {
-
- ut_print_timestamp(stderr);
- fprintf(stderr, " InnoDB: Warning: FTS max cache size "
- " (%lu) out of range. Minimum value is "
- "%luMB and the maximum values is %luMB, "
- "setting cache size to upper limit\n",
- cache_size_in_mb,
- FTS_CACHE_SIZE_LOWER_LIMIT_IN_MB,
- FTS_CACHE_SIZE_UPPER_LIMIT_IN_MB);
-
- cache_size_in_mb = FTS_CACHE_SIZE_UPPER_LIMIT_IN_MB;
-
- } else if (cache_size_in_mb
- < FTS_CACHE_SIZE_LOWER_LIMIT_IN_MB) {
-
- ut_print_timestamp(stderr);
- fprintf(stderr, " InnoDB: Warning: FTS max cache size "
- " (%lu) out of range. Minimum value is "
- "%luMB and the maximum values is %luMB, "
- "setting cache size to lower limit\n",
- cache_size_in_mb,
- FTS_CACHE_SIZE_LOWER_LIMIT_IN_MB,
- FTS_CACHE_SIZE_UPPER_LIMIT_IN_MB);
-
- cache_size_in_mb = FTS_CACHE_SIZE_LOWER_LIMIT_IN_MB;
- }
- } else {
- ut_print_timestamp(stderr);
- fprintf(stderr, "InnoDB: Error: (%lu) reading max cache "
- "config value from config table\n", error);
- }
-
- ut_free(value.f_str);
-
- return(cache_size_in_mb * 1024 * 1024);
-}
-#endif
-
-#ifdef FTS_DOC_STATS_DEBUG
-/*********************************************************************//**
-Get the total number of words in the FTS for a particular FTS index.
-@return DB_SUCCESS if all OK else error code */
-UNIV_INTERN
-dberr_t
-fts_get_total_word_count(
-/*=====================*/
- trx_t* trx, /*!< in: transaction */
- dict_index_t* index, /*!< in: for this index */
- ulint* total) /* out: total words */
-{
- dberr_t error;
- fts_string_t value;
-
- *total = 0;
-
- /* We set the length of value to the max bytes it can hold. This
- information is used by the callback that reads the value. */
- value.f_n_char = 0;
- value.f_len = FTS_MAX_CONFIG_VALUE_LEN;
- value.f_str = static_cast<byte*>(ut_malloc(value.f_len + 1));
-
- error = fts_config_get_index_value(
- trx, index, FTS_TOTAL_WORD_COUNT, &value);
-
- if (error == DB_SUCCESS) {
-
- value.f_str[value.f_len] = 0;
- *total = strtoul((char*) value.f_str, NULL, 10);
- } else {
- ut_print_timestamp(stderr);
- fprintf(stderr, " InnoDB: Error: (%s) reading total words "
- "value from config table\n", ut_strerr(error));
- }
-
- ut_free(value.f_str);
-
- return(error);
-}
-#endif /* FTS_DOC_STATS_DEBUG */
-
-/*********************************************************************//**
-Update the next and last Doc ID in the CONFIG table to be the input
-"doc_id" value (+ 1). We would do so after each FTS index build or
-table truncate */
-UNIV_INTERN
-void
-fts_update_next_doc_id(
-/*===================*/
- trx_t* trx, /*!< in/out: transaction */
- const dict_table_t* table, /*!< in: table */
- const char* table_name, /*!< in: table name, or NULL */
- doc_id_t doc_id) /*!< in: DOC ID to set */
-{
- table->fts->cache->synced_doc_id = doc_id;
- table->fts->cache->next_doc_id = doc_id + 1;
-
- table->fts->cache->first_doc_id = table->fts->cache->next_doc_id;
-
- fts_update_sync_doc_id(
- table, table_name, table->fts->cache->synced_doc_id, trx);
-
-}
-
-/*********************************************************************//**
-Get the next available document id.
-@return DB_SUCCESS if OK */
-UNIV_INTERN
-dberr_t
-fts_get_next_doc_id(
-/*================*/
- const dict_table_t* table, /*!< in: table */
- doc_id_t* doc_id) /*!< out: new document id */
-{
- fts_cache_t* cache = table->fts->cache;
-
- /* If the Doc ID system has not yet been initialized, we
- will consult the CONFIG table and user table to re-establish
- the initial value of the Doc ID */
-
- if (cache->first_doc_id != 0 || !fts_init_doc_id(table)) {
- if (!DICT_TF2_FLAG_IS_SET(table, DICT_TF2_FTS_HAS_DOC_ID)) {
- *doc_id = FTS_NULL_DOC_ID;
- return(DB_SUCCESS);
- }
-
- /* Otherwise, simply increment the value in cache */
- mutex_enter(&cache->doc_id_lock);
- *doc_id = ++cache->next_doc_id;
- mutex_exit(&cache->doc_id_lock);
- } else {
- mutex_enter(&cache->doc_id_lock);
- *doc_id = cache->next_doc_id;
- mutex_exit(&cache->doc_id_lock);
- }
-
- return(DB_SUCCESS);
-}
-
-/*********************************************************************//**
-This function fetch the Doc ID from CONFIG table, and compare with
-the Doc ID supplied. And store the larger one to the CONFIG table.
-@return DB_SUCCESS if OK */
-static MY_ATTRIBUTE((nonnull))
-dberr_t
-fts_cmp_set_sync_doc_id(
-/*====================*/
- const dict_table_t* table, /*!< in: table */
- doc_id_t doc_id_cmp, /*!< in: Doc ID to compare */
- ibool read_only, /*!< in: TRUE if read the
- synced_doc_id only */
- doc_id_t* doc_id) /*!< out: larger document id
- after comparing "doc_id_cmp"
- to the one stored in CONFIG
- table */
-{
- trx_t* trx;
- pars_info_t* info;
- dberr_t error;
- fts_table_t fts_table;
- que_t* graph = NULL;
- fts_cache_t* cache = table->fts->cache;
-retry:
- ut_a(table->fts->doc_col != ULINT_UNDEFINED);
-
- fts_table.suffix = "CONFIG";
- fts_table.table_id = table->id;
- fts_table.type = FTS_COMMON_TABLE;
- fts_table.table = table;
-
- fts_table.parent = table->name;
-
- trx = trx_allocate_for_background();
-
- trx->op_info = "update the next FTS document id";
-
- info = pars_info_create();
-
- pars_info_bind_function(
- info, "my_func", fts_fetch_store_doc_id, doc_id);
-
- graph = fts_parse_sql(
- &fts_table, info,
- "DECLARE FUNCTION my_func;\n"
- "DECLARE CURSOR c IS SELECT value FROM \"%s\""
- " 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);
-
- fts_que_graph_free_check_lock(&fts_table, NULL, graph);
-
- // FIXME: We need to retry deadlock errors
- if (error != DB_SUCCESS) {
- goto func_exit;
- }
-
- if (read_only) {
- goto func_exit;
- }
-
- if (doc_id_cmp == 0 && *doc_id) {
- cache->synced_doc_id = *doc_id - 1;
- } else {
- cache->synced_doc_id = ut_max(doc_id_cmp, *doc_id);
- }
-
- mutex_enter(&cache->doc_id_lock);
- /* For each sync operation, we will add next_doc_id by 1,
- so to mark a sync operation */
- if (cache->next_doc_id < cache->synced_doc_id + 1) {
- cache->next_doc_id = cache->synced_doc_id + 1;
- }
- mutex_exit(&cache->doc_id_lock);
-
- if (doc_id_cmp > *doc_id) {
- error = fts_update_sync_doc_id(
- table, table->name, cache->synced_doc_id, trx);
- }
-
- *doc_id = cache->next_doc_id;
-
-func_exit:
-
- if (error == DB_SUCCESS) {
- fts_sql_commit(trx);
- } else {
- *doc_id = 0;
-
- ut_print_timestamp(stderr);
- fprintf(stderr, " InnoDB: Error: (%s) "
- "while getting next doc id.\n", ut_strerr(error));
-
- fts_sql_rollback(trx);
-
- if (error == DB_DEADLOCK) {
- os_thread_sleep(FTS_DEADLOCK_RETRY_WAIT);
- goto retry;
- }
- }
-
- trx_free_for_background(trx);
-
- return(error);
-}
-
-/*********************************************************************//**
-Update the last document id. This function could create a new
-transaction to update the last document id.
-@return DB_SUCCESS if OK */
-static
-dberr_t
-fts_update_sync_doc_id(
-/*===================*/
- const dict_table_t* table, /*!< in: table */
- const char* table_name, /*!< in: table name, or NULL */
- doc_id_t doc_id, /*!< in: last document id */
- trx_t* trx) /*!< in: update trx, or NULL */
-{
- 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;
-
- fts_table.suffix = "CONFIG";
- fts_table.table_id = table->id;
- fts_table.type = FTS_COMMON_TABLE;
- fts_table.table = table;
- if (table_name) {
- fts_table.parent = table_name;
- } else {
- fts_table.parent = table->name;
- }
-
- if (!trx) {
- trx = trx_allocate_for_background();
-
- trx->op_info = "setting last FTS document id";
- local_trx = TRUE;
- }
-
- info = pars_info_create();
-
- id_len = ut_snprintf(
- (char*) id, sizeof(id), FTS_DOC_ID_FORMAT, doc_id + 1);
-
- pars_info_bind_varchar_literal(info, "doc_id", id, id_len);
-
- graph = fts_parse_sql(
- &fts_table, info,
- "BEGIN "
- "UPDATE \"%s\" SET value = :doc_id"
- " WHERE key = 'synced_doc_id';");
-
- error = fts_eval_sql(trx, graph);
-
- fts_que_graph_free_check_lock(&fts_table, NULL, graph);
-
- if (local_trx) {
- if (error == DB_SUCCESS) {
- fts_sql_commit(trx);
- cache->synced_doc_id = doc_id;
- } else {
-
- ib_logf(IB_LOG_LEVEL_ERROR,
- "(%s) while updating last doc id.",
- ut_strerr(error));
-
- fts_sql_rollback(trx);
- }
- trx_free_for_background(trx);
- }
-
- return(error);
-}
-
-/*********************************************************************//**
-Create a new fts_doc_ids_t.
-@return new fts_doc_ids_t */
-UNIV_INTERN
-fts_doc_ids_t*
-fts_doc_ids_create(void)
-/*====================*/
-{
- fts_doc_ids_t* fts_doc_ids;
- mem_heap_t* heap = mem_heap_create(512);
-
- fts_doc_ids = static_cast<fts_doc_ids_t*>(
- mem_heap_alloc(heap, sizeof(*fts_doc_ids)));
-
- fts_doc_ids->self_heap = ib_heap_allocator_create(heap);
-
- fts_doc_ids->doc_ids = static_cast<ib_vector_t*>(ib_vector_create(
- fts_doc_ids->self_heap, sizeof(fts_update_t), 32));
-
- return(fts_doc_ids);
-}
-
-/*********************************************************************//**
-Free a fts_doc_ids_t. */
-
-void
-fts_doc_ids_free(
-/*=============*/
- fts_doc_ids_t* fts_doc_ids)
-{
- mem_heap_t* heap = static_cast<mem_heap_t*>(
- fts_doc_ids->self_heap->arg);
-
- memset(fts_doc_ids, 0, sizeof(*fts_doc_ids));
-
- mem_heap_free(heap);
-}
-
-/*********************************************************************//**
-Do commit-phase steps necessary for the insertion of a new row. */
-void
-fts_add(
-/*====*/
- fts_trx_table_t*ftt, /*!< in: FTS trx table */
- fts_trx_row_t* row) /*!< in: row */
-{
- dict_table_t* table = ftt->table;
- doc_id_t doc_id = row->doc_id;
-
- ut_a(row->state == FTS_INSERT || row->state == FTS_MODIFY);
-
- fts_add_doc_by_id(ftt, doc_id, row->fts_indexes);
-
- mutex_enter(&table->fts->cache->deleted_lock);
- ++table->fts->cache->added;
- mutex_exit(&table->fts->cache->deleted_lock);
-
- if (!DICT_TF2_FLAG_IS_SET(table, DICT_TF2_FTS_HAS_DOC_ID)
- && doc_id >= table->fts->cache->next_doc_id) {
- table->fts->cache->next_doc_id = doc_id + 1;
- }
-}
-
-/*********************************************************************//**
-Do commit-phase steps necessary for the deletion of a row.
-@return DB_SUCCESS or error code */
-static MY_ATTRIBUTE((nonnull, warn_unused_result))
-dberr_t
-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;
- dberr_t error = DB_SUCCESS;
- 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 */
- if (doc_id == FTS_NULL_DOC_ID) {
- ut_ad(!DICT_TF2_FLAG_IS_SET(table, DICT_TF2_FTS_HAS_DOC_ID));
- return(error);
- }
-
- ut_a(row->state == FTS_DELETE || row->state == FTS_MODIFY);
-
- FTS_INIT_FTS_TABLE(&fts_table, "DELETED", FTS_COMMON_TABLE, table);
-
- /* 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
- sync). Avoid any added counter accounting until the FTS cache
- is re-established and sync-ed */
- if (table->fts->fts_status & ADDED_TABLE_SYNCED
- && doc_id > cache->synced_doc_id) {
- mutex_enter(&table->fts->cache->deleted_lock);
-
- /* The Doc ID could belong to those left in
- ADDED table from last crash. So need to check
- if it is less than first_doc_id when we initialize
- the Doc ID system after reboot */
- if (doc_id >= table->fts->cache->first_doc_id
- && table->fts->cache->added > 0) {
- --table->fts->cache->added;
- }
-
- mutex_exit(&table->fts->cache->deleted_lock);
-
- /* Only if the row was really deleted. */
- ut_a(row->state == FTS_DELETE || row->state == FTS_MODIFY);
- }
-
- /* Note the deleted document for OPTIMIZE to purge. */
- if (error == DB_SUCCESS) {
-
- trx->op_info = "adding doc id to FTS DELETED";
-
- info->graph_owns_us = TRUE;
-
- fts_table.suffix = "DELETED";
-
- graph = fts_parse_sql(
- &fts_table,
- info,
- "BEGIN INSERT INTO \"%s\" VALUES (:doc_id);");
-
- error = fts_eval_sql(trx, graph);
-
- fts_que_graph_free(graph);
- } else {
- pars_info_free(info);
- }
-
- /* Increment the total deleted count, this is used to calculate the
- number of documents indexed. */
- if (error == DB_SUCCESS) {
- mutex_enter(&table->fts->cache->deleted_lock);
-
- ++table->fts->cache->deleted;
-
- mutex_exit(&table->fts->cache->deleted_lock);
- }
-
- return(error);
-}
-
-/*********************************************************************//**
-Do commit-phase steps necessary for the modification of a row.
-@return DB_SUCCESS or error code */
-static MY_ATTRIBUTE((nonnull, warn_unused_result))
-dberr_t
-fts_modify(
-/*=======*/
- fts_trx_table_t* ftt, /*!< in: FTS trx table */
- fts_trx_row_t* row) /*!< in: row */
-{
- dberr_t error;
-
- ut_a(row->state == FTS_MODIFY);
-
- error = fts_delete(ftt, row);
-
- if (error == DB_SUCCESS) {
- fts_add(ftt, row);
- }
-
- return(error);
-}
-
-/*********************************************************************//**
-Create a new document id.
-@return DB_SUCCESS if all went well else error */
-UNIV_INTERN
-dberr_t
-fts_create_doc_id(
-/*==============*/
- dict_table_t* table, /*!< in: row is of this table. */
- dtuple_t* row, /* in/out: add doc id value to this
- row. This is the current row that is
- being inserted. */
- mem_heap_t* heap) /*!< in: heap */
-{
- doc_id_t doc_id;
- dberr_t error = DB_SUCCESS;
-
- ut_a(table->fts->doc_col != ULINT_UNDEFINED);
-
- if (!DICT_TF2_FLAG_IS_SET(table, DICT_TF2_FTS_HAS_DOC_ID)) {
- if (table->fts->cache->first_doc_id == FTS_NULL_DOC_ID) {
- error = fts_get_next_doc_id(table, &doc_id);
- }
- return(error);
- }
-
- error = fts_get_next_doc_id(table, &doc_id);
-
- if (error == DB_SUCCESS) {
- dfield_t* dfield;
- doc_id_t* write_doc_id;
-
- ut_a(doc_id > 0);
-
- dfield = dtuple_get_nth_field(row, table->fts->doc_col);
- write_doc_id = static_cast<doc_id_t*>(
- mem_heap_alloc(heap, sizeof(*write_doc_id)));
-
- ut_a(doc_id != FTS_NULL_DOC_ID);
- ut_a(sizeof(doc_id) == dfield->type.len);
- fts_write_doc_id((byte*) write_doc_id, doc_id);
-
- dfield_set_data(dfield, write_doc_id, sizeof(*write_doc_id));
- }
-
- return(error);
-}
-
-/*********************************************************************//**
-The given transaction is about to be committed; do whatever is necessary
-from the FTS system's POV.
-@return DB_SUCCESS or error code */
-static MY_ATTRIBUTE((nonnull, warn_unused_result))
-dberr_t
-fts_commit_table(
-/*=============*/
- fts_trx_table_t* ftt) /*!< in: FTS table to commit*/
-{
- const ib_rbt_node_t* node;
- ib_rbt_t* rows;
- dberr_t error = DB_SUCCESS;
- fts_cache_t* cache = ftt->table->fts->cache;
- trx_t* trx = trx_allocate_for_background();
-
- rows = ftt->rows;
-
- ftt->fts_trx->trx = trx;
-
- if (cache->get_docs == NULL) {
- rw_lock_x_lock(&cache->init_lock);
- if (cache->get_docs == NULL) {
- cache->get_docs = fts_get_docs_create(cache);
- }
- rw_lock_x_unlock(&cache->init_lock);
- }
-
- for (node = rbt_first(rows);
- node != NULL && error == DB_SUCCESS;
- node = rbt_next(rows, node)) {
-
- fts_trx_row_t* row = rbt_value(fts_trx_row_t, node);
-
- switch (row->state) {
- case FTS_INSERT:
- fts_add(ftt, row);
- break;
-
- case FTS_MODIFY:
- error = fts_modify(ftt, row);
- break;
-
- case FTS_DELETE:
- error = fts_delete(ftt, row);
- break;
-
- default:
- ut_error;
- }
- }
-
- fts_sql_commit(trx);
-
- trx_free_for_background(trx);
-
- return(error);
-}
-
-/*********************************************************************//**
-The given transaction is about to be committed; do whatever is necessary
-from the FTS system's POV.
-@return DB_SUCCESS or error code */
-UNIV_INTERN
-dberr_t
-fts_commit(
-/*=======*/
- trx_t* trx) /*!< in: transaction */
-{
- const ib_rbt_node_t* node;
- dberr_t error;
- ib_rbt_t* tables;
- fts_savepoint_t* savepoint;
-
- savepoint = static_cast<fts_savepoint_t*>(
- ib_vector_last(trx->fts_trx->savepoints));
- tables = savepoint->tables;
-
- for (node = rbt_first(tables), error = DB_SUCCESS;
- node != NULL && error == DB_SUCCESS;
- node = rbt_next(tables, node)) {
-
- fts_trx_table_t** ftt;
-
- ftt = rbt_value(fts_trx_table_t*, node);
-
- error = fts_commit_table(*ftt);
- }
-
- return(error);
-}
-
-/*********************************************************************//**
-Initialize a document. */
-UNIV_INTERN
-void
-fts_doc_init(
-/*=========*/
- fts_doc_t* doc) /*!< in: doc to initialize */
-{
- mem_heap_t* heap = mem_heap_create(32);
-
- memset(doc, 0, sizeof(*doc));
-
- doc->self_heap = ib_heap_allocator_create(heap);
-}
-
-/*********************************************************************//**
-Free document. */
-UNIV_INTERN
-void
-fts_doc_free(
-/*=========*/
- fts_doc_t* doc) /*!< in: document */
-{
- mem_heap_t* heap = static_cast<mem_heap_t*>(doc->self_heap->arg);
-
- if (doc->tokens) {
- rbt_free(doc->tokens);
- }
-
-#ifdef UNIV_DEBUG
- memset(doc, 0, sizeof(*doc));
-#endif /* UNIV_DEBUG */
-
- mem_heap_free(heap);
-}
-
-/*********************************************************************//**
-Callback function for fetch that stores a row id to the location pointed.
-The column's type must be DATA_FIXBINARY, DATA_BINARY_TYPE, length = 8.
-@return always returns NULL */
-UNIV_INTERN
-void*
-fts_fetch_row_id(
-/*=============*/
- void* row, /*!< in: sel_node_t* */
- void* user_arg) /*!< in: data pointer */
-{
- sel_node_t* node = static_cast<sel_node_t*>(row);
-
- dfield_t* dfield = que_node_get_val(node->select_list);
- dtype_t* type = dfield_get_type(dfield);
- ulint len = dfield_get_len(dfield);
-
- ut_a(dtype_get_mtype(type) == DATA_FIXBINARY);
- ut_a(dtype_get_prtype(type) & DATA_BINARY_TYPE);
- ut_a(len == 8);
-
- memcpy(user_arg, dfield_get_data(dfield), 8);
-
- return(NULL);
-}
-
-/*********************************************************************//**
-Callback function for fetch that stores the text of an FTS document,
-converting each column to UTF-16.
-@return always FALSE */
-UNIV_INTERN
-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<sel_node_t*>(row);
- fts_doc_t* result_doc = static_cast<fts_doc_t*>(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) {
- ulint prtype = dfield->type.prtype;
- doc_charset = innobase_get_fts_charset(
- (int)(prtype & DATA_MYSQL_TYPE_MASK),
- (uint) dtype_get_charset_coll(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<byte*>(
- dfield_get_data(dfield));
-
- doc.text.f_len = len;
- }
-
- if (field_no == 0) {
- fts_tokenize_document(&doc, result_doc);
- } else {
- fts_tokenize_document_next(&doc, doc_len, result_doc);
- }
-
- 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);
-
- return(FALSE);
-}
-
-/*********************************************************************//**
-fetch and tokenize the document. */
-static
-void
-fts_fetch_doc_from_rec(
-/*===================*/
- fts_get_doc_t* get_doc, /*!< in: FTS index's get_doc struct */
- dict_index_t* clust_index, /*!< in: cluster index */
- btr_pcur_t* pcur, /*!< in: cursor whose position
- has been stored */
- ulint* offsets, /*!< in: offsets */
- fts_doc_t* doc) /*!< out: fts doc to hold parsed
- documents */
-{
- dict_index_t* index;
- dict_table_t* table;
- const rec_t* clust_rec;
- ulint num_field;
- const dict_field_t* ifield;
- const dict_col_t* col;
- ulint clust_pos;
- ulint i;
- ulint doc_len = 0;
- ulint processed_doc = 0;
-
- if (!get_doc) {
- return;
- }
-
- index = get_doc->index_cache->index;
- table = get_doc->index_cache->index->table;
-
- clust_rec = btr_pcur_get_rec(pcur);
-
- num_field = dict_index_get_n_fields(index);
-
- for (i = 0; i < num_field; i++) {
- ifield = dict_index_get_nth_field(index, i);
- col = dict_field_get_col(ifield);
- clust_pos = dict_col_get_clust_pos(col, clust_index);
-
- if (!get_doc->index_cache->charset) {
- ulint prtype = ifield->col->prtype;
-
- get_doc->index_cache->charset =
- innobase_get_fts_charset(
- (int) (prtype & DATA_MYSQL_TYPE_MASK),
- (uint) dtype_get_charset_coll(prtype));
- }
-
- if (rec_offs_nth_extern(offsets, clust_pos)) {
- doc->text.f_str =
- btr_rec_copy_externally_stored_field(
- clust_rec, offsets,
- dict_table_zip_size(table),
- clust_pos, &doc->text.f_len,
- static_cast<mem_heap_t*>(
- doc->self_heap->arg),
- NULL);
- } else {
- doc->text.f_str = (byte*) rec_get_nth_field(
- clust_rec, offsets, clust_pos,
- &doc->text.f_len);
- }
-
- doc->found = TRUE;
- doc->charset = get_doc->index_cache->charset;
-
- /* Null Field */
- if (doc->text.f_len == UNIV_SQL_NULL || doc->text.f_len == 0) {
- continue;
- }
-
- if (processed_doc == 0) {
- fts_tokenize_document(doc, NULL);
- } else {
- fts_tokenize_document_next(doc, doc_len, NULL);
- }
-
- processed_doc++;
- doc_len += doc->text.f_len + 1;
- }
-}
-
-/*********************************************************************//**
-This function fetches the document inserted during the committing
-transaction, and tokenize the inserted text data and insert into
-FTS auxiliary table and its cache.
-@return TRUE if successful */
-static
-ulint
-fts_add_doc_by_id(
-/*==============*/
- fts_trx_table_t*ftt, /*!< in: FTS trx table */
- doc_id_t doc_id, /*!< in: doc id */
- ib_vector_t* fts_indexes MY_ATTRIBUTE((unused)))
- /*!< in: affected fts indexes */
-{
- mtr_t mtr;
- mem_heap_t* heap;
- btr_pcur_t pcur;
- dict_table_t* table;
- dtuple_t* tuple;
- dfield_t* dfield;
- fts_get_doc_t* get_doc;
- doc_id_t temp_doc_id;
- dict_index_t* clust_index;
- dict_index_t* fts_id_index;
- ibool is_id_cluster;
- fts_cache_t* cache = ftt->table->fts->cache;
-
- ut_ad(cache->get_docs);
-
- /* If Doc ID has been supplied by the user, then the table
- might not yet be sync-ed */
-
- if (!(ftt->table->fts->fts_status & ADDED_TABLE_SYNCED)) {
- fts_init_index(ftt->table, FALSE);
- }
-
- /* Get the first FTS index's get_doc */
- get_doc = static_cast<fts_get_doc_t*>(
- ib_vector_get(cache->get_docs, 0));
- ut_ad(get_doc);
-
- table = get_doc->index_cache->index->table;
-
- heap = mem_heap_create(512);
-
- clust_index = dict_table_get_first_index(table);
- fts_id_index = dict_table_get_index_on_name(
- table, FTS_DOC_ID_INDEX_NAME);
-
- /* Check whether the index on FTS_DOC_ID is cluster index */
- is_id_cluster = (clust_index == fts_id_index);
-
- mtr_start(&mtr);
- btr_pcur_init(&pcur);
-
- /* Search based on Doc ID. Here, we'll need to consider the case
- when there is no primary index on Doc ID */
- tuple = dtuple_create(heap, 1);
- dfield = dtuple_get_nth_field(tuple, 0);
- dfield->type.mtype = DATA_INT;
- dfield->type.prtype = DATA_NOT_NULL | DATA_UNSIGNED | DATA_BINARY_TYPE;
-
- mach_write_to_8((byte*) &temp_doc_id, doc_id);
- dfield_set_data(dfield, &temp_doc_id, sizeof(temp_doc_id));
-
- btr_pcur_open_with_no_init(
- fts_id_index, tuple, PAGE_CUR_LE, BTR_SEARCH_LEAF,
- &pcur, 0, &mtr);
-
- /* If we have a match, add the data to doc structure */
- if (btr_pcur_get_low_match(&pcur) == 1) {
- const rec_t* rec;
- btr_pcur_t* doc_pcur;
- const rec_t* clust_rec;
- btr_pcur_t clust_pcur;
- ulint* offsets = NULL;
- ulint num_idx = ib_vector_size(cache->get_docs);
-
- rec = btr_pcur_get_rec(&pcur);
-
- /* Doc could be deleted */
- if (page_rec_is_infimum(rec)
- || rec_get_deleted_flag(rec, dict_table_is_comp(table))) {
-
- goto func_exit;
- }
-
- if (is_id_cluster) {
- clust_rec = rec;
- doc_pcur = &pcur;
- } else {
- dtuple_t* clust_ref;
- ulint n_fields;
-
- btr_pcur_init(&clust_pcur);
- n_fields = dict_index_get_n_unique(clust_index);
-
- clust_ref = dtuple_create(heap, n_fields);
- dict_index_copy_types(clust_ref, clust_index, n_fields);
-
- row_build_row_ref_in_tuple(
- clust_ref, rec, fts_id_index, NULL, NULL);
-
- btr_pcur_open_with_no_init(
- clust_index, clust_ref, PAGE_CUR_LE,
- BTR_SEARCH_LEAF, &clust_pcur, 0, &mtr);
-
- doc_pcur = &clust_pcur;
- clust_rec = btr_pcur_get_rec(&clust_pcur);
-
- }
-
- offsets = rec_get_offsets(clust_rec, clust_index,
- NULL, ULINT_UNDEFINED, &heap);
-
- for (ulint i = 0; i < num_idx; ++i) {
- fts_doc_t doc;
- dict_table_t* table;
- fts_get_doc_t* get_doc;
-
- get_doc = static_cast<fts_get_doc_t*>(
- ib_vector_get(cache->get_docs, i));
-
- table = get_doc->index_cache->index->table;
-
- fts_doc_init(&doc);
-
- fts_fetch_doc_from_rec(
- get_doc, clust_index, doc_pcur, offsets, &doc);
-
- if (doc.found) {
- ibool success MY_ATTRIBUTE((unused));
-
- btr_pcur_store_position(doc_pcur, &mtr);
- mtr_commit(&mtr);
-
- rw_lock_x_lock(&table->fts->cache->lock);
-
- if (table->fts->cache->stopword_info.status
- & STOPWORD_NOT_INIT) {
- fts_load_stopword(table, NULL, NULL,
- NULL, TRUE, TRUE);
- }
-
- fts_cache_add_doc(
- table->fts->cache,
- get_doc->index_cache,
- doc_id, doc.tokens);
-
- bool need_sync = false;
- if ((cache->total_size > fts_max_cache_size / 10
- || fts_need_sync)
- && !cache->sync->in_progress) {
- need_sync = true;
- }
-
- rw_lock_x_unlock(&table->fts->cache->lock);
-
- DBUG_EXECUTE_IF(
- "fts_instrument_sync",
- fts_optimize_request_sync_table(table);
- os_event_wait(cache->sync->event);
- );
-
- DBUG_EXECUTE_IF(
- "fts_instrument_sync_debug",
- fts_sync(cache->sync, true, true, false);
- );
-
- DEBUG_SYNC_C("fts_instrument_sync_request");
- DBUG_EXECUTE_IF(
- "fts_instrument_sync_request",
- fts_optimize_request_sync_table(table);
- );
-
- if (need_sync) {
- fts_optimize_request_sync_table(table);
- }
-
- mtr_start(&mtr);
-
- if (i < num_idx - 1) {
-
- success = btr_pcur_restore_position(
- BTR_SEARCH_LEAF, doc_pcur,
- &mtr);
-
- ut_ad(success);
- }
- }
-
- fts_doc_free(&doc);
- }
-
- if (!is_id_cluster) {
- btr_pcur_close(doc_pcur);
- }
- }
-func_exit:
- mtr_commit(&mtr);
-
- btr_pcur_close(&pcur);
-
- mem_heap_free(heap);
- return(TRUE);
-}
-
-
-/*********************************************************************//**
-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<sel_node_t*>(row);
- ulint* value = static_cast<ulint*>(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 = static_cast<ulint>(mach_read_from_4(
- static_cast<const byte*>(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 */
-UNIV_INTERN
-doc_id_t
-fts_get_max_doc_id(
-/*===============*/
- dict_table_t* table) /*!< in: user table */
-{
- dict_index_t* index;
- dict_field_t* dfield MY_ATTRIBUTE((unused)) = NULL;
- doc_id_t doc_id = 0;
- mtr_t mtr;
- btr_pcur_t pcur;
-
- index = dict_table_get_index_on_name(table, FTS_DOC_ID_INDEX_NAME);
-
- if (!index) {
- return(0);
- }
-
- dfield = dict_index_get_nth_field(index, 0);
-
-#if 0 /* This can fail when renaming a column to FTS_DOC_ID_COL_NAME. */
- ut_ad(innobase_strcasecmp(FTS_DOC_ID_COL_NAME, dfield->name) == 0);
-#endif
-
- mtr_start(&mtr);
-
- /* fetch the largest indexes value */
- btr_pcur_open_at_index_side(
- false, index, BTR_SEARCH_LEAF, &pcur, true, 0, &mtr);
-
- if (!page_is_empty(btr_pcur_get_page(&pcur))) {
- const rec_t* rec = NULL;
- ulint offsets_[REC_OFFS_NORMAL_SIZE];
- ulint* offsets = offsets_;
- mem_heap_t* heap = NULL;
- ulint len;
- const void* data;
-
- rec_offs_init(offsets_);
-
- do {
- rec = btr_pcur_get_rec(&pcur);
-
- if (page_rec_is_user_rec(rec)) {
- break;
- }
- } while (btr_pcur_move_to_prev(&pcur, &mtr));
-
- if (!rec) {
- goto func_exit;
- }
-
- offsets = rec_get_offsets(
- rec, index, offsets, ULINT_UNDEFINED, &heap);
-
- data = rec_get_nth_field(rec, offsets, 0, &len);
-
- doc_id = static_cast<doc_id_t>(fts_read_doc_id(
- static_cast<const byte*>(data)));
- }
-
-func_exit:
- btr_pcur_close(&pcur);
- mtr_commit(&mtr);
- return(doc_id);
-}
-
-/*********************************************************************//**
-Fetch document with the given document id.
-@return DB_SUCCESS if OK else error */
-UNIV_INTERN
-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_allocate_for_background();
- 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, TRUE, "table_name", index->table_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_COL_NAME));
- } 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_COL_NAME,
- select_str, FTS_DOC_ID_COL_NAME));
- }
- if (get_doc) {
- get_doc->get_document_graph = graph;
- }
- } else {
- graph = get_doc->get_document_graph;
- }
-
- error = fts_eval_sql(trx, graph);
-
- if (error == DB_SUCCESS) {
- fts_sql_commit(trx);
- } else {
- fts_sql_rollback(trx);
- }
-
- trx_free_for_background(trx);
-
- if (!get_doc) {
- fts_que_graph_free(graph);
- }
-
- return(error);
-}
-
-/*********************************************************************//**
-Write out a single word's data as new entry/entries in the INDEX table.
-@return DB_SUCCESS if all OK. */
-UNIV_INTERN
-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 */
-{
- pars_info_t* info;
- dberr_t error;
- ib_uint32_t doc_count;
- ib_time_t start_time;
- doc_id_t last_doc_id;
- doc_id_t first_doc_id;
-
- if (*graph) {
- info = (*graph)->info;
- } else {
- info = pars_info_create();
- }
-
- 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 \"%s\" VALUES "
- "(:token, :first_doc_id,"
- " :last_doc_id, :doc_count, :ilist);");
- }
-
- start_time = ut_time();
- error = fts_eval_sql(trx, *graph);
- elapsed_time += ut_time() - start_time;
- ++n_nodes;
-
- return(error);
-}
-
-/*********************************************************************//**
-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 */
-{
- ulint i;
- pars_info_t* info;
- que_t* graph;
- fts_table_t fts_table;
- 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);
-
- ib_vector_sort(doc_ids, fts_update_doc_id_cmp);
-
- 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);
-
- graph = fts_parse_sql(
- &fts_table,
- info,
- "BEGIN INSERT INTO \"%s\" VALUES (:doc_id);");
-
- for (i = 0; i < n_elems && error == DB_SUCCESS; ++i) {
- fts_update_t* update;
- doc_id_t write_doc_id;
-
- update = static_cast<fts_update_t*>(ib_vector_get(doc_ids, i));
-
- /* Convert to "storage" byte order. */
- fts_write_doc_id((byte*) &write_doc_id, update->doc_id);
- fts_bind_doc_id(info, "doc_id", &write_doc_id);
-
- error = fts_eval_sql(sync->trx, graph);
- }
-
- fts_que_graph_free(graph);
-
- return(error);
-}
-
-/** Write the words and ilist to disk.
-@param[in,out] trx transaction
-@param[in] index_cache index cache
-@param[in] unlock_cache whether unlock cache when write node
-@return DB_SUCCESS if all went well else error code */
-static MY_ATTRIBUTE((nonnull, warn_unused_result))
-dberr_t
-fts_sync_write_words(
- trx_t* trx,
- fts_index_cache_t* index_cache,
- bool unlock_cache)
-{
- fts_table_t fts_table;
- ulint n_nodes = 0;
- ulint n_words = 0;
- const ib_rbt_node_t* rbt_node;
- dberr_t error = DB_SUCCESS;
- ibool print_error = FALSE;
- dict_table_t* table = index_cache->index->table;
-#ifdef FTS_DOC_STATS_DEBUG
- ulint n_new_words = 0;
-#endif /* FTS_DOC_STATS_DEBUG */
-
- FTS_INIT_INDEX_TABLE(
- &fts_table, NULL, FTS_INDEX_TABLE, index_cache->index);
-
- n_words = rbt_size(index_cache->words);
-
- /* We iterate over the entire tree, even if there is an error,
- since we want to free the memory used during caching. */
- for (rbt_node = rbt_first(index_cache->words);
- rbt_node;
- rbt_node = rbt_next(index_cache->words, rbt_node)) {
-
- ulint i;
- ulint selected;
- fts_tokenizer_word_t* word;
-
- word = rbt_value(fts_tokenizer_word_t, rbt_node);
-
- selected = fts_select_index(
- index_cache->charset, word->text.f_str,
- word->text.f_len);
-
- fts_table.suffix = fts_get_suffix(selected);
-
-#ifdef FTS_DOC_STATS_DEBUG
- /* Check if the word exists in the FTS index and if not
- then we need to increment the total word count stats. */
- if (error == DB_SUCCESS && fts_enable_diag_print) {
- ibool found = FALSE;
-
- error = fts_is_word_in_index(
- trx,
- &index_cache->sel_graph[selected],
- &fts_table,
- &word->text, &found);
-
- if (error == DB_SUCCESS && !found) {
-
- ++n_new_words;
- }
- }
-#endif /* FTS_DOC_STATS_DEBUG */
-
- /* We iterate over all the nodes even if there was an error */
- for (i = 0; i < ib_vector_size(word->nodes); ++i) {
-
- fts_node_t* fts_node = static_cast<fts_node_t*>(
- ib_vector_get(word->nodes, i));
-
- if (fts_node->synced) {
- continue;
- } else {
- fts_node->synced = true;
- }
-
- /*FIXME: we need to handle the error properly. */
- if (error == DB_SUCCESS) {
- if (unlock_cache) {
- rw_lock_x_unlock(
- &table->fts->cache->lock);
- }
-
- error = fts_write_node(
- trx,
- &index_cache->ins_graph[selected],
- &fts_table, &word->text, fts_node);
-
- DEBUG_SYNC_C("fts_write_node");
- DBUG_EXECUTE_IF("fts_write_node_crash",
- DBUG_SUICIDE(););
-
- DBUG_EXECUTE_IF("fts_instrument_sync_sleep",
- os_thread_sleep(1000000);
- );
-
- if (unlock_cache) {
- rw_lock_x_lock(
- &table->fts->cache->lock);
- }
- }
- }
-
- n_nodes += ib_vector_size(word->nodes);
-
- if (error != DB_SUCCESS && !print_error) {
- ut_print_timestamp(stderr);
- fprintf(stderr, " InnoDB: Error (%s) writing "
- "word node to FTS auxiliary index "
- "table.\n", ut_strerr(error));
-
- print_error = TRUE;
- }
- }
-
-#ifdef FTS_DOC_STATS_DEBUG
- if (error == DB_SUCCESS && n_new_words > 0 && fts_enable_diag_print) {
- fts_table_t fts_table;
-
- FTS_INIT_FTS_TABLE(&fts_table, NULL, FTS_COMMON_TABLE, table);
-
- /* Increment the total number of words in the FTS index */
- error = fts_config_increment_index_value(
- trx, index_cache->index, FTS_TOTAL_WORD_COUNT,
- n_new_words);
- }
-#endif /* FTS_DOC_STATS_DEBUG */
-
- if (fts_enable_diag_print) {
- printf("Avg number of nodes: %lf\n",
- (double) n_nodes / (double) (n_words > 1 ? n_words : 1));
- }
-
- return(error);
-}
-
-#ifdef FTS_DOC_STATS_DEBUG
-/*********************************************************************//**
-Write a single documents statistics to disk.
-@return DB_SUCCESS if all went well else error code */
-static MY_ATTRIBUTE((nonnull, warn_unused_result))
-dberr_t
-fts_sync_write_doc_stat(
-/*====================*/
- trx_t* trx, /*!< in: transaction */
- dict_index_t* index, /*!< in: index */
- que_t** graph, /* out: query graph */
- const fts_doc_stats_t* doc_stat) /*!< in: doc stats to write */
-{
- pars_info_t* info;
- doc_id_t doc_id;
- dberr_t error = DB_SUCCESS;
- ib_uint32_t word_count;
-
- if (*graph) {
- info = (*graph)->info;
- } else {
- info = pars_info_create();
- }
-
- /* Convert to "storage" byte order. */
- mach_write_to_4((byte*) &word_count, doc_stat->word_count);
- pars_info_bind_int4_literal(
- info, "count", (const ib_uint32_t*) &word_count);
-
- /* Convert to "storage" byte order. */
- fts_write_doc_id((byte*) &doc_id, doc_stat->doc_id);
- fts_bind_doc_id(info, "doc_id", &doc_id);
-
- if (!*graph) {
- fts_table_t fts_table;
-
- FTS_INIT_INDEX_TABLE(
- &fts_table, "DOC_ID", FTS_INDEX_TABLE, index);
-
- *graph = fts_parse_sql(
- &fts_table,
- info,
- "BEGIN INSERT INTO \"%s\" VALUES (:doc_id, :count);");
- }
-
- for (;;) {
- error = fts_eval_sql(trx, *graph);
-
- if (error == DB_SUCCESS) {
-
- break; /* Exit the loop. */
- } else {
- ut_print_timestamp(stderr);
-
- if (error == DB_LOCK_WAIT_TIMEOUT) {
- fprintf(stderr, " InnoDB: Warning: lock wait "
- "timeout writing to FTS doc_id. "
- "Retrying!\n");
-
- trx->error_state = DB_SUCCESS;
- } else {
- fprintf(stderr, " InnoDB: Error: (%s) "
- "while writing to FTS doc_id.\n",
- ut_strerr(error));
-
- break; /* Exit the loop. */
- }
- }
- }
-
- return(error);
-}
-
-/*********************************************************************//**
-Write document statistics to disk.
-@return DB_SUCCESS if all OK */
-static
-ulint
-fts_sync_write_doc_stats(
-/*=====================*/
- trx_t* trx, /*!< in: transaction */
- const fts_index_cache_t*index_cache) /*!< in: index cache */
-{
- dberr_t error = DB_SUCCESS;
- que_t* graph = NULL;
- fts_doc_stats_t* doc_stat;
-
- if (ib_vector_is_empty(index_cache->doc_stats)) {
- return(DB_SUCCESS);
- }
-
- doc_stat = static_cast<ts_doc_stats_t*>(
- ib_vector_pop(index_cache->doc_stats));
-
- while (doc_stat) {
- error = fts_sync_write_doc_stat(
- trx, index_cache->index, &graph, doc_stat);
-
- if (error != DB_SUCCESS) {
- break;
- }
-
- if (ib_vector_is_empty(index_cache->doc_stats)) {
- break;
- }
-
- doc_stat = static_cast<ts_doc_stats_t*>(
- ib_vector_pop(index_cache->doc_stats));
- }
-
- if (graph != NULL) {
- fts_que_graph_free_check_lock(NULL, index_cache, graph);
- }
-
- return(error);
-}
-
-/*********************************************************************//**
-Callback to check the existince of a word.
-@return always return NULL */
-static
-ibool
-fts_lookup_word(
-/*============*/
- void* row, /*!< in: sel_node_t* */
- void* user_arg) /*!< in: fts_doc_t* */
-{
-
- que_node_t* exp;
- sel_node_t* node = static_cast<sel_node_t*>(row);
- ibool* found = static_cast<ibool*>(user_arg);
-
- exp = node->select_list;
-
- while (exp) {
- dfield_t* dfield = que_node_get_val(exp);
- ulint len = dfield_get_len(dfield);
-
- if (len != UNIV_SQL_NULL && len != 0) {
- *found = TRUE;
- }
-
- exp = que_node_get_next(exp);
- }
-
- return(FALSE);
-}
-
-/*********************************************************************//**
-Check whether a particular word (term) exists in the FTS index.
-@return DB_SUCCESS if all went well else error code */
-static
-dberr_t
-fts_is_word_in_index(
-/*=================*/
- trx_t* trx, /*!< in: FTS query state */
- que_t** graph, /* out: Query graph */
- fts_table_t* fts_table, /*!< in: table instance */
- const fts_string_t*
- word, /*!< in: the word to check */
- ibool* found) /* out: TRUE if exists */
-{
- pars_info_t* info;
- dberr_t error;
-
- trx->op_info = "looking up word in FTS index";
-
- if (*graph) {
- info = (*graph)->info;
- } else {
- info = pars_info_create();
- }
-
- pars_info_bind_function(info, "my_func", fts_lookup_word, found);
- pars_info_bind_varchar_literal(info, "word", word->f_str, word->f_len);
-
- if (*graph == NULL) {
- *graph = fts_parse_sql(
- fts_table,
- info,
- "DECLARE FUNCTION my_func;\n"
- "DECLARE CURSOR c IS"
- " SELECT doc_count\n"
- " FROM \"%s\"\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 {
- ut_print_timestamp(stderr);
-
- if (error == DB_LOCK_WAIT_TIMEOUT) {
- fprintf(stderr, " InnoDB: Warning: lock wait "
- "timeout reading FTS index. "
- "Retrying!\n");
-
- trx->error_state = DB_SUCCESS;
- } else {
- fprintf(stderr, " InnoDB: Error: (%s) "
- "while reading FTS index.\n",
- ut_strerr(error));
-
- break; /* Exit the loop. */
- }
- }
- }
-
- return(error);
-}
-#endif /* FTS_DOC_STATS_DEBUG */
-
-/*********************************************************************//**
-Begin Sync, create transaction, acquire locks, etc. */
-static
-void
-fts_sync_begin(
-/*===========*/
- fts_sync_t* sync) /*!< in: sync state */
-{
- fts_cache_t* cache = sync->table->fts->cache;
-
- n_nodes = 0;
- elapsed_time = 0;
-
- sync->start_time = ut_time();
-
- sync->trx = trx_allocate_for_background();
-
- if (fts_enable_diag_print) {
- ib_logf(IB_LOG_LEVEL_INFO,
- "FTS SYNC for table %s, deleted count: %ld size: "
- "%lu bytes",
- sync->table->name,
- ib_vector_size(cache->deleted_doc_ids),
- cache->total_size);
- }
-}
-
-/*********************************************************************//**
-Run SYNC on the table, i.e., write out data from the index specific
-cache to the FTS aux INDEX table and FTS aux doc id stats table.
-@return DB_SUCCESS if all OK */
-static MY_ATTRIBUTE((nonnull, warn_unused_result))
-dberr_t
-fts_sync_index(
-/*===========*/
- fts_sync_t* sync, /*!< in: sync state */
- fts_index_cache_t* index_cache) /*!< in: index cache */
-{
- trx_t* trx = sync->trx;
- dberr_t error = DB_SUCCESS;
-
- trx->op_info = "doing SYNC index";
-
- if (fts_enable_diag_print) {
- ib_logf(IB_LOG_LEVEL_INFO,
- "SYNC words: %ld", rbt_size(index_cache->words));
- }
-
- ut_ad(rbt_validate(index_cache->words));
-
- error = fts_sync_write_words(sync->trx, index_cache, sync->unlock_cache);
-
-#ifdef FTS_DOC_STATS_DEBUG
- /* FTS_RESOLVE: the word counter info in auxiliary table "DOC_ID"
- is not used currently for ranking. We disable fts_sync_write_doc_stats()
- for now */
- /* Write the per doc statistics that will be used for ranking. */
- if (error == DB_SUCCESS) {
-
- error = fts_sync_write_doc_stats(trx, index_cache);
- }
-#endif /* FTS_DOC_STATS_DEBUG */
-
- return(error);
-}
-
-/** Check if index cache has been synced completely
-@param[in,out] index_cache index cache
-@return true if index is synced, otherwise false. */
-static
-bool
-fts_sync_index_check(
- fts_index_cache_t* index_cache)
-{
- const ib_rbt_node_t* rbt_node;
-
- for (rbt_node = rbt_first(index_cache->words);
- rbt_node != NULL;
- rbt_node = rbt_next(index_cache->words, rbt_node)) {
-
- fts_tokenizer_word_t* word;
- word = rbt_value(fts_tokenizer_word_t, rbt_node);
-
- fts_node_t* fts_node;
- fts_node = static_cast<fts_node_t*>(ib_vector_last(word->nodes));
-
- if (!fts_node->synced) {
- return(false);
- }
- }
-
- return(true);
-}
-
-/** Reset synced flag in index cache when rollback
-@param[in,out] index_cache index cache */
-static
-void
-fts_sync_index_reset(
- fts_index_cache_t* index_cache)
-{
- const ib_rbt_node_t* rbt_node;
-
- for (rbt_node = rbt_first(index_cache->words);
- rbt_node != NULL;
- rbt_node = rbt_next(index_cache->words, rbt_node)) {
-
- fts_tokenizer_word_t* word;
- word = rbt_value(fts_tokenizer_word_t, rbt_node);
-
- fts_node_t* fts_node;
- fts_node = static_cast<fts_node_t*>(ib_vector_last(word->nodes));
-
- fts_node->synced = false;
- }
-}
-
-/** Commit the SYNC, change state of processed doc ids etc.
-@param[in,out] sync sync state
-@return DB_SUCCESS if all OK */
-static MY_ATTRIBUTE((nonnull, warn_unused_result))
-dberr_t
-fts_sync_commit(
- fts_sync_t* sync)
-{
- dberr_t error;
- trx_t* trx = sync->trx;
- fts_cache_t* cache = sync->table->fts->cache;
- doc_id_t last_doc_id;
-
- trx->op_info = "doing SYNC commit";
-
- /* After each Sync, update the CONFIG table about the max doc id
- we just sync-ed to index table */
- error = fts_cmp_set_sync_doc_id(sync->table, sync->max_doc_id, FALSE,
- &last_doc_id);
-
- /* Get the list of deleted documents that are either in the
- cache or were headed there but were deleted before the add
- thread got to them. */
-
- if (error == DB_SUCCESS && ib_vector_size(cache->deleted_doc_ids) > 0) {
-
- error = fts_sync_add_deleted_cache(
- sync, cache->deleted_doc_ids);
- }
-
- /* We need to do this within the deleted lock since fts_delete() can
- attempt to add a deleted doc id to the cache deleted id array. */
- fts_cache_clear(cache);
- DEBUG_SYNC_C("fts_deleted_doc_ids_clear");
- fts_cache_init(cache);
- rw_lock_x_unlock(&cache->lock);
-
- if (error == DB_SUCCESS) {
-
- fts_sql_commit(trx);
-
- } else if (error != DB_SUCCESS) {
-
- fts_sql_rollback(trx);
-
- ut_print_timestamp(stderr);
- fprintf(stderr, " InnoDB: Error: (%s) during SYNC.\n",
- ut_strerr(error));
- }
-
- if (fts_enable_diag_print && elapsed_time) {
- ib_logf(IB_LOG_LEVEL_INFO,
- "SYNC for table %s: SYNC time : %lu secs: "
- "elapsed %lf ins/sec",
- sync->table->name,
- (ulong) (ut_time() - sync->start_time),
- (double) n_nodes/ (double) elapsed_time);
- }
-
- /* Avoid assertion in trx_free(). */
- trx->dict_operation_lock_mode = 0;
- trx_free_for_background(trx);
-
- return(error);
-}
-
-/** Rollback a sync operation
-@param[in,out] sync sync state */
-static
-void
-fts_sync_rollback(
- fts_sync_t* sync)
-{
- trx_t* trx = sync->trx;
- 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<fts_index_cache_t*>(
- ib_vector_get(cache->indexes, i));
-
- /* 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) {
-
- fts_que_graph_free_check_lock(
- NULL, index_cache,
- index_cache->ins_graph[j]);
-
- index_cache->ins_graph[j] = NULL;
- }
-
- if (index_cache->sel_graph[j] != NULL) {
-
- fts_que_graph_free_check_lock(
- NULL, index_cache,
- index_cache->sel_graph[j]);
-
- index_cache->sel_graph[j] = NULL;
- }
- }
- }
-
- rw_lock_x_unlock(&cache->lock);
-
- fts_sql_rollback(trx);
-
- /* Avoid assertion in trx_free(). */
- trx->dict_operation_lock_mode = 0;
- trx_free_for_background(trx);
-}
-
-/** Run SYNC on the table, i.e., write out data from the cache to the
-FTS auxiliary INDEX table and clear the cache at the end.
-@param[in,out] sync sync state
-@param[in] unlock_cache whether unlock cache lock when write node
-@param[in] wait whether wait when a sync is in progress
-@param[in] has_dict whether has dict operation lock
-@return DB_SUCCESS if all OK */
-static
-dberr_t
-fts_sync(
- fts_sync_t* sync,
- bool unlock_cache,
- bool wait,
- bool has_dict)
-{
- ulint i;
- dberr_t error = DB_SUCCESS;
- fts_cache_t* cache = sync->table->fts->cache;
-
- rw_lock_x_lock(&cache->lock);
-
- /* Check if cache is being synced.
- Note: we release cache lock in fts_sync_write_words() to
- avoid long wait for the lock by other threads. */
- while (sync->in_progress) {
- rw_lock_x_unlock(&cache->lock);
-
- if (wait) {
- os_event_wait(sync->event);
- } else {
- return(DB_SUCCESS);
- }
-
- rw_lock_x_lock(&cache->lock);
- }
-
- sync->unlock_cache = unlock_cache;
- sync->in_progress = true;
-
- DEBUG_SYNC_C("fts_sync_begin");
- fts_sync_begin(sync);
-
- /* When sync in background, we hold dict operation lock
- to prevent DDL like DROP INDEX, etc. */
- if (has_dict) {
- sync->trx->dict_operation_lock_mode = RW_S_LATCH;
- }
-
-begin_sync:
- if (cache->total_size > fts_max_cache_size) {
- /* Avoid the case: sync never finish when
- insert/update keeps comming. */
- ut_ad(sync->unlock_cache);
- sync->unlock_cache = false;
- }
-
- for (i = 0; i < ib_vector_size(cache->indexes); ++i) {
- fts_index_cache_t* index_cache;
-
- index_cache = static_cast<fts_index_cache_t*>(
- ib_vector_get(cache->indexes, i));
-
- if (index_cache->index->to_be_dropped) {
- continue;
- }
-
- error = fts_sync_index(sync, index_cache);
-
- if (error != DB_SUCCESS && !sync->interrupted) {
-
- goto end_sync;
- }
- }
-
- DBUG_EXECUTE_IF("fts_instrument_sync_interrupted",
- sync->interrupted = true;
- error = DB_INTERRUPTED;
- goto end_sync;
- );
-
- /* Make sure all the caches are synced. */
- for (i = 0; i < ib_vector_size(cache->indexes); ++i) {
- fts_index_cache_t* index_cache;
-
- index_cache = static_cast<fts_index_cache_t*>(
- ib_vector_get(cache->indexes, i));
-
- if (index_cache->index->to_be_dropped
- || fts_sync_index_check(index_cache)) {
- continue;
- }
-
- goto begin_sync;
- }
-
-end_sync:
- if (error == DB_SUCCESS && !sync->interrupted) {
- error = fts_sync_commit(sync);
- } else {
- fts_sync_rollback(sync);
- }
-
- rw_lock_x_lock(&cache->lock);
- sync->interrupted = false;
- sync->in_progress = false;
- os_event_set(sync->event);
- rw_lock_x_unlock(&cache->lock);
-
- /* We need to check whether an optimize is required, for that
- we make copies of the two variables that control the trigger. These
- variables can change behind our back and we don't want to hold the
- lock for longer than is needed. */
- mutex_enter(&cache->deleted_lock);
-
- cache->added = 0;
- cache->deleted = 0;
-
- mutex_exit(&cache->deleted_lock);
-
- return(error);
-}
-
-/** Run SYNC on the table, i.e., write out data from the cache to the
-FTS auxiliary INDEX table and clear the cache at the end.
-@param[in,out] table fts table
-@param[in] unlock_cache whether unlock cache when write node
-@param[in] wait whether wait for existing sync to finish
-@param[in] has_dict whether has dict operation lock
-@return DB_SUCCESS on success, error code on failure. */
-UNIV_INTERN
-dberr_t
-fts_sync_table(
- dict_table_t* table,
- bool unlock_cache,
- bool wait,
- bool has_dict)
-{
- dberr_t err = DB_SUCCESS;
-
- ut_ad(table->fts);
-
- if (!dict_table_is_discarded(table) && table->fts->cache) {
- err = fts_sync(table->fts->cache->sync,
- unlock_cache, wait, has_dict);
- }
-
- return(err);
-}
-
-/********************************************************************
-Process next token from document starting at the given position, i.e., add
-the token's start position to the token's list of positions.
-@return number of characters handled in this call */
-static
-ulint
-fts_process_token(
-/*==============*/
- fts_doc_t* doc, /* in/out: document to
- tokenize */
- fts_doc_t* result, /* out: if provided, save
- result here */
- ulint start_pos, /*!< in: start position in text */
- ulint add_pos) /*!< in: add this position to all
- tokens from this tokenization */
-{
- ulint ret;
- fts_string_t str;
- ulint offset = 0;
- fts_doc_t* result_doc;
-
- /* Determine where to save the result. */
- result_doc = (result) ? result : doc;
-
- /* The length of a string in characters is set here only. */
- ret = innobase_mysql_fts_get_token(
- doc->charset, doc->text.f_str + start_pos,
- doc->text.f_str + doc->text.f_len, &str, &offset);
-
- /* Ignore string whose character number is less than
- "fts_min_token_size" or more than "fts_max_token_size" */
-
- if (str.f_n_char >= fts_min_token_size
- && str.f_n_char <= fts_max_token_size) {
-
- mem_heap_t* heap;
- fts_string_t t_str;
- fts_token_t* token;
- ib_rbt_bound_t parent;
- ulint newlen;
-
- heap = static_cast<mem_heap_t*>(result_doc->self_heap->arg);
-
- t_str.f_n_char = str.f_n_char;
-
- t_str.f_len = str.f_len * doc->charset->casedn_multiply + 1;
-
- t_str.f_str = static_cast<byte*>(
- mem_heap_alloc(heap, t_str.f_len));
-
- newlen = innobase_fts_casedn_str(
- doc->charset, (char*) str.f_str, str.f_len,
- (char*) t_str.f_str, t_str.f_len);
-
- t_str.f_len = newlen;
- t_str.f_str[newlen] = 0;
-
- /* Add the word to the document statistics. If the word
- hasn't been seen before we create a new entry for it. */
- if (rbt_search(result_doc->tokens, &parent, &t_str) != 0) {
- fts_token_t new_token;
-
- new_token.text.f_len = newlen;
- new_token.text.f_str = t_str.f_str;
- new_token.text.f_n_char = t_str.f_n_char;
-
- new_token.positions = ib_vector_create(
- result_doc->self_heap, sizeof(ulint), 32);
-
- ut_a(new_token.text.f_n_char >= fts_min_token_size);
- ut_a(new_token.text.f_n_char <= fts_max_token_size);
-
- parent.last = rbt_add_node(
- result_doc->tokens, &parent, &new_token);
-
- ut_ad(rbt_validate(result_doc->tokens));
- }
-
-#ifdef FTS_CHARSET_DEBUG
- offset += start_pos + add_pos;
-#endif /* FTS_CHARSET_DEBUG */
-
- offset += start_pos + ret - str.f_len + add_pos;
-
- token = rbt_value(fts_token_t, parent.last);
- ib_vector_push(token->positions, &offset);
- }
-
- return(ret);
-}
-
-/******************************************************************//**
-Tokenize a document. */
-UNIV_INTERN
-void
-fts_tokenize_document(
-/*==================*/
- fts_doc_t* doc, /* in/out: document to
- tokenize */
- fts_doc_t* result) /* out: if provided, save
- the result token here */
-{
- ulint inc;
-
- ut_a(!doc->tokens);
- ut_a(doc->charset);
-
- doc->tokens = rbt_create_arg_cmp(
- sizeof(fts_token_t), innobase_fts_text_cmp, (void*) doc->charset);
-
- for (ulint i = 0; i < doc->text.f_len; i += inc) {
- inc = fts_process_token(doc, result, i, 0);
- ut_a(inc > 0);
- }
-}
-
-/******************************************************************//**
-Continue to tokenize a document. */
-UNIV_INTERN
-void
-fts_tokenize_document_next(
-/*=======================*/
- fts_doc_t* doc, /*!< in/out: document to
- tokenize */
- ulint add_pos, /*!< in: add this position to all
- tokens from this tokenization */
- fts_doc_t* result) /*!< out: if provided, save
- the result token here */
-{
- ulint inc;
-
- ut_a(doc->tokens);
-
- for (ulint i = 0; i < doc->text.f_len; i += inc) {
- inc = fts_process_token(doc, result, i, add_pos);
- ut_a(inc > 0);
- }
-}
-
-/********************************************************************
-Create the vector of fts_get_doc_t instances. */
-UNIV_INTERN
-ib_vector_t*
-fts_get_docs_create(
-/*================*/
- /* out: vector of
- fts_get_doc_t instances */
- fts_cache_t* cache) /*!< in: fts cache */
-{
- ulint i;
- ib_vector_t* get_docs;
-
-#ifdef UNIV_SYNC_DEBUG
- ut_ad(rw_lock_own(&cache->init_lock, RW_LOCK_EX));
-#endif
- /* We need one instance of fts_get_doc_t per index. */
- get_docs = ib_vector_create(
- cache->self_heap, sizeof(fts_get_doc_t), 4);
-
- /* Create the get_doc instance, we need one of these
- per FTS index. */
- for (i = 0; i < ib_vector_size(cache->indexes); ++i) {
-
- dict_index_t** index;
- fts_get_doc_t* get_doc;
-
- index = static_cast<dict_index_t**>(
- ib_vector_get(cache->indexes, i));
-
- get_doc = static_cast<fts_get_doc_t*>(
- ib_vector_push(get_docs, NULL));
-
- memset(get_doc, 0x0, sizeof(*get_doc));
-
- get_doc->index_cache = fts_get_index_cache(cache, *index);
- get_doc->cache = cache;
-
- /* Must find the index cache. */
- ut_a(get_doc->index_cache != NULL);
- }
-
- 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<fts_get_doc_t*>(
- ib_vector_get(get_docs, i));
-
- if (get_doc->get_document_graph != NULL) {
-
- ut_a(get_doc->index_cache);
-
- fts_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 */
-UNIV_INTERN
-doc_id_t
-fts_init_doc_id(
-/*============*/
- const dict_table_t* table) /*!< in: table */
-{
- doc_id_t max_doc_id = 0;
-
- rw_lock_x_lock(&table->fts->cache->lock);
-
- /* Return if the table is already initialized for DOC ID */
- if (table->fts->cache->first_doc_id != FTS_NULL_DOC_ID) {
- rw_lock_x_unlock(&table->fts->cache->lock);
- return(0);
- }
-
- DEBUG_SYNC_C("fts_initialize_doc_id");
-
- /* Then compare this value with the ID value stored in the CONFIG
- table. The larger one will be our new initial Doc ID */
- fts_cmp_set_sync_doc_id(table, 0, FALSE, &max_doc_id);
-
- /* If DICT_TF2_FTS_ADD_DOC_ID is set, we are in the process of
- creating index (and add doc id column. No need to recovery
- documents */
- if (!DICT_TF2_FLAG_IS_SET(table, DICT_TF2_FTS_ADD_DOC_ID)) {
- fts_init_index((dict_table_t*) table, TRUE);
- }
-
- table->fts->fts_status |= ADDED_TABLE_SYNCED;
-
- table->fts->cache->first_doc_id = max_doc_id;
-
- rw_lock_x_unlock(&table->fts->cache->lock);
-
- ut_ad(max_doc_id > 0);
-
- return(max_doc_id);
-}
-
-#ifdef FTS_MULT_INDEX
-/*********************************************************************//**
-Check if the index is in the affected set.
-@return TRUE if index is updated */
-static
-ibool
-fts_is_index_updated(
-/*=================*/
- const ib_vector_t* fts_indexes, /*!< in: affected FTS indexes */
- const fts_get_doc_t* get_doc) /*!< in: info for reading
- document */
-{
- ulint i;
- dict_index_t* index = get_doc->index_cache->index;
-
- for (i = 0; i < ib_vector_size(fts_indexes); ++i) {
- const dict_index_t* updated_fts_index;
-
- updated_fts_index = static_cast<const dict_index_t*>(
- ib_vector_getp_const(fts_indexes, i));
-
- ut_a(updated_fts_index != NULL);
-
- if (updated_fts_index == index) {
- return(TRUE);
- }
- }
-
- return(FALSE);
-}
-#endif
-
-/*********************************************************************//**
-Fetch COUNT(*) from specified table.
-@return the number of rows in the table */
-UNIV_INTERN
-ulint
-fts_get_rows_count(
-/*===============*/
- fts_table_t* fts_table) /*!< in: fts table to read */
-{
- trx_t* trx;
- pars_info_t* info;
- que_t* graph;
- dberr_t error;
- ulint count = 0;
-
- trx = trx_allocate_for_background();
-
- trx->op_info = "fetching FT table rows count";
-
- info = pars_info_create();
-
- pars_info_bind_function(info, "my_func", fts_read_ulint, &count);
-
- graph = fts_parse_sql(
- fts_table,
- info,
- "DECLARE FUNCTION my_func;\n"
- "DECLARE CURSOR c IS"
- " SELECT COUNT(*) "
- " FROM \"%s\";\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) {
- fts_sql_commit(trx);
-
- break; /* Exit the loop. */
- } else {
- fts_sql_rollback(trx);
-
- ut_print_timestamp(stderr);
-
- if (error == DB_LOCK_WAIT_TIMEOUT) {
- fprintf(stderr, " InnoDB: Warning: lock wait "
- "timeout reading FTS table. "
- "Retrying!\n");
-
- trx->error_state = DB_SUCCESS;
- } else {
- fprintf(stderr, " InnoDB: Error: (%s) "
- "while reading FTS table.\n",
- ut_strerr(error));
-
- break; /* Exit the loop. */
- }
- }
- }
-
- fts_que_graph_free(graph);
-
- trx_free_for_background(trx);
-
- return(count);
-}
-
-#ifdef FTS_CACHE_SIZE_DEBUG
-/*********************************************************************//**
-Read the max cache size parameter from the config table. */
-static
-void
-fts_update_max_cache_size(
-/*======================*/
- fts_sync_t* sync) /*!< in: sync state */
-{
- trx_t* trx;
- fts_table_t fts_table;
-
- trx = trx_allocate_for_background();
-
- FTS_INIT_FTS_TABLE(&fts_table, "CONFIG", FTS_COMMON_TABLE, sync->table);
-
- /* The size returned is in bytes. */
- sync->max_cache_size = fts_get_max_cache_size(trx, &fts_table);
-
- fts_sql_commit(trx);
-
- trx_free_for_background(trx);
-}
-#endif /* FTS_CACHE_SIZE_DEBUG */
-
-/*********************************************************************//**
-Free the modified rows of a table. */
-UNIV_INLINE
-void
-fts_trx_table_rows_free(
-/*====================*/
- ib_rbt_t* rows) /*!< in: rbt of rows to free */
-{
- const ib_rbt_node_t* node;
-
- for (node = rbt_first(rows); node; node = rbt_first(rows)) {
- fts_trx_row_t* row;
-
- row = rbt_value(fts_trx_row_t, node);
-
- if (row->fts_indexes != NULL) {
- /* This vector shouldn't be using the
- heap allocator. */
- ut_a(row->fts_indexes->allocator->arg == NULL);
-
- ib_vector_free(row->fts_indexes);
- row->fts_indexes = NULL;
- }
-
- ut_free(rbt_remove_node(rows, node));
- }
-
- ut_a(rbt_empty(rows));
- rbt_free(rows);
-}
-
-/*********************************************************************//**
-Free an FTS savepoint instance. */
-UNIV_INLINE
-void
-fts_savepoint_free(
-/*===============*/
- fts_savepoint_t* savepoint) /*!< in: savepoint instance */
-{
- const ib_rbt_node_t* node;
- ib_rbt_t* tables = savepoint->tables;
-
- /* Nothing to free! */
- if (tables == NULL) {
- return;
- }
-
- for (node = rbt_first(tables); node; node = rbt_first(tables)) {
- fts_trx_table_t* ftt;
- fts_trx_table_t** fttp;
-
- fttp = rbt_value(fts_trx_table_t*, node);
- ftt = *fttp;
-
- /* This can be NULL if a savepoint was released. */
- if (ftt->rows != NULL) {
- fts_trx_table_rows_free(ftt->rows);
- ftt->rows = NULL;
- }
-
- /* This can be NULL if a savepoint was released. */
- if (ftt->added_doc_ids != NULL) {
- fts_doc_ids_free(ftt->added_doc_ids);
- ftt->added_doc_ids = NULL;
- }
-
- /* The default savepoint name must be NULL. */
- if (ftt->docs_added_graph) {
- fts_que_graph_free(ftt->docs_added_graph);
- }
-
- /* NOTE: We are responsible for free'ing the node */
- ut_free(rbt_remove_node(tables, node));
- }
-
- ut_a(rbt_empty(tables));
- rbt_free(tables);
- savepoint->tables = NULL;
-}
-
-/*********************************************************************//**
-Free an FTS trx. */
-UNIV_INTERN
-void
-fts_trx_free(
-/*=========*/
- fts_trx_t* fts_trx) /* in, own: FTS trx */
-{
- ulint i;
-
- for (i = 0; i < ib_vector_size(fts_trx->savepoints); ++i) {
- fts_savepoint_t* savepoint;
-
- savepoint = static_cast<fts_savepoint_t*>(
- ib_vector_get(fts_trx->savepoints, i));
-
- /* The default savepoint name must be NULL. */
- if (i == 0) {
- ut_a(savepoint->name == NULL);
- }
-
- fts_savepoint_free(savepoint);
- }
-
- for (i = 0; i < ib_vector_size(fts_trx->last_stmt); ++i) {
- fts_savepoint_t* savepoint;
-
- savepoint = static_cast<fts_savepoint_t*>(
- ib_vector_get(fts_trx->last_stmt, i));
-
- /* The default savepoint name must be NULL. */
- if (i == 0) {
- ut_a(savepoint->name == NULL);
- }
-
- fts_savepoint_free(savepoint);
- }
-
- if (fts_trx->heap) {
- mem_heap_free(fts_trx->heap);
- }
-}
-
-/*********************************************************************//**
-Extract the doc id from the FTS hidden column.
-@return doc id that was extracted from rec */
-UNIV_INTERN
-doc_id_t
-fts_get_doc_id_from_row(
-/*====================*/
- dict_table_t* table, /*!< in: table */
- dtuple_t* row) /*!< in: row whose FTS doc id we
- want to extract.*/
-{
- dfield_t* field;
- doc_id_t doc_id = 0;
-
- ut_a(table->fts->doc_col != ULINT_UNDEFINED);
-
- field = dtuple_get_nth_field(row, table->fts->doc_col);
-
- ut_a(dfield_get_len(field) == sizeof(doc_id));
- ut_a(dfield_get_type(field)->mtype == DATA_INT);
-
- doc_id = fts_read_doc_id(
- static_cast<const byte*>(dfield_get_data(field)));
-
- return(doc_id);
-}
-
-/*********************************************************************//**
-Extract the doc id from the FTS hidden column.
-@return doc id that was extracted from rec */
-UNIV_INTERN
-doc_id_t
-fts_get_doc_id_from_rec(
-/*====================*/
- dict_table_t* table, /*!< in: table */
- const rec_t* rec, /*!< in: rec */
- mem_heap_t* heap) /*!< in: heap */
-{
- ulint len;
- const byte* data;
- ulint col_no;
- doc_id_t doc_id = 0;
- dict_index_t* clust_index;
- ulint offsets_[REC_OFFS_NORMAL_SIZE];
- ulint* offsets = offsets_;
- mem_heap_t* my_heap = heap;
-
- ut_a(table->fts->doc_col != ULINT_UNDEFINED);
-
- clust_index = dict_table_get_first_index(table);
-
- rec_offs_init(offsets_);
-
- offsets = rec_get_offsets(
- rec, clust_index, offsets, ULINT_UNDEFINED, &my_heap);
-
- col_no = dict_col_get_clust_pos(
- &table->cols[table->fts->doc_col], clust_index);
- ut_ad(col_no != ULINT_UNDEFINED);
-
- data = rec_get_nth_field(rec, offsets, col_no, &len);
-
- ut_a(len == 8);
- ut_ad(8 == sizeof(doc_id));
- doc_id = static_cast<doc_id_t>(mach_read_from_8(data));
-
- if (my_heap && !heap) {
- mem_heap_free(my_heap);
- }
-
- return(doc_id);
-}
-
-/*********************************************************************//**
-Search the index specific cache for a particular FTS index.
-@return the index specific cache else NULL */
-UNIV_INTERN
-fts_index_cache_t*
-fts_find_index_cache(
-/*=================*/
- const fts_cache_t* cache, /*!< in: cache to search */
- const dict_index_t* index) /*!< in: index to search for */
-{
- /* We cast away the const because our internal function, takes
- non-const cache arg and returns a non-const pointer. */
- return(static_cast<fts_index_cache_t*>(
- fts_get_index_cache((fts_cache_t*) cache, index)));
-}
-
-/*********************************************************************//**
-Search cache for word.
-@return the word node vector if found else NULL */
-UNIV_INTERN
-const ib_vector_t*
-fts_cache_find_word(
-/*================*/
- const fts_index_cache_t*index_cache, /*!< in: cache to search */
- const fts_string_t* text) /*!< in: word to search for */
-{
- ib_rbt_bound_t parent;
- const ib_vector_t* nodes = NULL;
-#ifdef UNIV_SYNC_DEBUG
- dict_table_t* table = index_cache->index->table;
- fts_cache_t* cache = table->fts->cache;
-
- ut_ad(rw_lock_own((rw_lock_t*) &cache->lock, RW_LOCK_EX));
-#endif
-
- /* Lookup the word in the rb tree */
- if (rbt_search(index_cache->words, &parent, text) == 0) {
- const fts_tokenizer_word_t* word;
-
- word = rbt_value(fts_tokenizer_word_t, parent.last);
-
- nodes = word->nodes;
- }
-
- return(nodes);
-}
-
-/*********************************************************************//**
-Check cache for deleted doc id.
-@return TRUE if deleted */
-UNIV_INTERN
-ibool
-fts_cache_is_deleted_doc_id(
-/*========================*/
- const fts_cache_t* cache, /*!< in: cache ito search */
- doc_id_t doc_id) /*!< in: doc id to search for */
-{
- ulint i;
-
-#ifdef UNIV_SYNC_DEBUG
- ut_ad(mutex_own(&cache->deleted_lock));
-#endif
-
- for (i = 0; i < ib_vector_size(cache->deleted_doc_ids); ++i) {
- const fts_update_t* update;
-
- update = static_cast<const fts_update_t*>(
- ib_vector_get_const(cache->deleted_doc_ids, i));
-
- if (doc_id == update->doc_id) {
-
- return(TRUE);
- }
- }
-
- return(FALSE);
-}
-
-/*********************************************************************//**
-Append deleted doc ids to vector. */
-UNIV_INTERN
-void
-fts_cache_append_deleted_doc_ids(
-/*=============================*/
- const fts_cache_t* cache, /*!< in: cache to use */
- ib_vector_t* vector) /*!< in: append to this vector */
-{
- ulint i;
-
- mutex_enter((ib_mutex_t*) &cache->deleted_lock);
-
- if (cache->deleted_doc_ids == NULL) {
- mutex_exit((ib_mutex_t*) &cache->deleted_lock);
- return;
- }
-
-
- for (i = 0; i < ib_vector_size(cache->deleted_doc_ids); ++i) {
- fts_update_t* update;
-
- update = static_cast<fts_update_t*>(
- ib_vector_get(cache->deleted_doc_ids, i));
-
- ib_vector_push(vector, &update->doc_id);
- }
-
- mutex_exit((ib_mutex_t*) &cache->deleted_lock);
-}
-
-/*********************************************************************//**
-Wait for the background thread to start. We poll to detect change
-of state, which is acceptable, since the wait should happen only
-once during startup.
-@return true if the thread started else FALSE (i.e timed out) */
-UNIV_INTERN
-ibool
-fts_wait_for_background_thread_to_start(
-/*====================================*/
- dict_table_t* table, /*!< in: table to which the thread
- is attached */
- ulint max_wait) /*!< in: time in microseconds, if
- set to 0 then it disables
- timeout checking */
-{
- ulint count = 0;
- ibool done = FALSE;
-
- ut_a(max_wait == 0 || max_wait >= FTS_MAX_BACKGROUND_THREAD_WAIT);
-
- for (;;) {
- fts_t* fts = table->fts;
-
- mutex_enter(&fts->bg_threads_mutex);
-
- if (fts->fts_status & BG_THREAD_READY) {
-
- done = TRUE;
- }
-
- mutex_exit(&fts->bg_threads_mutex);
-
- if (!done) {
- os_thread_sleep(FTS_MAX_BACKGROUND_THREAD_WAIT);
-
- if (max_wait > 0) {
-
- max_wait -= FTS_MAX_BACKGROUND_THREAD_WAIT;
-
- /* We ignore the residual value. */
- if (max_wait < FTS_MAX_BACKGROUND_THREAD_WAIT) {
- break;
- }
- }
-
- ++count;
- } else {
- break;
- }
-
- if (count >= FTS_BACKGROUND_THREAD_WAIT_COUNT) {
- ut_print_timestamp(stderr);
- fprintf(stderr, " InnoDB: Error the background thread "
- "for the FTS table %s refuses to start\n",
- table->name);
-
- count = 0;
- }
- }
-
- return(done);
-}
-
-/*********************************************************************//**
-Add the FTS document id hidden column. */
-UNIV_INTERN
-void
-fts_add_doc_id_column(
-/*==================*/
- dict_table_t* table, /*!< in/out: Table with FTS index */
- mem_heap_t* heap) /*!< in: temporary memory heap, or NULL */
-{
- dict_mem_table_add_col(
- table, heap,
- FTS_DOC_ID_COL_NAME,
- DATA_INT,
- dtype_form_prtype(
- DATA_NOT_NULL | DATA_UNSIGNED
- | DATA_BINARY_TYPE | DATA_FTS_DOC_ID, 0),
- sizeof(doc_id_t));
- DICT_TF2_FLAG_SET(table, DICT_TF2_FTS_HAS_DOC_ID);
-}
-
-/*********************************************************************//**
-Update the query graph with a new document id.
-@return Doc ID used */
-UNIV_INTERN
-doc_id_t
-fts_update_doc_id(
-/*==============*/
- dict_table_t* table, /*!< in: table */
- upd_field_t* ufield, /*!< out: update node */
- doc_id_t* next_doc_id) /*!< in/out: buffer for writing */
-{
- doc_id_t doc_id;
- dberr_t error = DB_SUCCESS;
-
- if (*next_doc_id) {
- doc_id = *next_doc_id;
- } else {
- /* Get the new document id that will be added. */
- error = fts_get_next_doc_id(table, &doc_id);
- }
-
- if (error == DB_SUCCESS) {
- dict_index_t* clust_index;
-
- ufield->exp = NULL;
-
- ufield->new_val.len = sizeof(doc_id);
-
- clust_index = dict_table_get_first_index(table);
-
- ufield->field_no = dict_col_get_clust_pos(
- &table->cols[table->fts->doc_col], clust_index);
-
- /* It is possible we update record that has
- not yet be sync-ed from last crash. */
-
- /* Convert to storage byte order. */
- ut_a(doc_id != FTS_NULL_DOC_ID);
- fts_write_doc_id((byte*) next_doc_id, doc_id);
-
- ufield->new_val.data = next_doc_id;
- }
-
- return(doc_id);
-}
-
-/*********************************************************************//**
-Check if the table has an FTS index. This is the non-inline version
-of dict_table_has_fts_index().
-@return TRUE if table has an FTS index */
-UNIV_INTERN
-ibool
-fts_dict_table_has_fts_index(
-/*=========================*/
- dict_table_t* table) /*!< in: table */
-{
- return(dict_table_has_fts_index(table));
-}
-
-/*********************************************************************//**
-Create an instance of fts_t.
-@return instance of fts_t */
-UNIV_INTERN
-fts_t*
-fts_create(
-/*=======*/
- dict_table_t* table) /*!< in/out: table with FTS indexes */
-{
- fts_t* fts;
- ib_alloc_t* heap_alloc;
- mem_heap_t* heap;
-
- ut_a(!table->fts);
-
- heap = mem_heap_create(512);
-
- fts = static_cast<fts_t*>(mem_heap_alloc(heap, sizeof(*fts)));
-
- memset(fts, 0x0, sizeof(*fts));
-
- fts->fts_heap = heap;
-
- fts->doc_col = ULINT_UNDEFINED;
-
- mutex_create(
- fts_bg_threads_mutex_key, &fts->bg_threads_mutex,
- SYNC_FTS_BG_THREADS);
-
- heap_alloc = ib_heap_allocator_create(heap);
- fts->indexes = ib_vector_create(heap_alloc, sizeof(dict_index_t*), 4);
- dict_table_get_all_fts_indexes(table, fts->indexes);
-
- return(fts);
-}
-
-/*********************************************************************//**
-Free the FTS resources. */
-UNIV_INTERN
-void
-fts_free(
-/*=====*/
- dict_table_t* table) /*!< in/out: table with FTS indexes */
-{
- fts_t* fts = table->fts;
-
- mutex_free(&fts->bg_threads_mutex);
-
- ut_ad(!fts->add_wq);
-
- if (fts->cache) {
- fts_cache_clear(fts->cache);
- fts_cache_destroy(fts->cache);
- fts->cache = NULL;
- }
-
- mem_heap_free(fts->fts_heap);
-
- table->fts = NULL;
-}
-
-/*********************************************************************//**
-Signal FTS threads to initiate shutdown. */
-UNIV_INTERN
-void
-fts_start_shutdown(
-/*===============*/
- dict_table_t* table, /*!< in: table with FTS indexes */
- fts_t* fts) /*!< in: fts instance that needs
- to be informed about shutdown */
-{
- mutex_enter(&fts->bg_threads_mutex);
-
- fts->fts_status |= BG_THREAD_STOP;
-
- mutex_exit(&fts->bg_threads_mutex);
-
-}
-
-/*********************************************************************//**
-Wait for FTS threads to shutdown. */
-UNIV_INTERN
-void
-fts_shutdown(
-/*=========*/
- dict_table_t* table, /*!< in: table with FTS indexes */
- fts_t* fts) /*!< in: fts instance to shutdown */
-{
- mutex_enter(&fts->bg_threads_mutex);
-
- ut_a(fts->fts_status & BG_THREAD_STOP);
-
- dict_table_wait_for_bg_threads_to_exit(table, 20000);
-
- mutex_exit(&fts->bg_threads_mutex);
-}
-
-/*********************************************************************//**
-Take a FTS savepoint. */
-UNIV_INLINE
-void
-fts_savepoint_copy(
-/*===============*/
- const fts_savepoint_t* src, /*!< in: source savepoint */
- fts_savepoint_t* dst) /*!< out: destination savepoint */
-{
- const ib_rbt_node_t* node;
- const ib_rbt_t* tables;
-
- tables = src->tables;
-
- for (node = rbt_first(tables); node; node = rbt_next(tables, node)) {
-
- fts_trx_table_t* ftt_dst;
- const fts_trx_table_t** ftt_src;
-
- ftt_src = rbt_value(const fts_trx_table_t*, node);
-
- ftt_dst = fts_trx_table_clone(*ftt_src);
-
- rbt_insert(dst->tables, &ftt_dst, &ftt_dst);
- }
-}
-
-/*********************************************************************//**
-Take a FTS savepoint. */
-UNIV_INTERN
-void
-fts_savepoint_take(
-/*===============*/
- trx_t* trx, /*!< in: transaction */
- fts_trx_t* fts_trx, /*!< in: fts transaction */
- const char* name) /*!< in: savepoint name */
-{
- mem_heap_t* heap;
- fts_savepoint_t* savepoint;
- fts_savepoint_t* last_savepoint;
-
- ut_a(name != NULL);
-
- heap = fts_trx->heap;
-
- /* The implied savepoint must exist. */
- ut_a(ib_vector_size(fts_trx->savepoints) > 0);
-
- last_savepoint = static_cast<fts_savepoint_t*>(
- ib_vector_last(fts_trx->savepoints));
- savepoint = fts_savepoint_create(fts_trx->savepoints, name, heap);
-
- if (last_savepoint->tables != NULL) {
- fts_savepoint_copy(last_savepoint, savepoint);
- }
-}
-
-/*********************************************************************//**
-Lookup a savepoint instance by name.
-@return ULINT_UNDEFINED if not found */
-UNIV_INLINE
-ulint
-fts_savepoint_lookup(
-/*==================*/
- ib_vector_t* savepoints, /*!< in: savepoints */
- const char* name) /*!< in: savepoint name */
-{
- ulint i;
-
- ut_a(ib_vector_size(savepoints) > 0);
-
- for (i = 1; i < ib_vector_size(savepoints); ++i) {
- fts_savepoint_t* savepoint;
-
- savepoint = static_cast<fts_savepoint_t*>(
- ib_vector_get(savepoints, i));
-
- if (strcmp(name, savepoint->name) == 0) {
- return(i);
- }
- }
-
- return(ULINT_UNDEFINED);
-}
-
-/*********************************************************************//**
-Release the savepoint data identified by name. All savepoints created
-after the named savepoint are kept.
-@return DB_SUCCESS or error code */
-UNIV_INTERN
-void
-fts_savepoint_release(
-/*==================*/
- trx_t* trx, /*!< in: transaction */
- const char* name) /*!< in: savepoint name */
-{
- ut_a(name != NULL);
-
- ib_vector_t* savepoints = trx->fts_trx->savepoints;
-
- ut_a(ib_vector_size(savepoints) > 0);
-
- ulint i = fts_savepoint_lookup(savepoints, name);
- if (i != ULINT_UNDEFINED) {
- ut_a(i >= 1);
-
- fts_savepoint_t* savepoint;
- savepoint = static_cast<fts_savepoint_t*>(
- ib_vector_get(savepoints, i));
-
- if (i == ib_vector_size(savepoints) - 1) {
- /* If the savepoint is the last, we save its
- tables to the previous savepoint. */
- fts_savepoint_t* prev_savepoint;
- prev_savepoint = static_cast<fts_savepoint_t*>(
- ib_vector_get(savepoints, i - 1));
-
- ib_rbt_t* tables = savepoint->tables;
- savepoint->tables = prev_savepoint->tables;
- prev_savepoint->tables = tables;
- }
-
- fts_savepoint_free(savepoint);
- ib_vector_remove(savepoints, *(void**)savepoint);
-
- /* Make sure we don't delete the implied savepoint. */
- ut_a(ib_vector_size(savepoints) > 0);
- }
-}
-
-/**********************************************************************//**
-Refresh last statement savepoint. */
-UNIV_INTERN
-void
-fts_savepoint_laststmt_refresh(
-/*===========================*/
- trx_t* trx) /*!< in: transaction */
-{
-
- fts_trx_t* fts_trx;
- fts_savepoint_t* savepoint;
-
- fts_trx = trx->fts_trx;
-
- savepoint = static_cast<fts_savepoint_t*>(
- ib_vector_pop(fts_trx->last_stmt));
- fts_savepoint_free(savepoint);
-
- ut_ad(ib_vector_is_empty(fts_trx->last_stmt));
- savepoint = fts_savepoint_create(fts_trx->last_stmt, NULL, NULL);
-}
-
-/********************************************************************
-Undo the Doc ID add/delete operations in last stmt */
-static
-void
-fts_undo_last_stmt(
-/*===============*/
- fts_trx_table_t* s_ftt, /*!< in: Transaction FTS table */
- fts_trx_table_t* l_ftt) /*!< in: last stmt FTS table */
-{
- ib_rbt_t* s_rows;
- ib_rbt_t* l_rows;
- const ib_rbt_node_t* node;
-
- l_rows = l_ftt->rows;
- s_rows = s_ftt->rows;
-
- for (node = rbt_first(l_rows);
- node;
- node = rbt_next(l_rows, node)) {
- fts_trx_row_t* l_row = rbt_value(fts_trx_row_t, node);
- ib_rbt_bound_t parent;
-
- rbt_search(s_rows, &parent, &(l_row->doc_id));
-
- if (parent.result == 0) {
- fts_trx_row_t* s_row = rbt_value(
- fts_trx_row_t, parent.last);
-
- switch (l_row->state) {
- case FTS_INSERT:
- ut_free(rbt_remove_node(s_rows, parent.last));
- break;
-
- case FTS_DELETE:
- if (s_row->state == FTS_NOTHING) {
- s_row->state = FTS_INSERT;
- } else if (s_row->state == FTS_DELETE) {
- ut_free(rbt_remove_node(
- s_rows, parent.last));
- }
- break;
-
- /* FIXME: Check if FTS_MODIFY need to be addressed */
- case FTS_MODIFY:
- case FTS_NOTHING:
- break;
- default:
- ut_error;
- }
- }
- }
-}
-
-/**********************************************************************//**
-Rollback to savepoint indentified by name.
-@return DB_SUCCESS or error code */
-UNIV_INTERN
-void
-fts_savepoint_rollback_last_stmt(
-/*=============================*/
- trx_t* trx) /*!< in: transaction */
-{
- ib_vector_t* savepoints;
- fts_savepoint_t* savepoint;
- fts_savepoint_t* last_stmt;
- fts_trx_t* fts_trx;
- ib_rbt_bound_t parent;
- const ib_rbt_node_t* node;
- ib_rbt_t* l_tables;
- ib_rbt_t* s_tables;
-
- fts_trx = trx->fts_trx;
- savepoints = fts_trx->savepoints;
-
- savepoint = static_cast<fts_savepoint_t*>(ib_vector_last(savepoints));
- last_stmt = static_cast<fts_savepoint_t*>(
- ib_vector_last(fts_trx->last_stmt));
-
- l_tables = last_stmt->tables;
- s_tables = savepoint->tables;
-
- for (node = rbt_first(l_tables);
- node;
- node = rbt_next(l_tables, node)) {
-
- fts_trx_table_t** l_ftt;
-
- l_ftt = rbt_value(fts_trx_table_t*, node);
-
- rbt_search_cmp(
- s_tables, &parent, &(*l_ftt)->table->id,
- fts_trx_table_id_cmp, NULL);
-
- if (parent.result == 0) {
- fts_trx_table_t** s_ftt;
-
- s_ftt = rbt_value(fts_trx_table_t*, parent.last);
-
- fts_undo_last_stmt(*s_ftt, *l_ftt);
- }
- }
-}
-
-/**********************************************************************//**
-Rollback to savepoint indentified by name.
-@return DB_SUCCESS or error code */
-UNIV_INTERN
-void
-fts_savepoint_rollback(
-/*===================*/
- trx_t* trx, /*!< in: transaction */
- const char* name) /*!< in: savepoint name */
-{
- ulint i;
- ib_vector_t* savepoints;
-
- ut_a(name != NULL);
-
- savepoints = trx->fts_trx->savepoints;
-
- /* We pop all savepoints from the the top of the stack up to
- and including the instance that was found. */
- i = fts_savepoint_lookup(savepoints, name);
-
- if (i != ULINT_UNDEFINED) {
- fts_savepoint_t* savepoint;
-
- ut_a(i > 0);
-
- while (ib_vector_size(savepoints) > i) {
- fts_savepoint_t* savepoint;
-
- savepoint = static_cast<fts_savepoint_t*>(
- ib_vector_pop(savepoints));
-
- if (savepoint->name != NULL) {
- /* Since name was allocated on the heap, the
- memory will be released when the transaction
- completes. */
- savepoint->name = NULL;
-
- fts_savepoint_free(savepoint);
- }
- }
-
- /* Pop all a elements from the top of the stack that may
- have been released. We have to be careful that we don't
- delete the implied savepoint. */
-
- for (savepoint = static_cast<fts_savepoint_t*>(
- ib_vector_last(savepoints));
- ib_vector_size(savepoints) > 1
- && savepoint->name == NULL;
- savepoint = static_cast<fts_savepoint_t*>(
- ib_vector_last(savepoints))) {
-
- ib_vector_pop(savepoints);
- }
-
- /* Make sure we don't delete the implied savepoint. */
- ut_a(ib_vector_size(savepoints) > 0);
-
- /* Restore the savepoint. */
- fts_savepoint_take(trx, trx->fts_trx, name);
- }
-}
-
-/**********************************************************************//**
-Check if a table is an FTS auxiliary table name.
-@return TRUE if the name matches an auxiliary table name pattern */
-static
-ibool
-fts_is_aux_table_name(
-/*==================*/
- fts_aux_table_t*table, /*!< out: table info */
- const char* name, /*!< in: table name */
- ulint len) /*!< in: length of table name */
-{
- const char* ptr;
- char* end;
- char my_name[MAX_FULL_NAME_LEN + 1];
-
- ut_ad(len <= MAX_FULL_NAME_LEN);
- ut_memcpy(my_name, name, len);
- my_name[len] = 0;
- end = my_name + len;
-
- ptr = static_cast<const char*>(memchr(my_name, '/', len));
-
- if (ptr != NULL) {
- /* We will start the match after the '/' */
- ++ptr;
- len = end - ptr;
- }
-
- /* All auxiliary tables are prefixed with "FTS_" and the name
- length will be at the very least greater than 20 bytes. */
- if (ptr != NULL && len > 20 && strncmp(ptr, "FTS_", 4) == 0) {
- ulint i;
-
- /* Skip the prefix. */
- ptr += 4;
- len -= 4;
-
- /* Try and read the table id. */
- if (!fts_read_object_id(&table->parent_id, ptr)) {
- return(FALSE);
- }
-
- /* Skip the table id. */
- ptr = static_cast<const char*>(memchr(ptr, '_', len));
-
- if (ptr == NULL) {
- return(FALSE);
- }
-
- /* Skip the underscore. */
- ++ptr;
- ut_a(end > ptr);
- len = end - ptr;
-
- /* First search the common table suffix array. */
- for (i = 0; fts_common_tables[i] != NULL; ++i) {
-
- if (strncmp(ptr, fts_common_tables[i], len) == 0) {
- return(TRUE);
- }
- }
-
- /* Could be obsolete common tables. */
- if (strncmp(ptr, "ADDED", len) == 0
- || strncmp(ptr, "STOPWORDS", len) == 0) {
- return(true);
- }
-
- /* Try and read the index id. */
- if (!fts_read_object_id(&table->index_id, ptr)) {
- return(FALSE);
- }
-
- /* Skip the table id. */
- ptr = static_cast<const char*>(memchr(ptr, '_', len));
-
- if (ptr == NULL) {
- return(FALSE);
- }
-
- /* Skip the underscore. */
- ++ptr;
- ut_a(end > ptr);
- len = end - ptr;
-
- /* Search the FT index specific array. */
- for (i = 0; fts_index_selector[i].value; ++i) {
-
- if (strncmp(ptr, fts_get_suffix(i), len) == 0) {
- return(TRUE);
- }
- }
-
- /* Other FT index specific table(s). */
- if (strncmp(ptr, "DOC_ID", len) == 0) {
- return(TRUE);
- }
- }
-
- return(FALSE);
-}
-
-/**********************************************************************//**
-Callback function to read a single table ID column.
-@return Always return TRUE */
-static
-ibool
-fts_read_tables(
-/*============*/
- void* row, /*!< in: sel_node_t* */
- void* user_arg) /*!< in: pointer to ib_vector_t */
-{
- int i;
- fts_aux_table_t*table;
- mem_heap_t* heap;
- ibool done = FALSE;
- ib_vector_t* tables = static_cast<ib_vector_t*>(user_arg);
- sel_node_t* sel_node = static_cast<sel_node_t*>(row);
- que_node_t* exp = sel_node->select_list;
-
- /* Must be a heap allocated vector. */
- ut_a(tables->allocator->arg != NULL);
-
- /* We will use this heap for allocating strings. */
- heap = static_cast<mem_heap_t*>(tables->allocator->arg);
- table = static_cast<fts_aux_table_t*>(ib_vector_push(tables, NULL));
-
- memset(table, 0x0, sizeof(*table));
-
- /* Iterate over the columns and read the values. */
- for (i = 0; exp && !done; 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: /* NAME */
-
- if (!fts_is_aux_table_name(
- table, static_cast<const char*>(data), len)) {
- ib_vector_pop(tables);
- done = TRUE;
- break;
- }
-
- table->name = static_cast<char*>(
- mem_heap_alloc(heap, len + 1));
- memcpy(table->name, data, len);
- table->name[len] = 0;
- break;
-
- case 1: /* ID */
- ut_a(len == 8);
- table->id = mach_read_from_8(
- static_cast<const byte*>(data));
- break;
-
- default:
- ut_error;
- }
- }
-
- return(TRUE);
-}
-
-/******************************************************************//**
-Callback that sets a hex formatted FTS table's flags2 in
-SYS_TABLES. The flags is stored in MIX_LEN column.
-@return FALSE if all OK */
-static
-ibool
-fts_set_hex_format(
-/*===============*/
- void* row, /*!< in: sel_node_t* */
- void* user_arg) /*!< in: bool set/unset flag */
-{
- sel_node_t* node = static_cast<sel_node_t*>(row);
- dfield_t* dfield = que_node_get_val(node->select_list);
-
- ut_ad(dtype_get_mtype(dfield_get_type(dfield)) == DATA_INT);
- ut_ad(dfield_get_len(dfield) == sizeof(ib_uint32_t));
- /* There should be at most one matching record. So the value
- must be the default value. */
- ut_ad(mach_read_from_4(static_cast<byte*>(user_arg))
- == ULINT32_UNDEFINED);
-
- ulint flags2 = mach_read_from_4(
- static_cast<byte*>(dfield_get_data(dfield)));
-
- flags2 |= DICT_TF2_FTS_AUX_HEX_NAME;
-
- mach_write_to_4(static_cast<byte*>(user_arg), flags2);
-
- return(FALSE);
-}
-
-/*****************************************************************//**
-Update the DICT_TF2_FTS_AUX_HEX_NAME flag in SYS_TABLES.
-@return DB_SUCCESS or error code. */
-UNIV_INTERN
-dberr_t
-fts_update_hex_format_flag(
-/*=======================*/
- trx_t* trx, /*!< in/out: transaction that
- covers the update */
- table_id_t table_id, /*!< in: Table for which we want
- to set the root table->flags2 */
- bool dict_locked) /*!< in: set to true if the
- caller already owns the
- dict_sys_t::mutex. */
-{
- pars_info_t* info;
- ib_uint32_t flags2;
-
- static const char sql[] =
- "PROCEDURE UPDATE_HEX_FORMAT_FLAG() IS\n"
- "DECLARE FUNCTION my_func;\n"
- "DECLARE CURSOR c IS\n"
- " SELECT MIX_LEN "
- " FROM SYS_TABLES "
- " WHERE ID = :table_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"
- "UPDATE SYS_TABLES"
- " SET MIX_LEN = :flags2"
- " WHERE ID = :table_id;\n"
- "CLOSE c;\n"
- "END;\n";
-
- flags2 = ULINT32_UNDEFINED;
-
- info = pars_info_create();
-
- pars_info_add_ull_literal(info, "table_id", table_id);
- pars_info_bind_int4_literal(info, "flags2", &flags2);
-
- pars_info_bind_function(
- info, "my_func", fts_set_hex_format, &flags2);
-
- if (trx_get_dict_operation(trx) == TRX_DICT_OP_NONE) {
- trx_set_dict_operation(trx, TRX_DICT_OP_INDEX);
- }
-
- dberr_t err = que_eval_sql(info, sql, !dict_locked, trx);
-
- ut_a(flags2 != ULINT32_UNDEFINED);
-
- return (err);
-}
-
-/*********************************************************************//**
-Rename an aux table to HEX format. It's called when "%016llu" is used
-to format an object id in table name, which only happens in Windows. */
-static MY_ATTRIBUTE((nonnull, warn_unused_result))
-dberr_t
-fts_rename_one_aux_table_to_hex_format(
-/*===================================*/
- trx_t* trx, /*!< in: transaction */
- const fts_aux_table_t* aux_table, /*!< in: table info */
- const dict_table_t* parent_table) /*!< in: parent table name */
-{
- const char* ptr;
- fts_table_t fts_table;
- char* new_name;
- dberr_t error;
-
- ptr = strchr(aux_table->name, '/');
- ut_a(ptr != NULL);
- ++ptr;
- /* Skip "FTS_", table id and underscore */
- for (ulint i = 0; i < 2; ++i) {
- ptr = strchr(ptr, '_');
- ut_a(ptr != NULL);
- ++ptr;
- }
-
- fts_table.suffix = NULL;
- if (aux_table->index_id == 0) {
- fts_table.type = FTS_COMMON_TABLE;
-
- for (ulint i = 0; fts_common_tables[i] != NULL; ++i) {
- if (strcmp(ptr, fts_common_tables[i]) == 0) {
- fts_table.suffix = fts_common_tables[i];
- break;
- }
- }
- } else {
- fts_table.type = FTS_INDEX_TABLE;
-
- /* Skip index id and underscore */
- ptr = strchr(ptr, '_');
- ut_a(ptr != NULL);
- ++ptr;
-
- for (ulint i = 0; fts_index_selector[i].value; ++i) {
- if (strcmp(ptr, fts_get_suffix(i)) == 0) {
- fts_table.suffix = fts_get_suffix(i);
- break;
- }
- }
- }
-
- ut_a(fts_table.suffix != NULL);
-
- fts_table.parent = parent_table->name;
- fts_table.table_id = aux_table->parent_id;
- fts_table.index_id = aux_table->index_id;
- fts_table.table = parent_table;
-
- new_name = fts_get_table_name(&fts_table);
- ut_ad(strcmp(new_name, aux_table->name) != 0);
-
- if (trx_get_dict_operation(trx) == TRX_DICT_OP_NONE) {
- trx_set_dict_operation(trx, TRX_DICT_OP_INDEX);
- }
-
- error = row_rename_table_for_mysql(aux_table->name, new_name, trx,
- FALSE);
-
- if (error != DB_SUCCESS) {
- ib_logf(IB_LOG_LEVEL_WARN,
- "Failed to rename aux table \'%s\' to "
- "new format \'%s\'. ",
- aux_table->name, new_name);
- } else {
- ib_logf(IB_LOG_LEVEL_INFO,
- "Renamed aux table \'%s\' to \'%s\'.",
- aux_table->name, new_name);
- }
-
- mem_free(new_name);
-
- return (error);
-}
-
-/**********************************************************************//**
-Rename all aux tables of a parent table to HEX format. Also set aux tables'
-flags2 and parent table's flags2 with DICT_TF2_FTS_AUX_HEX_NAME.
-It's called when "%016llu" is used to format an object id in table name,
-which only happens in Windows.
-Note the ids in tables are correct but the names are old ambiguous ones.
-
-This function should make sure that either all the parent table and aux tables
-are set DICT_TF2_FTS_AUX_HEX_NAME with flags2 or none of them are set */
-static MY_ATTRIBUTE((nonnull, warn_unused_result))
-dberr_t
-fts_rename_aux_tables_to_hex_format_low(
-/*====================================*/
- trx_t* trx, /*!< in: transaction */
- dict_table_t* parent_table, /*!< in: parent table */
- ib_vector_t* tables) /*!< in: aux tables to rename. */
-{
- dberr_t error;
- ulint count;
-
- ut_ad(!DICT_TF2_FLAG_IS_SET(parent_table, DICT_TF2_FTS_AUX_HEX_NAME));
- ut_ad(!ib_vector_is_empty(tables));
-
- error = fts_update_hex_format_flag(trx, parent_table->id, true);
-
- if (error != DB_SUCCESS) {
- ib_logf(IB_LOG_LEVEL_WARN,
- "Setting parent table %s to hex format failed.",
- parent_table->name);
-
- fts_sql_rollback(trx);
- return (error);
- }
-
- DICT_TF2_FLAG_SET(parent_table, DICT_TF2_FTS_AUX_HEX_NAME);
-
- for (count = 0; count < ib_vector_size(tables); ++count) {
- dict_table_t* table;
- fts_aux_table_t* aux_table;
-
- aux_table = static_cast<fts_aux_table_t*>(
- ib_vector_get(tables, count));
-
- table = dict_table_open_on_id(aux_table->id, TRUE,
- DICT_TABLE_OP_NORMAL);
-
- ut_ad(table != NULL);
- ut_ad(!DICT_TF2_FLAG_IS_SET(table, DICT_TF2_FTS_AUX_HEX_NAME));
-
- /* Set HEX_NAME flag here to make sure we can get correct
- new table name in following function */
- DICT_TF2_FLAG_SET(table, DICT_TF2_FTS_AUX_HEX_NAME);
- error = fts_rename_one_aux_table_to_hex_format(trx,
- aux_table, parent_table);
- /* We will rollback the trx if the error != DB_SUCCESS,
- so setting the flag here is the same with setting it in
- row_rename_table_for_mysql */
- DBUG_EXECUTE_IF("rename_aux_table_fail", error = DB_ERROR;);
-
- if (error != DB_SUCCESS) {
- dict_table_close(table, TRUE, FALSE);
-
- ib_logf(IB_LOG_LEVEL_WARN,
- "Failed to rename one aux table %s "
- "Will revert all successful rename "
- "operations.", aux_table->name);
-
- fts_sql_rollback(trx);
- break;
- }
-
- error = fts_update_hex_format_flag(trx, aux_table->id, true);
- dict_table_close(table, TRUE, FALSE);
-
- if (error != DB_SUCCESS) {
- ib_logf(IB_LOG_LEVEL_WARN,
- "Setting aux table %s to hex format failed.",
- aux_table->name);
-
- fts_sql_rollback(trx);
- break;
- }
- }
-
- if (error != DB_SUCCESS) {
- ut_ad(count != ib_vector_size(tables));
- /* If rename fails, thr trx would be rolled back, we can't
- use it any more, we'll start a new background trx to do
- the reverting. */
- ut_a(trx->state == TRX_STATE_NOT_STARTED);
- bool not_rename = false;
-
- /* Try to revert those succesful rename operations
- in order to revert the ibd file rename. */
- for (ulint i = 0; i <= count; ++i) {
- dict_table_t* table;
- fts_aux_table_t* aux_table;
- trx_t* trx_bg;
- dberr_t err;
-
- aux_table = static_cast<fts_aux_table_t*>(
- ib_vector_get(tables, i));
-
- table = dict_table_open_on_id(aux_table->id, TRUE,
- DICT_TABLE_OP_NORMAL);
- ut_ad(table != NULL);
-
- if (not_rename) {
- DICT_TF2_FLAG_UNSET(table,
- DICT_TF2_FTS_AUX_HEX_NAME);
- }
-
- if (!DICT_TF2_FLAG_IS_SET(table,
- DICT_TF2_FTS_AUX_HEX_NAME)) {
- dict_table_close(table, TRUE, FALSE);
- continue;
- }
-
- trx_bg = trx_allocate_for_background();
- trx_bg->op_info = "Revert half done rename";
- trx_bg->dict_operation_lock_mode = RW_X_LATCH;
- trx_start_for_ddl(trx_bg, TRX_DICT_OP_TABLE);
-
- DICT_TF2_FLAG_UNSET(table, DICT_TF2_FTS_AUX_HEX_NAME);
- err = row_rename_table_for_mysql(table->name,
- aux_table->name,
- trx_bg, FALSE);
-
- trx_bg->dict_operation_lock_mode = 0;
- dict_table_close(table, TRUE, FALSE);
-
- if (err != DB_SUCCESS) {
- ib_logf(IB_LOG_LEVEL_WARN, "Failed to revert "
- "table %s. Please revert manually.",
- table->name);
- fts_sql_rollback(trx_bg);
- trx_free_for_background(trx_bg);
- /* Continue to clear aux tables' flags2 */
- not_rename = true;
- continue;
- }
-
- fts_sql_commit(trx_bg);
- trx_free_for_background(trx_bg);
- }
-
- DICT_TF2_FLAG_UNSET(parent_table, DICT_TF2_FTS_AUX_HEX_NAME);
- }
-
- return (error);
-}
-
-/**********************************************************************//**
-Convert an id, which is actually a decimal number but was regard as a HEX
-from a string, to its real value. */
-static
-ib_id_t
-fts_fake_hex_to_dec(
-/*================*/
- ib_id_t id) /*!< in: number to convert */
-{
- ib_id_t dec_id = 0;
- char tmp_id[FTS_AUX_MIN_TABLE_ID_LENGTH];
- int ret MY_ATTRIBUTE((unused));
-
- ret = sprintf(tmp_id, UINT64PFx, id);
- ut_ad(ret == 16);
-#ifdef _WIN32
- ret = sscanf(tmp_id, "%016llu", &dec_id);
-#else
- ret = sscanf(tmp_id, "%016" PRIu64, &dec_id);
-#endif /* _WIN32 */
- ut_ad(ret == 1);
-
- return dec_id;
-}
-
-/*********************************************************************//**
-Compare two fts_aux_table_t parent_ids.
-@return < 0 if n1 < n2, 0 if n1 == n2, > 0 if n1 > n2 */
-UNIV_INLINE
-int
-fts_check_aux_table_parent_id_cmp(
-/*==============================*/
- const void* p1, /*!< in: id1 */
- const void* p2) /*!< in: id2 */
-{
- const fts_aux_table_t* fa1 = static_cast<const fts_aux_table_t*>(p1);
- const fts_aux_table_t* fa2 = static_cast<const fts_aux_table_t*>(p2);
-
- return static_cast<int>(fa1->parent_id - fa2->parent_id);
-}
-
-/** Mark all the fts index associated with the parent table as corrupted.
-@param[in] trx transaction
-@param[in, out] parent_table fts index associated with this parent table
- will be marked as corrupted. */
-static
-void
-fts_parent_all_index_set_corrupt(
- trx_t* trx,
- dict_table_t* parent_table)
-{
- fts_t* fts = parent_table->fts;
-
- if (trx_get_dict_operation(trx) == TRX_DICT_OP_NONE) {
- trx_set_dict_operation(trx, TRX_DICT_OP_INDEX);
- }
-
- for (ulint j = 0; j < ib_vector_size(fts->indexes); j++) {
- dict_index_t* index = static_cast<dict_index_t*>(
- ib_vector_getp_const(fts->indexes, j));
- dict_set_corrupted(index,
- trx, "DROP ORPHANED TABLE");
- }
-}
-
-/** Mark the fts index which index id matches the id as corrupted.
-@param[in] trx transaction
-@param[in] id index id to search
-@param[in, out] parent_table parent table to check with all
- the index. */
-static
-void
-fts_set_index_corrupt(
- trx_t* trx,
- index_id_t id,
- dict_table_t* table)
-{
- fts_t* fts = table->fts;
-
- if (trx_get_dict_operation(trx) == TRX_DICT_OP_NONE) {
- trx_set_dict_operation(trx, TRX_DICT_OP_INDEX);
- }
-
- for (ulint j = 0; j < ib_vector_size(fts->indexes); j++) {
- dict_index_t* index = static_cast<dict_index_t*>(
- ib_vector_getp_const(fts->indexes, j));
- if (index->id == id) {
- dict_set_corrupted(index, trx,
- "DROP ORPHANED TABLE");
- break;
- }
- }
-}
-
-/** Check the index for the aux table is corrupted.
-@param[in] aux_table auxiliary table
-@retval nonzero if index is corrupted, zero for valid index */
-static
-ulint
-fts_check_corrupt_index(
- fts_aux_table_t* aux_table)
-{
- dict_table_t* table;
- dict_index_t* index;
- table = dict_table_open_on_id(
- aux_table->parent_id, TRUE, DICT_TABLE_OP_NORMAL);
-
- if (table == NULL) {
- return(0);
- }
-
- for (index = UT_LIST_GET_FIRST(table->indexes);
- index;
- index = UT_LIST_GET_NEXT(indexes, index)) {
- if (index->id == aux_table->index_id) {
- ut_ad(index->type & DICT_FTS);
- dict_table_close(table, true, false);
- return(dict_index_is_corrupted(index));
- }
- }
-
- dict_table_close(table, true, false);
- return(0);
-}
-
-/* Get parent table name if it's a fts aux table
-@param[in] aux_table_name aux table name
-@param[in] aux_table_len aux table length
-@return parent table name, or NULL */
-char*
-fts_get_parent_table_name(
- const char* aux_table_name,
- ulint aux_table_len)
-{
- fts_aux_table_t aux_table;
- char* parent_table_name = NULL;
-
- if (fts_is_aux_table_name(&aux_table, aux_table_name, aux_table_len)) {
- dict_table_t* parent_table;
-
- parent_table = dict_table_open_on_id(
- aux_table.parent_id, TRUE, DICT_TABLE_OP_NORMAL);
-
- if (parent_table != NULL) {
- parent_table_name = mem_strdupl(
- parent_table->name,
- strlen(parent_table->name));
-
- dict_table_close(parent_table, TRUE, FALSE);
- }
- }
-
- return(parent_table_name);
-}
-
-/** Check the validity of the parent table.
-@param[in] aux_table auxiliary table
-@return true if it is a valid table or false if it is not */
-static
-bool
-fts_valid_parent_table(
- const fts_aux_table_t* aux_table)
-{
- dict_table_t* parent_table;
- bool valid = false;
-
- parent_table = dict_table_open_on_id(
- aux_table->parent_id, TRUE, DICT_TABLE_OP_NORMAL);
-
- if (parent_table != NULL && parent_table->fts != NULL) {
- if (aux_table->index_id == 0) {
- valid = true;
- } else {
- index_id_t id = aux_table->index_id;
- dict_index_t* index;
-
- /* Search for the FT index in the table's list. */
- for (index = UT_LIST_GET_FIRST(parent_table->indexes);
- index;
- index = UT_LIST_GET_NEXT(indexes, index)) {
- if (index->id == id) {
- valid = true;
- break;
- }
-
- }
- }
- }
-
- if (parent_table) {
- dict_table_close(parent_table, TRUE, FALSE);
- }
-
- return(valid);
-}
-
-/** Try to rename all aux tables of the specified parent table.
-@param[in] aux_tables aux_tables to be renamed
-@param[in] parent_table parent table of all aux
- tables stored in tables. */
-static
-void
-fts_rename_aux_tables_to_hex_format(
- ib_vector_t* aux_tables,
- dict_table_t* parent_table)
-{
- dberr_t err;
- trx_t* trx_rename = trx_allocate_for_background();
- trx_rename->op_info = "Rename aux tables to hex format";
- trx_rename->dict_operation_lock_mode = RW_X_LATCH;
- trx_start_for_ddl(trx_rename, TRX_DICT_OP_TABLE);
-
- err = fts_rename_aux_tables_to_hex_format_low(trx_rename,
- parent_table, aux_tables);
-
- trx_rename->dict_operation_lock_mode = 0;
-
- if (err != DB_SUCCESS) {
-
- ib_logf(IB_LOG_LEVEL_WARN,
- "Rollback operations on all aux tables of table %s. "
- "All the fts index associated with the table are "
- "marked as corrupted. Please rebuild the "
- "index again.", parent_table->name);
- fts_sql_rollback(trx_rename);
-
- /* Corrupting the fts index related to parent table. */
- trx_t* trx_corrupt;
- trx_corrupt = trx_allocate_for_background();
- trx_corrupt->dict_operation_lock_mode = RW_X_LATCH;
- trx_start_for_ddl(trx_corrupt, TRX_DICT_OP_TABLE);
- fts_parent_all_index_set_corrupt(trx_corrupt, parent_table);
- trx_corrupt->dict_operation_lock_mode = 0;
- fts_sql_commit(trx_corrupt);
- trx_free_for_background(trx_corrupt);
- } else {
- fts_sql_commit(trx_rename);
- }
-
- trx_free_for_background(trx_rename);
- ib_vector_reset(aux_tables);
-}
-
-/** Set the hex format flag for the parent table.
-@param[in, out] parent_table parent table
-@param[in] trx transaction */
-static
-void
-fts_set_parent_hex_format_flag(
- dict_table_t* parent_table,
- trx_t* trx)
-{
- if (!DICT_TF2_FLAG_IS_SET(parent_table,
- DICT_TF2_FTS_AUX_HEX_NAME)) {
- DBUG_EXECUTE_IF("parent_table_flag_fail",
- ib_logf(IB_LOG_LEVEL_FATAL,
- "Setting parent table %s to hex format "
- "failed. Please try to restart the server "
- "again, if it doesn't work, the system "
- "tables might be corrupted.",
- parent_table->name);
- return;);
-
- dberr_t err = fts_update_hex_format_flag(
- trx, parent_table->id, true);
-
- if (err != DB_SUCCESS) {
- ib_logf(IB_LOG_LEVEL_FATAL,
- "Setting parent table %s to hex format "
- "failed. Please try to restart the server "
- "again, if it doesn't work, the system "
- "tables might be corrupted.",
- parent_table->name);
- } else {
- DICT_TF2_FLAG_SET(
- parent_table, DICT_TF2_FTS_AUX_HEX_NAME);
- }
- }
-}
-
-/** Drop the obsolete auxilary table.
-@param[in] tables tables to be dropped. */
-static
-void
-fts_drop_obsolete_aux_table_from_vector(
- ib_vector_t* tables)
-{
- dberr_t err;
-
- for (ulint count = 0; count < ib_vector_size(tables);
- ++count) {
-
- fts_aux_table_t* aux_drop_table;
- aux_drop_table = static_cast<fts_aux_table_t*>(
- ib_vector_get(tables, count));
- trx_t* trx_drop = trx_allocate_for_background();
- trx_drop->op_info = "Drop obsolete aux tables";
- trx_drop->dict_operation_lock_mode = RW_X_LATCH;
- trx_start_for_ddl(trx_drop, TRX_DICT_OP_TABLE);
-
- err = row_drop_table_for_mysql(
- aux_drop_table->name, trx_drop, false, true);
-
- trx_drop->dict_operation_lock_mode = 0;
-
- if (err != DB_SUCCESS) {
- /* We don't need to worry about the
- failure, since server would try to
- drop it on next restart, even if
- the table was broken. */
- ib_logf(IB_LOG_LEVEL_WARN,
- "Fail to drop obsolete aux table '%s', which "
- "is harmless. will try to drop it on next "
- "restart.", aux_drop_table->name);
- fts_sql_rollback(trx_drop);
- } else {
- ib_logf(IB_LOG_LEVEL_INFO,
- "Dropped obsolete aux table '%s'.",
- aux_drop_table->name);
-
- fts_sql_commit(trx_drop);
- }
-
- trx_free_for_background(trx_drop);
- }
-}
-
-/** Drop all the auxiliary table present in the vector.
-@param[in] trx transaction
-@param[in] tables tables to be dropped */
-static
-void
-fts_drop_aux_table_from_vector(
- trx_t* trx,
- ib_vector_t* tables)
-{
- for (ulint count = 0; count < ib_vector_size(tables);
- ++count) {
- fts_aux_table_t* aux_drop_table;
- aux_drop_table = static_cast<fts_aux_table_t*>(
- ib_vector_get(tables, count));
-
- /* Check for the validity of the parent table */
- if (!fts_valid_parent_table(aux_drop_table)) {
- ib_logf(IB_LOG_LEVEL_WARN,
- "Parent table of FTS auxiliary table %s not "
- "found.", aux_drop_table->name);
- dberr_t err = fts_drop_table(trx, aux_drop_table->name);
- if (err == DB_FAIL) {
- char* path = fil_make_ibd_name(
- aux_drop_table->name, false);
- os_file_delete_if_exists(innodb_file_data_key,
- path);
- mem_free(path);
- }
- }
- }
-}
-
-/**********************************************************************//**
-Check and drop all orphaned FTS auxiliary tables, those that don't have
-a parent table or FTS index defined on them.
-@return DB_SUCCESS or error code */
-static MY_ATTRIBUTE((nonnull))
-void
-fts_check_and_drop_orphaned_tables(
-/*===============================*/
- trx_t* trx, /*!< in: transaction */
- ib_vector_t* tables) /*!< in: tables to check */
-{
- mem_heap_t* heap;
- ib_vector_t* aux_tables_to_rename;
- ib_vector_t* invalid_aux_tables;
- ib_vector_t* valid_aux_tables;
- ib_vector_t* drop_aux_tables;
- ib_vector_t* obsolete_aux_tables;
- ib_alloc_t* heap_alloc;
-
- heap = mem_heap_create(1024);
- heap_alloc = ib_heap_allocator_create(heap);
-
- /* We store all aux tables belonging to the same parent table here,
- and rename all these tables in a batch mode. */
- aux_tables_to_rename = ib_vector_create(heap_alloc,
- sizeof(fts_aux_table_t), 128);
-
- /* We store all fake auxiliary table and orphaned table here. */
- invalid_aux_tables = ib_vector_create(heap_alloc,
- sizeof(fts_aux_table_t), 128);
-
- /* We store all valid aux tables. We use this to filter the
- fake auxiliary table from invalid auxiliary tables. */
- valid_aux_tables = ib_vector_create(heap_alloc,
- sizeof(fts_aux_table_t), 128);
-
- /* We store all auxiliary tables to be dropped. */
- drop_aux_tables = ib_vector_create(heap_alloc,
- sizeof(fts_aux_table_t), 128);
-
- /* We store all obsolete auxiliary tables to be dropped. */
- obsolete_aux_tables = ib_vector_create(heap_alloc,
- sizeof(fts_aux_table_t), 128);
-
- /* Sort by parent_id first, in case rename will fail */
- ib_vector_sort(tables, fts_check_aux_table_parent_id_cmp);
-
- for (ulint i = 0; i < ib_vector_size(tables); ++i) {
- dict_table_t* parent_table;
- fts_aux_table_t* aux_table;
- bool drop = false;
- dict_table_t* table;
- fts_aux_table_t* next_aux_table = NULL;
- ib_id_t orig_parent_id = 0;
- ib_id_t orig_index_id = 0;
- bool rename = false;
-
- aux_table = static_cast<fts_aux_table_t*>(
- ib_vector_get(tables, i));
-
- table = dict_table_open_on_id(
- aux_table->id, TRUE, DICT_TABLE_OP_NORMAL);
- orig_parent_id = aux_table->parent_id;
- orig_index_id = aux_table->index_id;
-
- if (table == NULL || strcmp(table->name, aux_table->name)) {
-
- bool fake_aux = false;
-
- if (table != NULL) {
- dict_table_close(table, TRUE, FALSE);
- }
-
- if (i + 1 < ib_vector_size(tables)) {
- next_aux_table = static_cast<fts_aux_table_t*>(
- ib_vector_get(tables, i + 1));
- }
-
- /* To know whether aux table is fake fts or
- orphan fts table. */
- for (ulint count = 0;
- count < ib_vector_size(valid_aux_tables);
- count++) {
- fts_aux_table_t* valid_aux;
- valid_aux = static_cast<fts_aux_table_t*>(
- ib_vector_get(valid_aux_tables, count));
- if (strcmp(valid_aux->name,
- aux_table->name) == 0) {
- fake_aux = true;
- break;
- }
- }
-
- /* All aux tables of parent table, whose id is
- last_parent_id, have been checked, try to rename
- them if necessary. */
- if ((next_aux_table == NULL
- || orig_parent_id != next_aux_table->parent_id)
- && (!ib_vector_is_empty(aux_tables_to_rename))) {
-
- ulint parent_id = fts_fake_hex_to_dec(
- aux_table->parent_id);
-
- parent_table = dict_table_open_on_id(
- parent_id, TRUE,
- DICT_TABLE_OP_NORMAL);
-
- fts_rename_aux_tables_to_hex_format(
- aux_tables_to_rename, parent_table);
-
- dict_table_close(parent_table, TRUE,
- FALSE);
- }
-
- /* If the aux table is fake aux table. Skip it. */
- if (!fake_aux) {
- ib_vector_push(invalid_aux_tables, aux_table);
- }
-
- continue;
- } else if (!DICT_TF2_FLAG_IS_SET(table,
- DICT_TF2_FTS_AUX_HEX_NAME)) {
-
- aux_table->parent_id = fts_fake_hex_to_dec(
- aux_table->parent_id);
-
- if (aux_table->index_id != 0) {
- aux_table->index_id = fts_fake_hex_to_dec(
- aux_table->index_id);
- }
-
- ut_ad(aux_table->id > aux_table->parent_id);
-
- /* Check whether parent table id and index id
- are stored as decimal format. */
- if (fts_valid_parent_table(aux_table)) {
-
- parent_table = dict_table_open_on_id(
- aux_table->parent_id, true,
- DICT_TABLE_OP_NORMAL);
-
- ut_ad(parent_table != NULL);
- ut_ad(parent_table->fts != NULL);
-
- if (!DICT_TF2_FLAG_IS_SET(
- parent_table,
- DICT_TF2_FTS_AUX_HEX_NAME)) {
- rename = true;
- }
-
- dict_table_close(parent_table, TRUE, FALSE);
- }
-
- if (!rename) {
- /* Reassign the original value of
- aux table if it is not in decimal format */
- aux_table->parent_id = orig_parent_id;
- aux_table->index_id = orig_index_id;
- }
- }
-
- if (table != NULL) {
- dict_table_close(table, true, false);
- }
-
- if (!rename) {
- /* Check the validity of the parent table. */
- if (!fts_valid_parent_table(aux_table)) {
- drop = true;
- }
- }
-
- /* Filter out the fake aux table by comparing with the
- current valid auxiliary table name . */
- for (ulint count = 0;
- count < ib_vector_size(invalid_aux_tables); count++) {
- fts_aux_table_t* invalid_aux;
- invalid_aux = static_cast<fts_aux_table_t*>(
- ib_vector_get(invalid_aux_tables, count));
- if (strcmp(invalid_aux->name, aux_table->name) == 0) {
- ib_vector_remove(
- invalid_aux_tables,
- *reinterpret_cast<void**>(invalid_aux));
- break;
- }
- }
-
- ib_vector_push(valid_aux_tables, aux_table);
-
- /* If the index associated with aux table is corrupted,
- skip it. */
- if (fts_check_corrupt_index(aux_table) > 0) {
-
- if (i + 1 < ib_vector_size(tables)) {
- next_aux_table = static_cast<fts_aux_table_t*>(
- ib_vector_get(tables, i + 1));
- }
-
- if (next_aux_table == NULL
- || orig_parent_id != next_aux_table->parent_id) {
-
- parent_table = dict_table_open_on_id(
- aux_table->parent_id, TRUE,
- DICT_TABLE_OP_NORMAL);
-
- if (!ib_vector_is_empty(aux_tables_to_rename)) {
- fts_rename_aux_tables_to_hex_format(
- aux_tables_to_rename, parent_table);
-
- } else {
- fts_set_parent_hex_format_flag(
- parent_table, trx);
- }
-
- dict_table_close(parent_table, TRUE, FALSE);
- }
-
- continue;
- }
-
- parent_table = dict_table_open_on_id(
- aux_table->parent_id, TRUE, DICT_TABLE_OP_NORMAL);
-
- if (drop) {
- ib_vector_push(drop_aux_tables, aux_table);
- } else {
- if (FTS_IS_OBSOLETE_AUX_TABLE(aux_table->name)) {
-
- /* Current table could be one of the three
- obsolete tables, in this case, we should
- always try to drop it but not rename it.
- This could happen when we try to upgrade
- from older server to later one, which doesn't
- contain these obsolete tables. */
- ib_vector_push(obsolete_aux_tables, aux_table);
- continue;
- }
- }
-
- /* If the aux table is in decimal format, we should
- rename it, so push it to aux_tables_to_rename */
- if (!drop && rename) {
- ib_vector_push(aux_tables_to_rename, aux_table);
- }
-
- if (i + 1 < ib_vector_size(tables)) {
- next_aux_table = static_cast<fts_aux_table_t*>(
- ib_vector_get(tables, i + 1));
- }
-
- if ((next_aux_table == NULL
- || orig_parent_id != next_aux_table->parent_id)
- && !ib_vector_is_empty(aux_tables_to_rename)) {
- /* All aux tables of parent table, whose id is
- last_parent_id, have been checked, try to rename
- them if necessary. We had better use a new background
- trx to rename rather than the original trx, in case
- any failure would cause a complete rollback. */
- ut_ad(rename);
- ut_ad(!DICT_TF2_FLAG_IS_SET(
- parent_table, DICT_TF2_FTS_AUX_HEX_NAME));
-
- fts_rename_aux_tables_to_hex_format(
- aux_tables_to_rename,parent_table);
- }
-
- /* The IDs are already in correct hex format. */
- if (!drop && !rename) {
- dict_table_t* table;
-
- table = dict_table_open_on_id(
- aux_table->id, TRUE, DICT_TABLE_OP_NORMAL);
- if (table != NULL
- && strcmp(table->name, aux_table->name)) {
- dict_table_close(table, TRUE, FALSE);
- table = NULL;
- }
-
- if (table != NULL
- && !DICT_TF2_FLAG_IS_SET(
- table,
- DICT_TF2_FTS_AUX_HEX_NAME)) {
-
- DBUG_EXECUTE_IF("aux_table_flag_fail",
- ib_logf(IB_LOG_LEVEL_WARN,
- "Setting aux table %s to hex "
- "format failed.", table->name);
- fts_set_index_corrupt(
- trx, aux_table->index_id,
- parent_table);
- goto table_exit;);
-
- dberr_t err = fts_update_hex_format_flag(
- trx, table->id, true);
-
- if (err != DB_SUCCESS) {
- ib_logf(IB_LOG_LEVEL_WARN,
- "Setting aux table %s to hex "
- "format failed.", table->name);
-
- fts_set_index_corrupt(
- trx, aux_table->index_id,
- parent_table);
- } else {
- DICT_TF2_FLAG_SET(table,
- DICT_TF2_FTS_AUX_HEX_NAME);
- }
- }
-#ifndef DBUG_OFF
-table_exit:
-#endif /* !DBUG_OFF */
-
- if (table != NULL) {
- dict_table_close(table, TRUE, FALSE);
- }
-
- ut_ad(parent_table != NULL);
-
- fts_set_parent_hex_format_flag(
- parent_table, trx);
- }
-
- if (parent_table != NULL) {
- dict_table_close(parent_table, TRUE, FALSE);
- }
- }
-
- fts_drop_aux_table_from_vector(trx, invalid_aux_tables);
- fts_drop_aux_table_from_vector(trx, drop_aux_tables);
- fts_sql_commit(trx);
-
- fts_drop_obsolete_aux_table_from_vector(obsolete_aux_tables);
-
- /* Free the memory allocated at the beginning */
- if (heap != NULL) {
- mem_heap_free(heap);
- }
-}
-
-/**********************************************************************//**
-Drop all orphaned FTS auxiliary tables, those that don't have a parent
-table or FTS index defined on them. */
-UNIV_INTERN
-void
-fts_drop_orphaned_tables(void)
-/*==========================*/
-{
- trx_t* trx;
- pars_info_t* info;
- mem_heap_t* heap;
- que_t* graph;
- ib_vector_t* tables;
- ib_alloc_t* heap_alloc;
- space_name_list_t space_name_list;
- dberr_t error = DB_SUCCESS;
-
- /* Note: We have to free the memory after we are done with the list. */
- error = fil_get_space_names(space_name_list);
-
- if (error == DB_OUT_OF_MEMORY) {
- ib_logf(IB_LOG_LEVEL_ERROR, "Out of memory");
- ut_error;
- }
-
- heap = mem_heap_create(1024);
- heap_alloc = ib_heap_allocator_create(heap);
-
- /* We store the table ids of all the FTS indexes that were found. */
- tables = ib_vector_create(heap_alloc, sizeof(fts_aux_table_t), 128);
-
- /* Get the list of all known .ibd files and check for orphaned
- FTS auxiliary files in that list. We need to remove them because
- users can't map them back to table names and this will create
- unnecessary clutter. */
-
- for (space_name_list_t::iterator it = space_name_list.begin();
- it != space_name_list.end();
- ++it) {
-
- fts_aux_table_t* fts_aux_table;
-
- fts_aux_table = static_cast<fts_aux_table_t*>(
- ib_vector_push(tables, NULL));
-
- memset(fts_aux_table, 0x0, sizeof(*fts_aux_table));
-
- if (!fts_is_aux_table_name(fts_aux_table, *it, strlen(*it))) {
- ib_vector_pop(tables);
- } else {
- ulint len = strlen(*it);
-
- fts_aux_table->id = fil_get_space_id_for_table(*it);
-
- /* We got this list from fil0fil.cc. The tablespace
- with this name must exist. */
- ut_a(fts_aux_table->id != ULINT_UNDEFINED);
-
- fts_aux_table->name = static_cast<char*>(
- mem_heap_dup(heap, *it, len + 1));
-
- fts_aux_table->name[len] = 0;
- }
- }
-
- trx = trx_allocate_for_background();
- trx->op_info = "dropping orphaned FTS tables";
- row_mysql_lock_data_dictionary(trx);
-
- info = pars_info_create();
-
- pars_info_bind_function(info, "my_func", fts_read_tables, tables);
-
- graph = fts_parse_sql_no_dict_lock(
- NULL,
- info,
- "DECLARE FUNCTION my_func;\n"
- "DECLARE CURSOR c IS"
- " SELECT NAME, ID "
- " FROM SYS_TABLES;\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) {
- fts_check_and_drop_orphaned_tables(trx, tables);
- break; /* Exit the loop. */
- } else {
- ib_vector_reset(tables);
-
- fts_sql_rollback(trx);
-
- ut_print_timestamp(stderr);
-
- if (error == DB_LOCK_WAIT_TIMEOUT) {
- ib_logf(IB_LOG_LEVEL_WARN,
- "lock wait timeout reading SYS_TABLES. "
- "Retrying!");
-
- trx->error_state = DB_SUCCESS;
- } else {
- ib_logf(IB_LOG_LEVEL_ERROR,
- "(%s) while reading SYS_TABLES.",
- ut_strerr(error));
-
- break; /* Exit the loop. */
- }
- }
- }
-
- que_graph_free(graph);
-
- row_mysql_unlock_data_dictionary(trx);
-
- trx_free_for_background(trx);
-
- if (heap != NULL) {
- mem_heap_free(heap);
- }
-
- /** Free the memory allocated to store the .ibd names. */
- for (space_name_list_t::iterator it = space_name_list.begin();
- it != space_name_list.end();
- ++it) {
-
- delete[] *it;
- }
-}
-
-/**********************************************************************//**
-Check whether user supplied stopword table is of the right format.
-Caller is responsible to hold dictionary locks.
-@return the stopword column charset if qualifies */
-UNIV_INTERN
-CHARSET_INFO*
-fts_valid_stopword_table(
-/*=====================*/
- const char* stopword_table_name) /*!< in: Stopword table
- name */
-{
- dict_table_t* table;
- dict_col_t* col = NULL;
-
- if (!stopword_table_name) {
- return(NULL);
- }
-
- table = dict_table_get_low(stopword_table_name);
-
- if (!table) {
- fprintf(stderr,
- "InnoDB: user stopword table %s does not exist.\n",
- stopword_table_name);
-
- return(NULL);
- } else {
- const char* col_name;
-
- col_name = dict_table_get_col_name(table, 0);
-
- if (ut_strcmp(col_name, "value")) {
- fprintf(stderr,
- "InnoDB: invalid column name for stopword "
- "table %s. Its first column must be named as "
- "'value'.\n", stopword_table_name);
-
- return(NULL);
- }
-
- col = dict_table_get_nth_col(table, 0);
-
- if (col->mtype != DATA_VARCHAR
- && col->mtype != DATA_VARMYSQL) {
- fprintf(stderr,
- "InnoDB: invalid column type for stopword "
- "table %s. Its first column must be of "
- "varchar type\n", stopword_table_name);
-
- return(NULL);
- }
- }
-
- ut_ad(col);
-
- return(innobase_get_fts_charset(
- static_cast<int>(col->prtype & DATA_MYSQL_TYPE_MASK),
- static_cast<uint>(dtype_get_charset_coll(col->prtype))));
-}
-
-/**********************************************************************//**
-This function loads the stopword into the FTS cache. It also
-records/fetches stopword configuration to/from FTS configure
-table, depending on whether we are creating or reloading the
-FTS.
-@return TRUE if load operation is successful */
-UNIV_INTERN
-ibool
-fts_load_stopword(
-/*==============*/
- const dict_table_t*
- table, /*!< in: Table with FTS */
- trx_t* trx, /*!< in: Transactions */
- const char* global_stopword_table, /*!< in: Global stopword table
- name */
- const char* session_stopword_table, /*!< in: Session stopword table
- name */
- ibool stopword_is_on, /*!< in: Whether stopword
- option is turned on/off */
- ibool reload) /*!< in: Whether it is
- for reloading FTS table */
-{
- fts_table_t fts_table;
- fts_string_t str;
- dberr_t error = DB_SUCCESS;
- ulint use_stopword;
- fts_cache_t* cache;
- const char* stopword_to_use = NULL;
- ibool new_trx = FALSE;
- byte str_buffer[MAX_FULL_NAME_LEN + 1];
-
- FTS_INIT_FTS_TABLE(&fts_table, "CONFIG", FTS_COMMON_TABLE, table);
-
- cache = table->fts->cache;
-
- if (!reload && !(cache->stopword_info.status
- & STOPWORD_NOT_INIT)) {
- return(TRUE);
- }
-
- if (!trx) {
- trx = trx_allocate_for_background();
- trx->op_info = "upload FTS stopword";
- new_trx = TRUE;
- }
-
- /* First check whether stopword filtering is turned off */
- if (reload) {
- error = fts_config_get_ulint(
- trx, &fts_table, FTS_USE_STOPWORD, &use_stopword);
- } else {
- use_stopword = (ulint) stopword_is_on;
-
- error = fts_config_set_ulint(
- trx, &fts_table, FTS_USE_STOPWORD, use_stopword);
- }
-
- if (error != DB_SUCCESS) {
- goto cleanup;
- }
-
- /* If stopword is turned off, no need to continue to load the
- stopword into cache, but still need to do initialization */
- if (!use_stopword) {
- cache->stopword_info.status = STOPWORD_OFF;
- goto cleanup;
- }
-
- if (reload) {
- /* Fetch the stopword table name from FTS config
- table */
- str.f_n_char = 0;
- str.f_str = str_buffer;
- str.f_len = sizeof(str_buffer) - 1;
-
- error = fts_config_get_value(
- trx, &fts_table, FTS_STOPWORD_TABLE_NAME, &str);
-
- if (error != DB_SUCCESS) {
- goto cleanup;
- }
-
- if (strlen((char*) str.f_str) > 0) {
- stopword_to_use = (const char*) str.f_str;
- }
- } else {
- stopword_to_use = (session_stopword_table)
- ? session_stopword_table : global_stopword_table;
- }
-
- if (stopword_to_use
- && fts_load_user_stopword(table->fts, stopword_to_use,
- &cache->stopword_info)) {
- /* Save the stopword table name to the configure
- table */
- if (!reload) {
- str.f_n_char = 0;
- str.f_str = (byte*) stopword_to_use;
- str.f_len = ut_strlen(stopword_to_use);
-
- error = fts_config_set_value(
- trx, &fts_table, FTS_STOPWORD_TABLE_NAME, &str);
- }
- } else {
- /* Load system default stopword list */
- fts_load_default_stopword(&cache->stopword_info);
- }
-
-cleanup:
- if (new_trx) {
- if (error == DB_SUCCESS) {
- fts_sql_commit(trx);
- } else {
- fts_sql_rollback(trx);
- }
-
- trx_free_for_background(trx);
- }
-
- if (!cache->stopword_info.cached_stopword) {
- cache->stopword_info.cached_stopword = rbt_create(
- sizeof(fts_tokenizer_word_t), fts_utf8_string_cmp);
- }
-
- return(error == DB_SUCCESS);
-}
-
-/**********************************************************************//**
-Callback function when we initialize the FTS at the start up
-time. It recovers the maximum Doc IDs presented in the current table.
-@return: always returns TRUE */
-static
-ibool
-fts_init_get_doc_id(
-/*================*/
- void* row, /*!< in: sel_node_t* */
- void* user_arg) /*!< in: fts cache */
-{
- doc_id_t doc_id = FTS_NULL_DOC_ID;
- sel_node_t* node = static_cast<sel_node_t*>(row);
- que_node_t* exp = node->select_list;
- fts_cache_t* cache = static_cast<fts_cache_t*>(user_arg);
-
- 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<doc_id_t>(mach_read_from_8(
- static_cast<const byte*>(data)));
-
- 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
-time. It recovers Doc IDs that have not sync-ed to the auxiliary
-table, and require to bring them back into FTS index.
-@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<fts_get_doc_t*>(user_arg);
- doc_id_t doc_id = FTS_NULL_DOC_ID;
- sel_node_t* node = static_cast<sel_node_t*>(row);
- que_node_t* exp = node->select_list;
- fts_cache_t* cache = get_doc->cache;
-
- 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<doc_id_t>(mach_read_from_8(
- static_cast<const byte*>(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) {
- ulint prtype = dfield->type.prtype;
-
- get_doc->index_cache->charset =
- innobase_get_fts_charset(
- (int)(prtype & DATA_MYSQL_TYPE_MASK),
- (uint) dtype_get_charset_coll(prtype));
- }
-
- doc.charset = get_doc->index_cache->charset;
-
- if (dfield_is_ext(dfield)) {
- dict_table_t* table = cache->sync->table;
- ulint zip_size = dict_table_zip_size(table);
-
- doc.text.f_str = btr_copy_externally_stored_field(
- &doc.text.f_len,
- static_cast<byte*>(dfield_get_data(dfield)),
- zip_size, len,
- static_cast<mem_heap_t*>(doc.self_heap->arg),
- NULL);
- } else {
- doc.text.f_str = static_cast<byte*>(
- dfield_get_data(dfield));
-
- doc.text.f_len = len;
- }
-
- if (field_no == 1) {
- fts_tokenize_document(&doc, NULL);
- } else {
- fts_tokenize_document_next(&doc, doc_len, NULL);
- }
-
- 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++;
-
- if (doc_id >= cache->next_doc_id) {
- cache->next_doc_id = doc_id + 1;
- }
-
- return(TRUE);
-}
-
-/**********************************************************************//**
-This function brings FTS index in sync when FTS index is first
-used. There are documents that have not yet sync-ed to auxiliary
-tables from last server abnormally shutdown, we will need to bring
-such document into FTS cache before any further operations
-@return TRUE if all OK */
-UNIV_INTERN
-ibool
-fts_init_index(
-/*===========*/
- dict_table_t* table, /*!< in: Table with FTS */
- ibool has_cache_lock) /*!< in: Whether we already have
- cache lock */
-{
- dict_index_t* index;
- doc_id_t start_doc;
- fts_get_doc_t* get_doc = NULL;
- fts_cache_t* cache = table->fts->cache;
- bool need_init = false;
-
- ut_ad(!mutex_own(&dict_sys->mutex));
-
- /* First check cache->get_docs is initialized */
- if (!has_cache_lock) {
- rw_lock_x_lock(&cache->lock);
- }
-
- rw_lock_x_lock(&cache->init_lock);
- if (cache->get_docs == NULL) {
- cache->get_docs = fts_get_docs_create(cache);
- }
- rw_lock_x_unlock(&cache->init_lock);
-
- if (table->fts->fts_status & ADDED_TABLE_SYNCED) {
- goto func_exit;
- }
-
- need_init = true;
-
- start_doc = cache->synced_doc_id;
-
- if (!start_doc) {
- fts_cmp_set_sync_doc_id(table, 0, TRUE, &start_doc);
- cache->synced_doc_id = start_doc;
- }
-
- /* No FTS index, this is the case when previous FTS index
- dropped, and we re-initialize the Doc ID system for subsequent
- insertion */
- if (ib_vector_is_empty(cache->get_docs)) {
- index = dict_table_get_index_on_name(table, FTS_DOC_ID_INDEX_NAME);
-
- ut_a(index);
-
- fts_doc_fetch_by_doc_id(NULL, start_doc, index,
- FTS_FETCH_DOC_BY_ID_LARGE,
- fts_init_get_doc_id, cache);
- } else {
- if (table->fts->cache->stopword_info.status
- & STOPWORD_NOT_INIT) {
- fts_load_stopword(table, NULL, NULL, NULL, TRUE, TRUE);
- }
-
- for (ulint i = 0; i < ib_vector_size(cache->get_docs); ++i) {
- get_doc = static_cast<fts_get_doc_t*>(
- ib_vector_get(cache->get_docs, i));
-
- index = get_doc->index_cache->index;
-
- fts_doc_fetch_by_doc_id(NULL, start_doc, index,
- FTS_FETCH_DOC_BY_ID_LARGE,
- fts_init_recover_doc, get_doc);
- }
- }
-
- table->fts->fts_status |= ADDED_TABLE_SYNCED;
-
- fts_get_docs_clear(cache->get_docs);
-
-func_exit:
- if (!has_cache_lock) {
- rw_lock_x_unlock(&cache->lock);
- }
-
- if (need_init) {
- mutex_enter(&dict_sys->mutex);
- /* Register the table with the optimize thread. */
- fts_optimize_add_table(table);
- mutex_exit(&dict_sys->mutex);
- }
-
- return(TRUE);
-}