Skip to content

Model MySQL column positions and attributes - #2813

Open
minleejae wants to merge 1 commit into
JSQLParser:masterfrom
minleejae:feat/mysql-column-attributes-20261014
Open

minleejae wants to merge 1 commit into
JSQLParser:masterfrom
minleejae:feat/mysql-column-attributes-20261014

Conversation

@minleejae

@minleejae minleejae commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Purpose

Model the MySQL column position and column attributes that are currently split into one opaque option per token. For example, ALTER TABLE t ADD COLUMN c INT AFTER id exposes OTHER[AFTER] and OTHER[id], SRID 4326 exposes OTHER[SRID] and OTHER[4326], and the inline KEY primary key is only an opaque KEY token, so CreateTable.getConstraints() misses it.

Current design (As-is)

  • ColumnDefinitionOption() has typed alternatives for the common attributes. Anything else falls back to ColumnDefinitionParameter(), which reads one token at a time.
  • The following therefore become two or three Kind.OTHER options:
    • the FIRST / AFTER col position of ALTER ADD, CHANGE and MODIFY;
    • SRID n, COLUMN_FORMAT {FIXED|DYNAMIC|DEFAULT};
    • ENGINE_ATTRIBUTE [=] '…' and SECONDARY_ENGINE_ATTRIBUTE [=] '…'.
  • Inline UNIQUE KEY is a UNIQUE constraint followed by an opaque KEY. A bare inline KEY, MySQL's column-level synonym for PRIMARY KEY, is opaque.

Target design (To-be)

  • Position: add an immutable ColumnPosition (FIRST or AFTER column) and store it on AlterExpression.ColumnDataType (getPosition(), setPosition(), withPosition()).
    • It is not a column option: MySQL only allows it after an ALTER column definition. CREATE TABLE is unchanged.
    • The new ColumnPosition() production reads it after the column attributes, as MySQL does. ColumnDataType.appendTo renders it last, so toString and AlterDeParser agree.
    • AlterValidator checks the column named by AFTER with metadata validation.
  • Attributes: add ColumnOption kinds with typed factories and accessors, following the Unify column option storage and mutation contracts #2769/Make column option token access consistent and explicit #2777 contracts:
    • SRID: srid(long), getSrid();
    • COLUMN_FORMAT: columnFormat(ColumnFormat), getColumnFormat();
    • ENGINE_ATTRIBUTE / SECONDARY_ENGINE_ATTRIBUTE: engineAttribute(StringValue), secondaryEngineAttribute(StringValue), getEngineAttribute(), isUseEquals(). The literal is visited and rendered through the expression printer, like a COMMENT literal.
    • Values MySQL does not define keep the raw fallback, for example Percona Server's COLUMN_FORMAT COMPRESSED. No new tokens or reserved words.
  • Inline keys: ColumnKeyConstraint() reads UNIQUE [KEY] and bare KEY into one KeyConstraint.
    • KEY keeps its spelling and reports ConstraintKind.PRIMARY_KEY, so it also appears in getConstraints().
    • SQL Server's inline FOREIGN KEY REFERENCES … prefix stays one raw FOREIGN KEY option, so its KEY is not read as a primary key.
  • CHAR(n) BINARY stays an opaque option.
    • In MySQL, BINARY belongs to the data type's character set clause, together with ASCII, UNICODE and CHARACTER SET. It is not a column attribute, and it also appears in CAST(… AS CHAR(n) BINARY), so a column option kind would model it at the wrong level.
    • Its natural home is ColDataType, next to CharacterSetSyntax. That changes the type grammar shared with CAST/CONVERT/JSON_VALUE, and it needs order-preserving state for forms such as BINARY CHARACTER SET x and CHARACTER SET x BINARY. This is left for a separate change.
  • Compatibility
    • SQL rendering is unchanged, except that the new kinds use canonical keyword capitalization like the other typed options.
    • getColumnSpecs() and ColumnOption.getTokens() keep the previous token split, for example [SRID, 4326], [UNIQUE, KEY] and [NOT, NULL, AFTER, id]. On ColumnDataType, setColumnSpecs() replaces the options and the position, so a spec snapshot round-trips.
    • getColumnOptions() no longer contains the opaque nodes for these attributes or for the position.
    • MySQL rejects a position followed by further attributes, such as AFTER id NOT NULL. This now fails to parse instead of becoming raw tokens.
    • The usage guide documents the new API with migration notes.

