diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/TextNode.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/TextNode.php index 5d01e020e5..039e9e9f21 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/TextNode.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/TextNode.php @@ -3,6 +3,7 @@ namespace Alchemy\Phrasea\SearchEngine\Elastic\AST; use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext; +use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryHelper; use Alchemy\Phrasea\SearchEngine\Elastic\Thesaurus\Term; class TextNode extends AbstractTermNode implements ContextAbleInterface @@ -44,16 +45,58 @@ class TextNode extends AbstractTermNode implements ContextAbleInterface ) ); - if ($conceptQueries = $this->buildConceptQueries($context)) { - $textQuery = $query; - $query = array(); - $query['bool']['should'] = $conceptQueries; - $query['bool']['should'][] = $textQuery; + foreach ($this->buildPrivateFieldQueries($context) as $private_field_query) { + $query = QueryHelper::applyBooleanClause($query, 'should', $private_field_query); + } + + foreach ($this->buildConceptQueries($context) as $concept_query) { + $query = QueryHelper::applyBooleanClause($query, 'should', $concept_query); } return $query; } + private function buildPrivateFieldQueries(QueryContext $context) + { + // We make a boolean clause for each collection set to shrink query size + // (instead of a clause for each field, with his collection set) + $fields_map = []; + $collections_map = []; + foreach ($context->getAllowedPrivateFields() as $field) { + $collections = $context->getAllowedCollectionsOnPrivateField($field); + $hash = self::hashCollections($collections); + $collections_map[$hash] = $collections; + if (!isset($fields_map[$hash])) { + $fields_map[$hash] = []; + } + // Merge fields with others having the same collections + $fields = $context->localizeField($field->getIndexFieldName()); + foreach ($fields as $fields_map[$hash][]); + } + + $queries = []; + foreach ($fields_map as $hash => $fields) { + // Right to query on a private field is dependant of document collection + // Here we make sure we can only match on allowed collections + $match = []; + $match['multi_match']['fields'] = $fields; + $match['multi_match']['query'] = $this->text; + $match['multi_match']['operator'] = 'and'; + $query = []; + $query['bool']['must'][0]['terms']['base_id'] = $collections_map[$hash]; + $query['bool']['must'][1] = $match; + $queries[] = $query; + } + + return $queries; + } + + private static function hashCollections(array $collections) + { + sort($collections, SORT_REGULAR); + return implode('|', $collections); + } + public function __toString() { return sprintf('', Term::dump($this)); diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticSearchEngine.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticSearchEngine.php index c21a102e48..f38085db00 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticSearchEngine.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/ElasticSearchEngine.php @@ -300,6 +300,9 @@ class ElasticSearchEngine implements SearchEngineInterface ); } + /** + * @todo Move in search engine service provider + */ private function createQueryContext(SearchEngineOptions $options) { // TODO handle $user when null @@ -321,6 +324,7 @@ class ElasticSearchEngine implements SearchEngineInterface * "OtherFieldName" => [4], * ] * + * @todo Move in query context * @param SearchEngineOptions $options * @return array */ @@ -334,8 +338,11 @@ class ElasticSearchEngine implements SearchEngineInterface $map = $this->structure->getCollectionsUsedByPrivateFields(); // Remove collections base_id which access is restricted. - foreach ($map as $_ => &$collections) { + foreach ($map as $key => &$collections) { $collections = array_intersect($collections, $allowed_collections); + if (!$collections) { + unset($map[$key]); + } } return $map; diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryContext.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryContext.php index 88cb71d387..bef69453c7 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryContext.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryContext.php @@ -3,6 +3,7 @@ namespace Alchemy\Phrasea\SearchEngine\Elastic\Search; use Alchemy\Phrasea\SearchEngine\Elastic\Exception\QueryException; +use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Field; use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Structure; /** @@ -46,10 +47,7 @@ class QueryContext public function getRawFields() { if ($this->fields === null) { - return array( - 'caption_all.raw', - 'private_caption_all.raw' - ); + return array('caption_all.raw'); } $fields = array(); @@ -66,10 +64,7 @@ class QueryContext public function getLocalizedFields() { if ($this->fields === null) { - return array_merge( - $this->localizeField('caption_all'), - $this->localizeField('private_caption_all') - ); + return $this->localizeField('caption_all'); } $fields = array(); @@ -81,7 +76,27 @@ class QueryContext return $fields; } - private function localizeField($field) + public function getAllowedPrivateFields() + { + $allowed_field_names = array_keys($this->privateCollectionMap); + + return array_map(array($this->structure, 'get'), $allowed_field_names); + } + + public function getAllowedCollectionsOnPrivateField(Field $field) + { + $name = $field->getName(); + if (!isset($this->privateCollectionMap[$name])) { + throw new \OutOfRangeException('Given field is not an allowed private field.'); + } + + return $this->privateCollectionMap[$name]; + } + + /** + * @todo Maybe we should put this logic in Field class? + */ + public function localizeField($field) { $fields = array(); foreach ($this->locales as $locale) { diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryHelper.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryHelper.php new file mode 100644 index 0000000000..b7d09eb87c --- /dev/null +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryHelper.php @@ -0,0 +1,47 @@ +id])) { + return $base_ids_cache[$this->id]; } $conn = $this->get_appbox()->get_connection(); @@ -250,7 +250,7 @@ class databox extends base $base_ids[] = (int) $row['base_id']; } - return $base_ids; + return $base_ids_cache[$this->id] = $base_ids; } protected function get_available_collections()