Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,6 @@ public StringBuilder appendSelectBodyTo(StringBuilder builder) {
return builder;
}

@SuppressWarnings({"PMD.CyclomaticComplexity"})
@Override
public StringBuilder appendTo(StringBuilder builder) {

appendSelectBodyTo(builder);

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

if (limit != null) {
builder.append(limit);
}
if (offset != null) {
builder.append(offset);
}
return builder;
}

@Override
public <T, S> T accept(SelectVisitor<T> selectVisitor, S context) {
return selectVisitor.visit(this, context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import net.sf.jsqlparser.statement.piped.FromQuery;
import net.sf.jsqlparser.statement.select.*;

import java.util.List;

/**
* @author jxnu-liguobin
*/
Expand Down Expand Up @@ -84,18 +86,39 @@ public <S> StringBuilder visit(LateralSubSelect lateralSubSelect, S context) {

@Override
public <S> StringBuilder visit(TableStatement tableStatement, S context) {
List<WithItem<?>> withItemsList = tableStatement.getWithItemsList();
if (withItemsList != null && !withItemsList.isEmpty()) {
SelectDeParser selectDeParser = new SelectDeParser(expressionVisitor, builder);
builder.append("WITH ");
for (int i = 0; i < withItemsList.size(); i++) {
if (i > 0) {
builder.append(", ");
}
withItemsList.get(i).accept((SelectVisitor<?>) selectDeParser, context);
}
builder.append(" ");
}
tableStatement.appendSelectBodyTo(builder);
if (tableStatement.getOrderByElements() != null) {
new OrderByDeParser(expressionVisitor, builder)
.deParse(tableStatement.getOrderByElements());
}

if (tableStatement.getOption() != null) {
builder.append(tableStatement.getOption());
}
if (tableStatement.getLimit() != null) {
new LimitDeparser(expressionVisitor, builder).deParse(tableStatement.getLimit());
}
if (tableStatement.getOffset() != null) {
deparse(tableStatement.getOffset());
}
if (tableStatement.getFetch() != null) {
builder.append(tableStatement.getFetch());
}
if (tableStatement.getIsolation() != null) {
builder.append(tableStatement.getIsolation());
}

// TODO UNION

Expand Down
79 changes: 65 additions & 14 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -745,7 +745,8 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
// Direct subquery: ( SELECT/WITH/PIVOT ) → ParenthesedSelect handles it
// ( VALUES ) can be ParenthesedFromItem(Values) or ParenthesedSelect;
// ParenthesedFromItem is checked first so Values-as-FROM-item works
if (k2 == K_SELECT || k2 == K_WITH || k2 == K_PIVOT) return false;
if (k2 == K_SELECT || k2 == K_WITH || k2 == K_PIVOT || k2 == K_FROM) return false;
if (k2 == K_TABLE && isTableQueryAhead(2)) return false;
// ( ( SELECT ) UNION ( SELECT ) ) → isNestedSetOperationAhead handles it
// Other (( patterns: ( (SELECT...) JOIN ... ) or ( (table) ) → ParenthesedFromItem
if (k2 == K_LATERAL) return false; // handled by LateralSubSelect
Expand Down Expand Up @@ -777,11 +778,35 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
} catch (TokenMgrException e) { return false; }
}

/**
* Whether the TABLE at {@code offset} starts DuckDB's TABLE t query rather than naming a
* column, as in (table = 'x'): a table name follows, and then the end of the query.
*/
private boolean isTableQueryAhead(int offset) {
Token token = getToken(offset);
do {
token = nextWithItemToken(token);
if (!isIdentifierishNameToken(token)) {
return false;
}
token = nextWithItemToken(token);
} while (".".equals(token.image));
switch (token.kind) {
case CLOSING_BRACKET: case K_UNION: case K_EXCEPT: case K_INTERSECT: case K_MINUS:
case K_ORDER: case K_LIMIT: case K_OFFSET: case K_FETCH:
return true;
default:
return false;
}
}

protected boolean isParenthesedSelectAhead() {
try {
if (getToken(1).kind != OPENING_BRACKET) return false;
int k = getToken(2).kind;
return k == K_SELECT || k == K_WITH || k == K_VALUES;
// DuckDB's (FROM t ...) and (TABLE t ...)
return k == K_SELECT || k == K_WITH || k == K_VALUES || k == K_FROM
|| k == K_TABLE && isTableQueryAhead(2);
} catch (TokenMgrException e) {
return false;
}
Expand Down Expand Up @@ -1393,22 +1418,29 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
* nested in brackets (a sub-select, for instance) do not count.
*/
private boolean isFromFirstSelectAhead() {
if (getToken(1).kind != K_FROM) {
Token token = getToken(1);
if (token.kind != K_FROM) {
return false;
}
int depth = 0;
for (int i = 2;; i++) {
Token token = getToken(i);
if (token == null || token.kind == EOF || ";".equals(token.image)
|| "|>".equals(token.image)) {
while (true) {
token = nextWithItemToken(token);
if (token.kind == EOF || ";".equals(token.image) || "|>".equals(token.image)) {
return false;
}
if ("(".equals(token.image)) {
depth++;
} else if (")".equals(token.image)) {
depth--;
// the end of the parenthesized query this FROM starts
if (--depth < 0) {
return false;
}
} else if (depth == 0) {
switch (token.kind) {
case K_UNION:
case K_EXCEPT:
case K_INTERSECT:
case K_MINUS:
case K_SELECT:
case K_WHERE:
case K_GROUP:
Expand Down Expand Up @@ -3475,8 +3507,6 @@ Statement SingleStatement() :
|
stm = Select()
|
stm = TableStatement()
|
LOOKAHEAD(3) stm = Upsert()
|
LOOKAHEAD(2) stm = Alter()
Expand Down Expand Up @@ -7170,12 +7200,15 @@ Select Select() #Select:
{
[ with=WithList() ]
(
LOOKAHEAD({ isFromFirstSelectAhead() }) select = FromFirstSelect()
|
LOOKAHEAD(3) select = FromQuery()
LOOKAHEAD(3, { !isFromFirstSelectAhead() }) select = FromQuery()
|
(
(
LOOKAHEAD({ isFromFirstSelectAhead() }) select = FromFirstSelect()
|
// DuckDB's TABLE t, also as a set operation operand
LOOKAHEAD({ getToken(1).kind == K_TABLE }) select = TableStatement()
|
LOOKAHEAD(2) select = PivotQuery()
|
LOOKAHEAD(2) select = UnPivotQuery()
Expand Down Expand Up @@ -8141,6 +8174,10 @@ Select SetOperationList(Select select) #SetOperationList: {
)

(
LOOKAHEAD({ getToken(1).kind == K_FROM }) select = FromFirstSelect()
|
LOOKAHEAD({ getToken(1).kind == K_TABLE }) select = TableStatement()
|
select = PlainSelect()
|
select = Values()
Expand Down Expand Up @@ -8191,6 +8228,20 @@ Select SetOperationList(Select select) #SetOperationList: {
list.setIsolation(ps.getIsolation());
ps.setIsolation(null);
}
} else if (selects.get(selects.size() - 1) instanceof TableStatement) {
TableStatement table = (TableStatement) selects.get(selects.size() - 1);
if (table.getOrderByElements() != null) {
list.setOrderByElements(table.getOrderByElements());
table.setOrderByElements(null);
}
if (table.getLimit() != null) {
list.setLimit(table.getLimit());
table.setLimit(null);
}
if (table.getOffset() != null) {
list.setOffset(table.getOffset());
table.setOffset(null);
}
}
list.setBracketsOpsAndSelects(selects,operations);
return list;
Expand Down Expand Up @@ -15821,7 +15872,7 @@ CreateTable CreateTable(boolean isUsingOrReplace):
createTable.setExecute(execute);
}
|
( LOOKAHEAD(<K_TABLE>) select=TableStatement() | select=Select() )
select=Select()
{ createTable.setSelect(select, false); }
)
[ LOOKAHEAD(2) <K_WITH> withData=DataPopulation()
Expand Down
40 changes: 40 additions & 0 deletions src/test/java/net/sf/jsqlparser/statement/select/DuckDBTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -825,4 +825,44 @@ void testLambdaAsIdentifier() throws JSQLParserException {
TestUtils.assertSqlCanBeParsedAndDeparsed("ALTER TABLE t DROP COLUMN lambda", true);
TestUtils.assertSqlCanBeParsedAndDeparsed("ALTER TABLE t RENAME lambda TO x", true);
}

@Test
void testFromFirstAndTableAsQueryOperands() throws JSQLParserException {
for (String sqlStr : new String[] {"SELECT * FROM (FROM t)",
"SELECT * FROM (FROM t WHERE a > 1) x JOIN (SELECT 1) y ON true",
"SELECT * FROM t WHERE a IN (FROM u)", "SELECT (FROM u SELECT max(a))",
"FROM a UNION ALL FROM b", "FROM a WHERE x > 1 UNION ALL SELECT 1",
"SELECT 1 EXCEPT FROM b WHERE x", "FROM a SELECT x UNION FROM b SELECT x",
"WITH t AS (SELECT 1), u AS (SELECT 2) TABLE t", "TABLE a UNION TABLE b",
"SELECT 1 UNION TABLE b", "(TABLE a EXCEPT ALL TABLE b)",
"SELECT * FROM TABLE(f(1))"}) {
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
}

SetOperationList union = (SetOperationList) CCJSqlParserUtil
.parse("TABLE a UNION TABLE b ORDER BY 1 LIMIT 2");
Assertions.assertEquals("1", union.getOrderByElements().get(0).toString());
Assertions.assertNotNull(union.getLimit());
Assertions.assertNull(((TableStatement) union.getSelect(1)).getOrderByElements());

union = (SetOperationList) CCJSqlParserUtil.parse("FROM a UNION FROM b ORDER BY 1");
Assertions.assertTrue(((PlainSelect) union.getSelect(0)).isFromFirst());
Assertions.assertEquals("1", union.getOrderByElements().get(0).toString());
}

@Test
void testTableQueryKeepsTableColumnsAndTrailingClauses() throws JSQLParserException {
// TABLE also names columns, e.g. in ClickHouse's system.parts
for (String sqlStr : new String[] {
"SELECT database, table FROM system.parts WHERE (database = 'default') AND (table = 'hits')",
"SELECT (table), (table.a), (table + 1) FROM t",
"SELECT a FROM t WHERE (table, b) IN ((1, 2))",
"SELECT * FROM (TABLE s.t) x", "TABLE t FETCH FIRST 1 ROWS ONLY",
"WITH a AS (SELECT 1) TABLE a FETCH FIRST 1 ROWS ONLY",
"CREATE TABLE x AS TABLE a UNION TABLE b"}) {
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
}
Assertions.assertInstanceOf(net.sf.jsqlparser.statement.piped.FromQuery.class,
((ParenthesedSelect) CCJSqlParserUtil.parse("(FROM t)")).getSelect());
}
}
Loading