Skip to content

Share typed MySQL index options across CREATE INDEX and table definitions - #2810

Open
minleejae wants to merge 3 commits into
JSQLParser:masterfrom
minleejae:feat/mysql-index-options-20261014
Open

minleejae wants to merge 3 commits into
JSQLParser:masterfrom
minleejae:feat/mysql-index-options-20261014

Conversation

@minleejae

@minleejae minleejae commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Builds on #2809, which reworks the DROP model (typed targets, owner tables and CascadeBehavior); please review that first. Within DROP, this PR only changes the ALGORITHM and LOCK clauses of DROP INDEX.

Purpose

Give MySQL index options one typed model, whichever statement declares the index. Today the same option text produces different AST shapes in CREATE INDEX, CREATE TABLE and ALTER TABLE, so callers have to parse strings to read, for example, an index comment or USING BTREE. MySQL also writes the index type before the key list of a primary key, and ALTER TABLE t ADD [CONSTRAINT pk] PRIMARY KEY USING BTREE (id) currently fails to parse.

Current design (As-is)

  • CREATE TABLE key definitions collect options with the generic CreateParameter rule, one token per entry: KEY_BLOCK_SIZE, =, 4, COMMENT, 'idx'.
  • ALTER TABLE ADD/ALTER INDEX uses a separate IndexOption rule that joins each option into one string, such as KEY_BLOCK_SIZE = 4 or USING BTREE. Engine attributes are kept as tokens. Without =, KEY_BLOCK_SIZE 8 is stored and rendered as KEY_BLOCK_SIZE8.
  • CREATE INDEX keeps everything after the key list in CreateIndex.getTailParameters() as tokens, and getIndex() gets no options. ALGORITHM and LOCK of CREATE INDEX and DROP INDEX are tokens in getTailParameters() and Drop.getParameters().
  • An index type after the key list is a token, while one before the key list is getUsing(). PRIMARY KEY does not accept it before the key list.

Target design (To-be)

  • One grammar rule, IndexSpecOption(), parses MySQL index_option for CREATE INDEX, CREATE TABLE key definitions and ALTER TABLE ADD/ALTER INDEX. KEY_BLOCK_SIZE and the engine attributes reuse the scalar syntax of table options (MySqlScalarTableOption), as in fix: align CREATE and ALTER index option boundaries #2702.
  • IndexOptions.getSpecOptions(), also on Index and KeyConstraint, holds the options after the key list as ordered IndexSpecOption nodes:
    • kinds KEY_BLOCK_SIZE, INDEX_TYPE, PARSER, COMMENT, VISIBILITY, ENGINE_ATTRIBUTE, SECONDARY_ENGINE_ATTRIBUTE, each with a value and its optional =;
    • a COMMENT holds its literal as a StringValue in getComment(), read like other string literals so the dialect's escaping is recorded. getNotExcapedValue() returns the stored text, for example it's for 'it''s' or, with Dialect.MYSQL, for 'it\'s'. getValue() remains the literal's SQL text, and rendering is unchanged;
    • OTHER for opaque tokens that CREATE TABLE and CREATE INDEX accept for other dialects. They are never parsed.
    • The kind is fixed. Setters for a payload the kind does not have, and setTokens on a typed option, throw IllegalStateException, following Make column option token access consistent and explicit #2777.
  • The index type keeps its position: before the key list it is getUsing() (with CreateIndex.isIndexTypeBeforeOn() as before), after it an INDEX_TYPE option at its source position. PRIMARY KEY and named generic CONSTRAINT ... UNIQUE now accept USING type before the key list. The Cockroach USING COLUMNS form is excluded.
  • CREATE INDEX stores these options on its Index, as Share CREATE INDEX options with its index definition #2741 did for the PostgreSQL options. A replacement index inherits them, and detaching keeps them.
  • ALGORITHM and LOCK become AlgorithmOrLockOption nodes in CreateIndex.getAlgorithmOrLockOptions() and Drop.getAlgorithmOrLockOptions(). CREATE INDEX renders them after the index options, as MySQL requires. DROP INDEX keeps their position relative to the ON owner table and other parameter tokens; CASCADE/RESTRICT from Keep routine signatures and owning tables in DROP statements #2809 stays last. In Drop(), only the ALGORITHM/LOCK alternative and its result change; the raw ALGORITHM, NONE, = and LOCK token alternatives, unreachable or only meant for these clauses, are removed.
  • Legacy accessors are derived from these nodes:
    • getIndexSpec() is an unmodifiable token snapshot, or null when unset.
    • CreateIndex.getTailParameters() returns the index option tokens followed by ALGORITHM and LOCK.
    • Drop.getParameters() stays one derived snapshot in source order: ON with the owner table, ALGORITHM and LOCK, and the CASCADE/RESTRICT keyword of Keep routine signatures and owning tables in DROP statements #2809.
    • setIndexSpec and setTailParameters replace these clauses with one opaque option. Drop.setParameters clears all typed tail state: the owner table, ALGORITHM and LOCK, and the behavior. addParameters keeps them.
  • PostgreSQL index options (INCLUDE, NULLS DISTINCT, WITH, TABLESPACE, USING INDEX TABLESPACE) keep their dedicated properties and are unchanged.
  • Compatibility:
    • Edits of the getIndexSpec() and getTailParameters() snapshots now throw UnsupportedOperationException.
    • ALTER TABLE key definitions report the same tokens as CREATE TABLE, for example KEY_BLOCK_SIZE, =, 8 instead of KEY_BLOCK_SIZE = 8. Two AlterTest assertions now check the per-option strings through getSpecOptions().
    • Typed option keywords render in upper case.
    • DROP no longer accepts stray NONE or = tokens outside an ALGORITHM or LOCK clause.
    • usage.rst documents the model and the migration, and the Keep routine signatures and owning tables in DROP statements #2809 DROP section mentions ALGORITHM and LOCK in getParameters().

