1313use Illuminate \Support \Facades \Log ;
1414use OpenSearch \Client ;
1515use OpenSearch \ClientBuilder ;
16- use OpenSearch \Helper \Iterators \SearchHitIterator ;
17- use OpenSearch \Helper \Iterators \SearchResponseIterator ;
1816use OpenSearch \Namespaces \IndicesNamespace ;
1917use PDPhilip \Elasticsearch \Traits \HasOptions ;
2018use 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