Skip to content

Commit 8ecff15

Browse files
authored
Support Oracle XMLSERIALIZE CONTENT and DOCUMENT forms (#2560)
1 parent b2c84f9 commit 8ecff15

8 files changed

Lines changed: 365 additions & 46 deletions

File tree

src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -713,13 +713,7 @@ public <S> T visit(VariableAssignment variableAssignment, S context) {
713713

714714
@Override
715715
public <S> T visit(XMLSerializeExpr xmlSerializeExpr, S context) {
716-
ArrayList<Expression> subExpressions = new ArrayList<>();
717-
718-
subExpressions.add(xmlSerializeExpr.getExpression());
719-
for (OrderByElement orderByElement : xmlSerializeExpr.getOrderByElements()) {
720-
subExpressions.add(orderByElement.getExpression());
721-
}
722-
return visitExpressions(xmlSerializeExpr, context, subExpressions);
716+
return visitExpressions(xmlSerializeExpr, context, xmlSerializeExpr.getExpressions());
723717
}
724718

725719
@Override

src/main/java/net/sf/jsqlparser/expression/XMLSerializeExpr.java

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

12+
import java.util.ArrayList;
1213
import java.util.List;
14+
import java.util.function.Consumer;
1315

1416
import static java.util.stream.Collectors.joining;
1517

@@ -19,9 +21,19 @@
1921

2022
public class XMLSerializeExpr extends ASTNodeAccessImpl implements Expression {
2123

24+
public enum SerializationMode {
25+
CONTENT, DOCUMENT
26+
}
27+
2228
private Expression expression;
2329
private List<OrderByElement> orderByElements;
2430
private ColDataType dataType;
31+
private SerializationMode serializationMode;
32+
private StringValue encoding;
33+
private StringValue version;
34+
private Boolean indent;
35+
private LongValue indentSize;
36+
private Boolean showDefaults;
2537

2638
@Override
2739
public <T, S> T accept(ExpressionVisitor<T> expressionVisitor, S context) {
@@ -52,11 +64,141 @@ public void setDataType(ColDataType dataType) {
5264
this.dataType = dataType;
5365
}
5466

67+
/** Null retains the legacy XMLAGG(XMLTEXT(...)) form and its existing expression getter. */
68+
public SerializationMode getSerializationMode() {
69+
return serializationMode;
70+
}
71+
72+
public void setSerializationMode(SerializationMode serializationMode) {
73+
this.serializationMode = serializationMode;
74+
}
75+
76+
public StringValue getEncoding() {
77+
return encoding;
78+
}
79+
80+
public void setEncoding(StringValue encoding) {
81+
this.encoding = encoding;
82+
}
83+
84+
public StringValue getVersion() {
85+
return version;
86+
}
87+
88+
public void setVersion(StringValue version) {
89+
this.version = version;
90+
}
91+
92+
/** Null preserves omission, true is INDENT, and false is NO INDENT. */
93+
public Boolean getIndent() {
94+
return indent;
95+
}
96+
97+
public void setIndent(Boolean indent) {
98+
this.indent = indent;
99+
}
100+
101+
public LongValue getIndentSize() {
102+
return indentSize;
103+
}
104+
105+
public void setIndentSize(LongValue indentSize) {
106+
this.indentSize = indentSize;
107+
}
108+
109+
/** Null preserves omission, true is SHOW DEFAULTS, and false is HIDE DEFAULTS. */
110+
public Boolean getShowDefaults() {
111+
return showDefaults;
112+
}
113+
114+
public void setShowDefaults(Boolean showDefaults) {
115+
this.showDefaults = showDefaults;
116+
}
117+
118+
/** Shared child discovery for expression, table-name and validation visitors. */
119+
public List<Expression> getExpressions() {
120+
List<Expression> result = new ArrayList<>();
121+
if (expression != null) {
122+
result.add(expression);
123+
}
124+
if (orderByElements != null) {
125+
for (OrderByElement orderBy : orderByElements) {
126+
result.add(orderBy.getExpression());
127+
}
128+
}
129+
if (encoding != null) {
130+
result.add(encoding);
131+
}
132+
if (version != null) {
133+
result.add(version);
134+
}
135+
if (indentSize != null) {
136+
result.add(indentSize);
137+
}
138+
return result;
139+
}
140+
141+
/** Render both forms without bypassing custom expression or ORDER BY deparsers. */
142+
public StringBuilder appendTo(StringBuilder sql, Consumer<Expression> expressionWriter,
143+
Consumer<List<OrderByElement>> orderByWriter) {
144+
validateOptions();
145+
sql.append("xmlserialize(");
146+
if (serializationMode == null) {
147+
sql.append("xmlagg(xmltext(");
148+
expressionWriter.accept(expression);
149+
sql.append(")");
150+
if (orderByElements != null) {
151+
orderByWriter.accept(orderByElements);
152+
}
153+
sql.append(") AS ").append(dataType);
154+
} else {
155+
sql.append(serializationMode).append(" ");
156+
expressionWriter.accept(expression);
157+
if (dataType != null) {
158+
sql.append(" AS ").append(dataType);
159+
}
160+
if (encoding != null) {
161+
sql.append(" ENCODING ");
162+
expressionWriter.accept(encoding);
163+
}
164+
if (version != null) {
165+
sql.append(" VERSION ");
166+
expressionWriter.accept(version);
167+
}
168+
if (indent != null) {
169+
sql.append(indent ? " INDENT" : " NO INDENT");
170+
if (indent && indentSize != null) {
171+
sql.append(" SIZE = ");
172+
expressionWriter.accept(indentSize);
173+
}
174+
}
175+
if (showDefaults != null) {
176+
sql.append(showDefaults ? " SHOW DEFAULTS" : " HIDE DEFAULTS");
177+
}
178+
}
179+
return sql.append(")");
180+
}
181+
182+
public void validateOptions() {
183+
if (indentSize != null && (!Boolean.TRUE.equals(indent) || indentSize.getValue() < 0)) {
184+
throw new IllegalArgumentException(
185+
"An indentation size requires INDENT and must be nonnegative");
186+
}
187+
if (serializationMode == null && (encoding != null || version != null || indent != null
188+
|| indentSize != null || showDefaults != null)) {
189+
throw new IllegalArgumentException("Serialization options require CONTENT or DOCUMENT");
190+
}
191+
if (serializationMode != null && orderByElements != null && !orderByElements.isEmpty()) {
192+
throw new IllegalArgumentException(
193+
"ORDER BY belongs inside the serialized XMLAGG expression");
194+
}
195+
}
196+
55197
@Override
56198
public String toString() {
57-
return "xmlserialize(xmlagg(xmltext(" + expression + ")"
58-
+ (orderByElements != null ? " ORDER BY " + orderByElements.stream()
59-
.map(OrderByElement::toString).collect(joining(", ")) : "")
60-
+ ") AS " + dataType + ")";
199+
StringBuilder sql = new StringBuilder();
200+
return appendTo(sql, sql::append, orderBy -> sql.append(" ORDER BY ")
201+
.append(orderBy.stream().map(OrderByElement::toString).collect(joining(", "))))
202+
.toString();
61203
}
62204
}

src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2220,7 +2220,9 @@ public <S> Void visit(VariableAssignment variableAssignment, S context) {
22202220

22212221
@Override
22222222
public <S> Void visit(XMLSerializeExpr xmlSerializeExpr, S context) {
2223-
2223+
for (Expression expression : xmlSerializeExpr.getExpressions()) {
2224+
expression.accept(this, context);
2225+
}
22242226
return null;
22252227
}
22262228

src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1653,21 +1653,8 @@ public <S> StringBuilder visit(VariableAssignment var, S context) {
16531653

16541654
@Override
16551655
public <S> StringBuilder visit(XMLSerializeExpr expr, S context) {
1656-
// xmlserialize(xmlagg(xmltext(COMMENT_LINE) ORDER BY COMMENT_SEQUENCE) as varchar(1024))
1657-
builder.append("xmlserialize(xmlagg(xmltext(");
1658-
expr.getExpression().accept(this, context);
1659-
builder.append(")");
1660-
if (expr.getOrderByElements() != null) {
1661-
builder.append(" ORDER BY ");
1662-
for (Iterator<OrderByElement> i = expr.getOrderByElements().iterator(); i.hasNext();) {
1663-
builder.append(i.next().toString());
1664-
if (i.hasNext()) {
1665-
builder.append(", ");
1666-
}
1667-
}
1668-
}
1669-
builder.append(") AS ").append(expr.getDataType()).append(")");
1670-
return builder;
1656+
return expr.appendTo(builder, expression -> expression.accept(this, context),
1657+
orderBy -> new OrderByDeParser(this, builder).deParse(false, orderBy, context));
16711658
}
16721659

16731660
@Override

src/main/java/net/sf/jsqlparser/util/deparser/OrderByDeParser.java

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ public void deParse(List<OrderByElement> orderByElementList) {
3636
}
3737

3838
public void deParse(boolean oracleSiblings, List<OrderByElement> orderByElementList) {
39+
deParse(oracleSiblings, orderByElementList, null);
40+
}
41+
42+
public <S> void deParse(boolean oracleSiblings, List<OrderByElement> orderByElementList,
43+
S context) {
3944
if (oracleSiblings) {
4045
builder.append(" ORDER SIBLINGS BY ");
4146
} else {
@@ -45,15 +50,24 @@ public void deParse(boolean oracleSiblings, List<OrderByElement> orderByElementL
4550
for (Iterator<OrderByElement> iterator = orderByElementList.iterator(); iterator
4651
.hasNext();) {
4752
OrderByElement orderByElement = iterator.next();
48-
deParseElement(orderByElement);
53+
if (context == null) {
54+
// Preserve the customization point used by existing subclasses.
55+
deParseElement(orderByElement);
56+
} else {
57+
deParseElement(orderByElement, context);
58+
}
4959
if (iterator.hasNext()) {
5060
builder.append(", ");
5161
}
5262
}
5363
}
5464

5565
public void deParseElement(OrderByElement orderBy) {
56-
orderBy.getExpression().accept(expressionVisitor, null);
66+
deParseElement(orderBy, null);
67+
}
68+
69+
public <S> void deParseElement(OrderByElement orderBy, S context) {
70+
orderBy.getExpression().accept(expressionVisitor, context);
5771
if (!orderBy.isAsc()) {
5872
builder.append(" DESC");
5973
} else if (orderBy.isAscDescPresent()) {
@@ -67,30 +81,30 @@ public void deParseElement(OrderByElement orderBy) {
6781
}
6882
if (orderBy.getWithFill() != null) {
6983
builder.append(' ');
70-
deParseWithFill(orderBy.getWithFill());
84+
deParseWithFill(orderBy.getWithFill(), context);
7185
}
7286
if (orderBy.isMysqlWithRollup()) {
7387
builder.append(" WITH ROLLUP");
7488
}
7589
}
7690

77-
private void deParseWithFill(WithFill withFill) {
91+
private <S> void deParseWithFill(WithFill withFill, S context) {
7892
builder.append("WITH FILL");
7993
if (withFill.getFrom() != null) {
8094
builder.append(" FROM ");
81-
withFill.getFrom().accept(expressionVisitor, null);
95+
withFill.getFrom().accept(expressionVisitor, context);
8296
}
8397
if (withFill.getTo() != null) {
8498
builder.append(" TO ");
85-
withFill.getTo().accept(expressionVisitor, null);
99+
withFill.getTo().accept(expressionVisitor, context);
86100
}
87101
if (withFill.getStep() != null) {
88102
builder.append(" STEP ");
89-
withFill.getStep().accept(expressionVisitor, null);
103+
withFill.getStep().accept(expressionVisitor, context);
90104
}
91105
if (withFill.getStaleness() != null) {
92106
builder.append(" STALENESS ");
93-
withFill.getStaleness().accept(expressionVisitor, null);
107+
withFill.getStaleness().accept(expressionVisitor, context);
94108
}
95109
}
96110

src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,6 +1052,10 @@ public <S> Void visit(TimezoneExpression a, S context) {
10521052

10531053
@Override
10541054
public <S> Void visit(XMLSerializeExpr xml, S context) {
1055+
xml.validateOptions();
1056+
for (Expression expression : xml.getExpressions()) {
1057+
expression.accept(this, context);
1058+
}
10551059
return null;
10561060
}
10571061

src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10848,26 +10848,51 @@ ExpressionList FunctionArgumentList(Function retval):
1084810848
}
1084910849

1085010850
XMLSerializeExpr XMLSerializeExpr(): {
10851-
XMLSerializeExpr result;
10851+
XMLSerializeExpr result = new XMLSerializeExpr();
1085210852
Expression expression;
1085310853
List<OrderByElement> orderByElements = null;
1085410854
ColDataType dataType;
10855+
Token token;
1085510856
}
1085610857
{
10857-
<K_XMLSERIALIZE>
10858-
"(" <K_XMLAGG>
10858+
<K_XMLSERIALIZE> "("
10859+
(
10860+
LOOKAHEAD({ isKeywordAhead("CONTENT") || isKeywordAhead("DOCUMENT") })
10861+
token=<S_IDENTIFIER>
10862+
{ result.setSerializationMode(XMLSerializeExpr.SerializationMode.valueOf(token.image.toUpperCase(Locale.ROOT))); }
10863+
expression=Expression() { result.setExpression(expression); }
10864+
[ <K_AS> dataType=ColDataType() { result.setDataType(dataType); } ]
10865+
[ <K_ENCODING> token=<S_CHAR_LITERAL> { result.setEncoding(new StringValue(token.image)); } ]
10866+
[ <K_VERSION> token=<S_CHAR_LITERAL> { result.setVersion(new StringValue(token.image)); } ]
10867+
[
10868+
LOOKAHEAD(2) <K_NO> token=<S_IDENTIFIER> {
10869+
requireDdlSyntax("INDENT".equalsIgnoreCase(token.image), "Expected INDENT after NO");
10870+
result.setIndent(false);
10871+
}
10872+
|
10873+
LOOKAHEAD({ isKeywordAhead("INDENT") }) token=<S_IDENTIFIER> { result.setIndent(true); }
10874+
[ <K_SIZE> "=" token=<S_LONG> { result.setIndentSize(new LongValue(token.image)); } ]
10875+
]
10876+
[
10877+
<K_SHOW> <K_DEFAULTS> { result.setShowDefaults(true); }
10878+
|
10879+
LOOKAHEAD({ isKeywordAhead("HIDE") }) token=<S_IDENTIFIER> <K_DEFAULTS>
10880+
{ result.setShowDefaults(false); }
10881+
]
10882+
|
10883+
<K_XMLAGG>
1085910884
"(" <K_XMLTEXT>
1086010885
"(" expression=SimpleExpression() ")"
1086110886
[ orderByElements=OrderByElements() ]
1086210887
")"
10863-
<K_AS> dataType=ColDataType() ")"
10864-
{
10865-
result = new XMLSerializeExpr();
10866-
result.setExpression(expression);
10867-
result.setOrderByElements(orderByElements);
10868-
result.setDataType(dataType);
10869-
return result;
10870-
}
10888+
<K_AS> dataType=ColDataType() {
10889+
result.setExpression(expression);
10890+
result.setOrderByElements(orderByElements);
10891+
result.setDataType(dataType);
10892+
}
10893+
)
10894+
")"
10895+
{ return result; }
1087110896
}
1087210897

1087310898

0 commit comments

Comments
 (0)