diff --git a/docs/resources/function_grant.md b/docs/resources/function_grant.md index fd07bf2f62..714f787918 100644 --- a/docs/resources/function_grant.md +++ b/docs/resources/function_grant.md @@ -14,27 +14,15 @@ description: |- ```terraform resource "snowflake_function_grant" "grant" { - database_name = "database" - schema_name = "schema" - function_name = "function" - - arguments { - name = "a" - type = "array" - } - arguments { - name = "b" - type = "string" - } - return_type = "string" - - privilege = "USAGE" - roles = ["role1", "role2"] - - shares = ["share1", "share2"] - - on_future = false - with_grant_option = false + database_name = "database" + schema_name = "schema" + function_name = "function" + argument_data_types = ["array", "string"] + privilege = "USAGE" + roles = ["role1", "role2"] + shares = ["share1", "share2"] + on_future = false + with_grant_option = false } ``` @@ -48,12 +36,13 @@ resource "snowflake_function_grant" "grant" { ### Optional -- `arguments` (Block List) List of the arguments for the function (must be present if function has arguments and function_name is present) (see [below for nested schema](#nestedblock--arguments)) +- `argument_data_types` (List of String) List of the argument data types for the function (must be present if function has arguments and function_name is present) +- `arguments` (Block List, Deprecated) List of the arguments for the function (must be present if function has arguments and function_name is present) (see [below for nested schema](#nestedblock--arguments)) - `enable_multiple_grants` (Boolean) When this is set to true, multiple grants of the same type can be created. This will cause Terraform to not revoke grants applied to roles and objects outside Terraform. - `function_name` (String) The name of the function on which to grant privileges immediately (only valid if on_future is false). - `on_future` (Boolean) When this is set to true and a schema_name is provided, apply this grant on all future functions in the given schema. When this is true and no schema_name is provided apply this grant on all future functions in the given database. The function_name, arguments, return_type, and shares fields must be unset in order to use on_future. - `privilege` (String) The privilege to grant on the current or future function. Must be one of `USAGE` or `OWNERSHIP`. -- `return_type` (String) The return type of the function (must be present if function_name is present) +- `return_type` (String, Deprecated) The return type of the function (must be present if function_name is present) - `schema_name` (String) The name of the schema containing the current or future functions on which to grant privileges. - `shares` (Set of String) Grants privilege to these shares (only valid if on_future is false). - `with_grant_option` (Boolean) When this is set to true, allows the recipient role to grant the privileges to other roles. @@ -76,5 +65,5 @@ Import is supported using the following syntax: ```shell # format is database name | schema name | function signature | privilege | true/false for with_grant_option -terraform import snowflake_function_grant.example 'dbName|schemaName|functionName(ARG1 ARG1TYPE, ARG2 ARG2TYPE):RETURNTYPE|USAGE|false' +terraform import snowflake_function_grant.example 'dbName|schemaName|functionName(ARG1TYPE,ARG2TYPE)|USAGE|false' ``` diff --git a/docs/resources/procedure_grant.md b/docs/resources/procedure_grant.md index 3afccdb550..38958e0d16 100644 --- a/docs/resources/procedure_grant.md +++ b/docs/resources/procedure_grant.md @@ -14,27 +14,15 @@ description: |- ```terraform resource "snowflake_procedure_grant" "grant" { - database_name = "database" - schema_name = "schema" - procedure_name = "procedure" - - arguments { - name = "a" - type = "array" - } - arguments { - name = "b" - type = "string" - } - return_type = "string" - - privilege = "SELECT" - roles = ["role1", "role2"] - - shares = ["share1", "share2"] - - on_future = false - with_grant_option = false + database_name = "database" + schema_name = "schema" + procedure_name = "procedure" + argument_data_types = ["array", "string"] + privilege = "SELECT" + roles = ["role1", "role2"] + shares = ["share1", "share2"] + on_future = false + with_grant_option = false } ``` @@ -48,12 +36,13 @@ resource "snowflake_procedure_grant" "grant" { ### Optional -- `arguments` (Block List) List of the arguments for the procedure (must be present if procedure has arguments and procedure_name is present) (see [below for nested schema](#nestedblock--arguments)) +- `argument_data_types` (List of String) List of the argument data types for the procedure (must be present if procedure has arguments and procedure_name is present) +- `arguments` (Block List, Deprecated) List of the arguments for the procedure (must be present if procedure has arguments and procedure_name is present) (see [below for nested schema](#nestedblock--arguments)) - `enable_multiple_grants` (Boolean) When this is set to true, multiple grants of the same type can be created. This will cause Terraform to not revoke grants applied to roles and objects outside Terraform. - `on_future` (Boolean) When this is set to true and a schema_name is provided, apply this grant on all future procedures in the given schema. When this is true and no schema_name is provided apply this grant on all future procedures in the given database. The procedure_name and shares fields must be unset in order to use on_future. - `privilege` (String) The privilege to grant on the current or future procedure. - `procedure_name` (String) The name of the procedure on which to grant privileges immediately (only valid if on_future is false). -- `return_type` (String) The return type of the procedure (must be present if procedure_name is present) +- `return_type` (String, Deprecated) The return type of the procedure (must be present if procedure_name is present) - `schema_name` (String) The name of the schema containing the current or future procedures on which to grant privileges. - `shares` (Set of String) Grants privilege to these shares (only valid if on_future is false). - `with_grant_option` (Boolean) When this is set to true, allows the recipient role to grant the privileges to other roles. @@ -76,5 +65,5 @@ Import is supported using the following syntax: ```shell # format is database name | schema name | procedure signature | privilege | true/false for with_grant_option -terraform import snowflake_procedure_grant.example 'dbName|schemaName|procedureName(ARG1 ARG1TYPE, ARG2 ARG2TYPE):RETURNTYPE|USAGE|false' +terraform import snowflake_procedure_grant.example 'dbName|schemaName|procedureName(ARG1TYPE,ARG2TYPE)|USAGE|false' ``` diff --git a/examples/resources/snowflake_function_grant/import.sh b/examples/resources/snowflake_function_grant/import.sh index df776e97cd..d184a3861c 100644 --- a/examples/resources/snowflake_function_grant/import.sh +++ b/examples/resources/snowflake_function_grant/import.sh @@ -1,2 +1,2 @@ # format is database name | schema name | function signature | privilege | true/false for with_grant_option -terraform import snowflake_function_grant.example 'dbName|schemaName|functionName(ARG1 ARG1TYPE, ARG2 ARG2TYPE):RETURNTYPE|USAGE|false' +terraform import snowflake_function_grant.example 'dbName|schemaName|functionName(ARG1TYPE,ARG2TYPE)|USAGE|false' diff --git a/examples/resources/snowflake_function_grant/resource.tf b/examples/resources/snowflake_function_grant/resource.tf index ab8f99a7dc..ef5d04fc33 100644 --- a/examples/resources/snowflake_function_grant/resource.tf +++ b/examples/resources/snowflake_function_grant/resource.tf @@ -1,23 +1,11 @@ resource "snowflake_function_grant" "grant" { - database_name = "database" - schema_name = "schema" - function_name = "function" - - arguments { - name = "a" - type = "array" - } - arguments { - name = "b" - type = "string" - } - return_type = "string" - - privilege = "USAGE" - roles = ["role1", "role2"] - - shares = ["share1", "share2"] - - on_future = false - with_grant_option = false + database_name = "database" + schema_name = "schema" + function_name = "function" + argument_data_types = ["array", "string"] + privilege = "USAGE" + roles = ["role1", "role2"] + shares = ["share1", "share2"] + on_future = false + with_grant_option = false } diff --git a/examples/resources/snowflake_procedure_grant/import.sh b/examples/resources/snowflake_procedure_grant/import.sh index 90b0393a68..772952f89e 100644 --- a/examples/resources/snowflake_procedure_grant/import.sh +++ b/examples/resources/snowflake_procedure_grant/import.sh @@ -1,2 +1,2 @@ # format is database name | schema name | procedure signature | privilege | true/false for with_grant_option -terraform import snowflake_procedure_grant.example 'dbName|schemaName|procedureName(ARG1 ARG1TYPE, ARG2 ARG2TYPE):RETURNTYPE|USAGE|false' +terraform import snowflake_procedure_grant.example 'dbName|schemaName|procedureName(ARG1TYPE,ARG2TYPE)|USAGE|false' diff --git a/examples/resources/snowflake_procedure_grant/resource.tf b/examples/resources/snowflake_procedure_grant/resource.tf index a3303493a7..54178280cf 100644 --- a/examples/resources/snowflake_procedure_grant/resource.tf +++ b/examples/resources/snowflake_procedure_grant/resource.tf @@ -1,23 +1,11 @@ resource "snowflake_procedure_grant" "grant" { - database_name = "database" - schema_name = "schema" - procedure_name = "procedure" - - arguments { - name = "a" - type = "array" - } - arguments { - name = "b" - type = "string" - } - return_type = "string" - - privilege = "SELECT" - roles = ["role1", "role2"] - - shares = ["share1", "share2"] - - on_future = false - with_grant_option = false + database_name = "database" + schema_name = "schema" + procedure_name = "procedure" + argument_data_types = ["array", "string"] + privilege = "SELECT" + roles = ["role1", "role2"] + shares = ["share1", "share2"] + on_future = false + with_grant_option = false } diff --git a/pkg/resources/function_grant.go b/pkg/resources/function_grant.go index 630aa0bb58..d92610ee15 100644 --- a/pkg/resources/function_grant.go +++ b/pkg/resources/function_grant.go @@ -2,6 +2,7 @@ package resources import ( "errors" + "fmt" "strings" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/snowflake" @@ -34,6 +35,14 @@ var functionGrantSchema = map[string]*schema.Schema{ Optional: true, Description: "List of the arguments for the function (must be present if function has arguments and function_name is present)", ForceNew: true, + Deprecated: "Use argument_data_types instead", + }, + "argument_data_types": { + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + Description: "List of the argument data types for the function (must be present if function has arguments and function_name is present)", + ForceNew: true, }, "enable_multiple_grants": { Type: schema.TypeBool, @@ -74,6 +83,7 @@ var functionGrantSchema = map[string]*schema.Schema{ Optional: true, Description: "The return type of the function (must be present if function_name is present)", ForceNew: true, + Deprecated: "Not used anymore", }, "roles": { Type: schema.TypeSet, @@ -122,27 +132,29 @@ func FunctionGrant() *TerraformGrantResource { // CreateFunctionGrant implements schema.CreateFunc. func CreateFunctionGrant(d *schema.ResourceData, meta interface{}) error { - var ( - functionName string - arguments []interface{} - returnType string - functionSignature string - argumentTypes []string - ) + var functionName string if name, ok := d.GetOk("function_name"); ok { functionName = name.(string) - if ret, ok := d.GetOk("return_type"); ok { - returnType = strings.ToUpper(ret.(string)) - } else { - return errors.New("return_type must be set when specifying function_name") - } } + dbName := d.Get("database_name").(string) schemaName := d.Get("schema_name").(string) priv := d.Get("privilege").(string) onFuture := d.Get("on_future").(bool) grantOption := d.Get("with_grant_option").(bool) - arguments = d.Get("arguments").([]interface{}) + var argumentDataTypes []string + // support deprecated arguments + if v, ok := d.GetOk("arguments"); ok { + arguments := v.([]interface{}) + for _, argument := range arguments { + argumentMap := argument.(map[string]interface{}) + argumentDataTypes = append(argumentDataTypes, argumentMap["type"].(string)) + } + } + if v, ok := d.GetOk("argument_data_types"); ok { + argumentDataTypes = expandStringList(v.([]interface{})) + } + roles := expandStringList(d.Get("roles").(*schema.Set).List()) if (functionName == "") && !onFuture { @@ -155,100 +167,82 @@ func CreateFunctionGrant(d *schema.ResourceData, meta interface{}) error { return errors.New("schema_name must be set unless on_future is true") } - if functionName != "" { - functionSignature, _, argumentTypes = formatCallableObjectName(functionName, returnType, arguments) - } else { - argumentTypes = make([]string, 0) - } - var builder snowflake.GrantBuilder if onFuture { builder = snowflake.FutureFunctionGrant(dbName, schemaName) } else { - builder = snowflake.FunctionGrant(dbName, schemaName, functionName, argumentTypes) + builder = snowflake.FunctionGrant(dbName, schemaName, functionName, argumentDataTypes) } if err := createGenericGrant(d, meta, builder); err != nil { return err } + // If this is a on_futures grant then the function name and arguments do not get set. This is only used for refresh purposes. + var functionObjectName string + if !onFuture { + functionObjectName = fmt.Sprintf("%s(%s)", functionName, strings.Join(argumentDataTypes, ",")) + } grant := &grantID{ ResourceName: dbName, SchemaName: schemaName, - ObjectName: functionSignature, + ObjectName: functionObjectName, Privilege: priv, GrantOption: grantOption, Roles: roles, } - dataIDInput, err := grant.String() + grantID, err := grant.String() if err != nil { return err } - d.SetId(dataIDInput) - + d.SetId(grantID) return ReadFunctionGrant(d, meta) } // ReadFunctionGrant implements schema.ReadFunc. func ReadFunctionGrant(d *schema.ResourceData, meta interface{}) error { - var ( - functionName string - returnType string - arguments []interface{} - argumentTypes []string - ) + var functionName string grantID, err := grantIDFromString(d.Id()) if err != nil { return err } dbName := grantID.ResourceName - schemaName := grantID.SchemaName - functionSignature := grantID.ObjectName - priv := grantID.Privilege - if err := d.Set("database_name", dbName); err != nil { return err } + schemaName := grantID.SchemaName if err := d.Set("schema_name", schemaName); err != nil { return err } + if err := d.Set("privilege", grantID.Privilege); err != nil { + return err + } + if err := d.Set("with_grant_option", grantID.GrantOption); err != nil { + return err + } + functionObjectName := grantID.ObjectName + var argumentDataTypes []string onFuture := false - if functionSignature == "" { + if functionObjectName == "" { onFuture = true } else { - functionSignatureMap, err := parseCallableObjectName(functionSignature) - if err != nil { - return err - } - functionName = functionSignatureMap["callableName"].(string) - returnType = functionSignatureMap["returnType"].(string) - arguments = functionSignatureMap["arguments"].([]interface{}) - argumentTypes = functionSignatureMap["argumentTypes"].([]string) + functionName, argumentDataTypes = parseFunctionObjectName(functionObjectName) } if err := d.Set("function_name", functionName); err != nil { return err } - if err := d.Set("arguments", arguments); err != nil { - return err - } - if err := d.Set("return_type", returnType); err != nil { + if err := d.Set("argument_data_types", argumentDataTypes); err != nil { return err } if err := d.Set("on_future", onFuture); err != nil { return err } - if err := d.Set("privilege", priv); err != nil { - return err - } - if err := d.Set("with_grant_option", grantID.GrantOption); err != nil { - return err - } var builder snowflake.GrantBuilder if onFuture { builder = snowflake.FutureFunctionGrant(dbName, schemaName) } else { - builder = snowflake.FunctionGrant(dbName, schemaName, functionName, argumentTypes) + builder = snowflake.FunctionGrant(dbName, schemaName, functionName, argumentDataTypes) } return readGenericGrant(d, meta, functionGrantSchema, builder, onFuture, validFunctionPrivileges) @@ -265,17 +259,15 @@ func DeleteFunctionGrant(d *schema.ResourceData, meta interface{}) error { onFuture := (grantID.ObjectName == "") + functionObjectName := grantID.ObjectName + var functionName string + var argumentDataTypes []string var builder snowflake.GrantBuilder if onFuture { builder = snowflake.FutureFunctionGrant(dbName, schemaName) } else { - functionSignatureMap, err := parseCallableObjectName(grantID.ObjectName) - if err != nil { - return err - } - functionName := functionSignatureMap["callableName"].(string) - argumentTypes := functionSignatureMap["argumentTypes"].([]string) - builder = snowflake.FunctionGrant(dbName, schemaName, functionName, argumentTypes) + functionName, argumentDataTypes = parseFunctionObjectName(functionObjectName) + builder = snowflake.FunctionGrant(dbName, schemaName, functionName, argumentDataTypes) } return deleteGenericGrant(d, meta, builder) } @@ -305,32 +297,25 @@ func UpdateFunctionGrant(d *schema.ResourceData, meta interface{}) error { dbName := grantID.ResourceName schemaName := grantID.SchemaName - functionName := grantID.ObjectName - onFuture := (functionName == "") + functionObjectName := grantID.ObjectName + onFuture := (functionObjectName == "") // create the builder var builder snowflake.GrantBuilder if onFuture { builder = snowflake.FutureFunctionGrant(dbName, schemaName) } else { - functionSignatureMap, err := parseCallableObjectName(grantID.ObjectName) - if err != nil { - return err - } - functionName := functionSignatureMap["callableName"].(string) - argumentTypes := functionSignatureMap["argumentTypes"].([]string) - builder = snowflake.FunctionGrant(dbName, schemaName, functionName, argumentTypes) + functionName, argumentDataTypes := parseFunctionObjectName(functionObjectName) + builder = snowflake.FunctionGrant(dbName, schemaName, functionName, argumentDataTypes) } // first revoke - if err := deleteGenericGrantRolesAndShares( meta, builder, grantID.Privilege, rolesToRevoke, sharesToRevoke, ); err != nil { return err } // then add - if err := createGenericGrantRolesAndShares( meta, builder, grantID.Privilege, grantID.GrantOption, rolesToAdd, sharesToAdd, ); err != nil { diff --git a/pkg/resources/function_grant_acceptance_test.go b/pkg/resources/function_grant_acceptance_test.go index f41c10c942..d2a51f4fd9 100644 --- a/pkg/resources/function_grant_acceptance_test.go +++ b/pkg/resources/function_grant_acceptance_test.go @@ -26,7 +26,6 @@ func TestAcc_FunctionFutureGrant(t *testing.T) { resource.TestCheckResourceAttr("snowflake_function_grant.test", "database_name", databaseName), resource.TestCheckResourceAttr("snowflake_function_grant.test", "schema_name", schemaName), resource.TestCheckResourceAttr("snowflake_function_grant.test", "function_name", ""), - resource.TestCheckResourceAttr("snowflake_function_grant.test", "return_type", ""), resource.TestCheckResourceAttr("snowflake_function_grant.test", "with_grant_option", "false"), resource.TestCheckResourceAttr("snowflake_function_grant.test", "on_future", "true"), resource.TestCheckResourceAttr("snowflake_function_grant.test", "privilege", "USAGE"), diff --git a/pkg/resources/function_grant_test.go b/pkg/resources/function_grant_test.go deleted file mode 100644 index 8cfc7815bf..0000000000 --- a/pkg/resources/function_grant_test.go +++ /dev/null @@ -1,182 +0,0 @@ -package resources_test - -import ( - "database/sql" - "testing" - "time" - - sqlmock "github.com/DATA-DOG/go-sqlmock" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/provider" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/resources" - . "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/testhelpers" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/stretchr/testify/require" -) - -func TestFunctionGrant(t *testing.T) { - r := require.New(t) - err := resources.FunctionGrant().Resource.InternalValidate(provider.Provider().Schema, true) - r.NoError(err) -} - -func TestFunctionGrantCreate(t *testing.T) { - r := require.New(t) - - in := map[string]interface{}{ - "function_name": "test-function", - "arguments": []interface{}{map[string]interface{}{ - "name": "a", - "type": "array", - }, map[string]interface{}{ - "name": "b", - "type": "string", - }}, - "return_type": "string", - "schema_name": "PUBLIC", - "database_name": "test-db", - "privilege": "USAGE", - "roles": []interface{}{"test-role-1", "test-role-2"}, - "shares": []interface{}{"test-share-1", "test-share-2"}, - "with_grant_option": true, - } - d := schema.TestResourceDataRaw(t, resources.FunctionGrant().Resource.Schema, in) - r.NotNil(d) - - WithMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) { - mock.ExpectExec(`^GRANT USAGE ON FUNCTION "test-db"."PUBLIC"."test-function"\(ARRAY, STRING\) TO ROLE "test-role-1" WITH GRANT OPTION$`).WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec(`^GRANT USAGE ON FUNCTION "test-db"."PUBLIC"."test-function"\(ARRAY, STRING\) TO ROLE "test-role-2" WITH GRANT OPTION$`).WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec(`^GRANT USAGE ON FUNCTION "test-db"."PUBLIC"."test-function"\(ARRAY, STRING\) TO SHARE "test-share-1" WITH GRANT OPTION$`).WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec(`^GRANT USAGE ON FUNCTION "test-db"."PUBLIC"."test-function"\(ARRAY, STRING\) TO SHARE "test-share-2" WITH GRANT OPTION$`).WillReturnResult(sqlmock.NewResult(1, 1)) - expectReadFunctionGrant(mock) - err := resources.CreateFunctionGrant(d, db) - r.NoError(err) - }) -} - -func TestFunctionGrantRead(t *testing.T) { - r := require.New(t) - - d := functionGrant(t, "test-db|PUBLIC|test-function(A ARRAY, B STRING):STRING|USAGE||false", map[string]interface{}{ - "function_name": "test-function", - "arguments": []interface{}{map[string]interface{}{ - "name": "a", - "type": "array", - }, map[string]interface{}{ - "name": "b", - "type": "string", - }}, - "return_type": "string", - "schema_name": "PUBLIC", - "database_name": "test-db", - "privilege": "USAGE", - "roles": []interface{}{}, - "shares": []interface{}{}, - "with_grant_option": false, - }) - - r.NotNil(d) - - WithMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) { - expectReadFunctionGrant(mock) - err := resources.ReadFunctionGrant(d, db) - r.NoError(err) - }) - - roles := d.Get("roles").(*schema.Set) - r.True(roles.Contains("test-role-1")) - r.True(roles.Contains("test-role-2")) - r.Equal(2, roles.Len()) - - shares := d.Get("shares").(*schema.Set) - r.True(shares.Contains("test-share-1")) - r.True(shares.Contains("test-share-2")) - r.Equal(2, shares.Len()) -} - -func expectReadFunctionGrant(mock sqlmock.Sqlmock) { - rows := sqlmock.NewRows([]string{ - "created_on", "privilege", "granted_on", "name", "granted_to", "grantee_name", "grant_option", "granted_by", - }).AddRow( - time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), "USAGE", "FUNCTION", "test-db.test-schema.\"test-function(A ARRAY, B STRING):STRING\"", "ROLE", "test-role-1", false, "bob", - ).AddRow( - time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), "USAGE", "FUNCTION", "test-db.test-schema.\"test-function(A ARRAY, B STRING):STRING\"", "ROLE", "test-role-2", false, "bob", - ).AddRow( - time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), "USAGE", "FUNCTION", "test-db.test-schema.\"test-function(A ARRAY, B STRING):STRING\"", "SHARE", "test-share-1", false, "bob", - ).AddRow( - time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), "USAGE", "FUNCTION", "test-db.test-schema.\"test-function(A ARRAY, B STRING):STRING\"", "SHARE", "test-share-2", false, "bob", - ) - mock.ExpectQuery(`^SHOW GRANTS ON FUNCTION "test-db"."PUBLIC"."test-function"\(ARRAY, STRING\)$`).WillReturnRows(rows) -} - -func TestFutureFunctionGrantCreate(t *testing.T) { - r := require.New(t) - - in := map[string]interface{}{ - "on_future": true, - "schema_name": "PUBLIC", - "database_name": "test-db", - "privilege": "USAGE", - "roles": []interface{}{"test-role-1", "test-role-2"}, - "with_grant_option": true, - } - d := schema.TestResourceDataRaw(t, resources.FunctionGrant().Resource.Schema, in) - r.NotNil(d) - - WithMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) { - mock.ExpectExec( - `^GRANT USAGE ON FUTURE FUNCTIONS IN SCHEMA "test-db"."PUBLIC" TO ROLE "test-role-1" WITH GRANT OPTION$`, - ).WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec( - `^GRANT USAGE ON FUTURE FUNCTIONS IN SCHEMA "test-db"."PUBLIC" TO ROLE "test-role-2" WITH GRANT OPTION$`, - ).WillReturnResult(sqlmock.NewResult(1, 1)) - expectReadFutureFunctionGrant(mock) - err := resources.CreateFunctionGrant(d, db) - r.NoError(err) - }) - - b := require.New(t) - - in = map[string]interface{}{ - "on_future": true, - "database_name": "test-db", - "privilege": "USAGE", - "roles": []interface{}{"test-role-1", "test-role-2"}, - "with_grant_option": false, - } - d = schema.TestResourceDataRaw(t, resources.FunctionGrant().Resource.Schema, in) - b.NotNil(d) - - WithMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) { - mock.ExpectExec( - `^GRANT USAGE ON FUTURE FUNCTIONS IN DATABASE "test-db" TO ROLE "test-role-1"$`, - ).WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec( - `^GRANT USAGE ON FUTURE FUNCTIONS IN DATABASE "test-db" TO ROLE "test-role-2"$`, - ).WillReturnResult(sqlmock.NewResult(1, 1)) - expectReadFutureFunctionDatabaseGrant(mock) - err := resources.CreateFunctionGrant(d, db) - b.NoError(err) - }) -} - -func expectReadFutureFunctionGrant(mock sqlmock.Sqlmock) { - rows := sqlmock.NewRows([]string{ - "created_on", "privilege", "grant_on", "name", "grant_to", "grantee_name", "grant_option", - }).AddRow( - time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), "USAGE", "FUNCTION", "test-db.PUBLIC.", "ROLE", "test-role-1", false, - ).AddRow( - time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), "USAGE", "FUNCTION", "test-db.PUBLIC.", "ROLE", "test-role-2", false, - ) - mock.ExpectQuery(`^SHOW FUTURE GRANTS IN SCHEMA "test-db"."PUBLIC"$`).WillReturnRows(rows) -} - -func expectReadFutureFunctionDatabaseGrant(mock sqlmock.Sqlmock) { - rows := sqlmock.NewRows([]string{ - "created_on", "privilege", "grant_on", "name", "grant_to", "grantee_name", "grant_option", - }).AddRow( - time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), "USAGE", "FUNCTION", "test-db.", "ROLE", "test-role-1", false, - ).AddRow( - time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), "USAGE", "FUNCTION", "test-db.", "ROLE", "test-role-2", false, - ) - mock.ExpectQuery(`^SHOW FUTURE GRANTS IN DATABASE "test-db"$`).WillReturnRows(rows) -} diff --git a/pkg/resources/grant_helpers.go b/pkg/resources/grant_helpers.go index ce9ab5e3c0..7efaa0313c 100644 --- a/pkg/resources/grant_helpers.go +++ b/pkg/resources/grant_helpers.go @@ -6,7 +6,6 @@ import ( "encoding/csv" "fmt" "log" - "regexp" "strings" "time" @@ -425,54 +424,19 @@ func expandRolesAndShares(d *schema.ResourceData) ([]string, []string) { return roles, shares } -func parseCallableObjectName(objectName string) (map[string]interface{}, error) { - r := regexp.MustCompile(`(?P[^(]+)\((?P[^)]*)\):(?P.*)`) - matches := r.FindStringSubmatch(objectName) - if len(matches) == 0 { - return nil, fmt.Errorf(`Could not parse objectName: %v`, objectName) +// parseFunctionObjectName parses a callable object name (including procedures) into its identifier components. For example, functions and procedures. +func parseFunctionObjectName(objectIdentifier string) (string, []string) { + nameIndex := strings.Index(objectIdentifier, `(`) + if nameIndex == -1 { + return "", []string{} } - callableSignatureMap := make(map[string]interface{}) - - argumentsSignatures := strings.Split(matches[2], ", ") - - arguments := []interface{}{} - argumentTypes := []string{} - argumentNames := []string{} - - for i, argumentSignature := range argumentsSignatures { - if argumentSignature != "" { - signatureComponents := strings.Split(argumentSignature, " ") - argumentNames = append(argumentNames, signatureComponents[0]) - argumentTypes = append(argumentTypes, signatureComponents[1]) - arguments = append(arguments, map[string]interface{}{ - "name": argumentNames[i], - "type": argumentTypes[i], - }) - } - } - - callableSignatureMap["callableName"] = matches[1] - callableSignatureMap["arguments"] = arguments - callableSignatureMap["argumentTypes"] = argumentTypes - callableSignatureMap["argumentNames"] = argumentNames - callableSignatureMap["returnType"] = matches[3] - - return callableSignatureMap, nil -} - -func formatCallableObjectName(callableName string, returnType string, arguments []interface{}) (string, []string, []string) { - argumentSignatures := make([]string, len(arguments)) - argumentNames := make([]string, len(arguments)) - argumentTypes := make([]string, len(arguments)) - - for i, arg := range arguments { - argMap := arg.(map[string]interface{}) - argumentNames[i] = strings.ToUpper(argMap["name"].(string)) - argumentTypes[i] = strings.ToUpper(argMap["type"].(string)) - argumentSignatures[i] = fmt.Sprintf(`%v %v`, argumentNames[i], argumentTypes[i]) + name := objectIdentifier[:nameIndex] + argumentString := objectIdentifier[nameIndex+1 : len(objectIdentifier)-1] + arguments := strings.Split(argumentString, `,`) + for i, argument := range arguments { + arguments[i] = strings.TrimSpace(argument) } - - return fmt.Sprintf(`%v(%v):%v`, callableName, strings.Join(argumentSignatures, ", "), returnType), argumentNames, argumentTypes + return name, arguments } // changeDiff calculates roles/shares to add/revoke. diff --git a/pkg/resources/procedure_grant.go b/pkg/resources/procedure_grant.go index 37e581747b..e94fb391bc 100644 --- a/pkg/resources/procedure_grant.go +++ b/pkg/resources/procedure_grant.go @@ -2,6 +2,7 @@ package resources import ( "errors" + "fmt" "strings" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/snowflake" @@ -34,6 +35,14 @@ var procedureGrantSchema = map[string]*schema.Schema{ Optional: true, Description: "List of the arguments for the procedure (must be present if procedure has arguments and procedure_name is present)", ForceNew: true, + Deprecated: "use argument_data_types instead.", + }, + "argument_data_types": { + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + Description: "List of the argument data types for the procedure (must be present if procedure has arguments and procedure_name is present)", + ForceNew: true, }, "database_name": { Type: schema.TypeString, @@ -74,6 +83,7 @@ var procedureGrantSchema = map[string]*schema.Schema{ Optional: true, Description: "The return type of the procedure (must be present if procedure_name is present)", ForceNew: true, + Deprecated: "return_type is no longer required. It will be removed in a future release.", }, "roles": { Type: schema.TypeSet, @@ -122,27 +132,29 @@ func ProcedureGrant() *TerraformGrantResource { // CreateProcedureGrant implements schema.CreateFunc. func CreateProcedureGrant(d *schema.ResourceData, meta interface{}) error { - var ( - procedureName string - arguments []interface{} - returnType string - procedureSignature string - argumentTypes []string - ) + var procedureName string if name, ok := d.GetOk("procedure_name"); ok { procedureName = name.(string) - if ret, ok := d.GetOk("return_type"); ok { - returnType = strings.ToUpper(ret.(string)) - } else { - return errors.New("return_type must be set when specifying procedure_name") - } } dbName := d.Get("database_name").(string) schemaName := d.Get("schema_name").(string) priv := d.Get("privilege").(string) onFuture := d.Get("on_future").(bool) grantOption := d.Get("with_grant_option").(bool) - arguments = d.Get("arguments").([]interface{}) + + argumentDataTypes := make([]string, 0) + + if v, ok := d.GetOk("arguments"); ok { + arguments := v.([]interface{}) + for _, argument := range arguments { + argumentDataTypes = append(argumentDataTypes, argument.(map[string]interface{})["data_type"].(string)) + } + } + + if v, ok := d.GetOk("argument_data_types"); ok { + argumentDataTypes = expandStringList(v.([]interface{})) + } + roles := expandStringList(d.Get("roles").(*schema.Set).List()) if (procedureName == "") && !onFuture { @@ -155,55 +167,47 @@ func CreateProcedureGrant(d *schema.ResourceData, meta interface{}) error { return errors.New("schema_name must be set unless on_future is true") } - if procedureName != "" { - procedureSignature, _, argumentTypes = formatCallableObjectName(procedureName, returnType, arguments) - } else { - argumentTypes = make([]string, 0) - } - var builder snowflake.GrantBuilder if onFuture { builder = snowflake.FutureProcedureGrant(dbName, schemaName) } else { - builder = snowflake.ProcedureGrant(dbName, schemaName, procedureName, argumentTypes) + builder = snowflake.ProcedureGrant(dbName, schemaName, procedureName, argumentDataTypes) } if err := createGenericGrant(d, meta, builder); err != nil { return err } + // If this is a on_futures grant then the procedure name and arguments do not get set. This is only used for refresh purposes. + var procedureObjectName string + if !onFuture { + procedureObjectName = fmt.Sprintf("%s(%s)", procedureName, strings.Join(argumentDataTypes, ", ")) + } grant := &grantID{ ResourceName: dbName, SchemaName: schemaName, - ObjectName: procedureSignature, + ObjectName: procedureObjectName, Privilege: priv, GrantOption: grantOption, Roles: roles, } - dataIDInput, err := grant.String() + grantID, err := grant.String() if err != nil { return err } - d.SetId(dataIDInput) - + d.SetId(grantID) return ReadProcedureGrant(d, meta) } // ReadProcedureGrant implements schema.ReadFunc. func ReadProcedureGrant(d *schema.ResourceData, meta interface{}) error { - var ( - procedureName string - returnType string - arguments []interface{} - argumentTypes []string - ) grantID, err := grantIDFromString(d.Id()) if err != nil { return err } dbName := grantID.ResourceName schemaName := grantID.SchemaName - procedureSignature := grantID.ObjectName + procedureObjectName := grantID.ObjectName priv := grantID.Privilege if err := d.Set("database_name", dbName); err != nil { @@ -214,28 +218,19 @@ func ReadProcedureGrant(d *schema.ResourceData, meta interface{}) error { return err } onFuture := false - if procedureSignature == "" { + var procedureName string + argumentDataTypes := make([]string, 0) + if procedureObjectName == "" { onFuture = true } else { - procedureSignatureMap, err := parseCallableObjectName(procedureSignature) - if err != nil { - return err - } - procedureName = procedureSignatureMap["callableName"].(string) - returnType = procedureSignatureMap["returnType"].(string) - arguments = procedureSignatureMap["arguments"].([]interface{}) - argumentTypes = procedureSignatureMap["argumentTypes"].([]string) + procedureName, argumentDataTypes = parseFunctionObjectName(procedureObjectName) } if err := d.Set("procedure_name", procedureName); err != nil { return err } - if err := d.Set("arguments", arguments); err != nil { - return err - } - - if err := d.Set("return_type", returnType); err != nil { + if err := d.Set("argument_data_types", argumentDataTypes); err != nil { return err } @@ -255,7 +250,7 @@ func ReadProcedureGrant(d *schema.ResourceData, meta interface{}) error { if onFuture { builder = snowflake.FutureProcedureGrant(dbName, schemaName) } else { - builder = snowflake.ProcedureGrant(dbName, schemaName, procedureName, argumentTypes) + builder = snowflake.ProcedureGrant(dbName, schemaName, procedureName, argumentDataTypes) } return readGenericGrant(d, meta, procedureGrantSchema, builder, onFuture, validProcedurePrivileges) @@ -270,19 +265,15 @@ func DeleteProcedureGrant(d *schema.ResourceData, meta interface{}) error { dbName := grantID.ResourceName schemaName := grantID.SchemaName - onFuture := (grantID.ObjectName == "") + procedureObjectName := grantID.ObjectName + onFuture := (procedureObjectName == "") var builder snowflake.GrantBuilder if onFuture { builder = snowflake.FutureProcedureGrant(dbName, schemaName) } else { - procedureSignatureMap, err := parseCallableObjectName(grantID.ObjectName) - if err != nil { - return err - } - procedureName := procedureSignatureMap["callableName"].(string) - argumentTypes := procedureSignatureMap["argumentTypes"].([]string) - builder = snowflake.ProcedureGrant(dbName, schemaName, procedureName, argumentTypes) + procedureName, argumentDataTypes := parseFunctionObjectName(procedureObjectName) + builder = snowflake.ProcedureGrant(dbName, schemaName, procedureName, argumentDataTypes) } return deleteGenericGrant(d, meta, builder) } @@ -312,21 +303,16 @@ func UpdateProcedureGrant(d *schema.ResourceData, meta interface{}) error { dbName := grantID.ResourceName schemaName := grantID.SchemaName - procedureName := grantID.ObjectName - onFuture := (procedureName == "") + procedureObjectName := grantID.ObjectName + onFuture := (procedureObjectName == "") // create the builder var builder snowflake.GrantBuilder if onFuture { builder = snowflake.FutureProcedureGrant(dbName, schemaName) } else { - procedureSignatureMap, err := parseCallableObjectName(grantID.ObjectName) - if err != nil { - return err - } - procedureName := procedureSignatureMap["callableName"].(string) - argumentTypes := procedureSignatureMap["argumentTypes"].([]string) - builder = snowflake.ProcedureGrant(dbName, schemaName, procedureName, argumentTypes) + procedureName, argumentDataTypes := parseFunctionObjectName(procedureObjectName) + builder = snowflake.ProcedureGrant(dbName, schemaName, procedureName, argumentDataTypes) } // first revoke diff --git a/pkg/resources/procedure_grant_acceptance_test.go b/pkg/resources/procedure_grant_acceptance_test.go index 7c1385f6a7..42fe40ac2c 100644 --- a/pkg/resources/procedure_grant_acceptance_test.go +++ b/pkg/resources/procedure_grant_acceptance_test.go @@ -26,7 +26,6 @@ func TestAcc_ProcedureFutureGrant(t *testing.T) { resource.TestCheckResourceAttr("snowflake_procedure_grant.test", "database_name", databaseName), resource.TestCheckResourceAttr("snowflake_procedure_grant.test", "schema_name", schemaName), resource.TestCheckResourceAttr("snowflake_procedure_grant.test", "procedure_name", ""), - resource.TestCheckResourceAttr("snowflake_procedure_grant.test", "return_type", ""), resource.TestCheckResourceAttr("snowflake_procedure_grant.test", "with_grant_option", "false"), resource.TestCheckResourceAttr("snowflake_procedure_grant.test", "on_future", "true"), resource.TestCheckResourceAttr("snowflake_procedure_grant.test", "privilege", "USAGE"), diff --git a/pkg/resources/procedure_grant_test.go b/pkg/resources/procedure_grant_test.go deleted file mode 100644 index 5b71961d48..0000000000 --- a/pkg/resources/procedure_grant_test.go +++ /dev/null @@ -1,182 +0,0 @@ -package resources_test - -import ( - "database/sql" - "testing" - "time" - - sqlmock "github.com/DATA-DOG/go-sqlmock" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/provider" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/resources" - . "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/testhelpers" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/stretchr/testify/require" -) - -func TestProcedureGrant(t *testing.T) { - r := require.New(t) - err := resources.ProcedureGrant().Resource.InternalValidate(provider.Provider().Schema, true) - r.NoError(err) -} - -func TestProcedureGrantCreate(t *testing.T) { - r := require.New(t) - - in := map[string]interface{}{ - "procedure_name": "test-procedure", - "arguments": []interface{}{map[string]interface{}{ - "name": "a", - "type": "array", - }, map[string]interface{}{ - "name": "b", - "type": "string", - }}, - "return_type": "string", - "schema_name": "PUBLIC", - "database_name": "test-db", - "privilege": "USAGE", - "roles": []interface{}{"test-role-1", "test-role-2"}, - "shares": []interface{}{"test-share-1", "test-share-2"}, - "with_grant_option": true, - } - d := schema.TestResourceDataRaw(t, resources.ProcedureGrant().Resource.Schema, in) - r.NotNil(d) - - WithMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) { - mock.ExpectExec(`^GRANT USAGE ON PROCEDURE "test-db"."PUBLIC"."test-procedure"\(ARRAY, STRING\) TO ROLE "test-role-1" WITH GRANT OPTION$`).WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec(`^GRANT USAGE ON PROCEDURE "test-db"."PUBLIC"."test-procedure"\(ARRAY, STRING\) TO ROLE "test-role-2" WITH GRANT OPTION$`).WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec(`^GRANT USAGE ON PROCEDURE "test-db"."PUBLIC"."test-procedure"\(ARRAY, STRING\) TO SHARE "test-share-1" WITH GRANT OPTION$`).WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec(`^GRANT USAGE ON PROCEDURE "test-db"."PUBLIC"."test-procedure"\(ARRAY, STRING\) TO SHARE "test-share-2" WITH GRANT OPTION$`).WillReturnResult(sqlmock.NewResult(1, 1)) - expectReadProcedureGrant(mock) - err := resources.CreateProcedureGrant(d, db) - r.NoError(err) - }) -} - -func TestProcedureGrantRead(t *testing.T) { - r := require.New(t) - - d := procedureGrant(t, "test-db|PUBLIC|test-procedure(A ARRAY, B STRING):STRING|USAGE||false", map[string]interface{}{ - "procedure_name": "test-procedure", - "arguments": []interface{}{map[string]interface{}{ - "name": "a", - "type": "array", - }, map[string]interface{}{ - "name": "b", - "type": "string", - }}, - "return_type": "string", - "schema_name": "PUBLIC", - "database_name": "test-db", - "privilege": "USAGE", - "roles": []interface{}{}, - "shares": []interface{}{}, - "with_grant_option": false, - }) - - r.NotNil(d) - - WithMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) { - expectReadProcedureGrant(mock) - err := resources.ReadProcedureGrant(d, db) - r.NoError(err) - }) - - roles := d.Get("roles").(*schema.Set) - r.True(roles.Contains("test-role-1")) - r.True(roles.Contains("test-role-2")) - r.Equal(2, roles.Len()) - - shares := d.Get("shares").(*schema.Set) - r.True(shares.Contains("test-share-1")) - r.True(shares.Contains("test-share-2")) - r.Equal(2, shares.Len()) -} - -func expectReadProcedureGrant(mock sqlmock.Sqlmock) { - rows := sqlmock.NewRows([]string{ - "created_on", "privilege", "granted_on", "name", "granted_to", "grantee_name", "grant_option", "granted_by", - }).AddRow( - time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), "USAGE", "PROCEDURE", "test-db.test-schema.\"test-procedure(A ARRAY, B STRING):STRING\"", "ROLE", "test-role-1", false, "bob", - ).AddRow( - time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), "USAGE", "PROCEDURE", "test-db.test-schema.\"test-procedure(A ARRAY, B STRING):STRING\"", "ROLE", "test-role-2", false, "bob", - ).AddRow( - time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), "USAGE", "PROCEDURE", "test-db.test-schema.\"test-procedure(A ARRAY, B STRING):STRING\"", "SHARE", "test-share-1", false, "bob", - ).AddRow( - time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), "USAGE", "PROCEDURE", "test-db.test-schema.\"test-procedure(A ARRAY, B STRING):STRING\"", "SHARE", "test-share-2", false, "bob", - ) - mock.ExpectQuery(`^SHOW GRANTS ON PROCEDURE "test-db"."PUBLIC"."test-procedure"\(ARRAY, STRING\)$`).WillReturnRows(rows) -} - -func TestFutureProcedureGrantCreate(t *testing.T) { - r := require.New(t) - - in := map[string]interface{}{ - "on_future": true, - "schema_name": "PUBLIC", - "database_name": "test-db", - "privilege": "USAGE", - "roles": []interface{}{"test-role-1", "test-role-2"}, - "with_grant_option": true, - } - d := schema.TestResourceDataRaw(t, resources.ProcedureGrant().Resource.Schema, in) - r.NotNil(d) - - WithMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) { - mock.ExpectExec( - `^GRANT USAGE ON FUTURE PROCEDURES IN SCHEMA "test-db"."PUBLIC" TO ROLE "test-role-1" WITH GRANT OPTION$`, - ).WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec( - `^GRANT USAGE ON FUTURE PROCEDURES IN SCHEMA "test-db"."PUBLIC" TO ROLE "test-role-2" WITH GRANT OPTION$`, - ).WillReturnResult(sqlmock.NewResult(1, 1)) - expectReadFutureProcedureGrant(mock) - err := resources.CreateProcedureGrant(d, db) - r.NoError(err) - }) - - b := require.New(t) - - in = map[string]interface{}{ - "on_future": true, - "database_name": "test-db", - "privilege": "USAGE", - "roles": []interface{}{"test-role-1", "test-role-2"}, - "with_grant_option": false, - } - d = schema.TestResourceDataRaw(t, resources.ProcedureGrant().Resource.Schema, in) - b.NotNil(d) - - WithMockDb(t, func(db *sql.DB, mock sqlmock.Sqlmock) { - mock.ExpectExec( - `^GRANT USAGE ON FUTURE PROCEDURES IN DATABASE "test-db" TO ROLE "test-role-1"$`, - ).WillReturnResult(sqlmock.NewResult(1, 1)) - mock.ExpectExec( - `^GRANT USAGE ON FUTURE PROCEDURES IN DATABASE "test-db" TO ROLE "test-role-2"$`, - ).WillReturnResult(sqlmock.NewResult(1, 1)) - expectReadFutureProcedureDatabaseGrant(mock) - err := resources.CreateProcedureGrant(d, db) - b.NoError(err) - }) -} - -func expectReadFutureProcedureGrant(mock sqlmock.Sqlmock) { - rows := sqlmock.NewRows([]string{ - "created_on", "privilege", "grant_on", "name", "grant_to", "grantee_name", "grant_option", - }).AddRow( - time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), "USAGE", "PROCEDURE", "test-db.PUBLIC.", "ROLE", "test-role-1", false, - ).AddRow( - time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), "USAGE", "PROCEDURE", "test-db.PUBLIC.", "ROLE", "test-role-2", false, - ) - mock.ExpectQuery(`^SHOW FUTURE GRANTS IN SCHEMA "test-db"."PUBLIC"$`).WillReturnRows(rows) -} - -func expectReadFutureProcedureDatabaseGrant(mock sqlmock.Sqlmock) { - rows := sqlmock.NewRows([]string{ - "created_on", "privilege", "grant_on", "name", "grant_to", "grantee_name", "grant_option", - }).AddRow( - time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), "USAGE", "PROCEDURE", "test-db.", "ROLE", "test-role-1", false, - ).AddRow( - time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), "USAGE", "PROCEDURE", "test-db.", "ROLE", "test-role-2", false, - ) - mock.ExpectQuery(`^SHOW FUTURE GRANTS IN DATABASE "test-db"$`).WillReturnRows(rows) -}