Skip to content

Commit d64cf17

Browse files
Spamerczclaude
andcommitted
feat(query): add script score query
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 96e9126 commit d64cf17

3 files changed

Lines changed: 142 additions & 0 deletions

File tree

doc/02-query-objects.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,20 @@ new \Spameri\ElasticQuery\Query\GeoBoundingBox(
490490

491491
---
492492

493+
##### ScriptScore Query
494+
Re-score matching documents using a Painless script.
495+
- Class: `\Spameri\ElasticQuery\Query\ScriptScore`
496+
- [Documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-script-score-query.html)
497+
- [Implementation](https://github.com/Spameri/ElasticQuery/blob/master/src/Query/ScriptScore.php)
498+
499+
```php
500+
new \Spameri\ElasticQuery\Query\ScriptScore(
501+
query: new \Spameri\ElasticQuery\Query\MatchAll(),
502+
source: "doc['my_field'].value * 2",
503+
minScore: 0.5,
504+
);
505+
```
506+
493507
##### Script Query
494508
Filter documents with a Painless boolean script.
495509
- Class: `\Spameri\ElasticQuery\Query\Script`

src/Query/ScriptScore.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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-script-score-query.html
9+
*/
10+
class ScriptScore implements \Spameri\ElasticQuery\Query\LeafQueryInterface
11+
{
12+
13+
/**
14+
* @param array<string, mixed> $params
15+
*/
16+
public function __construct(
17+
private \Spameri\ElasticQuery\Query\LeafQueryInterface $query,
18+
private string $source,
19+
private string $lang = 'painless',
20+
private array $params = [],
21+
private float|null $minScore = null,
22+
private float $boost = 1.0,
23+
)
24+
{
25+
}
26+
27+
28+
public function key(): string
29+
{
30+
return 'script_score_' . $this->query->key();
31+
}
32+
33+
34+
/**
35+
* @return array<string, array<string, mixed>>
36+
*/
37+
public function toArray(): array
38+
{
39+
$script = [
40+
'source' => $this->source,
41+
'lang' => $this->lang,
42+
];
43+
44+
if ($this->params !== []) {
45+
$script['params'] = $this->params;
46+
}
47+
48+
$body = [
49+
'query' => $this->query->toArray(),
50+
'script' => $script,
51+
'boost' => $this->boost,
52+
];
53+
54+
if ($this->minScore !== null) {
55+
$body['min_score'] = $this->minScore;
56+
}
57+
58+
return [
59+
'script_score' => $body,
60+
];
61+
}
62+
63+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace SpameriTests\ElasticQuery\Query;
4+
5+
require_once __DIR__ . '/../../bootstrap.php';
6+
7+
8+
class ScriptScore extends \Tester\TestCase
9+
{
10+
11+
private const INDEX = 'spameri_test_query_script_score';
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+
$ss = new \Spameri\ElasticQuery\Query\ScriptScore(
29+
query: new \Spameri\ElasticQuery\Query\MatchAll(),
30+
source: "doc['my_field'].value * 2",
31+
minScore: 0.5,
32+
);
33+
34+
$array = $ss->toArray();
35+
36+
\Tester\Assert::same("doc['my_field'].value * 2", $array['script_score']['script']['source']);
37+
\Tester\Assert::same(0.5, $array['script_score']['min_score']);
38+
}
39+
40+
41+
public function testKey(): void
42+
{
43+
$ss = new \Spameri\ElasticQuery\Query\ScriptScore(
44+
query: new \Spameri\ElasticQuery\Query\MatchAll(),
45+
source: '_score',
46+
);
47+
48+
\Tester\Assert::same('script_score_match_all', $ss->key());
49+
}
50+
51+
52+
public function tearDown(): void
53+
{
54+
$ch = \curl_init();
55+
\curl_setopt($ch, \CURLOPT_URL, \ELASTICSEARCH_HOST . '/' . self::INDEX);
56+
\curl_setopt($ch, \CURLOPT_RETURNTRANSFER, 1);
57+
\curl_setopt($ch, \CURLOPT_CUSTOMREQUEST, 'DELETE');
58+
\curl_setopt($ch, \CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
59+
60+
\curl_exec($ch);
61+
}
62+
63+
}
64+
65+
(new ScriptScore())->run();

0 commit comments

Comments
 (0)