/** * @return array(string) */ public function getServerList() { // This clause provides backwards compatability with previous versions // of CirrusSearch. Once this variable is removed cluster configuration // will work as expected. if ($this->config->has('CirrusSearchServers')) { return $this->config->get('CirrusSearchServers'); } else { return $this->config->getElement('CirrusSearchClusters', $this->cluster); } }
/** * @param string $query * @param boolean $isRescore * @return \Elastica\Query\Simple */ public function wrapInSaferIfPossible($query, $isRescore) { // @todo: move this code to a common base class when Filters is refactored as non-static $saferQuery = $this->config->getElement('CirrusSearchWikimediaExtraPlugin', 'safer'); if (is_null($saferQuery)) { return $query; } $saferQuery['query'] = $query->toArray(); $tooLargeAction = $isRescore ? 'convert_to_match_all_query' : 'convert_to_term_queries'; $saferQuery['phrase']['phrase_too_large_action'] = $tooLargeAction; return new \Elastica\Query\Simple(array('safer' => $saferQuery)); }
private function decideCluster(SearchConfig $config) { $cluster = $this->getOption('cluster', null); if ($cluster === null) { return null; } if ($config->has('CirrusSearchServers')) { $this->error('Not configured for cluster operations.', 1); } $hosts = $config->getElement('CirrusSearchClusters', $cluster); if ($hosts === null) { $this->error('Unknown cluster.', 1); } return $cluster; }
/** * If there is any boosting to be done munge the the current query to get it right. */ private function installBoosts() { // Quick note: At the moment ".isEmpty()" is _much_ faster then ".empty". Never // use ".empty". See https://github.com/elasticsearch/elasticsearch/issues/5086 if ($this->sort !== 'relevance') { // Boosts are irrelevant if you aren't sorting by, well, relevance return; } $functionScore = new \Elastica\Query\FunctionScore(); $useFunctionScore = false; // Customize score by boosting based on incoming links count if ($this->boostLinks) { $useFunctionScore = true; if ($this->config->getElement('CirrusSearchWikimediaExtraPlugin', 'field_value_factor_with_default')) { $functionScore->addFunction('field_value_factor_with_default', array('field' => 'incoming_links', 'modifier' => 'log2p', 'missing' => 0)); } else { $scoreBoostExpression = "log10(doc['incoming_links'].value + 2)"; $functionScore->addScriptScoreFunction(new \Elastica\Script($scoreBoostExpression, null, 'expression')); } } // Customize score by decaying a portion by time since last update if ($this->preferRecentDecayPortion > 0 && $this->preferRecentHalfLife > 0) { // Convert half life for time in days to decay constant for time in milliseconds. $decayConstant = log(2) / $this->preferRecentHalfLife / 86400000; $parameters = array('decayConstant' => $decayConstant, 'decayPortion' => $this->preferRecentDecayPortion, 'nonDecayPortion' => 1 - $this->preferRecentDecayPortion, 'now' => time() * 1000); // e^ct where t is last modified time - now which is negative $exponentialDecayExpression = "exp(decayConstant * (doc['timestamp'].value - now))"; if ($this->preferRecentDecayPortion !== 1.0) { $exponentialDecayExpression = "{$exponentialDecayExpression} * decayPortion + nonDecayPortion"; } $functionScore->addScriptScoreFunction(new \Elastica\Script($exponentialDecayExpression, $parameters, 'expression')); $useFunctionScore = true; } // Add boosts for pages that contain certain templates if ($this->boostTemplates) { foreach ($this->boostTemplates as $name => $boost) { $match = new \Elastica\Query\Match(); $match->setFieldQuery('template', $name); $filterQuery = new \Elastica\Filter\Query($match); $filterQuery->setCached(true); $functionScore->addBoostFactorFunction($boost, $filterQuery); } $useFunctionScore = true; } // Add boosts for namespaces $namespacesToBoost = $this->namespaces ?: MWNamespace::getValidNamespaces(); if ($namespacesToBoost) { // Group common weights together and build a single filter per weight // to save on filters. $weightToNs = array(); foreach ($namespacesToBoost as $ns) { $weight = $this->getBoostForNamespace($ns); $weightToNs[(string) $weight][] = $ns; } if (count($weightToNs) > 1) { unset($weightToNs['1']); // That'd be redundant. foreach ($weightToNs as $weight => $namespaces) { $filter = new \Elastica\Filter\Terms('namespace', $namespaces); $functionScore->addBoostFactorFunction($weight, $filter); $useFunctionScore = true; } } } // Boost pages in a user's language $userLang = $this->config->getUserLanguage(); $userWeight = $this->config->getElement('CirrusSearchLanguageWeight', 'user'); if ($userWeight) { $functionScore->addBoostFactorFunction($userWeight, new \Elastica\Filter\Term(array('language' => $userLang))); $useFunctionScore = true; } // And a wiki's language, if it's different $wikiWeight = $this->config->getElement('CirrusSearchLanguageWeight', 'wiki'); if ($userLang != $this->config->get('LanguageCode') && $wikiWeight) { $functionScore->addBoostFactorFunction($wikiWeight, new \Elastica\Filter\Term(array('language' => $this->config->get('LanguageCode')))); $useFunctionScore = true; } if (!$useFunctionScore) { // Nothing to do return; } // The function score is done as a rescore on top of everything else $this->rescore[] = array('window_size' => $this->config->get('CirrusSearchFunctionRescoreWindowSize'), 'query' => array('rescore_query' => $functionScore, 'query_weight' => 1.0, 'rescore_query_weight' => 1.0, 'score_mode' => 'multiply')); }
/** * Produce a set of completion suggestions for text using _suggest * See https://www.elastic.co/guide/en/elasticsearch/reference/1.6/search-suggesters-completion.html * * WARNING: experimental API * * @param string $text Search term * @param string[]|null $variants Search term variants * (usually issued from $wgContLang->autoConvertToAllVariants( $text ) ) * @param array $context * @return Status */ public function suggest($text, $variants = null, $context = null) { $this->setTermAndVariants($text, $variants); $this->context = $context; list($profiles, $suggest) = $this->buildQuery(); $queryOptions = array(); $queryOptions['timeout'] = $this->config->getElement('CirrusSearchSearchShardTimeout', 'default'); $this->connection->setTimeout($queryOptions['timeout']); $index = $this->connection->getIndex($this->indexBaseName, Connection::TITLE_SUGGEST_TYPE); $logContext = array('query' => $text, 'queryType' => $this->queryType); $searcher = $this; $limit = $this->limit; $result = Util::doPoolCounterWork('CirrusSearch-Search', $this->user, function () use($searcher, $index, $suggest, $logContext, $queryOptions, $profiles, $text, $limit) { $description = "{queryType} search for '{query}'"; $searcher->start($description, $logContext); try { $result = $index->request("_suggest", Request::POST, $suggest, $queryOptions); if ($result->isOk()) { $result = $searcher->postProcessSuggest($result, $profiles, $limit); return $searcher->success($result); } return $result; } catch (\Elastica\Exception\ExceptionInterface $e) { return $searcher->failure($e); } }); return $result; }