1// Copyright 2015 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package bigquery
16
17import (
18	"context"
19	"errors"
20	"fmt"
21	"time"
22
23	"cloud.google.com/go/internal/optional"
24	"cloud.google.com/go/internal/trace"
25	bq "google.golang.org/api/bigquery/v2"
26	"google.golang.org/api/iterator"
27)
28
29// Dataset is a reference to a BigQuery dataset.
30type Dataset struct {
31	ProjectID string
32	DatasetID string
33	c         *Client
34}
35
36// DatasetMetadata contains information about a BigQuery dataset.
37type DatasetMetadata struct {
38	// These fields can be set when creating a dataset.
39	Name                    string            // The user-friendly name for this dataset.
40	Description             string            // The user-friendly description of this dataset.
41	Location                string            // The geo location of the dataset.
42	DefaultTableExpiration  time.Duration     // The default expiration time for new tables.
43	Labels                  map[string]string // User-provided labels.
44	Access                  []*AccessEntry    // Access permissions.
45	DefaultEncryptionConfig *EncryptionConfig
46
47	// These fields are read-only.
48	CreationTime     time.Time
49	LastModifiedTime time.Time // When the dataset or any of its tables were modified.
50	FullID           string    // The full dataset ID in the form projectID:datasetID.
51
52	// ETag is the ETag obtained when reading metadata. Pass it to Dataset.Update to
53	// ensure that the metadata hasn't changed since it was read.
54	ETag string
55}
56
57// DatasetMetadataToUpdate is used when updating a dataset's metadata.
58// Only non-nil fields will be updated.
59type DatasetMetadataToUpdate struct {
60	Description optional.String // The user-friendly description of this table.
61	Name        optional.String // The user-friendly name for this dataset.
62
63	// DefaultTableExpiration is the default expiration time for new tables.
64	// If set to time.Duration(0), new tables never expire.
65	DefaultTableExpiration optional.Duration
66
67	// DefaultEncryptionConfig defines CMEK settings for new resources created
68	// in the dataset.
69	DefaultEncryptionConfig *EncryptionConfig
70
71	// The entire access list. It is not possible to replace individual entries.
72	Access []*AccessEntry
73
74	labelUpdater
75}
76
77// Dataset creates a handle to a BigQuery dataset in the client's project.
78func (c *Client) Dataset(id string) *Dataset {
79	return c.DatasetInProject(c.projectID, id)
80}
81
82// DatasetInProject creates a handle to a BigQuery dataset in the specified project.
83func (c *Client) DatasetInProject(projectID, datasetID string) *Dataset {
84	return &Dataset{
85		ProjectID: projectID,
86		DatasetID: datasetID,
87		c:         c,
88	}
89}
90
91// Create creates a dataset in the BigQuery service. An error will be returned if the
92// dataset already exists. Pass in a DatasetMetadata value to configure the dataset.
93func (d *Dataset) Create(ctx context.Context, md *DatasetMetadata) (err error) {
94	ctx = trace.StartSpan(ctx, "cloud.google.com/go/bigquery.Dataset.Create")
95	defer func() { trace.EndSpan(ctx, err) }()
96
97	ds, err := md.toBQ()
98	if err != nil {
99		return err
100	}
101	ds.DatasetReference = &bq.DatasetReference{DatasetId: d.DatasetID}
102	// Use Client.Location as a default.
103	if ds.Location == "" {
104		ds.Location = d.c.Location
105	}
106	call := d.c.bqs.Datasets.Insert(d.ProjectID, ds).Context(ctx)
107	setClientHeader(call.Header())
108	_, err = call.Do()
109	return err
110}
111
112func (dm *DatasetMetadata) toBQ() (*bq.Dataset, error) {
113	ds := &bq.Dataset{}
114	if dm == nil {
115		return ds, nil
116	}
117	ds.FriendlyName = dm.Name
118	ds.Description = dm.Description
119	ds.Location = dm.Location
120	ds.DefaultTableExpirationMs = int64(dm.DefaultTableExpiration / time.Millisecond)
121	ds.Labels = dm.Labels
122	var err error
123	ds.Access, err = accessListToBQ(dm.Access)
124	if err != nil {
125		return nil, err
126	}
127	if !dm.CreationTime.IsZero() {
128		return nil, errors.New("bigquery: Dataset.CreationTime is not writable")
129	}
130	if !dm.LastModifiedTime.IsZero() {
131		return nil, errors.New("bigquery: Dataset.LastModifiedTime is not writable")
132	}
133	if dm.FullID != "" {
134		return nil, errors.New("bigquery: Dataset.FullID is not writable")
135	}
136	if dm.ETag != "" {
137		return nil, errors.New("bigquery: Dataset.ETag is not writable")
138	}
139	if dm.DefaultEncryptionConfig != nil {
140		ds.DefaultEncryptionConfiguration = dm.DefaultEncryptionConfig.toBQ()
141	}
142	return ds, nil
143}
144
145func accessListToBQ(a []*AccessEntry) ([]*bq.DatasetAccess, error) {
146	var q []*bq.DatasetAccess
147	for _, e := range a {
148		a, err := e.toBQ()
149		if err != nil {
150			return nil, err
151		}
152		q = append(q, a)
153	}
154	return q, nil
155}
156
157// Delete deletes the dataset.  Delete will fail if the dataset is not empty.
158func (d *Dataset) Delete(ctx context.Context) (err error) {
159	return d.deleteInternal(ctx, false)
160}
161
162// DeleteWithContents deletes the dataset, as well as contained resources.
163func (d *Dataset) DeleteWithContents(ctx context.Context) (err error) {
164	return d.deleteInternal(ctx, true)
165}
166
167func (d *Dataset) deleteInternal(ctx context.Context, deleteContents bool) (err error) {
168	ctx = trace.StartSpan(ctx, "cloud.google.com/go/bigquery.Dataset.Delete")
169	defer func() { trace.EndSpan(ctx, err) }()
170
171	call := d.c.bqs.Datasets.Delete(d.ProjectID, d.DatasetID).Context(ctx).DeleteContents(deleteContents)
172	setClientHeader(call.Header())
173	return call.Do()
174}
175
176// Metadata fetches the metadata for the dataset.
177func (d *Dataset) Metadata(ctx context.Context) (md *DatasetMetadata, err error) {
178	ctx = trace.StartSpan(ctx, "cloud.google.com/go/bigquery.Dataset.Metadata")
179	defer func() { trace.EndSpan(ctx, err) }()
180
181	call := d.c.bqs.Datasets.Get(d.ProjectID, d.DatasetID).Context(ctx)
182	setClientHeader(call.Header())
183	var ds *bq.Dataset
184	if err := runWithRetry(ctx, func() (err error) {
185		ds, err = call.Do()
186		return err
187	}); err != nil {
188		return nil, err
189	}
190	return bqToDatasetMetadata(ds)
191}
192
193func bqToDatasetMetadata(d *bq.Dataset) (*DatasetMetadata, error) {
194	dm := &DatasetMetadata{
195		CreationTime:            unixMillisToTime(d.CreationTime),
196		LastModifiedTime:        unixMillisToTime(d.LastModifiedTime),
197		DefaultTableExpiration:  time.Duration(d.DefaultTableExpirationMs) * time.Millisecond,
198		DefaultEncryptionConfig: bqToEncryptionConfig(d.DefaultEncryptionConfiguration),
199		Description:             d.Description,
200		Name:                    d.FriendlyName,
201		FullID:                  d.Id,
202		Location:                d.Location,
203		Labels:                  d.Labels,
204		ETag:                    d.Etag,
205	}
206	for _, a := range d.Access {
207		e, err := bqToAccessEntry(a, nil)
208		if err != nil {
209			return nil, err
210		}
211		dm.Access = append(dm.Access, e)
212	}
213	return dm, nil
214}
215
216// Update modifies specific Dataset metadata fields.
217// To perform a read-modify-write that protects against intervening reads,
218// set the etag argument to the DatasetMetadata.ETag field from the read.
219// Pass the empty string for etag for a "blind write" that will always succeed.
220func (d *Dataset) Update(ctx context.Context, dm DatasetMetadataToUpdate, etag string) (md *DatasetMetadata, err error) {
221	ctx = trace.StartSpan(ctx, "cloud.google.com/go/bigquery.Dataset.Update")
222	defer func() { trace.EndSpan(ctx, err) }()
223
224	ds, err := dm.toBQ()
225	if err != nil {
226		return nil, err
227	}
228	call := d.c.bqs.Datasets.Patch(d.ProjectID, d.DatasetID, ds).Context(ctx)
229	setClientHeader(call.Header())
230	if etag != "" {
231		call.Header().Set("If-Match", etag)
232	}
233	var ds2 *bq.Dataset
234	if err := runWithRetry(ctx, func() (err error) {
235		ds2, err = call.Do()
236		return err
237	}); err != nil {
238		return nil, err
239	}
240	return bqToDatasetMetadata(ds2)
241}
242
243func (dm *DatasetMetadataToUpdate) toBQ() (*bq.Dataset, error) {
244	ds := &bq.Dataset{}
245	forceSend := func(field string) {
246		ds.ForceSendFields = append(ds.ForceSendFields, field)
247	}
248
249	if dm.Description != nil {
250		ds.Description = optional.ToString(dm.Description)
251		forceSend("Description")
252	}
253	if dm.Name != nil {
254		ds.FriendlyName = optional.ToString(dm.Name)
255		forceSend("FriendlyName")
256	}
257	if dm.DefaultTableExpiration != nil {
258		dur := optional.ToDuration(dm.DefaultTableExpiration)
259		if dur == 0 {
260			// Send a null to delete the field.
261			ds.NullFields = append(ds.NullFields, "DefaultTableExpirationMs")
262		} else {
263			ds.DefaultTableExpirationMs = int64(dur / time.Millisecond)
264		}
265	}
266	if dm.DefaultEncryptionConfig != nil {
267		ds.DefaultEncryptionConfiguration = dm.DefaultEncryptionConfig.toBQ()
268		ds.DefaultEncryptionConfiguration.ForceSendFields = []string{"KmsKeyName"}
269	}
270	if dm.Access != nil {
271		var err error
272		ds.Access, err = accessListToBQ(dm.Access)
273		if err != nil {
274			return nil, err
275		}
276		if len(ds.Access) == 0 {
277			ds.NullFields = append(ds.NullFields, "Access")
278		}
279	}
280	labels, forces, nulls := dm.update()
281	ds.Labels = labels
282	ds.ForceSendFields = append(ds.ForceSendFields, forces...)
283	ds.NullFields = append(ds.NullFields, nulls...)
284	return ds, nil
285}
286
287// Table creates a handle to a BigQuery table in the dataset.
288// To determine if a table exists, call Table.Metadata.
289// If the table does not already exist, use Table.Create to create it.
290func (d *Dataset) Table(tableID string) *Table {
291	return &Table{ProjectID: d.ProjectID, DatasetID: d.DatasetID, TableID: tableID, c: d.c}
292}
293
294// Tables returns an iterator over the tables in the Dataset.
295func (d *Dataset) Tables(ctx context.Context) *TableIterator {
296	it := &TableIterator{
297		ctx:     ctx,
298		dataset: d,
299	}
300	it.pageInfo, it.nextFunc = iterator.NewPageInfo(
301		it.fetch,
302		func() int { return len(it.tables) },
303		func() interface{} { b := it.tables; it.tables = nil; return b })
304	return it
305}
306
307// A TableIterator is an iterator over Tables.
308type TableIterator struct {
309	ctx      context.Context
310	dataset  *Dataset
311	tables   []*Table
312	pageInfo *iterator.PageInfo
313	nextFunc func() error
314}
315
316// Next returns the next result. Its second return value is Done if there are
317// no more results. Once Next returns Done, all subsequent calls will return
318// Done.
319func (it *TableIterator) Next() (*Table, error) {
320	if err := it.nextFunc(); err != nil {
321		return nil, err
322	}
323	t := it.tables[0]
324	it.tables = it.tables[1:]
325	return t, nil
326}
327
328// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
329func (it *TableIterator) PageInfo() *iterator.PageInfo { return it.pageInfo }
330
331// listTables exists to aid testing.
332var listTables = func(it *TableIterator, pageSize int, pageToken string) (*bq.TableList, error) {
333	call := it.dataset.c.bqs.Tables.List(it.dataset.ProjectID, it.dataset.DatasetID).
334		PageToken(pageToken).
335		Context(it.ctx)
336	setClientHeader(call.Header())
337	if pageSize > 0 {
338		call.MaxResults(int64(pageSize))
339	}
340	var res *bq.TableList
341	err := runWithRetry(it.ctx, func() (err error) {
342		res, err = call.Do()
343		return err
344	})
345	return res, err
346}
347
348func (it *TableIterator) fetch(pageSize int, pageToken string) (string, error) {
349	res, err := listTables(it, pageSize, pageToken)
350	if err != nil {
351		return "", err
352	}
353	for _, t := range res.Tables {
354		it.tables = append(it.tables, bqToTable(t.TableReference, it.dataset.c))
355	}
356	return res.NextPageToken, nil
357}
358
359func bqToTable(tr *bq.TableReference, c *Client) *Table {
360	if tr == nil {
361		return nil
362	}
363	return &Table{
364		ProjectID: tr.ProjectId,
365		DatasetID: tr.DatasetId,
366		TableID:   tr.TableId,
367		c:         c,
368	}
369}
370
371// Model creates a handle to a BigQuery model in the dataset.
372// To determine if a model exists, call Model.Metadata.
373// If the model does not already exist, you can create it via execution
374// of a CREATE MODEL query.
375func (d *Dataset) Model(modelID string) *Model {
376	return &Model{ProjectID: d.ProjectID, DatasetID: d.DatasetID, ModelID: modelID, c: d.c}
377}
378
379// Models returns an iterator over the models in the Dataset.
380func (d *Dataset) Models(ctx context.Context) *ModelIterator {
381	it := &ModelIterator{
382		ctx:     ctx,
383		dataset: d,
384	}
385	it.pageInfo, it.nextFunc = iterator.NewPageInfo(
386		it.fetch,
387		func() int { return len(it.models) },
388		func() interface{} { b := it.models; it.models = nil; return b })
389	return it
390}
391
392// A ModelIterator is an iterator over Models.
393type ModelIterator struct {
394	ctx      context.Context
395	dataset  *Dataset
396	models   []*Model
397	pageInfo *iterator.PageInfo
398	nextFunc func() error
399}
400
401// Next returns the next result. Its second return value is Done if there are
402// no more results. Once Next returns Done, all subsequent calls will return
403// Done.
404func (it *ModelIterator) Next() (*Model, error) {
405	if err := it.nextFunc(); err != nil {
406		return nil, err
407	}
408	t := it.models[0]
409	it.models = it.models[1:]
410	return t, nil
411}
412
413// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
414func (it *ModelIterator) PageInfo() *iterator.PageInfo { return it.pageInfo }
415
416// listTables exists to aid testing.
417var listModels = func(it *ModelIterator, pageSize int, pageToken string) (*bq.ListModelsResponse, error) {
418	call := it.dataset.c.bqs.Models.List(it.dataset.ProjectID, it.dataset.DatasetID).
419		PageToken(pageToken).
420		Context(it.ctx)
421	setClientHeader(call.Header())
422	if pageSize > 0 {
423		call.MaxResults(int64(pageSize))
424	}
425	var res *bq.ListModelsResponse
426	err := runWithRetry(it.ctx, func() (err error) {
427		res, err = call.Do()
428		return err
429	})
430	return res, err
431}
432
433func (it *ModelIterator) fetch(pageSize int, pageToken string) (string, error) {
434	res, err := listModels(it, pageSize, pageToken)
435	if err != nil {
436		return "", err
437	}
438	for _, t := range res.Models {
439		it.models = append(it.models, bqToModel(t.ModelReference, it.dataset.c))
440	}
441	return res.NextPageToken, nil
442}
443
444func bqToModel(mr *bq.ModelReference, c *Client) *Model {
445	if mr == nil {
446		return nil
447	}
448	return &Model{
449		ProjectID: mr.ProjectId,
450		DatasetID: mr.DatasetId,
451		ModelID:   mr.ModelId,
452		c:         c,
453	}
454}
455
456// Routine creates a handle to a BigQuery routine in the dataset.
457// To determine if a routine exists, call Routine.Metadata.
458func (d *Dataset) Routine(routineID string) *Routine {
459	return &Routine{
460		ProjectID: d.ProjectID,
461		DatasetID: d.DatasetID,
462		RoutineID: routineID,
463		c:         d.c}
464}
465
466// Routines returns an iterator over the routines in the Dataset.
467func (d *Dataset) Routines(ctx context.Context) *RoutineIterator {
468	it := &RoutineIterator{
469		ctx:     ctx,
470		dataset: d,
471	}
472	it.pageInfo, it.nextFunc = iterator.NewPageInfo(
473		it.fetch,
474		func() int { return len(it.routines) },
475		func() interface{} { b := it.routines; it.routines = nil; return b })
476	return it
477}
478
479// A RoutineIterator is an iterator over Routines.
480type RoutineIterator struct {
481	ctx      context.Context
482	dataset  *Dataset
483	routines []*Routine
484	pageInfo *iterator.PageInfo
485	nextFunc func() error
486}
487
488// Next returns the next result. Its second return value is Done if there are
489// no more results. Once Next returns Done, all subsequent calls will return
490// Done.
491func (it *RoutineIterator) Next() (*Routine, error) {
492	if err := it.nextFunc(); err != nil {
493		return nil, err
494	}
495	t := it.routines[0]
496	it.routines = it.routines[1:]
497	return t, nil
498}
499
500// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
501func (it *RoutineIterator) PageInfo() *iterator.PageInfo { return it.pageInfo }
502
503// listRoutines exists to aid testing.
504var listRoutines = func(it *RoutineIterator, pageSize int, pageToken string) (*bq.ListRoutinesResponse, error) {
505	call := it.dataset.c.bqs.Routines.List(it.dataset.ProjectID, it.dataset.DatasetID).
506		PageToken(pageToken).
507		Context(it.ctx)
508	setClientHeader(call.Header())
509	if pageSize > 0 {
510		call.MaxResults(int64(pageSize))
511	}
512	var res *bq.ListRoutinesResponse
513	err := runWithRetry(it.ctx, func() (err error) {
514		res, err = call.Do()
515		return err
516	})
517	return res, err
518}
519
520func (it *RoutineIterator) fetch(pageSize int, pageToken string) (string, error) {
521	res, err := listRoutines(it, pageSize, pageToken)
522	if err != nil {
523		return "", err
524	}
525	for _, t := range res.Routines {
526		it.routines = append(it.routines, bqToRoutine(t.RoutineReference, it.dataset.c))
527	}
528	return res.NextPageToken, nil
529}
530
531func bqToRoutine(mr *bq.RoutineReference, c *Client) *Routine {
532	if mr == nil {
533		return nil
534	}
535	return &Routine{
536		ProjectID: mr.ProjectId,
537		DatasetID: mr.DatasetId,
538		RoutineID: mr.RoutineId,
539		c:         c,
540	}
541}
542
543// Datasets returns an iterator over the datasets in a project.
544// The Client's project is used by default, but that can be
545// changed by setting ProjectID on the returned iterator before calling Next.
546func (c *Client) Datasets(ctx context.Context) *DatasetIterator {
547	return c.DatasetsInProject(ctx, c.projectID)
548}
549
550// DatasetsInProject returns an iterator over the datasets in the provided project.
551//
552// Deprecated: call Client.Datasets, then set ProjectID on the returned iterator.
553func (c *Client) DatasetsInProject(ctx context.Context, projectID string) *DatasetIterator {
554	it := &DatasetIterator{
555		ctx:       ctx,
556		c:         c,
557		ProjectID: projectID,
558	}
559	it.pageInfo, it.nextFunc = iterator.NewPageInfo(
560		it.fetch,
561		func() int { return len(it.items) },
562		func() interface{} { b := it.items; it.items = nil; return b })
563	return it
564}
565
566// DatasetIterator iterates over the datasets in a project.
567type DatasetIterator struct {
568	// ListHidden causes hidden datasets to be listed when set to true.
569	// Set before the first call to Next.
570	ListHidden bool
571
572	// Filter restricts the datasets returned by label. The filter syntax is described in
573	// https://cloud.google.com/bigquery/docs/labeling-datasets#filtering_datasets_using_labels
574	// Set before the first call to Next.
575	Filter string
576
577	// The project ID of the listed datasets.
578	// Set before the first call to Next.
579	ProjectID string
580
581	ctx      context.Context
582	c        *Client
583	pageInfo *iterator.PageInfo
584	nextFunc func() error
585	items    []*Dataset
586}
587
588// PageInfo supports pagination. See the google.golang.org/api/iterator package for details.
589func (it *DatasetIterator) PageInfo() *iterator.PageInfo { return it.pageInfo }
590
591// Next returns the next Dataset. Its second return value is iterator.Done if
592// there are no more results. Once Next returns Done, all subsequent calls will
593// return Done.
594func (it *DatasetIterator) Next() (*Dataset, error) {
595	if err := it.nextFunc(); err != nil {
596		return nil, err
597	}
598	item := it.items[0]
599	it.items = it.items[1:]
600	return item, nil
601}
602
603// for testing
604var listDatasets = func(it *DatasetIterator, pageSize int, pageToken string) (*bq.DatasetList, error) {
605	call := it.c.bqs.Datasets.List(it.ProjectID).
606		Context(it.ctx).
607		PageToken(pageToken).
608		All(it.ListHidden)
609	setClientHeader(call.Header())
610	if pageSize > 0 {
611		call.MaxResults(int64(pageSize))
612	}
613	if it.Filter != "" {
614		call.Filter(it.Filter)
615	}
616	var res *bq.DatasetList
617	err := runWithRetry(it.ctx, func() (err error) {
618		res, err = call.Do()
619		return err
620	})
621	return res, err
622}
623
624func (it *DatasetIterator) fetch(pageSize int, pageToken string) (string, error) {
625	res, err := listDatasets(it, pageSize, pageToken)
626	if err != nil {
627		return "", err
628	}
629	for _, d := range res.Datasets {
630		it.items = append(it.items, &Dataset{
631			ProjectID: d.DatasetReference.ProjectId,
632			DatasetID: d.DatasetReference.DatasetId,
633			c:         it.c,
634		})
635	}
636	return res.NextPageToken, nil
637}
638
639// An AccessEntry describes the permissions that an entity has on a dataset.
640type AccessEntry struct {
641	Role       AccessRole // The role of the entity
642	EntityType EntityType // The type of entity
643	Entity     string     // The entity (individual or group) granted access
644	View       *Table     // The view granted access (EntityType must be ViewEntity)
645}
646
647// AccessRole is the level of access to grant to a dataset.
648type AccessRole string
649
650const (
651	// OwnerRole is the OWNER AccessRole.
652	OwnerRole AccessRole = "OWNER"
653	// ReaderRole is the READER AccessRole.
654	ReaderRole AccessRole = "READER"
655	// WriterRole is the WRITER AccessRole.
656	WriterRole AccessRole = "WRITER"
657)
658
659// EntityType is the type of entity in an AccessEntry.
660type EntityType int
661
662const (
663	// DomainEntity is a domain (e.g. "example.com").
664	DomainEntity EntityType = iota + 1
665
666	// GroupEmailEntity is an email address of a Google Group.
667	GroupEmailEntity
668
669	// UserEmailEntity is an email address of an individual user.
670	UserEmailEntity
671
672	// SpecialGroupEntity is a special group: one of projectOwners, projectReaders, projectWriters or
673	// allAuthenticatedUsers.
674	SpecialGroupEntity
675
676	// ViewEntity is a BigQuery view.
677	ViewEntity
678
679	// IAMMemberEntity represents entities present in IAM but not represented using
680	// the other entity types.
681	IAMMemberEntity
682)
683
684func (e *AccessEntry) toBQ() (*bq.DatasetAccess, error) {
685	q := &bq.DatasetAccess{Role: string(e.Role)}
686	switch e.EntityType {
687	case DomainEntity:
688		q.Domain = e.Entity
689	case GroupEmailEntity:
690		q.GroupByEmail = e.Entity
691	case UserEmailEntity:
692		q.UserByEmail = e.Entity
693	case SpecialGroupEntity:
694		q.SpecialGroup = e.Entity
695	case ViewEntity:
696		q.View = e.View.toBQ()
697	case IAMMemberEntity:
698		q.IamMember = e.Entity
699	default:
700		return nil, fmt.Errorf("bigquery: unknown entity type %d", e.EntityType)
701	}
702	return q, nil
703}
704
705func bqToAccessEntry(q *bq.DatasetAccess, c *Client) (*AccessEntry, error) {
706	e := &AccessEntry{Role: AccessRole(q.Role)}
707	switch {
708	case q.Domain != "":
709		e.Entity = q.Domain
710		e.EntityType = DomainEntity
711	case q.GroupByEmail != "":
712		e.Entity = q.GroupByEmail
713		e.EntityType = GroupEmailEntity
714	case q.UserByEmail != "":
715		e.Entity = q.UserByEmail
716		e.EntityType = UserEmailEntity
717	case q.SpecialGroup != "":
718		e.Entity = q.SpecialGroup
719		e.EntityType = SpecialGroupEntity
720	case q.View != nil:
721		e.View = c.DatasetInProject(q.View.ProjectId, q.View.DatasetId).Table(q.View.TableId)
722		e.EntityType = ViewEntity
723	case q.IamMember != "":
724		e.Entity = q.IamMember
725		e.EntityType = IAMMemberEntity
726	default:
727		return nil, errors.New("bigquery: invalid access value")
728	}
729	return e, nil
730}
731