From ccb60445b6c0145d4725dae8a0cb08e2094fa0c5 Mon Sep 17 00:00:00 2001 From: dengliming Date: Mon, 2 Mar 2026 23:46:59 +0800 Subject: [PATCH] fix: support SQL Server ORDER BY ... FOR XML PATH parsing in subqueries --- .../jsqlparser/statement/select/Select.java | 17 ++++++------- .../util/deparser/SelectDeParser.java | 25 +++++++++---------- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 2 +- .../statement/select/ForClauseTest.java | 7 ++++++ 4 files changed, 28 insertions(+), 23 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/statement/select/Select.java b/src/main/java/net/sf/jsqlparser/statement/select/Select.java index 0e88dfabc..08fed9dee 100644 --- a/src/main/java/net/sf/jsqlparser/statement/select/Select.java +++ b/src/main/java/net/sf/jsqlparser/statement/select/Select.java @@ -9,6 +9,12 @@ */ package net.sf.jsqlparser.statement.select; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; import net.sf.jsqlparser.expression.Alias; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.ExpressionVisitor; @@ -17,13 +23,6 @@ import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.StatementVisitor; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.Optional; - public abstract class Select extends ASTNodeAccessImpl implements Statement, Expression, FromItem { protected Table forUpdateTable = null; protected List> withItemsList; @@ -381,12 +380,12 @@ public StringBuilder appendTo(StringBuilder builder) { appendTo(builder, alias, null, pivot, unPivot); + builder.append(orderByToString(oracleSiblings, orderByElements)); + if (forClause != null) { forClause.appendTo(builder); } - builder.append(orderByToString(oracleSiblings, orderByElements)); - if (limitBy != null) { builder.append(limitBy); } diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java index bb6335d90..300f9d1c8 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java @@ -9,6 +9,13 @@ */ package net.sf.jsqlparser.util.deparser; +import static java.util.stream.Collectors.joining; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; import net.sf.jsqlparser.expression.Alias; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.ExpressionVisitor; @@ -33,10 +40,10 @@ import net.sf.jsqlparser.statement.piped.PivotPipeOperator; import net.sf.jsqlparser.statement.piped.RenamePipeOperator; import net.sf.jsqlparser.statement.piped.SelectPipeOperator; +import net.sf.jsqlparser.statement.piped.SetOperationPipeOperator; import net.sf.jsqlparser.statement.piped.SetPipeOperator; import net.sf.jsqlparser.statement.piped.TableSamplePipeOperator; import net.sf.jsqlparser.statement.piped.UnPivotPipeOperator; -import net.sf.jsqlparser.statement.piped.SetOperationPipeOperator; import net.sf.jsqlparser.statement.piped.WherePipeOperator; import net.sf.jsqlparser.statement.piped.WindowPipeOperator; import net.sf.jsqlparser.statement.select.Distinct; @@ -71,14 +78,6 @@ import net.sf.jsqlparser.statement.select.WithItem; import net.sf.jsqlparser.statement.update.UpdateSet; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Optional; - -import static java.util.stream.Collectors.joining; - @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"}) public class SelectDeParser extends AbstractDeParser implements SelectVisitor, SelectItemVisitor, @@ -316,10 +315,6 @@ public StringBuilder visit(PlainSelect plainSelect, S context) { builder.append(plainSelect.getWindowDefinitions().stream() .map(WindowDefinition::toString).collect(joining(", "))); } - if (plainSelect.getForClause() != null) { - plainSelect.getForClause().appendTo(builder); - } - Alias alias = plainSelect.getAlias(); if (alias != null) { builder.append(alias); @@ -335,6 +330,10 @@ public StringBuilder visit(PlainSelect plainSelect, S context) { deparseOrderByElementsClause(plainSelect, plainSelect.getOrderByElements()); + if (plainSelect.getForClause() != null) { + plainSelect.getForClause().appendTo(builder); + } + if (plainSelect.isEmitChanges()) { builder.append(" EMIT CHANGES"); } diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 800bc0b61..8d369f5ac 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -4246,7 +4246,6 @@ PlainSelect PlainSelect() #PlainSelect: [ LOOKAHEAD(2) groupBy=GroupByColumnReferences() { plainSelect.setGroupByElement(groupBy); }] [ LOOKAHEAD(2) having=Having() { plainSelect.setHaving(having); }] [ LOOKAHEAD(2) qualify=Qualify() {plainSelect.setQualify(qualify); }] - [ LOOKAHEAD(2) forClause = ForClause() {plainSelect.setForClause(forClause);} ] [ LOOKAHEAD( ) orderByElements = OrderByElements() { plainSelect.setOracleSiblings(true); plainSelect.setOrderByElements(orderByElements); } ] [ LOOKAHEAD(2) windowName = RelObjectName() winDef = windowDefinition() { List winDefs = new ArrayList(); winDefs.add(winDef.withWindowName(windowName)); } @@ -4254,6 +4253,7 @@ PlainSelect PlainSelect() #PlainSelect: { plainSelect.setWindowDefinitions(winDefs); } ] [ LOOKAHEAD( ) orderByElements = OrderByElements() { plainSelect.setOrderByElements(orderByElements); } ] + [ LOOKAHEAD(2) forClause = ForClause() {plainSelect.setForClause(forClause);} ] [ LOOKAHEAD(2) { plainSelect.setEmitChanges(true); } ] [ LOOKAHEAD(7) limit = LimitBy() { plainSelect.setLimitBy(limit); } ] [ LOOKAHEAD() limit = LimitWithOffset() { plainSelect.setLimit(limit); } ] diff --git a/src/test/java/net/sf/jsqlparser/statement/select/ForClauseTest.java b/src/test/java/net/sf/jsqlparser/statement/select/ForClauseTest.java index b8e22ed6e..dee2c99ba 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/ForClauseTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/ForClauseTest.java @@ -30,6 +30,13 @@ void testForXMLPath() throws JSQLParserException { TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); } + @Test + void testForXMLPathAfterOrderByInSubSelect() throws JSQLParserException { + String sqlStr = + "SELECT STUFF((SELECT ',' + name FROM class ORDER BY id FOR XML PATH('')),1,1,'') AS names FROM users"; + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } + @Test void testForXMLRaw() throws JSQLParserException { String sqlStr =