mirror of
				https://github.com/MariaDB/server.git
				synced 2025-11-04 12:56:14 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			816 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			816 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*****************************************************************************
 | 
						|
 | 
						|
Copyright (c) 2007, 2020, Oracle and/or its affiliates. All Rights Reserved.
 | 
						|
Copyright (c) 2018, 2022, MariaDB Corporation.
 | 
						|
 | 
						|
This program is free software; you can redistribute it and/or modify it under
 | 
						|
the terms of the GNU General Public License as published by the Free Software
 | 
						|
Foundation; version 2 of the License.
 | 
						|
 | 
						|
This program is distributed in the hope that it will be useful, but WITHOUT
 | 
						|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 | 
						|
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 | 
						|
 | 
						|
You should have received a copy of the GNU General Public License along with
 | 
						|
this program; if not, write to the Free Software Foundation, Inc.,
 | 
						|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
 | 
						|
 | 
						|
*****************************************************************************/
 | 
						|
 | 
						|
/**************************************************//**
 | 
						|
@file fts/fts0ast.cc
 | 
						|
Full Text Search parser helper file.
 | 
						|
 | 
						|
Created 2007/3/16 Sunny Bains.
 | 
						|
***********************************************************************/
 | 
						|
 | 
						|
#include "row0sel.h"
 | 
						|
#include "fts0ast.h"
 | 
						|
#include "fts0pars.h"
 | 
						|
#include "fts0fts.h"
 | 
						|
#include "trx0trx.h"
 | 
						|
 | 
						|
/* The FTS ast visit pass. */
 | 
						|
enum fts_ast_visit_pass_t {
 | 
						|
	FTS_PASS_FIRST,		/*!< First visit pass,
 | 
						|
				process operators excluding
 | 
						|
				FTS_EXIST and FTS_IGNORE */
 | 
						|
	FTS_PASS_EXIST,		/*!< Exist visit pass,
 | 
						|
				process operator FTS_EXIST */
 | 
						|
	FTS_PASS_IGNORE		/*!< Ignore visit pass,
 | 
						|
				process operator FTS_IGNORE */
 | 
						|
};
 | 
						|
 | 
						|
/******************************************************************//**
 | 
						|
Create an empty fts_ast_node_t.
 | 
						|
@return Create a new node */
 | 
						|
static
 | 
						|
fts_ast_node_t*
 | 
						|
fts_ast_node_create(void)
 | 
						|
/*=====================*/
 | 
						|
{
 | 
						|
	fts_ast_node_t*	node;
 | 
						|
 | 
						|
	node = (fts_ast_node_t*) ut_zalloc_nokey(sizeof(*node));
 | 
						|
 | 
						|
	return(node);
 | 
						|
}
 | 
						|
 | 
						|
/** Track node allocations, in case there is an error during parsing. */
 | 
						|
static
 | 
						|
void
 | 
						|
