Skip to content

Support the unsupported features from the clause-level MySQL 26.7 sweep - #44

Merged
kyleconroy merged 13 commits into
mainfrom
claude/unsupported-features-support-g9mb09
Aug 21, 2026
Merged

Support the unsupported features from the clause-level MySQL 26.7 sweep#44
kyleconroy merged 13 commits into
mainfrom
claude/unsupported-features-support-g9mb09

Conversation

@kyleconroy

@kyleconroy kyleconroy commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Summary

#43 recorded 148 parse gaps from the clause-level MySQL 26.7 sweep as error goldens across twelve mysql_unsupported_* test groups. This PR implements all of them: every -- error segment in those groups became a Restore() golden, one commit per group.

With nothing left unsupported, a final commit folds the mysql_unsupported_* groups away, following the #39#41 renaming precedent: the seven groups whose mysql_* counterpart already exists (txn, dml, ddl, admin, show, replication, compound) have their cases appended to it, and the five without one are renamed to mysql_types, mysql_functions, mysql_routines, mysql_account, and mysql_utility. No case or golden changes in that commit.

Per the repo's rules, every AST change is additive (new node types and fields; enum values appended so existing values keep their numbers), the public parser API is unchanged, and testdata/errors.json is unaffected.

What each group gained

Group (final name) Now supported
mysql_txn BEGIN/COMMIT/ROLLBACK WORK, START TRANSACTION characteristic lists, LOCK TABLES aliases (new TableLock.Alias) and LOW_PRIORITY WRITE
mysql_show SHOW STORAGE ENGINES, SHOW WARNINGS/ERRORS LIMIT, SHOW EXTENDED INDEX, SHOW REPLICA STATUS FOR CHANNEL (reusing existing ShowStmt fields)
mysql_ddl integer AUTOEXTEND_SIZE, CREATE TABLE ... START TRANSACTION, index-level ENGINE_ATTRIBUTE, ALTER DATABASE ... READ ONLY, bare WITH CHECK OPTION on CREATE/ALTER VIEW
mysql_replication IGNORE_SERVER_IDS = (...), PRIVILEGE_CHECKS_USER = user|NULL, bare-keyword option values (STREAM, GENERATE, ON, OFF, LOCAL), RESET BINARY LOGS AND GTIDS TO n
mysql_utility EXPLAIN FORMAT = TREE, EXPLAIN ... INTO @var, DESCRIBE tbl 'wildcard'
mysql_admin histogram USING DATA and AUTO/MANUAL UPDATE, INSTALL COMPONENT ... SET PERSIST, DROP RESOURCE GROUP FORCE, SET RESOURCE GROUP ... FOR ids, FLUSH multi-option lists plus OPTIMIZER_COSTS/RELAY LOGS [FOR CHANNEL]/USER_RESOURCES/TABLES ... FOR EXPORT, SET PERSIST/PERSIST_ONLY (with @@PERSIST. lexing so restores round-trip)
mysql_types column INVISIBLE/VISIBLE (incl. the GIPK versioned-comment round-trip and ALTER ... SET VISIBLE), column-level ENGINE_ATTRIBUTE, DEFAULT (expr) with operators and INTERVAL arithmetic
mysql_compound DECLARE ... CONDITION with named conditions in handler lists, and [label:] LOOP ... END LOOP [label]; CONDITION and LOOP become reserved words
mysql_dml INTO OUTFILE ... CHARACTER SET, multiple locking clauses, INTO before locking, derived-table column lists on (VALUES ...), LOAD DATA CONCURRENT/PARTITION/IGNORE n ROWS
mysql_functions JSON_TABLE (ordinality, typed PATH columns with ON EMPTY/ON ERROR, EXISTS, NESTED), JSON_VALUE RETURNING/ON EMPTY/ON ERROR, CAST ... AT TIME ZONE, CAST AS NCHAR, SOUNDS LIKE (desugared to SOUNDEX(a) = SOUNDEX(b), MySQL's definition)
mysql_routines the full CREATE PROCEDURE characteristic list (reusing the RoutineCharacteristics machinery from #41), dollar-quoted strings in the lexer ($$...$$, $tag$...$tag$), LANGUAGE JAVASCRIPT AS-string bodies for procedures/functions/libraries, and the USING (lib [AS alias], ...) imports clause
mysql_account BY RANDOM PASSWORD, multi-factor auth (AND IDENTIFIED, ADD/MODIFY/DROP n FACTOR, registration flow), dual passwords (REPLACE/RETAIN CURRENT PASSWORD/DISCARD OLD PASSWORD), DEFAULT ROLE on CREATE/ALTER USER, PASSWORD REQUIRE CURRENT [OPTIONAL], GRANT ... AS user WITH ROLE, GRANT role WITH ADMIN OPTION, REVOKE IF EXISTS/IGNORE UNKNOWN USER/PROXY, SET PASSWORD TO RANDOM/REPLACE/RETAIN

Notable decisions

  • New AST nodes: JSONTableExpr/JSONTableColumn/JSONOnResponse/JSONValueExpr (ast/mysql_json.go), ProcedureLoopStmt, ProcedureConditionDecl, ProcedureErrorName, RoutineImport, AlterUserFactor, UserDefaultRoles, GrantAs, RevokeProxyStmt — everything else extends existing nodes with new fields.
  • Keyword tables: 27 new keywords (25 unreserved; CONDITION and LOOP reserved, matching MySQL 26.7); GET DIAGNOSTICS ... CONDITION now matches the token instead of an identifier spelling. TestKeywordsLength counts updated.
  • Dollar quoting falls back to the '$'-led identifier reading when no closing tag matches, so $foo-style identifiers keep working.
  • Two pre-existing restore bugs surfaced by the new round-trips got fixed: LEAVE/ITERATE restored their label as a quoted string (unparseable), and PASSWORD REQUIRE CURRENT DEFAULT had no Restore case.
  • Test-only: the round-trip harness's nodeTextCleaner learns to clean ProcedureBlock statement lists (whose Accept deliberately skips them) and to canonicalize procedure parameter types the way CompactStr displays them.

Closes #14, closes #15, closes #17, closes #18, closes #19, closes #20, closes #21, closes #23, closes #28, closes #29, closes #30#43 noted the new goldens covered every parse gap tracked by those issues, and all of those goldens now parse.

Test plan

  • go test ./... -count=1 -timeout 120s passes, before and after the test-group reorganization
  • All 148 error goldens are now Restore() goldens (grep -r '^-- error' parser/testdata/parser/*/output.sql finds none in the touched groups); every passing case is round-tripped by the harness
  • go vet ./... is clean

🤖 Generated with Claude Code

https://claude.ai/code/session_01GBD2pnzaVgeXtHgWwUqxUg

claude added 13 commits August 20, 2026 23:58
…lists, and LOCK TABLES aliases

Implement the mysql_unsupported_txn coverage group (MySQL 26.7 §15.3.1,
§15.3.6): its error goldens turn into Restore() goldens.

- BEGIN WORK, COMMIT WORK, and ROLLBACK WORK accept the optional WORK
  keyword; Restore() drops it, matching the existing BEGIN -> START
  TRANSACTION canonicalization.
- START TRANSACTION now parses a comma-separated transaction
  characteristic list (WITH CONSISTENT SNAPSHOT | READ WRITE | READ
  ONLY), keeping the TiDB READ ONLY AS OF and WITH CAUSAL CONSISTENCY
  ONLY extensions.
- LOCK TABLES accepts [[AS] alias] per table (new TableLock.Alias field,
  restored with AS) and the deprecated LOW_PRIORITY WRITE lock type (new
  TableLockWriteLowPriority appended to the TableLockType enum).

Keyword tables: WORK becomes an unreserved keyword;
TestKeywordsLength count updated. testdata/errors.json is unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBD2pnzaVgeXtHgWwUqxUg
…and REPLICA STATUS FOR CHANNEL

Implement the mysql_unsupported_show coverage group (MySQL 26.7 §15.7.7):
its error goldens turn into Restore() goldens.

- SHOW STORAGE ENGINES parses; Restore() canonicalizes to SHOW ENGINES.
- SHOW WARNINGS and SHOW ERRORS accept SelectStmtLimitOpt, stored in the
  existing ShowStmt.Limit field and restored after the keyword.
- SHOW EXTENDED INDEX/INDEXES/KEYS FROM|IN parses, reusing
  ShowStmt.Extended; ShowIndex Restore() now writes the EXTENDED prefix.
- SHOW REPLICA STATUS accepts FOR CHANNEL, stored in the existing
  ShowStmt.ChannelName field and restored after the keyword.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBD2pnzaVgeXtHgWwUqxUg
…ATTRIBUTE, ALTER DATABASE READ ONLY, and bare WITH CHECK OPTION

Implement the mysql_unsupported_ddl coverage group (MySQL 26.7 §15.1):
its error goldens turn into Restore() goldens.

- The AUTOEXTEND_SIZE table option now also accepts an integer literal
  value (previously only StringName forms like '4M' parsed).
- CREATE TABLE ... START TRANSACTION parses as a new
  TableOptionStartTransaction table option (parsed and carried through
  Restore; like AUTOEXTEND_SIZE it has no storage-engine effect here).
- ENGINE_ATTRIBUTE joins SECONDARY_ENGINE_ATTRIBUTE as an index option
  (new IndexOption.EngineAttr field).
- ALTER DATABASE ... READ ONLY = {DEFAULT | 0 | 1} parses as a new
  DatabaseOptionReadOnly database option.
- CREATE VIEW and ALTER VIEW accept WITH CHECK OPTION without a
  CASCADED/LOCAL qualifier, meaning CASCADED as in MySQL.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBD2pnzaVgeXtHgWwUqxUg
… BINARY LOGS AND GTIDS TO

Implement the mysql_unsupported_replication coverage group (MySQL 26.7
§15.4.2.1, §15.4.1.2): its error goldens turn into Restore() goldens.

ReplicationSourceOption gains three value forms beyond literals, each a
new backwards-compatible field selected by the option name:

- IGNORE_SERVER_IDS = (n, ...) parses into ServerIDs (non-nil but empty
  for an empty list, so () round-trips).
- PRIVILEGE_CHECKS_USER takes an account name (User) or NULL.
- REQUIRE_TABLE_PRIMARY_KEY_CHECK and
  ASSIGN_GTIDS_TO_ANONYMOUS_TRANSACTIONS take bare keyword values
  (STREAM, GENERATE, ON, OFF, LOCAL), stored uppercase in KeywordValue;
  the ASSIGN_GTIDS uuid string form stays a literal Value.

ChangeReplicationSourceStmt.Accept now skips options without a literal
Value instead of dereferencing nil.

RESET BINARY LOGS AND GTIDS accepts the TO binary_log_file_index_number
clause (new ResetBinaryLogsAndGtidsStmt.To field, zero when absent).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBD2pnzaVgeXtHgWwUqxUg
…ble wildcards

Implement the mysql_unsupported_utility coverage group (MySQL 26.7
§15.8.2): its error goldens turn into Restore() goldens.

- The FORMAT = value of EXPLAIN [ANALYZE] now also accepts a bare
  identifier (e.g. TREE), yielding its spelling like the keyword
  alternatives; Restore() writes formats as quoted strings, unchanged.
- EXPLAIN FORMAT = ... INTO @var parses into the new
  ExplainStmt.IntoVar field, restored between the format and the
  explained statement.
- DESCRIBE tbl 'wildcard' parses the column wildcard into the inner
  ShowStmt's existing Pattern field (a LIKE pattern, as SHOW COLUMNS
  does); the DESC form of ExplainStmt Restore() writes it as a plain
  string since DESC does not accept a charset-prefixed literal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBD2pnzaVgeXtHgWwUqxUg
…nsions, FLUSH variants, and SET PERSIST

Implement the mysql_unsupported_admin coverage group (MySQL 26.7 §15.7):
its error goldens turn into Restore() goldens.

- ANALYZE TABLE ... UPDATE HISTOGRAM gains USING DATA 'json' (new
  AnalyzeTableStmt.HistogramData field) and the AUTO/MANUAL UPDATE
  clause (new HistogramUpdate field with HistogramUpdateType), composing
  with WITH n BUCKETS.
- INSTALL COMPONENT ... SET now works with PERSIST-scoped assignments
  (see below).
- DROP RESOURCE GROUP accepts FORCE (new field); SET RESOURCE GROUP
  accepts FOR thread_id, ... (new ThreadIDs field).
- FLUSH gains OPTIMIZER_COSTS and USER_RESOURCES option types, RELAY
  LOGS [FOR CHANNEL ch] (new LogTypeRelay and Channel field), TABLES ...
  FOR EXPORT (new ForExport field), and comma-separated option lists
  (new MoreOptions field; the option rendering split into
  restoreOption).
- SET PERSIST / PERSIST_ONLY parse as new VariableAssignment
  IsPersist/IsPersistOnly flags (also implying IsGlobal), restored as
  @@persist./@@PERSIST_ONLY. system variables; the lexer's @@ prefix
  list learns those spellings so the restored form round-trips. Scopes
  mix freely in one SET list.

Keyword tables: AUTO, EXPORT, OPTIMIZER_COSTS, PERSIST_ONLY, RELAY, and
USER_RESOURCES become unreserved keywords; TestKeywordsLength counts
updated. testdata/errors.json is unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBD2pnzaVgeXtHgWwUqxUg
…T (expr) with operators

Implement the mysql_unsupported_types coverage group (MySQL 26.7
§13.1.20, §15.1.9): its error goldens turn into Restore() goldens.

- VISIBLE and INVISIBLE parse as column attributes (new
  ColumnOptionVisible/ColumnOptionInvisible), covering CREATE TABLE,
  ADD/MODIFY COLUMN, and the GIPK SHOW CREATE TABLE round-trip through
  the /*!80023 ... */ versioned comment.
- ALTER TABLE ... ALTER COLUMN col SET {VISIBLE|INVISIBLE} parses as a
  new AlterTableAlterColumnVisibility spec, reusing the existing
  AlterTableSpec.Visibility field.
- ENGINE_ATTRIBUTE joins SECONDARY_ENGINE_ATTRIBUTE as a column
  attribute (new ColumnOptionEngineAttribute), with and without =.
- DEFAULT (expr): when the goyacc-shaped '('-led DefaultValueExpr
  alternatives do not span the parentheses, the content reparses as a
  full parenthesized expression, so operators and INTERVAL arithmetic
  parse. BinaryOperationExpr defaults restore inside parentheses, which
  MySQL requires; other shapes keep their existing Restore() output.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBD2pnzaVgeXtHgWwUqxUg
Implement the mysql_unsupported_compound coverage group (MySQL 26.7
§15.6.5, §15.6.7.1): its error goldens turn into Restore() goldens.

- DECLARE name CONDITION FOR {SQLSTATE [VALUE] 'x' | mysql_error_code}
  parses as a new ProcedureConditionDecl declaration, and handler
  condition lists accept a declared condition name (new
  ProcedureErrorName ErrNode).
- [label:] LOOP ... END LOOP [label] parses as a new ProcedureLoopStmt,
  joining WHILE/REPEAT in the unlabeled and labeled loop productions.

Keyword tables: CONDITION and LOOP become reserved words, matching
their MySQL 26.7 classification; GET DIAGNOSTICS ... CONDITION now
matches the token instead of an identifier spelling.
TestKeywordsLength counts updated. testdata/errors.json is unaffected.

ProcedureJump (LEAVE/ITERATE) restored its label as a quoted string
literal, which does not re-parse; it now restores as an identifier.
The test-only nodeTextCleaner also learns to clean the statements of a
ProcedureBlock, whose Accept deliberately does not traverse them, so
procedure bodies compare deep-equal in the round-trip harness.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBD2pnzaVgeXtHgWwUqxUg
…nt, derived-table column lists, and LOAD DATA options

Implement the mysql_unsupported_dml coverage group (MySQL 26.7 §15.2.13,
§15.2.9): its error goldens turn into Restore() goldens.

- SELECT ... INTO OUTFILE accepts CHARACTER SET (new
  SelectIntoOption.Charset field), in both the mid-statement and
  trailing INTO positions.
- A SELECT may carry several locking clauses (FOR SHARE OF t1 NOWAIT
  FOR UPDATE OF t2 SKIP LOCKED): the first stays in SelectStmt.LockInfo
  and the rest land in the new MoreLockInfos field; the lock rendering
  moved into a restoreSelectLockInfo helper shared by both.
- The SelectStmtIntoOption also parses between the limit and locking
  clauses (SELECT ... INTO @A FOR UPDATE); Restore() keeps the trailing
  position, which MySQL also accepts.
- Derived tables accept the column alias list ((VALUES ...) AS v (c1,
  c2)), filling the existing TableSource.ColumnNames field that only
  LATERAL tables populated.
- LOAD DATA gains CONCURRENT (new field, mirroring LoadXMLStmt),
  PARTITION (p, ...) (new Partitions field), and IGNORE n ROWS
  (canonicalized to LINES like LOAD XML).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBD2pnzaVgeXtHgWwUqxUg
…and SOUNDS LIKE

Implement the mysql_unsupported_functions coverage group (MySQL 26.7
§14.17.6, §14.17.3, §14.10): its error goldens turn into Restore()
goldens.

- JSON_TABLE(expr, path COLUMNS (...)) parses as a table factor: new
  JSONTableExpr ResultSetNode (ast/mysql_json.go) with JSONTableColumn
  covering FOR ORDINALITY, typed PATH columns with ON EMPTY/ON ERROR
  responses, EXISTS PATH, and NESTED PATH sub-columns. The shared
  JSONOnResponse node models NULL/ERROR/DEFAULT v ON EMPTY|ERROR.
- JSON_VALUE with RETURNING and/or ON EMPTY/ON ERROR parses as a new
  JSONValueExpr; the plain two-argument call stays a generic
  FuncCallExpr, selected by speculation.
- CAST accepts AT TIME ZONE 'tz' between the expression and AS (new
  FuncCastExpr.AtTimeZone field) and NCHAR[(n)] as a cast type
  (canonicalized to CHAR in the national character set).
- expr SOUNDS LIKE expr parses, desugaring to SOUNDEX(l) = SOUNDEX(r)
  as MySQL defines the operator.

Keyword tables: NESTED, ORDINALITY, PATH, RETURNING, SOUNDS, and ZONE
become unreserved keywords, and EMPTY (whose token existed unused)
joins the tables; TestKeywordsLength counts updated.
testdata/errors.json is unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBD2pnzaVgeXtHgWwUqxUg
…ollar quoting, and the USING clause

Implement the mysql_unsupported_routines coverage group (MySQL 26.7
§15.1.17, §11.1.1, §15.1.19): its error goldens turn into Restore()
goldens.

- CREATE PROCEDURE now accepts the full routine characteristic list
  (COMMENT, LANGUAGE, [NOT] DETERMINISTIC, CONTAINS/NO SQL,
  READS/MODIFIES SQL DATA, SQL SECURITY), reusing the
  RoutineCharacteristics machinery CREATE FUNCTION already had (new
  ProcedureInfo.Characteristics field).
- Dollar-quoted strings ($$...$$ and $tag$...$tag$) lex as string
  literals; an opening tag without a matching closing tag falls back to
  the '$'-led identifier reading. Restore() writes them as ordinary
  quoted strings.
- LANGUAGE JAVASCRIPT bodies: CREATE PROCEDURE and CREATE FUNCTION
  accept AS 'code' (string or dollar-quoted) via the new CodeBody
  fields, plus the USING (library [AS alias], ...) imports clause (new
  RoutineImport node and Imports fields). CREATE LIBRARY $$-bodies now
  parse through the same lexer change.
- ProcedureInfo.Accept no longer dereferences a nil body.

Restore() prints procedure parameter types via CompactStr, which
substitutes default display widths; the round-trip harness canonicalizes
parsed parameter types the same way and clears the recorded parameter
source text (test-only nodeTextCleaner cases).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBD2pnzaVgeXtHgWwUqxUg
…OLE, GRANT AS, and the REVOKE variants

Implement the mysql_unsupported_account coverage group (MySQL 26.7
§15.7.1): its error goldens turn into Restore() goldens.

- IDENTIFIED [WITH plugin] BY RANDOM PASSWORD parses (new
  AuthOption.ByRandomPassword), and every auth option accepts the
  dual-password clauses REPLACE 'current' and RETAIN CURRENT PASSWORD
  (new ReplacePassword/RetainCurrentPassword fields). ALTER USER ...
  DISCARD OLD PASSWORD parses as an AuthOption carrying only the new
  DiscardOldPassword flag.
- Multi-factor authentication: user specs chain AND IDENTIFIED ...
  factors (new UserSpec.MoreAuthOpts), and ALTER USER gains
  ADD/MODIFY/DROP n FACTOR plus the n FACTOR INITIATE/FINISH
  REGISTRATION and UNREGISTER steps (new AlterUserFactor clause and
  AlterUserStmt.Factors field).
- CREATE USER and ALTER USER accept DEFAULT ROLE {NONE | ALL | roles}
  (new UserDefaultRoles clause), and the PASSWORD REQUIRE CURRENT
  [OPTIONAL] account policies join the existing DEFAULT form (whose
  Restore case was missing).
- GRANT role TO user accepts WITH ADMIN OPTION (new
  GrantRoleStmt.AdminOption), and GRANT ... accepts AS user [WITH ROLE
  DEFAULT|NONE|ALL|ALL EXCEPT r...|r...] (new GrantAs clause reusing
  SetRoleStmtType).
- REVOKE gains IF EXISTS and IGNORE UNKNOWN USER on both the privilege
  and role forms, and REVOKE PROXY ON user FROM users parses as a new
  RevokeProxyStmt mirroring GrantProxyStmt.
- SET PASSWORD gains TO RANDOM, REPLACE 'old', and RETAIN CURRENT
  PASSWORD (new SetPwdStmt fields).

Keyword tables: CHALLENGE_RESPONSE, FACTOR, FINISH, INITIATE, OLD,
RANDOM, REGISTRATION, RETAIN, and UNREGISTER become unreserved
keywords; TestKeywordsLength counts updated. testdata/errors.json is
unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBD2pnzaVgeXtHgWwUqxUg
With every case now supported, the mysql_unsupported_* names no longer
describe their contents. Following the #39-#41 precedent of renaming an
implemented group, the seven groups whose mysql_* counterpart already
exists (txn, dml, ddl, admin, show, replication, compound) have their
cases appended to it, and the five without one (types, functions,
routines, account, utility) are renamed to mysql_<name>. No case or
golden changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GBD2pnzaVgeXtHgWwUqxUg
@kyleconroy
kyleconroy merged commit 2955bc1 into main Aug 21, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment