Skip to content

Commit fbb747b

Browse files
authored
Function arg types support (#228)
1 parent e866416 commit fbb747b

File tree

2 files changed

+96
-1
lines changed

2 files changed

+96
-1
lines changed

postgresql/helpers.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,12 +275,32 @@ func pgArrayToSet(arr pq.ByteaArray) *schema.Set {
275275
return schema.NewSet(schema.HashString, s)
276276
}
277277

278+
func quoteIdentifyIdent(ident string) string {
279+
// When passing a function with arguments like "test(text, char)" this will correctly parse it to "test"(text, char).
280+
// If we were to add quotes around the whole ident postgres would not be able to find the function.
281+
// Usually specifying parameters of a function is not necessary, but postgres allows function overloading where it
282+
// identifies the function by its parameters allowing the developer to have multiple functions with the same name.
283+
// Information:
284+
// https://en.wikipedia.org/wiki/Function_overloading
285+
// https://stackoverflow.com/a/48640797
286+
287+
s := strings.Split(ident, "(")
288+
289+
functionArgTypes := ""
290+
291+
if len(s) > 1 {
292+
functionArgTypes = "(" + s[1]
293+
}
294+
295+
return fmt.Sprintf("%s%s", pq.QuoteIdentifier(s[0]), functionArgTypes)
296+
}
297+
278298
func setToPgIdentList(schema string, idents *schema.Set) string {
279299
quotedIdents := make([]string, idents.Len())
280300
for i, ident := range idents.List() {
281301
quotedIdents[i] = fmt.Sprintf(
282302
"%s.%s",
283-
pq.QuoteIdentifier(schema), pq.QuoteIdentifier(ident.(string)),
303+
pq.QuoteIdentifier(schema), quoteIdentifyIdent(ident.(string)),
284304
)
285305
}
286306
return strings.Join(quotedIdents, ",")

postgresql/resource_postgresql_grant_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package postgresql
33
import (
44
"fmt"
55
"regexp"
6+
"strings"
67
"testing"
78

89
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
@@ -884,6 +885,68 @@ resource postgresql_grant "test" {
884885
}
885886
}
886887

888+
func TestAccPostgresqlGrantFunctionWithArgs(t *testing.T) {
889+
skipIfNotAcc(t)
890+
891+
config := getTestConfig(t)
892+
dsn := config.connStr("postgres")
893+
894+
// Create a test role and a schema as public has too wide open privileges
895+
dbExecute(t, dsn, fmt.Sprintf("CREATE ROLE test_role LOGIN PASSWORD '%s'", testRolePassword))
896+
dbExecute(t, dsn, "CREATE SCHEMA test_schema")
897+
dbExecute(t, dsn, "GRANT USAGE ON SCHEMA test_schema TO test_role")
898+
dbExecute(t, dsn, "ALTER DEFAULT PRIVILEGES REVOKE ALL ON FUNCTIONS FROM PUBLIC")
899+
900+
// Create test function in this schema
901+
dbExecute(t, dsn, `
902+
CREATE FUNCTION test_schema.test(arg1 text, arg2 character) RETURNS text
903+
AS $$ select 'foo'::text $$
904+
LANGUAGE SQL;
905+
`)
906+
defer func() {
907+
dbExecute(t, dsn, "DROP SCHEMA test_schema CASCADE")
908+
dbExecute(t, dsn, "DROP ROLE test_role")
909+
}()
910+
911+
// Test to grant directly to test_role and to public
912+
// in both case test_case should have the right
913+
for _, role := range []string{"test_role", "public"} {
914+
t.Run(role, func(t *testing.T) {
915+
916+
tfConfig := fmt.Sprintf(`
917+
resource postgresql_grant "test" {
918+
database = "postgres"
919+
role = "%s"
920+
schema = "test_schema"
921+
object_type = "function"
922+
privileges = ["EXECUTE"]
923+
objects = ["test(text, char)"]
924+
}
925+
`, role)
926+
927+
resource.Test(t, resource.TestCase{
928+
PreCheck: func() {
929+
testAccPreCheck(t)
930+
testCheckCompatibleVersion(t, featurePrivileges)
931+
},
932+
Providers: testAccProviders,
933+
Steps: []resource.TestStep{
934+
{
935+
Config: tfConfig,
936+
Check: resource.ComposeTestCheckFunc(
937+
resource.TestCheckResourceAttr("postgresql_grant.test", "id", fmt.Sprintf("%s_postgres_test_schema_function_test(text, char)", role)),
938+
resource.TestCheckResourceAttr("postgresql_grant.test", "privileges.#", "1"),
939+
resource.TestCheckResourceAttr("postgresql_grant.test", "privileges.0", "EXECUTE"),
940+
resource.TestCheckResourceAttr("postgresql_grant.test", "with_grant_option", "false"),
941+
testCheckFunctionWithArgsExecutable(t, "test_role", "test_schema.test", []string{pq.QuoteLiteral("value 1"), pq.QuoteLiteral("value 2")}),
942+
),
943+
},
944+
},
945+
})
946+
})
947+
}
948+
}
949+
887950
func TestAccPostgresqlGrantProcedure(t *testing.T) {
888951
skipIfNotAcc(t)
889952
testCheckCompatibleVersion(t, featureProcedure)
@@ -1288,6 +1351,18 @@ func testCheckFunctionExecutable(t *testing.T, role, function string) func(*terr
12881351
}
12891352
}
12901353

1354+
func testCheckFunctionWithArgsExecutable(t *testing.T, role, function string, args []string) func(*terraform.State) error {
1355+
return func(*terraform.State) error {
1356+
db := connectAsTestRole(t, role, "postgres")
1357+
defer db.Close()
1358+
1359+
if err := testHasGrantForQuery(db, fmt.Sprintf("SELECT %s(%s)", function, strings.Join(args, ", ")), true); err != nil {
1360+
return err
1361+
}
1362+
return nil
1363+
}
1364+
}
1365+
12911366
func testCheckProcedureExecutable(t *testing.T, role, procedure string) func(*terraform.State) error {
12921367
return func(*terraform.State) error {
12931368
db := connectAsTestRole(t, role, "postgres")

0 commit comments

Comments
 (0)