vibesql_executor/
schema_ddl.rs

1//! Schema DDL executor
2
3use vibesql_ast::*;
4use vibesql_storage::Database;
5
6use crate::{create_table::CreateTableExecutor, errors::ExecutorError};
7
8/// Executor for schema DDL statements
9pub struct SchemaExecutor;
10
11impl SchemaExecutor {
12    /// Execute CREATE SCHEMA
13    pub fn execute_create_schema(
14        stmt: &CreateSchemaStmt,
15        database: &mut Database,
16    ) -> Result<String, ExecutorError> {
17        // Begin transaction for atomic execution
18        database.begin_transaction().map_err(|e| {
19            ExecutorError::StorageError(format!("Failed to begin transaction: {}", e))
20        })?;
21
22        // Execute the schema creation with transaction protection
23        let result = Self::execute_create_schema_internal(stmt, database);
24
25        // Commit or rollback based on result
26        match result {
27            Ok(msg) => {
28                database.commit_transaction().map_err(|e| {
29                    ExecutorError::StorageError(format!("Failed to commit transaction: {}", e))
30                })?;
31                Ok(msg)
32            }
33            Err(e) => {
34                database.rollback_transaction().map_err(|rollback_err| {
35                    ExecutorError::StorageError(format!(
36                        "Failed to rollback transaction after error: {}. Original error: {}",
37                        rollback_err, e
38                    ))
39                })?;
40                Err(e)
41            }
42        }
43    }
44
45    /// Internal implementation of CREATE SCHEMA (without transaction management)
46    fn execute_create_schema_internal(
47        stmt: &CreateSchemaStmt,
48        database: &mut Database,
49    ) -> Result<String, ExecutorError> {
50        // Create the schema first
51        if !stmt.if_not_exists || !database.catalog.schema_exists(&stmt.schema_name) {
52            database
53                .catalog
54                .create_schema(stmt.schema_name.clone())
55                .map_err(|e| ExecutorError::StorageError(format!("Catalog error: {:?}", e)))?;
56        }
57
58        // Save the current schema and switch to the new schema for element execution
59        let original_schema = database.catalog.get_current_schema().to_string();
60        database
61            .catalog
62            .set_current_schema(&stmt.schema_name)
63            .map_err(|e| ExecutorError::StorageError(format!("Schema error: {:?}", e)))?;
64
65        // Execute embedded schema elements (CREATE TABLE, etc.)
66        for element in &stmt.schema_elements {
67            let result = match element {
68                SchemaElement::CreateTable(table_stmt) => {
69                    CreateTableExecutor::execute(table_stmt, database)
70                }
71            };
72
73            // On first error, restore original schema and return error
74            // Transaction will be rolled back by the outer function
75            if let Err(e) = result {
76                // Attempt to restore original schema before failing
77                let _ = database.catalog.set_current_schema(&original_schema);
78                return Err(ExecutorError::StorageError(format!(
79                    "Failed to execute schema element: {}",
80                    e
81                )));
82            }
83        }
84
85        // Restore original schema
86        database
87            .catalog
88            .set_current_schema(&original_schema)
89            .map_err(|e| ExecutorError::StorageError(format!("Schema error: {:?}", e)))?;
90
91        let element_count = stmt.schema_elements.len();
92        if element_count > 0 {
93            Ok(format!("Schema '{}' created with {} element(s)", stmt.schema_name, element_count))
94        } else {
95            Ok(format!("Schema '{}' created", stmt.schema_name))
96        }
97    }
98
99    /// Execute DROP SCHEMA
100    pub fn execute_drop_schema(
101        stmt: &DropSchemaStmt,
102        database: &mut Database,
103    ) -> Result<String, ExecutorError> {
104        if stmt.if_exists && !database.catalog.schema_exists(&stmt.schema_name) {
105            return Ok(format!("Schema '{}' does not exist, skipping", stmt.schema_name));
106        }
107
108        database
109            .catalog
110            .drop_schema(&stmt.schema_name, stmt.cascade)
111            .map_err(|e| ExecutorError::StorageError(format!("Catalog error: {:?}", e)))?;
112        Ok(format!("Schema '{}' dropped", stmt.schema_name))
113    }
114
115    /// Execute SET SCHEMA
116    pub fn execute_set_schema(
117        stmt: &SetSchemaStmt,
118        database: &mut Database,
119    ) -> Result<String, ExecutorError> {
120        database
121            .catalog
122            .set_current_schema(&stmt.schema_name)
123            .map_err(|e| ExecutorError::StorageError(format!("Catalog error: {:?}", e)))?;
124        Ok(format!("Current schema set to '{}'", stmt.schema_name))
125    }
126
127    /// Execute SET CATALOG
128    pub fn execute_set_catalog(
129        stmt: &vibesql_ast::SetCatalogStmt,
130        database: &mut Database,
131    ) -> Result<String, ExecutorError> {
132        database.catalog.set_current_catalog(Some(stmt.catalog_name.clone()));
133        Ok(format!("Current catalog set to '{}'", stmt.catalog_name))
134    }
135
136    /// Execute SET NAMES
137    pub fn execute_set_names(
138        stmt: &vibesql_ast::SetNamesStmt,
139        database: &mut Database,
140    ) -> Result<String, ExecutorError> {
141        database.catalog.set_current_charset(stmt.charset_name.clone());
142
143        if let Some(ref collation) = stmt.collation {
144            database.catalog.set_current_collation(Some(collation.clone()));
145            Ok(format!(
146                "Character set set to '{}' with collation '{}'",
147                stmt.charset_name, collation
148            ))
149        } else {
150            database.catalog.set_current_collation(None);
151            Ok(format!("Character set set to '{}'", stmt.charset_name))
152        }
153    }
154
155    /// Execute SET SESSION/GLOBAL variable
156    pub fn execute_set_variable(
157        stmt: &vibesql_ast::SetVariableStmt,
158        database: &mut Database,
159    ) -> Result<String, ExecutorError> {
160        // Create a dummy empty schema for expression evaluation
161        // Session variables don't need table context
162        let empty_schema = vibesql_catalog::TableSchema::new(String::new(), vec![]);
163
164        // Create an empty row for expression evaluation
165        let empty_row = vibesql_storage::Row::new(vec![]);
166
167        // Create an expression evaluator with database context
168        let evaluator = crate::evaluator::ExpressionEvaluator::with_database(
169            &empty_schema,
170            database,
171        );
172
173        // Evaluate the value expression
174        let value = evaluator.eval(&stmt.value, &empty_row)?;
175
176        // Special handling for sql_mode variable - update the SQL mode
177        if stmt.variable.eq_ignore_ascii_case("sql_mode") {
178            // Extract the mode string from the value
179            let mode_str = match &value {
180                vibesql_types::SqlValue::Varchar(s) | vibesql_types::SqlValue::Character(s) => s.clone(),
181                other => other.to_string(),
182            };
183
184            // Parse the mode string into SqlMode
185            let mode: vibesql_types::SqlMode = mode_str.parse().map_err(|e: String| {
186                ExecutorError::StorageError(e)
187            })?;
188
189            // Set the SQL mode (this also updates the @@sql_mode session variable)
190            database.set_sql_mode(mode.clone());
191
192            return Ok(format!("SQL mode set to '{}'", mode));
193        }
194
195        // Set the session variable in the database
196        // Note: For now, we ignore the GLOBAL scope and always set session variables
197        // Implementing true GLOBAL variables would require a separate storage mechanism
198        database.set_session_variable(&stmt.variable, value.clone());
199
200        Ok(format!("Variable '{}' set to {:?}", stmt.variable, value))
201    }
202
203    /// Execute SET TIME ZONE
204    pub fn execute_set_time_zone(
205        stmt: &vibesql_ast::SetTimeZoneStmt,
206        database: &mut Database,
207    ) -> Result<String, ExecutorError> {
208        let timezone_str = match &stmt.zone {
209            vibesql_ast::TimeZoneSpec::Local => {
210                database.catalog.set_current_timezone("LOCAL".to_string());
211                "LOCAL".to_string()
212            }
213            vibesql_ast::TimeZoneSpec::Interval(interval) => {
214                database.catalog.set_current_timezone(interval.clone());
215                interval.clone()
216            }
217        };
218
219        Ok(format!("Time zone set to '{}'", timezone_str))
220    }
221}