diff --git a/src/ast/query.rs b/src/ast/query.rs index 948febd26..69b7ea1c1 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -350,7 +350,9 @@ impl fmt::Display for Select { } } - write!(f, " {}", display_comma_separated(&self.projection))?; + if !self.projection.is_empty() { + write!(f, " {}", display_comma_separated(&self.projection))?; + } if let Some(ref into) = self.into { write!(f, " {into}")?; diff --git a/src/dialect/generic.rs b/src/dialect/generic.rs index 61e5070fb..f852152a0 100644 --- a/src/dialect/generic.rs +++ b/src/dialect/generic.rs @@ -127,4 +127,8 @@ impl Dialect for GenericDialect { fn supports_struct_literal(&self) -> bool { true } + + fn supports_empty_projections(&self) -> bool { + true + } } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index c32b763a4..7a71d662f 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -410,6 +410,16 @@ pub trait Dialect: Debug + Any { false } + /// Return true if the dialect supports empty projections in SELECT statements + /// + /// Example + /// ```sql + /// SELECT from table_name + /// ``` + fn supports_empty_projections(&self) -> bool { + false + } + /// Dialect-specific infix parser override /// /// This method is called to parse the next infix expression. diff --git a/src/dialect/postgresql.rs b/src/dialect/postgresql.rs index dcdcc88c1..80c14c8da 100644 --- a/src/dialect/postgresql.rs +++ b/src/dialect/postgresql.rs @@ -231,6 +231,16 @@ impl Dialect for PostgreSqlDialect { fn supports_named_fn_args_with_expr_name(&self) -> bool { true } + + /// Return true if the dialect supports empty projections in SELECT statements + /// + /// Example + /// ```sql + /// SELECT from table_name + /// ``` + fn supports_empty_projections(&self) -> bool { + true + } } pub fn parse_create(parser: &mut Parser) -> Option> { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index e809ffba5..6ab9318b0 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -9604,7 +9604,12 @@ impl<'a> Parser<'a> { top = Some(self.parse_top()?); } - let projection = self.parse_projection()?; + let projection = + if self.dialect.supports_empty_projections() && self.peek_keyword(Keyword::FROM) { + vec![] + } else { + self.parse_projection()? + }; let into = if self.parse_keyword(Keyword::INTO) { let temporary = self diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index e7e2e3bc2..ca217f1d9 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -12446,3 +12446,9 @@ fn overflow() { let statement = statements.pop().unwrap(); assert_eq!(statement.to_string(), sql); } + +#[test] +fn parse_select_without_projection() { + let dialects = all_dialects_where(|d| d.supports_empty_projections()); + dialects.verified_stmt("SELECT FROM users"); +}