47 private function parseQuery( $filteredText, $fulltext ) {
50 $this->searchTerms = [];
52 # @todo FIXME: This doesn't handle parenthetical expressions.
54 if ( preg_match_all(
'/([-+<>~]?)(([' . $lc .
']+)(\*?)|"[^"]*")/',
55 $filteredText, $m, PREG_SET_ORDER )
57 $services = MediaWikiServices::getInstance();
58 $contLang = $services->getContentLanguage();
59 $langConverter = $services->getLanguageConverterFactory()->getLanguageConverter( $contLang );
60 foreach ( $m as $bits ) {
61 Wikimedia\suppressWarnings();
62 list( , $modifier, $term, $nonQuoted, $wildcard ) = $bits;
63 Wikimedia\restoreWarnings();
65 if ( $nonQuoted !=
'' ) {
69 $term = str_replace(
'"',
'', $term );
73 if ( $searchon !==
'' ) {
76 if ( $this->strictMatching && ( $modifier ==
'' ) ) {
83 $convertedVariants = $langConverter->autoConvertToAllVariants( $term );
84 if ( is_array( $convertedVariants ) ) {
85 $variants = array_unique( array_values( $convertedVariants ) );
87 $variants = [ $term ];
94 $strippedVariants = array_map( [ $contLang,
'normalizeForSearch' ], $variants );
99 $strippedVariants = array_unique( $strippedVariants );
101 $searchon .= $modifier;
102 if ( count( $strippedVariants ) > 1 ) {
105 foreach ( $strippedVariants as $stripped ) {
107 if ( $nonQuoted && strpos( $stripped,
' ' ) !==
false ) {
111 $stripped =
'"' . trim( $stripped ) .
'"';
113 $searchon .=
"$quote$stripped$quote$wildcard ";
115 if ( count( $strippedVariants ) > 1 ) {
121 $regexp = $this->
regexTerm( $term, $wildcard );
122 $this->searchTerms[] = $regexp;
124 wfDebug( __METHOD__ .
": Would search with '$searchon'" );
125 wfDebug( __METHOD__ .
': Match with /' . implode(
'|', $this->searchTerms ) .
"/" );
127 wfDebug( __METHOD__ .
": Can't understand search query '{$filteredText}'" );
131 $searchon =
$dbr->addQuotes( $searchon );
134 " MATCH($field) AGAINST($searchon IN BOOLEAN MODE) ",
135 " MATCH($field) AGAINST($searchon IN NATURAL LANGUAGE MODE) DESC "
140 $regex = preg_quote( $string,
'/' );
141 if ( MediaWikiServices::getInstance()->getContentLanguage()->hasWordBreaks() ) {
146 $regex =
"\b$regex\b";
157 $searchChars = parent::legalSearchChars(
$type );
158 if (
$type === self::CHARS_ALL ) {
160 $searchChars =
"\"*" . $searchChars;
187 if ( trim( $term ) ===
'' ) {
191 $filteredTerm = $this->
filter( $term );
192 $query = $this->
getQuery( $filteredTerm, $fulltext );
194 $resultSet =
$dbr->select(
195 $query[
'tables'], $query[
'fields'], $query[
'conds'],
196 __METHOD__, $query[
'options'], $query[
'joins']
201 $totalResult =
$dbr->select(
202 $query[
'tables'], $query[
'fields'], $query[
'conds'],
203 __METHOD__, $query[
'options'], $query[
'joins']
206 $row = $totalResult->fetchObject();
208 $total = intval( $row->c );
210 $totalResult->free();
216 switch ( $feature ) {
217 case 'title-suffix-filter':
220 return parent::supports( $feature );
230 foreach ( $this->features as $feature => $value ) {
231 if ( $feature ===
'title-suffix-filter' && $value ) {
233 $query[
'conds'][] =
'page_title' .
$dbr->buildLike(
$dbr->anyString(), $value );
244 if ( is_array( $this->namespaces ) ) {
245 if ( count( $this->namespaces ) === 0 ) {
246 $this->namespaces[] =
'0';
270 private function getQuery( $filteredTerm, $fulltext ) {
279 $this->
queryMain( $query, $filteredTerm, $fulltext );
293 return $fulltext ?
'si_text' :
'si_title';
304 private function queryMain( &$query, $filteredTerm, $fulltext ) {
305 $match = $this->
parseQuery( $filteredTerm, $fulltext );
306 $query[
'tables'][] =
'page';
307 $query[
'tables'][] =
'searchindex';
308 $query[
'fields'][] =
'page_id';
309 $query[
'fields'][] =
'page_namespace';
310 $query[
'fields'][] =
'page_title';
311 $query[
'conds'][] =
'page_id=si_page';
312 $query[
'conds'][] = $match[0];
313 $query[
'options'][
'ORDER BY'] = $match[1];
323 $match = $this->
parseQuery( $filteredTerm, $fulltext );
326 'tables' => [
'page',
'searchindex' ],
327 'fields' => [
'COUNT(*) as c' ],
328 'conds' => [
'page_id=si_page', $match[0] ],
348 $dbw = $this->lb->getConnectionRef(
DB_PRIMARY );
369 $dbw = $this->lb->getConnectionRef(
DB_PRIMARY );
370 $dbw->update(
'searchindex',
372 [
'si_page' => $id ],
385 $dbw = $this->lb->getConnectionRef(
DB_PRIMARY );
386 $dbw->delete(
'searchindex', [
'si_page' => $id ], __METHOD__ );
396 $out = parent::normalizeText( $string );
400 $out = preg_replace_callback(
401 "/([\\xc0-\\xff][\\x80-\\xbf]*)/",
402 [ $this,
'stripForSearchCallback' ],
403 MediaWikiServices::getInstance()->getContentLanguage()->lc( $out ) );
409 if ( $minLength > 1 ) {
438 return 'u8' . bin2hex(
$matches[1] );
448 if ( self::$mMinSearchLength ===
null ) {
449 $sql =
"SHOW GLOBAL VARIABLES LIKE 'ft\\_min\\_word\\_len'";
452 $result =
$dbr->query( $sql, __METHOD__ );
453 $row = $result->fetchObject();
456 if ( $row && $row->Variable_name ==
'ft_min_word_len' ) {
457 self::$mMinSearchLength = intval( $row->Value );
459 self::$mMinSearchLength = 0;
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Base search engine base class for database-backed searches.
filter( $text)
Return a 'cleaned up' search string.
Search engine hook for MySQL.
parseQuery( $filteredText, $fulltext)
Parse the user's query and transform it into two SQL fragments: a WHERE condition and an ORDER BY exp...
queryMain(&$query, $filteredTerm, $fulltext)
Get the base part of the search query.
updateTitle( $id, $title)
Update a search index record's title only.
doSearchTitleInDB( $term)
Perform a title-only search query and return a result set.
getQuery( $filteredTerm, $fulltext)
Construct the SQL query to do the search.
stripForSearchCallback( $matches)
Armor a case-folded UTF-8 string to get through MySQL's fulltext search without being mucked up by fu...
getIndexField( $fulltext)
Picks which field to index on, depending on what type of query.
queryFeatures(&$query)
Add special conditions.
update( $id, $title, $text)
Create or update the search index record for the given page.
searchInternal( $term, $fulltext)
limitResult(&$query)
Add limit options.
regexTerm( $string, $wildcard)
queryNamespaces(&$query)
Add namespace conditions.
legalSearchChars( $type=self::CHARS_ALL)
Get chars legal for search.
getCountQuery( $filteredTerm, $fulltext)
normalizeText( $string)
Converts some characters for MySQL's indexing to grok it correctly, and pads short words to overcome ...
minSearchLength()
Check MySQL server's ft_min_word_len setting so we know if we need to pad short words....
doSearchTextInDB( $term)
Perform a full text search query and return a result set.
This class is used for different SQL-based search engines shipped with MediaWiki.