Skip to content

Commit 24ed52b

Browse files
authored
Merge pull request #8457 from kenjis/fix-sqlite3-modifyColumn
fix: [SQLite3] Forge::modifyColumn() messes up table
2 parents 51965f3 + 8d30df7 commit 24ed52b

File tree

5 files changed

+206
-6
lines changed

5 files changed

+206
-6
lines changed

system/Database/SQLite3/Forge.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,28 @@ protected function _alterTable(string $alterType, string $table, $processedField
131131
return ''; // Why empty string?
132132
133133
case 'CHANGE':
134+
$fieldsToModify = [];
135+
136+
foreach ($processedFields as $processedField) {
137+
$name = $processedField['name'];
138+
$newName = $processedField['new_name'];
139+
140+
$field = $this->fields[$name];
141+
$field['name'] = $name;
142+
$field['new_name'] = $newName;
143+
144+
// Unlike when creating a table, if `null` is not specified,
145+
// the column will be `NULL`, not `NOT NULL`.
146+
if ($processedField['null'] === '') {
147+
$field['null'] = true;
148+
}
149+
150+
$fieldsToModify[] = $field;
151+
}
152+
134153
(new Table($this->db, $this))
135154
->fromTable($table)
136-
->modifyColumn($processedFields) // @TODO Bug: should be NOT processed fields
155+
->modifyColumn($fieldsToModify)
137156
->run();
138157

139158
return null; // Why null?

system/Database/SQLite3/Table.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,24 @@ protected function formatFields($fields)
392392
'null' => $field->nullable,
393393
];
394394

