diff --git a/ir/normalize.go b/ir/normalize.go index 8edddac2..25b08cc3 100644 --- a/ir/normalize.go +++ b/ir/normalize.go @@ -57,6 +57,18 @@ func normalizeSchema(schema *Schema) { for _, typeObj := range schema.Types { normalizeType(typeObj) } + + // Strip same-schema qualifiers from function/procedure privilege signatures + for _, priv := range schema.Privileges { + if priv.ObjectType == PrivilegeObjectTypeFunction || priv.ObjectType == PrivilegeObjectTypeProcedure { + priv.ObjectName = stripSchemaFromFunctionSignature(priv.ObjectName, schema.Name) + } + } + for _, priv := range schema.RevokedDefaultPrivileges { + if priv.ObjectType == PrivilegeObjectTypeFunction || priv.ObjectType == PrivilegeObjectTypeProcedure { + priv.ObjectName = stripSchemaFromFunctionSignature(priv.ObjectName, schema.Name) + } + } } // normalizeTable normalizes table-related objects @@ -634,6 +646,18 @@ func stripSchemaPrefix(typeName, prefix string) string { return typeName } +// stripSchemaFromFunctionSignature strips same-schema qualifiers from type references +// within a function signature like "func_name(p_name text, p_kind myschema.mytype)". +func stripSchemaFromFunctionSignature(signature, schema string) string { + if schema == "" { + return signature + } + // Replace both quoted and unquoted schema prefixes within the signature + signature = strings.ReplaceAll(signature, `"`+schema+`".`, "") + signature = strings.ReplaceAll(signature, schema+".", "") + return signature +} + // normalizeTrigger normalizes trigger representation func normalizeTrigger(trigger *Trigger) { if trigger == nil { diff --git a/testdata/diff/privilege/grant_function_custom_type/diff.sql b/testdata/diff/privilege/grant_function_custom_type/diff.sql new file mode 100644 index 00000000..80bdfc23 --- /dev/null +++ b/testdata/diff/privilege/grant_function_custom_type/diff.sql @@ -0,0 +1,3 @@ +REVOKE EXECUTE ON FUNCTION create_entity(p_name text, p_kind entity_kind) FROM PUBLIC; + +GRANT EXECUTE ON FUNCTION create_entity(p_name text, p_kind entity_kind) TO app_user; diff --git a/testdata/diff/privilege/grant_function_custom_type/new.sql b/testdata/diff/privilege/grant_function_custom_type/new.sql new file mode 100644 index 00000000..eaef6fcd --- /dev/null +++ b/testdata/diff/privilege/grant_function_custom_type/new.sql @@ -0,0 +1,16 @@ +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'app_user') THEN + CREATE ROLE app_user; + END IF; +END $$; + +CREATE TYPE entity_kind AS ENUM ('person', 'company', 'organization'); + +CREATE FUNCTION create_entity(p_name text, p_kind entity_kind) +RETURNS uuid +LANGUAGE sql +AS $$ SELECT gen_random_uuid(); $$; + +REVOKE ALL ON FUNCTION create_entity(text, entity_kind) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION create_entity(text, entity_kind) TO app_user; diff --git a/testdata/diff/privilege/grant_function_custom_type/old.sql b/testdata/diff/privilege/grant_function_custom_type/old.sql new file mode 100644 index 00000000..cc65688c --- /dev/null +++ b/testdata/diff/privilege/grant_function_custom_type/old.sql @@ -0,0 +1,13 @@ +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'app_user') THEN + CREATE ROLE app_user; + END IF; +END $$; + +CREATE TYPE entity_kind AS ENUM ('person', 'company', 'organization'); + +CREATE FUNCTION create_entity(p_name text, p_kind entity_kind) +RETURNS uuid +LANGUAGE sql +AS $$ SELECT gen_random_uuid(); $$; diff --git a/testdata/diff/privilege/grant_function_custom_type/plan.json b/testdata/diff/privilege/grant_function_custom_type/plan.json new file mode 100644 index 00000000..30cc8d5c --- /dev/null +++ b/testdata/diff/privilege/grant_function_custom_type/plan.json @@ -0,0 +1,26 @@ +{ + "version": "1.0.0", + "pgschema_version": "1.7.4", + "created_at": "1970-01-01T00:00:00Z", + "source_fingerprint": { + "hash": "bdbb0798afa7207ece90d61d6b44f6a7625cdcb9578888587c326d9c6ae8d22a" + }, + "groups": [ + { + "steps": [ + { + "sql": "REVOKE EXECUTE ON FUNCTION create_entity(p_name text, p_kind entity_kind) FROM PUBLIC;", + "type": "revoked_default_privilege", + "operation": "create", + "path": "revoked_default.FUNCTION.create_entity(p_name text, p_kind entity_kind)" + }, + { + "sql": "GRANT EXECUTE ON FUNCTION create_entity(p_name text, p_kind entity_kind) TO app_user;", + "type": "privilege", + "operation": "create", + "path": "privileges.FUNCTION.create_entity(p_name text, p_kind entity_kind).app_user" + } + ] + } + ] +} diff --git a/testdata/diff/privilege/grant_function_custom_type/plan.sql b/testdata/diff/privilege/grant_function_custom_type/plan.sql new file mode 100644 index 00000000..80bdfc23 --- /dev/null +++ b/testdata/diff/privilege/grant_function_custom_type/plan.sql @@ -0,0 +1,3 @@ +REVOKE EXECUTE ON FUNCTION create_entity(p_name text, p_kind entity_kind) FROM PUBLIC; + +GRANT EXECUTE ON FUNCTION create_entity(p_name text, p_kind entity_kind) TO app_user; diff --git a/testdata/diff/privilege/grant_function_custom_type/plan.txt b/testdata/diff/privilege/grant_function_custom_type/plan.txt new file mode 100644 index 00000000..365204c0 --- /dev/null +++ b/testdata/diff/privilege/grant_function_custom_type/plan.txt @@ -0,0 +1,18 @@ +Plan: 2 to add. + +Summary by type: + privileges: 1 to add + revoked default privileges: 1 to add + +Privileges: + + app_user + +Revoked default privileges: + + create_entity(p_name text, p_kind entity_kind) + +DDL to be executed: +-------------------------------------------------- + +REVOKE EXECUTE ON FUNCTION create_entity(p_name text, p_kind entity_kind) FROM PUBLIC; + +GRANT EXECUTE ON FUNCTION create_entity(p_name text, p_kind entity_kind) TO app_user;