Skip to content

Commit fcefba0

Browse files
Spamerczclaude
andcommitted
feat(query): add percolate query
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 8da126d commit fcefba0

3 files changed

Lines changed: 161 additions & 0 deletions

File tree

doc/02-query-objects.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,19 @@ new \Spameri\ElasticQuery\Query\Pinned(
559559
);
560560
```
561561

562+
##### Percolate Query
563+
Match a document against stored queries (reverse search).
564+
- Class: `\Spameri\ElasticQuery\Query\Percolate`
565+
- [Documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-percolate-query.html)
566+
- [Implementation](https://github.com/Spameri/ElasticQuery/blob/master/src/Query/Percolate.php)
567+
568+
```php
569+
new \Spameri\ElasticQuery\Query\Percolate(
570+
field: 'query',
571+
document: ['message' => 'A new bonsai tree'],
572+
);
573+
```
574+
562575
##### Script Query
563576
Filter documents with a Painless boolean script.
564577
- Class: `\Spameri\ElasticQuery\Query\Script`

src/Query/Percolate.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
declare(strict_types = 1);
4+
5+
namespace Spameri\ElasticQuery\Query;
6+
7+
/**
8+
* @see https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-percolate-query.html
9+
*/
10+
class Percolate implements \Spameri\ElasticQuery\Query\LeafQueryInterface
11+
{
12+
13+
/**
14+
* @param array<string, mixed>|null $document Inline document to percolate.
15+
*/
16+
public function __construct(
17+
private string $field,
18+
private array|null $document = null,
19+
private string|null $index = null,
20+
private string|null $id = null,
21+
)
22+
{
23+
if ($document === null && ($index === null || $id === null)) {
24+
throw new \Spameri\ElasticQuery\Exception\InvalidArgumentException(
25+
'Percolate query requires either a document, or both index and id.',
26+
);
27+
}
28+
}
29+
30+
31+
public function key(): string
32+
{
33+
return 'percolate_' . $this->field;
34+
}
35+
36+
37+
/**
38+
* @return array<string, array<string, mixed>>
39+
*/
40+
public function toArray(): array
41+
{
42+
$body = [
43+
'field' => $this->field,
44+
];
45+
46+
if ($this->document !== null) {
47+
$body['document'] = $this->document;
48+
49+
} else {
50+
$body['index'] = $this->index;
51+
$body['id'] = $this->id;
52+
}
53+
54+
return [
55+
'percolate' => $body,
56+
];
57+
}
58+
59+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace SpameriTests\ElasticQuery\Query;
4+
5+
require_once __DIR__ . '/../../bootstrap.php';
6+
7+
8+
class Percolate extends \Tester\TestCase
9+
{
10+
11+
private const INDEX = 'spameri_test_query_percolate';
12+
13+
14+
public function setUp(): void
15+
{
16+
$ch = \curl_init();
17+
\curl_setopt($ch, \CURLOPT_URL, \ELASTICSEARCH_HOST . '/' . self::INDEX);
18+
\curl_setopt($ch, \CURLOPT_RETURNTRANSFER, 1);
19+
\curl_setopt($ch, \CURLOPT_CUSTOMREQUEST, 'PUT');
20+
\curl_setopt($ch, \CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
21+
22+
\curl_exec($ch);
23+
}
24+
25+
26+
public function testToArrayInline(): void
27+
{
28+
$percolate = new \Spameri\ElasticQuery\Query\Percolate(
29+
field: 'query',
30+
document: ['message' => 'A new bonsai tree'],
31+
);
32+
33+
$array = $percolate->toArray();
34+
35+
\Tester\Assert::same(['message' => 'A new bonsai tree'], $array['percolate']['document']);
36+
}
37+
38+
39+
public function testToArrayById(): void
40+
{
41+
$percolate = new \Spameri\ElasticQuery\Query\Percolate(
42+
field: 'query',
43+
index: 'my-index',
44+
id: '1',
45+
);
46+
47+
$array = $percolate->toArray();
48+
49+
\Tester\Assert::same('my-index', $array['percolate']['index']);
50+
\Tester\Assert::same('1', $array['percolate']['id']);
51+
}
52+
53+
54+
public function testRequiresDocOrId(): void
55+
{
56+
\Tester\Assert::exception(
57+
static function (): void {
58+
new \Spameri\ElasticQuery\Query\Percolate(field: 'query');
59+
},
60+
\Spameri\ElasticQuery\Exception\InvalidArgumentException::class,
61+
);
62+
}
63+
64+
65+
public function testKey(): void
66+
{
67+
$percolate = new \Spameri\ElasticQuery\Query\Percolate(
68+
field: 'query',
69+
document: ['m' => 'hello'],
70+
);
71+
72+
\Tester\Assert::same('percolate_query', $percolate->key());
73+
}
74+
75+
76+
public function tearDown(): void
77+
{
78+
$ch = \curl_init();
79+
\curl_setopt($ch, \CURLOPT_URL, \ELASTICSEARCH_HOST . '/' . self::INDEX);
80+
\curl_setopt($ch, \CURLOPT_RETURNTRANSFER, 1);
81+
\curl_setopt($ch, \CURLOPT_CUSTOMREQUEST, 'DELETE');
82+
\curl_setopt($ch, \CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
83+
84+
\curl_exec($ch);
85+
}
86+
87+
}
88+
89+
(new Percolate())->run();

0 commit comments

Comments
 (0)