Repository navigation
Conversation
…rces and MERGE DO NOTHING
UPDATE, DELETE and MERGE take PostgreSQL's relation_expr_opt_alias as the
target: ONLY t, ONLY (t) and t * with an alias, kept in a TableReference
next to getTable(). UPDATE and DELETE read WHERE CURRENT OF cursor into a
cursor name. A SET column list takes any expression as its source, such
as ROW(...), through one UpdateSet production. MERGE reads DO NOTHING for
every WHEN condition, INSERT DEFAULT VALUES and INSERT ... OVERRIDING
{SYSTEM | USER} VALUE; the WHEN prefix is rendered by MergeWhen.
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
PostgreSQL 18 accepts several forms of
UPDATE,DELETEandMERGEthat the parser rejects, in the PostgreSQL dialect and without a dialect:This change parses them, following
gram.yof PostgreSQL 18 (UPDATE, DELETE, MERGE).Current design (As-is)
Update,DeleteandMergehold the target as aTable.ONLYis refused as a table name in front of another name (RelationOptions.isOnlyAhead), and no production reads it as a keyword there, soUPDATE ONLY t …,DELETE FROM ONLY t …andMERGE INTO ONLY t …fail.t *fails as well.LOCKandTRUNCATEalready shareRelationTarget()andTableReferencefor the same PostgreSQLrelation_expr.WHEREis always an expression, soWHERE CURRENT OF cfails atOF.UpdateSets()writes the two alternatives of an assignment twice (first item, following items). A column list takes a parenthesized list or a sub-select only, so(a, b) = ROW(1, 2)fails.MergeDelete,MergeUpdateandMergeInserteach write theirWHEN …prefix, intoString()and again inMergeDeParser(six copies). There is noDO NOTHINGaction, andMergeInsertClause()requiresVALUES (…)directly after the optional column list.Target design (To-be)
Target (
relation_expr_opt_alias). ADmlTarget()production is shared byUPDATE,DELETEandMERGE. WithONLYin front of a name, or*after it, it reusesRelationTarget()and reads the alias after it; otherwise it isTableWithAlias()as before.Update,DeleteandMergekeep the target in aTableReference(getTableReference()/setTableReference()/withTableReference()).getTable()andsetTable()read and replace the table of that reference, so there is one source for the table.TableReference.appendWithAliasTo()rendersONLY t AS x,ONLY (t) xandt * AS x;toString()and the deparsers both use it. A target without a scope renders throughTableas before, with its alias and hints.ONLYthere, so in those dialects the forms stay as they are onmaster.WHERE CURRENT OF cursor.UpdateandDeleteget acursorName(getCursorName()/setCursorName()/withCursorName()), read by a sharedWhereCurrentOfClause(). The cursor is a name (ColId), not an expression:getWhere()staysnull, andWHERE CURRENT OF c AND …or a qualified cursor name is rejected as in PostgreSQL. The positioned forms are standard SQL and are read in every dialect; two Oracle corpus cases (condition17.sql,condition18.sql) now parse.SETitems (set_clause).UpdateSets()is a list of oneUpdateSet()production. A column list takes any expression as its source, asgram.ydoes ('(' set_target_list ')' '=' a_expr):ROW(…),DEFAULT, a function call. The expression is the single element ofUpdateSet.getValues(), in the way a sub-select already is;(a, b) = (1, 2)keeps one value per column.ROW(…)is theFunctionit is in other expressions (a = ROW(1, 2)).MERGE.
MergeWhen(MATCHED,NOT_MATCHED,NOT_MATCHED_BY_SOURCE,NOT_MATCHED_BY_TARGET) renders theWHEN … [AND …] THENprefix once fortoString()and the deparser.MergeDelete,MergeUpdateandMergeInsertderivegetWhen()from their existingside.MergeDoNothingis the new action for all four conditions.MergeOperationVisitorgets a defaultvisit(MergeDoNothing, …)so existing visitors compile and skip it;MergeOperationVisitorAdapter,MergeDeParser,MergeValidatorandTablesNamesFindervisit its predicate.MergeInsertreadsINSERT DEFAULT VALUES(isDefaultValues(): an insert without a values list) andOVERRIDING {SYSTEM | USER} VALUE(getOverridingMode(), the enum ofInsert). As ingram.y,DEFAULT VALUEStakes neither a column list norOVERRIDING.WHERE/DELETE WHEREclauses, SQL Server'sOUTPUTand theBY SOURCE/BY TARGETpairing checks are unchanged.Rejected, as by PostgreSQL:
UPDATE ONLY t * …,UPDATE (t) …,DELETE FROM t AS x * …,WHERE CURRENT OF sc.c,SET () = ROW(1, 2),SET (a + 1) = ROW(1),WHEN MATCHED THEN INSERT DEFAULT VALUES,INSERT (id) DEFAULT VALUES,INSERT OVERRIDING SYSTEM VALUE DEFAULT VALUES,THEN DO NOTHING WHERE ….Not part of this change:
ONLYin aUSINGorFROMsource (MERGE INTO t USING ONLY s …), which fails onmasteras well.Validation
New
PostgreSQLModifyFormsTest: 157 tests (targets, cursor forms,SETsources, MERGE clauses; model fields,toString(),StatementDeParser, reparse, rejected forms, MySQL/MariaDB/Oracle/SQL Server/SQLite readings, statement boundaries, visitors,TablesNamesFinder, validation)../gradlew spotlessApply cleanTest check jar: 13,491 tests, 0 failures (25 skipped), includingcheckGrammarAmbiguity, Checkstyle, PMD, SpotBugs and JaCoCo.Compared with
master(e27929f76) on a corpus of PostgreSQL 18 and MySQL 8.4 statements (DDL/DCL and other statements, parsed with the dialect and without): 7 PostgreSQL statements that failed now parse and round-trip (UPDATE ONLY … AS,UPDATE … WHERE CURRENT OF,UPDATE … SET (…) = ROW(…),DELETE FROM ONLY … AS,DELETE … WHERE CURRENT OF,MERGE … DO NOTHING,MERGE … INSERT DEFAULT VALUES), in both modes; no regressions and no other changes.PostgreSQL 18.6 accepts the syntax of 104 statements that this branch parses, 82 of which
masterrejects; some stop only at execution, for example with an unknown cursor. It reports a syntax error for 59 statements that this branch rejects. Eight statements differ:master:DELETE FROM only(a table namedonly, valid in MySQL),UPDATE t SET a = DEFAULT + 1(gram.yaccepts it),MERGEwithout aWHENclause, and the three Oracle formsINSERT … VALUES (…) WHERE …,UPDATE SET … WHERE …andUPDATE SET … DELETE WHERE ….MERGE … WHEN MATCHED THEN DO NOTHING WHEN MATCHED THEN DELETEparses:gram.yaccepts it and PostgreSQL reports the unreachable second clause after parsing. The same clauses with a condition on the first one are accepted by the server.MERGE INTO t x USING ONLY s …is accepted by PostgreSQL and fails here as onmaster(see above).AI assistance: prepared with Claude Code.