🔥
CodeIgniter4 PostgreSQL で modifyColumn 実行時、常に NOT NULL 制約が追加される件への暫定対応
実行環境
- CodeIgniter 4.2.12
- PostgreSQL 9.2
再現する手順
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class CreateTableTestTable extends Migration
{
public function up()
{
$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('test_table');
}
public function down()
{
$this->forge->dropTable('test_table');
}
}
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class ModifyColumnToTestTable extends Migration
{
public function up()
{
$this->forge->modifyColumn('test_table', [
'col1' => ['type' => 'VARCHAR', 'constraint' => 1],
'col2' => ['type' => 'VARCHAR', 'constraint' => 1, 'null' => true],
'col3' => ['type' => 'VARCHAR', 'constraint' => 1, 'null' => false],
]);
}
public function down()
{
}
}
php spark migrate
以下の SQL が実行される。
CREATE TABLE "test_table"
(
"col1" VARCHAR(255) NULL,
"col2" VARCHAR(255) NULL,
"col3" VARCHAR(255) NULL
);
ALTER TABLE "test_table" ALTER COLUMN "col1" TYPE VARCHAR(1);
ALTER TABLE "test_table" ALTER COLUMN "col1" SET NOT NULL;
ALTER TABLE "test_table" ALTER COLUMN "col2" TYPE VARCHAR(1);
ALTER TABLE "test_table" ALTER COLUMN "col2" SET NOT NULL;
ALTER TABLE "test_table" ALTER COLUMN "col3" TYPE VARCHAR(1);
ALTER TABLE "test_table" ALTER COLUMN "col3" SET NOT NULL;
当該テーブルの最終 DDL は以下となる。
CREATE TABLE "test_table"
(
"col1" VARCHAR(1) NOT NULL,
"col2" VARCHAR(1) NOT NULL,
"col3" VARCHAR(1) NOT NULL
);
問題箇所
PostgreSQL 用の Forge
クラス _alterTable
メソッド内 ALTER TABLE コマンド組立に
不具合がある。
vendor\codeigniter4\framework\system\Database\Postgre\Forge.php
<?php
// ...
class Forge extends BaseForge
{
// ...
protected function _alterTable(string $alterType, string $table, $field)
{
if (in_array($alterType, ['DROP', 'ADD'], true)) {
return parent::_alterTable($alterType, $table, $field);
}
$sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table);
$sqls = [];
foreach ($field as $data) {
if ($data['_literal'] !== false) {
return false;
}
if (version_compare($this->db->getVersion(), '8', '>=') && isset($data['type'])) {
$sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($data['name'])
. " TYPE {$data['type']}{$data['length']}";
}
if (! empty($data['default'])) {
$sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($data['name'])
. " SET DEFAULT {$data['default']}";
}
if (isset($data['null'])) {
$sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($data['name'])
. ($data['null'] === true ? ' DROP' : ' SET') . ' NOT NULL';
}
if (! empty($data['new_name'])) {
$sqls[] = $sql . ' RENAME COLUMN ' . $this->db->escapeIdentifiers($data['name'])
. ' TO ' . $this->db->escapeIdentifiers($data['new_name']);
}
if (! empty($data['comment'])) {
$sqls[] = 'COMMENT ON COLUMN' . $this->db->escapeIdentifiers($table)
. '.' . $this->db->escapeIdentifiers($data['name'])
. " IS {$data['comment']}";
}
}
return $sqls;
}
// ...
}
暫定対応
CodeIgniter4 独自ドライバーによるデータベースクラスの拡張 で作成した独自ドライバー内で
メソッド _alterTable
をオーバーライドして対応する。
app\Database\MyDriver\Postgre\Forge.php
<?php
namespace App\Database\MyDriver\Postgre;
class Forge extends \CodeIgniter\Database\Postgre\Forge
{
protected function _alterTable(string $alterType, string $table, $field)
{
if (in_array($alterType, ['DROP', 'ADD'], true)) {
return parent::_alterTable($alterType, $table, $field);
}
$sql = 'ALTER TABLE ' . $this->db->escapeIdentifiers($table);
$sqls = [];
foreach ($field as $data) {
if ($data['_literal'] !== false) {
return false;
}
if (version_compare($this->db->getVersion(), '8', '>=') && isset($data['type'])) {
$sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($data['name'])
. " TYPE {$data['type']}{$data['length']}";
}
if (! empty($data['default'])) {
$sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($data['name'])
. " SET DEFAULT {$data['default']}";
}
if (isset($data['null'])) {
$sqls[] = $sql . ' ALTER COLUMN ' . $this->db->escapeIdentifiers($data['name'])
- . ($data['null'] === true ? ' DROP' : ' SET') . ' NOT NULL';
+ . ($data['null'] === ' NULL' ? ' DROP' : ' SET') . ' NOT NULL';
}
if (! empty($data['new_name'])) {
$sqls[] = $sql . ' RENAME COLUMN ' . $this->db->escapeIdentifiers($data['name'])
. ' TO ' . $this->db->escapeIdentifiers($data['new_name']);
}
if (! empty($data['comment'])) {
$sqls[] = 'COMMENT ON COLUMN' . $this->db->escapeIdentifiers($table)
. '.' . $this->db->escapeIdentifiers($data['name'])
. " IS {$data['comment']}";
}
}
return $sqls;
}
}
動作確認
php spark migrate
以下の SQL が実行される。
CREATE TABLE "test_table"
(
"col1" VARCHAR(255) NULL,
"col2" VARCHAR(255) NULL,
"col3" VARCHAR(255) NULL
);
ALTER TABLE "test_table" ALTER COLUMN "col1" TYPE VARCHAR(1);
ALTER TABLE "test_table" ALTER COLUMN "col1" SET NOT NULL;
ALTER TABLE "test_table" ALTER COLUMN "col2" TYPE VARCHAR(1);
ALTER TABLE "test_table" ALTER COLUMN "col2" DROP NOT NULL;
ALTER TABLE "test_table" ALTER COLUMN "col3" TYPE VARCHAR(1);
ALTER TABLE "test_table" ALTER COLUMN "col3" SET NOT NULL;
当該テーブルの最終 DDL は以下となる。
CREATE TABLE "test_table"
(
"col1" VARCHAR(1) NOT NULL,
"col2" VARCHAR(1),
"col3" VARCHAR(1) NOT NULL
);
おわりに
本来は公式へバグ報告後、バージョンアップを適用すべきだが
開発の都合上、現在最新の v4.3 系へのアップデートが困難な為、力業での対応を行った。
Discussion
これはひどい振る舞いですね。
ひとまず、テストだけ書いてみました。
ありがとうございます。
記事のトップに記載させていただきました。