Skip to content

Commit 0564a78

Browse files
authored
Merge pull request #10650 from RinChanNOWWW/check-view-dep
fix: avoid dependency loop when selecting from view.
2 parents 1dabd66 + 922073b commit 0564a78

File tree

3 files changed

+66
-6
lines changed

3 files changed

+66
-6
lines changed

src/query/sql/src/planner/binder/bind_context.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,10 @@ pub struct BindContext {
135135

136136
pub ctes_map: Box<DashMap<String, CteInfo>>,
137137

138-
pub is_view: bool,
138+
/// If current binding table is a view, record its database and name.
139+
///
140+
/// It's used to check if the view has a loop dependency.
141+
pub view_info: Option<(String, String)>,
139142
}
140143

141144
#[derive(Clone, Debug)]
@@ -154,7 +157,7 @@ impl BindContext {
154157
windows: Vec::new(),
155158
in_grouping: false,
156159
ctes_map: Box::new(DashMap::new()),
157-
is_view: false,
160+
view_info: None,
158161
}
159162
}
160163

@@ -167,7 +170,7 @@ impl BindContext {
167170
windows: vec![],
168171
in_grouping: false,
169172
ctes_map: parent.ctes_map.clone(),
170-
is_view: false,
173+
view_info: None,
171174
}
172175
}
173176

src/query/sql/src/planner/binder/table.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,25 @@ impl Binder {
117117
.await
118118
}
119119

120+
fn check_view_dep(bind_context: &BindContext, database: &str, view_name: &str) -> Result<()> {
121+
match &bind_context.parent {
122+
Some(parent) => match &parent.view_info {
123+
Some((db, v)) => {
124+
if db == database && v == view_name {
125+
Err(ErrorCode::Internal(format!(
126+
"View dependency loop detected (view: {}.{})",
127+
database, view_name
128+
)))
129+
} else {
130+
Self::check_view_dep(parent, database, view_name)
131+
}
132+
}
133+
_ => Ok(()),
134+
},
135+
_ => Ok(()),
136+
}
137+
}
138+
120139
#[async_recursion]
121140
async fn bind_single_table(
122141
&mut self,
@@ -191,6 +210,7 @@ impl Binder {
191210

192211
match table_meta.engine() {
193212
"VIEW" => {
213+
Self::check_view_dep(bind_context, &database, &table_name)?;
194214
let query = table_meta
195215
.options()
196216
.get(QUERY)
@@ -200,7 +220,8 @@ impl Binder {
200220
// For view, we need use a new context to bind it.
201221
let mut new_bind_context =
202222
BindContext::with_parent(Box::new(bind_context.clone()));
203-
new_bind_context.is_view = true;
223+
new_bind_context.view_info =
224+
Some((database.clone(), table_name.to_string()));
204225
if let Statement::Query(query) = &stmt {
205226
self.metadata.write().add_table(
206227
catalog,
@@ -239,7 +260,7 @@ impl Binder {
239260
database.clone(),
240261
table_meta,
241262
table_alias_name,
242-
bind_context.is_view,
263+
bind_context.view_info.is_some(),
243264
);
244265

245266
let (s_expr, mut bind_context) = self
@@ -529,7 +550,7 @@ impl Binder {
529550
windows: vec![],
530551
in_grouping: false,
531552
ctes_map: Box::new(DashMap::new()),
532-
is_view: false,
553+
view_info: None,
533554
};
534555
let (s_expr, mut new_bind_context) =
535556
self.bind_query(&new_bind_context, &cte_info.query).await?;

tests/sqllogictests/suites/base/05_ddl/05_0019_ddl_create_view

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,39 @@ AS
111111
SELECT *
112112
FROM
113113
's3://testbucket/admin/data/tuple.parquet' (FILES => ('tuple.parquet','test.parquet'),FILE_FORMAT => 'parquet',PATTERN => '.*.parquet', aws_key_id => 'minioadmin', aws_secret_key => 'minioadmin', endpoint_url => 'http://127.0.0.1:9900/')
114+
115+
statement ok
116+
drop view if exists loop_view1;
117+
118+
statement ok
119+
drop view if exists loop_view2;
120+
121+
statement ok
122+
drop view if exists loop_view3;
123+
124+
statement ok
125+
create view loop_view1 as select * from loop_view3;
126+
127+
statement ok
128+
create view loop_view2 as select * from loop_view1;
129+
130+
statement ok
131+
create view loop_view3 as select * from loop_view2;
132+
133+
statement error 1001
134+
select * from loop_view1;
135+
136+
statement error 1001
137+
select * from loop_view2;
138+
139+
statement error 1001
140+
select * from loop_view2;
141+
142+
statement ok
143+
drop view loop_view1;
144+
145+
statement ok
146+
drop view loop_view2;
147+
148+
statement ok
149+
drop view loop_view3;

0 commit comments

Comments
 (0)