Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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