vibesql_catalog/store/advanced/
views.rs

1//! View management methods and dependency tracking.
2
3use crate::{errors::CatalogError, view::ViewDefinition};
4
5/// Drop behavior for views
6#[derive(Debug, Clone, Copy, PartialEq)]
7pub enum ViewDropBehavior {
8    /// CASCADE: drop dependent views recursively
9    Cascade,
10    /// RESTRICT: fail if dependents exist
11    Restrict,
12    /// Silent: drop the view, ignore dependents (SQLite-compatible)
13    Silent,
14}
15
16impl super::super::Catalog {
17    // ============================================================================
18    // View Management Methods
19    // ============================================================================
20
21    /// Create a VIEW
22    pub fn create_view(&mut self, view: ViewDefinition) -> Result<(), CatalogError> {
23        let name = view.name.clone();
24
25        // Normalize the key for case-insensitive storage
26        let key = if self.case_sensitive_identifiers { name.clone() } else { name.to_uppercase() };
27
28        // Check if view already exists
29        if self.views.contains_key(&key) {
30            return Err(CatalogError::ViewAlreadyExists(name));
31        }
32
33        self.views.insert(key, view);
34        Ok(())
35    }
36
37    /// Get a VIEW definition by name with optional case-insensitive lookup
38    pub fn get_view(&self, name: &str) -> Option<&ViewDefinition> {
39        // Normalize the key for lookup
40        let key =
41            if self.case_sensitive_identifiers { name.to_string() } else { name.to_uppercase() };
42
43        self.views.get(&key)
44    }
45
46    /// List all VIEW names (returns original names, not normalized keys)
47    pub fn list_views(&self) -> Vec<String> {
48        self.views.values().map(|v| v.name.clone()).collect()
49    }
50
51    /// Drop a VIEW with specified behavior
52    ///
53    /// - `Cascade`: Drop dependent views recursively
54    /// - `Restrict`: Fail if dependents exist
55    /// - `Silent`: Drop the view, ignore dependents (SQLite-compatible)
56    pub fn drop_view_with_behavior(
57        &mut self,
58        name: &str,
59        behavior: ViewDropBehavior,
60    ) -> Result<(), CatalogError> {
61        // Normalize the key for case-insensitive lookup
62        let key =
63            if self.case_sensitive_identifiers { name.to_string() } else { name.to_uppercase() };
64
65        // Check if view exists
66        if !self.views.contains_key(&key) {
67            return Err(CatalogError::ViewNotFound(name.to_string()));
68        }
69
70        match behavior {
71            ViewDropBehavior::Cascade => {
72                // Find all views that depend on this view or table
73                // Use the original name for dependency checking (not the normalized key)
74                let dependent_views = self.find_dependent_views(name);
75                let views_to_drop = dependent_views.clone();
76                for dependent_view in views_to_drop {
77                    // Recursively drop dependent views (they might have their own dependents)
78                    self.drop_view_with_behavior(&dependent_view, ViewDropBehavior::Cascade)?;
79                }
80            }
81            ViewDropBehavior::Restrict => {
82                // Check for dependent views and fail if any exist
83                let dependent_views = self.find_dependent_views(name);
84                if !dependent_views.is_empty() {
85                    return Err(CatalogError::ViewInUse {
86                        view_name: name.to_string(),
87                        dependent_views,
88                    });
89                }
90            }
91            ViewDropBehavior::Silent => {
92                // SQLite-compatible behavior: just drop the view, don't check dependents
93                // Dependent views will fail at query time if they reference this view
94            }
95        }
96
97        // Finally, drop the view itself using the normalized key
98        self.views.remove(&key);
99        Ok(())
100    }
101
102    /// Drop a VIEW (legacy method, defaults to RESTRICT behavior)
103    ///
104    /// This maintains backward compatibility with existing code.
105    /// Use `drop_view_with_behavior` for explicit control.
106    pub fn drop_view(&mut self, name: &str, cascade: bool) -> Result<(), CatalogError> {
107        let behavior = if cascade { ViewDropBehavior::Cascade } else { ViewDropBehavior::Restrict };
108        self.drop_view_with_behavior(name, behavior)
109    }
110
111    /// Find all views that depend on a given view or table
112    fn find_dependent_views(&self, target_name: &str) -> Vec<String> {
113        let mut dependent_views = Vec::new();
114
115        // Normalize target for key comparison
116        let target_key = if self.case_sensitive_identifiers {
117            target_name.to_string()
118        } else {
119            target_name.to_uppercase()
120        };
121
122        for (view_name, view_def) in &self.views {
123            if view_name == &target_key {
124                // Skip the view itself
125                continue;
126            }
127
128            // Check if this view's query references the target
129            if self.select_references_table(&view_def.query, target_name) {
130                dependent_views.push(view_name.clone());
131            }
132        }
133
134        dependent_views
135    }
136
137    /// Check if a SELECT statement references a specific table or view
138    fn select_references_table(&self, select: &vibesql_ast::SelectStmt, table_name: &str) -> bool {
139        // Check the FROM clause
140        if let Some(ref from) = select.from {
141            if self.does_from_clause_reference_table(from, table_name) {
142                return true;
143            }
144        }
145
146        // Check CTEs (WITH clause)
147        if let Some(ref ctes) = select.with_clause {
148            for cte in ctes {
149                if self.select_references_table(&cte.query, table_name) {
150                    return true;
151                }
152            }
153        }
154
155        // Check set operations (UNION, INTERSECT, EXCEPT)
156        if let Some(ref set_op) = select.set_operation {
157            if self.select_references_table(&set_op.right, table_name) {
158                return true;
159            }
160        }
161
162        false
163    }
164
165    /// Check if a FROM clause references a specific table or view
166    fn does_from_clause_reference_table(
167        &self,
168        from: &vibesql_ast::FromClause,
169        table_name: &str,
170    ) -> bool {
171        use vibesql_ast::FromClause;
172        match from {
173            FromClause::Table { name, .. } => {
174                // Respect case sensitivity setting when comparing table names
175                if self.case_sensitive_identifiers {
176                    name == table_name
177                } else {
178                    name.to_uppercase() == table_name.to_uppercase()
179                }
180            }
181            FromClause::Join { left, right, .. } => {
182                self.does_from_clause_reference_table(left, table_name)
183                    || self.does_from_clause_reference_table(right, table_name)
184            }
185            FromClause::Subquery { query, .. } => self.select_references_table(query, table_name),
186            // VALUES clauses don't reference any tables
187            FromClause::Values { .. } => false,
188        }
189    }
190}