Repository: googleapis/google-cloud-go
Pull request #5666 (2022-02-16 20:32:15)

feat(bigquery): support authorized datasets
This augments what you can express in a dataset ACL, and can include a
dataset with a set of allowed resource types.  In short, this allows for
simplifying authorized views by allowing an entry that says "all views
in this dataset are authorized" vs the current behavior, which requires
views to be enumerated individually.

Also addresses a gotcha in that client was plumbed all the way to the top
of the dataset metadata converter chain, which is needed as in Go references
like dataset and table retain a local reference to the client.

shollyman: Blocked on internal issue 211633697
shollyman: necessary discovery fixes present in google.golang.org/api v0.71.0 or later

Patch:
diff --git a/bigquery/dataset.go b/bigquery/dataset.go
index 7f9115a5c2..7c23b286e4 100644
--- a/bigquery/dataset.go
+++ b/bigquery/dataset.go
@@ -207,10 +207,10 @@ func (d *Dataset) Metadata(ctx context.Context) (md *DatasetMetadata, err error)
 	}); err != nil {
 		return nil, err
 	}
-	return bqToDatasetMetadata(ds)
+	return bqToDatasetMetadata(ds, d.c)
 }
 
-func bqToDatasetMetadata(d *bq.Dataset) (*DatasetMetadata, error) {
+func bqToDatasetMetadata(d *bq.Dataset, c *Client) (*DatasetMetadata, error) {
 	dm := &DatasetMetadata{
 		CreationTime:            unixMillisToTime(d.CreationTime),
 		LastModifiedTime:        unixMillisToTime(d.LastModifiedTime),
@@ -224,7 +224,7 @@ func bqToDatasetMetadata(d *bq.Dataset) (*DatasetMetadata, error) {
 		ETag:                    d.Etag,
 	}
 	for _, a := range d.Access {
-		e, err := bqToAccessEntry(a, nil)
+		e, err := bqToAccessEntry(a, c)
 		if err != nil {
 			return nil, err
 		}
@@ -257,7 +257,7 @@ func (d *Dataset) Update(ctx context.Context, dm DatasetMetadataToUpdate, etag s
 	}); err != nil {
 		return nil, err
 	}
-	return bqToDatasetMetadata(ds2)
+	return bqToDatasetMetadata(ds2, d.c)
 }
 
 func (dm *DatasetMetadataToUpdate) toBQ() (*bq.Dataset, error) {
@@ -658,11 +658,12 @@ func (it *DatasetIterator) fetch(pageSize int, pageToken string) (string, error)
 
 // An AccessEntry describes the permissions that an entity has on a dataset.
 type AccessEntry struct {
-	Role       AccessRole // The role of the entity
-	EntityType EntityType // The type of entity
-	Entity     string     // The entity (individual or group) granted access
-	View       *Table     // The view granted access (EntityType must be ViewEntity)
-	Routine    *Routine   // The routine granted access (only UDF currently supported)
+	Role       AccessRole          // The role of the entity
+	EntityType EntityType          // The type of entity
+	Entity     string              // The entity (individual or group) granted access
+	View       *Table              // The view granted access (EntityType must be ViewEntity)
+	Routine    *Routine            // The routine granted access (only UDF currently supported)
+	Dataset    *DatasetAccessEntry // The resources within a dataset granted access.
 }
 
 // AccessRole is the level of access to grant to a dataset.
@@ -703,6 +704,9 @@ const (
 
 	// RoutineEntity is a BigQuery routine, referencing a User Defined Function (UDF).
 	RoutineEntity
+
+	// DatasetEntity is BigQuery dataset, present in the access list.
+	DatasetEntity
 )
 
 func (e *AccessEntry) toBQ() (*bq.DatasetAccess, error) {
@@ -722,6 +726,8 @@ func (e *AccessEntry) toBQ() (*bq.DatasetAccess, error) {
 		q.IamMember = e.Entity
 	case RoutineEntity:
 		q.Routine = e.Routine.toBQ()
+	case DatasetEntity:
+		q.Dataset = e.Dataset.toBQ()
 	default:
 		return nil, fmt.Errorf("bigquery: unknown entity type %d", e.EntityType)
 	}
@@ -752,8 +758,48 @@ func bqToAccessEntry(q *bq.DatasetAccess, c *Client) (*AccessEntry, error) {
 	case q.Routine != nil:
 		e.Routine = c.DatasetInProject(q.Routine.ProjectId, q.Routine.DatasetId).Routine(q.Routine.RoutineId)
 		e.EntityType = RoutineEntity
+	case q.Dataset != nil:
+		e.Dataset = bqToDatasetAccessEntry(q.Dataset, c)
+		e.EntityType = DatasetEntity
 	default:
 		return nil, errors.New("bigquery: invalid access value")
 	}
 	return e, nil
 }
+
+// DatasetAccessEntry is an access entry that refers to resources within
+// another dataset.
+type DatasetAccessEntry struct {
+	// The dataset to which this entry applies.
+	Dataset *Dataset
+	// The list of target types within the dataset
+	// to which this entry applies.
+	//
+	// Current supported values:
+	//
+	// VIEWS - This entry applies to views in the dataset.
+	TargetTypes []string
+}
+
+func (dae *DatasetAccessEntry) toBQ() *bq.DatasetAccessEntry {
+	if dae == nil {
+		return nil
+	}
+	return &bq.DatasetAccessEntry{
+		Dataset: &bq.DatasetReference{
+			ProjectId: dae.Dataset.ProjectID,
+			DatasetId: dae.Dataset.DatasetID,
+		},
+		TargetTypes: dae.TargetTypes,
+	}
+}
+
+func bqToDatasetAccessEntry(entry *bq.DatasetAccessEntry, c *Client) *DatasetAccessEntry {
+	if entry == nil {
+		return nil
+	}
+	return &DatasetAccessEntry{
+		Dataset:     c.DatasetInProject(entry.Dataset.ProjectId, entry.Dataset.DatasetId),
+		TargetTypes: entry.TargetTypes,
+	}
+}
diff --git a/bigquery/go.mod b/bigquery/go.mod
index 26973b46f6..01ac715234 100644
--- a/bigquery/go.mod
+++ b/bigquery/go.mod
@@ -13,8 +13,8 @@ require (
 	go.opencensus.io v0.23.0
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
-	google.golang.org/api v0.70.0
-	google.golang.org/genproto v0.0.0-20220303160752-862486edd9cc
+	google.golang.org/api v0.71.0
+	google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8
 	google.golang.org/grpc v1.44.0
 	google.golang.org/protobuf v1.27.1
 )
diff --git a/bigquery/go.sum b/bigquery/go.sum
index c61b380366..b1f6ac37bc 100644
--- a/bigquery/go.sum
+++ b/bigquery/go.sum
@@ -37,8 +37,9 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g
 cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
 cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
 cloud.google.com/go/compute v1.2.0/go.mod h1:xlogom/6gr8RJGBe7nT2eGsQYAFUbbv8dbC29qE3Xmw=
-cloud.google.com/go/compute v1.3.0 h1:mPL/MzDDYHsh5tHRS9mhmhWlcgClCrCa6ApQCU6wnHI=
 cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
+cloud.google.com/go/compute v1.5.0 h1:b1zWmYuuHz7gO9kDcM/EpHGr06UgsYNRpNJzI2kFiLM=
+cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M=
 cloud.google.com/go/datacatalog v1.3.0 h1:3llKXv7cC1acsWjvWmG0NQQkYVSVgunMSfVk7h6zz8Q=
 cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0=
 cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
@@ -278,8 +279,9 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -295,8 +297,9 @@ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ
 golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg=
 golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg=
+golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -360,8 +363,9 @@ golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
 golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
+golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -468,8 +472,9 @@ google.golang.org/api v0.64.0/go.mod h1:931CdxA8Rm4t6zqTFGSsgwbAEZ2+GMYurbndwSim
 google.golang.org/api v0.66.0/go.mod h1:I1dmXYpX7HGwz/ejRxwQp2qj5bFAz93HiCU1C1oYd9M=
 google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
 google.golang.org/api v0.69.0/go.mod h1:boanBiw+h5c3s+tBPgEzLDRHfFLWV0qXxRHz3ws7C80=
-google.golang.org/api v0.70.0 h1:67zQnAE0T2rB0A3CwLSas0K+SbVzSxP+zTLkQLexeiw=
 google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
+google.golang.org/api v0.71.0 h1:SgWof18M8V2NylsX7bL4fM28j+nFdRopHZbdipaaw20=
+google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -548,8 +553,8 @@ google.golang.org/genproto v0.0.0-20220211171837-173942840c17/go.mod h1:kGP+zUP2
 google.golang.org/genproto v0.0.0-20220216160803-4663080d8bc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
 google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
 google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
-google.golang.org/genproto v0.0.0-20220303160752-862486edd9cc h1:fb/ViRpv3ln/LvbqZtTpoOd1YQDNH12gaGZreoSFovE=
-google.golang.org/genproto v0.0.0-20220303160752-862486edd9cc/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8 h1:U9V52f6rAgINH7kT+musA1qF8kWyVOxzF8eYuOVuFwQ=
+google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=


Test patch:
diff --git a/bigquery/dataset_test.go b/bigquery/dataset_test.go
index fb4d3ea2ee..1d49c68151 100644
--- a/bigquery/dataset_test.go
+++ b/bigquery/dataset_test.go
@@ -24,6 +24,7 @@ import (
 
 	"cloud.google.com/go/internal/testutil"
 	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-cmp/cmp/cmpopts"
 	bq "google.golang.org/api/bigquery/v2"
 	itest "google.golang.org/api/iterator/testing"
 )
@@ -310,6 +311,7 @@ func TestDatasets(t *testing.T) {
 }
 
 func TestDatasetToBQ(t *testing.T) {
+	testClient := &Client{projectID: "p"}
 	for _, test := range []struct {
 		in   *DatasetMetadata
 		want *bq.Dataset
@@ -325,7 +327,16 @@ func TestDatasetToBQ(t *testing.T) {
 			},
 			Location: "EU",
 			Labels:   map[string]string{"x": "y"},
-			Access:   []*AccessEntry{{Role: OwnerRole, Entity: "example.com", EntityType: DomainEntity}},
+			Access: []*AccessEntry{
+				{Role: OwnerRole, Entity: "example.com", EntityType: DomainEntity},
+				{
+					EntityType: DatasetEntity,
+					Dataset: &DatasetAccessEntry{
+						Dataset:     testClient.Dataset("otherdataset"),
+						TargetTypes: []string{"VIEWS"},
+					},
+				},
+			},
 		}, &bq.Dataset{
 			FriendlyName:             "name",
 			Description:              "desc",
@@ -335,15 +346,26 @@ func TestDatasetToBQ(t *testing.T) {
 			},
 			Location: "EU",
 			Labels:   map[string]string{"x": "y"},
-			Access:   []*bq.DatasetAccess{{Role: "OWNER", Domain: "example.com"}},
+			Access: []*bq.DatasetAccess{
+				{Role: "OWNER", Domain: "example.com"},
+				{
+					Dataset: &bq.DatasetAccessEntry{
+						Dataset: &bq.DatasetReference{
+							ProjectId: "p",
+							DatasetId: "otherdataset",
+						},
+						TargetTypes: []string{"VIEWS"},
+					},
+				},
+			},
 		}},
 	} {
 		got, err := test.in.toBQ()
 		if err != nil {
 			t.Fatal(err)
 		}
-		if !testutil.Equal(got, test.want) {
-			t.Errorf("%v:\ngot  %+v\nwant %+v", test.in, got, test.want)
+		if diff := testutil.Diff(got, test.want, cmp.AllowUnexported(Dataset{})); diff != "" {
+			t.Errorf("got=-, want=+:\n%s", diff)
 		}
 	}
 
@@ -362,6 +384,7 @@ func TestDatasetToBQ(t *testing.T) {
 }
 
 func TestBQToDatasetMetadata(t *testing.T) {
+	testClient := &Client{projectID: "p"}
 	cTime := time.Date(2017, 1, 26, 0, 0, 0, 0, time.Local)
 	cMillis := cTime.UnixNano() / 1e6
 	mTime := time.Date(2017, 10, 31, 0, 0, 0, 0, time.Local)
@@ -380,6 +403,15 @@ func TestBQToDatasetMetadata(t *testing.T) {
 		Access: []*bq.DatasetAccess{
 			{Role: "READER", UserByEmail: "joe@example.com"},
 			{Role: "WRITER", GroupByEmail: "users@example.com"},
+			{
+				Dataset: &bq.DatasetAccessEntry{
+					Dataset: &bq.DatasetReference{
+						ProjectId: "p",
+						DatasetId: "otherdataset",
+					},
+					TargetTypes: []string{"VIEWS"},
+				},
+			},
 		},
 		Etag: "etag",
 	}
@@ -397,14 +429,21 @@ func TestBQToDatasetMetadata(t *testing.T) {
 		Access: []*AccessEntry{
 			{Role: ReaderRole, Entity: "joe@example.com", EntityType: UserEmailEntity},
 			{Role: WriterRole, Entity: "users@example.com", EntityType: GroupEmailEntity},
+			{
+				EntityType: DatasetEntity,
+				Dataset: &DatasetAccessEntry{
+					Dataset:     testClient.Dataset("otherdataset"),
+					TargetTypes: []string{"VIEWS"},
+				},
+			},
 		},
 		ETag: "etag",
 	}
-	got, err := bqToDatasetMetadata(q)
+	got, err := bqToDatasetMetadata(q, client)
 	if err != nil {
 		t.Fatal(err)
 	}
-	if diff := testutil.Diff(got, want); diff != "" {
+	if diff := testutil.Diff(got, want, cmpopts.IgnoreUnexported(Dataset{})); diff != "" {
 		t.Errorf("-got, +want:\n%s", diff)
 	}
 }
diff --git a/bigquery/integration_test.go b/bigquery/integration_test.go
index 6e2e84a516..1c6f5c823e 100644
--- a/bigquery/integration_test.go
+++ b/bigquery/integration_test.go
@@ -56,6 +56,7 @@ var (
 	storageClient          *storage.Client
 	policyTagManagerClient *datacatalog.PolicyTagManagerClient
 	dataset                *Dataset
+	otherDataset           *Dataset
 	schema                 = Schema{
 		{Name: "name", Type: StringFieldType},
 		{Name: "nums", Type: IntegerFieldType, Repeated: true},
@@ -221,13 +222,22 @@ func initTestState(client *Client, t time.Time) func() {
 	Seed(t.UnixNano())
 
 	dataset = client.Dataset(datasetIDs.New())
+	otherDataset = client.Dataset(datasetIDs.New())
+
 	if err := dataset.Create(ctx, nil); err != nil {
 		log.Fatalf("creating dataset %s: %v", dataset.DatasetID, err)
 	}
+	if err := otherDataset.Create(ctx, nil); err != nil {
+		log.Fatalf("creating other dataset %s: %v", dataset.DatasetID, err)
+	}
+
 	return func() {
 		if err := dataset.DeleteWithContents(ctx); err != nil {
 			log.Printf("could not delete %s", dataset.DatasetID)
 		}
+		if err := otherDataset.DeleteWithContents(ctx); err != nil {
+			log.Printf("could not delete %s", dataset.DatasetID)
+		}
 	}
 }
 
@@ -955,6 +965,13 @@ func TestIntegration_DatasetUpdateAccess(t *testing.T) {
 			EntityType: RoutineEntity,
 			Routine:    routine,
 		},
+		{
+			EntityType: DatasetEntity,
+			Dataset: &DatasetAccessEntry{
+				Dataset:     otherDataset,
+				TargetTypes: []string{"VIEWS"},
+			},
+		},
 	}
 
 	newAccess := append(md.Access, newEntries...)
@@ -970,8 +987,8 @@ func TestIntegration_DatasetUpdateAccess(t *testing.T) {
 		}
 	}()
 
-	if diff := testutil.Diff(md.Access, newAccess, cmpopts.SortSlices(lessAccessEntries), cmpopts.IgnoreUnexported(Routine{})); diff != "" {
-		t.Fatalf("got=-, want=+:\n%s", diff)
+	if diff := testutil.Diff(md.Access, newAccess, cmpopts.SortSlices(lessAccessEntries), cmpopts.IgnoreUnexported(Routine{}, Dataset{})); diff != "" {
+		t.Errorf("got=-, want=+:\n%s", diff)
 	}
 }
 
@@ -998,6 +1015,12 @@ func lessAccessEntries(x, y *AccessEntry) bool {
 	if x.View == nil {
 		return y.View != nil
 	}
+	if x.Routine == nil {
+		return y.Routine == nil
+	}
+	if x.Dataset == nil {
+		return y.Dataset == nil
+	}
 	return false
 }