You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datafusion.apache.org by gi...@apache.org on 2026/10/20 18:55:41 UTC

(datafusion-sqlparser-rs) 01/01: PostgreSQL: Parse an empty SELECT target list without FROM (#2675)

This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-2675-fad68134e557458825390489d28bf8b222e96a56
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git

commit f937b73ef2f27b856cbc15274e7b056a38a88385
Author: Luca Cappelletti <ca...@gmail.com>
AuthorDate: Tue Oct 20 18:55:17 2026 +0000

    PostgreSQL: Parse an empty SELECT target list without FROM (#2675)
---
 src/dialect/postgresql.rs |  2 ++
 src/parser/mod.rs         | 27 ++++++++++++++++++++++++++-
 tests/sqlparser_common.rs | 37 +++++++++++++++++++++++++++++++++++++
 3 files changed, 65 insertions(+), 1 deletion(-)

diff --git a/src/dialect/postgresql.rs b/src/dialect/postgresql.rs
index 57a8899ae..9917cb7c2 100644
--- a/src/dialect/postgresql.rs
+++ b/src/dialect/postgresql.rs
@@ -291,6 +291,8 @@ impl Dialect for PostgreSqlDialect {
     /// ```sql
     /// SELECT from table_name
     /// ```
+    ///
+    /// The target list is optional in <https://www.postgresql.org/docs/current/sql-select.html>
     fn supports_empty_projections(&self) -> bool {
         true
     }
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 1877ac623..a5a45bf83 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -15564,7 +15564,7 @@ impl<'a> Parser<'a> {
         }
 
         let projection =
-            if self.dialect.supports_empty_projections() && self.peek_keyword(Keyword::FROM) {
+            if self.dialect.supports_empty_projections() && self.peek_empty_projection() {
                 vec![]
             } else {
                 self.parse_projection()?
@@ -15725,6 +15725,31 @@ impl<'a> Parser<'a> {
         })
     }
 
+    /// Returns true if the next token ends a `SELECT` target list before it begins.
+    fn peek_empty_projection(&self) -> bool {
+        match &self.peek_token_ref().token {
+            Token::EOF | Token::SemiColon | Token::RParen => true,
+            Token::Word(w) => matches!(
+                w.keyword,
+                Keyword::INTO
+                    | Keyword::FROM
+                    | Keyword::WHERE
+                    | Keyword::GROUP
+                    | Keyword::HAVING
+                    | Keyword::WINDOW
+                    | Keyword::ORDER
+                    | Keyword::LIMIT
+                    | Keyword::OFFSET
+                    | Keyword::FETCH
+                    | Keyword::FOR
+                    | Keyword::UNION
+                    | Keyword::INTERSECT
+                    | Keyword::EXCEPT
+            ),
+            _ => false,
+        }
+    }
+
     /// Parses optimizer hints at the current token position.
     ///
     /// Collects all `/*prefix+...*/` and `--prefix+...` patterns.
diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs
index aae5f7469..e110fe8f1 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -16515,6 +16515,43 @@ fn parse_deeply_nested_boolean_expr_does_not_stackoverflow() {
 fn parse_select_without_projection() {
     let dialects = all_dialects_where(|d| d.supports_empty_projections());
     dialects.verified_stmt("SELECT FROM users");
+    for sql in [
+        "SELECT",
+        "SELECT ALL",
+        "SELECT INTO t",
+        "SELECT WHERE true",
+        "SELECT GROUP BY () HAVING true",
+        "SELECT WINDOW w AS ()",
+        "SELECT ORDER BY 1",
+        "SELECT LIMIT 1",
+        "SELECT OFFSET 1",
+        "SELECT FETCH FIRST 1 ROWS ONLY",
+        "SELECT FOR UPDATE",
+        "SELECT UNION SELECT",
+        "SELECT INTERSECT SELECT",
+        "SELECT EXCEPT SELECT",
+        "SELECT EXISTS (SELECT)",
+        "SELECT * FROM (SELECT) AS t",
+    ] {
+        dialects.verified_stmt(sql);
+    }
+    assert_eq!(
+        dialects
+            .parse_sql_statements("SELECT; SELECT")
+            .unwrap()
+            .len(),
+        2
+    );
+    assert_eq!(
+        dialects.parse_sql_statements("SELECT WHERE").unwrap_err(),
+        ParserError::ParserError("Expected: an expression, found: EOF".to_string())
+    );
+
+    let dialects = all_dialects_where(|d| !d.supports_empty_projections());
+    assert_eq!(
+        dialects.parse_sql_statements("SELECT").unwrap_err(),
+        ParserError::ParserError("Expected: an expression, found: EOF".to_string())
+    );
 }
 
 #[test]


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datafusion.apache.org
For additional commands, e-mail: commits-help@datafusion.apache.org