Merge pull request #1570 from mdarse/incomplete-date-query

Pass structure to query visitor & enable range generation for regular fields (equal expression)
This commit is contained in:
Benoît Burnichon
2015-11-16 15:45:21 +01:00
7 changed files with 111 additions and 34 deletions

View File

@@ -13,6 +13,7 @@ namespace Alchemy\Phrasea\Core\Provider;
use Alchemy\Phrasea\Controller\LazyLocator;
use Alchemy\Phrasea\SearchEngine\Elastic\ElasticsearchOptions;
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryVisitor;
use Alchemy\Phrasea\SearchEngine\SearchEngineLogger;
use Alchemy\Phrasea\Exception\InvalidArgumentException;
use Alchemy\Phrasea\SearchEngine\SearchEngineInterface;
@@ -24,6 +25,7 @@ use Alchemy\Phrasea\SearchEngine\Elastic\Indexer\TermIndexer;
use Alchemy\Phrasea\SearchEngine\Elastic\RecordHelper;
use Alchemy\Phrasea\SearchEngine\Elastic\Search\Escaper;
use Alchemy\Phrasea\SearchEngine\Elastic\Search\FacetsResponse;
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContextFactory;
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryCompiler;
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\GlobalStructure;
use Alchemy\Phrasea\SearchEngine\Elastic\Thesaurus;
@@ -62,7 +64,7 @@ class SearchEngineServiceProvider implements ServiceProviderInterface
$app['search_engine.structure'],
$app['elasticsearch.client'],
$options->getIndexName(),
$app['locales.available'],
$app['query_context.factory'],
$app['elasticsearch.facets_response.factory'],
$options
);
@@ -181,6 +183,14 @@ class SearchEngineServiceProvider implements ServiceProviderInterface
);
});
$app['query_context.factory'] = $app->share(function ($app) {
return new QueryContextFactory(
$app['search_engine.structure'],
array_keys($app['locales.available']),
$app['locale']
);
});
$app['query_parser.grammar_path'] = function ($app) {
$configPath = ['registry', 'searchengine', 'query-grammar-path'];
$grammarPath = $app['conf']->get($configPath, 'grammar/query.pp');
@@ -194,9 +204,14 @@ class SearchEngineServiceProvider implements ServiceProviderInterface
return Compiler\Llk\Llk::load(new File\Read($grammarPath));
});
$app['query_visitor.factory'] = $app->protect(function () use ($app) {
return new QueryVisitor($app['search_engine.structure']);
});
$app['query_compiler'] = $app->share(function ($app) {
return new QueryCompiler(
$app['query_parser'],
$app['query_visitor.factory'],
$app['thesaurus']
);
});

View File

