mirror of
https://github.com/alchemy-fr/Phraseanet.git
synced 2025-10-23 18:03:17 +00:00
Merge pull request #1502 from mdarse/ar-731-status-search-dsl
PHRAS-731 Add status match DSL
This commit is contained in:
@@ -31,6 +31,9 @@
|
||||
%token collection collection
|
||||
%token type type
|
||||
%token id id|recordid
|
||||
%token flag_prefix flag.
|
||||
%token true true|1
|
||||
%token false false|0
|
||||
%token word [^\s()\[\]:<>≤≥=]+
|
||||
|
||||
// relative order of precedence is NOT > XOR > AND > OR
|
||||
@@ -58,9 +61,17 @@ quaternary:
|
||||
| ::collection:: ::colon:: string() #collection
|
||||
| ::type:: ::colon:: string() #type
|
||||
| ::id:: ::colon:: string() #id
|
||||
| ::flag_prefix:: flag() ::colon:: boolean() #flag_statement
|
||||
| quinary()
|
||||
|
||||
|
||||
#flag:
|
||||
word_or_keyword()+
|
||||
|
||||
boolean:
|
||||
<true>
|
||||
| <false>
|
||||
|
||||
// Field narrowing
|
||||
|
||||
quinary:
|
||||
@@ -140,6 +151,9 @@ keyword:
|
||||
| <collection>
|
||||
| <type>
|
||||
| <id>
|
||||
| <flag_prefix>
|
||||
| <true>
|
||||
| <false>
|
||||
|
||||
symbol:
|
||||
<parenthese_>
|
||||
|
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Alchemy\Phrasea\SearchEngine\Elastic\AST;
|
||||
|
||||
use Assert\Assertion;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\RecordHelper;
|
||||
|
||||
class FlagStatement extends Node
|
||||
{
|
||||
private $name;
|
||||
private $set;
|
||||
|
||||
public function __construct($name, $set)
|
||||
{
|
||||
Assertion::string($name);
|
||||
Assertion::boolean($set);
|
||||
$this->name = $name;
|
||||
$this->set = $set;
|
||||
}
|
||||
|
||||
public function buildQuery(QueryContext $context)
|
||||
{
|
||||
// TODO Ensure flag exists
|
||||
$key = RecordHelper::normalizeFlagKey($this->name);
|
||||
$field = sprintf('flags.%s', $key);
|
||||
return [
|
||||
'term' => [
|
||||
$field => $this->set
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public function getTermNodes()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return sprintf('<flag:%s %s>', $this->name, $this->set ? 'set' : 'cleared');
|
||||
}
|
||||
}
|
@@ -21,6 +21,8 @@ class NodeTypes
|
||||
const TERM = '#thesaurus_term';
|
||||
const TEXT = '#text';
|
||||
const CONTEXT = '#context';
|
||||
const FLAG_STATEMENT = '#flag_statement';
|
||||
const FLAG = '#flag';
|
||||
const COLLECTION = '#collection';
|
||||
const TYPE = '#type';
|
||||
const DATABASE = '#database';
|
||||
@@ -29,4 +31,6 @@ class NodeTypes
|
||||
const TOKEN_WORD = 'word';
|
||||
const TOKEN_QUOTED_STRING = 'quoted';
|
||||
const TOKEN_RAW_STRING = 'raw_quoted';
|
||||
const TOKEN_TRUE = 'true';
|
||||
const TOKEN_FALSE = 'false';
|
||||
}
|
||||
|
@@ -82,6 +82,12 @@ class QueryVisitor implements Visit
|
||||
case NodeTypes::FIELD:
|
||||
return new AST\Field($this->visitString($element));
|
||||
|
||||
case NodeTypes::FLAG_STATEMENT:
|
||||
return $this->visitFlagStatementNode($element);
|
||||
|
||||
case NodeTypes::FLAG:
|
||||
return $this->visitString($element);
|
||||
|
||||
case NodeTypes::DATABASE:
|
||||
return $this->visitDatabaseNode($element);
|
||||
|
||||
@@ -242,6 +248,8 @@ class QueryVisitor implements Visit
|
||||
}
|
||||
} elseif ($node instanceof AST\Node) {
|
||||
$root = new AST\AndExpression($root, $node);
|
||||
} else {
|
||||
throw new \Exception('Unexpected node type inside text node.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,6 +270,35 @@ class QueryVisitor implements Visit
|
||||
return implode($tokens);
|
||||
}
|
||||
|
||||
private function visitFlagStatementNode(TreeNode $node)
|
||||
{
|
||||
if ($node->getChildrenNumber() !== 2) {
|
||||
throw new \Exception('Flag statement can only have 2 childs.');
|
||||
}
|
||||
|
||||
return new AST\FlagStatement(
|
||||
$node->getChild(0)->accept($this),
|
||||
$this->visitBoolean($node->getChild(1))
|
||||
);
|
||||
}
|
||||
|
||||
private function visitBoolean(TreeNode $node)
|
||||
{
|
||||
if (null === $value = $node->getValue()) {
|
||||
throw new \Exception('Boolean node must be a token');
|
||||
}
|
||||
switch ($value['token']) {
|
||||
case NodeTypes::TOKEN_TRUE:
|
||||
return true;
|
||||
|
||||
case NodeTypes::TOKEN_FALSE:
|
||||
return false;
|
||||
|
||||
default:
|
||||
throw new \Exception('Unexpected token for a boolean.');
|
||||
}
|
||||
}
|
||||
|
||||
private function visitDatabaseNode(Element $element)
|
||||
{
|
||||
if ($element->getChildrenNumber() !== 1) {
|
||||
|
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Alchemy\Tests\Phrasea\SearchEngine\AST;
|
||||
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\AST\FlagStatement;
|
||||
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext;
|
||||
|
||||
/**
|
||||
* @group unit
|
||||
* @group searchengine
|
||||
* @group ast
|
||||
*/
|
||||
class FlagStatementTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testSerialization()
|
||||
{
|
||||
$this->assertTrue(method_exists(FlagStatement::class, '__toString'), 'Class does not have method __toString');
|
||||
$node = new FlagStatement('foo', true);
|
||||
$this->assertEquals('<flag:foo set>', (string) $node);
|
||||
$node = new FlagStatement('foo', false);
|
||||
$this->assertEquals('<flag:foo cleared>', (string) $node);
|
||||
}
|
||||
|
||||
public function testQueryBuild()
|
||||
{
|
||||
$query_context = $this->prophesize(QueryContext::class);
|
||||
|
||||
$node = new FlagStatement('foo', true);
|
||||
$query = $node->buildQuery($query_context->reveal());
|
||||
|
||||
$expected = '{
|
||||
"term": {
|
||||
"flags.foo": true
|
||||
}
|
||||
}';
|
||||
|
||||
$this->assertEquals(json_decode($expected, true), $query);
|
||||
}
|
||||
}
|
@@ -78,6 +78,15 @@ id:90 AND foo|(<record_identifier:90> AND <text:"foo">)
|
||||
id:90 foo|<text:"id:90 foo">
|
||||
recordid:90|<record_identifier:90>
|
||||
|
||||
# Flag matcher
|
||||
flag.foo:true|<flag:foo set>
|
||||
flag.foo:1|<flag:foo set>
|
||||
flag.foo:false|<flag:foo cleared>
|
||||
flag.foo:0|<flag:foo cleared>
|
||||
flag.true:true|<flag:true set>
|
||||
flag.foo bar:true|<text:"flag.foo bar:true">
|
||||
true|<text:"true">
|
||||
|
||||
# Matcher on unknown name --> fulltext
|
||||
foo:bar|<text:"foo:bar">
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 1 and column 11.
|
Reference in New Issue
Block a user