Skip to content

Commit c3e5661

Browse files
authored
Merge pull request #8 from ensi-platform/task-96329-2
#96329 Match and multi match query options
2 parents 7481129 + 910376f commit c3e5661

File tree

9 files changed

+161
-25
lines changed

9 files changed

+161
-25
lines changed

src/Concerns/DecoratesBoolQuery.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
namespace Ensi\LaravelElasticQuery\Concerns;
44

55
use Closure;
6+
use Ensi\LaravelElasticQuery\Contracts\MatchOptions;
7+
use Ensi\LaravelElasticQuery\Contracts\MultiMatchOptions;
68
use Ensi\LaravelElasticQuery\Filtering\BoolQueryBuilder;
79
use Illuminate\Contracts\Support\Arrayable;
810
use Illuminate\Support\Traits\ForwardsCalls;
@@ -69,14 +71,14 @@ public function whereNotNull(string $field): static
6971
return $this;
7072
}
7173

72-
public function whereMatch(string $field, string $query, string $operator = 'or'): static
74+
public function whereMatch(string $field, string $query, string|MatchOptions $operator = 'or'): static
7375
{
7476
$this->forwardCallTo($this->boolQuery(), __FUNCTION__, func_get_args());
7577

7678
return $this;
7779
}
7880

79-
public function whereMultiMatch(array $fields, string $query, ?string $type = null): static
81+
public function whereMultiMatch(array $fields, string $query, string|MultiMatchOptions|null $type = null): static
8082
{
8183
$this->forwardCallTo($this->boolQuery(), __FUNCTION__, func_get_args());
8284

src/Contracts/BoolQuery.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public function whereNull(string $field): static;
2323

2424
public function whereNotNull(string $field): static;
2525

26-
public function whereMatch(string $field, string $query, string $operator = 'or'): static;
26+
public function whereMatch(string $field, string $query, string|MatchOptions $operator = 'or'): static;
2727

28-
public function whereMultiMatch(array $fields, string $query, ?string $type = null): static;
28+
public function whereMultiMatch(array $fields, string $query, string|MultiMatchOptions|null $type = null): static;
2929
}

src/Contracts/MatchOptions.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace Ensi\LaravelElasticQuery\Contracts;
4+
5+
use Illuminate\Contracts\Support\Arrayable;
6+
use Webmozart\Assert\Assert;
7+
8+
class MatchOptions implements Arrayable
9+
{
10+
public function __construct(private array $options = [])
11+
{
12+
}
13+
14+
public static function make(
15+
?string $operator = null,
16+
?string $fuzziness = null,
17+
?string $minimumShouldMatch = null
18+
): static {
19+
Assert::nullOrOneOf($operator, ['or', 'and']);
20+
21+
return new static(array_filter([
22+
'operator' => $operator,
23+
'fuzziness' => $fuzziness,
24+
'minimum_should_match' => $minimumShouldMatch,
25+
]));
26+
}
27+
28+
public function toArray(): array
29+
{
30+
return $this->options;
31+
}
32+
}

src/Contracts/MultiMatchOptions.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace Ensi\LaravelElasticQuery\Contracts;
4+
5+
use Webmozart\Assert\Assert;
6+
7+
class MultiMatchOptions
8+
{
9+
public function __construct(private array $options = [])
10+
{
11+
}
12+
13+
public static function make(
14+
?string $type = null,
15+
?string $operator = null,
16+
?string $fuzziness = null,
17+
?string $minimumShouldMatch = null
18+
): static {
19+
Assert::nullOrOneOf($type, MatchType::cases());
20+
Assert::nullOrOneOf($operator, ['or', 'and']);
21+
22+
return new static(array_filter([
23+
'type' => $type,
24+
'operator' => $operator,
25+
'fuzziness' => $fuzziness,
26+
'minimum_should_match' => $minimumShouldMatch,
27+
]));
28+
}
29+
30+
public function toArray(): array
31+
{
32+
return $this->options;
33+
}
34+
}

src/Filtering/BoolQueryBuilder.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
use Ensi\LaravelElasticQuery\Concerns\SupportsPath;
77
use Ensi\LaravelElasticQuery\Contracts\BoolQuery;
88
use Ensi\LaravelElasticQuery\Contracts\Criteria;
9+
use Ensi\LaravelElasticQuery\Contracts\MatchOptions;
10+
use Ensi\LaravelElasticQuery\Contracts\MultiMatchOptions;
911
use Ensi\LaravelElasticQuery\Filtering\Criterias\Exists;
1012
use Ensi\LaravelElasticQuery\Filtering\Criterias\MultiMatch;
1113
use Ensi\LaravelElasticQuery\Filtering\Criterias\Nested;
@@ -141,21 +143,24 @@ public function whereNotNull(string $field): static
141143
return $this;
142144
}
143145

