diff --git a/src/main/java/net/sf/jsqlparser/statement/select/PlainSelect.java b/src/main/java/net/sf/jsqlparser/statement/select/PlainSelect.java index 25ee11654e..ea83b6d5ee 100644 --- a/src/main/java/net/sf/jsqlparser/statement/select/PlainSelect.java +++ b/src/main/java/net/sf/jsqlparser/statement/select/PlainSelect.java @@ -629,10 +629,16 @@ public StringBuilder appendSelectBodyTo(StringBuilder builder) { private void appendSelectClauseTo(StringBuilder builder) { builder.append("SELECT "); + int afterSelect = builder.length(); appendSelectHintsTo(builder); appendSelectQualifiersTo(builder); appendMySqlSelectModifiersTo(builder); builder.append(getStringList(selectItems)); + // PostgreSQL permits SELECT with no target expressions: keep "SELECT + // FROM" instead of doubling the space. + if (builder.length() == afterSelect) { + builder.deleteCharAt(afterSelect - 1); + } } private void appendSelectHintsTo(StringBuilder builder) { 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 8a0eb6e49f..3fee43edf6 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java @@ -379,7 +379,13 @@ private void deparseSelectClause(PlainSelect plainSelect, S context) { plainSelect.appendMySqlSelectModifiersTo(builder); + int beforeItems = builder.length(); deparseSelectItemsClause(plainSelect.getSelectItems()); + // PostgreSQL permits SELECT with no target expressions: keep "SELECT + // FROM" instead of doubling the space. + if (builder.length() == beforeItems && builder.charAt(beforeItems - 1) == ' ') { + builder.deleteCharAt(beforeItems - 1); + } if (plainSelect.getIntoTables() != null) { builder.append(" INTO "); diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 97856f1072..2100ab0f8f 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -8974,7 +8974,7 @@ PlainSelect PlainSelect() #PlainSelect: } } - [ + [ LOOKAHEAD( ( | )) ( { plainSelect.setBigQuerySelectQualifier( PlainSelect.BigQuerySelectQualifier.AS_STRUCT ); } @@ -8985,7 +8985,14 @@ PlainSelect PlainSelect() #PlainSelect: [ LOOKAHEAD(2) top = Top() { plainSelect.setTop(top); } ] - selectItems=SelectItemsList() + ( + // PostgreSQL allows SELECT with no target expressions. + LOOKAHEAD({ Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect)) + && getToken(1).kind == K_FROM }) + { selectItems = new ArrayList>(); } + | + selectItems=SelectItemsList() + ) [ LOOKAHEAD() ( diff --git a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java index ba39bc65cb..31f510c160 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java @@ -2398,6 +2398,21 @@ public void testBooleanTestAfterPredicate() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed("SELECT * FROM t WHERE a IS TRUE IS FALSE"); } + @Test + public void testSelectWithoutTargetListPostgreSql() throws JSQLParserException { + // PostgreSQL permits SELECT with no target expressions + Statement stmt = CCJSqlParserUtil.parse( + "SELECT FROM t", parser -> parser.withDialect(Dialect.POSTGRESQL)); + PlainSelect select = (PlainSelect) ((Select) stmt).getSelectBody(); + assertTrue(select.getSelectItems().isEmpty()); + assertDeparse(select, "SELECT FROM t"); + + // the reported case: empty target list inside a derived table + assertSqlCanBeParsedAndDeparsed( + "SELECT count(*) FROM (SELECT FROM (VALUES (1, 'a'), (2, 'b')) AS t(id, label)) AS q", + false, parser -> parser.withDialect(Dialect.POSTGRESQL)); + } + @Test public void testIsUnknown() throws JSQLParserException { String statement = "SELECT col FROM tbl WHERE col IS UNKNOWN";