diff --git a/app/Config/Database.php b/app/Config/Database.php index 7a1fd21e8d10..1b4c66e52e65 100644 --- a/app/Config/Database.php +++ b/app/Config/Database.php @@ -43,6 +43,7 @@ class Database extends Config 'failover' => [], 'port' => 3306, 'numberNative' => false, + 'foundRows' => false, 'dateFormat' => [ 'date' => 'Y-m-d', 'datetime' => 'Y-m-d H:i:s', diff --git a/phpstan-baseline.php b/phpstan-baseline.php index 6d13172e854d..4b518a56a00a 100644 --- a/phpstan-baseline.php +++ b/phpstan-baseline.php @@ -13339,6 +13339,12 @@ 'count' => 2, 'path' => __DIR__ . '/tests/system/Database/Live/MySQLi/NumberNativeTest.php', ]; +$ignoreErrors[] = [ + // identifier: property.notFound + 'message' => '#^Access to an undefined property CodeIgniter\\\\Database\\\\BaseConnection\\:\\:\\$foundRows\\.$#', + 'count' => 2, + 'path' => __DIR__ . '/tests/system/Database/Live/MySQLi/FoundRowsTest.php', +]; $ignoreErrors[] = [ // identifier: missingType.property 'message' => '#^Property CodeIgniter\\\\Database\\\\Live\\\\MySQLi\\\\NumberNativeTest\\:\\:\\$tests has no type specified\\.$#', diff --git a/system/Database/MySQLi/Connection.php b/system/Database/MySQLi/Connection.php index cc7d5e2a1612..a0d02ccbfb4d 100644 --- a/system/Database/MySQLi/Connection.php +++ b/system/Database/MySQLi/Connection.php @@ -81,6 +81,16 @@ class Connection extends BaseConnection */ public $numberNative = false; + /** + * Use MYSQLI_CLIENT_FOUND_ROWS + * + * Whether affectedRows() should return number of rows found, + * or number of rows changed, after an UPDATE query. + * + * @var bool + */ + public $foundRows = false; + /** * Connect to the database. * @@ -182,6 +192,10 @@ public function connect(bool $persistent = false) $clientFlags += MYSQLI_CLIENT_SSL; } + if ($this->foundRows) { + $clientFlags += MYSQLI_CLIENT_FOUND_ROWS; + } + try { if ($this->mysqli->real_connect( $hostname, diff --git a/tests/system/Database/Live/MySQLi/FoundRowsTest.php b/tests/system/Database/Live/MySQLi/FoundRowsTest.php new file mode 100644 index 000000000000..d5b88b49af28 --- /dev/null +++ b/tests/system/Database/Live/MySQLi/FoundRowsTest.php @@ -0,0 +1,166 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Database\Live\MySQLi; + +use CodeIgniter\Test\CIUnitTestCase; +use CodeIgniter\Test\DatabaseTestTrait; +use Config\Database; +use PHPUnit\Framework\Attributes\Group; +use Tests\Support\Database\Seeds\CITestSeeder; + +/** + * @internal + */ +#[Group('DatabaseLive')] +final class FoundRowsTest extends CIUnitTestCase +{ + use DatabaseTestTrait; + + /** + * Database config for tests + * + * @var array + */ + private $tests; + + protected $refresh = true; + protected $seed = CITestSeeder::class; + + protected function setUp(): void + { + $config = config('Database'); + + $this->tests = $config->tests; + + if ($this->tests['DBDriver'] !== 'MySQLi') { + $this->markTestSkipped('Only MySQLi can complete this test.'); + } + + parent::setUp(); + } + + public function testEnableFoundRows(): void + { + $this->tests['foundRows'] = true; + + $db1 = Database::connect($this->tests); + + $this->assertTrue($db1->foundRows); + } + + public function testDisableFoundRows(): void + { + $this->tests['foundRows'] = false; + + $db1 = Database::connect($this->tests); + + $this->assertFalse($db1->foundRows); + } + + public function testAffectedRowsAfterEnableFoundRowsWithNoChange(): void + { + $this->tests['foundRows'] = true; + + $db1 = Database::connect($this->tests); + + $db1->table('db_user') + ->set('country', 'US') + ->where('country', 'US') + ->update(); + + $affectedRows = $db1->affectedRows(); + + $this->assertSame($affectedRows, 2); + } + + public function testAffectedRowsAfterDisableFoundRowsWithNoChange(): void + { + $this->tests['foundRows'] = false; + + $db1 = Database::connect($this->tests); + + $db1->table('db_user') + ->set('country', 'US') + ->where('country', 'US') + ->update(); + + $affectedRows = $db1->affectedRows(); + + $this->assertSame($affectedRows, 0); + } + + public function testAffectedRowsAfterEnableFoundRowsWithChange(): void + { + $this->tests['foundRows'] = true; + + $db1 = Database::connect($this->tests); + + $db1->table('db_user') + ->set('country', 'NZ') + ->where('country', 'US') + ->update(); + + $affectedRows = $db1->affectedRows(); + + $this->assertSame($affectedRows, 2); + } + + public function testAffectedRowsAfterDisableFoundRowsWithChange(): void + { + $this->tests['foundRows'] = false; + + $db1 = Database::connect($this->tests); + + $db1->table('db_user') + ->set('country', 'NZ') + ->where('country', 'US') + ->update(); + + $affectedRows = $db1->affectedRows(); + + $this->assertSame($affectedRows, 2); + } + + public function testAffectedRowsAfterEnableFoundRowsWithPartialChange(): void + { + $this->tests['foundRows'] = true; + + $db1 = Database::connect($this->tests); + + $db1->table('db_user') + ->set('name', 'Derek Jones') + ->where('country', 'US') + ->update(); + + $affectedRows = $db1->affectedRows(); + + $this->assertSame($affectedRows, 2); + } + + public function testAffectedRowsAfterDisableFoundRowsWithPartialChange(): void + { + $this->tests['foundRows'] = false; + + $db1 = Database::connect($this->tests); + + $db1->table('db_user') + ->set('name', 'Derek Jones') + ->where('country', 'US') + ->update(); + + $affectedRows = $db1->affectedRows(); + + $this->assertSame($affectedRows, 1); + } +} diff --git a/user_guide_src/source/changelogs/v4.6.0.rst b/user_guide_src/source/changelogs/v4.6.0.rst index 4de1f8640248..f76902fc3588 100644 --- a/user_guide_src/source/changelogs/v4.6.0.rst +++ b/user_guide_src/source/changelogs/v4.6.0.rst @@ -133,6 +133,8 @@ Forge Others ------ +- Added a new configuration ``foundRows`` for MySQLi to use ``MYSQLI_CLIENT_FOUND_ROWS``. + Model ===== diff --git a/user_guide_src/source/database/configuration.rst b/user_guide_src/source/database/configuration.rst index 86155dea0803..b42317469038 100644 --- a/user_guide_src/source/database/configuration.rst +++ b/user_guide_src/source/database/configuration.rst @@ -179,6 +179,7 @@ Description of Values To enforce Foreign Key constraint, set this config item to true. **busyTimeout** (``SQLite3`` only) milliseconds (int) - Sleeps for a specified amount of time when a table is locked. **numberNative** (``MySQLi`` only) true/false (boolean) - Whether to enable MYSQLI_OPT_INT_AND_FLOAT_NATIVE. +**foundRows** (``MySQLi`` only) true/false (boolean) - Whether to enable MYSQLI_CLIENT_FOUND_ROWS. **dateFormat** The default date/time formats as PHP's `DateTime format`_. * ``date`` - date format * ``datetime`` - date and time format