From 290693e0fad5a9181d587c0e81e660e1ca9252c6 Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Sat, 1 Aug 2026 00:03:27 +0800 Subject: [PATCH] fix(oracle): prefix bare routine sources for saving --- crates/dbx-core/src/object_source_sql.rs | 77 ++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/crates/dbx-core/src/object_source_sql.rs b/crates/dbx-core/src/object_source_sql.rs index 3e7b9b63e9..ec29c73165 100644 --- a/crates/dbx-core/src/object_source_sql.rs +++ b/crates/dbx-core/src/object_source_sql.rs @@ -157,6 +157,12 @@ pub fn build_executable_object_source_statements(input: EditableObjectSourceSqlI return Ok(vec![executable_oracle_view_ddl(input.schema.as_deref(), &input.name, source)]); } + if is_oracle_like(input.database_type) + && matches!(input.object_type, ObjectSourceKind::Function | ObjectSourceKind::Procedure) + { + return Ok(vec![executable_oracle_routine_ddl(source)]); + } + if input.database_type == DatabaseType::Informix && input.object_type == ObjectSourceKind::View { return Ok(executable_informix_view_statements(input.schema.as_deref(), &input.name, source)); } @@ -441,6 +447,14 @@ fn executable_oracle_view_ddl(schema: Option<&str>, name: &str, source: &str) -> format!("CREATE OR REPLACE VIEW {} AS\n{}", postgres_qualified_name(schema, name), ensure_semicolon(trimmed)) } +fn executable_oracle_routine_ddl(source: &str) -> String { + let trimmed = source.trim(); + if Regex::new(r"(?i)^(?:PROCEDURE|FUNCTION)\b").unwrap().is_match(trimmed) { + return ensure_semicolon(&format!("CREATE OR REPLACE {trimmed}")); + } + 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()) { @@ -1535,6 +1549,69 @@ mod tests { assert_eq!(sql, "CREATE OR REPLACE PACKAGE BODY PAYROLL AS\nEND PAYROLL;"); } + #[test] + fn oracle_like_bare_procedure_source_saves_as_create_or_replace() { + let source = "PROCEDURE refresh_cache AS\nBEGIN\n DELETE FROM cache_entries;\n COMMIT;\nEND;"; + + for database_type in [DatabaseType::Oracle, DatabaseType::Dameng] { + let statements = + build_executable_object_source_statements(input(database_type, ObjectSourceKind::Procedure, source)) + .unwrap(); + + assert_eq!( + statements, + vec!["CREATE OR REPLACE PROCEDURE refresh_cache AS\nBEGIN\n DELETE FROM cache_entries;\n COMMIT;\nEND;"] + ); + } + } + + #[test] + fn oracle_like_bare_function_source_saves_as_create_or_replace() { + for database_type in [DatabaseType::Oracle, DatabaseType::Dameng] { + let statements = build_executable_object_source_statements(input( + database_type, + ObjectSourceKind::Function, + "FUNCTION refresh_cache RETURN NUMBER AS\nBEGIN\n RETURN 1;\nEND;", + )) + .unwrap(); + + assert_eq!( + statements, + vec!["CREATE OR REPLACE FUNCTION refresh_cache RETURN NUMBER AS\nBEGIN\n RETURN 1;\nEND;"] + ); + } + } + + #[test] + fn oracle_routine_create_prefix_is_not_duplicated() { + for source in [ + "CREATE PROCEDURE refresh_cache AS BEGIN NULL; END;", + "CREATE OR REPLACE PROCEDURE refresh_cache AS BEGIN NULL; END;", + ] { + let statements = build_executable_object_source_statements(input( + DatabaseType::Oracle, + ObjectSourceKind::Procedure, + source, + )) + .unwrap(); + + assert_eq!(statements, vec![source]); + } + } + + #[test] + fn non_oracle_bare_routine_source_is_unchanged() { + let source = "PROCEDURE refresh_cache AS BEGIN NULL; END;"; + let statements = build_executable_object_source_statements(input( + DatabaseType::Postgres, + ObjectSourceKind::Procedure, + source, + )) + .unwrap(); + + assert_eq!(statements, vec![source]); + } + #[test] fn parses_programmable_metadata_object_kinds() { assert_eq!(parse_object_source_kind("TRIGGER"), Some(ObjectSourceKind::Trigger));