Support SQLite column definitions with no type (#1075)

This commit is contained in:
Thomas Kluyver 2024-01-01 17:45:25 +00:00 committed by GitHub
parent a75778c8c7
commit a430d1a5a7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 51 additions and 3 deletions

View file

@ -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<StructField>),
/// 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(()),
}
}
}

View file

@ -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}")?;
}

View file

@ -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

View file

@ -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 {}

View file

@ -4272,7 +4272,11 @@ impl<'a> Parser<'a> {
pub fn parse_column_def(&mut self) -> Result<ColumnDef, ParserError> {
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<Option<ColumnOption>, ParserError> {
if self.parse_keywords(&[Keyword::CHARACTER, Keyword::SET]) {
Ok(Some(ColumnOption::CharacterSet(self.parse_object_name()?)))

View file

@ -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'