From 94b5d7b85dd26682ce0fe39691918865d0a50d29 Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Thu, 25 Jun 2015 10:52:56 +1200 Subject: [PATCH] Enable indexes to upload custom config Robust parsing of collation suggestions Support spelling suggestions Include default optional dictionary for _spellcheckText column --- code/solr/Solr.php | 104 +++++++++++++++++++----------- code/solr/SolrIndex.php | 60 +++++++++++++++-- conf/solr/4/extras/solrconfig.xml | 32 ++++++++- 3 files changed, 151 insertions(+), 45 deletions(-) diff --git a/code/solr/Solr.php b/code/solr/Solr.php index e48eebe..53f0530 100644 --- a/code/solr/Solr.php +++ b/code/solr/Solr.php @@ -96,6 +96,12 @@ class Solr { /** @var [SolrService_Core] - The instances of SolrService_Core for each core */ static protected $service_core_singletons = array(); + /** + * Get a SolrService + * + * @param string $core Optional core name + * @return SolrService_Core + */ static function service($core = null) { $options = self::solr_options(); @@ -143,58 +149,82 @@ class Solr { class Solr_Configure extends BuildTask { public function run($request) { - $service = Solr::service(); + // Find the IndexStore handler, which will handle uploading config files to Solr + $store = $this->getSolrConfigStore(); $indexes = Solr::get_indexes(); - $options = Solr::solr_options(); + foreach ($indexes as $instance) { + try { + $this->updateIndex($instance, $store); + } catch(Exception $e) { + // We got an exception. Warn, but continue to next index. + $this->log("Failure: " . $e->getMessage()); + } + } + } + + /** + * Update the index on the given store + * + * @param SolrIndex $instance Instance + * @param SolrConfigStore $store + */ + protected function updateIndex($instance, $store) { + $index = $instance->getIndexName(); + $this->log("Configuring $index."); + $this->log("Uploading configuration ... "); + + + // Upload the config files for this index + $instance->uploadConfig($store); + + // Then tell Solr to use those config files + $service = Solr::service(); + if ($service->coreIsActive($index)) { + $this->log("Reloading core ..."); + $service->coreReload($index); + } else { + $this->log("Creating core ..."); + $service->coreCreate($index, $store->instanceDir($index)); + } + + $this->log("Done"); + } + + /** + * Get config store + * + * @return SolrConfigStore + */ + protected function getSolrConfigStore() { + $options = Solr::solr_options(); + if (!isset($options['indexstore']) || !($indexstore = $options['indexstore'])) { user_error('No index configuration for Solr provided', E_USER_ERROR); } - + // Find the IndexStore handler, which will handle uploading config files to Solr $mode = $indexstore['mode']; if ($mode == 'file') { - $store = new SolrConfigStore_File($indexstore); + return new SolrConfigStore_File($indexstore); } elseif ($mode == 'webdav') { - $store = new SolrConfigStore_WebDAV($indexstore); + return new SolrConfigStore_WebDAV($indexstore); } elseif (ClassInfo::exists($mode) && ClassInfo::classImplements($mode, 'SolrConfigStore')) { - $store = new $mode($indexstore); + return new $mode($indexstore); } else { user_error('Unknown Solr index mode '.$indexstore['mode'], E_USER_ERROR); } - - foreach ($indexes as $instance) { - $index = $instance->getIndexName(); - echo "Configuring $index. \n"; flush(); - - try { - // Upload the config files for this index - echo "Uploading configuration ... \n"; flush(); - - $store->uploadString($index, 'schema.xml', (string)$instance->generateSchema()); - - foreach (glob($instance->getExtrasPath().'/*') as $file) { - if (is_file($file)) $store->uploadFile($index, $file); - } - - // Then tell Solr to use those config files - if ($service->coreIsActive($index)) { - echo "Reloading core ... \n"; - $service->coreReload($index); - } else { - echo "Creating core ... \n"; - $service->coreCreate($index, $store->instanceDir($index)); - } - - // And done - echo "Done\n"; - - } catch(Exception $e) { - // We got an exception. Warn, but continue to next index. - echo "Failure: " . $e->getMessage() . "\n"; flush(); - } + + } + + protected function log($message) { + if(Director::is_cli()) { + echo $message . "\n"; + } else { + echo Convert::raw2xml($message) . "
"; } + flush(); } } diff --git a/code/solr/SolrIndex.php b/code/solr/SolrIndex.php index 172e630..bea3b1d 100644 --- a/code/solr/SolrIndex.php +++ b/code/solr/SolrIndex.php @@ -141,6 +141,26 @@ abstract class SolrIndex extends SearchIndex { return implode("\n\t\t", $xml); } + + /** + * Extract first suggestion text from collated values + * + * @param mixed $collation + * @return string + */ + protected function getCollatedSuggestion($collation = '') { + if(is_string($collation)) { + return $collation; + } + if(is_object($collation)) { + if(isset($collation->misspellingsAndCorrections)) { + foreach($collation->misspellingsAndCorrections as $key => $value) { + return $value; + } + } + } + return ''; + } /** * Extract a human friendly spelling suggestion from a Solr spellcheck collation string. @@ -472,14 +492,18 @@ abstract class SolrIndex extends SearchIndex { * @param SearchQuery $query * @param integer $offset * @param integer $limit - * @param Array $params Extra request parameters passed through to Solr + * @param array $params Extra request parameters passed through to Solr * @return ArrayData Map with the following keys: * - 'Matches': ArrayList of the matched object instances */ public function search(SearchQuery $query, $offset = -1, $limit = -1, $params = array()) { $service = $this->getService(); - - SearchVariant::with(count($query->classes) == 1 ? $query->classes[0]['class'] : null)->call('alterQuery', $query, $this); + + $searchClass = count($query->classes) == 1 + ? $query->classes[0]['class'] + : null; + SearchVariant::with($searchClass) + ->call('alterQuery', $query, $this); $q = array(); // Query $fq = array(); // Filter query @@ -665,15 +689,18 @@ abstract class SolrIndex extends SearchIndex { // Suggestions. Requires spellcheck.collate=true in $params if(isset($res->spellcheck->suggestions->collation)) { + // Extract string suggestion + $suggestion = $this->getCollatedSuggestion($res->spellcheck->suggestions->collation); + // The collation, including advanced query params (e.g. +), suitable for making another query programmatically. - $ret['Suggestion'] = $res->spellcheck->suggestions->collation; + $ret['Suggestion'] = $suggestion; // A human friendly version of the suggestion, suitable for 'Did you mean $SuggestionNice?' display. - $ret['SuggestionNice'] = $this->getNiceSuggestion($res->spellcheck->suggestions->collation); + $ret['SuggestionNice'] = $this->getNiceSuggestion($suggestion); // A string suitable for appending to an href as a query string. // For example $SuggestionNice - $ret['SuggestionQueryString'] = $this->getSuggestionQueryString($res->spellcheck->suggestions->collation); + $ret['SuggestionQueryString'] = $this->getSuggestionQueryString($suggestion); } } @@ -694,4 +721,25 @@ abstract class SolrIndex extends SearchIndex { $this->service = $service; return $this; } + + /** + * Upload config for this index to the given store + * + * @param SolrConfigStore $store + */ + public function uploadConfig($store) { + // Upload the config files for this index + $store->uploadString( + $this->getIndexName(), + 'schema.xml', + (string)$this->generateSchema() + ); + + // Upload additional files + foreach (glob($this->getExtrasPath().'/*') as $file) { + if (is_file($file)) { + $store->uploadFile($this->getIndexName(), $file); + } + } + } } diff --git a/conf/solr/4/extras/solrconfig.xml b/conf/solr/4/extras/solrconfig.xml index 60ae117..8e19810 100644 --- a/conf/solr/4/extras/solrconfig.xml +++ b/conf/solr/4/extras/solrconfig.xml @@ -863,6 +863,10 @@ nameOfCustomComponent2 --> + + + spellcheck + @@ -1254,7 +1258,31 @@ default - text + _text + solr.DirectSolrSpellChecker + + internal + + 0.5 + + 2 + + 1 + + 5 + + 4 + + 0.01 + + + + + + _spellcheck + _spellcheckText solr.DirectSolrSpellChecker internal @@ -1279,7 +1307,7 @@ wordbreak solr.WordBreakSolrSpellChecker - name + _text true true 10