mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-09-26 15:39:12 +00:00
Support create or replace view/table (#239)
* Support create or replace table * Support create or replace view * Simplify create or replace table parser * Add tests for create or replace external table and materialized view * Formatting * Address review comments * Create error if we didn't see a (external) table or (materialized) view afer create or replace
This commit is contained in:
parent
f053383c71
commit
d2e4340a32
4 changed files with 174 additions and 14 deletions
|
@ -462,23 +462,25 @@ pub enum Statement {
|
||||||
},
|
},
|
||||||
/// CREATE VIEW
|
/// CREATE VIEW
|
||||||
CreateView {
|
CreateView {
|
||||||
|
or_replace: bool,
|
||||||
|
materialized: bool,
|
||||||
/// View name
|
/// View name
|
||||||
name: ObjectName,
|
name: ObjectName,
|
||||||
columns: Vec<Ident>,
|
columns: Vec<Ident>,
|
||||||
query: Box<Query>,
|
query: Box<Query>,
|
||||||
materialized: bool,
|
|
||||||
with_options: Vec<SqlOption>,
|
with_options: Vec<SqlOption>,
|
||||||
},
|
},
|
||||||
/// CREATE TABLE
|
/// CREATE TABLE
|
||||||
CreateTable {
|
CreateTable {
|
||||||
|
or_replace: bool,
|
||||||
|
external: bool,
|
||||||
|
if_not_exists: bool,
|
||||||
/// Table name
|
/// Table name
|
||||||
name: ObjectName,
|
name: ObjectName,
|
||||||
/// Optional schema
|
/// Optional schema
|
||||||
columns: Vec<ColumnDef>,
|
columns: Vec<ColumnDef>,
|
||||||
constraints: Vec<TableConstraint>,
|
constraints: Vec<TableConstraint>,
|
||||||
with_options: Vec<SqlOption>,
|
with_options: Vec<SqlOption>,
|
||||||
if_not_exists: bool,
|
|
||||||
external: bool,
|
|
||||||
file_format: Option<FileFormat>,
|
file_format: Option<FileFormat>,
|
||||||
location: Option<String>,
|
location: Option<String>,
|
||||||
query: Option<Box<Query>>,
|
query: Option<Box<Query>>,
|
||||||
|
@ -629,12 +631,18 @@ impl fmt::Display for Statement {
|
||||||
}
|
}
|
||||||
Statement::CreateView {
|
Statement::CreateView {
|
||||||
name,
|
name,
|
||||||
|
or_replace,
|
||||||
columns,
|
columns,
|
||||||
query,
|
query,
|
||||||
materialized,
|
materialized,
|
||||||
with_options,
|
with_options,
|
||||||
} => {
|
} => {
|
||||||
write!(f, "CREATE")?;
|
write!(f, "CREATE")?;
|
||||||
|
|
||||||
|
if *or_replace {
|
||||||
|
write!(f, " OR REPLACE")?;
|
||||||
|
}
|
||||||
|
|
||||||
if *materialized {
|
if *materialized {
|
||||||
write!(f, " MATERIALIZED")?;
|
write!(f, " MATERIALIZED")?;
|
||||||
}
|
}
|
||||||
|
@ -656,6 +664,7 @@ impl fmt::Display for Statement {
|
||||||
columns,
|
columns,
|
||||||
constraints,
|
constraints,
|
||||||
with_options,
|
with_options,
|
||||||
|
or_replace,
|
||||||
if_not_exists,
|
if_not_exists,
|
||||||
external,
|
external,
|
||||||
file_format,
|
file_format,
|
||||||
|
@ -672,7 +681,8 @@ impl fmt::Display for Statement {
|
||||||
// `CREATE TABLE t (a INT) AS SELECT a from t2`
|
// `CREATE TABLE t (a INT) AS SELECT a from t2`
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"CREATE {external}TABLE {if_not_exists}{name}",
|
"CREATE {or_replace}{external}TABLE {if_not_exists}{name}",
|
||||||
|
or_replace = if *or_replace { "OR REPLACE " } else { "" },
|
||||||
external = if *external { "EXTERNAL " } else { "" },
|
external = if *external { "EXTERNAL " } else { "" },
|
||||||
if_not_exists = if *if_not_exists { "IF NOT EXISTS " } else { "" },
|
if_not_exists = if *if_not_exists { "IF NOT EXISTS " } else { "" },
|
||||||
name = name,
|
name = name,
|
||||||
|
|
|
@ -343,6 +343,7 @@ define_keywords!(
|
||||||
RELEASE,
|
RELEASE,
|
||||||
RENAME,
|
RENAME,
|
||||||
REPEATABLE,
|
REPEATABLE,
|
||||||
|
REPLACE,
|
||||||
RESTRICT,
|
RESTRICT,
|
||||||
RESULT,
|
RESULT,
|
||||||
RETURN,
|
RETURN,
|
||||||
|
|
|
@ -987,17 +987,23 @@ impl Parser {
|
||||||
|
|
||||||
/// Parse a SQL CREATE statement
|
/// Parse a SQL CREATE statement
|
||||||
pub fn parse_create(&mut self) -> Result<Statement, ParserError> {
|
pub fn parse_create(&mut self) -> Result<Statement, ParserError> {
|
||||||
|
let or_replace = self.parse_keywords(&[Keyword::OR, Keyword::REPLACE]);
|
||||||
if self.parse_keyword(Keyword::TABLE) {
|
if self.parse_keyword(Keyword::TABLE) {
|
||||||
self.parse_create_table()
|
self.parse_create_table(or_replace)
|
||||||
|
} else if self.parse_keyword(Keyword::MATERIALIZED) || self.parse_keyword(Keyword::VIEW) {
|
||||||
|
self.prev_token();
|
||||||
|
self.parse_create_view(or_replace)
|
||||||
|
} else if self.parse_keyword(Keyword::EXTERNAL) {
|
||||||
|
self.parse_create_external_table(or_replace)
|
||||||
|
} else if or_replace {
|
||||||
|
self.expected(
|
||||||
|
"[EXTERNAL] TABLE or [MATERIALIZED] VIEW after CREATE OR REPLACE",
|
||||||
|
self.peek_token(),
|
||||||
|
)
|
||||||
} else if self.parse_keyword(Keyword::INDEX) {
|
} else if self.parse_keyword(Keyword::INDEX) {
|
||||||
self.parse_create_index(false)
|
self.parse_create_index(false)
|
||||||
} else if self.parse_keywords(&[Keyword::UNIQUE, Keyword::INDEX]) {
|
} else if self.parse_keywords(&[Keyword::UNIQUE, Keyword::INDEX]) {
|
||||||
self.parse_create_index(true)
|
self.parse_create_index(true)
|
||||||
} else if self.parse_keyword(Keyword::MATERIALIZED) || self.parse_keyword(Keyword::VIEW) {
|
|
||||||
self.prev_token();
|
|
||||||
self.parse_create_view()
|
|
||||||
} else if self.parse_keyword(Keyword::EXTERNAL) {
|
|
||||||
self.parse_create_external_table()
|
|
||||||
} else if self.parse_keyword(Keyword::VIRTUAL) {
|
} else if self.parse_keyword(Keyword::VIRTUAL) {
|
||||||
self.parse_create_virtual_table()
|
self.parse_create_virtual_table()
|
||||||
} else if self.parse_keyword(Keyword::SCHEMA) {
|
} else if self.parse_keyword(Keyword::SCHEMA) {
|
||||||
|
@ -1032,7 +1038,10 @@ impl Parser {
|
||||||
Ok(Statement::CreateSchema { schema_name })
|
Ok(Statement::CreateSchema { schema_name })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_create_external_table(&mut self) -> Result<Statement, ParserError> {
|
pub fn parse_create_external_table(
|
||||||
|
&mut self,
|
||||||
|
or_replace: bool,
|
||||||
|
) -> Result<Statement, ParserError> {
|
||||||
self.expect_keyword(Keyword::TABLE)?;
|
self.expect_keyword(Keyword::TABLE)?;
|
||||||
let table_name = self.parse_object_name()?;
|
let table_name = self.parse_object_name()?;
|
||||||
let (columns, constraints) = self.parse_columns()?;
|
let (columns, constraints) = self.parse_columns()?;
|
||||||
|
@ -1047,6 +1056,7 @@ impl Parser {
|
||||||
columns,
|
columns,
|
||||||
constraints,
|
constraints,
|
||||||
with_options: vec![],
|
with_options: vec![],
|
||||||
|
or_replace,
|
||||||
if_not_exists: false,
|
if_not_exists: false,
|
||||||
external: true,
|
external: true,
|
||||||
file_format: Some(file_format),
|
file_format: Some(file_format),
|
||||||
|
@ -1072,10 +1082,10 @@ impl Parser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_create_view(&mut self) -> Result<Statement, ParserError> {
|
pub fn parse_create_view(&mut self, or_replace: bool) -> Result<Statement, ParserError> {
|
||||||
let materialized = self.parse_keyword(Keyword::MATERIALIZED);
|
let materialized = self.parse_keyword(Keyword::MATERIALIZED);
|
||||||
self.expect_keyword(Keyword::VIEW)?;
|
self.expect_keyword(Keyword::VIEW)?;
|
||||||
// Many dialects support `OR REPLACE` | `OR ALTER` right after `CREATE`, but we don't (yet).
|
// Many dialects support `OR ALTER` right after `CREATE`, but we don't (yet).
|
||||||
// ANSI SQL and Postgres support RECURSIVE here, but we don't support it either.
|
// ANSI SQL and Postgres support RECURSIVE here, but we don't support it either.
|
||||||
let name = self.parse_object_name()?;
|
let name = self.parse_object_name()?;
|
||||||
let columns = self.parse_parenthesized_column_list(Optional)?;
|
let columns = self.parse_parenthesized_column_list(Optional)?;
|
||||||
|
@ -1088,6 +1098,7 @@ impl Parser {
|
||||||
columns,
|
columns,
|
||||||
query,
|
query,
|
||||||
materialized,
|
materialized,
|
||||||
|
or_replace,
|
||||||
with_options,
|
with_options,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1136,7 +1147,7 @@ impl Parser {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_create_table(&mut self) -> Result<Statement, ParserError> {
|
pub fn parse_create_table(&mut self, or_replace: bool) -> Result<Statement, ParserError> {
|
||||||
let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
|
let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
|
||||||
let table_name = self.parse_object_name()?;
|
let table_name = self.parse_object_name()?;
|
||||||
// parse optional column list (schema)
|
// parse optional column list (schema)
|
||||||
|
@ -1160,6 +1171,7 @@ impl Parser {
|
||||||
columns,
|
columns,
|
||||||
constraints,
|
constraints,
|
||||||
with_options,
|
with_options,
|
||||||
|
or_replace,
|
||||||
if_not_exists,
|
if_not_exists,
|
||||||
external: false,
|
external: false,
|
||||||
file_format: None,
|
file_format: None,
|
||||||
|
|
|
@ -1244,6 +1244,35 @@ fn parse_create_table_as() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_create_or_replace_table() {
|
||||||
|
let sql = "CREATE OR REPLACE TABLE t (a INT)";
|
||||||
|
|
||||||
|
match verified_stmt(sql) {
|
||||||
|
Statement::CreateTable {
|
||||||
|
name, or_replace, ..
|
||||||
|
} => {
|
||||||
|
assert_eq!(name.to_string(), "t".to_string());
|
||||||
|
assert!(or_replace);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
@ -1357,6 +1386,59 @@ fn parse_create_external_table() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_create_or_replace_external_table() {
|
||||||
|
// Supported by at least Snowflake
|
||||||
|
// https://docs.snowflake.com/en/sql-reference/sql/create-external-table.html
|
||||||
|
let sql = "CREATE OR REPLACE EXTERNAL TABLE uk_cities (\
|
||||||
|
name VARCHAR(100) NOT NULL)\
|
||||||
|
STORED AS TEXTFILE LOCATION '/tmp/example.csv'";
|
||||||
|
let ast = one_statement_parses_to(
|
||||||
|
sql,
|
||||||
|
"CREATE OR REPLACE EXTERNAL TABLE uk_cities (\
|
||||||
|
name CHARACTER VARYING(100) NOT NULL) \
|
||||||
|
STORED AS TEXTFILE LOCATION '/tmp/example.csv'",
|
||||||
|
);
|
||||||
|
match ast {
|
||||||
|
Statement::CreateTable {
|
||||||
|
name,
|
||||||
|
columns,
|
||||||
|
constraints,
|
||||||
|
with_options,
|
||||||
|
if_not_exists,
|
||||||
|
external,
|
||||||
|
file_format,
|
||||||
|
location,
|
||||||
|
or_replace,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
assert_eq!("uk_cities", name.to_string());
|
||||||
|
assert_eq!(
|
||||||
|
columns,
|
||||||
|
vec![ColumnDef {
|
||||||
|
name: "name".into(),
|
||||||
|
data_type: DataType::Varchar(Some(100)),
|
||||||
|
collation: None,
|
||||||
|
options: vec![ColumnOptionDef {
|
||||||
|
name: None,
|
||||||
|
option: ColumnOption::NotNull
|
||||||
|
}],
|
||||||
|
},]
|
||||||
|
);
|
||||||
|
assert!(constraints.is_empty());
|
||||||
|
|
||||||
|
assert!(external);
|
||||||
|
assert_eq!(FileFormat::TEXTFILE, file_format.unwrap());
|
||||||
|
assert_eq!("/tmp/example.csv", location.unwrap());
|
||||||
|
|
||||||
|
assert_eq!(with_options, vec![]);
|
||||||
|
assert!(!if_not_exists);
|
||||||
|
assert!(or_replace);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_create_external_table_lowercase() {
|
fn parse_create_external_table_lowercase() {
|
||||||
let sql = "create external table uk_cities (\
|
let sql = "create external table uk_cities (\
|
||||||
|
@ -2491,6 +2573,7 @@ fn parse_create_view() {
|
||||||
name,
|
name,
|
||||||
columns,
|
columns,
|
||||||
query,
|
query,
|
||||||
|
or_replace,
|
||||||
materialized,
|
materialized,
|
||||||
with_options,
|
with_options,
|
||||||
} => {
|
} => {
|
||||||
|
@ -2498,6 +2581,7 @@ fn parse_create_view() {
|
||||||
assert_eq!(Vec::<Ident>::new(), columns);
|
assert_eq!(Vec::<Ident>::new(), columns);
|
||||||
assert_eq!("SELECT foo FROM bar", query.to_string());
|
assert_eq!("SELECT foo FROM bar", query.to_string());
|
||||||
assert!(!materialized);
|
assert!(!materialized);
|
||||||
|
assert!(!or_replace);
|
||||||
assert_eq!(with_options, vec![]);
|
assert_eq!(with_options, vec![]);
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
|
@ -2534,6 +2618,7 @@ fn parse_create_view_with_columns() {
|
||||||
Statement::CreateView {
|
Statement::CreateView {
|
||||||
name,
|
name,
|
||||||
columns,
|
columns,
|
||||||
|
or_replace,
|
||||||
with_options,
|
with_options,
|
||||||
query,
|
query,
|
||||||
materialized,
|
materialized,
|
||||||
|
@ -2543,6 +2628,56 @@ fn parse_create_view_with_columns() {
|
||||||
assert_eq!(with_options, vec![]);
|
assert_eq!(with_options, vec![]);
|
||||||
assert_eq!("SELECT 1, 2", query.to_string());
|
assert_eq!("SELECT 1, 2", query.to_string());
|
||||||
assert!(!materialized);
|
assert!(!materialized);
|
||||||
|
assert!(!or_replace)
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn parse_create_or_replace_view() {
|
||||||
|
let sql = "CREATE OR REPLACE VIEW v AS SELECT 1";
|
||||||
|
match verified_stmt(sql) {
|
||||||
|
Statement::CreateView {
|
||||||
|
name,
|
||||||
|
columns,
|
||||||
|
or_replace,
|
||||||
|
with_options,
|
||||||
|
query,
|
||||||
|
materialized,
|
||||||
|
} => {
|
||||||
|
assert_eq!("v", name.to_string());
|
||||||
|
assert_eq!(columns, vec![]);
|
||||||
|
assert_eq!(with_options, vec![]);
|
||||||
|
assert_eq!("SELECT 1", query.to_string());
|
||||||
|
assert!(!materialized);
|
||||||
|
assert!(or_replace)
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_create_or_replace_materialized_view() {
|
||||||
|
// Supported in BigQuery (Beta)
|
||||||
|
// https://cloud.google.com/bigquery/docs/materialized-views-intro
|
||||||
|
// and Snowflake:
|
||||||
|
// https://docs.snowflake.com/en/sql-reference/sql/create-materialized-view.html
|
||||||
|
let sql = "CREATE OR REPLACE MATERIALIZED VIEW v AS SELECT 1";
|
||||||
|
match verified_stmt(sql) {
|
||||||
|
Statement::CreateView {
|
||||||
|
name,
|
||||||
|
columns,
|
||||||
|
or_replace,
|
||||||
|
with_options,
|
||||||
|
query,
|
||||||
|
materialized,
|
||||||
|
} => {
|
||||||
|
assert_eq!("v", name.to_string());
|
||||||
|
assert_eq!(columns, vec![]);
|
||||||
|
assert_eq!(with_options, vec![]);
|
||||||
|
assert_eq!("SELECT 1", query.to_string());
|
||||||
|
assert!(materialized);
|
||||||
|
assert!(or_replace)
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
@ -2554,6 +2689,7 @@ fn parse_create_materialized_view() {
|
||||||
match verified_stmt(sql) {
|
match verified_stmt(sql) {
|
||||||
Statement::CreateView {
|
Statement::CreateView {
|
||||||
name,
|
name,
|
||||||
|
or_replace,
|
||||||
columns,
|
columns,
|
||||||
query,
|
query,
|
||||||
materialized,
|
materialized,
|
||||||
|
@ -2564,6 +2700,7 @@ fn parse_create_materialized_view() {
|
||||||
assert_eq!("SELECT foo FROM bar", query.to_string());
|
assert_eq!("SELECT foo FROM bar", query.to_string());
|
||||||
assert!(materialized);
|
assert!(materialized);
|
||||||
assert_eq!(with_options, vec![]);
|
assert_eq!(with_options, vec![]);
|
||||||
|
assert!(!or_replace);
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue