Circle detection for views

This commit is contained in:
Duy Dang
2025-10-01 02:12:21 +07:00
parent 9788f6d005
commit 5ceab1b3f4
3 changed files with 84 additions and 16 deletions

View File

@@ -6,17 +6,71 @@ use crate::translate::expr::{
use crate::translate::planner::ROWID_STRS;
use parking_lot::RwLock;
/// Simple view structure for non-materialized views
#[derive(Debug, Clone)]
pub enum ViewState {
Ready,
InProgress,
}
/// Simple view structure for non-materialized views
#[derive(Debug)]
pub struct View {
pub name: String,
pub sql: String,
pub select_stmt: ast::Select,
pub columns: Vec<Column>,
pub state: Mutex<ViewState>,
}
impl View {
fn new(name: String, sql: String, select_stmt: ast::Select, columns: Vec<Column>) -> Self {
Self {
name,
sql,
select_stmt,
columns,
state: Mutex::new(ViewState::Ready),
}
}
pub fn process(&self) -> Result<()> {
let mut state = self.state.lock().unwrap();
match *state {
ViewState::InProgress => {
bail_parse_error!("view {} is circularly defined", self.name)
}
ViewState::Ready => {
*state = ViewState::InProgress;
Ok(())
}
}
}
pub fn done(&self) {
let mut state = self.state.lock().unwrap();
match *state {
ViewState::InProgress => {
*state = ViewState::Ready;
}
ViewState::Ready => {}
}
}
}
impl Clone for View {
fn clone(&self) -> Self {
Self {
name: self.name.clone(),
sql: self.sql.clone(),
select_stmt: self.select_stmt.clone(),
columns: self.columns.clone(),
state: Mutex::new(ViewState::Ready),
}
}
}
/// Type alias for regular views collection
pub type ViewsMap = HashMap<String, View>;
pub type ViewsMap = HashMap<String, Arc<View>>;
use crate::storage::btree::BTreeCursor;
use crate::translate::collate::CollationSeq;
@@ -235,13 +289,13 @@ impl Schema {
/// Add a regular (non-materialized) view
pub fn add_view(&mut self, view: View) {
let name = normalize_ident(&view.name);
self.views.insert(name, view);
self.views.insert(name, Arc::new(view));
}
/// Get a regular view by name
pub fn get_view(&self, name: &str) -> Option<&View> {
pub fn get_view(&self, name: &str) -> Option<Arc<View>> {
let name = normalize_ident(name);
self.views.get(&name)
self.views.get(&name).cloned()
}
pub fn add_btree_table(&mut self, table: Arc<BTreeTable>) {
@@ -781,12 +835,8 @@ impl Schema {
}
// Create regular view
let view = View {
name: name.to_string(),
sql: sql.to_string(),
select_stmt: select,
columns: final_columns,
};
let view =
View::new(name.to_string(), sql.to_string(), select, final_columns);
self.add_view(view);
}
_ => {}
@@ -848,7 +898,11 @@ impl Clone for Schema {
.iter()
.map(|(name, view)| (name.clone(), view.clone()))
.collect();
let views = self.views.clone();
let views = self
.views
.iter()
.map(|(name, view)| (name.clone(), Arc::new((**view).clone())))
.collect();
let incompatible_views = self.incompatible_views.clone();
Self {
tables,

View File

@@ -403,11 +403,11 @@ fn parse_table(
return Ok(());
};
let regular_view = connection.with_schema(database_id, |schema| {
schema.get_view(table_name.as_str()).cloned()
});
let regular_view =
connection.with_schema(database_id, |schema| schema.get_view(table_name.as_str()));
if let Some(view) = regular_view {
// Views are essentially query aliases, so just Expand the view as a subquery
view.process()?;
let view_select = view.select_stmt.clone();
let subselect = Box::new(view_select);
@@ -417,7 +417,7 @@ fn parse_table(
.or_else(|| Some(ast::As::As(table_name.clone())));
// Recursively call parse_from_clause_table with the view as a SELECT
return parse_from_clause_table(
let result = parse_from_clause_table(
ast::SelectTable::Select(*subselect.clone(), view_alias),
resolver,
program,
@@ -426,6 +426,8 @@ fn parse_table(
ctes,
connection,
);
view.done();
return result;
}
let view = connection.with_schema(database_id, |schema| {

View File

@@ -229,3 +229,15 @@ do_execsql_test_on_specific_db {:memory:} view-with-having {
SELECT * FROM high_volume_products ORDER BY total_units DESC;
} {C|380
A|250}
do_execsql_test_error view-self-circle-detection {
CREATE VIEW v AS SELECT * FROM v;
SELECT * FROM v;
} {view v is circularly defined}
do_execsql_test_error view-mutual-circle-detection {
CREATE VIEW v AS SELECT * FROM vv;
CREATE VIEW vv AS SELECT * FROM v;
SELECT * FROM v;
} {view v is circularly defined}