Skip to content

Keep ON UPDATE clause when baking migrations#1103

Merged
markstory merged 3 commits into
5.xfrom
snapshot-keep-on-update
Jul 17, 2026
Merged

Keep ON UPDATE clause when baking migrations#1103
markstory merged 3 commits into
5.xfrom
snapshot-keep-on-update

Conversation

@jamisonbryant

Copy link
Copy Markdown
Contributor

Summary

Fixes #1102.

bake migration_snapshot dropped ON UPDATE CURRENT_TIMESTAMP from datetime / timestamp columns. CakePHP core reflects the clause correctly as an onUpdate column attribute; MigrationHelper filtered it out before it reached the template, so databases rebuilt from a snapshot silently lost the clause.

This lets the attribute through both of the helper's allowlists and translates it to Phinx's update column option at render time.

Verified end-to-end against real MySQL by baking a snapshot of a table with an ON UPDATE column, applying the generated migration to a fresh database, and reading back the DDL

Major Changes

  • MigrationHelper::attributes() — add onUpdate to $validOptions so the attribute survives the snapshot path, and drop it when empty so columns without the clause do not gain a null key.
  • MigrationHelper::getColumnOption() — add onUpdate to $wantedOptions, drop it when empty, and translate it to Phinx's update option.

Both follow the pattern established by fixed (#1014, added to both allowlists; #1048, filtering the null case) and by the existing collate to collation translation in getColumnOption().

Minor Changes

  • The empty-value guard is load-bearing rather than defensive. Column::toArray() always emits an onUpdate key (null when the column has no clause) and the diff path feeds those arrays straight into getColumnOption(), while Column::setUpdate() is typed string. Passing the null through would render 'update' => null into generated migrations and fail. Table::addTimestamps() also uses 'update' => '' to mean "no clause", and core's columnSql() guards on !== '', so empty() is the correct test.

Backwards Compatibility Notes

Fully backwards compatible. Both changed methods are public, but the change is additive only — a key appears solely for MySQL datetime / timestamp columns that genuinely carry an ON UPDATE clause. No key is removed or renamed, no existing output shape changes, and userland bake templates are unaffected. Existing migration files are untouched.

CakePHP reflects `ON UPDATE CURRENT_TIMESTAMP` on datetime/timestamp
columns as an `onUpdate` column attribute, but MigrationHelper dropped
it in two places: the valid-options list in attributes() and the wanted-
options list in getColumnOption(). The clause never reached the template,
so any column declaring it lost it when snapshotted.

Databases rebuilt from a snapshot then silently stopped auto-updating
those columns on writes that bypass the ORM, while existing databases
kept the real schema.

Let the attribute through both filters and translate it to Phinx's
`update` option, mirroring the existing collate/collation handling.
Both filters drop the attribute when empty, since Column::toArray()
always emits an onUpdate key and Column::setUpdate() is not nullable.
The two existing tests fed getColumnOption() a hand-built array, which
bypasses attributes() entirely. Removing 'onUpdate' from the valid-options
list in attributes() therefore broke snapshot output with zero test
failures, even though it is one of the two changes the fix depends on.

Add a test that drives attributes() directly and one that walks the whole
snapshot path (columns() -> getColumnOption()). Both build the schema by
hand rather than reflecting it, so they run on every driver.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes snapshot baking for MySQL ON UPDATE column clauses by allowing CakePHP’s reflected onUpdate column attribute to survive MigrationHelper filtering and then rendering it as Phinx’s supported update option, preventing silent schema drift when rebuilding databases from a snapshot.

Changes:

  • Allow onUpdate through MigrationHelper::attributes() and drop it when empty/null to avoid emitting a meaningless key.
  • Allow onUpdate through MigrationHelper::getColumnOption(), drop it when empty/null, and translate onUpdateupdate for Phinx output.
  • Add/extend unit tests to cover both the attribute preservation and the render-time translation/removal behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
tests/TestCase/View/Helper/MigrationHelperTest.php Adds coverage ensuring onUpdate survives attributes() and is rendered as Phinx update, while null/empty values are filtered out.
src/View/Helper/MigrationHelper.php Updates allowlists and adds onUpdateupdate translation with empty/null guarding in both attributes() and getColumnOption().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@markstory markstory left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, would it be difficult to get a regression test for bake migration_snapshot so that we know the bug is fixed and that we won't regress in the future?

The MigrationHelper tests build their schema by hand, so they prove the
helper emits the option but not that a baked snapshot keeps it. Cover the
end-to-end path: reflect a real ON UPDATE column, bake a snapshot, and
compare the generated migration against a checked-in file.

Gated on MySQL because only MysqlSchemaDialect reflects onUpdate; the
other drivers never produce the attribute. Mirrors the existing
non-default-collation test, which faces the same constraint.

The comparison file differs from the plain snapshot by a single
'update' => 'CURRENT_TIMESTAMP' line on users.updated, while users.created
stays bare, so the test also pins down that the option is not applied to
columns that never declared it.
@jamisonbryant
jamisonbryant marked this pull request as ready for review July 14, 2026 15:09
@jamisonbryant
jamisonbryant requested a review from markstory July 14, 2026 15:09
@markstory
markstory merged commit 6f5444b into 5.x Jul 17, 2026
14 checks passed
@markstory
markstory deleted the snapshot-keep-on-update branch July 17, 2026 18:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bake migration_snapshot drops ON UPDATE CURRENT_TIMESTAMP from datetime columns

4 participants