Skip to content

Commit c62e4ee

Browse files
committed
DuckDB: accept FROM-first queries and TABLE t as subqueries and set operands
- `(FROM t ...)` is a sub-select wherever `(SELECT ...)` is, and a parenthesized `FROM t` without pipe operators is a FROM-first select. - FROM-first queries and `TABLE t` are set operation operands, e.g. `FROM a UNION ALL FROM b` or `TABLE a EXCEPT TABLE b`, with a trailing ORDER BY/LIMIT applying to the whole operation. - `WITH ... TABLE t` keeps and prints its WITH clause. - The FROM-first scan follows the token chain and stops at the end of the enclosing parentheses.
1 parent 19f12ea commit c62e4ee

4 files changed

Lines changed: 86 additions & 14 deletions

File tree

‎src/main/java/net/sf/jsqlparser/statement/select/TableStatement.java‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,13 @@ public StringBuilder appendSelectBodyTo(StringBuilder builder) {
3737
@SuppressWarnings({"PMD.CyclomaticComplexity"})
3838
@Override
3939
public StringBuilder appendTo(StringBuilder builder) {
40-
40+
if (withItemsList != null && !withItemsList.isEmpty()) {
41+
builder.append("WITH ");
42+
for (int i = 0; i < withItemsList.size(); i++) {
43+
builder.append(i > 0 ? ", " : "").append(withItemsList.get(i));
44+
}
45+
builder.append(" ");
46+
}
4147
appendSelectBodyTo(builder);
4248

4349
builder.append(orderByToString(false, orderByElements));

‎src/main/java/net/sf/jsqlparser/util/deparser/TableStatementDeParser.java‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import net.sf.jsqlparser.statement.piped.FromQuery;
1414
import net.sf.jsqlparser.statement.select.*;
1515

16+
import java.util.List;
17+
1618
/**
1719
* @author jxnu-liguobin
1820
*/
@@ -84,6 +86,18 @@ public <S> StringBuilder visit(LateralSubSelect lateralSubSelect, S context) {
8486

8587
@Override
8688
public <S> StringBuilder visit(TableStatement tableStatement, S context) {
89+
List<WithItem<?>> withItemsList = tableStatement.getWithItemsList();
90+
if (withItemsList != null && !withItemsList.isEmpty()) {
91+
SelectDeParser selectDeParser = new SelectDeParser(expressionVisitor, builder);
92+
builder.append("WITH ");
93+
for (int i = 0; i < withItemsList.size(); i++) {
94+
if (i > 0) {
95+
builder.append(", ");
96+
}
97+
withItemsList.get(i).accept((SelectVisitor<?>) selectDeParser, context);
98+
}
99+
builder.append(" ");
100+
}
87101
tableStatement.appendSelectBodyTo(builder);
88102
if (tableStatement.getOrderByElements() != null) {
89103
new OrderByDeParser(expressionVisitor, builder)

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

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,7 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
745745
// Direct subquery: ( SELECT/WITH/PIVOT ) → ParenthesedSelect handles it
746746
// ( VALUES ) can be ParenthesedFromItem(Values) or ParenthesedSelect;
747747
// ParenthesedFromItem is checked first so Values-as-FROM-item works
748-
if (k2 == K_SELECT || k2 == K_WITH || k2 == K_PIVOT) return false;
748+
if (k2 == K_SELECT || k2 == K_WITH || k2 == K_PIVOT || k2 == K_FROM) return false;
749749
// ( ( SELECT ) UNION ( SELECT ) ) → isNestedSetOperationAhead handles it
750750
// Other (( patterns: ( (SELECT...) JOIN ... ) or ( (table) ) → ParenthesedFromItem
751751
if (k2 == K_LATERAL) return false; // handled by LateralSubSelect
@@ -781,7 +781,9 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
781781
try {
782782
if (getToken(1).kind != OPENING_BRACKET) return false;
783783
int k = getToken(2).kind;
784-
return k == K_SELECT || k == K_WITH || k == K_VALUES;
784+
// DuckDB's (FROM t ...) and (TABLE t), but not a TABLE(...) function
785+
return k == K_SELECT || k == K_WITH || k == K_VALUES || k == K_FROM
786+
|| k == K_TABLE && getToken(3).kind != OPENING_BRACKET;
785787
} catch (TokenMgrException e) {
786788
return false;
787789
}
@@ -1393,22 +1395,29 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
13931395
* nested in brackets (a sub-select, for instance) do not count.
13941396
*/
13951397
private boolean isFromFirstSelectAhead() {
1396-
if (getToken(1).kind != K_FROM) {
1398+
Token token = getToken(1);
1399+
if (token.kind != K_FROM) {
13971400
return false;
13981401
}
13991402
int depth = 0;
1400-
for (int i = 2;; i++) {
1401-
Token token = getToken(i);
1402-
if (token == null || token.kind == EOF || ";".equals(token.image)
1403-
|| "|>".equals(token.image)) {
1403+
while (true) {
1404+
token = nextWithItemToken(token);
1405+
if (token.kind == EOF || ";".equals(token.image) || "|>".equals(token.image)) {
14041406
return false;
14051407
}
14061408
if ("(".equals(token.image)) {
14071409
depth++;
14081410
} else if (")".equals(token.image)) {
1409-
depth--;
1411+
// the end of the parenthesized query this FROM starts, e.g. (FROM t)
1412+
if (--depth < 0) {
1413+
return true;
1414+
}
14101415
} else if (depth == 0) {
14111416
switch (token.kind) {
1417+
case K_UNION:
1418+
case K_EXCEPT:
1419+
case K_INTERSECT:
1420+
case K_MINUS:
14121421
case K_SELECT:
14131422
case K_WHERE:
14141423
case K_GROUP:
@@ -3475,8 +3484,6 @@ Statement SingleStatement() :
34753484
|
34763485
stm = Select()
34773486
|
3478-
stm = TableStatement()
3479-
|
34803487
LOOKAHEAD(3) stm = Upsert()
34813488
|
34823489
LOOKAHEAD(2) stm = Alter()
@@ -7170,12 +7177,15 @@ Select Select() #Select:
71707177
{
71717178
[ with=WithList() ]
71727179
(
7173-
LOOKAHEAD({ isFromFirstSelectAhead() }) select = FromFirstSelect()
7174-
|
7175-
LOOKAHEAD(3) select = FromQuery()
7180+
LOOKAHEAD(3, { !isFromFirstSelectAhead() }) select = FromQuery()
71767181
|
71777182
(
71787183
(
7184+
LOOKAHEAD({ isFromFirstSelectAhead() }) select = FromFirstSelect()
7185+
|
7186+
// DuckDB's TABLE t, also as a set operation operand
7187+
LOOKAHEAD({ getToken(1).kind == K_TABLE }) select = TableStatement()
7188+
|
71797189
LOOKAHEAD(2) select = PivotQuery()
71807190
|
71817191
LOOKAHEAD(2) select = UnPivotQuery()
@@ -8141,6 +8151,10 @@ Select SetOperationList(Select select) #SetOperationList: {
81418151
)
81428152

81438153
(
8154+
LOOKAHEAD({ getToken(1).kind == K_FROM }) select = FromFirstSelect()
8155+
|
8156+
LOOKAHEAD({ getToken(1).kind == K_TABLE }) select = TableStatement()
8157+
|
81448158
select = PlainSelect()
81458159
|
81468160
select = Values()
@@ -8191,6 +8205,20 @@ Select SetOperationList(Select select) #SetOperationList: {
81918205
list.setIsolation(ps.getIsolation());
81928206
ps.setIsolation(null);
81938207
}
8208+
} else if (selects.get(selects.size() - 1) instanceof TableStatement) {
8209+
TableStatement table = (TableStatement) selects.get(selects.size() - 1);
8210+
if (table.getOrderByElements() != null) {
8211+
list.setOrderByElements(table.getOrderByElements());
8212+
table.setOrderByElements(null);
8213+
}
8214+
if (table.getLimit() != null) {
8215+
list.setLimit(table.getLimit());
8216+
table.setLimit(null);
8217+
}
8218+
if (table.getOffset() != null) {
8219+
list.setOffset(table.getOffset());
8220+
table.setOffset(null);
8221+
}
81948222
}
81958223
list.setBracketsOpsAndSelects(selects,operations);
81968224
return list;

‎src/test/java/net/sf/jsqlparser/statement/select/DuckDBTest.java‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -825,4 +825,28 @@ void testLambdaAsIdentifier() throws JSQLParserException {
825825
TestUtils.assertSqlCanBeParsedAndDeparsed("ALTER TABLE t DROP COLUMN lambda", true);
826826
TestUtils.assertSqlCanBeParsedAndDeparsed("ALTER TABLE t RENAME lambda TO x", true);
827827
}
828+
829+
@Test
830+
void testFromFirstAndTableAsQueryOperands() throws JSQLParserException {
831+
for (String sqlStr : new String[] {"SELECT * FROM (FROM t)",
832+
"SELECT * FROM (FROM t WHERE a > 1) x JOIN (SELECT 1) y ON true",
833+
"SELECT * FROM t WHERE a IN (FROM u)", "SELECT (FROM u SELECT max(a))",
834+
"FROM a UNION ALL FROM b", "FROM a WHERE x > 1 UNION ALL SELECT 1",
835+
"SELECT 1 EXCEPT FROM b WHERE x", "FROM a SELECT x UNION FROM b SELECT x",
836+
"WITH t AS (SELECT 1), u AS (SELECT 2) TABLE t", "TABLE a UNION TABLE b",
837+
"SELECT 1 UNION TABLE b", "(TABLE a EXCEPT ALL TABLE b)",
838+
"SELECT * FROM TABLE(f(1))"}) {
839+
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
840+
}
841+
842+
SetOperationList union = (SetOperationList) CCJSqlParserUtil
843+
.parse("TABLE a UNION TABLE b ORDER BY 1 LIMIT 2");
844+
Assertions.assertEquals("1", union.getOrderByElements().get(0).toString());
845+
Assertions.assertNotNull(union.getLimit());
846+
Assertions.assertNull(((TableStatement) union.getSelect(1)).getOrderByElements());
847+
848+
union = (SetOperationList) CCJSqlParserUtil.parse("FROM a UNION FROM b ORDER BY 1");
849+
Assertions.assertTrue(((PlainSelect) union.getSelect(0)).isFromFirst());
850+
Assertions.assertEquals("1", union.getOrderByElements().get(0).toString());
851+
}
828852
}

0 commit comments

Comments
 (0)