Skip to content

Commit 3446155

Browse files
authored
Merge pull request #100 from ensi-platform/v7-ecs-1350
V7. Post Filter, Ranges Aggregation, Between Filter
2 parents 973da6c + 92de7cb commit 3446155

File tree

11 files changed

+175
-0
lines changed

11 files changed

+175
-0
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
namespace Ensi\LaravelElasticQuery\Aggregating\Metrics;
4+
5+
use Ensi\LaravelElasticQuery\Aggregating\Range;
6+
use Ensi\LaravelElasticQuery\Aggregating\Result;
7+
use Ensi\LaravelElasticQuery\Contracts\Aggregation;
8+
use Webmozart\Assert\Assert;
9+
10+
class RangesAggregation implements Aggregation
11+
{
12+
/**
13+
* @param string $name
14+
* @param string $field
15+
* @param Range[] $ranges
16+
*/
17+
public function __construct(protected string $name, protected string $field, protected array $ranges)
18+
{
19+
Assert::stringNotEmpty(trim($name));
20+
Assert::stringNotEmpty(trim($field));
21+
Assert::allIsInstanceOf($ranges, Range::class);
22+
}
23+
24+
public function name(): string
25+
{
26+
return $this->name;
27+
}
28+
29+
public function add(Range $range): self
30+
{
31+
$this->ranges[] = $range;
32+
33+
return $this;
34+
}
35+
36+
public function toDSL(): array
37+
{
38+
if (empty($this->ranges)) {
39+
return [];
40+
}
41+
42+
return [
43+
$this->name => [
44+
"range" => [
45+
"field" => $this->field,
46+
"ranges" => array_map(fn (Range $range) => $range->toDSL(), $this->ranges),
47+
],
48+
],
49+
];
50+
}
51+
52+
public function parseResults(array $response): array
53+
{
54+
return array_map(
55+
callback: fn (array $bucket) => Result::parseBucket($bucket),
56+
array: $response[$this->name]['buckets'] ?? []
57+
);
58+
}
59+
}

src/Aggregating/Range.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Ensi\LaravelElasticQuery\Aggregating;
4+
5+
class Range
6+
{
7+
public function __construct(
8+
public int|float|null $from = null,
9+
public int|float|null $to = null,
10+
public ?string $key = null
11+
) {
12+
}
13+
14+
public function toDSL(): array
15+
{
16+
return array_filter(["from" => $this->from, "to" => $this->to, "key" => $this->key]);
17+
}
18+
}

src/Concerns/ConstructsAggregations.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Ensi\LaravelElasticQuery\Aggregating\Metrics\MaxAggregation;
1515
use Ensi\LaravelElasticQuery\Aggregating\Metrics\MinAggregation;
1616
use Ensi\LaravelElasticQuery\Aggregating\Metrics\MinMaxAggregation;
17+
use Ensi\LaravelElasticQuery\Aggregating\Metrics\RangesAggregation;
1718
use Ensi\LaravelElasticQuery\Aggregating\Metrics\ValueCountAggregation;
1819
use Ensi\LaravelElasticQuery\Contracts\Aggregation;
1920
use Ensi\LaravelElasticQuery\Contracts\Criteria;
@@ -87,6 +88,13 @@ public function count(string $name, string $field): static
8788
return $this;
8889
}
8990

