yf_search_driver_db_fulltext classSearch with MATCH (fields) AGAINST ('+word ...' IN BOOLEAN MODE) on MySQL / MariaDB: needs one FULLTEXT index over exactly the searched fields. Every word is required. Boolean-mode operators (+ - < > ( ) ~ * " @) cannot be escaped, so every character the InnoDB tokenizer does not keep in a word (all but letters, digits, _ and ') becomes a space. Words InnoDB does not index (shorter than FT_MIN_WORD_LEN, or in FT_STOPWORDS) make a required term match nothing, so they are left out; when no word is left, and on pgsql (no FULLTEXT index there), the search falls back to db_like (ILIKE). The score is MATCH()'s relevance: field weights apply only to the db_like fallback.
| File | plugins/search/classes/search/yf_search_driver_db_fulltext.class.php |
|---|---|
| Use | _class('search_driver_db_fulltext', 'classes/search/') |
| Extends | yf_search_driver_db_like → yf_search_driver |
A site sets them in its config with $PROJECT_CONF['search_driver_db_fulltext']['NAME'] = ...;
| Property | Default | Description |
|---|---|---|
$FT_MIN_WORD_LENint |
3 |
innodb_ft_min_token_size (MyISAM: ft_min_word_len, default 4) |
$FT_STOPWORDSstring[] |
|
InnoDB default stopwords (information_schema.INNODB_FT_DEFAULT_STOPWORD) |
from yf_search_driver_db_like: $DB, $PARENT, $SUGGEST_LIMIT
from yf_search_driver: $DEF_LIMIT, $MAX_LIMIT, $MAX_WINDOW, $MAX_WORDS, $last_error
public function escape($text)
Text for AGAINST('...'): operators and other non-word characters replaced by spaces, then the literal escaping (pgsql: as db_like).
public function escape($text)
{
if ($this->_family() === 'pgsql') {
return parent::escape($text);
}
return (string) $this->_db()->es($this->_strip_operators((string) $text));
}
_ hooks and helpers; not routable as page actionspublic function __call($name, $args)
Catch missing method call.
public function __call($name, $args)
{
return main()->extend_call($this, $name, $args);
}
public function _ft_words($words)
Words that go into the boolean query: operators removed, unindexed words dropped.
Returns string[]
public function _ft_words($words)
{
$out = [];
foreach ((array) $words as $word) {
foreach ($this->_words($this->_strip_operators($word)) as $part) {
$part = trim($part, "'");
$lower = mb_strtolower($part);
if (mb_strlen($part) >= (int) $this->FT_MIN_WORD_LEN && ! in_array($lower, $this->FT_STOPWORDS, true)) {
$out[$lower] ??= $part;
}
}
}
return array_values($out);
}
public function _highlight_words($words)
Returns string[]
public function _highlight_words($words)
{
return ($this->_family() === 'pgsql' ? [] : $this->_ft_words($words)) ?: (array) $words;
}
public function _strip_operators($text)
public function _strip_operators($text)
{
$text = (string) $text;
// invalid UTF-8 makes the /u pattern fail: then only ASCII non-word bytes go
return preg_replace('~[^\p{L}\p{N}_\']+~u', ' ', $text) ?? (string) preg_replace('~[\x00-\x26\x28-\x2f\x3a-\x40\x5b-\x5e\x60\x7b-\x7f]+~', ' ', $text);
}
public function _text_sql($p, $words)
$p mixed normalized paramsReturns array{0: string, 1: string}
public function _text_sql($p, $words)
{
$ft_words = $this->_family() === 'pgsql' ? [] : $this->_ft_words($words);
if ( ! $ft_words) {
return parent::_text_sql($p, $words);
}
$against = [];
foreach ($ft_words as $word) {
$against[] = '+' . $this->_db()->es($word);
}
$match = 'MATCH (' . implode(', ', array_map([$this, '_quote_name'], array_keys($p['fields'])))
. ") AGAINST ('" . implode(' ', $against) . "' IN BOOLEAN MODE)";
return [$match, $match];
}
from yf_search_driver_db_like: _db(), _family(), _filters_sql(), _get_all(), _highlight_text(), _init(), _like_escape(), _like_op(), _number(), _quote_name(), _table(), _value(), capabilities(), connect(), is_ready(), raw(), search(), suggest()
from yf_search_driver: _doc_id(), _fail(), _highlight_html(), _highlight_markers(), _is_valid_name(), _name(), _normalize_filter(), _normalize_params(), _not_supported(), _without_id(), _words(), add(), bulk(), create_index(), delete(), drop_index(), index_exists(), truncate(), update()