Skip to content

Commit 68ae9ac

Browse files
committed
OpenSearch 3.x support - the scroll id belongs in the body, not the url
1 parent 3295ae7 commit 68ae9ac

3 files changed

Lines changed: 67 additions & 57 deletions

File tree

.github/workflows/run-tests.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ jobs:
1616
matrix:
1717
php: [ 8.3, 8.4 ]
1818
laravel: [ 13.*, 12.*, 11.* ]
19+
opensearch: [ 2.19.6, 3.8.0 ]
1920
include:
2021
- laravel: 13.*
2122
testbench: 11.*
@@ -24,7 +25,7 @@ jobs:
2425
- laravel: 11.*
2526
testbench: 9.*
2627

27-
name: P${{ matrix.php }} - L${{ matrix.laravel }}
28+
name: P${{ matrix.php }} - L${{ matrix.laravel }} - OS${{ matrix.opensearch }}
2829

2930
env:
3031
BROADCAST_DRIVER: log
@@ -40,7 +41,10 @@ jobs:
4041

4142
services:
4243
opensearch:
43-
image: opensearchproject/opensearch:latest
44+
# Always pinned, never ":latest". A silent major bump on the server side
45+
# once turned every job red at the same time with no package change; a
46+
# new server line should be a deliberate row here instead.
47+
image: opensearchproject/opensearch:${{ matrix.opensearch }}
4448
env:
4549
discovery.type: single-node
4650
DISABLE_SECURITY_PLUGIN: "true"

phpstan-baseline.neon

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,5 @@
11
parameters:
22
ignoreErrors:
3-
-
4-
message: '''
5-
#^Call to method __construct\(\) of deprecated class OpenSearch\\Helper\\Iterators\\SearchHitIterator\:
6-
in 2\.4\.0 and will be removed in 3\.0\.0\.$#
7-
'''
8-
identifier: method.deprecatedClass
9-
count: 1
10-
path: src/Connection.php
11-
12-
-
13-
message: '''
14-
#^Call to method __construct\(\) of deprecated class OpenSearch\\Helper\\Iterators\\SearchResponseIterator\:
15-
in 2\.4\.0 and will be removed in 3\.0\.0\.$#
16-
'''
17-
identifier: method.deprecatedClass
18-
count: 2
19-
path: src/Connection.php
20-
213
-
224
message: '''
235
#^Call to method build\(\) of deprecated class OpenSearch\\ClientBuilder\:
@@ -126,24 +108,6 @@ parameters:
126108
count: 1
127109
path: src/Connection.php
128110

129-
-
130-
message: '''
131-
#^Instantiation of deprecated class OpenSearch\\Helper\\Iterators\\SearchHitIterator\:
132-
in 2\.4\.0 and will be removed in 3\.0\.0\.$#
133-
'''
134-
identifier: new.deprecatedClass
135-
count: 1
136-
path: src/Connection.php
137-
138-
-
139-
message: '''
140-
#^Instantiation of deprecated class OpenSearch\\Helper\\Iterators\\SearchResponseIterator\:
141-
in 2\.4\.0 and will be removed in 3\.0\.0\.$#
142-
'''
143-
identifier: new.deprecatedClass
144-
count: 2
145-
path: src/Connection.php
146-
147111
-
148112
message: '''
149113
#^Parameter \$builder of method PDPhilip\\OpenSearch\\Connection\:\:_buildAuth\(\) has typehint with deprecated class OpenSearch\\ClientBuilder\:

src/Connection.php

Lines changed: 61 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313
use Illuminate\Support\Facades\Log;
1414
use OpenSearch\Client;
1515
use OpenSearch\ClientBuilder;
16-
use OpenSearch\Helper\Iterators\SearchHitIterator;
17-
use OpenSearch\Helper\Iterators\SearchResponseIterator;
1816
use OpenSearch\Namespaces\IndicesNamespace;
1917
use PDPhilip\Elasticsearch\Traits\HasOptions;
2018
use PDPhilip\OpenSearch\Exceptions\BulkInsertQueryException;
@@ -377,16 +375,30 @@ public function statement($query, $bindings = [], ?Blueprint $blueprint = null):
377375
public function searchResponseIterator($query, $scrollTimeout = '30s', $size = 100): Generator
378376
{
379377

380-
$scrollParams = [
378+
$client = $this->openClient();
379+
380+
$response = $client->search([
381381
'scroll' => $scrollTimeout,
382382
'size' => $size, // Number of results per shard
383383
'index' => $query['index'],
384384
'body' => $query['body'],
385-
];
385+
]);
386+
387+
$scrollId = $response['_scroll_id'] ?? null;
388+
389+
try {
390+
while (! empty($response['hits']['hits'])) {
391+
yield $response;
386392

387-
$pages = new SearchResponseIterator($this->connection, $scrollParams);
388-
foreach ($pages as $page) {
389-
yield $page;
393+
$response = $client->scroll([
394+
'scroll' => $scrollTimeout,
395+
'body' => ['scroll_id' => $scrollId],
396+
]);
397+
398+
$scrollId = $response['_scroll_id'] ?? $scrollId;
399+
}
400+
} finally {
401+
$this->clearScroll($client, $scrollId);
390402
}
391403
}
392404

@@ -406,27 +418,57 @@ public function cursor($query, $bindings = [], $useReadPdo = false, $scrollTimeo
406418
// We want to scroll by 1000 row chunks
407419
$query['body']['size'] = 1000;
408420

409-
$scrollParams = [
421+
$client = $this->openClient();
422+
423+
$response = $client->search([
410424
'scroll' => $scrollTimeout,
411425
'index' => $query['index'],
412426
'body' => $query['body'],
413-
];
427+
]);
414428

429+
$scrollId = $response['_scroll_id'] ?? null;
415430
$count = 0;
416-
$pages = new SearchResponseIterator($this->openClient(), $scrollParams);
417-
$hits = new SearchHitIterator($pages);
418431

419-
foreach ($hits as $hit) {
420-
$count++;
421-
if ($count > $limit) {
422-
break;
432+
try {
433+
while (! empty($response['hits']['hits'])) {
434+
foreach ($response['hits']['hits'] as $hit) {
435+
$count++;
436+
if ($count > $limit) {
437+
return;
438+
}
439+
yield $hit;
440+
}
441+
442+
$response = $client->scroll([
443+
'scroll' => $scrollTimeout,
444+
'body' => ['scroll_id' => $scrollId],
445+
]);
446+
447+
$scrollId = $response['_scroll_id'] ?? $scrollId;
423448
}
424-
yield $hit;
449+
} finally {
450+
$this->clearScroll($client, $scrollId);
451+
}
452+
}
453+
454+
/**
455+
* Release a scroll context.
456+
*
457+
* The id goes in the body, never the url. The client rawurlencodes a path
458+
* scroll id and OpenSearch 3.x rejects the result ("Cannot parse scroll
459+
* id"), which is what breaks every scroll on 3.x when the id happens to
460+
* contain a character that needs encoding.
461+
*/
462+
protected function clearScroll($client, ?string $scrollId): void
463+
{
464+
if ($scrollId === null) {
465+
return;
425466
}
426467

427-
return (function () {
428-
yield;
429-
})();
468+
$client->clearScroll([
469+
'body' => ['scroll_id' => [$scrollId]],
470+
'client' => ['ignore' => 404],
471+
]);
430472
}
431473

432474
/**

0 commit comments

Comments
 (0)