Skip to content

Commit f354b6b

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

3 files changed

Lines changed: 137 additions & 0 deletions

File tree

doc/02-query-objects.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,20 @@ new \Spameri\ElasticQuery\Query\RankFeature(
532532
);
533533
```
534534

535+
##### DistanceFeature Query
536+
Boost documents whose date or geo_point is close to an origin.
537+
- Class: `\Spameri\ElasticQuery\Query\DistanceFeature`
538+
- [Documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-distance-feature-query.html)
539+
- [Implementation](https://github.com/Spameri/ElasticQuery/blob/master/src/Query/DistanceFeature.php)
540+
541+
```php
542+
new \Spameri\ElasticQuery\Query\DistanceFeature(
543+
field: 'production_date',
544+
origin: 'now',
545+
pivot: '7d',
546+
);
547+
```
548+
535549
##### Script Query
536550
Filter documents with a Painless boolean script.
537551
- Class: `\Spameri\ElasticQuery\Query\Script`

src/Query/DistanceFeature.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
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-distance-feature-query.html
9+
*/
10+
class DistanceFeature implements \Spameri\ElasticQuery\Query\LeafQueryInterface
11+
{
12+
13+
/**
14+
* @param array<int, float>|string $origin Either [lat, lon] for geo_point or a date string for date.
15+
*/
16+
public function __construct(
17+
private string $field,
18+
private array|string $origin,
19+
private string $pivot,
20+
private float $boost = 1.0,
21+
)
22+
{
23+
}
24+
25+
26+
public function key(): string
27+
{
28+
return 'distance_feature_' . $this->field;
29+
}
30+
31+
32+
/**
33+
* @return array<string, array<string, mixed>>
34+
*/
35+
public function toArray(): array
36+
{
37+
return [
38+
'distance_feature' => [
39+
'field' => $this->field,
40+
'origin' => $this->origin,
41+
'pivot' => $this->pivot,
42+
'boost' => $this->boost,
43+
],
44+
];
45+
}
46+
47+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace SpameriTests\ElasticQuery\Query;
4+
5+
require_once __DIR__ . '/../../bootstrap.php';
6+
7+
8+
class DistanceFeature extends \Tester\TestCase
9+
{
10+
11+
private const INDEX = 'spameri_test_query_distance_feature';
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 testToArrayDate(): void
27+
{
28+
$df = new \Spameri\ElasticQuery\Query\DistanceFeature(
29+
field: 'production_date',
30+
origin: 'now',
31+
pivot: '7d',
32+
);
33+
34+
$array = $df->toArray();
35+
36+
\Tester\Assert::same('now', $array['distance_feature']['origin']);
37+
\Tester\Assert::same('7d', $array['distance_feature']['pivot']);
38+
}
39+
40+
41+
public function testToArrayGeo(): void
42+
{
43+
$df = new \Spameri\ElasticQuery\Query\DistanceFeature(
44+
field: 'location',
45+
origin: [50.0, 14.4],
46+
pivot: '1000m',
47+
);
48+
49+
$array = $df->toArray();
50+
51+
\Tester\Assert::same([50.0, 14.4], $array['distance_feature']['origin']);
52+
}
53+
54+
55+
public function testKey(): void
56+
{
57+
$df = new \Spameri\ElasticQuery\Query\DistanceFeature('location', [0, 0], '1km');
58+
59+
\Tester\Assert::same('distance_feature_location', $df->key());
60+
}
61+
62+
63+
public function tearDown(): void
64+
{
65+
$ch = \curl_init();
66+
\curl_setopt($ch, \CURLOPT_URL, \ELASTICSEARCH_HOST . '/' . self::INDEX);
67+
\curl_setopt($ch, \CURLOPT_RETURNTRANSFER, 1);
68+
\curl_setopt($ch, \CURLOPT_CUSTOMREQUEST, 'DELETE');
69+
\curl_setopt($ch, \CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
70+
71+
\curl_exec($ch);
72+
}
73+
74+
}
75+
76+
(new DistanceFeature())->run();

0 commit comments

Comments
 (0)