fts_ast_state_add_node(
 | 
						|
	fts_ast_state_t*state,			/*!< in: ast instance */
 | 
						|
	fts_ast_node_t*	node)			/*!< in: node to add to ast */
 | 
						|
{
 | 
						|
	if (!state->list.head) {
 | 
						|
		ut_a(!state->list.tail);
 | 
						|
 | 
						|
		state->list.head = state->list.tail = node;
 | 
						|
	} else {
 | 
						|
		state->list.tail->next_alloc = node;
 | 
						|
		state->list.tail = node;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/******************************************************************//**
 | 
						|
Create a operator fts_ast_node_t.
 | 
						|
@return new node */
 | 
						|
fts_ast_node_t*
 | 
						|
fts_ast_create_node_oper(
 | 
						|
/*=====================*/
 | 
						|
	void*		arg,			/*!< in: ast state instance */
 | 
						|
	fts_ast_oper_t	oper)			/*!< in: ast operator */
 | 
						|
{
 | 
						|
	fts_ast_node_t*	node = fts_ast_node_create();
 | 
						|
 | 
						|
	node->type = FTS_AST_OPER;
 | 
						|
	node->oper = oper;
 | 
						|
 | 
						|
	fts_ast_state_add_node((fts_ast_state_t*) arg, node);
 | 
						|
 | 
						|
	return(node);
 | 
						|
}
 | 
						|
 | 
						|
/******************************************************************//**
 | 
						|
This function takes ownership of the ptr and is responsible
 | 
						|
for free'ing it
 | 
						|
@return new node or a node list with tokenized words */
 | 
						|
fts_ast_node_t*
 | 
						|
fts_ast_create_node_term(
 | 
						|
/*=====================*/
 | 
						|
	void*			arg,		/*!< in: ast state instance */
 | 
						|
	const fts_ast_string_t*	ptr)		/*!< in: ast term string */
 | 
						|
{
 | 
						|
	fts_ast_state_t*	state = static_cast<fts_ast_state_t*>(arg);
 | 
						|
	ulint			len = ptr->len;
 | 
						|
	ulint			cur_pos = 0;
 | 
						|
	fts_ast_node_t*         node = NULL;
 | 
						|
	fts_ast_node_t*		node_list = NULL;
 | 
						|
	fts_ast_node_t*		first_node = NULL;
 | 
						|
 | 
						|
	/* Scan the incoming string and filter out any "non-word" characters */
 | 
						|
	while (cur_pos < len) {
 | 
						|
		fts_string_t	str;
 | 
						|
		ulint		cur_len;
 | 
						|
 | 
						|
		cur_len = innobase_mysql_fts_get_token(
 | 
						|
			state->charset,
 | 
						|
			reinterpret_cast<const byte*>(ptr->str) + cur_pos,
 | 
						|
			reinterpret_cast<const byte*>(ptr->str) + len, &str);
 | 
						|
 | 
						|
		if (cur_len == 0) {
 | 
						|
			break;
 | 
						|
		}
 | 
						|
 | 
						|
		cur_pos += cur_len;
 | 
						|
 | 
						|
		if (str.f_n_char > 0) {
 | 
						|
			/* If the subsequent term (after the first one)'s size
 | 
						|
			is less than fts_min_token_size or the term is greater
 | 
						|
			than fts_max_token_size, we shall ignore that. This is
 | 
						|
			to make consistent with MyISAM behavior */
 | 
						|
			if ((first_node && (str.f_n_char < fts_min_token_size))
 | 
						|
			    || str.f_n_char > fts_max_token_size) {
 | 
						|
				continue;
 | 
						|
			}
 | 
						|
 | 
						|
			node = fts_ast_node_create();
 | 
						|
 | 
						|
			node->type = FTS_AST_TERM;
 | 
						|
 | 
						|
			node->term.ptr = fts_ast_string_create(
 | 
						|
						str.f_str, str.f_len);
 | 
						|
 | 
						|
			fts_ast_state_add_node(
 | 
						|
				static_cast<fts_ast_state_t*>(arg), node);
 | 
						|
 | 
						|
			if (first_node) {
 | 
						|
				/* There is more than one word, create
 | 
						|
				a list to organize them */
 | 
						|
				if (!node_list) {
 | 
						|
					node_list = fts_ast_create_node_list(
 | 
						|
						static_cast<fts_ast_state_t*>(
 | 
						|
							arg),
 | 
						|
						 first_node);
 | 
						|
				}
 | 
						|
 | 
						|
				fts_ast_add_node(node_list, node);
 | 
						|
			} else {
 | 
						|
				first_node = node;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return((node_list != NULL) ? node_list : first_node);
 | 
						|
}
 | 
						|
 | 
						|
/******************************************************************//**
 | 
						|
Create an AST term node, makes a copy of ptr for plugin parser
 | 
						|
@return node */
 | 
						|
fts_ast_node_t*
 | 
						|
fts_ast_create_node_term_for_parser(
 | 
						|
/*================================*/
 | 
						|
	void*		arg,		/*!< in: ast state */
 | 
						|
	const char*	ptr,		/*!< in: term string */
 | 
						|
	const ulint	len)		/*!< in: term string length */
 | 
						|
{
 | 
						|
	fts_ast_node_t*		node = NULL;
 | 
						|
 | 
						|
	/* '%' as first char is forbidden for LIKE in internal SQL parser;
 | 
						|
	'%' as last char is reserved for wildcard search;*/
 | 
						|
	if (len == 0 || len > FTS_MAX_WORD_LEN
 | 
						|
	    || ptr[0] == '%' || ptr[len - 1] == '%') {
 | 
						|
		return(NULL);
 | 
						|
	}
 | 
						|
 | 
						|
	node = fts_ast_node_create();
 | 
						|
 | 
						|
	node->type = FTS_AST_TERM;
 | 
						|
 | 
						|
	node->term.ptr = fts_ast_string_create(
 | 
						|
			reinterpret_cast<const byte*>(ptr), len);
 | 
						|
 | 
						|
	fts_ast_state_add_node(static_cast<fts_ast_state_t*>(arg), node);
 | 
						|
 | 
						|
	return(node);
 | 
						|
}
 | 
						|
 | 
						|
/******************************************************************//**
 | 
						|
This function takes ownership of the ptr and is responsible
 | 
						|
for free'ing it.
 | 
						|
@return new node */
 | 
						|
fts_ast_node_t*
 | 
						|
fts_ast_create_node_text(
 | 
						|
/*=====================*/
 | 
						|
	void*			arg,	/*!< in: ast state instance */
 | 
						|
	const fts_ast_string_t*	ptr)	/*!< in: ast text string */
 | 
						|
{
 | 
						|
	ulint		len = ptr->len;
 | 
						|
	fts_ast_node_t*	node = NULL;
 | 
						|
 | 
						|
	/* Once we come here, the string must have at least 2 quotes ""
 | 
						|
	around the query string, which could be empty. Also the query
 | 
						|
	string may contain 0x00 in it, we don't treat it as null-terminated. */
 | 
						|
	ut_ad(len >= 2);
 | 
						|
	ut_ad(ptr->str[0] == '\"' && ptr->str[len - 1] == '\"');
 | 
						|
 | 
						|
	if (len == 2) {
 | 
						|
		/* If the query string contains nothing except quotes,
 | 
						|
		it's obviously an invalid query. */
 | 
						|
		return(NULL);
 | 
						|
	}
 | 
						|
 | 
						|
	node = fts_ast_node_create();
 | 
						|
 | 
						|
	/*!< We ignore the actual quotes "" */
 | 
						|
	len -= 2;
 | 
						|
 | 
						|
	node->type = FTS_AST_TEXT;
 | 
						|
	/*!< Skip copying the first quote */
 | 
						|
	node->text.ptr = fts_ast_string_create(
 | 
						|
			reinterpret_cast<const byte*>(ptr->str + 1), len);
 | 
						|
	node->text.distance = ULINT_UNDEFINED;
 | 
						|
 | 
						|
	fts_ast_state_add_node((fts_ast_state_t*) arg, node);
 | 
						|
 | 
						|
	return(node);
 | 
						|
}
 | 
						|
 | 
						|
/******************************************************************//**
 | 
						|
Create an AST phrase list node for plugin parser
 | 
						|
@return node */
 | 
						|
fts_ast_node_t*
 | 
						|
fts_ast_create_node_phrase_list(
 | 
						|
/*============================*/
 | 
						|
	void*		arg)			/*!< in: ast state */
 | 
						|
{
 | 
						|
	fts_ast_node_t*		node = fts_ast_node_create();
 | 
						|
 | 
						|
	node->type = FTS_AST_PARSER_PHRASE_LIST;
 | 
						|
 | 
						|
	node->text.distance = ULINT_UNDEFINED;
 | 
						|
	node->list.head = node->list.tail = NULL;
 | 
						|
 | 
						|
	fts_ast_state_add_node(static_cast<fts_ast_state_t*>(arg), node);
 | 
						|
 | 
						|
	return(node);
 | 
						|
}
 | 
						|
 | 
						|
/******************************************************************//**
 | 
						|
This function takes ownership of the expr and is responsible
 | 
						|
for free'ing it.
 | 
						|
@return new node */
 | 
						|
fts_ast_node_t*
 | 
						|
fts_ast_create_node_list(
 | 
						|
/*=====================*/
 | 
						|
	void*		arg,			/*!< in: ast state instance */
 | 
						|
	fts_ast_node_t*	expr)			/*!< in: ast expr instance */
 | 
						|
{
 | 
						|
	fts_ast_node_t*	node = fts_ast_node_create();
 | 
						|
 | 
						|
	node->type = FTS_AST_LIST;
 | 
						|
	node->list.head = node->list.tail = expr;
 | 
						|
 | 
						|
	fts_ast_state_add_node((fts_ast_state_t*) arg, node);
 | 
						|
 | 
						|
	return(node);
 | 
						|
}
 | 
						|
 | 
						|
/******************************************************************//**
 | 
						|
Create a sub-expression list node. This function takes ownership of
 | 
						|
expr and is responsible for deleting it.
 | 
						|
@return new node */
 | 
						|
fts_ast_node_t*
 | 
						|
fts_ast_create_node_subexp_list(
 | 
						|
/*============================*/
 | 
						|
	void*		arg,			/*!< in: ast state instance */
 | 
						|
	fts_ast_node_t*	expr)			/*!< in: ast expr instance */
 | 
						|
{
 | 
						|
	fts_ast_node_t*	node = fts_ast_node_create();
 | 
						|
 | 
						|
	node->type = FTS_AST_SUBEXP_LIST;
 | 
						|
	node->list.head = node->list.tail = expr;
 | 
						|
 | 
						|
	fts_ast_state_add_node((fts_ast_state_t*) arg, node);
 | 
						|
 | 
						|
	return(node);
 | 
						|
}
 | 
						|
 | 
						|
/******************************************************************//**
 | 
						|
Free an expr list node elements. */
 | 
						|
static
 | 
						|
void
 | 
						|
fts_ast_free_list(
 | 
						|
/*==============*/
 | 
						|
	fts_ast_node_t*	node)			/*!< in: ast node to free */
 | 
						|
{
 | 
						|
	ut_a(node->type == FTS_AST_LIST
 | 
						|
	     || node->type == FTS_AST_SUBEXP_LIST
 | 
						|
	     || node->type == FTS_AST_PARSER_PHRASE_LIST);
 | 
						|
 | 
						|
	for (node = node->list.head;
 | 
						|
	     node != NULL;
 | 
						|
	     node = fts_ast_free_node(node)) {
 | 
						|
 | 
						|
		/*!< No op */
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/********************************************************************//**
 | 
						|
Free a fts_ast_node_t instance.
 | 
						|
@return next node to free */
 | 
						|
fts_ast_node_t*
 | 
						|
fts_ast_free_node(
 | 
						|
/*==============*/
 | 
						|
	fts_ast_node_t*	node)			/*!< in: the node to free */
 | 
						|
{
 | 
						|
	fts_ast_node_t*	next_node;
 | 
						|
 | 
						|
	switch (node->type) {
 | 
						|
	case FTS_AST_TEXT:
 | 
						|
		if (node->text.ptr) {
 | 
						|
			fts_ast_string_free(node->text.ptr);
 | 
						|
			node->text.ptr = NULL;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
 | 
						|
	case FTS_AST_TERM:
 | 
						|
		if (node->term.ptr) {
 | 
						|
			fts_ast_string_free(node->term.ptr);
 | 
						|
			node->term.ptr = NULL;
 | 
						|
		}
 | 
						|
		break;
 | 
						|
 | 
						|
	case FTS_AST_LIST:
 | 
						|
	case FTS_AST_SUBEXP_LIST:
 | 
						|
	case FTS_AST_PARSER_PHRASE_LIST:
 | 
						|
		fts_ast_free_list(node);
 | 
						|
		node->list.head = node->list.tail = NULL;
 | 
						|
		break;
 | 
						|
 | 
						|
	case FTS_AST_OPER:
 | 
						|
		break;
 | 
						|
 | 
						|
	default:
 | 
						|
		ut_error;
 | 
						|
	}
 | 
						|
 | 
						|
	/*!< Get next node before freeing the node itself */
 | 
						|
	next_node = node->next;
 | 
						|
 | 
						|
	ut_free(node);
 | 
						|
 | 
						|
	return(next_node);
 | 
						|
}
 | 
						|
 | 
						|
/******************************************************************//**
 | 
						|
This AST takes ownership of the expr and is responsible
 | 
						|
for free'ing it.
 | 
						|
@return in param "list" */
 | 
						|
fts_ast_node_t*
 | 
						|
fts_ast_add_node(
 | 
						|
/*=============*/
 | 
						|
	fts_ast_node_t*	node,			/*!< in: list instance */
 | 
						|
	fts_ast_node_t*	elem)			/*!< in: node to add to list */
 | 
						|
{
 | 
						|
	if (!elem) {
 | 
						|
		return(NULL);
 | 
						|
	}
 | 
						|
 | 
						|
	ut_a(!elem->next);
 | 
						|
	ut_a(node->type == FTS_AST_LIST
 | 
						|
	     || node->type == FTS_AST_SUBEXP_LIST
 | 
						|
	     || node->type == FTS_AST_PARSER_PHRASE_LIST);
 | 
						|
 | 
						|
	if (!node->list.head) {
 | 
						|
		ut_a(!node->list.tail);
 | 
						|
 | 
						|
		node->list.head = node->list.tail = elem;
 | 
						|
	} else {
 | 
						|
		ut_a(node->list.tail);
 | 
						|
 | 
						|
		node->list.tail->next = elem;
 | 
						|
		node->list.tail = elem;
 | 
						|
	}
 | 
						|
 | 
						|
	return(node);
 | 
						|
}
 | 
						|
 | 
						|
/******************************************************************//**
 | 
						|
Set the wildcard attribute of a term. */
 | 
						|
void
 | 
						|
fts_ast_term_set_wildcard(
 | 
						|
/*======================*/
 | 
						|
	fts_ast_node_t*	node)			/*!< in/out: set attribute of
 | 
						|
						a term node */
 | 
						|
{
 | 
						|
	if (!node) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	/* If it's a node list, the wildcard should be set to the tail node*/
 | 
						|
	if (node->type == FTS_AST_LIST)	{
 | 
						|
		ut_ad(node->list.tail != NULL);
 | 
						|
		node = node->list.tail;
 | 
						|
	}
 | 
						|
 | 
						|
	ut_a(node->type == FTS_AST_TERM);
 | 
						|
	ut_a(!node->term.wildcard);
 | 
						|
 | 
						|
	node->term.wildcard = TRUE;
 | 
						|
}
 | 
						|
 | 
						|
/******************************************************************//**
 | 
						|
Set the proximity attribute of a text node. */
 | 
						|
void
 | 
						|
fts_ast_text_set_distance(
 | 
						|
/*======================*/
 | 
						|
	fts_ast_node_t*	node,			/*!< in/out: text node */
 | 
						|
	ulint		distance)		/*!< in: the text proximity
 | 
						|
						distance */
 | 
						|
{
 | 
						|
	if (node == NULL) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	ut_a(node->type == FTS_AST_TEXT);
 | 
						|
	ut_a(node->text.distance == ULINT_UNDEFINED);
 | 
						|
 | 
						|
	node->text.distance = distance;
 | 
						|
}
 | 
						|
 | 
						|
/******************************************************************//**
 | 
						|
Free node and expr allocations. */
 | 
						|
void
 | 
						|
fts_ast_state_free(
 | 
						|
/*===============*/
 | 
						|
	fts_ast_state_t*state)			/*!< in: ast state to free */
 | 
						|
{
 | 
						|
	fts_ast_node_t*	node = state->list.head;
 | 
						|
 | 
						|
	/* Free the nodes that were allocated during parsing. */
 | 
						|
	while (node) {
 | 
						|
		fts_ast_node_t*	next = node->next_alloc;
 | 
						|
 | 
						|
		if (node->type == FTS_AST_TEXT && node->text.ptr) {
 | 
						|
			fts_ast_string_free(node->text.ptr);
 | 
						|
			node->text.ptr = NULL;
 | 
						|
		} else if (node->type == FTS_AST_TERM && node->term.ptr) {
 | 
						|
			fts_ast_string_free(node->term.ptr);
 | 
						|
			node->term.ptr = NULL;
 | 
						|
		}
 | 
						|
 | 
						|
		ut_free(node);
 | 
						|
		node = next;
 | 
						|
	}
 | 
						|
 | 
						|
	state->root = state->list.head = state->list.tail = NULL;
 | 
						|
}
 | 
						|
 | 
						|
/** Print the ast string
 | 
						|
@param[in] str		string to print */
 | 
						|
static
 | 
						|
void
 | 
						|
fts_ast_string_print(
 | 
						|
	const fts_ast_string_t*	ast_str)
 | 
						|
{
 | 
						|
	for (ulint i = 0; i < ast_str->len; ++i) {
 | 
						|
		printf("%c", ast_str->str[i]);
 | 
						|
	}
 | 
						|
 | 
						|
	printf("\n");
 | 
						|
}
 | 
						|
 | 
						|
/******************************************************************//**
 | 
						|
Print an ast node recursively. */
 | 
						|
static
 | 
						|
void
 | 
						|
fts_ast_node_print_recursive(
 | 
						|
/*=========================*/
 | 
						|
	fts_ast_node_t*	node,			/*!< in: ast node to print */
 | 
						|
	ulint		level)			/*!< in: recursive level */
 | 
						|
{
 | 
						|
	/* Print alignment blank */
 | 
						|
	for (ulint i = 0; i < level; i++) {
 | 
						|
		printf("  ");
 | 
						|
	}
 | 
						|
 | 
						|
	switch (node->type) {
 | 
						|
	case FTS_AST_TEXT:
 | 
						|
		printf("TEXT: ");
 | 
						|
		fts_ast_string_print(node->text.ptr);
 | 
						|
		break;
 | 
						|
 | 
						|
	case FTS_AST_TERM:
 | 
						|
		printf("TERM: ");
 | 
						|
		fts_ast_string_print(node->term.ptr);
 | 
						|
		break;
 | 
						|
 | 
						|
	case FTS_AST_LIST:
 | 
						|
		printf("LIST: \n");
 | 
						|
 | 
						|
		for (node = node->list.head; node; node = node->next) {
 | 
						|
			fts_ast_node_print_recursive(node, level + 1);
 | 
						|
		}
 | 
						|
		break;
 | 
						|
 | 
						|
	case FTS_AST_SUBEXP_LIST:
 | 
						|
		printf("SUBEXP_LIST: \n");
 | 
						|
 | 
						|
		for (node = node->list.head; node; node = node->next) {
 | 
						|
			fts_ast_node_print_recursive(node, level + 1);
 | 
						|
		}
 | 
						|
		break;
 | 
						|
 | 
						|
	case FTS_AST_OPER:
 | 
						|
		printf("OPER: %d\n", node->oper);
 | 
						|
		break;
 | 
						|
 | 
						|
	case FTS_AST_PARSER_PHRASE_LIST:
 | 
						|
		printf("PARSER_PHRASE_LIST: \n");
 | 
						|
 | 
						|
		for (node = node->list.head; node; node = node->next) {
 | 
						|
			fts_ast_node_print_recursive(node, level + 1);
 | 
						|
		}
 | 
						|
		break;
 | 
						|
 | 
						|
	default:
 | 
						|
		ut_error;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/******************************************************************//**
 | 
						|
Print an ast node */
 | 
						|
void
 | 
						|
fts_ast_node_print(
 | 
						|
/*===============*/
 | 
						|
	fts_ast_node_t* node)		/*!< in: ast node to print */
 | 
						|
{
 | 
						|
	fts_ast_node_print_recursive(node, 0);
 | 
						|
}
 | 
						|
 | 
						|
/** Check only union operation involved in the node
 | 
						|
@param[in]	node	ast node to check
 | 
						|
@return true if the node contains only union else false. */
 | 
						|
bool
 | 
						|
fts_ast_node_check_union(
 | 
						|
	fts_ast_node_t*	node)
 | 
						|
{
 | 
						|
	if (node->type == FTS_AST_LIST
 | 
						|
	    || node->type == FTS_AST_SUBEXP_LIST) {
 | 
						|
 | 
						|
		for (node = node->list.head; node; node = node->next) {
 | 
						|
			if (!fts_ast_node_check_union(node)) {
 | 
						|
				return(false);
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
	} else if (node->type == FTS_AST_PARSER_PHRASE_LIST) {
 | 
						|
		/* Phrase search for plugin parser */
 | 
						|
		return(false);
 | 
						|
	} else if (node->type == FTS_AST_OPER
 | 
						|
		   && (node->oper == FTS_IGNORE
 | 
						|
		       || node->oper == FTS_EXIST)) {
 | 
						|
 | 
						|
		return(false);
 | 
						|
	} else if (node->type == FTS_AST_TEXT) {
 | 
						|
		/* Distance or phrase search query. */
 | 
						|
		return(false);
 | 
						|
	}
 | 
						|
 | 
						|
	return(true);
 | 
						|
}
 | 
						|
 | 
						|
/******************************************************************//**
 | 
						|
Traverse the AST - in-order traversal, except for the FTX_EXIST and FTS_IGNORE
 | 
						|
nodes, which will be ignored in the first pass of each level, and visited in a
 | 
						|
second and third pass after all other nodes in the same level are visited.
 | 
						|
@return DB_SUCCESS if all went well */
 | 
						|
dberr_t
 | 
						|
fts_ast_visit(
 | 
						|
/*==========*/
 | 
						|
	fts_ast_oper_t		oper,		/*!< in: current operator */
 | 
						|
	fts_ast_node_t*		node,		/*!< in: current root node */
 | 
						|
	fts_ast_callback	visitor,	/*!< in: callback function */
 | 
						|
	void*			arg,		/*!< in: arg for callback */
 | 
						|
	bool*			has_ignore)	/*!< out: true, if the operator
 | 
						|
						was ignored during processing,
 | 
						|
						currently we ignore FTS_EXIST
 | 
						|
						and FTS_IGNORE operators */
 | 
						|
{
 | 
						|
	dberr_t			error = DB_SUCCESS;
 | 
						|
	fts_ast_node_t*		oper_node = NULL;
 | 
						|
	fts_ast_node_t*		start_node;
 | 
						|
	bool			revisit = false;
 | 
						|
	bool			will_be_ignored = false;
 | 
						|
	fts_ast_visit_pass_t	visit_pass = FTS_PASS_FIRST;
 | 
						|
	const trx_t*		trx = node->trx;
 | 
						|
 | 
						|
	start_node = node->list.head;
 | 
						|
 | 
						|
	ut_a(node->type == FTS_AST_LIST
 | 
						|
	     || node->type == FTS_AST_SUBEXP_LIST);
 | 
						|
 | 
						|
	if (oper == FTS_EXIST_SKIP) {
 | 
						|
		visit_pass = FTS_PASS_EXIST;
 | 
						|
	} else if (oper == FTS_IGNORE_SKIP) {
 | 
						|
		visit_pass = FTS_PASS_IGNORE;
 | 
						|
	}
 | 
						|
 | 
						|
	/* In the first pass of the tree, at the leaf level of the
 | 
						|
	tree, FTS_EXIST and FTS_IGNORE operation will be ignored.
 | 
						|
	It will be repeated at the level above the leaf level.
 | 
						|
 | 
						|
	The basic idea here is that when we encounter FTS_EXIST or
 | 
						|
	FTS_IGNORE, we will change the operator node into FTS_EXIST_SKIP
 | 
						|
	or FTS_IGNORE_SKIP, and term node & text node with the operators
 | 
						|
	is ignored in the first pass. We have two passes during the revisit:
 | 
						|
	We process nodes with FTS_EXIST_SKIP in the exist pass, and then
 | 
						|
	process nodes with FTS_IGNORE_SKIP in the ignore pass.
 | 
						|
 | 
						|
	The order should be restrictly followed, or we will get wrong results.
 | 
						|
	For example, we have a query 'a +b -c d +e -f'.
 | 
						|
	first pass: process 'a' and 'd' by union;
 | 
						|
	exist pass: process '+b' and '+e' by intersection;
 | 
						|
	ignore pass: process '-c' and '-f' by difference. */
 | 
						|
 | 
						|
	for (node = node->list.head;
 | 
						|
	     node && (error == DB_SUCCESS);
 | 
						|
	     node = node->next) {
 | 
						|
 | 
						|
		switch (node->type) {
 | 
						|
		case FTS_AST_LIST:
 | 
						|
			if (visit_pass != FTS_PASS_FIRST) {
 | 
						|
				break;
 | 
						|
			}
 | 
						|
 | 
						|
			error = fts_ast_visit(oper, node, visitor,
 | 
						|
					      arg, &will_be_ignored);
 | 
						|
 | 
						|
			/* If will_be_ignored is set to true, then
 | 
						|
			we encountered and ignored a FTS_EXIST or FTS_IGNORE
 | 
						|
			operator. */
 | 
						|
			if (will_be_ignored) {
 | 
						|
				revisit = true;
 | 
						|
				/* Remember oper for list in case '-abc&def',
 | 
						|
				ignored oper is from previous node of list.*/
 | 
						|
				node->oper = oper;
 | 
						|
			}
 | 
						|
 | 
						|
			break;
 | 
						|
 | 
						|
		case FTS_AST_OPER:
 | 
						|
			oper = node->oper;
 | 
						|
			oper_node = node;
 | 
						|
 | 
						|
			/* Change the operator for revisit */
 | 
						|
			if (oper == FTS_EXIST) {
 | 
						|
				oper_node->oper = FTS_EXIST_SKIP;
 | 
						|
			} else if (oper == FTS_IGNORE) {
 | 
						|
				oper_node->oper = FTS_IGNORE_SKIP;
 | 
						|
			}
 | 
						|
 | 
						|
			break;
 | 
						|
 | 
						|
		default:
 | 
						|
			if (node->visited) {
 | 
						|
				continue;
 | 
						|
			}
 | 
						|
 | 
						|
			ut_a(oper == FTS_NONE || !oper_node
 | 
						|
			     || oper_node->oper == oper
 | 
						|
			     || oper_node->oper == FTS_EXIST_SKIP
 | 
						|
			     || oper_node->oper == FTS_IGNORE_SKIP);
 | 
						|
 | 
						|
			if (oper== FTS_EXIST || oper == FTS_IGNORE) {
 | 
						|
				*has_ignore = true;
 | 
						|
				continue;
 | 
						|
			}
 | 
						|
 | 
						|
			/* Process leaf node according to its pass.*/
 | 
						|
			if (oper == FTS_EXIST_SKIP
 | 
						|
			    && visit_pass == FTS_PASS_EXIST) {
 | 
						|
				error = visitor(FTS_EXIST, node, arg);
 | 
						|
				node->visited = true;
 | 
						|
			} else if (oper == FTS_IGNORE_SKIP
 | 
						|
				   && visit_pass == FTS_PASS_IGNORE) {
 | 
						|
				error = visitor(FTS_IGNORE, node, arg);
 | 
						|
				node->visited = true;
 | 
						|
			} else if (visit_pass == FTS_PASS_FIRST) {
 | 
						|
				error = visitor(oper, node, arg);
 | 
						|
				node->visited = true;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (trx_is_interrupted(trx)) {
 | 
						|
		return DB_INTERRUPTED;
 | 
						|
	}
 | 
						|
 | 
						|
	if (revisit) {
 | 
						|
		/* Exist pass processes the skipped FTS_EXIST operation. */
 | 
						|
                for (node = start_node;
 | 
						|
		     node && error == DB_SUCCESS;
 | 
						|
		     node = node->next) {
 | 
						|
 | 
						|
			if (node->type == FTS_AST_LIST
 | 
						|
			    && node->oper != FTS_IGNORE) {
 | 
						|
				error = fts_ast_visit(FTS_EXIST_SKIP, node,
 | 
						|
					visitor, arg, &will_be_ignored);
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		/* Ignore pass processes the skipped FTS_IGNORE operation. */
 | 
						|
		for (node = start_node;
 | 
						|
		     node && error == DB_SUCCESS;
 | 
						|
		     node = node->next) {
 | 
						|
 | 
						|
			if (node->type == FTS_AST_LIST) {
 | 
						|
				error = fts_ast_visit(FTS_IGNORE_SKIP, node,
 | 
						|
					visitor, arg, &will_be_ignored);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return(error);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
Create an ast string object, with NUL-terminator, so the string
 | 
						|
has one more byte than len
 | 
						|
@param[in] str		pointer to string
 | 
						|
@param[in] len		length of the string
 | 
						|
@return ast string with NUL-terminator */
 | 
						|
fts_ast_string_t*
 | 
						|
fts_ast_string_create(
 | 
						|
	const byte*	str,
 | 
						|
	ulint		len)
 | 
						|
{
 | 
						|
	fts_ast_string_t*	ast_str;
 | 
						|
 | 
						|
	ut_ad(len > 0);
 | 
						|
 | 
						|
	ast_str = static_cast<fts_ast_string_t*>(
 | 
						|
		ut_malloc_nokey(sizeof(fts_ast_string_t)));
 | 
						|
 | 
						|
	ast_str->str = static_cast<byte*>(ut_malloc_nokey(len + 1));
 | 
						|
 | 
						|
	ast_str->len = len;
 | 
						|
	memcpy(ast_str->str, str, len);
 | 
						|
	ast_str->str[len] = '\0';
 | 
						|
 | 
						|
	return(ast_str);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
Free an ast string instance
 | 
						|
@param[in,out] ast_str		string to free */
 | 
						|
void
 | 
						|
fts_ast_string_free(
 | 
						|
	fts_ast_string_t*	ast_str)
 | 
						|
{
 | 
						|
	if (ast_str != NULL) {
 | 
						|
		ut_free(ast_str->str);
 | 
						|
		ut_free(ast_str);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
Translate ast string of type FTS_AST_NUMB to unsigned long by strtoul
 | 
						|
@param[in] str		string to translate
 | 
						|
@param[in] base		the base
 | 
						|
@return translated number */
 | 
						|
ulint
 | 
						|
fts_ast_string_to_ul(
 | 
						|
	const fts_ast_string_t*	ast_str,
 | 
						|
	int			base)
 | 
						|
{
 | 
						|
	return(strtoul(reinterpret_cast<const char*>(ast_str->str),
 | 
						|
		       NULL, base));
 | 
						|
}
 | 
						|
 | 
						|
#ifdef UNIV_DEBUG
 | 
						|
const char*
 | 
						|
fts_ast_node_type_get(fts_ast_type_t	type)
 | 
						|
{
 | 
						|
	switch (type) {
 | 
						|
	case FTS_AST_OPER:
 | 
						|
		return("FTS_AST_OPER");
 | 
						|
	case FTS_AST_NUMB:
 | 
						|
		return("FTS_AST_NUMB");
 | 
						|
	case FTS_AST_TERM:
 | 
						|
		return("FTS_AST_TERM");
 | 
						|
	case FTS_AST_TEXT:
 | 
						|
		return("FTS_AST_TEXT");
 | 
						|
	case FTS_AST_LIST:
 | 
						|
		return("FTS_AST_LIST");
 | 
						|
	case FTS_AST_SUBEXP_LIST:
 | 
						|
		return("FTS_AST_SUBEXP_LIST");
 | 
						|
	case FTS_AST_PARSER_PHRASE_LIST:
 | 
						|
		return("FTS_AST_PARSER_PHRASE_LIST");
 | 
						|
	}
 | 
						|
	ut_ad(0);
 | 
						|
	return("FTS_UNKNOWN");
 | 
						|
}
 | 
						|
#endif /* UNIV_DEBUG */
 |