Skip to content

Commit 4658415

Browse files
authored
fix(parser): support leading CONTENT/DOCUMENT keyword in XMLPARSE (#2501)
1 parent 1e4e92b commit 4658415

2 files changed

Lines changed: 99 additions & 2 deletions

File tree

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

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,38 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
657657
}
658658
}
659659

660+
/**
661+
* XML constructor functions take a bare leading keyword before their first argument,
662+
* e.g. XMLPARSE(CONTENT expr), XMLPARSE(DOCUMENT expr), XMLELEMENT(NAME ident, ...).
663+
* Guarded by the function name so that CONTENT/DOCUMENT/NAME stay plain identifiers
664+
* everywhere else, e.g. f(content).
665+
*/
666+
private void consumeXmlLeadingKeyword(Function function) {
667+
String name = function.getName();
668+
if (name == null) {
669+
return;
670+
}
671+
name = name.toUpperCase(java.util.Locale.ROOT);
672+
if (!"XMLPARSE".equals(name) && !"XMLELEMENT".equals(name) && !"XMLFOREST".equals(name)) {
673+
return;
674+
}
675+
Token token = getToken(1);
676+
if (token.kind != S_IDENTIFIER && token.kind != K_NAME) {
677+
return;
678+
}
679+
String keyword = token.image.toUpperCase(java.util.Locale.ROOT);
680+
if (!"CONTENT".equals(keyword) && !"DOCUMENT".equals(keyword) && !"NAME".equals(keyword)) {
681+
return;
682+
}
683+
// only a bare leading keyword: an expression must follow
684+
Token next = getToken(2);
685+
if (next.kind == EOF || ")".equals(next.image) || ",".equals(next.image)
686+
|| ".".equals(next.image) || "(".equals(next.image)) {
687+
return;
688+
}
689+
function.setExtraKeyword(getNextToken().image);
690+
}
691+
660692
private boolean isKeywordArgumentAhead() {
661693
Token t = getToken(1);
662694
if (t.kind == EOF || t.image.equals(")")) return false;
@@ -10154,9 +10186,9 @@ Function InternalFunction(boolean escaped):
1015410186
}
1015510187
{
1015610188
[ LOOKAHEAD(2) prefixToken = <K_APPROXIMATE> ]
10157-
funcName = RelObjectNames() { if (prefixToken!=null) funcName.getNames().add(0, prefixToken.image ); }
10189+
funcName = RelObjectNames() { if (prefixToken!=null) funcName.getNames().add(0, prefixToken.image ); retval.setName(funcName.getNames()); }
1015810190

10159-
"("
10191+
"(" { consumeXmlLeadingKeyword(retval); }
1016010192
[
1016110193
LOOKAHEAD(2) [
1016210194
LOOKAHEAD(2) (
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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.select;
11+
12+
import net.sf.jsqlparser.JSQLParserException;
13+
import org.junit.jupiter.api.Test;
14+
15+
import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed;
16+
17+
/**
18+
* XML constructor functions taking a bare leading keyword before their first argument, e.g.
19+
* {@code XMLPARSE(CONTENT expr)}.
20+
*/
21+
public class SelectXMLParseTest {
22+
23+
@Test
24+
public void testXmlParseContent() throws JSQLParserException {
25+
assertSqlCanBeParsedAndDeparsed("SELECT xmlparse(content a) FROM mytable");
26+
}
27+
28+
@Test
29+
public void testXmlParseDocument() throws JSQLParserException {
30+
assertSqlCanBeParsedAndDeparsed("SELECT xmlparse(document a) FROM mytable");
31+
}
32+
33+
@Test
34+
public void testXmlParseUpperCaseKeyword() throws JSQLParserException {
35+
assertSqlCanBeParsedAndDeparsed("SELECT xmlparse(CONTENT a) FROM mytable");
36+
}
37+
38+
@Test
39+
public void testXmlAggWithXmlParse() throws JSQLParserException {
40+
assertSqlCanBeParsedAndDeparsed(
41+
"SELECT xmlagg(xmlparse(content sn.CODE || ',') ORDER BY sn.CODE).getclobval() AS SN FROM GV_SYS_CODEINFO sn");
42+
}
43+
44+
@Test
45+
public void testXmlElementName() throws JSQLParserException {
46+
assertSqlCanBeParsedAndDeparsed("SELECT xmlelement(name a, b) FROM mytable");
47+
}
48+
49+
@Test
50+
public void testXmlForestName() throws JSQLParserException {
51+
assertSqlCanBeParsedAndDeparsed("SELECT xmlforest(name a) FROM mytable");
52+
}
53+
54+
/**
55+
* CONTENT, DOCUMENT and NAME must stay plain identifiers everywhere else.
56+
*/
57+
@Test
58+
public void testKeywordsRemainIdentifiers() throws JSQLParserException {
59+
assertSqlCanBeParsedAndDeparsed("SELECT content, document, name FROM mytable");
60+
assertSqlCanBeParsedAndDeparsed("SELECT content FROM content c WHERE c.document = 1");
61+
assertSqlCanBeParsedAndDeparsed("SELECT t.name AS document FROM mytable t");
62+
assertSqlCanBeParsedAndDeparsed("SELECT f(content) FROM mytable");
63+
assertSqlCanBeParsedAndDeparsed("SELECT xmlparse(content) FROM mytable");
64+
}
65+
}

0 commit comments

Comments
 (0)