/** For phrase matching, first we collect the documents and the positions
then we match. */ struct fts_match_t {
doc_id_t doc_id; /*!< Document id */
ulint start; /*!< Start the phrase match from thisoffsetwithinthepositions
vector. */
ib_vector_t* positions; /*!< Offsets of a word in a
document */
};
/** For matching tokens in a phrase search. We use this data structure in thecallbackthatdetermineswhetheradocumentshouldbeacceptedor
rejected for a phrase search. */ struct fts_select_t {
doc_id_t doc_id; /*!< The document id to match */
ulint min_pos; /*!< For found to be TRUE at least onepositionmustbegreaterthan
min_pos. */
ibool found; /*!< TRUE if found */
fts_word_freq_t*
word_freq; /*!< Word frequency instance of the currentwordbeinglookedupin
the FTS index */
};
/** structure defines a set of ranges for original documents, each of which hasaminimumpositionandmaximumposition.Textinsuchrangeshould containallwordsintheproximitysearch.Wewillneedtocountthe wordsinsuchrangetomakesureitislessthanthespecifieddistance
of the proximity search */ struct fts_proximity_t {
ulint n_pos; /*!< number of position set, defines arange(mintomax)containingall
matching words */
pos_vector_t min_pos; /*!< the minimum position (in bytes)
of the range */
pos_vector_t max_pos; /*!< the maximum position (in bytes)
of the range */
};
/** The match positions and tokens to match */ struct fts_phrase_t {
fts_phrase_t(const dict_table_t* table)
:
found(false),
match(NULL),
tokens(NULL),
distance(0),
charset(NULL),
heap(NULL),
zip_size(table->space->zip_size()),
proximity_pos(NULL),
parser(NULL)
{
}
/** Match result */
ibool found;
/** Positions within text */ const fts_match_t* match;
/** Tokens to match */ const ib_vector_t* tokens;
/** For matching on proximity distance. Can be 0 for exact match */
ulint distance;
/** Phrase match charset */
CHARSET_INFO* charset;
/** Heap for word processing */
mem_heap_t* heap;
/** ROW_FORMAT=COMPRESSED page size, or 0 */ const ulint zip_size;
/** Position info for proximity search verification. Records the
min and max position of words matched */
fts_proximity_t* proximity_pos;
/** Parameter passed to fts phrase match by parser */ struct fts_phrase_param_t {
fts_phrase_t* phrase; /*!< Match phrase instance */
ulint token_index; /*!< Index of token to match next */
mem_heap_t* heap; /*!< Heap for word processing */
};
/** For storing the frequency of a word/term in a document */ struct fts_doc_freq_t {
doc_id_t doc_id; /*!< Document id */
ulint freq; /*!< Frequency of a word in a document */
};
/** To determine the word frequency per document. */ struct fts_word_freq_t {
fts_string_t word; /*!< Word for which we need the freq,
it's allocated on the query heap */
ib_rbt_t* doc_freqs; /*!< RB Tree for storing per document wordfrequencies.Theelementsare
of type fts_doc_freq_t */
ib_uint64_t doc_count; /*!< Total number of documents that
contain this word */ double idf; /*!< Inverse document frequency */
};
/******************************************************************** Readandfilternodes.
@return fts_node_t instance */ static
dberr_t
fts_query_filter_doc_ids( /*=====================*/
fts_query_t* query, /*!< in: query instance */ const fts_string_t* word, /*!< in: the current word */
fts_word_freq_t* word_freq, /*!< in/out: word frequency */ const fts_node_t* node, /*!< in: current FTS node */ void* data, /*!< in: doc id ilist */
ulint len, /*!< in: doc id ilist size */
ibool calc_doc_count);/*!< in: whether to remember doc
count */
/** Process (nested) sub-expression, create a new result set to store the sub-expressionresultbyprocessingnodesundercurrentsub-expression list.Mergethesub-expressionresultwiththatofparentexpressionlist. @param[in,out]nodecurrentrootnode @param[in,out]visitorcallbackfunction @param[in,out]argargumentforcallback
@return DB_SUCCESS if all go well */ static
dberr_t
fts_ast_visit_sub_exp(
fts_ast_node_t* node,
fts_ast_callback visitor, void* arg);
/** Process query records for FTS queries. @paramrecrecord @paramindexindex @paramoffsetsrecordoffsets @paramuser_arguserargument
@return DB_SUCCESS to continue processing, DB_SUCCESS_LOCKED_REC to stop, or error code */ static dberr_t node_query_processor( const rec_t* rec, const dict_index_t* index, const rec_offs* offsets, void* user_arg)
{
fts_query_t* query= static_cast<fts_query_t*>(user_arg);
/* Need to consider the wildcard search case, the word frequency iscreatedonthesearchstringnottheactualword.Soweneed
to assign the frequency on search string behalf. */ if (query->cur_node->type == FTS_AST_TERM && query->cur_node->term.wildcard)
{
term.f_len = query->cur_node->term.ptr->len;
ut_ad(FTS_MAX_WORD_LEN >= term.f_len);
memcpy(term.f_str, query->cur_node->term.ptr->str, term.f_len);
} else
{
term.f_len = word_len;
ut_ad(FTS_MAX_WORD_LEN >= word_len);
memcpy(term.f_str, word_data, word_len);
}
/* Lookup the word in our rb tree, it must exist. */
ib_rbt_bound_t parent; int ret= rbt_search(query->word_freqs, &parent, &term);
/*************************************************************//** This function implements a simple "blind" query expansion search:
words in documents found in the first search pass will be used as
search arguments to search the document again, thus "expand"
the search result set.
@return DB_SUCCESS if success, otherwise the error code */ static
dberr_t
fts_expand_query( /*=============*/
dict_index_t* index, /*!< in: FTS index to search */
fts_query_t* query) /*!< in: query result, to be freed
by the client */
MY_ATTRIBUTE((nonnull, warn_unused_result)); /*************************************************************//** This function finds documents that contain all words in a
phrase or proximity search. Andif proximity search, verify
the words are close enough to each other, as in specified distance. This function is called for phrase and proximity search.
@returnTRUEif documents are found, FALSEif otherwise */ static
ibool
fts_phrase_or_proximity_search( /*===========================*/
fts_query_t* query, /*!< in/out: query instance query->doc_idsmightbeinstantiated
with qualified doc IDs */
ib_vector_t* tokens); /*!< in: Tokens contain words */ /*************************************************************//** This function checks whether words in result documents are close to
each other (within proximity range as specified by "distance"). If"distance" is MAX_ULINT, then it will find all combinations of
positions of matching words and store min and max positions
in the "qualified_pos"for later verification.
@returntrueif words are close to each other, falseif otherwise */ static bool
fts_proximity_get_positions( /*========================*/
fts_match_t** match, /*!< in: query instance */
ulint num_match, /*!< in: number of matching
items */
ulint distance, /*!< in: distance value
for proximity search */
fts_proximity_t* qualified_pos); /*!< out: the position info recordsrangescontaining
all matching words. */
/*******************************************************************//**
Compare two fts_ranking_t instance on their rank value and doc ids in
descending order on the rank and ascending order on doc id.
@return0if p1 == p2, < 0if p1 < p2, > 0if p1 > p2 */ static int
fts_query_compare_rank( /*===================*/ constvoid* p1, /*!< in: pointer to elem */ constvoid* p2) /*!< in: pointer to elem */
{ const fts_ranking_t* r1 = (const fts_ranking_t*) p1; const fts_ranking_t* r2 = (const fts_ranking_t*) p2;
if (r2->rank < r1->rank) { return(-1);
} elseif (r2->rank == r1->rank) { if (r1->doc_id < r2->doc_id) { return -1;
}
/*******************************************************************//**
Get a word from a ranking
@returntrueif it's successful */ static bool
fts_ranking_words_get_next( /*=======================*/ const fts_query_t* query, /*!< in: query instance */
fts_ranking_t* ranking,/*!< in: ranking instance */
ulint* pos, /*!< in/out: word start pos */
fts_string_t* word) /*!< in/out: term/word to add */
{ bool ret = false;
ulint max_pos = ranking->words_len * CHAR_BIT;
/* Search for next word */ while (*pos < max_pos) {
ulint byte_offset = *pos / CHAR_BIT;
ulint bit_offset = *pos % CHAR_BIT;
if (ranking->words[byte_offset] & (1 << bit_offset)) {
ret = true; break;
}
*pos += 1;
};
/* Get next word from word vector */ if (ret) {
ut_ad(*pos < query->word_vector->size());
*word = query->word_vector->at((size_t)*pos);
*pos += 1;
}
return ret;
}
/*******************************************************************//**
Add a word if it doesn't exist, to the term freq RB tree. We store
a pointer to the word that is passed in as the argument.
@return pointer to word */ static
fts_word_freq_t*
fts_query_add_word_freq( /*====================*/
fts_query_t* query, /*!< in: query instance */ const fts_string_t* word) /*!< in: term/word to add */
{
ib_rbt_bound_t parent;
/* Lookup the word in our rb tree and add if it doesn't exist. */ if (rbt_search(query->word_freqs, &parent, word) != 0) {
fts_word_freq_t word_freq;
memset(&word_freq, 0, sizeof(word_freq));
fts_string_dup(&word_freq.word, word, query->heap);
/*******************************************************************//**
Add a doc id if it doesn't exist, to the doc freq RB tree.
@return pointer to word */ static
fts_doc_freq_t*
fts_query_add_doc_freq( /*===================*/
fts_query_t* query, /*!< in: query instance */
ib_rbt_t* doc_freqs, /*!< in: rb tree of fts_doc_freq_t */
doc_id_t doc_id) /*!< in: doc id to add */
{
ib_rbt_bound_t parent;
/* Lookup the doc id in our rb tree and add if it doesn't exist. */ if (rbt_search(doc_freqs, &parent, &doc_id) != 0) {
fts_doc_freq_t doc_freq;
/*******************************************************************//**
Add the doc id to the query set only if it's not in the
deleted array. */ static void
fts_query_union_doc_id( /*===================*/
fts_query_t* query, /*!< in: query instance */
doc_id_t doc_id, /*!< in: the doc id to add */
fts_rank_t rank) /*!< in: if non-zero, it is the
rank associated with the doc_id */
{
ib_rbt_bound_t parent;
ulint size = ib_vector_size(query->deleted->doc_ids);
doc_id_t* updates = (doc_id_t*) query->deleted->doc_ids->data;
/* Check if the doc id is deleted and it's not already in our set. */ if (fts_bsearch(updates, 0, static_cast<int>(size), doc_id) < 0
&& rbt_search(query->doc_ids, &parent, &doc_id) != 0) {
/*******************************************************************//**
Remove the doc id from the query set only if it's not in the
deleted set. */ static void
fts_query_remove_doc_id( /*====================*/
fts_query_t* query, /*!< in: query instance */
doc_id_t doc_id) /*!< in: the doc id to add */
{
ib_rbt_bound_t parent;
ulint size = ib_vector_size(query->deleted->doc_ids);
doc_id_t* updates = (doc_id_t*) query->deleted->doc_ids->data;
/* Check if the doc id is deleted and it's in our set. */ if (fts_bsearch(updates, 0, static_cast<int>(size), doc_id) < 0
&& rbt_search(query->doc_ids, &parent, &doc_id) == 0) {
ut_free(rbt_remove_node(query->doc_ids, parent.last));
/*******************************************************************//**
Find the doc id in the query set but not in the deleted set, artificially
downgrade or upgrade its ranking by a value and make/initialize its ranking
under or above its normal range 0 to 1. This is used for Boolean Search operator such as Negation operator, which makes word's contribution to the
row's relevance to be negative */ static void
fts_query_change_ranking( /*====================*/
fts_query_t* query, /*!< in: query instance */
doc_id_t doc_id, /*!< in: the doc id to add */
ibool downgrade) /*!< in: Whether to downgrade ranking */
{
ib_rbt_bound_t parent;
ulint size = ib_vector_size(query->deleted->doc_ids);
doc_id_t* updates = (doc_id_t*) query->deleted->doc_ids->data;
/* Check if the doc id is deleted and it's in our set. */ if (fts_bsearch(updates, 0, static_cast<int>(size), doc_id) < 0
&& rbt_search(query->doc_ids, &parent, &doc_id) == 0) {
/* Allow at most 2 adjustment by RANK_DOWNGRADE (-0.5)
and RANK_UPGRADE (0.5) */ if (ranking->rank >= 1.0F) {
ranking->rank = 1.0F;
} elseif (ranking->rank <= -1.0F) {
ranking->rank = -1.0F;
}
}
}
/*******************************************************************//**
Check the doc id in the query set only if it's not in the
deleted array. The doc ids that were found are stored in
another rb tree (fts_query_t::intersect). */ static void
fts_query_intersect_doc_id( /*=======================*/
fts_query_t* query, /*!< in: query instance */
doc_id_t doc_id, /*!< in: the doc id to add */
fts_rank_t rank) /*!< in: if non-zero, it is the
rank associated with the doc_id */
{
ib_rbt_bound_t parent;
ulint size = ib_vector_size(query->deleted->doc_ids);
doc_id_t* updates = (doc_id_t*) query->deleted->doc_ids->data;
fts_ranking_t* ranking= NULL;
/* There are three types of intersect: 1.'+a':doc_idsisempty,adddocintointersectifitmatches'a'. 2.'a+b':docsmatch'a'isindoc_ids,adddocintointersect ifitmatches'b'.ifthedocisalsoindoc_ids,thenchangethe doc'srank,andadd'a'indoc'swords. 3.'+a+b':docsmatching'+a'isindoc_ids,adddocintointersect
if it matches 'b' and it's in doc_ids.(multi_exist = true). */
/* Check if the doc id is deleted and it's in our set */ if (fts_bsearch(updates, 0, static_cast<int>(size), doc_id) < 0) {
fts_ranking_t new_ranking;
if (rbt_search(query->intersection, &parent,
&new_ranking) != 0) { if (new_ranking.words == NULL) {
fts_ranking_words_create(query, &new_ranking);
query->total_size += RANKING_WORDS_INIT_LEN;
} else { /* Note that the intersection has taken
ownership of the ranking data. */
ranking->words = NULL;
}
/*******************************************************************//**
Add the word to the documents "list" of matching words from
the query. We make a copy of the word from the query heap. */ static void
fts_query_add_word_to_document( /*===========================*/
fts_query_t* query, /*!< in: query to update */
doc_id_t doc_id, /*!< in: the document to update */ const fts_string_t* word) /*!< in: the token to add */
{
ib_rbt_bound_t parent;
fts_ranking_t* ranking = NULL;
if (query->flags == FTS_OPT_RANKING) { return;
}
/* First we search the intersection RB tree as it could have
taken ownership of the words rb tree instance. */ if (query->intersection
&& rbt_search(query->intersection, &parent, &doc_id) == 0) {
/* Lookup the word in the rb tree */ if (rbt_search_cmp(index_cache->words, &parent, &srch_text, NULL,
innobase_fts_text_cmp_prefix) == 0) { const fts_tokenizer_word_t* word;
ulint i; const ib_rbt_node_t* cur_node;
ibool forward = FALSE;
word = rbt_value(fts_tokenizer_word_t, parent.last);
cur_node = parent.last;
while (innobase_fts_text_cmp_prefix(
index_cache->charset, &srch_text, &word->text) == 0) {
nodes = word->nodes;
for (i = 0; nodes && i < ib_vector_size(nodes); ++i) { int ret; const fts_node_t* node;
ib_rbt_bound_t freq_parent;
fts_word_freq_t* word_freqs;
/*****************************************************************//**
Set difference.
@return DB_SUCCESS if all go well */ static MY_ATTRIBUTE((nonnull, warn_unused_result))
dberr_t
fts_query_difference( /*=================*/
fts_query_t* query, /*!< in: query instance */ const fts_string_t* token) /*!< in: token to search */
{
ulint n_doc_ids= 0;
dict_table_t* table = query->index->table;
ut_a(query->oper == FTS_IGNORE);
if (query->doc_ids) {
n_doc_ids = rbt_size(query->doc_ids);
}
/* There is nothing we can substract from an empty set. */ if (query->doc_ids && !rbt_empty(query->doc_ids)) {
ulint i; const ib_vector_t* nodes; const fts_index_cache_t*index_cache;
fts_cache_t* cache = table->fts->cache;
dberr_t error;
/*****************************************************************//**
Intersect the token doc ids with the current set.
@return DB_SUCCESS if all go well */ static MY_ATTRIBUTE((nonnull, warn_unused_result))
dberr_t
fts_query_intersect( /*================*/
fts_query_t* query, /*!< in: query instance */ const fts_string_t* token) /*!< in: the token to search */
{
dict_table_t* table = query->index->table;
ut_a(query->oper == FTS_EXIST);
/* If the words set is not empty and multi exist is true,
we know the intersection set is empty in advance. */ if (!(rbt_empty(query->doc_ids) && query->multi_exist)) {
ulint n_doc_ids = 0;
ulint i; const ib_vector_t* nodes; const fts_index_cache_t*index_cache;
fts_cache_t* cache = table->fts->cache;
dberr_t error;
ut_a(!query->intersection);
n_doc_ids = rbt_size(query->doc_ids);
/* Create the rb tree that will hold the doc ids of
the intersection. */
static_assert(!offsetof(fts_ranking_t, doc_id), "ABI");
query->intersection = rbt_create( sizeof(fts_ranking_t), fts_doc_id_cmp);
query->total_size += SIZEOF_RBT_CREATE;
/* This is to avoid decompressing the ilist if the
node's ilist doc ids are out of range. */ if (!rbt_empty(query->doc_ids) && query->multi_exist) { const ib_rbt_node_t* node;
doc_id_t* doc_id;
if (query->error == DB_SUCCESS) { /* Make the intesection (rb tree) the current doc id
set and free the old set. */
fts_query_free_doc_ids(query, query->doc_ids);
query->doc_ids = query->intersection;
query->intersection = NULL;
/* The size can't decrease. */
ut_a(rbt_size(query->doc_ids) >= n_doc_ids);
/* Calculate the number of doc ids that were added to
the current doc id set. */ if (query->doc_ids) {
n_doc_ids = rbt_size(query->doc_ids) - n_doc_ids;
}
}
return(query->error);
}
/*****************************************************************//**
Depending upon the current query operator process the doc id. return DB_SUCCESS if all go well orreturn DB_FTS_EXCEED_RESULT_CACHE_LIMIT */ static
dberr_t
fts_query_process_doc_id( /*=====================*/
fts_query_t* query, /*!< in: query instance */
doc_id_t doc_id, /*!< in: doc id to process */
fts_rank_t rank) /*!< in: if non-zero, it is the
rank associated with the doc_id */
{ if (query->flags == FTS_OPT_RANKING) { return(DB_SUCCESS);
}
switch (query->oper) { case FTS_NONE:
fts_query_union_doc_id(query, doc_id, rank); break;
case FTS_EXIST:
fts_query_intersect_doc_id(query, doc_id, rank); break;
case FTS_IGNORE:
fts_query_remove_doc_id(query, doc_id); break;
case FTS_NEGATE:
fts_query_change_ranking(query, doc_id, TRUE); break;
case FTS_DECR_RATING:
fts_query_union_doc_id(query, doc_id, rank);
fts_query_change_ranking(query, doc_id, TRUE); break;
case FTS_INCR_RATING:
fts_query_union_doc_id(query, doc_id, rank);
fts_query_change_ranking(query, doc_id, FALSE); break;
/* Merge the elements to the result set. */ for (node = rbt_first(doc_ids); node; node = rbt_next(doc_ids, node)) {
fts_ranking_t* ranking;
ulint pos = 0;
fts_string_t word;
if (query->error != DB_SUCCESS) { if (query->intersection) {
ut_a(query->oper == FTS_EXIST);
fts_query_free_intersection(query);
}
DBUG_RETURN(query->error);
}
/* Merge words. Don't need to take operator into account. */
ut_a(ranking->words); while (fts_ranking_words_get_next(query, ranking, &pos, &word)) {
fts_query_add_word_to_document(query, ranking->doc_id,
&word);
}
}
/* If it is an intersection operation, reset query->doc_ids
to query->intersection and free the old result list. */ if (query->oper == FTS_EXIST && query->intersection != NULL) {
fts_query_free_doc_ids(query, query->doc_ids);
query->doc_ids = query->intersection;
query->intersection = NULL;
}
DBUG_RETURN(DB_SUCCESS);
}
/*****************************************************************//**
Skip non-whitespace in a string. Move ptr to the next word boundary.
@return pointer to first whitespace character or end */
UNIV_INLINE
byte*
fts_query_skip_word( /*================*/
byte* ptr, /*!< in: start of scan */ const byte* end) /*!< in: pointer to end of string */
{ /* TODO: Does this have to be UTF-8 too ? */ while (ptr < end && !(ispunct(*ptr) || isspace(*ptr))) {
++ptr;
}
return(ptr);
}
/*****************************************************************//**
Check whether the remaining terms in the phrase match the text.
@returnTRUEif matched elseFALSE */ static
ibool
fts_query_match_phrase_terms( /*=========================*/
fts_phrase_t* phrase, /*!< in: phrase to match */
byte** start, /*!< in/out: text to search, we can't makethisconstbecaseweneedto firstconvertthestringto
lowercase */ const byte* end, /*!< in: pointer to the end of
the string to search */
mem_heap_t* heap) /*!< in: heap */
{
ulint i;
byte* ptr = *start; const ib_vector_t* tokens = phrase->tokens;
ulint distance = phrase->distance;
/* We check only from the second term onwards, since the first
must have matched otherwise we wouldn't be here. */ for (i = 1; ptr < end && i < ib_vector_size(tokens); /* No op */) {
fts_string_t match;
fts_string_t cmp_str; const fts_string_t* token; int result;
ulint ret;
ret = innobase_mysql_fts_get_token(
phrase->charset, ptr, const_cast<byte*>(end), &match);
if (match.f_len > 0) { /* Get next token to match. */
token = static_cast<const fts_string_t*>(
ib_vector_get_const(tokens, i));
result = innobase_fts_text_cmp(
phrase->charset, token, &cmp_str);
/* Skip the rest of the tokens if this one doesn't
match and the proximity distance is exceeded. */ if (result
&& (distance == ULINT_UNDEFINED
|| distance == 0)) {
break;
}
/* This token matched move to the next token. */ if (result == 0) { /* Advance the text to search by the length
of the last token. */
ptr += ret;
/* Advance to the next token. */
++i;
} else {
ut_a(distance != ULINT_UNDEFINED);
ptr = fts_query_skip_word(ptr, end);
}
/* Distance can be 0 for exact matches. */ if (distance != ULINT_UNDEFINED && distance > 0) {
--distance;
}
} else {
ptr += ret;
}
}
*start = ptr;
/* Can't be greater than the number of elements. */
ut_a(i <= ib_vector_size(tokens));
/* This is the case for multiple words. */ if (i == ib_vector_size(tokens)) {
phrase->found = TRUE;
}
return(phrase->found);
}
/*****************************************************************//**
Callback function to count the number of words in position ranges, and see whether the word count is in specified "phrase->distance"
@returntrueif the number of characters is less than the "distance" */ static bool
fts_proximity_is_word_in_range( /*===========================*/ const fts_phrase_t*
phrase, /*!< in: phrase with the search info */
byte* start, /*!< in: text to search */
ulint total_len) /*!< in: length of text */
{
fts_proximity_t* proximity_pos = phrase->proximity_pos;
/* Search each matched position pair (with min and max positions)
and count the number of words in the range */ for (ulint i = 0; i < proximity_pos->n_pos; i++) {
ulint cur_pos = proximity_pos->min_pos[i];
ulint n_word = 0;
ut_ad(proximity_pos->max_pos[i] <= total_len);
/* Walk through words in the range and count them */ while (cur_pos <= proximity_pos->max_pos[i]) {
ulint len;
fts_string_t str;
if (innobase_fts_text_cmp(
phrase->charset, first, &cmp_str) == 0) {
/* This is the case for the single word
in the phrase. */ if (ib_vector_size(phrase->tokens) == 1) {
phrase->found = TRUE; break;
}
ptr += ret;
/* Match the remaining terms in the phrase. */ if (fts_query_match_phrase_terms(phrase, &ptr,
end, heap)) { break;
}
}
}
}
return(phrase->found);
}
/** Callback function to fetch and search the document. @paramfts_indexfulltextindex @paramdoc_iddocumentid @paramarguserargument @paramexpansionExpansiondocument
@return whether the phrase is found */ static
dberr_t fts_query_fetch_document(dict_index_t *fts_index,
doc_id_t doc_id, void *arg, THD *thd, bool expansion= false)
{
trx_t *trx= trx_create();
trx->mysql_thd= thd;
trx->op_info= "fetching FTS document for query";
dict_table_t *user_table= fts_index->table;
dict_index_t *fts_doc_id_index= user_table->fts_doc_id_index;
dict_index_t *clust_index= dict_table_get_first_index(user_table);
ut_a(user_table->fts->doc_col != ULINT_UNDEFINED);
ut_a(fts_doc_id_index);
QueryExecutor executor(trx);
/* Map FTS index columns to clustered index field positions */
ulint *clust_field_nos= static_cast<ulint*>(
mem_heap_alloc(executor.get_heap(),
fts_index->n_user_defined_cols * sizeof(ulint)));
/*****************************************************************//** This function fetches the original documents and count the
words in between matching words to see that is in specified distance
@return DB_SUCCESS if all OK */ static MY_ATTRIBUTE((nonnull, warn_unused_result)) bool
fts_query_is_in_proximity_range( /*============================*/ const fts_query_t* query, /*!< in: query instance */
fts_match_t** match, /*!< in: query instance */
fts_proximity_t* qualified_pos) /*!< in: position info for
qualified ranges */
{
fts_get_doc_t get_doc;
fts_cache_t* cache = query->index->table->fts->cache;
dberr_t err;
if (UNIV_UNLIKELY(err != DB_SUCCESS)) {
ib::error() << "(" << err << ") in verification" " phase of proximity search";
}
mem_heap_free(phrase.heap);
return(err == DB_SUCCESS && phrase.found);
}
/*****************************************************************//**
Iterate over the matched document ids and search the for the
actual phrase in the text.
@return DB_SUCCESS if all OK */ static MY_ATTRIBUTE((nonnull, warn_unused_result))
dberr_t
fts_query_search_phrase( /*====================*/
fts_query_t* query, /*!< in: query instance */
ib_vector_t* orig_tokens, /*!< in: tokens to search, withanystopwordsinthe
original phrase */
ib_vector_t* tokens) /*!< in: tokens that does notincludestopwordsand canbeusedtocalculate
ranking */
{
ulint i;
fts_get_doc_t get_doc;
ulint n_matched;
fts_cache_t* cache = query->index->table->fts->cache;
n_matched = ib_vector_size(query->matched);
/* Setup the doc retrieval infrastructure. */
memset(&get_doc, 0x0, sizeof(get_doc));
/* Must find the index cache */
ut_a(get_doc.index_cache != NULL);
mysql_mutex_unlock(&cache->lock);
/* Read the document from disk and do the actual match,matchingdocumentswillbeaddedtothecurrent
doc id set. */ for (i = 0; i < n_matched && query->error == DB_SUCCESS; ++i) {
fts_match_t* match;
ibool found = FALSE;
match = static_cast<fts_match_t*>(
ib_vector_get(query->matched, i));
/* Skip the document ids that were filtered out by
an earlier pass. */ if (match->doc_id != 0) {
if (fts_check_token(
&result_str,
cache->stopword_info.cached_stopword, cs)) { /* Add the word to the RB tree so that we can
calculate its frequency within a document. */
fts_query_add_word_freq(query, token);
} else {
ib_vector_pop(tokens);
}
/* we will start to store all words including stopwords inthe"orig_tokens"vector,butskipanyleadingwords
that are stopwords */ if (!ib_vector_is_empty(tokens)) {
fts_string_t* orig_token = static_cast<fts_string_t*>(
ib_vector_push(orig_tokens, NULL));
/* Create the vector for storing matching document ids
and the positions of the first token of the phrase. */ if (!query->matched) {
ib_alloc_t* heap_alloc;
/* If any of the token can't be found,
no need to continue match */ if (ib_vector_is_empty(query->match_array[i])
|| query->error != DB_SUCCESS) { goto func_exit;
}
}
/* Just a single word, no need to fetch the original
documents to do phrase matching */ if (ib_vector_size(orig_tokens) == 1
&& !ib_vector_is_empty(query->match_array[0])) {
fts_match_t* match;
ulint n_matched;
/* If we are doing proximity search, verify the distance
between all words, and check they are in specified distance. */ if (query->flags & FTS_PROXIMITY) {
fts_phrase_or_proximity_search(query, tokens);
} else {
ibool matched;
/* Read the actual text in and search for the phrase. */ if (matched) {
ut_ad(query->error == DB_SUCCESS);
query->error = fts_query_search_phrase(
query, orig_tokens, tokens);
}
}
/* Restore original operation. */
query->oper = oper;
if (query->error != DB_SUCCESS) { goto func_exit;
}
}
func_exit:
mem_heap_free(heap);
/* Don't need it anymore. */
query->matched = NULL;
return(query->error);
}
/*****************************************************************//**
Find the word and evaluate.
@return DB_SUCCESS if all go well */ static MY_ATTRIBUTE((nonnull, warn_unused_result))
dberr_t
fts_query_execute( /*==============*/
fts_query_t* query, /*!< in: query instance */
fts_string_t* token) /*!< in: token to search */
{ switch (query->oper) { case FTS_NONE: case FTS_NEGATE: case FTS_INCR_RATING: case FTS_DECR_RATING:
query->error = fts_query_union(query, token); break;
case FTS_EXIST:
query->error = fts_query_intersect(query, token); break;
case FTS_IGNORE:
query->error = fts_query_difference(query, token); break;
default:
ut_error;
}
return(query->error);
}
/*****************************************************************//**
Create a wildcard string. It's the responsibility of the caller to
free the byte* pointer. It's allocated using ut_malloc_nokey().
@return ptr to allocated memory */ static
byte*
fts_query_get_token( /*================*/
fts_ast_node_t* node, /*!< in: the current sub tree */
fts_string_t* token) /*!< in: token to create */
{
ulint str_len;
byte* new_ptr = NULL;
case FTS_AST_SUBEXP_LIST:
query->error = fts_ast_visit_sub_exp(node, fts_query_visitor, arg); break;
default:
ut_error;
}
if (query->oper == FTS_EXIST) {
query->multi_exist = true;
}
DBUG_RETURN(query->error);
}
/** Process (nested) sub-expression, create a new result set to store the sub-expressionresultbyprocessingnodesundercurrentsub-expression list.Mergethesub-expressionresultwiththatofparentexpressionlist. @param[in,out]nodecurrentrootnode @param[in,out]visitorcallbackfunction @param[in,out]argargumentforcallback
@return DB_SUCCESS if all go well */ static
dberr_t
fts_ast_visit_sub_exp(
fts_ast_node_t* node,
fts_ast_callback visitor, void* arg)
{
fts_ast_oper_t cur_oper;
fts_query_t* query = static_cast<fts_query_t*>(arg);
ib_rbt_t* parent_doc_ids;
ib_rbt_t* subexpr_doc_ids;
dberr_t error = DB_SUCCESS; bool will_be_ignored = false; bool multi_exist;
DBUG_ENTER("fts_ast_visit_sub_exp");
ut_a(node->type == FTS_AST_SUBEXP_LIST);
/* To avoid stack overflow, we limit the mutual recursion depthbetweenfts_ast_visit(),fts_query_visitor()and
fts_ast_visit_sub_exp(). */ if (query->visiting_sub_exp++ > 31) {
query->error = DB_OUT_OF_MEMORY;
DBUG_RETURN(query->error);
}
cur_oper = query->oper;
/* Save current result set */
parent_doc_ids = query->doc_ids;
/* Create new result set to store the sub-expression result. We
will merge this result set with the parent after processing. */
static_assert(!offsetof(fts_ranking_t, doc_id), "ABI");
query->doc_ids = rbt_create(sizeof(fts_ranking_t), fts_doc_id_cmp);
query->total_size += SIZEOF_RBT_CREATE;
multi_exist = query->multi_exist;
query->multi_exist = false; /* Process nodes in current sub-expression and store its
result set in query->doc_ids we created above. */
error = fts_ast_visit(FTS_NONE, node, visitor,
arg, &will_be_ignored);
/* Merge the sub-expression result with the parent result set. */
subexpr_doc_ids = query->doc_ids;
query->doc_ids = parent_doc_ids; if (error == DB_SUCCESS) {
error = fts_merge_doc_ids(query, subexpr_doc_ids);
}
/* Free current result set. Result already merged into parent. */
fts_query_free_doc_ids(query, subexpr_doc_ids);
DBUG_RETURN(error);
}
/*****************************************************************//**
Read and filter nodes.
@return DB_SUCCESS if all go well, orreturn DB_FTS_EXCEED_RESULT_CACHE_LIMIT */ static
dberr_t
fts_query_filter_doc_ids( /*=====================*/
fts_query_t* query, /*!< in: query instance */ const fts_string_t* word, /*!< in: the current word */
fts_word_freq_t* word_freq, /*!< in/out: word frequency */ const fts_node_t* node, /*!< in: current FTS node */ void* data, /*!< in: doc id ilist */
ulint len, /*!< in: doc id ilist size */
ibool calc_doc_count) /*!< in: whether to remember doc count */
{ const byte* ptr = static_cast<byte*>(data);
doc_id_t doc_id = 0;
ulint decoded = 0;
ib_rbt_t* doc_freqs = word_freq->doc_freqs;
/* Decode the ilist and add the doc ids to the query doc_id set. */ while (decoded < len) {
ulint freq = 0;
fts_doc_freq_t* doc_freq;
fts_match_t* match = NULL;
doc_id_t last_pos = 0;
doc_id_t pos = fts_decode_vlc(&ptr);
/* Some sanity checks. */ if (doc_id == 0) {
ut_a(pos == node->first_doc_id);
}
/* Add the delta. */
doc_id += pos;
if (calc_doc_count) {
word_freq->doc_count++;
}
/* We simply collect the matching instances here. */ if (query->collect_positions) {
ib_alloc_t* heap_alloc;
/* Create a new fts_match_t instance. */
match = static_cast<fts_match_t*>(
ib_vector_push(query->matched, NULL));
/* Unpack the positions within the document. */ while (*ptr) {
last_pos += fts_decode_vlc(&ptr);
/* Collect the matching word positions, for phrase
matching later. */ if (query->collect_positions) {
ib_vector_push(match->positions, &last_pos);
}
++freq;
}
/* End of list marker. */
last_pos = (ulint) -1;
if (query->collect_positions) {
ut_a(match != NULL);
ib_vector_push(match->positions, &last_pos);
}
/* Add the doc id to the doc freq rb tree, if the doc id
doesn't exist it will be created. */
doc_freq = fts_query_add_doc_freq(query, doc_freqs, doc_id);
/* Avoid duplicating frequency tally. */ if (doc_freq->freq == 0) {
doc_freq->freq = freq;
}
/* Skip the end of word position marker. */
++ptr;
/* Bytes decoded so far */
decoded = ulint(ptr - (byte*) data);
/* We simply collect the matching documents and the
positions here and match later. */ if (!query->collect_positions) { /* We ignore error here and will check it later */
fts_query_process_doc_id(query, doc_id, 0);
/* Add the word to the document's matched RB tree. */
fts_query_add_word_to_document(query, doc_id, word);
}
}
/* Some sanity checks. */
ut_a(doc_id == node->last_doc_id);
/*****************************************************************//**
Calculate the inverse document frequency (IDF) for all the terms. */ static void
fts_query_calculate_idf( /*====================*/
fts_query_t* query) /*!< in: Query state */
{ const ib_rbt_node_t* node;
ib_uint64_t total_docs = query->total_docs;
/* We need to free any instances of fts_doc_freq_t that we
may have allocated. */ for (node = rbt_first(query->word_freqs);
node;
node = rbt_next(query->word_freqs, node)) {
fts_word_freq_t* word_freq;
word_freq = rbt_value(fts_word_freq_t, node);
if (word_freq->doc_count > 0) { if (total_docs == word_freq->doc_count) { /* QP assume ranking > 0 if we find amatch.SinceLog10(1)=0,wecannot makeIDFazerovalueifdofinda wordinalldocuments.Solet'smake
it an arbitrary very small number */
word_freq->idf = log10(1.0001);
} else {
word_freq->idf = log10( static_cast<double>(total_docs)
/ static_cast<double>(
word_freq->doc_count));
}
}
}
}
/*****************************************************************//**
Calculate the ranking of the document. */ static void
fts_query_calculate_ranking( /*========================*/ const fts_query_t* query, /*!< in: query state */
fts_ranking_t* ranking) /*!< in: Document to rank */
{
ulint pos = 0;
fts_string_t word;
/* At this stage, ranking->rank should not exceed the 1.0
bound */
ut_ad(ranking->rank <= 1.0 && ranking->rank >= -1.0);
ut_ad(rbt_size(query->word_map) == query->word_vector->size());
while (fts_ranking_words_get_next(query, ranking, &pos, &word)) { int ret;
ib_rbt_bound_t parent; double weight;
fts_doc_freq_t* doc_freq;
fts_word_freq_t* word_freq;
ret = rbt_search(query->word_freqs, &parent, &word);
/*****************************************************************//**
Add ranking to the result set. */ static void
fts_query_add_ranking( /*==================*/
fts_query_t* query, /*!< in: query state */
ib_rbt_t* ranking_tree, /*!< in: ranking tree */ const fts_ranking_t* new_ranking) /*!< in: ranking of a document */
{
ib_rbt_bound_t parent;
/* Lookup the ranking in our rb tree and add if it doesn't exist. */ if (rbt_search(ranking_tree, &parent, new_ranking) == 0) {
fts_ranking_t* ranking;
/*****************************************************************//**
Retrieve the FTS Relevance Ranking result for doc with doc_id
@return the relevance ranking value, 0if no ranking value
present. */ float
fts_retrieve_ranking( /*=================*/
fts_result_t* result, /*!< in: FTS result structure */
doc_id_t doc_id) /*!< in: doc_id of the item to retrieve */
{
ib_rbt_bound_t parent;
fts_ranking_t new_ranking;
DBUG_ENTER("fts_retrieve_ranking");
if (!result || !result->rankings_by_id) {
DBUG_RETURN(0);
}
new_ranking.doc_id = doc_id;
/* Lookup the ranking in our rb tree */ if (rbt_search(result->rankings_by_id, &parent, &new_ranking) == 0) {
fts_ranking_t* ranking;
ranking = rbt_value(fts_ranking_t, parent.last);
DBUG_RETURN(ranking->rank);
}
DBUG_RETURN(0);
}
/*****************************************************************//**
Create the result and copy the data to it. */ static
fts_result_t*
fts_query_prepare_result( /*=====================*/
fts_query_t* query, /*!< in: Query state */
fts_result_t* result) /*!< in: result this can contain datafromaprevioussearchon
another FTS index */
{ const ib_rbt_node_t* node; bool result_is_null = false;
DBUG_ENTER("fts_query_prepare_result");
if (result == NULL) {
result = static_cast<fts_result_t*>(
ut_zalloc_nokey(sizeof(*result)));
/* Don't put deleted docs into result */ if (fts_bsearch(updates, 0, static_cast<int>(size),
doc_freq->doc_id) >= 0) { /* one less matching doc count */
--word_freq->doc_count; continue;
}
if (result_is_null) { /* Use doc_ids directly */
rbt_free(result->rankings_by_id);
result->rankings_by_id = query->doc_ids;
query->doc_ids = NULL;
}
DBUG_RETURN(result);
}
/*****************************************************************//**
Get the result of the query. Calculate the similarity coefficient. */ static
fts_result_t*
fts_query_get_result( /*=================*/
fts_query_t* query, /*!< in: query instance */
fts_result_t* result) /*!< in: result */
{
DBUG_ENTER("fts_query_get_result");
if (rbt_size(query->doc_ids) > 0 || query->flags == FTS_OPT_RANKING) { /* Copy the doc ids to the result. */
result = fts_query_prepare_result(query, result);
} else { /* Create an empty result instance. */
result = static_cast<fts_result_t*>(
ut_zalloc_nokey(sizeof(*result)));
}
if (query->read_nodes_graph) {
que_graph_free(query->read_nodes_graph);
}
if (query->root) {
fts_ast_free_node(query->root);
}
if (query->deleted) {
fts_doc_ids_free(query->deleted);
}
if (query->intersection) {
fts_query_free_doc_ids(query, query->intersection);
}
if (query->doc_ids) {
fts_query_free_doc_ids(query, query->doc_ids);
}
if (query->word_freqs) { const ib_rbt_node_t* node;
/* We need to free any instances of fts_doc_freq_t that we
may have allocated. */ for (node = rbt_first(query->word_freqs);
node;
node = rbt_next(query->word_freqs, node)) {
fts_word_freq_t* word_freq;
word_freq = rbt_value(fts_word_freq_t, node);
/* We need to cast away the const. */
rbt_free(word_freq->doc_freqs);
}
rbt_free(query->word_freqs);
}
if (query->wildcard_words != NULL) {
rbt_free(query->wildcard_words);
}
ut_a(!query->intersection);
if (query->word_map) {
rbt_free(query->word_map);
}
if (query->word_vector != NULL) {
UT_DELETE(query->word_vector);
}
if (query->heap) {
mem_heap_free(query->heap);
}
memset(query, 0, sizeof(*query));
}
/*****************************************************************//**
Parse the query using flex/bison or plugin parser.
@return parse tree node. */ static
fts_ast_node_t*
fts_query_parse( /*============*/
fts_query_t* query, /*!< in: query instance */
byte* query_str, /*!< in: query string */
ulint query_len) /*!< in: query string length */
{ int error;
fts_ast_state_t state; bool mode = query->boolean_mode;
DBUG_ENTER("fts_query_parse");
if (query->parser) {
state.root = state.cur_node =
fts_ast_create_node_list(&state, NULL);
error = fts_parse_by_parser(mode, query_str, query_len,
query->parser, &state);
} else { /* Setup the scanner to use, this depends on the mode flag. */
state.lexer = fts_lexer_create(mode, query_str, query_len);
state.charset = fts_index_get_charset(query->index);
error = fts_parse(&state);
fts_lexer_free(state.lexer);
state.lexer = NULL;
}
/* Error during parsing ? */ if (error) { /* Free the nodes that were allocated during parsing. */
fts_ast_state_free(&state);
} else {
query->root = state.root;
}
DBUG_RETURN(state.root);
}
/*******************************************************************//**
FTS Query optimization
Set FTS_OPT_RANKING if it is a simple term query */ static void
fts_query_can_optimize( /*===================*/
fts_query_t* query, /*!< in/out: query instance */
uint flags) /*!< In: FTS search mode */
{
fts_ast_node_t* node = query->root;
if (flags & FTS_EXPAND) { return;
}
/* Check if it has only a term without oper */
ut_ad(node->type == FTS_AST_LIST);
node = node->list.head; if (node != NULL && node->type == FTS_AST_TERM && node->next == NULL) {
query->flags = FTS_OPT_RANKING;
}
}
/* Setup the RB tree that will be used to collect per term
statistics. */
query.word_freqs = rbt_create_arg_cmp( sizeof(fts_word_freq_t), innobase_fts_text_cmp,
(void*) charset);
/* Create single FTSQueryExecutor for entire query lifecycle */
query.executor = new FTSQueryExecutor(query_trx, index->table);
/* Prefetch all auxiliary and common tables to avoid repeated dict_sys.latch acquisitions */
error = query.executor->open_all_aux_tables(index); if (error == DB_SUCCESS) {
error = query.executor->open_all_deletion_tables();
}
if (error == DB_SUCCESS) { /* Read the deleted doc_ids, we need these for filtering. */
error = fts_table_fetch_doc_ids(
query.executor, "DELETED", query.deleted);
}
if (error != DB_SUCCESS) {
query_trx->rollback(); goto func_exit;
}
trx_commit_for_mysql(query_trx);
/* Get the deleted doc ids that are in the cache. */
fts_cache_append_deleted_doc_ids(
index->table->fts->cache, query.deleted->doc_ids);
DEBUG_SYNC_C("fts_deleted_doc_ids_append");
/* Sort the vector so that we can do a binary search over the ids. */
fts_doc_ids_sort(query.deleted->doc_ids);
/* Convert the query string to lower case before parsing. We own
the ut_malloc'ed result and so remember to free it before return. */
/* For binary collations, a case sensitive search is
performed. Hence don't convert to lower case. */ if (my_binary_compare(charset)) {
memcpy(lc_query_str, query_str, query_len);
lc_query_str[query_len]= 0;
result_len= query_len;
} else {
result_len = charset->casedn_z(
(constchar*) query_str, query_len,
(char*) lc_query_str, lc_query_str_len);
}
ut_ad(result_len < lc_query_str_len);
query.heap = mem_heap_create(128);
/* Create the rb tree for the doc id (current) set. */
static_assert(!offsetof(fts_ranking_t, doc_id), "ABI");
query.doc_ids = rbt_create( sizeof(fts_ranking_t), fts_doc_id_cmp);
query.parser = index->parser;
query.total_size += SIZEOF_RBT_CREATE;
/* Parse the input query string. */ if (fts_query_parse(&query, lc_query_str, result_len)) {
fts_ast_node_t* ast = query.root;
ast->trx = trx;
/* Optimize query to check if it's a single term */
fts_query_can_optimize(&query, flags);
/* Traverse the Abstract Syntax Tree (AST) and execute
the query. */
query.error = fts_ast_visit(
FTS_NONE, ast, fts_query_visitor,
&query, &will_be_ignored); if (query.error == DB_INTERRUPTED) {
error = DB_INTERRUPTED;
ut_free(lc_query_str); goto func_exit;
}
/* If query expansion is requested, extend the search
with first search pass result */ if (query.error == DB_SUCCESS && (flags & FTS_EXPAND)) {
query.error = fts_expand_query(index, &query);
}
/* Calculate the inverse document frequency of the terms. */ if (query.error == DB_SUCCESS
&& query.flags != FTS_OPT_RANKING) {
fts_query_calculate_idf(&query);
}
/* Copy the result from the query state, so that we can
return it to the caller. */ if (query.error == DB_SUCCESS) {
*result = fts_query_get_result(&query, *result);
}
error = query.error;
} else { /* still return an empty result set */
*result = static_cast<fts_result_t*>(
ut_zalloc_nokey(sizeof(**result)));
}
if (trx_is_interrupted(trx)) {
error = DB_INTERRUPTED;
ut_free(lc_query_str); if (*result) {
fts_query_free_result(*result);
} goto func_exit;
}
ut_free(lc_query_str);
func_exit: /* Clean up the dynamically allocated executor */ if (query.executor) { delete query.executor;
query.executor = nullptr;
}
fts_query_free(&query);
query_trx->free();
return(error);
}
/*****************************************************************//**
FTS Query free result, returned by fts_query(). */ void
fts_query_free_result( /*==================*/
fts_result_t* result) /*!< in: result instance to free.*/
{ if (result) { if (result->rankings_by_id != NULL) {
rbt_free(result->rankings_by_id);
result->rankings_by_id = NULL;
} if (result->rankings_by_rank != NULL) {
rbt_free(result->rankings_by_rank);
result->rankings_by_rank = NULL;
}
ut_free(result);
result = NULL;
}
}
/*****************************************************************//**
FTS Query sort result, returned by fts_query() on fts_ranking_t::rank. */ void
fts_query_sort_result_on_rank( /*==========================*/
fts_result_t* result) /*!< out: result instance to sort.*/
{ const ib_rbt_node_t* node;
ib_rbt_t* ranked;
ut_a(result->rankings_by_id != NULL); if (result->rankings_by_rank) {
rbt_free(result->rankings_by_rank);
}
/* We need to free any instances of fts_doc_freq_t that we
may have allocated. */ for (node = rbt_first(result->rankings_by_id);
node;
node = rbt_next(result->rankings_by_id, node)) {
fts_ranking_t* ranking;
ranking = rbt_value(fts_ranking_t, node);
ut_a(ranking->words == NULL);
rbt_insert(ranked, ranking, ranking);
}
/* Reset the current node too. */
result->current = NULL;
result->rankings_by_rank = ranked;
}
/*************************************************************//** This function implements a simple "blind" query expansion search:
words in documents found in the first search pass will be used as
search arguments to search the document again, thus "expand"
the search result set.
@return DB_SUCCESS if success, otherwise the error code */ static MY_ATTRIBUTE((nonnull, warn_unused_result))
dberr_t
fts_expand_query( /*=============*/
dict_index_t* index, /*!< in: FTS index to search */
fts_query_t* query) /*!< in: FTS query instance */
{ const ib_rbt_node_t* node; const ib_rbt_node_t* token_node;
fts_doc_t result_doc;
dberr_t error = DB_SUCCESS; const fts_index_cache_t*index_cache;
/* If no doc is found in first search pass, return */ if (!rbt_size(query->doc_ids)) { return(error);
}
/* Init "result_doc", to hold words from the first search pass */
fts_doc_init(&result_doc);
/* Fetch the documents with the doc_id from the resultoffirstseachpass.Sincewedonot storedocument-to-wordmapping,weneedto fetchtheoriginaldocumentandparsethem. Futureoptimizationcouldbedonehereifwe
support some forms of document-to-word mapping */
fts_query_fetch_document(index, ranking->doc_id,
&result_doc,
query->trx->mysql_thd, true);
/* Remove words that have already been searched in the first pass */ for (ulint i = 0; i < query->word_vector->size(); i++) {
fts_string_t word = query->word_vector->at(i);
ib_rbt_bound_t parent;
if (query->wildcard_words
&& rbt_search(query->wildcard_words, &parent, &word) == 0) { /* If it's a wildcard word, remove words having
it as prefix. */ while (rbt_search_cmp(result_doc.tokens,
&parent, &word, NULL,
innobase_fts_text_cmp_prefix)
== 0) {
ut_free(rbt_remove_node(result_doc.tokens,
parent.last));
}
} else { /* We don't check return value, because the word may havebeendeletedbyapreviouswildcardwordasits
prefix, e.g. ('g * good'). */
rbt_delete(result_doc.tokens, &word);
}
}
/* Search the table the second time with expanded search list */ for (token_node = rbt_first(result_doc.tokens);
token_node;
token_node = rbt_next(result_doc.tokens, token_node)) {
fts_token_t* mytoken;
mytoken = rbt_value(fts_token_t, token_node);
/* '%' in the end is treated as prefix search,
it can cause assert failure, so we skip it. */ if (mytoken->text.f_str[mytoken->text.f_len - 1] == '%') { continue;
}
return(error);
} /*************************************************************//** This function finds documents that contain all words in a
phrase or proximity search. Andif proximity search, verify
the words are close enough to each other, as in specified distance. This function is called for phrase and proximity search.
@returnTRUEif documents are found, FALSEif otherwise */ static
ibool
fts_phrase_or_proximity_search( /*===========================*/
fts_query_t* query, /*!< in/out: query instance. query->doc_idsmightbeinstantiated
with qualified doc IDs */
ib_vector_t* tokens) /*!< in: Tokens contain words */
{
ulint n_matched;
ulint i;
ibool matched = FALSE;
ulint num_token = ib_vector_size(tokens);
fts_match_t* match[MAX_PROXIMITY_ITEM];
ibool end_list = FALSE;
/* Number of matched documents for the first token */
n_matched = ib_vector_size(query->match_array[0]);
/* We have a set of match list for each word, we shall walkthroughthelistandfindcommondocumentsthat
contain all the matching words. */ for (i = 0; i < n_matched; i++) {
ulint j;
ulint k = 0;
fts_proximity_t qualified_pos;
/* For remaining match list for the token(word), we trytoseeifthereisadocumentwiththesame
doc id */ for (j = 1; j < num_token; j++) {
match[j] = static_cast<fts_match_t*>(
ib_vector_get(query->match_array[j], k));
while (match[j]->doc_id < match[0]->doc_id
&& k < ib_vector_size(query->match_array[j])) {
match[j] = static_cast<fts_match_t*>(
ib_vector_get(
query->match_array[j], k));
k++;
}
if (match[j]->doc_id > match[0]->doc_id) { /* no match */ if (query->flags & FTS_PHRASE) {
match[0]->doc_id = 0;
} break;
}
if (k == ib_vector_size(query->match_array[j])) {
end_list = TRUE;
if (query->flags & FTS_PHRASE) {
ulint s; /* Since i is the last doc id in the match_array[j],removealldocids>i
from the match_array[0]. */
fts_match_t* match_temp; for (s = i + 1; s < n_matched; s++) {
match_temp = static_cast<
fts_match_t*>(ib_vector_get(
query->match_array[0], s));
match_temp->doc_id = 0;
}
if (match[j]->doc_id !=
match[0]->doc_id) { /* no match */
match[0]->doc_id = 0;
}
}
if (match[j]->doc_id != match[0]->doc_id) { goto func_exit;
}
}
/* FIXME: A better solution will be a counter array remembereachrun'slastposition.Sowedon't
reset it here very time */
k = 0;
}
if (j != num_token) { continue;
}
/* For this matching doc, we need to further verifywhetherthewordsinthedocareclose toeachother,andwithinthedistancespecified
in the proximity search */ if (query->flags & FTS_PHRASE) {
matched = TRUE;
} elseif (fts_proximity_get_positions(
match, num_token, ULINT_MAX, &qualified_pos)) {
/* Fetch the original documents and count the wordsinbetweenmatchingwordstoseethatisin
specified distance */ if (fts_query_is_in_proximity_range(
query, match, &qualified_pos)) { /* If so, mark we find a matching doc */
query->error = fts_query_process_doc_id(
query, match[0]->doc_id, 0); if (query->error != DB_SUCCESS) {
matched = FALSE; goto func_exit;
}
matched = TRUE; for (ulint z = 0; z < num_token; z++) {
fts_string_t* token;
token = static_cast<fts_string_t*>(
ib_vector_get(tokens, z));
fts_query_add_word_to_document(
query, match[0]->doc_id, token);
}
}
}
if (end_list) { break;
}
}
func_exit: return(matched);
}
/*************************************************************//** This function checks whether words in result documents are close to
each other (within proximity range as specified by "distance"). If"distance" is MAX_ULINT, then it will find all combinations of
positions of matching words and store min and max positions
in the "qualified_pos"for later verification.
@returntrueif words are close to each other, falseif otherwise */ static bool
fts_proximity_get_positions( /*========================*/
fts_match_t** match, /*!< in: query instance */
ulint num_match, /*!< in: number of matching
items */
ulint distance, /*!< in: distance value
for proximity search */
fts_proximity_t* qualified_pos) /*!< out: the position info recordsrangescontaining
all matching words. */
{
ulint i;
ulint idx[MAX_PROXIMITY_ITEM];
ulint num_pos[MAX_PROXIMITY_ITEM];
ulint min_idx;
qualified_pos->n_pos = 0;
ut_a(num_match <= MAX_PROXIMITY_ITEM);
/* Each word could appear multiple times in a doc. So weneedtowalkthrougheachword'spositionlist,andfind closestdistancebetweendifferentwordstoseeif
they are in the proximity distance. */
/* Assume each word's position list is sorted, we willjustdoawalkthroughtoallwords'lists
similar to a the merge phase of a merge sort */ for (i = 0; i < num_match; i++) { /* idx is the current position we are checking
for a particular word */
idx[i] = 0;
/* Number of positions for this word */
num_pos[i] = ib_vector_size(match[i]->positions);
}
/* Check positions in each word position list, and
record the max/min position */ for (i = 0; i < num_match; i++) {
position[i] = *(ulint*) ib_vector_get_const(
match[i]->positions, idx[i]);
if (position[i] > max_pos) {
max_pos = position[i];
}
}
/* If max and min position are within range, we
find a good match */ if (max_pos - min_pos <= distance
&& (i >= num_match || position[i] != ULINT_UNDEFINED)) { /* The charset has variable character lengthencoding,recordthemin_posand max_pos,wewillneedtoverifytheactual
number of characters */
qualified_pos->min_pos.push_back(min_pos);
qualified_pos->max_pos.push_back(max_pos);
qualified_pos->n_pos++;
}
/* Otherwise, move to the next position is the
list for the word with the smallest position */
idx[min_idx]++;
}
return(qualified_pos->n_pos != 0);
}
Messung V0.5 in Prozent
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.63Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-10-08)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.