@@ -20,6 +20,11 @@ class FieldKey implements Key, QueryPostProcessor
$this->name = $name;
}
public function getName()
{
return $this->name;
}
public function getIndexField(QueryContext $context, $raw = false)
{
return $this->getField($context)->getIndexField($raw);

View File

@@ -19,8 +19,8 @@ use Alchemy\Phrasea\SearchEngine\Elastic\Search\AggregationHelper;
use Alchemy\Phrasea\SearchEngine\Elastic\Search\FacetsResponse;
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryCompiler;
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContext;
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryContextFactory;
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Flag;
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\LimitedStructure;
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Structure;
use Alchemy\Phrasea\SearchEngine\SearchEngineInterface;
use Alchemy\Phrasea\SearchEngine\SearchEngineOptions;
@@ -48,12 +48,12 @@ class ElasticSearchEngine implements SearchEngineInterface
/** @var ElasticsearchOptions */
private $options;
public function __construct(Application $app, Structure $structure, Client $client, $indexName, array $locales, Closure $facetsResponseFactory, ElasticsearchOptions $options)
public function __construct(Application $app, Structure $structure, Client $client, $indexName, QueryContextFactory $context_factory, Closure $facetsResponseFactory, ElasticsearchOptions $options)
{
$this->app = $app;
$this->structure = $structure;
$this->client = $client;
$this->locales = array_keys($locales);
$this->context_factory = $context_factory;
$this->facetsResponseFactory = $facetsResponseFactory;
$this->options = $options;
@@ -62,6 +62,7 @@ class ElasticSearchEngine implements SearchEngineInterface
}
$this->indexName = $indexName;
}
public function getIndexName()
@@ -261,12 +262,7 @@ class ElasticSearchEngine implements SearchEngineInterface
public function query($string, $offset, $perPage, SearchEngineOptions $options = null)
{
$options = $options ?: new SearchEngineOptions();
$narrowToFields = array();
foreach($options->getFields() as $field) {
$narrowToFields[] = $field->get_name();
}
$context = $this->createQueryContext($options)->narrowToFields($narrowToFields);
$context = $this->context_factory->createContext($options);
/** @var QueryCompiler $query_compiler */
$query_compiler = $this->app['query_compiler'];
@@ -352,23 +348,6 @@ class ElasticSearchEngine implements SearchEngineInterface
];
}
/**
* @todo Move in search engine service provider
*/
private function createQueryContext(SearchEngineOptions $options)
{
return new QueryContext(
$this->getLimitedStructure($options),
$this->locales,
$this->app['locale']
);
}
private function getLimitedStructure(SearchEngineOptions $options)
{
return new LimitedStructure($this->structure, $options);
}
/**
* {@inheritdoc}
*/
@@ -455,7 +434,7 @@ class ElasticSearchEngine implements SearchEngineInterface
$base_facet_agg['terms']['field'] = 'type';
$aggs['Type'] = $base_facet_agg;
$structure = $this->getLimitedStructure($options);
$structure = $this->context_factory->getLimitedStructure($options);
foreach ($structure->getFacetFields() as $name => $field) {
// 2015-05-26 (mdarse) Removed databox filtering.
// It was already done by the ACL filter in the query scope, so no

View File

@@ -2,7 +2,6 @@
namespace Alchemy\Phrasea\SearchEngine\Elastic\Search;
use Alchemy\Phrasea\SearchEngine\Elastic\AST;
use Alchemy\Phrasea\SearchEngine\Elastic\Exception\QueryException;
use Alchemy\Phrasea\SearchEngine\Elastic\Thesaurus;
use Hoa\Compiler\Exception\Exception as CompilerException;
@@ -14,6 +13,7 @@ use Hoa\Visitor\Visit;
class QueryCompiler
{
private $parser;
private $queryVisitorFactory;
private $thesaurus;
private static $leftAssociativeOperators = array(
@@ -22,9 +22,10 @@ class QueryCompiler
NodeTypes::EXCEPT_EXPR
);
public function __construct(Parser $parser, Thesaurus $thesaurus)
public function __construct(Parser $parser, callable $queryVisitorFactory, Thesaurus $thesaurus)
{
$this->parser = $parser;
$this->queryVisitorFactory = $queryVisitorFactory;
$this->thesaurus = $thesaurus;
}
@@ -54,7 +55,15 @@ class QueryCompiler
*/
public function parse($string, $postprocessing = true)
{
return $this->visitString($string, new QueryVisitor(), $postprocessing);
return $this->visitString($string, $this->createQueryVisitor(), $postprocessing);
}
/**
* @return Visit
*/
private function createQueryVisitor()
{
return call_user_func($this->queryVisitorFactory);
}
public function dump($string, $postprocessing = true)

View File

@@ -0,0 +1,49 @@
<?php
namespace Alchemy\Phrasea\SearchEngine\Elastic\Search;
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\LimitedStructure;
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Structure;
use Alchemy\Phrasea\SearchEngine\SearchEngineOptions;
class QueryContextFactory
{
private $structure;
public function __construct(Structure $structure, array $locales, $current_locale)
{
$this->structure = $structure;
$this->locales = $locales;
$this->current_locale = $current_locale;
}
public function createContext(SearchEngineOptions $options = null)
{
$structure = $options
? $this->getLimitedStructure($options)
: $this->structure;
$context = new QueryContext($structure, $this->locales, $this->current_locale);
if ($options) {
$fields = $this->getSearchedFields($options);
$context = $context->narrowToFields($fields);
}
return $context;
}
private function getSearchedFields(SearchEngineOptions $options)
{
$fields = [];
foreach ($options->getFields() as $field) {
$fields[] = $field->get_name();
}
return $fields;
}
public function getLimitedStructure(SearchEngineOptions $options)
{
return new LimitedStructure($this->structure, $options);
}
}

View File

@@ -4,7 +4,7 @@ namespace Alchemy\Phrasea\SearchEngine\Elastic\Search;
use Alchemy\Phrasea\SearchEngine\Elastic\AST;
use Alchemy\Phrasea\SearchEngine\Elastic\Exception\Exception;
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryHelper;
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Structure;
use Hoa\Compiler\Llk\TreeNode;
use Hoa\Visitor\Element;
use Hoa\Visitor\Visit;
@@ -12,6 +12,13 @@ use InvalidArgumentException;
class QueryVisitor implements Visit
{
private $structure;
public function __construct(Structure $structure)
{
$this->structure = $structure;
}
public function visit(Element $element, &$handle = null, $eldnah = null)
{
if (null !== $value = $element->getValue()) {
@@ -197,7 +204,12 @@ class QueryVisitor implements Visit
private function isDateKey(AST\KeyValue\Key $key)
{
return $key instanceof AST\KeyValue\TimestampKey;
if ($key instanceof AST\KeyValue\TimestampKey) {
return true;
} elseif ($key instanceof AST\KeyValue\FieldKey) {
return $this->structure->get($key->getName()) !== null;
}
return false;
}
private function visitTerm(Element $element)

View File

@@ -3,6 +3,8 @@
namespace Alchemy\Tests\Phrasea\SearchEngine;
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryCompiler;
use Alchemy\Phrasea\SearchEngine\Elastic\Search\QueryVisitor;
use Alchemy\Phrasea\SearchEngine\Elastic\Structure\Structure;
use Alchemy\Phrasea\SearchEngine\Elastic\Thesaurus;
use Alchemy\Tests\Tools\CsvFileIterator;
use Hoa\Compiler;
@@ -23,11 +25,17 @@ class QueryCompilerTest extends \PHPUnit_Framework_TestCase
$grammar_path = realpath(implode('/', [__DIR__, $project_root, $grammar_path]));
$parser = Compiler\Llk\Llk::load(new File\Read($grammar_path));
$structure = $this->getMock(Structure::class);
$queryVisitorFactory = function () use ($structure) {
return new QueryVisitor($structure);
};
$thesaurus = $this->getMockBuilder(Thesaurus::class)
->disableOriginalConstructor()
->getMock();
$this->compiler = new QueryCompiler($parser, $thesaurus);
$this->compiler = new QueryCompiler($parser, $queryVisitorFactory, $thesaurus);
}
/**