Repository: chanzuckerberg/terraform-provider-snowflake
Pull request #824 (2022-01-19 15:07:35)

fix: Allow multiple resources of the same object grant
- Filtering grants on read to only return those managed by the resource.
- Updated grantID to contain a list of all the roles in the current grant resource.

These changes allows grants to multiple roles to be done in different resources.

**This change comes with the caveat that grants applied OUTSIDE Terraform will not be revoked. This is because the resource won't know if it's maintained by another resource or created elsewhere**

Previously the resource id had the structure:
resourceName|schemaName|ObjectName|Privilege|GrantOption
For example: DWH|TEST||USAGE|FALSE

New structure:
resourceName|schemaName|ObjectName|Privilege|Roles|GrantOption

```hcl
# id = DWH|TEST||USAGE|ROLE_A,ROLE_B|FALSE
resource "snowflake_schema_grant" "grant_a_b" {
  database_name = "DWH"
  schema_name   = "TEST"

  privilege = "USAGE"
  roles     = ["ROLE_A", "ROLE_B"]
  with_grant_option = false
}

# id = DWH|TEST||USAGE|ROLE_C|FALSE
resource "snowflake_schema_grant" "grant_c" {
  database_name = "DWH"
  schema_name   = "TEST"

  privilege = "USAGE"
  roles     = ["ROLE_C"]
  with_grant_option = false
}
```

<!-- summary of changes -->

## Test Plan
<!-- detail ways in which this PR has been tested or needs to be tested -->
* [ ] acceptance tests
* [X] unit tests
<!-- add more below if you think they are relevant -->

## References
<!-- issues documentation links, etc  -->

* https://github.com/chanzuckerberg/terraform-provider-snowflake/issues/210
* https://github.com/chanzuckerberg/terraform-provider-snowflake/issues/560
* https://github.com/chanzuckerberg/terraform-provider-snowflake/issues/733

