Support Oracle XMLSERIALIZE CONTENT and DOCUMENT forms by minleejae · Pull Request #2560 · JSQLParser/JSqlParser · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 146 additions & 4 deletions src/main/java/net/sf/jsqlparser/expression/XMLSerializeExpr.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
*/
package net.sf.jsqlparser.expression;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

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

Expand All @@ -19,9 +21,19 @@

public class XMLSerializeExpr extends ASTNodeAccessImpl implements Expression {

public enum SerializationMode {
CONTENT, DOCUMENT
}

private Expression expression;
private List<OrderByElement> orderByElements;
private ColDataType dataType;
private SerializationMode serializationMode;
private StringValue encoding;
private StringValue version;
private Boolean indent;
private LongValue indentSize;
private Boolean showDefaults;

@Override
public <T, S> T accept(ExpressionVisitor<T> expressionVisitor, S context) {
Expand Down Expand Up @@ -52,11 +64,141 @@ public void setDataType(ColDataType dataType) {
this.dataType = dataType;
}

/** Null retains the legacy XMLAGG(XMLTEXT(...)) form and its existing expression getter. */
public SerializationMode getSerializationMode() {
return serializationMode;
}

public void setSerializationMode(SerializationMode serializationMode) {
this.serializationMode = serializationMode;
}

public StringValue getEncoding() {
return encoding;
}

public void setEncoding(StringValue encoding) {
this.encoding = encoding;
}

public StringValue getVersion() {
return version;
}

public void setVersion(StringValue version) {
this.version = version;
}

/** Null preserves omission, true is INDENT, and false is NO INDENT. */
public Boolean getIndent() {
return indent;
}

public void setIndent(Boolean indent) {
this.indent = indent;
}

public LongValue getIndentSize() {
return indentSize;
}

public void setIndentSize(LongValue indentSize) {
this.indentSize = indentSize;
}

/** Null preserves omission, true is SHOW DEFAULTS, and false is HIDE DEFAULTS. */
public Boolean getShowDefaults() {
return showDefaults;
}

public void setShowDefaults(Boolean showDefaults) {
this.showDefaults = showDefaults;
}

/** Shared child discovery for expression, table-name and validation visitors. */
public List<Expression> getExpressions() {
List<Expression> result = new ArrayList<>();
if (expression != null) {
result.add(expression);
}
if (orderByElements != null) {
for (OrderByElement orderBy : orderByElements) {
result.add(orderBy.getExpression());
}
}
if (encoding != null) {
result.add(encoding);
}
if (version != null) {
result.add(version);
}
if (indentSize != null) {
result.add(indentSize);
}
return result;
}

/** Render both forms without bypassing custom expression or ORDER BY deparsers. */
public StringBuilder appendTo(StringBuilder sql, Consumer<Expression> expressionWriter,
Consumer<List<OrderByElement>> orderByWriter) {
validateOptions();
sql.append("xmlserialize(");
if (serializationMode == null) {
sql.append("xmlagg(xmltext(");
expressionWriter.accept(expression);
sql.append(")");
if (orderByElements != null) {
orderByWriter.accept(orderByElements);
}
sql.append(") AS ").append(dataType);
} else {
sql.append(serializationMode).append(" ");
expressionWriter.accept(expression);
if (dataType != null) {
sql.append(" AS ").append(dataType);
}
if (encoding != null) {
sql.append(" ENCODING ");
expressionWriter.accept(encoding);
}
if (version != null) {
sql.append(" VERSION ");
expressionWriter.accept(version);
}
if (indent != null) {
sql.append(indent ? " INDENT" : " NO INDENT");
if (indent && indentSize != null) {
sql.append(" SIZE = ");
expressionWriter.accept(indentSize);
}
}
if (showDefaults != null) {
sql.append(showDefaults ? " SHOW DEFAULTS" : " HIDE DEFAULTS");
}
}
return sql.append(")");
}

public void validateOptions() {
if (indentSize != null && (!Boolean.TRUE.equals(indent) || indentSize.getValue() < 0)) {
throw new IllegalArgumentException(
"An indentation size requires INDENT and must be nonnegative");
}
if (serializationMode == null && (encoding != null || version != null || indent != null
|| indentSize != null || showDefaults != null)) {
throw new IllegalArgumentException("Serialization options require CONTENT or DOCUMENT");
}
if (serializationMode != null && orderByElements != null && !orderByElements.isEmpty()) {
throw new IllegalArgumentException(
"ORDER BY belongs inside the serialized XMLAGG expression");
}
}

@Override
public String toString() {
return "xmlserialize(xmlagg(xmltext(" + expression + ")"
+ (orderByElements != null ? " ORDER BY " + orderByElements.stream()
.map(OrderByElement::toString).collect(joining(", ")) : "")
+ ") AS " + dataType + ")";
StringBuilder sql = new StringBuilder();
return appendTo(sql, sql::append, orderBy -> sql.append(" ORDER BY ")
.append(orderBy.stream().map(OrderByElement::toString).collect(joining(", "))))
.toString();
}
}
4 changes: 3 additions & 1 deletion src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java
Original file line number Diff line number Diff line change
Expand Up @@ -2213,7 +2213,9 @@ public <S> Void visit(VariableAssignment variableAssignment, S context) {

@Override
public <S> Void visit(XMLSerializeExpr xmlSerializeExpr, S context) {

for (Expression expression : xmlSerializeExpr.getExpressions()) {
expression.accept(this, context);
}
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1653,21 +1653,8 @@ public <S> StringBuilder visit(VariableAssignment var, S context) {

@Override
public <S> StringBuilder visit(XMLSerializeExpr expr, S context) {
// xmlserialize(xmlagg(xmltext(COMMENT_LINE) ORDER BY COMMENT_SEQUENCE) as varchar(1024))
builder.append("xmlserialize(xmlagg(xmltext(");
expr.getExpression().accept(this, context);
builder.append(")");
if (expr.getOrderByElements() != null) {
builder.append(" ORDER BY ");
for (Iterator<OrderByElement> i = expr.getOrderByElements().iterator(); i.hasNext();) {
builder.append(i.next().toString());
if (i.hasNext()) {
builder.append(", ");
}
}
}
builder.append(") AS ").append(expr.getDataType()).append(")");
return builder;
return expr.appendTo(builder, expression -> expression.accept(this, context),
orderBy -> new OrderByDeParser(this, builder).deParse(false, orderBy, context));
}

@Override
Expand Down
30 changes: 22 additions & 8 deletions src/main/java/net/sf/jsqlparser/util/deparser/OrderByDeParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ public void deParse(List<OrderByElement> orderByElementList) {
}

public void deParse(boolean oracleSiblings, List<OrderByElement> orderByElementList) {
deParse(oracleSiblings, orderByElementList, null);
}

public <S> void deParse(boolean oracleSiblings, List<OrderByElement> orderByElementList,
S context) {
if (oracleSiblings) {
builder.append(" ORDER SIBLINGS BY ");
} else {
Expand All @@ -45,15 +50,24 @@ public void deParse(boolean oracleSiblings, List<OrderByElement> orderByElementL
for (Iterator<OrderByElement> iterator = orderByElementList.iterator(); iterator
.hasNext();) {
OrderByElement orderByElement = iterator.next();
deParseElement(orderByElement);
if (context == null) {
// Preserve the customization point used by existing subclasses.
deParseElement(orderByElement);
} else {
deParseElement(orderByElement, context);
}
if (iterator.hasNext()) {
builder.append(", ");
}
}
}

public void deParseElement(OrderByElement orderBy) {
orderBy.getExpression().accept(expressionVisitor, null);
deParseElement(orderBy, null);
}

public <S> void deParseElement(OrderByElement orderBy, S context) {
orderBy.getExpression().accept(expressionVisitor, context);
if (!orderBy.isAsc()) {
builder.append(" DESC");
} else if (orderBy.isAscDescPresent()) {
Expand All @@ -67,30 +81,30 @@ public void deParseElement(OrderByElement orderBy) {
}
if (orderBy.getWithFill() != null) {
builder.append(' ');
deParseWithFill(orderBy.getWithFill());
deParseWithFill(orderBy.getWithFill(), context);
}
if (orderBy.isMysqlWithRollup()) {
builder.append(" WITH ROLLUP");
}
}

private void deParseWithFill(WithFill withFill) {
private <S> void deParseWithFill(WithFill withFill, S context) {
builder.append("WITH FILL");
if (withFill.getFrom() != null) {
builder.append(" FROM ");
withFill.getFrom().accept(expressionVisitor, null);
withFill.getFrom().accept(expressionVisitor, context);
}
if (withFill.getTo() != null) {
builder.append(" TO ");
withFill.getTo().accept(expressionVisitor, null);
withFill.getTo().accept(expressionVisitor, context);
}
if (withFill.getStep() != null) {
builder.append(" STEP ");
withFill.getStep().accept(expressionVisitor, null);
withFill.getStep().accept(expressionVisitor, context);
}
if (withFill.getStaleness() != null) {
builder.append(" STALENESS ");
withFill.getStaleness().accept(expressionVisitor, null);
withFill.getStaleness().accept(expressionVisitor, context);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1052,6 +1052,10 @@ public <S> Void visit(TimezoneExpression a, S context) {

@Override
public <S> Void visit(XMLSerializeExpr xml, S context) {
xml.validateOptions();
for (Expression expression : xml.getExpressions()) {
expression.accept(this, context);
}
return null;
}

Expand Down
47 changes: 36 additions & 11 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Loading
Loading