mirror of
https://github.com/alchemy-fr/Phraseanet.git
synced 2025-10-24 10:23: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 collection collection
|
||||||
%token type type
|
%token type type
|
||||||
%token id id|recordid
|
%token id id|recordid
|
||||||
|
%token flag_prefix flag.
|
||||||
|
%token true true|1
|
||||||
|
%token false false|0
|
||||||
%token word [^\s()\[\]:<>≤≥=]+
|
%token word [^\s()\[\]:<>≤≥=]+
|
||||||
|
|
||||||
// relative order of precedence is NOT > XOR > AND > OR
|
// relative order of precedence is NOT > XOR > AND > OR
|
||||||
@@ -58,9 +61,17 @@ quaternary:
|
|||||||
| ::collection:: ::colon:: string() #collection
|
| ::collection:: ::colon:: string() #collection
|
||||||
| ::type:: ::colon:: string() #type
|
| ::type:: ::colon:: string() #type
|
||||||
| ::id:: ::colon:: string() #id
|
| ::id:: ::colon:: string() #id
|
||||||
|
| ::flag_prefix:: flag() ::colon:: boolean() #flag_statement
|
||||||
| quinary()
|
| quinary()
|
||||||
|
|
||||||
|
|
||||||
|
#flag:
|
||||||
|
word_or_keyword()+
|
||||||
|
|
||||||
|
boolean:
|
||||||
|
<true>
|
||||||
|
| <false>
|
||||||
|
|
||||||
// Field narrowing
|
// Field narrowing
|
||||||
|
|
||||||
quinary:
|
quinary:
|
||||||
@@ -140,6 +151,9 @@ keyword:
|
|||||||
| <collection>
|
| <collection>
|
||||||
| <type>
|
| <type>
|
||||||
| <id>
|
| <id>
|
||||||
|
| <flag_prefix>
|
||||||
|
| <true>
|
||||||
|
| <false>
|
||||||
|
|
||||||
symbol:
|
symbol:
|
||||||
<parenthese_>
|
<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 TERM = '#thesaurus_term';
|
||||||
const TEXT = '#text';
|
const TEXT = '#text';
|
||||||
const CONTEXT = '#context';
|
const CONTEXT = '#context';
|
||||||
|
const FLAG_STATEMENT = '#flag_statement';
|
||||||
|
const FLAG = '#flag';
|
||||||
const COLLECTION = '#collection';
|
const COLLECTION = '#collection';
|
||||||
const TYPE = '#type';
|
const TYPE = '#type';
|
||||||
const DATABASE = '#database';
|
const DATABASE = '#database';
|
||||||
@@ -29,4 +31,6 @@ class NodeTypes
|
|||||||
const TOKEN_WORD = 'word';
|
const TOKEN_WORD = 'word';
|
||||||
const TOKEN_QUOTED_STRING = 'quoted';
|
const TOKEN_QUOTED_STRING = 'quoted';
|
||||||
const TOKEN_RAW_STRING = 'raw_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:
|
case NodeTypes::FIELD:
|
||||||
return new AST\Field($this->visitString($element));
|
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:
|
case NodeTypes::DATABASE:
|
||||||
return $this->visitDatabaseNode($element);
|
return $this->visitDatabaseNode($element);
|
||||||
|
|
||||||
@@ -242,6 +248,8 @@ class QueryVisitor implements Visit
|
|||||||
}
|
}
|
||||||
} elseif ($node instanceof AST\Node) {
|
} elseif ($node instanceof AST\Node) {
|
||||||
$root = new AST\AndExpression($root, $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);
|
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)
|
private function visitDatabaseNode(Element $element)
|
||||||
{
|
{
|
||||||
if ($element->getChildrenNumber() !== 1) {
|
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">
|
id:90 foo|<text:"id:90 foo">
|
||||||
recordid:90|<record_identifier:90>
|
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
|
# Matcher on unknown name --> fulltext
|
||||||
foo:bar|<text:"foo:bar">
|
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