diff --git a/grammar/query.pp b/grammar/query.pp index 2accc315d4..76c2419ff3 100644 --- a/grammar/query.pp +++ b/grammar/query.pp @@ -55,14 +55,15 @@ ternary: quaternary() ( ::space:: ::and:: ::space:: primary() #and )? -// Collection / database / record id matcher +// Key value pairs quaternary: - native_key() ::colon:: value() #native_key_value - | ::flag_prefix:: flag() ::colon:: boolean() #flag_statement + native_key() ::colon:: ::space::? value() #native_key_value + | ::flag_prefix:: flag() ::colon:: ::space::? boolean() #flag_statement + | ::field_prefix:: field() ::colon:: ::space::? quinary() #field_statement + | field() ::colon:: ::space::? quinary() #field_statement | quinary() - #flag: word_or_keyword()+ @@ -70,16 +71,6 @@ boolean: | -// Field narrowing - -quinary: - senary() ( ::space:: ::in:: ::space:: key() #in )? - -key: - native_key() #native_key - | ::field_prefix:: field() - | field() - #native_key: | @@ -93,7 +84,7 @@ key: // Field level matchers (*may* be restricted to a field subset) -senary: +quinary: group() #group | field() ::space::? ::lt:: ::space::? value() #less_than | field() ::space::? ::gt:: ::space::? value() #greater_than diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/FieldMatchExpression.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/FieldMatchExpression.php index 7d7a26ec4a..e3b3154ae6 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/FieldMatchExpression.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/AST/FieldMatchExpression.php @@ -29,6 +29,6 @@ class FieldMatchExpression extends Node public function __toString() { - return sprintf('(%s IN %s)', $this->expression, $this->field); + return sprintf('(%s MATCHES %s)', $this->field, $this->expression); } } diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/NodeTypes.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/NodeTypes.php index 5136347a85..30a282ae6a 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/NodeTypes.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/NodeTypes.php @@ -16,6 +16,7 @@ class NodeTypes const LTE_EXPR = '#less_than_or_equal_to'; const GTE_EXPR = '#greater_than_or_equal_to'; const EQUAL_EXPR = '#equal_to'; + const FIELD_STATEMENT = '#field_statement'; const FIELD = '#field'; const VALUE = '#value'; const TERM = '#thesaurus_term'; diff --git a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryVisitor.php b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryVisitor.php index 242045bdee..ada7f2baa8 100644 --- a/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryVisitor.php +++ b/lib/Alchemy/Phrasea/SearchEngine/Elastic/Search/QueryVisitor.php @@ -46,9 +46,6 @@ class QueryVisitor implements Visit case NodeTypes::GROUP: return $this->visitNode($element->getChild(0)); - case NodeTypes::IN_EXPR: - return $this->visitInNode($element); - case NodeTypes::AND_EXPR: return $this->visitAndNode($element); @@ -79,6 +76,9 @@ class QueryVisitor implements Visit case NodeTypes::CONTEXT: return new AST\Context($this->visitString($element)); + case NodeTypes::FIELD_STATEMENT: + return $this->visitFieldStatementNode($element); + case NodeTypes::FIELD: return new AST\Field($this->visitString($element)); @@ -108,26 +108,14 @@ class QueryVisitor implements Visit return new Query($root); } - private function visitInNode(Element $element) + private function visitFieldStatementNode(TreeNode $node) { - if ($element->getChildrenNumber() !== 2) { - throw new \Exception('IN expression can only have 2 childs.'); - } - $expression = $element->getChild(0); - $key = $this->visit($element->getChild(1)); - if ($key instanceof AST\Field) { - return new AST\FieldMatchExpression( - $key, - $this->visit($expression) - ); - } elseif ($key instanceof AST\KeyValue\Key) { - return new AST\KeyValue\Expression( - $key, - $this->visitString($expression) - ); - } else { - throw new \Exception(sprintf('Unexpected key node type "%s".', is_object($field) ? get_class($field) : gettype($field))); + if ($node->getChildrenNumber() !== 2) { + throw new \Exception('Field statement must have 2 childs.'); } + $field = $this->visit($node->getChild(0)); + $value = $this->visit($node->getChild(1)); + return new AST\FieldMatchExpression($field, $value); } private function visitAndNode(Element $element) diff --git a/tests/Alchemy/Tests/Phrasea/SearchEngine/resources/queries.csv b/tests/Alchemy/Tests/Phrasea/SearchEngine/resources/queries.csv index a1205ea877..00593e43be 100644 --- a/tests/Alchemy/Tests/Phrasea/SearchEngine/resources/queries.csv +++ b/tests/Alchemy/Tests/Phrasea/SearchEngine/resources/queries.csv @@ -61,45 +61,29 @@ foo > "2015/01/01"| foo ≥ "2015/01/01"| # Field narrowing -foo IN bar|( IN ) -foo bar IN baz|( IN ) -foo IN bar baz| -fooINbar| - -# Native fields with IN syntax (temporary) -foo IN collection| -foo IN collection AND bar|( AND ) -foo IN collection bar| -foo IN database| -foo IN database AND bar|( AND ) -foo IN database bar| -foo IN type| -foo IN type AND bar|( AND ) -foo IN type bar| -90 IN id| -90 IN id AND foo|( AND ) -90 IN id foo| -90 IN recordid| +foo:bar|( MATCHES ) +foo:bar baz|( MATCHES ) +foo bar:baz| # Regular field with name colliding with a native key -foo IN field.collection|( IN ) -foo IN field.database|( IN ) -foo IN field.type|( IN ) -foo IN field.id|( IN ) +field.collection:foo|( MATCHES ) +field.database:foo|( MATCHES ) +field.type:foo|( MATCHES ) +field.id:foo|( MATCHES ) # Matchers collection:foo| collection:foo AND bar|( AND ) -collection:foo bar| +#collection:foo bar| database:foo| database:foo AND bar|( AND ) -database:foo bar| +#database:foo bar| type:foo| type:foo AND bar|( AND ) -type:foo bar| +#type:foo bar| id:90| id:90 AND foo|( AND ) -id:90 foo| +#id:90 foo| recordid:90| # Flag matcher @@ -112,7 +96,9 @@ flag.foo bar:true| true| # Matcher on unknown name --> fulltext -foo:bar| +foo:bar|( MATCHES ) +foo:bar AND baz|(( MATCHES ) AND ) +foo AND bar:baz|( AND ( MATCHES )) # Search terms with embedded keywords INA|