fix(schema-diff): reuse native pg function header once in sync sql

This commit is contained in:
Thanadon Kongkanun authored and GitHub committed 2026-09-23 19:22:35 +08:00
1 parent 1d5ad22407
commit 642651f97a
1 file changed
+63
+63
View File
@@ -5081,6 +5081,17 @@ fn sqlserver_native_function_sql(definition: &str, qualified_name: &str, is_modi
Some(format!("{verb} {qualified_name}{};", &definition_after_verb[arguments_start..]))
}
/// PostgreSQL-family drivers read routines through `pg_get_functiondef`, which already
/// returns a complete `CREATE OR REPLACE FUNCTION name(args) ...` statement. Keep that
/// header verb and only swap in the target-qualified name.
fn native_create_routine_sql(definition: &str, qualified_name: &str) -> Option<String> {
let trimmed = definition.trim().trim_end_matches(';').trim_end();
let prefix = Regex::new(r"(?i)^CREATE\s+(?:OR\s+REPLACE\s+)?(?:FUNCTION|PROCEDURE)\b").ok()?.find(trimmed)?;
let definition_after_verb = trimmed[prefix.end()..].trim_start();
let arguments_start = definition_after_verb.find('(')?;
Some(format!("{} {qualified_name}{};", prefix.as_str(), &definition_after_verb[arguments_start..]))
}
fn generate_create_table_sql(
name: &str,
columns: &[ColumnDiff],
@@ -6196,6 +6207,13 @@ fn generate_schema_sync_sql_inner(
continue;
}
}
if db_type != DatabaseType::SqlServer {
let name = qualified_name(&diff.name, db_type, schema);
if let Some(sql) = native_create_routine_sql(&source.definition, &name) {
lines.push(sql);
continue;
}
}
let create_kw = if db_type == DatabaseType::SqlServer && diff.diff_type == "modified" {
"ALTER FUNCTION"
} else if profile.create_function_or_replace {
@@ -14087,6 +14105,51 @@ mod tests {
assert!(sql.contains("CASCADE"), "{sql}");
}
#[test]
fn postgres_function_sync_reuses_native_functiondef_header_once() {
let function = |arguments: &str, definition: &str| FunctionDiff {
diff_type: "added".into(),
name: "armor".into(),
source: Some(FunctionInfo {
name: "armor".into(),
function_type: "FUNCTION".into(),
data_type: "text".into(),
definition: definition.into(),
arguments: arguments.into(),
}),
target: None,
changes: vec![],
};
let diffs = [
function(
"bytea",
"CREATE OR REPLACE FUNCTION armor(bytea)\n RETURNS text\n LANGUAGE c\n IMMUTABLE PARALLEL SAFE STRICT\nAS '$libdir/pgcrypto', $function$pg_armor$function$\n",
),
function(
"bytea, text[], text[]",
"CREATE OR REPLACE FUNCTION armor(bytea, text[], text[])\n RETURNS text\n LANGUAGE c\nAS '$libdir/pgcrypto', $function$pg_armor$function$\n",
),
];
let sql = generate_schema_sync_sql(
&[],
&diffs,
&[],
&[],
&[],
DatabaseType::Postgres,
Some("public"),
false,
None,
&[],
);
assert_eq!(sql.matches("CREATE OR REPLACE FUNCTION").count(), 2, "{sql}");
assert!(sql.contains("CREATE OR REPLACE FUNCTION \"public\".\"armor\"(bytea)\n RETURNS text"), "{sql}");
assert!(sql.contains("CREATE OR REPLACE FUNCTION \"public\".\"armor\"(bytea, text[], text[])\n"), "{sql}");
assert!(sql.contains("$function$pg_armor$function$;"), "{sql}");
}
#[test]
fn mysql_skips_function_sequence_when_templates_absent() {
let fn_diff = FunctionDiff {