From 3ede47dd749e585ab93ccff0132130f98ae6b8f1 Mon Sep 17 00:00:00 2001 From: edalzell Date: Wed, 2 Sep 2026 15:43:06 -0700 Subject: [PATCH 1/4] Add --sync option to search:update Queueing stays the default; --sync runs indexing inline so deploy scripts wait until the index is ready. --- src/Search/Commands/Update.php | 7 ++++++- src/Search/Index.php | 22 +++++++++++++++----- tests/Search/Commands/UpdateTest.php | 31 ++++++++++++++++++++++++++++ tests/Search/IndexTests.php | 15 ++++++++++++++ 4 files changed, 69 insertions(+), 6 deletions(-) diff --git a/src/Search/Commands/Update.php b/src/Search/Commands/Update.php index 349d0066040..6420eb5e53f 100644 --- a/src/Search/Commands/Update.php +++ b/src/Search/Commands/Update.php @@ -15,7 +15,8 @@ class Update extends Command protected $signature = 'statamic:search:update { index? : The handle of the index to update. } - { --all : Update all indexes. }'; + { --all : Update all indexes. } + { --sync : Index the documents immediately instead of queueing them. }'; protected $description = 'Update a search index'; @@ -24,6 +25,10 @@ class Update extends Command public function handle() { foreach ($this->getIndexes() as $index) { + if ($this->option('sync')) { + $index->withoutQueue(); + } + $index->update(); SearchIndexUpdated::dispatch($index); diff --git a/src/Search/Index.php b/src/Search/Index.php index c4422dbb7f5..bf4062b6c92 100644 --- a/src/Search/Index.php +++ b/src/Search/Index.php @@ -12,6 +12,7 @@ abstract class Index protected $handle; protected $locale; protected $config; + protected bool $shouldQueue = true; protected static ?Closure $nameCallback = null; abstract public function search($query); @@ -98,11 +99,22 @@ public function insertMultiple($documents) { $documents ->chunk(config('statamic.search.chunk_size')) - ->each(fn ($documents) => InsertMultipleJob::dispatch( - name: $this->handle, - locale: $this->locale, - documents: $documents - )); + ->each(function ($documents) { + $job = new InsertMultipleJob( + name: $this->handle, + locale: $this->locale, + documents: $documents + ); + + $this->shouldQueue ? dispatch($job) : dispatch_sync($job); + }); + + return $this; + } + + public function withoutQueue() + { + $this->shouldQueue = false; return $this; } diff --git a/tests/Search/Commands/UpdateTest.php b/tests/Search/Commands/UpdateTest.php index b165ae00651..eadfbd1b57a 100644 --- a/tests/Search/Commands/UpdateTest.php +++ b/tests/Search/Commands/UpdateTest.php @@ -2,7 +2,9 @@ namespace Tests\Search\Commands; +use Mockery; use PHPUnit\Framework\Attributes\Test; +use Statamic\Facades\Search; use Statamic\Search\Commands\Update; use Statamic\Search\Index; use Tests\TestCase; @@ -17,6 +19,17 @@ public function tearDown(): void parent::tearDown(); } + private function fakeIndex() + { + $index = Mockery::mock(Index::class); + $index->shouldReceive('name')->andReturn('test'); + $index->shouldReceive('update')->once(); + + Search::shouldReceive('indexes')->andReturn(collect(['test' => $index])); + + return $index; + } + private function setUpIndexes() { $this->setSites([ @@ -60,6 +73,24 @@ public function it_updates_the_localized_indexes_when_the_names_have_been_custom ->assertExitCode(0); } + #[Test] + public function it_queues_the_indexing_by_default() + { + $index = $this->fakeIndex(); + $index->shouldReceive('withoutQueue')->never(); + + $this->artisan(Update::class, ['index' => 'test'])->assertExitCode(0); + } + + #[Test] + public function it_indexes_immediately_when_using_the_sync_option() + { + $index = $this->fakeIndex(); + $index->shouldReceive('withoutQueue')->once()->andReturnSelf(); + + $this->artisan(Update::class, ['index' => 'test', '--sync' => true])->assertExitCode(0); + } + #[Test] public function it_errors_when_the_index_doesnt_exist() { diff --git a/tests/Search/IndexTests.php b/tests/Search/IndexTests.php index 24e8983d94b..4d5b8902e2c 100644 --- a/tests/Search/IndexTests.php +++ b/tests/Search/IndexTests.php @@ -70,4 +70,19 @@ public function it_dispatches_the_insert_job_with_the_configured_handle_not_the_ fn (InsertMultipleJob $job) => $job->name === 'test' && $job->locale === 'en' ); } + + #[Test] + public function it_dispatches_the_insert_job_synchronously_when_queueing_is_disabled() + { + Bus::fake(); + + $index = $this->getIndex('test', [], 'en'); + + $index->withoutQueue()->insertMultiple(collect(['foo::bar'])); + + Bus::assertDispatchedSync( + InsertMultipleJob::class, + fn (InsertMultipleJob $job) => $job->name === 'test' && $job->locale === 'en' + ); + } } From c654cef3b2c8f263f2ef4f2c95205fee403c0eec Mon Sep 17 00:00:00 2001 From: edalzell Date: Thu, 3 Sep 2026 15:06:27 -0700 Subject: [PATCH 2/4] Replace --sync with --queue connection override Lets you pick the indexing connection per run, including --queue=sync so deploys wait until the index is ready. --- src/Search/Commands/Update.php | 6 +++--- src/Search/Index.php | 12 ++++++++---- tests/Search/Commands/UpdateTest.php | 10 +++++----- tests/Search/IndexTests.php | 8 ++++---- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/Search/Commands/Update.php b/src/Search/Commands/Update.php index 6420eb5e53f..42e409c0b26 100644 --- a/src/Search/Commands/Update.php +++ b/src/Search/Commands/Update.php @@ -16,7 +16,7 @@ class Update extends Command protected $signature = 'statamic:search:update { index? : The handle of the index to update. } { --all : Update all indexes. } - { --sync : Index the documents immediately instead of queueing them. }'; + { --queue= : The queue connection used for indexing jobs. }'; protected $description = 'Update a search index'; @@ -25,8 +25,8 @@ class Update extends Command public function handle() { foreach ($this->getIndexes() as $index) { - if ($this->option('sync')) { - $index->withoutQueue(); + if ($queue = $this->option('queue')) { + $index->onConnection($queue); } $index->update(); diff --git a/src/Search/Index.php b/src/Search/Index.php index bf4062b6c92..65e4d18a620 100644 --- a/src/Search/Index.php +++ b/src/Search/Index.php @@ -12,7 +12,7 @@ abstract class Index protected $handle; protected $locale; protected $config; - protected bool $shouldQueue = true; + protected ?string $queueConnection = null; protected static ?Closure $nameCallback = null; abstract public function search($query); @@ -106,15 +106,19 @@ public function insertMultiple($documents) documents: $documents ); - $this->shouldQueue ? dispatch($job) : dispatch_sync($job); + if ($this->queueConnection) { + $job->onConnection($this->queueConnection); + } + + dispatch($job); }); return $this; } - public function withoutQueue() + public function onConnection(string $connection) { - $this->shouldQueue = false; + $this->queueConnection = $connection; return $this; } diff --git a/tests/Search/Commands/UpdateTest.php b/tests/Search/Commands/UpdateTest.php index eadfbd1b57a..c5df93bfa71 100644 --- a/tests/Search/Commands/UpdateTest.php +++ b/tests/Search/Commands/UpdateTest.php @@ -74,21 +74,21 @@ public function it_updates_the_localized_indexes_when_the_names_have_been_custom } #[Test] - public function it_queues_the_indexing_by_default() + public function it_uses_the_configured_queue_connection_by_default() { $index = $this->fakeIndex(); - $index->shouldReceive('withoutQueue')->never(); + $index->shouldReceive('onConnection')->never(); $this->artisan(Update::class, ['index' => 'test'])->assertExitCode(0); } #[Test] - public function it_indexes_immediately_when_using_the_sync_option() + public function it_uses_the_queue_connection_from_the_queue_option() { $index = $this->fakeIndex(); - $index->shouldReceive('withoutQueue')->once()->andReturnSelf(); + $index->shouldReceive('onConnection')->once()->with('sync')->andReturnSelf(); - $this->artisan(Update::class, ['index' => 'test', '--sync' => true])->assertExitCode(0); + $this->artisan(Update::class, ['index' => 'test', '--queue' => 'sync'])->assertExitCode(0); } #[Test] diff --git a/tests/Search/IndexTests.php b/tests/Search/IndexTests.php index 4d5b8902e2c..6e1a93059f6 100644 --- a/tests/Search/IndexTests.php +++ b/tests/Search/IndexTests.php @@ -72,17 +72,17 @@ public function it_dispatches_the_insert_job_with_the_configured_handle_not_the_ } #[Test] - public function it_dispatches_the_insert_job_synchronously_when_queueing_is_disabled() + public function it_dispatches_the_insert_job_on_the_given_queue_connection() { Bus::fake(); $index = $this->getIndex('test', [], 'en'); - $index->withoutQueue()->insertMultiple(collect(['foo::bar'])); + $index->onConnection('sync')->insertMultiple(collect(['foo::bar'])); - Bus::assertDispatchedSync( + Bus::assertDispatched( InsertMultipleJob::class, - fn (InsertMultipleJob $job) => $job->name === 'test' && $job->locale === 'en' + fn (InsertMultipleJob $job) => $job->name === 'test' && $job->locale === 'en' && $job->connection === 'sync' ); } } From 62f790513e945884d356c0393a231e2eeeaa2b74 Mon Sep 17 00:00:00 2001 From: edalzell Date: Thu, 3 Sep 2026 15:09:18 -0700 Subject: [PATCH 3/4] Rename --queue to --connection sync is a queue connection, not a queue name. --- src/Search/Commands/Update.php | 6 +++--- tests/Search/Commands/UpdateTest.php | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Search/Commands/Update.php b/src/Search/Commands/Update.php index 42e409c0b26..34d92bb0536 100644 --- a/src/Search/Commands/Update.php +++ b/src/Search/Commands/Update.php @@ -16,7 +16,7 @@ class Update extends Command protected $signature = 'statamic:search:update { index? : The handle of the index to update. } { --all : Update all indexes. } - { --queue= : The queue connection used for indexing jobs. }'; + { --connection= : The queue connection used for indexing jobs. }'; protected $description = 'Update a search index'; @@ -25,8 +25,8 @@ class Update extends Command public function handle() { foreach ($this->getIndexes() as $index) { - if ($queue = $this->option('queue')) { - $index->onConnection($queue); + if ($connection = $this->option('connection')) { + $index->onConnection($connection); } $index->update(); diff --git a/tests/Search/Commands/UpdateTest.php b/tests/Search/Commands/UpdateTest.php index c5df93bfa71..beebc76a1df 100644 --- a/tests/Search/Commands/UpdateTest.php +++ b/tests/Search/Commands/UpdateTest.php @@ -83,12 +83,12 @@ public function it_uses_the_configured_queue_connection_by_default() } #[Test] - public function it_uses_the_queue_connection_from_the_queue_option() + public function it_uses_the_queue_connection_from_the_connection_option() { $index = $this->fakeIndex(); $index->shouldReceive('onConnection')->once()->with('sync')->andReturnSelf(); - $this->artisan(Update::class, ['index' => 'test', '--queue' => 'sync'])->assertExitCode(0); + $this->artisan(Update::class, ['index' => 'test', '--connection' => 'sync'])->assertExitCode(0); } #[Test] From 90f69e51b3ed9a4a6f1ddc1a25c5f943ba452caf Mon Sep 17 00:00:00 2001 From: edalzell Date: Thu, 3 Sep 2026 15:10:17 -0700 Subject: [PATCH 4/4] Allow overriding queue name and connection --connection and --queue independently override InsertMultipleJob routing for a single search:update run. --- src/Search/Commands/Update.php | 7 ++++++- src/Search/Index.php | 12 ++++++++++++ tests/Search/Commands/UpdateTest.php | 20 ++++++++++++++++++++ tests/Search/IndexTests.php | 15 +++++++++++++++ 4 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/Search/Commands/Update.php b/src/Search/Commands/Update.php index 34d92bb0536..ca398006f0b 100644 --- a/src/Search/Commands/Update.php +++ b/src/Search/Commands/Update.php @@ -16,7 +16,8 @@ class Update extends Command protected $signature = 'statamic:search:update { index? : The handle of the index to update. } { --all : Update all indexes. } - { --connection= : The queue connection used for indexing jobs. }'; + { --connection= : The queue connection used for indexing jobs. } + { --queue= : The queue name used for indexing jobs. }'; protected $description = 'Update a search index'; @@ -29,6 +30,10 @@ public function handle() $index->onConnection($connection); } + if ($queue = $this->option('queue')) { + $index->onQueue($queue); + } + $index->update(); SearchIndexUpdated::dispatch($index); diff --git a/src/Search/Index.php b/src/Search/Index.php index 65e4d18a620..84c93d4750d 100644 --- a/src/Search/Index.php +++ b/src/Search/Index.php @@ -12,6 +12,7 @@ abstract class Index protected $handle; protected $locale; protected $config; + protected ?string $queue = null; protected ?string $queueConnection = null; protected static ?Closure $nameCallback = null; @@ -110,6 +111,10 @@ public function insertMultiple($documents) $job->onConnection($this->queueConnection); } + if ($this->queue) { + $job->onQueue($this->queue); + } + dispatch($job); }); @@ -123,6 +128,13 @@ public function onConnection(string $connection) return $this; } + public function onQueue(string $queue) + { + $this->queue = $queue; + + return $this; + } + public function fields(Searchable $searchable) { return $this->searchables()->fields($searchable); diff --git a/tests/Search/Commands/UpdateTest.php b/tests/Search/Commands/UpdateTest.php index beebc76a1df..3eb378094c0 100644 --- a/tests/Search/Commands/UpdateTest.php +++ b/tests/Search/Commands/UpdateTest.php @@ -78,6 +78,7 @@ public function it_uses_the_configured_queue_connection_by_default() { $index = $this->fakeIndex(); $index->shouldReceive('onConnection')->never(); + $index->shouldReceive('onQueue')->never(); $this->artisan(Update::class, ['index' => 'test'])->assertExitCode(0); } @@ -91,6 +92,25 @@ public function it_uses_the_queue_connection_from_the_connection_option() $this->artisan(Update::class, ['index' => 'test', '--connection' => 'sync'])->assertExitCode(0); } + #[Test] + public function it_uses_the_queue_from_the_queue_option() + { + $index = $this->fakeIndex(); + $index->shouldReceive('onQueue')->once()->with('indexing')->andReturnSelf(); + + $this->artisan(Update::class, ['index' => 'test', '--queue' => 'indexing'])->assertExitCode(0); + } + + #[Test] + public function it_uses_the_connection_and_queue_together() + { + $index = $this->fakeIndex(); + $index->shouldReceive('onConnection')->once()->with('redis')->andReturnSelf(); + $index->shouldReceive('onQueue')->once()->with('indexing')->andReturnSelf(); + + $this->artisan(Update::class, ['index' => 'test', '--connection' => 'redis', '--queue' => 'indexing'])->assertExitCode(0); + } + #[Test] public function it_errors_when_the_index_doesnt_exist() { diff --git a/tests/Search/IndexTests.php b/tests/Search/IndexTests.php index 6e1a93059f6..5ce314b75c8 100644 --- a/tests/Search/IndexTests.php +++ b/tests/Search/IndexTests.php @@ -85,4 +85,19 @@ public function it_dispatches_the_insert_job_on_the_given_queue_connection() fn (InsertMultipleJob $job) => $job->name === 'test' && $job->locale === 'en' && $job->connection === 'sync' ); } + + #[Test] + public function it_dispatches_the_insert_job_on_the_given_queue() + { + Bus::fake(); + + $index = $this->getIndex('test', [], 'en'); + + $index->onQueue('indexing')->insertMultiple(collect(['foo::bar'])); + + Bus::assertDispatched( + InsertMultipleJob::class, + fn (InsertMultipleJob $job) => $job->name === 'test' && $job->locale === 'en' && $job->queue === 'indexing' + ); + } }