Skip to content
This repository has been archived by the owner on Jan 27, 2022. It is now read-only.

Commit

Permalink
bigquery: support update of Dataset.DefaultTableExpiration
Browse files Browse the repository at this point in the history
Allow users to change or delete the DefaultTableExpiration property
of a Dataset.

We have to distinguish "do not update" from "delete". To do so,
we make an optional type for Duration, interpret the default value (nil)
as "do not update", and treat a zero duration (otherwise illegal) as "delete".
Note that the bq command-line tool makes the same choice: you write

  bq update --default_table_expiration 0

to delete. We also do this in storage.ObjectHandle.Update, where we
have a Metadata field that is a map. If the map is nil, nothing
happens; if you set it to an empty map, the metadata is deleted.

Change-Id: I3ffdf580cd84c12ea2527d49ddd61dc0b04a53c7
Reviewed-on: https://code-review.googlesource.com/15570
Reviewed-by: kokoro <[email protected]>
Reviewed-by: Michael Darakananda <[email protected]>
  • Loading branch information
jba committed Aug 7, 2017
1 parent a8d7256 commit 9be7f82
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 6 deletions.
12 changes: 7 additions & 5 deletions bigquery/dataset.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ type Dataset struct {

type DatasetMetadata struct {
CreationTime time.Time
LastModifiedTime time.Time // When the dataset or any of its tables were modified.
DefaultTableExpiration time.Duration
Description string // The user-friendly description of this dataset.
Name string // The user-friendly name for this dataset.
LastModifiedTime time.Time // When the dataset or any of its tables were modified.
DefaultTableExpiration time.Duration // The default expiration time for new tables.
Description string // The user-friendly description of this dataset.
Name string // The user-friendly name for this dataset.
ID string
Location string // The geo location of the dataset.
Labels map[string]string // User-provided labels.
Expand All @@ -49,6 +49,9 @@ type DatasetMetadata struct {
type DatasetMetadataToUpdate struct {
Description optional.String // The user-friendly description of this table.
Name optional.String // The user-friendly name for this dataset.
// DefaultTableExpiration is the the default expiration time for new tables.
// If set to time.Duration(0), new tables never expire.
DefaultTableExpiration optional.Duration
}

// Dataset creates a handle to a BigQuery dataset in the client's project.
Expand Down Expand Up @@ -84,7 +87,6 @@ func (d *Dataset) Metadata(ctx context.Context) (*DatasetMetadata, error) {
// Update modifies specific Dataset metadata fields.
// To perform a read-modify-write that protects against intervening reads,
// set the etag argument to the DatasetMetadata.ETag field from the read.
// TODO(jba): describe errora
// Pass the empty string for etag for a "blind write" that will always succeed.
func (d *Dataset) Update(ctx context.Context, dm DatasetMetadataToUpdate, etag string) (*DatasetMetadata, error) {
return d.c.service.patchDataset(ctx, d.ProjectID, d.DatasetID, &dm, etag)
Expand Down
39 changes: 38 additions & 1 deletion bigquery/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ func TestIntegration_DatasetDelete(t *testing.T) {
}
}

func TestIntegration_DatasetUpdate(t *testing.T) {
func TestIntegration_DatasetUpdateETags(t *testing.T) {
if client == nil {
t.Skip("Integration tests skipped")
}
Expand Down Expand Up @@ -266,6 +266,43 @@ func TestIntegration_DatasetUpdate(t *testing.T) {
check(md3, "", "")
}

func TestIntegration_DatasetUpdateDefaultExpiration(t *testing.T) {
if client == nil {
t.Skip("Integration tests skipped")
}
ctx := context.Background()
md, err := dataset.Metadata(ctx)
if err != nil {
t.Fatal(err)
}
// Set the default expiration time.
md, err = dataset.Update(ctx,
DatasetMetadataToUpdate{DefaultTableExpiration: time.Hour}, "")
if err != nil {
t.Fatal(err)
}
if md.DefaultTableExpiration != time.Hour {
t.Fatalf("got %s, want 1h", md.DefaultTableExpiration)
}
// Omitting DefaultTableExpiration doesn't change it.
md, err = dataset.Update(ctx, DatasetMetadataToUpdate{Name: "xyz"}, "")
if err != nil {
t.Fatal(err)
}
if md.DefaultTableExpiration != time.Hour {
t.Fatalf("got %s, want 1h", md.DefaultTableExpiration)
}
// Setting it to 0 deletes it (which looks like a 0 duration).
md, err = dataset.Update(ctx,
DatasetMetadataToUpdate{DefaultTableExpiration: time.Duration(0)}, "")
if err != nil {
t.Fatal(err)
}
if md.DefaultTableExpiration != 0 {
t.Fatalf("got %s, want 0", md.DefaultTableExpiration)
}
}

func TestIntegration_Tables(t *testing.T) {
if client == nil {
t.Skip("Integration tests skipped")
Expand Down
9 changes: 9 additions & 0 deletions bigquery/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,15 @@ func (s *bigqueryService) patchDataset(ctx context.Context, projectID, datasetID
ds.FriendlyName = optional.ToString(dm.Name)
forceSend("FriendlyName")
}
if dm.DefaultTableExpiration != nil {
dur := optional.ToDuration(dm.DefaultTableExpiration)
if dur == 0 {
// Send a null to delete the field.
ds.NullFields = append(ds.NullFields, "DefaultTableExpirationMs")
} else {
ds.DefaultTableExpirationMs = int64(dur.Seconds() * 1000)
}
}
call := s.s.Datasets.Patch(projectID, datasetID, ds).Context(ctx)
setClientHeader(call.Header())
if etag != "" {
Expand Down
14 changes: 14 additions & 0 deletions internal/optional/optional.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package optional
import (
"fmt"
"strings"
"time"
)

type (
Expand All @@ -37,6 +38,9 @@ type (

// Float64 is either a float64 or nil.
Float64 interface{}

// Duration is either a time.Duration or nil.
Duration interface{}
)

// ToBool returns its argument as a bool.
Expand Down Expand Up @@ -89,6 +93,16 @@ func ToFloat64(v Float64) float64 {
return x
}

// ToDuration returns its argument as a time.Duration.
// It panics if its argument is nil or not a time.Duration.
func ToDuration(v Duration) time.Duration {
x, ok := v.(time.Duration)
if !ok {
doPanic("Duration", v)
}
return x
}

func doPanic(capType string, v interface{}) {
panic(fmt.Sprintf("optional.%s value should be %s, got %T", capType, strings.ToLower(capType), v))
}

0 comments on commit 9be7f82

Please sign in to comment.