1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
//! View DDL executor
use vibesql_ast::{CreateViewStmt, DropViewStmt};
use vibesql_catalog::{ViewDefinition, ViewDropBehavior};
use vibesql_storage::Database;
use crate::errors::ExecutorError;
/// Executor for view DDL statements
pub struct ViewExecutor;
impl ViewExecutor {
/// Execute CREATE VIEW, CREATE OR REPLACE VIEW, or CREATE VIEW IF NOT EXISTS
pub fn execute_create_view(
stmt: &CreateViewStmt,
database: &mut Database,
) -> Result<String, ExecutorError> {
let view_exists = database.catalog.get_view(&stmt.view_name).is_some();
// If IF NOT EXISTS and view already exists, just return success
if stmt.if_not_exists && view_exists {
return Ok(format!("View '{}' already exists (skipped)", stmt.view_name));
}
// If OR REPLACE, drop the view first if it exists
if stmt.or_replace && view_exists {
// Drop the existing view (no cascade needed for OR REPLACE)
database.catalog.drop_view(&stmt.view_name, false).map_err(|e| {
ExecutorError::StorageError(format!("Failed to drop existing view: {:?}", e))
})?;
}
// Tag temp views with the `temp` schema so they surface via
// sqlite_temp_master and are excluded from sqlite_master (#5541),
// mirroring the temp-trigger (#5532) and temp-index (#5513) tags.
let schema = if stmt.temporary { Some("temp".to_string()) } else { None };
// Create the view definition
let view_def = if let Some(ref sql) = stmt.sql_definition {
ViewDefinition::new_with_sql(
stmt.view_name.clone(),
stmt.columns.clone(),
*stmt.query.clone(),
stmt.with_check_option,
sql.clone(),
)
} else {
ViewDefinition::new(
stmt.view_name.clone(),
stmt.columns.clone(),
*stmt.query.clone(),
stmt.with_check_option,
)
}
.with_schema(schema);
// Add to catalog
database
.catalog
.create_view(view_def)
.map_err(|e| ExecutorError::StorageError(format!("Failed to create view: {:?}", e)))?;
if stmt.or_replace {
Ok(format!("View '{}' created or replaced", stmt.view_name))
} else {
Ok(format!("View '{}' created", stmt.view_name))
}
}
/// Execute DROP VIEW
pub fn execute_drop_view(
stmt: &DropViewStmt,
database: &mut Database,
) -> Result<String, ExecutorError> {
// Determine drop behavior:
// - CASCADE: drop dependent views recursively
// - RESTRICT (explicit): fail if dependents exist
// - Neither: SQLite-compatible behavior (just drop, ignore dependents)
let drop_behavior = if stmt.cascade {
ViewDropBehavior::Cascade
} else if stmt.restrict {
ViewDropBehavior::Restrict
} else {
ViewDropBehavior::Silent // SQLite-compatible: allow dropping even with dependents
};
// Cascade-drop the INSTEAD OF triggers defined ON this view, matching
// sqlite3 3.51.0: an INSTEAD OF trigger cannot outlive the view it is
// attached to, so `DROP VIEW v` removes every trigger whose `ON v`
// target is the dropped view. Resolve the view's temp/main schema tag
// *before* the catalog removes it so the temp-shadows-main filter can be
// applied (a main trigger on a main view must not be dropped when a
// same-named temp view is dropped, and vice versa). A trigger on a
// *different* view is left alone. See `Catalog::drop_view_triggers`
// (view analogue of the table path in #5597).
let view_is_temp =
database.catalog.get_view(&stmt.view_name).is_some_and(|v| v.is_temp());
// Drop the view
let result = database.catalog.drop_view_with_behavior(&stmt.view_name, drop_behavior);
match result {
Ok(()) => {
let dropped_triggers =
database.catalog.drop_view_triggers(&stmt.view_name, view_is_temp);
if dropped_triggers.is_empty() {
Ok(format!("View '{}' dropped", stmt.view_name))
} else {
Ok(format!(
"View '{}' and {} associated trigger(s) dropped",
stmt.view_name,
dropped_triggers.len()
))
}
}
Err(e) => {
// If IF EXISTS and view doesn't exist, that's OK
if stmt.if_exists
&& matches!(e, vibesql_catalog::errors::CatalogError::ViewNotFound(_))
{
Ok(format!("View '{}' does not exist (skipped)", stmt.view_name))
} else {
Err(ExecutorError::StorageError(format!("Failed to drop view: {:?}", e)))
}
}
}
}
}