From 78b8177f478b70f6ab909e49ba3c196fb3204d81 Mon Sep 17 00:00:00 2001 From: minleejae Date: Sat, 12 Sep 2026 21:44:00 +0900 Subject: [PATCH] Render DML RETURNING expressions through shared visitor handling --- .../util/deparser/AbstractDeParser.java | 9 +++ .../util/deparser/DeleteDeParser.java | 4 +- .../util/deparser/InsertDeParser.java | 4 +- .../util/deparser/UpdateDeParser.java | 4 +- .../deparser/DmlReturningExpressionTest.java | 57 +++++++++++++++++++ 5 files changed, 69 insertions(+), 9 deletions(-) create mode 100644 src/test/java/net/sf/jsqlparser/util/deparser/DmlReturningExpressionTest.java diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/AbstractDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/AbstractDeParser.java index 6458f79c9c..aa56a50762 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/AbstractDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/AbstractDeParser.java @@ -9,6 +9,7 @@ */ package net.sf.jsqlparser.util.deparser; +import net.sf.jsqlparser.statement.ReturningClause; import net.sf.jsqlparser.expression.ExpressionVisitor; import net.sf.jsqlparser.statement.update.UpdateSet; @@ -26,6 +27,14 @@ protected AbstractDeParser(StringBuilder builder) { this.builder = builder; } + protected void deparseReturningClause(ReturningClause clause, + ExpressionVisitor visitor) { + if (clause != null) { + SelectDeParser selectItems = new SelectDeParser(visitor, builder); + clause.appendTo(builder, item -> item.accept(selectItems, null)); + } + } + public static void deparseUpdateSets(List updateSets, StringBuilder buffer, ExpressionVisitor visitor) { ExpressionListDeParser expressionListDeParser = diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/DeleteDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/DeleteDeParser.java index 7472c968ff..21842c094e 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/DeleteDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/DeleteDeParser.java @@ -109,9 +109,7 @@ public void deParse(Delete delete) { builder.append(delete.getOption()); } - if (delete.getReturningClause() != null) { - delete.getReturningClause().appendTo(builder); - } + deparseReturningClause(delete.getReturningClause(), expressionVisitor); } diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/InsertDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/InsertDeParser.java index 492c3c5ef2..596af04565 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/InsertDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/InsertDeParser.java @@ -153,9 +153,7 @@ public void deParse(Insert insert) { insert.getConflictAction().appendTo(builder); } - if (insert.getReturningClause() != null) { - insert.getReturningClause().appendTo(builder); - } + deparseReturningClause(insert.getReturningClause(), expressionVisitor); } public ExpressionVisitor getExpressionVisitor() { diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/UpdateDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/UpdateDeParser.java index a6d9c2670c..e2aaf1e89b 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/UpdateDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/UpdateDeParser.java @@ -101,9 +101,7 @@ public void deParse(Update update) { builder.append(update.getOption()); } - if (update.getReturningClause() != null) { - update.getReturningClause().appendTo(builder); - } + deparseReturningClause(update.getReturningClause(), expressionVisitor); } protected void deparseWhereClause(Update update) { diff --git a/src/test/java/net/sf/jsqlparser/util/deparser/DmlReturningExpressionTest.java b/src/test/java/net/sf/jsqlparser/util/deparser/DmlReturningExpressionTest.java new file mode 100644 index 0000000000..a5f247f06f --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/util/deparser/DmlReturningExpressionTest.java @@ -0,0 +1,57 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2019 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.util.deparser; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import java.util.ArrayList; +import java.util.List; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class DmlReturningExpressionTest { + @ParameterizedTest + @ValueSource(strings = {"UPDATE t SET a = 7 RETURNING a + 8 AS result", + "DELETE FROM t WHERE a = 7 RETURNING a + 8 AS result", + "INSERT INTO t(a) VALUES (7) RETURNING a + 8 AS result", + "UPDATE t SET a = 7 RETURNING (SELECT b + 8 FROM source) AS result"}) + void visitsReturnedExpressionsExactlyOnce(String sql) throws Exception { + var statement = CCJSqlParserUtil.parse(sql); + String original = statement.toString(); + List seen = new ArrayList<>(); + ExpressionDeParser expressions = new ExpressionDeParser() { + @Override + public StringBuilder visit(LongValue value, S context) { + seen.add(value.getValue()); + return getBuilder().append(value.getValue() + 100); + } + }; + StringBuilder output = new StringBuilder(); + statement.accept(new StatementDeParser(expressions, new SelectDeParser(), output), null); + assertEquals(List.of(7L, 8L), seen); + assertEquals(original.replace("7", "107").replace("8", "108"), output.toString()); + assertEquals(original, statement.toString()); + assertEquals(output.toString(), CCJSqlParserUtil.parse(output.toString()).toString()); + } + + @ParameterizedTest + @ValueSource(strings = {"INSERT INTO t(a) VALUES (1) RETURNING *", + "DELETE FROM t RETURNING t.*, a AS result", + "UPDATE t SET a = 1 RETURNING a, b INTO x, y", + "DELETE FROM t RETURNING WITH (OLD AS o, NEW AS n) o.*, n.id"}) + void preservesWildcardsAliasesAndOutputTargets(String sql) throws Exception { + var statement = CCJSqlParserUtil.parse(sql); + StringBuilder output = new StringBuilder(); + statement.accept(new StatementDeParser(output), null); + assertEquals(statement.toString(), output.toString()); + assertEquals(statement.toString(), CCJSqlParserUtil.parse(output.toString()).toString()); + } +}