Skip to content

Commit 3c4b884

Browse files
authored
Merge pull request #2 from ensi-platform/dev-match
Full text search
2 parents 1ee826a + 29be3ab commit 3c4b884

File tree

11 files changed

+193
-11
lines changed

11 files changed

+193
-11
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,16 @@ $searchQuery->whereDoesntHave(
7070
`nested_field` must have `nested` type.
7171
Subqueries cannot use fields of main document only subdocument.
7272

73+
### Full text search
74+
75+
```php
76+
$searchQuery->whereMatch('field_one', 'query string');
77+
$searchQuery->whereMultiMatch(['field_one^3', 'field_two'], 'query string', MatchType::MOST_FIELDS);
78+
$searchQuery->whereMultiMatch([], 'query string'); // search by all text fields
79+
```
80+
81+
`field_one` and `field_two` must be of text type. If no type is given, the `MatchType::BEST_FIELDS` is used.
82+
7383
### Sorting
7484

7585
```php

src/Concerns/DecoratesBoolQuery.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,18 @@ public function whereNotNull(string $field): static
6868

6969
return $this;
7070
}
71+
72+
public function whereMatch(string $field, string $query, string $operator = 'or'): static
73+
{
74+
$this->forwardCallTo($this->boolQuery(), __FUNCTION__, func_get_args());
75+
76+
return $this;
77+
}
78+
79+
public function whereMultiMatch(array $fields, string $query, ?string $type = null): static
80+
{
81+
$this->forwardCallTo($this->boolQuery(), __FUNCTION__, func_get_args());
82+
83+
return $this;
84+
}
7185
}

src/Contracts/BoolQuery.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,8 @@ public function whereDoesntHave(string $nested, Closure $filter): static;
2222
public function whereNull(string $field): static;
2323

2424
public function whereNotNull(string $field): static;
25+
26+
public function whereMatch(string $field, string $query, string $operator = 'or'): static;
27+
28+
public function whereMultiMatch(array $fields, string $query, ?string $type = null): static;
2529
}

src/Contracts/MatchType.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace Ensi\LaravelElasticQuery\Contracts;
4+
5+
final class MatchType
6+
{
7+
public const BEST_FIELDS = 'best_fields';
8+
public const MOST_FIELDS = 'most_fields';
9+
public const CROSS_FIELDS = 'cross_fields';
10+
public const PHRASE = 'phrase';
11+
public const PHRASE_PREFIX = 'phrase_prefix';
12+
public const BOOL_PREFIX = 'bool_prefix';
13+
14+
public static function cases(): array
15+
{
16+
return [
17+
self::BEST_FIELDS,
18+
self::MOST_FIELDS,
19+
self::CROSS_FIELDS,
20+
self::PHRASE,
21+
self::PHRASE_PREFIX,
22+
self::BOOL_PREFIX,
23+
];
24+
}
25+
}

src/Filtering/BoolQueryBuilder.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
use Ensi\LaravelElasticQuery\Contracts\BoolQuery;
88
use Ensi\LaravelElasticQuery\Contracts\Criteria;
99
use Ensi\LaravelElasticQuery\Filtering\Criterias\Exists;
10+
use Ensi\LaravelElasticQuery\Filtering\Criterias\MultiMatch;
1011
use Ensi\LaravelElasticQuery\Filtering\Criterias\Nested;
12+
use Ensi\LaravelElasticQuery\Filtering\Criterias\OneMatch;
1113
use Ensi\LaravelElasticQuery\Filtering\Criterias\RangeBound;
1214
use Ensi\LaravelElasticQuery\Filtering\Criterias\Term;
1315
use Ensi\LaravelElasticQuery\Filtering\Criterias\Terms;
@@ -139,6 +141,25 @@ public function whereNotNull(string $field): static
139141
return $this;
140142
}
141143

144+
public function whereMatch(string $field, string $query, string $operator = 'or'): static
145+
{
146+
$this->must->add(new OneMatch($this->absolutePath($field), $query, $operator));
147+
148+
return $this;
149+
}
150+
151+
public function whereMultiMatch(array $fields, string $query, ?string $type = null): static
152+
{
153+
$fields = array_map(
154+
fn (string $field) => $this->absolutePath($field),
155+
$fields
156+
);
157+
158+
$this->must->add(new MultiMatch($fields, $query, $type));
159+
160+
return $this;
161+
}
162+
142163
protected function addNestedCriteria(string $nested, Closure $filter, CriteriaCollection $target): static
143164
{
144165
$path = $this->absolutePath($nested);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Ensi\LaravelElasticQuery\Filtering\Criterias;
4+
5+
use Ensi\LaravelElasticQuery\Contracts\Criteria;
6+
use Ensi\LaravelElasticQuery\Contracts\MatchType;
7+
use Webmozart\Assert\Assert;
8+
9+
class MultiMatch implements Criteria
10+
{
11+
public function __construct(private array $fields, private string $query, private ?string $type = null)
12+
{
13+
Assert::nullOrOneOf($this->type, MatchType::cases());
14+
}
15+
16+
public function toDSL(): array
17+
{
18+
$dsl = ['query' => $this->query];
19+
20+
if ($this->fields) {
21+
$dsl['fields'] = $this->fields;
22+
}
23+
24+
if ($this->type !== null) {
25+
$dsl['type'] = $this->type;
26+
}
27+
28+
return ['multi_match' => $dsl];
29+
}
30+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Ensi\LaravelElasticQuery\Filtering\Criterias;
4+
5+
use Ensi\LaravelElasticQuery\Contracts\Criteria;
6+
use Webmozart\Assert\Assert;
7+
8+
class OneMatch implements Criteria
9+
{
10+
public function __construct(private string $field, private string $query, private string $operator)
11+
{
12+
Assert::stringNotEmpty(trim($field));
13+
Assert::oneOf($operator, ['and', 'or']);
14+
}
15+
16+
public function toDSL(): array
17+
{
18+
$body = [
19+
'query' => $this->query,
20+
'operator' => $this->operator,
21+
];
22+
23+
return [
24+
'match' => [
25+
$this->field => $body,
26+
],
27+
];
28+
}
29+
}

tests/Functional/Search/FilteringTest.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,53 @@ public function testWhereDoesntHave(): void
3939

4040
$this->assertDocumentIds([1, 328, 471]);
4141
}
42+
43+
public function testWhereNull(): void
44+
{
45+
$this->testing->whereNull('package');
46+
47+
$this->assertDocumentIds([1, 319, 328, 471]);
48+
}
49+
50+
public function testWhereNotNull(): void
51+
{
52+
$this->testing->whereNotNull('package');
53+
54+
$this->assertDocumentIds([150, 405]);
55+
}
56+
57+
public function testWhereMatch(): void
58+
{
59+
$this->testing->whereMatch('search_name', 'black leather gloves');
60+
61+
$this->assertDocumentIds([319, 471]);
62+
}
63+
64+
public function whereMatchOperatorAnd(): void
65+
{
66+
$this->testing->whereMatch('search_name', 'leather gloves', 'and');
67+
68+
$this->assertDocumentIds([319]);
69+
}
70+
71+
public function testWhereMultiMatch(): void
72+
{
73+
$this->testing->whereMultiMatch(['search_name', 'description'], 'nice gloves');
74+
75+
$this->assertDocumentIds([471, 328, 319]);
76+
}
77+
78+
public function testWhereMultiMatchDefault(): void
79+
{
80+
$this->testing->whereMultiMatch([], 'nice gloves');
81+
82+
$this->assertDocumentIds([471, 328, 319]);
83+
}
84+
85+
public function testWhereMultiMatchPrioritized(): void
86+
{
87+
$this->testing->whereMultiMatch(['search_name^2', 'description'], 'water');
88+
89+
$this->assertDocumentOrder([150, 405]);
90+
}
4291
}

tests/Functional/Search/SearchQueryTest.php

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,4 @@ public function testSortByNested(): void
6868
}
6969

7070
//endregion
71-
72-
protected function assertDocumentOrder(array $ids): void
73-
{
74-
$actual = $this->testing->get()
75-
->pluck('_id')
76-
->all();
77-
78-
$this->assertEquals($ids, $actual);
79-
}
8071
}

tests/Functional/Search/SearchTestCase.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,13 @@ protected function assertDocumentIds(array $expected): void
2929

3030
$this->assertEqualsCanonicalizing($expected, $actual);
3131
}
32+
33+
protected function assertDocumentOrder(array $ids): void
34+
{
35+
$actual = $this->testing->get()
36+
->pluck('_id')
37+
->all();
38+
39+
$this->assertEquals($ids, $actual);
40+
}
3241
}

0 commit comments

Comments
 (0)