From fc19b690f847e943fe56ce0898f3f78d04965625 Mon Sep 17 00:00:00 2001 From: Michal Sniatala Date: Sun, 3 Nov 2024 19:48:50 +0100 Subject: [PATCH] fix: case-insensitivity in the `like()` method when in use with accented characters (#9238) * fix: case-insensitivity in the like() method when in use with accented characters * Update system/Database/BaseBuilder.php Co-authored-by: Pooya Parsa * add more cases for tests * even more tests * fix types * set collation for sqlsrv --------- Co-authored-by: Pooya Parsa --- .github/workflows/reusable-phpunit-test.yml | 2 +- system/Database/BaseBuilder.php | 2 +- system/Database/SQLSRV/Forge.php | 8 +++---- .../20160428212500_Create_test_tables.php | 1 + .../_support/Database/Seeds/CITestSeeder.php | 20 ++++++++++++++++ tests/system/Database/Live/ForgeTest.php | 12 +++++++--- tests/system/Database/Live/GetTest.php | 2 +- tests/system/Database/Live/LikeTest.php | 24 +++++++++++++++++++ user_guide_src/source/changelogs/v4.5.6.rst | 1 + 9 files changed, 62 insertions(+), 10 deletions(-) diff --git a/.github/workflows/reusable-phpunit-test.yml b/.github/workflows/reusable-phpunit-test.yml index 84ade1d08862..9758d6eb5410 100644 --- a/.github/workflows/reusable-phpunit-test.yml +++ b/.github/workflows/reusable-phpunit-test.yml @@ -138,7 +138,7 @@ jobs: steps: - name: Create database for MSSQL Server if: ${{ inputs.db-platform == 'SQLSRV' }} - run: sqlcmd -S 127.0.0.1 -U sa -P 1Secure*Password1 -Q "CREATE DATABASE test" + run: sqlcmd -S 127.0.0.1 -U sa -P 1Secure*Password1 -Q "CREATE DATABASE test COLLATE Latin1_General_100_CS_AS_SC_UTF8" - name: Install latest ImageMagick if: ${{ contains(inputs.extra-extensions, 'imagick') }} diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index 570340dffb9b..892dd903b690 100644 --- a/system/Database/BaseBuilder.php +++ b/system/Database/BaseBuilder.php @@ -1151,7 +1151,7 @@ protected function _like($field, string $match = '', string $type = 'AND ', stri foreach ($keyValue as $k => $v) { if ($insensitiveSearch) { - $v = strtolower($v); + $v = mb_strtolower($v, 'UTF-8'); } $prefix = empty($this->{$clause}) ? $this->groupGetType('') : $this->groupGetType($type); diff --git a/system/Database/SQLSRV/Forge.php b/system/Database/SQLSRV/Forge.php index 3bfda3bfe895..d121560c9d47 100644 --- a/system/Database/SQLSRV/Forge.php +++ b/system/Database/SQLSRV/Forge.php @@ -212,10 +212,10 @@ protected function _alterTable(string $alterType, string $table, $processedField $sql = <<db->DBDriver === 'SQLSRV') { unset($dataTypeFields['type_timestamp']); + $dataTypeFields['type_text'] = ['type' => 'NVARCHAR(max)', 'null' => true]; } if ($this->db->DBDriver === 'Postgre' || $this->db->DBDriver === 'SQLSRV') { diff --git a/tests/_support/Database/Seeds/CITestSeeder.php b/tests/_support/Database/Seeds/CITestSeeder.php index f8582839cf05..45b70a7fdd19 100644 --- a/tests/_support/Database/Seeds/CITestSeeder.php +++ b/tests/_support/Database/Seeds/CITestSeeder.php @@ -86,6 +86,26 @@ public function run(): void 'key' => 'key', 'value' => 'value', ], + [ + 'key' => 'multibyte characters pl', + 'value' => 'śćźżłąęó', + ], + [ + 'key' => 'multibyte characters fa', + 'value' => 'خٌوب', + ], + [ + 'key' => 'multibyte characters bn', + 'value' => 'টাইপ', + ], + [ + 'key' => 'multibyte characters ko', + 'value' => '캐스팅', + ], + [ + 'key' => 'multibyte characters ml', + 'value' => 'ടൈപ്പ്', + ], ], 'type_test' => [ [ diff --git a/tests/system/Database/Live/ForgeTest.php b/tests/system/Database/Live/ForgeTest.php index 7fccd6b1d298..64d8dbd7e576 100644 --- a/tests/system/Database/Live/ForgeTest.php +++ b/tests/system/Database/Live/ForgeTest.php @@ -1615,9 +1615,15 @@ public function testDropKey(): void public function testAddTextColumnWithConstraint(): void { // some DBMS do not allow a constraint for type TEXT - $this->forge->addColumn('user', [ - 'text_with_constraint' => ['type' => 'text', 'constraint' => 255, 'default' => ''], - ]); + if ($this->db->DBDriver === 'SQLSRV') { + $this->forge->addColumn('user', [ + 'text_with_constraint' => ['type' => 'nvarchar(max)', 'default' => ''], + ]); + } else { + $this->forge->addColumn('user', [ + 'text_with_constraint' => ['type' => 'text', 'constraint' => 255, 'default' => ''], + ]); + } $this->assertTrue($this->db->fieldExists('text_with_constraint', 'user')); diff --git a/tests/system/Database/Live/GetTest.php b/tests/system/Database/Live/GetTest.php index 681af7344183..ce98ebf413e0 100644 --- a/tests/system/Database/Live/GetTest.php +++ b/tests/system/Database/Live/GetTest.php @@ -178,7 +178,7 @@ public function testGetFieldData(): void $this->assertSame('int', $typeTest[0]->type_name); // INTEGER AUTOINC $this->assertSame('varchar', $typeTest[1]->type_name); // VARCHAR $this->assertSame('char', $typeTest[2]->type_name); // CHAR - $this->assertSame('text', $typeTest[3]->type_name); // TEXT + $this->assertSame('nvarchar', $typeTest[3]->type_name); // TEXT $this->assertSame('smallint', $typeTest[4]->type_name); // SMALLINT $this->assertSame('int', $typeTest[5]->type_name); // INTEGER $this->assertSame('float', $typeTest[6]->type_name); // FLOAT diff --git a/tests/system/Database/Live/LikeTest.php b/tests/system/Database/Live/LikeTest.php index 5884f9d01220..9591eef72ea8 100644 --- a/tests/system/Database/Live/LikeTest.php +++ b/tests/system/Database/Live/LikeTest.php @@ -16,6 +16,7 @@ use CodeIgniter\Database\RawSql; use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\DatabaseTestTrait; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Group; use Tests\Support\Database\Seeds\CITestSeeder; @@ -75,6 +76,29 @@ public function testLikeCaseInsensitive(): void $this->assertSame('Developer', $job->name); } + #[DataProvider('provideMultibyteCharacters')] + public function testLikeCaseInsensitiveWithMultibyteCharacter(string $match, string $result): void + { + $wai = $this->db->table('without_auto_increment')->like('value', $match, 'both', null, true)->get(); + $wai = $wai->getRow(); + + $this->assertSame($result, $wai->key); + } + + /** + * @return iterable + */ + public static function provideMultibyteCharacters(): iterable + { + yield from [ + 'polish' => ['ŁĄ', 'multibyte characters pl'], + 'farsi' => ['خٌوب', 'multibyte characters fa'], + 'bengali' => ['টাইপ', 'multibyte characters bn'], + 'korean' => ['캐스팅', 'multibyte characters ko'], + 'malayalam' => ['ടൈപ്പ്', 'multibyte characters ml'], + ]; + } + public function testOrLike(): void { $jobs = $this->db->table('job')->like('name', 'ian') diff --git a/user_guide_src/source/changelogs/v4.5.6.rst b/user_guide_src/source/changelogs/v4.5.6.rst index c2f396627442..44b11b724cb9 100644 --- a/user_guide_src/source/changelogs/v4.5.6.rst +++ b/user_guide_src/source/changelogs/v4.5.6.rst @@ -32,6 +32,7 @@ Bugs Fixed - **Session Library:** The session initialization debug message now uses the correct log type "debug" instead of "info". - **Validation:** Fixed the `getValidated()` method that did not return valid data when validation rules used multiple asterisks. +- **Database:** Fixed the case insensitivity option in the ``like()`` method when dealing with accented characters. - **Parser:** Fixed bug that caused equal key names to be replaced by the key name defined first.