395+
if ($field->default === null) {
396+
// `null` means that the default value is not defined.
397+
unset($return[$field->name]['default']);
398+
} elseif ($field->default === 'NULL') {
399+
// 'NULL' means that the default value is NULL.
400+
$return[$field->name]['default'] = null;
401+
} else {
402+
$default = trim($field->default, "'");
403+
404+
if ($this->isIntegerType($field->type)) {
405+
$default = (int) $default;
406+
} elseif ($this->isNumericType($field->type)) {
407+
$default = (float) $default;
408+
}
409+
410+
$return[$field->name]['default'] = $default;
411+
}
412+
395413
if ($field->primary_key) {
396414
$this->keys['primary'] = [
397415
'fields' => [$field->name],
@@ -403,6 +421,30 @@ protected function formatFields($fields)
403421
return $return;
404422
}
405423

424+
/**
425+
* Is INTEGER type?
426+
*
427+
* @param string $type SQLite data type (case-insensitive)
428+
*
429+
* @see https://www.sqlite.org/datatype3.html
430+
*/
431+
private function isIntegerType(string $type): bool
432+
{
433+
return strpos(strtoupper($type), 'INT') !== false;
434+
}
435+
436+
/**
437+
* Is NUMERIC type?
438+
*
439+
* @param string $type SQLite data type (case-insensitive)
440+
*
441+
* @see https://www.sqlite.org/datatype3.html
442+
*/
443+
private function isNumericType(string $type): bool
444+
{
445+
return in_array(strtoupper($type), ['NUMERIC', 'DECIMAL'], true);
446+
}
447+
406448
/**
407449
* Converts keys retrieved from the database to
408450
* the format needed to create later.

tests/system/Database/Live/ForgeTest.php

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1289,8 +1289,18 @@ public function testModifyColumnRename(): void
12891289
'unsigned' => false,
12901290
'auto_increment' => true,
12911291
],
1292+
'int' => [
1293+
'type' => 'INT',
1294+
'constraint' => 10,
1295+
'null' => false,
1296+
],
1297+
'varchar' => [
1298+
'type' => 'VARCHAR',
1299+
'constraint' => 7,
1300+
'null' => false,
1301+
],
12921302
'name' => [
1293-
'type' => 'varchar',
1303+
'type' => 'VARCHAR',
12941304
'constraint' => 255,
12951305
'null' => true,
12961306
],
@@ -1304,17 +1314,31 @@ public function testModifyColumnRename(): void
13041314
$this->forge->modifyColumn('forge_test_three', [
13051315
'name' => [
13061316
'name' => 'altered',
1307-
'type' => 'varchar',
1317+
'type' => 'VARCHAR',
13081318
'constraint' => 255,
13091319
'null' => true,
13101320
],
13111321
]);
13121322

13131323
$this->db->resetDataCache();
13141324

1325+
$fieldData = $this->db->getFieldData('forge_test_three');
1326+
$fields = [];
1327+
1328+
foreach ($fieldData as $obj) {
1329+
$fields[$obj->name] = $obj;
1330+
}
1331+
13151332
$this->assertFalse($this->db->fieldExists('name', 'forge_test_three'));
13161333
$this->assertTrue($this->db->fieldExists('altered', 'forge_test_three'));
13171334

1335+
$this->assertTrue($fields['altered']->nullable);
1336+
$this->assertFalse($fields['int']->nullable);
1337+
$this->assertFalse($fields['varchar']->nullable);
1338+
$this->assertNull($fields['altered']->default);
1339+
$this->assertNull($fields['int']->default);
1340+
$this->assertNull($fields['varchar']->default);
1341+
13181342
$this->forge->dropTable('forge_test_three', true);
13191343
}
13201344

tests/system/Database/Live/SQLite3/AlterTableTest.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,17 +89,17 @@ public function testFromTableFillsDetails(): void
8989

9090
$this->assertCount(5, $fields);
9191
$this->assertArrayHasKey('id', $fields);
92-
$this->assertNull($fields['id']['default']);
92+
$this->assertArrayNotHasKey('default', $fields['id']);
9393
$this->assertTrue($fields['id']['null']);
9494
$this->assertSame('integer', strtolower($fields['id']['type']));
9595

9696
$this->assertArrayHasKey('name', $fields);
97-
$this->assertNull($fields['name']['default']);
97+
$this->assertArrayNotHasKey('default', $fields['name']);
9898
$this->assertFalse($fields['name']['null']);
9999
$this->assertSame('varchar', strtolower($fields['name']['type']));
100100

101101
$this->assertArrayHasKey('email', $fields);
102-
$this->assertNull($fields['email']['default']);
102+
$this->assertArrayNotHasKey('default', $fields['email']);
103103
$this->assertTrue($fields['email']['null']);
104104
$this->assertSame('varchar', strtolower($fields['email']['type']));
105105

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<?php
2+
3+
/**
4+
* This file is part of CodeIgniter 4 framework.
5+
*
6+
* (c) CodeIgniter Foundation <[email protected]>
7+
*
8+
* For the full copyright and license information, please view
9+
* the LICENSE file that was distributed with this source code.
10+
*/
11+
12+
namespace CodeIgniter\Database\Live\SQLite3;
13+
14+
use CodeIgniter\Database\Forge;
15+
use CodeIgniter\Test\CIUnitTestCase;
16+
use Config\Database;
17+
18+
/**
19+
* @group DatabaseLive
20+
*
21+
* @internal
22+
*/
23+
final class ForgeModifyColumnTest extends CIUnitTestCase
24+
{
25+
private Forge $forge;
26+
27+
protected function setUp(): void
28+
{
29+
parent::setUp();
30+
31+
$this->db = Database::connect($this->DBGroup);
32+
33+
if ($this->db->DBDriver !== 'SQLite3') {
34+
$this->markTestSkipped('This test is only for SQLite3.');
35+
}
36+
37+
$this->forge = Database::forge($this->DBGroup);
38+
}
39+
40+
public function testModifyColumnRename(): void
41+
{
42+
$table = 'forge_test_three';
43+
44+
$this->forge->dropTable($table, true);
45+
46+
$this->forge->addField([
47+
'id' => [
48+
'type' => 'INTEGER',
49+
'constraint' => 11,
50+
'auto_increment' => true,
51+
],
52+
'int' => [
53+
'type' => 'INT',
54+
'constraint' => 10,
55+
'null' => false,
56+
'default' => 0,
57+
],
58+
'varchar' => [
59+
'type' => 'VARCHAR',
60+
'constraint' => 7,
61+
'null' => false,
62+
],
63+
'decimal' => [
64+
'type' => 'DECIMAL',
65+
'constraint' => '10,5',
66+
'default' => 0.1,
67+
],
68+
'name' => [
69+
'type' => 'VARCHAR',
70+
'constraint' => 255,
71+
'null' => true,
72+
],
73+
]);
74+
75+
$this->forge->addKey('id', true);
76+
$this->forge->createTable($table);
77+
78+
$this->assertTrue($this->db->fieldExists('name', $table));
79+
80+
$this->forge->modifyColumn($table, [
81+
'name' => [
82+
'name' => 'altered',
83+
'type' => 'VARCHAR',
84+
'constraint' => 255,
85+
'null' => true,
86+
],
87+
]);
88+
89+
$this->db->resetDataCache();
90+
91+
$fieldData = $this->db->getFieldData($table);
92+
$fields = [];
93+
94+
foreach ($fieldData as $obj) {
95+
$fields[$obj->name] = $obj;
96+
}
97+
98+
$this->assertFalse($this->db->fieldExists('name', $table));
99+
$this->assertTrue($this->db->fieldExists('altered', $table));
100+
101+
$this->assertFalse($fields['int']->nullable);
102+
$this->assertSame('0', $fields['int']->default);
103+
104+
$this->assertFalse($fields['varchar']->nullable);
105+
$this->assertNull($fields['varchar']->default);
106+
107+
$this->assertFalse($fields['decimal']->nullable);
108+
$this->assertSame('0.1', $fields['decimal']->default);
109+
110+
$this->assertTrue($fields['altered']->nullable);
111+
$this->assertNull($fields['altered']->default);
112+
113+
$this->forge->dropTable($table, true);
114+
}
115+
}

0 commit comments

Comments
 (0)