From ef1fba2d1fb307c095961511cd15008abc8053e0 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Mon, 10 Aug 2026 14:47:59 +0800 Subject: [PATCH] fix(mysql): save existing views with ALTER Closes #5735 --- crates/dbx-core/src/object_source_sql.rs | 68 ++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/crates/dbx-core/src/object_source_sql.rs b/crates/dbx-core/src/object_source_sql.rs index c6431f0c1c..bf3022ed5f 100644 --- a/crates/dbx-core/src/object_source_sql.rs +++ b/crates/dbx-core/src/object_source_sql.rs @@ -167,6 +167,10 @@ pub fn build_executable_object_source_statements(input: EditableObjectSourceSqlI return Ok(executable_informix_view_statements(input.schema.as_deref(), &input.name, source)); } + if input.database_type == DatabaseType::Mysql && input.object_type == ObjectSourceKind::View { + return Ok(vec![executable_mysql_view_ddl(source)]); + } + if is_mysql_like(input.database_type) && matches!(input.object_type, ObjectSourceKind::Function | ObjectSourceKind::Procedure) { @@ -455,6 +459,23 @@ fn executable_oracle_routine_ddl(source: &str) -> String { ensure_semicolon(trimmed) } +fn executable_mysql_view_ddl(source: &str) -> String { + let trimmed = source.trim(); + let statement_start = leading_sql_statement_start(trimmed); + let executable = &trimmed[statement_start..]; + let create_view = Regex::new( + r"(?is)^CREATE\s+(?:OR\s+REPLACE\s+)?(?:ALGORITHM\s*=\s*(?:UNDEFINED|MERGE|TEMPTABLE)\s+)?(?:DEFINER\s*=\s*(?:(?:`(?:``|[^`])+`|'(?:''|[^'])+'|[^\s]+)\s*@\s*(?:`(?:``|[^`])+`|'(?:''|[^'])+'|[^\s]+)|CURRENT_USER(?:\(\))?)\s+)?(?:SQL\s+SECURITY\s+(?:DEFINER|INVOKER)\s+)?VIEW\s+", + ) + .unwrap(); + if create_view.is_match(executable) { + let create = Regex::new(r"(?i)^CREATE\s+(?:OR\s+REPLACE\s+)?").unwrap(); + let replaced = create.replace(executable, "ALTER "); + return ensure_semicolon(&format!("{}{}", &trimmed[..statement_start], replaced)); + } + + ensure_semicolon(trimmed) +} + fn executable_informix_view_statements(schema: Option<&str>, name: &str, source: &str) -> Vec { let (target_name, create_tail) = informix_view_definition(schema, name, source); if source_starts_with_alter(source.trim()) { @@ -1785,6 +1806,53 @@ mod tests { assert_eq!(sql, "CREATE PROCEDURE `refresh_cache`() BEGIN SELECT 1; END;"); } + #[test] + fn mysql_view_source_opened_for_editing_uses_alter_view() { + let source = "CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`%` SQL SECURITY DEFINER VIEW `new_view` AS select `base_plugins`.`id` AS `id` from `base_plugins`"; + let expected = "ALTER ALGORITHM=UNDEFINED DEFINER=`root`@`%` SQL SECURITY DEFINER VIEW `new_view` AS select `base_plugins`.`id` AS `id` from `base_plugins`;"; + let input = EditableObjectSourceSqlInput { + database_type: DatabaseType::Mysql, + object_type: ObjectSourceKind::View, + schema: Some("dol_test".to_string()), + name: "new_view".to_string(), + source: source.to_string(), + }; + + assert_eq!(build_editable_object_source(input.clone()), expected); + assert_eq!(build_executable_object_source_sql(input).unwrap(), expected); + } + + #[test] + fn mysql_view_create_source_preserves_leading_comments() { + let source = + "-- keep this view note\n/* and this block */\nCREATE OR REPLACE VIEW `new_view` AS SELECT 1 AS `id`"; + let sql = build_executable_object_source_sql(EditableObjectSourceSqlInput { + database_type: DatabaseType::Mysql, + object_type: ObjectSourceKind::View, + schema: Some("dol_test".to_string()), + name: "new_view".to_string(), + source: source.to_string(), + }) + .unwrap(); + + assert_eq!(sql, "-- keep this view note\n/* and this block */\nALTER VIEW `new_view` AS SELECT 1 AS `id`;"); + } + + #[test] + fn mysql_view_alter_source_remains_unchanged() { + let source = "ALTER ALGORITHM=MERGE VIEW `new_view` AS SELECT 2 AS `id`;"; + let sql = build_executable_object_source_sql(EditableObjectSourceSqlInput { + database_type: DatabaseType::Mysql, + object_type: ObjectSourceKind::View, + schema: Some("dol_test".to_string()), + name: "new_view".to_string(), + source: source.to_string(), + }) + .unwrap(); + + assert_eq!(sql, source); + } + #[test] fn opengauss_procedure_source_omits_gsql_trailing_slash() { let source = "CREATE OR REPLACE PROCEDURE public.refresh_cache()\nAS DECLARE BEGIN\n NULL;\nEND;\n/";