Repository navigation
Column 'encoding' option is silently ignored for all types except enum/set #1113
Description
This is a (multiple allowed):
-
bug
-
enhancement
-
feature-discussion (RFC)
-
CakePHP Version: 5.4.1
-
Migrations plugin version: 5.2.6 (present since 5.0.0)
-
Bake plugin version (if relevant): n/a
-
Database server: MariaDB 10.11.14 (applies to MySQL as well)
-
PHP Version: 8.2.33
-
Platform / OS: Linux
What you did
Ran a migration that sets the encoding option on a column, without a collation:
public function up(): void
{
$this->table('requests')
->changeColumn('data', 'text', ['encoding' => 'ascii'])
->update();
}Expected Behavior
The column is created in the ascii character set. The option is documented as supported in columns-and-table-operations.md:
MySQL also supports
collationandencoding.
`data` TEXT CHARACTER SET ascii NOT NULLActual Behavior
The option is discarded. The generated definition carries no character set, so the column keeps the table default, and no error or warning is raised:
`data` TEXT NOT NULLAll three paths that render a column definition are affected, since each one goes through MysqlAdapter::columnDefinitionSql(). Reading back information_schema.COLUMNS.CHARACTER_SET_NAME on a utf8mb4 table, with ['encoding' => 'ascii'] on every column:
| Path | Expected | Actual |
|---|---|---|
CREATE TABLE |
ascii |
utf8mb4 |
ALTER TABLE ... ADD (addColumn) |
ascii |
utf8mb4 |
ALTER TABLE ... CHANGE (changeColumn) |
ascii |
utf8mb4 |
Why it has gone unnoticed
A migration that pairs encoding with a collation still produces the right column, because MySQL infers the character set from the collation. Only encoding on its own silently does nothing — which is also why no existing test covers it.
Where the option is lost
Column::getValidOptions() accepts encoding, and Table::getChangedColumnOptions() goes as far as preserving it across a changeColumn when it is not passed explicitly. From there:
Column::toArray()exports'collate'and no character-set key.MysqlSchemaDialect::columnDefinitionSql()in cakephp/database rendersCOLLATE, but has no per-columnCHARACTER SET— only the table-levelDEFAULT CHARSET.MysqlAdapter::columnDefinitionSql()emitsCHARACTER SETonly inside theenum/setbackwards-compatibility branch; every other type returns the dialect's output verbatim.
Earliest affected version
5.0.0. That release already had the enum/set-only branch in MysqlAdapter::columnDefinitionSql(). Up to 4.x the adapter was Phinx's, whose getColumnSqlDefinition() appended CHARACTER SET for every column type, so this is a regression introduced by the move to cakephp/database.
Other backends are not affected: PostgreSQL sets its character set per database, and SQL Server and SQLite have no per-column character set either. All three offer only per-column COLLATE, which works.
A fix with tests is proposed in #1112.
Activity