Repository: codeigniter4/CodeIgniter4 (PHP)

Issue:
Bug: insensitive like query failing with multibyte characters
### PHP Version

7.4

### CodeIgniter4 Version

4.4.8

### CodeIgniter4 Installation Method

Composer (using `codeigniter4/appstarter`)

### Which operating systems have you tested for this bug?

Windows

### Which server did you use?

cli

### Database

Postgres 16

### What happened?

strtolower() in BaseBuilder.php on line 1135 will ignore the multibyte characters (according to PHP8 docs) or in experience on 7.4 will mangle them into diamonds (�).

### Steps to Reproduce

`$query->like('name', 'Š', 'both', null, true)->find();`

### Expected Output
all rows where name contains `š`

### Actual Output
ErrorException: pg_query(): Query failed: ERROR:  invalid byte sequence for encoding &quot;UTF8&quot;: 0xe5 0xa1 0x27 in C:\....

### Anything else?

_No response_


Discussion:
@ECode16 Can you check if #9238 fixes it for you?
@michalsn Yes, it does.



Patch:
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 = <<<SQL
                 SELECT name
-                FROM SYS.DEFAULT_CONSTRAINTS
-                WHERE PARENT_OBJECT_ID = OBJECT_ID('{$fullTable}')
-                AND PARENT_COLUMN_ID IN (
-                SELECT column_id FROM sys.columns WHERE NAME IN ({$fields}) AND object_id = OBJECT_ID(N'{$fullTable}')
+                FROM sys.default_constraints
+                WHERE parent_object_id = OBJECT_ID('{$fullTable}')
+                AND parent_column_id IN (
+                SELECT column_id FROM sys.columns WHERE name IN ({$fields}) AND object_id = OBJECT_ID(N'{$fullTable}')
                 )
                 SQL;
 
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.
 


Test patch:
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/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php b/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php
index 1be92d1fe434..cf567ace96cf 100644
--- a/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php
+++ b/tests/_support/Database/Migrations/20160428212500_Create_test_tables.php
@@ -92,6 +92,7 @@ public function up(): void
 
         if ($this->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<string, array{0: string, 1: string}>
+     */
+    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')