Skip to content

Commit d2e4340

Browse files
authored
Support create or replace view/table (apache#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
1 parent f053383 commit d2e4340

File tree

4 files changed

+174
-14
lines changed

4 files changed

+174
-14
lines changed

src/ast/mod.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -462,23 +462,25 @@ pub enum Statement {
462462
},
463463
/// CREATE VIEW
464464
CreateView {
465+
or_replace: bool,
466+
materialized: bool,
465467
/// View name
466468
name: ObjectName,
467469
columns: Vec<Ident>,
468470
query: Box<Query>,
469-
materialized: bool,
470471
with_options: Vec<SqlOption>,
471472
},
472473
/// CREATE TABLE
473474
CreateTable {
475+
or_replace: bool,
476+
external: bool,
477+
if_not_exists: bool,
474478
/// Table name
475479
name: ObjectName,
476480
/// Optional schema
477481
columns: Vec<ColumnDef>,
478482
constraints: Vec<TableConstraint>,
479483
with_options: Vec<SqlOption>,
480-
if_not_exists: bool,
481-
external: bool,
482484
file_format: Option<FileFormat>,
483485
location: Option<String>,
484486
query: Option<Box<Query>>,
@@ -629,12 +631,18 @@ impl fmt::Display for Statement {
629631
}
630632
Statement::CreateView {
631633
name,
634+
or_replace,
632635
columns,
633636
query,
634637
materialized,
635638
with_options,
636639
} => {
637640
write!(f, "CREATE")?;
641+
642+
if *or_replace {
643+
write!(f, " OR REPLACE")?;
644+
}
645+
638646
if *materialized {
639647
write!(f, " MATERIALIZED")?;
640648
}
@@ -656,6 +664,7 @@ impl fmt::Display for Statement {
656664
columns,
657665
constraints,
658666
with_options,
667+
or_replace,
659668
if_not_exists,
660669
external,
661670
file_format,
@@ -672,7 +681,8 @@ impl fmt::Display for Statement {
672681
// `CREATE TABLE t (a INT) AS SELECT a from t2`
673682
write!(
674683
f,
675-
"CREATE {external}TABLE {if_not_exists}{name}",
684+
"CREATE {or_replace}{external}TABLE {if_not_exists}{name}",
685+
or_replace = if *or_replace { "OR REPLACE " } else { "" },
676686
external = if *external { "EXTERNAL " } else { "" },
677687
if_not_exists = if *if_not_exists { "IF NOT EXISTS " } else { "" },
678688
name = name,

src/dialect/keywords.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ define_keywords!(
343343
RELEASE,
344344
RENAME,
345345
REPEATABLE,
346+
REPLACE,
346347
RESTRICT,
347348
RESULT,
348349
RETURN,

src/parser.rs

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -987,17 +987,23 @@ impl Parser {
987987

988988
/// Parse a SQL CREATE statement
989989
pub fn parse_create(&mut self) -> Result<Statement, ParserError> {
990+
let or_replace = self.parse_keywords(&[Keyword::OR, Keyword::REPLACE]);
990991
if self.parse_keyword(Keyword::TABLE) {
991-
self.parse_create_table()
992+
self.parse_create_table(or_replace)
993+
} else if self.parse_keyword(Keyword::MATERIALIZED) || self.parse_keyword(Keyword::VIEW) {
994+
self.prev_token();
995+
self.parse_create_view(or_replace)
996+
} else if self.parse_keyword(Keyword::EXTERNAL) {
997+
self.parse_create_external_table(or_replace)
998+
} else if or_replace {
999+
self.expected(
1000+
"[EXTERNAL] TABLE or [MATERIALIZED] VIEW after CREATE OR REPLACE",
1001+
self.peek_token(),
1002+
)
9921003
} else if self.parse_keyword(Keyword::INDEX) {
9931004
self.parse_create_index(false)
9941005
} else if self.parse_keywords(&[Keyword::UNIQUE, Keyword::INDEX]) {
9951006
self.parse_create_index(true)
996-
} else if self.parse_keyword(Keyword::MATERIALIZED) || self.parse_keyword(Keyword::VIEW) {
997-
self.prev_token();
998-
self.parse_create_view()
999-
} else if self.parse_keyword(Keyword::EXTERNAL) {
1000-
self.parse_create_external_table()
10011007
} else if self.parse_keyword(Keyword::VIRTUAL) {
10021008
self.parse_create_virtual_table()
10031009
} else if self.parse_keyword(Keyword::SCHEMA) {
@@ -1032,7 +1038,10 @@ impl Parser {
10321038
Ok(Statement::CreateSchema { schema_name })
10331039
}
10341040

1035-
pub fn parse_create_external_table(&mut self) -> Result<Statement, ParserError> {
1041+
pub fn parse_create_external_table(
1042+
&mut self,
1043+
or_replace: bool,
1044+
) -> Result<Statement, ParserError> {
10361045
self.expect_keyword(Keyword::TABLE)?;
10371046
let table_name = self.parse_object_name()?;
10381047
let (columns, constraints) = self.parse_columns()?;
@@ -1047,6 +1056,7 @@ impl Parser {
10471056
columns,
10481057
constraints,
10491058
with_options: vec![],
1059+
or_replace,
10501060
if_not_exists: false,
10511061
external: true,
10521062
file_format: Some(file_format),
@@ -1072,10 +1082,10 @@ impl Parser {
10721082
}
10731083
}
10741084

1075-
pub fn parse_create_view(&mut self) -> Result<Statement, ParserError> {
1085+
pub fn parse_create_view(&mut self, or_replace: bool) -> Result<Statement, ParserError> {
10761086
let materialized = self.parse_keyword(Keyword::MATERIALIZED);
10771087
self.expect_keyword(Keyword::VIEW)?;
1078-
// Many dialects support `OR REPLACE` | `OR ALTER` right after `CREATE`, but we don't (yet).
1088+
// Many dialects support `OR ALTER` right after `CREATE`, but we don't (yet).
10791089
// ANSI SQL and Postgres support RECURSIVE here, but we don't support it either.
10801090
let name = self.parse_object_name()?;
10811091
let columns = self.parse_parenthesized_column_list(Optional)?;
@@ -1088,6 +1098,7 @@ impl Parser {
10881098
columns,
10891099
query,
10901100
materialized,
1101+
or_replace,
10911102
with_options,
10921103
})
10931104
}
@@ -1136,7 +1147,7 @@ impl Parser {
11361147
})
11371148
}
11381149

1139-
pub fn parse_create_table(&mut self) -> Result<Statement, ParserError> {
1150+
pub fn parse_create_table(&mut self, or_replace: bool) -> Result<Statement, ParserError> {
11401151
let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
11411152
let table_name = self.parse_object_name()?;
11421153
// parse optional column list (schema)
@@ -1160,6 +1171,7 @@ impl Parser {
11601171
columns,
11611172
constraints,
11621173
with_options,
1174+
or_replace,
11631175
if_not_exists,
11641176
external: false,
11651177
file_format: None,

tests/sqlparser_common.rs

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1244,6 +1244,35 @@ fn parse_create_table_as() {
12441244
}
12451245
}
12461246

1247+
#[test]
1248+
fn parse_create_or_replace_table() {
1249+
let sql = "CREATE OR REPLACE TABLE t (a INT)";
1250+
1251+
match verified_stmt(sql) {
1252+
Statement::CreateTable {
1253+
name, or_replace, ..
1254+
} => {
1255+
assert_eq!(name.to_string(), "t".to_string());
1256+
assert!(or_replace);
1257+
}
1258+
_ => unreachable!(),
1259+
}
1260+
1261+
let sql = "CREATE TABLE t (a INT, b INT) AS SELECT 1 AS b, 2 AS a";
1262+
match verified_stmt(sql) {
1263+
Statement::CreateTable { columns, query, .. } => {
1264+
assert_eq!(columns.len(), 2);
1265+
assert_eq!(columns[0].to_string(), "a INT".to_string());
1266+
assert_eq!(columns[1].to_string(), "b INT".to_string());
1267+
assert_eq!(
1268+
query,
1269+
Some(Box::new(verified_query("SELECT 1 AS b, 2 AS a")))
1270+
);
1271+
}
1272+
_ => unreachable!(),
1273+
}
1274+
}
1275+
12471276
#[test]
12481277
fn parse_create_table_with_on_delete_on_update_2in_any_order() -> Result<(), ParserError> {
12491278
let sql = |options: &str| -> String {
@@ -1357,6 +1386,59 @@ fn parse_create_external_table() {
13571386
}
13581387
}
13591388

1389+
#[test]
1390+
fn parse_create_or_replace_external_table() {
1391+
// Supported by at least Snowflake
1392+
// https://docs.snowflake.com/en/sql-reference/sql/create-external-table.html
1393+
let sql = "CREATE OR REPLACE EXTERNAL TABLE uk_cities (\
1394+
name VARCHAR(100) NOT NULL)\
1395+
STORED AS TEXTFILE LOCATION '/tmp/example.csv'";
1396+
let ast = one_statement_parses_to(
1397+
sql,
1398+
"CREATE OR REPLACE EXTERNAL TABLE uk_cities (\
1399+
name CHARACTER VARYING(100) NOT NULL) \
1400+
STORED AS TEXTFILE LOCATION '/tmp/example.csv'",
1401+
);
1402+
match ast {
1403+
Statement::CreateTable {
1404+
name,
1405+
columns,
1406+
constraints,
1407+
with_options,
1408+
if_not_exists,
1409+
external,
1410+
file_format,
1411+
location,
1412+
or_replace,
1413+
..
1414+
} => {
1415+
assert_eq!("uk_cities", name.to_string());
1416+
assert_eq!(
1417+
columns,
1418+
vec![ColumnDef {
1419+
name: "name".into(),
1420+
data_type: DataType::Varchar(Some(100)),
1421+
collation: None,
1422+
options: vec![ColumnOptionDef {
1423+
name: None,
1424+
option: ColumnOption::NotNull
1425+
}],
1426+
},]
1427+
);
1428+
assert!(constraints.is_empty());
1429+
1430+
assert!(external);
1431+
assert_eq!(FileFormat::TEXTFILE, file_format.unwrap());
1432+
assert_eq!("/tmp/example.csv", location.unwrap());
1433+
1434+
assert_eq!(with_options, vec![]);
1435+
assert!(!if_not_exists);
1436+
assert!(or_replace);
1437+
}
1438+
_ => unreachable!(),
1439+
}
1440+
}
1441+
13601442
#[test]
13611443
fn parse_create_external_table_lowercase() {
13621444
let sql = "create external table uk_cities (\
@@ -2491,13 +2573,15 @@ fn parse_create_view() {
24912573
name,
24922574
columns,
24932575
query,
2576+
or_replace,
24942577
materialized,
24952578
with_options,
24962579
} => {
24972580
assert_eq!("myschema.myview", name.to_string());
24982581
assert_eq!(Vec::<Ident>::new(), columns);
24992582
assert_eq!("SELECT foo FROM bar", query.to_string());
25002583
assert!(!materialized);
2584+
assert!(!or_replace);
25012585
assert_eq!(with_options, vec![]);
25022586
}
25032587
_ => unreachable!(),
@@ -2534,6 +2618,7 @@ fn parse_create_view_with_columns() {
25342618
Statement::CreateView {
25352619
name,
25362620
columns,
2621+
or_replace,
25372622
with_options,
25382623
query,
25392624
materialized,
@@ -2543,6 +2628,56 @@ fn parse_create_view_with_columns() {
25432628
assert_eq!(with_options, vec![]);
25442629
assert_eq!("SELECT 1, 2", query.to_string());
25452630
assert!(!materialized);
2631+
assert!(!or_replace)
2632+
}
2633+
_ => unreachable!(),
2634+
}
2635+
}
2636+
#[test]
2637+
fn parse_create_or_replace_view() {
2638+
let sql = "CREATE OR REPLACE VIEW v AS SELECT 1";
2639+
match verified_stmt(sql) {
2640+
Statement::CreateView {
2641+
name,
2642+
columns,
2643+
or_replace,
2644+
with_options,
2645+
query,
2646+
materialized,
2647+
} => {
2648+
assert_eq!("v", name.to_string());
2649+
assert_eq!(columns, vec![]);
2650+
assert_eq!(with_options, vec![]);
2651+
assert_eq!("SELECT 1", query.to_string());
2652+
assert!(!materialized);
2653+
assert!(or_replace)
2654+
}
2655+
_ => unreachable!(),
2656+
}
2657+
}
2658+
2659+
#[test]
2660+
fn parse_create_or_replace_materialized_view() {
2661+
// Supported in BigQuery (Beta)
2662+
// https://cloud.google.com/bigquery/docs/materialized-views-intro
2663+
// and Snowflake:
2664+
// https://docs.snowflake.com/en/sql-reference/sql/create-materialized-view.html
2665+
let sql = "CREATE OR REPLACE MATERIALIZED VIEW v AS SELECT 1";
2666+
match verified_stmt(sql) {
2667+
Statement::CreateView {
2668+
name,
2669+
columns,
2670+
or_replace,
2671+
with_options,
2672+
query,
2673+
materialized,
2674+
} => {
2675+
assert_eq!("v", name.to_string());
2676+
assert_eq!(columns, vec![]);
2677+
assert_eq!(with_options, vec![]);
2678+
assert_eq!("SELECT 1", query.to_string());
2679+
assert!(materialized);
2680+
assert!(or_replace)
25462681
}
25472682
_ => unreachable!(),
25482683
}
@@ -2554,6 +2689,7 @@ fn parse_create_materialized_view() {
25542689
match verified_stmt(sql) {
25552690
Statement::CreateView {
25562691
name,
2692+
or_replace,
25572693
columns,
25582694
query,
25592695
materialized,
@@ -2564,6 +2700,7 @@ fn parse_create_materialized_view() {
25642700
assert_eq!("SELECT foo FROM bar", query.to_string());
25652701
assert!(materialized);
25662702
assert_eq!(with_options, vec![]);
2703+
assert!(!or_replace);
25672704
}
25682705
_ => unreachable!(),
25692706
}

0 commit comments

Comments
 (0)