diff --git a/src/ast/data_type.rs b/src/ast/data_type.rs index 315d22b5..6b0ecd81 100644 --- a/src/ast/data_type.rs +++ b/src/ast/data_type.rs @@ -219,6 +219,10 @@ pub enum DataType { /// [hive]: https://docs.cloudera.com/cdw-runtime/cloud/impala-sql-reference/topics/impala-struct.html /// [bigquery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#struct_type Struct(Vec), + /// No type specified - only used with + /// [`SQLiteDialect`](crate::dialect::SQLiteDialect), from statements such + /// as `CREATE TABLE t1 (a)`. + Unspecified, } impl fmt::Display for DataType { @@ -379,6 +383,7 @@ impl fmt::Display for DataType { write!(f, "STRUCT") } } + DataType::Unspecified => Ok(()), } } } diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index cd0cf179..cc3d5a53 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -586,7 +586,11 @@ pub struct ColumnDef { impl fmt::Display for ColumnDef { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} {}", self.name, self.data_type)?; + if self.data_type == DataType::Unspecified { + write!(f, "{}", self.name)?; + } else { + write!(f, "{} {}", self.name, self.data_type)?; + } if let Some(collation) = &self.collation { write!(f, " COLLATE {collation}")?; } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 5167be24..8778948c 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -65,7 +65,9 @@ macro_rules! dialect_of { /// encapsulates the parsing differences between dialects. /// /// [`GenericDialect`] is the most permissive dialect, and parses the union of -/// all the other dialects, when there is no ambiguity. +/// all the other dialects, when there is no ambiguity. However, it does not +/// currently allow `CREATE TABLE` statements without types specified for all +/// columns; use [`SQLiteDialect`] if you require that. /// /// # Examples /// Most users create a [`Dialect`] directly, as shown on the [module diff --git a/src/dialect/sqlite.rs b/src/dialect/sqlite.rs index 0640466c..622fddee 100644 --- a/src/dialect/sqlite.rs +++ b/src/dialect/sqlite.rs @@ -16,6 +16,11 @@ use crate::keywords::Keyword; use crate::parser::{Parser, ParserError}; /// A [`Dialect`] for [SQLite](https://www.sqlite.org) +/// +/// This dialect allows columns in a +/// [`CREATE TABLE`](https://sqlite.org/lang_createtable.html) statement with no +/// type specified, as in `CREATE TABLE t1 (a)`. In the AST, these columns will +/// have the data type [`Unspecified`](crate::ast::DataType::Unspecified). #[derive(Debug)] pub struct SQLiteDialect {} diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 92f4cf02..28cabbb1 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -4272,7 +4272,11 @@ impl<'a> Parser<'a> { pub fn parse_column_def(&mut self) -> Result { let name = self.parse_identifier()?; - let data_type = self.parse_data_type()?; + let data_type = if self.is_column_type_sqlite_unspecified() { + DataType::Unspecified + } else { + self.parse_data_type()? + }; let mut collation = if self.parse_keyword(Keyword::COLLATE) { Some(self.parse_object_name()?) } else { @@ -4308,6 +4312,29 @@ impl<'a> Parser<'a> { }) } + fn is_column_type_sqlite_unspecified(&mut self) -> bool { + if dialect_of!(self is SQLiteDialect) { + match self.peek_token().token { + Token::Word(word) => matches!( + word.keyword, + Keyword::CONSTRAINT + | Keyword::PRIMARY + | Keyword::NOT + | Keyword::UNIQUE + | Keyword::CHECK + | Keyword::DEFAULT + | Keyword::COLLATE + | Keyword::REFERENCES + | Keyword::GENERATED + | Keyword::AS + ), + _ => true, // e.g. comma immediately after column name + } + } else { + false + } + } + pub fn parse_optional_column_option(&mut self) -> Result, ParserError> { if self.parse_keywords(&[Keyword::CHARACTER, Keyword::SET]) { Ok(Some(ColumnOption::CharacterSet(self.parse_object_name()?))) diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs index 0bf91d5b..8de862fc 100644 --- a/tests/sqlparser_sqlite.rs +++ b/tests/sqlparser_sqlite.rs @@ -221,6 +221,11 @@ fn parse_create_table_gencol() { sqlite_and_generic().verified_stmt("CREATE TABLE t1 (a INT, b INT AS (a * 2) STORED)"); } +#[test] +fn parse_create_table_untyped() { + sqlite().verified_stmt("CREATE TABLE t1 (a, b AS (a * 2), c NOT NULL)"); +} + #[test] fn test_placeholder() { // In postgres, this would be the absolute value operator '@' applied to the column 'xxx'