Validation

  • MySqlIndexSpecOptionTest adds 34 cases:
    • parse, deparse and reparse with Dialect.MYSQL and without a dialect;
    • AST fields for the same options in CREATE INDEX, CREATE TABLE and ALTER TABLE;
    • the index type before and after the key list;
    • ALGORITHM/LOCK on CREATE INDEX and DROP INDEX;
    • typed edits reaching toString() and StatementDeParser;
    • decoded COMMENT text in CREATE INDEX, CREATE TABLE keys and ALTER TABLE ADD INDEX ('it''s', 'it\'s', "dq", 'a\\b' and Korean text, with Dialect.MYSQL);
    • legacy snapshots and setters;
    • kind contracts and replacing or detaching the CREATE INDEX definition;
    • PostgreSQL options left unchanged;
    • rejected incomplete options.
  • Java 17 offline Gradle spotlessApply check on top of Keep routine signatures and owning tables in DROP statements #2809: 10,499 tests, 10,474 passed, 25 skipped, zero failures/errors. The grammar choice conflict check passes.
  • Compared with Keep routine signatures and owning tables in DROP statements #2809 on a corpus of 635 MySQL 8.4 and PostgreSQL 18 DDL/DCL statements, each accepted by the server, plus variants, no regressions:
    • ALTER TABLE t1 ADD CONSTRAINT pk_t1 PRIMARY KEY USING BTREE (id) is fixed with Dialect.MYSQL and without a dialect. ADD PRIMARY KEY USING BTREE (id) is fixed with Dialect.MYSQL.
    • toString() and deparser output is unchanged for every other case.
    • The AST changes only for the 17 statements with MySQL index options (CREATE INDEX, CREATE TABLE keys, ALTER TABLE ADD/ALTER INDEX, DROP INDEX): options move from string lists to typed nodes.
  • 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 210 MySQL and 336 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.

CREATE USER and DEFINER read user@host with their own copies of the
same rule. Neither accepted app@'%' or 'app'@localhost, because the
lexer reads app@ and @localhost as single tokens. Without a dialect a
double-quoted part also lost its quotes, so CREATE USER "app"@"%"
rendered as app@%.

Add one MySqlAccountName() production that splits those tokens, keep
the quote character of double-quoted and backquoted parts apart from
their value, and use it for CREATE USER and DEFINER.

Signed-off-by: minleejae <mmj9808@gmail.com>
DROP PROCEDURE p(int) lost its signature, DROP FUNCTION accepted only one
signature, and owner tables, CASCADE/RESTRICT and the roles of DROP OWNED BY
were kept as raw parameter tokens.

Model routine targets as RoutineReference, role and account targets as
UserAccount read with MySqlAccountName(), the ON table of DROP TRIGGER/RULE in
the existing owner field and CASCADE/RESTRICT as CascadeBehavior. Keep
getNames(), getTypeToParameters(), getParamsByType() and getParameters() as
derived values.

Parse DROP TYPE, DOMAIN, EVENT, EVENT TRIGGER, [UNDO] TABLESPACE ... ENGINE,
USER, GROUP, MySQL account names in DROP USER/ROLE, DROP DATABASE ...
WITH (FORCE) and PostgreSQL REASSIGN OWNED.

Signed-off-by: minleejae <mmj9808@gmail.com>
…efinitions

Parse MySQL index options with one IndexSpecOption production in CREATE
INDEX, CREATE TABLE key definitions and ALTER TABLE ADD/ALTER INDEX, and
keep them as ordered typed IndexSpecOption nodes on IndexOptions. The
ALGORITHM and LOCK clauses of CREATE INDEX and DROP INDEX become
AlgorithmOrLockOption nodes. PRIMARY KEY and named UNIQUE keys accept the
index type before the key list.

getIndexSpec(), CreateIndex.getTailParameters() and Drop.getParameters()
remain available as derived token snapshots.

Signed-off-by: minleejae <mmj9808@gmail.com>

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