From 41a039c048d5534234505e395a15130d41964d39 Mon Sep 17 00:00:00 2001 From: edalzell Date: Sun, 25 Oct 2026 16:31:34 -0700 Subject: [PATCH 1/2] Write the Comb index once when updating on a sync queue --- src/Search/Comb/Index.php | 25 +++++++++++++ tests/Search/CombIndexTest.php | 65 ++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/src/Search/Comb/Index.php b/src/Search/Comb/Index.php index 9126b35fccb..2a7bf8af61b 100644 --- a/src/Search/Comb/Index.php +++ b/src/Search/Comb/Index.php @@ -14,6 +14,8 @@ class Index extends BaseIndex { + protected $pending; + public function search($query) { return (new Query($this))->query($query); @@ -107,6 +109,12 @@ public function delete($document) public function insertDocuments(Documents $documents) { + if (! is_null($this->pending)) { + $this->pending = $documents->union($this->pending); + + return; + } + try { $data = $this->data(); } catch (IndexNotFoundException $e) { @@ -116,6 +124,23 @@ public function insertDocuments(Documents $documents) $this->save($documents->union($data)); } + public function update() + { + $this->pending = collect(); + + try { + parent::update(); + } finally { + [$pending, $this->pending] = [$this->pending, null]; + } + + if ($pending->isNotEmpty()) { + $this->save($pending); + } + + return $this; + } + public function deleteIndex() { File::delete($this->path()); diff --git a/tests/Search/CombIndexTest.php b/tests/Search/CombIndexTest.php index fa12fb837ac..2f46bbab8a8 100644 --- a/tests/Search/CombIndexTest.php +++ b/tests/Search/CombIndexTest.php @@ -2,11 +2,18 @@ namespace Tests\Search; +use Facades\Tests\Factories\EntryFactory; use Illuminate\Contracts\Filesystem\Filesystem; +use Illuminate\Filesystem\Filesystem as Files; +use Illuminate\Support\Facades\Bus; use Mockery; use PHPUnit\Framework\Attributes\Test; use Statamic\Contracts\Search\Searchable; +use Statamic\Facades\Collection; +use Statamic\Facades\Search; use Statamic\Search\Comb\Index; +use Statamic\Search\Documents; +use Tests\PreventSavingStacheItemsToDisk; use Tests\TestCase; class CombIndexTest extends TestCase @@ -14,6 +21,7 @@ class CombIndexTest extends TestCase use IndexTests { tearDown as protected indexTestsTearDown; } + use PreventSavingStacheItemsToDisk; private $fs; @@ -114,6 +122,63 @@ public function delete_rewrites_the_index_when_the_reference_is_present() ); } + #[Test] + public function updating_on_a_sync_queue_writes_the_index_file_once() + { + $files = $this->prepareUpdate(250); + + Search::index('test')->update(); + + $files->shouldHaveReceived('put')->once(); + $this->assertCount(250, json_decode(file_get_contents($this->tmpDir.'/test.json'), true)); + } + + #[Test] + public function updating_on_a_faked_bus_deletes_the_index_and_writes_nothing() + { + Bus::fake(); + $files = $this->prepareUpdate(3); + + Search::index('test')->update(); + + $files->shouldNotHaveReceived('put'); + $this->assertFileDoesNotExist($this->tmpDir.'/test.json'); + } + + #[Test] + public function inserting_outside_an_update_writes_immediately() + { + $files = $this->prepareUpdate(0); + + Search::index('test')->insertDocuments(new Documents(['entry::a' => ['title' => 'Foo']])); + Search::index('test')->insertDocuments(new Documents(['entry::b' => ['title' => 'Bar']])); + + $files->shouldHaveReceived('put')->twice(); + $this->assertCount(3, json_decode(file_get_contents($this->tmpDir.'/test.json'), true)); + } + + private function prepareUpdate(int $entries) + { + $this->createIndexFile(['entry::stale' => ['title' => 'Stale']]); + + config([ + 'statamic.search.chunk_size' => 100, + 'statamic.search.drivers.local.path' => $this->tmpDir, + 'statamic.search.indexes.test' => ['driver' => 'local', 'searchables' => 'collection:blog', 'fields' => ['title']], + ]); + + Collection::make('blog')->save(); + + for ($i = 1; $i <= $entries; $i++) { + EntryFactory::collection('blog')->id("entry-$i")->slug("entry-$i")->data(['title' => "Entry $i"])->create(); + } + + $files = Mockery::mock(Files::class)->makePartial(); + $this->instance('files', $files); + + return $files; + } + private function createIndexFile(array $data): string { $this->tmpDir = sys_get_temp_dir().'/statamic_comb_test_'.uniqid(); From f480fe3aa5fb0d1ad640a95ac046cb8699005e49 Mon Sep 17 00:00:00 2001 From: edalzell Date: Sun, 25 Oct 2026 17:17:02 -0700 Subject: [PATCH 2/2] Typehint the pending documents in Comb index --- src/Search/Comb/Index.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Search/Comb/Index.php b/src/Search/Comb/Index.php index 2a7bf8af61b..b1aa7d48078 100644 --- a/src/Search/Comb/Index.php +++ b/src/Search/Comb/Index.php @@ -2,6 +2,7 @@ namespace Statamic\Search\Comb; +use Illuminate\Support\Collection; use Statamic\Facades\File; use Statamic\Search\Comb\Exceptions\NoQuery; use Statamic\Search\Comb\Exceptions\NoResultsFound; @@ -14,7 +15,7 @@ class Index extends BaseIndex { - protected $pending; + protected ?Collection $pending = null; public function search($query) {