Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions system/Database/Forge.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ class Forge
*
* @internal Used for marking nullable fields. Not covered by BC promise.
*/
protected $null = '';
protected $null = 'NULL';

/**
* DEFAULT value representation in CREATE/ALTER TABLE statements
Expand Down Expand Up @@ -562,7 +562,7 @@ public function createTable(string $table, bool $ifNotExists = false, array $att
}

/**
* @return string
* @return string SQL string
*
* @deprecated $ifNotExists is no longer used, and will be removed.
*/
Expand Down Expand Up @@ -897,13 +897,19 @@ protected function _processFields(bool $createTable = false): array
$this->_attributeDefault($attributes, $field);

if (isset($attributes['NULL'])) {
$nullString = ' ' . $this->null;

if ($attributes['NULL'] === true) {
$field['null'] = empty($this->null) ? '' : ' ' . $this->null;
$field['null'] = empty($this->null) ? '' : $nullString;
} elseif ($attributes['NULL'] === $nullString) {
$field['null'] = $nullString;
} elseif ($attributes['NULL'] === '') {
$field['null'] = '';
} else {
$field['null'] = ' NOT NULL';
$field['null'] = ' NOT ' . $this->null;
}
} elseif ($createTable === true) {
$field['null'] = ' NOT NULL';
$field['null'] = ' NOT ' . $this->null;
}

$this->_attributeAutoIncrement($attributes, $field);
Expand Down
13 changes: 10 additions & 3 deletions system/Database/OCI8/Forge.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,17 @@ protected function _alterTable(string $alterType, string $table, $field)
// If a null constraint is added to a column with a null constraint,
// ORA-01451 will occur,
// so add null constraint is used only when it is different from the current null constraint.
$isWantToAddNull = strpos($field[$i]['null'], ' NOT') === false;
$currentNullAddable = $nullableMap[$field[$i]['name']];
// If a not null constraint is added to a column with a not null constraint,
// ORA-01442 will occur.
$wantToAddNull = strpos($field[$i]['null'], ' NOT') === false;
$currentNullable = $nullableMap[$field[$i]['name']];

if ($isWantToAddNull === $currentNullAddable) {
if ($wantToAddNull === true && $currentNullable === true) {
$field[$i]['null'] = '';
} elseif ($field[$i]['null'] === '' && $currentNullable === false) {
// Nullable by default
$field[$i]['null'] = ' NULL';
} elseif ($wantToAddNull === false && $currentNullable === false) {
$field[$i]['null'] = '';
}
}
Expand Down
8 changes: 5 additions & 3 deletions system/Database/Postgre/Forge.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,12 @@ protected function _alterTable(string $alterType, string $table, $field)
. " SET DEFAULT {$data['default']}";
}

