You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datafusion.apache.org by lu...@apache.org on 2026/10/02 19:24:33 UTC
(datafusion-sqlparser-rs) branch upstream/postgres-create-table-requires-columns created (now e158a9fc)
This is an automated email from the ASF dual-hosted git repository.
LucaCappelletti94 pushed a change to branch upstream/postgres-create-table-requires-columns
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
at e158a9fc Postgres: require a column list, AS, PARTITION OF, or LIKE on CREATE TABLE
This branch includes the following new commits:
new e158a9fc Postgres: require a column list, AS, PARTITION OF, or LIKE on CREATE TABLE
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datafusion.apache.org
For additional commands, e-mail: commits-help@datafusion.apache.org
(datafusion-sqlparser-rs) 01/01: Postgres: require a column list, AS, PARTITION OF, or LIKE on CREATE TABLE
Posted by lu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
LucaCappelletti94 pushed a commit to branch upstream/postgres-create-table-requires-columns
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
commit e158a9fc6cf431a3090350b1324573e3231dbf25
Author: LucaCappelletti94 <ca...@gmail.com>
AuthorDate: Fri Oct 2 21:24:15 2026 +0200
Postgres: require a column list, AS, PARTITION OF, or LIKE on CREATE TABLE
---
src/dialect/mod.rs | 9 ++++++++
src/dialect/postgresql.rs | 7 +++++++
src/parser/mod.rs | 15 ++++++++++++++
tests/sqlparser_postgres.rs | 50 +++++++++++++++++++++++++++++++++++++++++----
tests/sqlparser_teradata.rs | 9 +++++---
5 files changed, 83 insertions(+), 7 deletions(-)
diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs
index 2beb5655..20c0c5ac 100644
--- a/src/dialect/mod.rs
+++ b/src/dialect/mod.rs
@@ -1339,6 +1339,15 @@ pub trait Dialect: Debug + Any {
false
}
+ /// Returns true if the dialect accepts `CREATE TABLE t` with no parenthesized
+ /// column list and no `AS`, `OF`, `PARTITION OF` or `LIKE` clause in its place,
+ /// treating it the same as the explicit zero-column `CREATE TABLE t ()`.
+ ///
+ /// PostgreSQL rejects this form as a syntax error.
+ fn supports_create_table_without_column_list(&self) -> bool {
+ true
+ }
+
/// Returns true if this dialect supports `PROC` as an abbreviation for
/// `PROCEDURE` in a `CREATE` statement.
fn supports_create_proc_syntax(&self) -> bool {
diff --git a/src/dialect/postgresql.rs b/src/dialect/postgresql.rs
index 3bec6ceb..c815b71c 100644
--- a/src/dialect/postgresql.rs
+++ b/src/dialect/postgresql.rs
@@ -345,6 +345,13 @@ impl Dialect for PostgreSqlDialect {
true
}
+ /// PostgreSQL rejects `CREATE TABLE t` with nothing after the name: the
+ /// column list (even empty, as `()`), `OF type_name`, `PARTITION OF ...`
+ /// or `AS ...` is mandatory.
+ fn supports_create_table_without_column_list(&self) -> bool {
+ false
+ }
+
fn supports_select_wildcard_with_alias(&self) -> bool {
true
}
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 1a46c6e5..b4c3192f 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -8858,7 +8858,22 @@ impl<'a> Parser<'a> {
};
// parse optional column list (schema)
+ let has_column_list = matches!(self.peek_token_ref().token, Token::LParen);
let (columns, constraints) = self.parse_columns()?;
+ if !has_column_list
+ && partition_of.is_none()
+ && like.is_none()
+ && clone.is_none()
+ && !self.dialect.supports_create_table_without_column_list()
+ && !(self.peek_keyword(Keyword::AS)
+ || (self.dialect.supports_create_table_select()
+ && self.peek_keyword(Keyword::SELECT)))
+ {
+ return self.expected_ref(
+ "'(', AS, PARTITION OF, or LIKE after CREATE TABLE table name",
+ self.peek_token_ref(),
+ );
+ }
let comment_after_column_def =
if dialect_of!(self is HiveDialect) && self.parse_keyword(Keyword::COMMENT) {
let next_token = self.next_token();
diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs
index fb998510..2d1b00b1 100644
--- a/tests/sqlparser_postgres.rs
+++ b/tests/sqlparser_postgres.rs
@@ -1370,25 +1370,37 @@ fn parse_create_table_if_not_exists() {
fn parse_bad_if_not_exists() {
let res = pg().parse_sql_statements("CREATE TABLE NOT EXISTS uk_cities ()");
assert_eq!(
- ParserError::ParserError("Expected: end of statement, found: EXISTS".to_string()),
+ ParserError::ParserError(
+ "Expected: '(', AS, PARTITION OF, or LIKE after CREATE TABLE table name, found: EXISTS"
+ .to_string()
+ ),
res.unwrap_err()
);
let res = pg().parse_sql_statements("CREATE TABLE IF EXISTS uk_cities ()");
assert_eq!(
- ParserError::ParserError("Expected: end of statement, found: EXISTS".to_string()),
+ ParserError::ParserError(
+ "Expected: '(', AS, PARTITION OF, or LIKE after CREATE TABLE table name, found: EXISTS"
+ .to_string()
+ ),
res.unwrap_err()
);
let res = pg().parse_sql_statements("CREATE TABLE IF uk_cities ()");
assert_eq!(
- ParserError::ParserError("Expected: end of statement, found: uk_cities".to_string()),
+ ParserError::ParserError(
+ "Expected: '(', AS, PARTITION OF, or LIKE after CREATE TABLE table name, found: uk_cities"
+ .to_string()
+ ),
res.unwrap_err()
);
let res = pg().parse_sql_statements("CREATE TABLE IF NOT uk_cities ()");
assert_eq!(
- ParserError::ParserError("Expected: end of statement, found: NOT".to_string()),
+ ParserError::ParserError(
+ "Expected: '(', AS, PARTITION OF, or LIKE after CREATE TABLE table name, found: NOT"
+ .to_string()
+ ),
res.unwrap_err()
);
}
@@ -10126,3 +10138,33 @@ fn parse_bitstring_literal_escaping() {
pg_and_generic().verified_stmt("SELECT B''''");
pg_and_generic().verified_stmt("SELECT B'it''s'");
}
+
+#[test]
+fn parse_create_table_requires_column_list_or_alternative() {
+ let expected_err = "sql parser error: Expected: '(', AS, PARTITION OF, or LIKE after CREATE TABLE table name, found: EOF";
+ assert_eq!(
+ pg().parse_sql_statements("CREATE TABLE t")
+ .unwrap_err()
+ .to_string(),
+ expected_err
+ );
+ assert_eq!(
+ pg().parse_sql_statements("CREATE TEMP TABLE t")
+ .unwrap_err()
+ .to_string(),
+ expected_err
+ );
+
+ // A column list, even empty, an `AS` query, or a `PARTITION OF` clause
+ // all stand in for the missing column list and keep parsing.
+ pg().verified_stmt("CREATE TABLE t ()");
+ pg().verified_stmt("CREATE TABLE t (id INT)");
+ pg().verified_stmt("CREATE TABLE t AS SELECT 1");
+ pg().verified_stmt("CREATE TABLE t PARTITION OF p FOR VALUES IN (1)");
+
+ // Other dialects are unaffected: a bare table name still parses as a
+ // zero-column table.
+ let generic = TestedDialects::new(vec![Box::new(GenericDialect {})]);
+ generic.verified_stmt("CREATE TABLE t ()");
+ assert!(generic.parse_sql_statements("CREATE TABLE t").is_ok());
+}
diff --git a/tests/sqlparser_teradata.rs b/tests/sqlparser_teradata.rs
index a4e66af0..561de38e 100644
--- a/tests/sqlparser_teradata.rs
+++ b/tests/sqlparser_teradata.rs
@@ -18,6 +18,7 @@
//! Test SQL syntax, specific to [sqlparser::dialect::TeradataDialect].
use sqlparser::dialect::{Dialect, GenericDialect, TeradataDialect};
+use sqlparser::parser::Parser;
use sqlparser::test_utils::all_dialects_where;
use test_utils::TestedDialects;
@@ -117,7 +118,9 @@ fn parse_leading_comma_before_table_options() {
let unsupported_dialects =
all_dialects_where(|d| !d.supports_leading_comma_before_table_options());
- assert!(unsupported_dialects
- .parse_sql_statements("CREATE TABLE foo, FALLBACK (id INT)")
- .is_err());
+ for dialect in &unsupported_dialects.dialects {
+ assert!(
+ Parser::parse_sql(dialect.as_ref(), "CREATE TABLE foo, FALLBACK (id INT)").is_err()
+ );
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datafusion.apache.org
For additional commands, e-mail: commits-help@datafusion.apache.org