Repository navigation
feat(mysql): ALTER column changes via table rebuild; DML/function forms self-migrating apps use - #59
Merged
Merged
Conversation
…nction forms self-migrating apps use
Found by replaying Sendy 7.1.6's updater against MySQL 8.4 and litewire and
diffing the resulting schemas: 30 failed updater statements before; one
schema difference after, which needs GET_LOCK in the embedding server.
- MODIFY / CHANGE / ALTER COLUMN {SET|DROP} {DEFAULT|NOT NULL} and ADD ...
FIRST|AFTER: SQLite's table-rebuild procedure. Translation returns a
TranslateResult::RebuildTable plan; the session reads the definition,
indexes and triggers from sqlite_master and runs create/copy/drop/rename
in one transaction (savepoint inside a client transaction). Data, inline
PRIMARY KEY, constraints, indexes and triggers carry over; renames follow.
- Multi-operation ALTER split per operation; ADD (a ..., b ...); USING BTREE.
- INSERT IGNORE -> INSERT OR IGNORE; UPDATE/DELETE ... LIMIT via rowid
subquery; DELETE t1 FROM t t1 JOIN ...; UPDATE ... JOIN -> UPDATE FROM;
UPDATE a, b SET ... split per table when each value reads its own table.
- || and && are logical OR/AND (were string concat: a probe ORing column
names matched nothing and the migration re-added existing columns).
- IF, FROM_UNIXTIME, DATE_SUB/DATE_ADD/INTERVAL, FIND_IN_SET,
SUBSTRING_INDEX(+-1); SHOW TABLE STATUS.
- UNIX_TIMESTAMP(x) ignored its argument and returned text (which sorts
after every integer); it now returns an integer for the given time.
…ensrv fork 146de689; zstd ignores) RUSTSEC-2026-0385/0386/0410 fixed by lockfile bumps (tokio 1.54.1, ctutils 0.4.6, zstd-sys 2.1.2). The opensrv-mysql patch moves to the ePHPm fork's HEAD 146de689 (mysql_common without default features), as ePHPm already uses. zstd 0.13 stays via mysql_common's binlog decoder, which the server never runs: ignored with justification.
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.
I found these gaps by replaying Sendy 7.1.6's
includes/update.phpagainst MySQL 8.4.11 and litewire/Turso (through ePHPm), then diffing the resulting schemas. I also replayed a sweep of the app's ~1,000 SQL statement shapes. Before this PR, the updater hit 30 failed statements on litewire. After it, the schema diff shows one difference: a unique index whose guard needsGET_LOCK, which ePHPm wires through #642.ALTER TABLE
MODIFY/CHANGE/ALTER [COLUMN] c {SET|DROP} {DEFAULT|NOT NULL}andADD ... FIRST|AFTER c.ADD ... FIRST|AFTERmatters because MySQL column order shows up inSHOW COLUMNSandSELECT *.TranslateResult::RebuildTable(ColumnRebuild).CREATE, indexes and triggers fromsqlite_master, then runs create → copy → drop → rename → recreate in one transaction. Inside a client transaction it uses a savepoint instead.NOT NULLover existing NULL values fails the statement and leaves the table unchanged.ColumnRebuild::statementsinlitewire_translate::alter, unit-tested.ALTERis split into one statement per operation. ParenthesizedADD (a ..., b ...)andUSING BTREE|HASHare handled.DML
INSERT IGNORE→INSERT OR IGNORE.UPDATE/DELETE ... [ORDER BY] LIMIT n→ arowid IN (subquery)form.DELETE t1 FROM t t1 JOIN t t2 ...(the dedup idiom) works.UPDATE a JOIN b|(derived) ON ... SET a.x = ...→UPDATE ... FROM.UPDATE a, b SET a.x=.., b.y=..is split into one statement per table. This is only done when each value reads its own table; otherwise it errors.||/&&are now logical OR / AND. They were string concatenation, soSHOW COLUMNS ... WHERE Field='a' || Field='b'matched nothing. The updater then re-added columns that already existed.Functions
IF,FROM_UNIXTIME,DATE_SUB/DATE_ADD/d ± INTERVAL n unit,FIND_IN_SET,SUBSTRING_INDEX(counts ±1), andSHOW TABLE STATUS.UNIX_TIMESTAMP(x)ignored its argument and returned text, which sorts after every integer. As a resultWHERE t < UNIX_TIMESTAMP()matched every row.Tests and docs
tests/mysql_migration_forms.rsruns on rusqlite and Turso, plus unit tests for each rewrite.docs/sql-translation.mdand the CHANGELOG are updated.