if (isset($data['null'])) {
$sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($data['name'])
. ($data['null'] === true ? ' DROP' : ' SET') . ' NOT NULL';
$nullable = true; // Nullable by default.
if (isset($data['null']) && ($data['null'] === false || $data['null'] === ' NOT ' . $this->null)) {
$nullable = false;
}
$sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($data['name'])
. ($nullable === true ? ' DROP' : ' SET') . ' NOT NULL';

if (! empty($data['new_name'])) {
$sqls[] = $sql . ' RENAME COLUMN ' . $this->db->escapeIdentifiers($data['name'])
Expand Down
8 changes: 5 additions & 3 deletions system/Database/SQLSRV/Forge.php
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,12 @@ protected function _alterTable(string $alterType, string $table, $field)
. " DEFAULT {$data['default']} FOR " . $this->db->escapeIdentifiers($data['name']);
}

if (isset($data['null'])) {
$sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($data['name'])
. ($data['null'] === true ? ' DROP' : '') . " {$data['type']}{$data['length']} NOT NULL";
$nullable = true; // Nullable by default.
if (isset($data['null']) && ($data['null'] === false || $data['null'] === ' NOT ' . $this->null)) {
$nullable = false;
}
$sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($data['name'])
. " {$data['type']}{$data['length']} " . ($nullable === true ? '' : 'NOT') . ' NULL';

if (! empty($data['comment'])) {
$sqls[] = 'EXEC sys.sp_addextendedproperty '
Expand Down
12 changes: 6 additions & 6 deletions system/Database/SQLite3/Table.php
Original file line number Diff line number Diff line change
Expand Up @@ -183,14 +183,14 @@ public function dropColumn($columns)
*
* @return Table
*/
public function modifyColumn(array $field)
public function modifyColumn(array $fields)
{
$field = $field[0];

$oldName = $field['name'];
unset($field['name']);
foreach ($fields as $field) {
$oldName = $field['name'];
unset($field['name']);

$this->fields[$oldName] = $field;
$this->fields[$oldName] = $field;
}

return $this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public function up()
'constraint' => 255,
],
]);
$this->forge->createTable('foo', true);
$this->forge->createTable('foo');

$this->db->table('foo')->insert([
'key' => 'foobar',
Expand Down
4 changes: 4 additions & 0 deletions tests/system/Commands/Database/MigrateStatusTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use CodeIgniter\CLI\CLI;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\StreamFilterTrait;
use Config\Database;

/**
* @group DatabaseLive
Expand All @@ -29,6 +30,9 @@ final class MigrateStatusTest extends CIUnitTestCase

protected function setUp(): void
{
$forge = Database::forge();
$forge->dropTable('foo', true);

parent::setUp();

if (! is_file($this->migrationFileFrom)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ final class DatabaseTestCaseMigrationOnce1Test extends CIUnitTestCase

protected function setUp(): void
{
$forge = Database::forge();
$forge->dropTable('foo', true);

$this->setUpMethods[] = 'setUpAddNamespace';

parent::setUp();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\DatabaseTestTrait;
use Config\Database;
use Config\Services;

/**
Expand Down Expand Up @@ -55,6 +56,9 @@ final class DatabaseTestCaseMigrationOnce2Test extends CIUnitTestCase

protected function setUp(): void
{
$forge = Database::forge();
$forge->dropTable('foo', true);

$this->setUpMethods[] = 'setUpAddNamespace';

parent::setUp();
Expand Down
4 changes: 4 additions & 0 deletions tests/system/Database/DatabaseTestCaseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\DatabaseTestTrait;
use Config\Database;
use Config\Services;
use Tests\Support\Database\Seeds\AnotherSeeder;
use Tests\Support\Database\Seeds\CITestSeeder;
Expand Down Expand Up @@ -60,6 +61,9 @@ final class DatabaseTestCaseTest extends CIUnitTestCase

protected function setUp(): void
{
$forge = Database::forge();
$forge->dropTable('foo', true);

$this->setUpMethods[] = 'setUpAddNamespace';

parent::setUp();
Expand Down
87 changes: 87 additions & 0 deletions tests/system/Database/Live/ForgeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\DatabaseTestTrait;
use Config\Database;
use LogicException;
use RuntimeException;
use stdClass;
use Tests\Support\Database\Seeds\CITestSeeder;

/**
Expand Down Expand Up @@ -1276,6 +1278,91 @@ public function testModifyColumnRename()
$this->forge->dropTable('forge_test_three', true);
}

public function testModifyColumnNullTrue()
{
// @TODO remove this in `4.4` branch
if ($this->db->DBDriver === 'SQLSRV') {
$this->markTestSkipped('SQLSRV does not support getFieldData() nullable.');
}

$this->forge->dropTable('forge_test_modify', true);

$this->forge->addField([
'col1' => ['type' => 'VARCHAR', 'constraint' => 255, 'null' => true],
'col2' => ['type' => 'VARCHAR', 'constraint' => 255, 'null' => true],
'col3' => ['type' => 'VARCHAR', 'constraint' => 255, 'null' => true],
]);
$this->forge->createTable('forge_test_modify');

$this->forge->modifyColumn('forge_test_modify', [
'col1' => ['type' => 'VARCHAR', 'constraint' => 1],
'col2' => ['type' => 'VARCHAR', 'constraint' => 1, 'null' => true],
'col3' => ['type' => 'VARCHAR', 'constraint' => 1, 'null' => false],
]);

$this->db->resetDataCache();

$col1 = $this->getMetaData('col1', 'forge_test_modify');
$this->assertTrue($col1->nullable);
$col2 = $this->getMetaData('col2', 'forge_test_modify');
$this->assertTrue($col2->nullable);
$col3 = $this->getMetaData('col3', 'forge_test_modify');
$this->assertFalse($col3->nullable);

$this->forge->dropTable('forge_test_modify', true);
}

public function testModifyColumnNullFalse()
{
// @TODO remove this in `4.4` branch
if ($this->db->DBDriver === 'SQLSRV') {
$this->markTestSkipped('SQLSRV does not support getFieldData() nullable.');
}

$this->forge->dropTable('forge_test_modify', true);

$this->forge->addField([
'col1' => ['type' => 'VARCHAR', 'constraint' => 255, 'null' => false],
'col2' => ['type' => 'VARCHAR', 'constraint' => 255, 'null' => false],
'col3' => ['type' => 'VARCHAR', 'constraint' => 255, 'null' => false],
]);
$this->forge->createTable('forge_test_modify');

$this->forge->modifyColumn('forge_test_modify', [
'col1' => ['type' => 'VARCHAR', 'constraint' => 1],
'col2' => ['type' => 'VARCHAR', 'constraint' => 1, 'null' => true],
'col3' => ['type' => 'VARCHAR', 'constraint' => 1, 'null' => false],
]);

$this->db->resetDataCache();

$col1 = $this->getMetaData('col1', 'forge_test_modify');
$this->assertTrue($col1->nullable); // Nullable by default.
$col2 = $this->getMetaData('col2', 'forge_test_modify');
$this->assertTrue($col2->nullable);
$col3 = $this->getMetaData('col3', 'forge_test_modify');
$this->assertFalse($col3->nullable);

$this->forge->dropTable('forge_test_modify', true);
}

private function getMetaData(string $column, string $table): stdClass
{
$fields = $this->db->getFieldData($table);

$name = array_search(
$column,
array_column($fields, 'name'),
true
);

if ($name === false) {
throw new LogicException('Column not found: ' . $column);
}

return $fields[$name];
}

public function testConnectWithArrayGroup()
{
$group = config('Database');
Expand Down
9 changes: 9 additions & 0 deletions tests/system/Database/Migrations/MigrationRunnerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,9 @@ public function testLatestSuccess()

public function testRegressSuccess()
{
$forge = Database::forge();
$forge->dropTable('foo', true);

$runner = new MigrationRunner($this->config);
$runner->setSilent(false)
->setNamespace('Tests\Support\MigrationTestMigrations')
Expand All @@ -368,6 +371,9 @@ public function testRegressSuccess()

public function testLatestTriggersEvent()
{
$forge = Database::forge();
$forge->dropTable('foo', true);

$runner = new MigrationRunner($this->config);
$runner->setSilent(false)
->setNamespace('Tests\Support\MigrationTestMigrations');
Expand All @@ -387,6 +393,9 @@ public function testLatestTriggersEvent()

public function testRegressTriggersEvent()
{
$forge = Database::forge();
$forge->dropTable('foo', true);

$runner = new MigrationRunner($this->config);
$runner->setSilent(false)
->setNamespace('Tests\Support\MigrationTestMigrations');
Expand Down
15 changes: 15 additions & 0 deletions user_guide_src/source/changelogs/v4.3.4.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,21 @@ Redirect Status Code
always be used when you don't specify a status code. In previous versions,
302 might be changed.

.. _v434-forge-modifycolumn:

Forge::modifyColumn()
---------------------

- The :ref:`$forge->modifyColumn() <db-forge-modifyColumn>` has been fixed. Due
to a bug, in previous versions, SQLite3/Postgres/SQLSRV might change
``NULL``/``NOT NULL`` unpredictably.
- In previous versions, the OCI8 driver did not change ``NULL``/``NOT NULL``
when you don't specify the ``null`` key.
- Now in all database drivers ``$forge->modifyColumn()`` always sets ``NULL``
when you don't specify the ``null`` key.
- The ``NULL``/``NOT NULL`` change may still be unexpectedly, it is recommended
to always specify the ``null`` key.

Message Changes
***************

Expand Down
13 changes: 13 additions & 0 deletions user_guide_src/source/dbmgmt/forge.rst
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,8 @@ Used to remove multiple columns from a table.
Modifying a Field in a Table
============================

.. _db-forge-modifyColumn:

$forge->modifyColumn()
----------------------

Expand All @@ -294,6 +296,17 @@ change the name, you can add a "name" key into the field defining array.

.. literalinclude:: forge/026.php

.. note:: The ``modifyColumn()`` may unexpectedly change ``NULL``/``NOT NULL``.
So it is recommended to always specify the value for ``null`` key. Unlike when creating
a table, if ``null`` is not specified, the column will be ``NULL``, not
``NOT NULL``.

.. note:: Due to a bug, prior v4.3.3, SQLite3 may not set ``NOT NULL`` even if you
specify ``'null' => false``.

.. note:: Due to a bug, prior v4.3.3, Postgres and SQLSRV set ``NOT NULL`` even
if you specify ``'null' => false``.

.. _db-forge-adding-keys-to-a-table:

Adding Keys to a Table
Expand Down
3 changes: 2 additions & 1 deletion user_guide_src/source/dbmgmt/forge/026.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
'old_name' => [
'name' => 'new_name',
'type' => 'TEXT',
'null' => false,
],
];
$forge->modifyColumn('table_name', $fields);
// gives ALTER TABLE `table_name` CHANGE `old_name` `new_name` TEXT
// gives ALTER TABLE `table_name` CHANGE `old_name` `new_name` TEXT NOT NULL
12 changes: 12 additions & 0 deletions user_guide_src/source/installation/upgrade_434.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ Redirect Status Code
:ref:`ChangeLog v4.3.4 <v434-redirect-status-code>` and if the code is not
what you want, :ref:`specify status codes <response-redirect-status-code>`.

Forge::modifyColumn() and NULL
==============================

A bug fix may have changed the NULL constraint in the result of
:ref:`$forge->modifyColumn() <db-forge-modifyColumn>`. See
:ref:`Change Log <v434-forge-modifycolumn>`.
To set the desired NULL constraint, change ``Forge::modifyColumn()`` to always
specify the ``null`` key.

Note that the bug may have changed unexpected NULL constraints in previous
versions.

Breaking Enhancements
*********************

Expand Down