mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-10-17 09:17:14 +00:00
Add CREATE TABLE AS support (#206)
We parse it as a regular `CREATE TABLE` statement
followed by an `AS <query>`, which is how BigQuery works:
https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#create_table_statement
ANSI SQL and PostgreSQL only support a plain list of columns
after the table name in a CTAS
`CREATE TABLE t (a) AS SELECT a FROM foo`
We currently only allow specifying a full schema with data
types, or omitting it altogether.
https://www.postgresql.org/docs/12/sql-createtableas.html
https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#as-subquery-clause
Finally, when no schema is specified, we print empty parens after a
plain `CREATE TABLE t ();` as required by PostgreSQL, but skip them
in a CTAS: `CREATE TABLE t AS ...`. This affects serialization only,
the parser allows omitting the schema in a regular `CREATE TABLE` too
since the first release of the parser:
7d27abdfb4/src/sqlparser.rs (L325-L332)
Co-authored-by: Nickolay Ponomarev <asqueella@gmail.com>
This commit is contained in:
parent
26361fd854
commit
15d5f71646
6 changed files with 104 additions and 26 deletions
|
@ -32,6 +32,7 @@ Check https://github.com/andygrove/sqlparser-rs/commits/master for undocumented
|
||||||
- Add serde support to AST structs and enums (#196) - thanks @panarch!
|
- Add serde support to AST structs and enums (#196) - thanks @panarch!
|
||||||
- Support `ALTER TABLE ADD COLUMN`, `RENAME COLUMN`, and `RENAME TO` (#203) - thanks @mashuai!
|
- Support `ALTER TABLE ADD COLUMN`, `RENAME COLUMN`, and `RENAME TO` (#203) - thanks @mashuai!
|
||||||
- Support `ALTER TABLE DROP COLUMN` (#148) - thanks @ivanceras!
|
- Support `ALTER TABLE DROP COLUMN` (#148) - thanks @ivanceras!
|
||||||
|
- Support `CREATE TABLE ... AS ...` (#206) - thanks @Dandandan!
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Report an error for unterminated string literals (#165)
|
- Report an error for unterminated string literals (#165)
|
||||||
|
|
|
@ -481,6 +481,7 @@ pub enum Statement {
|
||||||
external: bool,
|
external: bool,
|
||||||
file_format: Option<FileFormat>,
|
file_format: Option<FileFormat>,
|
||||||
location: Option<String>,
|
location: Option<String>,
|
||||||
|
query: Option<Box<Query>>,
|
||||||
},
|
},
|
||||||
/// CREATE INDEX
|
/// CREATE INDEX
|
||||||
CreateIndex {
|
CreateIndex {
|
||||||
|
@ -645,19 +646,32 @@ impl fmt::Display for Statement {
|
||||||
external,
|
external,
|
||||||
file_format,
|
file_format,
|
||||||
location,
|
location,
|
||||||
|
query,
|
||||||
} => {
|
} => {
|
||||||
|
// We want to allow the following options
|
||||||
|
// Empty column list, allowed by PostgreSQL:
|
||||||
|
// `CREATE TABLE t ()`
|
||||||
|
// No columns provided for CREATE TABLE AS:
|
||||||
|
// `CREATE TABLE t AS SELECT a from t2`
|
||||||
|
// Columns provided for CREATE TABLE AS:
|
||||||
|
// `CREATE TABLE t (a INT) AS SELECT a from t2`
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"CREATE {}TABLE {}{} ({}",
|
"CREATE {external}TABLE {if_not_exists}{name}",
|
||||||
if *external { "EXTERNAL " } else { "" },
|
external = if *external { "EXTERNAL " } else { "" },
|
||||||
if *if_not_exists { "IF NOT EXISTS " } else { "" },
|
if_not_exists = if *if_not_exists { "IF NOT EXISTS " } else { "" },
|
||||||
name,
|
name = name,
|
||||||
display_comma_separated(columns)
|
|
||||||
)?;
|
)?;
|
||||||
if !constraints.is_empty() {
|
if !columns.is_empty() || !constraints.is_empty() {
|
||||||
write!(f, ", {}", display_comma_separated(constraints))?;
|
write!(f, " ({}", display_comma_separated(columns))?;
|
||||||
|
if !columns.is_empty() && !constraints.is_empty() {
|
||||||
|
write!(f, ", ")?;
|
||||||
|
}
|
||||||
|
write!(f, "{})", display_comma_separated(constraints))?;
|
||||||
|
} else if query.is_none() {
|
||||||
|
// PostgreSQL allows `CREATE TABLE t ();`, but requires empty parens
|
||||||
|
write!(f, " ()")?;
|
||||||
}
|
}
|
||||||
write!(f, ")")?;
|
|
||||||
|
|
||||||
if *external {
|
if *external {
|
||||||
write!(
|
write!(
|
||||||
|
@ -670,6 +684,9 @@ impl fmt::Display for Statement {
|
||||||
if !with_options.is_empty() {
|
if !with_options.is_empty() {
|
||||||
write!(f, " WITH ({})", display_comma_separated(with_options))?;
|
write!(f, " WITH ({})", display_comma_separated(with_options))?;
|
||||||
}
|
}
|
||||||
|
if let Some(query) = query {
|
||||||
|
write!(f, " AS {}", query)?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Statement::CreateIndex {
|
Statement::CreateIndex {
|
||||||
|
|
|
@ -1020,6 +1020,7 @@ impl Parser {
|
||||||
external: true,
|
external: true,
|
||||||
file_format: Some(file_format),
|
file_format: Some(file_format),
|
||||||
location: Some(location),
|
location: Some(location),
|
||||||
|
query: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1108,8 +1109,17 @@ impl Parser {
|
||||||
let table_name = self.parse_object_name()?;
|
let table_name = self.parse_object_name()?;
|
||||||
// parse optional column list (schema)
|
// parse optional column list (schema)
|
||||||
let (columns, constraints) = self.parse_columns()?;
|
let (columns, constraints) = self.parse_columns()?;
|
||||||
|
|
||||||
|
// PostgreSQL supports `WITH ( options )`, before `AS`
|
||||||
let with_options = self.parse_with_options()?;
|
let with_options = self.parse_with_options()?;
|
||||||
|
|
||||||
|
// Parse optional `AS ( query )`
|
||||||
|
let query = if self.parse_keyword(Keyword::AS) {
|
||||||
|
Some(Box::new(self.parse_query()?))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Statement::CreateTable {
|
Ok(Statement::CreateTable {
|
||||||
name: table_name,
|
name: table_name,
|
||||||
columns,
|
columns,
|
||||||
|
@ -1119,6 +1129,7 @@ impl Parser {
|
||||||
external: false,
|
external: false,
|
||||||
file_format: None,
|
file_format: None,
|
||||||
location: None,
|
location: None,
|
||||||
|
query,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1044,6 +1044,7 @@ fn parse_create_table() {
|
||||||
external: false,
|
external: false,
|
||||||
file_format: None,
|
file_format: None,
|
||||||
location: None,
|
location: None,
|
||||||
|
query: _query,
|
||||||
} => {
|
} => {
|
||||||
assert_eq!("uk_cities", name.to_string());
|
assert_eq!("uk_cities", name.to_string());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -1177,6 +1178,36 @@ fn parse_drop_schema() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_create_table_as() {
|
||||||
|
let sql = "CREATE TABLE t AS SELECT * FROM a";
|
||||||
|
|
||||||
|
match verified_stmt(sql) {
|
||||||
|
Statement::CreateTable { name, query, .. } => {
|
||||||
|
assert_eq!(name.to_string(), "t".to_string());
|
||||||
|
assert_eq!(query, Some(Box::new(verified_query("SELECT * FROM a"))));
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// BigQuery allows specifying table schema in CTAS
|
||||||
|
// ANSI SQL and PostgreSQL let you only specify the list of columns
|
||||||
|
// (without data types) in a CTAS, but we have yet to support that.
|
||||||
|
let sql = "CREATE TABLE t (a INT, b INT) AS SELECT 1 AS b, 2 AS a";
|
||||||
|
match verified_stmt(sql) {
|
||||||
|
Statement::CreateTable { columns, query, .. } => {
|
||||||
|
assert_eq!(columns.len(), 2);
|
||||||
|
assert_eq!(columns[0].to_string(), "a INT".to_string());
|
||||||
|
assert_eq!(columns[1].to_string(), "b INT".to_string());
|
||||||
|
assert_eq!(
|
||||||
|
query,
|
||||||
|
Some(Box::new(verified_query("SELECT 1 AS b, 2 AS a")))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_create_table_with_on_delete_on_update_2in_any_order() -> Result<(), ParserError> {
|
fn parse_create_table_with_on_delete_on_update_2in_any_order() -> Result<(), ParserError> {
|
||||||
let sql = |options: &str| -> String {
|
let sql = |options: &str| -> String {
|
||||||
|
@ -1245,6 +1276,7 @@ fn parse_create_external_table() {
|
||||||
external,
|
external,
|
||||||
file_format,
|
file_format,
|
||||||
location,
|
location,
|
||||||
|
query: _query,
|
||||||
} => {
|
} => {
|
||||||
assert_eq!("uk_cities", name.to_string());
|
assert_eq!("uk_cities", name.to_string());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -1307,12 +1339,6 @@ fn parse_create_external_table_lowercase() {
|
||||||
assert_matches!(ast, Statement::CreateTable{..});
|
assert_matches!(ast, Statement::CreateTable{..});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn parse_create_table_empty() {
|
|
||||||
// Zero-column tables are weird, but supported by at least PostgreSQL.
|
|
||||||
let _ = verified_stmt("CREATE TABLE t ()");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_alter_table() {
|
fn parse_alter_table() {
|
||||||
let add_column = "ALTER TABLE tab ADD COLUMN foo TEXT";
|
let add_column = "ALTER TABLE tab ADD COLUMN foo TEXT";
|
||||||
|
|
|
@ -77,7 +77,7 @@ fn parse_show_columns() {
|
||||||
Statement::ShowColumns {
|
Statement::ShowColumns {
|
||||||
extended: false,
|
extended: false,
|
||||||
full: false,
|
full: false,
|
||||||
table_name: table_name,
|
table_name,
|
||||||
filter: Some(ShowStatementFilter::Where(
|
filter: Some(ShowStatementFilter::Where(
|
||||||
mysql_and_generic().verified_expr("1 = 2")
|
mysql_and_generic().verified_expr("1 = 2")
|
||||||
)),
|
)),
|
||||||
|
|
|
@ -43,6 +43,7 @@ fn parse_create_table_with_defaults() {
|
||||||
external: false,
|
external: false,
|
||||||
file_format: None,
|
file_format: None,
|
||||||
location: None,
|
location: None,
|
||||||
|
query: _query,
|
||||||
} => {
|
} => {
|
||||||
assert_eq!("public.customer", name.to_string());
|
assert_eq!("public.customer", name.to_string());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -227,24 +228,46 @@ fn parse_create_table_with_inherit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_create_table_if_not_exists() {
|
fn parse_create_table_empty() {
|
||||||
let sql = "CREATE TABLE IF NOT EXISTS uk_cities ()";
|
// Zero-column tables are weird, but supported by at least PostgreSQL.
|
||||||
let ast =
|
// <https://github.com/andygrove/sqlparser-rs/pull/94>
|
||||||
pg_and_generic().one_statement_parses_to(sql, "CREATE TABLE IF NOT EXISTS uk_cities ()");
|
let _ = pg_and_generic().verified_stmt("CREATE TABLE t ()");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_create_table_constraints_only() {
|
||||||
|
// Zero-column tables can also have constraints in PostgreSQL
|
||||||
|
let sql = "CREATE TABLE t (CONSTRAINT positive CHECK (2 > 1))";
|
||||||
|
let ast = pg_and_generic().verified_stmt(sql);
|
||||||
match ast {
|
match ast {
|
||||||
Statement::CreateTable {
|
Statement::CreateTable {
|
||||||
name,
|
name,
|
||||||
columns: _columns,
|
columns,
|
||||||
constraints,
|
constraints,
|
||||||
with_options,
|
..
|
||||||
|
} => {
|
||||||
|
assert_eq!("t", name.to_string());
|
||||||
|
assert!(columns.is_empty());
|
||||||
|
assert_eq!(
|
||||||
|
only(constraints).to_string(),
|
||||||
|
"CONSTRAINT positive CHECK (2 > 1)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_create_table_if_not_exists() {
|
||||||
|
let sql = "CREATE TABLE IF NOT EXISTS uk_cities ()";
|
||||||
|
let ast = pg_and_generic().verified_stmt(sql);
|
||||||
|
match ast {
|
||||||
|
Statement::CreateTable {
|
||||||
|
name,
|
||||||
if_not_exists: true,
|
if_not_exists: true,
|
||||||
external: false,
|
..
|
||||||
file_format: None,
|
|
||||||
location: None,
|
|
||||||
} => {
|
} => {
|
||||||
assert_eq!("uk_cities", name.to_string());
|
assert_eq!("uk_cities", name.to_string());
|
||||||
assert!(constraints.is_empty());
|
|
||||||
assert_eq!(with_options, vec![]);
|
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue