Skip to content

Commit

Permalink
Handle CREATE [TEMPORARY|TEMP] VIEW [IF NOT EXISTS] (apache#993)
Browse files Browse the repository at this point in the history
  • Loading branch information
gabivlj authored and serprex committed Nov 6, 2023
1 parent b4434db commit 85a6229
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 4 deletions.
12 changes: 10 additions & 2 deletions src/ast/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1414,6 +1414,10 @@ pub enum Statement {
cluster_by: Vec<Ident>,
/// if true, has RedShift [`WITH NO SCHEMA BINDING`] clause <https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_VIEW.html>
with_no_schema_binding: bool,
/// if true, has SQLite `IF NOT EXISTS` clause <https://www.sqlite.org/lang_createview.html>
if_not_exists: bool,
/// if true, has SQLite `TEMP` or `TEMPORARY` clause <https://www.sqlite.org/lang_createview.html>
temporary: bool,
},
/// CREATE TABLE
CreateTable {
Expand Down Expand Up @@ -2462,13 +2466,17 @@ impl fmt::Display for Statement {
with_options,
cluster_by,
with_no_schema_binding,
if_not_exists,
temporary,
} => {
write!(
f,
"CREATE {or_replace}{materialized}VIEW {name}",
"CREATE {or_replace}{materialized}{temporary}VIEW {if_not_exists}{name}",
or_replace = if *or_replace { "OR REPLACE " } else { "" },
materialized = if *materialized { "MATERIALIZED " } else { "" },
name = name
name = name,
temporary = if *temporary { "TEMPORARY " } else { "" },
if_not_exists = if *if_not_exists { "IF NOT EXISTS " } else { "" }
)?;
if !with_options.is_empty() {
write!(f, " WITH ({})", display_comma_separated(with_options))?;
Expand Down
12 changes: 10 additions & 2 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2482,7 +2482,7 @@ impl<'a> Parser<'a> {
self.parse_create_table(or_replace, temporary, global, transient)
} else if self.parse_keyword(Keyword::MATERIALIZED) || self.parse_keyword(Keyword::VIEW) {
self.prev_token();
self.parse_create_view(or_replace)
self.parse_create_view(or_replace, temporary)
} else if self.parse_keyword(Keyword::EXTERNAL) {
self.parse_create_external_table(or_replace)
} else if self.parse_keyword(Keyword::FUNCTION) {
Expand Down Expand Up @@ -2963,9 +2963,15 @@ impl<'a> Parser<'a> {
}
}

pub fn parse_create_view(&mut self, or_replace: bool) -> Result<Statement, ParserError> {
pub fn parse_create_view(
&mut self,
or_replace: bool,
temporary: bool,
) -> Result<Statement, ParserError> {
let materialized = self.parse_keyword(Keyword::MATERIALIZED);
self.expect_keyword(Keyword::VIEW)?;
let if_not_exists = dialect_of!(self is SQLiteDialect|GenericDialect)
&& self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
// 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()?;
Expand Down Expand Up @@ -3000,6 +3006,8 @@ impl<'a> Parser<'a> {
with_options,
cluster_by,
with_no_schema_binding,
if_not_exists,
temporary,
})
}

Expand Down
55 changes: 55 additions & 0 deletions tests/sqlparser_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5321,6 +5321,8 @@ fn parse_create_view() {
with_options,
cluster_by,
with_no_schema_binding: late_binding,
if_not_exists,
temporary,
} => {
assert_eq!("myschema.myview", name.to_string());
assert_eq!(Vec::<Ident>::new(), columns);
Expand All @@ -5330,6 +5332,8 @@ fn parse_create_view() {
assert_eq!(with_options, vec![]);
assert_eq!(cluster_by, vec![]);
assert!(!late_binding);
assert!(!if_not_exists);
assert!(!temporary);
}
_ => unreachable!(),
}
Expand Down Expand Up @@ -5371,6 +5375,8 @@ fn parse_create_view_with_columns() {
materialized,
cluster_by,
with_no_schema_binding: late_binding,
if_not_exists,
temporary,
} => {
assert_eq!("v", name.to_string());
assert_eq!(columns, vec![Ident::new("has"), Ident::new("cols")]);
Expand All @@ -5380,6 +5386,39 @@ fn parse_create_view_with_columns() {
assert!(!or_replace);
assert_eq!(cluster_by, vec![]);
assert!(!late_binding);
assert!(!if_not_exists);
assert!(!temporary);
}
_ => unreachable!(),
}
}

#[test]
fn parse_create_view_temporary() {
let sql = "CREATE TEMPORARY VIEW myschema.myview AS SELECT foo FROM bar";
match verified_stmt(sql) {
Statement::CreateView {
name,
columns,
query,
or_replace,
materialized,
with_options,
cluster_by,
with_no_schema_binding: late_binding,
if_not_exists,
temporary,
} => {
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![]);
assert_eq!(cluster_by, vec![]);
assert!(!late_binding);
assert!(!if_not_exists);
assert!(temporary);
}
_ => unreachable!(),
}
Expand All @@ -5398,6 +5437,8 @@ fn parse_create_or_replace_view() {
materialized,
cluster_by,
with_no_schema_binding: late_binding,
if_not_exists,
temporary,
} => {
assert_eq!("v", name.to_string());
assert_eq!(columns, vec![]);
Expand All @@ -5407,6 +5448,8 @@ fn parse_create_or_replace_view() {
assert!(or_replace);
assert_eq!(cluster_by, vec![]);
assert!(!late_binding);
assert!(!if_not_exists);
assert!(!temporary);
}
_ => unreachable!(),
}
Expand All @@ -5429,6 +5472,8 @@ fn parse_create_or_replace_materialized_view() {
materialized,
cluster_by,
with_no_schema_binding: late_binding,
if_not_exists,
temporary,
} => {
assert_eq!("v", name.to_string());
assert_eq!(columns, vec![]);
Expand All @@ -5438,6 +5483,8 @@ fn parse_create_or_replace_materialized_view() {
assert!(or_replace);
assert_eq!(cluster_by, vec![]);
assert!(!late_binding);
assert!(!if_not_exists);
assert!(!temporary);
}
_ => unreachable!(),
}
Expand All @@ -5456,6 +5503,8 @@ fn parse_create_materialized_view() {
with_options,
cluster_by,
with_no_schema_binding: late_binding,
if_not_exists,
temporary,
} => {
assert_eq!("myschema.myview", name.to_string());
assert_eq!(Vec::<Ident>::new(), columns);
Expand All @@ -5465,6 +5514,8 @@ fn parse_create_materialized_view() {
assert!(!or_replace);
assert_eq!(cluster_by, vec![]);
assert!(!late_binding);
assert!(!if_not_exists);
assert!(!temporary);
}
_ => unreachable!(),
}
Expand All @@ -5483,6 +5534,8 @@ fn parse_create_materialized_view_with_cluster_by() {
with_options,
cluster_by,
with_no_schema_binding: late_binding,
if_not_exists,
temporary,
} => {
assert_eq!("myschema.myview", name.to_string());
assert_eq!(Vec::<Ident>::new(), columns);
Expand All @@ -5492,6 +5545,8 @@ fn parse_create_materialized_view_with_cluster_by() {
assert!(!or_replace);
assert_eq!(cluster_by, vec![Ident::new("foo")]);
assert!(!late_binding);
assert!(!if_not_exists);
assert!(!temporary);
}
_ => unreachable!(),
}
Expand Down
31 changes: 31 additions & 0 deletions tests/sqlparser_sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,37 @@ fn parse_create_virtual_table() {
sqlite_and_generic().verified_stmt(sql);
}

#[test]
fn parse_create_view_temporary_if_not_exists() {
let sql = "CREATE TEMPORARY VIEW IF NOT EXISTS myschema.myview AS SELECT foo FROM bar";
match sqlite_and_generic().verified_stmt(sql) {
Statement::CreateView {
name,
columns,
query,
or_replace,
materialized,
with_options,
cluster_by,
with_no_schema_binding: late_binding,
if_not_exists,
temporary,
} => {
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![]);
assert_eq!(cluster_by, vec![]);
assert!(!late_binding);
assert!(if_not_exists);
assert!(temporary);
}
_ => unreachable!(),
}
}

#[test]
fn double_equality_operator() {
// Sqlite supports this operator: https://www.sqlite.org/lang_expr.html#binaryops
Expand Down

0 comments on commit 85a6229

Please sign in to comment.