PostgreSQL: Parse an empty SELECT target list without FROM
diff --git a/src/dialect/postgresql.rs b/src/dialect/postgresql.rs
index 65bfd46..6f713e1 100644
--- a/src/dialect/postgresql.rs
+++ b/src/dialect/postgresql.rs
@@ -285,6 +285,8 @@
     /// ```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 340b2d0..a3d67b3 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -15564,7 +15564,7 @@
         }
 
         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 @@
         })
     }
 
+    /// 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 1959bcd..7be27ed 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -16472,6 +16472,43 @@
 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]