Fix SQLite ->change(): restore attribute preservation without the crash-prone rebuild - #239
Merged
Merged
Conversation
|
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 configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 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 |
…utes On SQLite, changing a column routed through Storm's own compileChange() override, which hand-rolled a full table rebuild in parallel with Laravel's. This caused three defects: - Tables containing decimal/binary columns threw "Method SQLiteGrammar:: typeNumeric/typeBlob does not exist", because every column was re-emitted through getType() using the introspected type name instead of Laravel's full_type_definition. - Every ->change() rebuilt the table twice (Storm's rebuild plus the base compileAlter rebuild triggered by the implied alter command). - The override's attribute preservation was silently discarded: the base compileAlter ran last and applied Laravel 11's "drop unspecified attributes" semantics. Instead of maintaining a parallel rebuild, defer to Laravel's single compileAlter/BlueprintState rebuild - which reproduces unchanged columns faithfully via full_type_definition - and re-add only attribute preservation via a Winter BlueprintState whose update() merges the existing column's unspecified modifiers onto a change command. It is wired in through Blueprint::addImpliedCommands(); because Blueprint::addAlterCommands() only builds state for SQLite, this affects SQLite exclusively (verified against Laravel 12.x and 13.x). Removes the redundant compileChange() override and its typeTinyint/ typeVarChar aliases (only reachable via the deleted round-trip), and rewrites the SQLite grammar tests as real integration tests covering attribute preservation, faithful type reproduction, the decimal/binary crash, and single-rebuild. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
LukeTowers
force-pushed
the
fix/sqlite-change-column-rebuild
branch
from
August 11, 2026 16:21
cf48eed to
57d41e8
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Background — why the override exists
Laravel 11 rewrote the schema builder to drop its
doctrine/dbaldependency and introspect schemas natively. A deliberate side effect (Laravel 11 upgrade guide — Modifying Columns):->change()no longer preserves a column's existing attributes — you must now re-specify every modifier on a change or it is dropped. Laravel's recommended mitigations are to squash migrations into a current-schema snapshot and to always fully redefine changed columns.Neither mitigation is viable for Winter. It's plugin-based, so any given install is an unbounded combination of plugins and migration states — there is no single migration history to squash, and plugin authors can't be expected to fully redefine columns they didn't author. To restore the pre-11 "keep unspecified attributes on change" behaviour, Storm added a
compileChange()grammar override in #207 (Support Laravel 12; commit8dcf119b, "restore migration behavior").SQLite can't
ALTER COLUMN— it has to rebuild the table — so on SQLite that override hand-rolled its own rebuild, re-emitting every column (including unchanged ones) through the grammar'sgetType()using SQLite's introspectedtype_name.The problem
Hand-rolling a parallel rebuild fought Laravel 11's design and broke in three ways:
getType()dispatches on the introspected type name. SQLite stores adecimalcolumn's declared type asnumericand abinarycolumn's asblob, so the rebuild calledtypeNumeric()/typeBlob()— which don't exist →BadMethodCallException: Method Winter\Storm\Database\Schema\Grammars\SQLiteGrammar::typeNumeric does not exist. This is the same failure class Implement typeTinyint for SQLite grammar #226 patched for booleans (tinyint→ the missingtypeTinyint) — a symptom, not the cause. Any table containing adecimal/binarycolumn made->change()unusable on SQLite.getAlterCommands()markschangeas an alter command, baseBlueprint::addAlterCommands()also emitted the impliedaltercommand, so Laravel's owncompileAlter()rebuilt the table a second time — two full create-temp → copy → drop → rename cycles per change.compileAlter()runs after the override and overwrote its result with L11 "drop unspecified attributes" semantics — so on SQLite the attribute preservation the override existed to provide was silently discarded. (It works on MySQL/PostgreSQL/SQL Server, which alter in place and never hit the second rebuild.)Why this approach fixes it properly
Laravel 11 funnels all SQLite alterations through a single rebuild —
BlueprintState+SQLiteGrammar::compileAlter()— which reproduces unchanged columns verbatim via$column->full_type_definition ?? $this->getType($column)and only routes changed/added columns throughgetType(). That's precisely what makes it faithful (nonumeric/blob/tinyintround-trip) and single-pass. The old override duplicated and fought that machinery; the fix is to stop fighting it and re-add only the piece Winter needs, at the seam Laravel already uses:compileChange()override and the now-deadtypeTinyint/typeVarCharaliases (only reachable via the deleted round-trip). Faithful type reproduction comes for free fromfull_type_definition.Winter\Storm\Database\Schema\BlueprintState(extends the base). Itsupdate()merges the existing column's unspecified modifiers onto achangecommand before delegating toparent::update()— restoring the pre-11 behaviour and, unlike the old override, actually taking effect because it feeds Laravel's single rebuild.Blueprint::addImpliedCommands(). SinceBlueprint::addAlterCommands()only builds stateif ($this->grammar instanceof SQLiteGrammar), this is SQLite-only — MySQL/PostgreSQL/SQL Server are untouched (theircompileChangeoverrides remain the sole handler and continue to work).Net effect: the crash, the double rebuild, and the silent
tinyint(1)→integertype drift all disappear; the #207 intent (preserve attributes across a change) finally works on SQLite; and #226'stypeTinyintband-aid is superseded. −243/+199 across five files.Changes
src/Database/Schema/BlueprintState.php— new; merges unspecified column modifiers onchange, then delegates to the base.src/Database/Schema/Blueprint.php—addImpliedCommands()override swaps in Winter'sBlueprintState(SQLite only).src/Database/Schema/Grammars/SQLiteGrammar.php— removecompileChange,typeTinyint,typeVarChar(leaves onlygetDefaultValue).tests/Database/Schema/Grammars/SQLiteSchemaGrammarTest.php— mock tests → real in-memory-SQLite integration tests + crash/faithfulness/single-rebuild coverage.phpstan-baseline.neon— drop the 6 entries pinned to the removed grammar code.Verification
wip/1.3after Fix CI: regenerate stale PHPStan baseline and code-quality drift #240).integer); decimal+binary faithful & no-crash; rebuilds-exactly-once.php -l/phpcsclean; PHPStan[OK] No errorsagainst the regenerated (Fix CI: regenerate stale PHPStan baseline and code-quality drift #240) baseline.Blueprint/BlueprintState/SQLiteGrammarseam is byte-identical on13.x— no change required.References
8dcf119b, "restore migration behavior").->change()requires full redefinition).Illuminate\Database\Schema\BlueprintState+Grammars\SQLiteGrammar::compileAlter()(full_type_definition).🤖 Generated with Claude Code