Repository navigation
Conversation
… DELETE USING joins MySQL 8.4 index hints with FOR JOIN/ORDER BY/GROUP BY, several hints on one table, USE INDEX () and IGNORE KEY failed, as did WEIGHT_STRING(x AS CHAR(n)), INSERT ... VALUES ROW(...), INSERT ... TABLE t, INSERT INTO t () VALUES () and DELETE FROM t1 USING t1 JOIN t2. INSERT ... PARTITION (p) (cols) rendered PARTITION after the column list, and toString() and the deparsers disagreed on several INSERT and REPLACE forms. Keep every index hint in Table.getIndexHints() with a typed MySQLIndexHint.Scope, parsed by one list production and rendered by one helper; getIndexHint() returns the first. Store the WEIGHT_STRING cast in Function.getCastType(), a ColDataType built as in CAST from exactly CHAR(n) or BINARY(n) after the single argument. Let VALUES ROW( and TABLE name reach the existing Values and TableStatement sources, and share an InsertColumnList production that accepts () between INSERT and REPLACE. Hold the joins of a DELETE USING clause in Delete.getUsingJoins() and render the target's joins before USING. The cast type and the REPLACE column list carry nodes spanning their source. Render INSERT through one ordered Insert.appendTo with renderer callbacks, used by toString() and InsertDeParser, with callback appendTo methods on InsertDuplicateAction, InsertConflictAction and OracleMultiInsertClause. The deparser now keeps ON DUPLICATE KEY UPDATE ... WHERE and visits the ON CONFLICT action; the unused Insert.duplicateUpdateSets field is gone. Signed-off-by: minleejae <mmj9808@gmail.com>
This branch has not been deployed
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.
Purpose
Several MySQL 8.4 DML statements fail to parse, and several INSERT and REPLACE forms render differently in
toString()andStatementDeParser:SELECT a FROM t IGNORE INDEX FOR ORDER BY (i1) ORDER BY a,SELECT a FROM t FORCE INDEX FOR JOIN (i1) JOIN s ON t.a = s.a,USE INDEX (), a second hint on the same table (USE INDEX (i1) IGNORE INDEX (i2)) andIGNORE KEY (i1)fail.WEIGHT_STRING:SELECT WEIGHT_STRING('x' AS CHAR(3))fails, also withAS BINARY(n).INSERT INTO t VALUES ROW(1, 2), ROW(3, 4),INSERT INTO t TABLE s,INSERT INTO t () VALUES ()andREPLACE INTO t () VALUES ()fail, although the queriesVALUES ROW(1, 2)andTABLE sparse.DELETE FROM t1 USING t1 JOIN t2 ON t1.id = t2.idfails. PostgreSQL'sDELETE FROM t USING s JOIN u ON …fails the same way.INSERT INTO t PARTITION (p0) (a) VALUES (1)renders asINSERT INTO t (a) PARTITION (p0) VALUES (1). MySQL rejects that, and JSqlParser cannot parse it again.StatementDeParserrendersINSERT INTO t (a, b) VALUES (1, 2) AS new ON DUPLICATE KEY UPDATE …andREPLACE INTO t(a, b) VALUES (1, 2), whiletoString()uses single spaces.Current design (As-is)
Tableholds one immutableMySQLIndexHint(action,INDEX/KEYand the names). The grammar reads at most one hint per table and has noFORscope.IGNOREbeforeKEYis read as an alias of the table.Table.appendToandSelectDeParser.visit(Table)each render the hint.CHAR(77 USING utf8mb4)is aFunctionwith a keyword argument.ASis not accepted inside a function call.INSERT … VALUES ROW(…)andINSERT … TABLE salready go throughSelect(), which yieldsValuesandTableStatement. The alias check before the source readsVALUES(beforeROW) andTABLEas an alias of the target, so the source is never reached. The column list requires at least one column.Insert.toString()andInsertDeParserare two hand-written copies, and they disagree:PARTITIONafter the column list andOVERRIDING;toString()adds a second space.toString()put the hint first.ON DUPLICATE KEY UPDATE … WHERE …, the deparser drops theWHERE.ON CONFLICTaction is appended through itstoString(), so the deparser's expression visitor never sees it.toString()leaves double spaces beforeOUTPUTand theSET … AS newrow alias, andON DUPLICATE KEY UPDATE NOTHINGrenders with extra spaces.Insert.duplicateUpdateSetsis never written.UpsertDeParserrenders the column list without a space, and it rendersON DUPLICATE KEY UPDATEonly when there is noSET.FromItem (, FromItem)*and cannot take a JOIN.Delete.toString()andDeleteDeParserrender the target's joins after the USING list, soDELETE FROM t1, t2 USING t1, t2 WHERE …renders asDELETE FROM t1 USING t1, t2, t2 WHERE ….DeleteDeParserdrops the aliases of the target tables thattoString()keeps.StatementVisitorAdaptercasts every USING item toTable, which fails for a subquery.Target design (To-be)
MySQLIndexHint.Scopeenum (JOIN,ORDER_BY,GROUP_BY), with a 4-argument constructor; the 3-argument constructor stays, with no scope.Table.getIndexHints()holds every hint in source order.getIndexHint()returns the first one, andsetHint()replaces the list with one hint (or clears it). NewsetIndexHints,withIndexHints,addIndexHint.MySQLIndexHints(), is used for FROM items, joined tables and the UPDATE target. One render helper,Table.appendIndexHintsTo, is used bytoString()andSelectDeParser.SelectValidatorchecks the index names of every hint.USE INDEX ()has an empty name list.IGNORE/FORCEwith an empty list are rejected, as the server rejects them.IGNOREfollowed byKEYis no longer taken as an alias, as forIGNORE INDEX, soIGNORE KEY (i1)is a hint.Function.getCastType()holds theAS CHAR(n)/AS BINARY(n)type as aColDataType, built asCAST('x' AS CHAR(3))builds it, with an AST node spanningCHAR(3). Both renderers write it after the argument. The grammar accepts exactlyCHAR(n)orBINARY(n)with an integer length, after the single argument ofWEIGHT_STRING; other types,CHARACTER SET, other functions and several arguments are rejected.VALUES ROW(orTABLE <name>as an alias. The sources reuse the query models:ValueswithROW(…)items, as forVALUES ROW(1, 2), andTableStatement.VALUE ROW(…)stays rejected, since onlyVALUESstarts a row constructor list.InsertColumnList()accepts(), and INSERT and REPLACE share it.INSERT INTO t () VALUES ()has an empty, non-nullgetColumns(); without a column list it staysnull. REPLACE keeps itsParenthesedExpressionList, with an AST node spanning the parentheses.Insert.appendTo(builder, withItemRenderer, selectRenderer, expressionRenderer, selectItemRenderer)writes the clauses in grammar order.toString()passesbuilder::append;InsertDeParserpasses its visitors.PARTITIONfollows the table, before the column list. The optimizer hint comes before the priority.InsertDuplicateAction,InsertConflictActionandOracleMultiInsertClauseget the same callbackappendTo, andUpdateSet.appendUpdateSetsTo(builder, sets, expressionRenderer)renders the assignments. The deparser keepsON DUPLICATE KEY UPDATE … WHERE …and visits theON CONFLICTaction.duplicateUpdateSetsfield is removed.getDuplicateUpdateSets()returns the action's update sets, as before.ValuesStatementDeParserrenders theAS newrow alias with one space.UpsertDeParserrendersREPLACE INTO t (a, b)and theON DUPLICATE KEY UPDATEaction throughInsertDuplicateAction.appendTo.Delete.getUsingJoins()holds the joins after the USING items, for MySQL'sDELETE FROM t1 USING t1 JOIN t2 …and PostgreSQL'sDELETE FROM t USING s JOIN u …. A comma list without joins keeps today'sgetUsingFromItemList().getJoins()keeps the target's joins, as inDELETE FROM t1, t2 USING …. Both renderers write them before USING.DeleteDeParserrenders target tables with their aliases.TablesNamesFinderandStatementVisitorAdaptervisit the USING items and joins, andDeleteValidatorvalidates the USING joins like the target's joins.ASTNodeAccess(theWEIGHT_STRINGcast type, REPLACE's column list, the USING joins) carry nodes spanning their source.ASTNodeCoverageTestwalks every form above.MySQLIndexHints,InsertColumnList/ParenthesedInsertColumnList,WeightStringCastType), two semantic lookahead helpers (isInsertTableSourceAhead,isFunctionCastTypeAhead), and two follower conditions in the alias check (VALUES ROW(,IGNORE KEY). The grammar choice conflict check passes. The new forms parse in every dialect, like the index hints andVALUES ROW(…)queries today; no existing reading changes.MySQLIndexHint.Scope,Table.getIndexHints(),Function.getCastType(),Delete.getUsingJoins(),Insert.appendTo(…)and the callbackappendTomethods.Table.getIndexHint()/setHint()/withHint()keep working.INSERT … PARTITIONorder and spacing;ON DUPLICATE KEY UPDATE NOTHING(InsertDuplicateAction.toString()is nowNOTHING); the deparser's WHERE, hint order and spacing; target joins of DELETE before USING; target aliases inDeleteDeParser.Deletebuilt with bothjoinsand USING items now renders the joins before USING. Joins of the USING clause belong inusingJoins.usage.rstdocuments the hint list and scope,getCastType(), the insert sources andgetUsingJoins().Validation
109 new tests:
MySqlIndexHintTest(22): the reported statements withDialect.MYSQLand without a dialect, exacttoString()and deparser output, every scope,USE KEY,IGNORE KEY,USE INDEX (), several hints, hints on joined tables and the UPDATE target,FOR UPDATEafter a hint, rejected forms, statement boundary,TablesNamesFinder, validation and model edits.WeightStringFunctionTest(20):AS CHAR(n)andAS BINARY(n)with and without a dialect, the sameColDataTypeasCAST, the cast type's node, theCHAR(… USING …)keyword argument, nesting, rejected forms (other types,CHARACTER SET, a non-integer length, several arguments,DISTINCT, other functions), statement boundary and the model.MySqlInsertSourceTest(18):VALUES ROW(…),TABLE sand() VALUES ()for INSERT and REPLACE, AST fields, REPLACE's column list node,TABLEstill as an alias before other sources, other dialects, rejected forms (includingVALUE ROW(…)), statement boundaries,TablesNamesFinderand validation.InsertRenderingTest(32): identicaltoString(), deparser and reparse output for PARTITION, row aliases,ON DUPLICATE KEY UPDATE … WHERE,NOTHING, OUTPUT, ON CONFLICT, BY NAME, WITH, Oracle multi-table INSERT and REPLACE/UPSERT; visited expressions in both actions and the SET list;appendTocallbacks;getDuplicateUpdateSets().DeleteUsingJoinTest(17): MySQL and PostgreSQL USING joins, several targets, comma lists followed by joins, target alias rendering, unchanged forms, rejected forms, statement boundary,TablesNamesFinder,StatementVisitorAdapter, validation and the model.ASTNodeCoverageTestwalks the new forms.Java 17 offline Gradle
spotlessApply check: 12,738 tests, 12,713 passed, 25 skipped, zero failures/errors. The grammar choice conflict check passes.Compared with
master(4a970a34e) on a corpus of 188 MySQL 8.4 and 166 PostgreSQL 18 statements other than DDL/DCL, each accepted by the server, with the MySQL or PostgreSQL dialect and without a dialect:Dialect.MYSQLand without a dialect (11 each):WEIGHT_STRING('x' AS CHAR(3)),INSERT … VALUES ROW(…),INSERT INTO t TABLE s,INSERT INTO t () VALUES ()andDELETE FROM t1 USING t1 JOIN t2 …;toString()and the deparser:INSERT INTO t PARTITION (p0) (a) VALUES (1)(which also parses again), bothINSERT … AS new …statements andREPLACE INTO t (a, b) VALUES (1, 2).Compared with
master(4a970a34e) on a corpus of 635 MySQL 8.4 and PostgreSQL 18 DDL/DCL statements, each accepted by the server, plus variants, scripts and the keyword sweeps: 0 regressed, 0 changed.On 395 INSERT, REPLACE, UPSERT and DELETE statements taken from the test suite and the corpora, every statement that
masterparses (312) still parses, and 4 more parse.toString()andStatementDeParserdiffer for 34 of them onmasterand for none on this branch.The statements of the new tests were run against MySQL 8.4.11 (63 accepted and 31 rejected forms) and PostgreSQL 18.3 (11 forms). Every form the parser accepts with the dialect is accepted by the server's grammar, and every form it rejects is a syntax error on the server. One leniency stays as on
master: MySQL rejects a row alias after aVALUES ROW(…)list (INSERT INTO t (a, b) VALUES ROW(1, 2), ROW(3, 4) AS new …), whichmasteralready parses.AI assistance: prepared with Claude Code.