Skip to content

Commit f47a8b3

Browse files
authored
feat: support PostgreSQL 18 RETURNING OLD/NEW (#2398)
1 parent b19d556 commit f47a8b3

File tree

7 files changed

+439
-10
lines changed

7 files changed

+439
-10
lines changed

src/main/java/net/sf/jsqlparser/schema/Column.java

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@
1212
import java.util.ArrayList;
1313
import java.util.Collections;
1414
import java.util.List;
15-
1615
import net.sf.jsqlparser.expression.ArrayConstructor;
1716
import net.sf.jsqlparser.expression.Expression;
1817
import net.sf.jsqlparser.expression.ExpressionVisitor;
1918
import net.sf.jsqlparser.expression.operators.relational.SupportsOldOracleJoinSyntax;
2019
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
20+
import net.sf.jsqlparser.statement.ReturningReferenceType;
2121

2222
/**
2323
* A column. It can have the table name it belongs to.
@@ -30,6 +30,8 @@ public class Column extends ASTNodeAccessImpl implements Expression, MultiPartNa
3030
private ArrayConstructor arrayConstructor;
3131
private String tableDelimiter = ".";
3232
private int oldOracleJoinSyntax = SupportsOldOracleJoinSyntax.NO_ORACLE_JOIN;
33+
private ReturningReferenceType returningReferenceType = null;
34+
private String returningQualifier = null;
3335

3436
// holds the physical table when resolved against an actual schema information
3537
private Table resolvedTable = null;
@@ -215,7 +217,9 @@ public String getUnquotedName() {
215217
public String getFullyQualifiedName(boolean aliases) {
216218
StringBuilder fqn = new StringBuilder();
217219

218-
if (table != null) {
220+
if (returningQualifier != null) {
221+
fqn.append(returningQualifier);
222+
} else if (table != null) {
219223
if (table.getAlias() != null && aliases) {
220224
fqn.append(table.getAlias().getName());
221225
} else {
@@ -284,6 +288,31 @@ public Column withOldOracleJoinSyntax(int oldOracleJoinSyntax) {
284288
return this;
285289
}
286290

291+
public ReturningReferenceType getReturningReferenceType() {
292+
return returningReferenceType;
293+
}
294+
295+
public Column setReturningReferenceType(ReturningReferenceType returningReferenceType) {
296+
this.returningReferenceType = returningReferenceType;
297+
return this;
298+
}
299+
300+
public String getReturningQualifier() {
301+
return returningQualifier;
302+
}
303+
304+
public Column setReturningQualifier(String returningQualifier) {
305+
this.returningQualifier = returningQualifier;
306+
return this;
307+
}
308+
309+
public Column withReturningReference(ReturningReferenceType returningReferenceType,
310+
String returningQualifier) {
311+
this.returningReferenceType = returningReferenceType;
312+
this.returningQualifier = returningQualifier;
313+
return this;
314+
}
315+
287316
public String getCommentText() {
288317
return commentText;
289318
}

src/main/java/net/sf/jsqlparser/statement/ReturningClause.java

Lines changed: 159 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,18 @@
99
*/
1010
package net.sf.jsqlparser.statement;
1111

12-
import net.sf.jsqlparser.statement.select.SelectItem;
13-
1412
import java.util.ArrayList;
13+
import java.util.LinkedHashMap;
1514
import java.util.List;
15+
import java.util.Locale;
16+
import java.util.Map;
17+
import java.util.Objects;
18+
import net.sf.jsqlparser.expression.ExpressionVisitorAdapter;
19+
import net.sf.jsqlparser.schema.Column;
20+
import net.sf.jsqlparser.schema.MultiPartName;
21+
import net.sf.jsqlparser.schema.Table;
22+
import net.sf.jsqlparser.statement.select.AllTableColumns;
23+
import net.sf.jsqlparser.statement.select.SelectItem;
1624

1725
/**
1826
* RETURNING clause according to <a href=
@@ -25,26 +33,39 @@ public class ReturningClause extends ArrayList<SelectItem<?>> {
2533
* List of output targets like Table or UserVariable
2634
*/
2735
private final List<Object> dataItems;
36+
private final List<ReturningOutputAlias> outputAliases;
2837
private Keyword keyword;
2938

3039
public ReturningClause(Keyword keyword, List<SelectItem<?>> selectItems,
3140
List<Object> dataItems) {
41+
this(keyword, selectItems, null, dataItems);
42+
}
43+
44+
public ReturningClause(Keyword keyword, List<SelectItem<?>> selectItems,
45+
List<ReturningOutputAlias> outputAliases, List<Object> dataItems) {
3246
this.keyword = keyword;
3347
this.addAll(selectItems);
48+
this.outputAliases = outputAliases;
3449
this.dataItems = dataItems;
50+
normalizeReturningReferences();
3551
}
3652

3753
public ReturningClause(String keyword, List<SelectItem<?>> selectItems,
3854
List<Object> dataItems) {
3955
this(Keyword.from(keyword), selectItems, dataItems);
4056
}
4157

58+
public ReturningClause(String keyword, List<SelectItem<?>> selectItems,
59+
List<ReturningOutputAlias> outputAliases, List<Object> dataItems) {
60+
this(Keyword.from(keyword), selectItems, outputAliases, dataItems);
61+
}
62+
4263
public ReturningClause(Keyword keyword, List<SelectItem<?>> selectItems) {
43-
this(keyword, selectItems, null);
64+
this(keyword, selectItems, null, null);
4465
}
4566

4667
public ReturningClause(String keyword, List<SelectItem<?>> selectItems) {
47-
this(Keyword.valueOf(keyword), selectItems, null);
68+
this(Keyword.from(keyword), selectItems, null, null);
4869
}
4970

5071
public Keyword getKeyword() {
@@ -60,8 +81,22 @@ public List<?> getDataItems() {
6081
return dataItems;
6182
}
6283

84+
public List<ReturningOutputAlias> getOutputAliases() {
85+
return outputAliases;
86+
}
87+
6388
public StringBuilder appendTo(StringBuilder builder) {
6489
builder.append(" ").append(keyword).append(" ");
90+
if (outputAliases != null && !outputAliases.isEmpty()) {
91+
builder.append("WITH (");
92+
for (int i = 0; i < outputAliases.size(); i++) {
93+
if (i > 0) {
94+
builder.append(", ");
95+
}
96+
builder.append(outputAliases.get(i));
97+
}
98+
builder.append(") ");
99+
}
65100
for (int i = 0; i < size(); i++) {
66101
if (i > 0) {
67102
builder.append(", ");
@@ -86,6 +121,126 @@ public String toString() {
86121
return appendTo(new StringBuilder()).toString();
87122
}
88123

124+
private void normalizeReturningReferences() {
125+
Map<QualifierKey, ReturningReferenceType> qualifierMap = buildQualifierMap();
126+
if (qualifierMap.isEmpty()) {
127+
return;
128+
}
129+
130+
ReturningReferenceNormalizer normalizer = new ReturningReferenceNormalizer(qualifierMap);
131+
forEach(selectItem -> {
132+
if (selectItem != null && selectItem.getExpression() != null) {
133+
selectItem.getExpression().accept(normalizer, null);
134+
}
135+
});
136+
}
137+
138+
private Map<QualifierKey, ReturningReferenceType> buildQualifierMap() {
139+
LinkedHashMap<QualifierKey, ReturningReferenceType> qualifierMap = new LinkedHashMap<>();
140+
141+
if (outputAliases == null || outputAliases.isEmpty()) {
142+
qualifierMap.put(QualifierKey.from("OLD"), ReturningReferenceType.OLD);
143+
qualifierMap.put(QualifierKey.from("NEW"), ReturningReferenceType.NEW);
144+
return qualifierMap;
145+
}
146+
147+
for (ReturningOutputAlias outputAlias : outputAliases) {
148+
if (outputAlias == null || outputAlias.getAlias() == null
149+
|| outputAlias.getReferenceType() == null) {
150+
continue;
151+
}
152+
qualifierMap.put(QualifierKey.from(outputAlias.getAlias()),
153+
outputAlias.getReferenceType());
154+
}
155+
return qualifierMap;
156+
}
157+
158+
private static class ReturningReferenceNormalizer extends ExpressionVisitorAdapter<Void> {
159+
private final Map<QualifierKey, ReturningReferenceType> qualifierMap;
160+
161+
ReturningReferenceNormalizer(Map<QualifierKey, ReturningReferenceType> qualifierMap) {
162+
this.qualifierMap = qualifierMap;
163+
}
164+
165+
@Override
166+
public <S> Void visit(Column column, S context) {
167+
Table table = column.getTable();
168+
String qualifier = extractSimpleQualifier(table);
169+
if (qualifier == null) {
170+
return null;
171+
}
172+
ReturningReferenceType referenceType = qualifierMap.get(QualifierKey.from(qualifier));
173+
if (referenceType != null) {
174+
column.withReturningReference(referenceType, qualifier);
175+
column.setTable(null);
176+
}
177+
return null;
178+
}
179+
180+
@Override
181+
public <S> Void visit(AllTableColumns allTableColumns, S context) {
182+
Table table = allTableColumns.getTable();
183+
String qualifier = extractSimpleQualifier(table);
184+
if (qualifier == null) {
185+
return null;
186+
}
187+
ReturningReferenceType referenceType = qualifierMap.get(QualifierKey.from(qualifier));
188+
if (referenceType != null) {
189+
allTableColumns.withReturningReference(referenceType, qualifier);
190+
allTableColumns.setTable(null);
191+
}
192+
return null;
193+
}
194+
195+
private String extractSimpleQualifier(Table table) {
196+
if (table == null || table.getSchemaName() != null || table.getDatabaseName() != null) {
197+
return null;
198+
}
199+
String qualifier = table.getName();
200+
if (qualifier == null || qualifier.contains("@")) {
201+
return null;
202+
}
203+
return qualifier;
204+
}
205+
}
206+
207+
private static class QualifierKey {
208+
private final boolean quoted;
209+
private final String normalizedIdentifier;
210+
211+
private QualifierKey(boolean quoted, String normalizedIdentifier) {
212+
this.quoted = quoted;
213+
this.normalizedIdentifier = normalizedIdentifier;
214+
}
215+
216+
static QualifierKey from(String identifier) {
217+
boolean quoted = MultiPartName.isQuoted(identifier);
218+
String unquoted = MultiPartName.unquote(identifier);
219+
if (!quoted && unquoted != null) {
220+
unquoted = unquoted.toUpperCase(Locale.ROOT);
221+
}
222+
return new QualifierKey(quoted, unquoted);
223+
}
224+
225+
@Override
226+
public boolean equals(Object o) {
227+
if (this == o) {
228+
return true;
229+
}
230+
if (!(o instanceof QualifierKey)) {
231+
return false;
232+
}
233+
QualifierKey that = (QualifierKey) o;
234+
return quoted == that.quoted
235+
&& Objects.equals(normalizedIdentifier, that.normalizedIdentifier);
236+
}
237+
238+
@Override
239+
public int hashCode() {
240+
return Objects.hash(quoted, normalizedIdentifier);
241+
}
242+
}
243+
89244
public enum Keyword {
90245
RETURN, RETURNING;
91246

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2026 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.statement;
11+
12+
import java.util.Objects;
13+
14+
public class ReturningOutputAlias {
15+
private ReturningReferenceType referenceType;
16+
private String alias;
17+
18+
public ReturningOutputAlias(ReturningReferenceType referenceType, String alias) {
19+
this.referenceType = referenceType;
20+
this.alias = alias;
21+
}
22+
23+
public ReturningReferenceType getReferenceType() {
24+
return referenceType;
25+
}
26+
27+
public ReturningOutputAlias setReferenceType(ReturningReferenceType referenceType) {
28+
this.referenceType = referenceType;
29+
return this;
30+
}
31+
32+
public String getAlias() {
33+
return alias;
34+
}
35+
36+
public ReturningOutputAlias setAlias(String alias) {
37+
this.alias = alias;
38+
return this;
39+
}
40+
41+
public StringBuilder appendTo(StringBuilder builder) {
42+
return builder.append(referenceType).append(" AS ").append(alias);
43+
}
44+
45+
@Override
46+
public String toString() {
47+
return appendTo(new StringBuilder()).toString();
48+
}
49+
50+
@Override
51+
public boolean equals(Object o) {
52+
if (this == o) {
53+
return true;
54+
}
55+
if (!(o instanceof ReturningOutputAlias)) {
56+
return false;
57+
}
58+
ReturningOutputAlias that = (ReturningOutputAlias) o;
59+
return referenceType == that.referenceType && Objects.equals(alias, that.alias);
60+
}
61+
62+
@Override
63+
public int hashCode() {
64+
return Objects.hash(referenceType, alias);
65+
}
66+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2026 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.statement;
11+
12+
import net.sf.jsqlparser.schema.MultiPartName;
13+
14+
public enum ReturningReferenceType {
15+
OLD, NEW;
16+
17+
public static ReturningReferenceType from(String name) {
18+
String unquoted = MultiPartName.unquote(name);
19+
if (unquoted == null) {
20+
return null;
21+
}
22+
if ("OLD".equalsIgnoreCase(unquoted)) {
23+
return OLD;
24+
}
25+
if ("NEW".equalsIgnoreCase(unquoted)) {
26+
return NEW;
27+
}
28+
return null;
29+
}
30+
}

0 commit comments

Comments
 (0)