From 725292ca383c2a0629f6d83748322fe182767bb8 Mon Sep 17 00:00:00 2001 From: michalsn Date: Fri, 25 Oct 2024 22:35:38 +0200 Subject: [PATCH 1/7] fix: case-insensitivity in the like() method when in use with accented characters --- system/Database/BaseBuilder.php | 2 +- tests/_support/Database/Seeds/CITestSeeder.php | 4 ++++ tests/system/Database/Live/LikeTest.php | 8 ++++++++ user_guide_src/source/changelogs/v4.5.6.rst | 1 + 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index 570340dffb9b..e39b8e113bde 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); } $prefix = empty($this->{$clause}) ? $this->groupGetType('') : $this->groupGetType($type); diff --git a/tests/_support/Database/Seeds/CITestSeeder.php b/tests/_support/Database/Seeds/CITestSeeder.php index f8582839cf05..3311ac969890 100644 --- a/tests/_support/Database/Seeds/CITestSeeder.php +++ b/tests/_support/Database/Seeds/CITestSeeder.php @@ -86,6 +86,10 @@ public function run(): void 'key' => 'key', 'value' => 'value', ], + [ + 'key' => 'accented characters', + 'value' => 'śćźżłąęó', + ], ], 'type_test' => [ [ diff --git a/tests/system/Database/Live/LikeTest.php b/tests/system/Database/Live/LikeTest.php index 5884f9d01220..48a85bf83b7b 100644 --- a/tests/system/Database/Live/LikeTest.php +++ b/tests/system/Database/Live/LikeTest.php @@ -75,6 +75,14 @@ public function testLikeCaseInsensitive(): void $this->assertSame('Developer', $job->name); } + public function testLikeCaseInsensitiveWithAccentedCharacter(): void + { + $wai = $this->db->table('without_auto_increment')->like('value', 'ŁĄ', 'both', null, true)->get(); + $wai = $wai->getRow(); + + $this->assertSame('accented characters', $wai->key); + } + 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. From bd3059c3176ed8548ecc64352aff43c5650210df Mon Sep 17 00:00:00 2001 From: Michal Sniatala Date: Sat, 26 Oct 2024 08:00:36 +0200 Subject: [PATCH 2/7] Update system/Database/BaseBuilder.php Co-authored-by: Pooya Parsa --- system/Database/BaseBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/Database/BaseBuilder.php b/system/Database/BaseBuilder.php index e39b8e113bde..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 = mb_strtolower($v); + $v = mb_strtolower($v, 'UTF-8'); } $prefix = empty($this->{$clause}) ? $this->groupGetType('') : $this->groupGetType($type); From ecf96bf5548fecfdd375630f70b702a3c53300ce Mon Sep 17 00:00:00 2001 From: michalsn Date: Sat, 26 Oct 2024 08:06:14 +0200 Subject: [PATCH 3/7] add more cases for tests --- tests/_support/Database/Seeds/CITestSeeder.php | 6 +++++- tests/system/Database/Live/LikeTest.php | 9 +++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/_support/Database/Seeds/CITestSeeder.php b/tests/_support/Database/Seeds/CITestSeeder.php index 3311ac969890..fb3e4b033d19 100644 --- a/tests/_support/Database/Seeds/CITestSeeder.php +++ b/tests/_support/Database/Seeds/CITestSeeder.php @@ -87,9 +87,13 @@ public function run(): void 'value' => 'value', ], [ - 'key' => 'accented characters', + 'key' => 'multibyte characters 1', 'value' => 'śćźżłąęó', ], + [ + 'key' => 'multibyte characters 2', + 'value' => 'خٌوب', + ], ], 'type_test' => [ [ diff --git a/tests/system/Database/Live/LikeTest.php b/tests/system/Database/Live/LikeTest.php index 48a85bf83b7b..aa2c57a2ed03 100644 --- a/tests/system/Database/Live/LikeTest.php +++ b/tests/system/Database/Live/LikeTest.php @@ -75,12 +75,17 @@ public function testLikeCaseInsensitive(): void $this->assertSame('Developer', $job->name); } - public function testLikeCaseInsensitiveWithAccentedCharacter(): void + public function testLikeCaseInsensitiveWithMultibyteCharacter(): void { $wai = $this->db->table('without_auto_increment')->like('value', 'ŁĄ', 'both', null, true)->get(); $wai = $wai->getRow(); - $this->assertSame('accented characters', $wai->key); + $this->assertSame('multibyte characters 1', $wai->key); + + $wai = $this->db->table('without_auto_increment')->like('value', 'خٌوب', 'both', null, true)->get(); + $wai = $wai->getRow(); + + $this->assertSame('multibyte characters 2', $wai->key); } public function testOrLike(): void From 01a636d81f103ac29e973674d14a0cda888cdaa1 Mon Sep 17 00:00:00 2001 From: michalsn Date: Mon, 28 Oct 2024 19:18:17 +0100 Subject: [PATCH 4/7] even more tests --- .../_support/Database/Seeds/CITestSeeder.php | 16 ++++++++++++-- tests/system/Database/Live/LikeTest.php | 22 +++++++++++++------ 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/tests/_support/Database/Seeds/CITestSeeder.php b/tests/_support/Database/Seeds/CITestSeeder.php index fb3e4b033d19..45b70a7fdd19 100644 --- a/tests/_support/Database/Seeds/CITestSeeder.php +++ b/tests/_support/Database/Seeds/CITestSeeder.php @@ -87,13 +87,25 @@ public function run(): void 'value' => 'value', ], [ - 'key' => 'multibyte characters 1', + 'key' => 'multibyte characters pl', 'value' => 'śćźżłąęó', ], [ - 'key' => 'multibyte characters 2', + '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/LikeTest.php b/tests/system/Database/Live/LikeTest.php index aa2c57a2ed03..812e1ba42289 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,17 +76,24 @@ public function testLikeCaseInsensitive(): void $this->assertSame('Developer', $job->name); } - public function testLikeCaseInsensitiveWithMultibyteCharacter(): void + #[DataProvider('provideMultibyteCharacters')] + public function testLikeCaseInsensitiveWithMultibyteCharacter($match, $result): void { - $wai = $this->db->table('without_auto_increment')->like('value', 'ŁĄ', 'both', null, true)->get(); + $wai = $this->db->table('without_auto_increment')->like('value', $match, 'both', null, true)->get(); $wai = $wai->getRow(); - $this->assertSame('multibyte characters 1', $wai->key); - - $wai = $this->db->table('without_auto_increment')->like('value', 'خٌوب', 'both', null, true)->get(); - $wai = $wai->getRow(); + $this->assertSame($result, $wai->key); + } - $this->assertSame('multibyte characters 2', $wai->key); + 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 From 4f1f8d0e030698569415c01f124e77912570aa09 Mon Sep 17 00:00:00 2001 From: michalsn Date: Mon, 28 Oct 2024 19:27:10 +0100 Subject: [PATCH 5/7] fix types --- tests/system/Database/Live/LikeTest.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/system/Database/Live/LikeTest.php b/tests/system/Database/Live/LikeTest.php index 812e1ba42289..9591eef72ea8 100644 --- a/tests/system/Database/Live/LikeTest.php +++ b/tests/system/Database/Live/LikeTest.php @@ -77,7 +77,7 @@ public function testLikeCaseInsensitive(): void } #[DataProvider('provideMultibyteCharacters')] - public function testLikeCaseInsensitiveWithMultibyteCharacter($match, $result): void + 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(); @@ -85,6 +85,9 @@ public function testLikeCaseInsensitiveWithMultibyteCharacter($match, $result): $this->assertSame($result, $wai->key); } + /** + * @return iterable + */ public static function provideMultibyteCharacters(): iterable { yield from [ From e5ebbbb6f2df0a980914f2a0ea083a8d7aed9c22 Mon Sep 17 00:00:00 2001 From: michalsn Date: Tue, 29 Oct 2024 18:20:07 +0100 Subject: [PATCH 6/7] skip unicode strings for SQLSRV --- tests/system/Database/Live/LikeTest.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/system/Database/Live/LikeTest.php b/tests/system/Database/Live/LikeTest.php index 9591eef72ea8..51ad0c19f228 100644 --- a/tests/system/Database/Live/LikeTest.php +++ b/tests/system/Database/Live/LikeTest.php @@ -79,6 +79,12 @@ public function testLikeCaseInsensitive(): void #[DataProvider('provideMultibyteCharacters')] public function testLikeCaseInsensitiveWithMultibyteCharacter(string $match, string $result): void { + if ($this->db->DBDriver === 'SQLSRV') { + $this->markTestSkipped( + 'Currently Builder class does not fully support Unicode strings in SQLSRV.' + ); + } + $wai = $this->db->table('without_auto_increment')->like('value', $match, 'both', null, true)->get(); $wai = $wai->getRow(); From 37152804fb58529c8236fb5d9d153a97e65fe217 Mon Sep 17 00:00:00 2001 From: michalsn Date: Tue, 29 Oct 2024 22:45:29 +0100 Subject: [PATCH 7/7] set collation for sqlsrv --- .github/workflows/reusable-phpunit-test.yml | 2 +- system/Database/SQLSRV/Forge.php | 8 ++++---- .../Migrations/20160428212500_Create_test_tables.php | 1 + tests/system/Database/Live/ForgeTest.php | 12 +++++++++--- tests/system/Database/Live/GetTest.php | 2 +- tests/system/Database/Live/LikeTest.php | 6 ------ 6 files changed, 16 insertions(+), 15 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/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/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 51ad0c19f228..9591eef72ea8 100644 --- a/tests/system/Database/Live/LikeTest.php +++ b/tests/system/Database/Live/LikeTest.php @@ -79,12 +79,6 @@ public function testLikeCaseInsensitive(): void #[DataProvider('provideMultibyteCharacters')] public function testLikeCaseInsensitiveWithMultibyteCharacter(string $match, string $result): void { - if ($this->db->DBDriver === 'SQLSRV') { - $this->markTestSkipped( - 'Currently Builder class does not fully support Unicode strings in SQLSRV.' - ); - } - $wai = $this->db->table('without_auto_increment')->like('value', $match, 'both', null, true)->get(); $wai = $wai->getRow();