From 1e7092b86a9797046d1ac65bae9dbdd16c65b70e Mon Sep 17 00:00:00 2001 From: jygaulier Date: Tue, 13 Apr 2021 16:35:15 +0200 Subject: [PATCH] [skip ci] PHRAS-3417_search-empty-fields_MASTER add : special values "_empty_" and "_any_" to search empty field or not-empty field. Works only with = operator. e.g.: Title=_empty_ wip/todo : unit test ; facet --- .../Elastic/AST/KeyValue/ExistsExpression.php | 45 +++++++++++++++++ .../AST/KeyValue/MissingExpression.php | 49 +++++++++++++++++++ .../Elastic/Search/QueryVisitor.php | 15 +++++- 3 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/KeyValue/ExistsExpression.php create mode 100644 lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/KeyValue/MissingExpression.php diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/KeyValue/ExistsExpression.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/KeyValue/ExistsExpression.php new file mode 100644 index 0000000000..c4986f61e1 --- /dev/null +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/KeyValue/ExistsExpression.php @@ -0,0 +1,45 @@ +key = $key; + } + + public function buildQuery(QueryContext $context) + { + $query = [ + 'exists' => [ + 'field' => $this->key->getIndexField($context, true) + ] + ]; + + if ($this->key instanceof QueryPostProcessor) { + return $this->key->postProcessQuery($query, $context); + } + + return $query; + } + + public function getTermNodes() + { + return array(); + } + + public function __toString() + { + return sprintf('(<%s> == <%s>)', $this->key, self::EXISTS_VALUE); + } +} diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/KeyValue/MissingExpression.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/KeyValue/MissingExpression.php new file mode 100644 index 0000000000..fc8d5b197b --- /dev/null +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/KeyValue/MissingExpression.php @@ -0,0 +1,49 @@ +key = $key; + } + + public function buildQuery(QueryContext $context) + { + $query = [ + 'bool' => [ + 'must_not' => [ + 'exists' => [ + 'field' => $this->key->getIndexField($context, true) + ] + ] + ] + ]; + + if ($this->key instanceof QueryPostProcessor) { + return $this->key->postProcessQuery($query, $context); + } + + return $query; + } + + public function getTermNodes() + { + return array(); + } + + public function __toString() + { + return sprintf('(<%s> == <%s>)', $this->key, self::MISSING_VALUE); + } +} diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryVisitor.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryVisitor.php index 52c0fa54a6..c933b3557d 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryVisitor.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryVisitor.php @@ -197,25 +197,36 @@ class QueryVisitor implements Visit private function visitEqualNode(TreeNode $node) { return $this->handleBinaryExpression($node, function($left, $right) { + if($right === AST\KeyValue\MissingExpression::MISSING_VALUE) { + return new AST\KeyValue\MissingExpression($left); + } + + if($right === AST\KeyValue\ExistsExpression::EXISTS_VALUE) { + return new AST\KeyValue\ExistsExpression($left); + } + if ($this->isDateKey($left)) { try { // Try to create a range for incomplete dates $range = QueryHelper::getRangeFromDateString($right); if ($range['from'] === $range['to']) { return new AST\KeyValue\EqualExpression($left, $range['from']); - } else { + } + else { return new AST\KeyValue\RangeExpression( $left, $range['from'], true, $range['to'], false ); } - } catch (\InvalidArgumentException $e) { + } + catch (\InvalidArgumentException $e) { // Fall back to equal expression } } return new AST\KeyValue\EqualExpression($left, $right); + }); }