144-
public function whereMatch(string $field, string $query, string $operator = 'or'): static
146+
public function whereMatch(string $field, string $query, string|MatchOptions $operator = 'or'): static
145147
{
146-
$this->must->add(new OneMatch($this->absolutePath($field), $query, $operator));
148+
$options = is_string($operator) ? MatchOptions::make($operator) : $operator;
149+
$this->must->add(new OneMatch($this->absolutePath($field), $query, $options));
147150

148151
return $this;
149152
}
150153

151-
public function whereMultiMatch(array $fields, string $query, ?string $type = null): static
154+
public function whereMultiMatch(array $fields, string $query, string|MultiMatchOptions|null $type = null): static
152155
{
156+
$options = is_string($type) ? MultiMatchOptions::make($type) : $type;
157+
153158
$fields = array_map(
154159
fn (string $field) => $this->absolutePath($field),
155160
$fields
156161
);
157162

158-
$this->must->add(new MultiMatch($fields, $query, $type));
163+
$this->must->add(new MultiMatch($fields, $query, $options ?? new MultiMatchOptions()));
159164

160165
return $this;
161166
}

src/Filtering/Criterias/MultiMatch.php

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,12 @@
33
namespace Ensi\LaravelElasticQuery\Filtering\Criterias;
44

55
use Ensi\LaravelElasticQuery\Contracts\Criteria;
6-
use Ensi\LaravelElasticQuery\Contracts\MatchType;
7-
use Webmozart\Assert\Assert;
6+
use Ensi\LaravelElasticQuery\Contracts\MultiMatchOptions;
87

98
class MultiMatch implements Criteria
109
{
11-
public function __construct(private array $fields, private string $query, private ?string $type = null)
10+
public function __construct(private array $fields, private string $query, private MultiMatchOptions $options)
1211
{
13-
Assert::nullOrOneOf($this->type, MatchType::cases());
1412
}
1513

1614
public function toDSL(): array
@@ -21,10 +19,6 @@ public function toDSL(): array
2119
$dsl['fields'] = $this->fields;
2220
}
2321

24-
if ($this->type !== null) {
25-
$dsl['type'] = $this->type;
26-
}
27-
28-
return ['multi_match' => $dsl];
22+
return ['multi_match' => array_merge($this->options->toArray(), $dsl)];
2923
}
3024
}

src/Filtering/Criterias/OneMatch.php

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,23 @@
33
namespace Ensi\LaravelElasticQuery\Filtering\Criterias;
44

55
use Ensi\LaravelElasticQuery\Contracts\Criteria;
6+
use Ensi\LaravelElasticQuery\Contracts\MatchOptions;
67
use Webmozart\Assert\Assert;
78

89
class OneMatch implements Criteria
910
{
10-
public function __construct(private string $field, private string $query, private string $operator)
11+
public function __construct(private string $field, private string $query, private MatchOptions $options)
1112
{
1213
Assert::stringNotEmpty(trim($field));
13-
Assert::oneOf($operator, ['and', 'or']);
1414
}
1515

1616
public function toDSL(): array
1717
{
18-
$body = [
19-
'query' => $this->query,
20-
'operator' => $this->operator,
21-
];
18+
$body = ['query' => $this->query];
2219

2320
return [
2421
'match' => [
25-
$this->field => $body,
22+
$this->field => array_merge($this->options->toArray(), $body),
2623
],
2724
];
2825
}

tests/Functional/Search/FilteringTest.php

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
namespace Ensi\LaravelElasticQuery\Tests\Functional\Search;
44

55
use Ensi\LaravelElasticQuery\Contracts\BoolQuery;
6+
use Ensi\LaravelElasticQuery\Contracts\MatchOptions;
7+
use Ensi\LaravelElasticQuery\Contracts\MultiMatchOptions;
68

79
class FilteringTest extends SearchTestCase
810
{
@@ -61,13 +63,20 @@ public function testWhereMatch(): void
6163
$this->assertDocumentIds([319, 471]);
6264
}
6365

64-
public function whereMatchOperatorAnd(): void
66+
public function testWhereMatchOperatorAnd(): void
6567
{
6668
$this->testing->whereMatch('search_name', 'leather gloves', 'and');
6769

6870
$this->assertDocumentIds([319]);
6971
}
7072

73+
public function testWhereMatchOptions(): void
74+
{
75+
$this->testing->whereMatch('search_name', 'leather glaves', MatchOptions::make(fuzziness: 'AUTO'));
76+
77+
$this->assertDocumentIds([319, 471]);
78+
}
79+
7180
public function testWhereMultiMatch(): void
7281
{
7382
$this->testing->whereMultiMatch(['search_name', 'description'], 'nice gloves');
@@ -88,4 +97,15 @@ public function testWhereMultiMatchPrioritized(): void
8897

8998
$this->assertDocumentOrder([150, 405]);
9099
}
100+
101+
public function testWhereMultiMatchOptions(): void
102+
{
103+
$this->testing->whereMultiMatch(
104+
['search_name', 'description'],
105+
'nace gloves',
106+
MultiMatchOptions::make(fuzziness: 'AUTO')
107+
);
108+
109+
$this->assertDocumentIds([471, 328, 319]);
110+
}
91111
}

