Skip to content

Commit fe04858

Browse files
authored
feat(frontend): support create [materialized] view if not exists (#10128)
1 parent 8762ba8 commit fe04858

File tree

9 files changed

+93
-10
lines changed

9 files changed

+93
-10
lines changed

e2e_test/streaming/basic.slt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,20 @@ statement ok
130130
drop materialized view mv
131131

132132
statement ok
133-
drop table t
133+
drop table t
134+
135+
statement ok
136+
create table t (v1 int, v2 int);
137+
138+
statement ok
139+
create materialized view if not exists mv as select count(*) from t;
140+
141+
statement ok
142+
create materialized view if not exists mv as select count(*) from t;
143+
144+
statement ok
145+
drop materialized view mv
146+
147+
statement ok
148+
drop table t
149+

src/frontend/planner_test/src/lib.rs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -451,24 +451,40 @@ impl TestCase {
451451
Statement::CreateView {
452452
materialized: true,
453453
or_replace: false,
454+
if_not_exists,
454455
name,
455456
query,
456457
columns,
457458
emit_mode,
458459
..
459460
} => {
460-
create_mv::handle_create_mv(handler_args, name, *query, columns, emit_mode)
461-
.await?;
461+
create_mv::handle_create_mv(
462+
handler_args,
463+
if_not_exists,
464+
name,
465+
*query,
466+
columns,
467+
emit_mode,
468+
)
469+
.await?;
462470
}
463471
Statement::CreateView {
464472
materialized: false,
465473
or_replace: false,
474+
if_not_exists,
466475
name,
467476
query,
468477
columns,
469478
..
470479
} => {
471-
create_view::handle_create_view(handler_args, name, columns, *query).await?;
480+
create_view::handle_create_view(
481+
handler_args,
482+
if_not_exists,
483+
name,
484+
columns,
485+
*query,
486+
)
487+
.await?;
472488
}
473489
Statement::Drop(drop_statement) => {
474490
drop_table::handle_drop_table(

src/frontend/src/handler/create_mv.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,14 +141,24 @@ pub fn gen_create_mv_plan(
141141

142142
pub async fn handle_create_mv(
143143
handler_args: HandlerArgs,
144+
if_not_exists: bool,
144145
name: ObjectName,
145146
query: Query,
146147
columns: Vec<Ident>,
147148
emit_mode: Option<EmitMode>,
148149
) -> Result<RwPgResponse> {
149150
let session = handler_args.session.clone();
150151

151-
session.check_relation_name_duplicated(name.clone())?;
152+
if let Err(e) = session.check_relation_name_duplicated(name.clone()) {
153+
if if_not_exists {
154+
return Ok(PgResponse::empty_result_with_notice(
155+
StatementType::CREATE_MATERIALIZED_VIEW,
156+
format!("relation \"{}\" already exists, skipping", name),
157+
));
158+
} else {
159+
return Err(e);
160+
}
161+
}
152162

153163
let (table, graph) = {
154164
let context = OptimizerContext::from_handler_args(handler_args);

src/frontend/src/handler/create_view.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ use crate::optimizer::OptimizerContext;
2828

2929
pub async fn handle_create_view(
3030
handler_args: HandlerArgs,
31+
if_not_exists: bool,
3132
name: ObjectName,
3233
columns: Vec<Ident>,
3334
query: Query,
@@ -40,7 +41,16 @@ pub async fn handle_create_view(
4041

4142
let properties = handler_args.with_options.clone();
4243

43-
session.check_relation_name_duplicated(name.clone())?;
44+
if let Err(e) = session.check_relation_name_duplicated(name.clone()) {
45+
if if_not_exists {
46+
return Ok(PgResponse::empty_result_with_notice(
47+
StatementType::CREATE_VIEW,
48+
format!("relation \"{}\" already exists, skipping", name),
49+
));
50+
} else {
51+
return Err(e);
52+
}
53+
}
4454

4555
// plan the query to validate it and resolve dependencies
4656
let (dependent_relations, schema) = {

src/frontend/src/handler/mod.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,7 @@ pub async fn handle(
348348
| Statement::Update { .. } => query::handle_query(handler_args, stmt, formats).await,
349349
Statement::CreateView {
350350
materialized,
351+
if_not_exists,
351352
name,
352353
columns,
353354
query,
@@ -363,9 +364,18 @@ pub async fn handle(
363364
.into());
364365
}
365366
if materialized {
366-
create_mv::handle_create_mv(handler_args, name, *query, columns, emit_mode).await
367+
create_mv::handle_create_mv(
368+
handler_args,
369+
if_not_exists,
370+
name,
371+
*query,
372+
columns,
373+
emit_mode,
374+
)
375+
.await
367376
} else {
368-
create_view::handle_create_view(handler_args, name, columns, *query).await
377+
create_view::handle_create_view(handler_args, if_not_exists, name, columns, *query)
378+
.await
369379
}
370380
}
371381
Statement::Flush => flush::handle_flush(handler_args).await,

src/sqlparser/src/ast/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -989,6 +989,7 @@ pub enum Statement {
989989
CreateView {
990990
or_replace: bool,
991991
materialized: bool,
992+
if_not_exists: bool,
992993
/// View name
993994
name: ObjectName,
994995
columns: Vec<Ident>,
@@ -1361,6 +1362,7 @@ impl fmt::Display for Statement {
13611362
Statement::CreateView {
13621363
name,
13631364
or_replace,
1365+
if_not_exists,
13641366
columns,
13651367
query,
13661368
materialized,
@@ -1369,9 +1371,10 @@ impl fmt::Display for Statement {
13691371
} => {
13701372
write!(
13711373
f,
1372-
"CREATE {or_replace}{materialized}VIEW {name}",
1374+
"CREATE {or_replace}{materialized}VIEW {if_not_exists}{name}",
13731375
or_replace = if *or_replace { "OR REPLACE " } else { "" },
13741376
materialized = if *materialized { "MATERIALIZED " } else { "" },
1377+
if_not_exists = if *if_not_exists { "IF NOT EXISTS " } else { "" },
13751378
name = name
13761379
)?;
13771380
if let Some(emit_mode) = emit_mode {

src/sqlparser/src/parser.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1881,6 +1881,7 @@ impl Parser {
18811881
materialized: bool,
18821882
or_replace: bool,
18831883
) -> Result<Statement, ParserError> {
1884+
let if_not_exists = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
18841885
// Many dialects support `OR ALTER` right after `CREATE`, but we don't (yet).
18851886
// ANSI SQL and Postgres support RECURSIVE here, but we don't support it either.
18861887
let name = self.parse_object_name()?;
@@ -1895,6 +1896,7 @@ impl Parser {
18951896
let query = Box::new(self.parse_query()?);
18961897
// Optional `WITH [ CASCADED | LOCAL ] CHECK OPTION` is widely supported here.
18971898
Ok(Statement::CreateView {
1899+
if_not_exists,
18981900
name,
18991901
columns,
19001902
query,

src/sqlparser/tests/sqlparser_common.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2908,10 +2908,11 @@ fn parse_exists_subquery() {
29082908

29092909
#[test]
29102910
fn parse_create_view() {
2911-
let sql = "CREATE VIEW myschema.myview AS SELECT foo FROM bar";
2911+
let sql = "CREATE VIEW IF NOT EXISTS myschema.myview AS SELECT foo FROM bar";
29122912
match verified_stmt(sql) {
29132913
Statement::CreateView {
29142914
name,
2915+
if_not_exists,
29152916
columns,
29162917
query,
29172918
or_replace,
@@ -2920,6 +2921,7 @@ fn parse_create_view() {
29202921
emit_mode,
29212922
} => {
29222923
assert_eq!("myschema.myview", name.to_string());
2924+
assert!(if_not_exists);
29232925
assert_eq!(Vec::<Ident>::new(), columns);
29242926
assert_eq!("SELECT foo FROM bar", query.to_string());
29252927
assert!(!materialized);
@@ -2959,6 +2961,7 @@ fn parse_create_view_with_columns() {
29592961
let sql = "CREATE VIEW v (has, cols) AS SELECT 1, 2";
29602962
match verified_stmt(sql) {
29612963
Statement::CreateView {
2964+
if_not_exists,
29622965
name,
29632966
columns,
29642967
or_replace,
@@ -2967,6 +2970,7 @@ fn parse_create_view_with_columns() {
29672970
materialized,
29682971
emit_mode,
29692972
} => {
2973+
assert!(!if_not_exists);
29702974
assert_eq!("v", name.to_string());
29712975
assert_eq!(
29722976
columns,
@@ -2986,6 +2990,7 @@ fn parse_create_or_replace_view() {
29862990
let sql = "CREATE OR REPLACE VIEW v AS SELECT 1";
29872991
match verified_stmt(sql) {
29882992
Statement::CreateView {
2993+
if_not_exists,
29892994
name,
29902995
columns,
29912996
or_replace,
@@ -2994,6 +2999,7 @@ fn parse_create_or_replace_view() {
29942999
materialized,
29953000
emit_mode,
29963001
} => {
3002+
assert!(!if_not_exists);
29973003
assert_eq!("v", name.to_string());
29983004
assert_eq!(columns, vec![]);
29993005
assert_eq!(with_options, vec![]);
@@ -3015,6 +3021,7 @@ fn parse_create_or_replace_materialized_view() {
30153021
let sql = "CREATE OR REPLACE MATERIALIZED VIEW v AS SELECT 1";
30163022
match verified_stmt(sql) {
30173023
Statement::CreateView {
3024+
if_not_exists,
30183025
name,
30193026
columns,
30203027
or_replace,
@@ -3023,7 +3030,9 @@ fn parse_create_or_replace_materialized_view() {
30233030
materialized,
30243031
emit_mode,
30253032
} => {
3033+
assert!(!if_not_exists);
30263034
assert_eq!("v", name.to_string());
3035+
assert!(!if_not_exists);
30273036
assert_eq!(columns, vec![]);
30283037
assert_eq!(with_options, vec![]);
30293038
assert_eq!("SELECT 1", query.to_string());
@@ -3040,6 +3049,7 @@ fn parse_create_materialized_view() {
30403049
let sql = "CREATE MATERIALIZED VIEW myschema.myview AS SELECT foo FROM bar";
30413050
match verified_stmt(sql) {
30423051
Statement::CreateView {
3052+
if_not_exists,
30433053
name,
30443054
or_replace,
30453055
columns,
@@ -3048,6 +3058,7 @@ fn parse_create_materialized_view() {
30483058
with_options,
30493059
emit_mode,
30503060
} => {
3061+
assert!(!if_not_exists);
30513062
assert_eq!("myschema.myview", name.to_string());
30523063
assert_eq!(Vec::<Ident>::new(), columns);
30533064
assert_eq!("SELECT foo FROM bar", query.to_string());
@@ -3065,6 +3076,7 @@ fn parse_create_materialized_view_emit_immediately() {
30653076
let sql = "CREATE MATERIALIZED VIEW myschema.myview EMIT IMMEDIATELY AS SELECT foo FROM bar";
30663077
match verified_stmt(sql) {
30673078
Statement::CreateView {
3079+
if_not_exists,
30683080
name,
30693081
or_replace,
30703082
columns,
@@ -3073,6 +3085,7 @@ fn parse_create_materialized_view_emit_immediately() {
30733085
with_options,
30743086
emit_mode,
30753087
} => {
3088+
assert!(!if_not_exists);
30763089
assert_eq!("myschema.myview", name.to_string());
30773090
assert_eq!(Vec::<Ident>::new(), columns);
30783091
assert_eq!("SELECT foo FROM bar", query.to_string());
@@ -3091,6 +3104,7 @@ fn parse_create_materialized_view_emit_on_window_close() {
30913104
"CREATE MATERIALIZED VIEW myschema.myview EMIT ON WINDOW CLOSE AS SELECT foo FROM bar";
30923105
match verified_stmt(sql) {
30933106
Statement::CreateView {
3107+
if_not_exists,
30943108
name,
30953109
or_replace,
30963110
columns,
@@ -3099,6 +3113,7 @@ fn parse_create_materialized_view_emit_on_window_close() {
30993113
with_options,
31003114
emit_mode,
31013115
} => {
3116+
assert!(!if_not_exists);
31023117
assert_eq!("myschema.myview", name.to_string());
31033118
assert_eq!(Vec::<Ident>::new(), columns);
31043119
assert_eq!("SELECT foo FROM bar", query.to_string());

src/tests/sqlsmith/src/sql_gen/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ impl<'a, R: Rng> SqlGenerator<'a, R> {
198198
let mview = Statement::CreateView {
199199
or_replace: false,
200200
materialized: true,
201+
if_not_exists: false,
201202
name,
202203
columns: vec![],
203204
query,

0 commit comments

Comments
 (0)