Repository navigation
Fix changing a column on MySQL, MariaDB, PostgreSQL and SQL Server - #258
JonasPardon wants to merge 6 commits into
Conversation
The ->change() shims used isset() to decide whether the migration set a modifier, which is false for null, so ->default(null) kept the old default instead of removing it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…MariaDB The change shim read the existing column with the modifier names, so the collation was never kept, and passed reported defaults through as strings: MariaDB's NULL became the string 'NULL' and CURRENT_TIMESTAMP was quoted. It also compiled every changed column for each change command. Keep the unset attributes on a copy of the column, normalise the reported default per server, and let Laravel compile the one column of each command. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
This one was fully done by Claude, so please give this one the necessary scrutiny. |
|
@JonasPardon can you take the |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mjauvin Done in f8e7293. I did the same for the loops in #254 (b330ecd) and picked that into this branch too. |
|
@JonasPardon have you made the same tests on all other DB servers? Maybe they have the same issues? |
The ->change() shims compiled the kept attributes as a new column definition, which PostgreSQL and SQL Server reject. Copy them onto the changed column instead and let Laravel compile it, as for MySQL and MariaDB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mjauvin Yes, and they do. PostgreSQL and SQL Server were worse: most changes fail with a syntax error on PostgreSQL, and on SQL Server a column with a default fails unless the migration restates it. I've added the fix to this PR in e87c9fb and updated the description with the results per server. SQLite is fine, apart from |
|
@JonasPardon Why is the MySQLGrammar calling |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mjauvin No good reason. I wrote the MySQL version first and changed a copy of the column. PostgreSQL and SQL Server have to change the column itself, because their comment and default are compiled by separate commands that run after the change and read the same column. On MySQL the only such command is the auto-increment starting value, which doesn't read any of the kept attributes, so both ways give the same SQL. I've made MySQL change the column itself too in 3c493b8. The statements are identical on MySQL 8.4 and MariaDB 11.4. |
|
@JonasPardon any idea what's causing the tests to fail now? |
@mjauvin A packagist security advisory, unrelated to the changes here
|

The
->change()shim loses or corrupts column attributes on MySQL and MariaDB onwip/1.3, and generates invalid SQL on PostgreSQL and SQL Server. SQLite is fine, apart from->default(null), which #254 fixes.I ran the same migrations on real servers against Winter 1.2 (Laravel 9 + DBAL), current
wip/1.3, and this branch: MySQL 8.4.11, MariaDB 11.4.13 (with both themysqlandmariadbdrivers), PostgreSQL 16.15 and SQL Server 2022. Winter 1.2 wasn't run on SQL Server.MySQL and MariaDB
->change()without restating the attributewip/1.3NULL'NULL'NULLutf8mb4_bincollationuseCurrent()timestamp, made nullable1067 Invalid default valueCURRENT_TIMESTAMPO'Briendefault on MariaDB')O''BrienPostgreSQL and SQL Server
->change()without restating the attributewip/1.3nullable()alter column "a" not null'foo',0.00)alter column "a" default …O'Briendefault')useCurrent()timestamp, made nullableCURRENT_TIMESTAMPCcollationIncorrect syntax near the keyword 'default'Latin1_General_BINcollationCauses:
getColumns(), but its attributes were read by modifier name (strtolower('Collate')iscollate, the key iscollation), so the collation was never kept. PostgreSQL only used the new definition's collation.changeflag, so Laravel's PostgreSQL modifiers compile it as a new column (null,default 'foo') instead ofdrop not nullandset default. On SQL Server,modifyDefault()adds an inline default, whichalter columndoesn't accept.'text','text'::character varying,('text')), so they were quoted again. MySQL reportsCURRENT_TIMESTAMPas is, which got quoted too.compileChange()compiled every changed column for eachchangecommand, instead of the one column in$command->column.Changes:
compileChange()compile it. That compiles one column per command and keepsinstant/lockon MySQL. The kept attributes are nullable, default and the collation while the column stays a text type (like Laravel 9 on MySQL), plus comment and unsigned on MySQL and MariaDB, and comment on PostgreSQL. They are set on the column itself, because on PostgreSQL and SQL Server the comment and the default are compiled by separate commands that run after the change.NULLmeans no default and anything else is anExpression, which also covers the escaped quote case from this comment. On MySQL,CURRENT_TIMESTAMP…becomes anExpression, as in Laravel'scompileLegacyRenameColumn(), and anything else is the raw value. On PostgreSQL and SQL Server, every default is anExpression.GrammarTestCase::existingColumns()helper and a newMariaDbSchemaGrammarTest). They fail without the change. The PostgreSQL test that assertedalter column "name" nullnow expectsdrop not null.ColumnDefinition::$name/$renameToaccess.What it doesn't change:
integer('x')->nullable()->change()still makes an unsigned column signed, becauseinteger()setsunsignedtofalseexplicitly. Winter 1.2 did the same.(uuid())is reported without a marker, so it's still treated as text, the same limitation as Laravel's heuristic.drop identity if existsfor a column that isn't auto-incrementing, andcomment on column … is NULLwhen the column has no comment.bigIncrements('id')->change()fails on the primary key constraint, as it does without the shim.This builds on #254 (same method), so it includes #254's commits. The diff gets smaller once that's merged.
🤖 Generated with Claude Code