diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/AbstractTermNode.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/AbstractTermNode.php index 57aa71ffcc..91201e1668 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/AbstractTermNode.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/AbstractTermNode.php @@ -13,6 +13,7 @@ abstract class AbstractTermNode extends Node implements TermInterface protected $text; protected $context; private $concepts = []; + private $pruned_concepts; public function __construct($text, Context $context = null) { @@ -22,56 +23,70 @@ abstract class AbstractTermNode extends Node implements TermInterface public function setConcepts(array $concepts) { + $this->pruned_concepts = null; $this->concepts = $concepts; } + private function getPrunedConcepts() + { + if ($this->pruned_concepts === null) { + $this->pruned_concepts = Concept::pruneNarrowConcepts($this->concepts); + } + return $this->pruned_concepts; + } + protected function buildConceptQueries(QueryContext $context) { - $concepts = Concept::pruneNarrowConcepts($this->concepts); - if (!$concepts) { + if (!$this->getPrunedConcepts()) { return []; } - $query_builder = function (array $fields) use ($concepts) { - $index_fields = []; - foreach ($fields as $field) { - $index_fields[] = $field->getConceptPathIndexField(); - } - if (!$index_fields) { - return null; - } + $query_builder = function (array $fields) { + $concept_queries = $this->buildConceptQueriesForFields($fields); $query = null; - foreach ($concepts as $concept) { - $concept_query = [ - 'multi_match' => [ - 'fields' => $index_fields, - 'query' => $concept->getPath() - ] - ]; + foreach ($concept_queries as $concept_query) { $query = QueryHelper::applyBooleanClause($query, 'should', $concept_query); } return $query; }; - $query = $query_builder($context->getUnrestrictedFields()); - if ( - isset($query['bool']) && - isset($query['bool']['should']) && - count($query) === 1 /* no options or must(_not) clauses */ - ) { - $queries = $query['bool']['should']; - } else { - $queries = [$query]; - } + $queries = $this->buildConceptQueriesForFields($context->getUnrestrictedFields()); $private_fields = $context->getPrivateFields(); - foreach (QueryHelper::wrapPrivateFieldConceptQueries($private_fields, $query_builder) as $private_field_query) { + foreach (QueryHelper::wrapPrivateFieldQueries($private_fields, $query_builder) as $private_field_query) { $queries[] = $private_field_query; } return $queries; } + protected function buildConceptQueriesForFields(array $fields) + { + $concepts = $this->getPrunedConcepts(); + if (!$concepts) { + return []; + } + + $index_fields = []; + foreach ($fields as $field) { + $index_fields[] = $field->getConceptPathIndexField(); + } + if (!$index_fields) { + return []; + } + + $queries = []; + foreach ($concepts as $concept) { + $queries[] = [ + 'multi_match' => [ + 'fields' => $index_fields, + 'query' => $concept->getPath() + ] + ]; + } + return $queries; + } + public function getValue() { return $this->text; diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/TextNode.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/TextNode.php index 042f313a18..a5b126fb2d 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/TextNode.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/TextNode.php @@ -59,19 +59,31 @@ class TextNode extends AbstractTermNode implements ContextAbleInterface }; $unrestricted_fields = $context->getUnrestrictedFields(); - $unrestricted_fields = Field::filterByValueCompatibility($unrestricted_fields, $this->text); - $query = $query_builder($unrestricted_fields); + $compatible_unrestricted_fields = Field::filterByValueCompatibility($unrestricted_fields, $this->text); + $query = $query_builder($compatible_unrestricted_fields); $private_fields = $context->getPrivateFields(); - $private_fields = Field::filterByValueCompatibility($private_fields, $this->text); - foreach (QueryHelper::wrapPrivateFieldQueries($private_fields, $query_builder) as $private_field_query) { + $compatible_private_fields = Field::filterByValueCompatibility($private_fields, $this->text); + foreach (QueryHelper::wrapPrivateFieldQueries($compatible_private_fields, $query_builder) as $private_field_query) { $query = QueryHelper::applyBooleanClause($query, 'should', $private_field_query); } - $concept_queries = $this->buildConceptQueries($context); + // Concepts handling + $concept_queries = $this->buildConceptQueriesForFields($unrestricted_fields); foreach ($concept_queries as $concept_query) { $query = QueryHelper::applyBooleanClause($query, 'should', $concept_query); } + $query_builder = function (array $fields) { + $concept_queries = $this->buildConceptQueriesForFields($fields); + $query = null; + foreach ($concept_queries as $concept_query) { + $query = QueryHelper::applyBooleanClause($query, 'should', $concept_query); + } + return $query; + }; + foreach (QueryHelper::wrapPrivateFieldQueries($private_fields, $query_builder) as $private_field_query) { + $query = QueryHelper::applyBooleanClause($query, 'should', $private_field_query); + } return $query; } diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryHelper.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryHelper.php index fb2103c3cf..9d652f86c7 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryHelper.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryHelper.php @@ -29,10 +29,10 @@ class QueryHelper 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 - $queries[] = self::restrictQueryToCollections( - $query_builder($fields), - $collections_map[$hash] - ); + $query = $query_builder($fields); + if ($query !== null) { + $queries[] = self::restrictQueryToCollections($query, $collections_map[$hash]); + } } return $queries; @@ -61,38 +61,6 @@ class QueryHelper return implode('|', $collections); } - /** - * @todo Factor with wrapPrivateFieldQueries() - */ - public static function wrapPrivateFieldConceptQueries(array $fields, \Closure $query_builder) - { - // 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 ($fields as $field) { - $collections = $field->getDependantCollections(); - $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_map[$hash][] = $field; - } - - $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 - $query = $query_builder($fields); - $query = self::restrictQueryToCollections($query, $collections_map[$hash]); - $queries[] = $query; - } - - return $queries; - } - /** * Apply conjunction or disjunction between a query and a sub query clause *