From cb0af750032629797fb4dbbd808512a81bcf769f Mon Sep 17 00:00:00 2001 From: princejain756 Date: Wed, 21 Oct 2026 10:38:20 +0530 Subject: [PATCH 1/3] feat: allow SELECT without target expressions for PostgreSQL Resolves JSQLParser/JSqlParser#2784 --- .../jsqlparser/statement/select/PlainSelect.java | 6 ++++++ .../jsqlparser/util/deparser/SelectDeParser.java | 6 ++++++ .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 9 ++++++++- .../jsqlparser/statement/select/SelectTest.java | 15 +++++++++++++++ 4 files changed, 35 insertions(+), 1 deletion(-) 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..88026171ec 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -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..bc7ea3c992 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 testSelectWithoutTargetList_PostgreSql() 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"; From 1ecee1f3291879695d38581eb3328b40c1343240 Mon Sep 17 00:00:00 2001 From: princejain756 Date: Wed, 21 Oct 2026 10:52:46 +0530 Subject: [PATCH 2/3] style: rename new test to camelCase for Codacy --- .../java/net/sf/jsqlparser/statement/select/SelectTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 bc7ea3c992..31f510c160 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java @@ -2399,7 +2399,7 @@ public void testBooleanTestAfterPredicate() throws JSQLParserException { } @Test - public void testSelectWithoutTargetList_PostgreSql() throws JSQLParserException { + public void testSelectWithoutTargetListPostgreSql() throws JSQLParserException { // PostgreSQL permits SELECT with no target expressions Statement stmt = CCJSqlParserUtil.parse( "SELECT FROM t", parser -> parser.withDialect(Dialect.POSTGRESQL)); From 3ea8fe7667d32312c988624dd0d0925b28df126d Mon Sep 17 00:00:00 2001 From: princejain756 Date: Thu, 22 Oct 2026 09:22:33 +0530 Subject: [PATCH 3/3] fix: disambiguate BigQuery AS STRUCT/VALUE qualifier from select tail Making the select-items group optional for the target-less PostgreSQL SELECT let JavaCC see K_AS in the follow set of the optional AS STRUCT/VALUE qualifier block, producing a choice conflict at the block. Guard it with an explicit lookahead so AS only opens the qualifier when STRUCT or VALUE comes next. --- src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 88026171ec..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 ); }