Skip to content

Commit 0cf6f83

Browse files
committed
Keep columns named table and print every clause of TABLE t
- `(TABLE` starts a sub-select only when a table name follows and then the end of the query, so `(table = 'hits')` and `(table)` stay expressions on a column named table. `FROM (TABLE s.t) x` now parses. - A parenthesized `FROM t` without clauses stays a `FromQuery`. - `TableStatement` uses `Select.appendTo`, and its deparser prints OPTION, FETCH and the isolation level, so `TABLE t FETCH FIRST 1 ROWS ONLY` keeps its FETCH. - `CREATE TABLE ... AS` takes any query, including `TABLE a UNION TABLE b`.
1 parent c62e4ee commit 0cf6f83

4 files changed

Lines changed: 53 additions & 28 deletions

File tree

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

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -34,29 +34,6 @@ public StringBuilder appendSelectBodyTo(StringBuilder builder) {
3434
return builder;
3535
}
3636

37-
@SuppressWarnings({"PMD.CyclomaticComplexity"})
38-
@Override
39-
public StringBuilder appendTo(StringBuilder builder) {
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-
}
47-
appendSelectBodyTo(builder);
48-
49-
builder.append(orderByToString(false, orderByElements));
50-
51-
if (limit != null) {
52-
builder.append(limit);
53-
}
54-
if (offset != null) {
55-
builder.append(offset);
56-
}
57-
return builder;
58-
}
59-
6037
@Override
6138
public <T, S> T accept(SelectVisitor<T> selectVisitor, S context) {
6239
return selectVisitor.visit(this, context);

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,21 @@ public <S> StringBuilder visit(TableStatement tableStatement, S context) {
104104
.deParse(tableStatement.getOrderByElements());
105105
}
106106

107+
if (tableStatement.getOption() != null) {
108+
builder.append(tableStatement.getOption());
109+
}
107110
if (tableStatement.getLimit() != null) {
108111
new LimitDeparser(expressionVisitor, builder).deParse(tableStatement.getLimit());
109112
}
110113
if (tableStatement.getOffset() != null) {
111114
deparse(tableStatement.getOffset());
112115
}
116+
if (tableStatement.getFetch() != null) {
117+
builder.append(tableStatement.getFetch());
118+
}
119+
if (tableStatement.getIsolation() != null) {
120+
builder.append(tableStatement.getIsolation());
121+
}
113122

114123
// TODO UNION
115124

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

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,7 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
746746
// ( VALUES ) can be ParenthesedFromItem(Values) or ParenthesedSelect;
747747
// ParenthesedFromItem is checked first so Values-as-FROM-item works
748748
if (k2 == K_SELECT || k2 == K_WITH || k2 == K_PIVOT || k2 == K_FROM) return false;
749+
if (k2 == K_TABLE && isTableQueryAhead(2)) return false;
749750
// ( ( SELECT ) UNION ( SELECT ) ) → isNestedSetOperationAhead handles it
750751
// Other (( patterns: ( (SELECT...) JOIN ... ) or ( (table) ) → ParenthesedFromItem
751752
if (k2 == K_LATERAL) return false; // handled by LateralSubSelect
@@ -777,13 +778,35 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
777778
} catch (TokenMgrException e) { return false; }
778779
}
779780

781+
/**
782+
* Whether the TABLE at {@code offset} starts DuckDB's TABLE t query rather than naming a
783+
* column, as in (table = 'x'): a table name follows, and then the end of the query.
784+
*/
785+
private boolean isTableQueryAhead(int offset) {
786+
Token token = getToken(offset);
787+
do {
788+
token = nextWithItemToken(token);
789+
if (!isIdentifierishNameToken(token)) {
790+
return false;
791+
}
792+
token = nextWithItemToken(token);
793+
} while (".".equals(token.image));
794+
switch (token.kind) {
795+
case CLOSING_BRACKET: case K_UNION: case K_EXCEPT: case K_INTERSECT: case K_MINUS:
796+
case K_ORDER: case K_LIMIT: case K_OFFSET: case K_FETCH:
797+
return true;
798+
default:
799+
return false;
800+
}
801+
}
802+
780803
protected boolean isParenthesedSelectAhead() {
781804
try {
782805
if (getToken(1).kind != OPENING_BRACKET) return false;
783806
int k = getToken(2).kind;
784-
// DuckDB's (FROM t ...) and (TABLE t), but not a TABLE(...) function
807+
// DuckDB's (FROM t ...) and (TABLE t ...)
785808
return k == K_SELECT || k == K_WITH || k == K_VALUES || k == K_FROM
786-
|| k == K_TABLE && getToken(3).kind != OPENING_BRACKET;
809+
|| k == K_TABLE && isTableQueryAhead(2);
787810
} catch (TokenMgrException e) {
788811
return false;
789812
}
@@ -1408,9 +1431,9 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
14081431
if ("(".equals(token.image)) {
14091432
depth++;
14101433
} else if (")".equals(token.image)) {
1411-
// the end of the parenthesized query this FROM starts, e.g. (FROM t)
1434+
// the end of the parenthesized query this FROM starts
14121435
if (--depth < 0) {
1413-
return true;
1436+
return false;
14141437
}
14151438
} else if (depth == 0) {
14161439
switch (token.kind) {
@@ -15849,7 +15872,7 @@ CreateTable CreateTable(boolean isUsingOrReplace):
1584915872
createTable.setExecute(execute);
1585015873
}
1585115874
|
15852-
( LOOKAHEAD(<K_TABLE>) select=TableStatement() | select=Select() )
15875+
select=Select()
1585315876
{ createTable.setSelect(select, false); }
1585415877
)
1585515878
[ LOOKAHEAD(2) <K_WITH> withData=DataPopulation()

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -849,4 +849,20 @@ void testFromFirstAndTableAsQueryOperands() throws JSQLParserException {
849849
Assertions.assertTrue(((PlainSelect) union.getSelect(0)).isFromFirst());
850850
Assertions.assertEquals("1", union.getOrderByElements().get(0).toString());
851851
}
852+
853+
@Test
854+
void testTableQueryKeepsTableColumnsAndTrailingClauses() throws JSQLParserException {
855+
// TABLE also names columns, e.g. in ClickHouse's system.parts
856+
for (String sqlStr : new String[] {
857+
"SELECT database, table FROM system.parts WHERE (database = 'default') AND (table = 'hits')",
858+
"SELECT (table), (table.a), (table + 1) FROM t",
859+
"SELECT a FROM t WHERE (table, b) IN ((1, 2))",
860+
"SELECT * FROM (TABLE s.t) x", "TABLE t FETCH FIRST 1 ROWS ONLY",
861+
"WITH a AS (SELECT 1) TABLE a FETCH FIRST 1 ROWS ONLY",
862+
"CREATE TABLE x AS TABLE a UNION TABLE b"}) {
863+
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
864+
}
865+
Assertions.assertInstanceOf(net.sf.jsqlparser.statement.piped.FromQuery.class,
866+
((ParenthesedSelect) CCJSqlParserUtil.parse("(FROM t)")).getSelect());
867+
}
852868
}

0 commit comments

Comments
 (0)