tests/Unit/Filtering/BoolQueryTest.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
namespace Ensi\LaravelElasticQuery\Tests\Unit\Filtering;
44

55
use Ensi\LaravelElasticQuery\Contracts\BoolQuery;
6+
use Ensi\LaravelElasticQuery\Contracts\MatchOptions;
7+
use Ensi\LaravelElasticQuery\Contracts\MatchType;
8+
use Ensi\LaravelElasticQuery\Contracts\MultiMatchOptions;
69
use Ensi\LaravelElasticQuery\Filtering\BoolQueryBuilder;
710
use Ensi\LaravelElasticQuery\Tests\AssertsArray;
811
use Ensi\LaravelElasticQuery\Tests\Unit\UnitTestCase;
@@ -129,4 +132,53 @@ public function provideWhereOperators(): array
129132
'<=' => ['<=', ['range' => ['rating' => ['lte' => 5]]]],
130133
];
131134
}
135+
136+
/**
137+
* @dataProvider provideMatch
138+
*/
139+
public function testMatch(string|MatchOptions $options, array $expected): void
140+
{
141+
$dsl = BoolQueryBuilder::make()->whereMatch('name', 'foo', $options)->toDSL();
142+
143+
$this->assertArrayFragment(array_merge(['query' => 'foo'], $expected), $dsl);
144+
}
145+
146+
public function provideMatch(): array
147+
{
148+
return [
149+
'operator' => ['and', ['operator' => 'and']],
150+
'fuzziness' => [MatchOptions::make(fuzziness: 'AUTO'), ['fuzziness' => 'AUTO']],
151+
'minimum_should_match' => [
152+
MatchOptions::make(minimumShouldMatch: '50%'),
153+
['minimum_should_match' => '50%'],
154+
],
155+
'many options' => [
156+
MatchOptions::make(operator: 'or', fuzziness: '2', minimumShouldMatch: '30%'),
157+
['minimum_should_match' => '30%', 'fuzziness' => '2', 'operator' => 'or'],
158+
],
159+
];
160+
}
161+
162+
/**
163+
* @dataProvider provideMultiMatch
164+
*/
165+
public function testMultiMatch(string|MultiMatchOptions|null $options, array $expected): void
166+
{
167+
$dsl = BoolQueryBuilder::make()->whereMultiMatch(['foo', 'bar'], 'baz', $options)->toDSL();
168+
169+
$this->assertArrayFragment(array_merge(['query' => 'baz', 'fields' => ['foo', 'bar']], $expected), $dsl);
170+
}
171+
172+
public function provideMultiMatch(): array
173+
{
174+
return [
175+
'type as string' => [MatchType::CROSS_FIELDS, ['type' => MatchType::CROSS_FIELDS]],
176+
'type in options' => [MultiMatchOptions::make(MatchType::PHRASE), ['type' => MatchType::PHRASE]],
177+
'fuzziness' => [MultiMatchOptions::make(fuzziness: 'AUTO'), ['fuzziness' => 'AUTO']],
178+
'multiple options' => [
179+
MultiMatchOptions::make(type: MatchType::MOST_FIELDS, fuzziness: '3', minimumShouldMatch: '30%'),
180+
['minimum_should_match' => '30%', 'fuzziness' => '3', 'type' => MatchType::MOST_FIELDS],
181+
],
182+
];
183+
}
132184
}

0 commit comments

Comments
 (0)