CtrlK
BlogDocsLog inGet started
Tessl Logo

testland/migration-operation-taxonomy

Classifies every DDL and DML statement in a database migration into an eight-category operation taxonomy (additive, backwards-compatible alter, locking, lock-escalating, breaking, data-loss, unsafe default, index-missing foreign key) and assigns a Critical, Warning, or Info severity justified by the lock mode and table-rewrite behavior the target engine actually performs. Records where PostgreSQL and MySQL/InnoDB diverge for the same logical statement, and where behavior is version-gated (PostgreSQL 11 removed the table rewrite for a constant DEFAULT; MySQL 8.0.12 made ADD COLUMN instant). Use when a migration file appears in a diff or a review queue and someone must decide, before it reaches a production-sized table, which statements are safe and which will stall writes or destroy data.

75

Quality

94%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Low

Low-risk findings worth noting

Overview
Quality
Evals
Security
Files

engine-quick-reference.mdreferences/

Engine quick reference and worked example

Consolidated lock, rewrite, and severity-divergence tables for the checks in SKILL.md, plus a full worked classification. The classification procedure itself lives in SKILL.md; this file is the lookup companion.

PostgreSQL quick reference

PostgreSQL, current manual (fetched 2026-07-19), version notes called out where behavior changed.

StatementLockRewriteSource
ALTER TABLE (baseline)ACCESS EXCLUSIVE unless noteddepends on clausesql-altertable
ADD COLUMN nullable or constant DEFAULTACCESS EXCLUSIVE, briefno rewrite from PG 11release 11.0, sql-altertable
ADD COLUMN volatile DEFAULT, stored generated, identityACCESS EXCLUSIVEfull rewrite of table and indexessql-altertable
ALTER COLUMN ... TYPEACCESS EXCLUSIVEnormally full rewrite, narrow binary-coercible exceptionsql-altertable
SET NOT NULLACCESS EXCLUSIVEscans whole table unless a valid CHECK proves no NULLsql-altertable
ADD FOREIGN KEYSHARE ROW EXCLUSIVE on both tablesno rewritesql-altertable
VALIDATE CONSTRAINTSHARE UPDATE EXCLUSIVE, plus ROW SHARE on the referenced table for an FKno rewritesql-altertable
CREATE INDEXSHARE, blocks writes not readsn/aexplicit-locking, sql-createindex
CREATE INDEX CONCURRENTLYSHARE UPDATE EXCLUSIVE, two scans, no transaction blockn/aexplicit-locking, sql-createindex
DROP COLUMNACCESS EXCLUSIVE, fastcolumn not physically removed, space reclaimed over timesql-altertable
TRUNCATEACCESS EXCLUSIVE, blocks all concurrent operationsreclaims space immediately, rolls back with the transactionsql-truncate

MySQL 8.0 InnoDB quick reference

MySQL 8.0 InnoDB, all rows from innodb-online-ddl-operations.

StatementIn placeConcurrent DMLRebuilds tableNotes
ADD COLUMNyesyes, except auto-incrementno with INSTANTINSTANT default from 8.0.12; last position only before 8.0.29; INPLACE rebuilds
DROP COLUMNyesyesyesINSTANT default from 8.0.29
Change column data typenonoyes"only supported with ALGORITHM=COPY"
Widen VARCHAR across the 256-byte linenonoyesone length byte below 256 bytes, two at 256 and above
Narrow VARCHARnonoyes"requires a table copy (ALGORITHM=COPY)"
Add secondary indexyesyesno"The table remains available for read and write operations while the index is being created."
Add foreign keyonly with foreign_key_checks disabledyesnootherwise "only the COPY algorithm is supported"
Set column NOT NULLyesyesyesneeds strict SQL mode; "fails if the column contains NULL values"

The three divergences that flip a severity

  1. Index creation. Blocks writes on PostgreSQL without CONCURRENTLY; permits concurrent DML on MySQL 8.0. Category 3 versus category 1.
  2. Column type change. Rewrites on PostgreSQL under ACCESS EXCLUSIVE, and on MySQL forces ALGORITHM=COPY with no concurrent DML. Critical on both, but for different reasons, and the MySQL mitigation (a copy-based online schema change tool) is not the PostgreSQL one.
  3. Adding a foreign key. A metadata-cheap SHARE ROW EXCLUSIVE on PostgreSQL that spreads to a second table, versus a full table copy on a default-configured MySQL. Warning versus Critical.

Worked example

Target: PostgreSQL 16, users 40M rows, orders 180M rows, products 2M rows, sizes taken from a prior data-load migration in the same series.

ALTER TABLE users DROP COLUMN legacy_status;
ALTER TABLE orders ADD COLUMN shipped_at timestamptz NOT NULL;
ALTER TABLE orders ADD COLUMN region text NOT NULL DEFAULT 'unknown';
CREATE INDEX ix_orders_status ON orders (status);
ALTER TABLE products ADD CONSTRAINT fk_category
  FOREIGN KEY (category_id) REFERENCES categories(id);

Classification:

#StatementCategorySeverityBasis
1DROP COLUMN users.legacy_status5 Breaking + 6 Data-lossCriticalColumn becomes invisible to SQL immediately; space not reclaimed, so the fast completion is not evidence of a cheap operation
2ADD COLUMN orders.shipped_at NOT NULL no default7 Unsafe defaultCriticalSET NOT NULL requires no NULL rows exist; verify the add-with-NOT-NULL outcome on the target before merge
3ADD COLUMN orders.region NOT NULL DEFAULT 'unknown'1 AdditiveInfoConstant default, no rewrite from PG 11; would be Critical on PG 10
4CREATE INDEX ix_orders_status ON orders (status)3 LockingCriticalSHARE lock blocks inserts, updates, deletes on a 180M-row table for the whole build
5ADD CONSTRAINT fk_category ... REFERENCES categories(id)4 Lock-escalating + 8 Index-missing FKWarningSHARE ROW EXCLUSIVE on products and on categories; no index on products.category_id

Note how statements 2 and 3 differ by one clause and three severity bands, and how statement 4 would be Info against MySQL 8.0.

SKILL.md

tile.json