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