Skip to content

Commit cc665f6

Browse files
Merge pull request #321 from daniellienert/feature/prefix-query
FEATURE: Prefix query operation
2 parents 0a0801a + ce50109 commit cc665f6

6 files changed

Lines changed: 92 additions & 52 deletions

File tree

Classes/Command/NodeIndexCommandController.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ public function indexNodeCommand(string $identifier, string $workspace = null, s
215215
* @return void
216216
* @throws StopCommandException
217217
* @throws \Flowpack\ElasticSearch\ContentRepositoryAdaptor\Exception
218+
* @throws ConfigurationException
218219
*/
219220
public function buildCommand(int $limit = null, bool $update = false, string $workspace = null, string $postfix = null): void
220221
{

Classes/Eel/ElasticSearchQueryBuilder.php

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -429,8 +429,8 @@ public function fieldBasedAggregation(string $name, string $field, string $type
429429
*
430430
* Example Usage to create a terms aggregation for a property color:
431431
*
432-
* aggregationDefinition = Neos.Fusion:RawArray {
433-
* terms = Neos.Fusion:RawArray {
432+
* aggregationDefinition = Neos.Fusion:DataStructure {
433+
* terms {
434434
* field = "color"
435435
* }
436436
* }
@@ -562,7 +562,7 @@ public function getFrom(): int
562562
* @param NodeInterface $node
563563
* @return array the Elasticsearch hit for the node as array, or NULL if it does not exist.
564564
*/
565-
public function getFullElasticSearchHitForNode(NodeInterface $node): array
565+
public function getFullElasticSearchHitForNode(NodeInterface $node): ?array
566566
{
567567
return $this->elasticSearchHitsIndexedByNodeFromLastRequest[$node->getIdentifier()] ?? null;
568568
}
@@ -596,7 +596,8 @@ public function fetch(): array
596596
$this->result['nodes'] = $this->convertHitsToNodes($searchResult->getHits());
597597
}
598598
} catch (ApiException $exception) {
599-
$this->throwableStorage->logThrowable($exception);
599+
$message = $this->throwableStorage->logThrowable($exception);
600+
$this->logger->error(sprintf('Request failed with %s', $message), LogEnvironment::fromMethodName(__METHOD__));
600601
$this->result['nodes'] = [];
601602
}
602603

@@ -668,7 +669,7 @@ public function count()
668669
* Match the searchword against the fulltext index
669670
*
670671
* @param string $searchWord
671-
* @param array $options Options to configure the query_string, see https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-dsl-query-string-query.html
672+
* @param array $options Options to configure the query_string, see https://www.elastic.co/guide/en/elasticsearch/reference/7.6/query-dsl-query-string-query.html
672673
* @return QueryBuilderInterface
673674
* @api
674675
*/
@@ -681,6 +682,22 @@ public function fulltext(string $searchWord, array $options = []): QueryBuilderI
681682
return $this;
682683
}
683684

685+
/**
686+
* Adds a prefix filter to the query
687+
* See: https://www.elastic.co/guide/en/elasticsearch/reference/7.6/query-dsl-prefix-query.html
688+
*
689+
* @param string $propertyName
690+
* @param string $prefix
691+
* @return $this|QueryBuilderInterface
692+
* @throws QueryBuildingException
693+
*/
694+
public function prefix(string $propertyName, string $prefix): QueryBuilderInterface
695+
{
696+
$this->request->queryFilter('prefix', [$propertyName => $prefix]);
697+
698+
return $this;
699+
}
700+
684701
/**
685702
* Configure Result Highlighting. Only makes sense in combination with fulltext(). By default, highlighting is enabled.
686703
* It can be disabled by calling "highlight(FALSE)".

Classes/Indexer/NodeIndexer.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,7 @@ public function updateIndexAlias(): void
558558
* @throws Exception
559559
* @throws Exception\ConfigurationException
560560
*/
561-
public function updateMainAlias()
561+
public function updateMainAlias(): void
562562
{
563563
$aliasActions = [];
564564
$aliasNamePrefix = $this->searchClient->getIndexNamePrefix(); // The alias name is the unprefixed index name

Configuration/Settings.Neos.ContentRepository.Search.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ Neos:
2323
elasticSearchMapping:
2424
type: boolean
2525

26+
array:
27+
elasticSearchMapping:
28+
type: keyword
29+
2630
integer:
2731
elasticSearchMapping:
2832
type: integer

README.md

Lines changed: 47 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,10 @@ Flowpack:
143143
indexes:
144144
default: # Configuration bundle name
145145
neoscontentrepository: # The index prefix name, must be the same as in the Neos.ContentRepository.Search.elasticSearch.indexName setting
146-
index:
147-
number_of_shards: 1
148-
number_of_replicas: 0
146+
settings:
147+
index:
148+
number_of_shards: 1
149+
number_of_replicas: 0
149150
```
150151

151152
### Configure per dimension
@@ -159,22 +160,23 @@ Flowpack:
159160
ElasticSearch:
160161
indexes:
161162
default:
162-
'neoscontentrepository-0359ed5c416567b8bc2e5ade0f277b36': # The hash specifies the dimension combination
163-
index:
164-
number_of_shards: 1
165-
number_of_replicas: 0
166-
analysis:
167-
filter:
168-
elision:
169-
type: 'elision'
170-
articles: [ 'l', 'm', 't', 'qu', 'n', 's', 'j', 'd' ]
171-
analyzer:
172-
custom_french_analyzer:
173-
tokenizer: 'letter'
174-
filter: [ 'asciifolding', 'lowercase', 'french_stem', 'elision', 'stop' ]
175-
tag_analyzer:
176-
tokenizer: 'keyword'
177-
filter: [ 'asciifolding', 'lowercase' ]
163+
'neoscontentrepository-0359ed5c416567b8bc2e5ade0f277b36': # The hash specifies the dimension combination
164+
settings:
165+
index:
166+
number_of_shards: 1
167+
number_of_replicas: 0
168+
analysis:
169+
filter:
170+
elision:
171+
type: 'elision'
172+
articles: [ 'l', 'm', 't', 'qu', 'n', 's', 'j', 'd' ]
173+
analyzer:
174+
custom_french_analyzer:
175+
tokenizer: 'letter'
176+
filter: [ 'asciifolding', 'lowercase', 'french_stem', 'elision', 'stop' ]
177+
tag_analyzer:
178+
tokenizer: 'keyword'
179+
filter: [ 'asciifolding', 'lowercase' ]
178180
```
179181

180182
Which dimension combinations are available in your system and which hashes they are identified with can be shown with the CLI command:
@@ -406,18 +408,20 @@ Furthermore, the following operators are supported:
406408

407409
As **value**, the following methods accept a simple type, a node object or a DateTime object.
408410

409-
* `nodeType('Your.Node:Type')`
410-
* `exactMatch('propertyName', value)` -- supports simple types: `exactMatch('tag', 'foo')`, or node references: `exactMatch('author', authorNode)`
411-
* `exclude('propertyName', value)` -- excludes results by property - the negation of exactMatch.
412-
* `greaterThan('propertyName', value, [clauseType])` -- range filter with property values greater than the given value
413-
* `greaterThanOrEqual('propertyName', value, [clauseType])` -- range filter with property values greater than or equal to the given value
414-
* `lessThan('propertyName', value, [clauseType])` -- range filter with property values less than the given value
415-
* `lessThanOrEqual('propertyName', value, [clauseType])` -- range filter with property values less than or equal to the given value
416-
* `sortAsc('propertyName')` and `sortDesc('propertyName')` -- can also be used multiple times, e.g. `sortAsc('tag').sortDesc(`date')`
417-
will first sort by tag ascending, and then by date descending.
418-
* `limit(5)` -- only return five results. If not specified, the default limit by Elasticsearch applies (which is at 10 by default)
419-
* `from(5)` -- return the results starting from the 6th one
420-
* `fulltext('searchWord', options)` -- do a query_string query on the Fulltext index using the searchword and additional [options](https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-dsl-query-string-query.html) to the query_string
411+
| Query Operator | Description |
412+
|----------------|-------------|
413+
|`nodeType('Your.Node:Type')` |Filters on the given NodeType|
414+
|`exactMatch('propertyName', value)` |Supports simple types: `exactMatch('tag', 'foo')`, or node references: `exactMatch('author', authorNode)`|
415+
|`exclude('propertyName', value)` |Excludes results by property - the negation of exactMatch.
416+
|`greaterThan('propertyName', value, [clauseType])` |Range filter with property values greater than the given value|
417+
|`greaterThanOrEqual('propertyName', value, [clauseType])`|Range filter with property values greater than or equal to the given value|
418+
|`lessThan('propertyName', value, [clauseType])` |Range filter with property values less than the given value|
419+
|`lessThanOrEqual('propertyName', value, [clauseType])`|Range filter with property values less than or equal to the given value|
420+
|`sortAsc('propertyName')` / `sortDesc('propertyName')`|Can also be used multiple times, e.g. `sortAsc('tag').sortDesc(`date')` will first sort by tag ascending, and then by date descending.|
421+
|`limit(5)` |Only return five results. If not specified, the default limit by Elasticsearch applies (which is at 10 by default)|
422+
|`from(5)` |Return the results starting from the 6th one|
423+
|`prefix('propertyName', 'prefix')` |Does a prefix on the given field with the given prefix|
424+
|`fulltext('searchWord', options)` |Does a query_string query on the Fulltext index using the searchword and additional [options](https://www.elastic.co/guide/en/elasticsearch/reference/7.6/query-dsl-query-string-query.html) to the query_string|
421425

422426
## moreLikeThis(like, fields, options)
423427

@@ -539,8 +543,8 @@ fieldBasedAggregation("anotherAggregation", "field", "avg", "colors.avgprice")
539543
To add a custom aggregation you can use the `aggregation()` method. All you have to do is to provide an array with your
540544
aggregation definition. This example would do the same as the fieldBasedAggregation would do for you:
541545
```
542-
aggregationDefinition = Neos.Fusion:RawArray {
543-
terms = Neos.Fusion:RawArray {
546+
aggregationDefinition = Neos.Fusion:DataStructure {
547+
terms = Neos.Fusion:DataStructure {
544548
field = "color"
545549
}
546550
}
@@ -558,7 +562,7 @@ prototype(Vendor.Name:FilteredProductList) < prototype(Neos.Neos:Content)
558562
prototype(Vendor.Name:FilteredProductList) {
559563

560564
// Create SearchFilter for products
561-
searchFilter = Neos.Fusion:RawArray {
565+
searchFilter = Neos.Fusion:DataStructure {
562566
sku = ${String.split(q(node).property("products"), ",")}
563567
}
564568

@@ -607,9 +611,6 @@ for all your filterable properties, or else filtering won't work on them properl
607611
type: keyword
608612
```
609613

610-
**Note:** When using Elasticsearch 5.x the mapping needs to be adjusted in a different way.
611-
More information on the [mapping in ElasticSearch 5.x](Documentation/ElasticMapping-5.x.md).
612-
613614
## Sorting
614615

615616
This package adapts Elasticsearchs sorting capabilities. You can add multiple sort operations to your query.
@@ -631,9 +632,9 @@ nodes = ${q(Search.query(site).....sortAsc("title").sortDesc("name").execute())}
631632
632633
# Custom sort operation
633634
634-
geoSorting = Neos.Fusion:RawArray {
635-
_geo_distance = Neos.Fusion:RawArray {
636-
latlng = Neos.Fusion:RawArray {
635+
geoSorting = Neos.Fusion:DataStructure {
636+
_geo_distance = Neos.Fusion:DataStructure {
637+
latlng = Neos.Fusion:DataStructure {
637638
lat = 51.512711
638639
lon = 7.453084
639640
}
@@ -666,9 +667,9 @@ First of all you have to define a property in your NodeTypes.yaml for your node
666667
667668
Query your nodes in your Fusion:
668669
```
669-
geoSorting = Neos.Fusion:RawArray {
670-
_geo_distance = Neos.Fusion:RawArray {
671-
latlng = Neos.Fusion:RawArray {
670+
geoSorting = Neos.Fusion:DataStructure {
671+
_geo_distance = Neos.Fusion:DataStructure {
672+
latlng = Neos.Fusion:DataStructure {
672673
lat = 51.512711
673674
lon = 7.453084
674675
}
@@ -757,10 +758,10 @@ You can access your suggestions inside your fluid template with
757758
Phrase query that returns query suggestions
758759

759760
```
760-
suggestionsQueryDefinition = Neos.Fusion:RawArray {
761+
suggestionsQueryDefinition = Neos.Fusion:DataStructure {
761762
text = 'some Text'
762-
simple_phrase = Neos.Fusion:RawArray {
763-
phrase = Neos.Fusion:RawArray {
763+
simple_phrase = Neos.Fusion:DataStructure {
764+
phrase = Neos.Fusion:DataStructure {
764765
analyzer = 'body'
765766
field = 'bigram'
766767
size = 1

Tests/Functional/Eel/ElasticSearchQueryTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,23 @@ public function filterNodeByProperty(): void
191191
static::assertEquals(1, $resultCount);
192192
}
193193

194+
/**
195+
* @test
196+
*
197+
* @throws QueryBuildingException
198+
* @throws \Flowpack\ElasticSearch\ContentRepositoryAdaptor\Exception
199+
* @throws \Flowpack\ElasticSearch\Exception
200+
* @throws \Neos\Flow\Http\Exception
201+
*/
202+
public function prefixFilter(): void
203+
{
204+
$resultCount = $this->getQueryBuilder()
205+
->log($this->getLogMessagePrefix(__METHOD__))
206+
->prefix('title', 'chi')
207+
->count();
208+
static::assertEquals(2, $resultCount);
209+
}
210+
194211
/**
195212
* @test
196213
*/

0 commit comments

Comments
 (0)