From 7ca93b3594e0f561b85adacf76ff9e8e6a5cbd90 Mon Sep 17 00:00:00 2001 From: Bogdan Date: Thu, 12 Nov 2026 01:43:45 +0100 Subject: [PATCH] fix(Database): reset pretend mode in BaseConnection::prepare() on exception BaseConnection::prepare() enables pretend mode while the user closure builds the query. If the closure threw, the following pretend(false) never ran, leaving the connection in pretend mode. Every later query on the same connection was then silently discarded instead of being executed, which is especially harmful in long-lived processes such as Worker Mode. Wrap the closure call in try/finally so pretend mode is always reset, and add a regression test covering the throwing closure. --- system/Database/BaseConnection.php | 8 +++++--- tests/system/Database/Live/PretendTest.php | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/system/Database/BaseConnection.php b/system/Database/BaseConnection.php index d025453f74ba..9060931d4c55 100644 --- a/system/Database/BaseConnection.php +++ b/system/Database/BaseConnection.php @@ -1188,9 +1188,11 @@ public function prepare(Closure $func, array $options = []) $this->pretend(); - $sql = $func($this); - - $this->pretend(false); + try { + $sql = $func($this); + } finally { + $this->pretend(false); + } if ($sql instanceof QueryInterface) { $sql = $sql->getOriginalQuery(); diff --git a/tests/system/Database/Live/PretendTest.php b/tests/system/Database/Live/PretendTest.php index b3e6506c1be6..c0548cc22f73 100644 --- a/tests/system/Database/Live/PretendTest.php +++ b/tests/system/Database/Live/PretendTest.php @@ -17,6 +17,7 @@ use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\DatabaseTestTrait; use PHPUnit\Framework\Attributes\Group; +use RuntimeException; /** * @internal @@ -46,4 +47,23 @@ public function testPretendReturnsQueryObject(): void $this->assertInstanceOf(Query::class, $result); } + + public function testPrepareResetsPretendWhenClosureThrowsException(): void + { + $this->db->pretend(false); + + $invoked = false; + + try { + $this->db->prepare(static function () use (&$invoked): never { + $invoked = true; + + throw new RuntimeException('Error in prepare closure'); + }); + } catch (RuntimeException) { + } + + $this->assertTrue($invoked); + $this->assertFalse($this->db->pretend); + } }