91+
public function ranges(string $name, string $field, array $ranges): static
92+
{
93+
$this->aggregations->add(new RangesAggregation($name, $this->absolutePath($field), $ranges));
94+
95+
return $this;
96+
}
97+
9098
public function cardinality(string $name, string $field): static
9199
{
92100
$this->aggregations->add(new CardinalityAggregation($name, $this->absolutePath($field)));

src/Concerns/DecoratesBoolQuery.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,13 @@ public function whereMoreLikeThis(array $fields, MoreLikeThis $likeThis, ?MoreLi
154154
return $this;
155155
}
156156

157+
public function whereBetween(string $field, mixed $from, mixed $to): static
158+
{
159+
$this->forwardCallTo($this->boolQuery(), __FUNCTION__, func_get_args());
160+
161+
return $this;
162+
}
163+
157164
/**
158165
* @param array<FunctionScoreItem> $functions
159166
* @param ?DSLAware $query

src/Contracts/AggregationsBuilder.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,22 @@
33
namespace Ensi\LaravelElasticQuery\Contracts;
44

55
use Closure;
6+
use Ensi\LaravelElasticQuery\Aggregating\Range;
67

78
interface AggregationsBuilder extends BoolQuery
89
{
910
public function terms(string $name, string $field, ?int $size = null): static;
1011

1112
public function minmax(string $name, string $field): static;
1213

14+
/**
15+
* @param string $name
16+
* @param string $field
17+
* @param Range[] $ranges
18+
* @return $this
19+
*/
20+
public function ranges(string $name, string $field, array $ranges): static;
21+
1322
public function min(string $name, string $field, mixed $missing = null): static;
1423

1524
public function max(string $name, string $field, mixed $missing = null): static;

src/Contracts/BoolQuery.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ public function addMustBool(callable $fn): static;
4545

4646
public function whereMoreLikeThis(array $fields, MoreLikeThis $likeThis, ?MoreLikeOptions $options = null): static;
4747

48+
public function whereBetween(string $field, mixed $from, mixed $to): static;
49+
4850
/**
4951
* @param array<FunctionScoreItem> $functions
5052
* @param ?DSLAware $query

src/Filtering/BoolQueryBuilder.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Ensi\LaravelElasticQuery\Contracts\MoreLikeThis;
1515
use Ensi\LaravelElasticQuery\Contracts\MultiMatchOptions;
1616
use Ensi\LaravelElasticQuery\Contracts\WildcardOptions;
17+
use Ensi\LaravelElasticQuery\Filtering\Criterias\Between;
1718
use Ensi\LaravelElasticQuery\Filtering\Criterias\Exists;
1819
use Ensi\LaravelElasticQuery\Filtering\Criterias\FunctionScore;
1920
use Ensi\LaravelElasticQuery\Filtering\Criterias\MoreLike;
@@ -259,6 +260,13 @@ public function whereMoreLikeThis(array $fields, MoreLikeThis $likeThis, ?MoreLi
259260
return $this;
260261
}
261262

263+
public function whereBetween(string $field, mixed $from, mixed $to): static
264+
{
265+
$this->filter->add(new Between($this->absolutePath($field), $from, $to));
266+
267+
return $this;
268+
}
269+
262270
/**
263271
* @param array<FunctionScoreItem> $functions
264272
* @param ?DSLAware $query

src/Filtering/Criterias/Between.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Ensi\LaravelElasticQuery\Filtering\Criterias;
4+
5+
use Ensi\LaravelElasticQuery\Contracts\Criteria;
6+
use Webmozart\Assert\Assert;
7+
8+
class Between implements Criteria
9+
{
10+
public function __construct(private string $field, private mixed $from, private mixed $to)
11+
{
12+
Assert::stringNotEmpty(trim($field));
13+
}
14+
15+
public function toDSL(): array
16+
{
17+
return ['range' => [$this->field => ['gte' => $this->from, 'lte' => $this->to]]];
18+
}
19+
}

src/Search/SearchQuery.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class SearchQuery implements SortableQuery, CollapsibleQuery, HighlightingQuery
3030
use ExtendsSort;
3131

3232
protected BoolQueryBuilder $boolQuery;
33+
protected ?BoolQueryBuilder $postFilter = null;
3334
protected SortCollection $sorts;
3435
protected ?Collapse $collapse = null;
3536
protected ?Highlight $highlight = null;
@@ -171,6 +172,10 @@ protected function execute(
171172
$dsl['highlight'] = $this->highlight->toDSL();
172173
}
173174

175+
if (!is_null($this->postFilter)) {
176+
$dsl['post_filter'] = $this->postFilter->toDSL();
177+
}
178+
174179
if ($cursor !== null && !$cursor->isBOF()) {
175180
$dsl['search_after'] = $cursor->toDSL();
176181
}
@@ -235,6 +240,13 @@ public function highlight(Highlight $highlight): static
235240
return $this;
236241
}
237242

243+
public function setPostFilter(BoolQueryBuilder $boolQueryBuilder): static
244+
{
245+
$this->postFilter = $boolQueryBuilder;
246+
247+
return $this;
248+
}
249+
238250
public function addAggregations(Aggregation $aggregation): static
239251
{
240252
$this->aggregations ??= new AggregationCollection();

tests/IntegrationTests/AggregationQueryIntegrationTest.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Ensi\LaravelElasticQuery\Aggregating\Metrics\MinMaxScoreAggregation;
66
use Ensi\LaravelElasticQuery\Aggregating\Metrics\TopHitsAggregation;
77
use Ensi\LaravelElasticQuery\Aggregating\MinMax;
8+
use Ensi\LaravelElasticQuery\Aggregating\Range;
89
use Ensi\LaravelElasticQuery\Contracts\AggregationsBuilder;
910
use Ensi\LaravelElasticQuery\Filtering\Criterias\RangeBound;
1011
use Ensi\LaravelElasticQuery\Filtering\Criterias\Term;
@@ -103,6 +104,30 @@
103104
assertEquals(2, $results->get('cardinality'));
104105
});
105106

107+
test('aggregation query ranges', function () {
108+
/** @var IntegrationTestCase $this */
109+
110+
$rangeFromTo = new Range(from: 0, to: 5, key: 'from-0-to-5');
111+
$rangeFrom = new Range(from: 7, key: 'from-7');
112+
$rangeTo = new Range(to: 8, key: 'to-8');
113+
114+
$results = ProductsIndex::aggregate()
115+
->ranges('ranges', 'rating', [$rangeFromTo, $rangeFrom, $rangeTo])
116+
->get();
117+
118+
/** @var Bucket $result */
119+
foreach ($results as $result) {
120+
$expected = match ($result->key) {
121+
'from-0-to-5' => 2,
122+
'from-7' => 3,
123+
'to-8' => 4,
124+
default => null,
125+
};
126+
127+
assertEquals($expected, $result->count);
128+
}
129+
});
130+
106131
test('aggregation query count all', function () {
107132
/** @var IntegrationTestCase $this */
108133

0 commit comments

Comments
 (0)