Skip to content

Commit 8827fa3

Browse files
Spamerczclaude
andcommitted
feat(query): add dis max compound query
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 2bcb7fb commit 8827fa3

3 files changed

Lines changed: 143 additions & 0 deletions

File tree

doc/02-query-objects.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,20 @@ new \Spameri\ElasticQuery\Query\ConstantScore(
310310
);
311311
```
312312

313+
##### DisMax Query
314+
Best-of-many — returns the highest-scoring sub-query per document, with `tie_breaker` for the rest.
315+
- Class: `\Spameri\ElasticQuery\Query\DisMax`
316+
- [Documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-dis-max-query.html)
317+
- [Implementation](https://github.com/Spameri/ElasticQuery/blob/master/src/Query/DisMax.php)
318+
319+
```php
320+
$disMax = new \Spameri\ElasticQuery\Query\DisMax(
321+
query: new \Spameri\ElasticQuery\Query\Term('title', 'foo'),
322+
tieBreaker: 0.7,
323+
);
324+
$disMax->addQuery(new \Spameri\ElasticQuery\Query\Term('body', 'foo'));
325+
```
326+
313327
##### Boosting Query
314328
Match `positive` docs but lower the score of `negative` matches.
315329
- Class: `\Spameri\ElasticQuery\Query\Boosting`

src/Query/DisMax.php

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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-dis-max-query.html
9+
*/
10+
class DisMax implements \Spameri\ElasticQuery\Query\LeafQueryInterface
11+
{
12+
13+
/**
14+
* @var array<int, \Spameri\ElasticQuery\Query\LeafQueryInterface>
15+
*/
16+
private array $queries;
17+
18+
19+
public function __construct(
20+
\Spameri\ElasticQuery\Query\LeafQueryInterface $query,
21+
private float $tieBreaker = 0.0,
22+
private float $boost = 1.0,
23+
)
24+
{
25+
$this->queries = [$query];
26+
}
27+
28+
29+
public function addQuery(\Spameri\ElasticQuery\Query\LeafQueryInterface $query): void
30+
{
31+
$this->queries[] = $query;
32+
}
33+
34+
35+
public function key(): string
36+
{
37+
$keys = [];
38+
foreach ($this->queries as $query) {
39+
$keys[] = $query->key();
40+
}
41+
42+
return 'dis_max_' . \implode('-', $keys);
43+
}
44+
45+
46+
/**
47+
* @return array<string, array<string, mixed>>
48+
*/
49+
public function toArray(): array
50+
{
51+
$queries = [];
52+
foreach ($this->queries as $query) {
53+
$queries[] = $query->toArray();
54+
}
55+
56+
return [
57+
'dis_max' => [
58+
'queries' => $queries,
59+
'tie_breaker' => $this->tieBreaker,
60+
'boost' => $this->boost,
61+
],
62+
];
63+
}
64+
65+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace SpameriTests\ElasticQuery\Query;
4+
5+
require_once __DIR__ . '/../../bootstrap.php';
6+
7+
8+
class DisMax extends \Tester\TestCase
9+
{
10+
11+
private const INDEX = 'spameri_test_query_dis_max';
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 testToArray(): void
27+
{
28+
$disMax = new \Spameri\ElasticQuery\Query\DisMax(
29+
query: new \Spameri\ElasticQuery\Query\Term('title', 'foo'),
30+
tieBreaker: 0.7,
31+
);
32+
$disMax->addQuery(new \Spameri\ElasticQuery\Query\Term('body', 'foo'));
33+
34+
$array = $disMax->toArray();
35+
36+
\Tester\Assert::same(0.7, $array['dis_max']['tie_breaker']);
37+
\Tester\Assert::count(2, $array['dis_max']['queries']);
38+
}
39+
40+
41+
public function testKey(): void
42+
{
43+
$disMax = new \Spameri\ElasticQuery\Query\DisMax(
44+
new \Spameri\ElasticQuery\Query\Term('title', 'foo'),
45+
);
46+
47+
\Tester\Assert::same('dis_max_term_title_foo', $disMax->key());
48+
}
49+
50+
51+
public function tearDown(): void
52+
{
53+
$ch = \curl_init();
54+
\curl_setopt($ch, \CURLOPT_URL, \ELASTICSEARCH_HOST . '/' . self::INDEX);
55+
\curl_setopt($ch, \CURLOPT_RETURNTRANSFER, 1);
56+
\curl_setopt($ch, \CURLOPT_CUSTOMREQUEST, 'DELETE');
57+
\curl_setopt($ch, \CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
58+
59+
\curl_exec($ch);
60+
}
61+
62+
}
63+
64+
(new DisMax())->run();

0 commit comments

Comments
 (0)