Skip to content

Commit

Permalink
Merge e8e12d4 into f053383
Browse files Browse the repository at this point in the history
  • Loading branch information
Dandandan committed Jul 27, 2020
2 parents f053383 + e8e12d4 commit a1b35de
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 14 deletions.
18 changes: 14 additions & 4 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -462,23 +462,25 @@ pub enum Statement {
},
/// CREATE VIEW
CreateView {
or_replace: bool,
materialized: bool,
/// View name
name: ObjectName,
columns: Vec<Ident>,
query: Box<Query>,
materialized: bool,
with_options: Vec<SqlOption>,
},
/// CREATE TABLE
CreateTable {
or_replace: bool,
external: bool,
if_not_exists: bool,
/// Table name
name: ObjectName,
/// Optional schema
columns: Vec<ColumnDef>,
constraints: Vec<TableConstraint>,
with_options: Vec<SqlOption>,
if_not_exists: bool,
external: bool,
file_format: Option<FileFormat>,
location: Option<String>,
query: Option<Box<Query>>,
Expand Down Expand Up @@ -629,12 +631,18 @@ impl fmt::Display for Statement {
}
Statement::CreateView {
name,
or_replace,
columns,
query,
materialized,
with_options,
} => {
write!(f, "CREATE")?;

if *or_replace {
write!(f, " OR REPLACE")?;
}

if *materialized {
write!(f, " MATERIALIZED")?;
}
Expand All @@ -656,6 +664,7 @@ impl fmt::Display for Statement {
columns,
constraints,
with_options,
or_replace,
if_not_exists,
external,
file_format,
Expand All @@ -672,7 +681,8 @@ impl fmt::Display for Statement {
// `CREATE TABLE t (a INT) AS SELECT a from t2`
write!(
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 { "" },
if_not_exists = if *if_not_exists { "IF NOT EXISTS " } else { "" },
name = name,
Expand Down
1 change: 1 addition & 0 deletions src/dialect/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ define_keywords!(
RELEASE,
RENAME,
REPEATABLE,
REPLACE,
RESTRICT,
RESULT,
RETURN,
Expand Down
32 changes: 22 additions & 10 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -987,17 +987,23 @@ impl Parser {

/// Parse a SQL CREATE statement
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) {
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) {
self.parse_create_index(false)
} else if self.parse_keywords(&[Keyword::UNIQUE, Keyword::INDEX]) {
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) {
self.parse_create_virtual_table()
} else if self.parse_keyword(Keyword::SCHEMA) {
Expand Down Expand Up @@ -1032,7 +1038,10 @@ impl Parser {
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)?;
let table_name = self.parse_object_name()?;
let (columns, constraints) = self.parse_columns()?;
Expand All @@ -1047,6 +1056,7 @@ impl Parser {
columns,
constraints,
with_options: vec![],
or_replace,
if_not_exists: false,
external: true,
file_format: Some(file_format),
Expand All @@ -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);
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.
let name = self.parse_object_name()?;
let columns = self.parse_parenthesized_column_list(Optional)?;
Expand All @@ -1088,6 +1098,7 @@ impl Parser {
columns,
query,
materialized,
or_replace,
with_options,
})
}
Expand Down Expand Up @@ -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 table_name = self.parse_object_name()?;
// parse optional column list (schema)
Expand All @@ -1160,6 +1171,7 @@ impl Parser {
columns,
constraints,
with_options,
or_replace,
if_not_exists,
external: false,
file_format: None,
Expand Down
137 changes: 137 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
fn parse_create_table_with_on_delete_on_update_2in_any_order() -> Result<(), ParserError> {
let sql = |options: &str| -> String {
Expand Down Expand Up @@ -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]
fn parse_create_external_table_lowercase() {
let sql = "create external table uk_cities (\
Expand Down Expand Up @@ -2491,13 +2573,15 @@ fn parse_create_view() {
name,
columns,
query,
or_replace,
materialized,
with_options,
} => {
assert_eq!("myschema.myview", name.to_string());
assert_eq!(Vec::<Ident>::new(), columns);
assert_eq!("SELECT foo FROM bar", query.to_string());
assert!(!materialized);
assert!(!or_replace);
assert_eq!(with_options, vec![]);
}
_ => unreachable!(),
Expand Down Expand Up @@ -2534,6 +2618,7 @@ fn parse_create_view_with_columns() {
Statement::CreateView {
name,
columns,
or_replace,
with_options,
query,
materialized,
Expand All @@ -2543,6 +2628,56 @@ fn parse_create_view_with_columns() {
assert_eq!(with_options, vec![]);
assert_eq!("SELECT 1, 2", query.to_string());
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!(),
}
Expand All @@ -2554,6 +2689,7 @@ fn parse_create_materialized_view() {
match verified_stmt(sql) {
Statement::CreateView {
name,
or_replace,
columns,
query,
materialized,
Expand All @@ -2564,6 +2700,7 @@ fn parse_create_materialized_view() {
assert_eq!("SELECT foo FROM bar", query.to_string());
assert!(materialized);
assert_eq!(with_options, vec![]);
assert!(!or_replace);
}
_ => unreachable!(),
}
Expand Down

0 comments on commit a1b35de

Please sign in to comment.