Validation

  • 43 new test cases:
    • parse, deparse and reparse with Dialect.MYSQL and without a dialect;
    • AST kinds and payloads, and legacy column specs;
    • typed edits in both renderers, read-only and detached token snapshots, and rejected raw token replacement;
    • factories, traversal of attribute literals, and the raw fallback;
    • UNIQUE KEY, bare KEY and SQL Server's inline FOREIGN KEY;
    • position editing, replacement through setColumnSpecs, builders, rejected placements, and AFTER-column validation.
  • Two existing tests parsed COLUMN_FORMAT DYNAMIC as an example of a raw option; they now use COLUMN_FORMAT COMPRESSED, which stays raw. ColumnAttributeTest now expects the typed COLUMN_FORMAT and keeps a raw extension (NOT SECONDARY).
  • Java 17 offline Gradle spotlessApply check: 10,400 tests, 10,375 passed, 25 skipped, zero failures/errors. The grammar choice conflict check, Checkstyle, PMD, SpotBugs and coverage checks pass.
  • Compared with master (bdf8ac31) on a corpus of 635 MySQL 8.4 and PostgreSQL 18 DDL/DCL statements, each accepted by the server, plus variants, with and without a dialect:
    • Statement class, flags, toString, deparser output and reparse are identical to master (bdf8ac31), with zero regressions.
    • With Dialect.MYSQL, opaque column options drop from 29 to 1; the one left is CHAR(10) BINARY. Without a dialect it is the same, 29 to 1.
    • In the MySQL variant corpus they drop from 3 to 0. PostgreSQL output has no option changes.
    • The 13 removed position tokens are now 8 ColumnPosition values on 7 ALTER statements.
  • MySQL 8.4 and PostgreSQL 18.3: every corpus statement this branch parses ran as written on one server and as rendered by toString() on a second, identical server, in the same order. All 205 MySQL and 328 PostgreSQL statements give the same result (success, or the same error).

Integration validation

Applied #2806–#2819 together on master (bdf8ac31), with the stacked PRs on top of their bases (#2806 → #2807 → #2808, #2806 → #2809 → #2810 and #2811 → #2812).

  • Each branch merges cleanly into master. Between the branches, conflicts are limited to adjacent additions (README/index rows, usage sections, imports, visitor and feature lists, one enum line and one alias guard).
  • The integrated build passes Java 17 offline Gradle spotlessApply check: 11,494 tests, 11,469 passed, 25 skipped, zero failures/errors. The grammar choice conflict check passes.
  • On a corpus of 635 MySQL 8.4 and PostgreSQL 18 DDL/DCL statements, each accepted by the server, the integrated build parses 265 of 271 MySQL statements (master: 206) and 363 of 364 PostgreSQL statements (master: 328). Every MySQL 8.4 non-reserved word and PostgreSQL 18 unreserved or column-name keyword parses as a column and table name.
  • On paired MySQL 8.4 and PostgreSQL 18.3 servers, all 263 MySQL and 362 PostgreSQL statements rendered by the integrated build give the same results as the originals.

@minleejae
minleejae force-pushed the feat/mysql-column-attributes-20261014 branch from fc52164 to eee3991 Compare October 14, 2026 07:50
MySQL column attributes without a typed alternative were split into one
opaque option per token: AFTER id became OTHER[AFTER] and OTHER[id],
SRID 4326 became OTHER[SRID] and OTHER[4326], and inline UNIQUE KEY was
a UNIQUE constraint followed by an opaque KEY.

Store the FIRST/AFTER position of ALTER ADD, CHANGE and MODIFY column
definitions as a ColumnPosition on AlterExpression.ColumnDataType, parsed
after the attributes as in MySQL. Add SRID, COLUMN_FORMAT,
ENGINE_ATTRIBUTE and SECONDARY_ENGINE_ATTRIBUTE column option kinds with
typed factories, read inline UNIQUE KEY as one key constraint, and read
bare KEY as a primary key that keeps its spelling. Legacy column specs
keep their previous tokens, and the validator checks the AFTER column.

Signed-off-by: minleejae <mmj9808@gmail.com>
@minleejae
minleejae force-pushed the feat/mysql-column-attributes-20261014 branch from eee3991 to de2c5ca Compare October 14, 2026 08:36

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant