yf_search_driver_db_fulltext class

Search 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.

Fileplugins/search/classes/search/yf_search_driver_db_fulltext.class.php
Use_class('search_driver_db_fulltext', 'classes/search/')
Extendsyf_search_driver_db_like → yf_search_driver

Public properties

A site sets them in its config with $PROJECT_CONF['search_driver_db_fulltext']['NAME'] = ...;

PropertyDefaultDescription
$FT_MIN_WORD_LEN
int
3 innodb_ft_min_token_size (MyISAM: ft_min_word_len, default 4)
$FT_STOPWORDS
string[]
['a', 'about', 'an', 'are', 'as', 'at', ... (35 items)]
array (
  0 => 'a',
  1 => 'about',
  2 => 'an',
  3 => 'are',
  4 => 'as',
  5 => 'at',
  6 => 'be',
  7 => 'by',
  8 => 'com',
  9 => 'de',
  10 => 'en',
  11 => 'for',
  12 => 'from',
  13 => 'how',
  14 => 'i',
  15 => 'in',
  16 => 'is',
  17 => 'it',
  18 => 'la',
  19 => 'of',
  20 => 'on',
  21 => 'or',
  22 => 'that',
  23 => 'the',
  24 => 'this',
  25 => 'to',
  26 => 'und',
  27 => 'was',
  28 => 'what',
  29 => 'when',
  30 => 'where',
  31 => 'who',
  32 => 'will',
  33 => 'with',
  34 => 'www',
)
InnoDB default stopwords (information_schema.INNODB_FT_DEFAULT_STOPWORD)

Inherited properties

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 methods 6

escape

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));
    }

Methods starting with _ hooks and helpers; not routable as page actions

__call

public function __call($name, $args)

Catch missing method call.

    public function __call($name, $args)
    {
        return main()->extend_call($this, $name, $args);
    }

_ft_words

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);
    }

_text_sql

public function _text_sql($p, $words)
  • $p mixed normalized params

Returns 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];
    }

Inherited methods

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()