mirror of
https://github.com/alchemy-fr/Phraseanet.git
synced 2025-10-23 18:03:17 +00:00
@@ -1,19 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
jison_version="master";
|
|
||||||
jison_php="jison-$jison_version/ports/php/php.js"
|
|
||||||
|
|
||||||
cd grammar
|
|
||||||
|
|
||||||
if [ -f $jison_php ];
|
|
||||||
then
|
|
||||||
echo "Skip jison download"
|
|
||||||
else
|
|
||||||
echo "Download jison lib"
|
|
||||||
wget https://github.com/zaach/jison/archive/$jison_version.zip
|
|
||||||
unzip $jison_version.zip
|
|
||||||
rm $jison_version.zip
|
|
||||||
fi
|
|
||||||
|
|
||||||
node jison-$jison_version/ports/php/php.js query.jison
|
|
||||||
mv QueryParser.php ../lib/Alchemy/Phrasea/SearchEngine/Elastic/QueryParser.php
|
|
@@ -1,149 +0,0 @@
|
|||||||
/* description: Parses Phraseanet search queries. */
|
|
||||||
|
|
||||||
/* lexical grammar */
|
|
||||||
%lex
|
|
||||||
|
|
||||||
/* lexical states */
|
|
||||||
%x literal
|
|
||||||
|
|
||||||
/* begin lexing */
|
|
||||||
%%
|
|
||||||
|
|
||||||
\s+ /* skip whitespace */
|
|
||||||
"AND" return 'AND'
|
|
||||||
"and" return 'AND'
|
|
||||||
"et" return 'AND'
|
|
||||||
"OR" return 'OR'
|
|
||||||
"or" return 'OR'
|
|
||||||
"ou" return 'OR'
|
|
||||||
"IN" return 'IN'
|
|
||||||
"in" return 'IN'
|
|
||||||
"dans" return 'IN'
|
|
||||||
"(" return '('
|
|
||||||
")" return ')'
|
|
||||||
"*" return '*'
|
|
||||||
'"' {
|
|
||||||
//js
|
|
||||||
this.begin('literal');
|
|
||||||
//php $this->begin('literal');
|
|
||||||
}
|
|
||||||
<literal>'"' {
|
|
||||||
//js
|
|
||||||
this.popState();
|
|
||||||
//php $this->popState();
|
|
||||||
}
|
|
||||||
<literal>([^"])* return 'LITERAL'
|
|
||||||
\S+ return 'WORD'
|
|
||||||
<<EOF>> return 'EOF'
|
|
||||||
|
|
||||||
/lex
|
|
||||||
|
|
||||||
|
|
||||||
/* operator associations and precedence */
|
|
||||||
|
|
||||||
%left 'WORD'
|
|
||||||
%left 'AND' 'OR'
|
|
||||||
%left 'IN'
|
|
||||||
|
|
||||||
%start query
|
|
||||||
|
|
||||||
|
|
||||||
%% /* language grammar */
|
|
||||||
|
|
||||||
|
|
||||||
query
|
|
||||||
: expressions EOF {
|
|
||||||
//js
|
|
||||||
console.log('[QUERY]', $$);
|
|
||||||
return $$;
|
|
||||||
/*php
|
|
||||||
return $$;
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
;
|
|
||||||
|
|
||||||
expressions
|
|
||||||
: expression expressions {
|
|
||||||
//js
|
|
||||||
$$ = '('+$1+' DEF_OP '+$2+')';
|
|
||||||
console.log('[DEF_OP]', $$);
|
|
||||||
// $$ = sprintf('(%s DEF_OP %s)', $1->text, $2->text);
|
|
||||||
/*php
|
|
||||||
$$ = new AST\AndExpression($1->text, $2->text);
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
| expression
|
|
||||||
;
|
|
||||||
|
|
||||||
expression
|
|
||||||
: expression AND expression {
|
|
||||||
//js
|
|
||||||
$$ = '('+$1+' AND '+$3+')';
|
|
||||||
console.log('[AND]', $$);
|
|
||||||
/*php
|
|
||||||
$$ = new AST\AndExpression($1->text, $3->text);
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
| expression OR expression {
|
|
||||||
//js
|
|
||||||
$$ = '('+$1+' OR '+$3+')';
|
|
||||||
console.log('[OR]', $$);
|
|
||||||
/*php
|
|
||||||
$$ = new AST\OrExpression($1->text, $3->text);
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
| expression IN keyword {
|
|
||||||
//js
|
|
||||||
$$ = '('+$1+' IN '+$3+')';
|
|
||||||
console.log('[IN]', $$);
|
|
||||||
/*php
|
|
||||||
$$ = new AST\InExpression($3->text, $1->text);
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
| '(' expression ')' {
|
|
||||||
//js
|
|
||||||
$$ = $2;
|
|
||||||
//php $$ = $2;
|
|
||||||
}
|
|
||||||
| prefix
|
|
||||||
| text
|
|
||||||
;
|
|
||||||
|
|
||||||
keyword
|
|
||||||
: WORD {
|
|
||||||
//js
|
|
||||||
$$ = '<'+$1+'>';
|
|
||||||
console.log('[FIELD]', $$);
|
|
||||||
//php $$ = new AST\KeywordNode($1->text);
|
|
||||||
}
|
|
||||||
;
|
|
||||||
|
|
||||||
prefix
|
|
||||||
: WORD '*' {
|
|
||||||
//js
|
|
||||||
$$ = $1+'*';
|
|
||||||
console.log('[PREFIX]', $$);
|
|
||||||
//php $$ = new AST\PrefixNode($1->text);
|
|
||||||
}
|
|
||||||
;
|
|
||||||
|
|
||||||
text
|
|
||||||
: WORD {
|
|
||||||
//js
|
|
||||||
$$ = '"'+$1+'"';
|
|
||||||
console.log('[WORD]', $$);
|
|
||||||
//php $$ = new AST\TextNode($1->text);
|
|
||||||
}
|
|
||||||
| LITERAL {
|
|
||||||
//js
|
|
||||||
$$ = '"'+$1+'"';
|
|
||||||
console.log('[LITERAL]', $$);
|
|
||||||
//php $$ = new AST\QuotedTextNode($1->text);
|
|
||||||
}
|
|
||||||
;
|
|
||||||
|
|
||||||
|
|
||||||
//option namespace:Alchemy\Phrasea\SearchEngine\Elastic
|
|
||||||
//option class:QueryParser
|
|
||||||
//option use:Alchemy\Phrasea\SearchEngine\Elastic\AST as AST;
|
|
||||||
//option fileName:QueryParser.php
|
|
@@ -21,7 +21,6 @@
|
|||||||
%token raw:_raw_quote " -> default
|
%token raw:_raw_quote " -> default
|
||||||
|
|
||||||
// Operators (too bad we can't use preg "i" flag)
|
// Operators (too bad we can't use preg "i" flag)
|
||||||
%token in [Ii][Nn]|[Dd][Aa][Nn][Ss]
|
|
||||||
%token and [Aa][Nn][Dd]|[Ee][Tt]
|
%token and [Aa][Nn][Dd]|[Ee][Tt]
|
||||||
%token or [Oo][Rr]|[Oo][Uu]
|
%token or [Oo][Rr]|[Oo][Uu]
|
||||||
%token except [Ee][Xx][Cc][Ee][Pp][Tt]|[Ss][Aa][Uu][Ff]
|
%token except [Ee][Xx][Cc][Ee][Pp][Tt]|[Ss][Aa][Uu][Ff]
|
||||||
@@ -31,16 +30,23 @@
|
|||||||
%token collection collection
|
%token collection collection
|
||||||
%token type type
|
%token type type
|
||||||
%token id id|recordid
|
%token id id|recordid
|
||||||
|
%token field_prefix field.
|
||||||
%token flag_prefix flag.
|
%token flag_prefix flag.
|
||||||
%token true true|1
|
%token true true|1
|
||||||
%token false false|0
|
%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
|
||||||
|
|
||||||
#query:
|
#query:
|
||||||
::space::? primary()? ::space::?
|
::space::? primary()? ::space::?
|
||||||
|
| catch_all()
|
||||||
|
|
||||||
|
catch_all:
|
||||||
|
( <space>
|
||||||
|
| <word>
|
||||||
|
| keyword()
|
||||||
|
| symbol() #text )*
|
||||||
|
|
||||||
// Boolean operators
|
// Boolean operators
|
||||||
|
|
||||||
@@ -53,46 +59,37 @@ secondary:
|
|||||||
ternary:
|
ternary:
|
||||||
quaternary() ( ::space:: ::and:: ::space:: primary() #and )?
|
quaternary() ( ::space:: ::and:: ::space:: primary() #and )?
|
||||||
|
|
||||||
|
|
||||||
// Collection / database / record id matcher
|
|
||||||
|
|
||||||
quaternary:
|
quaternary:
|
||||||
::database:: ::colon:: string() #database
|
group() #group
|
||||||
| ::collection:: ::colon:: string() #collection
|
| key_value_pair() ( ::space:: primary() #and )?
|
||||||
| ::type:: ::colon:: string() #type
|
| term() ( ::space:: key_value_pair() #and )?
|
||||||
| ::id:: ::colon:: string() #id
|
|
||||||
| ::flag_prefix:: flag() ::colon:: boolean() #flag_statement
|
|
||||||
| quinary()
|
|
||||||
|
|
||||||
|
// Key value pairs & field level matchers (restricted to a single field)
|
||||||
|
|
||||||
|
key_value_pair:
|
||||||
|
native_key() ::colon:: ::space::? value() #native_key_value
|
||||||
|
| ::flag_prefix:: flag() ::colon:: ::space::? boolean() #flag_statement
|
||||||
|
| ::field_prefix:: field() ::colon:: ::space::? term() #field_statement
|
||||||
|
| field() ::colon:: ::space::? term() #field_statement
|
||||||
|
| field() ::space::? ::lt:: ::space::? value() #less_than
|
||||||
|
| field() ::space::? ::gt:: ::space::? value() #greater_than
|
||||||
|
| field() ::space::? ::lte:: ::space::? value() #less_than_or_equal_to
|
||||||
|
| field() ::space::? ::gte:: ::space::? value() #greater_than_or_equal_to
|
||||||
|
| field() ::space::? ::equal:: ::space::? value() #equal_to
|
||||||
|
|
||||||
#flag:
|
#flag:
|
||||||
word_or_keyword()+
|
word_or_keyword()+
|
||||||
|
|
||||||
boolean:
|
#native_key:
|
||||||
<true>
|
<database>
|
||||||
| <false>
|
| <collection>
|
||||||
|
| <type>
|
||||||
// Field narrowing
|
| <id>
|
||||||
|
|
||||||
quinary:
|
|
||||||
senary() ( ::space:: ::in:: ::space:: field() #in )?
|
|
||||||
|
|
||||||
#field:
|
#field:
|
||||||
word_or_keyword()+
|
word_or_keyword()+
|
||||||
| quoted_string()
|
| quoted_string()
|
||||||
|
|
||||||
|
|
||||||
// Field level matchers (*may* be restricted to a field subset)
|
|
||||||
|
|
||||||
senary:
|
|
||||||
group() #group
|
|
||||||
| field() ::space::? ::lt:: ::space::? value() #less_than
|
|
||||||
| field() ::space::? ::gt:: ::space::? value() #greater_than
|
|
||||||
| field() ::space::? ::lte:: ::space::? value() #less_than_or_equal_to
|
|
||||||
| field() ::space::? ::gte:: ::space::? value() #greater_than_or_equal_to
|
|
||||||
| field() ::space::? ::equal:: ::space::? value() #equal_to
|
|
||||||
| term()
|
|
||||||
|
|
||||||
#value:
|
#value:
|
||||||
word_or_keyword()+
|
word_or_keyword()+
|
||||||
| quoted_string()
|
| quoted_string()
|
||||||
@@ -111,13 +108,9 @@ term:
|
|||||||
// Free text handling
|
// Free text handling
|
||||||
|
|
||||||
text:
|
text:
|
||||||
string_keyword_symbol()
|
|
||||||
( <space>? string_keyword_symbol() )*
|
|
||||||
( ::space::? context_block() )?
|
|
||||||
|
|
||||||
string_keyword_symbol:
|
|
||||||
string()
|
string()
|
||||||
| symbol()
|
( <space>? string() )*
|
||||||
|
( ::space::? context_block() )?
|
||||||
|
|
||||||
context_block:
|
context_block:
|
||||||
::parenthese_:: ::space::? context() ::space::? ::_parenthese:: #context
|
::parenthese_:: ::space::? context() ::space::? ::_parenthese:: #context
|
||||||
@@ -128,6 +121,10 @@ context:
|
|||||||
|
|
||||||
// Generic helpers
|
// Generic helpers
|
||||||
|
|
||||||
|
boolean:
|
||||||
|
<true>
|
||||||
|
| <false>
|
||||||
|
|
||||||
string:
|
string:
|
||||||
word_or_keyword()+
|
word_or_keyword()+
|
||||||
| quoted_string()
|
| quoted_string()
|
||||||
@@ -143,14 +140,14 @@ raw_quoted_string:
|
|||||||
::raw_quote_:: <raw_quoted> ::_raw_quote::
|
::raw_quote_:: <raw_quoted> ::_raw_quote::
|
||||||
|
|
||||||
keyword:
|
keyword:
|
||||||
<in>
|
<except>
|
||||||
| <except>
|
|
||||||
| <and>
|
| <and>
|
||||||
| <or>
|
| <or>
|
||||||
| <database>
|
| <database>
|
||||||
| <collection>
|
| <collection>
|
||||||
| <type>
|
| <type>
|
||||||
| <id>
|
| <id>
|
||||||
|
| <field_prefix>
|
||||||
| <flag_prefix>
|
| <flag_prefix>
|
||||||
| <true>
|
| <true>
|
||||||
| <false>
|
| <false>
|
||||||
|
@@ -4,7 +4,6 @@ namespace Alchemy\Phrasea\SearchEngine\Elastic\AST;
|
|||||||
|
|
||||||
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext;
|
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext;
|
||||||
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryHelper;
|
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryHelper;
|
||||||
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Field;
|
|
||||||
use Alchemy\Phrasea\SearchEngine\Elastic\Thesaurus\Concept;
|
use Alchemy\Phrasea\SearchEngine\Elastic\Thesaurus\Concept;
|
||||||
use Alchemy\Phrasea\SearchEngine\Elastic\Thesaurus\TermInterface;
|
use Alchemy\Phrasea\SearchEngine\Elastic\Thesaurus\TermInterface;
|
||||||
|
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Alchemy\Phrasea\SearchEngine\Elastic\AST;
|
namespace Alchemy\Phrasea\SearchEngine\Elastic\AST\Boolean;
|
||||||
|
|
||||||
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext;
|
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext;
|
||||||
|
|
||||||
class AndExpression extends BinaryOperator
|
class AndOperator extends BinaryOperator
|
||||||
{
|
{
|
||||||
protected $operator = 'AND';
|
protected $operator = 'AND';
|
||||||
|
|
@@ -1,6 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Alchemy\Phrasea\SearchEngine\Elastic\AST;
|
namespace Alchemy\Phrasea\SearchEngine\Elastic\AST\Boolean;
|
||||||
|
|
||||||
|
use Alchemy\Phrasea\SearchEngine\Elastic\AST\Node;
|
||||||
|
|
||||||
abstract class BinaryOperator extends Node
|
abstract class BinaryOperator extends Node
|
||||||
{
|
{
|
@@ -1,10 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Alchemy\Phrasea\SearchEngine\Elastic\AST;
|
namespace Alchemy\Phrasea\SearchEngine\Elastic\AST\Boolean;
|
||||||
|
|
||||||
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext;
|
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext;
|
||||||
|
|
||||||
class ExceptExpression extends BinaryOperator
|
class ExceptOperator extends BinaryOperator
|
||||||
{
|
{
|
||||||
protected $operator = 'EXCEPT';
|
protected $operator = 'EXCEPT';
|
||||||
|
|
@@ -1,10 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Alchemy\Phrasea\SearchEngine\Elastic\AST;
|
namespace Alchemy\Phrasea\SearchEngine\Elastic\AST\Boolean;
|
||||||
|
|
||||||
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext;
|
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext;
|
||||||
|
|
||||||
class OrExpression extends BinaryOperator
|
class OrOperator extends BinaryOperator
|
||||||
{
|
{
|
||||||
protected $operator = 'OR';
|
protected $operator = 'OR';
|
||||||
|
|
@@ -1,34 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Alchemy\Phrasea\SearchEngine\Elastic\AST;
|
|
||||||
|
|
||||||
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext;
|
|
||||||
|
|
||||||
class CollectionExpression extends Node
|
|
||||||
{
|
|
||||||
private $collectionName;
|
|
||||||
|
|
||||||
public function __construct($collectionName)
|
|
||||||
{
|
|
||||||
$this->collectionName = $collectionName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function buildQuery(QueryContext $context)
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'term' => [
|
|
||||||
'collection_name' => $this->collectionName
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTermNodes()
|
|
||||||
{
|
|
||||||
return array();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __toString()
|
|
||||||
{
|
|
||||||
return sprintf('<collection:%s>', $this->collectionName);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,34 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Alchemy\Phrasea\SearchEngine\Elastic\AST;
|
|
||||||
|
|
||||||
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext;
|
|
||||||
|
|
||||||
class DatabaseExpression extends Node
|
|
||||||
{
|
|
||||||
private $database;
|
|
||||||
|
|
||||||
public function __construct($database)
|
|
||||||
{
|
|
||||||
$this->database = $database;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function buildQuery(QueryContext $context)
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'term' => [
|
|
||||||
'databox_name' => $this->database
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTermNodes()
|
|
||||||
{
|
|
||||||
return array();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __toString()
|
|
||||||
{
|
|
||||||
return sprintf('<database:%s>', $this->database);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -29,7 +29,7 @@ class FieldEqualsExpression extends Node
|
|||||||
|
|
||||||
$query = [
|
$query = [
|
||||||
'term' => [
|
'term' => [
|
||||||
$structure_field->getIndexField() => $this->value
|
$structure_field->getIndexField(true) => $this->value
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@@ -4,7 +4,7 @@ namespace Alchemy\Phrasea\SearchEngine\Elastic\AST;
|
|||||||
|
|
||||||
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext;
|
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext;
|
||||||
|
|
||||||
class InExpression extends Node
|
class FieldMatchExpression extends Node
|
||||||
{
|
{
|
||||||
protected $field;
|
protected $field;
|
||||||
protected $expression;
|
protected $expression;
|
||||||
@@ -29,6 +29,6 @@ class InExpression extends Node
|
|||||||
|
|
||||||
public function __toString()
|
public function __toString()
|
||||||
{
|
{
|
||||||
return sprintf('(%s IN %s)', $this->expression, $this->field);
|
return sprintf('(%s MATCHES %s)', $this->field, $this->expression);
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Alchemy\Phrasea\SearchEngine\Elastic\AST\KeyValue;
|
||||||
|
|
||||||
|
use Alchemy\Phrasea\SearchEngine\Elastic\AST\Node;
|
||||||
|
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext;
|
||||||
|
use Assert\Assertion;
|
||||||
|
|
||||||
|
class Expression extends Node
|
||||||
|
{
|
||||||
|
protected $key;
|
||||||
|
protected $value;
|
||||||
|
|
||||||
|
public function __construct(Key $key, $value)
|
||||||
|
{
|
||||||
|
Assertion::string($value);
|
||||||
|
$this->key = $key;
|
||||||
|
$this->value = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildQuery(QueryContext $context)
|
||||||
|
{
|
||||||
|
return $this->key->buildQueryForValue($this->value, $context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTermNodes()
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
return sprintf('<%s:%s>', $this->key, $this->value);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Alchemy\Phrasea\SearchEngine\Elastic\AST\KeyValue;
|
||||||
|
|
||||||
|
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext;
|
||||||
|
|
||||||
|
interface Key
|
||||||
|
{
|
||||||
|
public function buildQueryForValue($value, QueryContext $context);
|
||||||
|
public function __toString();
|
||||||
|
}
|
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Alchemy\Phrasea\SearchEngine\Elastic\AST\KeyValue;
|
||||||
|
|
||||||
|
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext;
|
||||||
|
use Assert\Assertion;
|
||||||
|
|
||||||
|
class NativeKey implements Key
|
||||||
|
{
|
||||||
|
const TYPE_DATABASE = 'database';
|
||||||
|
const TYPE_COLLECTION = 'collection';
|
||||||
|
const TYPE_MEDIA_TYPE = 'media_type';
|
||||||
|
const TYPE_RECORD_IDENTIFIER = 'record_identifier';
|
||||||
|
|
||||||
|
private $type;
|
||||||
|
private $key;
|
||||||
|
|
||||||
|
public static function database()
|
||||||
|
{
|
||||||
|
return new self(self::TYPE_DATABASE, 'databox_name');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function collection()
|
||||||
|
{
|
||||||
|
return new self(self::TYPE_COLLECTION, 'collection_name');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function mediaType()
|
||||||
|
{
|
||||||
|
return new self(self::TYPE_MEDIA_TYPE, 'type');
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function recordIdentifier()
|
||||||
|
{
|
||||||
|
return new self(self::TYPE_RECORD_IDENTIFIER, 'record_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function __construct($type, $key)
|
||||||
|
{
|
||||||
|
$this->type = $type;
|
||||||
|
$this->key = $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildQueryForValue($value, QueryContext $context)
|
||||||
|
{
|
||||||
|
Assertion::string($value);
|
||||||
|
return [
|
||||||
|
'term' => [
|
||||||
|
$this->key => $value
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,34 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Alchemy\Phrasea\SearchEngine\Elastic\AST;
|
|
||||||
|
|
||||||
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext;
|
|
||||||
|
|
||||||
class RecordIdentifierExpression extends Node
|
|
||||||
{
|
|
||||||
private $record_id;
|
|
||||||
|
|
||||||
public function __construct($record_id)
|
|
||||||
{
|
|
||||||
$this->record_id = $record_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function buildQuery(QueryContext $context)
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'term' => [
|
|
||||||
'record_id' => $this->record_id
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTermNodes()
|
|
||||||
{
|
|
||||||
return array();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __toString()
|
|
||||||
{
|
|
||||||
return sprintf('<record_identifier:%s>', $this->record_id);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,34 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Alchemy\Phrasea\SearchEngine\Elastic\AST;
|
|
||||||
|
|
||||||
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext;
|
|
||||||
|
|
||||||
class TypeExpression extends Node
|
|
||||||
{
|
|
||||||
private $typeName;
|
|
||||||
|
|
||||||
public function __construct($typeName)
|
|
||||||
{
|
|
||||||
$this->typeName = $typeName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function buildQuery(QueryContext $context)
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'term' => [
|
|
||||||
'type' => $this->typeName
|
|
||||||
]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTermNodes()
|
|
||||||
{
|
|
||||||
return array();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __toString()
|
|
||||||
{
|
|
||||||
return sprintf('<type:%s>', $this->typeName);
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@@ -78,7 +78,9 @@ class FacetsResponse implements JsonSerializable
|
|||||||
case 'Type':
|
case 'Type':
|
||||||
return sprintf('type:%s', $this->escaper->escapeWord($value));
|
return sprintf('type:%s', $this->escaper->escapeWord($value));
|
||||||
default:
|
default:
|
||||||
return sprintf('r"%s" IN %s', $this->escaper->escapeRaw($value), $name);
|
return sprintf('%s = %s',
|
||||||
|
$this->escaper->escapeWord($name),
|
||||||
|
$this->escaper->escapeWord($value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,7 +7,6 @@ class NodeTypes
|
|||||||
// Tree node types
|
// Tree node types
|
||||||
const QUERY = '#query';
|
const QUERY = '#query';
|
||||||
const GROUP = '#group';
|
const GROUP = '#group';
|
||||||
const IN_EXPR = '#in';
|
|
||||||
const AND_EXPR = '#and';
|
const AND_EXPR = '#and';
|
||||||
const OR_EXPR = '#or';
|
const OR_EXPR = '#or';
|
||||||
const EXCEPT_EXPR = '#except';
|
const EXCEPT_EXPR = '#except';
|
||||||
@@ -16,6 +15,7 @@ class NodeTypes
|
|||||||
const LTE_EXPR = '#less_than_or_equal_to';
|
const LTE_EXPR = '#less_than_or_equal_to';
|
||||||
const GTE_EXPR = '#greater_than_or_equal_to';
|
const GTE_EXPR = '#greater_than_or_equal_to';
|
||||||
const EQUAL_EXPR = '#equal_to';
|
const EQUAL_EXPR = '#equal_to';
|
||||||
|
const FIELD_STATEMENT = '#field_statement';
|
||||||
const FIELD = '#field';
|
const FIELD = '#field';
|
||||||
const VALUE = '#value';
|
const VALUE = '#value';
|
||||||
const TERM = '#thesaurus_term';
|
const TERM = '#thesaurus_term';
|
||||||
@@ -23,14 +23,16 @@ class NodeTypes
|
|||||||
const CONTEXT = '#context';
|
const CONTEXT = '#context';
|
||||||
const FLAG_STATEMENT = '#flag_statement';
|
const FLAG_STATEMENT = '#flag_statement';
|
||||||
const FLAG = '#flag';
|
const FLAG = '#flag';
|
||||||
const COLLECTION = '#collection';
|
const NATIVE_KEY_VALUE = '#native_key_value';
|
||||||
const TYPE = '#type';
|
const NATIVE_KEY = '#native_key';
|
||||||
const DATABASE = '#database';
|
|
||||||
const IDENTIFIER = '#id';
|
|
||||||
// Token types for leaf nodes
|
// Token types for leaf nodes
|
||||||
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_DATABASE = 'database';
|
||||||
|
const TOKEN_COLLECTION = 'collection';
|
||||||
|
const TOKEN_MEDIA_TYPE = 'type';
|
||||||
|
const TOKEN_RECORD_ID = 'id';
|
||||||
const TOKEN_TRUE = 'true';
|
const TOKEN_TRUE = 'true';
|
||||||
const TOKEN_FALSE = 'false';
|
const TOKEN_FALSE = 'false';
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
namespace Alchemy\Phrasea\SearchEngine\Elastic\Search;
|
namespace Alchemy\Phrasea\SearchEngine\Elastic\Search;
|
||||||
|
|
||||||
use Alchemy\Phrasea\SearchEngine\Elastic\AST;
|
use Alchemy\Phrasea\SearchEngine\Elastic\AST;
|
||||||
|
use Alchemy\Phrasea\SearchEngine\Elastic\Exception\Exception;
|
||||||
use Hoa\Compiler\Llk\TreeNode;
|
use Hoa\Compiler\Llk\TreeNode;
|
||||||
use Hoa\Visitor\Element;
|
use Hoa\Visitor\Element;
|
||||||
use Hoa\Visitor\Visit;
|
use Hoa\Visitor\Visit;
|
||||||
@@ -46,9 +47,6 @@ class QueryVisitor implements Visit
|
|||||||
case NodeTypes::GROUP:
|
case NodeTypes::GROUP:
|
||||||
return $this->visitNode($element->getChild(0));
|
return $this->visitNode($element->getChild(0));
|
||||||
|
|
||||||
case NodeTypes::IN_EXPR:
|
|
||||||
return $this->visitInNode($element);
|
|
||||||
|
|
||||||
case NodeTypes::AND_EXPR:
|
case NodeTypes::AND_EXPR:
|
||||||
return $this->visitAndNode($element);
|
return $this->visitAndNode($element);
|
||||||
|
|
||||||
@@ -79,6 +77,9 @@ class QueryVisitor implements Visit
|
|||||||
case NodeTypes::CONTEXT:
|
case NodeTypes::CONTEXT:
|
||||||
return new AST\Context($this->visitString($element));
|
return new AST\Context($this->visitString($element));
|
||||||
|
|
||||||
|
case NodeTypes::FIELD_STATEMENT:
|
||||||
|
return $this->visitFieldStatementNode($element);
|
||||||
|
|
||||||
case NodeTypes::FIELD:
|
case NodeTypes::FIELD:
|
||||||
return new AST\Field($this->visitString($element));
|
return new AST\Field($this->visitString($element));
|
||||||
|
|
||||||
@@ -88,20 +89,14 @@ class QueryVisitor implements Visit
|
|||||||
case NodeTypes::FLAG:
|
case NodeTypes::FLAG:
|
||||||
return new AST\Flag($this->visitString($element));
|
return new AST\Flag($this->visitString($element));
|
||||||
|
|
||||||
case NodeTypes::DATABASE:
|
case NodeTypes::NATIVE_KEY_VALUE:
|
||||||
return $this->visitDatabaseNode($element);
|
return $this->visitNativeKeyValueNode($element);
|
||||||
|
|
||||||
case NodeTypes::COLLECTION:
|
case NodeTypes::NATIVE_KEY:
|
||||||
return $this->visitCollectionNode($element);
|
return $this->visitNativeKeyNode($element);
|
||||||
|
|
||||||
case NodeTypes::TYPE:
|
|
||||||
return $this->visitTypeNode($element);
|
|
||||||
|
|
||||||
case NodeTypes::IDENTIFIER:
|
|
||||||
return $this->visitIdentifierNode($element);
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new \Exception(sprintf('Unknown node type "%s".', $element->getId()));
|
throw new Exception(sprintf('Unknown node type "%s".', $element->getId()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,41 +109,41 @@ class QueryVisitor implements Visit
|
|||||||
return new Query($root);
|
return new Query($root);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function visitInNode(Element $element)
|
private function visitFieldStatementNode(TreeNode $node)
|
||||||
{
|
{
|
||||||
if ($element->getChildrenNumber() !== 2) {
|
if ($node->getChildrenNumber() !== 2) {
|
||||||
throw new \Exception('IN expression can only have 2 childs.');
|
throw new Exception('Field statement must have 2 childs.');
|
||||||
}
|
}
|
||||||
$expression = $element->getChild(0)->accept($this);
|
$field = $this->visit($node->getChild(0));
|
||||||
$field = $this->visit($element->getChild(1));
|
$value = $this->visit($node->getChild(1));
|
||||||
return new AST\InExpression($field, $expression);
|
return new AST\FieldMatchExpression($field, $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function visitAndNode(Element $element)
|
private function visitAndNode(Element $element)
|
||||||
{
|
{
|
||||||
return $this->handleBinaryOperator($element, function($left, $right) {
|
return $this->handleBinaryOperator($element, function($left, $right) {
|
||||||
return new AST\AndExpression($left, $right);
|
return new AST\Boolean\AndOperator($left, $right);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private function visitOrNode(Element $element)
|
private function visitOrNode(Element $element)
|
||||||
{
|
{
|
||||||
return $this->handleBinaryOperator($element, function($left, $right) {
|
return $this->handleBinaryOperator($element, function($left, $right) {
|
||||||
return new AST\OrExpression($left, $right);
|
return new AST\Boolean\OrOperator($left, $right);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private function visitExceptNode(Element $element)
|
private function visitExceptNode(Element $element)
|
||||||
{
|
{
|
||||||
return $this->handleBinaryOperator($element, function($left, $right) {
|
return $this->handleBinaryOperator($element, function($left, $right) {
|
||||||
return new AST\ExceptExpression($left, $right);
|
return new AST\Boolean\ExceptOperator($left, $right);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private function visitRangeNode(TreeNode $node)
|
private function visitRangeNode(TreeNode $node)
|
||||||
{
|
{
|
||||||
if ($node->getChildrenNumber() !== 2) {
|
if ($node->getChildrenNumber() !== 2) {
|
||||||
throw new \Exception('Comparison operator can only have 2 childs.');
|
throw new Exception('Comparison operator can only have 2 childs.');
|
||||||
}
|
}
|
||||||
$field = $node->getChild(0)->accept($this);
|
$field = $node->getChild(0)->accept($this);
|
||||||
$expression = $node->getChild(1)->accept($this);
|
$expression = $node->getChild(1)->accept($this);
|
||||||
@@ -168,7 +163,7 @@ class QueryVisitor implements Visit
|
|||||||
private function handleBinaryOperator(Element $element, \Closure $factory)
|
private function handleBinaryOperator(Element $element, \Closure $factory)
|
||||||
{
|
{
|
||||||
if ($element->getChildrenNumber() !== 2) {
|
if ($element->getChildrenNumber() !== 2) {
|
||||||
throw new \Exception('Binary expression can only have 2 childs.');
|
throw new Exception('Binary expression can only have 2 childs.');
|
||||||
}
|
}
|
||||||
$left = $element->getChild(0)->accept($this);
|
$left = $element->getChild(0)->accept($this);
|
||||||
$right = $element->getChild(1)->accept($this);
|
$right = $element->getChild(1)->accept($this);
|
||||||
@@ -179,7 +174,7 @@ class QueryVisitor implements Visit
|
|||||||
private function visitEqualNode(TreeNode $node)
|
private function visitEqualNode(TreeNode $node)
|
||||||
{
|
{
|
||||||
if ($node->getChildrenNumber() !== 2) {
|
if ($node->getChildrenNumber() !== 2) {
|
||||||
throw new \Exception('Equality operator can only have 2 childs.');
|
throw new Exception('Equality operator can only have 2 childs.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return new AST\FieldEqualsExpression(
|
return new AST\FieldEqualsExpression(
|
||||||
@@ -196,13 +191,13 @@ class QueryVisitor implements Visit
|
|||||||
$node = $child->accept($this);
|
$node = $child->accept($this);
|
||||||
if ($node instanceof AST\TextNode) {
|
if ($node instanceof AST\TextNode) {
|
||||||
if ($context) {
|
if ($context) {
|
||||||
throw new \Exception('Unexpected text node after context');
|
throw new Exception('Unexpected text node after context');
|
||||||
}
|
}
|
||||||
$words[] = $node->getValue();
|
$words[] = $node->getValue();
|
||||||
} elseif ($node instanceof AST\Context) {
|
} elseif ($node instanceof AST\Context) {
|
||||||
$context = $node;
|
$context = $node;
|
||||||
} else {
|
} else {
|
||||||
throw new \Exception('Term node can only contain text nodes');
|
throw new Exception('Term node can only contain text nodes');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,7 +218,7 @@ class QueryVisitor implements Visit
|
|||||||
$node instanceof AST\TextNode) {
|
$node instanceof AST\TextNode) {
|
||||||
// Prevent merge once a context is set
|
// Prevent merge once a context is set
|
||||||
if ($last->hasContext()) {
|
if ($last->hasContext()) {
|
||||||
throw new \Exception('Unexpected text node after context');
|
throw new Exception('Unexpected text node after context');
|
||||||
}
|
}
|
||||||
$nodes[$last_index] = $last = AST\TextNode::merge($last, $node);
|
$nodes[$last_index] = $last = AST\TextNode::merge($last, $node);
|
||||||
} else {
|
} else {
|
||||||
@@ -244,12 +239,12 @@ class QueryVisitor implements Visit
|
|||||||
if ($root instanceof AST\ContextAbleInterface) {
|
if ($root instanceof AST\ContextAbleInterface) {
|
||||||
$root = $root->withContext($node);
|
$root = $root->withContext($node);
|
||||||
} else {
|
} else {
|
||||||
throw new \Exception('Unexpected context after non-contextualizable node');
|
throw new Exception('Unexpected context after non-contextualizable node');
|
||||||
}
|
}
|
||||||
} elseif ($node instanceof AST\Node) {
|
} elseif ($node instanceof AST\Node) {
|
||||||
$root = new AST\AndExpression($root, $node);
|
$root = new AST\Boolean\AndOperator($root, $node);
|
||||||
} else {
|
} else {
|
||||||
throw new \Exception('Unexpected node type inside text node.');
|
throw new Exception('Unexpected node type inside text node.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,7 +268,7 @@ class QueryVisitor implements Visit
|
|||||||
private function visitFlagStatementNode(TreeNode $node)
|
private function visitFlagStatementNode(TreeNode $node)
|
||||||
{
|
{
|
||||||
if ($node->getChildrenNumber() !== 2) {
|
if ($node->getChildrenNumber() !== 2) {
|
||||||
throw new \Exception('Flag statement can only have 2 childs.');
|
throw new Exception('Flag statement can only have 2 childs.');
|
||||||
}
|
}
|
||||||
$flag = $node->getChild(0)->accept($this);
|
$flag = $node->getChild(0)->accept($this);
|
||||||
if (!$flag instanceof AST\Flag) {
|
if (!$flag instanceof AST\Flag) {
|
||||||
@@ -289,7 +284,7 @@ class QueryVisitor implements Visit
|
|||||||
private function visitBoolean(TreeNode $node)
|
private function visitBoolean(TreeNode $node)
|
||||||
{
|
{
|
||||||
if (null === $value = $node->getValue()) {
|
if (null === $value = $node->getValue()) {
|
||||||
throw new \Exception('Boolean node must be a token');
|
throw new Exception('Boolean node must be a token');
|
||||||
}
|
}
|
||||||
switch ($value['token']) {
|
switch ($value['token']) {
|
||||||
case NodeTypes::TOKEN_TRUE:
|
case NodeTypes::TOKEN_TRUE:
|
||||||
@@ -299,47 +294,37 @@ class QueryVisitor implements Visit
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new \Exception('Unexpected token for a boolean.');
|
throw new Exception('Unexpected token for a boolean.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function visitDatabaseNode(Element $element)
|
private function visitNativeKeyValueNode(TreeNode $node)
|
||||||
{
|
{
|
||||||
if ($element->getChildrenNumber() !== 1) {
|
if ($node->getChildrenNumber() !== 2) {
|
||||||
throw new \Exception('Base filter can only have a single child.');
|
throw new Exception('Key value expression can only have 2 childs.');
|
||||||
}
|
}
|
||||||
$baseName = $element->getChild(0)->getValue()['value'];
|
$key = $this->visit($node->getChild(0));
|
||||||
|
$value = $this->visit($node->getChild(1));
|
||||||
return new AST\DatabaseExpression($baseName);
|
return new AST\KeyValue\Expression($key, $value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function visitCollectionNode(Element $element)
|
private function visitNativeKeyNode(Element $element)
|
||||||
{
|
{
|
||||||
if ($element->getChildrenNumber() !== 1) {
|
if ($element->getChildrenNumber() !== 1) {
|
||||||
throw new \Exception('Collection filter can only have a single child.');
|
throw new Exception('Native key node can only have a single child.');
|
||||||
}
|
}
|
||||||
$collectionName = $element->getChild(0)->getValue()['value'];
|
$type = $element->getChild(0)->getValue()['token'];
|
||||||
|
switch ($type) {
|
||||||
return new AST\CollectionExpression($collectionName);
|
case NodeTypes::TOKEN_DATABASE:
|
||||||
}
|
return AST\KeyValue\NativeKey::database();
|
||||||
|
case NodeTypes::TOKEN_COLLECTION:
|
||||||
private function visitTypeNode(Element $element)
|
return AST\KeyValue\NativeKey::collection();
|
||||||
{
|
case NodeTypes::TOKEN_MEDIA_TYPE:
|
||||||
if ($element->getChildrenNumber() !== 1) {
|
return AST\KeyValue\NativeKey::mediaType();
|
||||||
throw new \Exception('Type filter can only have a single child.');
|
case NodeTypes::TOKEN_RECORD_ID:
|
||||||
|
return AST\KeyValue\NativeKey::recordIdentifier();
|
||||||
|
default:
|
||||||
|
throw new InvalidArgumentException(sprintf('Unexpected token type "%s" for native key.', $type));
|
||||||
}
|
}
|
||||||
$typeName = $element->getChild(0)->getValue()['value'];
|
|
||||||
|
|
||||||
return new AST\TypeExpression($typeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function visitIdentifierNode(Element $element)
|
|
||||||
{
|
|
||||||
if ($element->getChildrenNumber() !== 1) {
|
|
||||||
throw new \Exception('Identifier filter can only have a single child.');
|
|
||||||
}
|
|
||||||
$identifier = $element->getChild(0)->getValue()['value'];
|
|
||||||
|
|
||||||
return new AST\RecordIdentifierExpression($identifier);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Alchemy\Tests\Phrasea\SearchEngine\AST;
|
||||||
|
|
||||||
|
use Alchemy\Phrasea\SearchEngine\Elastic\AST\Field as ASTField;
|
||||||
|
use Alchemy\Phrasea\SearchEngine\Elastic\AST\FieldEqualsExpression;
|
||||||
|
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext;
|
||||||
|
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Field as StructureField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group unit
|
||||||
|
* @group searchengine
|
||||||
|
* @group ast
|
||||||
|
*/
|
||||||
|
class FieldEqualsExpressionTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
public function testSerialization()
|
||||||
|
{
|
||||||
|
$this->assertTrue(method_exists(FieldEqualsExpression::class, '__toString'), 'Class does not have method __toString');
|
||||||
|
$field = $this->prophesize(ASTField::class);
|
||||||
|
$field->__toString()->willReturn('foo');
|
||||||
|
$node = new FieldEqualsExpression($field->reveal(), 'bar');
|
||||||
|
$this->assertEquals('(foo == <value:"bar">)', (string) $node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider queryProvider
|
||||||
|
*/
|
||||||
|
public function testQueryBuild($index_field, $value, $compatible_value, $private, $expected_json)
|
||||||
|
{
|
||||||
|
$structure_field = $this->prophesize(StructureField::class);
|
||||||
|
$structure_field->isValueCompatible($value)->willReturn($compatible_value);
|
||||||
|
$structure_field->getIndexField(true)->willReturn($index_field);
|
||||||
|
$structure_field->isPrivate()->willReturn($private);
|
||||||
|
if ($private) {
|
||||||
|
$structure_field->getDependantCollections()->willReturn(['baz', 'qux']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ast_field = $this->prophesize(ASTField::class);
|
||||||
|
$query_context = $this->prophesize(QueryContext::class);
|
||||||
|
$query_context->get($ast_field->reveal())->willReturn($structure_field);
|
||||||
|
|
||||||
|
$node = new FieldEqualsExpression($ast_field->reveal(), 'bar');
|
||||||
|
$query = $node->buildQuery($query_context->reveal());
|
||||||
|
|
||||||
|
$this->assertEquals(json_decode($expected_json, true), $query);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function queryProvider()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
['foo.raw', 'bar', true, true, '{
|
||||||
|
"filtered": {
|
||||||
|
"filter": {
|
||||||
|
"terms": {
|
||||||
|
"base_id": ["baz","qux"] } },
|
||||||
|
"query": {
|
||||||
|
"term": {
|
||||||
|
"foo.raw": "bar" } } } }'],
|
||||||
|
['foo.raw', 'bar', true, false, '{
|
||||||
|
"term": {
|
||||||
|
"foo.raw": "bar" } }'],
|
||||||
|
['foo.raw', 'bar', false, true, 'null'],
|
||||||
|
['foo.raw', 'bar', false, false, 'null'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Alchemy\Tests\Phrasea\SearchEngine\AST;
|
||||||
|
|
||||||
|
use Alchemy\Phrasea\SearchEngine\Elastic\AST\KeyValue\Key;
|
||||||
|
use Alchemy\Phrasea\SearchEngine\Elastic\AST\KeyValue\Expression as KeyValueExpression;
|
||||||
|
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group unit
|
||||||
|
* @group searchengine
|
||||||
|
* @group ast
|
||||||
|
*/
|
||||||
|
class KeyValueExpressionTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
public function testSerialization()
|
||||||
|
{
|
||||||
|
$this->assertTrue(method_exists(KeyValueExpression::class, '__toString'), 'Class does not have method __toString');
|
||||||
|
$key = $this->prophesize(Key::class);
|
||||||
|
$key->__toString()->willReturn('foo');
|
||||||
|
$node = new KeyValueExpression($key->reveal(), 'bar');
|
||||||
|
$this->assertEquals('<foo:bar>', (string) $node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testQueryBuild()
|
||||||
|
{
|
||||||
|
$query_context = $this->prophesize(QueryContext::class);
|
||||||
|
$key = $this->prophesize(Key::class);
|
||||||
|
$key->buildQueryForValue('bar', $query_context->reveal())->willReturn('baz');
|
||||||
|
|
||||||
|
$node = new KeyValueExpression($key->reveal(), 'bar');
|
||||||
|
$query = $node->buildQuery($query_context->reveal());
|
||||||
|
$this->assertEquals('baz', $query);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,83 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Alchemy\Tests\Phrasea\SearchEngine\AST;
|
||||||
|
|
||||||
|
use Alchemy\Phrasea\SearchEngine\Elastic\AST\KeyValue\NativeKey;
|
||||||
|
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @group unit
|
||||||
|
* @group searchengine
|
||||||
|
* @group ast
|
||||||
|
*/
|
||||||
|
class NativeKeyTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
public function testSerialization()
|
||||||
|
{
|
||||||
|
$this->assertTrue(method_exists(NativeKey::class, '__toString'), 'Class does not have method __toString');
|
||||||
|
$this->assertEquals('database', (string) NativeKey::database());
|
||||||
|
$this->assertEquals('collection', (string) NativeKey::collection());
|
||||||
|
$this->assertEquals('media_type', (string) NativeKey::mediaType());
|
||||||
|
$this->assertEquals('record_identifier', (string) NativeKey::recordIdentifier());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDatabaseQuery()
|
||||||
|
{
|
||||||
|
$query_context = $this->prophesize(QueryContext::class);
|
||||||
|
$key = NativeKey::database();
|
||||||
|
$query = $key->buildQueryForValue('bar', $query_context->reveal());
|
||||||
|
|
||||||
|
$expected = '{
|
||||||
|
"term": {
|
||||||
|
"databox_name": "bar"
|
||||||
|
}
|
||||||
|
}';
|
||||||
|
|
||||||
|
$this->assertEquals(json_decode($expected, true), $query);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCollectionQuery()
|
||||||
|
{
|
||||||
|
$query_context = $this->prophesize(QueryContext::class);
|
||||||
|
$key = NativeKey::collection();
|
||||||
|
$query = $key->buildQueryForValue('bar', $query_context->reveal());
|
||||||
|
|
||||||
|
$expected = '{
|
||||||
|
"term": {
|
||||||
|
"collection_name": "bar"
|
||||||
|
}
|
||||||
|
}';
|
||||||
|
|
||||||
|
$this->assertEquals(json_decode($expected, true), $query);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMediaTypeQuery()
|
||||||
|
{
|
||||||
|
$query_context = $this->prophesize(QueryContext::class);
|
||||||
|
$key = NativeKey::mediaType();
|
||||||
|
$query = $key->buildQueryForValue('bar', $query_context->reveal());
|
||||||
|
|
||||||
|
$expected = '{
|
||||||
|
"term": {
|
||||||
|
"type": "bar"
|
||||||
|
}
|
||||||
|
}';
|
||||||
|
|
||||||
|
$this->assertEquals(json_decode($expected, true), $query);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRecordIdentifierQuery()
|
||||||
|
{
|
||||||
|
$query_context = $this->prophesize(QueryContext::class);
|
||||||
|
$key = NativeKey::recordIdentifier();
|
||||||
|
$query = $key->buildQueryForValue('bar', $query_context->reveal());
|
||||||
|
|
||||||
|
$expected = '{
|
||||||
|
"term": {
|
||||||
|
"record_id": "bar"
|
||||||
|
}
|
||||||
|
}';
|
||||||
|
|
||||||
|
$this->assertEquals(json_decode($expected, true), $query);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,5 +1,7 @@
|
|||||||
foo|<text:"foo">
|
foo|<text:"foo">
|
||||||
foo (bar)|<text:"foo" context:"bar">
|
foo (bar)|<text:"foo" context:"bar">
|
||||||
|
foo "bar"|(<text:"foo "> AND <exact_text:"bar">)
|
||||||
|
"foo" bar|(<exact_text:"foo"> AND <text:" bar">)
|
||||||
# foo ("bar baz")|<text:"foo" context:"bar baz">
|
# foo ("bar baz")|<text:"foo" context:"bar baz">
|
||||||
foo bar|<text:"foo bar">
|
foo bar|<text:"foo bar">
|
||||||
foo bar (baz qux)|<text:"foo bar" context:"baz qux">
|
foo bar (baz qux)|<text:"foo bar" context:"baz qux">
|
||||||
@@ -46,7 +48,7 @@ foo EXCEPT (bar AND baz)|(<text:"foo"> EXCEPT (<text:"bar"> AND <text:"baz">))
|
|||||||
foo EXCEPT (bar OR baz)|(<text:"foo"> EXCEPT (<text:"bar"> OR <text:"baz">))
|
foo EXCEPT (bar OR baz)|(<text:"foo"> EXCEPT (<text:"bar"> OR <text:"baz">))
|
||||||
foo EXCEPT (bar EXCEPT baz)|(<text:"foo"> EXCEPT (<text:"bar"> EXCEPT <text:"baz">))
|
foo EXCEPT (bar EXCEPT baz)|(<text:"foo"> EXCEPT (<text:"bar"> EXCEPT <text:"baz">))
|
||||||
|
|
||||||
# Inequality operators
|
# Comparison operators
|
||||||
foo < 42|<range:foo lt="42">
|
foo < 42|<range:foo lt="42">
|
||||||
foo ≤ 42|<range:foo lte="42">
|
foo ≤ 42|<range:foo lte="42">
|
||||||
foo > 42|<range:foo gt="42">
|
foo > 42|<range:foo gt="42">
|
||||||
@@ -59,23 +61,36 @@ foo < "2015/01/01"|<range:foo lt="2015/01/01">
|
|||||||
foo ≤ "2015/01/01"|<range:foo lte="2015/01/01">
|
foo ≤ "2015/01/01"|<range:foo lte="2015/01/01">
|
||||||
foo > "2015/01/01"|<range:foo gt="2015/01/01">
|
foo > "2015/01/01"|<range:foo gt="2015/01/01">
|
||||||
foo ≥ "2015/01/01"|<range:foo gte="2015/01/01">
|
foo ≥ "2015/01/01"|<range:foo gte="2015/01/01">
|
||||||
|
foo = 42|(<field:foo> == <value:"42">)
|
||||||
|
foo = bar|(<field:foo> == <value:"bar">)
|
||||||
|
foo = "bar"|(<field:foo> == <value:"bar">)
|
||||||
|
|
||||||
# Field narrowing
|
# Field narrowing
|
||||||
foo IN bar|(<text:"foo"> IN <field:bar>)
|
foo:bar|(<field:foo> MATCHES <text:"bar">)
|
||||||
foo bar IN baz|(<text:"foo bar"> IN <field:baz>)
|
foo:[bar]|(<field:foo> MATCHES <term:"bar">)
|
||||||
foo IN bar baz|<text:"foo IN bar baz">
|
foo:[bar (baz)]|(<field:foo> MATCHES <term:"bar" context:"baz">)
|
||||||
fooINbar|<text:"fooINbar">
|
foo:bar baz|((<field:foo> MATCHES <text:"bar">) AND <text:"baz">)
|
||||||
|
foo bar:baz|(<text:"foo"> AND (<field:bar> MATCHES <text:"baz">))
|
||||||
|
|
||||||
|
# Regular field with name colliding with a native key
|
||||||
|
field.collection:foo|(<field:collection> MATCHES <text:"foo">)
|
||||||
|
field.database:foo|(<field:database> MATCHES <text:"foo">)
|
||||||
|
field.type:foo|(<field:type> MATCHES <text:"foo">)
|
||||||
|
field.id:foo|(<field:id> MATCHES <text:"foo">)
|
||||||
|
|
||||||
# Matchers
|
# Matchers
|
||||||
collection:foo|<collection:foo>
|
collection:foo|<collection:foo>
|
||||||
collection:foo AND bar|(<collection:foo> AND <text:"bar">)
|
collection:foo AND bar|(<collection:foo> AND <text:"bar">)
|
||||||
collection:foo bar|<text:"collection:foo bar">
|
collection:foo bar|(<collection:foo> AND <text:"bar">)
|
||||||
database:foo|<database:foo>
|
database:foo|<database:foo>
|
||||||
database:foo AND bar|(<database:foo> AND <text:"bar">)
|
database:foo AND bar|(<database:foo> AND <text:"bar">)
|
||||||
database:foo bar|<text:"database:foo bar">
|
database:foo bar|(<database:foo> AND <text:"bar">)
|
||||||
|
type:foo|<media_type:foo>
|
||||||
|
type:foo AND bar|(<media_type:foo> AND <text:"bar">)
|
||||||
|
type:foo bar|(<media_type:foo> AND <text:"bar">)
|
||||||
id:90|<record_identifier:90>
|
id:90|<record_identifier:90>
|
||||||
id:90 AND foo|(<record_identifier:90> AND <text:"foo">)
|
id:90 AND foo|(<record_identifier:90> AND <text:"foo">)
|
||||||
id:90 foo|<text:"id:90 foo">
|
id:90 foo|(<record_identifier:90> AND <text:"foo">)
|
||||||
recordid:90|<record_identifier:90>
|
recordid:90|<record_identifier:90>
|
||||||
|
|
||||||
# Flag matcher
|
# Flag matcher
|
||||||
@@ -84,11 +99,13 @@ flag.foo:1|<flag:foo set>
|
|||||||
flag.foo:false|<flag:foo cleared>
|
flag.foo:false|<flag:foo cleared>
|
||||||
flag.foo:0|<flag:foo cleared>
|
flag.foo:0|<flag:foo cleared>
|
||||||
flag.true:true|<flag:true set>
|
flag.true:true|<flag:true set>
|
||||||
flag.foo bar:true|<text:"flag.foo bar:true">
|
flag.foo bar:true|(<text:"flag.foo"> AND (<field:bar> MATCHES <text:"true">))
|
||||||
true|<text:"true">
|
true|<text:"true">
|
||||||
|
|
||||||
# Matcher on unknown name --> fulltext
|
# Matcher on unknown name --> fulltext
|
||||||
foo:bar|<text:"foo:bar">
|
foo:bar|(<field:foo> MATCHES <text:"bar">)
|
||||||
|
foo:bar AND baz|((<field:foo> MATCHES <text:"bar">) AND <text:"baz">)
|
||||||
|
foo AND bar:baz|(<text:"foo"> AND (<field:bar> MATCHES <text:"baz">))
|
||||||
|
|
||||||
# Search terms with embedded keywords
|
# Search terms with embedded keywords
|
||||||
INA|<text:"INA">
|
INA|<text:"INA">
|
||||||
|
Can't render this file because it contains an unexpected character in line 1 and column 11.
|
Reference in New Issue
Block a user