daniepett: @alldoami Any idea of if this will be merged in? If not we'll have to plan workarounds for our RBAC solution.
alldoami: /ok-to-test sha=7e77feb
github-actions[bot]: Integration tests success for [7e77feb](https://github.com/chanzuckerberg/terraform-provider-snowflake/actions/runs/1785611907)

Patch:
diff --git a/pkg/resources/account_grant.go b/pkg/resources/account_grant.go
index 78db4e1b..a7d5aa42 100644
--- a/pkg/resources/account_grant.go
+++ b/pkg/resources/account_grant.go
@@ -74,6 +74,7 @@ func AccountGrant() *TerraformGrantResource {
 func CreateAccountGrant(d *schema.ResourceData, meta interface{}) error {
 	priv := d.Get("privilege").(string)
 	grantOption := d.Get("with_grant_option").(bool)
+	roles := expandStringList(d.Get("roles").(*schema.Set).List())
 
 	builder := snowflake.AccountGrant()
 
@@ -86,6 +87,7 @@ func CreateAccountGrant(d *schema.ResourceData, meta interface{}) error {
 		ResourceName: "ACCOUNT",
 		Privilege:    priv,
 		GrantOption:  grantOption,
+		Roles:        roles,
 	}
 	dataIDInput, err := grantID.String()
 	if err != nil {
diff --git a/pkg/resources/database_grant.go b/pkg/resources/database_grant.go
index 1223b047..309765ac 100644
--- a/pkg/resources/database_grant.go
+++ b/pkg/resources/database_grant.go
@@ -36,6 +36,7 @@ var databaseGrantSchema = map[string]*schema.Schema{
 		Type:        schema.TypeSet,
 		Elem:        &schema.Schema{Type: schema.TypeString},
 		Optional:    true,
+		ForceNew:    true,
 		Description: "Grants privilege to these roles.",
 	},
 	"shares": {
@@ -77,6 +78,7 @@ func CreateDatabaseGrant(d *schema.ResourceData, meta interface{}) error {
 	builder := snowflake.DatabaseGrant(dbName)
 	priv := d.Get("privilege").(string)
 	grantOption := d.Get("with_grant_option").(bool)
+	roles := expandStringList(d.Get("roles").(*schema.Set).List())
 
 	err := createGenericGrant(d, meta, builder)
 	if err != nil {
@@ -87,6 +89,7 @@ func CreateDatabaseGrant(d *schema.ResourceData, meta interface{}) error {
 		ResourceName: dbName,
 		Privilege:    priv,
 		GrantOption:  grantOption,
+		Roles:        roles,
 	}
 	dataIDInput, err := grant.String()
 	if err != nil {
diff --git a/pkg/resources/external_table_grant.go b/pkg/resources/external_table_grant.go
index 11c1de5a..b9a692fc 100644
--- a/pkg/resources/external_table_grant.go
+++ b/pkg/resources/external_table_grant.go
@@ -98,6 +98,7 @@ func CreateExternalTableGrant(d *schema.ResourceData, meta interface{}) error {
 	priv := d.Get("privilege").(string)
 	futureExternalTables := d.Get("on_future").(bool)
 	grantOption := d.Get("with_grant_option").(bool)
+	roles := expandStringList(d.Get("roles").(*schema.Set).List())
 
 	if (externalTableName == "") && !futureExternalTables {
 		return errors.New("external_table_name must be set unless on_future is true.")
@@ -124,6 +125,7 @@ func CreateExternalTableGrant(d *schema.ResourceData, meta interface{}) error {
 		ObjectName:   externalTableName,
 		Privilege:    priv,
 		GrantOption:  grantOption,
+		Roles:        roles,
 	}
 	dataIDInput, err := grant.String()
 	if err != nil {
diff --git a/pkg/resources/file_format_grant.go b/pkg/resources/file_format_grant.go
index c16317f5..a17f40cc 100644
--- a/pkg/resources/file_format_grant.go
+++ b/pkg/resources/file_format_grant.go
@@ -90,6 +90,7 @@ func CreateFileFormatGrant(d *schema.ResourceData, meta interface{}) error {
 	priv := d.Get("privilege").(string)
 	futureFileFormats := d.Get("on_future").(bool)
 	grantOption := d.Get("with_grant_option").(bool)
+	roles := expandStringList(d.Get("roles").(*schema.Set).List())
 
 	if (fileFormatName == "") && !futureFileFormats {
 		return errors.New("file_format_name must be set unless on_future is true.")
@@ -116,6 +117,7 @@ func CreateFileFormatGrant(d *schema.ResourceData, meta interface{}) error {
 		ObjectName:   fileFormatName,
 		Privilege:    priv,
 		GrantOption:  grantOption,
+		Roles:        roles,
 	}
 	dataIDInput, err := grant.String()
 	if err != nil {
diff --git a/pkg/resources/function_grant.go b/pkg/resources/function_grant.go
index 3e80ec0e..d26d6154 100644
--- a/pkg/resources/function_grant.go
+++ b/pkg/resources/function_grant.go
@@ -137,6 +137,7 @@ func CreateFunctionGrant(d *schema.ResourceData, meta interface{}) error {
 	futureFunctions := d.Get("on_future").(bool)
 	grantOption := d.Get("with_grant_option").(bool)
 	arguments = d.Get("arguments").([]interface{})
+	roles := expandStringList(d.Get("roles").(*schema.Set).List())
 
 	if (functionName == "") && !futureFunctions {
 		return errors.New("function_name must be set unless on_future is true.")
@@ -169,6 +170,7 @@ func CreateFunctionGrant(d *schema.ResourceData, meta interface{}) error {
 		ObjectName:   functionSignature,
 		Privilege:    priv,
 		GrantOption:  grantOption,
+		Roles:        roles,
 	}
 	dataIDInput, err := grant.String()
 	if err != nil {
diff --git a/pkg/resources/grant_helpers.go b/pkg/resources/grant_helpers.go
index 83d10562..cf670198 100644
--- a/pkg/resources/grant_helpers.go
+++ b/pkg/resources/grant_helpers.go
@@ -81,17 +81,19 @@ type grantID struct {
 	SchemaName   string
 	ObjectName   string
 	Privilege    string
+	Roles        []string
 	GrantOption  bool
 }
 
 // String() takes in a grantID object and returns a pipe-delimited string:
-// resourceName|schemaName|ObjectName|Privilege|GrantOption
+// resourceName|schemaName|ObjectName|Privilege|Roles|GrantOption
 func (gi *grantID) String() (string, error) {
 	var buf bytes.Buffer
 	csvWriter := csv.NewWriter(&buf)
 	csvWriter.Comma = grantIDDelimiter
 	grantOption := fmt.Sprintf("%v", gi.GrantOption)
-	dataIdentifiers := [][]string{{gi.ResourceName, gi.SchemaName, gi.ObjectName, gi.Privilege, grantOption}}
+	roles := strings.Join(gi.Roles, ",")
+	dataIdentifiers := [][]string{{gi.ResourceName, gi.SchemaName, gi.ObjectName, gi.Privilege, roles, grantOption}}
 	err := csvWriter.WriteAll(dataIdentifiers)
 	if err != nil {
 		return "", err
@@ -100,7 +102,7 @@ func (gi *grantID) String() (string, error) {
 	return strGrantID, nil
 }
 
-// grantIDFromString() takes in a pipe-delimited string: resourceName|schemaName|ObjectName|Privilege
+// grantIDFromString() takes in a pipe-delimited string: resourceName|schemaName|ObjectName|Privilege|Roles
 // and returns a grantID object
 func grantIDFromString(stringID string) (*grantID, error) {
 	reader := csv.NewReader(strings.NewReader(stringID))
@@ -113,12 +115,12 @@ func grantIDFromString(stringID string) (*grantID, error) {
 	if len(lines) != 1 {
 		return nil, fmt.Errorf("1 line per grant")
 	}
-	if len(lines[0]) != 4 && len(lines[0]) != 5 {
-		return nil, fmt.Errorf("4 or 5 fields allowed")
+	if len(lines[0]) != 5 && len(lines[0]) != 6 {
+		return nil, fmt.Errorf("5 or 6 fields allowed")
 	}
 
 	grantOption := false
-	if len(lines[0]) == 5 && lines[0][4] == "true" {
+	if len(lines[0]) == 6 && lines[0][5] == "true" {
 		grantOption = true
 	}
 
@@ -127,6 +129,7 @@ func grantIDFromString(stringID string) (*grantID, error) {
 		SchemaName:   lines[0][1],
 		ObjectName:   lines[0][2],
 		Privilege:    lines[0][3],
+		Roles:        strings.Split(lines[0][4], ","),
 		GrantOption:  grantOption,
 	}
 	return grantResult, nil
@@ -176,7 +179,7 @@ func createGenericGrant(d *schema.ResourceData, meta interface{}, builder snowfl
 func readGenericGrant(
 	d *schema.ResourceData,
 	meta interface{},
-	schema map[string]*schema.Schema,
+	grantSchema map[string]*schema.Schema,
 	builder snowflake.GrantBuilder,
 	futureObjects bool,
 	validPrivileges PrivilegeSet) error {
@@ -243,11 +246,13 @@ func readGenericGrant(
 		}
 	}
 
+	existingRoles := d.Get("roles").(*schema.Set)
 	var roles, shares []string
 	// Now see which roles have our privilege
 	for roleName, privileges := range rolePrivileges {
 		// Where priv is not all so it should match exactly
-		if privileges.hasString(priv) {
+		// Match to currently assigned roles or let everything through if no specific role grants
+		if privileges.hasString(priv) && (existingRoles.Contains(roleName) || existingRoles.Len() == 0) {
 			roles = append(roles, roleName)
 		}
 	}
@@ -269,7 +274,7 @@ func readGenericGrant(
 		return err
 	}
 
-	_, sharesOk := schema["shares"]
+	_, sharesOk := grantSchema["shares"]
 	if sharesOk && !futureObjects {
 		err = d.Set("shares", shares)
 		if err != nil {
diff --git a/pkg/resources/integration_grant.go b/pkg/resources/integration_grant.go
index de6bc25e..a81ce973 100644
--- a/pkg/resources/integration_grant.go
+++ b/pkg/resources/integration_grant.go
@@ -63,6 +63,8 @@ func CreateIntegrationGrant(d *schema.ResourceData, meta interface{}) error {
 	w := d.Get("integration_name").(string)
 	priv := d.Get("privilege").(string)
 	grantOption := d.Get("with_grant_option").(bool)
+	roles := expandStringList(d.Get("roles").(*schema.Set).List())
+
 	builder := snowflake.IntegrationGrant(w)
 
 	err := createGenericGrant(d, meta, builder)
@@ -74,6 +76,7 @@ func CreateIntegrationGrant(d *schema.ResourceData, meta interface{}) error {
 		ResourceName: w,
 		Privilege:    priv,
 		GrantOption:  grantOption,
+		Roles:        roles,
 	}
 	dataIDInput, err := grant.String()
 	if err != nil {
diff --git a/pkg/resources/masking_policy_grant.go b/pkg/resources/masking_policy_grant.go
index 592fecb7..eea28097 100644
--- a/pkg/resources/masking_policy_grant.go
+++ b/pkg/resources/masking_policy_grant.go
@@ -81,6 +81,7 @@ func CreateMaskingPolicyGrant(d *schema.ResourceData, meta interface{}) error {
 	schemaName := d.Get("schema_name").(string)
 	priv := d.Get("privilege").(string)
 	grantOption := d.Get("with_grant_option").(bool)
+	roles := expandStringList(d.Get("roles").(*schema.Set).List())
 
 	builder := snowflake.MaskingPolicyGrant(dbName, schemaName, maskingPolicyName)
 
@@ -95,6 +96,7 @@ func CreateMaskingPolicyGrant(d *schema.ResourceData, meta interface{}) error {
 		ObjectName:   maskingPolicyName,
 		Privilege:    priv,
 		GrantOption:  grantOption,
+		Roles:        roles,
 	}
 	dataIDInput, err := grant.String()
 	if err != nil {
diff --git a/pkg/resources/materialized_view_grant.go b/pkg/resources/materialized_view_grant.go
index 3ea2e822..7376bc41 100644
--- a/pkg/resources/materialized_view_grant.go
+++ b/pkg/resources/materialized_view_grant.go
@@ -104,6 +104,7 @@ func CreateMaterializedViewGrant(d *schema.ResourceData, meta interface{}) error
 	priv := d.Get("privilege").(string)
 	futureMaterializedViews := d.Get("on_future").(bool)
 	grantOption := d.Get("with_grant_option").(bool)
+	roles := expandStringList(d.Get("roles").(*schema.Set).List())
 
 	if (schemaName == "") && !futureMaterializedViews {
 		return errors.New("schema_name must be set unless on_future is true.")
@@ -134,6 +135,7 @@ func CreateMaterializedViewGrant(d *schema.ResourceData, meta interface{}) error
 		ObjectName:   materializedViewName,
 		Privilege:    priv,
 		GrantOption:  grantOption,
+		Roles:        roles,
 	}
 	dataIDInput, err := grant.String()
 	if err != nil {
diff --git a/pkg/resources/pipe_grant.go b/pkg/resources/pipe_grant.go
index 86f4a196..a5cb7074 100644
--- a/pkg/resources/pipe_grant.go
+++ b/pkg/resources/pipe_grant.go
@@ -91,6 +91,7 @@ func CreatePipeGrant(d *schema.ResourceData, meta interface{}) error {
 	priv := d.Get("privilege").(string)
 	futurePipes := d.Get("on_future").(bool)
 	grantOption := d.Get("with_grant_option").(bool)
+	roles := expandStringList(d.Get("roles").(*schema.Set).List())
 
 	if (pipeName == "") && !futurePipes {
 		return errors.New("pipe_name must be set unless on_future is true.")
@@ -117,6 +118,7 @@ func CreatePipeGrant(d *schema.ResourceData, meta interface{}) error {
 		ObjectName:   pipeName,
 		Privilege:    priv,
 		GrantOption:  grantOption,
+		Roles:        roles,
 	}
 	dataIDInput, err := grant.String()
 	if err != nil {
diff --git a/pkg/resources/procedure_grant.go b/pkg/resources/procedure_grant.go
index 0d854e8a..15c236ff 100644
--- a/pkg/resources/procedure_grant.go
+++ b/pkg/resources/procedure_grant.go
@@ -137,6 +137,7 @@ func CreateProcedureGrant(d *schema.ResourceData, meta interface{}) error {
 	futureProcedures := d.Get("on_future").(bool)
 	grantOption := d.Get("with_grant_option").(bool)
 	arguments = d.Get("arguments").([]interface{})
+	roles := expandStringList(d.Get("roles").(*schema.Set).List())
 
 	if (procedureName == "") && !futureProcedures {
 		return errors.New("procedure_name must be set unless on_future is true.")
@@ -169,6 +170,7 @@ func CreateProcedureGrant(d *schema.ResourceData, meta interface{}) error {
 		ObjectName:   procedureSignature,
 		Privilege:    priv,
 		GrantOption:  grantOption,
+		Roles:        roles,
 	}
 	dataIDInput, err := grant.String()
 	if err != nil {
diff --git a/pkg/resources/resource_monitor_grant.go b/pkg/resources/resource_monitor_grant.go
index d87a6fe6..f832ac26 100644
--- a/pkg/resources/resource_monitor_grant.go
+++ b/pkg/resources/resource_monitor_grant.go
@@ -62,6 +62,7 @@ func CreateResourceMonitorGrant(d *schema.ResourceData, meta interface{}) error
 	priv := d.Get("privilege").(string)
 	grantOption := d.Get("with_grant_option").(bool)
 	builder := snowflake.ResourceMonitorGrant(w)
+	roles := expandStringList(d.Get("roles").(*schema.Set).List())
 
 	err := createGenericGrant(d, meta, builder)
 	if err != nil {
@@ -72,6 +73,7 @@ func CreateResourceMonitorGrant(d *schema.ResourceData, meta interface{}) error
 		ResourceName: w,
 		Privilege:    priv,
 		GrantOption:  grantOption,
+		Roles:        roles,
 	}
 	dataIDInput, err := grant.String()
 	if err != nil {
diff --git a/pkg/resources/role_grants.go b/pkg/resources/role_grants.go
index 37ec3905..1cdf05cf 100644
--- a/pkg/resources/role_grants.go
+++ b/pkg/resources/role_grants.go
@@ -3,11 +3,13 @@ package resources
 import (
 	"database/sql"
 	"fmt"
+	"log"
 	"strings"
 
 	"github.com/chanzuckerberg/terraform-provider-snowflake/pkg/snowflake"
 	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
 	"github.com/jmoiron/sqlx"
+	"github.com/pkg/errors"
 )
 
 func RoleGrants() *schema.Resource {
@@ -57,6 +59,16 @@ func CreateRoleGrants(d *schema.ResourceData, meta interface{}) error {
 		return fmt.Errorf("no users or roles specified for role grants")
 	}
 
+	grant := &grantID{
+		ResourceName: roleName,
+		Roles:        roles,
+	}
+	dataIDInput, err := grant.String()
+	d.SetId(dataIDInput)
+
+	if err != nil {
+		return errors.Wrap(err, "error creating role grant")
+	}
 	for _, role := range roles {
 		err := grantRoleToRole(db, roleName, role)
 		if err != nil {
@@ -70,7 +82,7 @@ func CreateRoleGrants(d *schema.ResourceData, meta interface{}) error {
 			return err
 		}
 	}
-	d.SetId(roleName)
+
 	return ReadRoleGrants(d, meta)
 }
 
@@ -96,7 +108,12 @@ type roleGrant struct {
 
 func ReadRoleGrants(d *schema.ResourceData, meta interface{}) error {
 	db := meta.(*sql.DB)
-	roleName := d.Id()
+	log.Println(d.Id())
+	grantID, err := grantIDFromString(d.Id())
+	if err != nil {
+		return err
+	}
+	roleName := grantID.ResourceName
 
 	tfRoles := expandStringList(d.Get("roles").(*schema.Set).List())
 	tfUsers := expandStringList(d.Get("users").(*schema.Set).List())
diff --git a/pkg/resources/row_access_policy_grant.go b/pkg/resources/row_access_policy_grant.go
index f708911c..9ad29955 100644
--- a/pkg/resources/row_access_policy_grant.go
+++ b/pkg/resources/row_access_policy_grant.go
@@ -81,6 +81,7 @@ func CreateRowAccessPolicyGrant(d *schema.ResourceData, meta interface{}) error
 	schemaName := d.Get("schema_name").(string)
 	priv := d.Get("privilege").(string)
 	grantOption := d.Get("with_grant_option").(bool)
+	roles := expandStringList(d.Get("roles").(*schema.Set).List())
 
 	builder := snowflake.RowAccessPolicyGrant(dbName, schemaName, rowAccessPolicyName)
 
@@ -95,6 +96,7 @@ func CreateRowAccessPolicyGrant(d *schema.ResourceData, meta interface{}) error
 		ObjectName:   rowAccessPolicyName,
 		Privilege:    priv,
 		GrantOption:  grantOption,
+		Roles:        roles,
 	}
 	dataIDInput, err := grant.String()
 	if err != nil {
diff --git a/pkg/resources/schema_grant.go b/pkg/resources/schema_grant.go
index aab71730..7e33df4e 100644
--- a/pkg/resources/schema_grant.go
+++ b/pkg/resources/schema_grant.go
@@ -101,18 +101,19 @@ func SchemaGrant() *TerraformGrantResource {
 
 // CreateSchemaGrant implements schema.CreateFunc
 func CreateSchemaGrant(d *schema.ResourceData, meta interface{}) error {
-	var schema string
+	var schemaName string
 	if _, ok := d.GetOk("schema_name"); ok {
-		schema = d.Get("schema_name").(string)
+		schemaName = d.Get("schema_name").(string)
 	} else {
-		schema = ""
+		schemaName = ""
 	}
 	db := d.Get("database_name").(string)
 	priv := d.Get("privilege").(string)
 	onFuture := d.Get("on_future").(bool)
 	grantOption := d.Get("with_grant_option").(bool)
+	roles := expandStringList(d.Get("roles").(*schema.Set).List())
 
-	if (schema == "") && !onFuture {
+	if (schemaName == "") && !onFuture {
 		return errors.New("schema_name must be set unless on_future is true.")
 	}
 
@@ -120,7 +121,7 @@ func CreateSchemaGrant(d *schema.ResourceData, meta interface{}) error {
 	if onFuture {
 		builder = snowflake.FutureSchemaGrant(db)
 	} else {
-		builder = snowflake.SchemaGrant(db, schema)
+		builder = snowflake.SchemaGrant(db, schemaName)
 	}
 
 	err := createGenericGrant(d, meta, builder)
@@ -130,9 +131,10 @@ func CreateSchemaGrant(d *schema.ResourceData, meta interface{}) error {
 
 	grantID := &grantID{
 		ResourceName: db,
-		SchemaName:   schema,
+		SchemaName:   schemaName,
 		Privilege:    priv,
 		GrantOption:  grantOption,
+		Roles:        roles,
 	}
 	dataIDInput, err := grantID.String()
 	if err != nil {
diff --git a/pkg/resources/sequence_grant.go b/pkg/resources/sequence_grant.go
index 8116d705..02e17cf4 100644
--- a/pkg/resources/sequence_grant.go
+++ b/pkg/resources/sequence_grant.go
@@ -90,6 +90,7 @@ func CreateSequenceGrant(d *schema.ResourceData, meta interface{}) error {
 	priv := d.Get("privilege").(string)
 	futureSequences := d.Get("on_future").(bool)
 	grantOption := d.Get("with_grant_option").(bool)
+	roles := expandStringList(d.Get("roles").(*schema.Set).List())
 
 	if (sequenceName == "") && !futureSequences {
 		return errors.New("sequence_name must be set unless on_future is true.")
@@ -116,6 +117,7 @@ func CreateSequenceGrant(d *schema.ResourceData, meta interface{}) error {
 		ObjectName:   sequenceName,
 		Privilege:    priv,
 		GrantOption:  grantOption,
+		Roles:        roles,
 	}
 	dataIDInput, err := grant.String()
 	if err != nil {
diff --git a/pkg/resources/stream_grant.go b/pkg/resources/stream_grant.go
index 8c16fbf3..c4e29cf4 100644
--- a/pkg/resources/stream_grant.go
+++ b/pkg/resources/stream_grant.go
@@ -90,6 +90,7 @@ func CreateStreamGrant(d *schema.ResourceData, meta interface{}) error {
 	priv := d.Get("privilege").(string)
 	futureStreams := d.Get("on_future").(bool)
 	grantOption := d.Get("with_grant_option").(bool)
+	roles := expandStringList(d.Get("roles").(*schema.Set).List())
 
 	if (streamName == "") && !futureStreams {
 		return errors.New("stream_name must be set unless on_future is true.")
@@ -116,6 +117,7 @@ func CreateStreamGrant(d *schema.ResourceData, meta interface{}) error {
 		ObjectName:   streamName,
 		Privilege:    priv,
 		GrantOption:  grantOption,
+		Roles:        roles,
 	}
 	dataIDInput, err := grant.String()
 	if err != nil {
diff --git a/pkg/resources/table_grant.go b/pkg/resources/table_grant.go
index ac659acf..0e131d41 100644
--- a/pkg/resources/table_grant.go
+++ b/pkg/resources/table_grant.go
@@ -112,6 +112,7 @@ func CreateTableGrant(d *schema.ResourceData, meta interface{}) error {
 	priv := d.Get("privilege").(string)
 	onFuture := d.Get("on_future").(bool)
 	grantOption := d.Get("with_grant_option").(bool)
+	roles := expandStringList(d.Get("roles").(*schema.Set).List())
 
 	if (schemaName == "") && !onFuture {
 		return errors.New("schema_name must be set unless on_future is true.")
@@ -139,6 +140,7 @@ func CreateTableGrant(d *schema.ResourceData, meta interface{}) error {
 		SchemaName:   schemaName,
 		Privilege:    priv,
 		GrantOption:  grantOption,
+		Roles:        roles,
 	}
 	if !onFuture {
 		grantID.ObjectName = tableName
diff --git a/pkg/resources/task_grant.go b/pkg/resources/task_grant.go
index b5c7380c..c0e9b34d 100644
--- a/pkg/resources/task_grant.go
+++ b/pkg/resources/task_grant.go
@@ -91,6 +91,7 @@ func CreateTaskGrant(d *schema.ResourceData, meta interface{}) error {
 	priv := d.Get("privilege").(string)
 	futureTasks := d.Get("on_future").(bool)
 	grantOption := d.Get("with_grant_option").(bool)
+	roles := expandStringList(d.Get("roles").(*schema.Set).List())
 
 	if (taskName == "") && !futureTasks {
 		return errors.New("task_name must be set unless on_future is true.")
@@ -117,6 +118,7 @@ func CreateTaskGrant(d *schema.ResourceData, meta interface{}) error {
 		ObjectName:   taskName,
 		Privilege:    priv,
 		GrantOption:  grantOption,
+		Roles:        roles,
 	}
 	dataIDInput, err := grant.String()
 	if err != nil {
diff --git a/pkg/resources/view_grant.go b/pkg/resources/view_grant.go
index c0b0b5c3..03e6ce5f 100644
--- a/pkg/resources/view_grant.go
+++ b/pkg/resources/view_grant.go
@@ -107,6 +107,7 @@ func CreateViewGrant(d *schema.ResourceData, meta interface{}) error {
 	priv := d.Get("privilege").(string)
 	futureViews := d.Get("on_future").(bool)
 	grantOption := d.Get("with_grant_option").(bool)
+	roles := expandStringList(d.Get("roles").(*schema.Set).List())
 
 	if (schemaName == "") && !futureViews {
 		return errors.New("schema_name must be set unless on_future is true.")
@@ -137,6 +138,7 @@ func CreateViewGrant(d *schema.ResourceData, meta interface{}) error {
 		ObjectName:   viewName,
 		Privilege:    priv,
 		GrantOption:  grantOption,
+		Roles:        roles,
 	}
 	dataIDInput, err := grant.String()
 	if err != nil {
diff --git a/pkg/resources/warehouse_grant.go b/pkg/resources/warehouse_grant.go
index 9d90880a..bf54c978 100644
--- a/pkg/resources/warehouse_grant.go
+++ b/pkg/resources/warehouse_grant.go
@@ -68,6 +68,7 @@ func CreateWarehouseGrant(d *schema.ResourceData, meta interface{}) error {
 	priv := d.Get("privilege").(string)
 	grantOption := d.Get("with_grant_option").(bool)
 	builder := snowflake.WarehouseGrant(w)
+	roles := expandStringList(d.Get("roles").(*schema.Set).List())
 
 	err := createGenericGrant(d, meta, builder)
 	if err != nil {
@@ -78,6 +79,7 @@ func CreateWarehouseGrant(d *schema.ResourceData, meta interface{}) error {
 		ResourceName: w,
 		Privilege:    priv,
 		GrantOption:  grantOption,
+		Roles:        roles,
 	}
 	dataIDInput, err := grant.String()
 	if err != nil {


Test patch:
diff --git a/pkg/resources/account_grant_test.go b/pkg/resources/account_grant_test.go
index ad3ce13f..e65447b4 100644
--- a/pkg/resources/account_grant_test.go
+++ b/pkg/resources/account_grant_test.go
@@ -45,7 +45,7 @@ func TestAccountGrantCreate(t *testing.T) { //lintignore:AT003
 func TestAccountGrantRead(t *testing.T) {
 	r := require.New(t)
 
-	d := accountGrant(t, "ACCOUNT|||MANAGE GRANTS|true", map[string]interface{}{
+	d := accountGrant(t, "ACCOUNT|||MANAGE GRANTS||true", map[string]interface{}{
 		"privilege":         "MANAGE GRANTS",
 		"roles":             []interface{}{"test-role-1", "test-role-2"},
 		"with_grant_option": true,
@@ -63,7 +63,7 @@ func TestAccountGrantRead(t *testing.T) {
 func TestMonitorExecution(t *testing.T) {
 	r := require.New(t)
 
-	d := accountGrant(t, "ACCOUNT|||MONITOR EXECUTION|true", map[string]interface{}{
+	d := accountGrant(t, "ACCOUNT|||MONITOR EXECUTION||true", map[string]interface{}{
 		"privilege":         "MONITOR EXECUTION",
 		"roles":             []interface{}{"test-role-1", "test-role-2"},
 		"with_grant_option": true,
@@ -81,7 +81,7 @@ func TestMonitorExecution(t *testing.T) {
 func TestExecuteTask(t *testing.T) {
 	r := require.New(t)
 
-	d := accountGrant(t, "ACCOUNT|||EXECUTE TASK|false", map[string]interface{}{
+	d := accountGrant(t, "ACCOUNT|||EXECUTE TASK||false", map[string]interface{}{
 		"privilege":         "EXECUTE TASK",
 		"roles":             []interface{}{"test-role-1", "test-role-2"},
 		"with_grant_option": false,
@@ -110,7 +110,7 @@ func expectReadAccountGrant(mock sqlmock.Sqlmock) {
 func TestApplyMaskingPolicy(t *testing.T) {
 	r := require.New(t)
 
-	d := accountGrant(t, "ACCOUNT|||APPLY MASKING POLICY|true", map[string]interface{}{
+	d := accountGrant(t, "ACCOUNT|||APPLY MASKING POLICY||true", map[string]interface{}{
 		"privilege":         "APPLY MASKING POLICY",
 		"roles":             []interface{}{"test-role-1", "test-role-2"},
 		"with_grant_option": true,
diff --git a/pkg/resources/database_grant_test.go b/pkg/resources/database_grant_test.go
index f5316514..6abfa6e5 100644
--- a/pkg/resources/database_grant_test.go
+++ b/pkg/resources/database_grant_test.go
@@ -48,7 +48,7 @@ func TestDatabaseGrantCreate(t *testing.T) {
 func TestDatabaseGrantRead(t *testing.T) {
 	r := require.New(t)
 
-	d := databaseGrant(t, "test-database|||USAGE|false", map[string]interface{}{
+	d := databaseGrant(t, "test-database|||USAGE||false", map[string]interface{}{
 		"database_name":     "test-database",
 		"privilege":         "USAGE",
 		"roles":             []interface{}{},
diff --git a/pkg/resources/external_table_grant_test.go b/pkg/resources/external_table_grant_test.go
index e69ee1b6..900c13c4 100644
--- a/pkg/resources/external_table_grant_test.go
+++ b/pkg/resources/external_table_grant_test.go
@@ -48,7 +48,7 @@ func TestExternalTableGrantCreate(t *testing.T) {
 func TestExternalTableGrantRead(t *testing.T) {
 	r := require.New(t)
 
-	d := externalTableGrant(t, "test-db|PUBLIC|test-external-table|SELECT|false", map[string]interface{}{
+	d := externalTableGrant(t, "test-db|PUBLIC|test-external-table|SELECT||false", map[string]interface{}{
 		"external_table_name": "test-external-table",
 		"schema_name":         "PUBLIC",
 		"database_name":       "test-db",
diff --git a/pkg/resources/file_format_grant_test.go b/pkg/resources/file_format_grant_test.go
index e03587a6..0f6b09ec 100644
--- a/pkg/resources/file_format_grant_test.go
+++ b/pkg/resources/file_format_grant_test.go
@@ -45,7 +45,7 @@ func TestFileFormatGrantCreate(t *testing.T) {
 func TestFileFormatGrantRead(t *testing.T) {
 	r := require.New(t)
 
-	d := fileFormatGrant(t, "test-db|PUBLIC|test-file-format|USAGE|false", map[string]interface{}{
+	d := fileFormatGrant(t, "test-db|PUBLIC|test-file-format|USAGE||false", map[string]interface{}{
 		"file_format_name":  "test-file-format",
 		"schema_name":       "PUBLIC",
 		"database_name":     "test-db",
diff --git a/pkg/resources/function_grant_test.go b/pkg/resources/function_grant_test.go
index 273b34d2..85fbf023 100644
--- a/pkg/resources/function_grant_test.go
+++ b/pkg/resources/function_grant_test.go
@@ -56,7 +56,7 @@ func TestFunctionGrantCreate(t *testing.T) {
 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{}{
+	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",
diff --git a/pkg/resources/grant_helpers_internal_test.go b/pkg/resources/grant_helpers_internal_test.go
index aa4b0e77..f7c1b914 100644
--- a/pkg/resources/grant_helpers_internal_test.go
+++ b/pkg/resources/grant_helpers_internal_test.go
@@ -10,7 +10,7 @@ import (
 func TestGrantIDFromString(t *testing.T) {
 	r := require.New(t)
 	// Vanilla without GrantOption
-	id := "database_name|schema|view_name|privilege"
+	id := "database_name|schema|view_name|privilege|test1,test2"
 	grant, err := grantIDFromString(id)
 	r.NoError(err)
 
@@ -21,7 +21,7 @@ func TestGrantIDFromString(t *testing.T) {
 	r.Equal(false, grant.GrantOption)
 
 	// Vanilla with GrantOption
-	id = "database_name|schema|view_name|privilege|true"
+	id = "database_name|schema|view_name|privilege|test1,test2|true"
 	grant, err = grantIDFromString(id)
 	r.NoError(err)
 
@@ -44,17 +44,17 @@ func TestGrantIDFromString(t *testing.T) {
 	// Bad ID -- not enough fields
 	id = "database|name-privilege"
 	_, err = grantIDFromString(id)
-	r.Equal(fmt.Errorf("4 or 5 fields allowed"), err)
+	r.Equal(fmt.Errorf("5 or 6 fields allowed"), err)
 
 	// Bad ID -- privilege in wrong area
 	id = "database||name-privilege"
 	_, err = grantIDFromString(id)
-	r.Equal(fmt.Errorf("4 or 5 fields allowed"), err)
+	r.Equal(fmt.Errorf("5 or 6 fields allowed"), err)
 
 	// too many fields
-	id = "database_name|schema|view_name|privilege|false|2"
+	id = "database_name|schema|view_name|privilege|false|2|too-many"
 	_, err = grantIDFromString(id)
-	r.Equal(fmt.Errorf("4 or 5 fields allowed"), err)
+	r.Equal(fmt.Errorf("5 or 6 fields allowed"), err)
 
 	// 0 lines
 	id = ""
@@ -70,24 +70,24 @@ func TestGrantIDFromString(t *testing.T) {
 
 func TestGrantStruct(t *testing.T) {
 	r := require.New(t)
-
 	// Vanilla
 	grant := &grantID{
 		ResourceName: "database_name",
 		SchemaName:   "schema",
 		ObjectName:   "view_name",
 		Privilege:    "priv",
+		Roles:        []string{"test1", "test2"},
 		GrantOption:  true,
 	}
 	gID, err := grant.String()
 	r.NoError(err)
-	r.Equal("database_name|schema|view_name|priv|true", gID)
+	r.Equal("database_name|schema|view_name|priv|test1,test2|true", gID)
 
 	// Empty grant
 	grant = &grantID{}
 	gID, err = grant.String()
 	r.NoError(err)
-	r.Equal("||||false", gID)
+	r.Equal("|||||false", gID)
 
 	// Grant with extra delimiters
 	grant = &grantID{
@@ -95,6 +95,7 @@ func TestGrantStruct(t *testing.T) {
 		SchemaName:   "schema|name",
 		ObjectName:   "view|name",
 		Privilege:    "priv",
+		Roles:        []string{"test3", "test4"},
 		GrantOption:  false,
 	}
 	gID, err = grant.String()
@@ -105,5 +106,6 @@ func TestGrantStruct(t *testing.T) {
 	r.Equal("schema|name", newGrant.SchemaName)
 	r.Equal("view|name", newGrant.ObjectName)
 	r.Equal("priv", newGrant.Privilege)
+	r.Equal([]string{"test3", "test4"}, newGrant.Roles)
 	r.Equal(false, newGrant.GrantOption)
 }
diff --git a/pkg/resources/integration_grant_test.go b/pkg/resources/integration_grant_test.go
index 2e74f29c..5f0f18a8 100644
--- a/pkg/resources/integration_grant_test.go
+++ b/pkg/resources/integration_grant_test.go
@@ -45,7 +45,7 @@ func TestIntegrationGrantCreate(t *testing.T) {
 func TestIntegrationGrantRead(t *testing.T) {
 	r := require.New(t)
 
-	d := integrationGrant(t, "test-integration|||IMPORTED PRIVILIGES|false", map[string]interface{}{
+	d := integrationGrant(t, "test-integration|||IMPORTED PRIVILIGES||false", map[string]interface{}{
 		"integration_name":  "test-integration",
 		"privilege":         "IMPORTED PRIVILIGES",
 		"roles":             []interface{}{"test-role-1", "test-role-2"},
diff --git a/pkg/resources/masking_policy_grant_test.go b/pkg/resources/masking_policy_grant_test.go
index 1bb9a4a5..19bcff88 100644
--- a/pkg/resources/masking_policy_grant_test.go
+++ b/pkg/resources/masking_policy_grant_test.go
@@ -44,7 +44,7 @@ func TestMaskingPolicyGrantCreate(t *testing.T) {
 func TestMaskingPolicyGrantRead(t *testing.T) {
 	r := require.New(t)
 
-	d := maskingPolicyGrant(t, "test-db|PUBLIC|test-masking-policy|APPLY|false", map[string]interface{}{
+	d := maskingPolicyGrant(t, "test-db|PUBLIC|test-masking-policy|APPLY||false", map[string]interface{}{
 		"masking_policy_name": "test-masking-policy",
 		"schema_name":         "PUBLIC",
 		"database_name":       "test-db",
diff --git a/pkg/resources/materialized_view_grant_test.go b/pkg/resources/materialized_view_grant_test.go
index a0abb8ce..b7fc3287 100644
--- a/pkg/resources/materialized_view_grant_test.go
+++ b/pkg/resources/materialized_view_grant_test.go
@@ -48,7 +48,7 @@ func TestMaterializedViewGrantCreate(t *testing.T) {
 func TestMaterializedViewGrantRead(t *testing.T) {
 	r := require.New(t)
 
-	d := materializedViewGrant(t, "test-db|PUBLIC|test-materialized-view|SELECT|false", map[string]interface{}{
+	d := materializedViewGrant(t, "test-db|PUBLIC|test-materialized-view|SELECT||false", map[string]interface{}{
 		"materialized_view_name": "test-materialized-view",
 		"schema_name":            "PUBLIC",
 		"database_name":          "test-db",
diff --git a/pkg/resources/procedure_grant_test.go b/pkg/resources/procedure_grant_test.go
index 92d0aa7d..98a69b7c 100644
--- a/pkg/resources/procedure_grant_test.go
+++ b/pkg/resources/procedure_grant_test.go
@@ -56,7 +56,7 @@ func TestProcedureGrantCreate(t *testing.T) {
 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{}{
+	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",
diff --git a/pkg/resources/resource_monitor_grant_test.go b/pkg/resources/resource_monitor_grant_test.go
index b6096f0e..0fef299d 100644
--- a/pkg/resources/resource_monitor_grant_test.go
+++ b/pkg/resources/resource_monitor_grant_test.go
@@ -45,7 +45,7 @@ func TestResourceMonitorGrantCreate(t *testing.T) {
 func TestResourceMonitorGrantRead(t *testing.T) {
 	r := require.New(t)
 
-	d := resourceMonitorGrant(t, "test-monitor|||MONITOR|false", map[string]interface{}{
+	d := resourceMonitorGrant(t, "test-monitor|||MONITOR||false", map[string]interface{}{
 		"monitor_name":      "test-monitor",
 		"privilege":         "MONITOR",
 		"roles":             []interface{}{"test-role-1", "test-role-2"},
diff --git a/pkg/resources/role_grants_test.go b/pkg/resources/role_grants_test.go
index b131b76f..4d61abd5 100644
--- a/pkg/resources/role_grants_test.go
+++ b/pkg/resources/role_grants_test.go
@@ -56,7 +56,7 @@ func expectReadRoleGrants(mock sqlmock.Sqlmock) {
 func TestRoleGrantsRead(t *testing.T) {
 	r := require.New(t)
 
-	d := roleGrants(t, "good_name", map[string]interface{}{
+	d := roleGrants(t, "good_name||||role1,role2|false", map[string]interface{}{
 		"role_name": "good_name",
 		"roles":     []interface{}{"role1", "role2"},
 		"users":     []interface{}{"user1", "user2"},
@@ -74,7 +74,7 @@ func TestRoleGrantsRead(t *testing.T) {
 func TestRoleGrantsDelete(t *testing.T) {
 	r := require.New(t)
 
-	d := roleGrants(t, "drop_it", map[string]interface{}{
+	d := roleGrants(t, "drop_it||||role1,role2|false", map[string]interface{}{
 		"role_name": "drop_it",
 		"roles":     []interface{}{"role1", "role2"},
 		"users":     []interface{}{"user1", "user2"},
diff --git a/pkg/resources/row_access_policy_grant_test.go b/pkg/resources/row_access_policy_grant_test.go
index 88312256..2a82d43b 100644
--- a/pkg/resources/row_access_policy_grant_test.go
+++ b/pkg/resources/row_access_policy_grant_test.go
@@ -44,7 +44,7 @@ func TestRowAccessPolicyGrantCreate(t *testing.T) {
 func TestRowAccessPolicyGrantRead(t *testing.T) {
 	r := require.New(t)
 
-	d := rowAccessPolicyGrant(t, "test-db|PUBLIC|test-row-access-policy|APPLY|false", map[string]interface{}{
+	d := rowAccessPolicyGrant(t, "test-db|PUBLIC|test-row-access-policy|APPLY||false", map[string]interface{}{
 		"row_access_policy_name": "test-row-access-policy",
 		"schema_name":            "PUBLIC",
 		"database_name":          "test-db",
diff --git a/pkg/resources/schema_grant_test.go b/pkg/resources/schema_grant_test.go
index 13a6ceb9..3f236c0b 100644
--- a/pkg/resources/schema_grant_test.go
+++ b/pkg/resources/schema_grant_test.go
@@ -58,7 +58,7 @@ func TestSchemaGrantCreate(t *testing.T) {
 func TestSchemaGrantRead(t *testing.T) {
 	r := require.New(t)
 
-	d := schemaGrant(t, "test-db|test-schema||USAGE|false", map[string]interface{}{
+	d := schemaGrant(t, "test-db|test-schema||USAGE||false", map[string]interface{}{
 		"schema_name":       "test-schema",
 		"database_name":     "test-db",
 		"privilege":         "USAGE",
diff --git a/pkg/resources/sequence_grant_test.go b/pkg/resources/sequence_grant_test.go
index b98fc6be..2ad8025e 100644
--- a/pkg/resources/sequence_grant_test.go
+++ b/pkg/resources/sequence_grant_test.go
@@ -45,7 +45,7 @@ func TestSequenceGrantCreate(t *testing.T) {
 func TestSequenceGrantRead(t *testing.T) {
 	r := require.New(t)
 
-	d := sequenceGrant(t, "test-db|PUBLIC|test-sequence|USAGE|false", map[string]interface{}{
+	d := sequenceGrant(t, "test-db|PUBLIC|test-sequence|USAGE||false", map[string]interface{}{
 		"sequence_name":     "test-sequence",
 		"schema_name":       "PUBLIC",
 		"database_name":     "test-db",
diff --git a/pkg/resources/stage_grant_test.go b/pkg/resources/stage_grant_test.go
index ca592315..3c92bc5b 100644
--- a/pkg/resources/stage_grant_test.go
+++ b/pkg/resources/stage_grant_test.go
@@ -51,7 +51,7 @@ func TestStageGrantCreate(t *testing.T) {
 func TestStageGrantRead(t *testing.T) {
 	r := require.New(t)
 
-	d := stageGrant(t, "test-db|test-schema|test-stage|USAGE|false", map[string]interface{}{
+	d := stageGrant(t, "test-db|test-schema|test-stage|USAGE||false", map[string]interface{}{
 		"stage_name":        "test-stage",
 		"schema_name":       "test-schema",
 		"database_name":     "test-db",
diff --git a/pkg/resources/stream_grant_test.go b/pkg/resources/stream_grant_test.go
index 7b9e1514..c89d3f39 100644
--- a/pkg/resources/stream_grant_test.go
+++ b/pkg/resources/stream_grant_test.go
@@ -45,7 +45,7 @@ func TestStreamGrantCreate(t *testing.T) {
 func TestStreamGrantRead(t *testing.T) {
 	r := require.New(t)
 
-	d := streamGrant(t, "test-db|PUBLIC|test-stream|SELECT|false", map[string]interface{}{
+	d := streamGrant(t, "test-db|PUBLIC|test-stream|SELECT||false", map[string]interface{}{
 		"stream_name":       "test-stream",
 		"schema_name":       "PUBLIC",
 		"database_name":     "test-db",
diff --git a/pkg/resources/table_grant_test.go b/pkg/resources/table_grant_test.go
index 3a21d5ad..16e0dbc9 100644
--- a/pkg/resources/table_grant_test.go
+++ b/pkg/resources/table_grant_test.go
@@ -51,7 +51,7 @@ func TestTableGrantUpdate(t *testing.T) {
 	r := require.New(t)
 
 	// d := schema.TestResourceDataRaw(t, resources.TableGrant().Resource.Schema, in)
-	d := tableGrant(t, "test-db|PUBLIC|test-table|SELECT|false", map[string]interface{}{
+	d := tableGrant(t, "test-db|PUBLIC|test-table|SELECT||false", map[string]interface{}{
 		"table_name":    "test-table",
 		"schema_name":   "PUBLIC",
 		"database_name": "test-db",
diff --git a/pkg/resources/task_grant_test.go b/pkg/resources/task_grant_test.go
index dbfc3828..f5b75584 100644
--- a/pkg/resources/task_grant_test.go
+++ b/pkg/resources/task_grant_test.go
@@ -45,7 +45,7 @@ func TestTaskGrantCreate(t *testing.T) {
 func TestTaskGrantRead(t *testing.T) {
 	r := require.New(t)
 
-	d := taskGrant(t, "test-db|PUBLIC|test-task|OPERATE|false", map[string]interface{}{
+	d := taskGrant(t, "test-db|PUBLIC|test-task|OPERATE||false", map[string]interface{}{
 		"task_name":         "test-task",
 		"schema_name":       "PUBLIC",
 		"database_name":     "test-db",
diff --git a/pkg/resources/view_grant_test.go b/pkg/resources/view_grant_test.go
index 7f35e792..b40aa189 100644
--- a/pkg/resources/view_grant_test.go
+++ b/pkg/resources/view_grant_test.go
@@ -48,7 +48,7 @@ func TestViewGrantCreate(t *testing.T) {
 func TestViewGrantRead(t *testing.T) {
 	r := require.New(t)
 
-	d := viewGrant(t, "test-db|PUBLIC|test-view|SELECT|false", map[string]interface{}{
+	d := viewGrant(t, "test-db|PUBLIC|test-view|SELECT||false", map[string]interface{}{
 		"view_name":         "test-view",
 		"schema_name":       "PUBLIC",
 		"database_name":     "test-db",